@aura3d/assets 3.0.1 → 4.0.0

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 (158) hide show
  1. package/dist/AdvancedAssetCorpus.d.ts.map +1 -1
  2. package/dist/AdvancedAssetCorpus.js +7 -12
  3. package/dist/AdvancedAssetCorpus.js.map +1 -1
  4. package/dist/AssetDecoderRegistry.d.ts +56 -0
  5. package/dist/AssetDecoderRegistry.d.ts.map +1 -0
  6. package/dist/AssetDecoderRegistry.js +225 -0
  7. package/dist/AssetDecoderRegistry.js.map +1 -0
  8. package/dist/GLTFAnimationRuntime.d.ts +216 -2
  9. package/dist/GLTFAnimationRuntime.d.ts.map +1 -1
  10. package/dist/GLTFAnimationRuntime.js +674 -38
  11. package/dist/GLTFAnimationRuntime.js.map +1 -1
  12. package/dist/GLTFAssetParseCache.d.ts +24 -0
  13. package/dist/GLTFAssetParseCache.d.ts.map +1 -0
  14. package/dist/GLTFAssetParseCache.js +100 -0
  15. package/dist/GLTFAssetParseCache.js.map +1 -0
  16. package/dist/GLTFExtensionSupport.js +3 -3
  17. package/dist/GLTFExtensionSupport.js.map +1 -1
  18. package/dist/GLTFLoader.d.ts +28 -1
  19. package/dist/GLTFLoader.d.ts.map +1 -1
  20. package/dist/GLTFLoader.js +176 -23
  21. package/dist/GLTFLoader.js.map +1 -1
  22. package/dist/GLTFRenderResources.d.ts +77 -2
  23. package/dist/GLTFRenderResources.d.ts.map +1 -1
  24. package/dist/GLTFRenderResources.js +310 -153
  25. package/dist/GLTFRenderResources.js.map +1 -1
  26. package/dist/KTX2BasisTextureTranscoder.d.ts +70 -12
  27. package/dist/KTX2BasisTextureTranscoder.d.ts.map +1 -1
  28. package/dist/KTX2BasisTextureTranscoder.js +225 -192
  29. package/dist/KTX2BasisTextureTranscoder.js.map +1 -1
  30. package/dist/KTX2TargetSelection.d.ts +25 -0
  31. package/dist/KTX2TargetSelection.d.ts.map +1 -0
  32. package/dist/KTX2TargetSelection.js +36 -0
  33. package/dist/KTX2TargetSelection.js.map +1 -0
  34. package/dist/KTX2TranscodeDriver.d.ts +70 -0
  35. package/dist/KTX2TranscodeDriver.d.ts.map +1 -0
  36. package/dist/KTX2TranscodeDriver.js +104 -0
  37. package/dist/KTX2TranscodeDriver.js.map +1 -0
  38. package/dist/KTX2TranscodeWorker.d.ts +30 -0
  39. package/dist/KTX2TranscodeWorker.d.ts.map +1 -0
  40. package/dist/KTX2TranscodeWorker.js +230 -0
  41. package/dist/KTX2TranscodeWorker.js.map +1 -0
  42. package/dist/MikkTSpaceTangents.d.ts +43 -0
  43. package/dist/MikkTSpaceTangents.d.ts.map +1 -0
  44. package/dist/MikkTSpaceTangents.js +149 -0
  45. package/dist/MikkTSpaceTangents.js.map +1 -0
  46. package/dist/asset-corpus/ProductionAssetCorpus.d.ts.map +1 -1
  47. package/dist/asset-corpus/ProductionAssetCorpus.js +7 -13
  48. package/dist/asset-corpus/ProductionAssetCorpus.js.map +1 -1
  49. package/dist/asset-corpus/ProductionGLTFRenderPipeline.d.ts +43 -0
  50. package/dist/asset-corpus/ProductionGLTFRenderPipeline.d.ts.map +1 -1
  51. package/dist/asset-corpus/ProductionGLTFRenderPipeline.js +146 -22
  52. package/dist/asset-corpus/ProductionGLTFRenderPipeline.js.map +1 -1
  53. package/dist/assetPhaseMarks.d.ts +14 -0
  54. package/dist/assetPhaseMarks.d.ts.map +1 -0
  55. package/dist/assetPhaseMarks.js +23 -0
  56. package/dist/assetPhaseMarks.js.map +1 -0
  57. package/dist/browser-index.d.ts +5 -1
  58. package/dist/browser-index.d.ts.map +1 -1
  59. package/dist/browser-index.js +3 -1
  60. package/dist/browser-index.js.map +1 -1
  61. package/dist/contracts/decoders.d.ts +15 -0
  62. package/dist/contracts/decoders.d.ts.map +1 -0
  63. package/dist/contracts/decoders.js +8 -0
  64. package/dist/contracts/decoders.js.map +1 -0
  65. package/dist/decoderLoad.d.ts +28 -0
  66. package/dist/decoderLoad.d.ts.map +1 -0
  67. package/dist/decoderLoad.js +49 -0
  68. package/dist/decoderLoad.js.map +1 -0
  69. package/dist/gltf/ImageDecode.d.ts +18 -0
  70. package/dist/gltf/ImageDecode.d.ts.map +1 -0
  71. package/dist/gltf/ImageDecode.js +140 -0
  72. package/dist/gltf/ImageDecode.js.map +1 -0
  73. package/dist/gltf-runtime.d.ts +1 -1
  74. package/dist/gltf-runtime.d.ts.map +1 -1
  75. package/dist/gltf-runtime.js.map +1 -1
  76. package/dist/index.d.ts +5 -1
  77. package/dist/index.d.ts.map +1 -1
  78. package/dist/index.js +3 -1
  79. package/dist/index.js.map +1 -1
  80. package/dist/lanes/index.d.ts +17 -0
  81. package/dist/lanes/index.d.ts.map +1 -0
  82. package/dist/lanes/index.js +17 -0
  83. package/dist/lanes/index.js.map +1 -0
  84. package/dist/lanes/prd01.d.ts +6 -0
  85. package/dist/lanes/prd01.d.ts.map +1 -0
  86. package/dist/lanes/prd01.js +6 -0
  87. package/dist/lanes/prd01.js.map +1 -0
  88. package/dist/lanes/prd02.d.ts +6 -0
  89. package/dist/lanes/prd02.d.ts.map +1 -0
  90. package/dist/lanes/prd02.js +6 -0
  91. package/dist/lanes/prd02.js.map +1 -0
  92. package/dist/lanes/prd03.d.ts +6 -0
  93. package/dist/lanes/prd03.d.ts.map +1 -0
  94. package/dist/lanes/prd03.js +6 -0
  95. package/dist/lanes/prd03.js.map +1 -0
  96. package/dist/lanes/prd04.d.ts +6 -0
  97. package/dist/lanes/prd04.d.ts.map +1 -0
  98. package/dist/lanes/prd04.js +6 -0
  99. package/dist/lanes/prd04.js.map +1 -0
  100. package/dist/lanes/prd05.d.ts +14 -0
  101. package/dist/lanes/prd05.d.ts.map +1 -0
  102. package/dist/lanes/prd05.js +14 -0
  103. package/dist/lanes/prd05.js.map +1 -0
  104. package/dist/lanes/prd06.d.ts +6 -0
  105. package/dist/lanes/prd06.d.ts.map +1 -0
  106. package/dist/lanes/prd06.js +6 -0
  107. package/dist/lanes/prd06.js.map +1 -0
  108. package/dist/lanes/prd07.d.ts +6 -0
  109. package/dist/lanes/prd07.d.ts.map +1 -0
  110. package/dist/lanes/prd07.js +6 -0
  111. package/dist/lanes/prd07.js.map +1 -0
  112. package/dist/lanes/prd08.d.ts +6 -0
  113. package/dist/lanes/prd08.d.ts.map +1 -0
  114. package/dist/lanes/prd08.js +6 -0
  115. package/dist/lanes/prd08.js.map +1 -0
  116. package/dist/lanes/prd09.d.ts +6 -0
  117. package/dist/lanes/prd09.d.ts.map +1 -0
  118. package/dist/lanes/prd09.js +6 -0
  119. package/dist/lanes/prd09.js.map +1 -0
  120. package/dist/lanes/prd10.d.ts +6 -0
  121. package/dist/lanes/prd10.d.ts.map +1 -0
  122. package/dist/lanes/prd10.js +6 -0
  123. package/dist/lanes/prd10.js.map +1 -0
  124. package/dist/lanes/prd11.d.ts +6 -0
  125. package/dist/lanes/prd11.d.ts.map +1 -0
  126. package/dist/lanes/prd11.js +6 -0
  127. package/dist/lanes/prd11.js.map +1 -0
  128. package/dist/lanes/prd12.d.ts +6 -0
  129. package/dist/lanes/prd12.d.ts.map +1 -0
  130. package/dist/lanes/prd12.js +6 -0
  131. package/dist/lanes/prd12.js.map +1 -0
  132. package/dist/lanes/prd13.d.ts +6 -0
  133. package/dist/lanes/prd13.d.ts.map +1 -0
  134. package/dist/lanes/prd13.js +6 -0
  135. package/dist/lanes/prd13.js.map +1 -0
  136. package/dist/lanes/prd14.d.ts +6 -0
  137. package/dist/lanes/prd14.d.ts.map +1 -0
  138. package/dist/lanes/prd14.js +6 -0
  139. package/dist/lanes/prd14.js.map +1 -0
  140. package/dist/lanes/prd15.d.ts +6 -0
  141. package/dist/lanes/prd15.d.ts.map +1 -0
  142. package/dist/lanes/prd15.js +6 -0
  143. package/dist/lanes/prd15.js.map +1 -0
  144. package/dist/vite/auraDecoders.d.ts +31 -0
  145. package/dist/vite/auraDecoders.d.ts.map +1 -0
  146. package/dist/vite/auraDecoders.js +89 -0
  147. package/dist/vite/auraDecoders.js.map +1 -0
  148. package/package.json +13 -5
  149. package/vendor/basis/README.md +25 -0
  150. package/vendor/basis/basis_transcoder.js +19 -0
  151. package/vendor/basis/basis_transcoder.wasm +0 -0
  152. package/vendor/draco/README.md +23 -0
  153. package/vendor/draco/draco_decoder.js +33 -0
  154. package/vendor/draco/draco_decoder.wasm +0 -0
  155. package/vendor/meshopt/README.md +21 -0
  156. package/vendor/meshopt/meshopt_decoder.cjs +199 -0
  157. package/vendor/meshopt/meshopt_decoder.mjs +196 -0
  158. package/vendor/meshopt_decoder.mjs +196 -0
@@ -1,7 +1,61 @@
1
- import { Geometry, IndexBuffer, InstancedPBRMaterial, InstancedUnlitMaterial, Material, PBRMaterial, Sampler, SkinnedLitMaterial, Texture, TextureBinding, TexturedPBRMaterial, TexturedUnlitMaterial, UnlitMaterial, VertexBuffer, createExternalParityEnvironmentLighting, computePerspectiveCameraFrame, computeSkinnedMorphTargetEnvelopeBounds, VertexFormat, DEFAULT_PBR_ENVIRONMENT_INTENSITY, isTexturedPbrTextureSlotShaderActive, DEFAULT_TEXTURED_PBR_SHADER_NAME } from "@aura3d/rendering";
1
+ import { Geometry, IndexBuffer, InstancedPBRMaterial, InstancedUnlitMaterial, Material, PBRMaterial, Sampler, SkinnedLitMaterial, Texture, TextureBinding, TexturedPBRMaterial, TexturedUnlitMaterial, UnlitMaterial, VertexBuffer, createExternalParityEnvironmentLighting, computePerspectiveCameraFrame, computeSkinnedMorphTargetEnvelopeBounds, VertexFormat, DEFAULT_PBR_ENVIRONMENT_INTENSITY, isTexturedPbrTextureSlotShaderActive, DEFAULT_TEXTURED_PBR_SHADER_NAME, applyTextureBudget } from "@aura3d/rendering";
2
2
  import { Bounds3 as SceneBounds3, multiplyMat4 } from "@aura3d/scene";
