Skip to content

feat(geo-layers): TerrainLayer GlobeView + grid tesselator#10247

Closed
charlieforward9 wants to merge 2 commits intomasterfrom
feat/terrain-layer-globe-grid
Closed

feat(geo-layers): TerrainLayer GlobeView + grid tesselator#10247
charlieforward9 wants to merge 2 commits intomasterfrom
feat/terrain-layer-globe-grid

Conversation

@charlieforward9
Copy link
Copy Markdown
Collaborator

@charlieforward9 charlieforward9 commented Apr 19, 2026

Summary

Makes TerrainLayer render correctly on GlobeView and adds a grid tesselator that stays valid across projection toggles.

Three related changes:

  1. tesselator: 'grid' (opt-in) — emits a fixed-resolution lng/lat/elev mesh in-process (no worker). The default 'auto' path keeps using Martini/Delatin via @loaders.gl/terrain. Grid rows are uniform in Mercator-y so they line up with heightmap rows at any latitude (no polar sampling warp). Because vertices are lng/lat, the same cached mesh renders correctly on both MapView and GlobeView — no re-tesselation on projection toggle.
  2. Coordinate system selection in renderSubLayers — picks LNGLAT for the grid tesselator (vertices are lng/lat) and for the legacy path on GlobeView (where GlobeViewport.projectFlat is identity, so bounds land in lng/lat degrees). Uses CARTESIAN for the legacy path on MapView. Previously, legacy cartesian meshes landed inside the sphere on GlobeView and were invisible.
  3. Horizon culling in tile-2d-traversal — the frustum test accepts tiles on the far hemisphere of the globe; without a horizon check, low-zoom traversal fans out across the whole globe and loads many tiles that can't be visible. A point on the sphere is visible from camera C iff dot(P, C) > |P|^2 — applied to the tile-vs-camera closest point (with lng wrap so the clamp is angular, not Euclidean).

Two quality touches on the grid path:

  • Elevation samples clamped to [-500, 9000]m. Terrain-RGB encodes into 24 bits across three channels, so a single corrupt pixel (PNG decode artifact, overzoom noise, partial load) decodes to millions of meters and renders as a specular star. The clamp covers the full range of real terrain.
  • Edge-vertex skirts clamped to at most 1% of the in-tile grid step so the seam slope stays under ~0.6 degrees at any zoom. The default meshMaxError * 2 (~8m) over a ~0.6m grid step at z=21 produced a near-vertical cliff at every tile edge.

Default material changes from true (PBR with specular) to a matte configuration. Specular on terrain meshes catches as star-shaped glints on skirt edges and elevation discontinuities; matte reads as a fluid landscape.

Why

Grid tesselation makes terrain cheap enough to keep cached on projection toggle, and solves the polar sampling warp that lat-uniform tesselators exhibit near the poles. The LNGLAT coord-system fix and horizon culling are prerequisites for using TerrainLayer under GlobeView at all.

Test plan

  • MapView with tesselator: 'auto' — unchanged behavior
  • MapView with tesselator: 'grid' — renders equivalently, single cache across zoom levels
  • GlobeView with tesselator: 'grid' — renders correctly from z=0 through z=21+
  • GlobeView with tesselator: 'auto' — renders (via the new LNGLAT branch)
  • Projection toggle without rebuilding the tile cache
  • No specular star artifacts on z=21+ close-ups (flat beach terrain)
  • No visible tile-edge cliff at z=21+
  • Would welcome a render-test comparison vs. current master on the terrain-extension example

Companion PRs

Add a grid tesselator option to TerrainLayer and route the rendered mesh
through the correct coordinate system for each projection. Three pieces:

1. `tesselator: 'grid'` emits a fixed-resolution lng/lat/elev mesh in-process
   (no worker), keeping the same cached mesh valid on both MapView and
   GlobeView. The default `'auto'` path continues to use Martini/Delatin via
   `@loaders.gl/terrain`. Grid rows are uniform in Mercator-y so they line
   up with heightmap rows at any latitude (no polar sampling warp).

   Two quality touches on the grid path:
   - Elevation samples are clamped to [-500, 9000]m. Terrain-RGB encodes into
     24 bits across three channels, so one corrupt pixel decodes to millions
     of meters and renders as a specular star.
   - Edge-vertex skirts are clamped to ≤1% of the in-tile grid step so the
     seam slope stays imperceptible at any zoom (default 8m skirt over a
     ~0.6m grid step at z=21 was a visible cliff).

2. renderSubLayers picks the coordinate system per path: LNGLAT for the grid
   tesselator (vertices are lng/lat) and on GlobeView for the legacy path
   (GlobeViewport.projectFlat is identity so bounds land in lng/lat degrees),
   CARTESIAN on MapView for the legacy path (bounds in Mercator world units).
   Fixes the MapView→GlobeView case where legacy cartesian meshes landed
   inside the sphere and were invisible.

3. Default material changes from `true` (PBR with specular) to a matte
   configuration. Specular on terrain meshes catches as star-shaped glints
   on skirt edges and elevation discontinuities — a matte surface reads as
   a fluid landscape.

Also adds horizon culling in the tile-2d traversal used by the internal
TileLayer. The frustum test accepts tiles on the far hemisphere of the
globe; without a horizon check, low-zoom traversal fans out across the
entire globe. A point on the sphere is visible from camera C iff
dot(P, C) > |P|² — apply to the tile-vs-camera closest point (with lng
wrap so the clamp is angular, not Euclidean).

Docs updated for the new `tesselator` and `gridSize` props.
@charlieforward9
Copy link
Copy Markdown
Collaborator Author

Superseded by #10250 (rebranched under cr/ prefix).

@charlieforward9 charlieforward9 deleted the feat/terrain-layer-globe-grid branch April 19, 2026 19:35
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.

1 participant