@woosh/meep-engine 2.39.39 → 2.39.42

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 (62) hide show
  1. package/core/binary/operations/ceilPowerOfTwo.spec.js +17 -0
  2. package/core/bvh2/aabb3/AABB3.js +6 -6
  3. package/core/bvh2/binary/2/BinaryUint32BVH.js +7 -1
  4. package/core/bvh2/binary/2/BinaryUint32BVH.spec.js +31 -1
  5. package/core/bvh2/bvh3/bvh_query_user_data_nearest_to_point.js +5 -5
  6. package/core/bvh2/bvh3/bvh_query_user_data_nearest_to_point.spec.js +70 -0
  7. package/core/bvh2/bvh3/query/compute_tight_near_far_clipping_planes.spec.js +2 -2
  8. package/core/cache/Cache.js +6 -0
  9. package/core/collection/IteratorUtils.js +1 -0
  10. package/core/collection/queue/Deque.js +5 -2
  11. package/core/color/Color.js +22 -4
  12. package/core/color/hsluv/HSLuv.js +187 -0
  13. package/core/geom/3d/aabb/aabb3_matrix4_project.js +1 -0
  14. package/core/geom/3d/topology/simplify/simplifyTopoMesh2.js +1 -1
  15. package/core/primitives/strings/compareStrings.js +22 -0
  16. package/editor/Editor.js +2 -0
  17. package/editor/ecs/component/editors/ecs/terrain/TerrainEditor.js +12 -6
  18. package/editor/tools/v2/BlenderCameraOrientationGizmo.js +22 -11
  19. package/engine/EngineHarness.js +7 -0
  20. package/engine/asset/loaders/geometry/geometry/computeBufferAttributeHash.js +5 -6
  21. package/engine/asset/loaders/image/codec/Codec.js +9 -0
  22. package/engine/asset/loaders/image/codec/CodecWithFallback.js +52 -11
  23. package/engine/asset/loaders/image/codec/ThreadedImageDecoder.js +12 -0
  24. package/engine/asset/loaders/image/png/PNGReader.js +2 -1
  25. package/engine/asset/loaders/image/png/PNG_HEADER_BYTES.js +1 -0
  26. package/engine/computeStridedIntegerArrayHash.js +19 -0
  27. package/engine/ecs/EntityBuilder.d.ts +2 -0
  28. package/engine/ecs/gui/GUIElement.d.ts +3 -1
  29. package/engine/ecs/terrain/ecs/layers/TerrainLayers.js +5 -3
  30. package/engine/graphics/ecs/light/binding/fp/FPLightBinding.js +11 -2
  31. package/engine/graphics/ecs/mesh-v2/ShadedGeometrySystem.js +2 -8
  32. package/engine/graphics/geometry/buffered/query/GeometrySpatialQueryAccelerator.d.ts +7 -0
  33. package/engine/graphics/geometry/buffered/query/GeometrySpatialQueryAccelerator.js +17 -1
  34. package/engine/graphics/geometry/optimization/merge/merge_geometry_hierarchy.js +336 -0
  35. package/engine/graphics/geometry/optimization/merge/prototypeGeometryMerge.js +176 -0
  36. package/engine/graphics/micron/build/hierarchy/buildAbstractPatchHierarchy.js +7 -3
  37. package/engine/graphics/micron/build/hierarchy/merge_patches.js +0 -1
  38. package/engine/graphics/micron/prototypeVirtualGeometry.js +3 -3
  39. package/engine/graphics/particles/particular/engine/parameter/ParameterLookupTable.js +32 -1
  40. package/engine/graphics/render/forward_plus/LightManager.js +58 -13
  41. package/engine/graphics/render/forward_plus/debug/createScreenGrid.js +192 -9
  42. package/engine/graphics/render/forward_plus/materials/FP_SHADER_CHUNK_ACCUMULATION.js +42 -1
  43. package/engine/graphics/render/forward_plus/materials/FP_SHADER_CHUNK_PREAMBLE.js +1 -0
  44. package/engine/graphics/render/forward_plus/materials/ForwardPlusThreeMaterial.js +5 -0
  45. package/engine/graphics/render/forward_plus/materials/fp_build_fragment_shader.js +1 -4
  46. package/engine/graphics/render/forward_plus/model/Decal.js +33 -5
  47. package/engine/graphics/render/forward_plus/model/PointLight.js +1 -1
  48. package/engine/graphics/render/forward_plus/plugin/MaterialTransformer.js +4 -0
  49. package/engine/graphics/render/forward_plus/prototype/prototypeLightManager.js +480 -12
  50. package/engine/graphics/render/forward_plus/query/query_bvh_frustum_from_texture.js +49 -29
  51. package/engine/graphics/texture/atlas/AbstractTextureAtlas.js +11 -0
  52. package/engine/graphics/texture/atlas/CachingTextureAtlas.js +34 -0
  53. package/engine/graphics/texture/atlas/ReferencedTextureAtlas.js +9 -1
  54. package/engine/graphics/texture/atlas/TextureAtlas.js +29 -16
  55. package/engine/graphics/texture/atlas/TextureAtlasDebugger.js +75 -19
  56. package/engine/graphics/texture/sampler/Sampler2D.d.ts +2 -0
  57. package/engine/graphics/texture/sampler/Sampler2D.js +51 -10
  58. package/engine/sound/ecs/SoundListenerSystem.js +5 -0
  59. package/engine/sound/ecs/emitter/SoundEmitter.js +12 -1
  60. package/package.json +1 -1
  61. package/samples/terrain/from_image_2.js +8 -2
  62. package/view/tooltip/TooltipManager.js +0 -95
