@aura3d/assets 1.3.3 → 1.4.1

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 (73) hide show
  1. package/dist/GLTFLoader.js +1 -0
  2. package/dist/GLTFLoader.js.map +1 -1
  3. package/dist/GLTFRenderResources.d.ts.map +1 -1
  4. package/dist/GLTFRenderResources.js +1 -0
  5. package/dist/GLTFRenderResources.js.map +1 -1
  6. package/package.json +38 -5
  7. package/src/AdvancedAssetCorpus.ts +0 -454
  8. package/src/AssetBundleCacheFixtures.ts +0 -290
  9. package/src/AssetCache.ts +0 -76
  10. package/src/AssetCompatibility.ts +0 -224
  11. package/src/AssetCorpus.ts +0 -329
  12. package/src/AssetDependencyGraph.ts +0 -83
  13. package/src/AssetHandle.ts +0 -74
  14. package/src/AssetImportPreflight.ts +0 -179
  15. package/src/AssetInspection.ts +0 -706
  16. package/src/AssetLoader.ts +0 -36
  17. package/src/AssetManager.ts +0 -211
  18. package/src/AssetRegistry.ts +0 -44
  19. package/src/AssetRenderDefaults.ts +0 -36
  20. package/src/AudioLoader.ts +0 -41
  21. package/src/BlenderExportValidation.ts +0 -227
  22. package/src/CarConceptMaterialStability.ts +0 -1405
  23. package/src/EXRLoader.ts +0 -1
  24. package/src/ExternalParityGLTFCorpus.ts +0 -65
  25. package/src/GLTFAnimationRuntime.ts +0 -1416
  26. package/src/GLTFAutoFit.ts +0 -133
  27. package/src/GLTFCompressionDecoders.ts +0 -190
  28. package/src/GLTFExtensionSupport.ts +0 -138
  29. package/src/GLTFLoader.ts +0 -4059
  30. package/src/GLTFRenderResources.ts +0 -2269
  31. package/src/HDRLoader.ts +0 -1
  32. package/src/ImageLoader.ts +0 -52
  33. package/src/ImportPipeline.ts +0 -148
  34. package/src/KTX2BasisTextureTranscoder.ts +0 -225
  35. package/src/LoadContext.ts +0 -60
  36. package/src/MaterialLoader.ts +0 -166
  37. package/src/MeshOptimization.ts +0 -93
  38. package/src/OBJLoader.ts +0 -551
  39. package/src/SceneAnalysisFixtures.ts +0 -438
  40. package/src/SceneLoader.ts +0 -62
  41. package/src/ShaderLoader.ts +0 -30
  42. package/src/TextureLoader.ts +0 -30
  43. package/src/TexturePipeline.ts +0 -96
  44. package/src/TextureStreaming.ts +0 -20
  45. package/src/WorkerAssetJobs.ts +0 -56
  46. package/src/advanced-gallery/index.ts +0 -18
  47. package/src/asset-corpus/GLTFSceneLoader.ts +0 -9
  48. package/src/asset-corpus/ProductionAssetCorpus.ts +0 -230
  49. package/src/asset-corpus/ProductionAssetPipeline.ts +0 -5
  50. package/src/asset-corpus/ProductionGLTFRenderPipeline.ts +0 -182
  51. package/src/asset-corpus/ProductionHDRTextureLoader.ts +0 -2
  52. package/src/asset-corpus/ProductionKTX2TextureLoader.ts +0 -2
  53. package/src/asset-corpus/ProductionTextureLoader.ts +0 -2
  54. package/src/asset-corpus/index.ts +0 -29
  55. package/src/browser-index.ts +0 -229
  56. package/src/createRenderableScene.ts +0 -128
  57. package/src/index.ts +0 -325
  58. package/src/loadRenderableAsset.ts +0 -67
  59. package/src/loaders/CubeTextureLoader.ts +0 -8
  60. package/src/loaders/EXRLoader.ts +0 -10
  61. package/src/loaders/HDRLoader.ts +0 -7
  62. package/src/loaders/KTX2Loader.ts +0 -7
  63. package/src/loaders/LoaderDiagnostics.ts +0 -36
  64. package/src/loaders/MTLLoader.ts +0 -20
  65. package/src/loaders/OBJLoader.ts +0 -24
  66. package/src/loaders/TextureLoader.ts +0 -14
  67. package/src/loaders/ThreeCompatGLTFLoader.ts +0 -19
  68. package/src/loaders/index.ts +0 -14
  69. package/src/threejs-compatibility/ThreeCompatAssetProvenance.ts +0 -51
  70. package/src/threejs-compatibility/ThreeCompatAssetRegistry.ts +0 -61
  71. package/src/threejs-example-parity/index.ts +0 -262
  72. package/tests/assets.test.ts +0 -393
  73. package/tsconfig.json +0 -7
