@forgeax/engine-gltf 0.0.0-dev.8d955ade1c79

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 (220) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +204 -0
  3. package/dist/.tsbuildinfo +1 -0
  4. package/dist/__tests__/animation-target-errors.unit.test.d.ts +2 -0
  5. package/dist/__tests__/animation-target-errors.unit.test.d.ts.map +1 -0
  6. package/dist/__tests__/bridge-material-samplers.unit.test.d.ts +2 -0
  7. package/dist/__tests__/bridge-material-samplers.unit.test.d.ts.map +1 -0
  8. package/dist/__tests__/bridge-material-values.unit.test.d.ts +2 -0
  9. package/dist/__tests__/bridge-material-values.unit.test.d.ts.map +1 -0
  10. package/dist/__tests__/bridge-mixed-color.unit.test.d.ts +2 -0
  11. package/dist/__tests__/bridge-mixed-color.unit.test.d.ts.map +1 -0
  12. package/dist/__tests__/bridge-sampler-required.test-d.d.ts +2 -0
  13. package/dist/__tests__/bridge-sampler-required.test-d.d.ts.map +1 -0
  14. package/dist/__tests__/bridge-skin-shader-route.unit.test.d.ts +2 -0
  15. package/dist/__tests__/bridge-skin-shader-route.unit.test.d.ts.map +1 -0
  16. package/dist/__tests__/bridge-skin-stride.unit.test.d.ts +2 -0
  17. package/dist/__tests__/bridge-skin-stride.unit.test.d.ts.map +1 -0
  18. package/dist/__tests__/bridge-test-helpers.d.ts +5 -0
  19. package/dist/__tests__/bridge-test-helpers.d.ts.map +1 -0
  20. package/dist/__tests__/bridge.unit.test.d.ts +2 -0
  21. package/dist/__tests__/bridge.unit.test.d.ts.map +1 -0
  22. package/dist/__tests__/cli-gltf.integration.test.d.ts +2 -0
  23. package/dist/__tests__/cli-gltf.integration.test.d.ts.map +1 -0
  24. package/dist/__tests__/color-0-accessor.unit.test.d.ts +2 -0
  25. package/dist/__tests__/color-0-accessor.unit.test.d.ts.map +1 -0
  26. package/dist/__tests__/data-uri-surface.unit.test.d.ts +2 -0
  27. package/dist/__tests__/data-uri-surface.unit.test.d.ts.map +1 -0
  28. package/dist/__tests__/extension-admission.unit.test.d.ts +2 -0
  29. package/dist/__tests__/extension-admission.unit.test.d.ts.map +1 -0
  30. package/dist/__tests__/gltf-error-derived-views.test-d.d.ts +2 -0
  31. package/dist/__tests__/gltf-error-derived-views.test-d.d.ts.map +1 -0
  32. package/dist/__tests__/gltf-error-migration.test.d.ts +2 -0
  33. package/dist/__tests__/gltf-error-migration.test.d.ts.map +1 -0
  34. package/dist/__tests__/gltf-error-policy-owner.unit.test.d.ts +2 -0
  35. package/dist/__tests__/gltf-error-policy-owner.unit.test.d.ts.map +1 -0
  36. package/dist/__tests__/gltf-importer.test.d.ts +2 -0
  37. package/dist/__tests__/gltf-importer.test.d.ts.map +1 -0
  38. package/dist/__tests__/gltf-local-artifacts.test.d.ts +2 -0
  39. package/dist/__tests__/gltf-local-artifacts.test.d.ts.map +1 -0
  40. package/dist/__tests__/gltf.unit.test.d.ts +2 -0
  41. package/dist/__tests__/gltf.unit.test.d.ts.map +1 -0
  42. package/dist/__tests__/malformed-buffer-data-uri.integration.test.d.ts +2 -0
  43. package/dist/__tests__/malformed-buffer-data-uri.integration.test.d.ts.map +1 -0
  44. package/dist/__tests__/material-missing-uv.unit.test.d.ts +2 -0
  45. package/dist/__tests__/material-missing-uv.unit.test.d.ts.map +1 -0
  46. package/dist/__tests__/material-pack-refs.integration.test.d.ts +2 -0
  47. package/dist/__tests__/material-pack-refs.integration.test.d.ts.map +1 -0
  48. package/dist/__tests__/material-per-slot-ir.unit.test.d.ts +2 -0
  49. package/dist/__tests__/material-per-slot-ir.unit.test.d.ts.map +1 -0
  50. package/dist/__tests__/material-texture-transform.unit.test.d.ts +2 -0
  51. package/dist/__tests__/material-texture-transform.unit.test.d.ts.map +1 -0
  52. package/dist/__tests__/material-unused-uv.unit.test.d.ts +2 -0
  53. package/dist/__tests__/material-unused-uv.unit.test.d.ts.map +1 -0
  54. package/dist/__tests__/meshopt-decode.unit.test.d.ts +2 -0
  55. package/dist/__tests__/meshopt-decode.unit.test.d.ts.map +1 -0
  56. package/dist/__tests__/meshopt-falsifier.unit.test.d.ts +2 -0
  57. package/dist/__tests__/meshopt-falsifier.unit.test.d.ts.map +1 -0
  58. package/dist/__tests__/meshopt-mode-filter-owner.test-d.d.ts +2 -0
  59. package/dist/__tests__/meshopt-mode-filter-owner.test-d.d.ts.map +1 -0
  60. package/dist/__tests__/meshopt-real-matrix.integration.test.d.ts +2 -0
  61. package/dist/__tests__/meshopt-real-matrix.integration.test.d.ts.map +1 -0
  62. package/dist/__tests__/morph-import.integration.test.d.ts +2 -0
  63. package/dist/__tests__/morph-import.integration.test.d.ts.map +1 -0
  64. package/dist/__tests__/morph-import.unit.test.d.ts +2 -0
  65. package/dist/__tests__/morph-import.unit.test.d.ts.map +1 -0
  66. package/dist/__tests__/parse-decode.unit.test.d.ts +2 -0
  67. package/dist/__tests__/parse-decode.unit.test.d.ts.map +1 -0
  68. package/dist/__tests__/parse-gltf-multi-uv.test.d.ts +2 -0
  69. package/dist/__tests__/parse-gltf-multi-uv.test.d.ts.map +1 -0
  70. package/dist/__tests__/parse-gltf-skin-attrs.unit.test.d.ts +2 -0
  71. package/dist/__tests__/parse-gltf-skin-attrs.unit.test.d.ts.map +1 -0
  72. package/dist/__tests__/parse-gltf.unit.test.d.ts +2 -0
  73. package/dist/__tests__/parse-gltf.unit.test.d.ts.map +1 -0
  74. package/dist/__tests__/pick-e2e.integration.test.d.ts +2 -0
  75. package/dist/__tests__/pick-e2e.integration.test.d.ts.map +1 -0
  76. package/dist/__tests__/public-surface.unit.test.d.ts +2 -0
  77. package/dist/__tests__/public-surface.unit.test.d.ts.map +1 -0
  78. package/dist/__tests__/source-key-diagnostics.integration.test.d.ts +2 -0
  79. package/dist/__tests__/source-key-diagnostics.integration.test.d.ts.map +1 -0
  80. package/dist/__tests__/source-key-producer.integration.test.d.ts +2 -0
  81. package/dist/__tests__/source-key-producer.integration.test.d.ts.map +1 -0
  82. package/dist/__tests__/source-key.unit.test.d.ts +2 -0
  83. package/dist/__tests__/source-key.unit.test.d.ts.map +1 -0
  84. package/dist/accessor/decode-accessor.d.ts +75 -0
  85. package/dist/accessor/decode-accessor.d.ts.map +1 -0
  86. package/dist/accessor/decode-color.d.ts +15 -0
  87. package/dist/accessor/decode-color.d.ts.map +1 -0
  88. package/dist/accessor/index.d.ts +3 -0
  89. package/dist/accessor/index.d.ts.map +1 -0
  90. package/dist/bridge.d.ts +103 -0
  91. package/dist/bridge.d.ts.map +1 -0
  92. package/dist/check-extensions.d.ts +29 -0
  93. package/dist/check-extensions.d.ts.map +1 -0
  94. package/dist/cli-gltf.d.ts +8 -0
  95. package/dist/cli-gltf.d.ts.map +1 -0
  96. package/dist/cli-gltf.mjs +2435 -0
  97. package/dist/cli-gltf.mjs.map +1 -0
  98. package/dist/data-uri.d.ts +6 -0
  99. package/dist/data-uri.d.ts.map +1 -0
  100. package/dist/errors.d.ts +201 -0
  101. package/dist/errors.d.ts.map +1 -0
  102. package/dist/extract-image-bytes.d.ts +42 -0
  103. package/dist/extract-image-bytes.d.ts.map +1 -0
  104. package/dist/gltf-importer.d.ts +21 -0
  105. package/dist/gltf-importer.d.ts.map +1 -0
  106. package/dist/image-color-space.d.ts +35 -0
  107. package/dist/image-color-space.d.ts.map +1 -0
  108. package/dist/importer-entry.d.ts +10 -0
  109. package/dist/importer-entry.d.ts.map +1 -0
  110. package/dist/importer-entry.mjs +3378 -0
  111. package/dist/importer-entry.mjs.map +1 -0
  112. package/dist/index.d.ts +25 -0
  113. package/dist/index.d.ts.map +1 -0
  114. package/dist/index.mjs +3673 -0
  115. package/dist/index.mjs.map +1 -0
  116. package/dist/meshopt-decode.d.ts +39 -0
  117. package/dist/meshopt-decode.d.ts.map +1 -0
  118. package/dist/node-file-entry.d.ts +14 -0
  119. package/dist/node-file-entry.d.ts.map +1 -0
  120. package/dist/node-file-entry.mjs +1946 -0
  121. package/dist/node-file-entry.mjs.map +1 -0
  122. package/dist/node-path.d.ts +14 -0
  123. package/dist/node-path.d.ts.map +1 -0
  124. package/dist/parse-animation.d.ts +70 -0
  125. package/dist/parse-animation.d.ts.map +1 -0
  126. package/dist/parse-glb-chunks.d.ts +25 -0
  127. package/dist/parse-glb-chunks.d.ts.map +1 -0
  128. package/dist/parse-glb.d.ts +3 -0
  129. package/dist/parse-glb.d.ts.map +1 -0
  130. package/dist/parse-gltf-header.d.ts +18 -0
  131. package/dist/parse-gltf-header.d.ts.map +1 -0
  132. package/dist/parse-gltf.d.ts +263 -0
  133. package/dist/parse-gltf.d.ts.map +1 -0
  134. package/dist/parse-skin.d.ts +70 -0
  135. package/dist/parse-skin.d.ts.map +1 -0
  136. package/dist/reimport-reuse-meta.d.ts +48 -0
  137. package/dist/reimport-reuse-meta.d.ts.map +1 -0
  138. package/dist/serialize-meta.d.ts +2 -0
  139. package/dist/serialize-meta.d.ts.map +1 -0
  140. package/dist/source-key.d.ts +44 -0
  141. package/dist/source-key.d.ts.map +1 -0
  142. package/dist/sub-asset-key.d.ts +12 -0
  143. package/dist/sub-asset-key.d.ts.map +1 -0
  144. package/dist/to-asset-pack.d.ts +2 -0
  145. package/dist/to-asset-pack.d.ts.map +1 -0
  146. package/dist/transform.d.ts +37 -0
  147. package/dist/transform.d.ts.map +1 -0
  148. package/package.json +92 -0
  149. package/src/__tests__/animation-target-errors.unit.test.ts +51 -0
  150. package/src/__tests__/bridge-material-samplers.unit.test.ts +67 -0
  151. package/src/__tests__/bridge-material-values.unit.test.ts +106 -0
  152. package/src/__tests__/bridge-mixed-color.unit.test.ts +100 -0
  153. package/src/__tests__/bridge-sampler-required.test-d.ts +59 -0
  154. package/src/__tests__/bridge-skin-shader-route.unit.test.ts +42 -0
  155. package/src/__tests__/bridge-skin-stride.unit.test.ts +115 -0
  156. package/src/__tests__/bridge-test-helpers.ts +9 -0
  157. package/src/__tests__/bridge.unit.test.ts +1227 -0
  158. package/src/__tests__/cli-gltf.integration.test.ts +122 -0
  159. package/src/__tests__/color-0-accessor.unit.test.ts +174 -0
  160. package/src/__tests__/data-uri-surface.unit.test.ts +26 -0
  161. package/src/__tests__/extension-admission.unit.test.ts +19 -0
  162. package/src/__tests__/fixtures/color-0/float-vec3.gltf +13 -0
  163. package/src/__tests__/fixtures/color-0/interleaved-vec3.gltf +6 -0
  164. package/src/__tests__/fixtures/color-0/normalized-ubyte.gltf +6 -0
  165. package/src/__tests__/fixtures/color-0/normalized-ushort.gltf +6 -0
  166. package/src/__tests__/gltf-error-derived-views.test-d.ts +152 -0
  167. package/src/__tests__/gltf-error-migration.test.ts +147 -0
  168. package/src/__tests__/gltf-error-policy-owner.unit.test.ts +185 -0
  169. package/src/__tests__/gltf-importer.test.ts +12 -0
  170. package/src/__tests__/gltf-local-artifacts.test.ts +11 -0
  171. package/src/__tests__/gltf.unit.test.ts +2975 -0
  172. package/src/__tests__/malformed-buffer-data-uri.integration.test.ts +140 -0
  173. package/src/__tests__/material-missing-uv.unit.test.ts +29 -0
  174. package/src/__tests__/material-pack-refs.integration.test.ts +58 -0
  175. package/src/__tests__/material-per-slot-ir.unit.test.ts +90 -0
  176. package/src/__tests__/material-texture-transform.unit.test.ts +105 -0
  177. package/src/__tests__/material-unused-uv.unit.test.ts +44 -0
  178. package/src/__tests__/meshopt-decode.unit.test.ts +87 -0
  179. package/src/__tests__/meshopt-falsifier.unit.test.ts +92 -0
  180. package/src/__tests__/meshopt-mode-filter-owner.test-d.ts +79 -0
  181. package/src/__tests__/meshopt-real-matrix.integration.test.ts +370 -0
  182. package/src/__tests__/morph-import.integration.test.ts +115 -0
  183. package/src/__tests__/morph-import.unit.test.ts +62 -0
  184. package/src/__tests__/parse-decode.unit.test.ts +919 -0
  185. package/src/__tests__/parse-gltf-multi-uv.test.ts +238 -0
  186. package/src/__tests__/parse-gltf-skin-attrs.unit.test.ts +191 -0
  187. package/src/__tests__/parse-gltf.unit.test.ts +133 -0
  188. package/src/__tests__/pick-e2e.integration.test.ts +238 -0
  189. package/src/__tests__/public-surface.unit.test.ts +10 -0
  190. package/src/__tests__/source-key-diagnostics.integration.test.ts +140 -0
  191. package/src/__tests__/source-key-producer.integration.test.ts +171 -0
  192. package/src/__tests__/source-key.unit.test.ts +159 -0
  193. package/src/accessor/decode-accessor.ts +309 -0
  194. package/src/accessor/decode-color.ts +128 -0
  195. package/src/accessor/index.ts +15 -0
  196. package/src/bridge.ts +928 -0
  197. package/src/check-extensions.ts +85 -0
  198. package/src/cli-gltf.ts +339 -0
  199. package/src/data-uri.ts +29 -0
  200. package/src/errors.ts +386 -0
  201. package/src/extract-image-bytes.ts +343 -0
  202. package/src/gltf-importer.ts +976 -0
  203. package/src/image-color-space.ts +104 -0
  204. package/src/importer-entry.ts +24 -0
  205. package/src/index.ts +148 -0
  206. package/src/meshopt-decode.ts +200 -0
  207. package/src/node-file-entry.ts +98 -0
  208. package/src/node-path.ts +41 -0
  209. package/src/parse-animation.ts +286 -0
  210. package/src/parse-glb-chunks.ts +167 -0
  211. package/src/parse-glb.ts +16 -0
  212. package/src/parse-gltf-header.ts +58 -0
  213. package/src/parse-gltf.ts +1731 -0
  214. package/src/parse-skin.ts +238 -0
  215. package/src/reimport-reuse-meta.ts +134 -0
  216. package/src/serialize-meta.ts +24 -0
  217. package/src/source-key.ts +106 -0
  218. package/src/sub-asset-key.ts +61 -0
  219. package/src/to-asset-pack.ts +8 -0
  220. package/src/transform.ts +117 -0
