Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
09a2210
PoC 3D picking
felixpalmer Jan 13, 2026
5e3749b
Augment pickable prop
felixpalmer Jan 13, 2026
f59fb30
Tidy
felixpalmer Jan 13, 2026
a2e343b
hover support
felixpalmer Jan 13, 2026
97e0ce4
Add elevation label to terrain app
felixpalmer Jan 14, 2026
d1c1c76
Rotation pivot point fixed
felixpalmer Jan 13, 2026
85b1fcf
3d enabled rotation pivot point
felixpalmer Jan 13, 2026
068c4cc
prototype rotation pivot
felixpalmer Jan 13, 2026
50743f2
Hook up picking with rotation pivot
felixpalmer Jan 13, 2026
771c3f3
rotation pivot helper
felixpalmer Jan 13, 2026
e614ac3
Map3dcontroller
felixpalmer Jan 14, 2026
e367799
rotatePivot API
felixpalmer Jan 14, 2026
67914ba
Use planes in demo
felixpalmer Jan 14, 2026
79d2d7f
Shorthand
felixpalmer Jan 15, 2026
9544d5d
Revert vite config
felixpalmer Jan 15, 2026
9eebac1
remove extra override
felixpalmer Jan 15, 2026
90352d0
Unify naming
felixpalmer Jan 15, 2026
11d9dde
Lint
felixpalmer Jan 15, 2026
3b3da3d
Merge branch 'master' into felix/rotate-pivot-3d
felixpalmer Jan 15, 2026
447bdcb
Merge branch 'master' into felix/rotate-pivot-3d
felixpalmer Feb 23, 2026
6f0a5f1
Fix 3d interaction with draping
felixpalmer Feb 24, 2026
23e275b
Add draped layer
felixpalmer Feb 24, 2026
470280a
Test in google-3d example
felixpalmer Feb 24, 2026
35eddd4
Clearer logic
felixpalmer Feb 24, 2026
cc9029d
Alternate blending approach
felixpalmer Feb 24, 2026
cab20ac
Enable picking rendering in terrain shadermodule
felixpalmer Feb 24, 2026
8d5d6f2
Revert deck-picker _getDepthLayers
felixpalmer Feb 24, 2026
2b08742
Closer to master code
felixpalmer Feb 24, 2026
5a39e07
Reduce diff
felixpalmer Feb 24, 2026
7651339
revert 3d-tiles example changes
felixpalmer Feb 24, 2026
0e092ae
Support hybrid picking
felixpalmer Feb 24, 2026
4d1f6f3
Try via blending
felixpalmer Feb 24, 2026
433209d
Reinstate depthLayers
felixpalmer Feb 24, 2026
d1663aa
pass emptyInfo
felixpalmer Feb 24, 2026
8f70a08
Fix MISS case where high-res tiles have no info.object
felixpalmer Feb 24, 2026
1bea4c6
Revert emptyInfo hack
felixpalmer Feb 24, 2026
e50bd02
hover debug
felixpalmer Feb 24, 2026
4a0c526
Update after refactor
felixpalmer Feb 25, 2026
b5597d2
Merge branch 'master' into felix/rotate-pivot-3d-terrain
felixpalmer Feb 25, 2026
2f1874f
lint
felixpalmer Feb 25, 2026
0e2397d
revert test example
felixpalmer Mar 4, 2026
0fb6205
Merge branch 'master' into felix/rotate-pivot-3d-terrain
felixpalmer Mar 4, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 29 additions & 8 deletions modules/core/src/lib/deck-picker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -281,16 +281,17 @@ export default class DeckPicker {
}

