Skip to content

Commit d08a72b

Browse files
Improve perf of picking with large pickingRadius (visgl#1222)
1 parent c0af32a commit d08a72b

3 files changed

Lines changed: 114 additions & 25 deletions

File tree

src/core/lib/pick-layers.js

Lines changed: 36 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -339,7 +339,7 @@ function callLayerPickingCallbacks(infos, mode) {
339339
* Pick at a specified pixel with a tolerance radius
340340
* Returns the closest object to the pixel in shape `{pickedColor, pickedLayer, pickedObjectIndex}`
341341
*/
342-
function getClosestFromPickingBuffer(gl, {
342+
export function getClosestFromPickingBuffer(gl, {
343343
pickedColors,
344344
layers,
345345
deviceX,
@@ -348,42 +348,53 @@ function getClosestFromPickingBuffer(gl, {
348348
deviceRect
349349
}) {
350350
assert(pickedColors);
351-
let closestResultToCenter = NO_PICKED_OBJECT;
352351

353352
// Traverse all pixels in picking results and find the one closest to the supplied
354353
// [deviceX, deviceY]
354+
const {x, y, width, height} = deviceRect;
355355
let minSquareDistanceToCenter = deviceRadius * deviceRadius;
356+
let closestPixelIndex = -1;
356357
let i = 0;
357358

358-
for (let row = 0; row < deviceRect.height; row++) {
359-
for (let col = 0; col < deviceRect.width; col++) {
360-
// Decode picked layer from color
361-
const pickedLayerIndex = pickedColors[i + 3] - 1;
362-
363-
if (pickedLayerIndex >= 0) {
364-
const dx = col + deviceRect.x - deviceX;
365-
const dy = row + deviceRect.y - deviceY;
366-
const d2 = dx * dx + dy * dy;
367-
368-
if (d2 <= minSquareDistanceToCenter) {
369-
minSquareDistanceToCenter = d2;
370-
371-
// Decode picked object index from color
372-
const pickedColor = pickedColors.slice(i, i + 4);
373-
const pickedLayer = layers[pickedLayerIndex];
374-
if (pickedLayer) {
375-
const pickedObjectIndex = pickedLayer.decodePickingColor(pickedColor);
376-
closestResultToCenter = {pickedColor, pickedLayer, pickedObjectIndex};
377-
} else {
378-
log.error(0, 'Picked non-existent layer. Is picking buffer corrupt?');
359+
for (let row = 0; row < height; row++) {
360+
const dy = row + y - deviceY;
361+
const dy2 = dy * dy;
362+
363+
if (dy2 > minSquareDistanceToCenter) {
364+
// skip this row
365+
i += 4 * width;
366+
} else {
367+
for (let col = 0; col < width; col++) {
368+
// Decode picked layer from color
369+
const pickedLayerIndex = pickedColors[i + 3] - 1;
370+
371+
if (pickedLayerIndex >= 0) {
372+
const dx = col + x - deviceX;
373+
const d2 = dx * dx + dy2;
374+
375+
if (d2 <= minSquareDistanceToCenter) {
376+
minSquareDistanceToCenter = d2;
377+
closestPixelIndex = i;
379378
}
380379
}
380+
i += 4;
381381
}
382-
i += 4;
383382
}
384383
}
385384

386-
return closestResultToCenter;
385+
if (closestPixelIndex >= 0) {
386+
// Decode picked object index from color
387+
const pickedLayerIndex = pickedColors[closestPixelIndex + 3] - 1;
388+
const pickedColor = pickedColors.slice(closestPixelIndex, closestPixelIndex + 4);
389+
const pickedLayer = layers[pickedLayerIndex];
390+
if (pickedLayer) {
391+
const pickedObjectIndex = pickedLayer.decodePickingColor(pickedColor);
392+
return {pickedColor, pickedLayer, pickedObjectIndex};
393+
}
394+
log.error(0, 'Picked non-existent layer. Is picking buffer corrupt?');
395+
}
396+
397+
return NO_PICKED_OBJECT;
387398
}
388399
/* eslint-enable max-depth, max-statements */
389400

test/bench/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,12 @@ import coreLayersBench from './core-layers.bench';
2525
import layerBench from './layer.bench';
2626
import viewportBench from './viewport.bench';
2727
import colorBench from './color.bench';
28+
import pickLayersBench from './pick-layers.bench';
2829

2930
const suite = new Bench();
3031

3132
// add tests
33+
pickLayersBench(suite);
3234
coreLayersBench(suite);
3335
layerBench(suite);
3436
viewportBench(suite);

test/bench/pick-layers.bench.js

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import {Layer} from 'deck.gl/core';
2+
import {getClosestFromPickingBuffer} from 'deck.gl/core/lib/pick-layers';
3+
4+
const SAMPLE_LAYERS = [new Layer()];
5+
const OBJECT_COLOR = [0, 10, 20, 1];
6+
const NULL_COLOR = [0, 0, 0, 0];
7+
8+
const TEST_CASES = [
9+
{
10+
title: 'Solid',
11+
data: generateSampleData({
12+
pickingRadius: 10,
13+
getColor: () => OBJECT_COLOR
14+
})
15+
},
16+
{
17+
// radius 5 circle centered in 20x20 rect.
18+
title: 'Circle',
19+
data: generateSampleData({
20+
pickingRadius: 10,
21+
getColor: (x, y) => (10 - x) * (10 - x) + (10 - y) * (10 - y) < 25 ? OBJECT_COLOR : NULL_COLOR
22+
})
23+
},
24+
{
25+
// half of rect on top left
26+
title: 'Triangle',
27+
data: generateSampleData({
28+
pickingRadius: 10,
29+
getColor: (x, y) => x + y <= 10 ? OBJECT_COLOR : NULL_COLOR
30+
})
31+
},
32+
{
33+
title: 'Solid - Big',
34+
data: generateSampleData({
35+
pickingRadius: 50,
36+
getColor: () => OBJECT_COLOR
37+
})
38+
}
39+
];
40+
41+
export default function pickLayersBench(bench) {
42+
43+
bench = bench.group('getClosestFromPickingBuffer');
44+
45+
TEST_CASES.forEach(testCase => {
46+
bench = bench.add(testCase.title, () => getClosestFromPickingBuffer(null, testCase.data));
47+
});
48+
49+
return bench;
50+
}
51+
52+
function generateSampleData({pickingRadius, getColor}) {
53+
const width = pickingRadius * 2;
54+
const height = pickingRadius * 2;
55+
const pixels = new Uint8Array(width * height * 4);
56+
57+
let i = 0;
58+
for (let row = 0; row < height; row++) {
59+
for (let col = 0; col < width; col++) {
60+
const color = getColor(col, row);
61+
pixels[i++] = color[0];
62+
pixels[i++] = color[1];
63+
pixels[i++] = color[2];
64+
pixels[i++] = color[3];
65+
}
66+
}
67+
68+
return {
69+
pickedColors: pixels,
70+
layers: SAMPLE_LAYERS,
71+
deviceX: pickingRadius,
72+
deviceY: pickingRadius,
73+
deviceRadius: pickingRadius,
74+
deviceRect: {x: 0, y: 0, width, height}
75+
};
76+
}

0 commit comments

Comments
 (0)