@tangram.gl/tangram-layers 0.0.0-stage → 0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/README.md +35 -2
  2. package/dist/experimental/projected-basemaps.js +1310 -0
  3. package/dist/experimental/projected-basemaps.js.map +6 -0
  4. package/dist/experimental/terrain.js +379 -0
  5. package/dist/experimental/terrain.js.map +6 -0
  6. package/dist/experimental/webxr.js +1423 -0
  7. package/dist/experimental/webxr.js.map +6 -0
  8. package/dist/index.js +756 -0
  9. package/dist/index.js.map +6 -0
  10. package/dist/types/experimental/projected-basemaps.d.ts +120 -0
  11. package/dist/types/experimental/projected-detail.d.ts +33 -0
  12. package/dist/types/experimental/projected-navigation.d.ts +117 -0
  13. package/dist/types/experimental/terrain-display.d.ts +124 -0
  14. package/dist/types/experimental/terrain.d.ts +131 -0
  15. package/dist/types/first_person_view_adapter.d.ts +29 -0
  16. package/dist/types/globe_view_adapter.d.ts +33 -0
  17. package/dist/types/index.d.ts +84 -0
  18. package/dist/types/tangram-layer-lifecycle.d.ts +132 -0
  19. package/dist/types/tangram-layer.d.ts +157 -0
  20. package/dist/types/view_adapter_types.d.ts +42 -0
  21. package/dist/types/web_mercator_view_adapter.d.ts +77 -0
  22. package/package.json +80 -3
  23. package/src/experimental/projected-basemaps.ts +301 -0
  24. package/src/experimental/projected-detail.ts +116 -0
  25. package/src/experimental/projected-navigation.ts +323 -0
  26. package/src/experimental/terrain-display.ts +243 -0
  27. package/src/experimental/terrain.ts +240 -0
  28. package/src/experimental/webxr/grabbing.ts +166 -0
  29. package/src/experimental/webxr/index.d.ts +77 -0
  30. package/src/experimental/webxr/index.ts +72 -0
  31. package/src/experimental/webxr/interaction.ts +221 -0
  32. package/src/experimental/webxr/picking.ts +119 -0
  33. package/src/experimental/webxr/presentation.ts +457 -0
  34. package/src/experimental/webxr/projection.ts +371 -0
  35. package/src/experimental/webxr/types.ts +221 -0
  36. package/src/experimental/webxr/views.ts +190 -0
  37. package/src/first_person_view_adapter.ts +107 -0
  38. package/src/globe_view_adapter.ts +103 -0
  39. package/src/index.ts +64 -0
  40. package/src/tangram-layer-lifecycle.ts +130 -0
  41. package/src/tangram-layer.ts +679 -0
  42. package/src/view_adapter_types.ts +40 -0
  43. package/src/web_mercator_view_adapter.ts +185 -0
