@babylonjs/lite 1.9.0 → 1.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (128) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/dist/index.js +596 -526
  3. package/dist/index.js.map +1 -1
  4. package/index.d.ts +107 -4
  5. package/lib/engine/engine.js +1 -1
  6. package/lib/engine/engine.js.map +1 -1
  7. package/lib/frame-graph/render-task.js +6 -4
  8. package/lib/frame-graph/render-task.js.map +1 -1
  9. package/lib/index.js +4 -1
  10. package/lib/index.js.map +1 -1
  11. package/lib/loader-gltf/gltf-ext-quantization.js +4 -1
  12. package/lib/loader-gltf/gltf-ext-quantization.js.map +1 -1
  13. package/lib/loader-gltf/gltf-feature-registry.js +11 -1
  14. package/lib/loader-gltf/gltf-feature-registry.js.map +1 -1
  15. package/lib/loader-gltf/gltf-feature-variants.js +11 -1
  16. package/lib/loader-gltf/gltf-feature-variants.js.map +1 -1
  17. package/lib/loader-gltf/gltf-json-asset.js +8 -1
  18. package/lib/loader-gltf/gltf-json-asset.js.map +1 -1
  19. package/lib/loader-gltf/gltf-material.js +4 -29
  20. package/lib/loader-gltf/gltf-material.js.map +1 -1
  21. package/lib/loader-gltf/gltf-parser.js +1 -8
  22. package/lib/loader-gltf/gltf-parser.js.map +1 -1
  23. package/lib/loader-gltf/gltf-pbr-builder.js +1 -15
  24. package/lib/loader-gltf/gltf-pbr-builder.js.map +1 -1
  25. package/lib/loader-gltf/gltf-variants.js +5 -10
  26. package/lib/loader-gltf/gltf-variants.js.map +1 -1
  27. package/lib/loader-gltf/load-gltf.js +19 -29
  28. package/lib/loader-gltf/load-gltf.js.map +1 -1
  29. package/lib/material/pbr/background-dds-skybox.js +35 -93
  30. package/lib/material/pbr/background-dds-skybox.js.map +1 -1
  31. package/lib/material/pbr/background-ground.js +13 -18
  32. package/lib/material/pbr/background-ground.js.map +1 -1
  33. package/lib/material/pbr/background-hdr-skybox.js +35 -93
  34. package/lib/material/pbr/background-hdr-skybox.js.map +1 -1
  35. package/lib/material/pbr/background-solid-skybox.js +35 -93
  36. package/lib/material/pbr/background-solid-skybox.js.map +1 -1
  37. package/lib/material/pbr/fragments/clearcoat-fragment.js +5 -1
  38. package/lib/material/pbr/fragments/clearcoat-fragment.js.map +1 -1
  39. package/lib/material/pbr/fragments/iridescence-fragment.js +4 -1
  40. package/lib/material/pbr/fragments/iridescence-fragment.js.map +1 -1
  41. package/lib/material/pbr/fragments/refraction-rtt-fragment.js +7 -2
  42. package/lib/material/pbr/fragments/refraction-rtt-fragment.js.map +1 -1
  43. package/lib/material/pbr/fragments/sheen-fragment.js +2 -1
  44. package/lib/material/pbr/fragments/sheen-fragment.js.map +1 -1
  45. package/lib/material/pbr/fragments/subsurface-fragment.js +2 -1
  46. package/lib/material/pbr/fragments/subsurface-fragment.js.map +1 -1
  47. package/lib/material/pbr/fragments/unlit-fragment.js +1 -2
  48. package/lib/material/pbr/fragments/unlit-fragment.js.map +1 -1
  49. package/lib/material/pbr/pbr-compose.js +2 -0
  50. package/lib/material/pbr/pbr-compose.js.map +1 -1
  51. package/lib/material/pbr/pbr-flag-bits.js +1 -16
  52. package/lib/material/pbr/pbr-flag-bits.js.map +1 -1
  53. package/lib/material/pbr/pbr-flags.js +1 -1
  54. package/lib/material/pbr/pbr-fog-wgsl.js +1 -1
  55. package/lib/material/pbr/pbr-fog-wgsl.js.map +1 -1
  56. package/lib/material/pbr/pbr-geometry-output-shader.js +2 -1
  57. package/lib/material/pbr/pbr-geometry-output-shader.js.map +1 -1
  58. package/lib/material/pbr/pbr-geometry-view.js +2 -1
  59. package/lib/material/pbr/pbr-geometry-view.js.map +1 -1
  60. package/lib/material/pbr/pbr-renderable.js +4 -0
  61. package/lib/material/pbr/pbr-renderable.js.map +1 -1
  62. package/lib/material/pbr/pbr-template-gamma.js +7 -0
  63. package/lib/material/pbr/pbr-template-gamma.js.map +1 -0
  64. package/lib/material/pbr/pbr-template.js +2 -2
  65. package/lib/material/pbr/pbr-template.js.map +1 -1
  66. package/lib/material/standard/skybox-cubemap.js +1 -1
  67. package/lib/material/standard/skybox-cubemap.js.map +1 -1
  68. package/lib/material/standard/standard-template.js +1 -1
  69. package/lib/material/standard/standard-template.js.map +1 -1
  70. package/lib/mesh/GaussianSplatting/gaussian-splatting-pipeline-sh.js +3 -5
  71. package/lib/mesh/GaussianSplatting/gaussian-splatting-pipeline-sh.js.map +1 -1
  72. package/lib/mesh/GaussianSplatting/gaussian-splatting-pipeline.js +3 -5
  73. package/lib/mesh/GaussianSplatting/gaussian-splatting-pipeline.js.map +1 -1
  74. package/lib/particle/node/blocks/particle-condition-block.js +55 -0
  75. package/lib/particle/node/blocks/particle-condition-block.js.map +1 -0
  76. package/lib/particle/node/blocks/particle-float-to-int-block.js +28 -0
  77. package/lib/particle/node/blocks/particle-float-to-int-block.js.map +1 -0
  78. package/lib/particle/node/blocks/particle-gradient-block.js +32 -0
  79. package/lib/particle/node/blocks/particle-gradient-block.js.map +1 -0
  80. package/lib/particle/node/blocks/particle-gradient-value-block.js +11 -0
  81. package/lib/particle/node/blocks/particle-gradient-value-block.js.map +1 -0
  82. package/lib/particle/node/blocks/particle-lerp-block.js +2 -24
  83. package/lib/particle/node/blocks/particle-lerp-block.js.map +1 -1
  84. package/lib/particle/node/blocks/particle-lerp.js +27 -0
  85. package/lib/particle/node/blocks/particle-lerp.js.map +1 -0
  86. package/lib/particle/node/blocks/particle-vector-length-block.js +30 -0
  87. package/lib/particle/node/blocks/particle-vector-length-block.js.map +1 -0
  88. package/lib/particle/node/blocks/system-block.js +13 -3
  89. package/lib/particle/node/blocks/system-block.js.map +1 -1
  90. package/lib/particle/node/blocks/texture-source-block.js +2 -1
  91. package/lib/particle/node/blocks/texture-source-block.js.map +1 -1
  92. package/lib/particle/node/blocks/update-size-block.js +19 -0
  93. package/lib/particle/node/blocks/update-size-block.js.map +1 -0
  94. package/lib/particle/node/npe-build-state.js +10 -0
  95. package/lib/particle/node/npe-build-state.js.map +1 -1
  96. package/lib/particle/node/npe-registry.js +12 -0
  97. package/lib/particle/node/npe-registry.js.map +1 -1
  98. package/lib/particle/particle-system.js +7 -2
  99. package/lib/particle/particle-system.js.map +1 -1
  100. package/lib/picking/billboard-pick-pipeline.js +269 -0
  101. package/lib/picking/billboard-pick-pipeline.js.map +1 -0
  102. package/lib/picking/gpu-picker.js +37 -37
  103. package/lib/picking/gpu-picker.js.map +1 -1
  104. package/lib/picking/gs-picking-pipeline.js +26 -1
  105. package/lib/picking/gs-picking-pipeline.js.map +1 -1
  106. package/lib/picking/pick-contributor.js +13 -0
  107. package/lib/picking/pick-contributor.js.map +1 -0
  108. package/lib/picking/picking-info.js.map +1 -1
  109. package/lib/scene/scene-core.js +2 -2
  110. package/lib/scene/scene-core.js.map +1 -1
  111. package/lib/shader/wgsl-fog.js +18 -0
  112. package/lib/shader/wgsl-fog.js.map +1 -0
  113. package/lib/shader/wgsl-helpers.js +1 -16
  114. package/lib/shader/wgsl-helpers.js.map +1 -1
  115. package/lib/sprite/billboard-scene.js +2 -0
  116. package/lib/sprite/billboard-scene.js.map +1 -1
  117. package/lib/sprite/picking/pick-billboard.js +16 -0
  118. package/lib/sprite/picking/pick-billboard.js.map +1 -0
  119. package/lib/sprite/picking/pick-sprite-2d.js +41 -0
  120. package/lib/sprite/picking/pick-sprite-2d.js.map +1 -0
  121. package/lib/sprite/shared/sprite-atlas.js +4 -1
  122. package/lib/sprite/shared/sprite-atlas.js.map +1 -1
  123. package/lib/sprite/sprite-2d.js.map +1 -1
  124. package/lib/sprite/sprite-pipeline.js +18 -1
  125. package/lib/sprite/sprite-pipeline.js.map +1 -1
  126. package/lib/sprite/sprite-renderer.js +12 -6
  127. package/lib/sprite/sprite-renderer.js.map +1 -1
  128. package/package.json +3 -3
