diff --git a/docs/api-reference/core/deck.md b/docs/api-reference/core/deck.md index 9ca84f62459..def0032eeaf 100644 --- a/docs/api-reference/core/deck.md +++ b/docs/api-reference/core/deck.md @@ -634,8 +634,83 @@ Parameters: * `force` (boolean) - if `false`, only redraw if necessary (e.g. changes have been made to views or layers). If `true`, skip the check. Default `false`. +#### `pickObjectAsync` {#pickobjectasync} + +Get the closest pickable and visible object at the given screen coordinate. + +```ts +await deck.pickObjectAsync({x, y, radius, layerIds, unproject3D}) +``` + +Parameters: + +* `x` (number) - x position in pixels +* `y` (number) - y position in pixels +* `radius` (number, optional) - radius of tolerance in pixels. Default `0`. +* `layerIds` (string[], optional) - a list of layer ids to query from. If not specified, then all pickable and visible layers are queried. +* `unproject3D` (boolean, optional) - if `true`, `info.coordinate` will be a 3D point by unprojecting the `x, y` screen coordinates onto the picked geometry. Default `false`. + +Returns: + +* a single [`info`](../../developer-guide/interactivity.md#the-pickinginfo-object) object, or `null` if nothing is found. + + +#### `pickMultipleObjectsAsync` {#pickmultipleobjectsasync} + +Performs deep picking. Finds all close pickable and visible object at the given screen coordinate, even if those objects are occluded by other objects. + +```ts +await deck.pickMultipleObjectsAsync({x, y, radius, layerIds, depth, unproject3D}) +``` + +Parameters: + +* `x` (number) - x position in pixels +* `y` (number) - y position in pixels +* `radius` (number, optional) - radius of tolerance in pixels. Default `0`. +* `layerIds` (string[], optional) - a list of layer ids to query from. If not specified, then all pickable and visible layers are queried. +* `depth` - Specifies the max number of objects to return. Default `10`. +* `unproject3D` (boolean, optional) - if `true`, `info.coordinate` will be a 3D point by unprojecting the `x, y` screen coordinates onto the picked geometry. Default `false`. + +Returns: + +* An array of [`info`](../../developer-guide/interactivity.md#the-pickinginfo-object) objects. The array will be empty if no object was picked. + +Notes: + +* Deep picking is implemented as a sequence of simpler picking operations and can have a performance impact. Should this become a concern, you can use the `depth` parameter to limit the number of matches that can be returned, and thus the maximum number of picking operations. + + +#### `pickObjectsAsync` {#pickobjectsasync} + +Get all pickable and visible objects within a bounding box. + +```ts +await deck.pickObjectsAsync({x, y, width, height, layerIds, maxObjects}) +``` + +Parameters: + +* `x` (number) - left of the bounding box in pixels +* `y` (number) - top of the bouding box in pixels +* `width` (number, optional) - width of the bouding box in pixels. Default `1`. +* `height` (number, optional) - height of the bouding box in pixels. Default `1`. +* `layerIds` (string[], optional) - a list of layer ids to query from. If not specified, then all pickable and visible layers are queried. +* `maxObjects` (number, optional) - if specified, limits the number of objects that can be returned. + +Returns: + +* an array of unique [`info`](../../developer-guide/interactivity.md#the-pickinginfo-object) objects + +Notes: + +* The query methods are designed to quickly find objects by utilizing the picking buffer. +* The query methods offer more flexibility for developers to handle events compared to the built-in hover and click callbacks. + #### `pickObject` {#pickobject} + + Get the closest pickable and visible object at the given screen coordinate. ```js @@ -657,6 +732,8 @@ Returns: #### `pickMultipleObjects` {#pickmultipleobjects} + + Performs deep picking. Finds all close pickable and visible object at the given screen coordinate, even if those objects are occluded by other objects. ```js @@ -683,6 +760,8 @@ Notes: #### `pickObjects` {#pickobjects} + + Get all pickable and visible objects within a bounding box. ```js diff --git a/docs/api-reference/react/deckgl.md b/docs/api-reference/react/deckgl.md index 236a2bb5546..f87a99b6da6 100644 --- a/docs/api-reference/react/deckgl.md +++ b/docs/api-reference/react/deckgl.md @@ -192,6 +192,9 @@ All [Deck](../core/deck.md#methods) methods are available on the `DeckGL` compon The public methods you can call explicitly list below: +* `pickObjectAsync` +* `pickMultipleObjectsAsync` +* `pickObjectsAsync` * `pickObject` * `pickMultipleObjects` * `pickObjects` diff --git a/docs/developer-guide/interactivity.md b/docs/developer-guide/interactivity.md index 158d9d639e8..a7cc2fb5a33 100644 --- a/docs/developer-guide/interactivity.md +++ b/docs/developer-guide/interactivity.md @@ -965,7 +965,7 @@ function App() { While the default events handle most of the use cases, sometimes applications need more control over when and how picking is performed. -The picking engine is exposed through the [`Deck.pickObject`](../api-reference/core/deck.md#pickobject) and [`Deck.pickObjects`](../api-reference/core/deck.md#pickobjects) methods. These methods allow you to query what layers and objects within those layers are under a specific point or within a specified rectangle. They return `PickingInfo` objects as described above. +The picking engine is exposed through the [`Deck.pickObjectAsync`](../api-reference/core/deck.md#pickobjectasync) and [`Deck.pickObjectsAsync`](../api-reference/core/deck.md#pickobjectsasync) methods. These methods allow you to query what layers and objects within those layers are under a specific point or within a specified rectangle. They return `PickingInfo` objects as described above. @@ -976,9 +976,9 @@ import {Deck} from '@deck.gl/core'; const deckInstance = new Deck({ // ... - onClick: ({x, y}) => { + onClick: async ({x, y}) => { // Query up to 5 overlapping objects under the pointer - const pickInfos = deckInstance.pickMultipleObjects({x, y, radius: 1, depth: 5}); + const pickInfos = await deckInstance.pickMultipleObjectsAsync({x, y, radius: 1, depth: 5}); console.log(pickInfo); } }); @@ -992,9 +992,9 @@ import {Deck, PickingInfo} from '@deck.gl/core'; const deckInstance = new Deck({ // ... - onClick: ({x, y}: PickingInfo) => { + onClick: async ({x, y}: PickingInfo) => { // Query up to 5 overlapping objects under the pointer - const pickInfos: PickingInfo[] = deckInstance.pickMultipleObjects({x, y, radius: 1, depth: 5}); + const pickInfos: PickingInfo[] = await deckInstance.pickMultipleObjectsAsync({x, y, radius: 1, depth: 5}); console.log(pickInfo); } }); @@ -1011,13 +1011,13 @@ import {PickingInfo} from '@deck.gl/core'; function App() { const deckRef = useRef(); - const onClick = useCallback((evt: React.MouseEvent) => { + const onClick = useCallback(async (evt: React.MouseEvent) => { // Get mouse position relative to the containing div const containerRect = evt.currentTarget.getBoundingClientRect(); const x = evt.clientX - containerRect.left; const y = evt.clientY = containerRect.top; // Query up to 5 overlapping objects under the pointer - const pickInfos: PickingInfo[] = deckRef.current?.pickMultipleObjects({x, y, radius: 1, depth: 5}); + const pickInfos: PickingInfo[] = await deckRef.current?.pickMultipleObjectsAsync({x, y, radius: 1, depth: 5}); console.log(pickInfo); }, []) @@ -1031,7 +1031,7 @@ function App() { -Also note that by directly calling `pickObject`, integrating deck.gl into an existing application often becomes easier since you don't have to change the application's existing approach to event handling. +Also note that by directly calling `pickObjectAsync`, integrating deck.gl into an existing application often becomes easier since you don't have to change the application's existing approach to event handling. ### Under The Hood diff --git a/modules/core/src/lib/deck-picker.ts b/modules/core/src/lib/deck-picker.ts index e13036669b0..77a9f14167d 100644 --- a/modules/core/src/lib/deck-picker.ts +++ b/modules/core/src/lib/deck-picker.ts @@ -113,7 +113,7 @@ export default class DeckPicker { /** * Pick the closest info at given coordinate * @returns picking info - * @deprecated WebGL only - use pickObjectAsync instead + * @note WebGL only - use pickObjectAsync instead */ pickObject(opts: PickByPointOptions & PickOperationContext) { return this._pickClosestObject(opts); @@ -122,7 +122,7 @@ export default class DeckPicker { /** * Get all unique infos within a bounding box * @returns all unique infos within a bounding box - * @deprecated WebGL only - use pickObjectAsync instead + * @note WebGL only - use pickObjectAsync instead */ pickObjects(opts: PickByRectOptions & PickOperationContext) { return this._pickVisibleObjects(opts); diff --git a/modules/core/src/lib/deck.ts b/modules/core/src/lib/deck.ts index 47906599727..d0904926d8e 100644 --- a/modules/core/src/lib/deck.ts +++ b/modules/core/src/lib/deck.ts @@ -611,6 +611,66 @@ export default class Deck { } /** Query the object rendered on top at a given point */ + async pickObjectAsync(opts: { + /** x position in pixels */ + x: number; + /** y position in pixels */ + y: number; + /** Radius of tolerance in pixels. Default `0`. */ + radius?: number; + /** A list of layer ids to query from. If not specified, then all pickable and visible layers are queried. */ + layerIds?: string[]; + /** If `true`, `info.coordinate` will be a 3D point by unprojecting the `x, y` screen coordinates onto the picked geometry. Default `false`. */ + unproject3D?: boolean; + }): Promise { + const infos = (await this._pickAsync('pickObjectAsync', 'pickObject Time', opts)).result; + return infos.length ? infos[0] : null; + } + + /* Query all rendered objects at a given point */ + async pickMultipleObjectsAsync(opts: { + /** x position in pixels */ + x: number; + /** y position in pixels */ + y: number; + /** Radius of tolerance in pixels. Default `0`. */ + radius?: number; + /** Specifies the max number of objects to return. Default `10`. */ + depth?: number; + /** A list of layer ids to query from. If not specified, then all pickable and visible layers are queried. */ + layerIds?: string[]; + /** If `true`, `info.coordinate` will be a 3D point by unprojecting the `x, y` screen coordinates onto the picked geometry. Default `false`. */ + unproject3D?: boolean; + }): Promise { + opts.depth = opts.depth || 10; + return (await this._pickAsync('pickObjectAsync', 'pickMultipleObjects Time', opts)).result; + } + + /** + * Query all objects rendered on top within a bounding box + * @note Caveat: this method performs multiple async GPU queries, so state could potentially change between calls. + */ + async pickObjectsAsync(opts: { + /** Left of the bounding box in pixels */ + x: number; + /** Top of the bounding box in pixels */ + y: number; + /** Width of the bounding box in pixels. Default `1` */ + width?: number; + /** Height of the bounding box in pixels. Default `1` */ + height?: number; + /** A list of layer ids to query from. If not specified, then all pickable and visible layers are queried. */ + layerIds?: string[]; + /** If specified, limits the number of objects that can be returned. */ + maxObjects?: number | null; + }): Promise { + return await this._pickAsync('pickObjectsAsync', 'pickObjects Time', opts); + } + + /** + * Query the object rendered on top at a given point + * @deprecated WebGL only. Use `pickObjectsAsync` instead + */ pickObject(opts: { /** x position in pixels */ x: number; @@ -627,7 +687,10 @@ export default class Deck { return infos.length ? infos[0] : null; } - /* Query all rendered objects at a given point */ + /** + * Query all rendered objects at a given point + * @deprecated WebGL only. Use `pickObjectsAsync` instead + */ pickMultipleObjects(opts: { /** x position in pixels */ x: number; @@ -646,7 +709,10 @@ export default class Deck { return this._pick('pickObject', 'pickMultipleObjects Time', opts).result; } - /* Query all objects rendered on top within a bounding box */ + /** + * Query all objects rendered on top within a bounding box + * @deprecated WebGL only. Use `pickObjectsAsync` instead + */ pickObjects(opts: { /** Left of the bounding box in pixels */ x: number; @@ -704,6 +770,47 @@ export default class Deck { // Private Methods + private _pickAsync( + method: 'pickObjectAsync', + statKey: string, + opts: PickByPointOptions & {layerIds?: string[]} + ): Promise<{ + result: PickingInfo[]; + emptyInfo: PickingInfo; + }>; + private _pickAsync( + method: 'pickObjectsAsync', + statKey: string, + opts: PickByRectOptions & {layerIds?: string[]} + ): Promise; + + private _pickAsync( + method: 'pickObjectAsync' | 'pickObjectsAsync', + statKey: string, + opts: (PickByPointOptions | PickByRectOptions) & {layerIds?: string[]} + ) { + assert(this.deckPicker); + + const {stats} = this; + + stats.get('Pick Count').incrementCount(); + stats.get(statKey).timeStart(); + + const infos = this.deckPicker[method]({ + // layerManager, viewManager and effectManager are always defined if deckPicker is + layers: this.layerManager!.getLayers(opts), + views: this.viewManager!.getViews(), + viewports: this.getViewports(opts), + onViewportActive: this.layerManager!.activateViewport, + effects: this.effectManager!.getEffects(), + ...opts + }); + + stats.get(statKey).timeEnd(); + + return infos; + } + private _pick( method: 'pickObject', statKey: string, diff --git a/modules/react/src/deckgl.ts b/modules/react/src/deckgl.ts index c60e1486785..ae99ffec26d 100644 --- a/modules/react/src/deckgl.ts +++ b/modules/react/src/deckgl.ts @@ -44,6 +44,9 @@ export type DeckGLProps = Omit< export type DeckGLRef = { deck?: Deck; + pickObjectAsync: Deck['pickObjectAsync']; + pickObjectsAsync: Deck['pickObjectsAsync']; + pickMultipleObjectsAsync: Deck['pickMultipleObjectsAsync']; pickObject: Deck['pickObject']; pickObjects: Deck['pickObjects']; pickMultipleObjects: Deck['pickMultipleObjects']; @@ -57,6 +60,9 @@ function getRefHandles( return thisRef.deck; }, // The following method can only be called after ref is available, by which point deck is defined in useEffect + pickObjectAsync: opts => thisRef.deck!.pickObjectAsync(opts), + pickMultipleObjectsAsync: opts => thisRef.deck!.pickMultipleObjectsAsync(opts), + pickObjectsAsync: opts => thisRef.deck!.pickObjectsAsync(opts), pickObject: opts => thisRef.deck!.pickObject(opts), pickMultipleObjects: opts => thisRef.deck!.pickMultipleObjects(opts), pickObjects: opts => thisRef.deck!.pickObjects(opts)