@luma.gl/engine 9.2.5 → 9.3.0-alpha.10

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 (186) hide show
  1. package/dist/animation-loop/animation-loop.d.ts +11 -5
  2. package/dist/animation-loop/animation-loop.d.ts.map +1 -1
  3. package/dist/animation-loop/animation-loop.js +83 -47
  4. package/dist/animation-loop/animation-loop.js.map +1 -1
  5. package/dist/animation-loop/make-animation-loop.js +7 -1
  6. package/dist/animation-loop/make-animation-loop.js.map +1 -1
  7. package/dist/animation-loop/request-animation-frame.d.ts.map +1 -1
  8. package/dist/animation-loop/request-animation-frame.js +23 -6
  9. package/dist/animation-loop/request-animation-frame.js.map +1 -1
  10. package/dist/compute/computation.d.ts +3 -7
  11. package/dist/compute/computation.d.ts.map +1 -1
  12. package/dist/compute/computation.js +16 -13
  13. package/dist/compute/computation.js.map +1 -1
  14. package/dist/compute/swap.d.ts +2 -0
  15. package/dist/compute/swap.d.ts.map +1 -1
  16. package/dist/compute/swap.js +10 -5
  17. package/dist/compute/swap.js.map +1 -1
  18. package/dist/dist.dev.js +2639 -1290
  19. package/dist/dist.min.js +325 -210
  20. package/dist/dynamic-texture/dynamic-texture.d.ts +102 -0
  21. package/dist/dynamic-texture/dynamic-texture.d.ts.map +1 -0
  22. package/dist/dynamic-texture/dynamic-texture.js +556 -0
  23. package/dist/dynamic-texture/dynamic-texture.js.map +1 -0
  24. package/dist/dynamic-texture/texture-data.d.ts +144 -0
  25. package/dist/dynamic-texture/texture-data.d.ts.map +1 -0
  26. package/dist/dynamic-texture/texture-data.js +208 -0
  27. package/dist/dynamic-texture/texture-data.js.map +1 -0
  28. package/dist/geometries/cone-geometry.d.ts +3 -1
  29. package/dist/geometries/cone-geometry.d.ts.map +1 -1
  30. package/dist/geometries/cone-geometry.js.map +1 -1
  31. package/dist/geometries/cylinder-geometry.d.ts +2 -1
  32. package/dist/geometries/cylinder-geometry.d.ts.map +1 -1
  33. package/dist/geometries/cylinder-geometry.js.map +1 -1
  34. package/dist/geometry/gpu-geometry.d.ts.map +1 -1
  35. package/dist/geometry/gpu-geometry.js +8 -3
  36. package/dist/geometry/gpu-geometry.js.map +1 -1
  37. package/dist/index.cjs +2497 -1212
  38. package/dist/index.cjs.map +4 -4
  39. package/dist/index.d.ts +20 -6
  40. package/dist/index.d.ts.map +1 -1
  41. package/dist/index.js +12 -4
  42. package/dist/index.js.map +1 -1
  43. package/dist/material/material-factory.d.ts +73 -0
  44. package/dist/material/material-factory.d.ts.map +1 -0
  45. package/dist/material/material-factory.js +111 -0
  46. package/dist/material/material-factory.js.map +1 -0
  47. package/dist/material/material.d.ts +84 -0
  48. package/dist/material/material.d.ts.map +1 -0
  49. package/dist/material/material.js +176 -0
  50. package/dist/material/material.js.map +1 -0
  51. package/dist/model/model.d.ts +47 -16
  52. package/dist/model/model.d.ts.map +1 -1
  53. package/dist/model/model.js +113 -47
  54. package/dist/model/model.js.map +1 -1
  55. package/dist/model/split-uniforms-and-bindings.d.ts +4 -3
  56. package/dist/model/split-uniforms-and-bindings.d.ts.map +1 -1
  57. package/dist/model/split-uniforms-and-bindings.js +2 -2
  58. package/dist/model/split-uniforms-and-bindings.js.map +1 -1
  59. package/dist/models/billboard-texture-model.d.ts +8 -5
  60. package/dist/models/billboard-texture-model.d.ts.map +1 -1
  61. package/dist/models/billboard-texture-model.js +77 -23
  62. package/dist/models/billboard-texture-model.js.map +1 -1
  63. package/dist/models/billboard-texture-module.d.ts +1 -1
  64. package/dist/models/billboard-texture-module.js +1 -1
  65. package/dist/models/clip-space.js +7 -7
  66. package/dist/models/directional-light-model.d.ts +7 -0
  67. package/dist/models/directional-light-model.d.ts.map +1 -0
  68. package/dist/models/directional-light-model.js +23 -0
  69. package/dist/models/directional-light-model.js.map +1 -0
  70. package/dist/models/light-model-utils.d.ts +69 -0
  71. package/dist/models/light-model-utils.d.ts.map +1 -0
  72. package/dist/models/light-model-utils.js +395 -0
  73. package/dist/models/light-model-utils.js.map +1 -0
  74. package/dist/models/point-light-model.d.ts +7 -0
  75. package/dist/models/point-light-model.d.ts.map +1 -0
  76. package/dist/models/point-light-model.js +22 -0
  77. package/dist/models/point-light-model.js.map +1 -0
  78. package/dist/models/spot-light-model.d.ts +7 -0
  79. package/dist/models/spot-light-model.d.ts.map +1 -0
  80. package/dist/models/spot-light-model.js +23 -0
  81. package/dist/models/spot-light-model.js.map +1 -0
  82. package/dist/modules/picking/color-picking.d.ts +5 -9
  83. package/dist/modules/picking/color-picking.d.ts.map +1 -1
  84. package/dist/modules/picking/color-picking.js +122 -115
  85. package/dist/modules/picking/color-picking.js.map +1 -1
  86. package/dist/modules/picking/index-picking.d.ts +4 -4
  87. package/dist/modules/picking/index-picking.d.ts.map +1 -1
  88. package/dist/modules/picking/index-picking.js +36 -16
  89. package/dist/modules/picking/index-picking.js.map +1 -1
  90. package/dist/modules/picking/legacy-color-picking.d.ts +26 -0
  91. package/dist/modules/picking/legacy-color-picking.d.ts.map +1 -0
  92. package/dist/modules/picking/legacy-color-picking.js +7 -0
  93. package/dist/modules/picking/legacy-color-picking.js.map +1 -0
  94. package/dist/modules/picking/picking-manager.d.ts +29 -3
  95. package/dist/modules/picking/picking-manager.d.ts.map +1 -1
  96. package/dist/modules/picking/picking-manager.js +188 -41
  97. package/dist/modules/picking/picking-manager.js.map +1 -1
  98. package/dist/modules/picking/picking-uniforms.d.ts +13 -12
  99. package/dist/modules/picking/picking-uniforms.d.ts.map +1 -1
  100. package/dist/modules/picking/picking-uniforms.js +27 -14
  101. package/dist/modules/picking/picking-uniforms.js.map +1 -1
  102. package/dist/modules/picking/picking.d.ts +25 -0
  103. package/dist/modules/picking/picking.d.ts.map +1 -0
  104. package/dist/modules/picking/picking.js +18 -0
  105. package/dist/modules/picking/picking.js.map +1 -0
  106. package/dist/passes/get-fragment-shader.js +12 -27
  107. package/dist/passes/get-fragment-shader.js.map +1 -1
  108. package/dist/passes/shader-pass-renderer.d.ts +5 -7
  109. package/dist/passes/shader-pass-renderer.d.ts.map +1 -1
  110. package/dist/passes/shader-pass-renderer.js +16 -42
  111. package/dist/passes/shader-pass-renderer.js.map +1 -1
  112. package/dist/scenegraph/group-node.d.ts +5 -0
  113. package/dist/scenegraph/group-node.d.ts.map +1 -1
  114. package/dist/scenegraph/group-node.js +12 -0
  115. package/dist/scenegraph/group-node.js.map +1 -1
  116. package/dist/scenegraph/model-node.d.ts +2 -2
  117. package/dist/scenegraph/model-node.d.ts.map +1 -1
  118. package/dist/scenegraph/model-node.js.map +1 -1
  119. package/dist/scenegraph/scenegraph-node.d.ts +1 -1
  120. package/dist/scenegraph/scenegraph-node.d.ts.map +1 -1
  121. package/dist/scenegraph/scenegraph-node.js +23 -15
  122. package/dist/scenegraph/scenegraph-node.js.map +1 -1
  123. package/dist/shader-inputs.d.ts +9 -7
  124. package/dist/shader-inputs.d.ts.map +1 -1
  125. package/dist/shader-inputs.js +84 -4
  126. package/dist/shader-inputs.js.map +1 -1
  127. package/dist/utils/buffer-layout-order.d.ts.map +1 -1
  128. package/dist/utils/buffer-layout-order.js +12 -2
  129. package/dist/utils/buffer-layout-order.js.map +1 -1
  130. package/dist/utils/shader-module-utils.d.ts +7 -0
  131. package/dist/utils/shader-module-utils.d.ts.map +1 -0
  132. package/dist/utils/shader-module-utils.js +46 -0
  133. package/dist/utils/shader-module-utils.js.map +1 -0
  134. package/package.json +6 -6
  135. package/src/animation-loop/animation-loop.ts +89 -50
  136. package/src/animation-loop/make-animation-loop.ts +13 -5
  137. package/src/animation-loop/request-animation-frame.ts +32 -6
  138. package/src/compute/computation.ts +32 -17
  139. package/src/compute/swap.ts +13 -7
  140. package/src/dynamic-texture/dynamic-texture.ts +732 -0
  141. package/src/dynamic-texture/texture-data.ts +336 -0
  142. package/src/geometries/cone-geometry.ts +6 -1
  143. package/src/geometries/cylinder-geometry.ts +5 -1
  144. package/src/geometry/gpu-geometry.ts +8 -3
  145. package/src/index.ts +38 -8
  146. package/src/material/material-factory.ts +157 -0
  147. package/src/material/material.ts +254 -0
  148. package/src/model/model.ts +158 -67
  149. package/src/model/split-uniforms-and-bindings.ts +8 -6
  150. package/src/models/billboard-texture-model.ts +88 -27
  151. package/src/models/billboard-texture-module.ts +1 -1
  152. package/src/models/clip-space.ts +7 -7
  153. package/src/models/directional-light-model.ts +32 -0
  154. package/src/models/light-model-utils.ts +587 -0
  155. package/src/models/point-light-model.ts +31 -0
  156. package/src/models/spot-light-model.ts +32 -0
  157. package/src/modules/picking/color-picking.ts +123 -122
  158. package/src/modules/picking/index-picking.ts +36 -16
  159. package/src/modules/picking/legacy-color-picking.ts +8 -0
  160. package/src/modules/picking/picking-manager.ts +252 -50
  161. package/src/modules/picking/picking-uniforms.ts +39 -24
  162. package/src/modules/picking/picking.ts +22 -0
  163. package/src/passes/get-fragment-shader.ts +12 -27
  164. package/src/passes/shader-pass-renderer.ts +25 -48
  165. package/src/scenegraph/group-node.ts +16 -0
  166. package/src/scenegraph/model-node.ts +2 -2
  167. package/src/scenegraph/scenegraph-node.ts +27 -16
  168. package/src/shader-inputs.ts +165 -15
  169. package/src/utils/buffer-layout-order.ts +18 -2
  170. package/src/utils/shader-module-utils.ts +65 -0
  171. package/dist/async-texture/async-texture.d.ts +0 -166
  172. package/dist/async-texture/async-texture.d.ts.map +0 -1
  173. package/dist/async-texture/async-texture.js +0 -386
  174. package/dist/async-texture/async-texture.js.map +0 -1
  175. package/dist/factories/pipeline-factory.d.ts +0 -37
  176. package/dist/factories/pipeline-factory.d.ts.map +0 -1
  177. package/dist/factories/pipeline-factory.js +0 -181
  178. package/dist/factories/pipeline-factory.js.map +0 -1
  179. package/dist/factories/shader-factory.d.ts +0 -22
  180. package/dist/factories/shader-factory.d.ts.map +0 -1
  181. package/dist/factories/shader-factory.js +0 -88
  182. package/dist/factories/shader-factory.js.map +0 -1
  183. package/src/async-texture/async-texture.ts +0 -551
  184. package/src/factories/pipeline-factory.ts +0 -224
  185. package/src/factories/shader-factory.ts +0 -103
  186. /package/src/{async-texture/texture-setters.ts.disabled → dynamic-texture/texture-data.ts.disabled} +0 -0
