fix: volume select ignores splats outside the camera frustum#904
Open
mvanhorn wants to merge 1 commit into
Open
fix: volume select ignores splats outside the camera frustum#904mvanhorn wants to merge 1 commit into
mvanhorn wants to merge 1 commit into
Conversation
Sphere and Box volume selection only caught splats whose projection sat inside the current camera view: point the camera away from the volume and clicking Add selected nothing; aim partially and only the visible portion was selected. The root cause was in intersection-shader.ts: the frustum-cull check "if (!any(greaterThan(abs(ndc), vec3(1.0))))" wrapped every mode's predicate, gating world-space tests (sphere mode 2, box mode 3) on the splat also being inside NDC clip space. That gate is correct for screen-space modes (mask 0, rect 1) which need valid NDC for mask sampling and rect bounds, but wrong for world-space volume tests which should be independent of the current view. The fix restructures the shader body so the NDC computation and frustum gate only run for modes 0 and 1, while modes 2 and 3 evaluate their world-space predicate unconditionally. World position is still hoisted since both volume modes need it. The shader output channel layout, uniforms, and CPU-side dispatch path remain unchanged. Refs playcanvas#843
Contributor
There was a problem hiding this comment.
Pull request overview
Fixes volume selection (sphere/box modes) so splats are selected based on world-space containment regardless of camera orientation, by removing the NDC frustum cull from those modes while keeping it for screen-space modes (rect/mask).
Changes:
- Hoist world-space position computation before the mode switch.
- Gate the NDC computation and frustum cull behind
mode == 0 || mode == 1. - Run sphere (mode 2) and box (mode 3) predicates unconditionally in world space.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
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.
Summary
Restructure
src/shaders/intersection-shader.tsso the NDC frustum-cull check only gates the screen-space selection modes. Sphere (mode 2) and box (mode 3) now evaluate their world-space predicate unconditionally, so splats inside a volume are selected even when the camera is pointed away from them.Why this matters
Issue #843 reports that Sphere and Box volume selection only catches splats whose projection sits inside the current camera view - rotate the camera away from the volume and clicking Add selects nothing. Maintainer @slimbuck confirmed on 2026-03-18: "omg you're right. i can't believe it." The bug is the unconditional
if (!any(greaterThan(abs(ndc), vec3(1.0))))frustum cull that wraps every mode-specific predicate. That gate is correct for rect / mask / pixel-precise selection (modes 0 and 1) which operate in screen space, but it is wrong for sphere and box which test world-space containment.Implementation notes
!any(greaterThan(abs(ndc), vec3(1.0)))cull moved inside an(mode == 0 || mode == 1)branch.src/data-processor/intersect.tsandsrc/editor.tsare unchanged.Testing
npm run lintclean.npx tsc --noEmitclean.select.byMask(mode 0): mask sampling still requires valid NDC; behavior unchanged.Fixes #843