@@ -1,2269 +0,0 @@
1
- import {
2
- Geometry,
3
- IndexBuffer,
4
- InstancedPBRMaterial,
5
- InstancedUnlitMaterial,
6
- Material,
7
- PBRMaterial,
8
- Sampler,
9
- SkinnedLitMaterial,
10
- Texture,
11
- TextureBinding,
12
- TexturedPBRMaterial,
13
- TexturedUnlitMaterial,
14
- UnlitMaterial,
15
- VertexBuffer,
16
- createExternalParityEnvironmentLighting,
17
- computePerspectiveCameraFrame,
18
- computeSkinnedMorphTargetEnvelopeBounds,
19
- type CameraFrameBounds,
20
- type CameraFrameViewport,
21
- type CameraLike,
22
- type CollectedLight,
23
- type EnvironmentLightingOptions,
24
- type ForwardShadowMapOptions,
25
- type PerspectiveCameraFrame,
26
- type PerspectiveCameraFrameOptions,
27
- type VertexAttributeDescriptor,
28
- VertexFormat,
29
- DEFAULT_PBR_ENVIRONMENT_INTENSITY,
30
- type MorphTargetDelta,
31
- type RenderItem,
32
- type RendererCameraPolicy,
33
- type RendererInput,
34
- type RendererPostProcessOptions,
35
- type RendererShadowOptions,
36
- type RenderSource,
37
- type RenderState,
38
- type SamplerDescriptor,
39
- type TextureFormat,
40
- type TextureMipLevelDescriptor,
41
- type TexturePixelData,
42
- type TexturedPBRMaterialOptions,
43
- type TexturedPBRTextureSlot,
44
- isTexturedPbrTextureSlotShaderActive,
45
- DEFAULT_TEXTURED_PBR_SHADER_NAME
46
- } from "@aura3d/rendering";
47
- import { Bounds3 as SceneBounds3, multiplyMat4, type Mat4 } from "@aura3d/scene";
48
- import {
49
- parseGLTFRuntimeMaterialKey,
50
- type GLTFAsset,
51
- type GLTFImageAsset,
52
- type GLTFMaterialAsset,
53
- type GLTFMaterialVariantMappingAsset,
54
- type GLTFMeshAsset,
55
- type GLTFResolvedTextureInfo,
56
- type GLTFSceneCreateOptions,
57
- type GLTFTextureAsset
58
- } from "./GLTFLoader";
59
- import { transcodeKTX2BasisTexture, type KTX2BasisTargetFormat, type KTX2BasisTextureTranscoderOptions } from "./KTX2BasisTextureTranscoder";
60
-
61
- export interface DecodedGLTFImage {
62
- readonly width: number;
63
- readonly height: number;
64
- readonly format?: TextureFormat;
65
- readonly colorSpace?: "srgb" | "linear";
66
- readonly data?: TexturePixelData;
67
- readonly mipLevels?: readonly TextureMipLevelDescriptor[];
68
- readonly source?: TexImageSource;
69
- readonly fallbackData?: Uint8Array | Uint8ClampedArray;
70
- readonly fallbackMipLevels?: readonly TextureMipLevelDescriptor[];
71
- }
72
-
73
- export type GLTFImageDecoder = (image: GLTFImageAsset, imageIndex: number, asset: GLTFAsset) => DecodedGLTFImage | Promise<DecodedGLTFImage>;
74
-
75
- export interface GLTFRenderResourceOptions {
76
- readonly imageDecoder?: GLTFImageDecoder;
77
- readonly ktx2BasisTargetFormat?: KTX2BasisTargetFormat;
78
- readonly ktx2BasisTranscoderOptions?: KTX2BasisTextureTranscoderOptions;
79
- readonly materialVariant?: GLTFSceneCreateOptions["materialVariant"];
80
- readonly sceneIndex?: GLTFSceneCreateOptions["sceneIndex"];
81
- readonly sceneName?: GLTFSceneCreateOptions["sceneName"];
82
- readonly materialRenderStateOverrides?: readonly GLTFMaterialRenderStateOverride[];
83
- /**
84
- * When `true`, a texture that fails to fetch/decode aborts the whole asset load
85
- * (legacy behavior). When omitted/`false` (default), the loader logs a warning
86
- * and substitutes a neutral 1x1 texture so the material falls back to its
87
- * `baseColorFactor` and the rest of the asset still loads.
88
- */
89
- readonly failOnMissingTexture?: boolean;
90
- }
91
-
92
- export interface GLTFMaterialRenderStateOverride {
93
- readonly materialName: string | RegExp;
94
- readonly renderState: Partial<RenderState>;
95
- readonly reason?: string;
96
- }
97
-
98
- export interface GLTFRenderResources {
99
- readonly scene: ReturnType<GLTFAsset["createScene"]>;
100
- readonly geometryLibrary: ReadonlyMap<string, Geometry>;
101
- readonly materialLibrary: ReadonlyMap<string, Material>;
102
- readonly renderableBindings: readonly GLTFRenderableBinding[];
103
- readonly materialFidelityDiagnostics: readonly GLTFRenderResourceMaterialFidelityDiagnostic[];
104
- readonly morphTargetLibrary: ReadonlyMap<string, readonly MorphTargetDelta[]>;
105
- readonly textureLibrary: ReadonlyMap<string, Texture>;
106
- readonly bounds: CameraFrameBounds;
107
- collectMaterialOverrideTargets(query?: GLTFMaterialOverrideQuery): readonly GLTFMaterialOverrideTarget[];
108
- createCameraFrame(viewport: CameraFrameViewport, options?: PerspectiveCameraFrameOptions): PerspectiveCameraFrame;
109
- toRenderSource(options?: GLTFRenderSourceOptions): RenderSource;
110
- toRendererInput(viewport: CameraFrameViewport, options?: GLTFRendererInputOptions): GLTFRendererInput;
111
- dispose(): void;
112
- }
113
-
114
- export interface GLTFRenderableBinding {
115
- readonly nodeName: string;
116
- readonly geometryKey: string;
117
- readonly materialKey: string;
118
- readonly sourceMaterialName: string;
119
- readonly sourceMaterialIndex?: number;
120
- readonly sourceMeshIndex?: number;
121
- readonly primitiveIndex?: number;
122
- readonly materialVariants: readonly GLTFMaterialVariantMappingAsset[];
123
- readonly skinned: boolean;
124
- readonly instanced: boolean;
125
- }
126
-
127
- export type GLTFRenderResourceMaterialFidelityIssue =
128
- | "unsupported-texcoord-set"
129
- | "generated-tangent-uv-mismatch";
130
-
131
- export interface GLTFRenderResourceMaterialFidelityDiagnostic {
132
- readonly issue: GLTFRenderResourceMaterialFidelityIssue;
133
- readonly slot: TexturedPBRTextureSlot;
134
- readonly texCoord: number;
135
- readonly renderedTexCoord: number;
136
- readonly nodeName: string;
137
- readonly geometryKey: string;
138
- readonly materialKey: string;
139
- readonly sourceMaterialName: string;
140
- readonly sourceMaterialIndex?: number;
141
- readonly sourceMeshIndex?: number;
142
- readonly primitiveIndex?: number;
143
- readonly detail: string;
144
- }
145
-
146
- export interface GLTFMaterialOverrideQuery {
147
- readonly nodeName?: string | RegExp;
148
- readonly sourceMaterialName?: string | RegExp;
149
- readonly materialKey?: string | RegExp;
150
- readonly variant?: string;
151
- readonly uniqueMaterials?: boolean;
152
- }
153
-
154
- export interface GLTFMaterialOverrideTarget extends GLTFRenderableBinding {
155
- readonly material: Material;
156
- }
157
-
158
- export interface GLTFRendererInput extends RendererInput {
159
- readonly source: RenderSource;
160
- readonly camera: CameraLike;
161
- readonly frame: PerspectiveCameraFrame;
162
- readonly bounds: CameraFrameBounds;
163
- }
164
-
165
- export interface GLTFRendererInputOptions extends GLTFRenderSourceOptions {
166
- readonly frame?: PerspectiveCameraFrameOptions;
167
- }
168
-
169
- export type GLTFRenderQualityPreset = "default" | "studio-preview" | "hdr-studio-preview";
170
-
171
- export interface GLTFRenderSourceOptions {
172
- readonly qualityPreset?: GLTFRenderQualityPreset;
173
- readonly environmentLighting?: EnvironmentLightingOptions | false;
174
- readonly shadowMap?: ForwardShadowMapOptions;
175
- readonly shadow?: RendererShadowOptions | boolean;
176
- readonly collectedLights?: Iterable<CollectedLight>;
177
- readonly postprocess?: RendererPostProcessOptions | boolean;
178
- readonly renderItems?: Iterable<RenderItem>;
179
- readonly cameraPolicy?: RendererCameraPolicy;
180
- readonly cameraFrameBounds?: CameraFrameBounds;
181
- readonly cameraFrameOptions?: PerspectiveCameraFrameOptions;
182
- readonly cameraPosition?: readonly [number, number, number];
183
- readonly frustumCulling?: boolean;
184
- }
185
-
186
- export interface GLTFRenderResourceDiagnosticsOptions {
187
- readonly label?: string;
188
- readonly suspectStaticNodePattern?: RegExp;
189
- }
190
-
191
- export interface GLTFRenderResourceDiagnostics {
192
- readonly label: string;
193
- readonly drawItems: number;
194
- readonly skinnedDrawItems: number;
195
- readonly texturedDrawItems: number;
196
- readonly baseColorTextureDrawItems: number;
197
- readonly colorBearingTextureDrawItems: number;
198
- readonly surfaceDetailTextureDrawItems: number;
199
- readonly effectiveTextureBackedDrawItems: number;
200
- readonly unsupportedTexCoordDrawItems: number;
201
- readonly generatedTangentUvMismatchDrawItems: number;
202
- readonly texturedSkinnedDrawItems: number;
203
- readonly untexturedSkinnedDrawItems: number;
204
- readonly fallbackWhiteDrawItems: number;
205
- readonly missingGeometryDrawItems: number;
206
- readonly missingMaterialDrawItems: number;
207
- readonly materialCount: number;
208
- readonly textureCount: number;
209
- readonly textureBackedMaterialNames: readonly string[];
210
- readonly textureSlotDiagnostics: readonly GLTFRenderResourceTextureSlotDiagnostic[];
211
- readonly textureContributionDiagnostics: readonly GLTFRenderResourceTextureContributionDiagnostic[];
212
- readonly suppressedTextureSlotDiagnostics: readonly GLTFRenderResourceTextureContributionDiagnostic[];
213
- readonly materialFidelityDiagnostics: readonly GLTFRenderResourceMaterialFidelityDiagnostic[];
214
- readonly shaderActiveTextureSlotDiagnostics: readonly GLTFRenderResourceTextureSlotDiagnostic[];
215
- readonly shaderInactiveTextureSlotDiagnostics: readonly GLTFRenderResourceTextureSlotDiagnostic[];
216
- readonly fallbackWhiteMaterialNames: readonly string[];
217
- readonly skinnedLabels: readonly string[];
218
- readonly untexturedSkinnedLabels: readonly string[];
219
- readonly fallbackWhiteLabels: readonly string[];
220
- readonly missingGeometryLabels: readonly string[];
221
- readonly missingMaterialLabels: readonly string[];
222
- readonly suspectStaticLabels: readonly string[];
223
- }
224
-
225
- export interface GLTFRenderResourceTextureSlotDiagnostic {
226
- readonly slot: TexturedPBRTextureSlot;
227
- readonly drawItems: number;
228
- readonly materialNames: readonly string[];
229
- readonly labels: readonly string[];
230
- }
231
-
232
- export type GLTFRenderResourceTextureContribution = "color-bearing" | "surface-detail" | "suppressed";
233
-
234
- export interface GLTFRenderResourceTextureContributionDiagnostic extends GLTFRenderResourceTextureSlotDiagnostic {
235
- readonly contribution: GLTFRenderResourceTextureContribution;
236
- }
237
-
238
- type GLTFTextureColorSpace = "srgb" | "linear";
239
-
240
- const GLTF_DIAGNOSTIC_TEXTURE_BINDINGS: readonly (readonly [slot: TexturedPBRTextureSlot, textureParameter: string, enabledParameter: string])[] = [
241
- ["baseColor", "u_baseColorTexture", "u_baseColorTextureEnabled"],
242
- ["normal", "u_normalTexture", "u_normalTextureEnabled"],
243
- ["metallicRoughness", "u_metallicRoughnessTexture", "u_metallicRoughnessTextureEnabled"],
244
- ["occlusion", "u_occlusionTexture", "u_occlusionTextureEnabled"],
245
- ["emissive", "u_emissiveTexture", "u_emissiveTextureEnabled"],
246
- ["clearcoat", "u_clearcoatTexture", "u_clearcoatTextureEnabled"],
247
- ["clearcoatRoughness", "u_clearcoatRoughnessTexture", "u_clearcoatRoughnessTextureEnabled"],
248
- ["clearcoatNormal", "u_clearcoatNormalTexture", "u_clearcoatNormalTextureEnabled"],
249
- ["transmission", "u_transmissionTexture", "u_transmissionTextureEnabled"],
250
- ["diffuseTransmission", "u_diffuseTransmissionTexture", "u_diffuseTransmissionTextureEnabled"],
251
- ["diffuseTransmissionColor", "u_diffuseTransmissionColorTexture", "u_diffuseTransmissionColorTextureEnabled"],
252
- ["volumeThickness", "u_volumeThicknessTexture", "u_volumeThicknessTextureEnabled"],
253
- ["specular", "u_specularTexture", "u_specularTextureEnabled"],
254
- ["specularColor", "u_specularColorTexture", "u_specularColorTextureEnabled"],
255
- ["sheenColor", "u_sheenColorTexture", "u_sheenColorTextureEnabled"],
256
- ["sheenRoughness", "u_sheenRoughnessTexture", "u_sheenRoughnessTextureEnabled"],
257
- ["anisotropy", "u_anisotropyTexture", "u_anisotropyTextureEnabled"],
258
- ["iridescence", "u_iridescenceTexture", "u_iridescenceTextureEnabled"],
259
- ["iridescenceThickness", "u_iridescenceThicknessTexture", "u_iridescenceThicknessTextureEnabled"]
260
- ];
261
-
262
- export const DEFAULT_GLTF_RENDER_ENVIRONMENT_LIGHTING: EnvironmentLightingOptions = {
263
- color: [0.72, 0.74, 0.78],
264
- intensity: 0.38,
265
- proceduralMap: {
266
- skyColor: [0.62, 0.72, 0.9],
267
- horizonColor: [0.9, 0.82, 0.66],
268
- groundColor: [0.16, 0.16, 0.18],
269
- specularColor: [1, 0.92, 0.78],
270
- intensity: 0.42,
271
- specularIntensity: 0.7
272
- }
273
- };
274
-
275
- export const DEFAULT_GLTF_STUDIO_PREVIEW_ENVIRONMENT_LIGHTING: EnvironmentLightingOptions = {
276
- color: [0.82, 0.84, 0.88],
277
- intensity: 0.5,
278
- proceduralMap: {
279
- skyColor: [0.72, 0.82, 0.95],
280
- horizonColor: [1, 0.9, 0.72],
281
- groundColor: [0.12, 0.13, 0.15],
282
- specularColor: [1, 0.95, 0.82],
283
- intensity: 0.62,
284
- specularIntensity: 1.15
285
- }
286
- };
287
-
288
- export function createDefaultGLTFHdrStudioPreviewEnvironmentLighting(): EnvironmentLightingOptions {
289
- return createExternalParityEnvironmentLighting("studio").lighting;
290
- }
291
-
292
- export const DEFAULT_GLTF_STUDIO_PREVIEW_POSTPROCESS: RendererPostProcessOptions = {
293
- targetFormat: "rgba8",
294
- toneMapping: {
295
- exposure: 1.18,
296
- operator: "filmic",
297
- inputColorSpace: "linear",
298
- outputColorSpace: "srgb"
299
- },
300
- colorGrade: {
301
- contrast: 1.08,
302
- saturation: 1.06,
303
- vibrance: 0.1,
304
- vignette: 0.18,
305
- sharpening: 0.28
306
- },
307
- bloom: {
308
- threshold: 0.82,
309
- intensity: 0.08,
310
- radius: 1
311
- },
312
- fxaa: {
313
- edgeThreshold: 0.08,
314
- subpixelBlend: 0.55
315
- }
316
- };
317
-
318
- export const DEFAULT_GLTF_HDR_STUDIO_PREVIEW_POSTPROCESS: RendererPostProcessOptions = {
319
- targetFormat: "rgba16f",
320
- toneMapping: {
321
- exposure: 1.12,
322
- whitePoint: 1.25,
323
- operator: "filmic",
324
- inputColorSpace: "linear",
325
- outputColorSpace: "srgb"
326
- },
327
- colorGrade: {
328
- contrast: 1.1,
329
- saturation: 1.06,
330
- vibrance: 0.12,
331
- vignette: 0.16,
332
- sharpening: 0.32
333
- },
334
- bloom: {
335
- threshold: 0.9,
336
- intensity: 0.12,
337
- radius: 1
338
- },
339
- fxaa: {
340
- edgeThreshold: 0.08,
341
- subpixelBlend: 0.55
342
- }
343
- };
344
-
345
- export const DEFAULT_GLTF_STUDIO_PREVIEW_FRAME: PerspectiveCameraFrameOptions = {
346
- paddingRatio: 0.16,
347
- yawRadians: -0.38,
348
- pitchRadians: -0.16,
349
- nearPadding: 0.18,
350
- farPadding: 2.4
351
- };
352
-
353
- export async function createGLTFRenderResources(
354
- asset: GLTFAsset,
355
- options: GLTFRenderResourceOptions = {}
356
- ): Promise<GLTFRenderResources> {
357
- const geometryLibrary = new Map<string, Geometry>();
358
- const materialLibrary = new Map<string, Material>();
359
- const morphTargetLibrary = new Map<string, readonly MorphTargetDelta[]>();
360
- const textureLibrary = new Map<string, Texture>();
361
- const textureByImage = new Map<string, Promise<Texture>>();
362
- const scene = asset.createScene({
363
- ...(options.materialVariant ? { materialVariant: options.materialVariant } : {}),
364
- ...(options.sceneIndex !== undefined ? { sceneIndex: options.sceneIndex } : {}),
365
- ...(options.sceneName !== undefined ? { sceneName: options.sceneName } : {})
366
- });
367
-
368
- const getTexture = async (info: GLTFResolvedTextureInfo, colorSpace: GLTFTextureColorSpace): Promise<Texture> => {
369
- const textureAsset = asset.textures[info.texture];
370
- const image = asset.images[info.image];
371
- if (!textureAsset || !image) {
372
- throw new Error(`glTF material texture ${info.texture} references missing image ${info.image}`);
373
- }
374
- const cacheKey = textureCacheKey(info.image, colorSpace);
375
- const existing = textureByImage.get(cacheKey);
376
- if (existing) {
377
- const texture = await existing;
378
- setTextureLibraryEntry(textureLibrary, textureAsset.name, colorSpace, texture);
379
- return texture;
380
- }
381
- const decoder = options.imageDecoder ?? ((sourceImage, imageIndex, sourceAsset) => decodeImageInBrowser(sourceImage, imageIndex, sourceAsset, options));
382
- const texturePromise = (async () => {
383
- const decoded = await decoder(image, info.image, asset);
384
- return new Texture({
385
- width: decoded.width,
386
- height: decoded.height,
387
- ...(decoded.format ? { format: decoded.format } : {}),
388
- colorSpace,
389
- label: textureAsset.name,
390
- ...(decoded.mipLevels ? { mipLevels: decoded.mipLevels } : decoded.data ? { data: decoded.data } : { source: decoded.source }),
391
- ...(decoded.fallbackData ? { fallbackData: decoded.fallbackData } : {}),
392
- ...(decoded.fallbackMipLevels ? { fallbackMipLevels: decoded.fallbackMipLevels } : {})
393
- });
394
- })();
395
- textureByImage.set(cacheKey, texturePromise);
396
- try {
397
- const texture = await texturePromise;
398
- setTextureLibraryEntry(textureLibrary, textureAsset.name, colorSpace, texture);
399
- return texture;
400
- } catch (error) {
401
- textureByImage.delete(cacheKey);
402
- if (options.failOnMissingTexture) {
403
- throw error;
404
- }
405
- const reason = error instanceof Error ? error.message : String(error);
406
- warnMissingGLTFTexture(textureAsset.name, image.uri, reason);
407
- const fallback = createFallbackTexture(textureAsset.name, colorSpace);
408
- const fallbackPromise = Promise.resolve(fallback);
409
- textureByImage.set(cacheKey, fallbackPromise);
410
- setTextureLibraryEntry(textureLibrary, textureAsset.name, colorSpace, fallback);
411
- return fallback;
412
- }
413
- };
414
-
415
- try {
416
- for (const mesh of asset.meshes) {
417
- geometryLibrary.set(mesh.name, createGeometry(mesh, materialForMesh(asset, mesh, options.materialVariant)));
418
- if (mesh.morphTargets.length > 0) {
419
- morphTargetLibrary.set(mesh.name, mesh.morphTargets.map((target) => ({
420
- positions: target.positions,
421
- normals: target.normals,
422
- tangents: target.tangents
423
- })));
424
- }
425
- }
426
-
427
- const runtimeMaterialContracts = new Map<string, ReturnType<typeof parseGLTFRuntimeMaterialKey>>();
428
- for (const { renderable } of scene.collectRenderables()) {
429
- const parsed = parseGLTFRuntimeMaterialKey(renderable.material);
430
- runtimeMaterialContracts.set(renderable.material, {
431
- material: parsed.material,
432
- contract: {
433
- ...parsed.contract,
434
- ...(renderable.skinning ? { skinned: true } : {}),
435
- ...(renderable.instanceTransforms ? { instanced: true } : {})
436
- }
437
- });
438
- }
439
- const materialTasks: Promise<void>[] = [];
440
- for (const material of asset.materials) {
441
- const runtimeKeys = [...runtimeMaterialContracts.entries()].filter(([, runtime]) => runtime.material === material.name);
442
- if (runtimeKeys.length === 0) {
443
- materialTasks.push(createMaterial(asset, material, getTexture, {}, options.materialRenderStateOverrides).then((runtimeMaterial) => {
444
- materialLibrary.set(material.name, runtimeMaterial);
445
- }));
446
- continue;
447
- }
448
- for (const [key, runtime] of runtimeKeys) {
449
- materialTasks.push(createMaterial(asset, material, getTexture, runtime.contract, options.materialRenderStateOverrides).then((runtimeMaterial) => {
450
- materialLibrary.set(key, runtimeMaterial);
451
- }));
452
- }
453
- }
454
- await Promise.all(materialTasks);
455
-
456
- for (const mesh of asset.meshes) {
457
- for (const [key, runtime] of runtimeMaterialContracts) {
458
- if (runtime.material === mesh.material && !materialLibrary.has(key)) {
459
- materialLibrary.set(key, createDefaultGLTFMaterial(mesh, runtime.contract));
460
- }
461
- }
462
- if (!materialLibrary.has(mesh.material)) {
463
- materialLibrary.set(mesh.material, createDefaultGLTFMaterial(mesh));
464
- }
465
- }
466
- } catch (error) {
467
- disposeGLTFRenderResourceMaps(geometryLibrary, textureLibrary);
468
- throw error;
469
- }
470
- const bounds = computeGLTFRenderResourceBounds(scene, geometryLibrary, morphTargetLibrary);
471
- const renderableBindings = createGLTFRenderableBindings(asset, scene);
472
- const materialFidelityDiagnostics = createGLTFMaterialFidelityDiagnostics(asset, renderableBindings, options.materialVariant);
473
-
474
- return {
475
- scene,
476
- geometryLibrary,
477
- materialLibrary,
478
- renderableBindings,
479
- materialFidelityDiagnostics,
480
- morphTargetLibrary,
481
- textureLibrary,
482
- bounds,
483
- collectMaterialOverrideTargets: (query = {}) => collectGLTFMaterialOverrideTargets(renderableBindings, materialLibrary, query),
484
- createCameraFrame: (viewport, frameOptions = {}) => computePerspectiveCameraFrame(bounds, viewport, defaultGLTFCameraFrameOptions(bounds, frameOptions)),
485
- toRenderSource: (sourceOptions = {}) => createGLTFRenderSource({
486
- scene,
487
- geometryLibrary,
488
- materialLibrary,
489
- morphTargetLibrary,
490
- bounds
491
- }, sourceOptions),
492
- toRendererInput: (viewport, inputOptions = {}) => {
493
- const { frame: frameOptions, ...sourceOptions } = inputOptions;
494
- const qualityPreset = sourceOptions.qualityPreset ?? "studio-preview";
495
- const frame = computePerspectiveCameraFrame(bounds, viewport, defaultGLTFCameraFrameOptions(bounds, frameOptions));
496
- return {
497
- source: createGLTFRenderSource({
498
- scene,
499
- geometryLibrary,
500
- materialLibrary,
501
- morphTargetLibrary,
502
- bounds
503
- }, {
504
- ...sourceOptions,
505
- qualityPreset,
506
- cameraPosition: sourceOptions.cameraPosition ?? frame.cameraPosition,
507
- cameraPolicy: sourceOptions.cameraPolicy ?? "require"
508
- }),
509
- camera: {
510
- viewProjectionMatrix: frame.viewProjectionMatrix,
511
- viewMatrix: frame.viewMatrix,
512
- projectionMatrix: frame.projectionMatrix
513
- },
514
- frame,
515
- bounds
516
- };
517
- },
518
- dispose: () => disposeGLTFRenderResourceMaps(geometryLibrary, textureLibrary)
519
- };
520
- }
521
-
522
- function createGLTFRenderableBindings(
523
- asset: GLTFAsset,
524
- scene: ReturnType<GLTFAsset["createScene"]>
525
- ): readonly GLTFRenderableBinding[] {
526
- const meshByGeometry = new Map(asset.meshes.map((mesh) => [mesh.name, mesh]));
527
- return scene.collectRenderables().map(({ node, renderable }) => {
528
- const mesh = meshByGeometry.get(renderable.geometry);
529
- const materialKey = renderable.material;
530
- const parsedMaterial = parseGLTFRuntimeMaterialKey(materialKey);
531
- return {
532
- nodeName: node.name,
533
- geometryKey: renderable.geometry,
534
- materialKey,
535
- sourceMaterialName: parsedMaterial.material,
536
- ...(mesh?.materialIndex !== undefined ? { sourceMaterialIndex: mesh.materialIndex } : {}),
537
- ...(mesh?.sourceMeshIndex !== undefined ? { sourceMeshIndex: mesh.sourceMeshIndex } : {}),
538
- ...(mesh?.primitiveIndex !== undefined ? { primitiveIndex: mesh.primitiveIndex } : {}),
539
- materialVariants: mesh?.materialVariants ?? [],
540
- skinned: Boolean(renderable.skinning),
541
- instanced: Boolean(renderable.instanceTransforms)
542
- };
543
- });
544
- }
545
-
546
- function createGLTFMaterialFidelityDiagnostics(
547
- asset: GLTFAsset,
548
- bindings: readonly GLTFRenderableBinding[],
549
- materialVariant: string | undefined
550
- ): readonly GLTFRenderResourceMaterialFidelityDiagnostic[] {
551
- const diagnostics: GLTFRenderResourceMaterialFidelityDiagnostic[] = [];
552
- const meshByGeometry = new Map(asset.meshes.map((mesh) => [mesh.name, mesh]));
553
- for (const binding of bindings) {
554
- const mesh = meshByGeometry.get(binding.geometryKey);
555
- if (!mesh) continue;
556
- const material = materialForMesh(asset, mesh, materialVariant);
557
- if (!material) continue;
558
- for (const { slot, info, tangentSpace } of materialTextureInfosBySlot(material)) {
559
- const renderedTexCoord = renderTexCoord(info.texCoord);
560
- if (info.texCoord > 1) {
561
- diagnostics.push({
562
- issue: "unsupported-texcoord-set",
563
- slot,
564
- texCoord: info.texCoord,
565
- renderedTexCoord,
566
- ...materialFidelityBindingFields(binding),
567
- detail: `glTF ${slot} texture requests TEXCOORD_${info.texCoord}, but current WebGL render resources only bind TEXCOORD_0 and TEXCOORD_1; the shader samples TEXCOORD_${renderedTexCoord}.`
568
- });
569
- }
570
- if (tangentSpace && mesh.tangents.length === 0 && info.texCoord > 0) {
571
- diagnostics.push({
572
- issue: "generated-tangent-uv-mismatch",
573
- slot,
574
- texCoord: info.texCoord,
575
- renderedTexCoord,
576
- ...materialFidelityBindingFields(binding),
577
- detail: `glTF ${slot} texture samples TEXCOORD_${info.texCoord}, but generated tangents are derived from TEXCOORD_0 when source tangents are absent.`
578
- });
579
- }
580
- }
581
- }
582
- return diagnostics;
583
- }
584
-
585
- function materialFidelityBindingFields(binding: GLTFRenderableBinding): Omit<GLTFRenderResourceMaterialFidelityDiagnostic, "issue" | "slot" | "texCoord" | "renderedTexCoord" | "detail"> {
586
- return {
587
- nodeName: binding.nodeName,
588
- geometryKey: binding.geometryKey,
589
- materialKey: binding.materialKey,
590
- sourceMaterialName: binding.sourceMaterialName,
591
- ...(binding.sourceMaterialIndex !== undefined ? { sourceMaterialIndex: binding.sourceMaterialIndex } : {}),
592
- ...(binding.sourceMeshIndex !== undefined ? { sourceMeshIndex: binding.sourceMeshIndex } : {}),
593
- ...(binding.primitiveIndex !== undefined ? { primitiveIndex: binding.primitiveIndex } : {})
594
- };
595
- }
596
-
597
- function collectGLTFMaterialOverrideTargets(
598
- bindings: readonly GLTFRenderableBinding[],
599
- materialLibrary: ReadonlyMap<string, Material>,
600
- query: GLTFMaterialOverrideQuery
601
- ): readonly GLTFMaterialOverrideTarget[] {
602
- const uniqueMaterials = query.uniqueMaterials ?? true;
603
- const seen = new Set<string>();
604
- const targets: GLTFMaterialOverrideTarget[] = [];
605
- for (const binding of bindings) {
606
- if (!matchesGLTFMaterialOverrideQuery(binding, query)) continue;
607
- if (uniqueMaterials && seen.has(binding.materialKey)) continue;
608
- const material = materialLibrary.get(binding.materialKey);
609
- if (!material) continue;
610
- seen.add(binding.materialKey);
611
- targets.push({ ...binding, material });
612
- }
613
- return targets;
614
- }
615
-
616
- function matchesGLTFMaterialOverrideQuery(binding: GLTFRenderableBinding, query: GLTFMaterialOverrideQuery): boolean {
617
- if (query.nodeName && !matchesTextOrPattern(binding.nodeName, query.nodeName)) return false;
618
- if (query.sourceMaterialName && !matchesTextOrPattern(binding.sourceMaterialName, query.sourceMaterialName)) return false;
619
- if (query.materialKey && !matchesTextOrPattern(binding.materialKey, query.materialKey)) return false;
620
- if (query.variant && !binding.materialVariants.some((variant) => variant.variant === query.variant)) return false;
621
- return true;
622
- }
623
-
624
- function matchesTextOrPattern(value: string, matcher: string | RegExp): boolean {
625
- return typeof matcher === "string" ? value === matcher : matcher.test(value);
626
- }
627
-
628
- export function createGLTFRenderSource(
629
- resources: Pick<GLTFRenderResources, "scene" | "geometryLibrary" | "materialLibrary" | "morphTargetLibrary" | "bounds">,
630
- options: GLTFRenderSourceOptions = {}
631
- ): RenderSource {
632
- const qualityPreset = options.qualityPreset ?? "studio-preview";
633
- const environmentLighting = options.environmentLighting === false
634
- ? false
635
- : options.environmentLighting ?? cloneEnvironmentLighting(defaultEnvironmentLightingForPreset(qualityPreset));
636
- const postprocess = options.postprocess !== undefined
637
- ? options.postprocess
638
- : qualityPreset !== "default"
639
- ? cloneRendererPostprocess(defaultPostprocessForPreset(qualityPreset))
640
- : undefined;
641
- return {
642
- scene: resources.scene,
643
- geometryLibrary: resources.geometryLibrary,
644
- materialLibrary: resources.materialLibrary,
645
- morphTargetLibrary: resources.morphTargetLibrary,
646
- ...(options.renderItems ? { renderItems: options.renderItems } : {}),
647
- ...(environmentLighting !== undefined ? { environmentLighting } : {}),
648
- ...(options.shadowMap ? { shadowMap: options.shadowMap } : {}),
649
- ...(options.shadow !== undefined ? { shadow: options.shadow } : {}),
650
- ...(options.collectedLights ? { collectedLights: options.collectedLights } : {}),
651
- ...(postprocess !== undefined ? { postprocess } : {}),
652
- ...(options.cameraPolicy ? { cameraPolicy: options.cameraPolicy } : {}),
653
- ...(options.cameraFrameBounds ? { cameraFrameBounds: options.cameraFrameBounds } : { cameraFrameBounds: resources.bounds }),
654
- ...(options.cameraFrameOptions ? { cameraFrameOptions: options.cameraFrameOptions } : {}),
655
- ...(options.cameraPosition ? { cameraPosition: options.cameraPosition } : {}),
656
- ...(options.frustumCulling !== undefined ? { frustumCulling: options.frustumCulling } : {})
657
- };
658
- }
659
-
660
- export function createGLTFRenderResourceDiagnostics(
661
- resources: Pick<GLTFRenderResources, "scene" | "geometryLibrary" | "materialLibrary"> & Partial<Pick<GLTFRenderResources, "textureLibrary" | "materialFidelityDiagnostics">>,
662
- options: GLTFRenderResourceDiagnosticsOptions = {}
663
- ): GLTFRenderResourceDiagnostics {
664
- const label = options.label ?? "gltf-render-resources";
665
- const textureBackedMaterialNames = new Set<string>();
666
- const textureSlotDiagnostics = new Map<string, { drawItems: number; materialNames: Set<string>; labels: string[] }>();
667
- const textureContributionDiagnostics = new Map<string, { drawItems: number; materialNames: Set<string>; labels: string[] }>();
668
- const suppressedTextureSlotDiagnostics = new Map<string, { drawItems: number; materialNames: Set<string>; labels: string[] }>();
669
- const shaderActiveTextureSlotDiagnostics = new Map<string, { drawItems: number; materialNames: Set<string>; labels: string[] }>();
670
- const shaderInactiveTextureSlotDiagnostics = new Map<string, { drawItems: number; materialNames: Set<string>; labels: string[] }>();
671
- const fallbackWhiteMaterialNames = new Set<string>();
672
- const skinnedLabels: string[] = [];
673
- const untexturedSkinnedLabels: string[] = [];
674
- const fallbackWhiteLabels: string[] = [];
675
- const missingGeometryLabels: string[] = [];
676
- const missingMaterialLabels: string[] = [];
677
- const suspectStaticLabels: string[] = [];
678
- let drawItems = 0;
679
- let skinnedDrawItems = 0;
680
- let texturedDrawItems = 0;
681
- let baseColorTextureDrawItems = 0;
682
- let colorBearingTextureDrawItems = 0;
683
- let surfaceDetailTextureDrawItems = 0;
684
- let effectiveTextureBackedDrawItems = 0;
685
- let texturedSkinnedDrawItems = 0;
686
- let missingGeometryDrawItems = 0;
687
- let missingMaterialDrawItems = 0;
688
-
689
- resources.scene.updateWorldTransforms();
690
- for (const { node, renderable } of resources.scene.collectRenderables()) {
691
- const itemLabel = `${node.name}:${renderable.geometry}`;
692
- const geometry = resources.geometryLibrary.get(renderable.geometry);
693
- const material = resources.materialLibrary.get(renderable.material);
694
- if (!geometry) {
695
- missingGeometryDrawItems += 1;
696
- missingGeometryLabels.push(itemLabel);
697
- }
698
- if (!material) {
699
- missingMaterialDrawItems += 1;
700
- missingMaterialLabels.push(itemLabel);
701
- }
702
- if (!geometry || !material) continue;
703
-
704
- drawItems += 1;
705
- const isSkinned = Boolean(renderable.skinning);
706
- const validTextureSlots = GLTF_DIAGNOSTIC_TEXTURE_BINDINGS.filter(([, textureParameter, enabledParameter]) =>
707
- materialHasValidTextureBinding(material, textureParameter, enabledParameter)
708
- );
709
- const hasAnyTexture = validTextureSlots.length > 0;
710
- const hasBaseColorTexture = materialHasValidTextureBinding(material, "u_baseColorTexture", "u_baseColorTextureEnabled");
711
- if (isSkinned) {
712
- skinnedDrawItems += 1;
713
- skinnedLabels.push(itemLabel);
714
- }
715
- if (hasAnyTexture) {
716
- texturedDrawItems += 1;
717
- textureBackedMaterialNames.add(material.name);
718
- }
719
- let hasEffectiveBaseColorTexture = false;
720
- let hasColorBearingTexture = false;
721
- let hasSurfaceDetailTexture = false;
722
- for (const [slot] of validTextureSlots) {
723
- addTextureSlotDiagnostic(textureSlotDiagnostics, slot, material.name, itemLabel);
724
- const shaderActive = materialSamplesTextureSlot(material, slot);
725
- const shaderSlotMap = shaderActive ? shaderActiveTextureSlotDiagnostics : shaderInactiveTextureSlotDiagnostics;
726
- addTextureSlotDiagnostic(shaderSlotMap, slot, material.name, itemLabel);
727
- const contribution = shaderActive ? textureContributionForSlot(material, slot) : "suppressed";
728
- if (contribution === "color-bearing") {
729
- hasColorBearingTexture = true;
730
- if (slot === "baseColor") hasEffectiveBaseColorTexture = true;
731
- addTextureContributionDiagnostic(textureContributionDiagnostics, contribution, slot, material.name, itemLabel);
732
- } else if (contribution === "surface-detail") {
733
- hasSurfaceDetailTexture = true;
734
- addTextureContributionDiagnostic(textureContributionDiagnostics, contribution, slot, material.name, itemLabel);
735
- } else {
736
- addTextureContributionDiagnostic(suppressedTextureSlotDiagnostics, contribution, slot, material.name, itemLabel);
737
- }
738
- }
739
- if (hasEffectiveBaseColorTexture) baseColorTextureDrawItems += 1;
740
- if (hasColorBearingTexture) colorBearingTextureDrawItems += 1;
741
- if (hasSurfaceDetailTexture) surfaceDetailTextureDrawItems += 1;
742
- if (hasColorBearingTexture || hasSurfaceDetailTexture) effectiveTextureBackedDrawItems += 1;
743
- if (isSkinned && hasBaseColorTexture) texturedSkinnedDrawItems += 1;
744
- if (isSkinned && !hasBaseColorTexture) untexturedSkinnedLabels.push(itemLabel);
745
- if (isFallbackWhiteRuntimeMaterial(material)) {
746
- fallbackWhiteLabels.push(itemLabel);
747
- fallbackWhiteMaterialNames.add(material.name);
748
- }
749
- if (!isSkinned && options.suspectStaticNodePattern?.test(node.name)) suspectStaticLabels.push(itemLabel);
750
- }
751
-
752
- return {
753
- label,
754
- drawItems,
755
- skinnedDrawItems,
756
- texturedDrawItems,
757
- baseColorTextureDrawItems,
758
- colorBearingTextureDrawItems,
759
- surfaceDetailTextureDrawItems,
760
- effectiveTextureBackedDrawItems,
761
- unsupportedTexCoordDrawItems: countDistinctMaterialFidelityDrawItems(resources.materialFidelityDiagnostics, "unsupported-texcoord-set"),
762
- generatedTangentUvMismatchDrawItems: countDistinctMaterialFidelityDrawItems(resources.materialFidelityDiagnostics, "generated-tangent-uv-mismatch"),
763
- texturedSkinnedDrawItems,
764
- untexturedSkinnedDrawItems: untexturedSkinnedLabels.length,
765
- fallbackWhiteDrawItems: fallbackWhiteLabels.length,
766
- missingGeometryDrawItems,
767
- missingMaterialDrawItems,
768
- materialCount: resources.materialLibrary.size,
769
- textureCount: resources.textureLibrary?.size ?? 0,
770
- textureBackedMaterialNames: [...textureBackedMaterialNames].sort(),
771
- textureSlotDiagnostics: [...textureSlotDiagnostics.entries()]
772
- .sort(([left], [right]) => left.localeCompare(right))
773
- .map(([slot, diagnostics]) => ({
774
- slot: slot as TexturedPBRTextureSlot,
775
- drawItems: diagnostics.drawItems,
776
- materialNames: [...diagnostics.materialNames].sort(),
777
- labels: [...diagnostics.labels].sort()
778
- })),
779
- textureContributionDiagnostics: mapTextureContributionDiagnostics(textureContributionDiagnostics),
780
- suppressedTextureSlotDiagnostics: mapTextureContributionDiagnostics(suppressedTextureSlotDiagnostics),
781
- materialFidelityDiagnostics: resources.materialFidelityDiagnostics ?? [],
782
- shaderActiveTextureSlotDiagnostics: [...shaderActiveTextureSlotDiagnostics.entries()]
783
- .sort(([left], [right]) => left.localeCompare(right))
784
- .map(([slot, diagnostics]) => ({
785
- slot: slot as TexturedPBRTextureSlot,
786
- drawItems: diagnostics.drawItems,
787
- materialNames: [...diagnostics.materialNames].sort(),
788
- labels: [...diagnostics.labels].sort()
789
- })),
790
- shaderInactiveTextureSlotDiagnostics: [...shaderInactiveTextureSlotDiagnostics.entries()]
791
- .sort(([left], [right]) => left.localeCompare(right))
792
- .map(([slot, diagnostics]) => ({
793
- slot: slot as TexturedPBRTextureSlot,
794
- drawItems: diagnostics.drawItems,
795
- materialNames: [...diagnostics.materialNames].sort(),
796
- labels: [...diagnostics.labels].sort()
797
- })),
798
- fallbackWhiteMaterialNames: [...fallbackWhiteMaterialNames].sort(),
799
- skinnedLabels,
800
- untexturedSkinnedLabels,
801
- fallbackWhiteLabels,
802
- missingGeometryLabels,
803
- missingMaterialLabels,
804
- suspectStaticLabels
805
- };
806
- }
807
-
808
- function defaultEnvironmentLightingForPreset(preset: GLTFRenderQualityPreset): EnvironmentLightingOptions {
809
- if (preset === "hdr-studio-preview") return createDefaultGLTFHdrStudioPreviewEnvironmentLighting();
810
- if (preset === "studio-preview") return DEFAULT_GLTF_STUDIO_PREVIEW_ENVIRONMENT_LIGHTING;
811
- return DEFAULT_GLTF_RENDER_ENVIRONMENT_LIGHTING;
812
- }
813
-
814
- function addTextureSlotDiagnostic(
815
- diagnosticsBySlot: Map<string, { drawItems: number; materialNames: Set<string>; labels: string[] }>,
816
- slot: TexturedPBRTextureSlot,
817
- materialName: string,
818
- itemLabel: string
819
- ): void {
820
- let diagnostics = diagnosticsBySlot.get(slot);
821
- if (!diagnostics) {
822
- diagnostics = { drawItems: 0, materialNames: new Set(), labels: [] };
823
- diagnosticsBySlot.set(slot, diagnostics);
824
- }
825
- diagnostics.drawItems += 1;
826
- diagnostics.materialNames.add(materialName);
827
- diagnostics.labels.push(itemLabel);
828
- }
829
-
830
- function addTextureContributionDiagnostic(
831
- diagnosticsByContributionSlot: Map<string, { drawItems: number; materialNames: Set<string>; labels: string[] }>,
832
- contribution: GLTFRenderResourceTextureContribution,
833
- slot: TexturedPBRTextureSlot,
834
- materialName: string,
835
- itemLabel: string
836
- ): void {
837
- addTextureSlotDiagnostic(diagnosticsByContributionSlot, `${contribution}:${slot}` as TexturedPBRTextureSlot, materialName, itemLabel);
838
- }
839
-
840
- function mapTextureContributionDiagnostics(
841
- diagnosticsByContributionSlot: Map<string, { drawItems: number; materialNames: Set<string>; labels: string[] }>
842
- ): readonly GLTFRenderResourceTextureContributionDiagnostic[] {
843
- return [...diagnosticsByContributionSlot.entries()]
844
- .sort(([left], [right]) => left.localeCompare(right))
845
- .map(([key, diagnostics]) => {
846
- const [contribution, slot] = key.split(":") as [GLTFRenderResourceTextureContribution, TexturedPBRTextureSlot];
847
- return {
848
- contribution,
849
- slot,
850
- drawItems: diagnostics.drawItems,
851
- materialNames: [...diagnostics.materialNames].sort(),
852
- labels: [...diagnostics.labels].sort()
853
- };
854
- });
855
- }
856
-
857
- function countDistinctMaterialFidelityDrawItems(
858
- diagnostics: readonly GLTFRenderResourceMaterialFidelityDiagnostic[] | undefined,
859
- issue: GLTFRenderResourceMaterialFidelityIssue
860
- ): number {
861
- if (!diagnostics) return 0;
862
- const labels = new Set<string>();
863
- for (const diagnostic of diagnostics) {
864
- if (diagnostic.issue !== issue) continue;
865
- labels.add(`${diagnostic.nodeName}:${diagnostic.geometryKey}:${diagnostic.materialKey}`);
866
- }
867
- return labels.size;
868
- }
869
-
870
- function textureContributionForSlot(
871
- material: Material,
872
- slot: TexturedPBRTextureSlot
873
- ): GLTFRenderResourceTextureContribution {
874
- switch (slot) {
875
- case "baseColor":
876
- case "diffuseTransmissionColor":
877
- case "specularColor":
878
- case "sheenColor":
879
- return "color-bearing";
880
- case "emissive":
881
- return materialHasEmissiveContribution(material) ? "color-bearing" : "suppressed";
882
- case "normal":
883
- return numericMaterialParameter(material, "u_normalScale", 1) > 0.001 ? "surface-detail" : "suppressed";
884
- case "occlusion":
885
- return numericMaterialParameter(material, "u_occlusionStrength", 1) > 0.001 ? "surface-detail" : "suppressed";
886
- default:
887
- return "surface-detail";
888
- }
889
- }
890
-
891
- function numericMaterialParameter(material: Material, name: string, fallback: number): number {
892
- const value = material.getParameter(name);
893
- return typeof value === "number" && Number.isFinite(value) ? value : fallback;
894
- }
895
-
896
- function materialHasEmissiveContribution(material: Material): boolean {
897
- const strength = numericMaterialParameter(material, "u_emissiveStrength", 1);
898
- const color = material.getParameter("u_emissiveColor");
899
- if (strength <= 0.001) return false;
900
- if ((Array.isArray(color) || ArrayBuffer.isView(color)) && color.length >= 3) {
901
- return Math.max(Number(color[0]) || 0, Number(color[1]) || 0, Number(color[2]) || 0) > 0.001;
902
- }
903
- return true;
904
- }
905
-
906
- function materialSamplesTextureSlot(material: Material, slot: TexturedPBRTextureSlot): boolean {
907
- if (material.shaderKey !== DEFAULT_TEXTURED_PBR_SHADER_NAME) return true;
908
- return isTexturedPbrTextureSlotShaderActive(slot, material.shaderVariant);
909
- }
910
-
911
- function materialHasValidTextureBinding(material: Material, textureParameter: string, enabledParameter: string): boolean {
912
- const enabled = material.getParameter(enabledParameter);
913
- const binding = material.getParameter(textureParameter);
914
- const enabledByMaterial = enabled === undefined || enabled === 1;
915
- return enabledByMaterial
916
- && binding instanceof TextureBinding
917
- && Boolean(binding.texture)
918
- && !isGeneratedFallbackTexture(binding.texture)
919
- && binding.validate().ok;
920
- }
921
-
922
- function isFallbackWhiteRuntimeMaterial(material: Material): boolean {
923
- const baseColor = material.getParameter("u_baseColor");
924
- const metallic = numericMaterialParameter(material, "u_metallic", 0);
925
- const roughness = numericMaterialParameter(material, "u_roughness", 1);
926
- const transmission = numericMaterialParameter(material, "u_transmissionFactor", 0)
927
- + numericMaterialParameter(material, "u_diffuseTransmissionFactor", 0);
928
- const hasTexture = materialHasValidTextureBinding(material, "u_baseColorTexture", "u_baseColorTextureEnabled")
929
- || materialHasValidTextureBinding(material, "u_texture", "u_textureEnabled");
930
- return !hasTexture
931
- && transmission <= 0.001
932
- && !(metallic >= 0.9 && roughness <= 0.25)
933
- && (Array.isArray(baseColor) || ArrayBuffer.isView(baseColor))
934
- && baseColor.length >= 4
935
- && baseColor[0] >= 0.98
936
- && baseColor[1] >= 0.98
937
- && baseColor[2] >= 0.98
938
- && baseColor[3] >= 0.98;
939
- }
940
-
941
- function isGeneratedFallbackTexture(texture: Texture | null | undefined): boolean {
942
- if (!texture) return true;
943
- return /^default-(?:white|linear-white|flat-normal|metallic-roughness|occlusion|emissive|clearcoat|transmission|diffuse-transmission|volume-thickness|specular|sheen|anisotropy|iridescence)/.test(texture.label);
944
- }
945
-
946
- function defaultPostprocessForPreset(preset: Exclude<GLTFRenderQualityPreset, "default">): RendererPostProcessOptions {
947
- return preset === "hdr-studio-preview"
948
- ? DEFAULT_GLTF_HDR_STUDIO_PREVIEW_POSTPROCESS
949
- : DEFAULT_GLTF_STUDIO_PREVIEW_POSTPROCESS;
950
- }
951
-
952
- function cloneEnvironmentLighting(environment: EnvironmentLightingOptions): EnvironmentLightingOptions {
953
- return {
954
- color: [...environment.color] as [number, number, number],
955
- intensity: environment.intensity,
956
- ...(environment.proceduralMap
957
- ? {
958
- proceduralMap: {
959
- skyColor: [...environment.proceduralMap.skyColor] as [number, number, number],
960
- horizonColor: [...environment.proceduralMap.horizonColor] as [number, number, number],
961
- groundColor: [...environment.proceduralMap.groundColor] as [number, number, number],
962
- specularColor: [...environment.proceduralMap.specularColor] as [number, number, number],
963
- intensity: environment.proceduralMap.intensity,
964
- specularIntensity: environment.proceduralMap.specularIntensity
965
- }
966
- }
967
- : {}),
968
- ...(environment.environmentMapTexture ? { environmentMapTexture: environment.environmentMapTexture } : {}),
969
- ...(environment.environmentCubeMapTexture ? { environmentCubeMapTexture: environment.environmentCubeMapTexture } : {}),
970
- ...(environment.environmentMapIntensity !== undefined ? { environmentMapIntensity: environment.environmentMapIntensity } : {}),
971
- ...(environment.environmentMapSpecularIntensity !== undefined ? { environmentMapSpecularIntensity: environment.environmentMapSpecularIntensity } : {}),
972
- ...(environment.environmentMapRotation !== undefined ? { environmentMapRotation: environment.environmentMapRotation } : {}),
973
- ...(environment.environmentMapMipCount !== undefined ? { environmentMapMipCount: environment.environmentMapMipCount } : {}),
974
- ...(environment.environmentMapEncoding ? { environmentMapEncoding: environment.environmentMapEncoding } : {}),
975
- ...(environment.environmentBrdfLutTexture ? { environmentBrdfLutTexture: environment.environmentBrdfLutTexture } : {})
976
- };
977
- }
978
-
979
- function cloneRendererPostprocess(postprocess: RendererPostProcessOptions): RendererPostProcessOptions {
980
- return {
981
- ...(postprocess.targetFormat ? { targetFormat: postprocess.targetFormat } : {}),
982
- ...(postprocess.toneMapping !== undefined
983
- ? { toneMapping: typeof postprocess.toneMapping === "object" ? { ...postprocess.toneMapping } : postprocess.toneMapping }
984
- : {}),
985
- ...(postprocess.colorGrade !== undefined
986
- ? { colorGrade: typeof postprocess.colorGrade === "object" ? { ...postprocess.colorGrade } : postprocess.colorGrade }
987
- : {}),
988
- ...(postprocess.bloom !== undefined
989
- ? { bloom: typeof postprocess.bloom === "object" ? { ...postprocess.bloom } : postprocess.bloom }
990
- : {}),
991
- ...(postprocess.chromaticAberration !== undefined
992
- ? { chromaticAberration: typeof postprocess.chromaticAberration === "object" ? { ...postprocess.chromaticAberration } : postprocess.chromaticAberration }
993
- : {}),
994
- ...(postprocess.filmGrain !== undefined
995
- ? { filmGrain: typeof postprocess.filmGrain === "object" ? { ...postprocess.filmGrain } : postprocess.filmGrain }
996
- : {}),
997
- ...(postprocess.depthOfField !== undefined
998
- ? { depthOfField: postprocess.depthOfField === false ? false : { ...postprocess.depthOfField } }
999
- : {}),
1000
- ...(postprocess.motionBlur !== undefined
1001
- ? { motionBlur: postprocess.motionBlur === false ? false : { ...postprocess.motionBlur } }
1002
- : {}),
1003
- ...(postprocess.ssao !== undefined
1004
- ? { ssao: postprocess.ssao === false ? false : { ...postprocess.ssao } }
1005
- : {}),
1006
- ...(postprocess.ssr !== undefined
1007
- ? { ssr: postprocess.ssr === false ? false : { ...postprocess.ssr } }
1008
- : {}),
1009
- ...(postprocess.taa !== undefined
1010
- ? { taa: postprocess.taa === false ? false : { ...postprocess.taa } }
1011
- : {}),
1012
- ...(postprocess.outline !== undefined
1013
- ? { outline: typeof postprocess.outline === "object" ? { ...postprocess.outline } : postprocess.outline }
1014
- : {}),
1015
- ...(postprocess.fxaa !== undefined
1016
- ? { fxaa: typeof postprocess.fxaa === "object" ? { ...postprocess.fxaa } : postprocess.fxaa }
1017
- : {})
1018
- };
1019
- }
1020
-
1021
- function defaultGLTFCameraMinDistance(bounds: CameraFrameBounds): number {
1022
- const sizeX = Math.max(0, bounds.max[0] - bounds.min[0]);
1023
- const sizeY = Math.max(0, bounds.max[1] - bounds.min[1]);
1024
- const sizeZ = Math.max(0, bounds.max[2] - bounds.min[2]);
1025
- const diagonal = Math.hypot(sizeX, sizeY, sizeZ);
1026
- return Math.max(1.2, diagonal * 0.5);
1027
- }
1028
-
1029
- function defaultGLTFCameraFrameOptions(
1030
- bounds: CameraFrameBounds,
1031
- overrides: PerspectiveCameraFrameOptions = {}
1032
- ): PerspectiveCameraFrameOptions {
1033
- return {
1034
- ...DEFAULT_GLTF_STUDIO_PREVIEW_FRAME,
1035
- minDistance: defaultGLTFCameraMinDistance(bounds),
1036
- ...overrides
1037
- };
1038
- }
1039
-
1040
- function computeGLTFRenderResourceBounds(
1041
- scene: GLTFRenderResources["scene"],
1042
- geometryLibrary: ReadonlyMap<string, Geometry>,
1043
- morphTargetLibrary: ReadonlyMap<string, readonly MorphTargetDelta[]>
1044
- ): CameraFrameBounds {
1045
- scene.updateWorldTransforms();
1046
- let bounds: SceneBounds3 | undefined;
1047
- for (const { node, renderable } of scene.collectRenderables()) {
1048
- const geometry = geometryLibrary.get(renderable.geometry);
1049
- if (!geometry) continue;
1050
- const envelope = computeSkinnedMorphTargetEnvelopeBounds(geometry, renderable.skinning, morphTargetLibrary.get(renderable.geometry));
1051
- const local = new SceneBounds3(
1052
- [envelope.min[0], envelope.min[1], envelope.min[2]],
1053
- [envelope.max[0], envelope.max[1], envelope.max[2]]
1054
- );
1055
- const worldBounds = renderable.instanceTransforms
1056
- ? transformInstancedBounds(local, node.transform.worldMatrix, renderable.instanceTransforms)
1057
- : local.transform(node.transform.worldMatrix);
1058
- bounds = bounds ? bounds.union(worldBounds) : worldBounds;
1059
- }
1060
- if (!bounds || bounds.isEmpty()) {
1061
- return { min: [0, 0, 0], max: [0, 0, 0] };
1062
- }
1063
- return {
1064
- min: [bounds.min[0], bounds.min[1], bounds.min[2]],
1065
- max: [bounds.max[0], bounds.max[1], bounds.max[2]]
1066
- };
1067
- }
1068
-
1069
- function transformInstancedBounds(
1070
- local: SceneBounds3,
1071
- modelMatrix: Mat4,
1072
- instanceTransforms: Float32Array | readonly number[]
1073
- ): SceneBounds3 {
1074
- let bounds = new SceneBounds3();
1075
- for (let offset = 0; offset + 15 < instanceTransforms.length; offset += 16) {
1076
- bounds = bounds.union(local.transform(multiplyMat4(modelMatrix, toMat4(instanceTransforms.slice(offset, offset + 16)))));
1077
- }
1078
- return bounds;
1079
- }
1080
-
1081
- function toMat4(values: Float32Array | readonly number[]): Mat4 {
1082
- const array = Array.from(values);
1083
- if (array.length !== 16 || array.some((value) => !Number.isFinite(value))) {
1084
- throw new Error("glTF render resource instance transform must be a finite mat4.");
1085
- }
1086
- return array as Mat4;
1087
- }
1088
-
1089
- function disposeGLTFRenderResourceMaps(
1090
- geometryLibrary: ReadonlyMap<string, Geometry>,
1091
- textureLibrary: ReadonlyMap<string, Texture>
1092
- ): void {
1093
- for (const geometry of geometryLibrary.values()) geometry.dispose();
1094
- for (const texture of textureLibrary.values()) texture.dispose();
1095
- }
1096
-
1097
- function materialForMesh(
1098
- asset: GLTFAsset,
1099
- mesh: GLTFMeshAsset,
1100
- materialVariant: string | undefined
1101
- ): GLTFMaterialAsset | undefined {
1102
- const materialIndex = materialVariant
1103
- ? mesh.materialVariants.find((mapping) => mapping.variant === materialVariant)?.materialIndex ?? mesh.materialIndex
1104
- : mesh.materialIndex;
1105
- return materialIndex === undefined ? undefined : asset.materials[materialIndex];
1106
- }
1107
-
1108
- function createGeometry(mesh: GLTFMeshAsset, material: GLTFMaterialAsset | undefined): Geometry {
1109
- const usedTexCoordSets = usedRenderTexCoordSets(material);
1110
- const texcoords = selectRenderTexcoords(mesh, material, 0);
1111
- const texcoords1 = usedTexCoordSets.has(1) ? selectRenderTexcoords(mesh, material, 1) : [];
1112
- const needsUv = usedTexCoordSets.has(0) || texcoords.length > 0;
1113
- const needsUv1 = texcoords1.length > 0;
1114
- const needsNormal = mesh.normals.length > 0 || needsUv || materialNeedsNormals(material);
1115
- const needsTangent = mesh.tangents.length > 0 || (needsUv && (materialNeedsTangents(material) || (material !== undefined && !material.unlit)));
1116
- const needsColor = mesh.colors.length > 0;
1117
- const needsSkinning = mesh.skinIndex !== undefined || mesh.joints.length > 0 || mesh.weights.length > 0;
1118
- const format = vertexFormatForGLTFMesh(needsNormal, needsUv, needsTangent, needsUv1, needsColor, needsSkinning);
1119
- const renderNormals = needsNormal && mesh.normals.length === 0
1120
- ? generateMeshNormals(mesh.positions, mesh.indices, mesh.topology)
1121
- : mesh.normals;
1122
- const renderTangents = needsTangent && mesh.tangents.length === 0
1123
- ? generateMeshTangents(mesh.positions, texcoords, mesh.indices, mesh.topology, renderNormals)
1124
- : mesh.tangents;
1125
- const vertices = new VertexBuffer(format, mesh.positions.length);
1126
- for (let index = 0; index < mesh.positions.length; index += 1) {
1127
- vertices.setAttribute(index, "position", mesh.positions[index]!);
1128
- if (format.hasAttribute("normal")) {
1129
- vertices.setAttribute(index, "normal", renderNormals[index] ?? [0, 0, 1]);
1130
- }
1131
- if (format.hasAttribute("tangent")) {
1132
- vertices.setAttribute(index, "tangent", renderTangents[index] ?? fallbackTangent(renderNormals[index]));
1133
- }
1134
- if (format.hasAttribute("uv")) {
1135
- vertices.setAttribute(index, "uv", texcoords[index] ?? [0, 0]);
1136
- }
1137
- if (format.hasAttribute("uv1")) {
1138
- vertices.setAttribute(index, "uv1", texcoords1[index] ?? texcoords[index] ?? [0, 0]);
1139
- }
1140
- if (format.hasAttribute("color")) {
1141
- vertices.setAttribute(index, "color", mesh.colors[index] ?? [1, 1, 1, 1]);
1142
- }
1143
- if (format.hasAttribute("joints")) {
1144
- vertices.setAttribute(index, "joints", mesh.joints[index] ?? [0, 0, 0, 0]);
1145
- vertices.setAttribute(index, "weights", mesh.weights[index] ?? [0, 0, 0, 0]);
1146
- }
1147
- }
1148
- const indices = mesh.indices && mesh.indices.length > 0 ? new IndexBuffer(mesh.indices, mesh.positions.length) : null;
1149
- return new Geometry(vertices, indices, mesh.topology, mesh.geometry.bounds);
1150
- }
1151
-
1152
- function generateMeshNormals(
1153
- positions: readonly (readonly [number, number, number])[],
1154
- indices: readonly number[] | undefined,
1155
- topology: Geometry["topology"]
1156
- ): readonly (readonly [number, number, number])[] {
1157
- const normals = Array.from({ length: positions.length }, () => [0, 0, 0] as [number, number, number]);
1158
- if (topology !== "triangles") {
1159
- return normals.map(() => [0, 0, 1] as const);
1160
- }
1161
- const triangleIndices = indices && indices.length > 0 ? indices : positions.map((_, index) => index);
1162
- for (let offset = 0; offset + 2 < triangleIndices.length; offset += 3) {
1163
- const ia = triangleIndices[offset]!;
1164
- const ib = triangleIndices[offset + 1]!;
1165
- const ic = triangleIndices[offset + 2]!;
1166
- const a = positions[ia];
1167
- const b = positions[ib];
1168
- const c = positions[ic];
1169
- if (!a || !b || !c) continue;
1170
- const normal = cross3(subtract3(b, a), subtract3(c, a));
1171
- normals[ia] = add3(normals[ia]!, normal);
1172
- normals[ib] = add3(normals[ib]!, normal);
1173
- normals[ic] = add3(normals[ic]!, normal);
1174
- }
1175
- return normals.map((normal) => normalize3(normal));
1176
- }
1177
-
1178
- function generateMeshTangents(
1179
- positions: readonly (readonly [number, number, number])[],
1180
- texcoords: readonly (readonly [number, number])[],
1181
- indices: readonly number[] | undefined,
1182
- topology: Geometry["topology"],
1183
- normals: readonly (readonly [number, number, number])[]
1184
- ): readonly (readonly [number, number, number, number])[] {
1185
- const tangents = Array.from({ length: positions.length }, () => [0, 0, 0] as [number, number, number]);
1186
- const bitangents = Array.from({ length: positions.length }, () => [0, 0, 0] as [number, number, number]);
1187
- if (topology !== "triangles" || texcoords.length === 0) {
1188
- return normals.map((normal) => fallbackTangent(normal));
1189
- }
1190
- const triangleIndices = indices && indices.length > 0 ? indices : positions.map((_, index) => index);
1191
- for (let offset = 0; offset + 2 < triangleIndices.length; offset += 3) {
1192
- const ia = triangleIndices[offset]!;
1193
- const ib = triangleIndices[offset + 1]!;
1194
- const ic = triangleIndices[offset + 2]!;
1195
- const a = positions[ia];
1196
- const b = positions[ib];
1197
- const c = positions[ic];
1198
- const uva = texcoords[ia];
1199
- const uvb = texcoords[ib];
1200
- const uvc = texcoords[ic];
1201
- if (!a || !b || !c || !uva || !uvb || !uvc) continue;
1202
- const edge1 = subtract3(b, a);
1203
- const edge2 = subtract3(c, a);
1204
- const duv1: readonly [number, number] = [uvb[0] - uva[0], uvb[1] - uva[1]];
1205
- const duv2: readonly [number, number] = [uvc[0] - uva[0], uvc[1] - uva[1]];
1206
- const determinant = duv1[0] * duv2[1] - duv2[0] * duv1[1];
1207
- if (Math.abs(determinant) <= 1e-8) continue;
1208
- const scale = 1 / determinant;
1209
- const tangent = scale3(subtract3(scale3(edge1, duv2[1]), scale3(edge2, duv1[1])), scale);
1210
- const bitangent = scale3(subtract3(scale3(edge2, duv1[0]), scale3(edge1, duv2[0])), scale);
1211
- tangents[ia] = add3(tangents[ia]!, tangent);
1212
- tangents[ib] = add3(tangents[ib]!, tangent);
1213
- tangents[ic] = add3(tangents[ic]!, tangent);
1214
- bitangents[ia] = add3(bitangents[ia]!, bitangent);
1215
- bitangents[ib] = add3(bitangents[ib]!, bitangent);
1216
- bitangents[ic] = add3(bitangents[ic]!, bitangent);
1217
- }
1218
- return tangents.map((tangent, index) => {
1219
- const normal = normals[index] ?? [0, 0, 1];
1220
- const orthogonal = subtract3(tangent, scale3(normal, dot3(normal, tangent)));
1221
- if (length3(orthogonal) <= 1e-8) return fallbackTangent(normal);
1222
- const normalized = normalize3(orthogonal);
1223
- const handedness = dot3(cross3(normal, normalized), bitangents[index] ?? [0, 1, 0]) < 0 ? -1 : 1;
1224
- return [normalized[0], normalized[1], normalized[2], handedness] as const;
1225
- });
1226
- }
1227
-
1228
- function subtract3(left: readonly [number, number, number], right: readonly [number, number, number]): [number, number, number] {
1229
- return [left[0] - right[0], left[1] - right[1], left[2] - right[2]];
1230
- }
1231
-
1232
- function add3(left: readonly [number, number, number], right: readonly [number, number, number]): [number, number, number] {
1233
- return [left[0] + right[0], left[1] + right[1], left[2] + right[2]];
1234
- }
1235
-
1236
- function scale3(value: readonly [number, number, number], scalar: number): [number, number, number] {
1237
- return [value[0] * scalar, value[1] * scalar, value[2] * scalar];
1238
- }
1239
-
1240
- function dot3(left: readonly [number, number, number], right: readonly [number, number, number]): number {
1241
- return left[0] * right[0] + left[1] * right[1] + left[2] * right[2];
1242
- }
1243
-
1244
- function cross3(left: readonly [number, number, number], right: readonly [number, number, number]): [number, number, number] {
1245
- return [
1246
- left[1] * right[2] - left[2] * right[1],
1247
- left[2] * right[0] - left[0] * right[2],
1248
- left[0] * right[1] - left[1] * right[0]
1249
- ];
1250
- }
1251
-
1252
- function length3(value: readonly [number, number, number]): number {
1253
- return Math.hypot(value[0], value[1], value[2]);
1254
- }
1255
-
1256
- function normalize3(value: readonly [number, number, number]): readonly [number, number, number] {
1257
- const length = length3(value);
1258
- if (length <= 1e-8) return [0, 0, 1];
1259
- return [value[0] / length, value[1] / length, value[2] / length];
1260
- }
1261
-
1262
- function fallbackTangent(normal: readonly [number, number, number] | undefined): readonly [number, number, number, number] {
1263
- const n = normal ?? [0, 0, 1];
1264
- const reference: readonly [number, number, number] = Math.abs(n[1]) < 0.9 ? [0, 1, 0] : [1, 0, 0];
1265
- const tangent = normalize3(cross3(reference, n));
1266
- return [tangent[0], tangent[1], tangent[2], 1];
1267
- }
1268
-
1269
- function selectRenderTexcoords(
1270
- mesh: GLTFMeshAsset,
1271
- material: GLTFMaterialAsset | undefined,
1272
- setIndex: number
1273
- ): readonly (readonly [number, number])[] {
1274
- const selected = mesh.texcoordSets[setIndex];
1275
- if (selected && selected.length > 0) return selected;
1276
- if (setIndex > 0) {
1277
- throw new Error(`glTF mesh ${mesh.name} material ${mesh.material} references missing TEXCOORD_${setIndex}`);
1278
- }
1279
- return mesh.texcoords;
1280
- }
1281
-
1282
- function usedRenderTexCoordSets(material: GLTFMaterialAsset | undefined): ReadonlySet<number> {
1283
- const sets = new Set<number>();
1284
- const infos = material ? materialTextureInfos(material) : [];
1285
- for (const info of infos) {
1286
- if (info.texCoord <= 1) sets.add(info.texCoord);
1287
- }
1288
- if (sets.size === 0 && infos.length > 0) sets.add(0);
1289
- return sets;
1290
- }
1291
-
1292
- function materialTextureInfos(material: GLTFMaterialAsset): readonly GLTFResolvedTextureInfo[] {
1293
- return materialTextureInfosBySlot(material).map(({ info }) => info);
1294
- }
1295
-
1296
- function materialTextureInfosBySlot(material: GLTFMaterialAsset): readonly {
1297
- readonly slot: TexturedPBRTextureSlot;
1298
- readonly info: GLTFResolvedTextureInfo;
1299
- readonly tangentSpace: boolean;
1300
- }[] {
1301
- return [
1302
- material.baseColorTexture ? { slot: "baseColor", info: material.baseColorTexture, tangentSpace: false } : undefined,
1303
- material.metallicRoughnessTexture ? { slot: "metallicRoughness", info: material.metallicRoughnessTexture, tangentSpace: false } : undefined,
1304
- material.normalTexture ? { slot: "normal", info: material.normalTexture, tangentSpace: true } : undefined,
1305
- material.occlusionTexture ? { slot: "occlusion", info: material.occlusionTexture, tangentSpace: false } : undefined,
1306
- material.emissiveTexture ? { slot: "emissive", info: material.emissiveTexture, tangentSpace: false } : undefined,
1307
- material.clearcoat?.texture ? { slot: "clearcoat", info: material.clearcoat.texture, tangentSpace: false } : undefined,
1308
- material.clearcoat?.roughnessTexture ? { slot: "clearcoatRoughness", info: material.clearcoat.roughnessTexture, tangentSpace: false } : undefined,
1309
- material.clearcoat?.normalTexture ? { slot: "clearcoatNormal", info: material.clearcoat.normalTexture, tangentSpace: true } : undefined,
1310
- material.transmission?.texture ? { slot: "transmission", info: material.transmission.texture, tangentSpace: false } : undefined,
1311
- material.diffuseTransmission?.texture ? { slot: "diffuseTransmission", info: material.diffuseTransmission.texture, tangentSpace: false } : undefined,
1312
- material.diffuseTransmission?.colorTexture ? { slot: "diffuseTransmissionColor", info: material.diffuseTransmission.colorTexture, tangentSpace: false } : undefined,
1313
- material.volume?.thicknessTexture ? { slot: "volumeThickness", info: material.volume.thicknessTexture, tangentSpace: false } : undefined,
1314
- material.specular?.texture ? { slot: "specular", info: material.specular.texture, tangentSpace: false } : undefined,
1315
- material.specular?.colorTexture ? { slot: "specularColor", info: material.specular.colorTexture, tangentSpace: false } : undefined,
1316
- material.sheen?.colorTexture ? { slot: "sheenColor", info: material.sheen.colorTexture, tangentSpace: false } : undefined,
1317
- material.sheen?.roughnessTexture ? { slot: "sheenRoughness", info: material.sheen.roughnessTexture, tangentSpace: false } : undefined,
1318
- material.anisotropy?.texture ? { slot: "anisotropy", info: material.anisotropy.texture, tangentSpace: false } : undefined,
1319
- material.iridescence?.texture ? { slot: "iridescence", info: material.iridescence.texture, tangentSpace: false } : undefined,
1320
- material.iridescence?.thicknessTexture ? { slot: "iridescenceThickness", info: material.iridescence.thicknessTexture, tangentSpace: false } : undefined
1321
- ].filter((entry): entry is {
1322
- readonly slot: TexturedPBRTextureSlot;
1323
- readonly info: GLTFResolvedTextureInfo;
1324
- readonly tangentSpace: boolean;
1325
- } => entry !== undefined);
1326
- }
1327
-
1328
- function materialNeedsNormals(material: GLTFMaterialAsset | undefined): boolean {
1329
- return material === undefined || !material.unlit;
1330
- }
1331
-
1332
- function materialNeedsTangents(material: GLTFMaterialAsset | undefined): boolean {
1333
- return Boolean(material?.normalTexture || material?.clearcoat?.normalTexture);
1334
- }
1335
-
1336
- function vertexFormatForGLTFMesh(needsNormal: boolean, needsUv: boolean, needsTangent: boolean, needsUv1: boolean, needsColor: boolean, needsSkinning: boolean): VertexFormat {
1337
- if (!needsColor && !needsSkinning && !needsUv1) {
1338
- if (needsUv && needsTangent) return VertexFormat.P3N3T4T2;
1339
- if (needsUv) return needsNormal ? VertexFormat.P3N3T2 : new VertexFormat([
1340
- { semantic: "position", components: 3, offset: 0 },
1341
- { semantic: "uv", components: 2, offset: 12 }
1342
- ], 20);
1343
- return needsNormal ? VertexFormat.P3N3 : VertexFormat.P3;
1344
- }
1345
- if (!needsColor && needsSkinning && !needsUv) return needsNormal ? VertexFormat.P3N3J4W4 : VertexFormat.P3J4W4;
1346
- const attributes: VertexAttributeDescriptor[] = [{ semantic: "position", components: 3, offset: 0 }];
1347
- let offset = 12;
1348
- if (needsNormal) {
1349
- attributes.push({ semantic: "normal", components: 3, offset });
1350
- offset += 12;
1351
- }
1352
- if (needsTangent) {
1353
- attributes.push({ semantic: "tangent", components: 4, offset });
1354
- offset += 16;
1355
- }
1356
- if (needsUv) {
1357
- attributes.push({ semantic: "uv", components: 2, offset });
1358
- offset += 8;
1359
- }
1360
- if (needsUv1) {
1361
- attributes.push({ semantic: "uv1", components: 2, offset });
1362
- offset += 8;
1363
- }
1364
- if (needsColor) {
1365
- attributes.push({ semantic: "color", components: 4, offset });
1366
- offset += 16;
1367
- }
1368
- if (needsSkinning) {
1369
- attributes.push({ semantic: "joints", components: 4, offset });
1370
- offset += 16;
1371
- attributes.push({ semantic: "weights", components: 4, offset });
1372
- offset += 16;
1373
- }
1374
- return new VertexFormat(attributes, offset);
1375
- }
1376
-
1377
- async function createMaterial(
1378
- asset: GLTFAsset,
1379
- material: GLTFMaterialAsset,
1380
- getTexture: (info: GLTFResolvedTextureInfo, colorSpace: GLTFTextureColorSpace) => Promise<Texture>,
1381
- options: { readonly skinned?: boolean; readonly instanced?: boolean } = {},
1382
- renderStateOverrides: readonly GLTFMaterialRenderStateOverride[] = []
1383
- ): Promise<Material> {
1384
- const renderState = renderStateForGLTFMaterial(material, renderStateOverrides);
1385
- if (options.skinned && !material.unlit) {
1386
- const [
1387
- baseColorTexture,
1388
- normalTexture,
1389
- metallicRoughnessTexture,
1390
- occlusionTexture,
1391
- emissiveTexture,
1392
- clearcoatTexture,
1393
- clearcoatNormalTexture,
1394
- sheenColorTexture,
1395
- sheenRoughnessTexture,
1396
- transmissionTexture,
1397
- iridescenceTexture,
1398
- iridescenceThicknessTexture,
1399
- anisotropyTexture,
1400
- volumeThicknessTexture
1401
- ] = await Promise.all([
1402
- material.baseColorTexture
1403
- ? createTextureBinding(asset, material.baseColorTexture, "srgb", getTexture, "u_baseColorTexture")
1404
- : Promise.resolve(undefined),
1405
- material.normalTexture
1406
- ? createTextureBinding(asset, material.normalTexture, "linear", getTexture, "u_normalTexture", createNormalSampler(asset, material))
1407
- : Promise.resolve(undefined),
1408
- material.metallicRoughnessTexture
1409
- ? createTextureBinding(asset, material.metallicRoughnessTexture, "linear", getTexture, "u_metallicRoughnessTexture")
1410
- : Promise.resolve(undefined),
1411
- material.occlusionTexture
1412
- ? createTextureBinding(asset, material.occlusionTexture, "linear", getTexture, "u_occlusionTexture")
1413
- : Promise.resolve(undefined),
1414
- material.emissiveTexture
1415
- ? createTextureBinding(asset, material.emissiveTexture, "srgb", getTexture, "u_emissiveTexture")
1416
- : Promise.resolve(undefined),
1417
- material.clearcoat?.texture
1418
- ? createTextureBinding(asset, material.clearcoat.texture, "linear", getTexture, "u_clearcoatTexture")
1419
- : Promise.resolve(undefined),
1420
- material.clearcoat?.normalTexture
1421
- ? createTextureBinding(asset, material.clearcoat.normalTexture, "linear", getTexture, "u_clearcoatNormalTexture", createNormalSamplerForInfo(asset, material.clearcoat.normalTexture))
1422
- : Promise.resolve(undefined),
1423
- material.sheen?.colorTexture
1424
- ? createTextureBinding(asset, material.sheen.colorTexture, "srgb", getTexture, "u_sheenColorTexture")
1425
- : Promise.resolve(undefined),
1426
- material.sheen?.roughnessTexture
1427
- ? createTextureBinding(asset, material.sheen.roughnessTexture, "linear", getTexture, "u_sheenRoughnessTexture")
1428
- : Promise.resolve(undefined),
1429
- material.transmission?.texture
1430
- ? createTextureBinding(asset, material.transmission.texture, "linear", getTexture, "u_transmissionTexture")
1431
- : Promise.resolve(undefined),
1432
- material.iridescence?.texture
1433
- ? createTextureBinding(asset, material.iridescence.texture, "linear", getTexture, "u_iridescenceTexture")
1434
- : Promise.resolve(undefined),
1435
- material.iridescence?.thicknessTexture
1436
- ? createTextureBinding(asset, material.iridescence.thicknessTexture, "linear", getTexture, "u_iridescenceThicknessTexture")
1437
- : Promise.resolve(undefined),
1438
- material.anisotropy?.texture
1439
- ? createTextureBinding(asset, material.anisotropy.texture, "linear", getTexture, "u_anisotropyTexture")
1440
- : Promise.resolve(undefined),
1441
- material.volume?.thicknessTexture
1442
- ? createTextureBinding(asset, material.volume.thicknessTexture, "linear", getTexture, "u_volumeThicknessTexture")
1443
- : Promise.resolve(undefined)
1444
- ]);
1445
- const runtimeMaterial = new SkinnedLitMaterial({
1446
- name: material.name,
1447
- renderState,
1448
- baseColor: material.baseColorFactor,
1449
- baseColorTexture,
1450
- baseColorTextureOffset: material.baseColorTexture?.transform?.offset,
1451
- baseColorTextureScale: material.baseColorTexture?.transform?.scale,
1452
- baseColorTextureRotation: material.baseColorTexture?.transform?.rotation,
1453
- normalTexture,
1454
- normalScale: renderNormalScale(asset, material),
1455
- metallicRoughnessTexture,
1456
- occlusionTexture,
1457
- occlusionStrength: material.occlusionTexture?.strength,
1458
- emissiveTexture,
1459
- metallic: material.metallicFactor,
1460
- roughness: material.roughnessFactor,
1461
- emissiveColor: material.emissiveFactor,
1462
- emissiveStrength: material.emissiveStrength,
1463
- clearcoatTexture,
1464
- clearcoatNormalTexture,
1465
- sheenColorTexture,
1466
- sheenRoughnessTexture,
1467
- transmissionTexture,
1468
- iridescenceTexture,
1469
- iridescenceThicknessTexture,
1470
- anisotropyTexture,
1471
- volumeThicknessTexture,
1472
- ...pbrExtensionScalarOptions(material)
1473
- });
1474
- applyAlphaCutoff(runtimeMaterial, material);
1475
- await applyPBRExtensionParameters(asset, runtimeMaterial, material, getTexture);
1476
- return runtimeMaterial;
1477
- }
1478
- if (options.instanced && material.unlit && !material.baseColorTexture) {
1479
- const runtimeMaterial = new InstancedUnlitMaterial({
1480
- name: material.name,
1481
- color: material.baseColorFactor,
1482
- renderState
1483
- });
1484
- applyAlphaCutoff(runtimeMaterial, material);
1485
- await applyPBRExtensionParameters(asset, runtimeMaterial, material, getTexture);
1486
- return runtimeMaterial;
1487
- }
1488
- if (material.unlit) {
1489
- if (material.baseColorTexture) {
1490
- const texture = await getTexture(material.baseColorTexture, "srgb");
1491
- const runtimeMaterial = new TexturedUnlitMaterial({
1492
- name: material.name,
1493
- texture,
1494
- sampler: createSampler(asset.textures[material.baseColorTexture.texture]),
1495
- textureTransform: material.baseColorTexture.transform,
1496
- color: material.baseColorFactor,
1497
- renderState
1498
- });
1499
- applyAlphaCutoff(runtimeMaterial, material);
1500
- await applyPBRExtensionParameters(asset, runtimeMaterial, material, getTexture);
1501
- return runtimeMaterial;
1502
- }
1503
- const runtimeMaterial = new UnlitMaterial({
1504
- name: material.name,
1505
- color: material.baseColorFactor,
1506
- renderState
1507
- });
1508
- applyAlphaCutoff(runtimeMaterial, material);
1509
- await applyPBRExtensionParameters(asset, runtimeMaterial, material, getTexture);
1510
- return runtimeMaterial;
1511
- }
1512
- if (options.instanced && !requiresTexturedPBRMaterial(material) && !material.baseColorTexture) {
1513
- const runtimeMaterial = new InstancedPBRMaterial({
1514
- name: material.name,
1515
- renderState,
1516
- baseColor: material.baseColorFactor,
1517
- metallic: material.metallicFactor,
1518
- roughness: material.roughnessFactor,
1519
- emissiveColor: material.emissiveFactor,
1520
- emissiveStrength: material.emissiveStrength
1521
- });
1522
- applyAlphaCutoff(runtimeMaterial, material);
1523
- await applyPBRExtensionParameters(asset, runtimeMaterial, material, getTexture);
1524
- return runtimeMaterial;
1525
- }
1526
- if (requiresTexturedPBRMaterial(material)) {
1527
- const baseColorTexture = material.baseColorTexture ? await getTexture(material.baseColorTexture, "srgb") : undefined;
1528
- const normalTexture = material.normalTexture ? await getTexture(material.normalTexture, "linear") : undefined;
1529
- const metallicRoughnessTexture = material.metallicRoughnessTexture ? await getTexture(material.metallicRoughnessTexture, "linear") : undefined;
1530
- const occlusionTexture = material.occlusionTexture ? await getTexture(material.occlusionTexture, "linear") : undefined;
1531
- const emissiveTexture = material.emissiveTexture ? await getTexture(material.emissiveTexture, "srgb") : undefined;
1532
- const extensionTextureOptions = await pbrExtensionTextureOptions(asset, material, getTexture);
1533
- const runtimeMaterial = new TexturedPBRMaterial({
1534
- name: material.name,
1535
- renderState,
1536
- baseColor: renderPbrBaseColorFactor(material),
1537
- metallic: material.metallicFactor,
1538
- roughness: renderPbrRoughnessFactor(material),
1539
- emissiveColor: material.emissiveFactor,
1540
- emissiveStrength: material.emissiveStrength,
1541
- textureTexCoords: pbrTextureTexCoords(material),
1542
- ...pbrExtensionScalarOptions(material),
1543
- ...extensionTextureOptions,
1544
- baseColorTexture,
1545
- baseColorSampler: createSampler(material.baseColorTexture ? asset.textures[material.baseColorTexture.texture] : undefined),
1546
- baseColorTextureTransform: material.baseColorTexture?.transform,
1547
- normalTexture,
1548
- normalSampler: createNormalSampler(asset, material),
1549
- normalTextureTransform: material.normalTexture?.transform,
1550
- normalScale: renderNormalScale(asset, material),
1551
- metallicRoughnessTexture,
1552
- metallicRoughnessSampler: createSampler(material.metallicRoughnessTexture ? asset.textures[material.metallicRoughnessTexture.texture] : undefined),
1553
- metallicRoughnessTextureTransform: material.metallicRoughnessTexture?.transform,
1554
- occlusionTexture,
1555
- occlusionSampler: createSampler(material.occlusionTexture ? asset.textures[material.occlusionTexture.texture] : undefined),
1556
- occlusionTextureTransform: material.occlusionTexture?.transform,
1557
- occlusionStrength: material.occlusionTexture?.strength,
1558
- emissiveTexture,
1559
- emissiveSampler: createSampler(material.emissiveTexture ? asset.textures[material.emissiveTexture.texture] : undefined),
1560
- emissiveTextureTransform: material.emissiveTexture?.transform
1561
- });
1562
- applyAlphaCutoff(runtimeMaterial, material);
1563
- await applyPBRExtensionParameters(asset, runtimeMaterial, material, getTexture);
1564
- return runtimeMaterial;
1565
- }
1566
- if (material.baseColorTexture) {
1567
- const texture = await getTexture(material.baseColorTexture, "srgb");
1568
- const runtimeMaterial = new TexturedUnlitMaterial({
1569
- name: material.name,
1570
- texture,
1571
- sampler: createSampler(asset.textures[material.baseColorTexture.texture]),
1572
- textureTransform: material.baseColorTexture.transform,
1573
- color: material.baseColorFactor,
1574
- renderState
1575
- });
1576
- applyAlphaCutoff(runtimeMaterial, material);
1577
- await applyPBRExtensionParameters(asset, runtimeMaterial, material, getTexture);
1578
- return runtimeMaterial;
1579
- }
1580
- const runtimeMaterial = new PBRMaterial({
1581
- name: material.name,
1582
- renderState,
1583
- baseColor: renderPbrBaseColorFactor(material),
1584
- metallic: material.metallicFactor,
1585
- roughness: renderPbrRoughnessFactor(material),
1586
- emissiveColor: material.emissiveFactor,
1587
- emissiveStrength: material.emissiveStrength,
1588
- ...pbrExtensionScalarOptions(material)
1589
- });
1590
- applyAlphaCutoff(runtimeMaterial, material);
1591
- await applyPBRExtensionParameters(asset, runtimeMaterial, material, getTexture);
1592
- return runtimeMaterial;
1593
- }
1594
-
1595
- function createDefaultGLTFMaterial(mesh: GLTFMeshAsset, options: { readonly instanced?: boolean; readonly skinned?: boolean } = {}): Material {
1596
- // Use a neutral light-gray base color instead of glTF-spec white ([1,1,1,1])
1597
- // so missing-material fallbacks do not blow out as bright white artifacts.
1598
- const defaults = {
1599
- name: mesh.material,
1600
- baseColor: [0.76, 0.74, 0.72, 1] as const,
1601
- metallic: 0,
1602
- roughness: 0.85,
1603
- environmentIntensity: DEFAULT_PBR_ENVIRONMENT_INTENSITY
1604
- } as const;
1605
- if (options.skinned) {
1606
- return new SkinnedLitMaterial(defaults);
1607
- }
1608
- if (options.instanced) {
1609
- return new InstancedPBRMaterial(defaults);
1610
- }
1611
- return new PBRMaterial(defaults);
1612
- }
1613
-
1614
- /**
1615
- * baseColorFactor alpha at or above this value is treated as fully opaque when
1616
- * deciding whether a `BLEND` material is a spurious-blend (ghost) case. 8-bit
1617
- * authoring rounds 255/255 to 1.0 but tools occasionally emit 0.996 (254/255),
1618
- * so we allow a small epsilon while still catching genuine fades.
1619
- */
1620
- const BLEND_OPAQUE_ALPHA_THRESHOLD = 0.996;
1621
-
1622
- export function renderStateForGLTFMaterial(
1623
- material: GLTFMaterialAsset,
1624
- overrides: readonly GLTFMaterialRenderStateOverride[] = []
1625
- ): Partial<RenderState> {
1626
- const blend = requiresTransparentRenderState(material);
1627
- const cullBack = usesUnbackedScalarTransmission(material) || usesOpaqueDoubleSidedClearcoatShell(material);
1628
- const baseState: Partial<RenderState> = {
1629
- cullMode: cullBack ? "back" : material.doubleSided ? "none" : "back",
1630
- blend,
1631
- depthWrite: !blend
1632
- };
1633
- const override = overrides.find((entry) => matchesTextOrPattern(material.name, entry.materialName));
1634
- return override ? { ...baseState, ...override.renderState } : baseState;
1635
- }
1636
-
1637
- function requiresTransparentRenderState(material: GLTFMaterialAsset): boolean {
1638
- if (material.alphaMode === "BLEND") return !isEffectivelyOpaqueBlendMaterial(material);
1639
- if (material.alphaMode !== "OPAQUE") return false;
1640
- if (usesUnbackedScalarTransmission(material)) return false;
1641
- return materialHasTransmissionOrVolume(material);
1642
- }
1643
-
1644
- /**
1645
- * Many catalog GLBs (notably Sketchfab exports) spuriously tag fully opaque
1646
- * materials with `alphaMode: "BLEND"`. The standard glTF blend equation over a
1647
- * fully opaque surface (baseColorFactor alpha == 1, no texel alpha in use) is a
1648
- * no-op, but enabling blend disables depth writes — so those characters render
1649
- * as translucent "ghosts" with broken depth sorting.
1650
- *
1651
- * When a BLEND material is provably opaque we treat it as OPAQUE (blend off,
1652
- * depthWrite on). The check is deliberately conservative: anything that could
1653
- * carry real transparency — alpha factor < 1, transmission/volume, or a
1654
- * meaningful alphaCutoff — keeps blending.
1655
- *
1656
- * Known edge case: a baseColorTexture's own alpha channel cannot be inspected
1657
- * here (images are decoded asynchronously and channel-usage is not tracked on
1658
- * the material), so a BLEND material that relies purely on per-texel texture
1659
- * alpha while keeping baseColorFactor alpha == 1 would be flattened to opaque.
1660
- * That pattern is rare in the wild (such assets normally use MASK or set the
1661
- * factor alpha < 1), and the override path (renderStateOverrides /
1662
- * GLTFMaterialRenderStateOverride) remains available to force blend back on.
1663
- */
1664
- function isEffectivelyOpaqueBlendMaterial(material: GLTFMaterialAsset): boolean {
1665
- if (material.alphaMode !== "BLEND") return false;
1666
- if (material.baseColorFactor[3] < BLEND_OPAQUE_ALPHA_THRESHOLD) return false;
1667
- // Conservative: if a baseColorTexture is present, retain blending — the texture may
1668
- // carry per-texel alpha that we cannot inspect here. Flattening would depth-sort it
1669
- // as opaque and cause ghosting / overlay artifacts.
1670
- if (material.baseColorTexture) return false;
1671
- if (materialHasTransmissionOrVolume(material)) return false;
1672
- return true;
1673
- }
1674
-
1675
- function materialHasTransmissionOrVolume(material: GLTFMaterialAsset): boolean {
1676
- return (material.transmission?.factor ?? 0) > 0.001
1677
- || (material.diffuseTransmission?.factor ?? 0) > 0.001
1678
- || (material.volume?.thicknessFactor ?? 0) > 0.001
1679
- || material.volume?.thicknessTexture !== undefined;
1680
- }
1681
-
1682
- function usesUnbackedScalarTransmission(material: GLTFMaterialAsset): boolean {
1683
- return material.alphaMode === "OPAQUE"
1684
- && (material.transmission?.factor ?? 0) > 0.001
1685
- && material.transmission?.texture === undefined
1686
- && material.diffuseTransmission === undefined
1687
- && material.volume === undefined;
1688
- }
1689
-
1690
- function usesOpaqueDoubleSidedClearcoatShell(material: GLTFMaterialAsset): boolean {
1691
- return material.alphaMode === "OPAQUE"
1692
- && material.doubleSided
1693
- && (material.clearcoat?.factor ?? 0) > 0.001
1694
- && material.metallicFactor > 0.35
1695
- && material.roughnessFactor <= 0.42;
1696
- }
1697
-
1698
- function renderPbrBaseColorFactor(material: GLTFMaterialAsset): readonly [number, number, number, number] {
1699
- if (!usesUnbackedScalarTransmission(material)) return material.baseColorFactor;
1700
- const maxColor = Math.max(material.baseColorFactor[0], material.baseColorFactor[1], material.baseColorFactor[2]);
1701
- if (maxColor < 0.8) return material.baseColorFactor;
1702
- return [0.028, 0.036, 0.044, material.baseColorFactor[3]];
1703
- }
1704
-
1705
- function renderPbrRoughnessFactor(material: GLTFMaterialAsset): number {
1706
- if (!usesUnbackedScalarTransmission(material)) return material.roughnessFactor;
1707
- return Math.max(material.roughnessFactor, 0.72);
1708
- }
1709
-
1710
- function applyAlphaCutoff(runtimeMaterial: Material, material: GLTFMaterialAsset): void {
1711
- runtimeMaterial.setParameter("u_alphaCutoff", material.alphaMode === "MASK" ? material.alphaCutoff : 0);
1712
- }
1713
-
1714
- function renderNormalScale(asset: GLTFAsset, material: GLTFMaterialAsset): number | undefined {
1715
- return renderNormalMapScale(asset, material.normalTexture);
1716
- }
1717
-
1718
- function createNormalSampler(asset: GLTFAsset, material: GLTFMaterialAsset): Sampler {
1719
- const texture = material.normalTexture ? asset.textures[material.normalTexture.texture] : undefined;
1720
- return createNormalMapSampler(texture);
1721
- }
1722
-
1723
- function createNormalSamplerForInfo(asset: GLTFAsset, info: GLTFResolvedTextureInfo | undefined): Sampler {
1724
- const texture = info ? asset.textures[info.texture] : undefined;
1725
- return createNormalMapSampler(texture);
1726
- }
1727
-
1728
- function renderNormalMapScale(asset: GLTFAsset, info: (GLTFResolvedTextureInfo & { readonly scale: number }) | undefined): number | undefined {
1729
- if (!info) return undefined;
1730
- return info.scale;
1731
- }
1732
-
1733
- function createNormalMapSampler(texture: GLTFTextureAsset | undefined): Sampler {
1734
- const sampler = createSampler(texture);
1735
- if (!usesNearestSampler(texture)) return sampler;
1736
- return new Sampler({
1737
- minFilter: "linear-mipmap-linear",
1738
- magFilter: "linear",
1739
- addressU: sampler.addressU,
1740
- addressV: sampler.addressV,
1741
- maxAnisotropy: sampler.maxAnisotropy
1742
- });
1743
- }
1744
-
1745
- function usesNearestSampler(texture: GLTFTextureAsset | undefined): boolean {
1746
- const sampler = createSampler(texture);
1747
- return sampler.magFilter === "nearest" || sampler.minFilter.startsWith("nearest");
1748
- }
1749
-
1750
- function pbrExtensionScalarOptions(material: GLTFMaterialAsset): {
1751
- readonly clearcoatFactor?: number;
1752
- readonly clearcoatRoughnessFactor?: number;
1753
- readonly transmissionFactor?: number;
1754
- readonly diffuseTransmissionFactor?: number;
1755
- readonly diffuseTransmissionColorFactor?: readonly [number, number, number];
1756
- readonly transmissionFallbackEnergy?: number;
1757
- readonly volumeThicknessFactor?: number;
1758
- readonly volumeAttenuationDistance?: number;
1759
- readonly volumeAttenuationColor?: readonly [number, number, number];
1760
- readonly ior?: number;
1761
- readonly specularFactor?: number;
1762
- readonly specularColorFactor?: readonly [number, number, number];
1763
- readonly sheenColorFactor?: readonly [number, number, number];
1764
- readonly sheenRoughnessFactor?: number;
1765
- readonly anisotropyStrength?: number;
1766
- readonly anisotropyRotation?: number;
1767
- readonly iridescenceFactor?: number;
1768
- readonly iridescenceIor?: number;
1769
- readonly iridescenceThicknessMinimum?: number;
1770
- readonly iridescenceThicknessMaximum?: number;
1771
- readonly dispersion?: number;
1772
- } {
1773
- return {
1774
- ...(material.clearcoat ? {
1775
- clearcoatFactor: material.clearcoat.factor,
1776
- clearcoatRoughnessFactor: renderClearcoatRoughnessFactor(material)
1777
- } : {}),
1778
- ...(material.transmission ? {
1779
- transmissionFactor: usesUnbackedScalarTransmission(material) ? 0 : material.transmission.factor,
1780
- transmissionFallbackEnergy: renderTransmissionFallbackEnergy(material)
1781
- } : {}),
1782
- ...(material.diffuseTransmission ? {
1783
- diffuseTransmissionFactor: material.diffuseTransmission.factor,
1784
- diffuseTransmissionColorFactor: material.diffuseTransmission.colorFactor
1785
- } : {}),
1786
- ...(material.volume ? {
1787
- volumeThicknessFactor: material.volume.thicknessFactor,
1788
- volumeAttenuationDistance: renderVolumeAttenuationDistance(material.volume.attenuationDistance),
1789
- volumeAttenuationColor: material.volume.attenuationColor
1790
- } : {}),
1791
- ...(material.ior !== undefined ? { ior: material.ior } : {}),
1792
- ...(material.specular ? {
1793
- specularFactor: material.specular.factor,
1794
- specularColorFactor: renderSpecularColorFactor(material.specular.colorFactor)
1795
- } : {}),
1796
- ...(material.sheen ? {
1797
- sheenColorFactor: material.sheen.colorFactor,
1798
- sheenRoughnessFactor: material.sheen.roughnessFactor
1799
- } : {}),
1800
- ...(material.anisotropy ? {
1801
- anisotropyStrength: material.anisotropy.strength,
1802
- anisotropyRotation: material.anisotropy.rotation
1803
- } : {}),
1804
- ...(material.iridescence ? {
1805
- iridescenceFactor: material.iridescence.factor,
1806
- iridescenceIor: material.iridescence.ior,
1807
- iridescenceThicknessMinimum: material.iridescence.thicknessMinimum,
1808
- iridescenceThicknessMaximum: material.iridescence.thicknessMaximum
1809
- } : {}),
1810
- ...(material.dispersion !== undefined ? {
1811
- dispersion: material.dispersion
1812
- } : {})
1813
- };
1814
- }
1815
-
1816
- async function pbrExtensionTextureOptions(
1817
- asset: GLTFAsset,
1818
- material: GLTFMaterialAsset,
1819
- getTexture: (info: GLTFResolvedTextureInfo, colorSpace: GLTFTextureColorSpace) => Promise<Texture>
1820
- ): Promise<Partial<TexturedPBRMaterialOptions>> {
1821
- const clearcoatTexture = material.clearcoat?.texture;
1822
- const clearcoatRoughnessTexture = material.clearcoat?.roughnessTexture;
1823
- const clearcoatNormalTexture = material.clearcoat?.normalTexture;
1824
- const transmissionTexture = material.transmission?.texture;
1825
- const diffuseTransmissionTexture = material.diffuseTransmission?.texture;
1826
- const diffuseTransmissionColorTexture = material.diffuseTransmission?.colorTexture;
1827
- const volumeThicknessTexture = material.volume?.thicknessTexture;
1828
- const specularTexture = material.specular?.texture;
1829
- const specularColorTexture = material.specular?.colorTexture;
1830
- const sheenColorTexture = material.sheen?.colorTexture;
1831
- const sheenRoughnessTexture = material.sheen?.roughnessTexture;
1832
- const anisotropyTexture = material.anisotropy?.texture;
1833
- const iridescenceTexture = material.iridescence?.texture;
1834
- const iridescenceThicknessTexture = material.iridescence?.thicknessTexture;
1835
-
1836
- return {
1837
- ...(clearcoatTexture ? {
1838
- clearcoatTexture: await getTexture(clearcoatTexture, "linear"),
1839
- clearcoatSampler: createSamplerForInfo(asset, clearcoatTexture),
1840
- clearcoatTextureTransform: clearcoatTexture.transform
1841
- } : {}),
1842
- ...(clearcoatRoughnessTexture ? {
1843
- clearcoatRoughnessTexture: await getTexture(clearcoatRoughnessTexture, "linear"),
1844
- clearcoatRoughnessSampler: createSamplerForInfo(asset, clearcoatRoughnessTexture),
1845
- clearcoatRoughnessTextureTransform: clearcoatRoughnessTexture.transform
1846
- } : {}),
1847
- ...(clearcoatNormalTexture ? {
1848
- clearcoatNormalTexture: await getTexture(clearcoatNormalTexture, "linear"),
1849
- clearcoatNormalSampler: createNormalSamplerForInfo(asset, clearcoatNormalTexture),
1850
- clearcoatNormalTextureTransform: clearcoatNormalTexture.transform,
1851
- clearcoatNormalScale: renderNormalMapScale(asset, clearcoatNormalTexture)
1852
- } : {}),
1853
- ...(transmissionTexture ? {
1854
- transmissionTexture: await getTexture(transmissionTexture, "linear"),
1855
- transmissionSampler: createSamplerForInfo(asset, transmissionTexture),
1856
- transmissionTextureTransform: transmissionTexture.transform
1857
- } : {}),
1858
- ...(diffuseTransmissionTexture ? {
1859
- diffuseTransmissionTexture: await getTexture(diffuseTransmissionTexture, "linear"),
1860
- diffuseTransmissionSampler: createSamplerForInfo(asset, diffuseTransmissionTexture),
1861
- diffuseTransmissionTextureTransform: diffuseTransmissionTexture.transform
1862
- } : {}),
1863
- ...(diffuseTransmissionColorTexture ? {
1864
- diffuseTransmissionColorTexture: await getTexture(diffuseTransmissionColorTexture, "srgb"),
1865
- diffuseTransmissionColorSampler: createSamplerForInfo(asset, diffuseTransmissionColorTexture),
1866
- diffuseTransmissionColorTextureTransform: diffuseTransmissionColorTexture.transform
1867
- } : {}),
1868
- ...(volumeThicknessTexture ? {
1869
- volumeThicknessTexture: await getTexture(volumeThicknessTexture, "linear"),
1870
- volumeThicknessSampler: createSamplerForInfo(asset, volumeThicknessTexture),
1871
- volumeThicknessTextureTransform: volumeThicknessTexture.transform
1872
- } : {}),
1873
- ...(specularTexture ? {
1874
- specularTexture: await getTexture(specularTexture, "linear"),
1875
- specularSampler: createSamplerForInfo(asset, specularTexture),
1876
- specularTextureTransform: specularTexture.transform
1877
- } : {}),
1878
- ...(specularColorTexture ? {
1879
- specularColorTexture: await getTexture(specularColorTexture, "srgb"),
1880
- specularColorSampler: createSamplerForInfo(asset, specularColorTexture),
1881
- specularColorTextureTransform: specularColorTexture.transform
1882
- } : {}),
1883
- ...(sheenColorTexture ? {
1884
- sheenColorTexture: await getTexture(sheenColorTexture, "srgb"),
1885
- sheenColorSampler: createSamplerForInfo(asset, sheenColorTexture),
1886
- sheenColorTextureTransform: sheenColorTexture.transform
1887
- } : {}),
1888
- ...(sheenRoughnessTexture ? {
1889
- sheenRoughnessTexture: await getTexture(sheenRoughnessTexture, "linear"),
1890
- sheenRoughnessSampler: createSamplerForInfo(asset, sheenRoughnessTexture),
1891
- sheenRoughnessTextureTransform: sheenRoughnessTexture.transform
1892
- } : {}),
1893
- ...(anisotropyTexture ? {
1894
- anisotropyTexture: await getTexture(anisotropyTexture, "linear"),
1895
- anisotropySampler: createSamplerForInfo(asset, anisotropyTexture),
1896
- anisotropyTextureTransform: anisotropyTexture.transform
1897
- } : {}),
1898
- ...(iridescenceTexture ? {
1899
- iridescenceTexture: await getTexture(iridescenceTexture, "linear"),
1900
- iridescenceSampler: createSamplerForInfo(asset, iridescenceTexture),
1901
- iridescenceTextureTransform: iridescenceTexture.transform
1902
- } : {}),
1903
- ...(iridescenceThicknessTexture ? {
1904
- iridescenceThicknessTexture: await getTexture(iridescenceThicknessTexture, "linear"),
1905
- iridescenceThicknessSampler: createSamplerForInfo(asset, iridescenceThicknessTexture),
1906
- iridescenceThicknessTextureTransform: iridescenceThicknessTexture.transform
1907
- } : {})
1908
- };
1909
- }
1910
-
1911
- function renderTransmissionFallbackEnergy(material: GLTFMaterialAsset): number {
1912
- const usesUnbackedCutoutTransmission = material.alphaMode === "MASK" && material.transmission?.texture === undefined && material.volume === undefined;
1913
- return usesUnbackedCutoutTransmission || usesUnbackedScalarTransmission(material) ? 0 : 0.08;
1914
- }
1915
-
1916
- function pbrTextureTexCoords(material: GLTFMaterialAsset): Partial<Record<TexturedPBRTextureSlot, number>> {
1917
- return {
1918
- ...(material.baseColorTexture ? { baseColor: renderTexCoord(material.baseColorTexture.texCoord) } : {}),
1919
- ...(material.normalTexture ? { normal: renderTexCoord(material.normalTexture.texCoord) } : {}),
1920
- ...(material.metallicRoughnessTexture ? { metallicRoughness: renderTexCoord(material.metallicRoughnessTexture.texCoord) } : {}),
1921
- ...(material.occlusionTexture ? { occlusion: renderTexCoord(material.occlusionTexture.texCoord) } : {}),
1922
- ...(material.emissiveTexture ? { emissive: renderTexCoord(material.emissiveTexture.texCoord) } : {}),
1923
- ...(material.clearcoat?.texture ? { clearcoat: renderTexCoord(material.clearcoat.texture.texCoord) } : {}),
1924
- ...(material.clearcoat?.roughnessTexture ? { clearcoatRoughness: renderTexCoord(material.clearcoat.roughnessTexture.texCoord) } : {}),
1925
- ...(material.clearcoat?.normalTexture ? { clearcoatNormal: renderTexCoord(material.clearcoat.normalTexture.texCoord) } : {}),
1926
- ...(material.transmission?.texture ? { transmission: renderTexCoord(material.transmission.texture.texCoord) } : {}),
1927
- ...(material.diffuseTransmission?.texture ? { diffuseTransmission: renderTexCoord(material.diffuseTransmission.texture.texCoord) } : {}),
1928
- ...(material.diffuseTransmission?.colorTexture ? { diffuseTransmissionColor: renderTexCoord(material.diffuseTransmission.colorTexture.texCoord) } : {}),
1929
- ...(material.volume?.thicknessTexture ? { volumeThickness: renderTexCoord(material.volume.thicknessTexture.texCoord) } : {}),
1930
- ...(material.specular?.texture ? { specular: renderTexCoord(material.specular.texture.texCoord) } : {}),
1931
- ...(material.specular?.colorTexture ? { specularColor: renderTexCoord(material.specular.colorTexture.texCoord) } : {}),
1932
- ...(material.sheen?.colorTexture ? { sheenColor: renderTexCoord(material.sheen.colorTexture.texCoord) } : {}),
1933
- ...(material.sheen?.roughnessTexture ? { sheenRoughness: renderTexCoord(material.sheen.roughnessTexture.texCoord) } : {}),
1934
- ...(material.anisotropy?.texture ? { anisotropy: renderTexCoord(material.anisotropy.texture.texCoord) } : {}),
1935
- ...(material.iridescence?.texture ? { iridescence: renderTexCoord(material.iridescence.texture.texCoord) } : {}),
1936
- ...(material.iridescence?.thicknessTexture ? { iridescenceThickness: renderTexCoord(material.iridescence.thicknessTexture.texCoord) } : {})
1937
- };
1938
- }
1939
-
1940
- function renderTexCoord(texCoord: number): number {
1941
- return texCoord === 1 ? 1 : 0;
1942
- }
1943
-
1944
- async function applyPBRExtensionParameters(
1945
- asset: GLTFAsset,
1946
- runtimeMaterial: Material,
1947
- material: GLTFMaterialAsset,
1948
- getTexture: (info: GLTFResolvedTextureInfo, colorSpace: GLTFTextureColorSpace) => Promise<Texture>
1949
- ): Promise<void> {
1950
- if (material.clearcoat) {
1951
- runtimeMaterial.setParameter("u_clearcoatFactor", material.clearcoat.factor);
1952
- runtimeMaterial.setParameter("u_clearcoatRoughnessFactor", renderClearcoatRoughnessFactor(material));
1953
- if (material.clearcoat.texture) {
1954
- await setTextureParameter(asset, runtimeMaterial, "u_clearcoatTexture", material.clearcoat.texture, "linear", getTexture);
1955
- }
1956
- if (material.clearcoat.roughnessTexture) {
1957
- await setTextureParameter(asset, runtimeMaterial, "u_clearcoatRoughnessTexture", material.clearcoat.roughnessTexture, "linear", getTexture);
1958
- }
1959
- if (material.clearcoat.normalTexture) {
1960
- await setTextureParameter(asset, runtimeMaterial, "u_clearcoatNormalTexture", material.clearcoat.normalTexture, "linear", getTexture);
1961
- runtimeMaterial.setParameter("u_clearcoatNormalScale", renderNormalMapScale(asset, material.clearcoat.normalTexture) ?? material.clearcoat.normalTexture.scale);
1962
- }
1963
- }
1964
- if (material.transmission) {
1965
- runtimeMaterial.setParameter("u_transmissionFactor", usesUnbackedScalarTransmission(material) ? 0 : material.transmission.factor);
1966
- if (material.transmission.texture) {
1967
- await setTextureParameter(asset, runtimeMaterial, "u_transmissionTexture", material.transmission.texture, "linear", getTexture);
1968
- }
1969
- }
1970
- if (material.diffuseTransmission) {
1971
- runtimeMaterial.setParameter("u_diffuseTransmissionFactor", material.diffuseTransmission.factor);
1972
- runtimeMaterial.setParameter("u_diffuseTransmissionColorFactor", material.diffuseTransmission.colorFactor);
1973
- if (material.diffuseTransmission.texture) {
1974
- await setTextureParameter(asset, runtimeMaterial, "u_diffuseTransmissionTexture", material.diffuseTransmission.texture, "linear", getTexture);
1975
- }
1976
- if (material.diffuseTransmission.colorTexture) {
1977
- await setTextureParameter(asset, runtimeMaterial, "u_diffuseTransmissionColorTexture", material.diffuseTransmission.colorTexture, "srgb", getTexture);
1978
- }
1979
- }
1980
- if (material.volume) {
1981
- runtimeMaterial.setParameter("u_volumeThicknessFactor", material.volume.thicknessFactor);
1982
- runtimeMaterial.setParameter("u_volumeAttenuationDistance", renderVolumeAttenuationDistance(material.volume.attenuationDistance));
1983
- runtimeMaterial.setParameter("u_volumeAttenuationColor", material.volume.attenuationColor);
1984
- if (material.volume.thicknessTexture) {
1985
- await setTextureParameter(asset, runtimeMaterial, "u_volumeThicknessTexture", material.volume.thicknessTexture, "linear", getTexture);
1986
- }
1987
- }
1988
- if (material.ior !== undefined) {
1989
- runtimeMaterial.setParameter("u_ior", material.ior);
1990
- }
1991
- if (material.specular) {
1992
- runtimeMaterial.setParameter("u_specularFactor", material.specular.factor);
1993
- runtimeMaterial.setParameter("u_specularColorFactor", renderSpecularColorFactor(material.specular.colorFactor));
1994
- if (material.specular.texture) {
1995
- await setTextureParameter(asset, runtimeMaterial, "u_specularTexture", material.specular.texture, "linear", getTexture);
1996
- }
1997
- if (material.specular.colorTexture) {
1998
- await setTextureParameter(asset, runtimeMaterial, "u_specularColorTexture", material.specular.colorTexture, "srgb", getTexture);
1999
- }
2000
- }
2001
- if (material.sheen) {
2002
- runtimeMaterial.setParameter("u_sheenColorFactor", material.sheen.colorFactor);
2003
- runtimeMaterial.setParameter("u_sheenRoughnessFactor", material.sheen.roughnessFactor);
2004
- if (material.sheen.colorTexture) {
2005
- await setTextureParameter(asset, runtimeMaterial, "u_sheenColorTexture", material.sheen.colorTexture, "srgb", getTexture);
2006
- }
2007
- if (material.sheen.roughnessTexture) {
2008
- await setTextureParameter(asset, runtimeMaterial, "u_sheenRoughnessTexture", material.sheen.roughnessTexture, "linear", getTexture);
2009
- }
2010
- }
2011
- if (material.anisotropy) {
2012
- runtimeMaterial.setParameter("u_anisotropyStrength", material.anisotropy.strength);
2013
- runtimeMaterial.setParameter("u_anisotropyRotation", material.anisotropy.rotation);
2014
- if (material.anisotropy.texture) {
2015
- await setTextureParameter(asset, runtimeMaterial, "u_anisotropyTexture", material.anisotropy.texture, "linear", getTexture);
2016
- }
2017
- }
2018
- if (material.iridescence) {
2019
- runtimeMaterial.setParameter("u_iridescenceFactor", material.iridescence.factor);
2020
- runtimeMaterial.setParameter("u_iridescenceIor", material.iridescence.ior);
2021
- runtimeMaterial.setParameter("u_iridescenceThicknessMinimum", material.iridescence.thicknessMinimum);
2022
- runtimeMaterial.setParameter("u_iridescenceThicknessMaximum", material.iridescence.thicknessMaximum);
2023
- if (material.iridescence.texture) {
2024
- await setTextureParameter(asset, runtimeMaterial, "u_iridescenceTexture", material.iridescence.texture, "linear", getTexture);
2025
- }
2026
- if (material.iridescence.thicknessTexture) {
2027
- await setTextureParameter(asset, runtimeMaterial, "u_iridescenceThicknessTexture", material.iridescence.thicknessTexture, "linear", getTexture);
2028
- }
2029
- }
2030
- if (material.dispersion !== undefined) {
2031
- runtimeMaterial.setParameter("u_dispersion", material.dispersion);
2032
- }
2033
- }
2034
-
2035
- function renderClearcoatRoughnessFactor(material: GLTFMaterialAsset): number {
2036
- return material.clearcoat?.roughnessFactor ?? 0;
2037
- }
2038
-
2039
- async function setTextureParameter(
2040
- asset: GLTFAsset,
2041
- runtimeMaterial: Material,
2042
- uniformName: string,
2043
- info: GLTFResolvedTextureInfo,
2044
- colorSpace: GLTFTextureColorSpace,
2045
- getTexture: (info: GLTFResolvedTextureInfo, colorSpace: GLTFTextureColorSpace) => Promise<Texture>,
2046
- sampler?: Sampler
2047
- ): Promise<void> {
2048
- runtimeMaterial.setParameter(uniformName, await createTextureBinding(asset, info, colorSpace, getTexture, uniformName, sampler));
2049
- runtimeMaterial.setParameter(`${uniformName}Offset`, info.transform?.offset ?? [0, 0]);
2050
- runtimeMaterial.setParameter(`${uniformName}Scale`, info.transform?.scale ?? [1, 1]);
2051
- runtimeMaterial.setParameter(`${uniformName}Rotation`, info.transform?.rotation ?? 0);
2052
- runtimeMaterial.setParameter(`${uniformName}TexCoord`, renderTexCoord(info.texCoord));
2053
- }
2054
-
2055
- async function createTextureBinding(
2056
- asset: GLTFAsset,
2057
- info: GLTFResolvedTextureInfo,
2058
- colorSpace: GLTFTextureColorSpace,
2059
- getTexture: (info: GLTFResolvedTextureInfo, colorSpace: GLTFTextureColorSpace) => Promise<Texture>,
2060
- uniformName = `gltf-texture-${info.texture}`,
2061
- sampler?: Sampler
2062
- ): Promise<TextureBinding> {
2063
- return new TextureBinding({
2064
- name: uniformName,
2065
- texture: await getTexture(info, colorSpace),
2066
- sampler: sampler ?? createSampler(asset.textures[info.texture]),
2067
- required: true,
2068
- expectedColorSpace: colorSpace,
2069
- transform: info.transform
2070
- });
2071
- }
2072
-
2073
- function requiresTexturedPBRMaterial(material: GLTFMaterialAsset): boolean {
2074
- return Boolean(
2075
- material.baseColorTexture ||
2076
- material.normalTexture ||
2077
- material.metallicRoughnessTexture ||
2078
- material.occlusionTexture ||
2079
- material.emissiveTexture ||
2080
- material.clearcoat?.texture ||
2081
- material.clearcoat?.roughnessTexture ||
2082
- material.clearcoat?.normalTexture ||
2083
- material.transmission?.texture ||
2084
- material.diffuseTransmission?.texture ||
2085
- material.diffuseTransmission?.colorTexture ||
2086
- material.volume?.thicknessTexture ||
2087
- material.specular?.texture ||
2088
- material.specular?.colorTexture ||
2089
- material.sheen?.colorTexture ||
2090
- material.sheen?.roughnessTexture ||
2091
- material.anisotropy?.texture ||
2092
- material.iridescence?.texture ||
2093
- material.iridescence?.thicknessTexture
2094
- );
2095
- }
2096
-
2097
- function renderVolumeAttenuationDistance(distance: number): number {
2098
- return distance === Number.POSITIVE_INFINITY ? 1_000_000 : distance;
2099
- }
2100
-
2101
- function renderSpecularColorFactor(value: readonly [number, number, number]): readonly [number, number, number] {
2102
- return [clampNonNegative(value[0]), clampNonNegative(value[1]), clampNonNegative(value[2])];
2103
- }
2104
-
2105
- function clampUnit(value: number): number {
2106
- return Math.max(0, Math.min(1, value));
2107
- }
2108
-
2109
- function clampNonNegative(value: number): number {
2110
- return Math.max(0, value);
2111
- }
2112
-
2113
- function textureCacheKey(imageIndex: number, colorSpace: GLTFTextureColorSpace): string {
2114
- return `${imageIndex}:${colorSpace}`;
2115
- }
2116
-
2117
- function setTextureLibraryEntry(textureLibrary: Map<string, Texture>, textureName: string, colorSpace: GLTFTextureColorSpace, texture: Texture): void {
2118
- const existing = textureLibrary.get(textureName);
2119
- if (!existing) {
2120
- textureLibrary.set(textureName, texture);
2121
- return;
2122
- }
2123
- if (existing === texture) return;
2124
- const keyedName = textureLibraryKey(textureLibrary, textureName, colorSpace);
2125
- if (textureLibrary.get(keyedName) !== texture) {
2126
- textureLibrary.set(keyedName, texture);
2127
- }
2128
- }
2129
-
2130
- function textureLibraryKey(textureLibrary: ReadonlyMap<string, Texture>, textureName: string, colorSpace: GLTFTextureColorSpace): string {
2131
- if (!textureLibrary.has(textureName)) return textureName;
2132
- return `${textureName}:${colorSpace}`;
2133
- }
2134
-
2135
- function createSampler(texture: GLTFTextureAsset | undefined): Sampler {
2136
- const descriptor = texture?.samplerDescriptor;
2137
- const samplerDescriptor: SamplerDescriptor = descriptor
2138
- ? {
2139
- minFilter: descriptor.minFilter,
2140
- magFilter: descriptor.magFilter,
2141
- addressU: descriptor.addressU,
2142
- addressV: descriptor.addressV,
2143
- maxAnisotropy: 8
2144
- }
2145
- : {
2146
- minFilter: "linear-mipmap-linear",
2147
- magFilter: "linear",
2148
- addressU: "repeat",
2149
- addressV: "repeat",
2150
- maxAnisotropy: 8
2151
- };
2152
- return new Sampler(samplerDescriptor);
2153
- }
2154
-
2155
- async function decodeImageInBrowser(
2156
- image: GLTFImageAsset,
2157
- _imageIndex: number,
2158
- asset: GLTFAsset,
2159
- options: GLTFRenderResourceOptions = {}
2160
- ): Promise<DecodedGLTFImage> {
2161
- if (isKTX2BasisImage(image)) {
2162
- const bytes = await readImageBytes(asset, image);
2163
- return transcodeKTX2BasisTexture(bytes, {
2164
- ...options.ktx2BasisTranscoderOptions,
2165
- targetFormat: options.ktx2BasisTargetFormat ?? options.ktx2BasisTranscoderOptions?.targetFormat
2166
- });
2167
- }
2168
- if (typeof createImageBitmap === "function") {
2169
- const blob = image.data
2170
- ? new Blob([image.data], { type: image.mimeType ?? "application/octet-stream" })
2171
- : await fetchImageBlob(asset, image);
2172
- const bitmap = await createImageBitmap(blob, {
2173
- colorSpaceConversion: "none",
2174
- premultiplyAlpha: "none"
2175
- });
2176
- return { width: bitmap.width, height: bitmap.height, source: bitmap, colorSpace: "srgb" };
2177
- }
2178
- const ImageCtor = globalThis.Image;
2179
- if (!ImageCtor || !image.uri) {
2180
- throw new Error("glTF image decoding requires createImageBitmap, HTMLImageElement, or a custom imageDecoder");
2181
- }
2182
- const imageElement = new ImageCtor();
2183
- const url = resolveImageUrl(asset.url, image.uri);
2184
- return new Promise((resolve, reject) => {
2185
- imageElement.onload = () => resolve({ width: imageElement.width, height: imageElement.height, source: imageElement, colorSpace: "srgb" });
2186
- imageElement.onerror = () => reject(new Error(`glTF image decode failed for ${url}`));
2187
- imageElement.src = url;
2188
- });
2189
- }
2190
-
2191
- function createSamplerForInfo(asset: GLTFAsset, info: GLTFResolvedTextureInfo): Sampler {
2192
- return createSampler(asset.textures[info.texture]);
2193
- }
2194
-
2195
- async function readImageBytes(asset: GLTFAsset, image: GLTFImageAsset): Promise<ArrayBuffer> {
2196
- if (image.data) return image.data.slice(0);
2197
- const blob = await fetchImageBlob(asset, image);
2198
- return blob.arrayBuffer();
2199
- }
2200
-
2201
- async function fetchImageBlob(asset: GLTFAsset, image: GLTFImageAsset): Promise<Blob> {
2202
- if (!image.uri) {
2203
- throw new Error("glTF image has no uri or embedded data");
2204
- }
2205
- if (typeof fetch !== "function") {
2206
- throw new Error("glTF image fetch requires fetch or a custom imageDecoder");
2207
- }
2208
- const response = await fetch(resolveImageUrl(asset.url, image.uri));
2209
- if (!response.ok) {
2210
- throw new Error(`glTF image request failed with ${response.status}`);
2211
- }
2212
- return response.blob();
2213
- }
2214
-
2215
- function isKTX2BasisImage(image: GLTFImageAsset): boolean {
2216
- return image.mimeType === "image/ktx2" || /\.ktx2(?:[?#]|$)/i.test(image.uri ?? "");
2217
- }
2218
-
2219
- function createFallbackTexture(label: string, colorSpace: GLTFTextureColorSpace): Texture {
2220
- return new Texture({
2221
- width: 1,
2222
- height: 1,
2223
- colorSpace,
2224
- label: `${label} (missing-texture-fallback)`,
2225
- data: new Uint8Array([255, 255, 255, 255])
2226
- });
2227
- }
2228
-
2229
- function warnMissingGLTFTexture(textureName: string, imageUri: string | undefined, reason: string): void {
2230
- if (typeof console === "undefined" || typeof console.warn !== "function") return;
2231
- const source = imageUri ? ` (${imageUri})` : "";
2232
- console.warn(
2233
- `glTF texture "${textureName}"${source} could not be loaded; falling back to baseColorFactor. Reason: ${reason}`
2234
- );
2235
- }
2236
-
2237
- function resolveImageUrl(assetUrl: string, imageUri: string): string {
2238
- if (/^(?:data:|blob:|https?:|file:)/i.test(imageUri)) return imageUri;
2239
- if (assetUrl.startsWith("data:")) {
2240
- throw new Error(`Relative glTF image uri ${imageUri} cannot be resolved from a data URL asset`);
2241
- }
2242
- return new URL(imageUri, resolveAbsoluteAssetBase(assetUrl)).toString();
2243
- }
2244
-
2245
- /**
2246
- * Derive an absolute base URL for resolving relative texture URIs.
2247
- *
2248
- * Catalog assets are often loaded from root-relative paths (e.g.
2249
- * `/aura-assets/x.glb`). `new URL(uri, base)` requires `base` to be absolute, so
2250
- * a bare relative `assetUrl` throws `Failed to construct 'URL': Invalid base URL`.
2251
- * If the asset URL is already absolute we use it directly; otherwise we resolve it
2252
- * against the document/origin base when running in a browser, falling back to a
2253
- * synthetic `file:///` origin so resolution never throws in non-browser contexts.
2254
- */
2255
- function resolveAbsoluteAssetBase(assetUrl: string): string {
2256
- if (/^[a-z][a-z0-9+.-]*:/i.test(assetUrl)) return assetUrl;
2257
- const documentBase =
2258
- (typeof document !== "undefined" && document.baseURI) ||
2259
- (typeof location !== "undefined" && location.href) ||
2260
- undefined;
2261
- if (documentBase) {
2262
- try {
2263
- return new URL(assetUrl, documentBase).toString();
2264
- } catch {
2265
- // fall through to synthetic base below
2266
- }
2267
- }
2268
- return new URL(assetUrl, "file:///").toString();
2269
- }