@babylonjs/lite 1.4.0 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (152) hide show
  1. package/dist/index.js +512 -512
  2. package/dist/index.js.map +1 -1
  3. package/index.d.ts +829 -28
  4. package/lib/audio/analyzer.js +65 -0
  5. package/lib/audio/analyzer.js.map +1 -0
  6. package/lib/audio/audio-bus.js +38 -0
  7. package/lib/audio/audio-bus.js.map +1 -0
  8. package/lib/audio/audio-engine.js +188 -0
  9. package/lib/audio/audio-engine.js.map +1 -0
  10. package/lib/audio/audio-fetch.js +18 -0
  11. package/lib/audio/audio-fetch.js.map +1 -0
  12. package/lib/audio/audio-param.js +96 -0
  13. package/lib/audio/audio-param.js.map +1 -0
  14. package/lib/audio/audio-signal.js +46 -0
  15. package/lib/audio/audio-signal.js.map +1 -0
  16. package/lib/audio/bus.js +33 -0
  17. package/lib/audio/bus.js.map +1 -0
  18. package/lib/audio/host-types.js +2 -0
  19. package/lib/audio/host-types.js.map +1 -0
  20. package/lib/audio/index.js +12 -0
  21. package/lib/audio/index.js.map +1 -0
  22. package/lib/audio/sound-buffer.js +59 -0
  23. package/lib/audio/sound-buffer.js.map +1 -0
  24. package/lib/audio/sound-source.js +57 -0
  25. package/lib/audio/sound-source.js.map +1 -0
  26. package/lib/audio/sound-sub-graph.js +72 -0
  27. package/lib/audio/sound-sub-graph.js.map +1 -0
  28. package/lib/audio/spatial.js +466 -0
  29. package/lib/audio/spatial.js.map +1 -0
  30. package/lib/audio/static-sound.js +313 -0
  31. package/lib/audio/static-sound.js.map +1 -0
  32. package/lib/audio/stereo.js +40 -0
  33. package/lib/audio/stereo.js.map +1 -0
  34. package/lib/audio/streaming-sound.js +377 -0
  35. package/lib/audio/streaming-sound.js.map +1 -0
  36. package/lib/audio/unmute-ui.js +72 -0
  37. package/lib/audio/unmute-ui.js.map +1 -0
  38. package/lib/audio/visualizer.js +101 -0
  39. package/lib/audio/visualizer.js.map +1 -0
  40. package/lib/camera/geospatial-camera-controls.js +22 -0
  41. package/lib/camera/geospatial-camera-controls.js.map +1 -1
  42. package/lib/camera/geospatial-camera-fly.js +2 -1
  43. package/lib/camera/geospatial-camera-fly.js.map +1 -1
  44. package/lib/effect/effect-renderer.js +1 -1
  45. package/lib/effect/effect-renderer.js.map +1 -1
  46. package/lib/engine/engine.js +1 -1
  47. package/lib/index.js +15 -1
  48. package/lib/index.js.map +1 -1
  49. package/lib/light/types.js.map +1 -1
  50. package/lib/loader-gltf/animation-pointer-basecolor.js +25 -0
  51. package/lib/loader-gltf/animation-pointer-basecolor.js.map +1 -0
  52. package/lib/loader-gltf/animation-pointer-ext.js +244 -0
  53. package/lib/loader-gltf/animation-pointer-ext.js.map +1 -0
  54. package/lib/loader-gltf/animation-pointer-lights.js +46 -0
  55. package/lib/loader-gltf/animation-pointer-lights.js.map +1 -0
  56. package/lib/loader-gltf/animation-pointer.js +4 -1
  57. package/lib/loader-gltf/animation-pointer.js.map +1 -1
  58. package/lib/loader-gltf/gltf-animation.js +5 -3
  59. package/lib/loader-gltf/gltf-animation.js.map +1 -1
  60. package/lib/loader-gltf/gltf-color-normalize.js +10 -1
  61. package/lib/loader-gltf/gltf-color-normalize.js.map +1 -1
  62. package/lib/loader-gltf/gltf-feature-animation-pointer.js +67 -47
  63. package/lib/loader-gltf/gltf-feature-animation-pointer.js.map +1 -1
  64. package/lib/loader-gltf/gltf-feature-lights-punctual.js +51 -9
  65. package/lib/loader-gltf/gltf-feature-lights-punctual.js.map +1 -1
  66. package/lib/loader-gltf/gltf-feature-primitive.js +20 -0
  67. package/lib/loader-gltf/gltf-feature-primitive.js.map +1 -0
  68. package/lib/loader-gltf/gltf-feature-registry.js +25 -0
  69. package/lib/loader-gltf/gltf-feature-registry.js.map +1 -1
  70. package/lib/loader-gltf/gltf-feature-skeleton.js +18 -3
  71. package/lib/loader-gltf/gltf-feature-skeleton.js.map +1 -1
  72. package/lib/loader-gltf/gltf-interleave.js +3 -2
  73. package/lib/loader-gltf/gltf-interleave.js.map +1 -1
  74. package/lib/loader-gltf/gltf-light-pointer-state.js +18 -0
  75. package/lib/loader-gltf/gltf-light-pointer-state.js.map +1 -0
  76. package/lib/loader-gltf/gltf-parser.js +7 -1
  77. package/lib/loader-gltf/gltf-parser.js.map +1 -1
  78. package/lib/loader-gltf/gltf-pbr-builder-ext.js +1 -1
  79. package/lib/loader-gltf/gltf-pbr-builder-ext.js.map +1 -1
  80. package/lib/loader-gltf/gltf-pbr-builder.js +1 -1
  81. package/lib/loader-gltf/gltf-pbr-builder.js.map +1 -1
  82. package/lib/loader-gltf/gltf-sampler-denorm.js +20 -0
  83. package/lib/loader-gltf/gltf-sampler-denorm.js.map +1 -0
  84. package/lib/loader-gltf/gltf-sampler-desc.js +11 -2
  85. package/lib/loader-gltf/gltf-sampler-desc.js.map +1 -1
  86. package/lib/loader-gltf/gltf-uv-denorm.js +28 -0
  87. package/lib/loader-gltf/gltf-uv-denorm.js.map +1 -0
  88. package/lib/loader-gltf/load-gltf.js +15 -6
  89. package/lib/loader-gltf/load-gltf.js.map +1 -1
  90. package/lib/material/material-rebuild.js +4 -0
  91. package/lib/material/material-rebuild.js.map +1 -1
  92. package/lib/material/mesh-features.js +8 -1
  93. package/lib/material/mesh-features.js.map +1 -1
  94. package/lib/material/pbr/fragments/reflectance-fragment.js +1 -1
  95. package/lib/material/pbr/fragments/reflectance-fragment.js.map +1 -1
  96. package/lib/material/pbr/fragments/refraction-rtt-fragment.js +1 -1
  97. package/lib/material/pbr/fragments/refraction-rtt-fragment.js.map +1 -1
  98. package/lib/material/pbr/pbr-pipeline.js +7 -3
  99. package/lib/material/pbr/pbr-pipeline.js.map +1 -1
  100. package/lib/material/pbr/pbr-primitive-resolver.js +34 -0
  101. package/lib/material/pbr/pbr-primitive-resolver.js.map +1 -0
  102. package/lib/material/pbr/pbr-renderable.js +1 -1
  103. package/lib/material/pbr/pbr-renderable.js.map +1 -1
  104. package/lib/material/shader/shader-material.js +9 -5
  105. package/lib/material/shader/shader-material.js.map +1 -1
  106. package/lib/material/shader/shader-thin-instance.js +1 -1
  107. package/lib/material/shader/shader-thin-instance.js.map +1 -1
  108. package/lib/material/standard/standard-renderable.js +1 -1
  109. package/lib/material/standard/standard-renderable.js.map +1 -1
  110. package/lib/mesh/mesh-dispose.js +1 -0
  111. package/lib/mesh/mesh-dispose.js.map +1 -1
  112. package/lib/mesh/thin-instance-cull-binding.js +15 -6
  113. package/lib/mesh/thin-instance-cull-binding.js.map +1 -1
  114. package/lib/post-process/taa.js +193 -0
  115. package/lib/post-process/taa.js.map +1 -0
  116. package/lib/scene/scene-core.js +1 -0
  117. package/lib/scene/scene-core.js.map +1 -1
  118. package/lib/scene/scene-material-swap.js +2 -0
  119. package/lib/scene/scene-material-swap.js.map +1 -1
  120. package/lib/shadow/csm-shadow-task-hooks.js +67 -9
  121. package/lib/shadow/csm-shadow-task-hooks.js.map +1 -1
  122. package/lib/sprite/billboard-custom-shader.js +32 -32
  123. package/lib/sprite/billboard-custom-shader.js.map +1 -1
  124. package/lib/sprite/billboard-pipeline.js +54 -56
  125. package/lib/sprite/billboard-pipeline.js.map +1 -1
  126. package/lib/sprite/custom-shader-core.js +1 -1
  127. package/lib/sprite/custom-shader-core.js.map +1 -1
  128. package/lib/sprite/shared/sprite-atlas.js +2 -2
  129. package/lib/sprite/shared/sprite-atlas.js.map +1 -1
  130. package/lib/sprite/sprite-2d-coverage-gamma.js +58 -0
  131. package/lib/sprite/sprite-2d-coverage-gamma.js.map +1 -0
  132. package/lib/sprite/sprite-2d-uvscroll.js +39 -0
  133. package/lib/sprite/sprite-2d-uvscroll.js.map +1 -0
  134. package/lib/sprite/sprite-2d.js +6 -36
  135. package/lib/sprite/sprite-2d.js.map +1 -1
  136. package/lib/sprite/sprite-coverage-gamma-hook.js +10 -0
  137. package/lib/sprite/sprite-coverage-gamma-hook.js.map +1 -0
  138. package/lib/sprite/sprite-custom-shader.js +2 -2
  139. package/lib/sprite/sprite-custom-shader.js.map +1 -1
  140. package/lib/sprite/sprite-pipeline.js +61 -73
  141. package/lib/sprite/sprite-pipeline.js.map +1 -1
  142. package/lib/sprite/sprite-renderable.js +5 -5
  143. package/lib/sprite/sprite-renderable.js.map +1 -1
  144. package/lib/sprite/sprite-renderer.js +4 -4
  145. package/lib/sprite/sprite-renderer.js.map +1 -1
  146. package/lib/sprite/sprite-scene.js +1 -1
  147. package/lib/sprite/sprite-scene.js.map +1 -1
  148. package/lib/text/_gpu/text-pipeline.js +1 -1
  149. package/lib/text/_gpu/text-pipeline.js.map +1 -1
  150. package/lib/text/text-renderer.js +3 -1
  151. package/lib/text/text-renderer.js.map +1 -1
  152. package/package.json +3 -3
