@woosh/meep-engine 2.36.22 → 2.36.23

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 (25) hide show
  1. package/core/binary/BitSet.js +2 -1
  2. package/core/bvh2/LeafNode.d.ts +2 -0
  3. package/core/collection/array/arrayQuickSort.js +73 -3
  4. package/core/collection/array/array_shrink_to_size.js +14 -0
  5. package/engine/ecs/EntityComponentDataset.js +10 -5
  6. package/engine/graphics/GraphicsEngine.d.ts +7 -4
  7. package/engine/graphics/GraphicsEngine.js +3 -1
  8. package/engine/graphics/ecs/mesh/MeshSystem.js +3 -2
  9. package/engine/graphics/ecs/mesh-v2/ShadedGeometry.js +67 -14
  10. package/engine/graphics/ecs/mesh-v2/ShadedGeometrySystem.js +5 -5
  11. package/engine/graphics/ecs/mesh-v2/render/adapters/InstancedRendererAdapter.js +1 -1
  12. package/engine/graphics/ecs/mesh-v2/sample/prototypeShadedGeometry.js +13 -6
  13. package/engine/graphics/geometry/instancing/InstancedMeshGroup.js +5 -2
  14. package/engine/graphics/micron/plugin/MicronRenderPlugin.js +4 -3
  15. package/engine/graphics/micron/render/v1/VGThreeRenderer.js +1 -1
  16. package/engine/graphics/particles/ecs/ParticleEmitterSystem.js +3 -3
  17. package/engine/graphics/particles/particular/engine/emitter/ParticleEmitter.js +1 -1
  18. package/engine/graphics/render/forward_plus/LightManager.js +4 -2
  19. package/engine/graphics/render/layers/RenderLayer.d.ts +3 -0
  20. package/engine/graphics/render/layers/RenderLayer.js +7 -2
  21. package/engine/graphics/render/visibility/IncrementalDeltaSet.d.ts +8 -3
  22. package/engine/graphics/render/visibility/IncrementalDeltaSet.js +65 -64
  23. package/engine/graphics/render/visibility/IncrementalDeltaSet.spec.js +39 -0
  24. package/engine/graphics/shadows/ShadowMapRenderer.js +5 -0
  25. package/package.json +1 -1
@@ -278,12 +278,13 @@ BitSet.prototype.nextSetBit = function (fromIndex) {
278
278
  }
279
279
 
280
280
 
281
- //scan the rest
281
+ //scan the rest of the words
282
282
  const word_count = bit_length / 32;
