Skip to content

feat(picking)!: sprite & billboard picking (pickSprite2D + pickBillboardSprite)#341

Draft
VicenteCartas wants to merge 12 commits into
BabylonJS:masterfrom
VicenteCartas:picking
Draft

feat(picking)!: sprite & billboard picking (pickSprite2D + pickBillboardSprite)#341
VicenteCartas wants to merge 12 commits into
BabylonJS:masterfrom
VicenteCartas:picking

Conversation

@VicenteCartas

@VicenteCartas VicenteCartas commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds picking for both sprite families, bringing Lite to parity with Babylon's scene.pickSprite(x, y):

  • pickSprite2D(layers, xPx, yPx) — a pure-CPU hit test for Sprite2DLayer sprites (HUD / pure-2D). Walks layers in reverse draw order and inverts each sprite's pivot + rotation, so it reports exactly the sprite the GPU drew under
    the point. No GPU pass, no scene/camera required.
  • pickBillboardSprite(scene, xPx, yPx) — a GPU picker for world-space *BillboardSpriteSystem sprites. Billboards are drawn into the same 1×1 depth-sorted pass the mesh picker uses, so a billboard occluded by a mesh (or a nearer billboard) correctly loses the pick.

Previously the engine had no sprite-picking primitive at all — the Freeciv demo had to hand-roll tile selection from analytic grid math. This ships the real primitive and wires the demo to it.

Public API

// 2D sprites — CPU
export function pickSprite2D(layers: ReadonlyArray<Sprite2DLayer>, xPx: number, yPx: number): SpritePickInfo | null;
export interface SpritePickInfo {
    layer: Sprite2DLayer;
    spriteIndex: number;
    u: number;
    v: number;
}

// Billboards — GPU
export function pickBillboardSprite(scene: SceneContext, xPx: number, yPx: number): Promise<BillboardPickInfo | null>;
export interface BillboardPickInfo {
    system: BillboardSpriteSystem;
    spriteIndex: number;
    pickedPoint: [number, number, number] | null;
    distance: number;
}

Design

  • Two pickers, matched to the two families. 2D sprites are screen-space rectangles → a trivial CPU rect test (pure-2D apps have no scene/camera/RT to hook a GPU pass into anyway). Billboards live in the 3D depth buffer → they must
    be picked in the shared GPU pass to respect occlusion.
  • Zero cost when unused (mirrors GS picking). The billboard pick pipeline + all its draw/resolve/dispose orchestration live in a dynamically-imported picking/billboard-pick-pipeline.ts. gpu-picker.ts keeps only a thin guarded dispatch (if (scene._billboardSystems.length) …), exactly like the existing Gaussian-splatting picking path. A mesh-only or billboard-free picker scene fetches zero billboard-pick bytes (verified: the chunk is built but never in runtimeChunks). 2D pickSprite2D lives in its own folder and tree-shakes away unless called.
  • No render-path changes. Picking is purely additive — the only new scene state is an opaque scene._billboardSystems registry (mirroring _gsMeshes), so visual parity cannot move.

Demo

The Freeciv demo now selects the scout via pickSprite2D against its sprite (which overhangs its tile and slides between tiles mid-hop), while the move destination keeps the analytic tile inversion — each technique used where it fits. Also fixes the lab dev server serving .spec / .tilespec tileset files (they were falling through to the SPA HTML fallback).

Lab scenes

Two new parity scenes alongside the existing picking scenes (113–115):

  • Scene 117 — 2D Sprite Picking (pickSprite2D, MAD 0.005)
  • Scene 118 — Billboard Sprite Picking (pickBillboardSprite vs BJS scene.pickSprite, MAD 0.041)

Tests

  • Unit: pick-sprite-2d.test.ts (11), billboard-pick.test.ts (5).
  • GPU plumbing: billboard-pick.spec.ts — real-WebGPU hit / occlusion / miss.
  • Parity: scenes 117 + 118.
  • Existing mesh & GS picking parity (113 / 114 / 115 / 129) re-verified green — the
    gpu-picker refactor is non-regressing.

Bundle size

The shared gpu-picker dispatch glue grows picker scenes by ~+0.3 KB raw / +0.1 KB gzip (consistent with the existing GS-picking pattern; billboard code itself is 0 bytes unless a scene uses billboards). Three picker-scene ceilings bumped to absorb this natural growth.

bundle-size.spec.ts's SPRITE_USING_IDS allowlist gains 117 / 118 (they legitimately load sprite modules).

Notes / follow-ups

  • Sprite2DProps.pickable is accepted for API compatibility but not yet consulted
    by pickSprite2D (every visible sprite is pickable) — a future enhancement.
  • Compat-layer scene.pickSprite remains a separate follow-up.
  • Per-hit UV reconstruction for billboards is deferred (not in the v1
    BillboardPickInfo).
  • Additive change — no breaking-change marker needed.

BREAKING CHANGE: removed the unused optional pickable field from Sprite2DProps. It was accepted but never read (every visible sprite is pickable); delete it from any call sites.

Vicente Cartas Espinel added 2 commits June 30, 2026 14:36
Copilot AI review requested due to automatic review settings June 30, 2026 21:51
@bjsplat

bjsplat commented Jun 30, 2026

Copy link
Copy Markdown

API Changes

API Extractor detected public API changes for @babylonjs/lite.

No removed public API lines were detected; this appears to be additive.

API Extractor diff
diff --git a/home/vsts/work/1/s/test-results/api-report/target/temp/babylon-lite.api.md b/home/vsts/work/1/s/test-results/api-report/current/temp/babylon-lite.api.md
index 873e78f3..68e1a7b1 100644
--- a/home/vsts/work/1/s/test-results/api-report/target/temp/babylon-lite.api.md
+++ b/home/vsts/work/1/s/test-results/api-report/current/temp/babylon-lite.api.md
@@ -586,6 +586,14 @@ export type BillboardDepthMode = "transparent" | "cutout";
 // @public
 export type BillboardOrientation = "facing" | "axis-locked";
 