@@ -1 +1 @@
1
- {"version":3,"file":"gltf-parser.js","sources":["../../../src/loader-gltf/gltf-parser.ts"],"sourcesContent":["/**\n * Low-level glTF/GLB parsing helpers:\n * - Accessor resolution (buffer views → typed arrays)\n * - Image extraction (embedded or external)\n * - Node hierarchy traversal with memoized world-matrix computation\n */\nimport { F32, U32, U16, U8 } from \"../engine/typed-arrays.js\";\nimport type { Mat4 } from \"../math/types.js\";\nimport { mat4ComposeInto } from \"../math/mat4-compose-into.js\";\nimport { mat4MultiplyInto } from \"../math/mat4-multiply-into.js\";\nimport type { Mat4Storage } from \"../math/types.js\";\nimport { getLoaderTmpLocal } from \"./_loader-scratch.js\";\n\n// glTF 2.0 component types\nconst FLOAT = 5126;\nconst UNSIGNED_SHORT = 5123;\nconst UNSIGNED_INT = 5125;\nconst UNSIGNED_BYTE = 5121;\n\n// glTF accessor type → component count\nexport const TYPE_SIZES: Record<string, number> = {\n SCALAR: 1,\n VEC2: 2,\n VEC3: 3,\n VEC4: 4,\n MAT2: 4,\n MAT3: 9,\n MAT4: 16,\n};\n\n// --- Accessor Resolution ---\n\nexport interface AccessorView {\n /** @internal */\n _data: ArrayBufferView;\n /** @internal */\n _count: number;\n /** @internal */\n _componentCount: number;\n}\n\nexport function resolveAccessor(json: any, binChunk: DataView, accessorIdx: number): AccessorView {\n const accessor = json.accessors[accessorIdx];\n const componentCount = TYPE_SIZES[accessor.type] ?? 1;\n const count = accessor.count;\n const len = count * componentCount;\n\n let Ctor: Float32ArrayConstructor | Uint16ArrayConstructor | Uint32ArrayConstructor | Uint8ArrayConstructor;\n switch (accessor.componentType) {\n case FLOAT:\n Ctor = F32;\n break;\n case UNSIGNED_SHORT:\n Ctor = U16;\n break;\n case UNSIGNED_INT:\n Ctor = U32;\n break;\n case UNSIGNED_BYTE:\n Ctor = U8;\n break;\n default:\n throw new Error(`Unsupported component type: ${accessor.componentType}`);\n }\n\n // Spec: an accessor with no `bufferView` is zero-initialized (its values may be supplied by a `sparse`\n // substitution or an extension) — return a zero-filled array instead of dereferencing a missing\n // bufferView. Some skinned rigs ship all-zero morph-target POSITION/NORMAL accessors this way, which\n // otherwise crashed the morph feature with `undefined.byteOffset`.\n const data =\n accessor.bufferView === undefined\n ? new Ctor(len)\n : new Ctor(binChunk.buffer as ArrayBuffer, binChunk.byteOffset + (json.bufferViews[accessor.bufferView].byteOffset ?? 0) + (accessor.byteOffset ?? 0), len);\n\n return { _data: data, _count: count, _componentCount: componentCount };\n}\n\n// --- Image Extraction ---\n\n/** Resolve the image index for a glTF texture, honoring alternate-source\n * extensions such as EXT_texture_webp (WebP decode is native in\n * createImageBitmap, so no extra module is required — we only need to\n * pick the correct image source). */\nexport function getTextureImageIndex(tex: any): number {\n return tex.extensions?.EXT_texture_webp?.source ?? tex.source;\n}\n\n// --- Optional-feature detection (shared by the core loader gate and the\n// dynamically-imported feature registry) ---\n\n/** Returns true if any mesh primitive in the asset matches `pred`. */\nexport function anyPrimitive(json: any, pred: (p: any) => boolean): boolean {\n for (const m of json.meshes ?? []) {\n for (const p of m.primitives ?? []) {\n if (pred(p)) {\n return true;\n }\n }\n }\n return false;\n}\n\n/** Asset has at least one material that needs ORM compositing\n * (separate metallicRoughnessTexture + occlusionTexture pointing at different images). */\nexport function needsOrmComposite(json: any): boolean {\n const mats = json.materials ?? [];\n const textures = json.textures ?? [];\n for (const m of mats) {\n const mr = m.pbrMetallicRoughness?.metallicRoughnessTexture;\n const occ = m.occlusionTexture;\n if (mr && occ && textures[mr.index] && textures[occ.index] && getTextureImageIndex(textures[mr.index]) !== getTextureImageIndex(textures[occ.index])) {\n return true;\n }\n }\n return false;\n}\n\nexport async function resolveImage(json: any, binChunk: DataView, imageIdx: number, baseUrl: string): Promise<ImageBitmap> {\n const image = json.images[imageIdx];\n\n if (image.bufferView !== undefined) {\n // Embedded in binary chunk (GLB)\n const bv = json.bufferViews[image.bufferView];\n const offset = binChunk.byteOffset + (bv.byteOffset ?? 0);\n const slice = binChunk.buffer.slice(offset, offset + bv.byteLength);\n const blob = new Blob([slice as ArrayBuffer], { type: image.mimeType ?? \"image/png\" });\n return createImageBitmap(blob, { premultiplyAlpha: \"none\", colorSpaceConversion: \"none\" });\n }\n\n if (image.uri) {\n // External URI (relative to .gltf base URL)\n const imageUrl = new URL(image.uri, baseUrl + \"x\").href;\n const response = await fetch(imageUrl);\n if (!response.ok) {\n throw new Error(`Failed to load image: ${response.status} ${response.statusText}`);\n }\n const blob = await response.blob();\n const bmp = await createImageBitmap(blob, { premultiplyAlpha: \"none\", colorSpaceConversion: \"none\" });\n return bmp;\n }\n\n throw new Error(\"Image has neither bufferView nor uri\");\n}\n\n// --- Node Hierarchy → World Matrix (Memoized) ---\n\n// Babylon.js RH→LH root: rotation [0,1,0,0] + scale [1,1,-1] = diag(-1,1,1,1)\n// prettier-ignore\nconst RH_TO_LH_ROOT = new F32([-1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]) as unknown as Mat4;\n\n/** Build a parent index map by scanning node.children arrays once. O(n). */\nexport function buildParentMap(json: any): Map<number, number> {\n const parentMap = new Map<number, number>();\n const nodes = json.nodes ?? [];\n for (let i = 0; i < nodes.length; i++) {\n const children = nodes[i].children;\n if (children) {\n for (const childIdx of children) {\n parentMap.set(childIdx as number, i);\n }\n }\n }\n return parentMap;\n}\n\n/** Look up a node's parent using the pre-built parent map. O(1). */\nexport function findParent(parentMap: Map<number, number>, childIdx: number): number {\n return parentMap.get(childIdx) ?? -1;\n}\n\n/**\n * Compute world matrix for a glTF node with memoization.\n * Uses parentMap for O(1) parent lookup and cache for O(1) repeat queries.\n * Total cost across all nodes: O(n) instead of O(n²).\n *\n * Zero-alloc path for the TRS case (common): `local` is composed into a shared\n * scratch via `mat4ComposeInto`, then multiplied into the freshly-allocated\n * `world` via `mat4MultiplyInto`. Only one Float32Array allocation per node\n * (the cached world matrix itself) instead of two. Recursion always resolves\n * `parentWorld` before touching the scratch, so the shared buffer is safe.\n */\nexport function computeNodeWorldMatrix(json: any, nodeIdx: number, parentMap: Map<number, number>, cache: Map<number, Mat4>): Mat4 {\n const cached = cache.get(nodeIdx);\n if (cached) {\n return cached;\n }\n\n const node = json.nodes[nodeIdx];\n const parentIdx = findParent(parentMap, nodeIdx);\n // Resolve parent FIRST so any recursive call can safely reuse the shared scratch below.\n const parentWorld: Mat4 = parentIdx !== -1 ? computeNodeWorldMatrix(json, parentIdx, parentMap, cache) : RH_TO_LH_ROOT;\n\n let localBuf: import(\"../math/types.js\").Mat4Storage;\n if (node.matrix) {\n // Pre-built matrix — copy into a fresh Float32Array (cannot alias scratch safely across calls).\n localBuf = new F32(node.matrix);\n } else {\n const t = node.translation ?? [0, 0, 0];\n const r = node.rotation ?? [0, 0, 0, 1];\n const s = node.scale ?? [1, 1, 1];\n const local = getLoaderTmpLocal() as unknown as Mat4Storage;\n mat4ComposeInto(local, 0, t[0], t[1], t[2], r[0], r[1], r[2], r[3], s[0], s[1], s[2]);\n localBuf = local;\n }\n\n // Per-node world is allocated fresh because recursion mutates the parser's\n // scratch; it cannot alias the per-call `tmpLocal`. The result is then\n // stashed in the per-load `cache` (Map<nodeIdx, Mat4>) and never shared\n // across loadGltf calls. Allocates F32 directly here — these loader-local\n // world matrices are throwaway intermediaries used only during parsing;\n // mesh runtime world-matrix caches are separately allocated via\n // `allocateMat4()` in `initMeshTransform` and pick up whatever precision\n // the process-global allocator was set to.\n const world = new F32(16) as unknown as Mat4;\n mat4MultiplyInto(world as unknown as Mat4Storage, 0, parentWorld as unknown as Mat4Storage, 0, localBuf, 0);\n\n cache.set(nodeIdx, world);\n return world;\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"gltf-parser.js","sources":["../../../src/loader-gltf/gltf-parser.ts"],"sourcesContent":["/**\n * Low-level glTF/GLB parsing helpers:\n * - Accessor resolution (buffer views → typed arrays)\n * - Image extraction (embedded or external)\n * - Node hierarchy traversal with memoized world-matrix computation\n */\nimport { F32, U32, U16, U8, I16, I8 } from \"../engine/typed-arrays.js\";\nimport type { Mat4 } from \"../math/types.js\";\nimport { mat4ComposeInto } from \"../math/mat4-compose-into.js\";\nimport { mat4MultiplyInto } from \"../math/mat4-multiply-into.js\";\nimport type { Mat4Storage } from \"../math/types.js\";\nimport { getLoaderTmpLocal } from \"./_loader-scratch.js\";\n\n// glTF 2.0 component types\nconst FLOAT = 5126;\nconst UNSIGNED_SHORT = 5123;\nconst UNSIGNED_INT = 5125;\nconst UNSIGNED_BYTE = 5121;\n\n// glTF accessor type → component count\nexport const TYPE_SIZES: Record<string, number> = {\n SCALAR: 1,\n VEC2: 2,\n VEC3: 3,\n VEC4: 4,\n MAT2: 4,\n MAT3: 9,\n MAT4: 16,\n};\n\n// --- Accessor Resolution ---\n\nexport interface AccessorView {\n /** @internal */\n _data: ArrayBufferView;\n /** @internal */\n _count: number;\n /** @internal */\n _componentCount: number;\n}\n\nexport function resolveAccessor(json: any, binChunk: DataView, accessorIdx: number): AccessorView {\n const accessor = json.accessors[accessorIdx];\n const componentCount = TYPE_SIZES[accessor.type] ?? 1;\n const count = accessor.count;\n const len = count * componentCount;\n\n let Ctor: Float32ArrayConstructor | Uint16ArrayConstructor | Uint32ArrayConstructor | Uint8ArrayConstructor | Int16ArrayConstructor | Int8ArrayConstructor;\n switch (accessor.componentType) {\n case FLOAT:\n Ctor = F32;\n break;\n case UNSIGNED_SHORT:\n Ctor = U16;\n break;\n case UNSIGNED_INT:\n Ctor = U32;\n break;\n case UNSIGNED_BYTE:\n Ctor = U8;\n break;\n case 5122: // SHORT\n Ctor = I16;\n break;\n case 5120: // BYTE\n Ctor = I8;\n break;\n default:\n throw new Error(`Unsupported component type: ${accessor.componentType}`);\n }\n\n // Spec: an accessor with no `bufferView` is zero-initialized (its values may be supplied by a `sparse`\n // substitution or an extension) — return a zero-filled array instead of dereferencing a missing\n // bufferView. Some skinned rigs ship all-zero morph-target POSITION/NORMAL accessors this way, which\n // otherwise crashed the morph feature with `undefined.byteOffset`.\n const data =\n accessor.bufferView === undefined\n ? new Ctor(len)\n : new Ctor(binChunk.buffer as ArrayBuffer, binChunk.byteOffset + (json.bufferViews[accessor.bufferView].byteOffset ?? 0) + (accessor.byteOffset ?? 0), len);\n\n return { _data: data, _count: count, _componentCount: componentCount };\n}\n\n// --- Image Extraction ---\n\n/** Resolve the image index for a glTF texture, honoring alternate-source\n * extensions such as EXT_texture_webp (WebP decode is native in\n * createImageBitmap, so no extra module is required — we only need to\n * pick the correct image source). */\nexport function getTextureImageIndex(tex: any): number {\n return tex.extensions?.EXT_texture_webp?.source ?? tex.source;\n}\n\n// --- Optional-feature detection (shared by the core loader gate and the\n// dynamically-imported feature registry) ---\n\n/** Returns true if any mesh primitive in the asset matches `pred`. */\nexport function anyPrimitive(json: any, pred: (p: any) => boolean): boolean {\n for (const m of json.meshes ?? []) {\n for (const p of m.primitives ?? []) {\n if (pred(p)) {\n return true;\n }\n }\n }\n return false;\n}\n\n/** Asset has at least one material that needs ORM compositing\n * (separate metallicRoughnessTexture + occlusionTexture pointing at different images). */\nexport function needsOrmComposite(json: any): boolean {\n const mats = json.materials ?? [];\n const textures = json.textures ?? [];\n for (const m of mats) {\n const mr = m.pbrMetallicRoughness?.metallicRoughnessTexture;\n const occ = m.occlusionTexture;\n if (mr && occ && textures[mr.index] && textures[occ.index] && getTextureImageIndex(textures[mr.index]) !== getTextureImageIndex(textures[occ.index])) {\n return true;\n }\n }\n return false;\n}\n\nexport async function resolveImage(json: any, binChunk: DataView, imageIdx: number, baseUrl: string): Promise<ImageBitmap> {\n const image = json.images[imageIdx];\n\n if (image.bufferView !== undefined) {\n // Embedded in binary chunk (GLB)\n const bv = json.bufferViews[image.bufferView];\n const offset = binChunk.byteOffset + (bv.byteOffset ?? 0);\n const slice = binChunk.buffer.slice(offset, offset + bv.byteLength);\n const blob = new Blob([slice as ArrayBuffer], { type: image.mimeType ?? \"image/png\" });\n return createImageBitmap(blob, { premultiplyAlpha: \"none\", colorSpaceConversion: \"none\" });\n }\n\n if (image.uri) {\n // External URI (relative to .gltf base URL)\n const imageUrl = new URL(image.uri, baseUrl + \"x\").href;\n const response = await fetch(imageUrl);\n if (!response.ok) {\n throw new Error(`Failed to load image: ${response.status} ${response.statusText}`);\n }\n const blob = await response.blob();\n const bmp = await createImageBitmap(blob, { premultiplyAlpha: \"none\", colorSpaceConversion: \"none\" });\n return bmp;\n }\n\n throw new Error(\"Image has neither bufferView nor uri\");\n}\n\n// --- Node Hierarchy → World Matrix (Memoized) ---\n\n// Babylon.js RH→LH root: rotation [0,1,0,0] + scale [1,1,-1] = diag(-1,1,1,1)\n// prettier-ignore\nconst RH_TO_LH_ROOT = new F32([-1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]) as unknown as Mat4;\n\n/** Build a parent index map by scanning node.children arrays once. O(n). */\nexport function buildParentMap(json: any): Map<number, number> {\n const parentMap = new Map<number, number>();\n const nodes = json.nodes ?? [];\n for (let i = 0; i < nodes.length; i++) {\n const children = nodes[i].children;\n if (children) {\n for (const childIdx of children) {\n parentMap.set(childIdx as number, i);\n }\n }\n }\n return parentMap;\n}\n\n/** Look up a node's parent using the pre-built parent map. O(1). */\nexport function findParent(parentMap: Map<number, number>, childIdx: number): number {\n return parentMap.get(childIdx) ?? -1;\n}\n\n/**\n * Compute world matrix for a glTF node with memoization.\n * Uses parentMap for O(1) parent lookup and cache for O(1) repeat queries.\n * Total cost across all nodes: O(n) instead of O(n²).\n *\n * Zero-alloc path for the TRS case (common): `local` is composed into a shared\n * scratch via `mat4ComposeInto`, then multiplied into the freshly-allocated\n * `world` via `mat4MultiplyInto`. Only one Float32Array allocation per node\n * (the cached world matrix itself) instead of two. Recursion always resolves\n * `parentWorld` before touching the scratch, so the shared buffer is safe.\n */\nexport function computeNodeWorldMatrix(json: any, nodeIdx: number, parentMap: Map<number, number>, cache: Map<number, Mat4>): Mat4 {\n const cached = cache.get(nodeIdx);\n if (cached) {\n return cached;\n }\n\n const node = json.nodes[nodeIdx];\n const parentIdx = findParent(parentMap, nodeIdx);\n // Resolve parent FIRST so any recursive call can safely reuse the shared scratch below.\n const parentWorld: Mat4 = parentIdx !== -1 ? computeNodeWorldMatrix(json, parentIdx, parentMap, cache) : RH_TO_LH_ROOT;\n\n let localBuf: import(\"../math/types.js\").Mat4Storage;\n if (node.matrix) {\n // Pre-built matrix — copy into a fresh Float32Array (cannot alias scratch safely across calls).\n localBuf = new F32(node.matrix);\n } else {\n const t = node.translation ?? [0, 0, 0];\n const r = node.rotation ?? [0, 0, 0, 1];\n const s = node.scale ?? [1, 1, 1];\n const local = getLoaderTmpLocal() as unknown as Mat4Storage;\n mat4ComposeInto(local, 0, t[0], t[1], t[2], r[0], r[1], r[2], r[3], s[0], s[1], s[2]);\n localBuf = local;\n }\n\n // Per-node world is allocated fresh because recursion mutates the parser's\n // scratch; it cannot alias the per-call `tmpLocal`. The result is then\n // stashed in the per-load `cache` (Map<nodeIdx, Mat4>) and never shared\n // across loadGltf calls. Allocates F32 directly here — these loader-local\n // world matrices are throwaway intermediaries used only during parsing;\n // mesh runtime world-matrix caches are separately allocated via\n // `allocateMat4()` in `initMeshTransform` and pick up whatever precision\n // the process-global allocator was set to.\n const world = new F32(16) as unknown as Mat4;\n mat4MultiplyInto(world as unknown as Mat4Storage, 0, parentWorld as unknown as Mat4Storage, 0, localBuf, 0);\n\n cache.set(nodeIdx, world);\n return world;\n}\n"],"names":[],"mappings":"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"}
@@ -88,7 +88,7 @@ function buildDefaultPbrTexturesExt(engine, mat, sampler, generateMipmaps, getCa
88
88
  }