package/dist/index.mjs ADDED
@@ -0,0 +1,3673 @@
1
+ import { err, ok, reconcileMeshMaterialSlotTopology, MESH_MATERIAL_SLOT_SOURCE_OVERRIDE_PAYLOAD_SCHEMA, ImportError, IMPORT_ERROR_HINTS, toShared, resolveMeshMaterialSlotDefaultGuid, createMaterialError } from '@forgeax/engine-types';
2
+ export { err, ok } from '@forgeax/engine-types';
3
+ import { packInterleavedVertexAttributes } from '@forgeax/engine-geometry';
4
+ import { box3, mat4, vec3, quat } from '@forgeax/engine-math';
5
+ import { AssetGuid } from '@forgeax/engine-pack/guid';
6
+ import { packMeshBinV4 } from '@forgeax/engine-import/mesh-bin';
7
+ import { deriveAnimationTargetId } from '@forgeax/engine-animation/target-id';
8
+
9
+ // src/errors.ts
10
+ var GLTF_MESHOPT_MODES = ["ATTRIBUTES", "TRIANGLES", "INDICES"];
11
+ var GLTF_MESHOPT_FILTERS = ["NONE", "OCTAHEDRAL", "QUATERNION", "EXPONENTIAL"];
12
+ var gltfErrorPolicy = {
13
+ "gltf-malformed-header": {
14
+ expected: "GLB 12-byte header (magic 0x46546C67 + version=2 + length) plus mandatory JSON chunk",
15
+ hint: "verify .glb is not truncated; rerun: forgeax-engine-remote-gltf import <path>"
16
+ },
17
+ "gltf-version-unsupported": {
18
+ expected: 'asset.version === "2.0"',
19
+ hint: 'asset.version must be "2.0"; v1 or v3 not supported'
20
+ },
21
+ "gltf-buffer-out-of-bounds": {
22
+ expected: "accessor byte range within bufferView.byteLength",
23
+ hint: "rebuild .gltf with valid bufferViews; check accessor index; ensure accessor.byteOffset + EFFECTIVE_STRIDE * (count - 1) + element_size <= bufferView.byteLength"
24
+ },
25
+ "gltf-extension-unsupported": {
26
+ expected: "extension listed in v1 allowlist (see EXTENSION_ALLOWLIST in @forgeax/engine-gltf)",
27
+ hint: "see feat-future-gltf-extensions-allowlist; remove this extension or wait for the allowlist to expand"
28
+ },
29
+ "gltf-accessor-type-mismatch": {
30
+ expected: "dense fixed-stride accessor with supported componentType",
31
+ hint: "sparse: see feat-future-gltf-sparse-accessor; morph: see feat-future-gltf-morph; interleaved: see feat-future-gltf-mesh-multi-section"
32
+ },
33
+ "gltf-texture-load-failed": {
34
+ expected: "externalLoader resolved the URI into an ArrayBuffer without throwing",
35
+ hint: "check sidecar meta.json + textures/ directory + vite-plugin-pack /__pack/lookup route"
36
+ },
37
+ "gltf-meta-missing": {
38
+ expected: "sidecar <source>.meta.json (importer: 'gltf') present in same directory",
39
+ hint: "run: forgeax-engine-remote-gltf import <path>"
40
+ },
41
+ "gltf-instancing-count-mismatch": {
42
+ expected: "all instance attribute accessors share the same count",
43
+ hint: "EXT_mesh_gpu_instancing requires TRANSLATION/ROTATION/SCALE accessors to share count; see https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Khronos/EXT_mesh_gpu_instancing/README.md#extending-nodes-with-instance-attributes"
44
+ },
45
+ "gltf-image-mime-unsupported": {
46
+ expected: "image/mimeType is image/jpeg or image/png",
47
+ hint: "convert to JPG/PNG via external tool; only image/jpeg and image/png are supported"
48
+ },
49
+ "gltf-skin-joint-count-exceeded": {
50
+ expected: "skin.joints.length <= MAX_JOINTS (256)",
51
+ hint: "reduce joint count below MAX_JOINTS (256) or see OOS-skin-max-joints"
52
+ },
53
+ "gltf-animation-cubicspline-unsupported": {
54
+ expected: "animation sampler interpolation is LINEAR or STEP",
55
+ hint: "see OOS-skin-cubicspline; convert CUBICSPLINE to LINEAR/STEP in DCC tool"
56
+ },
57
+ "gltf-morph-unsupported": {
58
+ expected: "animation channel target path is one of translation, rotation, scale, or weights",
59
+ hint: "animation target path must be translation, rotation, scale, or weights"
60
+ },
61
+ "gltf-skin-joint-name-missing": {
62
+ expected: "every joint node has a non-empty name and belongs to an acyclic hierarchy",
63
+ hint: "ensure every joint node has a non-empty name and the node hierarchy is acyclic"
64
+ },
65
+ "gltf-image-extract-failed": {
66
+ expected: "image bytes extractable from bufferView / data-URI / external URI without corruption",
67
+ hint: "verify the bufferView byte range / data: URI base64 / external URI sibling file is intact next to the .gltf source; rerun: forgeax-engine-remote-gltf import <path>"
68
+ },
69
+ "gltf-skin-attr-asymmetric": {
70
+ expected: "mesh primitive declares JOINTS_0 and WEIGHTS_0 symmetrically (both present or both absent)",
71
+ hint: "glTF spec requires JOINTS_0 and WEIGHTS_0 to appear together for each skinned primitive; add the missing attribute or remove the present one in the DCC tool"
72
+ },
73
+ "gltf-animation-target-invalid": {
74
+ expected: "every animation channel resolves to one uniquely named scene node and stable target ID",
75
+ hint: "name every node in the animated hierarchy and ensure each animated full path is unique"
76
+ },
77
+ "gltf-meshopt-decoder-required": {
78
+ expected: "a required or compressed-only EXT_meshopt_compression bufferView has a ready decoder capability",
79
+ hint: "provide the build-only EXT_meshopt_compression decoder or author a valid core fallback bufferView"
80
+ },
81
+ "gltf-meshopt-decode-failed": {
82
+ expected: "the EXT_meshopt_compression declaration and decoder output are structurally valid",
83
+ hint: "the meshopt decoder accepts the declared compressed range and produces the declared byte count"
84
+ },
85
+ "gltf-morph-invalid": {
86
+ expected: "morph target and default-weight arrays are dense, bounded, and match the base vertex count",
87
+ hint: "re-export dense morph targets with at most eight targets/attributes and matching vertex/default-weight lengths"
88
+ },
89
+ "gltf-color-accessor-unsupported": {
90
+ expected: "COLOR_0 dense accessor uses VEC3/VEC4 FLOAT or normalized UBYTE/USHORT",
91
+ hint: "re-export COLOR_0 with a supported type/component/normalized combination; morph and sparse COLOR_0 remain deferred"
92
+ },
93
+ "gltf-color-accessor-malformed": {
94
+ expected: "COLOR_0 accessor is non-empty, finite, in range, and fully addressable",
95
+ hint: "repair the COLOR_0 accessor count, reference, range, or buffer bounds, then re-import the glTF source"
96
+ },
97
+ "gltf-mesh-bridge-invalid": {
98
+ expected: "a non-empty merged mesh with consistent morph and COLOR_0 cardinality",
99
+ hint: "repair the source primitive and re-import; inspect detail.reason and its typed facts"
100
+ }
101
+ };
102
+ var GLTF_ERROR_HINTS = Object.fromEntries(
103
+ Object.entries(gltfErrorPolicy).map(([code, policy]) => [code, policy.hint])
104
+ );
105
+ function gltfErr(code, detail) {
106
+ return {
107
+ code,
108
+ expected: gltfErrorPolicy[code].expected,
109
+ hint: gltfErrorPolicy[code].hint,
110
+ detail
111
+ };
112
+ }
113
+
114
+ // src/accessor/decode-accessor.ts
115
+ var COMPONENT_TYPE = {
116
+ I8: 5120,
117
+ U8: 5121,
118
+ I16: 5122,
119
+ U16: 5123,
120
+ U32: 5125,
121
+ F32: 5126
122
+ };
123
+ var TYPE_COMPONENT_COUNT = {
124
+ SCALAR: 1,
125
+ VEC2: 2,
126
+ VEC3: 3,
127
+ VEC4: 4,
128
+ MAT2: 4,
129
+ MAT3: 9,
130
+ MAT4: 16
131
+ };
132
+ var COMPONENT_BYTE_SIZE = {
133
+ [COMPONENT_TYPE.I8]: 1,
134
+ [COMPONENT_TYPE.U8]: 1,
135
+ [COMPONENT_TYPE.I16]: 2,
136
+ [COMPONENT_TYPE.U16]: 2,
137
+ [COMPONENT_TYPE.U32]: 4,
138
+ [COMPONENT_TYPE.F32]: 4
139
+ };
140
+ function decodeF32Accessor(accessorIndex, accessor, allowedTypes, bufferViews, buffers) {
141
+ if (accessor.componentType !== COMPONENT_TYPE.F32 || !allowedTypes.includes(accessor.type)) {
142
+ return err(
143
+ gltfErr("gltf-accessor-type-mismatch", { accessorIndex, reason: "unknownComponentType" })
144
+ );
145
+ }
146
+ const bufferViewIndex = accessor.bufferView;
147
+ if (bufferViewIndex === void 0) {
148
+ return err(
149
+ gltfErr("gltf-buffer-out-of-bounds", {
150
+ accessor: accessorIndex,
151
+ byteOffset: 0,
152
+ byteLength: 0,
153
+ bufferIndex: 0
154
+ })
155
+ );
156
+ }
157
+ const bufferView = bufferViews[bufferViewIndex];
158
+ if (bufferView === void 0) {
159
+ return err(
160
+ gltfErr("gltf-buffer-out-of-bounds", {
161
+ accessor: accessorIndex,
162
+ byteOffset: 0,
163
+ byteLength: 0,
164
+ bufferIndex: bufferViewIndex
165
+ })
166
+ );
167
+ }
168
+ const buffer = buffers[bufferView.buffer];
169
+ if (buffer === void 0) {
170
+ return err(
171
+ gltfErr("gltf-buffer-out-of-bounds", {
172
+ accessor: accessorIndex,
173
+ byteOffset: bufferView.byteOffset ?? 0,
174
+ byteLength: bufferView.byteLength,
175
+ bufferIndex: bufferView.buffer
176
+ })
177
+ );
178
+ }
179
+ const decoded = decodeAccessor({
180
+ accessor: { ...accessor, bufferView: bufferViewIndex },
181
+ bufferView,
182
+ buffer,
183
+ accessorIndex,
184
+ role: "attribute"
185
+ });
186
+ if (!decoded.ok) return decoded;
187
+ if (decoded.value.kind !== "f32") {
188
+ return err(
189
+ gltfErr("gltf-accessor-type-mismatch", { accessorIndex, reason: "unknownComponentType" })
190
+ );
191
+ }
192
+ return ok(decoded.value.data);
193
+ }
194
+ function decodeAccessor(input, flags = {}) {
195
+ const { accessorIndex, accessor, bufferView, buffer, role } = input;
196
+ if (flags.morph === true) {
197
+ return err(
198
+ gltfErr("gltf-accessor-type-mismatch", {
199
+ accessorIndex,
200
+ reason: "morph"
201
+ })
202
+ );
203
+ }
204
+ if ("sparse" in accessor && accessor.sparse !== void 0) {
205
+ return err(
206
+ gltfErr("gltf-accessor-type-mismatch", {
207
+ accessorIndex,
208
+ reason: "sparse"
209
+ })
210
+ );
211
+ }
212
+ const componentByteSize = COMPONENT_BYTE_SIZE[accessor.componentType];
213
+ const componentCount = TYPE_COMPONENT_COUNT[accessor.type];
214
+ if (componentByteSize === void 0 || componentCount === void 0) {
215
+ return err(
216
+ gltfErr("gltf-accessor-type-mismatch", {
217
+ accessorIndex,
218
+ reason: "unknownComponentType"
219
+ })
220
+ );
221
+ }
222
+ const elementSize = componentByteSize * componentCount;
223
+ const byteStride = bufferView.byteStride;
224
+ if (byteStride !== void 0 && byteStride !== elementSize) {
225
+ return err(
226
+ gltfErr("gltf-accessor-type-mismatch", {
227
+ accessorIndex,
228
+ reason: "interleaved"
229
+ })
230
+ );
231
+ }
232
+ const accessorByteOffset = accessor.byteOffset ?? 0;
233
+ const bufferViewByteOffset = bufferView.byteOffset ?? 0;
234
+ const totalByteLength = elementSize * accessor.count;
235
+ if (accessorByteOffset + totalByteLength > bufferView.byteLength) {
236
+ return err(
237
+ gltfErr("gltf-buffer-out-of-bounds", {
238
+ accessor: accessorIndex,
239
+ byteOffset: bufferViewByteOffset + accessorByteOffset,
240
+ byteLength: totalByteLength,
241
+ bufferIndex: bufferView.buffer
242
+ })
243
+ );
244
+ }
245
+ const absoluteOffset = bufferViewByteOffset + accessorByteOffset;
246
+ if (absoluteOffset + totalByteLength > buffer.byteLength) {
247
+ return err(
248
+ gltfErr("gltf-buffer-out-of-bounds", {
249
+ accessor: accessorIndex,
250
+ byteOffset: absoluteOffset,
251
+ byteLength: totalByteLength,
252
+ bufferIndex: bufferView.buffer
253
+ })
254
+ );
255
+ }
256
+ if (role === "indices" && accessor.componentType === COMPONENT_TYPE.U8) {
257
+ const widened = new Uint16Array(accessor.count);
258
+ for (let i = 0; i < accessor.count; i++) {
259
+ widened[i] = buffer[absoluteOffset + i] ?? 0;
260
+ }
261
+ return ok({ kind: "u16", data: widened });
262
+ }
263
+ if (role === "joints" && accessor.componentType === COMPONENT_TYPE.U8) {
264
+ const totalValues = accessor.count * componentCount;
265
+ const widened = new Uint16Array(totalValues);
266
+ for (let i = 0; i < totalValues; i++) {
267
+ widened[i] = buffer[absoluteOffset + i] ?? 0;
268
+ }
269
+ return ok({ kind: "u16", data: widened });
270
+ }
271
+ if (accessor.componentType === COMPONENT_TYPE.F32) {
272
+ const out = new Float32Array(accessor.count * componentCount);
273
+ const src = new Float32Array(buffer.buffer, buffer.byteOffset + absoluteOffset, out.length);
274
+ out.set(src);
275
+ return ok({ kind: "f32", data: out });
276
+ }
277
+ if (accessor.componentType === COMPONENT_TYPE.U16) {
278
+ const out = new Uint16Array(accessor.count * componentCount);
279
+ const src = new Uint16Array(buffer.buffer, buffer.byteOffset + absoluteOffset, out.length);
280
+ out.set(src);
281
+ return ok({ kind: "u16", data: out });
282
+ }
283
+ if (accessor.componentType === COMPONENT_TYPE.U32) {
284
+ const out = new Uint32Array(accessor.count * componentCount);
285
+ const src = new Uint32Array(buffer.buffer, buffer.byteOffset + absoluteOffset, out.length);
286
+ out.set(src);
287
+ return ok({ kind: "u32", data: out });
288
+ }
289
+ if (accessor.componentType === COMPONENT_TYPE.I16) {
290
+ const out = new Float32Array(accessor.count * componentCount);
291
+ const src = new Int16Array(buffer.buffer, buffer.byteOffset + absoluteOffset, out.length);
292
+ for (let index = 0; index < src.length; index++) {
293
+ const value = src[index] ?? 0;
294
+ out[index] = accessor.normalized === true ? Math.max(-1, value / 32767) : value;
295
+ }
296
+ return ok({ kind: "f32", data: out });
297
+ }
298
+ return err(
299
+ gltfErr("gltf-accessor-type-mismatch", {
300
+ accessorIndex,
301
+ reason: "unknownComponentType"
302
+ })
303
+ );
304
+ }
305
+
306
+ // src/accessor/decode-color.ts
307
+ var FLOAT = 5126;
308
+ var UNSIGNED_BYTE = 5121;
309
+ var UNSIGNED_SHORT = 5123;
310
+ function unsupported(input, reason) {
311
+ return err(
312
+ gltfErr("gltf-color-accessor-unsupported", {
313
+ semantic: input.semantic,
314
+ accessorIndex: input.accessorIndex,
315
+ reason,
316
+ expectedType: "VEC3 or VEC4",
317
+ expectedComponent: "FLOAT or normalized UNSIGNED_BYTE/UNSIGNED_SHORT",
318
+ ...reason === "normalized" ? { expectedNormalized: true } : {}
319
+ })
320
+ );
321
+ }
322
+ function malformed(input, reason) {
323
+ return err(
324
+ gltfErr("gltf-color-accessor-malformed", {
325
+ semantic: input.semantic,
326
+ accessorIndex: input.accessorIndex,
327
+ reason,
328
+ ...reason === "count" ? { expectedCount: "matches POSITION vertex count and is greater than zero" } : {},
329
+ ...reason === "range" || reason === "finite" ? { expectedRange: "[0,1] finite linear values" } : {}
330
+ })
331
+ );
332
+ }
333
+ function decodeColorAccessor(input, flags = {}) {
334
+ const { accessor, bufferView, buffer } = input;
335
+ if (flags.morph === true) return unsupported(input, "morph");
336
+ if (accessor.sparse !== void 0) return unsupported(input, "sparse");
337
+ if (accessor.type !== "VEC3" && accessor.type !== "VEC4") return unsupported(input, "type");
338
+ if (!Number.isSafeInteger(accessor.count) || accessor.count <= 0)
339
+ return malformed(input, "count");
340
+ let componentByteSize;
341
+ if (accessor.componentType === FLOAT) componentByteSize = 4;
342
+ else if (accessor.componentType === UNSIGNED_BYTE) componentByteSize = 1;
343
+ else if (accessor.componentType === UNSIGNED_SHORT) componentByteSize = 2;
344
+ else return unsupported(input, "component");
345
+ if (accessor.componentType === FLOAT) {
346
+ if (accessor.normalized !== void 0) return unsupported(input, "normalized");
347
+ } else if (accessor.normalized !== true) {
348
+ return unsupported(input, "normalized");
349
+ }
350
+ const componentCount = accessor.type === "VEC3" ? 3 : 4;
351
+ const elementByteLength = componentByteSize * componentCount;
352
+ const byteStride = bufferView.byteStride ?? elementByteLength;
353
+ if (!Number.isSafeInteger(byteStride) || byteStride < elementByteLength || byteStride % componentByteSize !== 0) {
354
+ return malformed(input, "bounds");
355
+ }
356
+ if (input.bufferIndex !== void 0 && bufferView.buffer !== input.bufferIndex) {
357
+ return malformed(input, "reference");
358
+ }
359
+ const accessorByteOffset = accessor.byteOffset ?? 0;
360
+ const viewByteOffset = bufferView.byteOffset ?? 0;
361
+ const lastElementEnd = accessorByteOffset + byteStride * (accessor.count - 1) + elementByteLength;
362
+ if (!Number.isSafeInteger(accessorByteOffset) || accessorByteOffset < 0 || lastElementEnd > bufferView.byteLength) {
363
+ return malformed(input, "bounds");
364
+ }
365
+ const absoluteLastElementEnd = viewByteOffset + lastElementEnd;
366
+ if (!Number.isSafeInteger(viewByteOffset) || viewByteOffset < 0 || absoluteLastElementEnd > buffer.byteLength) {
367
+ return malformed(input, "bounds");
368
+ }
369
+ const output = new Float32Array(accessor.count * 4);
370
+ const view = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength);
371
+ const base = viewByteOffset + accessorByteOffset;
372
+ for (let vertex = 0; vertex < accessor.count; vertex++) {
373
+ const sourceOffset = base + vertex * byteStride;
374
+ for (let component = 0; component < componentCount; component++) {
375
+ const byteOffset = sourceOffset + component * componentByteSize;
376
+ let value;
377
+ if (accessor.componentType === FLOAT) value = view.getFloat32(byteOffset, true);
378
+ else if (accessor.componentType === UNSIGNED_BYTE) value = view.getUint8(byteOffset) / 255;
379
+ else value = view.getUint16(byteOffset, true) / 65535;
380
+ if (!Number.isFinite(value)) return malformed(input, "finite");
381
+ if (value < 0 || value > 1) return malformed(input, "range");
382
+ output[vertex * 4 + component] = value;
383
+ }
384
+ if (componentCount === 3) output[vertex * 4 + 3] = 1;
385
+ }
386
+ return ok(output);
387
+ }
388
+ function meshIrToMeshAsset(prims, materials = {}) {
389
+ if (prims.length === 0) {
390
+ return err(
391
+ gltfErr("gltf-mesh-bridge-invalid", {
392
+ reason: "empty-input",
393
+ primitiveCount: 0
394
+ })
395
+ );
396
+ }
397
+ let totalVertexCount = 0;
398
+ let totalIndexCount = 0;
399
+ let hasAnySkin = false;
400
+ const hasAnyColor = prims.some((p) => p.colors0 !== void 0);
401
+ let hasAnyIndices = false;
402
+ let widestUvIndex = 0;
403
+ const morphTargetCount = prims[0]?.morphTargets?.length ?? 0;
404
+ for (const p of prims) {
405
+ const primVc = p.positions.length / 3;
406
+ totalVertexCount += primVc;
407
+ if (p.indices !== void 0) {
408
+ totalIndexCount += p.indices.length;
409
+ hasAnyIndices = true;
410
+ } else {
411
+ totalIndexCount += primVc;
412
+ }
413
+ if (p.joints0 !== void 0 && p.weights0 !== void 0) hasAnySkin = true;
414
+ if ((p.morphTargets?.length ?? 0) !== morphTargetCount) {
415
+ return err(
416
+ gltfErr("gltf-mesh-bridge-invalid", {
417
+ meshIndex: p.meshIndex,
418
+ primitiveIndex: prims.indexOf(p),
419
+ reason: "morph-count-mismatch",
420
+ expectedTargetCount: morphTargetCount,
421
+ actualTargetCount: p.morphTargets?.length ?? 0
422
+ })
423
+ );
424
+ }
425
+ for (let k = 7; k >= 1; k--) {
426
+ const key = `texcoord${k}`;
427
+ if (p[key] !== void 0) {
428
+ widestUvIndex = Math.max(widestUvIndex, k);
429
+ break;
430
+ }
431
+ }
432
+ }
433
+ const uvKeys = Array.from({ length: widestUvIndex }, (_, index) => `uv${index + 1}`);
434
+ const positionsCat = new Float32Array(totalVertexCount * 3);
435
+ const normalsCat = new Float32Array(totalVertexCount * 3);
436
+ const uvsCat = new Float32Array(totalVertexCount * 2);
437
+ const tangentsCat = new Float32Array(totalVertexCount * 4);
438
+ const colorsCat = hasAnyColor ? new Float32Array(totalVertexCount * 4) : void 0;
439
+ const morphTargets = Array.from({ length: morphTargetCount }, (_, targetIndex) => {
440
+ const source = prims[0]?.morphTargets?.[targetIndex];
441
+ return {
442
+ ...source?.position === void 0 ? {} : { position: new Float32Array(totalVertexCount * 3) },
443
+ ...source?.normal === void 0 ? {} : { normal: new Float32Array(totalVertexCount * 3) },
444
+ ...source?.tangent === void 0 ? {} : { tangent: new Float32Array(totalVertexCount * 4) }
445
+ };
446
+ });
447
+ const uvCats = uvKeys.map(() => new Float32Array(totalVertexCount * 2));
448
+ const skinIndicesCat = hasAnySkin ? new Uint16Array(totalVertexCount * 4) : void 0;
449
+ const skinWeightsCat = hasAnySkin ? new Float32Array(totalVertexCount * 4) : void 0;
450
+ const useUint32 = totalVertexCount > 65535;
451
+ const indices = !hasAnyIndices ? void 0 : useUint32 ? new Uint32Array(totalIndexCount) : new Uint16Array(totalIndexCount);
452
+ const submeshes = [];
453
+ const materialSlots = [];
454
+ const slotByMaterial = /* @__PURE__ */ new Map();
455
+ const usedNames = /* @__PURE__ */ new Set();
456
+ const uniqueSlotName = (base) => {
457
+ let candidate = base.trim() || "Material";
458
+ let suffix = 2;
459
+ while (usedNames.has(candidate)) candidate = `${base}_${suffix++}`;
460
+ usedNames.add(candidate);
461
+ return candidate;
462
+ };
463
+ const slotFor = (materialIndex) => {
464
+ const existing = slotByMaterial.get(materialIndex);
465
+ if (existing !== void 0) return existing;
466
+ const slotIndex = materialSlots.length;
467
+ const guid = materialIndex === null ? void 0 : materials.guidByIndex?.get(materialIndex);
468
+ const parsed = guid === void 0 ? void 0 : AssetGuid.parse(guid);
469
+ materialSlots.push({
470
+ slotName: uniqueSlotName(
471
+ materialIndex === null ? "Default" : materials.nameByIndex?.get(materialIndex) ?? `Material_${materialIndex}`
472
+ ),
473
+ sourceKey: materialIndex === null ? "gltf:default" : materials.sourceKeyByIndex?.get(materialIndex) ?? `gltf:material:${materialIndex}`,
474
+ ...parsed?.ok ? { defaultMaterial: parsed.value } : {}
475
+ });
476
+ slotByMaterial.set(materialIndex, slotIndex);
477
+ return slotIndex;
478
+ };
479
+ let vertexCursor = 0;
480
+ let indexCursor = 0;
481
+ for (const mesh of prims) {
482
+ const materialSlot = slotFor(mesh.materialIndex);
483
+ const primVertexCount = mesh.positions.length / 3;
484
+ const primIndexCount = mesh.indices === void 0 ? 0 : mesh.indices.length;
485
+ if (mesh.colors0 !== void 0 && mesh.colors0.length !== primVertexCount * 4) {
486
+ return err(
487
+ gltfErr("gltf-mesh-bridge-invalid", {
488
+ meshIndex: mesh.meshIndex,
489
+ primitiveIndex: prims.indexOf(mesh),
490
+ reason: "color-cardinality",
491
+ semantic: "COLOR_0",
492
+ vertexCount: primVertexCount,
493
+ expectedLength: primVertexCount * 4,
494
+ actualLength: mesh.colors0.length
495
+ })
496
+ );
497
+ }
498
+ for (let i = 0; i < primVertexCount; i++) {
499
+ const p = i * 3;
500
+ positionsCat[(vertexCursor + i) * 3 + 0] = mesh.positions[p + 0];
501
+ positionsCat[(vertexCursor + i) * 3 + 1] = mesh.positions[p + 1];
502
+ positionsCat[(vertexCursor + i) * 3 + 2] = mesh.positions[p + 2];
503
+ if (mesh.normals !== void 0) {
504
+ const n = i * 3;
505
+ normalsCat[(vertexCursor + i) * 3 + 0] = mesh.normals[n + 0];
506
+ normalsCat[(vertexCursor + i) * 3 + 1] = mesh.normals[n + 1];
507
+ normalsCat[(vertexCursor + i) * 3 + 2] = mesh.normals[n + 2];
508
+ } else {
509
+ normalsCat[(vertexCursor + i) * 3 + 1] = 1;
510
+ }
511
+ if (mesh.texcoord0 !== void 0) {
512
+ const t = i * 2;
513
+ uvsCat[(vertexCursor + i) * 2 + 0] = mesh.texcoord0[t + 0];
514
+ uvsCat[(vertexCursor + i) * 2 + 1] = mesh.texcoord0[t + 1];
515
+ }
516
+ if (mesh.tangents !== void 0) {
517
+ const g = i * 4;
518
+ tangentsCat[(vertexCursor + i) * 4 + 0] = mesh.tangents[g + 0];
519
+ tangentsCat[(vertexCursor + i) * 4 + 1] = mesh.tangents[g + 1];
520
+ tangentsCat[(vertexCursor + i) * 4 + 2] = mesh.tangents[g + 2];
521
+ tangentsCat[(vertexCursor + i) * 4 + 3] = mesh.tangents[g + 3];
522
+ } else {
523
+ tangentsCat[(vertexCursor + i) * 4 + 0] = 1;
524
+ tangentsCat[(vertexCursor + i) * 4 + 3] = 1;
525
+ }
526
+ if (colorsCat !== void 0) {
527
+ const colorDst = (vertexCursor + i) * 4;
528
+ const colorSrc = i * 4;
529
+ if (mesh.colors0 === void 0) {
530
+ colorsCat[colorDst + 0] = 1;
531
+ colorsCat[colorDst + 1] = 1;
532
+ colorsCat[colorDst + 2] = 1;
533
+ colorsCat[colorDst + 3] = 1;
534
+ } else {
535
+ colorsCat[colorDst + 0] = mesh.colors0[colorSrc + 0];
536
+ colorsCat[colorDst + 1] = mesh.colors0[colorSrc + 1];
537
+ colorsCat[colorDst + 2] = mesh.colors0[colorSrc + 2];
538
+ colorsCat[colorDst + 3] = mesh.colors0[colorSrc + 3];
539
+ }
540
+ }
541
+ for (let targetIndex = 0; targetIndex < morphTargetCount; targetIndex++) {
542
+ const source = mesh.morphTargets?.[targetIndex];
543
+ const target = morphTargets[targetIndex];
544
+ const vertex = vertexCursor + i;
545
+ if (source?.position !== void 0 && target.position !== void 0) {
546
+ target.position.set(source.position.subarray(i * 3, i * 3 + 3), vertex * 3);
547
+ }
548
+ if (source?.normal !== void 0 && target.normal !== void 0) {
549
+ target.normal.set(source.normal.subarray(i * 3, i * 3 + 3), vertex * 3);
550
+ }
551
+ if (source?.tangent !== void 0 && target.tangent !== void 0) {
552
+ target.tangent.set(source.tangent.subarray(i * 4, i * 4 + 4), vertex * 4);
553
+ }
554
+ }
555
+ if (hasAnySkin && skinIndicesCat !== void 0 && skinWeightsCat !== void 0) {
556
+ const skinDst = (vertexCursor + i) * 4;
557
+ if (mesh.joints0 !== void 0 && mesh.weights0 !== void 0) {
558
+ const j = i * 4;
559
+ const j0 = mesh.joints0[j + 0];
560
+ const j1 = mesh.joints0[j + 1];
561
+ const j2 = mesh.joints0[j + 2];
562
+ const j3 = mesh.joints0[j + 3];
563
+ skinIndicesCat[skinDst + 0] = j0;
564
+ skinIndicesCat[skinDst + 1] = j1;
565
+ skinIndicesCat[skinDst + 2] = j2;
566
+ skinIndicesCat[skinDst + 3] = j3;
567
+ const w0 = mesh.weights0[j + 0];
568
+ const w1 = mesh.weights0[j + 1];
569
+ const w2 = mesh.weights0[j + 2];
570
+ const w3 = mesh.weights0[j + 3];
571
+ skinWeightsCat[skinDst + 0] = w0;
572
+ skinWeightsCat[skinDst + 1] = w1;
573
+ skinWeightsCat[skinDst + 2] = w2;
574
+ skinWeightsCat[skinDst + 3] = w3;
575
+ }
576
+ }
577
+ for (let k = 1; k <= uvKeys.length; k++) {
578
+ const uvKey = `texcoord${k}`;
579
+ const catIdx = k - 1;
580
+ const cat = uvCats[catIdx];
581
+ const catDst = (vertexCursor + i) * 2;
582
+ const srcArr = mesh[uvKey];
583
+ if (srcArr !== void 0) {
584
+ const t = i * 2;
585
+ cat[catDst + 0] = srcArr[t + 0];
586
+ cat[catDst + 1] = srcArr[t + 1];
587
+ }
588
+ }
589
+ }
590
+ if (indices !== void 0) {
591
+ if (mesh.indices !== void 0) {
592
+ for (let i = 0; i < primIndexCount; i++) {
593
+ const src = mesh.indices[i];
594
+ indices[indexCursor + i] = src + vertexCursor;
595
+ }
596
+ submeshes.push({
597
+ indexOffset: indexCursor,
598
+ indexCount: primIndexCount,
599
+ vertexCount: primVertexCount,
600
+ topology: "triangle-list",
601
+ materialSlot
602
+ });
603
+ indexCursor += primIndexCount;
604
+ } else {
605
+ for (let i = 0; i < primVertexCount; i++) {
606
+ indices[indexCursor + i] = vertexCursor + i;
607
+ }
608
+ submeshes.push({
609
+ indexOffset: indexCursor,
610
+ indexCount: primVertexCount,
611
+ vertexCount: primVertexCount,
612
+ topology: "triangle-list",
613
+ materialSlot
614
+ });
615
+ indexCursor += primVertexCount;
616
+ }
617
+ } else {
618
+ submeshes.push({
619
+ indexOffset: 0,
620
+ indexCount: 0,
621
+ vertexCount: primVertexCount,
622
+ topology: "triangle-list",
623
+ materialSlot
624
+ });
625
+ }
626
+ vertexCursor += primVertexCount;
627
+ }
628
+ const attributes = {
629
+ position: positionsCat,
630
+ normal: normalsCat,
631
+ uv: uvsCat,
632
+ tangent: tangentsCat,
633
+ ...colorsCat === void 0 ? {} : { color: colorsCat },
634
+ ...skinIndicesCat === void 0 ? {} : { skinIndex: skinIndicesCat },
635
+ ...skinWeightsCat === void 0 ? {} : { skinWeight: skinWeightsCat },
636
+ ...Object.fromEntries(uvCats.map((cat, idx) => [`uv${idx + 1}`, cat]))
637
+ };
638
+ const packed = packInterleavedVertexAttributes(attributes, totalVertexCount);
639
+ if (!packed.ok) {
640
+ return err(
641
+ gltfErr("gltf-mesh-bridge-invalid", {
642
+ meshIndex: prims[0]?.meshIndex ?? -1,
643
+ reason: "layout-invalid",
644
+ cause: packed.error.detail
645
+ })
646
+ );
647
+ }
648
+ return ok({
649
+ kind: "mesh",
650
+ vertices: packed.value.vertices,
651
+ ...indices === void 0 ? {} : { indices },
652
+ submeshes,
653
+ materialSlots,
654
+ aabb: box3.fromPositions(box3.create(), positionsCat),
655
+ attributes,
656
+ ...morphTargetCount === 0 ? {} : { morphTargets },
657
+ ...prims[0]?.morphWeights === void 0 ? {} : { morphWeights: new Float32Array(prims[0].morphWeights) }
658
+ });
659
+ }
660
+ function lightDirection(currentWorld) {
661
+ const direction = mat4.getForward(vec3.create(), currentWorld);
662
+ return [direction[0] ?? 0, direction[1] ?? 0, direction[2] ?? -1];
663
+ }
664
+ function lightComponent(light, direction) {
665
+ const range = light.range ?? Number.POSITIVE_INFINITY;
666
+ if (light.type === "directional") {
667
+ return { direction, color: light.color, intensity: light.intensity };
668
+ }
669
+ if (light.type === "point") {
670
+ return { color: light.color, intensity: light.intensity, range };
671
+ }
672
+ return {
673
+ direction,
674
+ color: light.color,
675
+ intensity: light.intensity,
676
+ range,
677
+ innerConeDeg: (light.spot?.innerConeAngle ?? 0) * 180 / Math.PI,
678
+ outerConeDeg: (light.spot?.outerConeAngle ?? Math.PI / 4) * 180 / Math.PI,
679
+ castShadow: false
680
+ };
681
+ }
682
+ function composeMat4(out, tx, ty, tz, qx, qy, qz, qw, sx, sy, sz) {
683
+ const t = vec3.create(tx, ty, tz);
684
+ const r = quat.create();
685
+ r[0] = qx;
686
+ r[1] = qy;
687
+ r[2] = qz;
688
+ r[3] = qw;
689
+ const s = vec3.create(sx, sy, sz);
690
+ mat4.compose(out, t, r, s);
691
+ }
692
+ function gltfDocToSceneAsset(doc, ctx) {
693
+ const sceneIr = doc.scenes[doc.defaultSceneIndex];
694
+ const resultNodes = [];
695
+ if (sceneIr === void 0) return { kind: "scene", entities: [] };
696
+ const importedLights = doc.lights ?? doc.extensions?.KHR_lights_punctual?.lights ?? [];
697
+ const animationTargetIds = /* @__PURE__ */ new Map();
698
+ for (const clip of doc.animationClips) {
699
+ for (const channel of clip.channels) {
700
+ animationTargetIds.set(channel.targetNodeIndex, channel.targetId);
701
+ }
702
+ }
703
+ const parentWorld = mat4.create();
704
+ mat4.identity(parentWorld);
705
+ const currentWorld = mat4.create();
706
+ const localMat = mat4.create();
707
+ const pushLocalTransform = (transform) => ({
708
+ pos: [
709
+ transform.translation[0] ?? 0,
710
+ transform.translation[1] ?? 0,
711
+ transform.translation[2] ?? 0
712
+ ],
713
+ // Quaternion component order [x, y, z, w] (glTF-aligned; E6).
714
+ quat: [
715
+ transform.rotation[0] ?? 0,
716
+ transform.rotation[1] ?? 0,
717
+ transform.rotation[2] ?? 0,
718
+ transform.rotation[3] ?? 1
719
+ ],
720
+ scale: [transform.scale[0] ?? 1, transform.scale[1] ?? 1, transform.scale[2] ?? 1]
721
+ });
722
+ const visit = (gltfNodeIdx, parentLocalIdx) => {
723
+ const ir = doc.nodes[gltfNodeIdx];
724
+ if (ir === void 0) return;
725
+ composeMat4(
726
+ localMat,
727
+ ir.transform.translation[0] ?? 0,
728
+ ir.transform.translation[1] ?? 0,
729
+ ir.transform.translation[2] ?? 0,
730
+ ir.transform.rotation[0] ?? 0,
731
+ ir.transform.rotation[1] ?? 0,
732
+ ir.transform.rotation[2] ?? 0,
733
+ ir.transform.rotation[3] ?? 1,
734
+ ir.transform.scale[0] ?? 1,
735
+ ir.transform.scale[1] ?? 1,
736
+ ir.transform.scale[2] ?? 1
737
+ );
738
+ if (parentLocalIdx === null) {
739
+ for (let i = 0; i < 16; i++) {
740
+ currentWorld[i] = localMat[i] ?? 0;
741
+ }
742
+ } else {
743
+ mat4.multiply(currentWorld, parentWorld, localMat);
744
+ }
745
+ const isCamera = ir.camera !== null;
746
+ const hasMesh = ir.meshIndex !== null;
747
+ const components = {
748
+ Transform: pushLocalTransform(ir.transform)
749
+ };
750
+ const importedLight = importedLights[ir.lightIndex ?? -1];
751
+ if (importedLight !== void 0) {
752
+ const componentName = importedLight.type === "directional" ? "DirectionalLight" : importedLight.type === "point" ? "PointLight" : "SpotLight";
753
+ components[componentName] = lightComponent(
754
+ importedLight,
755
+ lightDirection(currentWorld)
756
+ );
757
+ }
758
+ if (ir.name !== void 0 && ir.name !== "") {
759
+ components.Name = { value: ir.name };
760
+ }
761
+ const animationTargetId = animationTargetIds.get(gltfNodeIdx);
762
+ if (animationTargetId !== void 0) {
763
+ components.AnimationTargetId = { value: animationTargetId };
764
+ }
765
+ if (hasMesh) {
766
+ const meshHandle = ctx.meshHandles.get(ir.meshIndex);
767
+ if (meshHandle !== void 0) {
768
+ components.MeshFilter = { assetHandle: meshHandle };
769
+ }
770
+ const meshMorph = doc.meshes.find((mesh) => mesh.meshIndex === ir.meshIndex);
771
+ const morphCount = meshMorph?.morphTargets?.length ?? 0;
772
+ if (morphCount > 0) {
773
+ const weights = ir.morphWeights ?? meshMorph?.morphWeights ?? new Float32Array(morphCount);
774
+ if (weights.length !== morphCount) {
775
+ throw new Error("gltfDocToSceneAsset: MorphWeights length does not match morph targets");
776
+ }
777
+ components.MorphWeights = { weights: new Float32Array(weights) };
778
+ }
779
+ if (ir.skinIndex !== null && ctx.skeletonGuidBySkinIndex !== void 0) {
780
+ const skeletonGuid = ctx.skeletonGuidBySkinIndex.get(ir.skinIndex);
781
+ if (skeletonGuid !== void 0) {
782
+ components.Skin = { skeleton: skeletonGuid };
783
+ }
784
+ }
785
+ components.MeshRenderer = { materials: [] };
786
+ }
787
+ if (ir.instancing !== void 0) {
788
+ components.Instances = { transforms: ir.instancing.transforms };
789
+ }
790
+ if (isCamera) {
791
+ components.Camera = {
792
+ fov: 0.7853981633974483,
793
+ aspect: 1.7777777777777777,
794
+ near: 0.1,
795
+ far: 100
796
+ };
797
+ }
798
+ const localIdx = resultNodes.length;
799
+ const node = {
800
+ localId: localIdx,
801
+ components,
802
+ localIdx
803
+ };
804
+ if (parentLocalIdx !== null) {
805
+ node.components.ChildOf = { parent: parentLocalIdx };
806
+ }
807
+ resultNodes.push(node);
808
+ const savedParent = mat4.clone(parentWorld);
809
+ for (let i = 0; i < 16; i++) parentWorld[i] = currentWorld[i] ?? 0;
810
+ for (const childIdx of ir.children) {
811
+ visit(childIdx, localIdx);
812
+ }
813
+ for (let i = 0; i < 16; i++) parentWorld[i] = savedParent[i] ?? 0;
814
+ };
815
+ for (const rootIdx of sceneIr.nodes) visit(rootIdx, null);
816
+ const frozen = resultNodes.map((n) => ({
817
+ localId: n.localId,
818
+ components: n.components
819
+ }));
820
+ const lightFacts = importedLights.map((light) => ({
821
+ kind: light.type,
822
+ intensity: light.intensity,
823
+ ...light.range === void 0 ? {} : { range: light.range },
824
+ ...light.spot === void 0 ? {} : { spot: light.spot }
825
+ }));
826
+ return {
827
+ kind: "scene",
828
+ entities: frozen,
829
+ ...lightFacts.length === 0 ? {} : { lights: lightFacts }
830
+ };
831
+ }
832
+ function textureInfo(info) {
833
+ return info === void 0 ? void 0 : typeof info === "number" ? { texture: info } : info;
834
+ }
835
+ function textureValue(info, slot, ctx) {
836
+ const binding = textureInfo(info);
837
+ if (binding === void 0 || ctx?.textureHandles === void 0) return void 0;
838
+ const textureHandle = ctx.textureHandles.get(binding.texture);
839
+ if (textureHandle === void 0) return void 0;
840
+ const samplerHandle = binding.sampler === void 0 ? void 0 : ctx.samplerHandles?.get(binding.sampler);
841
+ const coordinates = binding.texCoord === void 0 && binding.transform === void 0 ? void 0 : {
842
+ ...binding.texCoord === void 0 ? {} : { set: binding.texCoord },
843
+ ...binding.transform === void 0 ? {} : { transform: binding.transform }
844
+ };
845
+ const value = {
846
+ texture: textureHandle,
847
+ ...samplerHandle === void 0 ? {} : { sampler: samplerHandle },
848
+ ...coordinates === void 0 ? {} : { coordinates }
849
+ };
850
+ if (slot === "normalTexture") {
851
+ const normal = info;
852
+ if (typeof normal === "object" && normal?.scale !== void 0) {
853
+ return { ...value, normalScale: normal.scale };
854
+ }
855
+ }
856
+ if (slot === "occlusionTexture") {
857
+ const occlusion = info;
858
+ if (typeof occlusion === "object" && occlusion?.strength !== void 0) {
859
+ return { ...value, occlusionStrength: occlusion.strength };
860
+ }
861
+ }
862
+ return value;
863
+ }
864
+ function validateMaterialUvSets(mat, primitive, availableSets) {
865
+ const available = new Set(availableSets);
866
+ const slots = [
867
+ ["baseColorTexture", mat.baseColorTexture],
868
+ ["metallicRoughnessTexture", mat.metallicRoughnessTexture],
869
+ ["normalTexture", mat.normalTexture],
870
+ ["occlusionTexture", mat.occlusionTexture],
871
+ ["emissiveTexture", mat.emissiveTexture]
872
+ ];
873
+ for (const [slot, rawBinding] of slots) {
874
+ const binding = textureInfo(rawBinding);
875
+ if (binding === void 0) continue;
876
+ const requestedSet = binding.texCoord ?? 0;
877
+ if (!available.has(requestedSet)) {
878
+ return err(
879
+ createMaterialError("gltf-material-uv-set-missing", {
880
+ material: mat.name ?? "<unnamed>",
881
+ primitive,
882
+ slot,
883
+ requestedSet,
884
+ availableSets
885
+ })
886
+ );
887
+ }
888
+ }
889
+ return ok(void 0);
890
+ }
891
+ function toMaterialAsset(mat, ctx) {
892
+ const values = {
893
+ baseColor: mat.baseColorFactor,
894
+ metallic: mat.metallicFactor,
895
+ roughness: mat.roughnessFactor
896
+ };
897
+ if (mat.emissiveFactor !== void 0) {
898
+ values.emissive = mat.emissiveFactor;
899
+ values.emissiveIntensity = 1;
900
+ }
901
+ const textureSlots = [
902
+ ["baseColorTexture", mat.baseColorTexture],
903
+ ["metallicRoughnessTexture", mat.metallicRoughnessTexture],
904
+ ["normalTexture", mat.normalTexture],
905
+ ["occlusionTexture", mat.occlusionTexture],
906
+ ["emissiveTexture", mat.emissiveTexture]
907
+ ];
908
+ for (const [slot, info] of textureSlots) {
909
+ const value = textureValue(info, slot, ctx);
910
+ if (value !== void 0) values[slot] = value;
911
+ }
912
+ if (mat.occlusionTexture !== void 0 && values.occlusionStrength === void 0) {
913
+ values.occlusionStrength = 1;
914
+ }
915
+ const module = ctx?.skinned === true ? "forgeax::pbr-skin" : "forgeax::default-standard-pbr";
916
+ const isMask = mat.alphaMode === "MASK";
917
+ const alphaCutoff = isMask ? mat.alphaCutoff ?? 0.5 : void 0;
918
+ if (alphaCutoff !== void 0) values.alphaCutoff = alphaCutoff;
919
+ const isBlend = mat.alphaMode === "BLEND";
920
+ const straightAlphaBlend = {
921
+ color: {
922
+ srcFactor: "src-alpha",
923
+ dstFactor: "one-minus-src-alpha",
924
+ operation: "add"
925
+ },
926
+ alpha: {
927
+ srcFactor: "one",
928
+ dstFactor: "one-minus-src-alpha",
929
+ operation: "add"
930
+ }
931
+ };
932
+ const pass = {
933
+ name: "Forward",
934
+ program: { module },
935
+ renderState: {
936
+ tags: { LightMode: "Forward" },
937
+ queue: isBlend ? 3e3 : isMask ? 2450 : 2e3,
938
+ ...isBlend || isMask || mat.doubleSided === true ? {
939
+ ...isBlend ? { blend: straightAlphaBlend, depthWriteEnabled: false } : {},
940
+ ...mat.doubleSided === true ? { cullMode: "none" } : {}
941
+ } : {}
942
+ }
943
+ };
944
+ return {
945
+ kind: "material",
946
+ colorSpace: "linear",
947
+ ...ctx?.standardRootGuid === void 0 ? {} : { parent: ctx.standardRootGuid },
948
+ passes: [pass],
949
+ values
950
+ };
951
+ }
952
+
953
+ // src/check-extensions.ts
954
+ var EXTENSION_ALLOWLIST = [
955
+ "EXT_mesh_gpu_instancing",
956
+ "EXT_meshopt_compression"
957
+ ];
958
+ var SUPPORTED_EXTENSIONS = [
959
+ ...EXTENSION_ALLOWLIST,
960
+ "KHR_lights_punctual",
961
+ "KHR_texture_transform"
962
+ ];
963
+ function checkExtensions(json) {
964
+ const required = json.extensionsRequired ?? [];
965
+ for (const ext of required) {
966
+ if (!SUPPORTED_EXTENSIONS.includes(ext)) {
967
+ return err(
968
+ gltfErr("gltf-extension-unsupported", {
969
+ extension: ext,
970
+ source: "extensionsRequired"
971
+ })
972
+ );
973
+ }
974
+ }
975
+ const used = json.extensionsUsed ?? [];
976
+ const unsupportedUsed = [];
977
+ for (const ext of used) {
978
+ if (!SUPPORTED_EXTENSIONS.includes(ext)) {
979
+ unsupportedUsed.push(ext);
980
+ }
981
+ }
982
+ return ok({ unsupportedUsed });
983
+ }
984
+
985
+ // src/data-uri.ts
986
+ var DATA_URI_BASE64_RE = /^data:[^;,]*(?:;[^,;]+)*;base64,(.*)$/;
987
+ var Base64DecodeError = class extends Error {
988
+ constructor(cause) {
989
+ super(
990
+ `base64 payload decode failed: ${cause instanceof Error ? cause.message : String(cause)}`
991
+ );
992
+ this.name = "Base64DecodeError";
993
+ }
994
+ };
995
+ function dataUriBase64Payload(uri) {
996
+ const match = DATA_URI_BASE64_RE.exec(uri);
997
+ return match === null ? void 0 : match[1] ?? "";
998
+ }
999
+ function decodeBase64(b64) {
1000
+ let binary;
1001
+ try {
1002
+ binary = atob(b64);
1003
+ } catch (cause) {
1004
+ throw new Base64DecodeError(cause);
1005
+ }
1006
+ const out = new Uint8Array(binary.length);
1007
+ for (let i = 0; i < binary.length; i++) {
1008
+ out[i] = binary.charCodeAt(i);
1009
+ }
1010
+ return out;
1011
+ }
1012
+
1013
+ // src/parse-gltf-header.ts
1014
+ function parseGltfHeader(json, filePath) {
1015
+ if (json === null || typeof json !== "object" || !("asset" in json)) {
1016
+ return err(
1017
+ gltfErr("gltf-malformed-header", {
1018
+ filePath,
1019
+ byteOffset: 0
1020
+ })
1021
+ );
1022
+ }
1023
+ const asset = json.asset;
1024
+ if (!asset || typeof asset !== "object" || typeof asset.version !== "string") {
1025
+ return err(
1026
+ gltfErr("gltf-malformed-header", {
1027
+ filePath,
1028
+ byteOffset: 0
1029
+ })
1030
+ );
1031
+ }
1032
+ if (asset.version !== "2.0") {
1033
+ return err(
1034
+ gltfErr("gltf-version-unsupported", {
1035
+ filePath,
1036
+ actualVersion: asset.version
1037
+ })
1038
+ );
1039
+ }
1040
+ return ok(json);
1041
+ }
1042
+
1043
+ // src/parse-glb-chunks.ts
1044
+ var GLB_HEADER_SIZE = 12;
1045
+ var GLB_MAGIC = 1179937895;
1046
+ var CHUNK_HEADER_SIZE = 8;
1047
+ var CHUNK_TYPE_JSON = 1313821514;
1048
+ var CHUNK_TYPE_BIN = 5130562;
1049
+ function parseGlbChunks(buffer, filePath) {
1050
+ if (buffer.byteLength < GLB_HEADER_SIZE) {
1051
+ return err(
1052
+ gltfErr("gltf-malformed-header", {
1053
+ filePath,
1054
+ byteOffset: 0
1055
+ })
1056
+ );
1057
+ }
1058
+ const view = new DataView(buffer);
1059
+ const magic = view.getUint32(0, true);
1060
+ if (magic !== GLB_MAGIC) {
1061
+ return err(
1062
+ gltfErr("gltf-malformed-header", {
1063
+ filePath,
1064
+ byteOffset: 0,
1065
+ magic
1066
+ })
1067
+ );
1068
+ }
1069
+ const version = view.getUint32(4, true);
1070
+ if (version !== 2) {
1071
+ return err(
1072
+ gltfErr("gltf-version-unsupported", {
1073
+ filePath,
1074
+ actualVersion: String(version)
1075
+ })
1076
+ );
1077
+ }
1078
+ const declaredLength = view.getUint32(8, true);
1079
+ if (declaredLength !== buffer.byteLength) {
1080
+ return err(
1081
+ gltfErr("gltf-malformed-header", {
1082
+ filePath,
1083
+ byteOffset: 8
1084
+ })
1085
+ );
1086
+ }
1087
+ let offset = GLB_HEADER_SIZE;
1088
+ if (offset + CHUNK_HEADER_SIZE > buffer.byteLength) {
1089
+ return err(
1090
+ gltfErr("gltf-malformed-header", {
1091
+ filePath,
1092
+ byteOffset: offset
1093
+ })
1094
+ );
1095
+ }
1096
+ const jsonChunkLength = view.getUint32(offset, true);
1097
+ const jsonChunkType = view.getUint32(offset + 4, true);
1098
+ if (jsonChunkType !== CHUNK_TYPE_JSON) {
1099
+ return err(
1100
+ gltfErr("gltf-malformed-header", {
1101
+ filePath,
1102
+ byteOffset: offset + 4
1103
+ })
1104
+ );
1105
+ }
1106
+ if (offset + CHUNK_HEADER_SIZE + jsonChunkLength > buffer.byteLength) {
1107
+ return err(
1108
+ gltfErr("gltf-malformed-header", {
1109
+ filePath,
1110
+ byteOffset: offset
1111
+ })
1112
+ );
1113
+ }
1114
+ const jsonChunk = new Uint8Array(buffer, offset + CHUNK_HEADER_SIZE, jsonChunkLength);
1115
+ offset += CHUNK_HEADER_SIZE + jsonChunkLength;
1116
+ let binChunk;
1117
+ if (offset < buffer.byteLength) {
1118
+ if (offset + CHUNK_HEADER_SIZE > buffer.byteLength) {
1119
+ return err(
1120
+ gltfErr("gltf-malformed-header", {
1121
+ filePath,
1122
+ byteOffset: offset
1123
+ })
1124
+ );
1125
+ }
1126
+ const binChunkLength = view.getUint32(offset, true);
1127
+ const binChunkType = view.getUint32(offset + 4, true);
1128
+ if (binChunkType !== CHUNK_TYPE_BIN) {
1129
+ return err(
1130
+ gltfErr("gltf-malformed-header", {
1131
+ filePath,
1132
+ byteOffset: offset + 4
1133
+ })
1134
+ );
1135
+ }
1136
+ if (offset + CHUNK_HEADER_SIZE + binChunkLength > buffer.byteLength) {
1137
+ return err(
1138
+ gltfErr("gltf-malformed-header", {
1139
+ filePath,
1140
+ byteOffset: offset
1141
+ })
1142
+ );
1143
+ }
1144
+ binChunk = new Uint8Array(buffer, offset + CHUNK_HEADER_SIZE, binChunkLength);
1145
+ }
1146
+ return ok({
1147
+ version,
1148
+ length: declaredLength,
1149
+ jsonChunk,
1150
+ ...binChunk === void 0 ? {} : { binChunk }
1151
+ });
1152
+ }
1153
+
1154
+ // src/extract-image-bytes.ts
1155
+ var DATA_URI_MIME_RE = /^data:([^;,]+)(?:;[^,;]+)*;base64,/;
1156
+ function describeImportError(err2) {
1157
+ return err2.message;
1158
+ }
1159
+ function classifyMime(declaredMime, bytes) {
1160
+ if (declaredMime === "image/png" || declaredMime === "image/jpeg") return declaredMime;
1161
+ if (bytes.length >= 8 && bytes[0] === 137 && bytes[1] === 80 && bytes[2] === 78 && bytes[3] === 71) {
1162
+ return "image/png";
1163
+ }
1164
+ if (bytes.length >= 3 && bytes[0] === 255 && bytes[1] === 216 && bytes[2] === 255) {
1165
+ return "image/jpeg";
1166
+ }
1167
+ return void 0;
1168
+ }
1169
+ async function extractImageBytes(sourceBytes, sourcePath, ctx) {
1170
+ const isGlb = sourcePath.toLowerCase().endsWith(".glb");
1171
+ let json;
1172
+ let glbBin;
1173
+ if (isGlb) {
1174
+ const ab = sourceBytes.buffer.slice(
1175
+ sourceBytes.byteOffset,
1176
+ sourceBytes.byteOffset + sourceBytes.byteLength
1177
+ );
1178
+ const chunks = parseGlbChunks(ab, sourcePath);
1179
+ if (!chunks.ok) {
1180
+ return {
1181
+ extracted: /* @__PURE__ */ new Map(),
1182
+ failures: [
1183
+ {
1184
+ imageIndex: -1,
1185
+ source: "bufferView",
1186
+ reason: `GLB chunk parse failed: ${chunks.error.code}`
1187
+ }
1188
+ ]
1189
+ };
1190
+ }
1191
+ try {
1192
+ json = JSON.parse(new TextDecoder().decode(chunks.value.jsonChunk));
1193
+ } catch (e) {
1194
+ return {
1195
+ extracted: /* @__PURE__ */ new Map(),
1196
+ failures: [
1197
+ {
1198
+ imageIndex: -1,
1199
+ source: "bufferView",
1200
+ reason: `GLB JSON parse failed: ${e instanceof Error ? e.message : String(e)}`
1201
+ }
1202
+ ]
1203
+ };
1204
+ }
1205
+ glbBin = chunks.value.binChunk;
1206
+ } else {
1207
+ try {
1208
+ json = JSON.parse(new TextDecoder().decode(sourceBytes));
1209
+ } catch (e) {
1210
+ return {
1211
+ extracted: /* @__PURE__ */ new Map(),
1212
+ failures: [
1213
+ {
1214
+ imageIndex: -1,
1215
+ source: "bufferView",
1216
+ reason: `gltf JSON parse failed: ${e instanceof Error ? e.message : String(e)}`
1217
+ }
1218
+ ]
1219
+ };
1220
+ }
1221
+ }
1222
+ const images = json.images ?? [];
1223
+ const bufferViews = json.bufferViews ?? [];
1224
+ const buffersJson = json.buffers ?? [];
1225
+ const extracted = /* @__PURE__ */ new Map();
1226
+ const failures = [];
1227
+ const buffersCache = /* @__PURE__ */ new Map();
1228
+ async function getBuffer(bufferIndex) {
1229
+ const cached = buffersCache.get(bufferIndex);
1230
+ if (cached !== void 0) {
1231
+ if (cached instanceof Uint8Array) return cached;
1232
+ const e = cached.error;
1233
+ return { error: typeof e === "string" ? e : e.message };
1234
+ }
1235
+ const bufJson = buffersJson[bufferIndex];
1236
+ if (bufJson === void 0) {
1237
+ const reason = `buffer index ${bufferIndex} out of range (have ${buffersJson.length})`;
1238
+ buffersCache.set(bufferIndex, { error: reason });
1239
+ return { error: reason };
1240
+ }
1241
+ if (bufJson.uri === void 0) {
1242
+ if (glbBin === void 0) {
1243
+ const reason = `buffer ${bufferIndex} has no uri and no GLB BIN chunk available`;
1244
+ buffersCache.set(bufferIndex, { error: reason });
1245
+ return { error: reason };
1246
+ }
1247
+ buffersCache.set(bufferIndex, glbBin);
1248
+ return glbBin;
1249
+ }
1250
+ const dataPayload = dataUriBase64Payload(bufJson.uri);
1251
+ if (dataPayload !== void 0) {
1252
+ try {
1253
+ const bytes = decodeBase64(dataPayload);
1254
+ buffersCache.set(bufferIndex, bytes);
1255
+ return bytes;
1256
+ } catch (e) {
1257
+ const reason = `buffer ${bufferIndex} data URI base64 decode failed: ${e instanceof Error ? e.message : String(e)}`;
1258
+ buffersCache.set(bufferIndex, { error: reason });
1259
+ return { error: reason };
1260
+ }
1261
+ }
1262
+ const sib = await ctx.readSibling(bufJson.uri);
1263
+ if (!sib.ok) {
1264
+ const reason = `external buffer "${bufJson.uri}" read failed: ${describeImportError(sib.error)}`;
1265
+ buffersCache.set(bufferIndex, { error: reason });
1266
+ return { error: reason };
1267
+ }
1268
+ buffersCache.set(bufferIndex, sib.value);
1269
+ return sib.value;
1270
+ }
1271
+ for (let i = 0; i < images.length; i++) {
1272
+ const img = images[i];
1273
+ if (img === void 0) continue;
1274
+ if (img.bufferView !== void 0) {
1275
+ const bv = bufferViews[img.bufferView];
1276
+ if (bv === void 0) {
1277
+ failures.push({
1278
+ imageIndex: i,
1279
+ source: "bufferView",
1280
+ reason: `bufferView index ${img.bufferView} out of range`
1281
+ });
1282
+ continue;
1283
+ }
1284
+ const buf = await getBuffer(bv.buffer);
1285
+ if (!(buf instanceof Uint8Array)) {
1286
+ failures.push({ imageIndex: i, source: "bufferView", reason: buf.error });
1287
+ continue;
1288
+ }
1289
+ const off = bv.byteOffset ?? 0;
1290
+ const len = bv.byteLength;
1291
+ if (off + len > buf.byteLength) {
1292
+ failures.push({
1293
+ imageIndex: i,
1294
+ source: "bufferView",
1295
+ reason: `bufferView ${img.bufferView} byte range [${off}..${off + len}) exceeds buffer ${bv.buffer} length ${buf.byteLength}`
1296
+ });
1297
+ continue;
1298
+ }
1299
+ const bytes = buf.subarray(off, off + len);
1300
+ const mime = classifyMime(img.mimeType, bytes);
1301
+ if (mime === void 0) {
1302
+ failures.push({
1303
+ imageIndex: i,
1304
+ source: "bufferView",
1305
+ reason: `unsupported / unrecognised mime (declared "${img.mimeType ?? "<absent>"}", magic byte sniff failed)`
1306
+ });
1307
+ continue;
1308
+ }
1309
+ extracted.set(i, { bytes: new Uint8Array(bytes), mimeType: mime, source: "bufferView" });
1310
+ continue;
1311
+ }
1312
+ if (img.uri !== void 0) {
1313
+ const dataPayload = dataUriBase64Payload(img.uri);
1314
+ if (dataPayload !== void 0) {
1315
+ let bytes;
1316
+ try {
1317
+ bytes = decodeBase64(dataPayload);
1318
+ } catch (e) {
1319
+ failures.push({
1320
+ imageIndex: i,
1321
+ source: "data-uri",
1322
+ reason: `base64 decode failed: ${e instanceof Error ? e.message : String(e)}`
1323
+ });
1324
+ continue;
1325
+ }
1326
+ const mimeMatch = DATA_URI_MIME_RE.exec(img.uri);
1327
+ const declaredMime = mimeMatch?.[1] ?? img.mimeType;
1328
+ const mime2 = classifyMime(declaredMime, bytes);
1329
+ if (mime2 === void 0) {
1330
+ failures.push({
1331
+ imageIndex: i,
1332
+ source: "data-uri",
1333
+ reason: `data: URI mime "${declaredMime ?? "<absent>"}" not png/jpeg`
1334
+ });
1335
+ continue;
1336
+ }
1337
+ extracted.set(i, { bytes, mimeType: mime2, source: "data-uri" });
1338
+ continue;
1339
+ }
1340
+ const sib = await ctx.readSibling(img.uri);
1341
+ if (!sib.ok) {
1342
+ failures.push({
1343
+ imageIndex: i,
1344
+ source: "external-uri",
1345
+ reason: `external URI "${img.uri}" read failed: ${describeImportError(sib.error)}`
1346
+ });
1347
+ continue;
1348
+ }
1349
+ const mime = classifyMime(img.mimeType, sib.value);
1350
+ if (mime === void 0) {
1351
+ failures.push({
1352
+ imageIndex: i,
1353
+ source: "external-uri",
1354
+ reason: `external URI "${img.uri}" mime not png/jpeg (declared "${img.mimeType ?? "<absent>"}", magic byte sniff failed)`
1355
+ });
1356
+ continue;
1357
+ }
1358
+ extracted.set(i, {
1359
+ bytes: new Uint8Array(sib.value),
1360
+ mimeType: mime,
1361
+ source: "external-uri"
1362
+ });
1363
+ continue;
1364
+ }
1365
+ failures.push({
1366
+ imageIndex: i,
1367
+ source: "bufferView",
1368
+ reason: "image row has neither bufferView nor uri"
1369
+ });
1370
+ }
1371
+ return { extracted, failures };
1372
+ }
1373
+
1374
+ // src/image-color-space.ts
1375
+ function deriveTextureColorSpace(input) {
1376
+ const result = /* @__PURE__ */ new Map();
1377
+ const textures = input.textures ?? [];
1378
+ function imageOfTexture(binding) {
1379
+ if (binding === void 0) return void 0;
1380
+ const textureIndex = typeof binding === "number" ? binding : binding.texture;
1381
+ const tex = textures[textureIndex];
1382
+ if (tex === void 0) return void 0;
1383
+ return tex.source;
1384
+ }
1385
+ function record(imageIndex, colorSpace) {
1386
+ if (imageIndex === void 0) return;
1387
+ const prior = result.get(imageIndex);
1388
+ if (prior === void 0) {
1389
+ result.set(imageIndex, colorSpace);
1390
+ return;
1391
+ }
1392
+ if (prior === "srgb" || colorSpace === "srgb") {
1393
+ result.set(imageIndex, "srgb");
1394
+ }
1395
+ }
1396
+ for (const mat of input.materials) {
1397
+ record(imageOfTexture(mat.baseColorTexture), "srgb");
1398
+ record(imageOfTexture(mat.emissiveTexture), "srgb");
1399
+ record(imageOfTexture(mat.metallicRoughnessTexture), "linear");
1400
+ record(imageOfTexture(mat.normalTexture), "linear");
1401
+ record(imageOfTexture(mat.occlusionTexture), "linear");
1402
+ }
1403
+ for (let i = 0; i < input.imageCount; i++) {
1404
+ if (!result.has(i)) {
1405
+ result.set(i, "linear");
1406
+ }
1407
+ }
1408
+ return result;
1409
+ }
1410
+
1411
+ // src/meshopt-decode.ts
1412
+ function viewBytes(view, buffers) {
1413
+ const buffer = buffers[view.buffer];
1414
+ const offset = view.byteOffset ?? 0;
1415
+ if (buffer === void 0 || view.byteLength <= 0 || offset < 0 || offset + view.byteLength > buffer.length) {
1416
+ return void 0;
1417
+ }
1418
+ return buffer.subarray(offset, offset + view.byteLength);
1419
+ }
1420
+ function validMode(mode) {
1421
+ return GLTF_MESHOPT_MODES.some((candidate) => candidate === mode);
1422
+ }
1423
+ function validFilter(filter) {
1424
+ return GLTF_MESHOPT_FILTERS.some((candidate) => candidate === filter);
1425
+ }
1426
+ function filterAllowed(mode, filter) {
1427
+ return mode === "ATTRIBUTES" || filter === "NONE";
1428
+ }
1429
+ async function projectMeshoptBufferViews(inputViews, inputBuffers, extensionsRequired, capability) {
1430
+ const buffers = [...inputBuffers];
1431
+ const bufferViews = inputViews.map((view) => ({ ...view }));
1432
+ let decodedCount = 0;
1433
+ for (let index = 0; index < bufferViews.length; index++) {
1434
+ const view = bufferViews[index];
1435
+ if (view === void 0) continue;
1436
+ const extension = view.extensions?.EXT_meshopt_compression;
1437
+ if (extension === void 0) continue;
1438
+ const filter = extension.filter ?? "NONE";
1439
+ if (!validMode(extension.mode) || !validFilter(filter) || !filterAllowed(extension.mode, filter) || !Number.isInteger(extension.byteStride) || extension.byteStride <= 0 || !Number.isInteger(extension.count) || extension.count <= 0 || !Number.isInteger(extension.byteLength) || extension.byteLength <= 0) {
1440
+ return err(
1441
+ gltfErr("gltf-meshopt-decode-failed", {
1442
+ bufferView: index,
1443
+ actual: "invalid mode/filter/stride/count/byteLength",
1444
+ mode: validMode(extension.mode) ? extension.mode : "ATTRIBUTES",
1445
+ filter: validFilter(filter) ? filter : "NONE"
1446
+ })
1447
+ );
1448
+ }
1449
+ const fallback = viewBytes(view, buffers);
1450
+ const required = extensionsRequired.includes("EXT_meshopt_compression");
1451
+ if (capability === void 0 && !required && fallback !== void 0) continue;
1452
+ if (capability === void 0) {
1453
+ return err(
1454
+ gltfErr("gltf-meshopt-decoder-required", {
1455
+ bufferView: index,
1456
+ actual: required ? "required" : "compressed-only",
1457
+ hasCoreFallback: fallback !== void 0
1458
+ })
1459
+ );
1460
+ }
1461
+ const compressedBuffer = buffers[extension.buffer];
1462
+ const compressedOffset = extension.byteOffset ?? 0;
1463
+ if (compressedBuffer === void 0 || !Number.isInteger(compressedOffset) || compressedOffset < 0 || compressedOffset + extension.byteLength > compressedBuffer.length) {
1464
+ return err(
1465
+ gltfErr("gltf-meshopt-decode-failed", {
1466
+ bufferView: index,
1467
+ actual: "compressed range out of bounds",
1468
+ mode: extension.mode,
1469
+ filter
1470
+ })
1471
+ );
1472
+ }
1473
+ let decoded;
1474
+ try {
1475
+ decoded = await capability.decode({
1476
+ source: compressedBuffer.subarray(
1477
+ compressedOffset,
1478
+ compressedOffset + extension.byteLength
1479
+ ),
1480
+ count: extension.count,
1481
+ stride: extension.byteStride,
1482
+ mode: extension.mode,
1483
+ filter
1484
+ });
1485
+ } catch (cause) {
1486
+ return err(
1487
+ gltfErr("gltf-meshopt-decode-failed", {
1488
+ bufferView: index,
1489
+ actual: `decoder rejected compressed range: ${cause instanceof Error ? cause.message : String(cause)}`,
1490
+ mode: extension.mode,
1491
+ filter
1492
+ })
1493
+ );
1494
+ }
1495
+ const expectedLength = extension.count * extension.byteStride;
1496
+ if (!(decoded instanceof Uint8Array) || decoded.byteLength !== expectedLength) {
1497
+ return err(
1498
+ gltfErr("gltf-meshopt-decode-failed", {
1499
+ bufferView: index,
1500
+ actual: `decoded byteLength=${decoded?.byteLength ?? "invalid"}, expected=${expectedLength}`,
1501
+ mode: extension.mode,
1502
+ filter
1503
+ })
1504
+ );
1505
+ }
1506
+ const decodedBufferIndex = buffers.length;
1507
+ buffers.push(decoded);
1508
+ const { extensions: _extensions, ...plainView } = view;
1509
+ bufferViews[index] = {
1510
+ ...plainView,
1511
+ buffer: decodedBufferIndex,
1512
+ byteOffset: 0,
1513
+ byteLength: expectedLength
1514
+ };
1515
+ decodedCount++;
1516
+ }
1517
+ return ok({ bufferViews, buffers, decodedCount });
1518
+ }
1519
+
1520
+ // src/node-path.ts
1521
+ function buildNodeParentMap(nodes) {
1522
+ const parents = /* @__PURE__ */ new Map();
1523
+ for (let index = 0; index < nodes.length; index++) {
1524
+ for (const child of nodes[index]?.children ?? []) parents.set(child, index);
1525
+ }
1526
+ return parents;
1527
+ }
1528
+ function resolveNamedNodePath(nodes, parents, nodeIndex) {
1529
+ const reversed = [];
1530
+ const visited = /* @__PURE__ */ new Set();
1531
+ let current = nodeIndex;
1532
+ while (current !== void 0) {
1533
+ if (visited.has(current)) {
1534
+ return { ok: false, reason: "hierarchy-cycle", nodeIndex: current };
1535
+ }
1536
+ visited.add(current);
1537
+ const name = nodes[current]?.name;
1538
+ if (name === void 0 || name.length === 0) {
1539
+ return { ok: false, reason: "name-missing", nodeIndex: current };
1540
+ }
1541
+ reversed.push(name);
1542
+ current = parents.get(current);
1543
+ }
1544
+ return { ok: true, value: reversed.reverse() };
1545
+ }
1546
+
1547
+ // src/parse-animation.ts
1548
+ var ANIMATION_ACCESSOR_TYPES = ["SCALAR", "VEC2", "VEC3", "VEC4"];
1549
+ function parseAnimation(animationsJson, nodesJson, accessors, bufferViews, buffers) {
1550
+ if (animationsJson === void 0 || animationsJson.length === 0) {
1551
+ return ok([]);
1552
+ }
1553
+ const clips = [];
1554
+ const parentOf = buildNodeParentMap(nodesJson);
1555
+ const nodeByPath = /* @__PURE__ */ new Map();
1556
+ const pathById = /* @__PURE__ */ new Map();
1557
+ for (let animIdx = 0; animIdx < animationsJson.length; animIdx++) {
1558
+ const anim = animationsJson[animIdx];
1559
+ if (anim === void 0) continue;
1560
+ const decodedSamplers = [];
1561
+ for (let sampIdx = 0; sampIdx < anim.samplers.length; sampIdx++) {
1562
+ const sampler = anim.samplers[sampIdx];
1563
+ if (sampler === void 0) continue;
1564
+ const interpolation = sampler.interpolation ?? "LINEAR";
1565
+ if (interpolation === "CUBICSPLINE") {
1566
+ return err(
1567
+ gltfErr("gltf-animation-cubicspline-unsupported", {
1568
+ animationIndex: animIdx,
1569
+ samplerIndex: sampIdx
1570
+ })
1571
+ );
1572
+ }
1573
+ if (interpolation !== "LINEAR" && interpolation !== "STEP") {
1574
+ return err(
1575
+ gltfErr("gltf-animation-cubicspline-unsupported", {
1576
+ animationIndex: animIdx,
1577
+ samplerIndex: sampIdx
1578
+ })
1579
+ );
1580
+ }
1581
+ const inputAcc = accessors[sampler.input];
1582
+ if (inputAcc === void 0) {
1583
+ return err(
1584
+ gltfErr("gltf-buffer-out-of-bounds", {
1585
+ accessor: sampler.input,
1586
+ byteOffset: 0,
1587
+ byteLength: 0,
1588
+ bufferIndex: 0
1589
+ })
1590
+ );
1591
+ }
1592
+ const inputResult = decodeF32Accessor(
1593
+ sampler.input,
1594
+ inputAcc,
1595
+ ANIMATION_ACCESSOR_TYPES,
1596
+ bufferViews,
1597
+ buffers
1598
+ );
1599
+ if (!inputResult.ok) return err(inputResult.error);
1600
+ const outputAcc = accessors[sampler.output];
1601
+ if (outputAcc === void 0) {
1602
+ return err(
1603
+ gltfErr("gltf-buffer-out-of-bounds", {
1604
+ accessor: sampler.output,
1605
+ byteOffset: 0,
1606
+ byteLength: 0,
1607
+ bufferIndex: 0
1608
+ })
1609
+ );
1610
+ }
1611
+ const outputResult = decodeF32Accessor(
1612
+ sampler.output,
1613
+ outputAcc,
1614
+ ANIMATION_ACCESSOR_TYPES,
1615
+ bufferViews,
1616
+ buffers
1617
+ );
1618
+ if (!outputResult.ok) return err(outputResult.error);
1619
+ decodedSamplers.push({
1620
+ input: inputResult.value,
1621
+ output: outputResult.value,
1622
+ interpolation
1623
+ });
1624
+ }
1625
+ const channels = [];
1626
+ for (let chIdx = 0; chIdx < anim.channels.length; chIdx++) {
1627
+ const ch = anim.channels[chIdx];
1628
+ if (ch === void 0) continue;
1629
+ const samplerRecord = decodedSamplers[ch.sampler];
1630
+ if (samplerRecord === void 0) {
1631
+ return err(
1632
+ gltfErr("gltf-buffer-out-of-bounds", {
1633
+ accessor: ch.sampler,
1634
+ byteOffset: 0,
1635
+ byteLength: 0,
1636
+ bufferIndex: 0
1637
+ })
1638
+ );
1639
+ }
1640
+ const targetNodeIdx = ch.target.node;
1641
+ const path = targetNodeIdx === void 0 ? { ok: false, reason: "name-missing", nodeIndex: -1 } : resolveNamedNodePath(nodesJson, parentOf, targetNodeIdx);
1642
+ if (!path.ok) {
1643
+ return err(
1644
+ gltfErr("gltf-animation-target-invalid", {
1645
+ reason: path.reason,
1646
+ animationIndex: animIdx,
1647
+ channelIndex: chIdx,
1648
+ nodeIndex: path.nodeIndex
1649
+ })
1650
+ );
1651
+ }
1652
+ if (ch.target.path !== "weights" && ch.target.path !== "translation" && ch.target.path !== "rotation" && ch.target.path !== "scale") {
1653
+ return err(
1654
+ gltfErr("gltf-morph-unsupported", {
1655
+ animationIndex: animIdx,
1656
+ channelIndex: chIdx,
1657
+ nodeIndex: ch.target.node ?? -1
1658
+ })
1659
+ );
1660
+ }
1661
+ const pathKey = JSON.stringify(path.value);
1662
+ const previousNode = nodeByPath.get(pathKey);
1663
+ if (previousNode !== void 0 && previousNode !== targetNodeIdx) {
1664
+ return err(
1665
+ gltfErr("gltf-animation-target-invalid", {
1666
+ reason: "path-duplicate",
1667
+ animationIndex: animIdx,
1668
+ channelIndex: chIdx,
1669
+ nodeIndex: targetNodeIdx
1670
+ })
1671
+ );
1672
+ }
1673
+ nodeByPath.set(pathKey, targetNodeIdx);
1674
+ const targetId = deriveAnimationTargetId(path.value);
1675
+ const previousPath = pathById.get(targetId);
1676
+ if (previousPath !== void 0 && previousPath !== pathKey) {
1677
+ return err(
1678
+ gltfErr("gltf-animation-target-invalid", {
1679
+ reason: "id-collision",
1680
+ animationIndex: animIdx,
1681
+ channelIndex: chIdx,
1682
+ nodeIndex: targetNodeIdx
1683
+ })
1684
+ );
1685
+ }
1686
+ pathById.set(targetId, pathKey);
1687
+ channels.push({
1688
+ targetId,
1689
+ targetNodeIndex: targetNodeIdx,
1690
+ property: ch.target.path,
1691
+ sampler: samplerRecord
1692
+ });
1693
+ }
1694
+ let duration = 0;
1695
+ for (const ch of channels) {
1696
+ const input = ch.sampler.input;
1697
+ if (input.length > 0) {
1698
+ const last = input[input.length - 1];
1699
+ if (last !== void 0 && last > duration) {
1700
+ duration = last;
1701
+ }
1702
+ }
1703
+ }
1704
+ const name = anim.name?.trim();
1705
+ clips.push({
1706
+ ...name === void 0 || name.length === 0 ? {} : { name },
1707
+ duration,
1708
+ channels
1709
+ });
1710
+ }
1711
+ return ok(clips);
1712
+ }
1713
+
1714
+ // src/parse-skin.ts
1715
+ var MAX_JOINTS = 256;
1716
+ var SKIN_ACCESSOR_TYPES = ["MAT4"];
1717
+ function identityMat4() {
1718
+ const m = new Float32Array(16);
1719
+ m[0] = 1;
1720
+ m[5] = 1;
1721
+ m[10] = 1;
1722
+ m[15] = 1;
1723
+ return m;
1724
+ }
1725
+ function resolveJointPath(nodeIndex, nodes, parentOf, skinIndex, jointPathIndex) {
1726
+ const path = resolveNamedNodePath(nodes, parentOf, nodeIndex);
1727
+ if (!path.ok) {
1728
+ return err(
1729
+ gltfErr("gltf-skin-joint-name-missing", {
1730
+ reason: path.reason,
1731
+ skinIndex,
1732
+ jointPathIndex,
1733
+ nodeIndex: path.nodeIndex
1734
+ })
1735
+ );
1736
+ }
1737
+ return ok(path.value);
1738
+ }
1739
+ function parseSkin(skinsJson, nodesJson, accessors, bufferViews, buffers) {
1740
+ if (skinsJson === void 0 || skinsJson.length === 0) {
1741
+ return ok([]);
1742
+ }
1743
+ const records = [];
1744
+ const parentOf = buildNodeParentMap(nodesJson);
1745
+ for (let skinIdx = 0; skinIdx < skinsJson.length; skinIdx++) {
1746
+ const skin = skinsJson[skinIdx];
1747
+ if (skin === void 0) continue;
1748
+ const joints = skin.joints;
1749
+ if (joints.length > MAX_JOINTS) {
1750
+ return err(
1751
+ gltfErr("gltf-skin-joint-count-exceeded", {
1752
+ skinIndex: skinIdx,
1753
+ jointCount: joints.length,
1754
+ maxJoints: MAX_JOINTS
1755
+ })
1756
+ );
1757
+ }
1758
+ let ibm;
1759
+ if (skin.inverseBindMatrices !== void 0) {
1760
+ const ibmAccIdx = skin.inverseBindMatrices;
1761
+ const ibmAcc = accessors[ibmAccIdx];
1762
+ if (ibmAcc === void 0) {
1763
+ return err(
1764
+ gltfErr("gltf-buffer-out-of-bounds", {
1765
+ accessor: ibmAccIdx,
1766
+ byteOffset: 0,
1767
+ byteLength: 0,
1768
+ bufferIndex: 0
1769
+ })
1770
+ );
1771
+ }
1772
+ const ibmResult = decodeF32Accessor(
1773
+ ibmAccIdx,
1774
+ ibmAcc,
1775
+ SKIN_ACCESSOR_TYPES,
1776
+ bufferViews,
1777
+ buffers
1778
+ );
1779
+ if (!ibmResult.ok) return err(ibmResult.error);
1780
+ ibm = ibmResult.value;
1781
+ } else {
1782
+ ibm = new Float32Array(joints.length * 16);
1783
+ for (let j = 0; j < joints.length; j++) {
1784
+ ibm.set(identityMat4(), j * 16);
1785
+ }
1786
+ }
1787
+ const jointPaths = [];
1788
+ for (let j = 0; j < joints.length; j++) {
1789
+ const jointNodeIndex = joints[j];
1790
+ if (jointNodeIndex === void 0) continue;
1791
+ const pathResult = resolveJointPath(jointNodeIndex, nodesJson, parentOf, skinIdx, j);
1792
+ if (!pathResult.ok) return err(pathResult.error);
1793
+ jointPaths.push(pathResult.value.join("/"));
1794
+ }
1795
+ records.push({
1796
+ jointCount: joints.length,
1797
+ inverseBindMatrices: ibm,
1798
+ jointPaths
1799
+ });
1800
+ }
1801
+ return ok(records);
1802
+ }
1803
+
1804
+ // src/source-key.ts
1805
+ function sourceKeyForGltfOutput(item) {
1806
+ const kind = item.kind.trim();
1807
+ if (kind.length === 0) return void 0;
1808
+ const name = item.name?.trim();
1809
+ return name === void 0 || name.length === 0 ? kind : `${kind}:${name}`;
1810
+ }
1811
+ function deriveGltfSourceKeys(items) {
1812
+ const keys = items.map((item) => sourceKeyForGltfOutput(item));
1813
+ const missing = items.map((item, index) => keys[index] === void 0 ? item.sourceIndex : void 0).filter((index) => index !== void 0);
1814
+ if (missing.length > 0) {
1815
+ return {
1816
+ ok: false,
1817
+ error: {
1818
+ code: "missing-source-key",
1819
+ expected: "every glTF output needs a stable semantic kind or name",
1820
+ hint: "publish a semantic kind/name key; do not use sourceIndex",
1821
+ detail: { sourceIndices: missing }
1822
+ }
1823
+ };
1824
+ }
1825
+ const seen = /* @__PURE__ */ new Map();
1826
+ for (let index = 0; index < keys.length; index++) {
1827
+ const key = keys[index];
1828
+ if (key === void 0) continue;
1829
+ const current = items[index];
1830
+ if (current === void 0) continue;
1831
+ const prior = seen.get(key);
1832
+ if (prior !== void 0) {
1833
+ const entry = (item) => {
1834
+ const name = item.name?.trim();
1835
+ return {
1836
+ kind: item.kind.trim(),
1837
+ name: name === void 0 || name.length === 0 ? null : name,
1838
+ sourceIndex: item.sourceIndex
1839
+ };
1840
+ };
1841
+ const ambiguous = key === current.kind.trim();
1842
+ return {
1843
+ ok: false,
1844
+ error: {
1845
+ code: ambiguous ? "ambiguous-source-key" : "duplicate-source-key",
1846
+ expected: "sourceKey values must be unique within one glTF package",
1847
+ hint: ambiguous ? "name each otherwise anonymous output; do not use sourceIndex" : "rename duplicate outputs before publishing topology facts",
1848
+ detail: {
1849
+ key,
1850
+ sourceIndices: [prior.sourceIndex, current.sourceIndex],
1851
+ entries: [entry(prior), entry(current)]
1852
+ }
1853
+ }
1854
+ };
1855
+ }
1856
+ seen.set(key, current);
1857
+ }
1858
+ return { ok: true, keys, conflicts: [] };
1859
+ }
1860
+
1861
+ // src/sub-asset-key.ts
1862
+ var PLURAL = {
1863
+ mesh: "meshes",
1864
+ material: "materials",
1865
+ scene: "scenes",
1866
+ node: "nodes",
1867
+ texture: "textures",
1868
+ image: "images",
1869
+ sampler: "samplers",
1870
+ animation: "animations",
1871
+ camera: "cameras",
1872
+ skin: "skins",
1873
+ skeleton: "skeletons",
1874
+ "animation-clip": "animation-clips"
1875
+ };
1876
+ function pluraliseKind(kind) {
1877
+ const known = PLURAL[kind];
1878
+ if (known !== void 0) return known;
1879
+ return `${kind}s`;
1880
+ }
1881
+ function subAssetKey(item) {
1882
+ return {
1883
+ kind: item.kind,
1884
+ name: item.name ?? null,
1885
+ indexFallback: `${pluraliseKind(item.kind)}/${item.sourceIndex}`
1886
+ };
1887
+ }
1888
+
1889
+ // src/reimport-reuse-meta.ts
1890
+ function reimportReuseMeta(items, existingMeta) {
1891
+ const sourceKeys = deriveGltfSourceKeys(items);
1892
+ if (!sourceKeys.ok) return err(sourceKeys.error);
1893
+ const stage1Index = /* @__PURE__ */ new Map();
1894
+ const stage2Index = /* @__PURE__ */ new Map();
1895
+ if (existingMeta !== void 0) {
1896
+ for (const entry of existingMeta.subAssets) {
1897
+ if (entry.sourceKey !== void 0) stage1Index.set(entry.sourceKey, entry);
1898
+ stage2Index.set(`${entry.kind} ${entry.sourceIndex}`, entry);
1899
+ }
1900
+ }
1901
+ const subAssets = [];
1902
+ for (const item of items) {
1903
+ const sourceKey = sourceKeyForGltfOutput(item);
1904
+ const indexKey = `${item.kind} ${item.sourceIndex}`;
1905
+ let reused;
1906
+ if (sourceKey !== void 0) reused = stage1Index.get(sourceKey);
1907
+ if (reused === void 0) {
1908
+ reused = stage2Index.get(indexKey);
1909
+ }
1910
+ if (reused !== void 0) {
1911
+ subAssets.push({
1912
+ guid: reused.guid,
1913
+ sourceIndex: item.sourceIndex,
1914
+ kind: item.kind,
1915
+ ...sourceKey === void 0 ? {} : { sourceKey },
1916
+ ...item.name === void 0 ? {} : { name: item.name }
1917
+ });
1918
+ } else {
1919
+ const fresh = AssetGuid.random();
1920
+ subAssets.push({
1921
+ guid: AssetGuid.format(fresh),
1922
+ sourceIndex: item.sourceIndex,
1923
+ kind: item.kind,
1924
+ ...sourceKey === void 0 ? {} : { sourceKey },
1925
+ ...item.name === void 0 ? {} : { name: item.name }
1926
+ });
1927
+ }
1928
+ }
1929
+ return ok({ subAssets });
1930
+ }
1931
+ var IDENTITY = {
1932
+ translation: [0, 0, 0],
1933
+ rotation: [0, 0, 0, 1],
1934
+ scale: [1, 1, 1]
1935
+ };
1936
+ function tuple3(arr, fallback) {
1937
+ if (arr === void 0 || arr.length < 3) return fallback;
1938
+ return [arr[0] ?? fallback[0], arr[1] ?? fallback[1], arr[2] ?? fallback[2]];
1939
+ }
1940
+ function tuple4(arr, fallback) {
1941
+ if (arr === void 0 || arr.length < 4) return fallback;
1942
+ return [
1943
+ arr[0] ?? fallback[0],
1944
+ arr[1] ?? fallback[1],
1945
+ arr[2] ?? fallback[2],
1946
+ arr[3] ?? fallback[3]
1947
+ ];
1948
+ }
1949
+ function decomposeNodeTransform(node, nodeIndex, diagnostics) {
1950
+ const hasMatrix = node.matrix !== void 0 && node.matrix.length === 16;
1951
+ const hasTrs = node.translation !== void 0 || node.rotation !== void 0 || node.scale !== void 0;
1952
+ if (hasMatrix) {
1953
+ if (hasTrs) {
1954
+ console.error(`[warn] node[${nodeIndex}] has both matrix and TRS, matrix takes precedence`);
1955
+ diagnostics.matrixTrsCoexistNodes.push(nodeIndex);
1956
+ }
1957
+ const out_t = vec3.create();
1958
+ const out_r = quat.create();
1959
+ const out_s = vec3.create(1, 1, 1);
1960
+ const m = node.matrix;
1961
+ mat4.decompose(out_t, out_r, out_s, m);
1962
+ return {
1963
+ translation: [out_t[0] ?? 0, out_t[1] ?? 0, out_t[2] ?? 0],
1964
+ rotation: [out_r[0] ?? 0, out_r[1] ?? 0, out_r[2] ?? 0, out_r[3] ?? 1],
1965
+ scale: [out_s[0] ?? 1, out_s[1] ?? 1, out_s[2] ?? 1]
1966
+ };
1967
+ }
1968
+ if (!hasTrs) {
1969
+ return IDENTITY;
1970
+ }
1971
+ return {
1972
+ translation: tuple3(node.translation, IDENTITY.translation),
1973
+ rotation: tuple4(node.rotation, IDENTITY.rotation),
1974
+ scale: tuple3(node.scale, IDENTITY.scale)
1975
+ };
1976
+ }
1977
+
1978
+ // src/parse-gltf.ts
1979
+ function decodeNodeInstancing(nodeIndex, attributes, accessors, bufferViews, buffers) {
1980
+ const tIdx = attributes.TRANSLATION;
1981
+ const rIdx = attributes.ROTATION;
1982
+ const sIdx = attributes.SCALE;
1983
+ let count;
1984
+ const setOrCheck = (label, n2) => {
1985
+ if (count === void 0) {
1986
+ count = n2;
1987
+ return null;
1988
+ }
1989
+ if (n2 !== count) {
1990
+ return gltfErr("gltf-instancing-count-mismatch", {
1991
+ nodeIndex,
1992
+ accessor: label,
1993
+ expectedCount: count,
1994
+ actualCount: n2
1995
+ });
1996
+ }
1997
+ return null;
1998
+ };
1999
+ let tValues;
2000
+ if (tIdx !== void 0) {
2001
+ const acc = accessors[tIdx];
2002
+ if (acc === void 0) return err(unknownAccessor(tIdx));
2003
+ const e = setOrCheck("TRANSLATION", acc.count);
2004
+ if (e !== null) return err(e);
2005
+ const decoded = decodeAttributeAccessor(tIdx, acc, bufferViews, buffers);
2006
+ if (!decoded.ok) return err(decoded.error);
2007
+ tValues = decoded.value;
2008
+ }
2009
+ let rValues;
2010
+ if (rIdx !== void 0) {
2011
+ const acc = accessors[rIdx];
2012
+ if (acc === void 0) return err(unknownAccessor(rIdx));
2013
+ const e = setOrCheck("ROTATION", acc.count);
2014
+ if (e !== null) return err(e);
2015
+ const decoded = decodeAttributeAccessor(rIdx, acc, bufferViews, buffers);
2016
+ if (!decoded.ok) return err(decoded.error);
2017
+ rValues = decoded.value;
2018
+ }
2019
+ let sValues;
2020
+ if (sIdx !== void 0) {
2021
+ const acc = accessors[sIdx];
2022
+ if (acc === void 0) return err(unknownAccessor(sIdx));
2023
+ const e = setOrCheck("SCALE", acc.count);
2024
+ if (e !== null) return err(e);
2025
+ const decoded = decodeAttributeAccessor(sIdx, acc, bufferViews, buffers);
2026
+ if (!decoded.ok) return err(decoded.error);
2027
+ sValues = decoded.value;
2028
+ }
2029
+ const n = count ?? 0;
2030
+ const transforms = new Float32Array(n * 16);
2031
+ const tmp = mat4.create();
2032
+ const tv = vec3.create();
2033
+ const rv = quat.create();
2034
+ rv[3] = 1;
2035
+ const sv = vec3.create(1, 1, 1);
2036
+ for (let i = 0; i < n; i++) {
2037
+ if (tValues !== void 0) {
2038
+ tv[0] = tValues[i * 3] ?? 0;
2039
+ tv[1] = tValues[i * 3 + 1] ?? 0;
2040
+ tv[2] = tValues[i * 3 + 2] ?? 0;
2041
+ } else {
2042
+ tv[0] = 0;
2043
+ tv[1] = 0;
2044
+ tv[2] = 0;
2045
+ }
2046
+ if (rValues !== void 0) {
2047
+ rv[0] = rValues[i * 4] ?? 0;
2048
+ rv[1] = rValues[i * 4 + 1] ?? 0;
2049
+ rv[2] = rValues[i * 4 + 2] ?? 0;
2050
+ rv[3] = rValues[i * 4 + 3] ?? 1;
2051
+ } else {
2052
+ rv[0] = 0;
2053
+ rv[1] = 0;
2054
+ rv[2] = 0;
2055
+ rv[3] = 1;
2056
+ }
2057
+ if (sValues !== void 0) {
2058
+ sv[0] = sValues[i * 3] ?? 1;
2059
+ sv[1] = sValues[i * 3 + 1] ?? 1;
2060
+ sv[2] = sValues[i * 3 + 2] ?? 1;
2061
+ } else {
2062
+ sv[0] = 1;
2063
+ sv[1] = 1;
2064
+ sv[2] = 1;
2065
+ }
2066
+ mat4.compose(tmp, tv, rv, sv);
2067
+ for (let k = 0; k < 16; k++) {
2068
+ transforms[i * 16 + k] = tmp[k] ?? 0;
2069
+ }
2070
+ }
2071
+ return ok({ count: n, transforms });
2072
+ }
2073
+ function unknownAccessor(accessorIndex) {
2074
+ return gltfErr("gltf-accessor-type-mismatch", {
2075
+ accessorIndex,
2076
+ reason: "unknownComponentType"
2077
+ });
2078
+ }
2079
+ function decodeAttributeAccessor(accessorIndex, accessor, bufferViews, buffers) {
2080
+ const view = bufferViews[accessor.bufferView ?? -1];
2081
+ if (view === void 0) return err(unknownAccessor(accessorIndex));
2082
+ const buf = buffers[view.buffer];
2083
+ if (buf === void 0) return err(unknownAccessor(accessorIndex));
2084
+ const decoded = decodeAccessor({
2085
+ accessorIndex,
2086
+ accessor,
2087
+ bufferView: view,
2088
+ buffer: buf,
2089
+ role: "attribute"
2090
+ });
2091
+ if (!decoded.ok) return err(decoded.error);
2092
+ if (decoded.value.kind !== "f32") return err(unknownAccessor(accessorIndex));
2093
+ return ok(decoded.value.data);
2094
+ }
2095
+ function tuple2(values) {
2096
+ if (values === void 0 || values.length < 2) return void 0;
2097
+ return [values[0] ?? 0, values[1] ?? 0];
2098
+ }
2099
+ function parseTextureInfo(info, textures) {
2100
+ const transformJson = info.extensions?.KHR_texture_transform;
2101
+ const offset = tuple2(transformJson?.offset);
2102
+ const scale = tuple2(transformJson?.scale);
2103
+ const transform = offset === void 0 && transformJson?.rotation === void 0 && scale === void 0 ? void 0 : {
2104
+ ...offset === void 0 ? {} : { offset },
2105
+ ...transformJson?.rotation === void 0 ? {} : { rotation: transformJson.rotation },
2106
+ ...scale === void 0 ? {} : { scale }
2107
+ };
2108
+ const sampler = textures[info.index]?.sampler;
2109
+ return {
2110
+ texture: info.index,
2111
+ ...sampler === void 0 ? {} : { sampler },
2112
+ ...info.texCoord === void 0 && transformJson?.texCoord === void 0 ? {} : { texCoord: transformJson?.texCoord ?? info.texCoord },
2113
+ ...transform === void 0 ? {} : { transform }
2114
+ };
2115
+ }
2116
+ async function resolveBuffer(buf, externalLoader, binChunk) {
2117
+ if (buf.uri === void 0) {
2118
+ if (binChunk === void 0) {
2119
+ throw new Error("parseGltf: buffer 0 has no uri and no GLB BIN chunk available");
2120
+ }
2121
+ return binChunk;
2122
+ }
2123
+ const dataPayload = dataUriBase64Payload(buf.uri);
2124
+ if (dataPayload !== void 0) {
2125
+ return decodeBase64(dataPayload);
2126
+ }
2127
+ const arrayBuffer = await externalLoader(buf.uri);
2128
+ return new Uint8Array(arrayBuffer);
2129
+ }
2130
+ function invalidBufferDataUriError(filePath, bufferIndex, cause) {
2131
+ return new ImportError({
2132
+ code: "source-validation-failed",
2133
+ expected: `glTF buffer ${bufferIndex} data URI to contain a valid base64 payload`,
2134
+ hint: IMPORT_ERROR_HINTS["source-validation-failed"],
2135
+ detail: {
2136
+ diagnostics: [
2137
+ {
2138
+ code: "gltf-buffer-data-uri-invalid",
2139
+ severity: "error",
2140
+ sourcePath: filePath,
2141
+ sourceRange: { start: 0, end: 0, line: 1, column: 1 },
2142
+ rule: "gltf-buffer-data-uri-base64",
2143
+ expected: "a valid base64 payload after ;base64,",
2144
+ actual: `buffer ${bufferIndex}: ${cause.message}`,
2145
+ hint: "repair the buffer data URI or provide a valid external .bin sibling"
2146
+ }
2147
+ ]
2148
+ }
2149
+ });
2150
+ }
2151
+ function parsePunctualLights(json, filePath) {
2152
+ const source = json.extensions?.KHR_lights_punctual?.lights ?? [];
2153
+ const lights = [];
2154
+ for (const light of source) {
2155
+ const type = light?.type;
2156
+ if (type !== "directional" && type !== "point" && type !== "spot") {
2157
+ return err(gltfErr("gltf-malformed-header", { filePath, byteOffset: 0 }));
2158
+ }
2159
+ const color = light.color ?? [1, 1, 1];
2160
+ if (color.length < 3 || color.slice(0, 3).some(
2161
+ (value) => typeof value !== "number" || !Number.isFinite(value) || value < 0 || value > 1
2162
+ )) {
2163
+ return err(gltfErr("gltf-malformed-header", { filePath, byteOffset: 0 }));
2164
+ }
2165
+ const intensity = light.intensity ?? 1;
2166
+ if (typeof intensity !== "number" || !Number.isFinite(intensity) || intensity < 0) {
2167
+ return err(gltfErr("gltf-malformed-header", { filePath, byteOffset: 0 }));
2168
+ }
2169
+ const range = light.range;
2170
+ if (range !== void 0 && (typeof range !== "number" || !Number.isFinite(range) || range < 0)) {
2171
+ return err(gltfErr("gltf-malformed-header", { filePath, byteOffset: 0 }));
2172
+ }
2173
+ let spot;
2174
+ if (type === "spot") {
2175
+ const innerConeAngle = light.spot?.innerConeAngle ?? 0;
2176
+ const outerConeAngle = light.spot?.outerConeAngle ?? Math.PI / 4;
2177
+ if (!Number.isFinite(innerConeAngle) || !Number.isFinite(outerConeAngle) || innerConeAngle < 0 || innerConeAngle >= outerConeAngle || outerConeAngle > Math.PI / 2) {
2178
+ return err(gltfErr("gltf-malformed-header", { filePath, byteOffset: 0 }));
2179
+ }
2180
+ spot = { innerConeAngle, outerConeAngle };
2181
+ }
2182
+ lights.push({
2183
+ type,
2184
+ color: [color[0] ?? 1, color[1] ?? 1, color[2] ?? 1],
2185
+ intensity,
2186
+ ...range === void 0 || range === 0 ? {} : { range },
2187
+ ...spot === void 0 ? {} : { spot }
2188
+ });
2189
+ }
2190
+ return ok(lights);
2191
+ }
2192
+ async function parseGltfWithBin(json, ctx) {
2193
+ const headerResult = parseGltfHeader(json, ctx.filePath);
2194
+ if (!headerResult.ok) return err(headerResult.error);
2195
+ const extResult = checkExtensions(json);
2196
+ if (!extResult.ok) return err(extResult.error);
2197
+ const unsupportedExtensions = extResult.value.unsupportedUsed;
2198
+ const lightsResult = parsePunctualLights(json, ctx.filePath);
2199
+ if (!lightsResult.ok) return err(lightsResult.error);
2200
+ const meshesJson = json.meshes ?? [];
2201
+ const buffersJson = json.buffers ?? [];
2202
+ const buffers = [];
2203
+ for (let i = 0; i < buffersJson.length; i++) {
2204
+ const bufJson = buffersJson[i];
2205
+ if (bufJson === void 0) continue;
2206
+ try {
2207
+ const bytes = await resolveBuffer(bufJson, ctx.externalLoader, ctx.binChunk);
2208
+ buffers.push(bytes);
2209
+ } catch (e) {
2210
+ if (e instanceof Base64DecodeError) {
2211
+ return err(invalidBufferDataUriError(ctx.filePath, i, e));
2212
+ }
2213
+ return err(
2214
+ gltfErr("gltf-malformed-header", {
2215
+ filePath: ctx.filePath,
2216
+ byteOffset: 0
2217
+ })
2218
+ );
2219
+ }
2220
+ }
2221
+ const accessors = json.accessors ?? [];
2222
+ const rawBufferViews = json.bufferViews ?? [];
2223
+ const projected = await projectMeshoptBufferViews(
2224
+ rawBufferViews,
2225
+ buffers,
2226
+ json.extensionsRequired ?? [],
2227
+ ctx.meshopt
2228
+ );
2229
+ if (!projected.ok) return err(projected.error);
2230
+ const bufferViews = projected.value.bufferViews;
2231
+ buffers.splice(0, buffers.length, ...projected.value.buffers);
2232
+ const meshes = [];
2233
+ const meshPrimitiveCount = /* @__PURE__ */ new Map();
2234
+ for (let meshIndex = 0; meshIndex < meshesJson.length; meshIndex++) {
2235
+ const meshJson = meshesJson[meshIndex];
2236
+ if (meshJson === void 0) continue;
2237
+ meshPrimitiveCount.set(meshIndex, meshJson.primitives.length);
2238
+ for (const prim of meshJson.primitives) {
2239
+ if (prim === void 0) continue;
2240
+ const positionAccessorIndex = prim.attributes?.POSITION;
2241
+ if (positionAccessorIndex === void 0) {
2242
+ return err(
2243
+ gltfErr("gltf-accessor-type-mismatch", {
2244
+ accessorIndex: -1,
2245
+ reason: "unknownComponentType"
2246
+ })
2247
+ );
2248
+ }
2249
+ const positionAccessor = accessors[positionAccessorIndex];
2250
+ const positionBufferView = bufferViews[positionAccessor?.bufferView ?? -1];
2251
+ if (positionAccessor === void 0 || positionBufferView === void 0) {
2252
+ return err(
2253
+ gltfErr("gltf-buffer-out-of-bounds", {
2254
+ accessor: positionAccessorIndex,
2255
+ byteOffset: 0,
2256
+ byteLength: 0,
2257
+ bufferIndex: positionBufferView?.buffer ?? 0
2258
+ })
2259
+ );
2260
+ }
2261
+ const positionBuffer = buffers[positionBufferView.buffer];
2262
+ if (positionBuffer === void 0) {
2263
+ return err(
2264
+ gltfErr("gltf-buffer-out-of-bounds", {
2265
+ accessor: positionAccessorIndex,
2266
+ byteOffset: positionBufferView.byteOffset ?? 0,
2267
+ byteLength: positionBufferView.byteLength,
2268
+ bufferIndex: positionBufferView.buffer
2269
+ })
2270
+ );
2271
+ }
2272
+ const positionDecoded = decodeAccessor({
2273
+ accessorIndex: positionAccessorIndex,
2274
+ accessor: positionAccessor,
2275
+ bufferView: positionBufferView,
2276
+ buffer: positionBuffer,
2277
+ role: "attribute"
2278
+ });
2279
+ if (!positionDecoded.ok) return err(positionDecoded.error);
2280
+ if (positionDecoded.value.kind !== "f32") {
2281
+ return err(
2282
+ gltfErr("gltf-accessor-type-mismatch", {
2283
+ accessorIndex: positionAccessorIndex,
2284
+ reason: "unknownComponentType"
2285
+ })
2286
+ );
2287
+ }
2288
+ const positionsDecoded = positionDecoded.value.data;
2289
+ const positions = new Float32Array(positionsDecoded.length);
2290
+ positions.set(positionsDecoded);
2291
+ const attrs = prim.attributes ?? {};
2292
+ let colors0;
2293
+ const colorIdx = attrs.COLOR_0;
2294
+ if (colorIdx !== void 0) {
2295
+ const colorAccessor = accessors[colorIdx];
2296
+ const colorBufferView = bufferViews[colorAccessor?.bufferView ?? -1];
2297
+ if (colorAccessor === void 0 || colorBufferView === void 0) {
2298
+ return err(
2299
+ gltfErr("gltf-color-accessor-malformed", {
2300
+ semantic: "COLOR_0",
2301
+ accessorIndex: colorIdx,
2302
+ reason: "reference"
2303
+ })
2304
+ );
2305
+ }
2306
+ const colorBuffer = buffers[colorBufferView.buffer];
2307
+ if (colorBuffer === void 0) {
2308
+ return err(
2309
+ gltfErr("gltf-color-accessor-malformed", {
2310
+ semantic: "COLOR_0",
2311
+ accessorIndex: colorIdx,
2312
+ reason: "reference"
2313
+ })
2314
+ );
2315
+ }
2316
+ const decoded = decodeColorAccessor({
2317
+ accessorIndex: colorIdx,
2318
+ accessor: colorAccessor,
2319
+ bufferView: colorBufferView,
2320
+ buffer: colorBuffer,
2321
+ bufferIndex: colorBufferView.buffer,
2322
+ semantic: "COLOR_0"
2323
+ });
2324
+ if (!decoded.ok) return err(decoded.error);
2325
+ if (decoded.value.length !== positions.length / 3 * 4) {
2326
+ return err(
2327
+ gltfErr("gltf-color-accessor-malformed", {
2328
+ semantic: "COLOR_0",
2329
+ accessorIndex: colorIdx,
2330
+ reason: "count"
2331
+ })
2332
+ );
2333
+ }
2334
+ colors0 = decoded.value;
2335
+ }
2336
+ let normals;
2337
+ const normalIdx = attrs.NORMAL;
2338
+ if (normalIdx !== void 0) {
2339
+ const acc = accessors[normalIdx];
2340
+ if (acc !== void 0) {
2341
+ const decoded = decodeAttributeAccessor(normalIdx, acc, bufferViews, buffers);
2342
+ if (decoded.ok) {
2343
+ const src = decoded.value;
2344
+ const owned = new Float32Array(src.length);
2345
+ owned.set(src);
2346
+ normals = owned;
2347
+ }
2348
+ }
2349
+ }
2350
+ let texcoord0;
2351
+ const texIdx = attrs.TEXCOORD_0;
2352
+ if (texIdx !== void 0) {
2353
+ const acc = accessors[texIdx];
2354
+ if (acc !== void 0) {
2355
+ const decoded = decodeAttributeAccessor(texIdx, acc, bufferViews, buffers);
2356
+ if (decoded.ok) {
2357
+ const src = decoded.value;
2358
+ const owned = new Float32Array(src.length);
2359
+ owned.set(src);
2360
+ texcoord0 = owned;
2361
+ }
2362
+ }
2363
+ }
2364
+ let texcoord1;
2365
+ let texcoord2;
2366
+ let texcoord3;
2367
+ let texcoord4;
2368
+ let texcoord5;
2369
+ let texcoord6;
2370
+ let texcoord7;
2371
+ for (let k = 1; k <= 7; k++) {
2372
+ const tcIdx = attrs[`TEXCOORD_${k}`];
2373
+ if (tcIdx === void 0) continue;
2374
+ const acc = accessors[tcIdx];
2375
+ if (acc !== void 0) {
2376
+ const decoded = decodeAttributeAccessor(tcIdx, acc, bufferViews, buffers);
2377
+ if (decoded.ok) {
2378
+ const src = decoded.value;
2379
+ const owned = new Float32Array(src.length);
2380
+ owned.set(src);
2381
+ if (k === 1) texcoord1 = owned;
2382
+ else if (k === 2) texcoord2 = owned;
2383
+ else if (k === 3) texcoord3 = owned;
2384
+ else if (k === 4) texcoord4 = owned;
2385
+ else if (k === 5) texcoord5 = owned;
2386
+ else if (k === 6) texcoord6 = owned;
2387
+ else texcoord7 = owned;
2388
+ }
2389
+ }
2390
+ }
2391
+ let tangents;
2392
+ const tanIdx = attrs.TANGENT;
2393
+ if (tanIdx !== void 0) {
2394
+ const acc = accessors[tanIdx];
2395
+ if (acc !== void 0) {
2396
+ const decoded = decodeAttributeAccessor(tanIdx, acc, bufferViews, buffers);
2397
+ if (decoded.ok) {
2398
+ const src = decoded.value;
2399
+ const owned = new Float32Array(src.length);
2400
+ owned.set(src);
2401
+ tangents = owned;
2402
+ }
2403
+ }
2404
+ }
2405
+ const rawTargets = prim.targets ?? [];
2406
+ for (const target of rawTargets) {
2407
+ const colorTarget = target.COLOR_0;
2408
+ if (colorTarget !== void 0) {
2409
+ return err(
2410
+ gltfErr("gltf-color-accessor-unsupported", {
2411
+ semantic: "COLOR_0",
2412
+ accessorIndex: colorTarget,
2413
+ reason: "morph"
2414
+ })
2415
+ );
2416
+ }
2417
+ }
2418
+ const morphAttributeCount = rawTargets.reduce(
2419
+ (count, target) => count + (target.POSITION === void 0 ? 0 : 1) + (target.NORMAL === void 0 ? 0 : 1) + (target.TANGENT === void 0 ? 0 : 1),
2420
+ 0
2421
+ );
2422
+ const morphError = (reason) => err(
2423
+ gltfErr("gltf-morph-invalid", {
2424
+ meshIndex,
2425
+ primitiveIndex: meshJson.primitives.indexOf(prim),
2426
+ reason,
2427
+ targetCount: rawTargets.length,
2428
+ attributeCount: morphAttributeCount,
2429
+ vertexCount: positions.length / 3
2430
+ })
2431
+ );
2432
+ if (rawTargets.length > 8) return morphError("target-count-exceeded");
2433
+ if (morphAttributeCount > 8) return morphError("attribute-count-exceeded");
2434
+ const morphTargets = [];
2435
+ for (const target of rawTargets) {
2436
+ const output = {};
2437
+ for (const [key, accessorIndex] of Object.entries(target)) {
2438
+ if (key !== "POSITION" && key !== "NORMAL" && key !== "TANGENT") continue;
2439
+ const targetAccessor = accessors[accessorIndex];
2440
+ if (targetAccessor === void 0) return morphError("attribute-length-mismatch");
2441
+ const decoded = decodeAttributeAccessor(
2442
+ accessorIndex,
2443
+ targetAccessor,
2444
+ bufferViews,
2445
+ buffers
2446
+ );
2447
+ if (!decoded.ok) {
2448
+ return morphError(
2449
+ decoded.error.code === "gltf-accessor-type-mismatch" ? "sparse-or-unsupported-accessor" : "attribute-length-mismatch"
2450
+ );
2451
+ }
2452
+ const expected = positions.length / 3 * (key === "TANGENT" ? 4 : 3);
2453
+ if (decoded.value.length !== expected) return morphError("attribute-length-mismatch");
2454
+ const owned = new Float32Array(decoded.value.length);
2455
+ owned.set(decoded.value);
2456
+ if (key === "POSITION") output.position = owned;
2457
+ else if (key === "NORMAL") output.normal = owned;
2458
+ else output.tangent = owned;
2459
+ }
2460
+ morphTargets.push(output);
2461
+ }
2462
+ const meshWeights = meshJson.weights;
2463
+ if (meshWeights !== void 0 && meshWeights.length !== rawTargets.length) {
2464
+ return morphError("weights-length-mismatch");
2465
+ }
2466
+ const jointsIdx = attrs.JOINTS_0;
2467
+ const weightsIdx = attrs.WEIGHTS_0;
2468
+ const hasJoints = jointsIdx !== void 0;
2469
+ const hasWeights = weightsIdx !== void 0;
2470
+ if (hasJoints !== hasWeights) {
2471
+ const primitiveIndex = meshJson.primitives.indexOf(prim);
2472
+ return err(
2473
+ gltfErr("gltf-skin-attr-asymmetric", {
2474
+ meshIndex,
2475
+ primitiveIndex,
2476
+ hasJoints,
2477
+ hasWeights
2478
+ })
2479
+ );
2480
+ }
2481
+ let joints0;
2482
+ let weights0;
2483
+ if (hasJoints && hasWeights) {
2484
+ const jointsAccessor = accessors[jointsIdx];
2485
+ const jointsBufferView = bufferViews[jointsAccessor?.bufferView ?? -1];
2486
+ if (jointsAccessor === void 0 || jointsBufferView === void 0) {
2487
+ return err(unknownAccessor(jointsIdx));
2488
+ }
2489
+ const jointsBuffer = buffers[jointsBufferView.buffer];
2490
+ if (jointsBuffer === void 0) return err(unknownAccessor(jointsIdx));
2491
+ const jointsDecoded = decodeAccessor({
2492
+ accessorIndex: jointsIdx,
2493
+ accessor: jointsAccessor,
2494
+ bufferView: jointsBufferView,
2495
+ buffer: jointsBuffer,
2496
+ role: "joints"
2497
+ });
2498
+ if (!jointsDecoded.ok) return err(jointsDecoded.error);
2499
+ if (jointsDecoded.value.kind !== "u16") {
2500
+ return err(
2501
+ gltfErr("gltf-accessor-type-mismatch", {
2502
+ accessorIndex: jointsIdx,
2503
+ reason: "unknownComponentType"
2504
+ })
2505
+ );
2506
+ }
2507
+ const src = jointsDecoded.value.data;
2508
+ const owned = new Uint16Array(src.length);
2509
+ owned.set(src);
2510
+ joints0 = owned;
2511
+ const weightsAccessor = accessors[weightsIdx];
2512
+ const weightsBufferView = bufferViews[weightsAccessor?.bufferView ?? -1];
2513
+ if (weightsAccessor === void 0 || weightsBufferView === void 0) {
2514
+ return err(unknownAccessor(weightsIdx));
2515
+ }
2516
+ const weightsBuffer = buffers[weightsBufferView.buffer];
2517
+ if (weightsBuffer === void 0) return err(unknownAccessor(weightsIdx));
2518
+ const weightsDecoded = decodeAccessor({
2519
+ accessorIndex: weightsIdx,
2520
+ accessor: weightsAccessor,
2521
+ bufferView: weightsBufferView,
2522
+ buffer: weightsBuffer,
2523
+ role: "attribute"
2524
+ });
2525
+ if (!weightsDecoded.ok) return err(weightsDecoded.error);
2526
+ if (weightsDecoded.value.kind !== "f32") {
2527
+ return err(
2528
+ gltfErr("gltf-accessor-type-mismatch", {
2529
+ accessorIndex: weightsIdx,
2530
+ reason: "unknownComponentType"
2531
+ })
2532
+ );
2533
+ }
2534
+ const wsrc = weightsDecoded.value.data;
2535
+ const wowned = new Float32Array(wsrc.length);
2536
+ wowned.set(wsrc);
2537
+ weights0 = wowned;
2538
+ }
2539
+ let indices;
2540
+ if (prim.indices !== void 0) {
2541
+ const indexAccessor = accessors[prim.indices];
2542
+ const indexBufferView = bufferViews[indexAccessor?.bufferView ?? -1];
2543
+ if (indexAccessor === void 0 || indexBufferView === void 0) {
2544
+ return err(
2545
+ gltfErr("gltf-buffer-out-of-bounds", {
2546
+ accessor: prim.indices,
2547
+ byteOffset: 0,
2548
+ byteLength: 0,
2549
+ bufferIndex: indexBufferView?.buffer ?? 0
2550
+ })
2551
+ );
2552
+ }
2553
+ const indexBuffer = buffers[indexBufferView.buffer];
2554
+ if (indexBuffer === void 0) {
2555
+ return err(
2556
+ gltfErr("gltf-buffer-out-of-bounds", {
2557
+ accessor: prim.indices,
2558
+ byteOffset: indexBufferView.byteOffset ?? 0,
2559
+ byteLength: indexBufferView.byteLength,
2560
+ bufferIndex: indexBufferView.buffer
2561
+ })
2562
+ );
2563
+ }
2564
+ const indexDecoded = decodeAccessor({
2565
+ accessorIndex: prim.indices,
2566
+ accessor: indexAccessor,
2567
+ bufferView: indexBufferView,
2568
+ buffer: indexBuffer,
2569
+ role: "indices"
2570
+ });
2571
+ if (!indexDecoded.ok) return err(indexDecoded.error);
2572
+ if (indexDecoded.value.kind === "u16") {
2573
+ const src = indexDecoded.value.data;
2574
+ const owned = new Uint16Array(src.length);
2575
+ owned.set(src);
2576
+ indices = owned;
2577
+ } else if (indexDecoded.value.kind === "u32") {
2578
+ const src = indexDecoded.value.data;
2579
+ const owned = new Uint32Array(src.length);
2580
+ owned.set(src);
2581
+ indices = owned;
2582
+ } else {
2583
+ return err(
2584
+ gltfErr("gltf-accessor-type-mismatch", {
2585
+ accessorIndex: prim.indices,
2586
+ reason: "unknownComponentType"
2587
+ })
2588
+ );
2589
+ }
2590
+ }
2591
+ const meshIr = {
2592
+ ...meshJson.name === void 0 ? {} : { name: meshJson.name },
2593
+ positions,
2594
+ ...normals === void 0 ? {} : { normals },
2595
+ ...texcoord0 === void 0 ? {} : { texcoord0 },
2596
+ ...texcoord1 === void 0 ? {} : { texcoord1 },
2597
+ ...texcoord2 === void 0 ? {} : { texcoord2 },
2598
+ ...texcoord3 === void 0 ? {} : { texcoord3 },
2599
+ ...texcoord4 === void 0 ? {} : { texcoord4 },
2600
+ ...texcoord5 === void 0 ? {} : { texcoord5 },
2601
+ ...texcoord6 === void 0 ? {} : { texcoord6 },
2602
+ ...texcoord7 === void 0 ? {} : { texcoord7 },
2603
+ ...tangents === void 0 ? {} : { tangents },
2604
+ ...colors0 === void 0 ? {} : { colors0 },
2605
+ ...joints0 === void 0 ? {} : { joints0 },
2606
+ ...weights0 === void 0 ? {} : { weights0 },
2607
+ ...morphTargets.length === 0 ? {} : { morphTargets },
2608
+ ...meshWeights === void 0 ? {} : { morphWeights: new Float32Array(meshWeights) },
2609
+ ...indices === void 0 ? {} : { indices },
2610
+ materialIndex: prim.material ?? null,
2611
+ meshIndex
2612
+ };
2613
+ meshes.push(meshIr);
2614
+ }
2615
+ }
2616
+ const texturesJson = json.textures ?? [];
2617
+ const textures = [];
2618
+ for (const texJson of texturesJson) {
2619
+ const texIr = {
2620
+ source: texJson.source ?? -1,
2621
+ ...texJson.sampler === void 0 ? {} : { sampler: texJson.sampler },
2622
+ ...texJson.name === void 0 ? {} : { name: texJson.name }
2623
+ };
2624
+ textures.push(texIr);
2625
+ }
2626
+ const imagesJson = json.images ?? [];
2627
+ const images = [];
2628
+ for (const imgJson of imagesJson) {
2629
+ const mimeType = imgJson.mimeType;
2630
+ if (mimeType !== void 0 && mimeType !== "image/jpeg" && mimeType !== "image/png") {
2631
+ return err(gltfErr("gltf-image-mime-unsupported", { mimeType }));
2632
+ }
2633
+ const imgIr = {
2634
+ ...imgJson.uri === void 0 ? {} : { uri: imgJson.uri },
2635
+ ...imgJson.mimeType === void 0 ? {} : { mimeType: imgJson.mimeType },
2636
+ ...imgJson.bufferView === void 0 ? {} : { bufferView: imgJson.bufferView },
2637
+ ...imgJson.name === void 0 ? {} : { name: imgJson.name }
2638
+ };
2639
+ images.push(imgIr);
2640
+ }
2641
+ for (const img of images) {
2642
+ if (img.uri !== void 0) {
2643
+ if (dataUriBase64Payload(img.uri) !== void 0) continue;
2644
+ try {
2645
+ await ctx.externalLoader(img.uri);
2646
+ } catch (_e) {
2647
+ return err(gltfErr("gltf-texture-load-failed", { uri: img.uri }));
2648
+ }
2649
+ }
2650
+ }
2651
+ const samplersJson = json.samplers ?? [];
2652
+ const samplers = [];
2653
+ for (const sampJson of samplersJson) {
2654
+ samplers.push({
2655
+ ...sampJson.magFilter === void 0 ? {} : { magFilter: sampJson.magFilter },
2656
+ ...sampJson.minFilter === void 0 ? {} : { minFilter: sampJson.minFilter },
2657
+ wrapS: sampJson.wrapS ?? 10497,
2658
+ // REPEAT (glTF default)
2659
+ wrapT: sampJson.wrapT ?? 10497,
2660
+ ...sampJson.name === void 0 ? {} : { name: sampJson.name }
2661
+ });
2662
+ }
2663
+ const materialsJson = json.materials ?? [];
2664
+ const materials = [];
2665
+ for (const matJson of materialsJson) {
2666
+ const pbr = matJson.pbrMetallicRoughness;
2667
+ const baseColor = pbr?.baseColorFactor ?? [1, 1, 1, 1];
2668
+ const baseColor4 = [
2669
+ baseColor[0] ?? 1,
2670
+ baseColor[1] ?? 1,
2671
+ baseColor[2] ?? 1,
2672
+ baseColor[3] ?? 1
2673
+ ];
2674
+ const alphaMode = matJson.alphaMode === "MASK" || matJson.alphaMode === "BLEND" ? matJson.alphaMode : void 0;
2675
+ const alphaCutoff = alphaMode === "MASK" ? matJson.alphaCutoff ?? 0.5 : void 0;
2676
+ const emissiveFactor = matJson.emissiveFactor;
2677
+ const emissiveFactor3 = [
2678
+ emissiveFactor?.[0] ?? 0,
2679
+ emissiveFactor?.[1] ?? 0,
2680
+ emissiveFactor?.[2] ?? 0
2681
+ ];
2682
+ materials.push({
2683
+ ...matJson.name === void 0 ? {} : { name: matJson.name },
2684
+ baseColorFactor: baseColor4,
2685
+ ...emissiveFactor === void 0 ? {} : { emissiveFactor: emissiveFactor3 },
2686
+ metallicFactor: pbr?.metallicFactor ?? 1,
2687
+ roughnessFactor: pbr?.roughnessFactor ?? 1,
2688
+ ...pbr?.baseColorTexture === void 0 ? {} : { baseColorTexture: parseTextureInfo(pbr.baseColorTexture, textures) },
2689
+ ...pbr?.metallicRoughnessTexture === void 0 ? {} : { metallicRoughnessTexture: parseTextureInfo(pbr.metallicRoughnessTexture, textures) },
2690
+ ...matJson.normalTexture === void 0 ? {} : {
2691
+ normalTexture: {
2692
+ ...parseTextureInfo(matJson.normalTexture, textures),
2693
+ ...matJson.normalTexture.scale === void 0 ? {} : { scale: matJson.normalTexture.scale }
2694
+ }
2695
+ },
2696
+ ...matJson.occlusionTexture === void 0 ? {} : {
2697
+ occlusionTexture: {
2698
+ ...parseTextureInfo(matJson.occlusionTexture, textures),
2699
+ ...matJson.occlusionTexture.strength === void 0 ? {} : { strength: matJson.occlusionTexture.strength }
2700
+ }
2701
+ },
2702
+ ...matJson.emissiveTexture === void 0 ? {} : { emissiveTexture: parseTextureInfo(matJson.emissiveTexture, textures) },
2703
+ ...alphaMode === void 0 ? {} : { alphaMode },
2704
+ ...alphaCutoff === void 0 ? {} : { alphaCutoff },
2705
+ ...matJson.doubleSided === true ? { doubleSided: true } : {},
2706
+ ...pbr?.baseColorTexture?.texCoord === void 0 || pbr.baseColorTexture.texCoord === 0 ? {} : { baseColorTexCoord: pbr.baseColorTexture.texCoord }
2707
+ });
2708
+ }
2709
+ const diagnostics = {
2710
+ nodeNames: [],
2711
+ unsupportedExtensions: [...unsupportedExtensions],
2712
+ matrixTrsCoexistNodes: []
2713
+ };
2714
+ const nodes = [];
2715
+ const nodesJson = json.nodes ?? [];
2716
+ for (let nodeIndex = 0; nodeIndex < nodesJson.length; nodeIndex++) {
2717
+ const nodeJson = nodesJson[nodeIndex];
2718
+ if (nodeJson === void 0) continue;
2719
+ const transform = decomposeNodeTransform(nodeJson, nodeIndex, diagnostics);
2720
+ if (nodeJson.name !== void 0) diagnostics.nodeNames.push(nodeJson.name);
2721
+ const instancingExt = nodeJson.extensions?.EXT_mesh_gpu_instancing;
2722
+ let instancing;
2723
+ if (instancingExt !== void 0) {
2724
+ const instancingResult = decodeNodeInstancing(
2725
+ nodeIndex,
2726
+ instancingExt.attributes ?? {},
2727
+ accessors,
2728
+ bufferViews,
2729
+ buffers
2730
+ );
2731
+ if (!instancingResult.ok) return err(instancingResult.error);
2732
+ instancing = instancingResult.value;
2733
+ }
2734
+ const nodeMorphWeights = nodeJson.weights === void 0 ? void 0 : new Float32Array(nodeJson.weights);
2735
+ nodes.push({
2736
+ ...nodeJson.name === void 0 ? {} : { name: nodeJson.name },
2737
+ transform,
2738
+ meshIndex: nodeJson.mesh ?? null,
2739
+ skinIndex: nodeJson.skin ?? null,
2740
+ children: nodeJson.children ?? [],
2741
+ camera: nodeJson.camera ?? null,
2742
+ lightIndex: nodeJson.extensions?.KHR_lights_punctual?.light ?? null,
2743
+ ...instancing === void 0 ? {} : { instancing },
2744
+ ...nodeMorphWeights === void 0 ? {} : { morphWeights: nodeMorphWeights }
2745
+ });
2746
+ }
2747
+ const scenesJson = json.scenes ?? [];
2748
+ const scenes = scenesJson.map((s) => ({
2749
+ ...s.name === void 0 ? {} : { name: s.name },
2750
+ nodes: s.nodes ?? []
2751
+ }));
2752
+ const defaultSceneIndex = json.scene ?? 0;
2753
+ const skinsJson = json.skins;
2754
+ const skinResult = parseSkin(skinsJson, nodesJson, accessors, bufferViews, buffers);
2755
+ if (!skinResult.ok) return err(skinResult.error);
2756
+ const skeletons = skinResult.value;
2757
+ const animationsJson = json.animations;
2758
+ const animResult = parseAnimation(animationsJson, nodesJson, accessors, bufferViews, buffers);
2759
+ if (!animResult.ok) return err(animResult.error);
2760
+ const animationClips = animResult.value;
2761
+ return ok({
2762
+ meshes,
2763
+ materials,
2764
+ nodes,
2765
+ scenes,
2766
+ skeletons,
2767
+ animationClips,
2768
+ textures: textures.length > 0 ? textures : void 0,
2769
+ images: images.length > 0 ? images : void 0,
2770
+ samplers: samplers.length > 0 ? samplers : void 0,
2771
+ defaultSceneIndex,
2772
+ diagnostics: {
2773
+ nodeNames: diagnostics.nodeNames,
2774
+ unsupportedExtensions: diagnostics.unsupportedExtensions,
2775
+ matrixTrsCoexistNodes: diagnostics.matrixTrsCoexistNodes
2776
+ },
2777
+ meshPrimitiveCount,
2778
+ lights: lightsResult.value
2779
+ });
2780
+ }
2781
+ async function parseGltfForImporter(json, externalLoader, filePath, options = {}) {
2782
+ if (json === null || typeof json !== "object") {
2783
+ return err(
2784
+ gltfErr("gltf-malformed-header", {
2785
+ filePath,
2786
+ byteOffset: 0
2787
+ })
2788
+ );
2789
+ }
2790
+ return parseGltfWithBin(json, {
2791
+ externalLoader,
2792
+ filePath,
2793
+ ...options.meshopt === void 0 ? {} : { meshopt: options.meshopt }
2794
+ });
2795
+ }
2796
+ function legacyParseResult(result, filePath, byteOffset) {
2797
+ if (result.ok) return ok(result.value);
2798
+ if (result.error instanceof ImportError) {
2799
+ return err(gltfErr("gltf-malformed-header", { filePath, byteOffset }));
2800
+ }
2801
+ return err(result.error);
2802
+ }
2803
+ async function parseGltf(json, externalLoader, filePath, options = {}) {
2804
+ const result = await parseGltfForImporter(json, externalLoader, filePath, options);
2805
+ return legacyParseResult(result, filePath, 0);
2806
+ }
2807
+ async function parseGlbForImporter(buffer, filePath, options = {}) {
2808
+ const chunksResult = parseGlbChunks(buffer, filePath);
2809
+ if (!chunksResult.ok) return err(chunksResult.error);
2810
+ let json;
2811
+ try {
2812
+ json = JSON.parse(new TextDecoder().decode(chunksResult.value.jsonChunk));
2813
+ } catch (_e) {
2814
+ return err(
2815
+ gltfErr("gltf-malformed-header", {
2816
+ filePath,
2817
+ byteOffset: 12
2818
+ })
2819
+ );
2820
+ }
2821
+ if (json === null || typeof json !== "object") {
2822
+ return err(
2823
+ gltfErr("gltf-malformed-header", {
2824
+ filePath,
2825
+ byteOffset: 12
2826
+ })
2827
+ );
2828
+ }
2829
+ const externalLoader = async (uri) => {
2830
+ throw new Error(`parseGlb: GLB containers must not reference external URIs (got ${uri})`);
2831
+ };
2832
+ return parseGltfWithBin(json, {
2833
+ externalLoader,
2834
+ ...chunksResult.value.binChunk === void 0 ? {} : { binChunk: chunksResult.value.binChunk },
2835
+ filePath,
2836
+ ...options.meshopt === void 0 ? {} : { meshopt: options.meshopt }
2837
+ });
2838
+ }
2839
+ async function parseGlb(buffer, filePath, options = {}) {
2840
+ const result = await parseGlbForImporter(buffer, filePath, options);
2841
+ return legacyParseResult(result, filePath, 12);
2842
+ }
2843
+ function toAssetPack(doc, existingMeta, source) {
2844
+ const items = [];
2845
+ const seenMeshIndices = /* @__PURE__ */ new Set();
2846
+ for (const m of doc.meshes) {
2847
+ if (m === void 0) continue;
2848
+ if (seenMeshIndices.has(m.meshIndex)) continue;
2849
+ seenMeshIndices.add(m.meshIndex);
2850
+ items.push({
2851
+ kind: "mesh",
2852
+ sourceIndex: m.meshIndex,
2853
+ ...m.name === void 0 ? {} : { name: m.name }
2854
+ });
2855
+ }
2856
+ for (let i = 0; i < doc.materials.length; i++) {
2857
+ const m = doc.materials[i];
2858
+ if (m === void 0) continue;
2859
+ items.push({
2860
+ kind: "material",
2861
+ sourceIndex: i,
2862
+ ...m.name === void 0 ? {} : { name: m.name }
2863
+ });
2864
+ }
2865
+ for (let i = 0; i < doc.scenes.length; i++) {
2866
+ const s = doc.scenes[i];
2867
+ if (s === void 0) continue;
2868
+ items.push({
2869
+ kind: "scene",
2870
+ sourceIndex: i,
2871
+ ...s.name === void 0 ? {} : { name: s.name }
2872
+ });
2873
+ }
2874
+ const images = doc.images ?? [];
2875
+ for (let i = 0; i < images.length; i++) {
2876
+ const img = images[i];
2877
+ if (img === void 0) continue;
2878
+ items.push({
2879
+ kind: "texture",
2880
+ sourceIndex: i,
2881
+ ...img.name === void 0 ? {} : { name: img.name }
2882
+ });
2883
+ }
2884
+ const samplers = doc.samplers ?? [];
2885
+ for (let i = 0; i < samplers.length; i++) {
2886
+ const sampler = samplers[i];
2887
+ if (sampler === void 0) continue;
2888
+ items.push({
2889
+ kind: "sampler",
2890
+ sourceIndex: i,
2891
+ ...sampler.name === void 0 ? {} : { name: sampler.name }
2892
+ });
2893
+ }
2894
+ for (let i = 0; i < doc.skeletons.length; i++) {
2895
+ items.push({ kind: "skeleton", sourceIndex: i });
2896
+ }
2897
+ for (let i = 0; i < doc.skeletons.length; i++) {
2898
+ items.push({ kind: "skin", sourceIndex: i });
2899
+ }
2900
+ for (let i = 0; i < doc.animationClips.length; i++) {
2901
+ const clip = doc.animationClips[i];
2902
+ if (clip === void 0) continue;
2903
+ items.push({
2904
+ kind: "animation-clip",
2905
+ sourceIndex: i,
2906
+ ...clip.name === void 0 ? {} : { name: clip.name }
2907
+ });
2908
+ }
2909
+ const reuse = reimportReuseMeta(items, existingMeta);
2910
+ if (!reuse.ok) return reuse;
2911
+ const subAssetByKindIndex = new Map(
2912
+ reuse.value.subAssets.map((entry) => [`${entry.kind}:${entry.sourceIndex}`, entry])
2913
+ );
2914
+ const sourceOverrides = {
2915
+ ...existingMeta?.sourceOverrides ?? {}
2916
+ };
2917
+ for (const meshIndex of seenMeshIndices) {
2918
+ const meshOutput = subAssetByKindIndex.get(`mesh:${meshIndex}`);
2919
+ if (meshOutput?.sourceKey === void 0) continue;
2920
+ const materialSlots = [];
2921
+ const slotByMaterial = /* @__PURE__ */ new Map();
2922
+ const usedNames = /* @__PURE__ */ new Set();
2923
+ const uniqueName = (raw) => {
2924
+ const base = raw.trim() || "Material";
2925
+ let candidate = base;
2926
+ let suffix = 2;
2927
+ while (usedNames.has(candidate)) candidate = `${base}_${suffix++}`;
2928
+ usedNames.add(candidate);
2929
+ return candidate;
2930
+ };
2931
+ for (const primitive of doc.meshes.filter((mesh) => mesh.meshIndex === meshIndex)) {
2932
+ const materialIndex = primitive.materialIndex;
2933
+ if (slotByMaterial.has(materialIndex)) continue;
2934
+ const materialOutput = materialIndex === null ? void 0 : subAssetByKindIndex.get(`material:${materialIndex}`);
2935
+ slotByMaterial.set(materialIndex, materialSlots.length);
2936
+ materialSlots.push({
2937
+ slotName: uniqueName(
2938
+ materialIndex === null ? "Default" : doc.materials[materialIndex]?.name ?? `Material_${materialIndex}`
2939
+ ),
2940
+ sourceKey: materialIndex === null ? "gltf:default" : materialOutput?.sourceKey ?? `gltf:material:${materialIndex}`,
2941
+ ...materialOutput === void 0 ? {} : { defaultMaterialGuid: materialOutput.guid }
2942
+ });
2943
+ }
2944
+ const previousPayload = existingMeta?.sourceOverrides?.[meshOutput.sourceKey];
2945
+ const previousRaw = previousPayload?.materialSlots;
2946
+ const previous = Array.isArray(previousRaw) ? previousRaw.filter(
2947
+ (slot) => slot !== null && typeof slot === "object" && !Array.isArray(slot) && typeof slot.slotName === "string"
2948
+ ) : [];
2949
+ const reconciled = reconcileMeshMaterialSlotTopology(materialSlots, previous);
2950
+ if (!reconciled.ok) {
2951
+ return err({
2952
+ code: "mesh-material-slot-topology-change",
2953
+ expected: `unambiguous material slot identity for mesh ${meshOutput.guid}`,
2954
+ hint: reconciled.error.hint,
2955
+ detail: {
2956
+ sourceIndices: reconciled.error.nextIndices,
2957
+ previousIndices: reconciled.error.previousIndices
2958
+ }
2959
+ });
2960
+ }
2961
+ sourceOverrides[meshOutput.sourceKey] = {
2962
+ ...previousPayload ?? {},
2963
+ materialSlots: reconciled.slots
2964
+ };
2965
+ }
2966
+ const meta = {
2967
+ schemaVersion: 1,
2968
+ kind: "external-asset-package",
2969
+ importer: "gltf",
2970
+ source,
2971
+ subAssets: reuse.value.subAssets,
2972
+ ...Object.keys(sourceOverrides).length === 0 ? {} : { sourceOverrides },
2973
+ sourceOverrideDescriptors: reuse.value.subAssets.filter((entry) => entry.kind === "mesh" && entry.sourceKey !== void 0).map((entry) => ({
2974
+ sourceKey: entry.sourceKey,
2975
+ semantic: "mesh-material-slot-defaults",
2976
+ payloadSchema: MESH_MATERIAL_SLOT_SOURCE_OVERRIDE_PAYLOAD_SCHEMA
2977
+ })),
2978
+ importSettings: {
2979
+ defaultSceneIndex: doc.defaultSceneIndex,
2980
+ ...typeof existingMeta?.importSettings.standardMaterialGuid === "string" ? { standardMaterialGuid: existingMeta.importSettings.standardMaterialGuid } : {},
2981
+ ...existingMeta?.importSettings.downscaleMaxDimension !== void 0 ? { downscaleMaxDimension: existingMeta.importSettings.downscaleMaxDimension } : {},
2982
+ diagnostics: {
2983
+ nodeNames: doc.diagnostics.nodeNames,
2984
+ unsupportedExtensions: doc.diagnostics.unsupportedExtensions,
2985
+ matrixTrsCoexistNodes: doc.diagnostics.matrixTrsCoexistNodes
2986
+ }
2987
+ }
2988
+ };
2989
+ return ok({ meta, subAssets: reuse.value.subAssets });
2990
+ }
2991
+
2992
+ // src/gltf-importer.ts
2993
+ function isGlbBytes(source) {
2994
+ return source.toLowerCase().endsWith(".glb");
2995
+ }
2996
+ function publishesCatalogProduct(input) {
2997
+ return input.importSettings.geometry !== "procedural";
2998
+ }
2999
+ function previousMaterialSlotTopology(ctx, meshSourceKey) {
3000
+ if (meshSourceKey === void 0) return void 0;
3001
+ const value = ctx.sourceOverrides?.[meshSourceKey]?.materialSlots;
3002
+ if (value === void 0) return void 0;
3003
+ if (!Array.isArray(value)) {
3004
+ throw new ImportError({
3005
+ code: "invalid-source-override-payload",
3006
+ expected: `${meshSourceKey}.materialSlots to be an array`,
3007
+ hint: IMPORT_ERROR_HINTS["invalid-source-override-payload"],
3008
+ detail: {
3009
+ sourceKey: meshSourceKey,
3010
+ declaredSourceKeys: ctx.subAssets.flatMap((entry) => entry.sourceKey ?? []),
3011
+ reason: "materialSlots is not an array"
3012
+ }
3013
+ });
3014
+ }
3015
+ const slots = [];
3016
+ for (const [index, raw] of value.entries()) {
3017
+ if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
3018
+ throw new ImportError({
3019
+ code: "invalid-source-override-payload",
3020
+ expected: `${meshSourceKey}.materialSlots[${index}] to be an object`,
3021
+ hint: IMPORT_ERROR_HINTS["invalid-source-override-payload"],
3022
+ detail: {
3023
+ sourceKey: meshSourceKey,
3024
+ declaredSourceKeys: ctx.subAssets.flatMap((entry) => entry.sourceKey ?? []),
3025
+ reason: `materialSlots[${index}] is not an object`
3026
+ }
3027
+ });
3028
+ }
3029
+ const slot = raw;
3030
+ if (typeof slot.slotName !== "string" || slot.slotName.trim().length === 0) {
3031
+ throw new ImportError({
3032
+ code: "invalid-source-override-payload",
3033
+ expected: `${meshSourceKey}.materialSlots[${index}].slotName to be non-empty`,
3034
+ hint: IMPORT_ERROR_HINTS["invalid-source-override-payload"],
3035
+ detail: {
3036
+ sourceKey: meshSourceKey,
3037
+ declaredSourceKeys: ctx.subAssets.flatMap((entry) => entry.sourceKey ?? []),
3038
+ reason: `materialSlots[${index}].slotName is invalid`
3039
+ }
3040
+ });
3041
+ }
3042
+ slots.push({
3043
+ slotName: slot.slotName,
3044
+ ...typeof slot.sourceKey === "string" ? { sourceKey: slot.sourceKey } : {},
3045
+ ...typeof slot.defaultMaterialGuid === "string" ? { defaultMaterialGuid: slot.defaultMaterialGuid } : {}
3046
+ });
3047
+ }
3048
+ return slots;
3049
+ }
3050
+ function stabilizeMeshMaterialSlots(mesh, ctx, meshGuid, meshSourceKey) {
3051
+ const current = mesh.materialSlots.map(
3052
+ (slot) => ({
3053
+ slotName: slot.slotName,
3054
+ ...slot.sourceKey === void 0 ? {} : { sourceKey: slot.sourceKey },
3055
+ ...slot.defaultMaterial === void 0 ? {} : { defaultMaterialGuid: AssetGuid.format(slot.defaultMaterial) }
3056
+ })
3057
+ );
3058
+ const reconciled = reconcileMeshMaterialSlotTopology(
3059
+ current,
3060
+ previousMaterialSlotTopology(ctx, meshSourceKey)
3061
+ );
3062
+ if (!reconciled.ok) {
3063
+ throw new ImportError({
3064
+ code: "mesh-material-slot-topology-change",
3065
+ expected: `unambiguous material slot identity for mesh ${meshGuid}`,
3066
+ hint: IMPORT_ERROR_HINTS["mesh-material-slot-topology-change"],
3067
+ detail: {
3068
+ meshGuid,
3069
+ ...meshSourceKey === void 0 ? {} : { meshSourceKey },
3070
+ previousIndices: reconciled.error.previousIndices,
3071
+ nextIndices: reconciled.error.nextIndices
3072
+ }
3073
+ });
3074
+ }
3075
+ const authoredDefaults = meshSourceKey === void 0 ? void 0 : ctx.sourceOverrides?.[meshSourceKey]?.materialSlotDefaultOverrides;
3076
+ if (authoredDefaults !== void 0 && (authoredDefaults === null || typeof authoredDefaults !== "object" || Array.isArray(authoredDefaults))) {
3077
+ throw new ImportError({
3078
+ code: "invalid-source-override-payload",
3079
+ expected: `${meshSourceKey}.materialSlotDefaultOverrides to be an object`,
3080
+ hint: IMPORT_ERROR_HINTS["invalid-source-override-payload"],
3081
+ detail: {
3082
+ sourceKey: meshSourceKey,
3083
+ declaredSourceKeys: [],
3084
+ reason: "materialSlotDefaultOverrides is invalid"
3085
+ }
3086
+ });
3087
+ }
3088
+ const authoredBySlot = authoredDefaults;
3089
+ const activeStableSlots = new Set(reconciled.currentToStableSlot);
3090
+ return {
3091
+ ...mesh,
3092
+ submeshes: mesh.submeshes.map((submesh) => ({
3093
+ ...submesh,
3094
+ materialSlot: reconciled.currentToStableSlot[submesh.materialSlot]
3095
+ })),
3096
+ materialSlots: reconciled.slots.map((slot, stableIndex) => {
3097
+ const active = activeStableSlots.has(stableIndex);
3098
+ const authored = authoredBySlot?.[slot.sourceKey ?? slot.slotName];
3099
+ const effectiveDefault = resolveMeshMaterialSlotDefaultGuid(
3100
+ slot,
3101
+ active && (typeof authored === "string" || authored === null) ? authored : void 0
3102
+ );
3103
+ const parsed = effectiveDefault === void 0 ? void 0 : AssetGuid.parse(effectiveDefault);
3104
+ return {
3105
+ slotName: slot.slotName,
3106
+ ...slot.sourceKey === void 0 ? {} : { sourceKey: slot.sourceKey },
3107
+ ...active && parsed?.ok ? { defaultMaterial: parsed.value } : {}
3108
+ };
3109
+ })
3110
+ };
3111
+ }
3112
+ async function parseDoc(source, bytes, ctx, meshopt) {
3113
+ const ab = bytes.buffer.slice(
3114
+ bytes.byteOffset,
3115
+ bytes.byteOffset + bytes.byteLength
3116
+ );
3117
+ if (isGlbBytes(source)) {
3118
+ const res2 = await parseGlbForImporter(ab, source, {} );
3119
+ if (!res2.ok) {
3120
+ if (res2.error instanceof ImportError) return { ok: false, error: res2.error };
3121
+ throw new Error(`parseGlb failed: ${res2.error.code} ${res2.error.expected}`);
3122
+ }
3123
+ return { ok: true, value: res2.value };
3124
+ }
3125
+ let json;
3126
+ try {
3127
+ json = JSON.parse(new TextDecoder().decode(bytes));
3128
+ } catch (e) {
3129
+ throw new Error(`gltf JSON parse failed: ${e instanceof Error ? e.message : String(e)}`);
3130
+ }
3131
+ const externalLoader = async (uri) => {
3132
+ const sib = await ctx.readSibling(uri);
3133
+ if (!sib.ok) {
3134
+ throw new Error(`gltfImporter: external buffer "${uri}" read failed: ${sib.error.code}`);
3135
+ }
3136
+ return sib.value.buffer.slice(
3137
+ sib.value.byteOffset,
3138
+ sib.value.byteOffset + sib.value.byteLength
3139
+ );
3140
+ };
3141
+ const res = await parseGltfForImporter(
3142
+ json,
3143
+ externalLoader,
3144
+ source,
3145
+ {}
3146
+ );
3147
+ if (!res.ok) {
3148
+ if (res.error instanceof ImportError) return { ok: false, error: res.error };
3149
+ throw new Error(`parseGltf failed: ${res.error.code} ${res.error.expected}`);
3150
+ }
3151
+ return { ok: true, value: res.value };
3152
+ }
3153
+ function buildHandleMaps(subAssets, doc) {
3154
+ const meshHandles = /* @__PURE__ */ new Map();
3155
+ const materialHandles = /* @__PURE__ */ new Map();
3156
+ const textureHandles = /* @__PURE__ */ new Map();
3157
+ const samplerHandles = /* @__PURE__ */ new Map();
3158
+ const meshGuidByIndex = /* @__PURE__ */ new Map();
3159
+ const materialGuidByIndex = /* @__PURE__ */ new Map();
3160
+ const textureGuidByIndex = /* @__PURE__ */ new Map();
3161
+ const samplerGuidByIndex = /* @__PURE__ */ new Map();
3162
+ let meshCursor = 0;
3163
+ let materialCursor = 0;
3164
+ for (const sub of subAssets) {
3165
+ if (sub.kind === "mesh") {
3166
+ meshHandles.set(sub.sourceIndex, toShared(meshCursor));
3167
+ meshGuidByIndex.set(sub.sourceIndex, sub.guid);
3168
+ meshCursor += 1;
3169
+ } else if (sub.kind === "material") {
3170
+ materialHandles.set(sub.sourceIndex, toShared(materialCursor));
3171
+ materialGuidByIndex.set(sub.sourceIndex, sub.guid);
3172
+ materialCursor += 1;
3173
+ } else if (sub.kind === "texture") {
3174
+ textureGuidByIndex.set(sub.sourceIndex, sub.guid);
3175
+ } else if (sub.kind === "sampler") {
3176
+ samplerGuidByIndex.set(sub.sourceIndex, sub.guid);
3177
+ }
3178
+ }
3179
+ const meshCount = meshCursor;
3180
+ if (meshCount > 0) {
3181
+ for (const [k, v] of materialHandles) {
3182
+ const local = v;
3183
+ materialHandles.set(k, toShared(local + meshCount));
3184
+ }
3185
+ }
3186
+ const textures = doc.textures ?? [];
3187
+ for (let texIndex = 0; texIndex < textures.length; texIndex++) {
3188
+ const tex = textures[texIndex];
3189
+ if (tex === void 0) continue;
3190
+ if (textureGuidByIndex.has(tex.source)) {
3191
+ textureHandles.set(texIndex, toShared(tex.source));
3192
+ }
3193
+ }
3194
+ for (const [samplerIndex] of samplerGuidByIndex) {
3195
+ samplerHandles.set(samplerIndex, toShared(samplerIndex));
3196
+ }
3197
+ return {
3198
+ meshHandles,
3199
+ materialHandles,
3200
+ textureHandles,
3201
+ samplerHandles,
3202
+ meshGuidByIndex,
3203
+ materialGuidByIndex,
3204
+ textureGuidByIndex,
3205
+ samplerGuidByIndex
3206
+ };
3207
+ }
3208
+ function textureInfo2(info) {
3209
+ return info === void 0 ? void 0 : typeof info === "number" ? { texture: info } : info;
3210
+ }
3211
+ function materialRefsForPack(mat, doc, textureGuidByIndex, samplerGuidByIndex = /* @__PURE__ */ new Map()) {
3212
+ const refs = [];
3213
+ const textures = doc.textures ?? [];
3214
+ function pushRefsForSlot(info, fieldName) {
3215
+ const binding = textureInfo2(info);
3216
+ if (binding === void 0) return;
3217
+ const tex = textures[binding.texture];
3218
+ if (tex === void 0) return;
3219
+ const guid = textureGuidByIndex.get(tex.source);
3220
+ if (guid !== void 0) {
3221
+ refs.push({
3222
+ guid,
3223
+ sourceField: { componentName: "<material>", fieldName }
3224
+ });
3225
+ }
3226
+ if (binding.sampler !== void 0) {
3227
+ const samplerGuid = samplerGuidByIndex.get(binding.sampler);
3228
+ if (samplerGuid !== void 0) {
3229
+ refs.push({
3230
+ guid: samplerGuid,
3231
+ sourceField: { componentName: "<material>", fieldName: `${fieldName}.sampler` }
3232
+ });
3233
+ }
3234
+ }
3235
+ }
3236
+ pushRefsForSlot(mat.baseColorTexture, "baseColorTexture");
3237
+ pushRefsForSlot(mat.metallicRoughnessTexture, "metallicRoughnessTexture");
3238
+ pushRefsForSlot(mat.normalTexture, "normalTexture");
3239
+ pushRefsForSlot(mat.occlusionTexture, "occlusionTexture");
3240
+ pushRefsForSlot(mat.emissiveTexture, "emissiveTexture");
3241
+ return refs;
3242
+ }
3243
+ function availableUvSets(mesh) {
3244
+ const sets = [];
3245
+ for (let set = 0; set <= 7; set++) {
3246
+ const field = `texcoord${set}`;
3247
+ if (mesh[field] !== void 0) sets.push(set);
3248
+ }
3249
+ return sets;
3250
+ }
3251
+ function rewriteMaterialAssetRefs(matAsset, mat, doc, maps) {
3252
+ const values = { ...matAsset.values ?? {} };
3253
+ const textures = doc.textures ?? [];
3254
+ const slots = [
3255
+ ["baseColorTexture", mat.baseColorTexture],
3256
+ ["metallicRoughnessTexture", mat.metallicRoughnessTexture],
3257
+ ["normalTexture", mat.normalTexture],
3258
+ ["occlusionTexture", mat.occlusionTexture],
3259
+ ["emissiveTexture", mat.emissiveTexture]
3260
+ ];
3261
+ let cursor = 0;
3262
+ for (const [slot, rawBinding] of slots) {
3263
+ const binding = textureInfo2(rawBinding);
3264
+ if (binding === void 0) continue;
3265
+ const texture = textures[binding.texture];
3266
+ const textureGuid = texture === void 0 ? void 0 : maps.textureGuidByIndex.get(texture.source);
3267
+ const value = values[slot];
3268
+ if (textureGuid === void 0 || typeof value !== "object" || value === null) {
3269
+ delete values[slot];
3270
+ continue;
3271
+ }
3272
+ const textureValue2 = value;
3273
+ const textureRef = cursor;
3274
+ cursor++;
3275
+ const rewritten = binding.sampler !== void 0 && maps.samplerGuidByIndex.has(binding.sampler) ? {
3276
+ ...textureValue2,
3277
+ texture: textureRef,
3278
+ sampler: cursor
3279
+ } : { ...textureValue2, texture: textureRef };
3280
+ if (binding.sampler !== void 0 && maps.samplerGuidByIndex.has(binding.sampler)) cursor++;
3281
+ values[slot] = rewritten;
3282
+ }
3283
+ return { ...matAsset, values };
3284
+ }
3285
+ async function importGltf(ctx, meshopt) {
3286
+ const read = await ctx.readSource();
3287
+ if (!read.ok) {
3288
+ throw new Error(
3289
+ `gltfImporter: readSource failed: ${read.error instanceof Error ? read.error.message : String(read.error)}`
3290
+ );
3291
+ }
3292
+ const parsed = await parseDoc(ctx.source, read.value, ctx);
3293
+ if (!parsed.ok) return parsed;
3294
+ const doc = parsed.value;
3295
+ const maps = buildHandleMaps(ctx.subAssets, doc);
3296
+ const imageColorSpaces = deriveTextureColorSpace({
3297
+ imageCount: (doc.images ?? []).length,
3298
+ textures: doc.textures,
3299
+ materials: doc.materials
3300
+ });
3301
+ const declaredImageIndices = /* @__PURE__ */ new Set();
3302
+ for (const sub of ctx.subAssets) {
3303
+ if (sub.kind === "texture") declaredImageIndices.add(sub.sourceIndex);
3304
+ }
3305
+ const extraction = declaredImageIndices.size > 0 ? await extractImageBytes(read.value, ctx.source, ctx) : {
3306
+ extracted: /* @__PURE__ */ new Map(),
3307
+ failures: []
3308
+ };
3309
+ const skeletonGuidBySourceIndex = /* @__PURE__ */ new Map();
3310
+ for (const sub of ctx.subAssets) {
3311
+ if (sub.kind === "skeleton") skeletonGuidBySourceIndex.set(sub.sourceIndex, sub.guid);
3312
+ }
3313
+ const skinGuidBySourceIndex = /* @__PURE__ */ new Map();
3314
+ for (const sub of ctx.subAssets) {
3315
+ if (sub.kind === "skin") skinGuidBySourceIndex.set(sub.sourceIndex, sub.guid);
3316
+ }
3317
+ const out = [];
3318
+ const isMultiAsset = ctx.subAssets.length > 1;
3319
+ for (const sub of ctx.subAssets) {
3320
+ if (sub.kind === "mesh") {
3321
+ const prims = doc.meshes.filter((m) => m.meshIndex === sub.sourceIndex);
3322
+ if (prims.length === 0) continue;
3323
+ const meshName = isMultiAsset ? prims[0]?.name : void 0;
3324
+ const materialNameByIndex = /* @__PURE__ */ new Map();
3325
+ const materialSourceKeyByIndex = /* @__PURE__ */ new Map();
3326
+ for (let materialIndex = 0; materialIndex < doc.materials.length; materialIndex++) {
3327
+ const name = doc.materials[materialIndex]?.name;
3328
+ if (typeof name === "string") materialNameByIndex.set(materialIndex, name);
3329
+ }
3330
+ for (const material of ctx.subAssets) {
3331
+ if (material.kind === "material" && material.sourceKey !== void 0) {
3332
+ materialSourceKeyByIndex.set(material.sourceIndex, material.sourceKey);
3333
+ }
3334
+ }
3335
+ const bridged = meshIrToMeshAsset(prims, {
3336
+ guidByIndex: maps.materialGuidByIndex,
3337
+ nameByIndex: materialNameByIndex,
3338
+ sourceKeyByIndex: materialSourceKeyByIndex
3339
+ });
3340
+ if (!bridged.ok) {
3341
+ return {
3342
+ ok: false,
3343
+ error: new ImportError({
3344
+ code: "import-internal-error",
3345
+ expected: "gltf mesh bridge to produce a canonical MeshAsset",
3346
+ actual: bridged.error.code,
3347
+ hint: "repair the source primitive and re-run the glTF importer",
3348
+ detail: {
3349
+ reason: `gltf mesh bridge rejected mesh ${sub.sourceIndex}; inspect the bridge error detail`
3350
+ }
3351
+ })
3352
+ };
3353
+ }
3354
+ const meshPayload = stabilizeMeshMaterialSlots(bridged.value, ctx, sub.guid, sub.sourceKey);
3355
+ const materialRefs = [];
3356
+ const seenMaterialGuids = /* @__PURE__ */ new Set();
3357
+ for (let slotIndex = 0; slotIndex < meshPayload.materialSlots.length; slotIndex++) {
3358
+ const defaultMaterial = meshPayload.materialSlots[slotIndex]?.defaultMaterial;
3359
+ const guid = defaultMaterial === void 0 ? void 0 : AssetGuid.format(defaultMaterial);
3360
+ if (guid !== void 0 && !seenMaterialGuids.has(guid.toLowerCase())) {
3361
+ seenMaterialGuids.add(guid.toLowerCase());
3362
+ materialRefs.push({
3363
+ guid,
3364
+ sourceField: { fieldName: "materialSlots", arrayIndex: slotIndex }
3365
+ });
3366
+ }
3367
+ }
3368
+ out.push({
3369
+ guid: sub.guid,
3370
+ kind: "mesh",
3371
+ ...meshName !== void 0 ? { name: meshName } : {},
3372
+ payload: meshPayload,
3373
+ refs: materialRefs,
3374
+ artifacts: {
3375
+ body: {
3376
+ mediaType: "application/x-forgeax-mesh",
3377
+ assetCodec: { name: "mesh-binary", version: "4" },
3378
+ bytes: (() => {
3379
+ const packed = packMeshBinV4(
3380
+ meshPayload,
3381
+ sub.sourceKey ?? ctx.source,
3382
+ materialRefs.map((ref) => ref.guid)
3383
+ );
3384
+ if (!packed.ok) {
3385
+ throw new ImportError({
3386
+ code: "import-internal-error",
3387
+ expected: "mesh-bin v4 producer to accept the canonical mesh projection",
3388
+ hint: "re-cook the source with its Meta sidecar after fixing the mesh payload",
3389
+ detail: { reason: `${packed.error.code}: ${packed.error.actual}` }
3390
+ });
3391
+ }
3392
+ return packed.value;
3393
+ })()
3394
+ }
3395
+ }
3396
+ });
3397
+ } else if (sub.kind === "material") {
3398
+ const mat = doc.materials[sub.sourceIndex];
3399
+ if (mat === void 0) continue;
3400
+ let skinned = false;
3401
+ for (const meshIr of doc.meshes) {
3402
+ if (meshIr.materialIndex !== sub.sourceIndex) continue;
3403
+ if (meshIr.joints0 !== void 0 && meshIr.weights0 !== void 0) {
3404
+ skinned = true;
3405
+ break;
3406
+ }
3407
+ }
3408
+ for (let primitiveIndex = 0; primitiveIndex < doc.meshes.length; primitiveIndex++) {
3409
+ const meshIr = doc.meshes[primitiveIndex];
3410
+ if (meshIr?.materialIndex !== sub.sourceIndex) continue;
3411
+ const uvResult = validateMaterialUvSets(
3412
+ mat,
3413
+ `primitive-${primitiveIndex}`,
3414
+ availableUvSets(meshIr)
3415
+ );
3416
+ if (!uvResult.ok) {
3417
+ throw Object.assign(new Error(uvResult.error.message), uvResult.error);
3418
+ }
3419
+ }
3420
+ const matAsset = toMaterialAsset(mat, {
3421
+ textureHandles: maps.textureHandles,
3422
+ samplerHandles: maps.samplerHandles,
3423
+ skinned,
3424
+ ...typeof ctx.importSettings.standardMaterialGuid === "string" ? { standardRootGuid: ctx.importSettings.standardMaterialGuid } : {}
3425
+ });
3426
+ const refs = materialRefsForPack(mat, doc, maps.textureGuidByIndex, maps.samplerGuidByIndex);
3427
+ const materialRefs = [...refs];
3428
+ if (matAsset.parent !== void 0) {
3429
+ materialRefs.push({
3430
+ guid: matAsset.parent,
3431
+ sourceField: { fieldName: "parent" }
3432
+ });
3433
+ }
3434
+ const rewrittenAsset = rewriteMaterialAssetRefs(matAsset, mat, doc, maps);
3435
+ const matName = isMultiAsset ? mat.name : void 0;
3436
+ out.push({
3437
+ guid: sub.guid,
3438
+ kind: "material",
3439
+ ...matName !== void 0 ? { name: matName } : {},
3440
+ payload: rewrittenAsset,
3441
+ refs: materialRefs,
3442
+ artifacts: {}
3443
+ });
3444
+ } else if (sub.kind === "texture") {
3445
+ const imageIndex = sub.sourceIndex;
3446
+ const extracted = extraction.extracted.get(imageIndex);
3447
+ if (extracted === void 0) {
3448
+ const failure = extraction.failures.find((f) => f.imageIndex === imageIndex);
3449
+ const detail = failure ?? {
3450
+ imageIndex,
3451
+ source: "bufferView",
3452
+ reason: "image row missing from extraction map (no images[] entry?)"
3453
+ };
3454
+ const error = gltfErr("gltf-image-extract-failed", detail);
3455
+ throw new Error(
3456
+ `gltfImporter: ${error.code} on image ${imageIndex} (${detail.source}): ${detail.reason}`
3457
+ );
3458
+ }
3459
+ const colorSpace = imageColorSpaces.get(imageIndex) ?? "linear";
3460
+ const decodeSettings = {
3461
+ ...ctx.importSettings,
3462
+ colorSpace,
3463
+ mipmap: ctx.importSettings.mipmap ?? true
3464
+ };
3465
+ const decoded = await ctx.decodeImage(extracted.bytes, extracted.mimeType, decodeSettings);
3466
+ if (!decoded.ok) {
3467
+ const reason = `decodeImage failed: ${decoded.error.code}`;
3468
+ const error = gltfErr("gltf-image-extract-failed", {
3469
+ imageIndex,
3470
+ source: extracted.source,
3471
+ reason
3472
+ });
3473
+ throw new Error(
3474
+ `gltfImporter: ${error.code} on image ${imageIndex} (${extracted.source}): ${reason}`
3475
+ );
3476
+ }
3477
+ const imageItem = (doc.images ?? [])[imageIndex];
3478
+ const texName = isMultiAsset ? imageItem?.name : void 0;
3479
+ out.push({
3480
+ guid: sub.guid,
3481
+ kind: "texture",
3482
+ ...texName !== void 0 ? { name: texName } : {},
3483
+ payload: decoded.value.texture,
3484
+ refs: [],
3485
+ artifacts: {
3486
+ body: {
3487
+ mediaType: decoded.value.mediaType ?? extracted.mimeType,
3488
+ assetCodec: decoded.value.assetCodec ?? { name: "rgba8", version: "1" },
3489
+ bytes: decoded.value.bytes
3490
+ }
3491
+ }
3492
+ });
3493
+ } else if (sub.kind === "sampler") {
3494
+ const sampler = doc.samplers?.[sub.sourceIndex];
3495
+ if (sampler === void 0) continue;
3496
+ const filter = (value) => {
3497
+ if (value === void 0) return void 0;
3498
+ return value === 9728 || value === 9984 || value === 9986 || value === 9988 ? "nearest" : "linear";
3499
+ };
3500
+ const mipmapFilter = (value) => {
3501
+ if (value === void 0) return void 0;
3502
+ return value === 9984 || value === 9985 ? "nearest" : "linear";
3503
+ };
3504
+ const addressMode = (value) => {
3505
+ if (value === 33071) return "clamp-to-edge";
3506
+ if (value === 33648) return "mirror-repeat";
3507
+ return "repeat";
3508
+ };
3509
+ const magFilter = filter(sampler.magFilter);
3510
+ const minFilter = filter(sampler.minFilter);
3511
+ const mipmap = mipmapFilter(sampler.minFilter);
3512
+ const payload = {
3513
+ kind: "sampler",
3514
+ ...magFilter === void 0 ? {} : { magFilter },
3515
+ ...minFilter === void 0 ? {} : { minFilter },
3516
+ ...mipmap === void 0 ? {} : { mipmapFilter: mipmap },
3517
+ addressModeU: addressMode(sampler.wrapS),
3518
+ addressModeV: addressMode(sampler.wrapT)
3519
+ };
3520
+ out.push({ guid: sub.guid, kind: "sampler", payload, refs: [], artifacts: {} });
3521
+ } else if (sub.kind === "scene") {
3522
+ let makeRef2 = function(guid, idx) {
3523
+ const prov = handleValueProvenance.get(idx);
3524
+ if (prov !== void 0) {
3525
+ return {
3526
+ guid,
3527
+ sourceField: {
3528
+ componentName: prov.componentName,
3529
+ fieldName: prov.fieldName,
3530
+ ...prov.arrayIndex !== void 0 ? { arrayIndex: prov.arrayIndex } : {}
3531
+ },
3532
+ sceneEntityId: prov.sceneEntityId
3533
+ };
3534
+ }
3535
+ return { guid };
3536
+ };
3537
+ const scene = gltfDocToSceneAsset(doc, {
3538
+ meshHandles: maps.meshHandles,
3539
+ skeletonGuidBySkinIndex: skeletonGuidBySourceIndex
3540
+ });
3541
+ const handleValueProvenance = /* @__PURE__ */ new Map();
3542
+ const skeletonGuidProvenance = /* @__PURE__ */ new Map();
3543
+ for (const entity of scene.entities) {
3544
+ const comps = entity.components;
3545
+ const mf = comps.MeshFilter;
3546
+ if (mf !== void 0 && typeof mf.assetHandle === "number") {
3547
+ handleValueProvenance.set(mf.assetHandle, {
3548
+ sceneEntityId: entity.localId,
3549
+ componentName: "MeshFilter",
3550
+ fieldName: "assetHandle"
3551
+ });
3552
+ }
3553
+ const skin = comps.Skin;
3554
+ if (skin !== void 0 && typeof skin.skeleton === "string") {
3555
+ skeletonGuidProvenance.set(skin.skeleton, { sceneEntityId: entity.localId });
3556
+ }
3557
+ }
3558
+ const meshGuidList = [...maps.meshGuidByIndex.values()];
3559
+ const refs = [];
3560
+ let cursor = 0;
3561
+ for (const guid of meshGuidList) {
3562
+ refs.push(makeRef2(guid, cursor));
3563
+ cursor++;
3564
+ }
3565
+ {
3566
+ const skeletonGuidList = [...skeletonGuidBySourceIndex.values()];
3567
+ for (const guid of skeletonGuidList) {
3568
+ const skProv = skeletonGuidProvenance.get(guid);
3569
+ refs.push(
3570
+ skProv !== void 0 ? {
3571
+ guid,
3572
+ sourceField: { componentName: "Skin", fieldName: "skeleton" },
3573
+ sceneEntityId: skProv.sceneEntityId
3574
+ } : { guid }
3575
+ );
3576
+ cursor++;
3577
+ }
3578
+ }
3579
+ {
3580
+ const skinGuidList2 = [...skinGuidBySourceIndex.values()];
3581
+ for (const guid of skinGuidList2) {
3582
+ refs.push({ guid });
3583
+ cursor++;
3584
+ }
3585
+ }
3586
+ const skinGuidList = [...skinGuidBySourceIndex.values()];
3587
+ const sceneWithSkinGuids = skinGuidList.length > 0 ? { ...scene, skinGuids: skinGuidList } : scene;
3588
+ const sceneName = isMultiAsset ? doc.scenes[sub.sourceIndex]?.name : void 0;
3589
+ out.push({
3590
+ guid: sub.guid,
3591
+ kind: "scene",
3592
+ ...sceneName !== void 0 ? { name: sceneName } : {},
3593
+ payload: sceneWithSkinGuids,
3594
+ refs,
3595
+ artifacts: {}
3596
+ });
3597
+ } else if (sub.kind === "skeleton") {
3598
+ const rec = doc.skeletons[sub.sourceIndex];
3599
+ if (rec === void 0) continue;
3600
+ const payload = {
3601
+ kind: "skeleton",
3602
+ inverseBindMatrices: rec.inverseBindMatrices,
3603
+ jointCount: rec.jointCount
3604
+ };
3605
+ out.push({ guid: sub.guid, kind: "skeleton", payload, refs: [], artifacts: {} });
3606
+ } else if (sub.kind === "skin") {
3607
+ const rec = doc.skeletons[sub.sourceIndex];
3608
+ if (rec === void 0) continue;
3609
+ const skeletonGuid = skeletonGuidBySourceIndex.get(sub.sourceIndex);
3610
+ if (skeletonGuid === void 0) continue;
3611
+ const payload = {
3612
+ kind: "skin",
3613
+ skeletonGuid,
3614
+ jointPaths: rec.jointPaths
3615
+ };
3616
+ out.push({
3617
+ guid: sub.guid,
3618
+ kind: "skin",
3619
+ payload,
3620
+ refs: [{ guid: skeletonGuid, sourceField: { fieldName: "skeleton" } }],
3621
+ artifacts: {}
3622
+ });
3623
+ } else if (sub.kind === "animation-clip") {
3624
+ const rec = doc.animationClips[sub.sourceIndex];
3625
+ if (rec === void 0) continue;
3626
+ const payload = {
3627
+ kind: "animation-clip",
3628
+ duration: rec.duration,
3629
+ channels: rec.channels.map((ch) => ({
3630
+ targetId: ch.targetId,
3631
+ property: ch.property,
3632
+ sampler: {
3633
+ input: ch.sampler.input,
3634
+ output: ch.sampler.output,
3635
+ interpolation: ch.sampler.interpolation
3636
+ }
3637
+ }))
3638
+ };
3639
+ out.push({ guid: sub.guid, kind: "animation-clip", payload, refs: [], artifacts: {} });
3640
+ }
3641
+ }
3642
+ return { ok: true, value: { assets: out, sourceDependencies: [] } };
3643
+ }
3644
+ function createGltfImporter(meshopt) {
3645
+ return {
3646
+ key: "gltf",
3647
+ import: (ctx) => importGltf(ctx),
3648
+ capabilities: { catalog: { publish: publishesCatalogProduct } }
3649
+ };
3650
+ }
3651
+ var gltfImporter = createGltfImporter();
3652
+
3653
+ // src/serialize-meta.ts
3654
+ function sortKeysDeep(value) {
3655
+ if (Array.isArray(value)) return value.map(sortKeysDeep);
3656
+ if (value !== null && typeof value === "object") {
3657
+ const obj = value;
3658
+ const sorted = {};
3659
+ for (const key of Object.keys(obj).sort()) {
3660
+ sorted[key] = sortKeysDeep(obj[key]);
3661
+ }
3662
+ return sorted;
3663
+ }
3664
+ return value;
3665
+ }
3666
+ function serializeMetaJson(meta) {
3667
+ return `${JSON.stringify(sortKeysDeep(meta), null, 2)}
3668
+ `;
3669
+ }
3670
+
3671
+ export { COMPONENT_TYPE, EXTENSION_ALLOWLIST, GLTF_ERROR_HINTS, checkExtensions, decodeAccessor, decodeColorAccessor, decomposeNodeTransform, deriveGltfSourceKeys, gltfDocToSceneAsset, gltfErr, gltfImporter, meshIrToMeshAsset, parseGlb, parseGlbChunks, parseGltf, parseGltfHeader, reimportReuseMeta, serializeMetaJson, sourceKeyForGltfOutput, subAssetKey, toAssetPack, toMaterialAsset };
3672
+ //# sourceMappingURL=index.mjs.map
3673
+ //# sourceMappingURL=index.mjs.map