283
283
  for (; word_index < word_count; word_index++) {
284
284
  word = data[word_index];
285
285
 
286
286
  if (word === 0) {
287
+ // all 0s
287
288
  continue;
288
289
  }
289
290
 
@@ -2,4 +2,6 @@ import {Node} from "./Node";
2
2
 
3
3
  export class LeafNode<T> extends Node<T> {
4
4
  object: T
5
+
6
+ constructor(object: T, x0: number, y0: number, z0: number, x1: number, y1: number, z1: number)
5
7
  }
@@ -35,15 +35,15 @@ export function arrayQuickSort(data, score_function, score_function_context, sta
35
35
 
36
36
  const pivotIndex = (left + right) >> 1;
37
37
 
38
- const pivot = score_function.call(score_function_context,data[pivotIndex]);
38
+ const pivot = score_function.call(score_function_context, data[pivotIndex]);
39
39
 
40
40
  /* partition */
41
41
  while (i <= j) {
42
- while (score_function.call(score_function_context,data[i]) < pivot) {
42
+ while (score_function.call(score_function_context, data[i]) < pivot) {
43
43
  i++;
44
44
  }
45
45
 
46
- while (score_function.call(score_function_context,data[j]) > pivot) {
46
+ while (score_function.call(score_function_context, data[j]) > pivot) {
47
47
  j--;
48
48
  }
49
49
 
@@ -70,3 +70,73 @@ export function arrayQuickSort(data, score_function, score_function_context, sta
70
70
  }
71
71
  }
72
72
  }
73
+
74
+ /**
75
+ * @template T
76
+ * @param {T[]} data
77
+ * @param {function(T,T):number} compare_function
78
+ * @param {*} compare_function_context
79
+ * @param {number} start
80
+ * @param {number} end
81
+ */
82
+ export function array_quick_sort_by_comparator(data, compare_function, compare_function_context, start, end) {
83
+ if (start >= end) {
84
+ // section of 0 size, nothing to sort
85
+ return;
86
+ }
87
+
88
+ let stackPointer = 2;
89
+ let i, j;
90
+
91
+ stack[0] = start;
92
+ stack[1] = end;
93
+
94
+ while (stackPointer > 0) {
95
+ stackPointer -= 2;
96
+
97
+ const right = stack[stackPointer + 1];
98
+ const left = stack[stackPointer];
99
+
100
+ i = left;
101
+ j = right;
102
+
103
+ const pivotIndex = (left + right) >> 1;
104
+
105
+ const pivot = data[pivotIndex];
106
+
107
+ /* partition */
108
+ while (i <= j) {
109
+ while (compare_function.call(compare_function_context, data[i], pivot) < 0) {
110
+ i++;
111
+ }
112
+
113
+ while (compare_function.call(compare_function_context, data[j], pivot) > 0) {
114
+ j--;
115
+ }
116
+
117
+ if (i <= j) {
118
+
119
+ if (i !== j) {
120
+ //do swap
121
+ const tmp = data[i];
122
+ data[i] = data[j];
123
+ data[j] = tmp;
124
+
125
+ }
126
+
127
+ i++;
128
+ j--;
129
+ }
130
+ }
131
+
132
+ /* recursion */
133
+ if (left < j) {
134
+ stack[stackPointer++] = left;
135
+ stack[stackPointer++] = j;
136
+ }
137
+ if (i < right) {
138
+ stack[stackPointer++] = i;
139
+ stack[stackPointer++] = right;
140
+ }
141
+ }
142
+ }
@@ -0,0 +1,14 @@
1
+ //
2
+
3
+ /**
4
+ * @template T
5
+ * @param {T[]} array
6
+ * @param {number} length
7
+ */
8
+ export function array_shrink_to_size(array, length) {
9
+ const delta = array.length - length;
10
+
11
+ if (delta > 0) {
12
+ array.splice(length, delta);
13
+ }
14
+ }
@@ -7,6 +7,7 @@ import Signal, {
7
7
  import { EventType } from "./EntityManager.js";
8
8
  import { SignalHandler } from "../../core/events/signal/SignalHandler.js";
9
9
  import { arraySetDiff } from "../../core/collection/array/arraySetDiff.js";
10
+ import { array_shrink_to_size } from "../../core/collection/array/array_shrink_to_size.js";
10
11
 
11
12
  /**
12
13
  *
@@ -1088,7 +1089,9 @@ export class EntityComponentDataset {
1088
1089
  //map classes to indices
1089
1090
  const indices = scratch_array_0;
1090
1091
 
1092
+ // prepare sorted array of component indices
1091
1093
  const numClasses = classes.length;
1094
+
1092
1095
  for (i = 0; i < numClasses; i++) {
1093
1096
  const k = classes[i];
1094
1097
 
@@ -1108,11 +1111,8 @@ export class EntityComponentDataset {
1108
1111
 
1109
1112
  // crop args array to the right length (since we're re-using a scratch array)
1110
1113
  const args_length_intended = numClasses + 1;
1111
- const args_length_current = scratch_array_1.length;
1112
1114
 
1113
- if (args_length_current > args_length_intended) {
1114
- scratch_array_1.splice(args_length_intended, args_length_current - args_length_intended);
1115
- }
1115
+ array_shrink_to_size(scratch_array_1, args_length_intended);
1116
1116
 
1117
1117
  const entityOccupancy = this.entityOccupancy;
1118
1118
 
@@ -1120,7 +1120,12 @@ export class EntityComponentDataset {
1120
1120
  const component_occupancy = this.componentOccupancy;
1121
1121
  const components = this.components;
1122
1122
 
1123
- entity_loop: for (entityIndex = entityOccupancy.nextSetBit(0); entityIndex !== -1; entityIndex = entityOccupancy.nextSetBit(entityIndex + 1)) {
1123
+ entity_loop: for (
1124
+ entityIndex = entityOccupancy.nextSetBit(0);
1125
+ entityIndex !== -1;
1126
+ entityIndex = entityOccupancy.nextSetBit(entityIndex + 1)
1127
+ ) {
1128
+
1124
1129
  const componentOccupancyAddress = entityIndex * component_type_count;
1125
1130
 
1126
1131
  for (i = 0; i < numClasses; i++) {
@@ -5,12 +5,15 @@ import Vector2 from "../../core/geom/Vector2";
5
5
  import View from "../../view/View";
6
6
 
7
7
  export class GraphicsEngine {
8
- scene: Scene
9
- camera: PerspectiveCamera
8
+ readonly scene: Scene
9
+ readonly camera: PerspectiveCamera
10
10
 
11
- viewport: View
11
+ readonly viewport: View
12
12
 
13
- layers: RenderLayerManager
13
+ /**
14
+ * @deprecated
15
+ */
16
+ readonly layers: RenderLayerManager
14
17
 
15
18
  get domElement(): HTMLCanvasElement
16
19
 
@@ -559,10 +559,12 @@ GraphicsEngine.prototype.prepareRenderPass = function (passType) {
559
559
  let j = 0;
560
560
 
561
561
  const visible_objects = this.main_view.visible_objects;
562
+
562
563
  const element_count = visible_objects.size;
564
+ const elements = visible_objects.elements;
563
565
 
564
566
  for (let i = 0; i < element_count; i++) {
565
- const object3D = visible_objects.elements[i];
567
+ const object3D = elements[i];
566
568
 
567
569
  const object_pass = classifyPassTypeFromObject(object3D);
568
570
 
@@ -215,9 +215,10 @@ export class MeshSystem extends System {
215
215
  return;
216
216
  }
217
217
 
218
- const visibleElements = this.renderLayer.visibleSet.elements;
218
+ const visibleSet = this.renderLayer.visibleSet;
219
+ const visibleElements = visibleSet.elements;
219
220
 
220
- const n = visibleElements.length;
221
+ const n = visibleSet.size;
221
222
 
222
223
  for (let i = 0; i < n; i++) {
223
224
 
@@ -4,14 +4,53 @@ import { DrawMode } from "./DrawMode.js";
4
4
  import { ShadedGeometryFlags } from "./ShadedGeometryFlags.js";
5
5
  import { aabb3_matrix4_project } from "../../../../core/geom/3d/aabb/aabb3_matrix4_project.js";
6
6
 
7
- const _aabb3 = new AABB3();
8
-
7
+ /**
8
+ * @readonly
9
+ * @type {Float32Array}
10
+ */
9
11
  const scratch_aabb3_array = new Float32Array(6);
10
12
 
11
13
  const DEFAULT_FLAGS = ShadedGeometryFlags.CastShadow
12
14
  | ShadedGeometryFlags.ReceiveShadow
13
15
  ;
14
16
 
17
+ const FLAG_SET_EQUALITY = ShadedGeometryFlags.CastShadow
18
+ | ShadedGeometryFlags.ReceiveShadow
19
+ ;
20
+
21
+ /**
22
+ *
23
+ * @type {number}
24
+ */
25
+ const TRANSFORM_ALLOCATOR_BUCKET_CAPACITY = 16;
26
+ /**
27
+ *
28
+ * @type {ArrayBuffer|null}
29
+ */
30
+ let transform_bucket = null;
31
+ /**
32
+ *
33
+ * @type {number}
34
+ */
35
+ let transform_bucket_cursor = TRANSFORM_ALLOCATOR_BUCKET_CAPACITY;
36
+
37
+ /**
38
+ * custom Float32Array allocator, allocated memory in continuous chunks
39
+ * @returns {Float32Array}
40
+ */
41
+ function allocate_transform_m4() {
42
+ if (transform_bucket_cursor >= TRANSFORM_ALLOCATOR_BUCKET_CAPACITY) {
43
+ transform_bucket = new ArrayBuffer(TRANSFORM_ALLOCATOR_BUCKET_CAPACITY * 4 * 16);
44
+ transform_bucket_cursor = 0;
45
+ }
46
+
47
+ const result = new Float32Array(transform_bucket, transform_bucket_cursor * 16 * 4, 16);
48
+
49
+ transform_bucket_cursor++;
50
+
51
+ return result;
52
+ }
53
+
15
54
  export class ShadedGeometry {
16
55
  constructor() {
17
56
  /**
@@ -27,24 +66,23 @@ export class ShadedGeometry {
27
66
 
28
67
  /**
29
68
  *
30
- * @type {DrawMode|number}
69
+ * @type {number}
70
+ * @private
31
71
  */
32
- this.mode = DrawMode.Triangles;
72
+ this.__bvh_leaf_id = -1;
33
73
 
34
74
  /**
35
75
  *
36
- * @type {LeafNode<ShadedGeometry>}
76
+ * @type {ExplicitBinaryBoundingVolumeHierarchy|null}
37
77
  * @private
38
78
  */
39
- this.__bvh_leaf = new LeafNode(this, 0, 0, 0, 0, 0, 0);
79
+ this.__bvh_tree = null;
40
80
 
41
- this.__bvh_leaf_id = -1;
42
81
  /**
43
- *
44
- * @type {ExplicitBinaryBoundingVolumeHierarchy|null}
82
+ * @readonly
83
+ * @type {number[]|Float32Array}
45
84
  * @private
46
85
  */
47
- this.__bvh_tree = null;
48
86
  this.__bvh_aabb = new Float32Array(6);
49
87
 
50
88
  /**
@@ -65,12 +103,19 @@ export class ShadedGeometry {
65
103
  * Transform matrix
66
104
  * Transient, assigned in the system
67
105
  * @type {number[]|null}
106
+ * @readonly
68
107
  * @private
69
108
  */
70
- this.__transform = new Float32Array(16);
109
+ this.__transform = allocate_transform_m4();
110
+
111
+ /**
112
+ *
113
+ * @type {DrawMode|number}
114
+ */
115
+ this.mode = DrawMode.Triangles;
71
116
 
72
117
  /**
73
- * Transient property signifying how to render object
118
+ * Transient property signifying how to render object, such as using INSTANCED draw
74
119
  * @type {number}
75
120
  */
76
121
  this.draw_method = 0;
@@ -135,7 +180,12 @@ export class ShadedGeometry {
135
180
  * @return {number}
136
181
  */
137
182
  hash() {
138
- return this.geometry.id ^ this.material.id;
183
+ return this.geometry.id
184
+ ^ this.material.id
185
+ ^ (this.flags & FLAG_SET_EQUALITY)
186
+ ^ (this.draw_method << 16)
187
+ ^ (this.mode << 12)
188
+ ;
139
189
  }
140
190
 
141
191
  /**
@@ -146,6 +196,9 @@ export class ShadedGeometry {
146
196
  equals(other) {
147
197
  return this.geometry.id === other.geometry.id
148
198
  && this.material.id === other.material.id
199
+ && this.draw_method === other.draw_method
200
+ && this.mode === other.mode
201
+ && (this.flags & FLAG_SET_EQUALITY) === (other.flags & FLAG_SET_EQUALITY)
149
202
  ;
150
203
  }
151
204
 
@@ -171,7 +224,7 @@ export class ShadedGeometry {
171
224
  * @returns {LeafNode<ShadedGeometry>}
172
225
  */
173
226
  get bvh() {
174
- return this.__bvh_leaf;
227
+ throw new Error('Deprecated, use #getBoundingBox method instead');
175
228
  }
176
229
 
177
230
  /**
@@ -217,11 +217,6 @@ export class ShadedGeometrySystem extends System {
217
217
  t.toMatrix4(sg.__transform);
218
218
  sg.update_bounds();
219
219
 
220
- const leaf_node = sg.bvh;
221
-
222
- // record entity on the BVH leaf
223
- leaf_node.entity = entity;
224
-
225
220
  // remember entity for lookups
226
221
  sg.__entity = entity;
227
222
 
@@ -256,9 +251,14 @@ export class ShadedGeometrySystem extends System {
256
251
 
257
252
  // disconnect BVH
258
253
  const node_id = sg.__bvh_leaf_id;
254
+
259
255
  this.__bvh_binary.remove_leaf(node_id);
260
256
  this.__bvh_binary.release_node(node_id);
261
257
 
258
+ // reset BVH references
259
+ sg.__bvh_leaf_id = -1;
260
+ sg.__bvh_tree = null;
261
+
262
262
  const geometry_id = sg.geometry.id;
263
263
  const count_existing = this.__geometry_usage_counters.get(geometry_id);
264
264
 
@@ -119,7 +119,7 @@ export class InstancedRendererAdapter extends AbstractRenderAdapter {
119
119
 
120
120
  mesh.setTransformAt(index, sg.transform);
121
121
 
122
- mesh.count++;
122
+ mesh.count = new_size;
123
123
 
124
124
  this.__active_meshes.add(mesh);
125
125
  }
@@ -20,8 +20,11 @@ import { randomFromArray } from "../../../../../core/math/random/randomFromArray
20
20
  import { BehaviorComponent } from "../../../../intelligence/behavior/ecs/BehaviorComponent.js";
21
21
  import { OrbitingBehavior } from "../../../../../../model/game/util/behavior/OrbitingBehavior.js";
22
22
  import { BehaviorSystem } from "../../../../intelligence/behavior/ecs/BehaviorSystem.js";
23
- import { MicronShadedGeometryRenderAdapter } from "../../../micron/plugin/shaded_geometry/MicronShadedGeometryRenderAdapter.js";
23
+ import {
24
+ MicronShadedGeometryRenderAdapter
25
+ } from "../../../micron/plugin/shaded_geometry/MicronShadedGeometryRenderAdapter.js";
24
26
  import { MicronRenderContext } from "../../../micron/render/v1/MicronRenderContext.js";
27
+ import Vector2 from "../../../../../core/geom/Vector2.js";
25
28
 
26
29
  const eh = new EngineHarness();
27
30
 
@@ -65,12 +68,16 @@ async function main(engine) {
65
68
  await em.addSystem(sgs);
66
69
  await em.addSystem(new BehaviorSystem(engine));
67
70
 
71
+ const TERRAIN_SIZE = 200;
72
+
68
73
  EngineHarness.buildBasics({
69
74
  engine,
70
75
  yaw: -0.8,
71
76
  pitch: 1,
72
77
  distance: 46,
73
- focus: new Vector3(10, 0, 10)
78
+ terrainSize: new Vector2(TERRAIN_SIZE / 2, TERRAIN_SIZE / 2),
79
+ terrainResolution: 1,
80
+ focus: new Vector3(TERRAIN_SIZE / 2, 0, TERRAIN_SIZE / 2)
74
81
  });
75
82
 
76
83
 
@@ -86,7 +93,7 @@ async function main(engine) {
86
93
 
87
94
  const random = seededRandom(0);
88
95
 
89
- const INSTANCE_COUNT = 10000;
96
+ const INSTANCE_COUNT = 100000;
90
97
 
91
98
  const geometries = [];
92
99
 
@@ -108,12 +115,12 @@ async function main(engine) {
108
115
  new EntityBuilder()
109
116
  .add(Transform.fromJSON({
110
117
  position: {
111
- x: randomFloatBetween(random, 0, 20),
118
+ x: randomFloatBetween(random, 0, TERRAIN_SIZE),
112
119
  y: randomFloatBetween(random, 0, 10),
113
- z: randomFloatBetween(random, 0, 20)
120
+ z: randomFloatBetween(random, 0, TERRAIN_SIZE)
114
121
  },
115
122
  rotation: Quaternion.random(random),
116
- scale: 0.05
123
+ scale: 0.1
117
124
  }))
118
125
  .add(sg)
119
126
  .build(ecd);
@@ -12,6 +12,7 @@ import { composeCompile } from "../../material/composeCompile.js";
12
12
  import { typed_array_copy } from "../../../../core/collection/array/typed/typed_array_copy.js";
13
13
  import { rewriteMaterial } from "./rewriteMaterial.js";
14
14
  import { max3 } from "../../../../core/math/max3.js";
15
+ import { min2 } from "../../../../core/math/min2.js";
15
16
 
16
17
  export class InstancedMeshGroup {
17
18
  /**
@@ -217,8 +218,10 @@ export class InstancedMeshGroup {
217
218
  this.setCapacity(newCapacityInteger);
218
219
 
219
220
  } else if (
220
- currentCapacity * this.shrinkFactor > size
221
- && currentCapacity - this.shrinkConstant > size
221
+ min2(
222
+ currentCapacity * this.shrinkFactor,
223
+ currentCapacity - this.shrinkConstant
224
+ ) > size
222
225
  ) {
223
226
  // shrink
224
227
  this.setCapacity(size);
@@ -102,13 +102,14 @@ export class MicronRenderPlugin extends EnginePlugin {
102
102
  * @param {THREE.WebGLRenderer} renderer
103
103
  * @param {THREE.Camera} camera
104
104
  * @param {THREE.Object3D[]} elements
105
+ * @param element_count
105
106
  */
106
- render(renderer, camera, elements) {
107
+ render(renderer, camera, elements, element_count = elements.length) {
107
108
  const vgtr = this.__vgtr;
108
109
 
109
110
  vgtr.clear();
110
111
 
111
- vgtr.attachMany(elements);
112
+ vgtr.attachMany(elements, element_count);
112
113
 
113
114
  vgtr.update(renderer, camera);
114
115
  vgtr.render();
@@ -123,7 +124,7 @@ export class MicronRenderPlugin extends EnginePlugin {
123
124
  const observer = () => {
124
125
  const ge = this.engine.graphics;
125
126
 
126
- this.render(ge.renderer, view.camera, view.visible_objects.elements);
127
+ this.render(ge.renderer, view.camera, view.visible_objects.elements, view.visible_objects.size);
127
128
  };
128
129
 
129
130
  this.__view_observers.set(view, observer);
@@ -50,7 +50,7 @@ export class VGThreeRenderer {
50
50
  this.ctx.builder.appendToScene([scene]);
51
51
  }
52
52
 
53
- attachMany(objects) {
53
+ attachMany(objects, object_count = objects.length) {
54
54
  array_copy(objects, 0, this.__inputs, this.__inputs.length, objects.length);
55
55
  this.ctx.builder.appendToScene(objects);
56
56
  }
@@ -144,9 +144,9 @@ export class ParticleEmitterSystem extends System {
144
144
  self.particleEngine.shaderManager.update();
145
145
 
146
146
  const visibleSet = renderLayer.visibleSet;
147
- const visibleEmitters = visibleSet.size;
147
+ const visible_emitter_count = visibleSet.size;
148
148
 
149
- if (visibleEmitters > 0) {
149
+ if (visible_emitter_count > 0) {
150
150
  depthBuffer.referenceCount++;
151
151
  self.graphicsEngine.on.postRender.addOne(function () {
152
152
  depthBuffer.referenceCount--;
@@ -154,7 +154,7 @@ export class ParticleEmitterSystem extends System {
154
154
  }
155
155
 
156
156
 
157
- for (let i = 0; i < visibleEmitters; i++) {
157
+ for (let i = 0; i < visible_emitter_count; i++) {
158
158
  /**
159
159
  *
160
160
  * @type {Object3D}
@@ -724,7 +724,7 @@ export class ParticleEmitter {
724
724
 
725
725
  /**
726
726
  * PRECONDITION: no dead particles may exist in the pool. Make sure to call {@link ParticlePool#compact} before sorting
727
- * @private
727
+ * @protected
728
728
  * @param {Camera} camera THREE.js camera object
729
729
  */
730
730
  sort(camera) {
@@ -548,8 +548,10 @@ export class LightManager {
548
548
  __update_visible_bvh() {
549
549
  this.__visible_bvh_needs_update = false;
550
550
 
551
- const lights = this.__visible_lights.elements;
552
- const light_count = lights.length;
551
+ const visibleLights = this.__visible_lights;
552
+
553
+ const lights = visibleLights.elements;
554
+ const light_count = visibleLights.size;
553
555
 
554
556
 
555
557
  const vbvh = this.__visible_bvh;
@@ -12,5 +12,8 @@ export class RenderLayer {
12
12
 
13
13
  extractRenderable(leafData: any): Object3D
14
14
 
15
+ /**
16
+ * @deprecated
17
+ */
15
18
  visibleSet: IncrementalDeltaSet<Object3D>
16
19
  }
@@ -8,10 +8,14 @@ import { RenderPassType } from "../RenderPassType.js";
8
8
  import { IncrementalDeltaSet } from "../visibility/IncrementalDeltaSet.js";
9
9
  import { passThrough } from "../../../../core/function/Functions.js";
10
10
  import { traverseBinaryNodeUsingVisitor } from "../../../../core/bvh2/traversal/traverseBinaryNodeUsingVisitor.js";
11
- import { ThreeClippingPlaneComputingBVHVisitor } from "../../../../core/bvh2/traversal/ThreeClippingPlaneComputingBVHVisitor.js";
11
+ import {
12
+ ThreeClippingPlaneComputingBVHVisitor
13
+ } from "../../../../core/bvh2/traversal/ThreeClippingPlaneComputingBVHVisitor.js";
12
14
  import { RenderLayerState } from "./RenderLayerState.js";
13
15
  import { compare_three_objects } from "../../three/compare_three_objects.js";
14
- import { queryBinaryNode_FrustumIntersections_Data } from "../../../../core/bvh2/traversal/queryBinaryNode_FrustumIntersections.js";
16
+ import {
17
+ queryBinaryNode_FrustumIntersections_Data
18
+ } from "../../../../core/bvh2/traversal/queryBinaryNode_FrustumIntersections.js";
15
19
 
16
20
 
17
21
  /**
@@ -57,6 +61,7 @@ export class RenderLayer {
57
61
  /**
58
62
  * @deprecated use CameraView's visible set instead
59
63
  * Contains visible elements across all views, if element is in at least one view - it will be here
64
+ * @deprecated
60
65
  * @type {IncrementalDeltaSet<THREE.Object3D>}
61
66
  */
62
67
  this.visibleSet = new IncrementalDeltaSet(compare_three_objects);
@@ -1,7 +1,12 @@
1
+ import Signal from "../../../../core/events/signal/Signal";
2
+
1
3
  export class IncrementalDeltaSet<T> {
2
- elements: T[]
3
- version: number
4
- size: number
4
+ readonly elements: T[]
5
+ readonly version: number
6
+ readonly size: number
7
+
8
+ readonly onAdded: Signal<T>
9
+ readonly onRemoved: Signal<T>
5
10
 
6
11
  constructor(compare: (a: T, b: T) => number)
7
12
 
@@ -1,8 +1,8 @@
1
- import { BitSet } from "../../../../core/binary/BitSet.js";
2
1
  import Signal from "../../../../core/events/signal/Signal.js";
3
2
  import { assert } from "../../../../core/assert.js";
4
3
  import { IllegalStateException } from "../../../../core/fsm/exceptions/IllegalStateException.js";
5
4
  import { binarySearchHighIndex } from "../../../../core/collection/array/binarySearchHighIndex.js";
5
+ import { array_shrink_to_size } from "../../../../core/collection/array/array_shrink_to_size.js";
6
6
 
7
7
  /**
8
8
  * @readonly
@@ -34,6 +34,26 @@ export class IncrementalDeltaSet {
34
34
  */
35
35
  this.__elements = [];
36
36
 
37
+ /**
38
+ *
39
+ * @type {number}
40
+ * @private
41
+ */
42
+ this.__elements_cursor = 0;
43
+
44
+ /**
45
+ *
46
+ * @type {V[]}
47
+ * @private
48
+ */
49
+ this.__swap_elements = [];
50
+ /**
51
+ *
52
+ * @type {number}
53
+ * @private
54
+ */
55
+ this.__swap_elements_count = 0;
56
+
37
57
  /**
38
58
  *
39
59
  * @type {function(V, V): number}
@@ -47,13 +67,6 @@ export class IncrementalDeltaSet {
47
67
  */
48
68
  this.version = 0;
49
69
 
50
- /**
51
- * Dirty flag set, used during the update to keep track of which elements are to be remove
52
- * @type {BitSet}
53
- */
54
- this.flags = new BitSet();
55
- this.flags.preventShrink();
56
-
57
70
  /**
58
71
  * Number of currently held elements
59
72
  * @type {number}
@@ -185,9 +198,7 @@ export class IncrementalDeltaSet {
185
198
  return;
186
199
  }
187
200
 
188
- this.__elements.splice(0, this.size);
189
201
  this.size = 0;
190
- this.flags.reset();
191
202
 
192
203
  this.state = IncrementalDeltaSetState.Clear;
193
204
 
@@ -195,18 +206,20 @@ export class IncrementalDeltaSet {
195
206
  }
196
207
 
197
208
  includeAll() {
198
- if (this.size > 0) {
199
- this.flags.clearRange(0, this.size - 1);
200
- }
209
+ throw new Error('deprecated #includeAll');
201
210
  }
202
211
 
203
212
  initializeUpdate() {
204
- //mark all elements as dirty
205
- const size = this.size;
213
+ // perform swap
214
+ const swp_els = this.__elements;
215
+ this.__elements = this.__swap_elements
216
+ this.__swap_elements = swp_els;
206
217
 
207
- if (size > 0) {
208
- this.flags.setRange(0, size - 1);
209
- }
218
+ const swp_count = this.size;
219
+ this.size = this.__swap_elements_count;
220
+ this.__swap_elements_count = swp_count;
221
+
222
+ this.__elements_cursor = 0;
210
223
 
211
224
  this.state = IncrementalDeltaSetState.Building;
212
225
 
@@ -216,30 +229,41 @@ export class IncrementalDeltaSet {
216
229
  finalizeUpdate() {
217
230
  assert.equal(this.state, IncrementalDeltaSetState.Building, `Expected BUILDING state, instead got '${this.state}'`);
218
231
 
219
- let deletions = 0;
220
-
221
- //go through all dirty flags and remove corresponding elements
222
- const flags = this.flags;
223
- const elements = this.__elements;
224
-
225
- for (
226
- let i = flags.nextSetBit(0);
227
- i !== -1;
228
- i = flags.nextSetBit(i + 1)
229
- ) {
230
- const index = i - deletions;
231
-
232
- const removed = elements.splice(index, 1);
233
-
234
- this.onRemoved.send1(removed[0]);
235
-
236
- deletions++;
232
+ this.size = this.__elements_cursor;
233
+
234
+ // sort newly populated elements array
235
+ array_shrink_to_size(this.__elements, this.size);
236
+ this.__elements.sort(this.__compare);
237
+
238
+ let i0 = 0, i1 = 0;
239
+ while (i0 < this.size && i1 < this.__swap_elements_count) {
240
+ const el_0 = this.__elements[i0];
241
+ const el_1 = this.__swap_elements[i1];
242
+
243
+ const diff = this.__compare(el_0, el_1);
244
+
245
+ if (diff === 0) {
246
+ i0++;
247
+ i1++;
248
+ // found match
249
+ } else if (diff < 0) {
250
+ i0++;
251
+ // addition
252
+ this.onAdded.send1(el_0);
253
+ } else {
254
+ i1++;
255
+ // removal
256
+ this.onRemoved.send1(el_1);
257
+ }
237
258
  }
238
259
 
239
- //update size
240
- this.size -= deletions;
241
-
242
- flags.reset();
260
+ // process remainders
261
+ for (; i0 < this.size; i0++) {
262
+ this.onAdded.send1(this.__elements[i0]);
263
+ }
264
+ for (; i1 < this.__swap_elements_count; i1++) {
265
+ this.onRemoved.send1(this.__swap_elements[i1]);
266
+ }
243
267
 
244
268
  this.state = IncrementalDeltaSetState.Ready;
245
269
 
@@ -254,30 +278,7 @@ export class IncrementalDeltaSet {
254
278
  push(element) {
255
279
  const elements = this.__elements;
256
280
 
257
- //check if element is already in the set
258
- const existing_size = this.size;
259
- const index = binarySearchHighIndex(elements, element, this.__compare, 0, existing_size - 1);
260
-
261
-
262
- const flags = this.flags;
263
-
264
- if (elements[index] !== element) {
265
- // element is not in the set
266
-
267
- // shift flags to make space for it
268
- flags.shift_right(1, index, existing_size);
269
-
270
- // insert
271
- elements.splice(index, 0, element);
272
-
273
- this.size = existing_size + 1;
274
-
275
- this.onAdded.send1(element);
276
- }
277
-
278
- // clear flag for inserted element
279
- flags.clear(index);
280
-
281
+ elements[this.__elements_cursor++] = element;
281
282
  }
282
283
 
283
284
  /**
@@ -44,3 +44,42 @@ test('set is sorted after insert, push 3,2,1', () => {
44
44
  expect(set.elements[1]).toBe(2);
45
45
  expect(set.elements[2]).toBe(3);
46
46
  });
47
+
48
+
49
+ test('add/remove callbacks', () => {
50
+ const set = new IncrementalDeltaSet(compareNumbersAscending);
51
+
52
+ const add_listener = jest.fn();
53
+ const remove_listener = jest.fn();
54
+
55
+ set.onAdded.add(add_listener);
56
+ set.onRemoved.add(remove_listener);
57
+
58
+ set.initializeUpdate();
59
+
60
+ set.push(1);
61
+ set.push(2);
62
+ set.push(3);
63
+
64
+ set.finalizeUpdate();
65
+
66
+ expect(add_listener).toHaveBeenCalledWith(1);
67
+ expect(add_listener).toHaveBeenCalledWith(2);
68
+ expect(add_listener).toHaveBeenCalledWith(3);
69
+
70
+ expect(add_listener).toHaveBeenCalledTimes(3);
71
+
72
+ expect(remove_listener).not.toHaveBeenCalled();
73
+
74
+ set.initializeUpdate();
75
+
76
+ set.push(1);
77
+ set.push(3);
78
+
79
+ set.finalizeUpdate();
80
+
81
+ expect(add_listener).toHaveBeenCalledTimes(3);
82
+
83
+ expect(remove_listener).toHaveBeenCalledWith(2);
84
+ expect(remove_listener).toHaveBeenCalledTimes(1);
85
+ });
@@ -78,6 +78,7 @@ export class ShadowMapRenderer {
78
78
  const __autoClearColor = renderer.autoClearColor;
79
79
  const __autoClearDepth = renderer.autoClearDepth;
80
80
  const __autoClearStencil = renderer.autoClearStencil;
81
+ const __sortObjects = renderer.sortObjects;
81
82
 
82
83
  const target = shadow.map;
83
84
 
@@ -89,6 +90,9 @@ export class ShadowMapRenderer {
89
90
  renderer.autoClearDepth = false;
90
91
  renderer.autoClearStencil = false;
91
92
 
93
+ // disable sorting, unnecessary since fragment cost is very low and sorting is expensive for large number of objects
94
+ renderer.sortObjects = false;
95
+
92
96
  renderer.clear(true, true, false);
93
97
 
94
98
  shadow.updateMatrices(light, 0);
@@ -115,6 +119,7 @@ export class ShadowMapRenderer {
115
119
  renderer.autoClearColor = __autoClearColor;
116
120
  renderer.autoClearDepth = __autoClearDepth;
117
121
  renderer.autoClearStencil = __autoClearStencil;
122
+ renderer.sortObjects = __sortObjects;
118
123
 
119
124
  // increment frame index to signal to the renderer that geometries might need to be updated
120
125
  renderer.info.render.frame++;
package/package.json CHANGED
@@ -5,7 +5,7 @@
5
5
  "productName": "Meep",
6
6
  "description": "production-ready JavaScript game engine based on Entity Component System Architecture",
7
7
  "author": "Alexander Goldring",
8
- "version": "2.36.22",
8
+ "version": "2.36.23",
9
9
  "dependencies": {
10
10
  "gl-matrix": "3.4.3",
11
11
  "fast-levenshtein": "2.0.6",