let z;
if (pickInfo.pickedLayer && unproject3D && this.depthFBO) {
const depthLayers = this._getDepthLayers(pickInfo, pickableLayers, unproject3D);
if (depthLayers.length > 0) {
const {pickedColors: pickedColors2} = this._drawAndSample(
{
layers: [pickInfo.pickedLayer],
layers: depthLayers,
views,
viewports,
onViewportActive,
deviceRect: {
x: pickInfo.pickedX as number,
y: pickInfo.pickedY as number,
x: pickInfo.pickedX ?? devicePixel[0],
y: pickInfo.pickedY ?? devicePixel[1],
width: 1,
height: 1
},
Expand Down Expand Up @@ -444,16 +445,17 @@ export default class DeckPicker {
}

let z;
if (pickInfo.pickedLayer && unproject3D && this.depthFBO) {
const depthLayers = this._getDepthLayers(pickInfo, pickableLayers, unproject3D);
if (depthLayers.length > 0) {
const {pickedColors: pickedColors2} = this._drawAndSample(
{
layers: [pickInfo.pickedLayer],
layers: depthLayers,
views,
viewports,
onViewportActive,
deviceRect: {
x: pickInfo.pickedX as number,
y: pickInfo.pickedY as number,
x: pickInfo.pickedX ?? devicePixel[0],
y: pickInfo.pickedY ?? devicePixel[1],
width: 1,
height: 1
},
Expand Down Expand Up @@ -928,6 +930,25 @@ export default class DeckPicker {
return {pickedColors, decodePickingColor};
}

/**
* Determine which layers to use for the depth (pickZ) pass.
* - If a non-draped layer was picked, use just that layer.
* - If a draped layer was picked (geometry is at z=0) or no layer was picked
* (e.g. no-FBO tiles at extreme zoom), fall back to terrain layers.
*/
_getDepthLayers(pickInfo: PickedPixel, pickableLayers: Layer[], unproject3D?: boolean): Layer[] {
if (!unproject3D || !this.depthFBO) {
return [];
}
const {pickedLayer} = pickInfo;
const isDraped = pickedLayer?.state?.terrainDrawMode === 'drape';
if (pickedLayer && !isDraped) {
return [pickedLayer];
}
// For draped layers or when no layer was picked, use terrain layers for depth
return pickableLayers.filter(l => l.props.operation.includes('terrain'));
}

/**
* Calculate a picking rect centered on deviceX and deviceY and clipped to device
* @returns null if pixel is outside of device
Expand Down
12 changes: 10 additions & 2 deletions modules/core/src/passes/pick-layers-pass.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,8 @@ export default class PickLayersPass extends LayersPass {
effects,
pass = 'picking',
pickZ,
shaderModuleProps
shaderModuleProps,
clearColor
}: PickLayersPassRenderOptions): {
decodePickingColor: PickingColorDecoder | null;
stats: RenderStats;
Expand All @@ -96,7 +97,7 @@ export default class PickLayersPass extends LayersPass {
pass,
isPicking: true,
shaderModuleProps,
clearColor: [0, 0, 0, 0],
clearColor: clearColor ?? [0, 0, 0, 0],
colorMask: 0xf,
scissorRect
});
Expand Down Expand Up @@ -145,6 +146,13 @@ export default class PickLayersPass extends LayersPass {
pickParameters.blend = true;
// TODO: blendColor no longer part of luma.gl API
pickParameters.blendColor = encodeColor(this._colorEncoderState, layer, viewport);
if (operation.includes('terrain') && layer.state?._hasPickingCover) {
// For terrain+draw layers with a valid cover FBO, the terrain shader outputs the
// cover FBO pixel which already has correctly encoded alpha from the cover encoder.
// Use srcFactor 'one' to pass through the cover alpha without double-encoding.
// Without a cover FBO, keep 'constant' so the layer's own picking colors encode correctly.
pickParameters.blendAlphaSrcFactor = 'one';
}
} else if (operation.includes('terrain')) {
// Pure terrain layers (without 'draw') don't need picking colors
pickParameters.blend = false;
Expand Down
5 changes: 4 additions & 1 deletion modules/extensions/src/terrain/shader-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,14 +151,17 @@ if ((terrain.mode == TERRAIN_MODE_USE_COVER) || (terrain.mode == TERRAIN_MODE_US
: terrainCover.getRenderFramebuffer();
sampler = fbo?.colorAttachments[0].texture;
if (opts.isPicking) {
// Never render the layer itself in picking pass
mode = TERRAIN_MODE.SKIP;
}
if (sampler) {
mode = mode === TERRAIN_MODE.SKIP ? TERRAIN_MODE.USE_COVER_ONLY : TERRAIN_MODE.USE_COVER;
bounds = terrainCover.bounds;
} else {
sampler = dummyHeightMap!;
if (opts.isPicking && !terrainSkipRender) {
// terrain+draw layer without cover FBO: render own picking colors
mode = TERRAIN_MODE.NONE;
}
}
}

Expand Down
8 changes: 7 additions & 1 deletion modules/extensions/src/terrain/terrain-effect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,12 @@ export class TerrainEffect implements Effect {
otherShaderModuleProps: Record<string, any>
): {terrain: TerrainModuleProps} {
const {terrainDrawMode} = layer.state;
const terrainCover = this.isDrapingEnabled ? (this.terrainCovers.get(layer.id) ?? null) : null;

// Communicate cover FBO availability to getLayerParameters for blend factor selection
if (this.isPicking && layer.props.operation.includes('terrain')) {
layer.state._hasPickingCover = Boolean(terrainCover?.getPickingFramebuffer());
}

return {
terrain: {
Expand All @@ -99,7 +105,7 @@ export class TerrainEffect implements Effect {
heightMap: this.heightMap?.getRenderFramebuffer()?.colorAttachments[0].texture || null,
heightMapBounds: this.heightMap?.bounds,
dummyHeightMap: this.dummyHeightMap!,
terrainCover: this.isDrapingEnabled ? this.terrainCovers.get(layer.id) : null,
terrainCover,
useTerrainHeightMap: terrainDrawMode === 'offset',
terrainSkipRender: terrainDrawMode === 'drape' || !layer.props.operation.includes('draw')
}
Expand Down
14 changes: 12 additions & 2 deletions modules/extensions/src/terrain/terrain-picking-pass.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,12 @@ export class TerrainPickingPass extends PickLayersPass {
}
target.resize(viewport);

// Use the terrain layer's encoded alpha as the cover clear color.
// At pixels where no layer renders in the cover (e.g. mesh gaps at tile edges),
// this ensures the pixel still maps to the terrain layer instead of MISS.
const terrainParams = this.drawParameters[terrainLayer.id];
const terrainAlpha = terrainParams?.blendColor?.[3] ?? 0;

this.render({
...opts,
pickingFBO: target,
Expand All @@ -69,7 +75,8 @@ export class TerrainPickingPass extends PickLayersPass {
// not the viewport of the terrain cover. Culling is already done by `terrainCover.filterLayers`
cullRect: undefined,
deviceRect: viewport,
pickZ: false
pickZ: false,
clearColor: [0, 0, 0, terrainAlpha]
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.

Most of these changes are in the picking pass and I'm assuming won't change draw pass renders. Is this change perceptible to the user?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

No, except that picking works where it previously didn't

});
}

Expand All @@ -81,7 +88,10 @@ export class TerrainPickingPass extends PickLayersPass {
parameters = super.getLayerParameters(layer, layerIndex, viewport);
parameters.blend = true;
}
return {...parameters, depthCompare: 'always'};
// Cover rendering must use 'constant' blend factor to correctly encode layer indices
// in the alpha channel. The main picking pass uses 'one' for terrain+draw layers to
// pass through the cover alpha, but the cover itself needs proper encoding.
return {...parameters, depthCompare: 'always', blendAlphaSrcFactor: 'constant'};
}

getShaderModuleProps(layer: Layer, effects: any, otherShaderModuleProps: Record<string, any>) {
Expand Down
Loading