Skip to content

fix(core): scale cartesian z at the viewport latitude below zoom 12 - #10790

Merged
chrisgervang merged 6 commits into
visgl:masterfrom
mfrnd:fix/7064-cartesian-z-scale
Oct 11, 2026
Merged

chrisgervang merged 6 commits into
visgl:masterfrom
mfrnd:fix/7064-cartesian-z-scale

Conversation

@mfrnd

@mfrnd mfrnd commented Oct 3, 2026 •

Copy link
Copy Markdown
Contributor

For #7064

Background

Below zoom 12, getOffsetOrigin uses [0, 0, 0] as geospatial origin for 'cartesian' positions, so their z (meters) is scaled at the equator, cos(latitude) too small. Tiled TerrainLayer meshes are cartesian: the terrain is too flat below zoom 12 (0.69 of its height at Mount St. Helens) and jumps at 12, and TerrainExtension layers jump with it.

Change List

  • getOffsetOrigin: keep the viewport center as origin for 'cartesian'
  • terrain-layer, terrain-extension-drape, terrain-extension-offset golden images: terrain at true height
  • terrain-extension-offset: re-enable, skipped on WebGPU only
  • Unit tests
  • Render tests

before-after
Left old, right new: markers at their DEM elevation sit on the terrain only with the fix.

Verified after merging master f509179, in headless Chromium with SwiftShader: lint, build, node, headless and render tests, no new failures against master. On d1b0ae4, picked elevation over DEM elevation went from 0.696 to 1.002 at zoom 11.5 to 11.99.


Note

Medium Risk
Touches core shader projection uniforms used by all geospatial layers; behavior change is scoped to cartesian + WEB_MERCATOR but affects terrain and draped extensions at sub-zoom-12 views.

Overview
Fixes incorrect vertical scaling for COORDINATE_SYSTEM.CARTESIAN layers (e.g. TerrainLayer meshes) when the map uses Web Mercator below zoom 12. Previously, cartesian positions shared the lnglat path and used a [0,0,0] geospatial origin, so z in meters was scaled like the equator—terrain looked too flat and jumped when crossing zoom 12 into WEB_MERCATOR_AUTO_OFFSET.

In getOffsetOrigin for WEB_MERCATOR, cartesian is split from lnglat: the viewport center is unprojected to WGS84 and used as geospatialOrigin, matching how z is scaled at the viewport latitude in auto-offset mode.

Adds GLSL, uniform, and zoom-12 continuity unit tests, updates terrain golden images, and re-enables the terrain-extension-offset render case (still skipped on WebGPU).

Reviewed by Cursor Bugbot for commit 99faf9a. Bugbot is set up for automated code reviews on this repo. Configure here.

For visgl#7064. Below zoom 12, getOffsetOrigin sets the geospatial origin to
[0,0,0] for both 'lnglat' and 'cartesian' (since visgl#6117). Lnglat is
corrected per vertex in the shader, but cartesian z (meters) is not, so it
is scaled cos(latitude) too small, while from zoom 12 it uses the viewport
latitude. Tiled TerrainLayer meshes are cartesian, so the terrain is too
flat below zoom 12 (0.69 of its height at Mount St. Helens) and jumps at
12; TerrainExtension layers jump with it.

In WEB_MERCATOR mode, keep the viewport center as geospatial origin for
'cartesian', so z is scaled as in WEB_MERCATOR_AUTO_OFFSET mode.

Update the terrain-layer and terrain-extension-drape golden images: their
terrain is at zoom 11.5 and is now drawn at its true height.
@greptile-apps

greptile-apps Bot commented Oct 3, 2026 •

Copy link
Copy Markdown

RetriggerConfidence Score: 5/5

[Medium impact] The PR appears safe to merge; no outstanding finding or new actionable issue was identified.

Summary

Fixes Cartesian elevation scaling below zoom 12 by using the viewport center’s latitude, and updates projection tests and terrain render references.

  • Adds coverage for elevation-scale continuity across zoom 12, including a north–south viewport position offset.
  • Re-enables the terrain extension offset render case on WebGL.

Reviews (5) · Last reviewed commit: "Merge branch 'master' into fix/7064-cart..." · Reviewed by Greptile

Comment thread modules/core/src/shaderlib/project/viewport-uniforms.ts
@coveralls

coveralls commented Oct 3, 2026 •

Copy link
Copy Markdown

Coverage Status

coverage: 83.633% (+0.002%) from 83.631% — mfrnd:fix/7064-cartesian-z-scale into visgl:master

mfrnd and others added 2 commits October 3, 2026 03:09
…ow zoom 12

With a north-south position offset the viewport center lies at a different
latitude than viewport.latitude. Unproject the center as in
WEB_MERCATOR_AUTO_OFFSET mode, so the z scale of cartesian positions stays
continuous at zoom 12 with an offset too (1.66% jump at 100 km before).

@charlieforward9 charlieforward9 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems straightforward and smoothed.

Another contributor has been working on terrain and elevation compatabilities. Pinging to ensure the work is not duplicated across another PR.

@mfrnd

mfrnd commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor Author

A live before/after for this PR, measured from what each build draws: TerrainLayer on a MapView, master on the left.

terrain-layer

In the side view, the orange line is the terrain as each build draws it, over the elevation data in brown. It is picked with unproject3D along the center column of the view. Below zoom 12, master draws the terrain at 69 to 70% of its height (cos 46.2° at Mount St. Helens), and the terrain pops up at 12. With this PR it is at 100% at every zoom. Point by point it is within 5 m of the elevation tiles in the median, and 43 m at most. The camera is the same in both builds.

It is measured in Edge on a GPU. The demo's source is in my fork at scripts/globe-repro.

I guided and reviewed Claude Code 5.5 xhigh in creating this comment and the demo.

@chrisgervang chrisgervang left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the explainer and the fix. The unit tests cover the regression well. I noticed there wasn't a terrain extension offset mode render test re-rendered.

Would it be possible to add one that illustrates the issue and fix clearly?

@chrisgervang chrisgervang mentioned this pull request Oct 7, 2026
37 tasks
mfrnd added 2 commits October 9, 2026 23:10
… fix

The case was skipped because it no longer matched its 2023 golden image, which already showed the terrain flattened below zoom 12, with older fill colors. Regenerated with this fix: the terrain and the icons offset onto it rise to their height. With master's getOffsetOrigin the case matches 94.9%, below the 99% threshold. Skipped on WebGPU like the other TerrainExtension cases.
@mfrnd

mfrnd commented Oct 9, 2026

Copy link
Copy Markdown
Contributor Author

[...] I noticed there wasn't a terrain extension offset mode render test re-rendered.

Would it be possible to add one that illustrates the issue and fix clearly?

There was an offset mode case, terrain-extension-offset, but it has been skipped since the Vitest migration (#9969) because it no longer matched its golden image from 2023. That image already had the bug, the case is at zoom 11.5, and the fill colors have changed since. I re-enabled it with a golden image rendered with this fix. With master's getOffsetOrigin it now fails at 94.9%, as do terrain-layer and terrain-extension-drape (89% and 91%).

offset-master-vs-fix

On master the terrain is drawn at about 79% of its height at this latitude (cos 37.75°), so with the fix the hills, and the icons offset onto them, rise. The case is skipped on WebGPU like the other TerrainExtension cases; once #10751 brings offset mode to WebGPU, it could run there too.

I guided and reviewed Claude Code 5.5 xhigh in creating this comment.

@ibgreen ibgreen left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The main correction is sound: scaling preprojected elevation at the unprojected viewport center fixes the large low-zoom flattening and also handles a north-south camera position offset consistently across zoom 12. The latest CI checks, including WebGPU and coverage, are green.

I verified the projection path with an isolated WebGL GPU probe. There is a much smaller residual discontinuity from the second-order Y correction, described inline. I consider that a non-blocking follow-up to this substantial fix. Approving; I did not rerun the full suite locally.

// Positions are already in common space and only z is in meters. Use the viewport center
// as geospatial origin so that z is scaled the same as in WEB_MERCATOR_AUTO_OFFSET mode
offsetMode = false;
geospatialOrigin = viewport.unprojectPosition([

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Non-blocking follow-up: the second-order correction still introduces a small zoom-12 height step. Moving the geospatial origin away from the equator makes commonUnitsPerWorldUnit2.z nonzero. Below zoom 12, project_offset_ multiplies it by absolute common-space Y; above zoom 12, Y is relative to the viewport center.

An isolated WebGL GPU probe against this commit, using project_position at the viewport center with 1,000 m elevation and latitude 80 degrees, returned:

  • zoom 11.99: 1000.4046 m
  • zoom 12: 1000.0000 m

At latitude 46 degrees the residual step is about 0.054 m per 1,000 m elevation. This is much smaller than the original flattening, but the new continuity tests inspect only the base uniform scale, so they miss the extra shader term.

Could we ensure the correction uses center-relative Y in the non-offset path (or omit it there), and add a GPU continuity test that checks the final projected Z on both sides of zoom 12, including northern and southern latitudes? This can be handled as a focused follow-up rather than blocking the main fix.

@greptile-apps

greptile-apps Bot commented Oct 11, 2026

Copy link
Copy Markdown

Want your agent to iterate on Greptile's feedback? Try greploops.

@chrisgervang
chrisgervang merged commit 92bb3e5 into visgl:master Oct 11, 2026
6 checks passed
@chrisgervang

Copy link
Copy Markdown
Collaborator

@mfrnd merged this in. Please see @ibgreen's follow up note

@mfrnd

mfrnd commented Oct 11, 2026

Copy link
Copy Markdown
Contributor Author

@chrisgervang @ibgreen Follow-up in #10853

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.

5 participants