3
- import { parseGLTFRuntimeMaterialKey } from "./GLTFLoader.js";
4
- import { transcodeKTX2BasisTexture } from "./KTX2BasisTextureTranscoder.js";
3
+ import { gltfRuntimeMaterialKey, parseGLTFRuntimeMaterialKey } from "./GLTFLoader.js";
4
+ import { generateMikkTSpaceTangents, mikkTSpaceAvailable } from "./MikkTSpaceTangents.js";
5
+ import { decodeImageInBrowser } from "./gltf/ImageDecode.js";
6
+ /** Texture slots read sRGB-authored data; every other slot decodes linear. */
7
+ const IMAGE_COLORSPACE_SLOTS = [
8
+ { slot: "baseColor", colorSpace: "srgb", texture: (material) => material.baseColorTexture },
9
+ { slot: "emissive", colorSpace: "srgb", texture: (material) => material.emissiveTexture },
10
+ { slot: "metallicRoughness", colorSpace: "linear", texture: (material) => material.metallicRoughnessTexture },
11
+ { slot: "normal", colorSpace: "linear", texture: (material) => material.normalTexture },
12
+ { slot: "occlusion", colorSpace: "linear", texture: (material) => material.occlusionTexture },
13
+ { slot: "clearcoat", colorSpace: "linear", texture: (material) => material.clearcoat?.texture },
14
+ { slot: "clearcoatRoughness", colorSpace: "linear", texture: (material) => material.clearcoat?.roughnessTexture },
15
+ { slot: "clearcoatNormal", colorSpace: "linear", texture: (material) => material.clearcoat?.normalTexture },
16
+ { slot: "sheenColor", colorSpace: "srgb", texture: (material) => material.sheen?.colorTexture },
17
+ { slot: "sheenRoughness", colorSpace: "linear", texture: (material) => material.sheen?.roughnessTexture },
18
+ { slot: "transmission", colorSpace: "linear", texture: (material) => material.transmission?.texture },
19
+ { slot: "diffuseTransmission", colorSpace: "linear", texture: (material) => material.diffuseTransmission?.texture },
20
+ { slot: "diffuseTransmissionColor", colorSpace: "srgb", texture: (material) => material.diffuseTransmission?.colorTexture },
21
+ { slot: "volumeThickness", colorSpace: "linear", texture: (material) => material.volume?.thicknessTexture },
22
+ { slot: "specular", colorSpace: "linear", texture: (material) => material.specular?.texture },
23
+ { slot: "specularColor", colorSpace: "srgb", texture: (material) => material.specular?.colorTexture },
24
+ { slot: "anisotropy", colorSpace: "linear", texture: (material) => material.anisotropy?.texture },
25
+ { slot: "iridescence", colorSpace: "linear", texture: (material) => material.iridescence?.texture },
26
+ { slot: "iridescenceThickness", colorSpace: "linear", texture: (material) => material.iridescence?.thicknessTexture },
27
+ { slot: "sgDiffuse", colorSpace: "srgb", texture: (material) => material.pbrSpecularGlossiness?.diffuseTexture },
28
+ { slot: "sgSpecularGlossiness", colorSpace: "linear", texture: (material) => material.pbrSpecularGlossiness?.specularGlossinessTexture }
29
+ ];
30
+ /**
31
+ * PRD-04 P5-1: per-image colour-space intent derived from material slot usage, for the C-16
32
+ * decode path (a KTX2/Basis transcode target must be chosen before any slot samples the
33
+ * image). An image used both sRGB and linear records a `colorspace-conflict` in
34
+ * `conflicts`; its intent resolves `"linear"` so no gamma curve is baked into the decoded
35
+ * payload — the per-slot usage space still applies at the `Texture` level.
36
+ */
37
+ export function imageColorSpaceIntent(asset) {
38
+ const usages = new Map();
39
+ for (const material of asset.materials) {
40
+ for (const slot of IMAGE_COLORSPACE_SLOTS) {
41
+ const info = slot.texture(material);
42
+ if (!info)
43
+ continue;
44
+ const usage = usages.get(info.image) ?? { srgb: [], linear: [] };
45
+ (slot.colorSpace === "srgb" ? usage.srgb : usage.linear).push(`${material.name}:${slot.slot}`);
46
+ usages.set(info.image, usage);
47
+ }
48
+ }
49
+ const intent = new Map();
50
+ const conflicts = [];
51
+ for (const [image, usage] of usages) {
52
+ const conflict = usage.srgb.length > 0 && usage.linear.length > 0;
53
+ intent.set(image, usage.linear.length > 0 || !conflict && usage.srgb.length === 0 ? "linear" : "srgb");
54
+ if (conflict)
55
+ conflicts.push({ image, srgb: usage.srgb.join(","), linear: usage.linear.join(",") });
56
+ }
57
+ return { intent, conflicts };
58
+ }
5
59
  const GLTF_DIAGNOSTIC_TEXTURE_BINDINGS = [
6
60
  ["baseColor", "u_baseColorTexture", "u_baseColorTextureEnabled"],
7
61
  ["normal", "u_normalTexture", "u_normalTextureEnabled"],
@@ -119,6 +173,24 @@ export async function createGLTFRenderResources(asset, options = {}) {
119
173
  ...(options.sceneIndex !== undefined ? { sceneIndex: options.sceneIndex } : {}),
120
174
  ...(options.sceneName !== undefined ? { sceneName: options.sceneName } : {})
121
175
  });
176
+ // P5-1: per-image colour-space intent, resolved once from material slot usage and passed
177
+ // into the C-16 decode options (`ktx2BasisTranscoderOptions.colorSpace`) so a
178
+ // once-per-image transcode knows the intended space. Conflicts become `loadIssues`.
179
+ const colorSpaceIntent = imageColorSpaceIntent(asset);
180
+ const loadIssues = colorSpaceIntent.conflicts.map((conflict) => ({
181
+ code: "colorspace-conflict",
182
+ detail: `glTF image ${conflict.image} is used as sRGB (${conflict.srgb}) and linear (${conflict.linear}); decoding as linear.`
183
+ }));
184
+ const decodeOptionsForImage = (imageIndex) => options.materialsR185 === true
185
+ ? {
186
+ ...options,
187
+ ktx2BasisTranscoderOptions: {
188
+ ...options.ktx2BasisTranscoderOptions,
189
+ // Flag-on only: pre-intent decode options stay byte-identical off-flag.
190
+ colorSpace: colorSpaceIntent.intent.get(imageIndex) ?? "linear"
191
+ }
192
+ }
193
+ : options;
122
194
  const getTexture = async (info, colorSpace) => {
123
195
  const textureAsset = asset.textures[info.texture];
124
196
  const image = asset.images[info.image];
@@ -132,10 +204,10 @@ export async function createGLTFRenderResources(asset, options = {}) {
132
204
  setTextureLibraryEntry(textureLibrary, textureAsset.name, colorSpace, texture);
133
205
  return texture;
134
206
  }
135
- const decoder = options.imageDecoder ?? ((sourceImage, imageIndex, sourceAsset) => decodeImageInBrowser(sourceImage, imageIndex, sourceAsset, options));
207
+ const decoder = options.imageDecoder ?? ((sourceImage, imageIndex, sourceAsset) => decodeImageInBrowser(sourceImage, imageIndex, sourceAsset, decodeOptionsForImage(imageIndex)));
136
208
  const texturePromise = (async () => {
137
209
  const decoded = await decoder(image, info.image, asset);
138
- return new Texture({
210
+ const texture = new Texture({
139
211
  width: decoded.width,
140
212
  height: decoded.height,
141
213
  ...(decoded.format ? { format: decoded.format } : {}),
@@ -145,6 +217,16 @@ export async function createGLTFRenderResources(asset, options = {}) {
145
217
  ...(decoded.fallbackData ? { fallbackData: decoded.fallbackData } : {}),
146
218
  ...(decoded.fallbackMipLevels ? { fallbackMipLevels: decoded.fallbackMipLevels } : {})
147
219
  });
220
+ // R16/P2-11: the C-27 policy arrives as `textureBudget`/`maxTextureSize` on
221
+ // GLTFRenderResourceOptions (forwarded by the actor/pipeline). No device exists at
222
+ // this layer — the shared ledger accounts these uploads.
223
+ if (options.textureBudget !== undefined || options.maxTextureSize !== undefined) {
224
+ return applyTextureBudget(texture, {
225
+ ...(options.textureBudget !== undefined ? { textureBudgetBytes: options.textureBudget } : {}),
226
+ ...(options.maxTextureSize !== undefined ? { maxTextureSize: options.maxTextureSize } : {})
227
+ });
228
+ }
229
+ return texture;
148
230
  })();
149
231
  textureByImage.set(cacheKey, texturePromise);
150
232
  try {
@@ -166,9 +248,37 @@ export async function createGLTFRenderResources(asset, options = {}) {
166
248
  return fallback;
167
249
  }
168
250
  };
251
+ // When deduplication is enabled, materials with identical definitions share one runtime instance
252
+ // so renderer static batching (which keys on material identity) can collapse them. Declared before
253
+ // the try block so `setMaterialVariant` (R11) can rebuild missing contract variants on rebind.
254
+ const sharedMaterials = options.deduplicateIdenticalMaterials
255
+ ? new Map()
256
+ : null;
257
+ const buildMaterial = (material, contract) => {
258
+ if (!sharedMaterials) {
259
+ return createMaterial(asset, material, getTexture, contract, options.materialRenderStateOverrides, options.materialsR185, { materialsTransmission: options.materialsTransmission, transmission: options.transmission });
260
+ }
261
+ // The contract participates in the key: the same glTF material under two different runtime
262
+ // contracts (skinned vs instanced, for example) must not collapse into one instance.
263
+ const key = identicalMaterialKey(material, contract);
264
+ const existing = sharedMaterials.get(key);
265
+ if (existing)
266
+ return existing;
267
+ const created = createMaterial(asset, material, getTexture, contract, options.materialRenderStateOverrides, options.materialsR185, { materialsTransmission: options.materialsTransmission, transmission: options.transmission });
268
+ sharedMaterials.set(key, created);
269
+ return created;
270
+ };
169
271
  try {
272
+ // P5-4: under `A3D_QR_MATERIALS` prefer MikkTSpace-generated tangents (r185's frame) over
273
+ // the legacy triangle-average generator, unless `tangents === "legacy"` or the MikkTSpace
274
+ // module is not registered. `tangent-derivative-fallback` is reported inside createGeometry
275
+ // only when a tangent-needing program lands on the derivative frame.
276
+ const tangentGeneration = {
277
+ useMikkTSpace: options.materialsR185 === true && options.tangents !== "legacy" && mikkTSpaceAvailable(),
278
+ mikkTSpaceWanted: options.materialsR185 === true && options.tangents !== "legacy"
279
+ };
170
280
  for (const mesh of asset.meshes) {
171
- geometryLibrary.set(mesh.name, createGeometry(mesh, materialForMesh(asset, mesh, options.materialVariant)));
281
+ geometryLibrary.set(mesh.name, await createGeometry(mesh, materialForMesh(asset, mesh, options.materialVariant), tangentGeneration, loadIssues));
172
282
  if (mesh.morphTargets.length > 0) {
173
283
  morphTargetLibrary.set(mesh.name, mesh.morphTargets.map((target) => ({
174
284
  ...(target.name ? { name: target.name } : {}),
@@ -191,25 +301,6 @@ export async function createGLTFRenderResources(asset, options = {}) {
191
301
  });
192
302
  }
193
303
  const materialTasks = [];
194
- // When deduplication is enabled, materials with identical definitions share one runtime instance
195
- // so renderer static batching (which keys on material identity) can collapse them.
196
- const sharedMaterials = options.deduplicateIdenticalMaterials
197
- ? new Map()
198
- : null;
199
- const buildMaterial = (material, contract) => {
200
- if (!sharedMaterials) {
201
- return createMaterial(asset, material, getTexture, contract, options.materialRenderStateOverrides);
202
- }
203
- // The contract participates in the key: the same glTF material under two different runtime
204
- // contracts (skinned vs instanced, for example) must not collapse into one instance.
205
- const key = identicalMaterialKey(material, contract);
206
- const existing = sharedMaterials.get(key);
207
- if (existing)
208
- return existing;
209
- const created = createMaterial(asset, material, getTexture, contract, options.materialRenderStateOverrides);
210
- sharedMaterials.set(key, created);
211
- return created;
212
- };
213
304
  for (const material of asset.materials) {
214
305
  const runtimeKeys = [...runtimeMaterialContracts.entries()].filter(([, runtime]) => runtime.material === material.name);
215
306
  if (runtimeKeys.length === 0) {
@@ -228,11 +319,11 @@ export async function createGLTFRenderResources(asset, options = {}) {
228
319
  for (const mesh of asset.meshes) {
229
320
  for (const [key, runtime] of runtimeMaterialContracts) {
230
321
  if (runtime.material === mesh.material && !materialLibrary.has(key)) {
231
- materialLibrary.set(key, createDefaultGLTFMaterial(mesh, runtime.contract));
322
+ materialLibrary.set(key, createDefaultGLTFMaterial(mesh, runtime.contract, options.materialsR185));
232
323
  }
233
324
  }
234
325
  if (!materialLibrary.has(mesh.material)) {
235
- materialLibrary.set(mesh.material, createDefaultGLTFMaterial(mesh));
326
+ materialLibrary.set(mesh.material, createDefaultGLTFMaterial(mesh, {}, options.materialsR185));
236
327
  }
237
328
  }
238
329
  }
@@ -249,6 +340,7 @@ export async function createGLTFRenderResources(asset, options = {}) {
249
340
  materialLibrary,
250
341
  renderableBindings,
251
342
  materialFidelityDiagnostics,
343
+ loadIssues,
252
344
  morphTargetLibrary,
253
345
  textureLibrary,
254
346
  bounds,
@@ -287,9 +379,55 @@ export async function createGLTFRenderResources(asset, options = {}) {
287
379
  bounds
288
380
  };
289
381
  },
382
+ setMaterialVariant: (variant) => applyGLTFMaterialVariant({ asset, scene, materialLibrary, renderableBindings, buildMaterial, variant }),
290
383
  dispose: () => disposeGLTFRenderResourceMaps(geometryLibrary, textureLibrary)
291
384
  };
292
385
  }
386
+ /**
387
+ * PRD-04 P2/R11: KHR_materials_variants rebinding without a pipeline rebuild — the actor's variant
388
+ * switch remaps each renderable's material key. Meshes lacking a mapping for the active variant
389
+ * keep their authored material per the glTF spec; `null` restores the authored mapping.
390
+ */
391
+ async function applyGLTFMaterialVariant(options) {
392
+ const { asset, scene, materialLibrary, renderableBindings, buildMaterial, variant } = options;
393
+ if (variant !== null
394
+ && !renderableBindings.some((binding) => binding.materialVariants.some((entry) => entry.variant === variant))) {
395
+ return "variant-unknown";
396
+ }
397
+ const meshByGeometry = new Map(asset.meshes.map((mesh) => [mesh.name, mesh]));
398
+ const tasks = [];
399
+ scene.collectRenderables().forEach(({ renderable }, index) => {
400
+ const binding = renderableBindings[index];
401
+ const mesh = meshByGeometry.get(renderable.geometry);
402
+ if (!binding || !mesh)
403
+ return;
404
+ const parsed = parseGLTFRuntimeMaterialKey(renderable.material);
405
+ const contract = {
406
+ ...parsed.contract,
407
+ ...(renderable.skinning ? { skinned: true } : {}),
408
+ ...(renderable.instanceTransforms ? { instanced: true } : {})
409
+ };
410
+ const mapping = variant === null
411
+ ? undefined
412
+ : mesh.materialVariants.find((entry) => entry.variant === variant);
413
+ const targetName = mapping?.material ?? binding.sourceMaterialName;
414
+ const key = gltfRuntimeMaterialKey(targetName, contract);
415
+ if (key === renderable.material)
416
+ return;
417
+ if (!materialLibrary.has(key)) {
418
+ const materialDef = asset.materials.find((entry) => entry.name === targetName);
419
+ tasks.push((materialDef
420
+ ? buildMaterial(materialDef, contract)
421
+ : Promise.resolve(createDefaultGLTFMaterial({ ...mesh, material: targetName }, contract))).then((runtimeMaterial) => {
422
+ materialLibrary.set(key, runtimeMaterial);
423
+ }));
424
+ }
425
+ // Renderable.material is readonly at the type level; the rebind mutates the key in place.
426
+ renderable.material = key;
427
+ });
428
+ await Promise.all(tasks);
429
+ return "applied";
430
+ }
293
431
  function createGLTFRenderableBindings(asset, scene) {
294
432
  const meshByGeometry = new Map(asset.meshes.map((mesh) => [mesh.name, mesh]));
295
433
  return scene.collectRenderables().map(({ node, renderable }) => {
@@ -814,7 +952,7 @@ function materialForMesh(asset, mesh, materialVariant) {
814
952
  : mesh.materialIndex;
815
953
  return materialIndex === undefined ? undefined : asset.materials[materialIndex];
816
954
  }
817
- function createGeometry(mesh, material) {
955
+ async function createGeometry(mesh, material, tangentGeneration, loadIssues) {
818
956
  const usedTexCoordSets = usedRenderTexCoordSets(material);
819
957
  const texcoords = selectRenderTexcoords(mesh, material, 0);
820
958
  const texcoords1 = usedTexCoordSets.has(1) ? selectRenderTexcoords(mesh, material, 1) : [];
@@ -829,8 +967,20 @@ function createGeometry(mesh, material) {
829
967
  ? generateMeshNormals(mesh.positions, mesh.indices, mesh.topology)
830
968
  : mesh.normals;
831
969
  const renderTangents = needsTangent && mesh.tangents.length === 0
832
- ? generateMeshTangents(mesh.positions, texcoords, mesh.indices, mesh.topology, renderNormals)
970
+ ? await generateRenderTangents(mesh, texcoords, renderNormals, tangentGeneration)
833
971
  : mesh.tangents;
972
+ /*
973
+ * P5-4: `tangent-derivative-fallback` is reported only when a generated program will
974
+ * actually use the derivative frame — the material needs a tangent frame but the produced
975
+ * vertex format carries no tangent attribute (e.g. a normal-mapped mesh without UVs).
976
+ * A successful MikkTSpace->legacy fallback still produces the generated-attribute path.
977
+ */
978
+ if (tangentGeneration.mikkTSpaceWanted && materialNeedsTangents(material) && !format.hasAttribute("tangent")) {
979
+ loadIssues.push({
980
+ code: "tangent-derivative-fallback",
981
+ detail: `glTF mesh ${mesh.name}: material needs a tangent frame but no tangent attribute was produced; the generated program uses the derivative frame.`
982
+ });
983
+ }
834
984
  const vertices = new VertexBuffer(format, mesh.positions.length);
835
985
  for (let index = 0; index < mesh.positions.length; index += 1) {
836
986
  vertices.setAttribute(index, "position", mesh.positions[index]);
@@ -879,6 +1029,54 @@ function generateMeshNormals(positions, indices, topology) {
879
1029
  }
880
1030
  return normals.map((normal) => normalize3(normal));
881
1031
  }
1032
+ /**
1033
+ * P5-4: tangent generation for a mesh with no authored TANGENT. MikkTSpace when the lane flag
1034
+ * asked for it and the module is registered; otherwise the legacy triangle-average generator.
1035
+ * The `tangent-derivative-fallback` load issue is reported by the caller — only when the
1036
+ * produced vertex format leaves a tangent-needing program on the derivative frame.
1037
+ */
1038
+ async function generateRenderTangents(mesh, texcoords, renderNormals, tangentGeneration) {
1039
+ if (tangentGeneration.useMikkTSpace) {
1040
+ try {
1041
+ const generated = await generateMikkTSpaceTangents({
1042
+ positions: flatten3(mesh.positions),
1043
+ normals: flatten3(renderNormals),
1044
+ uvs: flatten2(texcoords),
1045
+ ...(mesh.indices && mesh.indices.length > 0 ? { indices: new Uint32Array(mesh.indices) } : {})
1046
+ });
1047
+ // The MikkTSpace helper unwelds indexed input (output is per-index, xyzw); scatter back
1048
+ // per vertex — last write wins, matching how indexed TANGENT attributes behave.
1049
+ const count = mesh.positions.length;
1050
+ const rows = Array.from({ length: count }, () => [0, 0, 0, 1]);
1051
+ if (mesh.indices && mesh.indices.length > 0) {
1052
+ mesh.indices.forEach((vertexIndex, i) => {
1053
+ rows[vertexIndex] = [generated[i * 4] ?? 0, generated[i * 4 + 1] ?? 0, generated[i * 4 + 2] ?? 0, generated[i * 4 + 3] ?? 1];
1054
+ });
1055
+ }
1056
+ else {
1057
+ for (let i = 0; i < count; i += 1) {
1058
+ rows[i] = [generated[i * 4] ?? 0, generated[i * 4 + 1] ?? 0, generated[i * 4 + 2] ?? 0, generated[i * 4 + 3] ?? 1];
1059
+ }
1060
+ }
1061
+ return rows;
1062
+ }
1063
+ catch {
1064
+ // MikkTSpace failure falls back to the legacy generator silently — the program still
1065
+ // runs the generated-attribute path (P5-4 reports the issue only for the derivative path).
1066
+ }
1067
+ }
1068
+ return generateMeshTangents(mesh.positions, texcoords, mesh.indices, mesh.topology, renderNormals);
1069
+ }
1070
+ function flatten3(rows) {
1071
+ const flat = new Float32Array(rows.length * 3);
1072
+ rows.forEach(([x, y, z], i) => { flat[i * 3] = x; flat[i * 3 + 1] = y; flat[i * 3 + 2] = z; });
1073
+ return flat;
1074
+ }
1075
+ function flatten2(rows) {
1076
+ const flat = new Float32Array(rows.length * 2);
1077
+ rows.forEach(([x, y], i) => { flat[i * 2] = x; flat[i * 2 + 1] = y; });
1078
+ return flat;
1079
+ }
882
1080
  function generateMeshTangents(positions, texcoords, indices, topology, normals) {
883
1081
  const tangents = Array.from({ length: positions.length }, () => [0, 0, 0]);
884
1082
  const bitangents = Array.from({ length: positions.length }, () => [0, 0, 0]);
@@ -1075,8 +1273,9 @@ function stableJson(value) {
1075
1273
  return nested;
1076
1274
  });
1077
1275
  }
1078
- async function createMaterial(asset, material, getTexture, options = {}, renderStateOverrides = []) {
1079
- const renderState = renderStateForGLTFMaterial(material, renderStateOverrides);
1276
+ async function createMaterial(asset, material, getTexture, options = {}, renderStateOverrides = [], materialsR185 = false, qrTransmission) {
1277
+ const qr = { ...qrTransmission, materialsR185 };
1278
+ const renderState = renderStateForGLTFMaterial(material, renderStateOverrides, materialsR185, qrTransmission);
1080
1279
  if (options.skinned && !material.unlit) {
1081
1280
  const [baseColorTexture, normalTexture, metallicRoughnessTexture, occlusionTexture, emissiveTexture, clearcoatTexture, clearcoatNormalTexture, sheenColorTexture, sheenRoughnessTexture, transmissionTexture, iridescenceTexture, iridescenceThicknessTexture, anisotropyTexture, volumeThicknessTexture] = await Promise.all([
1082
1281
  material.baseColorTexture
@@ -1149,10 +1348,10 @@ async function createMaterial(asset, material, getTexture, options = {}, renderS
1149
1348
  iridescenceThicknessTexture,
1150
1349
  anisotropyTexture,
1151
1350
  volumeThicknessTexture,
1152
- ...pbrExtensionScalarOptions(material)
1351
+ ...pbrExtensionScalarOptions(material, qr)
1153
1352
  });
1154
1353
  applyAlphaCutoff(runtimeMaterial, material);
1155
- await applyPBRExtensionParameters(asset, runtimeMaterial, material, getTexture);
1354
+ await applyPBRExtensionParameters(asset, runtimeMaterial, material, getTexture, qr);
1156
1355
  return runtimeMaterial;
1157
1356
  }
1158
1357
  if (options.instanced && material.unlit && !material.baseColorTexture) {
@@ -1162,7 +1361,7 @@ async function createMaterial(asset, material, getTexture, options = {}, renderS
1162
1361
  renderState
1163
1362
  });
1164
1363
  applyAlphaCutoff(runtimeMaterial, material);
1165
- await applyPBRExtensionParameters(asset, runtimeMaterial, material, getTexture);
1364
+ await applyPBRExtensionParameters(asset, runtimeMaterial, material, getTexture, qr);
1166
1365
  return runtimeMaterial;
1167
1366
  }
1168
1367
  if (material.unlit) {
@@ -1177,7 +1376,7 @@ async function createMaterial(asset, material, getTexture, options = {}, renderS
1177
1376
  renderState
1178
1377
  });
1179
1378
  applyAlphaCutoff(runtimeMaterial, material);
1180
- await applyPBRExtensionParameters(asset, runtimeMaterial, material, getTexture);
1379
+ await applyPBRExtensionParameters(asset, runtimeMaterial, material, getTexture, qr);
1181
1380
  return runtimeMaterial;
1182
1381
  }
1183
1382
  const runtimeMaterial = new UnlitMaterial({
@@ -1186,7 +1385,7 @@ async function createMaterial(asset, material, getTexture, options = {}, renderS
1186
1385
  renderState
1187
1386
  });
1188
1387
  applyAlphaCutoff(runtimeMaterial, material);
1189
- await applyPBRExtensionParameters(asset, runtimeMaterial, material, getTexture);
1388
+ await applyPBRExtensionParameters(asset, runtimeMaterial, material, getTexture, qr);
1190
1389
  return runtimeMaterial;
1191
1390
  }
1192
1391
  if (options.instanced && !requiresTexturedPBRMaterial(material) && !material.baseColorTexture) {
@@ -1200,7 +1399,7 @@ async function createMaterial(asset, material, getTexture, options = {}, renderS
1200
1399
  emissiveStrength: material.emissiveStrength
1201
1400
  });
1202
1401
  applyAlphaCutoff(runtimeMaterial, material);
1203
- await applyPBRExtensionParameters(asset, runtimeMaterial, material, getTexture);
1402
+ await applyPBRExtensionParameters(asset, runtimeMaterial, material, getTexture, qr);
1204
1403
  return runtimeMaterial;
1205
1404
  }
1206
1405
  if (requiresTexturedPBRMaterial(material)) {
@@ -1213,13 +1412,13 @@ async function createMaterial(asset, material, getTexture, options = {}, renderS
1213
1412
  const runtimeMaterial = new TexturedPBRMaterial({
1214
1413
  name: material.name,
1215
1414
  renderState,
1216
- baseColor: renderPbrBaseColorFactor(material),
1415
+ baseColor: renderPbrBaseColorFactor(material, qr),
1217
1416
  metallic: material.metallicFactor,
1218
- roughness: renderPbrRoughnessFactor(material),
1417
+ roughness: renderPbrRoughnessFactor(material, qr),
1219
1418
  emissiveColor: material.emissiveFactor,
1220
1419
  emissiveStrength: material.emissiveStrength,
1221
1420
  textureTexCoords: pbrTextureTexCoords(material),
1222
- ...pbrExtensionScalarOptions(material),
1421
+ ...pbrExtensionScalarOptions(material, qr),
1223
1422
  ...extensionTextureOptions,
1224
1423
  baseColorTexture,
1225
1424
  baseColorSampler: createSampler(material.baseColorTexture ? asset.textures[material.baseColorTexture.texture] : undefined),
@@ -1240,7 +1439,7 @@ async function createMaterial(asset, material, getTexture, options = {}, renderS
1240
1439
  emissiveTextureTransform: material.emissiveTexture?.transform
1241
1440
  });
1242
1441
  applyAlphaCutoff(runtimeMaterial, material);
1243
- await applyPBRExtensionParameters(asset, runtimeMaterial, material, getTexture);
1442
+ await applyPBRExtensionParameters(asset, runtimeMaterial, material, getTexture, qr);
1244
1443
  return runtimeMaterial;
1245
1444
  }
1246
1445
  if (material.baseColorTexture) {
@@ -1254,27 +1453,37 @@ async function createMaterial(asset, material, getTexture, options = {}, renderS
1254
1453
  renderState
1255
1454
  });
1256
1455
  applyAlphaCutoff(runtimeMaterial, material);
1257
- await applyPBRExtensionParameters(asset, runtimeMaterial, material, getTexture);
1456
+ await applyPBRExtensionParameters(asset, runtimeMaterial, material, getTexture, qr);
1258
1457
  return runtimeMaterial;
1259
1458
  }
1260
1459
  const runtimeMaterial = new PBRMaterial({
1261
1460
  name: material.name,
1262
1461
  renderState,
1263
- baseColor: renderPbrBaseColorFactor(material),
1462
+ baseColor: renderPbrBaseColorFactor(material, qr),
1264
1463
  metallic: material.metallicFactor,
1265
- roughness: renderPbrRoughnessFactor(material),
1464
+ roughness: renderPbrRoughnessFactor(material, qr),
1266
1465
  emissiveColor: material.emissiveFactor,
1267
1466
  emissiveStrength: material.emissiveStrength,
1268
- ...pbrExtensionScalarOptions(material)
1467
+ ...pbrExtensionScalarOptions(material, qr)
1269
1468
  });
1270
1469
  applyAlphaCutoff(runtimeMaterial, material);
1271
- await applyPBRExtensionParameters(asset, runtimeMaterial, material, getTexture);
1470
+ await applyPBRExtensionParameters(asset, runtimeMaterial, material, getTexture, qr);
1272
1471
  return runtimeMaterial;
1273
1472
  }
1274
- function createDefaultGLTFMaterial(mesh, options = {}) {
1473
+ function createDefaultGLTFMaterial(mesh, options = {}, materialsR185 = false) {
1275
1474
  // Use a neutral light-gray base color instead of glTF-spec white ([1,1,1,1])
1276
1475
  // so missing-material fallbacks do not blow out as bright white artifacts.
1277
- const defaults = {
1476
+ // PRD-04 P2-12/E23: flag-on uses the glTF-spec default ([1,1,1,1], roughness 1).
1477
+ // T0-18(a): metallic stays 0, not the spec's 1 — a fully metallic surface has
1478
+ // no diffuse response and renders near-black until a C-09 probe/IBL is bound;
1479
+ // restore spec metallic once environment lighting is guaranteed on this path.
1480
+ const defaults = materialsR185 ? {
1481
+ name: mesh.material,
1482
+ baseColor: [1, 1, 1, 1],
1483
+ metallic: 0,
1484
+ roughness: 1,
1485
+ environmentIntensity: DEFAULT_PBR_ENVIRONMENT_INTENSITY
1486
+ } : {
1278
1487
  name: mesh.material,
1279
1488
  baseColor: [0.76, 0.74, 0.72, 1],
1280
1489
  metallic: 0,
@@ -1296,23 +1505,38 @@ function createDefaultGLTFMaterial(mesh, options = {}) {
1296
1505
  * so we allow a small epsilon while still catching genuine fades.
1297
1506
  */
1298
1507
  const BLEND_OPAQUE_ALPHA_THRESHOLD = 0.996;
1299
- export function renderStateForGLTFMaterial(material, overrides = []) {
1300
- const blend = requiresTransparentRenderState(material);
1301
- const cullBack = usesUnbackedScalarTransmission(material) || usesOpaqueDoubleSidedClearcoatShell(material);
1508
+ export function renderStateForGLTFMaterial(material, overrides = [], materialsR185 = false, qrTransmission) {
1509
+ const qr = { ...qrTransmission, materialsR185 };
1510
+ const blend = requiresTransparentRenderState(material, qr);
1511
+ // PRD-04 P2-12/E23: the double-sided + metallic + clearcoat back-face cull is a fudge;
1512
+ // flag-on honours authored doubleSided.
1513
+ const cullBack = usesUnbackedScalarTransmission(material, qr)
1514
+ || (!materialsR185 && usesOpaqueDoubleSidedClearcoatShell(material));
1515
+ // P5-5 (C-04): under the lane flag a MASK material requests alpha-to-coverage. The material
1516
+ // RenderState type predates C-04's `RenderCommandState.alphaToCoverage`, so the field rides
1517
+ // the state object at runtime (validateRenderState preserves unknown keys); the
1518
+ // `prd04.alphaToCoverage` feature reads it and only honours it when the tier has MSAA
1519
+ // samples. Flag-off omits the key entirely.
1302
1520
  const baseState = {
1303
1521
  cullMode: cullBack ? "back" : material.doubleSided ? "none" : "back",
1304
1522
  blend,
1305
- depthWrite: !blend
1523
+ depthWrite: !blend,
1524
+ ...(materialsR185 && material.alphaMode === "MASK" ? { alphaToCoverage: true } : {})
1306
1525
  };
1307
1526
  const override = overrides.find((entry) => matchesTextOrPattern(material.name, entry.materialName));
1308
1527
  return override ? { ...baseState, ...override.renderState } : baseState;
1309
1528
  }
1310
- function requiresTransparentRenderState(material) {
1529
+ function requiresTransparentRenderState(material, qr) {
1311
1530
  if (material.alphaMode === "BLEND")
1312
1531
  return !isEffectivelyOpaqueBlendMaterial(material);
1313
1532
  if (material.alphaMode !== "OPAQUE")
1314
1533
  return false;
1315
- if (usesUnbackedScalarTransmission(material))
1534
+ if (usesUnbackedScalarTransmission(material, qr))
1535
+ return false;
1536
+ // E22: on the real path transmissive items ride the C-01 `transmission` phase
1537
+ // queue (blend must stay off — the sorter rejects blended transmission), not
1538
+ // the legacy transparent queue.
1539
+ if (qrTransmissionRealPath(qr))
1316
1540
  return false;
1317
1541
  return materialHasTransmissionOrVolume(material);
1318
1542
  }
@@ -1356,12 +1580,31 @@ function materialHasTransmissionOrVolume(material) {
1356
1580
  || (material.volume?.thicknessFactor ?? 0) > 0.001
1357
1581
  || material.volume?.thicknessTexture !== undefined;
1358
1582
  }
1359
- function usesUnbackedScalarTransmission(material) {
1360
- return material.alphaMode === "OPAQUE"
1583
+ /**
1584
+ * E22 gate (PRD-04 P4-3): real transmission rendering exists — so the legacy
1585
+ * unbacked-scalar-transmission rewrite must not run — exactly when
1586
+ * `A3D_QR_MATERIALS` **and** `A3D_QR_MATERIALS_TRANSMISSION` are on **and**
1587
+ * `renderer.transmission === "env"` (the env-refraction path).
1588
+ * T0-18(b): `programCacheSlot.provided` is not evidence — it is true whenever
1589
+ * the prd01 barrel loaded, which skipped E22 even though the scene-copy path
1590
+ * did not draw. `"auto"` keeps the E22 rewrite until the transmission target
1591
+ * is bound end-to-end on generated programs (04-S10).
1592
+ */
1593
+ function qrTransmissionRealPath(qr) {
1594
+ return qr?.materialsR185 === true
1595
+ && qr?.materialsTransmission === true
1596
+ && qr.transmission === "env";
1597
+ }
1598
+ function usesUnbackedScalarTransmission(material, qr) {
1599
+ const unbacked = material.alphaMode === "OPAQUE"
1361
1600
  && (material.transmission?.factor ?? 0) > 0.001
1362
1601
  && material.transmission?.texture === undefined
1363
1602
  && material.diffuseTransmission === undefined
1364
1603
  && material.volume === undefined;
1604
+ // E22 path-gated skip: real transmission handles the material; no rewrite.
1605
+ if (unbacked && qrTransmissionRealPath(qr))
1606
+ return false;
1607
+ return unbacked;
1365
1608
  }
1366
1609
  function usesOpaqueDoubleSidedClearcoatShell(material) {
1367
1610
  return material.alphaMode === "OPAQUE"
@@ -1370,16 +1613,16 @@ function usesOpaqueDoubleSidedClearcoatShell(material) {
1370
1613
  && material.metallicFactor > 0.35
1371
1614
  && material.roughnessFactor <= 0.42;
1372
1615
  }
1373
- function renderPbrBaseColorFactor(material) {
1374
- if (!usesUnbackedScalarTransmission(material))
1616
+ function renderPbrBaseColorFactor(material, qr) {
1617
+ if (!usesUnbackedScalarTransmission(material, qr))
1375
1618
  return material.baseColorFactor;
1376
1619
  const maxColor = Math.max(material.baseColorFactor[0], material.baseColorFactor[1], material.baseColorFactor[2]);
1377
1620
  if (maxColor < 0.8)
1378
1621
  return material.baseColorFactor;
1379
1622
  return [0.028, 0.036, 0.044, material.baseColorFactor[3]];
1380
1623
  }
1381
- function renderPbrRoughnessFactor(material) {
1382
- if (!usesUnbackedScalarTransmission(material))
1624
+ function renderPbrRoughnessFactor(material, qr) {
1625
+ if (!usesUnbackedScalarTransmission(material, qr))
1383
1626
  return material.roughnessFactor;
1384
1627
  return Math.max(material.roughnessFactor, 0.72);
1385
1628
  }
@@ -1418,15 +1661,15 @@ function usesNearestSampler(texture) {
1418
1661
  const sampler = createSampler(texture);
1419
1662
  return sampler.magFilter === "nearest" || sampler.minFilter.startsWith("nearest");
1420
1663
  }
1421
- function pbrExtensionScalarOptions(material) {
1664
+ function pbrExtensionScalarOptions(material, qr) {
1422
1665
  return {
1423
1666
  ...(material.clearcoat ? {
1424
1667
  clearcoatFactor: material.clearcoat.factor,
1425
1668
  clearcoatRoughnessFactor: renderClearcoatRoughnessFactor(material)
1426
1669
  } : {}),
1427
1670
  ...(material.transmission ? {
1428
- transmissionFactor: usesUnbackedScalarTransmission(material) ? 0 : material.transmission.factor,
1429
- transmissionFallbackEnergy: renderTransmissionFallbackEnergy(material)
1671
+ transmissionFactor: usesUnbackedScalarTransmission(material, qr) ? 0 : material.transmission.factor,
1672
+ transmissionFallbackEnergy: renderTransmissionFallbackEnergy(material, qr)
1430
1673
  } : {}),
1431
1674
  ...(material.diffuseTransmission ? {
1432
1675
  diffuseTransmissionFactor: material.diffuseTransmission.factor,
@@ -1550,9 +1793,9 @@ async function pbrExtensionTextureOptions(asset, material, getTexture) {
1550
1793
  } : {})
1551
1794
  };
1552
1795
  }
1553
- function renderTransmissionFallbackEnergy(material) {
1796
+ function renderTransmissionFallbackEnergy(material, qr) {
1554
1797
  const usesUnbackedCutoutTransmission = material.alphaMode === "MASK" && material.transmission?.texture === undefined && material.volume === undefined;
1555
- return usesUnbackedCutoutTransmission || usesUnbackedScalarTransmission(material) ? 0 : 0.08;
1798
+ return usesUnbackedCutoutTransmission || usesUnbackedScalarTransmission(material, qr) ? 0 : 0.08;
1556
1799
  }
1557
1800
  function pbrTextureTexCoords(material) {
1558
1801
  return {
@@ -1580,7 +1823,7 @@ function pbrTextureTexCoords(material) {
1580
1823
  function renderTexCoord(texCoord) {
1581
1824
  return texCoord === 1 ? 1 : 0;
1582
1825
  }
1583
- async function applyPBRExtensionParameters(asset, runtimeMaterial, material, getTexture) {
1826
+ async function applyPBRExtensionParameters(asset, runtimeMaterial, material, getTexture, qr) {
1584
1827
  if (material.clearcoat) {
1585
1828
  runtimeMaterial.setParameter("u_clearcoatFactor", material.clearcoat.factor);
1586
1829
  runtimeMaterial.setParameter("u_clearcoatRoughnessFactor", renderClearcoatRoughnessFactor(material));
@@ -1596,7 +1839,7 @@ async function applyPBRExtensionParameters(asset, runtimeMaterial, material, get
1596
1839
  }
1597
1840
  }
1598
1841
  if (material.transmission) {
1599
- runtimeMaterial.setParameter("u_transmissionFactor", usesUnbackedScalarTransmission(material) ? 0 : material.transmission.factor);
1842
+ runtimeMaterial.setParameter("u_transmissionFactor", usesUnbackedScalarTransmission(material, qr) ? 0 : material.transmission.factor);
1600
1843
  if (material.transmission.texture) {
1601
1844
  await setTextureParameter(asset, runtimeMaterial, "u_transmissionTexture", material.transmission.texture, "linear", getTexture);
1602
1845
  }
@@ -1758,61 +2001,9 @@ function createSampler(texture) {
1758
2001
  };
1759
2002
  return new Sampler(samplerDescriptor);
1760
2003
  }
1761
- async function decodeImageInBrowser(image, _imageIndex, asset, options = {}) {
1762
- if (isKTX2BasisImage(image)) {
1763
- const bytes = await readImageBytes(asset, image);
1764
- return transcodeKTX2BasisTexture(bytes, {
1765
- ...options.ktx2BasisTranscoderOptions,
1766
- targetFormat: options.ktx2BasisTargetFormat ?? options.ktx2BasisTranscoderOptions?.targetFormat
1767
- });
1768
- }
1769
- if (typeof createImageBitmap === "function") {
1770
- const blob = image.data
1771
- ? new Blob([image.data], { type: image.mimeType ?? "application/octet-stream" })
1772
- : await fetchImageBlob(asset, image);
1773
- const bitmap = await createImageBitmap(blob, {
1774
- colorSpaceConversion: "none",
1775
- premultiplyAlpha: "none"
1776
- });
1777
- return { width: bitmap.width, height: bitmap.height, source: bitmap, colorSpace: "srgb" };
1778
- }
1779
- const ImageCtor = globalThis.Image;
1780
- if (!ImageCtor || !image.uri) {
1781
- throw new Error("glTF image decoding requires createImageBitmap, HTMLImageElement, or a custom imageDecoder");
1782
- }
1783
- const imageElement = new ImageCtor();
1784
- const url = resolveImageUrl(asset.url, image.uri);
1785
- return new Promise((resolve, reject) => {
1786
- imageElement.onload = () => resolve({ width: imageElement.width, height: imageElement.height, source: imageElement, colorSpace: "srgb" });
1787
- imageElement.onerror = () => reject(new Error(`glTF image decode failed for ${url}`));
1788
- imageElement.src = url;
1789
- });
1790
- }
1791
2004
  function createSamplerForInfo(asset, info) {
1792
2005
  return createSampler(asset.textures[info.texture]);
1793
2006
  }
1794
- async function readImageBytes(asset, image) {
1795
- if (image.data)
1796
- return image.data.slice(0);
1797
- const blob = await fetchImageBlob(asset, image);
1798
- return blob.arrayBuffer();
1799
- }
1800
- async function fetchImageBlob(asset, image) {
1801
- if (!image.uri) {
1802
- throw new Error("glTF image has no uri or embedded data");
1803
- }
1804
- if (typeof fetch !== "function") {
1805
- throw new Error("glTF image fetch requires fetch or a custom imageDecoder");
1806
- }
1807
- const response = await fetch(resolveImageUrl(asset.url, image.uri));
1808
- if (!response.ok) {
1809
- throw new Error(`glTF image request failed with ${response.status}`);
1810
- }
1811
- return response.blob();
1812
- }
1813
- function isKTX2BasisImage(image) {
1814
- return image.mimeType === "image/ktx2" || /\.ktx2(?:[?#]|$)/i.test(image.uri ?? "");
1815
- }
1816
2007
  function createFallbackTexture(label, colorSpace) {
1817
2008
  return new Texture({
1818
2009
  width: 1,
@@ -1828,38 +2019,4 @@ function warnMissingGLTFTexture(textureName, imageUri, reason) {
1828
2019
  const source = imageUri ? ` (${imageUri})` : "";
1829
2020
  console.warn(`glTF texture "${textureName}"${source} could not be loaded; falling back to baseColorFactor. Reason: ${reason}`);
1830
2021
  }
1831
- function resolveImageUrl(assetUrl, imageUri) {
1832
- if (/^(?:data:|blob:|https?:|file:)/i.test(imageUri))
1833
- return imageUri;
1834
- if (assetUrl.startsWith("data:")) {
1835
- throw new Error(`Relative glTF image uri ${imageUri} cannot be resolved from a data URL asset`);
1836
- }
1837
- return new URL(imageUri, resolveAbsoluteAssetBase(assetUrl)).toString();
1838
- }
1839
- /**
1840
- * Derive an absolute base URL for resolving relative texture URIs.
1841
- *
1842
- * Catalog assets are often loaded from root-relative paths (e.g.
1843
- * `/aura-assets/x.glb`). `new URL(uri, base)` requires `base` to be absolute, so
1844
- * a bare relative `assetUrl` throws `Failed to construct 'URL': Invalid base URL`.
1845
- * If the asset URL is already absolute we use it directly; otherwise we resolve it
1846
- * against the document/origin base when running in a browser, falling back to a
1847
- * synthetic `file:///` origin so resolution never throws in non-browser contexts.
1848
- */
1849
- function resolveAbsoluteAssetBase(assetUrl) {
1850
- if (/^[a-z][a-z0-9+.-]*:/i.test(assetUrl))
1851
- return assetUrl;
1852
- const documentBase = (typeof document !== "undefined" && document.baseURI) ||
1853
- (typeof location !== "undefined" && location.href) ||
1854
- undefined;
1855
- if (documentBase) {
1856
- try {
1857
- return new URL(assetUrl, documentBase).toString();
1858
- }
1859
- catch {
1860
- // fall through to synthetic base below
1861
- }
1862
- }
1863
- return new URL(assetUrl, "file:///").toString();
1864
- }
1865
2022
  //# sourceMappingURL=GLTFRenderResources.js.map