@woosh/meep-engine 2.34.31 → 2.34.35

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 (33) hide show
  1. package/core/assert.js +11 -0
  2. package/core/bvh2/BinaryNode.js +213 -26
  3. package/core/bvh2/Node.d.ts +3 -0
  4. package/core/bvh2/aabb3/AABB3.js +4 -3
  5. package/core/bvh2/aabb3/AABB3Math.js +1 -1
  6. package/core/bvh2/transform/RotationOptimizer.js +2 -262
  7. package/core/bvh2/transform/tryRotateSingleNode.js +259 -0
  8. package/core/collection/list/List.js +0 -34
  9. package/core/collection/table/DataTypeByteSizes.js +16 -0
  10. package/core/events/signal/Signal.js +1 -1
  11. package/engine/Engine.js +5 -0
  12. package/engine/asset/loaders/ArrayBufferLoader.js +7 -0
  13. package/engine/asset/loaders/image/ImageRGBADataLoader.js +11 -6
  14. package/engine/development/performance/AbstractMetric.js +4 -0
  15. package/engine/development/performance/RingBufferMetric.js +4 -0
  16. package/engine/ecs/EntityBuilder.js +12 -5
  17. package/engine/ecs/EntityComponentDataset.js +26 -6
  18. package/engine/ecs/attachment/AttachmentSystem.js +1 -1
  19. package/engine/ecs/sockets/AttachmentSockets.js +11 -1
  20. package/engine/ecs/transform/Transform.js +1 -1
  21. package/engine/graphics/ecs/mesh-v2/ShadedGeometry.js +16 -3
  22. package/engine/graphics/ecs/mesh-v2/ShadedGeometrySystem.js +3 -0
  23. package/engine/graphics/ecs/mesh-v2/sample/load_gltf.js +60 -0
  24. package/engine/graphics/ecs/mesh-v2/{prototypeShadedGeometry.js → sample/prototypeShadedGeometry.js} +5 -5
  25. package/engine/graphics/ecs/mesh-v2/three_object_to_entity_composition.js +37 -0
  26. package/engine/graphics/micron/build/buildMicronGeometryFromBufferGeometry.js +15 -0
  27. package/engine/graphics/micron/build/compute_micron_buffer_array_constructor.js +2 -0
  28. package/engine/graphics/micron/build/fill_patch_geometry_data.js +39 -5
  29. package/engine/graphics/micron/prototypeVirtualGeometry.js +7 -1
  30. package/engine/graphics/micron/render/instanced/AttributeDataTexture.js +3 -0
  31. package/engine/graphics/micron/render/instanced/shader/shader_rewrite_standard.js +23 -0
  32. package/engine/sound/ecs/emitter/SoundEmitterSystem.js +1 -1
  33. package/package.json +1 -1