@@ -0,0 +1,269 @@
1
+ import { U32, F32 } from '../engine/typed-arrays.js';
2
+ import { BU, SS, CW } from '../engine/gpu-flags.js';
3
+ import { BILLBOARD_INSTANCE_STRIDE_BYTES } from '../sprite/billboard-sprite.js';
4
+ import { getPickingSceneBGL } from './picking-pipeline.js';
5
+ import { getViewMatrix } from '../camera/camera.js';
6
+
7
+ const BILLBOARD_PICK_UBO_BYTES = 48;
8
+ const POSITION_OFFSET_BYTES = 0;
9
+ const SIZE_OFFSET_BYTES = 12;
10
+ const UV_MIN_OFFSET_BYTES = 20;
11
+ const UV_MAX_OFFSET_BYTES = 28;
12
+ const ROTATION_OFFSET_BYTES = 36;
13
+ const PIVOT_OFFSET_BYTES = 40;
14
+ const SHARED_QUAD_INDICES = new Uint16Array([0, 1, 2, 0, 2, 3]);
15
+ function packBillboardPickUbo(view, baseId, cutoff, axis, f32, u32) {
16
+ f32[0] = view[0];
17
+ f32[1] = view[4];
18
+ f32[2] = view[8];
19
+ u32[3] = baseId;
20
+ f32[4] = view[1];
21
+ f32[5] = view[5];
22
+ f32[6] = view[9];
23
+ f32[7] = cutoff;
24
+ f32[8] = axis[0];
25
+ f32[9] = axis[1];
26
+ f32[10] = axis[2];
27
+ }
28
+ function makePickBasisWgsl(orientation) {
29
+ switch (orientation) {
30
+ case "facing":
31
+ return `fn basis() -> B {
32
+ let r = normalize(bb.camRight);
33
+ let u = normalize(bb.camUp);
34
+ return B(r, -u);
35
+ }`;
36
+ case "axis-locked":
37
+ return `fn basis() -> B {
38
+ let a = normalize(bb.axis);
39
+ let cr = normalize(bb.camRight);
40
+ let pr = cr - a * dot(cr, a);
41
+ let pl = length(pr);
42
+ let f = select(vec3f(0, 0, 1), vec3f(1, 0, 0), abs(a.z) > 0.999);
43
+ let fr = cross(a, f);
44
+ let r = select(fr / max(length(fr), 1e-4), pr / max(pl, 1e-4), pl > 1e-4);
45
+ return B(r, -a);
46
+ }`;
47
+ }
48
+ }
49
+ function makeBillboardPickWgsl(orientation, isCutout) {
50
+ const cutoutBindings = isCutout ? `@group(1) @binding(1) var atlasTex: texture_2d<f32>;
51
+ @group(1) @binding(2) var atlasSamp: sampler;` : "";
52
+ const uvVarying = isCutout ? `,
53
+ @location(1) uv: vec2f` : "";
54
+ const uvAssign = isCutout ? `out.uv = mix(in.a, in.b, q);` : "";
55
+ const cutoutDiscard = isCutout ? `let s = textureSample(atlasTex, atlasSamp, in.uv);
56
+ if (s.a < bb.cutoff) {
57
+ discard;
58
+ }` : "";
59
+ return `struct PickScene { viewProjection: mat4x4f };
60
+ @group(0) @binding(0) var<uniform> scene: PickScene;
61
+ struct BB {
62
+ camRight: vec3f,
63
+ baseId: u32,
64
+ camUp: vec3f,
65
+ cutoff: f32,
66
+ axis: vec3f,
67
+ _pad: f32,
68
+ };
69
+ @group(1) @binding(0) var<uniform> bb: BB;
70
+ ${cutoutBindings}
71
+ struct B { r: vec3f, u: vec3f };
72
+ ${makePickBasisWgsl(orientation)}
73
+ struct I {
74
+ @builtin(vertex_index) vid: u32,
75
+ @builtin(instance_index) iid: u32,
76
+ @location(0) p: vec3f,
77
+ @location(1) s: vec2f,
78
+ @location(2) a: vec2f,
79
+ @location(3) b: vec2f,
80
+ @location(4) r: f32,
81
+ @location(5) o: vec2f,
82
+ };
83
+ struct O {
84
+ @builtin(position) p: vec4f,
85
+ @location(0) @interpolate(flat) pickId: u32${uvVarying}
86
+ };
87
+ @vertex
88
+ fn vs(in: I) -> O {
89
+ let q = vec2f(select(0.0, 1.0, in.vid == 1u || in.vid == 2u), select(0.0, 1.0, in.vid >= 2u));
90
+ let l = (q - in.o) * in.s;
91
+ let cr = cos(in.r);
92
+ let sr = sin(in.r);
93
+ let rot = vec2f(l.x * cr - l.y * sr, l.x * sr + l.y * cr);
94
+ let bs = basis();
95
+ let wp = in.p + bs.r * rot.x + bs.u * rot.y;
96
+ var out: O;
97
+ out.p = scene.viewProjection * vec4f(wp, 1);
98
+ out.pickId = bb.baseId + in.iid;
99
+ ${uvAssign}
100
+ return out;
101
+ }
102
+ struct FsOut { @location(0) color: vec4f, @location(1) depth: vec4f };
103
+ @fragment
104
+ fn fs(in: O) -> FsOut {
105
+ ${cutoutDiscard}
106
+ let id = in.pickId;
107
+ let r = f32((id >> 16u) & 0xFFu) / 255.0;
108
+ let g = f32((id >> 8u) & 0xFFu) / 255.0;
109
+ let b = f32(id & 0xFFu) / 255.0;
110
+ return FsOut(vec4f(r, g, b, 1.0), vec4f(in.p.z, 0.0, 0.0, 0.0));
111
+ }`;
112
+ }
113
+ let _cache = null;
114
+ function getCache(engine) {
115
+ if (_cache && _cache.device === engine._device) {
116
+ return _cache;
117
+ }
118
+ _cache = { device: engine._device, pipelines: /* @__PURE__ */ new Map(), bgls: /* @__PURE__ */ new Map() };
119
+ return _cache;
120
+ }
121
+ function getGroup1Bgl(engine, cache, isCutout) {
122
+ const key = isCutout ? "cutout" : "plain";
123
+ let bgl = cache.bgls.get(key);
124
+ if (!bgl) {
125
+ const entries = [{ binding: 0, visibility: SS.VERTEX | SS.FRAGMENT, buffer: { type: "uniform" } }];
126
+ if (isCutout) {
127
+ entries.push({ binding: 1, visibility: SS.FRAGMENT, texture: { sampleType: "float" } });
128
+ entries.push({ binding: 2, visibility: SS.FRAGMENT, sampler: { type: "filtering" } });
129
+ }
130
+ bgl = engine._device.createBindGroupLayout({ label: `billboard-pick-bgl-${key}`, entries });
131
+ cache.bgls.set(key, bgl);
132
+ }
133
+ return bgl;
134
+ }
135
+ function getPickPipeline(engine, system) {
136
+ const cache = getCache(engine);
137
+ const orientation = system._orientation;
138
+ const isCutout = system._depthMode === "cutout";
139
+ const key = `${orientation}|${isCutout ? 1 : 0}`;
140
+ const cached = cache.pipelines.get(key);
141
+ if (cached) {
142
+ return cached;
143
+ }
144
+ const device = engine._device;
145
+ const module = device.createShaderModule({ label: `billboard-pick-${key}`, code: makeBillboardPickWgsl(orientation, isCutout) });
146
+ const group1Bgl = getGroup1Bgl(engine, cache, isCutout);
147
+ const pipeline = device.createRenderPipeline({
148
+ label: `billboard-pick-pipeline-${key}`,
149
+ layout: device.createPipelineLayout({ bindGroupLayouts: [getPickingSceneBGL(engine), group1Bgl] }),
150
+ vertex: {
151
+ module,
152
+ entryPoint: "vs",
153
+ buffers: [
154
+ {
155
+ arrayStride: BILLBOARD_INSTANCE_STRIDE_BYTES,
156
+ stepMode: "instance",
157
+ attributes: [
158
+ { shaderLocation: 0, offset: POSITION_OFFSET_BYTES, format: "float32x3" },
159
+ { shaderLocation: 1, offset: SIZE_OFFSET_BYTES, format: "float32x2" },
160
+ { shaderLocation: 2, offset: UV_MIN_OFFSET_BYTES, format: "float32x2" },
161
+ { shaderLocation: 3, offset: UV_MAX_OFFSET_BYTES, format: "float32x2" },
162
+ { shaderLocation: 4, offset: ROTATION_OFFSET_BYTES, format: "float32" },
163
+ { shaderLocation: 5, offset: PIVOT_OFFSET_BYTES, format: "float32x2" }
164
+ ]
165
+ }
166
+ ]
167
+ },
168
+ fragment: { module, entryPoint: "fs", targets: [{ format: "rgba8unorm", writeMask: CW.ALL }, { format: "r32float" }] },
169
+ // Match the mesh picker: reverse-Z depth (clear 0, "greater") so a billboard occluded by a
170
+ // mesh — or by a nearer billboard — loses the pick. Write depth so billboards occlude too.
171
+ primitive: { topology: "triangle-list", cullMode: "none" },
172
+ depthStencil: { format: "depth24plus", depthCompare: "greater", depthWriteEnabled: true },
173
+ multisample: { count: 1 }
174
+ });
175
+ cache.pipelines.set(key, pipeline);
176
+ return pipeline;
177
+ }
178
+ function createBillboardPickResources(engine, system) {
179
+ const device = engine._device;
180
+ const cache = getCache(engine);
181
+ const isCutout = system._depthMode === "cutout";
182
+ const ubo = device.createBuffer({ label: "billboard-pick-ubo", size: BILLBOARD_PICK_UBO_BYTES, usage: BU.UNIFORM | BU.COPY_DST });
183
+ const uboScratch = new ArrayBuffer(BILLBOARD_PICK_UBO_BYTES);
184
+ const instanceCapacity = Math.max(1, system._capacity);
185
+ const instanceBuffer = device.createBuffer({
186
+ label: "billboard-pick-instances",
187
+ size: instanceCapacity * BILLBOARD_INSTANCE_STRIDE_BYTES,
188
+ usage: BU.VERTEX | BU.COPY_DST
189
+ });
190
+ const indexBuffer = device.createBuffer({ label: "billboard-pick-indices", size: SHARED_QUAD_INDICES.byteLength, usage: BU.INDEX | BU.COPY_DST });
191
+ device.queue.writeBuffer(indexBuffer, 0, SHARED_QUAD_INDICES);
192
+ const entries = [{ binding: 0, resource: { buffer: ubo } }];
193
+ if (isCutout) {
194
+ entries.push({ binding: 1, resource: system.atlas.texture.view });
195
+ entries.push({ binding: 2, resource: system.atlas.texture.sampler });
196
+ }
197
+ const bindGroup = device.createBindGroup({ label: "billboard-pick-bg", layout: getGroup1Bgl(engine, cache, isCutout), entries });
198
+ return {
199
+ ubo,
200
+ uboScratch,
201
+ uboF32: new F32(uboScratch),
202
+ uboU32: new U32(uboScratch),
203
+ instanceBuffer,
204
+ instanceCapacity,
205
+ indexBuffer,
206
+ bindGroup
207
+ };
208
+ }
209
+ function disposeBillboardPickResources(res) {
210
+ res.ubo.destroy();
211
+ res.instanceBuffer.destroy();
212
+ res.indexBuffer.destroy();
213
+ }
214
+ function drawBillboardForPicking(pass, engine, system, res, baseId, view) {
215
+ const device = engine._device;
216
+ const count = system.count;
217
+ if (count === 0) {
218
+ return;
219
+ }
220
+ if (system._capacity > res.instanceCapacity) {
221
+ res.instanceBuffer.destroy();
222
+ res.instanceCapacity = system._capacity;
223
+ res.instanceBuffer = device.createBuffer({
224
+ label: "billboard-pick-instances",
225
+ size: res.instanceCapacity * BILLBOARD_INSTANCE_STRIDE_BYTES,
226
+ usage: BU.VERTEX | BU.COPY_DST
227
+ });
228
+ }
229
+ const data = system._instanceData;
230
+ device.queue.writeBuffer(res.instanceBuffer, 0, data.buffer, data.byteOffset, count * BILLBOARD_INSTANCE_STRIDE_BYTES);
231
+ const cutoff = system._depthMode === "cutout" ? system.alphaCutoff : 0;
232
+ packBillboardPickUbo(view, baseId, cutoff, system._axis, res.uboF32, res.uboU32);
233
+ device.queue.writeBuffer(res.ubo, 0, res.uboScratch, 0, BILLBOARD_PICK_UBO_BYTES);
234
+ pass.setPipeline(getPickPipeline(engine, system));
235
+ pass.setBindGroup(1, res.bindGroup);
236
+ pass.setIndexBuffer(res.indexBuffer, "uint16");
237
+ pass.setVertexBuffer(0, res.instanceBuffer);
238
+ pass.drawIndexed(6, count);
239
+ }
240
+ function drawBillboardSystemForPicking(ctx, system, res, baseId) {
241
+ ctx.pass.setBindGroup(0, ctx.sceneBG);
242
+ drawBillboardForPicking(ctx.pass, ctx.engine, system, res, baseId, getViewMatrix(ctx.camera));
243
+ }
244
+ function createPickContributor(system) {
245
+ let res = null;
246
+ return {
247
+ draw(ctx, baseId) {
248
+ const count = system.count;
249
+ if (!system.visible || count === 0) {
250
+ return baseId + count;
251
+ }
252
+ res ??= createBillboardPickResources(ctx.engine, system);
253
+ drawBillboardSystemForPicking(ctx, system, res, baseId);
254
+ return baseId + count;
255
+ },
256
+ resolve(info, localId) {
257
+ info._spritePick = { system, spriteIndex: localId, pickedPoint: info.pickedPoint, distance: info.distance };
258
+ },
259
+ dispose() {
260
+ if (res) {
261
+ disposeBillboardPickResources(res);
262
+ res = null;
263
+ }
264
+ }
265
+ };
266
+ }
267
+
268
+ export { createBillboardPickResources, createPickContributor, disposeBillboardPickResources, drawBillboardForPicking, drawBillboardSystemForPicking, packBillboardPickUbo };
269
+ //# sourceMappingURL=billboard-pick-pipeline.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"billboard-pick-pipeline.js","sources":["../../../src/picking/billboard-pick-pipeline.ts"],"sourcesContent":["/** Billboard GPU-picking pipeline — dynamic-imported by `gpu-picker.ts` when a\n * scene contains a `BillboardSpriteSystem`.\n *\n * Billboards are world-space, camera-oriented quads drawn inside the scene's 3D\n * pass, so they must be picked in the SAME depth-sorted 1×1 pass the mesh picker\n * uses (a billboard occluded by a wall must not win the pick). This mirrors the\n * Gaussian-splatting picking integration (`gs-picking-pipeline.ts`): the picker\n * assigns each system a contiguous pick-id range after the meshes + GS meshes,\n * draws every system into the shared pass with the pick-zoomed view-projection,\n * and resolves the read-back id back to `{ system, spriteIndex }`.\n *\n * The vertex stage reproduces the render shader's quad math (`billboard-pipeline.ts`)\n * exactly — same corner derivation, pivot, rotation, and camera-basis orientation —\n * so the picked pixel matches what the renderer drew. The camera basis cannot be\n * read from the pick scene UBO (it carries only the pick-zoomed view-projection),\n * so the camera right/up world axes are extracted on the CPU and passed in the\n * per-system pick UBO. The fragment encodes the pick id as RGB and the NDC depth\n * as `r32float`, matching the mesh picker's two-attachment contract.\n */\nimport { F32, U32 } from \"../engine/typed-arrays.js\";\nimport { BU, SS, CW } from \"../engine/gpu-flags.js\";\nimport type { EngineContext } from \"../engine/engine.js\";\nimport type { Mat4 } from \"../math/types.js\";\nimport type { BillboardOrientation, BillboardSpriteSystem } from \"../sprite/billboard-sprite.js\";\nimport { BILLBOARD_INSTANCE_STRIDE_BYTES } from \"../sprite/billboard-sprite.js\";\nimport { getPickingSceneBGL } from \"./picking-pipeline.js\";\nimport type { PickContributor, PickPassContext } from \"./pick-contributor.js\";\nimport { getViewMatrix } from \"../camera/camera.js\";\n\n/** Result of a successful {@link pickBillboardSprite} hit (re-exported from the public picker). */\nexport interface BillboardPickInfo {\n /** The billboard system that owns the hit sprite. */\n system: BillboardSpriteSystem;\n /** Index-API slot of the hit sprite within `system` (the value returned by `addBillboardSpriteIndex`). */\n spriteIndex: number;\n /** World-space hit point reconstructed from the pick depth, or `null` if it could not be reconstructed. */\n pickedPoint: [number, number, number] | null;\n /** Distance from the camera to `pickedPoint`, or `0` when unavailable. */\n distance: number;\n}\n\n/** Per-pick UBO: 48 bytes. Layout matches the WGSL `BB` struct below. */\nconst BILLBOARD_PICK_UBO_BYTES = 48;\n\n// Instance vertex attribute byte offsets — must match the render layout in `billboard-pipeline.ts`.\nconst POSITION_OFFSET_BYTES = 0;\nconst SIZE_OFFSET_BYTES = 12;\nconst UV_MIN_OFFSET_BYTES = 20;\nconst UV_MAX_OFFSET_BYTES = 28;\nconst ROTATION_OFFSET_BYTES = 36;\nconst PIVOT_OFFSET_BYTES = 40;\n\nconst SHARED_QUAD_INDICES = new Uint16Array([0, 1, 2, 0, 2, 3]);\n\n/**\n * Pack the per-pick billboard UBO into `f32`/`u32` (aliased views of one 48-byte buffer).\n * Layout (vec3 + scalar interleave): `[0..2]` camRight, `[3]` baseId (u32), `[4..6]` camUp,\n * `[7]` cutoff, `[8..10]` axis, `[11]` pad. camRight/camUp are rows 0/1 of the column-major\n * view matrix — reproducing the render shader's `normalize(view rowN)` camera basis.\n */\nexport function packBillboardPickUbo(view: Mat4, baseId: number, cutoff: number, axis: readonly [number, number, number], f32: Float32Array, u32: Uint32Array): void {\n f32[0] = view[0]!;\n f32[1] = view[4]!;\n f32[2] = view[8]!;\n u32[3] = baseId;\n f32[4] = view[1]!;\n f32[5] = view[5]!;\n f32[6] = view[9]!;\n f32[7] = cutoff;\n f32[8] = axis[0];\n f32[9] = axis[1];\n f32[10] = axis[2];\n}\n\nfunction makePickBasisWgsl(orientation: BillboardOrientation): string {\n switch (orientation) {\n case \"facing\":\n return `fn basis() -> B {\nlet r = normalize(bb.camRight);\nlet u = normalize(bb.camUp);\nreturn B(r, -u);\n}`;\n case \"axis-locked\":\n return `fn basis() -> B {\nlet a = normalize(bb.axis);\nlet cr = normalize(bb.camRight);\nlet pr = cr - a * dot(cr, a);\nlet pl = length(pr);\nlet f = select(vec3f(0, 0, 1), vec3f(1, 0, 0), abs(a.z) > 0.999);\nlet fr = cross(a, f);\nlet r = select(fr / max(length(fr), 1e-4), pr / max(pl, 1e-4), pl > 1e-4);\nreturn B(r, -a);\n}`;\n }\n}\n\nfunction makeBillboardPickWgsl(orientation: BillboardOrientation, isCutout: boolean): string {\n const cutoutBindings = isCutout\n ? `@group(1) @binding(1) var atlasTex: texture_2d<f32>;\n@group(1) @binding(2) var atlasSamp: sampler;`\n : \"\";\n const uvVarying = isCutout ? `,\\n@location(1) uv: vec2f` : \"\";\n const uvAssign = isCutout ? `out.uv = mix(in.a, in.b, q);` : \"\";\n const cutoutDiscard = isCutout\n ? `let s = textureSample(atlasTex, atlasSamp, in.uv);\nif (s.a < bb.cutoff) {\ndiscard;\n}`\n : \"\";\n return `struct PickScene { viewProjection: mat4x4f };\n@group(0) @binding(0) var<uniform> scene: PickScene;\nstruct BB {\ncamRight: vec3f,\nbaseId: u32,\ncamUp: vec3f,\ncutoff: f32,\naxis: vec3f,\n_pad: f32,\n};\n@group(1) @binding(0) var<uniform> bb: BB;\n${cutoutBindings}\nstruct B { r: vec3f, u: vec3f };\n${makePickBasisWgsl(orientation)}\nstruct I {\n@builtin(vertex_index) vid: u32,\n@builtin(instance_index) iid: u32,\n@location(0) p: vec3f,\n@location(1) s: vec2f,\n@location(2) a: vec2f,\n@location(3) b: vec2f,\n@location(4) r: f32,\n@location(5) o: vec2f,\n};\nstruct O {\n@builtin(position) p: vec4f,\n@location(0) @interpolate(flat) pickId: u32${uvVarying}\n};\n@vertex\nfn vs(in: I) -> O {\nlet q = vec2f(select(0.0, 1.0, in.vid == 1u || in.vid == 2u), select(0.0, 1.0, in.vid >= 2u));\nlet l = (q - in.o) * in.s;\nlet cr = cos(in.r);\nlet sr = sin(in.r);\nlet rot = vec2f(l.x * cr - l.y * sr, l.x * sr + l.y * cr);\nlet bs = basis();\nlet wp = in.p + bs.r * rot.x + bs.u * rot.y;\nvar out: O;\nout.p = scene.viewProjection * vec4f(wp, 1);\nout.pickId = bb.baseId + in.iid;\n${uvAssign}\nreturn out;\n}\nstruct FsOut { @location(0) color: vec4f, @location(1) depth: vec4f };\n@fragment\nfn fs(in: O) -> FsOut {\n${cutoutDiscard}\nlet id = in.pickId;\nlet r = f32((id >> 16u) & 0xFFu) / 255.0;\nlet g = f32((id >> 8u) & 0xFFu) / 255.0;\nlet b = f32(id & 0xFFu) / 255.0;\nreturn FsOut(vec4f(r, g, b, 1.0), vec4f(in.p.z, 0.0, 0.0, 0.0));\n}`;\n}\n\ninterface BillboardPickCache {\n device: GPUDevice;\n /** Pipelines keyed by `${orientation}|${isCutout ? 1 : 0}`. */\n pipelines: Map<string, GPURenderPipeline>;\n /** Group-1 bind-group layouts keyed by `isCutout`. */\n bgls: Map<string, GPUBindGroupLayout>;\n}\n\nlet _cache: BillboardPickCache | null = null;\n\nfunction getCache(engine: EngineContext): BillboardPickCache {\n if (_cache && _cache.device === engine._device) {\n return _cache;\n }\n _cache = { device: engine._device, pipelines: new Map(), bgls: new Map() };\n return _cache;\n}\n\nfunction getGroup1Bgl(engine: EngineContext, cache: BillboardPickCache, isCutout: boolean): GPUBindGroupLayout {\n const key = isCutout ? \"cutout\" : \"plain\";\n let bgl = cache.bgls.get(key);\n if (!bgl) {\n const entries: GPUBindGroupLayoutEntry[] = [{ binding: 0, visibility: SS.VERTEX | SS.FRAGMENT, buffer: { type: \"uniform\" } }];\n if (isCutout) {\n entries.push({ binding: 1, visibility: SS.FRAGMENT, texture: { sampleType: \"float\" } });\n entries.push({ binding: 2, visibility: SS.FRAGMENT, sampler: { type: \"filtering\" } });\n }\n bgl = engine._device.createBindGroupLayout({ label: `billboard-pick-bgl-${key}`, entries });\n cache.bgls.set(key, bgl);\n }\n return bgl;\n}\n\nfunction getPickPipeline(engine: EngineContext, system: BillboardSpriteSystem): GPURenderPipeline {\n const cache = getCache(engine);\n const orientation = system._orientation;\n const isCutout = system._depthMode === \"cutout\";\n const key = `${orientation}|${isCutout ? 1 : 0}`;\n const cached = cache.pipelines.get(key);\n if (cached) {\n return cached;\n }\n const device = engine._device;\n const module = device.createShaderModule({ label: `billboard-pick-${key}`, code: makeBillboardPickWgsl(orientation, isCutout) });\n const group1Bgl = getGroup1Bgl(engine, cache, isCutout);\n const pipeline = device.createRenderPipeline({\n label: `billboard-pick-pipeline-${key}`,\n layout: device.createPipelineLayout({ bindGroupLayouts: [getPickingSceneBGL(engine), group1Bgl] }),\n vertex: {\n module,\n entryPoint: \"vs\",\n buffers: [\n {\n arrayStride: BILLBOARD_INSTANCE_STRIDE_BYTES,\n stepMode: \"instance\",\n attributes: [\n { shaderLocation: 0, offset: POSITION_OFFSET_BYTES, format: \"float32x3\" },\n { shaderLocation: 1, offset: SIZE_OFFSET_BYTES, format: \"float32x2\" },\n { shaderLocation: 2, offset: UV_MIN_OFFSET_BYTES, format: \"float32x2\" },\n { shaderLocation: 3, offset: UV_MAX_OFFSET_BYTES, format: \"float32x2\" },\n { shaderLocation: 4, offset: ROTATION_OFFSET_BYTES, format: \"float32\" },\n { shaderLocation: 5, offset: PIVOT_OFFSET_BYTES, format: \"float32x2\" },\n ],\n },\n ],\n },\n fragment: { module, entryPoint: \"fs\", targets: [{ format: \"rgba8unorm\", writeMask: CW.ALL }, { format: \"r32float\" }] },\n // Match the mesh picker: reverse-Z depth (clear 0, \"greater\") so a billboard occluded by a\n // mesh — or by a nearer billboard — loses the pick. Write depth so billboards occlude too.\n primitive: { topology: \"triangle-list\", cullMode: \"none\" },\n depthStencil: { format: \"depth24plus\", depthCompare: \"greater\", depthWriteEnabled: true },\n multisample: { count: 1 },\n });\n cache.pipelines.set(key, pipeline);\n return pipeline;\n}\n\n/** Per-system GPU resources allocated on first pick of that system. */\nexport interface BillboardPickResources {\n ubo: GPUBuffer;\n uboScratch: ArrayBuffer;\n uboF32: Float32Array;\n uboU32: Uint32Array;\n instanceBuffer: GPUBuffer;\n instanceCapacity: number;\n indexBuffer: GPUBuffer;\n bindGroup: GPUBindGroup;\n}\n\nexport function createBillboardPickResources(engine: EngineContext, system: BillboardSpriteSystem): BillboardPickResources {\n const device = engine._device;\n const cache = getCache(engine);\n const isCutout = system._depthMode === \"cutout\";\n\n const ubo = device.createBuffer({ label: \"billboard-pick-ubo\", size: BILLBOARD_PICK_UBO_BYTES, usage: BU.UNIFORM | BU.COPY_DST });\n const uboScratch = new ArrayBuffer(BILLBOARD_PICK_UBO_BYTES);\n\n const instanceCapacity = Math.max(1, system._capacity);\n const instanceBuffer = device.createBuffer({\n label: \"billboard-pick-instances\",\n size: instanceCapacity * BILLBOARD_INSTANCE_STRIDE_BYTES,\n usage: BU.VERTEX | BU.COPY_DST,\n });\n\n const indexBuffer = device.createBuffer({ label: \"billboard-pick-indices\", size: SHARED_QUAD_INDICES.byteLength, usage: BU.INDEX | BU.COPY_DST });\n device.queue.writeBuffer(indexBuffer, 0, SHARED_QUAD_INDICES);\n\n const entries: GPUBindGroupEntry[] = [{ binding: 0, resource: { buffer: ubo } }];\n if (isCutout) {\n entries.push({ binding: 1, resource: system.atlas.texture.view });\n entries.push({ binding: 2, resource: system.atlas.texture.sampler });\n }\n const bindGroup = device.createBindGroup({ label: \"billboard-pick-bg\", layout: getGroup1Bgl(engine, cache, isCutout), entries });\n\n return {\n ubo,\n uboScratch,\n uboF32: new F32(uboScratch),\n uboU32: new U32(uboScratch),\n instanceBuffer,\n instanceCapacity,\n indexBuffer,\n bindGroup,\n };\n}\n\nexport function disposeBillboardPickResources(res: BillboardPickResources): void {\n res.ubo.destroy();\n res.instanceBuffer.destroy();\n res.indexBuffer.destroy();\n}\n\n/**\n * Draw one billboard system into the shared pick pass with consecutive pick ids `[baseId, baseId + count)`.\n * The caller must have already bound the pick scene UBO (pick-zoomed view-projection) at group 0.\n * `view` is the camera's column-major view matrix (for the billboard basis).\n */\nexport function drawBillboardForPicking(\n pass: GPURenderPassEncoder,\n engine: EngineContext,\n system: BillboardSpriteSystem,\n res: BillboardPickResources,\n baseId: number,\n view: Mat4\n): void {\n const device = engine._device;\n const count = system.count;\n if (count === 0) {\n return;\n }\n\n // Grow the pick instance buffer if the system outgrew it since the last pick.\n if (system._capacity > res.instanceCapacity) {\n res.instanceBuffer.destroy();\n res.instanceCapacity = system._capacity;\n res.instanceBuffer = device.createBuffer({\n label: \"billboard-pick-instances\",\n size: res.instanceCapacity * BILLBOARD_INSTANCE_STRIDE_BYTES,\n usage: BU.VERTEX | BU.COPY_DST,\n });\n }\n // Upload the system's CPU instance data in logical order, so pick id − baseId == sprite index.\n const data = system._instanceData;\n device.queue.writeBuffer(res.instanceBuffer, 0, data.buffer, data.byteOffset, count * BILLBOARD_INSTANCE_STRIDE_BYTES);\n\n // Per-pick UBO: camera basis + axis + baseId + alpha cutoff (cutout only).\n const cutoff = system._depthMode === \"cutout\" ? system.alphaCutoff : 0;\n packBillboardPickUbo(view, baseId, cutoff, system._axis, res.uboF32, res.uboU32);\n device.queue.writeBuffer(res.ubo, 0, res.uboScratch, 0, BILLBOARD_PICK_UBO_BYTES);\n\n pass.setPipeline(getPickPipeline(engine, system));\n pass.setBindGroup(1, res.bindGroup);\n pass.setIndexBuffer(res.indexBuffer, \"uint16\");\n pass.setVertexBuffer(0, res.instanceBuffer);\n pass.drawIndexed(6, count);\n}\n\n// ─── Contributor orchestration ──────────────────────────────────────────────\n// The per-system pick draw runs on behalf of the GPU picker's generic contributor loop. It lives\n// here (the dynamic-imported pick module) rather than in `gpu-picker.ts` so a picker scene with no\n// billboards never fetches it; the picker iterates `scene._pickSources` and this module's\n// `createPickContributor` (reached via the pick source registered in `billboard-scene.ts`) is\n// lazy-imported on the first pick.\n\n/**\n * Draw one billboard system into the shared pick pass, assigning pick ids `[baseId, baseId + system.count)`.\n * Rebinds group 0 to the mesh pick view-projection (a prior contributor, e.g. GS, may have rebound\n * it) and derives the camera basis from the view matrix. Only called when the system is visible and\n * non-empty; the contributor consumes the id range for hidden/empty systems without drawing.\n */\nexport function drawBillboardSystemForPicking(ctx: PickPassContext, system: BillboardSpriteSystem, res: BillboardPickResources, baseId: number): void {\n ctx.pass.setBindGroup(0, ctx.sceneBG);\n drawBillboardForPicking(ctx.pass, ctx.engine, system, res, baseId, getViewMatrix(ctx.camera));\n}\n\n/** Build the pick contributor for one billboard system. The picker calls this once (via the pick\n * source registered in `billboard-scene.ts`) and reuses the result, so the GPU pick resources live\n * in this closure and free in `dispose`. A hidden/empty system still consumes its id range so\n * id↔sprite mapping stays positional. */\nexport function createPickContributor(system: BillboardSpriteSystem): PickContributor {\n let res: BillboardPickResources | null = null;\n return {\n draw(ctx, baseId) {\n const count = system.count;\n if (!system.visible || count === 0) {\n return baseId + count; // consume the id range, but nothing to draw\n }\n res ??= createBillboardPickResources(ctx.engine, system);\n drawBillboardSystemForPicking(ctx, system, res, baseId);\n return baseId + count;\n },\n resolve(info, localId) {\n info._spritePick = { system, spriteIndex: localId, pickedPoint: info.pickedPoint, distance: info.distance };\n },\n dispose() {\n if (res) {\n disposeBillboardPickResources(res);\n res = null;\n }\n },\n };\n}\n"],"names":[],"mappings":"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"}
@@ -9,7 +9,6 @@ import { resolveCameraViewport } from '../camera/viewport.js';
9
9
  import { createUniformBuffer, createMappedBuffer, createEmptyUniformBuffer } from '../resource/gpu-buffers.js';
