@deck.gl/core 9.3.0-alpha.2 → 9.3.0-alpha.5

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 (80) hide show
  1. package/dist/controllers/terrain-controller.d.ts +30 -0
  2. package/dist/controllers/terrain-controller.d.ts.map +1 -0
  3. package/dist/controllers/terrain-controller.js +127 -0
  4. package/dist/controllers/terrain-controller.js.map +1 -0
  5. package/dist/dist.dev.js +3137 -1160
  6. package/dist/index.cjs +584 -181
  7. package/dist/index.cjs.map +4 -4
  8. package/dist/index.d.ts +1 -0
  9. package/dist/index.d.ts.map +1 -1
  10. package/dist/index.js +1 -0
  11. package/dist/index.js.map +1 -1
  12. package/dist/lib/attribute/gl-utils.d.ts +1 -2
  13. package/dist/lib/attribute/gl-utils.d.ts.map +1 -1
  14. package/dist/lib/attribute/gl-utils.js +2 -2
  15. package/dist/lib/attribute/gl-utils.js.map +1 -1
  16. package/dist/lib/deck-picker.d.ts +3 -2
  17. package/dist/lib/deck-picker.d.ts.map +1 -1
  18. package/dist/lib/deck-picker.js +74 -17
  19. package/dist/lib/deck-picker.js.map +1 -1
  20. package/dist/lib/deck.d.ts +62 -0
  21. package/dist/lib/deck.d.ts.map +1 -1
  22. package/dist/lib/deck.js +219 -77
  23. package/dist/lib/deck.js.map +1 -1
  24. package/dist/lib/init.js +2 -2
  25. package/dist/lib/layer.d.ts.map +1 -1
  26. package/dist/lib/layer.js +60 -9
  27. package/dist/lib/layer.js.map +1 -1
  28. package/dist/lib/view-manager.js +1 -1
  29. package/dist/lib/view-manager.js.map +1 -1
  30. package/dist/passes/pick-layers-pass.d.ts.map +1 -1
  31. package/dist/passes/pick-layers-pass.js +7 -2
  32. package/dist/passes/pick-layers-pass.js.map +1 -1
  33. package/dist/passes/screen-pass-uniforms.d.ts +1 -1
  34. package/dist/passes/screen-pass-uniforms.js +1 -1
  35. package/dist/shaderlib/color/color.d.ts +1 -4
  36. package/dist/shaderlib/color/color.d.ts.map +1 -1
  37. package/dist/shaderlib/color/color.js +0 -12
  38. package/dist/shaderlib/color/color.js.map +1 -1
  39. package/dist/shaderlib/misc/layer-uniforms.d.ts +3 -2
  40. package/dist/shaderlib/misc/layer-uniforms.d.ts.map +1 -1
  41. package/dist/shaderlib/misc/layer-uniforms.js +10 -1
  42. package/dist/shaderlib/misc/layer-uniforms.js.map +1 -1
  43. package/dist/shaderlib/picking/picking.d.ts +3 -2
  44. package/dist/shaderlib/picking/picking.d.ts.map +1 -1
  45. package/dist/shaderlib/picking/picking.js +29 -0
  46. package/dist/shaderlib/picking/picking.js.map +1 -1
  47. package/dist/shaderlib/project/project.glsl.js +1 -1
  48. package/dist/shaderlib/project/project.wgsl.d.ts.map +1 -1
  49. package/dist/shaderlib/project/project.wgsl.js +4 -6
  50. package/dist/shaderlib/project/project.wgsl.js.map +1 -1
  51. package/dist/shaderlib/shadow/shadow.d.ts +2 -2
  52. package/dist/shaderlib/shadow/shadow.js +1 -1
  53. package/dist/transitions/gpu-interpolation-transition.js +2 -2
  54. package/dist/transitions/gpu-interpolation-transition.js.map +1 -1
  55. package/dist/transitions/gpu-spring-transition.js +1 -1
  56. package/dist/transitions/gpu-transition-utils.d.ts.map +1 -1
  57. package/dist/transitions/gpu-transition-utils.js +3 -4
  58. package/dist/transitions/gpu-transition-utils.js.map +1 -1
  59. package/dist/utils/typed-array-manager.js.map +1 -1
  60. package/dist.min.js +506 -234
  61. package/package.json +6 -7
  62. package/src/controllers/terrain-controller.ts +155 -0
  63. package/src/index.ts +1 -0
  64. package/src/lib/attribute/gl-utils.ts +2 -2
  65. package/src/lib/deck-picker.ts +98 -17
  66. package/src/lib/deck.ts +334 -86
  67. package/src/lib/layer.ts +98 -8
  68. package/src/lib/view-manager.ts +1 -1
  69. package/src/passes/pick-layers-pass.ts +6 -2
  70. package/src/passes/screen-pass-uniforms.ts +1 -1
  71. package/src/shaderlib/color/color.ts +0 -12
  72. package/src/shaderlib/misc/layer-uniforms.ts +11 -1
  73. package/src/shaderlib/picking/picking.ts +30 -0
  74. package/src/shaderlib/project/project.glsl.ts +1 -1
  75. package/src/shaderlib/project/project.wgsl.ts +4 -6
  76. package/src/shaderlib/shadow/shadow.ts +1 -1
  77. package/src/transitions/gpu-interpolation-transition.ts +2 -2
  78. package/src/transitions/gpu-spring-transition.ts +1 -1
  79. package/src/transitions/gpu-transition-utils.ts +4 -5
  80. package/src/utils/typed-array-manager.ts +3 -3
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "description": "deck.gl core library",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
- "version": "9.3.0-alpha.2",
6
+ "version": "9.3.0-alpha.5",
7
7
  "publishConfig": {
8
8
  "access": "public"
9
9
  },
