Zoom-independent hit testing for custom 2D symbols in Three.js

Hi everyone,

I render many point markers in a Three.js scene. Some markers are custom 2D symbols made of lines, circles and filled shapes. They are drawn in screen-like size, so their visual size should stay usable while zooming.

Current picking:

  • meshes: Raycaster
  • lines/arcs: project endpoints to screen space, then test mouse distance to 2D segments
  • point markers: project marker center to screen space and use a pixel radius

Problem:

The radius-based marker picking is too rough. Some markers are hollow or only outlines, so I want the marker to be picked when the mouse is close to the actually visible stroke/fill, not just the center.

Requirements:

  • zoom-independent tolerance in pixels
  • works for custom marker shapes
  • clicking near a visible stroke should select it
  • clicking empty space should not select it
  • should work with many markers

What is the best technical approach in Three.js?

Options I’m considering:

  • custom CPU-side 2D hit testing against projected marker primitives
  • render an offscreen ID/color picking buffer
  • approximate each marker with simpler hit shapes
  • use instanced geometry and somehow derive picking from that

Which approach is usually best for this kind of screen-space marker picking?

have you considered using sprites with a transparent texture for the objects? this way you’d have a padding to the clickable area depending on the sprite / texture size ratio… or do the objects need to be rotatable in space relative to the camera?

Yes, I considered sprites, and they would work well for simple icon-like markers.

In my case the markers are CAD/survey-style symbols made from vector primitives: lines, arcs/circles, fills and sometimes text. They may have their own 2D symbol rotation, but they do not need arbitrary 3D rotation relative to the camera. They are mostly meant to behave like screen-facing / screen-sized CAD symbols.

The main issue with sprites is picking accuracy. Three.js raycasting against a Sprite would still hit the sprite quad, not the visible non-transparent pixels. So a transparent texture with padding would help tune the clickable area, but hollow symbols or outline-only symbols would still be selectable in empty areas unless I also do alpha-aware picking.

So for this use case I’m leaning toward either:

  1. CPU-side 2D hit testing against the actual symbol primitives, after a coarse screen-space bounding-box / spatial-index filter, or
  2. an offscreen ID picking pass where the same symbol shape is rendered with alpha/discard, so only visible pixels count.

If I converted the symbols to cached sprite textures, I’d probably still need alpha-based picking or an ID buffer to avoid selecting transparent parts.what would you recommend?