Skip to content

Commit a417eb1

Browse files
committed
(test) Coverage for core changes
1 parent c681ee7 commit a417eb1

5 files changed

Lines changed: 67 additions & 93 deletions

File tree

modules/arcgis/src/deck-renderer.ts

Lines changed: 21 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -103,16 +103,18 @@ export default function createDeckRenderer(DeckProps, RenderNode) {
103103
cancelInitialization: (() => void) | null = null;
104104
renderNode: any = null;
105105

106-
constructor(view: SceneView, props: DeckProps) {
107-
this.view = view;
108-
this.deck = new DeckProps(props);
106+
constructor(viewOrProps: SceneView | ({view: SceneView} & DeckProps), props?: DeckProps) {
107+
if (viewOrProps && typeof viewOrProps === 'object' && 'view' in viewOrProps) {
108+
const {view, ...deckProps} = viewOrProps;
109+
this.view = view;
110+
this.deck = new DeckProps(deckProps);
111+
} else {
112+
this.view = viewOrProps;
113+
this.deck = new DeckProps(props);
114+
}
109115

110-
// Guard against concurrent initialization attempts (e.g. if render fires
111-
// multiple times before the async init completes).
112116
let isInitializing = false;
113117

114-
// Alias outer `this` for use inside the RenderNode.createSubclass()
115-
// callbacks below, where `this` refers to the RenderNode instance.
116118
// eslint-disable-next-line @typescript-eslint/no-this-alias
117119
const self = this;
118120
const DeckRenderNode = RenderNode.createSubclass({
@@ -122,15 +124,11 @@ export default function createDeckRenderer(DeckProps, RenderNode) {
122124
initialize() {},
123125

124126
render(inputs: Array<{name: string}>) {
125-
// `this` here is the RenderNode instance, typed loosely because
126-
// ArcGIS's RenderNode.createSubclass() does not surface types.
127127
// biome-ignore lint/complexity/noThisInStatic: RenderNode render is an instance method
128128
// eslint-disable-next-line @typescript-eslint/no-this-alias, consistent-this
129129
const renderNode: any = this;
130130
const passthrough = inputs.find(i => i.name === 'composite-color');
131131

132-
// Lazy init: the first render() call is the earliest point at which
133-
// `gl` is guaranteed to be available on the RenderNode.
134132
if (!self.resources && !isInitializing) {
135133
const gl = renderNode.gl;
136134
if (gl) {
@@ -164,59 +162,31 @@ export default function createDeckRenderer(DeckProps, RenderNode) {
164162
return passthrough;
165163
}
166164

167-
// bindRenderTarget() must be called BEFORE render() so that the
168-
// FRAMEBUFFER_BINDING captured inside commons.render() points at the
169-
// OUTPUT framebuffer rather than the composite-color INPUT.
170165
const output = renderNode.bindRenderTarget();
171-
172-
// Use gl drawing buffer rather than view.size, which can include
173-
// non-GL chrome pixels.
174166
const gl = renderNode.gl as WebGL2RenderingContext;
175167

176168
const dpr = window.devicePixelRatio || 1;
177169
const width = gl.drawingBufferWidth / dpr;
178170
const height = gl.drawingBufferHeight / dpr;
179171

180-
// deck.gl's 512-px tile convention: at zoom z / latitude φ,
181-
// ground-meters-per-pixel = 78271.5 * cos(φ) / 2^z. ArcGIS's
182-
// `view.scale` is related to ArcGIS's `view.resolution` by
183-
// `resolution = scale / 3779.527559` (96 DPI), where resolution
184-
// is *projected* Web-Mercator meters-per-pixel. Ground mpp at
185-
// latitude is `resolution * cos(φ)` — the cos(φ) stretch is
186-
// baked into both sides, so it cancels when equating deck ground
187-
// mpp to ArcGIS ground mpp. The zoom formula is therefore:
188-
// zoom = log2(591657550.5 / view.scale) - 1
189-
// (the `-1` converts the 256-px constant to deck's 512-px
190-
// convention.) Earlier revisions included a `* cos(lat)` factor
191-
// here; that was wrong — it was inadvertently matching
192-
// *projected* Mercator mpp instead of ground mpp, and caused
193-
// the deck layer to render ~26% smaller than the basemap at SF
194-
// latitudes.
195-
// deck.gl's `altitude` is the camera distance above the focal
196-
// plane, measured in viewport-height units. In deck.gl,
197-
// `altitude` couples camera distance with projection FOV:
198-
// verticalFOV = 2 * atan(0.5 / altitude)
199-
// and the camera sits at slant distance = altitude from the
200-
// focal point regardless of pitch.
201-
//
202-
// ArcGIS behaves differently: it uses a fixed 55° *diagonal*
203-
// FOV and moves the camera closer to the focal point as tilt
204-
// increases (keeping the focal point at screen center). Using
205-
// the FOV-only formula (altitude = 0.5 / tan(verticalFOV/2))
206-
// works at tilt=0 but diverges at high tilt because ArcGIS's
207-
// actual camera is closer.
172+
// Keep deck.gl's SceneView approximation anchored at the ArcGIS
173+
// focal point every frame. Zoom comes from the actual horizontal
174+
// ground meters-per-pixel at screen center (`toMap(center)` and
175+
// `toMap(center + 1px)`), which stays stable under tilt in local
176+
// SceneView. We keep the older `view.scale` formula only as a
177+
// fallback when focal-point sampling is unavailable.
208178
//
209-
// We match ArcGIS by deriving altitude from the *actual*
210-
// slant distance between `camera.position` and the focal
211-
// point on the ground, expressed in viewport-height units.
179+
// deck.gl's `altitude` still couples camera distance and FOV, while
180+
// ArcGIS keeps them separate. We therefore compute a slant-distance
181+
// altitude from the live camera position and focal point, then only
182+
// at high tilt blend toward the ArcGIS fixed-FOV altitude. That
183+
// preserves the better mid-tilt camera match while reducing the
184+
// remaining extreme-angle drift.
212185
const cameraPos = self.view.camera.position;
213186
const focalPoint = self.view.toMap({x: width / 2, y: height / 2} as any);
214187
const latitude = focalPoint ? focalPoint.latitude : self.view.center.latitude;
215188
const longitude = focalPoint ? focalPoint.longitude : self.view.center.longitude;
216189

217-
// Horizontal ground distance from camera to focal point (meters).
218-
// Use a flat-earth approximation; viewingMode='local' in the
219-
// SceneView makes this exact.
220190
const midLatRad = (((latitude + cameraPos.latitude) / 2) * Math.PI) / 180;
221191
const dLatM = (cameraPos.latitude - latitude) * METERS_PER_DEG_LAT;
222192
const dLngM =
@@ -226,8 +196,6 @@ export default function createDeckRenderer(DeckProps, RenderNode) {
226196

227197
const zoom = getZoom(self.view, longitude, latitude, width, height);
228198

229-
// Viewport height in meters at the focal plane. ArcGIS's
230-
// `view.resolution` is meters-per-pixel at the focal point.
231199
const viewportHeightM = height * self.view.resolution;
232200
const slantAltitude = slantM / viewportHeightM;
233201
const altitude = getAltitude(self.view.camera.tilt, slantAltitude, width, height);
@@ -277,8 +245,6 @@ export default function createDeckRenderer(DeckProps, RenderNode) {
277245
this.renderNode = new DeckRenderNode({view: this.view});
278246
}
279247

280-
// Kept for API compatibility. Resource initialization now happens lazily
281-
// inside the RenderNode's render() callback on the first frame.
282248
// eslint-disable-next-line @typescript-eslint/no-unused-vars
283249
async setup(_context?: unknown) {}
284250

test/apps/arcgis-i3s/app.js

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@ loadArcGISModules(['esri/Map', 'esri/views/SceneView', 'esri/views/3d/webgl/Rend
3232
viewingMode: 'local'
3333
});
3434

35-
const renderer = new DeckRenderer(sceneView, {
35+
const renderer = new DeckRenderer({
36+
view: sceneView,
3637
layers: [
3738
new Tile3DLayer({
3839
id: 'tile-3d-layer',
@@ -42,13 +43,4 @@ loadArcGISModules(['esri/Map', 'esri/views/SceneView', 'esri/views/3d/webgl/Rend
4243
})
4344
]
4445
});
45-
46-
// DeckRenderer is an ArcGIS RenderNode; it self-registers with the
47-
// SceneView via the view property and does not need to be added as a layer.
48-
// eslint-disable-next-line no-unused-expressions
49-
renderer;
50-
51-
// Debug hook
52-
window.__sceneView = sceneView;
53-
window.__deckRenderer = renderer;
5446
});

test/apps/arcgis/app.js

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -68,10 +68,7 @@ loadArcGISModules(
6868
viewingMode: 'local'
6969
});
7070

71-
const renderer = new DeckRenderer(sceneView, {});
72-
73-
// DeckRenderer is an ArcGIS RenderNode; it registers itself with the
74-
// SceneView via the view property and does not need to be added as a layer.
71+
const renderer = new DeckRenderer({view: sceneView});
7572

7673
/* global setInterval */
7774
setInterval(() => {

test/modules/core/shaderlib/project/project-functions.spec.ts

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -149,17 +149,25 @@ test('project#projectPosition', () => {
149149
}
150150
});
151151

152-
test('project#projectPosition rejects legacy numeric coordinate systems', () => {
152+
test('project#projectPosition normalizes legacy numeric coordinate systems', () => {
153+
const numericResult = projectPosition([-122.46, 37.8, 1000], {
154+
viewport: TEST_VIEWPORT,
155+
coordinateSystem: 2 as never,
156+
coordinateOrigin: TEST_COORDINATE_ORIGIN,
157+
fromCoordinateSystem: 1 as never
158+
});
159+
160+
const stringResult = projectPosition([-122.46, 37.8, 1000], {
161+
viewport: TEST_VIEWPORT,
162+
coordinateSystem: 'meter-offsets',
163+
coordinateOrigin: TEST_COORDINATE_ORIGIN,
164+
fromCoordinateSystem: 'lnglat'
165+
});
166+
153167
expect(
154-
() =>
155-
projectPosition([-122.46, 37.8, 1000], {
156-
viewport: TEST_VIEWPORT,
157-
coordinateSystem: 2 as never,
158-
coordinateOrigin: TEST_COORDINATE_ORIGIN,
159-
fromCoordinateSystem: 1 as never
160-
}),
161-
'Legacy numeric coordinate systems are rejected'
162-
).toThrow(/Invalid coordinateSystem/);
168+
equals(numericResult, stringResult),
169+
'Legacy numeric coordinate systems normalize to string equivalents'
170+
).toBeTruthy();
163171

164172
const identityResult = projectPosition([0, 0, 0], {
165173
viewport: TEST_VIEWPORT,

test/modules/core/shaderlib/project/viewport-uniforms.spec.ts

Lines changed: 25 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -154,21 +154,32 @@ test('project#getUniforms', () => {
154154
).toBeTruthy();
155155
});
156156

157-
test('project#getUniforms rejects legacy numeric coordinate systems', () => {
158-
expect(
159-
() => project.getUniforms({viewport: TEST_VIEWPORTS.map, coordinateSystem: -1 as never}),
160-
'Legacy DEFAULT is rejected'
161-
).toThrow(/Invalid coordinateSystem/);
157+
test('project#getUniforms normalizes legacy numeric coordinate systems', () => {
158+
const numericDefault = project.getUniforms({
159+
viewport: TEST_VIEWPORTS.map,
160+
coordinateSystem: -1 as never
161+
});
162+
const stringDefault = project.getUniforms({
163+
viewport: TEST_VIEWPORTS.map,
164+
coordinateSystem: COORDINATE_SYSTEM.DEFAULT
165+
});
162166

163-
expect(
164-
() =>
165-
project.getUniforms({
166-
viewport: TEST_VIEWPORTS.mapHighZoom,
167-
coordinateSystem: 2 as never,
168-
coordinateOrigin: Object.freeze([-122.4, 37.7])
169-
}),
170-
'Legacy offsets are rejected'
171-
).toThrow(/Invalid coordinateSystem/);
167+
expect(numericDefault, 'Legacy DEFAULT normalizes to current DEFAULT').toEqual(stringDefault);
168+
169+
const numericOffsets = project.getUniforms({
170+
viewport: TEST_VIEWPORTS.mapHighZoom,
171+
coordinateSystem: 2 as never,
172+
coordinateOrigin: Object.freeze([-122.4, 37.7])
173+
});
174+
const stringOffsets = project.getUniforms({
175+
viewport: TEST_VIEWPORTS.mapHighZoom,
176+
coordinateSystem: COORDINATE_SYSTEM.METER_OFFSETS,
177+
coordinateOrigin: Object.freeze([-122.4, 37.7])
178+
});
179+
180+
expect(numericOffsets, 'Legacy offsets normalize to current meter-offsets behavior').toEqual(
181+
stringOffsets
182+
);
172183
});
173184

174185
test('project#getUniforms normalizes IDENTITY to CARTESIAN', () => {

0 commit comments

Comments
 (0)