+// @public
+export interface BillboardPickInfo {
+    distance: number;
+    pickedPoint: [number, number, number] | null;
+    spriteIndex: number;
+    system: BillboardSpriteSystem;
+}
+
 // @public
 export interface BillboardSpriteHandle {
     // (undocumented)
@@ -3615,6 +3623,9 @@ export interface PhysicsWorld {}
 // @public
 export function pickAsync(picker: GpuPicker, x: number, y: number, options?: PickOptions): Promise<PickingInfo>;
 
+// @public
+export function pickBillboardSprite(scene: SceneContext, x: number, y: number): Promise<BillboardPickInfo | null>;
+
 // @public
 export interface PickDiscardRule {
     readonly key: string;
@@ -3660,6 +3671,9 @@ export interface PickOptions {
     filter?: (mesh: Mesh) => boolean;
 }
 
+// @public
+export function pickSprite2D(layers: ReadonlyArray<Sprite2DLayer>, xPx: number, yPx: number): SpritePickInfo | null;
+
 // @public
 export interface PixelsTexture2DOptions {
     addressModeU?: GPUAddressMode;
@@ -5269,6 +5283,15 @@ export interface SpriteFrameAnimation {
     to: number;
 }
 
+// @public
+export interface SpritePickInfo {
+    layer: Sprite2DLayer;
+    spriteIndex: number;
+    u: number;
+    // (undocumented)
+    v: number;
+}
+
 // @public
 export interface SpriteRenderer extends RenderingContext_2 {
     readonly layers: readonly Sprite2DLayer[];

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR adds sprite picking to Babylon Lite for both sprite families: a CPU-based pickSprite2D for Sprite2DLayer HUD/pure-2D sprites and a GPU-based pickBillboardSprite that integrates billboard picking into the existing 1×1 mesh picking pass (respecting occlusion). It also wires the Freeciv demo to use the new 2D picker, adds new parity scenes (117/118), and introduces unit/plumbing tests plus supporting lab/docs updates.

Changes:

  • Add pickSprite2D (CPU) + pickBillboardSprite (GPU) and export them from the package root.
  • Integrate billboard systems into gpu-picker via a dynamically imported billboard-pick-pipeline.
  • Add new test coverage (unit + Playwright plumbing + parity scenes) and update lab/demo/docs/bundle manifests accordingly.

Reviewed changes

Copilot reviewed 62 out of 64 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
tests/lite/unit/picking-discard-api.test.ts Updates picker scene stub to include _billboardSystems.
tests/lite/unit/pick-sprite-2d.test.ts Unit tests for pickSprite2D behavior (pivot/rotation/ordering/visibility).
tests/lite/unit/billboard-pick.test.ts Unit tests for UBO packing and billboard-system scene registry behavior.
tests/lite/plumbing/billboard-pick.spec.ts Playwright GPU plumbing test for billboard picking hit/occlusion/miss.
tests/lite/parity/scenes/scene118-billboard-picking.spec.ts Parity test for billboard picking scene 118.
tests/lite/parity/scenes/scene117-sprite-2d-picking.spec.ts Parity test for 2D sprite picking scene 117.
tests/lite/parity/bundle-size.spec.ts Adds 117/118 to sprite-using allowlist for bundle-size validation.
scene-config.json Adds scenes 117/118 and updates bundle ceilings for select scenes.
packages/babylon-lite/src/sprite/sprite-2d.ts Clarifies pickable doc comment (API compat; not yet used by picker).
packages/babylon-lite/src/sprite/picking/pick-sprite-2d.ts Implements CPU hit-testing for 2D sprites (reverse draw order, pivot/rotation inversion).
packages/babylon-lite/src/sprite/picking/pick-billboard.ts Adds public async wrapper to pick billboard sprites via the GPU picker.
packages/babylon-lite/src/sprite/billboard-scene.ts Registers billboard systems on the scene for picker iteration.
packages/babylon-lite/src/scene/scene-core.ts Adds _billboardSystems to SceneContext lifecycle (init/dispose).
packages/babylon-lite/src/picking/picking-info.ts Extends PickingInfo with internal _spritePick payload.
packages/babylon-lite/src/picking/gpu-picker.ts Integrates billboard picking into the shared GPU pick pass + disposal path.
packages/babylon-lite/src/picking/billboard-pick-pipeline.ts Implements dynamic-imported billboard GPU picking pipeline (draw/resolve/resources).
packages/babylon-lite/src/index.ts Exports new picking APIs and result types from package root.
lab/vite.config.ts Serves new billboard-pick test HTML and sets MIME types for .spec/.tilespec.
lab/public/bundle/manifest/scene86.json Updates measured bundle runtime sizes/chunks.
lab/public/bundle/manifest/scene72.json Updates measured bundle runtime sizes/chunks.
lab/public/bundle/manifest/scene59.json Updates measured bundle runtime sizes/chunks.
lab/public/bundle/manifest/scene49.json Updates measured bundle runtime sizes/chunks.
lab/public/bundle/manifest/scene48.json Updates measured bundle runtime sizes/chunks.
lab/public/bundle/manifest/scene43.json Updates measured bundle runtime sizes/chunks.
lab/public/bundle/manifest/scene244.json Updates measured bundle runtime sizes/chunks.
lab/public/bundle/manifest/scene240.json Updates measured bundle runtime sizes/chunks.
lab/public/bundle/manifest/scene224.json Updates measured bundle runtime sizes/chunks.
lab/public/bundle/manifest/scene222.json Updates measured bundle runtime sizes/chunks.
lab/public/bundle/manifest/scene221.json Updates measured bundle runtime sizes/chunks.
lab/public/bundle/manifest/scene216.json Updates measured bundle runtime sizes/chunks.
lab/public/bundle/manifest/scene206.json Updates measured bundle runtime sizes/chunks.
lab/public/bundle/manifest/scene201.json Updates measured bundle runtime sizes/chunks.
lab/public/bundle/manifest/scene2.json Updates measured bundle runtime sizes/chunks.
lab/public/bundle/manifest/scene19.json Updates measured bundle runtime sizes/chunks.
lab/public/bundle/manifest/scene177.json Updates measured bundle runtime sizes/chunks.
lab/public/bundle/manifest/scene158.json Updates measured bundle runtime sizes/chunks.
lab/public/bundle/manifest/scene147.json Updates measured bundle runtime sizes/chunks.
lab/public/bundle/manifest/scene129.json Updates measured bundle runtime sizes/chunks.
lab/public/bundle/manifest/scene118.json Adds new scene 118 bundle manifest.
lab/public/bundle/manifest/scene117.json Adds new scene 117 bundle manifest.
lab/public/bundle/manifest/scene115.json Updates measured bundle runtime sizes/chunks.
lab/public/bundle/manifest/scene114.json Updates measured bundle runtime sizes/chunks.
lab/public/bundle/manifest/scene113.json Updates measured bundle runtime sizes/chunks.
lab/public/bundle/manifest/scene110.json Updates measured bundle runtime sizes/chunks.
lab/public/bundle/manifest/scene100.json Updates measured bundle runtime sizes/chunks.
lab/lite/src/lite/scene118.ts New Lite parity scene implementing billboard sprite picking visualization.
lab/lite/src/lite/scene117.ts New Lite parity scene implementing 2D sprite picking visualization.
lab/lite/src/demos/freeciv/live.ts Adds hitScout using pickSprite2D for unit selection hit-testing.
lab/lite/src/demos/freeciv.ts Uses sprite picking for scout selection and passes world coords through controls.
lab/lite/src/bjs/scene118.ts Babylon.js oracle for scene 118 using scene.pickSprite.
lab/lite/src/bjs/scene117.ts Babylon.js oracle for scene 117 rendering deterministic highlight (no 2D pick API).
lab/lite/src/billboard-pick-test.ts Standalone GPU billboard picking test harness for plumbing spec.
lab/lite/scene118.html Adds lab HTML entry for scene 118.
lab/lite/scene117.html Adds lab HTML entry for scene 117.
lab/lite/bundle-scene118.html Adds bundle HTML entry for scene 118.
lab/lite/bundle-scene117.html Adds bundle HTML entry for scene 117.
lab/lite/bundle-bjs-scene118.html Adds Babylon.js bundle HTML entry for scene 118.
lab/lite/bundle-bjs-scene117.html Adds Babylon.js bundle HTML entry for scene 117.
lab/lite/billboard-pick-test.html Adds lab HTML entry for GPU billboard pick plumbing test.
lab/lite/babylon-ref-scene118.html Adds Babylon.js reference HTML entry for scene 118.
lab/lite/babylon-ref-scene117.html Adds Babylon.js reference HTML entry for scene 117.
docs/lite/architecture/32-sprites.md Updates sprite architecture docs to reflect exported picking APIs and design.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/babylon-lite/src/picking/gpu-picker.ts Outdated
Comment thread scene-config.json Outdated
Comment thread scene-config.json Outdated
Comment thread scene-config.json Outdated
@VicenteCartas VicenteCartas marked this pull request as draft June 30, 2026 22:01
@bjsplat

bjsplat commented Jun 30, 2026

Copy link
Copy Markdown

Bundle Size Changes

Increases

Package Current Master Change
Scene 129 — Gaussian Splatting GPU Picking
scene129
84 KB 83 KB +1 KB
Scene 221 — Pointer Drags
scene221
101 KB 100 KB +1 KB

Sizes rounded to nearest KB. Run pnpm build:bundle-scenes locally to verify.

@bjsplat

bjsplat commented Jun 30, 2026

Copy link
Copy Markdown

Lab - Static Site

Open deployed site

Build 20260630.3 - merge @ b838194

@bjsplat

bjsplat commented Jun 30, 2026

Copy link
Copy Markdown

API Changes

API Extractor detected public API changes for @babylonjs/lite.

Potentially breaking changes detected. Removed or changed public API lines:

  • pickable?: boolean;
API Extractor diff
diff --git a/home/vsts/work/1/s/test-results/api-report/target/temp/babylon-lite.api.md b/home/vsts/work/1/s/test-results/api-report/current/temp/babylon-lite.api.md
index 873e78f3..21e45470 100644
--- a/home/vsts/work/1/s/test-results/api-report/target/temp/babylon-lite.api.md
+++ b/home/vsts/work/1/s/test-results/api-report/current/temp/babylon-lite.api.md
@@ -586,6 +586,14 @@ export type BillboardDepthMode = "transparent" | "cutout";
 // @public
 export type BillboardOrientation = "facing" | "axis-locked";
 
+// @public
+export interface BillboardPickInfo {
+    distance: number;
+    pickedPoint: [number, number, number] | null;
+    spriteIndex: number;
+    system: BillboardSpriteSystem;
+}
+
 // @public
 export interface BillboardSpriteHandle {
     // (undocumented)
@@ -3615,6 +3623,9 @@ export interface PhysicsWorld {}
 // @public
 export function pickAsync(picker: GpuPicker, x: number, y: number, options?: PickOptions): Promise<PickingInfo>;
 
+// @public
+export function pickBillboardSprite(scene: SceneContext, x: number, y: number): Promise<BillboardPickInfo | null>;
+
 // @public
 export interface PickDiscardRule {
     readonly key: string;
@@ -3660,6 +3671,9 @@ export interface PickOptions {
     filter?: (mesh: Mesh) => boolean;
 }
 
+// @public
+export function pickSprite2D(layers: ReadonlyArray<Sprite2DLayer>, xPx: number, yPx: number): SpritePickInfo | null;
+
 // @public
 export interface PixelsTexture2DOptions {
     addressModeU?: GPUAddressMode;
@@ -5127,7 +5141,6 @@ export interface Sprite2DProps {
     flipY?: boolean;
     // (undocumented)
     frame?: number;
-    pickable?: boolean;
     // (undocumented)
     positionPx: [number, number];
     // (undocumented)
@@ -5269,6 +5282,15 @@ export interface SpriteFrameAnimation {
     to: number;
 }
 
+// @public
+export interface SpritePickInfo {
+    layer: Sprite2DLayer;
+    spriteIndex: number;
+    u: number;
+    // (undocumented)
+    v: number;
+}
+
 // @public
 export interface SpriteRenderer extends RenderingContext_2 {
     readonly layers: readonly Sprite2DLayer[];

@VicenteCartas VicenteCartas changed the title feat(picking): sprite & billboard picking (pickSprite2D + pickBillboardSprite) feat!(picking): sprite & billboard picking (pickSprite2D + pickBillboardSprite) Jun 30, 2026
@bjsplat

bjsplat commented Jun 30, 2026

Copy link
Copy Markdown

API Changes

API Extractor detected public API changes for @babylonjs/lite.

Potentially breaking changes detected. Removed or changed public API lines:

  • pickable?: boolean;
API Extractor diff
diff --git a/home/vsts/work/1/s/test-results/api-report/target/temp/babylon-lite.api.md b/home/vsts/work/1/s/test-results/api-report/current/temp/babylon-lite.api.md
index 873e78f3..21e45470 100644
--- a/home/vsts/work/1/s/test-results/api-report/target/temp/babylon-lite.api.md
+++ b/home/vsts/work/1/s/test-results/api-report/current/temp/babylon-lite.api.md
@@ -586,6 +586,14 @@ export type BillboardDepthMode = "transparent" | "cutout";
 // @public
 export type BillboardOrientation = "facing" | "axis-locked";
 
+// @public
+export interface BillboardPickInfo {
+    distance: number;
+    pickedPoint: [number, number, number] | null;
+    spriteIndex: number;
+    system: BillboardSpriteSystem;
+}
+
 // @public
 export interface BillboardSpriteHandle {
     // (undocumented)
@@ -3615,6 +3623,9 @@ export interface PhysicsWorld {}
 // @public
 export function pickAsync(picker: GpuPicker, x: number, y: number, options?: PickOptions): Promise<PickingInfo>;
 
+// @public
+export function pickBillboardSprite(scene: SceneContext, x: number, y: number): Promise<BillboardPickInfo | null>;
+
 // @public
 export interface PickDiscardRule {
     readonly key: string;
@@ -3660,6 +3671,9 @@ export interface PickOptions {
     filter?: (mesh: Mesh) => boolean;
 }
 
+// @public
+export function pickSprite2D(layers: ReadonlyArray<Sprite2DLayer>, xPx: number, yPx: number): SpritePickInfo | null;
+
 // @public
 export interface PixelsTexture2DOptions {
     addressModeU?: GPUAddressMode;
@@ -5127,7 +5141,6 @@ export interface Sprite2DProps {
     flipY?: boolean;
     // (undocumented)
     frame?: number;
-    pickable?: boolean;
     // (undocumented)
     positionPx: [number, number];
     // (undocumented)
@@ -5269,6 +5282,15 @@ export interface SpriteFrameAnimation {
     to: number;
 }
 
+// @public
+export interface SpritePickInfo {
+    layer: Sprite2DLayer;
+    spriteIndex: number;
+    u: number;
+    // (undocumented)
+    v: number;
+}
+
 // @public
 export interface SpriteRenderer extends RenderingContext_2 {
     readonly layers: readonly Sprite2DLayer[];

@VicenteCartas VicenteCartas changed the title feat!(picking): sprite & billboard picking (pickSprite2D + pickBillboardSprite) feat(picking)!: sprite & billboard picking (pickSprite2D + pickBillboardSprite) Jun 30, 2026
@VicenteCartas VicenteCartas marked this pull request as ready for review June 30, 2026 23:34
@bjsplat

bjsplat commented Jun 30, 2026

Copy link
Copy Markdown

API Changes

API Extractor detected public API changes for @babylonjs/lite.

Potentially breaking changes detected. Removed or changed public API lines:

  • pickable?: boolean;
API Extractor diff
diff --git a/home/vsts/work/1/s/test-results/api-report/target/temp/babylon-lite.api.md b/home/vsts/work/1/s/test-results/api-report/current/temp/babylon-lite.api.md
index 873e78f3..21e45470 100644
--- a/home/vsts/work/1/s/test-results/api-report/target/temp/babylon-lite.api.md
+++ b/home/vsts/work/1/s/test-results/api-report/current/temp/babylon-lite.api.md
@@ -586,6 +586,14 @@ export type BillboardDepthMode = "transparent" | "cutout";
 // @public
 export type BillboardOrientation = "facing" | "axis-locked";
 
+// @public
+export interface BillboardPickInfo {
+    distance: number;
+    pickedPoint: [number, number, number] | null;
+    spriteIndex: number;
+    system: BillboardSpriteSystem;
+}
+
 // @public
 export interface BillboardSpriteHandle {
     // (undocumented)
@@ -3615,6 +3623,9 @@ export interface PhysicsWorld {}
 // @public
 export function pickAsync(picker: GpuPicker, x: number, y: number, options?: PickOptions): Promise<PickingInfo>;
 
+// @public
+export function pickBillboardSprite(scene: SceneContext, x: number, y: number): Promise<BillboardPickInfo | null>;
+
 // @public
 export interface PickDiscardRule {
     readonly key: string;
@@ -3660,6 +3671,9 @@ export interface PickOptions {
     filter?: (mesh: Mesh) => boolean;
 }
 
+// @public
+export function pickSprite2D(layers: ReadonlyArray<Sprite2DLayer>, xPx: number, yPx: number): SpritePickInfo | null;
+
 // @public
 export interface PixelsTexture2DOptions {
     addressModeU?: GPUAddressMode;
@@ -5127,7 +5141,6 @@ export interface Sprite2DProps {
     flipY?: boolean;
     // (undocumented)
     frame?: number;
-    pickable?: boolean;
     // (undocumented)
     positionPx: [number, number];
     // (undocumented)
@@ -5269,6 +5282,15 @@ export interface SpriteFrameAnimation {
     to: number;
 }
 
+// @public
+export interface SpritePickInfo {
+    layer: Sprite2DLayer;
+    spriteIndex: number;
+    u: number;
+    // (undocumented)
+    v: number;
+}
+
 // @public
 export interface SpriteRenderer extends RenderingContext_2 {
     readonly layers: readonly Sprite2DLayer[];

@bjsplat

bjsplat commented Jul 1, 2026

Copy link
Copy Markdown

Bundle Size Changes

Increases

Package Current Master Change
Scene 129 — Gaussian Splatting GPU Picking
scene129
84 KB 83 KB +1 KB
Scene 221 — Pointer Drags
scene221
101 KB 100 KB +1 KB

Sizes rounded to nearest KB. Run pnpm build:bundle-scenes locally to verify.

@bjsplat

bjsplat commented Jul 1, 2026

Copy link
Copy Markdown

Lab - Static Site

Open deployed site

Build 20260630.7 - merge @ fc28ed4

@sebavan sebavan left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

0.3k in quite a few scenes sounds like a meaningfull change ?

@ryantrem

ryantrem commented Jul 1, 2026

Copy link
Copy Markdown
Member

0.3k in quite a few scenes sounds like a meaningfull change ?

I was thinking the same. Generally speaking, any time we have entity type specific code (e.g. code to deal with billboards) inside code that needs to deal with many entity types (scene-core.ts or gpu-picker.ts), then that code will grow in size with the number of entities. I think usually we want to avoid this, and we often do through light weight abstractions like RenderContext. Can we do something similar here? Like don't force new billboard specific code into gpu-picker for example, but instead have some kind of light weight abstraction, and only when you are actually using billboards would a handler for that entity type be added?

@VicenteCartas VicenteCartas marked this pull request as draft July 1, 2026 20:59
@VicenteCartas

Copy link
Copy Markdown
Contributor Author

Chatted with Ryan, moving to draft until we can figure out a better way to handle the multiple different entities that may require picking but may not be in the scene in a better way than adding ifs.

Vicente Cartas Espinel added 4 commits July 1, 2026 18:01
noUncheckedIndexedAccess flags a[i]/b[i] as possibly undefined; the tuple elements are always defined for i in [0,3). Add non-null assertions so the lint gate (tsc) passes.
…ypes

gpu-picker.ts and scene-core.ts no longer contain billboard- or GS-specific code. Optional pickable entities (billboard systems, GS meshes) register a PickContributor (draw + resolve) when added to the scene; the picker draws meshes itself, then iterates scene._pickContributors with no entity-type knowledge. A single _pickContributors array replaces the _billboardSystems and _gsMeshes registries; per-picker GPU pick state is cached on picker._contributorState (GUIDANCE forbids module-level maps) and disposed generically. Heavy pick pipelines stay behind lazy imports registered by the lightweight scene-wiring modules, so pay-for-use is preserved and the shared picker shrank for every picker scene (mesh -0.5 to -0.6 KB, billboard -0.5 KB; 2D/GS neutral). Behaviour-preserving: all picking parity tests unchanged (MAD identical). Ceilings 115/118 re-tightened to the new sizes.
@bjsplat

bjsplat commented Jul 3, 2026

Copy link
Copy Markdown

API Changes

API Extractor detected public API changes for @babylonjs/lite.

Potentially breaking changes detected. Removed or changed public API lines:

  • pickable?: boolean;
API Extractor diff
diff --git a/home/vsts/work/1/s/test-results/api-report/target/temp/babylon-lite.api.md b/home/vsts/work/1/s/test-results/api-report/current/temp/babylon-lite.api.md
index b97f112d..0a05cd23 100644
--- a/home/vsts/work/1/s/test-results/api-report/target/temp/babylon-lite.api.md
+++ b/home/vsts/work/1/s/test-results/api-report/current/temp/babylon-lite.api.md
@@ -589,6 +589,14 @@ export type BillboardDepthMode = "transparent" | "cutout";
 // @public
 export type BillboardOrientation = "facing" | "axis-locked";
 
+// @public
+export interface BillboardPickInfo {
+    distance: number;
+    pickedPoint: [number, number, number] | null;
+    spriteIndex: number;
+    system: BillboardSpriteSystem;
+}
+
 // @public
 export interface BillboardSpriteHandle {
     // (undocumented)
@@ -3649,6 +3657,9 @@ export interface PhysicsWorld {}
 // @public
 export function pickAsync(picker: GpuPicker, x: number, y: number, options?: PickOptions): Promise<PickingInfo>;
 
+// @public
+export function pickBillboardSprite(scene: SceneContext, x: number, y: number): Promise<BillboardPickInfo | null>;
+
 // @public
 export interface PickDiscardRule {
     readonly key: string;
@@ -3694,6 +3705,9 @@ export interface PickOptions {
     filter?: (mesh: Mesh) => boolean;
 }
 
+// @public
+export function pickSprite2D(layers: ReadonlyArray<Sprite2DLayer>, xPx: number, yPx: number): SpritePickInfo | null;
+
 // @public
 export interface PixelsTexture2DOptions {
     addressModeU?: GPUAddressMode;
@@ -5166,7 +5180,6 @@ export interface Sprite2DProps {
     flipY?: boolean;
     // (undocumented)
     frame?: number;
-    pickable?: boolean;
     // (undocumented)
     positionPx: [number, number];
     // (undocumented)
@@ -5308,6 +5321,15 @@ export interface SpriteFrameAnimation {
     to: number;
 }
 
+// @public
+export interface SpritePickInfo {
+    layer: Sprite2DLayer;
+    spriteIndex: number;
+    u: number;
+    // (undocumented)
+    v: number;
+}
+
 // @public
 export interface SpriteRenderer extends RenderingContext_2 {
     readonly layers: readonly Sprite2DLayer[];

@bjsplat

bjsplat commented Jul 3, 2026

Copy link
Copy Markdown

Bundle Size Changes

Increases

Package Current Master Change
Scene 222 — Composite Gizmos
scene222
114 KB 112 KB +2 KB
Scene 54 — Facing Billboards
scene54
61 KB 60 KB +1 KB
Scene 55 — Billboard Sorting
scene55
32 KB 31 KB +1 KB
Scene 56 — Axis-Locked Billboards
scene56
61 KB 60 KB +1 KB
Scene 57 — Cutout Billboards
scene57
61 KB 60 KB +1 KB
Scene 59 — Billboard Sprite Animation
scene59
64 KB 63 KB +1 KB
Scene 94 — Billboard Custom Shader (params tint)
scene94
65 KB 64 KB +1 KB
Scene 95 — Billboard Custom Shader (palette remap)
scene95
66 KB 65 KB +1 KB
Scene 98 — Billboard Additive Blend
scene98
61 KB 60 KB +1 KB
Scene 122 — Gaussian Splatting SOG
scene122
49 KB 48 KB +1 KB
Scene 125 — Gaussian Splatting bakeCurrentTransformIntoVertices
scene125
38 KB 37 KB +1 KB
Scene 127 — Gaussian Splatting Depth Rendering
scene127
59 KB 58 KB +1 KB
Scene 128 — Gaussian Splatting Depth Rendering (Alpha-blended)
scene128
59 KB 58 KB +1 KB
Scene 129 — Gaussian Splatting GPU Picking
scene129
84 KB 83 KB +1 KB
Scene 205 - LWR Facing Billboards
scene205
63 KB 62 KB +1 KB
Scene 206 - LWR Cutout Billboards
scene206
63 KB 62 KB +1 KB

Sizes rounded to nearest KB. Run pnpm build:bundle-scenes locally to verify.

@bjsplat

bjsplat commented Jul 3, 2026

Copy link
Copy Markdown

API Changes

API Extractor detected public API changes for @babylonjs/lite.

Potentially breaking changes detected. Removed or changed public API lines:

  • pickable?: boolean;
API Extractor diff
diff --git a/home/vsts/work/1/s/test-results/api-report/target/temp/babylon-lite.api.md b/home/vsts/work/1/s/test-results/api-report/current/temp/babylon-lite.api.md
index e50c3542..ee876d48 100644
--- a/home/vsts/work/1/s/test-results/api-report/target/temp/babylon-lite.api.md
+++ b/home/vsts/work/1/s/test-results/api-report/current/temp/babylon-lite.api.md
@@ -589,6 +589,14 @@ export type BillboardDepthMode = "transparent" | "cutout";
 // @public
 export type BillboardOrientation = "facing" | "axis-locked";
 
+// @public
+export interface BillboardPickInfo {
+    distance: number;
+    pickedPoint: [number, number, number] | null;
+    spriteIndex: number;
+    system: BillboardSpriteSystem;
+}
+
 // @public
 export interface BillboardSpriteHandle {
     // (undocumented)
@@ -3658,6 +3666,9 @@ export interface PhysicsWorld {}
 // @public
 export function pickAsync(picker: GpuPicker, x: number, y: number, options?: PickOptions): Promise<PickingInfo>;
 
+// @public
+export function pickBillboardSprite(scene: SceneContext, x: number, y: number): Promise<BillboardPickInfo | null>;
+
 // @public
 export interface PickDiscardRule {
     readonly key: string;
@@ -3703,6 +3714,9 @@ export interface PickOptions {
     filter?: (mesh: Mesh) => boolean;
 }
 
+// @public
+export function pickSprite2D(layers: ReadonlyArray<Sprite2DLayer>, xPx: number, yPx: number): SpritePickInfo | null;
+
 // @public
 export interface PixelsTexture2DOptions {
     addressModeU?: GPUAddressMode;
@@ -5185,7 +5199,6 @@ export interface Sprite2DProps {
     flipY?: boolean;
     // (undocumented)
     frame?: number;
-    pickable?: boolean;
     // (undocumented)
     positionPx: [number, number];
     // (undocumented)
@@ -5327,6 +5340,15 @@ export interface SpriteFrameAnimation {
     to: number;
 }
 
+// @public
+export interface SpritePickInfo {
+    layer: Sprite2DLayer;
+    spriteIndex: number;
+    u: number;
+    // (undocumented)
+    v: number;
+}
+
 // @public
 export interface SpriteRenderer extends RenderingContext_2 {
     readonly layers: readonly Sprite2DLayer[];

@bjsplat

bjsplat commented Jul 3, 2026

Copy link
Copy Markdown

Bundle Size Changes

Increases

Package Current Master Change
Scene 222 — Composite Gizmos
scene222
114 KB 112 KB +2 KB
Scene 54 — Facing Billboards
scene54
61 KB 60 KB +1 KB
Scene 55 — Billboard Sorting
scene55
32 KB 31 KB +1 KB
Scene 56 — Axis-Locked Billboards
scene56
61 KB 60 KB +1 KB
Scene 57 — Cutout Billboards
scene57
61 KB 60 KB +1 KB
Scene 59 — Billboard Sprite Animation
scene59
64 KB 63 KB +1 KB
Scene 94 — Billboard Custom Shader (params tint)
scene94
65 KB 64 KB +1 KB
Scene 95 — Billboard Custom Shader (palette remap)
scene95
66 KB 65 KB +1 KB
Scene 98 — Billboard Additive Blend
scene98
61 KB 60 KB +1 KB
Scene 122 — Gaussian Splatting SOG
scene122
49 KB 48 KB +1 KB
Scene 125 — Gaussian Splatting bakeCurrentTransformIntoVertices
scene125
38 KB 37 KB +1 KB
Scene 127 — Gaussian Splatting Depth Rendering
scene127
59 KB 58 KB +1 KB
Scene 128 — Gaussian Splatting Depth Rendering (Alpha-blended)
scene128
59 KB 58 KB +1 KB
Scene 129 — Gaussian Splatting GPU Picking
scene129
84 KB 83 KB +1 KB
Scene 205 - LWR Facing Billboards
scene205
63 KB 62 KB +1 KB
Scene 206 - LWR Cutout Billboards
scene206
63 KB 62 KB +1 KB

Sizes rounded to nearest KB. Run pnpm build:bundle-scenes locally to verify.

@VicenteCartas VicenteCartas marked this pull request as ready for review July 3, 2026 21:44
@bjsplat

bjsplat commented Jul 3, 2026

Copy link
Copy Markdown

API Changes

API Extractor detected public API changes for @babylonjs/lite.

Potentially breaking changes detected. Removed or changed public API lines:

  • pickable?: boolean;
API Extractor diff
diff --git a/home/vsts/work/1/s/test-results/api-report/target/temp/babylon-lite.api.md b/home/vsts/work/1/s/test-results/api-report/current/temp/babylon-lite.api.md
index e50c3542..ee876d48 100644
--- a/home/vsts/work/1/s/test-results/api-report/target/temp/babylon-lite.api.md
+++ b/home/vsts/work/1/s/test-results/api-report/current/temp/babylon-lite.api.md
@@ -589,6 +589,14 @@ export type BillboardDepthMode = "transparent" | "cutout";
 // @public
 export type BillboardOrientation = "facing" | "axis-locked";
 
+// @public
+export interface BillboardPickInfo {
+    distance: number;
+    pickedPoint: [number, number, number] | null;
+    spriteIndex: number;
+    system: BillboardSpriteSystem;
+}
+
 // @public
 export interface BillboardSpriteHandle {
     // (undocumented)
@@ -3658,6 +3666,9 @@ export interface PhysicsWorld {}
 // @public
 export function pickAsync(picker: GpuPicker, x: number, y: number, options?: PickOptions): Promise<PickingInfo>;
 
+// @public
+export function pickBillboardSprite(scene: SceneContext, x: number, y: number): Promise<BillboardPickInfo | null>;
+
 // @public
 export interface PickDiscardRule {
     readonly key: string;
@@ -3703,6 +3714,9 @@ export interface PickOptions {
     filter?: (mesh: Mesh) => boolean;
 }
 
+// @public
+export function pickSprite2D(layers: ReadonlyArray<Sprite2DLayer>, xPx: number, yPx: number): SpritePickInfo | null;
+
 // @public
 export interface PixelsTexture2DOptions {
     addressModeU?: GPUAddressMode;
@@ -5185,7 +5199,6 @@ export interface Sprite2DProps {
     flipY?: boolean;
     // (undocumented)
     frame?: number;
-    pickable?: boolean;
     // (undocumented)
     positionPx: [number, number];
     // (undocumented)
@@ -5327,6 +5340,15 @@ export interface SpriteFrameAnimation {
     to: number;
 }
 
+// @public
+export interface SpritePickInfo {
+    layer: Sprite2DLayer;
+    spriteIndex: number;
+    u: number;
+    // (undocumented)
+    v: number;
+}
+
 // @public
 export interface SpriteRenderer extends RenderingContext_2 {
     readonly layers: readonly Sprite2DLayer[];

@bjsplat

bjsplat commented Jul 3, 2026

Copy link
Copy Markdown

Bundle Size Changes

Increases

Package Current Master Change
Scene 222 — Composite Gizmos
scene222
114 KB 112 KB +2 KB
Scene 54 — Facing Billboards
scene54
61 KB 60 KB +1 KB
Scene 55 — Billboard Sorting
scene55
32 KB 31 KB +1 KB
Scene 56 — Axis-Locked Billboards
scene56
61 KB 60 KB +1 KB
Scene 57 — Cutout Billboards
scene57
61 KB 60 KB +1 KB
Scene 59 — Billboard Sprite Animation
scene59
64 KB 63 KB +1 KB
Scene 94 — Billboard Custom Shader (params tint)
scene94
65 KB 64 KB +1 KB
Scene 95 — Billboard Custom Shader (palette remap)
scene95
66 KB 65 KB +1 KB
Scene 98 — Billboard Additive Blend
scene98
61 KB 60 KB +1 KB
Scene 122 — Gaussian Splatting SOG
scene122
49 KB 48 KB +1 KB
Scene 125 — Gaussian Splatting bakeCurrentTransformIntoVertices
scene125
38 KB 37 KB +1 KB
Scene 127 — Gaussian Splatting Depth Rendering
scene127
59 KB 58 KB +1 KB
Scene 128 — Gaussian Splatting Depth Rendering (Alpha-blended)
scene128
59 KB 58 KB +1 KB
Scene 129 — Gaussian Splatting GPU Picking
scene129
84 KB 83 KB +1 KB
Scene 205 - LWR Facing Billboards
scene205
63 KB 62 KB +1 KB
Scene 206 - LWR Cutout Billboards
scene206
63 KB 62 KB +1 KB

Sizes rounded to nearest KB. Run pnpm build:bundle-scenes locally to verify.

…ick code

Register a lazy factory thunk at entity-add time instead of an eager contributor. The thunk reaches the pick pipeline only through a dynamic import, so billboard and GS rendering pull zero pick-pipeline bytes; the picker builds and caches each contributor on the first pick. Restores pay-for-use: billboard/GS render scenes drop back to master sizes (reverts the 4 GS ceiling bumps) while gpu-picker stays entity-type-agnostic. draw() is now sync and the factory async, with an optional dispose(); gs-pick-contributor.ts folded into gs-picking-pipeline. Regenerated 26 manifests. Addresses ryantrem and sebavan review feedback.
@bjsplat

bjsplat commented Jul 3, 2026

Copy link
Copy Markdown

API Changes

API Extractor detected public API changes for @babylonjs/lite.

Potentially breaking changes detected. Removed or changed public API lines:

  • pickable?: boolean;
API Extractor diff
diff --git a/home/vsts/work/1/s/test-results/api-report/target/temp/babylon-lite.api.md b/home/vsts/work/1/s/test-results/api-report/current/temp/babylon-lite.api.md
index 1042a4ff..b7919f2d 100644
--- a/home/vsts/work/1/s/test-results/api-report/target/temp/babylon-lite.api.md
+++ b/home/vsts/work/1/s/test-results/api-report/current/temp/babylon-lite.api.md
@@ -589,6 +589,14 @@ export type BillboardDepthMode = "transparent" | "cutout";
 // @public
 export type BillboardOrientation = "facing" | "axis-locked";
 
+// @public
+export interface BillboardPickInfo {
+    distance: number;
+    pickedPoint: [number, number, number] | null;
+    spriteIndex: number;
+    system: BillboardSpriteSystem;
+}
+
 // @public
 export interface BillboardSpriteHandle {
     // (undocumented)
@@ -3666,6 +3674,9 @@ export interface PhysicsWorld {}
 // @public
 export function pickAsync(picker: GpuPicker, x: number, y: number, options?: PickOptions): Promise<PickingInfo>;
 
+// @public
+export function pickBillboardSprite(scene: SceneContext, x: number, y: number): Promise<BillboardPickInfo | null>;
+
 // @public
 export interface PickDiscardRule {
     readonly key: string;
@@ -3711,6 +3722,9 @@ export interface PickOptions {
     filter?: (mesh: Mesh) => boolean;
 }
 
+// @public
+export function pickSprite2D(layers: ReadonlyArray<Sprite2DLayer>, xPx: number, yPx: number): SpritePickInfo | null;
+
 // @public
 export interface PixelsTexture2DOptions {
     addressModeU?: GPUAddressMode;
@@ -5196,7 +5210,6 @@ export interface Sprite2DProps {
     flipY?: boolean;
     // (undocumented)
     frame?: number;
-    pickable?: boolean;
     // (undocumented)
     positionPx: [number, number];
     // (undocumented)
@@ -5338,6 +5351,15 @@ export interface SpriteFrameAnimation {
     to: number;
 }
 
+// @public
+export interface SpritePickInfo {
+    layer: Sprite2DLayer;
+    spriteIndex: number;
+    u: number;
+    // (undocumented)
+    v: number;
+}
+
 // @public
 export interface SpriteRenderer extends RenderingContext_2 {
     readonly layers: readonly Sprite2DLayer[];

@bjsplat

bjsplat commented Jul 3, 2026

Copy link
Copy Markdown

API Changes

API Extractor detected public API changes for @babylonjs/lite.

Potentially breaking changes detected. Removed or changed public API lines:

  • pickable?: boolean;
API Extractor diff
diff --git a/home/vsts/work/1/s/test-results/api-report/target/temp/babylon-lite.api.md b/home/vsts/work/1/s/test-results/api-report/current/temp/babylon-lite.api.md
index 1042a4ff..b7919f2d 100644
--- a/home/vsts/work/1/s/test-results/api-report/target/temp/babylon-lite.api.md
+++ b/home/vsts/work/1/s/test-results/api-report/current/temp/babylon-lite.api.md
@@ -589,6 +589,14 @@ export type BillboardDepthMode = "transparent" | "cutout";
 // @public
 export type BillboardOrientation = "facing" | "axis-locked";
 
+// @public
+export interface BillboardPickInfo {
+    distance: number;
+    pickedPoint: [number, number, number] | null;
+    spriteIndex: number;
+    system: BillboardSpriteSystem;
+}
+
 // @public
 export interface BillboardSpriteHandle {
     // (undocumented)
@@ -3666,6 +3674,9 @@ export interface PhysicsWorld {}
 // @public
 export function pickAsync(picker: GpuPicker, x: number, y: number, options?: PickOptions): Promise<PickingInfo>;
 
+// @public
+export function pickBillboardSprite(scene: SceneContext, x: number, y: number): Promise<BillboardPickInfo | null>;
+
 // @public
 export interface PickDiscardRule {
     readonly key: string;
@@ -3711,6 +3722,9 @@ export interface PickOptions {
     filter?: (mesh: Mesh) => boolean;
 }
 
+// @public
+export function pickSprite2D(layers: ReadonlyArray<Sprite2DLayer>, xPx: number, yPx: number): SpritePickInfo | null;
+
 // @public
 export interface PixelsTexture2DOptions {
     addressModeU?: GPUAddressMode;
@@ -5196,7 +5210,6 @@ export interface Sprite2DProps {
     flipY?: boolean;
     // (undocumented)
     frame?: number;
-    pickable?: boolean;
     // (undocumented)
     positionPx: [number, number];
     // (undocumented)
@@ -5338,6 +5351,15 @@ export interface SpriteFrameAnimation {
     to: number;
 }
 
+// @public
+export interface SpritePickInfo {
+    layer: Sprite2DLayer;
+    spriteIndex: number;
+    u: number;
+    // (undocumented)
+    v: number;
+}
+
 // @public
 export interface SpriteRenderer extends RenderingContext_2 {
     readonly layers: readonly Sprite2DLayer[];

@bjsplat

bjsplat commented Jul 3, 2026

Copy link
Copy Markdown

Bundle Size Changes

Increases

Package Current Master Change
Scene 222 — Composite Gizmos
scene222
114 KB 112 KB +2 KB
Scene 55 — Billboard Sorting
scene55
32 KB 31 KB +1 KB
Scene 56 — Axis-Locked Billboards
scene56
61 KB 60 KB +1 KB
Scene 98 — Billboard Additive Blend
scene98
61 KB 60 KB +1 KB
Scene 125 — Gaussian Splatting bakeCurrentTransformIntoVertices
scene125
38 KB 37 KB +1 KB
Scene 127 — Gaussian Splatting Depth Rendering
scene127
59 KB 58 KB +1 KB
Scene 128 — Gaussian Splatting Depth Rendering (Alpha-blended)
scene128
59 KB 58 KB +1 KB
Scene 129 — Gaussian Splatting GPU Picking
scene129
84 KB 83 KB +1 KB
Scene 206 - LWR Cutout Billboards
scene206
63 KB 62 KB +1 KB

Sizes rounded to nearest KB. Run pnpm build:bundle-scenes locally to verify.

@VicenteCartas VicenteCartas marked this pull request as draft July 4, 2026 19:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants