mirror of https://github.com/OpenTTD/OpenTTD
Change: make GetPartialZ consistent, meaning Z of adjacent slopes continue
Previously, on a straight line of a one corner up slope with the adjacent steep sloop the Z would increase one step every two sub pixels, except for one case where one sub pixel is skipped. Similarly, a steep slope with two adjacent one corner up slopes, would have a bump in the height line along the diagonal whenever it enters/leaves the steep slope tile.pull/10596/head
parent
1fcd69096c
commit
9d2a0f3d0b
|
@ -165,25 +165,25 @@ struct GroundVehicle : public SpecializedVehicle<T, Type> {
|
|||
if (HasBit(this->gv_flags, GVF_GOINGUP_BIT)) {
|
||||
switch (this->direction) {
|
||||
case DIR_NE:
|
||||
this->z_pos += (this->x_pos & 1); break;
|
||||
case DIR_SW:
|
||||
this->z_pos += (this->x_pos & 1) ^ 1; break;
|
||||
case DIR_SW:
|
||||
this->z_pos += (this->x_pos & 1); break;
|
||||
case DIR_NW:
|
||||
this->z_pos += (this->y_pos & 1); break;
|
||||
case DIR_SE:
|
||||
this->z_pos += (this->y_pos & 1) ^ 1; break;
|
||||
case DIR_SE:
|
||||
this->z_pos += (this->y_pos & 1); break;
|
||||
default: break;
|
||||
}
|
||||
} else if (HasBit(this->gv_flags, GVF_GOINGDOWN_BIT)) {
|
||||
switch (this->direction) {
|
||||
case DIR_NE:
|
||||
this->z_pos -= (this->x_pos & 1); break;
|
||||
case DIR_SW:
|
||||
this->z_pos -= (this->x_pos & 1) ^ 1; break;
|
||||
case DIR_SW:
|
||||
this->z_pos -= (this->x_pos & 1); break;
|
||||
case DIR_NW:
|
||||
this->z_pos -= (this->y_pos & 1); break;
|
||||
case DIR_SE:
|
||||
this->z_pos -= (this->y_pos & 1) ^ 1; break;
|
||||
case DIR_SE:
|
||||
this->z_pos -= (this->y_pos & 1); break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
@ -212,8 +212,7 @@ struct GroundVehicle : public SpecializedVehicle<T, Type> {
|
|||
int8 d = DiagDirToAxis(dir) == AXIS_X ? x_pos : y_pos;
|
||||
/* We need only the least significant bit */
|
||||
d &= 1;
|
||||
/* Conditional "^ 1". Optimised to "(dir - 1) <= 1". */
|
||||
d ^= (int8)(dir == DIAGDIR_SW || dir == DIAGDIR_SE);
|
||||
d ^= (int8)(dir == DIAGDIR_NW || dir == DIAGDIR_NE);
|
||||
/* Subtraction instead of addition because we are testing for GVF_GOINGUP_BIT.
|
||||
* GVF_GOINGUP_BIT is used because it's bit 0, so simple AND can be used,
|
||||
* without any shift */
|
||||
|
|
|
@ -210,137 +210,79 @@ uint ApplyFoundationToSlope(Foundation f, Slope *s)
|
|||
|
||||
|
||||
/**
|
||||
* Determines height at given coordinate of a slope
|
||||
* @param x x coordinate
|
||||
* @param y y coordinate
|
||||
* Determines height at given coordinate of a slope.
|
||||
*
|
||||
* At the northern corner (0, 0) the result is always a multiple of TILE_HEIGHT.
|
||||
* When the height is a fractional Z, then the height is rounded down. For example,
|
||||
* when at the height is 0 at x = 0 and the height is 8 at x = 16 (actually x = 0
|
||||
* of the next tile), then height is 0 at x = 1, 1 at x = 2, and 7 at x = 15.
|
||||
* @param x x coordinate (value from 0 to 15)
|
||||
* @param y y coordinate (value from 0 to 15)
|
||||
* @param corners slope to examine
|
||||
* @return height of given point of given slope
|
||||
*/
|
||||
uint GetPartialPixelZ(int x, int y, Slope corners)
|
||||
{
|
||||
if (IsHalftileSlope(corners)) {
|
||||
/* A foundation is placed on half the tile at a specific corner. This means that,
|
||||
* depending on the corner, that one half of the tile is at the maximum height. */
|
||||
switch (GetHalftileSlopeCorner(corners)) {
|
||||
case CORNER_W:
|
||||
if (x - y >= 0) return GetSlopeMaxPixelZ(corners);
|
||||
if (x > y) return GetSlopeMaxPixelZ(corners);
|
||||
break;
|
||||
|
||||
case CORNER_S:
|
||||
if (x - (y ^ 0xF) >= 0) return GetSlopeMaxPixelZ(corners);
|
||||
if (x + y >= (int)TILE_SIZE) return GetSlopeMaxPixelZ(corners);
|
||||
break;
|
||||
|
||||
case CORNER_E:
|
||||
if (y - x >= 0) return GetSlopeMaxPixelZ(corners);
|
||||
if (x <= y) return GetSlopeMaxPixelZ(corners);
|
||||
break;
|
||||
|
||||
case CORNER_N:
|
||||
if ((y ^ 0xF) - x >= 0) return GetSlopeMaxPixelZ(corners);
|
||||
if (x + y < (int)TILE_SIZE) return GetSlopeMaxPixelZ(corners);
|
||||
break;
|
||||
|
||||
default: NOT_REACHED();
|
||||
}
|
||||
}
|
||||
|
||||
int z = 0;
|
||||
|
||||
switch (RemoveHalftileSlope(corners)) {
|
||||
case SLOPE_W:
|
||||
if (x - y >= 0) {
|
||||
z = (x - y) >> 1;
|
||||
}
|
||||
break;
|
||||
case SLOPE_FLAT: return 0;
|
||||
|
||||
case SLOPE_S:
|
||||
y ^= 0xF;
|
||||
if ((x - y) >= 0) {
|
||||
z = (x - y) >> 1;
|
||||
}
|
||||
break;
|
||||
/* One corner is up.*/
|
||||
case SLOPE_N: return x + y <= (int)TILE_SIZE ? (TILE_SIZE - x - y) >> 1 : 0;
|
||||
case SLOPE_E: return y >= x ? (1 + y - x) >> 1 : 0;
|
||||
case SLOPE_S: return x + y >= (int)TILE_SIZE ? (1 + x + y - TILE_SIZE) >> 1 : 0;
|
||||
case SLOPE_W: return x >= y ? (x - y) >> 1 : 0;
|
||||
|
||||
case SLOPE_SW:
|
||||
z = (x >> 1) + 1;
|
||||
break;
|
||||
/* Two corners next to eachother are up. */
|
||||
case SLOPE_NE: return (TILE_SIZE - x) >> 1;
|
||||
case SLOPE_SE: return (y + 1) >> 1;
|
||||
case SLOPE_SW: return (x + 1) >> 1;
|
||||
case SLOPE_NW: return (TILE_SIZE - y) >> 1;
|
||||
|
||||
case SLOPE_E:
|
||||
if (y - x >= 0) {
|
||||
z = (y - x) >> 1;
|
||||
}
|
||||
break;
|
||||
/* Three corners are up on the same level. */
|
||||
case SLOPE_ENW: return x + y >= (int)TILE_SIZE ? TILE_HEIGHT - ((1 + x + y - TILE_SIZE) >> 1) : TILE_HEIGHT;
|
||||
case SLOPE_SEN: return y < x ? TILE_HEIGHT - ((x - y) >> 1) : TILE_HEIGHT;
|
||||
case SLOPE_WSE: return x + y <= (int)TILE_SIZE ? TILE_HEIGHT - ((TILE_SIZE - x - y) >> 1) : TILE_HEIGHT;
|
||||
case SLOPE_NWS: return x < y ? TILE_HEIGHT - ((1 + y - x) >> 1) : TILE_HEIGHT;
|
||||
|
||||
case SLOPE_EW:
|
||||
case SLOPE_NS:
|
||||
case SLOPE_ELEVATED:
|
||||
z = 4;
|
||||
break;
|
||||
/* Two corners at opposite sides are up. */
|
||||
case SLOPE_NS: return x + y < (int)TILE_SIZE ? (TILE_SIZE - x - y) >> 1 : (1 + x + y - TILE_SIZE) >> 1;
|
||||
case SLOPE_EW: return x >= y ? (x - y) >> 1 : (1 + y - x) >> 1;
|
||||
|
||||
case SLOPE_SE:
|
||||
z = (y >> 1) + 1;
|
||||
break;
|
||||
/* Very special cases. */
|
||||
case SLOPE_ELEVATED: return TILE_HEIGHT;
|
||||
|
||||
case SLOPE_WSE:
|
||||
z = 8;
|
||||
y ^= 0xF;
|
||||
if (x - y < 0) {
|
||||
z += (x - y) >> 1;
|
||||
}
|
||||
break;
|
||||
/* Steep slopes. The top is at 2 * TILE_HEIGHT. */
|
||||
case SLOPE_STEEP_N: return (TILE_SIZE - x + TILE_SIZE - y) >> 1;
|
||||
case SLOPE_STEEP_E: return (TILE_SIZE + 1 + y - x) >> 1;
|
||||
case SLOPE_STEEP_S: return (1 + x + y) >> 1;
|
||||
case SLOPE_STEEP_W: return (TILE_SIZE + x - y) >> 1;
|
||||
|
||||
case SLOPE_N:
|
||||
y ^= 0xF;
|
||||
if (y - x >= 0) {
|
||||
z = (y - x) >> 1;
|
||||
}
|
||||
break;
|
||||
|
||||
case SLOPE_NW:
|
||||
z = (y ^ 0xF) >> 1;
|
||||
break;
|
||||
|
||||
case SLOPE_NWS:
|
||||
z = 8;
|
||||
if (x - y < 0) {
|
||||
z += (x - y) >> 1;
|
||||
}
|
||||
break;
|
||||
|
||||
case SLOPE_NE:
|
||||
z = (x ^ 0xF) >> 1;
|
||||
break;
|
||||
|
||||
case SLOPE_ENW:
|
||||
z = 8;
|
||||
y ^= 0xF;
|
||||
if (y - x < 0) {
|
||||
z += (y - x) >> 1;
|
||||
}
|
||||
break;
|
||||
|
||||
case SLOPE_SEN:
|
||||
z = 8;
|
||||
if (y - x < 0) {
|
||||
z += (y - x) >> 1;
|
||||
}
|
||||
break;
|
||||
|
||||
case SLOPE_STEEP_S:
|
||||
z = 1 + ((x + y) >> 1);
|
||||
break;
|
||||
|
||||
case SLOPE_STEEP_W:
|
||||
z = 1 + ((x + (y ^ 0xF)) >> 1);
|
||||
break;
|
||||
|
||||
case SLOPE_STEEP_N:
|
||||
z = 1 + (((x ^ 0xF) + (y ^ 0xF)) >> 1);
|
||||
break;
|
||||
|
||||
case SLOPE_STEEP_E:
|
||||
z = 1 + (((x ^ 0xF) + y) >> 1);
|
||||
break;
|
||||
|
||||
default: break;
|
||||
default: NOT_REACHED();
|
||||
}
|
||||
|
||||
return z;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -527,6 +527,25 @@ static uint FixVehicleInclination(Vehicle *v, Direction dir)
|
|||
return 1U << GVF_GOINGUP_BIT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the ground vehicles are at the correct Z-coordinate. When they
|
||||
* are not, this will cause all kinds of problems later on as the vehicle might
|
||||
* not get onto bridges and so on.
|
||||
*/
|
||||
static void CheckGroundVehiclesAtCorrectZ()
|
||||
{
|
||||
for (Vehicle *v : Vehicle::Iterate()) {
|
||||
if (v->IsGroundVehicle()) {
|
||||
/*
|
||||
* Either the vehicle is not actually on the given tile, i.e. it is
|
||||
* in the wormhole of a bridge or a tunnel, or the Z-coordinate must
|
||||
* be the same as when it would be recalculated right now.
|
||||
*/
|
||||
assert(v->tile != TileVirtXY(v->x_pos, v->y_pos) || v->z_pos == GetSlopePixelZ(v->x_pos, v->y_pos, true));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for the possibility that a bridge may be on this tile
|
||||
* These are in fact all the tile types on which a bridge can be found
|
||||
|
@ -1249,7 +1268,7 @@ bool AfterLoadGame()
|
|||
case DIAGDIR_SW: if ((v->x_pos & 0xF) != TILE_SIZE - 1) continue; break;
|
||||
case DIAGDIR_NW: if ((v->y_pos & 0xF) != 0) continue; break;
|
||||
}
|
||||
} else if (v->z_pos > GetSlopePixelZ(v->x_pos, v->y_pos, true)) {
|
||||
} else if (v->z_pos > GetTileMaxPixelZ(TileVirtXY(v->x_pos, v->y_pos))) {
|
||||
v->tile = GetNorthernBridgeEnd(v->tile);
|
||||
v->UpdatePosition();
|
||||
} else {
|
||||
|
@ -2594,6 +2613,24 @@ bool AfterLoadGame()
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
if (IsSavegameVersionBefore(SLV_CONSISTENT_PARTIAL_Z)) {
|
||||
/*
|
||||
* The logic of GetPartialPixelZ has been changed, so the resulting Zs on
|
||||
* the map are consistent. This requires that the Z position of some
|
||||
* vehicles is updated to reflect this new situation.
|
||||
*
|
||||
* This needs to be before SLV_158, because that performs asserts using
|
||||
* GetSlopePixelZ which internally uses GetPartialPixelZ.
|
||||
*/
|
||||
for (Vehicle *v : Vehicle::Iterate()) {
|
||||
if (v->IsGroundVehicle() && TileVirtXY(v->x_pos, v->y_pos) == v->tile) {
|
||||
/* Vehicle is on the ground, and not in a wormhole. */
|
||||
v->z_pos = GetSlopePixelZ(v->x_pos, v->y_pos, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (IsSavegameVersionBefore(SLV_158)) {
|
||||
for (Vehicle *v : Vehicle::Iterate()) {
|
||||
switch (v->type) {
|
||||
|
@ -3228,6 +3265,8 @@ bool AfterLoadGame()
|
|||
|
||||
AfterLoadLinkGraphs();
|
||||
|
||||
CheckGroundVehiclesAtCorrectZ();
|
||||
|
||||
/* Start the scripts. This MUST happen after everything else except
|
||||
* starting a new company. */
|
||||
StartScripts();
|
||||
|
|
|
@ -348,6 +348,7 @@ enum SaveLoadVersion : uint16 {
|
|||
SLV_LINKGRAPH_EDGES, ///< 304 PR#10314 Explicitly store link graph edges destination, PR#10471 int64 instead of uint64 league rating
|
||||
|
||||
SLV_VELOCITY_NAUTICAL, ///< 305 PR#10594 Separation of land and nautical velocity (knots!)
|
||||
SLV_CONSISTENT_PARTIAL_Z, ///< 306 PR#10570 Conversion from an inconsistent partial Z calculation for slopes, to one that is (more) consistent.
|
||||
|
||||
SL_MAX_VERSION, ///< Highest possible saveload version
|
||||
};
|
||||
|
|
|
@ -1680,18 +1680,15 @@ static int GetSlopePixelZ_TunnelBridge(TileIndex tile, uint x, uint y, bool grou
|
|||
|
||||
/* On the bridge ramp? */
|
||||
if (ground_vehicle) {
|
||||
int delta;
|
||||
|
||||
if (tileh != SLOPE_FLAT) return z + TILE_HEIGHT;
|
||||
|
||||
switch (dir) {
|
||||
default: NOT_REACHED();
|
||||
case DIAGDIR_NE: delta = (TILE_SIZE - 1 - x) / 2; break;
|
||||
case DIAGDIR_SE: delta = y / 2; break;
|
||||
case DIAGDIR_SW: delta = x / 2; break;
|
||||
case DIAGDIR_NW: delta = (TILE_SIZE - 1 - y) / 2; break;
|
||||
case DIAGDIR_NE: tileh = SLOPE_NE; break;
|
||||
case DIAGDIR_SE: tileh = SLOPE_SE; break;
|
||||
case DIAGDIR_SW: tileh = SLOPE_SW; break;
|
||||
case DIAGDIR_NW: tileh = SLOPE_NW; break;
|
||||
}
|
||||
return z + 1 + delta;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1858,6 +1855,29 @@ static void ChangeTileOwner_TunnelBridge(TileIndex tile, Owner old_owner, Owner
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to prepare the ground vehicle when entering a bridge. This get called
|
||||
* when entering the bridge, at the last frame of travel on the bridge head.
|
||||
* Our calling function gets called before UpdateInclination/UpdateZPosition,
|
||||
* which normally controls the Z-coordinate. However, in the wormhole of the
|
||||
* bridge the vehicle is in a strange state so UpdateInclination does not get
|
||||
* called for the wormhole of the bridge and as such the going up/down bits
|
||||
* would remain set. As such, this function clears those. In doing so, the call
|
||||
* to UpdateInclination will not update the Z-coordinate, so that has to be
|
||||
* done here as well.
|
||||
* @param gv The ground vehicle entering the bridge.
|
||||
*/
|
||||
template <typename T>
|
||||
static void PrepareToEnterBridge(T *gv)
|
||||
{
|
||||
if (HasBit(gv->gv_flags, GVF_GOINGUP_BIT)) {
|
||||
gv->z_pos++;
|
||||
ClrBit(gv->gv_flags, GVF_GOINGUP_BIT);
|
||||
} else {
|
||||
ClrBit(gv->gv_flags, GVF_GOINGDOWN_BIT);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Frame when the 'enter tunnel' sound should be played. This is the second
|
||||
* frame on a tile, so the sound is played shortly after entering the tunnel
|
||||
|
@ -1959,17 +1979,14 @@ static VehicleEnterTileStatus VehicleEnter_TunnelBridge(Vehicle *v, TileIndex ti
|
|||
case VEH_TRAIN: {
|
||||
Train *t = Train::From(v);
|
||||
t->track = TRACK_BIT_WORMHOLE;
|
||||
ClrBit(t->gv_flags, GVF_GOINGUP_BIT);
|
||||
ClrBit(t->gv_flags, GVF_GOINGDOWN_BIT);
|
||||
PrepareToEnterBridge(t);
|
||||
break;
|
||||
}
|
||||
|
||||
case VEH_ROAD: {
|
||||
RoadVehicle *rv = RoadVehicle::From(v);
|
||||
rv->state = RVSB_WORMHOLE;
|
||||
/* There are no slopes inside bridges / tunnels. */
|
||||
ClrBit(rv->gv_flags, GVF_GOINGUP_BIT);
|
||||
ClrBit(rv->gv_flags, GVF_GOINGDOWN_BIT);
|
||||
PrepareToEnterBridge(rv);
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue