|
|
|
@ -58,14 +58,8 @@
|
|
|
|
|
*
|
|
|
|
|
*
|
|
|
|
|
* Rows are horizontal sections of the viewport, also half a tile wide.
|
|
|
|
|
* This time the nothern most tile on the map at height level 0 defines 0 and
|
|
|
|
|
* everything south of that has a positive number. In theory this works the
|
|
|
|
|
* same as for columns with the massive difference that due to the isometric
|
|
|
|
|
* projection the actual row where the tile is visible differs from the row
|
|
|
|
|
* where the tile would be if it were at height level 0. Strictly speaking,
|
|
|
|
|
* if you know the row of the tile at height level 0, then the row number
|
|
|
|
|
* where it is actually drawn is tile height / 2 lower than the row number
|
|
|
|
|
* of the same tile at height level 0.
|
|
|
|
|
* This time the nothern most tile on the map defines 0 and
|
|
|
|
|
* everything south of that has a positive number.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
#include "stdafx.h"
|
|
|
|
@ -1083,84 +1077,6 @@ draw_inner:
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Given a screen coordinate (x,y) as e.g. stored in _vd.dpi, this function
|
|
|
|
|
* returns the tile coordinate of the tile which would be painted at (x,y)
|
|
|
|
|
* if one assumes height zero at that position.
|
|
|
|
|
* @param x Some x screen coordinate
|
|
|
|
|
* @param y Some y screen coordinate
|
|
|
|
|
* @return Tile coordinate assuming height zero as described
|
|
|
|
|
*/
|
|
|
|
|
static inline Point GetTileCoordFromScreenCoord(int x, int y)
|
|
|
|
|
{
|
|
|
|
|
/* First convert from the screen coordinate system (where the width of tiles
|
|
|
|
|
* is twice their height) to the tile coordinate system. That means, turn
|
|
|
|
|
* around by 45 degrees and make the tiles quadratic. */
|
|
|
|
|
Point tile_coord = InverseRemapCoords(x, y);
|
|
|
|
|
|
|
|
|
|
/* Scale from a 16x16-grid to a 1x1-grid as returned by TileX/TileY. */
|
|
|
|
|
tile_coord.x /= (int)TILE_SIZE;
|
|
|
|
|
tile_coord.y /= (int)TILE_SIZE;
|
|
|
|
|
|
|
|
|
|
return tile_coord;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Assume a region, given by screen coordinates (x1,y1,x2,y2), as defined in _vd.dpi.
|
|
|
|
|
* This function basically takes (x1,y1) (i.e. the upper left corner of that region)
|
|
|
|
|
* and returns the tile coordinate of the tile, which would be painted at (x1,y1)
|
|
|
|
|
* if one assumes height zero at that position.
|
|
|
|
|
*
|
|
|
|
|
* However, in detail: Imagine tiles being split up into their upper left,upper right,
|
|
|
|
|
* etc. isometric sections. We return a tile where the upper left corner of the
|
|
|
|
|
* mentioned region is either in its lower right section or in a neighbor tile
|
|
|
|
|
* below / right of that section. By doing so, we want to enforce that we can
|
|
|
|
|
* travel to east or south from that point without leaving the region again.
|
|
|
|
|
*
|
|
|
|
|
* @param x Some x screen coordinate, x1 in terms of the description above
|
|
|
|
|
* @param y Some y screen coordinate, y1 in terms of the description above
|
|
|
|
|
* @return Upper left corner of the region as tile coordinates.
|
|
|
|
|
*/
|
|
|
|
|
static Point GetMinTileCoordsIgnoringHeight(int x, int y)
|
|
|
|
|
{
|
|
|
|
|
Point tile_coord = GetTileCoordFromScreenCoord(x, y);
|
|
|
|
|
|
|
|
|
|
/* Expand area to be painted in order to avoid situations
|
|
|
|
|
* where south or east of the to be painted point in dpi are tiles
|
|
|
|
|
* which will not be painted. */
|
|
|
|
|
tile_coord.y--;
|
|
|
|
|
|
|
|
|
|
return tile_coord;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Assume a region, given by screen coordinates (x1,y1,x2,y2), as defined in _vd.dpi.
|
|
|
|
|
* This function basically takes (x2,y2) (i.e. the lower right corner of that region)
|
|
|
|
|
* and returns the tile coordinate of the tile, which would be painted at (x2,y2)
|
|
|
|
|
* if one assumes height zero at that position.
|
|
|
|
|
*
|
|
|
|
|
* However, in detail: Imagine tiles being split up into their upper left,upper right,
|
|
|
|
|
* etc. isometric sections. We return a tile where the lower right corner of the
|
|
|
|
|
* mentioned region is either in its upper left section or in a neighbor tile
|
|
|
|
|
* above / left of that section. By doing so, we want to enforce that we can
|
|
|
|
|
* travel to north or west from that point without leaving the region again.
|
|
|
|
|
*
|
|
|
|
|
* @param x Some x screen coordinate, x2 in terms of the description above
|
|
|
|
|
* @param y Some y screen coordinate, y2 in terms of the description above
|
|
|
|
|
* @return Upper left corner of the region as tile coordinates.
|
|
|
|
|
*/
|
|
|
|
|
static Point GetMaxTileCoordsIgnoringHeight(int x, int y)
|
|
|
|
|
{
|
|
|
|
|
Point tile_coord = GetTileCoordFromScreenCoord(x, y);
|
|
|
|
|
|
|
|
|
|
/* Expand area to be painted to southeast in order to avoid situations
|
|
|
|
|
* where north or east of the given to be painted point in dpi are
|
|
|
|
|
* tiles which will not be repainted. */
|
|
|
|
|
tile_coord.y++;
|
|
|
|
|
|
|
|
|
|
return tile_coord;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Returns the y coordinate in the viewport coordinate system where the given
|
|
|
|
|
* tile is painted.
|
|
|
|
@ -1169,376 +1085,109 @@ static Point GetMaxTileCoordsIgnoringHeight(int x, int y)
|
|
|
|
|
*/
|
|
|
|
|
static int GetViewportY(Point tile)
|
|
|
|
|
{
|
|
|
|
|
return (tile.y * TILE_SIZE + tile.x * TILE_SIZE - GetTileMaxPixelZOutsideMap(tile.x, tile.y)) << ZOOM_LVL_SHIFT;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Given a tile coordinate as returned by TileX / TileY, this returns its column.
|
|
|
|
|
*
|
|
|
|
|
* @param tile_coord The coordinate of the tile.
|
|
|
|
|
* @return The column index.
|
|
|
|
|
* @ingroup vp_column_row
|
|
|
|
|
*/
|
|
|
|
|
static int GetTileColumnFromTileCoord(Point tile_coord)
|
|
|
|
|
{
|
|
|
|
|
return tile_coord.y - tile_coord.x;
|
|
|
|
|
/* Each increment in X or Y direction moves down by half a tile, i.e. TILE_PIXELS / 2. */
|
|
|
|
|
return (tile.y * (int)(TILE_PIXELS / 2) + tile.x * (int)(TILE_PIXELS / 2) - TilePixelHeightOutsideMap(tile.x, tile.y)) << ZOOM_LVL_SHIFT;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Returns the position of the tile at the northern end of the column of the
|
|
|
|
|
* given tile.
|
|
|
|
|
* @param tile Any tile.
|
|
|
|
|
* @return Position of the tile at the northern end of the column as described.
|
|
|
|
|
* @ingroup vp_column_row
|
|
|
|
|
*/
|
|
|
|
|
static Point GetNorthernEndOfColumn(Point tile)
|
|
|
|
|
{
|
|
|
|
|
Point northern_end;
|
|
|
|
|
|
|
|
|
|
if (tile.x < tile.y) {
|
|
|
|
|
northern_end.x = 0;
|
|
|
|
|
northern_end.y = tile.y - tile.x;
|
|
|
|
|
} else {
|
|
|
|
|
northern_end.x = tile.x - tile.y;
|
|
|
|
|
northern_end.y = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return northern_end;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Returns the position of the tile at the southern end of the column of the
|
|
|
|
|
* given tile, if it is within the given limit expressed in number of tiles
|
|
|
|
|
* @param tile Any tile.
|
|
|
|
|
* @param limit Number of tiles to go to south at most, if the southern end is
|
|
|
|
|
* further away, stop after that number of tiles
|
|
|
|
|
* @return Position of the tile at the soutern end of the column as described.
|
|
|
|
|
* @ingroup vp_column_row
|
|
|
|
|
*/
|
|
|
|
|
static Point GetSouthernEndOfColumnWithLimit(Point tile, uint limit)
|
|
|
|
|
{
|
|
|
|
|
Point distance_to_end;
|
|
|
|
|
distance_to_end.x = (int)MapMaxX() - tile.x;
|
|
|
|
|
distance_to_end.y = (int)MapMaxY() - tile.y;
|
|
|
|
|
|
|
|
|
|
Point southern_end;
|
|
|
|
|
if (distance_to_end.x < distance_to_end.y) {
|
|
|
|
|
int number_of_steps = min(limit, distance_to_end.x);
|
|
|
|
|
southern_end.x = tile.x + number_of_steps;
|
|
|
|
|
southern_end.y = tile.y + number_of_steps;
|
|
|
|
|
} else {
|
|
|
|
|
int number_of_steps = min(limit, distance_to_end.y);
|
|
|
|
|
southern_end.x = tile.x + number_of_steps;
|
|
|
|
|
southern_end.y = tile.y + number_of_steps;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return southern_end;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Returns the position of the tile at the southern end of the column of the
|
|
|
|
|
* given tile.
|
|
|
|
|
* @param tile Any tile.
|
|
|
|
|
* @return Position of the tile at the soutern end of the column as described.
|
|
|
|
|
* @ingroup vp_column_row
|
|
|
|
|
*/
|
|
|
|
|
static Point GetSouthernEndOfColumn(Point tile)
|
|
|
|
|
{
|
|
|
|
|
return GetSouthernEndOfColumnWithLimit(tile, UINT32_MAX);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Returns the tile exactly in the middle between two given tiles.
|
|
|
|
|
*
|
|
|
|
|
* @param tile Point upper_tile, any tile.
|
|
|
|
|
* @param tile Point lower_tile, any tile.
|
|
|
|
|
* @return The tile in the middle of Point upper_tile and Point lower_tile.
|
|
|
|
|
*/
|
|
|
|
|
static Point GetMiddleTile(Point upper_tile, Point lower_tile)
|
|
|
|
|
{
|
|
|
|
|
Point middle_tile;
|
|
|
|
|
middle_tile.x = (lower_tile.x + upper_tile.x) / 2;
|
|
|
|
|
middle_tile.y = (lower_tile.y + upper_tile.y) / 2;
|
|
|
|
|
return middle_tile;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Given a tile coordinate assuming height zero, this returns the row actually
|
|
|
|
|
* painted at this tile coordinate if one recognizes height.
|
|
|
|
|
*
|
|
|
|
|
* The problem concerning this calculation is that we have not enough
|
|
|
|
|
* information to calculate this in one closed formula. Which row we
|
|
|
|
|
* search rather depends on the height distribution on the map. So
|
|
|
|
|
* we have to search.
|
|
|
|
|
*
|
|
|
|
|
* First, the searched tile may be located outside map. Then, we know
|
|
|
|
|
* that we are not too far outside map, so we can step tile by tile,
|
|
|
|
|
* starting at the given tile, until we have passed the searched tile.
|
|
|
|
|
*
|
|
|
|
|
* If the searched tile is inside map, searching is more difficult. A
|
|
|
|
|
* linear search on some thousand tiles would be not that efficient. But,
|
|
|
|
|
* we can solve the problem by interval intersection. We know for sure,
|
|
|
|
|
* that the searched tile is south of the given tile, simply because
|
|
|
|
|
* mountains of height > 0 (and we have only such mountains) are always
|
|
|
|
|
* painted north of their tile. So we choose a tile half way between the
|
|
|
|
|
* given tile and the southern end of the map, have a look whether it is
|
|
|
|
|
* north or south of the given position, and intersect again. Until
|
|
|
|
|
* our interval has length 1, then we take the upper one.
|
|
|
|
|
*
|
|
|
|
|
* @param viewport_y The viewport y corresponding to tile, if one assumes height zero for that tile
|
|
|
|
|
* @param tile Some tile coordinate assuming height zero.
|
|
|
|
|
* @param bridge_correct If true, consider bridges south of the calculated tile, and if the bridge
|
|
|
|
|
* visually intersect the calculated tile, shift it southwards.
|
|
|
|
|
* @return The row which is painted at this coordinate, according to the discussion above.
|
|
|
|
|
* @ingroup vp_column_row
|
|
|
|
|
* Add the landscape to the viewport, i.e. all ground tiles and buildings.
|
|
|
|
|
*/
|
|
|
|
|
int GetRowAtTile(int viewport_y, Point tile, bool bridge_correct)
|
|
|
|
|
static void ViewportAddLandscape()
|
|
|
|
|
{
|
|
|
|
|
Point northern_tile = GetNorthernEndOfColumn(tile);
|
|
|
|
|
Point southern_tile = GetSouthernEndOfColumn(tile);
|
|
|
|
|
|
|
|
|
|
int northern_tile_viewport_y = GetViewportY(northern_tile);
|
|
|
|
|
int southern_tile_viewport_y = GetViewportY(southern_tile);
|
|
|
|
|
|
|
|
|
|
if (northern_tile_viewport_y >= viewport_y) {
|
|
|
|
|
/* We are north of the map, search tile by tile with direction north. */
|
|
|
|
|
while (northern_tile_viewport_y >= viewport_y) {
|
|
|
|
|
northern_tile.x--;
|
|
|
|
|
northern_tile.y--;
|
|
|
|
|
northern_tile_viewport_y = GetViewportY(northern_tile);
|
|
|
|
|
}
|
|
|
|
|
return northern_tile.x + northern_tile.y;
|
|
|
|
|
}
|
|
|
|
|
assert(_vd.dpi.top <= _vd.dpi.top + _vd.dpi.height);
|
|
|
|
|
assert(_vd.dpi.left <= _vd.dpi.left + _vd.dpi.width);
|
|
|
|
|
|
|
|
|
|
if (southern_tile_viewport_y <= viewport_y) {
|
|
|
|
|
/* We are south of the map, search tile by tile with direction south. */
|
|
|
|
|
while (southern_tile_viewport_y <= viewport_y) {
|
|
|
|
|
southern_tile.x++;
|
|
|
|
|
southern_tile.y++;
|
|
|
|
|
southern_tile_viewport_y = GetViewportY(southern_tile);
|
|
|
|
|
}
|
|
|
|
|
return southern_tile.x + southern_tile.y;
|
|
|
|
|
}
|
|
|
|
|
Point upper_left = InverseRemapCoords(_vd.dpi.left, _vd.dpi.top);
|
|
|
|
|
Point upper_right = InverseRemapCoords(_vd.dpi.left + _vd.dpi.width, _vd.dpi.top);
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* We are inside the map. The searched tile is at most
|
|
|
|
|
* <maximum heightlevel / 4> tiles south of the given tile (as one tile
|
|
|
|
|
* painted on the screen needs as much vertical space as painting a tile
|
|
|
|
|
* by 4 heightlevels ascended). Add one to avoid rounding errors to the
|
|
|
|
|
* wrong side.
|
|
|
|
|
*
|
|
|
|
|
* Invariant in the code below: The searched tile shown at viewport_y
|
|
|
|
|
* always is between upper_tile and lower_tile.
|
|
|
|
|
/* Transformations between tile coordinates and viewport rows/columns: See vp_column_row
|
|
|
|
|
* column = y - x
|
|
|
|
|
* row = x + y
|
|
|
|
|
* x = (row - column) / 2
|
|
|
|
|
* y = (row + column) / 2
|
|
|
|
|
* Note: (row, columns) pairs are only valid, if they are both even or both odd.
|
|
|
|
|
*/
|
|
|
|
|
Point upper_tile = tile;
|
|
|
|
|
Point lower_tile = GetSouthernEndOfColumnWithLimit(upper_tile, _settings_game.construction.max_heightlevel / 4 + 1);
|
|
|
|
|
int middle_bound;
|
|
|
|
|
|
|
|
|
|
do {
|
|
|
|
|
Point middle_tile = GetMiddleTile(upper_tile, lower_tile);
|
|
|
|
|
middle_bound = GetViewportY(middle_tile);
|
|
|
|
|
/* Columns overlap with neighbouring columns by a half tile.
|
|
|
|
|
* - Left column is column of upper_left (rounded down) and one column to the left.
|
|
|
|
|
* - Right column is column of upper_right (rounded up) and one column to the right.
|
|
|
|
|
* Note: Integer-division does not round down for negative numbers, so ensure rounding with another increment/decrement.
|
|
|
|
|
*/
|
|
|
|
|
int left_column = (upper_left.y - upper_left.x) / (int)TILE_SIZE - 2;
|
|
|
|
|
int right_column = (upper_right.y - upper_right.x) / (int)TILE_SIZE + 2;
|
|
|
|
|
|
|
|
|
|
if (middle_bound >= viewport_y) {
|
|
|
|
|
/* The tile shown at viewport_y is somewhere in the upper half of
|
|
|
|
|
* the currently observed section. */
|
|
|
|
|
lower_tile = middle_tile;
|
|
|
|
|
} else {
|
|
|
|
|
/* The tile shown at viewport_y is somewhere in the lower half of
|
|
|
|
|
* the currently observed section. */
|
|
|
|
|
upper_tile = middle_tile;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
while (lower_tile.y - upper_tile.y > 1);
|
|
|
|
|
int potential_bridge_height = ZOOM_LVL_BASE * TILE_HEIGHT * _settings_game.construction.max_bridge_height;
|
|
|
|
|
|
|
|
|
|
/* Now our interval has length 1, so only contains two tiles, and we take the upper one.
|
|
|
|
|
* However, there is one problem left: Tiles being located southwards, containing a high bridge.
|
|
|
|
|
* They may, though not high enough in terms of landscape, intersect the drawing area with parts
|
|
|
|
|
* of the bridge.
|
|
|
|
|
* Luckily, there is a guaranteed upper bound for bridge height, thus we know how far we have to
|
|
|
|
|
* search southwards whether such a bridge exists.
|
|
|
|
|
/* Rows overlap with neighbouring rows by a half tile.
|
|
|
|
|
* The first row that could possibly be visible is the row above upper_left (if it is at height 0).
|
|
|
|
|
* Due to integer-division not rounding down for negative numbers, we need another decrement.
|
|
|
|
|
*/
|
|
|
|
|
int correction_step = 0;
|
|
|
|
|
if (bridge_correct) {
|
|
|
|
|
/* Calculate, how many tiles below upper_tile, a worst case bridge intersecting upper_tile in
|
|
|
|
|
* terms of painting can be located. Lets inspect that formula in detail:
|
|
|
|
|
* ... - 5: The magic constant near the beginning of ViewportAddLandscape accounts for 5 harmless heightlevels a bridge can have. Thus subtract them.
|
|
|
|
|
* ... / 2: Four heightlevels account for one tile height. On the other hand, if landscape ascends from upper_tile southwards, this can account for
|
|
|
|
|
* as many additional heightlevels as we step southwards. In combination: A division by two gains the number of tiles to step southwards.
|
|
|
|
|
* ... + 1: Avoid rounding errors, and fall back to the safe side.
|
|
|
|
|
*/
|
|
|
|
|
int worst_case_steps_southwards = max(0, ((int)_settings_game.construction.max_bridge_height - 5) / 2 + 1);
|
|
|
|
|
for (int n = 0; n < worst_case_steps_southwards; n++) {
|
|
|
|
|
TileIndex potential_bridge_tile = TileXY(upper_tile.x + n, upper_tile.y + n);
|
|
|
|
|
if (IsValidTile(potential_bridge_tile) && IsBridgeAbove(potential_bridge_tile)) {
|
|
|
|
|
/* There is a bridge. */
|
|
|
|
|
TileIndex bridge_start = GetNorthernBridgeEnd(potential_bridge_tile);
|
|
|
|
|
int bridge_height = GetBridgeHeight(bridge_start);
|
|
|
|
|
int upper_tile_height = GetTileZ(TileXY(upper_tile.x, upper_tile.y));
|
|
|
|
|
|
|
|
|
|
/* Start at the bridge level, descend by the number of heightlevels equivalent to our steps southwards (in worst case), subtract the harmless
|
|
|
|
|
* bridge heightlevels, and compare whether we are still above the height of the upper_tile. If yes, we need to paint that tile, to avoid glitches.
|
|
|
|
|
*/
|
|
|
|
|
if (bridge_height - 2 * n - 1 > upper_tile_height) {
|
|
|
|
|
correction_step = n;
|
|
|
|
|
}
|
|
|
|
|
int row = (upper_left.x + upper_left.y) / (int)TILE_SIZE - 2;
|
|
|
|
|
bool last_row = false;
|
|
|
|
|
for (; !last_row; row++) {
|
|
|
|
|
last_row = true;
|
|
|
|
|
for (int column = left_column; column <= right_column; column++) {
|
|
|
|
|
/* Valid row/column? */
|
|
|
|
|
if ((row + column) % 2 != 0) continue;
|
|
|
|
|
|
|
|
|
|
Point tilecoord;
|
|
|
|
|
tilecoord.x = (row - column) / 2;
|
|
|
|
|
tilecoord.y = (row + column) / 2;
|
|
|
|
|
assert(column == tilecoord.y - tilecoord.x);
|
|
|
|
|
assert(row == tilecoord.y + tilecoord.x);
|
|
|
|
|
|
|
|
|
|
TileType tile_type;
|
|
|
|
|
TileInfo tile_info;
|
|
|
|
|
_cur_ti = &tile_info;
|
|
|
|
|
tile_info.x = tilecoord.x * TILE_SIZE; // FIXME tile_info should use signed integers
|
|
|
|
|
tile_info.y = tilecoord.y * TILE_SIZE;
|
|
|
|
|
|
|
|
|
|
if (IsInsideBS(tilecoord.x, 0, MapMaxX()) && IsInsideBS(tilecoord.y, 0, MapMaxY())) {
|
|
|
|
|
/* We are inside the map => paint landscape. */
|
|
|
|
|
tile_info.tile = TileXY(tilecoord.x, tilecoord.y);
|
|
|
|
|
tile_info.tileh = GetTilePixelSlope(tile_info.tile, &tile_info.z);
|
|
|
|
|
tile_type = GetTileType(tile_info.tile);
|
|
|
|
|
} else {
|
|
|
|
|
/* We are outside the map => paint black. */
|
|
|
|
|
tile_info.tile = INVALID_TILE;
|
|
|
|
|
tile_info.tileh = GetTilePixelSlopeOutsideMap(tilecoord.x, tilecoord.y, &tile_info.z);
|
|
|
|
|
tile_type = MP_VOID;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* The biggest recorded correction_step defines, which tile we actually return. */
|
|
|
|
|
upper_tile.x += correction_step;
|
|
|
|
|
upper_tile.y += correction_step;
|
|
|
|
|
|
|
|
|
|
/* Returns its row. */
|
|
|
|
|
return upper_tile.x + upper_tile.y;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Returns the bottom tile of the column of upper_tile shown on the viewport,
|
|
|
|
|
* given upper_tile and the lower right tile shown on the viewport.
|
|
|
|
|
*
|
|
|
|
|
* @param upper_tile Sny tile inside the map.
|
|
|
|
|
* @param lower_right_tile The tile shown at the southeast edge of the viewport
|
|
|
|
|
* (ignoring height). Note that this tile may be located
|
|
|
|
|
* northeast of the upper_tile, because upper_tile is usually
|
|
|
|
|
* calculated by shifting a tile southwards until we reach
|
|
|
|
|
* the northern map border.
|
|
|
|
|
* @return The lowest existing tile located in the column defined by upper_tile,
|
|
|
|
|
* which is in the same row as lower_right_tile or above that row
|
|
|
|
|
* If lower_right_tile was northeast of upper_tile, (-1,-1) is returned.
|
|
|
|
|
* @ingroup vp_column_row
|
|
|
|
|
*/
|
|
|
|
|
static Point GetBottomTileOfColumn(Point upper_tile, Point lower_right_tile)
|
|
|
|
|
{
|
|
|
|
|
int upper_row = upper_tile.x + upper_tile.y;
|
|
|
|
|
int lower_row = lower_right_tile.x + lower_right_tile.y;
|
|
|
|
|
|
|
|
|
|
assert(upper_row <= lower_row);
|
|
|
|
|
|
|
|
|
|
int number_of_rows = lower_row - upper_row;
|
|
|
|
|
|
|
|
|
|
if (number_of_rows % 2 != 0) {
|
|
|
|
|
/* Avoid 0.5 being rounded down to zero; painting too much is better than
|
|
|
|
|
* painting too little. */
|
|
|
|
|
number_of_rows++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Point bottom_tile;
|
|
|
|
|
bottom_tile.x = upper_tile.x + number_of_rows / 2;
|
|
|
|
|
bottom_tile.y = upper_tile.y + number_of_rows / 2;
|
|
|
|
|
|
|
|
|
|
int bottom_row = bottom_tile.x + bottom_tile.y;
|
|
|
|
|
|
|
|
|
|
assert(bottom_row >= lower_row);
|
|
|
|
|
|
|
|
|
|
return bottom_tile;
|
|
|
|
|
}
|
|
|
|
|
int viewport_y = GetViewportY(tilecoord);
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Add the landscape to the viewport, i.e. all ground tiles and buildings.
|
|
|
|
|
*/
|
|
|
|
|
static void ViewportAddLandscape()
|
|
|
|
|
{
|
|
|
|
|
assert(_vd.dpi.top <= _vd.dpi.top + _vd.dpi.height);
|
|
|
|
|
assert(_vd.dpi.left <= _vd.dpi.left + _vd.dpi.width);
|
|
|
|
|
if (viewport_y + MAX_TILE_EXTENT_BOTTOM < _vd.dpi.top) {
|
|
|
|
|
/* The tile in this column is not visible yet.
|
|
|
|
|
* Tiles in other columns may be visible, but we need more rows in any case. */
|
|
|
|
|
last_row = false;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* The upper and lower edge of the viewport part to paint. Add some number
|
|
|
|
|
* of pixels to the lower end in order to ensure that we also take tiles
|
|
|
|
|
* south of the given area, but with high buildings intersecting the area.
|
|
|
|
|
* Subtract some pixels from the upper end in order to avoid glitches at the
|
|
|
|
|
* upper end of the top be painted area. */
|
|
|
|
|
int viewport_top = _vd.dpi.top - 16;
|
|
|
|
|
int viewport_bottom = _vd.dpi.top + _vd.dpi.height + 116;
|
|
|
|
|
|
|
|
|
|
/* First get the position of the tile at the upper left / lower right edge,
|
|
|
|
|
* for now ignoring the height. (i.e. assuming height zero.) */
|
|
|
|
|
Point upper_left_tile = GetMinTileCoordsIgnoringHeight(_vd.dpi.left, viewport_top);
|
|
|
|
|
Point lower_right_tile = GetMaxTileCoordsIgnoringHeight(_vd.dpi.left + _vd.dpi.width, viewport_bottom);
|
|
|
|
|
|
|
|
|
|
/* Calculate the bounding columns. We won't need to draw anything
|
|
|
|
|
* left / right of them. */
|
|
|
|
|
int left_column = GetTileColumnFromTileCoord(upper_left_tile);
|
|
|
|
|
/* Correction to avoid glitches when approaching the left edge of the map. */
|
|
|
|
|
left_column--;
|
|
|
|
|
int right_column = GetTileColumnFromTileCoord(lower_right_tile);
|
|
|
|
|
right_column++;
|
|
|
|
|
|
|
|
|
|
/* For each column, calculate the top and the bottom row. These are the
|
|
|
|
|
* bounding rows for that specific column. */
|
|
|
|
|
int *top_row = AllocaM(int, right_column - left_column + 1); // Pre-allocate memory for visual studio/express to be able to compile.
|
|
|
|
|
int *bottom_row = AllocaM(int, right_column - left_column + 1); // Pre-allocate memory for visual studio/express to be able to compile.
|
|
|
|
|
int min_top_row = MapMaxX() + MapMaxY();
|
|
|
|
|
int max_bottom_row = 0;
|
|
|
|
|
Point top_tile_of_column = upper_left_tile;
|
|
|
|
|
|
|
|
|
|
/* And now for each column, determine the top and the bottom row we must paint. */
|
|
|
|
|
bool south_east_direction = false;
|
|
|
|
|
for (int x = left_column; x <= right_column; x++) {
|
|
|
|
|
Point bottom_tile_of_column = GetBottomTileOfColumn(top_tile_of_column, lower_right_tile);
|
|
|
|
|
|
|
|
|
|
/* And then actually find out the top and the bottom row. Note that
|
|
|
|
|
* top_tile_of_column and bottom_tile_of_column may be outside the map here.
|
|
|
|
|
* This possibility is needed, otherwise we couldn't paint the black area
|
|
|
|
|
* outside the map (and in particular the edge of map) properly.
|
|
|
|
|
* Subtract three / add one to avoid glitches. */
|
|
|
|
|
top_row[x - left_column] = GetRowAtTile(viewport_top, top_tile_of_column, false);
|
|
|
|
|
|
|
|
|
|
top_row[x - left_column] -= 3;
|
|
|
|
|
bottom_row[x - left_column] = GetRowAtTile(viewport_bottom, bottom_tile_of_column, true);
|
|
|
|
|
bottom_row[x - left_column]++;
|
|
|
|
|
|
|
|
|
|
/* We never paint things in rows < min_top_row or > max_bottom_row. */
|
|
|
|
|
min_top_row = min(min_top_row, top_row[x - left_column]);
|
|
|
|
|
max_bottom_row = max(max_bottom_row, bottom_row[x - left_column]);
|
|
|
|
|
|
|
|
|
|
/* Go to next column in the east. */
|
|
|
|
|
if (south_east_direction) {
|
|
|
|
|
top_tile_of_column.y++;
|
|
|
|
|
} else {
|
|
|
|
|
top_tile_of_column.x--;
|
|
|
|
|
}
|
|
|
|
|
int min_visible_height = viewport_y - (_vd.dpi.top + _vd.dpi.height);
|
|
|
|
|
bool tile_visible = min_visible_height <= 0;
|
|
|
|
|
|
|
|
|
|
/* Switch between directions southeast and northeast. */
|
|
|
|
|
south_east_direction = !south_east_direction;
|
|
|
|
|
}
|
|
|
|
|
if (tile_type != MP_VOID) {
|
|
|
|
|
/* Is tile with buildings visible? */
|
|
|
|
|
if (min_visible_height < MAX_TILE_EXTENT_TOP) tile_visible = true;
|
|
|
|
|
|
|
|
|
|
for (int row = min_top_row; row <= max_bottom_row; row++) {
|
|
|
|
|
for (int column = left_column; column <= right_column; column++) {
|
|
|
|
|
/* For each column, we only paint the interval top_row .. bottom_row.
|
|
|
|
|
* Due to the division by two below, even and odd values of row + column map to
|
|
|
|
|
* the same (x,y) combinations. Thus, we only paint one of them. */
|
|
|
|
|
if (((row + column) % 2 == 0) &&
|
|
|
|
|
(top_row[column - left_column] <= row) &&
|
|
|
|
|
(row <= bottom_row[column - left_column])) {
|
|
|
|
|
TileType tile_type;
|
|
|
|
|
TileInfo tile_info;
|
|
|
|
|
_cur_ti = &tile_info;
|
|
|
|
|
|
|
|
|
|
/* column = y - x; row = x + y; now solve the equation system
|
|
|
|
|
* for x and y. */
|
|
|
|
|
int x = (row - column) / 2;
|
|
|
|
|
int y = (row + column) / 2;
|
|
|
|
|
tile_info.x = x;
|
|
|
|
|
tile_info.y = y;
|
|
|
|
|
|
|
|
|
|
/* For some strange reason, those fields inside tile_info are uints. However,
|
|
|
|
|
* in the old code their copies in an int variable where compared against zero. */
|
|
|
|
|
if (0 < x && x < (int)MapMaxX() && 0 < y && y < (int)MapMaxY()) {
|
|
|
|
|
/* We are inside the map => paint landscape. */
|
|
|
|
|
tile_info.tile = TileXY(tile_info.x, tile_info.y);
|
|
|
|
|
tile_info.tileh = GetTilePixelSlope(tile_info.tile, &tile_info.z);
|
|
|
|
|
tile_type = GetTileType(tile_info.tile);
|
|
|
|
|
} else {
|
|
|
|
|
/* We are outside the map => paint black. */
|
|
|
|
|
tile_info.tile = INVALID_TILE;
|
|
|
|
|
tile_info.tileh = GetTilePixelSlopeOutsideMap(tile_info.x, tile_info.y, &tile_info.z);
|
|
|
|
|
tile_type = MP_VOID;
|
|
|
|
|
if (IsBridgeAbove(tile_info.tile)) {
|
|
|
|
|
/* Is the bridge visible? */
|
|
|
|
|
TileIndex bridge_tile = GetNorthernBridgeEnd(tile_info.tile);
|
|
|
|
|
int bridge_height = ZOOM_LVL_BASE * (GetBridgePixelHeight(bridge_tile) - TilePixelHeight(tile_info.tile));
|
|
|
|
|
if (min_visible_height < bridge_height + MAX_TILE_EXTENT_TOP) tile_visible = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Scale to 16x16 tiles, needed for the drawing procedures called below. */
|
|
|
|
|
tile_info.x *= TILE_SIZE;
|
|
|
|
|
tile_info.y *= TILE_SIZE;
|
|
|
|
|
/* Would a higher bridge on a more southern tile be visible?
|
|
|
|
|
* If yes, we need to loop over more rows to possibly find one. */
|
|
|
|
|
if (min_visible_height < potential_bridge_height + MAX_TILE_EXTENT_TOP) last_row = false;
|
|
|
|
|
} else {
|
|
|
|
|
/* Outside of map. If we are on the north border of the map, there may still be a bridge visible,
|
|
|
|
|
* so we need to loop over more rows to possibly find one. */
|
|
|
|
|
if ((tilecoord.x <= 0 || tilecoord.y <= 0) && min_visible_height < potential_bridge_height + MAX_TILE_EXTENT_TOP) last_row = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (tile_visible) {
|
|
|
|
|
last_row = false;
|
|
|
|
|
_vd.foundation_part = FOUNDATION_PART_NONE;
|
|
|
|
|
_vd.foundation[0] = -1;
|
|
|
|
|
_vd.foundation[1] = -1;
|
|
|
|
|