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 #include "map/zoneheight.h"
31 #include "map/slope.h"
32 #include "landscape.h"
33 #include "viewport_func.h"
34 #include "station_base.h"
35 #include "waypoint_base.h"
37 #include "signs_base.h"
38 #include "signs_func.h"
39 #include "vehicle_base.h"
40 #include "vehicle_gui.h"
41 #include "blitter/factory.hpp"
42 #include "strings_func.h"
43 #include "zoom_func.h"
44 #include "vehicle_func.h"
45 #include "company_func.h"
46 #include "waypoint_func.h"
47 #include "window_func.h"
48 #include "tilehighlight_func.h"
49 #include "window_gui.h"
50 #include "linkgraph/linkgraph_gui.h"
51 #include "clear_func.h"
52 #include "station_func.h"
53 #include "viewport_sprite_sorter.h"
55 #include "table/strings.h"
56 #include "table/palettes.h"
58 Point _tile_fract_coords
;
60 struct StringSpriteToDraw
{
69 struct TileSpriteToDraw
{
72 const SubSprite
*sub
; ///< only draw a rectangular part of the sprite
73 int32 x
; ///< screen X coordinate of sprite
74 int32 y
; ///< screen Y coordinate of sprite
77 struct ChildScreenSpriteToDraw
{
80 const SubSprite
*sub
; ///< only draw a rectangular part of the sprite
83 int next
; ///< next child to draw (-1 at the end)
86 /** Enumeration of multi-part foundations */
88 FOUNDATION_PART_NONE
= 0xFF, ///< Neither foundation nor groundsprite drawn yet.
89 FOUNDATION_PART_NORMAL
= 0, ///< First part (normal foundation or no foundation)
90 FOUNDATION_PART_HALFTILE
= 1, ///< Second part (halftile foundation)
95 * Mode of "sprite combining"
96 * @see StartSpriteCombine
98 enum SpriteCombineMode
{
99 SPRITE_COMBINE_NONE
, ///< Every #AddSortableSpriteToDraw start its own bounding box
100 SPRITE_COMBINE_PENDING
, ///< %Sprite combining will start with the next unclipped sprite.
101 SPRITE_COMBINE_ACTIVE
, ///< %Sprite combining is active. #AddSortableSpriteToDraw outputs child sprites.
104 typedef SmallVector
<TileSpriteToDraw
, 64> TileSpriteToDrawVector
;
105 typedef SmallVector
<StringSpriteToDraw
, 4> StringSpriteToDrawVector
;
106 typedef SmallVector
<ParentSpriteToDraw
, 64> ParentSpriteToDrawVector
;
107 typedef SmallVector
<ChildScreenSpriteToDraw
, 16> ChildScreenSpriteToDrawVector
;
109 /** Data structure storing rendering information */
110 struct ViewportDrawer
{
113 StringSpriteToDrawVector string_sprites_to_draw
;
114 TileSpriteToDrawVector tile_sprites_to_draw
;
115 ParentSpriteToDrawVector parent_sprites_to_draw
;
116 ParentSpriteToSortVector parent_sprites_to_sort
; ///< Parent sprite pointer array used for sorting
117 ChildScreenSpriteToDrawVector child_screen_sprites_to_draw
;
121 SpriteCombineMode combine_sprites
; ///< Current mode of "sprite combining". @see StartSpriteCombine
123 int foundation
[FOUNDATION_PART_END
]; ///< Foundation sprites (index into parent_sprites_to_draw).
124 FoundationPart foundation_part
; ///< Currently active foundation for ground sprite drawing.
125 int *last_foundation_child
[FOUNDATION_PART_END
]; ///< Tail of ChildSprite list of the foundations. (index into child_screen_sprites_to_draw)
126 Point foundation_offset
[FOUNDATION_PART_END
]; ///< Pixel offset for ground sprites on the foundations.
129 static void MarkViewportDirty(const ViewPort
*vp
, int left
, int top
, int right
, int bottom
);
131 static ViewportDrawer _vd
;
133 TileHighlightData _thd
;
134 static TileInfo
*_cur_ti
;
135 bool _draw_bounding_boxes
= false;
136 bool _draw_dirty_blocks
= false;
137 uint _dirty_block_colour
= 0;
138 static VpSpriteSorter _vp_sprite_sorter
= NULL
;
140 static Point
MapXYZToViewport(const ViewPort
*vp
, int x
, int y
, int z
)
142 Point p
= RemapCoords(x
, y
, z
);
143 p
.x
-= vp
->virtual_width
/ 2;
144 p
.y
-= vp
->virtual_height
/ 2;
148 void DeleteWindowViewport(Window
*w
)
150 if (w
->viewport
== NULL
) return;
152 delete w
->viewport
->overlay
;
158 * Initialize viewport of the window for use.
159 * @param w Window to use/display the viewport in
160 * @param x Offset of left edge of viewport with respect to left edge window \a w
161 * @param y Offset of top edge of viewport with respect to top edge window \a w
162 * @param width Width of the viewport
163 * @param height Height of the viewport
164 * @param follow_flags Flags controlling the viewport.
165 * - If bit 31 is set, the lower 20 bits are the vehicle that the viewport should follow.
166 * - If bit 31 is clear, it is a #TileIndex.
167 * @param zoom Zoomlevel to display
169 void InitializeWindowViewport(Window
*w
, int x
, int y
,
170 int width
, int height
, uint32 follow_flags
, ZoomLevel zoom
)
172 assert(w
->viewport
== NULL
);
174 ViewportData
*vp
= CallocT
<ViewportData
>(1);
176 vp
->left
= x
+ w
->left
;
177 vp
->top
= y
+ w
->top
;
181 vp
->zoom
= static_cast<ZoomLevel
>(Clamp(zoom
, _settings_client
.gui
.zoom_min
, _settings_client
.gui
.zoom_max
));
183 vp
->virtual_width
= ScaleByZoom(width
, zoom
);
184 vp
->virtual_height
= ScaleByZoom(height
, zoom
);
188 if (follow_flags
& 0x80000000) {
191 vp
->follow_vehicle
= (VehicleID
)(follow_flags
& 0xFFFFF);
192 veh
= Vehicle::Get(vp
->follow_vehicle
);
193 pt
= MapXYZToViewport(vp
, veh
->x_pos
, veh
->y_pos
, veh
->z_pos
);
195 uint x
= TileX(follow_flags
) * TILE_SIZE
;
196 uint y
= TileY(follow_flags
) * TILE_SIZE
;
198 vp
->follow_vehicle
= INVALID_VEHICLE
;
199 pt
= MapXYZToViewport(vp
, x
, y
, GetSlopePixelZ(x
, y
));
202 vp
->scrollpos_x
= pt
.x
;
203 vp
->scrollpos_y
= pt
.y
;
204 vp
->dest_scrollpos_x
= pt
.x
;
205 vp
->dest_scrollpos_y
= pt
.y
;
210 vp
->virtual_left
= 0;//pt.x;
211 vp
->virtual_top
= 0;//pt.y;
214 static Point _vp_move_offs
;
216 static void DoSetViewportPosition(const Window
*w
, int left
, int top
, int width
, int height
)
218 FOR_ALL_WINDOWS_FROM_BACK_FROM(w
, w
) {
219 if (left
+ width
> w
->left
&&
220 w
->left
+ w
->width
> left
&&
221 top
+ height
> w
->top
&&
222 w
->top
+ w
->height
> top
) {
224 if (left
< w
->left
) {
225 DoSetViewportPosition(w
, left
, top
, w
->left
- left
, height
);
226 DoSetViewportPosition(w
, left
+ (w
->left
- left
), top
, width
- (w
->left
- left
), height
);
230 if (left
+ width
> w
->left
+ w
->width
) {
231 DoSetViewportPosition(w
, left
, top
, (w
->left
+ w
->width
- left
), height
);
232 DoSetViewportPosition(w
, left
+ (w
->left
+ w
->width
- left
), top
, width
- (w
->left
+ w
->width
- left
), height
);
237 DoSetViewportPosition(w
, left
, top
, width
, (w
->top
- top
));
238 DoSetViewportPosition(w
, left
, top
+ (w
->top
- top
), width
, height
- (w
->top
- top
));
242 if (top
+ height
> w
->top
+ w
->height
) {
243 DoSetViewportPosition(w
, left
, top
, width
, (w
->top
+ w
->height
- top
));
244 DoSetViewportPosition(w
, left
, top
+ (w
->top
+ w
->height
- top
), width
, height
- (w
->top
+ w
->height
- top
));
253 int xo
= _vp_move_offs
.x
;
254 int yo
= _vp_move_offs
.y
;
256 if (abs(xo
) >= width
|| abs(yo
) >= height
) {
258 RedrawScreenRect(left
, top
, left
+ width
, top
+ height
);
262 GfxScroll(left
, top
, width
, height
, xo
, yo
);
265 RedrawScreenRect(left
, top
, xo
+ left
, top
+ height
);
269 RedrawScreenRect(left
+ width
+ xo
, top
, left
+ width
, top
+ height
);
274 RedrawScreenRect(left
, top
, width
+ left
, top
+ yo
);
276 RedrawScreenRect(left
, top
+ height
+ yo
, width
+ left
, top
+ height
);
281 static void SetViewportPosition(Window
*w
, int x
, int y
)
283 ViewPort
*vp
= w
->viewport
;
284 int old_left
= vp
->virtual_left
;
285 int old_top
= vp
->virtual_top
;
287 int left
, top
, width
, height
;
289 vp
->virtual_left
= x
;
292 /* Viewport is bound to its left top corner, so it must be rounded down (UnScaleByZoomLower)
293 * else glitch described in FS#1412 will happen (offset by 1 pixel with zoom level > NORMAL)
295 old_left
= UnScaleByZoomLower(old_left
, vp
->zoom
);
296 old_top
= UnScaleByZoomLower(old_top
, vp
->zoom
);
297 x
= UnScaleByZoomLower(x
, vp
->zoom
);
298 y
= UnScaleByZoomLower(y
, vp
->zoom
);
303 if (old_top
== 0 && old_left
== 0) return;
305 _vp_move_offs
.x
= old_left
;
306 _vp_move_offs
.y
= old_top
;
318 i
= left
+ width
- _screen
.width
;
319 if (i
>= 0) width
-= i
;
327 i
= top
+ height
- _screen
.height
;
328 if (i
>= 0) height
-= i
;
330 if (height
> 0) DoSetViewportPosition(w
->z_front
, left
, top
, width
, height
);
335 * Is a xy position inside the viewport of the window?
336 * @param w Window to examine its viewport
337 * @param x X coordinate of the xy position
338 * @param y Y coordinate of the xy position
339 * @return Pointer to the viewport if the xy position is in the viewport of the window,
340 * otherwise \c NULL is returned.
342 ViewPort
*IsPtInWindowViewport(const Window
*w
, int x
, int y
)
344 ViewPort
*vp
= w
->viewport
;
347 IsInsideMM(x
, vp
->left
, vp
->left
+ vp
->width
) &&
348 IsInsideMM(y
, vp
->top
, vp
->top
+ vp
->height
))
355 * Translate screen coordinate in a viewport to a tile coordinate
356 * @param vp Viewport that contains the (\a x, \a y) screen coordinate
357 * @param x Screen x coordinate
358 * @param y Screen y coordinate
359 * @return Tile coordinate
361 static Point
TranslateXYToTileCoord(const ViewPort
*vp
, int x
, int y
)
367 if ( (uint
)(x
-= vp
->left
) >= (uint
)vp
->width
||
368 (uint
)(y
-= vp
->top
) >= (uint
)vp
->height
) {
373 x
= (ScaleByZoom(x
, vp
->zoom
) + vp
->virtual_left
) >> (2 + ZOOM_LVL_SHIFT
);
374 y
= (ScaleByZoom(y
, vp
->zoom
) + vp
->virtual_top
) >> (1 + ZOOM_LVL_SHIFT
);
379 /* we need to move variables in to the valid range, as the
380 * GetTileZoomCenterWindow() function can call here with invalid x and/or y,
381 * when the user tries to zoom out along the sides of the map */
382 a
= Clamp(a
, -4 * (int)TILE_SIZE
, (int)(MapMaxX() * TILE_SIZE
) - 1);
383 b
= Clamp(b
, -4 * (int)TILE_SIZE
, (int)(MapMaxY() * TILE_SIZE
) - 1);
385 /* (a, b) is the X/Y-world coordinate that belongs to (x,y) if the landscape would be completely flat on height 0.
386 * Now find the Z-world coordinate by fix point iteration.
387 * This is a bit tricky because the tile height is non-continuous at foundations.
388 * The clicked point should be approached from the back, otherwise there are regions that are not clickable.
389 * (FOUNDATION_HALFTILE_LOWER on SLOPE_STEEP_S hides north halftile completely)
390 * So give it a z-malus of 4 in the first iterations.
394 int min_coord
= _settings_game
.construction
.freeform_edges
? TILE_SIZE
: 0;
396 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;
397 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;
398 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;
400 pt
.x
= Clamp(a
+ z
, min_coord
, MapMaxX() * TILE_SIZE
- 1);
401 pt
.y
= Clamp(b
+ z
, min_coord
, MapMaxY() * TILE_SIZE
- 1);
406 /* When used for zooming, check area below current coordinates (x,y)
407 * and return the tile of the zoomed out/in position (zoom_x, zoom_y)
408 * when you just want the tile, make x = zoom_x and y = zoom_y */
409 static Point
GetTileFromScreenXY(int x
, int y
, int zoom_x
, int zoom_y
)
415 if ( (w
= FindWindowFromPt(x
, y
)) != NULL
&&
416 (vp
= IsPtInWindowViewport(w
, x
, y
)) != NULL
)
417 return TranslateXYToTileCoord(vp
, zoom_x
, zoom_y
);
423 Point
GetTileBelowCursor()
425 return GetTileFromScreenXY(_cursor
.pos
.x
, _cursor
.pos
.y
, _cursor
.pos
.x
, _cursor
.pos
.y
);
429 Point
GetTileZoomCenterWindow(bool in
, Window
* w
)
432 ViewPort
*vp
= w
->viewport
;
435 x
= ((_cursor
.pos
.x
- vp
->left
) >> 1) + (vp
->width
>> 2);
436 y
= ((_cursor
.pos
.y
- vp
->top
) >> 1) + (vp
->height
>> 2);
438 x
= vp
->width
- (_cursor
.pos
.x
- vp
->left
);
439 y
= vp
->height
- (_cursor
.pos
.y
- vp
->top
);
441 /* Get the tile below the cursor and center on the zoomed-out center */
442 return GetTileFromScreenXY(_cursor
.pos
.x
, _cursor
.pos
.y
, x
+ vp
->left
, y
+ vp
->top
);
446 * Update the status of the zoom-buttons according to the zoom-level
447 * of the viewport. This will update their status and invalidate accordingly
448 * @param w Window pointer to the window that has the zoom buttons
449 * @param vp pointer to the viewport whose zoom-level the buttons represent
450 * @param widget_zoom_in widget index for window with zoom-in button
451 * @param widget_zoom_out widget index for window with zoom-out button
453 void HandleZoomMessage(Window
*w
, const ViewPort
*vp
, byte widget_zoom_in
, byte widget_zoom_out
)
455 w
->SetWidgetDisabledState(widget_zoom_in
, vp
->zoom
<= _settings_client
.gui
.zoom_min
);
456 w
->SetWidgetDirty(widget_zoom_in
);
458 w
->SetWidgetDisabledState(widget_zoom_out
, vp
->zoom
>= _settings_client
.gui
.zoom_max
);
459 w
->SetWidgetDirty(widget_zoom_out
);
463 * Schedules a tile sprite for drawing.
465 * @param image the image to draw.
466 * @param pal the provided palette.
467 * @param x position x (world coordinates) of the sprite.
468 * @param y position y (world coordinates) of the sprite.
469 * @param z position z (world coordinates) of the sprite.
470 * @param sub Only draw a part of the sprite.
471 * @param extra_offs_x Pixel X offset for the sprite position.
472 * @param extra_offs_y Pixel Y offset for the sprite position.
474 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)
476 assert((image
& SPRITE_MASK
) < MAX_SPRITES
);
478 TileSpriteToDraw
*ts
= _vd
.tile_sprites_to_draw
.Append();
482 Point pt
= RemapCoords(x
, y
, z
);
483 ts
->x
= pt
.x
+ extra_offs_x
;
484 ts
->y
= pt
.y
+ extra_offs_y
;
488 * Adds a child sprite to the active foundation.
490 * The pixel offset of the sprite relative to the ParentSprite is the sum of the offset passed to OffsetGroundSprite() and extra_offs_?.
492 * @param image the image to draw.
493 * @param pal the provided palette.
494 * @param sub Only draw a part of the sprite.
495 * @param foundation_part Foundation part.
496 * @param extra_offs_x Pixel X offset for the sprite position.
497 * @param extra_offs_y Pixel Y offset for the sprite position.
499 static void AddChildSpriteToFoundation(SpriteID image
, PaletteID pal
, const SubSprite
*sub
, FoundationPart foundation_part
, int extra_offs_x
, int extra_offs_y
)
501 assert(IsInsideMM(foundation_part
, 0, FOUNDATION_PART_END
));
502 assert(_vd
.foundation
[foundation_part
] != -1);
503 Point offs
= _vd
.foundation_offset
[foundation_part
];
505 /* Change the active ChildSprite list to the one of the foundation */
506 int *old_child
= _vd
.last_child
;
507 _vd
.last_child
= _vd
.last_foundation_child
[foundation_part
];
509 AddChildSpriteScreen(image
, pal
, offs
.x
+ extra_offs_x
, offs
.y
+ extra_offs_y
, false, sub
, false);
511 /* Switch back to last ChildSprite list */
512 _vd
.last_child
= old_child
;
516 * Draws a ground sprite at a specific world-coordinate relative to the current tile.
517 * If the current tile is drawn on top of a foundation the sprite is added as child sprite to the "foundation"-ParentSprite.
519 * @param image the image to draw.
520 * @param pal the provided palette.
521 * @param x position x (world coordinates) of the sprite relative to current tile.
522 * @param y position y (world coordinates) of the sprite relative to current tile.
523 * @param z position z (world coordinates) of the sprite relative to current tile.
524 * @param sub Only draw a part of the sprite.
525 * @param extra_offs_x Pixel X offset for the sprite position.
526 * @param extra_offs_y Pixel Y offset for the sprite position.
528 void DrawGroundSpriteAt(SpriteID image
, PaletteID pal
, int32 x
, int32 y
, int z
, const SubSprite
*sub
, int extra_offs_x
, int extra_offs_y
)
530 /* Switch to first foundation part, if no foundation was drawn */
531 if (_vd
.foundation_part
== FOUNDATION_PART_NONE
) _vd
.foundation_part
= FOUNDATION_PART_NORMAL
;
533 if (_vd
.foundation
[_vd
.foundation_part
] != -1) {
534 Point pt
= RemapCoords(x
, y
, z
);
535 AddChildSpriteToFoundation(image
, pal
, sub
, _vd
.foundation_part
, pt
.x
+ extra_offs_x
* ZOOM_LVL_BASE
, pt
.y
+ extra_offs_y
* ZOOM_LVL_BASE
);
537 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
);
542 * Draws a ground sprite for the current tile.
543 * If the current tile is drawn on top of a foundation the sprite is added as child sprite to the "foundation"-ParentSprite.
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 extra_offs_x Pixel X offset for the sprite position.
549 * @param extra_offs_y Pixel Y offset for the sprite position.
551 void DrawGroundSprite(SpriteID image
, PaletteID pal
, const SubSprite
*sub
, int extra_offs_x
, int extra_offs_y
)
553 DrawGroundSpriteAt(image
, pal
, 0, 0, 0, sub
, extra_offs_x
, extra_offs_y
);
557 * Called when a foundation has been drawn for the current tile.
558 * Successive ground sprites for the current tile will be drawn as child sprites of the "foundation"-ParentSprite, not as TileSprites.
560 * @param x sprite x-offset (screen coordinates) of ground sprites relative to the "foundation"-ParentSprite.
561 * @param y sprite y-offset (screen coordinates) of ground sprites relative to the "foundation"-ParentSprite.
563 void OffsetGroundSprite(int x
, int y
)
565 /* Switch to next foundation part */
566 switch (_vd
.foundation_part
) {
567 case FOUNDATION_PART_NONE
:
568 _vd
.foundation_part
= FOUNDATION_PART_NORMAL
;
570 case FOUNDATION_PART_NORMAL
:
571 _vd
.foundation_part
= FOUNDATION_PART_HALFTILE
;
573 default: NOT_REACHED();
576 /* _vd.last_child == NULL if foundation sprite was clipped by the viewport bounds */
577 if (_vd
.last_child
!= NULL
) _vd
.foundation
[_vd
.foundation_part
] = _vd
.parent_sprites_to_draw
.Length() - 1;
579 _vd
.foundation_offset
[_vd
.foundation_part
].x
= x
* ZOOM_LVL_BASE
;
580 _vd
.foundation_offset
[_vd
.foundation_part
].y
= y
* ZOOM_LVL_BASE
;
581 _vd
.last_foundation_child
[_vd
.foundation_part
] = _vd
.last_child
;
585 * Adds a child sprite to a parent sprite.
586 * In contrast to "AddChildSpriteScreen()" the sprite position is in world coordinates
588 * @param image the image to draw.
589 * @param pal the provided palette.
590 * @param x position x of the sprite.
591 * @param y position y of the sprite.
592 * @param z position z of the sprite.
593 * @param sub Only draw a part of the sprite.
595 static void AddCombinedSprite(SpriteID image
, PaletteID pal
, int x
, int y
, int z
, const SubSprite
*sub
)
597 Point pt
= RemapCoords(x
, y
, z
);
598 const Sprite
*spr
= GetSprite(image
& SPRITE_MASK
, ST_NORMAL
);
600 if (pt
.x
+ spr
->x_offs
>= _vd
.dpi
.left
+ _vd
.dpi
.width
||
601 pt
.x
+ spr
->x_offs
+ spr
->width
<= _vd
.dpi
.left
||
602 pt
.y
+ spr
->y_offs
>= _vd
.dpi
.top
+ _vd
.dpi
.height
||
603 pt
.y
+ spr
->y_offs
+ spr
->height
<= _vd
.dpi
.top
)
606 const ParentSpriteToDraw
*pstd
= _vd
.parent_sprites_to_draw
.End() - 1;
607 AddChildSpriteScreen(image
, pal
, pt
.x
- pstd
->left
, pt
.y
- pstd
->top
, false, sub
, false);
611 * Draw a (transparent) sprite at given coordinates with a given bounding box.
612 * 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.
613 * Bounding boxes with bb_offset_x == w or bb_offset_y == h or bb_offset_z == dz are allowed and produce thin slices.
615 * @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
616 * defined by the sprite offset in the grf file.
617 * However if modifying the sprite offsets is not suitable (e.g. when using existing graphics), the bounding box can be tuned by bb_offset.
619 * @pre w >= bb_offset_x, h >= bb_offset_y, dz >= bb_offset_z. Else w, h or dz are ignored.
621 * @param image the image to combine and draw,
622 * @param pal the provided palette,
623 * @param x position X (world) of the sprite,
624 * @param y position Y (world) of the sprite,
625 * @param w bounding box extent towards positive X (world),
626 * @param h bounding box extent towards positive Y (world),
627 * @param dz bounding box extent towards positive Z (world),
628 * @param z position Z (world) of the sprite,
629 * @param transparent if true, switch the palette between the provided palette and the transparent palette,
630 * @param bb_offset_x bounding box extent towards negative X (world),
631 * @param bb_offset_y bounding box extent towards negative Y (world),
632 * @param bb_offset_z bounding box extent towards negative Z (world)
633 * @param sub Only draw a part of the sprite.
635 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
)
637 int32 left
, right
, top
, bottom
;
639 assert((image
& SPRITE_MASK
) < MAX_SPRITES
);
641 /* make the sprites transparent with the right palette */
643 SetBit(image
, PALETTE_MODIFIER_TRANSPARENT
);
644 pal
= PALETTE_TO_TRANSPARENT
;
647 if (_vd
.combine_sprites
== SPRITE_COMBINE_ACTIVE
) {
648 AddCombinedSprite(image
, pal
, x
, y
, z
, sub
);
652 _vd
.last_child
= NULL
;
654 Point pt
= RemapCoords(x
, y
, z
);
655 int tmp_left
, tmp_top
, tmp_x
= pt
.x
, tmp_y
= pt
.y
;
657 /* Compute screen extents of sprite */
658 if (image
== SPR_EMPTY_BOUNDING_BOX
) {
659 left
= tmp_left
= RemapCoords(x
+ w
, y
+ bb_offset_y
, z
+ bb_offset_z
).x
;
660 right
= RemapCoords(x
+ bb_offset_x
, y
+ h
, z
+ bb_offset_z
).x
+ 1;
661 top
= tmp_top
= RemapCoords(x
+ bb_offset_x
, y
+ bb_offset_y
, z
+ dz
).y
;
662 bottom
= RemapCoords(x
+ w
, y
+ h
, z
+ bb_offset_z
).y
+ 1;
664 const Sprite
*spr
= GetSprite(image
& SPRITE_MASK
, ST_NORMAL
);
665 left
= tmp_left
= (pt
.x
+= spr
->x_offs
);
666 right
= (pt
.x
+ spr
->width
);
667 top
= tmp_top
= (pt
.y
+= spr
->y_offs
);
668 bottom
= (pt
.y
+ spr
->height
);
671 if (_draw_bounding_boxes
&& (image
!= SPR_EMPTY_BOUNDING_BOX
)) {
672 /* Compute maximal extents of sprite and its bounding box */
673 left
= min(left
, RemapCoords(x
+ w
, y
+ bb_offset_y
, z
+ bb_offset_z
).x
);
674 right
= max(right
, RemapCoords(x
+ bb_offset_x
, y
+ h
, z
+ bb_offset_z
).x
+ 1);
675 top
= min(top
, RemapCoords(x
+ bb_offset_x
, y
+ bb_offset_y
, z
+ dz
).y
);
676 bottom
= max(bottom
, RemapCoords(x
+ w
, y
+ h
, z
+ bb_offset_z
).y
+ 1);
679 /* Do not add the sprite to the viewport, if it is outside */
680 if (left
>= _vd
.dpi
.left
+ _vd
.dpi
.width
||
681 right
<= _vd
.dpi
.left
||
682 top
>= _vd
.dpi
.top
+ _vd
.dpi
.height
||
683 bottom
<= _vd
.dpi
.top
) {
687 ParentSpriteToDraw
*ps
= _vd
.parent_sprites_to_draw
.Append();
697 ps
->xmin
= x
+ bb_offset_x
;
698 ps
->xmax
= x
+ max(bb_offset_x
, w
) - 1;
700 ps
->ymin
= y
+ bb_offset_y
;
701 ps
->ymax
= y
+ max(bb_offset_y
, h
) - 1;
703 ps
->zmin
= z
+ bb_offset_z
;
704 ps
->zmax
= z
+ max(bb_offset_z
, dz
) - 1;
706 ps
->comparison_done
= false;
707 ps
->first_child
= -1;
709 _vd
.last_child
= &ps
->first_child
;
711 if (_vd
.combine_sprites
== SPRITE_COMBINE_PENDING
) _vd
.combine_sprites
= SPRITE_COMBINE_ACTIVE
;
715 * Starts a block of sprites, which are "combined" into a single bounding box.
717 * Subsequent calls to #AddSortableSpriteToDraw will be drawn into the same bounding box.
718 * That is: The first sprite that is not clipped by the viewport defines the bounding box, and
719 * the following sprites will be child sprites to that one.
722 * - The drawing order is definite. No other sprites will be sorted between those of the block.
723 * - You have to provide a valid bounding box for all sprites,
724 * as you won't know which one is the first non-clipped one.
725 * Preferable you use the same bounding box for all.
726 * - You cannot use #AddChildSpriteScreen inside the block, as its result will be indefinite.
728 * The block is terminated by #EndSpriteCombine.
730 * You cannot nest "combined" blocks.
732 void StartSpriteCombine()
734 assert(_vd
.combine_sprites
== SPRITE_COMBINE_NONE
);
735 _vd
.combine_sprites
= SPRITE_COMBINE_PENDING
;
739 * Terminates a block of sprites started by #StartSpriteCombine.
740 * Take a look there for details.
742 void EndSpriteCombine()
744 assert(_vd
.combine_sprites
!= SPRITE_COMBINE_NONE
);
745 _vd
.combine_sprites
= SPRITE_COMBINE_NONE
;
749 * Check if the parameter "check" is inside the interval between
750 * begin and end, including both begin and end.
751 * @note Whether \c begin or \c end is the biggest does not matter.
752 * This method will account for that.
753 * @param begin The begin of the interval.
754 * @param end The end of the interval.
755 * @param check The value to check.
757 static bool IsInRangeInclusive(int begin
, int end
, int check
)
759 if (begin
> end
) Swap(begin
, end
);
760 return begin
<= check
&& check
<= end
;
764 * Checks whether a point is inside the selected a diagonal rectangle given by _thd.size and _thd.pos
765 * @param x The x coordinate of the point to be checked.
766 * @param y The y coordinate of the point to be checked.
767 * @return True if the point is inside the rectangle, else false.
769 bool IsInsideRotatedRectangle(int x
, int y
)
771 int dist_a
= (_thd
.size
.x
+ _thd
.size
.y
); // Rotated coordinate system for selected rectangle.
772 int dist_b
= (_thd
.size
.x
- _thd
.size
.y
); // We don't have to divide by 2. It's all relative!
773 int a
= ((x
- _thd
.pos
.x
) + (y
- _thd
.pos
.y
)); // Rotated coordinate system for the point under scrutiny.
774 int b
= ((x
- _thd
.pos
.x
) - (y
- _thd
.pos
.y
));
776 /* Check if a and b are between 0 and dist_a or dist_b respectively. */
777 return IsInRangeInclusive(dist_a
, 0, a
) && IsInRangeInclusive(dist_b
, 0, b
);
781 * Add a child sprite to a parent sprite.
783 * @param image the image to draw.
784 * @param pal the provided palette.
785 * @param x sprite x-offset (screen coordinates) relative to parent sprite.
786 * @param y sprite y-offset (screen coordinates) relative to parent sprite.
787 * @param transparent if true, switch the palette between the provided palette and the transparent palette,
788 * @param sub Only draw a part of the sprite.
790 void AddChildSpriteScreen(SpriteID image
, PaletteID pal
, int x
, int y
, bool transparent
, const SubSprite
*sub
, bool scale
)
792 assert((image
& SPRITE_MASK
) < MAX_SPRITES
);
794 /* If the ParentSprite was clipped by the viewport bounds, do not draw the ChildSprites either */
795 if (_vd
.last_child
== NULL
) return;
797 /* make the sprites transparent with the right palette */
799 SetBit(image
, PALETTE_MODIFIER_TRANSPARENT
);
800 pal
= PALETTE_TO_TRANSPARENT
;
803 *_vd
.last_child
= _vd
.child_screen_sprites_to_draw
.Length();
805 ChildScreenSpriteToDraw
*cs
= _vd
.child_screen_sprites_to_draw
.Append();
809 cs
->x
= scale
? x
* ZOOM_LVL_BASE
: x
;
810 cs
->y
= scale
? y
* ZOOM_LVL_BASE
: y
;
813 /* Append the sprite to the active ChildSprite list.
814 * If the active ParentSprite is a foundation, update last_foundation_child as well.
815 * Note: ChildSprites of foundations are NOT sequential in the vector, as selection sprites are added at last. */
816 if (_vd
.last_foundation_child
[0] == _vd
.last_child
) _vd
.last_foundation_child
[0] = &cs
->next
;
817 if (_vd
.last_foundation_child
[1] == _vd
.last_child
) _vd
.last_foundation_child
[1] = &cs
->next
;
818 _vd
.last_child
= &cs
->next
;
821 static void AddStringToDraw(int x
, int y
, StringID string
, uint64 params_1
, uint64 params_2
, Colours colour
, uint16 width
)
824 StringSpriteToDraw
*ss
= _vd
.string_sprites_to_draw
.Append();
828 ss
->params
[0] = params_1
;
829 ss
->params
[1] = params_2
;
836 * Draws sprites between ground sprite and everything above.
838 * The sprite is either drawn as TileSprite or as ChildSprite of the active foundation.
840 * @param image the image to draw.
841 * @param pal the provided palette.
842 * @param ti TileInfo Tile that is being drawn
843 * @param z_offset Z offset relative to the groundsprite. Only used for the sprite position, not for sprite sorting.
844 * @param foundation_part Foundation part the sprite belongs to.
846 static void DrawSelectionSprite(SpriteID image
, PaletteID pal
, const TileInfo
*ti
, int z_offset
, FoundationPart foundation_part
)
848 /* FIXME: This is not totally valid for some autorail highlights that extend over the edges of the tile. */
849 if (_vd
.foundation
[foundation_part
] == -1) {
850 /* draw on real ground */
851 AddTileSpriteToDraw(image
, pal
, ti
->x
, ti
->y
, ti
->z
+ z_offset
);
853 /* draw on top of foundation */
854 AddChildSpriteToFoundation(image
, pal
, NULL
, foundation_part
, 0, -z_offset
* ZOOM_LVL_BASE
);
859 * Draws a selection rectangle on a tile.
861 * @param ti TileInfo Tile that is being drawn
862 * @param pal Palette to apply.
864 static void DrawTileSelectionRect(const TileInfo
*ti
, PaletteID pal
)
866 if (!IsValidTile(ti
->tile
)) return;
869 if (IsHalftileSlope(ti
->tileh
)) {
870 Corner halftile_corner
= GetHalftileSlopeCorner(ti
->tileh
);
871 SpriteID sel2
= SPR_HALFTILE_SELECTION_FLAT
+ halftile_corner
;
872 DrawSelectionSprite(sel2
, pal
, ti
, 7 + TILE_HEIGHT
, FOUNDATION_PART_HALFTILE
);
874 Corner opposite_corner
= OppositeCorner(halftile_corner
);
875 if (IsSteepSlope(ti
->tileh
)) {
876 sel
= SPR_HALFTILE_SELECTION_DOWN
;
878 sel
= ((ti
->tileh
& SlopeWithOneCornerRaised(opposite_corner
)) != 0 ? SPR_HALFTILE_SELECTION_UP
: SPR_HALFTILE_SELECTION_FLAT
);
880 sel
+= opposite_corner
;
882 sel
= SPR_SELECT_TILE
+ SlopeToSpriteOffset(ti
->tileh
);
884 DrawSelectionSprite(sel
, pal
, ti
, 7, FOUNDATION_PART_NORMAL
);
887 static bool IsPartOfAutoLine(int px
, int py
)
889 px
-= _thd
.selstart
.x
;
890 py
-= _thd
.selstart
.y
;
892 if ((_thd
.drawstyle
& HT_DRAG_MASK
) != HT_LINE
) return false;
894 switch (_thd
.drawstyle
& HT_DIR_MASK
) {
895 case HT_DIR_X
: return py
== 0; // x direction
896 case HT_DIR_Y
: return px
== 0; // y direction
897 case HT_DIR_HU
: return px
== -py
|| px
== -py
- 16; // horizontal upper
898 case HT_DIR_HL
: return px
== -py
|| px
== -py
+ 16; // horizontal lower
899 case HT_DIR_VL
: return px
== py
|| px
== py
+ 16; // vertical left
900 case HT_DIR_VR
: return px
== py
|| px
== py
- 16; // vertical right
906 /* [direction][side] */
907 static const HighLightStyle _autorail_type
[6][2] = {
908 { HT_DIR_X
, HT_DIR_X
},
909 { HT_DIR_Y
, HT_DIR_Y
},
910 { HT_DIR_HU
, HT_DIR_HL
},
911 { HT_DIR_HL
, HT_DIR_HU
},
912 { HT_DIR_VL
, HT_DIR_VR
},
913 { HT_DIR_VR
, HT_DIR_VL
}
916 #include "table/autorail.h"
919 * Draws autorail highlights.
921 * @param *ti TileInfo Tile that is being drawn
922 * @param autorail_type Offset into _AutorailTilehSprite[][]
924 static void DrawAutorailSelection(const TileInfo
*ti
, uint autorail_type
)
930 FoundationPart foundation_part
= FOUNDATION_PART_NORMAL
;
931 Slope autorail_tileh
= RemoveHalftileSlope(ti
->tileh
);
932 if (IsHalftileSlope(ti
->tileh
)) {
933 static const uint _lower_rail
[4] = { 5U, 2U, 4U, 3U };
934 Corner halftile_corner
= GetHalftileSlopeCorner(ti
->tileh
);
935 if (autorail_type
!= _lower_rail
[halftile_corner
]) {
936 foundation_part
= FOUNDATION_PART_HALFTILE
;
937 /* Here we draw the highlights of the "three-corners-raised"-slope. That looks ok to me. */
938 autorail_tileh
= SlopeWithThreeCornersRaised(OppositeCorner(halftile_corner
));
942 offset
= _AutorailTilehSprite
[autorail_tileh
][autorail_type
];
944 image
= SPR_AUTORAIL_BASE
+ offset
;
947 image
= SPR_AUTORAIL_BASE
- offset
;
948 pal
= PALETTE_SEL_TILE_RED
;
951 DrawSelectionSprite(image
, _thd
.make_square_red
? PALETTE_SEL_TILE_RED
: pal
, ti
, 7, foundation_part
);
955 * Checks if the specified tile is selected and if so draws selection using correct selectionstyle.
956 * @param *ti TileInfo Tile that is being drawn
958 static void DrawTileSelection(const TileInfo
*ti
)
960 /* Draw a red error square? */
961 bool is_redsq
= _thd
.redsq
== ti
->tile
;
962 if (is_redsq
) DrawTileSelectionRect(ti
, PALETTE_TILE_RED_PULSATING
);
964 /* No tile selection active? */
965 if ((_thd
.drawstyle
& HT_DRAG_MASK
) == HT_NONE
) return;
967 if (_thd
.diagonal
) { // We're drawing a 45 degrees rotated (diagonal) rectangle
968 if (IsInsideRotatedRectangle((int)ti
->x
, (int)ti
->y
)) goto draw_inner
;
972 /* Inside the inner area? */
973 if (IsInsideBS(ti
->x
, _thd
.pos
.x
, _thd
.size
.x
) &&
974 IsInsideBS(ti
->y
, _thd
.pos
.y
, _thd
.size
.y
)) {
976 if (_thd
.drawstyle
& HT_RECT
) {
977 if (!is_redsq
) DrawTileSelectionRect(ti
, _thd
.make_square_red
? PALETTE_SEL_TILE_RED
: PAL_NONE
);
978 } else if (_thd
.drawstyle
& HT_POINT
) {
979 /* Figure out the Z coordinate for the single dot. */
981 FoundationPart foundation_part
= FOUNDATION_PART_NORMAL
;
982 if (ti
->tileh
& SLOPE_N
) {
984 if (RemoveHalftileSlope(ti
->tileh
) == SLOPE_STEEP_N
) z
+= TILE_HEIGHT
;
986 if (IsHalftileSlope(ti
->tileh
)) {
987 Corner halftile_corner
= GetHalftileSlopeCorner(ti
->tileh
);
988 if ((halftile_corner
== CORNER_W
) || (halftile_corner
== CORNER_E
)) z
+= TILE_HEIGHT
;
989 if (halftile_corner
!= CORNER_S
) {
990 foundation_part
= FOUNDATION_PART_HALFTILE
;
991 if (IsSteepSlope(ti
->tileh
)) z
-= TILE_HEIGHT
;
994 DrawSelectionSprite(_cur_dpi
->zoom
<= ZOOM_LVL_DETAIL
? SPR_DOT
: SPR_DOT_SMALL
, PAL_NONE
, ti
, z
, foundation_part
);
995 } else if (_thd
.drawstyle
& HT_RAIL
) {
996 /* autorail highlight piece under cursor */
997 HighLightStyle type
= _thd
.drawstyle
& HT_DIR_MASK
;
998 assert(type
< HT_DIR_END
);
999 DrawAutorailSelection(ti
, _autorail_type
[type
][0]);
1000 } else if (IsPartOfAutoLine(ti
->x
, ti
->y
)) {
1001 /* autorail highlighting long line */
1002 HighLightStyle dir
= _thd
.drawstyle
& HT_DIR_MASK
;
1005 if (dir
== HT_DIR_X
|| dir
== HT_DIR_Y
) {
1008 TileIndex start
= TileVirtXY(_thd
.selstart
.x
, _thd
.selstart
.y
);
1009 side
= Delta(Delta(TileX(start
), TileX(ti
->tile
)), Delta(TileY(start
), TileY(ti
->tile
)));
1012 DrawAutorailSelection(ti
, _autorail_type
[dir
][side
]);
1017 /* Check if it's inside the outer area? */
1018 if (!is_redsq
&& _thd
.outersize
.x
> 0 &&
1019 IsInsideBS(ti
->x
, _thd
.pos
.x
+ _thd
.offs
.x
, _thd
.size
.x
+ _thd
.outersize
.x
) &&
1020 IsInsideBS(ti
->y
, _thd
.pos
.y
+ _thd
.offs
.y
, _thd
.size
.y
+ _thd
.outersize
.y
)) {
1021 /* Draw a blue rect. */
1022 DrawTileSelectionRect(ti
, PALETTE_SEL_TILE_BLUE
);
1027 static void ViewportAddLandscape()
1029 int x
, y
, width
, height
;
1035 /* Transform into tile coordinates and round to closest full tile */
1036 x
= ((_vd
.dpi
.top
>> (1 + ZOOM_LVL_SHIFT
)) - (_vd
.dpi
.left
>> (2 + ZOOM_LVL_SHIFT
))) & ~TILE_UNIT_MASK
;
1037 y
= ((_vd
.dpi
.top
>> (1 + ZOOM_LVL_SHIFT
)) + (_vd
.dpi
.left
>> (2 + ZOOM_LVL_SHIFT
)) - TILE_SIZE
) & ~TILE_UNIT_MASK
;
1039 /* determine size of area */
1041 Point pt
= RemapCoords(x
, y
, 241);
1042 width
= (_vd
.dpi
.left
+ _vd
.dpi
.width
- pt
.x
+ 96 * ZOOM_LVL_BASE
- 1) >> (6 + ZOOM_LVL_SHIFT
);
1043 height
= (_vd
.dpi
.top
+ _vd
.dpi
.height
- pt
.y
) >> (5 + ZOOM_LVL_SHIFT
) << 1;
1052 int width_cur
= width
;
1057 DrawTileProc
*dtp
= DrawVoidTile
;
1064 ti
.tileh
= SLOPE_FLAT
;
1065 ti
.tile
= INVALID_TILE
;
1067 if (x_cur
< MapMaxX() * TILE_SIZE
&&
1068 y_cur
< MapMaxY() * TILE_SIZE
) {
1069 TileIndex tile
= TileVirtXY(x_cur
, y_cur
);
1071 if (!_settings_game
.construction
.freeform_edges
|| (TileX(tile
) != 0 && TileY(tile
) != 0)) {
1072 if (x_cur
== ((int)MapMaxX() - 1) * TILE_SIZE
|| y_cur
== ((int)MapMaxY() - 1) * TILE_SIZE
) {
1073 uint maxh
= max
<uint
>(TileHeight(tile
), 1);
1074 for (uint h
= 0; h
< maxh
; h
++) {
1075 AddTileSpriteToDraw(SPR_SHADOW_CELL
, PAL_NONE
, ti
.x
, ti
.y
, h
* TILE_HEIGHT
);
1080 ti
.tileh
= GetTilePixelSlope(tile
, &ti
.z
);
1081 dtp
= GetTileProcs(tile
)->draw_tile_proc
;
1085 _vd
.foundation_part
= FOUNDATION_PART_NONE
;
1086 _vd
.foundation
[0] = -1;
1087 _vd
.foundation
[1] = -1;
1088 _vd
.last_foundation_child
[0] = NULL
;
1089 _vd
.last_foundation_child
[1] = NULL
;
1093 if ((x_cur
== (int)MapMaxX() * TILE_SIZE
&& IsInsideMM(y_cur
, 0, MapMaxY() * TILE_SIZE
+ 1)) ||
1094 (y_cur
== (int)MapMaxY() * TILE_SIZE
&& IsInsideMM(x_cur
, 0, MapMaxX() * TILE_SIZE
+ 1))) {
1095 TileIndex tile
= TileVirtXY(x_cur
, y_cur
);
1097 ti
.tileh
= GetTilePixelSlope(tile
, &ti
.z
);
1098 dtp
= GetTileProcs(tile
)->draw_tile_proc
;
1100 if (ti
.tile
!= INVALID_TILE
) DrawTileSelection(&ti
);
1104 } while (--width_cur
);
1106 if ((direction
^= 1) != 0) {
1115 * Add a string to draw in the viewport
1116 * @param dpi current viewport area
1117 * @param small_from Zoomlevel from when the small font should be used
1118 * @param sign sign position and dimension
1119 * @param string_normal String for normal and 2x zoom level
1120 * @param string_small String for 4x and 8x zoom level
1121 * @param string_small_shadow Shadow string for 4x and 8x zoom level; or #STR_NULL if no shadow
1122 * @param colour colour of the sign background; or INVALID_COLOUR if transparent
1124 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
)
1126 bool small
= dpi
->zoom
>= small_from
;
1128 int left
= dpi
->left
;
1130 int right
= left
+ dpi
->width
;
1131 int bottom
= top
+ dpi
->height
;
1133 int sign_height
= ScaleByZoom(VPSM_TOP
+ FONT_HEIGHT_NORMAL
+ VPSM_BOTTOM
, dpi
->zoom
);
1134 int sign_half_width
= ScaleByZoom((small
? sign
->width_small
: sign
->width_normal
) / 2, dpi
->zoom
);
1136 if (bottom
< sign
->top
||
1137 top
> sign
->top
+ sign_height
||
1138 right
< sign
->center
- sign_half_width
||
1139 left
> sign
->center
+ sign_half_width
) {
1144 AddStringToDraw(sign
->center
- sign_half_width
, sign
->top
, string_normal
, params_1
, params_2
, colour
, sign
->width_normal
);
1146 int shadow_offset
= 0;
1147 if (string_small_shadow
!= STR_NULL
) {
1149 AddStringToDraw(sign
->center
- sign_half_width
+ shadow_offset
, sign
->top
, string_small_shadow
, params_1
, params_2
, INVALID_COLOUR
, sign
->width_small
);
1151 AddStringToDraw(sign
->center
- sign_half_width
, sign
->top
- shadow_offset
, string_small
, params_1
, params_2
,
1152 colour
, sign
->width_small
| 0x8000);
1156 static void ViewportAddTownNames(DrawPixelInfo
*dpi
)
1158 if (!HasBit(_display_opt
, DO_SHOW_TOWN_NAMES
) || _game_mode
== GM_MENU
) return;
1162 ViewportAddString(dpi
, ZOOM_LVL_OUT_16X
, &t
->cache
.sign
,
1163 _settings_client
.gui
.population_in_label
? STR_VIEWPORT_TOWN_POP
: STR_VIEWPORT_TOWN
,
1164 STR_VIEWPORT_TOWN_TINY_WHITE
, STR_VIEWPORT_TOWN_TINY_BLACK
,
1165 t
->index
, t
->cache
.population
);
1170 static void ViewportAddStationNames(DrawPixelInfo
*dpi
)
1172 if (!(HasBit(_display_opt
, DO_SHOW_STATION_NAMES
) || HasBit(_display_opt
, DO_SHOW_WAYPOINT_NAMES
)) || _game_mode
== GM_MENU
) return;
1174 const BaseStation
*st
;
1175 FOR_ALL_BASE_STATIONS(st
) {
1176 /* Check whether the base station is a station or a waypoint */
1177 bool is_station
= Station::IsExpected(st
);
1179 /* Don't draw if the display options are disabled */
1180 if (!HasBit(_display_opt
, is_station
? DO_SHOW_STATION_NAMES
: DO_SHOW_WAYPOINT_NAMES
)) continue;
1182 /* Don't draw if station is owned by another company and competitor station names are hidden. Stations owned by none are never ignored. */
1183 if (!HasBit(_display_opt
, DO_SHOW_COMPETITOR_SIGNS
) && _local_company
!= st
->owner
&& st
->owner
!= OWNER_NONE
) continue;
1185 ViewportAddString(dpi
, ZOOM_LVL_OUT_16X
, &st
->sign
,
1186 is_station
? STR_VIEWPORT_STATION
: STR_VIEWPORT_WAYPOINT
,
1187 (is_station
? STR_VIEWPORT_STATION
: STR_VIEWPORT_WAYPOINT
) + 1, STR_NULL
,
1188 st
->index
, st
->facilities
, (st
->owner
== OWNER_NONE
|| !st
->IsInUse()) ? COLOUR_GREY
: _company_colours
[st
->owner
]);
1193 static void ViewportAddSigns(DrawPixelInfo
*dpi
)
1195 /* Signs are turned off or are invisible */
1196 if (!HasBit(_display_opt
, DO_SHOW_SIGNS
) || IsInvisibilitySet(TO_SIGNS
)) return;
1200 /* Don't draw if sign is owned by another company and competitor signs should be hidden.
1201 * Note: It is intentional that also signs owned by OWNER_NONE are hidden. Bankrupt
1202 * companies can leave OWNER_NONE signs after them. */
1203 if (!HasBit(_display_opt
, DO_SHOW_COMPETITOR_SIGNS
) && _local_company
!= si
->owner
&& si
->owner
!= OWNER_DEITY
) continue;
1205 ViewportAddString(dpi
, ZOOM_LVL_OUT_16X
, &si
->sign
,
1207 (IsTransparencySet(TO_SIGNS
) || si
->owner
== OWNER_DEITY
) ? STR_VIEWPORT_SIGN_SMALL_WHITE
: STR_VIEWPORT_SIGN_SMALL_BLACK
, STR_NULL
,
1208 si
->index
, 0, (si
->owner
== OWNER_NONE
) ? COLOUR_GREY
: (si
->owner
== OWNER_DEITY
? INVALID_COLOUR
: _company_colours
[si
->owner
]));
1213 * Update the position of the viewport sign.
1214 * @param center the (preferred) center of the viewport sign
1215 * @param top the new top of the sign
1216 * @param str the string to show in the sign
1218 void ViewportSign::UpdatePosition(int center
, int top
, StringID str
)
1220 if (this->width_normal
!= 0) this->MarkDirty();
1224 char buffer
[DRAW_STRING_BUFFER
];
1226 GetString(buffer
, str
, lastof(buffer
));
1227 this->width_normal
= VPSM_LEFT
+ Align(GetStringBoundingBox(buffer
).width
, 2) + VPSM_RIGHT
;
1228 this->center
= center
;
1230 /* zoomed out version */
1231 this->width_small
= VPSM_LEFT
+ Align(GetStringBoundingBox(buffer
, FS_SMALL
).width
, 2) + VPSM_RIGHT
;
1237 * Mark the sign dirty in all viewports.
1238 * @param maxzoom Maximum %ZoomLevel at which the text is visible.
1242 void ViewportSign::MarkDirty(ZoomLevel maxzoom
) const
1244 Rect zoomlevels
[ZOOM_LVL_COUNT
];
1246 for (ZoomLevel zoom
= ZOOM_LVL_BEGIN
; zoom
!= ZOOM_LVL_END
; zoom
++) {
1247 /* FIXME: This doesn't switch to width_small when appropriate. */
1248 zoomlevels
[zoom
].left
= this->center
- ScaleByZoom(this->width_normal
/ 2 + 1, zoom
);
1249 zoomlevels
[zoom
].top
= this->top
- ScaleByZoom(1, zoom
);
1250 zoomlevels
[zoom
].right
= this->center
+ ScaleByZoom(this->width_normal
/ 2 + 1, zoom
);
1251 zoomlevels
[zoom
].bottom
= this->top
+ ScaleByZoom(VPSM_TOP
+ FONT_HEIGHT_NORMAL
+ VPSM_BOTTOM
+ 1, zoom
);
1255 FOR_ALL_WINDOWS_FROM_BACK(w
) {
1256 ViewPort
*vp
= w
->viewport
;
1257 if (vp
!= NULL
&& vp
->zoom
<= maxzoom
) {
1258 assert(vp
->width
!= 0);
1259 Rect
&zl
= zoomlevels
[vp
->zoom
];
1260 MarkViewportDirty(vp
, zl
.left
, zl
.top
, zl
.right
, zl
.bottom
);
1265 static void ViewportDrawTileSprites(const TileSpriteToDrawVector
*tstdv
)
1267 const TileSpriteToDraw
*tsend
= tstdv
->End();
1268 for (const TileSpriteToDraw
*ts
= tstdv
->Begin(); ts
!= tsend
; ++ts
) {
1269 DrawSpriteViewport(ts
->image
, ts
->pal
, ts
->x
, ts
->y
, ts
->sub
);
1273 /** This fallback sprite checker always exists. */
1274 static bool ViewportSortParentSpritesChecker()
1279 /** Sort parent sprites pointer array */
1280 static void ViewportSortParentSprites(ParentSpriteToSortVector
*psdv
)
1282 ParentSpriteToDraw
**psdvend
= psdv
->End();
1283 ParentSpriteToDraw
**psd
= psdv
->Begin();
1284 while (psd
!= psdvend
) {
1285 ParentSpriteToDraw
*ps
= *psd
;
1287 if (ps
->comparison_done
) {
1292 ps
->comparison_done
= true;
1294 for (ParentSpriteToDraw
**psd2
= psd
+ 1; psd2
!= psdvend
; psd2
++) {
1295 ParentSpriteToDraw
*ps2
= *psd2
;
1297 if (ps2
->comparison_done
) continue;
1299 /* Decide which comparator to use, based on whether the bounding
1302 if (ps
->xmax
>= ps2
->xmin
&& ps
->xmin
<= ps2
->xmax
&& // overlap in X?
1303 ps
->ymax
>= ps2
->ymin
&& ps
->ymin
<= ps2
->ymax
&& // overlap in Y?
1304 ps
->zmax
>= ps2
->zmin
&& ps
->zmin
<= ps2
->zmax
) { // overlap in Z?
1305 /* Use X+Y+Z as the sorting order, so sprites closer to the bottom of
1306 * the screen and with higher Z elevation, are drawn in front.
1307 * Here X,Y,Z are the coordinates of the "center of mass" of the sprite,
1308 * i.e. X=(left+right)/2, etc.
1309 * However, since we only care about order, don't actually divide / 2
1311 if (ps
->xmin
+ ps
->xmax
+ ps
->ymin
+ ps
->ymax
+ ps
->zmin
+ ps
->zmax
<=
1312 ps2
->xmin
+ ps2
->xmax
+ ps2
->ymin
+ ps2
->ymax
+ ps2
->zmin
+ ps2
->zmax
) {
1316 /* We only change the order, if it is definite.
1317 * I.e. every single order of X, Y, Z says ps2 is behind ps or they overlap.
1318 * That is: If one partial order says ps behind ps2, do not change the order.
1320 if (ps
->xmax
< ps2
->xmin
||
1321 ps
->ymax
< ps2
->ymin
||
1322 ps
->zmax
< ps2
->zmin
) {
1327 /* Move ps2 in front of ps */
1328 ParentSpriteToDraw
*temp
= ps2
;
1329 for (ParentSpriteToDraw
**psd3
= psd2
; psd3
> psd
; psd3
--) {
1330 *psd3
= *(psd3
- 1);
1337 static void ViewportDrawParentSprites(const ParentSpriteToSortVector
*psd
, const ChildScreenSpriteToDrawVector
*csstdv
)
1339 const ParentSpriteToDraw
* const *psd_end
= psd
->End();
1340 for (const ParentSpriteToDraw
* const *it
= psd
->Begin(); it
!= psd_end
; it
++) {
1341 const ParentSpriteToDraw
*ps
= *it
;
1342 if (ps
->image
!= SPR_EMPTY_BOUNDING_BOX
) DrawSpriteViewport(ps
->image
, ps
->pal
, ps
->x
, ps
->y
, ps
->sub
);
1344 int child_idx
= ps
->first_child
;
1345 while (child_idx
>= 0) {
1346 const ChildScreenSpriteToDraw
*cs
= csstdv
->Get(child_idx
);
1347 child_idx
= cs
->next
;
1348 DrawSpriteViewport(cs
->image
, cs
->pal
, ps
->left
+ cs
->x
, ps
->top
+ cs
->y
, cs
->sub
);
1354 * Draws the bounding boxes of all ParentSprites
1355 * @param psd Array of ParentSprites
1357 static void ViewportDrawBoundingBoxes(const ParentSpriteToSortVector
*psd
)
1359 const ParentSpriteToDraw
* const *psd_end
= psd
->End();
1360 for (const ParentSpriteToDraw
* const *it
= psd
->Begin(); it
!= psd_end
; it
++) {
1361 const ParentSpriteToDraw
*ps
= *it
;
1362 Point pt1
= RemapCoords(ps
->xmax
+ 1, ps
->ymax
+ 1, ps
->zmax
+ 1); // top front corner
1363 Point pt2
= RemapCoords(ps
->xmin
, ps
->ymax
+ 1, ps
->zmax
+ 1); // top left corner
1364 Point pt3
= RemapCoords(ps
->xmax
+ 1, ps
->ymin
, ps
->zmax
+ 1); // top right corner
1365 Point pt4
= RemapCoords(ps
->xmax
+ 1, ps
->ymax
+ 1, ps
->zmin
); // bottom front corner
1367 DrawBox( pt1
.x
, pt1
.y
,
1368 pt2
.x
- pt1
.x
, pt2
.y
- pt1
.y
,
1369 pt3
.x
- pt1
.x
, pt3
.y
- pt1
.y
,
1370 pt4
.x
- pt1
.x
, pt4
.y
- pt1
.y
);
1375 * Draw/colour the blocks that have been redrawn.
1377 static void ViewportDrawDirtyBlocks()
1379 Blitter
*blitter
= BlitterFactory::GetCurrentBlitter();
1380 const DrawPixelInfo
*dpi
= _cur_dpi
;
1382 int right
= UnScaleByZoom(dpi
->width
, dpi
->zoom
);
1383 int bottom
= UnScaleByZoom(dpi
->height
, dpi
->zoom
);
1385 int colour
= _string_colourmap
[_dirty_block_colour
& 0xF];
1389 byte bo
= UnScaleByZoom(dpi
->left
+ dpi
->top
, dpi
->zoom
) & 1;
1391 for (int i
= (bo
^= 1); i
< right
; i
+= 2) blitter
->SetPixel(dst
, i
, 0, (uint8
)colour
);
1392 dst
= blitter
->MoveTo(dst
, 0, 1);
1393 } while (--bottom
> 0);
1396 static void ViewportDrawStrings(ZoomLevel zoom
, const StringSpriteToDrawVector
*sstdv
)
1398 const StringSpriteToDraw
*ssend
= sstdv
->End();
1399 for (const StringSpriteToDraw
*ss
= sstdv
->Begin(); ss
!= ssend
; ++ss
) {
1400 TextColour colour
= TC_BLACK
;
1401 bool small
= HasBit(ss
->width
, 15);
1402 int w
= GB(ss
->width
, 0, 15);
1403 int x
= UnScaleByZoom(ss
->x
, zoom
);
1404 int y
= UnScaleByZoom(ss
->y
, zoom
);
1405 int h
= VPSM_TOP
+ (small
? FONT_HEIGHT_SMALL
: FONT_HEIGHT_NORMAL
) + VPSM_BOTTOM
;
1407 SetDParam(0, ss
->params
[0]);
1408 SetDParam(1, ss
->params
[1]);
1410 if (ss
->colour
!= INVALID_COLOUR
) {
1411 /* Do not draw signs nor station names if they are set invisible */
1412 if (IsInvisibilitySet(TO_SIGNS
) && ss
->string
!= STR_WHITE_SIGN
) continue;
1414 if (IsTransparencySet(TO_SIGNS
) && ss
->string
!= STR_WHITE_SIGN
) {
1415 /* Don't draw the rectangle.
1416 * Real colours need the TC_IS_PALETTE_COLOUR flag.
1417 * Otherwise colours from _string_colourmap are assumed. */
1418 colour
= (TextColour
)_colour_gradient
[ss
->colour
][6] | TC_IS_PALETTE_COLOUR
;
1420 /* Draw the rectangle if 'transparent station signs' is off,
1421 * or if we are drawing a general text sign (STR_WHITE_SIGN). */
1423 x
, y
, x
+ w
, y
+ h
, ss
->colour
,
1424 IsTransparencySet(TO_SIGNS
) ? FR_TRANSPARENT
: FR_NONE
1429 DrawString(x
+ VPSM_LEFT
, x
+ w
- 1 - VPSM_RIGHT
, y
+ VPSM_TOP
, ss
->string
, colour
, SA_HOR_CENTER
);
1433 void ViewportDoDraw(const ViewPort
*vp
, int left
, int top
, int right
, int bottom
)
1435 DrawPixelInfo
*old_dpi
= _cur_dpi
;
1436 _cur_dpi
= &_vd
.dpi
;
1438 _vd
.dpi
.zoom
= vp
->zoom
;
1439 int mask
= ScaleByZoom(-1, vp
->zoom
);
1441 _vd
.combine_sprites
= SPRITE_COMBINE_NONE
;
1443 _vd
.dpi
.width
= (right
- left
) & mask
;
1444 _vd
.dpi
.height
= (bottom
- top
) & mask
;
1445 _vd
.dpi
.left
= left
& mask
;
1446 _vd
.dpi
.top
= top
& mask
;
1447 _vd
.dpi
.pitch
= old_dpi
->pitch
;
1448 _vd
.last_child
= NULL
;
1450 int x
= UnScaleByZoom(_vd
.dpi
.left
- (vp
->virtual_left
& mask
), vp
->zoom
) + vp
->left
;
1451 int y
= UnScaleByZoom(_vd
.dpi
.top
- (vp
->virtual_top
& mask
), vp
->zoom
) + vp
->top
;
1453 _vd
.dpi
.dst_ptr
= BlitterFactory::GetCurrentBlitter()->MoveTo(old_dpi
->dst_ptr
, x
- old_dpi
->left
, y
- old_dpi
->top
);
1455 ViewportAddLandscape();
1456 ViewportAddVehicles(&_vd
.dpi
);
1458 ViewportAddTownNames(&_vd
.dpi
);
1459 ViewportAddStationNames(&_vd
.dpi
);
1460 ViewportAddSigns(&_vd
.dpi
);
1462 DrawTextEffects(&_vd
.dpi
);
1464 if (_vd
.tile_sprites_to_draw
.Length() != 0) ViewportDrawTileSprites(&_vd
.tile_sprites_to_draw
);
1466 ParentSpriteToDraw
*psd_end
= _vd
.parent_sprites_to_draw
.End();
1467 for (ParentSpriteToDraw
*it
= _vd
.parent_sprites_to_draw
.Begin(); it
!= psd_end
; it
++) {
1468 *_vd
.parent_sprites_to_sort
.Append() = it
;
1471 _vp_sprite_sorter(&_vd
.parent_sprites_to_sort
);
1472 ViewportDrawParentSprites(&_vd
.parent_sprites_to_sort
, &_vd
.child_screen_sprites_to_draw
);
1474 if (_draw_bounding_boxes
) ViewportDrawBoundingBoxes(&_vd
.parent_sprites_to_sort
);
1475 if (_draw_dirty_blocks
) ViewportDrawDirtyBlocks();
1477 DrawPixelInfo dp
= _vd
.dpi
;
1478 ZoomLevel zoom
= _vd
.dpi
.zoom
;
1479 dp
.zoom
= ZOOM_LVL_NORMAL
;
1480 dp
.width
= UnScaleByZoom(dp
.width
, zoom
);
1481 dp
.height
= UnScaleByZoom(dp
.height
, zoom
);
1484 /* translate to window coordinates */
1488 if (vp
->overlay
!= NULL
) vp
->overlay
->Draw(&dp
);
1490 /* translate back to world coordinates */
1491 dp
.left
= UnScaleByZoom(_vd
.dpi
.left
, zoom
);
1492 dp
.top
= UnScaleByZoom(_vd
.dpi
.top
, zoom
);
1494 if (_vd
.string_sprites_to_draw
.Length() != 0) ViewportDrawStrings(zoom
, &_vd
.string_sprites_to_draw
);
1498 _vd
.string_sprites_to_draw
.Clear();
1499 _vd
.tile_sprites_to_draw
.Clear();
1500 _vd
.parent_sprites_to_draw
.Clear();
1501 _vd
.parent_sprites_to_sort
.Clear();
1502 _vd
.child_screen_sprites_to_draw
.Clear();
1506 * Make sure we don't draw a too big area at a time.
1507 * If we do, the sprite memory will overflow.
1509 static void ViewportDrawChk(const ViewPort
*vp
, int left
, int top
, int right
, int bottom
)
1511 if (ScaleByZoom(bottom
- top
, vp
->zoom
) * ScaleByZoom(right
- left
, vp
->zoom
) > 180000 * ZOOM_LVL_BASE
* ZOOM_LVL_BASE
) {
1512 if ((bottom
- top
) > (right
- left
)) {
1513 int t
= (top
+ bottom
) >> 1;
1514 ViewportDrawChk(vp
, left
, top
, right
, t
);
1515 ViewportDrawChk(vp
, left
, t
, right
, bottom
);
1517 int t
= (left
+ right
) >> 1;
1518 ViewportDrawChk(vp
, left
, top
, t
, bottom
);
1519 ViewportDrawChk(vp
, t
, top
, right
, bottom
);
1523 ScaleByZoom(left
- vp
->left
, vp
->zoom
) + vp
->virtual_left
,
1524 ScaleByZoom(top
- vp
->top
, vp
->zoom
) + vp
->virtual_top
,
1525 ScaleByZoom(right
- vp
->left
, vp
->zoom
) + vp
->virtual_left
,
1526 ScaleByZoom(bottom
- vp
->top
, vp
->zoom
) + vp
->virtual_top
1531 static inline void ViewportDraw(const ViewPort
*vp
, int left
, int top
, int right
, int bottom
)
1533 if (right
<= vp
->left
|| bottom
<= vp
->top
) return;
1535 if (left
>= vp
->left
+ vp
->width
) return;
1537 if (left
< vp
->left
) left
= vp
->left
;
1538 if (right
> vp
->left
+ vp
->width
) right
= vp
->left
+ vp
->width
;
1540 if (top
>= vp
->top
+ vp
->height
) return;
1542 if (top
< vp
->top
) top
= vp
->top
;
1543 if (bottom
> vp
->top
+ vp
->height
) bottom
= vp
->top
+ vp
->height
;
1545 ViewportDrawChk(vp
, left
, top
, right
, bottom
);
1549 * Draw the viewport of this window.
1551 void Window::DrawViewport() const
1553 DrawPixelInfo
*dpi
= _cur_dpi
;
1555 dpi
->left
+= this->left
;
1556 dpi
->top
+= this->top
;
1558 ViewportDraw(this->viewport
, dpi
->left
, dpi
->top
, dpi
->left
+ dpi
->width
, dpi
->top
+ dpi
->height
);
1560 dpi
->left
-= this->left
;
1561 dpi
->top
-= this->top
;
1564 static inline void ClampViewportToMap(const ViewPort
*vp
, int &x
, int &y
)
1566 /* Centre of the viewport is hot spot */
1567 x
+= vp
->virtual_width
/ 2;
1568 y
+= vp
->virtual_height
/ 2;
1570 /* Convert viewport coordinates to map coordinates
1571 * Calculation is scaled by 4 to avoid rounding errors */
1572 int vx
= -x
+ y
* 2;
1575 /* clamp to size of map */
1576 vx
= Clamp(vx
, 0, MapMaxX() * TILE_SIZE
* 4 * ZOOM_LVL_BASE
);
1577 vy
= Clamp(vy
, 0, MapMaxY() * TILE_SIZE
* 4 * ZOOM_LVL_BASE
);
1579 /* Convert map coordinates to viewport coordinates */
1583 /* Remove centering */
1584 x
-= vp
->virtual_width
/ 2;
1585 y
-= vp
->virtual_height
/ 2;
1589 * Update the viewport position being displayed.
1590 * @param w %Window owning the viewport.
1592 void UpdateViewportPosition(Window
*w
)
1594 const ViewPort
*vp
= w
->viewport
;
1596 if (w
->viewport
->follow_vehicle
!= INVALID_VEHICLE
) {
1597 const Vehicle
*veh
= Vehicle::Get(w
->viewport
->follow_vehicle
);
1598 Point pt
= MapXYZToViewport(vp
, veh
->x_pos
, veh
->y_pos
, veh
->z_pos
);
1600 w
->viewport
->scrollpos_x
= pt
.x
;
1601 w
->viewport
->scrollpos_y
= pt
.y
;
1602 SetViewportPosition(w
, pt
.x
, pt
.y
);
1604 /* Ensure the destination location is within the map */
1605 ClampViewportToMap(vp
, w
->viewport
->dest_scrollpos_x
, w
->viewport
->dest_scrollpos_y
);
1607 int delta_x
= w
->viewport
->dest_scrollpos_x
- w
->viewport
->scrollpos_x
;
1608 int delta_y
= w
->viewport
->dest_scrollpos_y
- w
->viewport
->scrollpos_y
;
1610 bool update_overlay
= false;
1611 if (delta_x
!= 0 || delta_y
!= 0) {
1612 if (_settings_client
.gui
.smooth_scroll
) {
1613 int max_scroll
= ScaleByMapPerimeter(512 * ZOOM_LVL_BASE
);
1614 /* Not at our desired position yet... */
1615 w
->viewport
->scrollpos_x
+= Clamp(delta_x
/ 4, -max_scroll
, max_scroll
);
1616 w
->viewport
->scrollpos_y
+= Clamp(delta_y
/ 4, -max_scroll
, max_scroll
);
1618 w
->viewport
->scrollpos_x
= w
->viewport
->dest_scrollpos_x
;
1619 w
->viewport
->scrollpos_y
= w
->viewport
->dest_scrollpos_y
;
1621 update_overlay
= (w
->viewport
->scrollpos_x
== w
->viewport
->dest_scrollpos_x
&&
1622 w
->viewport
->scrollpos_y
== w
->viewport
->dest_scrollpos_y
);
1625 ClampViewportToMap(vp
, w
->viewport
->scrollpos_x
, w
->viewport
->scrollpos_y
);
1627 SetViewportPosition(w
, w
->viewport
->scrollpos_x
, w
->viewport
->scrollpos_y
);
1628 if (update_overlay
) RebuildViewportOverlay(w
);
1633 * Marks a viewport as dirty for repaint if it displays (a part of) the area the needs to be repainted.
1634 * @param vp The viewport to mark as dirty
1635 * @param left Left edge of area to repaint
1636 * @param top Top edge of area to repaint
1637 * @param right Right edge of area to repaint
1638 * @param bottom Bottom edge of area to repaint
1641 static void MarkViewportDirty(const ViewPort
*vp
, int left
, int top
, int right
, int bottom
)
1643 right
-= vp
->virtual_left
;
1644 if (right
<= 0) return;
1646 bottom
-= vp
->virtual_top
;
1647 if (bottom
<= 0) return;
1649 left
= max(0, left
- vp
->virtual_left
);
1651 if (left
>= vp
->virtual_width
) return;
1653 top
= max(0, top
- vp
->virtual_top
);
1655 if (top
>= vp
->virtual_height
) return;
1658 UnScaleByZoomLower(left
, vp
->zoom
) + vp
->left
,
1659 UnScaleByZoomLower(top
, vp
->zoom
) + vp
->top
,
1660 UnScaleByZoom(right
, vp
->zoom
) + vp
->left
+ 1,
1661 UnScaleByZoom(bottom
, vp
->zoom
) + vp
->top
+ 1
1666 * Mark all viewports that display an area as dirty (in need of repaint).
1667 * @param left Left edge of area to repaint
1668 * @param top Top edge of area to repaint
1669 * @param right Right edge of area to repaint
1670 * @param bottom Bottom edge of area to repaint
1673 void MarkAllViewportsDirty(int left
, int top
, int right
, int bottom
)
1676 FOR_ALL_WINDOWS_FROM_BACK(w
) {
1677 ViewPort
*vp
= w
->viewport
;
1679 assert(vp
->width
!= 0);
1680 MarkViewportDirty(vp
, left
, top
, right
, bottom
);
1685 void ConstrainAllViewportsZoom()
1688 FOR_ALL_WINDOWS_FROM_FRONT(w
) {
1689 if (w
->viewport
== NULL
) continue;
1691 ZoomLevel zoom
= static_cast<ZoomLevel
>(Clamp(w
->viewport
->zoom
, _settings_client
.gui
.zoom_min
, _settings_client
.gui
.zoom_max
));
1692 if (zoom
!= w
->viewport
->zoom
) {
1693 while (w
->viewport
->zoom
< zoom
) DoZoomInOutWindow(ZOOM_OUT
, w
);
1694 while (w
->viewport
->zoom
> zoom
) DoZoomInOutWindow(ZOOM_IN
, w
);
1700 * Mark a tile given by its index dirty for repaint.
1701 * @param tile The tile to mark dirty.
1704 void MarkTileDirtyByTile(TileIndex tile
)
1706 Point pt
= RemapCoords(TileX(tile
) * TILE_SIZE
, TileY(tile
) * TILE_SIZE
, GetTilePixelZ(tile
));
1707 MarkAllViewportsDirty(
1708 pt
.x
- 31 * ZOOM_LVL_BASE
,
1709 pt
.y
- 122 * ZOOM_LVL_BASE
,
1710 pt
.x
- 31 * ZOOM_LVL_BASE
+ 67 * ZOOM_LVL_BASE
,
1711 pt
.y
- 122 * ZOOM_LVL_BASE
+ 154 * ZOOM_LVL_BASE
1716 * Marks the selected tiles as dirty.
1718 * This function marks the selected tiles as dirty for repaint
1722 static void SetSelectionTilesDirty()
1724 int x_size
= _thd
.size
.x
;
1725 int y_size
= _thd
.size
.y
;
1727 if (!_thd
.diagonal
) { // Selecting in a straight rectangle (or a single square)
1728 int x_start
= _thd
.pos
.x
;
1729 int y_start
= _thd
.pos
.y
;
1731 if (_thd
.outersize
.x
!= 0) {
1732 x_size
+= _thd
.outersize
.x
;
1733 x_start
+= _thd
.offs
.x
;
1734 y_size
+= _thd
.outersize
.y
;
1735 y_start
+= _thd
.offs
.y
;
1738 x_size
-= TILE_SIZE
;
1739 y_size
-= TILE_SIZE
;
1741 assert(x_size
>= 0);
1742 assert(y_size
>= 0);
1744 int x_end
= Clamp(x_start
+ x_size
, 0, MapSizeX() * TILE_SIZE
- TILE_SIZE
);
1745 int y_end
= Clamp(y_start
+ y_size
, 0, MapSizeY() * TILE_SIZE
- TILE_SIZE
);
1747 x_start
= Clamp(x_start
, 0, MapSizeX() * TILE_SIZE
- TILE_SIZE
);
1748 y_start
= Clamp(y_start
, 0, MapSizeY() * TILE_SIZE
- TILE_SIZE
);
1750 /* make sure everything is multiple of TILE_SIZE */
1751 assert((x_end
| y_end
| x_start
| y_start
) % TILE_SIZE
== 0);
1754 * Suppose we have to mark dirty rectangle of 3x4 tiles:
1761 * This algorithm marks dirty columns of tiles, so it is done in 3+4-1 steps:
1771 int top_x
= x_end
; // coordinates of top dirty tile
1772 int top_y
= y_start
;
1773 int bot_x
= top_x
; // coordinates of bottom dirty tile
1777 /* topmost dirty point */
1778 TileIndex top_tile
= TileVirtXY(top_x
, top_y
);
1779 Point top
= RemapCoords(top_x
, top_y
, GetTileMaxPixelZ(top_tile
));
1781 /* bottommost point */
1782 TileIndex bottom_tile
= TileVirtXY(bot_x
, bot_y
);
1783 Point bot
= RemapCoords(bot_x
+ TILE_SIZE
, bot_y
+ TILE_SIZE
, GetTilePixelZ(bottom_tile
)); // bottommost point
1785 /* the 'x' coordinate of 'top' and 'bot' is the same (and always in the same distance from tile middle),
1786 * tile height/slope affects only the 'y' on-screen coordinate! */
1788 int l
= top
.x
- (TILE_PIXELS
- 2) * ZOOM_LVL_BASE
; // 'x' coordinate of left side of dirty rectangle
1789 int t
= top
.y
; // 'y' coordinate of top side -//-
1790 int r
= top
.x
+ (TILE_PIXELS
- 2) * ZOOM_LVL_BASE
; // right side of dirty rectangle
1791 int b
= bot
.y
; // bottom -//-
1793 static const int OVERLAY_WIDTH
= 4 * ZOOM_LVL_BASE
; // part of selection sprites is drawn outside the selected area
1795 /* For halftile foundations on SLOPE_STEEP_S the sprite extents some more towards the top */
1796 MarkAllViewportsDirty(l
- OVERLAY_WIDTH
, t
- OVERLAY_WIDTH
- TILE_HEIGHT
* ZOOM_LVL_BASE
, r
+ OVERLAY_WIDTH
, b
+ OVERLAY_WIDTH
* ZOOM_LVL_BASE
);
1798 /* haven't we reached the topmost tile yet? */
1799 if (top_x
!= x_start
) {
1805 /* the way the bottom tile changes is different when we reach the bottommost tile */
1806 if (bot_y
!= y_end
) {
1811 } while (bot_x
>= top_x
);
1812 } else { // Selecting in a 45 degrees rotated (diagonal) rectangle.
1813 /* a_size, b_size describe a rectangle with rotated coordinates */
1814 int a_size
= x_size
+ y_size
, b_size
= x_size
- y_size
;
1816 int interval_a
= a_size
< 0 ? -(int)TILE_SIZE
: (int)TILE_SIZE
;
1817 int interval_b
= b_size
< 0 ? -(int)TILE_SIZE
: (int)TILE_SIZE
;
1819 for (int a
= -interval_a
; a
!= a_size
+ interval_a
; a
+= interval_a
) {
1820 for (int b
= -interval_b
; b
!= b_size
+ interval_b
; b
+= interval_b
) {
1821 uint x
= (_thd
.pos
.x
+ (a
+ b
) / 2) / TILE_SIZE
;
1822 uint y
= (_thd
.pos
.y
+ (a
- b
) / 2) / TILE_SIZE
;
1824 if (x
< MapMaxX() && y
< MapMaxY()) {
1825 MarkTileDirtyByTile(TileXY(x
, y
));
1833 void SetSelectionRed(bool b
)
1835 _thd
.make_square_red
= b
;
1836 SetSelectionTilesDirty();
1840 * Test whether a sign is below the mouse
1841 * @param vp the clicked viewport
1842 * @param x X position of click
1843 * @param y Y position of click
1844 * @param sign the sign to check
1845 * @return true if the sign was hit
1847 static bool CheckClickOnViewportSign(const ViewPort
*vp
, int x
, int y
, const ViewportSign
*sign
)
1849 bool small
= (vp
->zoom
>= ZOOM_LVL_OUT_16X
);
1850 int sign_half_width
= ScaleByZoom((small
? sign
->width_small
: sign
->width_normal
) / 2, vp
->zoom
);
1851 int sign_height
= ScaleByZoom(VPSM_TOP
+ (small
? FONT_HEIGHT_SMALL
: FONT_HEIGHT_NORMAL
) + VPSM_BOTTOM
, vp
->zoom
);
1853 x
= ScaleByZoom(x
- vp
->left
, vp
->zoom
) + vp
->virtual_left
;
1854 y
= ScaleByZoom(y
- vp
->top
, vp
->zoom
) + vp
->virtual_top
;
1856 return y
>= sign
->top
&& y
< sign
->top
+ sign_height
&&
1857 x
>= sign
->center
- sign_half_width
&& x
< sign
->center
+ sign_half_width
;
1860 static bool CheckClickOnTown(const ViewPort
*vp
, int x
, int y
)
1862 if (!HasBit(_display_opt
, DO_SHOW_TOWN_NAMES
)) return false;
1866 if (CheckClickOnViewportSign(vp
, x
, y
, &t
->cache
.sign
)) {
1867 ShowTownViewWindow(t
->index
);
1875 static bool CheckClickOnStation(const ViewPort
*vp
, int x
, int y
)
1877 if (!(HasBit(_display_opt
, DO_SHOW_STATION_NAMES
) || HasBit(_display_opt
, DO_SHOW_WAYPOINT_NAMES
)) || IsInvisibilitySet(TO_SIGNS
)) return false;
1879 const BaseStation
*st
;
1880 FOR_ALL_BASE_STATIONS(st
) {
1881 /* Check whether the base station is a station or a waypoint */
1882 bool is_station
= Station::IsExpected(st
);
1884 /* Don't check if the display options are disabled */
1885 if (!HasBit(_display_opt
, is_station
? DO_SHOW_STATION_NAMES
: DO_SHOW_WAYPOINT_NAMES
)) continue;
1887 /* Don't check if competitor signs are not shown and the sign isn't owned by the local company */
1888 if (!HasBit(_display_opt
, DO_SHOW_COMPETITOR_SIGNS
) && _local_company
!= st
->owner
&& st
->owner
!= OWNER_NONE
) continue;
1890 if (CheckClickOnViewportSign(vp
, x
, y
, &st
->sign
)) {
1892 ShowStationViewWindow(st
->index
);
1894 ShowWaypointWindow(Waypoint::From(st
));
1904 static bool CheckClickOnSign(const ViewPort
*vp
, int x
, int y
)
1906 /* Signs are turned off, or they are transparent and invisibility is ON, or company is a spectator */
1907 if (!HasBit(_display_opt
, DO_SHOW_SIGNS
) || IsInvisibilitySet(TO_SIGNS
) || _local_company
== COMPANY_SPECTATOR
) return false;
1911 /* If competitor signs are hidden, don't check signs that aren't owned by local company */
1912 if (!HasBit(_display_opt
, DO_SHOW_COMPETITOR_SIGNS
) && _local_company
!= si
->owner
&& si
->owner
!= OWNER_DEITY
) continue;
1913 if (si
->owner
== OWNER_DEITY
&& _game_mode
!= GM_EDITOR
) continue;
1915 if (CheckClickOnViewportSign(vp
, x
, y
, &si
->sign
)) {
1916 HandleClickOnSign(si
);
1925 static bool CheckClickOnLandscape(const ViewPort
*vp
, int x
, int y
)
1927 Point pt
= TranslateXYToTileCoord(vp
, x
, y
);
1929 if (pt
.x
!= -1) return ClickTile(TileVirtXY(pt
.x
, pt
.y
));
1933 static void PlaceObject()
1938 pt
= GetTileBelowCursor();
1939 if (pt
.x
== -1) return;
1941 if ((_thd
.place_mode
& HT_DRAG_MASK
) == HT_POINT
) {
1942 pt
.x
+= TILE_SIZE
/ 2;
1943 pt
.y
+= TILE_SIZE
/ 2;
1946 _tile_fract_coords
.x
= pt
.x
& TILE_UNIT_MASK
;
1947 _tile_fract_coords
.y
= pt
.y
& TILE_UNIT_MASK
;
1949 w
= _thd
.GetCallbackWnd();
1950 if (w
!= NULL
) w
->OnPlaceObject(pt
, TileVirtXY(pt
.x
, pt
.y
));
1954 bool HandleViewportClicked(const ViewPort
*vp
, int x
, int y
)
1956 const Vehicle
*v
= CheckClickOnVehicle(vp
, x
, y
);
1958 if (_thd
.place_mode
& HT_VEHICLE
) {
1959 if (v
!= NULL
&& VehicleClicked(v
)) return true;
1962 /* Vehicle placement mode already handled above. */
1963 if ((_thd
.place_mode
& HT_DRAG_MASK
) != HT_NONE
) {
1968 if (CheckClickOnTown(vp
, x
, y
)) return true;
1969 if (CheckClickOnStation(vp
, x
, y
)) return true;
1970 if (CheckClickOnSign(vp
, x
, y
)) return true;
1971 bool result
= CheckClickOnLandscape(vp
, x
, y
);
1974 DEBUG(misc
, 2, "Vehicle %d (index %d) at %p", v
->unitnumber
, v
->index
, v
);
1975 if (IsCompanyBuildableVehicleType(v
)) {
1977 if (_ctrl_pressed
&& v
->owner
== _local_company
) {
1978 StartStopVehicle(v
, true);
1980 ShowVehicleViewWindow(v
);
1988 void RebuildViewportOverlay(Window
*w
)
1990 if (w
->viewport
->overlay
!= NULL
&&
1991 w
->viewport
->overlay
->GetCompanyMask() != 0 &&
1992 w
->viewport
->overlay
->GetCargoMask() != 0) {
1993 w
->viewport
->overlay
->RebuildCache();
1999 * Scrolls the viewport in a window to a given location.
2000 * @param x Desired x location of the map to scroll to (world coordinate).
2001 * @param y Desired y location of the map to scroll to (world coordinate).
2002 * @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.
2003 * @param w %Window containing the viewport.
2004 * @param instant Jump to the location instead of slowly moving to it.
2005 * @return Destination of the viewport was changed (to activate other actions when the viewport is already at the desired position).
2007 bool ScrollWindowTo(int x
, int y
, int z
, Window
*w
, bool instant
)
2009 /* The slope cannot be acquired outside of the map, so make sure we are always within the map. */
2010 if (z
== -1) z
= GetSlopePixelZ(Clamp(x
, 0, MapSizeX() * TILE_SIZE
- 1), Clamp(y
, 0, MapSizeY() * TILE_SIZE
- 1));
2012 Point pt
= MapXYZToViewport(w
->viewport
, x
, y
, z
);
2013 w
->viewport
->follow_vehicle
= INVALID_VEHICLE
;
2015 if (w
->viewport
->dest_scrollpos_x
== pt
.x
&& w
->viewport
->dest_scrollpos_y
== pt
.y
) return false;
2018 w
->viewport
->scrollpos_x
= pt
.x
;
2019 w
->viewport
->scrollpos_y
= pt
.y
;
2020 RebuildViewportOverlay(w
);
2023 w
->viewport
->dest_scrollpos_x
= pt
.x
;
2024 w
->viewport
->dest_scrollpos_y
= pt
.y
;
2029 * Scrolls the viewport in a window to a given location.
2030 * @param tile Desired tile to center on.
2031 * @param w %Window containing the viewport.
2032 * @param instant Jump to the location instead of slowly moving to it.
2033 * @return Destination of the viewport was changed (to activate other actions when the viewport is already at the desired position).
2035 bool ScrollWindowToTile(TileIndex tile
, Window
*w
, bool instant
)
2037 return ScrollWindowTo(TileX(tile
) * TILE_SIZE
, TileY(tile
) * TILE_SIZE
, -1, w
, instant
);
2041 * Scrolls the viewport of the main window to a given location.
2042 * @param tile Desired tile to center on.
2043 * @param instant Jump to the location instead of slowly moving to it.
2044 * @return Destination of the viewport was changed (to activate other actions when the viewport is already at the desired position).
2046 bool ScrollMainWindowToTile(TileIndex tile
, bool instant
)
2048 return ScrollMainWindowTo(TileX(tile
) * TILE_SIZE
+ TILE_SIZE
/ 2, TileY(tile
) * TILE_SIZE
+ TILE_SIZE
/ 2, -1, instant
);
2052 * Set a tile to display a red error square.
2053 * @param tile Tile that should show the red error square.
2055 void SetRedErrorSquare(TileIndex tile
)
2063 if (tile
!= INVALID_TILE
) MarkTileDirtyByTile(tile
);
2064 if (old
!= INVALID_TILE
) MarkTileDirtyByTile(old
);
2069 * Highlight \a w by \a h tiles at the cursor.
2070 * @param w Width of the highlighted tiles rectangle.
2071 * @param h Height of the highlighted tiles rectangle.
2073 void SetTileSelectSize(int w
, int h
)
2075 _thd
.new_size
.x
= w
* TILE_SIZE
;
2076 _thd
.new_size
.y
= h
* TILE_SIZE
;
2077 _thd
.new_outersize
.x
= 0;
2078 _thd
.new_outersize
.y
= 0;
2081 void SetTileSelectBigSize(int ox
, int oy
, int sx
, int sy
)
2083 _thd
.offs
.x
= ox
* TILE_SIZE
;
2084 _thd
.offs
.y
= oy
* TILE_SIZE
;
2085 _thd
.new_outersize
.x
= sx
* TILE_SIZE
;
2086 _thd
.new_outersize
.y
= sy
* TILE_SIZE
;
2089 /** returns the best autorail highlight type from map coordinates */
2090 static HighLightStyle
GetAutorailHT(int x
, int y
)
2092 return HT_RAIL
| _autorail_piece
[x
& TILE_UNIT_MASK
][y
& TILE_UNIT_MASK
];
2096 * Reset tile highlighting.
2098 void TileHighlightData::Reset()
2102 this->new_pos
.x
= 0;
2103 this->new_pos
.y
= 0;
2107 * Is the user dragging a 'diagonal rectangle'?
2108 * @return User is dragging a rotated rectangle.
2110 bool TileHighlightData::IsDraggingDiagonal()
2112 return (this->place_mode
& HT_DIAGONAL
) != 0 && _ctrl_pressed
&& _left_button_down
;
2116 * Get the window that started the current highlighting.
2117 * @return The window that requested the current tile highlighting, or \c NULL if not available.
2119 Window
*TileHighlightData::GetCallbackWnd()
2121 return FindWindowById(this->window_class
, this->window_number
);
2127 * Updates tile highlighting for all cases.
2128 * Uses _thd.selstart and _thd.selend and _thd.place_mode (set elsewhere) to determine _thd.pos and _thd.size
2129 * Also drawstyle is determined. Uses _thd.new.* as a buffer and calls SetSelectionTilesDirty() twice,
2130 * Once for the old and once for the new selection.
2131 * _thd is TileHighlightData, found in viewport.h
2133 void UpdateTileSelection()
2138 HighLightStyle new_drawstyle
= HT_NONE
;
2139 bool new_diagonal
= false;
2141 if ((_thd
.place_mode
& HT_DRAG_MASK
) == HT_SPECIAL
) {
2145 int x2
= _thd
.selstart
.x
& ~TILE_UNIT_MASK
;
2146 int y2
= _thd
.selstart
.y
& ~TILE_UNIT_MASK
;
2147 x1
&= ~TILE_UNIT_MASK
;
2148 y1
&= ~TILE_UNIT_MASK
;
2150 if (_thd
.IsDraggingDiagonal()) {
2151 new_diagonal
= true;
2153 if (x1
>= x2
) Swap(x1
, x2
);
2154 if (y1
>= y2
) Swap(y1
, y2
);
2156 _thd
.new_pos
.x
= x1
;
2157 _thd
.new_pos
.y
= y1
;
2158 _thd
.new_size
.x
= x2
- x1
;
2159 _thd
.new_size
.y
= y2
- y1
;
2160 if (!new_diagonal
) {
2161 _thd
.new_size
.x
+= TILE_SIZE
;
2162 _thd
.new_size
.y
+= TILE_SIZE
;
2164 new_drawstyle
= _thd
.next_drawstyle
;
2166 } else if ((_thd
.place_mode
& HT_DRAG_MASK
) != HT_NONE
) {
2167 Point pt
= GetTileBelowCursor();
2171 switch (_thd
.place_mode
& HT_DRAG_MASK
) {
2173 new_drawstyle
= HT_RECT
;
2176 new_drawstyle
= HT_POINT
;
2177 x1
+= TILE_SIZE
/ 2;
2178 y1
+= TILE_SIZE
/ 2;
2181 /* Draw one highlighted tile in any direction */
2182 new_drawstyle
= GetAutorailHT(pt
.x
, pt
.y
);
2185 switch (_thd
.place_mode
& HT_DIR_MASK
) {
2186 case HT_DIR_X
: new_drawstyle
= HT_LINE
| HT_DIR_X
; break;
2187 case HT_DIR_Y
: new_drawstyle
= HT_LINE
| HT_DIR_Y
; break;
2191 new_drawstyle
= (pt
.x
& TILE_UNIT_MASK
) + (pt
.y
& TILE_UNIT_MASK
) <= TILE_SIZE
? HT_LINE
| HT_DIR_HU
: HT_LINE
| HT_DIR_HL
;
2196 new_drawstyle
= (pt
.x
& TILE_UNIT_MASK
) > (pt
.y
& TILE_UNIT_MASK
) ? HT_LINE
| HT_DIR_VL
: HT_LINE
| HT_DIR_VR
;
2199 default: NOT_REACHED();
2201 _thd
.selstart
.x
= x1
& ~TILE_UNIT_MASK
;
2202 _thd
.selstart
.y
= y1
& ~TILE_UNIT_MASK
;
2208 _thd
.new_pos
.x
= x1
& ~TILE_UNIT_MASK
;
2209 _thd
.new_pos
.y
= y1
& ~TILE_UNIT_MASK
;
2213 /* redraw selection */
2214 if (_thd
.drawstyle
!= new_drawstyle
||
2215 _thd
.pos
.x
!= _thd
.new_pos
.x
|| _thd
.pos
.y
!= _thd
.new_pos
.y
||
2216 _thd
.size
.x
!= _thd
.new_size
.x
|| _thd
.size
.y
!= _thd
.new_size
.y
||
2217 _thd
.outersize
.x
!= _thd
.new_outersize
.x
||
2218 _thd
.outersize
.y
!= _thd
.new_outersize
.y
||
2219 _thd
.diagonal
!= new_diagonal
) {
2220 /* Clear the old tile selection? */
2221 if ((_thd
.drawstyle
& HT_DRAG_MASK
) != HT_NONE
) SetSelectionTilesDirty();
2223 _thd
.drawstyle
= new_drawstyle
;
2224 _thd
.pos
= _thd
.new_pos
;
2225 _thd
.size
= _thd
.new_size
;
2226 _thd
.outersize
= _thd
.new_outersize
;
2227 _thd
.diagonal
= new_diagonal
;
2230 /* Draw the new tile selection? */
2231 if ((new_drawstyle
& HT_DRAG_MASK
) != HT_NONE
) SetSelectionTilesDirty();
2236 * Displays the measurement tooltips when selecting multiple tiles
2237 * @param str String to be displayed
2238 * @param paramcount number of params to deal with
2239 * @param params (optional) up to 5 pieces of additional information that may be added to a tooltip
2240 * @param close_cond Condition for closing this tooltip.
2242 static inline void ShowMeasurementTooltips(StringID str
, uint paramcount
, const uint64 params
[], TooltipCloseCondition close_cond
= TCC_LEFT_CLICK
)
2244 if (!_settings_client
.gui
.measure_tooltip
) return;
2245 GuiShowTooltips(_thd
.GetCallbackWnd(), str
, paramcount
, params
, close_cond
);
2248 /** highlighting tiles while only going over them with the mouse */
2249 void VpStartPlaceSizing(TileIndex tile
, ViewportPlaceMethod method
, ViewportDragDropSelectionProcess process
)
2251 _thd
.select_method
= method
;
2252 _thd
.select_proc
= process
;
2253 _thd
.selend
.x
= TileX(tile
) * TILE_SIZE
;
2254 _thd
.selstart
.x
= TileX(tile
) * TILE_SIZE
;
2255 _thd
.selend
.y
= TileY(tile
) * TILE_SIZE
;
2256 _thd
.selstart
.y
= TileY(tile
) * TILE_SIZE
;
2258 /* Needed so several things (road, autoroad, bridges, ...) are placed correctly.
2259 * In effect, placement starts from the centre of a tile
2261 if (method
== VPM_X_OR_Y
|| method
== VPM_FIX_X
|| method
== VPM_FIX_Y
) {
2262 _thd
.selend
.x
+= TILE_SIZE
/ 2;
2263 _thd
.selend
.y
+= TILE_SIZE
/ 2;
2264 _thd
.selstart
.x
+= TILE_SIZE
/ 2;
2265 _thd
.selstart
.y
+= TILE_SIZE
/ 2;
2268 HighLightStyle others
= _thd
.place_mode
& ~(HT_DRAG_MASK
| HT_DIR_MASK
);
2269 if ((_thd
.place_mode
& HT_DRAG_MASK
) == HT_RECT
) {
2270 _thd
.place_mode
= HT_SPECIAL
| others
;
2271 _thd
.next_drawstyle
= HT_RECT
| others
;
2272 } else if (_thd
.place_mode
& (HT_RAIL
| HT_LINE
)) {
2273 _thd
.place_mode
= HT_SPECIAL
| others
;
2274 _thd
.next_drawstyle
= _thd
.drawstyle
| others
;
2276 _thd
.place_mode
= HT_SPECIAL
| others
;
2277 _thd
.next_drawstyle
= HT_POINT
| others
;
2279 _special_mouse_mode
= WSM_SIZING
;
2282 void VpSetPlaceSizingLimit(int limit
)
2284 _thd
.sizelimit
= limit
;
2288 * Highlights all tiles between a set of two tiles. Used in dock and tunnel placement
2289 * @param from TileIndex of the first tile to highlight
2290 * @param to TileIndex of the last tile to highlight
2292 void VpSetPresizeRange(TileIndex from
, TileIndex to
)
2294 uint64 distance
= DistanceManhattan(from
, to
) + 1;
2296 _thd
.selend
.x
= TileX(to
) * TILE_SIZE
;
2297 _thd
.selend
.y
= TileY(to
) * TILE_SIZE
;
2298 _thd
.selstart
.x
= TileX(from
) * TILE_SIZE
;
2299 _thd
.selstart
.y
= TileY(from
) * TILE_SIZE
;
2300 _thd
.next_drawstyle
= HT_RECT
;
2302 /* show measurement only if there is any length to speak of */
2303 if (distance
> 1) ShowMeasurementTooltips(STR_MEASURE_LENGTH
, 1, &distance
, TCC_HOVER
);
2306 static void VpStartPreSizing()
2309 _special_mouse_mode
= WSM_PRESIZE
;
2313 * returns information about the 2x1 piece to be build.
2314 * The lower bits (0-3) are the track type.
2316 static HighLightStyle
Check2x1AutoRail(int mode
)
2318 int fxpy
= _tile_fract_coords
.x
+ _tile_fract_coords
.y
;
2319 int sxpy
= (_thd
.selend
.x
& TILE_UNIT_MASK
) + (_thd
.selend
.y
& TILE_UNIT_MASK
);
2320 int fxmy
= _tile_fract_coords
.x
- _tile_fract_coords
.y
;
2321 int sxmy
= (_thd
.selend
.x
& TILE_UNIT_MASK
) - (_thd
.selend
.y
& TILE_UNIT_MASK
);
2324 default: NOT_REACHED();
2325 case 0: // end piece is lower right
2326 if (fxpy
>= 20 && sxpy
<= 12) return HT_DIR_HL
;
2327 if (fxmy
< -3 && sxmy
> 3) return HT_DIR_VR
;
2331 if (fxmy
> 3 && sxmy
< -3) return HT_DIR_VL
;
2332 if (fxpy
<= 12 && sxpy
>= 20) return HT_DIR_HU
;
2336 if (fxmy
> 3 && sxmy
< -3) return HT_DIR_VL
;
2337 if (fxpy
>= 20 && sxpy
<= 12) return HT_DIR_HL
;
2341 if (fxmy
< -3 && sxmy
> 3) return HT_DIR_VR
;
2342 if (fxpy
<= 12 && sxpy
>= 20) return HT_DIR_HU
;
2348 * Check if the direction of start and end tile should be swapped based on
2349 * the dragging-style. Default directions are:
2350 * in the case of a line (HT_RAIL, HT_LINE): DIR_NE, DIR_NW, DIR_N, DIR_E
2351 * in the case of a rect (HT_RECT, HT_POINT): DIR_S, DIR_E
2352 * For example dragging a rectangle area from south to north should be swapped to
2353 * north-south (DIR_S) to obtain the same results with less code. This is what
2354 * the return value signifies.
2355 * @param style HighLightStyle dragging style
2356 * @param start_tile start tile of drag
2357 * @param end_tile end tile of drag
2358 * @return boolean value which when true means start/end should be swapped
2360 static bool SwapDirection(HighLightStyle style
, TileIndex start_tile
, TileIndex end_tile
)
2362 uint start_x
= TileX(start_tile
);
2363 uint start_y
= TileY(start_tile
);
2364 uint end_x
= TileX(end_tile
);
2365 uint end_y
= TileY(end_tile
);
2367 switch (style
& HT_DRAG_MASK
) {
2369 case HT_LINE
: return (end_x
> start_x
|| (end_x
== start_x
&& end_y
> start_y
));
2372 case HT_POINT
: return (end_x
!= start_x
&& end_y
< start_y
);
2373 default: NOT_REACHED();
2380 * Calculates height difference between one tile and another.
2381 * Multiplies the result to suit the standard given by #TILE_HEIGHT_STEP.
2383 * To correctly get the height difference we need the direction we are dragging
2384 * in, as well as with what kind of tool we are dragging. For example a horizontal
2385 * autorail tool that starts in bottom and ends at the top of a tile will need the
2386 * maximum of SW, S and SE, N corners respectively. This is handled by the lookup table below
2387 * See #_tileoffs_by_dir in map.cpp for the direction enums if you can't figure out the values yourself.
2388 * @param style Highlighting style of the drag. This includes direction and style (autorail, rect, etc.)
2389 * @param distance Number of tiles dragged, important for horizontal/vertical drags, ignored for others.
2390 * @param start_tile Start tile of the drag operation.
2391 * @param end_tile End tile of the drag operation.
2392 * @return Height difference between two tiles. The tile measurement tool utilizes this value in its tooltip.
2394 static int CalcHeightdiff(HighLightStyle style
, uint distance
, TileIndex start_tile
, TileIndex end_tile
)
2396 bool swap
= SwapDirection(style
, start_tile
, end_tile
);
2397 uint h0
, h1
; // Start height and end height.
2399 if (start_tile
== end_tile
) return 0;
2400 if (swap
) Swap(start_tile
, end_tile
);
2402 switch (style
& HT_DRAG_MASK
) {
2404 /* In the case of an area we can determine whether we were dragging south or
2405 * east by checking the X-coordinates of the tiles */
2406 if (TileX(end_tile
) > TileX(start_tile
)) {
2407 start_tile
= TILE_ADD(start_tile
, TileDiffXY(0, 0));
2408 end_tile
= TILE_ADD(end_tile
, TileDiffXY(1, 1));
2410 start_tile
= TILE_ADD(start_tile
, TileDiffXY(1, 0));
2411 end_tile
= TILE_ADD(end_tile
, TileDiffXY(0, 1));
2415 h0
= TileHeight(start_tile
);
2416 h1
= TileHeight(end_tile
);
2418 default: { // All other types, this is mostly only line/autorail
2419 static const HighLightStyle flip_style_direction
[] = {
2420 HT_DIR_X
, HT_DIR_Y
, HT_DIR_HL
, HT_DIR_HU
, HT_DIR_VR
, HT_DIR_VL
2422 static const CoordDiff heightdiff_line_by_dir_start
[][2] = {
2423 /* Start */ { {1, 0}, {1, 1} }, /* HT_DIR_X */ { {0, 1}, {1, 1} }, // HT_DIR_Y
2424 /* Start */ { {1, 0}, {0, 0} }, /* HT_DIR_HU */ { {1, 0}, {1, 1} }, // HT_DIR_HL
2425 /* Start */ { {1, 0}, {1, 1} }, /* HT_DIR_VL */ { {0, 1}, {1, 1} }, // HT_DIR_VR
2427 static const CoordDiff heightdiff_line_by_dir_end
[][2] = {
2428 /* End */ { {0, 1}, {0, 0} }, /* HT_DIR_X */ { {1, 0}, {0, 0} }, // HT_DIR_Y
2429 /* End */ { {0, 1}, {0, 0} }, /* HT_DIR_HU */ { {1, 1}, {0, 1} }, // HT_DIR_HL
2430 /* End */ { {1, 0}, {0, 0} }, /* HT_DIR_VL */ { {0, 0}, {0, 1} }, // HT_DIR_VR
2433 distance
%= 2; // we're only interested if the distance is even or uneven
2434 style
&= HT_DIR_MASK
;
2436 /* To handle autorail, we do some magic to be able to use a lookup table.
2437 * Firstly if we drag the other way around, we switch start&end, and if needed
2438 * also flip the drag-position. Eg if it was on the left, and the distance is even
2439 * that means the end, which is now the start is on the right */
2440 if (swap
&& distance
== 0) style
= flip_style_direction
[style
];
2442 /* Use lookup table for start-tile based on HighLightStyle direction */
2443 assert(style
< lengthof(heightdiff_line_by_dir_start
));
2444 h0
= TileHeight(TILE_ADD(start_tile
, ToTileIndexDiff(heightdiff_line_by_dir_start
[style
][0])));
2445 uint ht
= TileHeight(TILE_ADD(start_tile
, ToTileIndexDiff(heightdiff_line_by_dir_start
[style
][1])));
2448 /* Use lookup table for end-tile based on HighLightStyle direction
2449 * flip around side (lower/upper, left/right) based on distance */
2450 if (distance
== 0) style
= flip_style_direction
[style
];
2451 assert(style
< lengthof(heightdiff_line_by_dir_end
));
2452 h1
= TileHeight(TILE_ADD(end_tile
, ToTileIndexDiff(heightdiff_line_by_dir_end
[style
][0])));
2453 ht
= TileHeight(TILE_ADD(end_tile
, ToTileIndexDiff(heightdiff_line_by_dir_end
[style
][1])));
2459 if (swap
) Swap(h0
, h1
);
2460 return (int)(h1
- h0
) * TILE_HEIGHT_STEP
;
2463 static const StringID measure_strings_length
[] = {STR_NULL
, STR_MEASURE_LENGTH
, STR_MEASURE_LENGTH_HEIGHTDIFF
};
2466 * Check for underflowing the map.
2467 * @param test the variable to test for underflowing
2468 * @param other the other variable to update to keep the line
2469 * @param mult the constant to multiply the difference by for \c other
2471 static void CheckUnderflow(int &test
, int &other
, int mult
)
2473 if (test
>= 0) return;
2475 other
+= mult
* test
;
2480 * Check for overflowing the map.
2481 * @param test the variable to test for overflowing
2482 * @param other the other variable to update to keep the line
2483 * @param max the maximum value for the \c test variable
2484 * @param mult the constant to multiply the difference by for \c other
2486 static void CheckOverflow(int &test
, int &other
, int max
, int mult
)
2488 if (test
<= max
) return;
2490 other
+= mult
* (test
- max
);
2494 /** while dragging */
2495 static void CalcRaildirsDrawstyle(int x
, int y
, int method
)
2499 int dx
= _thd
.selstart
.x
- (_thd
.selend
.x
& ~TILE_UNIT_MASK
);
2500 int dy
= _thd
.selstart
.y
- (_thd
.selend
.y
& ~TILE_UNIT_MASK
);
2501 uint w
= abs(dx
) + TILE_SIZE
;
2502 uint h
= abs(dy
) + TILE_SIZE
;
2504 if (method
& ~(VPM_RAILDIRS
| VPM_SIGNALDIRS
)) {
2505 /* We 'force' a selection direction; first four rail buttons. */
2506 method
&= ~(VPM_RAILDIRS
| VPM_SIGNALDIRS
);
2507 int raw_dx
= _thd
.selstart
.x
- _thd
.selend
.x
;
2508 int raw_dy
= _thd
.selstart
.y
- _thd
.selend
.y
;
2511 b
= HT_LINE
| HT_DIR_Y
;
2512 x
= _thd
.selstart
.x
;
2516 b
= HT_LINE
| HT_DIR_X
;
2517 y
= _thd
.selstart
.y
;
2520 case VPM_FIX_HORIZONTAL
:
2522 /* We are on a straight horizontal line. Determine the 'rail'
2523 * to build based the sub tile location. */
2524 b
= (x
& TILE_UNIT_MASK
) + (y
& TILE_UNIT_MASK
) >= TILE_SIZE
? HT_LINE
| HT_DIR_HL
: HT_LINE
| HT_DIR_HU
;
2526 /* We are not on a straight line. Determine the rail to build
2527 * based on whether we are above or below it. */
2528 b
= dx
+ dy
>= (int)TILE_SIZE
? HT_LINE
| HT_DIR_HU
: HT_LINE
| HT_DIR_HL
;
2530 /* Calculate where a horizontal line through the start point and
2531 * a vertical line from the selected end point intersect and
2532 * use that point as the end point. */
2533 int offset
= (raw_dx
- raw_dy
) / 2;
2534 x
= _thd
.selstart
.x
- (offset
& ~TILE_UNIT_MASK
);
2535 y
= _thd
.selstart
.y
+ (offset
& ~TILE_UNIT_MASK
);
2537 /* 'Build' the last half rail tile if needed */
2538 if ((offset
& TILE_UNIT_MASK
) > (TILE_SIZE
/ 2)) {
2539 if (dx
+ dy
>= (int)TILE_SIZE
) {
2540 x
+= (dx
+ dy
< 0) ? (int)TILE_SIZE
: -(int)TILE_SIZE
;
2542 y
+= (dx
+ dy
< 0) ? (int)TILE_SIZE
: -(int)TILE_SIZE
;
2546 /* Make sure we do not overflow the map! */
2547 CheckUnderflow(x
, y
, 1);
2548 CheckUnderflow(y
, x
, 1);
2549 CheckOverflow(x
, y
, (MapMaxX() - 1) * TILE_SIZE
, 1);
2550 CheckOverflow(y
, x
, (MapMaxY() - 1) * TILE_SIZE
, 1);
2551 assert(x
>= 0 && y
>= 0 && x
<= (int)(MapMaxX() * TILE_SIZE
) && y
<= (int)(MapMaxY() * TILE_SIZE
));
2555 case VPM_FIX_VERTICAL
:
2557 /* We are on a straight vertical line. Determine the 'rail'
2558 * to build based the sub tile location. */
2559 b
= (x
& TILE_UNIT_MASK
) > (y
& TILE_UNIT_MASK
) ? HT_LINE
| HT_DIR_VL
: HT_LINE
| HT_DIR_VR
;
2561 /* We are not on a straight line. Determine the rail to build
2562 * based on whether we are left or right from it. */
2563 b
= dx
< dy
? HT_LINE
| HT_DIR_VL
: HT_LINE
| HT_DIR_VR
;
2565 /* Calculate where a vertical line through the start point and
2566 * a horizontal line from the selected end point intersect and
2567 * use that point as the end point. */
2568 int offset
= (raw_dx
+ raw_dy
+ (int)TILE_SIZE
) / 2;
2569 x
= _thd
.selstart
.x
- (offset
& ~TILE_UNIT_MASK
);
2570 y
= _thd
.selstart
.y
- (offset
& ~TILE_UNIT_MASK
);
2572 /* 'Build' the last half rail tile if needed */
2573 if ((offset
& TILE_UNIT_MASK
) > (TILE_SIZE
/ 2)) {
2575 y
+= (dx
> dy
) ? (int)TILE_SIZE
: -(int)TILE_SIZE
;
2577 x
+= (dx
< dy
) ? (int)TILE_SIZE
: -(int)TILE_SIZE
;
2581 /* Make sure we do not overflow the map! */
2582 CheckUnderflow(x
, y
, -1);
2583 CheckUnderflow(y
, x
, -1);
2584 CheckOverflow(x
, y
, (MapMaxX() - 1) * TILE_SIZE
, -1);
2585 CheckOverflow(y
, x
, (MapMaxY() - 1) * TILE_SIZE
, -1);
2586 assert(x
>= 0 && y
>= 0 && x
<= (int)(MapMaxX() * TILE_SIZE
) && y
<= (int)(MapMaxY() * TILE_SIZE
));
2593 } else if (TileVirtXY(_thd
.selstart
.x
, _thd
.selstart
.y
) == TileVirtXY(x
, y
)) { // check if we're only within one tile
2594 if (method
& VPM_RAILDIRS
) {
2595 b
= GetAutorailHT(x
, y
);
2596 } else { // rect for autosignals on one tile
2599 } else if (h
== TILE_SIZE
) { // Is this in X direction?
2600 if (dx
== (int)TILE_SIZE
) { // 2x1 special handling
2601 b
= (Check2x1AutoRail(3)) | HT_LINE
;
2602 } else if (dx
== -(int)TILE_SIZE
) {
2603 b
= (Check2x1AutoRail(2)) | HT_LINE
;
2605 b
= HT_LINE
| HT_DIR_X
;
2607 y
= _thd
.selstart
.y
;
2608 } else if (w
== TILE_SIZE
) { // Or Y direction?
2609 if (dy
== (int)TILE_SIZE
) { // 2x1 special handling
2610 b
= (Check2x1AutoRail(1)) | HT_LINE
;
2611 } else if (dy
== -(int)TILE_SIZE
) { // 2x1 other direction
2612 b
= (Check2x1AutoRail(0)) | HT_LINE
;
2614 b
= HT_LINE
| HT_DIR_Y
;
2616 x
= _thd
.selstart
.x
;
2617 } else if (w
> h
* 2) { // still count as x dir?
2618 b
= HT_LINE
| HT_DIR_X
;
2619 y
= _thd
.selstart
.y
;
2620 } else if (h
> w
* 2) { // still count as y dir?
2621 b
= HT_LINE
| HT_DIR_Y
;
2622 x
= _thd
.selstart
.x
;
2623 } else { // complicated direction
2625 _thd
.selend
.x
= _thd
.selend
.x
& ~TILE_UNIT_MASK
;
2626 _thd
.selend
.y
= _thd
.selend
.y
& ~TILE_UNIT_MASK
;
2629 if (x
> _thd
.selstart
.x
) {
2630 if (y
> _thd
.selstart
.y
) {
2633 b
= (x
& TILE_UNIT_MASK
) > (y
& TILE_UNIT_MASK
) ? HT_LINE
| HT_DIR_VL
: HT_LINE
| HT_DIR_VR
;
2634 } else if (d
>= 0) {
2635 x
= _thd
.selstart
.x
+ h
;
2636 b
= HT_LINE
| HT_DIR_VL
;
2638 y
= _thd
.selstart
.y
+ w
;
2639 b
= HT_LINE
| HT_DIR_VR
;
2644 b
= (x
& TILE_UNIT_MASK
) + (y
& TILE_UNIT_MASK
) >= TILE_SIZE
? HT_LINE
| HT_DIR_HL
: HT_LINE
| HT_DIR_HU
;
2645 } else if (d
>= 0) {
2646 x
= _thd
.selstart
.x
+ h
;
2647 b
= HT_LINE
| HT_DIR_HL
;
2649 y
= _thd
.selstart
.y
- w
;
2650 b
= HT_LINE
| HT_DIR_HU
;
2654 if (y
> _thd
.selstart
.y
) {
2657 b
= (x
& TILE_UNIT_MASK
) + (y
& TILE_UNIT_MASK
) >= TILE_SIZE
? HT_LINE
| HT_DIR_HL
: HT_LINE
| HT_DIR_HU
;
2658 } else if (d
>= 0) {
2659 x
= _thd
.selstart
.x
- h
;
2660 b
= HT_LINE
| HT_DIR_HU
;
2662 y
= _thd
.selstart
.y
+ w
;
2663 b
= HT_LINE
| HT_DIR_HL
;
2668 b
= (x
& TILE_UNIT_MASK
) > (y
& TILE_UNIT_MASK
) ? HT_LINE
| HT_DIR_VL
: HT_LINE
| HT_DIR_VR
;
2669 } else if (d
>= 0) {
2670 x
= _thd
.selstart
.x
- h
;
2671 b
= HT_LINE
| HT_DIR_VR
;
2673 y
= _thd
.selstart
.y
- w
;
2674 b
= HT_LINE
| HT_DIR_VL
;
2680 if (_settings_client
.gui
.measure_tooltip
) {
2681 TileIndex t0
= TileVirtXY(_thd
.selstart
.x
, _thd
.selstart
.y
);
2682 TileIndex t1
= TileVirtXY(x
, y
);
2683 uint distance
= DistanceManhattan(t0
, t1
) + 1;
2687 if (distance
!= 1) {
2688 int heightdiff
= CalcHeightdiff(b
, distance
, t0
, t1
);
2689 /* If we are showing a tooltip for horizontal or vertical drags,
2690 * 2 tiles have a length of 1. To bias towards the ceiling we add
2691 * one before division. It feels more natural to count 3 lengths as 2 */
2692 if ((b
& HT_DIR_MASK
) != HT_DIR_X
&& (b
& HT_DIR_MASK
) != HT_DIR_Y
) {
2693 distance
= CeilDiv(distance
, 2);
2696 params
[index
++] = distance
;
2697 if (heightdiff
!= 0) params
[index
++] = heightdiff
;
2700 ShowMeasurementTooltips(measure_strings_length
[index
], index
, params
);
2705 _thd
.next_drawstyle
= b
;
2709 * Selects tiles while dragging
2710 * @param x X coordinate of end of selection
2711 * @param y Y coordinate of end of selection
2712 * @param method modifies the way tiles are selected. Possible
2713 * methods are VPM_* in viewport.h
2715 void VpSelectTilesWithMethod(int x
, int y
, ViewportPlaceMethod method
)
2718 HighLightStyle style
;
2725 /* Special handling of drag in any (8-way) direction */
2726 if (method
& (VPM_RAILDIRS
| VPM_SIGNALDIRS
)) {
2729 CalcRaildirsDrawstyle(x
, y
, method
);
2733 /* Needed so level-land is placed correctly */
2734 if ((_thd
.next_drawstyle
& HT_DRAG_MASK
) == HT_POINT
) {
2739 sx
= _thd
.selstart
.x
;
2740 sy
= _thd
.selstart
.y
;
2745 case VPM_X_OR_Y
: // drag in X or Y direction
2746 if (abs(sy
- y
) < abs(sx
- x
)) {
2753 goto calc_heightdiff_single_direction
;
2755 case VPM_X_LIMITED
: // Drag in X direction (limited size).
2756 limit
= (_thd
.sizelimit
- 1) * TILE_SIZE
;
2759 case VPM_FIX_X
: // drag in Y direction
2762 goto calc_heightdiff_single_direction
;
2764 case VPM_Y_LIMITED
: // Drag in Y direction (limited size).
2765 limit
= (_thd
.sizelimit
- 1) * TILE_SIZE
;
2768 case VPM_FIX_Y
: // drag in X direction
2772 calc_heightdiff_single_direction
:;
2774 x
= sx
+ Clamp(x
- sx
, -limit
, limit
);
2775 y
= sy
+ Clamp(y
- sy
, -limit
, limit
);
2777 if (_settings_client
.gui
.measure_tooltip
) {
2778 TileIndex t0
= TileVirtXY(sx
, sy
);
2779 TileIndex t1
= TileVirtXY(x
, y
);
2780 uint distance
= DistanceManhattan(t0
, t1
) + 1;
2784 if (distance
!= 1) {
2785 /* With current code passing a HT_LINE style to calculate the height
2786 * difference is enough. However if/when a point-tool is created
2787 * with this method, function should be called with new_style (below)
2788 * instead of HT_LINE | style case HT_POINT is handled specially
2789 * new_style := (_thd.next_drawstyle & HT_RECT) ? HT_LINE | style : _thd.next_drawstyle; */
2790 int heightdiff
= CalcHeightdiff(HT_LINE
| style
, 0, t0
, t1
);
2792 params
[index
++] = distance
;
2793 if (heightdiff
!= 0) params
[index
++] = heightdiff
;
2796 ShowMeasurementTooltips(measure_strings_length
[index
], index
, params
);
2800 case VPM_X_AND_Y_LIMITED
: // Drag an X by Y constrained rect area.
2801 limit
= (_thd
.sizelimit
- 1) * TILE_SIZE
;
2802 x
= sx
+ Clamp(x
- sx
, -limit
, limit
);
2803 y
= sy
+ Clamp(y
- sy
, -limit
, limit
);
2806 case VPM_X_AND_Y
: // drag an X by Y area
2807 if (_settings_client
.gui
.measure_tooltip
) {
2808 static const StringID measure_strings_area
[] = {
2809 STR_NULL
, STR_NULL
, STR_MEASURE_AREA
, STR_MEASURE_AREA_HEIGHTDIFF
2812 TileIndex t0
= TileVirtXY(sx
, sy
);
2813 TileIndex t1
= TileVirtXY(x
, y
);
2814 uint dx
= Delta(TileX(t0
), TileX(t1
)) + 1;
2815 uint dy
= Delta(TileY(t0
), TileY(t1
)) + 1;
2819 /* If dragging an area (eg dynamite tool) and it is actually a single
2820 * row/column, change the type to 'line' to get proper calculation for height */
2821 style
= (HighLightStyle
)_thd
.next_drawstyle
;
2822 if (_thd
.IsDraggingDiagonal()) {
2823 /* Determine the "area" of the diagonal dragged selection.
2824 * We assume the area is the number of tiles along the X
2825 * edge and the number of tiles along the Y edge. However,
2826 * multiplying these two numbers does not give the exact
2827 * number of tiles; basically we are counting the black
2828 * squares on a chess board and ignore the white ones to
2829 * make the tile counts at the edges match up. There is no
2830 * other way to make a proper count though.
2832 * First convert to the rotated coordinate system. */
2833 int dist_x
= TileX(t0
) - TileX(t1
);
2834 int dist_y
= TileY(t0
) - TileY(t1
);
2835 int a_max
= dist_x
+ dist_y
;
2836 int b_max
= dist_y
- dist_x
;
2838 /* Now determine the size along the edge, but due to the
2839 * chess board principle this counts double. */
2840 a_max
= abs(a_max
+ (a_max
> 0 ? 2 : -2)) / 2;
2841 b_max
= abs(b_max
+ (b_max
> 0 ? 2 : -2)) / 2;
2843 /* We get a 1x1 on normal 2x1 rectangles, due to it being
2844 * a seen as two sides. As the result for actual building
2845 * will be the same as non-diagonal dragging revert to that
2846 * behaviour to give it a more normally looking size. */
2847 if (a_max
!= 1 || b_max
!= 1) {
2851 } else if (style
& HT_RECT
) {
2853 style
= HT_LINE
| HT_DIR_Y
;
2854 } else if (dy
== 1) {
2855 style
= HT_LINE
| HT_DIR_X
;
2859 if (dx
!= 1 || dy
!= 1) {
2860 int heightdiff
= CalcHeightdiff(style
, 0, t0
, t1
);
2862 params
[index
++] = dx
- (style
& HT_POINT
? 1 : 0);
2863 params
[index
++] = dy
- (style
& HT_POINT
? 1 : 0);
2864 if (heightdiff
!= 0) params
[index
++] = heightdiff
;
2867 ShowMeasurementTooltips(measure_strings_area
[index
], index
, params
);
2871 default: NOT_REACHED();
2879 * Handle the mouse while dragging for placement/resizing.
2880 * @return State of handling the event.
2882 EventState
VpHandlePlaceSizingDrag()
2884 if (_special_mouse_mode
!= WSM_SIZING
) return ES_NOT_HANDLED
;
2886 /* stop drag mode if the window has been closed */
2887 Window
*w
= _thd
.GetCallbackWnd();
2889 ResetObjectToPlace();
2893 /* while dragging execute the drag procedure of the corresponding window (mostly VpSelectTilesWithMethod() ) */
2894 if (_left_button_down
) {
2895 w
->OnPlaceDrag(_thd
.select_method
, _thd
.select_proc
, GetTileBelowCursor());
2899 /* mouse button released..
2900 * keep the selected tool, but reset it to the original mode. */
2901 _special_mouse_mode
= WSM_NONE
;
2902 HighLightStyle others
= _thd
.place_mode
& ~(HT_DRAG_MASK
| HT_DIR_MASK
);
2903 if ((_thd
.next_drawstyle
& HT_DRAG_MASK
) == HT_RECT
) {
2904 _thd
.place_mode
= HT_RECT
| others
;
2905 } else if (_thd
.select_method
& VPM_SIGNALDIRS
) {
2906 _thd
.place_mode
= HT_RECT
| others
;
2907 } else if (_thd
.select_method
& VPM_RAILDIRS
) {
2908 _thd
.place_mode
= (_thd
.select_method
& ~VPM_RAILDIRS
) ? _thd
.next_drawstyle
: (HT_RAIL
| others
);
2910 _thd
.place_mode
= HT_POINT
| others
;
2912 SetTileSelectSize(1, 1);
2914 w
->OnPlaceMouseUp(_thd
.select_method
, _thd
.select_proc
, _thd
.selend
, TileVirtXY(_thd
.selstart
.x
, _thd
.selstart
.y
), TileVirtXY(_thd
.selend
.x
, _thd
.selend
.y
));
2919 void SetObjectToPlaceWnd(CursorID icon
, PaletteID pal
, HighLightStyle mode
, Window
*w
)
2921 SetObjectToPlace(icon
, pal
, mode
, w
->window_class
, w
->window_number
);
2924 #include "table/animcursors.h"
2926 void SetObjectToPlace(CursorID icon
, PaletteID pal
, HighLightStyle mode
, WindowClass window_class
, WindowNumber window_num
)
2928 if (_thd
.window_class
!= WC_INVALID
) {
2929 /* Undo clicking on button and drag & drop */
2930 Window
*w
= _thd
.GetCallbackWnd();
2931 /* Call the abort function, but set the window class to something
2932 * that will never be used to avoid infinite loops. Setting it to
2933 * the 'next' window class must not be done because recursion into
2934 * this function might in some cases reset the newly set object to
2935 * place or not properly reset the original selection. */
2936 _thd
.window_class
= WC_INVALID
;
2937 if (w
!= NULL
) w
->OnPlaceObjectAbort();
2940 /* Mark the old selection dirty, in case the selection shape or colour changes */
2941 if ((_thd
.drawstyle
& HT_DRAG_MASK
) != HT_NONE
) SetSelectionTilesDirty();
2943 SetTileSelectSize(1, 1);
2945 _thd
.make_square_red
= false;
2947 if (mode
== HT_DRAG
) { // HT_DRAG is for dragdropping trains in the depot window
2949 _special_mouse_mode
= WSM_DRAGDROP
;
2951 _special_mouse_mode
= WSM_NONE
;
2954 _thd
.place_mode
= mode
;
2955 _thd
.window_class
= window_class
;
2956 _thd
.window_number
= window_num
;
2958 if ((mode
& HT_DRAG_MASK
) == HT_SPECIAL
) { // special tools, like tunnels or docks start with presizing mode
2962 if ((icon
& ANIMCURSOR_FLAG
) != 0) {
2963 SetAnimatedMouseCursor(_animcursors
[icon
& ~ANIMCURSOR_FLAG
]);
2965 SetMouseCursor(icon
, pal
);
2970 void ResetObjectToPlace()
2972 SetObjectToPlace(SPR_CURSOR_MOUSE
, PAL_NONE
, HT_NONE
, WC_MAIN_WINDOW
, 0);
2975 Point
GetViewportStationMiddle(const ViewPort
*vp
, const Station
*st
)
2977 int x
= TileX(st
->xy
) * TILE_SIZE
;
2978 int y
= TileY(st
->xy
) * TILE_SIZE
;
2979 int z
= GetSlopePixelZ(Clamp(x
, 0, MapSizeX() * TILE_SIZE
- 1), Clamp(y
, 0, MapSizeY() * TILE_SIZE
- 1));
2981 Point p
= RemapCoords(x
, y
, z
);
2982 p
.x
= UnScaleByZoom(p
.x
- vp
->virtual_left
, vp
->zoom
) + vp
->left
;
2983 p
.y
= UnScaleByZoom(p
.y
- vp
->virtual_top
, vp
->zoom
) + vp
->top
;
2987 /** Helper class for getting the best sprite sorter. */
2988 struct ViewportSSCSS
{
2989 VpSorterChecker fct_checker
; ///< The check function.
2990 VpSpriteSorter fct_sorter
; ///< The sorting function.
2993 /** List of sorters ordered from best to worst. */
2994 static ViewportSSCSS _vp_sprite_sorters
[] = {
2996 { &ViewportSortParentSpritesSSE41Checker
, &ViewportSortParentSpritesSSE41
},
2998 { &ViewportSortParentSpritesChecker
, &ViewportSortParentSprites
}
3001 /** Choose the "best" sprite sorter and set _vp_sprite_sorter. */
3002 void InitializeSpriteSorter()
3004 for (uint i
= 0; i
< lengthof(_vp_sprite_sorters
); i
++) {
3005 if (_vp_sprite_sorters
[i
].fct_checker()) {
3006 _vp_sprite_sorter
= _vp_sprite_sorters
[i
].fct_sorter
;
3010 assert(_vp_sprite_sorter
!= NULL
);