@woosh/meep-engine 2.34.49 → 2.35.2

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 (57) hide show
  1. package/core/bvh2/BinaryNode.js +19 -35
  2. package/core/bvh2/aabb3/aabb3_intersects_frustum_array.js +5 -5
  3. package/core/bvh2/aabb3/aabb3_intersects_frustum_degree.js +6 -6
  4. package/core/bvh2/aabb3_combined_surface_area.js +21 -0
  5. package/core/bvh2/traversal/queryBinaryNode_CollectData.js +1 -2
  6. package/core/bvh2/traversal/queryBinaryNode_FrustumIntersections.js +42 -17
  7. package/core/cache/Cache.d.ts +2 -0
  8. package/core/cache/Cache.js +27 -23
  9. package/core/collection/HashMap.js +1 -1
  10. package/core/geom/3d/frustum/frustum_from_projection_matrix_array.js +2 -0
  11. package/core/math/max2.js +2 -6
  12. package/core/math/min2.js +2 -6
  13. package/engine/asset/loaders/geometry/geometry/computeGeometryEquality.js +1 -1
  14. package/engine/asset/loaders/geometry/geometry/computeGeometryHash.js +9 -1
  15. package/engine/ecs/EntityComponentDataset.js +7 -2
  16. package/engine/ecs/foliage/ViewState.js +13 -3
  17. package/engine/ecs/parent/EntityNode.js +23 -11
  18. package/engine/ecs/transform/Transform.js +15 -13
  19. package/engine/graphics/ecs/highlight/system/ShadedGeometryHighlightSystem.js +13 -4
  20. package/engine/graphics/ecs/mesh-v2/ShadedGeometry.js +46 -108
  21. package/engine/graphics/ecs/mesh-v2/ShadedGeometrySystem.js +72 -10
  22. package/engine/graphics/ecs/mesh-v2/build_three_object.js +39 -0
  23. package/engine/graphics/ecs/mesh-v2/render/AbstractRenderAdapter.js +67 -0
  24. package/engine/graphics/ecs/mesh-v2/render/GenericRendererAdapter.js +48 -0
  25. package/engine/graphics/ecs/mesh-v2/render/InstancedRendererAdapter.js +158 -0
  26. package/engine/graphics/ecs/mesh-v2/render/ShadedGeometryRendererContext.js +98 -0
  27. package/engine/graphics/ecs/mesh-v2/sample/load_gltf.js +5 -0
  28. package/engine/graphics/ecs/mesh-v2/sample/prototypeShadedGeometry.js +37 -10
  29. package/engine/graphics/ecs/mesh-v2/three_object_to_entity_composition.js +8 -3
  30. package/engine/graphics/geometry/instancing/InstancedMeshGroup.js +29 -82
  31. package/engine/graphics/geometry/instancing/rewriteMaterial.js +9 -45
  32. package/engine/graphics/material/optimization/BUFFER_GEOMETRY_UVS.js +10 -0
  33. package/engine/graphics/material/optimization/MaterialDescriptor.js +46 -0
  34. package/engine/graphics/material/optimization/MaterialOptimizationContext.js +470 -201
  35. package/engine/graphics/material/optimization/UvContext.js +18 -0
  36. package/engine/graphics/material/optimization/is_compliant_mesh.js +71 -0
  37. package/engine/graphics/material/optimization/prepare_atlas_texture.js +23 -0
  38. package/engine/graphics/material/optimization/prototypeMaterialOptimizer.js +127 -0
  39. package/engine/graphics/micron/format/MicronGeometryPatch.js +1 -1
  40. package/engine/graphics/micron/format/VirtualGeometry.js +47 -11
  41. package/engine/graphics/micron/format/serialization/MicronGeometryBinarySerializationAdapter.js +16 -0
  42. package/engine/graphics/micron/prototypeVirtualGeometry.js +3 -2
  43. package/engine/graphics/micron/render/VirtualGeometryBuilder.js +5 -7
  44. package/engine/graphics/micron/render/instanced/AttributeDataTexture.js +1 -45
  45. package/engine/graphics/micron/render/instanced/ThreeInstancedAdapter.js +18 -7
  46. package/engine/graphics/micron/render/refinement/RefinementSpec.js +0 -1
  47. package/engine/graphics/micron/render/refinement/get_geometry_patch_cut.js +17 -24
  48. package/engine/graphics/micron/render/v1/MaterialContext.js +3 -2
  49. package/engine/graphics/micron/render/v1/VGThreeRenderer.js +20 -4
  50. package/engine/graphics/render/utils/ThreeBypassRenderer.js +3 -2
  51. package/engine/graphics/render/visibility/IncrementalDeltaSet.js +1 -0
  52. package/engine/graphics/texture/normalized_internal_format.js +48 -0
  53. package/engine/graphics/texture/sampler/Sampler2D.js +6 -0
  54. package/engine/graphics/texture/sampler/sampler2d_copy_channel_data.js +25 -0
  55. package/engine/graphics/texture/sampler/scaleSampler2D.js +3 -0
  56. package/engine/graphics/texture/sampler/util/drawSamplerHTML.js +14 -1
  57. package/package.json +1 -1
@@ -1,9 +1,6 @@
1
1
  import { traverseThreeObject } from "../../ecs/highlight/renderer/traverseThreeObject.js";
2
2
  import { TextureAttachmentsByMaterialType } from "../../../asset/loaders/material/TextureAttachmensByMaterialType.js";
3
3
  import { HashMap } from "../../../../core/collection/HashMap.js";
4
- import Vector2 from "../../../../core/geom/Vector2.js";
5
- import { traverseMaterialTextures } from "../../../asset/loaders/material/traverseMaterialTextures.js";
6
- import { max2 } from "../../../../core/math/max2.js";
7
4
  import AABB2 from "../../../../core/geom/AABB2.js";
8
5
  import { MaxRectanglesPacker } from "../../../../core/geom/packing/MaxRectangles.js";
9
6
  import { Sampler2D } from "../../texture/sampler/Sampler2D.js";
@@ -12,192 +9,358 @@ import { computeMaterialHash } from "../../../asset/loaders/material/computeMate
12
9
  import { computeMaterialEquality } from "../../../asset/loaders/material/computeMaterialEquality.js";
13
10
  import { collectIteratorValueToArray } from "../../../../core/collection/IteratorUtils.js";
14
11
  import {
12
+ AdditiveBlending,
15
13
  ClampToEdgeWrapping,
16
14
  DataTexture,
17
- LinearEncoding,
18
- LinearFilter,
19
- LinearMipmapLinearFilter,
20
15
  Matrix3,
21
- sRGBEncoding,
22
- UnsignedByteType
16
+ MeshStandardMaterial,
17
+ MultiplyBlending,
18
+ NormalBlending
23
19
  } from "three";
24
20
  import { convertTexture2Sampler2D } from "../../texture/sampler/convertTexture2Sampler2D.js";
21
+ import { BUFFER_GEOMETRY_UVS } from "./BUFFER_GEOMETRY_UVS.js";
22
+ import { is_compliant_mesh } from "./is_compliant_mesh.js";
23
+ import { MaterialDescriptor } from "./MaterialDescriptor.js";
24
+ import { computeThreeTextureTypeFromDataType } from "../../render/forward_plus/data/computeThreeTextureTypeFromDataType.js";
25
+ import { typedArrayToDataType } from "../../../../core/collection/array/typedArrayToDataType.js";
26
+ import { DataType } from "../../../../core/collection/table/DataType.js";
27
+ import { compute_typed_array_constructor_from_data_type } from "../../../../core/collection/table/DataType2TypedArrayConstructorMapping.js";
28
+ import { copy_Sampler2D_channel_data } from "../../texture/sampler/copy_Sampler2D_channel_data.js";
29
+ import { channelCountToThreeTextureFormat } from "../../texture/channelCountToThreeTextureFormat.js";
30
+ import { prepare_atlas_texture } from "./prepare_atlas_texture.js";
31
+ import { sampler2d_copy_channel_data } from "../../texture/sampler/sampler2d_copy_channel_data.js";
32
+ import { normalized_internal_format } from "../../texture/normalized_internal_format.js";
33
+
34
+ const ATLAS_PATCH_BORDER = 0;
25
35
 
26
- const ATLAS_PATCH_BORDER = 4;
27
36
 
28
- const BUFFER_GEOMETRY_UVS = [
29
- {
30
- name: 'uv',
31
- index: 0,
32
- },
33
- {
34
- name: 'uv2',
35
- index: 1
36
- }
37
- ];
37
+ /**
38
+ *
39
+ * @param {THREE.Material} a
40
+ * @param {THREE.Material} b
41
+ * @returns {boolean}
42
+ */
43
+ function materialsSimilarForCharting(a, b) {
44
+
45
+ return a.visible === b.visible
46
+ && a.transparent === b.transparent
47
+ && a.blending === b.blending
48
+ && a.alphaTest === b.alphaTest
49
+ && a.depthTest === b.depthTest
50
+ && a.depthWrite === b.depthWrite
51
+ ;
52
+ }
38
53
 
39
- class TextureReference {
40
- constructor() {
41
- /**
42
- *
43
- * @type {TextureAttachment|null}
44
- */
45
- this.attachment = null;
46
- /**
47
- *
48
- * @type {THREE.Material|null}
49
- */
50
- this.material = null;
51
- /**
52
- *
53
- * @type {THREE.Texture|null}
54
- */
55
- this.source_texture = null;
56
- }
54
+ /**
55
+ *
56
+ * @param {THREE.Material} a
57
+ * @returns {number}
58
+ */
59
+ function materialChartingHash(a) {
60
+ const flags = (a.visible ? 1 : 0)
61
+ | ((a.transparent ? 1 : 0) << 1)
62
+ | ((a.depthTest ? 1 : 0) << 2)
63
+ | ((a.depthWrite ? 1 : 0) << 3)
64
+ | (([NormalBlending, AdditiveBlending, MultiplyBlending].indexOf(a.blending) & 0b11) << 4)
65
+ | ((Math.floor(a.alphaTest * 15)) << 6) // 4 bit
66
+ ;
67
+ return flags;
57
68
  }
58
69
 
59
70
  /**
60
71
  *
61
- * @param {THREE.Mesh} mesh
62
- * @returns {boolean}
72
+ * @param {THREE.DataTexture} metalness
73
+ * @param {THREE.DataTexture} roughness
74
+ * @returns {THREE.DataTexture}
63
75
  */
64
- function is_compliant_mesh(mesh) {
65
- const material = mesh.material;
76
+ function merge_textures_metalness_rougness({
77
+ metalness,
78
+ roughness
79
+ }) {
80
+ const ref = metalness !== undefined ? metalness : roughness;
66
81
 
67
- const attachments = TextureAttachmentsByMaterialType[material.type];
82
+ const pixel_count = ref.image.width * ref.image.height;
83
+ const result_data = new Uint8Array(pixel_count * 4);
68
84
 
69
- const uv_scales = [1, 1, 1, 1];
85
+ const result = new DataTexture(result_data, ref.image.width, ref.image.height);
70
86
 
71
- for (let i = 0; i < attachments.length; i++) {
72
- const attachment = attachments[i];
87
+ result.encoding = ref.encoding;
73
88
 
74
- const texture = attachment.read(material);
75
- if (texture === undefined || texture === null) {
76
- continue;
89
+ if (metalness !== undefined) {
90
+ for (let i = 0; i < pixel_count; i++) {
91
+ result_data[i * 4 + 2] = metalness.image.data[i];
77
92
  }
78
-
79
- const uv_index = attachment.uv_index;
80
-
81
- uv_scales[uv_index * 2] = max2(uv_scales[uv_index * 2], texture.repeat.x)
82
- uv_scales[uv_index * 2 + 1] = max2(uv_scales[uv_index * 2 + 1], texture.repeat.y)
83
93
  }
84
94
 
85
- const geometry = mesh.geometry;
95
+ if (roughness !== undefined) {
96
+ for (let i = 0; i < pixel_count; i++) {
97
+ result_data[i * 4 + 1] = roughness.image.data[i];
98
+ }
99
+ }
86
100
 
87
- for (let i = 0; i < BUFFER_GEOMETRY_UVS.length; i++) {
88
- const uv_metadata = BUFFER_GEOMETRY_UVS[i];
101
+ result.needsUpdate = true;
89
102
 
90
- /**
91
- *
92
- * @type {THREE.BufferAttribute }
93
- */
94
- const attribute = geometry.attributes[uv_metadata.name];
103
+ return result;
104
+ }
95
105
 
96
- if (attribute === undefined) {
97
- continue;
106
+ /**
107
+ *
108
+ * @param {THREE.Material[]} source
109
+ * @param {Map<string, THREE.Texture>} textures
110
+ * @returns {Map<THREE.Material, THREE.Material>}
111
+ */
112
+ function merge_materials(source, textures) {
113
+ // collect unique materials
114
+ const unique_materials = new HashMap({
115
+ keyEqualityFunction: materialsSimilarForCharting,
116
+ keyHashFunction: materialChartingHash
117
+ });
118
+
119
+ for (let i = 0; i < source.length; i++) {
120
+ const material = source[i];
121
+
122
+ let bucket = unique_materials.get(material);
123
+ if (bucket === undefined) {
124
+ bucket = [];
125
+ unique_materials.set(material, bucket);
98
126
  }
99
127
 
100
- const attribute_data = attribute.array;
101
- const n = attribute_data.length;
128
+ bucket.push(material);
129
+ }
102
130
 
103
- const uv_index = uv_metadata.index;
104
- const uv_index_2 = uv_index * 2;
131
+ // build new materials
132
+ const source_unique_keys = [];
133
+ collectIteratorValueToArray(source_unique_keys, unique_materials.keys());
105
134
 
106
- for (let j = 0; j < n; j += 2) {
107
- const x = attribute_data[j];
108
- const y = attribute_data[j + 1];
135
+ /**
136
+ *
137
+ * @type {Map<THREE.Material, THREE.Material>}
138
+ */
139
+ const result = new Map();
109
140
 
110
- const scaled_u = x * uv_scales[uv_index_2];
111
- const scaled_v = y * uv_scales[uv_index_2 + 1];
141
+ for (let i = 0; i < source_unique_keys.length; i++) {
142
+ const source_material = source_unique_keys[i];
112
143
 
113
- if (scaled_u > 1 || scaled_u < 0 || scaled_v > 1 || scaled_v < 0) {
114
- // repeating/negative UV value
115
- return false;
116
- }
144
+ const result_material = new MeshStandardMaterial();
117
145
 
118
- }
146
+ result_material.name = `Generated charted material ${i}`;
119
147
 
120
- }
148
+ result_material.visible = source_material.visible;
149
+ result_material.transparent = source_material.transparent;
150
+ result_material.blending = source_material.blending;
151
+ result_material.alphaTest = source_material.alphaTest;
152
+ result_material.depthTest = source_material.depthTest;
153
+ result_material.depthWrite = source_material.depthWrite;
121
154
 
122
- return true;
123
- }
155
+ // write atlased texture
156
+ const texture_attachments = TextureAttachmentsByMaterialType[result_material.type];
124
157
 
125
- class UvContext {
126
- constructor() {
158
+ for (let j = 0; j < texture_attachments.length; j++) {
159
+ const textureAttachment = texture_attachments[j];
127
160
 
128
- this.resolution = new Vector2(1, 1);
161
+ const existing_texture = textures.get(textureAttachment.name);
129
162
 
130
- /**
131
- *
132
- * @type {AABB2}
133
- */
134
- this.atlas_patch = new AABB2();
163
+ if (existing_texture !== undefined) {
164
+ textureAttachment.write(result_material, existing_texture);
165
+ }
166
+ }
167
+
168
+ // write result mapping
169
+ const destination_materials = unique_materials.get(source_material);
170
+ for (let j = 0; j < destination_materials.length; j++) {
171
+ result.set(destination_materials[j], result_material);
172
+ }
135
173
  }
174
+
175
+ return result;
136
176
  }
137
177
 
138
178
  /**
139
179
  *
140
- * @param {Texture} texture
141
- * @param {string} attachment_name
180
+ * @param {Sampler2D} destination
181
+ * @param {Sampler2D} source
182
+ * @param {string} texture_name
142
183
  */
143
- function prepare_atlas_texture(texture, attachment_name) {
144
-
145
- switch (attachment_name) {
146
- case 'diffuse':
147
- texture.encoding = sRGBEncoding;
184
+ function extract_texture_data(destination, source, texture_name) {
185
+ switch (texture_name) {
186
+ case 'ao':
187
+ sampler2d_copy_channel_data(source, 0, destination, 0);
188
+ break;
189
+ case 'roughness':
190
+ sampler2d_copy_channel_data(source, 1, destination, 0);
191
+ break;
192
+ case 'metalness':
193
+ sampler2d_copy_channel_data(source, 2, destination, 0);
148
194
  break;
149
195
  default:
150
- texture.encoding = LinearEncoding;
196
+ copy_Sampler2D_channel_data(source, destination);
151
197
  }
198
+ }
152
199
 
153
- texture.minFilter = LinearMipmapLinearFilter;
154
- texture.magFilter = LinearFilter;
200
+ /**
201
+ *
202
+ * @param {number[]} destination
203
+ * @param {THREE.Material|THREE.MeshStandardMaterial} source_material
204
+ * @param {string} texture_name
205
+ */
206
+ function compute_default_texture_texel_value(destination, source_material, texture_name) {
207
+ switch (texture_name) {
208
+ case 'diffuse':
209
+ destination[0] = 255;
210
+ destination[1] = 255;
211
+ destination[2] = 255;
155
212
 
156
- texture.generateMipmaps = true;
213
+ if (destination.length > 3) {
214
+ destination[3] = 255;
215
+ }
216
+ break;
217
+ case 'metalness':
218
+ for (let i = 0; i < destination.length; i++) {
219
+ destination[i] = Math.round(source_material.metalness * 255);
220
+ }
221
+ if (destination.length > 3) {
222
+ destination[3] = 255;
223
+ }
224
+ break;
225
+ case 'roughness':
226
+ for (let i = 0; i < destination.length; i++) {
227
+ destination[i] = Math.round(source_material.roughness * 255);
228
+ }
229
+ if (destination.length > 3) {
230
+ destination[3] = 255;
231
+ }
232
+ break;
233
+ case 'ao':
234
+ for (let i = 0; i < destination.length; i++) {
235
+ destination[i] = 255;
236
+ }
237
+ break;
238
+ case 'normal':
239
+ destination[0] = 127;
240
+ destination[1] = 127;
241
+ destination[2] = 255;
242
+ break;
243
+ }
157
244
  }
158
245
 
246
+ /**
247
+ *
248
+ * @param {Sampler2D} sampler
249
+ * @param {THREE.MeshStandardMaterial} material
250
+ * @param {string} texture_name
251
+ */
252
+ function apply_material_shading_to_texture(sampler, material, texture_name) {
253
+ switch (texture_name) {
254
+ case 'diffuse': {
255
+ const r = material.color.r;
256
+ const g = material.color.g;
257
+ const b = material.color.b;
258
+ const opacity = material.opacity;
259
+
260
+ const itemSize = sampler.itemSize;
261
+
262
+ const data = sampler.data;
263
+ const data_size = data.length;
264
+
265
+ if (r < 1 || g < 1 || b < 1) {
266
+ // apply color tint
267
+ for (let i = 0; i < data_size; i += itemSize) {
268
+ data[i] *= r;
269
+ data[i + 1] *= g;
270
+ data[i + 2] *= b;
271
+ }
272
+ }
159
273
 
160
- class MaterialDescriptor {
161
- constructor() {
162
-
163
- /**
164
- *
165
- * @type {THREE.Material|null}
166
- */
167
- this.source_material = null;
274
+ if (itemSize > 3 && opacity < 1) {
275
+ // apply opacity
276
+ for (let i = 3; i < data_size; i += itemSize) {
277
+ data[i] *= opacity;
278
+ }
168
279
 
169
- /**
170
- *
171
- * @type {UvContext[]}
172
- */
173
- this.uvs = [];
280
+ }
281
+ }
282
+ break;
283
+ default:
284
+ // do nothing
174
285
  }
286
+ }
287
+
288
+ /**
289
+ *
290
+ * @param {Sampler2D} destination
291
+ * @param {THREE.Material} source_material
292
+ * @param {string} texture_name
293
+ */
294
+ function compute_default_texture_value(destination, source_material, texture_name) {
295
+ const itemSize = destination.itemSize;
175
296
 
176
- build_uvs() {
297
+ const texel = new Array(itemSize);
177
298
 
178
- traverseMaterialTextures(this.source_material, (t, attachment) => {
179
- const uv_index = attachment.uv_index;
299
+ compute_default_texture_texel_value(texel, source_material, texture_name);
180
300
 
181
- let uv_context = this.uvs[uv_index];
301
+ const width = destination.width;
302
+ const height = destination.height;
182
303
 
183
- if (uv_context === undefined) {
184
- uv_context = new UvContext();
185
- uv_context.resolution.set(1, 1);
304
+ const destination_data = destination.data;
186
305
 
187
- this.uvs[uv_index] = uv_context;
188
- }
306
+ if (itemSize === 1) {
307
+ // special fast case
308
+ destination_data.fill(texel[0]);
309
+ } else {
189
310
 
190
- const image = t.image;
311
+ const texel_count = width * height;
191
312
 
192
- if (image !== undefined && image.width !== undefined) {
193
- uv_context.resolution.x = max2(uv_context.resolution.x, image.width);
194
- uv_context.resolution.y = max2(uv_context.resolution.y, image.height);
195
- }
313
+ for (let i = 0; i < texel_count; i++) {
314
+ const destination_offset = i * itemSize;
196
315
 
197
- });
316
+ destination_data.set(texel, destination_offset);
317
+ }
318
+ }
319
+ }
198
320
 
321
+ /**
322
+ *
323
+ * @param {string} texture_name
324
+ * @returns {{type:DataType,itemSize:number, default_value:[]}}
325
+ */
326
+ function build_atlas_sampler_options_texture_name(texture_name) {
327
+ let type;
328
+ let itemSize;
329
+ let default_value;
330
+
331
+ switch (texture_name) {
332
+ case 'normal':
333
+ type = DataType.Uint8;
334
+ itemSize = 3;
335
+ default_value = [127, 127, 255];
336
+ break;
337
+ case 'ao':
338
+ type = DataType.Uint8;
339
+ itemSize = 1;
340
+ default_value = [255];
341
+ break;
342
+ case "metalness":
343
+ type = DataType.Uint8;
344
+ itemSize = 1;
345
+ default_value = [0];
346
+ break;
347
+ case "roughness":
348
+ type = DataType.Uint8;
349
+ itemSize = 1;
350
+ default_value = [255];
351
+ break;
352
+ default:
353
+ type = DataType.Uint8;
354
+ itemSize = 4;
355
+ default_value = [255, 255, 255, 255];
356
+ break;
199
357
  }
200
358
 
359
+ return {
360
+ type,
361
+ itemSize,
362
+ default_value
363
+ };
201
364
  }
202
365
 
203
366
  export class MaterialOptimizationContext {
@@ -304,12 +467,16 @@ export class MaterialOptimizationContext {
304
467
  */
305
468
  build_atlas_samplers(materials) {
306
469
 
470
+ let i;
471
+
307
472
  this.atlases.clear();
308
473
 
309
474
  const names = [];
310
475
  const uv_indices = [];
311
476
 
312
- for (let i = 0; i < materials.length; i++) {
477
+ const material_count = materials.length;
478
+
479
+ for (i = 0; i < material_count; i++) {
313
480
  const material = materials[i];
314
481
 
315
482
  const source_material = material.source_material;
@@ -324,6 +491,13 @@ export class MaterialOptimizationContext {
324
491
  for (let i = 0; i < attachments.length; i++) {
325
492
  const attachment = attachments[i];
326
493
 
494
+ const attachment_texture = attachment.read(source_material);
495
+
496
+ if (!attachment_texture) {
497
+ // no attachment on the material
498
+ continue;
499
+ }
500
+
327
501
  if (array_push_if_unique(names, attachment.name)) {
328
502
  // added
329
503
  uv_indices[names.length - 1] = attachment.uv_index;
@@ -332,13 +506,29 @@ export class MaterialOptimizationContext {
332
506
  }
333
507
  }
334
508
 
335
- for (let i = 0; i < names.length; i++) {
509
+ for (i = 0; i < names.length; i++) {
336
510
  const name = names[i];
337
511
  const uv_index = uv_indices[i];
338
512
 
339
513
  const size = this.atlas_size[uv_index];
340
514
 
341
- this.atlases.set(name, Sampler2D.uint8(4, size.x, size.y));
515
+ const width = size.x;
516
+ const height = size.y;
517
+
518
+ const sampler_options = build_atlas_sampler_options_texture_name(name);
519
+
520
+ const TA = compute_typed_array_constructor_from_data_type(sampler_options.type);
521
+
522
+ const itemSize = sampler_options.itemSize;
523
+ const sampler_data_size = itemSize * width * height;
524
+ const sampler_data = new TA(sampler_data_size);
525
+
526
+ for (let j = 0; j < sampler_data_size; j += itemSize) {
527
+ sampler_data.set(sampler_options.default_value, j);
528
+ }
529
+
530
+ const atlas_sampler = new Sampler2D(sampler_data, itemSize, width, height);
531
+ this.atlases.set(name, atlas_sampler);
342
532
  }
343
533
  }
344
534
 
@@ -357,9 +547,17 @@ export class MaterialOptimizationContext {
357
547
  */
358
548
  const attachments = TextureAttachmentsByMaterialType[materialType];
359
549
 
550
+ const attachment_names = Array.from(this.atlases.keys());
551
+
360
552
  for (let i = 0; i < attachments.length; i++) {
361
553
  const attachment = attachments[i];
362
554
 
555
+ const texture_name = attachment.name;
556
+
557
+ if (!attachment_names.includes(texture_name)) {
558
+ continue;
559
+ }
560
+
363
561
  const uv_context = material.uvs[attachment.uv_index];
364
562
 
365
563
  if (uv_context === undefined) {
@@ -367,28 +565,45 @@ export class MaterialOptimizationContext {
367
565
  continue;
368
566
  }
369
567
 
568
+ const atlas_sampler = this.atlases.get(texture_name);
569
+
370
570
  let source_sampler;
371
571
 
372
572
  const texture = attachment.read(source_material);
373
573
 
374
- const texture_width = uv_context.resolution.x;
375
- const texture_height = uv_context.resolution.y;
574
+ const resolution = uv_context.resolution;
575
+
576
+ const texture_width = resolution.x;
577
+ const texture_height = resolution.y;
578
+
376
579
  if (texture !== undefined && texture !== null) {
377
580
  // source_sampler = Sampler2D.uint8(5, texture_width, texture_height);
378
581
  //
379
582
  // source_sampler.fill(0, 0, texture_width, texture_height, [255, 255, 255, 255]);
380
583
 
381
584
  source_sampler = convertTexture2Sampler2D(texture, texture_width, texture_height, false);
585
+
586
+ if (source_sampler.itemSize !== atlas_sampler.itemSize) {
587
+ const destination = Sampler2D.uint8(atlas_sampler.itemSize, texture_width, texture_height);
588
+
589
+ extract_texture_data(destination, source_sampler, texture_name);
590
+
591
+ source_sampler = destination;
592
+ }
593
+
382
594
  } else {
383
- // TODO proper defaults when a texture is missing
384
- source_sampler = Sampler2D.uint8(4, texture_width, texture_height);
595
+ // texture for a given slot is missing, create a default
596
+ source_sampler = Sampler2D.uint8(atlas_sampler.itemSize, texture_width, texture_height);
597
+
598
+ compute_default_texture_value(source_sampler, source_material, texture_name);
385
599
  }
386
600
 
387
- const atlas = this.atlases.get(attachment.name);
601
+ // apply modification to texture to remove dependency on source material
602
+ apply_material_shading_to_texture(source_sampler, source_material, texture_name);
388
603
 
389
604
  const patch = uv_context.atlas_patch;
390
605
 
391
- atlas.copy_sameItemSize(
606
+ atlas_sampler.copy_sameItemSize(
392
607
  source_sampler,
393
608
  0, 0,
394
609
  patch.x0, patch.y0, patch.getWidth(), patch.getHeight()
@@ -406,7 +621,7 @@ export class MaterialOptimizationContext {
406
621
  if (material.isMeshStandardMaterial) {
407
622
  this.meshes.push(mesh);
408
623
  } else {
409
- throw new Error(`Unsupported material type '${material.type}'`);
624
+ // throw new Error(`Unsupported material type '${material.type}'`);
410
625
  }
411
626
  }
412
627
 
@@ -488,8 +703,10 @@ export class MaterialOptimizationContext {
488
703
  const unique_material_array = [];
489
704
  collectIteratorValueToArray(unique_material_array, unique_materials.values());
490
705
 
491
- if (unique_material_array.length <= 4) {
492
- // no point in atlasing
706
+ const unique_material_count = unique_material_array.length;
707
+
708
+ if (unique_material_count <= 4) {
709
+ // too few unique materials, no point in atlasing
493
710
  return;
494
711
  }
495
712
 
@@ -497,28 +714,10 @@ export class MaterialOptimizationContext {
497
714
 
498
715
  this.build_atlas_samplers(unique_material_array);
499
716
 
500
- const textures = new Map();
501
- //build new textures
502
- for (const [name, sampler] of this.atlases) {
503
- const texture = new DataTexture();
504
-
505
- texture.image = {
506
- data: sampler.data,
507
- width: sampler.width,
508
- height: sampler.height
509
- };
510
-
511
- texture.wrapS = ClampToEdgeWrapping;
512
- texture.wrapT = ClampToEdgeWrapping;
513
- texture.type = UnsignedByteType;
514
- texture.anisotropy = 4;
515
-
516
- prepare_atlas_texture(texture, name);
717
+ const textures = this.initialize_atlas_textures();
517
718
 
518
- textures.set(name, texture);
519
- }
520
719
 
521
- for (let i = 0; i < unique_material_array.length; i++) {
720
+ for (let i = 0; i < unique_material_count; i++) {
522
721
  const mat = unique_material_array[i];
523
722
 
524
723
  this.write_material_to_atlas(mat);
@@ -529,65 +728,135 @@ export class MaterialOptimizationContext {
529
728
  for (let j = 0; j < geometries.length; j++) {
530
729
  const bufferGeometry = geometries[j];
531
730
 
532
- for (let k = 0; k < BUFFER_GEOMETRY_UVS.length; k++) {
533
- const uv_metadata = BUFFER_GEOMETRY_UVS[k];
731
+ this.apply_geometry_uv_changes(bufferGeometry, mat);
732
+ }
733
+ }
734
+
735
+ /*
736
+ merge roughness and metalness. Three.js shaders sample roughness from G channel (index = 1) and metalness from B channel (index 2)
737
+ Because of this, we can't pass B/W textures with only Red channel set, so we are forced to create a merged texture from metalness and roughnes
738
+ */
739
+ if (textures.get('roughness') !== undefined || textures.get('metalness') !== undefined) {
740
+ const result_texture = merge_textures_metalness_rougness({
741
+ roughness: textures.get('roughness'),
742
+ metalness: textures.get('metalness')
743
+ });
744
+
745
+ if (textures.get('roughness') !== undefined) {
746
+ textures.set('roughness', result_texture);
747
+ }
748
+ if (textures.get('metalness') !== undefined) {
749
+ textures.set('roughness', result_texture);
750
+ }
751
+ }
752
+
753
+ // update textures
754
+ for (const [name, texture] of textures) {
755
+ texture.needsUpdate = true;
756
+ }
757
+
758
+ // compute merged materials
759
+ const unique_source_materials = [];
760
+ collectIteratorValueToArray(unique_source_materials, unique_materials.keys());
534
761
 
535
- // update UVs
536
- const uv_attribute = bufferGeometry.attributes[uv_metadata.name];
762
+ const merged_materials = merge_materials(unique_source_materials, textures);
763
+
764
+ for (let i = 0; i < unique_material_count; i++) {
765
+ const mat = unique_material_array[i];
537
766
 
538
- if (uv_attribute === undefined) {
539
- // no UVs, nothing to update
540
- continue;
541
- }
767
+ // bind material
768
+ const new_material = merged_materials.get(mat.source_material);
769
+
770
+ // set new material to all associated meshes
771
+ const meshes = material_to_mesh_map.get(mat);
772
+
773
+ for (let j = 0; j < meshes.length; j++) {
774
+ const mesh = meshes[j];
775
+ mesh.material = new_material;
776
+ }
777
+ }
542
778
 
779
+ }
543
780
 
544
- const uv_index = uv_metadata.index;
545
- const uv_context = mat.uvs[uv_index];
781
+ /**
782
+ *
783
+ * @return {Map<string, THREE.DataTexture>}
784
+ */
785
+ initialize_atlas_textures() {
786
+ const textures = new Map();
787
+ //build new textures
788
+ for (const [name, sampler] of this.atlases) {
546
789
 
547
- if (uv_context === undefined) {
548
- // no UV context present
549
- // TODO need to force context to exist in this case or remove the attribute
550
- continue;
551
- }
790
+ const sampler_data_type = typedArrayToDataType(sampler.data);
552
791
 
553
- const atlas_size = this.atlas_size[uv_index];
792
+ const format = channelCountToThreeTextureFormat(sampler.itemSize);
793
+ const type = computeThreeTextureTypeFromDataType(sampler_data_type);
554
794
 
555
- const atlas_width = atlas_size.x;
556
- const atlas_height = atlas_size.y;
795
+ const texture = new DataTexture(sampler.data, sampler.width, sampler.height, format, type);
557
796
 
558
- const patch = uv_context.atlas_patch;
797
+ texture.wrapS = ClampToEdgeWrapping;
798
+ texture.wrapT = ClampToEdgeWrapping;
799
+ texture.name = name;
559
800
 
560
- const matrix3 = new Matrix3();
561
- matrix3.setUvTransform(
562
- patch.x0 / atlas_width,
563
- patch.y0 / atlas_height,
564
- patch.getWidth() / atlas_width,
565
- patch.getHeight() / atlas_height,
566
- 0,
567
- 0, 0
568
- );
801
+ texture.internalFormat = normalized_internal_format(sampler_data_type, sampler.itemSize);
569
802
 
570
- uv_attribute.applyMatrix3(matrix3);
803
+ prepare_atlas_texture(texture, name);
571
804
 
572
- uv_attribute.needsUpdate = true;
573
- }
805
+ textures.set(name, texture);
806
+ }
807
+ return textures;
808
+ }
809
+
810
+ /**
811
+ *
812
+ * @param {THREE.BufferGeometry} bufferGeometry
813
+ * @param {MaterialDescriptor} mat
814
+ */
815
+ apply_geometry_uv_changes(bufferGeometry, mat) {
816
+ const uv_count = BUFFER_GEOMETRY_UVS.length;
817
+
818
+ for (let k = 0; k < uv_count; k++) {
819
+ const uv_metadata = BUFFER_GEOMETRY_UVS[k];
820
+
821
+ // update UVs
822
+ const uv_attribute = bufferGeometry.attributes[uv_metadata.name];
823
+
824
+ if (uv_attribute === undefined) {
825
+ // no UVs, nothing to update
826
+ continue;
574
827
  }
575
828
 
576
- // build material
577
- const new_material = mat.source_material.clone();
578
829
 
579
- traverseMaterialTextures(new_material, (old_texture, attachment) => {
580
- attachment.write(new_material, textures.get(attachment.name));
581
- });
830
+ const uv_index = uv_metadata.index;
831
+ const uv_context = mat.uvs[uv_index];
582
832
 
583
- // set new material to all associated meshes
584
- const meshes = material_to_mesh_map.get(mat);
585
- for (let j = 0; j < meshes.length; j++) {
586
- const mesh = meshes[j];
587
- mesh.material = new_material;
833
+ if (uv_context === undefined) {
834
+ // no UV context present
835
+ // TODO need to force context to exist in this case or remove the attribute
836
+ continue;
588
837
  }
589
- }
590
838
 
839
+ const atlas_size = this.atlas_size[uv_index];
840
+
841
+ const atlas_width = atlas_size.x;
842
+ const atlas_height = atlas_size.y;
843
+
844
+ const patch = uv_context.atlas_patch;
845
+
846
+ const matrix3 = new Matrix3();
847
+ matrix3.setUvTransform(
848
+ patch.x0 / atlas_width,
849
+ patch.y0 / atlas_height,
850
+ patch.getWidth() / atlas_width,
851
+ patch.getHeight() / atlas_height,
852
+ 0,
853
+ 0, 0
854
+ );
855
+
856
+ uv_attribute.applyMatrix3(matrix3);
857
+
858
+ uv_attribute.needsUpdate = true;
859
+ }
591
860
  }
592
861
  }
593
862