Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix #6583: Rework Tileheight handling (patch by adf88) #7061

Merged
merged 6 commits into from Jan 24, 2019
81 changes: 81 additions & 0 deletions src/landscape.cpp
Expand Up @@ -89,6 +89,70 @@ extern const byte _slope_to_sprite_offset[32] = {
*/
static SnowLine *_snow_line = NULL;

/**
* Map 2D viewport or smallmap coordinate to 3D world or tile coordinate.
* Function takes into account height of tiles and foundations.
*
* @param x X viewport 2D coordinate.
* @param y Y viewport 2D coordinate.
* @param clamp_to_map Clamp the coordinate outside of the map to the closest, non-void tile within the map.
* @param[out] clamped Whether coordinates were clamped.
* @return 3D world coordinate of point visible at the given screen coordinate (3D perspective).
*
* @note Inverse of #RemapCoords2 function. Smaller values may get rounded.
* @see InverseRemapCoords
*/
Point InverseRemapCoords2(int x, int y, bool clamp_to_map, bool *clamped)
{
if (clamped != NULL) *clamped = false; // Not clamping yet.

/* Initial x/y world coordinate is like if the landscape
* was completely flat on height 0. */
Point pt = InverseRemapCoords(x, y);

const uint min_coord = _settings_game.construction.freeform_edges ? TILE_SIZE : 0;
const uint max_x = MapMaxX() * TILE_SIZE - 1;
const uint max_y = MapMaxY() * TILE_SIZE - 1;

if (clamp_to_map) {
/* Bring the coordinates near to a valid range. At the top we allow a number
* of extra tiles. This is mostly due to the tiles on the north side of
* the map possibly being drawn higher due to the extra height levels. */
int extra_tiles = CeilDiv(_settings_game.construction.max_heightlevel * TILE_HEIGHT, TILE_PIXELS);
Point old_pt = pt;
pt.x = Clamp(pt.x, -extra_tiles * TILE_SIZE, max_x);
pt.y = Clamp(pt.y, -extra_tiles * TILE_SIZE, max_y);
if (clamped != NULL) *clamped = (pt.x != old_pt.x) || (pt.y != old_pt.y);
}

/* Now find the Z-world coordinate by fix point iteration.
* This is a bit tricky because the tile height is non-continuous at foundations.
* The clicked point should be approached from the back, otherwise there are regions that are not clickable.
* (FOUNDATION_HALFTILE_LOWER on SLOPE_STEEP_S hides north halftile completely)
* So give it a z-malus of 4 in the first iterations. */
int z = 0;
if (clamp_to_map) {
for (int i = 0; i < 5; i++) z = GetSlopePixelZ(Clamp(pt.x + max(z, 4) - 4, min_coord, max_x), Clamp(pt.y + max(z, 4) - 4, min_coord, max_y)) / 2;
for (int m = 3; m > 0; m--) z = GetSlopePixelZ(Clamp(pt.x + max(z, m) - m, min_coord, max_x), Clamp(pt.y + max(z, m) - m, min_coord, max_y)) / 2;
for (int i = 0; i < 5; i++) z = GetSlopePixelZ(Clamp(pt.x + z, min_coord, max_x), Clamp(pt.y + z, min_coord, max_y)) / 2;
} else {
for (int i = 0; i < 5; i++) z = GetSlopePixelZOutsideMap(pt.x + max(z, 4) - 4, pt.y + max(z, 4) - 4) / 2;
for (int m = 3; m > 0; m--) z = GetSlopePixelZOutsideMap(pt.x + max(z, m) - m, pt.y + max(z, m) - m) / 2;
for (int i = 0; i < 5; i++) z = GetSlopePixelZOutsideMap(pt.x + z, pt.y + z ) / 2;
}

pt.x += z;
pt.y += z;
if (clamp_to_map) {
Point old_pt = pt;
pt.x = Clamp(pt.x, min_coord, max_x);
pt.y = Clamp(pt.y, min_coord, max_y);
if (clamped != NULL) *clamped = *clamped || (pt.x != old_pt.x) || (pt.y != old_pt.y);
}

return pt;
}

/**
* Applies a foundation to a slope.
*
Expand Down Expand Up @@ -284,6 +348,23 @@ int GetSlopePixelZ(int x, int y)
return _tile_type_procs[GetTileType(tile)]->get_slope_z_proc(tile, x, y);
}

/**
* Return world \c z coordinate of a given point of a tile,
* also for tiles outside the map (virtual "black" tiles).
*
* @param x World X coordinate in tile "units", may be ouside the map.
* @param y World Y coordinate in tile "units", may be ouside the map.
* @return World Z coordinate at tile ground level, including slopes and foundations.
*/
int GetSlopePixelZOutsideMap(int x, int y)
{
if (IsInsideBS(x, 0, MapSizeX() * TILE_SIZE) && IsInsideBS(y, 0, MapSizeY() * TILE_SIZE)) {
return GetSlopePixelZ(x, y);
} else {
return _tile_type_procs[MP_VOID]->get_slope_z_proc(INVALID_TILE, x, y);
}
}

/**
* Determine the Z height of a corner relative to TileZ.
*
Expand Down
4 changes: 4 additions & 0 deletions src/landscape.h
Expand Up @@ -40,6 +40,7 @@ Slope GetFoundationSlope(TileIndex tile, int *z = NULL);

uint GetPartialPixelZ(int x, int y, Slope corners);
int GetSlopePixelZ(int x, int y);
int GetSlopePixelZOutsideMap(int x, int y);
void GetSlopePixelZOnEdge(Slope tileh, DiagDirection edge, int *z1, int *z2);

/**
Expand Down Expand Up @@ -108,13 +109,16 @@ static inline Point RemapCoords2(int x, int y)
* @param y Y coordinate of the 2D coordinate.
* @return X and Y components of equivalent world or tile coordinate.
* @note Inverse of #RemapCoords function. Smaller values may get rounded.
* @see InverseRemapCoords2
*/
static inline Point InverseRemapCoords(int x, int y)
{
Point pt = {(y * 2 - x) >> (2 + ZOOM_LVL_SHIFT), (y * 2 + x) >> (2 + ZOOM_LVL_SHIFT)};
return pt;
}

Point InverseRemapCoords2(int x, int y, bool clamp_to_map = false, bool *clamped = NULL);

uint ApplyFoundationToSlope(Foundation f, Slope *s);
/**
* Applies a foundation to a slope.
Expand Down
6 changes: 3 additions & 3 deletions src/smallmap_gui.cpp
Expand Up @@ -923,8 +923,8 @@ void SmallMapWindow::DrawMapIndicators() const
/* Find main viewport. */
const ViewPort *vp = FindWindowById(WC_MAIN_WINDOW, 0)->viewport;

Point upper_left_smallmap_coord = TranslateXYToTileCoord(vp, vp->left, vp->top, false);
Point lower_right_smallmap_coord = TranslateXYToTileCoord(vp, vp->left + vp->width - 1, vp->top + vp->height - 1, false);
Point upper_left_smallmap_coord = InverseRemapCoords2(vp->virtual_left, vp->virtual_top);
Point lower_right_smallmap_coord = InverseRemapCoords2(vp->virtual_left + vp->virtual_width - 1, vp->virtual_top + vp->virtual_height - 1);

Point upper_left = this->RemapTile(upper_left_smallmap_coord.x / (int)TILE_SIZE, upper_left_smallmap_coord.y / (int)TILE_SIZE);
upper_left.x -= this->subscroll;
Expand Down Expand Up @@ -1645,7 +1645,7 @@ void SmallMapWindow::SetNewScroll(int sx, int sy, int sub)
void SmallMapWindow::SmallMapCenterOnCurrentPos()
{
const ViewPort *vp = FindWindowById(WC_MAIN_WINDOW, 0)->viewport;
Point viewport_center = TranslateXYToTileCoord(vp, vp->left + vp->width / 2, vp->top + vp->height / 2);
Point viewport_center = InverseRemapCoords2(vp->virtual_left + vp->virtual_width / 2, vp->virtual_top + vp->virtual_height / 2);

int sub;
const NWidgetBase *wid = this->GetWidget<NWidgetBase>(WID_SM_MAP);
Expand Down
93 changes: 8 additions & 85 deletions src/terraform_cmd.cpp
Expand Up @@ -309,6 +309,14 @@ CommandCost CmdTerraformLand(TileIndex tile, DoCommandFlag flags, uint32 p1, uin
}

if (flags & DC_EXEC) {
/* Mark affected areas dirty. */
for (TileIndexSet::const_iterator it = ts.dirty_tiles.begin(); it != ts.dirty_tiles.end(); it++) {
MarkTileDirtyByTile(*it);
TileIndexToHeightMap::const_iterator new_height = ts.tile_to_new_height.find(tile);
if (new_height != ts.tile_to_new_height.end()) continue;
MarkTileDirtyByTile(*it, 0, new_height->second);
}

/* change the height */
for (TileIndexToHeightMap::const_iterator it = ts.tile_to_new_height.begin();
it != ts.tile_to_new_height.end(); it++) {
Expand All @@ -318,91 +326,6 @@ CommandCost CmdTerraformLand(TileIndex tile, DoCommandFlag flags, uint32 p1, uin
SetTileHeight(tile, (uint)height);
}

/* Finally mark the dirty tiles dirty */
for (TileIndexSet::const_iterator it = ts.dirty_tiles.begin(); it != ts.dirty_tiles.end(); it++) {
MarkTileDirtyByTile(*it);

int height = TerraformGetHeightOfTile(&ts, *it);

/* Now, if we alter the height of the map edge, we need to take care
* about repainting the affected areas outside map as well.
* Remember:
* Outside map, we assume that our landscape descends to
* height zero as fast as possible.
* Those simulated tiles (they don't exist as datastructure,
* only as concept in code) need to be repainted properly,
* otherwise we will get ugly glitches.
*
* Furthermore, note that we have to take care about the possibility,
* that landscape was higher before the change,
* so also tiles a bit outside need to be repainted.
*/
int x = TileX(*it);
int y = TileY(*it);
if (x == 0) {
if (y == 0) {
/* Height of the northern corner is altered. */
for (int cx = 0; cx >= -height - 1; cx--) {
for (int cy = 0; cy >= -height - 1; cy--) {
/* This means, tiles in the sector north of that
* corner need to be repainted.
*/
if (cx + cy >= -height - 2) {
/* But only tiles that actually might have changed. */
MarkTileDirtyByTileOutsideMap(cx, cy);
}
}
}
} else if (y < (int)MapMaxY()) {
for (int cx = 0; cx >= -height - 1; cx--) {
MarkTileDirtyByTileOutsideMap(cx, y);
}
} else {
for (int cx = 0; cx >= -height - 1; cx--) {
for (int cy = (int)MapMaxY(); cy <= (int)MapMaxY() + height + 1; cy++) {
if (cx + ((int)MapMaxY() - cy) >= -height - 2) {
MarkTileDirtyByTileOutsideMap(cx, cy);
}
}
}
}
} else if (x < (int)MapMaxX()) {
if (y == 0) {
for (int cy = 0; cy >= -height - 1; cy--) {
MarkTileDirtyByTileOutsideMap(x, cy);
}
} else if (y < (int)MapMaxY()) {
/* Nothing to be done here, we are inside the map. */
} else {
for (int cy = (int)MapMaxY(); cy <= (int)MapMaxY() + height + 1; cy++) {
MarkTileDirtyByTileOutsideMap(x, cy);
}
}
} else {
if (y == 0) {
for (int cx = (int)MapMaxX(); cx <= (int)MapMaxX() + height + 1; cx++) {
for (int cy = 0; cy >= -height - 1; cy--) {
if (((int)MapMaxX() - cx) + cy >= -height - 2) {
MarkTileDirtyByTileOutsideMap(cx, cy);
}
}
}
} else if (y < (int)MapMaxY()) {
for (int cx = (int)MapMaxX(); cx <= (int)MapMaxX() + height + 1; cx++) {
MarkTileDirtyByTileOutsideMap(cx, y);
}
} else {
for (int cx = (int)MapMaxX(); cx <= (int)MapMaxX() + height + 1; cx++) {
for (int cy = (int)MapMaxY(); cy <= (int)MapMaxY() + height + 1; cy++) {
if (((int)MapMaxX() - cx) + ((int)MapMaxY() - cy) >= -height - 2) {
MarkTileDirtyByTileOutsideMap(cx, cy);
}
}
}
}
}
}

if (c != NULL) c->terraform_limit -= (uint32)ts.tile_to_new_height.size() << 16;
}
return total_cost;
Expand Down