Skip to main content
Version: v0.1.0

Point picking

onPointPick gives you the world coordinates and attributes of the point closest to where the user clicked. It works on both StreamedPointCloud and PointCloud.

Basic setup

<StreamedPointCloud
maxPoints={100_000}
colorBy="intensity"
onPointPick={(pt) => {
console.log(`clicked at (${pt.x.toFixed(2)}, ${pt.y.toFixed(2)}, ${pt.z.toFixed(2)})`);
console.log("intensity:", pt.attributes.intensity);
}}
/>

The callback fires on pointerdown inside the canvas when a point is found within pickRadius CSS pixels of the click.

What you get back

type PickedPoint = {
x: number; // world-space position
y: number;
z: number;
attributes: Record<string, number>; // all ingested attribute channels
screenDist: number; // distance from the click center in CSS px
slotIndex: number; // ring buffer slot (stable until next eviction)
confidence: number; // 0-1 proximity confidence
};

slotIndex is stable until the point is evicted. You can use it to track a specific point across frames, but don't rely on it surviving a reset() or heavy buffer pressure.

Pick radius

<StreamedPointCloud
pickRadius={12} // default: 8 CSS pixels
onPointPick={...}
/>

Larger values make it easier to click dense clouds. Smaller values give more precision on sparse clouds.

Pick strategy

When multiple points fall within the radius, pickStrategy decides which one wins:

<StreamedPointCloud
pickStrategy="highestImportance" // default
onPointPick={...}
/>
StrategyBehavior
highestImportanceReturns the point with the highest importance attribute value. Screen distance is the tiebreaker.
nearestReturns the point with the smallest screen distance from the click.
recentFirstReturns the most recently ingested point within the radius.

How it works

On every frame, the scene component writes the current VP matrix to a shared ref. On pointerdown, PointBuffer.pickNearest() projects all buffered points into screen space in O(N) and returns the best match within pickRadius. It runs on the CPU in both WebGPU and WebGL modes.

For large buffers (500k+ points), picking can take a few milliseconds. If that's a concern, consider reducing pickRadius to narrow the candidate set, or debouncing the pointerdown handler.

Null result

The callback is not called when the click misses all points. If you need to know when a click missed, use pointerdown on the canvas element directly and compare:

const lastPick = useRef(null);

<StreamedPointCloud
onPointPick={(pt) => { lastPick.current = pt; }}
/>

// in a canvas pointerdown handler:
// if lastPick.current was not just updated, it's a miss