@@ -0,0 +1,190 @@
1
+ // tangram-layers
2
+ // SPDX-License-Identifier: MIT
3
+ // Copyright (c) vis.gl contributors
4
+
5
+ import {
6
+ _GlobeController as GlobeController,
7
+ FirstPersonView,
8
+ FirstPersonController,
9
+ MapController,
10
+ MapView,
11
+ _GlobeView as GlobeView
12
+ } from '@deck.gl/core';
13
+ import type {FirstPersonViewProps, GlobeViewProps, MapViewState, GlobeViewState, WebMercatorViewport, FirstPersonViewport, _GlobeViewport as GlobeViewport} from '@deck.gl/core';
14
+ import type {MjolnirGestureEvent} from 'mjolnir.js';
15
+ import type {HostCamera} from '@tangram.gl/tangram-renderer/core';
16
+ import WebMercatorViewAdapter from '../../web_mercator_view_adapter.js';
17
+ import FirstPersonViewAdapter, {getFirstPersonFrameForCamera} from '../../first_person_view_adapter.js';
18
+ import GlobeViewAdapter from '../../globe_view_adapter.js';
19
+ import type {XRDeckViewport, XREyeViewportOptions, XRHostFrameFields, XRProjectionOptions} from './types.js';
20
+
21
+ /** Shared conservative visibility policy for mono and actual stereo eye cameras. */
22
+ type FirstPersonVisibilityProperties = {
23
+ /** Maximum local geographic east/north extent per axis, in meters. */
24
+ firstPersonMaxGroundExtent?: number;
25
+ /** Finite ordered physical height interval, in meters; defaults to ground only. */
26
+ firstPersonElevationRange?: readonly [number, number];
27
+ };
28
+
29
+ /**
30
+ * Map controller that keeps touch pinch rotation enabled while treating a
31
+ * two-finger trackpad gesture as a map pan.
32
+ *
33
+ * deck.gl uses the same `multiTouchDrag` option for both gesture families.
34
+ * The temporary mode below lets the normal controller state handle each
35
+ * gesture without duplicating deck.gl's pan and rotate calculations.
36
+ */
37
+ export class WebXRMapController extends MapController {
38
+ _onMultiPanStart(event: MjolnirGestureEvent) {
39
+ const multiTouchDrag = this.multiTouchDrag;
40
+ if (event.pointerType === 'trackpad') {
41
+ this.multiTouchDrag = 'pan';
42
+ }
43
+ const handled = super._onMultiPanStart(event);
44
+ this.multiTouchDrag = multiTouchDrag;
45
+ return handled;
46
+ }
47
+ }
48
+
49
+ /** First-person controller with separate trackpad pan and touch pinch modes. */
50
+ export class WebXRFirstPersonController extends FirstPersonController {
51
+ _onMultiPanStart(event: MjolnirGestureEvent) {
52
+ const multiTouchDrag = this.multiTouchDrag;
53
+ if (event.pointerType === 'trackpad') {
54
+ this.multiTouchDrag = 'pan';
55
+ }
56
+ const handled = super._onMultiPanStart(event);
57
+ this.multiTouchDrag = multiTouchDrag;
58
+ return handled;
59
+ }
60
+ }
61
+
62
+ /** Globe controller with separate trackpad pan and touch pinch modes. */
63
+ export class WebXRGlobeController extends GlobeController {
64
+ _onMultiPanStart(event: MjolnirGestureEvent) {
65
+ const multiTouchDrag = this.multiTouchDrag;
66
+ if (event.pointerType === 'trackpad') {
67
+ this.multiTouchDrag = 'pan';
68
+ }
69
+ const handled = super._onMultiPanStart(event);
70
+ this.multiTouchDrag = multiTouchDrag;
71
+ return handled;
72
+ }
73
+ }
74
+
75
+ /** MapView that can derive Tangram host-frame fields and per-eye viewports. */
76
+ export class WebXRMapView extends MapView {
77
+ static displayName = 'WebXRMapView';
78
+
79
+ makeEyeViewport({width, height, viewState, eyeOffset = 0}: XREyeViewportOptions<MapViewState>) {
80
+ const position = viewState.position || [0, 0, 0];
81
+ return this.makeViewport({
82
+ width,
83
+ height,
84
+ viewState: {...viewState, position: [position[0] + eyeOffset, position[1], position[2]]}
85
+ });
86
+ }
87
+
88
+ getHostFrame(viewport: WebMercatorViewport): XRHostFrameFields {
89
+ return {
90
+ view: {
91
+ longitude: viewport.longitude,
92
+ latitude: viewport.latitude,
93
+ zoom: viewport.zoom + 1
94
+ },
95
+ projection: {type: 'web-mercator'},
96
+ camera: WebMercatorViewAdapter.getCameraFrame(viewport),
97
+ tileBuffer: Math.min(
98
+ 4,
99
+ Math.ceil(
100
+ (Math.tan((Math.abs(viewport.pitch || 0) * Math.PI) / 180) * viewport.height) / 256
101
+ )
102
+ )
103
+ };
104
+ }
105
+
106
+ getXRProjectionMatrix({projectionMatrix}: XRProjectionOptions) {
107
+ return projectionMatrix;
108
+ }
109
+ }
110
+
111
+ /** FirstPersonView that can derive Tangram host-frame fields and per-eye viewports. */
112
+ export class WebXRFirstPersonView extends FirstPersonView {
113
+ static displayName = 'WebXRFirstPersonView';
114
+ /** Base deck view settings plus Tangram's bounded ground visibility policy. */
115
+ declare props: FirstPersonViewProps & FirstPersonVisibilityProperties;
116
+
117
+ /** Configure the logical first-person view and optional ground extent. */
118
+ constructor(props: FirstPersonViewProps & FirstPersonVisibilityProperties = {}) {
119
+ super(props);
120
+ }
121
+
122
+ makeEyeViewport({width, height, viewState, eyeOffset = 0}: XREyeViewportOptions) {
123
+ const position = viewState.position || [0, 0, 0];
124
+ return this.makeViewport({
125
+ width,
126
+ height,
127
+ viewState: {...viewState, position: [position[0] + eyeOffset, position[1], position[2]]}
128
+ });
129
+ }
130
+
131
+ getHostFrame(viewport: FirstPersonViewport): XRHostFrameFields {
132
+ const frame = FirstPersonViewAdapter.getFrame(viewport, {maxGroundExtent: this.props.firstPersonMaxGroundExtent ?? undefined,
133
+ elevationRange: this.props.firstPersonElevationRange});
134
+ return {
135
+ view: frame.view,
136
+ projection: frame.projection,
137
+ camera: frame.camera,
138
+ tileBuffer: frame.tileBuffer
139
+ };
140
+ }
141
+
142
+ /** Recomputes ground visibility after stereo or immersive camera transforms. */
143
+ getHostFrameForCamera(viewport: XRDeckViewport, camera: HostCamera, options: {width?: number; height?: number} = {}): XRHostFrameFields {
144
+ return getFirstPersonFrameForCamera(viewport, camera, {
145
+ ...options,
146
+ maxGroundExtent: this.props.firstPersonMaxGroundExtent ?? undefined,
147
+ elevationRange: this.props.firstPersonElevationRange
148
+ });
149
+ }
150
+
151
+ getXRProjectionMatrix({projectionMatrix}: XRProjectionOptions) {
152
+ return projectionMatrix;
153
+ }
154
+ }
155
+
156
+ /** GlobeView that can derive Tangram host-frame fields and per-eye viewports. */
157
+ export class WebXRGlobeView extends GlobeView {
158
+ static displayName = 'WebXRGlobeView';
159
+ /** Base deck view settings plus Tangram's globe visibility policies. */
160
+ declare props: GlobeViewProps & {globeMaxElevation?: number; globePreloadZoom?: number};
161
+
162
+ /** Configure a globe view and its optional globally resident loading fallback. */
163
+ constructor(props: GlobeViewProps & {globeMaxElevation?: number; globePreloadZoom?: number} = {}) {
164
+ super(props);
165
+ }
166
+
167
+ makeEyeViewport({width, height, viewState, eyeOffset = 0}: XREyeViewportOptions<GlobeViewState>) {
168
+ return this.makeViewport({
169
+ width,
170
+ height,
171
+ viewState: {...viewState, longitude: viewState.longitude + eyeOffset * 0.06}
172
+ });
173
+ }
174
+
175
+ getHostFrame(viewport: GlobeViewport): XRHostFrameFields {
176
+ const frame = GlobeViewAdapter.getFrame(viewport, {maxElevation: this.props.globeMaxElevation ?? undefined,
177
+ preloadZoom: this.props.globePreloadZoom ?? undefined});
178
+ return {
179
+ view: frame.view,
180
+ projection: frame.projection,
181
+ camera: frame.camera,
182
+ tileBuffer: frame.tileBuffer,
183
+ globePreloadZoom: frame.globePreloadZoom
184
+ };
185
+ }
186
+
187
+ getXRProjectionMatrix({projectionMatrix, viewMatrix}: XRProjectionOptions) {
188
+ return projectionMatrix.clone().multiplyRight(viewMatrix);
189
+ }
190
+ }
@@ -0,0 +1,107 @@
1
+ // tangram-layers
2
+ // SPDX-License-Identifier: MIT
3
+ // Copyright (c) vis.gl contributors
4
+
5
+ import {Matrix4} from '@math.gl/core';
6
+ import {calculatePlanarVolumeBounds, type HostCamera, type LegacyHostFrame} from '@tangram.gl/tangram-renderer/core';
7
+ import WebMercatorViewAdapter from './web_mercator_view_adapter.js';
8
+ import type {FirstPersonViewport} from './view_adapter_types.js';
9
+
10
+ const DECK_WORLD_SIZE = 512;
11
+ const HALF_WORLD_METERS = 20037508.342789244;
12
+ const TILE_SIZE = 256;
13
+ const MAX_MERCATOR_LATITUDE = 85.05112878;
14
+ const DEFAULT_GROUND_EXTENT = 20000;
15
+
16
+ /** Rendering dimensions and bounded first-person ground visibility policy. */
17
+ export type FirstPersonViewAdapterOptions = {
18
+ /** Target width in CSS pixels; defaults to viewport width. */
19
+ width?: number;
20
+ /** Target height in CSS pixels; defaults to viewport height. */
21
+ height?: number;
22
+ /** Maximum east/north extent from the eye in local geographic meters, per axis. Defaults to 20 km. */
23
+ maxGroundExtent?: number;
24
+ /** Conservative minimum/maximum physical heights in meters; defaults to ground only, [0, 0]. */
25
+ elevationRange?: readonly [number, number];
26
+ };
27
+
28
+ /** Typed bounded ground-footprint boundary for deck.gl FirstPersonView. */
29
+ export default class FirstPersonViewAdapter {
30
+ /** Converts the finite frustum/ground intersection without changing camera matrices. */
31
+ static getFrame = getFrame;
32
+ }
33
+
34
+ /**
35
+ * Derives geographic tile visibility from the full finite camera frustum.
36
+ * Near/far planes and an eye-centered square limit bound horizon intersections.
37
+ * Looking entirely away from ground supplies explicit empty visibility instead
38
+ * of an error. An optional elevation slab includes elevated-only geometry;
39
+ * it is a conservative footprint, not a terrain intersection or occlusion test.
40
+ */
41
+ function getFrame(viewport: FirstPersonViewport, options: FirstPersonViewAdapterOptions = {}): LegacyHostFrame {
42
+ return getFirstPersonFrameForCamera(viewport, WebMercatorViewAdapter.getCameraFrame(viewport), options);
43
+ }
44
+
45
+ /** Derives flat-ground visibility from the actual mono or per-eye EPSG:3857 camera. */
46
+ export function getFirstPersonFrameForCamera(
47
+ viewport: FirstPersonViewport,
48
+ camera: HostCamera,
49
+ options: FirstPersonViewAdapterOptions = {}
50
+ ): LegacyHostFrame {
51
+ const width = options.width ?? viewport.width;
52
+ const height = options.height ?? viewport.height;
53
+ const maxGroundExtent = options.maxGroundExtent ?? DEFAULT_GROUND_EXTENT;
54
+ if (!Number.isFinite(width) || width <= 0 || !Number.isFinite(height) || height <= 0) {
55
+ throw new Error('FirstPersonViewport requires positive width and height');
56
+ }
57
+ if (!Number.isFinite(maxGroundExtent) || maxGroundExtent <= 0) {
58
+ throw new Error('FirstPersonViewport maxGroundExtent must be finite and positive');
59
+ }
60
+ const {longitude, latitude} = viewport;
61
+ const unprojectFlat = viewport.unprojectFlat;
62
+ if (typeof longitude !== 'number' || !Number.isFinite(longitude) ||
63
+ typeof latitude !== 'number' || !Number.isFinite(latitude) ||
64
+ typeof unprojectFlat !== 'function') {
65
+ throw new Error('FirstPersonViewport geographic center and ground projection method are required');
66
+ }
67
+ const inverseView = new Matrix4().copy(camera.view);
68
+ if (!Number.isFinite(inverseView.determinant()) || inverseView.determinant() === 0) {
69
+ throw new Error('FirstPersonViewport camera is singular');
70
+ }
71
+ const eye = inverseView.invert().transformAsPoint([0, 0, 0]);
72
+ if (!eye.every(Number.isFinite)) throw new Error('FirstPersonViewport eye position is invalid');
73
+ const latitudeScale = Math.cos(Math.max(-MAX_MERCATOR_LATITUDE,
74
+ Math.min(MAX_MERCATOR_LATITUDE, latitude)) * Math.PI / 180);
75
+ const extent = maxGroundExtent / latitudeScale;
76
+ const bounds = calculatePlanarVolumeBounds(camera, options.elevationRange ?? [0, 0], {
77
+ sw: {x: eye[0] - extent, y: eye[1] - extent},
78
+ ne: {x: eye[0] + extent, y: eye[1] + extent}
79
+ });
80
+ const xyScale = DECK_WORLD_SIZE / (HALF_WORLD_METERS * 2);
81
+ const toGeographic = (x: number, y: number) => unprojectFlat.call(viewport, [
82
+ x * xyScale + DECK_WORLD_SIZE / 2, y * xyScale + DECK_WORLD_SIZE / 2
83
+ ]);
84
+ const center = bounds ? toGeographic((bounds.sw.x + bounds.ne.x) / 2,
85
+ (bounds.sw.y + bounds.ne.y) / 2) : [longitude, latitude];
86
+ const metersPerPixel = bounds ? Math.max((bounds.ne.x - bounds.sw.x) / width,
87
+ (bounds.ne.y - bounds.sw.y) / height) : extent * 2 / Math.min(width, height);
88
+ let visibleBounds: [number, number, number, number] | null = null;
89
+ if (bounds) {
90
+ const southwest = toGeographic(bounds.sw.x, bounds.sw.y);
91
+ const northeast = toGeographic(bounds.ne.x, bounds.ne.y);
92
+ // Keep the longitude interval unwrapped; the renderer's tile wrapping owns world copies.
93
+ visibleBounds = [southwest[0], southwest[1], northeast[0], northeast[1]];
94
+ }
95
+ if (!center.every(Number.isFinite) || !Number.isFinite(metersPerPixel) || metersPerPixel <= 0 ||
96
+ (visibleBounds && !visibleBounds.every(Number.isFinite))) {
97
+ throw new Error('FirstPersonViewport ground footprint is invalid');
98
+ }
99
+ return {
100
+ viewport: {width, height},
101
+ view: {longitude: center[0], latitude: center[1], altitude: eye[2],
102
+ zoom: Math.max(0, Math.log2(HALF_WORLD_METERS * 2 / (TILE_SIZE * metersPerPixel)))},
103
+ projection: {type: 'web-mercator', visibleBounds},
104
+ camera,
105
+ tileBuffer: 1
106
+ };
107
+ }
@@ -0,0 +1,103 @@
1
+ // tangram-layers
2
+ // SPDX-License-Identifier: MIT
3
+ // Copyright (c) vis.gl contributors
4
+
5
+ import type {LegacyHostFrame} from '@tangram.gl/tangram-renderer/core';
6
+ import type {GlobeViewport} from './view_adapter_types.js';
7
+ const DECK_TO_TANGRAM_ZOOM_OFFSET = 1;
8
+
9
+ /** Scene visibility declarations, independent of the host camera's altitude. */
10
+ export type GlobeViewAdapterOptions = {
11
+ /** Conservative maximum rendered height in geographic meters; omit if unknown. */
12
+ maxElevation?: number;
13
+ /** Optional host-supplied footprint enclosing ground AND elevated content. */
14
+ visibleBounds?: readonly [number, number, number, number];
15
+ /** Globally resident loading fallback level, integer 0–3; omit to disable. */
16
+ preloadZoom?: number;
17
+ };
18
+
19
+ /** Typed globe projection and visibility boundary for deck.gl. */
20
+ export default class GlobeViewAdapter {
21
+ /** Converts globe common-space matrices and visible geographic bounds. */
22
+ static getFrame = getFrame;
23
+ }
24
+
25
+ /**
26
+ * Converts a deck.gl GlobeViewport into Tangram's host-frame contract.
27
+ *
28
+ * Globe matrices consume deck common-space coordinates directly. Tangram's
29
+ * renderer converts its EPSG:3857 tile vertices to deck's radius-256 globe in
30
+ * the vertex shader before applying these matrices.
31
+ * GlobeViewport already compensates its camera scale for latitude. The local
32
+ * Mercator-to-sphere derivative cancels that compensation, so data/style zoom
33
+ * needs only the same 512-to-256 tile-size offset as MapView, not another
34
+ * latitude adjustment. Source tile_size and zoom_offset are applied later.
35
+ *
36
+ * @param {object} viewport deck.gl GlobeViewport.
37
+ * @param options Explicit scene height and conservative geographic footprint.
38
+ * @returns {object} Tangram HostFrame fields for a globe render view.
39
+ */
40
+ function getFrame(viewport: GlobeViewport, options: GlobeViewAdapterOptions = {}): LegacyHostFrame {
41
+ if (options.maxElevation !== undefined &&
42
+ (!Number.isFinite(options.maxElevation) || options.maxElevation < 0)) {
43
+ throw new Error('GlobeView maxElevation must be a finite non-negative number');
44
+ }
45
+ if (
46
+ !viewport ||
47
+ !Number.isFinite(viewport.width) ||
48
+ !Number.isFinite(viewport.height) ||
49
+ !viewport.viewMatrix ||
50
+ viewport.viewMatrix.length !== 16 ||
51
+ !viewport.projectionMatrix ||
52
+ viewport.projectionMatrix.length !== 16 ||
53
+ !viewport.cameraPosition || viewport.cameraPosition.length !== 3 || !Array.from(viewport.cameraPosition).every(Number.isFinite) ||
54
+ typeof viewport.getBounds !== 'function'
55
+ ) {
56
+ throw new Error('deck GlobeViewport matrices, camera position, size, and visible bounds are required');
57
+ }
58
+
59
+ const visibleBounds = options.visibleBounds ?? viewport.getBounds({z: 0});
60
+ if (
61
+ !Array.isArray(visibleBounds) ||
62
+ visibleBounds.length !== 4 ||
63
+ visibleBounds.some((value) => !Number.isFinite(value))
64
+ ) {
65
+ throw new Error('deck GlobeViewport must provide finite geographic bounds');
66
+ }
67
+
68
+ return {
69
+ viewport: {width: viewport.width, height: viewport.height},
70
+ view: {
71
+ longitude: viewport.longitude,
72
+ latitude: viewport.latitude,
73
+ zoom: Math.max(0, viewport.zoom + DECK_TO_TANGRAM_ZOOM_OFFSET)
74
+ },
75
+ projection: {
76
+ type: 'globe', visibleBounds: [visibleBounds[0], visibleBounds[1], visibleBounds[2], visibleBounds[3]],
77
+ ...(options.maxElevation === undefined ? {} : {maxElevation: options.maxElevation})
78
+ },
79
+ camera: {
80
+ view: new Float64Array(viewport.viewMatrix),
81
+ projection: new Float32Array(
82
+ multiplyMatrices(viewport.projectionMatrix, viewport.viewMatrix)
83
+ ),
84
+ position: [viewport.cameraPosition[0], viewport.cameraPosition[1], viewport.cameraPosition[2]]
85
+ },
86
+ tileBuffer: 0,
87
+ globePreloadZoom: options.preloadZoom
88
+ };
89
+ }
90
+
91
+ function multiplyMatrices(left: ArrayLike<number>, right: ArrayLike<number>): Float64Array {
92
+ const result = new Float64Array(16);
93
+ for (let column = 0; column < 4; column++) {
94
+ for (let row = 0; row < 4; row++) {
95
+ let value = 0;
96
+ for (let index = 0; index < 4; index++) {
97
+ value += left[index * 4 + row] * right[column * 4 + index];
98
+ }
99
+ result[column * 4 + row] = value;
100
+ }
101
+ }
102
+ return result;
103
+ }
package/src/index.ts ADDED
@@ -0,0 +1,64 @@
1
+ // tangram-layers
2
+ // SPDX-License-Identifier: MIT
3
+ // Copyright (c) vis.gl contributors
4
+
5
+ import {Layer} from '@deck.gl/core';
6
+ import {Renderer as ClassicWebGLRenderer} from '@tangram.gl/tangram-renderer/core';
7
+ import createTangramLayerClass, {
8
+ getExternalCameraFrame,
9
+ getFirstPersonViewFrame,
10
+ getGlobeViewFrame,
11
+ injectNextzenApiKey
12
+ } from './tangram-layer.js';
13
+ import type {TangramLayerProps} from './tangram-layer.js';
14
+
15
+ const BaseTangramLayer = createTangramLayerClass({Layer, ClassicWebGLRenderer, Renderer: undefined});
16
+
17
+ /**
18
+ * A deck.gl basemap layer that renders a Tangram scene into deck's active
19
+ * luma.gl device and render pass.
20
+ */
21
+ class TangramLayer extends BaseTangramLayer {
22
+ /** Preserve deck.gl's complete public context rather than only the bridge capabilities. */
23
+ declare context: Layer<TangramLayerProps>['context'];
24
+ /** Public layer properties retain deck's resolved defaults. */
25
+ declare props: Layer<TangramLayerProps>['props'];
26
+ /** Preserve deck's state update signature through the injected base class. */
27
+ declare setState: Layer<TangramLayerProps>['setState'];
28
+ /** Keep deck's public state surface while the internal adapter checks its own record. */
29
+ declare state: Layer<TangramLayerProps>['state'];
30
+ /** Stable deck.gl metadata for the typed public subclass. */
31
+ static componentName = 'TangramLayer';
32
+ /** Compatibility name for integrations using deck.gl's historical metadata. */
33
+ static layerName = 'TangramLayer';
34
+ /** Preserve deck.gl's public initialization signature through the injected superclass. */
35
+ initializeState(_context: Layer<TangramLayerProps>['context']): void {super.initializeState();}
36
+ /** Forward the standard deck.gl property/state change contract. */
37
+ updateState(parameters: Parameters<Layer<TangramLayerProps>['updateState']>[0]): void {super.updateState(parameters);}
38
+ /** Release the scene using the standard deck.gl finalization signature. */
39
+ finalizeState(_context: Layer<TangramLayerProps>['context']): void {super.finalizeState();}
40
+ /** Forward deck's complete draw options to the existing render-pass bridge. */
41
+ draw(options: Parameters<Layer<TangramLayerProps>['draw']>[0]): void {super.draw(options);}
42
+ }
43
+
44
+ /** deck.gl fields inherited through the dependency-injected runtime superclass. */
45
+ interface TangramLayer extends Layer<TangramLayerProps> {}
46
+
47
+ export {
48
+ TangramLayer,
49
+ createTangramLayerClass,
50
+ getExternalCameraFrame,
51
+ getFirstPersonViewFrame,
52
+ getGlobeViewFrame,
53
+ injectNextzenApiKey
54
+ };
55
+
56
+ export default TangramLayer;
57
+ export type {TangramLayerProps} from './tangram-layer.js';
58
+
59
+ export {default as WebMercatorViewAdapter} from './web_mercator_view_adapter.js';
60
+ export {default as FirstPersonViewAdapter} from './first_person_view_adapter.js';
61
+ export type {FirstPersonViewAdapterOptions} from './first_person_view_adapter.js';
62
+ export {default as GlobeViewAdapter} from './globe_view_adapter.js';
63
+ export type {GlobeViewAdapterOptions} from './globe_view_adapter.js';
64
+ export type {FirstPersonViewport, GlobeViewport, PlanarCameraViewport} from './view_adapter_types.js';
@@ -0,0 +1,130 @@
1
+ // tangram-layers
2
+ // SPDX-License-Identifier: MIT
3
+ // Copyright (c) vis.gl contributors
4
+
5
+ import type {Device, RenderPass} from '@luma.gl/core';
6
+ import type {Scene} from '@tangram.gl/tangram-renderer';
7
+ import type {Renderer, RendererOptions, SceneDefinition, SceneListeners, SceneLoadOptions, RenderOptions} from '@tangram.gl/tangram-renderer/core';
8
+ import type {TangramLayerProps} from './tangram-layer';
9
+ import type {FirstPersonViewport} from './view_adapter_types';
10
+
11
+ /** Geographic viewport capabilities read by the existing view dispatch. */
12
+ export interface TangramLayerViewport extends FirstPersonViewport {
13
+ /** Camera bearing in degrees. */
14
+ bearing?: number;
15
+ /** Class metadata used by deck.gl's existing experimental globe/first-person adapters. */
16
+ constructor: Function & {displayName?: string};
17
+ /** Optional globe eye and visible-boundary capabilities. */
18
+ cameraPosition?: ArrayLike<number>;
19
+ getBounds?: (options: {z: number}) => unknown;
20
+ }
21
+
22
+ /** Device state tracked only on the WebGL branch of the bridge. */
23
+ export interface TangramBridgeDevice extends Device {
24
+ /** Optional WebGL state stack, validated before constructing a WebGL record. */
25
+ pushState?: () => void;
26
+ popState?: () => void;
27
+ }
28
+
29
+ /** Renderer scene capabilities needed by the bridge, without exposing scene implementation state. */
30
+ export interface TangramBridgeScene {
31
+ /** Normalized animation policy, absent before loading. */
32
+ config?: {scene?: {animated?: boolean}} | null;
33
+ /** Main-thread context ownership used only for WebGL submission. */
34
+ withWebGLContext?<Value>(operation: () => Value): Value;
35
+ }
36
+
37
+ /** Narrow injected renderer API; GPU resources and workers remain renderer-owned. */
38
+ export interface TangramBridgeRenderer extends Pick<Renderer,
39
+ 'setFrame' | 'setTerrain' | 'setTerrainExaggeration' | 'getFeatureAt' | 'getTerrainAt' |
40
+ 'getDrapedTerrainSurface' | 'getTerrainDrapingStatistics' | 'getAttributions' | 'destroy' | 'setProjectedBasemapProjection'> {
41
+ /** Scene owned by this renderer instance. */
42
+ scene: Scene;
43
+ /** Scene event registration. */
44
+ subscribe(listeners: SceneListeners): unknown;
45
+ /** Asynchronous scene load; synchronous legacy returns remain supported. */
46
+ load(scene: SceneDefinition, options: SceneLoadOptions): unknown;
47
+ /** Submit one host frame through the caller's active render pass. */
48
+ render(options: RenderOptions): boolean;
49
+ }
50
+
51
+ /** Factory injection preserves both the current and legacy renderer argument names. */
52
+ export interface TangramRendererFactory {
53
+ /** Construct one independently owned renderer; missing factories still fail through the existing catch path. */
54
+ create?: (scene: SceneDefinition, options: RendererOptions & {requestRedraw: () => void}) => TangramBridgeRenderer;
55
+ }
56
+
57
+ /** deck lifecycle capabilities inherited from the injected superclass. */
58
+ export interface TangramLayerOwner {
59
+ /** Resolved deck properties, including callback defaults. */
60
+ props: Partial<TangramLayerProps>;
61
+ /** Notify deck that asynchronous scene work needs another frame. */
62
+ setNeedsRedraw(): void;
63
+ /** Forward existing error reporting to deck. */
64
+ raiseError(error: Error, message: string): void;
65
+ /** Existing error callback used by the opt-in projected subclass. */
66
+ _reportSceneError(record: TangramRecord, error: Error, isFatal?: boolean): void;
67
+ }
68
+
69
+ /** Scene ownership shared by callbacks when deck replaces its layer instance. */
70
+ export interface TangramRecord {
71
+ /** Latest layer instance holding the logical scene. */
72
+ owner: TangramLayerOwner;
73
+ /** Assigned renderer and scene, after successful factory construction. */
74
+ renderer: TangramBridgeRenderer;
75
+ scene: Scene;
76
+ /** Device and optional native WebGL context. */
77
+ device: TangramBridgeDevice;
78
+ gl: (WebGLRenderingContext & {lumaState?: {program?: WebGLProgram | null}}) | null;
79
+ /** Host canvas never replaced by Tangram. */
80
+ deckCanvas: HTMLCanvasElement;
81
+ /** Immutable constructor input identities. */
82
+ sceneSource: SceneDefinition;
83
+ sceneBasePath?: string | null;
84
+ apiKey?: string | null;
85
+ projectionEngine?: TangramLayerProps['projectionEngine'];
86
+ projectionEngineExecution?: TangramLayerProps['projectionEngineExecution'];
87
+ maxConcurrentTileLoadsPerWorker?: number;
88
+ /** Current asynchronous terrain request and latest relief policy. */
89
+ terrain?: TangramLayerProps['terrain'];
90
+ /** Immutable borrowed admission-pool identity. */
91
+ terrainResourceBudget?: TangramLayerProps['terrainResourceBudget'];
92
+ terrainRevision?: number;
93
+ terrainPendingRevision: number;
94
+ /** Current canvas CSS size, assigned during view synchronization. */
95
+ canvasWidth: number | null;
96
+ canvasHeight: number | null;
97
+ /** Disposal waits for an in-flight load, and releases the renderer once. */
98
+ disposed: boolean;
99
+ destroyed: boolean;
100
+ loadSettled: boolean;
101
+ loadFailed: boolean;
102
+ loaded: boolean;
103
+ loadPromise: Promise<unknown> | null;
104
+ /** Deduplicated scene/viewport errors and attribution request generation. */
105
+ lastSceneError: string | null;
106
+ lastViewportError: string | null;
107
+ reportedViewportError: string | null;
108
+ attributionGeneration?: number;
109
+ /** Latest opt-in projected mesh generation requested by the projected subclass. */
110
+ projectedProjectionKey?: string | null;
111
+ /** Reentrant WebGL state-scope depth. */
112
+ webglScopeDepth: number;
113
+ }
114
+
115
+ /** Record initialization happens before the factory is called so redraw callbacks can close over it. */
116
+ export type PendingTangramRecord = Omit<TangramRecord, 'renderer' | 'scene'> & {
117
+ renderer: TangramBridgeRenderer | null;
118
+ scene: TangramBridgeScene | null;
119
+ };
120
+
121
+ /** Required host context capabilities, with no dependency on a concrete deck.gl subclass. */
122
+ export interface TangramLayerContext {
123
+ /** Host device validated by the existing creation path. */
124
+ device: TangramBridgeDevice;
125
+ /** Current viewport plus the complete view inventory. */
126
+ viewport: TangramLayerViewport;
127
+ deck?: {getCanvas(): HTMLCanvasElement | null; getViewports?(): readonly TangramLayerViewport[]};
128
+ /** Caller-owned pass; undefined keeps the existing renderer fallback. */
129
+ renderPass?: RenderPass;
130
+ }