89
89
  function assemblePbrPropsExt(mat, tex, extLayers) {
90
90
  const ef = mat._emissiveFactor;
91
- const defaultFactor = ef[0] === 1 && ef[1] === 1 && ef[2] === 1 || ef[0] === 0 && ef[1] === 0 && ef[2] === 0;
91
+ const defaultFactor = ef[0] === 0 && ef[1] === 0 && ef[2] === 0 || !!tex.emissiveTexture && ef[0] === 1 && ef[1] === 1 && ef[2] === 1;
92
92
  const hasAnyUvTx = !!tex.baseColorTexture._hasTx || !!tex.normalTexture?._hasTx || !!tex.ormTexture._hasTx || !!tex.emissiveTexture?._hasTx || !!tex.occlusionTexture?._hasTx;
93
93
  return {
94
94
  baseColorTexture: tex.baseColorTexture,
@@ -1 +1 @@
1
- {"version":3,"file":"gltf-pbr-builder-ext.js","sources":["../../../src/loader-gltf/gltf-pbr-builder-ext.ts"],"sourcesContent":["/** Lazy-loaded slow path for PBR material assembly.\n * Only pulled into bundles whose glTF uses features that require per-texture\n * wrapping (e.g. KHR_texture_transform) or occlusion on UV2 (texCoord=1 with\n * no shared MR image). Scene1 (BoomBox) and any vanilla-PBR glTF skip this\n * module entirely. */\n\nimport { U8 } from \"../engine/typed-arrays.js\";\nimport type { EngineContext } from \"../engine/engine.js\";\nimport type { Texture2D } from \"../texture/texture-2d.js\";\nimport { cloneTexture2D } from \"../texture/texture-2d.js\";\nimport type { PbrMaterialProps } from \"../material/pbr/pbr-material.js\";\nimport { getPbrGroupBuilder } from \"../material/pbr/pbr-material.js\";\nimport type { GltfMaterialData } from \"./gltf-material.js\";\nimport { linearToSrgbByte } from \"../math/color.js\";\nimport type { TextureWrapFn, GenerateMipmapsFn } from \"./gltf-pbr-builder.js\";\nimport { uploadTex } from \"./gltf-pbr-builder.js\";\n\nexport interface PbrTexturesExt {\n baseColorTexture: Texture2D;\n ormTexture: Texture2D;\n normalTexture: Texture2D | undefined;\n emissiveTexture: Texture2D | undefined;\n occlusionTexture: Texture2D | undefined;\n}\n\n/** Stamp `_texCoord=1` on a clone when textureInfo selects UV1 and the\n * wrapTex layer didn't already set it (i.e. scene has no KHR_texture_transform). */\nfunction wrapTexCoord(tex: Texture2D, texInfo: unknown): Texture2D {\n if (!texInfo) {\n return tex;\n }\n if ((tex as { _texCoord?: 0 | 1 })._texCoord === 1) {\n return tex;\n }\n const ti = texInfo as { texCoord?: number; extensions?: { KHR_texture_transform?: { texCoord?: number } } };\n const tc = ti.extensions?.KHR_texture_transform?.texCoord ?? ti.texCoord;\n return tc === 1 ? cloneTexture2D(tex, { _texCoord: 1 }) : tex;\n}\n\n/** True when occlusion shares the ORM image with metallic-roughness but must be sampled\n * with its OWN UV transform — i.e. occlusion references a distinct glTF texture object,\n * or it carries its own KHR_texture_transform that an animation pointer can drive apart\n * from the MR transform. Drives the orm-unpack split in buildDefaultPbrTexturesExt. */\nfunction occlusionNeedsSplit(raw: {\n occlusionTexture?: { index?: number; extensions?: { KHR_texture_transform?: unknown } };\n pbrMetallicRoughness?: { metallicRoughnessTexture?: { index?: number } };\n}): boolean {\n const occ = raw.occlusionTexture;\n const mr = raw.pbrMetallicRoughness?.metallicRoughnessTexture;\n if (!occ || !mr) {\n return false;\n }\n return occ.index !== mr.index || occ.extensions?.KHR_texture_transform != null;\n}\n\n/** Build textures with wrapTex + occlusionOnUv2 support. Mirrors master's\n * default texture building but honors per-textureInfo wrapping so\n * KHR_texture_transform can attach per-texture UV state. */\nexport function buildDefaultPbrTexturesExt(\n engine: EngineContext,\n mat: GltfMaterialData,\n sampler: GPUSampler,\n generateMipmaps: GenerateMipmapsFn,\n getCachedTex: (bitmap: ImageBitmap, srgb: boolean) => Texture2D,\n wrapTex: TextureWrapFn,\n samplerFor?: (texInfo: unknown) => GPUSampler\n): PbrTexturesExt {\n const wrap: TextureWrapFn = (tex, ti) => wrapTexCoord(wrapTex(tex, ti), ti);\n // When the asset declares non-default glTF samplers, upload each texture with its own\n // sampler (wrap/filter), caching per (sampler, image, srgb) exactly like the fast-path\n // buildSampledPbrTextures. Without samplerFor (common case) reuse the shared default-\n // sampler cache. A GPUTexture is sampler-independent, so the same image with two\n // samplers yields two Texture2D wrappers, matching the fast path.\n const _localCache = samplerFor ? new Map<GPUSampler, Map<number, Texture2D>>() : null;\n const _ids = samplerFor ? new Map<ImageBitmap, number>() : null;\n let _nextId = 0;\n const pickTex = (image: ImageBitmap, srgb: boolean, texInfo: unknown): Texture2D => {\n if (!samplerFor) {\n return getCachedTex(image, srgb);\n }\n const s = samplerFor(texInfo);\n let bySampler = _localCache!.get(s);\n if (!bySampler) {\n _localCache!.set(s, (bySampler = new Map()));\n }\n let id = _ids!.get(image);\n if (id === undefined) {\n _ids!.set(image, (id = _nextId++));\n }\n const key = id * 2 + (srgb ? 1 : 0);\n let tex = bySampler.get(key);\n if (!tex) {\n tex = uploadTex(engine, image, srgb, s, generateMipmaps);\n bySampler.set(key, tex);\n }\n return tex;\n };\n const raw = mat._rawMatDef ?? {};\n const pbr = raw.pbrMetallicRoughness ?? {};\n const baseColorTexture = mat._baseColorImage\n ? wrap(pickTex(mat._baseColorImage, true, pbr.baseColorTexture), pbr.baseColorTexture)\n : (() => {\n const f = mat._baseColorFactor;\n return uploadTex(\n engine,\n null,\n true,\n sampler,\n generateMipmaps,\n new U8([linearToSrgbByte(f[0]), linearToSrgbByte(f[1]), linearToSrgbByte(f[2]), Math.round(Math.max(0, Math.min(1, f[3])) * 255)])\n );\n })();\n const normalTexture = mat._normalImage ? wrap(pickTex(mat._normalImage, false, raw.normalTexture), raw.normalTexture) : undefined;\n const emissiveTexture = mat._emissiveImage ? wrap(pickTex(mat._emissiveImage, true, raw.emissiveTexture), raw.emissiveTexture) : undefined;\n\n const occlusionOnUv2 = mat._occlusionTexCoord !== 0 && mat._occlusionImage && !mat._metallicRoughnessImage;\n let occlusionTexture: Texture2D | undefined;\n const single = mat._metallicRoughnessImage ?? (occlusionOnUv2 ? null : mat._occlusionImage);\n let ormTexture: Texture2D;\n if (occlusionOnUv2) {\n const clamp = (v: number) => Math.round(Math.max(0, Math.min(1, v)) * 255);\n ormTexture = uploadTex(engine, null, false, sampler, generateMipmaps, new U8([255, clamp(mat._roughnessFactor), clamp(mat._metallicFactor), 255]));\n occlusionTexture = wrap(pickTex(mat._occlusionImage!, false, raw.occlusionTexture), raw.occlusionTexture);\n } else if (single && (!mat._metallicRoughnessImage || !mat._occlusionImage || mat._metallicRoughnessImage === mat._occlusionImage)) {\n const ormTi = mat._metallicRoughnessImage ? pbr.metallicRoughnessTexture : raw.occlusionTexture;\n ormTexture = wrap(pickTex(single, false, ormTi), ormTi);\n } else if (!single) {\n const clamp = (v: number) => Math.round(Math.max(0, Math.min(1, v)) * 255);\n ormTexture = uploadTex(engine, null, false, sampler, generateMipmaps, new U8([255, clamp(mat._roughnessFactor), clamp(mat._metallicFactor), 255]));\n } else {\n ormTexture = wrap(pickTex(mat._metallicRoughnessImage!, false, pbr.metallicRoughnessTexture), pbr.metallicRoughnessTexture);\n }\n // Independent-occlusion UV transform (orm-unpack): occlusion and metallic-roughness\n // share the ORM texture (same image), but the glTF gives occlusion its OWN\n // KHR_texture_transform (or a distinct texture object) so the two can be animated\n // independently via KHR_animation_pointer. Sampling occlusion with MR's transform\n // (the single ormUV) would wrongly animate it. Build a transform-carrying occlusion\n // texture (shares the ORM GPU image) so the shader can sample occlusion with occlUV.\n // Requires the same underlying image as the ORM texture, since the shader re-samples\n // ormTexture at occlUV.\n if (!occlusionTexture && mat._occlusionImage && mat._occlusionImage === mat._metallicRoughnessImage && occlusionNeedsSplit(raw)) {\n occlusionTexture = wrap(pickTex(mat._occlusionImage, false, raw.occlusionTexture), raw.occlusionTexture);\n }\n return { baseColorTexture, ormTexture, normalTexture, emissiveTexture, occlusionTexture };\n}\n\n/** Slow-path assembly: adds occlusionTexCoord and occlusionTexture props. */\nexport function assemblePbrPropsExt(mat: GltfMaterialData, tex: PbrTexturesExt, extLayers: Partial<PbrMaterialProps> | undefined): PbrMaterialProps {\n const ef = mat._emissiveFactor;\n const defaultFactor = (ef[0] === 1 && ef[1] === 1 && ef[2] === 1) || (ef[0] === 0 && ef[1] === 0 && ef[2] === 0);\n // Precompute UV-transform presence so the renderer doesn't scan 5 textures\n // per mesh. Any wrapped texture with `_hasTx=true` (set by gltf-ext-uv-transform)\n // flips this once at build time; omitted entirely on fast path.\n const hasAnyUvTx =\n !!(tex.baseColorTexture as { _hasTx?: true })._hasTx ||\n !!(tex.normalTexture as { _hasTx?: true } | undefined)?._hasTx ||\n !!(tex.ormTexture as { _hasTx?: true })._hasTx ||\n !!(tex.emissiveTexture as { _hasTx?: true } | undefined)?._hasTx ||\n !!(tex.occlusionTexture as { _hasTx?: true } | undefined)?._hasTx;\n return {\n baseColorTexture: tex.baseColorTexture,\n normalTexture: tex.normalTexture,\n ormTexture: tex.ormTexture,\n emissiveTexture: tex.emissiveTexture,\n ...(mat._baseColorImage && !isDefaultBaseColorFactor(mat._baseColorFactor) ? { baseColorFactor: mat._baseColorFactor } : undefined),\n doubleSided: mat._doubleSided,\n occlusionStrength: mat._occlusionImage ? 1.0 : 0,\n ...(mat._occlusionTexCoord ? { occlusionTexCoord: mat._occlusionTexCoord } : undefined),\n ...(tex.occlusionTexture ? { occlusionTexture: tex.occlusionTexture } : undefined),\n ...(mat._normalScale !== 1 ? { normalTextureScale: mat._normalScale } : undefined),\n ...(mat._metallicRoughnessImage ? { metallicFactor: mat._metallicFactor, roughnessFactor: mat._roughnessFactor } : undefined),\n ...(!defaultFactor ? { emissiveColor: [ef[0], ef[1], ef[2]] as [number, number, number] } : undefined),\n enableSpecularAA: true,\n ...(mat._alphaMode === \"BLEND\" ? { alphaBlend: true, alpha: mat._baseColorFactor[3] } : undefined),\n ...(mat._alphaMode === \"MASK\" ? { alpha: mat._baseColorFactor[3], alphaCutOff: mat._alphaCutoff } : undefined),\n ...(hasAnyUvTx ? { _hasUvTx: true } : undefined),\n ...(mat._rawMatDef?.name ? { name: mat._rawMatDef.name as string } : undefined),\n ...extLayers,\n _buildGroup: getPbrGroupBuilder(),\n _uboVersion: 0,\n } as PbrMaterialProps;\n}\n\nfunction isDefaultBaseColorFactor(f: readonly number[]): boolean {\n return f[0] === 1 && f[1] === 1 && f[2] === 1 && f[3] === 1;\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"gltf-pbr-builder-ext.js","sources":["../../../src/loader-gltf/gltf-pbr-builder-ext.ts"],"sourcesContent":["/** Lazy-loaded slow path for PBR material assembly.\n * Only pulled into bundles whose glTF uses features that require per-texture\n * wrapping (e.g. KHR_texture_transform) or occlusion on UV2 (texCoord=1 with\n * no shared MR image). Scene1 (BoomBox) and any vanilla-PBR glTF skip this\n * module entirely. */\n\nimport { U8 } from \"../engine/typed-arrays.js\";\nimport type { EngineContext } from \"../engine/engine.js\";\nimport type { Texture2D } from \"../texture/texture-2d.js\";\nimport { cloneTexture2D } from \"../texture/texture-2d.js\";\nimport type { PbrMaterialProps } from \"../material/pbr/pbr-material.js\";\nimport { getPbrGroupBuilder } from \"../material/pbr/pbr-material.js\";\nimport type { GltfMaterialData } from \"./gltf-material.js\";\nimport { linearToSrgbByte } from \"../math/color.js\";\nimport type { TextureWrapFn, GenerateMipmapsFn } from \"./gltf-pbr-builder.js\";\nimport { uploadTex } from \"./gltf-pbr-builder.js\";\n\nexport interface PbrTexturesExt {\n baseColorTexture: Texture2D;\n ormTexture: Texture2D;\n normalTexture: Texture2D | undefined;\n emissiveTexture: Texture2D | undefined;\n occlusionTexture: Texture2D | undefined;\n}\n\n/** Stamp `_texCoord=1` on a clone when textureInfo selects UV1 and the\n * wrapTex layer didn't already set it (i.e. scene has no KHR_texture_transform). */\nfunction wrapTexCoord(tex: Texture2D, texInfo: unknown): Texture2D {\n if (!texInfo) {\n return tex;\n }\n if ((tex as { _texCoord?: 0 | 1 })._texCoord === 1) {\n return tex;\n }\n const ti = texInfo as { texCoord?: number; extensions?: { KHR_texture_transform?: { texCoord?: number } } };\n const tc = ti.extensions?.KHR_texture_transform?.texCoord ?? ti.texCoord;\n return tc === 1 ? cloneTexture2D(tex, { _texCoord: 1 }) : tex;\n}\n\n/** True when occlusion shares the ORM image with metallic-roughness but must be sampled\n * with its OWN UV transform — i.e. occlusion references a distinct glTF texture object,\n * or it carries its own KHR_texture_transform that an animation pointer can drive apart\n * from the MR transform. Drives the orm-unpack split in buildDefaultPbrTexturesExt. */\nfunction occlusionNeedsSplit(raw: {\n occlusionTexture?: { index?: number; extensions?: { KHR_texture_transform?: unknown } };\n pbrMetallicRoughness?: { metallicRoughnessTexture?: { index?: number } };\n}): boolean {\n const occ = raw.occlusionTexture;\n const mr = raw.pbrMetallicRoughness?.metallicRoughnessTexture;\n if (!occ || !mr) {\n return false;\n }\n return occ.index !== mr.index || occ.extensions?.KHR_texture_transform != null;\n}\n\n/** Build textures with wrapTex + occlusionOnUv2 support. Mirrors master's\n * default texture building but honors per-textureInfo wrapping so\n * KHR_texture_transform can attach per-texture UV state. */\nexport function buildDefaultPbrTexturesExt(\n engine: EngineContext,\n mat: GltfMaterialData,\n sampler: GPUSampler,\n generateMipmaps: GenerateMipmapsFn,\n getCachedTex: (bitmap: ImageBitmap, srgb: boolean) => Texture2D,\n wrapTex: TextureWrapFn,\n samplerFor?: (texInfo: unknown) => GPUSampler\n): PbrTexturesExt {\n const wrap: TextureWrapFn = (tex, ti) => wrapTexCoord(wrapTex(tex, ti), ti);\n // When the asset declares non-default glTF samplers, upload each texture with its own\n // sampler (wrap/filter), caching per (sampler, image, srgb) exactly like the fast-path\n // buildSampledPbrTextures. Without samplerFor (common case) reuse the shared default-\n // sampler cache. A GPUTexture is sampler-independent, so the same image with two\n // samplers yields two Texture2D wrappers, matching the fast path.\n const _localCache = samplerFor ? new Map<GPUSampler, Map<number, Texture2D>>() : null;\n const _ids = samplerFor ? new Map<ImageBitmap, number>() : null;\n let _nextId = 0;\n const pickTex = (image: ImageBitmap, srgb: boolean, texInfo: unknown): Texture2D => {\n if (!samplerFor) {\n return getCachedTex(image, srgb);\n }\n const s = samplerFor(texInfo);\n let bySampler = _localCache!.get(s);\n if (!bySampler) {\n _localCache!.set(s, (bySampler = new Map()));\n }\n let id = _ids!.get(image);\n if (id === undefined) {\n _ids!.set(image, (id = _nextId++));\n }\n const key = id * 2 + (srgb ? 1 : 0);\n let tex = bySampler.get(key);\n if (!tex) {\n tex = uploadTex(engine, image, srgb, s, generateMipmaps);\n bySampler.set(key, tex);\n }\n return tex;\n };\n const raw = mat._rawMatDef ?? {};\n const pbr = raw.pbrMetallicRoughness ?? {};\n const baseColorTexture = mat._baseColorImage\n ? wrap(pickTex(mat._baseColorImage, true, pbr.baseColorTexture), pbr.baseColorTexture)\n : (() => {\n const f = mat._baseColorFactor;\n return uploadTex(\n engine,\n null,\n true,\n sampler,\n generateMipmaps,\n new U8([linearToSrgbByte(f[0]), linearToSrgbByte(f[1]), linearToSrgbByte(f[2]), Math.round(Math.max(0, Math.min(1, f[3])) * 255)])\n );\n })();\n const normalTexture = mat._normalImage ? wrap(pickTex(mat._normalImage, false, raw.normalTexture), raw.normalTexture) : undefined;\n const emissiveTexture = mat._emissiveImage ? wrap(pickTex(mat._emissiveImage, true, raw.emissiveTexture), raw.emissiveTexture) : undefined;\n\n const occlusionOnUv2 = mat._occlusionTexCoord !== 0 && mat._occlusionImage && !mat._metallicRoughnessImage;\n let occlusionTexture: Texture2D | undefined;\n const single = mat._metallicRoughnessImage ?? (occlusionOnUv2 ? null : mat._occlusionImage);\n let ormTexture: Texture2D;\n if (occlusionOnUv2) {\n const clamp = (v: number) => Math.round(Math.max(0, Math.min(1, v)) * 255);\n ormTexture = uploadTex(engine, null, false, sampler, generateMipmaps, new U8([255, clamp(mat._roughnessFactor), clamp(mat._metallicFactor), 255]));\n occlusionTexture = wrap(pickTex(mat._occlusionImage!, false, raw.occlusionTexture), raw.occlusionTexture);\n } else if (single && (!mat._metallicRoughnessImage || !mat._occlusionImage || mat._metallicRoughnessImage === mat._occlusionImage)) {\n const ormTi = mat._metallicRoughnessImage ? pbr.metallicRoughnessTexture : raw.occlusionTexture;\n ormTexture = wrap(pickTex(single, false, ormTi), ormTi);\n } else if (!single) {\n const clamp = (v: number) => Math.round(Math.max(0, Math.min(1, v)) * 255);\n ormTexture = uploadTex(engine, null, false, sampler, generateMipmaps, new U8([255, clamp(mat._roughnessFactor), clamp(mat._metallicFactor), 255]));\n } else {\n ormTexture = wrap(pickTex(mat._metallicRoughnessImage!, false, pbr.metallicRoughnessTexture), pbr.metallicRoughnessTexture);\n }\n // Independent-occlusion UV transform (orm-unpack): occlusion and metallic-roughness\n // share the ORM texture (same image), but the glTF gives occlusion its OWN\n // KHR_texture_transform (or a distinct texture object) so the two can be animated\n // independently via KHR_animation_pointer. Sampling occlusion with MR's transform\n // (the single ormUV) would wrongly animate it. Build a transform-carrying occlusion\n // texture (shares the ORM GPU image) so the shader can sample occlusion with occlUV.\n // Requires the same underlying image as the ORM texture, since the shader re-samples\n // ormTexture at occlUV.\n if (!occlusionTexture && mat._occlusionImage && mat._occlusionImage === mat._metallicRoughnessImage && occlusionNeedsSplit(raw)) {\n occlusionTexture = wrap(pickTex(mat._occlusionImage, false, raw.occlusionTexture), raw.occlusionTexture);\n }\n return { baseColorTexture, ormTexture, normalTexture, emissiveTexture, occlusionTexture };\n}\n\n/** Slow-path assembly: adds occlusionTexCoord and occlusionTexture props. */\nexport function assemblePbrPropsExt(mat: GltfMaterialData, tex: PbrTexturesExt, extLayers: Partial<PbrMaterialProps> | undefined): PbrMaterialProps {\n const ef = mat._emissiveFactor;\n // See gltf-pbr-builder.ts: emissiveFactor [1,1,1] is a no-op only with an emissive texture;\n // with no texture it is a real full-white emissive that must be applied (Material_03).\n const defaultFactor = (ef[0] === 0 && ef[1] === 0 && ef[2] === 0) || (!!tex.emissiveTexture && ef[0] === 1 && ef[1] === 1 && ef[2] === 1);\n // Precompute UV-transform presence so the renderer doesn't scan 5 textures\n // per mesh. Any wrapped texture with `_hasTx=true` (set by gltf-ext-uv-transform)\n // flips this once at build time; omitted entirely on fast path.\n const hasAnyUvTx =\n !!(tex.baseColorTexture as { _hasTx?: true })._hasTx ||\n !!(tex.normalTexture as { _hasTx?: true } | undefined)?._hasTx ||\n !!(tex.ormTexture as { _hasTx?: true })._hasTx ||\n !!(tex.emissiveTexture as { _hasTx?: true } | undefined)?._hasTx ||\n !!(tex.occlusionTexture as { _hasTx?: true } | undefined)?._hasTx;\n return {\n baseColorTexture: tex.baseColorTexture,\n normalTexture: tex.normalTexture,\n ormTexture: tex.ormTexture,\n emissiveTexture: tex.emissiveTexture,\n ...(mat._baseColorImage && !isDefaultBaseColorFactor(mat._baseColorFactor) ? { baseColorFactor: mat._baseColorFactor } : undefined),\n doubleSided: mat._doubleSided,\n occlusionStrength: mat._occlusionImage ? 1.0 : 0,\n ...(mat._occlusionTexCoord ? { occlusionTexCoord: mat._occlusionTexCoord } : undefined),\n ...(tex.occlusionTexture ? { occlusionTexture: tex.occlusionTexture } : undefined),\n ...(mat._normalScale !== 1 ? { normalTextureScale: mat._normalScale } : undefined),\n ...(mat._metallicRoughnessImage ? { metallicFactor: mat._metallicFactor, roughnessFactor: mat._roughnessFactor } : undefined),\n ...(!defaultFactor ? { emissiveColor: [ef[0], ef[1], ef[2]] as [number, number, number] } : undefined),\n enableSpecularAA: true,\n ...(mat._alphaMode === \"BLEND\" ? { alphaBlend: true, alpha: mat._baseColorFactor[3] } : undefined),\n ...(mat._alphaMode === \"MASK\" ? { alpha: mat._baseColorFactor[3], alphaCutOff: mat._alphaCutoff } : undefined),\n ...(hasAnyUvTx ? { _hasUvTx: true } : undefined),\n ...(mat._rawMatDef?.name ? { name: mat._rawMatDef.name as string } : undefined),\n ...extLayers,\n _buildGroup: getPbrGroupBuilder(),\n _uboVersion: 0,\n } as PbrMaterialProps;\n}\n\nfunction isDefaultBaseColorFactor(f: readonly number[]): boolean {\n return f[0] === 1 && f[1] === 1 && f[2] === 1 && f[3] === 1;\n}\n"],"names":[],"mappings":";;;;;;AA2BA,SAAS,YAAA,CAAa,KAAgB,OAAA,EAA6B;AAC/D,EAAA,IAAI,CAAC,OAAA,EAAS;AACV,IAAA,OAAO,GAAA;AAAA,EACX;AACA,EAAA,IAAK,GAAA,CAA8B,cAAc,CAAA,EAAG;AAChD,IAAA,OAAO,GAAA;AAAA,EACX;AACA,EAAA,MAAM,EAAA,GAAK,OAAA;AACX,EAAA,MAAM,EAAA,GAAK,EAAA,CAAG,UAAA,EAAY,qBAAA,EAAuB,YAAY,EAAA,CAAG,QAAA;AAChE,EAAA,OAAO,EAAA,KAAO,IAAI,cAAA,CAAe,GAAA,EAAK,EAAE,SAAA,EAAW,CAAA,EAAG,CAAA,GAAI,GAAA;AAC9D;AAMA,SAAS,oBAAoB,GAAA,EAGjB;AACR,EAAA,MAAM,MAAM,GAAA,CAAI,gBAAA;AAChB,EAAA,MAAM,EAAA,GAAK,IAAI,oBAAA,EAAsB,wBAAA;AACrC,EAAA,IAAI,CAAC,GAAA,IAAO,CAAC,EAAA,EAAI;AACb,IAAA,OAAO,KAAA;AAAA,EACX;AACA,EAAA,OAAO,IAAI,KAAA,KAAU,EAAA,CAAG,KAAA,IAAS,GAAA,CAAI,YAAY,qBAAA,IAAyB,IAAA;AAC9E;AAKO,SAAS,2BACZ,MAAA,EACA,GAAA,EACA,SACA,eAAA,EACA,YAAA,EACA,SACA,UAAA,EACc;AACd,EAAA,MAAM,IAAA,GAAsB,CAAC,GAAA,EAAK,EAAA,KAAO,aAAa,OAAA,CAAQ,GAAA,EAAK,EAAE,CAAA,EAAG,EAAE,CAAA;AAM1E,EAAA,MAAM,WAAA,GAAc,UAAA,mBAAa,IAAI,GAAA,EAAwC,GAAI,IAAA;AACjF,EAAA,MAAM,IAAA,GAAO,UAAA,mBAAa,IAAI,GAAA,EAAyB,GAAI,IAAA;AAC3D,EAAA,IAAI,OAAA,GAAU,CAAA;AACd,EAAA,MAAM,OAAA,GAAU,CAAC,KAAA,EAAoB,IAAA,EAAe,OAAA,KAAgC;AAChF,IAAA,IAAI,CAAC,UAAA,EAAY;AACb,MAAA,OAAO,YAAA,CAAa,OAAO,IAAI,CAAA;AAAA,IACnC;AACA,IAAA,MAAM,CAAA,GAAI,WAAW,OAAO,CAAA;AAC5B,IAAA,IAAI,SAAA,GAAY,WAAA,CAAa,GAAA,CAAI,CAAC,CAAA;AAClC,IAAA,IAAI,CAAC,SAAA,EAAW;AACZ,MAAA,WAAA,CAAa,GAAA,CAAI,CAAA,EAAI,SAAA,mBAAY,IAAI,KAAM,CAAA;AAAA,IAC/C;AACA,IAAA,IAAI,EAAA,GAAK,IAAA,CAAM,GAAA,CAAI,KAAK,CAAA;AACxB,IAAA,IAAI,OAAO,MAAA,EAAW;AAClB,MAAA,IAAA,CAAM,GAAA,CAAI,KAAA,EAAQ,EAAA,GAAK,OAAA,EAAU,CAAA;AAAA,IACrC;AACA,IAAA,MAAM,GAAA,GAAM,EAAA,GAAK,CAAA,IAAK,IAAA,GAAO,CAAA,GAAI,CAAA,CAAA;AACjC,IAAA,IAAI,GAAA,GAAM,SAAA,CAAU,GAAA,CAAI,GAAG,CAAA;AAC3B,IAAA,IAAI,CAAC,GAAA,EAAK;AACN,MAAA,GAAA,GAAM,SAAA,CAAU,MAAA,EAAQ,KAAA,EAAO,IAAA,EAAM,GAAG,eAAe,CAAA;AACvD,MAAA,SAAA,CAAU,GAAA,CAAI,KAAK,GAAG,CAAA;AAAA,IAC1B;AACA,IAAA,OAAO,GAAA;AAAA,EACX,CAAA;AACA,EAAA,MAAM,GAAA,GAAM,GAAA,CAAI,UAAA,IAAc,EAAC;AAC/B,EAAA,MAAM,GAAA,GAAM,GAAA,CAAI,oBAAA,IAAwB,EAAC;AACzC,EAAA,MAAM,gBAAA,GAAmB,GAAA,CAAI,eAAA,GACvB,IAAA,CAAK,QAAQ,GAAA,CAAI,eAAA,EAAiB,IAAA,EAAM,GAAA,CAAI,gBAAgB,CAAA,EAAG,GAAA,CAAI,gBAAgB,KAClF,MAAM;AACH,IAAA,MAAM,IAAI,GAAA,CAAI,gBAAA;AACd,IAAA,OAAO,SAAA;AAAA,MACH,MAAA;AAAA,MACA,IAAA;AAAA,MACA,IAAA;AAAA,MACA,OAAA;AAAA,MACA,eAAA;AAAA,MACA,IAAI,EAAA,CAAG,CAAC,gBAAA,CAAiB,EAAE,CAAC,CAAC,CAAA,EAAG,gBAAA,CAAiB,CAAA,CAAE,CAAC,CAAC,CAAA,EAAG,iBAAiB,CAAA,CAAE,CAAC,CAAC,CAAA,EAAG,IAAA,CAAK,KAAA,CAAM,IAAA,CAAK,GAAA,CAAI,GAAG,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,CAAA,CAAE,CAAC,CAAC,CAAC,CAAA,GAAI,GAAG,CAAC,CAAC;AAAA,KACrI;AAAA,EACJ,CAAA,GAAG;AACT,EAAA,MAAM,aAAA,GAAgB,GAAA,CAAI,YAAA,GAAe,IAAA,CAAK,OAAA,CAAQ,GAAA,CAAI,YAAA,EAAc,KAAA,EAAO,GAAA,CAAI,aAAa,CAAA,EAAG,GAAA,CAAI,aAAa,CAAA,GAAI,MAAA;AACxH,EAAA,MAAM,eAAA,GAAkB,GAAA,CAAI,cAAA,GAAiB,IAAA,CAAK,OAAA,CAAQ,GAAA,CAAI,cAAA,EAAgB,IAAA,EAAM,GAAA,CAAI,eAAe,CAAA,EAAG,GAAA,CAAI,eAAe,CAAA,GAAI,MAAA;AAEjI,EAAA,MAAM,iBAAiB,GAAA,CAAI,kBAAA,KAAuB,KAAK,GAAA,CAAI,eAAA,IAAmB,CAAC,GAAA,CAAI,uBAAA;AACnF,EAAA,IAAI,gBAAA;AACJ,EAAA,MAAM,MAAA,GAAS,GAAA,CAAI,uBAAA,KAA4B,cAAA,GAAiB,OAAO,GAAA,CAAI,eAAA,CAAA;AAC3E,EAAA,IAAI,UAAA;AACJ,EAAA,IAAI,cAAA,EAAgB;AAChB,IAAA,MAAM,KAAA,GAAQ,CAAC,CAAA,KAAc,IAAA,CAAK,MAAM,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,CAAC,CAAC,IAAI,GAAG,CAAA;AACzE,IAAA,UAAA,GAAa,SAAA,CAAU,QAAQ,IAAA,EAAM,KAAA,EAAO,SAAS,eAAA,EAAiB,IAAI,GAAG,CAAC,GAAA,EAAK,MAAM,GAAA,CAAI,gBAAgB,GAAG,KAAA,CAAM,GAAA,CAAI,eAAe,CAAA,EAAG,GAAG,CAAC,CAAC,CAAA;AACjJ,IAAA,gBAAA,GAAmB,IAAA,CAAK,QAAQ,GAAA,CAAI,eAAA,EAAkB,OAAO,GAAA,CAAI,gBAAgB,CAAA,EAAG,GAAA,CAAI,gBAAgB,CAAA;AAAA,EAC5G,CAAA,MAAA,IAAW,MAAA,KAAW,CAAC,GAAA,CAAI,uBAAA,IAA2B,CAAC,GAAA,CAAI,eAAA,IAAmB,GAAA,CAAI,uBAAA,KAA4B,GAAA,CAAI,eAAA,CAAA,EAAkB;AAChI,IAAA,MAAM,KAAA,GAAQ,GAAA,CAAI,uBAAA,GAA0B,GAAA,CAAI,2BAA2B,GAAA,CAAI,gBAAA;AAC/E,IAAA,UAAA,GAAa,KAAK,OAAA,CAAQ,MAAA,EAAQ,KAAA,EAAO,KAAK,GAAG,KAAK,CAAA;AAAA,EAC1D,CAAA,MAAA,IAAW,CAAC,MAAA,EAAQ;AAChB,IAAA,MAAM,KAAA,GAAQ,CAAC,CAAA,KAAc,IAAA,CAAK,MAAM,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,CAAC,CAAC,IAAI,GAAG,CAAA;AACzE,IAAA,UAAA,GAAa,SAAA,CAAU,QAAQ,IAAA,EAAM,KAAA,EAAO,SAAS,eAAA,EAAiB,IAAI,GAAG,CAAC,GAAA,EAAK,MAAM,GAAA,CAAI,gBAAgB,GAAG,KAAA,CAAM,GAAA,CAAI,eAAe,CAAA,EAAG,GAAG,CAAC,CAAC,CAAA;AAAA,EACrJ,CAAA,MAAO;AACH,IAAA,UAAA,GAAa,IAAA,CAAK,QAAQ,GAAA,CAAI,uBAAA,EAA0B,OAAO,GAAA,CAAI,wBAAwB,CAAA,EAAG,GAAA,CAAI,wBAAwB,CAAA;AAAA,EAC9H;AASA,EAAA,IAAI,CAAC,gBAAA,IAAoB,GAAA,CAAI,eAAA,IAAmB,GAAA,CAAI,oBAAoB,GAAA,CAAI,uBAAA,IAA2B,mBAAA,CAAoB,GAAG,CAAA,EAAG;AAC7H,IAAA,gBAAA,GAAmB,IAAA,CAAK,QAAQ,GAAA,CAAI,eAAA,EAAiB,OAAO,GAAA,CAAI,gBAAgB,CAAA,EAAG,GAAA,CAAI,gBAAgB,CAAA;AAAA,EAC3G;AACA,EAAA,OAAO,EAAE,gBAAA,EAAkB,UAAA,EAAY,aAAA,EAAe,iBAAiB,gBAAA,EAAiB;AAC5F;AAGO,SAAS,mBAAA,CAAoB,GAAA,EAAuB,GAAA,EAAqB,SAAA,EAAoE;AAChJ,EAAA,MAAM,KAAK,GAAA,CAAI,eAAA;AAGf,EAAA,MAAM,aAAA,GAAiB,EAAA,CAAG,CAAC,CAAA,KAAM,CAAA,IAAK,EAAA,CAAG,CAAC,CAAA,KAAM,CAAA,IAAK,EAAA,CAAG,CAAC,CAAA,KAAM,CAAA,IAAO,CAAC,CAAC,GAAA,CAAI,eAAA,IAAmB,EAAA,CAAG,CAAC,CAAA,KAAM,CAAA,IAAK,EAAA,CAAG,CAAC,CAAA,KAAM,CAAA,IAAK,EAAA,CAAG,CAAC,CAAA,KAAM,CAAA;AAIvI,EAAA,MAAM,UAAA,GACF,CAAC,CAAE,GAAA,CAAI,gBAAA,CAAuC,UAC9C,CAAC,CAAE,GAAA,CAAI,aAAA,EAAiD,MAAA,IACxD,CAAC,CAAE,GAAA,CAAI,UAAA,CAAiC,MAAA,IACxC,CAAC,CAAE,GAAA,CAAI,iBAAmD,MAAA,IAC1D,CAAC,CAAE,GAAA,CAAI,gBAAA,EAAoD,MAAA;AAC/D,EAAA,OAAO;AAAA,IACH,kBAAkB,GAAA,CAAI,gBAAA;AAAA,IACtB,eAAe,GAAA,CAAI,aAAA;AAAA,IACnB,YAAY,GAAA,CAAI,UAAA;AAAA,IAChB,iBAAiB,GAAA,CAAI,eAAA;AAAA,IACrB,GAAI,GAAA,CAAI,eAAA,IAAmB,CAAC,wBAAA,CAAyB,GAAA,CAAI,gBAAgB,CAAA,GAAI,EAAE,eAAA,EAAiB,GAAA,CAAI,gBAAA,EAAiB,GAAI,MAAA;AAAA,IACzH,aAAa,GAAA,CAAI,YAAA;AAAA,IACjB,iBAAA,EAAmB,GAAA,CAAI,eAAA,GAAkB,CAAA,GAAM,CAAA;AAAA,IAC/C,GAAI,GAAA,CAAI,kBAAA,GAAqB,EAAE,iBAAA,EAAmB,GAAA,CAAI,oBAAmB,GAAI,MAAA;AAAA,IAC7E,GAAI,GAAA,CAAI,gBAAA,GAAmB,EAAE,gBAAA,EAAkB,GAAA,CAAI,kBAAiB,GAAI,MAAA;AAAA,IACxE,GAAI,IAAI,YAAA,KAAiB,CAAA,GAAI,EAAE,kBAAA,EAAoB,GAAA,CAAI,cAAa,GAAI,MAAA;AAAA,IACxE,GAAI,GAAA,CAAI,uBAAA,GAA0B,EAAE,cAAA,EAAgB,IAAI,eAAA,EAAiB,eAAA,EAAiB,GAAA,CAAI,gBAAA,EAAiB,GAAI,MAAA;AAAA,IACnH,GAAI,CAAC,aAAA,GAAgB,EAAE,aAAA,EAAe,CAAC,EAAA,CAAG,CAAC,CAAA,EAAG,EAAA,CAAG,CAAC,CAAA,EAAG,EAAA,CAAG,CAAC,CAAC,GAA8B,GAAI,MAAA;AAAA,IAC5F,gBAAA,EAAkB,IAAA;AAAA,IAClB,GAAI,GAAA,CAAI,UAAA,KAAe,OAAA,GAAU,EAAE,UAAA,EAAY,IAAA,EAAM,KAAA,EAAO,GAAA,CAAI,gBAAA,CAAiB,CAAC,CAAA,EAAE,GAAI,MAAA;AAAA,IACxF,GAAI,GAAA,CAAI,UAAA,KAAe,MAAA,GAAS,EAAE,KAAA,EAAO,GAAA,CAAI,gBAAA,CAAiB,CAAC,CAAA,EAAG,WAAA,EAAa,GAAA,CAAI,cAAa,GAAI,MAAA;AAAA,IACpG,GAAI,UAAA,GAAa,EAAE,QAAA,EAAU,MAAK,GAAI,MAAA;AAAA,IACtC,GAAI,IAAI,UAAA,EAAY,IAAA,GAAO,EAAE,IAAA,EAAM,GAAA,CAAI,UAAA,CAAW,IAAA,EAAe,GAAI,MAAA;AAAA,IACrE,GAAG,SAAA;AAAA,IACH,aAAa,kBAAA,EAAmB;AAAA,IAChC,WAAA,EAAa;AAAA,GACjB;AACJ;AAEA,SAAS,yBAAyB,CAAA,EAA+B;AAC7D,EAAA,OAAO,CAAA,CAAE,CAAC,CAAA,KAAM,CAAA,IAAK,EAAE,CAAC,CAAA,KAAM,CAAA,IAAK,CAAA,CAAE,CAAC,CAAA,KAAM,CAAA,IAAK,CAAA,CAAE,CAAC,CAAA,KAAM,CAAA;AAC9D;;;;"}
@@ -35,7 +35,7 @@ function uploadTex(engine, bitmap, srgb, sampler, generateMipmaps, fallback) {
35
35
  }
36
36
  function assemblePbrProps(mat, baseColorTexture, ormTexture, normalTexture, emissiveTexture, extLayers) {
37
37
  const ef = mat._emissiveFactor;
38
- const defaultFactor = ef[0] === 1 && ef[1] === 1 && ef[2] === 1 || ef[0] === 0 && ef[1] === 0 && ef[2] === 0;
38
+ const defaultFactor = ef[0] === 0 && ef[1] === 0 && ef[2] === 0 || !!emissiveTexture && ef[0] === 1 && ef[1] === 1 && ef[2] === 1;
39
39
  return {
40
40
  baseColorTexture,
41
41
  normalTexture,
@@ -1 +1 @@
1
- {"version":3,"file":"gltf-pbr-builder.js","sources":["../../../src/loader-gltf/gltf-pbr-builder.ts"],"sourcesContent":["/** Shared PBR-material assembly + texture upload + ext-layer merging.\n * Used by both the core loader (`load-gltf.ts`) and the variants loader\n * (`gltf-variants.ts`) so they can't drift. */\n\nimport { U8 } from \"../engine/typed-arrays.js\";\nimport { TU } from \"../engine/gpu-flags.js\";\nimport type { EngineContext } from \"../engine/engine.js\";\nimport type { Texture2D } from \"../texture/texture-2d.js\";\nimport type { PbrMaterialProps } from \"../material/pbr/pbr-material.js\";\nimport { getPbrGroupBuilder } from \"../material/pbr/pbr-material.js\";\nimport type { GltfMaterialData, GltfMatExtCtx } from \"./gltf-material.js\";\nimport type { GltfFeature } from \"./gltf-feature.js\";\nimport { mipLevelCount } from \"../texture/mip-count.js\";\nimport { linearToSrgbByte } from \"../math/color.js\";\n\n/** Texture post-processor composed from every active feature's `wrapTexture`\n * hook. Identity when no feature contributes one (common case). Kept simple\n * so the core loader stays feature-agnostic and tree-shakes cleanly. */\nexport type TextureWrapFn = (tex: Texture2D, texInfo: unknown) => Texture2D;\nexport const identityTexWrap: TextureWrapFn = (tex) => tex;\n\nexport type GenerateMipmapsFn = (engine: EngineContext, texture: GPUTexture, face?: number) => void;\n\nexport function uploadTex(\n engine: EngineContext,\n bitmap: ImageBitmap | null,\n srgb: boolean,\n sampler: GPUSampler,\n generateMipmaps: GenerateMipmapsFn,\n fallback?: Uint8Array\n): Texture2D {\n const device = engine._device;\n const w = bitmap?.width ?? 1;\n const h = bitmap?.height ?? 1;\n const fmt: GPUTextureFormat = srgb ? \"rgba8unorm-srgb\" : \"rgba8unorm\";\n const mips = bitmap ? mipLevelCount(w, h) : 1;\n const tex = device.createTexture({\n size: { width: w, height: h },\n format: fmt,\n usage: TU.TEXTURE_BINDING | TU.COPY_DST | TU.COPY_SRC | TU.RENDER_ATTACHMENT,\n mipLevelCount: mips,\n });\n if (bitmap) {\n device.queue.copyExternalImageToTexture({ source: bitmap }, { texture: tex, premultipliedAlpha: false }, { width: w, height: h });\n generateMipmaps(engine, tex);\n } else {\n device.queue.writeTexture({ texture: tex }, (fallback ?? new U8([255, 255, 255, 255])) as Uint8Array<ArrayBuffer>, { bytesPerRow: 4 }, { width: 1, height: 1 });\n }\n const result: Texture2D = {\n texture: tex,\n view: tex.createView(),\n sampler,\n width: w,\n height: h,\n };\n engine._dlr?.b(result, bitmap, srgb, !!bitmap, fallback);\n return result;\n}\n\n/** Assemble a PbrMaterialProps from parsed glTF material data + already-uploaded\n * textures + per-ext fragment overrides. Fast-path: no wrapTex, no occlusionOnUv2,\n * no occlusionTexture. Slow-path additions live in gltf-pbr-builder-ext.ts. */\nexport function assemblePbrProps(\n mat: GltfMaterialData,\n baseColorTexture: Texture2D,\n ormTexture: Texture2D,\n normalTexture: Texture2D | undefined,\n emissiveTexture: Texture2D | undefined,\n extLayers: Partial<PbrMaterialProps> | undefined\n): PbrMaterialProps {\n const ef = mat._emissiveFactor;\n const defaultFactor = (ef[0] === 1 && ef[1] === 1 && ef[2] === 1) || (ef[0] === 0 && ef[1] === 0 && ef[2] === 0);\n return {\n baseColorTexture,\n normalTexture,\n ormTexture,\n emissiveTexture,\n ...(mat._baseColorImage && !isDefaultBaseColorFactor(mat._baseColorFactor) ? { baseColorFactor: mat._baseColorFactor } : undefined),\n doubleSided: mat._doubleSided,\n occlusionStrength: mat._occlusionImage ? 1.0 : 0,\n ...(mat._normalScale !== 1 ? { normalTextureScale: mat._normalScale } : undefined),\n ...(mat._metallicRoughnessImage ? { metallicFactor: mat._metallicFactor, roughnessFactor: mat._roughnessFactor } : undefined),\n ...(!defaultFactor ? { emissiveColor: [ef[0], ef[1], ef[2]] as [number, number, number] } : undefined),\n enableSpecularAA: true,\n ...(mat._alphaMode === \"BLEND\" ? { alphaBlend: true, alpha: mat._baseColorFactor[3] } : undefined),\n ...(mat._alphaMode === \"MASK\" ? { alpha: mat._baseColorFactor[3], alphaCutOff: mat._alphaCutoff } : undefined),\n ...(mat._rawMatDef?.name ? { name: mat._rawMatDef.name as string } : undefined),\n ...extLayers,\n _buildGroup: getPbrGroupBuilder(),\n _uboVersion: 0,\n } as PbrMaterialProps;\n}\n\nfunction isDefaultBaseColorFactor(f: readonly number[]): boolean {\n return f[0] === 1 && f[1] === 1 && f[2] === 1 && f[3] === 1;\n}\n\n/** Build the always-present default textures (base color + ORM) from a parsed glTF material.\n * Fast-path version: no wrapTex, no occlusion-on-uv2 handling. The slow path lives\n * in gltf-pbr-builder-ext.ts and is lazy-loaded only when needed. */\nexport function buildDefaultPbrTextures(\n engine: EngineContext,\n mat: GltfMaterialData,\n sampler: GPUSampler,\n generateMipmaps: GenerateMipmapsFn,\n getCachedTex: (bitmap: ImageBitmap, srgb: boolean) => Texture2D\n): { baseColorTexture: Texture2D; ormTexture: Texture2D; normalTexture: Texture2D | undefined; emissiveTexture: Texture2D | undefined } {\n const baseColorTexture = mat._baseColorImage\n ? getCachedTex(mat._baseColorImage, true)\n : (() => {\n const f = mat._baseColorFactor;\n return uploadTex(\n engine,\n null,\n true,\n sampler,\n generateMipmaps,\n new U8([linearToSrgbByte(f[0]), linearToSrgbByte(f[1]), linearToSrgbByte(f[2]), Math.round(Math.max(0, Math.min(1, f[3])) * 255)])\n );\n })();\n const normalTexture = mat._normalImage ? getCachedTex(mat._normalImage, false) : undefined;\n const emissiveTexture = mat._emissiveImage ? getCachedTex(mat._emissiveImage, true) : undefined;\n\n const single = mat._metallicRoughnessImage ?? mat._occlusionImage;\n let ormTexture: Texture2D;\n if (single && (!mat._metallicRoughnessImage || !mat._occlusionImage || mat._metallicRoughnessImage === mat._occlusionImage)) {\n ormTexture = getCachedTex(single, false);\n } else if (!single) {\n const clamp = (v: number) => Math.round(Math.max(0, Math.min(1, v)) * 255);\n ormTexture = uploadTex(engine, null, false, sampler, generateMipmaps, new U8([255, clamp(mat._roughnessFactor), clamp(mat._metallicFactor), 255]));\n } else {\n ormTexture = getCachedTex(mat._metallicRoughnessImage!, false);\n }\n return { baseColorTexture, ormTexture, normalTexture, emissiveTexture };\n}\n\n/** Run all material-layer features and merge their fragments. */\nexport async function runMatExts(mat: GltfMaterialData, exts: GltfFeature[], ctx: GltfMatExtCtx): Promise<Partial<PbrMaterialProps> | undefined> {\n if (!exts.length) {\n return undefined;\n }\n const fragments = await Promise.all(exts.map((ext) => ext.applyMaterial!(mat, ctx)));\n let layers: Partial<PbrMaterialProps> | undefined;\n for (const f of fragments) {\n if (f) {\n layers ??= {};\n Object.assign(layers, f);\n }\n }\n return layers;\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"gltf-pbr-builder.js","sources":["../../../src/loader-gltf/gltf-pbr-builder.ts"],"sourcesContent":["/** Shared PBR-material assembly + texture upload + ext-layer merging.\n * Used by both the core loader (`load-gltf.ts`) and the variants loader\n * (`gltf-variants.ts`) so they can't drift. */\n\nimport { U8 } from \"../engine/typed-arrays.js\";\nimport { TU } from \"../engine/gpu-flags.js\";\nimport type { EngineContext } from \"../engine/engine.js\";\nimport type { Texture2D } from \"../texture/texture-2d.js\";\nimport type { PbrMaterialProps } from \"../material/pbr/pbr-material.js\";\nimport { getPbrGroupBuilder } from \"../material/pbr/pbr-material.js\";\nimport type { GltfMaterialData, GltfMatExtCtx } from \"./gltf-material.js\";\nimport type { GltfFeature } from \"./gltf-feature.js\";\nimport { mipLevelCount } from \"../texture/mip-count.js\";\nimport { linearToSrgbByte } from \"../math/color.js\";\n\n/** Texture post-processor composed from every active feature's `wrapTexture`\n * hook. Identity when no feature contributes one (common case). Kept simple\n * so the core loader stays feature-agnostic and tree-shakes cleanly. */\nexport type TextureWrapFn = (tex: Texture2D, texInfo: unknown) => Texture2D;\nexport const identityTexWrap: TextureWrapFn = (tex) => tex;\n\nexport type GenerateMipmapsFn = (engine: EngineContext, texture: GPUTexture, face?: number) => void;\n\nexport function uploadTex(\n engine: EngineContext,\n bitmap: ImageBitmap | null,\n srgb: boolean,\n sampler: GPUSampler,\n generateMipmaps: GenerateMipmapsFn,\n fallback?: Uint8Array\n): Texture2D {\n const device = engine._device;\n const w = bitmap?.width ?? 1;\n const h = bitmap?.height ?? 1;\n const fmt: GPUTextureFormat = srgb ? \"rgba8unorm-srgb\" : \"rgba8unorm\";\n const mips = bitmap ? mipLevelCount(w, h) : 1;\n const tex = device.createTexture({\n size: { width: w, height: h },\n format: fmt,\n usage: TU.TEXTURE_BINDING | TU.COPY_DST | TU.COPY_SRC | TU.RENDER_ATTACHMENT,\n mipLevelCount: mips,\n });\n if (bitmap) {\n device.queue.copyExternalImageToTexture({ source: bitmap }, { texture: tex, premultipliedAlpha: false }, { width: w, height: h });\n generateMipmaps(engine, tex);\n } else {\n device.queue.writeTexture({ texture: tex }, (fallback ?? new U8([255, 255, 255, 255])) as Uint8Array<ArrayBuffer>, { bytesPerRow: 4 }, { width: 1, height: 1 });\n }\n const result: Texture2D = {\n texture: tex,\n view: tex.createView(),\n sampler,\n width: w,\n height: h,\n };\n engine._dlr?.b(result, bitmap, srgb, !!bitmap, fallback);\n return result;\n}\n\n/** Assemble a PbrMaterialProps from parsed glTF material data + already-uploaded\n * textures + per-ext fragment overrides. Fast-path: no wrapTex, no occlusionOnUv2,\n * no occlusionTexture. Slow-path additions live in gltf-pbr-builder-ext.ts. */\nexport function assemblePbrProps(\n mat: GltfMaterialData,\n baseColorTexture: Texture2D,\n ormTexture: Texture2D,\n normalTexture: Texture2D | undefined,\n emissiveTexture: Texture2D | undefined,\n extLayers: Partial<PbrMaterialProps> | undefined\n): PbrMaterialProps {\n const ef = mat._emissiveFactor;\n // emissiveFactor multiplies the emissive texture, so [1,1,1] is a no-op WHEN a texture is\n // present. With no emissive texture, [1,1,1] is a real full-white emissive that must be applied\n // (the glTF default is [0,0,0]) — otherwise the surface renders unlit/dark (Material_03).\n const defaultFactor = (ef[0] === 0 && ef[1] === 0 && ef[2] === 0) || (!!emissiveTexture && ef[0] === 1 && ef[1] === 1 && ef[2] === 1);\n return {\n baseColorTexture,\n normalTexture,\n ormTexture,\n emissiveTexture,\n ...(mat._baseColorImage && !isDefaultBaseColorFactor(mat._baseColorFactor) ? { baseColorFactor: mat._baseColorFactor } : undefined),\n doubleSided: mat._doubleSided,\n occlusionStrength: mat._occlusionImage ? 1.0 : 0,\n ...(mat._normalScale !== 1 ? { normalTextureScale: mat._normalScale } : undefined),\n ...(mat._metallicRoughnessImage ? { metallicFactor: mat._metallicFactor, roughnessFactor: mat._roughnessFactor } : undefined),\n ...(!defaultFactor ? { emissiveColor: [ef[0], ef[1], ef[2]] as [number, number, number] } : undefined),\n enableSpecularAA: true,\n ...(mat._alphaMode === \"BLEND\" ? { alphaBlend: true, alpha: mat._baseColorFactor[3] } : undefined),\n ...(mat._alphaMode === \"MASK\" ? { alpha: mat._baseColorFactor[3], alphaCutOff: mat._alphaCutoff } : undefined),\n ...(mat._rawMatDef?.name ? { name: mat._rawMatDef.name as string } : undefined),\n ...extLayers,\n _buildGroup: getPbrGroupBuilder(),\n _uboVersion: 0,\n } as PbrMaterialProps;\n}\n\nfunction isDefaultBaseColorFactor(f: readonly number[]): boolean {\n return f[0] === 1 && f[1] === 1 && f[2] === 1 && f[3] === 1;\n}\n\n/** Build the always-present default textures (base color + ORM) from a parsed glTF material.\n * Fast-path version: no wrapTex, no occlusion-on-uv2 handling. The slow path lives\n * in gltf-pbr-builder-ext.ts and is lazy-loaded only when needed. */\nexport function buildDefaultPbrTextures(\n engine: EngineContext,\n mat: GltfMaterialData,\n sampler: GPUSampler,\n generateMipmaps: GenerateMipmapsFn,\n getCachedTex: (bitmap: ImageBitmap, srgb: boolean) => Texture2D\n): { baseColorTexture: Texture2D; ormTexture: Texture2D; normalTexture: Texture2D | undefined; emissiveTexture: Texture2D | undefined } {\n const baseColorTexture = mat._baseColorImage\n ? getCachedTex(mat._baseColorImage, true)\n : (() => {\n const f = mat._baseColorFactor;\n return uploadTex(\n engine,\n null,\n true,\n sampler,\n generateMipmaps,\n new U8([linearToSrgbByte(f[0]), linearToSrgbByte(f[1]), linearToSrgbByte(f[2]), Math.round(Math.max(0, Math.min(1, f[3])) * 255)])\n );\n })();\n const normalTexture = mat._normalImage ? getCachedTex(mat._normalImage, false) : undefined;\n const emissiveTexture = mat._emissiveImage ? getCachedTex(mat._emissiveImage, true) : undefined;\n\n const single = mat._metallicRoughnessImage ?? mat._occlusionImage;\n let ormTexture: Texture2D;\n if (single && (!mat._metallicRoughnessImage || !mat._occlusionImage || mat._metallicRoughnessImage === mat._occlusionImage)) {\n ormTexture = getCachedTex(single, false);\n } else if (!single) {\n const clamp = (v: number) => Math.round(Math.max(0, Math.min(1, v)) * 255);\n ormTexture = uploadTex(engine, null, false, sampler, generateMipmaps, new U8([255, clamp(mat._roughnessFactor), clamp(mat._metallicFactor), 255]));\n } else {\n ormTexture = getCachedTex(mat._metallicRoughnessImage!, false);\n }\n return { baseColorTexture, ormTexture, normalTexture, emissiveTexture };\n}\n\n/** Run all material-layer features and merge their fragments. */\nexport async function runMatExts(mat: GltfMaterialData, exts: GltfFeature[], ctx: GltfMatExtCtx): Promise<Partial<PbrMaterialProps> | undefined> {\n if (!exts.length) {\n return undefined;\n }\n const fragments = await Promise.all(exts.map((ext) => ext.applyMaterial!(mat, ctx)));\n let layers: Partial<PbrMaterialProps> | undefined;\n for (const f of fragments) {\n if (f) {\n layers ??= {};\n Object.assign(layers, f);\n }\n }\n return layers;\n}\n"],"names":[],"mappings":";;;;;;AAmBO,MAAM,eAAA,GAAiC,CAAC,GAAA,KAAQ;AAIhD,SAAS,UACZ,MAAA,EACA,MAAA,EACA,IAAA,EACA,OAAA,EACA,iBACA,QAAA,EACS;AACT,EAAA,MAAM,SAAS,MAAA,CAAO,OAAA;AACtB,EAAA,MAAM,CAAA,GAAI,QAAQ,KAAA,IAAS,CAAA;AAC3B,EAAA,MAAM,CAAA,GAAI,QAAQ,MAAA,IAAU,CAAA;AAC5B,EAAA,MAAM,GAAA,GAAwB,OAAO,iBAAA,GAAoB,YAAA;AACzD,EAAA,MAAM,IAAA,GAAO,MAAA,GAAS,aAAA,CAAc,CAAA,EAAG,CAAC,CAAA,GAAI,CAAA;AAC5C,EAAA,MAAM,GAAA,GAAM,OAAO,aAAA,CAAc;AAAA,IAC7B,IAAA,EAAM,EAAE,KAAA,EAAO,CAAA,EAAG,QAAQ,CAAA,EAAE;AAAA,IAC5B,MAAA,EAAQ,GAAA;AAAA,IACR,OAAO,EAAA,CAAG,eAAA,GAAkB,GAAG,QAAA,GAAW,EAAA,CAAG,WAAW,EAAA,CAAG,iBAAA;AAAA,IAC3D,aAAA,EAAe;AAAA,GAClB,CAAA;AACD,EAAA,IAAI,MAAA,EAAQ;AACR,IAAA,MAAA,CAAO,MAAM,0BAAA,CAA2B,EAAE,MAAA,EAAQ,MAAA,IAAU,EAAE,OAAA,EAAS,GAAA,EAAK,kBAAA,EAAoB,OAAM,EAAG,EAAE,OAAO,CAAA,EAAG,MAAA,EAAQ,GAAG,CAAA;AAChI,IAAA,eAAA,CAAgB,QAAQ,GAAG,CAAA;AAAA,EAC/B,CAAA,MAAO;AACH,IAAA,MAAA,CAAO,KAAA,CAAM,YAAA,CAAa,EAAE,OAAA,EAAS,GAAA,IAAQ,QAAA,IAAY,IAAI,EAAA,CAAG,CAAC,GAAA,EAAK,GAAA,EAAK,KAAK,GAAG,CAAC,CAAA,EAA+B,EAAE,WAAA,EAAa,CAAA,EAAE,EAAG,EAAE,KAAA,EAAO,CAAA,EAAG,MAAA,EAAQ,CAAA,EAAG,CAAA;AAAA,EAClK;AACA,EAAA,MAAM,MAAA,GAAoB;AAAA,IACtB,OAAA,EAAS,GAAA;AAAA,IACT,IAAA,EAAM,IAAI,UAAA,EAAW;AAAA,IACrB,OAAA;AAAA,IACA,KAAA,EAAO,CAAA;AAAA,IACP,MAAA,EAAQ;AAAA,GACZ;AACA,EAAA,MAAA,CAAO,IAAA,EAAM,EAAE,MAAA,EAAQ,MAAA,EAAQ,MAAM,CAAC,CAAC,QAAQ,QAAQ,CAAA;AACvD,EAAA,OAAO,MAAA;AACX;AAKO,SAAS,iBACZ,GAAA,EACA,gBAAA,EACA,UAAA,EACA,aAAA,EACA,iBACA,SAAA,EACgB;AAChB,EAAA,MAAM,KAAK,GAAA,CAAI,eAAA;AAIf,EAAA,MAAM,aAAA,GAAiB,EAAA,CAAG,CAAC,CAAA,KAAM,CAAA,IAAK,EAAA,CAAG,CAAC,CAAA,KAAM,CAAA,IAAK,EAAA,CAAG,CAAC,CAAA,KAAM,CAAA,IAAO,CAAC,CAAC,eAAA,IAAmB,EAAA,CAAG,CAAC,CAAA,KAAM,CAAA,IAAK,EAAA,CAAG,CAAC,CAAA,KAAM,CAAA,IAAK,EAAA,CAAG,CAAC,CAAA,KAAM,CAAA;AACnI,EAAA,OAAO;AAAA,IACH,gBAAA;AAAA,IACA,aAAA;AAAA,IACA,UAAA;AAAA,IACA,eAAA;AAAA,IACA,GAAI,GAAA,CAAI,eAAA,IAAmB,CAAC,wBAAA,CAAyB,GAAA,CAAI,gBAAgB,CAAA,GAAI,EAAE,eAAA,EAAiB,GAAA,CAAI,gBAAA,EAAiB,GAAI,MAAA;AAAA,IACzH,aAAa,GAAA,CAAI,YAAA;AAAA,IACjB,iBAAA,EAAmB,GAAA,CAAI,eAAA,GAAkB,CAAA,GAAM,CAAA;AAAA,IAC/C,GAAI,IAAI,YAAA,KAAiB,CAAA,GAAI,EAAE,kBAAA,EAAoB,GAAA,CAAI,cAAa,GAAI,MAAA;AAAA,IACxE,GAAI,GAAA,CAAI,uBAAA,GAA0B,EAAE,cAAA,EAAgB,IAAI,eAAA,EAAiB,eAAA,EAAiB,GAAA,CAAI,gBAAA,EAAiB,GAAI,MAAA;AAAA,IACnH,GAAI,CAAC,aAAA,GAAgB,EAAE,aAAA,EAAe,CAAC,EAAA,CAAG,CAAC,CAAA,EAAG,EAAA,CAAG,CAAC,CAAA,EAAG,EAAA,CAAG,CAAC,CAAC,GAA8B,GAAI,MAAA;AAAA,IAC5F,gBAAA,EAAkB,IAAA;AAAA,IAClB,GAAI,GAAA,CAAI,UAAA,KAAe,OAAA,GAAU,EAAE,UAAA,EAAY,IAAA,EAAM,KAAA,EAAO,GAAA,CAAI,gBAAA,CAAiB,CAAC,CAAA,EAAE,GAAI,MAAA;AAAA,IACxF,GAAI,GAAA,CAAI,UAAA,KAAe,MAAA,GAAS,EAAE,KAAA,EAAO,GAAA,CAAI,gBAAA,CAAiB,CAAC,CAAA,EAAG,WAAA,EAAa,GAAA,CAAI,cAAa,GAAI,MAAA;AAAA,IACpG,GAAI,IAAI,UAAA,EAAY,IAAA,GAAO,EAAE,IAAA,EAAM,GAAA,CAAI,UAAA,CAAW,IAAA,EAAe,GAAI,MAAA;AAAA,IACrE,GAAG,SAAA;AAAA,IACH,aAAa,kBAAA,EAAmB;AAAA,IAChC,WAAA,EAAa;AAAA,GACjB;AACJ;AAEA,SAAS,yBAAyB,CAAA,EAA+B;AAC7D,EAAA,OAAO,CAAA,CAAE,CAAC,CAAA,KAAM,CAAA,IAAK,EAAE,CAAC,CAAA,KAAM,CAAA,IAAK,CAAA,CAAE,CAAC,CAAA,KAAM,CAAA,IAAK,CAAA,CAAE,CAAC,CAAA,KAAM,CAAA;AAC9D;AAKO,SAAS,uBAAA,CACZ,MAAA,EACA,GAAA,EACA,OAAA,EACA,iBACA,YAAA,EACoI;AACpI,EAAA,MAAM,gBAAA,GAAmB,IAAI,eAAA,GACvB,YAAA,CAAa,IAAI,eAAA,EAAiB,IAAI,KACrC,MAAM;AACH,IAAA,MAAM,IAAI,GAAA,CAAI,gBAAA;AACd,IAAA,OAAO,SAAA;AAAA,MACH,MAAA;AAAA,MACA,IAAA;AAAA,MACA,IAAA;AAAA,MACA,OAAA;AAAA,MACA,eAAA;AAAA,MACA,IAAI,EAAA,CAAG,CAAC,gBAAA,CAAiB,EAAE,CAAC,CAAC,CAAA,EAAG,gBAAA,CAAiB,CAAA,CAAE,CAAC,CAAC,CAAA,EAAG,iBAAiB,CAAA,CAAE,CAAC,CAAC,CAAA,EAAG,IAAA,CAAK,KAAA,CAAM,IAAA,CAAK,GAAA,CAAI,GAAG,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,CAAA,CAAE,CAAC,CAAC,CAAC,CAAA,GAAI,GAAG,CAAC,CAAC;AAAA,KACrI;AAAA,EACJ,CAAA,GAAG;AACT,EAAA,MAAM,gBAAgB,GAAA,CAAI,YAAA,GAAe,aAAa,GAAA,CAAI,YAAA,EAAc,KAAK,CAAA,GAAI,MAAA;AACjF,EAAA,MAAM,kBAAkB,GAAA,CAAI,cAAA,GAAiB,aAAa,GAAA,CAAI,cAAA,EAAgB,IAAI,CAAA,GAAI,MAAA;AAEtF,EAAA,MAAM,MAAA,GAAS,GAAA,CAAI,uBAAA,IAA2B,GAAA,CAAI,eAAA;AAClD,EAAA,IAAI,UAAA;AACJ,EAAA,IAAI,MAAA,KAAW,CAAC,GAAA,CAAI,uBAAA,IAA2B,CAAC,IAAI,eAAA,IAAmB,GAAA,CAAI,uBAAA,KAA4B,GAAA,CAAI,eAAA,CAAA,EAAkB;AACzH,IAAA,UAAA,GAAa,YAAA,CAAa,QAAQ,KAAK,CAAA;AAAA,EAC3C,CAAA,MAAA,IAAW,CAAC,MAAA,EAAQ;AAChB,IAAA,MAAM,KAAA,GAAQ,CAAC,CAAA,KAAc,IAAA,CAAK,MAAM,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,CAAC,CAAC,IAAI,GAAG,CAAA;AACzE,IAAA,UAAA,GAAa,SAAA,CAAU,QAAQ,IAAA,EAAM,KAAA,EAAO,SAAS,eAAA,EAAiB,IAAI,GAAG,CAAC,GAAA,EAAK,MAAM,GAAA,CAAI,gBAAgB,GAAG,KAAA,CAAM,GAAA,CAAI,eAAe,CAAA,EAAG,GAAG,CAAC,CAAC,CAAA;AAAA,EACrJ,CAAA,MAAO;AACH,IAAA,UAAA,GAAa,YAAA,CAAa,GAAA,CAAI,uBAAA,EAA0B,KAAK,CAAA;AAAA,EACjE;AACA,EAAA,OAAO,EAAE,gBAAA,EAAkB,UAAA,EAAY,aAAA,EAAe,eAAA,EAAgB;AAC1E;AAGA,eAAsB,UAAA,CAAW,GAAA,EAAuB,IAAA,EAAqB,GAAA,EAAoE;AAC7I,EAAA,IAAI,CAAC,KAAK,MAAA,EAAQ;AACd,IAAA,OAAO,MAAA;AAAA,EACX;AACA,EAAA,MAAM,SAAA,GAAY,MAAM,OAAA,CAAQ,GAAA,CAAI,IAAA,CAAK,GAAA,CAAI,CAAC,GAAA,KAAQ,GAAA,CAAI,aAAA,CAAe,GAAA,EAAK,GAAG,CAAC,CAAC,CAAA;AACnF,EAAA,IAAI,MAAA;AACJ,EAAA,KAAA,MAAW,KAAK,SAAA,EAAW;AACvB,IAAA,IAAI,CAAA,EAAG;AACH,MAAA,MAAA,KAAW,EAAC;AACZ,MAAA,MAAA,CAAO,MAAA,CAAO,QAAQ,CAAC,CAAA;AAAA,IAC3B;AAAA,EACJ;AACA,EAAA,OAAO,MAAA;AACX;;;;"}
@@ -0,0 +1,20 @@
1
+ import { F32, I8, I16, U8 } from '../engine/typed-arrays.js';
2
+ import { _installSamplerConverter } from './gltf-animation.js';
3
+
4
+ _installSamplerConverter((src, length, normalized) => {
5
+ if (src instanceof F32) {
6
+ return new F32(src.buffer, src.byteOffset, length);
7
+ }
8
+ const out = new F32(length);
9
+ const div = src instanceof I8 ? 127 : src instanceof I16 ? 32767 : src instanceof U8 ? 255 : 65535;
10
+ const signed = src instanceof I8 || src instanceof I16;
11
+ const s = src;
12
+ for (let i = 0; i < length; i++) {
13
+ out[i] = normalized ? signed ? Math.max(s[i] / div, -1) : s[i] / div : s[i];
14
+ }
15
+ return out;
16
+ });
17
+ const feature = { id: "_sampler_denorm" };
18
+
19
+ export { feature as default };
20
+ //# sourceMappingURL=gltf-sampler-denorm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gltf-sampler-denorm.js","sources":["../../../src/loader-gltf/gltf-sampler-denorm.ts"],"sourcesContent":["/** Lazily-installed animation-sampler converter for non-Float32 / normalized accessor payloads:\n * normalized signed BYTE/SHORT rotation output (glTF-Asset-Generator Animation_SamplerType),\n * normalized UNSIGNED_BYTE flags, misaligned Float32, etc. Imported for side effect only when an\n * animation sampler is non-float (registry trigger) or by KHR_animation_pointer, so plain\n * float-sampler animations never bundle this denormalization code and stay byte-identical. */\nimport { F32, U8, I8, I16 } from \"../engine/typed-arrays.js\";\nimport type { GltfFeature } from \"./gltf-feature.js\";\nimport { _installSamplerConverter } from \"./gltf-animation.js\";\n\n_installSamplerConverter((src, length, normalized) => {\n // Aligned Float32 fast path (also covers the misaligned-reinterpret case the default handled).\n if (src instanceof F32) {\n return new F32(src.buffer, src.byteOffset, length);\n }\n const out = new F32(length);\n const div = src instanceof I8 ? 127 : src instanceof I16 ? 32767 : src instanceof U8 ? 255 : 65535;\n const signed = src instanceof I8 || src instanceof I16;\n const s = src as unknown as { [i: number]: number };\n for (let i = 0; i < length; i++) {\n out[i] = normalized ? (signed ? Math.max(s[i]! / div, -1) : s[i]! / div) : s[i]!;\n }\n return out;\n});\n\n// Hookless feature: registered in the registry so loadGltfFeatures imports this module (installing\n// the converter above as a side effect). The empty default keeps `mods.map(m => m.default)` valid.\nconst feature: GltfFeature = { id: \"_sampler_denorm\" };\nexport default feature;\n"],"names":[],"mappings":";;;AASA,wBAAA,CAAyB,CAAC,GAAA,EAAK,MAAA,EAAQ,UAAA,KAAe;AAElD,EAAA,IAAI,eAAe,GAAA,EAAK;AACpB,IAAA,OAAO,IAAI,GAAA,CAAI,GAAA,CAAI,MAAA,EAAQ,GAAA,CAAI,YAAY,MAAM,CAAA;AAAA,EACrD;AACA,EAAA,MAAM,GAAA,GAAM,IAAI,GAAA,CAAI,MAAM,CAAA;AAC1B,EAAA,MAAM,GAAA,GAAM,eAAe,EAAA,GAAK,GAAA,GAAM,eAAe,GAAA,GAAM,KAAA,GAAQ,GAAA,YAAe,EAAA,GAAK,GAAA,GAAM,KAAA;AAC7F,EAAA,MAAM,MAAA,GAAS,GAAA,YAAe,EAAA,IAAM,GAAA,YAAe,GAAA;AACnD,EAAA,MAAM,CAAA,GAAI,GAAA;AACV,EAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,MAAA,EAAQ,CAAA,EAAA,EAAK;AAC7B,IAAA,GAAA,CAAI,CAAC,CAAA,GAAI,UAAA,GAAc,MAAA,GAAS,IAAA,CAAK,IAAI,CAAA,CAAE,CAAC,CAAA,GAAK,GAAA,EAAK,EAAE,CAAA,GAAI,CAAA,CAAE,CAAC,CAAA,GAAK,GAAA,GAAO,EAAE,CAAC,CAAA;AAAA,EAClF;AACA,EAAA,OAAO,GAAA;AACX,CAAC,CAAA;AAID,MAAM,OAAA,GAAuB,EAAE,EAAA,EAAI,iBAAA;;;;"}
@@ -9,6 +9,7 @@ function gltfTexSamplerDesc(json, texInfo) {
9
9
  const minF = s?.minFilter;
10
10
  const minNearest = minF === 9728 || minF === 9984 || minF === 9986;
11
11
  const mipNearest = minF === 9984 || minF === 9985;
12
+ const noMip = minF === 9728 || minF === 9729;
12
13
  const magLinear = s?.magFilter !== 9728;
13
14
  return {
14
15
  magFilter: magLinear ? "linear" : "nearest",
@@ -16,13 +17,21 @@ function gltfTexSamplerDesc(json, texInfo) {
16
17
  mipmapFilter: mipNearest ? "nearest" : "linear",
17
18
  addressModeU: wrap(s?.wrapS),
18
19
  addressModeV: wrap(s?.wrapT),
20
+ ...noMip ? { lodMaxClamp: 0 } : void 0,
19
21
  // WebGPU forbids anisotropy unless mag/min/mip filters are ALL linear; gate on
20
22
  // every filter (incl. mipNearest, e.g. glTF LINEAR_MIPMAP_NEAREST) or createSampler throws.
21
- maxAnisotropy: magLinear && !minNearest && !mipNearest ? 4 : 1
23
+ // Also disable it for the clamped-mip path (single LOD → anisotropy is meaningless).
24
+ maxAnisotropy: magLinear && !minNearest && !mipNearest && !noMip ? 4 : 1
22
25
  };
23
26
  }
24
27
  function makeSamplerFor(engine, json, defaultSampler) {
25
- return (texInfo) => texInfo == null ? defaultSampler : getOrCreateSampler(engine, gltfTexSamplerDesc(json, texInfo));
28
+ return (texInfo) => {
29
+ if (texInfo == null) {
30
+ return defaultSampler;
31
+ }
32
+ const desc = gltfTexSamplerDesc(json, texInfo);
33
+ return desc.lodMaxClamp === 0 ? engine._device.createSampler(desc) : getOrCreateSampler(engine, desc);
34
+ };
26
35
  }
27
36
  function buildSampledPbrTextures(engine, mat, defaultSampler, generateMipmaps, samplerFor, getCachedTex) {
28
37
  const def = mat._rawMatDef ?? {};
@@ -1 +1 @@
1
- {"version":3,"file":"gltf-sampler-desc.js","sources":["../../../src/loader-gltf/gltf-sampler-desc.ts"],"sourcesContent":["import { getOrCreateSampler } from \"../resource/gpu-pool.js\";\nimport { U8 } from \"../engine/typed-arrays.js\";\nimport { linearToSrgbByte } from \"../math/color.js\";\nimport { uploadTex } from \"./gltf-pbr-builder.js\";\nimport type { GenerateMipmapsFn } from \"./gltf-pbr-builder.js\";\nimport type { EngineContext } from \"../engine/engine.js\";\nimport type { Texture2D } from \"../texture/texture-2d.js\";\nimport type { GltfMaterialData } from \"./gltf-material.js\";\n\n/** Map a glTF textureInfo's sampler (wrapS/wrapT/magFilter/minFilter) to a WebGPU sampler\n * descriptor. glTF wrap: 33071 CLAMP_TO_EDGE, 33648 MIRRORED_REPEAT, else REPEAT.\n * glTF filter: 9728 NEAREST else LINEAR; min/mip from the combined min filter enum. */\nfunction gltfTexSamplerDesc(json: any, texInfo: any): GPUSamplerDescriptor {\n const s = json.textures?.[texInfo.index]?.sampler != null ? json.samplers?.[json.textures[texInfo.index].sampler] : undefined;\n const wrap = (m: number | undefined): GPUAddressMode => (m === 33071 ? \"clamp-to-edge\" : m === 33648 ? \"mirror-repeat\" : \"repeat\");\n const minF: number | undefined = s?.minFilter;\n const minNearest = minF === 9728 || minF === 9984 || minF === 9986;\n const mipNearest = minF === 9984 || minF === 9985;\n const magLinear = s?.magFilter !== 9728;\n return {\n magFilter: magLinear ? \"linear\" : \"nearest\",\n minFilter: minNearest ? \"nearest\" : \"linear\",\n mipmapFilter: mipNearest ? \"nearest\" : \"linear\",\n addressModeU: wrap(s?.wrapS),\n addressModeV: wrap(s?.wrapT),\n // WebGPU forbids anisotropy unless mag/min/mip filters are ALL linear; gate on\n // every filter (incl. mipNearest, e.g. glTF LINEAR_MIPMAP_NEAREST) or createSampler throws.\n maxAnisotropy: magLinear && !minNearest && !mipNearest ? 4 : 1,\n };\n}\n\n/** Build a per-texture sampler resolver honoring each texture's glTF sampler\n * (wrap/filter). Loaded lazily only when an asset declares a non-default sampler;\n * the common case (default repeat/linear) uses one shared sampler and never loads this.\n * `texInfo == null` (factor textures) falls back to `defaultSampler`.\n * @internal */\nexport function makeSamplerFor(engine: EngineContext, json: any, defaultSampler: GPUSampler): (texInfo: any) => GPUSampler {\n return (texInfo: any): GPUSampler => (texInfo == null ? defaultSampler : getOrCreateSampler(engine, gltfTexSamplerDesc(json, texInfo)));\n}\n\n/** Sampler-aware variant of buildDefaultPbrTextures. Mirrors the core fast path but wraps\n * each shared GPU texture with the sampler resolved from its glTF textureInfo (wrap/filter),\n * so clamp/mirror/nearest assets render correctly without re-uploading identical images.\n * Lazy-loaded only for non-default-sampler assets — the common path stays byte-identical.\n * @internal */\nexport function buildSampledPbrTextures(\n engine: EngineContext,\n mat: GltfMaterialData,\n defaultSampler: GPUSampler,\n generateMipmaps: GenerateMipmapsFn,\n samplerFor: (texInfo: any) => GPUSampler,\n getCachedTex: (bitmap: ImageBitmap, srgb: boolean) => Texture2D\n): { baseColorTexture: Texture2D; ormTexture: Texture2D; normalTexture: Texture2D | undefined; emissiveTexture: Texture2D | undefined } {\n const def = mat._rawMatDef ?? {};\n const pbr = def.pbrMetallicRoughness ?? {};\n const cached = (bitmap: ImageBitmap, srgb: boolean, texInfo: any): Texture2D => {\n const s = samplerFor(texInfo);\n const tex = getCachedTex(bitmap, srgb);\n return s === defaultSampler ? tex : { ...tex, sampler: s };\n };\n\n const baseColorTexture = mat._baseColorImage\n ? cached(mat._baseColorImage, true, pbr.baseColorTexture)\n : (() => {\n const f = mat._baseColorFactor;\n return uploadTex(\n engine,\n null,\n true,\n defaultSampler,\n generateMipmaps,\n new U8([linearToSrgbByte(f[0]), linearToSrgbByte(f[1]), linearToSrgbByte(f[2]), Math.round(Math.max(0, Math.min(1, f[3])) * 255)])\n );\n })();\n const normalTexture = mat._normalImage ? cached(mat._normalImage, false, def.normalTexture) : undefined;\n const emissiveTexture = mat._emissiveImage ? cached(mat._emissiveImage, true, def.emissiveTexture) : undefined;\n\n const single = mat._metallicRoughnessImage ?? mat._occlusionImage;\n const ormTexInfo = mat._metallicRoughnessImage ? pbr.metallicRoughnessTexture : def.occlusionTexture;\n let ormTexture: Texture2D;\n if (single && (!mat._metallicRoughnessImage || !mat._occlusionImage || mat._metallicRoughnessImage === mat._occlusionImage)) {\n ormTexture = cached(single, false, ormTexInfo);\n } else if (!single) {\n const clamp = (v: number) => Math.round(Math.max(0, Math.min(1, v)) * 255);\n ormTexture = uploadTex(engine, null, false, defaultSampler, generateMipmaps, new U8([255, clamp(mat._roughnessFactor), clamp(mat._metallicFactor), 255]));\n } else {\n ormTexture = cached(mat._metallicRoughnessImage!, false, pbr.metallicRoughnessTexture);\n }\n return { baseColorTexture, ormTexture, normalTexture, emissiveTexture };\n}\n"],"names":[],"mappings":";;;;;AAYA,SAAS,kBAAA,CAAmB,MAAW,OAAA,EAAoC;AACvE,EAAA,MAAM,IAAI,IAAA,CAAK,QAAA,GAAW,OAAA,CAAQ,KAAK,GAAG,OAAA,IAAW,IAAA,GAAO,IAAA,CAAK,QAAA,GAAW,KAAK,QAAA,CAAS,OAAA,CAAQ,KAAK,CAAA,CAAE,OAAO,CAAA,GAAI,MAAA;AACpH,EAAA,MAAM,IAAA,GAAO,CAAC,CAAA,KAA2C,CAAA,KAAM,QAAQ,eAAA,GAAkB,CAAA,KAAM,QAAQ,eAAA,GAAkB,QAAA;AACzH,EAAA,MAAM,OAA2B,CAAA,EAAG,SAAA;AACpC,EAAA,MAAM,UAAA,GAAa,IAAA,KAAS,IAAA,IAAQ,IAAA,KAAS,QAAQ,IAAA,KAAS,IAAA;AAC9D,EAAA,MAAM,UAAA,GAAa,IAAA,KAAS,IAAA,IAAQ,IAAA,KAAS,IAAA;AAC7C,EAAA,MAAM,SAAA,GAAY,GAAG,SAAA,KAAc,IAAA;AACnC,EAAA,OAAO;AAAA,IACH,SAAA,EAAW,YAAY,QAAA,GAAW,SAAA;AAAA,IAClC,SAAA,EAAW,aAAa,SAAA,GAAY,QAAA;AAAA,IACpC,YAAA,EAAc,aAAa,SAAA,GAAY,QAAA;AAAA,IACvC,YAAA,EAAc,IAAA,CAAK,CAAA,EAAG,KAAK,CAAA;AAAA,IAC3B,YAAA,EAAc,IAAA,CAAK,CAAA,EAAG,KAAK,CAAA;AAAA;AAAA;AAAA,IAG3B,eAAe,SAAA,IAAa,CAAC,UAAA,IAAc,CAAC,aAAa,CAAA,GAAI;AAAA,GACjE;AACJ;AAOO,SAAS,cAAA,CAAe,MAAA,EAAuB,IAAA,EAAW,cAAA,EAA0D;AACvH,EAAA,OAAO,CAAC,OAAA,KAA8B,OAAA,IAAW,IAAA,GAAO,cAAA,GAAiB,mBAAmB,MAAA,EAAQ,kBAAA,CAAmB,IAAA,EAAM,OAAO,CAAC,CAAA;AACzI;AAOO,SAAS,wBACZ,MAAA,EACA,GAAA,EACA,cAAA,EACA,eAAA,EACA,YACA,YAAA,EACoI;AACpI,EAAA,MAAM,GAAA,GAAM,GAAA,CAAI,UAAA,IAAc,EAAC;AAC/B,EAAA,MAAM,GAAA,GAAM,GAAA,CAAI,oBAAA,IAAwB,EAAC;AACzC,EAAA,MAAM,MAAA,GAAS,CAAC,MAAA,EAAqB,IAAA,EAAe,OAAA,KAA4B;AAC5E,IAAA,MAAM,CAAA,GAAI,WAAW,OAAO,CAAA;AAC5B,IAAA,MAAM,GAAA,GAAM,YAAA,CAAa,MAAA,EAAQ,IAAI,CAAA;AACrC,IAAA,OAAO,MAAM,cAAA,GAAiB,GAAA,GAAM,EAAE,GAAG,GAAA,EAAK,SAAS,CAAA,EAAE;AAAA,EAC7D,CAAA;AAEA,EAAA,MAAM,gBAAA,GAAmB,GAAA,CAAI,eAAA,GACvB,MAAA,CAAO,GAAA,CAAI,iBAAiB,IAAA,EAAM,GAAA,CAAI,gBAAgB,CAAA,GAAA,CACrD,MAAM;AACH,IAAA,MAAM,IAAI,GAAA,CAAI,gBAAA;AACd,IAAA,OAAO,SAAA;AAAA,MACH,MAAA;AAAA,MACA,IAAA;AAAA,MACA,IAAA;AAAA,MACA,cAAA;AAAA,MACA,eAAA;AAAA,MACA,IAAI,EAAA,CAAG,CAAC,gBAAA,CAAiB,EAAE,CAAC,CAAC,CAAA,EAAG,gBAAA,CAAiB,CAAA,CAAE,CAAC,CAAC,CAAA,EAAG,iBAAiB,CAAA,CAAE,CAAC,CAAC,CAAA,EAAG,IAAA,CAAK,KAAA,CAAM,IAAA,CAAK,GAAA,CAAI,GAAG,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,CAAA,CAAE,CAAC,CAAC,CAAC,CAAA,GAAI,GAAG,CAAC,CAAC;AAAA,KACrI;AAAA,EACJ,CAAA,GAAG;AACT,EAAA,MAAM,aAAA,GAAgB,IAAI,YAAA,GAAe,MAAA,CAAO,IAAI,YAAA,EAAc,KAAA,EAAO,GAAA,CAAI,aAAa,CAAA,GAAI,MAAA;AAC9F,EAAA,MAAM,eAAA,GAAkB,IAAI,cAAA,GAAiB,MAAA,CAAO,IAAI,cAAA,EAAgB,IAAA,EAAM,GAAA,CAAI,eAAe,CAAA,GAAI,MAAA;AAErG,EAAA,MAAM,MAAA,GAAS,GAAA,CAAI,uBAAA,IAA2B,GAAA,CAAI,eAAA;AAClD,EAAA,MAAM,UAAA,GAAa,GAAA,CAAI,uBAAA,GAA0B,GAAA,CAAI,2BAA2B,GAAA,CAAI,gBAAA;AACpF,EAAA,IAAI,UAAA;AACJ,EAAA,IAAI,MAAA,KAAW,CAAC,GAAA,CAAI,uBAAA,IAA2B,CAAC,IAAI,eAAA,IAAmB,GAAA,CAAI,uBAAA,KAA4B,GAAA,CAAI,eAAA,CAAA,EAAkB;AACzH,IAAA,UAAA,GAAa,MAAA,CAAO,MAAA,EAAQ,KAAA,EAAO,UAAU,CAAA;AAAA,EACjD,CAAA,MAAA,IAAW,CAAC,MAAA,EAAQ;AAChB,IAAA,MAAM,KAAA,GAAQ,CAAC,CAAA,KAAc,IAAA,CAAK,MAAM,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,CAAC,CAAC,IAAI,GAAG,CAAA;AACzE,IAAA,UAAA,GAAa,SAAA,CAAU,QAAQ,IAAA,EAAM,KAAA,EAAO,gBAAgB,eAAA,EAAiB,IAAI,GAAG,CAAC,GAAA,EAAK,MAAM,GAAA,CAAI,gBAAgB,GAAG,KAAA,CAAM,GAAA,CAAI,eAAe,CAAA,EAAG,GAAG,CAAC,CAAC,CAAA;AAAA,EAC5J,CAAA,MAAO;AACH,IAAA,UAAA,GAAa,MAAA,CAAO,GAAA,CAAI,uBAAA,EAA0B,KAAA,EAAO,IAAI,wBAAwB,CAAA;AAAA,EACzF;AACA,EAAA,OAAO,EAAE,gBAAA,EAAkB,UAAA,EAAY,aAAA,EAAe,eAAA,EAAgB;AAC1E;;;;"}
1
+ {"version":3,"file":"gltf-sampler-desc.js","sources":["../../../src/loader-gltf/gltf-sampler-desc.ts"],"sourcesContent":["import { getOrCreateSampler } from \"../resource/gpu-pool.js\";\nimport { U8 } from \"../engine/typed-arrays.js\";\nimport { linearToSrgbByte } from \"../math/color.js\";\nimport { uploadTex } from \"./gltf-pbr-builder.js\";\nimport type { GenerateMipmapsFn } from \"./gltf-pbr-builder.js\";\nimport type { EngineContext } from \"../engine/engine.js\";\nimport type { Texture2D } from \"../texture/texture-2d.js\";\nimport type { GltfMaterialData } from \"./gltf-material.js\";\n\n/** Map a glTF textureInfo's sampler (wrapS/wrapT/magFilter/minFilter) to a WebGPU sampler\n * descriptor. glTF wrap: 33071 CLAMP_TO_EDGE, 33648 MIRRORED_REPEAT, else REPEAT.\n * glTF filter: 9728 NEAREST else LINEAR; min/mip from the combined min filter enum. */\nfunction gltfTexSamplerDesc(json: any, texInfo: any): GPUSamplerDescriptor {\n const s = json.textures?.[texInfo.index]?.sampler != null ? json.samplers?.[json.textures[texInfo.index].sampler] : undefined;\n const wrap = (m: number | undefined): GPUAddressMode => (m === 33071 ? \"clamp-to-edge\" : m === 33648 ? \"mirror-repeat\" : \"repeat\");\n const minF: number | undefined = s?.minFilter;\n const minNearest = minF === 9728 || minF === 9984 || minF === 9986;\n const mipNearest = minF === 9984 || minF === 9985;\n // glTF non-mipmap min filters (9728 NEAREST, 9729 LINEAR) mean \"sample mip 0 only\".\n // The shared uploaded GPU texture always carries a full mip chain, so clamp the LOD\n // to 0 for these filters (matching BJS `noMipmap`); otherwise a minified texture\n // (e.g. small SDF text in a top-down view) would sample blurred mips and render\n // softer/darker than BJS. Mipmapped filters (9984–9987) leave LOD unclamped.\n const noMip = minF === 9728 || minF === 9729;\n const magLinear = s?.magFilter !== 9728;\n return {\n magFilter: magLinear ? \"linear\" : \"nearest\",\n minFilter: minNearest ? \"nearest\" : \"linear\",\n mipmapFilter: mipNearest ? \"nearest\" : \"linear\",\n addressModeU: wrap(s?.wrapS),\n addressModeV: wrap(s?.wrapT),\n ...(noMip ? { lodMaxClamp: 0 } : undefined),\n // WebGPU forbids anisotropy unless mag/min/mip filters are ALL linear; gate on\n // every filter (incl. mipNearest, e.g. glTF LINEAR_MIPMAP_NEAREST) or createSampler throws.\n // Also disable it for the clamped-mip path (single LOD → anisotropy is meaningless).\n maxAnisotropy: magLinear && !minNearest && !mipNearest && !noMip ? 4 : 1,\n };\n}\n\n/** Build a per-texture sampler resolver honoring each texture's glTF sampler\n * (wrap/filter). Loaded lazily only when an asset declares a non-default sampler;\n * the common case (default repeat/linear) uses one shared sampler and never loads this.\n * `texInfo == null` (factor textures) falls back to `defaultSampler`.\n * @internal */\nexport function makeSamplerFor(engine: EngineContext, json: any, defaultSampler: GPUSampler): (texInfo: any) => GPUSampler {\n return (texInfo: any): GPUSampler => {\n if (texInfo == null) {\n return defaultSampler;\n }\n const desc = gltfTexSamplerDesc(json, texInfo);\n // A non-mipmap sampler (lodMaxClamp 0) is created directly: the shared cache key omits\n // the LOD clamp, so caching it there could alias a full-mip sampler with identical\n // filter/wrap. These are rare (SDF/UI textures), so per-call creation is cheaper than\n // growing the universal sampler key — which would move every non-glTF scene's bundle.\n return desc.lodMaxClamp === 0 ? engine._device.createSampler(desc) : getOrCreateSampler(engine, desc);\n };\n}\n\n/** Sampler-aware variant of buildDefaultPbrTextures. Mirrors the core fast path but wraps\n * each shared GPU texture with the sampler resolved from its glTF textureInfo (wrap/filter),\n * so clamp/mirror/nearest assets render correctly without re-uploading identical images.\n * Lazy-loaded only for non-default-sampler assets — the common path stays byte-identical.\n * @internal */\nexport function buildSampledPbrTextures(\n engine: EngineContext,\n mat: GltfMaterialData,\n defaultSampler: GPUSampler,\n generateMipmaps: GenerateMipmapsFn,\n samplerFor: (texInfo: any) => GPUSampler,\n getCachedTex: (bitmap: ImageBitmap, srgb: boolean) => Texture2D\n): { baseColorTexture: Texture2D; ormTexture: Texture2D; normalTexture: Texture2D | undefined; emissiveTexture: Texture2D | undefined } {\n const def = mat._rawMatDef ?? {};\n const pbr = def.pbrMetallicRoughness ?? {};\n const cached = (bitmap: ImageBitmap, srgb: boolean, texInfo: any): Texture2D => {\n const s = samplerFor(texInfo);\n const tex = getCachedTex(bitmap, srgb);\n return s === defaultSampler ? tex : { ...tex, sampler: s };\n };\n\n const baseColorTexture = mat._baseColorImage\n ? cached(mat._baseColorImage, true, pbr.baseColorTexture)\n : (() => {\n const f = mat._baseColorFactor;\n return uploadTex(\n engine,\n null,\n true,\n defaultSampler,\n generateMipmaps,\n new U8([linearToSrgbByte(f[0]), linearToSrgbByte(f[1]), linearToSrgbByte(f[2]), Math.round(Math.max(0, Math.min(1, f[3])) * 255)])\n );\n })();\n const normalTexture = mat._normalImage ? cached(mat._normalImage, false, def.normalTexture) : undefined;\n const emissiveTexture = mat._emissiveImage ? cached(mat._emissiveImage, true, def.emissiveTexture) : undefined;\n\n const single = mat._metallicRoughnessImage ?? mat._occlusionImage;\n const ormTexInfo = mat._metallicRoughnessImage ? pbr.metallicRoughnessTexture : def.occlusionTexture;\n let ormTexture: Texture2D;\n if (single && (!mat._metallicRoughnessImage || !mat._occlusionImage || mat._metallicRoughnessImage === mat._occlusionImage)) {\n ormTexture = cached(single, false, ormTexInfo);\n } else if (!single) {\n const clamp = (v: number) => Math.round(Math.max(0, Math.min(1, v)) * 255);\n ormTexture = uploadTex(engine, null, false, defaultSampler, generateMipmaps, new U8([255, clamp(mat._roughnessFactor), clamp(mat._metallicFactor), 255]));\n } else {\n ormTexture = cached(mat._metallicRoughnessImage!, false, pbr.metallicRoughnessTexture);\n }\n return { baseColorTexture, ormTexture, normalTexture, emissiveTexture };\n}\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ import { F32 } from '../engine/typed-arrays.js';
2
+
3
+ const FLOAT = 5126;
4
+ const UNSIGNED_SHORT = 5123;
5
+ const UNSIGNED_BYTE = 5121;
6
+ const COMP_BYTES = { [UNSIGNED_BYTE]: 1, [UNSIGNED_SHORT]: 2, [FLOAT]: 4 };
7
+ function resolveUvVec2(json, binChunk, idx) {
8
+ const accessor = json.accessors[idx];
9
+ const ct = accessor.componentType;
10
+ const cb = COMP_BYTES[ct] ?? 4;
11
+ const bv = json.bufferViews[accessor.bufferView];
12
+ const stride = bv.byteStride ?? 2 * cb;
13
+ const inv = ct === UNSIGNED_BYTE ? 1 / 255 : ct === UNSIGNED_SHORT ? 1 / 65535 : 1;
14
+ const base = (bv.byteOffset ?? 0) + (accessor.byteOffset ?? 0);
15
+ const out = new F32(accessor.count * 2);
16
+ for (let v = 0; v < accessor.count; v++) {
17
+ const row = base + v * stride;
18
+ for (let c = 0; c < 2; c++) {
19
+ const off = row + c * cb;
20
+ const raw = ct === FLOAT ? binChunk.getFloat32(off, true) : ct === UNSIGNED_SHORT ? binChunk.getUint16(off, true) : binChunk.getUint8(off);
21
+ out[v * 2 + c] = raw * inv;
22
+ }
23
+ }
24
+ return out;
25
+ }
26
+
27
+ export { resolveUvVec2 };
28
+ //# sourceMappingURL=gltf-uv-denorm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gltf-uv-denorm.js","sources":["../../../src/loader-gltf/gltf-uv-denorm.ts"],"sourcesContent":["/** Denormalize / de-stride a non-Float32 interleaved TEXCOORD_0/_1 accessor to a tight float32 VEC2\n * [0,1] buffer. glTF UVs may be normalized UNSIGNED_BYTE/SHORT and interleaved with a byteStride;\n * bound raw they misalign every vertex (garbage UVs → wrong texturing). Dynamically imported only\n * when an interleaved primitive actually has a non-float UV, so float-UV interleaved meshes (the\n * common case) never bundle this code. Mirrors the tight path's normalizeUvToVec2. */\nimport { F32 } from \"../engine/typed-arrays.js\";\n\nconst FLOAT = 5126;\nconst UNSIGNED_SHORT = 5123;\nconst UNSIGNED_BYTE = 5121;\nconst COMP_BYTES: Record<number, number> = { [UNSIGNED_BYTE]: 1, [UNSIGNED_SHORT]: 2, [FLOAT]: 4 };\n\nexport function resolveUvVec2(json: any, binChunk: DataView, idx: number): Float32Array {\n const accessor = json.accessors[idx];\n const ct = accessor.componentType;\n const cb = COMP_BYTES[ct] ?? 4;\n const bv = json.bufferViews[accessor.bufferView];\n const stride = bv.byteStride ?? 2 * cb;\n const inv = ct === UNSIGNED_BYTE ? 1 / 255 : ct === UNSIGNED_SHORT ? 1 / 65535 : 1;\n const base = (bv.byteOffset ?? 0) + (accessor.byteOffset ?? 0);\n const out = new F32(accessor.count * 2);\n for (let v = 0; v < accessor.count; v++) {\n const row = base + v * stride;\n for (let c = 0; c < 2; c++) {\n const off = row + c * cb;\n const raw = ct === FLOAT ? binChunk.getFloat32(off, true) : ct === UNSIGNED_SHORT ? binChunk.getUint16(off, true) : binChunk.getUint8(off);\n out[v * 2 + c] = raw * inv;\n }\n }\n return out;\n}\n"],"names":[],"mappings":";;AAOA,MAAM,KAAA,GAAQ,IAAA;AACd,MAAM,cAAA,GAAiB,IAAA;AACvB,MAAM,aAAA,GAAgB,IAAA;AACtB,MAAM,UAAA,GAAqC,EAAE,CAAC,aAAa,GAAG,CAAA,EAAG,CAAC,cAAc,GAAG,CAAA,EAAG,CAAC,KAAK,GAAG,CAAA,EAAE;AAE1F,SAAS,aAAA,CAAc,IAAA,EAAW,QAAA,EAAoB,GAAA,EAA2B;AACpF,EAAA,MAAM,QAAA,GAAW,IAAA,CAAK,SAAA,CAAU,GAAG,CAAA;AACnC,EAAA,MAAM,KAAK,QAAA,CAAS,aAAA;AACpB,EAAA,MAAM,EAAA,GAAK,UAAA,CAAW,EAAE,CAAA,IAAK,CAAA;AAC7B,EAAA,MAAM,EAAA,GAAK,IAAA,CAAK,WAAA,CAAY,QAAA,CAAS,UAAU,CAAA;AAC/C,EAAA,MAAM,MAAA,GAAS,EAAA,CAAG,UAAA,IAAc,CAAA,GAAI,EAAA;AACpC,EAAA,MAAM,GAAA,GAAM,OAAO,aAAA,GAAgB,CAAA,GAAI,MAAM,EAAA,KAAO,cAAA,GAAiB,IAAI,KAAA,GAAQ,CAAA;AACjF,EAAA,MAAM,IAAA,GAAA,CAAQ,EAAA,CAAG,UAAA,IAAc,CAAA,KAAM,SAAS,UAAA,IAAc,CAAA,CAAA;AAC5D,EAAA,MAAM,GAAA,GAAM,IAAI,GAAA,CAAI,QAAA,CAAS,QAAQ,CAAC,CAAA;AACtC,EAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,QAAA,CAAS,OAAO,CAAA,EAAA,EAAK;AACrC,IAAA,MAAM,GAAA,GAAM,OAAO,CAAA,GAAI,MAAA;AACvB,IAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,CAAA,EAAG,CAAA,EAAA,EAAK;AACxB,MAAA,MAAM,GAAA,GAAM,MAAM,CAAA,GAAI,EAAA;AACtB,MAAA,MAAM,MAAM,EAAA,KAAO,KAAA,GAAQ,QAAA,CAAS,UAAA,CAAW,KAAK,IAAI,CAAA,GAAI,EAAA,KAAO,cAAA,GAAiB,SAAS,SAAA,CAAU,GAAA,EAAK,IAAI,CAAA,GAAI,QAAA,CAAS,SAAS,GAAG,CAAA;AACzI,MAAA,GAAA,CAAI,CAAA,GAAI,CAAA,GAAI,CAAC,CAAA,GAAI,GAAA,GAAM,GAAA;AAAA,IAC3B;AAAA,EACJ;AACA,EAAA,OAAO,GAAA;AACX;;;;"}
@@ -1,4 +1,4 @@
1
- import { DV, U32, U8, U16, F32 } from '../engine/typed-arrays.js';
1
+ import { DV, F32, U32, U8, U16 } from '../engine/typed-arrays.js';
2
2
  import { BU } from '../engine/gpu-flags.js';
3
3
  import { c as computeAabb } from '../_chunks/__vite-browser-external-BFTKbDcV.js';
4
4
  import { createTransformNode } from '../scene/transform-node.js';
@@ -72,7 +72,7 @@ async function loadGltf(engine, source) {
72
72
  }
73
73
  async function fetchGltfAsset(source) {
74
74
  const isUrl = typeof source === "string";
75
- const baseUrl = isUrl ? source.substring(0, source.lastIndexOf("/") + 1) : "";
75
+ const baseUrl = !isUrl ? "" : typeof location !== "undefined" ? new URL(".", new URL(source, location.href)).href : source.slice(0, source.lastIndexOf("/") + 1);
76
76
  const buffer = isUrl ? await fetch(source).then((r) => r.arrayBuffer()) : source instanceof Blob ? await source.arrayBuffer() : source;
77
77
  if (buffer.byteLength >= 4 && new DV(buffer).getUint32(0, true) === 1179937895) {
78
78
  const glb = await import('./gltf-glb-parser.js');
@@ -82,7 +82,14 @@ async function fetchGltfAsset(source) {
82
82
  return jsonAsset.parseGltfJsonAsset(buffer, baseUrl);
83
83
  }
84
84
  function assetUsesGltfFeatures(json) {
85
- return json.extensionsUsed?.length || json.animations?.length || JSON.stringify(json).includes("extras") || json.skins?.length && anyPrimitive(json, (p) => p.attributes?.JOINTS_0 !== void 0) || anyPrimitive(json, (p) => !!p.targets?.length) || needsOrmComposite(json);
85
+ return json.extensionsUsed?.length || json.animations?.length || JSON.stringify(json).includes("extras") || json.skins?.length && anyPrimitive(json, (p) => p.attributes?.JOINTS_0 !== void 0) || anyPrimitive(json, (p) => !!p.targets?.length) || // A node with a negative-determinant local transform (odd negative scale, or a `matrix`
86
+ // with negative 3x3 determinant) may need the negative-winding feature. This mirrors the
87
+ // registry's `hasNegDetNode` predicate so a positive-determinant `matrix` node — extremely
88
+ // common, e.g. TextureSettingsTest — does NOT needlessly pull the feature registry.
89
+ json.nodes?.some(
90
+ (n) => n.scale ? n.scale[0] * n.scale[1] * n.scale[2] < 0 : n.matrix ? n.matrix[0] * (n.matrix[5] * n.matrix[10] - n.matrix[6] * n.matrix[9]) + n.matrix[1] * (n.matrix[6] * n.matrix[8] - n.matrix[4] * n.matrix[10]) + n.matrix[2] * (n.matrix[4] * n.matrix[9] - n.matrix[5] * n.matrix[8]) < 0 : false
91
+ ) || // Non-triangle primitive topology (POINTS/LINES/LINE_STRIP/TRIANGLE_STRIP).
92
+ anyPrimitive(json, (p) => p.mode !== void 0 && p.mode !== 4) || needsOrmComposite(json);
86
93
  }
87
94
  function buildNodeHierarchy(json, meshes, meshDatas) {
88
95
  const nodeToMeshes = /* @__PURE__ */ new Map();
@@ -163,7 +170,7 @@ async function extractAllMeshes(json, binChunk, baseUrl, parentMap, worldMatrixC
163
170
  const attrs = primitive.attributes;
164
171
  const decoded = decodedPrimitives.get(primitive);
165
172
  if (!decoded && _strided(primitive)) {
166
- const ip = (await loadInterleave()).buildInterleavedPartial(json, binChunk, primitive, worldMatrix, nodeIdx);
173
+ const ip = await (await loadInterleave()).buildInterleavedPartial(json, binChunk, primitive, worldMatrix, nodeIdx);
167
174
  if (ip) {
168
175
  matPromises.push(getMat(primitive.material));
169
176
  partials.push(ip);
@@ -188,6 +195,8 @@ async function extractAllMeshes(json, binChunk, baseUrl, parentMap, worldMatrixC
188
195
  const idxData = decoded ? decoded._indexCount > 0 ? { _data: decoded._indices, _count: decoded._indexCount} : null : primitive.indices !== void 0 ? resolveAccessor(json, binChunk, primitive.indices) : null;
189
196
  const normalsHelper = !idxData || !normData ? await import('./gltf-normals.js') : null;
190
197
  const colors = colorData ? (await importColorNormalize()).normalizeColorToVec4(colorData._data, colorData._count, colorData._componentCount) : null;
198
+ const uvs = uvData ? uvData._data instanceof F32 ? uvData._data : (await importColorNormalize()).normalizeUvToVec2(uvData._data, uvData._count) : new F32(posData._count * 2);
199
+ const uv2s = uv2Data ? uv2Data._data instanceof F32 ? uv2Data._data : (await importColorNormalize()).normalizeUvToVec2(uv2Data._data, uv2Data._count) : null;
191
200
  const indices = idxData ? idxData._data instanceof U32 ? new U32(idxData._data) : idxData._data instanceof U8 ? Uint16Array.from(idxData._data) : new U16(idxData._data.buffer, idxData._data.byteOffset, idxData._count) : normalsHelper.createSequentialIndices(posData._count);
192
201
  matPromises.push(getMat(primitive.material));
193
202
  const normals = normData ? normData._data : normalsHelper.computeSmoothNormals(posData._data, indices, posData._count);
@@ -195,8 +204,8 @@ async function extractAllMeshes(json, binChunk, baseUrl, parentMap, worldMatrixC
195
204
  _positions: posData._data,
196
205
  _normals: normals,
197
206
  _tangents: tanData ? tanData._data : null,
198
- _uvs: uvData ? uvData._data : new F32(posData._count * 2),
199
- _uv2s: uv2Data ? uv2Data._data : null,
207
+ _uvs: uvs,
208
+ _uv2s: uv2s,
200
209
  _colors: colors,
201
210
  _flatNormal: !normData,
202
211
  _indices: indices,