@luma.gl/gltf 9.3.0-alpha.4 → 9.3.0-alpha.8

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 (90) hide show
  1. package/dist/dist.dev.js +1305 -327
  2. package/dist/dist.min.js +98 -46
  3. package/dist/gltf/animations/animations.d.ts +16 -4
  4. package/dist/gltf/animations/animations.d.ts.map +1 -1
  5. package/dist/gltf/animations/interpolate.d.ts +4 -3
  6. package/dist/gltf/animations/interpolate.d.ts.map +1 -1
  7. package/dist/gltf/animations/interpolate.js +27 -36
  8. package/dist/gltf/animations/interpolate.js.map +1 -1
  9. package/dist/gltf/create-gltf-model.d.ts +15 -1
  10. package/dist/gltf/create-gltf-model.d.ts.map +1 -1
  11. package/dist/gltf/create-gltf-model.js +154 -48
  12. package/dist/gltf/create-gltf-model.js.map +1 -1
  13. package/dist/gltf/create-scenegraph-from-gltf.d.ts +37 -2
  14. package/dist/gltf/create-scenegraph-from-gltf.d.ts.map +1 -1
  15. package/dist/gltf/create-scenegraph-from-gltf.js +74 -6
  16. package/dist/gltf/create-scenegraph-from-gltf.js.map +1 -1
  17. package/dist/gltf/gltf-animator.d.ts +26 -0
  18. package/dist/gltf/gltf-animator.d.ts.map +1 -1
  19. package/dist/gltf/gltf-animator.js +22 -19
  20. package/dist/gltf/gltf-animator.js.map +1 -1
  21. package/dist/gltf/gltf-extension-support.d.ts +10 -0
  22. package/dist/gltf/gltf-extension-support.d.ts.map +1 -0
  23. package/dist/gltf/gltf-extension-support.js +173 -0
  24. package/dist/gltf/gltf-extension-support.js.map +1 -0
  25. package/dist/index.cjs +1247 -294
  26. package/dist/index.cjs.map +4 -4
  27. package/dist/index.d.ts +2 -2
  28. package/dist/index.d.ts.map +1 -1
  29. package/dist/index.js +1 -0
  30. package/dist/index.js.map +1 -1
  31. package/dist/parsers/parse-gltf-animations.d.ts +1 -0
  32. package/dist/parsers/parse-gltf-animations.d.ts.map +1 -1
  33. package/dist/parsers/parse-gltf-animations.js +73 -28
  34. package/dist/parsers/parse-gltf-animations.js.map +1 -1
  35. package/dist/parsers/parse-gltf-lights.d.ts.map +1 -1
  36. package/dist/parsers/parse-gltf-lights.js +112 -18
  37. package/dist/parsers/parse-gltf-lights.js.map +1 -1
  38. package/dist/parsers/parse-gltf.d.ts +19 -2
  39. package/dist/parsers/parse-gltf.d.ts.map +1 -1
  40. package/dist/parsers/parse-gltf.js +101 -61
  41. package/dist/parsers/parse-gltf.js.map +1 -1
  42. package/dist/parsers/parse-pbr-material.d.ts +115 -2
  43. package/dist/parsers/parse-pbr-material.d.ts.map +1 -1
  44. package/dist/parsers/parse-pbr-material.js +565 -54
  45. package/dist/parsers/parse-pbr-material.js.map +1 -1
  46. package/dist/pbr/pbr-environment.d.ts +6 -0
  47. package/dist/pbr/pbr-environment.d.ts.map +1 -1
  48. package/dist/pbr/pbr-environment.js +15 -12
  49. package/dist/pbr/pbr-environment.js.map +1 -1
  50. package/dist/pbr/pbr-material.d.ts +13 -3
  51. package/dist/pbr/pbr-material.d.ts.map +1 -1
  52. package/dist/webgl-to-webgpu/convert-webgl-attribute.d.ts +12 -1
  53. package/dist/webgl-to-webgpu/convert-webgl-attribute.d.ts.map +1 -1
  54. package/dist/webgl-to-webgpu/convert-webgl-attribute.js +3 -0
  55. package/dist/webgl-to-webgpu/convert-webgl-attribute.js.map +1 -1
  56. package/dist/webgl-to-webgpu/convert-webgl-sampler.d.ts +11 -5
  57. package/dist/webgl-to-webgpu/convert-webgl-sampler.d.ts.map +1 -1
  58. package/dist/webgl-to-webgpu/convert-webgl-sampler.js +16 -12
  59. package/dist/webgl-to-webgpu/convert-webgl-sampler.js.map +1 -1
  60. package/dist/webgl-to-webgpu/convert-webgl-topology.d.ts +2 -9
  61. package/dist/webgl-to-webgpu/convert-webgl-topology.d.ts.map +1 -1
  62. package/dist/webgl-to-webgpu/convert-webgl-topology.js +2 -14
  63. package/dist/webgl-to-webgpu/convert-webgl-topology.js.map +1 -1
  64. package/dist/webgl-to-webgpu/gltf-webgl-constants.d.ts +27 -0
  65. package/dist/webgl-to-webgpu/gltf-webgl-constants.d.ts.map +1 -0
  66. package/dist/webgl-to-webgpu/gltf-webgl-constants.js +34 -0
  67. package/dist/webgl-to-webgpu/gltf-webgl-constants.js.map +1 -0
  68. package/package.json +8 -9
  69. package/src/gltf/animations/animations.ts +17 -5
  70. package/src/gltf/animations/interpolate.ts +49 -68
  71. package/src/gltf/create-gltf-model.ts +214 -48
  72. package/src/gltf/create-scenegraph-from-gltf.ts +131 -12
  73. package/src/gltf/gltf-animator.ts +34 -25
  74. package/src/gltf/gltf-extension-support.ts +214 -0
  75. package/src/index.ts +10 -2
  76. package/src/parsers/parse-gltf-animations.ts +94 -33
  77. package/src/parsers/parse-gltf-lights.ts +147 -20
  78. package/src/parsers/parse-gltf.ts +170 -90
  79. package/src/parsers/parse-pbr-material.ts +865 -80
  80. package/src/pbr/pbr-environment.ts +38 -15
  81. package/src/pbr/pbr-material.ts +18 -3
  82. package/src/webgl-to-webgpu/convert-webgl-attribute.ts +12 -1
  83. package/src/webgl-to-webgpu/convert-webgl-sampler.ts +38 -29
  84. package/src/webgl-to-webgpu/convert-webgl-topology.ts +2 -14
  85. package/src/webgl-to-webgpu/gltf-webgl-constants.ts +35 -0
  86. package/dist/utils/deep-copy.d.ts +0 -3
  87. package/dist/utils/deep-copy.d.ts.map +0 -1
  88. package/dist/utils/deep-copy.js +0 -21
  89. package/dist/utils/deep-copy.js.map +0 -1
  90. package/src/utils/deep-copy.ts +0 -22
package/dist/index.cjs CHANGED
@@ -22,6 +22,7 @@ var dist_exports = {};
22
22
  __export(dist_exports, {
23
23
  GLTFAnimator: () => GLTFAnimator,
24
24
  createScenegraphsFromGLTF: () => createScenegraphsFromGLTF,
25
+ getGLTFExtensionSupport: () => getGLTFExtensionSupport,
25
26
  loadPBREnvironment: () => loadPBREnvironment,
26
27
  parseGLTFLights: () => parseGLTFLights,
27
28
  parsePBRMaterial: () => parsePBRMaterial
@@ -32,6 +33,7 @@ module.exports = __toCommonJS(dist_exports);
32
33
  var import_engine = require("@luma.gl/engine");
33
34
  var import_textures = require("@loaders.gl/textures");
34
35
  function loadPBREnvironment(device, props) {
36
+ const specularMipLevels = props.specularMipLevels ?? 1;
35
37
  const brdfLutTexture = new import_engine.DynamicTexture(device, {
36
38
  id: "brdfLUT",
37
39
  sampler: {
@@ -45,7 +47,7 @@ function loadPBREnvironment(device, props) {
45
47
  });
46
48
  const diffuseEnvSampler = makeCube(device, {
47
49
  id: "DiffuseEnvSampler",
48
- getTextureForFace: (dir) => (0, import_textures.loadImageTexture)(props.getTexUrl("diffuse", dir, 0)),
50
+ getTextureForFace: (face) => (0, import_textures.loadImageTexture)(props.getTexUrl("diffuse", FACES.indexOf(face), 0)),
49
51
  sampler: {
50
52
  addressModeU: "clamp-to-edge",
51
53
  addressModeV: "clamp-to-edge",
@@ -55,12 +57,13 @@ function loadPBREnvironment(device, props) {
55
57
  });
56
58
  const specularEnvSampler = makeCube(device, {
57
59
  id: "SpecularEnvSampler",
58
- getTextureForFace: (dir) => {
60
+ getTextureForFace: (face) => {
59
61
  const imageArray = [];
60
- for (let lod = 0; lod <= props.specularMipLevels - 1; lod++) {
61
- imageArray.push((0, import_textures.loadImageTexture)(props.getTexUrl("specular", dir, lod)));
62
+ const direction = FACES.indexOf(face);
63
+ for (let lod = 0; lod < specularMipLevels; lod++) {
64
+ imageArray.push((0, import_textures.loadImageTexture)(props.getTexUrl("specular", direction, lod)));
62
65
  }
63
- return imageArray;
66
+ return Promise.all(imageArray);
64
67
  },
65
68
  sampler: {
66
69
  addressModeU: "clamp-to-edge",
@@ -76,28 +79,57 @@ function loadPBREnvironment(device, props) {
76
79
  specularEnvSampler
77
80
  };
78
81
  }
79
- var FACES = [0, 1, 2, 3, 4, 5];
82
+ var FACES = ["+X", "-X", "+Y", "-Y", "+Z", "-Z"];
80
83
  function makeCube(device, { id, getTextureForFace, sampler }) {
81
- const data = {};
82
- FACES.forEach((face) => {
83
- data[String(face)] = getTextureForFace(face);
84
+ const data = Promise.all(FACES.map((face) => getTextureForFace(face))).then((faceDataArray) => {
85
+ const cubeData = {};
86
+ FACES.forEach((face, index) => {
87
+ cubeData[face] = faceDataArray[index];
88
+ });
89
+ return cubeData;
84
90
  });
85
91
  return new import_engine.DynamicTexture(device, {
86
92
  id,
87
93
  dimension: "cube",
88
94
  mipmaps: false,
89
95
  sampler,
90
- // @ts-expect-error
91
96
  data
92
97
  });
93
98
  }
94
99
 
95
100
  // dist/parsers/parse-pbr-material.js
96
- var import_constants2 = require("@luma.gl/constants");
97
101
  var import_core = require("@luma.gl/core");
98
102
 
103
+ // dist/webgl-to-webgpu/gltf-webgl-constants.js
104
+ var GLEnum;
105
+ (function(GLEnum2) {
106
+ GLEnum2[GLEnum2["POINTS"] = 0] = "POINTS";
107
+ GLEnum2[GLEnum2["LINES"] = 1] = "LINES";
108
+ GLEnum2[GLEnum2["LINE_LOOP"] = 2] = "LINE_LOOP";
109
+ GLEnum2[GLEnum2["LINE_STRIP"] = 3] = "LINE_STRIP";
110
+ GLEnum2[GLEnum2["TRIANGLES"] = 4] = "TRIANGLES";
111
+ GLEnum2[GLEnum2["TRIANGLE_STRIP"] = 5] = "TRIANGLE_STRIP";
112
+ GLEnum2[GLEnum2["TRIANGLE_FAN"] = 6] = "TRIANGLE_FAN";
113
+ GLEnum2[GLEnum2["ONE"] = 1] = "ONE";
114
+ GLEnum2[GLEnum2["SRC_ALPHA"] = 770] = "SRC_ALPHA";
115
+ GLEnum2[GLEnum2["ONE_MINUS_SRC_ALPHA"] = 771] = "ONE_MINUS_SRC_ALPHA";
116
+ GLEnum2[GLEnum2["FUNC_ADD"] = 32774] = "FUNC_ADD";
117
+ GLEnum2[GLEnum2["LINEAR"] = 9729] = "LINEAR";
118
+ GLEnum2[GLEnum2["NEAREST"] = 9728] = "NEAREST";
119
+ GLEnum2[GLEnum2["NEAREST_MIPMAP_NEAREST"] = 9984] = "NEAREST_MIPMAP_NEAREST";
120
+ GLEnum2[GLEnum2["LINEAR_MIPMAP_NEAREST"] = 9985] = "LINEAR_MIPMAP_NEAREST";
121
+ GLEnum2[GLEnum2["NEAREST_MIPMAP_LINEAR"] = 9986] = "NEAREST_MIPMAP_LINEAR";
122
+ GLEnum2[GLEnum2["LINEAR_MIPMAP_LINEAR"] = 9987] = "LINEAR_MIPMAP_LINEAR";
123
+ GLEnum2[GLEnum2["TEXTURE_MIN_FILTER"] = 10241] = "TEXTURE_MIN_FILTER";
124
+ GLEnum2[GLEnum2["TEXTURE_WRAP_S"] = 10242] = "TEXTURE_WRAP_S";
125
+ GLEnum2[GLEnum2["TEXTURE_WRAP_T"] = 10243] = "TEXTURE_WRAP_T";
126
+ GLEnum2[GLEnum2["REPEAT"] = 10497] = "REPEAT";
127
+ GLEnum2[GLEnum2["CLAMP_TO_EDGE"] = 33071] = "CLAMP_TO_EDGE";
128
+ GLEnum2[GLEnum2["MIRRORED_REPEAT"] = 33648] = "MIRRORED_REPEAT";
129
+ GLEnum2[GLEnum2["UNPACK_FLIP_Y_WEBGL"] = 37440] = "UNPACK_FLIP_Y_WEBGL";
130
+ })(GLEnum || (GLEnum = {}));
131
+
99
132
  // dist/webgl-to-webgpu/convert-webgl-sampler.js
100
- var import_constants = require("@luma.gl/constants");
101
133
  function convertSampler(gltfSampler) {
102
134
  return {
103
135
  addressModeU: convertSamplerWrapMode(gltfSampler.wrapS),
@@ -108,11 +140,11 @@ function convertSampler(gltfSampler) {
108
140
  }
109
141
  function convertSamplerWrapMode(mode) {
110
142
  switch (mode) {
111
- case 33071:
143
+ case GLEnum.CLAMP_TO_EDGE:
112
144
  return "clamp-to-edge";
113
- case 10497:
145
+ case GLEnum.REPEAT:
114
146
  return "repeat";
115
- case 33648:
147
+ case GLEnum.MIRRORED_REPEAT:
116
148
  return "mirror-repeat";
117
149
  default:
118
150
  return void 0;
@@ -120,9 +152,9 @@ function convertSamplerWrapMode(mode) {
120
152
  }
121
153
  function convertSamplerMagFilter(mode) {
122
154
  switch (mode) {
123
- case 9728:
155
+ case GLEnum.NEAREST:
124
156
  return "nearest";
125
- case 9729:
157
+ case GLEnum.LINEAR:
126
158
  return "linear";
127
159
  default:
128
160
  return void 0;
@@ -130,17 +162,17 @@ function convertSamplerMagFilter(mode) {
130
162
  }
131
163
  function convertSamplerMinFilter(mode) {
132
164
  switch (mode) {
133
- case 9728:
165
+ case GLEnum.NEAREST:
134
166
  return { minFilter: "nearest" };
135
- case 9729:
167
+ case GLEnum.LINEAR:
136
168
  return { minFilter: "linear" };
137
- case 9984:
169
+ case GLEnum.NEAREST_MIPMAP_NEAREST:
138
170
  return { minFilter: "nearest", mipmapFilter: "nearest" };
139
- case 9985:
171
+ case GLEnum.LINEAR_MIPMAP_NEAREST:
140
172
  return { minFilter: "linear", mipmapFilter: "nearest" };
141
- case 9986:
173
+ case GLEnum.NEAREST_MIPMAP_LINEAR:
142
174
  return { minFilter: "nearest", mipmapFilter: "linear" };
143
- case 9987:
175
+ case GLEnum.LINEAR_MIPMAP_LINEAR:
144
176
  return { minFilter: "linear", mipmapFilter: "linear" };
145
177
  default:
146
178
  return {};
@@ -172,7 +204,8 @@ function parsePBRMaterial(device, material, attributes, options) {
172
204
  if (imageBasedLightingEnvironment) {
173
205
  parsedMaterial.bindings.pbr_diffuseEnvSampler = imageBasedLightingEnvironment.diffuseEnvSampler.texture;
174
206
  parsedMaterial.bindings.pbr_specularEnvSampler = imageBasedLightingEnvironment.specularEnvSampler.texture;
175
- parsedMaterial.bindings.pbr_BrdfLUT = imageBasedLightingEnvironment.brdfLutTexture.texture;
207
+ parsedMaterial.bindings.pbr_brdfLUT = imageBasedLightingEnvironment.brdfLutTexture.texture;
208
+ parsedMaterial.uniforms.IBLenabled = true;
176
209
  parsedMaterial.uniforms.scaleIBLAmbient = [1, 1];
177
210
  }
178
211
  if (options == null ? void 0 : options.pbrDebug) {
@@ -186,6 +219,8 @@ function parsePBRMaterial(device, material, attributes, options) {
186
219
  parsedMaterial.defines["HAS_TANGENTS"] = true;
187
220
  if (attributes["TEXCOORD_0"])
188
221
  parsedMaterial.defines["HAS_UV"] = true;
222
+ if (attributes["JOINTS_0"] && attributes["WEIGHTS_0"])
223
+ parsedMaterial.defines["HAS_SKIN"] = true;
189
224
  if (attributes["COLOR_0"])
190
225
  parsedMaterial.defines["HAS_COLORS"] = true;
191
226
  if (options == null ? void 0 : options.imageBasedLightingEnvironment)
@@ -193,74 +228,418 @@ function parsePBRMaterial(device, material, attributes, options) {
193
228
  if (options == null ? void 0 : options.lights)
194
229
  parsedMaterial.defines["USE_LIGHTS"] = true;
195
230
  if (material) {
196
- parseMaterial(device, material, parsedMaterial);
231
+ if (options.validateAttributes !== false) {
232
+ warnOnMissingExpectedAttributes(material, attributes);
233
+ }
234
+ parseMaterial(device, material, parsedMaterial, options.gltf);
197
235
  }
198
236
  return parsedMaterial;
199
237
  }
200
- function parseMaterial(device, material, parsedMaterial) {
201
- parsedMaterial.uniforms.unlit = Boolean(material.unlit);
238
+ function warnOnMissingExpectedAttributes(material, attributes) {
239
+ var _a;
240
+ const uvDependentTextureSlots = getUvDependentTextureSlots(material);
241
+ if (uvDependentTextureSlots.length > 0 && !attributes["TEXCOORD_0"]) {
242
+ import_core.log.warn(`glTF material uses ${uvDependentTextureSlots.join(", ")} but primitive is missing TEXCOORD_0; textured shading will sample the default UV coordinates`)();
243
+ }
244
+ const isUnlitMaterial = Boolean(material.unlit || ((_a = material.extensions) == null ? void 0 : _a.KHR_materials_unlit));
245
+ if (isUnlitMaterial || attributes["NORMAL"]) {
246
+ return;
247
+ }
248
+ const missingNormalReason = material.normalTexture ? "lit PBR shading with normalTexture" : "lit PBR shading";
249
+ import_core.log.warn(`glTF primitive is missing NORMAL while using ${missingNormalReason}; shading will fall back to geometric normals`)();
250
+ }
251
+ function getUvDependentTextureSlots(material) {
252
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r, _s, _t;
253
+ const uvDependentTextureSlots = [];
254
+ if ((_a = material.pbrMetallicRoughness) == null ? void 0 : _a.baseColorTexture) {
255
+ uvDependentTextureSlots.push("baseColorTexture");
256
+ }
257
+ if ((_b = material.pbrMetallicRoughness) == null ? void 0 : _b.metallicRoughnessTexture) {
258
+ uvDependentTextureSlots.push("metallicRoughnessTexture");
259
+ }
260
+ if (material.normalTexture) {
261
+ uvDependentTextureSlots.push("normalTexture");
262
+ }
263
+ if (material.occlusionTexture) {
264
+ uvDependentTextureSlots.push("occlusionTexture");
265
+ }
266
+ if (material.emissiveTexture) {
267
+ uvDependentTextureSlots.push("emissiveTexture");
268
+ }
269
+ if ((_d = (_c = material.extensions) == null ? void 0 : _c.KHR_materials_specular) == null ? void 0 : _d.specularTexture) {
270
+ uvDependentTextureSlots.push("KHR_materials_specular.specularTexture");
271
+ }
272
+ if ((_f = (_e = material.extensions) == null ? void 0 : _e.KHR_materials_specular) == null ? void 0 : _f.specularColorTexture) {
273
+ uvDependentTextureSlots.push("KHR_materials_specular.specularColorTexture");
274
+ }
275
+ if ((_h = (_g = material.extensions) == null ? void 0 : _g.KHR_materials_transmission) == null ? void 0 : _h.transmissionTexture) {
276
+ uvDependentTextureSlots.push("KHR_materials_transmission.transmissionTexture");
277
+ }
278
+ if ((_j = (_i = material.extensions) == null ? void 0 : _i.KHR_materials_clearcoat) == null ? void 0 : _j.clearcoatTexture) {
279
+ uvDependentTextureSlots.push("KHR_materials_clearcoat.clearcoatTexture");
280
+ }
281
+ if ((_l = (_k = material.extensions) == null ? void 0 : _k.KHR_materials_clearcoat) == null ? void 0 : _l.clearcoatRoughnessTexture) {
282
+ uvDependentTextureSlots.push("KHR_materials_clearcoat.clearcoatRoughnessTexture");
283
+ }
284
+ if ((_n = (_m = material.extensions) == null ? void 0 : _m.KHR_materials_sheen) == null ? void 0 : _n.sheenColorTexture) {
285
+ uvDependentTextureSlots.push("KHR_materials_sheen.sheenColorTexture");
286
+ }
287
+ if ((_p = (_o = material.extensions) == null ? void 0 : _o.KHR_materials_sheen) == null ? void 0 : _p.sheenRoughnessTexture) {
288
+ uvDependentTextureSlots.push("KHR_materials_sheen.sheenRoughnessTexture");
289
+ }
290
+ if ((_r = (_q = material.extensions) == null ? void 0 : _q.KHR_materials_iridescence) == null ? void 0 : _r.iridescenceTexture) {
291
+ uvDependentTextureSlots.push("KHR_materials_iridescence.iridescenceTexture");
292
+ }
293
+ if ((_t = (_s = material.extensions) == null ? void 0 : _s.KHR_materials_anisotropy) == null ? void 0 : _t.anisotropyTexture) {
294
+ uvDependentTextureSlots.push("KHR_materials_anisotropy.anisotropyTexture");
295
+ }
296
+ return uvDependentTextureSlots;
297
+ }
298
+ function parseMaterial(device, material, parsedMaterial, gltf) {
299
+ var _a;
300
+ parsedMaterial.uniforms.unlit = Boolean(material.unlit || ((_a = material.extensions) == null ? void 0 : _a.KHR_materials_unlit));
202
301
  if (material.pbrMetallicRoughness) {
203
- parsePbrMetallicRoughness(device, material.pbrMetallicRoughness, parsedMaterial);
302
+ parsePbrMetallicRoughness(device, material.pbrMetallicRoughness, parsedMaterial, gltf);
204
303
  }
205
304
  if (material.normalTexture) {
206
- addTexture(device, material.normalTexture, "pbr_normalSampler", "HAS_NORMALMAP", parsedMaterial);
305
+ addTexture(device, material.normalTexture, "pbr_normalSampler", parsedMaterial, {
306
+ featureOptions: {
307
+ define: "HAS_NORMALMAP",
308
+ enabledUniformName: "normalMapEnabled"
309
+ },
310
+ gltf
311
+ });
207
312
  const { scale = 1 } = material.normalTexture;
208
313
  parsedMaterial.uniforms.normalScale = scale;
209
314
  }
210
315
  if (material.occlusionTexture) {
211
- addTexture(device, material.occlusionTexture, "pbr_occlusionSampler", "HAS_OCCLUSIONMAP", parsedMaterial);
316
+ addTexture(device, material.occlusionTexture, "pbr_occlusionSampler", parsedMaterial, {
317
+ featureOptions: {
318
+ define: "HAS_OCCLUSIONMAP",
319
+ enabledUniformName: "occlusionMapEnabled"
320
+ },
321
+ gltf
322
+ });
212
323
  const { strength = 1 } = material.occlusionTexture;
213
324
  parsedMaterial.uniforms.occlusionStrength = strength;
214
325
  }
326
+ parsedMaterial.uniforms.emissiveFactor = material.emissiveFactor || [0, 0, 0];
215
327
  if (material.emissiveTexture) {
216
- addTexture(device, material.emissiveTexture, "pbr_emissiveSampler", "HAS_EMISSIVEMAP", parsedMaterial);
217
- parsedMaterial.uniforms.emissiveFactor = material.emissiveFactor || [0, 0, 0];
328
+ addTexture(device, material.emissiveTexture, "pbr_emissiveSampler", parsedMaterial, {
329
+ featureOptions: {
330
+ define: "HAS_EMISSIVEMAP",
331
+ enabledUniformName: "emissiveMapEnabled"
332
+ },
333
+ gltf
334
+ });
218
335
  }
219
- switch (material.alphaMode || "MASK") {
220
- case "MASK":
336
+ parseMaterialExtensions(device, material.extensions, parsedMaterial, gltf);
337
+ switch (material.alphaMode || "OPAQUE") {
338
+ case "OPAQUE":
339
+ break;
340
+ case "MASK": {
221
341
  const { alphaCutoff = 0.5 } = material;
222
342
  parsedMaterial.defines["ALPHA_CUTOFF"] = true;
343
+ parsedMaterial.uniforms.alphaCutoffEnabled = true;
223
344
  parsedMaterial.uniforms.alphaCutoff = alphaCutoff;
224
345
  break;
346
+ }
225
347
  case "BLEND":
226
348
  import_core.log.warn("glTF BLEND alphaMode might not work well because it requires mesh sorting")();
227
- parsedMaterial.parameters.blend = true;
228
- parsedMaterial.parameters.blendColorOperation = "add";
229
- parsedMaterial.parameters.blendColorSrcFactor = "src-alpha";
230
- parsedMaterial.parameters.blendColorDstFactor = "one-minus-src-alpha";
231
- parsedMaterial.parameters.blendAlphaOperation = "add";
232
- parsedMaterial.parameters.blendAlphaSrcFactor = "one";
233
- parsedMaterial.parameters.blendAlphaDstFactor = "one-minus-src-alpha";
234
- parsedMaterial.glParameters["blend"] = true;
235
- parsedMaterial.glParameters["blendEquation"] = 32774;
236
- parsedMaterial.glParameters["blendFunc"] = [
237
- 770,
238
- 771,
239
- 1,
240
- 771
241
- ];
349
+ applyAlphaBlendParameters(parsedMaterial);
242
350
  break;
243
351
  }
244
352
  }
245
- function parsePbrMetallicRoughness(device, pbrMetallicRoughness, parsedMaterial) {
353
+ function applyAlphaBlendParameters(parsedMaterial) {
354
+ parsedMaterial.parameters.blend = true;
355
+ parsedMaterial.parameters.blendColorOperation = "add";
356
+ parsedMaterial.parameters.blendColorSrcFactor = "src-alpha";
357
+ parsedMaterial.parameters.blendColorDstFactor = "one-minus-src-alpha";
358
+ parsedMaterial.parameters.blendAlphaOperation = "add";
359
+ parsedMaterial.parameters.blendAlphaSrcFactor = "one";
360
+ parsedMaterial.parameters.blendAlphaDstFactor = "one-minus-src-alpha";
361
+ parsedMaterial.glParameters["blend"] = true;
362
+ parsedMaterial.glParameters["blendEquation"] = GLEnum.FUNC_ADD;
363
+ parsedMaterial.glParameters["blendFunc"] = [
364
+ GLEnum.SRC_ALPHA,
365
+ GLEnum.ONE_MINUS_SRC_ALPHA,
366
+ GLEnum.ONE,
367
+ GLEnum.ONE_MINUS_SRC_ALPHA
368
+ ];
369
+ }
370
+ function applyTransmissionBlendApproximation(parsedMaterial) {
371
+ parsedMaterial.parameters.blend = true;
372
+ parsedMaterial.parameters.depthWriteEnabled = false;
373
+ parsedMaterial.parameters.blendColorOperation = "add";
374
+ parsedMaterial.parameters.blendColorSrcFactor = "one";
375
+ parsedMaterial.parameters.blendColorDstFactor = "one-minus-src-alpha";
376
+ parsedMaterial.parameters.blendAlphaOperation = "add";
377
+ parsedMaterial.parameters.blendAlphaSrcFactor = "one";
378
+ parsedMaterial.parameters.blendAlphaDstFactor = "one-minus-src-alpha";
379
+ parsedMaterial.glParameters["blend"] = true;
380
+ parsedMaterial.glParameters["depthMask"] = false;
381
+ parsedMaterial.glParameters["blendEquation"] = GLEnum.FUNC_ADD;
382
+ parsedMaterial.glParameters["blendFunc"] = [
383
+ GLEnum.ONE,
384
+ GLEnum.ONE_MINUS_SRC_ALPHA,
385
+ GLEnum.ONE,
386
+ GLEnum.ONE_MINUS_SRC_ALPHA
387
+ ];
388
+ }
389
+ function parsePbrMetallicRoughness(device, pbrMetallicRoughness, parsedMaterial, gltf) {
246
390
  if (pbrMetallicRoughness.baseColorTexture) {
247
- addTexture(device, pbrMetallicRoughness.baseColorTexture, "pbr_baseColorSampler", "HAS_BASECOLORMAP", parsedMaterial);
391
+ addTexture(device, pbrMetallicRoughness.baseColorTexture, "pbr_baseColorSampler", parsedMaterial, {
392
+ featureOptions: {
393
+ define: "HAS_BASECOLORMAP",
394
+ enabledUniformName: "baseColorMapEnabled"
395
+ },
396
+ gltf
397
+ });
248
398
  }
249
399
  parsedMaterial.uniforms.baseColorFactor = pbrMetallicRoughness.baseColorFactor || [1, 1, 1, 1];
250
400
  if (pbrMetallicRoughness.metallicRoughnessTexture) {
251
- addTexture(device, pbrMetallicRoughness.metallicRoughnessTexture, "pbr_metallicRoughnessSampler", "HAS_METALROUGHNESSMAP", parsedMaterial);
401
+ addTexture(device, pbrMetallicRoughness.metallicRoughnessTexture, "pbr_metallicRoughnessSampler", parsedMaterial, {
402
+ featureOptions: {
403
+ define: "HAS_METALROUGHNESSMAP",
404
+ enabledUniformName: "metallicRoughnessMapEnabled"
405
+ },
406
+ gltf
407
+ });
252
408
  }
253
409
  const { metallicFactor = 1, roughnessFactor = 1 } = pbrMetallicRoughness;
254
410
  parsedMaterial.uniforms.metallicRoughnessValues = [metallicFactor, roughnessFactor];
255
411
  }
256
- function addTexture(device, gltfTexture, uniformName, define, parsedMaterial) {
257
- var _a;
258
- const image = gltfTexture.texture.source.image;
259
- let textureOptions;
260
- if (image.compressed) {
261
- textureOptions = image;
262
- } else {
263
- textureOptions = { data: image };
412
+ function parseMaterialExtensions(device, extensions, parsedMaterial, gltf) {
413
+ if (!extensions) {
414
+ return;
415
+ }
416
+ if (hasMaterialExtensionShading(extensions)) {
417
+ parsedMaterial.defines["USE_MATERIAL_EXTENSIONS"] = true;
418
+ }
419
+ parseSpecularExtension(device, extensions.KHR_materials_specular, parsedMaterial, gltf);
420
+ parseIorExtension(extensions.KHR_materials_ior, parsedMaterial);
421
+ parseTransmissionExtension(device, extensions.KHR_materials_transmission, parsedMaterial, gltf);
422
+ parseVolumeExtension(device, extensions.KHR_materials_volume, parsedMaterial, gltf);
423
+ parseClearcoatExtension(device, extensions.KHR_materials_clearcoat, parsedMaterial, gltf);
424
+ parseSheenExtension(device, extensions.KHR_materials_sheen, parsedMaterial, gltf);
425
+ parseIridescenceExtension(device, extensions.KHR_materials_iridescence, parsedMaterial, gltf);
426
+ parseAnisotropyExtension(device, extensions.KHR_materials_anisotropy, parsedMaterial, gltf);
427
+ parseEmissiveStrengthExtension(extensions.KHR_materials_emissive_strength, parsedMaterial);
428
+ }
429
+ function hasMaterialExtensionShading(extensions) {
430
+ return Boolean(extensions.KHR_materials_specular || extensions.KHR_materials_ior || extensions.KHR_materials_transmission || extensions.KHR_materials_volume || extensions.KHR_materials_clearcoat || extensions.KHR_materials_sheen || extensions.KHR_materials_iridescence || extensions.KHR_materials_anisotropy);
431
+ }
432
+ function parseSpecularExtension(device, extension, parsedMaterial, gltf) {
433
+ if (!extension) {
434
+ return;
435
+ }
436
+ if (extension.specularColorFactor) {
437
+ parsedMaterial.uniforms.specularColorFactor = extension.specularColorFactor;
438
+ }
439
+ if (extension.specularFactor !== void 0) {
440
+ parsedMaterial.uniforms.specularIntensityFactor = extension.specularFactor;
441
+ }
442
+ if (extension.specularColorTexture) {
443
+ addTexture(device, extension.specularColorTexture, "pbr_specularColorSampler", parsedMaterial, {
444
+ featureOptions: {
445
+ define: "HAS_SPECULARCOLORMAP",
446
+ enabledUniformName: "specularColorMapEnabled"
447
+ },
448
+ gltf
449
+ });
450
+ }
451
+ if (extension.specularTexture) {
452
+ addTexture(device, extension.specularTexture, "pbr_specularIntensitySampler", parsedMaterial, {
453
+ featureOptions: {
454
+ define: "HAS_SPECULARINTENSITYMAP",
455
+ enabledUniformName: "specularIntensityMapEnabled"
456
+ },
457
+ gltf
458
+ });
459
+ }
460
+ }
461
+ function parseIorExtension(extension, parsedMaterial) {
462
+ if ((extension == null ? void 0 : extension.ior) !== void 0) {
463
+ parsedMaterial.uniforms.ior = extension.ior;
464
+ }
465
+ }
466
+ function parseTransmissionExtension(device, extension, parsedMaterial, gltf) {
467
+ if (!extension) {
468
+ return;
469
+ }
470
+ if (extension.transmissionFactor !== void 0) {
471
+ parsedMaterial.uniforms.transmissionFactor = extension.transmissionFactor;
472
+ }
473
+ if (extension.transmissionTexture) {
474
+ addTexture(device, extension.transmissionTexture, "pbr_transmissionSampler", parsedMaterial, {
475
+ featureOptions: {
476
+ define: "HAS_TRANSMISSIONMAP",
477
+ enabledUniformName: "transmissionMapEnabled"
478
+ },
479
+ gltf
480
+ });
481
+ }
482
+ if ((extension.transmissionFactor ?? 0) > 0 || extension.transmissionTexture) {
483
+ import_core.log.warn("KHR_materials_transmission uses a premultiplied-alpha blending approximation and may require mesh sorting")();
484
+ applyTransmissionBlendApproximation(parsedMaterial);
485
+ }
486
+ }
487
+ function parseVolumeExtension(device, extension, parsedMaterial, gltf) {
488
+ if (!extension) {
489
+ return;
490
+ }
491
+ if (extension.thicknessFactor !== void 0) {
492
+ parsedMaterial.uniforms.thicknessFactor = extension.thicknessFactor;
493
+ }
494
+ if (extension.thicknessTexture) {
495
+ addTexture(device, extension.thicknessTexture, "pbr_thicknessSampler", parsedMaterial, {
496
+ featureOptions: {
497
+ define: "HAS_THICKNESSMAP"
498
+ },
499
+ gltf
500
+ });
501
+ }
502
+ if (extension.attenuationDistance !== void 0) {
503
+ parsedMaterial.uniforms.attenuationDistance = extension.attenuationDistance;
504
+ }
505
+ if (extension.attenuationColor) {
506
+ parsedMaterial.uniforms.attenuationColor = extension.attenuationColor;
507
+ }
508
+ }
509
+ function parseClearcoatExtension(device, extension, parsedMaterial, gltf) {
510
+ if (!extension) {
511
+ return;
512
+ }
513
+ if (extension.clearcoatFactor !== void 0) {
514
+ parsedMaterial.uniforms.clearcoatFactor = extension.clearcoatFactor;
515
+ }
516
+ if (extension.clearcoatRoughnessFactor !== void 0) {
517
+ parsedMaterial.uniforms.clearcoatRoughnessFactor = extension.clearcoatRoughnessFactor;
518
+ }
519
+ if (extension.clearcoatTexture) {
520
+ addTexture(device, extension.clearcoatTexture, "pbr_clearcoatSampler", parsedMaterial, {
521
+ featureOptions: {
522
+ define: "HAS_CLEARCOATMAP",
523
+ enabledUniformName: "clearcoatMapEnabled"
524
+ },
525
+ gltf
526
+ });
527
+ }
528
+ if (extension.clearcoatRoughnessTexture) {
529
+ addTexture(device, extension.clearcoatRoughnessTexture, "pbr_clearcoatRoughnessSampler", parsedMaterial, {
530
+ featureOptions: {
531
+ define: "HAS_CLEARCOATROUGHNESSMAP",
532
+ enabledUniformName: "clearcoatRoughnessMapEnabled"
533
+ },
534
+ gltf
535
+ });
536
+ }
537
+ if (extension.clearcoatNormalTexture) {
538
+ addTexture(device, extension.clearcoatNormalTexture, "pbr_clearcoatNormalSampler", parsedMaterial, {
539
+ featureOptions: {
540
+ define: "HAS_CLEARCOATNORMALMAP"
541
+ },
542
+ gltf
543
+ });
544
+ }
545
+ }
546
+ function parseSheenExtension(device, extension, parsedMaterial, gltf) {
547
+ if (!extension) {
548
+ return;
549
+ }
550
+ if (extension.sheenColorFactor) {
551
+ parsedMaterial.uniforms.sheenColorFactor = extension.sheenColorFactor;
552
+ }
553
+ if (extension.sheenRoughnessFactor !== void 0) {
554
+ parsedMaterial.uniforms.sheenRoughnessFactor = extension.sheenRoughnessFactor;
555
+ }
556
+ if (extension.sheenColorTexture) {
557
+ addTexture(device, extension.sheenColorTexture, "pbr_sheenColorSampler", parsedMaterial, {
558
+ featureOptions: {
559
+ define: "HAS_SHEENCOLORMAP",
560
+ enabledUniformName: "sheenColorMapEnabled"
561
+ },
562
+ gltf
563
+ });
564
+ }
565
+ if (extension.sheenRoughnessTexture) {
566
+ addTexture(device, extension.sheenRoughnessTexture, "pbr_sheenRoughnessSampler", parsedMaterial, {
567
+ featureOptions: {
568
+ define: "HAS_SHEENROUGHNESSMAP",
569
+ enabledUniformName: "sheenRoughnessMapEnabled"
570
+ },
571
+ gltf
572
+ });
573
+ }
574
+ }
575
+ function parseIridescenceExtension(device, extension, parsedMaterial, gltf) {
576
+ if (!extension) {
577
+ return;
578
+ }
579
+ if (extension.iridescenceFactor !== void 0) {
580
+ parsedMaterial.uniforms.iridescenceFactor = extension.iridescenceFactor;
581
+ }
582
+ if (extension.iridescenceIor !== void 0) {
583
+ parsedMaterial.uniforms.iridescenceIor = extension.iridescenceIor;
584
+ }
585
+ if (extension.iridescenceThicknessMinimum !== void 0 || extension.iridescenceThicknessMaximum !== void 0) {
586
+ parsedMaterial.uniforms.iridescenceThicknessRange = [
587
+ extension.iridescenceThicknessMinimum ?? 100,
588
+ extension.iridescenceThicknessMaximum ?? 400
589
+ ];
590
+ }
591
+ if (extension.iridescenceTexture) {
592
+ addTexture(device, extension.iridescenceTexture, "pbr_iridescenceSampler", parsedMaterial, {
593
+ featureOptions: {
594
+ define: "HAS_IRIDESCENCEMAP",
595
+ enabledUniformName: "iridescenceMapEnabled"
596
+ },
597
+ gltf
598
+ });
599
+ }
600
+ if (extension.iridescenceThicknessTexture) {
601
+ addTexture(device, extension.iridescenceThicknessTexture, "pbr_iridescenceThicknessSampler", parsedMaterial, {
602
+ featureOptions: {
603
+ define: "HAS_IRIDESCENCETHICKNESSMAP"
604
+ },
605
+ gltf
606
+ });
607
+ }
608
+ }
609
+ function parseAnisotropyExtension(device, extension, parsedMaterial, gltf) {
610
+ if (!extension) {
611
+ return;
612
+ }
613
+ if (extension.anisotropyStrength !== void 0) {
614
+ parsedMaterial.uniforms.anisotropyStrength = extension.anisotropyStrength;
615
+ }
616
+ if (extension.anisotropyRotation !== void 0) {
617
+ parsedMaterial.uniforms.anisotropyRotation = extension.anisotropyRotation;
618
+ }
619
+ if (extension.anisotropyTexture) {
620
+ addTexture(device, extension.anisotropyTexture, "pbr_anisotropySampler", parsedMaterial, {
621
+ featureOptions: {
622
+ define: "HAS_ANISOTROPYMAP",
623
+ enabledUniformName: "anisotropyMapEnabled"
624
+ },
625
+ gltf
626
+ });
627
+ }
628
+ }
629
+ function parseEmissiveStrengthExtension(extension, parsedMaterial) {
630
+ if ((extension == null ? void 0 : extension.emissiveStrength) !== void 0) {
631
+ parsedMaterial.uniforms.emissiveStrength = extension.emissiveStrength;
632
+ }
633
+ }
634
+ function addTexture(device, gltfTexture, uniformName, parsedMaterial, textureParseOptions = {}) {
635
+ var _a, _b, _c;
636
+ const { featureOptions = {}, gltf } = textureParseOptions;
637
+ const { define, enabledUniformName } = featureOptions;
638
+ const resolvedTextureInfo = resolveTextureInfo(gltfTexture, gltf);
639
+ const image = (_b = (_a = resolvedTextureInfo.texture) == null ? void 0 : _a.source) == null ? void 0 : _b.image;
640
+ if (!image) {
641
+ import_core.log.warn(`Skipping unresolved glTF texture for ${String(uniformName)}`)();
642
+ return;
264
643
  }
265
644
  const gltfSampler = {
266
645
  wrapS: 10497,
@@ -271,49 +650,183 @@ function addTexture(device, gltfTexture, uniformName, define, parsedMaterial) {
271
650
  // default LINEAR filtering
272
651
  magFilter: 9729,
273
652
  // default LINEAR filtering
274
- ...(_a = gltfTexture == null ? void 0 : gltfTexture.texture) == null ? void 0 : _a.sampler
653
+ ...(_c = resolvedTextureInfo == null ? void 0 : resolvedTextureInfo.texture) == null ? void 0 : _c.sampler
275
654
  };
276
- const texture = device.createTexture({
277
- id: gltfTexture.uniformName || gltfTexture.id,
278
- sampler: convertSampler(gltfSampler),
279
- ...textureOptions
280
- });
655
+ const baseOptions = {
656
+ id: resolvedTextureInfo.uniformName || resolvedTextureInfo.id,
657
+ sampler: convertSampler(gltfSampler)
658
+ };
659
+ let texture;
660
+ if (image.compressed) {
661
+ texture = createCompressedTexture(device, image, baseOptions);
662
+ } else {
663
+ const { width, height } = device.getExternalImageSize(image);
664
+ texture = device.createTexture({
665
+ ...baseOptions,
666
+ width,
667
+ height,
668
+ data: image
669
+ });
670
+ }
281
671
  parsedMaterial.bindings[uniformName] = texture;
282
672
  if (define)
283
673
  parsedMaterial.defines[define] = true;
674
+ if (enabledUniformName) {
675
+ parsedMaterial.uniforms[enabledUniformName] = true;
676
+ }
284
677
  parsedMaterial.generatedTextures.push(texture);
285
678
  }
679
+ function resolveTextureInfo(gltfTexture, gltf) {
680
+ if (gltfTexture.texture || gltfTexture.index === void 0 || !(gltf == null ? void 0 : gltf.textures)) {
681
+ return gltfTexture;
682
+ }
683
+ const resolvedTextureEntry = gltf.textures[gltfTexture.index];
684
+ if (!resolvedTextureEntry) {
685
+ return gltfTexture;
686
+ }
687
+ if ("texture" in resolvedTextureEntry && resolvedTextureEntry.texture) {
688
+ return {
689
+ ...resolvedTextureEntry,
690
+ ...gltfTexture,
691
+ texture: resolvedTextureEntry.texture
692
+ };
693
+ }
694
+ if (!("source" in resolvedTextureEntry)) {
695
+ return gltfTexture;
696
+ }
697
+ return {
698
+ ...gltfTexture,
699
+ texture: resolvedTextureEntry
700
+ };
701
+ }
702
+ function createCompressedTextureFallback(device, baseOptions) {
703
+ return device.createTexture({
704
+ ...baseOptions,
705
+ format: "rgba8unorm",
706
+ width: 1,
707
+ height: 1,
708
+ mipLevels: 1
709
+ });
710
+ }
711
+ function resolveCompressedTextureFormat(level) {
712
+ return level.textureFormat;
713
+ }
714
+ function getMaxCompressedMipLevels(baseWidth, baseHeight, format) {
715
+ const { blockWidth = 1, blockHeight = 1 } = import_core.textureFormatDecoder.getInfo(format);
716
+ let count = 1;
717
+ for (let i = 1; ; i++) {
718
+ const w = Math.max(1, baseWidth >> i);
719
+ const h = Math.max(1, baseHeight >> i);
720
+ if (w < blockWidth || h < blockHeight)
721
+ break;
722
+ count++;
723
+ }
724
+ return count;
725
+ }
726
+ function createCompressedTexture(device, image, baseOptions) {
727
+ var _a, _b;
728
+ let levels;
729
+ if (Array.isArray(image.data) && ((_a = image.data[0]) == null ? void 0 : _a.data)) {
730
+ levels = image.data;
731
+ } else if ("mipmaps" in image && Array.isArray(image.mipmaps)) {
732
+ levels = image.mipmaps;
733
+ } else {
734
+ levels = [];
735
+ }
736
+ if (levels.length === 0 || !((_b = levels[0]) == null ? void 0 : _b.data)) {
737
+ import_core.log.warn("createCompressedTexture: compressed image has no valid mip levels, creating fallback")();
738
+ return createCompressedTextureFallback(device, baseOptions);
739
+ }
740
+ const baseLevel = levels[0];
741
+ const baseWidth = baseLevel.width ?? image.width ?? 0;
742
+ const baseHeight = baseLevel.height ?? image.height ?? 0;
743
+ if (baseWidth <= 0 || baseHeight <= 0) {
744
+ import_core.log.warn("createCompressedTexture: base level has invalid dimensions, creating fallback")();
745
+ return createCompressedTextureFallback(device, baseOptions);
746
+ }
747
+ const format = resolveCompressedTextureFormat(baseLevel);
748
+ if (!format) {
749
+ import_core.log.warn("createCompressedTexture: compressed image has no textureFormat, creating fallback")();
750
+ return createCompressedTextureFallback(device, baseOptions);
751
+ }
752
+ const maxMipLevels = getMaxCompressedMipLevels(baseWidth, baseHeight, format);
753
+ const levelLimit = Math.min(levels.length, maxMipLevels);
754
+ let validLevelCount = 1;
755
+ for (let i = 1; i < levelLimit; i++) {
756
+ const level = levels[i];
757
+ if (!level.data || level.width <= 0 || level.height <= 0) {
758
+ import_core.log.warn(`createCompressedTexture: mip level ${i} has invalid data/dimensions, truncating`)();
759
+ break;
760
+ }
761
+ const levelFormat = resolveCompressedTextureFormat(level);
762
+ if (levelFormat && levelFormat !== format) {
763
+ import_core.log.warn(`createCompressedTexture: mip level ${i} format '${levelFormat}' differs from base '${format}', truncating`)();
764
+ break;
765
+ }
766
+ const expectedW = Math.max(1, baseWidth >> i);
767
+ const expectedH = Math.max(1, baseHeight >> i);
768
+ if (level.width !== expectedW || level.height !== expectedH) {
769
+ import_core.log.warn(`createCompressedTexture: mip level ${i} dimensions ${level.width}x${level.height} don't match expected ${expectedW}x${expectedH}, truncating`)();
770
+ break;
771
+ }
772
+ validLevelCount++;
773
+ }
774
+ const texture = device.createTexture({
775
+ ...baseOptions,
776
+ format,
777
+ usage: import_core.Texture.TEXTURE | import_core.Texture.COPY_DST,
778
+ width: baseWidth,
779
+ height: baseHeight,
780
+ mipLevels: validLevelCount,
781
+ data: baseLevel.data
782
+ });
783
+ for (let i = 1; i < validLevelCount; i++) {
784
+ texture.writeData(levels[i].data, {
785
+ width: levels[i].width,
786
+ height: levels[i].height,
787
+ mipLevel: i
788
+ });
789
+ }
790
+ return texture;
791
+ }
286
792
 
287
793
  // dist/parsers/parse-gltf-lights.js
288
794
  var import_core2 = require("@math.gl/core");
795
+ var GLTF_COLOR_FACTOR = 255;
289
796
  function parseGLTFLights(gltf) {
290
- var _a, _b, _c;
291
- const lightDefs = (_b = (_a = gltf.extensions) == null ? void 0 : _a["KHR_lights_punctual"]) == null ? void 0 : _b["lights"];
797
+ var _a, _b, _c, _d;
798
+ const lightDefs = (
799
+ // `postProcessGLTF()` moves KHR_lights_punctual into `gltf.lights`.
800
+ gltf.lights || ((_b = (_a = gltf.extensions) == null ? void 0 : _a["KHR_lights_punctual"]) == null ? void 0 : _b["lights"])
801
+ );
292
802
  if (!lightDefs || !Array.isArray(lightDefs) || lightDefs.length === 0) {
293
803
  return [];
294
804
  }
295
805
  const lights = [];
806
+ const parentNodeById = createParentNodeMap(gltf.nodes || []);
807
+ const worldMatrixByNodeId = /* @__PURE__ */ new Map();
296
808
  for (const node of gltf.nodes || []) {
297
- const nodeLight = (_c = node.extensions) == null ? void 0 : _c.KHR_lights_punctual;
298
- if (!nodeLight || typeof nodeLight.light !== "number") {
809
+ const lightIndex = node.light ?? ((_d = (_c = node.extensions) == null ? void 0 : _c.KHR_lights_punctual) == null ? void 0 : _d.light);
810
+ if (typeof lightIndex !== "number") {
299
811
  continue;
300
812
  }
301
- const gltfLight = lightDefs[nodeLight.light];
813
+ const gltfLight = lightDefs[lightIndex];
302
814
  if (!gltfLight) {
303
815
  continue;
304
816
  }
305
- const color = gltfLight.color || [1, 1, 1];
817
+ const color = normalizeGLTFLightColor(gltfLight.color || [1, 1, 1]);
306
818
  const intensity = gltfLight.intensity ?? 1;
307
819
  const range = gltfLight.range;
820
+ const worldMatrix = getNodeWorldMatrix(node, parentNodeById, worldMatrixByNodeId);
308
821
  switch (gltfLight.type) {
309
822
  case "directional":
310
- lights.push(parseDirectionalLight(node, color, intensity));
823
+ lights.push(parseDirectionalLight(worldMatrix, color, intensity));
311
824
  break;
312
825
  case "point":
313
- lights.push(parsePointLight(node, color, intensity, range));
826
+ lights.push(parsePointLight(worldMatrix, color, intensity, range));
314
827
  break;
315
828
  case "spot":
316
- lights.push(parsePointLight(node, color, intensity, range));
829
+ lights.push(parseSpotLight(worldMatrix, color, intensity, range, gltfLight.spot));
317
830
  break;
318
831
  default:
319
832
  break;
@@ -321,8 +834,11 @@ function parseGLTFLights(gltf) {
321
834
  }
322
835
  return lights;
323
836
  }
324
- function parsePointLight(node, color, intensity, range) {
325
- const position = node.translation ? [...node.translation] : [0, 0, 0];
837
+ function normalizeGLTFLightColor(color) {
838
+ return color.map((component) => component * GLTF_COLOR_FACTOR);
839
+ }
840
+ function parsePointLight(worldMatrix, color, intensity, range) {
841
+ const position = getLightPosition(worldMatrix);
326
842
  let attenuation = [1, 0, 0];
327
843
  if (range !== void 0 && range > 0) {
328
844
  attenuation = [1, 0, 1 / (range * range)];
@@ -335,12 +851,8 @@ function parsePointLight(node, color, intensity, range) {
335
851
  attenuation
336
852
  };
337
853
  }
338
- function parseDirectionalLight(node, color, intensity) {
339
- let direction = [0, 0, -1];
340
- if (node.rotation) {
341
- const orientation = new import_core2.Matrix4().fromQuaternion(node.rotation);
342
- direction = orientation.transformDirection([0, 0, -1]);
343
- }
854
+ function parseDirectionalLight(worldMatrix, color, intensity) {
855
+ const direction = getLightDirection(worldMatrix);
344
856
  return {
345
857
  type: "directional",
346
858
  direction,
@@ -348,22 +860,72 @@ function parseDirectionalLight(node, color, intensity) {
348
860
  intensity
349
861
  };
350
862
  }
863
+ function parseSpotLight(worldMatrix, color, intensity, range, spot = {}) {
864
+ const position = getLightPosition(worldMatrix);
865
+ const direction = getLightDirection(worldMatrix);
866
+ let attenuation = [1, 0, 0];
867
+ if (range !== void 0 && range > 0) {
868
+ attenuation = [1, 0, 1 / (range * range)];
869
+ }
870
+ return {
871
+ type: "spot",
872
+ position,
873
+ direction,
874
+ color,
875
+ intensity,
876
+ attenuation,
877
+ innerConeAngle: spot.innerConeAngle ?? 0,
878
+ outerConeAngle: spot.outerConeAngle ?? Math.PI / 4
879
+ };
880
+ }
881
+ function createParentNodeMap(nodes) {
882
+ const parentNodeById = /* @__PURE__ */ new Map();
883
+ for (const node of nodes) {
884
+ for (const childNode of node.children || []) {
885
+ parentNodeById.set(childNode.id, node);
886
+ }
887
+ }
888
+ return parentNodeById;
889
+ }
890
+ function getNodeWorldMatrix(node, parentNodeById, worldMatrixByNodeId) {
891
+ const cachedWorldMatrix = worldMatrixByNodeId.get(node.id);
892
+ if (cachedWorldMatrix) {
893
+ return cachedWorldMatrix;
894
+ }
895
+ const localMatrix = getNodeLocalMatrix(node);
896
+ const parentNode = parentNodeById.get(node.id);
897
+ const worldMatrix = parentNode ? new import_core2.Matrix4(getNodeWorldMatrix(parentNode, parentNodeById, worldMatrixByNodeId)).multiplyRight(localMatrix) : localMatrix;
898
+ worldMatrixByNodeId.set(node.id, worldMatrix);
899
+ return worldMatrix;
900
+ }
901
+ function getNodeLocalMatrix(node) {
902
+ if (node.matrix) {
903
+ return new import_core2.Matrix4(node.matrix);
904
+ }
905
+ const matrix = new import_core2.Matrix4();
906
+ if (node.translation) {
907
+ matrix.translate(node.translation);
908
+ }
909
+ if (node.rotation) {
910
+ matrix.multiplyRight(new import_core2.Matrix4().fromQuaternion(node.rotation));
911
+ }
912
+ if (node.scale) {
913
+ matrix.scale(node.scale);
914
+ }
915
+ return matrix;
916
+ }
917
+ function getLightPosition(worldMatrix) {
918
+ return worldMatrix.transformAsPoint([0, 0, 0]);
919
+ }
920
+ function getLightDirection(worldMatrix) {
921
+ return worldMatrix.transformDirection([0, 0, -1]);
922
+ }
351
923
 
352
924
  // dist/parsers/parse-gltf.js
353
- var import_engine3 = require("@luma.gl/engine");
354
- var import_core4 = require("@math.gl/core");
925
+ var import_engine4 = require("@luma.gl/engine");
926
+ var import_shadertools2 = require("@luma.gl/shadertools");
355
927
 
356
928
  // dist/webgl-to-webgpu/convert-webgl-topology.js
357
- var GLEnum;
358
- (function(GLEnum2) {
359
- GLEnum2[GLEnum2["POINTS"] = 0] = "POINTS";
360
- GLEnum2[GLEnum2["LINES"] = 1] = "LINES";
361
- GLEnum2[GLEnum2["LINE_LOOP"] = 2] = "LINE_LOOP";
362
- GLEnum2[GLEnum2["LINE_STRIP"] = 3] = "LINE_STRIP";
363
- GLEnum2[GLEnum2["TRIANGLES"] = 4] = "TRIANGLES";
364
- GLEnum2[GLEnum2["TRIANGLE_STRIP"] = 5] = "TRIANGLE_STRIP";
365
- GLEnum2[GLEnum2["TRIANGLE_FAN"] = 6] = "TRIANGLE_FAN";
366
- })(GLEnum || (GLEnum = {}));
367
929
  function convertGLDrawModeToTopology(drawMode) {
368
930
  switch (drawMode) {
369
931
  case GLEnum.POINTS:
@@ -383,55 +945,94 @@ function convertGLDrawModeToTopology(drawMode) {
383
945
 
384
946
  // dist/gltf/create-gltf-model.js
385
947
  var import_core3 = require("@luma.gl/core");
386
- var import_shadertools = require("@luma.gl/shadertools");
387
948
  var import_engine2 = require("@luma.gl/engine");
949
+ var import_shadertools = require("@luma.gl/shadertools");
950
+ var import_engine3 = require("@luma.gl/engine");
388
951
  var SHADER = (
389
952
  /* WGSL */
390
953
  `
391
- layout(0) positions: vec4; // in vec4 POSITION;
392
-
393
- #ifdef HAS_NORMALS
394
- in vec4 normals; // in vec4 NORMAL;
395
- #endif
396
-
397
- #ifdef HAS_TANGENTS
398
- in vec4 TANGENT;
399
- #endif
954
+ struct VertexInputs {
955
+ @location(0) positions: vec3f,
956
+ #ifdef HAS_NORMALS
957
+ @location(1) normals: vec3f,
958
+ #endif
959
+ #ifdef HAS_TANGENTS
960
+ @location(2) TANGENT: vec4f,
961
+ #endif
962
+ #ifdef HAS_UV
963
+ @location(3) texCoords: vec2f,
964
+ #endif
965
+ #ifdef HAS_SKIN
966
+ @location(4) JOINTS_0: vec4u,
967
+ @location(5) WEIGHTS_0: vec4f,
968
+ #endif
969
+ };
400
970
 
401
- #ifdef HAS_UV
402
- // in vec2 TEXCOORD_0;
403
- in vec2 texCoords;
404
- #endif
971
+ struct FragmentInputs {
972
+ @builtin(position) position: vec4f,
973
+ @location(0) pbrPosition: vec3f,
974
+ @location(1) pbrUV: vec2f,
975
+ @location(2) pbrNormal: vec3f,
976
+ #ifdef HAS_TANGENTS
977
+ @location(3) pbrTangent: vec4f,
978
+ #endif
979
+ };
405
980
 
406
981
  @vertex
407
- void main(void) {
408
- vec4 _NORMAL = vec4(0.);
409
- vec4 _TANGENT = vec4(0.);
410
- vec2 _TEXCOORD_0 = vec2(0.);
982
+ fn vertexMain(inputs: VertexInputs) -> FragmentInputs {
983
+ var outputs: FragmentInputs;
984
+ var position = vec4f(inputs.positions, 1.0);
985
+ var normal = vec3f(0.0, 0.0, 1.0);
986
+ var tangent = vec4f(1.0, 0.0, 0.0, 1.0);
987
+ var uv = vec2f(0.0, 0.0);
411
988
 
412
- #ifdef HAS_NORMALS
413
- _NORMAL = normals;
414
- #endif
989
+ #ifdef HAS_NORMALS
990
+ normal = inputs.normals;
991
+ #endif
992
+ #ifdef HAS_UV
993
+ uv = inputs.texCoords;
994
+ #endif
995
+ #ifdef HAS_TANGENTS
996
+ tangent = inputs.TANGENT;
997
+ #endif
998
+ #ifdef HAS_SKIN
999
+ let skinMatrix = getSkinMatrix(inputs.WEIGHTS_0, inputs.JOINTS_0);
1000
+ position = skinMatrix * position;
1001
+ normal = normalize((skinMatrix * vec4f(normal, 0.0)).xyz);
1002
+ #ifdef HAS_TANGENTS
1003
+ tangent = vec4f(normalize((skinMatrix * vec4f(tangent.xyz, 0.0)).xyz), tangent.w);
1004
+ #endif
1005
+ #endif
415
1006
 
416
- #ifdef HAS_TANGENTS
417
- _TANGENT = TANGENT;
418
- #endif
1007
+ let worldPosition = pbrProjection.modelMatrix * position;
419
1008
 
420
- #ifdef HAS_UV
421
- _TEXCOORD_0 = texCoords;
422
- #endif
1009
+ #ifdef HAS_NORMALS
1010
+ normal = normalize((pbrProjection.normalMatrix * vec4f(normal, 0.0)).xyz);
1011
+ #endif
1012
+ #ifdef HAS_TANGENTS
1013
+ let worldTangent = normalize((pbrProjection.modelMatrix * vec4f(tangent.xyz, 0.0)).xyz);
1014
+ outputs.pbrTangent = vec4f(worldTangent, tangent.w);
1015
+ #endif
423
1016
 
424
- pbr_setPositionNormalTangentUV(positions, _NORMAL, _TANGENT, _TEXCOORD_0);
425
- gl_Position = u_MVPMatrix * positions;
426
- }
1017
+ outputs.position = pbrProjection.modelViewProjectionMatrix * position;
1018
+ outputs.pbrPosition = worldPosition.xyz / worldPosition.w;
1019
+ outputs.pbrUV = uv;
1020
+ outputs.pbrNormal = normal;
1021
+ return outputs;
1022
+ }
427
1023
 
428
1024
  @fragment
429
- out vec4 fragmentColor;
430
-
431
- void main(void) {
432
- vec3 pos = pbr_vPosition;
433
- fragmentColor = pbr_filterColor(vec4(1.0));
434
- }
1025
+ fn fragmentMain(inputs: FragmentInputs) -> @location(0) vec4f {
1026
+ fragmentInputs.pbr_vPosition = inputs.pbrPosition;
1027
+ fragmentInputs.pbr_vUV = inputs.pbrUV;
1028
+ fragmentInputs.pbr_vNormal = inputs.pbrNormal;
1029
+ #ifdef HAS_TANGENTS
1030
+ let tangent = normalize(inputs.pbrTangent.xyz);
1031
+ let bitangent = normalize(cross(inputs.pbrNormal, tangent)) * inputs.pbrTangent.w;
1032
+ fragmentInputs.pbr_vTBN = mat3x3f(tangent, bitangent, inputs.pbrNormal);
1033
+ #endif
1034
+ return pbr_filterColor(vec4f(1.0));
1035
+ }
435
1036
  `
436
1037
  );
437
1038
  var vs = (
@@ -455,6 +1056,11 @@ var vs = (
455
1056
  in vec2 texCoords;
456
1057
  #endif
457
1058
 
1059
+ #ifdef HAS_SKIN
1060
+ in uvec4 JOINTS_0;
1061
+ in vec4 WEIGHTS_0;
1062
+ #endif
1063
+
458
1064
  void main(void) {
459
1065
  vec4 _NORMAL = vec4(0.);
460
1066
  vec4 _TANGENT = vec4(0.);
@@ -472,8 +1078,17 @@ var vs = (
472
1078
  _TEXCOORD_0 = texCoords;
473
1079
  #endif
474
1080
 
475
- pbr_setPositionNormalTangentUV(positions, _NORMAL, _TANGENT, _TEXCOORD_0);
476
- gl_Position = pbrProjection.modelViewProjectionMatrix * positions;
1081
+ vec4 pos = positions;
1082
+
1083
+ #ifdef HAS_SKIN
1084
+ mat4 skinMat = getSkinMatrix(WEIGHTS_0, JOINTS_0);
1085
+ pos = skinMat * pos;
1086
+ _NORMAL = skinMat * _NORMAL;
1087
+ _TANGENT = vec4((skinMat * vec4(_TANGENT.xyz, 0.)).xyz, _TANGENT.w);
1088
+ #endif
1089
+
1090
+ pbr_setPositionNormalTangentUV(pos, _NORMAL, _TANGENT, _TEXCOORD_0);
1091
+ gl_Position = pbrProjection.modelViewProjectionMatrix * pos;
477
1092
  }
478
1093
  `
479
1094
  );
@@ -488,6 +1103,21 @@ var fs = (
488
1103
  }
489
1104
  `
490
1105
  );
1106
+ function createGLTFMaterial(device, options) {
1107
+ const materialFactory = options.materialFactory || new import_engine3.MaterialFactory(device, { modules: [import_shadertools.pbrMaterial] });
1108
+ const pbrMaterialProps = { ...options.parsedPPBRMaterial.uniforms };
1109
+ delete pbrMaterialProps.camera;
1110
+ const materialBindings = Object.fromEntries(Object.entries({
1111
+ ...pbrMaterialProps,
1112
+ ...options.parsedPPBRMaterial.bindings
1113
+ }).filter(([name, value]) => materialFactory.ownsBinding(name) && isMaterialBindingResource(value)));
1114
+ const material = materialFactory.createMaterial({
1115
+ id: options.id,
1116
+ bindings: materialBindings
1117
+ });
1118
+ material.setProps({ pbrMaterial: pbrMaterialProps });
1119
+ return material;
1120
+ }
491
1121
  function createGLTFModel(device, options) {
492
1122
  const { id, geometry, parsedPPBRMaterial, vertexCount, modelOptions = {} } = options;
493
1123
  import_core3.log.info(4, "createGLTFModel defines: ", parsedPPBRMaterial.defines)();
@@ -506,20 +1136,53 @@ function createGLTFModel(device, options) {
506
1136
  geometry,
507
1137
  topology: geometry.topology,
508
1138
  vertexCount,
509
- modules: [import_shadertools.pbrMaterial],
1139
+ modules: [import_shadertools.pbrMaterial, import_shadertools.skin],
510
1140
  ...modelOptions,
511
1141
  defines: { ...parsedPPBRMaterial.defines, ...modelOptions.defines },
512
1142
  parameters: { ...parameters, ...parsedPPBRMaterial.parameters, ...modelOptions.parameters }
513
1143
  };
514
- const model = new import_engine2.Model(device, modelProps);
515
- const { camera, ...pbrMaterialProps } = {
1144
+ const material = options.material || createGLTFMaterial(device, {
1145
+ id: id ? `${id}-material` : void 0,
1146
+ parsedPPBRMaterial
1147
+ });
1148
+ modelProps.material = material;
1149
+ const model = new import_engine3.Model(device, modelProps);
1150
+ const sceneShaderInputValues = {
516
1151
  ...parsedPPBRMaterial.uniforms,
517
1152
  ...modelOptions.uniforms,
518
1153
  ...parsedPPBRMaterial.bindings,
519
1154
  ...modelOptions.bindings
520
1155
  };
521
- model.shaderInputs.setProps({ pbrMaterial: pbrMaterialProps, pbrProjection: { camera } });
522
- return new import_engine2.ModelNode({ managedResources, model });
1156
+ const sceneShaderInputProps = getSceneShaderInputProps(model.shaderInputs.getModules(), material, sceneShaderInputValues);
1157
+ model.shaderInputs.setProps(sceneShaderInputProps);
1158
+ return new import_engine3.ModelNode({ managedResources, model });
1159
+ }
1160
+ function isMaterialBindingResource(value) {
1161
+ return value instanceof import_core3.Buffer || value instanceof import_engine2.DynamicTexture || value instanceof import_core3.Sampler || value instanceof import_core3.Texture || value instanceof import_core3.TextureView;
1162
+ }
1163
+ function getSceneShaderInputProps(modules, material, shaderInputValues) {
1164
+ const propertyToModuleNameMap = /* @__PURE__ */ new Map();
1165
+ for (const module2 of modules) {
1166
+ for (const uniformName of Object.keys(module2.uniformTypes || {})) {
1167
+ propertyToModuleNameMap.set(uniformName, module2.name);
1168
+ }
1169
+ for (const binding of module2.bindingLayout || []) {
1170
+ propertyToModuleNameMap.set(binding.name, module2.name);
1171
+ }
1172
+ }
1173
+ const sceneShaderInputProps = {};
1174
+ for (const [propertyName, value] of Object.entries(shaderInputValues)) {
1175
+ if (value === void 0) {
1176
+ continue;
1177
+ }
1178
+ const moduleName = propertyToModuleNameMap.get(propertyName);
1179
+ if (!moduleName || material.ownsModule(moduleName)) {
1180
+ continue;
1181
+ }
1182
+ sceneShaderInputProps[moduleName] ||= {};
1183
+ sceneShaderInputProps[moduleName][propertyName] = value;
1184
+ }
1185
+ return sceneShaderInputProps;
523
1186
  }
524
1187
 
525
1188
  // dist/parsers/parse-gltf.js
@@ -530,73 +1193,103 @@ var defaultOptions = {
530
1193
  lights: true,
531
1194
  useTangents: false
532
1195
  };
533
- function parseGLTF(device, gltf, options_ = {}) {
534
- const options = { ...defaultOptions, ...options_ };
535
- const sceneNodes = gltf.scenes.map((gltfScene) => createScene(device, gltfScene, gltf.nodes, options));
536
- return sceneNodes;
537
- }
538
- function createScene(device, gltfScene, gltfNodes, options) {
539
- const gltfSceneNodes = gltfScene.nodes || [];
540
- const nodes = gltfSceneNodes.map((node) => createNode(device, node, gltfNodes, options));
541
- const sceneNode = new import_engine3.GroupNode({
542
- id: gltfScene.name || gltfScene.id,
543
- children: nodes
1196
+ function parseGLTF(device, gltf, options = {}) {
1197
+ const combinedOptions = { ...defaultOptions, ...options };
1198
+ const materialFactory = new import_engine4.MaterialFactory(device, { modules: [import_shadertools2.pbrMaterial] });
1199
+ const materials = (gltf.materials || []).map((gltfMaterial, materialIndex) => createGLTFMaterial(device, {
1200
+ id: getGLTFMaterialId(gltfMaterial, materialIndex),
1201
+ parsedPPBRMaterial: parsePBRMaterial(device, gltfMaterial, {}, {
1202
+ ...combinedOptions,
1203
+ gltf,
1204
+ validateAttributes: false
1205
+ }),
1206
+ materialFactory
1207
+ }));
1208
+ const gltfMaterialIdToMaterialMap = /* @__PURE__ */ new Map();
1209
+ (gltf.materials || []).forEach((gltfMaterial, materialIndex) => {
1210
+ gltfMaterialIdToMaterialMap.set(gltfMaterial.id, materials[materialIndex]);
544
1211
  });
545
- return sceneNode;
546
- }
547
- function createNode(device, gltfNode, gltfNodes, options) {
548
- if (!gltfNode._node) {
549
- const gltfChildren = gltfNode.children || [];
550
- const children = gltfChildren.map((child) => createNode(device, child, gltfNodes, options));
1212
+ const gltfMeshIdToNodeMap = /* @__PURE__ */ new Map();
1213
+ gltf.meshes.forEach((gltfMesh, idx) => {
1214
+ const newMesh = createNodeForGLTFMesh(device, gltfMesh, gltf, gltfMaterialIdToMaterialMap, combinedOptions);
1215
+ gltfMeshIdToNodeMap.set(gltfMesh.id, newMesh);
1216
+ });
1217
+ const gltfNodeIndexToNodeMap = /* @__PURE__ */ new Map();
1218
+ const gltfNodeIdToNodeMap = /* @__PURE__ */ new Map();
1219
+ gltf.nodes.forEach((gltfNode, idx) => {
1220
+ const newNode = createNodeForGLTFNode(device, gltfNode, combinedOptions);
1221
+ gltfNodeIndexToNodeMap.set(idx, newNode);
1222
+ gltfNodeIdToNodeMap.set(gltfNode.id, newNode);
1223
+ });
1224
+ gltf.nodes.forEach((gltfNode, idx) => {
1225
+ gltfNodeIndexToNodeMap.get(idx).add((gltfNode.children ?? []).map(({ id }) => {
1226
+ const child = gltfNodeIdToNodeMap.get(id);
1227
+ if (!child)
1228
+ throw new Error(`Cannot find child ${id} of node ${idx}`);
1229
+ return child;
1230
+ }));
551
1231
  if (gltfNode.mesh) {
552
- children.push(createMesh(device, gltfNode.mesh, options));
553
- }
554
- const node = new import_engine3.GroupNode({
555
- id: gltfNode.name || gltfNode.id,
556
- children
557
- });
558
- if (gltfNode.matrix) {
559
- node.setMatrix(gltfNode.matrix);
560
- } else {
561
- node.matrix.identity();
562
- if (gltfNode.translation) {
563
- node.matrix.translate(gltfNode.translation);
564
- }
565
- if (gltfNode.rotation) {
566
- const rotationMatrix = new import_core4.Matrix4().fromQuaternion(gltfNode.rotation);
567
- node.matrix.multiplyRight(rotationMatrix);
568
- }
569
- if (gltfNode.scale) {
570
- node.matrix.scale(gltfNode.scale);
1232
+ const mesh = gltfMeshIdToNodeMap.get(gltfNode.mesh.id);
1233
+ if (!mesh) {
1234
+ throw new Error(`Cannot find mesh child ${gltfNode.mesh.id} of node ${idx}`);
571
1235
  }
1236
+ gltfNodeIndexToNodeMap.get(idx).add(mesh);
572
1237
  }
573
- gltfNode._node = node;
574
- }
575
- const topLevelNode = gltfNodes.find((node) => node.id === gltfNode.id);
576
- topLevelNode._node = gltfNode._node;
577
- return gltfNode._node;
578
- }
579
- function createMesh(device, gltfMesh, options) {
580
- if (!gltfMesh._mesh) {
581
- const gltfPrimitives = gltfMesh.primitives || [];
582
- const primitives = gltfPrimitives.map((gltfPrimitive, i) => createPrimitive(device, gltfPrimitive, i, gltfMesh, options));
583
- const mesh = new import_engine3.GroupNode({
584
- id: gltfMesh.name || gltfMesh.id,
585
- children: primitives
1238
+ });
1239
+ const scenes = gltf.scenes.map((gltfScene) => {
1240
+ const children = (gltfScene.nodes || []).map(({ id }) => {
1241
+ const child = gltfNodeIdToNodeMap.get(id);
1242
+ if (!child)
1243
+ throw new Error(`Cannot find child ${id} of scene ${gltfScene.name || gltfScene.id}`);
1244
+ return child;
586
1245
  });
587
- gltfMesh._mesh = mesh;
588
- }
589
- return gltfMesh._mesh;
1246
+ return new import_engine4.GroupNode({
1247
+ id: gltfScene.name || gltfScene.id,
1248
+ children
1249
+ });
1250
+ });
1251
+ return { scenes, materials, gltfMeshIdToNodeMap, gltfNodeIdToNodeMap, gltfNodeIndexToNodeMap };
1252
+ }
1253
+ function createNodeForGLTFNode(device, gltfNode, options) {
1254
+ return new import_engine4.GroupNode({
1255
+ id: gltfNode.name || gltfNode.id,
1256
+ children: [],
1257
+ matrix: gltfNode.matrix,
1258
+ position: gltfNode.translation,
1259
+ rotation: gltfNode.rotation,
1260
+ scale: gltfNode.scale
1261
+ });
590
1262
  }
591
- function createPrimitive(device, gltfPrimitive, i, gltfMesh, options) {
592
- const id = gltfPrimitive.name || `${gltfMesh.name || gltfMesh.id}-primitive-${i}`;
1263
+ function createNodeForGLTFMesh(device, gltfMesh, gltf, gltfMaterialIdToMaterialMap, options) {
1264
+ const gltfPrimitives = gltfMesh.primitives || [];
1265
+ const primitives = gltfPrimitives.map((gltfPrimitive, i) => createNodeForGLTFPrimitive({
1266
+ device,
1267
+ gltfPrimitive,
1268
+ primitiveIndex: i,
1269
+ gltfMesh,
1270
+ gltf,
1271
+ gltfMaterialIdToMaterialMap,
1272
+ options
1273
+ }));
1274
+ const mesh = new import_engine4.GroupNode({
1275
+ id: gltfMesh.name || gltfMesh.id,
1276
+ children: primitives
1277
+ });
1278
+ return mesh;
1279
+ }
1280
+ function createNodeForGLTFPrimitive({ device, gltfPrimitive, primitiveIndex, gltfMesh, gltf, gltfMaterialIdToMaterialMap, options }) {
1281
+ const id = gltfPrimitive.name || `${gltfMesh.name || gltfMesh.id}-primitive-${primitiveIndex}`;
593
1282
  const topology = convertGLDrawModeToTopology(gltfPrimitive.mode || 4);
594
1283
  const vertexCount = gltfPrimitive.indices ? gltfPrimitive.indices.count : getVertexCount(gltfPrimitive.attributes);
595
1284
  const geometry = createGeometry(id, gltfPrimitive, topology);
596
- const parsedPPBRMaterial = parsePBRMaterial(device, gltfPrimitive.material, geometry.attributes, options);
1285
+ const parsedPPBRMaterial = parsePBRMaterial(device, gltfPrimitive.material, geometry.attributes, {
1286
+ ...options,
1287
+ gltf
1288
+ });
597
1289
  const modelNode = createGLTFModel(device, {
598
1290
  id,
599
1291
  geometry: createGeometry(id, gltfPrimitive, topology),
1292
+ material: gltfPrimitive.material ? gltfMaterialIdToMaterialMap.get(gltfPrimitive.material.id) || null : null,
600
1293
  parsedPPBRMaterial,
601
1294
  modelOptions: options.modelOptions,
602
1295
  vertexCount
@@ -613,42 +1306,41 @@ function createGeometry(id, gltfPrimitive, topology) {
613
1306
  const { components, size, value } = attribute;
614
1307
  attributes[attributeName] = { size: size ?? components, value };
615
1308
  }
616
- return new import_engine3.Geometry({
1309
+ return new import_engine4.Geometry({
617
1310
  id,
618
1311
  topology,
619
1312
  indices: gltfPrimitive.indices.value,
620
1313
  attributes
621
1314
  });
622
1315
  }
1316
+ function getGLTFMaterialId(gltfMaterial, materialIndex) {
1317
+ return gltfMaterial.name || gltfMaterial.id || `material-${materialIndex}`;
1318
+ }
623
1319
 
624
1320
  // dist/gltf/gltf-animator.js
625
- var import_core7 = require("@luma.gl/core");
626
- var import_core8 = require("@math.gl/core");
1321
+ var import_core6 = require("@luma.gl/core");
627
1322
 
628
1323
  // dist/gltf/animations/interpolate.js
629
- var import_core5 = require("@luma.gl/core");
630
- var import_core6 = require("@math.gl/core");
631
- var scratchQuaternion = new import_core6.Quaternion();
1324
+ var import_core4 = require("@luma.gl/core");
1325
+ var import_core5 = require("@math.gl/core");
1326
+ function updateTargetPath(target, path, newValue) {
1327
+ switch (path) {
1328
+ case "translation":
1329
+ return target.setPosition(newValue).updateMatrix();
1330
+ case "rotation":
1331
+ return target.setRotation(newValue).updateMatrix();
1332
+ case "scale":
1333
+ return target.setScale(newValue).updateMatrix();
1334
+ default:
1335
+ import_core4.log.warn(`Bad animation path ${path}`)();
1336
+ return null;
1337
+ }
1338
+ }
632
1339
  function interpolate(time, { input, interpolation, output }, target, path) {
633
1340
  const maxTime = input[input.length - 1];
634
1341
  const animationTime = time % maxTime;
635
1342
  const nextIndex = input.findIndex((t) => t >= animationTime);
636
1343
  const previousIndex = Math.max(0, nextIndex - 1);
637
- if (!Array.isArray(target[path])) {
638
- switch (path) {
639
- case "translation":
640
- target[path] = [0, 0, 0];
641
- break;
642
- case "rotation":
643
- target[path] = [0, 0, 0, 1];
644
- break;
645
- case "scale":
646
- target[path] = [1, 1, 1];
647
- break;
648
- default:
649
- import_core5.log.warn(`Bad animation path ${path}`)();
650
- }
651
- }
652
1344
  const previousTime = input[previousIndex];
653
1345
  const nextTime = input[nextIndex];
654
1346
  switch (interpolation) {
@@ -673,103 +1365,99 @@ function interpolate(time, { input, interpolation, output }, target, path) {
673
1365
  }
674
1366
  break;
675
1367
  default:
676
- import_core5.log.warn(`Interpolation ${interpolation} not supported`)();
1368
+ import_core4.log.warn(`Interpolation ${interpolation} not supported`)();
677
1369
  break;
678
1370
  }
679
1371
  }
680
1372
  function linearInterpolate(target, path, start, stop, ratio) {
681
- if (!target[path]) {
682
- throw new Error();
683
- }
684
1373
  if (path === "rotation") {
685
- scratchQuaternion.slerp({ start, target: stop, ratio });
686
- for (let i = 0; i < scratchQuaternion.length; i++) {
687
- target[path][i] = scratchQuaternion[i];
688
- }
1374
+ updateTargetPath(target, path, new import_core5.Quaternion().slerp({ start, target: stop, ratio }));
689
1375
  } else {
1376
+ const newVal = [];
690
1377
  for (let i = 0; i < start.length; i++) {
691
- target[path][i] = ratio * stop[i] + (1 - ratio) * start[i];
1378
+ newVal[i] = ratio * stop[i] + (1 - ratio) * start[i];
692
1379
  }
1380
+ updateTargetPath(target, path, newVal);
693
1381
  }
694
1382
  }
695
1383
  function cubicsplineInterpolate(target, path, { p0, outTangent0, inTangent1, p1, tDiff, ratio: t }) {
696
- if (!target[path]) {
697
- throw new Error();
698
- }
699
- for (let i = 0; i < target[path].length; i++) {
1384
+ const newVal = [];
1385
+ for (let i = 0; i < p0.length; i++) {
700
1386
  const m0 = outTangent0[i] * tDiff;
701
1387
  const m1 = inTangent1[i] * tDiff;
702
- target[path][i] = (2 * Math.pow(t, 3) - 3 * Math.pow(t, 2) + 1) * p0[i] + (Math.pow(t, 3) - 2 * Math.pow(t, 2) + t) * m0 + (-2 * Math.pow(t, 3) + 3 * Math.pow(t, 2)) * p1[i] + (Math.pow(t, 3) - Math.pow(t, 2)) * m1;
1388
+ newVal[i] = (2 * Math.pow(t, 3) - 3 * Math.pow(t, 2) + 1) * p0[i] + (Math.pow(t, 3) - 2 * Math.pow(t, 2) + t) * m0 + (-2 * Math.pow(t, 3) + 3 * Math.pow(t, 2)) * p1[i] + (Math.pow(t, 3) - Math.pow(t, 2)) * m1;
703
1389
  }
1390
+ updateTargetPath(target, path, newVal);
704
1391
  }
705
1392
  function stepInterpolate(target, path, value) {
706
- if (!target[path]) {
707
- throw new Error();
708
- }
709
- for (let i = 0; i < value.length; i++) {
710
- target[path][i] = value[i];
711
- }
1393
+ updateTargetPath(target, path, value);
712
1394
  }
713
1395
 
714
1396
  // dist/gltf/gltf-animator.js
715
1397
  var GLTFSingleAnimator = class {
1398
+ /** Animation definition being played. */
716
1399
  animation;
1400
+ /** Target scenegraph lookup table. */
1401
+ gltfNodeIdToNodeMap;
1402
+ /** Playback start time in seconds. */
717
1403
  startTime = 0;
1404
+ /** Whether playback is currently enabled. */
718
1405
  playing = true;
1406
+ /** Playback speed multiplier. */
719
1407
  speed = 1;
1408
+ /** Creates a single-animation controller. */
720
1409
  constructor(props) {
721
1410
  this.animation = props.animation;
1411
+ this.gltfNodeIdToNodeMap = props.gltfNodeIdToNodeMap;
722
1412
  this.animation.name ||= "unnamed";
723
1413
  Object.assign(this, props);
724
1414
  }
1415
+ /** Advances the animation to the supplied wall-clock time in milliseconds. */
725
1416
  setTime(timeMs) {
726
1417
  if (!this.playing) {
727
1418
  return;
728
1419
  }
729
1420
  const absTime = timeMs / 1e3;
730
1421
  const time = (absTime - this.startTime) * this.speed;
731
- this.animation.channels.forEach(({ sampler, target, path }) => {
732
- interpolate(time, sampler, target, path);
733
- applyTranslationRotationScale(target, target._node);
1422
+ this.animation.channels.forEach(({ sampler, targetNodeId, path }) => {
1423
+ const targetNode = this.gltfNodeIdToNodeMap.get(targetNodeId);
1424
+ if (!targetNode) {
1425
+ throw new Error(`Cannot find animation target node ${targetNodeId}`);
1426
+ }
1427
+ interpolate(time, sampler, targetNode, path);
734
1428
  });
735
1429
  }
736
1430
  };
737
1431
  var GLTFAnimator = class {
1432
+ /** Individual animation controllers. */
738
1433
  animations;
1434
+ /** Creates an animator for the supplied glTF scenegraph. */
739
1435
  constructor(props) {
740
1436
  this.animations = props.animations.map((animation, index) => {
741
1437
  const name = animation.name || `Animation-${index}`;
742
1438
  return new GLTFSingleAnimator({
1439
+ gltfNodeIdToNodeMap: props.gltfNodeIdToNodeMap,
743
1440
  animation: { name, channels: animation.channels }
744
1441
  });
745
1442
  });
746
1443
  }
747
1444
  /** @deprecated Use .setTime(). Will be removed (deck.gl is using this) */
748
1445
  animate(time) {
749
- import_core7.log.warn("GLTFAnimator#animate is deprecated. Use GLTFAnimator#setTime instead")();
1446
+ import_core6.log.warn("GLTFAnimator#animate is deprecated. Use GLTFAnimator#setTime instead")();
750
1447
  this.setTime(time);
751
1448
  }
1449
+ /** Advances every animation to the supplied wall-clock time in milliseconds. */
752
1450
  setTime(time) {
753
1451
  this.animations.forEach((animation) => animation.setTime(time));
754
1452
  }
1453
+ /** Returns the per-animation controllers managed by this animator. */
755
1454
  getAnimations() {
756
1455
  return this.animations;
757
1456
  }
758
1457
  };
759
- var scratchMatrix = new import_core8.Matrix4();
760
- function applyTranslationRotationScale(gltfNode, node) {
761
- node.matrix.identity();
762
- if (gltfNode.translation) {
763
- node.matrix.translate(gltfNode.translation);
764
- }
765
- if (gltfNode.rotation) {
766
- const rotationMatrix = scratchMatrix.fromQuaternion(gltfNode.rotation);
767
- node.matrix.multiplyRight(rotationMatrix);
768
- }
769
- if (gltfNode.scale) {
770
- node.matrix.scale(gltfNode.scale);
771
- }
772
- }
1458
+
1459
+ // dist/parsers/parse-gltf-animations.js
1460
+ var import_core7 = require("@luma.gl/core");
773
1461
 
774
1462
  // dist/webgl-to-webgpu/convert-webgl-attribute.js
775
1463
  var ATTRIBUTE_TYPE_TO_COMPONENTS = {
@@ -802,62 +1490,327 @@ function accessorToTypedArray(accessor) {
802
1490
  // dist/parsers/parse-gltf-animations.js
803
1491
  function parseGLTFAnimations(gltf) {
804
1492
  const gltfAnimations = gltf.animations || [];
805
- return gltfAnimations.map((animation, index) => {
1493
+ const accessorCache1D = /* @__PURE__ */ new Map();
1494
+ const accessorCache2D = /* @__PURE__ */ new Map();
1495
+ return gltfAnimations.flatMap((animation, index) => {
806
1496
  const name = animation.name || `Animation-${index}`;
807
- const samplers = animation.samplers.map(({ input, interpolation = "LINEAR", output }) => ({
808
- input: accessorToJsArray(gltf.accessors[input]),
809
- interpolation,
810
- output: accessorToJsArray(gltf.accessors[output])
811
- }));
812
- const channels = animation.channels.map(({ sampler, target }) => ({
813
- sampler: samplers[sampler],
814
- target: gltf.nodes[target.node ?? 0],
815
- path: target.path
816
- }));
817
- return { name, channels };
1497
+ const samplerCache = /* @__PURE__ */ new Map();
1498
+ const channels = animation.channels.flatMap(({ sampler, target }) => {
1499
+ const path = getSupportedAnimationPath(target.path);
1500
+ if (!path) {
1501
+ return [];
1502
+ }
1503
+ const targetNode = gltf.nodes[target.node ?? 0];
1504
+ if (!targetNode) {
1505
+ throw new Error(`Cannot find animation target ${target.node}`);
1506
+ }
1507
+ let parsedSampler = samplerCache.get(sampler);
1508
+ if (!parsedSampler) {
1509
+ const gltfSampler = animation.samplers[sampler];
1510
+ if (!gltfSampler) {
1511
+ throw new Error(`Cannot find animation sampler ${sampler}`);
1512
+ }
1513
+ const { input, interpolation = "LINEAR", output } = gltfSampler;
1514
+ parsedSampler = {
1515
+ input: accessorToJsArray1D(gltf.accessors[input], accessorCache1D),
1516
+ interpolation,
1517
+ output: accessorToJsArray2D(gltf.accessors[output], accessorCache2D)
1518
+ };
1519
+ samplerCache.set(sampler, parsedSampler);
1520
+ }
1521
+ return {
1522
+ sampler: parsedSampler,
1523
+ targetNodeId: targetNode.id,
1524
+ path
1525
+ };
1526
+ });
1527
+ return channels.length ? [{ name, channels }] : [];
818
1528
  });
819
1529
  }
820
- function accessorToJsArray(accessor) {
821
- if (!accessor._animation) {
822
- const { typedArray: array, components } = accessorToTypedArray(accessor);
823
- if (components === 1) {
824
- accessor._animation = Array.from(array);
825
- } else {
826
- const slicedArray = [];
827
- for (let i = 0; i < array.length; i += components) {
828
- slicedArray.push(Array.from(array.slice(i, i + components)));
829
- }
830
- accessor._animation = slicedArray;
831
- }
1530
+ function getSupportedAnimationPath(path) {
1531
+ if (path === "pointer") {
1532
+ import_core7.log.warn("KHR_animation_pointer channels are not supported and will be skipped")();
1533
+ return null;
1534
+ }
1535
+ return path;
1536
+ }
1537
+ function accessorToJsArray1D(accessor, accessorCache) {
1538
+ if (accessorCache.has(accessor)) {
1539
+ return accessorCache.get(accessor);
1540
+ }
1541
+ const { typedArray: array, components } = accessorToTypedArray(accessor);
1542
+ assert(components === 1, "accessorToJsArray1D must have exactly 1 component");
1543
+ const result = Array.from(array);
1544
+ accessorCache.set(accessor, result);
1545
+ return result;
1546
+ }
1547
+ function accessorToJsArray2D(accessor, accessorCache) {
1548
+ if (accessorCache.has(accessor)) {
1549
+ return accessorCache.get(accessor);
1550
+ }
1551
+ const { typedArray: array, components } = accessorToTypedArray(accessor);
1552
+ assert(components >= 1, "accessorToJsArray2D must have at least 1 component");
1553
+ const result = [];
1554
+ for (let i = 0; i < array.length; i += components) {
1555
+ result.push(Array.from(array.slice(i, i + components)));
1556
+ }
1557
+ accessorCache.set(accessor, result);
1558
+ return result;
1559
+ }
1560
+ function assert(condition, message) {
1561
+ if (!condition) {
1562
+ throw new Error(message);
832
1563
  }
833
- return accessor._animation;
834
1564
  }
835
1565
 
836
- // dist/utils/deep-copy.js
837
- function deepCopy(object) {
838
- if (ArrayBuffer.isView(object) || object instanceof ArrayBuffer || object instanceof ImageBitmap) {
839
- return object;
1566
+ // dist/gltf/gltf-extension-support.js
1567
+ var UNKNOWN_EXTENSION_SUPPORT = {
1568
+ supportLevel: "none",
1569
+ comment: "Not currently listed in the luma.gl glTF extension support registry."
1570
+ };
1571
+ var GLTF_EXTENSION_SUPPORT_REGISTRY = {
1572
+ KHR_draco_mesh_compression: {
1573
+ supportLevel: "built-in",
1574
+ comment: "Decoded by loaders.gl before luma.gl builds the scenegraph."
1575
+ },
1576
+ EXT_meshopt_compression: {
1577
+ supportLevel: "built-in",
1578
+ comment: "Meshopt-compressed primitives are decoded during load."
1579
+ },
1580
+ KHR_mesh_quantization: {
1581
+ supportLevel: "built-in",
1582
+ comment: "Quantized accessors are unpacked before geometry creation."
1583
+ },
1584
+ KHR_lights_punctual: {
1585
+ supportLevel: "built-in",
1586
+ comment: "Parsed into luma.gl Light objects."
1587
+ },
1588
+ KHR_materials_unlit: {
1589
+ supportLevel: "built-in",
1590
+ comment: "Unlit materials bypass the default lighting path."
1591
+ },
1592
+ KHR_materials_emissive_strength: {
1593
+ supportLevel: "built-in",
1594
+ comment: "Applied by the stock PBR shader."
1595
+ },
1596
+ KHR_texture_basisu: {
1597
+ supportLevel: "built-in",
1598
+ comment: "BasisU / KTX2 textures pass through when the device supports them."
1599
+ },
1600
+ KHR_texture_transform: {
1601
+ supportLevel: "built-in",
1602
+ comment: "UV transforms are applied during load."
1603
+ },
1604
+ EXT_texture_webp: {
1605
+ supportLevel: "loader-only",
1606
+ comment: "Texture source is resolved during load; final support depends on browser and device decode support."
1607
+ },
1608
+ EXT_texture_avif: {
1609
+ supportLevel: "loader-only",
1610
+ comment: "Texture source is resolved during load; final support depends on browser and device decode support."
1611
+ },
1612
+ KHR_materials_specular: {
1613
+ supportLevel: "built-in",
1614
+ comment: "The stock shader now applies specular factors and textures to the dielectric F0 term."
1615
+ },
1616
+ KHR_materials_ior: {
1617
+ supportLevel: "built-in",
1618
+ comment: "The stock shader now drives dielectric reflectance from the glTF IOR value."
1619
+ },
1620
+ KHR_materials_transmission: {
1621
+ supportLevel: "built-in",
1622
+ comment: "The stock shader now applies transmission to the base layer and exposes transparency through alpha, without a scene-color refraction buffer."
1623
+ },
1624
+ KHR_materials_volume: {
1625
+ supportLevel: "built-in",
1626
+ comment: "Thickness and attenuation now tint transmitted light in the stock shader."
1627
+ },
1628
+ KHR_materials_clearcoat: {
1629
+ supportLevel: "built-in",
1630
+ comment: "The stock shader now adds a secondary clearcoat specular lobe."
1631
+ },
1632
+ KHR_materials_sheen: {
1633
+ supportLevel: "built-in",
1634
+ comment: "The stock shader now adds a sheen lobe for cloth-like materials."
1635
+ },
1636
+ KHR_materials_iridescence: {
1637
+ supportLevel: "built-in",
1638
+ comment: "The stock shader now tints specular response with a view-dependent thin-film iridescence approximation."
1639
+ },
1640
+ KHR_materials_anisotropy: {
1641
+ supportLevel: "built-in",
1642
+ comment: "The stock shader now shapes highlights and IBL response with an anisotropy-direction approximation."
1643
+ },
1644
+ KHR_materials_pbrSpecularGlossiness: {
1645
+ supportLevel: "loader-only",
1646
+ comment: "Extension data can be loaded, but it is not translated into the default metallic-roughness material path."
1647
+ },
1648
+ KHR_materials_variants: {
1649
+ supportLevel: "loader-only",
1650
+ comment: "Variant metadata can be loaded, but applications must choose and apply variants."
1651
+ },
1652
+ EXT_mesh_gpu_instancing: {
1653
+ supportLevel: "none",
1654
+ comment: "GPU instancing data is not yet converted into luma.gl instanced draw setup."
1655
+ },
1656
+ KHR_node_visibility: {
1657
+ supportLevel: "none",
1658
+ comment: "Node-visibility animations and toggles are not mapped onto runtime scenegraph state."
1659
+ },
1660
+ KHR_animation_pointer: {
1661
+ supportLevel: "none",
1662
+ comment: "Animation pointers are not mapped onto runtime scenegraph updates."
1663
+ },
1664
+ KHR_materials_diffuse_transmission: {
1665
+ supportLevel: "none",
1666
+ comment: "Diffuse-transmission shading is not implemented in the stock PBR shader."
1667
+ },
1668
+ KHR_materials_dispersion: {
1669
+ supportLevel: "none",
1670
+ comment: "Chromatic dispersion is not implemented in the stock PBR shader."
1671
+ },
1672
+ KHR_materials_volume_scatter: {
1673
+ supportLevel: "none",
1674
+ comment: "Volume scattering is not implemented in the stock PBR shader."
1675
+ },
1676
+ KHR_xmp: {
1677
+ supportLevel: "none",
1678
+ comment: "Metadata payloads remain in the loaded glTF, but luma.gl does not interpret them."
1679
+ },
1680
+ KHR_xmp_json_ld: {
1681
+ supportLevel: "none",
1682
+ comment: "Metadata is preserved in the glTF, but luma.gl does not interpret it."
1683
+ },
1684
+ EXT_lights_image_based: {
1685
+ supportLevel: "none",
1686
+ comment: "Use loadPBREnvironment() or custom environment setup instead."
1687
+ },
1688
+ EXT_texture_video: {
1689
+ supportLevel: "none",
1690
+ comment: "Video textures are not created automatically by the stock pipeline."
1691
+ },
1692
+ MSFT_lod: {
1693
+ supportLevel: "none",
1694
+ comment: "Level-of-detail switching is not implemented in the stock scenegraph loader."
1695
+ }
1696
+ };
1697
+ function getGLTFExtensionSupport(gltf) {
1698
+ const extensionNames = Array.from(collectGLTFExtensionNames(gltf)).sort();
1699
+ const extensionSupportEntries = extensionNames.map((extensionName) => {
1700
+ const extensionSupportDefinition = GLTF_EXTENSION_SUPPORT_REGISTRY[extensionName] || UNKNOWN_EXTENSION_SUPPORT;
1701
+ return [
1702
+ extensionName,
1703
+ {
1704
+ extensionName,
1705
+ supported: extensionSupportDefinition.supportLevel === "built-in",
1706
+ supportLevel: extensionSupportDefinition.supportLevel,
1707
+ comment: extensionSupportDefinition.comment
1708
+ }
1709
+ ];
1710
+ });
1711
+ return new Map(extensionSupportEntries);
1712
+ }
1713
+ function collectGLTFExtensionNames(gltf) {
1714
+ var _a;
1715
+ const gltfWithRemovedExtensions = gltf;
1716
+ const extensionNames = /* @__PURE__ */ new Set();
1717
+ addExtensionNames(extensionNames, gltf.extensionsUsed);
1718
+ addExtensionNames(extensionNames, gltf.extensionsRequired);
1719
+ addExtensionNames(extensionNames, gltfWithRemovedExtensions.extensionsRemoved);
1720
+ addExtensionNames(extensionNames, Object.keys(gltf.extensions || {}));
1721
+ if (((_a = gltfWithRemovedExtensions.lights) == null ? void 0 : _a.length) || gltf.nodes.some((node) => "light" in node)) {
1722
+ extensionNames.add("KHR_lights_punctual");
840
1723
  }
841
- if (Array.isArray(object)) {
842
- return object.map(deepCopy);
1724
+ if (gltf.materials.some((material) => {
1725
+ var _a2;
1726
+ const gltfMaterial = material;
1727
+ return gltfMaterial.unlit || ((_a2 = gltfMaterial.extensions) == null ? void 0 : _a2.KHR_materials_unlit);
1728
+ })) {
1729
+ extensionNames.add("KHR_materials_unlit");
843
1730
  }
844
- if (object && typeof object === "object") {
845
- const result = {};
846
- for (const key in object) {
847
- result[key] = deepCopy(object[key]);
848
- }
849
- return result;
1731
+ return extensionNames;
1732
+ }
1733
+ function addExtensionNames(extensionNames, newExtensionNames = []) {
1734
+ for (const extensionName of newExtensionNames) {
1735
+ extensionNames.add(extensionName);
850
1736
  }
851
- return object;
852
1737
  }
853
1738
 
854
1739
  // dist/gltf/create-scenegraph-from-gltf.js
855
1740
  function createScenegraphsFromGLTF(device, gltf, options) {
856
- gltf = deepCopy(gltf);
857
- const scenes = parseGLTF(device, gltf, options);
1741
+ const { scenes, materials, gltfMeshIdToNodeMap, gltfNodeIdToNodeMap, gltfNodeIndexToNodeMap } = parseGLTF(device, gltf, options);
858
1742
  const animations = parseGLTFAnimations(gltf);
859
- const animator = new GLTFAnimator({ animations });
1743
+ const animator = new GLTFAnimator({ animations, gltfNodeIdToNodeMap });
860
1744
  const lights = parseGLTFLights(gltf);
861
- return { scenes, animator, lights };
1745
+ const extensionSupport = getGLTFExtensionSupport(gltf);
1746
+ const sceneBounds = scenes.map((scene) => getScenegraphBounds(scene.getBounds()));
1747
+ const modelBounds = getCombinedScenegraphBounds(sceneBounds);
1748
+ return {
1749
+ scenes,
1750
+ materials,
1751
+ animator,
1752
+ lights,
1753
+ extensionSupport,
1754
+ sceneBounds,
1755
+ modelBounds,
1756
+ gltfMeshIdToNodeMap,
1757
+ gltfNodeIdToNodeMap,
1758
+ gltfNodeIndexToNodeMap,
1759
+ gltf
1760
+ };
1761
+ }
1762
+ function getScenegraphBounds(bounds) {
1763
+ if (!bounds) {
1764
+ return {
1765
+ bounds: null,
1766
+ center: [0, 0, 0],
1767
+ size: [0, 0, 0],
1768
+ radius: 0.5,
1769
+ recommendedOrbitDistance: 1
1770
+ };
1771
+ }
1772
+ const normalizedBounds = [
1773
+ [bounds[0][0], bounds[0][1], bounds[0][2]],
1774
+ [bounds[1][0], bounds[1][1], bounds[1][2]]
1775
+ ];
1776
+ const size = [
1777
+ normalizedBounds[1][0] - normalizedBounds[0][0],
1778
+ normalizedBounds[1][1] - normalizedBounds[0][1],
1779
+ normalizedBounds[1][2] - normalizedBounds[0][2]
1780
+ ];
1781
+ const center = [
1782
+ normalizedBounds[0][0] + size[0] * 0.5,
1783
+ normalizedBounds[0][1] + size[1] * 0.5,
1784
+ normalizedBounds[0][2] + size[2] * 0.5
1785
+ ];
1786
+ const maxHalfExtent = Math.max(size[0], size[1], size[2]) * 0.5;
1787
+ const radius = Math.max(0.5 * Math.hypot(size[0], size[1], size[2]), 1e-3);
1788
+ return {
1789
+ bounds: normalizedBounds,
1790
+ center,
1791
+ size,
1792
+ radius,
1793
+ recommendedOrbitDistance: Math.max(Math.max(maxHalfExtent, 1e-3) / Math.tan(Math.PI / 6) * 1.15, radius * 1.1)
1794
+ };
1795
+ }
1796
+ function getCombinedScenegraphBounds(sceneBounds) {
1797
+ let combinedBounds = null;
1798
+ for (const sceneBoundInfo of sceneBounds) {
1799
+ if (!sceneBoundInfo.bounds) {
1800
+ continue;
1801
+ }
1802
+ if (!combinedBounds) {
1803
+ combinedBounds = [
1804
+ [...sceneBoundInfo.bounds[0]],
1805
+ [...sceneBoundInfo.bounds[1]]
1806
+ ];
1807
+ continue;
1808
+ }
1809
+ for (let axis = 0; axis < 3; axis++) {
1810
+ combinedBounds[0][axis] = Math.min(combinedBounds[0][axis], sceneBoundInfo.bounds[0][axis]);
1811
+ combinedBounds[1][axis] = Math.max(combinedBounds[1][axis], sceneBoundInfo.bounds[1][axis]);
1812
+ }
1813
+ }
1814
+ return getScenegraphBounds(combinedBounds);
862
1815
  }
863
1816
  //# sourceMappingURL=index.cjs.map