@@ -0,0 +1,259 @@
1
+ import { boxSurfaceArea2 as computeArea, scoreBoxesSAH as computeAreaFor2 } from "../aabb3/AABB3Math.js";
2
+ import { surfaceAreaHeuristic as sah } from "../sah/surfaceAreaHeuristic.js";
3
+ import { isLeaf } from "../LeafNode.js";
4
+
5
+ /**
6
+ *
7
+ * @param {Node} node
8
+ * @param {BinaryNode} parent
9
+ */
10
+ function setParent(node, parent) {
11
+ node.parentNode = parent;
12
+ }
13
+
14
+ /**
15
+ * Based on paper "Fast, Effective BVH Updates for Animated Scenes" Kopta et. al. (url: http://www.cs.utah.edu/~thiago/papers/rotations.pdf)
16
+ * @param {BinaryNode} node
17
+ * @returns {int} value from 0 to 6, 0 means no rotation has taken place
18
+ */
19
+ export function tryRotateSingleNode(node) {
20
+
21
+ const left = node.left;
22
+ const right = node.right;
23
+
24
+ if (left === null || right === null) {
25
+ return 0;
26
+ }
27
+
28
+ /*
29
+ First we compute best rotation to apply, enumerated by 0-6
30
+ */
31
+
32
+ let bestRotation = 0;
33
+
34
+
35
+ const nodeArea = computeArea(node);
36
+ const leftArea = computeArea(left);
37
+ const rightArea = computeArea(right);
38
+
39
+ const leftLeaves = left.leafNodeCount;
40
+ const rightLeaves = right.leafNodeCount;
41
+
42
+
43
+ let bestCost = sah(
44
+ nodeArea,
45
+ leftArea,
46
+ rightArea,
47
+ leftLeaves,
48
+ rightLeaves
49
+ );
50
+
51
+ let candidateCost;
52
+
53
+ let leftLeftArea,
54
+ leftRightArea,
55
+ rightLeftArea,
56
+ rightRightArea;
57
+
58
+ let leftLeft,
59
+ leftRight,
60
+ rightLeft,
61
+ rightRight;
62
+
63
+ let leftLeftLeaves,
64
+ leftRightLeaves,
65
+ rightLeftLeaves,
66
+ rightRightLeaves;
67
+
68
+ let considerLeft = !isLeaf(left);
69
+
70
+ if (considerLeft) {
71
+ leftLeft = left.left;
72
+ leftRight = left.right;
73
+
74
+ considerLeft = (leftLeft !== null && leftRight !== null);
75
+ if (considerLeft) {
76
+
77
+ leftLeftArea = computeArea(leftLeft);
78
+ leftRightArea = computeArea(leftRight);
79
+
80
+ leftLeftLeaves = leftLeft.leafNodeCount;
81
+ leftRightLeaves = leftRight.leafNodeCount;
82
+
83
+ // (1) N N //
84
+ // / \ / \ //
85
+ // L R -----> L LL //
86
+ // / \ / \ //
87
+ // LL LR R LR //
88
+ candidateCost = sah(nodeArea, computeAreaFor2(right, leftRight), leftLeftArea, rightLeaves + leftRightLeaves, leftLeftLeaves);
89
+
90
+ if (candidateCost < bestCost) {
91
+ bestRotation = 1;
92
+ bestCost = candidateCost;
93
+ }
94
+
95
+ // (2) N N //
96
+ // / \ / \ //
97
+ // L R -----> L LR //
98
+ // / \ / \ //
99
+ // LL LR LL R //
100
+ candidateCost = sah(nodeArea, computeAreaFor2(leftLeft, right), leftRightArea, leftLeftLeaves + rightLeaves, leftRightLeaves);
101
+
102
+
103
+ if (candidateCost < bestCost) {
104
+ bestRotation = 2;
105
+ bestCost = candidateCost;
106
+ }
107
+ }
108
+ }
109
+ if (!isLeaf(right)) {
110
+ rightLeft = right.left;
111
+ rightRight = right.right;
112
+ if (rightLeft !== null && rightRight !== null) {
113
+
114
+ rightLeftArea = computeArea(rightLeft);
115
+ rightRightArea = computeArea(rightRight);
116
+
117
+ rightLeftLeaves = rightLeft.leafNodeCount;
118
+ rightRightLeaves = rightRight.leafNodeCount;
119
+
120
+ // (3) N N //
121
+ // / \ / \ //
122
+ // L R -----> RL R //
123
+ // / \ / \ //
124
+ // RL RR L RR //
125
+ candidateCost = sah(nodeArea, rightLeftArea, computeAreaFor2(left, rightRight), rightLeftLeaves, leftLeaves + rightRightLeaves);
126
+
127
+ if (candidateCost < bestCost) {
128
+ bestRotation = 3;
129
+ bestCost = candidateCost;
130
+ }
131
+
132
+ // (4) N N //
133
+ // / \ / \ //
134
+ // L R -----> RR R //
135
+ // / \ / \ //
136
+ // RL RR RL L //
137
+ candidateCost = sah(nodeArea, rightRightArea, computeAreaFor2(rightLeft, left), rightRightLeaves, rightLeftLeaves + leftLeaves);
138
+
139
+
140
+ if (candidateCost < bestCost) {
141
+ bestRotation = 4;
142
+ bestCost = candidateCost;
143
+ }
144
+
145
+ if (considerLeft) {
146
+ //both child nodes are not leaf
147
+
148
+ // (5) N N //
149
+ // / \ / \ //
150
+ // L R -----> L R //
151
+ // / \ / \ / \ / \ //
152
+ // LL LR RL RR LL RL LR RR //
153
+ candidateCost = sah(
154
+ nodeArea,
155
+ computeAreaFor2(leftLeft, rightLeft),
156
+ computeAreaFor2(leftRight, rightRight),
157
+ leftLeftLeaves + rightLeftLeaves,
158
+ leftRightLeaves + rightRightLeaves
159
+ );
160
+
161
+
162
+ if (candidateCost < bestCost) {
163
+ bestRotation = 5;
164
+ bestCost = candidateCost;
165
+ }
166
+
167
+ // (6) N N //
168
+ // / \ / \ //
169
+ // L R -----> L R //
170
+ // / \ / \ / \ / \ //
171
+ // LL LR RL RR LL RR RL LR //
172
+ candidateCost = sah(
173
+ nodeArea,
174
+ computeAreaFor2(leftLeft, rightRight),
175
+ computeAreaFor2(rightLeft, leftRight),
176
+ leftLeftLeaves + rightRightLeaves,
177
+ rightLeftLeaves + leftRightLeaves
178
+ );
179
+
180
+ if (candidateCost < bestCost) {
181
+ bestRotation = 6;
182
+ }
183
+ }
184
+ }
185
+ }
186
+
187
+ switch (bestRotation) {
188
+ case 0:
189
+ //no rotation
190
+ break;
191
+ case 1:
192
+ node.right = leftLeft;
193
+ setParent(leftLeft, node);
194
+ left.left = right;
195
+ setParent(right, left);
196
+
197
+ left.refit();
198
+ //update leaf count
199
+ node.left.updateLeafNodeCount();
200
+ break;
201
+ case 2:
202
+ node.right = leftRight;
203
+ setParent(leftRight, node);
204
+ left.right = right;
205
+ setParent(right, left);
206
+
207
+ left.refit();
208
+ //update leaf count
209
+ node.left.updateLeafNodeCount();
210
+ break;
211
+ case 3:
212
+ node.left = rightLeft;
213
+ setParent(rightLeft, node);
214
+ right.left = left;
215
+ setParent(left, right);
216
+
217
+ right.refit();
218
+ //update leaf count
219
+ node.right.updateLeafNodeCount();
220
+ break;
221
+ case 4:
222
+ node.left = rightRight;
223
+ setParent(rightRight, node);
224
+ right.right = left;
225
+ setParent(left, right);
226
+
227
+ right.refit();
228
+ //update leaf count
229
+ node.right.updateLeafNodeCount();
230
+ break;
231
+ case 5:
232
+ left.right = rightLeft;
233
+ setParent(rightLeft, left);
234
+ right.left = leftRight;
235
+ setParent(leftRight, right);
236
+
237
+ left.refit();
238
+ right.refit();
239
+
240
+ //update leaf count
241
+ node.left.updateLeafNodeCount();
242
+ node.right.updateLeafNodeCount();
243
+ break;
244
+ case 6:
245
+ left.right = rightRight;
246
+ setParent(rightRight, left);
247
+ right.right = leftRight;
248
+ setParent(leftRight, right);
249
+
250
+ left.refit();
251
+ right.refit();
252
+ //update leaf count
253
+ node.left.updateLeafNodeCount();
254
+ node.right.updateLeafNodeCount();
255
+ break;
256
+ }
257
+
258
+ return bestRotation;
259
+ }
@@ -719,40 +719,6 @@ List.prototype.copy = function (other) {
719
719
  }