@@ -42,11 +42,10 @@
42
42
  "dependencies": {
43
43
  "@loaders.gl/core": "^4.4.0-alpha.18",
44
44
  "@loaders.gl/images": "^4.4.0-alpha.18",
45
- "@luma.gl/constants": "^9.3.0-alpha.6",
46
- "@luma.gl/core": "^9.3.0-alpha.6",
47
- "@luma.gl/engine": "^9.3.0-alpha.6",
48
- "@luma.gl/shadertools": "^9.3.0-alpha.6",
49
- "@luma.gl/webgl": "^9.3.0-alpha.6",
45
+ "@luma.gl/core": "^9.3.0-alpha.10",
46
+ "@luma.gl/engine": "^9.3.0-alpha.10",
47
+ "@luma.gl/shadertools": "^9.3.0-alpha.10",
48
+ "@luma.gl/webgl": "^9.3.0-alpha.10",
50
49
  "@math.gl/core": "^4.1.0",
51
50
  "@math.gl/sun": "^4.1.0",
52
51
  "@math.gl/types": "^4.1.0",
@@ -58,5 +57,5 @@
58
57
  "gl-matrix": "^3.0.0",
59
58
  "mjolnir.js": "^3.0.0"
60
59
  },
61
- "gitHead": "135d329f4a4b596ae2c16e4eb801eda30252f3bc"
60
+ "gitHead": "c3ad1cee357af674cc31df37979d61325baf9d7a"
62
61
  }
