Skip to content

BufferPrimitiveCollection: Picking API#13301

Open
donmccurdy wants to merge 8 commits intomainfrom
donmccurdy/feat/bufferprimitivecollection-picking
Open

BufferPrimitiveCollection: Picking API#13301
donmccurdy wants to merge 8 commits intomainfrom
donmccurdy/feat/bufferprimitivecollection-picking

Conversation

@donmccurdy
Copy link
Member

@donmccurdy donmccurdy commented Mar 13, 2026

Description

Adds picking / interactivity support to BufferPointCollection, BufferPolylineCollection, and BufferPolygonCollection. scene.pick(...) returns a {collection: BufferPrimitiveCollection, index: number} result. From this the user can create or reuse a primitive; hovering over N polygons sequentially should only require a single BufferPolygon instance.

bufferprim-picking.webm

Issue number and link

n/a

Testing plan

Unit tests added.

Picking code in a sandbox would look something like this:

const handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);

let selection = null;
const selectionOriginalColor = new Cesium.Color();

handler.setInputAction((movement) => {
  // Clear previous selection.
  if (selection) {
    const { collection, index } = selection;
    getCollectionPrimitive(collection, index).setColor(selectionOriginalColor);
    selection = null;
  }

  const picked = viewer.scene.pick(movement.endPosition);

  // Highlight current selection.
  if (picked && picked.collection instanceof BufferPrimitiveCollection) {
    const { collection, index } = picked;
    selection = { collection, index };

    const prim = getCollectionPrimitive(collection, index);
    prim.getColor(selectionOriginalColor);
    prim.setColor(Cesium.Color.CRIMSON);
  }
}, Cesium.ScreenSpaceEventType.MOUSE_MOVE);

const point = new BufferPoint();
const line = new BufferPolyline();
const polygon = new BufferPolygon();

/**
 * Returns BufferPrimitive view onto collection[index]. Repeated
 * calls will modify and reuse the same result object.
 */
function getCollectionPrimitive(collection, index) {
  switch (collection.constructor) {
    case BufferPointCollection:
      return collection.get(index, point);
    case BufferPolylineCollection:
      return collection.get(index, line);
    case BufferPolygonCollection:
      return collection.get(index, polygon);
    default:
      throw new Error(`Unknown type picked: ${collection.constructor.name}`);
  }
}

Author checklist

  • I have submitted a Contributor License Agreement
  • I have added my name to CONTRIBUTORS.md
  • I have updated CHANGES.md with a short summary of my change
  • I have added or updated unit tests to ensure consistent code coverage
  • I have updated the inline documentation, and included code examples where relevant
  • I have performed a self-review of my code

PR Dependency Tree

This tree was auto-generated by Charcoal

@github-actions
Copy link

Thank you for the pull request, @donmccurdy!

✅ We can confirm we have a CLA on file for you.

* @param {number} [startingIndex=0] The index into the array at which to start packing the elements.
*
* @returns {number[]} The array that was packed into
* @returns {number[]|TypedArray} The array that was packed into
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This may need to change, discussion ongoing in #13282.

@lilleyse
Copy link
Contributor

I think this may require some tweaks to work with scene.drillPick.

Check out the code here. The pick object needs to be hideable in order to continue drill picking. The Cesium3DTileFeature approach might make sense to use here.

@donmccurdy
Copy link
Member Author

@lilleyse I hadn't noticed scene.drillPick, thanks! That should be fixed now. I opted to make picked.primitive a getter returning a new BufferPrimitive, rather than allocating objects for every primitive up front. It would be nice to avoid the getter too, but this keeps the changes self-contained without changes to picking. We could reduce those allocations later with changes to picking, if we want.

@donmccurdy
Copy link
Member Author

donmccurdy commented Mar 16, 2026

Unfortunately, after doing some performance measurements ... .createPickId() has a pretty high cost relative to the rest of the renderer implementation. For a collection of 300K points, the first call to renderBufferPointCollection is:

  • 25–35ms without pick IDs
  • 65–75ms with context.createPickId
  • 100–120ms with context.createPickId + result.primitive getter

I was a bit worried about it, but didn't expect a 2–4x slowdown! For now maybe we put this feature behind an allowPicking: true constructor parameter on the collection? Possibly this is something that we could optimize in the picking implementation later on.

EDIT: I've put this behind an allowPicking flag for now.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants