diff --git a/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DView.cpp b/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DView.cpp index c029fa4880a..ef3ac599d2a 100644 --- a/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DView.cpp +++ b/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DView.cpp @@ -3715,6 +3715,88 @@ void W3DView::updateTerrain() drawHeight = WorldHeightMap::LOW_ANGLE_DRAW_HEIGHT; } + // TheSuperHackers @bugfix sailro 13/06/2026 Grow the terrain draw window to cover the visible ground + // when zoomed far out, for all maps. Terrain is only drawn in a window around the view center; when the + // visible ground extends past it, the uncovered area lets the map water plane show through (maps with no + // real water still keep a map-sized one, so they look flooded). We size the window to the footprint of + // the four view corners projected onto the ground: a no-op at normal zoom, growing only when zoomed out. + if (WorldHeightMap *map = TheTerrainRenderObject->getMap()) + { + const Matrix3D &cameraTransform = m_3DCamera->Get_Transform(); + const Vector3 cameraLocation = m_3DCamera->Get_Position(); + Vector2 viewPlaneMin, viewPlaneMax; + m_3DCamera->Get_View_Plane(viewPlaneMin, viewPlaneMax); + Vector2 viewportMin, viewportMax; + m_3DCamera->Get_Viewport(viewportMin, viewportMax); + + const Real viewPlaneScaleX = viewPlaneMax.X - viewPlaneMin.X; + const Real viewPlaneScaleY = viewPlaneMax.Y - viewPlaneMin.Y; + const Real viewPlaneDist = -1.0f; // The view plane is always 1.0 from the camera, looking down -Z. + const Real groundZ = m_pos.z; + + // Bound the projected corners to the map extent so a near horizontal corner ray - which meets the + // ground far away, behind the camera, or not at all - cannot produce a degenerate draw size. + const Int mapExtent = (map->getXExtent() > map->getYExtent()) ? map->getXExtent() : map->getYExtent(); + const Real worldBound = (Real)mapExtent * MAP_XY_FACTOR; + + Real footprintMinX = cameraLocation.X, footprintMaxX = cameraLocation.X; + Real footprintMinY = cameraLocation.Y, footprintMaxY = cameraLocation.Y; + + for (Int i = 0; i < 2; ++i) + { + for (Int j = 0; j < 2; ++j) + { + const Real xMod = (-i + 0.5f + viewportMin.X) * viewPlaneDist * viewPlaneScaleX; + const Real yMod = ( j - 0.5f - viewportMin.Y) * viewPlaneDist * viewPlaneScaleY; + + Vector3 rayDirection( + viewPlaneDist * cameraTransform[0][2] + xMod * cameraTransform[0][0] + yMod * cameraTransform[0][1], + viewPlaneDist * cameraTransform[1][2] + xMod * cameraTransform[1][0] + yMod * cameraTransform[1][1], + viewPlaneDist * cameraTransform[2][2] + xMod * cameraTransform[2][0] + yMod * cameraTransform[2][1]); + rayDirection.Normalize(); + const Vector3 rayPoint = cameraLocation + rayDirection; + + Real groundX = Vector3::Find_X_At_Z(groundZ, cameraLocation, rayPoint); + Real groundY = Vector3::Find_Y_At_Z(groundZ, cameraLocation, rayPoint); + + // Clamp into [camera +/- worldBound]; the !(>=) form also rejects NaN from parallel rays. + if (!(groundX >= cameraLocation.X - worldBound)) + groundX = cameraLocation.X - worldBound; + else if (groundX > cameraLocation.X + worldBound) + groundX = cameraLocation.X + worldBound; + if (!(groundY >= cameraLocation.Y - worldBound)) + groundY = cameraLocation.Y - worldBound; + else if (groundY > cameraLocation.Y + worldBound) + groundY = cameraLocation.Y + worldBound; + + if (groundX < footprintMinX) + footprintMinX = groundX; + if (groundX > footprintMaxX) + footprintMaxX = groundX; + if (groundY < footprintMinY) + footprintMinY = groundY; + if (groundY > footprintMaxY) + footprintMaxY = groundY; + } + } + + // Use a single square size (the larger footprint dimension) so the window does not reallocate as the + // camera rotates, plus one tile block of margin. + const Real footprintSpanX = footprintMaxX - footprintMinX; + const Real footprintSpanY = footprintMaxY - footprintMinY; + const Real footprintSpan = (footprintSpanX > footprintSpanY) ? footprintSpanX : footprintSpanY; + Int footprintTiles = (Int)(footprintSpan / MAP_XY_FACTOR) + 1 + VERTEX_BUFFER_TILE_LENGTH; + if (footprintTiles > mapExtent) + footprintTiles = mapExtent; + + // Snap up to 1 + N * VERTEX_BUFFER_TILE_LENGTH to match the engine's tile-based draw sizes. + const Int zoomDrawSize = ((footprintTiles - 1 + VERTEX_BUFFER_TILE_LENGTH) / VERTEX_BUFFER_TILE_LENGTH) * VERTEX_BUFFER_TILE_LENGTH + 1; + if (zoomDrawSize > drawWidth) + drawWidth = zoomDrawSize; + if (zoomDrawSize > drawHeight) + drawHeight = zoomDrawSize; + } + TheTerrainRenderObject->setTerrainDrawSize(drawWidth, drawHeight); TheTerrainRenderObject->updateCenter(m_3DCamera, &cameraPivot, it);