@@ -27,6 +27,8 @@ import { JsonAssetLoader } from "./asset/loaders/JsonAssetLoader.js";
27
27
  import { logger } from "./logging/GlobalLogger.js";
28
28
  import { ConsoleLoggerBackend } from "./logging/ConsoleLoggerBackend.js";
29
29
  import { noop } from "../core/function/Functions.js";
30
+ import SoundListenerSystem from "./sound/ecs/SoundListenerSystem.js";
31
+ import SoundListener from "./sound/ecs/SoundListener.js";
30
32
 
31
33
  /**
32
34
  *
@@ -174,6 +176,10 @@ export class EngineHarness {
174
176
  em.addSystem(new CameraSystem(engine.graphics.scene, engine.graphics))
175
177
  }
176
178
 
179
+ if (em.getSystem(SoundListenerSystem) === null) {
180
+ em.addSystem(new SoundListenerSystem(engine.sound.context));
181
+ }
182
+
177
183
  if (!ecd.isComponentTypeRegistered(Tag)) {
178
184
  ecd.registerComponentType(Tag);
179
185
  }
@@ -199,6 +205,7 @@ export class EngineHarness {
199
205
  .add(transform)
200
206
  .add(cameraController)
201
207
  .add(camera)
208
+ .add(new SoundListener())
202
209
  .add(Tag.fromJSON(["Camera"]))
203
210
  .build(ecd);
204
211
 
@@ -1,3 +1,5 @@
1
+ import { computeStridedIntegerArrayHash } from "../../../../computeStridedIntegerArrayHash.js";
2
+
1
3
  /**
2
4
  *
3
5
  * @param {THREE.BufferAttribute} attribute
@@ -15,14 +17,11 @@ export function computeBufferAttributeHash(attribute) {
15
17
  // compute stride so that we don't have to iterate over the entire buffer, instead picking at most X(509) values to consider
16
18
  const stride = Math.max(1, Math.ceil(index_data_size / 509));
17
19
 
18
- for (let i = 0; i < index_data_size; i += stride) {
19
- const value = index_data[i];
20
-
21
- result = ((result * 31) + value) | 0;
22
-
23
- }
20
+ result ^= computeStridedIntegerArrayHash(index_data, 0, index_data_size, stride);
24
21
 
25
22
  }
26
23
 
27
24
  return result;
28
25
  }
26
+
27
+
@@ -7,4 +7,13 @@ export class Codec {
7
7
  async decode(data) {
8
8
  throw new Error('Unsupported Operation');
9
9
  }
10
+
11
+ /**
12
+ * Whether codec is able to decode this data, typically checks headers in the data
13
+ * @param {Uint8Array} data
14
+ * @return {Promise<boolean>} true if able, false otherwise
15
+ */
16
+ async test(data) {
17
+ return true;
18
+ }
10
19
  }
@@ -1,28 +1,69 @@
1
1
  import { Codec } from "./Codec.js";
2
- import { logger } from "../../../../logging/GlobalLogger.js";
3
2
 
4
3
  export class CodecWithFallback extends Codec {
5
4
  /**
6
5
  *
7
- * @param {Codec} main
8
- * @param {Codec} fallback
6
+ * @param {Codec} codecs
9
7
  */
10
- constructor(main, fallback) {
8
+ constructor(...codecs) {
11
9
  super();
12
10
 
13
- this.main = main;
14
- this.fallback = fallback;
11
+ this.children = codecs.slice();
12
+
13
+ if (this.children.length < 1) {
14
+ throw new Error('At least one codec must be provided');
15
+ }
16
+ }
17
+
18
+ async test(data) {
19
+ const codecs = this.children;
20
+ const codec_count = codecs.length;
21
+
22
+ for (let i = 0; i < codec_count; i++) {
23
+ const codec = codecs[i];
24
+
25
+ if (codec.test(data)) {
26
+ return true;
27
+ }
28
+ }
29
+
30
+ return false;
15
31
  }
16
32
 
17
33
  async decode(data) {
18
34
 
19
- try {
20
- return await this.main.decode(data);
21
- } catch (e) {
22
- logger.warn(`Main codec failed, will attempt fallback. Error: ${e}`);
35
+ const codecs = this.children;
36
+ const codec_count = codecs.length;
23
37
 
24
- return this.fallback.decode(data);
38
+ const errors = [];
39
+
40
+ let result;
41
+
42
+ for (let i = 0; i < codec_count; i++) {
43
+ const codec = codecs[i];
44
+
45
+ if (!codec.test(data)) {
46
+ errors.push({
47
+ index: i, error: "Codec doesn't support this data"
48
+ });
49
+ continue;
50
+ }
51
+
52
+ try {
53
+ result = await codec.decode(data);
54
+ } catch (e) {
55
+
56
+ errors.push({
57
+ index: i, error: e
58
+ });
59
+
60
+ continue;
61
+ }
62
+
63
+ return result;
25
64
  }
26
65
 
66
+ throw new Error(`All codecs failed, errors: ${errors.map(a => JSON.stringify(a)).join('\n')}`);
67
+
27
68
  }
28
69
  }
@@ -1,6 +1,7 @@
1
1
  import { Codec } from "./Codec.js";
2
2
  import WorkerBuilder from "../../../../../core/process/worker/WorkerBuilder.js";
3
3
  import { OnDemandWorkerManager } from "../../../../../core/process/worker/OnDemandWorkerManager.js";
4
+ import { PNG_HEADER_BYTES } from "../png/PNG_HEADER_BYTES.js";
4
5
 
5
6
  export class ThreadedImageDecoder extends Codec {
6
7
  constructor() {
@@ -23,6 +24,17 @@ export class ThreadedImageDecoder extends Codec {
23
24
  this.worker.setTimeout(200);
24
25
  }
25
26
 
27
+ async test(data) {
28
+
29
+ for (let i = 0; i < PNG_HEADER_BYTES.length; i++) {
30
+ if (data[i] !== PNG_HEADER_BYTES[i]) {
31
+ return false;
32
+ }
33
+ }
34
+
35
+ return true;
36
+ }
37
+
26
38
  async decode(data) {
27
39
  const result = await this.worker.request('decode', [data.buffer]);
28
40
  return result;
@@ -6,6 +6,7 @@ import zlib from 'pako';
6
6
  import { PNG } from './PNG.js';
7
7
  import { BinaryBuffer } from "../../../../../core/binary/BinaryBuffer.js";
8
8
  import { isArrayEqualStrict } from "../../../../../core/collection/array/isArrayEqualStrict.js";
9
+ import { PNG_HEADER_BYTES } from "./PNG_HEADER_BYTES.js";
9
10
 
10
11
  function readUInt32(buffer, offset) {
11
12
  return (buffer[offset] << 24) +
@@ -62,7 +63,7 @@ PNGReader.prototype.decodeHeader = function () {
62
63
  buffer.readBytes(header, 0, 8)
63
64
 
64
65
  // see https://www.w3.org/TR/PNG-Structure.html
65
- if (!isArrayEqualStrict(header, [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A])) {
66
+ if (!isArrayEqualStrict(header, PNG_HEADER_BYTES)) {
66
67
  throw new Error('invalid PNGReader file (bad signature)');
67
68
  }
68
69
 
@@ -0,0 +1 @@
1
+ export const PNG_HEADER_BYTES = [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A];
@@ -0,0 +1,19 @@
1
+ /**
2
+ *
3
+ * @param {number[]|Uint32Array|Uint16Array|Uint8Array} array
4
+ * @param {number} offset
5
+ * @param {number} length
6
+ * @param {number} stride
7
+ * @return {number}
8
+ */
9
+ export function computeStridedIntegerArrayHash(array, offset, length, stride) {
10
+ let result = length;
11
+
12
+ for (let i = offset; i < length; i += stride) {
13
+ const value = array[i];
14
+
15
+ result = ((result * 31) + value) | 0;
16
+ }
17
+
18
+ return result;
19
+ }
@@ -28,4 +28,6 @@ export default class EntityBuilder {
28
28
  addEventListener(eventName: string, listener: (data?: any) => void, context?: any): EntityBuilder
29
29
 
30
30
  removeEventListener(eventName: string, listener: (data?: any) => void, context?: any): EntityBuilder
31
+
32
+ static readFromDataset(entity: number, dataset: EntityComponentDataset): EntityBuilder
31
33
  }
@@ -1,3 +1,5 @@
1
- export default class GUIElement {
1
+ import View from "../../../view/View";
2
2
 
3
+ export default class GUIElement {
4
+ static fromView(view: View): GUIElement
3
5
  }
@@ -390,16 +390,18 @@ export class TerrainLayers {
390
390
 
391
391
  const resolution = this.resolution;
392
392
 
393
- const singleLayerByteSize = resolution.x * resolution.y * 3;
393
+ const single_layer_byte_size = resolution.x * resolution.y * 3;
394
394
 
395
- if (arrayData.length < singleLayerByteSize * index) {
395
+ if (arrayData.length < single_layer_byte_size * index) {
396
396
  throw new Error('Texture data is too small, make sure you rebuild texture data before attempting to write');
397
397
  }
398
398
 
399
- const address = singleLayerByteSize * index;
399
+ const address = single_layer_byte_size * index;
400
400
 
401
401
  const sampler = this.__obtain_layer_data_at_resolution(layer, resolution);
402
402
 
403
+ assert.equal(sampler.data.length, single_layer_byte_size, 'Incorrect layer data size');
404
+
403
405
  arrayData.set(sampler.data, address);
404
406
 
405
407
  //mark texture for update
@@ -8,12 +8,21 @@ export class FPLightBinding extends LightBinding {
8
8
  this.__light = null;
9
9
  }
10
10
 
11
+ /**
12
+ *
13
+ * @return {ForwardPlusRenderingPlugin}
14
+ * @private
15
+ */
16
+ get __plugin(){
17
+ return ctx.system.__plugin.getValue();
18
+ }
19
+
11
20
  link(ctx) {
12
21
  /**
13
22
  *
14
23
  * @type {ForwardPlusRenderingPlugin}
15
24
  */
16
- const fp = ctx.system.__plugin.getValue();
25
+ const fp = this.__plugin;
17
26
 
18
27
  const lightManager = fp.getLightManager();
19
28
 
@@ -43,7 +52,7 @@ export class FPLightBinding extends LightBinding {
43
52
  *
44
53
  * @type {ForwardPlusRenderingPlugin}
45
54
  */
46
- const fp = ctx.system.__plugin.getValue();
55
+ const fp = this.__plugin;
47
56
 
48
57
  const lightManager = fp.getLightManager();
49
58
 
@@ -28,12 +28,6 @@ import {
28
28
  */
29
29
  const scratch_point = new SurfacePoint3();
30
30
 
31
- /**
32
- *
33
- * @type {Float32Array|mat4}
34
- */
35
- const scratch_m4 = new Float32Array(16);
36
-
37
31
  /**
38
32
  * @readonly
39
33
  * @type {Float32Array}
@@ -256,12 +250,12 @@ export class ShadedGeometrySystem extends System {
256
250
  link(sg, t, entity) {
257
251
  sg.__c_transform = t;
258
252
 
253
+ sg.update_bounds();
254
+
259
255
  t.position.onChanged.add(sg.updateTransform, sg);
260
256
  t.rotation.onChanged.add(sg.updateTransform, sg);
261
257
  t.scale.onChanged.add(sg.updateTransform, sg);
262
258
 
263
- sg.update_bounds();
264
-
265
259
  // remember entity for lookups
266
260
  sg.__entity = entity;
267
261
 
@@ -15,4 +15,11 @@ export class GeometrySpatialQueryAccelerator {
15
15
  origin_x: number, origin_y: number, origin_z: number,
16
16
  direction_x: number, direction_y: number, direction_z: number
17
17
  ): boolean
18
+
19
+ /**
20
+ * In bytes
21
+ */
22
+ cache_size: number
23
+
24
+ static INSTANCE: GeometrySpatialQueryAccelerator;
18
25
  }
@@ -33,6 +33,22 @@ export class GeometrySpatialQueryAccelerator {
33
33
  this.__visitor_raycast = new RaycastNearestHitComputingVisitor();
34
34
  }
35
35
 
36
+ /**
37
+ *
38
+ * @param {number} value in bytes
39
+ */
40
+ set cache_size(value) {
41
+ this.cache.setMaxWeight(value);
42
+ }
43
+
44
+ /**
45
+ *
46
+ * @return {number} in bytes
47
+ */
48
+ get cache_size() {
49
+ return this.cache.maxWeight;
50
+ }
51
+
36
52
  /**
37
53
  *
38
54
  * @param {THREE.BufferGeometry} geometry
@@ -184,7 +200,7 @@ export class GeometrySpatialQueryAccelerator {
184
200
  const de_interleaved_position_attribute = deinterleaveBufferAttribute(position_attribute);
185
201
  const vertices = de_interleaved_position_attribute.array;
186
202
 
187
- if(index_attribute === undefined || index_attribute === null){
203
+ if (index_attribute === undefined || index_attribute === null) {
188
204
  return buildUnsortedUnindexed(vertices);
189
205
  }
190
206
 
@@ -0,0 +1,336 @@
1
+ import { StaticMaterialCache } from "../../../../asset/loaders/material/StaticMaterialCache.js";
2
+ import { HashMap } from "../../../../../core/collection/HashMap.js";
3
+ import { computeGeometryEquality } from "../../../../asset/loaders/geometry/geometry/computeGeometryEquality.js";
4
+ import { computeGeometryHash } from "../../../../asset/loaders/geometry/geometry/computeGeometryHash.js";
5
+ import { array_copy } from "../../../../../core/collection/array/copyArray.js";
6
+ import { mat4 } from "gl-matrix";
7
+ import { Group, Matrix4, Mesh } from "three";
8
+ import { mergeBufferGeometries } from "three/examples/jsm/utils/BufferGeometryUtils.js";
9
+ import { groupArrayBy } from "../../../../../core/collection/ArrayUtils.js";
10
+ import { collectIteratorValueToArray } from "../../../../../core/collection/IteratorUtils.js";
11
+
12
+ class GeometryContext {
13
+ constructor() {
14
+ this.matrix = new Float32Array(16);
15
+ this.material = null;
16
+ this.geometry = null;
17
+ }
18
+ }
19
+
20
+ const DEFAULT_INSTANCING_MERGE_POLYCOUNT = 1024;
21
+
22
+ /**
23
+ *
24
+ * @param {THREE.Object3D} object
25
+ * @param {THREE.Object3D} ancestor_root
26
+ * @param {Float32Array} result
27
+ */
28
+ function compute_transform_matrix(object, ancestor_root, result) {
29
+ /**
30
+ *
31
+ * @type {THREE.Object3D[]}
32
+ */
33
+ const path = [];
34
+
35
+ let node = object;
36
+
37
+ while (node !== ancestor_root) {
38
+
39
+ if (path.indexOf(node) !== -1) {
40
+ throw new Error('Circular dependency');
41
+ }
42
+
43
+ path.push(node);
44
+
45
+ node = node.parent;
46
+
47
+ if (node === null) {
48
+ throw new Error('Ancestor not found');
49
+ }
50
+
51
+ }
52
+
53
+ //
54
+ path.reverse();
55
+
56
+ mat4.identity(result);
57
+
58
+ for (let i = 0; i < path.length; i++) {
59
+ const el = path[i];
60
+
61
+ mat4.multiply(result, result, el.matrix.elements);
62
+ }
63
+
64
+ }
65
+
66
+
67
+ /**
68
+ *
69
+ * @param {THREE.BufferGeometry} a
70
+ * @param {THREE.BufferGeometry} b
71
+ * @returns {boolean}
72
+ */
73
+ function equal_attribute_structure(a, b) {
74
+ if (a === b) {
75
+ return true;
76
+ }
77
+
78
+ const a_attribute_names = Object.keys(a.attributes);
79
+ const b_attribute_names = Object.keys(b.attributes);
80
+
81
+
82
+ const n = a_attribute_names.length;
83
+ if (n !== b_attribute_names.length) {
84
+ return false;
85
+ }
86
+
87
+ for (let i = 0; i < n; i++) {
88
+ const a_key = a_attribute_names[i];
89
+
90
+ const b_attribute_index = b_attribute_names.indexOf(a_key);
91
+
92
+ if (b_attribute_index === -1) {
93
+ return -1;
94
+ }
95
+
96
+ const a_attribute = a.attributes[a_key];
97
+ const b_attribute = b.attributes[a_key];
98
+
99
+ if (a_attribute.itemSize !== b_attribute.itemSize) {
100
+ return false;
101
+ }
102
+
103
+ if (a_attribute.normalized !== b_attribute.normalized) {
104
+ return false;
105
+ }
106
+
107
+ if (a_attribute.array.constructor !== b_attribute.array.constructor) {
108
+ return false;
109
+ }
110
+ }
111
+
112
+ return true;
113
+ }
114
+
115
+ /**
116
+ *
117
+ * @param {GeometryContext[]} contexts
118
+ * @returns {GeometryContext}
119
+ */
120
+ function merge_contexts(contexts) {
121
+
122
+ const geometries = contexts.map(ctx => {
123
+ const g = ctx.geometry.clone();
124
+
125
+ const matrix4 = new Matrix4();
126
+ matrix4.elements = ctx.matrix;
127
+
128
+ g.applyMatrix4(matrix4);
129
+
130
+ return g;
131
+ });
132
+
133
+ const merged = mergeBufferGeometries(geometries);
134
+
135
+ const merged_context = new GeometryContext();
136
+
137
+ mat4.identity(merged_context.matrix);
138
+ merged_context.geometry = merged;
139
+ merged_context.material = contexts[0].material;
140
+
141
+ return merged_context;
142
+ }
143
+
144
+ /**
145
+ *
146
+ * @param {GeometryContext[]} input
147
+ * @param {GeometryContext[]} output
148
+ * @param {number} output_offset
149
+ * @returns {number}
150
+ */
151
+ function merge_by_material(input, output, output_offset) {
152
+ let result_offset = output_offset;
153
+
154
+ const undecided = input.slice();
155
+
156
+ while (undecided.length > 0) {
157
+ for (let i = 0; i < undecided.length; i++) {
158
+ const ref_0 = undecided[i];
159
+
160
+ const set_indices = [i];
161
+ const set_contexts = [ref_0];
162
+
163
+ for (let j = i + 1; j < undecided.length; j++) {
164
+ const ref_1 = undecided[j];
165
+
166
+ const geo_0 = ref_0.geometry;
167
+ const geo_1 = ref_1.geometry;
168
+
169
+ if (geo_0 === geo_1) {
170
+ const tri_count = (geo_0.getIndex().count) / 3;
171
+ if (tri_count > DEFAULT_INSTANCING_MERGE_POLYCOUNT) {
172
+ // instancing, and instances are large enough for instancing to be efficient
173
+ continue;
174
+ }
175
+ }
176
+
177
+ if (!equal_attribute_structure(geo_0, geo_1)) {
178
+ continue;
179
+ }
180
+
181
+ if (ref_0.material !== ref_1.material) {
182
+ continue;
183
+ }
184
+
185
+ set_indices.push(j);
186
+ set_contexts.push(ref_1);
187
+ }
188
+
189
+ let out = ref_0;
190
+
191
+ for (let j = set_indices.length - 1; j >= 0; j--) {
192
+ undecided.splice(set_indices[j], 1);
193
+ }
194
+
195
+ if (set_indices.length > 1) {
196
+
197
+
198
+ out = merge_contexts(set_contexts);
199
+
200
+ i--;
201
+ }
202
+
203
+ output[result_offset++] = out;
204
+ }
205
+ }
206
+
207
+ return result_offset - output_offset;
208
+
209
+ }
210
+
211
+ /**
212
+ *
213
+ * @param {GeometryContext} ctx
214
+ * @returns {Mesh}
215
+ */
216
+ function context_to_mesh(ctx) {
217
+ const mesh = new Mesh(ctx.geometry, ctx.material);
218
+
219
+ matrix_array_to_three_object(ctx.matrix, mesh);
220
+
221
+ return mesh;
222
+ }
223
+
224
+ /**
225
+ *
226
+ * @param {number[]} matrix
227
+ * @param {THREE.Object3D} destination
228
+ */
229
+ function matrix_array_to_three_object(matrix, destination) {
230
+
231
+ array_copy(matrix, 0, destination.matrix.elements, 0, 16);
232
+
233
+ destination.matrix.decompose(destination.position, destination.quaternion, destination.scale);
234
+
235
+ destination.rotation.setFromQuaternion(destination.quaternion);
236
+ }
237
+
238
+ /**
239
+ *
240
+ * @param {THREE.Object3D} source
241
+ * @param {THREE.Object3D} destination
242
+ */
243
+ function apply_transform_to_another_three_object(source, destination) {
244
+ const m4 = new Matrix4();
245
+
246
+ m4.multiplyMatrices(source.matrix, destination.matrix);
247
+
248
+ matrix_array_to_three_object(m4.elements, destination);
249
+ }
250
+
251
+ /**
252
+ *
253
+ * @param {THREE.Object3D} input
254
+ * @param context
255
+ * @returns {THREE.Object3D}
256
+ */
257
+ export function merge_geometry_hierarchy(input, context = new Context()) {
258
+ // material cache
259
+ const material_cache = new StaticMaterialCache();
260
+
261
+
262
+ const geometry_cache = new HashMap({
263
+ keyEqualityFunction: computeGeometryEquality,
264
+ keyHashFunction: computeGeometryHash
265
+ });
266
+
267
+
268
+ /**
269
+ *
270
+ * @param {THREE.BufferGeometry} geo
271
+ * @return {THREE.BufferGeometry}
272
+ */
273
+ function dedupe_geometry(geo) {
274
+ const existing = geometry_cache.get(geo);
275
+
276
+ if (existing !== undefined) {
277
+ return existing;
278
+ }
279
+
280
+ geometry_cache.set(geo, geo);
281
+
282
+ return geo;
283
+ }
284
+
285
+ let contexts = [];
286
+
287
+ // collect geometries
288
+ input.traverse(o => {
289
+ if (o.isMesh && !!(o.geometry)) {
290
+
291
+ const ctx = new GeometryContext();
292
+
293
+ ctx.material = material_cache.acquire(o.material);
294
+ ctx.geometry = dedupe_geometry(o.geometry);
295
+
296
+ compute_transform_matrix(o, input, ctx.matrix);
297
+
298
+ contexts.push(ctx);
299
+ }
300
+ });
301
+
302
+ const materials = collectIteratorValueToArray([], material_cache.materialCache.data.keys());
303
+
304
+ const color_groups = groupArrayBy(materials, (m => m.color.getHex()));
305
+
306
+ console.warn(color_groups);
307
+
308
+
309
+ const contexts_merge_stage_0 = [];
310
+
311
+ merge_by_material(contexts, contexts_merge_stage_0, 0);
312
+
313
+ // convert contexts to Meshes
314
+
315
+ const meshes = contexts_merge_stage_0.map(context_to_mesh);
316
+
317
+ let result_object;
318
+
319
+ if (meshes.length === 1) {
320
+ // only one mesh is present, return that
321
+ result_object = meshes[0];
322
+ } else {
323
+
324
+ result_object = new Group();
325
+
326
+ for (let i = 0; i < meshes.length; i++) {
327
+ const m = meshes[i];
328
+ result_object.add(m);
329
+ }
330
+ }
331
+
332
+ // copy transform from original
333
+ apply_transform_to_another_three_object(input, result_object);
334
+
335
+ return result_object;
336
+ }