@aethervtt/shard-render 0.0.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (170) hide show
  1. package/dist/ablation.d.ts +74 -0
  2. package/dist/ablation.js +117 -0
  3. package/dist/assets.d.ts +3 -0
  4. package/dist/assets.js +43 -2
  5. package/dist/atmosphere-nodes.d.ts +9 -0
  6. package/dist/atmosphere-nodes.js +157 -30
  7. package/dist/atmosphere-plugin.js +8 -2
  8. package/dist/atmosphere-shaders.js +17 -5
  9. package/dist/baseline/atmosphere.d.ts +16 -0
  10. package/dist/baseline/atmosphere.js +301 -0
  11. package/dist/baseline/data-texture.d.ts +36 -0
  12. package/dist/baseline/data-texture.js +181 -0
  13. package/dist/baseline/environment.d.ts +14 -0
  14. package/dist/baseline/environment.js +368 -0
  15. package/dist/baseline/exposure.d.ts +25 -0
  16. package/dist/baseline/exposure.js +133 -0
  17. package/dist/baseline/lights.d.ts +33 -0
  18. package/dist/baseline/lights.js +133 -0
  19. package/dist/baseline/textures.d.ts +97 -0
  20. package/dist/baseline/textures.js +203 -0
  21. package/dist/camera.d.ts +2 -0
  22. package/dist/camera.js +4 -0
  23. package/dist/crowd.d.ts +21 -0
  24. package/dist/crowd.js +121 -0
  25. package/dist/culling-shaders.js +21 -2
  26. package/dist/culling.d.ts +11 -2
  27. package/dist/culling.js +44 -18
  28. package/dist/cutaway-plugin.d.ts +48 -0
  29. package/dist/cutaway-plugin.js +364 -0
  30. package/dist/cutaway.d.ts +27 -0
  31. package/dist/cutaway.js +49 -0
  32. package/dist/data-store.d.ts +69 -0
  33. package/dist/data-store.js +105 -0
  34. package/dist/debug-shaders.js +17 -3
  35. package/dist/debug-views.js +4 -2
  36. package/dist/decals-plugin.d.ts +5 -0
  37. package/dist/decals-plugin.js +54 -0
  38. package/dist/decals.d.ts +69 -0
  39. package/dist/decals.js +326 -0
  40. package/dist/deferred-shaders.js +2 -0
  41. package/dist/deferred.js +28 -4
  42. package/dist/deform.d.ts +5 -4
  43. package/dist/deform.js +4 -9
  44. package/dist/describe-plugin.d.ts +4 -2
  45. package/dist/describe-plugin.js +75 -3
  46. package/dist/display-nodes.js +37 -13
  47. package/dist/dynamic-resolution.d.ts +2 -0
  48. package/dist/dynamic-resolution.js +6 -1
  49. package/dist/environment-plugin.js +17 -2
  50. package/dist/environment-state.d.ts +7 -3
  51. package/dist/environment-state.js +20 -9
  52. package/dist/environment.js +7 -1
  53. package/dist/features.d.ts +49 -0
  54. package/dist/features.js +106 -0
  55. package/dist/foliage-budget.d.ts +64 -0
  56. package/dist/foliage-budget.js +108 -0
  57. package/dist/foliage-path.d.ts +19 -0
  58. package/dist/foliage-path.js +7 -0
  59. package/dist/foliage-shaders.d.ts +11 -0
  60. package/dist/foliage-shaders.js +504 -0
  61. package/dist/foliage.d.ts +194 -0
  62. package/dist/foliage.js +1027 -0
  63. package/dist/forward.d.ts +38 -2
  64. package/dist/forward.js +608 -186
  65. package/dist/fxaa.js +12 -2
  66. package/dist/gizmos-plugin.js +7 -0
  67. package/dist/gizmos.d.ts +3 -3
  68. package/dist/gizmos.js +11 -13
  69. package/dist/gpu-assets.d.ts +58 -4
  70. package/dist/gpu-assets.js +191 -8
  71. package/dist/graph.d.ts +45 -0
  72. package/dist/graph.js +169 -19
  73. package/dist/health.d.ts +58 -0
  74. package/dist/health.js +112 -0
  75. package/dist/index.d.ts +40 -11
  76. package/dist/index.js +343 -9
  77. package/dist/instances.d.ts +119 -8
  78. package/dist/instances.js +387 -78
  79. package/dist/interior-plugin.d.ts +121 -0
  80. package/dist/interior-plugin.js +465 -0
  81. package/dist/interior.d.ts +24 -0
  82. package/dist/interior.js +13 -0
  83. package/dist/layers.d.ts +35 -0
  84. package/dist/layers.js +49 -0
  85. package/dist/lens-plugin.d.ts +43 -0
  86. package/dist/lens-plugin.js +304 -0
  87. package/dist/lens.d.ts +41 -0
  88. package/dist/lens.js +99 -0
  89. package/dist/lights.d.ts +50 -5
  90. package/dist/lights.js +101 -22
  91. package/dist/material-pipelines.d.ts +37 -4
  92. package/dist/material-pipelines.js +102 -5
  93. package/dist/materials.d.ts +23 -0
  94. package/dist/materials.js +44 -1
  95. package/dist/outline-plugin.d.ts +48 -0
  96. package/dist/outline-plugin.js +529 -0
  97. package/dist/outline.d.ts +15 -0
  98. package/dist/outline.js +42 -0
  99. package/dist/overlay-registry.d.ts +3 -1
  100. package/dist/overlay-registry.js +3 -1
  101. package/dist/overlays.js +6 -2
  102. package/dist/perf-overlay.d.ts +9 -0
  103. package/dist/perf-overlay.js +125 -0
  104. package/dist/picking.d.ts +17 -0
  105. package/dist/picking.js +40 -11
  106. package/dist/pixel-perfect-plugin.js +10 -2
  107. package/dist/plugin.d.ts +69 -7
  108. package/dist/plugin.js +223 -25
  109. package/dist/post-common.d.ts +22 -2
  110. package/dist/post-common.js +82 -2
  111. package/dist/post-nodes.d.ts +7 -1
  112. package/dist/post-nodes.js +235 -111
  113. package/dist/post-plugin.js +5 -3
  114. package/dist/post-scene.d.ts +130 -0
  115. package/dist/post-scene.js +164 -0
  116. package/dist/post-shaders.js +85 -62
  117. package/dist/post.js +11 -1
  118. package/dist/projection.d.ts +24 -0
  119. package/dist/projection.js +87 -0
  120. package/dist/readback.d.ts +1 -0
  121. package/dist/readback.js +1 -0
  122. package/dist/render-scale.js +1 -0
  123. package/dist/ribbons-plugin.d.ts +5 -0
  124. package/dist/ribbons-plugin.js +59 -0
  125. package/dist/ribbons.d.ts +44 -0
  126. package/dist/ribbons.js +288 -0
  127. package/dist/scenario.d.ts +70 -0
  128. package/dist/scenario.js +125 -0
  129. package/dist/screen-effects.d.ts +97 -0
  130. package/dist/screen-effects.js +192 -0
  131. package/dist/shaders.d.ts +13 -0
  132. package/dist/shaders.js +189 -34
  133. package/dist/shadow-catcher.d.ts +9 -0
  134. package/dist/shadow-catcher.js +133 -0
  135. package/dist/shadows.d.ts +27 -2
  136. package/dist/shadows.js +100 -20
  137. package/dist/standard-fields.d.ts +1 -0
  138. package/dist/standard-fields.js +5 -0
  139. package/dist/standard.js +18 -0
  140. package/dist/stats.d.ts +45 -1
  141. package/dist/stats.js +91 -3
  142. package/dist/surface-model.d.ts +57 -0
  143. package/dist/surface-model.js +289 -0
  144. package/dist/surface-shaders.d.ts +3 -0
  145. package/dist/surface-shaders.js +160 -0
  146. package/dist/surface.d.ts +40 -0
  147. package/dist/surface.js +86 -0
  148. package/dist/target.d.ts +1 -13
  149. package/dist/target.js +1 -26
  150. package/dist/testing.d.ts +11 -0
  151. package/dist/testing.js +39 -1
  152. package/dist/tier.d.ts +13 -0
  153. package/dist/tier.js +14 -0
  154. package/dist/timer.d.ts +18 -1
  155. package/dist/timer.js +73 -20
  156. package/dist/variants.d.ts +37 -0
  157. package/dist/variants.js +104 -0
  158. package/dist/vfx-draw.d.ts +37 -0
  159. package/dist/vfx-draw.js +291 -0
  160. package/dist/vfx-state.d.ts +42 -0
  161. package/dist/vfx-state.js +81 -0
  162. package/dist/vfx.d.ts +69 -0
  163. package/dist/vfx.js +103 -0
  164. package/dist/view-visibility-plugin.d.ts +61 -0
  165. package/dist/view-visibility-plugin.js +267 -0
  166. package/dist/view-visibility.d.ts +28 -0
  167. package/dist/view-visibility.js +46 -0
  168. package/dist/view.d.ts +29 -1
  169. package/dist/view.js +57 -34
  170. package/package.json +23 -11
