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)) {
|
if (HasBit(this->gv_flags, GVF_GOINGUP_BIT)) {
|
||||||
switch (this->direction) {
|
switch (this->direction) {
|
||||||
case DIR_NE:
|
case DIR_NE:
|
||||||
this->z_pos += (this->x_pos & 1); break;
|
|
||||||
case DIR_SW:
|
|
||||||
this->z_pos += (this->x_pos & 1) ^ 1; break;
|
this->z_pos += (this->x_pos & 1) ^ 1; break;
|
||||||
|
case DIR_SW:
|
||||||
|
this->z_pos += (this->x_pos & 1); break;
|
||||||
case DIR_NW:
|
case DIR_NW:
|
||||||
this->z_pos += (this->y_pos & 1); break;
|
|
||||||
case DIR_SE:
|
|
||||||
this->z_pos += (this->y_pos & 1) ^ 1; break;
|
this->z_pos += (this->y_pos & 1) ^ 1; break;
|
||||||
|
case DIR_SE:
|
||||||
|
this->z_pos += (this->y_pos & 1); break;
|
||||||
default: break;
|
default: break;
|
||||||
}
|
}
|
||||||
} else if (HasBit(this->gv_flags, GVF_GOINGDOWN_BIT)) {
|
} else if (HasBit(this->gv_flags, GVF_GOINGDOWN_BIT)) {
|
||||||
switch (this->direction) {
|
switch (this->direction) {
|
||||||
case DIR_NE:
|
case DIR_NE:
|
||||||
this->z_pos -= (this->x_pos & 1); break;
|
|
||||||
case DIR_SW:
|
|
||||||
this->z_pos -= (this->x_pos & 1) ^ 1; break;
|
this->z_pos -= (this->x_pos & 1) ^ 1; break;
|
||||||
|
case DIR_SW:
|
||||||
|
this->z_pos -= (this->x_pos & 1); break;
|
||||||
case DIR_NW:
|
case DIR_NW:
|
||||||
this->z_pos -= (this->y_pos & 1); break;
|
|
||||||
case DIR_SE:
|
|
||||||
this->z_pos -= (this->y_pos & 1) ^ 1; break;
|
this->z_pos -= (this->y_pos & 1) ^ 1; break;
|
||||||
|
case DIR_SE:
|
||||||
|
this->z_pos -= (this->y_pos & 1); break;
|
||||||
default: break;
|
default: break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -212,8 +212,7 @@ struct GroundVehicle : public SpecializedVehicle<T, Type> {
|
||||||
int8 d = DiagDirToAxis(dir) == AXIS_X ? x_pos : y_pos;
|
int8 d = DiagDirToAxis(dir) == AXIS_X ? x_pos : y_pos;
|
||||||
/* We need only the least significant bit */
|
/* We need only the least significant bit */
|
||||||
d &= 1;
|
d &= 1;
|
||||||
/* Conditional "^ 1". Optimised to "(dir - 1) <= 1". */
|
d ^= (int8)(dir == DIAGDIR_NW || dir == DIAGDIR_NE);
|
||||||
d ^= (int8)(dir == DIAGDIR_SW || dir == DIAGDIR_SE);
|
|
||||||
/* Subtraction instead of addition because we are testing for GVF_GOINGUP_BIT.
|
/* 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,
|
* GVF_GOINGUP_BIT is used because it's bit 0, so simple AND can be used,
|
||||||
* without any shift */
|
* without any shift */
|
||||||
|
|
|
@ -210,137 +210,79 @@ uint ApplyFoundationToSlope(Foundation f, Slope *s)
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determines height at given coordinate of a slope
|
* Determines height at given coordinate of a slope.
|
||||||
* @param x x coordinate
|
*
|
||||||
* @param y y coordinate
|
* 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
|
* @param corners slope to examine
|
||||||
* @return height of given point of given slope
|
* @return height of given point of given slope
|
||||||
*/
|
*/
|
||||||
uint GetPartialPixelZ(int x, int y, Slope corners)
|
uint GetPartialPixelZ(int x, int y, Slope corners)
|
||||||
{
|
{
|
||||||
if (IsHalftileSlope(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)) {
|
switch (GetHalftileSlopeCorner(corners)) {
|
||||||
case CORNER_W:
|
case CORNER_W:
|
||||||
if (x - y >= 0) return GetSlopeMaxPixelZ(corners);
|
if (x > y) return GetSlopeMaxPixelZ(corners);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case CORNER_S:
|
case CORNER_S:
|
||||||
if (x - (y ^ 0xF) >= 0) return GetSlopeMaxPixelZ(corners);
|
if (x + y >= (int)TILE_SIZE) return GetSlopeMaxPixelZ(corners);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case CORNER_E:
|
case CORNER_E:
|
||||||
if (y - x >= 0) return GetSlopeMaxPixelZ(corners);
|
if (x <= y) return GetSlopeMaxPixelZ(corners);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case CORNER_N:
|
case CORNER_N:
|
||||||
if ((y ^ 0xF) - x >= 0) return GetSlopeMaxPixelZ(corners);
|
if (x + y < (int)TILE_SIZE) return GetSlopeMaxPixelZ(corners);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default: NOT_REACHED();
|
default: NOT_REACHED();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int z = 0;
|
|
||||||
|
|
||||||
switch (RemoveHalftileSlope(corners)) {
|
switch (RemoveHalftileSlope(corners)) {
|
||||||
case SLOPE_W:
|
case SLOPE_FLAT: return 0;
|
||||||
if (x - y >= 0) {
|
|
||||||
z = (x - y) >> 1;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case SLOPE_S:
|
/* One corner is up.*/
|
||||||
y ^= 0xF;
|
case SLOPE_N: return x + y <= (int)TILE_SIZE ? (TILE_SIZE - x - y) >> 1 : 0;
|
||||||
if ((x - y) >= 0) {
|
case SLOPE_E: return y >= x ? (1 + y - x) >> 1 : 0;
|
||||||
z = (x - y) >> 1;
|
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;
|
||||||
break;
|
|
||||||
|
|
||||||
case SLOPE_SW:
|
/* Two corners next to eachother are up. */
|
||||||
z = (x >> 1) + 1;
|
case SLOPE_NE: return (TILE_SIZE - x) >> 1;
|
||||||
break;
|
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:
|
/* Three corners are up on the same level. */
|
||||||
if (y - x >= 0) {
|
case SLOPE_ENW: return x + y >= (int)TILE_SIZE ? TILE_HEIGHT - ((1 + x + y - TILE_SIZE) >> 1) : TILE_HEIGHT;
|
||||||
z = (y - x) >> 1;
|
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;
|
||||||
break;
|
case SLOPE_NWS: return x < y ? TILE_HEIGHT - ((1 + y - x) >> 1) : TILE_HEIGHT;
|
||||||
|
|
||||||
case SLOPE_EW:
|
/* Two corners at opposite sides are up. */
|
||||||
case SLOPE_NS:
|
case SLOPE_NS: return x + y < (int)TILE_SIZE ? (TILE_SIZE - x - y) >> 1 : (1 + x + y - TILE_SIZE) >> 1;
|
||||||
case SLOPE_ELEVATED:
|
case SLOPE_EW: return x >= y ? (x - y) >> 1 : (1 + y - x) >> 1;
|
||||||
z = 4;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case SLOPE_SE:
|
/* Very special cases. */
|
||||||
z = (y >> 1) + 1;
|
case SLOPE_ELEVATED: return TILE_HEIGHT;
|
||||||
break;
|
|
||||||
|
|
||||||
case SLOPE_WSE:
|
/* Steep slopes. The top is at 2 * TILE_HEIGHT. */
|
||||||
z = 8;
|
case SLOPE_STEEP_N: return (TILE_SIZE - x + TILE_SIZE - y) >> 1;
|
||||||
y ^= 0xF;
|
case SLOPE_STEEP_E: return (TILE_SIZE + 1 + y - x) >> 1;
|
||||||
if (x - y < 0) {
|
case SLOPE_STEEP_S: return (1 + x + y) >> 1;
|
||||||
z += (x - y) >> 1;
|
case SLOPE_STEEP_W: return (TILE_SIZE + x - y) >> 1;
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case SLOPE_N:
|
default: NOT_REACHED();
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return z;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -527,6 +527,25 @@ static uint FixVehicleInclination(Vehicle *v, Direction dir)
|
||||||
return 1U << GVF_GOINGUP_BIT;
|
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
|
* 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
|
* 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_SW: if ((v->x_pos & 0xF) != TILE_SIZE - 1) continue; break;
|
||||||
case DIAGDIR_NW: if ((v->y_pos & 0xF) != 0) 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->tile = GetNorthernBridgeEnd(v->tile);
|
||||||
v->UpdatePosition();
|
v->UpdatePosition();
|
||||||
} else {
|
} 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)) {
|
if (IsSavegameVersionBefore(SLV_158)) {
|
||||||
for (Vehicle *v : Vehicle::Iterate()) {
|
for (Vehicle *v : Vehicle::Iterate()) {
|
||||||
switch (v->type) {
|
switch (v->type) {
|
||||||
|
@ -3228,6 +3265,8 @@ bool AfterLoadGame()
|
||||||
|
|
||||||
AfterLoadLinkGraphs();
|
AfterLoadLinkGraphs();
|
||||||
|
|
||||||
|
CheckGroundVehiclesAtCorrectZ();
|
||||||
|
|
||||||
/* Start the scripts. This MUST happen after everything else except
|
/* Start the scripts. This MUST happen after everything else except
|
||||||
* starting a new company. */
|
* starting a new company. */
|
||||||
StartScripts();
|
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_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_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
|
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? */
|
/* On the bridge ramp? */
|
||||||
if (ground_vehicle) {
|
if (ground_vehicle) {
|
||||||
int delta;
|
|
||||||
|
|
||||||
if (tileh != SLOPE_FLAT) return z + TILE_HEIGHT;
|
if (tileh != SLOPE_FLAT) return z + TILE_HEIGHT;
|
||||||
|
|
||||||
switch (dir) {
|
switch (dir) {
|
||||||
default: NOT_REACHED();
|
default: NOT_REACHED();
|
||||||
case DIAGDIR_NE: delta = (TILE_SIZE - 1 - x) / 2; break;
|
case DIAGDIR_NE: tileh = SLOPE_NE; break;
|
||||||
case DIAGDIR_SE: delta = y / 2; break;
|
case DIAGDIR_SE: tileh = SLOPE_SE; break;
|
||||||
case DIAGDIR_SW: delta = x / 2; break;
|
case DIAGDIR_SW: tileh = SLOPE_SW; break;
|
||||||
case DIAGDIR_NW: delta = (TILE_SIZE - 1 - y) / 2; 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 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
|
* 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: {
|
case VEH_TRAIN: {
|
||||||
Train *t = Train::From(v);
|
Train *t = Train::From(v);
|
||||||
t->track = TRACK_BIT_WORMHOLE;
|
t->track = TRACK_BIT_WORMHOLE;
|
||||||
ClrBit(t->gv_flags, GVF_GOINGUP_BIT);
|
PrepareToEnterBridge(t);
|
||||||
ClrBit(t->gv_flags, GVF_GOINGDOWN_BIT);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case VEH_ROAD: {
|
case VEH_ROAD: {
|
||||||
RoadVehicle *rv = RoadVehicle::From(v);
|
RoadVehicle *rv = RoadVehicle::From(v);
|
||||||
rv->state = RVSB_WORMHOLE;
|
rv->state = RVSB_WORMHOLE;
|
||||||
/* There are no slopes inside bridges / tunnels. */
|
PrepareToEnterBridge(rv);
|
||||||
ClrBit(rv->gv_flags, GVF_GOINGUP_BIT);
|
|
||||||
ClrBit(rv->gv_flags, GVF_GOINGDOWN_BIT);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue