@woosh/meep-engine 2.34.21 → 2.34.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.
@@ -509,6 +509,26 @@ BitSet.prototype.getAndClear = function (index) {
509
509
  return v;
510
510
  };
511
511
 
512
+ /**
513
+ *
514
+ * @param {number} distance
515
+ * @param {number} start_index
516
+ * @param {number} end_index
517
+ */
518
+ BitSet.prototype.shift = function (distance, start_index, end_index) {
519
+ if (distance > 0) {
520
+ for (let i = end_index; i >= start_index; i--) {
521
+ const v = this.get(i);
522
+ this.set(i + distance, v);
523
+ }
524
+ } else {
525
+ for (let i = start_index; i <= end_index; i++) {
526
+ const v = this.get(i);
527
+ this.set(i + distance, v);
528
+ }
529
+ }
530
+ };
531
+
512
532
  /**
513
533
  * Sets all of the bits in this BitSet to false.
514
534
  */
@@ -919,4 +919,34 @@ List.prototype.equals = function (other) {
919
919
  return true;
920
920
  };
921
921
 
922
+ /**
923
+ *
924
+ * @param {List<T>} other
925
+ * @returns {number}
926
+ */
927
+ List.prototype.compare = function (other) {
928
+ const l = this.length;
929
+
930
+ const other_length = other.length;
931
+ if (l !== other_length) {
932
+ return l - other_length;
933
+ }
934
+
935
+ for (let i = 0; i < l; i++) {
936
+ const a = this.get(i);
937
+
938
+ const b = other.get(i);
939
+
940
+ const delta = a.compare(b);
941
+
942
+ if (delta !== 0) {
943
+ return delta;
944
+ }
945
+
946
+ }
947
+
948
+ // same
949
+ return 0;
950
+ };
951
+
922
952
  export default List;
@@ -6,3 +6,5 @@
6
6
  export function invokeObjectClone(object) {
7
7
  return object.clone();
8
8
  }
9
+
10
+
@@ -0,0 +1,9 @@
1
+ /**
2
+ * @template {{compare:function(T):number}} T
3
+ * @param {T} a
4
+ * @param {T} b
5
+ * @return {number}
6
+ */
7
+ export function invokeObjectCompare(a, b) {
8
+ return a.compare(b);
9
+ }
@@ -42,6 +42,9 @@ import { Decal } from "./model/Decal.js";
42
42
  import { ReferencedTextureAtlas } from "../../texture/atlas/ReferencedTextureAtlas.js";
43
43
  import { v3_morton_encode_transformed } from "../../../../core/geom/3d/morton/v3_morton_encode_transformed.js";
44
44
  import { v3_distance } from "../../../../core/geom/v3_distance.js";
45
+ import { array_copy } from "../../../../core/collection/array/copyArray.js";
46
+ import { arrayQuickSort } from "../../../../core/collection/array/arrayQuickSort.js";
47
+ import { invokeObjectCompare } from "../../../../core/model/object/invokeObjectCompare.js";
45
48
 
46
49
  const LOOKUP_CACHE_SIZE = 1024; // must be power of two
47
50
 
@@ -227,7 +230,13 @@ export class LightManager {
227
230
  * @readonly
228
231
  * @private
229
232
  */
230
- this.__visible_lights = new IncrementalDeltaSet();
233
+ this.__visible_lights = new IncrementalDeltaSet(invokeObjectCompare);
234
+ /**
235
+ *
236
+ * @type {PointLightData[]}
237
+ * @private
238
+ */
239
+ this.__sorted_visible_lights = [];
231
240
 
232
241
  /**
233
242
  *
@@ -520,10 +529,18 @@ export class LightManager {
520
529
  * @private
521
530
  */
522
531
  __sort_visible_light() {
523
- this.__visible_lights.sort(
524
- this.__sort_visible_light_score,
525
- this
526
- );
532
+ const sorted_lights = this.__sorted_visible_lights;
533
+ const visible_light_set = this.__visible_lights;
534
+ const visible_light_count = visible_light_set.size;
535
+
536
+ if (sorted_lights.length > visible_light_count) {
537
+ // crop array if it's too big
538
+ sorted_lights.splice(visible_light_count, sorted_lights.length - visible_light_count)
539
+ }
540
+
541
+ array_copy(visible_light_set.elements, 0, sorted_lights, 0, visible_light_count);
542
+
543
+ arrayQuickSort(sorted_lights, this.__sort_visible_light_score, this, 0, visible_light_count - 1);
527
544
  }
528
545
 
529
546
  __update_visible_bvh() {
@@ -615,7 +632,7 @@ export class LightManager {
615
632
  *
616
633
  * @type {PointLightData[]}
617
634
  */
618
- const visible_lights = this.__visible_lights.elements;
635
+ const visible_lights = this.__sorted_visible_lights;
619
636
 
620
637
  // write light data into texture
621
638
  const visible_light_count = visible_lights.length;
@@ -21,6 +21,15 @@ export class PointLightData {
21
21
  this.bvh_leaf_main = new LeafNode(this, 0, 0, 0, 0, 0, 0);
22
22
  }
23
23
 
24
+ /**
25
+ *
26
+ * @param {PointLightData} other
27
+ * @returns {number}
28
+ */
29
+ compare(other) {
30
+ return this.light.compare(other.light);
31
+ }
32
+
24
33
  update() {
25
34
 
26
35
  const leaf = this.bvh_leaf_main;
@@ -1,4 +1,9 @@
1
+ let id_counter = 0;
2
+
1
3
  export class AbstractLight {
4
+ constructor() {
5
+ this.id = id_counter++;
6
+ }
2
7
 
3
8
  /**
4
9
  *
@@ -47,4 +52,15 @@ export class AbstractLight {
47
52
  toArray(destination, address) {
48
53
  throw new Error('Not Implemented');
49
54
  }
55
+
56
+ /**
57
+ *
58
+ * @param {AbstractLight} other
59
+ * @returns {number}
60
+ */
61
+ compare(other) {
62
+ return this.id - other.id;
63
+ }
50
64
  }
65
+
66
+ AbstractLight.prototype.type = 'Abstract';
@@ -12,6 +12,7 @@ import { ThreeFrustumsIntersectionBVHVisitor } from "../../../../core/bvh2/trave
12
12
  import { traverseBinaryNodeUsingVisitor } from "../../../../core/bvh2/traversal/traverseBinaryNodeUsingVisitor.js";
13
13
  import { ThreeClippingPlaneComputingBVHVisitor } from "../../../../core/bvh2/traversal/ThreeClippingPlaneComputingBVHVisitor.js";
14
14
  import { RenderLayerState } from "./RenderLayerState.js";
15
+ import { compare_three_objects } from "../../three/compare_three_objects.js";
15
16
 
16
17
 
17
18
  class VisibleLeafCollector extends BVHVisitor {
@@ -109,9 +110,9 @@ export class RenderLayer {
109
110
 
110
111
  /**
111
112
  * Contains visible elements across all views, if element is in at least one view - it will be here
112
- * @type {IncrementalDeltaSet<Object3D>}
113
+ * @type {IncrementalDeltaSet<THREE.Object3D>}
113
114
  */
114
- this.visibleSet = new IncrementalDeltaSet();
115
+ this.visibleSet = new IncrementalDeltaSet(compare_three_objects);
115
116
 
116
117
  /**
117
118
  *
@@ -5,6 +5,7 @@ import { frustumFromCamera } from "../../ecs/camera/CameraSystem.js";
5
5
  import { Frustum } from "three";
6
6
  import { read_frustum_planes_to_array } from "../forward_plus/cluster/read_frustum_planes_to_array.js";
7
7
  import Signal from "../../../../core/events/signal/Signal.js";
8
+ import { compare_three_objects } from "../../three/compare_three_objects.js";
8
9
 
9
10
  const DEFAULT_FLAGS = CameraViewFlags.Active
10
11
  | CameraViewFlags.AdaptivePlaneNear
@@ -52,7 +53,7 @@ export class CameraView {
52
53
  *
53
54
  * @type {IncrementalDeltaSet<THREE.Object3D>}
54
55
  */
55
- this.visible_objects = new IncrementalDeltaSet();
56
+ this.visible_objects = new IncrementalDeltaSet(compare_three_objects);
56
57
 
57
58
  /**
58
59
  *
@@ -3,6 +3,8 @@ export class IncrementalDeltaSet<T> {
3
3
  version: number
4
4
  size: number
5
5
 
6
+ constructor(compare: (a: T, b: T) => number)
7
+
6
8
  contains(element: T): boolean
7
9
 
8
10
  push(element: T): void
@@ -14,6 +16,4 @@ export class IncrementalDeltaSet<T> {
14
16
  initializeUpdate(): void
15
17
 
16
18
  finalizeUpdate(): void
17
-
18
- sort(f: (T) => number, ctx?: any): void
19
19
  }
@@ -2,7 +2,7 @@ import { BitSet } from "../../../../core/binary/BitSet.js";
2
2
  import Signal from "../../../../core/events/signal/Signal.js";
3
3
  import { assert } from "../../../../core/assert.js";
4
4
  import { IllegalStateException } from "../../../../core/fsm/exceptions/IllegalStateException.js";
5
- import { arrayQuickSort } from "../../../../core/collection/array/arrayQuickSort.js";
5
+ import { binarySearchHighIndex } from "../../../../core/collection/array/binarySearchHighIndex.js";
6
6
 
7
7
  /**
8
8
  * @readonly
@@ -16,15 +16,30 @@ const IncrementalDeltaSetState = {
16
16
 
17
17
  /**
18
18
  * accelerated Set data structure optimized for incremental re-building
19
+ * Always sorted
19
20
  * @template V
20
21
  */
21
22
  export class IncrementalDeltaSet {
22
- constructor() {
23
+ /**
24
+ *
25
+ * @param {function(V,V):number} compare stable comparator
26
+ */
27
+ constructor(compare) {
28
+ assert.isFunction(compare, 'compare');
29
+
23
30
  /**
24
31
  *
25
32
  * @type {V[]}
33
+ * @private
26
34
  */
27
- this.elements = [];
35
+ this.__elements = [];
36
+
37
+ /**
38
+ *
39
+ * @type {function(V, V): number}
40
+ * @private
41
+ */
42
+ this.__compare = compare;
28
43
 
29
44
  /**
30
45
  * Current version of the state, each time the set is updated - version changes
@@ -69,15 +84,39 @@ export class IncrementalDeltaSet {
69
84
  this.state = IncrementalDeltaSetState.Clear;
70
85
  }
71
86
 
87
+ /**
88
+ *
89
+ * @return {V[]}
90
+ */
91
+ get elements() {
92
+ return this.__elements;
93
+ }
72
94
 
73
95
  /**
74
96
  *
97
+ * @param {(V)=>any} visitor
98
+ * @param {*} [visitorContext]
99
+ */
100
+ forEach(visitor, visitorContext) {
101
+ const n = this.size;
102
+ const elements = this.__elements;
103
+
104
+ for (let i = 0; i < n; i++) {
105
+ const el = elements[i];
106
+
107
+ visitor.call(visitorContext, el);
108
+ }
109
+ }
110
+
111
+
112
+ /**
113
+ * @deprecated
75
114
  * @param {function(T):number} score_function
76
115
  * @param {*} [score_context]
77
116
  * @returns {void}
78
117
  */
79
118
  sort(score_function, score_context) {
80
- arrayQuickSort(this.elements, score_function, score_context, 0, this.size - 1);
119
+ throw new Error('Deprecated, the set is always sorted');
81
120
  }
82
121
 
83
122
  /**
@@ -86,7 +125,33 @@ export class IncrementalDeltaSet {
86
125
  * @returns {boolean}
87
126
  */
88
127
  contains(element) {
89
- return this.elements.indexOf(element) !== -1;
128
+ return this.__indexOf(element) !== -1;
129
+ }
130
+
131
+ /**
132
+ *
133
+ * @param {V} element
134
+ * @return {number}
135
+ * @private
136
+ */
137
+ __indexOf(element) {
138
+ const i = this.__indexExpected(element);
139
+
140
+ if (this.__elements[i] === element) {
141
+ return i;
142
+ } else {
143
+ return -1;
144
+ }
145
+ }
146
+
147
+ /**
148
+ *
149
+ * @param {V} element
150
+ * @return {number}
151
+ * @private
152
+ */
153
+ __indexExpected(element) {
154
+ return binarySearchHighIndex(this.__elements, element, this.__compare, 0, this.size - 1);
90
155
  }
91
156
 
92
157
  /**
@@ -99,10 +164,10 @@ export class IncrementalDeltaSet {
99
164
  throw new IllegalStateException("Cannot remove during build");
100
165
  }
101
166
 
102
- const i = this.elements.indexOf(element, 0);
167
+ const i = this.__indexOf(element);
103
168
 
104
169
  if (i !== -1) {
105
- this.elements.splice(i, 1);
170
+ this.__elements.splice(i, 1);
106
171
  this.size--;
107
172
 
108
173
  this.version++;
@@ -119,7 +184,7 @@ export class IncrementalDeltaSet {
119
184
  return;
120
185
  }
121
186
 
122
- this.elements.splice(0, this.size);
187
+ this.__elements.splice(0, this.size);
123
188
  this.size = 0;
124
189
  this.flags.reset();
125
190
 
@@ -153,14 +218,17 @@ export class IncrementalDeltaSet {
153
218
  let deletions = 0;
154
219
 
155
220
  //go through all dirty flags and remove corresponding elements
221
+ const flags = this.flags;
222
+ const elements = this.__elements;
223
+
156
224
  for (
157
- let i = this.flags.nextSetBit(0);
225
+ let i = flags.nextSetBit(0);
158
226
  i !== -1;
159
- i = this.flags.nextSetBit(i + 1)
227
+ i = flags.nextSetBit(i + 1)
160
228
  ) {
161
229
  const index = i - deletions;
162
230
 
163
- const removed = this.elements.splice(index, 1);
231
+ const removed = elements.splice(index, 1);
164
232
 
165
233
  this.onRemoved.send1(removed[0]);
166
234
 
@@ -170,7 +238,7 @@ export class IncrementalDeltaSet {
170
238
  //update size
171
239
  this.size -= deletions;
172
240
 
173
- this.flags.reset();
241
+ flags.reset();
174
242
 
175
243
  this.state = IncrementalDeltaSetState.Ready;
176
244
 
@@ -184,19 +252,26 @@ export class IncrementalDeltaSet {
184
252
  */
185
253
  push(element) {
186
254
  //check if element is already in the set
187
- const elements = this.elements;
188
- const i = elements.indexOf(element); //TODO this can probably be done faster. Maybe use a bloom filter? or some kind of a hash
255
+ const index = this.__indexExpected(element);
189
256
 
190
- if (i === -1) {
191
- elements.push(element);
257
+ const elements = this.__elements;
258
+
259
+ if (elements[index] !== element) {
260
+ // element is not in the set
261
+
262
+ // shift flags to make space for it
263
+ this.flags.shift(1, index, this.size);
264
+
265
+ // insert
266
+ elements.splice(index, 0, element);
192
267
  this.size++;
193
268
 
194
269
  this.onAdded.send1(element);
195
- } else {
196
- //Existing element. We're keeping it. clear dirty flag
197
- this.flags.clear(i);
198
270
  }
199
271
 
272
+ // clear flag for inserted element
273
+ this.flags.clear(index);
274
+
200
275
  }
201
276
 
202
277
  /**
@@ -204,7 +279,7 @@ export class IncrementalDeltaSet {
204
279
  * @param {IncrementalDeltaSet<V>} other
205
280
  */
206
281
  pushOther(other) {
207
- const elements = other.elements;
282
+ const elements = other.__elements;
208
283
  const n = other.size;
209
284
  for (let i = 0; i < n; i++) {
210
285
  const el = elements[i];
@@ -0,0 +1,46 @@
1
+ import { IncrementalDeltaSet } from "./IncrementalDeltaSet.js";
2
+ import { compareNumbersAscending } from "../../../../core/function/Functions.js";
3
+
4
+ test('set is sorted after insert, push 1,2', () => {
5
+ const set = new IncrementalDeltaSet(compareNumbersAscending);
6
+
7
+ set.push(1);
8
+ set.push(2);
9
+
10
+ expect(set.elements[0]).toBe(1);
11
+ expect(set.elements[1]).toBe(2);
12
+ });
13
+
14
+ test('set is sorted after insert, push 2,1', () => {
15
+ const set = new IncrementalDeltaSet(compareNumbersAscending);
16
+
17
+ set.push(2);
18
+ set.push(1);
19
+
20
+ expect(set.elements[0]).toBe(1);
21
+ expect(set.elements[1]).toBe(2);
22
+ });
23
+
24
+ test('set is sorted after insert, push 1,2,3', () => {
25
+ const set = new IncrementalDeltaSet(compareNumbersAscending);
26
+
27
+ set.push(1);
28
+ set.push(2);
29
+ set.push(3);
30
+
31
+ expect(set.elements[0]).toBe(1);
32
+ expect(set.elements[1]).toBe(2);
33
+ expect(set.elements[2]).toBe(3);
34
+ });
35
+
36
+ test('set is sorted after insert, push 3,2,1', () => {
37
+ const set = new IncrementalDeltaSet(compareNumbersAscending);
38
+
39
+ set.push(3);
40
+ set.push(2);
41
+ set.push(1);
42
+
43
+ expect(set.elements[0]).toBe(1);
44
+ expect(set.elements[1]).toBe(2);
45
+ expect(set.elements[2]).toBe(3);
46
+ });
@@ -0,0 +1,8 @@
1
+ /**
2
+ *
3
+ * @param {THREE.Object3D} a
4
+ * @param {THREE.Object3D} b
5
+ */
6
+ export function compare_three_objects(a, b) {
7
+ return a.id - b.id;
8
+ }
@@ -16,6 +16,8 @@ import { objectKeyByValue } from "../../../../core/model/object/objectKeyByValue
16
16
  import { computeStringHash } from "../../../../core/primitives/strings/computeStringHash.js";
17
17
  import { computeHashIntegerArray } from "../../../../core/collection/array/computeHashIntegerArray.js";
18
18
  import { computeHashFloat } from "../../../../core/math/hash/computeHashFloat.js";
19
+ import { compareStrings } from "../../../../core/primitives/strings/compareStrings.js";
20
+ import { compareNumbers } from "../../../../core/primitives/numbers/compareNumbers.js";
19
21
 
20
22
  /**
21
23
  * Convert decibel to percentage volume
@@ -127,6 +129,63 @@ export class SoundEmitter {
127
129
 
128
130
  }
129
131
 
132
+ /**
133
+ *
134
+ * @param {SoundEmitter} other
135
+ * @returns number
136
+ */
137
+ compare(other) {
138
+ const tracks = this.tracks.compare(other.tracks);
139
+
140
+ if (tracks !== 0) {
141
+ return tracks;
142
+ }
143
+
144
+ const channel = compareStrings(this.channel, other.channel);
145
+
146
+ if (channel !== 0) {
147
+ return channel;
148
+ }
149
+
150
+ const distanceMin = compareNumbers(this.__distanceMin, other.__distanceMin);
151
+
152
+ if (distanceMin !== 0) {
153
+ return distanceMin;
154
+ }
155
+ const distanceMax = compareNumbers(this.__distanceMax, other.__distanceMax);
156
+
157
+ if (distanceMax !== 0) {
158
+ return distanceMax;
159
+ }
160
+
161
+ const panningModel = compareNumbers(this.panningModel, other.panningModel);
162
+
163
+ if (panningModel !== 0) {
164
+ return panningModel;
165
+ }
166
+
167
+ const attenuation = compareNumbers(this.attenuation, other.attenuation);
168
+
169
+ if (attenuation !== 0) {
170
+ return attenuation;
171
+ }
172
+
173
+ const flags = compareNumbers(this.flags, other.flags);
174
+
175
+ if (flags !== 0) {
176
+ return flags;
177
+ }
178
+
179
+ const volume = compareNumbers(this.volume.getValue(), other.volume.getValue());
180
+
181
+ if (volume !== 0) {
182
+ return volume;
183
+ }
184
+
185
+
186
+ return 0;
187
+ }
188
+
130
189
 
131
190
  /**
132
191
  *
@@ -45,6 +45,14 @@ export class SoundEmitterComponentContext {
45
45
  this.__isConnected = false;
46
46
  }
47
47
 
48
+ /**
49
+ *
50
+ * @param {SoundEmitterComponentContext} other
51
+ */
52
+ compare(other) {
53
+ return this.emitter.compare(other.emitter);
54
+ }
55
+
48
56
  update() {
49
57
 
50
58
  const position = this.transform.position;
@@ -18,6 +18,7 @@ import { tryRotateSingleNode } from "../../../../core/bvh2/transform/RotationOpt
18
18
  import { SoundEmitterComponentContext } from "./SoundEmitterComponentContext.js";
19
19
  import { SoundEmitterFlags } from "./SoundEmitterFlags.js";
20
20
  import { SoundEmitterChannel } from "./SoundEmitterChannel.js";
21
+ import { invokeObjectCompare } from "../../../../core/model/object/invokeObjectCompare.js";
21
22
 
22
23
  /**
23
24
  * @readonly
@@ -96,7 +97,7 @@ export class SoundEmitterSystem extends System {
96
97
  *
97
98
  * @type {IncrementalDeltaSet<SoundEmitterComponentContext>}
98
99
  */
99
- this.activeSet = new IncrementalDeltaSet();
100
+ this.activeSet = new IncrementalDeltaSet(invokeObjectCompare);
100
101
 
101
102
  /**
102
103
  * Spatial index
@@ -4,6 +4,8 @@ import { assert } from "../../../../core/assert.js";
4
4
  import { computeStringHash } from "../../../../core/primitives/strings/computeStringHash.js";
5
5
  import { computeHashIntegerArray } from "../../../../core/collection/array/computeHashIntegerArray.js";
6
6
  import { computeHashFloat } from "../../../../core/math/hash/computeHashFloat.js";
7
+ import { compareStrings } from "../../../../core/primitives/strings/compareStrings.js";
8
+ import { compareNumbers } from "../../../../core/primitives/numbers/compareNumbers.js";
7
9
 
8
10
  const DEFAULT_FLAGS = SoundTrackFlags.StartWhenReady;
9
11
 
@@ -55,6 +57,45 @@ export class SoundTrack {
55
57
  this.nodes = null;
56
58
  }
57
59
 
60
+ /**
61
+ *
62
+ * @param {SoundTrack} other
63
+ * @returns number
64
+ */
65
+ compare(other) {
66
+ const url = compareStrings(this.url, other.url);
67
+
68
+ if (url !== 0) {
69
+ return url;
70
+ }
71
+
72
+ const time = compareNumbers(this.time, other.time);
73
+
74
+ if (time !== 0) {
75
+ return time;
76
+ }
77
+
78
+ const channel = compareStrings(this.channel, other.channel);
79
+
80
+ if (channel !== 0) {
81
+ return channel;
82
+ }
83
+
84
+ const volume = compareNumbers(this.__volume, other.__volume);
85
+
86
+ if (volume !== 0) {
87
+ return volume;
88
+ }
89
+
90
+ const flags = compareNumbers(this.flags, other.flags);
91
+
92
+ if (flags !== 0) {
93
+ return flags;
94
+ }
95
+
96
+ return 0;
97
+ }
98
+
58
99
  /**
59
100
  *
60
101
  * @param {number|SoundEmitterFlags} flag
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.34.21",
8
+ "version": "2.34.23",
9
9
  "dependencies": {
10
10
  "gl-matrix": "3.4.3",
11
11
  "fast-levenshtein": "2.0.6",