diff --git a/Server/Scripts/ServerChunkManager.cs b/Server/Scripts/ServerChunkManager.cs index 5170f0e..71a24f0 100644 --- a/Server/Scripts/ServerChunkManager.cs +++ b/Server/Scripts/ServerChunkManager.cs @@ -233,13 +233,39 @@ namespace IslaApocalypse.Server /// Turns a map-pixel position into a world surface height, with bounds clamping. /// Same mapping used everywhere else: raw 0-1 heightmap value scaled into the /// usable vertical band of the chunk. + /// + /// The position handed in is usually FRACTIONAL (road path points land between + /// heightmap cells), so we read the four cells around it and blend — bilinear + /// interpolation — rather than snapping to whichever cell we happen to land in. + /// + /// Why that matters: snapping made the height jump by a whole cell's worth the + /// instant a point crossed a cell boundary, and hold perfectly flat until it did. + /// Along a road whose points sit less than a metre apart, that produced a fine + /// metre-scale staircase — the "washboard". Blending makes the height vary + /// continuously as the position moves, so the staircase has nothing to stand on. /// private float HeightAtPixel(Vector2 pixel) { - int px = Mathf.Clamp((int)pixel.X, 0, _blueprint.MapSize - 1); - int py = Mathf.Clamp((int)pixel.Y, 0, _blueprint.MapSize - 1); + int max = _blueprint.MapSize - 1; + + // The four heightmap cells surrounding this position. + // At the very edge of the map x1/y1 clamp back onto x0/y0, which makes the + // blend a harmless no-op instead of reading out of bounds. + int x0 = Mathf.Clamp(Mathf.FloorToInt(pixel.X), 0, max); + int y0 = Mathf.Clamp(Mathf.FloorToInt(pixel.Y), 0, max); + int x1 = Mathf.Clamp(x0 + 1, 0, max); + int y1 = Mathf.Clamp(y0 + 1, 0, max); + + // How far between those cells we actually are, 0 to 1 on each axis. + // Clamped so a position outside the map can't push the blend past its corners. + float fx = Mathf.Clamp(pixel.X - x0, 0.0f, 1.0f); + float fy = Mathf.Clamp(pixel.Y - y0, 0.0f, 1.0f); + + // Blend across X on the near row and the far row, then blend those two down Y. + float nearRow = Mathf.Lerp(_blueprint.HeightMap[x0, y0], _blueprint.HeightMap[x1, y0], fx); + float farRow = Mathf.Lerp(_blueprint.HeightMap[x0, y1], _blueprint.HeightMap[x1, y1], fx); + float raw = Mathf.Lerp(nearRow, farRow, fy); - float raw = _blueprint.HeightMap[px, py]; return Mathf.Clamp(raw * (Constants.CHUNK_HEIGHT - 5), 2.0f, Constants.CHUNK_HEIGHT - 2.0f); }