4 * This file is part of OpenTTD.
5 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
6 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
7 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
11 * @file viewport.cpp Handling of all viewports.
14 * The in-game coordinate system looks like this *
30 * @defgroup vp_column_row Rows and columns in the viewport
32 * Columns are vertical sections of the viewport that are half a tile wide.
33 * The origin, i.e. column 0, is through the northern and southern most tile.
34 * This means that the column of e.g. Tile(0, 0) and Tile(100, 100) are in
35 * column number 0. The negative columns are towards the left of the screen,
36 * or towards the west, whereas the positive ones are towards respectively
38 * With half a tile wide is meant that the next column of tiles directly west
39 * or east of the centre line are respectively column -1 and 1. Their tile
40 * centers are only half a tile from the center of their adjoining tile when
41 * looking only at the X-coordinate.
60 * Rows are horizontal sections of the viewport, also half a tile wide.
61 * This time the nothern most tile on the map defines 0 and
62 * everything south of that has a positive number.
66 #include "landscape.h"
67 #include "viewport_func.h"
68 #include "station_base.h"
69 #include "waypoint_base.h"
71 #include "signs_base.h"
72 #include "signs_func.h"
73 #include "vehicle_base.h"
74 #include "vehicle_gui.h"
75 #include "blitter/factory.hpp"
76 #include "strings_func.h"
77 #include "zoom_func.h"
78 #include "vehicle_func.h"
79 #include "company_func.h"
80 #include "waypoint_func.h"
81 #include "window_func.h"
82 #include "tilehighlight_func.h"
83 #include "window_gui.h"
84 #include "linkgraph/linkgraph_gui.h"
85 #include "viewport_sprite_sorter.h"
86 #include "bridge_map.h"
90 #include "table/strings.h"
91 #include "table/string_colours.h"
93 #include "safeguards.h"
95 Point _tile_fract_coords
;
98 static const int MAX_TILE_EXTENT_LEFT
= ZOOM_LVL_BASE
* TILE_PIXELS
; ///< Maximum left extent of tile relative to north corner.
99 static const int MAX_TILE_EXTENT_RIGHT
= ZOOM_LVL_BASE
* TILE_PIXELS
; ///< Maximum right extent of tile relative to north corner.
100 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).
101 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).
103 struct StringSpriteToDraw
{
112 struct TileSpriteToDraw
{
115 const SubSprite
*sub
; ///< only draw a rectangular part of the sprite
116 int32 x
; ///< screen X coordinate of sprite
117 int32 y
; ///< screen Y coordinate of sprite
120 struct ChildScreenSpriteToDraw
{
123 const SubSprite
*sub
; ///< only draw a rectangular part of the sprite
126 int next
; ///< next child to draw (-1 at the end)
129 /** Enumeration of multi-part foundations */
130 enum FoundationPart
{
131 FOUNDATION_PART_NONE
= 0xFF, ///< Neither foundation nor groundsprite drawn yet.
132 FOUNDATION_PART_NORMAL
= 0, ///< First part (normal foundation or no foundation)
133 FOUNDATION_PART_HALFTILE
= 1, ///< Second part (halftile foundation)
138 * Mode of "sprite combining"
139 * @see StartSpriteCombine
141 enum SpriteCombineMode
{
142 SPRITE_COMBINE_NONE
, ///< Every #AddSortableSpriteToDraw start its own bounding box
143 SPRITE_COMBINE_PENDING
, ///< %Sprite combining will start with the next unclipped sprite.
144 SPRITE_COMBINE_ACTIVE
, ///< %Sprite combining is active. #AddSortableSpriteToDraw outputs child sprites.
147 typedef SmallVector
<TileSpriteToDraw
, 64> TileSpriteToDrawVector
;
148 typedef SmallVector
<StringSpriteToDraw
, 4> StringSpriteToDrawVector
;
149 typedef SmallVector
<ParentSpriteToDraw
, 64> ParentSpriteToDrawVector
;
150 typedef SmallVector
<ChildScreenSpriteToDraw
, 16> ChildScreenSpriteToDrawVector
;
152 /** Data structure storing rendering information */
153 struct ViewportDrawer
{
156 StringSpriteToDrawVector string_sprites_to_draw
;
157 TileSpriteToDrawVector tile_sprites_to_draw
;
158 ParentSpriteToDrawVector parent_sprites_to_draw
;
159 ParentSpriteToSortVector parent_sprites_to_sort
; ///< Parent sprite pointer array used for sorting
160 ChildScreenSpriteToDrawVector child_screen_sprites_to_draw
;
164 SpriteCombineMode combine_sprites
; ///< Current mode of "sprite combining". @see StartSpriteCombine
166 int foundation
[FOUNDATION_PART_END
]; ///< Foundation sprites (index into parent_sprites_to_draw).
167 FoundationPart foundation_part
; ///< Currently active foundation for ground sprite drawing.
168 int *last_foundation_child
[FOUNDATION_PART_END
]; ///< Tail of ChildSprite list of the foundations. (index into child_screen_sprites_to_draw)
169 Point foundation_offset
[FOUNDATION_PART_END
]; ///< Pixel offset for ground sprites on the foundations.
172 static void MarkViewportDirty(const ViewPort
*vp
, int left
, int top
, int right
, int bottom
);
174 static ViewportDrawer _vd
;
176 TileHighlightData _thd
;
177 static TileInfo
*_cur_ti
;
178 bool _draw_bounding_boxes
= false;
179 bool _draw_dirty_blocks
= false;
180 uint _dirty_block_colour
= 0;
181 static VpSpriteSorter _vp_sprite_sorter
= NULL
;
183 static Point
MapXYZToViewport(const ViewPort
*vp
, int x
, int y
, int z
)
185 Point p
= RemapCoords(x
, y
, z
);
186 p
.x
-= vp
->virtual_width
/ 2;
187 p
.y
-= vp
->virtual_height
/ 2;
191 void DeleteWindowViewport(Window
*w
)
193 if (w
->viewport
== NULL
) return;
195 delete w
->viewport
->overlay
;
201 * Initialize viewport of the window for use.
202 * @param w Window to use/display the viewport in
203 * @param x Offset of left edge of viewport with respect to left edge window \a w
204 * @param y Offset of top edge of viewport with respect to top edge window \a w
205 * @param width Width of the viewport
206 * @param height Height of the viewport
207 * @param follow_flags Flags controlling the viewport.
208 * - If bit 31 is set, the lower 20 bits are the vehicle that the viewport should follow.
209 * - If bit 31 is clear, it is a #TileIndex.
210 * @param zoom Zoomlevel to display
212 void InitializeWindowViewport(Window
*w
, int x
, int y
,
213 int width
, int height
, uint32 follow_flags
, ZoomLevel zoom
)
215 assert(w
->viewport
== NULL
);
217 ViewportData
*vp
= CallocT
<ViewportData
>(1);
219 vp
->left
= x
+ w
->left
;
220 vp
->top
= y
+ w
->top
;
224 vp
->zoom
= static_cast<ZoomLevel
>(Clamp(zoom
, _settings_client
.gui
.zoom_min
, _settings_client
.gui
.zoom_max
));
226 vp
->virtual_width
= ScaleByZoom(width
, zoom
);
227 vp
->virtual_height
= ScaleByZoom(height
, zoom
);
231 if (follow_flags
& 0x80000000) {
234 vp
->follow_vehicle
= (VehicleID
)(follow_flags
& 0xFFFFF);
235 veh
= Vehicle::Get(vp
->follow_vehicle
);
236 pt
= MapXYZToViewport(vp
, veh
->x_pos
, veh
->y_pos
, veh
->z_pos
);
238 uint x
= TileX(follow_flags
) * TILE_SIZE
;
239 uint y
= TileY(follow_flags
) * TILE_SIZE
;
241 vp
->follow_vehicle
= INVALID_VEHICLE
;
242 pt
= MapXYZToViewport(vp
, x
, y
, GetSlopePixelZ(x
, y
));
245 vp
->scrollpos_x
= pt
.x
;
246 vp
->scrollpos_y
= pt
.y
;
247 vp
->dest_scrollpos_x
= pt
.x
;
248 vp
->dest_scrollpos_y
= pt
.y
;
253 vp
->virtual_left
= 0; // pt.x;
254 vp
->virtual_top
= 0; // pt.y;
257 static Point _vp_move_offs
;
259 static void DoSetViewportPosition(const Window
*w
, int left
, int top
, int width
, int height
)
261 FOR_ALL_WINDOWS_FROM_BACK_FROM(w
, w
) {
262 if (left
+ width
> w
->left
&&
263 w
->left
+ w
->width
> left
&&
264 top
+ height
> w
->top
&&
265 w
->top
+ w
->height
> top
) {
267 if (left
< w
->left
) {
268 DoSetViewportPosition(w
, left
, top
, w
->left
- left
, height
);
269 DoSetViewportPosition(w
, left
+ (w
->left
- left
), top
, width
- (w
->left
- left
), height
);
273 if (left
+ width
> w
->left
+ w
->width
) {
274 DoSetViewportPosition(w
, left
, top
, (w
->left
+ w
->width
- left
), height
);
275 DoSetViewportPosition(w
, left
+ (w
->left
+ w
->width
- left
), top
, width
- (w
->left
+ w
->width
- left
), height
);
280 DoSetViewportPosition(w
, left
, top
, width
, (w
->top
- top
));
281 DoSetViewportPosition(w
, left
, top
+ (w
->top
- top
), width
, height
- (w
->top
- top
));
285 if (top
+ height
> w
->top
+ w
->height
) {
286 DoSetViewportPosition(w
, left
, top
, width
, (w
->top
+ w
->height
- top
));
287 DoSetViewportPosition(w
, left
, top
+ (w
->top
+ w
->height
- top
), width
, height
- (w
->top
+ w
->height
- top
));
296 int xo
= _vp_move_offs
.x
;
297 int yo
= _vp_move_offs
.y
;
299 if (abs(xo
) >= width
|| abs(yo
) >= height
) {
301 RedrawScreenRect(left
, top
, left
+ width
, top
+ height
);
305 GfxScroll(left
, top
, width
, height
, xo
, yo
);
308 RedrawScreenRect(left
, top
, xo
+ left
, top
+ height
);
312 RedrawScreenRect(left
+ width
+ xo
, top
, left
+ width
, top
+ height
);
317 RedrawScreenRect(left
, top
, width
+ left
, top
+ yo
);
319 RedrawScreenRect(left
, top
+ height
+ yo
, width
+ left
, top
+ height
);
324 static void SetViewportPosition(Window
*w
, int x
, int y
)
326 ViewPort
*vp
= w
->viewport
;
327 int old_left
= vp
->virtual_left
;
328 int old_top
= vp
->virtual_top
;
330 int left
, top
, width
, height
;
332 vp
->virtual_left
= x
;
335 /* Viewport is bound to its left top corner, so it must be rounded down (UnScaleByZoomLower)
336 * else glitch described in FS#1412 will happen (offset by 1 pixel with zoom level > NORMAL)
338 old_left
= UnScaleByZoomLower(old_left
, vp
->zoom
);
339 old_top
= UnScaleByZoomLower(old_top
, vp
->zoom
);
340 x
= UnScaleByZoomLower(x
, vp
->zoom
);
341 y
= UnScaleByZoomLower(y
, vp
->zoom
);
346 if (old_top
== 0 && old_left
== 0) return;
348 _vp_move_offs
.x
= old_left
;
349 _vp_move_offs
.y
= old_top
;
361 i
= left
+ width
- _screen
.width
;
362 if (i
>= 0) width
-= i
;
370 i
= top
+ height
- _screen
.height
;
371 if (i
>= 0) height
-= i
;
373 if (height
> 0) DoSetViewportPosition(w
->z_front
, left
, top
, width
, height
);
378 * Is a xy position inside the viewport of the window?
379 * @param w Window to examine its viewport
380 * @param x X coordinate of the xy position
381 * @param y Y coordinate of the xy position
382 * @return Pointer to the viewport if the xy position is in the viewport of the window,
383 * otherwise \c NULL is returned.
385 ViewPort
*IsPtInWindowViewport(const Window
*w
, int x
, int y
)
387 ViewPort
*vp
= w
->viewport
;
390 IsInsideMM(x
, vp
->left
, vp
->left
+ vp
->width
) &&
391 IsInsideMM(y
, vp
->top
, vp
->top
+ vp
->height
))
398 * Translate screen coordinate in a viewport to a tile coordinate
399 * @param vp Viewport that contains the (\a x, \a y) screen coordinate
400 * @param x Screen x coordinate
401 * @param y Screen y coordinate
402 * @param clamp_to_map Clamp the coordinate outside of the map to the closest tile within the map.
403 * @return Tile coordinate
405 Point
TranslateXYToTileCoord(const ViewPort
*vp
, int x
, int y
, bool clamp_to_map
)
411 if ( (uint
)(x
-= vp
->left
) >= (uint
)vp
->width
||
412 (uint
)(y
-= vp
->top
) >= (uint
)vp
->height
) {
417 x
= (ScaleByZoom(x
, vp
->zoom
) + vp
->virtual_left
) >> (2 + ZOOM_LVL_SHIFT
);
418 y
= (ScaleByZoom(y
, vp
->zoom
) + vp
->virtual_top
) >> (1 + ZOOM_LVL_SHIFT
);
424 /* Bring the coordinates near to a valid range. This is mostly due to the
425 * tiles on the north side of the map possibly being drawn too high due to
426 * the extra height levels. So at the top we allow a number of extra tiles.
427 * This number is based on the tile height and pixels. */
428 int extra_tiles
= CeilDiv(_settings_game
.construction
.max_heightlevel
* TILE_HEIGHT
, TILE_PIXELS
);
429 a
= Clamp(a
, -extra_tiles
* TILE_SIZE
, MapMaxX() * TILE_SIZE
- 1);
430 b
= Clamp(b
, -extra_tiles
* TILE_SIZE
, MapMaxY() * TILE_SIZE
- 1);
433 /* (a, b) is the X/Y-world coordinate that belongs to (x,y) if the landscape would be completely flat on height 0.
434 * Now find the Z-world coordinate by fix point iteration.
435 * This is a bit tricky because the tile height is non-continuous at foundations.
436 * The clicked point should be approached from the back, otherwise there are regions that are not clickable.
437 * (FOUNDATION_HALFTILE_LOWER on SLOPE_STEEP_S hides north halftile completely)
438 * So give it a z-malus of 4 in the first iterations.
442 int min_coord
= _settings_game
.construction
.freeform_edges
? TILE_SIZE
: 0;
444 for (int i
= 0; i
< 5; i
++) z
= GetSlopePixelZ(Clamp(a
+ max(z
, 4) - 4, min_coord
, MapMaxX() * TILE_SIZE
- 1), Clamp(b
+ max(z
, 4) - 4, min_coord
, MapMaxY() * TILE_SIZE
- 1)) / 2;
445 for (int malus
= 3; malus
> 0; malus
--) z
= GetSlopePixelZ(Clamp(a
+ max(z
, malus
) - malus
, min_coord
, MapMaxX() * TILE_SIZE
- 1), Clamp(b
+ max(z
, malus
) - malus
, min_coord
, MapMaxY() * TILE_SIZE
- 1)) / 2;
446 for (int i
= 0; i
< 5; i
++) z
= GetSlopePixelZ(Clamp(a
+ z
, min_coord
, MapMaxX() * TILE_SIZE
- 1), Clamp(b
+ z
, min_coord
, MapMaxY() * TILE_SIZE
- 1)) / 2;
449 pt
.x
= Clamp(a
+ z
, min_coord
, MapMaxX() * TILE_SIZE
- 1);
450 pt
.y
= Clamp(b
+ z
, min_coord
, MapMaxY() * TILE_SIZE
- 1);
459 /* When used for zooming, check area below current coordinates (x,y)
460 * and return the tile of the zoomed out/in position (zoom_x, zoom_y)
461 * when you just want the tile, make x = zoom_x and y = zoom_y */
462 static Point
GetTileFromScreenXY(int x
, int y
, int zoom_x
, int zoom_y
)
468 if ( (w
= FindWindowFromPt(x
, y
)) != NULL
&&
469 (vp
= IsPtInWindowViewport(w
, x
, y
)) != NULL
)
470 return TranslateXYToTileCoord(vp
, zoom_x
, zoom_y
);
476 Point
GetTileBelowCursor()
478 return GetTileFromScreenXY(_cursor
.pos
.x
, _cursor
.pos
.y
, _cursor
.pos
.x
, _cursor
.pos
.y
);
482 Point
GetTileZoomCenterWindow(bool in
, Window
* w
)
485 ViewPort
*vp
= w
->viewport
;
488 x
= ((_cursor
.pos
.x
- vp
->left
) >> 1) + (vp
->width
>> 2);
489 y
= ((_cursor
.pos
.y
- vp
->top
) >> 1) + (vp
->height
>> 2);
491 x
= vp
->width
- (_cursor
.pos
.x
- vp
->left
);
492 y
= vp
->height
- (_cursor
.pos
.y
- vp
->top
);
494 /* Get the tile below the cursor and center on the zoomed-out center */
495 return GetTileFromScreenXY(_cursor
.pos
.x
, _cursor
.pos
.y
, x
+ vp
->left
, y
+ vp
->top
);
499 * Update the status of the zoom-buttons according to the zoom-level
500 * of the viewport. This will update their status and invalidate accordingly
501 * @param w Window pointer to the window that has the zoom buttons
502 * @param vp pointer to the viewport whose zoom-level the buttons represent
503 * @param widget_zoom_in widget index for window with zoom-in button
504 * @param widget_zoom_out widget index for window with zoom-out button
506 void HandleZoomMessage(Window
*w
, const ViewPort
*vp
, byte widget_zoom_in
, byte widget_zoom_out
)
508 w
->SetWidgetDisabledState(widget_zoom_in
, vp
->zoom
<= _settings_client
.gui
.zoom_min
);
509 w
->SetWidgetDirty(widget_zoom_in
);
511 w
->SetWidgetDisabledState(widget_zoom_out
, vp
->zoom
>= _settings_client
.gui
.zoom_max
);
512 w
->SetWidgetDirty(widget_zoom_out
);
516 * Schedules a tile sprite for drawing.
518 * @param image the image to draw.
519 * @param pal the provided palette.
520 * @param x position x (world coordinates) of the sprite.
521 * @param y position y (world coordinates) of the sprite.
522 * @param z position z (world coordinates) of the sprite.
523 * @param sub Only draw a part of the sprite.
524 * @param extra_offs_x Pixel X offset for the sprite position.
525 * @param extra_offs_y Pixel Y offset for the sprite position.
527 static void AddTileSpriteToDraw(SpriteID image
, PaletteID pal
, int32 x
, int32 y
, int z
, const SubSprite
*sub
= NULL
, int extra_offs_x
= 0, int extra_offs_y
= 0)
529 assert((image
& SPRITE_MASK
) < MAX_SPRITES
);
531 TileSpriteToDraw
*ts
= _vd
.tile_sprites_to_draw
.Append();
535 Point pt
= RemapCoords(x
, y
, z
);
536 ts
->x
= pt
.x
+ extra_offs_x
;
537 ts
->y
= pt
.y
+ extra_offs_y
;
541 * Adds a child sprite to the active foundation.
543 * The pixel offset of the sprite relative to the ParentSprite is the sum of the offset passed to OffsetGroundSprite() and extra_offs_?.
545 * @param image the image to draw.
546 * @param pal the provided palette.
547 * @param sub Only draw a part of the sprite.
548 * @param foundation_part Foundation part.
549 * @param extra_offs_x Pixel X offset for the sprite position.
550 * @param extra_offs_y Pixel Y offset for the sprite position.
552 static void AddChildSpriteToFoundation(SpriteID image
, PaletteID pal
, const SubSprite
*sub
, FoundationPart foundation_part
, int extra_offs_x
, int extra_offs_y
)
554 assert(IsInsideMM(foundation_part
, 0, FOUNDATION_PART_END
));
555 assert(_vd
.foundation
[foundation_part
] != -1);
556 Point offs
= _vd
.foundation_offset
[foundation_part
];
558 /* Change the active ChildSprite list to the one of the foundation */
559 int *old_child
= _vd
.last_child
;
560 _vd
.last_child
= _vd
.last_foundation_child
[foundation_part
];
562 AddChildSpriteScreen(image
, pal
, offs
.x
+ extra_offs_x
, offs
.y
+ extra_offs_y
, false, sub
, false);
564 /* Switch back to last ChildSprite list */
565 _vd
.last_child
= old_child
;
569 * Draws a ground sprite at a specific world-coordinate relative to the current tile.
570 * If the current tile is drawn on top of a foundation the sprite is added as child sprite to the "foundation"-ParentSprite.
572 * @param image the image to draw.
573 * @param pal the provided palette.
574 * @param x position x (world coordinates) of the sprite relative to current tile.
575 * @param y position y (world coordinates) of the sprite relative to current tile.
576 * @param z position z (world coordinates) of the sprite relative to current tile.
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.
581 void DrawGroundSpriteAt(SpriteID image
, PaletteID pal
, int32 x
, int32 y
, int z
, const SubSprite
*sub
, int extra_offs_x
, int extra_offs_y
)
583 /* Switch to first foundation part, if no foundation was drawn */
584 if (_vd
.foundation_part
== FOUNDATION_PART_NONE
) _vd
.foundation_part
= FOUNDATION_PART_NORMAL
;
586 if (_vd
.foundation
[_vd
.foundation_part
] != -1) {
587 Point pt
= RemapCoords(x
, y
, z
);
588 AddChildSpriteToFoundation(image
, pal
, sub
, _vd
.foundation_part
, pt
.x
+ extra_offs_x
* ZOOM_LVL_BASE
, pt
.y
+ extra_offs_y
* ZOOM_LVL_BASE
);
590 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
);
595 * Draws a ground sprite for the current tile.
596 * If the current tile is drawn on top of a foundation the sprite is added as child sprite to the "foundation"-ParentSprite.
598 * @param image the image to draw.
599 * @param pal the provided palette.
600 * @param sub Only draw a part of the sprite.
601 * @param extra_offs_x Pixel X offset for the sprite position.
602 * @param extra_offs_y Pixel Y offset for the sprite position.
604 void DrawGroundSprite(SpriteID image
, PaletteID pal
, const SubSprite
*sub
, int extra_offs_x
, int extra_offs_y
)
606 DrawGroundSpriteAt(image
, pal
, 0, 0, 0, sub
, extra_offs_x
, extra_offs_y
);
610 * Called when a foundation has been drawn for the current tile.
611 * Successive ground sprites for the current tile will be drawn as child sprites of the "foundation"-ParentSprite, not as TileSprites.
613 * @param x sprite x-offset (screen coordinates) of ground sprites relative to the "foundation"-ParentSprite.
614 * @param y sprite y-offset (screen coordinates) of ground sprites relative to the "foundation"-ParentSprite.
616 void OffsetGroundSprite(int x
, int y
)
618 /* Switch to next foundation part */
619 switch (_vd
.foundation_part
) {
620 case FOUNDATION_PART_NONE
:
621 _vd
.foundation_part
= FOUNDATION_PART_NORMAL
;
623 case FOUNDATION_PART_NORMAL
:
624 _vd
.foundation_part
= FOUNDATION_PART_HALFTILE
;
626 default: NOT_REACHED();
629 /* _vd.last_child == NULL if foundation sprite was clipped by the viewport bounds */
630 if (_vd
.last_child
!= NULL
) _vd
.foundation
[_vd
.foundation_part
] = _vd
.parent_sprites_to_draw
.Length() - 1;
632 _vd
.foundation_offset
[_vd
.foundation_part
].x
= x
* ZOOM_LVL_BASE
;
633 _vd
.foundation_offset
[_vd
.foundation_part
].y
= y
* ZOOM_LVL_BASE
;
634 _vd
.last_foundation_child
[_vd
.foundation_part
] = _vd
.last_child
;
638 * Adds a child sprite to a parent sprite.
639 * In contrast to "AddChildSpriteScreen()" the sprite position is in world coordinates
641 * @param image the image to draw.
642 * @param pal the provided palette.
643 * @param x position x of the sprite.
644 * @param y position y of the sprite.
645 * @param z position z of the sprite.
646 * @param sub Only draw a part of the sprite.
648 static void AddCombinedSprite(SpriteID image
, PaletteID pal
, int x
, int y
, int z
, const SubSprite
*sub
)
650 Point pt
= RemapCoords(x
, y
, z
);
651 const Sprite
*spr
= GetSprite(image
& SPRITE_MASK
, ST_NORMAL
);
653 if (pt
.x
+ spr
->x_offs
>= _vd
.dpi
.left
+ _vd
.dpi
.width
||
654 pt
.x
+ spr
->x_offs
+ spr
->width
<= _vd
.dpi
.left
||
655 pt
.y
+ spr
->y_offs
>= _vd
.dpi
.top
+ _vd
.dpi
.height
||
656 pt
.y
+ spr
->y_offs
+ spr
->height
<= _vd
.dpi
.top
)
659 const ParentSpriteToDraw
*pstd
= _vd
.parent_sprites_to_draw
.End() - 1;
660 AddChildSpriteScreen(image
, pal
, pt
.x
- pstd
->left
, pt
.y
- pstd
->top
, false, sub
, false);
664 * Draw a (transparent) sprite at given coordinates with a given bounding box.
665 * 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.
666 * Bounding boxes with bb_offset_x == w or bb_offset_y == h or bb_offset_z == dz are allowed and produce thin slices.
668 * @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
669 * defined by the sprite offset in the grf file.
670 * However if modifying the sprite offsets is not suitable (e.g. when using existing graphics), the bounding box can be tuned by bb_offset.
672 * @pre w >= bb_offset_x, h >= bb_offset_y, dz >= bb_offset_z. Else w, h or dz are ignored.
674 * @param image the image to combine and draw,
675 * @param pal the provided palette,
676 * @param x position X (world) of the sprite,
677 * @param y position Y (world) of the sprite,
678 * @param w bounding box extent towards positive X (world),
679 * @param h bounding box extent towards positive Y (world),
680 * @param dz bounding box extent towards positive Z (world),
681 * @param z position Z (world) of the sprite,
682 * @param transparent if true, switch the palette between the provided palette and the transparent palette,
683 * @param bb_offset_x bounding box extent towards negative X (world),
684 * @param bb_offset_y bounding box extent towards negative Y (world),
685 * @param bb_offset_z bounding box extent towards negative Z (world)
686 * @param sub Only draw a part of the sprite.
688 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
)
690 int32 left
, right
, top
, bottom
;
692 assert((image
& SPRITE_MASK
) < MAX_SPRITES
);
694 /* make the sprites transparent with the right palette */
696 SetBit(image
, PALETTE_MODIFIER_TRANSPARENT
);
697 pal
= PALETTE_TO_TRANSPARENT
;
700 if (_vd
.combine_sprites
== SPRITE_COMBINE_ACTIVE
) {
701 AddCombinedSprite(image
, pal
, x
, y
, z
, sub
);
705 _vd
.last_child
= NULL
;
707 Point pt
= RemapCoords(x
, y
, z
);
708 int tmp_left
, tmp_top
, tmp_x
= pt
.x
, tmp_y
= pt
.y
;
710 /* Compute screen extents of sprite */
711 if (image
== SPR_EMPTY_BOUNDING_BOX
) {
712 left
= tmp_left
= RemapCoords(x
+ w
, y
+ bb_offset_y
, z
+ bb_offset_z
).x
;
713 right
= RemapCoords(x
+ bb_offset_x
, y
+ h
, z
+ bb_offset_z
).x
+ 1;
714 top
= tmp_top
= RemapCoords(x
+ bb_offset_x
, y
+ bb_offset_y
, z
+ dz
).y
;
715 bottom
= RemapCoords(x
+ w
, y
+ h
, z
+ bb_offset_z
).y
+ 1;
717 const Sprite
*spr
= GetSprite(image
& SPRITE_MASK
, ST_NORMAL
);
718 left
= tmp_left
= (pt
.x
+= spr
->x_offs
);
719 right
= (pt
.x
+ spr
->width
);
720 top
= tmp_top
= (pt
.y
+= spr
->y_offs
);
721 bottom
= (pt
.y
+ spr
->height
);
724 if (_draw_bounding_boxes
&& (image
!= SPR_EMPTY_BOUNDING_BOX
)) {
725 /* Compute maximal extents of sprite and its bounding box */
726 left
= min(left
, RemapCoords(x
+ w
, y
+ bb_offset_y
, z
+ bb_offset_z
).x
);
727 right
= max(right
, RemapCoords(x
+ bb_offset_x
, y
+ h
, z
+ bb_offset_z
).x
+ 1);
728 top
= min(top
, RemapCoords(x
+ bb_offset_x
, y
+ bb_offset_y
, z
+ dz
).y
);
729 bottom
= max(bottom
, RemapCoords(x
+ w
, y
+ h
, z
+ bb_offset_z
).y
+ 1);
732 /* Do not add the sprite to the viewport, if it is outside */
733 if (left
>= _vd
.dpi
.left
+ _vd
.dpi
.width
||
734 right
<= _vd
.dpi
.left
||
735 top
>= _vd
.dpi
.top
+ _vd
.dpi
.height
||
736 bottom
<= _vd
.dpi
.top
) {
740 ParentSpriteToDraw
*ps
= _vd
.parent_sprites_to_draw
.Append();
750 ps
->xmin
= x
+ bb_offset_x
;
751 ps
->xmax
= x
+ max(bb_offset_x
, w
) - 1;
753 ps
->ymin
= y
+ bb_offset_y
;
754 ps
->ymax
= y
+ max(bb_offset_y
, h
) - 1;
756 ps
->zmin
= z
+ bb_offset_z
;
757 ps
->zmax
= z
+ max(bb_offset_z
, dz
) - 1;
759 ps
->comparison_done
= false;
760 ps
->first_child
= -1;
762 _vd
.last_child
= &ps
->first_child
;
764 if (_vd
.combine_sprites
== SPRITE_COMBINE_PENDING
) _vd
.combine_sprites
= SPRITE_COMBINE_ACTIVE
;
768 * Starts a block of sprites, which are "combined" into a single bounding box.
770 * Subsequent calls to #AddSortableSpriteToDraw will be drawn into the same bounding box.
771 * That is: The first sprite that is not clipped by the viewport defines the bounding box, and
772 * the following sprites will be child sprites to that one.
775 * - The drawing order is definite. No other sprites will be sorted between those of the block.
776 * - You have to provide a valid bounding box for all sprites,
777 * as you won't know which one is the first non-clipped one.
778 * Preferable you use the same bounding box for all.
779 * - You cannot use #AddChildSpriteScreen inside the block, as its result will be indefinite.
781 * The block is terminated by #EndSpriteCombine.
783 * You cannot nest "combined" blocks.
785 void StartSpriteCombine()
787 assert(_vd
.combine_sprites
== SPRITE_COMBINE_NONE
);
788 _vd
.combine_sprites
= SPRITE_COMBINE_PENDING
;
792 * Terminates a block of sprites started by #StartSpriteCombine.
793 * Take a look there for details.
795 void EndSpriteCombine()
797 assert(_vd
.combine_sprites
!= SPRITE_COMBINE_NONE
);
798 _vd
.combine_sprites
= SPRITE_COMBINE_NONE
;
802 * Check if the parameter "check" is inside the interval between
803 * begin and end, including both begin and end.
804 * @note Whether \c begin or \c end is the biggest does not matter.
805 * This method will account for that.
806 * @param begin The begin of the interval.
807 * @param end The end of the interval.
808 * @param check The value to check.
810 static bool IsInRangeInclusive(int begin
, int end
, int check
)
812 if (begin
> end
) Swap(begin
, end
);
813 return begin
<= check
&& check
<= end
;
817 * Checks whether a point is inside the selected a diagonal rectangle given by _thd.size and _thd.pos
818 * @param x The x coordinate of the point to be checked.
819 * @param y The y coordinate of the point to be checked.
820 * @return True if the point is inside the rectangle, else false.
822 bool IsInsideRotatedRectangle(int x
, int y
)
824 int dist_a
= (_thd
.size
.x
+ _thd
.size
.y
); // Rotated coordinate system for selected rectangle.
825 int dist_b
= (_thd
.size
.x
- _thd
.size
.y
); // We don't have to divide by 2. It's all relative!
826 int a
= ((x
- _thd
.pos
.x
) + (y
- _thd
.pos
.y
)); // Rotated coordinate system for the point under scrutiny.
827 int b
= ((x
- _thd
.pos
.x
) - (y
- _thd
.pos
.y
));
829 /* Check if a and b are between 0 and dist_a or dist_b respectively. */
830 return IsInRangeInclusive(dist_a
, 0, a
) && IsInRangeInclusive(dist_b
, 0, b
);
834 * Add a child sprite to a parent sprite.
836 * @param image the image to draw.
837 * @param pal the provided palette.
838 * @param x sprite x-offset (screen coordinates) relative to parent sprite.
839 * @param y sprite y-offset (screen coordinates) relative to parent sprite.
840 * @param transparent if true, switch the palette between the provided palette and the transparent palette,
841 * @param sub Only draw a part of the sprite.
843 void AddChildSpriteScreen(SpriteID image
, PaletteID pal
, int x
, int y
, bool transparent
, const SubSprite
*sub
, bool scale
)
845 assert((image
& SPRITE_MASK
) < MAX_SPRITES
);
847 /* If the ParentSprite was clipped by the viewport bounds, do not draw the ChildSprites either */
848 if (_vd
.last_child
== NULL
) return;
850 /* make the sprites transparent with the right palette */
852 SetBit(image
, PALETTE_MODIFIER_TRANSPARENT
);
853 pal
= PALETTE_TO_TRANSPARENT
;
856 *_vd
.last_child
= _vd
.child_screen_sprites_to_draw
.Length();
858 ChildScreenSpriteToDraw
*cs
= _vd
.child_screen_sprites_to_draw
.Append();
862 cs
->x
= scale
? x
* ZOOM_LVL_BASE
: x
;
863 cs
->y
= scale
? y
* ZOOM_LVL_BASE
: y
;
866 /* Append the sprite to the active ChildSprite list.
867 * If the active ParentSprite is a foundation, update last_foundation_child as well.
868 * Note: ChildSprites of foundations are NOT sequential in the vector, as selection sprites are added at last. */
869 if (_vd
.last_foundation_child
[0] == _vd
.last_child
) _vd
.last_foundation_child
[0] = &cs
->next
;
870 if (_vd
.last_foundation_child
[1] == _vd
.last_child
) _vd
.last_foundation_child
[1] = &cs
->next
;
871 _vd
.last_child
= &cs
->next
;
874 static void AddStringToDraw(int x
, int y
, StringID string
, uint64 params_1
, uint64 params_2
, Colours colour
, uint16 width
)
877 StringSpriteToDraw
*ss
= _vd
.string_sprites_to_draw
.Append();
881 ss
->params
[0] = params_1
;
882 ss
->params
[1] = params_2
;
889 * Draws sprites between ground sprite and everything above.
891 * The sprite is either drawn as TileSprite or as ChildSprite of the active foundation.
893 * @param image the image to draw.
894 * @param pal the provided palette.
895 * @param ti TileInfo Tile that is being drawn
896 * @param z_offset Z offset relative to the groundsprite. Only used for the sprite position, not for sprite sorting.
897 * @param foundation_part Foundation part the sprite belongs to.
899 static void DrawSelectionSprite(SpriteID image
, PaletteID pal
, const TileInfo
*ti
, int z_offset
, FoundationPart foundation_part
)
901 /* FIXME: This is not totally valid for some autorail highlights that extend over the edges of the tile. */
902 if (_vd
.foundation
[foundation_part
] == -1) {
903 /* draw on real ground */
904 AddTileSpriteToDraw(image
, pal
, ti
->x
, ti
->y
, ti
->z
+ z_offset
);
906 /* draw on top of foundation */
907 AddChildSpriteToFoundation(image
, pal
, NULL
, foundation_part
, 0, -z_offset
* ZOOM_LVL_BASE
);
912 * Draws a selection rectangle on a tile.
914 * @param ti TileInfo Tile that is being drawn
915 * @param pal Palette to apply.
917 static void DrawTileSelectionRect(const TileInfo
*ti
, PaletteID pal
)
919 if (!IsValidTile(ti
->tile
)) return;
922 if (IsHalftileSlope(ti
->tileh
)) {
923 Corner halftile_corner
= GetHalftileSlopeCorner(ti
->tileh
);
924 SpriteID sel2
= SPR_HALFTILE_SELECTION_FLAT
+ halftile_corner
;
925 DrawSelectionSprite(sel2
, pal
, ti
, 7 + TILE_HEIGHT
, FOUNDATION_PART_HALFTILE
);
927 Corner opposite_corner
= OppositeCorner(halftile_corner
);
928 if (IsSteepSlope(ti
->tileh
)) {
929 sel
= SPR_HALFTILE_SELECTION_DOWN
;
931 sel
= ((ti
->tileh
& SlopeWithOneCornerRaised(opposite_corner
)) != 0 ? SPR_HALFTILE_SELECTION_UP
: SPR_HALFTILE_SELECTION_FLAT
);
933 sel
+= opposite_corner
;
935 sel
= SPR_SELECT_TILE
+ SlopeToSpriteOffset(ti
->tileh
);
937 DrawSelectionSprite(sel
, pal
, ti
, 7, FOUNDATION_PART_NORMAL
);
940 static bool IsPartOfAutoLine(int px
, int py
)
942 px
-= _thd
.selstart
.x
;
943 py
-= _thd
.selstart
.y
;
945 if ((_thd
.drawstyle
& HT_DRAG_MASK
) != HT_LINE
) return false;
947 switch (_thd
.drawstyle
& HT_DIR_MASK
) {
948 case HT_DIR_X
: return py
== 0; // x direction
949 case HT_DIR_Y
: return px
== 0; // y direction
950 case HT_DIR_HU
: return px
== -py
|| px
== -py
- 16; // horizontal upper
951 case HT_DIR_HL
: return px
== -py
|| px
== -py
+ 16; // horizontal lower
952 case HT_DIR_VL
: return px
== py
|| px
== py
+ 16; // vertical left
953 case HT_DIR_VR
: return px
== py
|| px
== py
- 16; // vertical right
959 /* [direction][side] */
960 static const HighLightStyle _autorail_type
[6][2] = {
961 { HT_DIR_X
, HT_DIR_X
},
962 { HT_DIR_Y
, HT_DIR_Y
},
963 { HT_DIR_HU
, HT_DIR_HL
},
964 { HT_DIR_HL
, HT_DIR_HU
},
965 { HT_DIR_VL
, HT_DIR_VR
},
966 { HT_DIR_VR
, HT_DIR_VL
}
969 #include "table/autorail.h"
972 * Draws autorail highlights.
974 * @param *ti TileInfo Tile that is being drawn
975 * @param autorail_type Offset into _AutorailTilehSprite[][]
977 static void DrawAutorailSelection(const TileInfo
*ti
, uint autorail_type
)
983 FoundationPart foundation_part
= FOUNDATION_PART_NORMAL
;
984 Slope autorail_tileh
= RemoveHalftileSlope(ti
->tileh
);
985 if (IsHalftileSlope(ti
->tileh
)) {
986 static const uint _lower_rail
[4] = { 5U, 2U, 4U, 3U };
987 Corner halftile_corner
= GetHalftileSlopeCorner(ti
->tileh
);
988 if (autorail_type
!= _lower_rail
[halftile_corner
]) {
989 foundation_part
= FOUNDATION_PART_HALFTILE
;
990 /* Here we draw the highlights of the "three-corners-raised"-slope. That looks ok to me. */
991 autorail_tileh
= SlopeWithThreeCornersRaised(OppositeCorner(halftile_corner
));
995 offset
= _AutorailTilehSprite
[autorail_tileh
][autorail_type
];
997 image
= SPR_AUTORAIL_BASE
+ offset
;
1000 image
= SPR_AUTORAIL_BASE
- offset
;
1001 pal
= PALETTE_SEL_TILE_RED
;
1004 DrawSelectionSprite(image
, _thd
.make_square_red
? PALETTE_SEL_TILE_RED
: pal
, ti
, 7, foundation_part
);
1008 * Checks if the specified tile is selected and if so draws selection using correct selectionstyle.
1009 * @param *ti TileInfo Tile that is being drawn
1011 static void DrawTileSelection(const TileInfo
*ti
)
1013 /* Draw a red error square? */
1014 bool is_redsq
= _thd
.redsq
== ti
->tile
;
1015 if (is_redsq
) DrawTileSelectionRect(ti
, PALETTE_TILE_RED_PULSATING
);
1017 /* No tile selection active? */
1018 if ((_thd
.drawstyle
& HT_DRAG_MASK
) == HT_NONE
) return;
1020 if (_thd
.diagonal
) { // We're drawing a 45 degrees rotated (diagonal) rectangle
1021 if (IsInsideRotatedRectangle((int)ti
->x
, (int)ti
->y
)) goto draw_inner
;
1025 /* Inside the inner area? */
1026 if (IsInsideBS(ti
->x
, _thd
.pos
.x
, _thd
.size
.x
) &&
1027 IsInsideBS(ti
->y
, _thd
.pos
.y
, _thd
.size
.y
)) {
1029 if (_thd
.drawstyle
& HT_RECT
) {
1030 if (!is_redsq
) DrawTileSelectionRect(ti
, _thd
.make_square_red
? PALETTE_SEL_TILE_RED
: PAL_NONE
);
1031 } else if (_thd
.drawstyle
& HT_POINT
) {
1032 /* Figure out the Z coordinate for the single dot. */
1034 FoundationPart foundation_part
= FOUNDATION_PART_NORMAL
;
1035 if (ti
->tileh
& SLOPE_N
) {
1037 if (RemoveHalftileSlope(ti
->tileh
) == SLOPE_STEEP_N
) z
+= TILE_HEIGHT
;
1039 if (IsHalftileSlope(ti
->tileh
)) {
1040 Corner halftile_corner
= GetHalftileSlopeCorner(ti
->tileh
);
1041 if ((halftile_corner
== CORNER_W
) || (halftile_corner
== CORNER_E
)) z
+= TILE_HEIGHT
;
1042 if (halftile_corner
!= CORNER_S
) {
1043 foundation_part
= FOUNDATION_PART_HALFTILE
;
1044 if (IsSteepSlope(ti
->tileh
)) z
-= TILE_HEIGHT
;
1047 DrawSelectionSprite(_cur_dpi
->zoom
<= ZOOM_LVL_DETAIL
? SPR_DOT
: SPR_DOT_SMALL
, PAL_NONE
, ti
, z
, foundation_part
);
1048 } else if (_thd
.drawstyle
& HT_RAIL
) {
1049 /* autorail highlight piece under cursor */
1050 HighLightStyle type
= _thd
.drawstyle
& HT_DIR_MASK
;
1051 assert(type
< HT_DIR_END
);
1052 DrawAutorailSelection(ti
, _autorail_type
[type
][0]);
1053 } else if (IsPartOfAutoLine(ti
->x
, ti
->y
)) {
1054 /* autorail highlighting long line */
1055 HighLightStyle dir
= _thd
.drawstyle
& HT_DIR_MASK
;
1058 if (dir
== HT_DIR_X
|| dir
== HT_DIR_Y
) {
1061 TileIndex start
= TileVirtXY(_thd
.selstart
.x
, _thd
.selstart
.y
);
1062 side
= Delta(Delta(TileX(start
), TileX(ti
->tile
)), Delta(TileY(start
), TileY(ti
->tile
)));
1065 DrawAutorailSelection(ti
, _autorail_type
[dir
][side
]);
1070 /* Check if it's inside the outer area? */
1071 if (!is_redsq
&& _thd
.outersize
.x
> 0 &&
1072 IsInsideBS(ti
->x
, _thd
.pos
.x
+ _thd
.offs
.x
, _thd
.size
.x
+ _thd
.outersize
.x
) &&
1073 IsInsideBS(ti
->y
, _thd
.pos
.y
+ _thd
.offs
.y
, _thd
.size
.y
+ _thd
.outersize
.y
)) {
1074 /* Draw a blue rect. */
1075 DrawTileSelectionRect(ti
, PALETTE_SEL_TILE_BLUE
);
1081 * Returns the y coordinate in the viewport coordinate system where the given
1083 * @param tile Any tile.
1084 * @return The viewport y coordinate where the tile is painted.
1086 static int GetViewportY(Point tile
)
1088 /* Each increment in X or Y direction moves down by half a tile, i.e. TILE_PIXELS / 2. */
1089 return (tile
.y
* (int)(TILE_PIXELS
/ 2) + tile
.x
* (int)(TILE_PIXELS
/ 2) - TilePixelHeightOutsideMap(tile
.x
, tile
.y
)) << ZOOM_LVL_SHIFT
;
1093 * Add the landscape to the viewport, i.e. all ground tiles and buildings.
1095 static void ViewportAddLandscape()
1097 assert(_vd
.dpi
.top
<= _vd
.dpi
.top
+ _vd
.dpi
.height
);
1098 assert(_vd
.dpi
.left
<= _vd
.dpi
.left
+ _vd
.dpi
.width
);
1100 Point upper_left
= InverseRemapCoords(_vd
.dpi
.left
, _vd
.dpi
.top
);
1101 Point upper_right
= InverseRemapCoords(_vd
.dpi
.left
+ _vd
.dpi
.width
, _vd
.dpi
.top
);
1103 /* Transformations between tile coordinates and viewport rows/columns: See vp_column_row
1106 * x = (row - column) / 2
1107 * y = (row + column) / 2
1108 * Note: (row, columns) pairs are only valid, if they are both even or both odd.
1111 /* Columns overlap with neighbouring columns by a half tile.
1112 * - Left column is column of upper_left (rounded down) and one column to the left.
1113 * - Right column is column of upper_right (rounded up) and one column to the right.
1114 * Note: Integer-division does not round down for negative numbers, so ensure rounding with another increment/decrement.
1116 int left_column
= (upper_left
.y
- upper_left
.x
) / (int)TILE_SIZE
- 2;
1117 int right_column
= (upper_right
.y
- upper_right
.x
) / (int)TILE_SIZE
+ 2;
1119 int potential_bridge_height
= ZOOM_LVL_BASE
* TILE_HEIGHT
* _settings_game
.construction
.max_bridge_height
;
1121 /* Rows overlap with neighbouring rows by a half tile.
1122 * The first row that could possibly be visible is the row above upper_left (if it is at height 0).
1123 * Due to integer-division not rounding down for negative numbers, we need another decrement.
1125 int row
= (upper_left
.x
+ upper_left
.y
) / (int)TILE_SIZE
- 2;
1126 bool last_row
= false;
1127 for (; !last_row
; row
++) {
1129 for (int column
= left_column
; column
<= right_column
; column
++) {
1130 /* Valid row/column? */
1131 if ((row
+ column
) % 2 != 0) continue;
1134 tilecoord
.x
= (row
- column
) / 2;
1135 tilecoord
.y
= (row
+ column
) / 2;
1136 assert(column
== tilecoord
.y
- tilecoord
.x
);
1137 assert(row
== tilecoord
.y
+ tilecoord
.x
);
1141 _cur_ti
= &tile_info
;
1142 tile_info
.x
= tilecoord
.x
* TILE_SIZE
; // FIXME tile_info should use signed integers
1143 tile_info
.y
= tilecoord
.y
* TILE_SIZE
;
1145 if (IsInsideBS(tilecoord
.x
, 0, MapSizeX()) && IsInsideBS(tilecoord
.y
, 0, MapSizeY())) {
1146 /* This includes the south border at MapMaxX / MapMaxY. When terraforming we still draw tile selections there. */
1147 tile_info
.tile
= TileXY(tilecoord
.x
, tilecoord
.y
);
1148 tile_type
= GetTileType(tile_info
.tile
);
1150 tile_info
.tile
= INVALID_TILE
;
1151 tile_type
= MP_VOID
;
1154 if (tile_type
!= MP_VOID
) {
1155 /* We are inside the map => paint landscape. */
1156 tile_info
.tileh
= GetTilePixelSlope(tile_info
.tile
, &tile_info
.z
);
1158 /* We are outside the map => paint black. */
1159 tile_info
.tileh
= GetTilePixelSlopeOutsideMap(tilecoord
.x
, tilecoord
.y
, &tile_info
.z
);
1162 int viewport_y
= GetViewportY(tilecoord
);
1164 if (viewport_y
+ MAX_TILE_EXTENT_BOTTOM
< _vd
.dpi
.top
) {
1165 /* The tile in this column is not visible yet.
1166 * Tiles in other columns may be visible, but we need more rows in any case. */
1171 int min_visible_height
= viewport_y
- (_vd
.dpi
.top
+ _vd
.dpi
.height
);
1172 bool tile_visible
= min_visible_height
<= 0;
1174 if (tile_type
!= MP_VOID
) {
1175 /* Is tile with buildings visible? */
1176 if (min_visible_height
< MAX_TILE_EXTENT_TOP
) tile_visible
= true;
1178 if (IsBridgeAbove(tile_info
.tile
)) {
1179 /* Is the bridge visible? */
1180 TileIndex bridge_tile
= GetNorthernBridgeEnd(tile_info
.tile
);
1181 int bridge_height
= ZOOM_LVL_BASE
* (GetBridgePixelHeight(bridge_tile
) - TilePixelHeight(tile_info
.tile
));
1182 if (min_visible_height
< bridge_height
+ MAX_TILE_EXTENT_TOP
) tile_visible
= true;
1185 /* Would a higher bridge on a more southern tile be visible?
1186 * If yes, we need to loop over more rows to possibly find one. */
1187 if (min_visible_height
< potential_bridge_height
+ MAX_TILE_EXTENT_TOP
) last_row
= false;
1189 /* Outside of map. If we are on the north border of the map, there may still be a bridge visible,
1190 * so we need to loop over more rows to possibly find one. */
1191 if ((tilecoord
.x
<= 0 || tilecoord
.y
<= 0) && min_visible_height
< potential_bridge_height
+ MAX_TILE_EXTENT_TOP
) last_row
= false;
1196 _vd
.foundation_part
= FOUNDATION_PART_NONE
;
1197 _vd
.foundation
[0] = -1;
1198 _vd
.foundation
[1] = -1;
1199 _vd
.last_foundation_child
[0] = NULL
;
1200 _vd
.last_foundation_child
[1] = NULL
;
1202 _tile_type_procs
[tile_type
]->draw_tile_proc(&tile_info
);
1203 if (tile_info
.tile
!= INVALID_TILE
) DrawTileSelection(&tile_info
);
1210 * Add a string to draw in the viewport
1211 * @param dpi current viewport area
1212 * @param small_from Zoomlevel from when the small font should be used
1213 * @param sign sign position and dimension
1214 * @param string_normal String for normal and 2x zoom level
1215 * @param string_small String for 4x and 8x zoom level
1216 * @param string_small_shadow Shadow string for 4x and 8x zoom level; or #STR_NULL if no shadow
1217 * @param colour colour of the sign background; or INVALID_COLOUR if transparent
1219 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
)
1221 bool small
= dpi
->zoom
>= small_from
;
1223 int left
= dpi
->left
;
1225 int right
= left
+ dpi
->width
;
1226 int bottom
= top
+ dpi
->height
;
1228 int sign_height
= ScaleByZoom(VPSM_TOP
+ FONT_HEIGHT_NORMAL
+ VPSM_BOTTOM
, dpi
->zoom
);
1229 int sign_half_width
= ScaleByZoom((small
? sign
->width_small
: sign
->width_normal
) / 2, dpi
->zoom
);
1231 if (bottom
< sign
->top
||
1232 top
> sign
->top
+ sign_height
||
1233 right
< sign
->center
- sign_half_width
||
1234 left
> sign
->center
+ sign_half_width
) {
1239 AddStringToDraw(sign
->center
- sign_half_width
, sign
->top
, string_normal
, params_1
, params_2
, colour
, sign
->width_normal
);
1241 int shadow_offset
= 0;
1242 if (string_small_shadow
!= STR_NULL
) {
1244 AddStringToDraw(sign
->center
- sign_half_width
+ shadow_offset
, sign
->top
, string_small_shadow
, params_1
, params_2
, INVALID_COLOUR
, sign
->width_small
);
1246 AddStringToDraw(sign
->center
- sign_half_width
, sign
->top
- shadow_offset
, string_small
, params_1
, params_2
,
1247 colour
, sign
->width_small
| 0x8000);
1251 static void ViewportAddTownNames(DrawPixelInfo
*dpi
)
1253 if (!HasBit(_display_opt
, DO_SHOW_TOWN_NAMES
) || _game_mode
== GM_MENU
) return;
1257 ViewportAddString(dpi
, ZOOM_LVL_OUT_16X
, &t
->cache
.sign
,
1258 _settings_client
.gui
.population_in_label
? STR_VIEWPORT_TOWN_POP
: STR_VIEWPORT_TOWN
,
1259 STR_VIEWPORT_TOWN_TINY_WHITE
, STR_VIEWPORT_TOWN_TINY_BLACK
,
1260 t
->index
, t
->cache
.population
);
1265 static void ViewportAddStationNames(DrawPixelInfo
*dpi
)
1267 if (!(HasBit(_display_opt
, DO_SHOW_STATION_NAMES
) || HasBit(_display_opt
, DO_SHOW_WAYPOINT_NAMES
)) || _game_mode
== GM_MENU
) return;
1269 const BaseStation
*st
;
1270 FOR_ALL_BASE_STATIONS(st
) {
1271 /* Check whether the base station is a station or a waypoint */
1272 bool is_station
= Station::IsExpected(st
);
1274 /* Don't draw if the display options are disabled */
1275 if (!HasBit(_display_opt
, is_station
? DO_SHOW_STATION_NAMES
: DO_SHOW_WAYPOINT_NAMES
)) continue;
1277 /* Don't draw if station is owned by another company and competitor station names are hidden. Stations owned by none are never ignored. */
1278 if (!HasBit(_display_opt
, DO_SHOW_COMPETITOR_SIGNS
) && _local_company
!= st
->owner
&& st
->owner
!= OWNER_NONE
) continue;
1280 ViewportAddString(dpi
, ZOOM_LVL_OUT_16X
, &st
->sign
,
1281 is_station
? STR_VIEWPORT_STATION
: STR_VIEWPORT_WAYPOINT
,
1282 (is_station
? STR_VIEWPORT_STATION
: STR_VIEWPORT_WAYPOINT
) + 1, STR_NULL
,
1283 st
->index
, st
->facilities
, (st
->owner
== OWNER_NONE
|| !st
->IsInUse()) ? COLOUR_GREY
: _company_colours
[st
->owner
]);
1288 static void ViewportAddSigns(DrawPixelInfo
*dpi
)
1290 /* Signs are turned off or are invisible */
1291 if (!HasBit(_display_opt
, DO_SHOW_SIGNS
) || IsInvisibilitySet(TO_SIGNS
)) return;
1295 /* Don't draw if sign is owned by another company and competitor signs should be hidden.
1296 * Note: It is intentional that also signs owned by OWNER_NONE are hidden. Bankrupt
1297 * companies can leave OWNER_NONE signs after them. */
1298 if (!HasBit(_display_opt
, DO_SHOW_COMPETITOR_SIGNS
) && _local_company
!= si
->owner
&& si
->owner
!= OWNER_DEITY
) continue;
1300 ViewportAddString(dpi
, ZOOM_LVL_OUT_16X
, &si
->sign
,
1302 (IsTransparencySet(TO_SIGNS
) || si
->owner
== OWNER_DEITY
) ? STR_VIEWPORT_SIGN_SMALL_WHITE
: STR_VIEWPORT_SIGN_SMALL_BLACK
, STR_NULL
,
1303 si
->index
, 0, (si
->owner
== OWNER_NONE
) ? COLOUR_GREY
: (si
->owner
== OWNER_DEITY
? INVALID_COLOUR
: _company_colours
[si
->owner
]));
1308 * Update the position of the viewport sign.
1309 * @param center the (preferred) center of the viewport sign
1310 * @param top the new top of the sign
1311 * @param str the string to show in the sign
1312 * @param str_small the string to show when zoomed out. STR_NULL means same as \a str
1314 void ViewportSign::UpdatePosition(int center
, int top
, StringID str
, StringID str_small
)
1316 if (this->width_normal
!= 0) this->MarkDirty();
1320 char buffer
[DRAW_STRING_BUFFER
];
1322 GetString(buffer
, str
, lastof(buffer
));
1323 this->width_normal
= VPSM_LEFT
+ Align(GetStringBoundingBox(buffer
).width
, 2) + VPSM_RIGHT
;
1324 this->center
= center
;
1326 /* zoomed out version */
1327 if (str_small
!= STR_NULL
) {
1328 GetString(buffer
, str_small
, lastof(buffer
));
1330 this->width_small
= VPSM_LEFT
+ Align(GetStringBoundingBox(buffer
, FS_SMALL
).width
, 2) + VPSM_RIGHT
;
1336 * Mark the sign dirty in all viewports.
1337 * @param maxzoom Maximum %ZoomLevel at which the text is visible.
1341 void ViewportSign::MarkDirty(ZoomLevel maxzoom
) const
1343 Rect zoomlevels
[ZOOM_LVL_COUNT
];
1345 for (ZoomLevel zoom
= ZOOM_LVL_BEGIN
; zoom
!= ZOOM_LVL_END
; zoom
++) {
1346 /* FIXME: This doesn't switch to width_small when appropriate. */
1347 zoomlevels
[zoom
].left
= this->center
- ScaleByZoom(this->width_normal
/ 2 + 1, zoom
);
1348 zoomlevels
[zoom
].top
= this->top
- ScaleByZoom(1, zoom
);
1349 zoomlevels
[zoom
].right
= this->center
+ ScaleByZoom(this->width_normal
/ 2 + 1, zoom
);
1350 zoomlevels
[zoom
].bottom
= this->top
+ ScaleByZoom(VPSM_TOP
+ FONT_HEIGHT_NORMAL
+ VPSM_BOTTOM
+ 1, zoom
);
1354 FOR_ALL_WINDOWS_FROM_BACK(w
) {
1355 ViewPort
*vp
= w
->viewport
;
1356 if (vp
!= NULL
&& vp
->zoom
<= maxzoom
) {
1357 assert(vp
->width
!= 0);
1358 Rect
&zl
= zoomlevels
[vp
->zoom
];
1359 MarkViewportDirty(vp
, zl
.left
, zl
.top
, zl
.right
, zl
.bottom
);
1364 static void ViewportDrawTileSprites(const TileSpriteToDrawVector
*tstdv
)
1366 const TileSpriteToDraw
*tsend
= tstdv
->End();
1367 for (const TileSpriteToDraw
*ts
= tstdv
->Begin(); ts
!= tsend
; ++ts
) {
1368 DrawSpriteViewport(ts
->image
, ts
->pal
, ts
->x
, ts
->y
, ts
->sub
);
1372 /** This fallback sprite checker always exists. */
1373 static bool ViewportSortParentSpritesChecker()
1378 /** Sort parent sprites pointer array */
1379 static void ViewportSortParentSprites(ParentSpriteToSortVector
*psdv
)
1381 ParentSpriteToDraw
**psdvend
= psdv
->End();
1382 ParentSpriteToDraw
**psd
= psdv
->Begin();
1383 while (psd
!= psdvend
) {
1384 ParentSpriteToDraw
*ps
= *psd
;
1386 if (ps
->comparison_done
) {
1391 ps
->comparison_done
= true;
1393 for (ParentSpriteToDraw
**psd2
= psd
+ 1; psd2
!= psdvend
; psd2
++) {
1394 ParentSpriteToDraw
*ps2
= *psd2
;
1396 if (ps2
->comparison_done
) continue;
1398 /* Decide which comparator to use, based on whether the bounding
1401 if (ps
->xmax
>= ps2
->xmin
&& ps
->xmin
<= ps2
->xmax
&& // overlap in X?
1402 ps
->ymax
>= ps2
->ymin
&& ps
->ymin
<= ps2
->ymax
&& // overlap in Y?
1403 ps
->zmax
>= ps2
->zmin
&& ps
->zmin
<= ps2
->zmax
) { // overlap in Z?
1404 /* Use X+Y+Z as the sorting order, so sprites closer to the bottom of
1405 * the screen and with higher Z elevation, are drawn in front.
1406 * Here X,Y,Z are the coordinates of the "center of mass" of the sprite,
1407 * i.e. X=(left+right)/2, etc.
1408 * However, since we only care about order, don't actually divide / 2
1410 if (ps
->xmin
+ ps
->xmax
+ ps
->ymin
+ ps
->ymax
+ ps
->zmin
+ ps
->zmax
<=
1411 ps2
->xmin
+ ps2
->xmax
+ ps2
->ymin
+ ps2
->ymax
+ ps2
->zmin
+ ps2
->zmax
) {
1415 /* We only change the order, if it is definite.
1416 * I.e. every single order of X, Y, Z says ps2 is behind ps or they overlap.
1417 * That is: If one partial order says ps behind ps2, do not change the order.
1419 if (ps
->xmax
< ps2
->xmin
||
1420 ps
->ymax
< ps2
->ymin
||
1421 ps
->zmax
< ps2
->zmin
) {
1426 /* Move ps2 in front of ps */
1427 ParentSpriteToDraw
*temp
= ps2
;
1428 for (ParentSpriteToDraw
**psd3
= psd2
; psd3
> psd
; psd3
--) {
1429 *psd3
= *(psd3
- 1);
1436 static void ViewportDrawParentSprites(const ParentSpriteToSortVector
*psd
, const ChildScreenSpriteToDrawVector
*csstdv
)
1438 const ParentSpriteToDraw
* const *psd_end
= psd
->End();
1439 for (const ParentSpriteToDraw
* const *it
= psd
->Begin(); it
!= psd_end
; it
++) {
1440 const ParentSpriteToDraw
*ps
= *it
;
1441 if (ps
->image
!= SPR_EMPTY_BOUNDING_BOX
) DrawSpriteViewport(ps
->image
, ps
->pal
, ps
->x
, ps
->y
, ps
->sub
);
1443 int child_idx
= ps
->first_child
;
1444 while (child_idx
>= 0) {
1445 const ChildScreenSpriteToDraw
*cs
= csstdv
->Get(child_idx
);
1446 child_idx
= cs
->next
;
1447 DrawSpriteViewport(cs
->image
, cs
->pal
, ps
->left
+ cs
->x
, ps
->top
+ cs
->y
, cs
->sub
);
1453 * Draws the bounding boxes of all ParentSprites
1454 * @param psd Array of ParentSprites
1456 static void ViewportDrawBoundingBoxes(const ParentSpriteToSortVector
*psd
)
1458 const ParentSpriteToDraw
* const *psd_end
= psd
->End();
1459 for (const ParentSpriteToDraw
* const *it
= psd
->Begin(); it
!= psd_end
; it
++) {
1460 const ParentSpriteToDraw
*ps
= *it
;
1461 Point pt1
= RemapCoords(ps
->xmax
+ 1, ps
->ymax
+ 1, ps
->zmax
+ 1); // top front corner
1462 Point pt2
= RemapCoords(ps
->xmin
, ps
->ymax
+ 1, ps
->zmax
+ 1); // top left corner
1463 Point pt3
= RemapCoords(ps
->xmax
+ 1, ps
->ymin
, ps
->zmax
+ 1); // top right corner
1464 Point pt4
= RemapCoords(ps
->xmax
+ 1, ps
->ymax
+ 1, ps
->zmin
); // bottom front corner
1466 DrawBox( pt1
.x
, pt1
.y
,
1467 pt2
.x
- pt1
.x
, pt2
.y
- pt1
.y
,
1468 pt3
.x
- pt1
.x
, pt3
.y
- pt1
.y
,
1469 pt4
.x
- pt1
.x
, pt4
.y
- pt1
.y
);
1474 * Draw/colour the blocks that have been redrawn.
1476 static void ViewportDrawDirtyBlocks()
1478 Blitter
*blitter
= BlitterFactory::GetCurrentBlitter();
1479 const DrawPixelInfo
*dpi
= _cur_dpi
;
1481 int right
= UnScaleByZoom(dpi
->width
, dpi
->zoom
);
1482 int bottom
= UnScaleByZoom(dpi
->height
, dpi
->zoom
);
1484 int colour
= _string_colourmap
[_dirty_block_colour
& 0xF];
1488 byte bo
= UnScaleByZoom(dpi
->left
+ dpi
->top
, dpi
->zoom
) & 1;
1490 for (int i
= (bo
^= 1); i
< right
; i
+= 2) blitter
->SetPixel(dst
, i
, 0, (uint8
)colour
);
1491 dst
= blitter
->MoveTo(dst
, 0, 1);
1492 } while (--bottom
> 0);
1495 static void ViewportDrawStrings(ZoomLevel zoom
, const StringSpriteToDrawVector
*sstdv
)
1497 const StringSpriteToDraw
*ssend
= sstdv
->End();
1498 for (const StringSpriteToDraw
*ss
= sstdv
->Begin(); ss
!= ssend
; ++ss
) {
1499 TextColour colour
= TC_BLACK
;
1500 bool small
= HasBit(ss
->width
, 15);
1501 int w
= GB(ss
->width
, 0, 15);
1502 int x
= UnScaleByZoom(ss
->x
, zoom
);
1503 int y
= UnScaleByZoom(ss
->y
, zoom
);
1504 int h
= VPSM_TOP
+ (small
? FONT_HEIGHT_SMALL
: FONT_HEIGHT_NORMAL
) + VPSM_BOTTOM
;
1506 SetDParam(0, ss
->params
[0]);
1507 SetDParam(1, ss
->params
[1]);
1509 if (ss
->colour
!= INVALID_COLOUR
) {
1510 /* Do not draw signs nor station names if they are set invisible */
1511 if (IsInvisibilitySet(TO_SIGNS
) && ss
->string
!= STR_WHITE_SIGN
) continue;
1513 if (IsTransparencySet(TO_SIGNS
) && ss
->string
!= STR_WHITE_SIGN
) {
1514 /* Don't draw the rectangle.
1515 * Real colours need the TC_IS_PALETTE_COLOUR flag.
1516 * Otherwise colours from _string_colourmap are assumed. */
1517 colour
= (TextColour
)_colour_gradient
[ss
->colour
][6] | TC_IS_PALETTE_COLOUR
;
1519 /* Draw the rectangle if 'transparent station signs' is off,
1520 * or if we are drawing a general text sign (STR_WHITE_SIGN). */
1522 x
, y
, x
+ w
, y
+ h
, ss
->colour
,
1523 IsTransparencySet(TO_SIGNS
) ? FR_TRANSPARENT
: FR_NONE
1528 DrawString(x
+ VPSM_LEFT
, x
+ w
- 1 - VPSM_RIGHT
, y
+ VPSM_TOP
, ss
->string
, colour
, SA_HOR_CENTER
);
1532 void ViewportDoDraw(const ViewPort
*vp
, int left
, int top
, int right
, int bottom
)
1534 DrawPixelInfo
*old_dpi
= _cur_dpi
;
1535 _cur_dpi
= &_vd
.dpi
;
1537 _vd
.dpi
.zoom
= vp
->zoom
;
1538 int mask
= ScaleByZoom(-1, vp
->zoom
);
1540 _vd
.combine_sprites
= SPRITE_COMBINE_NONE
;
1542 _vd
.dpi
.width
= (right
- left
) & mask
;
1543 _vd
.dpi
.height
= (bottom
- top
) & mask
;
1544 _vd
.dpi
.left
= left
& mask
;
1545 _vd
.dpi
.top
= top
& mask
;
1546 _vd
.dpi
.pitch
= old_dpi
->pitch
;
1547 _vd
.last_child
= NULL
;
1549 int x
= UnScaleByZoom(_vd
.dpi
.left
- (vp
->virtual_left
& mask
), vp
->zoom
) + vp
->left
;
1550 int y
= UnScaleByZoom(_vd
.dpi
.top
- (vp
->virtual_top
& mask
), vp
->zoom
) + vp
->top
;
1552 _vd
.dpi
.dst_ptr
= BlitterFactory::GetCurrentBlitter()->MoveTo(old_dpi
->dst_ptr
, x
- old_dpi
->left
, y
- old_dpi
->top
);
1554 ViewportAddLandscape();
1555 ViewportAddVehicles(&_vd
.dpi
);
1557 ViewportAddTownNames(&_vd
.dpi
);
1558 ViewportAddStationNames(&_vd
.dpi
);
1559 ViewportAddSigns(&_vd
.dpi
);
1561 DrawTextEffects(&_vd
.dpi
);
1563 if (_vd
.tile_sprites_to_draw
.Length() != 0) ViewportDrawTileSprites(&_vd
.tile_sprites_to_draw
);
1565 ParentSpriteToDraw
*psd_end
= _vd
.parent_sprites_to_draw
.End();
1566 for (ParentSpriteToDraw
*it
= _vd
.parent_sprites_to_draw
.Begin(); it
!= psd_end
; it
++) {
1567 *_vd
.parent_sprites_to_sort
.Append() = it
;
1570 _vp_sprite_sorter(&_vd
.parent_sprites_to_sort
);
1571 ViewportDrawParentSprites(&_vd
.parent_sprites_to_sort
, &_vd
.child_screen_sprites_to_draw
);
1573 if (_draw_bounding_boxes
) ViewportDrawBoundingBoxes(&_vd
.parent_sprites_to_sort
);
1574 if (_draw_dirty_blocks
) ViewportDrawDirtyBlocks();
1576 DrawPixelInfo dp
= _vd
.dpi
;
1577 ZoomLevel zoom
= _vd
.dpi
.zoom
;
1578 dp
.zoom
= ZOOM_LVL_NORMAL
;
1579 dp
.width
= UnScaleByZoom(dp
.width
, zoom
);
1580 dp
.height
= UnScaleByZoom(dp
.height
, zoom
);
1583 if (vp
->overlay
!= NULL
&& vp
->overlay
->GetCargoMask() != 0 && vp
->overlay
->GetCompanyMask() != 0) {
1584 /* translate to window coordinates */
1587 vp
->overlay
->Draw(&dp
);
1590 if (_vd
.string_sprites_to_draw
.Length() != 0) {
1591 /* translate to world coordinates */
1592 dp
.left
= UnScaleByZoom(_vd
.dpi
.left
, zoom
);
1593 dp
.top
= UnScaleByZoom(_vd
.dpi
.top
, zoom
);
1594 ViewportDrawStrings(zoom
, &_vd
.string_sprites_to_draw
);
1599 _vd
.string_sprites_to_draw
.Clear();
1600 _vd
.tile_sprites_to_draw
.Clear();
1601 _vd
.parent_sprites_to_draw
.Clear();
1602 _vd
.parent_sprites_to_sort
.Clear();
1603 _vd
.child_screen_sprites_to_draw
.Clear();
1607 * Make sure we don't draw a too big area at a time.
1608 * If we do, the sprite memory will overflow.
1610 static void ViewportDrawChk(const ViewPort
*vp
, int left
, int top
, int right
, int bottom
)
1612 if (ScaleByZoom(bottom
- top
, vp
->zoom
) * ScaleByZoom(right
- left
, vp
->zoom
) > 180000 * ZOOM_LVL_BASE
* ZOOM_LVL_BASE
) {
1613 if ((bottom
- top
) > (right
- left
)) {
1614 int t
= (top
+ bottom
) >> 1;
1615 ViewportDrawChk(vp
, left
, top
, right
, t
);
1616 ViewportDrawChk(vp
, left
, t
, right
, bottom
);
1618 int t
= (left
+ right
) >> 1;
1619 ViewportDrawChk(vp
, left
, top
, t
, bottom
);
1620 ViewportDrawChk(vp
, t
, top
, right
, bottom
);
1624 ScaleByZoom(left
- vp
->left
, vp
->zoom
) + vp
->virtual_left
,
1625 ScaleByZoom(top
- vp
->top
, vp
->zoom
) + vp
->virtual_top
,
1626 ScaleByZoom(right
- vp
->left
, vp
->zoom
) + vp
->virtual_left
,
1627 ScaleByZoom(bottom
- vp
->top
, vp
->zoom
) + vp
->virtual_top
1632 static inline void ViewportDraw(const ViewPort
*vp
, int left
, int top
, int right
, int bottom
)
1634 if (right
<= vp
->left
|| bottom
<= vp
->top
) return;
1636 if (left
>= vp
->left
+ vp
->width
) return;
1638 if (left
< vp
->left
) left
= vp
->left
;
1639 if (right
> vp
->left
+ vp
->width
) right
= vp
->left
+ vp
->width
;
1641 if (top
>= vp
->top
+ vp
->height
) return;
1643 if (top
< vp
->top
) top
= vp
->top
;
1644 if (bottom
> vp
->top
+ vp
->height
) bottom
= vp
->top
+ vp
->height
;
1646 ViewportDrawChk(vp
, left
, top
, right
, bottom
);
1650 * Draw the viewport of this window.
1652 void Window::DrawViewport() const
1654 DrawPixelInfo
*dpi
= _cur_dpi
;
1656 dpi
->left
+= this->left
;
1657 dpi
->top
+= this->top
;
1659 ViewportDraw(this->viewport
, dpi
->left
, dpi
->top
, dpi
->left
+ dpi
->width
, dpi
->top
+ dpi
->height
);
1661 dpi
->left
-= this->left
;
1662 dpi
->top
-= this->top
;
1666 * Continue criteria for the SearchMapEdge function.
1667 * @param iter Value to check.
1668 * @param iter_limit Maximum value for the iter
1669 * @param sy Screen y coordinate calculated for the tile at hand
1670 * @param sy_limit Limit to the screen y coordinate
1671 * @return True when we should continue searching.
1673 typedef bool ContinueMapEdgeSearch(int iter
, int iter_limit
, int sy
, int sy_limit
);
1675 /** Continue criteria for searching a no-longer-visible tile in negative direction, starting at some tile. */
1676 static inline bool ContinueLowerMapEdgeSearch(int iter
, int iter_limit
, int sy
, int sy_limit
) { return iter
> 0 && sy
> sy_limit
; }
1677 /** Continue criteria for searching a no-longer-visible tile in positive direction, starting at some tile. */
1678 static inline bool ContinueUpperMapEdgeSearch(int iter
, int iter_limit
, int sy
, int sy_limit
) { return iter
< iter_limit
&& sy
< sy_limit
; }
1681 * Searches, starting at the given tile, by applying the given offset to iter, for a no longer visible tile.
1682 * The whole sense of this function is keeping the to-be-written code small, thus it is a little bit abstracted
1683 * so the same function can be used for both the X and Y locations. As such a reference to one of the elements
1684 * in curr_tile was needed.
1685 * @param curr_tile A tile
1686 * @param iter Reference to either the X or Y of curr_tile.
1687 * @param iter_limit Upper search limit for the iter value.
1688 * @param offset Search in steps of this size
1689 * @param sy_limit Search limit to be passed to the criteria
1690 * @param continue_criteria Search as long as this criteria is true
1691 * @return The final value of iter.
1693 static int SearchMapEdge(Point
&curr_tile
, int &iter
, int iter_limit
, int offset
, int sy_limit
, ContinueMapEdgeSearch continue_criteria
)
1697 iter
= Clamp(iter
+ offset
, 0, iter_limit
);
1698 sy
= GetViewportY(curr_tile
);
1699 } while (continue_criteria(iter
, iter_limit
, sy
, sy_limit
));
1705 * Determine the clamping of either the X or Y coordinate to the map.
1706 * @param curr_tile A tile
1707 * @param iter Reference to either the X or Y of curr_tile.
1708 * @param iter_limit Upper search limit for the iter value.
1709 * @param start Start value for the iteration.
1710 * @param other_ref Reference to the opposite axis in curr_tile than of iter.
1711 * @param other_value Start value for of the opposite axis
1712 * @param vp_value Value of the viewport location in the opposite axis as for iter.
1713 * @param other_limit Limit for the other value, so if iter is X, then other_limit is for Y.
1714 * @param vp_top Top of the viewport.
1715 * @param vp_bottom Bottom of the viewport.
1716 * @return Clamped version of vp_value.
1718 static inline int ClampXYToMap(Point
&curr_tile
, int &iter
, int iter_limit
, int start
, int &other_ref
, int other_value
, int vp_value
, int other_limit
, int vp_top
, int vp_bottom
)
1720 bool upper_edge
= other_value
< _settings_game
.construction
.max_heightlevel
/ 4;
1723 * First get an estimate of the tiles relevant for us at that edge. Relevant in the sense
1724 * "at least close to the visible area". Thus, we don't look at exactly each tile, inspecting
1725 * e.g. every tenth should be enough. After all, the desired screen limit is set such that
1726 * the bordermost tiles are painted in the middle of the screen when one hits the limit,
1727 * i.e. it is no harm if there is some small error in that calculation
1730 other_ref
= upper_edge
? 0 : other_limit
;
1732 int min_iter
= SearchMapEdge(curr_tile
, iter
, iter_limit
, upper_edge
? -10 : +10, vp_top
, upper_edge
? ContinueLowerMapEdgeSearch
: ContinueUpperMapEdgeSearch
);
1734 int max_iter
= SearchMapEdge(curr_tile
, iter
, iter_limit
, upper_edge
? +10 : -10, vp_bottom
, upper_edge
? ContinueUpperMapEdgeSearch
: ContinueLowerMapEdgeSearch
);
1736 max_iter
= min(max_iter
+ _settings_game
.construction
.max_heightlevel
/ 4, iter_limit
);
1737 min_iter
= min(min_iter
, max_iter
);
1739 /* Now, calculate the highest heightlevel of these tiles. Again just as an estimate. */
1740 int max_heightlevel_at_edge
= 0;
1741 for (iter
= min_iter
; iter
<= max_iter
; iter
+= 10) {
1742 max_heightlevel_at_edge
= max(max_heightlevel_at_edge
, (int)TileHeight(TileXY(curr_tile
.x
, curr_tile
.y
)));
1745 /* Based on that heightlevel, calculate the limit. For the upper edge a tile with height zero would
1746 * get a limit of zero, on the other side it depends on the number of tiles along the axis. */
1748 max(vp_value
, -max_heightlevel_at_edge
* (int)(TILE_HEIGHT
* 2 * ZOOM_LVL_BASE
)) :
1749 min(vp_value
, (other_limit
* TILE_SIZE
* 4 - max_heightlevel_at_edge
* TILE_HEIGHT
* 2) * ZOOM_LVL_BASE
);
1752 static inline void ClampViewportToMap(const ViewPort
*vp
, int &x
, int &y
)
1756 /* Centre of the viewport is hot spot */
1757 x
+= vp
->virtual_width
/ 2;
1758 y
+= vp
->virtual_height
/ 2;
1760 /* Convert viewport coordinates to map coordinates
1761 * Calculation is scaled by 4 to avoid rounding errors */
1762 int vx
= -x
+ y
* 2;
1765 /* Find out which tile corresponds to (vx,vy) if one assumes height zero. The cast is necessary to prevent C++ from
1766 * converting the result to an uint, which gives an overflow instead of a negative result... */
1767 int tx
= vx
/ (int)(TILE_SIZE
* 4 * ZOOM_LVL_BASE
);
1768 int ty
= vy
/ (int)(TILE_SIZE
* 4 * ZOOM_LVL_BASE
);
1771 vx
= ClampXYToMap(curr_tile
, curr_tile
.y
, MapMaxY(), ty
, curr_tile
.x
, tx
, vx
, MapMaxX(), original_y
, original_y
+ vp
->virtual_height
);
1772 vy
= ClampXYToMap(curr_tile
, curr_tile
.x
, MapMaxX(), tx
, curr_tile
.y
, ty
, vy
, MapMaxY(), original_y
, original_y
+ vp
->virtual_height
);
1774 /* Convert map coordinates to viewport coordinates */
1778 /* Remove centering */
1779 x
-= vp
->virtual_width
/ 2;
1780 y
-= vp
->virtual_height
/ 2;
1784 * Update the viewport position being displayed.
1785 * @param w %Window owning the viewport.
1787 void UpdateViewportPosition(Window
*w
)
1789 const ViewPort
*vp
= w
->viewport
;
1791 if (w
->viewport
->follow_vehicle
!= INVALID_VEHICLE
) {
1792 const Vehicle
*veh
= Vehicle::Get(w
->viewport
->follow_vehicle
);
1793 Point pt
= MapXYZToViewport(vp
, veh
->x_pos
, veh
->y_pos
, veh
->z_pos
);
1795 w
->viewport
->scrollpos_x
= pt
.x
;
1796 w
->viewport
->scrollpos_y
= pt
.y
;
1797 SetViewportPosition(w
, pt
.x
, pt
.y
);
1799 /* Ensure the destination location is within the map */
1800 ClampViewportToMap(vp
, w
->viewport
->dest_scrollpos_x
, w
->viewport
->dest_scrollpos_y
);
1802 int delta_x
= w
->viewport
->dest_scrollpos_x
- w
->viewport
->scrollpos_x
;
1803 int delta_y
= w
->viewport
->dest_scrollpos_y
- w
->viewport
->scrollpos_y
;
1805 bool update_overlay
= false;
1806 if (delta_x
!= 0 || delta_y
!= 0) {
1807 if (_settings_client
.gui
.smooth_scroll
) {
1808 int max_scroll
= ScaleByMapSize1D(512 * ZOOM_LVL_BASE
);
1809 /* Not at our desired position yet... */
1810 w
->viewport
->scrollpos_x
+= Clamp(delta_x
/ 4, -max_scroll
, max_scroll
);
1811 w
->viewport
->scrollpos_y
+= Clamp(delta_y
/ 4, -max_scroll
, max_scroll
);
1813 w
->viewport
->scrollpos_x
= w
->viewport
->dest_scrollpos_x
;
1814 w
->viewport
->scrollpos_y
= w
->viewport
->dest_scrollpos_y
;
1816 update_overlay
= (w
->viewport
->scrollpos_x
== w
->viewport
->dest_scrollpos_x
&&
1817 w
->viewport
->scrollpos_y
== w
->viewport
->dest_scrollpos_y
);
1820 ClampViewportToMap(vp
, w
->viewport
->scrollpos_x
, w
->viewport
->scrollpos_y
);
1822 SetViewportPosition(w
, w
->viewport
->scrollpos_x
, w
->viewport
->scrollpos_y
);
1823 if (update_overlay
) RebuildViewportOverlay(w
);
1828 * Marks a viewport as dirty for repaint if it displays (a part of) the area the needs to be repainted.
1829 * @param vp The viewport to mark as dirty
1830 * @param left Left edge of area to repaint
1831 * @param top Top edge of area to repaint
1832 * @param right Right edge of area to repaint
1833 * @param bottom Bottom edge of area to repaint
1836 static void MarkViewportDirty(const ViewPort
*vp
, int left
, int top
, int right
, int bottom
)
1838 /* Rounding wrt. zoom-out level */
1839 right
+= (1 << vp
->zoom
) - 1;
1840 bottom
+= (1 << vp
->zoom
) - 1;
1842 right
-= vp
->virtual_left
;
1843 if (right
<= 0) return;
1845 bottom
-= vp
->virtual_top
;
1846 if (bottom
<= 0) return;
1848 left
= max(0, left
- vp
->virtual_left
);
1850 if (left
>= vp
->virtual_width
) return;
1852 top
= max(0, top
- vp
->virtual_top
);
1854 if (top
>= vp
->virtual_height
) return;
1857 UnScaleByZoomLower(left
, vp
->zoom
) + vp
->left
,
1858 UnScaleByZoomLower(top
, vp
->zoom
) + vp
->top
,
1859 UnScaleByZoom(right
, vp
->zoom
) + vp
->left
+ 1,
1860 UnScaleByZoom(bottom
, vp
->zoom
) + vp
->top
+ 1
1865 * Mark all viewports that display an area as dirty (in need of repaint).
1866 * @param left Left edge of area to repaint. (viewport coordinates, that is wrt. #ZOOM_LVL_NORMAL)
1867 * @param top Top edge of area to repaint. (viewport coordinates, that is wrt. #ZOOM_LVL_NORMAL)
1868 * @param right Right edge of area to repaint. (viewport coordinates, that is wrt. #ZOOM_LVL_NORMAL)
1869 * @param bottom Bottom edge of area to repaint. (viewport coordinates, that is wrt. #ZOOM_LVL_NORMAL)
1872 void MarkAllViewportsDirty(int left
, int top
, int right
, int bottom
)
1875 FOR_ALL_WINDOWS_FROM_BACK(w
) {
1876 ViewPort
*vp
= w
->viewport
;
1878 assert(vp
->width
!= 0);
1879 MarkViewportDirty(vp
, left
, top
, right
, bottom
);
1884 void ConstrainAllViewportsZoom()
1887 FOR_ALL_WINDOWS_FROM_FRONT(w
) {
1888 if (w
->viewport
== NULL
) continue;
1890 ZoomLevel zoom
= static_cast<ZoomLevel
>(Clamp(w
->viewport
->zoom
, _settings_client
.gui
.zoom_min
, _settings_client
.gui
.zoom_max
));
1891 if (zoom
!= w
->viewport
->zoom
) {
1892 while (w
->viewport
->zoom
< zoom
) DoZoomInOutWindow(ZOOM_OUT
, w
);
1893 while (w
->viewport
->zoom
> zoom
) DoZoomInOutWindow(ZOOM_IN
, w
);
1899 * Mark a tile given by its index dirty for repaint.
1900 * @param tile The tile to mark dirty.
1901 * @param bridge_level_offset Height of bridge on tile to also mark dirty. (Height level relative to north corner.)
1904 void MarkTileDirtyByTile(TileIndex tile
, int bridge_level_offset
)
1906 Point pt
= RemapCoords(TileX(tile
) * TILE_SIZE
, TileY(tile
) * TILE_SIZE
, TilePixelHeight(tile
));
1907 MarkAllViewportsDirty(
1908 pt
.x
- MAX_TILE_EXTENT_LEFT
,
1909 pt
.y
- MAX_TILE_EXTENT_TOP
- ZOOM_LVL_BASE
* TILE_HEIGHT
* bridge_level_offset
,
1910 pt
.x
+ MAX_TILE_EXTENT_RIGHT
,
1911 pt
.y
+ MAX_TILE_EXTENT_BOTTOM
);
1915 * Mark a (virtual) tile outside the map dirty for repaint.
1916 * @param x Tile X position.
1917 * @param y Tile Y position.
1920 void MarkTileDirtyByTileOutsideMap(int x
, int y
)
1922 Point pt
= RemapCoords(x
* TILE_SIZE
, y
* TILE_SIZE
, TilePixelHeightOutsideMap(x
, y
));
1923 MarkAllViewportsDirty(
1924 pt
.x
- MAX_TILE_EXTENT_LEFT
,
1925 pt
.y
, // no buildings outside of map
1926 pt
.x
+ MAX_TILE_EXTENT_RIGHT
,
1927 pt
.y
+ MAX_TILE_EXTENT_BOTTOM
);
1931 * Marks the selected tiles as dirty.
1933 * This function marks the selected tiles as dirty for repaint
1937 static void SetSelectionTilesDirty()
1939 int x_size
= _thd
.size
.x
;
1940 int y_size
= _thd
.size
.y
;
1942 if (!_thd
.diagonal
) { // Selecting in a straight rectangle (or a single square)
1943 int x_start
= _thd
.pos
.x
;
1944 int y_start
= _thd
.pos
.y
;
1946 if (_thd
.outersize
.x
!= 0) {
1947 x_size
+= _thd
.outersize
.x
;
1948 x_start
+= _thd
.offs
.x
;
1949 y_size
+= _thd
.outersize
.y
;
1950 y_start
+= _thd
.offs
.y
;
1953 x_size
-= TILE_SIZE
;
1954 y_size
-= TILE_SIZE
;
1956 assert(x_size
>= 0);
1957 assert(y_size
>= 0);
1959 int x_end
= Clamp(x_start
+ x_size
, 0, MapSizeX() * TILE_SIZE
- TILE_SIZE
);
1960 int y_end
= Clamp(y_start
+ y_size
, 0, MapSizeY() * TILE_SIZE
- TILE_SIZE
);
1962 x_start
= Clamp(x_start
, 0, MapSizeX() * TILE_SIZE
- TILE_SIZE
);
1963 y_start
= Clamp(y_start
, 0, MapSizeY() * TILE_SIZE
- TILE_SIZE
);
1965 /* make sure everything is multiple of TILE_SIZE */
1966 assert((x_end
| y_end
| x_start
| y_start
) % TILE_SIZE
== 0);
1969 * Suppose we have to mark dirty rectangle of 3x4 tiles:
1976 * This algorithm marks dirty columns of tiles, so it is done in 3+4-1 steps:
1986 int top_x
= x_end
; // coordinates of top dirty tile
1987 int top_y
= y_start
;
1988 int bot_x
= top_x
; // coordinates of bottom dirty tile
1992 /* topmost dirty point */
1993 TileIndex top_tile
= TileVirtXY(top_x
, top_y
);
1994 Point top
= RemapCoords(top_x
, top_y
, GetTileMaxPixelZ(top_tile
));
1996 /* bottommost point */
1997 TileIndex bottom_tile
= TileVirtXY(bot_x
, bot_y
);
1998 Point bot
= RemapCoords(bot_x
+ TILE_SIZE
, bot_y
+ TILE_SIZE
, GetTilePixelZ(bottom_tile
)); // bottommost point
2000 /* the 'x' coordinate of 'top' and 'bot' is the same (and always in the same distance from tile middle),
2001 * tile height/slope affects only the 'y' on-screen coordinate! */
2003 int l
= top
.x
- TILE_PIXELS
* ZOOM_LVL_BASE
; // 'x' coordinate of left side of the dirty rectangle
2004 int t
= top
.y
; // 'y' coordinate of top side of the dirty rectangle
2005 int r
= top
.x
+ TILE_PIXELS
* ZOOM_LVL_BASE
; // 'x' coordinate of right side of the dirty rectangle
2006 int b
= bot
.y
; // 'y' coordinate of bottom side of the dirty rectangle
2008 static const int OVERLAY_WIDTH
= 4 * ZOOM_LVL_BASE
; // part of selection sprites is drawn outside the selected area (in particular: terraforming)
2010 /* For halftile foundations on SLOPE_STEEP_S the sprite extents some more towards the top */
2011 MarkAllViewportsDirty(l
- OVERLAY_WIDTH
, t
- OVERLAY_WIDTH
- TILE_HEIGHT
* ZOOM_LVL_BASE
, r
+ OVERLAY_WIDTH
, b
+ OVERLAY_WIDTH
);
2013 /* haven't we reached the topmost tile yet? */
2014 if (top_x
!= x_start
) {
2020 /* the way the bottom tile changes is different when we reach the bottommost tile */
2021 if (bot_y
!= y_end
) {
2026 } while (bot_x
>= top_x
);
2027 } else { // Selecting in a 45 degrees rotated (diagonal) rectangle.
2028 /* a_size, b_size describe a rectangle with rotated coordinates */
2029 int a_size
= x_size
+ y_size
, b_size
= x_size
- y_size
;
2031 int interval_a
= a_size
< 0 ? -(int)TILE_SIZE
: (int)TILE_SIZE
;
2032 int interval_b
= b_size
< 0 ? -(int)TILE_SIZE
: (int)TILE_SIZE
;
2034 for (int a
= -interval_a
; a
!= a_size
+ interval_a
; a
+= interval_a
) {
2035 for (int b
= -interval_b
; b
!= b_size
+ interval_b
; b
+= interval_b
) {
2036 uint x
= (_thd
.pos
.x
+ (a
+ b
) / 2) / TILE_SIZE
;
2037 uint y
= (_thd
.pos
.y
+ (a
- b
) / 2) / TILE_SIZE
;
2039 if (x
< MapMaxX() && y
< MapMaxY()) {
2040 MarkTileDirtyByTile(TileXY(x
, y
));
2048 void SetSelectionRed(bool b
)
2050 _thd
.make_square_red
= b
;
2051 SetSelectionTilesDirty();
2055 * Test whether a sign is below the mouse
2056 * @param vp the clicked viewport
2057 * @param x X position of click
2058 * @param y Y position of click
2059 * @param sign the sign to check
2060 * @return true if the sign was hit
2062 static bool CheckClickOnViewportSign(const ViewPort
*vp
, int x
, int y
, const ViewportSign
*sign
)
2064 bool small
= (vp
->zoom
>= ZOOM_LVL_OUT_16X
);
2065 int sign_half_width
= ScaleByZoom((small
? sign
->width_small
: sign
->width_normal
) / 2, vp
->zoom
);
2066 int sign_height
= ScaleByZoom(VPSM_TOP
+ (small
? FONT_HEIGHT_SMALL
: FONT_HEIGHT_NORMAL
) + VPSM_BOTTOM
, vp
->zoom
);
2068 x
= ScaleByZoom(x
- vp
->left
, vp
->zoom
) + vp
->virtual_left
;
2069 y
= ScaleByZoom(y
- vp
->top
, vp
->zoom
) + vp
->virtual_top
;
2071 return y
>= sign
->top
&& y
< sign
->top
+ sign_height
&&
2072 x
>= sign
->center
- sign_half_width
&& x
< sign
->center
+ sign_half_width
;
2075 static bool CheckClickOnTown(const ViewPort
*vp
, int x
, int y
)
2077 if (!HasBit(_display_opt
, DO_SHOW_TOWN_NAMES
)) return false;
2081 if (CheckClickOnViewportSign(vp
, x
, y
, &t
->cache
.sign
)) {
2082 ShowTownViewWindow(t
->index
);
2090 static bool CheckClickOnStation(const ViewPort
*vp
, int x
, int y
)
2092 if (!(HasBit(_display_opt
, DO_SHOW_STATION_NAMES
) || HasBit(_display_opt
, DO_SHOW_WAYPOINT_NAMES
)) || IsInvisibilitySet(TO_SIGNS
)) return false;
2094 const BaseStation
*st
;
2095 FOR_ALL_BASE_STATIONS(st
) {
2096 /* Check whether the base station is a station or a waypoint */
2097 bool is_station
= Station::IsExpected(st
);
2099 /* Don't check if the display options are disabled */
2100 if (!HasBit(_display_opt
, is_station
? DO_SHOW_STATION_NAMES
: DO_SHOW_WAYPOINT_NAMES
)) continue;
2102 /* Don't check if competitor signs are not shown and the sign isn't owned by the local company */
2103 if (!HasBit(_display_opt
, DO_SHOW_COMPETITOR_SIGNS
) && _local_company
!= st
->owner
&& st
->owner
!= OWNER_NONE
) continue;
2105 if (CheckClickOnViewportSign(vp
, x
, y
, &st
->sign
)) {
2107 ShowStationViewWindow(st
->index
);
2109 ShowWaypointWindow(Waypoint::From(st
));
2119 static bool CheckClickOnSign(const ViewPort
*vp
, int x
, int y
)
2121 /* Signs are turned off, or they are transparent and invisibility is ON, or company is a spectator */
2122 if (!HasBit(_display_opt
, DO_SHOW_SIGNS
) || IsInvisibilitySet(TO_SIGNS
) || _local_company
== COMPANY_SPECTATOR
) return false;
2126 /* If competitor signs are hidden, don't check signs that aren't owned by local company */
2127 if (!HasBit(_display_opt
, DO_SHOW_COMPETITOR_SIGNS
) && _local_company
!= si
->owner
&& si
->owner
!= OWNER_DEITY
) continue;
2128 if (si
->owner
== OWNER_DEITY
&& _game_mode
!= GM_EDITOR
) continue;
2130 if (CheckClickOnViewportSign(vp
, x
, y
, &si
->sign
)) {
2131 HandleClickOnSign(si
);
2140 static bool CheckClickOnLandscape(const ViewPort
*vp
, int x
, int y
)
2142 Point pt
= TranslateXYToTileCoord(vp
, x
, y
);
2144 if (pt
.x
!= -1) return ClickTile(TileVirtXY(pt
.x
, pt
.y
));
2148 static void PlaceObject()
2153 pt
= GetTileBelowCursor();
2154 if (pt
.x
== -1) return;
2156 if ((_thd
.place_mode
& HT_DRAG_MASK
) == HT_POINT
) {
2157 pt
.x
+= TILE_SIZE
/ 2;
2158 pt
.y
+= TILE_SIZE
/ 2;
2161 _tile_fract_coords
.x
= pt
.x
& TILE_UNIT_MASK
;
2162 _tile_fract_coords
.y
= pt
.y
& TILE_UNIT_MASK
;
2164 w
= _thd
.GetCallbackWnd();
2165 if (w
!= NULL
) w
->OnPlaceObject(pt
, TileVirtXY(pt
.x
, pt
.y
));
2169 bool HandleViewportClicked(const ViewPort
*vp
, int x
, int y
)
2171 const Vehicle
*v
= CheckClickOnVehicle(vp
, x
, y
);
2173 if (_thd
.place_mode
& HT_VEHICLE
) {
2174 if (v
!= NULL
&& VehicleClicked(v
)) return true;
2177 /* Vehicle placement mode already handled above. */
2178 if ((_thd
.place_mode
& HT_DRAG_MASK
) != HT_NONE
) {
2183 if (CheckClickOnTown(vp
, x
, y
)) return true;
2184 if (CheckClickOnStation(vp
, x
, y
)) return true;
2185 if (CheckClickOnSign(vp
, x
, y
)) return true;
2186 bool result
= CheckClickOnLandscape(vp
, x
, y
);
2189 DEBUG(misc
, 2, "Vehicle %d (index %d) at %p", v
->unitnumber
, v
->index
, v
);
2190 if (IsCompanyBuildableVehicleType(v
)) {
2192 if (_ctrl_pressed
&& v
->owner
== _local_company
) {
2193 StartStopVehicle(v
, true);
2195 ShowVehicleViewWindow(v
);
2203 void RebuildViewportOverlay(Window
*w
)
2205 if (w
->viewport
->overlay
!= NULL
&&
2206 w
->viewport
->overlay
->GetCompanyMask() != 0 &&
2207 w
->viewport
->overlay
->GetCargoMask() != 0) {
2208 w
->viewport
->overlay
->RebuildCache();
2214 * Scrolls the viewport in a window to a given location.
2215 * @param x Desired x location of the map to scroll to (world coordinate).
2216 * @param y Desired y location of the map to scroll to (world coordinate).
2217 * @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.
2218 * @param w %Window containing the viewport.
2219 * @param instant Jump to the location instead of slowly moving to it.
2220 * @return Destination of the viewport was changed (to activate other actions when the viewport is already at the desired position).
2222 bool ScrollWindowTo(int x
, int y
, int z
, Window
*w
, bool instant
)
2224 /* The slope cannot be acquired outside of the map, so make sure we are always within the map. */
2226 if ( x
>= 0 && x
<= (int)MapSizeX() * (int)TILE_SIZE
- 1
2227 && y
>= 0 && y
<= (int)MapSizeY() * (int)TILE_SIZE
- 1) {
2228 z
= GetSlopePixelZ(x
, y
);
2230 z
= TileHeightOutsideMap(x
/ (int)TILE_SIZE
, y
/ (int)TILE_SIZE
);
2234 Point pt
= MapXYZToViewport(w
->viewport
, x
, y
, z
);
2235 w
->viewport
->follow_vehicle
= INVALID_VEHICLE
;
2237 if (w
->viewport
->dest_scrollpos_x
== pt
.x
&& w
->viewport
->dest_scrollpos_y
== pt
.y
) return false;
2240 w
->viewport
->scrollpos_x
= pt
.x
;
2241 w
->viewport
->scrollpos_y
= pt
.y
;
2242 RebuildViewportOverlay(w
);
2245 w
->viewport
->dest_scrollpos_x
= pt
.x
;
2246 w
->viewport
->dest_scrollpos_y
= pt
.y
;
2251 * Scrolls the viewport in a window to a given location.
2252 * @param tile Desired tile to center on.
2253 * @param w %Window containing the viewport.
2254 * @param instant Jump to the location instead of slowly moving to it.
2255 * @return Destination of the viewport was changed (to activate other actions when the viewport is already at the desired position).
2257 bool ScrollWindowToTile(TileIndex tile
, Window
*w
, bool instant
)
2259 return ScrollWindowTo(TileX(tile
) * TILE_SIZE
, TileY(tile
) * TILE_SIZE
, -1, w
, instant
);
2263 * Scrolls the viewport of the main window to a given location.
2264 * @param tile Desired tile to center on.
2265 * @param instant Jump to the location instead of slowly moving to it.
2266 * @return Destination of the viewport was changed (to activate other actions when the viewport is already at the desired position).
2268 bool ScrollMainWindowToTile(TileIndex tile
, bool instant
)
2270 return ScrollMainWindowTo(TileX(tile
) * TILE_SIZE
+ TILE_SIZE
/ 2, TileY(tile
) * TILE_SIZE
+ TILE_SIZE
/ 2, -1, instant
);
2274 * Set a tile to display a red error square.
2275 * @param tile Tile that should show the red error square.
2277 void SetRedErrorSquare(TileIndex tile
)
2285 if (tile
!= INVALID_TILE
) MarkTileDirtyByTile(tile
);
2286 if (old
!= INVALID_TILE
) MarkTileDirtyByTile(old
);
2291 * Highlight \a w by \a h tiles at the cursor.
2292 * @param w Width of the highlighted tiles rectangle.
2293 * @param h Height of the highlighted tiles rectangle.
2295 void SetTileSelectSize(int w
, int h
)
2297 _thd
.new_size
.x
= w
* TILE_SIZE
;
2298 _thd
.new_size
.y
= h
* TILE_SIZE
;
2299 _thd
.new_outersize
.x
= 0;
2300 _thd
.new_outersize
.y
= 0;
2303 void SetTileSelectBigSize(int ox
, int oy
, int sx
, int sy
)
2305 _thd
.offs
.x
= ox
* TILE_SIZE
;
2306 _thd
.offs
.y
= oy
* TILE_SIZE
;
2307 _thd
.new_outersize
.x
= sx
* TILE_SIZE
;
2308 _thd
.new_outersize
.y
= sy
* TILE_SIZE
;
2311 /** returns the best autorail highlight type from map coordinates */
2312 static HighLightStyle
GetAutorailHT(int x
, int y
)
2314 return HT_RAIL
| _autorail_piece
[x
& TILE_UNIT_MASK
][y
& TILE_UNIT_MASK
];
2318 * Reset tile highlighting.
2320 void TileHighlightData::Reset()
2324 this->new_pos
.x
= 0;
2325 this->new_pos
.y
= 0;
2329 * Is the user dragging a 'diagonal rectangle'?
2330 * @return User is dragging a rotated rectangle.
2332 bool TileHighlightData::IsDraggingDiagonal()
2334 return (this->place_mode
& HT_DIAGONAL
) != 0 && _ctrl_pressed
&& _left_button_down
;
2338 * Get the window that started the current highlighting.
2339 * @return The window that requested the current tile highlighting, or \c NULL if not available.
2341 Window
*TileHighlightData::GetCallbackWnd()
2343 return FindWindowById(this->window_class
, this->window_number
);
2349 * Updates tile highlighting for all cases.
2350 * Uses _thd.selstart and _thd.selend and _thd.place_mode (set elsewhere) to determine _thd.pos and _thd.size
2351 * Also drawstyle is determined. Uses _thd.new.* as a buffer and calls SetSelectionTilesDirty() twice,
2352 * Once for the old and once for the new selection.
2353 * _thd is TileHighlightData, found in viewport.h
2355 void UpdateTileSelection()
2360 HighLightStyle new_drawstyle
= HT_NONE
;
2361 bool new_diagonal
= false;
2363 if ((_thd
.place_mode
& HT_DRAG_MASK
) == HT_SPECIAL
) {
2367 int x2
= _thd
.selstart
.x
& ~TILE_UNIT_MASK
;
2368 int y2
= _thd
.selstart
.y
& ~TILE_UNIT_MASK
;
2369 x1
&= ~TILE_UNIT_MASK
;
2370 y1
&= ~TILE_UNIT_MASK
;
2372 if (_thd
.IsDraggingDiagonal()) {
2373 new_diagonal
= true;
2375 if (x1
>= x2
) Swap(x1
, x2
);
2376 if (y1
>= y2
) Swap(y1
, y2
);
2378 _thd
.new_pos
.x
= x1
;
2379 _thd
.new_pos
.y
= y1
;
2380 _thd
.new_size
.x
= x2
- x1
;
2381 _thd
.new_size
.y
= y2
- y1
;
2382 if (!new_diagonal
) {
2383 _thd
.new_size
.x
+= TILE_SIZE
;
2384 _thd
.new_size
.y
+= TILE_SIZE
;
2386 new_drawstyle
= _thd
.next_drawstyle
;
2388 } else if ((_thd
.place_mode
& HT_DRAG_MASK
) != HT_NONE
) {
2389 Point pt
= GetTileBelowCursor();
2393 switch (_thd
.place_mode
& HT_DRAG_MASK
) {
2395 new_drawstyle
= HT_RECT
;
2398 new_drawstyle
= HT_POINT
;
2399 x1
+= TILE_SIZE
/ 2;
2400 y1
+= TILE_SIZE
/ 2;
2403 /* Draw one highlighted tile in any direction */
2404 new_drawstyle
= GetAutorailHT(pt
.x
, pt
.y
);
2407 switch (_thd
.place_mode
& HT_DIR_MASK
) {
2408 case HT_DIR_X
: new_drawstyle
= HT_LINE
| HT_DIR_X
; break;
2409 case HT_DIR_Y
: new_drawstyle
= HT_LINE
| HT_DIR_Y
; break;
2413 new_drawstyle
= (pt
.x
& TILE_UNIT_MASK
) + (pt
.y
& TILE_UNIT_MASK
) <= TILE_SIZE
? HT_LINE
| HT_DIR_HU
: HT_LINE
| HT_DIR_HL
;
2418 new_drawstyle
= (pt
.x
& TILE_UNIT_MASK
) > (pt
.y
& TILE_UNIT_MASK
) ? HT_LINE
| HT_DIR_VL
: HT_LINE
| HT_DIR_VR
;
2421 default: NOT_REACHED();
2423 _thd
.selstart
.x
= x1
& ~TILE_UNIT_MASK
;
2424 _thd
.selstart
.y
= y1
& ~TILE_UNIT_MASK
;
2430 _thd
.new_pos
.x
= x1
& ~TILE_UNIT_MASK
;
2431 _thd
.new_pos
.y
= y1
& ~TILE_UNIT_MASK
;
2435 /* redraw selection */
2436 if (_thd
.drawstyle
!= new_drawstyle
||
2437 _thd
.pos
.x
!= _thd
.new_pos
.x
|| _thd
.pos
.y
!= _thd
.new_pos
.y
||
2438 _thd
.size
.x
!= _thd
.new_size
.x
|| _thd
.size
.y
!= _thd
.new_size
.y
||
2439 _thd
.outersize
.x
!= _thd
.new_outersize
.x
||
2440 _thd
.outersize
.y
!= _thd
.new_outersize
.y
||
2441 _thd
.diagonal
!= new_diagonal
) {
2442 /* Clear the old tile selection? */
2443 if ((_thd
.drawstyle
& HT_DRAG_MASK
) != HT_NONE
) SetSelectionTilesDirty();
2445 _thd
.drawstyle
= new_drawstyle
;
2446 _thd
.pos
= _thd
.new_pos
;
2447 _thd
.size
= _thd
.new_size
;
2448 _thd
.outersize
= _thd
.new_outersize
;
2449 _thd
.diagonal
= new_diagonal
;
2452 /* Draw the new tile selection? */
2453 if ((new_drawstyle
& HT_DRAG_MASK
) != HT_NONE
) SetSelectionTilesDirty();
2458 * Displays the measurement tooltips when selecting multiple tiles
2459 * @param str String to be displayed
2460 * @param paramcount number of params to deal with
2461 * @param params (optional) up to 5 pieces of additional information that may be added to a tooltip
2462 * @param close_cond Condition for closing this tooltip.
2464 static inline void ShowMeasurementTooltips(StringID str
, uint paramcount
, const uint64 params
[], TooltipCloseCondition close_cond
= TCC_LEFT_CLICK
)
2466 if (!_settings_client
.gui
.measure_tooltip
) return;
2467 GuiShowTooltips(_thd
.GetCallbackWnd(), str
, paramcount
, params
, close_cond
);
2470 /** highlighting tiles while only going over them with the mouse */
2471 void VpStartPlaceSizing(TileIndex tile
, ViewportPlaceMethod method
, ViewportDragDropSelectionProcess process
)
2473 _thd
.select_method
= method
;
2474 _thd
.select_proc
= process
;
2475 _thd
.selend
.x
= TileX(tile
) * TILE_SIZE
;
2476 _thd
.selstart
.x
= TileX(tile
) * TILE_SIZE
;
2477 _thd
.selend
.y
= TileY(tile
) * TILE_SIZE
;
2478 _thd
.selstart
.y
= TileY(tile
) * TILE_SIZE
;
2480 /* Needed so several things (road, autoroad, bridges, ...) are placed correctly.
2481 * In effect, placement starts from the centre of a tile
2483 if (method
== VPM_X_OR_Y
|| method
== VPM_FIX_X
|| method
== VPM_FIX_Y
) {
2484 _thd
.selend
.x
+= TILE_SIZE
/ 2;
2485 _thd
.selend
.y
+= TILE_SIZE
/ 2;
2486 _thd
.selstart
.x
+= TILE_SIZE
/ 2;
2487 _thd
.selstart
.y
+= TILE_SIZE
/ 2;
2490 HighLightStyle others
= _thd
.place_mode
& ~(HT_DRAG_MASK
| HT_DIR_MASK
);
2491 if ((_thd
.place_mode
& HT_DRAG_MASK
) == HT_RECT
) {
2492 _thd
.place_mode
= HT_SPECIAL
| others
;
2493 _thd
.next_drawstyle
= HT_RECT
| others
;
2494 } else if (_thd
.place_mode
& (HT_RAIL
| HT_LINE
)) {
2495 _thd
.place_mode
= HT_SPECIAL
| others
;
2496 _thd
.next_drawstyle
= _thd
.drawstyle
| others
;
2498 _thd
.place_mode
= HT_SPECIAL
| others
;
2499 _thd
.next_drawstyle
= HT_POINT
| others
;
2501 _special_mouse_mode
= WSM_SIZING
;
2504 void VpSetPlaceSizingLimit(int limit
)
2506 _thd
.sizelimit
= limit
;
2510 * Highlights all tiles between a set of two tiles. Used in dock and tunnel placement
2511 * @param from TileIndex of the first tile to highlight
2512 * @param to TileIndex of the last tile to highlight
2514 void VpSetPresizeRange(TileIndex from
, TileIndex to
)
2516 uint64 distance
= DistanceManhattan(from
, to
) + 1;
2518 _thd
.selend
.x
= TileX(to
) * TILE_SIZE
;
2519 _thd
.selend
.y
= TileY(to
) * TILE_SIZE
;
2520 _thd
.selstart
.x
= TileX(from
) * TILE_SIZE
;
2521 _thd
.selstart
.y
= TileY(from
) * TILE_SIZE
;
2522 _thd
.next_drawstyle
= HT_RECT
;
2524 /* show measurement only if there is any length to speak of */
2525 if (distance
> 1) ShowMeasurementTooltips(STR_MEASURE_LENGTH
, 1, &distance
, TCC_HOVER
);
2528 static void VpStartPreSizing()
2531 _special_mouse_mode
= WSM_PRESIZE
;
2535 * returns information about the 2x1 piece to be build.
2536 * The lower bits (0-3) are the track type.
2538 static HighLightStyle
Check2x1AutoRail(int mode
)
2540 int fxpy
= _tile_fract_coords
.x
+ _tile_fract_coords
.y
;
2541 int sxpy
= (_thd
.selend
.x
& TILE_UNIT_MASK
) + (_thd
.selend
.y
& TILE_UNIT_MASK
);
2542 int fxmy
= _tile_fract_coords
.x
- _tile_fract_coords
.y
;
2543 int sxmy
= (_thd
.selend
.x
& TILE_UNIT_MASK
) - (_thd
.selend
.y
& TILE_UNIT_MASK
);
2546 default: NOT_REACHED();
2547 case 0: // end piece is lower right
2548 if (fxpy
>= 20 && sxpy
<= 12) return HT_DIR_HL
;
2549 if (fxmy
< -3 && sxmy
> 3) return HT_DIR_VR
;
2553 if (fxmy
> 3 && sxmy
< -3) return HT_DIR_VL
;
2554 if (fxpy
<= 12 && sxpy
>= 20) return HT_DIR_HU
;
2558 if (fxmy
> 3 && sxmy
< -3) return HT_DIR_VL
;
2559 if (fxpy
>= 20 && sxpy
<= 12) return HT_DIR_HL
;
2563 if (fxmy
< -3 && sxmy
> 3) return HT_DIR_VR
;
2564 if (fxpy
<= 12 && sxpy
>= 20) return HT_DIR_HU
;
2570 * Check if the direction of start and end tile should be swapped based on
2571 * the dragging-style. Default directions are:
2572 * in the case of a line (HT_RAIL, HT_LINE): DIR_NE, DIR_NW, DIR_N, DIR_E
2573 * in the case of a rect (HT_RECT, HT_POINT): DIR_S, DIR_E
2574 * For example dragging a rectangle area from south to north should be swapped to
2575 * north-south (DIR_S) to obtain the same results with less code. This is what
2576 * the return value signifies.
2577 * @param style HighLightStyle dragging style
2578 * @param start_tile start tile of drag
2579 * @param end_tile end tile of drag
2580 * @return boolean value which when true means start/end should be swapped
2582 static bool SwapDirection(HighLightStyle style
, TileIndex start_tile
, TileIndex end_tile
)
2584 uint start_x
= TileX(start_tile
);
2585 uint start_y
= TileY(start_tile
);
2586 uint end_x
= TileX(end_tile
);
2587 uint end_y
= TileY(end_tile
);
2589 switch (style
& HT_DRAG_MASK
) {
2591 case HT_LINE
: return (end_x
> start_x
|| (end_x
== start_x
&& end_y
> start_y
));
2594 case HT_POINT
: return (end_x
!= start_x
&& end_y
< start_y
);
2595 default: NOT_REACHED();
2602 * Calculates height difference between one tile and another.
2603 * Multiplies the result to suit the standard given by #TILE_HEIGHT_STEP.
2605 * To correctly get the height difference we need the direction we are dragging
2606 * in, as well as with what kind of tool we are dragging. For example a horizontal
2607 * autorail tool that starts in bottom and ends at the top of a tile will need the
2608 * maximum of SW, S and SE, N corners respectively. This is handled by the lookup table below
2609 * See #_tileoffs_by_dir in map.cpp for the direction enums if you can't figure out the values yourself.
2610 * @param style Highlighting style of the drag. This includes direction and style (autorail, rect, etc.)
2611 * @param distance Number of tiles dragged, important for horizontal/vertical drags, ignored for others.
2612 * @param start_tile Start tile of the drag operation.
2613 * @param end_tile End tile of the drag operation.
2614 * @return Height difference between two tiles. The tile measurement tool utilizes this value in its tooltip.
2616 static int CalcHeightdiff(HighLightStyle style
, uint distance
, TileIndex start_tile
, TileIndex end_tile
)
2618 bool swap
= SwapDirection(style
, start_tile
, end_tile
);
2619 uint h0
, h1
; // Start height and end height.
2621 if (start_tile
== end_tile
) return 0;
2622 if (swap
) Swap(start_tile
, end_tile
);
2624 switch (style
& HT_DRAG_MASK
) {
2626 static const TileIndexDiffC heightdiff_area_by_dir
[] = {
2627 /* Start */ {1, 0}, /* Dragging east */ {0, 0}, // Dragging south
2628 /* End */ {0, 1}, /* Dragging east */ {1, 1} // Dragging south
2631 /* In the case of an area we can determine whether we were dragging south or
2632 * east by checking the X-coordinates of the tiles */
2633 byte style_t
= (byte
)(TileX(end_tile
) > TileX(start_tile
));
2634 start_tile
= TILE_ADD(start_tile
, ToTileIndexDiff(heightdiff_area_by_dir
[style_t
]));
2635 end_tile
= TILE_ADD(end_tile
, ToTileIndexDiff(heightdiff_area_by_dir
[2 + style_t
]));
2640 h0
= TileHeight(start_tile
);
2641 h1
= TileHeight(end_tile
);
2643 default: { // All other types, this is mostly only line/autorail
2644 static const HighLightStyle flip_style_direction
[] = {
2645 HT_DIR_X
, HT_DIR_Y
, HT_DIR_HL
, HT_DIR_HU
, HT_DIR_VR
, HT_DIR_VL
2647 static const TileIndexDiffC heightdiff_line_by_dir
[] = {
2648 /* Start */ {1, 0}, {1, 1}, /* HT_DIR_X */ {0, 1}, {1, 1}, // HT_DIR_Y
2649 /* Start */ {1, 0}, {0, 0}, /* HT_DIR_HU */ {1, 0}, {1, 1}, // HT_DIR_HL
2650 /* Start */ {1, 0}, {1, 1}, /* HT_DIR_VL */ {0, 1}, {1, 1}, // HT_DIR_VR
2652 /* Start */ {0, 1}, {0, 0}, /* HT_DIR_X */ {1, 0}, {0, 0}, // HT_DIR_Y
2653 /* End */ {0, 1}, {0, 0}, /* HT_DIR_HU */ {1, 1}, {0, 1}, // HT_DIR_HL
2654 /* End */ {1, 0}, {0, 0}, /* HT_DIR_VL */ {0, 0}, {0, 1}, // HT_DIR_VR
2657 distance
%= 2; // we're only interested if the distance is even or uneven
2658 style
&= HT_DIR_MASK
;
2660 /* To handle autorail, we do some magic to be able to use a lookup table.
2661 * Firstly if we drag the other way around, we switch start&end, and if needed
2662 * also flip the drag-position. Eg if it was on the left, and the distance is even
2663 * that means the end, which is now the start is on the right */
2664 if (swap
&& distance
== 0) style
= flip_style_direction
[style
];
2666 /* Use lookup table for start-tile based on HighLightStyle direction */
2667 byte style_t
= style
* 2;
2668 assert(style_t
< lengthof(heightdiff_line_by_dir
) - 13);
2669 h0
= TileHeight(TILE_ADD(start_tile
, ToTileIndexDiff(heightdiff_line_by_dir
[style_t
])));
2670 uint ht
= TileHeight(TILE_ADD(start_tile
, ToTileIndexDiff(heightdiff_line_by_dir
[style_t
+ 1])));
2673 /* Use lookup table for end-tile based on HighLightStyle direction
2674 * flip around side (lower/upper, left/right) based on distance */
2675 if (distance
== 0) style_t
= flip_style_direction
[style
] * 2;
2676 assert(style_t
< lengthof(heightdiff_line_by_dir
) - 13);
2677 h1
= TileHeight(TILE_ADD(end_tile
, ToTileIndexDiff(heightdiff_line_by_dir
[12 + style_t
])));
2678 ht
= TileHeight(TILE_ADD(end_tile
, ToTileIndexDiff(heightdiff_line_by_dir
[12 + style_t
+ 1])));
2684 if (swap
) Swap(h0
, h1
);
2685 return (int)(h1
- h0
) * TILE_HEIGHT_STEP
;
2688 static const StringID measure_strings_length
[] = {STR_NULL
, STR_MEASURE_LENGTH
, STR_MEASURE_LENGTH_HEIGHTDIFF
};
2691 * Check for underflowing the map.
2692 * @param test the variable to test for underflowing
2693 * @param other the other variable to update to keep the line
2694 * @param mult the constant to multiply the difference by for \c other
2696 static void CheckUnderflow(int &test
, int &other
, int mult
)
2698 if (test
>= 0) return;
2700 other
+= mult
* test
;
2705 * Check for overflowing the map.
2706 * @param test the variable to test for overflowing
2707 * @param other the other variable to update to keep the line
2708 * @param max the maximum value for the \c test variable
2709 * @param mult the constant to multiply the difference by for \c other
2711 static void CheckOverflow(int &test
, int &other
, int max
, int mult
)
2713 if (test
<= max
) return;
2715 other
+= mult
* (test
- max
);
2719 /** while dragging */
2720 static void CalcRaildirsDrawstyle(int x
, int y
, int method
)
2724 int dx
= _thd
.selstart
.x
- (_thd
.selend
.x
& ~TILE_UNIT_MASK
);
2725 int dy
= _thd
.selstart
.y
- (_thd
.selend
.y
& ~TILE_UNIT_MASK
);
2726 uint w
= abs(dx
) + TILE_SIZE
;
2727 uint h
= abs(dy
) + TILE_SIZE
;
2729 if (method
& ~(VPM_RAILDIRS
| VPM_SIGNALDIRS
)) {
2730 /* We 'force' a selection direction; first four rail buttons. */
2731 method
&= ~(VPM_RAILDIRS
| VPM_SIGNALDIRS
);
2732 int raw_dx
= _thd
.selstart
.x
- _thd
.selend
.x
;
2733 int raw_dy
= _thd
.selstart
.y
- _thd
.selend
.y
;
2736 b
= HT_LINE
| HT_DIR_Y
;
2737 x
= _thd
.selstart
.x
;
2741 b
= HT_LINE
| HT_DIR_X
;
2742 y
= _thd
.selstart
.y
;
2745 case VPM_FIX_HORIZONTAL
:
2747 /* We are on a straight horizontal line. Determine the 'rail'
2748 * to build based the sub tile location. */
2749 b
= (x
& TILE_UNIT_MASK
) + (y
& TILE_UNIT_MASK
) >= TILE_SIZE
? HT_LINE
| HT_DIR_HL
: HT_LINE
| HT_DIR_HU
;
2751 /* We are not on a straight line. Determine the rail to build
2752 * based on whether we are above or below it. */
2753 b
= dx
+ dy
>= (int)TILE_SIZE
? HT_LINE
| HT_DIR_HU
: HT_LINE
| HT_DIR_HL
;
2755 /* Calculate where a horizontal line through the start point and
2756 * a vertical line from the selected end point intersect and
2757 * use that point as the end point. */
2758 int offset
= (raw_dx
- raw_dy
) / 2;
2759 x
= _thd
.selstart
.x
- (offset
& ~TILE_UNIT_MASK
);
2760 y
= _thd
.selstart
.y
+ (offset
& ~TILE_UNIT_MASK
);
2762 /* 'Build' the last half rail tile if needed */
2763 if ((offset
& TILE_UNIT_MASK
) > (TILE_SIZE
/ 2)) {
2764 if (dx
+ dy
>= (int)TILE_SIZE
) {
2765 x
+= (dx
+ dy
< 0) ? (int)TILE_SIZE
: -(int)TILE_SIZE
;
2767 y
+= (dx
+ dy
< 0) ? (int)TILE_SIZE
: -(int)TILE_SIZE
;
2771 /* Make sure we do not overflow the map! */
2772 CheckUnderflow(x
, y
, 1);
2773 CheckUnderflow(y
, x
, 1);
2774 CheckOverflow(x
, y
, (MapMaxX() - 1) * TILE_SIZE
, 1);
2775 CheckOverflow(y
, x
, (MapMaxY() - 1) * TILE_SIZE
, 1);
2776 assert(x
>= 0 && y
>= 0 && x
<= (int)(MapMaxX() * TILE_SIZE
) && y
<= (int)(MapMaxY() * TILE_SIZE
));
2780 case VPM_FIX_VERTICAL
:
2782 /* We are on a straight vertical line. Determine the 'rail'
2783 * to build based the sub tile location. */
2784 b
= (x
& TILE_UNIT_MASK
) > (y
& TILE_UNIT_MASK
) ? HT_LINE
| HT_DIR_VL
: HT_LINE
| HT_DIR_VR
;
2786 /* We are not on a straight line. Determine the rail to build
2787 * based on whether we are left or right from it. */
2788 b
= dx
< dy
? HT_LINE
| HT_DIR_VL
: HT_LINE
| HT_DIR_VR
;
2790 /* Calculate where a vertical line through the start point and
2791 * a horizontal line from the selected end point intersect and
2792 * use that point as the end point. */
2793 int offset
= (raw_dx
+ raw_dy
+ (int)TILE_SIZE
) / 2;
2794 x
= _thd
.selstart
.x
- (offset
& ~TILE_UNIT_MASK
);
2795 y
= _thd
.selstart
.y
- (offset
& ~TILE_UNIT_MASK
);
2797 /* 'Build' the last half rail tile if needed */
2798 if ((offset
& TILE_UNIT_MASK
) > (TILE_SIZE
/ 2)) {
2800 y
+= (dx
> dy
) ? (int)TILE_SIZE
: -(int)TILE_SIZE
;
2802 x
+= (dx
< dy
) ? (int)TILE_SIZE
: -(int)TILE_SIZE
;
2806 /* Make sure we do not overflow the map! */
2807 CheckUnderflow(x
, y
, -1);
2808 CheckUnderflow(y
, x
, -1);
2809 CheckOverflow(x
, y
, (MapMaxX() - 1) * TILE_SIZE
, -1);
2810 CheckOverflow(y
, x
, (MapMaxY() - 1) * TILE_SIZE
, -1);
2811 assert(x
>= 0 && y
>= 0 && x
<= (int)(MapMaxX() * TILE_SIZE
) && y
<= (int)(MapMaxY() * TILE_SIZE
));
2818 } else if (TileVirtXY(_thd
.selstart
.x
, _thd
.selstart
.y
) == TileVirtXY(x
, y
)) { // check if we're only within one tile
2819 if (method
& VPM_RAILDIRS
) {
2820 b
= GetAutorailHT(x
, y
);
2821 } else { // rect for autosignals on one tile
2824 } else if (h
== TILE_SIZE
) { // Is this in X direction?
2825 if (dx
== (int)TILE_SIZE
) { // 2x1 special handling
2826 b
= (Check2x1AutoRail(3)) | HT_LINE
;
2827 } else if (dx
== -(int)TILE_SIZE
) {
2828 b
= (Check2x1AutoRail(2)) | HT_LINE
;
2830 b
= HT_LINE
| HT_DIR_X
;
2832 y
= _thd
.selstart
.y
;
2833 } else if (w
== TILE_SIZE
) { // Or Y direction?
2834 if (dy
== (int)TILE_SIZE
) { // 2x1 special handling
2835 b
= (Check2x1AutoRail(1)) | HT_LINE
;
2836 } else if (dy
== -(int)TILE_SIZE
) { // 2x1 other direction
2837 b
= (Check2x1AutoRail(0)) | HT_LINE
;
2839 b
= HT_LINE
| HT_DIR_Y
;
2841 x
= _thd
.selstart
.x
;
2842 } else if (w
> h
* 2) { // still count as x dir?
2843 b
= HT_LINE
| HT_DIR_X
;
2844 y
= _thd
.selstart
.y
;
2845 } else if (h
> w
* 2) { // still count as y dir?
2846 b
= HT_LINE
| HT_DIR_Y
;
2847 x
= _thd
.selstart
.x
;
2848 } else { // complicated direction
2850 _thd
.selend
.x
= _thd
.selend
.x
& ~TILE_UNIT_MASK
;
2851 _thd
.selend
.y
= _thd
.selend
.y
& ~TILE_UNIT_MASK
;
2854 if (x
> _thd
.selstart
.x
) {
2855 if (y
> _thd
.selstart
.y
) {
2858 b
= (x
& TILE_UNIT_MASK
) > (y
& TILE_UNIT_MASK
) ? HT_LINE
| HT_DIR_VL
: HT_LINE
| HT_DIR_VR
;
2859 } else if (d
>= 0) {
2860 x
= _thd
.selstart
.x
+ h
;
2861 b
= HT_LINE
| HT_DIR_VL
;
2863 y
= _thd
.selstart
.y
+ w
;
2864 b
= HT_LINE
| HT_DIR_VR
;
2869 b
= (x
& TILE_UNIT_MASK
) + (y
& TILE_UNIT_MASK
) >= TILE_SIZE
? HT_LINE
| HT_DIR_HL
: HT_LINE
| HT_DIR_HU
;
2870 } else if (d
>= 0) {
2871 x
= _thd
.selstart
.x
+ h
;
2872 b
= HT_LINE
| HT_DIR_HL
;
2874 y
= _thd
.selstart
.y
- w
;
2875 b
= HT_LINE
| HT_DIR_HU
;
2879 if (y
> _thd
.selstart
.y
) {
2882 b
= (x
& TILE_UNIT_MASK
) + (y
& TILE_UNIT_MASK
) >= TILE_SIZE
? HT_LINE
| HT_DIR_HL
: HT_LINE
| HT_DIR_HU
;
2883 } else if (d
>= 0) {
2884 x
= _thd
.selstart
.x
- h
;
2885 b
= HT_LINE
| HT_DIR_HU
;
2887 y
= _thd
.selstart
.y
+ w
;
2888 b
= HT_LINE
| HT_DIR_HL
;
2893 b
= (x
& TILE_UNIT_MASK
) > (y
& TILE_UNIT_MASK
) ? HT_LINE
| HT_DIR_VL
: HT_LINE
| HT_DIR_VR
;
2894 } else if (d
>= 0) {
2895 x
= _thd
.selstart
.x
- h
;
2896 b
= HT_LINE
| HT_DIR_VR
;
2898 y
= _thd
.selstart
.y
- w
;
2899 b
= HT_LINE
| HT_DIR_VL
;
2905 if (_settings_client
.gui
.measure_tooltip
) {
2906 TileIndex t0
= TileVirtXY(_thd
.selstart
.x
, _thd
.selstart
.y
);
2907 TileIndex t1
= TileVirtXY(x
, y
);
2908 uint distance
= DistanceManhattan(t0
, t1
) + 1;
2912 if (distance
!= 1) {
2913 int heightdiff
= CalcHeightdiff(b
, distance
, t0
, t1
);
2914 /* If we are showing a tooltip for horizontal or vertical drags,
2915 * 2 tiles have a length of 1. To bias towards the ceiling we add
2916 * one before division. It feels more natural to count 3 lengths as 2 */
2917 if ((b
& HT_DIR_MASK
) != HT_DIR_X
&& (b
& HT_DIR_MASK
) != HT_DIR_Y
) {
2918 distance
= CeilDiv(distance
, 2);
2921 params
[index
++] = distance
;
2922 if (heightdiff
!= 0) params
[index
++] = heightdiff
;
2925 ShowMeasurementTooltips(measure_strings_length
[index
], index
, params
);
2930 _thd
.next_drawstyle
= b
;
2934 * Selects tiles while dragging
2935 * @param x X coordinate of end of selection
2936 * @param y Y coordinate of end of selection
2937 * @param method modifies the way tiles are selected. Possible
2938 * methods are VPM_* in viewport.h
2940 void VpSelectTilesWithMethod(int x
, int y
, ViewportPlaceMethod method
)
2943 HighLightStyle style
;
2950 /* Special handling of drag in any (8-way) direction */
2951 if (method
& (VPM_RAILDIRS
| VPM_SIGNALDIRS
)) {
2954 CalcRaildirsDrawstyle(x
, y
, method
);
2958 /* Needed so level-land is placed correctly */
2959 if ((_thd
.next_drawstyle
& HT_DRAG_MASK
) == HT_POINT
) {
2964 sx
= _thd
.selstart
.x
;
2965 sy
= _thd
.selstart
.y
;
2970 case VPM_X_OR_Y
: // drag in X or Y direction
2971 if (abs(sy
- y
) < abs(sx
- x
)) {
2978 goto calc_heightdiff_single_direction
;
2980 case VPM_X_LIMITED
: // Drag in X direction (limited size).
2981 limit
= (_thd
.sizelimit
- 1) * TILE_SIZE
;
2984 case VPM_FIX_X
: // drag in Y direction
2987 goto calc_heightdiff_single_direction
;
2989 case VPM_Y_LIMITED
: // Drag in Y direction (limited size).
2990 limit
= (_thd
.sizelimit
- 1) * TILE_SIZE
;
2993 case VPM_FIX_Y
: // drag in X direction
2997 calc_heightdiff_single_direction
:;
2999 x
= sx
+ Clamp(x
- sx
, -limit
, limit
);
3000 y
= sy
+ Clamp(y
- sy
, -limit
, limit
);
3002 if (_settings_client
.gui
.measure_tooltip
) {
3003 TileIndex t0
= TileVirtXY(sx
, sy
);
3004 TileIndex t1
= TileVirtXY(x
, y
);
3005 uint distance
= DistanceManhattan(t0
, t1
) + 1;
3009 if (distance
!= 1) {
3010 /* With current code passing a HT_LINE style to calculate the height
3011 * difference is enough. However if/when a point-tool is created
3012 * with this method, function should be called with new_style (below)
3013 * instead of HT_LINE | style case HT_POINT is handled specially
3014 * new_style := (_thd.next_drawstyle & HT_RECT) ? HT_LINE | style : _thd.next_drawstyle; */
3015 int heightdiff
= CalcHeightdiff(HT_LINE
| style
, 0, t0
, t1
);
3017 params
[index
++] = distance
;
3018 if (heightdiff
!= 0) params
[index
++] = heightdiff
;
3021 ShowMeasurementTooltips(measure_strings_length
[index
], index
, params
);
3025 case VPM_X_AND_Y_LIMITED
: // Drag an X by Y constrained rect area.
3026 limit
= (_thd
.sizelimit
- 1) * TILE_SIZE
;
3027 x
= sx
+ Clamp(x
- sx
, -limit
, limit
);
3028 y
= sy
+ Clamp(y
- sy
, -limit
, limit
);
3031 case VPM_X_AND_Y
: // drag an X by Y area
3032 if (_settings_client
.gui
.measure_tooltip
) {
3033 static const StringID measure_strings_area
[] = {
3034 STR_NULL
, STR_NULL
, STR_MEASURE_AREA
, STR_MEASURE_AREA_HEIGHTDIFF
3037 TileIndex t0
= TileVirtXY(sx
, sy
);
3038 TileIndex t1
= TileVirtXY(x
, y
);
3039 uint dx
= Delta(TileX(t0
), TileX(t1
)) + 1;
3040 uint dy
= Delta(TileY(t0
), TileY(t1
)) + 1;
3044 /* If dragging an area (eg dynamite tool) and it is actually a single
3045 * row/column, change the type to 'line' to get proper calculation for height */
3046 style
= (HighLightStyle
)_thd
.next_drawstyle
;
3047 if (_thd
.IsDraggingDiagonal()) {
3048 /* Determine the "area" of the diagonal dragged selection.
3049 * We assume the area is the number of tiles along the X
3050 * edge and the number of tiles along the Y edge. However,
3051 * multiplying these two numbers does not give the exact
3052 * number of tiles; basically we are counting the black
3053 * squares on a chess board and ignore the white ones to
3054 * make the tile counts at the edges match up. There is no
3055 * other way to make a proper count though.
3057 * First convert to the rotated coordinate system. */
3058 int dist_x
= TileX(t0
) - TileX(t1
);
3059 int dist_y
= TileY(t0
) - TileY(t1
);
3060 int a_max
= dist_x
+ dist_y
;
3061 int b_max
= dist_y
- dist_x
;
3063 /* Now determine the size along the edge, but due to the
3064 * chess board principle this counts double. */
3065 a_max
= abs(a_max
+ (a_max
> 0 ? 2 : -2)) / 2;
3066 b_max
= abs(b_max
+ (b_max
> 0 ? 2 : -2)) / 2;
3068 /* We get a 1x1 on normal 2x1 rectangles, due to it being
3069 * a seen as two sides. As the result for actual building
3070 * will be the same as non-diagonal dragging revert to that
3071 * behaviour to give it a more normally looking size. */
3072 if (a_max
!= 1 || b_max
!= 1) {
3076 } else if (style
& HT_RECT
) {
3078 style
= HT_LINE
| HT_DIR_Y
;
3079 } else if (dy
== 1) {
3080 style
= HT_LINE
| HT_DIR_X
;
3084 if (dx
!= 1 || dy
!= 1) {
3085 int heightdiff
= CalcHeightdiff(style
, 0, t0
, t1
);
3087 params
[index
++] = dx
- (style
& HT_POINT
? 1 : 0);
3088 params
[index
++] = dy
- (style
& HT_POINT
? 1 : 0);
3089 if (heightdiff
!= 0) params
[index
++] = heightdiff
;
3092 ShowMeasurementTooltips(measure_strings_area
[index
], index
, params
);
3096 default: NOT_REACHED();
3104 * Handle the mouse while dragging for placement/resizing.
3105 * @return State of handling the event.
3107 EventState
VpHandlePlaceSizingDrag()
3109 if (_special_mouse_mode
!= WSM_SIZING
) return ES_NOT_HANDLED
;
3111 /* stop drag mode if the window has been closed */
3112 Window
*w
= _thd
.GetCallbackWnd();
3114 ResetObjectToPlace();
3118 /* while dragging execute the drag procedure of the corresponding window (mostly VpSelectTilesWithMethod() ) */
3119 if (_left_button_down
) {
3120 w
->OnPlaceDrag(_thd
.select_method
, _thd
.select_proc
, GetTileBelowCursor());
3124 /* mouse button released..
3125 * keep the selected tool, but reset it to the original mode. */
3126 _special_mouse_mode
= WSM_NONE
;
3127 HighLightStyle others
= _thd
.place_mode
& ~(HT_DRAG_MASK
| HT_DIR_MASK
);
3128 if ((_thd
.next_drawstyle
& HT_DRAG_MASK
) == HT_RECT
) {
3129 _thd
.place_mode
= HT_RECT
| others
;
3130 } else if (_thd
.select_method
& VPM_SIGNALDIRS
) {
3131 _thd
.place_mode
= HT_RECT
| others
;
3132 } else if (_thd
.select_method
& VPM_RAILDIRS
) {
3133 _thd
.place_mode
= (_thd
.select_method
& ~VPM_RAILDIRS
) ? _thd
.next_drawstyle
: (HT_RAIL
| others
);
3135 _thd
.place_mode
= HT_POINT
| others
;
3137 SetTileSelectSize(1, 1);
3139 w
->OnPlaceMouseUp(_thd
.select_method
, _thd
.select_proc
, _thd
.selend
, TileVirtXY(_thd
.selstart
.x
, _thd
.selstart
.y
), TileVirtXY(_thd
.selend
.x
, _thd
.selend
.y
));
3145 * Change the cursor and mouse click/drag handling to a mode for performing special operations like tile area selection, object placement, etc.
3146 * @param icon New shape of the mouse cursor.
3147 * @param pal Palette to use.
3148 * @param mode Mode to perform.
3149 * @param w %Window requesting the mode change.
3151 void SetObjectToPlaceWnd(CursorID icon
, PaletteID pal
, HighLightStyle mode
, Window
*w
)
3153 SetObjectToPlace(icon
, pal
, mode
, w
->window_class
, w
->window_number
);
3156 #include "table/animcursors.h"
3159 * Change the cursor and mouse click/drag handling to a mode for performing special operations like tile area selection, object placement, etc.
3160 * @param icon New shape of the mouse cursor.
3161 * @param pal Palette to use.
3162 * @param mode Mode to perform.
3163 * @param window_class %Window class of the window requesting the mode change.
3164 * @param window_num Number of the window in its class requesting the mode change.
3166 void SetObjectToPlace(CursorID icon
, PaletteID pal
, HighLightStyle mode
, WindowClass window_class
, WindowNumber window_num
)
3168 if (_thd
.window_class
!= WC_INVALID
) {
3169 /* Undo clicking on button and drag & drop */
3170 Window
*w
= _thd
.GetCallbackWnd();
3171 /* Call the abort function, but set the window class to something
3172 * that will never be used to avoid infinite loops. Setting it to
3173 * the 'next' window class must not be done because recursion into
3174 * this function might in some cases reset the newly set object to
3175 * place or not properly reset the original selection. */
3176 _thd
.window_class
= WC_INVALID
;
3177 if (w
!= NULL
) w
->OnPlaceObjectAbort();
3180 /* Mark the old selection dirty, in case the selection shape or colour changes */
3181 if ((_thd
.drawstyle
& HT_DRAG_MASK
) != HT_NONE
) SetSelectionTilesDirty();
3183 SetTileSelectSize(1, 1);
3185 _thd
.make_square_red
= false;
3187 if (mode
== HT_DRAG
) { // HT_DRAG is for dragdropping trains in the depot window
3189 _special_mouse_mode
= WSM_DRAGDROP
;
3191 _special_mouse_mode
= WSM_NONE
;
3194 _thd
.place_mode
= mode
;
3195 _thd
.window_class
= window_class
;
3196 _thd
.window_number
= window_num
;
3198 if ((mode
& HT_DRAG_MASK
) == HT_SPECIAL
) { // special tools, like tunnels or docks start with presizing mode
3202 if ((icon
& ANIMCURSOR_FLAG
) != 0) {
3203 SetAnimatedMouseCursor(_animcursors
[icon
& ~ANIMCURSOR_FLAG
]);
3205 SetMouseCursor(icon
, pal
);
3210 /** Reset the cursor and mouse mode handling back to default (normal cursor, only clicking in windows). */
3211 void ResetObjectToPlace()
3213 SetObjectToPlace(SPR_CURSOR_MOUSE
, PAL_NONE
, HT_NONE
, WC_MAIN_WINDOW
, 0);
3216 Point
GetViewportStationMiddle(const ViewPort
*vp
, const Station
*st
)
3218 int x
= TileX(st
->xy
) * TILE_SIZE
;
3219 int y
= TileY(st
->xy
) * TILE_SIZE
;
3220 int z
= GetSlopePixelZ(Clamp(x
, 0, MapSizeX() * TILE_SIZE
- 1), Clamp(y
, 0, MapSizeY() * TILE_SIZE
- 1));
3222 Point p
= RemapCoords(x
, y
, z
);
3223 p
.x
= UnScaleByZoom(p
.x
- vp
->virtual_left
, vp
->zoom
) + vp
->left
;
3224 p
.y
= UnScaleByZoom(p
.y
- vp
->virtual_top
, vp
->zoom
) + vp
->top
;
3228 /** Helper class for getting the best sprite sorter. */
3229 struct ViewportSSCSS
{
3230 VpSorterChecker fct_checker
; ///< The check function.
3231 VpSpriteSorter fct_sorter
; ///< The sorting function.
3234 /** List of sorters ordered from best to worst. */
3235 static ViewportSSCSS _vp_sprite_sorters
[] = {
3237 { &ViewportSortParentSpritesSSE41Checker
, &ViewportSortParentSpritesSSE41
},
3239 { &ViewportSortParentSpritesChecker
, &ViewportSortParentSprites
}
3242 /** Choose the "best" sprite sorter and set _vp_sprite_sorter. */
3243 void InitializeSpriteSorter()
3245 for (uint i
= 0; i
< lengthof(_vp_sprite_sorters
); i
++) {
3246 if (_vp_sprite_sorters
[i
].fct_checker()) {
3247 _vp_sprite_sorter
= _vp_sprite_sorters
[i
].fct_sorter
;
3251 assert(_vp_sprite_sorter
!= NULL
);