Skip to content

fix(terrain): Cover the visible ground at high camera zoom#2785

Open
sailro wants to merge 3 commits into
TheSuperHackers:mainfrom
sailro:fix/terrain-cover-visible-ground-at-zoom
Open

fix(terrain): Cover the visible ground at high camera zoom#2785
sailro wants to merge 3 commits into
TheSuperHackers:mainfrom
sailro:fix/terrain-cover-visible-ground-at-zoom

Conversation

@sailro

@sailro sailro commented Jun 12, 2026

Copy link
Copy Markdown

Problem

With the default settings (DrawEntireTerrain=No) the terrain is only drawn within a window of tiles around the view center. At high camera zoom this window no longer covers the visible ground, so any map water renders behind it. Many maps that have no real water still keep a map-sized "Default Water" polygon, which then makes the whole map look flooded when the camera is zoomed out.

The legacy workaround was DrawEntireTerrain=Yes, but that always draws the entire map and is very slow even at normal zoom (#2743).

Fix

Size the normal draw window to the actual visible footprint instead of a fixed tile count:

  • Project the four view corners onto the ground plane (the same method updateCenter() already uses to position the window).
  • Grow the draw size just enough to span them, bounded by the map extent and snapped to whole vertex-buffer tiles, so the terrain only reallocates when a zoom threshold is crossed.
  • Use a single square size so the window stays stable as the camera rotates (no reallocation on yaw).
  • Near-horizontal / parallel corner rays (which meet the ground far away, behind the camera, or not at all) are clamped to the map extent, so they cannot produce a degenerate or NaN draw size.

At normal zoom the footprint is smaller than the normal window, so this is a no-op and behavior is unchanged; the window only grows as the camera zooms out. It works for all maps.

This change lives in the default DrawEntireTerrain=No path. It does not modify the DrawEntireTerrain path, so it does not by itself resolve #2743 — it removes the need for that slow workaround in the common case.

Before / After

Before — zoomed out on a no-water map, the terrain window ends and the map's default water polygon fills the rest of the view:

before

After — the draw window covers the visible ground, so the sand renders all the way out (same scene type, patched build):

after

After on a stock map (Whiteout) — the real lake still renders correctly and the terrain fills the view (the black wedge top-left is genuinely off-map, past the terrain border at extreme zoom):

after-whiteout

Testing

  • Built Release / Win32 and tested in skirmish (Zero Hour).
  • At maximum camera zoom-out: no water bleed-through on no-water maps, and the real water on stock maps still renders correctly.
  • Performance stays smooth at all zoom levels across multiple maps (no DrawEntireTerrain=Yes-style degradation).

AI disclosure

Per the contribution guidelines: the code in this PR was written with the help of an LLM (GitHub Copilot CLI) and then reviewed, iterated on, and verified by a human. The approach — projecting the view footprint the same way updateCenter() does, tile snapping, and map-extent / NaN bounding — was chosen deliberately, and the change was validated in-game across several maps and zoom levels. The diff is intentionally small and self-contained: one file, only the draw-size calculation in updateTerrain().

cc @xezon

With the default settings (DrawEntireTerrain=No) the terrain is drawn only
within a window of tiles around the view center. At high camera zoom this
window no longer covers the visible ground, so any map water renders behind
it. Maps that have no real water often still keep a map-sized "Default Water"
polygon, which then makes the whole map look flooded.

Players worked around this with DrawEntireTerrain=Yes, but that always draws
the entire map and is very slow even at normal zoom (TheSuperHackers#2743).

This change instead sizes the normal draw window to the actual visible
footprint: it projects the four view corners onto the ground plane (the same
method updateCenter() uses to position the window) and grows the draw size
just enough to span them, bounded by the map extent and snapped to whole
vertex-buffer tiles. At normal zoom the footprint is smaller than the normal
window, so this is a no-op and behavior is unchanged; the window only grows as
the camera zooms out.

This does not change the DrawEntireTerrain path, but removes the need for it in
the common case.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@greptile-apps

greptile-apps Bot commented Jun 12, 2026

Copy link
Copy Markdown

Greptile Summary

This PR fixes a terrain draw window that was too small at high camera zoom, causing the map's default water plane to bleed through on maps without real water. The fix is self-contained: one function, one file, one additional block in updateTerrain().

  • Projects the four view frustum corners onto the ground plane (reusing the same matrix/ray math as the existing HeightMapRenderObjClass::updateCenter()) to compute the actual visible footprint, then sizes the draw window to that footprint rather than a fixed constant.
  • The footprint is bounded to the map extent, NaN/infinite values from near-horizontal rays are safely clamped via !(>=) guards, and the result is snapped up to the nearest tile-aligned boundary to avoid reallocation on camera rotation.
  • Only activates when the computed zoom draw size exceeds the existing NORMAL_DRAW_WIDTH / LOW_ANGLE_DRAW_WIDTH values, making it a no-op at normal zoom levels.

Confidence Score: 5/5

Safe to merge — the change is additive and only enlarges the draw window beyond the existing constants when the camera is zoomed out far enough to require it.

The corner-projection math is identical to the pattern already used by HeightMapRenderObjClass::updateCenter(), NaN/infinity from near-horizontal rays is correctly rejected by the !(>=) clamping idiom, the result is bounded by mapExtent and snapped to tile boundaries, and the override only fires when the computed zoom size exceeds the existing draw size — making it a true no-op at normal zoom. No existing draw path is modified.

No files require special attention.

Important Files Changed

Filename Overview
Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DView.cpp Adds a footprint-projection block in updateTerrain() that grows the terrain draw window to cover visible ground at high zoom; logic mirrors existing updateCenter() corner-projection code, edge cases well-handled.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[updateTerrain] --> B{cameraPitch high?}
    B -- yes --> C[NORMAL draw size]
    B -- no --> D[LOW_ANGLE draw size]
    C --> E{map available?}
    D --> E
    E -- no --> L[setTerrainDrawSize / updateCenter]
    E -- yes --> F[Get camera transform + view plane]
    F --> G[Project 4 corner rays onto groundZ]
    G --> H[Clamp to map extent, reject NaN]
    H --> I[Accumulate footprint min/max]
    I --> J[Compute span in tiles + margin]
    J --> K[Cap to mapExtent, snap to tile boundary]
    K --> M{zoomDrawSize bigger?}
    M -- yes --> N[Override drawWidth/drawHeight]
    M -- no --> L
    N --> L
Loading

Reviews (3): Last reviewed commit: "style(terrain): Make updateTerrain comme..." | Re-trigger Greptile

Split the newly added single-line if/else statements in W3DView::updateTerrain() (the NaN clamping, the footprint min/max update, and the final draw-size update) so each body is on its own line. This follows the project formatting convention and allows precise debugger breakpoint placement. No behavior change.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@Skyaero42

Copy link
Copy Markdown

@Mauller is this something you were addressing in #1711 as well, or is it a separate issue?

Remove references to other functions and the issue id from the comments added in this PR, and tighten the wording. No code change.
@sailro sailro force-pushed the fix/terrain-cover-visible-ground-at-zoom branch from e001c0c to c245104 Compare June 13, 2026 13:15
@sailro

sailro commented Jun 13, 2026

Copy link
Copy Markdown
Author

Tidied the comments in updateTerrain() to match the style we landed on in #2786:

  • Dropped references to other functions (updateCenter()) so the comments can't go stale if that code is renamed or moved.
  • Removed the issue id from the comments.
  • Condensed the header block from ~13 lines down to 5, keeping just the what/why/how. The finer implementation details (map-extent bounding, NaN clamping, tile snapping, rotation stability) are already documented by the inline comments right next to the relevant lines, so nothing was lost.

No functional change — comments only.

@sailro

sailro commented Jun 13, 2026

Copy link
Copy Markdown
Author

@Mauller is this something you were addressing in #1711 as well, or is it a separate issue?

Hi @Skyaero42, From the #2785 side, I think these are separate, complementary fixes rather than the same one:

Same file, but different functions (setDefaultView/setZoomToDefault vs updateTerrain), so no logical overlap or expected conflict.

They're actually complementary: #1711 raises the effective camera height on wide aspect ratios, which is exactly the zoomed-out condition where an un-sized terrain window stops covering the view and the water bleeds through. #2785 derives the window from the projected view corners, so it adapts automatically to whatever camera height #1711 produces. So #1711 can make the symptom more visible on wide screens, and #2785 fixes the rendering side of it.

@Mauller can confirm the #1711 specifics/intent — I'm only speaking to how #2785 relates.

Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Massive performance degradation with DrawEntireTerrain=Yes

2 participants