@@ -0,0 +1,155 @@
1
+ // deck.gl
2
+ // SPDX-License-Identifier: MIT
3
+ // Copyright (c) vis.gl contributors
4
+
5
+ import MapController from './map-controller';
6
+ import {MapState, MapStateProps} from './map-controller';
7
+ import type {ControllerProps, InteractionState} from './controller';
8
+ import type {MjolnirGestureEvent, MjolnirWheelEvent} from 'mjolnir.js';
9
+
10
+ /**
11
+ * Controller that extends MapController with terrain-aware behavior.
12
+ * The camera smoothly follows terrain elevation during pan/zoom.
13
+ */
14
+ export default class TerrainController extends MapController {
15
+ /** Cached terrain altitude from depth picking at viewport center (smoothed) */
16
+ private _terrainAltitude?: number = undefined;
17
+ /** Raw (unsmoothed) terrain altitude from latest pick */
18
+ private _terrainAltitudeTarget?: number = undefined;
19
+ /** rAF handle for periodic terrain altitude picking */
20
+ private _pickFrameId: number | null = null;
21
+ /** Timestamp of last pick */
22
+ private _lastPickTime: number = 0;
23
+
24
+ setProps(
25
+ props: ControllerProps &
26
+ MapStateProps & {
27
+ rotationPivot?: 'center' | '2d' | '3d';
28
+ getAltitude?: (pos: [number, number]) => number | undefined;
29
+ }
30
+ ) {
31
+ super.setProps({rotationPivot: '3d', ...props});
32
+
33
+ // Periodically pick terrain altitude at the viewport center using rAF.
34
+ // Keeps the altitude cache warm so interactions don't need expensive
35
+ // synchronous GPU readbacks. rAF naturally pauses when tab is backgrounded.
36
+ if (this._pickFrameId === null) {
37
+ const loop = () => {
38
+ const now = Date.now();
39
+ if (now - this._lastPickTime > 500 && !this.isDragging()) {
40
+ this._lastPickTime = now;
41
+ this._pickTerrainCenterAltitude();
42
+ // On first successful pick, rebase viewport to terrain altitude.
43
+ // Runs from rAF (outside React render) so onViewStateChange won't loop.
44
+ if (this._terrainAltitude === undefined && this._terrainAltitudeTarget !== undefined) {
45
+ this._terrainAltitude = this._terrainAltitudeTarget;
46
+ const controllerState = new this.ControllerState({
47
+ makeViewport: this.makeViewport,
48
+ ...this.props,
49
+ ...this.state
50
+ } as any);
51
+ const rebaseProps = this._rebaseViewport(this._terrainAltitudeTarget, controllerState);
52
+ if (rebaseProps) {
53
+ // Build a controllerState that includes the rebase adjustments so
54
+ // internal state matches the rebased viewState after React round-trip.
55
+ const rebasedState = new this.ControllerState({
56
+ makeViewport: this.makeViewport,
57
+ ...this.props,
58
+ ...this.state,
59
+ ...rebaseProps
60
+ } as any);
61
+ super.updateViewport(rebasedState);
62
+ }
63
+ }
64
+ }
65
+ this._pickFrameId = requestAnimationFrame(loop);
66
+ };
67
+ this._pickFrameId = requestAnimationFrame(loop);
68
+ }
69
+ }
70
+
71
+ finalize() {
72
+ if (this._pickFrameId !== null) {
73
+ cancelAnimationFrame(this._pickFrameId);
74
+ this._pickFrameId = null;
75
+ }
76
+ super.finalize();
77
+ }
78
+
79
+ protected updateViewport(
80
+ newControllerState: MapState,
81
+ extraProps: Record<string, any> | null = null,
82
+ interactionState: InteractionState = {}
83
+ ): void {
84
+ // Not initialized yet — pass through to MapController
85
+ if (this._terrainAltitude === undefined) {
86
+ super.updateViewport(newControllerState, extraProps, interactionState);
87
+ return;
88
+ }
89
+
90
+ // Smoothly blend toward target altitude
91
+ const SMOOTHING = 0.05;
92
+ this._terrainAltitude += (this._terrainAltitudeTarget! - this._terrainAltitude) * SMOOTHING;
93
+
94
+ const viewportProps = newControllerState.getViewportProps();
95
+ const pos = viewportProps.position || [0, 0, 0];
96
+ extraProps = {
97
+ ...extraProps,
98
+ position: [pos[0], pos[1], this._terrainAltitude]
99
+ };
100
+
101
+ super.updateViewport(newControllerState, extraProps, interactionState);
102
+ }
103
+
104
+ private _pickTerrainCenterAltitude(): void {
105
+ if (!this.pickPosition) {
106
+ return;
107
+ }
108
+ const {x, y, width, height} = this.props;
109
+ const pickResult = this.pickPosition(x + width / 2, y + height / 2);
110
+ if (pickResult?.coordinate && pickResult.coordinate.length >= 3) {
111
+ this._terrainAltitudeTarget = pickResult.coordinate[2];
112
+ }
113
+ }
114
+
115
+ /**
116
+ * Compute viewport adjustments to keep the view visually the same
117
+ * when shifting position to [0, 0, altitude].
118
+ */
119
+ private _rebaseViewport(
120
+ altitude: number,
121
+ newControllerState: MapState
122
+ ): Record<string, any> | null {
123
+ const viewportProps = newControllerState.getViewportProps();
124
+ const oldViewport = this.makeViewport({...viewportProps, position: [0, 0, 0]});
125
+ const oldCameraPos = oldViewport.cameraPosition;
126
+
127
+ const centerZOffset = altitude * oldViewport.distanceScales.unitsPerMeter[2];
128
+ const cameraHeightAboveOldCenter = oldCameraPos[2];
129
+ const newCameraHeightAboveCenter = cameraHeightAboveOldCenter - centerZOffset;
130
+ if (newCameraHeightAboveCenter <= 0) {
131
+ return null;
132
+ }
133
+
134
+ const zoomDelta = Math.log2(cameraHeightAboveOldCenter / newCameraHeightAboveCenter);
135
+ const newZoom = viewportProps.zoom + zoomDelta;
136
+
137
+ const newViewport = this.makeViewport({
138
+ ...viewportProps,
139
+ zoom: newZoom,
140
+ position: [0, 0, altitude]
141
+ });
142
+ const {width, height} = viewportProps;
143
+ const screenCenter: [number, number] = [width / 2, height / 2];
144
+ const worldPoint = oldViewport.unproject(screenCenter, {targetZ: altitude});
145
+ if (
146
+ worldPoint &&
147
+ 'panByPosition3D' in newViewport &&
148
+ typeof newViewport.panByPosition3D === 'function'
149
+ ) {
150
+ const adjusted = newViewport.panByPosition3D(worldPoint, screenCenter);
151
+ return {position: [0, 0, altitude], zoom: newZoom, ...adjusted};
152
+ }
153
+ return null;
154
+ }
155
+ }
package/src/index.ts CHANGED
@@ -65,6 +65,7 @@ export {default as _GlobeView} from './views/globe-view';
65
65
  // Controllers
