2 * This file is part of OpenTTD.
3 * 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.
4 * 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.
5 * 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/>.
9 * @file viewport.cpp Handling of all viewports.
12 * The in-game coordinate system looks like this *
28 * @defgroup vp_column_row Rows and columns in the viewport
30 * Columns are vertical sections of the viewport that are half a tile wide.
31 * The origin, i.e. column 0, is through the northern and southern most tile.
32 * This means that the column of e.g. Tile(0, 0) and Tile(100, 100) are in
33 * column number 0. The negative columns are towards the left of the screen,
34 * or towards the west, whereas the positive ones are towards respectively
36 * With half a tile wide is meant that the next column of tiles directly west
37 * or east of the centre line are respectively column -1 and 1. Their tile
38 * centers are only half a tile from the center of their adjoining tile when
39 * looking only at the X-coordinate.
58 * Rows are horizontal sections of the viewport, also half a tile wide.
59 * This time the northern most tile on the map defines 0 and
60 * everything south of that has a positive number.
64 #include "clear_map.h"
67 #include "smallmap_gui.h"
68 #include "smallmap_colours.h"
69 #include "table/tree_land.h"
70 #include "blitter/32bpp_base.hpp"
71 #include "blitter/8bpp_simple.hpp"
72 #include "blitter/null.hpp"
73 #include "core/math_func.hpp"
74 #include "landscape.h"
75 #include "viewport_func.h"
76 #include "station_base.h"
77 #include "waypoint_base.h"
79 #include "signs_base.h"
80 #include "signs_func.h"
81 #include "plans_base.h"
82 #include "plans_func.h"
83 #include "vehicle_base.h"
84 #include "vehicle_gui.h"
85 #include "blitter/factory.hpp"
86 #include "strings_func.h"
87 #include "zoom_func.h"
88 #include "vehicle_func.h"
89 #include "company_func.h"
90 #include "waypoint_func.h"
91 #include "window_func.h"
92 #include "tilehighlight_func.h"
94 #include "window_gui.h"
95 #include "linkgraph/linkgraph_gui.h"
96 #include "viewport_kdtree.h"
97 #include "town_kdtree.h"
98 #include "viewport_sprite_sorter.h"
99 #include "bridge_map.h"
100 #include "company_base.h"
101 #include "command_func.h"
102 #include "network/network_func.h"
103 #include "framerate_type.h"
104 #include "depot_base.h"
105 #include "tunnelbridge_map.h"
107 #include "core/container_func.hpp"
108 #include "tunnelbridge_map.h"
109 #include "video/video_driver.hpp"
110 #include "scope_info.h"
112 #include "blitter/32bpp_base.hpp"
113 #include "object_map.h"
114 #include "newgrf_object.h"
115 #include "infrastructure_func.h"
116 #include "tracerestrict.h"
117 #include "worker_thread.h"
118 #include "vehiclelist.h"
119 #include "core/backup_type.hpp"
120 #include "3rdparty/robin_hood/robin_hood.h"
132 #include <condition_variable>
134 #include "table/strings.h"
135 #include "table/string_colours.h"
137 #include "safeguards.h"
139 Point _tile_fract_coords
;
141 #if defined(DEDICATED)
142 using Blitter_8bppDrawing
= Blitter_Null
;
144 using Blitter_8bppDrawing
= Blitter_8bppSimple
;
147 ViewportSignKdtree _viewport_sign_kdtree
{};
148 bool _viewport_sign_kdtree_valid
= false;
149 static int _viewport_sign_maxwidth
= 0;
152 //static const int MAX_TILE_EXTENT_LEFT = ZOOM_BASE * TILE_PIXELS; ///< Maximum left extent of tile relative to north corner.
153 //static const int MAX_TILE_EXTENT_RIGHT = ZOOM_BASE * TILE_PIXELS; ///< Maximum right extent of tile relative to north corner.
154 static const int MAX_TILE_EXTENT_TOP
= ZOOM_BASE
* MAX_BUILDING_PIXELS
; ///< Maximum top extent of tile relative to north corner (not considering bridges).
155 static const int MAX_TILE_EXTENT_BOTTOM
= ZOOM_BASE
* (TILE_PIXELS
+ 2 * TILE_HEIGHT
); ///< Maximum bottom extent of tile relative to north corner (worst case: #SLOPE_STEEP_N).
157 struct StringSpriteToDraw
{
166 struct TileSpriteToDraw
{
169 const SubSprite
*sub
; ///< only draw a rectangular part of the sprite
170 int32_t x
; ///< screen X coordinate of sprite
171 int32_t y
; ///< screen Y coordinate of sprite
174 struct ChildScreenSpriteToDraw
{
177 const SubSprite
*sub
; ///< only draw a rectangular part of the sprite
180 ChildScreenSpritePositionMode position_mode
;
181 int next
; ///< next child to draw (-1 at the end)
185 * Mode of "sprite combining"
186 * @see StartSpriteCombine
188 enum SpriteCombineMode
{
189 SPRITE_COMBINE_NONE
, ///< Every #AddSortableSpriteToDraw start its own bounding box
190 SPRITE_COMBINE_PENDING
, ///< %Sprite combining will start with the next unclipped sprite.
191 SPRITE_COMBINE_ACTIVE
, ///< %Sprite combining is active. #AddSortableSpriteToDraw outputs child sprites.
194 typedef std::vector
<TileSpriteToDraw
> TileSpriteToDrawVector
;
195 typedef std::vector
<StringSpriteToDraw
> StringSpriteToDrawVector
;
196 typedef std::vector
<ParentSpriteToDraw
> ParentSpriteToDrawVector
;
197 typedef std::vector
<ChildScreenSpriteToDraw
> ChildScreenSpriteToDrawVector
;
199 enum RouteStepOrderType
: uint8_t {
208 typedef std::vector
<std::pair
<uint16_t, RouteStepOrderType
>> RankOrderTypeList
;
209 typedef std::map
<TileIndex
, RankOrderTypeList
> RouteStepsMap
;
211 const uint max_rank_order_type_count
= 10;
220 * Snapping point for a track.
222 * Point where a track (rail/road/other) can be snapped to while selecting tracks with polyline
223 * tool (HT_POLY). Besides of x/y coordinates expressed in tile "units" it contains a set of
224 * allowed line directions.
226 struct LineSnapPoint
: Point
{
227 uint8_t dirs
; ///< Allowed line directions, set of #Direction bits.
230 typedef std::vector
<LineSnapPoint
> LineSnapPoints
; ///< Set of snapping points
232 /** Coordinates of a polyline track made of 2 connected line segments. */
233 struct PolylineInfo
{
234 Point start
; ///< The point where the first segment starts (as given in LineSnapPoint).
235 Direction first_dir
; ///< Direction of the first line segment.
236 uint first_len
; ///< size of the first segment - number of track pieces.
237 Direction second_dir
; ///< Direction of the second line segment.
238 uint second_len
; ///< size of the second segment - number of track pieces.
242 TunnelBridgeToMap tb
;
246 struct TunnelToMapStorage
{
247 std::vector
<TunnelToMap
> tunnels
;
250 struct BridgeSetXComparator
{
251 bool operator() (const TileIndex a
, const TileIndex b
) const
253 return std::make_tuple(TileX(a
), TileY(a
)) < std::make_tuple(TileX(b
), TileY(b
));
257 struct BridgeSetYComparator
{
258 bool operator() (const TileIndex a
, const TileIndex b
) const
264 using ChildStoreID
= uint32_t;
265 static constexpr ChildStoreID NO_CHILD_STORE
= UINT32_MAX
;
266 static constexpr ChildStoreID CHILD_SPRITE_STORE_TAG
= 1 << 31;
268 /** Data structure storing rendering information */
269 struct ViewportDrawer
{
270 TunnelToMapStorage tunnel_to_map_x
;
271 TunnelToMapStorage tunnel_to_map_y
;
273 ChildStoreID last_child
;
275 SpriteCombineMode combine_sprites
; ///< Current mode of "sprite combining". @see StartSpriteCombine
276 uint combine_psd_index
;
282 int foundation
[FOUNDATION_PART_END
]; ///< Foundation sprites (index into parent_sprites_to_draw).
283 FoundationPart foundation_part
; ///< Currently active foundation for ground sprite drawing.
284 ChildStoreID last_foundation_child
[FOUNDATION_PART_END
]; ///< Tail of ChildSprite list of the foundations. (index into child_screen_sprites_to_draw)
285 Point foundation_offset
[FOUNDATION_PART_END
]; ///< Pixel offset for ground sprites on the foundations.
287 static ViewportDrawer _vd
;
289 struct ViewportProcessParentSpritesData
{
291 ParentSpriteToSortVector psts
;
294 /** Data structure storing rendering information */
295 struct ViewportDrawerDynamic
{
300 StringSpriteToDrawVector string_sprites_to_draw
;
301 TileSpriteToDrawVector tile_sprites_to_draw
;
302 ParentSpriteToDrawVector parent_sprites_to_draw
;
303 std::vector
<ViewportProcessParentSpritesData
> parent_sprite_sets
;
304 ParentSpriteToDrawSubSpriteHolder parent_sprite_subsprites
;
305 ChildScreenSpriteToDrawVector child_screen_sprites_to_draw
;
306 btree::btree_map
<TileIndex
, TileIndex
, BridgeSetXComparator
> bridge_to_map_x
;
307 btree::btree_map
<TileIndex
, TileIndex
, BridgeSetYComparator
> bridge_to_map_y
;
309 uint8_t display_flags
;
311 std::atomic
<uint
> draw_jobs_active
;
313 TransparencyOptionBits transparency_opt
;
314 TransparencyOptionBits invisibility_opt
;
316 const uint8_t *pal2trsp_remap_ptr
= nullptr;
318 SpritePointerHolder sprite_data
;
320 inline bool IsTransparencySet(TransparencyOption to
)
322 return (HasBit(this->transparency_opt
, to
) && _game_mode
!= GM_MENU
);
325 inline bool IsInvisibilitySet(TransparencyOption to
)
327 return (HasBit(this->transparency_opt
& this->invisibility_opt
, to
) && _game_mode
!= GM_MENU
);
330 inline DrawPixelInfo
MakeDPIForText() const
332 DrawPixelInfo dpi_for_text
= this->dpi
;
333 dpi_for_text
.left
= UnScaleByZoom(this->dpi
.left
, this->dpi
.zoom
);
334 dpi_for_text
.top
= UnScaleByZoom(this->dpi
.top
, this->dpi
.zoom
);
335 dpi_for_text
.width
= UnScaleByZoom(this->dpi
.width
, this->dpi
.zoom
);
336 dpi_for_text
.height
= UnScaleByZoom(this->dpi
.height
, this->dpi
.zoom
);
337 dpi_for_text
.zoom
= ZOOM_LVL_MIN
;
341 inline void SetChild(ChildStoreID store_index
, int child
)
343 if ((store_index
& CHILD_SPRITE_STORE_TAG
) != 0) {
344 this->child_screen_sprites_to_draw
[store_index
& ~CHILD_SPRITE_STORE_TAG
].next
= child
;
346 this->parent_sprites_to_draw
[store_index
].first_child
= child
;
351 static void MarkRouteStepDirty(RouteStepsMap::const_iterator cit
);
352 static void MarkRouteStepDirty(const TileIndex tile
, uint order_nr
);
353 static void HideMeasurementTooltips();
354 static void ViewportDrawPlans(const Viewport
*vp
, Blitter
*blitter
, DrawPixelInfo
*plan_dpi
);
356 static std::unique_ptr
<ViewportDrawerDynamic
> _vdd
;
357 std::vector
<std::unique_ptr
<ViewportDrawerDynamic
>> _spare_viewport_drawers
;
359 struct ViewportDrawerReturn
{
361 std::unique_ptr
<ViewportDrawerDynamic
> vdd
;
363 static std::mutex _viewport_drawer_return_lock
;
364 static std::vector
<ViewportDrawerReturn
> _viewport_drawer_returns
;
365 static std::condition_variable _viewport_drawer_empty_cv
;
366 static uint _viewport_drawer_jobs
= 0;
368 static std::vector
<Viewport
*> _viewport_window_cache
;
369 static std::vector
<Rect
> _viewport_coverage_rects
;
370 std::vector
<Rect
> _viewport_vehicle_normal_redraw_rects
;
371 std::vector
<Rect
> _viewport_vehicle_map_redraw_rects
;
373 uint _vp_route_step_sprite_width
= 0;
374 uint _vp_route_step_base_width
= 0;
375 uint _vp_route_step_height_top
= 0;
376 uint _vp_route_step_height_bottom
= 0;
377 uint _vp_route_step_string_width
[4] = {};
379 struct DrawnPathRouteTileLine
{
382 bool order_conditional
;
384 bool operator==(const DrawnPathRouteTileLine
&other
) const
386 return std::tie(this->from_tile
, this->to_tile
, this->order_conditional
) == std::tie(other
.from_tile
, other
.to_tile
, other
.order_conditional
);
389 bool operator!=(const DrawnPathRouteTileLine
&other
) const
391 return !(*this == other
);
394 bool operator<(const DrawnPathRouteTileLine
&other
) const
396 return std::tie(this->from_tile
, this->to_tile
, this->order_conditional
) < std::tie(other
.from_tile
, other
.to_tile
, other
.order_conditional
);
400 struct ViewportRouteOverlay
{
402 RouteStepsMap route_steps
;
403 RouteStepsMap route_steps_last_mark_dirty
;
404 std::vector
<DrawnPathRouteTileLine
> route_paths
;
405 std::vector
<DrawnPathRouteTileLine
> route_paths_last_mark_dirty
;
407 struct PrepareRouteStepState
{
408 robin_hood::unordered_flat_set
<const Order
*> visited
;
412 inline void reset(TileIndex from_tile
)
414 this->visited
.clear();
415 this->lines_added
= 0;
416 this->from_tile
= from_tile
;
420 void PrepareRoutePathsConditionalOrder(const Vehicle
*veh
, const Order
*order
, PrepareRouteStepState
&state
, bool conditional
, uint depth
);
421 void PrepareRouteSteps(const Vehicle
*veh
);
422 void PrepareRoutePaths(const Vehicle
*veh
);
423 void PrepareRouteStepsAndMarkDirtyIfChanged(const Vehicle
*veh
);
424 void PrepareRoutePathsAndMarkDirtyIfChanged(const Vehicle
*veh
);
427 void PrepareRouteAndMarkDirtyIfChanged(const Vehicle
*veh
);
428 void DrawVehicleRouteSteps(const Viewport
*vp
);
429 void DrawVehicleRoutePath(const Viewport
*vp
, ViewportDrawerDynamic
*vdd
);
431 inline bool HasVehicleRouteSteps() const { return !this->route_steps
.empty(); }
433 static ViewportRouteOverlay _vp_focused_window_route_overlay
;
435 struct FixedVehicleViewportRouteOverlay
: public ViewportRouteOverlay
{
437 bool enabled
= false;
439 static std::vector
<FixedVehicleViewportRouteOverlay
> _vp_fixed_route_overlays
;
441 static void MarkRoutePathsDirty(const std::vector
<DrawnPathRouteTileLine
> &lines
);
443 TileHighlightData _thd
;
444 static TileInfo _cur_ti
;
445 bool _draw_bounding_boxes
= false;
446 bool _draw_dirty_blocks
= false;
447 std::atomic
<uint
> _dirty_block_colour
;
448 static VpSpriteSorter _vp_sprite_sorter
= nullptr;
450 const uint8_t *_pal2trsp_remap_ptr
= nullptr;
452 static RailSnapMode _rail_snap_mode
= RSM_NO_SNAP
; ///< Type of rail track snapping (polyline tool).
453 static LineSnapPoints _tile_snap_points
; ///< Tile to which a rail track will be snapped to (polyline tool).
454 static LineSnapPoints _rail_snap_points
; ///< Set of points where a rail track will be snapped to (polyline tool).
455 static LineSnapPoint _current_snap_lock
; ///< Start point and direction at which selected track is locked on currently (while dragging in polyline mode).
457 static RailSnapMode
GetRailSnapMode();
458 static void SetRailSnapMode(RailSnapMode mode
);
459 static TileIndex
GetRailSnapTile();
460 static void SetRailSnapTile(TileIndex tile
);
462 enum ViewportDebugFlags
{
463 VDF_DIRTY_BLOCK_PER_DRAW
,
464 VDF_DIRTY_WHOLE_VIEWPORT
,
465 VDF_DIRTY_BLOCK_PER_SPLIT
,
466 VDF_DISABLE_DRAW_SPLIT
,
467 VDF_SHOW_NO_LANDSCAPE_MAP_DRAW
,
468 VDF_DISABLE_LANDSCAPE_CACHE
,
471 uint32_t _viewport_debug_flags
;
473 static Point
MapXYZToViewport(const Viewport
*vp
, int x
, int y
, int z
)
475 Point p
= RemapCoords(x
, y
, z
);
476 p
.x
-= vp
->virtual_width
/ 2;
477 p
.y
-= vp
->virtual_height
/ 2;
481 static void FillViewportCoverageRect()
483 _viewport_coverage_rects
.resize(_viewport_window_cache
.size());
484 _viewport_vehicle_normal_redraw_rects
.clear();
485 _viewport_vehicle_map_redraw_rects
.clear();
487 for (uint i
= 0; i
< _viewport_window_cache
.size(); i
++) {
488 const Viewport
*vp
= _viewport_window_cache
[i
];
489 Rect
&r
= _viewport_coverage_rects
[i
];
490 r
.left
= vp
->virtual_left
;
491 r
.top
= vp
->virtual_top
;
492 r
.right
= vp
->virtual_left
+ vp
->virtual_width
+ (1 << vp
->zoom
) - 1;
493 r
.bottom
= vp
->virtual_top
+ vp
->virtual_height
+ (1 << vp
->zoom
) - 1;
495 if (vp
->zoom
>= ZOOM_LVL_DRAW_MAP
) {
496 _viewport_vehicle_map_redraw_rects
.push_back(r
);
498 _viewport_vehicle_normal_redraw_rects
.push_back({
499 r
.left
- (MAX_VEHICLE_PIXEL_X
* ZOOM_BASE
),
500 r
.top
- (MAX_VEHICLE_PIXEL_Y
* ZOOM_BASE
),
501 r
.right
+ (MAX_VEHICLE_PIXEL_X
* ZOOM_BASE
),
502 r
.bottom
+ (MAX_VEHICLE_PIXEL_Y
* ZOOM_BASE
),
508 using ScrollViewportPixelCacheGenericFillRegion
= void (*)(Viewport
*vp
, int x
, int y
, int width
, int height
);
510 static bool ScrollViewportPixelCacheGeneric(Viewport
*vp
, std::vector
<uint8_t> &cache
, int offset_x
, int offset_y
, uint pixel_width
, ScrollViewportPixelCacheGenericFillRegion fill_region
)
512 if (cache
.empty()) return false;
513 if (abs(offset_x
) >= vp
->width
|| abs(offset_y
) >= vp
->height
) return true;
515 int width
= vp
->width
* pixel_width
;
516 offset_x
*= pixel_width
;
518 int height
= vp
->height
;
520 /* Blitter_8bppDrawing::ScrollBuffer can be used on 32 bit buffers if widths and offsets are suitably adjusted */
521 const int pitch
= width
;
522 Blitter_8bppDrawing
blitter(&pitch
);
523 blitter
.ScrollBuffer(cache
.data(), 0, 0, width
, height
, offset_x
, offset_y
);
525 auto fill_rect
= [&](int x
, int y
, int w
, int h
) {
526 blitter
.DrawRectAt(cache
.data(), x
, y
, w
, h
, 0xD7);
527 if (fill_region
!= nullptr) fill_region(vp
, x
, y
, w
, h
);
532 /* scrolling right, moving pixels left, fill in on right */
534 fill_rect(width
, 0, -offset_x
, height
);
535 } else if (offset_x
> 0) {
536 /* scrolling left, moving pixels right, fill in on left */
537 fill_rect(0, 0, offset_x
, height
);
542 /* scrolling down, moving pixels up, fill in at bottom */
544 fill_rect(x
, height
, width
, -offset_y
);
545 } else if (offset_y
> 0) {
546 /* scrolling up, moving pixels down, fill in at top */
547 fill_rect(x
, 0, width
, offset_y
);
552 void ClearViewportLandPixelCache(Viewport
*vp
)
554 vp
->land_pixel_cache
.assign(vp
->land_pixel_cache
.size(), 0xD7);
557 static void ScrollViewportLandPixelCache(Viewport
*vp
, int offset_x
, int offset_y
)
559 bool clear
= ScrollViewportPixelCacheGeneric(vp
, vp
->land_pixel_cache
, offset_x
, offset_y
, BlitterFactory::GetCurrentBlitter()->GetScreenDepth() / 8, nullptr);
560 if (clear
) ClearViewportLandPixelCache(vp
);
563 static void ClearViewportPlanPixelCache(Viewport
*vp
)
565 vp
->plan_pixel_cache
.clear();
566 vp
->last_plan_update_number
= 0;
569 static void ScrollPlanPixelCache(Viewport
*vp
, int offset_x
, int offset_y
)
571 if (vp
->last_plan_update_number
!= _plan_update_counter
) {
572 ClearViewportPlanPixelCache(vp
);
575 bool clear
= ScrollViewportPixelCacheGeneric(vp
, vp
->plan_pixel_cache
, offset_x
, offset_y
, 1, [](Viewport
*vp
, int x
, int y
, int width
, int height
) {
576 DrawPixelInfo plan_dpi
;
577 plan_dpi
.dst_ptr
= vp
->plan_pixel_cache
.data() + x
+ (y
* vp
->width
);
578 plan_dpi
.height
= height
;
579 plan_dpi
.width
= width
;
580 plan_dpi
.pitch
= vp
->width
;
581 plan_dpi
.zoom
= ZOOM_LVL_MIN
;
582 plan_dpi
.left
= UnScaleByZoomLower(vp
->virtual_left
, vp
->zoom
) + x
;
583 plan_dpi
.top
= UnScaleByZoomLower(vp
->virtual_top
, vp
->zoom
) + y
;
585 const int pitch
= vp
->width
;
586 Blitter_8bppDrawing
blitter(&pitch
);
587 ViewportDrawPlans(vp
, &blitter
, &plan_dpi
);
589 if (clear
) ClearViewportPlanPixelCache(vp
);
592 static void ScrollOrInvalidateOverlayPixelCache(Viewport
*vp
, int offset_x
, int offset_y
)
594 if (vp
->overlay_pixel_cache
.empty()) return;
596 if (vp
->zoom
< ZOOM_LVL_DRAW_MAP
|| vp
->last_overlay_rebuild_counter
!= vp
->overlay
->GetRebuildCounter()) {
597 vp
->overlay_pixel_cache
.clear();
601 bool clear
= ScrollViewportPixelCacheGeneric(vp
, vp
->overlay_pixel_cache
, offset_x
, offset_y
, 1, [](Viewport
*vp
, int x
, int y
, int width
, int height
) {
602 DrawPixelInfo overlay_dpi
;
603 overlay_dpi
.dst_ptr
= vp
->overlay_pixel_cache
.data() + x
+ (y
* vp
->width
);
604 overlay_dpi
.height
= height
;
605 overlay_dpi
.width
= width
;
606 overlay_dpi
.pitch
= vp
->width
;
607 overlay_dpi
.zoom
= ZOOM_LVL_MIN
;
608 overlay_dpi
.left
= UnScaleByZoomLower(vp
->virtual_left
, vp
->zoom
) + x
;
609 overlay_dpi
.top
= UnScaleByZoomLower(vp
->virtual_top
, vp
->zoom
) + y
;
611 const int pitch
= vp
->width
;
612 Blitter_8bppDrawing
blitter(&pitch
);
613 vp
->overlay
->Draw(&blitter
, &overlay_dpi
);
615 if (clear
) vp
->overlay_pixel_cache
.clear();
618 void ClearViewportCache(Viewport
*vp
)
620 if (vp
->zoom
>= ZOOM_LVL_DRAW_MAP
) {
621 memset(vp
->map_draw_vehicles_cache
.done_hash_bits
, 0, sizeof(vp
->map_draw_vehicles_cache
.done_hash_bits
));
622 if (!vp
->map_draw_vehicles_cache
.vehicle_pixels
.empty()) {
623 MemSetT(vp
->map_draw_vehicles_cache
.vehicle_pixels
.data(), 0, vp
->map_draw_vehicles_cache
.vehicle_pixels
.size());
628 void ClearViewportCaches()
630 for (Viewport
*vp
: _viewport_window_cache
) {
631 ClearViewportCache(vp
);
633 if (unlikely(HasBit(_viewport_debug_flags
, VDF_DISABLE_LANDSCAPE_CACHE
))) {
634 for (Viewport
*vp
: _viewport_window_cache
) {
635 ClearViewportLandPixelCache(vp
);
640 void DeleteWindowViewport(Window
*w
)
642 if (w
->viewport
== nullptr) return;
644 container_unordered_remove(_viewport_window_cache
, w
->viewport
);
645 delete w
->viewport
->overlay
;
647 w
->viewport
= nullptr;
648 FillViewportCoverageRect();
652 * Initialize viewport of the window for use.
653 * @param w Window to use/display the viewport in
654 * @param x Offset of left edge of viewport with respect to left edge window \a w
655 * @param y Offset of top edge of viewport with respect to top edge window \a w
656 * @param width Width of the viewport
657 * @param height Height of the viewport
658 * @param follow_flags Flags controlling the viewport.
659 * - If bit 31 is set, the lower 20 bits are the vehicle that the viewport should follow.
660 * - If bit 31 is clear, it is a #TileIndex.
661 * @param zoom Zoomlevel to display
663 void InitializeWindowViewport(Window
*w
, int x
, int y
,
664 int width
, int height
, uint32_t follow_flags
, ZoomLevel zoom
)
666 assert(w
->viewport
== nullptr);
668 ViewportData
*vp
= new ViewportData();
670 vp
->overlay
= nullptr;
671 vp
->left
= x
+ w
->left
;
672 vp
->top
= y
+ w
->top
;
676 vp
->zoom
= static_cast<ZoomLevel
>(Clamp(zoom
, _settings_client
.gui
.zoom_min
, _settings_client
.gui
.zoom_max
));
678 vp
->virtual_left
= 0;
680 vp
->virtual_width
= ScaleByZoom(width
, vp
->zoom
);
681 vp
->virtual_height
= ScaleByZoom(height
, vp
->zoom
);
683 vp
->map_type
= VPMT_BEGIN
;
685 UpdateViewportSizeZoom(vp
);
689 if (follow_flags
& 0x80000000) {
692 vp
->follow_vehicle
= (VehicleID
)(follow_flags
& 0xFFFFF);
693 veh
= Vehicle::Get(vp
->follow_vehicle
);
694 pt
= MapXYZToViewport(vp
, veh
->x_pos
, veh
->y_pos
, veh
->z_pos
);
696 x
= TileX(follow_flags
) * TILE_SIZE
;
697 y
= TileY(follow_flags
) * TILE_SIZE
;
699 vp
->follow_vehicle
= INVALID_VEHICLE
;
700 pt
= MapXYZToViewport(vp
, x
, y
, GetSlopePixelZ(x
, y
));
703 vp
->scrollpos_x
= pt
.x
;
704 vp
->scrollpos_y
= pt
.y
;
705 vp
->dest_scrollpos_x
= pt
.x
;
706 vp
->dest_scrollpos_y
= pt
.y
;
709 _viewport_window_cache
.push_back(vp
);
710 FillViewportCoverageRect();
713 struct ViewportRedrawRegion
{
717 static std::vector
<ViewportRedrawRegion
> _vp_redraw_regions
;
719 static void DoViewportRedrawRegions(const Window
*w_start
, int left
, int top
, int width
, int height
)
721 if (width
<= 0 || height
<= 0) return;
723 for (const Window
*w
: Window::IterateFromBack
<const Window
>(w_start
)) {
724 if (left
+ width
> w
->left
&&
725 w
->left
+ w
->width
> left
&&
726 top
+ height
> w
->top
&&
727 w
->top
+ w
->height
> top
) {
729 if (left
< w
->left
) {
730 DoViewportRedrawRegions(w
, left
, top
, w
->left
- left
, height
);
731 DoViewportRedrawRegions(w
, left
+ (w
->left
- left
), top
, width
- (w
->left
- left
), height
);
735 if (left
+ width
> w
->left
+ w
->width
) {
736 DoViewportRedrawRegions(w
, left
, top
, (w
->left
+ w
->width
- left
), height
);
737 DoViewportRedrawRegions(w
, left
+ (w
->left
+ w
->width
- left
), top
, width
- (w
->left
+ w
->width
- left
), height
);
742 DoViewportRedrawRegions(w
, left
, top
, width
, (w
->top
- top
));
743 DoViewportRedrawRegions(w
, left
, top
+ (w
->top
- top
), width
, height
- (w
->top
- top
));
747 if (top
+ height
> w
->top
+ w
->height
) {
748 DoViewportRedrawRegions(w
, left
, top
, width
, (w
->top
+ w
->height
- top
));
749 DoViewportRedrawRegions(w
, left
, top
+ (w
->top
+ w
->height
- top
), width
, height
- (w
->top
+ w
->height
- top
));
757 _vp_redraw_regions
.push_back({ { left
, top
, left
+ width
, top
+ height
} });
760 static void DoSetViewportPositionFillRegion(int left
, int top
, int width
, int height
, int xo
, int yo
) {
761 int src_left
= left
- xo
;
762 int src_top
= top
- yo
;
763 int src_right
= src_left
+ width
;
764 int src_bottom
= src_top
+ height
;
765 for (const auto ®ion
: _vp_redraw_regions
) {
766 if (region
.coords
.left
< src_right
&&
767 region
.coords
.right
> src_left
&&
768 region
.coords
.top
< src_bottom
&&
769 region
.coords
.bottom
> src_top
) {
770 /* can use this region as a source */
771 if (src_left
< region
.coords
.left
) {
772 DoSetViewportPositionFillRegion(src_left
+ xo
, src_top
+ yo
, region
.coords
.left
- src_left
, height
, xo
, yo
);
773 src_left
= region
.coords
.left
;
774 width
= src_right
- src_left
;
776 if (src_top
< region
.coords
.top
) {
777 DoSetViewportPositionFillRegion(src_left
+ xo
, src_top
+ yo
, width
, region
.coords
.top
- src_top
, xo
, yo
);
778 src_top
= region
.coords
.top
;
779 height
= src_bottom
- src_top
;
781 if (src_right
> region
.coords
.right
) {
782 DoSetViewportPositionFillRegion(region
.coords
.right
+ xo
, src_top
+ yo
, src_right
- region
.coords
.right
, height
, xo
, yo
);
783 src_right
= region
.coords
.right
;
784 width
= src_right
- src_left
;
786 if (src_bottom
> region
.coords
.bottom
) {
787 DoSetViewportPositionFillRegion(src_left
+ xo
, region
.coords
.bottom
+ yo
, width
, src_bottom
- region
.coords
.bottom
, xo
, yo
);
788 src_bottom
= region
.coords
.bottom
;
789 height
= src_bottom
- src_top
;
793 /* scrolling left, moving pixels right */
796 /* scrolling right, moving pixels left */
801 /* scrolling down, moving pixels up */
804 /* scrolling up, moving pixels down */
808 BlitterFactory::GetCurrentBlitter()->ScrollBuffer(_screen
.dst_ptr
, src_left
, src_top
, width
, height
, xo
, yo
);
813 DrawOverlappedWindowForAll(left
, top
, left
+ width
, top
+ height
);
816 static void DoSetViewportPosition(Window
*w
, const Point move_offset
, const int vp_left
, const int vp_top
, const int vp_width
, const int vp_height
)
818 const int xo
= move_offset
.x
;
819 const int yo
= move_offset
.y
;
821 IncrementWindowUpdateNumber();
823 _vp_redraw_regions
.clear();
824 DoViewportRedrawRegions(w
, vp_left
, vp_top
, vp_width
, vp_height
);
826 if (abs(xo
) >= vp_width
|| abs(yo
) >= vp_height
) {
828 for (ViewportRedrawRegion
&vrr
: _vp_redraw_regions
) {
829 RedrawScreenRect(vrr
.coords
.left
, vrr
.coords
.top
, vrr
.coords
.right
, vrr
.coords
.bottom
);
834 Blitter
*blitter
= BlitterFactory::GetCurrentBlitter();
836 if (_cursor
.visible
) UndrawMouseCursor();
838 if (_networking
) NetworkUndrawChatMessage();
841 std::sort(_vp_redraw_regions
.begin(), _vp_redraw_regions
.end(), [&](const ViewportRedrawRegion
&a
, const ViewportRedrawRegion
&b
) {
842 if (a
.coords
.right
<= b
.coords
.left
&& xo
> 0) return true;
843 if (a
.coords
.left
>= b
.coords
.right
&& xo
< 0) return true;
847 std::stable_sort(_vp_redraw_regions
.begin(), _vp_redraw_regions
.end(), [&](const ViewportRedrawRegion
&a
, const ViewportRedrawRegion
&b
) {
848 if (a
.coords
.bottom
<= b
.coords
.top
&& yo
> 0) return true;
849 if (a
.coords
.top
>= b
.coords
.bottom
&& yo
< 0) return true;
854 std::sort(_vp_redraw_regions
.begin(), _vp_redraw_regions
.end(), [&](const ViewportRedrawRegion
&a
, const ViewportRedrawRegion
&b
) {
855 if (a
.coords
.bottom
<= b
.coords
.top
&& yo
> 0) return true;
856 if (a
.coords
.top
>= b
.coords
.bottom
&& yo
< 0) return true;
861 while (!_vp_redraw_regions
.empty()) {
862 const Rect
&rect
= _vp_redraw_regions
.back().coords
;
863 int left
= rect
.left
;
865 int width
= rect
.right
- rect
.left
;
866 int height
= rect
.bottom
- rect
.top
;
867 _vp_redraw_regions
.pop_back();
868 VideoDriver::GetInstance()->MakeDirty(left
, top
, width
, height
);
869 int fill_width
= abs(xo
);
870 int fill_height
= abs(yo
);
871 if (fill_width
< width
&& fill_height
< height
) {
872 blitter
->ScrollBuffer(_screen
.dst_ptr
, left
, top
, width
, height
, xo
, yo
);
874 if (width
< fill_width
) fill_width
= width
;
875 if (height
< fill_height
) fill_height
= height
;
878 /* scrolling right, moving pixels left, fill in on right */
880 DoSetViewportPositionFillRegion(left
+ width
, top
, fill_width
, height
, xo
, yo
);
882 /* scrolling left, moving pixels right, fill in on left */
883 DoSetViewportPositionFillRegion(left
, top
, fill_width
, height
, xo
, yo
);
887 if (yo
< 0 && width
> 0) {
888 /* scrolling down, moving pixels up, fill in at bottom */
889 height
-= fill_height
;
890 DoSetViewportPositionFillRegion(left
, top
+ height
, width
, fill_height
, xo
, yo
);
891 } else if (yo
> 0 && width
> 0) {
892 /* scrolling up, moving pixels down, fill in at top */
893 DoSetViewportPositionFillRegion(left
, top
, width
, fill_height
, xo
, yo
);
898 inline void UpdateViewportDirtyBlockLeftMargin(Viewport
*vp
)
900 if (vp
->zoom
>= ZOOM_LVL_DRAW_MAP
) {
901 vp
->dirty_block_left_margin
= 0;
903 vp
->dirty_block_left_margin
= UnScaleByZoomLower((-vp
->virtual_left
) & 127, vp
->zoom
);
907 static void SetViewportPosition(Window
*w
, int x
, int y
, bool force_update_overlay
)
909 if (unlikely(HasBit(_viewport_debug_flags
, VDF_DIRTY_WHOLE_VIEWPORT
))) {
910 w
->flags
|= WF_DIRTY
;
913 Viewport
*vp
= w
->viewport
;
914 int old_left
= vp
->virtual_left
;
915 int old_top
= vp
->virtual_top
;
917 int left
, top
, width
, height
;
919 vp
->virtual_left
= x
;
921 UpdateViewportDirtyBlockLeftMargin(vp
);
923 bool have_overlay
= w
->viewport
->overlay
!= nullptr &&
924 w
->viewport
->overlay
->GetCompanyMask() != 0 &&
925 w
->viewport
->overlay
->GetCargoMask() != 0;
927 if (have_overlay
&& (force_update_overlay
|| !w
->viewport
->overlay
->CacheStillValid())) RebuildViewportOverlay(w
, true);
929 /* Viewport is bound to its left top corner, so it must be rounded down (UnScaleByZoomLower)
930 * else glitch described in FS#1412 will happen (offset by 1 pixel with zoom level > NORMAL)
932 old_left
= UnScaleByZoomLower(old_left
, vp
->zoom
);
933 old_top
= UnScaleByZoomLower(old_top
, vp
->zoom
);
934 x
= UnScaleByZoomLower(x
, vp
->zoom
);
935 y
= UnScaleByZoomLower(y
, vp
->zoom
);
940 if (old_top
== 0 && old_left
== 0) return;
942 Point move_offset
= { old_left
, old_top
};
954 i
= left
+ width
- _screen
.width
;
955 if (i
>= 0) width
-= i
;
963 i
= top
+ height
- _screen
.height
;
964 if (i
>= 0) height
-= i
;
966 if (height
> 0 && (move_offset
.x
!= 0 || move_offset
.y
!= 0)) {
967 SCOPE_INFO_FMT([&], "DoSetViewportPosition: {}, {}, {}, {}, {}, {}, {}", left
, top
, width
, height
, move_offset
.x
, move_offset
.y
, WindowInfoDumper(w
));
968 ScrollViewportLandPixelCache(vp
, move_offset
.x
, move_offset
.y
);
969 ScrollPlanPixelCache(vp
, move_offset
.x
, move_offset
.y
);
970 if (have_overlay
) ScrollOrInvalidateOverlayPixelCache(vp
, move_offset
.x
, move_offset
.y
);
971 w
->viewport
->update_vehicles
= true;
972 DoSetViewportPosition((Window
*) w
->z_front
, move_offset
, left
, top
, width
, height
);
973 ClearViewportCache(w
->viewport
);
974 FillViewportCoverageRect();
980 * Is a xy position inside the viewport of the window?
981 * @param w Window to examine its viewport
982 * @param x X coordinate of the xy position
983 * @param y Y coordinate of the xy position
984 * @return Pointer to the viewport if the xy position is in the viewport of the window,
985 * otherwise \c nullptr is returned.
987 Viewport
*IsPtInWindowViewport(const Window
*w
, int x
, int y
)
989 Viewport
*vp
= w
->viewport
;
992 IsInsideMM(x
, vp
->left
, vp
->left
+ vp
->width
) &&
993 IsInsideMM(y
, vp
->top
, vp
->top
+ vp
->height
))
1000 * Translate screen coordinate in a viewport to underlying tile coordinate.
1002 * Returns exact point of the map that is visible in the given place
1003 * of the viewport (3D perspective), height of tiles and foundations matter.
1005 * @param vp Viewport that contains the (\a x, \a y) screen coordinate
1006 * @param x Screen x coordinate, distance in pixels from the left edge of viewport frame
1007 * @param y Screen y coordinate, distance in pixels from the top edge of viewport frame
1008 * @param clamp_to_map Clamp the coordinate outside of the map to the closest, non-void tile within the map
1009 * @return Tile coordinate or (-1, -1) if given x or y is not within viewport frame
1011 Point
TranslateXYToTileCoord(const Viewport
*vp
, int x
, int y
, bool clamp_to_map
)
1013 if (!IsInsideBS(x
, vp
->left
, vp
->width
) || !IsInsideBS(y
, vp
->top
, vp
->height
)) {
1014 Point pt
= { -1, -1 };
1018 return InverseRemapCoords2(
1019 ScaleByZoom(x
- vp
->left
, vp
->zoom
) + vp
->virtual_left
,
1020 ScaleByZoom(y
- vp
->top
, vp
->zoom
) + vp
->virtual_top
, clamp_to_map
);
1023 /* When used for zooming, check area below current coordinates (x,y)
1024 * and return the tile of the zoomed out/in position (zoom_x, zoom_y)
1025 * when you just want the tile, make x = zoom_x and y = zoom_y */
1026 static Point
GetTileFromScreenXY(int x
, int y
, int zoom_x
, int zoom_y
)
1028 Window
*w
= FindWindowFromPt(x
, y
);
1030 Viewport
*vp
= IsPtInWindowViewport(w
, x
, y
);
1031 if (vp
!= nullptr) {
1032 return TranslateXYToTileCoord(vp
, zoom_x
, zoom_y
);
1039 Point
GetTileBelowCursor()
1041 return GetTileFromScreenXY(_cursor
.pos
.x
, _cursor
.pos
.y
, _cursor
.pos
.x
, _cursor
.pos
.y
);
1045 Point
GetTileZoomCenterWindow(bool in
, Window
* w
)
1048 Viewport
*vp
= w
->viewport
;
1051 x
= ((_cursor
.pos
.x
- vp
->left
) >> 1) + (vp
->width
>> 2);
1052 y
= ((_cursor
.pos
.y
- vp
->top
) >> 1) + (vp
->height
>> 2);
1054 x
= vp
->width
- (_cursor
.pos
.x
- vp
->left
);
1055 y
= vp
->height
- (_cursor
.pos
.y
- vp
->top
);
1057 /* Get the tile below the cursor and center on the zoomed-out center */
1058 return GetTileFromScreenXY(_cursor
.pos
.x
, _cursor
.pos
.y
, x
+ vp
->left
, y
+ vp
->top
);
1062 * Update the status of the zoom-buttons according to the zoom-level
1063 * of the viewport. This will update their status and invalidate accordingly
1064 * @param w Window pointer to the window that has the zoom buttons
1065 * @param vp pointer to the viewport whose zoom-level the buttons represent
1066 * @param widget_zoom_in widget index for window with zoom-in button
1067 * @param widget_zoom_out widget index for window with zoom-out button
1069 void HandleZoomMessage(Window
*w
, const Viewport
*vp
, WidgetID widget_zoom_in
, WidgetID widget_zoom_out
)
1071 w
->SetWidgetDisabledState(widget_zoom_in
, vp
->zoom
<= _settings_client
.gui
.zoom_min
);
1072 w
->SetWidgetDirty(widget_zoom_in
);
1074 w
->SetWidgetDisabledState(widget_zoom_out
, vp
->zoom
>= _settings_client
.gui
.zoom_max
);
1075 w
->SetWidgetDirty(widget_zoom_out
);
1079 * Schedules a tile sprite for drawing.
1081 * @param image the image to draw.
1082 * @param pal the provided palette.
1083 * @param x position x (world coordinates) of the sprite.
1084 * @param y position y (world coordinates) of the sprite.
1085 * @param z position z (world coordinates) of the sprite.
1086 * @param sub Only draw a part of the sprite.
1087 * @param extra_offs_x Pixel X offset for the sprite position.
1088 * @param extra_offs_y Pixel Y offset for the sprite position.
1090 static void AddTileSpriteToDraw(SpriteID image
, PaletteID pal
, int32_t x
, int32_t y
, int z
, const SubSprite
*sub
= nullptr, int extra_offs_x
= 0, int extra_offs_y
= 0)
1092 dbg_assert((image
& SPRITE_MASK
) < MAX_SPRITES
);
1094 TileSpriteToDraw
&ts
= _vdd
->tile_sprites_to_draw
.emplace_back();
1098 Point pt
= RemapCoords(x
, y
, z
);
1099 ts
.x
= pt
.x
+ extra_offs_x
;
1100 ts
.y
= pt
.y
+ extra_offs_y
;
1104 * Adds a child sprite to the active foundation.
1106 * The pixel offset of the sprite relative to the ParentSprite is the sum of the offset passed to OffsetGroundSprite() and extra_offs_?.
1108 * @param image the image to draw.
1109 * @param pal the provided palette.
1110 * @param sub Only draw a part of the sprite.
1111 * @param foundation_part Foundation part.
1112 * @param extra_offs_x Pixel X offset for the sprite position.
1113 * @param extra_offs_y Pixel Y offset for the sprite position.
1115 static void AddChildSpriteToFoundation(SpriteID image
, PaletteID pal
, const SubSprite
*sub
, FoundationPart foundation_part
, int extra_offs_x
, int extra_offs_y
)
1117 dbg_assert(IsInsideMM(foundation_part
, 0, FOUNDATION_PART_END
));
1118 dbg_assert(_vd
.foundation
[foundation_part
] != -1);
1119 Point offs
= _vd
.foundation_offset
[foundation_part
];
1121 /* Change the active ChildSprite list to the one of the foundation */
1122 ChildStoreID old_child
= _vd
.last_child
;
1123 _vd
.last_child
= _vd
.last_foundation_child
[foundation_part
];
1125 AddChildSpriteScreen(image
, pal
, offs
.x
+ extra_offs_x
, offs
.y
+ extra_offs_y
, false, sub
, false, ChildScreenSpritePositionMode::NonRelative
);
1127 /* Switch back to last ChildSprite list */
1128 _vd
.last_child
= old_child
;
1132 * Draws a ground sprite at a specific world-coordinate relative to the current tile.
1133 * If the current tile is drawn on top of a foundation the sprite is added as child sprite to the "foundation"-ParentSprite.
1135 * @param image the image to draw.
1136 * @param pal the provided palette.
1137 * @param x position x (world coordinates) of the sprite relative to current tile.
1138 * @param y position y (world coordinates) of the sprite relative to current tile.
1139 * @param z position z (world coordinates) of the sprite relative to current tile.
1140 * @param sub Only draw a part of the sprite.
1141 * @param extra_offs_x Pixel X offset for the sprite position.
1142 * @param extra_offs_y Pixel Y offset for the sprite position.
1144 void DrawGroundSpriteAt(SpriteID image
, PaletteID pal
, int32_t x
, int32_t y
, int z
, const SubSprite
*sub
, int extra_offs_x
, int extra_offs_y
)
1146 /* Switch to first foundation part, if no foundation was drawn */
1147 if (_vd
.foundation_part
== FOUNDATION_PART_NONE
) _vd
.foundation_part
= FOUNDATION_PART_NORMAL
;
1149 if (_vd
.foundation
[_vd
.foundation_part
] != -1) {
1150 Point pt
= RemapCoords(x
, y
, z
);
1151 AddChildSpriteToFoundation(image
, pal
, sub
, _vd
.foundation_part
, pt
.x
+ extra_offs_x
* ZOOM_BASE
, pt
.y
+ extra_offs_y
* ZOOM_BASE
);
1153 AddTileSpriteToDraw(image
, pal
, _cur_ti
.x
+ x
, _cur_ti
.y
+ y
, _cur_ti
.z
+ z
, sub
, extra_offs_x
* ZOOM_BASE
, extra_offs_y
* ZOOM_BASE
);
1158 * Draws a ground sprite for the current tile.
1159 * If the current tile is drawn on top of a foundation the sprite is added as child sprite to the "foundation"-ParentSprite.
1161 * @param image the image to draw.
1162 * @param pal the provided palette.
1163 * @param sub Only draw a part of the sprite.
1164 * @param extra_offs_x Pixel X offset for the sprite position.
1165 * @param extra_offs_y Pixel Y offset for the sprite position.
1167 void DrawGroundSprite(SpriteID image
, PaletteID pal
, const SubSprite
*sub
, int extra_offs_x
, int extra_offs_y
)
1169 DrawGroundSpriteAt(image
, pal
, 0, 0, 0, sub
, extra_offs_x
, extra_offs_y
);
1173 * Called when a foundation has been drawn for the current tile.
1174 * Successive ground sprites for the current tile will be drawn as child sprites of the "foundation"-ParentSprite, not as TileSprites.
1176 * @param x sprite x-offset (screen coordinates) of ground sprites relative to the "foundation"-ParentSprite.
1177 * @param y sprite y-offset (screen coordinates) of ground sprites relative to the "foundation"-ParentSprite.
1179 void OffsetGroundSprite(int x
, int y
)
1181 /* Switch to next foundation part */
1182 switch (_vd
.foundation_part
) {
1183 case FOUNDATION_PART_NONE
:
1184 _vd
.foundation_part
= FOUNDATION_PART_NORMAL
;
1186 case FOUNDATION_PART_NORMAL
:
1187 _vd
.foundation_part
= FOUNDATION_PART_HALFTILE
;
1189 default: NOT_REACHED();
1192 /* _vd.last_child == NO_CHILD_STORE if foundation sprite was clipped by the viewport bounds */
1193 if (_vd
.last_child
!= NO_CHILD_STORE
) _vd
.foundation
[_vd
.foundation_part
] = (uint
)_vdd
->parent_sprites_to_draw
.size() - 1;
1195 _vd
.foundation_offset
[_vd
.foundation_part
].x
= x
* ZOOM_BASE
;
1196 _vd
.foundation_offset
[_vd
.foundation_part
].y
= y
* ZOOM_BASE
;
1197 _vd
.last_foundation_child
[_vd
.foundation_part
] = _vd
.last_child
;
1201 * Adds a child sprite to a parent sprite.
1202 * In contrast to "AddChildSpriteScreen()" the sprite position is in world coordinates
1204 * @param image the image to draw.
1205 * @param pal the provided palette.
1206 * @param x position x of the sprite.
1207 * @param y position y of the sprite.
1208 * @param z position z of the sprite.
1209 * @param sub Only draw a part of the sprite.
1211 static void AddCombinedSprite(SpriteID image
, PaletteID pal
, int x
, int y
, int z
, const SubSprite
*sub
)
1213 Point pt
= RemapCoords(x
, y
, z
);
1214 const Sprite
*spr
= GetSprite(image
& SPRITE_MASK
, SpriteType::Normal
, ZoomMask(_vdd
->dpi
.zoom
));
1216 int left
= pt
.x
+ spr
->x_offs
;
1217 int right
= pt
.x
+ spr
->x_offs
+ spr
->width
;
1218 int top
= pt
.y
+ spr
->y_offs
;
1219 int bottom
= pt
.y
+ spr
->y_offs
+ spr
->height
;
1220 if (left
>= _vdd
->dpi
.left
+ _vdd
->dpi
.width
||
1221 right
<= _vdd
->dpi
.left
||
1222 top
>= _vdd
->dpi
.top
+ _vdd
->dpi
.height
||
1223 bottom
<= _vdd
->dpi
.top
) {
1227 AddChildSpriteScreen(image
, pal
, pt
.x
, pt
.y
, false, sub
, false, ChildScreenSpritePositionMode::Absolute
);
1228 if (left
< _vd
.combine_left
) _vd
.combine_left
= left
;
1229 if (right
> _vd
.combine_right
) _vd
.combine_right
= right
;
1230 if (top
< _vd
.combine_top
) _vd
.combine_top
= top
;
1231 if (bottom
> _vd
.combine_bottom
) _vd
.combine_bottom
= bottom
;
1235 * Draw a (transparent) sprite at given coordinates with a given bounding box.
1236 * 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.
1237 * Bounding boxes with bb_offset_x == w or bb_offset_y == h or bb_offset_z == dz are allowed and produce thin slices.
1239 * @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
1240 * defined by the sprite offset in the grf file.
1241 * However if modifying the sprite offsets is not suitable (e.g. when using existing graphics), the bounding box can be tuned by bb_offset.
1243 * @pre w >= bb_offset_x, h >= bb_offset_y, dz >= bb_offset_z. Else w, h or dz are ignored.
1245 * @param image the image to combine and draw,
1246 * @param pal the provided palette,
1247 * @param x position X (world) of the sprite,
1248 * @param y position Y (world) of the sprite,
1249 * @param w bounding box extent towards positive X (world),
1250 * @param h bounding box extent towards positive Y (world),
1251 * @param dz bounding box extent towards positive Z (world),
1252 * @param z position Z (world) of the sprite,
1253 * @param transparent if true, switch the palette between the provided palette and the transparent palette,
1254 * @param bb_offset_x bounding box extent towards negative X (world),
1255 * @param bb_offset_y bounding box extent towards negative Y (world),
1256 * @param bb_offset_z bounding box extent towards negative Z (world)
1257 * @param sub Only draw a part of the sprite.
1258 * @param special_flags Special flags (special sorting, etc).
1260 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
, ViewportSortableSpriteSpecialFlags special_flags
)
1262 int32_t left
, right
, top
, bottom
;
1264 dbg_assert((image
& SPRITE_MASK
) < MAX_SPRITES
);
1266 /* make the sprites transparent with the right palette */
1268 SetBit(image
, PALETTE_MODIFIER_TRANSPARENT
);
1269 pal
= PALETTE_TO_TRANSPARENT
;
1272 if (_vd
.combine_sprites
== SPRITE_COMBINE_ACTIVE
) {
1273 AddCombinedSprite(image
, pal
, x
, y
, z
, sub
);
1277 _vd
.last_child
= NO_CHILD_STORE
;
1279 Point pt
= RemapCoords(x
, y
, z
);
1280 int tmp_left
, tmp_top
, tmp_x
= pt
.x
, tmp_y
= pt
.y
;
1281 uint16_t tmp_width
, tmp_height
;
1283 /* Compute screen extents of sprite */
1284 if (unlikely(image
== SPR_EMPTY_BOUNDING_BOX
)) {
1285 left
= tmp_left
= RemapCoords(x
+ w
, y
+ bb_offset_y
, z
+ bb_offset_z
).x
;
1286 right
= RemapCoords(x
+ bb_offset_x
, y
+ h
, z
+ bb_offset_z
).x
+ 1;
1287 top
= tmp_top
= RemapCoords(x
+ bb_offset_x
, y
+ bb_offset_y
, z
+ dz
).y
;
1288 bottom
= RemapCoords(x
+ w
, y
+ h
, z
+ bb_offset_z
).y
+ 1;
1289 tmp_width
= right
- left
;
1290 tmp_height
= bottom
- top
;
1292 const Sprite
*spr
= GetSprite(image
& SPRITE_MASK
, SpriteType::Normal
, ZoomMask(_vdd
->dpi
.zoom
));
1293 left
= tmp_left
= (pt
.x
+= spr
->x_offs
);
1294 right
= (pt
.x
+ spr
->width
);
1295 top
= tmp_top
= (pt
.y
+= spr
->y_offs
);
1296 bottom
= (pt
.y
+ spr
->height
);
1297 tmp_width
= spr
->width
;
1298 tmp_height
= spr
->height
;
1301 if (unlikely(_draw_bounding_boxes
&& (image
!= SPR_EMPTY_BOUNDING_BOX
))) {
1302 /* Compute maximal extents of sprite and its bounding box */
1303 left
= std::min(left
, RemapCoords(x
+ w
, y
+ bb_offset_y
, z
+ bb_offset_z
).x
);
1304 right
= std::max(right
, RemapCoords(x
+ bb_offset_x
, y
+ h
, z
+ bb_offset_z
).x
+ 1);
1305 top
= std::min(top
, RemapCoords(x
+ bb_offset_x
, y
+ bb_offset_y
, z
+ dz
).y
);
1306 bottom
= std::max(bottom
, RemapCoords(x
+ w
, y
+ h
, z
+ bb_offset_z
).y
+ 1);
1309 /* Do not add the sprite to the viewport, if it is outside */
1310 if (left
>= _vdd
->dpi
.left
+ _vdd
->dpi
.width
||
1311 right
<= _vdd
->dpi
.left
||
1312 top
>= _vdd
->dpi
.top
+ _vdd
->dpi
.height
||
1313 bottom
<= _vdd
->dpi
.top
) {
1317 _vd
.last_child
= static_cast<ChildStoreID
>(_vdd
->parent_sprites_to_draw
.size());
1319 ParentSpriteToDraw
&ps
= _vdd
->parent_sprites_to_draw
.emplace_back();
1328 _vdd
->parent_sprite_subsprites
.Set(&ps
, sub
);
1329 ps
.special_flags
= special_flags
;
1331 ps
.xmin
= x
+ bb_offset_x
;
1332 ps
.xmax
= x
+ std::max(bb_offset_x
, w
) - 1;
1334 ps
.ymin
= y
+ bb_offset_y
;
1335 ps
.ymax
= y
+ std::max(bb_offset_y
, h
) - 1;
1337 ps
.zmin
= z
+ bb_offset_z
;
1338 ps
.zmax
= z
+ std::max(bb_offset_z
, dz
) - 1;
1340 ps
.first_child
= -1;
1341 ps
.width
= tmp_width
;
1342 ps
.height
= tmp_height
;
1344 /* bit 15 of ps.height */
1345 // ps.comparison_done = false;
1347 if (_vd
.combine_sprites
== SPRITE_COMBINE_PENDING
) {
1348 _vd
.combine_sprites
= SPRITE_COMBINE_ACTIVE
;
1349 _vd
.combine_psd_index
= (uint
)_vdd
->parent_sprites_to_draw
.size() - 1;
1350 _vd
.combine_left
= tmp_left
;
1351 _vd
.combine_right
= right
;
1352 _vd
.combine_top
= tmp_top
;
1353 _vd
.combine_bottom
= bottom
;
1357 void SetLastSortableSpriteToDrawSpecialFlags(ViewportSortableSpriteSpecialFlags flags
)
1359 _vdd
->parent_sprites_to_draw
.back().special_flags
= flags
;
1363 * Starts a block of sprites, which are "combined" into a single bounding box.
1365 * Subsequent calls to #AddSortableSpriteToDraw will be drawn into the same bounding box.
1366 * That is: The first sprite that is not clipped by the viewport defines the bounding box, and
1367 * the following sprites will be child sprites to that one.
1370 * - The drawing order is definite. No other sprites will be sorted between those of the block.
1371 * - You have to provide a valid bounding box for all sprites,
1372 * as you won't know which one is the first non-clipped one.
1373 * Preferable you use the same bounding box for all.
1374 * - You cannot use #AddChildSpriteScreen inside the block, as its result will be indefinite.
1376 * The block is terminated by #EndSpriteCombine.
1378 * You cannot nest "combined" blocks.
1380 void StartSpriteCombine()
1382 dbg_assert(_vd
.combine_sprites
== SPRITE_COMBINE_NONE
);
1383 _vd
.combine_sprites
= SPRITE_COMBINE_PENDING
;
1387 * Terminates a block of sprites started by #StartSpriteCombine.
1388 * Take a look there for details.
1390 void EndSpriteCombine()
1392 dbg_assert(_vd
.combine_sprites
!= SPRITE_COMBINE_NONE
);
1393 if (_vd
.combine_sprites
== SPRITE_COMBINE_ACTIVE
) {
1394 ParentSpriteToDraw
&ps
= _vdd
->parent_sprites_to_draw
[_vd
.combine_psd_index
];
1395 ps
.left
= _vd
.combine_left
;
1396 ps
.top
= _vd
.combine_top
;
1397 ps
.width
= _vd
.combine_right
- _vd
.combine_left
;
1398 ps
.height
= _vd
.combine_bottom
- _vd
.combine_top
;
1400 _vd
.combine_sprites
= SPRITE_COMBINE_NONE
;
1404 * Check if the parameter "check" is inside the interval between
1405 * begin and end, including both begin and end.
1406 * @note Whether \c begin or \c end is the biggest does not matter.
1407 * This method will account for that.
1408 * @param begin The begin of the interval.
1409 * @param end The end of the interval.
1410 * @param check The value to check.
1412 static bool IsInRangeInclusive(int begin
, int end
, int check
)
1414 if (begin
> end
) Swap(begin
, end
);
1415 return begin
<= check
&& check
<= end
;
1419 * Checks whether a point is inside the selected rectangle given by _thd.size, _thd.pos and _thd.diagonal
1420 * @param x The x coordinate of the point to be checked.
1421 * @param y The y coordinate of the point to be checked.
1422 * @return True if the point is inside the rectangle, else false.
1424 static bool IsInsideSelectedRectangle(int x
, int y
)
1426 if (!_thd
.diagonal
) {
1427 return IsInsideBS(x
, _thd
.pos
.x
, _thd
.size
.x
) && IsInsideBS(y
, _thd
.pos
.y
, _thd
.size
.y
);
1430 int dist_a
= (_thd
.size
.x
+ _thd
.size
.y
); // Rotated coordinate system for selected rectangle.
1431 int dist_b
= (_thd
.size
.x
- _thd
.size
.y
); // We don't have to divide by 2. It's all relative!
1432 int a
= ((x
- _thd
.pos
.x
) + (y
- _thd
.pos
.y
)); // Rotated coordinate system for the point under scrutiny.
1433 int b
= ((x
- _thd
.pos
.x
) - (y
- _thd
.pos
.y
));
1435 /* Check if a and b are between 0 and dist_a or dist_b respectively. */
1436 return IsInRangeInclusive(dist_a
, 0, a
) && IsInRangeInclusive(dist_b
, 0, b
);
1440 * Add a child sprite to a parent sprite.
1442 * @param image the image to draw.
1443 * @param pal the provided palette.
1444 * @param x sprite x-offset (screen coordinates), optionally relative to parent sprite.
1445 * @param y sprite y-offset (screen coordinates), optionally relative to parent sprite.
1446 * @param transparent if true, switch the palette between the provided palette and the transparent palette,
1447 * @param sub Only draw a part of the sprite.
1448 * @param scale if true, scale offsets to base zoom level.
1449 * @param position_mode position mode.
1451 void AddChildSpriteScreen(SpriteID image
, PaletteID pal
, int x
, int y
, bool transparent
, const SubSprite
*sub
, bool scale
, ChildScreenSpritePositionMode position_mode
)
1453 dbg_assert((image
& SPRITE_MASK
) < MAX_SPRITES
);
1455 /* If the ParentSprite was clipped by the viewport bounds, do not draw the ChildSprites either */
1456 if (_vd
.last_child
== NO_CHILD_STORE
) return;
1458 /* make the sprites transparent with the right palette */
1460 SetBit(image
, PALETTE_MODIFIER_TRANSPARENT
);
1461 pal
= PALETTE_TO_TRANSPARENT
;
1464 _vdd
->SetChild(_vd
.last_child
, (uint
)_vdd
->child_screen_sprites_to_draw
.size());
1465 const ChildStoreID child_store
= static_cast<ChildStoreID
>(_vdd
->child_screen_sprites_to_draw
.size()) | CHILD_SPRITE_STORE_TAG
;
1467 ChildScreenSpriteToDraw
&cs
= _vdd
->child_screen_sprites_to_draw
.emplace_back();
1471 cs
.x
= scale
? x
* ZOOM_BASE
: x
;
1472 cs
.y
= scale
? y
* ZOOM_BASE
: y
;
1473 cs
.position_mode
= position_mode
;
1476 /* Append the sprite to the active ChildSprite list.
1477 * If the active ParentSprite is a foundation, update last_foundation_child as well.
1478 * Note: ChildSprites of foundations are NOT sequential in the vector, as selection sprites are added at last. */
1479 if (_vd
.last_foundation_child
[0] == _vd
.last_child
) _vd
.last_foundation_child
[0] = child_store
;
1480 if (_vd
.last_foundation_child
[1] == _vd
.last_child
) _vd
.last_foundation_child
[1] = child_store
;
1481 _vd
.last_child
= child_store
;
1484 static void AddStringToDraw(ViewportDrawerDynamic
*vdd
, int x
, int y
, StringID string
, uint64_t params_1
, uint64_t params_2
, Colours colour
, uint16_t width
)
1486 dbg_assert(width
!= 0);
1487 StringSpriteToDraw
&ss
= vdd
->string_sprites_to_draw
.emplace_back();
1491 ss
.params
[0] = params_1
;
1492 ss
.params
[1] = params_2
;
1499 * Draws sprites between ground sprite and everything above.
1501 * The sprite is either drawn as TileSprite or as ChildSprite of the active foundation.
1503 * @param image the image to draw.
1504 * @param pal the provided palette.
1505 * @param ti TileInfo Tile that is being drawn
1506 * @param z_offset Z offset relative to the groundsprite. Only used for the sprite position, not for sprite sorting.
1507 * @param foundation_part Foundation part the sprite belongs to.
1508 * @param extra_offs_x Pixel X offset for the sprite position.
1509 * @param extra_offs_y Pixel Y offset for the sprite position.
1510 * @param sub Sub-section of sprite to draw.
1512 void DrawSelectionSprite(SpriteID image
, PaletteID pal
, const TileInfo
*ti
, int z_offset
, FoundationPart foundation_part
, int extra_offs_x
, int extra_offs_y
, const SubSprite
*sub
)
1514 /* FIXME: This is not totally valid for some autorail highlights that extend over the edges of the tile. */
1515 if (_vd
.foundation
[foundation_part
] == -1) {
1516 /* draw on real ground */
1517 AddTileSpriteToDraw(image
, pal
, ti
->x
, ti
->y
, ti
->z
+ z_offset
, sub
, extra_offs_x
, extra_offs_y
);
1519 /* draw on top of foundation */
1520 AddChildSpriteToFoundation(image
, pal
, sub
, foundation_part
, extra_offs_x
, extra_offs_y
- z_offset
* ZOOM_BASE
);
1525 * Draws a selection rectangle on a tile.
1527 * @param ti TileInfo Tile that is being drawn
1528 * @param pal Palette to apply.
1530 void DrawTileSelectionRect(const TileInfo
*ti
, PaletteID pal
)
1532 if (!IsValidTile(ti
->tile
)) return;
1535 if (IsHalftileSlope(ti
->tileh
)) {
1536 Corner halftile_corner
= GetHalftileSlopeCorner(ti
->tileh
);
1537 SpriteID sel2
= SPR_HALFTILE_SELECTION_FLAT
+ halftile_corner
;
1538 DrawSelectionSprite(sel2
, pal
, ti
, 7 + TILE_HEIGHT
, FOUNDATION_PART_HALFTILE
);
1540 Corner opposite_corner
= OppositeCorner(halftile_corner
);
1541 if (IsSteepSlope(ti
->tileh
)) {
1542 sel
= SPR_HALFTILE_SELECTION_DOWN
;
1544 sel
= ((ti
->tileh
& SlopeWithOneCornerRaised(opposite_corner
)) != 0 ? SPR_HALFTILE_SELECTION_UP
: SPR_HALFTILE_SELECTION_FLAT
);
1546 sel
+= opposite_corner
;
1548 sel
= SPR_SELECT_TILE
+ SlopeToSpriteOffset(ti
->tileh
);
1550 DrawSelectionSprite(sel
, pal
, ti
, 7, FOUNDATION_PART_NORMAL
);
1553 static HighLightStyle
GetPartOfAutoLine(int px
, int py
, const Point
&selstart
, const Point
&selend
, HighLightStyle dir
)
1555 if (!IsInRangeInclusive(selstart
.x
& ~TILE_UNIT_MASK
, selend
.x
& ~TILE_UNIT_MASK
, px
)) return HT_DIR_END
;
1556 if (!IsInRangeInclusive(selstart
.y
& ~TILE_UNIT_MASK
, selend
.y
& ~TILE_UNIT_MASK
, py
)) return HT_DIR_END
;
1558 px
-= selstart
.x
& ~TILE_UNIT_MASK
;
1559 py
-= selstart
.y
& ~TILE_UNIT_MASK
;
1562 case HT_DIR_X
: return (py
== 0) ? HT_DIR_X
: HT_DIR_END
;
1563 case HT_DIR_Y
: return (px
== 0) ? HT_DIR_Y
: HT_DIR_END
;
1564 case HT_DIR_HU
: return (px
== -py
) ? HT_DIR_HU
: (px
== -py
- (int)TILE_SIZE
) ? HT_DIR_HL
: HT_DIR_END
;
1565 case HT_DIR_HL
: return (px
== -py
) ? HT_DIR_HL
: (px
== -py
+ (int)TILE_SIZE
) ? HT_DIR_HU
: HT_DIR_END
;
1566 case HT_DIR_VL
: return (px
== py
) ? HT_DIR_VL
: (px
== py
+ (int)TILE_SIZE
) ? HT_DIR_VR
: HT_DIR_END
;
1567 case HT_DIR_VR
: return (px
== py
) ? HT_DIR_VR
: (px
== py
- (int)TILE_SIZE
) ? HT_DIR_VL
: HT_DIR_END
;
1568 default: NOT_REACHED(); break;
1574 #include "table/autorail.h"
1577 * Draws autorail highlights.
1579 * @param *ti TileInfo Tile that is being drawn
1580 * @param autorail_type \c HT_DIR_XXX, offset into _AutorailTilehSprite[][]
1581 * @param pal Palette to use, -1 to autodetect
1583 static void DrawAutorailSelection(const TileInfo
*ti
, HighLightStyle autorail_type
, PaletteID pal
= -1)
1586 FoundationPart foundation_part
= FOUNDATION_PART_NORMAL
;
1588 bool bridge_head_mode
= false;
1590 if (IsFlatRailBridgeHeadTile(ti
->tile
)) {
1591 extern bool IsValidFlatRailBridgeHeadTrackBits(Slope normalised_slope
, DiagDirection bridge_direction
, TrackBits tracks
);
1593 offset
= _AutorailTilehSprite
[SLOPE_FLAT
][autorail_type
];
1594 const Slope real_tileh
= GetTileSlope(ti
->tile
);
1595 const Slope normalised_tileh
= IsSteepSlope(real_tileh
) ? SlopeWithOneCornerRaised(GetHighestSlopeCorner(real_tileh
)) : real_tileh
;
1596 if (!IsValidFlatRailBridgeHeadTrackBits(normalised_tileh
, GetTunnelBridgeDirection(ti
->tile
), TrackToTrackBits((Track
) autorail_type
))) {
1599 if (!IsRailCustomBridgeHead(ti
->tile
)) {
1600 bridge_head_mode
= true;
1603 Slope autorail_tileh
= RemoveHalftileSlope(ti
->tileh
);
1604 if (IsHalftileSlope(ti
->tileh
)) {
1605 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
1606 Corner halftile_corner
= GetHalftileSlopeCorner(ti
->tileh
);
1607 if (autorail_type
!= _lower_rail
[halftile_corner
]) {
1608 foundation_part
= FOUNDATION_PART_HALFTILE
;
1609 /* Here we draw the highlights of the "three-corners-raised"-slope. That looks ok to me. */
1610 autorail_tileh
= SlopeWithThreeCornersRaised(OppositeCorner(halftile_corner
));
1613 assert(autorail_type
< HT_DIR_END
);
1614 offset
= _AutorailTilehSprite
[autorail_tileh
][autorail_type
];
1618 image
= SPR_AUTORAIL_BASE
+ offset
;
1619 if (pal
== (PaletteID
)-1) pal
= _thd
.square_palette
;
1621 image
= SPR_AUTORAIL_BASE
- offset
;
1622 if (pal
== (PaletteID
)-1) pal
= PALETTE_SEL_TILE_RED
;
1625 if (bridge_head_mode
) {
1626 AddSortableSpriteToDraw(image
, pal
, ti
->x
, ti
->y
, 16, 16, 0, ti
->z
+ 15);
1628 DrawSelectionSprite(image
, pal
, ti
, 7, foundation_part
);
1632 enum TileHighlightType
{
1640 const Station
*_viewport_highlight_station
; ///< Currently selected station for coverage area highlight
1641 const Waypoint
*_viewport_highlight_waypoint
; ///< Currently selected waypoint for coverage area highlight
1642 const Town
*_viewport_highlight_town
; ///< Currently selected town for coverage area highlight
1643 const TraceRestrictProgram
*_viewport_highlight_tracerestrict_program
; ///< Currently selected tracerestrict program for highlight
1646 * Get tile highlight type of coverage area for a given tile.
1647 * @param t Tile that is being drawn
1648 * @return Tile highlight type to draw
1650 static TileHighlightType
GetTileHighlightType(TileIndex t
)
1652 if (_viewport_highlight_station
!= nullptr) {
1653 if (IsTileType(t
, MP_STATION
) && GetStationIndex(t
) == _viewport_highlight_station
->index
) return THT_LIGHT_BLUE
;
1654 if (_viewport_highlight_station
->TileIsInCatchment(t
)) return THT_BLUE
;
1656 if (_viewport_highlight_waypoint
!= nullptr) {
1657 if (IsTileType(t
, MP_STATION
) && GetStationIndex(t
) == _viewport_highlight_waypoint
->index
) return THT_LIGHT_BLUE
;
1660 if (_viewport_highlight_town
!= nullptr) {
1661 if (IsTileType(t
, MP_HOUSE
)) {
1662 if (GetTownIndex(t
) == _viewport_highlight_town
->index
) {
1663 TileHighlightType type
= THT_RED
;
1664 for (const Station
*st
: _viewport_highlight_town
->stations_near
) {
1665 if (st
->owner
!= _current_company
) continue;
1666 if (st
->TileIsInCatchment(t
)) return THT_BLUE
;
1670 } else if (IsTileType(t
, MP_STATION
)) {
1671 for (const Station
*st
: _viewport_highlight_town
->stations_near
) {
1672 if (st
->owner
!= _current_company
) continue;
1673 if (GetStationIndex(t
) == st
->index
) return THT_WHITE
;
1678 if (_viewport_highlight_tracerestrict_program
!= nullptr) {
1679 const TraceRestrictRefId
*refs
= _viewport_highlight_tracerestrict_program
->GetRefIdsPtr();
1680 for (uint i
= 0; i
< _viewport_highlight_tracerestrict_program
->refcount
; i
++) {
1681 if (GetTraceRestrictRefIdTileIndex(refs
[i
]) == t
) return THT_LIGHT_BLUE
;
1689 * Draw tile highlight for coverage area highlight.
1690 * @param *ti TileInfo Tile that is being drawn
1691 * @param tht Highlight type to draw.
1693 static void DrawTileHighlightType(const TileInfo
*ti
, TileHighlightType tht
)
1697 case THT_NONE
: break;
1698 case THT_WHITE
: DrawTileSelectionRect(ti
, PAL_NONE
); break;
1699 case THT_BLUE
: DrawTileSelectionRect(ti
, PALETTE_SEL_TILE_BLUE
); break;
1700 case THT_RED
: DrawTileSelectionRect(ti
, PALETTE_SEL_TILE_RED
); break;
1701 case THT_LIGHT_BLUE
: DrawTileSelectionRect(ti
, SPR_ZONING_INNER_HIGHLIGHT_LIGHT_BLUE
); break;
1706 * Highlights tiles insede local authority of selected towns.
1707 * @param *ti TileInfo Tile that is being drawn
1709 static void HighlightTownLocalAuthorityTiles(const TileInfo
*ti
)
1711 /* Going through cases in order of computational time. */
1713 if (_town_local_authority_kdtree
.Count() == 0) return;
1715 /* Tile belongs to town regardless of distance from town. */
1716 if (GetTileType(ti
->tile
) == MP_HOUSE
) {
1717 if (!Town::GetByTile(ti
->tile
)->show_zone
) return;
1719 DrawTileSelectionRect(ti
, PALETTE_CRASH
);
1723 /* If the closest town in the highlighted list is far, we can stop searching. */
1724 TownID tid
= _town_local_authority_kdtree
.FindNearest(TileX(ti
->tile
), TileY(ti
->tile
));
1725 Town
*closest_highlighted_town
= Town::Get(tid
);
1727 if (DistanceManhattan(ti
->tile
, closest_highlighted_town
->xy
) >= _settings_game
.economy
.dist_local_authority
) return;
1729 /* Tile is inside of the local autrhority distance of a highlighted town,
1730 but it is possible that a non-highlighted town is even closer. */
1731 Town
*closest_town
= ClosestTownFromTile(ti
->tile
, _settings_game
.economy
.dist_local_authority
);
1733 if (closest_town
->show_zone
) {
1734 DrawTileSelectionRect(ti
, PALETTE_CRASH
);
1740 * Checks if the specified tile is selected and if so draws selection using correct selectionstyle.
1741 * @param *ti TileInfo Tile that is being drawn
1743 static void DrawTileSelection(const TileInfo
*ti
)
1745 /* Highlight tiles insede local authority of selected towns. */
1746 HighlightTownLocalAuthorityTiles(ti
);
1748 /* Draw a red error square? */
1749 bool is_redsq
= _thd
.redsq
== ti
->tile
;
1750 if (is_redsq
) DrawTileSelectionRect(ti
, PALETTE_TILE_RED_PULSATING
);
1752 TileHighlightType tht
= GetTileHighlightType(ti
->tile
);
1753 DrawTileHighlightType(ti
, tht
);
1755 switch (_thd
.drawstyle
& HT_DRAG_MASK
) {
1756 default: break; // No tile selection active?
1760 if (IsInsideSelectedRectangle(ti
->x
, ti
->y
)) {
1761 DrawTileSelectionRect(ti
, _thd
.square_palette
);
1762 } else if (_thd
.outersize
.x
> 0 && (tht
== THT_NONE
|| tht
== THT_RED
) &&
1763 /* Check if it's inside the outer area? */
1764 IsInsideBS(ti
->x
, _thd
.pos
.x
+ _thd
.offs
.x
, _thd
.size
.x
+ _thd
.outersize
.x
) &&
1765 IsInsideBS(ti
->y
, _thd
.pos
.y
+ _thd
.offs
.y
, _thd
.size
.y
+ _thd
.outersize
.y
)) {
1766 /* Draw a blue rect. */
1767 DrawTileSelectionRect(ti
, PALETTE_SEL_TILE_BLUE
);
1773 if (IsInsideSelectedRectangle(ti
->x
, ti
->y
)) {
1774 /* Figure out the Z coordinate for the single dot. */
1776 FoundationPart foundation_part
= FOUNDATION_PART_NORMAL
;
1777 if (ti
->tileh
& SLOPE_N
) {
1779 if (RemoveHalftileSlope(ti
->tileh
) == SLOPE_STEEP_N
) z
+= TILE_HEIGHT
;
1781 if (IsHalftileSlope(ti
->tileh
)) {
1782 Corner halftile_corner
= GetHalftileSlopeCorner(ti
->tileh
);
1783 if ((halftile_corner
== CORNER_W
) || (halftile_corner
== CORNER_E
)) z
+= TILE_HEIGHT
;
1784 if (halftile_corner
!= CORNER_S
) {
1785 foundation_part
= FOUNDATION_PART_HALFTILE
;
1786 if (IsSteepSlope(ti
->tileh
)) z
-= TILE_HEIGHT
;
1789 DrawSelectionSprite(SPR_DOT
, PAL_NONE
, ti
, z
, foundation_part
);
1794 if (ti
->tile
== TileVirtXY(_thd
.pos
.x
, _thd
.pos
.y
)) {
1795 assert((_thd
.drawstyle
& HT_DIR_MASK
) < HT_DIR_END
);
1796 DrawAutorailSelection(ti
, _thd
.drawstyle
& HT_DIR_MASK
);
1801 HighLightStyle type
= GetPartOfAutoLine(ti
->x
, ti
->y
, _thd
.selstart
, _thd
.selend
, _thd
.drawstyle
& HT_DIR_MASK
);
1802 if (type
< HT_DIR_END
) {
1803 DrawAutorailSelection(ti
, type
);
1804 } else if (_thd
.dir2
< HT_DIR_END
) {
1805 type
= GetPartOfAutoLine(ti
->x
, ti
->y
, _thd
.selstart2
, _thd
.selend2
, _thd
.dir2
);
1806 if (type
< HT_DIR_END
) DrawAutorailSelection(ti
, type
, PALETTE_SEL_TILE_BLUE
);
1814 * Returns the y coordinate in the viewport coordinate system where the given
1816 * @param tile Any tile.
1817 * @return The viewport y coordinate where the tile is painted.
1819 static int GetViewportY(Point tile
)
1821 /* Each increment in X or Y direction moves down by half a tile, i.e. TILE_PIXELS / 2. */
1822 return (tile
.y
* (int)(TILE_PIXELS
/ 2) + tile
.x
* (int)(TILE_PIXELS
/ 2) - TilePixelHeightOutsideMap(tile
.x
, tile
.y
)) << ZOOM_BASE_SHIFT
;
1826 * Add the landscape to the viewport, i.e. all ground tiles and buildings.
1828 static void ViewportAddLandscape()
1830 dbg_assert(_vdd
->dpi
.top
<= _vdd
->dpi
.top
+ _vdd
->dpi
.height
);
1831 dbg_assert(_vdd
->dpi
.left
<= _vdd
->dpi
.left
+ _vdd
->dpi
.width
);
1833 Point upper_left
= InverseRemapCoords(_vdd
->dpi
.left
, _vdd
->dpi
.top
);
1834 Point upper_right
= InverseRemapCoords(_vdd
->dpi
.left
+ _vdd
->dpi
.width
, _vdd
->dpi
.top
);
1836 /* Transformations between tile coordinates and viewport rows/columns: See vp_column_row
1839 * x = (row - column) / 2
1840 * y = (row + column) / 2
1841 * Note: (row, columns) pairs are only valid, if they are both even or both odd.
1844 /* Columns overlap with neighbouring columns by a half tile.
1845 * - Left column is column of upper_left (rounded down) and one column to the left.
1846 * - Right column is column of upper_right (rounded up) and one column to the right.
1847 * Note: Integer-division does not round down for negative numbers, so ensure rounding with another increment/decrement.
1849 int left_column
= DivTowardsNegativeInf(upper_left
.y
- upper_left
.x
, (int)TILE_SIZE
) - 1;
1850 int right_column
= DivTowardsPositiveInf(upper_right
.y
- upper_right
.x
, (int)TILE_SIZE
) + 1;
1852 int potential_bridge_height
= ZOOM_BASE
* TILE_HEIGHT
* _settings_game
.construction
.max_bridge_height
;
1854 /* Rows overlap with neighbouring rows by a half tile.
1855 * The first row that could possibly be visible is the row above upper_left (if it is at height 0).
1856 * Due to integer-division not rounding down for negative numbers, we need another decrement.
1858 int row
= DivTowardsNegativeInf(upper_left
.y
+ upper_left
.x
, (int)TILE_SIZE
) - 1;
1859 bool last_row
= false;
1860 for (; !last_row
; row
++) {
1862 for (int column
= left_column
; column
<= right_column
; column
++) {
1863 /* Valid row/column? */
1864 if ((row
+ column
) % 2 != 0) continue;
1867 tilecoord
.x
= (row
- column
) / 2;
1868 tilecoord
.y
= (row
+ column
) / 2;
1869 dbg_assert(column
== tilecoord
.y
- tilecoord
.x
);
1870 dbg_assert(row
== tilecoord
.y
+ tilecoord
.x
);
1873 _cur_ti
.x
= tilecoord
.x
* TILE_SIZE
;
1874 _cur_ti
.y
= tilecoord
.y
* TILE_SIZE
;
1876 if (IsInsideBS(tilecoord
.x
, 0, MapSizeX()) && IsInsideBS(tilecoord
.y
, 0, MapSizeY())) {
1877 /* This includes the south border at MapMaxX / MapMaxY. When terraforming we still draw tile selections there. */
1878 _cur_ti
.tile
= TileXY(tilecoord
.x
, tilecoord
.y
);
1879 tile_type
= GetTileType(_cur_ti
.tile
);
1881 _cur_ti
.tile
= INVALID_TILE
;
1882 tile_type
= MP_VOID
;
1885 if (tile_type
!= MP_VOID
) {
1886 /* We are inside the map => paint landscape. */
1887 std::tie(_cur_ti
.tileh
, _cur_ti
.z
) = GetTilePixelSlope(_cur_ti
.tile
);
1889 /* We are outside the map => paint black. */
1890 std::tie(_cur_ti
.tileh
, _cur_ti
.z
) = GetTilePixelSlopeOutsideMap(tilecoord
.x
, tilecoord
.y
);
1893 int viewport_y
= GetViewportY(tilecoord
);
1895 if (viewport_y
+ MAX_TILE_EXTENT_BOTTOM
< _vdd
->dpi
.top
) {
1896 /* The tile in this column is not visible yet.
1897 * Tiles in other columns may be visible, but we need more rows in any case. */
1902 int min_visible_height
= viewport_y
- (_vdd
->dpi
.top
+ _vdd
->dpi
.height
);
1903 bool tile_visible
= min_visible_height
<= 0;
1905 if (tile_type
!= MP_VOID
) {
1906 /* Is tile with buildings visible? */
1907 if (min_visible_height
< MAX_TILE_EXTENT_TOP
) tile_visible
= true;
1909 if (IsBridgeAbove(_cur_ti
.tile
)) {
1910 /* Is the bridge visible? */
1911 TileIndex bridge_tile
= GetNorthernBridgeEnd(_cur_ti
.tile
);
1912 int bridge_height
= ZOOM_BASE
* (GetBridgePixelHeight(bridge_tile
) - TilePixelHeight(_cur_ti
.tile
));
1913 if (min_visible_height
< bridge_height
+ MAX_TILE_EXTENT_TOP
) tile_visible
= true;
1916 /* Would a higher bridge on a more southern tile be visible?
1917 * If yes, we need to loop over more rows to possibly find one. */
1918 if (min_visible_height
< potential_bridge_height
+ MAX_TILE_EXTENT_TOP
) last_row
= false;
1920 /* Outside of map. If we are on the north border of the map, there may still be a bridge visible,
1921 * so we need to loop over more rows to possibly find one. */
1922 if ((tilecoord
.x
<= 0 || tilecoord
.y
<= 0) && min_visible_height
< potential_bridge_height
+ MAX_TILE_EXTENT_TOP
) last_row
= false;
1924 if (_settings_game
.construction
.map_edge_mode
== 2 && _cur_ti
.tileh
== SLOPE_FLAT
&& _cur_ti
.z
== 0 && min_visible_height
<= 0) {
1926 AddTileSpriteToDraw(SPR_FLAT_WATER_TILE
, PAL_NONE
, _cur_ti
.x
, _cur_ti
.y
, _cur_ti
.z
);
1933 _vd
.foundation_part
= FOUNDATION_PART_NONE
;
1934 _vd
.foundation
[0] = -1;
1935 _vd
.foundation
[1] = -1;
1936 _vd
.last_foundation_child
[0] = NO_CHILD_STORE
;
1937 _vd
.last_foundation_child
[1] = NO_CHILD_STORE
;
1939 bool no_ground_tiles
= min_visible_height
> 0;
1940 _tile_type_procs
[tile_type
]->draw_tile_proc(&_cur_ti
, { min_visible_height
, no_ground_tiles
});
1941 if (_cur_ti
.tile
!= INVALID_TILE
&& min_visible_height
<= 0) {
1942 DrawTileSelection(&_cur_ti
);
1943 DrawTileZoning(&_cur_ti
);
1951 * Add a string to draw in the viewport
1952 * @param vdd viewport drawer
1953 * @param dpi current viewport area
1954 * @param small_from Zoomlevel from when the small font should be used
1955 * @param sign sign position and dimension
1956 * @param string_normal String for normal and 2x zoom level
1957 * @param string_small String for 4x and 8x zoom level
1958 * @param string_small_shadow Shadow string for 4x and 8x zoom level; or #STR_NULL if no shadow
1959 * @param colour colour of the sign background; or INVALID_COLOUR if transparent
1961 void ViewportAddString(ViewportDrawerDynamic
*vdd
, const DrawPixelInfo
*dpi
, ZoomLevel small_from
, const ViewportSign
*sign
, StringID string_normal
, StringID string_small
, StringID string_small_shadow
, uint64_t params_1
, uint64_t params_2
, Colours colour
)
1963 bool small
= dpi
->zoom
>= small_from
;
1965 int left
= dpi
->left
;
1967 int right
= left
+ dpi
->width
;
1968 int bottom
= top
+ dpi
->height
;
1970 int sign_height
= ScaleByZoom(WidgetDimensions::scaled
.fullbevel
.top
+ GetCharacterHeight(small
? FS_SMALL
: FS_NORMAL
) + WidgetDimensions::scaled
.fullbevel
.bottom
, dpi
->zoom
);
1971 int sign_half_width
= ScaleByZoom((small
? sign
->width_small
: sign
->width_normal
) / 2, dpi
->zoom
);
1973 if (bottom
< sign
->top
||
1974 top
> sign
->top
+ sign_height
||
1975 right
< sign
->center
- sign_half_width
||
1976 left
> sign
->center
+ sign_half_width
) {
1981 AddStringToDraw(vdd
, sign
->center
- sign_half_width
, sign
->top
, string_normal
, params_1
, params_2
, colour
, sign
->width_normal
);
1983 int shadow_offset
= 0;
1984 if (string_small_shadow
!= STR_NULL
) {
1986 AddStringToDraw(vdd
, sign
->center
- sign_half_width
+ shadow_offset
, sign
->top
, string_small_shadow
, params_1
, params_2
, INVALID_COLOUR
, sign
->width_small
| 0x8000);
1988 AddStringToDraw(vdd
, sign
->center
- sign_half_width
, sign
->top
- shadow_offset
, string_small
, params_1
, params_2
,
1989 colour
, sign
->width_small
| 0x8000);
1993 static Rect
ExpandRectWithViewportSignMargins(Rect r
, ZoomLevel zoom
)
1995 const int fh
= std::max(GetCharacterHeight(FS_NORMAL
), GetCharacterHeight(FS_SMALL
));
1996 const int max_tw
= _viewport_sign_maxwidth
/ 2 + 1;
1997 const int expand_y
= ScaleByZoom(WidgetDimensions::scaled
.fullbevel
.top
+ fh
+ WidgetDimensions::scaled
.fullbevel
.bottom
, zoom
);
1998 const int expand_x
= ScaleByZoom(WidgetDimensions::scaled
.fullbevel
.left
+ max_tw
+ WidgetDimensions::scaled
.fullbevel
.right
, zoom
);
2001 r
.right
+= expand_x
;
2003 r
.bottom
+= expand_y
;
2008 static void ViewportAddKdtreeSigns(ViewportDrawerDynamic
*vdd
, DrawPixelInfo
*dpi
, bool towns_only
)
2010 Rect search_rect
{ dpi
->left
, dpi
->top
, dpi
->left
+ dpi
->width
, dpi
->top
+ dpi
->height
};
2011 search_rect
= ExpandRectWithViewportSignMargins(search_rect
, dpi
->zoom
);
2013 bool show_stations
= HasBit(_display_opt
, DO_SHOW_STATION_NAMES
) && _game_mode
!= GM_MENU
&& !towns_only
;
2014 bool show_waypoints
= HasBit(_display_opt
, DO_SHOW_WAYPOINT_NAMES
) && _game_mode
!= GM_MENU
&& !towns_only
;
2015 bool show_towns
= HasBit(_display_opt
, DO_SHOW_TOWN_NAMES
) && _game_mode
!= GM_MENU
;
2016 bool show_signs
= HasBit(_display_opt
, DO_SHOW_SIGNS
) && !vdd
->IsInvisibilitySet(TO_SIGNS
) && !towns_only
;
2017 bool show_competitors
= HasBit(_display_opt
, DO_SHOW_COMPETITOR_SIGNS
) && !towns_only
;
2018 bool hide_hidden_waypoints
= _settings_client
.gui
.allow_hiding_waypoint_labels
&& !HasBit(_extra_display_opt
, XDO_SHOW_HIDDEN_SIGNS
);
2020 /* Collect all the items first and draw afterwards, to ensure layering */
2021 std::vector
<const BaseStation
*> stations
;
2022 std::vector
<const Town
*> towns
;
2023 std::vector
<const Sign
*> signs
;
2025 _viewport_sign_kdtree
.FindContained(search_rect
.left
, search_rect
.top
, search_rect
.right
, search_rect
.bottom
, [&](const ViewportSignKdtreeItem
& item
) {
2026 switch (item
.type
) {
2027 case ViewportSignKdtreeItem::VKI_STATION
: {
2028 if (!show_stations
) break;
2029 const BaseStation
*st
= BaseStation::Get(item
.id
.station
);
2031 /* Don't draw if station is owned by another company and competitor station names are hidden. Stations owned by none are never ignored. */
2032 if (!show_competitors
&& _local_company
!= st
->owner
&& st
->owner
!= OWNER_NONE
) break;
2034 stations
.push_back(st
);
2038 case ViewportSignKdtreeItem::VKI_WAYPOINT
: {
2039 if (!show_waypoints
) break;
2040 const BaseStation
*st
= BaseStation::Get(item
.id
.station
);
2042 /* Don't draw if station is owned by another company and competitor station names are hidden. Stations owned by none are never ignored. */
2043 if (!show_competitors
&& _local_company
!= st
->owner
&& st
->owner
!= OWNER_NONE
) break;
2044 if (hide_hidden_waypoints
&& HasBit(Waypoint::From(st
)->waypoint_flags
, WPF_HIDE_LABEL
)) break;
2046 stations
.push_back(st
);
2050 case ViewportSignKdtreeItem::VKI_TOWN
:
2051 if (!show_towns
) break;
2052 towns
.push_back(Town::Get(item
.id
.town
));
2055 case ViewportSignKdtreeItem::VKI_SIGN
: {
2056 if (!show_signs
) break;
2057 const Sign
*si
= Sign::Get(item
.id
.sign
);
2059 /* Don't draw if sign is owned by another company and competitor signs should be hidden.
2060 * Note: It is intentional that also signs owned by OWNER_NONE are hidden. Bankrupt
2061 * companies can leave OWNER_NONE signs after them. */
2062 if (!show_competitors
&& si
->IsCompetitorOwned()) break;
2064 signs
.push_back(si
);
2073 /* Layering order (bottom to top): Town names, signs, stations */
2075 for (const auto *t
: towns
) {
2076 ViewportAddString(vdd
, dpi
, ZOOM_LVL_OUT_4X
, &t
->cache
.sign
,
2077 STR_VIEWPORT_TOWN_LABEL
, STR_VIEWPORT_TOWN_LABEL_TINY
, STR_VIEWPORT_TOWN_TINY_BLACK
,
2078 t
->index
, t
->LabelParam2());
2081 /* Do not draw signs nor station names if they are set invisible */
2082 if (vdd
->IsInvisibilitySet(TO_SIGNS
)) return;
2084 for (const auto *si
: signs
) {
2085 ViewportAddString(vdd
, dpi
, ZOOM_LVL_OUT_4X
, &si
->sign
,
2087 (vdd
->IsTransparencySet(TO_SIGNS
) || si
->owner
== OWNER_DEITY
) ? STR_VIEWPORT_SIGN_SMALL_WHITE
: STR_VIEWPORT_SIGN_SMALL_BLACK
, STR_NULL
,
2088 si
->index
, 0, (si
->owner
== OWNER_NONE
) ? COLOUR_GREY
: (si
->owner
== OWNER_DEITY
? INVALID_COLOUR
: _company_colours
[si
->owner
]));
2091 for (const auto *st
: stations
) {
2092 if (Station::IsExpected(st
)) {
2094 ViewportAddString(vdd
, dpi
, ZOOM_LVL_OUT_4X
, &st
->sign
,
2095 STR_VIEWPORT_STATION
, STR_VIEWPORT_STATION_TINY
, STR_NULL
,
2096 st
->index
, st
->facilities
, (st
->owner
== OWNER_NONE
|| !st
->IsInUse()) ? COLOUR_GREY
: _company_colours
[st
->owner
]);
2099 ViewportAddString(vdd
, dpi
, ZOOM_LVL_OUT_4X
, &st
->sign
,
2100 STR_VIEWPORT_WAYPOINT
, STR_VIEWPORT_WAYPOINT_TINY
, STR_NULL
,
2101 st
->index
, st
->facilities
, (st
->owner
== OWNER_NONE
|| !st
->IsInUse()) ? COLOUR_GREY
: _company_colours
[st
->owner
]);
2108 * Update the position of the viewport sign.
2109 * @param center the (preferred) center of the viewport sign
2110 * @param top the new top of the sign
2111 * @param str the string to show in the sign
2112 * @param str_small the string to show when zoomed out. STR_NULL means same as \a str
2114 void ViewportSign::UpdatePosition(ZoomLevel maxzoom
, int center
, int top
, StringID str
, StringID str_small
)
2116 if (this->width_normal
!= 0) this->MarkDirty(maxzoom
);
2120 format_buffer buffer
;
2122 AppendStringInPlace(buffer
, str
);
2123 this->width_normal
= WidgetDimensions::scaled
.fullbevel
.left
+ Align(GetStringBoundingBox(buffer
).width
, 2) + WidgetDimensions::scaled
.fullbevel
.right
;
2124 this->center
= center
;
2126 /* zoomed out version */
2127 if (str_small
!= STR_NULL
) {
2129 AppendStringInPlace(buffer
, str_small
);
2131 this->width_small
= WidgetDimensions::scaled
.fullbevel
.left
+ Align(GetStringBoundingBox(buffer
, FS_SMALL
).width
, 2) + WidgetDimensions::scaled
.fullbevel
.right
;
2133 this->MarkDirty(maxzoom
);
2137 * Mark the sign dirty in all viewports.
2138 * @param maxzoom Maximum %ZoomLevel at which the text is visible.
2142 void ViewportSign::MarkDirty(ZoomLevel maxzoom
) const
2144 if (maxzoom
== ZOOM_LVL_END
) return;
2146 Rect zoomlevels
[ZOOM_LVL_END
];
2148 const uint small_height
= WidgetDimensions::scaled
.fullbevel
.top
+ GetCharacterHeight(FS_SMALL
) + WidgetDimensions::scaled
.fullbevel
.bottom
+ 1;
2149 const uint normal_height
= WidgetDimensions::scaled
.fullbevel
.top
+ GetCharacterHeight(FS_NORMAL
) + WidgetDimensions::scaled
.fullbevel
.bottom
+ 1;
2151 for (ZoomLevel zoom
= ZOOM_LVL_BEGIN
; zoom
!= ZOOM_LVL_END
; zoom
++) {
2152 const ZoomLevel small_from
= (maxzoom
== ZOOM_LVL_OUT_2X
) ? ZOOM_LVL_OUT_2X
: ZOOM_LVL_OUT_4X
;
2153 const int width
= (zoom
>= small_from
) ? this->width_small
: this->width_normal
;
2154 zoomlevels
[zoom
].left
= this->center
- ScaleByZoom(width
/ 2 + 1, zoom
);
2155 zoomlevels
[zoom
].top
= this->top
- ScaleByZoom(1, zoom
);
2156 zoomlevels
[zoom
].right
= this->center
+ ScaleByZoom(width
/ 2 + 1, zoom
);
2157 zoomlevels
[zoom
].bottom
= this->top
+ ScaleByZoom((zoom
>= small_from
) ? small_height
: normal_height
, zoom
);
2160 for (Viewport
*vp
: _viewport_window_cache
) {
2161 if (vp
->zoom
<= maxzoom
) {
2162 Rect
&zl
= zoomlevels
[vp
->zoom
];
2163 MarkViewportDirty(vp
, zl
.left
, zl
.top
, zl
.right
, zl
.bottom
, VMDF_NONE
);
2168 static void ViewportDrawTileSprites(const ViewportDrawerDynamic
*vdd
)
2170 for (const TileSpriteToDraw
&ts
: vdd
->tile_sprites_to_draw
) {
2171 DrawSpriteViewport(vdd
->sprite_data
, &vdd
->dpi
, ts
.image
, ts
.pal
, ts
.x
, ts
.y
, ts
.sub
);
2175 /** This fallback sprite checker always exists. */
2176 static bool ViewportSortParentSpritesChecker()
2181 static void ViewportSortParentSpritesSingleComparison(ParentSpriteToDraw
*ps
, ParentSpriteToDraw
*ps2
, ParentSpriteToDraw
*ps_to_move
, ParentSpriteToDraw
**psd
, ParentSpriteToDraw
**psd2
)
2183 /* Decide which comparator to use, based on whether the bounding
2186 if (ps
->xmax
>= ps2
->xmin
&& ps
->xmin
<= ps2
->xmax
&& // overlap in X?
2187 ps
->ymax
>= ps2
->ymin
&& ps
->ymin
<= ps2
->ymax
&& // overlap in Y?
2188 ps
->zmax
>= ps2
->zmin
&& ps
->zmin
<= ps2
->zmax
) { // overlap in Z?
2189 /* Use X+Y+Z as the sorting order, so sprites closer to the bottom of
2190 * the screen and with higher Z elevation, are drawn in front.
2191 * Here X,Y,Z are the coordinates of the "center of mass" of the sprite,
2192 * i.e. X=(left+right)/2, etc.
2193 * However, since we only care about order, don't actually divide / 2
2195 if (ps
->xmin
+ ps
->xmax
+ ps
->ymin
+ ps
->ymax
+ ps
->zmin
+ ps
->zmax
<=
2196 ps2
->xmin
+ ps2
->xmax
+ ps2
->ymin
+ ps2
->ymax
+ ps2
->zmin
+ ps2
->zmax
) {
2200 /* We only change the order, if it is definite.
2201 * I.e. every single order of X, Y, Z says ps2 is behind ps or they overlap.
2202 * That is: If one partial order says ps behind ps2, do not change the order.
2204 if (ps
->xmax
< ps2
->xmin
||
2205 ps
->ymax
< ps2
->ymin
||
2206 ps
->zmax
< ps2
->zmin
) {
2211 /* Move ps_to_move (ps2) in front of ps */
2212 ParentSpriteToDraw
*temp
= ps_to_move
;
2213 for (auto psd3
= psd2
; psd3
> psd
; psd3
--) {
2214 *psd3
= *(psd3
- 1);
2219 bool ViewportSortParentSpritesSpecial(ParentSpriteToDraw
*ps
, ParentSpriteToDraw
*ps2
, ParentSpriteToDraw
**psd
, ParentSpriteToDraw
**psd2
)
2221 ParentSpriteToDraw temp
;
2223 auto is_bridge_diag_veh_comparison
= [&](ParentSpriteToDraw
*a
, ParentSpriteToDraw
*b
) -> bool {
2224 if ((a
->special_flags
& VSSSF_SORT_SPECIAL_TYPE_MASK
) == VSSSF_SORT_SORT_BRIDGE_BB
&& (b
->special_flags
& VSSSF_SORT_SPECIAL_TYPE_MASK
) == VSSSF_SORT_DIAG_VEH
&& a
->zmin
> b
->zmax
) {
2233 if (is_bridge_diag_veh_comparison(ps
, ps2
)) {
2234 ViewportSortParentSpritesSingleComparison(&temp
, ps2
, ps2
, psd
, psd2
);
2237 if (is_bridge_diag_veh_comparison(ps2
, ps
)) {
2238 ViewportSortParentSpritesSingleComparison(ps
, &temp
, ps2
, psd
, psd2
);
2245 /** Sort parent sprites pointer array */
2246 static void ViewportSortParentSprites(ParentSpriteToSortVector
*psdv
)
2248 ParentSpriteToDraw
** const psdvend
= psdv
->data() + psdv
->size();
2249 ParentSpriteToDraw
**psd
= psdv
->data();
2250 while (psd
!= psdvend
) {
2251 ParentSpriteToDraw
*ps
= *psd
;
2253 if (ps
->IsComparisonDone()) {
2258 ps
->SetComparisonDone(true);
2259 const bool is_special
= (ps
->special_flags
& VSSSF_SORT_SPECIAL
) != 0;
2261 for (auto psd2
= psd
+ 1; psd2
!= psdvend
; psd2
++) {
2262 ParentSpriteToDraw
*ps2
= *psd2
;
2264 if (ps2
->IsComparisonDone()) continue;
2266 if (is_special
&& (ps2
->special_flags
& VSSSF_SORT_SPECIAL
) != 0) {
2267 if (ViewportSortParentSpritesSpecial(ps
, ps2
, psd
, psd2
)) continue;
2270 ViewportSortParentSpritesSingleComparison(ps
, ps2
, ps2
, psd
, psd2
);
2275 static void ViewportDrawParentSprites(const ViewportDrawerDynamic
*vdd
, const DrawPixelInfo
*dpi
, const ParentSpriteToSortVector
*psd
, const ChildScreenSpriteToDrawVector
*csstdv
)
2277 for (const ParentSpriteToDraw
*ps
: *psd
) {
2278 if (ps
->image
!= SPR_EMPTY_BOUNDING_BOX
) DrawSpriteViewport(vdd
->sprite_data
, dpi
, ps
->image
, ps
->pal
, ps
->x
, ps
->y
, vdd
->parent_sprite_subsprites
.Get(ps
));
2280 int child_idx
= ps
->first_child
;
2281 while (child_idx
>= 0) {
2282 const ChildScreenSpriteToDraw
*cs
= csstdv
->data() + child_idx
;
2283 child_idx
= cs
->next
;
2286 switch (cs
->position_mode
) {
2287 case ChildScreenSpritePositionMode::Relative
:
2291 case ChildScreenSpritePositionMode::NonRelative
:
2295 case ChildScreenSpritePositionMode::Absolute
:
2299 DrawSpriteViewport(vdd
->sprite_data
, dpi
, cs
->image
, cs
->pal
, x
, y
, cs
->sub
);
2305 * Draws the bounding boxes of all ParentSprites
2306 * @param psd Array of ParentSprites
2308 static void ViewportDrawBoundingBoxes(const DrawPixelInfo
*dpi
, const ParentSpriteToDrawVector
&psd
)
2310 for (const ParentSpriteToDraw
&ps
: psd
) {
2311 Point pt1
= RemapCoords(ps
.xmax
+ 1, ps
.ymax
+ 1, ps
.zmax
+ 1); // top front corner
2312 Point pt2
= RemapCoords(ps
.xmin
, ps
.ymax
+ 1, ps
.zmax
+ 1); // top left corner
2313 Point pt3
= RemapCoords(ps
.xmax
+ 1, ps
.ymin
, ps
.zmax
+ 1); // top right corner
2314 Point pt4
= RemapCoords(ps
.xmax
+ 1, ps
.ymax
+ 1, ps
.zmin
); // bottom front corner
2318 pt2
.x
- pt1
.x
, pt2
.y
- pt1
.y
,
2319 pt3
.x
- pt1
.x
, pt3
.y
- pt1
.y
,
2320 pt4
.x
- pt1
.x
, pt4
.y
- pt1
.y
);
2324 static void ViewportMapStoreBridge(const Viewport
* const vp
, const TileIndex tile
)
2326 extern LegendAndColour _legend_land_owners
[NUM_NO_COMPANY_ENTRIES
+ MAX_COMPANIES
+ 1];
2327 extern uint _company_to_list_pos
[MAX_COMPANIES
];
2329 /* No need to bother for hidden things */
2330 if (!_settings_client
.gui
.show_bridges_on_map
) return;
2331 const Owner o
= GetTileOwner(tile
);
2332 if (o
< MAX_COMPANIES
&& !_legend_land_owners
[_company_to_list_pos
[o
]].show_on_map
) return;
2334 switch (GetTunnelBridgeDirection(tile
)) {
2336 /* X axis: tile at higher coordinate, facing towards lower coordinate */
2337 auto iter
= _vdd
->bridge_to_map_x
.lower_bound(tile
);
2338 if (iter
!= _vdd
->bridge_to_map_x
.begin()) {
2341 if (prev
->second
== tile
) return;
2343 _vdd
->bridge_to_map_x
.insert(iter
, std::make_pair(GetOtherTunnelBridgeEnd(tile
), tile
));
2348 /* Y axis: tile at higher coordinate, facing towards lower coordinate */
2349 auto iter
= _vdd
->bridge_to_map_y
.lower_bound(tile
);
2350 if (iter
!= _vdd
->bridge_to_map_y
.begin()) {
2353 if (prev
->second
== tile
) return;
2355 _vdd
->bridge_to_map_y
.insert(iter
, std::make_pair(GetOtherTunnelBridgeEnd(tile
), tile
));
2360 /* X axis: tile at lower coordinate, facing towards higher coordinate */
2361 auto iter
= _vdd
->bridge_to_map_x
.lower_bound(tile
);
2362 if (iter
!= _vdd
->bridge_to_map_x
.end() && iter
->first
== tile
) return;
2363 _vdd
->bridge_to_map_x
.insert(iter
, std::make_pair(tile
, GetOtherTunnelBridgeEnd(tile
)));
2368 /* Y axis: tile at lower coordinate, facing towards higher coordinate */
2369 auto iter
= _vdd
->bridge_to_map_y
.lower_bound(tile
);
2370 if (iter
!= _vdd
->bridge_to_map_y
.end() && iter
->first
== tile
) return;
2371 _vdd
->bridge_to_map_y
.insert(iter
, std::make_pair(tile
, GetOtherTunnelBridgeEnd(tile
)));
2380 void ViewportMapStoreTunnel(const TileIndex tile
, const TileIndex tile_south
, const int tunnel_z
, const bool insert_sorted
)
2382 extern LegendAndColour _legend_land_owners
[NUM_NO_COMPANY_ENTRIES
+ MAX_COMPANIES
+ 1];
2383 extern uint _company_to_list_pos
[MAX_COMPANIES
];
2385 /* No need to bother for hidden things */
2386 if (!_settings_client
.gui
.show_tunnels_on_map
) return;
2387 const Owner o
= GetTileOwner(tile
);
2388 if (o
< MAX_COMPANIES
&& !_legend_land_owners
[_company_to_list_pos
[o
]].show_on_map
) return;
2390 const Axis axis
= (TileX(tile
) == TileX(tile_south
)) ? AXIS_Y
: AXIS_X
;
2391 const Point viewport_pt
= RemapCoords(TileX(tile
) * TILE_SIZE
, TileY(tile
) * TILE_SIZE
, tunnel_z
);
2393 if (axis
== AXIS_X
) {
2395 y_intercept
= viewport_pt
.y
+ (viewport_pt
.x
/ 2);
2398 y_intercept
= viewport_pt
.y
- (viewport_pt
.x
/ 2);
2400 TunnelToMapStorage
&storage
= (axis
== AXIS_X
) ? _vd
.tunnel_to_map_x
: _vd
.tunnel_to_map_y
;
2402 if (insert_sorted
) {
2403 auto iter
= std::upper_bound(storage
.tunnels
.begin(), storage
.tunnels
.end(), y_intercept
, [](int a
, const TunnelToMap
&b
) -> bool {
2404 return a
< b
.y_intercept
;
2406 tbtm
= &(*(storage
.tunnels
.emplace(iter
)));
2408 storage
.tunnels
.emplace_back();
2409 tbtm
= &(storage
.tunnels
.back());
2412 /* ensure deterministic ordering, to avoid render flicker */
2413 tbtm
->tb
.from_tile
= tile
;
2414 tbtm
->tb
.to_tile
= tile_south
;
2415 tbtm
->y_intercept
= y_intercept
;
2416 tbtm
->tunnel_z
= tunnel_z
;
2419 void ViewportMapClearTunnelCache()
2421 _vd
.tunnel_to_map_x
.tunnels
.clear();
2422 _vd
.tunnel_to_map_y
.tunnels
.clear();
2425 void ViewportMapInvalidateTunnelCacheByTile(const TileIndex tile
, const Axis axis
)
2427 if (!_settings_client
.gui
.show_tunnels_on_map
) return;
2428 std::vector
<TunnelToMap
> &tbtmv
= (axis
== AXIS_X
) ? _vd
.tunnel_to_map_x
.tunnels
: _vd
.tunnel_to_map_y
.tunnels
;
2429 for (auto tbtm
= tbtmv
.begin(); tbtm
!= tbtmv
.end(); tbtm
++) {
2430 if (tbtm
->tb
.from_tile
== tile
) {
2437 void ViewportMapBuildTunnelCache()
2439 ViewportMapClearTunnelCache();
2440 if (_settings_client
.gui
.show_tunnels_on_map
) {
2441 for (Tunnel
*tunnel
: Tunnel::Iterate()) {
2442 ViewportMapStoreTunnel(tunnel
->tile_n
, tunnel
->tile_s
, tunnel
->height
, false);
2444 auto sorter
= [](const TunnelToMap
&a
, const TunnelToMap
&b
) -> bool {
2445 return a
.y_intercept
< b
.y_intercept
;
2447 std::sort(_vd
.tunnel_to_map_x
.tunnels
.begin(), _vd
.tunnel_to_map_x
.tunnels
.end(), sorter
);
2448 std::sort(_vd
.tunnel_to_map_y
.tunnels
.begin(), _vd
.tunnel_to_map_y
.tunnels
.end(), sorter
);
2453 * Draw/colour the blocks that have been redrawn.
2455 void ViewportDrawDirtyBlocks(const DrawPixelInfo
*dpi
, bool increment_colour
)
2457 Blitter
*blitter
= BlitterFactory::GetCurrentBlitter();
2459 int right
= UnScaleByZoom(dpi
->width
, dpi
->zoom
);
2460 int bottom
= UnScaleByZoom(dpi
->height
, dpi
->zoom
);
2462 const uint dirty_block_colour
= increment_colour
? _dirty_block_colour
.fetch_add(1, std::memory_order_relaxed
) : _dirty_block_colour
.load(std::memory_order_relaxed
);
2463 int colour
= _string_colourmap
[dirty_block_colour
& 0xF];
2467 uint8_t bo
= UnScaleByZoom(dpi
->left
+ dpi
->top
, dpi
->zoom
) & 1;
2469 for (int i
= (bo
^= 1); i
< right
; i
+= 2) blitter
->SetPixel(dst
, i
, 0, (uint8_t)colour
);
2470 dst
= blitter
->MoveTo(dst
, 0, 1);
2471 } while (--bottom
> 0);
2474 static void ViewportDrawStrings(ViewportDrawerDynamic
*vdd
, ZoomLevel zoom
, const StringSpriteToDrawVector
*sstdv
)
2476 for (const StringSpriteToDraw
&ss
: *sstdv
) {
2477 TextColour colour
= TC_BLACK
;
2478 bool small
= HasBit(ss
.width
, 15);
2479 int w
= GB(ss
.width
, 0, 15);
2480 int x
= UnScaleByZoom(ss
.x
, zoom
);
2481 int y
= UnScaleByZoom(ss
.y
, zoom
);
2482 int h
= WidgetDimensions::scaled
.fullbevel
.Vertical() + GetCharacterHeight(small
? FS_SMALL
: FS_NORMAL
);
2484 SetDParam(0, ss
.params
[0]);
2485 SetDParam(1, ss
.params
[1]);
2487 if (ss
.colour
!= INVALID_COLOUR
) {
2488 if (vdd
->IsTransparencySet(TO_SIGNS
) && ss
.string
!= STR_WHITE_SIGN
) {
2489 /* Don't draw the rectangle.
2490 * Real colours need the TC_IS_PALETTE_COLOUR flag.
2491 * Otherwise colours from _string_colourmap are assumed. */
2492 colour
= (TextColour
)GetColourGradient(ss
.colour
, SHADE_LIGHTER
) | TC_IS_PALETTE_COLOUR
;
2494 /* Draw the rectangle if 'transparent station signs' is off,
2495 * or if we are drawing a general text sign (STR_WHITE_SIGN). */
2497 x
, y
, x
+ w
, y
+ h
, ss
.colour
,
2498 vdd
->IsTransparencySet(TO_SIGNS
) ? FR_TRANSPARENT
: FR_NONE
2503 DrawString(x
+ WidgetDimensions::scaled
.fullbevel
.left
, x
+ w
- 1 - WidgetDimensions::scaled
.fullbevel
.right
, y
+ WidgetDimensions::scaled
.fullbevel
.top
, ss
.string
, colour
, SA_HOR_CENTER
, false, small
? FS_SMALL
: FS_NORMAL
);
2507 static inline Vehicle
*GetVehicleFromWindow(const Window
*w
)
2510 WindowClass wc
= w
->window_class
;
2511 WindowNumber wn
= w
->window_number
;
2513 if (wc
== WC_DROPDOWN_MENU
) GetDropDownParentWindowInfo(w
, wc
, wn
);
2516 case WC_VEHICLE_VIEW
:
2517 case WC_VEHICLE_ORDERS
:
2518 case WC_VEHICLE_TIMETABLE
:
2519 case WC_VEHICLE_DETAILS
:
2520 case WC_VEHICLE_REFIT
:
2521 case WC_VEHICLE_CARGO_TYPE_LOAD_ORDERS
:
2522 case WC_VEHICLE_CARGO_TYPE_UNLOAD_ORDERS
:
2523 case WC_SCHDISPATCH_SLOTS
:
2524 if (wn
!= INVALID_VEHICLE
) return Vehicle::GetIfValid(wn
);
2526 case WC_TRAINS_LIST
:
2527 case WC_ROADVEH_LIST
:
2529 case WC_AIRCRAFT_LIST
: {
2530 VehicleListIdentifier vli
= VehicleListIdentifier::UnPack(wn
);
2531 if (vli
.type
== VL_SHARED_ORDERS
) {
2532 return Vehicle::GetIfValid(vli
.index
);
2543 static bool ViewportVehicleRouteShouldSkipOrder(const Order
*order
)
2545 if (_settings_client
.gui
.show_vehicle_route_mode
!= 2) return false;
2547 switch (order
->GetType()) {
2548 case OT_GOTO_STATION
:
2550 return (order
->GetNonStopType() & ONSF_NO_STOP_AT_DESTINATION_STATION
) != 0;
2557 void ViewportRouteOverlay::PrepareRoutePathsConditionalOrder(const Vehicle
*veh
, const Order
*order
, PrepareRouteStepState
&state
, bool conditional
, uint depth
)
2559 /* Prevent excessive recursion */
2560 if (depth
>= 10) return;
2562 for (; order
!= nullptr && state
.lines_added
< 16; order
= veh
->orders
->GetNext(order
)) {
2563 if (!state
.visited
.insert(order
).second
) {
2564 /* Already visited this order */
2568 if (ViewportVehicleRouteShouldSkipOrder(order
)) continue;
2570 if (order
->IsType(OT_CONDITIONAL
)) {
2571 this->PrepareRoutePathsConditionalOrder(veh
, veh
->GetOrder(order
->GetConditionSkipToOrder()), state
,
2572 conditional
|| order
->GetConditionVariable() != OCV_UNCONDITIONALLY
, depth
+ 1);
2573 if (order
->GetConditionVariable() == OCV_UNCONDITIONALLY
) return;
2578 const TileIndex to_tile
= order
->GetLocation(veh
, veh
->type
== VEH_AIRCRAFT
);
2579 if (to_tile
== INVALID_TILE
) continue;
2581 DrawnPathRouteTileLine path
= { state
.from_tile
, to_tile
, conditional
};
2582 if (path
.from_tile
> path
.to_tile
) std::swap(path
.from_tile
, path
.to_tile
);
2583 this->route_paths
.push_back(path
);
2584 state
.lines_added
++;
2589 void ViewportRouteOverlay::PrepareRoutePaths(const Vehicle
*veh
)
2591 this->route_paths
.clear();
2593 if (veh
== nullptr || !_settings_client
.gui
.show_vehicle_route
) return;
2595 PrepareRouteStepState state
;
2597 TileIndex from_tile
= INVALID_TILE
;
2598 bool conditional
= false;
2599 auto handle_order
= [&](const Order
*order
) -> bool {
2600 if (ViewportVehicleRouteShouldSkipOrder(order
)) return false;
2602 if (order
->IsType(OT_CONDITIONAL
) && from_tile
!= INVALID_TILE
) {
2603 state
.reset(from_tile
);
2604 this->PrepareRoutePathsConditionalOrder(veh
, order
, state
,
2605 conditional
|| order
->GetConditionVariable() != OCV_UNCONDITIONALLY
, 0);
2606 if (order
->GetConditionVariable() == OCV_UNCONDITIONALLY
) {
2607 from_tile
= INVALID_TILE
;
2614 const TileIndex to_tile
= order
->GetLocation(veh
, veh
->type
== VEH_AIRCRAFT
);
2615 if (to_tile
== INVALID_TILE
) return false;
2617 if (from_tile
!= INVALID_TILE
) {
2618 DrawnPathRouteTileLine path
= { from_tile
, to_tile
, conditional
};
2619 if (path
.from_tile
> path
.to_tile
) std::swap(path
.from_tile
, path
.to_tile
);
2620 this->route_paths
.push_back(path
);
2623 from_tile
= to_tile
;
2624 conditional
= false;
2628 for (const Order
*order
: veh
->Orders()) {
2629 handle_order(order
);
2631 if (from_tile
!= INVALID_TILE
) {
2632 /* Handle wrap around from last order back to first */
2633 for (const Order
*order
: veh
->Orders()) {
2634 if (handle_order(order
)) break;
2638 /* Remove duplicate lines */
2639 std::sort(this->route_paths
.begin(), this->route_paths
.end());
2640 auto unique_end
= std::unique(this->route_paths
.begin(), this->route_paths
.end(), [](const DrawnPathRouteTileLine
&a
, const DrawnPathRouteTileLine
&b
) {
2641 /* Consider elements with the same tile values but different order_conditional values as equal */
2642 return a
.from_tile
== b
.from_tile
&& a
.to_tile
== b
.to_tile
;
2644 this->route_paths
.erase(unique_end
, this->route_paths
.end());
2647 /** Draw the route of a vehicle. */
2648 void ViewportRouteOverlay::DrawVehicleRoutePath(const Viewport
*vp
, ViewportDrawerDynamic
*vdd
)
2650 if (this->route_paths
.empty()) return;
2652 DrawPixelInfo dpi_for_text
= vdd
->MakeDPIForText();
2654 for (const auto &iter
: this->route_paths
) {
2655 const int from_tile_x
= TileX(iter
.from_tile
) * TILE_SIZE
+ TILE_SIZE
/ 2;
2656 const int from_tile_y
= TileY(iter
.from_tile
) * TILE_SIZE
+ TILE_SIZE
/ 2;
2657 Point from_pt
= RemapCoords(from_tile_x
, from_tile_y
, 0);
2658 const int from_x
= UnScaleByZoom(from_pt
.x
, vp
->zoom
);
2660 const int to_tile_x
= TileX(iter
.to_tile
) * TILE_SIZE
+ TILE_SIZE
/ 2;
2661 const int to_tile_y
= TileY(iter
.to_tile
) * TILE_SIZE
+ TILE_SIZE
/ 2;
2662 Point to_pt
= RemapCoords(to_tile_x
, to_tile_y
, 0);
2663 const int to_x
= UnScaleByZoom(to_pt
.x
, vp
->zoom
);
2665 if (from_x
< dpi_for_text
.left
- 1 && to_x
< dpi_for_text
.left
- 1) continue;
2666 if (from_x
> dpi_for_text
.left
+ dpi_for_text
.width
+ 1 && to_x
> dpi_for_text
.left
+ dpi_for_text
.width
+ 1) continue;
2668 from_pt
.y
-= GetSlopePixelZ(from_tile_x
, from_tile_y
) * ZOOM_BASE
;
2669 to_pt
.y
-= GetSlopePixelZ(to_tile_x
, to_tile_y
) * ZOOM_BASE
;
2670 const int from_y
= UnScaleByZoom(from_pt
.y
, vp
->zoom
);
2671 const int to_y
= UnScaleByZoom(to_pt
.y
, vp
->zoom
);
2674 if (_settings_client
.gui
.dash_level_of_route_lines
== 0) {
2675 GfxDrawLine(BlitterFactory::GetCurrentBlitter(), &dpi_for_text
, from_x
, from_y
, to_x
, to_y
, PC_BLACK
, 3, _settings_client
.gui
.dash_level_of_route_lines
);
2678 GfxDrawLine(BlitterFactory::GetCurrentBlitter(), &dpi_for_text
, from_x
, from_y
, to_x
, to_y
, iter
.order_conditional
? PC_YELLOW
: PC_WHITE
, line_width
, _settings_client
.gui
.dash_level_of_route_lines
);
2682 static void ViewportDrawVehicleRoutePath(const Viewport
*vp
, ViewportDrawerDynamic
*vdd
)
2684 _vp_focused_window_route_overlay
.DrawVehicleRoutePath(vp
, vdd
);
2685 for (auto &it
: _vp_fixed_route_overlays
) {
2686 if (it
.enabled
) it
.DrawVehicleRoutePath(vp
, vdd
);
2690 static inline void DrawRouteStep(const Viewport
* const vp
, const TileIndex tile
, const RankOrderTypeList list
)
2692 if (tile
== INVALID_TILE
) return;
2693 const int x_pos
= TileX(tile
) * TILE_SIZE
+ TILE_SIZE
/ 2;
2694 const int y_pos
= TileY(tile
) * TILE_SIZE
+ TILE_SIZE
/ 2;
2695 Point pt
= RemapCoords(x_pos
, y_pos
, 0);
2696 uint width_bucket
= 0;
2697 if (list
.size() <= max_rank_order_type_count
) {
2698 for (RankOrderTypeList::const_iterator cit
= list
.begin(); cit
!= list
.end(); cit
++) {
2699 if (cit
->first
>= 10000) {
2700 width_bucket
= std::max
<uint
>(width_bucket
, 3);
2701 } else if (cit
->first
>= 1000) {
2702 width_bucket
= std::max
<uint
>(width_bucket
, 2);
2703 } else if (cit
->first
>= 100) {
2704 width_bucket
= std::max
<uint
>(width_bucket
, 1);
2708 const uint str_width
= _vp_route_step_string_width
[width_bucket
];
2709 const uint total_width
= str_width
+ _vp_route_step_base_width
;
2710 const int x_centre
= UnScaleByZoomLower(pt
.x
- _vdd
->dpi
.left
, _vdd
->dpi
.zoom
);
2711 const int x
= x_centre
- (total_width
/ 2);
2712 if (x
>= _cur_dpi
->width
|| (x
+ total_width
) <= 0) return;
2713 const uint step_count
= list
.size() > max_rank_order_type_count
? 1 : (uint
)list
.size();
2714 pt
.y
-= GetSlopePixelZ(x_pos
, y_pos
) * ZOOM_BASE
;
2715 const int char_height
= GetCharacterHeight(FS_SMALL
) + 1;
2716 const int rsth
= _vp_route_step_height_top
+ (int) step_count
* char_height
+ _vp_route_step_height_bottom
;
2717 const int y
= UnScaleByZoomLower(pt
.y
- _vdd
->dpi
.top
, _vdd
->dpi
.zoom
) - rsth
;
2718 if (y
>= _cur_dpi
->height
|| (y
+ rsth
) <= 0) return;
2720 /* Draw the background. */
2721 GfxFillRect(_cur_dpi
->left
+ x
, _cur_dpi
->top
+ y
, _cur_dpi
->left
+ x
+ total_width
- 1, _cur_dpi
->top
+ y
+ _vp_route_step_height_top
- 1, PC_BLACK
);
2722 int y2
= y
+ _vp_route_step_height_top
+ (char_height
* step_count
);
2724 GfxFillRect(_cur_dpi
->left
+ x
, _cur_dpi
->top
+ y
+ _vp_route_step_height_top
, _cur_dpi
->left
+ x
+ total_width
- 1, _cur_dpi
->top
+ y2
- 1, PC_WHITE
);
2725 GfxFillRect(_cur_dpi
->left
+ x
, _cur_dpi
->top
+ y
+ _vp_route_step_height_top
, _cur_dpi
->left
+ x
+ _vp_route_step_height_top
- 1, _cur_dpi
->top
+ y2
- 1, PC_BLACK
);
2726 GfxFillRect(_cur_dpi
->left
+ x
+ total_width
- _vp_route_step_height_top
, _cur_dpi
->top
+ y
+ _vp_route_step_height_top
, _cur_dpi
->left
+ x
+ total_width
- 1, _cur_dpi
->top
+ y2
- 1, PC_BLACK
);
2728 if (total_width
> _vp_route_step_sprite_width
) {
2729 GfxFillRect(_cur_dpi
->left
+ x
, _cur_dpi
->top
+ y2
, _cur_dpi
->left
+ x
+ total_width
- 1, _cur_dpi
->top
+ y2
+ _vp_route_step_height_top
- 1, PC_BLACK
);
2732 const int x_bottom_spr
= x_centre
- (_vp_route_step_sprite_width
/ 2);
2733 DrawSprite(SPR_ROUTE_STEP_BOTTOM
, PAL_NONE
, _cur_dpi
->left
+ x_bottom_spr
, _cur_dpi
->top
+ y2
);
2734 SpriteID s
= SPR_ROUTE_STEP_BOTTOM_SHADOW
;
2735 DrawSprite(SetBit(s
, PALETTE_MODIFIER_TRANSPARENT
), PALETTE_TO_TRANSPARENT
, _cur_dpi
->left
+ x_bottom_spr
, _cur_dpi
->top
+ y2
);
2737 /* Fill with the data. */
2738 y2
= y
+ _vp_route_step_height_top
;
2739 DrawPixelInfo dpi_for_text
= _vdd
->MakeDPIForText();
2740 AutoRestoreBackup
dpi_backup(_cur_dpi
, &dpi_for_text
);
2742 const int x_str
= x_centre
- (str_width
/ 2);
2743 if (list
.size() > max_rank_order_type_count
) {
2744 /* Write order overflow item */
2745 SetDParam(0, list
.size());
2746 DrawString(dpi_for_text
.left
+ x_str
, dpi_for_text
.left
+ x_str
+ str_width
- 1, dpi_for_text
.top
+ y2
,
2747 STR_VIEWPORT_SHOW_VEHICLE_ROUTE_STEP_OVERFLOW
, TC_FROMSTRING
, SA_CENTER
, false, FS_SMALL
);
2749 for (RankOrderTypeList::const_iterator cit
= list
.begin(); cit
!= list
.end(); cit
++, y2
+= char_height
) {
2751 switch (cit
->second
) {
2752 case RSOT_GOTO_STATION
:
2753 SetDParam(1, STR_VIEWPORT_SHOW_VEHICLE_ROUTE_STEP_STATION
);
2755 case RSOT_VIA_STATION
:
2756 SetDParam(1, STR_VIEWPORT_SHOW_VEHICLE_ROUTE_STEP_VIA_STATION
);
2759 SetDParam(1, STR_VIEWPORT_SHOW_VEHICLE_ROUTE_STEP_DEPOT
);
2762 SetDParam(1, STR_VIEWPORT_SHOW_VEHICLE_ROUTE_STEP_WAYPOINT
);
2765 SetDParam(1, STR_VIEWPORT_SHOW_VEHICLE_ROUTE_STEP_IMPLICIT
);
2772 /* Write order info */
2773 SetDParam(0, cit
->first
);
2774 DrawString(dpi_for_text
.left
+ x_str
, dpi_for_text
.left
+ x_str
+ str_width
- 1, dpi_for_text
.top
+ y2
,
2775 STR_VIEWPORT_SHOW_VEHICLE_ROUTE_STEP
, TC_FROMSTRING
, SA_CENTER
, false, FS_SMALL
);
2781 void ViewportRouteOverlay::PrepareRouteSteps(const Vehicle
*veh
)
2783 this->route_steps
.clear();
2785 if (veh
== nullptr || !_settings_client
.gui
.show_vehicle_route_steps
) return;
2788 uint16_t order_rank
= 0;
2789 for (const Order
*order
: veh
->Orders()) {
2791 if (ViewportVehicleRouteShouldSkipOrder(order
)) continue;
2792 const TileIndex tile
= order
->GetLocation(veh
, veh
->type
== VEH_AIRCRAFT
);
2793 if (tile
== INVALID_TILE
) continue;
2794 RouteStepOrderType type
= RSOT_INVALID
;
2795 switch (order
->GetType()) {
2796 case OT_GOTO_STATION
:
2797 type
= (order
->GetNonStopType() & ONSF_NO_STOP_AT_DESTINATION_STATION
) != 0 ? RSOT_VIA_STATION
: RSOT_GOTO_STATION
;
2801 type
= RSOT_IMPLICIT
;
2804 case OT_GOTO_WAYPOINT
:
2805 type
= RSOT_WAYPOINT
;
2815 if (type
!= RSOT_INVALID
) {
2816 this->route_steps
[tile
].push_back(std::pair
<uint16_t, RouteStepOrderType
>(order_rank
, type
));
2821 void ViewportPrepareVehicleRoute()
2823 if (_settings_client
.gui
.show_vehicle_route_mode
== 0) return;
2824 if (!_settings_client
.gui
.show_vehicle_route_steps
&& !_settings_client
.gui
.show_vehicle_route
) return;
2826 const Vehicle
*focused_veh
= GetVehicleFromWindow(_focused_window
);
2827 _vp_focused_window_route_overlay
.PrepareRouteAndMarkDirtyIfChanged(focused_veh
);
2828 for (auto &it
: _vp_fixed_route_overlays
) {
2829 const Vehicle
*v
= Vehicle::GetIfValid(it
.veh
);
2830 it
.PrepareRouteAndMarkDirtyIfChanged(v
);
2831 it
.enabled
= !(v
!= nullptr && focused_veh
!= nullptr && v
->FirstShared() == focused_veh
->FirstShared());
2835 void ViewportRouteOverlay::DrawVehicleRouteSteps(const Viewport
*vp
)
2837 for (RouteStepsMap::const_iterator cit
= this->route_steps
.begin(); cit
!= this->route_steps
.end(); cit
++) {
2838 DrawRouteStep(vp
, cit
->first
, cit
->second
);
2842 static bool ViewportDrawHasVehicleRouteSteps()
2844 return _vp_focused_window_route_overlay
.HasVehicleRouteSteps() || !_vp_fixed_route_overlays
.empty();
2847 /** Draw the route steps of a vehicle. */
2848 static void ViewportDrawVehicleRouteSteps(const Viewport
* const vp
)
2850 _vp_focused_window_route_overlay
.DrawVehicleRouteSteps(vp
);
2851 for (auto &it
: _vp_fixed_route_overlays
) {
2852 if (it
.enabled
) it
.DrawVehicleRouteSteps(vp
);
2856 static void ViewportDrawPlans(const Viewport
*vp
, Blitter
*blitter
, DrawPixelInfo
*plan_dpi
)
2858 const Rect bounds
= {
2859 ScaleByZoom(plan_dpi
->left
- 2, vp
->zoom
),
2860 ScaleByZoom(plan_dpi
->top
- 2, vp
->zoom
),
2861 ScaleByZoom(plan_dpi
->left
+ plan_dpi
->width
+ 2, vp
->zoom
),
2862 ScaleByZoom(plan_dpi
->top
+ plan_dpi
->height
+ 2, vp
->zoom
) + (int)(ZOOM_BASE
* TILE_HEIGHT
* _settings_game
.construction
.map_height_limit
)
2865 const int min_coord_delta
= bounds
.left
/ (int)(2 * ZOOM_BASE
* TILE_SIZE
);
2866 const int max_coord_delta
= (bounds
.right
/ (int)(2 * ZOOM_BASE
* TILE_SIZE
)) + 1;
2868 for (const Plan
*p
: Plan::Iterate()) {
2869 if (!p
->IsVisible()) continue;
2870 for (const PlanLine
&pl
: p
->lines
) {
2872 bounds
.left
> pl
.viewport_extents
.right
||
2873 bounds
.right
< pl
.viewport_extents
.left
||
2874 bounds
.top
> pl
.viewport_extents
.bottom
||
2875 bounds
.bottom
< pl
.viewport_extents
.top
2880 TileIndex to_tile
= pl
.tiles
[0];
2881 int to_coord_delta
= (int)TileY(to_tile
) - (int)TileX(to_tile
);
2882 for (uint i
= 1; i
< pl
.tiles
.size(); i
++) {
2883 const TileIndex from_tile
= to_tile
;
2884 const int from_coord_delta
= to_coord_delta
;
2885 to_tile
= pl
.tiles
[i
];
2886 to_coord_delta
= (int)TileY(to_tile
) - (int)TileX(to_tile
);
2888 if (to_coord_delta
< min_coord_delta
&& from_coord_delta
< min_coord_delta
) continue;
2889 if (to_coord_delta
> max_coord_delta
&& from_coord_delta
> max_coord_delta
) continue;
2891 const Point from_pt
= RemapCoords2(TileX(from_tile
) * TILE_SIZE
+ TILE_SIZE
/ 2, TileY(from_tile
) * TILE_SIZE
+ TILE_SIZE
/ 2);
2892 const int from_x
= UnScaleByZoom(from_pt
.x
, vp
->zoom
);
2893 const int from_y
= UnScaleByZoom(from_pt
.y
, vp
->zoom
);
2895 const Point to_pt
= RemapCoords2(TileX(to_tile
) * TILE_SIZE
+ TILE_SIZE
/ 2, TileY(to_tile
) * TILE_SIZE
+ TILE_SIZE
/ 2);
2896 const int to_x
= UnScaleByZoom(to_pt
.x
, vp
->zoom
);
2897 const int to_y
= UnScaleByZoom(to_pt
.y
, vp
->zoom
);
2899 GfxDrawLine(blitter
, plan_dpi
, from_x
, from_y
, to_x
, to_y
, PC_BLACK
, 3);
2901 GfxDrawLine(blitter
, plan_dpi
, from_x
, from_y
, to_x
, to_y
, PC_RED
, 1);
2903 GfxDrawLine(blitter
, plan_dpi
, from_x
, from_y
, to_x
, to_y
, _colour_value
[p
->colour
], 1);
2909 if (_current_plan
&& _current_plan
->temp_line
.tiles
.size() > 1) {
2910 const BasePlanLine
&pl
= _current_plan
->temp_line
;
2911 TileIndex to_tile
= pl
.tiles
[0];
2912 int to_coord_delta
= (int)TileY(to_tile
) - (int)TileX(to_tile
);
2913 for (uint i
= 1; i
< pl
.tiles
.size(); i
++) {
2914 const TileIndex from_tile
= to_tile
;
2915 const int from_coord_delta
= to_coord_delta
;
2916 to_tile
= pl
.tiles
[i
];
2917 to_coord_delta
= (int)TileY(to_tile
) - (int)TileX(to_tile
);
2919 if (to_coord_delta
< min_coord_delta
&& from_coord_delta
< min_coord_delta
) continue;
2920 if (to_coord_delta
> max_coord_delta
&& from_coord_delta
> max_coord_delta
) continue;
2922 const Point from_pt
= RemapCoords2(TileX(from_tile
) * TILE_SIZE
+ TILE_SIZE
/ 2, TileY(from_tile
) * TILE_SIZE
+ TILE_SIZE
/ 2);
2923 const int from_x
= UnScaleByZoom(from_pt
.x
, vp
->zoom
);
2924 const int from_y
= UnScaleByZoom(from_pt
.y
, vp
->zoom
);
2926 const Point to_pt
= RemapCoords2(TileX(to_tile
) * TILE_SIZE
+ TILE_SIZE
/ 2, TileY(to_tile
) * TILE_SIZE
+ TILE_SIZE
/ 2);
2927 const int to_x
= UnScaleByZoom(to_pt
.x
, vp
->zoom
);
2928 const int to_y
= UnScaleByZoom(to_pt
.y
, vp
->zoom
);
2930 GfxDrawLine(blitter
, plan_dpi
, from_x
, from_y
, to_x
, to_y
, _colour_value
[_current_plan
->colour
], 3, 1);
2935 #define SLOPIFY_COLOUR(tile, vF, vW, vS, vE, vN, action) { \
2937 const Slope slope = GetTileSlope((tile)); \
2940 case SLOPE_ELEVATED: \
2941 action (vF); break; \
2943 switch (slope & SLOPE_EW) { \
2944 case SLOPE_W: action (vW); break; \
2945 case SLOPE_E: action (vE); break; \
2946 default: action (slope & SLOPE_S) ? (vS) : (vN); break; \
2955 #define RETURN_SLOPIFIED_COLOUR(tile, colour, colour_light, colour_dark) SLOPIFY_COLOUR(tile, colour, colour_light, colour_dark, colour_dark, colour_light, return)
2956 #define ASSIGN_SLOPIFIED_COLOUR(tile, colour, colour_light, colour_dark, to_var) SLOPIFY_COLOUR(tile, colour, colour_light, colour_dark, colour_dark, colour_light, to_var =)
2957 #define GET_SLOPE_INDEX(slope_index) SLOPIFY_COLOUR(tile, 0, 1, 2, 3, 4, slope_index =)
2959 #define COL8TO32(x) _cur_palette.palette[x].data
2960 #define COLOUR_FROM_INDEX(x) ((const uint8_t *)&(x))[colour_index]
2961 #define IS32(x) (is_32bpp ? COL8TO32(x) : (x))
2963 /* Variables containing Colour if 32bpp or palette index if 8bpp. */
2964 uint32_t _vp_map_vegetation_clear_colours
[16][6][8]; ///< [Slope][ClearGround][Multi (see LoadClearGroundMainColours())]
2965 uint32_t _vp_map_vegetation_tree_colours
[16][5][MAX_TREE_COUNT_BY_LANDSCAPE
]; ///< [Slope][TreeGround][max of _tree_count_by_landscape]
2966 uint32_t _vp_map_water_colour
[5]; ///< [Slope]
2968 static inline uint
ViewportMapGetColourIndexMulti(const TileIndex tile
, const ClearGround cg
)
2974 return GetClearDensity(tile
);
2976 return GB(TileX(tile
) ^ TileY(tile
), 4, 3);
2978 return TileHash(TileX(tile
), TileY(tile
)) & 1;
2980 return GetFieldType(tile
) & 7;
2981 default: NOT_REACHED();
2985 static const ClearGround _treeground_to_clearground
[5] = {
2986 CLEAR_GRASS
, // TREE_GROUND_GRASS
2987 CLEAR_ROUGH
, // TREE_GROUND_ROUGH
2988 CLEAR_SNOW
, // TREE_GROUND_SNOW_DESERT, make it +1 if _settings_game.game_creation.landscape == LT_TROPIC
2989 CLEAR_GRASS
, // TREE_GROUND_SHORE
2990 CLEAR_SNOW
, // TREE_GROUND_ROUGH_SNOW, make it +1 if _settings_game.game_creation.landscape == LT_TROPIC
2993 template <bool is_32bpp
>
2994 static inline uint32_t ViewportMapGetColourVegetationTree(const TileIndex tile
, const TreeGround tg
, const uint td
, const uint tc
, const uint colour_index
, Slope slope
)
2996 if (IsTransparencySet(TO_TREES
)) {
2997 ClearGround cg
= _treeground_to_clearground
[tg
];
2998 if (cg
== CLEAR_SNOW
&& _settings_game
.game_creation
.landscape
== LT_TROPIC
) cg
= CLEAR_DESERT
;
2999 uint32_t ground_colour
= _vp_map_vegetation_clear_colours
[slope
][cg
][td
];
3001 if (IsInvisibilitySet(TO_TREES
)) {
3003 return ground_colour
;
3006 /* Take ground and make it darker. */
3008 return Blitter_32bppBase::MakeTransparent(ground_colour
, 192, 256).data
;
3010 /* 8bpp transparent snow trees give blue. Definitely don't want that. Prefer grey. */
3011 if (cg
== CLEAR_SNOW
&& td
> 1) return GREY_SCALE(13 - tc
);
3012 return _pal2trsp_remap_ptr
[ground_colour
];
3015 if (tg
== TREE_GROUND_SNOW_DESERT
|| tg
== TREE_GROUND_ROUGH_SNOW
) {
3016 return _vp_map_vegetation_clear_colours
[colour_index
^ slope
][_settings_game
.game_creation
.landscape
== LT_TROPIC
? CLEAR_DESERT
: CLEAR_SNOW
][td
];
3018 const uint rnd
= std::min
<uint
>(tc
^ (((tile
& 3) ^ (TileY(tile
) & 3)) * td
), MAX_TREE_COUNT_BY_LANDSCAPE
- 1);
3019 return _vp_map_vegetation_tree_colours
[slope
][tg
][rnd
];
3024 static bool ViewportMapGetColourVegetationCustomObject(uint32_t &colour
, const TileIndex tile
, const uint colour_index
, bool is_32bpp
, bool show_slope
)
3026 ObjectViewportMapType vmtype
= OVMT_DEFAULT
;
3027 const ObjectSpec
*spec
= ObjectSpec::GetByTile(tile
);
3028 if (spec
->ctrl_flags
& OBJECT_CTRL_FLAG_VPORT_MAP_TYPE
) vmtype
= spec
->vport_map_type
;
3030 auto do_clear_ground
= [&](ClearGround cg
, uint multi
) -> bool {
3031 Slope slope
= SLOPE_FLAT
;
3033 slope
= GetTileSlope(tile
);
3034 extern Foundation
GetFoundation_Object(TileIndex tile
, Slope tileh
);
3035 ApplyFoundationToSlope(GetFoundation_Object(tile
, slope
), slope
);
3036 slope
&= SLOPE_ELEVATED
;
3038 colour
= _vp_map_vegetation_clear_colours
[slope
][cg
][multi
];
3042 auto do_water
= [&](bool coast
) -> bool {
3044 uint slope_index
= 0;
3045 if (!coast
) GET_SLOPE_INDEX(slope_index
);
3046 colour
= _vp_map_water_colour
[slope_index
];
3049 colour
= ApplyMask(MKCOLOUR_XXXX(GREY_SCALE(3)), &_smallmap_vehicles_andor
[MP_WATER
]);
3050 colour
= COLOUR_FROM_INDEX(colour
);
3056 if (spec
->ctrl_flags
& OBJECT_CTRL_FLAG_USE_LAND_GROUND
) {
3057 if (IsTileOnWater(tile
) && GetObjectGroundType(tile
) != OBJECT_GROUND_SHORE
) {
3058 return do_water(false);
3060 switch (GetObjectGroundType(tile
)) {
3061 case OBJECT_GROUND_GRASS
:
3062 return do_clear_ground(CLEAR_GRASS
, GetObjectGroundDensity(tile
));
3064 case OBJECT_GROUND_SNOW_DESERT
:
3065 return do_clear_ground(_settings_game
.game_creation
.landscape
== LT_TROPIC
? CLEAR_DESERT
: CLEAR_SNOW
, GetObjectGroundDensity(tile
));
3067 case OBJECT_GROUND_SHORE
:
3068 return do_water(true);
3071 /* This should never be reached, just draw as clear as a fallback */
3072 return do_clear_ground(CLEAR_GRASS
, 0);
3076 return do_clear_ground(CLEAR_GRASS
, 0);
3078 return do_clear_ground(CLEAR_GRASS
, 3);
3080 return do_clear_ground(CLEAR_ROUGH
, GB(TileX(tile
) ^ TileY(tile
), 4, 3));
3082 return do_clear_ground(CLEAR_ROCKS
, TileHash(TileX(tile
), TileY(tile
)) & 1);
3084 return (colour_index
& 1) ? do_clear_ground(CLEAR_GRASS
, 1) : do_clear_ground(CLEAR_FIELDS
, spec
->vport_map_subtype
& 7);
3086 return do_clear_ground(CLEAR_SNOW
, 3);
3088 return do_clear_ground(CLEAR_DESERT
, 3);
3090 Slope slope
= SLOPE_FLAT
;
3092 slope
= GetTileSlope(tile
);
3093 extern Foundation
GetFoundation_Object(TileIndex tile
, Slope tileh
);
3094 ApplyFoundationToSlope(GetFoundation_Object(tile
, slope
), slope
);
3095 slope
&= SLOPE_ELEVATED
;
3097 TreeGround tg
= (TreeGround
)GB(spec
->vport_map_subtype
, 0, 4);
3098 if (tg
> TREE_GROUND_ROUGH_SNOW
) tg
= TREE_GROUND_GRASS
;
3099 const uint td
= std::min
<uint
>(GB(spec
->vport_map_subtype
, 4, 4), 3);
3100 const uint tc
= Clamp
<uint
>(GB(spec
->vport_map_subtype
, 8, 4), 1, 4);
3102 colour
= ViewportMapGetColourVegetationTree
<true>(tile
, tg
, td
, tc
, colour_index
, slope
);
3104 colour
= ViewportMapGetColourVegetationTree
<false>(tile
, tg
, td
, tc
, colour_index
, slope
);
3109 colour
= ApplyMask(MKCOLOUR_XXXX(GREY_SCALE(3)), &_smallmap_vehicles_andor
[MP_HOUSE
]);
3110 colour
= COLOUR_FROM_INDEX(colour
);
3113 return do_water(false);
3120 template <bool is_32bpp
, bool show_slope
>
3121 static inline uint32_t ViewportMapGetColourVegetation(const TileIndex tile
, TileType t
, const uint colour_index
)
3125 auto set_default_colour
= [&](TileType ttype
) {
3126 colour
= ApplyMask(MKCOLOUR_XXXX(GREY_SCALE(3)), &_smallmap_vehicles_andor
[ttype
]);
3127 colour
= COLOUR_FROM_INDEX(colour
);
3132 Slope slope
= show_slope
? (Slope
) (GetTileSlope(tile
) & 15) : SLOPE_FLAT
;
3134 ClearGround cg
= GetClearGround(tile
);
3135 if (cg
== CLEAR_FIELDS
&& colour_index
& 1) {
3138 } else multi
= ViewportMapGetColourIndexMulti(tile
, cg
);
3139 return _vp_map_vegetation_clear_colours
[slope
][cg
][multi
];
3143 colour
= IsTileForestIndustry(tile
) ? (colour_index
& 1 ? PC_GREEN
: 0x7B) : GREY_SCALE(3);
3147 const TreeGround tg
= GetTreeGround(tile
);
3148 const uint td
= GetTreeDensity(tile
);
3149 const uint tc
= GetTreeCount(tile
);
3150 Slope slope
= show_slope
? (Slope
) (GetTileSlope(tile
) & 15) : SLOPE_FLAT
;
3151 return ViewportMapGetColourVegetationTree
<is_32bpp
>(tile
, tg
, td
, tc
, colour_index
, slope
);
3155 set_default_colour(MP_OBJECT
);
3156 if (GetObjectHasViewportMapViewOverride(tile
)) {
3157 if (ViewportMapGetColourVegetationCustomObject(colour
, tile
, colour_index
, is_32bpp
, show_slope
)) return colour
;
3164 uint slope_index
= 0;
3165 if (IsTileType(tile
, MP_WATER
) && GetWaterTileType(tile
) != WATER_TILE_COAST
) GET_SLOPE_INDEX(slope_index
);
3166 return _vp_map_water_colour
[slope_index
];
3168 set_default_colour(t
);
3172 colour
= ApplyMask(MKCOLOUR_XXXX(GREY_SCALE(3)), &_smallmap_vehicles_andor
[t
]);
3173 colour
= COLOUR_FROM_INDEX(colour
);
3174 set_default_colour(t
);
3179 return COL8TO32(colour
);
3181 if (show_slope
) ASSIGN_SLOPIFIED_COLOUR(tile
, colour
, _lighten_colour
[colour
], _darken_colour
[colour
], colour
);
3186 template <bool is_32bpp
, bool show_slope
>
3187 static inline uint32_t ViewportMapGetColourIndustries(const TileIndex tile
, const TileType t
, const uint colour_index
)
3189 extern LegendAndColour _legend_from_industries
[NUM_INDUSTRYTYPES
+ 1];
3190 extern uint _industry_to_list_pos
[NUM_INDUSTRYTYPES
];
3193 if (t
== MP_INDUSTRY
) {
3194 /* If industry is allowed to be seen, use its colour on the map. */
3195 const IndustryType it
= Industry::GetByTile(tile
)->type
;
3196 if (_legend_from_industries
[_industry_to_list_pos
[it
]].show_on_map
)
3197 return IS32(GetIndustrySpec(it
)->map_colour
);
3198 /* Otherwise, return the colour which will make it disappear. */
3199 t2
= IsTileOnWater(tile
) ? MP_WATER
: MP_CLEAR
;
3202 if (t
== MP_OBJECT
&& GetObjectHasViewportMapViewOverride(tile
)) {
3203 ObjectViewportMapType vmtype
= OVMT_DEFAULT
;
3204 const ObjectSpec
*spec
= ObjectSpec::GetByTile(tile
);
3205 if (spec
->ctrl_flags
& OBJECT_CTRL_FLAG_VPORT_MAP_TYPE
) vmtype
= spec
->vport_map_type
;
3206 if (vmtype
== OVMT_CLEAR
&& spec
->ctrl_flags
& OBJECT_CTRL_FLAG_USE_LAND_GROUND
) {
3207 if (IsTileOnWater(tile
) && GetObjectGroundType(tile
) != OBJECT_GROUND_SHORE
) {
3208 vmtype
= OVMT_WATER
;
3233 if (is_32bpp
&& t2
== MP_WATER
) {
3234 uint slope_index
= 0;
3235 if (t
!= MP_INDUSTRY
&& IsTileType(tile
, MP_WATER
) && GetWaterTileType(tile
) != WATER_TILE_COAST
) GET_SLOPE_INDEX(slope_index
); ///< Ignore industry on water not shown on map.
3236 return _vp_map_water_colour
[slope_index
];
3239 const int h
= TileHeight(tile
);
3240 const SmallMapColourScheme
* const cs
= &_heightmap_schemes
[_settings_client
.gui
.smallmap_land_colour
];
3241 const uint32_t colours
= ApplyMask(_settings_client
.gui
.show_height_on_viewport_map
? cs
->height_colours
[h
] : cs
->default_colour
, &_smallmap_vehicles_andor
[t2
]);
3242 uint32_t colour
= COLOUR_FROM_INDEX(colours
);
3244 if (show_slope
) ASSIGN_SLOPIFIED_COLOUR(tile
, colour
, _lighten_colour
[colour
], _darken_colour
[colour
], colour
);
3246 return IS32(colour
);
3249 template <bool is_32bpp
, bool show_slope
>
3250 static inline uint32_t ViewportMapGetColourOwner(const TileIndex tile
, TileType t
, const uint colour_index
)
3252 extern LegendAndColour _legend_land_owners
[NUM_NO_COMPANY_ENTRIES
+ MAX_COMPANIES
+ 1];
3253 extern uint _company_to_list_pos
[MAX_COMPANIES
];
3256 case MP_INDUSTRY
: return IS32(PC_DARK_GREY
);
3257 case MP_HOUSE
: return IS32(colour_index
& 1 ? PC_DARK_RED
: GREY_SCALE(3));
3261 const Owner o
= GetTileOwner(tile
);
3262 if (o
== OWNER_NONE
&& t
== MP_ROAD
) {
3263 return IS32(colour_index
& 1 ? PC_BLACK
: GREY_SCALE(3));
3264 } else if ((o
< MAX_COMPANIES
&& !_legend_land_owners
[_company_to_list_pos
[o
]].show_on_map
) || o
== OWNER_NONE
|| o
== OWNER_WATER
) {
3265 if (t
== MP_WATER
) {
3267 uint slope_index
= 0;
3268 if (IsTileType(tile
, MP_WATER
) && GetWaterTileType(tile
) != WATER_TILE_COAST
) GET_SLOPE_INDEX(slope_index
);
3269 return _vp_map_water_colour
[slope_index
];
3275 const SmallMapColourScheme
* const cs
= &_heightmap_schemes
[_settings_client
.gui
.smallmap_land_colour
];
3276 uint32_t colour
= COLOUR_FROM_INDEX(_settings_client
.gui
.show_height_on_viewport_map
? cs
->height_colours
[TileHeight(tile
)] : cs
->default_colour
);
3277 if (show_slope
) ASSIGN_SLOPIFIED_COLOUR(tile
, colour
, _lighten_colour
[colour
], _darken_colour
[colour
], colour
);
3278 return IS32(colour
);
3280 } else if (o
== OWNER_TOWN
) {
3281 return IS32(t
== MP_ROAD
? (colour_index
& 1 ? PC_BLACK
: GREY_SCALE(3)) : PC_DARK_RED
);
3284 /* Train stations are sometimes hard to spot.
3285 * So we give the player a hint by mixing his colour with black. */
3286 uint32_t colour
= _legend_land_owners
[_company_to_list_pos
[o
]].colour
;
3287 if (t
!= MP_STATION
) {
3288 if (show_slope
) ASSIGN_SLOPIFIED_COLOUR(tile
, colour
, _lighten_colour
[colour
], _darken_colour
[colour
], colour
);
3290 if (GetStationType(tile
) == STATION_RAIL
) colour
= colour_index
& 1 ? colour
: PC_BLACK
;
3292 if (is_32bpp
) return COL8TO32(colour
);
3296 template <bool is_32bpp
, bool show_slope
>
3297 static inline uint32_t ViewportMapGetColourRoutes(const TileIndex tile
, TileType t
, const uint colour_index
)
3304 uint slope_index
= 0;
3305 if (IsTileType(tile
, MP_WATER
) && GetWaterTileType(tile
) != WATER_TILE_COAST
) GET_SLOPE_INDEX(slope_index
);
3306 return _vp_map_water_colour
[slope_index
];
3312 return IS32(PC_DARK_GREY
);
3315 return IS32(colour_index
& 1 ? PC_DARK_RED
: GREY_SCALE(3));
3318 ObjectViewportMapType vmtype
= OVMT_DEFAULT
;
3319 if (GetObjectHasViewportMapViewOverride(tile
)) {
3320 const ObjectSpec
*spec
= ObjectSpec::GetByTile(tile
);
3321 if (spec
->ctrl_flags
& OBJECT_CTRL_FLAG_VPORT_MAP_TYPE
) vmtype
= spec
->vport_map_type
;
3322 if (vmtype
== OVMT_CLEAR
&& spec
->ctrl_flags
& OBJECT_CTRL_FLAG_USE_LAND_GROUND
) {
3323 if (IsTileOnWater(tile
) && GetObjectGroundType(tile
) != OBJECT_GROUND_SHORE
) {
3324 vmtype
= OVMT_WATER
;
3331 return IS32(colour_index
& 1 ? PC_DARK_RED
: GREY_SCALE(3));
3335 return _vp_map_water_colour
[0];
3341 const SmallMapColourScheme
* const cs
= &_heightmap_schemes
[_settings_client
.gui
.smallmap_land_colour
];
3342 colour
= COLOUR_FROM_INDEX(_settings_client
.gui
.show_height_on_viewport_map
? cs
->height_colours
[TileHeight(tile
)] : cs
->default_colour
);
3350 switch (GetStationType(tile
)) {
3351 case STATION_RAIL
: return IS32(PC_VERY_DARK_BROWN
);
3352 case STATION_AIRPORT
: return IS32(PC_RED
);
3353 case STATION_TRUCK
: return IS32(PC_ORANGE
);
3354 case STATION_BUS
: return IS32(PC_YELLOW
);
3355 case STATION_DOCK
: return IS32(PC_LIGHT_BLUE
);
3356 default: return IS32(0xFF);
3360 colour
= GetRailTypeInfo(GetRailType(tile
))->map_colour
;
3365 const RoadTypeInfo
*rti
= nullptr;
3366 if (GetRoadTypeRoad(tile
) != INVALID_ROADTYPE
) {
3367 rti
= GetRoadTypeInfo(GetRoadTypeRoad(tile
));
3369 rti
= GetRoadTypeInfo(GetRoadTypeTram(tile
));
3371 if (rti
!= nullptr) {
3372 colour
= rti
->map_colour
;
3379 const SmallMapColourScheme
* const cs
= &_heightmap_schemes
[_settings_client
.gui
.smallmap_land_colour
];
3380 colour
= COLOUR_FROM_INDEX(_settings_client
.gui
.show_height_on_viewport_map
? cs
->height_colours
[TileHeight(tile
)] : cs
->default_colour
);
3385 if (show_slope
) ASSIGN_SLOPIFIED_COLOUR(tile
, colour
, _lighten_colour
[colour
], _darken_colour
[colour
], colour
);
3386 return IS32(colour
);
3389 static inline void ViewportMapStoreBridgeAboveTile(const Viewport
* const vp
, const TileIndex tile
)
3391 /* No need to bother for hidden things */
3392 if (!_settings_client
.gui
.show_bridges_on_map
) return;
3394 if (GetBridgeAxis(tile
) == AXIS_X
) {
3395 auto iter
= _vdd
->bridge_to_map_x
.lower_bound(tile
);
3396 if (iter
!= _vdd
->bridge_to_map_x
.end() && iter
->first
< tile
&& iter
->second
> tile
) return; /* already covered */
3397 _vdd
->bridge_to_map_x
.insert(iter
, std::make_pair(GetNorthernBridgeEnd(tile
), GetSouthernBridgeEnd(tile
)));
3399 auto iter
= _vdd
->bridge_to_map_y
.lower_bound(tile
);
3400 if (iter
!= _vdd
->bridge_to_map_y
.end() && iter
->first
< tile
&& iter
->second
> tile
) return; /* already covered */
3401 _vdd
->bridge_to_map_y
.insert(iter
, std::make_pair(GetNorthernBridgeEnd(tile
), GetSouthernBridgeEnd(tile
)));
3405 static inline TileIndex
ViewportMapGetMostSignificantTileType(const Viewport
* const vp
, const TileIndex from_tile
, TileType
* const tile_type
)
3407 if (vp
->zoom
<= ZOOM_LVL_OUT_32X
) {
3408 const TileType ttype
= GetTileType(from_tile
);
3409 /* Store bridges and tunnels. */
3410 if (ttype
!= MP_TUNNELBRIDGE
) {
3412 if (IsBridgeAbove(from_tile
)) ViewportMapStoreBridgeAboveTile(vp
, from_tile
);
3414 if (IsBridge(from_tile
)) {
3415 ViewportMapStoreBridge(vp
, from_tile
);
3417 switch (GetTunnelBridgeTransportType(from_tile
)) {
3418 case TRANSPORT_RAIL
: *tile_type
= MP_RAILWAY
; break;
3419 case TRANSPORT_ROAD
: *tile_type
= MP_ROAD
; break;
3420 case TRANSPORT_WATER
: *tile_type
= MP_WATER
; break;
3421 default: NOT_REACHED(); break;
3427 const uint8_t length
= (vp
->zoom
- ZOOM_LVL_OUT_32X
) * 2;
3428 TileArea tile_area
= TileArea(from_tile
, length
, length
);
3429 tile_area
.ClampToMap();
3431 /* Find the most important tile of the area. */
3432 TileIndex result
= from_tile
;
3433 uint importance
= 0;
3434 for (OrthogonalPrefetchTileIterator
tile(tile_area
); tile
!= INVALID_TILE
; ++tile
) {
3435 const TileType ttype
= GetTileType(tile
);
3436 const uint tile_importance
= _tiletype_importance
[ttype
];
3437 if (tile_importance
> importance
) {
3438 importance
= tile_importance
;
3441 if (ttype
!= MP_TUNNELBRIDGE
&& IsBridgeAbove(tile
)) {
3442 ViewportMapStoreBridgeAboveTile(vp
, tile
);
3446 /* Store bridges and tunnels. */
3447 *tile_type
= GetTileType(result
);
3448 if (*tile_type
== MP_TUNNELBRIDGE
) {
3449 if (IsBridge(result
)) {
3450 ViewportMapStoreBridge(vp
, result
);
3452 switch (GetTunnelBridgeTransportType(result
)) {
3453 case TRANSPORT_RAIL
: *tile_type
= MP_RAILWAY
; break;
3454 case TRANSPORT_ROAD
: *tile_type
= MP_ROAD
; break;
3455 default: *tile_type
= MP_WATER
; break;
3462 static uint32_t ViewportMapVoidColour()
3464 return (_settings_game
.construction
.map_edge_mode
== 2) ? _vp_map_water_colour
[SLOPE_FLAT
] : 0;
3467 /** Get the colour of a tile, can be 32bpp RGB or 8bpp palette index. */
3468 template <bool is_32bpp
, bool show_slope
>
3469 uint32_t ViewportMapGetColour(const Viewport
* const vp
, int x
, int y
, const uint colour_index
)
3471 if (x
>= static_cast<int>(MapMaxX() * TILE_SIZE
) || y
>= static_cast<int>(MapMaxY() * TILE_SIZE
)) return ViewportMapVoidColour();
3473 /* Very approximative but fast way to get the tile when taking Z into account. */
3474 const TileIndex tile_tmp
= TileVirtXY(std::max(0, x
), std::max(0, y
));
3475 const int z
= TileHeight(tile_tmp
) * 4;
3476 if (x
+ z
< 0 || y
+ z
< 0 || static_cast<uint
>(x
+ z
) >= MapSizeX() << 4) {
3477 /* Wrapping of tile X coordinate causes a graphic glitch below south west border. */
3478 return ViewportMapVoidColour();
3480 TileIndex tile
= TileVirtXY(x
+ z
, y
+ z
);
3481 if (tile
>= MapSize()) return ViewportMapVoidColour();
3482 const int z2
= TileHeight(tile
) * 4;
3483 if (unlikely(z2
!= z
)) {
3484 const int approx_z
= (z
+ z2
) / 2;
3485 if (x
+ approx_z
< 0 || y
+ approx_z
< 0 || static_cast<uint
>(x
+ approx_z
) >= MapSizeX() << 4) {
3486 /* Wrapping of tile X coordinate causes a graphic glitch below south west border. */
3487 return ViewportMapVoidColour();
3489 tile
= TileVirtXY(x
+ approx_z
, y
+ approx_z
);
3490 if (tile
>= MapSize()) return ViewportMapVoidColour();
3492 TileType tile_type
= MP_VOID
;
3493 tile
= ViewportMapGetMostSignificantTileType(vp
, tile
, &tile_type
);
3494 if (tile_type
== MP_VOID
) return ViewportMapVoidColour();
3496 /* Return the colours. */
3497 switch (vp
->map_type
) {
3498 default: return ViewportMapGetColourOwner
<is_32bpp
, show_slope
>(tile
, tile_type
, colour_index
);
3499 case VPMT_INDUSTRY
: return ViewportMapGetColourIndustries
<is_32bpp
, show_slope
>(tile
, tile_type
, colour_index
);
3500 case VPMT_VEGETATION
: return ViewportMapGetColourVegetation
<is_32bpp
, show_slope
>(tile
, tile_type
, colour_index
);
3501 case VPMT_ROUTES
: return ViewportMapGetColourRoutes
<is_32bpp
, show_slope
>(tile
, tile_type
, colour_index
);
3505 /* Taken from http://stereopsis.com/doubleblend.html, PixelBlend() is faster than ComposeColourRGBANoCheck() */
3506 static inline void PixelBlend(uint32_t * const d
, const uint32_t s
)
3508 #if defined(__EMSCRIPTEN__)
3509 *d
= Blitter_32bppBase::ComposeColourRGBANoCheck(s
& 0xFF, (s
>> 8) & 0xFF, (s
>> 16) & 0xFF, (s
>> 24) & 0xFF, Colour(*d
)).data
;
3512 const uint32_t a
= (s
>> 24) + 1;
3513 const uint32_t dstrb
= *d
& 0xFF00FF;
3514 const uint32_t dstg
= *d
& 0xFF00;
3515 const uint32_t srcrb
= s
& 0xFF00FF;
3516 const uint32_t srcg
= s
& 0xFF00;
3517 uint32_t drb
= srcrb
- dstrb
;
3518 uint32_t dg
= srcg
- dstg
;
3523 uint32_t rb
= (drb
+ dstrb
) & 0xFF00FF;
3524 uint32_t g
= (dg
+ dstg
) & 0xFF00;
3528 /** Draw the bounding boxes of the scrolling viewport (right-clicked and dragged) */
3529 static void ViewportMapDrawScrollingViewportBox(const Viewport
* const vp
)
3531 if (_scrolling_viewport
&& _scrolling_viewport
->viewport
) {
3532 const ViewportData
* const vp_scrolling
= _scrolling_viewport
->viewport
;
3533 if (vp_scrolling
->zoom
< ZOOM_LVL_DRAW_MAP
) {
3534 const int w
= UnScaleByZoom(_vdd
->dpi
.width
, vp
->zoom
);
3535 const int l
= UnScaleByZoomLower(vp_scrolling
->next_scrollpos_x
- _vdd
->dpi
.left
, _vdd
->dpi
.zoom
);
3536 const int r
= UnScaleByZoomLower(vp_scrolling
->next_scrollpos_x
+ vp_scrolling
->virtual_width
- _vdd
->dpi
.left
, _vdd
->dpi
.zoom
);
3537 /* Check intersection of dpi and vp_scrolling */
3538 if (l
< w
&& r
>= 0) {
3539 const int h
= UnScaleByZoom(_vdd
->dpi
.height
, vp
->zoom
);
3540 const int t
= UnScaleByZoomLower(vp_scrolling
->next_scrollpos_y
- _vdd
->dpi
.top
, _vdd
->dpi
.zoom
);
3541 const int b
= UnScaleByZoomLower(vp_scrolling
->next_scrollpos_y
+ vp_scrolling
->virtual_height
- _vdd
->dpi
.top
, _vdd
->dpi
.zoom
);
3542 if (t
< h
&& b
>= 0) {
3543 /* OK, so we can draw something that tells where the scrolling viewport is */
3544 Blitter
* const blitter
= BlitterFactory::GetCurrentBlitter();
3545 const int l_inter
= std::max(l
, 0);
3546 const int r_inter
= std::min(r
, w
);
3547 const int t_inter
= std::max(t
, 0);
3548 const int b_inter
= std::min(b
, h
);
3550 /* If asked, with 32bpp we can do some blending */
3551 if (_settings_client
.gui
.show_scrolling_viewport_on_map
>= 2 && blitter
->GetScreenDepth() == 32) {
3552 for (int j
= t_inter
; j
< b_inter
; j
++) {
3553 uint32_t *buf
= (uint32_t*) blitter
->MoveTo(_vdd
->dpi
.dst_ptr
, 0, j
);
3554 for (int i
= l_inter
; i
< r_inter
; i
++) {
3555 PixelBlend(buf
+ i
, 0x40FCFCFC);
3560 /* Draw area contour */
3561 if (_settings_client
.gui
.show_scrolling_viewport_on_map
!= 2) {
3563 for (int i
= l_inter
; i
< r_inter
; i
+= 2) {
3564 blitter
->SetPixel(_vdd
->dpi
.dst_ptr
, i
, t
, PC_WHITE
);
3568 for (int i
= l_inter
; i
< r_inter
; i
+= 2) {
3569 blitter
->SetPixel(_vdd
->dpi
.dst_ptr
, i
, b
, PC_WHITE
);
3573 for (int j
= t_inter
; j
< b_inter
; j
+= 2) {
3574 blitter
->SetPixel(_vdd
->dpi
.dst_ptr
, l
, j
, PC_WHITE
);
3578 for (int j
= t_inter
; j
< b_inter
; j
+= 2) {
3579 blitter
->SetPixel(_vdd
->dpi
.dst_ptr
, r
, j
, PC_WHITE
);
3589 static void ViewportMapDrawSelection(const Viewport
* const vp
)
3591 DrawPixelInfo dpi_for_text
= _vdd
->MakeDPIForText();
3592 AutoRestoreBackup
dpi_backup(_cur_dpi
, &dpi_for_text
);
3594 auto draw_line
= [&](Point from_pt
, Point to_pt
) {
3595 GfxDrawLine(from_pt
.x
, from_pt
.y
, to_pt
.x
, to_pt
.y
, PC_WHITE
, 2, 0);
3598 Point start_coord
= RemapCoords2(_thd
.selstart
.x
, _thd
.selstart
.y
);
3599 Point end_coord
= RemapCoords2(_thd
.selend
.x
, _thd
.selend
.y
);
3601 Point start_effective
= InverseRemapCoords(start_coord
.x
, start_coord
.y
);
3602 Point end_effective
= InverseRemapCoords(end_coord
.x
, end_coord
.y
);
3604 auto get_corner
= [&](int pos_x
, int pos_y
) -> Point
{
3605 Point pt
= RemapCoords(pos_x
, pos_y
, 0);
3606 return { UnScaleByZoom(pt
.x
, vp
->zoom
), UnScaleByZoom(pt
.y
, vp
->zoom
) };
3608 Point start_pt
= get_corner(start_effective
.x
, start_effective
.y
);
3609 Point end_pt
= get_corner(end_effective
.x
, end_effective
.y
);
3610 Point mid1_pt
= get_corner(start_effective
.x
, end_effective
.y
);
3611 Point mid2_pt
= get_corner(end_effective
.x
, start_effective
.y
);
3613 draw_line(start_pt
, mid1_pt
);
3614 draw_line(mid1_pt
, end_pt
);
3615 draw_line(end_pt
, mid2_pt
);
3616 draw_line(mid2_pt
, start_pt
);
3618 if (BlitterFactory::GetCurrentBlitter()->GetScreenDepth() == 32) {
3619 static std::vector
<Point
> points(4);
3620 points
[0] = start_pt
;
3621 points
[1] = mid1_pt
;
3623 points
[3] = mid2_pt
;
3624 GfxFillPolygon(points
, 0, FILLRECT_FUNCTOR
, [](void *dst
, int count
) {
3625 uint32_t *buf
= reinterpret_cast<uint32_t *>(dst
);
3626 for (int i
= 0; i
< count
; i
++) {
3627 PixelBlend(buf
+ i
, 0x40FCFCFC);
3631 draw_line(start_pt
, end_pt
);
3635 template <bool is_32bpp
>
3636 static void ViewportMapDrawBridgeTunnel(Viewport
* const vp
, const TunnelBridgeToMap
* const tbtm
, const int z
,
3637 const bool is_tunnel
, const int w
, const int h
, Blitter
* const blitter
)
3639 extern LegendAndColour _legend_land_owners
[NUM_NO_COMPANY_ENTRIES
+ MAX_COMPANIES
+ 1];
3640 extern uint _company_to_list_pos
[MAX_COMPANIES
];
3642 TileIndex tile
= tbtm
->from_tile
;
3643 const Owner o
= GetTileOwner(tile
);
3644 if (o
< MAX_COMPANIES
&& !_legend_land_owners
[_company_to_list_pos
[o
]].show_on_map
) return;
3647 if (vp
->map_type
== VPMT_OWNER
&& _settings_client
.gui
.use_owner_colour_for_tunnelbridge
&& o
< MAX_COMPANIES
) {
3648 colour
= _legend_land_owners
[_company_to_list_pos
[o
]].colour
;
3649 colour
= is_tunnel
? _darken_colour
[colour
] : _lighten_colour
[colour
];
3650 } else if (vp
->map_type
== VPMT_ROUTES
&& IsTileType(tile
, MP_TUNNELBRIDGE
)) {
3651 switch (GetTunnelBridgeTransportType(tile
)) {
3652 case TRANSPORT_WATER
:
3656 case TRANSPORT_RAIL
:
3657 colour
= GetRailTypeInfo(GetRailType(tile
))->map_colour
;
3660 case TRANSPORT_ROAD
: {
3661 const RoadTypeInfo
*rti
= nullptr;
3662 if (GetRoadTypeRoad(tile
) != INVALID_ROADTYPE
) {
3663 rti
= GetRoadTypeInfo(GetRoadTypeRoad(tile
));
3665 rti
= GetRoadTypeInfo(GetRoadTypeTram(tile
));
3667 if (rti
!= nullptr) {
3668 colour
= rti
->map_colour
;
3680 colour
= is_tunnel
? PC_BLACK
: PC_VERY_LIGHT_YELLOW
;
3683 TileIndexDiff delta
= TileOffsByDiagDir(GetTunnelBridgeDirection(tile
));
3684 uint zoom_mask
= (1 << (vp
->zoom
- ZOOM_LVL_DRAW_MAP
)) - 1;
3685 for (tile
+= delta
; tile
!= tbtm
->to_tile
; tile
+= delta
) { // For each tile
3686 if (zoom_mask
!= 0 && ((TileX(tile
) ^ TileY(tile
)) & zoom_mask
)) continue;
3687 const Point pt
= RemapCoords(TileX(tile
) * TILE_SIZE
, TileY(tile
) * TILE_SIZE
, z
);
3688 const int x
= UnScaleByZoomLower(pt
.x
- _vdd
->dpi
.left
, _vdd
->dpi
.zoom
);
3689 if (IsInsideMM(x
, 0, w
)) {
3690 const int y
= UnScaleByZoomLower(pt
.y
- _vdd
->dpi
.top
, _vdd
->dpi
.zoom
);
3691 if (IsInsideMM(y
, 0, h
)) {
3692 uint idx
= (x
+ _vdd
->offset_x
) + ((y
+ _vdd
->offset_y
) * vp
->width
);
3694 reinterpret_cast<uint32_t *>(vp
->land_pixel_cache
.data())[idx
] = COL8TO32(colour
);
3696 reinterpret_cast<uint8_t *>(vp
->land_pixel_cache
.data())[idx
] = colour
;
3703 /** Draw the map on a viewport. */
3704 template <bool is_32bpp
, bool show_slope
>
3705 void ViewportMapDraw(Viewport
* const vp
)
3707 dbg_assert(vp
!= nullptr);
3708 Blitter
* const blitter
= BlitterFactory::GetCurrentBlitter();
3710 SmallMapWindow::RebuildColourIndexIfNecessary();
3712 /* Index of colour: _green_map_heights[] contains blocks of 4 colours, say ABCD
3713 * For a XXXY colour block to render nicely, follow the model:
3714 * line 1: ABCDABCDABCD
3715 * line 2: CDABCDABCDAB
3716 * line 3: ABCDABCDABCD
3717 * => colour_index_base's second bit is changed every new line.
3719 const int sx
= UnScaleByZoomLower(_vdd
->dpi
.left
, _vdd
->dpi
.zoom
);
3720 const int sy
= UnScaleByZoomLower(_vdd
->dpi
.top
, _vdd
->dpi
.zoom
);
3721 const uint line_padding
= 2 * (sy
& 1);
3722 uint colour_index_base
= (sx
+ line_padding
) & 3;
3724 const int incr_a
= (1 << (vp
->zoom
- 2)) / ZOOM_BASE
;
3725 const int incr_b
= (1 << (vp
->zoom
- 1)) / ZOOM_BASE
;
3726 const int a
= (_vdd
->dpi
.left
>> 2) / ZOOM_BASE
;
3727 int b
= (_vdd
->dpi
.top
>> 1) / ZOOM_BASE
;
3728 const int w
= UnScaleByZoom(_vdd
->dpi
.width
, vp
->zoom
);
3729 const int h
= UnScaleByZoom(_vdd
->dpi
.height
, vp
->zoom
);
3732 const int land_cache_start
= _vdd
->offset_x
+ (_vdd
->offset_y
* vp
->width
);
3733 uint32_t *land_cache_ptr32
= reinterpret_cast<uint32_t *>(vp
->land_pixel_cache
.data()) + land_cache_start
;
3734 uint8_t *land_cache_ptr8
= reinterpret_cast<uint8_t *>(vp
->land_pixel_cache
.data()) + land_cache_start
;
3736 bool cache_updated
= false;
3738 /* Render base map. */
3739 do { // For each line
3741 uint colour_index
= colour_index_base
;
3742 colour_index_base
^= 2;
3745 do { // For each pixel of a line
3747 if (*land_cache_ptr32
== 0xD7D7D7D7) {
3748 *land_cache_ptr32
= ViewportMapGetColour
<is_32bpp
, show_slope
>(vp
, c
, d
, colour_index
);
3749 cache_updated
= true;
3753 if (*land_cache_ptr8
== 0xD7) {
3754 *land_cache_ptr8
= (uint8_t) ViewportMapGetColour
<is_32bpp
, show_slope
>(vp
, c
, d
, colour_index
);
3755 cache_updated
= true;
3759 colour_index
= (colour_index
+ 1) & 3;
3764 land_cache_ptr32
+= (vp
->width
- w
);
3766 land_cache_ptr8
+= (vp
->width
- w
);
3771 auto draw_tunnels
= [&](const int y_intercept_min
, const int y_intercept_max
, const TunnelToMapStorage
&storage
) {
3772 auto iter
= std::lower_bound(storage
.tunnels
.begin(), storage
.tunnels
.end(), y_intercept_min
, [](const TunnelToMap
&a
, int b
) -> bool {
3773 return a
.y_intercept
< b
;
3775 for (; iter
!= storage
.tunnels
.end() && iter
->y_intercept
<= y_intercept_max
; ++iter
) {
3776 const TunnelToMap
&ttm
= *iter
;
3777 const int tunnel_z
= (ttm
.tunnel_z
- 1) * TILE_HEIGHT
;
3778 const Point pt_from
= RemapCoords(TileX(ttm
.tb
.from_tile
) * TILE_SIZE
, TileY(ttm
.tb
.from_tile
) * TILE_SIZE
, tunnel_z
);
3779 const Point pt_to
= RemapCoords(TileX(ttm
.tb
.to_tile
) * TILE_SIZE
, TileY(ttm
.tb
.to_tile
) * TILE_SIZE
, tunnel_z
);
3781 /* check if tunnel is wholly outside redrawing area */
3782 const int x_from
= UnScaleByZoomLower(pt_from
.x
- _vdd
->dpi
.left
, _vdd
->dpi
.zoom
);
3783 const int x_to
= UnScaleByZoomLower(pt_to
.x
- _vdd
->dpi
.left
, _vdd
->dpi
.zoom
);
3784 if ((x_from
< 0 && x_to
< 0) || (x_from
> w
&& x_to
> w
)) continue;
3785 const int y_from
= UnScaleByZoomLower(pt_from
.y
- _vdd
->dpi
.top
, _vdd
->dpi
.zoom
);
3786 const int y_to
= UnScaleByZoomLower(pt_to
.y
- _vdd
->dpi
.top
, _vdd
->dpi
.zoom
);
3787 if ((y_from
< 0 && y_to
< 0) || (y_from
> h
&& y_to
> h
)) continue;
3789 ViewportMapDrawBridgeTunnel
<is_32bpp
>(vp
, &ttm
.tb
, tunnel_z
, true, w
, h
, blitter
);
3793 if (cache_updated
) {
3794 /* Render tunnels */
3795 if (_settings_client
.gui
.show_tunnels_on_map
&& _vd
.tunnel_to_map_x
.tunnels
.size() != 0) {
3796 const int y_intercept_min
= _vdd
->dpi
.top
+ (_vdd
->dpi
.left
/ 2);
3797 const int y_intercept_max
= _vdd
->dpi
.top
+ _vdd
->dpi
.height
+ ((_vdd
->dpi
.left
+ _vdd
->dpi
.width
) / 2);
3798 draw_tunnels(y_intercept_min
, y_intercept_max
, _vd
.tunnel_to_map_x
);
3800 if (_settings_client
.gui
.show_tunnels_on_map
&& _vd
.tunnel_to_map_y
.tunnels
.size() != 0) {
3801 const int y_intercept_min
= _vdd
->dpi
.top
- ((_vdd
->dpi
.left
+ _vdd
->dpi
.width
) / 2);
3802 const int y_intercept_max
= _vdd
->dpi
.top
+ _vdd
->dpi
.height
- (_vdd
->dpi
.left
/ 2);
3803 draw_tunnels(y_intercept_min
, y_intercept_max
, _vd
.tunnel_to_map_y
);
3806 /* Render bridges */
3807 if (_settings_client
.gui
.show_bridges_on_map
&& _vdd
->bridge_to_map_x
.size() != 0) {
3808 for (const auto &it
: _vdd
->bridge_to_map_x
) { // For each bridge
3809 TunnelBridgeToMap tbtm
{ it
.first
, it
.second
};
3810 ViewportMapDrawBridgeTunnel
<is_32bpp
>(vp
, &tbtm
, (GetBridgeHeight(tbtm
.from_tile
) - 1) * TILE_HEIGHT
, false, w
, h
, blitter
);
3813 if (_settings_client
.gui
.show_bridges_on_map
&& _vdd
->bridge_to_map_y
.size() != 0) {
3814 for (const auto &it
: _vdd
->bridge_to_map_y
) { // For each bridge
3815 TunnelBridgeToMap tbtm
{ it
.first
, it
.second
};
3816 ViewportMapDrawBridgeTunnel
<is_32bpp
>(vp
, &tbtm
, (GetBridgeHeight(tbtm
.from_tile
) - 1) * TILE_HEIGHT
, false, w
, h
, blitter
);
3822 blitter
->SetRect32(_vdd
->dpi
.dst_ptr
, 0, 0, reinterpret_cast<uint32_t *>(vp
->land_pixel_cache
.data()) + land_cache_start
, h
, w
, vp
->width
);
3824 blitter
->SetRect(_vdd
->dpi
.dst_ptr
, 0, 0, reinterpret_cast<uint8_t *>(vp
->land_pixel_cache
.data()) + land_cache_start
, h
, w
, vp
->width
);
3827 if (unlikely(HasBit(_viewport_debug_flags
, VDF_SHOW_NO_LANDSCAPE_MAP_DRAW
)) && !cache_updated
) {
3828 ViewportDrawDirtyBlocks(_cur_dpi
, true);
3832 static void ViewportProcessParentSprites(ViewportDrawerDynamic
*vdd
, uint data_index
)
3834 ViewportProcessParentSpritesData
*data
= &vdd
->parent_sprite_sets
[data_index
];
3835 if (data
->psts
.size() > 80 && (UnScaleByZoomLower(data
->dpi
.width
, data
->dpi
.zoom
) >= 64 || UnScaleByZoomLower(data
->dpi
.height
, data
->dpi
.zoom
) >= 64) && !HasBit(_viewport_debug_flags
, VDF_DISABLE_DRAW_SPLIT
)) {
3836 /* split drawing region */
3838 uint data_index_2
= (uint
)vdd
->parent_sprite_sets
.size();
3839 vdd
->parent_sprite_sets
.emplace_back();
3840 data
= &vdd
->parent_sprite_sets
[data_index
];
3841 ViewportProcessParentSpritesData
*data2
= &vdd
->parent_sprite_sets
[data_index_2
];
3842 data2
->dpi
= data
->dpi
;
3844 if (data
->dpi
.height
> data
->dpi
.width
) {
3845 /* vertical split: upper half */
3846 const int upper_height
= (data
->dpi
.height
/ 2) & ScaleByZoom(-1, data
->dpi
.zoom
);
3847 const int split
= data2
->dpi
.top
+ upper_height
;
3848 data2
->dpi
.height
= upper_height
;
3849 for (ParentSpriteToDraw
*psd
: data
->psts
) {
3850 if (psd
->top
< split
) data2
->psts
.push_back(psd
);
3853 ViewportProcessParentSprites(vdd
, data_index_2
);
3854 data
= &vdd
->parent_sprite_sets
[data_index
];
3856 /* vertical split: lower half */
3857 data
->dpi
.dst_ptr
= BlitterFactory::GetCurrentBlitter()->MoveTo(data
->dpi
.dst_ptr
, 0, UnScaleByZoom(upper_height
, data
->dpi
.zoom
));
3858 data
->dpi
.top
= split
;
3859 data
->dpi
.height
= data
->dpi
.height
- upper_height
;
3861 ParentSpriteToSortVector psts
;
3862 for (ParentSpriteToDraw
*psd
: data
->psts
) {
3863 psd
->SetComparisonDone(false);
3864 if (psd
->top
+ psd
->height
> data
->dpi
.top
) {
3865 psts
.push_back(psd
);
3868 data
->psts
= std::move(psts
);
3870 ViewportProcessParentSprites(vdd
, data_index
);
3872 /* horizontal split: left half */
3873 const int left_width
= (data
->dpi
.width
/ 2) & ScaleByZoom(-1, data
->dpi
.zoom
);
3874 const int margin
= UnScaleByZoom(128, data
->dpi
.zoom
); // Half tile (1 column) margin either side of split
3875 const int split
= data2
->dpi
.left
+ left_width
;
3876 data2
->dpi
.width
= left_width
;
3877 for (ParentSpriteToDraw
*psd
: data
->psts
) {
3878 if (psd
->left
< split
+ margin
) data2
->psts
.push_back(psd
);
3881 ViewportProcessParentSprites(vdd
, data_index_2
);
3882 data
= &vdd
->parent_sprite_sets
[data_index
];
3884 /* horizontal split: right half */
3885 data
->dpi
.dst_ptr
= BlitterFactory::GetCurrentBlitter()->MoveTo(data
->dpi
.dst_ptr
, UnScaleByZoom(left_width
, data
->dpi
.zoom
), 0);
3886 data
->dpi
.left
= split
;
3887 data
->dpi
.width
= data
->dpi
.width
- left_width
;
3889 ParentSpriteToSortVector psts
;
3890 for (ParentSpriteToDraw
*psd
: data
->psts
) {
3891 psd
->SetComparisonDone(false);
3892 if (psd
->left
+ psd
->width
> data
->dpi
.left
- margin
) {
3893 psts
.push_back(psd
);
3896 data
->psts
= std::move(psts
);
3898 ViewportProcessParentSprites(vdd
, data_index
);
3901 _vp_sprite_sorter(&data
->psts
);
3905 static void ViewportDoDrawPhase2(Viewport
*vp
, ViewportDrawerDynamic
*vdd
);
3906 static void ViewportDoDrawPhase3(Viewport
*vp
);
3907 static void ViewportDoDrawRenderJob(Viewport
*vp
, ViewportDrawerDynamic
*vdd
);
3909 /* This is run in the main thread */
3910 void ViewportDoDraw(Viewport
*vp
, int left
, int top
, int right
, int bottom
, uint8_t display_flags
)
3912 if (_spare_viewport_drawers
.empty()) {
3913 _vdd
.reset(new ViewportDrawerDynamic());
3915 _vdd
= std::move(_spare_viewport_drawers
.back());
3916 _spare_viewport_drawers
.pop_back();
3919 _vdd
->display_flags
= display_flags
;
3920 _vdd
->transparency_opt
= _transparency_opt
;
3921 _vdd
->invisibility_opt
= _invisibility_opt
;
3923 _vdd
->dpi
.zoom
= vp
->zoom
;
3924 int mask
= ScaleByZoom(-1, vp
->zoom
);
3926 _vd
.combine_sprites
= SPRITE_COMBINE_NONE
;
3928 _vdd
->dpi
.width
= (right
- left
) & mask
;
3929 _vdd
->dpi
.height
= (bottom
- top
) & mask
;
3930 _vdd
->dpi
.left
= left
& mask
;
3931 _vdd
->dpi
.top
= top
& mask
;
3932 _vdd
->dpi
.pitch
= _cur_dpi
->pitch
;
3933 _vd
.last_child
= NO_CHILD_STORE
;
3935 _vdd
->offset_x
= UnScaleByZoomLower(_vdd
->dpi
.left
- (vp
->virtual_left
& mask
), vp
->zoom
);
3936 _vdd
->offset_y
= UnScaleByZoomLower(_vdd
->dpi
.top
- (vp
->virtual_top
& mask
), vp
->zoom
);
3937 int x
= _vdd
->offset_x
+ vp
->left
;
3938 int y
= _vdd
->offset_y
+ vp
->top
;
3940 _vdd
->dpi
.dst_ptr
= BlitterFactory::GetCurrentBlitter()->MoveTo(_cur_dpi
->dst_ptr
, x
- _cur_dpi
->left
, y
- _cur_dpi
->top
);
3942 AutoRestoreBackup
dpi_backup(_cur_dpi
, &_vdd
->dpi
);
3944 if (vp
->overlay
!= nullptr && vp
->overlay
->GetCargoMask() != 0 && vp
->overlay
->GetCompanyMask() != 0) {
3945 vp
->overlay
->PrepareDraw();
3947 if (vp
->zoom
>= ZOOM_LVL_DRAW_MAP
&& (vp
->overlay_pixel_cache
.empty() || vp
->last_overlay_rebuild_counter
!= vp
->overlay
->GetRebuildCounter())) {
3948 vp
->last_overlay_rebuild_counter
= vp
->overlay
->GetRebuildCounter();
3950 vp
->overlay_pixel_cache
.assign(vp
->ScreenArea(), 0xD7);
3952 DrawPixelInfo overlay_dpi
;
3953 overlay_dpi
.dst_ptr
= vp
->overlay_pixel_cache
.data();
3954 overlay_dpi
.height
= vp
->height
;
3955 overlay_dpi
.width
= vp
->width
;
3956 overlay_dpi
.pitch
= vp
->width
;
3957 overlay_dpi
.zoom
= ZOOM_LVL_MIN
;
3958 overlay_dpi
.left
= UnScaleByZoomLower(vp
->virtual_left
, vp
->zoom
);
3959 overlay_dpi
.top
= UnScaleByZoomLower(vp
->virtual_top
, vp
->zoom
);
3961 const int pitch
= vp
->width
;
3962 Blitter_8bppDrawing
blitter(&pitch
);
3963 vp
->overlay
->Draw(&blitter
, &overlay_dpi
);
3967 if (vp
->zoom
>= ZOOM_LVL_DRAW_MAP
) {
3968 /* Here the rendering is like smallmap. */
3969 if (BlitterFactory::GetCurrentBlitter()->GetScreenDepth() == 32) {
3970 if (_settings_client
.gui
.show_slopes_on_viewport_map
) {
3971 ViewportMapDraw
<true, true>(vp
);
3973 ViewportMapDraw
<true, false>(vp
);
3976 _pal2trsp_remap_ptr
= IsTransparencySet(TO_TREES
) ? GetNonSprite(GB(PALETTE_TO_TRANSPARENT
, 0, PALETTE_WIDTH
), SpriteType::Recolour
) : nullptr;
3977 if (_settings_client
.gui
.show_slopes_on_viewport_map
) {
3978 ViewportMapDraw
<false, true>(vp
);
3980 ViewportMapDraw
<false, false>(vp
);
3983 ViewportMapDrawVehicles(&_vdd
->dpi
, vp
);
3984 if (_scrolling_viewport
&& _settings_client
.gui
.show_scrolling_viewport_on_map
) ViewportMapDrawScrollingViewportBox(vp
);
3985 if (unlikely(_thd
.place_mode
== (HT_SPECIAL
| HT_MAP
) && (_thd
.drawstyle
& HT_DRAG_MASK
) == HT_RECT
&& _thd
.select_proc
== DDSP_MEASURE
)) ViewportMapDrawSelection(vp
);
3986 if (vp
->zoom
< ZOOM_LVL_OUT_64X
) ViewportAddKdtreeSigns(_vdd
.get(), &_vdd
->dpi
, true);
3988 if (AreAnyPlansVisible()) {
3989 if (vp
->last_plan_update_number
!= _plan_update_counter
) {
3990 vp
->last_plan_update_number
= _plan_update_counter
;
3992 vp
->plan_pixel_cache
.assign(vp
->ScreenArea(), 0xD7);
3994 DrawPixelInfo plan_dpi
;
3995 plan_dpi
.dst_ptr
= vp
->plan_pixel_cache
.data();
3996 plan_dpi
.height
= vp
->height
;
3997 plan_dpi
.width
= vp
->width
;
3998 plan_dpi
.pitch
= vp
->width
;
3999 plan_dpi
.zoom
= ZOOM_LVL_MIN
;
4000 plan_dpi
.left
= UnScaleByZoomLower(vp
->virtual_left
, vp
->zoom
);
4001 plan_dpi
.top
= UnScaleByZoomLower(vp
->virtual_top
, vp
->zoom
);
4003 const int pitch
= vp
->width
;
4004 Blitter_8bppDrawing
blitter(&pitch
);
4005 ViewportDrawPlans(vp
, &blitter
, &plan_dpi
);
4008 vp
->plan_pixel_cache
.clear();
4011 ViewportDoDrawPhase2(vp
, _vdd
.get());
4012 ViewportDoDrawPhase3(vp
);
4014 /* Classic rendering. */
4015 ViewportAddLandscape();
4016 ViewportAddVehicles(&_vdd
->dpi
, vp
->update_vehicles
);
4018 for (const TileSpriteToDraw
&ts
: _vdd
->tile_sprites_to_draw
) {
4019 PrepareDrawSpriteViewportSpriteStore(_vdd
->sprite_data
, &_vdd
->dpi
, ts
.image
, ts
.pal
);
4021 for (const ParentSpriteToDraw
&ps
: _vdd
->parent_sprites_to_draw
) {
4022 if (ps
.image
!= SPR_EMPTY_BOUNDING_BOX
) PrepareDrawSpriteViewportSpriteStore(_vdd
->sprite_data
, &_vdd
->dpi
, ps
.image
, ps
.pal
);
4024 for (const ChildScreenSpriteToDraw
&cs
: _vdd
->child_screen_sprites_to_draw
) {
4025 PrepareDrawSpriteViewportSpriteStore(_vdd
->sprite_data
, &_vdd
->dpi
, cs
.image
, cs
.pal
);
4028 _viewport_drawer_jobs
++;
4029 extern bool _draw_widget_outlines
;
4030 if (unlikely(_draw_widget_outlines
|| HasBit(_viewport_debug_flags
, VDF_DISABLE_THREAD
))) {
4031 ViewportDoDrawRenderJob(vp
, _vdd
.release());
4033 _general_worker_pool
.EnqueueJob
<ViewportDoDrawRenderJob
>(vp
, _vdd
.release());
4038 /* This is run in a worker thread */
4039 static void ViewportDoDrawRenderSubJob(Viewport
*vp
, ViewportDrawerDynamic
*vdd
, uint data_index
) {
4040 ViewportDrawParentSprites(vdd
, &vdd
->parent_sprite_sets
[data_index
].dpi
, &vdd
->parent_sprite_sets
[data_index
].psts
, &vdd
->child_screen_sprites_to_draw
);
4042 if (_draw_dirty_blocks
&& HasBit(_viewport_debug_flags
, VDF_DIRTY_BLOCK_PER_SPLIT
)) {
4043 ViewportDrawDirtyBlocks(&vdd
->parent_sprite_sets
[data_index
].dpi
, true);
4046 if (vdd
->draw_jobs_active
.fetch_sub(1) != 1) return;
4048 if (_draw_bounding_boxes
) ViewportDrawBoundingBoxes(&vdd
->dpi
, vdd
->parent_sprites_to_draw
);
4050 ViewportDoDrawPhase2(vp
, vdd
);
4052 std::unique_lock
<std::mutex
> lk(_viewport_drawer_return_lock
);
4053 bool notify
= _viewport_drawer_returns
.empty();
4054 ViewportDrawerReturn
&ret
= _viewport_drawer_returns
.emplace_back();
4058 if (notify
) _viewport_drawer_empty_cv
.notify_one();
4061 /* This is run in a worker thread */
4062 static void ViewportDoDrawRenderJob(Viewport
*vp
, ViewportDrawerDynamic
*vdd
)
4064 ViewportAddKdtreeSigns(vdd
, &vdd
->dpi
, false);
4066 DrawTextEffects(vdd
, &vdd
->dpi
, vdd
->IsTransparencySet(TO_LOADING
));
4068 if (vdd
->tile_sprites_to_draw
.size() != 0) {
4069 ViewportDrawTileSprites(vdd
);
4072 vdd
->parent_sprite_sets
.resize(1);
4073 vdd
->parent_sprite_sets
[0].psts
.reserve(vdd
->parent_sprites_to_draw
.size());
4074 for (auto &psd
: vdd
->parent_sprites_to_draw
) {
4075 vdd
->parent_sprite_sets
[0].psts
.push_back(&psd
);
4077 vdd
->parent_sprite_sets
[0].dpi
= vdd
->dpi
;
4079 ViewportProcessParentSprites(vdd
, 0);
4081 vdd
->draw_jobs_active
.store((uint
)vdd
->parent_sprite_sets
.size(), std::memory_order_relaxed
);
4083 for (uint i
= 1; i
< (uint
)vdd
->parent_sprite_sets
.size(); i
++) {
4084 extern bool _draw_widget_outlines
;
4085 if (unlikely(_draw_widget_outlines
|| HasBit(_viewport_debug_flags
, VDF_DISABLE_THREAD
))) {
4086 ViewportDoDrawRenderSubJob(vp
, vdd
, i
);
4088 _general_worker_pool
.EnqueueJob
<ViewportDoDrawRenderSubJob
>(vp
, vdd
, i
);
4092 ViewportDoDrawRenderSubJob(vp
, vdd
, 0);
4095 void ViewportDoDrawProcessAllPending()
4097 if (_viewport_drawer_jobs
== 0) return;
4099 PerformanceAccumulator
framerate(PFE_DRAWWORLD
);
4101 std::unique_lock
<std::mutex
> lk(_viewport_drawer_return_lock
);
4103 if (_viewport_drawer_returns
.empty()) {
4104 _viewport_drawer_empty_cv
.wait(lk
);
4106 Viewport
*vp
= _viewport_drawer_returns
.back().vp
;
4107 _vdd
= std::move(_viewport_drawer_returns
.back().vdd
);
4108 _viewport_drawer_returns
.pop_back();
4112 AutoRestoreBackup
dpi_backup(_cur_dpi
, AutoRestoreBackupNoNewValueTag
{});
4113 ViewportDoDrawPhase3(vp
);
4116 _viewport_drawer_jobs
--;
4117 if (_viewport_drawer_jobs
== 0) return;
4123 /* This may be run either in a worker thread, or in the main thead */
4124 static void ViewportDoDrawPhase2(Viewport
*vp
, ViewportDrawerDynamic
*vdd
)
4126 if (_draw_dirty_blocks
&& !(HasBit(_viewport_debug_flags
, VDF_DIRTY_BLOCK_PER_SPLIT
) && vp
->zoom
< ZOOM_LVL_DRAW_MAP
)) {
4127 ViewportDrawDirtyBlocks(&vdd
->dpi
, HasBit(_viewport_debug_flags
, VDF_DIRTY_BLOCK_PER_DRAW
));
4130 if (vp
->overlay
!= nullptr && vp
->overlay
->GetCargoMask() != 0 && vp
->overlay
->GetCompanyMask() != 0) {
4131 if (vp
->zoom
< ZOOM_LVL_DRAW_MAP
) {
4132 /* translate to window coordinates */
4133 DrawPixelInfo dp
= vdd
->dpi
;
4134 ZoomLevel zoom
= vdd
->dpi
.zoom
;
4135 dp
.zoom
= ZOOM_LVL_MIN
;
4136 dp
.width
= UnScaleByZoom(dp
.width
, zoom
);
4137 dp
.height
= UnScaleByZoom(dp
.height
, zoom
);
4138 dp
.left
= vdd
->offset_x
+ vp
->left
;
4139 dp
.top
= vdd
->offset_y
+ vp
->top
;
4140 vp
->overlay
->Draw(BlitterFactory::GetCurrentBlitter(), &dp
);
4142 const int pixel_cache_start
= vdd
->offset_x
+ (vdd
->offset_y
* vp
->width
);
4143 BlitterFactory::GetCurrentBlitter()->SetRectNoD7(vdd
->dpi
.dst_ptr
, 0, 0, vp
->overlay_pixel_cache
.data() + pixel_cache_start
,
4144 UnScaleByZoom(vdd
->dpi
.height
, vdd
->dpi
.zoom
), UnScaleByZoom(vdd
->dpi
.width
, vdd
->dpi
.zoom
), vp
->width
);
4148 if (_settings_client
.gui
.show_vehicle_route_mode
!= 0 && _settings_client
.gui
.show_vehicle_route
) ViewportDrawVehicleRoutePath(vp
, vdd
);
4151 /* This is run in the main thread */
4152 static void ViewportDoDrawPhase3(Viewport
*vp
)
4154 DrawPixelInfo dp
= _vdd
->dpi
;
4155 ZoomLevel zoom
= _vdd
->dpi
.zoom
;
4156 dp
.zoom
= ZOOM_LVL_MIN
;
4157 dp
.width
= UnScaleByZoom(dp
.width
, zoom
);
4158 dp
.height
= UnScaleByZoom(dp
.height
, zoom
);
4160 if (_vdd
->string_sprites_to_draw
.size() != 0) {
4161 /* translate to world coordinates */
4162 dp
.left
= UnScaleByZoom(_vdd
->dpi
.left
, zoom
);
4163 dp
.top
= UnScaleByZoom(_vdd
->dpi
.top
, zoom
);
4164 ViewportDrawStrings(_vdd
.get(), zoom
, &_vdd
->string_sprites_to_draw
);
4166 if (_settings_client
.gui
.show_vehicle_route_mode
!= 0 && _settings_client
.gui
.show_vehicle_route_steps
&& ViewportDrawHasVehicleRouteSteps()) {
4167 dp
.left
= _vdd
->offset_x
+ vp
->left
;
4168 dp
.top
= _vdd
->offset_y
+ vp
->top
;
4169 ViewportDrawVehicleRouteSteps(vp
);
4173 if (vp
->zoom
< ZOOM_LVL_DRAW_MAP
&& AreAnyPlansVisible()) {
4174 DrawPixelInfo plan_dpi
= _vdd
->MakeDPIForText();
4175 ViewportDrawPlans(vp
, BlitterFactory::GetCurrentBlitter(), &plan_dpi
);
4176 } else if (vp
->zoom
>= ZOOM_LVL_DRAW_MAP
&& !vp
->plan_pixel_cache
.empty()) {
4177 const int pixel_cache_start
= _vdd
->offset_x
+ (_vdd
->offset_y
* vp
->width
);
4178 BlitterFactory::GetCurrentBlitter()->SetRectNoD7(_vdd
->dpi
.dst_ptr
, 0, 0, vp
->plan_pixel_cache
.data() + pixel_cache_start
,
4179 dp
.height
, dp
.width
, vp
->width
);
4182 if (_vdd
->display_flags
& (ND_SHADE_GREY
| ND_SHADE_DIMMED
)) {
4183 DrawPixelInfo dp
= _vdd
->MakeDPIForText();
4184 GfxFillRect(BlitterFactory::GetCurrentBlitter(), &dp
, dp
.left
, dp
.top
, dp
.left
+ dp
.width
, dp
.top
+ dp
.height
,
4185 (_vdd
->display_flags
& ND_SHADE_DIMMED
) ? PALETTE_TO_TRANSPARENT
: PALETTE_NEWSPAPER
, FILLRECT_RECOLOUR
);
4188 _vdd
->bridge_to_map_x
.clear();
4189 _vdd
->bridge_to_map_y
.clear();
4190 _vdd
->string_sprites_to_draw
.clear();
4191 _vdd
->tile_sprites_to_draw
.clear();
4192 _vdd
->parent_sprites_to_draw
.clear();
4193 _vdd
->parent_sprite_sets
.clear();
4194 _vdd
->parent_sprite_subsprites
.Clear();
4195 _vdd
->child_screen_sprites_to_draw
.clear();
4196 _vdd
->sprite_data
.Clear();
4198 _spare_viewport_drawers
.emplace_back(std::move(_vdd
));
4202 * Make sure we don't draw a too big area at a time.
4203 * If we do, the sprite sorter will run into major performance problems and the sprite memory may overflow.
4205 void ViewportDrawChk(Viewport
*vp
, int left
, int top
, int right
, int bottom
, uint8_t display_flags
)
4207 if ((vp
->zoom
< ZOOM_LVL_DRAW_MAP
) && ((int64_t)ScaleByZoom(bottom
- top
, vp
->zoom
) * (int64_t)ScaleByZoom(right
- left
, vp
->zoom
) > (int64_t)(1000000 * ZOOM_BASE
* ZOOM_BASE
))) {
4208 if ((bottom
- top
) > (right
- left
)) {
4209 int t
= (top
+ bottom
) >> 1;
4210 ViewportDrawChk(vp
, left
, top
, right
, t
, display_flags
);
4211 ViewportDrawChk(vp
, left
, t
, right
, bottom
, display_flags
);
4213 int t
= (left
+ right
) >> 1;
4214 ViewportDrawChk(vp
, left
, top
, t
, bottom
, display_flags
);
4215 ViewportDrawChk(vp
, t
, top
, right
, bottom
, display_flags
);
4219 ScaleByZoom(left
- vp
->left
, vp
->zoom
) + vp
->virtual_left
,
4220 ScaleByZoom(top
- vp
->top
, vp
->zoom
) + vp
->virtual_top
,
4221 ScaleByZoom(right
- vp
->left
, vp
->zoom
) + vp
->virtual_left
,
4222 ScaleByZoom(bottom
- vp
->top
, vp
->zoom
) + vp
->virtual_top
,
4228 static inline void ViewportDraw(Viewport
*vp
, int left
, int top
, int right
, int bottom
, uint8_t display_flags
)
4230 if (right
<= vp
->left
|| bottom
<= vp
->top
) return;
4232 if (left
>= vp
->left
+ vp
->width
) return;
4234 if (left
< vp
->left
) left
= vp
->left
;
4235 if (right
> vp
->left
+ vp
->width
) right
= vp
->left
+ vp
->width
;
4237 if (top
>= vp
->top
+ vp
->height
) return;
4239 if (top
< vp
->top
) top
= vp
->top
;
4240 if (bottom
> vp
->top
+ vp
->height
) bottom
= vp
->top
+ vp
->height
;
4242 vp
->is_drawn
= true;
4244 ViewportDrawChk(vp
, left
, top
, right
, bottom
, display_flags
);
4248 * Draw the viewport of this window.
4250 void Window::DrawViewport(uint8_t display_flags
) const
4252 PerformanceAccumulator
framerate(PFE_DRAWWORLD
);
4254 DrawPixelInfo
*dpi
= _cur_dpi
;
4256 dpi
->left
+= this->left
;
4257 dpi
->top
+= this->top
;
4259 ViewportDraw(this->viewport
, dpi
->left
, dpi
->top
, dpi
->left
+ dpi
->width
, dpi
->top
+ dpi
->height
, display_flags
);
4261 dpi
->left
-= this->left
;
4262 dpi
->top
-= this->top
;
4266 * Ensure that a given viewport has a valid scroll position.
4268 * There must be a visible piece of the map in the center of the viewport.
4269 * If there isn't, the viewport will be scrolled to nearest such location.
4271 * @param vp The viewport.
4272 * @param[in,out] scroll_x Viewport X scroll.
4273 * @param[in,out] scroll_y Viewport Y scroll.
4275 static inline void ClampViewportToMap(const Viewport
*vp
, int *scroll_x
, int *scroll_y
)
4277 /* Centre of the viewport is hot spot. */
4279 *scroll_x
+ vp
->virtual_width
/ 2,
4280 *scroll_y
+ vp
->virtual_height
/ 2
4283 /* Find nearest tile that is within borders of the map. */
4285 pt
= InverseRemapCoords2(pt
.x
, pt
.y
, true, &clamped
);
4288 /* Convert back to viewport coordinates and remove centering. */
4289 pt
= RemapCoords2(pt
.x
, pt
.y
);
4290 *scroll_x
= pt
.x
- vp
->virtual_width
/ 2;
4291 *scroll_y
= pt
.y
- vp
->virtual_height
/ 2;
4297 * Clamp the smooth scroll to a maxmimum speed and distance based on time elapsed.
4299 * Every 30ms, we move 1/4th of the distance, to give a smooth movement experience.
4300 * But we never go over the max_scroll speed.
4302 * @param delta_ms Time elapsed since last update.
4303 * @param delta_hi The distance to move in highest dimension (can't be zero).
4304 * @param delta_lo The distance to move in lowest dimension.
4305 * @param[out] delta_hi_clamped The clamped distance to move in highest dimension.
4306 * @param[out] delta_lo_clamped The clamped distance to move in lowest dimension.
4308 static void ClampSmoothScroll(uint32_t delta_ms
, int64_t delta_hi
, int64_t delta_lo
, int &delta_hi_clamped
, int &delta_lo_clamped
)
4310 /** A tile is 64 pixels in width at 1x zoom; viewport coordinates are in 4x zoom. */
4311 constexpr int PIXELS_PER_TILE
= TILE_PIXELS
* 2 * ZOOM_BASE
;
4313 assert(delta_hi
!= 0);
4315 /* Move at most 75% of the distance every 30ms, for a smooth experience */
4316 int64_t delta_left
= delta_hi
* std::pow(0.75, delta_ms
/ 30.0);
4317 /* Move never more than 16 tiles per 30ms. */
4318 int max_scroll
= ScaleByMapSize1D(16 * PIXELS_PER_TILE
* delta_ms
/ 30);
4320 /* We never go over the max_scroll speed. */
4321 delta_hi_clamped
= Clamp(delta_hi
- delta_left
, -max_scroll
, max_scroll
);
4322 /* The lower delta is in ratio of the higher delta, so we keep going straight at the destination. */
4323 delta_lo_clamped
= delta_lo
* delta_hi_clamped
/ delta_hi
;
4325 /* Ensure we always move (delta_hi can't be zero). */
4326 if (delta_hi_clamped
== 0) {
4327 delta_hi_clamped
= delta_hi
> 0 ? 1 : -1;
4332 * Update the next viewport position being displayed.
4333 * @param w %Window owning the viewport.
4335 void UpdateNextViewportPosition(Window
*w
, uint32_t delta_ms
)
4337 const Viewport
*vp
= w
->viewport
;
4339 if (w
->viewport
->follow_vehicle
!= INVALID_VEHICLE
) {
4340 const Vehicle
*veh
= Vehicle::Get(w
->viewport
->follow_vehicle
);
4341 Point pt
= MapXYZToViewport(vp
, veh
->x_pos
, veh
->y_pos
, veh
->z_pos
);
4343 w
->viewport
->next_scrollpos_x
= pt
.x
;
4344 w
->viewport
->next_scrollpos_y
= pt
.y
;
4345 w
->viewport
->force_update_overlay_pending
= false;
4347 /* Ensure the destination location is within the map */
4348 ClampViewportToMap(vp
, &w
->viewport
->dest_scrollpos_x
, &w
->viewport
->dest_scrollpos_y
);
4350 int delta_x
= w
->viewport
->dest_scrollpos_x
- w
->viewport
->scrollpos_x
;
4351 int delta_y
= w
->viewport
->dest_scrollpos_y
- w
->viewport
->scrollpos_y
;
4353 int current_x
= w
->viewport
->scrollpos_x
;
4354 int current_y
= w
->viewport
->scrollpos_y
;
4356 w
->viewport
->next_scrollpos_x
= w
->viewport
->scrollpos_x
;
4357 w
->viewport
->next_scrollpos_y
= w
->viewport
->scrollpos_y
;
4359 bool update_overlay
= false;
4360 if (delta_x
!= 0 || delta_y
!= 0) {
4361 if (_settings_client
.gui
.smooth_scroll
) {
4362 int delta_x_clamped
;
4363 int delta_y_clamped
;
4365 if (abs(delta_x
) > abs(delta_y
)) {
4366 ClampSmoothScroll(delta_ms
, delta_x
, delta_y
, delta_x_clamped
, delta_y_clamped
);
4368 ClampSmoothScroll(delta_ms
, delta_y
, delta_x
, delta_y_clamped
, delta_x_clamped
);
4371 w
->viewport
->next_scrollpos_x
+= delta_x_clamped
;
4372 w
->viewport
->next_scrollpos_y
+= delta_y_clamped
;
4374 w
->viewport
->next_scrollpos_x
= w
->viewport
->dest_scrollpos_x
;
4375 w
->viewport
->next_scrollpos_y
= w
->viewport
->dest_scrollpos_y
;
4377 update_overlay
= (w
->viewport
->next_scrollpos_x
== w
->viewport
->dest_scrollpos_x
&&
4378 w
->viewport
->next_scrollpos_y
== w
->viewport
->dest_scrollpos_y
);
4380 w
->viewport
->force_update_overlay_pending
= update_overlay
;
4382 ClampViewportToMap(vp
, &w
->viewport
->next_scrollpos_x
, &w
->viewport
->next_scrollpos_y
);
4384 /* When moving small amounts around the border we can get stuck, and
4385 * not actually move. In those cases, teleport to the destination. */
4386 if ((delta_x
!= 0 || delta_y
!= 0) && current_x
== w
->viewport
->next_scrollpos_x
&& current_y
== w
->viewport
->next_scrollpos_y
) {
4387 w
->viewport
->next_scrollpos_x
= w
->viewport
->dest_scrollpos_x
;
4388 w
->viewport
->next_scrollpos_y
= w
->viewport
->dest_scrollpos_y
;
4391 if (_scrolling_viewport
== w
) UpdateActiveScrollingViewport(w
);
4396 * Apply the next viewport position being displayed.
4397 * @param w %Window owning the viewport.
4399 void ApplyNextViewportPosition(Window
*w
)
4401 w
->viewport
->scrollpos_x
= w
->viewport
->next_scrollpos_x
;
4402 w
->viewport
->scrollpos_y
= w
->viewport
->next_scrollpos_y
;
4403 SetViewportPosition(w
, w
->viewport
->next_scrollpos_x
, w
->viewport
->next_scrollpos_y
, w
->viewport
->force_update_overlay_pending
);
4406 void UpdateViewportSizeZoom(Viewport
*vp
)
4408 vp
->dirty_blocks_per_column
= CeilDiv(vp
->height
, vp
->GetDirtyBlockHeight());
4409 vp
->dirty_blocks_per_row
= CeilDiv(vp
->width
, vp
->GetDirtyBlockWidth());
4410 vp
->dirty_blocks_column_pitch
= CeilDivT(vp
->dirty_blocks_per_column
, VP_BLOCK_BITS
);
4411 vp
->dirty_blocks
.assign(vp
->dirty_blocks_column_pitch
* vp
->dirty_blocks_per_row
, 0);
4412 UpdateViewportDirtyBlockLeftMargin(vp
);
4413 if (vp
->zoom
>= ZOOM_LVL_DRAW_MAP
) {
4414 memset(vp
->map_draw_vehicles_cache
.done_hash_bits
, 0, sizeof(vp
->map_draw_vehicles_cache
.done_hash_bits
));
4415 vp
->map_draw_vehicles_cache
.vehicle_pixels
.assign(CeilDivT
<size_t>(vp
->ScreenArea(), VP_BLOCK_BITS
), 0);
4417 if (BlitterFactory::GetCurrentBlitter()->GetScreenDepth() == 32) {
4418 vp
->land_pixel_cache
.assign(vp
->ScreenArea() * 4, 0xD7);
4420 vp
->land_pixel_cache
.assign(vp
->ScreenArea(), 0xD7);
4422 vp
->overlay_pixel_cache
.clear();
4423 vp
->plan_pixel_cache
.clear();
4425 vp
->map_draw_vehicles_cache
.vehicle_pixels
.clear();
4426 vp
->land_pixel_cache
.clear();
4427 vp
->land_pixel_cache
.shrink_to_fit();
4428 vp
->overlay_pixel_cache
.clear();
4429 vp
->overlay_pixel_cache
.shrink_to_fit();
4430 vp
->plan_pixel_cache
.clear();
4431 vp
->plan_pixel_cache
.shrink_to_fit();
4433 vp
->last_plan_update_number
= 0;
4434 vp
->update_vehicles
= true;
4435 FillViewportCoverageRect();
4438 void UpdateActiveScrollingViewport(Window
*w
)
4440 if (w
!= nullptr && (!_settings_client
.gui
.show_scrolling_viewport_on_map
|| w
->viewport
->zoom
>= ZOOM_LVL_DRAW_MAP
)) w
= nullptr;
4442 const bool bound_valid
= (_scrolling_viewport_bound
.left
!= _scrolling_viewport_bound
.right
);
4444 if (w
== nullptr && !bound_valid
) return;
4446 const int gap
= ScaleByZoom(1, ZOOM_LVL_MAX
);
4448 auto get_bounds
= [](const ViewportData
*vp
) -> Rect
{
4449 return { vp
->next_scrollpos_x
, vp
->next_scrollpos_y
, vp
->next_scrollpos_x
+ vp
->virtual_width
+ 1, vp
->next_scrollpos_y
+ vp
->virtual_height
+ 1 };
4452 if (w
!= nullptr && !bound_valid
) {
4453 const Rect bounds
= get_bounds(w
->viewport
);
4454 MarkAllViewportMapsDirty(bounds
.left
, bounds
.top
, bounds
.right
, bounds
.bottom
);
4455 _scrolling_viewport_bound
= bounds
;
4456 } else if (w
== nullptr && bound_valid
) {
4457 const Rect
&bounds
= _scrolling_viewport_bound
;
4458 MarkAllViewportMapsDirty(bounds
.left
, bounds
.top
, bounds
.right
, bounds
.bottom
);
4459 _scrolling_viewport_bound
= { 0, 0, 0, 0 };
4461 /* Calculate symmetric difference of two rectangles */
4462 const Rect a
= get_bounds(w
->viewport
);
4463 const Rect
&b
= _scrolling_viewport_bound
;
4464 if (a
.left
!= b
.left
) MarkAllViewportMapsDirty(std::min(a
.left
, b
.left
) - gap
, std::min(a
.top
, b
.top
) - gap
, std::max(a
.left
, b
.left
) + gap
, std::max(a
.bottom
, b
.bottom
) + gap
);
4465 if (a
.top
!= b
.top
) MarkAllViewportMapsDirty(std::min(a
.left
, b
.left
) - gap
, std::min(a
.top
, b
.top
) - gap
, std::max(a
.right
, b
.right
) + gap
, std::max(a
.top
, b
.top
) + gap
);
4466 if (a
.right
!= b
.right
) MarkAllViewportMapsDirty(std::min(a
.right
, b
.right
) - gap
, std::min(a
.top
, b
.top
) - gap
, std::max(a
.right
, b
.right
) + gap
, std::max(a
.bottom
, b
.bottom
) + gap
);
4467 if (a
.bottom
!= b
.bottom
) MarkAllViewportMapsDirty(std::min(a
.left
, b
.left
) - gap
, std::min(a
.bottom
, b
.bottom
) - gap
, std::max(a
.right
, b
.right
) + gap
, std::max(a
.bottom
, b
.bottom
) + gap
);
4468 _scrolling_viewport_bound
= a
;
4473 * Marks a viewport as dirty for repaint if it displays (a part of) the area the needs to be repainted.
4474 * @param vp The viewport to mark as dirty
4475 * @param left Left edge of area to repaint
4476 * @param top Top edge of area to repaint
4477 * @param right Right edge of area to repaint
4478 * @param bottom Bottom edge of area to repaint
4481 void MarkViewportDirty(Viewport
* const vp
, int left
, int top
, int right
, int bottom
, ViewportMarkDirtyFlags flags
)
4483 /* Rounding wrt. zoom-out level */
4484 right
+= (1 << vp
->zoom
) - 1;
4485 bottom
+= (1 << vp
->zoom
) - 1;
4487 right
-= vp
->virtual_left
;
4488 if (right
<= 0) return;
4489 right
= std::min(right
, vp
->virtual_width
);
4491 bottom
-= vp
->virtual_top
;
4492 if (bottom
<= 0) return;
4493 bottom
= std::min(bottom
, vp
->virtual_height
);
4495 left
= std::max(0, left
- vp
->virtual_left
);
4497 if (left
>= vp
->virtual_width
) return;
4499 top
= std::max(0, top
- vp
->virtual_top
);
4501 if (top
>= vp
->virtual_height
) return;
4503 uint x
= std::max
<int>(0, UnScaleByZoomLower(left
, vp
->zoom
) - vp
->dirty_block_left_margin
) >> vp
->GetDirtyBlockWidthShift();
4504 uint y
= UnScaleByZoomLower(top
, vp
->zoom
) >> vp
->GetDirtyBlockHeightShift();
4505 uint w
= (std::max
<int>(0, UnScaleByZoom(right
, vp
->zoom
) - 1 - vp
->dirty_block_left_margin
) >> vp
->GetDirtyBlockWidthShift()) + 1 - x
;
4506 uint h
= ((UnScaleByZoom(bottom
, vp
->zoom
) - 1) >> vp
->GetDirtyBlockHeightShift()) + 1 - y
;
4508 if (w
== 0 || h
== 0) return;
4510 uint col_start
= (x
* vp
->dirty_blocks_column_pitch
) + (y
/ VP_BLOCK_BITS
);
4512 if ((y_end
- 1) / VP_BLOCK_BITS
== y
/ VP_BLOCK_BITS
) {
4513 /* Only dirtying a single block row */
4514 const ViewPortBlockT mask
= GetBitMaskSC
<ViewPortBlockT
>(y
% VP_BLOCK_BITS
, h
);
4515 for (uint i
= 0; i
< w
; i
++, col_start
+= vp
->dirty_blocks_column_pitch
) {
4516 vp
->dirty_blocks
[col_start
] |= mask
;
4519 /* Dirtying multiple block rows */
4520 const uint h_non_first
= y_end
- Align(y
+ 1, VP_BLOCK_BITS
); // Height, excluding the first block
4521 for (uint i
= 0; i
< w
; i
++, col_start
+= vp
->dirty_blocks_column_pitch
) {
4522 uint pos
= col_start
;
4524 /* Set only high bits for first block in column */
4525 vp
->dirty_blocks
[pos
] |= (~static_cast<ViewPortBlockT
>(0)) << (y
% VP_BLOCK_BITS
);
4527 uint left
= h_non_first
;
4530 if (left
< VP_BLOCK_BITS
) {
4531 /* Set only low bits for last block in column */
4532 vp
->dirty_blocks
[pos
] |= GetBitMaskSC
<ViewPortBlockT
>(0, left
);
4535 /* Set all bits for middle blocks in column */
4536 vp
->dirty_blocks
[pos
] = ~static_cast<ViewPortBlockT
>(0);
4538 left
-= VP_BLOCK_BITS
;
4542 vp
->is_dirty
= true;
4544 if (unlikely(vp
->zoom
>= ZOOM_LVL_DRAW_MAP
&& !(flags
& VMDF_NOT_LANDSCAPE
))) {
4545 uint l
= UnScaleByZoomLower(left
, vp
->zoom
);
4546 uint t
= UnScaleByZoomLower(top
, vp
->zoom
);
4547 uint w
= UnScaleByZoom(right
, vp
->zoom
) - l
;
4548 uint h
= UnScaleByZoom(bottom
, vp
->zoom
) - t
;
4549 uint bitdepth
= BlitterFactory::GetCurrentBlitter()->GetScreenDepth() / 8;
4550 uint8_t *land_cache
= vp
->land_pixel_cache
.data() + ((l
+ (t
* vp
->width
)) * bitdepth
);
4552 memset(land_cache
, 0xD7, (size_t)w
* bitdepth
);
4553 land_cache
+= vp
->width
* bitdepth
;
4559 * Mark all viewports that display an area as dirty (in need of repaint).
4560 * @param left Left edge of area to repaint. (viewport coordinates, that is wrt. #ZOOM_LVL_MIN)
4561 * @param top Top edge of area to repaint. (viewport coordinates, that is wrt. #ZOOM_LVL_MIN)
4562 * @param right Right edge of area to repaint. (viewport coordinates, that is wrt. #ZOOM_LVL_MIN)
4563 * @param bottom Bottom edge of area to repaint. (viewport coordinates, that is wrt. #ZOOM_LVL_MIN)
4564 * @param flags To tell if an update is relevant or not (for example, animations in map mode are not)
4567 void MarkAllViewportsDirty(int left
, int top
, int right
, int bottom
, ViewportMarkDirtyFlags flags
)
4569 for (uint i
= 0; i
< _viewport_window_cache
.size(); i
++) {
4570 if (flags
& VMDF_NOT_MAP_MODE
&& _viewport_window_cache
[i
]->zoom
>= ZOOM_LVL_DRAW_MAP
) continue;
4571 if (flags
& VMDF_NOT_MAP_MODE_NON_VEG
&& _viewport_window_cache
[i
]->zoom
>= ZOOM_LVL_DRAW_MAP
&& _viewport_window_cache
[i
]->map_type
!= VPMT_VEGETATION
) continue;
4572 const Rect
&r
= _viewport_coverage_rects
[i
];
4573 if (left
>= r
.right
||
4579 MarkViewportDirty(_viewport_window_cache
[i
], left
, top
, right
, bottom
, flags
);
4583 static void MarkRouteStepDirty(RouteStepsMap::const_iterator cit
)
4585 const uint size
= cit
->second
.size() > max_rank_order_type_count
? 1 : (uint
)cit
->second
.size();
4586 MarkRouteStepDirty(cit
->first
, size
);
4589 static void MarkRouteStepDirty(const TileIndex tile
, uint order_nr
)
4591 dbg_assert(tile
!= INVALID_TILE
);
4592 const Point pt
= RemapCoords2(TileX(tile
) * TILE_SIZE
+ TILE_SIZE
/ 2, TileY(tile
) * TILE_SIZE
+ TILE_SIZE
/ 2);
4593 const int char_height
= GetCharacterHeight(FS_SMALL
) + 1;
4594 const int max_width
= _vp_route_step_base_width
+ _vp_route_step_string_width
[3];
4595 const int half_width_base
= (max_width
/ 2) + 1;
4596 for (Viewport
* const vp
: _viewport_window_cache
) {
4597 const int half_width
= ScaleByZoom(half_width_base
, vp
->zoom
);
4598 const int height
= ScaleByZoom(_vp_route_step_height_top
+ char_height
* order_nr
+ _vp_route_step_height_bottom
, vp
->zoom
);
4599 MarkViewportDirty(vp
, pt
.x
- half_width
, pt
.y
- height
, pt
.x
+ half_width
, pt
.y
, VMDF_NOT_LANDSCAPE
);
4603 void ViewportRouteOverlay::PrepareRouteStepsAndMarkDirtyIfChanged(const Vehicle
*veh
)
4605 this->PrepareRouteSteps(veh
);
4606 if (this->route_steps
!= this->route_steps_last_mark_dirty
) {
4607 for (RouteStepsMap::const_iterator cit
= this->route_steps_last_mark_dirty
.begin(); cit
!= this->route_steps_last_mark_dirty
.end(); cit
++) {
4608 MarkRouteStepDirty(cit
);
4610 for (RouteStepsMap::const_iterator cit
= this->route_steps
.begin(); cit
!= this->route_steps
.end(); ++cit
) {
4611 MarkRouteStepDirty(cit
);
4613 this->route_steps_last_mark_dirty
= this->route_steps
;
4618 * Mark all viewports in map mode that display an area as dirty (in need of repaint).
4619 * @param left Left edge of area to repaint
4620 * @param top Top edge of area to repaint
4621 * @param right Right edge of area to repaint
4622 * @param bottom Bottom edge of area to repaint
4625 void MarkAllViewportMapsDirty(int left
, int top
, int right
, int bottom
)
4627 for (Viewport
*vp
: _viewport_window_cache
) {
4628 if (vp
->zoom
>= ZOOM_LVL_DRAW_MAP
) {
4629 MarkViewportDirty(vp
, left
, top
, right
, bottom
, VMDF_NOT_LANDSCAPE
);
4634 void MarkAllViewportMapLandscapesDirty()
4636 for (Window
*w
: Window::Iterate()) {
4637 Viewport
*vp
= w
->viewport
;
4638 if (vp
!= nullptr && vp
->zoom
>= ZOOM_LVL_DRAW_MAP
) {
4639 ClearViewportLandPixelCache(vp
);
4645 void MarkWholeNonMapViewportsDirty()
4647 for (Window
*w
: Window::Iterate()) {
4648 Viewport
*vp
= w
->viewport
;
4649 if (vp
!= nullptr && vp
->zoom
< ZOOM_LVL_DRAW_MAP
) {
4656 * Mark all viewport overlays for a specific station dirty (in need of repaint).
4660 void MarkAllViewportOverlayStationLinksDirty(const Station
*st
)
4662 for (Viewport
*vp
: _viewport_window_cache
) {
4663 if (vp
->overlay
!= nullptr) {
4664 vp
->overlay
->MarkStationViewportLinksDirty(st
);
4669 void ConstrainAllViewportsZoom()
4671 for (Window
*w
: Window::Iterate()) {
4672 if (w
->viewport
== nullptr) continue;
4674 ZoomLevel zoom
= static_cast<ZoomLevel
>(Clamp(w
->viewport
->zoom
, _settings_client
.gui
.zoom_min
, _settings_client
.gui
.zoom_max
));
4675 if (zoom
!= w
->viewport
->zoom
) {
4676 while (w
->viewport
->zoom
< zoom
) DoZoomInOutWindow(ZOOM_OUT
, w
);
4677 while (w
->viewport
->zoom
> zoom
) DoZoomInOutWindow(ZOOM_IN
, w
);
4683 * Mark a tile given by its index dirty for repaint.
4684 * @param tile The tile to mark dirty.
4685 * @param flags To tell if an update is relevant or not (for example, animations in map mode are not).
4686 * @param bridge_level_offset Height of bridge on tile to also mark dirty. (Height level relative to north corner.)
4687 * @param tile_height_override Height of the tile (#TileHeight).
4690 void MarkTileDirtyByTile(TileIndex tile
, ViewportMarkDirtyFlags flags
, int bridge_level_offset
, int tile_height_override
)
4692 Point pt
= RemapCoords(TileX(tile
) * TILE_SIZE
, TileY(tile
) * TILE_SIZE
, tile_height_override
* TILE_HEIGHT
);
4693 MarkAllViewportsDirty(
4694 pt
.x
- 31 * ZOOM_BASE
,
4695 pt
.y
- 122 * ZOOM_BASE
- ZOOM_BASE
* TILE_HEIGHT
* bridge_level_offset
,
4696 pt
.x
- 31 * ZOOM_BASE
+ 67 * ZOOM_BASE
,
4697 pt
.y
- 122 * ZOOM_BASE
+ 154 * ZOOM_BASE
,
4702 void MarkTileGroundDirtyByTile(TileIndex tile
, ViewportMarkDirtyFlags flags
)
4704 int x
= TileX(tile
) * TILE_SIZE
;
4705 int y
= TileY(tile
) * TILE_SIZE
;
4706 Point top
= RemapCoords(x
, y
, GetTileMaxPixelZ(tile
));
4707 Point bot
= RemapCoords(x
+ TILE_SIZE
, y
+ TILE_SIZE
, GetTilePixelZ(tile
));
4708 MarkAllViewportsDirty(top
.x
- TILE_PIXELS
* ZOOM_BASE
, top
.y
- TILE_HEIGHT
* ZOOM_BASE
, top
.x
+ TILE_PIXELS
* ZOOM_BASE
, bot
.y
, flags
);
4711 void MarkViewportLineDirty(Viewport
* const vp
, const Point from_pt
, const Point to_pt
, const int block_radius
, ViewportMarkDirtyFlags flags
)
4713 int x1
= from_pt
.x
/ block_radius
;
4714 int y1
= from_pt
.y
/ block_radius
;
4715 const int x2
= to_pt
.x
/ block_radius
;
4716 const int y2
= to_pt
.y
/ block_radius
;
4718 /* http://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm#Simplification */
4719 const int dx
= abs(x2
- x1
);
4720 const int dy
= abs(y2
- y1
);
4721 const int sx
= (x1
< x2
) ? 1 : -1;
4722 const int sy
= (y1
< y2
) ? 1 : -1;
4727 (x1
- 2) * block_radius
,
4728 (y1
- 2) * block_radius
,
4729 (x1
+ 2) * block_radius
,
4730 (y1
+ 2) * block_radius
,
4733 if (x1
== x2
&& y1
== y2
) break;
4734 const int e2
= 2 * err
;
4746 void MarkTileLineDirty(const TileIndex from_tile
, const TileIndex to_tile
, ViewportMarkDirtyFlags flags
)
4748 dbg_assert(from_tile
!= INVALID_TILE
);
4749 dbg_assert(to_tile
!= INVALID_TILE
);
4751 const Point from_pt
= RemapCoords2(TileX(from_tile
) * TILE_SIZE
+ TILE_SIZE
/ 2, TileY(from_tile
) * TILE_SIZE
+ TILE_SIZE
/ 2);
4752 const Point to_pt
= RemapCoords2(TileX(to_tile
) * TILE_SIZE
+ TILE_SIZE
/ 2, TileY(to_tile
) * TILE_SIZE
+ TILE_SIZE
/ 2);
4754 for (Viewport
* const vp
: _viewport_window_cache
) {
4755 if (flags
& VMDF_NOT_MAP_MODE
&& vp
->zoom
>= ZOOM_LVL_DRAW_MAP
) continue;
4757 const int block_shift
= 2 + vp
->zoom
;
4759 int x1
= from_pt
.x
>> block_shift
;
4760 int y1
= from_pt
.y
>> block_shift
;
4761 const int x2
= to_pt
.x
>> block_shift
;
4762 const int y2
= to_pt
.y
>> block_shift
;
4764 /* http://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm#Simplification */
4765 const int dx
= abs(x2
- x1
);
4766 const int dy
= abs(y2
- y1
);
4767 const int sx
= (x1
< x2
) ? 1 : -1;
4768 const int sy
= (y1
< y2
) ? 1 : -1;
4773 (x1
- 1) << block_shift
,
4774 (y1
- 1) << block_shift
,
4775 (x1
+ 2) << block_shift
,
4776 (y1
+ 2) << block_shift
,
4779 if (x1
== x2
&& y1
== y2
) break;
4780 const int e2
= 2 * err
;
4793 static void MarkRoutePathsDirty(const std::vector
<DrawnPathRouteTileLine
> &lines
)
4795 for (const DrawnPathRouteTileLine
&it
: lines
) {
4796 MarkTileLineDirty(it
.from_tile
, it
.to_tile
, VMDF_NOT_LANDSCAPE
);
4800 void ViewportRouteOverlay::PrepareRoutePathsAndMarkDirtyIfChanged(const Vehicle
*veh
)
4802 this->PrepareRoutePaths(veh
);
4803 if (this->route_paths_last_mark_dirty
!= this->route_paths
) {
4804 MarkRoutePathsDirty(this->route_paths_last_mark_dirty
);
4805 MarkRoutePathsDirty(this->route_paths
);
4806 this->route_paths_last_mark_dirty
= this->route_paths
;
4810 void ViewportRouteOverlay::PrepareRouteAndMarkDirtyIfChanged(const Vehicle
*veh
)
4812 this->PrepareRoutePathsAndMarkDirtyIfChanged(veh
);
4813 this->PrepareRouteStepsAndMarkDirtyIfChanged(veh
);
4816 void HandleViewportRoutePathFocusChange(const Window
*old
, const Window
*focused
)
4818 const Vehicle
*old_v
= (old
!= nullptr) ? GetVehicleFromWindow(old
) : nullptr;
4819 const Vehicle
*new_v
= (focused
!= nullptr) ? GetVehicleFromWindow(focused
) : nullptr;
4820 if (old_v
!= new_v
) {
4821 _vp_focused_window_route_overlay
.PrepareRouteAndMarkDirtyIfChanged(new_v
);
4825 void AddFixedViewportRoutePath(VehicleID veh
)
4827 FixedVehicleViewportRouteOverlay
&overlay
= _vp_fixed_route_overlays
.emplace_back();
4831 void RemoveFixedViewportRoutePath(VehicleID veh
)
4833 container_unordered_remove_if(_vp_fixed_route_overlays
, [&](FixedVehicleViewportRouteOverlay
&it
) -> bool {
4834 if (it
.veh
== veh
) {
4835 it
.PrepareRouteAndMarkDirtyIfChanged(nullptr);
4842 void ChangeFixedViewportRoutePath(VehicleID from
, VehicleID to
)
4844 for (auto &it
: _vp_fixed_route_overlays
) {
4845 if (it
.veh
== from
) it
.veh
= to
;
4850 * Marks the selected tiles as dirty.
4852 * This function marks the selected tiles as dirty for repaint
4856 static void SetSelectionTilesDirty()
4858 int x_size
= _thd
.size
.x
;
4859 int y_size
= _thd
.size
.y
;
4861 if (!_thd
.diagonal
) { // Selecting in a straight rectangle (or a single square)
4862 int x_start
= _thd
.pos
.x
;
4863 int y_start
= _thd
.pos
.y
;
4865 if (_thd
.outersize
.x
!= 0 || _thd
.outersize
.y
!= 0) {
4866 x_size
+= _thd
.outersize
.x
;
4867 x_start
+= _thd
.offs
.x
;
4868 y_size
+= _thd
.outersize
.y
;
4869 y_start
+= _thd
.offs
.y
;
4872 x_size
-= TILE_SIZE
;
4873 y_size
-= TILE_SIZE
;
4875 dbg_assert(x_size
>= 0);
4876 dbg_assert(y_size
>= 0);
4878 int x_end
= Clamp(x_start
+ x_size
, 0, MapSizeX() * TILE_SIZE
- TILE_SIZE
);
4879 int y_end
= Clamp(y_start
+ y_size
, 0, MapSizeY() * TILE_SIZE
- TILE_SIZE
);
4881 x_start
= Clamp(x_start
, 0, MapSizeX() * TILE_SIZE
- TILE_SIZE
);
4882 y_start
= Clamp(y_start
, 0, MapSizeY() * TILE_SIZE
- TILE_SIZE
);
4884 /* make sure everything is multiple of TILE_SIZE */
4885 dbg_assert((x_end
| y_end
| x_start
| y_start
) % TILE_SIZE
== 0);
4888 * Suppose we have to mark dirty rectangle of 3x4 tiles:
4895 * This algorithm marks dirty columns of tiles, so it is done in 3+4-1 steps:
4905 int top_x
= x_end
; // coordinates of top dirty tile
4906 int top_y
= y_start
;
4907 int bot_x
= top_x
; // coordinates of bottom dirty tile
4910 const bool conservative_mode
= (_thd
.place_mode
& HT_MAP
) && !_viewport_vehicle_map_redraw_rects
.empty();
4913 /* topmost dirty point */
4914 TileIndex top_tile
= TileVirtXY(top_x
, top_y
);
4915 Point top
= RemapCoords(top_x
, top_y
, conservative_mode
? _settings_game
.construction
.map_height_limit
* TILE_HEIGHT
: GetTileMaxPixelZ(top_tile
));
4917 /* bottommost point */
4918 TileIndex bottom_tile
= TileVirtXY(bot_x
, bot_y
);
4919 Point bot
= RemapCoords(bot_x
+ TILE_SIZE
, bot_y
+ TILE_SIZE
, conservative_mode
? 0 : GetTilePixelZ(bottom_tile
)); // bottommost point
4921 /* the 'x' coordinate of 'top' and 'bot' is the same (and always in the same distance from tile middle),
4922 * tile height/slope affects only the 'y' on-screen coordinate! */
4924 int l
= top
.x
- TILE_PIXELS
* ZOOM_BASE
; // 'x' coordinate of left side of the dirty rectangle
4925 int t
= top
.y
; // 'y' coordinate of top side of the dirty rectangle
4926 int r
= top
.x
+ TILE_PIXELS
* ZOOM_BASE
; // 'x' coordinate of right side of the dirty rectangle
4927 int b
= bot
.y
; // 'y' coordinate of bottom side of the dirty rectangle
4929 static const int OVERLAY_WIDTH
= conservative_mode
? 2 << ZOOM_LVL_END
: 4 * ZOOM_BASE
; // part of selection sprites is drawn outside the selected area (in particular: terraforming)
4931 /* For halftile foundations on SLOPE_STEEP_S the sprite extents some more towards the top */
4932 ViewportMarkDirtyFlags mode
= (_thd
.place_mode
& HT_MAP
) ? VMDF_NOT_LANDSCAPE
: VMDF_NOT_MAP_MODE
;
4933 MarkAllViewportsDirty(l
- OVERLAY_WIDTH
, t
- OVERLAY_WIDTH
- TILE_HEIGHT
* ZOOM_BASE
, r
+ OVERLAY_WIDTH
, b
+ OVERLAY_WIDTH
, mode
);
4935 /* haven't we reached the topmost tile yet? */
4936 if (top_x
!= x_start
) {
4942 /* the way the bottom tile changes is different when we reach the bottommost tile */
4943 if (bot_y
!= y_end
) {
4948 } while (bot_x
>= top_x
);
4949 } else { // Selecting in a 45 degrees rotated (diagonal) rectangle.
4950 /* a_size, b_size describe a rectangle with rotated coordinates */
4951 int a_size
= x_size
+ y_size
, b_size
= x_size
- y_size
;
4953 int interval_a
= a_size
< 0 ? -(int)TILE_SIZE
: (int)TILE_SIZE
;
4954 int interval_b
= b_size
< 0 ? -(int)TILE_SIZE
: (int)TILE_SIZE
;
4956 for (int a
= -interval_a
; a
!= a_size
+ interval_a
; a
+= interval_a
) {
4957 for (int b
= -interval_b
; b
!= b_size
+ interval_b
; b
+= interval_b
) {
4958 uint x
= (_thd
.pos
.x
+ (a
+ b
) / 2) / TILE_SIZE
;
4959 uint y
= (_thd
.pos
.y
+ (a
- b
) / 2) / TILE_SIZE
;
4961 if (x
< MapMaxX() && y
< MapMaxY()) {
4962 MarkTileDirtyByTile(TileXY(x
, y
), VMDF_NOT_MAP_MODE
);
4970 void SetSelectionRed(bool b
)
4972 SetSelectionPalette(b
? PALETTE_SEL_TILE_RED
: PAL_NONE
);
4975 void SetSelectionPalette(PaletteID pal
)
4977 _thd
.square_palette
= pal
;
4978 SetSelectionTilesDirty();
4982 * Test whether a sign is below the mouse
4983 * @param vp the clicked viewport
4984 * @param x X position of click
4985 * @param y Y position of click
4986 * @param sign the sign to check
4987 * @return true if the sign was hit
4989 static bool CheckClickOnViewportSign(const Viewport
*vp
, int x
, int y
, const ViewportSign
*sign
)
4991 bool small
= (vp
->zoom
>= ZOOM_LVL_OUT_4X
);
4992 int sign_half_width
= ScaleByZoom((small
? sign
->width_small
: sign
->width_normal
) / 2, vp
->zoom
);
4993 int sign_height
= ScaleByZoom(WidgetDimensions::scaled
.fullbevel
.top
+ GetCharacterHeight(small
? FS_SMALL
: FS_NORMAL
) + WidgetDimensions::scaled
.fullbevel
.bottom
, vp
->zoom
);
4995 return y
>= sign
->top
&& y
< sign
->top
+ sign_height
&&
4996 x
>= sign
->center
- sign_half_width
&& x
< sign
->center
+ sign_half_width
;
5001 * Check whether any viewport sign was clicked, and dispatch the click.
5002 * @param vp the clicked viewport
5003 * @param x X position of click
5004 * @param y Y position of click
5005 * @return true if the sign was hit
5007 static bool CheckClickOnViewportSign(const Viewport
*vp
, int x
, int y
)
5009 if (_game_mode
== GM_MENU
) return false;
5011 x
= ScaleByZoom(x
- vp
->left
, vp
->zoom
) + vp
->virtual_left
;
5012 y
= ScaleByZoom(y
- vp
->top
, vp
->zoom
) + vp
->virtual_top
;
5014 Rect search_rect
{ x
- 1, y
- 1, x
+ 1, y
+ 1 };
5015 search_rect
= ExpandRectWithViewportSignMargins(search_rect
, vp
->zoom
);
5017 bool show_stations
= HasBit(_display_opt
, DO_SHOW_STATION_NAMES
) && !IsInvisibilitySet(TO_SIGNS
);
5018 bool show_waypoints
= HasBit(_display_opt
, DO_SHOW_WAYPOINT_NAMES
) && !IsInvisibilitySet(TO_SIGNS
);
5019 bool show_towns
= HasBit(_display_opt
, DO_SHOW_TOWN_NAMES
);
5020 bool show_signs
= HasBit(_display_opt
, DO_SHOW_SIGNS
) && !IsInvisibilitySet(TO_SIGNS
);
5021 bool show_competitors
= HasBit(_display_opt
, DO_SHOW_COMPETITOR_SIGNS
);
5022 bool hide_hidden_waypoints
= _settings_client
.gui
.allow_hiding_waypoint_labels
&& !HasBit(_extra_display_opt
, XDO_SHOW_HIDDEN_SIGNS
);
5024 /* Topmost of each type that was hit */
5025 BaseStation
*st
= nullptr, *last_st
= nullptr;
5026 Town
*t
= nullptr, *last_t
= nullptr;
5027 Sign
*si
= nullptr, *last_si
= nullptr;
5029 /* See ViewportAddKdtreeSigns() for details on the search logic */
5030 _viewport_sign_kdtree
.FindContained(search_rect
.left
, search_rect
.top
, search_rect
.right
, search_rect
.bottom
, [&](const ViewportSignKdtreeItem
& item
) {
5031 switch (item
.type
) {
5032 case ViewportSignKdtreeItem::VKI_STATION
:
5033 if (!show_stations
) break;
5034 st
= BaseStation::Get(item
.id
.station
);
5035 if (!show_competitors
&& _local_company
!= st
->owner
&& st
->owner
!= OWNER_NONE
) break;
5036 if (CheckClickOnViewportSign(vp
, x
, y
, &st
->sign
)) last_st
= st
;
5039 case ViewportSignKdtreeItem::VKI_WAYPOINT
:
5040 if (!show_waypoints
) break;
5041 st
= BaseStation::Get(item
.id
.station
);
5042 if (!show_competitors
&& _local_company
!= st
->owner
&& st
->owner
!= OWNER_NONE
) break;
5043 if (hide_hidden_waypoints
&& HasBit(Waypoint::From(st
)->waypoint_flags
, WPF_HIDE_LABEL
)) break;
5044 if (CheckClickOnViewportSign(vp
, x
, y
, &st
->sign
)) last_st
= st
;
5047 case ViewportSignKdtreeItem::VKI_TOWN
:
5048 if (!show_towns
) break;
5049 t
= Town::Get(item
.id
.town
);
5050 if (CheckClickOnViewportSign(vp
, x
, y
, &t
->cache
.sign
)) last_t
= t
;
5053 case ViewportSignKdtreeItem::VKI_SIGN
:
5054 if (!show_signs
) break;
5055 si
= Sign::Get(item
.id
.sign
);
5056 if (!show_competitors
&& _local_company
!= si
->owner
&& si
->owner
!= OWNER_DEITY
) break;
5057 if (CheckClickOnViewportSign(vp
, x
, y
, &si
->sign
)) last_si
= si
;
5065 /* Select which hit to handle based on priority */
5066 if (last_st
!= nullptr) {
5067 if (Station::IsExpected(last_st
)) {
5068 ShowStationViewWindow(last_st
->index
);
5070 ShowWaypointWindow(Waypoint::From(last_st
));
5073 } else if (last_t
!= nullptr) {
5074 ShowTownViewWindow(last_t
->index
);
5076 } else if (last_si
!= nullptr) {
5077 HandleClickOnSign(last_si
);
5085 ViewportSignKdtreeItem
ViewportSignKdtreeItem::MakeStation(StationID id
)
5087 ViewportSignKdtreeItem item
;
5088 item
.type
= VKI_STATION
;
5089 item
.id
.station
= id
;
5091 const Station
*st
= Station::Get(id
);
5092 assert(st
->sign
.kdtree_valid
);
5093 item
.center
= st
->sign
.center
;
5094 item
.top
= st
->sign
.top
;
5096 /* Assume the sign can be a candidate for drawing, so measure its width */
5097 _viewport_sign_maxwidth
= std::max
<int>({_viewport_sign_maxwidth
, st
->sign
.width_normal
, st
->sign
.width_small
});
5102 ViewportSignKdtreeItem
ViewportSignKdtreeItem::MakeWaypoint(StationID id
)
5104 ViewportSignKdtreeItem item
;
5105 item
.type
= VKI_WAYPOINT
;
5106 item
.id
.station
= id
;
5108 const Waypoint
*st
= Waypoint::Get(id
);
5109 assert(st
->sign
.kdtree_valid
);
5110 item
.center
= st
->sign
.center
;
5111 item
.top
= st
->sign
.top
;
5113 /* Assume the sign can be a candidate for drawing, so measure its width */
5114 _viewport_sign_maxwidth
= std::max
<int>({_viewport_sign_maxwidth
, st
->sign
.width_normal
, st
->sign
.width_small
});
5119 ViewportSignKdtreeItem
ViewportSignKdtreeItem::MakeTown(TownID id
)
5121 ViewportSignKdtreeItem item
;
5122 item
.type
= VKI_TOWN
;
5125 const Town
*town
= Town::Get(id
);
5126 assert(town
->cache
.sign
.kdtree_valid
);
5127 item
.center
= town
->cache
.sign
.center
;
5128 item
.top
= town
->cache
.sign
.top
;
5130 /* Assume the sign can be a candidate for drawing, so measure its width */
5131 _viewport_sign_maxwidth
= std::max
<int>({_viewport_sign_maxwidth
, town
->cache
.sign
.width_normal
, town
->cache
.sign
.width_small
});
5136 ViewportSignKdtreeItem
ViewportSignKdtreeItem::MakeSign(SignID id
)
5138 ViewportSignKdtreeItem item
;
5139 item
.type
= VKI_SIGN
;
5142 const Sign
*sign
= Sign::Get(id
);
5143 assert(sign
->sign
.kdtree_valid
);
5144 item
.center
= sign
->sign
.center
;
5145 item
.top
= sign
->sign
.top
;
5147 /* Assume the sign can be a candidate for drawing, so measure its width */
5148 _viewport_sign_maxwidth
= std::max
<int>({_viewport_sign_maxwidth
, sign
->sign
.width_normal
, sign
->sign
.width_small
});
5153 void RebuildViewportKdtree()
5155 /* Reset biggest size sign seen */
5156 _viewport_sign_maxwidth
= 0;
5159 _viewport_sign_kdtree_valid
= false;
5160 _viewport_sign_kdtree
.Build
<ViewportSignKdtreeItem
*>(nullptr, nullptr);
5164 _viewport_sign_kdtree_valid
= true;
5166 std::vector
<ViewportSignKdtreeItem
> items
;
5167 items
.reserve(BaseStation::GetNumItems() + Town::GetNumItems() + Sign::GetNumItems());
5169 for (const Station
*st
: Station::Iterate()) {
5170 if (st
->sign
.kdtree_valid
) items
.push_back(ViewportSignKdtreeItem::MakeStation(st
->index
));
5173 for (const Waypoint
*wp
: Waypoint::Iterate()) {
5174 if (wp
->sign
.kdtree_valid
) items
.push_back(ViewportSignKdtreeItem::MakeWaypoint(wp
->index
));
5177 for (const Town
*town
: Town::Iterate()) {
5178 if (town
->cache
.sign
.kdtree_valid
) items
.push_back(ViewportSignKdtreeItem::MakeTown(town
->index
));
5181 for (const Sign
*sign
: Sign::Iterate()) {
5182 if (sign
->sign
.kdtree_valid
) items
.push_back(ViewportSignKdtreeItem::MakeSign(sign
->index
));
5185 _viewport_sign_kdtree
.Build(items
.begin(), items
.end());
5189 static bool CheckClickOnLandscape(const Viewport
*vp
, int x
, int y
)
5191 Point pt
= TranslateXYToTileCoord(vp
, x
, y
);
5193 _tile_fract_coords
.x
= pt
.x
& TILE_UNIT_MASK
;
5194 _tile_fract_coords
.y
= pt
.y
& TILE_UNIT_MASK
;
5196 if (pt
.x
!= -1) return ClickTile(TileVirtXY(pt
.x
, pt
.y
));
5200 static void PlaceObject()
5205 pt
= GetTileBelowCursor();
5206 if (pt
.x
== -1) return;
5208 if ((_thd
.place_mode
& HT_DRAG_MASK
) == HT_POINT
) {
5209 pt
.x
+= TILE_SIZE
/ 2;
5210 pt
.y
+= TILE_SIZE
/ 2;
5213 _tile_fract_coords
.x
= pt
.x
& TILE_UNIT_MASK
;
5214 _tile_fract_coords
.y
= pt
.y
& TILE_UNIT_MASK
;
5216 w
= _thd
.GetCallbackWnd();
5217 if (w
!= nullptr) w
->OnPlaceObject(pt
, TileVirtXY(pt
.x
, pt
.y
));
5220 bool HandleViewportDoubleClicked(Window
*w
, int x
, int y
)
5222 Viewport
*vp
= w
->viewport
;
5223 if (vp
->zoom
< ZOOM_LVL_DRAW_MAP
) return false;
5225 switch (_settings_client
.gui
.action_when_viewport_map_is_dblclicked
) {
5226 case 0: // Do nothing
5228 case 1: // Zoom in main viewport
5229 while (vp
->zoom
!= ZOOM_LVL_VIEWPORT
)
5230 ZoomInOrOutToCursorWindow(true, w
);
5232 case 2: // Open an extra viewport
5233 ShowExtraViewportWindowForTileUnderCursor();
5240 HandleViewportClickedResult
HandleViewportClicked(const Viewport
*vp
, int x
, int y
, bool double_click
)
5242 /* No click in smallmap mode except for plan making and left-button scrolling. */
5243 if (vp
->zoom
>= ZOOM_LVL_DRAW_MAP
&& !(_thd
.place_mode
& HT_MAP
)) return HVCR_SCROLL_ONLY
;
5245 const Vehicle
*v
= CheckClickOnVehicle(vp
, x
, y
);
5247 if (_thd
.place_mode
& HT_VEHICLE
) {
5248 if (v
!= nullptr && VehicleClicked(v
)) return HVCR_DENY
;
5251 /* Vehicle placement mode already handled above. */
5252 if ((_thd
.place_mode
& HT_DRAG_MASK
) != HT_NONE
) {
5253 if (_thd
.place_mode
& HT_POLY
) {
5254 /* In polyline mode double-clicking on a single white line, finishes current polyline.
5255 * If however the user double-clicks on a line that has a white and a blue section,
5256 * both lines (white and blue) will be constructed consecutively. */
5257 static bool stop_snap_on_double_click
= false;
5258 if (double_click
&& stop_snap_on_double_click
) {
5259 SetRailSnapMode(RSM_NO_SNAP
);
5260 HideMeasurementTooltips();
5263 stop_snap_on_double_click
= !(_thd
.drawstyle
& HT_LINE
) || (_thd
.dir2
== HT_DIR_END
);
5270 if (vp
->zoom
>= ZOOM_LVL_DRAW_MAP
) return HVCR_SCROLL_ONLY
;
5272 if (CheckClickOnViewportSign(vp
, x
, y
)) return HVCR_DENY
;
5273 bool result
= CheckClickOnLandscape(vp
, x
, y
);
5276 Debug(misc
, 2, "Vehicle {} (index {}) at {}", v
->unitnumber
, v
->index
, fmt::ptr(v
));
5277 if (IsCompanyBuildableVehicleType(v
)) {
5279 WindowClass wc
= _thd
.GetCallbackWnd()->window_class
;
5280 if (_ctrl_pressed
&& IsVehicleControlAllowed(v
, _local_company
)) {
5281 StartStopVehicle(v
, true);
5282 } else if (wc
!= WC_CREATE_TEMPLATE
&& wc
!= WC_TEMPLATEGUI_MAIN
) {
5283 ShowVehicleViewWindow(v
);
5288 return result
? HVCR_DENY
: HVCR_ALLOW
;
5291 void RebuildViewportOverlay(Window
*w
, bool incremental
)
5293 if (w
->viewport
->overlay
!= nullptr &&
5294 w
->viewport
->overlay
->GetCompanyMask() != 0 &&
5295 w
->viewport
->overlay
->GetCargoMask() != 0) {
5296 w
->viewport
->overlay
->RebuildCache(incremental
);
5297 if (!incremental
) w
->SetDirty();
5302 * Scrolls the viewport in a window to a given location.
5303 * @param x Desired x location of the map to scroll to (world coordinate).
5304 * @param y Desired y location of the map to scroll to (world coordinate).
5305 * @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.
5306 * @param w %Window containing the viewport.
5307 * @param instant Jump to the location instead of slowly moving to it.
5308 * @return Destination of the viewport was changed (to activate other actions when the viewport is already at the desired position).
5310 bool ScrollWindowTo(int x
, int y
, int z
, Window
*w
, bool instant
)
5312 /* The slope cannot be acquired outside of the map, so make sure we are always within the map. */
5314 if ( x
>= 0 && x
<= (int)MapSizeX() * (int)TILE_SIZE
- 1
5315 && y
>= 0 && y
<= (int)MapSizeY() * (int)TILE_SIZE
- 1) {
5316 z
= GetSlopePixelZ(x
, y
);
5318 z
= TileHeightOutsideMap(x
/ (int)TILE_SIZE
, y
/ (int)TILE_SIZE
);
5322 Point pt
= MapXYZToViewport(w
->viewport
, x
, y
, z
);
5323 w
->viewport
->follow_vehicle
= INVALID_VEHICLE
;
5325 if (w
->viewport
->dest_scrollpos_x
== pt
.x
&& w
->viewport
->dest_scrollpos_y
== pt
.y
) return false;
5328 w
->viewport
->scrollpos_x
= pt
.x
;
5329 w
->viewport
->scrollpos_y
= pt
.y
;
5330 RebuildViewportOverlay(w
, true);
5333 w
->viewport
->dest_scrollpos_x
= pt
.x
;
5334 w
->viewport
->dest_scrollpos_y
= pt
.y
;
5339 * Scrolls the viewport in a window to a given location.
5340 * @param tile Desired tile to center on.
5341 * @param w %Window containing the viewport.
5342 * @param instant Jump to the location instead of slowly moving to it.
5343 * @return Destination of the viewport was changed (to activate other actions when the viewport is already at the desired position).
5345 bool ScrollWindowToTile(TileIndex tile
, Window
*w
, bool instant
)
5347 return ScrollWindowTo(TileX(tile
) * TILE_SIZE
, TileY(tile
) * TILE_SIZE
, -1, w
, instant
);
5351 * Scrolls the viewport of the main window to a given location.
5352 * @param tile Desired tile to center on.
5353 * @param instant Jump to the location instead of slowly moving to it.
5354 * @return Destination of the viewport was changed (to activate other actions when the viewport is already at the desired position).
5356 bool ScrollMainWindowToTile(TileIndex tile
, bool instant
)
5358 return ScrollMainWindowTo(TileX(tile
) * TILE_SIZE
+ TILE_SIZE
/ 2, TileY(tile
) * TILE_SIZE
+ TILE_SIZE
/ 2, -1, instant
);
5362 * Set a tile to display a red error square.
5363 * @param tile Tile that should show the red error square.
5365 void SetRedErrorSquare(TileIndex tile
)
5373 if (tile
!= INVALID_TILE
) MarkTileDirtyByTile(tile
, VMDF_NOT_MAP_MODE
);
5374 if (old
!= INVALID_TILE
) MarkTileDirtyByTile(old
, VMDF_NOT_MAP_MODE
);
5379 * Highlight \a w by \a h tiles at the cursor.
5380 * @param w Width of the highlighted tiles rectangle.
5381 * @param h Height of the highlighted tiles rectangle.
5383 void SetTileSelectSize(int w
, int h
)
5385 _thd
.new_size
.x
= w
* TILE_SIZE
;
5386 _thd
.new_size
.y
= h
* TILE_SIZE
;
5387 _thd
.new_outersize
.x
= 0;
5388 _thd
.new_outersize
.y
= 0;
5391 void SetTileSelectBigSize(int ox
, int oy
, int sx
, int sy
)
5393 _thd
.new_offs
.x
= ox
* TILE_SIZE
;
5394 _thd
.new_offs
.y
= oy
* TILE_SIZE
;
5395 _thd
.new_outersize
.x
= sx
* TILE_SIZE
;
5396 _thd
.new_outersize
.y
= sy
* TILE_SIZE
;
5399 /** returns the best autorail highlight type from map coordinates */
5400 static HighLightStyle
GetAutorailHT(int x
, int y
)
5402 return HT_RAIL
| _autorail_piece
[x
& TILE_UNIT_MASK
][y
& TILE_UNIT_MASK
];
5406 * Reset tile highlighting.
5408 void TileHighlightData::Reset()
5412 this->new_pos
.x
= 0;
5413 this->new_pos
.y
= 0;
5417 * Is the user dragging a 'diagonal rectangle'?
5418 * @return User is dragging a rotated rectangle.
5420 bool TileHighlightData::IsDraggingDiagonal()
5422 return (this->place_mode
& HT_DIAGONAL
) != 0 && _ctrl_pressed
&& _left_button_down
;
5426 * Get the window that started the current highlighting.
5427 * @return The window that requested the current tile highlighting, or \c nullptr if not available.
5429 Window
*TileHighlightData::GetCallbackWnd()
5431 if (this->window_token
!= WindowToken(0)) {
5432 return FindWindowByToken(this->window_token
);
5434 return FindWindowById(this->window_class
, this->window_number
);
5437 static HighLightStyle
CalcPolyrailDrawstyle(Point pt
, bool dragging
);
5439 static inline void CalcNewPolylineOutersize()
5441 /* use the 'outersize' to mark the second (blue) part of a polyline selection */
5442 if (_thd
.dir2
< HT_DIR_END
) {
5443 /* get bounds of the second part */
5444 int outer_x1
= _thd
.selstart2
.x
& ~TILE_UNIT_MASK
;
5445 int outer_y1
= _thd
.selstart2
.y
& ~TILE_UNIT_MASK
;
5446 int outer_x2
= _thd
.selend2
.x
& ~TILE_UNIT_MASK
;
5447 int outer_y2
= _thd
.selend2
.y
& ~TILE_UNIT_MASK
;
5448 if (outer_x1
> outer_x2
) Swap(outer_x1
, outer_x2
);
5449 if (outer_y1
> outer_y2
) Swap(outer_y1
, outer_y2
);
5450 /* include the first part */
5451 outer_x1
= std::min
<int>(outer_x1
, _thd
.new_pos
.x
);
5452 outer_y1
= std::min
<int>(outer_y1
, _thd
.new_pos
.y
);
5453 outer_x2
= std::max
<int>(outer_x2
, _thd
.new_pos
.x
+ _thd
.new_size
.x
- TILE_SIZE
);
5454 outer_y2
= std::max
<int>(outer_y2
, _thd
.new_pos
.y
+ _thd
.new_size
.y
- TILE_SIZE
);
5455 /* write new values */
5456 _thd
.new_offs
.x
= outer_x1
- _thd
.new_pos
.x
;
5457 _thd
.new_offs
.y
= outer_y1
- _thd
.new_pos
.y
;
5458 _thd
.new_outersize
.x
= outer_x2
- outer_x1
+ TILE_SIZE
- _thd
.new_size
.x
;
5459 _thd
.new_outersize
.y
= outer_y2
- outer_y1
+ TILE_SIZE
- _thd
.new_size
.y
;
5461 _thd
.new_offs
.x
= 0;
5462 _thd
.new_offs
.y
= 0;
5463 _thd
.new_outersize
.x
= 0;
5464 _thd
.new_outersize
.y
= 0;
5469 * Updates tile highlighting for all cases.
5470 * Uses _thd.selstart and _thd.selend and _thd.place_mode (set elsewhere) to determine _thd.pos and _thd.size
5471 * Also drawstyle is determined. Uses _thd.new.* as a buffer and calls SetSelectionTilesDirty() twice,
5472 * Once for the old and once for the new selection.
5473 * _thd is TileHighlightData, found in viewport.h
5475 void UpdateTileSelection()
5480 if (_thd
.freeze
) return;
5482 HighLightStyle new_drawstyle
= HT_NONE
;
5483 bool new_diagonal
= false;
5485 if ((_thd
.place_mode
& HT_DRAG_MASK
) == HT_SPECIAL
) {
5489 int x2
= _thd
.selstart
.x
& ~TILE_UNIT_MASK
;
5490 int y2
= _thd
.selstart
.y
& ~TILE_UNIT_MASK
;
5491 x1
&= ~TILE_UNIT_MASK
;
5492 y1
&= ~TILE_UNIT_MASK
;
5494 if (_thd
.IsDraggingDiagonal()) {
5495 new_diagonal
= true;
5497 if (x1
>= x2
) Swap(x1
, x2
);
5498 if (y1
>= y2
) Swap(y1
, y2
);
5500 _thd
.new_pos
.x
= x1
;
5501 _thd
.new_pos
.y
= y1
;
5502 _thd
.new_size
.x
= x2
- x1
;
5503 _thd
.new_size
.y
= y2
- y1
;
5504 if (!new_diagonal
) {
5505 _thd
.new_size
.x
+= TILE_SIZE
;
5506 _thd
.new_size
.y
+= TILE_SIZE
;
5508 new_drawstyle
= _thd
.next_drawstyle
;
5510 } else if ((_thd
.place_mode
& HT_DRAG_MASK
) != HT_NONE
) {
5511 Point pt
= GetTileBelowCursor();
5515 switch (_thd
.place_mode
& HT_DRAG_MASK
) {
5517 new_drawstyle
= HT_RECT
;
5520 new_drawstyle
= HT_POINT
;
5521 x1
+= TILE_SIZE
/ 2;
5522 y1
+= TILE_SIZE
/ 2;
5527 if (_thd
.place_mode
& HT_POLY
) {
5528 RailSnapMode snap_mode
= GetRailSnapMode();
5529 if (snap_mode
== RSM_NO_SNAP
||
5530 (snap_mode
== RSM_SNAP_TO_TILE
&& GetRailSnapTile() == TileVirtXY(pt
.x
, pt
.y
))) {
5531 new_drawstyle
= GetAutorailHT(pt
.x
, pt
.y
);
5532 _thd
.new_offs
.x
= 0;
5533 _thd
.new_offs
.y
= 0;
5534 _thd
.new_outersize
.x
= 0;
5535 _thd
.new_outersize
.y
= 0;
5536 _thd
.dir2
= HT_DIR_END
;
5538 new_drawstyle
= CalcPolyrailDrawstyle(pt
, false);
5539 if (new_drawstyle
!= HT_NONE
) {
5540 x1
= _thd
.selstart
.x
& ~TILE_UNIT_MASK
;
5541 y1
= _thd
.selstart
.y
& ~TILE_UNIT_MASK
;
5542 int x2
= _thd
.selend
.x
& ~TILE_UNIT_MASK
;
5543 int y2
= _thd
.selend
.y
& ~TILE_UNIT_MASK
;
5544 if (x1
> x2
) Swap(x1
, x2
);
5545 if (y1
> y2
) Swap(y1
, y2
);
5546 _thd
.new_pos
.x
= x1
;
5547 _thd
.new_pos
.y
= y1
;
5548 _thd
.new_size
.x
= x2
- x1
+ TILE_SIZE
;
5549 _thd
.new_size
.y
= y2
- y1
+ TILE_SIZE
;
5555 if (_thd
.place_mode
& HT_RAIL
) {
5556 /* Draw one highlighted tile in any direction */
5557 new_drawstyle
= GetAutorailHT(pt
.x
, pt
.y
);
5561 switch (_thd
.place_mode
& HT_DIR_MASK
) {
5562 case HT_DIR_X
: new_drawstyle
= HT_LINE
| HT_DIR_X
; break;
5563 case HT_DIR_Y
: new_drawstyle
= HT_LINE
| HT_DIR_Y
; break;
5567 new_drawstyle
= (pt
.x
& TILE_UNIT_MASK
) + (pt
.y
& TILE_UNIT_MASK
) <= TILE_SIZE
? HT_LINE
| HT_DIR_HU
: HT_LINE
| HT_DIR_HL
;
5572 new_drawstyle
= (pt
.x
& TILE_UNIT_MASK
) > (pt
.y
& TILE_UNIT_MASK
) ? HT_LINE
| HT_DIR_VL
: HT_LINE
| HT_DIR_VR
;
5575 default: NOT_REACHED();
5577 _thd
.selstart
.x
= x1
& ~TILE_UNIT_MASK
;
5578 _thd
.selstart
.y
= y1
& ~TILE_UNIT_MASK
;
5585 _thd
.new_pos
.x
= x1
& ~TILE_UNIT_MASK
;
5586 _thd
.new_pos
.y
= y1
& ~TILE_UNIT_MASK
;
5590 if (new_drawstyle
& HT_LINE
) CalcNewPolylineOutersize();
5592 /* redraw selection */
5593 if (_thd
.drawstyle
!= new_drawstyle
||
5594 _thd
.pos
.x
!= _thd
.new_pos
.x
|| _thd
.pos
.y
!= _thd
.new_pos
.y
||
5595 _thd
.size
.x
!= _thd
.new_size
.x
|| _thd
.size
.y
!= _thd
.new_size
.y
||
5596 _thd
.offs
.x
!= _thd
.new_offs
.x
|| _thd
.offs
.y
!= _thd
.new_offs
.y
||
5597 _thd
.outersize
.x
!= _thd
.new_outersize
.x
||
5598 _thd
.outersize
.y
!= _thd
.new_outersize
.y
||
5599 _thd
.diagonal
!= new_diagonal
) {
5600 /* Clear the old tile selection? */
5601 if ((_thd
.drawstyle
& HT_DRAG_MASK
) != HT_NONE
) SetSelectionTilesDirty();
5603 _thd
.drawstyle
= new_drawstyle
;
5604 _thd
.pos
= _thd
.new_pos
;
5605 _thd
.size
= _thd
.new_size
;
5606 _thd
.offs
= _thd
.new_offs
;
5607 _thd
.outersize
= _thd
.new_outersize
;
5608 _thd
.diagonal
= new_diagonal
;
5611 /* Draw the new tile selection? */
5612 if ((new_drawstyle
& HT_DRAG_MASK
) != HT_NONE
) SetSelectionTilesDirty();
5617 * Displays the measurement tooltips when selecting multiple tiles
5618 * @param str String to be displayed
5619 * @param paramcount number of params to deal with
5620 * @param params (optional) up to 5 pieces of additional information that may be added to a tooltip
5621 * @param close_cond Condition for closing this tooltip.
5623 static inline void ShowMeasurementTooltips(StringID str
, uint paramcount
, TooltipCloseCondition close_cond
= TCC_EXIT_VIEWPORT
)
5625 if (!_settings_client
.gui
.measure_tooltip
) return;
5626 GuiShowTooltips(_thd
.GetCallbackWnd(), str
, close_cond
, paramcount
);
5629 static void HideMeasurementTooltips()
5631 CloseWindowById(WC_TOOLTIPS
, 0);
5634 /** highlighting tiles while only going over them with the mouse */
5635 void VpStartPlaceSizing(TileIndex tile
, ViewportPlaceMethod method
, ViewportDragDropSelectionProcess process
)
5637 _thd
.select_method
= method
;
5638 _thd
.select_proc
= process
;
5639 _thd
.selend
.x
= TileX(tile
) * TILE_SIZE
;
5640 _thd
.selstart
.x
= TileX(tile
) * TILE_SIZE
;
5641 _thd
.selend
.y
= TileY(tile
) * TILE_SIZE
;
5642 _thd
.selstart
.y
= TileY(tile
) * TILE_SIZE
;
5644 /* Needed so several things (road, autoroad, bridges, ...) are placed correctly.
5645 * In effect, placement starts from the centre of a tile
5647 if (method
== VPM_X_OR_Y
|| method
== VPM_FIX_X
|| method
== VPM_FIX_Y
) {
5648 _thd
.selend
.x
+= TILE_SIZE
/ 2;
5649 _thd
.selend
.y
+= TILE_SIZE
/ 2;
5650 _thd
.selstart
.x
+= TILE_SIZE
/ 2;
5651 _thd
.selstart
.y
+= TILE_SIZE
/ 2;
5654 HighLightStyle others
= _thd
.place_mode
& ~(HT_DRAG_MASK
| HT_DIR_MASK
);
5655 if ((_thd
.place_mode
& HT_DRAG_MASK
) == HT_RECT
) {
5656 _thd
.place_mode
= HT_SPECIAL
| others
;
5657 _thd
.next_drawstyle
= HT_RECT
| others
;
5658 } else if (_thd
.place_mode
& (HT_RAIL
| HT_LINE
)) {
5659 _thd
.place_mode
= HT_SPECIAL
| others
;
5660 _thd
.next_drawstyle
= _thd
.drawstyle
| others
;
5661 _current_snap_lock
.x
= -1;
5662 if ((_thd
.place_mode
& HT_POLY
) != 0 && GetRailSnapMode() == RSM_NO_SNAP
) {
5663 SetRailSnapMode(RSM_SNAP_TO_TILE
);
5664 SetRailSnapTile(tile
);
5667 _thd
.place_mode
= HT_SPECIAL
| others
;
5668 _thd
.next_drawstyle
= HT_POINT
| others
;
5670 _special_mouse_mode
= WSM_SIZING
;
5673 /** Drag over the map while holding the left mouse down. */
5674 void VpStartDragging(ViewportDragDropSelectionProcess process
)
5676 _thd
.select_method
= VPM_X_AND_Y
;
5677 _thd
.select_proc
= process
;
5678 _thd
.selstart
.x
= 0;
5679 _thd
.selstart
.y
= 0;
5680 _thd
.next_drawstyle
= HT_RECT
;
5682 _special_mouse_mode
= WSM_DRAGGING
;
5685 void VpSetPlaceSizingLimit(int limit
)
5687 _thd
.sizelimit
= limit
;
5691 * Highlights all tiles between a set of two tiles. Used in dock and tunnel placement
5692 * @param from TileIndex of the first tile to highlight
5693 * @param to TileIndex of the last tile to highlight
5695 void VpSetPresizeRange(TileIndex from
, TileIndex to
)
5697 uint64_t distance
= DistanceManhattan(from
, to
) + 1;
5699 _thd
.selend
.x
= TileX(to
) * TILE_SIZE
;
5700 _thd
.selend
.y
= TileY(to
) * TILE_SIZE
;
5701 _thd
.selstart
.x
= TileX(from
) * TILE_SIZE
;
5702 _thd
.selstart
.y
= TileY(from
) * TILE_SIZE
;
5703 _thd
.next_drawstyle
= HT_RECT
;
5705 /* show measurement only if there is any length to speak of */
5707 SetDParam(0, distance
);
5708 ShowMeasurementTooltips(STR_MEASURE_LENGTH
, 1);
5710 HideMeasurementTooltips();
5714 static void VpStartPreSizing()
5717 _special_mouse_mode
= WSM_PRESIZE
;
5721 * returns information about the 2x1 piece to be build.
5722 * The lower bits (0-3) are the track type.
5724 static HighLightStyle
Check2x1AutoRail(int mode
)
5726 int fxpy
= _tile_fract_coords
.x
+ _tile_fract_coords
.y
;
5727 int sxpy
= (_thd
.selend
.x
& TILE_UNIT_MASK
) + (_thd
.selend
.y
& TILE_UNIT_MASK
);
5728 int fxmy
= _tile_fract_coords
.x
- _tile_fract_coords
.y
;
5729 int sxmy
= (_thd
.selend
.x
& TILE_UNIT_MASK
) - (_thd
.selend
.y
& TILE_UNIT_MASK
);
5732 default: NOT_REACHED();
5733 case 0: // end piece is lower right
5734 if (fxpy
>= 20 && sxpy
<= 12) return HT_DIR_HL
;
5735 if (fxmy
< -3 && sxmy
> 3) return HT_DIR_VR
;
5739 if (fxmy
> 3 && sxmy
< -3) return HT_DIR_VL
;
5740 if (fxpy
<= 12 && sxpy
>= 20) return HT_DIR_HU
;
5744 if (fxmy
> 3 && sxmy
< -3) return HT_DIR_VL
;
5745 if (fxpy
>= 20 && sxpy
<= 12) return HT_DIR_HL
;
5749 if (fxmy
< -3 && sxmy
> 3) return HT_DIR_VR
;
5750 if (fxpy
<= 12 && sxpy
>= 20) return HT_DIR_HU
;
5756 * Check if the direction of start and end tile should be swapped based on
5757 * the dragging-style. Default directions are:
5758 * in the case of a line (HT_RAIL, HT_LINE): DIR_NE, DIR_NW, DIR_N, DIR_E
5759 * in the case of a rect (HT_RECT, HT_POINT): DIR_S, DIR_E
5760 * For example dragging a rectangle area from south to north should be swapped to
5761 * north-south (DIR_S) to obtain the same results with less code. This is what
5762 * the return value signifies.
5763 * @param style HighLightStyle dragging style
5764 * @param start_tile start tile of drag
5765 * @param end_tile end tile of drag
5766 * @return boolean value which when true means start/end should be swapped
5768 static bool SwapDirection(HighLightStyle style
, TileIndex start_tile
, TileIndex end_tile
)
5770 uint start_x
= TileX(start_tile
);
5771 uint start_y
= TileY(start_tile
);
5772 uint end_x
= TileX(end_tile
);
5773 uint end_y
= TileY(end_tile
);
5775 switch (style
& HT_DRAG_MASK
) {
5777 case HT_LINE
: return (end_x
> start_x
|| (end_x
== start_x
&& end_y
> start_y
));
5780 case HT_POINT
: return (end_x
!= start_x
&& end_y
< start_y
);
5781 default: NOT_REACHED();
5788 * Calculates height difference between one tile and another.
5789 * Multiplies the result to suit the standard given by #TILE_HEIGHT_STEP.
5791 * To correctly get the height difference we need the direction we are dragging
5792 * in, as well as with what kind of tool we are dragging. For example a horizontal
5793 * autorail tool that starts in bottom and ends at the top of a tile will need the
5794 * maximum of SW, S and SE, N corners respectively. This is handled by the lookup table below
5795 * See #_tileoffs_by_dir in map.cpp for the direction enums if you can't figure out the values yourself.
5796 * @param style Highlighting style of the drag. This includes direction and style (autorail, rect, etc.)
5797 * @param distance Number of tiles dragged, important for horizontal/vertical drags, ignored for others.
5798 * @param start_tile Start tile of the drag operation.
5799 * @param end_tile End tile of the drag operation.
5800 * @return Height difference between two tiles. The tile measurement tool utilizes this value in its tooltip.
5802 static int CalcHeightdiff(HighLightStyle style
, uint distance
, TileIndex start_tile
, TileIndex end_tile
)
5804 bool swap
= SwapDirection(style
, start_tile
, end_tile
);
5805 uint h0
, h1
; // Start height and end height.
5807 if (start_tile
== end_tile
) return 0;
5808 if (swap
) Swap(start_tile
, end_tile
);
5810 switch (style
& HT_DRAG_MASK
) {
5812 /* In the case of an area we can determine whether we were dragging south or
5813 * east by checking the X-coordinates of the tiles */
5814 if (TileX(end_tile
) > TileX(start_tile
)) {
5815 /* Dragging south does not need to change the start tile. */
5816 end_tile
= TileAddByDir(end_tile
, DIR_S
);
5818 /* Dragging east. */
5819 start_tile
= TileAddByDir(start_tile
, DIR_SW
);
5820 end_tile
= TileAddByDir(end_tile
, DIR_SE
);
5825 h0
= TileHeight(start_tile
);
5826 h1
= TileHeight(end_tile
);
5828 default: { // All other types, this is mostly only line/autorail
5829 static const HighLightStyle flip_style_direction
[] = {
5830 HT_DIR_X
, HT_DIR_Y
, HT_DIR_HL
, HT_DIR_HU
, HT_DIR_VR
, HT_DIR_VL
5832 static const std::pair
<TileIndexDiffC
, TileIndexDiffC
> start_heightdiff_line_by_dir
[] = {
5833 { {1, 0}, {1, 1} }, // HT_DIR_X
5834 { {0, 1}, {1, 1} }, // HT_DIR_Y
5835 { {1, 0}, {0, 0} }, // HT_DIR_HU
5836 { {1, 0}, {1, 1} }, // HT_DIR_HL
5837 { {1, 0}, {1, 1} }, // HT_DIR_VL
5838 { {0, 1}, {1, 1} }, // HT_DIR_VR
5840 static const std::pair
<TileIndexDiffC
, TileIndexDiffC
> end_heightdiff_line_by_dir
[] = {
5841 { {0, 1}, {0, 0} }, // HT_DIR_X
5842 { {1, 0}, {0, 0} }, // HT_DIR_Y
5843 { {0, 1}, {0, 0} }, // HT_DIR_HU
5844 { {1, 1}, {0, 1} }, // HT_DIR_HL
5845 { {1, 0}, {0, 0} }, // HT_DIR_VL
5846 { {0, 0}, {0, 1} }, // HT_DIR_VR
5848 static_assert(std::size(start_heightdiff_line_by_dir
) == HT_DIR_END
);
5849 static_assert(std::size(end_heightdiff_line_by_dir
) == HT_DIR_END
);
5851 distance
%= 2; // we're only interested if the distance is even or uneven
5852 style
&= HT_DIR_MASK
;
5853 dbg_assert(style
< HT_DIR_END
);
5855 /* To handle autorail, we do some magic to be able to use a lookup table.
5856 * Firstly if we drag the other way around, we switch start&end, and if needed
5857 * also flip the drag-position. Eg if it was on the left, and the distance is even
5858 * that means the end, which is now the start is on the right */
5859 if (swap
&& distance
== 0) style
= flip_style_direction
[style
];
5861 /* Lambda to help calculating the height at one side of the line. */
5862 auto get_height
= [](auto &tile
, auto &heightdiffs
) {
5864 TileHeight(TileAdd(tile
, ToTileIndexDiff(heightdiffs
.first
))),
5865 TileHeight(TileAdd(tile
, ToTileIndexDiff(heightdiffs
.second
))));
5868 /* Use lookup table for start-tile based on HighLightStyle direction */
5869 h0
= get_height(start_tile
, start_heightdiff_line_by_dir
[style
]);
5871 /* Use lookup table for end-tile based on HighLightStyle direction
5872 * flip around side (lower/upper, left/right) based on distance */
5873 if (distance
== 0) style
= flip_style_direction
[style
];
5874 h1
= get_height(end_tile
, end_heightdiff_line_by_dir
[style
]);
5879 if (swap
) Swap(h0
, h1
);
5880 return (int)(h1
- h0
) * TILE_HEIGHT_STEP
;
5883 static void ShowLengthMeasurement(HighLightStyle style
, TileIndex start_tile
, TileIndex end_tile
, TooltipCloseCondition close_cond
= TCC_EXIT_VIEWPORT
, bool show_single_tile_length
= false)
5885 static const StringID measure_strings_length
[] = {STR_NULL
, STR_MEASURE_LENGTH
, STR_MEASURE_LENGTH_HEIGHTDIFF
};
5887 if (_settings_client
.gui
.measure_tooltip
) {
5888 uint distance
= DistanceManhattan(start_tile
, end_tile
) + 1;
5891 if (show_single_tile_length
|| distance
!= 1) {
5892 int heightdiff
= CalcHeightdiff(style
, distance
, start_tile
, end_tile
);
5893 /* If we are showing a tooltip for horizontal or vertical drags,
5894 * 2 tiles have a length of 1. To bias towards the ceiling we add
5895 * one before division. It feels more natural to count 3 lengths as 2 */
5896 if ((style
& HT_DIR_MASK
) != HT_DIR_X
&& (style
& HT_DIR_MASK
) != HT_DIR_Y
) {
5897 distance
= CeilDiv(distance
, 2);
5900 SetDParam(index
++, distance
);
5901 if (heightdiff
!= 0) SetDParam(index
++, heightdiff
);
5904 ShowMeasurementTooltips(measure_strings_length
[index
], index
, close_cond
);
5909 * Check for underflowing the map.
5910 * @param test the variable to test for underflowing
5911 * @param other the other variable to update to keep the line
5912 * @param mult the constant to multiply the difference by for \c other
5914 static void CheckUnderflow(int &test
, int &other
, int mult
)
5916 if (test
>= 0) return;
5918 other
+= mult
* test
;
5923 * Check for overflowing the map.
5924 * @param test the variable to test for overflowing
5925 * @param other the other variable to update to keep the line
5926 * @param max the maximum value for the \c test variable
5927 * @param mult the constant to multiply the difference by for \c other
5929 static void CheckOverflow(int &test
, int &other
, int max
, int mult
)
5931 if (test
<= max
) return;
5933 other
+= mult
* (test
- max
);
5937 [[maybe_unused
]] static const uint X_DIRS
= (1 << DIR_NE
) | (1 << DIR_SW
);
5938 [[maybe_unused
]] static const uint Y_DIRS
= (1 << DIR_SE
) | (1 << DIR_NW
);
5939 static const uint HORZ_DIRS
= (1 << DIR_W
) | (1 << DIR_E
);
5940 //static const uint VERT_DIRS = (1 << DIR_N) | (1 << DIR_S);
5942 Trackdir
PointDirToTrackdir(const Point
&pt
, Direction dir
)
5946 if (IsDiagonalDirection(dir
)) {
5947 ret
= DiagDirToDiagTrackdir(DirToDiagDir(dir
));
5949 int x
= pt
.x
& TILE_UNIT_MASK
;
5950 int y
= pt
.y
& TILE_UNIT_MASK
;
5953 if (HasBit(HORZ_DIRS
, dir
)) {
5954 ret
= TrackDirectionToTrackdir(ns
< (int)TILE_SIZE
? TRACK_UPPER
: TRACK_LOWER
, dir
);
5956 ret
= TrackDirectionToTrackdir(we
< 0 ? TRACK_LEFT
: TRACK_RIGHT
, dir
);
5963 static bool FindPolyline(const Point
&pt
, const LineSnapPoint
&start
, PolylineInfo
*ret
)
5965 /* relative coordinats of the mouse point (offset against the snap point) */
5966 int x
= pt
.x
- start
.x
;
5967 int y
= pt
.y
- start
.y
;
5971 /* in-tile alignment of the snap point (there are two variants: [0, 8] or [8, 0]) */
5972 uint align_x
= start
.x
& TILE_UNIT_MASK
;
5973 uint align_y
= start
.y
& TILE_UNIT_MASK
;
5974 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
)));
5976 /* absolute distance between points (in tiles) */
5977 uint d_x
= abs(RoundDivSU(x
< 0 ? x
- align_y
: x
+ align_y
, TILE_SIZE
));
5978 uint d_y
= abs(RoundDivSU(y
< 0 ? y
- align_x
: y
+ align_x
, TILE_SIZE
));
5979 uint d_ns
= abs(RoundDivSU(ns
, TILE_SIZE
));
5980 uint d_we
= abs(RoundDivSU(we
, TILE_SIZE
));
5982 /* Find on which quadrant is the mouse point (reltively to the snap point).
5983 * Numeration (clockwise like in Direction):
5990 uint ortho_quadrant
= 2 * (x
< 0) + ((x
< 0) != (y
< 0)); // implicit cast: false/true --> 0/1
5991 uint diag_quadrant
= 2 * (ns
< 0) + ((ns
< 0) != (we
< 0));
5993 /* direction from the snap point to the mouse point */
5994 Direction ortho_line_dir
= ChangeDir(DIR_S
, (DirDiff
)(2 * ortho_quadrant
)); // DIR_S is the middle of the ortho quadrant no. 0
5995 Direction diag_line_dir
= ChangeDir(DIR_SE
, (DirDiff
)(2 * diag_quadrant
)); // DIR_SE is the middle of the diag quadrant no. 0
5996 if (!HasBit(start
.dirs
, ortho_line_dir
) && !HasBit(start
.dirs
, diag_line_dir
)) return false;
5998 /* length of booth segments of auto line (choosing orthogonal direction first) */
5999 uint ortho_len
= 0, ortho_len2
= 0;
6000 if (HasBit(start
.dirs
, ortho_line_dir
)) {
6001 bool is_len_even
= (align_x
!= 0) ? d_x
>= d_y
: d_x
<= d_y
;
6002 ortho_len
= 2 * std::min(d_x
, d_y
) - (int)is_len_even
;
6003 assert((int)ortho_len
>= 0);
6004 if (d_ns
== 0 || d_we
== 0) { // just single segment?
6007 ortho_len2
= abs((int)d_x
- (int)d_y
) + (int)is_len_even
;
6011 /* length of booth segments of auto line (choosing diagonal direction first) */
6012 uint diag_len
= 0, diag_len2
= 0;
6013 if (HasBit(start
.dirs
, diag_line_dir
)) {
6014 if (d_x
== 0 || d_y
== 0) { // just single segment?
6015 diag_len
= d_x
+ d_y
;
6017 diag_len
= std::min(d_ns
, d_we
);
6018 diag_len2
= d_x
+ d_y
- diag_len
;
6022 /* choose the best variant */
6023 if (ortho_len
!= 0 && diag_len
!= 0) {
6024 /* in the first place, choose this line whose first segment ends up closer
6025 * to the mouse point (thus the second segment is shorter) */
6026 int cmp
= ortho_len2
- diag_len2
;
6027 /* if equeal, choose the shorter line */
6028 if (cmp
== 0) cmp
= ortho_len
- diag_len
;
6029 /* finally look at small "units" and choose the line which is closer to the mouse point */
6030 if (cmp
== 0) cmp
= std::min(abs(we
), abs(ns
)) - std::min(abs(x
), abs(y
));
6031 /* based on comparison, disable one of variants */
6040 if (ortho_len
!= 0) {
6041 ret
->first_dir
= ortho_line_dir
;
6042 ret
->first_len
= ortho_len
;
6043 ret
->second_dir
= (ortho_len2
!= 0) ? diag_line_dir
: INVALID_DIR
;
6044 ret
->second_len
= ortho_len2
;
6045 } else if (diag_len
!= 0) {
6046 ret
->first_dir
= diag_line_dir
;
6047 ret
->first_len
= diag_len
;
6048 ret
->second_dir
= (diag_len2
!= 0) ? ortho_line_dir
: INVALID_DIR
;
6049 ret
->second_len
= diag_len2
;
6059 * Calculate squared euclidean distance between two points.
6060 * @param a the first point
6061 * @param b the second point
6062 * @return |b - a| ^ 2
6064 static inline uint
SqrDist(const Point
&a
, const Point
&b
)
6066 return (b
.x
- a
.x
) * (b
.x
- a
.x
) + (b
.y
- a
.y
) * (b
.y
- a
.y
);
6069 static LineSnapPoint
*FindBestPolyline(const Point
&pt
, LineSnapPoint
*snap_points
, uint num_points
, PolylineInfo
*ret
)
6071 /* Find the best polyline (a pair of two lines - the white one and the blue
6072 * one) led from any of saved snap points to the mouse cursor. */
6074 LineSnapPoint
*best_snap_point
= nullptr; // the best polyline we found so far is led from this snap point
6076 for (int i
= 0; i
< (int)num_points
; i
++) {
6077 /* try to fit a polyline */
6078 PolylineInfo polyline
;
6079 if (!FindPolyline(pt
, snap_points
[i
], &polyline
)) continue; // skip non-matching snap points
6080 /* check whether we've found a better polyline */
6081 if (best_snap_point
!= nullptr) {
6082 /* firstly choose shorter polyline (the one with smaller amount of
6083 * track pieces composing booth the white and the blue line) */
6084 uint cur_len
= polyline
.first_len
+ polyline
.second_len
;
6085 uint best_len
= ret
->first_len
+ ret
->second_len
;
6086 if (cur_len
> best_len
) continue;
6087 /* secondly choose that polyline which has longer first (white) line */
6088 if (cur_len
== best_len
&& polyline
.first_len
< ret
->first_len
) continue;
6089 /* finally check euclidean distance to snap points and choose the
6090 * one which is closer */
6091 if (cur_len
== best_len
&& polyline
.first_len
== ret
->first_len
&& SqrDist(pt
, snap_points
[i
]) >= SqrDist(pt
, *best_snap_point
)) continue;
6093 /* save the found polyline */
6095 best_snap_point
= &snap_points
[i
];
6098 return best_snap_point
;
6101 /** while dragging */
6102 static void CalcRaildirsDrawstyle(int x
, int y
, int method
)
6106 int dx
= _thd
.selstart
.x
- (_thd
.selend
.x
& ~TILE_UNIT_MASK
);
6107 int dy
= _thd
.selstart
.y
- (_thd
.selend
.y
& ~TILE_UNIT_MASK
);
6108 uint w
= abs(dx
) + TILE_SIZE
;
6109 uint h
= abs(dy
) + TILE_SIZE
;
6111 if (method
& ~(VPM_RAILDIRS
| VPM_SIGNALDIRS
)) {
6112 /* We 'force' a selection direction; first four rail buttons. */
6113 method
&= ~(VPM_RAILDIRS
| VPM_SIGNALDIRS
);
6114 int raw_dx
= _thd
.selstart
.x
- _thd
.selend
.x
;
6115 int raw_dy
= _thd
.selstart
.y
- _thd
.selend
.y
;
6118 b
= HT_LINE
| HT_DIR_Y
;
6119 x
= _thd
.selstart
.x
;
6123 b
= HT_LINE
| HT_DIR_X
;
6124 y
= _thd
.selstart
.y
;
6127 case VPM_FIX_HORIZONTAL
:
6129 /* We are on a straight horizontal line. Determine the 'rail'
6130 * to build based the sub tile location. */
6131 b
= (x
& TILE_UNIT_MASK
) + (y
& TILE_UNIT_MASK
) >= TILE_SIZE
? HT_LINE
| HT_DIR_HL
: HT_LINE
| HT_DIR_HU
;
6133 /* We are not on a straight line. Determine the rail to build
6134 * based on whether we are above or below it. */
6135 b
= dx
+ dy
>= (int)TILE_SIZE
? HT_LINE
| HT_DIR_HU
: HT_LINE
| HT_DIR_HL
;
6137 /* Calculate where a horizontal line through the start point and
6138 * a vertical line from the selected end point intersect and
6139 * use that point as the end point. */
6140 int offset
= (raw_dx
- raw_dy
) / 2;
6141 x
= _thd
.selstart
.x
- (offset
& ~TILE_UNIT_MASK
);
6142 y
= _thd
.selstart
.y
+ (offset
& ~TILE_UNIT_MASK
);
6144 /* 'Build' the last half rail tile if needed */
6145 if ((offset
& TILE_UNIT_MASK
) > (TILE_SIZE
/ 2)) {
6146 if (dx
+ dy
>= (int)TILE_SIZE
) {
6147 x
+= (dx
+ dy
< 0) ? (int)TILE_SIZE
: -(int)TILE_SIZE
;
6149 y
+= (dx
+ dy
< 0) ? (int)TILE_SIZE
: -(int)TILE_SIZE
;
6153 /* Make sure we do not overflow the map! */
6154 CheckUnderflow(x
, y
, 1);
6155 CheckUnderflow(y
, x
, 1);
6156 CheckOverflow(x
, y
, (MapMaxX() - 1) * TILE_SIZE
, 1);
6157 CheckOverflow(y
, x
, (MapMaxY() - 1) * TILE_SIZE
, 1);
6158 assert(x
>= 0 && y
>= 0 && x
<= (int)(MapMaxX() * TILE_SIZE
) && y
<= (int)(MapMaxY() * TILE_SIZE
));
6162 case VPM_FIX_VERTICAL
:
6164 /* We are on a straight vertical line. Determine the 'rail'
6165 * to build based the sub tile location. */
6166 b
= (x
& TILE_UNIT_MASK
) > (y
& TILE_UNIT_MASK
) ? HT_LINE
| HT_DIR_VL
: HT_LINE
| HT_DIR_VR
;
6168 /* We are not on a straight line. Determine the rail to build
6169 * based on whether we are left or right from it. */
6170 b
= dx
< dy
? HT_LINE
| HT_DIR_VL
: HT_LINE
| HT_DIR_VR
;
6172 /* Calculate where a vertical line through the start point and
6173 * a horizontal line from the selected end point intersect and
6174 * use that point as the end point. */
6175 int offset
= (raw_dx
+ raw_dy
+ (int)TILE_SIZE
) / 2;
6176 x
= _thd
.selstart
.x
- (offset
& ~TILE_UNIT_MASK
);
6177 y
= _thd
.selstart
.y
- (offset
& ~TILE_UNIT_MASK
);
6179 /* 'Build' the last half rail tile if needed */
6180 if ((offset
& TILE_UNIT_MASK
) > (TILE_SIZE
/ 2)) {
6182 y
+= (dx
> dy
) ? (int)TILE_SIZE
: -(int)TILE_SIZE
;
6184 x
+= (dx
< dy
) ? (int)TILE_SIZE
: -(int)TILE_SIZE
;
6188 /* Make sure we do not overflow the map! */
6189 CheckUnderflow(x
, y
, -1);
6190 CheckUnderflow(y
, x
, -1);
6191 CheckOverflow(x
, y
, (MapMaxX() - 1) * TILE_SIZE
, -1);
6192 CheckOverflow(y
, x
, (MapMaxY() - 1) * TILE_SIZE
, -1);
6193 assert(x
>= 0 && y
>= 0 && x
<= (int)(MapMaxX() * TILE_SIZE
) && y
<= (int)(MapMaxY() * TILE_SIZE
));
6200 } else if (TileVirtXY(_thd
.selstart
.x
, _thd
.selstart
.y
) == TileVirtXY(x
, y
)) { // check if we're only within one tile
6201 if (method
& VPM_RAILDIRS
) {
6202 b
= GetAutorailHT(x
, y
);
6203 } else { // rect for autosignals on one tile
6206 } else if (h
== TILE_SIZE
) { // Is this in X direction?
6207 if (dx
== (int)TILE_SIZE
) { // 2x1 special handling
6208 b
= (Check2x1AutoRail(3)) | HT_LINE
;
6209 } else if (dx
== -(int)TILE_SIZE
) {
6210 b
= (Check2x1AutoRail(2)) | HT_LINE
;
6212 b
= HT_LINE
| HT_DIR_X
;
6214 y
= _thd
.selstart
.y
;
6215 } else if (w
== TILE_SIZE
) { // Or Y direction?
6216 if (dy
== (int)TILE_SIZE
) { // 2x1 special handling
6217 b
= (Check2x1AutoRail(1)) | HT_LINE
;
6218 } else if (dy
== -(int)TILE_SIZE
) { // 2x1 other direction
6219 b
= (Check2x1AutoRail(0)) | HT_LINE
;
6221 b
= HT_LINE
| HT_DIR_Y
;
6223 x
= _thd
.selstart
.x
;
6224 } else if (w
> h
* 2) { // still count as x dir?
6225 b
= HT_LINE
| HT_DIR_X
;
6226 y
= _thd
.selstart
.y
;
6227 } else if (h
> w
* 2) { // still count as y dir?
6228 b
= HT_LINE
| HT_DIR_Y
;
6229 x
= _thd
.selstart
.x
;
6230 } else { // complicated direction
6232 _thd
.selend
.x
= _thd
.selend
.x
& ~TILE_UNIT_MASK
;
6233 _thd
.selend
.y
= _thd
.selend
.y
& ~TILE_UNIT_MASK
;
6236 if (x
> _thd
.selstart
.x
) {
6237 if (y
> _thd
.selstart
.y
) {
6240 b
= (x
& TILE_UNIT_MASK
) > (y
& TILE_UNIT_MASK
) ? HT_LINE
| HT_DIR_VL
: HT_LINE
| HT_DIR_VR
;
6241 } else if (d
>= 0) {
6242 x
= _thd
.selstart
.x
+ h
;
6243 b
= HT_LINE
| HT_DIR_VL
;
6245 y
= _thd
.selstart
.y
+ w
;
6246 b
= HT_LINE
| HT_DIR_VR
;
6251 b
= (x
& TILE_UNIT_MASK
) + (y
& TILE_UNIT_MASK
) >= TILE_SIZE
? HT_LINE
| HT_DIR_HL
: HT_LINE
| HT_DIR_HU
;
6252 } else if (d
>= 0) {
6253 x
= _thd
.selstart
.x
+ h
;
6254 b
= HT_LINE
| HT_DIR_HL
;
6256 y
= _thd
.selstart
.y
- w
;
6257 b
= HT_LINE
| HT_DIR_HU
;
6261 if (y
> _thd
.selstart
.y
) {
6264 b
= (x
& TILE_UNIT_MASK
) + (y
& TILE_UNIT_MASK
) >= TILE_SIZE
? HT_LINE
| HT_DIR_HL
: HT_LINE
| HT_DIR_HU
;
6265 } else if (d
>= 0) {
6266 x
= _thd
.selstart
.x
- h
;
6267 b
= HT_LINE
| HT_DIR_HU
;
6269 y
= _thd
.selstart
.y
+ w
;
6270 b
= HT_LINE
| HT_DIR_HL
;
6275 b
= (x
& TILE_UNIT_MASK
) > (y
& TILE_UNIT_MASK
) ? HT_LINE
| HT_DIR_VL
: HT_LINE
| HT_DIR_VR
;
6276 } else if (d
>= 0) {
6277 x
= _thd
.selstart
.x
- h
;
6278 b
= HT_LINE
| HT_DIR_VR
;
6280 y
= _thd
.selstart
.y
- w
;
6281 b
= HT_LINE
| HT_DIR_VL
;
6289 _thd
.dir2
= HT_DIR_END
;
6290 _thd
.next_drawstyle
= b
;
6292 ShowLengthMeasurement(b
, TileVirtXY(_thd
.selstart
.x
, _thd
.selstart
.y
), TileVirtXY(_thd
.selend
.x
, _thd
.selend
.y
));
6295 static HighLightStyle
CalcPolyrailDrawstyle(Point pt
, bool dragging
)
6297 RailSnapMode snap_mode
= GetRailSnapMode();
6299 /* are we only within one tile? */
6300 if (snap_mode
== RSM_SNAP_TO_TILE
&& GetRailSnapTile() == TileVirtXY(pt
.x
, pt
.y
)) {
6301 _thd
.selend
.x
= pt
.x
;
6302 _thd
.selend
.y
= pt
.y
;
6303 HideMeasurementTooltips();
6304 return GetAutorailHT(pt
.x
, pt
.y
);
6307 /* find the best track */
6310 bool lock_snapping
= dragging
&& snap_mode
== RSM_SNAP_TO_RAIL
;
6311 if (!lock_snapping
) _current_snap_lock
.x
= -1;
6313 const LineSnapPoint
*snap_point
;
6314 if (_current_snap_lock
.x
!= -1) {
6315 snap_point
= FindBestPolyline(pt
, &_current_snap_lock
, 1, &line
);
6316 } else if (snap_mode
== RSM_SNAP_TO_TILE
) {
6317 snap_point
= FindBestPolyline(pt
, _tile_snap_points
.data(), (uint
)_tile_snap_points
.size(), &line
);
6319 assert(snap_mode
== RSM_SNAP_TO_RAIL
);
6320 snap_point
= FindBestPolyline(pt
, _rail_snap_points
.data(), (uint
)_rail_snap_points
.size(), &line
);
6323 if (snap_point
== nullptr) {
6324 HideMeasurementTooltips();
6325 return HT_NONE
; // no match
6328 if (lock_snapping
&& _current_snap_lock
.x
== -1) {
6329 /* lock down the snap point */
6330 _current_snap_lock
= *snap_point
;
6331 _current_snap_lock
.dirs
&= (1 << line
.first_dir
) | (1 << ReverseDir(line
.first_dir
));
6334 TileIndexDiffC first_dir
= TileIndexDiffCByDir(line
.first_dir
);
6335 _thd
.selstart
.x
= line
.start
.x
;
6336 _thd
.selstart
.y
= line
.start
.y
;
6337 _thd
.selend
.x
= _thd
.selstart
.x
+ line
.first_len
* first_dir
.x
* (IsDiagonalDirection(line
.first_dir
) ? TILE_SIZE
: TILE_SIZE
/ 2);
6338 _thd
.selend
.y
= _thd
.selstart
.y
+ line
.first_len
* first_dir
.y
* (IsDiagonalDirection(line
.first_dir
) ? TILE_SIZE
: TILE_SIZE
/ 2);
6339 _thd
.selstart2
.x
= _thd
.selend
.x
;
6340 _thd
.selstart2
.y
= _thd
.selend
.y
;
6341 _thd
.selstart
.x
+= first_dir
.x
;
6342 _thd
.selstart
.y
+= first_dir
.y
;
6343 _thd
.selend
.x
-= first_dir
.x
;
6344 _thd
.selend
.y
-= first_dir
.y
;
6345 Trackdir seldir
= PointDirToTrackdir(_thd
.selstart
, line
.first_dir
);
6346 _thd
.selstart
.x
&= ~TILE_UNIT_MASK
;
6347 _thd
.selstart
.y
&= ~TILE_UNIT_MASK
;
6349 if (line
.second_len
!= 0) {
6350 TileIndexDiffC second_dir
= TileIndexDiffCByDir(line
.second_dir
);
6351 _thd
.selend2
.x
= _thd
.selstart2
.x
+ line
.second_len
* second_dir
.x
* (IsDiagonalDirection(line
.second_dir
) ? TILE_SIZE
: TILE_SIZE
/ 2);
6352 _thd
.selend2
.y
= _thd
.selstart2
.y
+ line
.second_len
* second_dir
.y
* (IsDiagonalDirection(line
.second_dir
) ? TILE_SIZE
: TILE_SIZE
/ 2);
6353 _thd
.selstart2
.x
+= second_dir
.x
;
6354 _thd
.selstart2
.y
+= second_dir
.y
;
6355 _thd
.selend2
.x
-= second_dir
.x
;
6356 _thd
.selend2
.y
-= second_dir
.y
;
6357 Trackdir seldir2
= PointDirToTrackdir(_thd
.selstart2
, line
.second_dir
);
6358 _thd
.selstart2
.x
&= ~TILE_UNIT_MASK
;
6359 _thd
.selstart2
.y
&= ~TILE_UNIT_MASK
;
6360 _thd
.dir2
= (HighLightStyle
)TrackdirToTrack(seldir2
);
6362 _thd
.dir2
= HT_DIR_END
;
6365 HighLightStyle ret
= HT_LINE
| (HighLightStyle
)TrackdirToTrack(seldir
);
6366 ShowLengthMeasurement(ret
, TileVirtXY(_thd
.selstart
.x
, _thd
.selstart
.y
), TileVirtXY(_thd
.selend
.x
, _thd
.selend
.y
), TCC_EXIT_VIEWPORT
, true);
6371 * Selects tiles while dragging
6372 * @param x X coordinate of end of selection
6373 * @param y Y coordinate of end of selection
6374 * @param method modifies the way tiles are selected. Possible
6375 * methods are VPM_* in viewport.h
6377 void VpSelectTilesWithMethod(int x
, int y
, ViewportPlaceMethod method
)
6380 HighLightStyle style
;
6387 if ((_thd
.place_mode
& HT_POLY
) && GetRailSnapMode() != RSM_NO_SNAP
) {
6388 Point pt
= { x
, y
};
6389 _thd
.next_drawstyle
= CalcPolyrailDrawstyle(pt
, true);
6393 /* Special handling of drag in any (8-way) direction */
6394 if (method
& (VPM_RAILDIRS
| VPM_SIGNALDIRS
)) {
6397 CalcRaildirsDrawstyle(x
, y
, method
);
6401 /* Needed so level-land is placed correctly */
6402 if ((_thd
.next_drawstyle
& HT_DRAG_MASK
) == HT_POINT
) {
6407 sx
= _thd
.selstart
.x
;
6408 sy
= _thd
.selstart
.y
;
6413 case VPM_X_OR_Y
: // drag in X or Y direction
6414 if (abs(sy
- y
) < abs(sx
- x
)) {
6421 goto calc_heightdiff_single_direction
;
6423 case VPM_X_LIMITED
: // Drag in X direction (limited size).
6424 limit
= (_thd
.sizelimit
- 1) * TILE_SIZE
;
6427 case VPM_FIX_X
: // drag in Y direction
6430 goto calc_heightdiff_single_direction
;
6432 case VPM_Y_LIMITED
: // Drag in Y direction (limited size).
6433 limit
= (_thd
.sizelimit
- 1) * TILE_SIZE
;
6436 case VPM_FIX_Y
: // drag in X direction
6440 calc_heightdiff_single_direction
:;
6442 x
= sx
+ Clamp(x
- sx
, -limit
, limit
);
6443 y
= sy
+ Clamp(y
- sy
, -limit
, limit
);
6445 /* With current code passing a HT_LINE style to calculate the height
6446 * difference is enough. However if/when a point-tool is created
6447 * with this method, function should be called with new_style (below)
6448 * instead of HT_LINE | style case HT_POINT is handled specially
6449 * new_style := (_thd.next_drawstyle & HT_RECT) ? HT_LINE | style : _thd.next_drawstyle; */
6450 ShowLengthMeasurement(HT_LINE
| style
, TileVirtXY(sx
, sy
), TileVirtXY(x
, y
));
6453 case VPM_A_B_LINE
: { // drag an A to B line
6454 TileIndex t0
= TileVirtXY(sx
, sy
);
6455 TileIndex t1
= TileVirtXY(x
, y
);
6456 uint dx
= Delta(TileX(t0
), TileX(t1
)) + 1;
6457 uint dy
= Delta(TileY(t0
), TileY(t1
)) + 1;
6459 /* If dragging an area (eg dynamite tool) and it is actually a single
6460 * row/column, change the type to 'line' to get proper calculation for height */
6461 style
= (HighLightStyle
)_thd
.next_drawstyle
;
6462 if (style
& HT_RECT
) {
6464 style
= HT_LINE
| HT_DIR_Y
;
6465 } else if (dy
== 1) {
6466 style
= HT_LINE
| HT_DIR_X
;
6472 if (dx
!= 1 || dy
!= 1) {
6473 heightdiff
= CalcHeightdiff(style
, 0, t0
, t1
);
6474 SetDParam(0, DistanceManhattan(t0
, t1
));
6475 SetDParam(1, IntSqrt64(DistanceSquare64(t0
, t1
))); // Avoid overflow in DistanceSquare
6481 SetDParam(2, DistanceFromEdge(t1
));
6482 SetDParam(3, GetTileMaxZ(t1
) * TILE_HEIGHT_STEP
);
6483 SetDParam(4, heightdiff
);
6484 /* Always show the measurement tooltip */
6485 GuiShowTooltips(_thd
.GetCallbackWnd(), STR_MEASURE_DIST_HEIGHTDIFF
, TCC_EXIT_VIEWPORT
, 5);
6489 case VPM_X_AND_Y_LIMITED
: // Drag an X by Y constrained rect area.
6490 limit
= (_thd
.sizelimit
- 1) * TILE_SIZE
;
6491 x
= sx
+ Clamp(x
- sx
, -limit
, limit
);
6492 y
= sy
+ Clamp(y
- sy
, -limit
, limit
);
6495 case VPM_X_AND_Y
: // drag an X by Y area
6496 if (_settings_client
.gui
.measure_tooltip
) {
6497 static const StringID measure_strings_area
[] = {
6498 STR_NULL
, STR_NULL
, STR_MEASURE_AREA
, STR_MEASURE_AREA_HEIGHTDIFF
6501 TileIndex t0
= TileVirtXY(sx
, sy
);
6502 TileIndex t1
= TileVirtXY(x
, y
);
6503 uint dx
= Delta(TileX(t0
), TileX(t1
)) + 1;
6504 uint dy
= Delta(TileY(t0
), TileY(t1
)) + 1;
6507 /* If dragging an area (eg dynamite tool) and it is actually a single
6508 * row/column, change the type to 'line' to get proper calculation for height */
6509 style
= (HighLightStyle
)_thd
.next_drawstyle
;
6510 if (_thd
.IsDraggingDiagonal()) {
6511 /* Determine the "area" of the diagonal dragged selection.
6512 * We assume the area is the number of tiles along the X
6513 * edge and the number of tiles along the Y edge. However,
6514 * multiplying these two numbers does not give the exact
6515 * number of tiles; basically we are counting the black
6516 * squares on a chess board and ignore the white ones to
6517 * make the tile counts at the edges match up. There is no
6518 * other way to make a proper count though.
6520 * First convert to the rotated coordinate system. */
6521 int dist_x
= TileX(t0
) - TileX(t1
);
6522 int dist_y
= TileY(t0
) - TileY(t1
);
6523 int a_max
= dist_x
+ dist_y
;
6524 int b_max
= dist_y
- dist_x
;
6526 /* Now determine the size along the edge, but due to the
6527 * chess board principle this counts double. */
6528 a_max
= abs(a_max
+ (a_max
> 0 ? 2 : -2)) / 2;
6529 b_max
= abs(b_max
+ (b_max
> 0 ? 2 : -2)) / 2;
6531 /* We get a 1x1 on normal 2x1 rectangles, due to it being
6532 * a seen as two sides. As the result for actual building
6533 * will be the same as non-diagonal dragging revert to that
6534 * behaviour to give it a more normally looking size. */
6535 if (a_max
!= 1 || b_max
!= 1) {
6539 } else if (style
& HT_RECT
) {
6541 style
= HT_LINE
| HT_DIR_Y
;
6542 } else if (dy
== 1) {
6543 style
= HT_LINE
| HT_DIR_X
;
6547 if (dx
!= 1 || dy
!= 1) {
6548 int heightdiff
= CalcHeightdiff(style
, 0, t0
, t1
);
6550 SetDParam(index
++, dx
- (style
& HT_POINT
? 1 : 0));
6551 SetDParam(index
++, dy
- (style
& HT_POINT
? 1 : 0));
6552 if (heightdiff
!= 0) SetDParam(index
++, heightdiff
);
6555 ShowMeasurementTooltips(measure_strings_area
[index
], index
);
6559 default: NOT_REACHED();
6564 _thd
.dir2
= HT_DIR_END
;
6568 * Handle the mouse while dragging for placement/resizing.
6569 * @return State of handling the event.
6571 EventState
VpHandlePlaceSizingDrag()
6573 if (_special_mouse_mode
!= WSM_SIZING
&& _special_mouse_mode
!= WSM_DRAGGING
) return ES_NOT_HANDLED
;
6575 /* stop drag mode if the window has been closed */
6576 Window
*w
= _thd
.GetCallbackWnd();
6578 ResetObjectToPlace();
6582 if (_left_button_down
&& _special_mouse_mode
== WSM_DRAGGING
) {
6583 /* Only register a drag event when the mouse moved. */
6584 if (_thd
.new_pos
.x
== _thd
.selstart
.x
&& _thd
.new_pos
.y
== _thd
.selstart
.y
) return ES_HANDLED
;
6585 _thd
.selstart
.x
= _thd
.new_pos
.x
;
6586 _thd
.selstart
.y
= _thd
.new_pos
.y
;
6589 /* While dragging execute the drag procedure of the corresponding window (mostly VpSelectTilesWithMethod() ).
6590 * Do it even if the button is no longer pressed to make sure that OnPlaceDrag was called at least once. */
6591 w
->OnPlaceDrag(_thd
.select_method
, _thd
.select_proc
, GetTileBelowCursor());
6592 if (_left_button_down
) return ES_HANDLED
;
6594 /* Mouse button released. */
6595 _special_mouse_mode
= WSM_NONE
;
6596 if (_special_mouse_mode
== WSM_DRAGGING
) return ES_HANDLED
;
6598 /* Keep the selected tool, but reset it to the original mode. */
6599 HighLightStyle others
= _thd
.place_mode
& ~(HT_DRAG_MASK
| HT_DIR_MASK
);
6600 if ((_thd
.next_drawstyle
& HT_DRAG_MASK
) == HT_RECT
) {
6601 _thd
.place_mode
= HT_RECT
| others
;
6602 } else if (_thd
.select_method
& VPM_SIGNALDIRS
) {
6603 _thd
.place_mode
= HT_RECT
| others
;
6604 } else if (_thd
.select_method
& VPM_RAILDIRS
) {
6605 _thd
.place_mode
= (_thd
.select_method
& ~VPM_RAILDIRS
? _thd
.next_drawstyle
: HT_RAIL
) | others
;
6607 _thd
.place_mode
= HT_POINT
| others
;
6609 SetTileSelectSize(1, 1);
6611 if (_thd
.place_mode
& HT_POLY
) {
6612 if (GetRailSnapMode() == RSM_SNAP_TO_TILE
) SetRailSnapMode(RSM_NO_SNAP
);
6613 if (_thd
.drawstyle
== HT_NONE
) return ES_HANDLED
;
6615 HideMeasurementTooltips();
6617 w
->OnPlaceMouseUp(_thd
.select_method
, _thd
.select_proc
, _thd
.selend
, TileVirtXY(_thd
.selstart
.x
, _thd
.selstart
.y
), TileVirtXY(_thd
.selend
.x
, _thd
.selend
.y
));
6622 * Change the cursor and mouse click/drag handling to a mode for performing special operations like tile area selection, object placement, etc.
6623 * @param icon New shape of the mouse cursor.
6624 * @param pal Palette to use.
6625 * @param mode Mode to perform.
6626 * @param w %Window requesting the mode change.
6628 void SetObjectToPlaceWnd(CursorID icon
, PaletteID pal
, HighLightStyle mode
, Window
*w
)
6630 SetObjectToPlace(icon
, pal
, mode
, w
->window_class
, w
->window_number
, w
->GetWindowToken());
6633 #include "table/animcursors.h"
6636 * Change the cursor and mouse click/drag handling to a mode for performing special operations like tile area selection, object placement, etc.
6637 * @param icon New shape of the mouse cursor.
6638 * @param pal Palette to use.
6639 * @param mode Mode to perform.
6640 * @param window_class %Window class of the window requesting the mode change.
6641 * @param window_num Number of the window in its class requesting the mode change.
6642 * @param window_token Window token of the window in its class requesting the mode change, if non-zero.
6644 void SetObjectToPlace(CursorID icon
, PaletteID pal
, HighLightStyle mode
, WindowClass window_class
, WindowNumber window_num
, WindowToken window_token
)
6646 if (_thd
.window_class
!= WC_INVALID
) {
6647 /* Undo clicking on button and drag & drop */
6648 Window
*w
= _thd
.GetCallbackWnd();
6649 /* Call the abort function, but set the window class to something
6650 * that will never be used to avoid infinite loops. Setting it to
6651 * the 'next' window class must not be done because recursion into
6652 * this function might in some cases reset the newly set object to
6653 * place or not properly reset the original selection. */
6654 _thd
.window_class
= WC_INVALID
;
6655 _thd
.window_token
= WindowToken(0);
6657 w
->OnPlaceObjectAbort();
6658 HideMeasurementTooltips();
6662 /* Mark the old selection dirty, in case the selection shape or colour changes */
6663 if ((_thd
.drawstyle
& HT_DRAG_MASK
) != HT_NONE
) SetSelectionTilesDirty();
6665 SetTileSelectSize(1, 1);
6667 _thd
.square_palette
= PAL_NONE
;
6669 if (mode
== HT_DRAG
) { // HT_DRAG is for dragdropping trains in the depot window
6671 _special_mouse_mode
= WSM_DRAGDROP
;
6673 _special_mouse_mode
= WSM_NONE
;
6676 _thd
.place_mode
= mode
;
6677 _thd
.window_class
= window_class
;
6678 _thd
.window_number
= window_num
;
6679 _thd
.window_token
= window_token
;
6681 if ((mode
& HT_DRAG_MASK
) == HT_SPECIAL
) { // special tools, like tunnels or docks start with presizing mode
6685 if (mode
& HT_POLY
) {
6686 SetRailSnapMode((mode
& HT_NEW_POLY
) == HT_NEW_POLY
? RSM_NO_SNAP
: RSM_SNAP_TO_RAIL
);
6689 if ((icon
& ANIMCURSOR_FLAG
) != 0) {
6690 SetAnimatedMouseCursor(_animcursors
[icon
& ~ANIMCURSOR_FLAG
]);
6692 SetMouseCursor(icon
, pal
);
6697 /** Reset the cursor and mouse mode handling back to default (normal cursor, only clicking in windows). */
6698 void ResetObjectToPlace()
6700 SetObjectToPlace(SPR_CURSOR_MOUSE
, PAL_NONE
, HT_NONE
, WC_MAIN_WINDOW
, 0);
6703 void ChangeRenderMode(Viewport
*vp
, bool down
) {
6704 ViewportMapType map_type
= vp
->map_type
;
6705 if (vp
->zoom
< ZOOM_LVL_DRAW_MAP
) return;
6706 ClearViewportLandPixelCache(vp
);
6708 vp
->map_type
= (map_type
== VPMT_MIN
) ? VPMT_MAX
: (ViewportMapType
) (map_type
- 1);
6710 vp
->map_type
= (map_type
== VPMT_MAX
) ? VPMT_MIN
: (ViewportMapType
) (map_type
+ 1);
6714 Point
GetViewportStationMiddle(const Viewport
*vp
, const Station
*st
)
6716 int x
= TileX(st
->xy
) * TILE_SIZE
;
6717 int y
= TileY(st
->xy
) * TILE_SIZE
;
6719 /* Be faster/less precise in viewport map mode, sub-pixel precision is not needed.
6720 * Don't rebase point into screen coordinates in viewport map mode.
6722 if (vp
->zoom
< ZOOM_LVL_DRAW_MAP
) {
6723 int z
= GetSlopePixelZ(Clamp(x
, 0, MapSizeX() * TILE_SIZE
- 1), Clamp(y
, 0, MapSizeY() * TILE_SIZE
- 1));
6724 Point p
= RemapCoords(x
, y
, z
);
6725 p
.x
= UnScaleByZoom(p
.x
- vp
->virtual_left
, vp
->zoom
) + vp
->left
;
6726 p
.y
= UnScaleByZoom(p
.y
- vp
->virtual_top
, vp
->zoom
) + vp
->top
;
6729 int z
= st
->xy
< MapSize() ? TILE_HEIGHT
* TileHeight(st
->xy
) : 0;
6730 Point p
= RemapCoords(x
, y
, z
);
6731 p
.x
= UnScaleByZoomLower(p
.x
, vp
->zoom
);
6732 p
.y
= UnScaleByZoomLower(p
.y
, vp
->zoom
);
6737 /** Helper class for getting the best sprite sorter. */
6738 struct ViewportSSCSS
{
6739 VpSorterChecker fct_checker
; ///< The check function.
6740 VpSpriteSorter fct_sorter
; ///< The sorting function.
6743 /** List of sorters ordered from best to worst. */
6744 static ViewportSSCSS _vp_sprite_sorters
[] = {
6746 { &ViewportSortParentSpritesSSE41Checker
, &ViewportSortParentSpritesSSE41
},
6748 { &ViewportSortParentSpritesChecker
, &ViewportSortParentSprites
}
6751 /** Choose the "best" sprite sorter and set _vp_sprite_sorter. */
6752 void InitializeSpriteSorter()
6754 for (const auto &sprite_sorter
: _vp_sprite_sorters
) {
6755 if (sprite_sorter
.fct_checker()) {
6756 _vp_sprite_sorter
= sprite_sorter
.fct_sorter
;
6760 dbg_assert(_vp_sprite_sorter
!= nullptr);
6764 * Scroll players main viewport.
6765 * @param tile tile to center viewport on
6766 * @param flags type of operation
6767 * @param p1 ViewportScrollTarget of scroll target
6768 * @param p2 company or client id depending on the target
6769 * @param text unused
6770 * @return the cost of this operation or an error
6772 CommandCost
CmdScrollViewport(TileIndex tile
, DoCommandFlag flags
, uint32_t p1
, uint32_t p2
, const char *text
)
6774 if (_current_company
!= OWNER_DEITY
) return CMD_ERROR
;
6775 ViewportScrollTarget target
= (ViewportScrollTarget
)p1
;
6780 if (_local_company
!= (CompanyID
)p2
) return CommandCost();
6783 if (_network_own_client_id
!= (ClientID
)p2
) return CommandCost();
6789 if (flags
& DC_EXEC
) {
6790 ResetObjectToPlace();
6791 ScrollMainWindowToTile(tile
);
6793 return CommandCost();
6796 static LineSnapPoint
LineSnapPointAtRailTrackEndpoint(TileIndex tile
, DiagDirection exit_dir
, bool bidirectional
)
6799 ret
.x
= (TILE_SIZE
/ 2) * (uint
)(2 * TileX(tile
) + TileIndexDiffCByDiagDir(exit_dir
).x
+ 1);
6800 ret
.y
= (TILE_SIZE
/ 2) * (uint
)(2 * TileY(tile
) + TileIndexDiffCByDiagDir(exit_dir
).y
+ 1);
6803 SetBit(ret
.dirs
, DiagDirToDir(exit_dir
));
6804 SetBit(ret
.dirs
, ChangeDir(DiagDirToDir(exit_dir
), DIRDIFF_45LEFT
));
6805 SetBit(ret
.dirs
, ChangeDir(DiagDirToDir(exit_dir
), DIRDIFF_45RIGHT
));
6806 if (bidirectional
) ret
.dirs
|= std::rotr
<uint8_t>(ret
.dirs
, DIRDIFF_REVERSE
);
6812 * Store the position of lastly built rail track; for highlighting purposes.
6814 * In "polyline" highlighting mode, the stored end point will be used as a snapping point for new
6815 * tracks allowing to place multi-segment polylines.
6817 * @param start_tile tile where the track starts
6818 * @param end_tile tile where the track ends
6819 * @param start_track track piece on the start_tile
6820 * @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)
6822 void StoreRailPlacementEndpoints(TileIndex start_tile
, TileIndex end_tile
, Track start_track
, bool bidirectional_exit
)
6824 if (start_tile
!= INVALID_TILE
&& end_tile
!= INVALID_TILE
) {
6825 /* calculate trackdirs at booth ends of the track */
6826 Trackdir exit_trackdir_at_start
= TrackToTrackdir(start_track
);
6827 Trackdir exit_trackdir_at_end
= ReverseTrackdir(TrackToTrackdir(start_track
));
6828 if (start_tile
!= end_tile
) { // multi-tile case
6829 /* determine proper direction (pointing outside of the track) */
6830 uint distance
= DistanceManhattan(start_tile
, end_tile
);
6831 if (distance
> DistanceManhattan(TileAddByDiagDir(start_tile
, TrackdirToExitdir(exit_trackdir_at_start
)), end_tile
)) {
6832 Swap(exit_trackdir_at_start
, exit_trackdir_at_end
);
6834 /* determine proper track on the end tile - switch between upper/lower or left/right based on the length */
6835 if (distance
% 2 != 0) exit_trackdir_at_end
= NextTrackdir(exit_trackdir_at_end
);
6838 LineSnapPoint snap_start
= LineSnapPointAtRailTrackEndpoint(start_tile
, TrackdirToExitdir(exit_trackdir_at_start
), bidirectional_exit
);
6839 LineSnapPoint snap_end
= LineSnapPointAtRailTrackEndpoint(end_tile
, TrackdirToExitdir(exit_trackdir_at_end
), bidirectional_exit
);
6840 /* Find if we already had these coordinates before. */
6841 bool had_start
= false;
6842 bool had_end
= false;
6843 for (const LineSnapPoint
&snap
: _rail_snap_points
) {
6844 had_start
|= (snap
.x
== snap_start
.x
&& snap
.y
== snap_start
.y
);
6845 had_end
|= (snap
.x
== snap_end
.x
&& snap
.y
== snap_end
.y
);
6847 /* Create new snap point set. */
6848 if (had_start
&& had_end
) {
6849 /* just stop snaping, don't forget snap points */
6850 SetRailSnapMode(RSM_NO_SNAP
);
6852 /* include only new points */
6853 _rail_snap_points
.clear();
6854 if (!had_start
) _rail_snap_points
.push_back(snap_start
);
6855 if (!had_end
) _rail_snap_points
.push_back(snap_end
);
6856 SetRailSnapMode(RSM_SNAP_TO_RAIL
);
6861 static void MarkCatchmentTilesDirty()
6863 if (_viewport_highlight_town
!= nullptr) {
6864 MarkWholeNonMapViewportsDirty();
6867 if (_viewport_highlight_station
!= nullptr) {
6868 if (_viewport_highlight_station
->catchment_tiles
.tile
== INVALID_TILE
) {
6869 MarkWholeNonMapViewportsDirty();
6870 _viewport_highlight_station
= nullptr;
6872 BitmapTileIterator
it(_viewport_highlight_station
->catchment_tiles
);
6873 for (TileIndex tile
= it
; tile
!= INVALID_TILE
; tile
= ++it
) {
6874 MarkTileDirtyByTile(tile
, VMDF_NOT_MAP_MODE
);
6878 if (_viewport_highlight_waypoint
!= nullptr) {
6879 if (!_viewport_highlight_waypoint
->IsInUse()) {
6880 _viewport_highlight_waypoint
= nullptr;
6882 MarkWholeNonMapViewportsDirty();
6886 bool CurrentlySnappingRailPlacement()
6888 return (_thd
.place_mode
& HT_POLY
) && GetRailSnapMode() == RSM_SNAP_TO_RAIL
;
6891 static RailSnapMode
GetRailSnapMode()
6893 if (_rail_snap_mode
== RSM_SNAP_TO_TILE
&& _tile_snap_points
.size() == 0) return RSM_NO_SNAP
;
6894 if (_rail_snap_mode
== RSM_SNAP_TO_RAIL
&& _rail_snap_points
.size() == 0) return RSM_NO_SNAP
;
6895 return _rail_snap_mode
;
6898 static void SetRailSnapMode(RailSnapMode mode
)
6900 _rail_snap_mode
= mode
;
6902 if ((_thd
.place_mode
& HT_POLY
) && (GetRailSnapMode() == RSM_NO_SNAP
)) {
6903 SetTileSelectSize(1, 1);
6907 static TileIndex
GetRailSnapTile()
6909 if (_tile_snap_points
.size() == 0) return INVALID_TILE
;
6910 return TileVirtXY(_tile_snap_points
[DIAGDIR_NE
].x
, _tile_snap_points
[DIAGDIR_NE
].y
);
6913 static void SetRailSnapTile(TileIndex tile
)
6915 _tile_snap_points
.clear();
6916 if (tile
== INVALID_TILE
) return;
6918 for (DiagDirection dir
= DIAGDIR_BEGIN
; dir
< DIAGDIR_END
; dir
++) {
6919 _tile_snap_points
.push_back(LineSnapPointAtRailTrackEndpoint(tile
, dir
, false));
6920 LineSnapPoint
&point
= _tile_snap_points
.back();
6921 point
.dirs
= std::rotr
<uint8_t>(point
.dirs
, DIRDIFF_REVERSE
);
6925 void ResetRailPlacementSnapping()
6927 _rail_snap_mode
= RSM_NO_SNAP
;
6928 _tile_snap_points
.clear();
6929 _rail_snap_points
.clear();
6930 _current_snap_lock
.x
= -1;
6933 static void SetWindowDirtyForViewportCatchment()
6935 if (_viewport_highlight_station
!= nullptr) SetWindowDirty(WC_STATION_VIEW
, _viewport_highlight_station
->index
);
6936 if (_viewport_highlight_waypoint
!= nullptr) SetWindowDirty(WC_WAYPOINT_VIEW
, _viewport_highlight_waypoint
->index
);
6937 if (_viewport_highlight_town
!= nullptr) SetWindowDirty(WC_TOWN_VIEW
, _viewport_highlight_town
->index
);
6938 if (_viewport_highlight_tracerestrict_program
!= nullptr) InvalidateWindowClassesData(WC_TRACE_RESTRICT
);
6941 static void ClearViewportCatchment()
6943 MarkCatchmentTilesDirty();
6944 _viewport_highlight_station
= nullptr;
6945 _viewport_highlight_waypoint
= nullptr;
6946 _viewport_highlight_town
= nullptr;
6947 _viewport_highlight_tracerestrict_program
= nullptr;
6951 * Select or deselect station for coverage area highlight.
6952 * Selecting a station will deselect a town.
6953 * @param *st Station in question
6954 * @param sel Select or deselect given station
6956 void SetViewportCatchmentStation(const Station
*st
, bool sel
)
6958 SetWindowDirtyForViewportCatchment();
6959 if (sel
&& _viewport_highlight_station
!= st
) {
6960 ClearViewportCatchment();
6961 _viewport_highlight_station
= st
;
6962 MarkCatchmentTilesDirty();
6963 } else if (!sel
&& _viewport_highlight_station
== st
) {
6964 MarkCatchmentTilesDirty();
6965 _viewport_highlight_station
= nullptr;
6967 if (_viewport_highlight_station
!= nullptr) SetWindowDirty(WC_STATION_VIEW
, _viewport_highlight_station
->index
);
6971 * Select or deselect waypoint for coverage area highlight.
6972 * Selecting a waypoint will deselect a town.
6973 * @param *wp Waypoint in question
6974 * @param sel Select or deselect given waypoint
6976 void SetViewportCatchmentWaypoint(const Waypoint
*wp
, bool sel
)
6978 SetWindowDirtyForViewportCatchment();
6979 if (sel
&& _viewport_highlight_waypoint
!= wp
) {
6980 ClearViewportCatchment();
6981 _viewport_highlight_waypoint
= wp
;
6982 MarkCatchmentTilesDirty();
6983 } else if (!sel
&& _viewport_highlight_waypoint
== wp
) {
6984 MarkCatchmentTilesDirty();
6985 _viewport_highlight_waypoint
= nullptr;
6987 if (_viewport_highlight_waypoint
!= nullptr) SetWindowDirty(WC_WAYPOINT_VIEW
, _viewport_highlight_waypoint
->index
);
6991 * Select or deselect town for coverage area highlight.
6992 * Selecting a town will deselect a station.
6993 * @param *t Town in question
6994 * @param sel Select or deselect given town
6996 void SetViewportCatchmentTown(const Town
*t
, bool sel
)
6998 SetWindowDirtyForViewportCatchment();
6999 if (sel
&& _viewport_highlight_town
!= t
) {
7000 ClearViewportCatchment();
7001 _viewport_highlight_town
= t
;
7002 MarkWholeNonMapViewportsDirty();
7003 } else if (!sel
&& _viewport_highlight_town
== t
) {
7004 _viewport_highlight_town
= nullptr;
7005 MarkWholeNonMapViewportsDirty();
7007 if (_viewport_highlight_town
!= nullptr) SetWindowDirty(WC_TOWN_VIEW
, _viewport_highlight_town
->index
);
7010 void SetViewportCatchmentTraceRestrictProgram(const TraceRestrictProgram
*prog
, bool sel
)
7012 SetWindowDirtyForViewportCatchment();
7013 if (sel
&& _viewport_highlight_tracerestrict_program
!= prog
) {
7014 ClearViewportCatchment();
7015 _viewport_highlight_tracerestrict_program
= prog
;
7016 MarkWholeNonMapViewportsDirty();
7017 } else if (!sel
&& _viewport_highlight_tracerestrict_program
== prog
) {
7018 _viewport_highlight_tracerestrict_program
= nullptr;
7019 MarkWholeNonMapViewportsDirty();
7021 if (_viewport_highlight_tracerestrict_program
!= nullptr) InvalidateWindowClassesData(WC_TRACE_RESTRICT
);
7024 int GetSlopeTreeBrightnessAdjust(Slope slope
)
7055 bool IsViewportMouseHoverActive()
7057 if (_settings_client
.gui
.hover_delay_ms
== 0) {
7058 /* right click mode */
7059 return _right_button_down
|| _settings_client
.gui
.instant_tile_tooltip
;
7062 return _mouse_hovering
;