1 /*
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/>.
6  */
7 
8 /**
9  * @file viewport.cpp Handling of all viewports.
10  *
11  * \verbatim
12  * The in-game coordinate system looks like this *
13  *                                               *
14  *                    ^ Z                        *
15  *                    |                          *
16  *                    |                          *
17  *                    |                          *
18  *                    |                          *
19  *                 /     \                       *
20  *              /           \                    *
21  *           /                 \                 *
22  *        /                       \              *
23  *   X <                             > Y         *
24  * \endverbatim
25  */
26 
27 /**
28  * @defgroup vp_column_row Rows and columns in the viewport
29  *
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
35  * the right and east.
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.
40  *
41  * \verbatim
42  *        ╳        *
43  *       ╱ ╲       *
44  *      ╳ 0 ╳      *
45  *     ╱ ╲ ╱ ╲     *
46  *    ╳-1 ╳ 1 ╳    *
47  *   ╱ ╲ ╱ ╲ ╱ ╲   *
48  *  ╳-2 ╳ 0 ╳ 2 ╳  *
49  *   ╲ ╱ ╲ ╱ ╲ ╱   *
50  *    ╳-1 ╳ 1 ╳    *
51  *     ╲ ╱ ╲ ╱     *
52  *      ╳ 0 ╳      *
53  *       ╲ ╱       *
54  *        ╳        *
55  * \endverbatim
56  *
57  *
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.
61  */
62 
63 #include "stdafx.h"
64 #include "landscape.h"
65 #include "viewport_func.h"
66 #include "station_base.h"
67 #include "waypoint_base.h"
68 #include "town.h"
69 #include "signs_base.h"
70 #include "signs_func.h"
71 #include "vehicle_base.h"
72 #include "vehicle_gui.h"
73 #include "blitter/factory.hpp"
74 #include "strings_func.h"
75 #include "zoom_func.h"
76 #include "vehicle_func.h"
77 #include "company_func.h"
78 #include "waypoint_func.h"
79 #include "window_func.h"
80 #include "tilehighlight_func.h"
81 #include "window_gui.h"
82 #include "linkgraph/linkgraph_gui.h"
83 #include "viewport_kdtree.h"
84 #include "town_kdtree.h"
85 #include "viewport_sprite_sorter.h"
86 #include "bridge_map.h"
87 #include "company_base.h"
88 #include "command_func.h"
89 #include "network/network_func.h"
90 #include "framerate_type.h"
91 
92 #include <forward_list>
93 #include <map>
94 #include <stack>
95 
96 #include "table/strings.h"
97 #include "table/string_colours.h"
98 
99 #include "safeguards.h"
100 
101 Point _tile_fract_coords;
102 
103 
104 ViewportSignKdtree _viewport_sign_kdtree(&Kdtree_ViewportSignXYFunc);
105 static int _viewport_sign_maxwidth = 0;
106 
107 
108 static const int MAX_TILE_EXTENT_LEFT   = ZOOM_LVL_BASE * TILE_PIXELS;                     ///< Maximum left   extent of tile relative to north corner.
109 static const int MAX_TILE_EXTENT_RIGHT  = ZOOM_LVL_BASE * TILE_PIXELS;                     ///< Maximum right  extent of tile relative to north corner.
110 static const int MAX_TILE_EXTENT_TOP    = ZOOM_LVL_BASE * MAX_BUILDING_PIXELS;             ///< Maximum top    extent of tile relative to north corner (not considering bridges).
111 static const int MAX_TILE_EXTENT_BOTTOM = ZOOM_LVL_BASE * (TILE_PIXELS + 2 * TILE_HEIGHT); ///< Maximum bottom extent of tile relative to north corner (worst case: #SLOPE_STEEP_N).
112 
113 struct StringSpriteToDraw {
114 	StringID string;
115 	Colours colour;
116 	int32 x;
117 	int32 y;
118 	uint64 params[2];
119 	uint16 width;
120 };
121 
122 struct TileSpriteToDraw {
123 	SpriteID image;
124 	PaletteID pal;
125 	const SubSprite *sub;           ///< only draw a rectangular part of the sprite
126 	int32 x;                        ///< screen X coordinate of sprite
127 	int32 y;                        ///< screen Y coordinate of sprite
128 };
129 
130 struct ChildScreenSpriteToDraw {
131 	SpriteID image;
132 	PaletteID pal;
133 	const SubSprite *sub;           ///< only draw a rectangular part of the sprite
134 	int32 x;
135 	int32 y;
136 	int next;                       ///< next child to draw (-1 at the end)
137 };
138 
139 /** Enumeration of multi-part foundations */
140 enum FoundationPart {
141 	FOUNDATION_PART_NONE     = 0xFF,  ///< Neither foundation nor groundsprite drawn yet.
142 	FOUNDATION_PART_NORMAL   = 0,     ///< First part (normal foundation or no foundation)
143 	FOUNDATION_PART_HALFTILE = 1,     ///< Second part (halftile foundation)
144 	FOUNDATION_PART_END
145 };
146 
147 /**
148  * Mode of "sprite combining"
149  * @see StartSpriteCombine
150  */
151 enum SpriteCombineMode {
152 	SPRITE_COMBINE_NONE,     ///< Every #AddSortableSpriteToDraw start its own bounding box
153 	SPRITE_COMBINE_PENDING,  ///< %Sprite combining will start with the next unclipped sprite.
154 	SPRITE_COMBINE_ACTIVE,   ///< %Sprite combining is active. #AddSortableSpriteToDraw outputs child sprites.
155 };
156 
157 typedef std::vector<TileSpriteToDraw> TileSpriteToDrawVector;
158 typedef std::vector<StringSpriteToDraw> StringSpriteToDrawVector;
159 typedef std::vector<ParentSpriteToDraw> ParentSpriteToDrawVector;
160 typedef std::vector<ChildScreenSpriteToDraw> ChildScreenSpriteToDrawVector;
161 
162 /** Data structure storing rendering information */
163 struct ViewportDrawer {
164 	DrawPixelInfo dpi;
165 
166 	StringSpriteToDrawVector string_sprites_to_draw;
167 	TileSpriteToDrawVector tile_sprites_to_draw;
168 	ParentSpriteToDrawVector parent_sprites_to_draw;
169 	ParentSpriteToSortVector parent_sprites_to_sort; ///< Parent sprite pointer array used for sorting
170 	ChildScreenSpriteToDrawVector child_screen_sprites_to_draw;
171 
172 	int *last_child;
173 
174 	SpriteCombineMode combine_sprites;               ///< Current mode of "sprite combining". @see StartSpriteCombine
175 
176 	int foundation[FOUNDATION_PART_END];             ///< Foundation sprites (index into parent_sprites_to_draw).
177 	FoundationPart foundation_part;                  ///< Currently active foundation for ground sprite drawing.
178 	int *last_foundation_child[FOUNDATION_PART_END]; ///< Tail of ChildSprite list of the foundations. (index into child_screen_sprites_to_draw)
179 	Point foundation_offset[FOUNDATION_PART_END];    ///< Pixel offset for ground sprites on the foundations.
180 };
181 
182 static bool MarkViewportDirty(const Viewport *vp, int left, int top, int right, int bottom);
183 
184 static ViewportDrawer _vd;
185 
186 TileHighlightData _thd;
187 static TileInfo *_cur_ti;
188 bool _draw_bounding_boxes = false;
189 bool _draw_dirty_blocks = false;
190 uint _dirty_block_colour = 0;
191 static VpSpriteSorter _vp_sprite_sorter = nullptr;
192 
MapXYZToViewport(const Viewport * vp,int x,int y,int z)193 static Point MapXYZToViewport(const Viewport *vp, int x, int y, int z)
194 {
195 	Point p = RemapCoords(x, y, z);
196 	p.x -= vp->virtual_width / 2;
197 	p.y -= vp->virtual_height / 2;
198 	return p;
199 }
200 
DeleteWindowViewport(Window * w)201 void DeleteWindowViewport(Window *w)
202 {
203 	if (w->viewport == nullptr) return;
204 
205 	delete w->viewport->overlay;
206 	free(w->viewport);
207 	w->viewport = nullptr;
208 }
209 
210 /**
211  * Initialize viewport of the window for use.
212  * @param w Window to use/display the viewport in
213  * @param x Offset of left edge of viewport with respect to left edge window \a w
214  * @param y Offset of top edge of viewport with respect to top edge window \a w
215  * @param width Width of the viewport
216  * @param height Height of the viewport
217  * @param follow_flags Flags controlling the viewport.
218  *        - If bit 31 is set, the lower 20 bits are the vehicle that the viewport should follow.
219  *        - If bit 31 is clear, it is a #TileIndex.
220  * @param zoom Zoomlevel to display
221  */
InitializeWindowViewport(Window * w,int x,int y,int width,int height,uint32 follow_flags,ZoomLevel zoom)222 void InitializeWindowViewport(Window *w, int x, int y,
223 	int width, int height, uint32 follow_flags, ZoomLevel zoom)
224 {
225 	assert(w->viewport == nullptr);
226 
227 	ViewportData *vp = CallocT<ViewportData>(1);
228 
229 	vp->left = x + w->left;
230 	vp->top = y + w->top;
231 	vp->width = width;
232 	vp->height = height;
233 
234 	vp->zoom = static_cast<ZoomLevel>(Clamp(zoom, _settings_client.gui.zoom_min, _settings_client.gui.zoom_max));
235 
236 	vp->virtual_width = ScaleByZoom(width, zoom);
237 	vp->virtual_height = ScaleByZoom(height, zoom);
238 
239 	Point pt;
240 
241 	if (follow_flags & 0x80000000) {
242 		const Vehicle *veh;
243 
244 		vp->follow_vehicle = (VehicleID)(follow_flags & 0xFFFFF);
245 		veh = Vehicle::Get(vp->follow_vehicle);
246 		pt = MapXYZToViewport(vp, veh->x_pos, veh->y_pos, veh->z_pos);
247 	} else {
248 		x = TileX(follow_flags) * TILE_SIZE;
249 		y = TileY(follow_flags) * TILE_SIZE;
250 
251 		vp->follow_vehicle = INVALID_VEHICLE;
252 		pt = MapXYZToViewport(vp, x, y, GetSlopePixelZ(x, y));
253 	}
254 
255 	vp->scrollpos_x = pt.x;
256 	vp->scrollpos_y = pt.y;
257 	vp->dest_scrollpos_x = pt.x;
258 	vp->dest_scrollpos_y = pt.y;
259 
260 	vp->overlay = nullptr;
261 
262 	w->viewport = vp;
263 	vp->virtual_left = 0; // pt.x;
264 	vp->virtual_top = 0;  // pt.y;
265 }
266 
267 static Point _vp_move_offs;
268 
DoSetViewportPosition(Window::IteratorToFront it,int left,int top,int width,int height)269 static void DoSetViewportPosition(Window::IteratorToFront it, int left, int top, int width, int height)
270 {
271 	for (; !it.IsEnd(); ++it) {
272 		const Window *w = *it;
273 		if (left + width > w->left &&
274 				w->left + w->width > left &&
275 				top + height > w->top &&
276 				w->top + w->height > top) {
277 
278 			if (left < w->left) {
279 				DoSetViewportPosition(it, left, top, w->left - left, height);
280 				DoSetViewportPosition(it, left + (w->left - left), top, width - (w->left - left), height);
281 				return;
282 			}
283 
284 			if (left + width > w->left + w->width) {
285 				DoSetViewportPosition(it, left, top, (w->left + w->width - left), height);
286 				DoSetViewportPosition(it, left + (w->left + w->width - left), top, width - (w->left + w->width - left), height);
287 				return;
288 			}
289 
290 			if (top < w->top) {
291 				DoSetViewportPosition(it, left, top, width, (w->top - top));
292 				DoSetViewportPosition(it, left, top + (w->top - top), width, height - (w->top - top));
293 				return;
294 			}
295 
296 			if (top + height > w->top + w->height) {
297 				DoSetViewportPosition(it, left, top, width, (w->top + w->height - top));
298 				DoSetViewportPosition(it, left, top + (w->top + w->height - top), width, height - (w->top + w->height - top));
299 				return;
300 			}
301 
302 			return;
303 		}
304 	}
305 
306 	{
307 		int xo = _vp_move_offs.x;
308 		int yo = _vp_move_offs.y;
309 
310 		if (abs(xo) >= width || abs(yo) >= height) {
311 			/* fully_outside */
312 			RedrawScreenRect(left, top, left + width, top + height);
313 			return;
314 		}
315 
316 		GfxScroll(left, top, width, height, xo, yo);
317 
318 		if (xo > 0) {
319 			RedrawScreenRect(left, top, xo + left, top + height);
320 			left += xo;
321 			width -= xo;
322 		} else if (xo < 0) {
323 			RedrawScreenRect(left + width + xo, top, left + width, top + height);
324 			width += xo;
325 		}
326 
327 		if (yo > 0) {
328 			RedrawScreenRect(left, top, width + left, top + yo);
329 		} else if (yo < 0) {
330 			RedrawScreenRect(left, top + height + yo, width + left, top + height);
331 		}
332 	}
333 }
334 
SetViewportPosition(Window * w,int x,int y)335 static void SetViewportPosition(Window *w, int x, int y)
336 {
337 	Viewport *vp = w->viewport;
338 	int old_left = vp->virtual_left;
339 	int old_top = vp->virtual_top;
340 	int i;
341 	int left, top, width, height;
342 
343 	vp->virtual_left = x;
344 	vp->virtual_top = y;
345 
346 	/* Viewport is bound to its left top corner, so it must be rounded down (UnScaleByZoomLower)
347 	 * else glitch described in FS#1412 will happen (offset by 1 pixel with zoom level > NORMAL)
348 	 */
349 	old_left = UnScaleByZoomLower(old_left, vp->zoom);
350 	old_top = UnScaleByZoomLower(old_top, vp->zoom);
351 	x = UnScaleByZoomLower(x, vp->zoom);
352 	y = UnScaleByZoomLower(y, vp->zoom);
353 
354 	old_left -= x;
355 	old_top -= y;
356 
357 	if (old_top == 0 && old_left == 0) return;
358 
359 	_vp_move_offs.x = old_left;
360 	_vp_move_offs.y = old_top;
361 
362 	left = vp->left;
363 	top = vp->top;
364 	width = vp->width;
365 	height = vp->height;
366 
367 	if (left < 0) {
368 		width += left;
369 		left = 0;
370 	}
371 
372 	i = left + width - _screen.width;
373 	if (i >= 0) width -= i;
374 
375 	if (width > 0) {
376 		if (top < 0) {
377 			height += top;
378 			top = 0;
379 		}
380 
381 		i = top + height - _screen.height;
382 		if (i >= 0) height -= i;
383 
384 		if (height > 0) {
385 			Window::IteratorToFront it(w);
386 			++it;
387 			DoSetViewportPosition(it, left, top, width, height);
388 		}
389 	}
390 }
391 
392 /**
393  * Is a xy position inside the viewport of the window?
394  * @param w Window to examine its viewport
395  * @param x X coordinate of the xy position
396  * @param y Y coordinate of the xy position
397  * @return Pointer to the viewport if the xy position is in the viewport of the window,
398  *         otherwise \c nullptr is returned.
399  */
IsPtInWindowViewport(const Window * w,int x,int y)400 Viewport *IsPtInWindowViewport(const Window *w, int x, int y)
401 {
402 	Viewport *vp = w->viewport;
403 
404 	if (vp != nullptr &&
405 			IsInsideMM(x, vp->left, vp->left + vp->width) &&
406 			IsInsideMM(y, vp->top, vp->top + vp->height))
407 		return vp;
408 
409 	return nullptr;
410 }
411 
412 /**
413  * Translate screen coordinate in a viewport to underlying tile coordinate.
414  *
415  * Returns exact point of the map that is visible in the given place
416  * of the viewport (3D perspective), height of tiles and foundations matter.
417  *
418  * @param vp  Viewport that contains the (\a x, \a y) screen coordinate
419  * @param x   Screen x coordinate, distance in pixels from the left edge of viewport frame
420  * @param y   Screen y coordinate, distance in pixels from the top edge of viewport frame
421  * @param clamp_to_map Clamp the coordinate outside of the map to the closest, non-void tile within the map
422  * @return Tile coordinate or (-1, -1) if given x or y is not within viewport frame
423  */
TranslateXYToTileCoord(const Viewport * vp,int x,int y,bool clamp_to_map)424 Point TranslateXYToTileCoord(const Viewport *vp, int x, int y, bool clamp_to_map)
425 {
426 	if (!IsInsideBS(x, vp->left, vp->width) || !IsInsideBS(y, vp->top, vp->height)) {
427 		Point pt = { -1, -1 };
428 		return pt;
429 	}
430 
431 	return InverseRemapCoords2(
432 			ScaleByZoom(x - vp->left, vp->zoom) + vp->virtual_left,
433 			ScaleByZoom(y - vp->top, vp->zoom) + vp->virtual_top, clamp_to_map);
434 }
435 
436 /* When used for zooming, check area below current coordinates (x,y)
437  * and return the tile of the zoomed out/in position (zoom_x, zoom_y)
438  * when you just want the tile, make x = zoom_x and y = zoom_y */
GetTileFromScreenXY(int x,int y,int zoom_x,int zoom_y)439 static Point GetTileFromScreenXY(int x, int y, int zoom_x, int zoom_y)
440 {
441 	Window *w;
442 	Viewport *vp;
443 	Point pt;
444 
445 	if ( (w = FindWindowFromPt(x, y)) != nullptr &&
446 			 (vp = IsPtInWindowViewport(w, x, y)) != nullptr)
447 				return TranslateXYToTileCoord(vp, zoom_x, zoom_y);
448 
449 	pt.y = pt.x = -1;
450 	return pt;
451 }
452 
GetTileBelowCursor()453 Point GetTileBelowCursor()
454 {
455 	return GetTileFromScreenXY(_cursor.pos.x, _cursor.pos.y, _cursor.pos.x, _cursor.pos.y);
456 }
457 
458 
GetTileZoomCenterWindow(bool in,Window * w)459 Point GetTileZoomCenterWindow(bool in, Window * w)
460 {
461 	int x, y;
462 	Viewport *vp = w->viewport;
463 
464 	if (in) {
465 		x = ((_cursor.pos.x - vp->left) >> 1) + (vp->width >> 2);
466 		y = ((_cursor.pos.y - vp->top) >> 1) + (vp->height >> 2);
467 	} else {
468 		x = vp->width - (_cursor.pos.x - vp->left);
469 		y = vp->height - (_cursor.pos.y - vp->top);
470 	}
471 	/* Get the tile below the cursor and center on the zoomed-out center */
472 	return GetTileFromScreenXY(_cursor.pos.x, _cursor.pos.y, x + vp->left, y + vp->top);
473 }
474 
475 /**
476  * Update the status of the zoom-buttons according to the zoom-level
477  * of the viewport. This will update their status and invalidate accordingly
478  * @param w Window pointer to the window that has the zoom buttons
479  * @param vp pointer to the viewport whose zoom-level the buttons represent
480  * @param widget_zoom_in widget index for window with zoom-in button
481  * @param widget_zoom_out widget index for window with zoom-out button
482  */
HandleZoomMessage(Window * w,const Viewport * vp,byte widget_zoom_in,byte widget_zoom_out)483 void HandleZoomMessage(Window *w, const Viewport *vp, byte widget_zoom_in, byte widget_zoom_out)
484 {
485 	w->SetWidgetDisabledState(widget_zoom_in, vp->zoom <= _settings_client.gui.zoom_min);
486 	w->SetWidgetDirty(widget_zoom_in);
487 
488 	w->SetWidgetDisabledState(widget_zoom_out, vp->zoom >= _settings_client.gui.zoom_max);
489 	w->SetWidgetDirty(widget_zoom_out);
490 }
491 
492 /**
493  * Schedules a tile sprite for drawing.
494  *
495  * @param image the image to draw.
496  * @param pal the provided palette.
497  * @param x position x (world coordinates) of the sprite.
498  * @param y position y (world coordinates) of the sprite.
499  * @param z position z (world coordinates) of the sprite.
500  * @param sub Only draw a part of the sprite.
501  * @param extra_offs_x Pixel X offset for the sprite position.
502  * @param extra_offs_y Pixel Y offset for the sprite position.
503  */
AddTileSpriteToDraw(SpriteID image,PaletteID pal,int32 x,int32 y,int z,const SubSprite * sub=nullptr,int extra_offs_x=0,int extra_offs_y=0)504 static void AddTileSpriteToDraw(SpriteID image, PaletteID pal, int32 x, int32 y, int z, const SubSprite *sub = nullptr, int extra_offs_x = 0, int extra_offs_y = 0)
505 {
506 	assert((image & SPRITE_MASK) < MAX_SPRITES);
507 
508 	TileSpriteToDraw &ts = _vd.tile_sprites_to_draw.emplace_back();
509 	ts.image = image;
510 	ts.pal = pal;
511 	ts.sub = sub;
512 	Point pt = RemapCoords(x, y, z);
513 	ts.x = pt.x + extra_offs_x;
514 	ts.y = pt.y + extra_offs_y;
515 }
516 
517 /**
518  * Adds a child sprite to the active foundation.
519  *
520  * The pixel offset of the sprite relative to the ParentSprite is the sum of the offset passed to OffsetGroundSprite() and extra_offs_?.
521  *
522  * @param image the image to draw.
523  * @param pal the provided palette.
524  * @param sub Only draw a part of the sprite.
525  * @param foundation_part Foundation part.
526  * @param extra_offs_x Pixel X offset for the sprite position.
527  * @param extra_offs_y Pixel Y offset for the sprite position.
528  */
AddChildSpriteToFoundation(SpriteID image,PaletteID pal,const SubSprite * sub,FoundationPart foundation_part,int extra_offs_x,int extra_offs_y)529 static void AddChildSpriteToFoundation(SpriteID image, PaletteID pal, const SubSprite *sub, FoundationPart foundation_part, int extra_offs_x, int extra_offs_y)
530 {
531 	assert(IsInsideMM(foundation_part, 0, FOUNDATION_PART_END));
532 	assert(_vd.foundation[foundation_part] != -1);
533 	Point offs = _vd.foundation_offset[foundation_part];
534 
535 	/* Change the active ChildSprite list to the one of the foundation */
536 	int *old_child = _vd.last_child;
537 	_vd.last_child = _vd.last_foundation_child[foundation_part];
538 
539 	AddChildSpriteScreen(image, pal, offs.x + extra_offs_x, offs.y + extra_offs_y, false, sub, false);
540 
541 	/* Switch back to last ChildSprite list */
542 	_vd.last_child = old_child;
543 }
544 
545 /**
546  * Draws a ground sprite at a specific world-coordinate relative to the current tile.
547  * If the current tile is drawn on top of a foundation the sprite is added as child sprite to the "foundation"-ParentSprite.
548  *
549  * @param image the image to draw.
550  * @param pal the provided palette.
551  * @param x position x (world coordinates) of the sprite relative to current tile.
552  * @param y position y (world coordinates) of the sprite relative to current tile.
553  * @param z position z (world coordinates) of the sprite relative to current tile.
554  * @param sub Only draw a part of the sprite.
555  * @param extra_offs_x Pixel X offset for the sprite position.
556  * @param extra_offs_y Pixel Y offset for the sprite position.
557  */
DrawGroundSpriteAt(SpriteID image,PaletteID pal,int32 x,int32 y,int z,const SubSprite * sub,int extra_offs_x,int extra_offs_y)558 void DrawGroundSpriteAt(SpriteID image, PaletteID pal, int32 x, int32 y, int z, const SubSprite *sub, int extra_offs_x, int extra_offs_y)
559 {
560 	/* Switch to first foundation part, if no foundation was drawn */
561 	if (_vd.foundation_part == FOUNDATION_PART_NONE) _vd.foundation_part = FOUNDATION_PART_NORMAL;
562 
563 	if (_vd.foundation[_vd.foundation_part] != -1) {
564 		Point pt = RemapCoords(x, y, z);
565 		AddChildSpriteToFoundation(image, pal, sub, _vd.foundation_part, pt.x + extra_offs_x * ZOOM_LVL_BASE, pt.y + extra_offs_y * ZOOM_LVL_BASE);
566 	} else {
567 		AddTileSpriteToDraw(image, pal, _cur_ti->x + x, _cur_ti->y + y, _cur_ti->z + z, sub, extra_offs_x * ZOOM_LVL_BASE, extra_offs_y * ZOOM_LVL_BASE);
568 	}
569 }
570 
571 /**
572  * Draws a ground sprite for the current tile.
573  * If the current tile is drawn on top of a foundation the sprite is added as child sprite to the "foundation"-ParentSprite.
574  *
575  * @param image the image to draw.
576  * @param pal the provided palette.
577  * @param sub Only draw a part of the sprite.
578  * @param extra_offs_x Pixel X offset for the sprite position.
579  * @param extra_offs_y Pixel Y offset for the sprite position.
580  */
DrawGroundSprite(SpriteID image,PaletteID pal,const SubSprite * sub,int extra_offs_x,int extra_offs_y)581 void DrawGroundSprite(SpriteID image, PaletteID pal, const SubSprite *sub, int extra_offs_x, int extra_offs_y)
582 {
583 	DrawGroundSpriteAt(image, pal, 0, 0, 0, sub, extra_offs_x, extra_offs_y);
584 }
585 
586 /**
587  * Called when a foundation has been drawn for the current tile.
588  * Successive ground sprites for the current tile will be drawn as child sprites of the "foundation"-ParentSprite, not as TileSprites.
589  *
590  * @param x sprite x-offset (screen coordinates) of ground sprites relative to the "foundation"-ParentSprite.
591  * @param y sprite y-offset (screen coordinates) of ground sprites relative to the "foundation"-ParentSprite.
592  */
OffsetGroundSprite(int x,int y)593 void OffsetGroundSprite(int x, int y)
594 {
595 	/* Switch to next foundation part */
596 	switch (_vd.foundation_part) {
597 		case FOUNDATION_PART_NONE:
598 			_vd.foundation_part = FOUNDATION_PART_NORMAL;
599 			break;
600 		case FOUNDATION_PART_NORMAL:
601 			_vd.foundation_part = FOUNDATION_PART_HALFTILE;
602 			break;
603 		default: NOT_REACHED();
604 	}
605 
606 	/* _vd.last_child == nullptr if foundation sprite was clipped by the viewport bounds */
607 	if (_vd.last_child != nullptr) _vd.foundation[_vd.foundation_part] = (uint)_vd.parent_sprites_to_draw.size() - 1;
608 
609 	_vd.foundation_offset[_vd.foundation_part].x = x * ZOOM_LVL_BASE;
610 	_vd.foundation_offset[_vd.foundation_part].y = y * ZOOM_LVL_BASE;
611 	_vd.last_foundation_child[_vd.foundation_part] = _vd.last_child;
612 }
613 
614 /**
615  * Adds a child sprite to a parent sprite.
616  * In contrast to "AddChildSpriteScreen()" the sprite position is in world coordinates
617  *
618  * @param image the image to draw.
619  * @param pal the provided palette.
620  * @param x position x of the sprite.
621  * @param y position y of the sprite.
622  * @param z position z of the sprite.
623  * @param sub Only draw a part of the sprite.
624  */
AddCombinedSprite(SpriteID image,PaletteID pal,int x,int y,int z,const SubSprite * sub)625 static void AddCombinedSprite(SpriteID image, PaletteID pal, int x, int y, int z, const SubSprite *sub)
626 {
627 	Point pt = RemapCoords(x, y, z);
628 	const Sprite *spr = GetSprite(image & SPRITE_MASK, ST_NORMAL);
629 
630 	if (pt.x + spr->x_offs >= _vd.dpi.left + _vd.dpi.width ||
631 			pt.x + spr->x_offs + spr->width <= _vd.dpi.left ||
632 			pt.y + spr->y_offs >= _vd.dpi.top + _vd.dpi.height ||
633 			pt.y + spr->y_offs + spr->height <= _vd.dpi.top)
634 		return;
635 
636 	const ParentSpriteToDraw &pstd = _vd.parent_sprites_to_draw.back();
637 	AddChildSpriteScreen(image, pal, pt.x - pstd.left, pt.y - pstd.top, false, sub, false);
638 }
639 
640 /**
641  * Draw a (transparent) sprite at given coordinates with a given bounding box.
642  * 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.
643  * Bounding boxes with bb_offset_x == w or bb_offset_y == h or bb_offset_z == dz are allowed and produce thin slices.
644  *
645  * @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
646  *       defined by the sprite offset in the grf file.
647  *       However if modifying the sprite offsets is not suitable (e.g. when using existing graphics), the bounding box can be tuned by bb_offset.
648  *
649  * @pre w >= bb_offset_x, h >= bb_offset_y, dz >= bb_offset_z. Else w, h or dz are ignored.
650  *
651  * @param image the image to combine and draw,
652  * @param pal the provided palette,
653  * @param x position X (world) of the sprite,
654  * @param y position Y (world) of the sprite,
655  * @param w bounding box extent towards positive X (world),
656  * @param h bounding box extent towards positive Y (world),
657  * @param dz bounding box extent towards positive Z (world),
658  * @param z position Z (world) of the sprite,
659  * @param transparent if true, switch the palette between the provided palette and the transparent palette,
660  * @param bb_offset_x bounding box extent towards negative X (world),
661  * @param bb_offset_y bounding box extent towards negative Y (world),
662  * @param bb_offset_z bounding box extent towards negative Z (world)
663  * @param sub Only draw a part of the sprite.
664  */
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)665 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)
666 {
667 	int32 left, right, top, bottom;
668 
669 	assert((image & SPRITE_MASK) < MAX_SPRITES);
670 
671 	/* make the sprites transparent with the right palette */
672 	if (transparent) {
673 		SetBit(image, PALETTE_MODIFIER_TRANSPARENT);
674 		pal = PALETTE_TO_TRANSPARENT;
675 	}
676 
677 	if (_vd.combine_sprites == SPRITE_COMBINE_ACTIVE) {
678 		AddCombinedSprite(image, pal, x, y, z, sub);
679 		return;
680 	}
681 
682 	_vd.last_child = nullptr;
683 
684 	Point pt = RemapCoords(x, y, z);
685 	int tmp_left, tmp_top, tmp_x = pt.x, tmp_y = pt.y;
686 
687 	/* Compute screen extents of sprite */
688 	if (image == SPR_EMPTY_BOUNDING_BOX) {
689 		left = tmp_left = RemapCoords(x + w          , y + bb_offset_y, z + bb_offset_z).x;
690 		right           = RemapCoords(x + bb_offset_x, y + h          , z + bb_offset_z).x + 1;
691 		top  = tmp_top  = RemapCoords(x + bb_offset_x, y + bb_offset_y, z + dz         ).y;
692 		bottom          = RemapCoords(x + w          , y + h          , z + bb_offset_z).y + 1;
693 	} else {
694 		const Sprite *spr = GetSprite(image & SPRITE_MASK, ST_NORMAL);
695 		left = tmp_left = (pt.x += spr->x_offs);
696 		right           = (pt.x +  spr->width );
697 		top  = tmp_top  = (pt.y += spr->y_offs);
698 		bottom          = (pt.y +  spr->height);
699 	}
700 
701 	if (_draw_bounding_boxes && (image != SPR_EMPTY_BOUNDING_BOX)) {
702 		/* Compute maximal extents of sprite and its bounding box */
703 		left   = std::min(left  , RemapCoords(x + w          , y + bb_offset_y, z + bb_offset_z).x);
704 		right  = std::max(right , RemapCoords(x + bb_offset_x, y + h          , z + bb_offset_z).x + 1);
705 		top    = std::min(top   , RemapCoords(x + bb_offset_x, y + bb_offset_y, z + dz         ).y);
706 		bottom = std::max(bottom, RemapCoords(x + w          , y + h          , z + bb_offset_z).y + 1);
707 	}
708 
709 	/* Do not add the sprite to the viewport, if it is outside */
710 	if (left   >= _vd.dpi.left + _vd.dpi.width ||
711 	    right  <= _vd.dpi.left                 ||
712 	    top    >= _vd.dpi.top + _vd.dpi.height ||
713 	    bottom <= _vd.dpi.top) {
714 		return;
715 	}
716 
717 	ParentSpriteToDraw &ps = _vd.parent_sprites_to_draw.emplace_back();
718 	ps.x = tmp_x;
719 	ps.y = tmp_y;
720 
721 	ps.left = tmp_left;
722 	ps.top  = tmp_top;
723 
724 	ps.image = image;
725 	ps.pal = pal;
726 	ps.sub = sub;
727 	ps.xmin = x + bb_offset_x;
728 	ps.xmax = x + std::max(bb_offset_x, w) - 1;
729 
730 	ps.ymin = y + bb_offset_y;
731 	ps.ymax = y + std::max(bb_offset_y, h) - 1;
732 
733 	ps.zmin = z + bb_offset_z;
734 	ps.zmax = z + std::max(bb_offset_z, dz) - 1;
735 
736 	ps.first_child = -1;
737 
738 	_vd.last_child = &ps.first_child;
739 
740 	if (_vd.combine_sprites == SPRITE_COMBINE_PENDING) _vd.combine_sprites = SPRITE_COMBINE_ACTIVE;
741 }
742 
743 /**
744  * Starts a block of sprites, which are "combined" into a single bounding box.
745  *
746  * Subsequent calls to #AddSortableSpriteToDraw will be drawn into the same bounding box.
747  * That is: The first sprite that is not clipped by the viewport defines the bounding box, and
748  * the following sprites will be child sprites to that one.
749  *
750  * That implies:
751  *  - The drawing order is definite. No other sprites will be sorted between those of the block.
752  *  - You have to provide a valid bounding box for all sprites,
753  *    as you won't know which one is the first non-clipped one.
754  *    Preferable you use the same bounding box for all.
755  *  - You cannot use #AddChildSpriteScreen inside the block, as its result will be indefinite.
756  *
757  * The block is terminated by #EndSpriteCombine.
758  *
759  * You cannot nest "combined" blocks.
760  */
StartSpriteCombine()761 void StartSpriteCombine()
762 {
763 	assert(_vd.combine_sprites == SPRITE_COMBINE_NONE);
764 	_vd.combine_sprites = SPRITE_COMBINE_PENDING;
765 }
766 
767 /**
768  * Terminates a block of sprites started by #StartSpriteCombine.
769  * Take a look there for details.
770  */
EndSpriteCombine()771 void EndSpriteCombine()
772 {
773 	assert(_vd.combine_sprites != SPRITE_COMBINE_NONE);
774 	_vd.combine_sprites = SPRITE_COMBINE_NONE;
775 }
776 
777 /**
778  * Check if the parameter "check" is inside the interval between
779  * begin and end, including both begin and end.
780  * @note Whether \c begin or \c end is the biggest does not matter.
781  *       This method will account for that.
782  * @param begin The begin of the interval.
783  * @param end   The end of the interval.
784  * @param check The value to check.
785  */
IsInRangeInclusive(int begin,int end,int check)786 static bool IsInRangeInclusive(int begin, int end, int check)
787 {
788 	if (begin > end) Swap(begin, end);
789 	return begin <= check && check <= end;
790 }
791 
792 /**
793  * Checks whether a point is inside the selected a diagonal rectangle given by _thd.size and _thd.pos
794  * @param x The x coordinate of the point to be checked.
795  * @param y The y coordinate of the point to be checked.
796  * @return True if the point is inside the rectangle, else false.
797  */
IsInsideRotatedRectangle(int x,int y)798 bool IsInsideRotatedRectangle(int x, int y)
799 {
800 	int dist_a = (_thd.size.x + _thd.size.y);      // Rotated coordinate system for selected rectangle.
801 	int dist_b = (_thd.size.x - _thd.size.y);      // We don't have to divide by 2. It's all relative!
802 	int a = ((x - _thd.pos.x) + (y - _thd.pos.y)); // Rotated coordinate system for the point under scrutiny.
803 	int b = ((x - _thd.pos.x) - (y - _thd.pos.y));
804 
805 	/* Check if a and b are between 0 and dist_a or dist_b respectively. */
806 	return IsInRangeInclusive(dist_a, 0, a) && IsInRangeInclusive(dist_b, 0, b);
807 }
808 
809 /**
810  * Add a child sprite to a parent sprite.
811  *
812  * @param image the image to draw.
813  * @param pal the provided palette.
814  * @param x sprite x-offset (screen coordinates) relative to parent sprite.
815  * @param y sprite y-offset (screen coordinates) relative to parent sprite.
816  * @param transparent if true, switch the palette between the provided palette and the transparent palette,
817  * @param sub Only draw a part of the sprite.
818  */
AddChildSpriteScreen(SpriteID image,PaletteID pal,int x,int y,bool transparent,const SubSprite * sub,bool scale)819 void AddChildSpriteScreen(SpriteID image, PaletteID pal, int x, int y, bool transparent, const SubSprite *sub, bool scale)
820 {
821 	assert((image & SPRITE_MASK) < MAX_SPRITES);
822 
823 	/* If the ParentSprite was clipped by the viewport bounds, do not draw the ChildSprites either */
824 	if (_vd.last_child == nullptr) return;
825 
826 	/* make the sprites transparent with the right palette */
827 	if (transparent) {
828 		SetBit(image, PALETTE_MODIFIER_TRANSPARENT);
829 		pal = PALETTE_TO_TRANSPARENT;
830 	}
831 
832 	*_vd.last_child = (uint)_vd.child_screen_sprites_to_draw.size();
833 
834 	ChildScreenSpriteToDraw &cs = _vd.child_screen_sprites_to_draw.emplace_back();
835 	cs.image = image;
836 	cs.pal = pal;
837 	cs.sub = sub;
838 	cs.x = scale ? x * ZOOM_LVL_BASE : x;
839 	cs.y = scale ? y * ZOOM_LVL_BASE : y;
840 	cs.next = -1;
841 
842 	/* Append the sprite to the active ChildSprite list.
843 	 * If the active ParentSprite is a foundation, update last_foundation_child as well.
844 	 * Note: ChildSprites of foundations are NOT sequential in the vector, as selection sprites are added at last. */
845 	if (_vd.last_foundation_child[0] == _vd.last_child) _vd.last_foundation_child[0] = &cs.next;
846 	if (_vd.last_foundation_child[1] == _vd.last_child) _vd.last_foundation_child[1] = &cs.next;
847 	_vd.last_child = &cs.next;
848 }
849 
AddStringToDraw(int x,int y,StringID string,uint64 params_1,uint64 params_2,Colours colour,uint16 width)850 static void AddStringToDraw(int x, int y, StringID string, uint64 params_1, uint64 params_2, Colours colour, uint16 width)
851 {
852 	assert(width != 0);
853 	StringSpriteToDraw &ss = _vd.string_sprites_to_draw.emplace_back();
854 	ss.string = string;
855 	ss.x = x;
856 	ss.y = y;
857 	ss.params[0] = params_1;
858 	ss.params[1] = params_2;
859 	ss.width = width;
860 	ss.colour = colour;
861 }
862 
863 
864 /**
865  * Draws sprites between ground sprite and everything above.
866  *
867  * The sprite is either drawn as TileSprite or as ChildSprite of the active foundation.
868  *
869  * @param image the image to draw.
870  * @param pal the provided palette.
871  * @param ti TileInfo Tile that is being drawn
872  * @param z_offset Z offset relative to the groundsprite. Only used for the sprite position, not for sprite sorting.
873  * @param foundation_part Foundation part the sprite belongs to.
874  */
DrawSelectionSprite(SpriteID image,PaletteID pal,const TileInfo * ti,int z_offset,FoundationPart foundation_part)875 static void DrawSelectionSprite(SpriteID image, PaletteID pal, const TileInfo *ti, int z_offset, FoundationPart foundation_part)
876 {
877 	/* FIXME: This is not totally valid for some autorail highlights that extend over the edges of the tile. */
878 	if (_vd.foundation[foundation_part] == -1) {
879 		/* draw on real ground */
880 		AddTileSpriteToDraw(image, pal, ti->x, ti->y, ti->z + z_offset);
881 	} else {
882 		/* draw on top of foundation */
883 		AddChildSpriteToFoundation(image, pal, nullptr, foundation_part, 0, -z_offset * ZOOM_LVL_BASE);
884 	}
885 }
886 
887 /**
888  * Draws a selection rectangle on a tile.
889  *
890  * @param ti TileInfo Tile that is being drawn
891  * @param pal Palette to apply.
892  */
DrawTileSelectionRect(const TileInfo * ti,PaletteID pal)893 static void DrawTileSelectionRect(const TileInfo *ti, PaletteID pal)
894 {
895 	if (!IsValidTile(ti->tile)) return;
896 
897 	SpriteID sel;
898 	if (IsHalftileSlope(ti->tileh)) {
899 		Corner halftile_corner = GetHalftileSlopeCorner(ti->tileh);
900 		SpriteID sel2 = SPR_HALFTILE_SELECTION_FLAT + halftile_corner;
901 		DrawSelectionSprite(sel2, pal, ti, 7 + TILE_HEIGHT, FOUNDATION_PART_HALFTILE);
902 
903 		Corner opposite_corner = OppositeCorner(halftile_corner);
904 		if (IsSteepSlope(ti->tileh)) {
905 			sel = SPR_HALFTILE_SELECTION_DOWN;
906 		} else {
907 			sel = ((ti->tileh & SlopeWithOneCornerRaised(opposite_corner)) != 0 ? SPR_HALFTILE_SELECTION_UP : SPR_HALFTILE_SELECTION_FLAT);
908 		}
909 		sel += opposite_corner;
910 	} else {
911 		sel = SPR_SELECT_TILE + SlopeToSpriteOffset(ti->tileh);
912 	}
913 	DrawSelectionSprite(sel, pal, ti, 7, FOUNDATION_PART_NORMAL);
914 }
915 
IsPartOfAutoLine(int px,int py)916 static bool IsPartOfAutoLine(int px, int py)
917 {
918 	px -= _thd.selstart.x;
919 	py -= _thd.selstart.y;
920 
921 	if ((_thd.drawstyle & HT_DRAG_MASK) != HT_LINE) return false;
922 
923 	switch (_thd.drawstyle & HT_DIR_MASK) {
924 		case HT_DIR_X:  return py == 0; // x direction
925 		case HT_DIR_Y:  return px == 0; // y direction
926 		case HT_DIR_HU: return px == -py || px == -py - 16; // horizontal upper
927 		case HT_DIR_HL: return px == -py || px == -py + 16; // horizontal lower
928 		case HT_DIR_VL: return px == py || px == py + 16; // vertical left
929 		case HT_DIR_VR: return px == py || px == py - 16; // vertical right
930 		default:
931 			NOT_REACHED();
932 	}
933 }
934 
935 /* [direction][side] */
936 static const HighLightStyle _autorail_type[6][2] = {
937 	{ HT_DIR_X,  HT_DIR_X },
938 	{ HT_DIR_Y,  HT_DIR_Y },
939 	{ HT_DIR_HU, HT_DIR_HL },
940 	{ HT_DIR_HL, HT_DIR_HU },
941 	{ HT_DIR_VL, HT_DIR_VR },
942 	{ HT_DIR_VR, HT_DIR_VL }
943 };
944 
945 #include "table/autorail.h"
946 
947 /**
948  * Draws autorail highlights.
949  *
950  * @param *ti TileInfo Tile that is being drawn
951  * @param autorail_type Offset into _AutorailTilehSprite[][]
952  */
DrawAutorailSelection(const TileInfo * ti,uint autorail_type)953 static void DrawAutorailSelection(const TileInfo *ti, uint autorail_type)
954 {
955 	SpriteID image;
956 	PaletteID pal;
957 	int offset;
958 
959 	FoundationPart foundation_part = FOUNDATION_PART_NORMAL;
960 	Slope autorail_tileh = RemoveHalftileSlope(ti->tileh);
961 	if (IsHalftileSlope(ti->tileh)) {
962 		static const uint _lower_rail[4] = { 5U, 2U, 4U, 3U };
963 		Corner halftile_corner = GetHalftileSlopeCorner(ti->tileh);
964 		if (autorail_type != _lower_rail[halftile_corner]) {
965 			foundation_part = FOUNDATION_PART_HALFTILE;
966 			/* Here we draw the highlights of the "three-corners-raised"-slope. That looks ok to me. */
967 			autorail_tileh = SlopeWithThreeCornersRaised(OppositeCorner(halftile_corner));
968 		}
969 	}
970 
971 	offset = _AutorailTilehSprite[autorail_tileh][autorail_type];
972 	if (offset >= 0) {
973 		image = SPR_AUTORAIL_BASE + offset;
974 		pal = PAL_NONE;
975 	} else {
976 		image = SPR_AUTORAIL_BASE - offset;
977 		pal = PALETTE_SEL_TILE_RED;
978 	}
979 
980 	DrawSelectionSprite(image, _thd.make_square_red ? PALETTE_SEL_TILE_RED : pal, ti, 7, foundation_part);
981 }
982 
983 enum TileHighlightType {
984 	THT_NONE,
985 	THT_WHITE,
986 	THT_BLUE,
987 	THT_RED,
988 };
989 
990 const Station *_viewport_highlight_station; ///< Currently selected station for coverage area highlight
991 const Town *_viewport_highlight_town;       ///< Currently selected town for coverage area highlight
992 
993 /**
994  * Get tile highlight type of coverage area for a given tile.
995  * @param t Tile that is being drawn
996  * @return Tile highlight type to draw
997  */
GetTileHighlightType(TileIndex t)998 static TileHighlightType GetTileHighlightType(TileIndex t)
999 {
1000 	if (_viewport_highlight_station != nullptr) {
1001 		if (IsTileType(t, MP_STATION) && GetStationIndex(t) == _viewport_highlight_station->index) return THT_WHITE;
1002 		if (_viewport_highlight_station->TileIsInCatchment(t)) return THT_BLUE;
1003 	}
1004 
1005 	if (_viewport_highlight_town != nullptr) {
1006 		if (IsTileType(t, MP_HOUSE)) {
1007 			if (GetTownIndex(t) == _viewport_highlight_town->index) {
1008 				TileHighlightType type = THT_RED;
1009 				for (const Station *st : _viewport_highlight_town->stations_near) {
1010 					if (st->owner != _current_company) continue;
1011 					if (st->TileIsInCatchment(t)) return THT_BLUE;
1012 				}
1013 				return type;
1014 			}
1015 		} else if (IsTileType(t, MP_STATION)) {
1016 			for (const Station *st : _viewport_highlight_town->stations_near) {
1017 				if (st->owner != _current_company) continue;
1018 				if (GetStationIndex(t) == st->index) return THT_WHITE;
1019 			}
1020 		}
1021 	}
1022 
1023 	return THT_NONE;
1024 }
1025 
1026 /**
1027  * Draw tile highlight for coverage area highlight.
1028  * @param *ti TileInfo Tile that is being drawn
1029  * @param tht Highlight type to draw.
1030  */
DrawTileHighlightType(const TileInfo * ti,TileHighlightType tht)1031 static void DrawTileHighlightType(const TileInfo *ti, TileHighlightType tht)
1032 {
1033 	switch (tht) {
1034 		default:
1035 		case THT_NONE: break;
1036 		case THT_WHITE: DrawTileSelectionRect(ti, PAL_NONE); break;
1037 		case THT_BLUE:  DrawTileSelectionRect(ti, PALETTE_SEL_TILE_BLUE); break;
1038 		case THT_RED:   DrawTileSelectionRect(ti, PALETTE_SEL_TILE_RED); break;
1039 	}
1040 }
1041 
1042 /**
1043  * Highlights tiles insede local authority of selected towns.
1044  * @param *ti TileInfo Tile that is being drawn
1045  */
HighlightTownLocalAuthorityTiles(const TileInfo * ti)1046 static void HighlightTownLocalAuthorityTiles(const TileInfo *ti)
1047 {
1048 	/* Going through cases in order of computational time. */
1049 
1050 	if (_town_local_authority_kdtree.Count() == 0) return;
1051 
1052 	/* Tile belongs to town regardless of distance from town. */
1053 	if (GetTileType(ti->tile) == MP_HOUSE) {
1054 		if (!Town::GetByTile(ti->tile)->show_zone) return;
1055 
1056 		DrawTileSelectionRect(ti, PALETTE_CRASH);
1057 		return;
1058 	}
1059 
1060 	/* If the closest town in the highlighted list is far, we can stop searching. */
1061 	TownID tid = _town_local_authority_kdtree.FindNearest(TileX(ti->tile), TileY(ti->tile));
1062 	Town *closest_highlighted_town = Town::Get(tid);
1063 
1064 	if (DistanceManhattan(ti->tile, closest_highlighted_town->xy) >= _settings_game.economy.dist_local_authority) return;
1065 
1066 	/* Tile is inside of the local autrhority distance of a highlighted town,
1067 	   but it is possible that a non-highlighted town is even closer. */
1068 	Town *closest_town = ClosestTownFromTile(ti->tile, _settings_game.economy.dist_local_authority);
1069 
1070 	if (closest_town->show_zone) {
1071 		DrawTileSelectionRect(ti, PALETTE_CRASH);
1072 	}
1073 
1074 }
1075 
1076 /**
1077  * Checks if the specified tile is selected and if so draws selection using correct selectionstyle.
1078  * @param *ti TileInfo Tile that is being drawn
1079  */
DrawTileSelection(const TileInfo * ti)1080 static void DrawTileSelection(const TileInfo *ti)
1081 {
1082 	/* Highlight tiles insede local authority of selected towns. */
1083 	HighlightTownLocalAuthorityTiles(ti);
1084 
1085 	/* Draw a red error square? */
1086 	bool is_redsq = _thd.redsq == ti->tile;
1087 	if (is_redsq) DrawTileSelectionRect(ti, PALETTE_TILE_RED_PULSATING);
1088 
1089 	TileHighlightType tht = GetTileHighlightType(ti->tile);
1090 	DrawTileHighlightType(ti, tht);
1091 
1092 	/* No tile selection active? */
1093 	if ((_thd.drawstyle & HT_DRAG_MASK) == HT_NONE) return;
1094 
1095 	if (_thd.diagonal) { // We're drawing a 45 degrees rotated (diagonal) rectangle
1096 		if (IsInsideRotatedRectangle((int)ti->x, (int)ti->y)) goto draw_inner;
1097 		return;
1098 	}
1099 
1100 	/* Inside the inner area? */
1101 	if (IsInsideBS(ti->x, _thd.pos.x, _thd.size.x) &&
1102 			IsInsideBS(ti->y, _thd.pos.y, _thd.size.y)) {
1103 draw_inner:
1104 		if (_thd.drawstyle & HT_RECT) {
1105 			if (!is_redsq) DrawTileSelectionRect(ti, _thd.make_square_red ? PALETTE_SEL_TILE_RED : PAL_NONE);
1106 		} else if (_thd.drawstyle & HT_POINT) {
1107 			/* Figure out the Z coordinate for the single dot. */
1108 			int z = 0;
1109 			FoundationPart foundation_part = FOUNDATION_PART_NORMAL;
1110 			if (ti->tileh & SLOPE_N) {
1111 				z += TILE_HEIGHT;
1112 				if (RemoveHalftileSlope(ti->tileh) == SLOPE_STEEP_N) z += TILE_HEIGHT;
1113 			}
1114 			if (IsHalftileSlope(ti->tileh)) {
1115 				Corner halftile_corner = GetHalftileSlopeCorner(ti->tileh);
1116 				if ((halftile_corner == CORNER_W) || (halftile_corner == CORNER_E)) z += TILE_HEIGHT;
1117 				if (halftile_corner != CORNER_S) {
1118 					foundation_part = FOUNDATION_PART_HALFTILE;
1119 					if (IsSteepSlope(ti->tileh)) z -= TILE_HEIGHT;
1120 				}
1121 			}
1122 			DrawSelectionSprite(_cur_dpi->zoom <= ZOOM_LVL_DETAIL ? SPR_DOT : SPR_DOT_SMALL, PAL_NONE, ti, z, foundation_part);
1123 		} else if (_thd.drawstyle & HT_RAIL) {
1124 			/* autorail highlight piece under cursor */
1125 			HighLightStyle type = _thd.drawstyle & HT_DIR_MASK;
1126 			assert(type < HT_DIR_END);
1127 			DrawAutorailSelection(ti, _autorail_type[type][0]);
1128 		} else if (IsPartOfAutoLine(ti->x, ti->y)) {
1129 			/* autorail highlighting long line */
1130 			HighLightStyle dir = _thd.drawstyle & HT_DIR_MASK;
1131 			uint side;
1132 
1133 			if (dir == HT_DIR_X || dir == HT_DIR_Y) {
1134 				side = 0;
1135 			} else {
1136 				TileIndex start = TileVirtXY(_thd.selstart.x, _thd.selstart.y);
1137 				side = Delta(Delta(TileX(start), TileX(ti->tile)), Delta(TileY(start), TileY(ti->tile)));
1138 			}
1139 
1140 			DrawAutorailSelection(ti, _autorail_type[dir][side]);
1141 		}
1142 		return;
1143 	}
1144 
1145 	/* Check if it's inside the outer area? */
1146 	if (!is_redsq && (tht == THT_NONE || tht == THT_RED) && _thd.outersize.x > 0 &&
1147 			IsInsideBS(ti->x, _thd.pos.x + _thd.offs.x, _thd.size.x + _thd.outersize.x) &&
1148 			IsInsideBS(ti->y, _thd.pos.y + _thd.offs.y, _thd.size.y + _thd.outersize.y)) {
1149 		/* Draw a blue rect. */
1150 		DrawTileSelectionRect(ti, PALETTE_SEL_TILE_BLUE);
1151 		return;
1152 	}
1153 }
1154 
1155 /**
1156  * Returns the y coordinate in the viewport coordinate system where the given
1157  * tile is painted.
1158  * @param tile Any tile.
1159  * @return The viewport y coordinate where the tile is painted.
1160  */
GetViewportY(Point tile)1161 static int GetViewportY(Point tile)
1162 {
1163 	/* Each increment in X or Y direction moves down by half a tile, i.e. TILE_PIXELS / 2. */
1164 	return (tile.y * (int)(TILE_PIXELS / 2) + tile.x * (int)(TILE_PIXELS / 2) - TilePixelHeightOutsideMap(tile.x, tile.y)) << ZOOM_LVL_SHIFT;
1165 }
1166 
1167 /**
1168  * Add the landscape to the viewport, i.e. all ground tiles and buildings.
1169  */
ViewportAddLandscape()1170 static void ViewportAddLandscape()
1171 {
1172 	assert(_vd.dpi.top <= _vd.dpi.top + _vd.dpi.height);
1173 	assert(_vd.dpi.left <= _vd.dpi.left + _vd.dpi.width);
1174 
1175 	Point upper_left = InverseRemapCoords(_vd.dpi.left, _vd.dpi.top);
1176 	Point upper_right = InverseRemapCoords(_vd.dpi.left + _vd.dpi.width, _vd.dpi.top);
1177 
1178 	/* Transformations between tile coordinates and viewport rows/columns: See vp_column_row
1179 	 *   column = y - x
1180 	 *   row    = x + y
1181 	 *   x      = (row - column) / 2
1182 	 *   y      = (row + column) / 2
1183 	 * Note: (row, columns) pairs are only valid, if they are both even or both odd.
1184 	 */
1185 
1186 	/* Columns overlap with neighbouring columns by a half tile.
1187 	 *  - Left column is column of upper_left (rounded down) and one column to the left.
1188 	 *  - Right column is column of upper_right (rounded up) and one column to the right.
1189 	 * Note: Integer-division does not round down for negative numbers, so ensure rounding with another increment/decrement.
1190 	 */
1191 	int left_column = (upper_left.y - upper_left.x) / (int)TILE_SIZE - 2;
1192 	int right_column = (upper_right.y - upper_right.x) / (int)TILE_SIZE + 2;
1193 
1194 	int potential_bridge_height = ZOOM_LVL_BASE * TILE_HEIGHT * _settings_game.construction.max_bridge_height;
1195 
1196 	/* Rows overlap with neighbouring rows by a half tile.
1197 	 * The first row that could possibly be visible is the row above upper_left (if it is at height 0).
1198 	 * Due to integer-division not rounding down for negative numbers, we need another decrement.
1199 	 */
1200 	int row = (upper_left.x + upper_left.y) / (int)TILE_SIZE - 2;
1201 	bool last_row = false;
1202 	for (; !last_row; row++) {
1203 		last_row = true;
1204 		for (int column = left_column; column <= right_column; column++) {
1205 			/* Valid row/column? */
1206 			if ((row + column) % 2 != 0) continue;
1207 
1208 			Point tilecoord;
1209 			tilecoord.x = (row - column) / 2;
1210 			tilecoord.y = (row + column) / 2;
1211 			assert(column == tilecoord.y - tilecoord.x);
1212 			assert(row == tilecoord.y + tilecoord.x);
1213 
1214 			TileType tile_type;
1215 			TileInfo tile_info;
1216 			_cur_ti = &tile_info;
1217 			tile_info.x = tilecoord.x * TILE_SIZE; // FIXME tile_info should use signed integers
1218 			tile_info.y = tilecoord.y * TILE_SIZE;
1219 
1220 			if (IsInsideBS(tilecoord.x, 0, MapSizeX()) && IsInsideBS(tilecoord.y, 0, MapSizeY())) {
1221 				/* This includes the south border at MapMaxX / MapMaxY. When terraforming we still draw tile selections there. */
1222 				tile_info.tile = TileXY(tilecoord.x, tilecoord.y);
1223 				tile_type = GetTileType(tile_info.tile);
1224 			} else {
1225 				tile_info.tile = INVALID_TILE;
1226 				tile_type = MP_VOID;
1227 			}
1228 
1229 			if (tile_type != MP_VOID) {
1230 				/* We are inside the map => paint landscape. */
1231 				tile_info.tileh = GetTilePixelSlope(tile_info.tile, &tile_info.z);
1232 			} else {
1233 				/* We are outside the map => paint black. */
1234 				tile_info.tileh = GetTilePixelSlopeOutsideMap(tilecoord.x, tilecoord.y, &tile_info.z);
1235 			}
1236 
1237 			int viewport_y = GetViewportY(tilecoord);
1238 
1239 			if (viewport_y + MAX_TILE_EXTENT_BOTTOM < _vd.dpi.top) {
1240 				/* The tile in this column is not visible yet.
1241 				 * Tiles in other columns may be visible, but we need more rows in any case. */
1242 				last_row = false;
1243 				continue;
1244 			}
1245 
1246 			int min_visible_height = viewport_y - (_vd.dpi.top + _vd.dpi.height);
1247 			bool tile_visible = min_visible_height <= 0;
1248 
1249 			if (tile_type != MP_VOID) {
1250 				/* Is tile with buildings visible? */
1251 				if (min_visible_height < MAX_TILE_EXTENT_TOP) tile_visible = true;
1252 
1253 				if (IsBridgeAbove(tile_info.tile)) {
1254 					/* Is the bridge visible? */
1255 					TileIndex bridge_tile = GetNorthernBridgeEnd(tile_info.tile);
1256 					int bridge_height = ZOOM_LVL_BASE * (GetBridgePixelHeight(bridge_tile) - TilePixelHeight(tile_info.tile));
1257 					if (min_visible_height < bridge_height + MAX_TILE_EXTENT_TOP) tile_visible = true;
1258 				}
1259 
1260 				/* Would a higher bridge on a more southern tile be visible?
1261 				 * If yes, we need to loop over more rows to possibly find one. */
1262 				if (min_visible_height < potential_bridge_height + MAX_TILE_EXTENT_TOP) last_row = false;
1263 			} else {
1264 				/* Outside of map. If we are on the north border of the map, there may still be a bridge visible,
1265 				 * so we need to loop over more rows to possibly find one. */
1266 				if ((tilecoord.x <= 0 || tilecoord.y <= 0) && min_visible_height < potential_bridge_height + MAX_TILE_EXTENT_TOP) last_row = false;
1267 			}
1268 
1269 			if (tile_visible) {
1270 				last_row = false;
1271 				_vd.foundation_part = FOUNDATION_PART_NONE;
1272 				_vd.foundation[0] = -1;
1273 				_vd.foundation[1] = -1;
1274 				_vd.last_foundation_child[0] = nullptr;
1275 				_vd.last_foundation_child[1] = nullptr;
1276 
1277 				_tile_type_procs[tile_type]->draw_tile_proc(&tile_info);
1278 				if (tile_info.tile != INVALID_TILE) DrawTileSelection(&tile_info);
1279 			}
1280 		}
1281 	}
1282 }
1283 
1284 /**
1285  * Add a string to draw in the viewport
1286  * @param dpi current viewport area
1287  * @param small_from Zoomlevel from when the small font should be used
1288  * @param sign sign position and dimension
1289  * @param string_normal String for normal and 2x zoom level
1290  * @param string_small String for 4x and 8x zoom level
1291  * @param string_small_shadow Shadow string for 4x and 8x zoom level; or #STR_NULL if no shadow
1292  * @param colour colour of the sign background; or INVALID_COLOUR if transparent
1293  */
ViewportAddString(const DrawPixelInfo * dpi,ZoomLevel small_from,const ViewportSign * sign,StringID string_normal,StringID string_small,StringID string_small_shadow,uint64 params_1,uint64 params_2,Colours colour)1294 void ViewportAddString(const DrawPixelInfo *dpi, ZoomLevel small_from, const ViewportSign *sign, StringID string_normal, StringID string_small, StringID string_small_shadow, uint64 params_1, uint64 params_2, Colours colour)
1295 {
1296 	bool small = dpi->zoom >= small_from;
1297 
1298 	int left   = dpi->left;
1299 	int top    = dpi->top;
1300 	int right  = left + dpi->width;
1301 	int bottom = top + dpi->height;
1302 
1303 	int sign_height     = ScaleByZoom(VPSM_TOP + FONT_HEIGHT_NORMAL + VPSM_BOTTOM, dpi->zoom);
1304 	int sign_half_width = ScaleByZoom((small ? sign->width_small : sign->width_normal) / 2, dpi->zoom);
1305 
1306 	if (bottom < sign->top ||
1307 			top   > sign->top + sign_height ||
1308 			right < sign->center - sign_half_width ||
1309 			left  > sign->center + sign_half_width) {
1310 		return;
1311 	}
1312 
1313 	if (!small) {
1314 		AddStringToDraw(sign->center - sign_half_width, sign->top, string_normal, params_1, params_2, colour, sign->width_normal);
1315 	} else {
1316 		int shadow_offset = 0;
1317 		if (string_small_shadow != STR_NULL) {
1318 			shadow_offset = 4;
1319 			AddStringToDraw(sign->center - sign_half_width + shadow_offset, sign->top, string_small_shadow, params_1, params_2, INVALID_COLOUR, sign->width_small);
1320 		}
1321 		AddStringToDraw(sign->center - sign_half_width, sign->top - shadow_offset, string_small, params_1, params_2,
1322 				colour, sign->width_small | 0x8000);
1323 	}
1324 }
1325 
ExpandRectWithViewportSignMargins(Rect r,ZoomLevel zoom)1326 static Rect ExpandRectWithViewportSignMargins(Rect r, ZoomLevel zoom)
1327 {
1328 	/* Pessimistically always use normal font, but also assume small font is never larger in either dimension */
1329 	const int fh = FONT_HEIGHT_NORMAL;
1330 	const int max_tw = _viewport_sign_maxwidth / 2 + 1;
1331 	const int expand_y = ScaleByZoom(VPSM_TOP + fh + VPSM_BOTTOM, zoom);
1332 	const int expand_x = ScaleByZoom(VPSM_LEFT + max_tw + VPSM_RIGHT, zoom);
1333 
1334 	r.left -= expand_x;
1335 	r.right += expand_x;
1336 	r.top -= expand_y;
1337 	r.bottom += expand_y;
1338 
1339 	return r;
1340 }
1341 
ViewportAddKdtreeSigns(DrawPixelInfo * dpi)1342 static void ViewportAddKdtreeSigns(DrawPixelInfo *dpi)
1343 {
1344 	Rect search_rect{ dpi->left, dpi->top, dpi->left + dpi->width, dpi->top + dpi->height };
1345 	search_rect = ExpandRectWithViewportSignMargins(search_rect, dpi->zoom);
1346 
1347 	bool show_stations = HasBit(_display_opt, DO_SHOW_STATION_NAMES) && _game_mode != GM_MENU;
1348 	bool show_waypoints = HasBit(_display_opt, DO_SHOW_WAYPOINT_NAMES) && _game_mode != GM_MENU;
1349 	bool show_towns = HasBit(_display_opt, DO_SHOW_TOWN_NAMES) && _game_mode != GM_MENU;
1350 	bool show_signs = HasBit(_display_opt, DO_SHOW_SIGNS) && !IsInvisibilitySet(TO_SIGNS);
1351 	bool show_competitors = HasBit(_display_opt, DO_SHOW_COMPETITOR_SIGNS);
1352 
1353 	const BaseStation *st;
1354 	const Sign *si;
1355 
1356 	/* Collect all the items first and draw afterwards, to ensure layering */
1357 	std::vector<const BaseStation *> stations;
1358 	std::vector<const Town *> towns;
1359 	std::vector<const Sign *> signs;
1360 
1361 	_viewport_sign_kdtree.FindContained(search_rect.left, search_rect.top, search_rect.right, search_rect.bottom, [&](const ViewportSignKdtreeItem & item) {
1362 		switch (item.type) {
1363 			case ViewportSignKdtreeItem::VKI_STATION:
1364 				if (!show_stations) break;
1365 				st = BaseStation::Get(item.id.station);
1366 
1367 				/* Don't draw if station is owned by another company and competitor station names are hidden. Stations owned by none are never ignored. */
1368 				if (!show_competitors && _local_company != st->owner && st->owner != OWNER_NONE) break;
1369 
1370 				stations.push_back(st);
1371 				break;
1372 
1373 			case ViewportSignKdtreeItem::VKI_WAYPOINT:
1374 				if (!show_waypoints) break;
1375 				st = BaseStation::Get(item.id.station);
1376 
1377 				/* Don't draw if station is owned by another company and competitor station names are hidden. Stations owned by none are never ignored. */
1378 				if (!show_competitors && _local_company != st->owner && st->owner != OWNER_NONE) break;
1379 
1380 				stations.push_back(st);
1381 				break;
1382 
1383 			case ViewportSignKdtreeItem::VKI_TOWN:
1384 				if (!show_towns) break;
1385 				towns.push_back(Town::Get(item.id.town));
1386 				break;
1387 
1388 			case ViewportSignKdtreeItem::VKI_SIGN:
1389 				if (!show_signs) break;
1390 				si = Sign::Get(item.id.sign);
1391 
1392 				/* Don't draw if sign is owned by another company and competitor signs should be hidden.
1393 				* Note: It is intentional that also signs owned by OWNER_NONE are hidden. Bankrupt
1394 				* companies can leave OWNER_NONE signs after them. */
1395 				if (!show_competitors && _local_company != si->owner && si->owner != OWNER_DEITY) break;
1396 
1397 				signs.push_back(si);
1398 				break;
1399 
1400 			default:
1401 				NOT_REACHED();
1402 		}
1403 	});
1404 
1405 	/* Layering order (bottom to top): Town names, signs, stations */
1406 
1407 	for (const auto *t : towns) {
1408 		ViewportAddString(dpi, ZOOM_LVL_OUT_16X, &t->cache.sign,
1409 			_settings_client.gui.population_in_label ? STR_VIEWPORT_TOWN_POP : STR_VIEWPORT_TOWN,
1410 			STR_VIEWPORT_TOWN_TINY_WHITE, STR_VIEWPORT_TOWN_TINY_BLACK,
1411 			t->index, t->cache.population);
1412 	}
1413 
1414 	for (const auto *si : signs) {
1415 		ViewportAddString(dpi, ZOOM_LVL_OUT_16X, &si->sign,
1416 			STR_WHITE_SIGN,
1417 			(IsTransparencySet(TO_SIGNS) || si->owner == OWNER_DEITY) ? STR_VIEWPORT_SIGN_SMALL_WHITE : STR_VIEWPORT_SIGN_SMALL_BLACK, STR_NULL,
1418 			si->index, 0, (si->owner == OWNER_NONE) ? COLOUR_GREY : (si->owner == OWNER_DEITY ? INVALID_COLOUR : _company_colours[si->owner]));
1419 	}
1420 
1421 	for (const auto *st : stations) {
1422 		if (Station::IsExpected(st)) {
1423 			/* Station */
1424 			ViewportAddString(dpi, ZOOM_LVL_OUT_16X, &st->sign,
1425 				STR_VIEWPORT_STATION, STR_VIEWPORT_STATION_TINY, STR_NULL,
1426 				st->index, st->facilities, (st->owner == OWNER_NONE || !st->IsInUse()) ? COLOUR_GREY : _company_colours[st->owner]);
1427 		} else {
1428 			/* Waypoint */
1429 			ViewportAddString(dpi, ZOOM_LVL_OUT_16X, &st->sign,
1430 				STR_VIEWPORT_WAYPOINT, STR_VIEWPORT_WAYPOINT_TINY, STR_NULL,
1431 				st->index, st->facilities, (st->owner == OWNER_NONE || !st->IsInUse()) ? COLOUR_GREY : _company_colours[st->owner]);
1432 		}
1433 	}
1434 }
1435 
1436 
1437 /**
1438  * Update the position of the viewport sign.
1439  * @param center the (preferred) center of the viewport sign
1440  * @param top    the new top of the sign
1441  * @param str    the string to show in the sign
1442  * @param str_small the string to show when zoomed out. STR_NULL means same as \a str
1443  */
UpdatePosition(int center,int top,StringID str,StringID str_small)1444 void ViewportSign::UpdatePosition(int center, int top, StringID str, StringID str_small)
1445 {
1446 	if (this->width_normal != 0) this->MarkDirty();
1447 
1448 	this->top = top;
1449 
1450 	char buffer[DRAW_STRING_BUFFER];
1451 
1452 	GetString(buffer, str, lastof(buffer));
1453 	this->width_normal = VPSM_LEFT + Align(GetStringBoundingBox(buffer).width, 2) + VPSM_RIGHT;
1454 	this->center = center;
1455 
1456 	/* zoomed out version */
1457 	if (str_small != STR_NULL) {
1458 		GetString(buffer, str_small, lastof(buffer));
1459 	}
1460 	this->width_small = VPSM_LEFT + Align(GetStringBoundingBox(buffer, FS_SMALL).width, 2) + VPSM_RIGHT;
1461 
1462 	this->MarkDirty();
1463 }
1464 
1465 /**
1466  * Mark the sign dirty in all viewports.
1467  * @param maxzoom Maximum %ZoomLevel at which the text is visible.
1468  *
1469  * @ingroup dirty
1470  */
MarkDirty(ZoomLevel maxzoom) const1471 void ViewportSign::MarkDirty(ZoomLevel maxzoom) const
1472 {
1473 	Rect zoomlevels[ZOOM_LVL_COUNT];
1474 
1475 	for (ZoomLevel zoom = ZOOM_LVL_BEGIN; zoom != ZOOM_LVL_END; zoom++) {
1476 		/* FIXME: This doesn't switch to width_small when appropriate. */
1477 		zoomlevels[zoom].left   = this->center - ScaleByZoom(this->width_normal / 2 + 1, zoom);
1478 		zoomlevels[zoom].top    = this->top    - ScaleByZoom(1, zoom);
1479 		zoomlevels[zoom].right  = this->center + ScaleByZoom(this->width_normal / 2 + 1, zoom);
1480 		zoomlevels[zoom].bottom = this->top    + ScaleByZoom(VPSM_TOP + FONT_HEIGHT_NORMAL + VPSM_BOTTOM + 1, zoom);
1481 	}
1482 
1483 	for (const Window *w : Window::Iterate()) {
1484 		Viewport *vp = w->viewport;
1485 		if (vp != nullptr && vp->zoom <= maxzoom) {
1486 			assert(vp->width != 0);
1487 			Rect &zl = zoomlevels[vp->zoom];
1488 			MarkViewportDirty(vp, zl.left, zl.top, zl.right, zl.bottom);
1489 		}
1490 	}
1491 }
1492 
ViewportDrawTileSprites(const TileSpriteToDrawVector * tstdv)1493 static void ViewportDrawTileSprites(const TileSpriteToDrawVector *tstdv)
1494 {
1495 	for (const TileSpriteToDraw &ts : *tstdv) {
1496 		DrawSpriteViewport(ts.image, ts.pal, ts.x, ts.y, ts.sub);
1497 	}
1498 }
1499 
1500 /** This fallback sprite checker always exists. */
ViewportSortParentSpritesChecker()1501 static bool ViewportSortParentSpritesChecker()
1502 {
1503 	return true;
1504 }
1505 
1506 /** Sort parent sprites pointer array replicating the way original sorter did it. */
ViewportSortParentSprites(ParentSpriteToSortVector * psdv)1507 static void ViewportSortParentSprites(ParentSpriteToSortVector *psdv)
1508 {
1509 	if (psdv->size() < 2) return;
1510 
1511 	/* We rely on sprites being, for the most part, already ordered.
1512 	 * So we don't need to move many of them and can keep track of their
1513 	 * order efficiently by using stack. We always move sprites to the front
1514 	 * of the current position, i.e. to the top of the stack.
1515 	 * Also use special constants to indicate sorting state without
1516 	 * adding extra fields to ParentSpriteToDraw structure.
1517 	 */
1518 	const uint32 ORDER_COMPARED = UINT32_MAX; // Sprite was compared but we still need to compare the ones preceding it
1519 	const uint32 ORDER_RETURNED = UINT32_MAX - 1; // Makr sorted sprite in case there are other occurrences of it in the stack
1520 	std::stack<ParentSpriteToDraw *> sprite_order;
1521 	uint32 next_order = 0;
1522 
1523 	std::forward_list<std::pair<int64, ParentSpriteToDraw *>> sprite_list;  // We store sprites in a list sorted by xmin+ymin
1524 
1525 	/* Initialize sprite list and order. */
1526 	for (auto p = psdv->rbegin(); p != psdv->rend(); p++) {
1527 		sprite_list.push_front(std::make_pair((*p)->xmin + (*p)->ymin, *p));
1528 		sprite_order.push(*p);
1529 		(*p)->order = next_order++;
1530 	}
1531 
1532 	sprite_list.sort();
1533 
1534 	std::vector<ParentSpriteToDraw *> preceding;  // Temporarily stores sprites that precede current and their position in the list
1535 	auto preceding_prev = sprite_list.begin(); // Store iterator in case we need to delete a single preciding sprite
1536 	auto out = psdv->begin();  // Iterator to output sorted sprites
1537 
1538 	while (!sprite_order.empty()) {
1539 
1540 		auto s = sprite_order.top();
1541 		sprite_order.pop();
1542 
1543 		/* Sprite is already sorted, ignore it. */
1544 		if (s->order == ORDER_RETURNED) continue;
1545 
1546 		/* Sprite was already compared, just need to output it. */
1547 		if (s->order == ORDER_COMPARED) {
1548 			*(out++) = s;
1549 			s->order = ORDER_RETURNED;
1550 			continue;
1551 		}
1552 
1553 		preceding.clear();
1554 
1555 		/* We only need sprites with xmin <= s->xmax && ymin <= s->ymax && zmin <= s->zmax
1556 		 * So by iterating sprites with xmin + ymin <= s->xmax + s->ymax
1557 		 * we get all we need and some more that we filter out later.
1558 		 * We don't include zmin into the sum as there are usually more neighbors on x and y than z
1559 		 * so including it will actually increase the amount of false positives.
1560 		 * Also min coordinates can be > max so using max(xmin, xmax) + max(ymin, ymax)
1561 		 * to ensure that we iterate the current sprite as we need to remove it from the list.
1562 		 */
1563 		auto ssum = std::max(s->xmax, s->xmin) + std::max(s->ymax, s->ymin);
1564 		auto prev = sprite_list.before_begin();
1565 		auto x = sprite_list.begin();
1566 		while (x != sprite_list.end() && ((*x).first <= ssum)) {
1567 			auto p = (*x).second;
1568 			if (p == s) {
1569 				/* We found the current sprite, remove it and move on. */
1570 				x = sprite_list.erase_after(prev);
1571 				continue;
1572 			}
1573 
1574 			auto p_prev = prev;
1575 			prev = x++;
1576 
1577 			if (s->xmax < p->xmin || s->ymax < p->ymin || s->zmax < p->zmin) continue;
1578 			if (s->xmin <= p->xmax && // overlap in X?
1579 					s->ymin <= p->ymax && // overlap in Y?
1580 					s->zmin <= p->zmax) { // overlap in Z?
1581 				if (s->xmin + s->xmax + s->ymin + s->ymax + s->zmin + s->zmax <=
1582 						p->xmin + p->xmax + p->ymin + p->ymax + p->zmin + p->zmax) {
1583 					continue;
1584 				}
1585 			}
1586 			preceding.push_back(p);
1587 			preceding_prev = p_prev;
1588 		}
1589 
1590 		if (preceding.empty()) {
1591 			/* No preceding sprites, add current one to the output */
1592 			*(out++) = s;
1593 			s->order = ORDER_RETURNED;
1594 			continue;
1595 		}
1596 
1597 		/* Optimization for the case when we only have 1 sprite to move. */
1598 		if (preceding.size() == 1) {
1599 			auto p = preceding[0];
1600 			/* We can only output the preceding sprite if there can't be any other sprites preceding it. */
1601 			if (p->xmax <= s->xmax && p->ymax <= s->ymax && p->zmax <= s->zmax) {
1602 				p->order = ORDER_RETURNED;
1603 				s->order = ORDER_RETURNED;
1604 				sprite_list.erase_after(preceding_prev);
1605 				*(out++) = p;
1606 				*(out++) = s;
1607 				continue;
1608 			}
1609 		}
1610 
1611 		/* Sort all preceding sprites by order and assign new orders in reverse (as original sorter did). */
1612 		std::sort(preceding.begin(), preceding.end(), [](const ParentSpriteToDraw *a, const ParentSpriteToDraw *b) {
1613 			return a->order > b->order;
1614 		});
1615 
1616 		s->order = ORDER_COMPARED;
1617 		sprite_order.push(s);  // Still need to output so push it back for now
1618 
1619 		for (auto p: preceding) {
1620 			p->order = next_order++;
1621 			sprite_order.push(p);
1622 		}
1623 	}
1624 }
1625 
1626 
ViewportDrawParentSprites(const ParentSpriteToSortVector * psd,const ChildScreenSpriteToDrawVector * csstdv)1627 static void ViewportDrawParentSprites(const ParentSpriteToSortVector *psd, const ChildScreenSpriteToDrawVector *csstdv)
1628 {
1629 	for (const ParentSpriteToDraw *ps : *psd) {
1630 		if (ps->image != SPR_EMPTY_BOUNDING_BOX) DrawSpriteViewport(ps->image, ps->pal, ps->x, ps->y, ps->sub);
1631 
1632 		int child_idx = ps->first_child;
1633 		while (child_idx >= 0) {
1634 			const ChildScreenSpriteToDraw *cs = csstdv->data() + child_idx;
1635 			child_idx = cs->next;
1636 			DrawSpriteViewport(cs->image, cs->pal, ps->left + cs->x, ps->top + cs->y, cs->sub);
1637 		}
1638 	}
1639 }
1640 
1641 /**
1642  * Draws the bounding boxes of all ParentSprites
1643  * @param psd Array of ParentSprites
1644  */
ViewportDrawBoundingBoxes(const ParentSpriteToSortVector * psd)1645 static void ViewportDrawBoundingBoxes(const ParentSpriteToSortVector *psd)
1646 {
1647 	for (const ParentSpriteToDraw *ps : *psd) {
1648 		Point pt1 = RemapCoords(ps->xmax + 1, ps->ymax + 1, ps->zmax + 1); // top front corner
1649 		Point pt2 = RemapCoords(ps->xmin    , ps->ymax + 1, ps->zmax + 1); // top left corner
1650 		Point pt3 = RemapCoords(ps->xmax + 1, ps->ymin    , ps->zmax + 1); // top right corner
1651 		Point pt4 = RemapCoords(ps->xmax + 1, ps->ymax + 1, ps->zmin    ); // bottom front corner
1652 
1653 		DrawBox(        pt1.x,         pt1.y,
1654 		        pt2.x - pt1.x, pt2.y - pt1.y,
1655 		        pt3.x - pt1.x, pt3.y - pt1.y,
1656 		        pt4.x - pt1.x, pt4.y - pt1.y);
1657 	}
1658 }
1659 
1660 /**
1661  * Draw/colour the blocks that have been redrawn.
1662  */
ViewportDrawDirtyBlocks()1663 static void ViewportDrawDirtyBlocks()
1664 {
1665 	Blitter *blitter = BlitterFactory::GetCurrentBlitter();
1666 	const DrawPixelInfo *dpi = _cur_dpi;
1667 	void *dst;
1668 	int right =  UnScaleByZoom(dpi->width,  dpi->zoom);
1669 	int bottom = UnScaleByZoom(dpi->height, dpi->zoom);
1670 
1671 	int colour = _string_colourmap[_dirty_block_colour & 0xF];
1672 
1673 	dst = dpi->dst_ptr;
1674 
1675 	byte bo = UnScaleByZoom(dpi->left + dpi->top, dpi->zoom) & 1;
1676 	do {
1677 		for (int i = (bo ^= 1); i < right; i += 2) blitter->SetPixel(dst, i, 0, (uint8)colour);
1678 		dst = blitter->MoveTo(dst, 0, 1);
1679 	} while (--bottom > 0);
1680 }
1681 
ViewportDrawStrings(ZoomLevel zoom,const StringSpriteToDrawVector * sstdv)1682 static void ViewportDrawStrings(ZoomLevel zoom, const StringSpriteToDrawVector *sstdv)
1683 {
1684 	for (const StringSpriteToDraw &ss : *sstdv) {
1685 		TextColour colour = TC_BLACK;
1686 		bool small = HasBit(ss.width, 15);
1687 		int w = GB(ss.width, 0, 15);
1688 		int x = UnScaleByZoom(ss.x, zoom);
1689 		int y = UnScaleByZoom(ss.y, zoom);
1690 		int h = VPSM_TOP + (small ? FONT_HEIGHT_SMALL : FONT_HEIGHT_NORMAL) + VPSM_BOTTOM;
1691 
1692 		SetDParam(0, ss.params[0]);
1693 		SetDParam(1, ss.params[1]);
1694 
1695 		if (ss.colour != INVALID_COLOUR) {
1696 			/* Do not draw signs nor station names if they are set invisible */
1697 			if (IsInvisibilitySet(TO_SIGNS) && ss.string != STR_WHITE_SIGN) continue;
1698 
1699 			if (IsTransparencySet(TO_SIGNS) && ss.string != STR_WHITE_SIGN) {
1700 				/* Don't draw the rectangle.
1701 				 * Real colours need the TC_IS_PALETTE_COLOUR flag.
1702 				 * Otherwise colours from _string_colourmap are assumed. */
1703 				colour = (TextColour)_colour_gradient[ss.colour][6] | TC_IS_PALETTE_COLOUR;
1704 			} else {
1705 				/* Draw the rectangle if 'transparent station signs' is off,
1706 				 * or if we are drawing a general text sign (STR_WHITE_SIGN). */
1707 				DrawFrameRect(
1708 					x, y, x + w, y + h, ss.colour,
1709 					IsTransparencySet(TO_SIGNS) ? FR_TRANSPARENT : FR_NONE
1710 				);
1711 			}
1712 		}
1713 
1714 		DrawString(x + VPSM_LEFT, x + w - 1 - VPSM_RIGHT, y + VPSM_TOP, ss.string, colour, SA_HOR_CENTER);
1715 	}
1716 }
1717 
ViewportDoDraw(const Viewport * vp,int left,int top,int right,int bottom)1718 void ViewportDoDraw(const Viewport *vp, int left, int top, int right, int bottom)
1719 {
1720 	DrawPixelInfo *old_dpi = _cur_dpi;
1721 	_cur_dpi = &_vd.dpi;
1722 
1723 	_vd.dpi.zoom = vp->zoom;
1724 	int mask = ScaleByZoom(-1, vp->zoom);
1725 
1726 	_vd.combine_sprites = SPRITE_COMBINE_NONE;
1727 
1728 	_vd.dpi.width = (right - left) & mask;
1729 	_vd.dpi.height = (bottom - top) & mask;
1730 	_vd.dpi.left = left & mask;
1731 	_vd.dpi.top = top & mask;
1732 	_vd.dpi.pitch = old_dpi->pitch;
1733 	_vd.last_child = nullptr;
1734 
1735 	int x = UnScaleByZoom(_vd.dpi.left - (vp->virtual_left & mask), vp->zoom) + vp->left;
1736 	int y = UnScaleByZoom(_vd.dpi.top - (vp->virtual_top & mask), vp->zoom) + vp->top;
1737 
1738 	_vd.dpi.dst_ptr = BlitterFactory::GetCurrentBlitter()->MoveTo(old_dpi->dst_ptr, x - old_dpi->left, y - old_dpi->top);
1739 
1740 	ViewportAddLandscape();
1741 	ViewportAddVehicles(&_vd.dpi);
1742 
1743 	ViewportAddKdtreeSigns(&_vd.dpi);
1744 
1745 	DrawTextEffects(&_vd.dpi);
1746 
1747 	if (_vd.tile_sprites_to_draw.size() != 0) ViewportDrawTileSprites(&_vd.tile_sprites_to_draw);
1748 
1749 	for (auto &psd : _vd.parent_sprites_to_draw) {
1750 		_vd.parent_sprites_to_sort.push_back(&psd);
1751 	}
1752 
1753 	_vp_sprite_sorter(&_vd.parent_sprites_to_sort);
1754 	ViewportDrawParentSprites(&_vd.parent_sprites_to_sort, &_vd.child_screen_sprites_to_draw);
1755 
1756 	if (_draw_bounding_boxes) ViewportDrawBoundingBoxes(&_vd.parent_sprites_to_sort);
1757 	if (_draw_dirty_blocks) ViewportDrawDirtyBlocks();
1758 
1759 	DrawPixelInfo dp = _vd.dpi;
1760 	ZoomLevel zoom = _vd.dpi.zoom;
1761 	dp.zoom = ZOOM_LVL_NORMAL;
1762 	dp.width = UnScaleByZoom(dp.width, zoom);
1763 	dp.height = UnScaleByZoom(dp.height, zoom);
1764 	_cur_dpi = &dp;
1765 
1766 	if (vp->overlay != nullptr && vp->overlay->GetCargoMask() != 0 && vp->overlay->GetCompanyMask() != 0) {
1767 		/* translate to window coordinates */
1768 		dp.left = x;
1769 		dp.top = y;
1770 		vp->overlay->Draw(&dp);
1771 	}
1772 
1773 	if (_vd.string_sprites_to_draw.size() != 0) {
1774 		/* translate to world coordinates */
1775 		dp.left = UnScaleByZoom(_vd.dpi.left, zoom);
1776 		dp.top = UnScaleByZoom(_vd.dpi.top, zoom);
1777 		ViewportDrawStrings(zoom, &_vd.string_sprites_to_draw);
1778 	}
1779 
1780 	_cur_dpi = old_dpi;
1781 
1782 	_vd.string_sprites_to_draw.clear();
1783 	_vd.tile_sprites_to_draw.clear();
1784 	_vd.parent_sprites_to_draw.clear();
1785 	_vd.parent_sprites_to_sort.clear();
1786 	_vd.child_screen_sprites_to_draw.clear();
1787 }
1788 
ViewportDraw(const Viewport * vp,int left,int top,int right,int bottom)1789 static inline void ViewportDraw(const Viewport *vp, int left, int top, int right, int bottom)
1790 {
1791 	if (right <= vp->left || bottom <= vp->top) return;
1792 
1793 	if (left >= vp->left + vp->width) return;
1794 
1795 	if (left < vp->left) left = vp->left;
1796 	if (right > vp->left + vp->width) right = vp->left + vp->width;
1797 
1798 	if (top >= vp->top + vp->height) return;
1799 
1800 	if (top < vp->top) top = vp->top;
1801 	if (bottom > vp->top + vp->height) bottom = vp->top + vp->height;
1802 
1803 	ViewportDoDraw(vp,
1804 		ScaleByZoom(left - vp->left, vp->zoom) + vp->virtual_left,
1805 		ScaleByZoom(top - vp->top, vp->zoom) + vp->virtual_top,
1806 		ScaleByZoom(right - vp->left, vp->zoom) + vp->virtual_left,
1807 		ScaleByZoom(bottom - vp->top, vp->zoom) + vp->virtual_top
1808 	);
1809 }
1810 
1811 /**
1812  * Draw the viewport of this window.
1813  */
DrawViewport() const1814 void Window::DrawViewport() const
1815 {
1816 	PerformanceAccumulator framerate(PFE_DRAWWORLD);
1817 
1818 	DrawPixelInfo *dpi = _cur_dpi;
1819 
1820 	dpi->left += this->left;
1821 	dpi->top += this->top;
1822 
1823 	ViewportDraw(this->viewport, dpi->left, dpi->top, dpi->left + dpi->width, dpi->top + dpi->height);
1824 
1825 	dpi->left -= this->left;
1826 	dpi->top -= this->top;
1827 }
1828 
1829 /**
1830  * Ensure that a given viewport has a valid scroll position.
1831  *
1832  * There must be a visible piece of the map in the center of the viewport.
1833  * If there isn't, the viewport will be scrolled to nearest such location.
1834  *
1835  * @param vp The viewport.
1836  * @param[in,out] scroll_x Viewport X scroll.
1837  * @param[in,out] scroll_y Viewport Y scroll.
1838  */
ClampViewportToMap(const Viewport * vp,int * scroll_x,int * scroll_y)1839 static inline void ClampViewportToMap(const Viewport *vp, int *scroll_x, int *scroll_y)
1840 {
1841 	/* Centre of the viewport is hot spot. */
1842 	Point pt = {
1843 		*scroll_x + vp->virtual_width / 2,
1844 		*scroll_y + vp->virtual_height / 2
1845 	};
1846 
1847 	/* Find nearest tile that is within borders of the map. */
1848 	bool clamped;
1849 	pt = InverseRemapCoords2(pt.x, pt.y, true, &clamped);
1850 
1851 	if (clamped) {
1852 		/* Convert back to viewport coordinates and remove centering. */
1853 		pt = RemapCoords2(pt.x, pt.y);
1854 		*scroll_x = pt.x - vp->virtual_width / 2;
1855 		*scroll_y = pt.y - vp->virtual_height / 2;
1856 	}
1857 }
1858 
1859 /**
1860  * Update the viewport position being displayed.
1861  * @param w %Window owning the viewport.
1862  */
UpdateViewportPosition(Window * w)1863 void UpdateViewportPosition(Window *w)
1864 {
1865 	const Viewport *vp = w->viewport;
1866 
1867 	if (w->viewport->follow_vehicle != INVALID_VEHICLE) {
1868 		const Vehicle *veh = Vehicle::Get(w->viewport->follow_vehicle);
1869 		Point pt = MapXYZToViewport(vp, veh->x_pos, veh->y_pos, veh->z_pos);
1870 
1871 		w->viewport->scrollpos_x = pt.x;
1872 		w->viewport->scrollpos_y = pt.y;
1873 		SetViewportPosition(w, pt.x, pt.y);
1874 	} else {
1875 		/* Ensure the destination location is within the map */
1876 		ClampViewportToMap(vp, &w->viewport->dest_scrollpos_x, &w->viewport->dest_scrollpos_y);
1877 
1878 		int delta_x = w->viewport->dest_scrollpos_x - w->viewport->scrollpos_x;
1879 		int delta_y = w->viewport->dest_scrollpos_y - w->viewport->scrollpos_y;
1880 
1881 		bool update_overlay = false;
1882 		if (delta_x != 0 || delta_y != 0) {
1883 			if (_settings_client.gui.smooth_scroll) {
1884 				int max_scroll = ScaleByMapSize1D(512 * ZOOM_LVL_BASE);
1885 				/* Not at our desired position yet... */
1886 				w->viewport->scrollpos_x += Clamp(DivAwayFromZero(delta_x, 4), -max_scroll, max_scroll);
1887 				w->viewport->scrollpos_y += Clamp(DivAwayFromZero(delta_y, 4), -max_scroll, max_scroll);
1888 			} else {
1889 				w->viewport->scrollpos_x = w->viewport->dest_scrollpos_x;
1890 				w->viewport->scrollpos_y = w->viewport->dest_scrollpos_y;
1891 			}
1892 			update_overlay = (w->viewport->scrollpos_x == w->viewport->dest_scrollpos_x &&
1893 								w->viewport->scrollpos_y == w->viewport->dest_scrollpos_y);
1894 		}
1895 
1896 		ClampViewportToMap(vp, &w->viewport->scrollpos_x, &w->viewport->scrollpos_y);
1897 
1898 		SetViewportPosition(w, w->viewport->scrollpos_x, w->viewport->scrollpos_y);
1899 		if (update_overlay) RebuildViewportOverlay(w);
1900 	}
1901 }
1902 
1903 /**
1904  * Marks a viewport as dirty for repaint if it displays (a part of) the area the needs to be repainted.
1905  * @param vp     The viewport to mark as dirty
1906  * @param left   Left edge of area to repaint
1907  * @param top    Top edge of area to repaint
1908  * @param right  Right edge of area to repaint
1909  * @param bottom Bottom edge of area to repaint
1910  * @return true if the viewport contains a dirty block
1911  * @ingroup dirty
1912  */
MarkViewportDirty(const Viewport * vp,int left,int top,int right,int bottom)1913 static bool MarkViewportDirty(const Viewport *vp, int left, int top, int right, int bottom)
1914 {
1915 	/* Rounding wrt. zoom-out level */
1916 	right  += (1 << vp->zoom) - 1;
1917 	bottom += (1 << vp->zoom) - 1;
1918 
1919 	right -= vp->virtual_left;
1920 	if (right <= 0) return false;
1921 
1922 	bottom -= vp->virtual_top;
1923 	if (bottom <= 0) return false;
1924 
1925 	left = std::max(0, left - vp->virtual_left);
1926 
1927 	if (left >= vp->virtual_width) return false;
1928 
1929 	top = std::max(0, top - vp->virtual_top);
1930 
1931 	if (top >= vp->virtual_height) return false;
1932 
1933 	AddDirtyBlock(
1934 		UnScaleByZoomLower(left, vp->zoom) + vp->left,
1935 		UnScaleByZoomLower(top, vp->zoom) + vp->top,
1936 		UnScaleByZoom(right, vp->zoom) + vp->left + 1,
1937 		UnScaleByZoom(bottom, vp->zoom) + vp->top + 1
1938 	);
1939 
1940 	return true;
1941 }
1942 
1943 /**
1944  * Mark all viewports that display an area as dirty (in need of repaint).
1945  * @param left   Left   edge of area to repaint. (viewport coordinates, that is wrt. #ZOOM_LVL_NORMAL)
1946  * @param top    Top    edge of area to repaint. (viewport coordinates, that is wrt. #ZOOM_LVL_NORMAL)
1947  * @param right  Right  edge of area to repaint. (viewport coordinates, that is wrt. #ZOOM_LVL_NORMAL)
1948  * @param bottom Bottom edge of area to repaint. (viewport coordinates, that is wrt. #ZOOM_LVL_NORMAL)
1949  * @return true if at least one viewport has a dirty block
1950  * @ingroup dirty
1951  */
MarkAllViewportsDirty(int left,int top,int right,int bottom)1952 bool MarkAllViewportsDirty(int left, int top, int right, int bottom)
1953 {
1954 	bool dirty = false;
1955 
1956 	for (const Window *w : Window::Iterate()) {
1957 		Viewport *vp = w->viewport;
1958 		if (vp != nullptr) {
1959 			assert(vp->width != 0);
1960 			if (MarkViewportDirty(vp, left, top, right, bottom)) dirty = true;
1961 		}
1962 	}
1963 
1964 	return dirty;
1965 }
1966 
ConstrainAllViewportsZoom()1967 void ConstrainAllViewportsZoom()
1968 {
1969 	for (Window *w : Window::Iterate()) {
1970 		if (w->viewport == nullptr) continue;
1971 
1972 		ZoomLevel zoom = static_cast<ZoomLevel>(Clamp(w->viewport->zoom, _settings_client.gui.zoom_min, _settings_client.gui.zoom_max));
1973 		if (zoom != w->viewport->zoom) {
1974 			while (w->viewport->zoom < zoom) DoZoomInOutWindow(ZOOM_OUT, w);
1975 			while (w->viewport->zoom > zoom) DoZoomInOutWindow(ZOOM_IN, w);
1976 		}
1977 	}
1978 }
1979 
1980 /**
1981  * Mark a tile given by its index dirty for repaint.
1982  * @param tile The tile to mark dirty.
1983  * @param bridge_level_offset Height of bridge on tile to also mark dirty. (Height level relative to north corner.)
1984  * @param tile_height_override Height of the tile (#TileHeight).
1985  * @ingroup dirty
1986  */
MarkTileDirtyByTile(TileIndex tile,int bridge_level_offset,int tile_height_override)1987 void MarkTileDirtyByTile(TileIndex tile, int bridge_level_offset, int tile_height_override)
1988 {
1989 	Point pt = RemapCoords(TileX(tile) * TILE_SIZE, TileY(tile) * TILE_SIZE, tile_height_override * TILE_HEIGHT);
1990 	MarkAllViewportsDirty(
1991 			pt.x - MAX_TILE_EXTENT_LEFT,
1992 			pt.y - MAX_TILE_EXTENT_TOP - ZOOM_LVL_BASE * TILE_HEIGHT * bridge_level_offset,
1993 			pt.x + MAX_TILE_EXTENT_RIGHT,
1994 			pt.y + MAX_TILE_EXTENT_BOTTOM);
1995 }
1996 
1997 /**
1998  * Marks the selected tiles as dirty.
1999  *
2000  * This function marks the selected tiles as dirty for repaint
2001  *
2002  * @ingroup dirty
2003  */
SetSelectionTilesDirty()2004 static void SetSelectionTilesDirty()
2005 {
2006 	int x_size = _thd.size.x;
2007 	int y_size = _thd.size.y;
2008 
2009 	if (!_thd.diagonal) { // Selecting in a straight rectangle (or a single square)
2010 		int x_start = _thd.pos.x;
2011 		int y_start = _thd.pos.y;
2012 
2013 		if (_thd.outersize.x != 0) {
2014 			x_size  += _thd.outersize.x;
2015 			x_start += _thd.offs.x;
2016 			y_size  += _thd.outersize.y;
2017 			y_start += _thd.offs.y;
2018 		}
2019 
2020 		x_size -= TILE_SIZE;
2021 		y_size -= TILE_SIZE;
2022 
2023 		assert(x_size >= 0);
2024 		assert(y_size >= 0);
2025 
2026 		int x_end = Clamp(x_start + x_size, 0, MapSizeX() * TILE_SIZE - TILE_SIZE);
2027 		int y_end = Clamp(y_start + y_size, 0, MapSizeY() * TILE_SIZE - TILE_SIZE);
2028 
2029 		x_start = Clamp(x_start, 0, MapSizeX() * TILE_SIZE - TILE_SIZE);
2030 		y_start = Clamp(y_start, 0, MapSizeY() * TILE_SIZE - TILE_SIZE);
2031 
2032 		/* make sure everything is multiple of TILE_SIZE */
2033 		assert((x_end | y_end | x_start | y_start) % TILE_SIZE == 0);
2034 
2035 		/* How it works:
2036 		 * Suppose we have to mark dirty rectangle of 3x4 tiles:
2037 		 *   x
2038 		 *  xxx
2039 		 * xxxxx
2040 		 *  xxxxx
2041 		 *   xxx
2042 		 *    x
2043 		 * This algorithm marks dirty columns of tiles, so it is done in 3+4-1 steps:
2044 		 * 1)  x     2)  x
2045 		 *    xxx       Oxx
2046 		 *   Oxxxx     xOxxx
2047 		 *    xxxxx     Oxxxx
2048 		 *     xxx       xxx
2049 		 *      x         x
2050 		 * And so forth...
2051 		 */
2052 
2053 		int top_x = x_end; // coordinates of top dirty tile
2054 		int top_y = y_start;
2055 		int bot_x = top_x; // coordinates of bottom dirty tile
2056 		int bot_y = top_y;
2057 
2058 		do {
2059 			/* topmost dirty point */
2060 			TileIndex top_tile = TileVirtXY(top_x, top_y);
2061 			Point top = RemapCoords(top_x, top_y, GetTileMaxPixelZ(top_tile));
2062 
2063 			/* bottommost point */
2064 			TileIndex bottom_tile = TileVirtXY(bot_x, bot_y);
2065 			Point bot = RemapCoords(bot_x + TILE_SIZE, bot_y + TILE_SIZE, GetTilePixelZ(bottom_tile)); // bottommost point
2066 
2067 			/* the 'x' coordinate of 'top' and 'bot' is the same (and always in the same distance from tile middle),
2068 			 * tile height/slope affects only the 'y' on-screen coordinate! */
2069 
2070 			int l = top.x - TILE_PIXELS * ZOOM_LVL_BASE; // 'x' coordinate of left   side of the dirty rectangle
2071 			int t = top.y;                               // 'y' coordinate of top    side of the dirty rectangle
2072 			int r = top.x + TILE_PIXELS * ZOOM_LVL_BASE; // 'x' coordinate of right  side of the dirty rectangle
2073 			int b = bot.y;                               // 'y' coordinate of bottom side of the dirty rectangle
2074 
2075 			static const int OVERLAY_WIDTH = 4 * ZOOM_LVL_BASE; // part of selection sprites is drawn outside the selected area (in particular: terraforming)
2076 
2077 			/* For halftile foundations on SLOPE_STEEP_S the sprite extents some more towards the top */
2078 			MarkAllViewportsDirty(l - OVERLAY_WIDTH, t - OVERLAY_WIDTH - TILE_HEIGHT * ZOOM_LVL_BASE, r + OVERLAY_WIDTH, b + OVERLAY_WIDTH);
2079 
2080 			/* haven't we reached the topmost tile yet? */
2081 			if (top_x != x_start) {
2082 				top_x -= TILE_SIZE;
2083 			} else {
2084 				top_y += TILE_SIZE;
2085 			}
2086 
2087 			/* the way the bottom tile changes is different when we reach the bottommost tile */
2088 			if (bot_y != y_end) {
2089 				bot_y += TILE_SIZE;
2090 			} else {
2091 				bot_x -= TILE_SIZE;
2092 			}
2093 		} while (bot_x >= top_x);
2094 	} else { // Selecting in a 45 degrees rotated (diagonal) rectangle.
2095 		/* a_size, b_size describe a rectangle with rotated coordinates */
2096 		int a_size = x_size + y_size, b_size = x_size - y_size;
2097 
2098 		int interval_a = a_size < 0 ? -(int)TILE_SIZE : (int)TILE_SIZE;
2099 		int interval_b = b_size < 0 ? -(int)TILE_SIZE : (int)TILE_SIZE;
2100 
2101 		for (int a = -interval_a; a != a_size + interval_a; a += interval_a) {
2102 			for (int b = -interval_b; b != b_size + interval_b; b += interval_b) {
2103 				uint x = (_thd.pos.x + (a + b) / 2) / TILE_SIZE;
2104 				uint y = (_thd.pos.y + (a - b) / 2) / TILE_SIZE;
2105 
2106 				if (x < MapMaxX() && y < MapMaxY()) {
2107 					MarkTileDirtyByTile(TileXY(x, y));
2108 				}
2109 			}
2110 		}
2111 	}
2112 }
2113 
2114 
SetSelectionRed(bool b)2115 void SetSelectionRed(bool b)
2116 {
2117 	_thd.make_square_red = b;
2118 	SetSelectionTilesDirty();
2119 }
2120 
2121 /**
2122  * Test whether a sign is below the mouse
2123  * @param vp the clicked viewport
2124  * @param x X position of click
2125  * @param y Y position of click
2126  * @param sign the sign to check
2127  * @return true if the sign was hit
2128  */
CheckClickOnViewportSign(const Viewport * vp,int x,int y,const ViewportSign * sign)2129 static bool CheckClickOnViewportSign(const Viewport *vp, int x, int y, const ViewportSign *sign)
2130 {
2131 	bool small = (vp->zoom >= ZOOM_LVL_OUT_16X);
2132 	int sign_half_width = ScaleByZoom((small ? sign->width_small : sign->width_normal) / 2, vp->zoom);
2133 	int sign_height = ScaleByZoom(VPSM_TOP + (small ? FONT_HEIGHT_SMALL : FONT_HEIGHT_NORMAL) + VPSM_BOTTOM, vp->zoom);
2134 
2135 	return y >= sign->top && y < sign->top + sign_height &&
2136 			x >= sign->center - sign_half_width && x < sign->center + sign_half_width;
2137 }
2138 
2139 
2140 /**
2141  * Check whether any viewport sign was clicked, and dispatch the click.
2142  * @param vp the clicked viewport
2143  * @param x X position of click
2144  * @param y Y position of click
2145  * @return true if the sign was hit
2146  */
CheckClickOnViewportSign(const Viewport * vp,int x,int y)2147 static bool CheckClickOnViewportSign(const Viewport *vp, int x, int y)
2148 {
2149 	if (_game_mode == GM_MENU) return false;
2150 
2151 	x = ScaleByZoom(x - vp->left, vp->zoom) + vp->virtual_left;
2152 	y = ScaleByZoom(y - vp->top, vp->zoom) + vp->virtual_top;
2153 
2154 	Rect search_rect{ x - 1, y - 1, x + 1, y + 1 };
2155 	search_rect = ExpandRectWithViewportSignMargins(search_rect, vp->zoom);
2156 
2157 	bool show_stations = HasBit(_display_opt, DO_SHOW_STATION_NAMES) && !IsInvisibilitySet(TO_SIGNS);
2158 	bool show_waypoints = HasBit(_display_opt, DO_SHOW_WAYPOINT_NAMES) && !IsInvisibilitySet(TO_SIGNS);
2159 	bool show_towns = HasBit(_display_opt, DO_SHOW_TOWN_NAMES);
2160 	bool show_signs = HasBit(_display_opt, DO_SHOW_SIGNS) && !IsInvisibilitySet(TO_SIGNS);
2161 	bool show_competitors = HasBit(_display_opt, DO_SHOW_COMPETITOR_SIGNS);
2162 
2163 	/* Topmost of each type that was hit */
2164 	BaseStation *st = nullptr, *last_st = nullptr;
2165 	Town *t = nullptr, *last_t = nullptr;
2166 	Sign *si = nullptr, *last_si = nullptr;
2167 
2168 	/* See ViewportAddKdtreeSigns() for details on the search logic */
2169 	_viewport_sign_kdtree.FindContained(search_rect.left, search_rect.top, search_rect.right, search_rect.bottom, [&](const ViewportSignKdtreeItem & item) {
2170 		switch (item.type) {
2171 			case ViewportSignKdtreeItem::VKI_STATION:
2172 				if (!show_stations) break;
2173 				st = BaseStation::Get(item.id.station);
2174 				if (!show_competitors && _local_company != st->owner && st->owner != OWNER_NONE) break;
2175 				if (CheckClickOnViewportSign(vp, x, y, &st->sign)) last_st = st;
2176 				break;
2177 
2178 			case ViewportSignKdtreeItem::VKI_WAYPOINT:
2179 				if (!show_waypoints) break;
2180 				st = BaseStation::Get(item.id.station);
2181 				if (!show_competitors && _local_company != st->owner && st->owner != OWNER_NONE) break;
2182 				if (CheckClickOnViewportSign(vp, x, y, &st->sign)) last_st = st;
2183 				break;
2184 
2185 			case ViewportSignKdtreeItem::VKI_TOWN:
2186 				if (!show_towns) break;
2187 				t = Town::Get(item.id.town);
2188 				if (CheckClickOnViewportSign(vp, x, y, &t->cache.sign)) last_t = t;
2189 				break;
2190 
2191 			case ViewportSignKdtreeItem::VKI_SIGN:
2192 				if (!show_signs) break;
2193 				si = Sign::Get(item.id.sign);
2194 				if (!show_competitors && _local_company != si->owner && si->owner != OWNER_DEITY) break;
2195 				if (CheckClickOnViewportSign(vp, x, y, &si->sign)) last_si = si;
2196 				break;
2197 
2198 			default:
2199 				NOT_REACHED();
2200 		}
2201 	});
2202 
2203 	/* Select which hit to handle based on priority */
2204 	if (last_st != nullptr) {
2205 		if (Station::IsExpected(last_st)) {
2206 			ShowStationViewWindow(last_st->index);
2207 		} else {
2208 			ShowWaypointWindow(Waypoint::From(last_st));
2209 		}
2210 		return true;
2211 	} else if (last_t != nullptr) {
2212 		ShowTownViewWindow(last_t->index);
2213 		return true;
2214 	} else if (last_si != nullptr) {
2215 		HandleClickOnSign(last_si);
2216 		return true;
2217 	} else {
2218 		return false;
2219 	}
2220 }
2221 
2222 
MakeStation(StationID id)2223 ViewportSignKdtreeItem ViewportSignKdtreeItem::MakeStation(StationID id)
2224 {
2225 	ViewportSignKdtreeItem item;
2226 	item.type = VKI_STATION;
2227 	item.id.station = id;
2228 
2229 	const Station *st = Station::Get(id);
2230 	assert(st->sign.kdtree_valid);
2231 	item.center = st->sign.center;
2232 	item.top = st->sign.top;
2233 
2234 	/* Assume the sign can be a candidate for drawing, so measure its width */
2235 	_viewport_sign_maxwidth = std::max<int>(_viewport_sign_maxwidth, st->sign.width_normal);
2236 
2237 	return item;
2238 }
2239 
MakeWaypoint(StationID id)2240 ViewportSignKdtreeItem ViewportSignKdtreeItem::MakeWaypoint(StationID id)
2241 {
2242 	ViewportSignKdtreeItem item;
2243 	item.type = VKI_WAYPOINT;
2244 	item.id.station = id;
2245 
2246 	const Waypoint *st = Waypoint::Get(id);
2247 	assert(st->sign.kdtree_valid);
2248 	item.center = st->sign.center;
2249 	item.top = st->sign.top;
2250 
2251 	/* Assume the sign can be a candidate for drawing, so measure its width */
2252 	_viewport_sign_maxwidth = std::max<int>(_viewport_sign_maxwidth, st->sign.width_normal);
2253 
2254 	return item;
2255 }
2256 
MakeTown(TownID id)2257 ViewportSignKdtreeItem ViewportSignKdtreeItem::MakeTown(TownID id)
2258 {
2259 	ViewportSignKdtreeItem item;
2260 	item.type = VKI_TOWN;
2261 	item.id.town = id;
2262 
2263 	const Town *town = Town::Get(id);
2264 	assert(town->cache.sign.kdtree_valid);
2265 	item.center = town->cache.sign.center;
2266 	item.top = town->cache.sign.top;
2267 
2268 	/* Assume the sign can be a candidate for drawing, so measure its width */
2269 	_viewport_sign_maxwidth = std::max<int>(_viewport_sign_maxwidth, town->cache.sign.width_normal);
2270 
2271 	return item;
2272 }
2273 
MakeSign(SignID id)2274 ViewportSignKdtreeItem ViewportSignKdtreeItem::MakeSign(SignID id)
2275 {
2276 	ViewportSignKdtreeItem item;
2277 	item.type = VKI_SIGN;
2278 	item.id.sign = id;
2279 
2280 	const Sign *sign = Sign::Get(id);
2281 	assert(sign->sign.kdtree_valid);
2282 	item.center = sign->sign.center;
2283 	item.top = sign->sign.top;
2284 
2285 	/* Assume the sign can be a candidate for drawing, so measure its width */
2286 	_viewport_sign_maxwidth = std::max<int>(_viewport_sign_maxwidth, sign->sign.width_normal);
2287 
2288 	return item;
2289 }
2290 
RebuildViewportKdtree()2291 void RebuildViewportKdtree()
2292 {
2293 	/* Reset biggest size sign seen */
2294 	_viewport_sign_maxwidth = 0;
2295 
2296 	std::vector<ViewportSignKdtreeItem> items;
2297 	items.reserve(BaseStation::GetNumItems() + Town::GetNumItems() + Sign::GetNumItems());
2298 
2299 	for (const Station *st : Station::Iterate()) {
2300 		if (st->sign.kdtree_valid) items.push_back(ViewportSignKdtreeItem::MakeStation(st->index));
2301 	}
2302 
2303 	for (const Waypoint *wp : Waypoint::Iterate()) {
2304 		if (wp->sign.kdtree_valid) items.push_back(ViewportSignKdtreeItem::MakeWaypoint(wp->index));
2305 	}
2306 
2307 	for (const Town *town : Town::Iterate()) {
2308 		if (town->cache.sign.kdtree_valid) items.push_back(ViewportSignKdtreeItem::MakeTown(town->index));
2309 	}
2310 
2311 	for (const Sign *sign : Sign::Iterate()) {
2312 		if (sign->sign.kdtree_valid) items.push_back(ViewportSignKdtreeItem::MakeSign(sign->index));
2313 	}
2314 
2315 	_viewport_sign_kdtree.Build(items.begin(), items.end());
2316 }
2317 
2318 
CheckClickOnLandscape(const Viewport * vp,int x,int y)2319 static bool CheckClickOnLandscape(const Viewport *vp, int x, int y)
2320 {
2321 	Point pt = TranslateXYToTileCoord(vp, x, y);
2322 
2323 	if (pt.x != -1) return ClickTile(TileVirtXY(pt.x, pt.y));
2324 	return true;
2325 }
2326 
PlaceObject()2327 static void PlaceObject()
2328 {
2329 	Point pt;
2330 	Window *w;
2331 
2332 	pt = GetTileBelowCursor();
2333 	if (pt.x == -1) return;
2334 
2335 	if ((_thd.place_mode & HT_DRAG_MASK) == HT_POINT) {
2336 		pt.x += TILE_SIZE / 2;
2337 		pt.y += TILE_SIZE / 2;
2338 	}
2339 
2340 	_tile_fract_coords.x = pt.x & TILE_UNIT_MASK;
2341 	_tile_fract_coords.y = pt.y & TILE_UNIT_MASK;
2342 
2343 	w = _thd.GetCallbackWnd();
2344 	if (w != nullptr) w->OnPlaceObject(pt, TileVirtXY(pt.x, pt.y));
2345 }
2346 
2347 
HandleViewportClicked(const Viewport * vp,int x,int y)2348 bool HandleViewportClicked(const Viewport *vp, int x, int y)
2349 {
2350 	const Vehicle *v = CheckClickOnVehicle(vp, x, y);
2351 
2352 	if (_thd.place_mode & HT_VEHICLE) {
2353 		if (v != nullptr && VehicleClicked(v)) return true;
2354 	}
2355 
2356 	/* Vehicle placement mode already handled above. */
2357 	if ((_thd.place_mode & HT_DRAG_MASK) != HT_NONE) {
2358 		PlaceObject();
2359 		return true;
2360 	}
2361 
2362 	if (CheckClickOnViewportSign(vp, x, y)) return true;
2363 	bool result = CheckClickOnLandscape(vp, x, y);
2364 
2365 	if (v != nullptr) {
2366 		Debug(misc, 2, "Vehicle {} (index {}) at {}", v->unitnumber, v->index, fmt::ptr(v));
2367 		if (IsCompanyBuildableVehicleType(v)) {
2368 			v = v->First();
2369 			if (_ctrl_pressed && v->owner == _local_company) {
2370 				StartStopVehicle(v, true);
2371 			} else {
2372 				ShowVehicleViewWindow(v);
2373 			}
2374 		}
2375 		return true;
2376 	}
2377 	return result;
2378 }
2379 
RebuildViewportOverlay(Window * w)2380 void RebuildViewportOverlay(Window *w)
2381 {
2382 	if (w->viewport->overlay != nullptr &&
2383 			w->viewport->overlay->GetCompanyMask() != 0 &&
2384 			w->viewport->overlay->GetCargoMask() != 0) {
2385 		w->viewport->overlay->SetDirty();
2386 		w->SetDirty();
2387 	}
2388 }
2389 
2390 /**
2391  * Scrolls the viewport in a window to a given location.
2392  * @param x       Desired x location of the map to scroll to (world coordinate).
2393  * @param y       Desired y location of the map to scroll to (world coordinate).
2394  * @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.
2395  * @param w       %Window containing the viewport.
2396  * @param instant Jump to the location instead of slowly moving to it.
2397  * @return Destination of the viewport was changed (to activate other actions when the viewport is already at the desired position).
2398  */
ScrollWindowTo(int x,int y,int z,Window * w,bool instant)2399 bool ScrollWindowTo(int x, int y, int z, Window *w, bool instant)
2400 {
2401 	/* The slope cannot be acquired outside of the map, so make sure we are always within the map. */
2402 	if (z == -1) {
2403 		if ( x >= 0 && x <= (int)MapSizeX() * (int)TILE_SIZE - 1
2404 				&& y >= 0 && y <= (int)MapSizeY() * (int)TILE_SIZE - 1) {
2405 			z = GetSlopePixelZ(x, y);
2406 		} else {
2407 			z = TileHeightOutsideMap(x / (int)TILE_SIZE, y / (int)TILE_SIZE);
2408 		}
2409 	}
2410 
2411 	Point pt = MapXYZToViewport(w->viewport, x, y, z);
2412 	w->viewport->follow_vehicle = INVALID_VEHICLE;
2413 
2414 	if (w->viewport->dest_scrollpos_x == pt.x && w->viewport->dest_scrollpos_y == pt.y) return false;
2415 
2416 	if (instant) {
2417 		w->viewport->scrollpos_x = pt.x;
2418 		w->viewport->scrollpos_y = pt.y;
2419 		RebuildViewportOverlay(w);
2420 	}
2421 
2422 	w->viewport->dest_scrollpos_x = pt.x;
2423 	w->viewport->dest_scrollpos_y = pt.y;
2424 	return true;
2425 }
2426 
2427 /**
2428  * Scrolls the viewport in a window to a given location.
2429  * @param tile    Desired tile to center on.
2430  * @param w       %Window containing the viewport.
2431  * @param instant Jump to the location instead of slowly moving to it.
2432  * @return Destination of the viewport was changed (to activate other actions when the viewport is already at the desired position).
2433  */
ScrollWindowToTile(TileIndex tile,Window * w,bool instant)2434 bool ScrollWindowToTile(TileIndex tile, Window *w, bool instant)
2435 {
2436 	return ScrollWindowTo(TileX(tile) * TILE_SIZE, TileY(tile) * TILE_SIZE, -1, w, instant);
2437 }
2438 
2439 /**
2440  * Scrolls the viewport of the main window to a given location.
2441  * @param tile    Desired tile to center on.
2442  * @param instant Jump to the location instead of slowly moving to it.
2443  * @return Destination of the viewport was changed (to activate other actions when the viewport is already at the desired position).
2444  */
ScrollMainWindowToTile(TileIndex tile,bool instant)2445 bool ScrollMainWindowToTile(TileIndex tile, bool instant)
2446 {
2447 	return ScrollMainWindowTo(TileX(tile) * TILE_SIZE + TILE_SIZE / 2, TileY(tile) * TILE_SIZE + TILE_SIZE / 2, -1, instant);
2448 }
2449 
2450 /**
2451  * Set a tile to display a red error square.
2452  * @param tile Tile that should show the red error square.
2453  */
SetRedErrorSquare(TileIndex tile)2454 void SetRedErrorSquare(TileIndex tile)
2455 {
2456 	TileIndex old;
2457 
2458 	old = _thd.redsq;
2459 	_thd.redsq = tile;
2460 
2461 	if (tile != old) {
2462 		if (tile != INVALID_TILE) MarkTileDirtyByTile(tile);
2463 		if (old  != INVALID_TILE) MarkTileDirtyByTile(old);
2464 	}
2465 }
2466 
2467 /**
2468  * Highlight \a w by \a h tiles at the cursor.
2469  * @param w Width of the highlighted tiles rectangle.
2470  * @param h Height of the highlighted tiles rectangle.
2471  */
SetTileSelectSize(int w,int h)2472 void SetTileSelectSize(int w, int h)
2473 {
2474 	_thd.new_size.x = w * TILE_SIZE;
2475 	_thd.new_size.y = h * TILE_SIZE;
2476 	_thd.new_outersize.x = 0;
2477 	_thd.new_outersize.y = 0;
2478 }
2479 
SetTileSelectBigSize(int ox,int oy,int sx,int sy)2480 void SetTileSelectBigSize(int ox, int oy, int sx, int sy)
2481 {
2482 	_thd.offs.x = ox * TILE_SIZE;
2483 	_thd.offs.y = oy * TILE_SIZE;
2484 	_thd.new_outersize.x = sx * TILE_SIZE;
2485 	_thd.new_outersize.y = sy * TILE_SIZE;
2486 }
2487 
2488 /** returns the best autorail highlight type from map coordinates */
GetAutorailHT(int x,int y)2489 static HighLightStyle GetAutorailHT(int x, int y)
2490 {
2491 	return HT_RAIL | _autorail_piece[x & TILE_UNIT_MASK][y & TILE_UNIT_MASK];
2492 }
2493 
2494 /**
2495  * Reset tile highlighting.
2496  */
Reset()2497 void TileHighlightData::Reset()
2498 {
2499 	this->pos.x = 0;
2500 	this->pos.y = 0;
2501 	this->new_pos.x = 0;
2502 	this->new_pos.y = 0;
2503 }
2504 
2505 /**
2506  * Is the user dragging a 'diagonal rectangle'?
2507  * @return User is dragging a rotated rectangle.
2508  */
IsDraggingDiagonal()2509 bool TileHighlightData::IsDraggingDiagonal()
2510 {
2511 	return (this->place_mode & HT_DIAGONAL) != 0 && _ctrl_pressed && _left_button_down;
2512 }
2513 
2514 /**
2515  * Get the window that started the current highlighting.
2516  * @return The window that requested the current tile highlighting, or \c nullptr if not available.
2517  */
GetCallbackWnd()2518 Window *TileHighlightData::GetCallbackWnd()
2519 {
2520 	return FindWindowById(this->window_class, this->window_number);
2521 }
2522 
2523 
2524 
2525 /**
2526  * Updates tile highlighting for all cases.
2527  * Uses _thd.selstart and _thd.selend and _thd.place_mode (set elsewhere) to determine _thd.pos and _thd.size
2528  * Also drawstyle is determined. Uses _thd.new.* as a buffer and calls SetSelectionTilesDirty() twice,
2529  * Once for the old and once for the new selection.
2530  * _thd is TileHighlightData, found in viewport.h
2531  */
UpdateTileSelection()2532 void UpdateTileSelection()
2533 {
2534 	int x1;
2535 	int y1;
2536 
2537 	if (_thd.freeze) return;
2538 
2539 	HighLightStyle new_drawstyle = HT_NONE;
2540 	bool new_diagonal = false;
2541 
2542 	if ((_thd.place_mode & HT_DRAG_MASK) == HT_SPECIAL) {
2543 		x1 = _thd.selend.x;
2544 		y1 = _thd.selend.y;
2545 		if (x1 != -1) {
2546 			int x2 = _thd.selstart.x & ~TILE_UNIT_MASK;
2547 			int y2 = _thd.selstart.y & ~TILE_UNIT_MASK;
2548 			x1 &= ~TILE_UNIT_MASK;
2549 			y1 &= ~TILE_UNIT_MASK;
2550 
2551 			if (_thd.IsDraggingDiagonal()) {
2552 				new_diagonal = true;
2553 			} else {
2554 				if (x1 >= x2) Swap(x1, x2);
2555 				if (y1 >= y2) Swap(y1, y2);
2556 			}
2557 			_thd.new_pos.x = x1;
2558 			_thd.new_pos.y = y1;
2559 			_thd.new_size.x = x2 - x1;
2560 			_thd.new_size.y = y2 - y1;
2561 			if (!new_diagonal) {
2562 				_thd.new_size.x += TILE_SIZE;
2563 				_thd.new_size.y += TILE_SIZE;
2564 			}
2565 			new_drawstyle = _thd.next_drawstyle;
2566 		}
2567 	} else if ((_thd.place_mode & HT_DRAG_MASK) != HT_NONE) {
2568 		Point pt = GetTileBelowCursor();
2569 		x1 = pt.x;
2570 		y1 = pt.y;
2571 		if (x1 != -1) {
2572 			switch (_thd.place_mode & HT_DRAG_MASK) {
2573 				case HT_RECT:
2574 					new_drawstyle = HT_RECT;
2575 					break;
2576 				case HT_POINT:
2577 					new_drawstyle = HT_POINT;
2578 					x1 += TILE_SIZE / 2;
2579 					y1 += TILE_SIZE / 2;
2580 					break;
2581 				case HT_RAIL:
2582 					/* Draw one highlighted tile in any direction */
2583 					new_drawstyle = GetAutorailHT(pt.x, pt.y);
2584 					break;
2585 				case HT_LINE:
2586 					switch (_thd.place_mode & HT_DIR_MASK) {
2587 						case HT_DIR_X: new_drawstyle = HT_LINE | HT_DIR_X; break;
2588 						case HT_DIR_Y: new_drawstyle = HT_LINE | HT_DIR_Y; break;
2589 
2590 						case HT_DIR_HU:
2591 						case HT_DIR_HL:
2592 							new_drawstyle = (pt.x & TILE_UNIT_MASK) + (pt.y & TILE_UNIT_MASK) <= TILE_SIZE ? HT_LINE | HT_DIR_HU : HT_LINE | HT_DIR_HL;
2593 							break;
2594 
2595 						case HT_DIR_VL:
2596 						case HT_DIR_VR:
2597 							new_drawstyle = (pt.x & TILE_UNIT_MASK) > (pt.y & TILE_UNIT_MASK) ? HT_LINE | HT_DIR_VL : HT_LINE | HT_DIR_VR;
2598 							break;
2599 
2600 						default: NOT_REACHED();
2601 					}
2602 					_thd.selstart.x = x1 & ~TILE_UNIT_MASK;
2603 					_thd.selstart.y = y1 & ~TILE_UNIT_MASK;
2604 					break;
2605 				default:
2606 					NOT_REACHED();
2607 			}
2608 			_thd.new_pos.x = x1 & ~TILE_UNIT_MASK;
2609 			_thd.new_pos.y = y1 & ~TILE_UNIT_MASK;
2610 		}
2611 	}
2612 
2613 	/* redraw selection */
2614 	if (_thd.drawstyle != new_drawstyle ||
2615 			_thd.pos.x != _thd.new_pos.x || _thd.pos.y != _thd.new_pos.y ||
2616 			_thd.size.x != _thd.new_size.x || _thd.size.y != _thd.new_size.y ||
2617 			_thd.outersize.x != _thd.new_outersize.x ||
2618 			_thd.outersize.y != _thd.new_outersize.y ||
2619 			_thd.diagonal    != new_diagonal) {
2620 		/* Clear the old tile selection? */
2621 		if ((_thd.drawstyle & HT_DRAG_MASK) != HT_NONE) SetSelectionTilesDirty();
2622 
2623 		_thd.drawstyle = new_drawstyle;
2624 		_thd.pos = _thd.new_pos;
2625 		_thd.size = _thd.new_size;
2626 		_thd.outersize = _thd.new_outersize;
2627 		_thd.diagonal = new_diagonal;
2628 		_thd.dirty = 0xff;
2629 
2630 		/* Draw the new tile selection? */
2631 		if ((new_drawstyle & HT_DRAG_MASK) != HT_NONE) SetSelectionTilesDirty();
2632 	}
2633 }
2634 
2635 /**
2636  * Displays the measurement tooltips when selecting multiple tiles
2637  * @param str String to be displayed
2638  * @param paramcount number of params to deal with
2639  * @param params (optional) up to 5 pieces of additional information that may be added to a tooltip
2640  * @param close_cond Condition for closing this tooltip.
2641  */
ShowMeasurementTooltips(StringID str,uint paramcount,const uint64 params[],TooltipCloseCondition close_cond=TCC_EXIT_VIEWPORT)2642 static inline void ShowMeasurementTooltips(StringID str, uint paramcount, const uint64 params[], TooltipCloseCondition close_cond = TCC_EXIT_VIEWPORT)
2643 {
2644 	if (!_settings_client.gui.measure_tooltip) return;
2645 	GuiShowTooltips(_thd.GetCallbackWnd(), str, paramcount, params, close_cond);
2646 }
2647 
HideMeasurementTooltips()2648 static void HideMeasurementTooltips()
2649 {
2650 	CloseWindowById(WC_TOOLTIPS, 0);
2651 }
2652 
2653 /** highlighting tiles while only going over them with the mouse */
VpStartPlaceSizing(TileIndex tile,ViewportPlaceMethod method,ViewportDragDropSelectionProcess process)2654 void VpStartPlaceSizing(TileIndex tile, ViewportPlaceMethod method, ViewportDragDropSelectionProcess process)
2655 {
2656 	_thd.select_method = method;
2657 	_thd.select_proc   = process;
2658 	_thd.selend.x = TileX(tile) * TILE_SIZE;
2659 	_thd.selstart.x = TileX(tile) * TILE_SIZE;
2660 	_thd.selend.y = TileY(tile) * TILE_SIZE;
2661 	_thd.selstart.y = TileY(tile) * TILE_SIZE;
2662 
2663 	/* Needed so several things (road, autoroad, bridges, ...) are placed correctly.
2664 	 * In effect, placement starts from the centre of a tile
2665 	 */
2666 	if (method == VPM_X_OR_Y || method == VPM_FIX_X || method == VPM_FIX_Y) {
2667 		_thd.selend.x += TILE_SIZE / 2;
2668 		_thd.selend.y += TILE_SIZE / 2;
2669 		_thd.selstart.x += TILE_SIZE / 2;
2670 		_thd.selstart.y += TILE_SIZE / 2;
2671 	}
2672 
2673 	HighLightStyle others = _thd.place_mode & ~(HT_DRAG_MASK | HT_DIR_MASK);
2674 	if ((_thd.place_mode & HT_DRAG_MASK) == HT_RECT) {
2675 		_thd.place_mode = HT_SPECIAL | others;
2676 		_thd.next_drawstyle = HT_RECT | others;
2677 	} else if (_thd.place_mode & (HT_RAIL | HT_LINE)) {
2678 		_thd.place_mode = HT_SPECIAL | others;
2679 		_thd.next_drawstyle = _thd.drawstyle | others;
2680 	} else {
2681 		_thd.place_mode = HT_SPECIAL | others;
2682 		_thd.next_drawstyle = HT_POINT | others;
2683 	}
2684 	_special_mouse_mode = WSM_SIZING;
2685 }
2686 
2687 /** Drag over the map while holding the left mouse down. */
VpStartDragging(ViewportDragDropSelectionProcess process)2688 void VpStartDragging(ViewportDragDropSelectionProcess process)
2689 {
2690 	_thd.select_method = VPM_X_AND_Y;
2691 	_thd.select_proc = process;
2692 	_thd.selstart.x = 0;
2693 	_thd.selstart.y = 0;
2694 	_thd.next_drawstyle = HT_RECT;
2695 
2696 	_special_mouse_mode = WSM_DRAGGING;
2697 }
2698 
VpSetPlaceSizingLimit(int limit)2699 void VpSetPlaceSizingLimit(int limit)
2700 {
2701 	_thd.sizelimit = limit;
2702 }
2703 
2704 /**
2705  * Highlights all tiles between a set of two tiles. Used in dock and tunnel placement
2706  * @param from TileIndex of the first tile to highlight
2707  * @param to TileIndex of the last tile to highlight
2708  */
VpSetPresizeRange(TileIndex from,TileIndex to)2709 void VpSetPresizeRange(TileIndex from, TileIndex to)
2710 {
2711 	uint64 distance = DistanceManhattan(from, to) + 1;
2712 
2713 	_thd.selend.x = TileX(to) * TILE_SIZE;
2714 	_thd.selend.y = TileY(to) * TILE_SIZE;
2715 	_thd.selstart.x = TileX(from) * TILE_SIZE;
2716 	_thd.selstart.y = TileY(from) * TILE_SIZE;
2717 	_thd.next_drawstyle = HT_RECT;
2718 
2719 	/* show measurement only if there is any length to speak of */
2720 	if (distance > 1) {
2721 		ShowMeasurementTooltips(STR_MEASURE_LENGTH, 1, &distance);
2722 	} else {
2723 		HideMeasurementTooltips();
2724 	}
2725 }
2726 
VpStartPreSizing()2727 static void VpStartPreSizing()
2728 {
2729 	_thd.selend.x = -1;
2730 	_special_mouse_mode = WSM_PRESIZE;
2731 }
2732 
2733 /**
2734  * returns information about the 2x1 piece to be build.
2735  * The lower bits (0-3) are the track type.
2736  */
Check2x1AutoRail(int mode)2737 static HighLightStyle Check2x1AutoRail(int mode)
2738 {
2739 	int fxpy = _tile_fract_coords.x + _tile_fract_coords.y;
2740 	int sxpy = (_thd.selend.x & TILE_UNIT_MASK) + (_thd.selend.y & TILE_UNIT_MASK);
2741 	int fxmy = _tile_fract_coords.x - _tile_fract_coords.y;
2742 	int sxmy = (_thd.selend.x & TILE_UNIT_MASK) - (_thd.selend.y & TILE_UNIT_MASK);
2743 
2744 	switch (mode) {
2745 		default: NOT_REACHED();
2746 		case 0: // end piece is lower right
2747 			if (fxpy >= 20 && sxpy <= 12) return HT_DIR_HL;
2748 			if (fxmy < -3 && sxmy > 3) return HT_DIR_VR;
2749 			return HT_DIR_Y;
2750 
2751 		case 1:
2752 			if (fxmy > 3 && sxmy < -3) return HT_DIR_VL;
2753 			if (fxpy <= 12 && sxpy >= 20) return HT_DIR_HU;
2754 			return HT_DIR_Y;
2755 
2756 		case 2:
2757 			if (fxmy > 3 && sxmy < -3) return HT_DIR_VL;
2758 			if (fxpy >= 20 && sxpy <= 12) return HT_DIR_HL;
2759 			return HT_DIR_X;
2760 
2761 		case 3:
2762 			if (fxmy < -3 && sxmy > 3) return HT_DIR_VR;
2763 			if (fxpy <= 12 && sxpy >= 20) return HT_DIR_HU;
2764 			return HT_DIR_X;
2765 	}
2766 }
2767 
2768 /**
2769  * Check if the direction of start and end tile should be swapped based on
2770  * the dragging-style. Default directions are:
2771  * in the case of a line (HT_RAIL, HT_LINE):  DIR_NE, DIR_NW, DIR_N, DIR_E
2772  * in the case of a rect (HT_RECT, HT_POINT): DIR_S, DIR_E
2773  * For example dragging a rectangle area from south to north should be swapped to
2774  * north-south (DIR_S) to obtain the same results with less code. This is what
2775  * the return value signifies.
2776  * @param style HighLightStyle dragging style
2777  * @param start_tile start tile of drag
2778  * @param end_tile end tile of drag
2779  * @return boolean value which when true means start/end should be swapped
2780  */
SwapDirection(HighLightStyle style,TileIndex start_tile,TileIndex end_tile)2781 static bool SwapDirection(HighLightStyle style, TileIndex start_tile, TileIndex end_tile)
2782 {
2783 	uint start_x = TileX(start_tile);
2784 	uint start_y = TileY(start_tile);
2785 	uint end_x = TileX(end_tile);
2786 	uint end_y = TileY(end_tile);
2787 
2788 	switch (style & HT_DRAG_MASK) {
2789 		case HT_RAIL:
2790 		case HT_LINE: return (end_x > start_x || (end_x == start_x && end_y > start_y));
2791 
2792 		case HT_RECT:
2793 		case HT_POINT: return (end_x != start_x && end_y < start_y);
2794 		default: NOT_REACHED();
2795 	}
2796 
2797 	return false;
2798 }
2799 
2800 /**
2801  * Calculates height difference between one tile and another.
2802  * Multiplies the result to suit the standard given by #TILE_HEIGHT_STEP.
2803  *
2804  * To correctly get the height difference we need the direction we are dragging
2805  * in, as well as with what kind of tool we are dragging. For example a horizontal
2806  * autorail tool that starts in bottom and ends at the top of a tile will need the
2807  * maximum of SW, S and SE, N corners respectively. This is handled by the lookup table below
2808  * See #_tileoffs_by_dir in map.cpp for the direction enums if you can't figure out the values yourself.
2809  * @param style      Highlighting style of the drag. This includes direction and style (autorail, rect, etc.)
2810  * @param distance   Number of tiles dragged, important for horizontal/vertical drags, ignored for others.
2811  * @param start_tile Start tile of the drag operation.
2812  * @param end_tile   End tile of the drag operation.
2813  * @return Height difference between two tiles. The tile measurement tool utilizes this value in its tooltip.
2814  */
CalcHeightdiff(HighLightStyle style,uint distance,TileIndex start_tile,TileIndex end_tile)2815 static int CalcHeightdiff(HighLightStyle style, uint distance, TileIndex start_tile, TileIndex end_tile)
2816 {
2817 	bool swap = SwapDirection(style, start_tile, end_tile);
2818 	uint h0, h1; // Start height and end height.
2819 
2820 	if (start_tile == end_tile) return 0;
2821 	if (swap) Swap(start_tile, end_tile);
2822 
2823 	switch (style & HT_DRAG_MASK) {
2824 		case HT_RECT: {
2825 			static const TileIndexDiffC heightdiff_area_by_dir[] = {
2826 				/* Start */ {1, 0}, /* Dragging east */ {0, 0}, // Dragging south
2827 				/* End   */ {0, 1}, /* Dragging east */ {1, 1}  // Dragging south
2828 			};
2829 
2830 			/* In the case of an area we can determine whether we were dragging south or
2831 			 * east by checking the X-coordinates of the tiles */
2832 			byte style_t = (byte)(TileX(end_tile) > TileX(start_tile));
2833 			start_tile = TILE_ADD(start_tile, ToTileIndexDiff(heightdiff_area_by_dir[style_t]));
2834 			end_tile   = TILE_ADD(end_tile, ToTileIndexDiff(heightdiff_area_by_dir[2 + style_t]));
2835 			FALLTHROUGH;
2836 		}
2837 
2838 		case HT_POINT:
2839 			h0 = TileHeight(start_tile);
2840 			h1 = TileHeight(end_tile);
2841 			break;
2842 		default: { // All other types, this is mostly only line/autorail
2843 			static const HighLightStyle flip_style_direction[] = {
2844 				HT_DIR_X, HT_DIR_Y, HT_DIR_HL, HT_DIR_HU, HT_DIR_VR, HT_DIR_VL
2845 			};
2846 			static const TileIndexDiffC heightdiff_line_by_dir[] = {
2847 				/* Start */ {1, 0}, {1, 1}, /* HT_DIR_X  */ {0, 1}, {1, 1}, // HT_DIR_Y
2848 				/* Start */ {1, 0}, {0, 0}, /* HT_DIR_HU */ {1, 0}, {1, 1}, // HT_DIR_HL
2849 				/* Start */ {1, 0}, {1, 1}, /* HT_DIR_VL */ {0, 1}, {1, 1}, // HT_DIR_VR
2850 
2851 				/* Start */ {0, 1}, {0, 0}, /* HT_DIR_X  */ {1, 0}, {0, 0}, // HT_DIR_Y
2852 				/* End   */ {0, 1}, {0, 0}, /* HT_DIR_HU */ {1, 1}, {0, 1}, // HT_DIR_HL
2853 				/* End   */ {1, 0}, {0, 0}, /* HT_DIR_VL */ {0, 0}, {0, 1}, // HT_DIR_VR
2854 			};
2855 
2856 			distance %= 2; // we're only interested if the distance is even or uneven
2857 			style &= HT_DIR_MASK;
2858 
2859 			/* To handle autorail, we do some magic to be able to use a lookup table.
2860 			 * Firstly if we drag the other way around, we switch start&end, and if needed
2861 			 * also flip the drag-position. Eg if it was on the left, and the distance is even
2862 			 * that means the end, which is now the start is on the right */
2863 			if (swap && distance == 0) style = flip_style_direction[style];
2864 
2865 			/* Use lookup table for start-tile based on HighLightStyle direction */
2866 			byte style_t = style * 2;
2867 			assert(style_t < lengthof(heightdiff_line_by_dir) - 13);
2868 			h0 = TileHeight(TILE_ADD(start_tile, ToTileIndexDiff(heightdiff_line_by_dir[style_t])));
2869 			uint ht = TileHeight(TILE_ADD(start_tile, ToTileIndexDiff(heightdiff_line_by_dir[style_t + 1])));
2870 			h0 = std::max(h0, ht);
2871 
2872 			/* Use lookup table for end-tile based on HighLightStyle direction
2873 			 * flip around side (lower/upper, left/right) based on distance */
2874 			if (distance == 0) style_t = flip_style_direction[style] * 2;
2875 			assert(style_t < lengthof(heightdiff_line_by_dir) - 13);
2876 			h1 = TileHeight(TILE_ADD(end_tile, ToTileIndexDiff(heightdiff_line_by_dir[12 + style_t])));
2877 			ht = TileHeight(TILE_ADD(end_tile, ToTileIndexDiff(heightdiff_line_by_dir[12 + style_t + 1])));
2878 			h1 = std::max(h1, ht);
2879 			break;
2880 		}
2881 	}
2882 
2883 	if (swap) Swap(h0, h1);
2884 	return (int)(h1 - h0) * TILE_HEIGHT_STEP;
2885 }
2886 
2887 static const StringID measure_strings_length[] = {STR_NULL, STR_MEASURE_LENGTH, STR_MEASURE_LENGTH_HEIGHTDIFF};
2888 
2889 /**
2890  * Check for underflowing the map.
2891  * @param test  the variable to test for underflowing
2892  * @param other the other variable to update to keep the line
2893  * @param mult  the constant to multiply the difference by for \c other
2894  */
CheckUnderflow(int & test,int & other,int mult)2895 static void CheckUnderflow(int &test, int &other, int mult)
2896 {
2897 	if (test >= 0) return;
2898 
2899 	other += mult * test;
2900 	test = 0;
2901 }
2902 
2903 /**
2904  * Check for overflowing the map.
2905  * @param test  the variable to test for overflowing
2906  * @param other the other variable to update to keep the line
2907  * @param max   the maximum value for the \c test variable
2908  * @param mult  the constant to multiply the difference by for \c other
2909  */
CheckOverflow(int & test,int & other,int max,int mult)2910 static void CheckOverflow(int &test, int &other, int max, int mult)
2911 {
2912 	if (test <= max) return;
2913 
2914 	other += mult * (test - max);
2915 	test = max;
2916 }
2917 
2918 /** while dragging */
CalcRaildirsDrawstyle(int x,int y,int method)2919 static void CalcRaildirsDrawstyle(int x, int y, int method)
2920 {
2921 	HighLightStyle b;
2922 
2923 	int dx = _thd.selstart.x - (_thd.selend.x & ~TILE_UNIT_MASK);
2924 	int dy = _thd.selstart.y - (_thd.selend.y & ~TILE_UNIT_MASK);
2925 	uint w = abs(dx) + TILE_SIZE;
2926 	uint h = abs(dy) + TILE_SIZE;
2927 
2928 	if (method & ~(VPM_RAILDIRS | VPM_SIGNALDIRS)) {
2929 		/* We 'force' a selection direction; first four rail buttons. */
2930 		method &= ~(VPM_RAILDIRS | VPM_SIGNALDIRS);
2931 		int raw_dx = _thd.selstart.x - _thd.selend.x;
2932 		int raw_dy = _thd.selstart.y - _thd.selend.y;
2933 		switch (method) {
2934 			case VPM_FIX_X:
2935 				b = HT_LINE | HT_DIR_Y;
2936 				x = _thd.selstart.x;
2937 				break;
2938 
2939 			case VPM_FIX_Y:
2940 				b = HT_LINE | HT_DIR_X;
2941 				y = _thd.selstart.y;
2942 				break;
2943 
2944 			case VPM_FIX_HORIZONTAL:
2945 				if (dx == -dy) {
2946 					/* We are on a straight horizontal line. Determine the 'rail'
2947 					 * to build based the sub tile location. */
2948 					b = (x & TILE_UNIT_MASK) + (y & TILE_UNIT_MASK) >= TILE_SIZE ? HT_LINE | HT_DIR_HL : HT_LINE | HT_DIR_HU;
2949 				} else {
2950 					/* We are not on a straight line. Determine the rail to build
2951 					 * based on whether we are above or below it. */
2952 					b = dx + dy >= (int)TILE_SIZE ? HT_LINE | HT_DIR_HU : HT_LINE | HT_DIR_HL;
2953 
2954 					/* Calculate where a horizontal line through the start point and
2955 					 * a vertical line from the selected end point intersect and
2956 					 * use that point as the end point. */
2957 					int offset = (raw_dx - raw_dy) / 2;
2958 					x = _thd.selstart.x - (offset & ~TILE_UNIT_MASK);
2959 					y = _thd.selstart.y + (offset & ~TILE_UNIT_MASK);
2960 
2961 					/* 'Build' the last half rail tile if needed */
2962 					if ((offset & TILE_UNIT_MASK) > (TILE_SIZE / 2)) {
2963 						if (dx + dy >= (int)TILE_SIZE) {
2964 							x += (dx + dy < 0) ? (int)TILE_SIZE : -(int)TILE_SIZE;
2965 						} else {
2966 							y += (dx + dy < 0) ? (int)TILE_SIZE : -(int)TILE_SIZE;
2967 						}
2968 					}
2969 
2970 					/* Make sure we do not overflow the map! */
2971 					CheckUnderflow(x, y, 1);
2972 					CheckUnderflow(y, x, 1);
2973 					CheckOverflow(x, y, (MapMaxX() - 1) * TILE_SIZE, 1);
2974 					CheckOverflow(y, x, (MapMaxY() - 1) * TILE_SIZE, 1);
2975 					assert(x >= 0 && y >= 0 && x <= (int)(MapMaxX() * TILE_SIZE) && y <= (int)(MapMaxY() * TILE_SIZE));
2976 				}
2977 				break;
2978 
2979 			case VPM_FIX_VERTICAL:
2980 				if (dx == dy) {
2981 					/* We are on a straight vertical line. Determine the 'rail'
2982 					 * to build based the sub tile location. */
2983 					b = (x & TILE_UNIT_MASK) > (y & TILE_UNIT_MASK) ? HT_LINE | HT_DIR_VL : HT_LINE | HT_DIR_VR;
2984 				} else {
2985 					/* We are not on a straight line. Determine the rail to build
2986 					 * based on whether we are left or right from it. */
2987 					b = dx < dy ? HT_LINE | HT_DIR_VL : HT_LINE | HT_DIR_VR;
2988 
2989 					/* Calculate where a vertical line through the start point and
2990 					 * a horizontal line from the selected end point intersect and
2991 					 * use that point as the end point. */
2992 					int offset = (raw_dx + raw_dy + (int)TILE_SIZE) / 2;
2993 					x = _thd.selstart.x - (offset & ~TILE_UNIT_MASK);
2994 					y = _thd.selstart.y - (offset & ~TILE_UNIT_MASK);
2995 
2996 					/* 'Build' the last half rail tile if needed */
2997 					if ((offset & TILE_UNIT_MASK) > (TILE_SIZE / 2)) {
2998 						if (dx - dy < 0) {
2999 							y += (dx > dy) ? (int)TILE_SIZE : -(int)TILE_SIZE;
3000 						} else {
3001 							x += (dx < dy) ? (int)TILE_SIZE : -(int)TILE_SIZE;
3002 						}
3003 					}
3004 
3005 					/* Make sure we do not overflow the map! */
3006 					CheckUnderflow(x, y, -1);
3007 					CheckUnderflow(y, x, -1);
3008 					CheckOverflow(x, y, (MapMaxX() - 1) * TILE_SIZE, -1);
3009 					CheckOverflow(y, x, (MapMaxY() - 1) * TILE_SIZE, -1);
3010 					assert(x >= 0 && y >= 0 && x <= (int)(MapMaxX() * TILE_SIZE) && y <= (int)(MapMaxY() * TILE_SIZE));
3011 				}
3012 				break;
3013 
3014 			default:
3015 				NOT_REACHED();
3016 		}
3017 	} else if (TileVirtXY(_thd.selstart.x, _thd.selstart.y) == TileVirtXY(x, y)) { // check if we're only within one tile
3018 		if (method & VPM_RAILDIRS) {
3019 			b = GetAutorailHT(x, y);
3020 		} else { // rect for autosignals on one tile
3021 			b = HT_RECT;
3022 		}
3023 	} else if (h == TILE_SIZE) { // Is this in X direction?
3024 		if (dx == (int)TILE_SIZE) { // 2x1 special handling
3025 			b = (Check2x1AutoRail(3)) | HT_LINE;
3026 		} else if (dx == -(int)TILE_SIZE) {
3027 			b = (Check2x1AutoRail(2)) | HT_LINE;
3028 		} else {
3029 			b = HT_LINE | HT_DIR_X;
3030 		}
3031 		y = _thd.selstart.y;
3032 	} else if (w == TILE_SIZE) { // Or Y direction?
3033 		if (dy == (int)TILE_SIZE) { // 2x1 special handling
3034 			b = (Check2x1AutoRail(1)) | HT_LINE;
3035 		} else if (dy == -(int)TILE_SIZE) { // 2x1 other direction
3036 			b = (Check2x1AutoRail(0)) | HT_LINE;
3037 		} else {
3038 			b = HT_LINE | HT_DIR_Y;
3039 		}
3040 		x = _thd.selstart.x;
3041 	} else if (w > h * 2) { // still count as x dir?
3042 		b = HT_LINE | HT_DIR_X;
3043 		y = _thd.selstart.y;
3044 	} else if (h > w * 2) { // still count as y dir?
3045 		b = HT_LINE | HT_DIR_Y;
3046 		x = _thd.selstart.x;
3047 	} else { // complicated direction
3048 		int d = w - h;
3049 		_thd.selend.x = _thd.selend.x & ~TILE_UNIT_MASK;
3050 		_thd.selend.y = _thd.selend.y & ~TILE_UNIT_MASK;
3051 
3052 		/* four cases. */
3053 		if (x > _thd.selstart.x) {
3054 			if (y > _thd.selstart.y) {
3055 				/* south */
3056 				if (d == 0) {
3057 					b = (x & TILE_UNIT_MASK) > (y & TILE_UNIT_MASK) ? HT_LINE | HT_DIR_VL : HT_LINE | HT_DIR_VR;
3058 				} else if (d >= 0) {
3059 					x = _thd.selstart.x + h;
3060 					b = HT_LINE | HT_DIR_VL;
3061 				} else {
3062 					y = _thd.selstart.y + w;
3063 					b = HT_LINE | HT_DIR_VR;
3064 				}
3065 			} else {
3066 				/* west */
3067 				if (d == 0) {
3068 					b = (x & TILE_UNIT_MASK) + (y & TILE_UNIT_MASK) >= TILE_SIZE ? HT_LINE | HT_DIR_HL : HT_LINE | HT_DIR_HU;
3069 				} else if (d >= 0) {
3070 					x = _thd.selstart.x + h;
3071 					b = HT_LINE | HT_DIR_HL;
3072 				} else {
3073 					y = _thd.selstart.y - w;
3074 					b = HT_LINE | HT_DIR_HU;
3075 				}
3076 			}
3077 		} else {
3078 			if (y > _thd.selstart.y) {
3079 				/* east */
3080 				if (d == 0) {
3081 					b = (x & TILE_UNIT_MASK) + (y & TILE_UNIT_MASK) >= TILE_SIZE ? HT_LINE | HT_DIR_HL : HT_LINE | HT_DIR_HU;
3082 				} else if (d >= 0) {
3083 					x = _thd.selstart.x - h;
3084 					b = HT_LINE | HT_DIR_HU;
3085 				} else {
3086 					y = _thd.selstart.y + w;
3087 					b = HT_LINE | HT_DIR_HL;
3088 				}
3089 			} else {
3090 				/* north */
3091 				if (d == 0) {
3092 					b = (x & TILE_UNIT_MASK) > (y & TILE_UNIT_MASK) ? HT_LINE | HT_DIR_VL : HT_LINE | HT_DIR_VR;
3093 				} else if (d >= 0) {
3094 					x = _thd.selstart.x - h;
3095 					b = HT_LINE | HT_DIR_VR;
3096 				} else {
3097 					y = _thd.selstart.y - w;
3098 					b = HT_LINE | HT_DIR_VL;
3099 				}
3100 			}
3101 		}
3102 	}
3103 
3104 	if (_settings_client.gui.measure_tooltip) {
3105 		TileIndex t0 = TileVirtXY(_thd.selstart.x, _thd.selstart.y);
3106 		TileIndex t1 = TileVirtXY(x, y);
3107 		uint distance = DistanceManhattan(t0, t1) + 1;
3108 		byte index = 0;
3109 		uint64 params[2];
3110 
3111 		if (distance != 1) {
3112 			int heightdiff = CalcHeightdiff(b, distance, t0, t1);
3113 			/* If we are showing a tooltip for horizontal or vertical drags,
3114 			 * 2 tiles have a length of 1. To bias towards the ceiling we add
3115 			 * one before division. It feels more natural to count 3 lengths as 2 */
3116 			if ((b & HT_DIR_MASK) != HT_DIR_X && (b & HT_DIR_MASK) != HT_DIR_Y) {
3117 				distance = CeilDiv(distance, 2);
3118 			}
3119 
3120 			params[index++] = distance;
3121 			if (heightdiff != 0) params[index++] = heightdiff;
3122 		}
3123 
3124 		ShowMeasurementTooltips(measure_strings_length[index], index, params);
3125 	}
3126 
3127 	_thd.selend.x = x;
3128 	_thd.selend.y = y;
3129 	_thd.next_drawstyle = b;
3130 }
3131 
3132 /**
3133  * Selects tiles while dragging
3134  * @param x X coordinate of end of selection
3135  * @param y Y coordinate of end of selection
3136  * @param method modifies the way tiles are selected. Possible
3137  * methods are VPM_* in viewport.h
3138  */
VpSelectTilesWithMethod(int x,int y,ViewportPlaceMethod method)3139 void VpSelectTilesWithMethod(int x, int y, ViewportPlaceMethod method)
3140 {
3141 	int sx, sy;
3142 	HighLightStyle style;
3143 
3144 	if (x == -1) {
3145 		_thd.selend.x = -1;
3146 		return;
3147 	}
3148 
3149 	/* Special handling of drag in any (8-way) direction */
3150 	if (method & (VPM_RAILDIRS | VPM_SIGNALDIRS)) {
3151 		_thd.selend.x = x;
3152 		_thd.selend.y = y;
3153 		CalcRaildirsDrawstyle(x, y, method);
3154 		return;
3155 	}
3156 
3157 	/* Needed so level-land is placed correctly */
3158 	if ((_thd.next_drawstyle & HT_DRAG_MASK) == HT_POINT) {
3159 		x += TILE_SIZE / 2;
3160 		y += TILE_SIZE / 2;
3161 	}
3162 
3163 	sx = _thd.selstart.x;
3164 	sy = _thd.selstart.y;
3165 
3166 	int limit = 0;
3167 
3168 	switch (method) {
3169 		case VPM_X_OR_Y: // drag in X or Y direction
3170 			if (abs(sy - y) < abs(sx - x)) {
3171 				y = sy;
3172 				style = HT_DIR_X;
3173 			} else {
3174 				x = sx;
3175 				style = HT_DIR_Y;
3176 			}
3177 			goto calc_heightdiff_single_direction;
3178 
3179 		case VPM_X_LIMITED: // Drag in X direction (limited size).
3180 			limit = (_thd.sizelimit - 1) * TILE_SIZE;
3181 			FALLTHROUGH;
3182 
3183 		case VPM_FIX_X: // drag in Y direction
3184 			x = sx;
3185 			style = HT_DIR_Y;
3186 			goto calc_heightdiff_single_direction;
3187 
3188 		case VPM_Y_LIMITED: // Drag in Y direction (limited size).
3189 			limit = (_thd.sizelimit - 1) * TILE_SIZE;
3190 			FALLTHROUGH;
3191 
3192 		case VPM_FIX_Y: // drag in X direction
3193 			y = sy;
3194 			style = HT_DIR_X;
3195 
3196 calc_heightdiff_single_direction:;
3197 			if (limit > 0) {
3198 				x = sx + Clamp(x - sx, -limit, limit);
3199 				y = sy + Clamp(y - sy, -limit, limit);
3200 			}
3201 			if (_settings_client.gui.measure_tooltip) {
3202 				TileIndex t0 = TileVirtXY(sx, sy);
3203 				TileIndex t1 = TileVirtXY(x, y);
3204 				uint distance = DistanceManhattan(t0, t1) + 1;
3205 				byte index = 0;
3206 				uint64 params[2];
3207 
3208 				if (distance != 1) {
3209 					/* With current code passing a HT_LINE style to calculate the height
3210 					 * difference is enough. However if/when a point-tool is created
3211 					 * with this method, function should be called with new_style (below)
3212 					 * instead of HT_LINE | style case HT_POINT is handled specially
3213 					 * new_style := (_thd.next_drawstyle & HT_RECT) ? HT_LINE | style : _thd.next_drawstyle; */
3214 					int heightdiff = CalcHeightdiff(HT_LINE | style, 0, t0, t1);
3215 
3216 					params[index++] = distance;
3217 					if (heightdiff != 0) params[index++] = heightdiff;
3218 				}
3219 
3220 				ShowMeasurementTooltips(measure_strings_length[index], index, params);
3221 			}
3222 			break;
3223 
3224 		case VPM_X_AND_Y_LIMITED: // Drag an X by Y constrained rect area.
3225 			limit = (_thd.sizelimit - 1) * TILE_SIZE;
3226 			x = sx + Clamp(x - sx, -limit, limit);
3227 			y = sy + Clamp(y - sy, -limit, limit);
3228 			FALLTHROUGH;
3229 
3230 		case VPM_X_AND_Y: // drag an X by Y area
3231 			if (_settings_client.gui.measure_tooltip) {
3232 				static const StringID measure_strings_area[] = {
3233 					STR_NULL, STR_NULL, STR_MEASURE_AREA, STR_MEASURE_AREA_HEIGHTDIFF
3234 				};
3235 
3236 				TileIndex t0 = TileVirtXY(sx, sy);
3237 				TileIndex t1 = TileVirtXY(x, y);
3238 				uint dx = Delta(TileX(t0), TileX(t1)) + 1;
3239 				uint dy = Delta(TileY(t0), TileY(t1)) + 1;
3240 				byte index = 0;
3241 				uint64 params[3];
3242 
3243 				/* If dragging an area (eg dynamite tool) and it is actually a single
3244 				 * row/column, change the type to 'line' to get proper calculation for height */
3245 				style = (HighLightStyle)_thd.next_drawstyle;
3246 				if (_thd.IsDraggingDiagonal()) {
3247 					/* Determine the "area" of the diagonal dragged selection.
3248 					 * We assume the area is the number of tiles along the X
3249 					 * edge and the number of tiles along the Y edge. However,
3250 					 * multiplying these two numbers does not give the exact
3251 					 * number of tiles; basically we are counting the black
3252 					 * squares on a chess board and ignore the white ones to
3253 					 * make the tile counts at the edges match up. There is no
3254 					 * other way to make a proper count though.
3255 					 *
3256 					 * First convert to the rotated coordinate system. */
3257 					int dist_x = TileX(t0) - TileX(t1);
3258 					int dist_y = TileY(t0) - TileY(t1);
3259 					int a_max = dist_x + dist_y;
3260 					int b_max = dist_y - dist_x;
3261 
3262 					/* Now determine the size along the edge, but due to the
3263 					 * chess board principle this counts double. */
3264 					a_max = abs(a_max + (a_max > 0 ? 2 : -2)) / 2;
3265 					b_max = abs(b_max + (b_max > 0 ? 2 : -2)) / 2;
3266 
3267 					/* We get a 1x1 on normal 2x1 rectangles, due to it being
3268 					 * a seen as two sides. As the result for actual building
3269 					 * will be the same as non-diagonal dragging revert to that
3270 					 * behaviour to give it a more normally looking size. */
3271 					if (a_max != 1 || b_max != 1) {
3272 						dx = a_max;
3273 						dy = b_max;
3274 					}
3275 				} else if (style & HT_RECT) {
3276 					if (dx == 1) {
3277 						style = HT_LINE | HT_DIR_Y;
3278 					} else if (dy == 1) {
3279 						style = HT_LINE | HT_DIR_X;
3280 					}
3281 				}
3282 
3283 				if (dx != 1 || dy != 1) {
3284 					int heightdiff = CalcHeightdiff(style, 0, t0, t1);
3285 
3286 					params[index++] = dx - (style & HT_POINT ? 1 : 0);
3287 					params[index++] = dy - (style & HT_POINT ? 1 : 0);
3288 					if (heightdiff != 0) params[index++] = heightdiff;
3289 				}
3290 
3291 				ShowMeasurementTooltips(measure_strings_area[index], index, params);
3292 			}
3293 			break;
3294 
3295 		default: NOT_REACHED();
3296 	}
3297 
3298 	_thd.selend.x = x;
3299 	_thd.selend.y = y;
3300 }
3301 
3302 /**
3303  * Handle the mouse while dragging for placement/resizing.
3304  * @return State of handling the event.
3305  */
VpHandlePlaceSizingDrag()3306 EventState VpHandlePlaceSizingDrag()
3307 {
3308 	if (_special_mouse_mode != WSM_SIZING && _special_mouse_mode != WSM_DRAGGING) return ES_NOT_HANDLED;
3309 
3310 	/* stop drag mode if the window has been closed */
3311 	Window *w = _thd.GetCallbackWnd();
3312 	if (w == nullptr) {
3313 		ResetObjectToPlace();
3314 		return ES_HANDLED;
3315 	}
3316 
3317 	/* while dragging execute the drag procedure of the corresponding window (mostly VpSelectTilesWithMethod() ) */
3318 	if (_left_button_down) {
3319 		if (_special_mouse_mode == WSM_DRAGGING) {
3320 			/* Only register a drag event when the mouse moved. */
3321 			if (_thd.new_pos.x == _thd.selstart.x && _thd.new_pos.y == _thd.selstart.y) return ES_HANDLED;
3322 			_thd.selstart.x = _thd.new_pos.x;
3323 			_thd.selstart.y = _thd.new_pos.y;
3324 		}
3325 
3326 		w->OnPlaceDrag(_thd.select_method, _thd.select_proc, GetTileBelowCursor());
3327 		return ES_HANDLED;
3328 	}
3329 
3330 	/* Mouse button released. */
3331 	_special_mouse_mode = WSM_NONE;
3332 	if (_special_mouse_mode == WSM_DRAGGING) return ES_HANDLED;
3333 
3334 	/* Keep the selected tool, but reset it to the original mode. */
3335 	HighLightStyle others = _thd.place_mode & ~(HT_DRAG_MASK | HT_DIR_MASK);
3336 	if ((_thd.next_drawstyle & HT_DRAG_MASK) == HT_RECT) {
3337 		_thd.place_mode = HT_RECT | others;
3338 	} else if (_thd.select_method & VPM_SIGNALDIRS) {
3339 		_thd.place_mode = HT_RECT | others;
3340 	} else if (_thd.select_method & VPM_RAILDIRS) {
3341 		_thd.place_mode = (_thd.select_method & ~VPM_RAILDIRS) ? _thd.next_drawstyle : (HT_RAIL | others);
3342 	} else {
3343 		_thd.place_mode = HT_POINT | others;
3344 	}
3345 	SetTileSelectSize(1, 1);
3346 
3347 	HideMeasurementTooltips();
3348 	w->OnPlaceMouseUp(_thd.select_method, _thd.select_proc, _thd.selend, TileVirtXY(_thd.selstart.x, _thd.selstart.y), TileVirtXY(_thd.selend.x, _thd.selend.y));
3349 
3350 	return ES_HANDLED;
3351 }
3352 
3353 /**
3354  * Change the cursor and mouse click/drag handling to a mode for performing special operations like tile area selection, object placement, etc.
3355  * @param icon New shape of the mouse cursor.
3356  * @param pal Palette to use.
3357  * @param mode Mode to perform.
3358  * @param w %Window requesting the mode change.
3359  */
SetObjectToPlaceWnd(CursorID icon,PaletteID pal,HighLightStyle mode,Window * w)3360 void SetObjectToPlaceWnd(CursorID icon, PaletteID pal, HighLightStyle mode, Window *w)
3361 {
3362 	SetObjectToPlace(icon, pal, mode, w->window_class, w->window_number);
3363 }
3364 
3365 #include "table/animcursors.h"
3366 
3367 /**
3368  * Change the cursor and mouse click/drag handling to a mode for performing special operations like tile area selection, object placement, etc.
3369  * @param icon New shape of the mouse cursor.
3370  * @param pal Palette to use.
3371  * @param mode Mode to perform.
3372  * @param window_class %Window class of the window requesting the mode change.
3373  * @param window_num Number of the window in its class requesting the mode change.
3374  */
SetObjectToPlace(CursorID icon,PaletteID pal,HighLightStyle mode,WindowClass window_class,WindowNumber window_num)3375 void SetObjectToPlace(CursorID icon, PaletteID pal, HighLightStyle mode, WindowClass window_class, WindowNumber window_num)
3376 {
3377 	if (_thd.window_class != WC_INVALID) {
3378 		/* Undo clicking on button and drag & drop */
3379 		Window *w = _thd.GetCallbackWnd();
3380 		/* Call the abort function, but set the window class to something
3381 		 * that will never be used to avoid infinite loops. Setting it to
3382 		 * the 'next' window class must not be done because recursion into
3383 		 * this function might in some cases reset the newly set object to
3384 		 * place or not properly reset the original selection. */
3385 		_thd.window_class = WC_INVALID;
3386 		if (w != nullptr) {
3387 			w->OnPlaceObjectAbort();
3388 			HideMeasurementTooltips();
3389 		}
3390 	}
3391 
3392 	/* Mark the old selection dirty, in case the selection shape or colour changes */
3393 	if ((_thd.drawstyle & HT_DRAG_MASK) != HT_NONE) SetSelectionTilesDirty();
3394 
3395 	SetTileSelectSize(1, 1);
3396 
3397 	_thd.make_square_red = false;
3398 
3399 	if (mode == HT_DRAG) { // HT_DRAG is for dragdropping trains in the depot window
3400 		mode = HT_NONE;
3401 		_special_mouse_mode = WSM_DRAGDROP;
3402 	} else {
3403 		_special_mouse_mode = WSM_NONE;
3404 	}
3405 
3406 	_thd.place_mode = mode;
3407 	_thd.window_class = window_class;
3408 	_thd.window_number = window_num;
3409 
3410 	if ((mode & HT_DRAG_MASK) == HT_SPECIAL) { // special tools, like tunnels or docks start with presizing mode
3411 		VpStartPreSizing();
3412 	}
3413 
3414 	if ((icon & ANIMCURSOR_FLAG) != 0) {
3415 		SetAnimatedMouseCursor(_animcursors[icon & ~ANIMCURSOR_FLAG]);
3416 	} else {
3417 		SetMouseCursor(icon, pal);
3418 	}
3419 
3420 }
3421 
3422 /** Reset the cursor and mouse mode handling back to default (normal cursor, only clicking in windows). */
ResetObjectToPlace()3423 void ResetObjectToPlace()
3424 {
3425 	SetObjectToPlace(SPR_CURSOR_MOUSE, PAL_NONE, HT_NONE, WC_MAIN_WINDOW, 0);
3426 }
3427 
GetViewportStationMiddle(const Viewport * vp,const Station * st)3428 Point GetViewportStationMiddle(const Viewport *vp, const Station *st)
3429 {
3430 	int x = TileX(st->xy) * TILE_SIZE;
3431 	int y = TileY(st->xy) * TILE_SIZE;
3432 	int z = GetSlopePixelZ(Clamp(x, 0, MapSizeX() * TILE_SIZE - 1), Clamp(y, 0, MapSizeY() * TILE_SIZE - 1));
3433 
3434 	Point p = RemapCoords(x, y, z);
3435 	p.x = UnScaleByZoom(p.x - vp->virtual_left, vp->zoom) + vp->left;
3436 	p.y = UnScaleByZoom(p.y - vp->virtual_top, vp->zoom) + vp->top;
3437 	return p;
3438 }
3439 
3440 /** Helper class for getting the best sprite sorter. */
3441 struct ViewportSSCSS {
3442 	VpSorterChecker fct_checker; ///< The check function.
3443 	VpSpriteSorter fct_sorter;   ///< The sorting function.
3444 };
3445 
3446 /** List of sorters ordered from best to worst. */
3447 static ViewportSSCSS _vp_sprite_sorters[] = {
3448 #ifdef WITH_SSE
3449 	{ &ViewportSortParentSpritesSSE41Checker, &ViewportSortParentSpritesSSE41 },
3450 #endif
3451 	{ &ViewportSortParentSpritesChecker, &ViewportSortParentSprites }
3452 };
3453 
3454 /** Choose the "best" sprite sorter and set _vp_sprite_sorter. */
InitializeSpriteSorter()3455 void InitializeSpriteSorter()
3456 {
3457 	for (uint i = 0; i < lengthof(_vp_sprite_sorters); i++) {
3458 		if (_vp_sprite_sorters[i].fct_checker()) {
3459 			_vp_sprite_sorter = _vp_sprite_sorters[i].fct_sorter;
3460 			break;
3461 		}
3462 	}
3463 	assert(_vp_sprite_sorter != nullptr);
3464 }
3465 
3466 /**
3467  * Scroll players main viewport.
3468  * @param tile tile to center viewport on
3469  * @param flags type of operation
3470  * @param p1 ViewportScrollTarget of scroll target
3471  * @param p2 company or client id depending on the target
3472  * @param text unused
3473  * @return the cost of this operation or an error
3474  */
CmdScrollViewport(TileIndex tile,DoCommandFlag flags,uint32 p1,uint32 p2,const std::string & text)3475 CommandCost CmdScrollViewport(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const std::string &text)
3476 {
3477 	if (_current_company != OWNER_DEITY) return CMD_ERROR;
3478 	ViewportScrollTarget target = (ViewportScrollTarget)p1;
3479 	switch (target) {
3480 		case VST_EVERYONE:
3481 			break;
3482 		case VST_COMPANY:
3483 			if (_local_company != (CompanyID)p2) return CommandCost();
3484 			break;
3485 		case VST_CLIENT:
3486 			if (_network_own_client_id != (ClientID)p2) return CommandCost();
3487 			break;
3488 		default:
3489 			return CMD_ERROR;
3490 	}
3491 
3492 	if (flags & DC_EXEC) {
3493 		ResetObjectToPlace();
3494 		ScrollMainWindowToTile(tile);
3495 	}
3496 	return CommandCost();
3497 }
3498 
MarkCatchmentTilesDirty()3499 static void MarkCatchmentTilesDirty()
3500 {
3501 	if (_viewport_highlight_town != nullptr) {
3502 		MarkWholeScreenDirty();
3503 		return;
3504 	}
3505 	if (_viewport_highlight_station != nullptr) {
3506 		if (_viewport_highlight_station->catchment_tiles.tile == INVALID_TILE) {
3507 			MarkWholeScreenDirty();
3508 			_viewport_highlight_station = nullptr;
3509 		} else {
3510 			BitmapTileIterator it(_viewport_highlight_station->catchment_tiles);
3511 			for (TileIndex tile = it; tile != INVALID_TILE; tile = ++it) {
3512 				MarkTileDirtyByTile(tile);
3513 			}
3514 		}
3515 	}
3516 }
3517 
3518 /**
3519  * Select or deselect station for coverage area highlight.
3520  * Selecting a station will deselect a town.
3521  * @param *st Station in question
3522  * @param sel Select or deselect given station
3523  */
SetViewportCatchmentStation(const Station * st,bool sel)3524 void SetViewportCatchmentStation(const Station *st, bool sel)
3525 {
3526 	if (_viewport_highlight_station != nullptr) SetWindowDirty(WC_STATION_VIEW, _viewport_highlight_station->index);
3527 	if (_viewport_highlight_town != nullptr) SetWindowDirty(WC_TOWN_VIEW, _viewport_highlight_town->index);
3528 	if (sel && _viewport_highlight_station != st) {
3529 		MarkCatchmentTilesDirty();
3530 		_viewport_highlight_station = st;
3531 		_viewport_highlight_town = nullptr;
3532 		MarkCatchmentTilesDirty();
3533 	} else if (!sel && _viewport_highlight_station == st) {
3534 		MarkCatchmentTilesDirty();
3535 		_viewport_highlight_station = nullptr;
3536 	}
3537 	if (_viewport_highlight_station != nullptr) SetWindowDirty(WC_STATION_VIEW, _viewport_highlight_station->index);
3538 }
3539 
3540 /**
3541  * Select or deselect town for coverage area highlight.
3542  * Selecting a town will deselect a station.
3543  * @param *t Town in question
3544  * @param sel Select or deselect given town
3545  */
SetViewportCatchmentTown(const Town * t,bool sel)3546 void SetViewportCatchmentTown(const Town *t, bool sel)
3547 {
3548 	if (_viewport_highlight_town != nullptr) SetWindowDirty(WC_TOWN_VIEW, _viewport_highlight_town->index);
3549 	if (_viewport_highlight_station != nullptr) SetWindowDirty(WC_STATION_VIEW, _viewport_highlight_station->index);
3550 	if (sel && _viewport_highlight_town != t) {
3551 		_viewport_highlight_station = nullptr;
3552 		_viewport_highlight_town = t;
3553 		MarkWholeScreenDirty();
3554 	} else if (!sel && _viewport_highlight_town == t) {
3555 		_viewport_highlight_town = nullptr;
3556 		MarkWholeScreenDirty();
3557 	}
3558 	if (_viewport_highlight_town != nullptr) SetWindowDirty(WC_TOWN_VIEW, _viewport_highlight_town->index);
3559 }
3560