66
66
  export {default as Controller} from './controllers/controller';
67
67
  export {default as MapController} from './controllers/map-controller';
68
+ export {default as TerrainController} from './controllers/terrain-controller';
68
69
  export {default as _GlobeController} from './controllers/globe-controller';
69
70
  export {default as FirstPersonController} from './controllers/first-person-controller';
70
71
  export {default as OrbitController} from './controllers/orbit-controller';
@@ -2,7 +2,7 @@
2
2
  // SPDX-License-Identifier: MIT
3
3
  // Copyright (c) vis.gl contributors
4
4
 
5
- import {getTypedArrayConstructor, getDataType} from '@luma.gl/core';
5
+ import {dataTypeDecoder, getTypedArrayConstructor} from '@luma.gl/core';
6
6
  import type {BufferAttributeLayout, VertexFormat} from '@luma.gl/core';
7
7
  import type {TypedArrayConstructor} from '../../types/types';
8
8
  import type {BufferAccessor, DataColumnSettings, LogicalDataType} from './data-column';
@@ -20,7 +20,7 @@ export function typedArrayFromDataType(type: LogicalDataType): TypedArrayConstru
20
20
  }
21
21
  }
22
22
 
23
- export const dataTypeFromTypedArray = getDataType;
23
+ export const dataTypeFromTypedArray = dataTypeDecoder.getDataType.bind(dataTypeDecoder);
24
24
 