package/dist/instances.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { AssetServerResource } from "@aethervtt/shard-assets";
1
2
  import {
2
3
  defineComponent,
3
4
  defineResource,
@@ -5,13 +6,16 @@ import {
5
6
  onRemove,
6
7
  t
7
8
  } from "@aethervtt/shard-core";
8
- import { GpuBuffer } from "@aethervtt/shard-gpu";
9
- import { LogResource } from "@aethervtt/shard-runtime";
9
+ import { DevMode } from "@aethervtt/shard-runtime";
10
10
  import { toHalf } from "@aethervtt/shard-texture";
11
11
  import { GlobalTransform, Transform } from "@aethervtt/shard-transform";
12
12
  import { MaterialAsset, Materials, Meshes } from "./assets.js";
13
+ import { Cutaway, CutawayPath } from "./cutaway.js";
14
+ import { DataStore } from "./data-store.js";
13
15
  import { DEFORM_WORDS, DeformStore } from "./deform.js";
16
+ import { warnFeatureMissing } from "./features.js";
14
17
  import { GpuAssetsResource } from "./gpu-assets.js";
18
+ import { GroundLayer, OVERLAY_BAND, RenderLayers } from "./layers.js";
15
19
  import { isTransparent } from "./materials.js";
16
20
  import { ComputedVisibility, Visibility } from "./visibility.js";
17
21
  const InstanceSlot = defineComponent(
@@ -32,6 +36,11 @@ const NotShadowCaster = defineComponent(
32
36
  {},
33
37
  { description: "Tag: this mesh casts no shadows." }
34
38
  );
39
+ const ShadowWhenHidden = defineComponent(
40
+ "render/ShadowWhenHidden",
41
+ {},
42
+ { description: "Tag: while hidden, this mesh still casts shadows (camera views skip it)." }
43
+ );
35
44
  const NotShadowReceiver = defineComponent(
36
45
  "render/NotShadowReceiver",
37
46
  {},
@@ -153,8 +162,21 @@ const InstanceFlags = {
153
162
  /** Skinned: the vertex stage reads joint matrices; culling reads the slot's own sphere. */
154
163
  Skinned: 32,
155
164
  /** Morphed: the vertex stage adds morph target deltas. */
156
- Morph: 64
165
+ Morph: 64,
166
+ /** Hidden, but drawn in shadow views (ShadowWhenHidden); Visible is off. */
167
+ ShadowOnly: 128,
168
+ /**
169
+ * May be cut away around a camera's reveal points (Cutaway, 0070). In the record's flags word
170
+ * it rides in bit 28 (`CUTAWAY_BIT`), above the layers and the LOD.
171
+ */
172
+ Cutaway: 256
157
173
  };
174
+ const CUTAWAY_BIT = 1 << 28;
175
+ function recordFlags(flags, layers) {
176
+ return (flags & 255 | layers << LAYER_SHIFT | (flags & InstanceFlags.Cutaway ? CUTAWAY_BIT : 0)) >>> 0;
177
+ }
178
+ const LAYER_SHIFT = 8;
179
+ const LAYER_BITS = 65535 << LAYER_SHIFT;
158
180
  const NO_BATCH = 4294967295;
159
181
  const LOD_BIT = 2147483648;
160
182
  const SLOT_MASK = 268435455;
@@ -237,7 +259,7 @@ class InstanceStore {
237
259
  * Each slot's InstanceFlags, mirrored from its record: prepare compares against these every
238
260
  * frame, and reading them from the 64-byte records would touch a cache line per instance.
239
261
  */
240
- flags = new Uint8Array(0);
262
+ flags = new Uint16Array(0);
241
263
  /** Hidden instances per ECS table id, from the last time prepare visited the table. */
242
264
  tableHidden = [];
243
265
  /** Batch index (>= 0), LOD set (-2 - index), or -1 (none), per slot. */
@@ -275,9 +297,24 @@ class InstanceStore {
275
297
  /** Batches in draw order (sorted by pipeline, then material, then mesh). */
276
298
  sorted = [];
277
299
  byMesh = /* @__PURE__ */ new Map();
300
+ byMeshGround = /* @__PURE__ */ new Map();
301
+ /**
302
+ * Render layer mask per slot (0057); 1 for slots without RenderLayers. It also rides in bits
303
+ * 8–23 of the record's flags word, where the GPU culler reads it.
304
+ */
305
+ layers = new Uint16Array(0);
306
+ /** Ground key per slot, (level, band, order) packed; NaN off the ground (0057, 0067). */
307
+ groundKeys = new Float64Array(0);
278
308
  lodSets = [];
279
309
  lodByKey = /* @__PURE__ */ new Map();
280
310
  defaultMaterial = new MaterialAsset();
311
+ /**
312
+ * What stand-ins for failed meshes are drawn with (0061): matte gray, or magenta in a dev build
313
+ * (`DevMode`, read by prepareInstances).
314
+ */
315
+ missingMaterial = new MaterialAsset({ baseColor: [0.5, 0.5, 0.5, 1], roughness: 1 });
316
+ /** A development build: the missing material turns magenta. */
317
+ dev = false;
281
318
  /** Bytes of instance data uploaded this frame. */
282
319
  uploadedBytes = 0;
283
320
  /** Slots with a batch and not hidden, and hidden ones: stats for GPU-culled views. */
@@ -285,6 +322,13 @@ class InstanceStore {
285
322
  hiddenCount = 0;
286
323
  /** Bumps when batches or LOD sets change shape (the GPU tables re-upload). */
287
324
  structureVersion = 0;
325
+ /** Bumps when a slot is allocated or freed (hidden sets resolve again, 0070). */
326
+ slotEpoch = 0;
327
+ /**
328
+ * Bumps when what casts shadows changes other than by moving: a slot's batch, its caster or
329
+ * visible flag, or a batch's mesh data. Cached shadow maps (0055) redraw when it moves.
330
+ */
331
+ shadowEpoch = 0;
288
332
  /** Visible slot lists of every CPU-culled view this frame, concatenated. */
289
333
  visible = new Uint32Array(1024);
290
334
  visibleCount = 0;
@@ -305,23 +349,11 @@ class InstanceStore {
305
349
  constructor(gpu) {
306
350
  this.gpu = gpu;
307
351
  this.generation = gpu.generation;
308
- this.instanceBuffer = new GpuBuffer(gpu, {
309
- label: "instances",
310
- usage: GPUBufferUsage.STORAGE,
311
- size: INSTANCE_BYTES * 256
312
- });
313
- this.prevBuffer = new GpuBuffer(gpu, {
314
- label: "instances/previous",
315
- usage: GPUBufferUsage.STORAGE,
316
- size: PREV_BYTES * 256
317
- });
318
- this.visibleBuffer = new GpuBuffer(gpu, {
319
- label: "instances/visible",
320
- usage: GPUBufferUsage.STORAGE,
321
- size: 4 * 1024
322
- });
323
- this.layout = createInstanceLayout(gpu);
352
+ this.instanceBuffer = new DataStore(gpu, { label: "instances", size: INSTANCE_BYTES * 256 });
353
+ this.prevBuffer = new DataStore(gpu, { label: "instances/previous", size: PREV_BYTES * 256 });
354
+ this.visibleBuffer = new DataStore(gpu, { label: "instances/visible", size: 4 * 1024 });
324
355
  this.deform = new DeformStore(gpu);
356
+ this.layout = createInstanceLayout(gpu, this);
325
357
  this.grow(256);
326
358
  }
327
359
  get live() {
@@ -344,7 +376,7 @@ class InstanceStore {
344
376
  const dirty = new Uint8Array(capacity);
345
377
  dirty.set(this.dirty);
346
378
  this.dirty = dirty;
347
- const flags = new Uint8Array(capacity);
379
+ const flags = new Uint16Array(capacity);
348
380
  flags.set(this.flags);
349
381
  this.flags = flags;
350
382
  const prev = new Float32Array(capacity * 12);
@@ -356,6 +388,12 @@ class InstanceStore {
356
388
  const movedFrame = new Uint32Array(capacity);
357
389
  movedFrame.set(this.movedFrame);
358
390
  this.movedFrame = movedFrame;
391
+ const layers = new Uint16Array(capacity).fill(1);
392
+ layers.set(this.layers);
393
+ this.layers = layers;
394
+ const groundKeys = new Float64Array(capacity).fill(Number.NaN);
395
+ groundKeys.set(this.groundKeys);
396
+ this.groundKeys = groundKeys;
359
397
  this.capacity = capacity;
360
398
  this.deform.ensureSlots(capacity);
361
399
  }
@@ -366,16 +404,39 @@ class InstanceStore {
366
404
  slot = this.high++;
367
405
  }
368
406
  this.u32[slot * INSTANCE_FLOATS + 12] = NO_BATCH;
369
- this.u32[slot * INSTANCE_FLOATS + 13] = 0;
407
+ this.u32[slot * INSTANCE_FLOATS + 13] = 1 << LAYER_SHIFT;
370
408
  this.flags[slot] = 0;
371
409
  this.u32[slot * INSTANCE_FLOATS + 14] = 0;
372
410
  this.u32[slot * INSTANCE_FLOATS + 15] = entity % 4294967296;
373
411
  this.batchOf[slot] = -1;
374
412
  this.memberIndex[slot] = -1;
375
413
  this.lods[slot] = null;
414
+ this.groundKeys[slot] = Number.NaN;
415
+ this.layers[slot] = 1;
376
416
  this.markDirty(slot);
417
+ this.slotEpoch++;
377
418
  return slot;
378
419
  }
420
+ /** A slot's render layer mask (0057). What casts shadows in a view may change: cached maps redraw. */
421
+ setLayers(slot, mask) {
422
+ const m = mask & 65535;
423
+ if (this.layers[slot] === m) return;
424
+ this.layers[slot] = m;
425
+ const o = slot * INSTANCE_FLOATS + 13;
426
+ this.u32[o] = (this.u32[o] & ~LAYER_BITS | m << LAYER_SHIFT) >>> 0;
427
+ this.markDirty(slot);
428
+ this.shadowEpoch++;
429
+ }
430
+ /**
431
+ * A slot's ground key (0057): `groundKey(band, order, level)`, or NaN off the ground. Returns whether
432
+ * it moved on or off the ground (its batch must be resolved again).
433
+ */
434
+ setGround(slot, key) {
435
+ const old = this.groundKeys[slot];
436
+ if (Object.is(old, key)) return false;
437
+ this.groundKeys[slot] = key;
438
+ return Number.isNaN(old) !== Number.isNaN(key);
439
+ }
379
440
  release(slot) {
380
441
  this.assign(slot, -1);
381
442
  this.u32[slot * INSTANCE_FLOATS + 13] = 0;
@@ -387,6 +448,7 @@ class InstanceStore {
387
448
  this.deform.clear(slot);
388
449
  this.markDirty(slot);
389
450
  this.free.push(slot);
451
+ this.slotEpoch++;
390
452
  }
391
453
  boundsDirty = true;
392
454
  cachedBounds = new Float32Array(6);
@@ -401,12 +463,11 @@ class InstanceStore {
401
463
  b[0] = b[1] = b[2] = Number.POSITIVE_INFINITY;
402
464
  b[3] = b[4] = b[5] = Number.NEGATIVE_INFINITY;
403
465
  const sphere = scratchSphere;
404
- const need = InstanceFlags.Visible | InstanceFlags.Caster;
405
466
  for (let s = 0; s < this.high; s++) {
406
467
  const a = this.batchOf[s];
407
- if (a === -1 || (this.u32[s * INSTANCE_FLOATS + 13] & need) !== need) continue;
468
+ if (a === -1 || !casts(this.u32[s * INSTANCE_FLOATS + 13])) continue;
408
469
  const batch = this.batches[a >= 0 ? a : this.lodSets[-2 - a].batches[0]];
409
- if (batch.transparent) continue;
470
+ if (batch.transparent || batch.ground) continue;
410
471
  sphereOfSlot(sphere, this, s, this.u32[s * INSTANCE_FLOATS + 13], batch.mesh.bounds);
411
472
  for (let k = 0; k < 3; k++) {
412
473
  if (sphere[k] - sphere[3] < b[k]) b[k] = sphere[k] - sphere[3];
@@ -505,6 +566,9 @@ class InstanceStore {
505
566
  assign(slot, assignment) {
506
567
  const old = this.batchOf[slot];
507
568
  if (old === assignment) return;
569
+ this.shadowEpoch++;
570
+ const cut = this.flags[slot] & InstanceFlags.Cutaway;
571
+ if (cut) this.countCutaway(slot, -1);
508
572
  if (old >= 0) {
509
573
  this.batches[old].count--;
510
574
  this.removeMember(slot, this.batches[old]);
@@ -523,8 +587,55 @@ class InstanceStore {
523
587
  }
524
588
  this.batchOf[slot] = assignment;
525
589
  this.u32[slot * INSTANCE_FLOATS + 12] = assignment >= 0 ? assignment : assignment <= -2 ? (LOD_BIT | -2 - assignment) >>> 0 : NO_BATCH;
590
+ if (cut) this.countCutaway(slot, 1);
526
591
  this.markDirty(slot);
527
592
  }
593
+ /** Adds `delta` to the cutaway count of every batch the slot can land in (0070). */
594
+ countCutaway(slot, delta) {
595
+ const a = this.batchOf[slot];
596
+ if (a >= 0) this.batches[a].cutaway += delta;
597
+ else if (a <= -2)
598
+ for (const b of this.lodSets[-2 - a].batches) this.batches[b].cutaway += delta;
599
+ }
600
+ /**
601
+ * Bumps `shadowEpoch` if any batch's mesh data changed since the last call (a structure chunk
602
+ * rebuilt in place). One compare per batch.
603
+ */
604
+ checkMeshVersions() {
605
+ const batches = this.batches;
606
+ for (let i = 0; i < batches.length; i++) {
607
+ const b = batches[i];
608
+ if (b.meshVersion !== b.mesh.version) {
609
+ b.meshVersion = b.mesh.version;
610
+ this.shadowEpoch++;
611
+ }
612
+ }
613
+ }
614
+ /**
615
+ * Whether a visible caster that moved this frame touches the half-spaces `planes`, where it
616
+ * was or where it is now. Call after prepare. Skinned and morphed poses aren't moves; see
617
+ * `deforming`.
618
+ */
619
+ movedCasterIn(planes) {
620
+ const sphere = scratchSphere;
621
+ for (let i = 0; i < this.lastMovedCount; i++) {
622
+ const slot = this.lastMoved[i];
623
+ if (slot >= this.high || !casts(this.flags[slot])) continue;
624
+ const a = this.batchOf[slot];
625
+ if (a === -1) continue;
626
+ const batch = this.batches[a >= 0 ? a : this.lodSets[-2 - a].batches[0]];
627
+ if (batch.transparent || batch.ground) continue;
628
+ sphereOfSlot(sphere, this, slot, this.flags[slot], batch.mesh.bounds);
629
+ if (sphereInFrustum(planes, sphere)) return true;
630
+ slotSphere(sphere, this.prev, slot * 12, batch.mesh.bounds);
631
+ if (sphereInFrustum(planes, sphere)) return true;
632
+ }
633
+ return false;
634
+ }
635
+ /** Whether skinned or morphed poses are being written this frame (their shadows move). */
636
+ get deforming() {
637
+ return this.deform.poseCount > 0;
638
+ }
528
639
  /** A plain batch assignment. */
529
640
  setBatch(slot, index) {
530
641
  this.assign(slot, index);
@@ -546,11 +657,12 @@ class InstanceStore {
546
657
  this.memberIndex[last] = i;
547
658
  this.memberIndex[slot] = -1;
548
659
  }
549
- batchFor(mesh, material, meshGuid, materialGuid) {
550
- let byMaterial = this.byMesh.get(mesh);
660
+ batchFor(mesh, material, meshGuid, materialGuid, ground = false) {
661
+ const byMesh = ground ? this.byMeshGround : this.byMesh;
662
+ let byMaterial = byMesh.get(mesh);
551
663
  if (!byMaterial) {
552
664
  byMaterial = /* @__PURE__ */ new Map();
553
- this.byMesh.set(mesh, byMaterial);
665
+ byMesh.set(mesh, byMaterial);
554
666
  }
555
667
  let batch = byMaterial.get(material);
556
668
  if (!batch) {
@@ -570,7 +682,10 @@ class InstanceStore {
570
682
  scratch: new Uint32Array(64),
571
683
  scratchCount: 0,
572
684
  forwardScratch: new Uint32Array(16),
573
- forwardCount: 0
685
+ forwardCount: 0,
686
+ meshVersion: mesh.version,
687
+ cutaway: 0,
688
+ ground
574
689
  };
575
690
  byMaterial.set(material, batch);
576
691
  this.batches.push(batch);
@@ -648,7 +763,7 @@ class InstanceStore {
648
763
  upload() {
649
764
  if (this.generation !== this.gpu.generation) {
650
765
  this.generation = this.gpu.generation;
651
- this.layout = createInstanceLayout(this.gpu);
766
+ this.layout = createInstanceLayout(this.gpu, this);
652
767
  this.bindGroup = void 0;
653
768
  this.gpuBindGroup = void 0;
654
769
  this.dirtyLo = 0;
@@ -716,13 +831,19 @@ class InstanceStore {
716
831
  tBatches = new Int32Array(64);
717
832
  tDepths = new Float32Array(64);
718
833
  tOrder = [];
834
+ // Ground instances this cull: slot, batch, and (band, order) key.
835
+ groundCount = 0;
836
+ gSlots = new Uint32Array(64);
837
+ gBatches = new Int32Array(64);
838
+ gKeys = new Float64Array(64);
839
+ gOrder = [];
719
840
  /**
720
841
  * CPU culling of every slot into `list` (and, for camera views, `transparent` back to front and
721
842
  * deferred `forwardOnly`). Same tests as the GPU culler: bounding sphere against the frustum,
722
843
  * visibility range, LOD level. `bounds` gets the union of what passed (cascade fitting). With
723
844
  * `transparentOnly`, only blended batches are culled (the GPU culls the rest).
724
845
  */
725
- cullCpu(list, params, bounds, transparent, forward, forwardOnly, transparentOnly = false) {
846
+ cullCpu(list, params, bounds, transparent, forward, forwardOnly, transparentOnly = false, ground, overlay) {
726
847
  if (bounds) {
727
848
  bounds[0] = bounds[1] = bounds[2] = Number.POSITIVE_INFINITY;
728
849
  bounds[3] = bounds[4] = bounds[5] = Number.NEGATIVE_INFINITY;
@@ -735,6 +856,10 @@ class InstanceStore {
735
856
  list.cullView = -1;
736
857
  this.transparentCount = 0;
737
858
  this.forwardOnlyCount = 0;
859
+ this.groundCount = 0;
860
+ const layers = this.layers;
861
+ const viewLayers = params.layers ?? 4294967295;
862
+ const hiddenBits = params.hidden;
738
863
  const batches = this.batches;
739
864
  for (let b = 0; b < batches.length; b++) {
740
865
  batches[b].scratchCount = 0;
@@ -743,16 +868,25 @@ class InstanceStore {
743
868
  const f = this.f32;
744
869
  const u32 = this.u32;
745
870
  const mask = InstanceFlags.Visible | params.require;
871
+ const shadowOnly = (params.require & InstanceFlags.Caster) !== 0 ? InstanceFlags.ShadowOnly : 0;
746
872
  const eye = params.eye;
747
873
  const sphere = scratchSphere;
748
874
  for (let s = 0; s < this.high; s++) {
749
875
  const assignment = this.batchOf[s];
750
876
  if (assignment === -1) continue;
751
877
  const flags = u32[s * INSTANCE_FLOATS + 13];
752
- if ((flags & mask) !== mask) {
878
+ if (((flags & shadowOnly ? flags | InstanceFlags.Visible : flags) & mask) !== mask) {
753
879
  if ((flags & InstanceFlags.Visible) === 0) list.hidden++;
754
880
  continue;
755
881
  }
882
+ if ((layers[s] & viewLayers) === 0) {
883
+ list.culled++;
884
+ continue;
885
+ }
886
+ if (hiddenBits !== void 0 && (hiddenBits[s >>> 5] & 1 << (s & 31)) !== 0) {
887
+ list.hidden++;
888
+ continue;
889
+ }
756
890
  let set;
757
891
  let b = assignment;
758
892
  if (assignment <= -2) {
@@ -765,6 +899,7 @@ class InstanceStore {
765
899
  list.pending++;
766
900
  continue;
767
901
  }
902
+ if (batch.ground && (params.require !== 0 || !ground)) continue;
768
903
  if (batch.transparent && (params.require !== 0 || !transparent)) continue;
769
904
  if (flags & InstanceFlags.Skinned) sphereOfSlot(sphere, this, s, flags, batch.mesh.bounds);
770
905
  else slotSphere(sphere, f, s * INSTANCE_FLOATS, batch.mesh.bounds);
@@ -814,6 +949,10 @@ class InstanceStore {
814
949
  if (sphere[k] + sphere[3] > bounds[k + 3]) bounds[k + 3] = sphere[k] + sphere[3];
815
950
  }
816
951
  }
952
+ if (batch.ground) {
953
+ this.pushGround(entry, b);
954
+ continue;
955
+ }
817
956
  if (batch.transparent) {
818
957
  this.pushTransparent(entry, b, eye, forward);
819
958
  list.visible++;
@@ -839,6 +978,7 @@ class InstanceStore {
839
978
  }
840
979
  this.pack(list);
841
980
  if (transparent) this.packTransparent(transparent);
981
+ if (ground) this.packGround(ground, overlay);
842
982
  if (forwardOnly) {
843
983
  for (const batch of this.batches) {
844
984
  const n = batch.forwardCount;
@@ -859,12 +999,16 @@ class InstanceStore {
859
999
  const u32 = this.u32;
860
1000
  const eye = params.eye;
861
1001
  const sphere = scratchSphere;
1002
+ const viewLayers = params.layers ?? 4294967295;
1003
+ const hiddenBits = params.hidden;
862
1004
  for (const batch of this.batches) {
863
- if (!batch.transparent || !batch.ready || batch.memberCount === 0) continue;
1005
+ if (!batch.transparent || batch.ground || !batch.ready || batch.memberCount === 0) continue;
864
1006
  for (let m = 0; m < batch.memberCount; m++) {
865
1007
  const s = batch.members[m];
866
1008
  const flags = u32[s * INSTANCE_FLOATS + 13];
867
1009
  if ((flags & InstanceFlags.Visible) === 0) continue;
1010
+ if ((this.layers[s] & viewLayers) === 0) continue;
1011
+ if (hiddenBits !== void 0 && (hiddenBits[s >>> 5] & 1 << (s & 31)) !== 0) continue;
868
1012
  sphereOfSlot(sphere, this, s, flags, batch.mesh.bounds);
869
1013
  if (params.planes && !sphereInFrustum(params.planes, sphere)) continue;
870
1014
  if (flags & InstanceFlags.Range && eye) {
@@ -879,6 +1023,125 @@ class InstanceStore {
879
1023
  }
880
1024
  this.packTransparent(list);
881
1025
  }
1026
+ /**
1027
+ * Ground-only CPU cull over ground batches' members (0057), for views the GPU culls: the ground
1028
+ * list is always built on the CPU, in (band, order) order.
1029
+ */
1030
+ cullGroundMembers(list, params, overlay) {
1031
+ this.groundCount = 0;
1032
+ const u32 = this.u32;
1033
+ const sphere = scratchSphere;
1034
+ const viewLayers = params.layers ?? 4294967295;
1035
+ const hiddenBits = params.hidden;
1036
+ for (const batch of this.batches) {
1037
+ if (!batch.ground || !batch.ready || batch.memberCount === 0) continue;
1038
+ for (let m = 0; m < batch.memberCount; m++) {
1039
+ const s = batch.members[m];
1040
+ const flags = u32[s * INSTANCE_FLOATS + 13];
1041
+ if ((flags & InstanceFlags.Visible) === 0) continue;
1042
+ if ((this.layers[s] & viewLayers) === 0) continue;
1043
+ if (hiddenBits !== void 0 && (hiddenBits[s >>> 5] & 1 << (s & 31)) !== 0) continue;
1044
+ slotSphere(sphere, this.f32, s * INSTANCE_FLOATS, batch.mesh.bounds);
1045
+ if (params.planes && !sphereInFrustum(params.planes, sphere)) continue;
1046
+ this.pushGround(s, batch.index);
1047
+ }
1048
+ }
1049
+ this.packGround(list, overlay);
1050
+ }
1051
+ pushGround(entry, batch) {
1052
+ const n = this.groundCount;
1053
+ if (n >= this.gSlots.length) {
1054
+ const size = this.gSlots.length * 2;
1055
+ const s = new Uint32Array(size);
1056
+ s.set(this.gSlots);
1057
+ this.gSlots = s;
1058
+ const b = new Int32Array(size);
1059
+ b.set(this.gBatches);
1060
+ this.gBatches = b;
1061
+ const k = new Float64Array(size);
1062
+ k.set(this.gKeys);
1063
+ this.gKeys = k;
1064
+ }
1065
+ this.gSlots[n] = entry;
1066
+ this.gBatches[n] = batch;
1067
+ this.gKeys[n] = this.groundKeys[entry & SLOT_MASK];
1068
+ this.groundCount = n + 1;
1069
+ }
1070
+ /**
1071
+ * Packs visible-list entries (a slot in the low 28 bits, 4 spare bits a pass may use, like an
1072
+ * outline style) into `list`, one draw per batch: for passes that draw chosen slots (0057's
1073
+ * outline mask). `batches` holds each entry's batch index. Skips entries whose batch isn't ready.
1074
+ */
1075
+ packEntries(list, entries, batches, count) {
1076
+ list.length = 0;
1077
+ list.cullView = -1;
1078
+ list.visible = 0;
1079
+ if (count === 0) return;
1080
+ const order = this.gOrder;
1081
+ order.length = count;
1082
+ for (let i = 0; i < count; i++) order[i] = i;
1083
+ order.sort((a, b) => batches[a] - batches[b] || entries[a] - entries[b]);
1084
+ this.reserve(count);
1085
+ let item;
1086
+ for (let k = 0; k < count; k++) {
1087
+ const i = order[k];
1088
+ const batch = this.batches[batches[i]];
1089
+ if (!batch?.ready) continue;
1090
+ if (!item || item.batch !== batch) {
1091
+ item = this.item(list, batch);
1092
+ item.first = this.visibleCount;
1093
+ item.count = 0;
1094
+ }
1095
+ this.visible[this.visibleCount++] = entries[i];
1096
+ item.count++;
1097
+ list.visible++;
1098
+ }
1099
+ }
1100
+ /**
1101
+ * Sorts this cull's ground instances by (level, band, order), then batch; runs of a batch share a
1102
+ * draw. With `overlay`, bands from OVERLAY_BAND up go there instead (0058).
1103
+ */
1104
+ packGround(list, overlay) {
1105
+ list.length = 0;
1106
+ list.cullView = -1;
1107
+ if (overlay) {
1108
+ overlay.length = 0;
1109
+ overlay.cullView = -1;
1110
+ overlay.visible = 0;
1111
+ }
1112
+ const n = this.groundCount;
1113
+ list.visible = n;
1114
+ if (n === 0) return;
1115
+ const order = this.gOrder;
1116
+ order.length = n;
1117
+ for (let i = 0; i < n; i++) order[i] = i;
1118
+ const keys = this.gKeys;
1119
+ const batches = this.gBatches;
1120
+ const slots = this.gSlots;
1121
+ order.sort((a, b) => keys[a] - keys[b] || batches[a] - batches[b] || slots[a] - slots[b]);
1122
+ this.reserve(n);
1123
+ let item;
1124
+ let into = list;
1125
+ for (let k = 0; k < n; k++) {
1126
+ const i = order[k];
1127
+ const batch = this.batches[batches[i]];
1128
+ if (overlay && into === list && bandOfKey(keys[i]) >= OVERLAY_BAND) {
1129
+ into = overlay;
1130
+ item = void 0;
1131
+ }
1132
+ if (!item || item.batch !== batch) {
1133
+ item = this.item(into, batch);
1134
+ item.first = this.visibleCount;
1135
+ item.count = 0;
1136
+ }
1137
+ this.visible[this.visibleCount++] = slots[i];
1138
+ item.count++;
1139
+ if (into === overlay) {
1140
+ overlay.visible++;
1141
+ list.visible--;
1142
+ }
1143
+ }
1144
+ }
882
1145
  /**
883
1146
  * CPU culling with positional arguments: planes, flag mask, bounds, transparent list, eye, view
884
1147
  * direction, and the deferred forward-only list. LOD selects fresh (no hysteresis state).
@@ -970,7 +1233,8 @@ class InstanceStore {
970
1233
  const n = batch.memberCount;
971
1234
  if (n === 0 || n !== batch.count || n > 8) return true;
972
1235
  for (let i = 0; i < n; i++) {
973
- if (this.u32[batch.members[i] * INSTANCE_FLOATS + 13] & InstanceFlags.Visible) return true;
1236
+ const flags = this.u32[batch.members[i] * INSTANCE_FLOATS + 13];
1237
+ if (flags & (InstanceFlags.Visible | InstanceFlags.ShadowOnly)) return true;
974
1238
  }
975
1239
  return false;
976
1240
  }
@@ -1040,12 +1304,12 @@ class InstanceStore {
1040
1304
  label,
1041
1305
  layout: this.layout,
1042
1306
  entries: [
1043
- { binding: 0, resource: { buffer: this.instanceBuffer.buffer } },
1044
- { binding: 1, resource: { buffer: visible } },
1045
- { binding: 2, resource: { buffer: this.prevBuffer.buffer } },
1046
- { binding: 3, resource: { buffer: this.deform.recordBuffer.buffer } },
1047
- { binding: 4, resource: { buffer: this.deform.poseBuffer.buffer } },
1048
- { binding: 5, resource: { buffer: this.deform.vertexBuffer.buffer } }
1307
+ { binding: 0, resource: this.instanceBuffer.resource() },
1308
+ { binding: 1, resource: visible },
1309
+ { binding: 2, resource: this.prevBuffer.resource() },
1310
+ { binding: 3, resource: this.deform.recordBuffer.resource() },
1311
+ { binding: 4, resource: this.deform.poseBuffer.resource() },
1312
+ { binding: 5, resource: this.deform.vertexBuffer.resource() }
1049
1313
  ]
1050
1314
  });
1051
1315
  }
@@ -1058,14 +1322,14 @@ class InstanceStore {
1058
1322
  const deformKey = `${d.recordBuffer.version}/${d.poseBuffer.version}/${d.vertexBuffer.version}`;
1059
1323
  const key = `${this.instanceBuffer.version}/${this.visibleBuffer.version}/${this.prevBuffer.version}/${deformKey}/${this.gpu.generation}`;
1060
1324
  if (!this.bindGroup || this.bound !== key) {
1061
- this.bindGroup = this.createBindGroup("instances", this.visibleBuffer.buffer);
1325
+ this.bindGroup = this.createBindGroup("instances", this.visibleBuffer.resource());
1062
1326
  this.bound = key;
1063
1327
  }
1064
1328
  const g = this.gpuVisible;
1065
1329
  if (g) {
1066
1330
  const gkey = `${this.instanceBuffer.version}/${g.version}/${this.prevBuffer.version}/${deformKey}/${this.gpu.generation}`;
1067
1331
  if (!this.gpuBindGroup || this.gpuBound !== gkey) {
1068
- this.gpuBindGroup = this.createBindGroup("instances/gpu-culled", g.buffer);
1332
+ this.gpuBindGroup = this.createBindGroup("instances/gpu-culled", { buffer: g.buffer });
1069
1333
  this.gpuBound = gkey;
1070
1334
  }
1071
1335
  }
@@ -1094,25 +1358,19 @@ function idOf(o) {
1094
1358
  }
1095
1359
  return id;
1096
1360
  }
1097
- function createInstanceLayout(gpu) {
1361
+ function createInstanceLayout(gpu, store) {
1362
+ const VF = GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT;
1363
+ const V = GPUShaderStage.VERTEX;
1098
1364
  return gpu.layouts.bindGroupLayout({
1099
1365
  label: "instances",
1100
1366
  entries: [
1101
- {
1102
- binding: 0,
1103
- visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT,
1104
- buffer: { type: "read-only-storage" }
1105
- },
1106
- {
1107
- binding: 1,
1108
- visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT,
1109
- buffer: { type: "read-only-storage" }
1110
- },
1111
- { binding: 2, visibility: GPUShaderStage.VERTEX, buffer: { type: "read-only-storage" } },
1367
+ store.instanceBuffer.layoutEntry(0, VF),
1368
+ store.visibleBuffer.layoutEntry(1, VF),
1369
+ store.prevBuffer.layoutEntry(2, V),
1112
1370
  // Deform records, poses (joint matrices, morph weights), and per-vertex deform data.
1113
- { binding: 3, visibility: GPUShaderStage.VERTEX, buffer: { type: "read-only-storage" } },
1114
- { binding: 4, visibility: GPUShaderStage.VERTEX, buffer: { type: "read-only-storage" } },
1115
- { binding: 5, visibility: GPUShaderStage.VERTEX, buffer: { type: "read-only-storage" } }
1371
+ store.deform.recordBuffer.layoutEntry(3, V),
1372
+ store.deform.poseBuffer.layoutEntry(4, V),
1373
+ store.deform.vertexBuffer.layoutEntry(5, V)
1116
1374
  ]
1117
1375
  });
1118
1376
  }
@@ -1120,7 +1378,16 @@ const Instances = defineResource("render/Instances", {
1120
1378
  description: "The persistent GPU instance buffer: one slot per Mesh3d entity."
1121
1379
  });
1122
1380
  function tableChanged(table, since) {
1123
- return table.lastStructural > since || table.lastChanged(GlobalTransform) > since || table.lastChanged(Mesh3d) > since || table.lastChanged(ComputedVisibility) > since || table.lastChanged(InstanceSlot) > since || table.has(MeshMaterial) && table.lastChanged(MeshMaterial) > since || table.has(Lod) && table.lastChanged(Lod) > since || table.has(VisibilityRange) && table.lastChanged(VisibilityRange) > since || table.has(InstanceData) && table.lastChanged(InstanceData) > since;
1381
+ return table.lastStructural > since || table.lastChanged(GlobalTransform) > since || table.lastChanged(Mesh3d) > since || table.lastChanged(ComputedVisibility) > since || table.lastChanged(InstanceSlot) > since || table.has(MeshMaterial) && table.lastChanged(MeshMaterial) > since || table.has(Lod) && table.lastChanged(Lod) > since || table.has(VisibilityRange) && table.lastChanged(VisibilityRange) > since || table.has(InstanceData) && table.lastChanged(InstanceData) > since || table.has(RenderLayers) && table.lastChanged(RenderLayers) > since || table.has(GroundLayer) && table.lastChanged(GroundLayer) > since;
1382
+ }
1383
+ function casts(flags) {
1384
+ return (flags & InstanceFlags.Caster) !== 0 && (flags & (InstanceFlags.Visible | InstanceFlags.ShadowOnly)) !== 0;
1385
+ }
1386
+ function groundKey(band, order, level = 0) {
1387
+ return (level + 16) * 281474976710656 + (band + 32768) * 4294967296 + (order + 2147483648);
1388
+ }
1389
+ function bandOfKey(key) {
1390
+ return Math.floor(key / 4294967296) % 65536 - 32768;
1124
1391
  }
1125
1392
  let zeros = new Uint32Array(1024);
1126
1393
  function zeroTicks(n) {
@@ -1130,19 +1397,6 @@ function zeroTicks(n) {
1130
1397
  const DeformPath = defineResource("render/DeformPath", {
1131
1398
  description: "Present when skinning and morph targets (skinningPlugin) are installed."
1132
1399
  });
1133
- const warnedDeforms = /* @__PURE__ */ new WeakSet();
1134
- function warnNoDeforms(world) {
1135
- if (warnedDeforms.has(world)) return;
1136
- warnedDeforms.add(world);
1137
- world.tryResource(LogResource)?.log(
1138
- "warn",
1139
- "An entity has SkinnedMesh or MorphWeights, but skinningPlugin isn't installed; it draws in its rest pose",
1140
- {
1141
- code: "render/feature-missing",
1142
- hint: "Add skinningPlugin from '@aethervtt/shard-render' (forwardPlugin includes it)."
1143
- }
1144
- );
1145
- }
1146
1400
  const prepareInstances = defineSystem({
1147
1401
  name: "render/prepare-instances",
1148
1402
  description: "Assigns instance slots, writes changed transforms and batches, and uploads dirty slots.",
@@ -1156,7 +1410,13 @@ const prepareInstances = defineSystem({
1156
1410
  const assets = world.resource(GpuAssetsResource);
1157
1411
  assets.beginFrame();
1158
1412
  store.beginFrame();
1413
+ const dev = world.tryResource(DevMode)?.enabled ?? false;
1414
+ if (dev !== store.dev) {
1415
+ store.dev = dev;
1416
+ store.missingMaterial.set({ baseColor: dev ? [1, 0, 1, 1] : [0.5, 0.5, 0.5, 1] });
1417
+ }
1159
1418
  const deforms = world.hasResource(DeformPath);
1419
+ const cutaways = world.hasResource(CutawayPath);
1160
1420
  const since = ctx.lastRunTick;
1161
1421
  let hidden = 0;
1162
1422
  let rows = 0;
@@ -1191,9 +1451,24 @@ const prepareInstances = defineSystem({
1191
1451
  const dataY = hasData ? table.column(InstanceData, "y") : void 0;
1192
1452
  const dataChanged = hasData ? table.changedTicks(InstanceData) : zeroTicks(n);
1193
1453
  const vis = table.column(ComputedVisibility, "visible");
1454
+ const hiddenFlag = table.has(ShadowWhenHidden) ? InstanceFlags.ShadowOnly : 0;
1455
+ const layerMasks = table.has(RenderLayers) ? table.column(RenderLayers, "mask") : void 0;
1456
+ const hasGround = table.has(GroundLayer);
1457
+ const bands = hasGround ? table.column(GroundLayer, "band") : void 0;
1458
+ const orders = hasGround ? table.column(GroundLayer, "order") : void 0;
1459
+ const levels = hasGround ? table.column(GroundLayer, "level") : void 0;
1194
1460
  const deformed = table.has(SkinnedMesh) || table.has(MorphWeights);
1195
- if (deformed && !deforms) warnNoDeforms(world);
1196
- const tableFlags = (deforms && table.has(SkinnedMesh) ? InstanceFlags.Skinned : 0) | (deforms && table.has(MorphWeights) ? InstanceFlags.Morph : 0) | (table.has(NotShadowCaster) ? 0 : InstanceFlags.Caster) | (table.has(NotShadowReceiver) ? 0 : InstanceFlags.Receiver) | (hasRange ? InstanceFlags.Range : 0) | (hasLod ? InstanceFlags.Lod : 0);
1461
+ if (deformed && !deforms)
1462
+ warnFeatureMissing(
1463
+ world,
1464
+ "An entity has SkinnedMesh or MorphWeights",
1465
+ "skinningPlugin",
1466
+ "it draws in its rest pose"
1467
+ );
1468
+ const cut = table.has(Cutaway);
1469
+ if (cut && !cutaways)
1470
+ warnFeatureMissing(world, "An entity has Cutaway", "cutawayPlugin", "it draws whole");
1471
+ const tableFlags = (cut && cutaways ? InstanceFlags.Cutaway : 0) | (deforms && table.has(SkinnedMesh) ? InstanceFlags.Skinned : 0) | (deforms && table.has(MorphWeights) ? InstanceFlags.Morph : 0) | (table.has(NotShadowCaster) ? 0 : InstanceFlags.Caster) | (table.has(NotShadowReceiver) ? 0 : InstanceFlags.Receiver) | (hasRange ? InstanceFlags.Range : 0) | (hasLod ? InstanceFlags.Lod : 0);
1197
1472
  let u32 = store.u32;
1198
1473
  let slotFlags = store.flags;
1199
1474
  for (let i = 0; i < n; i++) {
@@ -1209,10 +1484,15 @@ const prepareInstances = defineSystem({
1209
1484
  const o = slot * INSTANCE_FLOATS;
1210
1485
  const visible = vis[i] !== 0;
1211
1486
  if (!visible) hidden++;
1212
- const flags = tableFlags | (visible ? InstanceFlags.Visible : 0);
1487
+ const flags = tableFlags | (visible ? InstanceFlags.Visible : hiddenFlag);
1213
1488
  const oldFlags = slotFlags[slot];
1214
1489
  if (fresh || gChanged[i] > since) store.moveSlot(slot, g, i * 12, fresh);
1215
- if (fresh || ((oldFlags ^ flags) & InstanceFlags.Lod) !== 0 || meshChanged[i] > since || materialChanged[i] > since || lodChanged[i] > since) {
1490
+ store.setLayers(slot, layerMasks ? layerMasks[i] : 1);
1491
+ const onOffGround = store.setGround(
1492
+ slot,
1493
+ hasGround ? groundKey(bands[i], orders[i], levels[i]) : Number.NaN
1494
+ );
1495
+ if (fresh || onOffGround || ((oldFlags ^ flags) & InstanceFlags.Lod) !== 0 || meshChanged[i] > since || materialChanged[i] > since || lodChanged[i] > since) {
1216
1496
  store.meshRefs[slot] = meshRefs[i] ?? null;
1217
1497
  store.materialRefs[slot] = materialRefs?.[i] ?? null;
1218
1498
  store.lods[slot] = hasLod ? {
@@ -1223,9 +1503,13 @@ const prepareInstances = defineSystem({
1223
1503
  resolveSlot(store, slot, meshes, materials);
1224
1504
  }
1225
1505
  if (oldFlags !== flags) {
1226
- u32[o + 13] = flags;
1506
+ u32[o + 13] = recordFlags(flags, store.layers[slot]);
1227
1507
  slotFlags[slot] = flags;
1508
+ if ((oldFlags ^ flags) & InstanceFlags.Cutaway)
1509
+ store.countCutaway(slot, flags & InstanceFlags.Cutaway ? 1 : -1);
1228
1510
  store.markDirty(slot);
1511
+ if ((oldFlags ^ flags) & (InstanceFlags.Visible | InstanceFlags.Caster | InstanceFlags.ShadowOnly))
1512
+ store.shadowEpoch++;
1229
1513
  }
1230
1514
  if (fresh || rangeChanged[i] > since) {
1231
1515
  if (hasRange) store.setRange(slot, rangeStart[i], rangeEnd[i]);
@@ -1240,7 +1524,18 @@ const prepareInstances = defineSystem({
1240
1524
  store.hiddenCount = hidden;
1241
1525
  store.drawableCount = rows - store.pending.size;
1242
1526
  if (store.pending.size > 0) {
1243
- for (const slot of store.pending) resolveSlot(store, slot, meshes, materials);
1527
+ const server = world.tryResource(AssetServerResource);
1528
+ for (const slot of store.pending) {
1529
+ if (server) {
1530
+ const meshRef = store.meshRefs[slot];
1531
+ if (meshRef?.guid !== void 0 && !meshes.has(meshRef.guid))
1532
+ server.missing(meshRef, "Mesh");
1533
+ const materialRef = store.materialRefs[slot];
1534
+ if (materialRef?.guid !== void 0 && !materials.has(materialRef.guid))
1535
+ server.missing(materialRef, "Material");
1536
+ }
1537
+ resolveSlot(store, slot, meshes, materials);
1538
+ }
1244
1539
  }
1245
1540
  for (const batch of store.batches) {
1246
1541
  if (batch.count === 0) continue;
@@ -1287,9 +1582,17 @@ function resolveSlot(store, slot, meshes, materials) {
1287
1582
  return;
1288
1583
  }
1289
1584
  store.pending.delete(slot);
1585
+ const ground = !Number.isNaN(store.groundKeys[slot]);
1586
+ const drawn = mesh.missing ? store.missingMaterial : material;
1290
1587
  store.assign(
1291
1588
  slot,
1292
- store.batchFor(mesh, material, meshRef?.guid, materialRef?.guid ?? void 0).index
1589
+ store.batchFor(
1590
+ mesh,
1591
+ drawn,
1592
+ meshRef?.guid,
1593
+ mesh.missing ? void 0 : materialRef?.guid ?? void 0,
1594
+ ground
1595
+ ).index
1293
1596
  );
1294
1597
  }
1295
1598
  function observeInstanceRemovals(world) {
@@ -1303,6 +1606,7 @@ function observeInstanceRemovals(world) {
1303
1606
  });
1304
1607
  }
1305
1608
  export {
1609
+ CUTAWAY_BIT,
1306
1610
  DeformPath,
1307
1611
  INSTANCE_BYTES,
1308
1612
  INSTANCE_FLOATS,
@@ -1311,6 +1615,8 @@ export {
1311
1615
  InstanceSlot,
1312
1616
  InstanceStore,
1313
1617
  Instances,
1618
+ LAYER_BITS,
1619
+ LAYER_SHIFT,
1314
1620
  LOD_BIT,
1315
1621
  LOD_UNSET,
1316
1622
  Lod,
@@ -1322,13 +1628,16 @@ export {
1322
1628
  NotShadowCaster,
1323
1629
  NotShadowReceiver,
1324
1630
  SLOT_MASK,
1631
+ ShadowWhenHidden,
1325
1632
  SkinnedMesh,
1326
1633
  VisibilityRange,
1327
1634
  createDrawList,
1328
1635
  createInstanceLayout,
1636
+ groundKey,
1329
1637
  lodSize,
1330
1638
  observeInstanceRemovals,
1331
1639
  prepareInstances,
1640
+ recordFlags,
1332
1641
  selectLod,
1333
1642
  slotSphere,
1334
1643
  sphereInFrustum