UNPKG

@deck.gl/core

Version:

deck.gl core library

221 lines (200 loc) 6.3 kB
// deck.gl // SPDX-License-Identifier: MIT // Copyright (c) vis.gl contributors import LayersPass, {LayersPassRenderOptions, RenderStats, Rect} from './layers-pass'; import type {Framebuffer, Parameters, RenderPipelineParameters} from '@luma.gl/core'; import log from '../utils/log'; import type {Effect} from '../lib/effect'; import type Viewport from '../viewports/viewport'; import type Layer from '../lib/layer'; const PICKING_BLENDING: RenderPipelineParameters = { blendColorOperation: 'add', blendColorSrcFactor: 'one', blendColorDstFactor: 'zero', blendAlphaOperation: 'add', blendAlphaSrcFactor: 'constant-alpha', blendAlphaDstFactor: 'zero' }; type PickLayersPassRenderOptions = LayersPassRenderOptions & { pickingFBO: Framebuffer; deviceRect: Rect; pickZ: boolean; }; type EncodedPickingColors = { a: number; layer: Layer; viewports: Viewport[]; }; export type PickingColorDecoder = (pickedColor: number[] | Uint8Array) => | { pickedLayer: Layer; pickedViewports: Viewport[]; pickedObjectIndex: number; } | undefined; export default class PickLayersPass extends LayersPass { private pickZ?: boolean; private _colorEncoderState: { byLayer: Map<Layer, EncodedPickingColors>; byAlpha: EncodedPickingColors[]; } | null = null; render(props: LayersPassRenderOptions | PickLayersPassRenderOptions) { if ('pickingFBO' in props) { // When drawing into an off-screen buffer, use the alpha channel to encode layer index return this._drawPickingBuffer(props); } // When drawing to screen (debug mode), do not use the alpha channel so that result is always visible return super.render(props); } // Private // Draws list of layers and viewports into the picking buffer // Note: does not sample the buffer, that has to be done by the caller _drawPickingBuffer({ layers, layerFilter, views, viewports, onViewportActive, pickingFBO, deviceRect: {x, y, width, height}, cullRect, effects, pass = 'picking', pickZ, shaderModuleProps }: PickLayersPassRenderOptions): { decodePickingColor: PickingColorDecoder | null; stats: RenderStats; } { this.pickZ = pickZ; const colorEncoderState = this._resetColorEncoder(pickZ); const scissorRect = [x, y, width, height]; // Make sure we clear scissor test and fbo bindings in case of exceptions // We are only interested in one pixel, no need to render anything else // Note that the callback here is called synchronously. // Set blend mode for picking // always overwrite existing pixel with [r,g,b,layerIndex] const renderStatus = super.render({ target: pickingFBO, layers, layerFilter, views, viewports, onViewportActive, cullRect, effects: effects?.filter(e => e.useInPicking), pass, isPicking: true, shaderModuleProps, clearColor: [0, 0, 0, 0], colorMask: 0xf, scissorRect }); // Clear the temp field this._colorEncoderState = null; const decodePickingColor = colorEncoderState && decodeColor.bind(null, colorEncoderState); return {decodePickingColor, stats: renderStatus}; } shouldDrawLayer(layer: Layer): boolean { const {pickable, operation} = layer.props; return ( (pickable && operation.includes('draw')) || operation.includes('terrain') || operation.includes('mask') ); } protected getShaderModuleProps( layer: Layer, effects: Effect[] | undefined, otherShaderModuleProps: Record<string, any> ): any { return { picking: { isActive: 1, isAttribute: this.pickZ }, lighting: {enabled: false} }; } protected getLayerParameters(layer: Layer, layerIndex: number, viewport: Viewport): Parameters { // TODO use Parameters type const pickParameters: any = { ...layer.props.parameters }; const {pickable, operation} = layer.props; if (!this._colorEncoderState || operation.includes('terrain')) { pickParameters.blend = false; } else if (pickable && operation.includes('draw')) { Object.assign(pickParameters, PICKING_BLENDING); pickParameters.blend = true; // TODO: blendColor no longer part of luma.gl API pickParameters.blendColor = encodeColor(this._colorEncoderState, layer, viewport); } return pickParameters; } protected _resetColorEncoder(pickZ: boolean) { // Track encoded layer indices this._colorEncoderState = pickZ ? null : { byLayer: new Map<Layer, EncodedPickingColors>(), byAlpha: [] }; // Temporarily store it on the instance so that it can be accessed by this.getLayerParameters return this._colorEncoderState; } } // Assign an unique alpha value for each pickable layer and track the encoding in the cache object // Returns normalized blend color function encodeColor( encoded: { byLayer: Map<Layer, EncodedPickingColors>; byAlpha: EncodedPickingColors[]; }, layer: Layer, viewport: Viewport ): number[] { const {byLayer, byAlpha} = encoded; let a; // Encode layerIndex in the alpha channel // TODO - combine small layers to better utilize the picking color space let entry = byLayer.get(layer); if (entry) { entry.viewports.push(viewport); a = entry.a; } else { a = byLayer.size + 1; if (a <= 255) { entry = {a, layer, viewports: [viewport]}; byLayer.set(layer, entry); byAlpha[a] = entry; } else { log.warn('Too many pickable layers, only picking the first 255')(); a = 0; } } return [0, 0, 0, a / 255]; } // Given a picked color, retrieve the corresponding layer and viewports from cache function decodeColor( encoded: { byLayer: Map<Layer, EncodedPickingColors>; byAlpha: EncodedPickingColors[]; }, pickedColor: number[] | Uint8Array ): | { pickedLayer: Layer; pickedViewports: Viewport[]; pickedObjectIndex: number; } | undefined { const entry = encoded.byAlpha[pickedColor[3]]; return ( entry && { pickedLayer: entry.layer, pickedViewports: entry.viewports, pickedObjectIndex: entry.layer.decodePickingColor(pickedColor) } ); }