25
25
  export function getBufferAttributeLayout(
26
26
  name: string,
@@ -2,8 +2,10 @@
2
2
  // SPDX-License-Identifier: MIT
3
3
  // Copyright (c) vis.gl contributors
4
4
 
5
+ import {Buffer, Texture} from '@luma.gl/core';
5
6
  import type {Device} from '@luma.gl/core';
6
7
  import PickLayersPass, {PickingColorDecoder} from '../passes/pick-layers-pass';
8
+ import log from '../utils/log';
7
9
  import {getClosestObject, getUniqueObjects, PickedPixel} from './picking/query-object';
8
10
  import {
9
11
  processPickInfo,
@@ -116,7 +118,7 @@ export default class DeckPicker {
116
118
  /**
117
119
  * Pick the closest info at given coordinate
118
120
  * @returns picking info
119
- * @deprecated WebGL only - use pickObjectAsync instead
121
+ * @note WebGL only - use pickObjectAsync instead
120
122
  */
121
123
  pickObject(opts: PickByPointOptions & PickOperationContext) {
122
124
  return this._pickClosestObject(opts);
@@ -125,7 +127,7 @@ export default class DeckPicker {
125
127
  /**
126
128
  * Get all unique infos within a bounding box
127
129
  * @returns all unique infos within a bounding box
128
- * @deprecated WebGL only - use pickObjectAsync instead
130
+ * @note WebGL only - use pickObjectAsync instead
129
131
  */
130
132
  pickObjects(opts: PickByRectOptions & PickOperationContext) {
131
133
  return this._pickVisibleObjects(opts);
@@ -158,14 +160,26 @@ export default class DeckPicker {
158
160
  _resizeBuffer() {
159
161
  // Create a frame buffer if not already available
160
162
  if (!this.pickingFBO) {
163
+ const pickingColorTexture = this.device.createTexture({
164
+ format: 'rgba8unorm',
165
+ width: 1,
166
+ height: 1,
167
+ usage: Texture.RENDER_ATTACHMENT | Texture.COPY_SRC
168
+ });
161
169
  this.pickingFBO = this.device.createFramebuffer({
162
- colorAttachments: ['rgba8unorm'],
170
+ colorAttachments: [pickingColorTexture],
163
171
  depthStencilAttachment: 'depth16unorm'
164
172
  });
165
173
 
166
174
  if (this.device.isTextureFormatRenderable('rgba32float')) {
175
+ const depthColorTexture = this.device.createTexture({
176
+ format: 'rgba32float',
177
+ width: 1,
178
+ height: 1,
179
+ usage: Texture.RENDER_ATTACHMENT | Texture.COPY_SRC
180
+ });
167
181
  const depthFBO = this.device.createFramebuffer({
168
- colorAttachments: ['rgba32float'],
182
+ colorAttachments: [depthColorTexture],
169
183
  depthStencilAttachment: 'depth16unorm'
170
184
  });
171
185
  this.depthFBO = depthFBO;
@@ -258,7 +272,7 @@ export default class DeckPicker {
258
272
  let pickInfo: PickedPixel;
259
273
 
260
274
  if (deviceRect) {
261
- const pickedResult = this._drawAndSample({
275
+ const pickedResult = await this._drawAndSampleAsync({
262
276
  layers: pickableLayers,
263
277
  views,
264
278
  viewports,
@@ -286,7 +300,7 @@ export default class DeckPicker {
286
300
  let z;
287
301
  const depthLayers = this._getDepthLayers(pickInfo, pickableLayers, unproject3D);
288
302
  if (depthLayers.length > 0) {
289
- const {pickedColors: pickedColors2} = this._drawAndSample(
303
+ const {pickedColors: pickedColors2} = await this._drawAndSampleAsync(
290
304
  {
291
305
  layers: depthLayers,
292
306
  views,
@@ -566,7 +580,7 @@ export default class DeckPicker {
566
580
  height: deviceTop - deviceBottom
567
581
  };
568
582
 
569
- const pickedResult = this._drawAndSample({
583
+ const pickedResult = await this._drawAndSampleAsync({
570
584
  layers: pickableLayers,
571
585
  views,
572
586
  viewports,
@@ -814,21 +828,88 @@ export default class DeckPicker {
814
828
  const {decodePickingColor, stats} = this.pickLayersPass.render(opts);
815
829
  this._updateStats(stats);
816
830
 
817
- // Read from an already rendered picking buffer
818
- // Returns an Uint8ClampedArray of picked pixels
819
831
  const {x, y, width, height} = deviceRect;
820
- const pickedColors = new (pickZ ? Float32Array : Uint8Array)(width * height * 4);
821
- this.device.readPixelsToArrayWebGL(pickingFBO as Framebuffer, {
822
- sourceX: x,
823
- sourceY: y,
824
- sourceWidth: width,
825
- sourceHeight: height,
826
- target: pickedColors
827
- });
832
+ const texture = (pickingFBO as Framebuffer).colorAttachments[0]?.texture;
833
+ if (!texture) {
834
+ throw new Error('Picking framebuffer color attachment is missing');
835
+ }
836
+
837
+ const pickedColors = await this._readTextureDataAsync(
838
+ texture,
839
+ {x, y, width, height},
840
+ pickZ ? Float32Array : Uint8Array
841
+ );
842
+
843
+ if (!pickZ) {
844
+ let hasNonZeroAlpha = false;
845
+ for (let i = 3; i < pickedColors.length; i += 4) {
846
+ if (pickedColors[i] !== 0) {
847
+ hasNonZeroAlpha = true;
848
+ break;
849
+ }
850
+ }
851
+ if (!hasNonZeroAlpha && pickedColors.length > 0) {
852
+ log.warn('Async pick readback returned only zero alpha values', {
853
+ deviceRect,
854
+ bytes: Array.from(pickedColors.subarray(0, Math.min(pickedColors.length, 16)))
855
+ })();
856
+ }
857
+ }
828
858
 
829
859
  return {pickedColors, decodePickingColor};
830
860
  }
831
861
 
862
+ private async _readTextureDataAsync<T extends Uint8Array | Float32Array>(
863
+ texture: Texture,
864
+ options: {x: number; y: number; width: number; height: number},
865
+ ArrayType: Uint8ArrayConstructor | Float32ArrayConstructor
866
+ ): Promise<T> {
867
+ const {width, height} = options;
868
+ const layout = texture.computeMemoryLayout(options);
869
+ const readBuffer = this.device.createBuffer({
870
+ byteLength: layout.byteLength,
871
+ usage: Buffer.COPY_DST | Buffer.MAP_READ
872
+ });
873
+
874
+ try {
875
+ texture.readBuffer(options, readBuffer);
876
+ const readData = await readBuffer.readAsync(0, layout.byteLength);
877
+ const bytesPerElement = ArrayType.BYTES_PER_ELEMENT;
878
+ if (layout.bytesPerRow % bytesPerElement !== 0) {
879
+ throw new Error(
880
+ `Texture readback row stride ${layout.bytesPerRow} is not aligned to ${bytesPerElement}-byte elements.`
881
+ );
882
+ }
883
+ const source = new ArrayType(
884
+ readData.buffer,
885
+ readData.byteOffset,
886
+ layout.byteLength / bytesPerElement
887
+ );
888
+ // Picking textures are RGBA. WebGPU rows may be padded to satisfy GPU alignment
889
+ // requirements, so repack each row into a tightly packed CPU array before decode.
890
+ const packedRowLength = width * 4;
891
+ const sourceRowLength = layout.bytesPerRow / bytesPerElement;
892
+ if (sourceRowLength < packedRowLength) {
893
+ throw new Error(
894
+ `Texture readback row stride ${sourceRowLength} is smaller than packed row length ${packedRowLength}.`
895
+ );
896
+ }
897
+ const packed = new ArrayType(width * height * 4);
898
+
899
+ for (let row = 0; row < height; row++) {
900
+ const sourceStart = row * sourceRowLength;
901
+ packed.set(
902
+ source.subarray(sourceStart, sourceStart + packedRowLength),
903
+ row * packedRowLength
904
+ );
905
+ }
906
+
907
+ return packed as T;
908
+ } finally {
909
+ readBuffer.destroy();
910
+ }
911
+ }
912
+
832
913
  /**
833
914
  * Renders layers into the picking buffer with picking colors and read the pixels.
834
915
  * @deprecated WebGL only, use _drawAndSampleAsync instead