feat(extensions): TerrainExtension GlobeView support#10251
feat(extensions): TerrainExtension GlobeView support#10251charlieforward9 wants to merge 15 commits into
Conversation
Make TerrainExtension's draped rendering (height-map and terrain-cover FBOs) work on GlobeView as well as MapView. The existing implementation packed layer bounds into the live viewport's common space, which on GlobeView is sphere cartesian — incomparable with screen-space bounds, and invalid as a UV basis for the draped sampler. All bounds are now expressed in ABSOLUTE Web Mercator common space, regardless of the active projection. The FBO is rendered via a WebMercatorViewport in both cases, so the cover/height-map texture is projection-invariant and a projection toggle no longer requires re-rendering. Changes: - projection-utils: new `MERCATOR_REFERENCE_VIEWPORT` plus helpers `lngLatToMercatorCommon` and `getMercatorReferenceViewport` so other modules can compute bounds in the shared absolute-Mercator basis. - terrain-cover, height-map-builder: project layer bounds through the Mercator reference viewport; on globe, skip the viewport-bounds intersection in `getRenderBounds` (which would yield sphere cartesian coords) and use full layer bounds. - shader-module: compute `terrainMercPos` per-fragment by unprojecting globe cartesian back to lng/lat and forward-projecting through project_mercator_, so USE_HEIGHT_MAP / USE_COVER samples against the absolute-Mercator bounds uniform on any projection. The project module's helpers are VS-only, so the value is passed via varying; terrain meshes are fine enough that interpolation error is negligible. Bounds uniform is packed as absolute Mercator (no commonOrigin subtract), since the shader computes absolute xy itself. Example updated with a GlobeView toggle so the terrain-extension demo exercises both projections (requires TerrainLayer's grid tesselator from the companion geo-layers PR. EOF )
| if (terrain.mode == TERRAIN_MODE_USE_HEIGHT_MAP) { | ||
| vec3 anchor = geometry.worldPosition; | ||
| anchor.z = 0.0; | ||
| vec3 anchorCommon = project_position(anchor); |
There was a problem hiding this comment.
OK, so if I understand correctly you're basically forcing project_position() to internally take the following branch:
if (project.projectionMode == PROJECTION_MODE_WEB_MERCATOR) {A natural question that comes to mind is that if we are going to hardcode a projection, then why Mercator? Sure it works well for MapView but it doesn't fit the other views.
There was a problem hiding this comment.
Perhaps related, I have been feeling that if we extend layers to take a "deformation" when generating geometry, such as a deformation UV grid for BitmapLayer than we could generalize that to support any projection.
| // Convert bounds to the common space, as [minX, minY, width, height] | ||
| // Pack bounds as [minX, minY, width, height] | ||
| bounds: bounds | ||
| ? [ |
There was a problem hiding this comment.
I worry this will introduce precision issues
| const targetLayer = this.targetLayer; | ||
| let shouldRedraw = false; | ||
|
|
||
| // Bounds are computed in ABSOLUTE Mercator common space — NOT the live |
There was a problem hiding this comment.
Could we try to use a cartesian space? Seems like a closer match to the spherical coordinates
| // Recalculate cached bounds | ||
| // Recalculate cached bounds. | ||
| // Use a Mercator reference viewport so layer bounds live in ABSOLUTE | ||
| // Mercator common space — same rationale as terrain-cover.ts. On |
There was a problem hiding this comment.
Should we add mercator common space as a supported coordinateSystem?
Something like first class support for EPSG: 3857?
Or that is not helpful?
| if (terrain.mode == TERRAIN_MODE_USE_HEIGHT_MAP) { | ||
| vec3 anchor = geometry.worldPosition; | ||
| anchor.z = 0.0; | ||
| vec3 anchorCommon = project_position(anchor); |
There was a problem hiding this comment.
Perhaps related, I have been feeling that if we extend layers to take a "deformation" when generating geometry, such as a deformation UV grid for BitmapLayer than we could generalize that to support any projection.
felixpalmer
left a comment
There was a problem hiding this comment.
@charlieforward9 I've done a pass a tidied a few things:
- Neater
terrain_globe_to_mercatorequations - More succinct comments
- Revert removal of
commonOriginoffset in bounds. This way the existing behavior isn't changed, only globeview support is added
Could you check that I haven't broken anything? Otherwise looks good to merge!
|
Confirmed locally: the TerrainExtension example loads tiles with the Mapbox token, and the View selector is now in the example controls. CI is green. |
felixpalmer
left a comment
There was a problem hiding this comment.
Tested with new globe controls, works nicely
…xtension-globe # Conflicts: # modules/geo-layers/src/terrain-layer/terrain-layer.ts
Summary
Makes
TerrainExtension(draped rendering of layers onto a terrain surface) work onGlobeViewas well asMapView.The root bug is that the terrain cover / height-map textures are 2D render targets, but the old implementation derived their bounds and UVs from the live viewport's common space. On
GlobeViewthat common space is sphere cartesian, which is not comparable with screen-space bounds and is not a stable 2D sampling basis. As a result, cover and height-map UV math broke on globe.This branch keeps the terrain textures in a projection-invariant absolute Web Mercator common-space basis. The important point is that Mercator here is not being chosen as the display projection; it is the texture space of the offscreen terrain cover / height-map FBOs, which are rendered through a
WebMercatorViewportin both map and globe modes.Changes
projection-utilsAdded a shared Mercator reference viewport plus helpers to compute bounds in absolute Mercator common space.
terrain-cover,height-map-builderLayer bounds are projected through the Mercator reference viewport. On globe, viewport-bound intersection is skipped where it would otherwise mix sphere-cartesian viewport bounds with Mercator layer bounds.
shader-moduleGlobe terrain positions are converted directly from globe cartesian to Mercator world coordinates in the shader, instead of going through
atan/degrees/project_mercator_round-trips. Bounds stay packed in absolute Mercator, with nocommonOriginsubtract.Folded in the minimal
TerrainLayer/ tile traversal fixes needed for the example preview to render onGlobeViewwithout bringing in the grid tesselator work from fix(geo-layers): TerrainLayer GlobeView support #10250:LNGLATonly when their mesh bounds are actually lng/lat, so stale MapView tiles are not misinterpreted during togglesexamples/website/terrain-extensionnow has aMapView/GlobeViewtoggle and uses the minimal globe terrain fixes above for local previewing.Addressed Review Changes
project_mercator_path with direct globe-cartesian -> Mercator math in the shader.Why
_TerrainExtensionis already used for draping icons, labels, and polygons onto terrain. Extending it toGlobeViewlets applications keep the same draped layers across projection toggles instead of maintaining separate map and globe code paths.Test plan
MapViewwithTerrainExtensionandTerrainLayersource -- unchanged behaviorGlobeViewwithTerrainExtensionandTerrainLayersource -- draped layers and terrain preview render correctlyUSE_HEIGHT_MAP(elevation offsets for icons/text) correct on both projectionsUSE_COVER(draped overlays on terrain surface) correct on both projectionstest/modules/extensions/terraintest/modules/geo-layers/terrain-layer.spec.tstest/modules/geo-layers/tileset-2d/utils.spec.tsCompanion PRs
TerrainLayerGlobeView + grid-tesselator work. This branch only pulls in the minimal non-tessellator fixes needed to preview this extension change cleanly on globe.