720
720
  };
721
721
 
722
- /**
723
- *
724
- * @param {List} other
725
- * @param {function} elementComparator
726
- * @returns {number} -1, 0 or 1
727
- */
728
- List.prototype.compare = function (other, elementComparator) {
729
- const thisLength = this.length;
730
- const otherLength = other.length;
731
-
732
- const deltaLength = thisLength - otherLength;
733
-
734
- if (deltaLength !== 0) {
735
- return deltaLength;
736
- }
737
-
738
- for (let i = 0; i < thisLength; i++) {
739
- const thisElement = this.get(i);
740
- const otherElement = other.get(i);
741
-
742
- if (thisElement === otherElement) {
743
- continue;
744
- }
745
-
746
- const elementDelta = elementComparator(thisElement, otherElement);
747
-
748
- if (elementDelta !== 0) {
749
- return elementDelta;
750
- }
751
- }
752
-
753
- return 0;
754
- };
755
-
756
722
  /**
757
723
  * NOTE: do not modify resulting array
758
724
  * @returns {T[]}
@@ -19,3 +19,19 @@ export const DataTypeByteSizes = {
19
19
  [DataType.Float32]: 4,
20
20
  [DataType.Float64]: 8
21
21
  };
22
+
23
+
24
+ /**
25
+ *
26
+ * @param {DataType} type
27
+ * @return {number}
28
+ */
29
+ export function dataTypeByteSize(type) {
30
+ const size = DataTypeByteSizes[type];
31
+
32
+ if (size === undefined) {
33
+ throw new Error(`Unsupported type '${type}'`);
34
+ }
35
+
36
+ return size;
37
+ }
@@ -162,7 +162,7 @@ export class Signal {
162
162
  * @param {*} [context]
163
163
  */
164
164
  add(h, context) {
165
- assert.typeOf(h, "function", "handler");
165
+ assert.isFunction(h, "handler");
166
166
 
167
167
  // if (!ENV_PRODUCTION) {
168
168
  // if (this.handlers.length + 1 === 100) {
package/engine/Engine.js CHANGED
@@ -131,6 +131,11 @@ class Engine {
131
131
  const v_fps = (1 / stats.mean).toFixed(2);
132
132
 
133
133
 
134
+ // clear stats
135
+ m_render.clear();
136
+ m_sim.clear();
137
+ m_frame.clear();
138
+
134
139
  return `FPS: ${v_fps}, RENDER: ${v_render}ms, SIMULATION: ${v_sim}ms`;
135
140
  });
136
141
 
@@ -1,5 +1,6 @@
1
1
  import { Asset } from "../Asset.js";
2
2
  import { AssetLoader } from "./AssetLoader.js";
3
+ import { CrossOriginKind } from "../CORS/CrossOriginKind.js";
3
4
 
4
5
  export class ArrayBufferLoader extends AssetLoader {
5
6
  load(path, success, failure, progress) {
@@ -8,6 +9,12 @@ export class ArrayBufferLoader extends AssetLoader {
8
9
  xhr.open('GET', path, true);
9
10
  xhr.responseType = "arraybuffer";
10
11
 
12
+ const coc = this.assetManager.crossOriginConfig;
13
+
14
+ if (coc.kind === CrossOriginKind.UseCredentials) {
15
+ xhr.withCredentials = true;
16
+ }
17
+
11
18
  xhr.addEventListener('load', function () {
12
19
  if (xhr.readyState === xhr.DONE && xhr.status === 200) {
13
20
  const arrayBuffer = xhr.response;
@@ -4,11 +4,11 @@ import { GameAssetType } from "../../GameAssetType.js";
4
4
  import { convertTexture2Sampler2D } from "../../../graphics/texture/sampler/convertTexture2Sampler2D.js";
5
5
  import { ImageRGBADataAsset } from "./ImageRGBADataAsset.js";
6
6
  import { ArrayBufferLoader } from "../ArrayBufferLoader.js";
7
- import { noop } from "../../../../core/function/Functions.js";
8
7
  import { CodecWithFallback } from "./codec/CodecWithFallback.js";
9
8
  import { ThreadedImageDecoder } from "./codec/ThreadedImageDecoder.js";
10
9
  import { NativeImageDecoder } from "./codec/NativeImageDecoder.js";
11
10
 
11
+ const ASSET_TYPE_ARRAY_BUFFER = "arraybuffer";
12
12
 
13
13
  export class ImageRGBADataLoader extends AssetLoader {
14
14
  constructor() {
@@ -20,6 +20,15 @@ export class ImageRGBADataLoader extends AssetLoader {
20
20
  );
21
21
  }
22
22
 
23
+ async link(assetManager, engine) {
24
+
25
+ if (assetManager.getLoaderByType(ASSET_TYPE_ARRAY_BUFFER) === undefined) {
26
+ assetManager.registerLoader(ASSET_TYPE_ARRAY_BUFFER, new ArrayBufferLoader());
27
+ }
28
+
29
+ super.link(assetManager, engine);
30
+ }
31
+
23
32
  /**
24
33
  *
25
34
  * @param {string} path
@@ -27,11 +36,7 @@ export class ImageRGBADataLoader extends AssetLoader {
27
36
  * @private
28
37
  */
29
38
  async __decode_via_worker(path) {
30
- const loader = new ArrayBufferLoader();
31
-
32
- const asset = await new Promise((resolve, reject) => {
33
- loader.load(path, resolve, reject, noop);
34
- });
39
+ const asset = await this.assetManager.promise(path, ASSET_TYPE_ARRAY_BUFFER);
35
40
 
36
41
  /**
37
42
  * @type {ArrayBuffer}
@@ -21,4 +21,8 @@ export class AbstractMetric {
21
21
  computeStats(result) {
22
22
  throw new Error('Not implemented');
23
23
  }
24
+
25
+ clear(){
26
+ throw new Error('Not implemented');
27
+ }
24
28
  }
@@ -31,4 +31,8 @@ export class RingBufferMetric extends AbstractMetric {
31
31
  result.max = computeArrayMax(array, 0, data_count);
32
32
  result.min = computeArrayMin(array, 0, data_count);
33
33
  }
34
+
35
+ clear() {
36
+ this.__data.clear();
37
+ }
34
38
  }
@@ -132,7 +132,7 @@ class EntityBuilder {
132
132
  throw new Error("Can not add " + componentInstance + " to EntityBuilder");
133
133
  }
134
134
 
135
- assert.notOk(this.getComponent(componentInstance.__proto__.constructor) !== null, `Component of this type already exists`);
135
+ assert.notOk(this.getComponent(componentInstance.__proto__.constructor) !== null, 'Component of this type already exists');
136
136
 
137
137
  this.element.push(componentInstance);
138
138
 
@@ -150,12 +150,16 @@ class EntityBuilder {
150
150
  * @returns {T|null} component of specified class
151
151
  */
152
152
  getComponent(klass) {
153
- for (let i = 0; i < this.element.length; i++) {
154
- const component = this.element[i];
153
+ const elements = this.element;
154
+ const element_count = elements.length;
155
+
156
+ for (let i = 0; i < element_count; i++) {
157
+ const component = elements[i];
155
158
  if (component instanceof klass) {
156
159
  return component;
157
160
  }
158
161
  }
162
+
159
163
  return null;
160
164
  }
161
165
 
@@ -314,7 +318,10 @@ class EntityBuilder {
314
318
  assert.defined(dataset, "dataset");
315
319
  assert.notNull(dataset, "dataset");
316
320
 
317
- if (this.getFlag(EntityBuilderFlags.Built) && checkExistingComponents(this.entity, this.element, dataset)) {
321
+ if (
322
+ this.getFlag(EntityBuilderFlags.Built)
323
+ && checkExistingComponents(this.entity, this.element, dataset)
324
+ ) {
318
325
  //already built
319
326
  return this.entity;
320
327
  }
@@ -322,7 +329,7 @@ class EntityBuilder {
322
329
  const entity = this.entity = dataset.createEntity();
323
330
  this.dataset = dataset;
324
331
 
325
- let i, l;
332
+ let i;
326
333
 
327
334
  const listeners = this.deferredListeners;
328
335
  const listeners_count = listeners.length;
@@ -122,6 +122,13 @@ export class EntityComponentDataset {
122
122
  */
123
123
  this.componentTypeMap = [];
124
124
 
125
+ /**
126
+ * Fast index lookup from a component class
127
+ * @type {Map<Class, number>}
128
+ * @private
129
+ */
130
+ this.__type_to_index_map = new Map();
131
+
125
132
  /**
126
133
  * How many component types exist for this collection. This is the same as componentMap.length
127
134
  * @private
@@ -605,6 +612,13 @@ export class EntityComponentDataset {
605
612
  updateObservers(indexRemapping);
606
613
 
607
614
  this.componentTypeMap = map;
615
+
616
+ // rebuild class->index lookup
617
+ this.__type_to_index_map.clear();
618
+ for (let i = 0; i < newComponentTypeCount; i++) {
619
+ this.__type_to_index_map.set(map[i], i);
620
+ }
621
+
608
622
  this.componentTypeCount = newComponentTypeCount;
609
623
  }
610
624
 
@@ -876,7 +890,13 @@ export class EntityComponentDataset {
876
890
  * @returns {number}
877
891
  */
878
892
  computeComponentTypeIndex(klass) {
879
- return this.componentTypeMap.indexOf(klass);
893
+ const idx = this.__type_to_index_map.get(klass);
894
+
895
+ if (idx === undefined) {
896
+ return -1;
897
+ }
898
+
899
+ return idx;
880
900
  }
881
901
 
882
902
  /**
@@ -926,16 +946,16 @@ export class EntityComponentDataset {
926
946
  addComponentToEntity(entityIndex, componentInstance) {
927
947
 
928
948
  if (!ENV_PRODUCTION) {
929
- assert.notEqual(componentInstance, null, "componentInstance is null");
949
+ assert.notNull(componentInstance, "componentInstance");
930
950
 
931
- assert.notEqual(componentInstance, undefined, "componentInstance is undefined");
951
+ assert.defined(componentInstance, "componentInstance");
932
952
  }
933
953
 
934
954
  const klass = componentInstance.constructor;
935
955
 
936
- const componentTypeIndex = this.componentTypeMap.indexOf(klass);
956
+ const componentTypeIndex = this.__type_to_index_map.get(klass);
937
957
 
938
- if (componentTypeIndex === -1) {
958
+ if (typeof componentTypeIndex !== "number") {
939
959
 
940
960
 
941
961
  throw new Error(`Component class not found in this dataset for componentInstance ${stringifyComponent(componentInstance)}`);
@@ -1509,7 +1529,7 @@ export class EntityComponentDataset {
1509
1529
  sendEvent(entity, name, event) {
1510
1530
  // console.log("sendEvent", entity, name, event);
1511
1531
 
1512
- assert.typeOf(name, "string", "name");
1532
+ assert.isString(name, "name");
1513
1533
 
1514
1534
  dispatchEntityEventListeners(this.__entityEventListeners, entity, name, event);
1515
1535
 
@@ -175,7 +175,7 @@ export class AttachmentSystem extends System {
175
175
 
176
176
  const boneName = attachmentSocket.boneName;
177
177
 
178
- assert.typeOf(boneName, 'string', 'boneName');
178
+ assert.isString(boneName, 'boneName');
179
179
 
180
180
  //try to get the bone
181
181
  const mesh = ecd.getComponent(parent, Mesh);
@@ -18,7 +18,17 @@ export class AttachmentSockets {
18
18
  * @returns {AttachmentSocket|undefined}
19
19
  */
20
20
  get(id) {
21
- return this.elements.find(s => s.id === id);
21
+ const elements = this.elements;
22
+ const n = elements.length;
23
+
24
+ for (let i = 0; i < n; i++) {
25
+ const socket = elements.get(i);
26
+
27
+ if (socket.id === id) {
28
+ return socket;
29
+ }
30
+ }
31
+
22
32
  }
23
33
 
24
34
  /**
@@ -162,7 +162,7 @@ export class Transform {
162
162
 
163
163
  /**
164
164
  *
165
- * @param {Matrix4} matrix
165
+ * @param {THREE.Matrix4} matrix
166
166
  */
167
167
  fromThreeMatrix4(matrix) {
168
168
  this.fromMatrix4(matrix.elements);
@@ -125,16 +125,29 @@ export class ShadedGeometry {
125
125
  }
126
126
 
127
127
  __build_object() {
128
+ /**
129
+ * @type {THREE.Object3D}
130
+ */
131
+ let object;
132
+
128
133
  const mode = this.mode;
134
+ const shader = this.material;
135
+ const geo = this.geometry;
136
+
129
137
  if (mode === DrawMode.Triangles) {
130
- this.__cached_object = new Mesh(this.geometry, this.material);
138
+ object = new Mesh(geo, shader);
131
139
  } else if (mode === DrawMode.Lines) {
132
- this.__cached_object = new Line(this.geometry, this.material);
140
+ object = new Line(geo, shader);
133
141
  } else if (mode === DrawMode.LineSegments) {
134
- this.__cached_object = new LineSegments(this.geometry, this.material);
142
+ object = new LineSegments(geo, shader);
135
143
  } else {
136
144
  throw new Error(`Unsupported draw mode '${mode}'`);
137
145
  }
146
+
147
+ object.matrixAutoUpdate = false;
148
+ object.frustumCulled = false;
149
+
150
+ this.__cached_object = object;
138
151
  }
139
152
 
140
153
  get bvh() {
@@ -81,6 +81,9 @@ export class ShadedGeometrySystem extends System {
81
81
 
82
82
  const leaf_node = sg.bvh;
83
83
 
84
+ // record entity on the BVH leaf
85
+ leaf_node.entity = entity;
86
+
84
87
  // remember entity for lookups
85
88
  sg.__entity = entity;
86
89