package/dist/index.cjs CHANGED
@@ -35,22 +35,26 @@ __export(dist_exports, {
35
35
  ConeGeometry: () => ConeGeometry,
36
36
  CubeGeometry: () => CubeGeometry,
37
37
  CylinderGeometry: () => CylinderGeometry,
38
+ DirectionalLightModel: () => DirectionalLightModel,
39
+ DynamicTexture: () => DynamicTexture,
38
40
  GPUGeometry: () => GPUGeometry,
39
41
  Geometry: () => Geometry,
40
42
  GroupNode: () => GroupNode,
41
43
  IcoSphereGeometry: () => IcoSphereGeometry,
42
44
  KeyFrames: () => KeyFrames,
43
45
  LegacyPickingManager: () => LegacyPickingManager,
46
+ Material: () => Material,
47
+ MaterialFactory: () => MaterialFactory,
44
48
  Model: () => Model,
45
49
  ModelNode: () => ModelNode,
46
50
  PickingManager: () => PickingManager,
47
- PipelineFactory: () => PipelineFactory,
48
51
  PlaneGeometry: () => PlaneGeometry,
52
+ PointLightModel: () => PointLightModel,
49
53
  ScenegraphNode: () => ScenegraphNode,
50
- ShaderFactory: () => ShaderFactory,
51
54
  ShaderInputs: () => ShaderInputs,
52
55
  ShaderPassRenderer: () => ShaderPassRenderer,
53
56
  SphereGeometry: () => SphereGeometry,
57
+ SpotLightModel: () => SpotLightModel,
54
58
  Swap: () => Swap,
55
59
  SwapBuffers: () => SwapBuffers,
56
60
  SwapFramebuffers: () => SwapFramebuffers,
@@ -58,14 +62,19 @@ __export(dist_exports, {
58
62
  Timeline: () => Timeline,
59
63
  TruncatedConeGeometry: () => TruncatedConeGeometry,
60
64
  cancelAnimationFramePolyfill: () => cancelAnimationFramePolyfill,
61
- colorPicking: () => picking2,
62
- indexPicking: () => picking,
65
+ colorPicking: () => picking,
66
+ indexPicking: () => picking2,
67
+ legacyColorPicking: () => legacyColorPicking,
63
68
  loadImage: () => loadImage,
64
69
  loadImageBitmap: () => loadImageBitmap,
65
70
  makeAnimationLoop: () => makeAnimationLoop,
66
71
  makeRandomGenerator: () => makeRandomGenerator,
72
+ picking: () => picking3,
67
73
  requestAnimationFramePolyfill: () => requestAnimationFramePolyfill,
68
- setPathPrefix: () => setPathPrefix
74
+ resolvePickingBackend: () => resolvePickingBackend,
75
+ resolvePickingMode: () => resolvePickingMode,
76
+ setPathPrefix: () => setPathPrefix,
77
+ supportsIndexPicking: () => supportsIndexPicking
69
78
  });
70
79
  module.exports = __toCommonJS(dist_exports);
71
80
 
@@ -245,15 +254,25 @@ var import_core = require("@luma.gl/core");
245
254
 
246
255
  // dist/animation-loop/request-animation-frame.js
247
256
  function requestAnimationFramePolyfill(callback) {
248
- return typeof window !== "undefined" && window.requestAnimationFrame ? window.requestAnimationFrame(callback) : setTimeout(callback, 1e3 / 60);
257
+ const browserRequestAnimationFrame = typeof window !== "undefined" ? window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame : null;
258
+ if (browserRequestAnimationFrame) {
259
+ return browserRequestAnimationFrame.call(window, callback);
260
+ }
261
+ return setTimeout(() => callback(typeof performance !== "undefined" ? performance.now() : Date.now()), 1e3 / 60);
249
262
  }
250
263
  function cancelAnimationFramePolyfill(timerId) {
251
- return typeof window !== "undefined" && window.cancelAnimationFrame ? window.cancelAnimationFrame(timerId) : clearTimeout(timerId);
264
+ const browserCancelAnimationFrame = typeof window !== "undefined" ? window.cancelAnimationFrame || window.webkitCancelAnimationFrame || window.mozCancelAnimationFrame : null;
265
+ if (browserCancelAnimationFrame) {
266
+ browserCancelAnimationFrame.call(window, timerId);
267
+ return;
268
+ }
269
+ clearTimeout(timerId);
252
270
  }
253
271
 
254
272
  // dist/animation-loop/animation-loop.js
255
273
  var import_stats = require("@probe.gl/stats");
256
274
  var statIdCounter = 0;
275
+ var ANIMATION_LOOP_STATS = "Animation Loop";
257
276
  var _AnimationLoop = class {
258
277
  device = null;
259
278
  canvas = null;
@@ -261,11 +280,12 @@ var _AnimationLoop = class {
261
280
  animationProps = null;
262
281
  timeline = null;
263
282
  stats;
283
+ sharedStats;
264
284
  cpuTime;
265
285
  gpuTime;
266
286
  frameRate;
267
287
  display;
268
- needsRedraw = "initialized";
288
+ _needsRedraw = "initialized";
269
289
  _initialized = false;
270
290
  _running = false;
271
291
  _animationFrameId = null;
@@ -273,7 +293,7 @@ var _AnimationLoop = class {
273
293
  _resolveNextFrame = null;
274
294
  _cpuStartTime = 0;
275
295
  _error = null;
276
- // _gpuTimeQuery: Query | null = null;
296
+ _lastFrameTime = 0;
277
297
  /*
278
298
  * @param {HTMLCanvasElement} canvas - if provided, width and height will be passed to context
279
299
  */
@@ -283,10 +303,12 @@ var _AnimationLoop = class {
283
303
  if (!props.device) {
284
304
  throw new Error("No device provided");
285
305
  }
286
- this.stats = props.stats || new import_stats.Stats({ id: "animation-loop-stats" });
306
+ this.stats = props.stats || new import_stats.Stats({ id: `animation-loop-${statIdCounter++}` });
307
+ this.sharedStats = import_core.luma.stats.get(ANIMATION_LOOP_STATS);
308
+ this.frameRate = this.stats.get("Frame Rate");
309
+ this.frameRate.setSampleSize(1);
287
310
  this.cpuTime = this.stats.get("CPU Time");
288
311
  this.gpuTime = this.stats.get("GPU Time");
289
- this.frameRate = this.stats.get("Frame Rate");
290
312
  this.setProps({ autoResizeViewport: props.autoResizeViewport });
291
313
  this.start = this.start.bind(this);
292
314
  this.stop = this.stop.bind(this);
@@ -294,8 +316,10 @@ var _AnimationLoop = class {
294
316
  this._onMouseleave = this._onMouseleave.bind(this);
295
317
  }
296
318
  destroy() {
319
+ var _a;
297
320
  this.stop();
298
321
  this._setDisplay(null);
322
+ (_a = this.device) == null ? void 0 : _a._disableDebugGPUTime();
299
323
  }
300
324
  /** @deprecated Use .destroy() */
301
325
  delete() {
@@ -307,9 +331,15 @@ var _AnimationLoop = class {
307
331
  }
308
332
  /** Flags this animation loop as needing redraw */
309
333
  setNeedsRedraw(reason) {
310
- this.needsRedraw = this.needsRedraw || reason;
334
+ this._needsRedraw = this._needsRedraw || reason;
311
335
  return this;
312
336
  }
337
+ /** Query redraw status. Clears the flag. */
338
+ needsRedraw() {
339
+ const reason = this._needsRedraw;
340
+ this._needsRedraw = false;
341
+ return reason;
342
+ }
313
343
  setProps(props) {
314
344
  if ("autoResizeViewport" in props) {
315
345
  this.props.autoResizeViewport = props.autoResizeViewport || false;
@@ -328,6 +358,9 @@ var _AnimationLoop = class {
328
358
  this._initialized = true;
329
359
  await this._initDevice();
330
360
  this._initialize();
361
+ if (!this._running) {
362
+ return null;
363
+ }
331
364
  await this.props.onInitialize(this._getAnimationProps());
332
365
  }
333
366
  if (!this._running) {
@@ -354,16 +387,17 @@ var _AnimationLoop = class {
354
387
  this._nextFramePromise = null;
355
388
  this._resolveNextFrame = null;
356
389
  this._running = false;
390
+ this._lastFrameTime = 0;
357
391
  }
358
392
  return this;
359
393
  }
360
394
  /** Explicitly draw a frame */
361
- redraw() {
395
+ redraw(time) {
362
396
  var _a;
363
397
  if (((_a = this.device) == null ? void 0 : _a.isLost) || this._error) {
364
398
  return this;
365
399
  }
366
- this._beginFrameTimers();
400
+ this._beginFrameTimers(time);
367
401
  this._setupFrame();
368
402
  this._updateAnimationProps();
369
403
  this._renderFrame(this._getAnimationProps());
@@ -406,10 +440,12 @@ var _AnimationLoop = class {
406
440
  }
407
441
  // PRIVATE METHODS
408
442
  _initialize() {
443
+ var _a;
409
444
  this._startEventHandling();
410
445
  this._initializeAnimationProps();
411
446
  this._updateAnimationProps();
412
447
  this._resizeViewport();
448
+ (_a = this.device) == null ? void 0 : _a._enableDebugGPUTime();
413
449
  }
414
450
  _setDisplay(display) {
415
451
  if (this.display) {
@@ -434,11 +470,11 @@ var _AnimationLoop = class {
434
470
  cancelAnimationFramePolyfill(this._animationFrameId);
435
471
  this._animationFrameId = null;
436
472
  }
437
- _animationFrame() {
473
+ _animationFrame(time) {
438
474
  if (!this._running) {
439
475
  return;
440
476
  }
441
- this.redraw();
477
+ this.redraw(time);
442
478
  this._requestAnimationFrame();
443
479
  }
444
480
  // Called on each frame, can be overridden to call onRender multiple times
@@ -453,7 +489,7 @@ var _AnimationLoop = class {
453
489
  (_a = this.device) == null ? void 0 : _a.submit();
454
490
  }
455
491
  _clearNeedsRedraw() {
456
- this.needsRedraw = false;
492
+ this._needsRedraw = false;
457
493
  }
458
494
  _setupFrame() {
459
495
  this._resizeViewport();
@@ -512,7 +548,7 @@ var _AnimationLoop = class {
512
548
  this.animationProps.width = width;
513
549
  this.animationProps.height = height;
514
550
  this.animationProps.aspect = aspect;
515
- this.animationProps.needsRedraw = this.needsRedraw;
551
+ this.animationProps.needsRedraw = this._needsRedraw;
516
552
  this.animationProps.engineTime = Date.now() - this.animationProps.startTime;
517
553
  if (this.timeline) {
518
554
  this.timeline.update(this.animationProps.engineTime);
@@ -551,18 +587,11 @@ var _AnimationLoop = class {
551
587
  }
552
588
  }
553
589
  _getSizeAndAspect() {
554
- var _a, _b;
555
590
  if (!this.device) {
556
591
  return { width: 1, height: 1, aspect: 1 };
557
592
  }
558
- const [width, height] = ((_a = this.device) == null ? void 0 : _a.getDefaultCanvasContext().getDevicePixelSize()) || [1, 1];
559
- let aspect = 1;
560
- const canvas2 = (_b = this.device) == null ? void 0 : _b.getDefaultCanvasContext().canvas;
561
- if (canvas2 && canvas2.clientHeight) {
562
- aspect = canvas2.clientWidth / canvas2.clientHeight;
563
- } else if (width > 0 && height > 0) {
564
- aspect = width / height;
565
- }
593
+ const [width, height] = this.device.getDefaultCanvasContext().getDrawingBufferSize();
594
+ const aspect = width > 0 && height > 0 ? width / height : 1;
566
595
  return { width, height, aspect };
567
596
  }
568
597
  /** @deprecated Default viewport setup */
@@ -578,13 +607,63 @@ var _AnimationLoop = class {
578
607
  );
579
608
  }
580
609
  }
581
- _beginFrameTimers() {
582
- this.frameRate.timeEnd();
583
- this.frameRate.timeStart();
610
+ _beginFrameTimers(time) {
611
+ var _a;
612
+ const now = time ?? (typeof performance !== "undefined" ? performance.now() : Date.now());
613
+ if (this._lastFrameTime) {
614
+ const frameTime = now - this._lastFrameTime;
615
+ if (frameTime > 0) {
616
+ this.frameRate.addTime(frameTime);
617
+ }
618
+ }
619
+ this._lastFrameTime = now;
620
+ if ((_a = this.device) == null ? void 0 : _a._isDebugGPUTimeEnabled()) {
621
+ this._consumeEncodedGpuTime();
622
+ }
584
623
  this.cpuTime.timeStart();
585
624
  }
586
625
  _endFrameTimers() {
626
+ var _a;
627
+ if ((_a = this.device) == null ? void 0 : _a._isDebugGPUTimeEnabled()) {
628
+ this._consumeEncodedGpuTime();
629
+ }
587
630
  this.cpuTime.timeEnd();
631
+ this._updateSharedStats();
632
+ }
633
+ _consumeEncodedGpuTime() {
634
+ if (!this.device) {
635
+ return;
636
+ }
637
+ const gpuTimeMs = this.device.commandEncoder._gpuTimeMs;
638
+ if (gpuTimeMs !== void 0) {
639
+ this.gpuTime.addTime(gpuTimeMs);
640
+ this.device.commandEncoder._gpuTimeMs = void 0;
641
+ }
642
+ }
643
+ _updateSharedStats() {
644
+ if (this.stats === this.sharedStats) {
645
+ return;
646
+ }
647
+ for (const name of Object.keys(this.sharedStats.stats)) {
648
+ if (!this.stats.stats[name]) {
649
+ delete this.sharedStats.stats[name];
650
+ }
651
+ }
652
+ this.stats.forEach((sourceStat) => {
653
+ const targetStat = this.sharedStats.get(sourceStat.name, sourceStat.type);
654
+ targetStat.sampleSize = sourceStat.sampleSize;
655
+ targetStat.time = sourceStat.time;
656
+ targetStat.count = sourceStat.count;
657
+ targetStat.samples = sourceStat.samples;
658
+ targetStat.lastTiming = sourceStat.lastTiming;
659
+ targetStat.lastSampleTime = sourceStat.lastSampleTime;
660
+ targetStat.lastSampleCount = sourceStat.lastSampleCount;
661
+ targetStat._count = sourceStat._count;
662
+ targetStat._time = sourceStat._time;
663
+ targetStat._samples = sourceStat._samples;
664
+ targetStat._startTime = sourceStat._startTime;
665
+ targetStat._timerPending = sourceStat._timerPending;
666
+ });
588
667
  }
589
668
  // Event handling
590
669
  _startEventHandling() {
@@ -613,7 +692,7 @@ __publicField(AnimationLoop, "defaultAnimationLoopProps", {
613
692
  },
614
693
  onError: (error) => console.error(error),
615
694
  // eslint-disable-line no-console
616
- stats: import_core.luma.stats.get(`animation-loop-${statIdCounter++}`),
695
+ stats: void 0,
617
696
  // view parameters
618
697
  autoResizeViewport: false
619
698
  });
@@ -646,7 +725,10 @@ function makeAnimationLoop(AnimationLoopTemplateCtor, props) {
646
725
  }
647
726
  function setError(device, error) {
648
727
  var _a;
649
- const canvas2 = device == null ? void 0 : device.getDefaultCanvasContext().canvas;
728
+ if (!device) {
729
+ return;
730
+ }
731
+ const canvas2 = device.getDefaultCanvasContext().canvas;
650
732
  if (canvas2 instanceof HTMLCanvasElement) {
651
733
  canvas2.style.overflow = "visible";
652
734
  let errorDiv = document.getElementById("animation-loop-error");
@@ -663,6 +745,9 @@ function setError(device, error) {
663
745
  }
664
746
  }
665
747
  function clearError(device) {
748
+ if (!device) {
749
+ return;
750
+ }
666
751
  const errorDiv = document.getElementById("animation-loop-error");
667
752
  if (errorDiv) {
668
753
  errorDiv.remove();
@@ -670,7 +755,7 @@ function clearError(device) {
670
755
  }
671
756
 
672
757
  // dist/model/model.js
673
- var import_core9 = require("@luma.gl/core");
758
+ var import_core8 = require("@luma.gl/core");
674
759
  var import_shadertools2 = require("@luma.gl/shadertools");
675
760
 
676
761
  // dist/geometry/gpu-geometry.js
@@ -774,262 +859,19 @@ function getAttributeBuffersFromGeometry(device, geometry) {
774
859
  id: `${attributeName}-buffer`
775
860
  });
776
861
  const { value, size, normalized } = attribute;
777
- bufferLayout.push({ name, format: (0, import_core3.getVertexFormatFromAttribute)(value, size, normalized) });
862
+ if (size === void 0) {
863
+ throw new Error(`Attribute ${attributeName} is missing a size`);
864
+ }
865
+ bufferLayout.push({
866
+ name,
867
+ format: import_core3.vertexFormatDecoder.getVertexFormatFromAttribute(value, size, normalized)
868
+ });
778
869
  }
779
870
  }
780
871
  const vertexCount = geometry._calculateVertexCount(geometry.attributes, geometry.indices);
781
872
  return { attributes, bufferLayout, vertexCount };
782
873
  }
783
874
 
784
- // dist/factories/pipeline-factory.js
785
- var import_core4 = require("@luma.gl/core");
786
- var _PipelineFactory = class {
787
- /** Get the singleton default pipeline factory for the specified device */
788
- static getDefaultPipelineFactory(device) {
789
- device._lumaData["defaultPipelineFactory"] = device._lumaData["defaultPipelineFactory"] || new _PipelineFactory(device);
790
- return device._lumaData["defaultPipelineFactory"];
791
- }
792
- device;
793
- cachingEnabled;
794
- destroyPolicy;
795
- debug;
796
- _hashCounter = 0;
797
- _hashes = {};
798
- _renderPipelineCache = {};
799
- _computePipelineCache = {};
800
- get [Symbol.toStringTag]() {
801
- return "PipelineFactory";
802
- }
803
- toString() {
804
- return `PipelineFactory(${this.device.id})`;
805
- }
806
- constructor(device) {
807
- this.device = device;
808
- this.cachingEnabled = device.props._cachePipelines;
809
- this.destroyPolicy = device.props._cacheDestroyPolicy;
810
- this.debug = device.props.debugFactories;
811
- }
812
- /** Return a RenderPipeline matching supplied props. Reuses an equivalent pipeline if already created. */
813
- createRenderPipeline(props) {
814
- var _a;
815
- if (!this.cachingEnabled) {
816
- return this.device.createRenderPipeline(props);
817
- }
818
- const allProps = { ...import_core4.RenderPipeline.defaultProps, ...props };
819
- const cache = this._renderPipelineCache;
820
- const hash = this._hashRenderPipeline(allProps);
821
- let pipeline = (_a = cache[hash]) == null ? void 0 : _a.pipeline;
822
- if (!pipeline) {
823
- pipeline = this.device.createRenderPipeline({
824
- ...allProps,
825
- id: allProps.id ? `${allProps.id}-cached` : uid("unnamed-cached")
826
- });
827
- pipeline.hash = hash;
828
- cache[hash] = { pipeline, useCount: 1 };
829
- if (this.debug) {
830
- import_core4.log.log(3, `${this}: ${pipeline} created, count=${cache[hash].useCount}`)();
831
- }
832
- } else {
833
- cache[hash].useCount++;
834
- if (this.debug) {
835
- import_core4.log.log(3, `${this}: ${cache[hash].pipeline} reused, count=${cache[hash].useCount}, (id=${props.id})`)();
836
- }
837
- }
838
- return pipeline;
839
- }
840
- /** Return a ComputePipeline matching supplied props. Reuses an equivalent pipeline if already created. */
841
- createComputePipeline(props) {
842
- var _a;
843
- if (!this.cachingEnabled) {
844
- return this.device.createComputePipeline(props);
845
- }
846
- const allProps = { ...import_core4.ComputePipeline.defaultProps, ...props };
847
- const cache = this._computePipelineCache;
848
- const hash = this._hashComputePipeline(allProps);
849
- let pipeline = (_a = cache[hash]) == null ? void 0 : _a.pipeline;
850
- if (!pipeline) {
851
- pipeline = this.device.createComputePipeline({
852
- ...allProps,
853
- id: allProps.id ? `${allProps.id}-cached` : void 0
854
- });
855
- pipeline.hash = hash;
856
- cache[hash] = { pipeline, useCount: 1 };
857
- if (this.debug) {
858
- import_core4.log.log(3, `${this}: ${pipeline} created, count=${cache[hash].useCount}`)();
859
- }
860
- } else {
861
- cache[hash].useCount++;
862
- if (this.debug) {
863
- import_core4.log.log(3, `${this}: ${cache[hash].pipeline} reused, count=${cache[hash].useCount}, (id=${props.id})`)();
864
- }
865
- }
866
- return pipeline;
867
- }
868
- release(pipeline) {
869
- if (!this.cachingEnabled) {
870
- pipeline.destroy();
871
- return;
872
- }
873
- const cache = this._getCache(pipeline);
874
- const hash = pipeline.hash;
875
- cache[hash].useCount--;
876
- if (cache[hash].useCount === 0) {
877
- this._destroyPipeline(pipeline);
878
- if (this.debug) {
879
- import_core4.log.log(3, `${this}: ${pipeline} released and destroyed`)();
880
- }
881
- } else if (cache[hash].useCount < 0) {
882
- import_core4.log.error(`${this}: ${pipeline} released, useCount < 0, resetting`)();
883
- cache[hash].useCount = 0;
884
- } else if (this.debug) {
885
- import_core4.log.log(3, `${this}: ${pipeline} released, count=${cache[hash].useCount}`)();
886
- }
887
- }
888
- // PRIVATE
889
- /** Destroy a cached pipeline, removing it from the cache (depending on destroy policy) */
890
- _destroyPipeline(pipeline) {
891
- const cache = this._getCache(pipeline);
892
- switch (this.destroyPolicy) {
893
- case "never":
894
- return false;
895
- case "unused":
896
- delete cache[pipeline.hash];
897
- pipeline.destroy();
898
- return true;
899
- }
900
- }
901
- /** Get the appropriate cache for the type of pipeline */
902
- _getCache(pipeline) {
903
- let cache;
904
- if (pipeline instanceof import_core4.ComputePipeline) {
905
- cache = this._computePipelineCache;
906
- }
907
- if (pipeline instanceof import_core4.RenderPipeline) {
908
- cache = this._renderPipelineCache;
909
- }
910
- if (!cache) {
911
- throw new Error(`${this}`);
912
- }
913
- if (!cache[pipeline.hash]) {
914
- throw new Error(`${this}: ${pipeline} matched incorrect entry`);
915
- }
916
- return cache;
917
- }
918
- /** Calculate a hash based on all the inputs for a compute pipeline */
919
- _hashComputePipeline(props) {
920
- const { type } = this.device;
921
- const shaderHash = this._getHash(props.shader.source);
922
- return `${type}/C/${shaderHash}`;
923
- }
924
- /** Calculate a hash based on all the inputs for a render pipeline */
925
- _hashRenderPipeline(props) {
926
- const vsHash = props.vs ? this._getHash(props.vs.source) : 0;
927
- const fsHash = props.fs ? this._getHash(props.fs.source) : 0;
928
- const varyingHash = "-";
929
- const bufferLayoutHash = this._getHash(JSON.stringify(props.bufferLayout));
930
- const { type } = this.device;
931
- switch (type) {
932
- case "webgl":
933
- return `${type}/R/${vsHash}/${fsHash}V${varyingHash}BL${bufferLayoutHash}`;
934
- case "webgpu":
935
- default:
936
- const parameterHash = this._getHash(JSON.stringify(props.parameters));
937
- return `${type}/R/${vsHash}/${fsHash}V${varyingHash}T${props.topology}P${parameterHash}BL${bufferLayoutHash}`;
938
- }
939
- }
940
- _getHash(key) {
941
- if (this._hashes[key] === void 0) {
942
- this._hashes[key] = this._hashCounter++;
943
- }
944
- return this._hashes[key];
945
- }
946
- };
947
- var PipelineFactory = _PipelineFactory;
948
- __publicField(PipelineFactory, "defaultProps", { ...import_core4.RenderPipeline.defaultProps });
949
-
950
- // dist/factories/shader-factory.js
951
- var import_core5 = require("@luma.gl/core");
952
- var _ShaderFactory = class {
953
- /** Returns the default ShaderFactory for the given {@link Device}, creating one if necessary. */
954
- static getDefaultShaderFactory(device) {
955
- device._lumaData["defaultShaderFactory"] ||= new _ShaderFactory(device);
956
- return device._lumaData["defaultShaderFactory"];
957
- }
958
- device;
959
- cachingEnabled;
960
- destroyPolicy;
961
- debug;
962
- _cache = {};
963
- get [Symbol.toStringTag]() {
964
- return "ShaderFactory";
965
- }
966
- toString() {
967
- return `${this[Symbol.toStringTag]}(${this.device.id})`;
968
- }
969
- /** @internal */
970
- constructor(device) {
971
- this.device = device;
972
- this.cachingEnabled = device.props._cacheShaders;
973
- this.destroyPolicy = device.props._cacheDestroyPolicy;
974
- this.debug = true;
975
- }
976
- /** Requests a {@link Shader} from the cache, creating a new Shader only if necessary. */
977
- createShader(props) {
978
- if (!this.cachingEnabled) {
979
- return this.device.createShader(props);
980
- }
981
- const key = this._hashShader(props);
982
- let cacheEntry = this._cache[key];
983
- if (!cacheEntry) {
984
- const shader = this.device.createShader({
985
- ...props,
986
- id: props.id ? `${props.id}-cached` : void 0
987
- });
988
- this._cache[key] = cacheEntry = { shader, useCount: 1 };
989
- if (this.debug) {
990
- import_core5.log.log(3, `${this}: Created new shader ${shader.id}`)();
991
- }
992
- } else {
993
- cacheEntry.useCount++;
994
- if (this.debug) {
995
- import_core5.log.log(3, `${this}: Reusing shader ${cacheEntry.shader.id} count=${cacheEntry.useCount}`)();
996
- }
997
- }
998
- return cacheEntry.shader;
999
- }
1000
- /** Releases a previously-requested {@link Shader}, destroying it if no users remain. */
1001
- release(shader) {
1002
- if (!this.cachingEnabled) {
1003
- shader.destroy();
1004
- return;
1005
- }
1006
- const key = this._hashShader(shader);
1007
- const cacheEntry = this._cache[key];
1008
- if (cacheEntry) {
1009
- cacheEntry.useCount--;
1010
- if (cacheEntry.useCount === 0) {
1011
- if (this.destroyPolicy === "unused") {
1012
- delete this._cache[key];
1013
- cacheEntry.shader.destroy();
1014
- if (this.debug) {
1015
- import_core5.log.log(3, `${this}: Releasing shader ${shader.id}, destroyed`)();
1016
- }
1017
- }
1018
- } else if (cacheEntry.useCount < 0) {
1019
- throw new Error(`ShaderFactory: Shader ${shader.id} released too many times`);
1020
- } else if (this.debug) {
1021
- import_core5.log.log(3, `${this}: Releasing shader ${shader.id} count=${cacheEntry.useCount}`)();
1022
- }
1023
- }
1024
- }
1025
- // PRIVATE
1026
- _hashShader(value) {
1027
- return `${value.stage}:${value.source}`;
1028
- }
1029
- };
1030
- var ShaderFactory = _ShaderFactory;
1031
- __publicField(ShaderFactory, "defaultProps", { ...import_core5.Shader.defaultProps });
1032
-
1033
875
  // dist/debug/debug-shader-layout.js
1034
876
  function getDebugTableForShaderLayout(layout, name) {
1035
877
  var _a;
@@ -1130,7 +972,7 @@ function deepEqual(a, b, depth) {
1130
972
  }
1131
973
 
1132
974
  // dist/utils/buffer-layout-helper.js
1133
- var import_core6 = require("@luma.gl/core");
975
+ var import_core4 = require("@luma.gl/core");
1134
976
  var BufferLayoutHelper = class {
1135
977
  bufferLayouts;
1136
978
  constructor(bufferLayouts) {
@@ -1159,28 +1001,82 @@ var BufferLayoutHelper = class {
1159
1001
  getBufferIndex(bufferName) {
1160
1002
  const bufferIndex = this.bufferLayouts.findIndex((layout) => layout.name === bufferName);
1161
1003
  if (bufferIndex === -1) {
1162
- import_core6.log.warn(`BufferLayout: Missing buffer for "${bufferName}".`)();
1004
+ import_core4.log.warn(`BufferLayout: Missing buffer for "${bufferName}".`)();
1163
1005
  }
1164
1006
  return bufferIndex;
1165
1007
  }
1166
1008
  };
1167
1009
 
1168
1010
  // dist/utils/buffer-layout-order.js
1011
+ function getMinLocation(attributeNames, shaderLayoutMap) {
1012
+ let minLocation = Infinity;
1013
+ for (const name of attributeNames) {
1014
+ const location = shaderLayoutMap[name];
1015
+ if (location !== void 0) {
1016
+ minLocation = Math.min(minLocation, location);
1017
+ }
1018
+ }
1019
+ return minLocation;
1020
+ }
1169
1021
  function sortedBufferLayoutByShaderSourceLocations(shaderLayout, bufferLayout) {
1170
1022
  const shaderLayoutMap = Object.fromEntries(shaderLayout.attributes.map((attr) => [attr.name, attr.location]));
1171
1023
  const sortedLayout = bufferLayout.slice();
1172
1024
  sortedLayout.sort((a, b) => {
1173
1025
  const attributeNamesA = a.attributes ? a.attributes.map((attr) => attr.attribute) : [a.name];
1174
1026
  const attributeNamesB = b.attributes ? b.attributes.map((attr) => attr.attribute) : [b.name];
1175
- const minLocationA = Math.min(...attributeNamesA.map((name) => shaderLayoutMap[name]));
1176
- const minLocationB = Math.min(...attributeNamesB.map((name) => shaderLayoutMap[name]));
1027
+ const minLocationA = getMinLocation(attributeNamesA, shaderLayoutMap);
1028
+ const minLocationB = getMinLocation(attributeNamesB, shaderLayoutMap);
1177
1029
  return minLocationA - minLocationB;
1178
1030
  });
1179
1031
  return sortedLayout;
1180
1032
  }
1181
1033
 
1034
+ // dist/utils/shader-module-utils.js
1035
+ function mergeShaderModuleBindingsIntoLayout(shaderLayout, modules) {
1036
+ if (!shaderLayout || !modules.some((module2) => {
1037
+ var _a;
1038
+ return (_a = module2.bindingLayout) == null ? void 0 : _a.length;
1039
+ })) {
1040
+ return shaderLayout;
1041
+ }
1042
+ const mergedLayout = {
1043
+ ...shaderLayout,
1044
+ bindings: shaderLayout.bindings.map((binding) => ({ ...binding }))
1045
+ };
1046
+ if ("attributes" in (shaderLayout || {})) {
1047
+ mergedLayout.attributes = (shaderLayout == null ? void 0 : shaderLayout.attributes) || [];
1048
+ }
1049
+ for (const module2 of modules) {
1050
+ for (const bindingLayout of module2.bindingLayout || []) {
1051
+ for (const relatedBindingName of getRelatedBindingNames(bindingLayout.name)) {
1052
+ const binding = mergedLayout.bindings.find((candidate) => candidate.name === relatedBindingName);
1053
+ if ((binding == null ? void 0 : binding.group) === 0) {
1054
+ binding.group = bindingLayout.group;
1055
+ }
1056
+ }
1057
+ }
1058
+ }
1059
+ return mergedLayout;
1060
+ }
1061
+ function shaderModuleHasUniforms(module2) {
1062
+ return Boolean(module2.uniformTypes && !isObjectEmpty(module2.uniformTypes));
1063
+ }
1064
+ function getRelatedBindingNames(bindingName) {
1065
+ const bindingNames = /* @__PURE__ */ new Set([bindingName, `${bindingName}Uniforms`]);
1066
+ if (!bindingName.endsWith("Uniforms")) {
1067
+ bindingNames.add(`${bindingName}Sampler`);
1068
+ }
1069
+ return [...bindingNames];
1070
+ }
1071
+ function isObjectEmpty(obj) {
1072
+ for (const key in obj) {
1073
+ return false;
1074
+ }
1075
+ return true;
1076
+ }
1077
+
1182
1078
  // dist/shader-inputs.js
1183
- var import_core7 = require("@luma.gl/core");
1079
+ var import_core5 = require("@luma.gl/core");
1184
1080
  var import_shadertools = require("@luma.gl/shadertools");
1185
1081
 
1186
1082
  // dist/model/split-uniforms-and-bindings.js
@@ -1188,11 +1084,11 @@ var import_types = require("@math.gl/types");
1188
1084
  function isUniformValue(value) {
1189
1085
  return (0, import_types.isNumericArray)(value) || typeof value === "number" || typeof value === "boolean";
1190
1086
  }
1191
- function splitUniformsAndBindings(uniforms) {
1087
+ function splitUniformsAndBindings(uniforms, uniformTypes2 = {}) {
1192
1088
  const result = { bindings: {}, uniforms: {} };
1193
1089
  Object.keys(uniforms).forEach((name) => {
1194
1090
  const uniform = uniforms[name];
1195
- if (isUniformValue(uniform)) {
1091
+ if (Object.prototype.hasOwnProperty.call(uniformTypes2, name) || isUniformValue(uniform)) {
1196
1092
  result.uniforms[name] = uniform;
1197
1093
  } else {
1198
1094
  result.bindings[name] = uniform;
@@ -1224,18 +1120,21 @@ var ShaderInputs = class {
1224
1120
  */
1225
1121
  constructor(modules, options) {
1226
1122
  Object.assign(this.options, options);
1227
- const resolvedModules = (0, import_shadertools.getShaderModuleDependencies)(Object.values(modules).filter((module2) => module2.dependencies));
1123
+ const resolvedModules = (0, import_shadertools.getShaderModuleDependencies)(Object.values(modules).filter(isShaderInputsModuleWithDependencies));
1228
1124
  for (const resolvedModule of resolvedModules) {
1229
1125
  modules[resolvedModule.name] = resolvedModule;
1230
1126
  }
1231
- import_core7.log.log(1, "Creating ShaderInputs with modules", Object.keys(modules))();
1127
+ import_core5.log.log(1, "Creating ShaderInputs with modules", Object.keys(modules))();
1232
1128
  this.modules = modules;
1233
1129
  this.moduleUniforms = {};
1234
1130
  this.moduleBindings = {};
1235
1131
  for (const [name, module2] of Object.entries(modules)) {
1132
+ if (!module2) {
1133
+ continue;
1134
+ }
1236
1135
  this._addModule(module2);
1237
1136
  if (module2.name && name !== module2.name && !this.options.disableWarnings) {
1238
- import_core7.log.warn(`Module name: ${name} vs ${module2.name}`)();
1137
+ import_core5.log.warn(`Module name: ${name} vs ${module2.name}`)();
1239
1138
  }
1240
1139
  }
1241
1140
  }
@@ -1253,15 +1152,15 @@ var ShaderInputs = class {
1253
1152
  const module2 = this.modules[moduleName];
1254
1153
  if (!module2) {
1255
1154
  if (!this.options.disableWarnings) {
1256
- import_core7.log.warn(`Module ${name} not found`)();
1155
+ import_core5.log.warn(`Module ${name} not found`)();
1257
1156
  }
1258
1157
  continue;
1259
1158
  }
1260
1159
  const oldUniforms = this.moduleUniforms[moduleName];
1261
1160
  const oldBindings = this.moduleBindings[moduleName];
1262
1161
  const uniformsAndBindings = ((_a = module2.getUniforms) == null ? void 0 : _a.call(module2, moduleProps, oldUniforms)) || moduleProps;
1263
- const { uniforms, bindings } = splitUniformsAndBindings(uniformsAndBindings);
1264
- this.moduleUniforms[moduleName] = { ...oldUniforms, ...uniforms };
1162
+ const { uniforms, bindings } = splitUniformsAndBindings(uniformsAndBindings, module2.uniformTypes);
1163
+ this.moduleUniforms[moduleName] = mergeModuleUniforms(oldUniforms, uniforms, module2.uniformTypes);
1265
1164
  this.moduleBindings[moduleName] = { ...oldBindings, ...bindings };
1266
1165
  }
1267
1166
  }
@@ -1301,54 +1200,260 @@ var ShaderInputs = class {
1301
1200
  }
1302
1201
  _addModule(module2) {
1303
1202
  const moduleName = module2.name;
1304
- this.moduleUniforms[moduleName] = module2.defaultUniforms || {};
1203
+ this.moduleUniforms[moduleName] = mergeModuleUniforms({}, module2.defaultUniforms || {}, module2.uniformTypes);
1305
1204
  this.moduleBindings[moduleName] = {};
1306
1205
  }
1307
1206
  };
1207
+ function mergeModuleUniforms(currentUniforms = {}, nextUniforms = {}, uniformTypes2 = {}) {
1208
+ const mergedUniforms = { ...currentUniforms };
1209
+ for (const [key, value] of Object.entries(nextUniforms)) {
1210
+ if (value === void 0) {
1211
+ continue;
1212
+ }
1213
+ mergedUniforms[key] = mergeModuleUniformValue(currentUniforms[key], value, uniformTypes2[key]);
1214
+ }
1215
+ return mergedUniforms;
1216
+ }
1217
+ function mergeModuleUniformValue(currentValue, nextValue, uniformType) {
1218
+ if (!uniformType || typeof uniformType === "string") {
1219
+ return cloneModuleUniformValue(nextValue);
1220
+ }
1221
+ if (Array.isArray(uniformType)) {
1222
+ if (isPackedUniformArrayValue(nextValue) || !Array.isArray(nextValue)) {
1223
+ return cloneModuleUniformValue(nextValue);
1224
+ }
1225
+ const currentArray = Array.isArray(currentValue) && !isPackedUniformArrayValue(currentValue) ? [...currentValue] : [];
1226
+ const mergedArray = currentArray.slice();
1227
+ for (let index = 0; index < nextValue.length; index++) {
1228
+ const elementValue = nextValue[index];
1229
+ if (elementValue === void 0) {
1230
+ continue;
1231
+ }
1232
+ mergedArray[index] = mergeModuleUniformValue(currentArray[index], elementValue, uniformType[0]);
1233
+ }
1234
+ return mergedArray;
1235
+ }
1236
+ if (!isPlainUniformObject(nextValue)) {
1237
+ return cloneModuleUniformValue(nextValue);
1238
+ }
1239
+ const uniformStruct = uniformType;
1240
+ const currentObject = isPlainUniformObject(currentValue) ? currentValue : {};
1241
+ const mergedObject = { ...currentObject };
1242
+ for (const [key, value] of Object.entries(nextValue)) {
1243
+ if (value === void 0) {
1244
+ continue;
1245
+ }
1246
+ mergedObject[key] = mergeModuleUniformValue(currentObject[key], value, uniformStruct[key]);
1247
+ }
1248
+ return mergedObject;
1249
+ }
1250
+ function cloneModuleUniformValue(value) {
1251
+ if (ArrayBuffer.isView(value)) {
1252
+ return Array.prototype.slice.call(value);
1253
+ }
1254
+ if (Array.isArray(value)) {
1255
+ if (isPackedUniformArrayValue(value)) {
1256
+ return value.slice();
1257
+ }
1258
+ const compositeArray = value;
1259
+ return compositeArray.map((element) => element === void 0 ? void 0 : cloneModuleUniformValue(element));
1260
+ }
1261
+ if (isPlainUniformObject(value)) {
1262
+ return Object.fromEntries(Object.entries(value).map(([key, nestedValue]) => [
1263
+ key,
1264
+ nestedValue === void 0 ? void 0 : cloneModuleUniformValue(nestedValue)
1265
+ ]));
1266
+ }
1267
+ return value;
1268
+ }
1269
+ function isPackedUniformArrayValue(value) {
1270
+ return ArrayBuffer.isView(value) || Array.isArray(value) && (value.length === 0 || typeof value[0] === "number");
1271
+ }
1272
+ function isPlainUniformObject(value) {
1273
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value) && !ArrayBuffer.isView(value);
1274
+ }
1275
+ function isShaderInputsModuleWithDependencies(module2) {
1276
+ return Boolean(module2 == null ? void 0 : module2.dependencies);
1277
+ }
1308
1278
 
1309
- // dist/async-texture/async-texture.js
1310
- var import_core8 = require("@luma.gl/core");
1279
+ // dist/dynamic-texture/dynamic-texture.js
1280
+ var import_core7 = require("@luma.gl/core");
1311
1281
 
1312
- // dist/application-utils/load-file.js
1313
- var pathPrefix = "";
1314
- function setPathPrefix(prefix) {
1315
- pathPrefix = prefix;
1282
+ // dist/dynamic-texture/texture-data.js
1283
+ var import_core6 = require("@luma.gl/core");
1284
+ var TEXTURE_CUBE_FACE_MAP = { "+X": 0, "-X": 1, "+Y": 2, "-Y": 3, "+Z": 4, "-Z": 5 };
1285
+ function getFirstMipLevel(layer) {
1286
+ if (!layer)
1287
+ return null;
1288
+ return Array.isArray(layer) ? layer[0] ?? null : layer;
1316
1289
  }
1317
- async function loadImageBitmap(url, opts) {
1318
- const image = new Image();
1319
- image.crossOrigin = (opts == null ? void 0 : opts.crossOrigin) || "anonymous";
1320
- image.src = url.startsWith("http") ? url : pathPrefix + url;
1321
- await image.decode();
1322
- return opts ? await createImageBitmap(image, opts) : await createImageBitmap(image);
1290
+ function getTextureSizeFromData(props) {
1291
+ const { dimension, data } = props;
1292
+ if (!data) {
1293
+ return null;
1294
+ }
1295
+ switch (dimension) {
1296
+ case "1d": {
1297
+ const mipLevel = getFirstMipLevel(data);
1298
+ if (!mipLevel)
1299
+ return null;
1300
+ const { width } = getTextureMipLevelSize(mipLevel);
1301
+ return { width, height: 1 };
1302
+ }
1303
+ case "2d": {
1304
+ const mipLevel = getFirstMipLevel(data);
1305
+ return mipLevel ? getTextureMipLevelSize(mipLevel) : null;
1306
+ }
1307
+ case "3d":
1308
+ case "2d-array": {
1309
+ if (!Array.isArray(data) || data.length === 0)
1310
+ return null;
1311
+ const mipLevel = getFirstMipLevel(data[0]);
1312
+ return mipLevel ? getTextureMipLevelSize(mipLevel) : null;
1313
+ }
1314
+ case "cube": {
1315
+ const face = Object.keys(data)[0] ?? null;
1316
+ if (!face)
1317
+ return null;
1318
+ const faceData = data[face];
1319
+ const mipLevel = getFirstMipLevel(faceData);
1320
+ return mipLevel ? getTextureMipLevelSize(mipLevel) : null;
1321
+ }
1322
+ case "cube-array": {
1323
+ if (!Array.isArray(data) || data.length === 0)
1324
+ return null;
1325
+ const firstCube = data[0];
1326
+ const face = Object.keys(firstCube)[0] ?? null;
1327
+ if (!face)
1328
+ return null;
1329
+ const mipLevel = getFirstMipLevel(firstCube[face]);
1330
+ return mipLevel ? getTextureMipLevelSize(mipLevel) : null;
1331
+ }
1332
+ default:
1333
+ return null;
1334
+ }
1323
1335
  }
1324
- async function loadImage(url, opts) {
1325
- return await new Promise((resolve, reject) => {
1326
- try {
1327
- const image = new Image();
1328
- image.onload = () => resolve(image);
1329
- image.onerror = () => reject(new Error(`Could not load image ${url}.`));
1330
- image.crossOrigin = (opts == null ? void 0 : opts.crossOrigin) || "anonymous";
1331
- image.src = url.startsWith("http") ? url : pathPrefix + url;
1332
- } catch (error) {
1333
- reject(error);
1336
+ function getTextureMipLevelSize(data) {
1337
+ if ((0, import_core6.isExternalImage)(data)) {
1338
+ return (0, import_core6.getExternalImageSize)(data);
1339
+ }
1340
+ if (typeof data === "object" && "width" in data && "height" in data) {
1341
+ return { width: data.width, height: data.height };
1342
+ }
1343
+ throw new Error("Unsupported mip-level data");
1344
+ }
1345
+ function isTextureImageData(data) {
1346
+ return typeof data === "object" && data !== null && "data" in data && "width" in data && "height" in data;
1347
+ }
1348
+ function isTypedArrayMipLevelData(data) {
1349
+ return ArrayBuffer.isView(data);
1350
+ }
1351
+ function resolveTextureImageFormat(data) {
1352
+ const { textureFormat, format } = data;
1353
+ if (textureFormat && format && textureFormat !== format) {
1354
+ throw new Error(`Conflicting texture formats "${textureFormat}" and "${format}" provided for the same mip level`);
1355
+ }
1356
+ return textureFormat ?? format;
1357
+ }
1358
+ function getCubeFaceIndex(face) {
1359
+ const idx = TEXTURE_CUBE_FACE_MAP[face];
1360
+ if (idx === void 0)
1361
+ throw new Error(`Invalid cube face: ${face}`);
1362
+ return idx;
1363
+ }
1364
+ function getCubeArrayFaceIndex(cubeIndex, face) {
1365
+ return 6 * cubeIndex + getCubeFaceIndex(face);
1366
+ }
1367
+ function getTexture1DSubresources(data) {
1368
+ throw new Error("setTexture1DData not supported in WebGL.");
1369
+ }
1370
+ function _normalizeTexture2DData(data) {
1371
+ return Array.isArray(data) ? data : [data];
1372
+ }
1373
+ function getTexture2DSubresources(slice, lodData, baseLevelSize, textureFormat) {
1374
+ const lodArray = _normalizeTexture2DData(lodData);
1375
+ const z = slice;
1376
+ const subresources = [];
1377
+ for (let mipLevel = 0; mipLevel < lodArray.length; mipLevel++) {
1378
+ const imageData = lodArray[mipLevel];
1379
+ if ((0, import_core6.isExternalImage)(imageData)) {
1380
+ subresources.push({
1381
+ type: "external-image",
1382
+ image: imageData,
1383
+ z,
1384
+ mipLevel
1385
+ });
1386
+ } else if (isTextureImageData(imageData)) {
1387
+ subresources.push({
1388
+ type: "texture-data",
1389
+ data: imageData,
1390
+ textureFormat: resolveTextureImageFormat(imageData),
1391
+ z,
1392
+ mipLevel
1393
+ });
1394
+ } else if (isTypedArrayMipLevelData(imageData) && baseLevelSize) {
1395
+ subresources.push({
1396
+ type: "texture-data",
1397
+ data: {
1398
+ data: imageData,
1399
+ width: Math.max(1, baseLevelSize.width >> mipLevel),
1400
+ height: Math.max(1, baseLevelSize.height >> mipLevel),
1401
+ ...textureFormat ? { format: textureFormat } : {}
1402
+ },
1403
+ textureFormat,
1404
+ z,
1405
+ mipLevel
1406
+ });
1407
+ } else {
1408
+ throw new Error("Unsupported 2D mip-level payload");
1409
+ }
1410
+ }
1411
+ return subresources;
1412
+ }
1413
+ function getTexture3DSubresources(data) {
1414
+ const subresources = [];
1415
+ for (let depth = 0; depth < data.length; depth++) {
1416
+ subresources.push(...getTexture2DSubresources(depth, data[depth]));
1417
+ }
1418
+ return subresources;
1419
+ }
1420
+ function getTextureArraySubresources(data) {
1421
+ const subresources = [];
1422
+ for (let layer = 0; layer < data.length; layer++) {
1423
+ subresources.push(...getTexture2DSubresources(layer, data[layer]));
1424
+ }
1425
+ return subresources;
1426
+ }
1427
+ function getTextureCubeSubresources(data) {
1428
+ const subresources = [];
1429
+ for (const [face, faceData] of Object.entries(data)) {
1430
+ const faceDepth = getCubeFaceIndex(face);
1431
+ subresources.push(...getTexture2DSubresources(faceDepth, faceData));
1432
+ }
1433
+ return subresources;
1434
+ }
1435
+ function getTextureCubeArraySubresources(data) {
1436
+ const subresources = [];
1437
+ data.forEach((cubeData, cubeIndex) => {
1438
+ for (const [face, faceData] of Object.entries(cubeData)) {
1439
+ const faceDepth = getCubeArrayFaceIndex(cubeIndex, face);
1440
+ subresources.push(...getTexture2DSubresources(faceDepth, faceData));
1334
1441
  }
1335
1442
  });
1443
+ return subresources;
1336
1444
  }
1337
1445
 
1338
- // dist/async-texture/async-texture.js
1339
- var TextureCubeFaces = ["+X", "-X", "+Y", "-Y", "+Z", "-Z"];
1340
- var CubeFaces = ["+X", "-X", "+Y", "-Y", "+Z", "-Z"];
1341
- var _AsyncTexture = class {
1446
+ // dist/dynamic-texture/dynamic-texture.js
1447
+ var _DynamicTexture = class {
1342
1448
  device;
1343
1449
  id;
1450
+ /** Props with defaults resolved (except `data` which is processed separately) */
1344
1451
  props;
1345
- // TODO - should we type these as possibly `null`? It will make usage harder?
1346
- // @ts-expect-error
1347
- texture;
1348
- // @ts-expect-error
1349
- sampler;
1350
- // @ts-expect-error
1351
- view;
1452
+ /** Created resources */
1453
+ _texture = null;
1454
+ _sampler = null;
1455
+ _view = null;
1456
+ /** Ready when GPU texture has been created and data (if any) uploaded */
1352
1457
  ready;
1353
1458
  isReady = false;
1354
1459
  destroyed = false;
@@ -1356,272 +1461,437 @@ var _AsyncTexture = class {
1356
1461
  };
1357
1462
  rejectReady = () => {
1358
1463
  };
1464
+ get texture() {
1465
+ if (!this._texture)
1466
+ throw new Error("Texture not initialized yet");
1467
+ return this._texture;
1468
+ }
1469
+ get sampler() {
1470
+ if (!this._sampler)
1471
+ throw new Error("Sampler not initialized yet");
1472
+ return this._sampler;
1473
+ }
1474
+ get view() {
1475
+ if (!this._view)
1476
+ throw new Error("View not initialized yet");
1477
+ return this._view;
1478
+ }
1359
1479
  get [Symbol.toStringTag]() {
1360
- return "AsyncTexture";
1480
+ return "DynamicTexture";
1361
1481
  }
1362
1482
  toString() {
1363
- return `AsyncTexture:"${this.id}"(${this.isReady ? "ready" : "loading"})`;
1483
+ return `DynamicTexture:"${this.id}":${this.texture.width}x${this.texture.height}px:(${this.isReady ? "ready" : "loading..."})`;
1364
1484
  }
1365
1485
  constructor(device, props) {
1366
1486
  this.device = device;
1367
- const id = uid("async-texture");
1368
- this.props = { ..._AsyncTexture.defaultProps, id, ...props };
1487
+ const id = uid("dynamic-texture");
1488
+ const originalPropsWithAsyncData = props;
1489
+ this.props = { ..._DynamicTexture.defaultProps, id, ...props, data: null };
1369
1490
  this.id = this.props.id;
1370
- props = { ...props };
1371
- if (typeof (props == null ? void 0 : props.data) === "string" && props.dimension === "2d") {
1372
- props.data = loadImageBitmap(props.data);
1373
- }
1374
- if (props.mipmaps) {
1375
- props.mipLevels = "auto";
1376
- }
1377
1491
  this.ready = new Promise((resolve, reject) => {
1378
- this.resolveReady = () => {
1379
- this.isReady = true;
1380
- resolve();
1381
- };
1492
+ this.resolveReady = resolve;
1382
1493
  this.rejectReady = reject;
1383
1494
  });
1384
- this.initAsync(props);
1495
+ this.initAsync(originalPropsWithAsyncData);
1385
1496
  }
1386
- async initAsync(props) {
1387
- const asyncData = props.data;
1388
- const data = await awaitAllPromises(asyncData).then(void 0, this.rejectReady);
1389
- if (this.destroyed) {
1390
- return;
1391
- }
1392
- const size = this.props.width && this.props.height ? { width: this.props.width, height: this.props.height } : this.getTextureDataSize(data);
1393
- if (!size) {
1394
- throw new Error("Texture size could not be determined");
1395
- }
1396
- const syncProps = { ...size, ...props, data: void 0, mipLevels: 1 };
1397
- const maxMips = this.device.getMipLevelCount(syncProps.width, syncProps.height);
1398
- syncProps.mipLevels = this.props.mipLevels === "auto" ? maxMips : Math.min(maxMips, this.props.mipLevels);
1399
- this.texture = this.device.createTexture(syncProps);
1400
- this.sampler = this.texture.sampler;
1401
- this.view = this.texture.view;
1402
- if (props.data) {
1403
- switch (this.props.dimension) {
1404
- case "1d":
1405
- this._setTexture1DData(this.texture, data);
1406
- break;
1407
- case "2d":
1408
- this._setTexture2DData(data);
1409
- break;
1410
- case "3d":
1411
- this._setTexture3DData(this.texture, data);
1412
- break;
1413
- case "2d-array":
1414
- this._setTextureArrayData(this.texture, data);
1415
- break;
1416
- case "cube":
1417
- this._setTextureCubeData(this.texture, data);
1418
- break;
1419
- case "cube-array":
1420
- this._setTextureCubeArrayData(this.texture, data);
1421
- break;
1497
+ /** @note Fire and forget; caller can await `ready` */
1498
+ async initAsync(originalPropsWithAsyncData) {
1499
+ try {
1500
+ const propsWithSyncData = await this._loadAllData(originalPropsWithAsyncData);
1501
+ this._checkNotDestroyed();
1502
+ const subresources = propsWithSyncData.data ? getTextureSubresources({
1503
+ ...propsWithSyncData,
1504
+ width: originalPropsWithAsyncData.width,
1505
+ height: originalPropsWithAsyncData.height,
1506
+ format: originalPropsWithAsyncData.format
1507
+ }) : [];
1508
+ const userProvidedFormat = "format" in originalPropsWithAsyncData && originalPropsWithAsyncData.format !== void 0;
1509
+ const userProvidedUsage = "usage" in originalPropsWithAsyncData && originalPropsWithAsyncData.usage !== void 0;
1510
+ const deduceSize = () => {
1511
+ if (this.props.width && this.props.height) {
1512
+ return { width: this.props.width, height: this.props.height };
1513
+ }
1514
+ const size2 = getTextureSizeFromData(propsWithSyncData);
1515
+ if (size2) {
1516
+ return size2;
1517
+ }
1518
+ return { width: this.props.width || 1, height: this.props.height || 1 };
1519
+ };
1520
+ const size = deduceSize();
1521
+ if (!size || size.width <= 0 || size.height <= 0) {
1522
+ throw new Error(`${this} size could not be determined or was zero`);
1422
1523
  }
1524
+ const textureData = analyzeTextureSubresources(this.device, subresources, size, {
1525
+ format: userProvidedFormat ? originalPropsWithAsyncData.format : void 0
1526
+ });
1527
+ const resolvedFormat = textureData.format ?? this.props.format;
1528
+ const baseTextureProps = {
1529
+ ...this.props,
1530
+ ...size,
1531
+ format: resolvedFormat,
1532
+ mipLevels: 1,
1533
+ // temporary; updated below
1534
+ data: void 0
1535
+ };
1536
+ if (this.device.isTextureFormatCompressed(resolvedFormat) && !userProvidedUsage) {
1537
+ baseTextureProps.usage = import_core7.Texture.SAMPLE | import_core7.Texture.COPY_DST;
1538
+ }
1539
+ const shouldGenerateMipmaps = this.props.mipmaps && !textureData.hasExplicitMipChain && !this.device.isTextureFormatCompressed(resolvedFormat);
1540
+ if (this.device.type === "webgpu" && shouldGenerateMipmaps) {
1541
+ const requiredUsage = this.props.dimension === "3d" ? import_core7.Texture.SAMPLE | import_core7.Texture.STORAGE | import_core7.Texture.COPY_DST | import_core7.Texture.COPY_SRC : import_core7.Texture.SAMPLE | import_core7.Texture.RENDER | import_core7.Texture.COPY_DST | import_core7.Texture.COPY_SRC;
1542
+ baseTextureProps.usage |= requiredUsage;
1543
+ }
1544
+ const maxMips = this.device.getMipLevelCount(baseTextureProps.width, baseTextureProps.height);
1545
+ const desired = textureData.hasExplicitMipChain ? textureData.mipLevels : this.props.mipLevels === "auto" ? maxMips : Math.max(1, Math.min(maxMips, this.props.mipLevels ?? 1));
1546
+ const finalTextureProps = { ...baseTextureProps, mipLevels: desired };
1547
+ this._texture = this.device.createTexture(finalTextureProps);
1548
+ this._sampler = this.texture.sampler;
1549
+ this._view = this.texture.view;
1550
+ if (textureData.subresources.length) {
1551
+ this._setTextureSubresources(textureData.subresources);
1552
+ }
1553
+ if (this.props.mipmaps && !textureData.hasExplicitMipChain && !shouldGenerateMipmaps) {
1554
+ import_core7.log.warn(`${this} skipping auto-generated mipmaps for compressed texture format`)();
1555
+ }
1556
+ if (shouldGenerateMipmaps) {
1557
+ this.generateMipmaps();
1558
+ }
1559
+ this.isReady = true;
1560
+ this.resolveReady(this.texture);
1561
+ import_core7.log.info(0, `${this} created`)();
1562
+ } catch (e) {
1563
+ const err = e instanceof Error ? e : new Error(String(e));
1564
+ this.rejectReady(err);
1423
1565
  }
1424
- if (this.props.mipmaps) {
1425
- this.generateMipmaps();
1426
- }
1427
- import_core8.log.info(1, `${this} loaded`);
1428
- this.resolveReady();
1429
1566
  }
1430
1567
  destroy() {
1431
- if (this.texture) {
1432
- this.texture.destroy();
1433
- this.texture = null;
1568
+ if (this._texture) {
1569
+ this._texture.destroy();
1570
+ this._texture = null;
1571
+ this._sampler = null;
1572
+ this._view = null;
1434
1573
  }
1435
1574
  this.destroyed = true;
1436
1575
  }
1437
1576
  generateMipmaps() {
1438
- this.texture.generateMipmapsWebGL();
1577
+ if (this.device.type === "webgl") {
1578
+ this.texture.generateMipmapsWebGL();
1579
+ } else if (this.device.type === "webgpu") {
1580
+ this.device.generateMipmapsWebGPU(this.texture);
1581
+ } else {
1582
+ import_core7.log.warn(`${this} mipmaps not supported on ${this.device.type}`);
1583
+ }
1439
1584
  }
1440
- /** Set sampler or create and set new Sampler from SamplerProps */
1585
+ /** Set sampler or create one from props */
1441
1586
  setSampler(sampler = {}) {
1442
- this.texture.setSampler(sampler instanceof import_core8.Sampler ? sampler : this.device.createSampler(sampler));
1587
+ this._checkReady();
1588
+ const s = sampler instanceof import_core7.Sampler ? sampler : this.device.createSampler(sampler);
1589
+ this.texture.setSampler(s);
1590
+ this._sampler = s;
1443
1591
  }
1444
1592
  /**
1445
- * Textures are immutable and cannot be resized after creation,
1446
- * but we can create a similar texture with the same parameters but a new size.
1447
- * @note Does not copy contents of the texture
1448
- * @note Mipmaps may need to be regenerated after resizing / setting new data
1449
- * @todo Abort pending promise and create a texture with the new size?
1593
+ * Copies texture contents into a GPU buffer and waits until the copy is complete.
1594
+ * The caller owns the returned buffer and must destroy it when finished.
1450
1595
  */
1451
- resize(size) {
1596
+ async readBuffer(options = {}) {
1597
+ if (!this.isReady) {
1598
+ await this.ready;
1599
+ }
1600
+ const width = options.width ?? this.texture.width;
1601
+ const height = options.height ?? this.texture.height;
1602
+ const depthOrArrayLayers = options.depthOrArrayLayers ?? this.texture.depth;
1603
+ const layout = this.texture.computeMemoryLayout({ width, height, depthOrArrayLayers });
1604
+ const buffer = this.device.createBuffer({
1605
+ byteLength: layout.byteLength,
1606
+ usage: import_core7.Buffer.COPY_DST | import_core7.Buffer.MAP_READ
1607
+ });
1608
+ this.texture.readBuffer({
1609
+ ...options,
1610
+ width,
1611
+ height,
1612
+ depthOrArrayLayers
1613
+ }, buffer);
1614
+ const fence = this.device.createFence();
1615
+ await fence.signaled;
1616
+ fence.destroy();
1617
+ return buffer;
1618
+ }
1619
+ /** Reads texture contents back to CPU memory. */
1620
+ async readAsync(options = {}) {
1452
1621
  if (!this.isReady) {
1453
- throw new Error("Cannot resize texture before it is ready");
1622
+ await this.ready;
1454
1623
  }
1624
+ const width = options.width ?? this.texture.width;
1625
+ const height = options.height ?? this.texture.height;
1626
+ const depthOrArrayLayers = options.depthOrArrayLayers ?? this.texture.depth;
1627
+ const layout = this.texture.computeMemoryLayout({ width, height, depthOrArrayLayers });
1628
+ const buffer = await this.readBuffer(options);
1629
+ const data = await buffer.readAsync(0, layout.byteLength);
1630
+ buffer.destroy();
1631
+ return data.buffer;
1632
+ }
1633
+ /**
1634
+ * Resize by cloning the underlying immutable texture.
1635
+ * Does not copy contents; caller may need to re-upload and/or regenerate mips.
1636
+ */
1637
+ resize(size) {
1638
+ this._checkReady();
1455
1639
  if (size.width === this.texture.width && size.height === this.texture.height) {
1456
1640
  return false;
1457
1641
  }
1458
- if (this.texture) {
1459
- const texture = this.texture;
1460
- this.texture = texture.clone(size);
1461
- texture.destroy();
1462
- }
1642
+ const prev = this.texture;
1643
+ this._texture = prev.clone(size);
1644
+ this._sampler = this.texture.sampler;
1645
+ this._view = this.texture.view;
1646
+ prev.destroy();
1647
+ import_core7.log.info(`${this} resized`);
1463
1648
  return true;
1464
1649
  }
1465
- /** Check if texture data is a typed array */
1466
- isTextureLevelData(data) {
1467
- const typedArray = data == null ? void 0 : data.data;
1468
- return ArrayBuffer.isView(typedArray);
1469
- }
1470
- /** Get the size of the texture described by the provided TextureData */
1471
- getTextureDataSize(data) {
1472
- if (!data) {
1473
- return null;
1474
- }
1475
- if (ArrayBuffer.isView(data)) {
1476
- return null;
1477
- }
1478
- if (Array.isArray(data)) {
1479
- return this.getTextureDataSize(data[0]);
1480
- }
1481
- if (this.device.isExternalImage(data)) {
1482
- return this.device.getExternalImageSize(data);
1483
- }
1484
- if (data && typeof data === "object" && data.constructor === Object) {
1485
- const textureDataArray = Object.values(data);
1486
- const untypedData = textureDataArray[0];
1487
- return { width: untypedData.width, height: untypedData.height };
1488
- }
1489
- throw new Error("texture size deduction failed");
1490
- }
1491
- /** Convert luma.gl cubemap face constants to depth index */
1492
- getCubeFaceDepth(face) {
1493
- switch (face) {
1494
- case "+X":
1495
- return 0;
1496
- case "-X":
1497
- return 1;
1498
- case "+Y":
1499
- return 2;
1500
- case "-Y":
1501
- return 3;
1502
- case "+Z":
1503
- return 4;
1504
- case "-Z":
1505
- return 5;
1506
- default:
1507
- throw new Error(face);
1508
- }
1509
- }
1510
- // EXPERIMENTAL
1511
- setTextureData(data) {
1512
- }
1513
- /** Experimental: Set multiple mip levels */
1514
- _setTexture1DData(texture, data) {
1515
- throw new Error("setTexture1DData not supported in WebGL.");
1516
- }
1517
- /** Experimental: Set multiple mip levels */
1518
- _setTexture2DData(lodData, depth = 0) {
1519
- if (!this.texture) {
1520
- throw new Error("Texture not initialized");
1521
- }
1522
- const lodArray = this._normalizeTextureData(lodData);
1523
- if (lodArray.length > 1 && this.props.mipmaps !== false) {
1524
- import_core8.log.warn(`Texture ${this.id} mipmap and multiple LODs.`)();
1525
- }
1526
- for (let mipLevel = 0; mipLevel < lodArray.length; mipLevel++) {
1527
- const imageData = lodArray[mipLevel];
1528
- if (this.device.isExternalImage(imageData)) {
1529
- this.texture.copyExternalImage({ image: imageData, depth, mipLevel, flipY: true });
1530
- } else {
1531
- this.texture.copyImageData({ data: imageData.data, mipLevel });
1650
+ /** Convert cube face label to texture slice index. Index can be used with `setTexture2DData()`. */
1651
+ getCubeFaceIndex(face) {
1652
+ const index = TEXTURE_CUBE_FACE_MAP[face];
1653
+ if (index === void 0)
1654
+ throw new Error(`Invalid cube face: ${face}`);
1655
+ return index;
1656
+ }
1657
+ /** Convert cube face label to texture slice index. Index can be used with `setTexture2DData()`. */
1658
+ getCubeArrayFaceIndex(cubeIndex, face) {
1659
+ return 6 * cubeIndex + this.getCubeFaceIndex(face);
1660
+ }
1661
+ /** @note experimental: Set multiple mip levels (1D) */
1662
+ setTexture1DData(data) {
1663
+ this._checkReady();
1664
+ if (this.texture.props.dimension !== "1d") {
1665
+ throw new Error(`${this} is not 1d`);
1666
+ }
1667
+ const subresources = getTexture1DSubresources(data);
1668
+ this._setTextureSubresources(subresources);
1669
+ }
1670
+ /** @note experimental: Set multiple mip levels (2D), optionally at `z`, slice (depth/array level) index */
1671
+ setTexture2DData(lodData, z = 0) {
1672
+ this._checkReady();
1673
+ if (this.texture.props.dimension !== "2d") {
1674
+ throw new Error(`${this} is not 2d`);
1675
+ }
1676
+ const subresources = getTexture2DSubresources(z, lodData);
1677
+ this._setTextureSubresources(subresources);
1678
+ }
1679
+ /** 3D: multiple depth slices, each may carry multiple mip levels */
1680
+ setTexture3DData(data) {
1681
+ if (this.texture.props.dimension !== "3d") {
1682
+ throw new Error(`${this} is not 3d`);
1683
+ }
1684
+ const subresources = getTexture3DSubresources(data);
1685
+ this._setTextureSubresources(subresources);
1686
+ }
1687
+ /** 2D array: multiple layers, each may carry multiple mip levels */
1688
+ setTextureArrayData(data) {
1689
+ if (this.texture.props.dimension !== "2d-array") {
1690
+ throw new Error(`${this} is not 2d-array`);
1691
+ }
1692
+ const subresources = getTextureArraySubresources(data);
1693
+ this._setTextureSubresources(subresources);
1694
+ }
1695
+ /** Cube: 6 faces, each may carry multiple mip levels */
1696
+ setTextureCubeData(data) {
1697
+ if (this.texture.props.dimension !== "cube") {
1698
+ throw new Error(`${this} is not cube`);
1699
+ }
1700
+ const subresources = getTextureCubeSubresources(data);
1701
+ this._setTextureSubresources(subresources);
1702
+ }
1703
+ /** Cube array: multiple cubes (faces×layers), each face may carry multiple mips */
1704
+ setTextureCubeArrayData(data) {
1705
+ if (this.texture.props.dimension !== "cube-array") {
1706
+ throw new Error(`${this} is not cube-array`);
1707
+ }
1708
+ const subresources = getTextureCubeArraySubresources(data);
1709
+ this._setTextureSubresources(subresources);
1710
+ }
1711
+ /** Sets multiple mip levels on different `z` slices (depth/array index) */
1712
+ _setTextureSubresources(subresources) {
1713
+ for (const subresource of subresources) {
1714
+ const { z, mipLevel } = subresource;
1715
+ switch (subresource.type) {
1716
+ case "external-image":
1717
+ const { image, flipY } = subresource;
1718
+ this.texture.copyExternalImage({ image, z, mipLevel, flipY });
1719
+ break;
1720
+ case "texture-data":
1721
+ const { data, textureFormat } = subresource;
1722
+ if (textureFormat && textureFormat !== this.texture.format) {
1723
+ throw new Error(`${this} mip level ${mipLevel} uses format "${textureFormat}" but texture format is "${this.texture.format}"`);
1724
+ }
1725
+ this.texture.writeData(data.data, {
1726
+ x: 0,
1727
+ y: 0,
1728
+ z,
1729
+ width: data.width,
1730
+ height: data.height,
1731
+ depthOrArrayLayers: 1,
1732
+ mipLevel
1733
+ });
1734
+ break;
1735
+ default:
1736
+ throw new Error("Unsupported 2D mip-level payload");
1532
1737
  }
1533
1738
  }
1534
1739
  }
1535
- /**
1536
- * Experimental: Sets 3D texture data: multiple depth slices, multiple mip levels
1537
- * @param data
1538
- */
1539
- _setTexture3DData(texture, data) {
1540
- var _a;
1541
- if (((_a = this.texture) == null ? void 0 : _a.props.dimension) !== "3d") {
1542
- throw new Error(this.id);
1543
- }
1544
- for (let depth = 0; depth < data.length; depth++) {
1545
- this._setTexture2DData(data[depth], depth);
1546
- }
1740
+ // ------------------ helpers ------------------
1741
+ /** Recursively resolve all promises in data structures */
1742
+ async _loadAllData(props) {
1743
+ const syncData = await awaitAllPromises(props.data);
1744
+ const dimension = props.dimension ?? "2d";
1745
+ return { dimension, data: syncData ?? null };
1547
1746
  }
1548
- /**
1549
- * Experimental: Set Cube texture data, multiple faces, multiple mip levels
1550
- * @todo - could support TextureCubeArray with depth
1551
- * @param data
1552
- * @param index
1553
- */
1554
- _setTextureCubeData(texture, data) {
1555
- var _a;
1556
- if (((_a = this.texture) == null ? void 0 : _a.props.dimension) !== "cube") {
1557
- throw new Error(this.id);
1558
- }
1559
- for (const [face, faceData] of Object.entries(data)) {
1560
- const faceDepth = CubeFaces.indexOf(face);
1561
- this._setTexture2DData(faceData, faceDepth);
1747
+ _checkNotDestroyed() {
1748
+ if (this.destroyed) {
1749
+ import_core7.log.warn(`${this} already destroyed`);
1562
1750
  }
1563
1751
  }
1564
- /**
1565
- * Experimental: Sets texture array data, multiple levels, multiple depth slices
1566
- * @param data
1567
- */
1568
- _setTextureArrayData(texture, data) {
1569
- var _a;
1570
- if (((_a = this.texture) == null ? void 0 : _a.props.dimension) !== "2d-array") {
1571
- throw new Error(this.id);
1572
- }
1573
- for (let depth = 0; depth < data.length; depth++) {
1574
- this._setTexture2DData(data[depth], depth);
1752
+ _checkReady() {
1753
+ if (!this.isReady) {
1754
+ import_core7.log.warn(`${this} Cannot perform this operation before ready`);
1575
1755
  }
1576
1756
  }
1577
- /**
1578
- * Experimental: Sets texture cube array, multiple faces, multiple levels, multiple mip levels
1579
- * @param data
1580
- */
1581
- _setTextureCubeArrayData(texture, data) {
1582
- throw new Error("setTextureCubeArrayData not supported in WebGL2.");
1757
+ };
1758
+ var DynamicTexture = _DynamicTexture;
1759
+ __publicField(DynamicTexture, "defaultProps", {
1760
+ ...import_core7.Texture.defaultProps,
1761
+ dimension: "2d",
1762
+ data: null,
1763
+ mipmaps: false
1764
+ });
1765
+ function getTextureSubresources(props) {
1766
+ if (!props.data) {
1767
+ return [];
1768
+ }
1769
+ const baseLevelSize = props.width && props.height ? { width: props.width, height: props.height } : void 0;
1770
+ const textureFormat = "format" in props ? props.format : void 0;
1771
+ switch (props.dimension) {
1772
+ case "1d":
1773
+ return getTexture1DSubresources(props.data);
1774
+ case "2d":
1775
+ return getTexture2DSubresources(0, props.data, baseLevelSize, textureFormat);
1776
+ case "3d":
1777
+ return getTexture3DSubresources(props.data);
1778
+ case "2d-array":
1779
+ return getTextureArraySubresources(props.data);
1780
+ case "cube":
1781
+ return getTextureCubeSubresources(props.data);
1782
+ case "cube-array":
1783
+ return getTextureCubeArraySubresources(props.data);
1784
+ default:
1785
+ throw new Error(`Unhandled dimension ${props.dimension}`);
1583
1786
  }
1584
- /** Experimental */
1585
- _setTextureCubeFaceData(texture, lodData, face, depth = 0) {
1586
- if (Array.isArray(lodData) && lodData.length > 1 && this.props.mipmaps !== false) {
1587
- import_core8.log.warn(`${this.id} has mipmap and multiple LODs.`)();
1588
- }
1589
- const faceDepth = TextureCubeFaces.indexOf(face);
1590
- this._setTexture2DData(lodData, faceDepth);
1787
+ }
1788
+ function analyzeTextureSubresources(device, subresources, size, options) {
1789
+ if (subresources.length === 0) {
1790
+ return {
1791
+ subresources,
1792
+ mipLevels: 1,
1793
+ format: options.format,
1794
+ hasExplicitMipChain: false
1795
+ };
1591
1796
  }
1592
- /**
1593
- * Normalize TextureData to an array of TextureImageData / ExternalImages
1594
- * @param data
1595
- * @param options
1596
- * @returns array of TextureImageData / ExternalImages
1597
- */
1598
- _normalizeTextureData(data) {
1599
- const options = this.texture;
1600
- let mipLevelArray;
1601
- if (ArrayBuffer.isView(data)) {
1602
- mipLevelArray = [
1603
- {
1604
- // ts-expect-error does data really need to be Uint8ClampedArray?
1605
- data,
1606
- width: options.width,
1607
- height: options.height
1608
- // depth: options.depth
1797
+ const groupedSubresources = /* @__PURE__ */ new Map();
1798
+ for (const subresource of subresources) {
1799
+ const group = groupedSubresources.get(subresource.z) ?? [];
1800
+ group.push(subresource);
1801
+ groupedSubresources.set(subresource.z, group);
1802
+ }
1803
+ const hasExplicitMipChain = subresources.some((subresource) => subresource.mipLevel > 0);
1804
+ let resolvedFormat = options.format;
1805
+ let resolvedMipLevels = Number.POSITIVE_INFINITY;
1806
+ const validSubresources = [];
1807
+ for (const [z, sliceSubresources] of groupedSubresources) {
1808
+ const sortedSubresources = [...sliceSubresources].sort((left, right) => left.mipLevel - right.mipLevel);
1809
+ const baseLevel = sortedSubresources[0];
1810
+ if (!baseLevel || baseLevel.mipLevel !== 0) {
1811
+ throw new Error(`DynamicTexture: slice ${z} is missing mip level 0`);
1812
+ }
1813
+ const baseSize = getTextureSubresourceSize(device, baseLevel);
1814
+ if (baseSize.width !== size.width || baseSize.height !== size.height) {
1815
+ throw new Error(`DynamicTexture: slice ${z} base level dimensions ${baseSize.width}x${baseSize.height} do not match expected ${size.width}x${size.height}`);
1816
+ }
1817
+ const baseFormat = getTextureSubresourceFormat(baseLevel);
1818
+ if (baseFormat) {
1819
+ if (resolvedFormat && resolvedFormat !== baseFormat) {
1820
+ throw new Error(`DynamicTexture: slice ${z} base level format "${baseFormat}" does not match texture format "${resolvedFormat}"`);
1821
+ }
1822
+ resolvedFormat = baseFormat;
1823
+ }
1824
+ const mipLevelLimit = resolvedFormat && device.isTextureFormatCompressed(resolvedFormat) ? (
1825
+ // Block-compressed formats cannot have mips smaller than a single compression block.
1826
+ getMaxCompressedMipLevels(device, baseSize.width, baseSize.height, resolvedFormat)
1827
+ ) : device.getMipLevelCount(baseSize.width, baseSize.height);
1828
+ let validMipLevelsForSlice = 0;
1829
+ for (let expectedMipLevel = 0; expectedMipLevel < sortedSubresources.length; expectedMipLevel++) {
1830
+ const subresource = sortedSubresources[expectedMipLevel];
1831
+ if (!subresource || subresource.mipLevel !== expectedMipLevel) {
1832
+ break;
1833
+ }
1834
+ if (expectedMipLevel >= mipLevelLimit) {
1835
+ break;
1836
+ }
1837
+ const subresourceSize = getTextureSubresourceSize(device, subresource);
1838
+ const expectedWidth = Math.max(1, baseSize.width >> expectedMipLevel);
1839
+ const expectedHeight = Math.max(1, baseSize.height >> expectedMipLevel);
1840
+ if (subresourceSize.width !== expectedWidth || subresourceSize.height !== expectedHeight) {
1841
+ break;
1842
+ }
1843
+ const subresourceFormat = getTextureSubresourceFormat(subresource);
1844
+ if (subresourceFormat) {
1845
+ if (!resolvedFormat) {
1846
+ resolvedFormat = subresourceFormat;
1609
1847
  }
1610
- ];
1611
- } else if (!Array.isArray(data)) {
1612
- mipLevelArray = [data];
1613
- } else {
1614
- mipLevelArray = data;
1848
+ if (subresourceFormat !== resolvedFormat) {
1849
+ break;
1850
+ }
1851
+ }
1852
+ validMipLevelsForSlice++;
1853
+ validSubresources.push(subresource);
1615
1854
  }
1616
- return mipLevelArray;
1855
+ resolvedMipLevels = Math.min(resolvedMipLevels, validMipLevelsForSlice);
1617
1856
  }
1618
- };
1619
- var AsyncTexture = _AsyncTexture;
1620
- __publicField(AsyncTexture, "defaultProps", {
1621
- ...import_core8.Texture.defaultProps,
1622
- data: null,
1623
- mipmaps: false
1624
- });
1857
+ const mipLevels = Number.isFinite(resolvedMipLevels) ? Math.max(1, resolvedMipLevels) : 1;
1858
+ return {
1859
+ // Keep every slice trimmed to the same mip count so the texture shape stays internally consistent.
1860
+ subresources: validSubresources.filter((subresource) => subresource.mipLevel < mipLevels),
1861
+ mipLevels,
1862
+ format: resolvedFormat,
1863
+ hasExplicitMipChain
1864
+ };
1865
+ }
1866
+ function getTextureSubresourceFormat(subresource) {
1867
+ if (subresource.type !== "texture-data") {
1868
+ return void 0;
1869
+ }
1870
+ return subresource.textureFormat ?? resolveTextureImageFormat(subresource.data);
1871
+ }
1872
+ function getTextureSubresourceSize(device, subresource) {
1873
+ switch (subresource.type) {
1874
+ case "external-image":
1875
+ return device.getExternalImageSize(subresource.image);
1876
+ case "texture-data":
1877
+ return { width: subresource.data.width, height: subresource.data.height };
1878
+ default:
1879
+ throw new Error("Unsupported texture subresource");
1880
+ }
1881
+ }
1882
+ function getMaxCompressedMipLevels(device, baseWidth, baseHeight, format) {
1883
+ const { blockWidth = 1, blockHeight = 1 } = device.getTextureFormatInfo(format);
1884
+ let mipLevels = 1;
1885
+ for (let mipLevel = 1; ; mipLevel++) {
1886
+ const width = Math.max(1, baseWidth >> mipLevel);
1887
+ const height = Math.max(1, baseHeight >> mipLevel);
1888
+ if (width < blockWidth || height < blockHeight) {
1889
+ break;
1890
+ }
1891
+ mipLevels++;
1892
+ }
1893
+ return mipLevels;
1894
+ }
1625
1895
  async function awaitAllPromises(x) {
1626
1896
  x = await x;
1627
1897
  if (Array.isArray(x)) {
@@ -1629,7 +1899,7 @@ async function awaitAllPromises(x) {
1629
1899
  }
1630
1900
  if (x && typeof x === "object" && x.constructor === Object) {
1631
1901
  const object = x;
1632
- const values = await Promise.all(Object.values(object));
1902
+ const values = await Promise.all(Object.values(object).map(awaitAllPromises));
1633
1903
  const keys = Object.keys(object);
1634
1904
  const resolvedObject = {};
1635
1905
  for (let i = 0; i < keys.length; i++) {
@@ -1644,16 +1914,24 @@ async function awaitAllPromises(x) {
1644
1914
  var LOG_DRAW_PRIORITY = 2;
1645
1915
  var LOG_DRAW_TIMEOUT = 1e4;
1646
1916
  var _Model = class {
1917
+ /** Device that created this model */
1647
1918
  device;
1919
+ /** Application provided identifier */
1648
1920
  id;
1921
+ /** WGSL shader source when using unified shader */
1649
1922
  // @ts-expect-error assigned in function called from constructor
1650
1923
  source;
1924
+ /** GLSL vertex shader source */
1651
1925
  // @ts-expect-error assigned in function called from constructor
1652
1926
  vs;
1927
+ /** GLSL fragment shader source */
1653
1928
  // @ts-expect-error assigned in function called from constructor
1654
1929
  fs;
1930
+ /** Factory used to create render pipelines */
1655
1931
  pipelineFactory;
1932
+ /** Factory used to create shaders */
1656
1933
  shaderFactory;
1934
+ /** User-supplied per-model data */
1657
1935
  userData = {};
1658
1936
  // Fixed properties (change can trigger pipeline rebuild)
1659
1937
  /** The render pipeline GPU parameters, depth testing etc */
@@ -1690,6 +1968,7 @@ var _Model = class {
1690
1968
  /** ShaderInputs instance */
1691
1969
  // @ts-expect-error Assigned in function called by constructor
1692
1970
  shaderInputs;
1971
+ material = null;
1693
1972
  // @ts-expect-error Assigned in function called by constructor
1694
1973
  _uniformStore;
1695
1974
  _attributeInfos = {};
@@ -1700,6 +1979,7 @@ var _Model = class {
1700
1979
  _destroyed = false;
1701
1980
  /** "Time" of last draw. Monotonically increasing timestamp */
1702
1981
  _lastDrawTimestamp = -1;
1982
+ _bindingTable = [];
1703
1983
  get [Symbol.toStringTag]() {
1704
1984
  return "Model";
1705
1985
  }
@@ -1707,12 +1987,13 @@ var _Model = class {
1707
1987
  return `Model(${this.id})`;
1708
1988
  }
1709
1989
  constructor(device, props) {
1710
- var _a, _b, _c;
1990
+ var _a, _b, _c, _d;
1711
1991
  this.props = { ..._Model.defaultProps, ...props };
1712
1992
  props = this.props;
1713
1993
  this.id = props.id || uid("model");
1714
1994
  this.device = device;
1715
1995
  Object.assign(this.userData, props.userData);
1996
+ this.material = props.material || null;
1716
1997
  const moduleMap = Object.fromEntries(((_a = this.props.modules) == null ? void 0 : _a.map((module2) => [module2.name, module2])) || []);
1717
1998
  const shaderInputs = props.shaderInputs || new ShaderInputs(moduleMap, { disableWarnings: this.props.disableWarnings });
1718
1999
  this.setShaderInputs(shaderInputs);
@@ -1721,16 +2002,19 @@ var _Model = class {
1721
2002
  // @ts-ignore shaderInputs is assigned in setShaderInputs above.
1722
2003
  (((_b = this.props.modules) == null ? void 0 : _b.length) > 0 ? this.props.modules : (_c = this.shaderInputs) == null ? void 0 : _c.getModules()) || []
1723
2004
  );
2005
+ this.props.shaderLayout = mergeShaderModuleBindingsIntoLayout(this.props.shaderLayout, modules) || null;
1724
2006
  const isWebGPU = this.device.type === "webgpu";
1725
2007
  if (isWebGPU && this.props.source) {
1726
- const { source: source3, getUniforms: getUniforms2 } = this.props.shaderAssembler.assembleWGSLShader({
2008
+ const { source: source3, getUniforms: getUniforms2, bindingTable } = this.props.shaderAssembler.assembleWGSLShader({
1727
2009
  platformInfo,
1728
2010
  ...this.props,
1729
2011
  modules
1730
2012
  });
1731
2013
  this.source = source3;
1732
2014
  this._getModuleUniforms = getUniforms2;
1733
- this.props.shaderLayout ||= (0, import_shadertools2.getShaderLayoutFromWGSL)(this.source);
2015
+ this._bindingTable = bindingTable;
2016
+ const inferredShaderLayout = (_d = device.getShaderLayout) == null ? void 0 : _d.call(device, this.source);
2017
+ this.props.shaderLayout = mergeShaderModuleBindingsIntoLayout(this.props.shaderLayout || inferredShaderLayout || null, modules) || null;
1734
2018
  } else {
1735
2019
  const { vs: vs3, fs: fs3, getUniforms: getUniforms2 } = this.props.shaderAssembler.assembleGLSLShaderPair({
1736
2020
  platformInfo,
@@ -1740,6 +2024,7 @@ var _Model = class {
1740
2024
  this.vs = vs3;
1741
2025
  this.fs = fs3;
1742
2026
  this._getModuleUniforms = getUniforms2;
2027
+ this._bindingTable = [];
1743
2028
  }
1744
2029
  this.vertexCount = this.props.vertexCount;
1745
2030
  this.instanceCount = this.props.instanceCount;
@@ -1749,8 +2034,8 @@ var _Model = class {
1749
2034
  if (props.geometry) {
1750
2035
  this.setGeometry(props.geometry);
1751
2036
  }
1752
- this.pipelineFactory = props.pipelineFactory || PipelineFactory.getDefaultPipelineFactory(this.device);
1753
- this.shaderFactory = props.shaderFactory || ShaderFactory.getDefaultShaderFactory(this.device);
2037
+ this.pipelineFactory = props.pipelineFactory || import_core8.PipelineFactory.getDefaultPipelineFactory(this.device);
2038
+ this.shaderFactory = props.shaderFactory || import_core8.ShaderFactory.getDefaultShaderFactory(this.device);
1754
2039
  this.pipeline = this._updatePipeline();
1755
2040
  this.vertexArray = device.createVertexArray({
1756
2041
  shaderLayout: this.pipeline.shaderLayout,
@@ -1783,14 +2068,13 @@ var _Model = class {
1783
2068
  if (props.transformFeedback) {
1784
2069
  this.transformFeedback = props.transformFeedback;
1785
2070
  }
1786
- Object.seal(this);
1787
2071
  }
1788
2072
  destroy() {
1789
2073
  var _a;
1790
2074
  if (!this._destroyed) {
1791
2075
  this.pipelineFactory.release(this.pipeline);
1792
2076
  this.shaderFactory.release(this.pipeline.vs);
1793
- if (this.pipeline.fs) {
2077
+ if (this.pipeline.fs && this.pipeline.fs !== this.pipeline.vs) {
1794
2078
  this.shaderFactory.release(this.pipeline.fs);
1795
2079
  }
1796
2080
  this._uniformStore.destroy();
@@ -1812,14 +2096,24 @@ var _Model = class {
1812
2096
  setNeedsRedraw(reason) {
1813
2097
  this._needsRedraw ||= reason;
1814
2098
  }
2099
+ /** Returns WGSL binding debug rows for the assembled shader. Returns an empty array for GLSL models. */
2100
+ getBindingDebugTable() {
2101
+ return this._bindingTable;
2102
+ }
2103
+ /** Update uniforms and pipeline state prior to drawing. */
1815
2104
  predraw() {
1816
2105
  this.updateShaderInputs();
1817
2106
  this.pipeline = this._updatePipeline();
1818
2107
  }
2108
+ /**
2109
+ * Issue one draw call.
2110
+ * @param renderPass - render pass to draw into
2111
+ * @returns `true` if the draw call was executed, `false` if resources were not ready.
2112
+ */
1819
2113
  draw(renderPass) {
1820
2114
  const loadingBinding = this._areBindingsLoading();
1821
2115
  if (loadingBinding) {
1822
- import_core9.log.info(LOG_DRAW_PRIORITY, `>>> DRAWING ABORTED ${this.id}: ${loadingBinding} not loaded`)();
2116
+ import_core8.log.info(LOG_DRAW_PRIORITY, `>>> DRAWING ABORTED ${this.id}: ${loadingBinding} not loaded`)();
1823
2117
  return false;
1824
2118
  }
1825
2119
  try {
@@ -1834,9 +2128,7 @@ var _Model = class {
1834
2128
  this._logDrawCallStart();
1835
2129
  this.pipeline = this._updatePipeline();
1836
2130
  const syncBindings = this._getBindings();
1837
- this.pipeline.setBindings(syncBindings, {
1838
- disableWarnings: this.props.disableWarnings
1839
- });
2131
+ const syncBindGroups = this._getBindGroups();
1840
2132
  const { indexBuffer } = this.vertexArray;
1841
2133
  const indexCount = indexBuffer ? indexBuffer.byteLength / (indexBuffer.indexType === "uint32" ? 4 : 2) : void 0;
1842
2134
  drawSuccess = this.pipeline.draw({
@@ -1847,6 +2139,13 @@ var _Model = class {
1847
2139
  instanceCount: this.instanceCount,
1848
2140
  indexCount,
1849
2141
  transformFeedback: this.transformFeedback || void 0,
2142
+ // Pipelines may be shared across models when caching is enabled, so bindings
2143
+ // and WebGL uniforms must be supplied on every draw instead of being stored
2144
+ // on the pipeline instance.
2145
+ bindings: syncBindings,
2146
+ bindGroups: syncBindGroups,
2147
+ _bindGroupCacheKeys: this._getBindGroupCacheKeys(),
2148
+ uniforms: this.props.uniforms,
1850
2149
  // WebGL shares underlying cached pipelines even for models that have different parameters and topology,
1851
2150
  // so we must provide our unique parameters to each draw
1852
2151
  // (In WebGPU most parameters are encoded in the pipeline and cannot be changed per draw call)
@@ -1946,20 +2245,25 @@ var _Model = class {
1946
2245
  }
1947
2246
  /** Set the shader inputs */
1948
2247
  setShaderInputs(shaderInputs) {
2248
+ var _a;
1949
2249
  this.shaderInputs = shaderInputs;
1950
- this._uniformStore = new import_core9.UniformStore(this.shaderInputs.modules);
2250
+ this._uniformStore = new import_core8.UniformStore(this.device, this.shaderInputs.modules);
1951
2251
  for (const [moduleName, module2] of Object.entries(this.shaderInputs.modules)) {
1952
- if (shaderModuleHasUniforms(module2)) {
1953
- const uniformBuffer = this._uniformStore.getManagedUniformBuffer(this.device, moduleName);
2252
+ if (shaderModuleHasUniforms(module2) && !((_a = this.material) == null ? void 0 : _a.ownsModule(moduleName))) {
2253
+ const uniformBuffer = this._uniformStore.getManagedUniformBuffer(moduleName);
1954
2254
  this.bindings[`${moduleName}Uniforms`] = uniformBuffer;
1955
2255
  }
1956
2256
  }
1957
2257
  this.setNeedsRedraw("shaderInputs");
1958
2258
  }
2259
+ setMaterial(material) {
2260
+ this.material = material;
2261
+ this.setNeedsRedraw("material");
2262
+ }
1959
2263
  /** Update uniform buffers from the model's shader inputs */
1960
2264
  updateShaderInputs() {
1961
2265
  this._uniformStore.setUniforms(this.shaderInputs.getUniformValues());
1962
- this.setBindings(this.shaderInputs.getBindingValues());
2266
+ this.setBindings(this._getNonMaterialBindings(this.shaderInputs.getBindingValues()));
1963
2267
  this.setNeedsRedraw("shaderInputs");
1964
2268
  }
1965
2269
  /**
@@ -1991,7 +2295,7 @@ var _Model = class {
1991
2295
  setAttributes(buffers, options) {
1992
2296
  const disableWarnings = (options == null ? void 0 : options.disableWarnings) ?? this.props.disableWarnings;
1993
2297
  if (buffers["indices"]) {
1994
- import_core9.log.warn(`Model:${this.id} setAttributes() - indexBuffer should be set using setIndexBuffer()`)();
2298
+ import_core8.log.warn(`Model:${this.id} setAttributes() - indexBuffer should be set using setIndexBuffer()`)();
1995
2299
  }
1996
2300
  this.bufferLayout = sortedBufferLayoutByShaderSourceLocations(this.pipeline.shaderLayout, this.bufferLayout);
1997
2301
  const bufferLayoutHelper = new BufferLayoutHelper(this.bufferLayout);
@@ -1999,7 +2303,7 @@ var _Model = class {
1999
2303
  const bufferLayout = bufferLayoutHelper.getBufferLayout(bufferName);
2000
2304
  if (!bufferLayout) {
2001
2305
  if (!disableWarnings) {
2002
- import_core9.log.warn(`Model(${this.id}): Missing layout for buffer "${bufferName}".`)();
2306
+ import_core8.log.warn(`Model(${this.id}): Missing layout for buffer "${bufferName}".`)();
2003
2307
  }
2004
2308
  continue;
2005
2309
  }
@@ -2014,7 +2318,7 @@ var _Model = class {
2014
2318
  }
2015
2319
  }
2016
2320
  if (!set && !disableWarnings) {
2017
- import_core9.log.warn(`Model(${this.id}): Ignoring buffer "${buffer.id}" for unknown attribute "${bufferName}"`)();
2321
+ import_core8.log.warn(`Model(${this.id}): Ignoring buffer "${buffer.id}" for unknown attribute "${bufferName}"`)();
2018
2322
  }
2019
2323
  }
2020
2324
  this.setNeedsRedraw("attributes");
@@ -2033,7 +2337,7 @@ var _Model = class {
2033
2337
  if (attributeInfo) {
2034
2338
  this.vertexArray.setConstantWebGL(attributeInfo.location, value);
2035
2339
  } else if (!((options == null ? void 0 : options.disableWarnings) ?? this.props.disableWarnings)) {
2036
- import_core9.log.warn(`Model "${this.id}: Ignoring constant supplied for unknown attribute "${attributeName}"`)();
2340
+ import_core8.log.warn(`Model "${this.id}: Ignoring constant supplied for unknown attribute "${attributeName}"`)();
2037
2341
  }
2038
2342
  }
2039
2343
  this.setNeedsRedraw("constants");
@@ -2041,8 +2345,14 @@ var _Model = class {
2041
2345
  // INTERNAL METHODS
2042
2346
  /** Check that bindings are loaded. Returns id of first binding that is still loading. */
2043
2347
  _areBindingsLoading() {
2348
+ var _a;
2044
2349
  for (const binding of Object.values(this.bindings)) {
2045
- if (binding instanceof AsyncTexture && !binding.isReady) {
2350
+ if (binding instanceof DynamicTexture && !binding.isReady) {
2351
+ return binding.id;
2352
+ }
2353
+ }
2354
+ for (const binding of Object.values(((_a = this.material) == null ? void 0 : _a.bindings) || {})) {
2355
+ if (binding instanceof DynamicTexture && !binding.isReady) {
2046
2356
  return binding.id;
2047
2357
  }
2048
2358
  }
@@ -2052,7 +2362,7 @@ var _Model = class {
2052
2362
  _getBindings() {
2053
2363
  const validBindings = {};
2054
2364
  for (const [name, binding] of Object.entries(this.bindings)) {
2055
- if (binding instanceof AsyncTexture) {
2365
+ if (binding instanceof DynamicTexture) {
2056
2366
  if (binding.isReady) {
2057
2367
  validBindings[name] = binding.texture;
2058
2368
  }
@@ -2062,24 +2372,46 @@ var _Model = class {
2062
2372
  }
2063
2373
  return validBindings;
2064
2374
  }
2375
+ _getBindGroups() {
2376
+ var _a;
2377
+ const shaderLayout = ((_a = this.pipeline) == null ? void 0 : _a.shaderLayout) || this.props.shaderLayout || { bindings: [] };
2378
+ const bindGroups = shaderLayout.bindings.length ? (0, import_core8.normalizeBindingsByGroup)(shaderLayout, this._getBindings()) : { 0: this._getBindings() };
2379
+ if (!this.material) {
2380
+ return bindGroups;
2381
+ }
2382
+ for (const [groupKey, groupBindings] of Object.entries(this.material.getBindingsByGroup())) {
2383
+ const group = Number(groupKey);
2384
+ bindGroups[group] = {
2385
+ ...bindGroups[group] || {},
2386
+ ...groupBindings
2387
+ };
2388
+ }
2389
+ return bindGroups;
2390
+ }
2391
+ _getBindGroupCacheKeys() {
2392
+ var _a;
2393
+ const bindGroupCacheKey = (_a = this.material) == null ? void 0 : _a.getBindGroupCacheKey(3);
2394
+ return bindGroupCacheKey ? { 3: bindGroupCacheKey } : {};
2395
+ }
2065
2396
  /** Get the timestamp of the latest updated bound GPU memory resource (buffer/texture). */
2066
2397
  _getBindingsUpdateTimestamp() {
2398
+ var _a;
2067
2399
  let timestamp = 0;
2068
2400
  for (const binding of Object.values(this.bindings)) {
2069
- if (binding instanceof import_core9.TextureView) {
2401
+ if (binding instanceof import_core8.TextureView) {
2070
2402
  timestamp = Math.max(timestamp, binding.texture.updateTimestamp);
2071
- } else if (binding instanceof import_core9.Buffer || binding instanceof import_core9.Texture) {
2403
+ } else if (binding instanceof import_core8.Buffer || binding instanceof import_core8.Texture) {
2072
2404
  timestamp = Math.max(timestamp, binding.updateTimestamp);
2073
- } else if (binding instanceof AsyncTexture) {
2405
+ } else if (binding instanceof DynamicTexture) {
2074
2406
  timestamp = binding.texture ? Math.max(timestamp, binding.texture.updateTimestamp) : (
2075
2407
  // The texture will become available in the future
2076
2408
  Infinity
2077
2409
  );
2078
- } else if (!(binding instanceof import_core9.Sampler)) {
2410
+ } else if (!(binding instanceof import_core8.Sampler)) {
2079
2411
  timestamp = Math.max(timestamp, binding.buffer.updateTimestamp);
2080
2412
  }
2081
2413
  }
2082
- return timestamp;
2414
+ return Math.max(timestamp, ((_a = this.material) == null ? void 0 : _a.getBindingsUpdateTimestamp()) || 0);
2083
2415
  }
2084
2416
  /**
2085
2417
  * Updates the optional geometry attributes
@@ -2110,7 +2442,7 @@ var _Model = class {
2110
2442
  let prevShaderVs = null;
2111
2443
  let prevShaderFs = null;
2112
2444
  if (this.pipeline) {
2113
- import_core9.log.log(1, `Model ${this.id}: Recreating pipeline because "${this._pipelineNeedsUpdate}".`)();
2445
+ import_core8.log.log(1, `Model ${this.id}: Recreating pipeline because "${this._pipelineNeedsUpdate}".`)();
2114
2446
  prevShaderVs = this.pipeline.vs;
2115
2447
  prevShaderFs = this.pipeline.fs;
2116
2448
  }
@@ -2134,20 +2466,20 @@ var _Model = class {
2134
2466
  }
2135
2467
  this.pipeline = this.pipelineFactory.createRenderPipeline({
2136
2468
  ...this.props,
2469
+ bindings: void 0,
2137
2470
  bufferLayout: this.bufferLayout,
2138
2471
  topology: this.topology,
2139
2472
  parameters: this.parameters,
2140
- // TODO - why set bindings here when we reset them every frame?
2141
- // Should we expose a BindGroup abstraction?
2142
- bindings: this._getBindings(),
2473
+ bindGroups: this._getBindGroups(),
2143
2474
  vs: vs3,
2144
2475
  fs: fs3
2145
2476
  });
2146
- this._attributeInfos = (0, import_core9.getAttributeInfosFromLayouts)(this.pipeline.shaderLayout, this.bufferLayout);
2477
+ this._attributeInfos = (0, import_core8.getAttributeInfosFromLayouts)(this.pipeline.shaderLayout, this.bufferLayout);
2147
2478
  if (prevShaderVs)
2148
2479
  this.shaderFactory.release(prevShaderVs);
2149
- if (prevShaderFs)
2480
+ if (prevShaderFs && prevShaderFs !== prevShaderVs) {
2150
2481
  this.shaderFactory.release(prevShaderFs);
2482
+ }
2151
2483
  }
2152
2484
  return this.pipeline;
2153
2485
  }
@@ -2155,24 +2487,24 @@ var _Model = class {
2155
2487
  _lastLogTime = 0;
2156
2488
  _logOpen = false;
2157
2489
  _logDrawCallStart() {
2158
- const logDrawTimeout = import_core9.log.level > 3 ? 0 : LOG_DRAW_TIMEOUT;
2159
- if (import_core9.log.level < 2 || Date.now() - this._lastLogTime < logDrawTimeout) {
2490
+ const logDrawTimeout = import_core8.log.level > 3 ? 0 : LOG_DRAW_TIMEOUT;
2491
+ if (import_core8.log.level < 2 || Date.now() - this._lastLogTime < logDrawTimeout) {
2160
2492
  return;
2161
2493
  }
2162
2494
  this._lastLogTime = Date.now();
2163
2495
  this._logOpen = true;
2164
- import_core9.log.group(LOG_DRAW_PRIORITY, `>>> DRAWING MODEL ${this.id}`, { collapsed: import_core9.log.level <= 2 })();
2496
+ import_core8.log.group(LOG_DRAW_PRIORITY, `>>> DRAWING MODEL ${this.id}`, { collapsed: import_core8.log.level <= 2 })();
2165
2497
  }
2166
2498
  _logDrawCallEnd() {
2167
2499
  if (this._logOpen) {
2168
2500
  const shaderLayoutTable = getDebugTableForShaderLayout(this.pipeline.shaderLayout, this.id);
2169
- import_core9.log.table(LOG_DRAW_PRIORITY, shaderLayoutTable)();
2501
+ import_core8.log.table(LOG_DRAW_PRIORITY, shaderLayoutTable)();
2170
2502
  const uniformTable = this.shaderInputs.getDebugTable();
2171
- import_core9.log.table(LOG_DRAW_PRIORITY, uniformTable)();
2503
+ import_core8.log.table(LOG_DRAW_PRIORITY, uniformTable)();
2172
2504
  const attributeTable = this._getAttributeDebugTable();
2173
- import_core9.log.table(LOG_DRAW_PRIORITY, this._attributeInfos)();
2174
- import_core9.log.table(LOG_DRAW_PRIORITY, attributeTable)();
2175
- import_core9.log.groupEnd(LOG_DRAW_PRIORITY)();
2505
+ import_core8.log.table(LOG_DRAW_PRIORITY, this._attributeInfos)();
2506
+ import_core8.log.table(LOG_DRAW_PRIORITY, attributeTable)();
2507
+ import_core8.log.groupEnd(LOG_DRAW_PRIORITY)();
2176
2508
  this._logOpen = false;
2177
2509
  }
2178
2510
  }
@@ -2211,14 +2543,26 @@ var _Model = class {
2211
2543
  }
2212
2544
  // TODO - fix typing of luma data types
2213
2545
  _getBufferOrConstantValues(attribute, dataType) {
2214
- const TypedArrayConstructor = (0, import_core9.getTypedArrayConstructor)(dataType);
2215
- const typedArray = attribute instanceof import_core9.Buffer ? new TypedArrayConstructor(attribute.debugData) : attribute;
2546
+ const TypedArrayConstructor = import_core8.dataTypeDecoder.getTypedArrayConstructor(dataType);
2547
+ const typedArray = attribute instanceof import_core8.Buffer ? new TypedArrayConstructor(attribute.debugData) : attribute;
2216
2548
  return typedArray.toString();
2217
2549
  }
2550
+ _getNonMaterialBindings(bindings) {
2551
+ if (!this.material) {
2552
+ return bindings;
2553
+ }
2554
+ const filteredBindings = {};
2555
+ for (const [name, binding] of Object.entries(bindings)) {
2556
+ if (!this.material.ownsBinding(name)) {
2557
+ filteredBindings[name] = binding;
2558
+ }
2559
+ }
2560
+ return filteredBindings;
2561
+ }
2218
2562
  };
2219
2563
  var Model = _Model;
2220
2564
  __publicField(Model, "defaultProps", {
2221
- ...import_core9.RenderPipeline.defaultProps,
2565
+ ...import_core8.RenderPipeline.defaultProps,
2222
2566
  source: void 0,
2223
2567
  vs: null,
2224
2568
  fs: null,
@@ -2231,11 +2575,14 @@ __publicField(Model, "defaultProps", {
2231
2575
  indexBuffer: null,
2232
2576
  attributes: {},
2233
2577
  constantAttributes: {},
2578
+ bindings: {},
2579
+ uniforms: {},
2234
2580
  varyings: [],
2235
2581
  isInstanced: void 0,
2236
2582
  instanceCount: 0,
2237
2583
  vertexCount: 0,
2238
2584
  shaderInputs: void 0,
2585
+ material: void 0,
2239
2586
  pipelineFactory: void 0,
2240
2587
  shaderFactory: void 0,
2241
2588
  transformFeedback: void 0,
@@ -2243,9 +2590,6 @@ __publicField(Model, "defaultProps", {
2243
2590
  debugShaders: void 0,
2244
2591
  disableWarnings: void 0
2245
2592
  });
2246
- function shaderModuleHasUniforms(module2) {
2247
- return Boolean(module2.uniformTypes && !isObjectEmpty(module2.uniformTypes));
2248
- }
2249
2593
  function getPlatformInfo(device) {
2250
2594
  return {
2251
2595
  type: device.type,
@@ -2256,13 +2600,234 @@ function getPlatformInfo(device) {
2256
2600
  features: device.features
2257
2601
  };
2258
2602
  }
2259
- function isObjectEmpty(obj) {
2260
- for (const key in obj) {
2261
- return false;
2603
+
2604
+ // dist/material/material.js
2605
+ var import_core9 = require("@luma.gl/core");
2606
+
2607
+ // dist/material/material-factory.js
2608
+ var MATERIAL_BIND_GROUP = 3;
2609
+ var MaterialFactory = class {
2610
+ /** Device that creates materials for this schema. */
2611
+ device;
2612
+ /** Shader modules that define the material schema. */
2613
+ modules;
2614
+ _materialBindingNames;
2615
+ _materialModuleNames;
2616
+ constructor(device, props = {}) {
2617
+ this.device = device;
2618
+ this.modules = props.modules || [];
2619
+ const shaderInputs = new ShaderInputs(Object.fromEntries(this.modules.map((module2) => [module2.name, module2])));
2620
+ this._materialBindingNames = getMaterialBindingNames(shaderInputs);
2621
+ this._materialModuleNames = getMaterialModuleNames(shaderInputs);
2622
+ }
2623
+ /** Creates one typed material instance for this factory's schema. */
2624
+ createMaterial(props = {}) {
2625
+ return new Material(this.device, {
2626
+ ...props,
2627
+ factory: this
2628
+ });
2262
2629
  }
2263
- return true;
2630
+ /** Returns the logical material-owned resource binding names. */
2631
+ getBindingNames() {
2632
+ return Array.from(this._materialBindingNames);
2633
+ }
2634
+ /** Returns `true` when the supplied binding belongs to this material schema. */
2635
+ ownsBinding(bindingName) {
2636
+ if (this._materialBindingNames.has(bindingName)) {
2637
+ return true;
2638
+ }
2639
+ const aliasedModuleName = getModuleNameFromUniformBinding(bindingName);
2640
+ return aliasedModuleName ? this._materialModuleNames.has(aliasedModuleName) : false;
2641
+ }
2642
+ /** Returns `true` when the supplied shader module is owned by this material schema. */
2643
+ ownsModule(moduleName) {
2644
+ return this._materialModuleNames.has(moduleName);
2645
+ }
2646
+ /** Packages resolved material bindings into bind group `3`. */
2647
+ getBindingsByGroup(bindings) {
2648
+ return Object.keys(bindings).length > 0 ? { [MATERIAL_BIND_GROUP]: bindings } : {};
2649
+ }
2650
+ };
2651
+ function getModuleNameFromUniformBinding(bindingName) {
2652
+ return bindingName.endsWith("Uniforms") ? bindingName.slice(0, -"Uniforms".length) : null;
2653
+ }
2654
+ function getMaterialBindingNames(shaderInputs) {
2655
+ const bindingNames = /* @__PURE__ */ new Set();
2656
+ for (const module2 of Object.values(shaderInputs.modules)) {
2657
+ for (const binding of module2.bindingLayout || []) {
2658
+ if (binding.group === MATERIAL_BIND_GROUP) {
2659
+ bindingNames.add(binding.name);
2660
+ }
2661
+ }
2662
+ }
2663
+ return bindingNames;
2664
+ }
2665
+ function getMaterialModuleNames(shaderInputs) {
2666
+ var _a;
2667
+ const moduleNames = /* @__PURE__ */ new Set();
2668
+ for (const module2 of Object.values(shaderInputs.modules)) {
2669
+ if (module2.name && ((_a = module2.bindingLayout) == null ? void 0 : _a.some((binding) => binding.group === MATERIAL_BIND_GROUP && binding.name === module2.name))) {
2670
+ moduleNames.add(module2.name);
2671
+ }
2672
+ }
2673
+ return moduleNames;
2264
2674
  }
2265
2675
 
2676
+ // dist/material/material.js
2677
+ var Material = class {
2678
+ /** Application-provided identifier. */
2679
+ id;
2680
+ /** Device that owns the material resources. */
2681
+ device;
2682
+ /** Factory that defines the material schema. */
2683
+ factory;
2684
+ /** Shader inputs for the material-owned modules. */
2685
+ shaderInputs;
2686
+ /** Internal binding store including uniform buffers and resource bindings. */
2687
+ bindings = {};
2688
+ _uniformStore;
2689
+ _bindGroupCacheToken = {};
2690
+ constructor(device, props = {}) {
2691
+ var _a, _b;
2692
+ this.id = props.id || uid("material");
2693
+ this.device = device;
2694
+ this.factory = props.factory || new MaterialFactory(device, {
2695
+ modules: props.modules || ((_a = props.shaderInputs) == null ? void 0 : _a.getModules()) || []
2696
+ });
2697
+ const moduleMap = Object.fromEntries((((_b = props.shaderInputs) == null ? void 0 : _b.getModules()) || this.factory.modules).map((module2) => [
2698
+ module2.name,
2699
+ module2
2700
+ ]));
2701
+ this.shaderInputs = props.shaderInputs || new ShaderInputs(moduleMap);
2702
+ this._uniformStore = new import_core9.UniformStore(this.device, this.shaderInputs.modules);
2703
+ for (const [moduleName, module2] of Object.entries(this.shaderInputs.modules)) {
2704
+ if (this.ownsModule(moduleName) && shaderModuleHasUniforms(module2)) {
2705
+ const uniformBuffer = this._uniformStore.getManagedUniformBuffer(moduleName);
2706
+ this.bindings[`${moduleName}Uniforms`] = uniformBuffer;
2707
+ }
2708
+ }
2709
+ this.updateShaderInputs();
2710
+ if (props.bindings) {
2711
+ this._replaceOwnedBindings(props.bindings);
2712
+ }
2713
+ }
2714
+ /** Destroys managed uniform-buffer resources owned by this material. */
2715
+ destroy() {
2716
+ this._uniformStore.destroy();
2717
+ }
2718
+ /** Creates a new material variant with optional structural and uniform overrides. */
2719
+ clone(props = {}) {
2720
+ const material = this.factory.createMaterial({
2721
+ id: props.id,
2722
+ shaderInputs: props.shaderInputs,
2723
+ bindings: {
2724
+ ...this.getResourceBindings(),
2725
+ ...props.bindings
2726
+ }
2727
+ });
2728
+ if (!props.shaderInputs) {
2729
+ material.setProps(this.shaderInputs.getUniformValues());
2730
+ }
2731
+ if (props.moduleProps) {
2732
+ material.setProps(props.moduleProps);
2733
+ }
2734
+ return material;
2735
+ }
2736
+ /** Returns `true` if this material owns the supplied binding name. */
2737
+ ownsBinding(bindingName) {
2738
+ return this.factory.ownsBinding(bindingName);
2739
+ }
2740
+ /** Returns `true` if this material owns the supplied shader module. */
2741
+ ownsModule(moduleName) {
2742
+ return this.factory.ownsModule(moduleName);
2743
+ }
2744
+ /** Updates material uniform/module props in place without changing material identity. */
2745
+ setProps(props) {
2746
+ this.shaderInputs.setProps(props);
2747
+ this.updateShaderInputs();
2748
+ }
2749
+ /** Updates managed uniform buffers and shader-input-owned bindings. */
2750
+ updateShaderInputs() {
2751
+ this._uniformStore.setUniforms(this.shaderInputs.getUniformValues());
2752
+ const didChange = this._setOwnedBindings(this.shaderInputs.getBindingValues());
2753
+ if (didChange) {
2754
+ this._bindGroupCacheToken = {};
2755
+ }
2756
+ }
2757
+ /** Returns the material-owned resource bindings without internal uniform buffers. */
2758
+ getResourceBindings() {
2759
+ const resourceBindings = {};
2760
+ for (const [name, binding] of Object.entries(this.bindings)) {
2761
+ if (!getModuleNameFromUniformBinding(name)) {
2762
+ resourceBindings[name] = binding;
2763
+ }
2764
+ }
2765
+ return resourceBindings;
2766
+ }
2767
+ /** Returns the resolved bindings, including internal uniform buffers and ready textures. */
2768
+ getBindings() {
2769
+ const validBindings = {};
2770
+ const validBindingsMap = validBindings;
2771
+ for (const [name, binding] of Object.entries(this.bindings)) {
2772
+ if (binding instanceof DynamicTexture) {
2773
+ if (binding.isReady) {
2774
+ validBindingsMap[name] = binding.texture;
2775
+ }
2776
+ } else {
2777
+ validBindingsMap[name] = binding;
2778
+ }
2779
+ }
2780
+ return validBindings;
2781
+ }
2782
+ /** Packages resolved material bindings into logical bind group `3`. */
2783
+ getBindingsByGroup() {
2784
+ return this.factory.getBindingsByGroup(this.getBindings());
2785
+ }
2786
+ /** Returns the stable bind-group cache token for the requested bind group. */
2787
+ getBindGroupCacheKey(group) {
2788
+ return group === MATERIAL_BIND_GROUP ? this._bindGroupCacheToken : null;
2789
+ }
2790
+ /** Returns the latest update timestamp across material-owned resources. */
2791
+ getBindingsUpdateTimestamp() {
2792
+ let timestamp = 0;
2793
+ for (const binding of Object.values(this.bindings)) {
2794
+ if (binding instanceof import_core9.TextureView) {
2795
+ timestamp = Math.max(timestamp, binding.texture.updateTimestamp);
2796
+ } else if (binding instanceof import_core9.Buffer || binding instanceof import_core9.Texture) {
2797
+ timestamp = Math.max(timestamp, binding.updateTimestamp);
2798
+ } else if (binding instanceof DynamicTexture) {
2799
+ timestamp = binding.texture ? Math.max(timestamp, binding.texture.updateTimestamp) : Infinity;
2800
+ } else if (!(binding instanceof import_core9.Sampler)) {
2801
+ timestamp = Math.max(timestamp, binding.buffer.updateTimestamp);
2802
+ }
2803
+ }
2804
+ return timestamp;
2805
+ }
2806
+ /** Replaces owned resource bindings and invalidates the material cache identity when needed. */
2807
+ _replaceOwnedBindings(bindings) {
2808
+ const didChange = this._setOwnedBindings(bindings);
2809
+ if (didChange) {
2810
+ this._bindGroupCacheToken = {};
2811
+ }
2812
+ }
2813
+ _setOwnedBindings(bindings) {
2814
+ let didChange = false;
2815
+ for (const [name, binding] of Object.entries(bindings)) {
2816
+ if (binding === void 0) {
2817
+ continue;
2818
+ }
2819
+ if (!this.ownsBinding(name)) {
2820
+ continue;
2821
+ }
2822
+ if (this.bindings[name] !== binding) {
2823
+ this.bindings[name] = binding;
2824
+ didChange = true;
2825
+ }
2826
+ }
2827
+ return didChange;
2828
+ }
2829
+ };
2830
+
2266
2831
  // dist/compute/buffer-transform.js
2267
2832
  var import_core10 = require("@luma.gl/core");
2268
2833
  var import_shadertools3 = require("@luma.gl/shadertools");
@@ -2535,9 +3100,9 @@ var Geometry = class {
2535
3100
  var CLIPSPACE_VERTEX_SHADER_WGSL = (
2536
3101
  /* wgsl */
2537
3102
  `struct VertexInputs {
2538
- @location(0) clipSpacePosition: vec2<f32>,
2539
- @location(1) texCoord: vec2<f32>,
2540
- @location(2) coordinate: vec2<f32>
3103
+ @location(0) clipSpacePositions: vec2<f32>,
3104
+ @location(1) texCoords: vec2<f32>,
3105
+ @location(2) coordinates: vec2<f32>
2541
3106
  }
2542
3107
 
2543
3108
  struct FragmentInputs {
@@ -2550,10 +3115,10 @@ struct FragmentInputs {
2550
3115
  @vertex
2551
3116
  fn vertexMain(inputs: VertexInputs) -> FragmentInputs {
2552
3117
  var outputs: FragmentInputs;
2553
- outputs.Position = vec4(inputs.clipSpacePosition, 0., 1.);
2554
- outputs.position = inputs.clipSpacePosition;
2555
- outputs.coordinate = inputs.coordinate;
2556
- outputs.uv = inputs.texCoord;
3118
+ outputs.Position = vec4(inputs.clipSpacePositions, 0., 1.);
3119
+ outputs.position = inputs.clipSpacePositions;
3120
+ outputs.coordinate = inputs.coordinates;
3121
+ outputs.uv = inputs.texCoords;
2557
3122
  return outputs;
2558
3123
  }
2559
3124
  `
@@ -2604,22 +3169,31 @@ ${props.source}` };
2604
3169
  };
2605
3170
 
2606
3171
  // dist/models/billboard-texture-model.js
3172
+ var backgroundModule = {
3173
+ name: "background",
3174
+ uniformTypes: {
3175
+ scale: "vec2<f32>"
3176
+ }
3177
+ };
2607
3178
  var BACKGROUND_FS_WGSL = (
2608
3179
  /* wgsl */
2609
3180
  `@group(0) @binding(0) var backgroundTexture: texture_2d<f32>;
2610
3181
  @group(0) @binding(1) var backgroundTextureSampler: sampler;
3182
+ struct backgroundUniforms {
3183
+ scale: vec2<f32>,
3184
+ };
3185
+ @group(0) @binding(2) var<uniform> background: backgroundUniforms;
2611
3186
 
2612
- fn billboardTexture_getTextureUV(coordinates: vec2<f32>) -> vec2<f32> {
2613
- let iTexSize: vec2<u32> = textureDimensions(backgroundTexture, 0);
2614
- let texSize: vec2<f32> = vec2<f32>(f32(iTexSize.x), f32(iTexSize.y));
2615
- var position: vec2<f32> = coordinates.xy / texSize;
2616
- return position;
2617
- }
3187
+ fn billboardTexture_getTextureUV(uv: vec2<f32>) -> vec2<f32> {
3188
+ let scale: vec2<f32> = background.scale;
3189
+ var position: vec2<f32> = (uv - vec2<f32>(0.5, 0.5)) / scale + vec2<f32>(0.5, 0.5);
3190
+ return position;
3191
+ }
2618
3192
 
2619
3193
  @fragment
2620
3194
  fn fragmentMain(inputs: FragmentInputs) -> @location(0) vec4<f32> {
2621
- let position: vec2<f32> = billboardTexture_getTextureUV(inputs.coordinate);
2622
- return textureSample(backgroundTexture, backgroundTextureSampler, position);
3195
+ let position: vec2<f32> = billboardTexture_getTextureUV(inputs.uv);
3196
+ return textureSample(backgroundTexture, backgroundTextureSampler, position);
2623
3197
  }
2624
3198
  `
2625
3199
  );
@@ -2629,97 +3203,774 @@ var BACKGROUND_FS = (
2629
3203
  precision highp float;
2630
3204
 
2631
3205
  uniform sampler2D backgroundTexture;
3206
+
3207
+ layout(std140) uniform backgroundUniforms {
3208
+ vec2 scale;
3209
+ } background;
3210
+
3211
+ in vec2 coordinate;
2632
3212
  out vec4 fragColor;
2633
3213
 
2634
- vec2 billboardTexture_getTextureUV() {
2635
- ivec2 iTexSize = textureSize(backgroundTexture, 0);
2636
- vec2 texSize = vec2(float(iTexSize.x), float(iTexSize.y));
2637
- vec2 position = gl_FragCoord.xy / texSize;
3214
+ vec2 billboardTexture_getTextureUV(vec2 coord) {
3215
+ vec2 position = (coord - 0.5) / background.scale + 0.5;
2638
3216
  return position;
2639
3217
  }
2640
3218
 
2641
3219
  void main(void) {
2642
- vec2 position = billboardTexture_getTextureUV();
3220
+ vec2 position = billboardTexture_getTextureUV(coordinate);
2643
3221
  fragColor = texture(backgroundTexture, position);
2644
3222
  }
2645
3223
  `
2646
3224
  );
2647
3225
  var BackgroundTextureModel = class extends ClipSpace {
3226
+ backgroundTexture = null;
2648
3227
  constructor(device, props) {
2649
3228
  super(device, {
3229
+ ...props,
2650
3230
  id: props.id || "background-texture-model",
2651
3231
  source: BACKGROUND_FS_WGSL,
2652
3232
  fs: BACKGROUND_FS,
3233
+ modules: [...props.modules || [], backgroundModule],
2653
3234
  parameters: {
2654
3235
  depthWriteEnabled: false,
3236
+ ...props.parameters || {},
2655
3237
  ...props.blend ? {
2656
3238
  blend: true,
2657
3239
  blendColorOperation: "add",
2658
3240
  blendAlphaOperation: "add",
2659
- blendColorSrcFactor: "one",
2660
- blendColorDstFactor: "one-minus-src",
2661
- blendAlphaSrcFactor: "one",
2662
- blendAlphaDstFactor: "one-minus-src-alpha"
3241
+ blendColorSrcFactor: "one-minus-dst-alpha",
3242
+ blendColorDstFactor: "one",
3243
+ blendAlphaSrcFactor: "one-minus-dst-alpha",
3244
+ blendAlphaDstFactor: "one"
2663
3245
  } : {}
2664
3246
  }
2665
3247
  });
2666
3248
  if (!props.backgroundTexture) {
2667
3249
  throw new Error("BackgroundTextureModel requires a backgroundTexture prop");
2668
3250
  }
2669
- this.setTexture(props.backgroundTexture);
3251
+ this.setProps(props);
2670
3252
  }
2671
- setTexture(backgroundTexture) {
2672
- this.setBindings({
2673
- backgroundTexture
2674
- });
3253
+ /** Update the background texture */
3254
+ setProps(props) {
3255
+ const { backgroundTexture } = props;
3256
+ if (backgroundTexture) {
3257
+ this.setBindings({ backgroundTexture });
3258
+ if (backgroundTexture.isReady) {
3259
+ const texture = backgroundTexture instanceof DynamicTexture ? backgroundTexture.texture : backgroundTexture;
3260
+ this.backgroundTexture = texture;
3261
+ this.updateScale(texture);
3262
+ } else {
3263
+ backgroundTexture.ready.then((texture) => {
3264
+ this.backgroundTexture = texture;
3265
+ this.updateScale(texture);
3266
+ });
3267
+ }
3268
+ }
2675
3269
  }
2676
3270
  predraw() {
2677
- this.shaderInputs.setProps({});
2678
3271
  super.predraw();
2679
3272
  }
3273
+ updateScale(texture) {
3274
+ if (!texture) {
3275
+ this.shaderInputs.setProps({ background: { scale: [1, 1] } });
3276
+ return;
3277
+ }
3278
+ const [screenWidth, screenHeight] = this.device.getCanvasContext().getDrawingBufferSize();
3279
+ const textureWidth = texture.width;
3280
+ const textureHeight = texture.height;
3281
+ const screenAspect = screenWidth / screenHeight;
3282
+ const textureAspect = textureWidth / textureHeight;
3283
+ let scaleX = 1;
3284
+ let scaleY = 1;
3285
+ if (screenAspect > textureAspect) {
3286
+ scaleY = screenAspect / textureAspect;
3287
+ } else {
3288
+ scaleX = textureAspect / screenAspect;
3289
+ }
3290
+ this.shaderInputs.setProps({ background: { scale: [scaleX, scaleY] } });
3291
+ }
2680
3292
  };
2681
3293
 
2682
- // dist/scenegraph/scenegraph-node.js
2683
- var import_core11 = require("@math.gl/core");
2684
- var ScenegraphNode = class {
2685
- id;
2686
- matrix = new import_core11.Matrix4();
2687
- display = true;
2688
- position = new import_core11.Vector3();
2689
- rotation = new import_core11.Vector3();
2690
- scale = new import_core11.Vector3(1, 1, 1);
2691
- userData = {};
2692
- props = {};
3294
+ // dist/geometries/sphere-geometry.js
3295
+ var SphereGeometry = class extends Geometry {
2693
3296
  constructor(props = {}) {
2694
- const { id } = props;
2695
- this.id = id || uid(this.constructor.name);
2696
- this._setScenegraphNodeProps(props);
2697
- }
2698
- getBounds() {
2699
- return null;
2700
- }
2701
- destroy() {
2702
- }
2703
- /** @deprecated use .destroy() */
2704
- delete() {
2705
- this.destroy();
2706
- }
2707
- setProps(props) {
2708
- this._setScenegraphNodeProps(props);
2709
- return this;
3297
+ const { id = uid("sphere-geometry") } = props;
3298
+ const { indices, attributes } = tesselateSphere(props);
3299
+ super({
3300
+ ...props,
3301
+ id,
3302
+ topology: "triangle-list",
3303
+ indices,
3304
+ attributes: { ...attributes, ...props.attributes }
3305
+ });
3306
+ }
3307
+ };
3308
+ function tesselateSphere(props) {
3309
+ const { nlat = 10, nlong = 10 } = props;
3310
+ const startLat = 0;
3311
+ const endLat = Math.PI;
3312
+ const latRange = endLat - startLat;
3313
+ const startLong = 0;
3314
+ const endLong = 2 * Math.PI;
3315
+ const longRange = endLong - startLong;
3316
+ const numVertices = (nlat + 1) * (nlong + 1);
3317
+ const radius = (n1, n2, n3, u, v) => props.radius || 1;
3318
+ const positions = new Float32Array(numVertices * 3);
3319
+ const normals = new Float32Array(numVertices * 3);
3320
+ const texCoords = new Float32Array(numVertices * 2);
3321
+ const IndexType = numVertices > 65535 ? Uint32Array : Uint16Array;
3322
+ const indices = new IndexType(nlat * nlong * 6);
3323
+ for (let y = 0; y <= nlat; y++) {
3324
+ for (let x = 0; x <= nlong; x++) {
3325
+ const u = x / nlong;
3326
+ const v = y / nlat;
3327
+ const index = x + y * (nlong + 1);
3328
+ const i2 = index * 2;
3329
+ const i3 = index * 3;
3330
+ const theta = longRange * u;
3331
+ const phi = latRange * v;
3332
+ const sinTheta = Math.sin(theta);
3333
+ const cosTheta = Math.cos(theta);
3334
+ const sinPhi = Math.sin(phi);
3335
+ const cosPhi = Math.cos(phi);
3336
+ const ux = cosTheta * sinPhi;
3337
+ const uy = cosPhi;
3338
+ const uz = sinTheta * sinPhi;
3339
+ const r = radius(ux, uy, uz, u, v);
3340
+ positions[i3 + 0] = r * ux;
3341
+ positions[i3 + 1] = r * uy;
3342
+ positions[i3 + 2] = r * uz;
3343
+ normals[i3 + 0] = ux;
3344
+ normals[i3 + 1] = uy;
3345
+ normals[i3 + 2] = uz;
3346
+ texCoords[i2 + 0] = u;
3347
+ texCoords[i2 + 1] = 1 - v;
3348
+ }
3349
+ }
3350
+ const numVertsAround = nlong + 1;
3351
+ for (let x = 0; x < nlong; x++) {
3352
+ for (let y = 0; y < nlat; y++) {
3353
+ const index = (x * nlat + y) * 6;
3354
+ indices[index + 0] = y * numVertsAround + x;
3355
+ indices[index + 1] = y * numVertsAround + x + 1;
3356
+ indices[index + 2] = (y + 1) * numVertsAround + x;
3357
+ indices[index + 3] = (y + 1) * numVertsAround + x;
3358
+ indices[index + 4] = y * numVertsAround + x + 1;
3359
+ indices[index + 5] = (y + 1) * numVertsAround + x + 1;
3360
+ }
3361
+ }
3362
+ return {
3363
+ indices: { size: 1, value: indices },
3364
+ attributes: {
3365
+ POSITION: { size: 3, value: positions },
3366
+ NORMAL: { size: 3, value: normals },
3367
+ TEXCOORD_0: { size: 2, value: texCoords }
3368
+ }
3369
+ };
3370
+ }
3371
+
3372
+ // dist/models/light-model-utils.js
3373
+ var import_core11 = require("@math.gl/core");
3374
+ var DEFAULT_POINT_LIGHT_RADIUS_FACTOR = 0.02;
3375
+ var DEFAULT_SPOT_LIGHT_LENGTH_FACTOR = 0.12;
3376
+ var DEFAULT_DIRECTIONAL_LIGHT_LENGTH_FACTOR = 0.15;
3377
+ var DEFAULT_DIRECTIONAL_LIGHT_RADIUS_FACTOR = 0.2;
3378
+ var DEFAULT_DIRECTION_FALLBACK = [0, 1, 0];
3379
+ var DEFAULT_LIGHT_COLOR = [255, 255, 255];
3380
+ var DEFAULT_MARKER_SCALE = 1;
3381
+ var DIRECTIONAL_ANCHOR_DISTANCE_FACTOR = 0.35;
3382
+ var LIGHT_COLOR_FACTOR = 255;
3383
+ var MIN_SCENE_SCALE = 1;
3384
+ var SPOTLIGHT_OUTER_CONE_EPSILON = 0.01;
3385
+ var LIGHT_MARKER_PARAMETERS = {
3386
+ depthCompare: "less-equal",
3387
+ depthWriteEnabled: false,
3388
+ cullMode: "none"
3389
+ };
3390
+ var INSTANCE_BUFFER_LAYOUT = [
3391
+ { name: "instancePosition", format: "float32x3", stepMode: "instance" },
3392
+ { name: "instanceDirection", format: "float32x3", stepMode: "instance" },
3393
+ { name: "instanceScale", format: "float32x3", stepMode: "instance" },
3394
+ { name: "instanceColor", format: "float32x4", stepMode: "instance" }
3395
+ ];
3396
+ var lightMarker = {
3397
+ name: "lightMarker",
3398
+ props: {},
3399
+ uniforms: {},
3400
+ uniformTypes: {
3401
+ viewProjectionMatrix: "mat4x4<f32>"
3402
+ }
3403
+ };
3404
+ var CENTERED_LOCAL_POSITION_WGSL = "inputs.positions * inputs.instanceScale";
3405
+ var APEX_LOCAL_POSITION_WGSL = "vec3<f32>(inputs.positions.x * inputs.instanceScale.x, (inputs.positions.y - 0.5) * inputs.instanceScale.y, inputs.positions.z * inputs.instanceScale.z)";
3406
+ var CENTERED_LOCAL_POSITION_GLSL = "positions * instanceScale";
3407
+ var APEX_LOCAL_POSITION_GLSL = "vec3(positions.x * instanceScale.x, (positions.y - 0.5) * instanceScale.y, positions.z * instanceScale.z)";
3408
+ var BaseLightModel = class extends Model {
3409
+ lightModelProps;
3410
+ _instanceData;
3411
+ _managedBuffers;
3412
+ buildInstanceData;
3413
+ sizePropNames;
3414
+ constructor(device, props, options) {
3415
+ const instanceData = options.buildInstanceData(props);
3416
+ const managedBuffers = createManagedInstanceBuffers(device, props.id || options.idPrefix, instanceData);
3417
+ const shaderInputs = new ShaderInputs({ lightMarker });
3418
+ shaderInputs.setProps({
3419
+ lightMarker: { viewProjectionMatrix: createViewProjectionMatrix(props) }
3420
+ });
3421
+ const { source: source3, vs: vs3, fs: fs3 } = getLightMarkerShaders(options.anchorMode);
3422
+ const modelProps = props;
3423
+ super(device, {
3424
+ ...modelProps,
3425
+ id: props.id || options.idPrefix,
3426
+ source: source3,
3427
+ vs: vs3,
3428
+ fs: fs3,
3429
+ geometry: options.geometry,
3430
+ shaderInputs,
3431
+ bufferLayout: [...INSTANCE_BUFFER_LAYOUT],
3432
+ attributes: managedBuffers,
3433
+ instanceCount: instanceData.instanceCount,
3434
+ parameters: mergeLightMarkerParameters(props.parameters)
3435
+ });
3436
+ this.lightModelProps = props;
3437
+ this._instanceData = instanceData;
3438
+ this._managedBuffers = managedBuffers;
3439
+ this.buildInstanceData = options.buildInstanceData;
3440
+ this.sizePropNames = options.sizePropNames;
3441
+ }
3442
+ destroy() {
3443
+ super.destroy();
3444
+ destroyManagedInstanceBuffers(this._managedBuffers);
3445
+ this._managedBuffers = {};
3446
+ }
3447
+ draw(renderPass) {
3448
+ if (this.instanceCount === 0) {
3449
+ return true;
3450
+ }
3451
+ return super.draw(renderPass);
3452
+ }
3453
+ setProps(props) {
3454
+ this.lightModelProps = { ...this.lightModelProps, ...props };
3455
+ if (props.parameters) {
3456
+ this.setParameters(mergeLightMarkerParameters(this.lightModelProps.parameters));
3457
+ }
3458
+ if ("viewMatrix" in props || "projectionMatrix" in props) {
3459
+ this.shaderInputs.setProps({
3460
+ lightMarker: { viewProjectionMatrix: createViewProjectionMatrix(this.lightModelProps) }
3461
+ });
3462
+ this.setNeedsRedraw("lightMarker camera");
3463
+ }
3464
+ if (shouldRebuildInstanceData(props, this.sizePropNames)) {
3465
+ this.rebuildInstanceData();
3466
+ }
3467
+ }
3468
+ rebuildInstanceData() {
3469
+ const nextInstanceData = this.buildInstanceData(this.lightModelProps);
3470
+ const nextManagedBuffers = createManagedInstanceBuffers(this.device, this.id, nextInstanceData);
3471
+ this.setAttributes(nextManagedBuffers);
3472
+ this.setInstanceCount(nextInstanceData.instanceCount);
3473
+ destroyManagedInstanceBuffers(this._managedBuffers);
3474
+ this._managedBuffers = nextManagedBuffers;
3475
+ this._instanceData = nextInstanceData;
3476
+ }
3477
+ };
3478
+ function buildPointLightInstanceData(props) {
3479
+ const pointLights = getPointLights(props.lights);
3480
+ const context = getLightMarkerContext(props);
3481
+ const pointLightRadius = props.pointLightRadius ?? DEFAULT_POINT_LIGHT_RADIUS_FACTOR * context.sceneScale * context.markerScale;
3482
+ return createLightMarkerInstanceData(pointLights.length, (light, _index) => ({
3483
+ color: getDisplayColor(light),
3484
+ direction: DEFAULT_DIRECTION_FALLBACK,
3485
+ position: light.position,
3486
+ scale: [pointLightRadius, pointLightRadius, pointLightRadius]
3487
+ }), pointLights);
3488
+ }
3489
+ function buildSpotLightInstanceData(props) {
3490
+ const spotLights = getSpotLights(props.lights);
3491
+ const context = getLightMarkerContext(props);
3492
+ const spotLightLength = props.spotLightLength ?? DEFAULT_SPOT_LIGHT_LENGTH_FACTOR * context.sceneScale * context.markerScale;
3493
+ return createLightMarkerInstanceData(spotLights.length, (light, _index) => {
3494
+ const outerConeAngle = clamp(light.outerConeAngle ?? Math.PI / 4, 0, Math.PI / 2 - SPOTLIGHT_OUTER_CONE_EPSILON);
3495
+ const radius = Math.tan(outerConeAngle) * spotLightLength;
3496
+ return {
3497
+ color: getDisplayColor(light),
3498
+ direction: normalizeDirection(light.direction),
3499
+ position: light.position,
3500
+ scale: [radius, spotLightLength, radius]
3501
+ };
3502
+ }, spotLights);
3503
+ }
3504
+ function buildDirectionalLightInstanceData(props) {
3505
+ const directionalLights = getDirectionalLights(props.lights);
3506
+ const context = getLightMarkerContext(props);
3507
+ const directionalLightLength = props.directionalLightLength ?? DEFAULT_DIRECTIONAL_LIGHT_LENGTH_FACTOR * context.sceneScale * context.markerScale;
3508
+ const directionalLightRadius = directionalLightLength * DEFAULT_DIRECTIONAL_LIGHT_RADIUS_FACTOR;
3509
+ return createLightMarkerInstanceData(directionalLights.length, (light, _index) => {
3510
+ const direction = normalizeDirection(light.direction);
3511
+ const position = [
3512
+ context.sceneCenter[0] - direction[0] * context.sceneScale * DIRECTIONAL_ANCHOR_DISTANCE_FACTOR,
3513
+ context.sceneCenter[1] - direction[1] * context.sceneScale * DIRECTIONAL_ANCHOR_DISTANCE_FACTOR,
3514
+ context.sceneCenter[2] - direction[2] * context.sceneScale * DIRECTIONAL_ANCHOR_DISTANCE_FACTOR
3515
+ ];
3516
+ return {
3517
+ color: getDisplayColor(light),
3518
+ direction,
3519
+ position,
3520
+ scale: [directionalLightRadius, directionalLightLength, directionalLightRadius]
3521
+ };
3522
+ }, directionalLights);
3523
+ }
3524
+ function getPointLights(lights) {
3525
+ return lights.filter((light) => light.type === "point");
3526
+ }
3527
+ function getSpotLights(lights) {
3528
+ return lights.filter((light) => light.type === "spot");
3529
+ }
3530
+ function getDirectionalLights(lights) {
3531
+ return lights.filter((light) => light.type === "directional");
3532
+ }
3533
+ function getLightMarkerContext(props) {
3534
+ const bounds = getSceneBounds(props.lights, props.bounds);
3535
+ const sceneCenter = [
3536
+ (bounds[0][0] + bounds[1][0]) / 2,
3537
+ (bounds[0][1] + bounds[1][1]) / 2,
3538
+ (bounds[0][2] + bounds[1][2]) / 2
3539
+ ];
3540
+ const sceneScale = Math.max(Math.hypot(bounds[1][0] - bounds[0][0], bounds[1][1] - bounds[0][1], bounds[1][2] - bounds[0][2]), MIN_SCENE_SCALE);
3541
+ return {
3542
+ bounds,
3543
+ markerScale: Math.max(props.markerScale ?? DEFAULT_MARKER_SCALE, 0),
3544
+ sceneCenter,
3545
+ sceneScale
3546
+ };
3547
+ }
3548
+ function getDisplayColor(light) {
3549
+ const color = light.color || DEFAULT_LIGHT_COLOR;
3550
+ const intensity = Math.max(light.intensity ?? 1, 0);
3551
+ const brightness = clamp(0.35 + 0.3 * Math.log10(intensity + 1), 0.35, 1);
3552
+ return [
3553
+ clamp(color[0] / LIGHT_COLOR_FACTOR, 0, 1) * brightness,
3554
+ clamp(color[1] / LIGHT_COLOR_FACTOR, 0, 1) * brightness,
3555
+ clamp(color[2] / LIGHT_COLOR_FACTOR, 0, 1) * brightness,
3556
+ 1
3557
+ ];
3558
+ }
3559
+ function normalizeDirection(direction) {
3560
+ const [x, y, z] = direction || DEFAULT_DIRECTION_FALLBACK;
3561
+ const length = Math.hypot(x, y, z);
3562
+ if (length === 0) {
3563
+ return [...DEFAULT_DIRECTION_FALLBACK];
3564
+ }
3565
+ return [x / length, y / length, z / length];
3566
+ }
3567
+ function createLightMarkerInstanceData(instanceCount, getInstance, lights = []) {
3568
+ const instancePositions = new Float32Array(instanceCount * 3);
3569
+ const instanceDirections = new Float32Array(instanceCount * 3);
3570
+ const instanceScales = new Float32Array(instanceCount * 3);
3571
+ const instanceColors = new Float32Array(instanceCount * 4);
3572
+ for (const [index, light] of lights.entries()) {
3573
+ const instance = getInstance(light, index);
3574
+ instancePositions.set(instance.position, index * 3);
3575
+ instanceDirections.set(instance.direction, index * 3);
3576
+ instanceScales.set(instance.scale, index * 3);
3577
+ instanceColors.set(instance.color, index * 4);
3578
+ }
3579
+ return {
3580
+ instanceCount,
3581
+ instancePositions,
3582
+ instanceDirections,
3583
+ instanceScales,
3584
+ instanceColors
3585
+ };
3586
+ }
3587
+ function getSceneBounds(lights, bounds) {
3588
+ if (bounds) {
3589
+ return cloneBounds(bounds);
3590
+ }
3591
+ const positions = [
3592
+ ...getPointLights(lights).map((light) => light.position),
3593
+ ...getSpotLights(lights).map((light) => light.position)
3594
+ ];
3595
+ if (positions.length === 0) {
3596
+ return [
3597
+ [-0.5, -0.5, -0.5],
3598
+ [0.5, 0.5, 0.5]
3599
+ ];
3600
+ }
3601
+ const minBounds = [...positions[0]];
3602
+ const maxBounds = [...positions[0]];
3603
+ for (const position of positions.slice(1)) {
3604
+ minBounds[0] = Math.min(minBounds[0], position[0]);
3605
+ minBounds[1] = Math.min(minBounds[1], position[1]);
3606
+ minBounds[2] = Math.min(minBounds[2], position[2]);
3607
+ maxBounds[0] = Math.max(maxBounds[0], position[0]);
3608
+ maxBounds[1] = Math.max(maxBounds[1], position[1]);
3609
+ maxBounds[2] = Math.max(maxBounds[2], position[2]);
3610
+ }
3611
+ return [minBounds, maxBounds];
3612
+ }
3613
+ function cloneBounds(bounds) {
3614
+ return [[...bounds[0]], [...bounds[1]]];
3615
+ }
3616
+ function createManagedInstanceBuffers(device, idPrefix, instanceData) {
3617
+ return {
3618
+ instancePosition: device.createBuffer({
3619
+ id: `${idPrefix}-instance-position`,
3620
+ data: getBufferDataOrPlaceholder(instanceData.instancePositions, 3)
3621
+ }),
3622
+ instanceDirection: device.createBuffer({
3623
+ id: `${idPrefix}-instance-direction`,
3624
+ data: getBufferDataOrPlaceholder(instanceData.instanceDirections, 3)
3625
+ }),
3626
+ instanceScale: device.createBuffer({
3627
+ id: `${idPrefix}-instance-scale`,
3628
+ data: getBufferDataOrPlaceholder(instanceData.instanceScales, 3)
3629
+ }),
3630
+ instanceColor: device.createBuffer({
3631
+ id: `${idPrefix}-instance-color`,
3632
+ data: getBufferDataOrPlaceholder(instanceData.instanceColors, 4)
3633
+ })
3634
+ };
3635
+ }
3636
+ function getBufferDataOrPlaceholder(data, size) {
3637
+ return data.length > 0 ? data : new Float32Array(size);
3638
+ }
3639
+ function destroyManagedInstanceBuffers(managedBuffers) {
3640
+ for (const buffer of Object.values(managedBuffers)) {
3641
+ buffer == null ? void 0 : buffer.destroy();
3642
+ }
3643
+ }
3644
+ function createViewProjectionMatrix(props) {
3645
+ return new import_core11.Matrix4(props.projectionMatrix).multiplyRight(props.viewMatrix);
3646
+ }
3647
+ function shouldRebuildInstanceData(props, sizePropNames) {
3648
+ if ("lights" in props || "bounds" in props || "markerScale" in props) {
3649
+ return true;
3650
+ }
3651
+ return sizePropNames.some((sizePropName) => sizePropName in props);
3652
+ }
3653
+ function mergeLightMarkerParameters(parameters) {
3654
+ return {
3655
+ ...LIGHT_MARKER_PARAMETERS,
3656
+ ...parameters || {}
3657
+ };
3658
+ }
3659
+ function getLightMarkerShaders(anchorMode) {
3660
+ const localPositionWGSL = anchorMode === "apex" ? APEX_LOCAL_POSITION_WGSL : CENTERED_LOCAL_POSITION_WGSL;
3661
+ const localPositionGLSL = anchorMode === "apex" ? APEX_LOCAL_POSITION_GLSL : CENTERED_LOCAL_POSITION_GLSL;
3662
+ return {
3663
+ source: `struct lightMarkerUniforms {
3664
+ viewProjectionMatrix: mat4x4<f32>,
3665
+ };
3666
+
3667
+ @binding(0) @group(0) var<uniform> lightMarker : lightMarkerUniforms;
3668
+
3669
+ struct VertexInputs {
3670
+ @location(0) positions : vec3<f32>,
3671
+ @location(1) instancePosition : vec3<f32>,
3672
+ @location(2) instanceDirection : vec3<f32>,
3673
+ @location(3) instanceScale : vec3<f32>,
3674
+ @location(4) instanceColor : vec4<f32>,
3675
+ };
3676
+
3677
+ struct FragmentInputs {
3678
+ @builtin(position) Position : vec4<f32>,
3679
+ @location(0) color : vec4<f32>,
3680
+ };
3681
+
3682
+ fn lightMarker_rotate(localPosition: vec3<f32>, direction: vec3<f32>) -> vec3<f32> {
3683
+ let forward = normalize(direction);
3684
+ var helperAxis = vec3<f32>(0.0, 1.0, 0.0);
3685
+ if (abs(forward.y) > 0.999) {
3686
+ helperAxis = vec3<f32>(1.0, 0.0, 0.0);
3687
+ }
3688
+
3689
+ let tangent = normalize(cross(helperAxis, forward));
3690
+ let bitangent = cross(forward, tangent);
3691
+ return tangent * localPosition.x + forward * localPosition.y + bitangent * localPosition.z;
3692
+ }
3693
+
3694
+ @vertex
3695
+ fn vertexMain(inputs: VertexInputs) -> FragmentInputs {
3696
+ var outputs : FragmentInputs;
3697
+ let localPosition = ${localPositionWGSL};
3698
+ let worldPosition = inputs.instancePosition + lightMarker_rotate(localPosition, inputs.instanceDirection);
3699
+ outputs.Position = lightMarker.viewProjectionMatrix * vec4<f32>(worldPosition, 1.0);
3700
+ outputs.color = inputs.instanceColor;
3701
+ return outputs;
3702
+ }
3703
+
3704
+ @fragment
3705
+ fn fragmentMain(inputs: FragmentInputs) -> @location(0) vec4<f32> {
3706
+ return inputs.color;
3707
+ }
3708
+ `,
3709
+ vs: `#version 300 es
3710
+
3711
+ in vec3 positions;
3712
+ in vec3 instancePosition;
3713
+ in vec3 instanceDirection;
3714
+ in vec3 instanceScale;
3715
+ in vec4 instanceColor;
3716
+
3717
+ layout(std140) uniform lightMarkerUniforms {
3718
+ mat4 viewProjectionMatrix;
3719
+ } lightMarker;
3720
+
3721
+ out vec4 vColor;
3722
+
3723
+ vec3 lightMarker_rotate(vec3 localPosition, vec3 direction) {
3724
+ vec3 forward = normalize(direction);
3725
+ vec3 helperAxis = abs(forward.y) > 0.999 ? vec3(1.0, 0.0, 0.0) : vec3(0.0, 1.0, 0.0);
3726
+ vec3 tangent = normalize(cross(helperAxis, forward));
3727
+ vec3 bitangent = cross(forward, tangent);
3728
+ return tangent * localPosition.x + forward * localPosition.y + bitangent * localPosition.z;
3729
+ }
3730
+
3731
+ void main(void) {
3732
+ vec3 localPosition = ${localPositionGLSL};
3733
+ vec3 worldPosition = instancePosition + lightMarker_rotate(localPosition, instanceDirection);
3734
+ gl_Position = lightMarker.viewProjectionMatrix * vec4(worldPosition, 1.0);
3735
+ vColor = instanceColor;
3736
+ }
3737
+ `,
3738
+ fs: `#version 300 es
3739
+ precision highp float;
3740
+
3741
+ in vec4 vColor;
3742
+ out vec4 fragColor;
3743
+
3744
+ void main(void) {
3745
+ fragColor = vColor;
3746
+ }
3747
+ `
3748
+ };
3749
+ }
3750
+ function clamp(value, minValue, maxValue) {
3751
+ return Math.min(maxValue, Math.max(minValue, value));
3752
+ }
3753
+
3754
+ // dist/models/point-light-model.js
3755
+ var POINT_LIGHT_GEOMETRY = new SphereGeometry({
3756
+ nlat: 8,
3757
+ nlong: 12,
3758
+ radius: 1
3759
+ });
3760
+ var PointLightModel = class extends BaseLightModel {
3761
+ constructor(device, props) {
3762
+ super(device, props, {
3763
+ anchorMode: "centered",
3764
+ buildInstanceData: buildPointLightInstanceData,
3765
+ geometry: POINT_LIGHT_GEOMETRY,
3766
+ idPrefix: "point-light-model",
3767
+ sizePropNames: ["pointLightRadius"]
3768
+ });
3769
+ }
3770
+ };
3771
+
3772
+ // dist/geometries/truncated-cone-geometry.js
3773
+ var INDEX_OFFSETS = {
3774
+ x: [2, 0, 1],
3775
+ y: [0, 1, 2],
3776
+ z: [1, 2, 0]
3777
+ };
3778
+ var TruncatedConeGeometry = class extends Geometry {
3779
+ constructor(props = {}) {
3780
+ const { id = uid("truncated-code-geometry") } = props;
3781
+ const { indices, attributes } = tesselateTruncatedCone(props);
3782
+ super({
3783
+ ...props,
3784
+ id,
3785
+ topology: "triangle-list",
3786
+ indices,
3787
+ attributes: {
3788
+ POSITION: { size: 3, value: attributes.POSITION },
3789
+ NORMAL: { size: 3, value: attributes.NORMAL },
3790
+ TEXCOORD_0: { size: 2, value: attributes.TEXCOORD_0 },
3791
+ ...props.attributes
3792
+ }
3793
+ });
3794
+ }
3795
+ };
3796
+ function tesselateTruncatedCone(props = {}) {
3797
+ const { bottomRadius = 0, topRadius = 0, height = 1, nradial = 10, nvertical = 10, verticalAxis = "y", topCap = false, bottomCap = false } = props;
3798
+ const extra = (topCap ? 2 : 0) + (bottomCap ? 2 : 0);
3799
+ const numVertices = (nradial + 1) * (nvertical + 1 + extra);
3800
+ const slant = Math.atan2(bottomRadius - topRadius, height);
3801
+ const msin = Math.sin;
3802
+ const mcos = Math.cos;
3803
+ const mpi = Math.PI;
3804
+ const cosSlant = mcos(slant);
3805
+ const sinSlant = msin(slant);
3806
+ const start = topCap ? -2 : 0;
3807
+ const end = nvertical + (bottomCap ? 2 : 0);
3808
+ const vertsAroundEdge = nradial + 1;
3809
+ const indices = new Uint16Array(nradial * (nvertical + extra) * 6);
3810
+ const indexOffset = INDEX_OFFSETS[verticalAxis];
3811
+ const positions = new Float32Array(numVertices * 3);
3812
+ const normals = new Float32Array(numVertices * 3);
3813
+ const texCoords = new Float32Array(numVertices * 2);
3814
+ let i3 = 0;
3815
+ let i2 = 0;
3816
+ for (let i = start; i <= end; i++) {
3817
+ let v = i / nvertical;
3818
+ let y = height * v;
3819
+ let ringRadius;
3820
+ if (i < 0) {
3821
+ y = 0;
3822
+ v = 1;
3823
+ ringRadius = bottomRadius;
3824
+ } else if (i > nvertical) {
3825
+ y = height;
3826
+ v = 1;
3827
+ ringRadius = topRadius;
3828
+ } else {
3829
+ ringRadius = bottomRadius + (topRadius - bottomRadius) * (i / nvertical);
3830
+ }
3831
+ if (i === -2 || i === nvertical + 2) {
3832
+ ringRadius = 0;
3833
+ v = 0;
3834
+ }
3835
+ y -= height / 2;
3836
+ for (let j = 0; j < vertsAroundEdge; j++) {
3837
+ const sin = msin(j * mpi * 2 / nradial);
3838
+ const cos = mcos(j * mpi * 2 / nradial);
3839
+ positions[i3 + indexOffset[0]] = sin * ringRadius;
3840
+ positions[i3 + indexOffset[1]] = y;
3841
+ positions[i3 + indexOffset[2]] = cos * ringRadius;
3842
+ normals[i3 + indexOffset[0]] = i < 0 || i > nvertical ? 0 : sin * cosSlant;
3843
+ normals[i3 + indexOffset[1]] = i < 0 ? -1 : i > nvertical ? 1 : sinSlant;
3844
+ normals[i3 + indexOffset[2]] = i < 0 || i > nvertical ? 0 : cos * cosSlant;
3845
+ texCoords[i2 + 0] = j / nradial;
3846
+ texCoords[i2 + 1] = v;
3847
+ i2 += 2;
3848
+ i3 += 3;
3849
+ }
3850
+ }
3851
+ for (let i = 0; i < nvertical + extra; i++) {
3852
+ for (let j = 0; j < nradial; j++) {
3853
+ const index = (i * nradial + j) * 6;
3854
+ indices[index + 0] = vertsAroundEdge * (i + 0) + 0 + j;
3855
+ indices[index + 1] = vertsAroundEdge * (i + 0) + 1 + j;
3856
+ indices[index + 2] = vertsAroundEdge * (i + 1) + 1 + j;
3857
+ indices[index + 3] = vertsAroundEdge * (i + 0) + 0 + j;
3858
+ indices[index + 4] = vertsAroundEdge * (i + 1) + 1 + j;
3859
+ indices[index + 5] = vertsAroundEdge * (i + 1) + 0 + j;
3860
+ }
3861
+ }
3862
+ return {
3863
+ indices,
3864
+ attributes: {
3865
+ POSITION: positions,
3866
+ NORMAL: normals,
3867
+ TEXCOORD_0: texCoords
3868
+ }
3869
+ };
3870
+ }
3871
+
3872
+ // dist/geometries/cone-geometry.js
3873
+ var ConeGeometry = class extends TruncatedConeGeometry {
3874
+ constructor(props = {}) {
3875
+ const { id = uid("cone-geometry"), radius = 1, cap = true } = props;
3876
+ super({
3877
+ ...props,
3878
+ id,
3879
+ topRadius: 0,
3880
+ topCap: Boolean(cap),
3881
+ bottomCap: Boolean(cap),
3882
+ bottomRadius: radius
3883
+ });
3884
+ }
3885
+ };
3886
+
3887
+ // dist/models/spot-light-model.js
3888
+ var SPOT_LIGHT_GEOMETRY = new ConeGeometry({
3889
+ cap: true,
3890
+ nradial: 16,
3891
+ nvertical: 1,
3892
+ radius: 1
3893
+ });
3894
+ var SpotLightModel = class extends BaseLightModel {
3895
+ constructor(device, props) {
3896
+ super(device, props, {
3897
+ anchorMode: "apex",
3898
+ buildInstanceData: buildSpotLightInstanceData,
3899
+ geometry: SPOT_LIGHT_GEOMETRY,
3900
+ idPrefix: "spot-light-model",
3901
+ sizePropNames: ["spotLightLength"]
3902
+ });
3903
+ }
3904
+ };
3905
+
3906
+ // dist/models/directional-light-model.js
3907
+ var DIRECTIONAL_LIGHT_GEOMETRY = new ConeGeometry({
3908
+ cap: true,
3909
+ nradial: 12,
3910
+ nvertical: 1,
3911
+ radius: 1
3912
+ });
3913
+ var DirectionalLightModel = class extends BaseLightModel {
3914
+ constructor(device, props) {
3915
+ super(device, props, {
3916
+ anchorMode: "apex",
3917
+ buildInstanceData: buildDirectionalLightInstanceData,
3918
+ geometry: DIRECTIONAL_LIGHT_GEOMETRY,
3919
+ idPrefix: "directional-light-model",
3920
+ sizePropNames: ["directionalLightLength"]
3921
+ });
3922
+ }
3923
+ };
3924
+
3925
+ // dist/scenegraph/scenegraph-node.js
3926
+ var import_core12 = require("@math.gl/core");
3927
+ function assert(condition, message) {
3928
+ if (!condition) {
3929
+ throw new Error(message);
3930
+ }
3931
+ }
3932
+ var ScenegraphNode = class {
3933
+ id;
3934
+ matrix = new import_core12.Matrix4();
3935
+ display = true;
3936
+ position = new import_core12.Vector3();
3937
+ rotation = new import_core12.Vector3();
3938
+ scale = new import_core12.Vector3(1, 1, 1);
3939
+ userData = {};
3940
+ props = {};
3941
+ constructor(props = {}) {
3942
+ const { id } = props;
3943
+ this.id = id || uid(this.constructor.name);
3944
+ this._setScenegraphNodeProps(props);
3945
+ }
3946
+ getBounds() {
3947
+ return null;
3948
+ }
3949
+ destroy() {
3950
+ }
3951
+ /** @deprecated use .destroy() */
3952
+ delete() {
3953
+ this.destroy();
3954
+ }
3955
+ setProps(props) {
3956
+ this._setScenegraphNodeProps(props);
3957
+ return this;
2710
3958
  }
2711
3959
  toString() {
2712
3960
  return `{type: ScenegraphNode, id: ${this.id})}`;
2713
3961
  }
2714
3962
  setPosition(position) {
3963
+ assert(position.length === 3, "setPosition requires vector argument");
2715
3964
  this.position = position;
2716
3965
  return this;
2717
3966
  }
2718
3967
  setRotation(rotation) {
3968
+ assert(rotation.length === 3 || rotation.length === 4, "setRotation requires vector argument");
2719
3969
  this.rotation = rotation;
2720
3970
  return this;
2721
3971
  }
2722
3972
  setScale(scale) {
3973
+ assert(scale.length === 3, "setScale requires vector argument");
2723
3974
  this.scale = scale;
2724
3975
  return this;
2725
3976
  }
@@ -2747,17 +3998,18 @@ var ScenegraphNode = class {
2747
3998
  return this;
2748
3999
  }
2749
4000
  updateMatrix() {
2750
- const pos = this.position;
2751
- const rot = this.rotation;
2752
- const scale = this.scale;
2753
4001
  this.matrix.identity();
2754
- this.matrix.translate(pos);
2755
- this.matrix.rotateXYZ(rot);
2756
- this.matrix.scale(scale);
4002
+ this.matrix.translate(this.position);
4003
+ if (this.rotation.length === 4) {
4004
+ const rotationMatrix = new import_core12.Matrix4().fromQuaternion(this.rotation);
4005
+ this.matrix.multiplyRight(rotationMatrix);
4006
+ } else {
4007
+ this.matrix.rotateXYZ(this.rotation);
4008
+ }
4009
+ this.matrix.scale(this.scale);
2757
4010
  return this;
2758
4011
  }
2759
- update(options = {}) {
2760
- const { position, rotation, scale } = options;
4012
+ update({ position, rotation, scale } = {}) {
2761
4013
  if (position) {
2762
4014
  this.setPosition(position);
2763
4015
  }
@@ -2772,7 +4024,7 @@ var ScenegraphNode = class {
2772
4024
  }
2773
4025
  getCoordinateUniforms(viewMatrix, modelMatrix) {
2774
4026
  modelMatrix = modelMatrix || this.matrix;
2775
- const worldMatrix = new import_core11.Matrix4(viewMatrix).multiplyRight(modelMatrix);
4027
+ const worldMatrix = new import_core12.Matrix4(viewMatrix).multiplyRight(modelMatrix);
2776
4028
  const worldInverse = worldMatrix.invert();
2777
4029
  const worldInverseTranspose = worldInverse.transpose();
2778
4030
  return {
@@ -2807,16 +4059,17 @@ var ScenegraphNode = class {
2807
4059
  }
2808
4060
  */
2809
4061
  _setScenegraphNodeProps(props) {
2810
- if ("position" in props) {
4062
+ if (props == null ? void 0 : props.position) {
2811
4063
  this.setPosition(props.position);
2812
4064
  }
2813
- if ("rotation" in props) {
4065
+ if (props == null ? void 0 : props.rotation) {
2814
4066
  this.setRotation(props.rotation);
2815
4067
  }
2816
- if ("scale" in props) {
4068
+ if (props == null ? void 0 : props.scale) {
2817
4069
  this.setScale(props.scale);
2818
4070
  }
2819
- if ("matrix" in props) {
4071
+ this.updateMatrix();
4072
+ if (props == null ? void 0 : props.matrix) {
2820
4073
  this.setMatrix(props.matrix);
2821
4074
  }
2822
4075
  Object.assign(this.props, props);
@@ -2824,14 +4077,14 @@ var ScenegraphNode = class {
2824
4077
  };
2825
4078
 
2826
4079
  // dist/scenegraph/group-node.js
2827
- var import_core12 = require("@math.gl/core");
2828
- var import_core13 = require("@luma.gl/core");
4080
+ var import_core13 = require("@math.gl/core");
4081
+ var import_core14 = require("@luma.gl/core");
2829
4082
  var GroupNode = class extends ScenegraphNode {
2830
4083
  children;
2831
4084
  constructor(props = {}) {
2832
4085
  props = Array.isArray(props) ? { children: props } : props;
2833
4086
  const { children = [] } = props;
2834
- import_core13.log.assert(children.every((child) => child instanceof ScenegraphNode), "every child must an instance of ScenegraphNode");
4087
+ import_core14.log.assert(children.every((child) => child instanceof ScenegraphNode), "every child must an instance of ScenegraphNode");
2835
4088
  super(props);
2836
4089
  this.children = children;
2837
4090
  }
@@ -2846,12 +4099,12 @@ var GroupNode = class extends ScenegraphNode {
2846
4099
  return;
2847
4100
  }
2848
4101
  const [min, max] = bounds;
2849
- const center = new import_core12.Vector3(min).add(max).divide([2, 2, 2]);
4102
+ const center = new import_core13.Vector3(min).add(max).divide([2, 2, 2]);
2850
4103
  worldMatrix.transformAsPoint(center, center);
2851
- const halfSize = new import_core12.Vector3(max).subtract(min).divide([2, 2, 2]);
4104
+ const halfSize = new import_core13.Vector3(max).subtract(min).divide([2, 2, 2]);
2852
4105
  worldMatrix.transformAsVector(halfSize, halfSize);
2853
4106
  for (let v = 0; v < 8; v++) {
2854
- const position = new import_core12.Vector3(v & 1 ? -1 : 1, v & 2 ? -1 : 1, v & 4 ? -1 : 1).multiply(halfSize).add(center);
4107
+ const position = new import_core13.Vector3(v & 1 ? -1 : 1, v & 2 ? -1 : 1, v & 4 ? -1 : 1).multiply(halfSize).add(center);
2855
4108
  for (let i = 0; i < 3; i++) {
2856
4109
  result[0][i] = Math.min(result[0][i], position[i]);
2857
4110
  result[1][i] = Math.max(result[1][i], position[i]);
@@ -2864,190 +4117,86 @@ var GroupNode = class extends ScenegraphNode {
2864
4117
  return result;
2865
4118
  }
2866
4119
  destroy() {
2867
- this.children.forEach((child) => child.destroy());
2868
- this.removeAll();
2869
- super.destroy();
2870
- }
2871
- // Unpacks arrays and nested arrays of children
2872
- add(...children) {
2873
- for (const child of children) {
2874
- if (Array.isArray(child)) {
2875
- this.add(...child);
2876
- } else {
2877
- this.children.push(child);
2878
- }
2879
- }
2880
- return this;
2881
- }
2882
- remove(child) {
2883
- const children = this.children;
2884
- const indexOf = children.indexOf(child);
2885
- if (indexOf > -1) {
2886
- children.splice(indexOf, 1);
2887
- }
2888
- return this;
2889
- }
2890
- removeAll() {
2891
- this.children = [];
2892
- return this;
2893
- }
2894
- traverse(visitor, { worldMatrix = new import_core12.Matrix4() } = {}) {
2895
- const modelMatrix = new import_core12.Matrix4(worldMatrix).multiplyRight(this.matrix);
2896
- for (const child of this.children) {
2897
- if (child instanceof GroupNode) {
2898
- child.traverse(visitor, { worldMatrix: modelMatrix });
2899
- } else {
2900
- visitor(child, { worldMatrix: modelMatrix });
2901
- }
2902
- }
2903
- }
2904
- };
2905
-
2906
- // dist/scenegraph/model-node.js
2907
- var ModelNode = class extends ScenegraphNode {
2908
- model;
2909
- bounds = null;
2910
- managedResources;
2911
- // TODO - is this used? override callbacks to make sure we call them with this
2912
- // onBeforeRender = null;
2913
- // onAfterRender = null;
2914
- // AfterRender = null;
2915
- constructor(props) {
2916
- super(props);
2917
- this.model = props.model;
2918
- this.managedResources = props.managedResources || [];
2919
- this.bounds = props.bounds || null;
2920
- this.setProps(props);
2921
- }
2922
- destroy() {
2923
- if (this.model) {
2924
- this.model.destroy();
2925
- this.model = null;
2926
- }
2927
- this.managedResources.forEach((resource) => resource.destroy());
2928
- this.managedResources = [];
2929
- }
2930
- getBounds() {
2931
- return this.bounds;
2932
- }
2933
- // Expose model methods
2934
- draw(renderPass) {
2935
- return this.model.draw(renderPass);
2936
- }
2937
- };
2938
-
2939
- // dist/geometries/truncated-cone-geometry.js
2940
- var INDEX_OFFSETS = {
2941
- x: [2, 0, 1],
2942
- y: [0, 1, 2],
2943
- z: [1, 2, 0]
2944
- };
2945
- var TruncatedConeGeometry = class extends Geometry {
2946
- constructor(props = {}) {
2947
- const { id = uid("truncated-code-geometry") } = props;
2948
- const { indices, attributes } = tesselateTruncatedCone(props);
2949
- super({
2950
- ...props,
2951
- id,
2952
- topology: "triangle-list",
2953
- indices,
2954
- attributes: {
2955
- POSITION: { size: 3, value: attributes.POSITION },
2956
- NORMAL: { size: 3, value: attributes.NORMAL },
2957
- TEXCOORD_0: { size: 2, value: attributes.TEXCOORD_0 },
2958
- ...props.attributes
2959
- }
2960
- });
2961
- }
2962
- };
2963
- function tesselateTruncatedCone(props = {}) {
2964
- const { bottomRadius = 0, topRadius = 0, height = 1, nradial = 10, nvertical = 10, verticalAxis = "y", topCap = false, bottomCap = false } = props;
2965
- const extra = (topCap ? 2 : 0) + (bottomCap ? 2 : 0);
2966
- const numVertices = (nradial + 1) * (nvertical + 1 + extra);
2967
- const slant = Math.atan2(bottomRadius - topRadius, height);
2968
- const msin = Math.sin;
2969
- const mcos = Math.cos;
2970
- const mpi = Math.PI;
2971
- const cosSlant = mcos(slant);
2972
- const sinSlant = msin(slant);
2973
- const start = topCap ? -2 : 0;
2974
- const end = nvertical + (bottomCap ? 2 : 0);
2975
- const vertsAroundEdge = nradial + 1;
2976
- const indices = new Uint16Array(nradial * (nvertical + extra) * 6);
2977
- const indexOffset = INDEX_OFFSETS[verticalAxis];
2978
- const positions = new Float32Array(numVertices * 3);
2979
- const normals = new Float32Array(numVertices * 3);
2980
- const texCoords = new Float32Array(numVertices * 2);
2981
- let i3 = 0;
2982
- let i2 = 0;
2983
- for (let i = start; i <= end; i++) {
2984
- let v = i / nvertical;
2985
- let y = height * v;
2986
- let ringRadius;
2987
- if (i < 0) {
2988
- y = 0;
2989
- v = 1;
2990
- ringRadius = bottomRadius;
2991
- } else if (i > nvertical) {
2992
- y = height;
2993
- v = 1;
2994
- ringRadius = topRadius;
2995
- } else {
2996
- ringRadius = bottomRadius + (topRadius - bottomRadius) * (i / nvertical);
2997
- }
2998
- if (i === -2 || i === nvertical + 2) {
2999
- ringRadius = 0;
3000
- v = 0;
3001
- }
3002
- y -= height / 2;
3003
- for (let j = 0; j < vertsAroundEdge; j++) {
3004
- const sin = msin(j * mpi * 2 / nradial);
3005
- const cos = mcos(j * mpi * 2 / nradial);
3006
- positions[i3 + indexOffset[0]] = sin * ringRadius;
3007
- positions[i3 + indexOffset[1]] = y;
3008
- positions[i3 + indexOffset[2]] = cos * ringRadius;
3009
- normals[i3 + indexOffset[0]] = i < 0 || i > nvertical ? 0 : sin * cosSlant;
3010
- normals[i3 + indexOffset[1]] = i < 0 ? -1 : i > nvertical ? 1 : sinSlant;
3011
- normals[i3 + indexOffset[2]] = i < 0 || i > nvertical ? 0 : cos * cosSlant;
3012
- texCoords[i2 + 0] = j / nradial;
3013
- texCoords[i2 + 1] = v;
3014
- i2 += 2;
3015
- i3 += 3;
4120
+ this.children.forEach((child) => child.destroy());
4121
+ this.removeAll();
4122
+ super.destroy();
4123
+ }
4124
+ // Unpacks arrays and nested arrays of children
4125
+ add(...children) {
4126
+ for (const child of children) {
4127
+ if (Array.isArray(child)) {
4128
+ this.add(...child);
4129
+ } else {
4130
+ this.children.push(child);
4131
+ }
3016
4132
  }
4133
+ return this;
3017
4134
  }
3018
- for (let i = 0; i < nvertical + extra; i++) {
3019
- for (let j = 0; j < nradial; j++) {
3020
- const index = (i * nradial + j) * 6;
3021
- indices[index + 0] = vertsAroundEdge * (i + 0) + 0 + j;
3022
- indices[index + 1] = vertsAroundEdge * (i + 0) + 1 + j;
3023
- indices[index + 2] = vertsAroundEdge * (i + 1) + 1 + j;
3024
- indices[index + 3] = vertsAroundEdge * (i + 0) + 0 + j;
3025
- indices[index + 4] = vertsAroundEdge * (i + 1) + 1 + j;
3026
- indices[index + 5] = vertsAroundEdge * (i + 1) + 0 + j;
4135
+ remove(child) {
4136
+ const children = this.children;
4137
+ const indexOf = children.indexOf(child);
4138
+ if (indexOf > -1) {
4139
+ children.splice(indexOf, 1);
3027
4140
  }
4141
+ return this;
3028
4142
  }
3029
- return {
3030
- indices,
3031
- attributes: {
3032
- POSITION: positions,
3033
- NORMAL: normals,
3034
- TEXCOORD_0: texCoords
4143
+ removeAll() {
4144
+ this.children = [];
4145
+ return this;
4146
+ }
4147
+ traverse(visitor, { worldMatrix = new import_core13.Matrix4() } = {}) {
4148
+ const modelMatrix = new import_core13.Matrix4(worldMatrix).multiplyRight(this.matrix);
4149
+ for (const child of this.children) {
4150
+ if (child instanceof GroupNode) {
4151
+ child.traverse(visitor, { worldMatrix: modelMatrix });
4152
+ } else {
4153
+ visitor(child, { worldMatrix: modelMatrix });
4154
+ }
3035
4155
  }
3036
- };
3037
- }
4156
+ }
4157
+ preorderTraversal(visitor, { worldMatrix = new import_core13.Matrix4() } = {}) {
4158
+ const modelMatrix = new import_core13.Matrix4(worldMatrix).multiplyRight(this.matrix);
4159
+ visitor(this, { worldMatrix: modelMatrix });
4160
+ for (const child of this.children) {
4161
+ if (child instanceof GroupNode) {
4162
+ child.preorderTraversal(visitor, { worldMatrix: modelMatrix });
4163
+ } else {
4164
+ visitor(child, { worldMatrix: modelMatrix });
4165
+ }
4166
+ }
4167
+ }
4168
+ };
3038
4169
 
3039
- // dist/geometries/cone-geometry.js
3040
- var ConeGeometry = class extends TruncatedConeGeometry {
3041
- constructor(props = {}) {
3042
- const { id = uid("cone-geometry"), radius = 1, cap = true } = props;
3043
- super({
3044
- ...props,
3045
- id,
3046
- topRadius: 0,
3047
- topCap: Boolean(cap),
3048
- bottomCap: Boolean(cap),
3049
- bottomRadius: radius
3050
- });
4170
+ // dist/scenegraph/model-node.js
4171
+ var ModelNode = class extends ScenegraphNode {
4172
+ model;
4173
+ bounds = null;
4174
+ managedResources;
4175
+ // TODO - is this used? override callbacks to make sure we call them with this
4176
+ // onBeforeRender = null;
4177
+ // onAfterRender = null;
4178
+ // AfterRender = null;
4179
+ constructor(props) {
4180
+ super(props);
4181
+ this.model = props.model;
4182
+ this.managedResources = props.managedResources || [];
4183
+ this.bounds = props.bounds || null;
4184
+ this.setProps(props);
4185
+ }
4186
+ destroy() {
4187
+ if (this.model) {
4188
+ this.model.destroy();
4189
+ this.model = null;
4190
+ }
4191
+ this.managedResources.forEach((resource) => resource.destroy());
4192
+ this.managedResources = [];
4193
+ }
4194
+ getBounds() {
4195
+ return this.bounds;
4196
+ }
4197
+ // Expose model methods
4198
+ draw(renderPass) {
4199
+ return this.model.draw(renderPass);
3051
4200
  }
3052
4201
  };
3053
4202
 
@@ -3674,7 +4823,7 @@ var CylinderGeometry = class extends TruncatedConeGeometry {
3674
4823
  };
3675
4824
 
3676
4825
  // dist/geometries/ico-sphere-geometry.js
3677
- var import_core14 = require("@math.gl/core");
4826
+ var import_core15 = require("@math.gl/core");
3678
4827
  var ICO_POSITIONS = [-1, 0, 0, 0, 1, 0, 0, 0, -1, 0, 0, 1, 0, -1, 0, 1, 0, 0];
3679
4828
  var ICO_INDICES = [3, 4, 5, 3, 5, 1, 3, 1, 0, 3, 0, 4, 4, 0, 2, 4, 2, 5, 2, 0, 1, 5, 2, 1];
3680
4829
  var IcoSphereGeometry = class extends Geometry {
@@ -3772,7 +4921,7 @@ function tesselateIcosaHedron(props) {
3772
4921
  const u3 = 1 - phi3 / PI2;
3773
4922
  const vec1 = [x3 - x2, y3 - y2, z3 - z2];
3774
4923
  const vec2 = [x1 - x2, y1 - y2, z1 - z2];
3775
- const normal = new import_core14.Vector3(vec1).cross(vec2).normalize();
4924
+ const normal = new import_core15.Vector3(vec1).cross(vec2).normalize();
3776
4925
  let newIndex;
3777
4926
  if ((u1 === 0 || u2 === 0 || u3 === 0) && (u1 === 0 || u1 > 0.5) && (u2 === 0 || u2 > 0.5) && (u3 === 0 || u3 > 0.5)) {
3778
4927
  positions.push(positions[in1 + 0], positions[in1 + 1], positions[in1 + 2]);
@@ -3940,84 +5089,6 @@ function tesselatePlane(props) {
3940
5089
  return unpack ? unpackIndexedGeometry(geometry) : geometry;
3941
5090
  }
3942
5091
 
3943
- // dist/geometries/sphere-geometry.js
3944
- var SphereGeometry = class extends Geometry {
3945
- constructor(props = {}) {
3946
- const { id = uid("sphere-geometry") } = props;
3947
- const { indices, attributes } = tesselateSphere(props);
3948
- super({
3949
- ...props,
3950
- id,
3951
- topology: "triangle-list",
3952
- indices,
3953
- attributes: { ...attributes, ...props.attributes }
3954
- });
3955
- }
3956
- };
3957
- function tesselateSphere(props) {
3958
- const { nlat = 10, nlong = 10 } = props;
3959
- const startLat = 0;
3960
- const endLat = Math.PI;
3961
- const latRange = endLat - startLat;
3962
- const startLong = 0;
3963
- const endLong = 2 * Math.PI;
3964
- const longRange = endLong - startLong;
3965
- const numVertices = (nlat + 1) * (nlong + 1);
3966
- const radius = (n1, n2, n3, u, v) => props.radius || 1;
3967
- const positions = new Float32Array(numVertices * 3);
3968
- const normals = new Float32Array(numVertices * 3);
3969
- const texCoords = new Float32Array(numVertices * 2);
3970
- const IndexType = numVertices > 65535 ? Uint32Array : Uint16Array;
3971
- const indices = new IndexType(nlat * nlong * 6);
3972
- for (let y = 0; y <= nlat; y++) {
3973
- for (let x = 0; x <= nlong; x++) {
3974
- const u = x / nlong;
3975
- const v = y / nlat;
3976
- const index = x + y * (nlong + 1);
3977
- const i2 = index * 2;
3978
- const i3 = index * 3;
3979
- const theta = longRange * u;
3980
- const phi = latRange * v;
3981
- const sinTheta = Math.sin(theta);
3982
- const cosTheta = Math.cos(theta);
3983
- const sinPhi = Math.sin(phi);
3984
- const cosPhi = Math.cos(phi);
3985
- const ux = cosTheta * sinPhi;
3986
- const uy = cosPhi;
3987
- const uz = sinTheta * sinPhi;
3988
- const r = radius(ux, uy, uz, u, v);
3989
- positions[i3 + 0] = r * ux;
3990
- positions[i3 + 1] = r * uy;
3991
- positions[i3 + 2] = r * uz;
3992
- normals[i3 + 0] = ux;
3993
- normals[i3 + 1] = uy;
3994
- normals[i3 + 2] = uz;
3995
- texCoords[i2 + 0] = u;
3996
- texCoords[i2 + 1] = 1 - v;
3997
- }
3998
- }
3999
- const numVertsAround = nlong + 1;
4000
- for (let x = 0; x < nlong; x++) {
4001
- for (let y = 0; y < nlat; y++) {
4002
- const index = (x * nlat + y) * 6;
4003
- indices[index + 0] = y * numVertsAround + x;
4004
- indices[index + 1] = y * numVertsAround + x + 1;
4005
- indices[index + 2] = (y + 1) * numVertsAround + x;
4006
- indices[index + 3] = (y + 1) * numVertsAround + x;
4007
- indices[index + 4] = y * numVertsAround + x + 1;
4008
- indices[index + 5] = (y + 1) * numVertsAround + x + 1;
4009
- }
4010
- }
4011
- return {
4012
- indices: { size: 1, value: indices },
4013
- attributes: {
4014
- POSITION: { size: 3, value: positions },
4015
- NORMAL: { size: 3, value: normals },
4016
- TEXCOORD_0: { size: 2, value: texCoords }
4017
- }
4018
- };
4019
- }
4020
-
4021
5092
  // dist/application-utils/random.js
4022
5093
  function makeRandomGenerator() {
4023
5094
  let s = 1;
@@ -4032,17 +5103,45 @@ function fract(n) {
4032
5103
  return n - Math.floor(n);
4033
5104
  }
4034
5105
 
5106
+ // dist/application-utils/load-file.js
5107
+ var pathPrefix = "";
5108
+ function setPathPrefix(prefix) {
5109
+ pathPrefix = prefix;
5110
+ }
5111
+ async function loadImageBitmap(url, opts) {
5112
+ const image = new Image();
5113
+ image.crossOrigin = (opts == null ? void 0 : opts.crossOrigin) || "anonymous";
5114
+ image.src = url.startsWith("http") ? url : pathPrefix + url;
5115
+ await image.decode();
5116
+ return opts ? await createImageBitmap(image, opts) : await createImageBitmap(image);
5117
+ }
5118
+ async function loadImage(url, opts) {
5119
+ return await new Promise((resolve, reject) => {
5120
+ try {
5121
+ const image = new Image();
5122
+ image.onload = () => resolve(image);
5123
+ image.onerror = () => reject(new Error(`Could not load image ${url}.`));
5124
+ image.crossOrigin = (opts == null ? void 0 : opts.crossOrigin) || "anonymous";
5125
+ image.src = url.startsWith("http") ? url : pathPrefix + url;
5126
+ } catch (error) {
5127
+ reject(error);
5128
+ }
5129
+ });
5130
+ }
5131
+
4035
5132
  // dist/passes/shader-pass-renderer.js
4036
5133
  var import_shadertools5 = require("@luma.gl/shadertools");
4037
5134
 
4038
5135
  // dist/compute/swap.js
4039
- var import_core15 = require("@luma.gl/core");
5136
+ var import_core16 = require("@luma.gl/core");
4040
5137
  var Swap = class {
5138
+ id;
4041
5139
  /** The current resource - usually the source for renders or computations */
4042
5140
  current;
4043
5141
  /** The next resource - usually the target/destination for transforms / computations */
4044
5142
  next;
4045
5143
  constructor(props) {
5144
+ this.id = props.id || "swap";
4046
5145
  this.current = props.current;
4047
5146
  this.next = props.next;
4048
5147
  }
@@ -4063,18 +5162,21 @@ var SwapFramebuffers = class extends Swap {
4063
5162
  constructor(device, props) {
4064
5163
  var _a, _b;
4065
5164
  props = { ...props };
5165
+ const { width = 1, height = 1 } = props;
4066
5166
  let colorAttachments = (_a = props.colorAttachments) == null ? void 0 : _a.map((colorAttachment) => typeof colorAttachment !== "string" ? colorAttachment : device.createTexture({
5167
+ id: `${props.id}-texture-0`,
4067
5168
  format: colorAttachment,
4068
- usage: import_core15.Texture.SAMPLE | import_core15.Texture.RENDER | import_core15.Texture.COPY_SRC | import_core15.Texture.COPY_DST,
4069
- width: 1,
4070
- height: 1
5169
+ usage: import_core16.Texture.SAMPLE | import_core16.Texture.RENDER | import_core16.Texture.COPY_SRC | import_core16.Texture.COPY_DST,
5170
+ width,
5171
+ height
4071
5172
  }));
4072
5173
  const current = device.createFramebuffer({ ...props, colorAttachments });
4073
5174
  colorAttachments = (_b = props.colorAttachments) == null ? void 0 : _b.map((colorAttachment) => typeof colorAttachment !== "string" ? colorAttachment : device.createTexture({
5175
+ id: `${props.id}-texture-1`,
4074
5176
  format: colorAttachment,
4075
- usage: import_core15.Texture.TEXTURE | import_core15.Texture.COPY_SRC | import_core15.Texture.COPY_DST | import_core15.Texture.RENDER_ATTACHMENT,
4076
- width: 1,
4077
- height: 1
5177
+ usage: import_core16.Texture.SAMPLE | import_core16.Texture.RENDER | import_core16.Texture.COPY_SRC | import_core16.Texture.COPY_DST,
5178
+ width,
5179
+ height
4078
5180
  }));
4079
5181
  const next = device.createFramebuffer({ ...props, colorAttachments });
4080
5182
  super({ current, next });
@@ -4135,22 +5237,16 @@ function getFragmentShaderForRenderPass(options) {
4135
5237
  function getFilterShaderWGSL(func) {
4136
5238
  return (
4137
5239
  /* wgsl */
4138
- `// Binding 0:1 is reserved for shader passes
4139
- // @group(0) @binding(0) var<uniform> brightnessContrast : brightnessContrastUniforms;
4140
- @group(0) @binding(1) var texture: texture_2d<f32>;
4141
- @group(0) @binding(2) var sampler: sampler;
4142
-
4143
- struct FragmentInputs {
4144
- @location(0) fragUV: vec2f,
4145
- @location(1) fragPosition: vec4f,
4146
- @location(2) fragCoordinate: vec4f
4147
- };
5240
+ `@group(0) @binding(0) var sourceTexture: texture_2d<f32>;
5241
+ @group(0) @binding(2) var sourceTextureSampler: sampler;
4148
5242
 
4149
5243
  @fragment
4150
5244
  fn fragmentMain(inputs: FragmentInputs) -> @location(0) vec4f {
4151
- let texSize = textureDimensions(texture, 0);
4152
- var fragColor = textureSample(texture, sampler, fragUV);
4153
- fragColor = ${func}(gl_FragColor, texSize, texCoord);
5245
+ let texCoord = inputs.coordinate;
5246
+ let texSize = vec2f(textureDimensions(sourceTexture));
5247
+
5248
+ var fragColor = textureSample(sourceTexture, sourceTextureSampler, texCoord);
5249
+ fragColor = ${func}(fragColor, texSize, texCoord);
4154
5250
  return fragColor;
4155
5251
  }
4156
5252
  `
@@ -4159,23 +5255,14 @@ fn fragmentMain(inputs: FragmentInputs) -> @location(0) vec4f {
4159
5255
  function getSamplerShaderWGSL(func) {
4160
5256
  return (
4161
5257
  /* wgsl */
4162
- `// Binding 0:1 is reserved for shader passes
4163
- @group(0) @binding(0) var<uniform> brightnessContrast : brightnessContrastUniforms;
4164
- @group(0) @binding(1) var texture: texture_2d<f32>;
4165
- @group(0) @binding(2) var sampler: sampler;
4166
-
4167
- struct FragmentInputs = {
4168
- @location(0) fragUV: vec2f,
4169
- @location(1) fragPosition: vec4f,
4170
- @location(2) fragCoordinate: vec4f
4171
- };
5258
+ `@group(0) @binding(0) var sourceTexture: texture_2d<f32>;
5259
+ @group(0) @binding(2) var sourceTextureSampler: sampler;
4172
5260
 
4173
5261
  @fragment
4174
5262
  fn fragmentMain(inputs: FragmentInputs) -> @location(0) vec4f {
4175
- let texSize = textureDimensions(texture, 0);
4176
- var fragColor = textureSample(texture, sampler, fragUV);
4177
- fragColor = ${func}(gl_FragColor, texSize, texCoord);
4178
- return fragColor;
5263
+ let texCoord = inputs.coordinate;
5264
+ let texSize = vec2f(textureDimensions(sourceTexture));
5265
+ return ${func}(sourceTexture, sourceTextureSampler, texSize, texCoord);
4179
5266
  }
4180
5267
  `
4181
5268
  );
@@ -4234,8 +5321,6 @@ var ShaderPassRenderer = class {
4234
5321
  shaderInputs;
4235
5322
  passRenderers;
4236
5323
  swapFramebuffers;
4237
- /** For rendering to the screen */
4238
- clipSpace;
4239
5324
  textureModel;
4240
5325
  constructor(device, props) {
4241
5326
  this.device = device;
@@ -4251,35 +5336,6 @@ var ShaderPassRenderer = class {
4251
5336
  this.textureModel = new BackgroundTextureModel(device, {
4252
5337
  backgroundTexture: this.swapFramebuffers.current.colorAttachments[0].texture
4253
5338
  });
4254
- this.clipSpace = new ClipSpace(device, {
4255
- source: (
4256
- /* wgsl */
4257
- ` @group(0) @binding(0) var sourceTexture: texture_2d<f32>;
4258
- @group(0) @binding(1) var sourceTextureSampler: sampler;
4259
-
4260
- @fragment
4261
- fn fragmentMain(inputs: FragmentInputs) -> @location(0) vec4<f32> {
4262
- let texCoord: vec2<f32> = inputs.coordinate;
4263
- return textureSample(sourceTexture, sourceTextureSampler, texCoord);
4264
- }
4265
- `
4266
- ),
4267
- fs: (
4268
- /* glsl */
4269
- `#version 300 es
4270
-
4271
- uniform sampler2D sourceTexture;
4272
- in vec2 uv;
4273
- in vec2 coordinate;
4274
- out vec4 fragColor;
4275
-
4276
- void main() {
4277
- vec2 texCoord = coordinate;
4278
- fragColor = texture(sourceTexture, coordinate);
4279
- }
4280
- `
4281
- )
4282
- });
4283
5339
  this.passRenderers = props.shaderPasses.map((shaderPass) => new PassRenderer(device, shaderPass));
4284
5340
  }
4285
5341
  /** Destroys resources created by this ShaderPassRenderer */
@@ -4288,25 +5344,26 @@ void main() {
4288
5344
  subPassRenderer.destroy();
4289
5345
  }
4290
5346
  this.swapFramebuffers.destroy();
4291
- this.clipSpace.destroy();
5347
+ this.textureModel.destroy();
4292
5348
  }
4293
- resize(width, height) {
4294
- this.swapFramebuffers.resize({ width, height });
5349
+ resize(size) {
5350
+ size ||= this.device.getCanvasContext().getDrawingBufferSize();
5351
+ this.swapFramebuffers.resize({ width: size[0], height: size[1] });
4295
5352
  }
4296
5353
  renderToScreen(options) {
4297
5354
  const outputTexture = this.renderToTexture(options);
4298
5355
  if (!outputTexture) {
4299
5356
  return false;
4300
5357
  }
4301
- const framebuffer = this.device.getDefaultCanvasContext().getCurrentFramebuffer({ depthStencilAttachment: false });
5358
+ const framebuffer = this.device.getDefaultCanvasContext().getCurrentFramebuffer({ depthStencilFormat: false });
4302
5359
  const renderPass = this.device.beginRenderPass({
4303
5360
  id: "shader-pass-renderer-to-screen",
4304
5361
  framebuffer,
4305
- clearColor: [0, 0, 0, 1],
4306
- clearDepth: 1
5362
+ // clearColor: [1, 1, 0, 1],
5363
+ clearDepth: false
4307
5364
  });
4308
- this.clipSpace.setBindings({ sourceTexture: outputTexture });
4309
- this.clipSpace.draw(renderPass);
5365
+ this.textureModel.setProps({ backgroundTexture: outputTexture });
5366
+ this.textureModel.draw(renderPass);
4310
5367
  renderPass.end();
4311
5368
  return true;
4312
5369
  }
@@ -4318,14 +5375,14 @@ void main() {
4318
5375
  if (!sourceTexture.isReady) {
4319
5376
  return null;
4320
5377
  }
4321
- this.textureModel.destroy();
4322
- this.textureModel = new BackgroundTextureModel(this.device, {
4323
- backgroundTexture: sourceTexture
4324
- });
5378
+ if (this.passRenderers.length === 0) {
5379
+ return sourceTexture.texture;
5380
+ }
5381
+ this.textureModel.setProps({ backgroundTexture: sourceTexture });
4325
5382
  const clearTexturePass = this.device.beginRenderPass({
4326
5383
  id: "shader-pass-renderer-clear-texture",
4327
5384
  framebuffer: this.swapFramebuffers.current,
4328
- clearColor: [0, 0, 0, 1]
5385
+ clearColor: [1, 0, 0, 1]
4329
5386
  });
4330
5387
  this.textureModel.draw(clearTexturePass);
4331
5388
  clearTexturePass.end();
@@ -4412,7 +5469,7 @@ var SubPassRenderer = class {
4412
5469
  };
4413
5470
 
4414
5471
  // dist/compute/computation.js
4415
- var import_core16 = require("@luma.gl/core");
5472
+ var import_core17 = require("@luma.gl/core");
4416
5473
  var import_shadertools6 = require("@luma.gl/shadertools");
4417
5474
  var import_types2 = require("@math.gl/types");
4418
5475
  var LOG_DRAW_PRIORITY2 = 2;
@@ -4441,7 +5498,7 @@ var _Computation = class {
4441
5498
  props;
4442
5499
  _destroyed = false;
4443
5500
  constructor(device, props) {
4444
- var _a, _b, _c;
5501
+ var _a, _b, _c, _d;
4445
5502
  if (device.type !== "webgpu") {
4446
5503
  throw new Error("Computation is only supported in WebGPU");
4447
5504
  }
@@ -4453,11 +5510,11 @@ var _Computation = class {
4453
5510
  const moduleMap = Object.fromEntries(((_a = this.props.modules) == null ? void 0 : _a.map((module2) => [module2.name, module2])) || []);
4454
5511
  this.shaderInputs = props.shaderInputs || new ShaderInputs(moduleMap);
4455
5512
  this.setShaderInputs(this.shaderInputs);
4456
- this.props.shaderLayout ||= (0, import_shadertools6.getShaderLayoutFromWGSL)(this.props.source);
4457
5513
  const platformInfo = getPlatformInfo2(device);
4458
5514
  const modules = (((_b = this.props.modules) == null ? void 0 : _b.length) > 0 ? this.props.modules : (_c = this.shaderInputs) == null ? void 0 : _c.getModules()) || [];
4459
- this.pipelineFactory = props.pipelineFactory || PipelineFactory.getDefaultPipelineFactory(this.device);
4460
- this.shaderFactory = props.shaderFactory || ShaderFactory.getDefaultShaderFactory(this.device);
5515
+ this.props.shaderLayout = mergeShaderModuleBindingsIntoLayout(this.props.shaderLayout, modules) || null;
5516
+ this.pipelineFactory = props.pipelineFactory || import_core17.PipelineFactory.getDefaultPipelineFactory(this.device);
5517
+ this.shaderFactory = props.shaderFactory || import_core17.ShaderFactory.getDefaultShaderFactory(this.device);
4461
5518
  const { source: source3, getUniforms: getUniforms2 } = this.props.shaderAssembler.assembleWGSLShader({
4462
5519
  platformInfo,
4463
5520
  ...this.props,
@@ -4465,6 +5522,8 @@ var _Computation = class {
4465
5522
  });
4466
5523
  this.source = source3;
4467
5524
  this._getModuleUniforms = getUniforms2;
5525
+ const inferredShaderLayout = (_d = device.getShaderLayout) == null ? void 0 : _d.call(device, this.source);
5526
+ this.props.shaderLayout = mergeShaderModuleBindingsIntoLayout(this.props.shaderLayout || inferredShaderLayout || null, modules) || null;
4468
5527
  this.pipeline = this._updatePipeline();
4469
5528
  if (props.bindings) {
4470
5529
  this.setBindings(props.bindings);
@@ -4489,7 +5548,7 @@ var _Computation = class {
4489
5548
  this.pipeline = this._updatePipeline();
4490
5549
  this.pipeline.setBindings(this.bindings);
4491
5550
  computePass.setPipeline(this.pipeline);
4492
- computePass.setBindings([]);
5551
+ computePass.setBindings({});
4493
5552
  computePass.dispatch(x, y, z);
4494
5553
  } finally {
4495
5554
  this._logDrawCallEnd();
@@ -4511,10 +5570,12 @@ var _Computation = class {
4511
5570
  }
4512
5571
  setShaderInputs(shaderInputs) {
4513
5572
  this.shaderInputs = shaderInputs;
4514
- this._uniformStore = new import_core16.UniformStore(this.shaderInputs.modules);
4515
- for (const moduleName of Object.keys(this.shaderInputs.modules)) {
4516
- const uniformBuffer = this._uniformStore.getManagedUniformBuffer(this.device, moduleName);
4517
- this.bindings[`${moduleName}Uniforms`] = uniformBuffer;
5573
+ this._uniformStore = new import_core17.UniformStore(this.device, this.shaderInputs.modules);
5574
+ for (const [moduleName, module2] of Object.entries(this.shaderInputs.modules)) {
5575
+ if (shaderModuleHasUniforms(module2)) {
5576
+ const uniformBuffer = this._uniformStore.getManagedUniformBuffer(moduleName);
5577
+ this.bindings[`${moduleName}Uniforms`] = uniformBuffer;
5578
+ }
4518
5579
  }
4519
5580
  }
4520
5581
  /**
@@ -4548,7 +5609,7 @@ var _Computation = class {
4548
5609
  if (this._pipelineNeedsUpdate) {
4549
5610
  let prevShader = null;
4550
5611
  if (this.pipeline) {
4551
- import_core16.log.log(1, `Model ${this.id}: Recreating pipeline because "${this._pipelineNeedsUpdate}".`)();
5612
+ import_core17.log.log(1, `Model ${this.id}: Recreating pipeline because "${this._pipelineNeedsUpdate}".`)();
4552
5613
  prevShader = this.shader;
4553
5614
  }
4554
5615
  this._pipelineNeedsUpdate = false;
@@ -4572,33 +5633,33 @@ var _Computation = class {
4572
5633
  _lastLogTime = 0;
4573
5634
  _logOpen = false;
4574
5635
  _logDrawCallStart() {
4575
- const logDrawTimeout = import_core16.log.level > 3 ? 0 : LOG_DRAW_TIMEOUT2;
4576
- if (import_core16.log.level < 2 || Date.now() - this._lastLogTime < logDrawTimeout) {
5636
+ const logDrawTimeout = import_core17.log.level > 3 ? 0 : LOG_DRAW_TIMEOUT2;
5637
+ if (import_core17.log.level < 2 || Date.now() - this._lastLogTime < logDrawTimeout) {
4577
5638
  return;
4578
5639
  }
4579
5640
  this._lastLogTime = Date.now();
4580
5641
  this._logOpen = true;
4581
- import_core16.log.group(LOG_DRAW_PRIORITY2, `>>> DRAWING MODEL ${this.id}`, { collapsed: import_core16.log.level <= 2 })();
5642
+ import_core17.log.group(LOG_DRAW_PRIORITY2, `>>> DRAWING MODEL ${this.id}`, { collapsed: import_core17.log.level <= 2 })();
4582
5643
  }
4583
5644
  _logDrawCallEnd() {
4584
5645
  if (this._logOpen) {
4585
5646
  const uniformTable = this.shaderInputs.getDebugTable();
4586
- import_core16.log.table(LOG_DRAW_PRIORITY2, uniformTable)();
4587
- import_core16.log.groupEnd(LOG_DRAW_PRIORITY2)();
5647
+ import_core17.log.table(LOG_DRAW_PRIORITY2, uniformTable)();
5648
+ import_core17.log.groupEnd(LOG_DRAW_PRIORITY2)();
4588
5649
  this._logOpen = false;
4589
5650
  }
4590
5651
  }
4591
5652
  _drawCount = 0;
4592
5653
  // TODO - fix typing of luma data types
4593
5654
  _getBufferOrConstantValues(attribute, dataType) {
4594
- const TypedArrayConstructor = (0, import_core16.getTypedArrayConstructor)(dataType);
4595
- const typedArray = attribute instanceof import_core16.Buffer ? new TypedArrayConstructor(attribute.debugData) : attribute;
5655
+ const TypedArrayConstructor = import_core17.dataTypeDecoder.getTypedArrayConstructor(dataType);
5656
+ const typedArray = attribute instanceof import_core17.Buffer ? new TypedArrayConstructor(attribute.debugData) : attribute;
4596
5657
  return typedArray.toString();
4597
5658
  }
4598
5659
  };
4599
5660
  var Computation = _Computation;
4600
5661
  __publicField(Computation, "defaultProps", {
4601
- ...import_core16.ComputePipeline.defaultProps,
5662
+ ...import_core17.ComputePipeline.defaultProps,
4602
5663
  id: "unnamed",
4603
5664
  handle: void 0,
4604
5665
  userData: {},
@@ -4623,6 +5684,9 @@ function getPlatformInfo2(device) {
4623
5684
  };
4624
5685
  }
4625
5686
 
5687
+ // dist/modules/picking/picking-manager.js
5688
+ var import_core18 = require("@luma.gl/core");
5689
+
4626
5690
  // dist/modules/picking/picking-uniforms.js
4627
5691
  var DEFAULT_HIGHLIGHT_COLOR = [0, 1, 1, 1];
4628
5692
  var INVALID_INDEX = -1;
@@ -4640,7 +5704,7 @@ var GLSL_UNIFORMS = (
4640
5704
  `precision highp float;
4641
5705
  precision highp int;
4642
5706
 
4643
- uniform pickingUniforms {
5707
+ layout(std140) uniform pickingUniforms {
4644
5708
  int isActive;
4645
5709
  int indexMode;
4646
5710
  int batchIndex;
@@ -4655,15 +5719,17 @@ uniform pickingUniforms {
4655
5719
  var WGSL_UNIFORMS = (
4656
5720
  /* wgsl */
4657
5721
  `struct pickingUniforms {
4658
- isActive: int32;
4659
- indexMode: int32;
4660
- batchIndex: int32;
4661
-
4662
- isHighlightActive: int32;
4663
- highlightedBatchIndex: int32;
4664
- highlightedObjectIndex: int32;
4665
- highlightColor: vec4<f32>;
4666
- } picking;
5722
+ isActive: i32,
5723
+ indexMode: i32,
5724
+ batchIndex: i32,
5725
+
5726
+ isHighlightActive: i32,
5727
+ highlightedBatchIndex: i32,
5728
+ highlightedObjectIndex: i32,
5729
+ highlightColor: vec4<f32>,
5730
+ };
5731
+
5732
+ @group(0) @binding(auto) var<uniform> picking: pickingUniforms;
4667
5733
  `
4668
5734
  );
4669
5735
  function getUniforms(props = {}, prevUniforms) {
@@ -4675,25 +5741,36 @@ function getUniforms(props = {}, prevUniforms) {
4675
5741
  case "instance":
4676
5742
  uniforms.indexMode = 0;
4677
5743
  break;
4678
- case "custom":
5744
+ case "attribute":
4679
5745
  uniforms.indexMode = 1;
4680
5746
  break;
4681
5747
  case void 0:
4682
5748
  break;
4683
5749
  }
5750
+ if (typeof props.batchIndex === "number") {
5751
+ uniforms.batchIndex = props.batchIndex;
5752
+ }
4684
5753
  switch (props.highlightedObjectIndex) {
4685
5754
  case void 0:
4686
5755
  break;
4687
5756
  case null:
4688
5757
  uniforms.isHighlightActive = false;
4689
- uniforms.highlightedObjectIndex = INVALID_INDEX;
5758
+ uniforms.highlightedObjectIndex = INVALID_INDEX;
5759
+ break;
5760
+ default:
5761
+ uniforms.isHighlightActive = true;
5762
+ uniforms.highlightedObjectIndex = props.highlightedObjectIndex;
5763
+ }
5764
+ switch (props.highlightedBatchIndex) {
5765
+ case void 0:
5766
+ break;
5767
+ case null:
5768
+ uniforms.isHighlightActive = false;
5769
+ uniforms.highlightedBatchIndex = INVALID_INDEX;
4690
5770
  break;
4691
5771
  default:
4692
5772
  uniforms.isHighlightActive = true;
4693
- uniforms.highlightedObjectIndex = props.highlightedObjectIndex;
4694
- }
4695
- if (typeof props.highlightedBatchIndex === "number") {
4696
- uniforms.highlightedBatchIndex = props.highlightedBatchIndex;
5773
+ uniforms.highlightedBatchIndex = props.highlightedBatchIndex;
4697
5774
  }
4698
5775
  if (props.highlightColor) {
4699
5776
  uniforms.highlightColor = props.highlightColor;
@@ -4709,7 +5786,7 @@ var pickingUniforms = {
4709
5786
  isActive: false,
4710
5787
  indexMode: 0,
4711
5788
  batchIndex: 0,
4712
- isHighlightActive: true,
5789
+ isHighlightActive: false,
4713
5790
  highlightedBatchIndex: INVALID_INDEX,
4714
5791
  highlightedObjectIndex: INVALID_INDEX,
4715
5792
  highlightColor: DEFAULT_HIGHLIGHT_COLOR
@@ -4718,9 +5795,42 @@ var pickingUniforms = {
4718
5795
  };
4719
5796
 
4720
5797
  // dist/modules/picking/picking-manager.js
5798
+ var INDEX_PICKING_ATTACHMENT_INDEX = 1;
5799
+ var INDEX_PICKING_CLEAR_COLOR = new Int32Array([INVALID_INDEX, INVALID_INDEX, 0, 0]);
5800
+ function resolvePickingMode(deviceType, mode = "color", indexPickingSupported = deviceType === "webgpu") {
5801
+ if (mode === "auto") {
5802
+ return indexPickingSupported ? "index" : "color";
5803
+ }
5804
+ if (mode === "index" && !indexPickingSupported) {
5805
+ throw new Error(`Picking mode "${mode}" requires WebGPU or a WebGL device that supports renderable rg32sint textures.`);
5806
+ }
5807
+ return mode;
5808
+ }
5809
+ function supportsIndexPicking(device) {
5810
+ return device.type === "webgpu" || device.type === "webgl" && device.isTextureFormatRenderable("rg32sint");
5811
+ }
5812
+ var resolvePickingBackend = resolvePickingMode;
5813
+ function decodeIndexPickInfo(pixelData) {
5814
+ return {
5815
+ objectIndex: pixelData[0] === INVALID_INDEX ? null : pixelData[0],
5816
+ batchIndex: pixelData[1] === INVALID_INDEX ? null : pixelData[1]
5817
+ };
5818
+ }
5819
+ function decodeColorPickInfo(pixelData) {
5820
+ const encodedObjectIndex = pixelData[0] + pixelData[1] * 256 + pixelData[2] * 65536;
5821
+ if (encodedObjectIndex === 0) {
5822
+ return { objectIndex: null, batchIndex: null };
5823
+ }
5824
+ const batchIndex = pixelData[3] > 0 ? pixelData[3] - 1 : 0;
5825
+ return {
5826
+ objectIndex: encodedObjectIndex - 1,
5827
+ batchIndex
5828
+ };
5829
+ }
4721
5830
  var _PickingManager = class {
4722
5831
  device;
4723
5832
  props;
5833
+ mode;
4724
5834
  /** Info from latest pick operation */
4725
5835
  pickInfo = { batchIndex: null, objectIndex: null };
4726
5836
  /** Framebuffer used for picking */
@@ -4728,6 +5838,10 @@ var _PickingManager = class {
4728
5838
  constructor(device, props) {
4729
5839
  this.device = device;
4730
5840
  this.props = { ..._PickingManager.defaultProps, ...props };
5841
+ const requestedMode = props.mode ?? props.backend ?? _PickingManager.defaultProps.mode;
5842
+ this.props.mode = requestedMode;
5843
+ this.props.backend = requestedMode;
5844
+ this.mode = resolvePickingMode(this.device.type, requestedMode, supportsIndexPicking(this.device));
4731
5845
  }
4732
5846
  destroy() {
4733
5847
  var _a;
@@ -4736,58 +5850,44 @@ var _PickingManager = class {
4736
5850
  // TODO - Ask for a cached framebuffer? a Framebuffer factory?
4737
5851
  getFramebuffer() {
4738
5852
  if (!this.framebuffer) {
4739
- this.framebuffer = this.device.createFramebuffer({
4740
- colorAttachments: ["rgba8unorm", "rg32sint"],
4741
- depthStencilAttachment: "depth24plus"
4742
- });
5853
+ this.framebuffer = this.mode === "index" ? this.createIndexFramebuffer() : this.createColorFramebuffer();
4743
5854
  }
4744
5855
  return this.framebuffer;
4745
5856
  }
4746
5857
  /** Clear highlighted / picked object */
4747
5858
  clearPickState() {
4748
- this.props.shaderInputs.setProps({ picking: { highlightedObjectIndex: null } });
5859
+ this.setPickingProps({ highlightedBatchIndex: null, highlightedObjectIndex: null });
4749
5860
  }
4750
5861
  /** Prepare for rendering picking colors */
4751
5862
  beginRenderPass() {
4752
- var _a;
4753
5863
  const framebuffer = this.getFramebuffer();
4754
5864
  framebuffer.resize(this.device.getDefaultCanvasContext().getDevicePixelSize());
4755
- (_a = this.props.shaderInputs) == null ? void 0 : _a.setProps({ picking: { isActive: true } });
4756
- const pickingPass = this.device.beginRenderPass({
5865
+ this.setPickingProps({ isActive: true });
5866
+ return this.mode === "index" ? this.device.beginRenderPass({
5867
+ framebuffer,
5868
+ clearColors: [new Float32Array([0, 0, 0, 0]), INDEX_PICKING_CLEAR_COLOR],
5869
+ clearDepth: 1
5870
+ }) : this.device.beginRenderPass({
4757
5871
  framebuffer,
4758
- clearColors: [new Float32Array([0, 0, 0, 0]), new Int32Array([-1, -1, 0, 0])],
5872
+ clearColor: [0, 0, 0, 0],
4759
5873
  clearDepth: 1
4760
5874
  });
4761
- return pickingPass;
4762
5875
  }
4763
5876
  async updatePickInfo(mousePosition) {
4764
- var _a;
4765
5877
  const framebuffer = this.getFramebuffer();
4766
- const [pickX, pickY] = this.getPickPosition(mousePosition);
4767
- const pixelData = this.device.readPixelsToArrayWebGL(framebuffer, {
4768
- sourceX: pickX,
4769
- sourceY: pickY,
4770
- sourceWidth: 1,
4771
- sourceHeight: 1,
4772
- sourceAttachment: 1
4773
- });
4774
- if (!pixelData) {
5878
+ const pickPosition = this.getPickPosition(mousePosition);
5879
+ const pickInfo = await this.readPickInfo(framebuffer, pickPosition);
5880
+ if (!pickInfo) {
4775
5881
  return null;
4776
5882
  }
4777
- const pickInfo = {
4778
- objectIndex: pixelData[0] === INVALID_INDEX ? null : pixelData[0],
4779
- batchIndex: pixelData[1] === INVALID_INDEX ? null : pixelData[1]
4780
- };
4781
- if (pickInfo.objectIndex !== this.pickInfo.objectIndex || pickInfo.batchIndex !== this.pickInfo.batchIndex) {
5883
+ if (this.hasPickInfoChanged(pickInfo)) {
4782
5884
  this.pickInfo = pickInfo;
4783
5885
  this.props.onObjectPicked(pickInfo);
4784
5886
  }
4785
- (_a = this.props.shaderInputs) == null ? void 0 : _a.setProps({
4786
- picking: {
4787
- isActive: false,
4788
- highlightedBatchIndex: pickInfo.batchIndex,
4789
- highlightedObjectIndex: pickInfo.objectIndex
4790
- }
5887
+ this.setPickingProps({
5888
+ isActive: false,
5889
+ highlightedBatchIndex: pickInfo.batchIndex,
5890
+ highlightedObjectIndex: pickInfo.objectIndex
4791
5891
  });
4792
5892
  return this.pickInfo;
4793
5893
  }
@@ -4796,49 +5896,204 @@ var _PickingManager = class {
4796
5896
  * use the center pixel location in device pixel range
4797
5897
  */
4798
5898
  getPickPosition(mousePosition) {
4799
- const devicePixels = this.device.getDefaultCanvasContext().cssToDevicePixels(mousePosition);
5899
+ const yInvert = this.device.type !== "webgpu";
5900
+ const devicePixels = this.device.getDefaultCanvasContext().cssToDevicePixels(mousePosition, yInvert);
4800
5901
  const pickX = devicePixels.x + Math.floor(devicePixels.width / 2);
4801
5902
  const pickY = devicePixels.y + Math.floor(devicePixels.height / 2);
4802
5903
  return [pickX, pickY];
4803
5904
  }
5905
+ createIndexFramebuffer() {
5906
+ const colorTexture = this.device.createTexture({
5907
+ format: "rgba8unorm",
5908
+ width: 1,
5909
+ height: 1,
5910
+ usage: import_core18.Texture.RENDER_ATTACHMENT
5911
+ });
5912
+ const pickingTexture = this.device.createTexture({
5913
+ format: "rg32sint",
5914
+ width: 1,
5915
+ height: 1,
5916
+ usage: import_core18.Texture.RENDER_ATTACHMENT | import_core18.Texture.COPY_SRC
5917
+ });
5918
+ return this.device.createFramebuffer({
5919
+ colorAttachments: [colorTexture, pickingTexture],
5920
+ depthStencilAttachment: "depth24plus"
5921
+ });
5922
+ }
5923
+ createColorFramebuffer() {
5924
+ const pickingTexture = this.device.createTexture({
5925
+ format: "rgba8unorm",
5926
+ width: 1,
5927
+ height: 1,
5928
+ usage: import_core18.Texture.RENDER_ATTACHMENT | import_core18.Texture.COPY_SRC
5929
+ });
5930
+ return this.device.createFramebuffer({
5931
+ colorAttachments: [pickingTexture],
5932
+ depthStencilAttachment: "depth24plus"
5933
+ });
5934
+ }
5935
+ setPickingProps(props) {
5936
+ var _a;
5937
+ (_a = this.props.shaderInputs) == null ? void 0 : _a.setProps({ picking: props });
5938
+ }
5939
+ async readPickInfo(framebuffer, pickPosition) {
5940
+ return this.mode === "index" ? this.readIndexPickInfo(framebuffer, pickPosition) : this.readColorPickInfo(framebuffer, pickPosition);
5941
+ }
5942
+ async readIndexPickInfo(framebuffer, [pickX, pickY]) {
5943
+ var _a;
5944
+ if (this.device.type === "webgpu") {
5945
+ const pickTexture = (_a = framebuffer.colorAttachments[INDEX_PICKING_ATTACHMENT_INDEX]) == null ? void 0 : _a.texture;
5946
+ if (!pickTexture) {
5947
+ return null;
5948
+ }
5949
+ const layout = pickTexture.computeMemoryLayout({ width: 1, height: 1 });
5950
+ const readBuffer = this.device.createBuffer({
5951
+ byteLength: layout.byteLength,
5952
+ usage: import_core18.Buffer.COPY_DST | import_core18.Buffer.MAP_READ
5953
+ });
5954
+ try {
5955
+ pickTexture.readBuffer({
5956
+ x: pickX,
5957
+ y: pickY,
5958
+ width: 1,
5959
+ height: 1
5960
+ }, readBuffer);
5961
+ const pickDataView = await readBuffer.readAsync(0, layout.byteLength);
5962
+ return decodeIndexPickInfo(new Int32Array(pickDataView.buffer, pickDataView.byteOffset, 2));
5963
+ } finally {
5964
+ readBuffer.destroy();
5965
+ }
5966
+ }
5967
+ const pixelData = this.device.readPixelsToArrayWebGL(framebuffer, {
5968
+ sourceX: pickX,
5969
+ sourceY: pickY,
5970
+ sourceWidth: 1,
5971
+ sourceHeight: 1,
5972
+ sourceAttachment: INDEX_PICKING_ATTACHMENT_INDEX
5973
+ });
5974
+ return pixelData ? decodeIndexPickInfo(new Int32Array(pixelData.buffer, pixelData.byteOffset, 2)) : null;
5975
+ }
5976
+ async readColorPickInfo(framebuffer, [pickX, pickY]) {
5977
+ var _a;
5978
+ if (this.device.type === "webgpu") {
5979
+ const pickTexture = (_a = framebuffer.colorAttachments[0]) == null ? void 0 : _a.texture;
5980
+ if (!pickTexture) {
5981
+ return null;
5982
+ }
5983
+ const layout = pickTexture.computeMemoryLayout({ width: 1, height: 1 });
5984
+ const readBuffer = this.device.createBuffer({
5985
+ byteLength: layout.byteLength,
5986
+ usage: import_core18.Buffer.COPY_DST | import_core18.Buffer.MAP_READ
5987
+ });
5988
+ try {
5989
+ pickTexture.readBuffer({
5990
+ x: pickX,
5991
+ y: pickY,
5992
+ width: 1,
5993
+ height: 1
5994
+ }, readBuffer);
5995
+ const pickDataView = await readBuffer.readAsync(0, layout.byteLength);
5996
+ return decodeColorPickInfo(new Uint8Array(pickDataView.buffer, pickDataView.byteOffset, 4));
5997
+ } finally {
5998
+ readBuffer.destroy();
5999
+ }
6000
+ }
6001
+ const pixelData = this.device.readPixelsToArrayWebGL(framebuffer, {
6002
+ sourceX: pickX,
6003
+ sourceY: pickY,
6004
+ sourceWidth: 1,
6005
+ sourceHeight: 1,
6006
+ sourceAttachment: 0
6007
+ });
6008
+ return pixelData ? decodeColorPickInfo(new Uint8Array(pixelData.buffer, pixelData.byteOffset, 4)) : null;
6009
+ }
6010
+ hasPickInfoChanged(pickInfo) {
6011
+ return pickInfo.objectIndex !== this.pickInfo.objectIndex || pickInfo.batchIndex !== this.pickInfo.batchIndex;
6012
+ }
4804
6013
  };
4805
6014
  var PickingManager = _PickingManager;
4806
6015
  __publicField(PickingManager, "defaultProps", {
4807
6016
  shaderInputs: void 0,
4808
6017
  onObjectPicked: () => {
4809
- }
6018
+ },
6019
+ mode: "color",
6020
+ backend: "color"
4810
6021
  });
4811
6022
 
4812
- // dist/modules/picking/index-picking.js
6023
+ // dist/modules/picking/color-picking.js
4813
6024
  var source = (
4814
6025
  /* wgsl */
4815
6026
  `${WGSL_UNIFORMS}
4816
6027
 
4817
- const INDEX_PICKING_MODE_INSTANCE = 0;
4818
- const INDEX_PICKING_MODE_CUSTOM = 1;
4819
- const INDEX_PICKING_INVALID_INDEX = ${INVALID_INDEX}; // 2^32 - 1
6028
+ const COLOR_PICKING_INVALID_INDEX = ${INVALID_INDEX};
6029
+ const COLOR_PICKING_MAX_OBJECT_INDEX = 16777214;
6030
+ const COLOR_PICKING_MAX_BATCH_INDEX = 254;
4820
6031
 
4821
- struct indexPickingFragmentInputs = {
4822
- objectIndex: int32;
4823
- };
6032
+ fn picking_setObjectIndex(objectIndex: i32) -> i32 {
6033
+ return objectIndex;
6034
+ }
4824
6035
 
4825
- let indexPickingFragmentInputs: indexPickingFragmentInputs;
6036
+ fn picking_isObjectHighlighted(objectIndex: i32) -> bool {
6037
+ return
6038
+ picking.isHighlightActive != 0 &&
6039
+ picking.highlightedBatchIndex == picking.batchIndex &&
6040
+ picking.highlightedObjectIndex == objectIndex;
6041
+ }
4826
6042
 
4827
- /**
4828
- * Vertex shaders should call this function to set the object index.
4829
- * If using instance or vertex mode, argument will be ignored, 0 can be supplied.
4830
- */
4831
- fn picking_setObjectIndex(objectIndex: int32) {
4832
- switch (picking.indexMode) {
4833
- case INDEX_PICKING_MODE_INSTANCE, default: {
4834
- picking_objectIndex = instance_index;
4835
- };
4836
- case INDEX_PICKING_MODE_CUSTOM: {
4837
- picking_objectIndex = objectIndex;
4838
- };
6043
+ fn picking_filterHighlightColor(color: vec4<f32>, objectIndex: i32) -> vec4<f32> {
6044
+ if (picking.isActive != 0 || !picking_isObjectHighlighted(objectIndex)) {
6045
+ return color;
6046
+ }
6047
+
6048
+ let highLightAlpha = picking.highlightColor.a;
6049
+ let blendedAlpha = highLightAlpha + color.a * (1.0 - highLightAlpha);
6050
+ if (blendedAlpha == 0.0) {
6051
+ return vec4<f32>(color.rgb, 0.0);
6052
+ }
6053
+
6054
+ let highLightRatio = highLightAlpha / blendedAlpha;
6055
+ let blendedRGB = mix(color.rgb, picking.highlightColor.rgb, highLightRatio);
6056
+ return vec4<f32>(blendedRGB, blendedAlpha);
6057
+ }
6058
+
6059
+ fn picking_canEncodePickInfo(objectIndex: i32) -> bool {
6060
+ return
6061
+ objectIndex != COLOR_PICKING_INVALID_INDEX &&
6062
+ objectIndex >= 0 &&
6063
+ objectIndex <= COLOR_PICKING_MAX_OBJECT_INDEX &&
6064
+ picking.batchIndex >= 0 &&
6065
+ picking.batchIndex <= COLOR_PICKING_MAX_BATCH_INDEX;
6066
+ }
6067
+
6068
+ fn picking_getPickingColor(objectIndex: i32) -> vec4<f32> {
6069
+ if (!picking_canEncodePickInfo(objectIndex)) {
6070
+ return vec4<f32>(0.0, 0.0, 0.0, 0.0);
4839
6071
  }
6072
+
6073
+ let encodedObjectIndex = objectIndex + 1;
6074
+ let red = encodedObjectIndex % 256;
6075
+ let green = (encodedObjectIndex / 256) % 256;
6076
+ let blue = (encodedObjectIndex / 65536) % 256;
6077
+ let alpha = picking.batchIndex + 1;
6078
+
6079
+ return vec4<f32>(
6080
+ f32(red) / 255.0,
6081
+ f32(green) / 255.0,
6082
+ f32(blue) / 255.0,
6083
+ f32(alpha) / 255.0
6084
+ );
4840
6085
  }
4841
6086
 
6087
+ fn picking_filterPickingColor(color: vec4<f32>, objectIndex: i32) -> vec4<f32> {
6088
+ if (picking.isActive != 0) {
6089
+ if (!picking_canEncodePickInfo(objectIndex)) {
6090
+ discard;
6091
+ }
6092
+ return picking_getPickingColor(objectIndex);
6093
+ }
6094
+
6095
+ return color;
6096
+ }
4842
6097
  `
4843
6098
  );
4844
6099
  var vs = (
@@ -4848,14 +6103,10 @@ var vs = (
4848
6103
  const int INDEX_PICKING_MODE_INSTANCE = 0;
4849
6104
  const int INDEX_PICKING_MODE_CUSTOM = 1;
4850
6105
 
4851
- const int INDEX_PICKING_INVALID_INDEX = ${INVALID_INDEX}; // 2^32 - 1
6106
+ const int COLOR_PICKING_INVALID_INDEX = ${INVALID_INDEX};
4852
6107
 
4853
6108
  flat out int picking_objectIndex;
4854
6109
 
4855
- /**
4856
- * Vertex shaders should call this function to set the object index.
4857
- * If using instance or vertex mode, argument will be ignored, 0 can be supplied.
4858
- */
4859
6110
  void picking_setObjectIndex(int objectIndex) {
4860
6111
  switch (picking.indexMode) {
4861
6112
  case INDEX_PICKING_MODE_INSTANCE:
@@ -4872,36 +6123,29 @@ var fs = (
4872
6123
  /* glsl */
4873
6124
  `${GLSL_UNIFORMS}
4874
6125
 
4875
- const int INDEX_PICKING_INVALID_INDEX = ${INVALID_INDEX}; // 2^32 - 1
6126
+ const int COLOR_PICKING_INVALID_INDEX = ${INVALID_INDEX};
6127
+ const int COLOR_PICKING_MAX_OBJECT_INDEX = 16777214;
6128
+ const int COLOR_PICKING_MAX_BATCH_INDEX = 254;
4876
6129
 
4877
6130
  flat in int picking_objectIndex;
4878
6131
 
4879
- /**
4880
- * Check if this vertex is highlighted (part of the selected batch and object)
4881
- */
4882
6132
  bool picking_isFragmentHighlighted() {
4883
- return
6133
+ return
4884
6134
  bool(picking.isHighlightActive) &&
4885
6135
  picking.highlightedBatchIndex == picking.batchIndex &&
4886
6136
  picking.highlightedObjectIndex == picking_objectIndex
4887
6137
  ;
4888
6138
  }
4889
6139
 
4890
- /**
4891
- * Returns highlight color if this item is selected.
4892
- */
4893
6140
  vec4 picking_filterHighlightColor(vec4 color) {
4894
- // If we are still picking, we don't highlight
4895
6141
  if (bool(picking.isActive)) {
4896
6142
  return color;
4897
6143
  }
4898
6144
 
4899
- // If we are not highlighted, return color as is
4900
6145
  if (!picking_isFragmentHighlighted()) {
4901
6146
  return color;
4902
6147
  }
4903
-
4904
- // Blend in highlight color based on its alpha value
6148
+
4905
6149
  float highLightAlpha = picking.highlightColor.a;
4906
6150
  float blendedAlpha = highLightAlpha + color.a * (1.0 - highLightAlpha);
4907
6151
  float highLightRatio = highLightAlpha / blendedAlpha;
@@ -4910,28 +6154,40 @@ vec4 picking_filterHighlightColor(vec4 color) {
4910
6154
  return vec4(blendedRGB, blendedAlpha);
4911
6155
  }
4912
6156
 
4913
- /*
4914
- * Returns picking color if picking enabled else unmodified argument.
4915
- */
4916
- ivec4 picking_getPickingColor() {
4917
- // Assumes that colorAttachment0 is rg32int
4918
- // TODO? - we could render indices into a second color attachment and not mess with fragColor
4919
- return ivec4(picking_objectIndex, picking.batchIndex, 0u, 0u);
6157
+ bool picking_canEncodePickInfo(int objectIndex) {
6158
+ return
6159
+ objectIndex != COLOR_PICKING_INVALID_INDEX &&
6160
+ objectIndex >= 0 &&
6161
+ objectIndex <= COLOR_PICKING_MAX_OBJECT_INDEX &&
6162
+ picking.batchIndex >= 0 &&
6163
+ picking.batchIndex <= COLOR_PICKING_MAX_BATCH_INDEX;
6164
+ }
6165
+
6166
+ vec4 picking_getPickingColor() {
6167
+ if (!picking_canEncodePickInfo(picking_objectIndex)) {
6168
+ return vec4(0.0);
6169
+ }
6170
+
6171
+ int encodedObjectIndex = picking_objectIndex + 1;
6172
+ int red = encodedObjectIndex % 256;
6173
+ int green = (encodedObjectIndex / 256) % 256;
6174
+ int blue = (encodedObjectIndex / 65536) % 256;
6175
+ int alpha = picking.batchIndex + 1;
6176
+
6177
+ return vec4(float(red), float(green), float(blue), float(alpha)) / 255.0;
4920
6178
  }
4921
6179
 
4922
6180
  vec4 picking_filterPickingColor(vec4 color) {
4923
6181
  if (bool(picking.isActive)) {
4924
- if (picking_objectIndex == INDEX_PICKING_INVALID_INDEX) {
6182
+ if (!picking_canEncodePickInfo(picking_objectIndex)) {
4925
6183
  discard;
4926
6184
  }
6185
+ return picking_getPickingColor();
4927
6186
  }
6187
+
4928
6188
  return color;
4929
6189
  }
4930
6190
 
4931
- /*
4932
- * Returns picking color if picking is enabled if not
4933
- * highlight color if this item is selected, otherwise unmodified argument.
4934
- */
4935
6191
  vec4 picking_filterColor(vec4 color) {
4936
6192
  vec4 outColor = color;
4937
6193
  outColor = picking_filterHighlightColor(outColor);
@@ -4948,87 +6204,81 @@ var picking = {
4948
6204
  fs
4949
6205
  };
4950
6206
 
4951
- // dist/modules/picking/color-picking.js
6207
+ // dist/modules/picking/index-picking.js
4952
6208
  var source2 = (
4953
6209
  /* wgsl */
4954
6210
  `${WGSL_UNIFORMS}
4955
- `
4956
- );
4957
- var vs2 = (
4958
- /* glsl */
4959
- `${GLSL_UNIFORMS}
4960
- out vec4 picking_vRGBcolor_Avalid;
4961
-
4962
- // Normalize unsigned byte color to 0-1 range
4963
- vec3 picking_normalizeColor(vec3 color) {
4964
- return picking.useFloatColors > 0.5 ? color : color / 255.0;
4965
- }
4966
6211
 
4967
- // Normalize unsigned byte color to 0-1 range
4968
- vec4 picking_normalizeColor(vec4 color) {
4969
- return picking.useFloatColors > 0.5 ? color : color / 255.0;
4970
- }
4971
-
4972
- bool picking_isColorZero(vec3 color) {
4973
- return dot(color, vec3(1.0)) < 0.00001;
4974
- }
6212
+ const INDEX_PICKING_MODE_INSTANCE = 0;
6213
+ const INDEX_PICKING_MODE_CUSTOM = 1;
6214
+ const INDEX_PICKING_INVALID_INDEX = ${INVALID_INDEX}; // 2^32 - 1
4975
6215
 
4976
- bool picking_isColorValid(vec3 color) {
4977
- return dot(color, vec3(1.0)) > 0.00001;
6216
+ /**
6217
+ * WGSL shaders need to carry the returned object index through their own stage outputs.
6218
+ */
6219
+ fn picking_setObjectIndex(objectIndex: i32) -> i32 {
6220
+ return objectIndex;
4978
6221
  }
4979
6222
 
4980
- // Check if this vertex is highlighted
4981
- bool isVertexHighlighted(vec3 vertexColor) {
4982
- vec3 highlightedObjectColor = picking_normalizeColor(picking.highlightedObjectColor);
6223
+ fn picking_isObjectHighlighted(objectIndex: i32) -> bool {
4983
6224
  return
4984
- bool(picking.isHighlightActive) && picking_isColorZero(abs(vertexColor - highlightedObjectColor));
6225
+ picking.isHighlightActive != 0 &&
6226
+ picking.highlightedBatchIndex == picking.batchIndex &&
6227
+ picking.highlightedObjectIndex == objectIndex;
4985
6228
  }
4986
6229
 
4987
- // Set the current picking color
4988
- void picking_setPickingColor(vec3 pickingColor) {
4989
- pickingColor = picking_normalizeColor(pickingColor);
4990
-
4991
- if (bool(picking.isActive)) {
4992
- // Use alpha as the validity flag. If pickingColor is [0, 0, 0] fragment is non-pickable
4993
- picking_vRGBcolor_Avalid.a = float(picking_isColorValid(pickingColor));
4994
-
4995
- if (!bool(picking.isAttribute)) {
4996
- // Stores the picking color so that the fragment shader can render it during picking
4997
- picking_vRGBcolor_Avalid.rgb = pickingColor;
4998
- }
4999
- } else {
5000
- // Do the comparison with selected item color in vertex shader as it should mean fewer compares
5001
- picking_vRGBcolor_Avalid.a = float(isVertexHighlighted(pickingColor));
6230
+ fn picking_filterHighlightColor(color: vec4<f32>, objectIndex: i32) -> vec4<f32> {
6231
+ if (picking.isActive != 0 || !picking_isObjectHighlighted(objectIndex)) {
6232
+ return color;
5002
6233
  }
5003
- }
5004
6234
 
5005
- void picking_setObjectIndex(uint objectIndex) {
5006
- if (bool(picking.isActive)) {
5007
- uint index = objectIndex;
5008
- if (picking.indexMode == PICKING_INDEX_MODE_INSTANCE) {
5009
- index = uint(gl_InstanceID);
5010
- }
5011
- picking_vRGBcolor_Avalid.r = float(index % 255) / 255.0;
5012
- picking_vRGBcolor_Avalid.g = float((index / 255) % 255) / 255.0;
5013
- picking_vRGBcolor_Avalid.b = float((index / 255 / 255) %255) / 255.0;
6235
+ let highLightAlpha = picking.highlightColor.a;
6236
+ let blendedAlpha = highLightAlpha + color.a * (1.0 - highLightAlpha);
6237
+ if (blendedAlpha == 0.0) {
6238
+ return vec4<f32>(color.rgb, 0.0);
5014
6239
  }
6240
+
6241
+ let highLightRatio = highLightAlpha / blendedAlpha;
6242
+ let blendedRGB = mix(color.rgb, picking.highlightColor.rgb, highLightRatio);
6243
+ return vec4<f32>(blendedRGB, blendedAlpha);
5015
6244
  }
5016
6245
 
5017
- void picking_setPickingAttribute(float value) {
5018
- if (bool(picking.isAttribute)) {
5019
- picking_vRGBcolor_Avalid.r = value;
6246
+ fn picking_filterPickingColor(color: vec4<f32>, objectIndex: i32) -> vec4<f32> {
6247
+ if (picking.isActive != 0 && objectIndex == INDEX_PICKING_INVALID_INDEX) {
6248
+ discard;
5020
6249
  }
6250
+ return color;
5021
6251
  }
5022
6252
 
5023
- void picking_setPickingAttribute(vec2 value) {
5024
- if (bool(picking.isAttribute)) {
5025
- picking_vRGBcolor_Avalid.rg = value;
5026
- }
6253
+ fn picking_getPickingColor(objectIndex: i32) -> vec2<i32> {
6254
+ return vec2<i32>(objectIndex, picking.batchIndex);
5027
6255
  }
5028
6256
 
5029
- void picking_setPickingAttribute(vec3 value) {
5030
- if (bool(picking.isAttribute)) {
5031
- picking_vRGBcolor_Avalid.rgb = value;
6257
+ `
6258
+ );
6259
+ var vs2 = (
6260
+ /* glsl */
6261
+ `${GLSL_UNIFORMS}
6262
+
6263
+ const int INDEX_PICKING_MODE_INSTANCE = 0;
6264
+ const int INDEX_PICKING_MODE_CUSTOM = 1;
6265
+
6266
+ const int INDEX_PICKING_INVALID_INDEX = ${INVALID_INDEX}; // 2^32 - 1
6267
+
6268
+ flat out int picking_objectIndex;
6269
+
6270
+ /**
6271
+ * Vertex shaders should call this function to set the object index.
6272
+ * If using instance or vertex mode, argument will be ignored, 0 can be supplied.
6273
+ */
6274
+ void picking_setObjectIndex(int objectIndex) {
6275
+ switch (picking.indexMode) {
6276
+ case INDEX_PICKING_MODE_INSTANCE:
6277
+ picking_objectIndex = gl_InstanceID;
6278
+ break;
6279
+ case INDEX_PICKING_MODE_CUSTOM:
6280
+ picking_objectIndex = objectIndex;
6281
+ break;
5032
6282
  }
5033
6283
  }
5034
6284
  `
@@ -5037,41 +6287,58 @@ var fs2 = (
5037
6287
  /* glsl */
5038
6288
  `${GLSL_UNIFORMS}
5039
6289
 
5040
- in vec4 picking_vRGBcolor_Avalid;
6290
+ const int INDEX_PICKING_INVALID_INDEX = ${INVALID_INDEX}; // 2^32 - 1
5041
6291
 
5042
- /*
6292
+ flat in int picking_objectIndex;
6293
+
6294
+ /**
6295
+ * Check if this vertex is highlighted (part of the selected batch and object)
6296
+ */
6297
+ bool picking_isFragmentHighlighted() {
6298
+ return
6299
+ bool(picking.isHighlightActive) &&
6300
+ picking.highlightedBatchIndex == picking.batchIndex &&
6301
+ picking.highlightedObjectIndex == picking_objectIndex
6302
+ ;
6303
+ }
6304
+
6305
+ /**
5043
6306
  * Returns highlight color if this item is selected.
5044
6307
  */
5045
6308
  vec4 picking_filterHighlightColor(vec4 color) {
5046
6309
  // If we are still picking, we don't highlight
5047
- if (picking.isActive > 0.5) {
6310
+ if (bool(picking.isActive)) {
5048
6311
  return color;
5049
6312
  }
5050
6313
 
5051
- bool selected = bool(picking_vRGBcolor_Avalid.a);
5052
-
5053
- if (selected) {
5054
- // Blend in highlight color based on its alpha value
5055
- float highLightAlpha = picking.highlightColor.a;
5056
- float blendedAlpha = highLightAlpha + color.a * (1.0 - highLightAlpha);
5057
- float highLightRatio = highLightAlpha / blendedAlpha;
5058
-
5059
- vec3 blendedRGB = mix(color.rgb, picking.highlightColor.rgb, highLightRatio);
5060
- return vec4(blendedRGB, blendedAlpha);
5061
- } else {
6314
+ // If we are not highlighted, return color as is
6315
+ if (!picking_isFragmentHighlighted()) {
5062
6316
  return color;
5063
6317
  }
6318
+
6319
+ // Blend in highlight color based on its alpha value
6320
+ float highLightAlpha = picking.highlightColor.a;
6321
+ float blendedAlpha = highLightAlpha + color.a * (1.0 - highLightAlpha);
6322
+ float highLightRatio = highLightAlpha / blendedAlpha;
6323
+
6324
+ vec3 blendedRGB = mix(color.rgb, picking.highlightColor.rgb, highLightRatio);
6325
+ return vec4(blendedRGB, blendedAlpha);
5064
6326
  }
5065
6327
 
5066
6328
  /*
5067
6329
  * Returns picking color if picking enabled else unmodified argument.
5068
6330
  */
6331
+ ivec4 picking_getPickingColor() {
6332
+ // Assumes that colorAttachment0 is rg32int
6333
+ // TODO? - we could render indices into a second color attachment and not mess with fragColor
6334
+ return ivec4(picking_objectIndex, picking.batchIndex, 0u, 0u);
6335
+ }
6336
+
5069
6337
  vec4 picking_filterPickingColor(vec4 color) {
5070
6338
  if (bool(picking.isActive)) {
5071
- if (picking_vRGBcolor_Avalid.a == 0.0) {
6339
+ if (picking_objectIndex == INDEX_PICKING_INVALID_INDEX) {
5072
6340
  discard;
5073
6341
  }
5074
- return picking_vRGBcolor_Avalid;
5075
6342
  }
5076
6343
  return color;
5077
6344
  }
@@ -5081,8 +6348,10 @@ vec4 picking_filterPickingColor(vec4 color) {
5081
6348
  * highlight color if this item is selected, otherwise unmodified argument.
5082
6349
  */
5083
6350
  vec4 picking_filterColor(vec4 color) {
5084
- vec4 highlightColor = picking_filterHighlightColor(color);
5085
- return picking_filterPickingColor(highlightColor);
6351
+ vec4 outColor = color;
6352
+ outColor = picking_filterHighlightColor(outColor);
6353
+ outColor = picking_filterPickingColor(outColor);
6354
+ return outColor;
5086
6355
  }
5087
6356
  `
5088
6357
  );
@@ -5094,6 +6363,15 @@ var picking2 = {
5094
6363
  fs: fs2
5095
6364
  };
5096
6365
 
6366
+ // dist/modules/picking/picking.js
6367
+ var picking3 = {
6368
+ ...pickingUniforms,
6369
+ name: "picking",
6370
+ source: picking2.source,
6371
+ vs: picking.vs,
6372
+ fs: picking.fs
6373
+ };
6374
+
5097
6375
  // dist/modules/picking/legacy-picking-manager.js
5098
6376
  var LegacyPickingManager = class {
5099
6377
  device;
@@ -5161,4 +6439,11 @@ var LegacyPickingManager = class {
5161
6439
  return [pickX, pickY];
5162
6440
  }
5163
6441
  };
6442
+
6443
+ // dist/modules/picking/legacy-color-picking.js
6444
+ var import_shadertools7 = require("@luma.gl/shadertools");
6445
+ var legacyColorPicking = import_shadertools7.picking;
6446
+
6447
+ // dist/index.js
6448
+ var AsyncTexture = DynamicTexture;
5164
6449
  //# sourceMappingURL=index.cjs.map