Implement client-side snapping#13531
Open
markschlosseratbentley wants to merge 20 commits into
Open
Conversation
Add snapping to 3d-tiles-edge-visibility sandbox.
Contributor
|
Thank you for the pull request, @markschlosseratbentley! ✅ We can confirm we have a CLA on file for you. |
2 tasks
Contributor
Author
|
Filed an issue to brainstorm other snap modes: #13548 |
Contributor
Author
markschlosseratbentley
marked this pull request as ready for review
July 14, 2026 15:03
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Description
Adds
Scene.snap(windowPosition, width, height)which returns the best geometry hit (edges preferred) in a pixel region around the cursor (default 25×25), with a world-space position. Returns aSceneSnapResult:{ object, isEdge, position, screenPosition }. The API is tagged@experimentalso it can evolve based on feedback.The behavior approximates the
NearestMicroStation snap mode documented here.Nearest: Tentative point snaps to point on the element nearest to the pointer.Screen.Recording.2026-05-29.at.1.18.35.PM.mov
How it works
Snapping runs as a dedicated offscreen pass, fully decoupled from
Scene.pick. The normal pick framebuffer stays RGBA8 (zero diff vsmain); plainpick/pickPosition/drillPick/pickAsyncpay no edge-rasterization cost.The snap pass renders into its own RGBA32F framebuffer. Each pixel carries:
isEdgeflag-v_positionEC.z)The snap payload lives entirely in the derived-command layer (
command.snapIdalongside the unchangedcommand.pickId); there are no ifdefs in shared shaders. New filesSnapping.jsandSnapFramebuffer.jshold everything specific to snapping (payload format, selection policy, world-position math).During the snap pass, edges render at 1 px (independent of the color-pass width) in all three
edgeDisplayModevalues, includingSURFACES_ONLY, where they're visually suppressed but still snappable.Hit selection is occlusion-gated, edges-beat-surfaces, closest-to-cursor. See
selectBestHitinSnapping.js. The winning hit's eye-space depth is unprojected into the returned world position.Only Model-pipeline primitives (3D Tiles, glTF models) are snappable.
Scene.snapreturnsundefined(with a one-time warning) when float color attachments are unsupported (requires WebGL2 /EXT_color_buffer_float).Possible future enhancements (deferred under
@experimental)objectsToExcludeequivalent). PlainScene.pickhas no exclusion either (only the ray-based APIs do) so this is not a stabilization blocker.snapAsyncanalog (mirroring thepick/pickAsyncsplit).Issue number and link
Related: #13548 (brainstorming for additional snap modes)
Testing plan
SnappingSpec.js3d-tiles-snappingmultifrustum-snapping-dev(multifrustum correctness harness with per-frustum diagnostics)Author checklist
CONTRIBUTORS.mdCHANGES.mdwith a short summary of my changeAI acknowledgment
If yes, I used the following Tools(s) and/or Service(s):
GitHub Copilot
If yes, I used the following Model(s):
Claude Opus 4.7
Claude Opus 4.8
Claude Fable 5