10
10
 
11
11
  const _pickVP = new F32(16);
12
- const _gsPickMatrix = new F32(16);
13
12
  const PICK_MESH_UBO_BYTES = 80;
14
13
  const PICK_TI_UBO_BYTES = 16;
15
14
  const _uboScratch = new ArrayBuffer(PICK_MESH_UBO_BYTES);
@@ -26,7 +25,7 @@ function createGpuPicker(scene) {
26
25
  _rt: null,
27
26
  _sceneUbo: null,
28
27
  _sceneBG: null,
29
- _gsMeshResources: null,
28
+ _contributors: null,
30
29
  _pending: null
31
30
  };
32
31
  }
@@ -227,23 +226,22 @@ async function pickAsyncImpl(picker, x, y, options) {
227
226
  nextId++;
228
227
  }
229
228
  }
230
- const gsMeshes = scene._gsMeshes;
231
- const gsMeshCount = gsMeshes.length;
232
- const gsNextIdStart = nextId;
233
- if (gsMeshCount > 0) {
234
- const gsModule = await import('./gs-picking-pipeline.js');
235
- gsModule.computeGsPickMatrix(_gsPickMatrix, px, py, w, h);
236
- gsModule.gsPickWritePickMatrixAndBind(pass, engine, _gsPickMatrix);
237
- const resMap = picker._gsMeshResources ?? (picker._gsMeshResources = /* @__PURE__ */ new Map());
238
- for (let gi = 0; gi < gsMeshCount; gi++) {
239
- const gsMesh = gsMeshes[gi];
240
- let res = resMap.get(gsMesh);
241
- if (!res) {
242
- res = gsModule.createGsPickMeshResources(engine, gsMesh);
243
- resMap.set(gsMesh, res);
229
+ const contribRanges = [];
230
+ if (scene._pickSources.length > 0) {
231
+ const pickCtx = { picker, pass, engine, scene, camera, sceneBG: picker._sceneBG, px, py, w, h };
232
+ for (let ci = 0; ci < scene._pickSources.length; ci++) {
233
+ const src = scene._pickSources[ci];
234
+ let contributor = picker._contributors?.get(src);
235
+ if (!contributor) {
236
+ const pipeline = await src.load();
237
+ contributor = pipeline.createPickContributor(src.entity);
238
+ (picker._contributors ??= /* @__PURE__ */ new Map()).set(src, contributor);
239
+ }
240
+ const base = nextId;
241
+ nextId = contributor.draw(pickCtx, base);
242
+ if (nextId > base) {
243
+ contribRanges.push({ base, count: nextId - base, contributor });
244
244
  }
245
- gsModule.drawGsForPicking(pass, engine, scene, gsMesh, res, nextId, w, h);
246
- nextId++;
247
245
  }
248
246
  }
249
247
  pass.end();
@@ -274,7 +272,6 @@ async function pickAsyncImpl(picker, x, y, options) {
274
272
  }
275
273
  let hitMesh = null;
276
274
  let hitThinIdx = -1;
277
- let hitIsGs = false;
278
275
  let scanId = 1;
279
276
  for (let mi = 0; mi < meshCount; mi++) {
280
277
  const mesh = meshes[mi];
@@ -303,14 +300,19 @@ async function pickAsyncImpl(picker, x, y, options) {
303
300
  scanId++;
304
301
  }
305
302
  }
306
- if (!hitMesh && gsMeshCount > 0 && pickId >= gsNextIdStart) {
307
- const gsIdx = pickId - gsNextIdStart;
308
- if (gsIdx < gsMeshCount) {
309
- hitMesh = gsMeshes[gsIdx];
310
- hitIsGs = true;
303
+ let hitContributor = null;
304
+ let contribLocalId = -1;
305
+ if (!hitMesh) {
306
+ for (let ri = 0; ri < contribRanges.length; ri++) {
307
+ const r = contribRanges[ri];
308
+ if (pickId >= r.base && pickId < r.base + r.count) {
309
+ hitContributor = r.contributor;
310
+ contribLocalId = pickId - r.base;
311
+ break;
312
+ }
311
313
  }
312
314
  }
313
- if (!hitMesh) {
315
+ if (!hitMesh && !hitContributor) {
314
316
  if (debugLabel) {
315
317
  console.trace("pick-debug", {
316
318
  label: debugLabel,
@@ -344,7 +346,10 @@ async function pickAsyncImpl(picker, x, y, options) {
344
346
  const dz = info.pickedPoint[2] - camPos.z;
345
347
  info.distance = Math.sqrt(dx * dx + dy * dy + dz * dz);
346
348
  }
347
- if (picker._detailedPick && !hitIsGs) {
349
+ if (hitContributor) {
350
+ hitContributor.resolve(info, contribLocalId);
351
+ }
352
+ if (picker._detailedPick && hitMesh) {
348
353
  const ray = createPickingRay(pickX - viewport.x, pickY - viewport.y, vp, w, h);
349
354
  if (ray) {
350
355
  info.ray = ray;
@@ -359,7 +364,7 @@ async function pickAsyncImpl(picker, x, y, options) {
359
364
  pickId,
360
365
  depth,
361
366
  hit: true,
362
- mesh: hitMesh.name ?? "(unnamed)",
367
+ mesh: hitMesh ? hitMesh.name ?? "(unnamed)" : "(contributor)",
363
368
  thinInstanceIndex: hitThinIdx,
364
369
  pickedPoint: info.pickedPoint,
365
370
  distance: info.distance
@@ -394,16 +399,11 @@ function disposePicker(picker) {
394
399
  picker._sceneUbo = null;
395
400
  picker._sceneBG = null;
396
401
  }
397
- if (picker._gsMeshResources) {
398
- void import('./gs-picking-pipeline.js').then((m) => {
399
- if (!picker._gsMeshResources) {
400
- return;
401
- }
402
- for (const res of picker._gsMeshResources.values()) {
403
- m.disposeGsPickMeshResources(res);
404
- }
405
- picker._gsMeshResources = null;
406
- });
402
+ if (picker._contributors) {
403
+ for (const contributor of picker._contributors.values()) {
404
+ contributor.dispose?.();
405
+ }
406
+ picker._contributors = null;
407
407
  }
408
408
  }
409
409