|
|
/* $Id$ */
|
|
|
|
|
|
/*
|
|
|
* This file is part of OpenTTD.
|
|
|
* 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.
|
|
|
* 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.
|
|
|
* 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/>.
|
|
|
*/
|
|
|
|
|
|
/** @file viewport.cpp Handling of all viewports.
|
|
|
/**
|
|
|
* @file viewport.cpp Handling of all viewports.
|
|
|
*
|
|
|
* \verbatim
|
|
|
* The in-game coordinate system looks like this *
|
|
|
* *
|
|
|
* ^ Z *
|
|
|
* | *
|
|
|
* | *
|
|
|
* | *
|
|
|
* | *
|
|
|
* / \ *
|
|
|
* / \ *
|
|
|
* / \ *
|
|
|
* / \ *
|
|
|
* X < > Y *
|
|
|
* \endverbatim
|
|
|
*/
|
|
|
|
|
|
#include "stdafx.h"
|
|
|
#include "landscape.h"
|
|
|
#include "viewport_func.h"
|
|
|
#include "station_base.h"
|
|
|
#include "waypoint_base.h"
|
|
|
#include "town.h"
|
|
|
#include "signs_base.h"
|
|
|
#include "signs_func.h"
|
|
|
#include "vehicle_base.h"
|
|
|
#include "vehicle_gui.h"
|
|
|
#include "blitter/factory.hpp"
|
|
|
#include "strings_func.h"
|
|
|
#include "zoom_func.h"
|
|
|
#include "vehicle_func.h"
|
|
|
#include "company_func.h"
|
|
|
#include "waypoint_func.h"
|
|
|
#include "window_func.h"
|
|
|
#include "tilehighlight_func.h"
|
|
|
#include "window_gui.h"
|
|
|
|
|
|
#include "table/sprites.h"
|
|
|
#include "table/strings.h"
|
|
|
|
|
|
PlaceProc *_place_proc;
|
|
|
Point _tile_fract_coords;
|
|
|
|
|
|
struct StringSpriteToDraw {
|
|
|
StringID string;
|
|
|
Colours colour;
|
|
|
int32 x;
|
|
|
int32 y;
|
|
@@ -63,97 +64,98 @@ struct StringSpriteToDraw {
|
|
|
struct TileSpriteToDraw {
|
|
|
SpriteID image;
|
|
|
PaletteID pal;
|
|
|
const SubSprite *sub; ///< only draw a rectangular part of the sprite
|
|
|
int32 x; ///< screen X coordinate of sprite
|
|
|
int32 y; ///< screen Y coordinate of sprite
|
|
|
};
|
|
|
|
|
|
struct ChildScreenSpriteToDraw {
|
|
|
SpriteID image;
|
|
|
PaletteID pal;
|
|
|
const SubSprite *sub; ///< only draw a rectangular part of the sprite
|
|
|
int32 x;
|
|
|
int32 y;
|
|
|
int next; ///< next child to draw (-1 at the end)
|
|
|
};
|
|
|
|
|
|
/** Parent sprite that should be drawn */
|
|
|
struct ParentSpriteToDraw {
|
|
|
SpriteID image; ///< sprite to draw
|
|
|
PaletteID pal; ///< palette to use
|
|
|
const SubSprite *sub; ///< only draw a rectangular part of the sprite
|
|
|
|
|
|
int32 x; ///< screen X coordinate of sprite
|
|
|
int32 y; ///< screen Y coordinate of sprite
|
|
|
|
|
|
int32 left; ///< minimal screen X coordinate of sprite (= x + sprite->x_offs), reference point for child sprites
|
|
|
int32 top; ///< minimal screen Y coordinate of sprite (= y + sprite->y_offs), reference point for child sprites
|
|
|
|
|
|
int32 xmin; ///< minimal world X coordinate of bounding box
|
|
|
int32 xmax; ///< maximal world X coordinate of bounding box
|
|
|
int32 ymin; ///< minimal world Y coordinate of bounding box
|
|
|
int32 ymax; ///< maximal world Y coordinate of bounding box
|
|
|
int zmin; ///< minimal world Z coordinate of bounding box
|
|
|
int zmax; ///< maximal world Z coordinate of bounding box
|
|
|
|
|
|
int first_child; ///< the first child to draw.
|
|
|
bool comparison_done; ///< Used during sprite sorting: true if sprite has been compared with all other sprites
|
|
|
};
|
|
|
|
|
|
/** Enumeration of multi-part foundations */
|
|
|
enum FoundationPart {
|
|
|
FOUNDATION_PART_NONE = 0xFF, ///< Neither foundation nor groundsprite drawn yet.
|
|
|
FOUNDATION_PART_NORMAL = 0, ///< First part (normal foundation or no foundation)
|
|
|
FOUNDATION_PART_HALFTILE = 1, ///< Second part (halftile foundation)
|
|
|
FOUNDATION_PART_END
|
|
|
};
|
|
|
|
|
|
/** Mode of "sprite combining"
|
|
|
/**
|
|
|
* Mode of "sprite combining"
|
|
|
* @see StartSpriteCombine
|
|
|
*/
|
|
|
enum SpriteCombineMode {
|
|
|
SPRITE_COMBINE_NONE, ///< Every #AddSortableSpriteToDraw start its own bounding box
|
|
|
SPRITE_COMBINE_PENDING, ///< %Sprite combining will start with the next unclipped sprite.
|
|
|
SPRITE_COMBINE_ACTIVE, ///< %Sprite combining is active. #AddSortableSpriteToDraw outputs child sprites.
|
|
|
};
|
|
|
|
|
|
typedef SmallVector<TileSpriteToDraw, 64> TileSpriteToDrawVector;
|
|
|
typedef SmallVector<StringSpriteToDraw, 4> StringSpriteToDrawVector;
|
|
|
typedef SmallVector<ParentSpriteToDraw, 64> ParentSpriteToDrawVector;
|
|
|
typedef SmallVector<ParentSpriteToDraw*, 64> ParentSpriteToSortVector;
|
|
|
typedef SmallVector<ChildScreenSpriteToDraw, 16> ChildScreenSpriteToDrawVector;
|
|
|
|
|
|
/** Data structure storing rendering information */
|
|
|
struct ViewportDrawer {
|
|
|
DrawPixelInfo dpi;
|
|
|
|
|
|
StringSpriteToDrawVector string_sprites_to_draw;
|
|
|
TileSpriteToDrawVector tile_sprites_to_draw;
|
|
|
ParentSpriteToDrawVector parent_sprites_to_draw;
|
|
|
ParentSpriteToSortVector parent_sprites_to_sort; ///< Parent sprite pointer array used for sorting
|
|
|
ChildScreenSpriteToDrawVector child_screen_sprites_to_draw;
|
|
|
|
|
|
int *last_child;
|
|
|
|
|
|
SpriteCombineMode combine_sprites; ///< Current mode of "sprite combining". @see StartSpriteCombine
|
|
|
|
|
|
int foundation[FOUNDATION_PART_END]; ///< Foundation sprites (index into parent_sprites_to_draw).
|
|
|
FoundationPart foundation_part; ///< Currently active foundation for ground sprite drawing.
|
|
|
int *last_foundation_child[FOUNDATION_PART_END]; ///< Tail of ChildSprite list of the foundations. (index into child_screen_sprites_to_draw)
|
|
|
Point foundation_offset[FOUNDATION_PART_END]; ///< Pixel offset for ground sprites on the foundations.
|
|
|
};
|
|
|
|
|
|
static ViewportDrawer _vd;
|
|
|
|
|
|
TileHighlightData _thd;
|
|
|
static TileInfo *_cur_ti;
|
|
|
bool _draw_bounding_boxes = false;
|
|
|
|
|
|
static Point MapXYZToViewport(const ViewPort *vp, int x, int y, int z)
|
|
|
{
|
|
|
Point p = RemapCoords(x, y, z);
|
|
|
p.x -= vp->virtual_width / 2;
|
|
|
p.y -= vp->virtual_height / 2;
|
|
|
return p;
|
|
|
}
|
|
|
|
|
@@ -403,97 +405,98 @@ static Point TranslateXYToTileCoord(cons
|
|
|
for (uint malus = 3; malus > 0; malus--) z = GetSlopeZ(Clamp(a + (int)max(z, malus) - (int)malus, min_coord, MapMaxX() * TILE_SIZE - 1), Clamp(b + (int)max(z, malus) - (int)malus, min_coord, MapMaxY() * TILE_SIZE - 1)) / 2;
|
|
|
for (int i = 0; i < 5; i++) z = GetSlopeZ(Clamp(a + (int)z, min_coord, MapMaxX() * TILE_SIZE - 1), Clamp(b + (int)z, min_coord, MapMaxY() * TILE_SIZE - 1)) / 2;
|
|
|
|
|
|
pt.x = Clamp(a + (int)z, min_coord, MapMaxX() * TILE_SIZE - 1);
|
|
|
pt.y = Clamp(b + (int)z, min_coord, MapMaxY() * TILE_SIZE - 1);
|
|
|
|
|
|
return pt;
|
|
|
}
|
|
|
|
|
|
/* When used for zooming, check area below current coordinates (x,y)
|
|
|
* and return the tile of the zoomed out/in position (zoom_x, zoom_y)
|
|
|
* when you just want the tile, make x = zoom_x and y = zoom_y */
|
|
|
static Point GetTileFromScreenXY(int x, int y, int zoom_x, int zoom_y)
|
|
|
{
|
|
|
Window *w;
|
|
|
ViewPort *vp;
|
|
|
Point pt;
|
|
|
|
|
|
if ( (w = FindWindowFromPt(x, y)) != NULL &&
|
|
|
(vp = IsPtInWindowViewport(w, x, y)) != NULL)
|
|
|
return TranslateXYToTileCoord(vp, zoom_x, zoom_y);
|
|
|
|
|
|
pt.y = pt.x = -1;
|
|
|
return pt;
|
|
|
}
|
|
|
|
|
|
Point GetTileBelowCursor()
|
|
|
{
|
|
|
return GetTileFromScreenXY(_cursor.pos.x, _cursor.pos.y, _cursor.pos.x, _cursor.pos.y);
|
|
|
}
|
|
|
|
|
|
|
|
|
Point GetTileZoomCenterWindow(bool in, Window * w)
|
|
|
{
|
|
|
int x, y;
|
|
|
ViewPort *vp = w->viewport;
|
|
|
|
|
|
if (in) {
|
|
|
x = ((_cursor.pos.x - vp->left) >> 1) + (vp->width >> 2);
|
|
|
y = ((_cursor.pos.y - vp->top) >> 1) + (vp->height >> 2);
|
|
|
} else {
|
|
|
x = vp->width - (_cursor.pos.x - vp->left);
|
|
|
y = vp->height - (_cursor.pos.y - vp->top);
|
|
|
}
|
|
|
/* Get the tile below the cursor and center on the zoomed-out center */
|
|
|
return GetTileFromScreenXY(_cursor.pos.x, _cursor.pos.y, x + vp->left, y + vp->top);
|
|
|
}
|
|
|
|
|
|
/** Update the status of the zoom-buttons according to the zoom-level
|
|
|
/**
|
|
|
* Update the status of the zoom-buttons according to the zoom-level
|
|
|
* of the viewport. This will update their status and invalidate accordingly
|
|
|
* @param w Window pointer to the window that has the zoom buttons
|
|
|
* @param vp pointer to the viewport whose zoom-level the buttons represent
|
|
|
* @param widget_zoom_in widget index for window with zoom-in button
|
|
|
* @param widget_zoom_out widget index for window with zoom-out button */
|
|
|
void HandleZoomMessage(Window *w, const ViewPort *vp, byte widget_zoom_in, byte widget_zoom_out)
|
|
|
{
|
|
|
w->SetWidgetDisabledState(widget_zoom_in, vp->zoom == ZOOM_LVL_MIN);
|
|
|
w->SetWidgetDirty(widget_zoom_in);
|
|
|
|
|
|
w->SetWidgetDisabledState(widget_zoom_out, vp->zoom == ZOOM_LVL_MAX);
|
|
|
w->SetWidgetDirty(widget_zoom_out);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* Schedules a tile sprite for drawing.
|
|
|
*
|
|
|
* @param image the image to draw.
|
|
|
* @param pal the provided palette.
|
|
|
* @param x position x (world coordinates) of the sprite.
|
|
|
* @param y position y (world coordinates) of the sprite.
|
|
|
* @param z position z (world coordinates) of the sprite.
|
|
|
* @param sub Only draw a part of the sprite.
|
|
|
* @param extra_offs_x Pixel X offset for the sprite position.
|
|
|
* @param extra_offs_y Pixel Y offset for the sprite position.
|
|
|
*/
|
|
|
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)
|
|
|
{
|
|
|
assert((image & SPRITE_MASK) < MAX_SPRITES);
|
|
|
|
|
|
TileSpriteToDraw *ts = _vd.tile_sprites_to_draw.Append();
|
|
|
ts->image = image;
|
|
|
ts->pal = pal;
|
|
|
ts->sub = sub;
|
|
|
Point pt = RemapCoords(x, y, z);
|
|
|
ts->x = pt.x + extra_offs_x;
|
|
|
ts->y = pt.y + extra_offs_y;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* Adds a child sprite to the active foundation.
|
|
|
*
|
|
|
* The pixel offset of the sprite relative to the ParentSprite is the sum of the offset passed to OffsetGroundSprite() and extra_offs_?.
|
|
|
*
|
|
|
* @param image the image to draw.
|
|
|
* @param pal the provided palette.
|
|
|
* @param sub Only draw a part of the sprite.
|
|
|
* @param foundation_part Foundation part.
|
|
@@ -566,97 +569,98 @@ void DrawGroundSprite(SpriteID image, Pa
|
|
|
*/
|
|
|
void OffsetGroundSprite(int x, int y)
|
|
|
{
|
|
|
/* Switch to next foundation part */
|
|
|
switch (_vd.foundation_part) {
|
|
|
case FOUNDATION_PART_NONE:
|
|
|
_vd.foundation_part = FOUNDATION_PART_NORMAL;
|
|
|
break;
|
|
|
case FOUNDATION_PART_NORMAL:
|
|
|
_vd.foundation_part = FOUNDATION_PART_HALFTILE;
|
|
|
break;
|
|
|
default: NOT_REACHED();
|
|
|
}
|
|
|
|
|
|
/* _vd.last_child == NULL if foundation sprite was clipped by the viewport bounds */
|
|
|
if (_vd.last_child != NULL) _vd.foundation[_vd.foundation_part] = _vd.parent_sprites_to_draw.Length() - 1;
|
|
|
|
|
|
_vd.foundation_offset[_vd.foundation_part].x = x;
|
|
|
_vd.foundation_offset[_vd.foundation_part].y = y;
|
|
|
_vd.last_foundation_child[_vd.foundation_part] = _vd.last_child;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* Adds a child sprite to a parent sprite.
|
|
|
* In contrast to "AddChildSpriteScreen()" the sprite position is in world coordinates
|
|
|
*
|
|
|
* @param image the image to draw.
|
|
|
* @param pal the provided palette.
|
|
|
* @param x position x of the sprite.
|
|
|
* @param y position y of the sprite.
|
|
|
* @param z position z of the sprite.
|
|
|
* @param sub Only draw a part of the sprite.
|
|
|
*/
|
|
|
static void AddCombinedSprite(SpriteID image, PaletteID pal, int x, int y, byte z, const SubSprite *sub)
|
|
|
{
|
|
|
Point pt = RemapCoords(x, y, z);
|
|
|
const Sprite *spr = GetSprite(image & SPRITE_MASK, ST_NORMAL);
|
|
|
|
|
|
if (pt.x + spr->x_offs >= _vd.dpi.left + _vd.dpi.width ||
|
|
|
pt.x + spr->x_offs + spr->width <= _vd.dpi.left ||
|
|
|
pt.y + spr->y_offs >= _vd.dpi.top + _vd.dpi.height ||
|
|
|
pt.y + spr->y_offs + spr->height <= _vd.dpi.top)
|
|
|
return;
|
|
|
|
|
|
const ParentSpriteToDraw *pstd = _vd.parent_sprites_to_draw.End() - 1;
|
|
|
AddChildSpriteScreen(image, pal, pt.x - pstd->left, pt.y - pstd->top, false, sub);
|
|
|
}
|
|
|
|
|
|
/** Draw a (transparent) sprite at given coordinates with a given bounding box.
|
|
|
/**
|
|
|
* Draw a (transparent) sprite at given coordinates with a given bounding box.
|
|
|
* 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.
|
|
|
* Bounding boxes with bb_offset_x == w or bb_offset_y == h or bb_offset_z == dz are allowed and produce thin slices.
|
|
|
*
|
|
|
* @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
|
|
|
* defined by the sprite offset in the grf file.
|
|
|
* However if modifying the sprite offsets is not suitable (e.g. when using existing graphics), the bounding box can be tuned by bb_offset.
|
|
|
*
|
|
|
* @pre w >= bb_offset_x, h >= bb_offset_y, dz >= bb_offset_z. Else w, h or dz are ignored.
|
|
|
*
|
|
|
* @param image the image to combine and draw,
|
|
|
* @param pal the provided palette,
|
|
|
* @param x position X (world) of the sprite,
|
|
|
* @param y position Y (world) of the sprite,
|
|
|
* @param w bounding box extent towards positive X (world),
|
|
|
* @param h bounding box extent towards positive Y (world),
|
|
|
* @param dz bounding box extent towards positive Z (world),
|
|
|
* @param z position Z (world) of the sprite,
|
|
|
* @param transparent if true, switch the palette between the provided palette and the transparent palette,
|
|
|
* @param bb_offset_x bounding box extent towards negative X (world),
|
|
|
* @param bb_offset_y bounding box extent towards negative Y (world),
|
|
|
* @param bb_offset_z bounding box extent towards negative Z (world)
|
|
|
* @param sub Only draw a part of the sprite.
|
|
|
*/
|
|
|
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)
|
|
|
{
|
|
|
int32 left, right, top, bottom;
|
|
|
|
|
|
assert((image & SPRITE_MASK) < MAX_SPRITES);
|
|
|
|
|
|
/* make the sprites transparent with the right palette */
|
|
|
if (transparent) {
|
|
|
SetBit(image, PALETTE_MODIFIER_TRANSPARENT);
|
|
|
pal = PALETTE_TO_TRANSPARENT;
|
|
|
}
|
|
|
|
|
|
if (_vd.combine_sprites == SPRITE_COMBINE_ACTIVE) {
|
|
|
AddCombinedSprite(image, pal, x, y, z, sub);
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
_vd.last_child = NULL;
|
|
|
|
|
|
Point pt = RemapCoords(x, y, z);
|
|
|
int tmp_left, tmp_top, tmp_x = pt.x, tmp_y = pt.y;
|
|
|
|
|
|
/* Compute screen extents of sprite */
|
|
|
if (image == SPR_EMPTY_BOUNDING_BOX) {
|
|
|
left = tmp_left = RemapCoords(x + w , y + bb_offset_y, z + bb_offset_z).x;
|
|
@@ -1375,97 +1379,98 @@ void ViewportDoDraw(const ViewPort *vp,
|
|
|
int mask = ScaleByZoom(-1, vp->zoom);
|
|
|
|
|
|
_vd.combine_sprites = SPRITE_COMBINE_NONE;
|
|
|
|
|
|
_vd.dpi.width = (right - left) & mask;
|
|
|
_vd.dpi.height = (bottom - top) & mask;
|
|
|
_vd.dpi.left = left & mask;
|
|
|
_vd.dpi.top = top & mask;
|
|
|
_vd.dpi.pitch = old_dpi->pitch;
|
|
|
_vd.last_child = NULL;
|
|
|
|
|
|
int x = UnScaleByZoom(_vd.dpi.left - (vp->virtual_left & mask), vp->zoom) + vp->left;
|
|
|
int y = UnScaleByZoom(_vd.dpi.top - (vp->virtual_top & mask), vp->zoom) + vp->top;
|
|
|
|
|
|
_vd.dpi.dst_ptr = BlitterFactoryBase::GetCurrentBlitter()->MoveTo(old_dpi->dst_ptr, x - old_dpi->left, y - old_dpi->top);
|
|
|
|
|
|
ViewportAddLandscape();
|
|
|
ViewportAddVehicles(&_vd.dpi);
|
|
|
|
|
|
ViewportAddTownNames(&_vd.dpi);
|
|
|
ViewportAddStationNames(&_vd.dpi);
|
|
|
ViewportAddSigns(&_vd.dpi);
|
|
|
|
|
|
DrawTextEffects(&_vd.dpi);
|
|
|
|
|
|
if (_vd.tile_sprites_to_draw.Length() != 0) ViewportDrawTileSprites(&_vd.tile_sprites_to_draw);
|
|
|
|
|
|
ParentSpriteToDraw *psd_end = _vd.parent_sprites_to_draw.End();
|
|
|
for (ParentSpriteToDraw *it = _vd.parent_sprites_to_draw.Begin(); it != psd_end; it++) {
|
|
|
*_vd.parent_sprites_to_sort.Append() = it;
|
|
|
}
|
|
|
|
|
|
ViewportSortParentSprites(&_vd.parent_sprites_to_sort);
|
|
|
ViewportDrawParentSprites(&_vd.parent_sprites_to_sort, &_vd.child_screen_sprites_to_draw);
|
|
|
|
|
|
if (_draw_bounding_boxes) ViewportDrawBoundingBoxes(&_vd.parent_sprites_to_sort);
|
|
|
|
|
|
if (_vd.string_sprites_to_draw.Length() != 0) ViewportDrawStrings(&_vd.dpi, &_vd.string_sprites_to_draw);
|
|
|
|
|
|
_cur_dpi = old_dpi;
|
|
|
|
|
|
_vd.string_sprites_to_draw.Clear();
|
|
|
_vd.tile_sprites_to_draw.Clear();
|
|
|
_vd.parent_sprites_to_draw.Clear();
|
|
|
_vd.parent_sprites_to_sort.Clear();
|
|
|
_vd.child_screen_sprites_to_draw.Clear();
|
|
|
}
|
|
|
|
|
|
/** Make sure we don't draw a too big area at a time.
|
|
|
/**
|
|
|
* Make sure we don't draw a too big area at a time.
|
|
|
* If we do, the sprite memory will overflow. */
|
|
|
static void ViewportDrawChk(const ViewPort *vp, int left, int top, int right, int bottom)
|
|
|
{
|
|
|
if (ScaleByZoom(bottom - top, vp->zoom) * ScaleByZoom(right - left, vp->zoom) > 180000) {
|
|
|
if ((bottom - top) > (right - left)) {
|
|
|
int t = (top + bottom) >> 1;
|
|
|
ViewportDrawChk(vp, left, top, right, t);
|
|
|
ViewportDrawChk(vp, left, t, right, bottom);
|
|
|
} else {
|
|
|
int t = (left + right) >> 1;
|
|
|
ViewportDrawChk(vp, left, top, t, bottom);
|
|
|
ViewportDrawChk(vp, t, top, right, bottom);
|
|
|
}
|
|
|
} else {
|
|
|
ViewportDoDraw(vp,
|
|
|
ScaleByZoom(left - vp->left, vp->zoom) + vp->virtual_left,
|
|
|
ScaleByZoom(top - vp->top, vp->zoom) + vp->virtual_top,
|
|
|
ScaleByZoom(right - vp->left, vp->zoom) + vp->virtual_left,
|
|
|
ScaleByZoom(bottom - vp->top, vp->zoom) + vp->virtual_top
|
|
|
);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
static inline void ViewportDraw(const ViewPort *vp, int left, int top, int right, int bottom)
|
|
|
{
|
|
|
if (right <= vp->left || bottom <= vp->top) return;
|
|
|
|
|
|
if (left >= vp->left + vp->width) return;
|
|
|
|
|
|
if (left < vp->left) left = vp->left;
|
|
|
if (right > vp->left + vp->width) right = vp->left + vp->width;
|
|
|
|
|
|
if (top >= vp->top + vp->height) return;
|
|
|
|
|
|
if (top < vp->top) top = vp->top;
|
|
|
if (bottom > vp->top + vp->height) bottom = vp->top + vp->height;
|
|
|
|
|
|
ViewportDrawChk(vp, left, top, right, bottom);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* Draw the viewport of this window.
|
|
|
*/
|
|
|
void Window::DrawViewport() const
|
|
|
{
|
|
|
DrawPixelInfo *dpi = _cur_dpi;
|
|
|
|
|
|
dpi->left += this->left;
|
|
@@ -1814,143 +1819,145 @@ bool HandleViewportClicked(const ViewPor
|
|
|
StartStopVehicle(v, true);
|
|
|
} else {
|
|
|
ShowVehicleViewWindow(v);
|
|
|
}
|
|
|
}
|
|
|
return true;
|
|
|
}
|
|
|
return CheckClickOnLandscape(vp, x, y);
|
|
|
}
|
|
|
|
|
|
Vehicle *CheckMouseOverVehicle()
|
|
|
{
|
|
|
const Window *w;
|
|
|
const ViewPort *vp;
|
|
|
|
|
|
int x = _cursor.pos.x;
|
|
|
int y = _cursor.pos.y;
|
|
|
|
|
|
w = FindWindowFromPt(x, y);
|
|
|
if (w == NULL) return NULL;
|
|
|
|
|
|
vp = IsPtInWindowViewport(w, x, y);
|
|
|
return (vp != NULL) ? CheckClickOnVehicle(vp, x, y) : NULL;
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void PlaceObject()
|
|
|
{
|
|
|
Point pt;
|
|
|
Window *w;
|
|
|
|
|
|
pt = GetTileBelowCursor();
|
|
|
if (pt.x == -1) return;
|
|
|
|
|
|
if (_thd.place_mode == HT_POINT) {
|
|
|
pt.x += 8;
|
|
|
pt.y += 8;
|
|
|
}
|
|
|
|
|
|
_tile_fract_coords.x = pt.x & TILE_UNIT_MASK;
|
|
|
_tile_fract_coords.y = pt.y & TILE_UNIT_MASK;
|
|
|
|
|
|
w = GetCallbackWnd();
|
|
|
if (w != NULL) w->OnPlaceObject(pt, TileVirtXY(pt.x, pt.y));
|
|
|
}
|
|
|
|
|
|
|
|
|
/** Scrolls the viewport in a window to a given location.
|
|
|
/**
|
|
|
* Scrolls the viewport in a window to a given location.
|
|
|
* @param x Desired x location of the map to scroll to (world coordinate).
|
|
|
* @param y Desired y location of the map to scroll to (world coordinate).
|
|
|
* @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.
|
|
|
* @param w %Window containing the viewport.
|
|
|
* @param instant Jump to the location instead of slowly moving to it.
|
|
|
* @return Destination of the viewport was changed (to activate other actions when the viewport is already at the desired position).
|
|
|
*/
|
|
|
bool ScrollWindowTo(int x, int y, int z, Window *w, bool instant)
|
|
|
{
|
|
|
/* The slope cannot be acquired outside of the map, so make sure we are always within the map. */
|
|
|
if (z == -1) z = GetSlopeZ(Clamp(x, 0, MapSizeX() * TILE_SIZE - 1), Clamp(y, 0, MapSizeY() * TILE_SIZE - 1));
|
|
|
|
|
|
Point pt = MapXYZToViewport(w->viewport, x, y, z);
|
|
|
w->viewport->follow_vehicle = INVALID_VEHICLE;
|
|
|
|
|
|
if (w->viewport->dest_scrollpos_x == pt.x && w->viewport->dest_scrollpos_y == pt.y) return false;
|
|
|
|
|
|
if (instant) {
|
|
|
w->viewport->scrollpos_x = pt.x;
|
|
|
w->viewport->scrollpos_y = pt.y;
|
|
|
}
|
|
|
|
|
|
w->viewport->dest_scrollpos_x = pt.x;
|
|
|
w->viewport->dest_scrollpos_y = pt.y;
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
bool ScrollMainWindowToTile(TileIndex tile, bool instant)
|
|
|
{
|
|
|
return ScrollMainWindowTo(TileX(tile) * TILE_SIZE + TILE_SIZE / 2, TileY(tile) * TILE_SIZE + TILE_SIZE / 2, -1, instant);
|
|
|
}
|
|
|
|
|
|
void SetRedErrorSquare(TileIndex tile)
|
|
|
{
|
|
|
TileIndex old;
|
|
|
|
|
|
old = _thd.redsq;
|
|
|
_thd.redsq = tile;
|
|
|
|
|
|
if (tile != old) {
|
|
|
if (tile != INVALID_TILE) MarkTileDirtyByTile(tile);
|
|
|
if (old != INVALID_TILE) MarkTileDirtyByTile(old);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/** Highlight \a w by \a h tiles at the cursor.
|
|
|
/**
|
|
|
* Highlight \a w by \a h tiles at the cursor.
|
|
|
* @param w Width of the highlighted tiles rectangle.
|
|
|
* @param h Height of the highlighted tiles rectangle.
|
|
|
*/
|
|
|
void SetTileSelectSize(int w, int h)
|
|
|
{
|
|
|
_thd.new_size.x = w * TILE_SIZE;
|
|
|
_thd.new_size.y = h * TILE_SIZE;
|
|
|
_thd.new_outersize.x = 0;
|
|
|
_thd.new_outersize.y = 0;
|
|
|
}
|
|
|
|
|
|
void SetTileSelectBigSize(int ox, int oy, int sx, int sy)
|
|
|
{
|
|
|
_thd.offs.x = ox * TILE_SIZE;
|
|
|
_thd.offs.y = oy * TILE_SIZE;
|
|
|
_thd.new_outersize.x = sx * TILE_SIZE;
|
|
|
_thd.new_outersize.y = sy * TILE_SIZE;
|
|
|
}
|
|
|
|
|
|
/** returns the best autorail highlight type from map coordinates */
|
|
|
static HighLightStyle GetAutorailHT(int x, int y)
|
|
|
{
|
|
|
return HT_RAIL | _autorail_piece[x & TILE_UNIT_MASK][y & TILE_UNIT_MASK];
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* Updates tile highlighting for all cases.
|
|
|
* Uses _thd.selstart and _thd.selend and _thd.place_mode (set elsewhere) to determine _thd.pos and _thd.size
|
|
|
* Also drawstyle is determined. Uses _thd.new.* as a buffer and calls SetSelectionTilesDirty() twice,
|
|
|
* Once for the old and once for the new selection.
|
|
|
* _thd is TileHighlightData, found in viewport.h
|
|
|
*/
|
|
|
void UpdateTileSelection()
|
|
|
{
|
|
|
int x1;
|
|
|
int y1;
|
|
|
|
|
|
_thd.new_drawstyle = HT_NONE;
|
|
|
|
|
|
if (_thd.place_mode == HT_SPECIAL) {
|
|
|
x1 = _thd.selend.x;
|
|
|
y1 = _thd.selend.y;
|
|
|
if (x1 != -1) {
|
|
|
int x2 = _thd.selstart.x & ~TILE_UNIT_MASK;
|
|
|
int y2 = _thd.selstart.y & ~TILE_UNIT_MASK;
|
|
|
x1 &= ~TILE_UNIT_MASK;
|
|
|
y1 &= ~TILE_UNIT_MASK;
|
|
|
|
|
@@ -1984,233 +1991,237 @@ void UpdateTileSelection()
|
|
|
switch (_thd.place_mode & HT_DIR_MASK) {
|
|
|
case HT_DIR_X: _thd.new_drawstyle = HT_LINE | HT_DIR_X; break;
|
|
|
case HT_DIR_Y: _thd.new_drawstyle = HT_LINE | HT_DIR_Y; break;
|
|
|
|
|
|
case HT_DIR_HU:
|
|
|
case HT_DIR_HL:
|
|
|
_thd.new_drawstyle = (pt.x & TILE_UNIT_MASK) + (pt.y & TILE_UNIT_MASK) <= TILE_SIZE ? HT_LINE | HT_DIR_HU : HT_LINE | HT_DIR_HL;
|
|
|
break;
|
|
|
|
|
|
case HT_DIR_VL:
|
|
|
case HT_DIR_VR:
|
|
|
_thd.new_drawstyle = (pt.x & TILE_UNIT_MASK) > (pt.y & TILE_UNIT_MASK) ? HT_LINE | HT_DIR_VL : HT_LINE | HT_DIR_VR;
|
|
|
break;
|
|
|
|
|
|
default: NOT_REACHED();
|
|
|
}
|
|
|
_thd.selstart.x = x1 & ~TILE_UNIT_MASK;
|
|
|
_thd.selstart.y = y1 & ~TILE_UNIT_MASK;
|
|
|
break;
|
|
|
default:
|
|
|
NOT_REACHED();
|
|
|
break;
|
|
|
}
|
|
|
_thd.new_pos.x = x1 & ~TILE_UNIT_MASK;
|
|
|
_thd.new_pos.y = y1 & ~TILE_UNIT_MASK;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/* redraw selection */
|
|
|
if (_thd.drawstyle != _thd.new_drawstyle ||
|
|
|
_thd.pos.x != _thd.new_pos.x || _thd.pos.y != _thd.new_pos.y ||
|
|
|
_thd.size.x != _thd.new_size.x || _thd.size.y != _thd.new_size.y ||
|
|
|
_thd.outersize.x != _thd.new_outersize.x ||
|
|
|
_thd.outersize.y != _thd.new_outersize.y) {
|
|
|
/* clear the old selection? */
|
|
|
if (_thd.drawstyle) SetSelectionTilesDirty();
|
|
|
|
|
|
_thd.drawstyle = _thd.new_drawstyle;
|
|
|
_thd.pos = _thd.new_pos;
|
|
|
_thd.size = _thd.new_size;
|
|
|
_thd.outersize = _thd.new_outersize;
|
|
|
_thd.dirty = 0xff;
|
|
|
|
|
|
/* draw the new selection? */
|
|
|
if (_thd.new_drawstyle) SetSelectionTilesDirty();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/** Displays the measurement tooltips when selecting multiple tiles
|
|
|
/**
|
|
|
* Displays the measurement tooltips when selecting multiple tiles
|
|
|
* @param str String to be displayed
|
|
|
* @param paramcount number of params to deal with
|
|
|
* @param params (optional) up to 5 pieces of additional information that may be added to a tooltip
|
|
|
*/
|
|
|
static inline void ShowMeasurementTooltips(StringID str, uint paramcount, const uint64 params[])
|
|
|
{
|
|
|
if (!_settings_client.gui.measure_tooltip) return;
|
|
|
GuiShowTooltips(str, paramcount, params, TCC_LEFT_CLICK);
|
|
|
}
|
|
|
|
|
|
/** highlighting tiles while only going over them with the mouse */
|
|
|
void VpStartPlaceSizing(TileIndex tile, ViewportPlaceMethod method, ViewportDragDropSelectionProcess process)
|
|
|
{
|
|
|
_thd.select_method = method;
|
|
|
_thd.select_proc = process;
|
|
|
_thd.selend.x = TileX(tile) * TILE_SIZE;
|
|
|
_thd.selstart.x = TileX(tile) * TILE_SIZE;
|
|
|
_thd.selend.y = TileY(tile) * TILE_SIZE;
|
|
|
_thd.selstart.y = TileY(tile) * TILE_SIZE;
|
|
|
|
|
|
/* Needed so several things (road, autoroad, bridges, ...) are placed correctly.
|
|
|
* In effect, placement starts from the centre of a tile
|
|
|
*/
|
|
|
if (method == VPM_X_OR_Y || method == VPM_FIX_X || method == VPM_FIX_Y) {
|
|
|
_thd.selend.x += TILE_SIZE / 2;
|
|
|
_thd.selend.y += TILE_SIZE / 2;
|
|
|
_thd.selstart.x += TILE_SIZE / 2;
|
|
|
_thd.selstart.y += TILE_SIZE / 2;
|
|
|
}
|
|
|
|
|
|
if (_thd.place_mode == HT_RECT) {
|
|
|
_thd.place_mode = HT_SPECIAL;
|
|
|
_thd.next_drawstyle = HT_RECT;
|
|
|
} else if (_thd.place_mode & (HT_RAIL | HT_LINE)) {
|
|
|
_thd.place_mode = HT_SPECIAL;
|
|
|
_thd.next_drawstyle = _thd.drawstyle;
|
|
|
} else {
|
|
|
_thd.place_mode = HT_SPECIAL;
|
|
|
_thd.next_drawstyle = HT_POINT;
|
|
|
}
|
|
|
_special_mouse_mode = WSM_SIZING;
|
|
|
}
|
|
|
|
|
|
void VpSetPlaceSizingLimit(int limit)
|
|
|
{
|
|
|
_thd.sizelimit = limit;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* Highlights all tiles between a set of two tiles. Used in dock and tunnel placement
|
|
|
* @param from TileIndex of the first tile to highlight
|
|
|
* @param to TileIndex of the last tile to highlight */
|
|
|
void VpSetPresizeRange(TileIndex from, TileIndex to)
|
|
|
{
|
|
|
uint64 distance = DistanceManhattan(from, to) + 1;
|
|
|
|
|
|
_thd.selend.x = TileX(to) * TILE_SIZE;
|
|
|
_thd.selend.y = TileY(to) * TILE_SIZE;
|
|
|
_thd.selstart.x = TileX(from) * TILE_SIZE;
|
|
|
_thd.selstart.y = TileY(from) * TILE_SIZE;
|
|
|
_thd.next_drawstyle = HT_RECT;
|
|
|
|
|
|
/* show measurement only if there is any length to speak of */
|
|
|
if (distance > 1) ShowMeasurementTooltips(STR_MEASURE_LENGTH, 1, &distance);
|
|
|
}
|
|
|
|
|
|
static void VpStartPreSizing()
|
|
|
{
|
|
|
_thd.selend.x = -1;
|
|
|
_special_mouse_mode = WSM_PRESIZE;
|
|
|
}
|
|
|
|
|
|
/** returns information about the 2x1 piece to be build.
|
|
|
/**
|
|
|
* returns information about the 2x1 piece to be build.
|
|
|
* The lower bits (0-3) are the track type. */
|
|
|
static HighLightStyle Check2x1AutoRail(int mode)
|
|
|
{
|
|
|
int fxpy = _tile_fract_coords.x + _tile_fract_coords.y;
|
|
|
int sxpy = (_thd.selend.x & TILE_UNIT_MASK) + (_thd.selend.y & TILE_UNIT_MASK);
|
|
|
int fxmy = _tile_fract_coords.x - _tile_fract_coords.y;
|
|
|
int sxmy = (_thd.selend.x & TILE_UNIT_MASK) - (_thd.selend.y & TILE_UNIT_MASK);
|
|
|
|
|
|
switch (mode) {
|
|
|
default: NOT_REACHED();
|
|
|
case 0: // end piece is lower right
|
|
|
if (fxpy >= 20 && sxpy <= 12) return HT_DIR_HL;
|
|
|
if (fxmy < -3 && sxmy > 3) return HT_DIR_VR;
|
|
|
return HT_DIR_Y;
|
|
|
|
|
|
case 1:
|
|
|
if (fxmy > 3 && sxmy < -3) return HT_DIR_VL;
|
|
|
if (fxpy <= 12 && sxpy >= 20) return HT_DIR_HU;
|
|
|
return HT_DIR_Y;
|
|
|
|
|
|
case 2:
|
|
|
if (fxmy > 3 && sxmy < -3) return HT_DIR_VL;
|
|
|
if (fxpy >= 20 && sxpy <= 12) return HT_DIR_HL;
|
|
|
return HT_DIR_X;
|
|
|
|
|
|
case 3:
|
|
|
if (fxmy < -3 && sxmy > 3) return HT_DIR_VR;
|
|
|
if (fxpy <= 12 && sxpy >= 20) return HT_DIR_HU;
|
|
|
return HT_DIR_X;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/** Check if the direction of start and end tile should be swapped based on
|
|
|
/**
|
|
|
* Check if the direction of start and end tile should be swapped based on
|
|
|
* the dragging-style. Default directions are:
|
|
|
* in the case of a line (HT_RAIL, HT_LINE): DIR_NE, DIR_NW, DIR_N, DIR_E
|
|
|
* in the case of a rect (HT_RECT, HT_POINT): DIR_S, DIR_E
|
|
|
* For example dragging a rectangle area from south to north should be swapped to
|
|
|
* north-south (DIR_S) to obtain the same results with less code. This is what
|
|
|
* the return value signifies.
|
|
|
* @param style HighLightStyle dragging style
|
|
|
* @param start_tile start tile of drag
|
|
|
* @param end_tile end tile of drag
|
|
|
* @return boolean value which when true means start/end should be swapped */
|
|
|
static bool SwapDirection(HighLightStyle style, TileIndex start_tile, TileIndex end_tile)
|
|
|
{
|
|
|
uint start_x = TileX(start_tile);
|
|
|
uint start_y = TileY(start_tile);
|
|
|
uint end_x = TileX(end_tile);
|
|
|
uint end_y = TileY(end_tile);
|
|
|
|
|
|
switch (style & HT_DRAG_MASK) {
|
|
|
case HT_RAIL:
|
|
|
case HT_LINE: return (end_x > start_x || (end_x == start_x && end_y > start_y));
|
|
|
|
|
|
case HT_RECT:
|
|
|
case HT_POINT: return (end_x != start_x && end_y < start_y);
|
|
|
default: NOT_REACHED();
|
|
|
}
|
|
|
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
/** Calculates height difference between one tile and another.
|
|
|
/**
|
|
|
* Calculates height difference between one tile and another.
|
|
|
* Multiplies the result to suit the standard given by #TILE_HEIGHT_STEP.
|
|
|
*
|
|
|
* To correctly get the height difference we need the direction we are dragging
|
|
|
* in, as well as with what kind of tool we are dragging. For example a horizontal
|
|
|
* autorail tool that starts in bottom and ends at the top of a tile will need the
|
|
|
* maximum of SW, S and SE, N corners respectively. This is handled by the lookup table below
|
|
|
* See #_tileoffs_by_dir in map.cpp for the direction enums if you can't figure out the values yourself.
|
|
|
* @param style Highlighting style of the drag. This includes direction and style (autorail, rect, etc.)
|
|
|
* @param distance Number of tiles dragged, important for horizontal/vertical drags, ignored for others.
|
|
|
* @param start_tile Start tile of the drag operation.
|
|
|
* @param end_tile End tile of the drag operation.
|
|
|
* @return Height difference between two tiles. The tile measurement tool utilizes this value in its tooltip.
|
|
|
*/
|
|
|
static int CalcHeightdiff(HighLightStyle style, uint distance, TileIndex start_tile, TileIndex end_tile)
|
|
|
{
|
|
|
bool swap = SwapDirection(style, start_tile, end_tile);
|
|
|
uint h0, h1; // Start height and end height.
|
|
|
|
|
|
if (start_tile == end_tile) return 0;
|
|
|
if (swap) Swap(start_tile, end_tile);
|
|
|
|
|
|
switch (style & HT_DRAG_MASK) {
|
|
|
case HT_RECT: {
|
|
|
static const TileIndexDiffC heightdiff_area_by_dir[] = {
|
|
|
/* Start */ {1, 0}, /* Dragging east */ {0, 0}, // Dragging south
|
|
|
/* End */ {0, 1}, /* Dragging east */ {1, 1} // Dragging south
|
|
|
};
|
|
|
|
|
|
/* In the case of an area we can determine whether we were dragging south or
|
|
|
* east by checking the X-coordinates of the tiles */
|
|
|
byte style_t = (byte)(TileX(end_tile) > TileX(start_tile));
|
|
|
start_tile = TILE_ADD(start_tile, ToTileIndexDiff(heightdiff_area_by_dir[style_t]));
|
|
|
end_tile = TILE_ADD(end_tile, ToTileIndexDiff(heightdiff_area_by_dir[2 + style_t]));
|
|
|
}
|
|
|
/* FALL THROUGH */
|
|
|
case HT_POINT:
|
|
|
h0 = TileHeight(start_tile);
|
|
|
h1 = TileHeight(end_tile);
|
|
|
break;
|
|
|
default: { // All other types, this is mostly only line/autorail
|
|
|
static const HighLightStyle flip_style_direction[] = {
|
|
|
HT_DIR_X, HT_DIR_Y, HT_DIR_HL, HT_DIR_HU, HT_DIR_VR, HT_DIR_VL
|
|
|
};
|
|
|
static const TileIndexDiffC heightdiff_line_by_dir[] = {
|
|
|
/* Start */ {1, 0}, {1, 1}, /* HT_DIR_X */ {0, 1}, {1, 1}, // HT_DIR_Y
|
|
|
/* Start */ {1, 0}, {0, 0}, /* HT_DIR_HU */ {1, 0}, {1, 1}, // HT_DIR_HL
|
|
|
/* Start */ {1, 0}, {1, 1}, /* HT_DIR_VL */ {0, 1}, {1, 1}, // HT_DIR_VR
|
|
|
|