@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
@@ -0,0 +1,2435 @@
1
+ #!/usr/bin/env node
2
+ import { realpath, stat, readFile, writeFile, readdir } from 'fs/promises';
3
+ import { resolve, dirname, join } from 'path';
4
+ import { fileURLToPath } from 'url';
5
+ import { parseArgs } from 'util';
6
+ import { reconcileMeshMaterialSlotTopology, err, MESH_MATERIAL_SLOT_SOURCE_OVERRIDE_PAYLOAD_SCHEMA, ok, ImportError, IMPORT_ERROR_HINTS } from '@forgeax/engine-types';
7
+ import { vec3, quat, mat4 } from '@forgeax/engine-math';
8
+ import { deriveAnimationTargetId } from '@forgeax/engine-animation/target-id';
9
+ import { AssetGuid } from '@forgeax/engine-pack/guid';
10
+
11
+ var GLTF_MESHOPT_MODES = ["ATTRIBUTES", "TRIANGLES", "INDICES"];
12
+ var GLTF_MESHOPT_FILTERS = ["NONE", "OCTAHEDRAL", "QUATERNION", "EXPONENTIAL"];
13
+ var gltfErrorPolicy = {
14
+ "gltf-malformed-header": {
15
+ expected: "GLB 12-byte header (magic 0x46546C67 + version=2 + length) plus mandatory JSON chunk",
16
+ hint: "verify .glb is not truncated; rerun: forgeax-engine-remote-gltf import <path>"
17
+ },
18
+ "gltf-version-unsupported": {
19
+ expected: 'asset.version === "2.0"',
20
+ hint: 'asset.version must be "2.0"; v1 or v3 not supported'
21
+ },
22
+ "gltf-buffer-out-of-bounds": {
23
+ expected: "accessor byte range within bufferView.byteLength",
24
+ hint: "rebuild .gltf with valid bufferViews; check accessor index; ensure accessor.byteOffset + EFFECTIVE_STRIDE * (count - 1) + element_size <= bufferView.byteLength"
25
+ },
26
+ "gltf-extension-unsupported": {
27
+ expected: "extension listed in v1 allowlist (see EXTENSION_ALLOWLIST in @forgeax/engine-gltf)",
28
+ hint: "see feat-future-gltf-extensions-allowlist; remove this extension or wait for the allowlist to expand"
29
+ },
30
+ "gltf-accessor-type-mismatch": {
31
+ expected: "dense fixed-stride accessor with supported componentType",
32
+ hint: "sparse: see feat-future-gltf-sparse-accessor; morph: see feat-future-gltf-morph; interleaved: see feat-future-gltf-mesh-multi-section"
33
+ },
34
+ "gltf-texture-load-failed": {
35
+ expected: "externalLoader resolved the URI into an ArrayBuffer without throwing",
36
+ hint: "check sidecar meta.json + textures/ directory + vite-plugin-pack /__pack/lookup route"
37
+ },
38
+ "gltf-meta-missing": {
39
+ expected: "sidecar <source>.meta.json (importer: 'gltf') present in same directory",
40
+ hint: "run: forgeax-engine-remote-gltf import <path>"
41
+ },
42
+ "gltf-instancing-count-mismatch": {
43
+ expected: "all instance attribute accessors share the same count",
44
+ 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"
45
+ },
46
+ "gltf-image-mime-unsupported": {
47
+ expected: "image/mimeType is image/jpeg or image/png",
48
+ hint: "convert to JPG/PNG via external tool; only image/jpeg and image/png are supported"
49
+ },
50
+ "gltf-skin-joint-count-exceeded": {
51
+ expected: "skin.joints.length <= MAX_JOINTS (256)",
52
+ hint: "reduce joint count below MAX_JOINTS (256) or see OOS-skin-max-joints"
53
+ },
54
+ "gltf-animation-cubicspline-unsupported": {
55
+ expected: "animation sampler interpolation is LINEAR or STEP",
56
+ hint: "see OOS-skin-cubicspline; convert CUBICSPLINE to LINEAR/STEP in DCC tool"
57
+ },
58
+ "gltf-morph-unsupported": {
59
+ expected: "animation channel target path is one of translation, rotation, scale, or weights",
60
+ hint: "animation target path must be translation, rotation, scale, or weights"
61
+ },
62
+ "gltf-skin-joint-name-missing": {
63
+ expected: "every joint node has a non-empty name and belongs to an acyclic hierarchy",
64
+ hint: "ensure every joint node has a non-empty name and the node hierarchy is acyclic"
65
+ },
66
+ "gltf-image-extract-failed": {
67
+ expected: "image bytes extractable from bufferView / data-URI / external URI without corruption",
68
+ 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>"
69
+ },
70
+ "gltf-skin-attr-asymmetric": {
71
+ expected: "mesh primitive declares JOINTS_0 and WEIGHTS_0 symmetrically (both present or both absent)",
72
+ 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"
73
+ },
74
+ "gltf-animation-target-invalid": {
75
+ expected: "every animation channel resolves to one uniquely named scene node and stable target ID",
76
+ hint: "name every node in the animated hierarchy and ensure each animated full path is unique"
77
+ },
78
+ "gltf-meshopt-decoder-required": {
79
+ expected: "a required or compressed-only EXT_meshopt_compression bufferView has a ready decoder capability",
80
+ hint: "provide the build-only EXT_meshopt_compression decoder or author a valid core fallback bufferView"
81
+ },
82
+ "gltf-meshopt-decode-failed": {
83
+ expected: "the EXT_meshopt_compression declaration and decoder output are structurally valid",
84
+ hint: "the meshopt decoder accepts the declared compressed range and produces the declared byte count"
85
+ },
86
+ "gltf-morph-invalid": {
87
+ expected: "morph target and default-weight arrays are dense, bounded, and match the base vertex count",
88
+ hint: "re-export dense morph targets with at most eight targets/attributes and matching vertex/default-weight lengths"
89
+ },
90
+ "gltf-color-accessor-unsupported": {
91
+ expected: "COLOR_0 dense accessor uses VEC3/VEC4 FLOAT or normalized UBYTE/USHORT",
92
+ hint: "re-export COLOR_0 with a supported type/component/normalized combination; morph and sparse COLOR_0 remain deferred"
93
+ },
94
+ "gltf-color-accessor-malformed": {
95
+ expected: "COLOR_0 accessor is non-empty, finite, in range, and fully addressable",
96
+ hint: "repair the COLOR_0 accessor count, reference, range, or buffer bounds, then re-import the glTF source"
97
+ },
98
+ "gltf-mesh-bridge-invalid": {
99
+ expected: "a non-empty merged mesh with consistent morph and COLOR_0 cardinality",
100
+ hint: "repair the source primitive and re-import; inspect detail.reason and its typed facts"
101
+ }
102
+ };
103
+ var GLTF_ERROR_HINTS = Object.fromEntries(
104
+ Object.entries(gltfErrorPolicy).map(([code, policy]) => [code, policy.hint])
105
+ );
106
+ function gltfErr(code, detail) {
107
+ return {
108
+ code,
109
+ expected: gltfErrorPolicy[code].expected,
110
+ hint: gltfErrorPolicy[code].hint,
111
+ detail
112
+ };
113
+ }
114
+
115
+ // src/accessor/decode-accessor.ts
116
+ var COMPONENT_TYPE = {
117
+ I8: 5120,
118
+ U8: 5121,
119
+ I16: 5122,
120
+ U16: 5123,
121
+ U32: 5125,
122
+ F32: 5126
123
+ };
124
+ var TYPE_COMPONENT_COUNT = {
125
+ SCALAR: 1,
126
+ VEC2: 2,
127
+ VEC3: 3,
128
+ VEC4: 4,
129
+ MAT2: 4,
130
+ MAT3: 9,
131
+ MAT4: 16
132
+ };
133
+ var COMPONENT_BYTE_SIZE = {
134
+ [COMPONENT_TYPE.I8]: 1,
135
+ [COMPONENT_TYPE.U8]: 1,
136
+ [COMPONENT_TYPE.I16]: 2,
137
+ [COMPONENT_TYPE.U16]: 2,
138
+ [COMPONENT_TYPE.U32]: 4,
139
+ [COMPONENT_TYPE.F32]: 4
140
+ };
141
+ function decodeF32Accessor(accessorIndex, accessor, allowedTypes, bufferViews, buffers) {
142
+ if (accessor.componentType !== COMPONENT_TYPE.F32 || !allowedTypes.includes(accessor.type)) {
143
+ return err(
144
+ gltfErr("gltf-accessor-type-mismatch", { accessorIndex, reason: "unknownComponentType" })
145
+ );
146
+ }
147
+ const bufferViewIndex = accessor.bufferView;
148
+ if (bufferViewIndex === void 0) {
149
+ return err(
150
+ gltfErr("gltf-buffer-out-of-bounds", {
151
+ accessor: accessorIndex,
152
+ byteOffset: 0,
153
+ byteLength: 0,
154
+ bufferIndex: 0
155
+ })
156
+ );
157
+ }
158
+ const bufferView = bufferViews[bufferViewIndex];
159
+ if (bufferView === void 0) {
160
+ return err(
161
+ gltfErr("gltf-buffer-out-of-bounds", {
162
+ accessor: accessorIndex,
163
+ byteOffset: 0,
164
+ byteLength: 0,
165
+ bufferIndex: bufferViewIndex
166
+ })
167
+ );
168
+ }
169
+ const buffer = buffers[bufferView.buffer];
170
+ if (buffer === void 0) {
171
+ return err(
172
+ gltfErr("gltf-buffer-out-of-bounds", {
173
+ accessor: accessorIndex,
174
+ byteOffset: bufferView.byteOffset ?? 0,
175
+ byteLength: bufferView.byteLength,
176
+ bufferIndex: bufferView.buffer
177
+ })
178
+ );
179
+ }
180
+ const decoded = decodeAccessor({
181
+ accessor: { ...accessor, bufferView: bufferViewIndex },
182
+ bufferView,
183
+ buffer,
184
+ accessorIndex,
185
+ role: "attribute"
186
+ });
187
+ if (!decoded.ok) return decoded;
188
+ if (decoded.value.kind !== "f32") {
189
+ return err(
190
+ gltfErr("gltf-accessor-type-mismatch", { accessorIndex, reason: "unknownComponentType" })
191
+ );
192
+ }
193
+ return ok(decoded.value.data);
194
+ }
195
+ function decodeAccessor(input, flags = {}) {
196
+ const { accessorIndex, accessor, bufferView, buffer, role } = input;
197
+ if (flags.morph === true) {
198
+ return err(
199
+ gltfErr("gltf-accessor-type-mismatch", {
200
+ accessorIndex,
201
+ reason: "morph"
202
+ })
203
+ );
204
+ }
205
+ if ("sparse" in accessor && accessor.sparse !== void 0) {
206
+ return err(
207
+ gltfErr("gltf-accessor-type-mismatch", {
208
+ accessorIndex,
209
+ reason: "sparse"
210
+ })
211
+ );
212
+ }
213
+ const componentByteSize = COMPONENT_BYTE_SIZE[accessor.componentType];
214
+ const componentCount = TYPE_COMPONENT_COUNT[accessor.type];
215
+ if (componentByteSize === void 0 || componentCount === void 0) {
216
+ return err(
217
+ gltfErr("gltf-accessor-type-mismatch", {
218
+ accessorIndex,
219
+ reason: "unknownComponentType"
220
+ })
221
+ );
222
+ }
223
+ const elementSize = componentByteSize * componentCount;
224
+ const byteStride = bufferView.byteStride;
225
+ if (byteStride !== void 0 && byteStride !== elementSize) {
226
+ return err(
227
+ gltfErr("gltf-accessor-type-mismatch", {
228
+ accessorIndex,
229
+ reason: "interleaved"
230
+ })
231
+ );
232
+ }
233
+ const accessorByteOffset = accessor.byteOffset ?? 0;
234
+ const bufferViewByteOffset = bufferView.byteOffset ?? 0;
235
+ const totalByteLength = elementSize * accessor.count;
236
+ if (accessorByteOffset + totalByteLength > bufferView.byteLength) {
237
+ return err(
238
+ gltfErr("gltf-buffer-out-of-bounds", {
239
+ accessor: accessorIndex,
240
+ byteOffset: bufferViewByteOffset + accessorByteOffset,
241
+ byteLength: totalByteLength,
242
+ bufferIndex: bufferView.buffer
243
+ })
244
+ );
245
+ }
246
+ const absoluteOffset = bufferViewByteOffset + accessorByteOffset;
247
+ if (absoluteOffset + totalByteLength > buffer.byteLength) {
248
+ return err(
249
+ gltfErr("gltf-buffer-out-of-bounds", {
250
+ accessor: accessorIndex,
251
+ byteOffset: absoluteOffset,
252
+ byteLength: totalByteLength,
253
+ bufferIndex: bufferView.buffer
254
+ })
255
+ );
256
+ }
257
+ if (role === "indices" && accessor.componentType === COMPONENT_TYPE.U8) {
258
+ const widened = new Uint16Array(accessor.count);
259
+ for (let i = 0; i < accessor.count; i++) {
260
+ widened[i] = buffer[absoluteOffset + i] ?? 0;
261
+ }
262
+ return ok({ kind: "u16", data: widened });
263
+ }
264
+ if (role === "joints" && accessor.componentType === COMPONENT_TYPE.U8) {
265
+ const totalValues = accessor.count * componentCount;
266
+ const widened = new Uint16Array(totalValues);
267
+ for (let i = 0; i < totalValues; i++) {
268
+ widened[i] = buffer[absoluteOffset + i] ?? 0;
269
+ }
270
+ return ok({ kind: "u16", data: widened });
271
+ }
272
+ if (accessor.componentType === COMPONENT_TYPE.F32) {
273
+ const out = new Float32Array(accessor.count * componentCount);
274
+ const src = new Float32Array(buffer.buffer, buffer.byteOffset + absoluteOffset, out.length);
275
+ out.set(src);
276
+ return ok({ kind: "f32", data: out });
277
+ }
278
+ if (accessor.componentType === COMPONENT_TYPE.U16) {
279
+ const out = new Uint16Array(accessor.count * componentCount);
280
+ const src = new Uint16Array(buffer.buffer, buffer.byteOffset + absoluteOffset, out.length);
281
+ out.set(src);
282
+ return ok({ kind: "u16", data: out });
283
+ }
284
+ if (accessor.componentType === COMPONENT_TYPE.U32) {
285
+ const out = new Uint32Array(accessor.count * componentCount);
286
+ const src = new Uint32Array(buffer.buffer, buffer.byteOffset + absoluteOffset, out.length);
287
+ out.set(src);
288
+ return ok({ kind: "u32", data: out });
289
+ }
290
+ if (accessor.componentType === COMPONENT_TYPE.I16) {
291
+ const out = new Float32Array(accessor.count * componentCount);
292
+ const src = new Int16Array(buffer.buffer, buffer.byteOffset + absoluteOffset, out.length);
293
+ for (let index = 0; index < src.length; index++) {
294
+ const value = src[index] ?? 0;
295
+ out[index] = accessor.normalized === true ? Math.max(-1, value / 32767) : value;
296
+ }
297
+ return ok({ kind: "f32", data: out });
298
+ }
299
+ return err(
300
+ gltfErr("gltf-accessor-type-mismatch", {
301
+ accessorIndex,
302
+ reason: "unknownComponentType"
303
+ })
304
+ );
305
+ }
306
+
307
+ // src/accessor/decode-color.ts
308
+ var FLOAT = 5126;
309
+ var UNSIGNED_BYTE = 5121;
310
+ var UNSIGNED_SHORT = 5123;
311
+ function unsupported(input, reason) {
312
+ return err(
313
+ gltfErr("gltf-color-accessor-unsupported", {
314
+ semantic: input.semantic,
315
+ accessorIndex: input.accessorIndex,
316
+ reason,
317
+ expectedType: "VEC3 or VEC4",
318
+ expectedComponent: "FLOAT or normalized UNSIGNED_BYTE/UNSIGNED_SHORT",
319
+ ...reason === "normalized" ? { expectedNormalized: true } : {}
320
+ })
321
+ );
322
+ }
323
+ function malformed(input, reason) {
324
+ return err(
325
+ gltfErr("gltf-color-accessor-malformed", {
326
+ semantic: input.semantic,
327
+ accessorIndex: input.accessorIndex,
328
+ reason,
329
+ ...reason === "count" ? { expectedCount: "matches POSITION vertex count and is greater than zero" } : {},
330
+ ...reason === "range" || reason === "finite" ? { expectedRange: "[0,1] finite linear values" } : {}
331
+ })
332
+ );
333
+ }
334
+ function decodeColorAccessor(input, flags = {}) {
335
+ const { accessor, bufferView, buffer } = input;
336
+ if (flags.morph === true) return unsupported(input, "morph");
337
+ if (accessor.sparse !== void 0) return unsupported(input, "sparse");
338
+ if (accessor.type !== "VEC3" && accessor.type !== "VEC4") return unsupported(input, "type");
339
+ if (!Number.isSafeInteger(accessor.count) || accessor.count <= 0)
340
+ return malformed(input, "count");
341
+ let componentByteSize;
342
+ if (accessor.componentType === FLOAT) componentByteSize = 4;
343
+ else if (accessor.componentType === UNSIGNED_BYTE) componentByteSize = 1;
344
+ else if (accessor.componentType === UNSIGNED_SHORT) componentByteSize = 2;
345
+ else return unsupported(input, "component");
346
+ if (accessor.componentType === FLOAT) {
347
+ if (accessor.normalized !== void 0) return unsupported(input, "normalized");
348
+ } else if (accessor.normalized !== true) {
349
+ return unsupported(input, "normalized");
350
+ }
351
+ const componentCount = accessor.type === "VEC3" ? 3 : 4;
352
+ const elementByteLength = componentByteSize * componentCount;
353
+ const byteStride = bufferView.byteStride ?? elementByteLength;
354
+ if (!Number.isSafeInteger(byteStride) || byteStride < elementByteLength || byteStride % componentByteSize !== 0) {
355
+ return malformed(input, "bounds");
356
+ }
357
+ if (input.bufferIndex !== void 0 && bufferView.buffer !== input.bufferIndex) {
358
+ return malformed(input, "reference");
359
+ }
360
+ const accessorByteOffset = accessor.byteOffset ?? 0;
361
+ const viewByteOffset = bufferView.byteOffset ?? 0;
362
+ const lastElementEnd = accessorByteOffset + byteStride * (accessor.count - 1) + elementByteLength;
363
+ if (!Number.isSafeInteger(accessorByteOffset) || accessorByteOffset < 0 || lastElementEnd > bufferView.byteLength) {
364
+ return malformed(input, "bounds");
365
+ }
366
+ const absoluteLastElementEnd = viewByteOffset + lastElementEnd;
367
+ if (!Number.isSafeInteger(viewByteOffset) || viewByteOffset < 0 || absoluteLastElementEnd > buffer.byteLength) {
368
+ return malformed(input, "bounds");
369
+ }
370
+ const output = new Float32Array(accessor.count * 4);
371
+ const view = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength);
372
+ const base = viewByteOffset + accessorByteOffset;
373
+ for (let vertex = 0; vertex < accessor.count; vertex++) {
374
+ const sourceOffset = base + vertex * byteStride;
375
+ for (let component = 0; component < componentCount; component++) {
376
+ const byteOffset = sourceOffset + component * componentByteSize;
377
+ let value;
378
+ if (accessor.componentType === FLOAT) value = view.getFloat32(byteOffset, true);
379
+ else if (accessor.componentType === UNSIGNED_BYTE) value = view.getUint8(byteOffset) / 255;
380
+ else value = view.getUint16(byteOffset, true) / 65535;
381
+ if (!Number.isFinite(value)) return malformed(input, "finite");
382
+ if (value < 0 || value > 1) return malformed(input, "range");
383
+ output[vertex * 4 + component] = value;
384
+ }
385
+ if (componentCount === 3) output[vertex * 4 + 3] = 1;
386
+ }
387
+ return ok(output);
388
+ }
389
+
390
+ // src/check-extensions.ts
391
+ var EXTENSION_ALLOWLIST = [
392
+ "EXT_mesh_gpu_instancing",
393
+ "EXT_meshopt_compression"
394
+ ];
395
+ var SUPPORTED_EXTENSIONS = [
396
+ ...EXTENSION_ALLOWLIST,
397
+ "KHR_lights_punctual",
398
+ "KHR_texture_transform"
399
+ ];
400
+ function checkExtensions(json) {
401
+ const required = json.extensionsRequired ?? [];
402
+ for (const ext of required) {
403
+ if (!SUPPORTED_EXTENSIONS.includes(ext)) {
404
+ return err(
405
+ gltfErr("gltf-extension-unsupported", {
406
+ extension: ext,
407
+ source: "extensionsRequired"
408
+ })
409
+ );
410
+ }
411
+ }
412
+ const used = json.extensionsUsed ?? [];
413
+ const unsupportedUsed = [];
414
+ for (const ext of used) {
415
+ if (!SUPPORTED_EXTENSIONS.includes(ext)) {
416
+ unsupportedUsed.push(ext);
417
+ }
418
+ }
419
+ return ok({ unsupportedUsed });
420
+ }
421
+
422
+ // src/data-uri.ts
423
+ var DATA_URI_BASE64_RE = /^data:[^;,]*(?:;[^,;]+)*;base64,(.*)$/;
424
+ var Base64DecodeError = class extends Error {
425
+ constructor(cause) {
426
+ super(
427
+ `base64 payload decode failed: ${cause instanceof Error ? cause.message : String(cause)}`
428
+ );
429
+ this.name = "Base64DecodeError";
430
+ }
431
+ };
432
+ function dataUriBase64Payload(uri) {
433
+ const match = DATA_URI_BASE64_RE.exec(uri);
434
+ return match === null ? void 0 : match[1] ?? "";
435
+ }
436
+ function decodeBase64(b64) {
437
+ let binary;
438
+ try {
439
+ binary = atob(b64);
440
+ } catch (cause) {
441
+ throw new Base64DecodeError(cause);
442
+ }
443
+ const out = new Uint8Array(binary.length);
444
+ for (let i = 0; i < binary.length; i++) {
445
+ out[i] = binary.charCodeAt(i);
446
+ }
447
+ return out;
448
+ }
449
+
450
+ // src/meshopt-decode.ts
451
+ function viewBytes(view, buffers) {
452
+ const buffer = buffers[view.buffer];
453
+ const offset = view.byteOffset ?? 0;
454
+ if (buffer === void 0 || view.byteLength <= 0 || offset < 0 || offset + view.byteLength > buffer.length) {
455
+ return void 0;
456
+ }
457
+ return buffer.subarray(offset, offset + view.byteLength);
458
+ }
459
+ function validMode(mode) {
460
+ return GLTF_MESHOPT_MODES.some((candidate) => candidate === mode);
461
+ }
462
+ function validFilter(filter) {
463
+ return GLTF_MESHOPT_FILTERS.some((candidate) => candidate === filter);
464
+ }
465
+ function filterAllowed(mode, filter) {
466
+ return mode === "ATTRIBUTES" || filter === "NONE";
467
+ }
468
+ async function projectMeshoptBufferViews(inputViews, inputBuffers, extensionsRequired, capability) {
469
+ const buffers = [...inputBuffers];
470
+ const bufferViews = inputViews.map((view) => ({ ...view }));
471
+ let decodedCount = 0;
472
+ for (let index = 0; index < bufferViews.length; index++) {
473
+ const view = bufferViews[index];
474
+ if (view === void 0) continue;
475
+ const extension = view.extensions?.EXT_meshopt_compression;
476
+ if (extension === void 0) continue;
477
+ const filter = extension.filter ?? "NONE";
478
+ 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) {
479
+ return err(
480
+ gltfErr("gltf-meshopt-decode-failed", {
481
+ bufferView: index,
482
+ actual: "invalid mode/filter/stride/count/byteLength",
483
+ mode: validMode(extension.mode) ? extension.mode : "ATTRIBUTES",
484
+ filter: validFilter(filter) ? filter : "NONE"
485
+ })
486
+ );
487
+ }
488
+ const fallback = viewBytes(view, buffers);
489
+ const required = extensionsRequired.includes("EXT_meshopt_compression");
490
+ if (capability === void 0 && !required && fallback !== void 0) continue;
491
+ if (capability === void 0) {
492
+ return err(
493
+ gltfErr("gltf-meshopt-decoder-required", {
494
+ bufferView: index,
495
+ actual: required ? "required" : "compressed-only",
496
+ hasCoreFallback: fallback !== void 0
497
+ })
498
+ );
499
+ }
500
+ const compressedBuffer = buffers[extension.buffer];
501
+ const compressedOffset = extension.byteOffset ?? 0;
502
+ if (compressedBuffer === void 0 || !Number.isInteger(compressedOffset) || compressedOffset < 0 || compressedOffset + extension.byteLength > compressedBuffer.length) {
503
+ return err(
504
+ gltfErr("gltf-meshopt-decode-failed", {
505
+ bufferView: index,
506
+ actual: "compressed range out of bounds",
507
+ mode: extension.mode,
508
+ filter
509
+ })
510
+ );
511
+ }
512
+ let decoded;
513
+ try {
514
+ decoded = await capability.decode({
515
+ source: compressedBuffer.subarray(
516
+ compressedOffset,
517
+ compressedOffset + extension.byteLength
518
+ ),
519
+ count: extension.count,
520
+ stride: extension.byteStride,
521
+ mode: extension.mode,
522
+ filter
523
+ });
524
+ } catch (cause) {
525
+ return err(
526
+ gltfErr("gltf-meshopt-decode-failed", {
527
+ bufferView: index,
528
+ actual: `decoder rejected compressed range: ${cause instanceof Error ? cause.message : String(cause)}`,
529
+ mode: extension.mode,
530
+ filter
531
+ })
532
+ );
533
+ }
534
+ const expectedLength = extension.count * extension.byteStride;
535
+ if (!(decoded instanceof Uint8Array) || decoded.byteLength !== expectedLength) {
536
+ return err(
537
+ gltfErr("gltf-meshopt-decode-failed", {
538
+ bufferView: index,
539
+ actual: `decoded byteLength=${decoded?.byteLength ?? "invalid"}, expected=${expectedLength}`,
540
+ mode: extension.mode,
541
+ filter
542
+ })
543
+ );
544
+ }
545
+ const decodedBufferIndex = buffers.length;
546
+ buffers.push(decoded);
547
+ const { extensions: _extensions, ...plainView } = view;
548
+ bufferViews[index] = {
549
+ ...plainView,
550
+ buffer: decodedBufferIndex,
551
+ byteOffset: 0,
552
+ byteLength: expectedLength
553
+ };
554
+ decodedCount++;
555
+ }
556
+ return ok({ bufferViews, buffers, decodedCount });
557
+ }
558
+
559
+ // src/node-path.ts
560
+ function buildNodeParentMap(nodes) {
561
+ const parents = /* @__PURE__ */ new Map();
562
+ for (let index = 0; index < nodes.length; index++) {
563
+ for (const child of nodes[index]?.children ?? []) parents.set(child, index);
564
+ }
565
+ return parents;
566
+ }
567
+ function resolveNamedNodePath(nodes, parents, nodeIndex) {
568
+ const reversed = [];
569
+ const visited = /* @__PURE__ */ new Set();
570
+ let current = nodeIndex;
571
+ while (current !== void 0) {
572
+ if (visited.has(current)) {
573
+ return { ok: false, reason: "hierarchy-cycle", nodeIndex: current };
574
+ }
575
+ visited.add(current);
576
+ const name = nodes[current]?.name;
577
+ if (name === void 0 || name.length === 0) {
578
+ return { ok: false, reason: "name-missing", nodeIndex: current };
579
+ }
580
+ reversed.push(name);
581
+ current = parents.get(current);
582
+ }
583
+ return { ok: true, value: reversed.reverse() };
584
+ }
585
+
586
+ // src/parse-animation.ts
587
+ var ANIMATION_ACCESSOR_TYPES = ["SCALAR", "VEC2", "VEC3", "VEC4"];
588
+ function parseAnimation(animationsJson, nodesJson, accessors, bufferViews, buffers) {
589
+ if (animationsJson === void 0 || animationsJson.length === 0) {
590
+ return ok([]);
591
+ }
592
+ const clips = [];
593
+ const parentOf = buildNodeParentMap(nodesJson);
594
+ const nodeByPath = /* @__PURE__ */ new Map();
595
+ const pathById = /* @__PURE__ */ new Map();
596
+ for (let animIdx = 0; animIdx < animationsJson.length; animIdx++) {
597
+ const anim = animationsJson[animIdx];
598
+ if (anim === void 0) continue;
599
+ const decodedSamplers = [];
600
+ for (let sampIdx = 0; sampIdx < anim.samplers.length; sampIdx++) {
601
+ const sampler = anim.samplers[sampIdx];
602
+ if (sampler === void 0) continue;
603
+ const interpolation = sampler.interpolation ?? "LINEAR";
604
+ if (interpolation === "CUBICSPLINE") {
605
+ return err(
606
+ gltfErr("gltf-animation-cubicspline-unsupported", {
607
+ animationIndex: animIdx,
608
+ samplerIndex: sampIdx
609
+ })
610
+ );
611
+ }
612
+ if (interpolation !== "LINEAR" && interpolation !== "STEP") {
613
+ return err(
614
+ gltfErr("gltf-animation-cubicspline-unsupported", {
615
+ animationIndex: animIdx,
616
+ samplerIndex: sampIdx
617
+ })
618
+ );
619
+ }
620
+ const inputAcc = accessors[sampler.input];
621
+ if (inputAcc === void 0) {
622
+ return err(
623
+ gltfErr("gltf-buffer-out-of-bounds", {
624
+ accessor: sampler.input,
625
+ byteOffset: 0,
626
+ byteLength: 0,
627
+ bufferIndex: 0
628
+ })
629
+ );
630
+ }
631
+ const inputResult = decodeF32Accessor(
632
+ sampler.input,
633
+ inputAcc,
634
+ ANIMATION_ACCESSOR_TYPES,
635
+ bufferViews,
636
+ buffers
637
+ );
638
+ if (!inputResult.ok) return err(inputResult.error);
639
+ const outputAcc = accessors[sampler.output];
640
+ if (outputAcc === void 0) {
641
+ return err(
642
+ gltfErr("gltf-buffer-out-of-bounds", {
643
+ accessor: sampler.output,
644
+ byteOffset: 0,
645
+ byteLength: 0,
646
+ bufferIndex: 0
647
+ })
648
+ );
649
+ }
650
+ const outputResult = decodeF32Accessor(
651
+ sampler.output,
652
+ outputAcc,
653
+ ANIMATION_ACCESSOR_TYPES,
654
+ bufferViews,
655
+ buffers
656
+ );
657
+ if (!outputResult.ok) return err(outputResult.error);
658
+ decodedSamplers.push({
659
+ input: inputResult.value,
660
+ output: outputResult.value,
661
+ interpolation
662
+ });
663
+ }
664
+ const channels = [];
665
+ for (let chIdx = 0; chIdx < anim.channels.length; chIdx++) {
666
+ const ch = anim.channels[chIdx];
667
+ if (ch === void 0) continue;
668
+ const samplerRecord = decodedSamplers[ch.sampler];
669
+ if (samplerRecord === void 0) {
670
+ return err(
671
+ gltfErr("gltf-buffer-out-of-bounds", {
672
+ accessor: ch.sampler,
673
+ byteOffset: 0,
674
+ byteLength: 0,
675
+ bufferIndex: 0
676
+ })
677
+ );
678
+ }
679
+ const targetNodeIdx = ch.target.node;
680
+ const path = targetNodeIdx === void 0 ? { ok: false, reason: "name-missing", nodeIndex: -1 } : resolveNamedNodePath(nodesJson, parentOf, targetNodeIdx);
681
+ if (!path.ok) {
682
+ return err(
683
+ gltfErr("gltf-animation-target-invalid", {
684
+ reason: path.reason,
685
+ animationIndex: animIdx,
686
+ channelIndex: chIdx,
687
+ nodeIndex: path.nodeIndex
688
+ })
689
+ );
690
+ }
691
+ if (ch.target.path !== "weights" && ch.target.path !== "translation" && ch.target.path !== "rotation" && ch.target.path !== "scale") {
692
+ return err(
693
+ gltfErr("gltf-morph-unsupported", {
694
+ animationIndex: animIdx,
695
+ channelIndex: chIdx,
696
+ nodeIndex: ch.target.node ?? -1
697
+ })
698
+ );
699
+ }
700
+ const pathKey = JSON.stringify(path.value);
701
+ const previousNode = nodeByPath.get(pathKey);
702
+ if (previousNode !== void 0 && previousNode !== targetNodeIdx) {
703
+ return err(
704
+ gltfErr("gltf-animation-target-invalid", {
705
+ reason: "path-duplicate",
706
+ animationIndex: animIdx,
707
+ channelIndex: chIdx,
708
+ nodeIndex: targetNodeIdx
709
+ })
710
+ );
711
+ }
712
+ nodeByPath.set(pathKey, targetNodeIdx);
713
+ const targetId = deriveAnimationTargetId(path.value);
714
+ const previousPath = pathById.get(targetId);
715
+ if (previousPath !== void 0 && previousPath !== pathKey) {
716
+ return err(
717
+ gltfErr("gltf-animation-target-invalid", {
718
+ reason: "id-collision",
719
+ animationIndex: animIdx,
720
+ channelIndex: chIdx,
721
+ nodeIndex: targetNodeIdx
722
+ })
723
+ );
724
+ }
725
+ pathById.set(targetId, pathKey);
726
+ channels.push({
727
+ targetId,
728
+ targetNodeIndex: targetNodeIdx,
729
+ property: ch.target.path,
730
+ sampler: samplerRecord
731
+ });
732
+ }
733
+ let duration = 0;
734
+ for (const ch of channels) {
735
+ const input = ch.sampler.input;
736
+ if (input.length > 0) {
737
+ const last = input[input.length - 1];
738
+ if (last !== void 0 && last > duration) {
739
+ duration = last;
740
+ }
741
+ }
742
+ }
743
+ const name = anim.name?.trim();
744
+ clips.push({
745
+ ...name === void 0 || name.length === 0 ? {} : { name },
746
+ duration,
747
+ channels
748
+ });
749
+ }
750
+ return ok(clips);
751
+ }
752
+
753
+ // src/parse-gltf-header.ts
754
+ function parseGltfHeader(json, filePath) {
755
+ if (json === null || typeof json !== "object" || !("asset" in json)) {
756
+ return err(
757
+ gltfErr("gltf-malformed-header", {
758
+ filePath,
759
+ byteOffset: 0
760
+ })
761
+ );
762
+ }
763
+ const asset = json.asset;
764
+ if (!asset || typeof asset !== "object" || typeof asset.version !== "string") {
765
+ return err(
766
+ gltfErr("gltf-malformed-header", {
767
+ filePath,
768
+ byteOffset: 0
769
+ })
770
+ );
771
+ }
772
+ if (asset.version !== "2.0") {
773
+ return err(
774
+ gltfErr("gltf-version-unsupported", {
775
+ filePath,
776
+ actualVersion: asset.version
777
+ })
778
+ );
779
+ }
780
+ return ok(json);
781
+ }
782
+
783
+ // src/parse-glb-chunks.ts
784
+ var GLB_HEADER_SIZE = 12;
785
+ var GLB_MAGIC = 1179937895;
786
+ var CHUNK_HEADER_SIZE = 8;
787
+ var CHUNK_TYPE_JSON = 1313821514;
788
+ var CHUNK_TYPE_BIN = 5130562;
789
+ function parseGlbChunks(buffer, filePath) {
790
+ if (buffer.byteLength < GLB_HEADER_SIZE) {
791
+ return err(
792
+ gltfErr("gltf-malformed-header", {
793
+ filePath,
794
+ byteOffset: 0
795
+ })
796
+ );
797
+ }
798
+ const view = new DataView(buffer);
799
+ const magic = view.getUint32(0, true);
800
+ if (magic !== GLB_MAGIC) {
801
+ return err(
802
+ gltfErr("gltf-malformed-header", {
803
+ filePath,
804
+ byteOffset: 0,
805
+ magic
806
+ })
807
+ );
808
+ }
809
+ const version = view.getUint32(4, true);
810
+ if (version !== 2) {
811
+ return err(
812
+ gltfErr("gltf-version-unsupported", {
813
+ filePath,
814
+ actualVersion: String(version)
815
+ })
816
+ );
817
+ }
818
+ const declaredLength = view.getUint32(8, true);
819
+ if (declaredLength !== buffer.byteLength) {
820
+ return err(
821
+ gltfErr("gltf-malformed-header", {
822
+ filePath,
823
+ byteOffset: 8
824
+ })
825
+ );
826
+ }
827
+ let offset = GLB_HEADER_SIZE;
828
+ if (offset + CHUNK_HEADER_SIZE > buffer.byteLength) {
829
+ return err(
830
+ gltfErr("gltf-malformed-header", {
831
+ filePath,
832
+ byteOffset: offset
833
+ })
834
+ );
835
+ }
836
+ const jsonChunkLength = view.getUint32(offset, true);
837
+ const jsonChunkType = view.getUint32(offset + 4, true);
838
+ if (jsonChunkType !== CHUNK_TYPE_JSON) {
839
+ return err(
840
+ gltfErr("gltf-malformed-header", {
841
+ filePath,
842
+ byteOffset: offset + 4
843
+ })
844
+ );
845
+ }
846
+ if (offset + CHUNK_HEADER_SIZE + jsonChunkLength > buffer.byteLength) {
847
+ return err(
848
+ gltfErr("gltf-malformed-header", {
849
+ filePath,
850
+ byteOffset: offset
851
+ })
852
+ );
853
+ }
854
+ const jsonChunk = new Uint8Array(buffer, offset + CHUNK_HEADER_SIZE, jsonChunkLength);
855
+ offset += CHUNK_HEADER_SIZE + jsonChunkLength;
856
+ let binChunk;
857
+ if (offset < buffer.byteLength) {
858
+ if (offset + CHUNK_HEADER_SIZE > buffer.byteLength) {
859
+ return err(
860
+ gltfErr("gltf-malformed-header", {
861
+ filePath,
862
+ byteOffset: offset
863
+ })
864
+ );
865
+ }
866
+ const binChunkLength = view.getUint32(offset, true);
867
+ const binChunkType = view.getUint32(offset + 4, true);
868
+ if (binChunkType !== CHUNK_TYPE_BIN) {
869
+ return err(
870
+ gltfErr("gltf-malformed-header", {
871
+ filePath,
872
+ byteOffset: offset + 4
873
+ })
874
+ );
875
+ }
876
+ if (offset + CHUNK_HEADER_SIZE + binChunkLength > buffer.byteLength) {
877
+ return err(
878
+ gltfErr("gltf-malformed-header", {
879
+ filePath,
880
+ byteOffset: offset
881
+ })
882
+ );
883
+ }
884
+ binChunk = new Uint8Array(buffer, offset + CHUNK_HEADER_SIZE, binChunkLength);
885
+ }
886
+ return ok({
887
+ version,
888
+ length: declaredLength,
889
+ jsonChunk,
890
+ ...binChunk === void 0 ? {} : { binChunk }
891
+ });
892
+ }
893
+
894
+ // src/parse-skin.ts
895
+ var MAX_JOINTS = 256;
896
+ var SKIN_ACCESSOR_TYPES = ["MAT4"];
897
+ function identityMat4() {
898
+ const m = new Float32Array(16);
899
+ m[0] = 1;
900
+ m[5] = 1;
901
+ m[10] = 1;
902
+ m[15] = 1;
903
+ return m;
904
+ }
905
+ function resolveJointPath(nodeIndex, nodes, parentOf, skinIndex, jointPathIndex) {
906
+ const path = resolveNamedNodePath(nodes, parentOf, nodeIndex);
907
+ if (!path.ok) {
908
+ return err(
909
+ gltfErr("gltf-skin-joint-name-missing", {
910
+ reason: path.reason,
911
+ skinIndex,
912
+ jointPathIndex,
913
+ nodeIndex: path.nodeIndex
914
+ })
915
+ );
916
+ }
917
+ return ok(path.value);
918
+ }
919
+ function parseSkin(skinsJson, nodesJson, accessors, bufferViews, buffers) {
920
+ if (skinsJson === void 0 || skinsJson.length === 0) {
921
+ return ok([]);
922
+ }
923
+ const records = [];
924
+ const parentOf = buildNodeParentMap(nodesJson);
925
+ for (let skinIdx = 0; skinIdx < skinsJson.length; skinIdx++) {
926
+ const skin = skinsJson[skinIdx];
927
+ if (skin === void 0) continue;
928
+ const joints = skin.joints;
929
+ if (joints.length > MAX_JOINTS) {
930
+ return err(
931
+ gltfErr("gltf-skin-joint-count-exceeded", {
932
+ skinIndex: skinIdx,
933
+ jointCount: joints.length,
934
+ maxJoints: MAX_JOINTS
935
+ })
936
+ );
937
+ }
938
+ let ibm;
939
+ if (skin.inverseBindMatrices !== void 0) {
940
+ const ibmAccIdx = skin.inverseBindMatrices;
941
+ const ibmAcc = accessors[ibmAccIdx];
942
+ if (ibmAcc === void 0) {
943
+ return err(
944
+ gltfErr("gltf-buffer-out-of-bounds", {
945
+ accessor: ibmAccIdx,
946
+ byteOffset: 0,
947
+ byteLength: 0,
948
+ bufferIndex: 0
949
+ })
950
+ );
951
+ }
952
+ const ibmResult = decodeF32Accessor(
953
+ ibmAccIdx,
954
+ ibmAcc,
955
+ SKIN_ACCESSOR_TYPES,
956
+ bufferViews,
957
+ buffers
958
+ );
959
+ if (!ibmResult.ok) return err(ibmResult.error);
960
+ ibm = ibmResult.value;
961
+ } else {
962
+ ibm = new Float32Array(joints.length * 16);
963
+ for (let j = 0; j < joints.length; j++) {
964
+ ibm.set(identityMat4(), j * 16);
965
+ }
966
+ }
967
+ const jointPaths = [];
968
+ for (let j = 0; j < joints.length; j++) {
969
+ const jointNodeIndex = joints[j];
970
+ if (jointNodeIndex === void 0) continue;
971
+ const pathResult = resolveJointPath(jointNodeIndex, nodesJson, parentOf, skinIdx, j);
972
+ if (!pathResult.ok) return err(pathResult.error);
973
+ jointPaths.push(pathResult.value.join("/"));
974
+ }
975
+ records.push({
976
+ jointCount: joints.length,
977
+ inverseBindMatrices: ibm,
978
+ jointPaths
979
+ });
980
+ }
981
+ return ok(records);
982
+ }
983
+
984
+ // src/source-key.ts
985
+ function sourceKeyForGltfOutput(item) {
986
+ const kind = item.kind.trim();
987
+ if (kind.length === 0) return void 0;
988
+ const name = item.name?.trim();
989
+ return name === void 0 || name.length === 0 ? kind : `${kind}:${name}`;
990
+ }
991
+ function deriveGltfSourceKeys(items) {
992
+ const keys = items.map((item) => sourceKeyForGltfOutput(item));
993
+ const missing = items.map((item, index) => keys[index] === void 0 ? item.sourceIndex : void 0).filter((index) => index !== void 0);
994
+ if (missing.length > 0) {
995
+ return {
996
+ ok: false,
997
+ error: {
998
+ code: "missing-source-key",
999
+ expected: "every glTF output needs a stable semantic kind or name",
1000
+ hint: "publish a semantic kind/name key; do not use sourceIndex",
1001
+ detail: { sourceIndices: missing }
1002
+ }
1003
+ };
1004
+ }
1005
+ const seen = /* @__PURE__ */ new Map();
1006
+ for (let index = 0; index < keys.length; index++) {
1007
+ const key = keys[index];
1008
+ if (key === void 0) continue;
1009
+ const current = items[index];
1010
+ if (current === void 0) continue;
1011
+ const prior = seen.get(key);
1012
+ if (prior !== void 0) {
1013
+ const entry = (item) => {
1014
+ const name = item.name?.trim();
1015
+ return {
1016
+ kind: item.kind.trim(),
1017
+ name: name === void 0 || name.length === 0 ? null : name,
1018
+ sourceIndex: item.sourceIndex
1019
+ };
1020
+ };
1021
+ const ambiguous = key === current.kind.trim();
1022
+ return {
1023
+ ok: false,
1024
+ error: {
1025
+ code: ambiguous ? "ambiguous-source-key" : "duplicate-source-key",
1026
+ expected: "sourceKey values must be unique within one glTF package",
1027
+ hint: ambiguous ? "name each otherwise anonymous output; do not use sourceIndex" : "rename duplicate outputs before publishing topology facts",
1028
+ detail: {
1029
+ key,
1030
+ sourceIndices: [prior.sourceIndex, current.sourceIndex],
1031
+ entries: [entry(prior), entry(current)]
1032
+ }
1033
+ }
1034
+ };
1035
+ }
1036
+ seen.set(key, current);
1037
+ }
1038
+ return { ok: true, keys, conflicts: [] };
1039
+ }
1040
+
1041
+ // src/reimport-reuse-meta.ts
1042
+ function reimportReuseMeta(items, existingMeta) {
1043
+ const sourceKeys = deriveGltfSourceKeys(items);
1044
+ if (!sourceKeys.ok) return err(sourceKeys.error);
1045
+ const stage1Index = /* @__PURE__ */ new Map();
1046
+ const stage2Index = /* @__PURE__ */ new Map();
1047
+ if (existingMeta !== void 0) {
1048
+ for (const entry of existingMeta.subAssets) {
1049
+ if (entry.sourceKey !== void 0) stage1Index.set(entry.sourceKey, entry);
1050
+ stage2Index.set(`${entry.kind} ${entry.sourceIndex}`, entry);
1051
+ }
1052
+ }
1053
+ const subAssets = [];
1054
+ for (const item of items) {
1055
+ const sourceKey = sourceKeyForGltfOutput(item);
1056
+ const indexKey = `${item.kind} ${item.sourceIndex}`;
1057
+ let reused;
1058
+ if (sourceKey !== void 0) reused = stage1Index.get(sourceKey);
1059
+ if (reused === void 0) {
1060
+ reused = stage2Index.get(indexKey);
1061
+ }
1062
+ if (reused !== void 0) {
1063
+ subAssets.push({
1064
+ guid: reused.guid,
1065
+ sourceIndex: item.sourceIndex,
1066
+ kind: item.kind,
1067
+ ...sourceKey === void 0 ? {} : { sourceKey },
1068
+ ...item.name === void 0 ? {} : { name: item.name }
1069
+ });
1070
+ } else {
1071
+ const fresh = AssetGuid.random();
1072
+ subAssets.push({
1073
+ guid: AssetGuid.format(fresh),
1074
+ sourceIndex: item.sourceIndex,
1075
+ kind: item.kind,
1076
+ ...sourceKey === void 0 ? {} : { sourceKey },
1077
+ ...item.name === void 0 ? {} : { name: item.name }
1078
+ });
1079
+ }
1080
+ }
1081
+ return ok({ subAssets });
1082
+ }
1083
+ var IDENTITY = {
1084
+ translation: [0, 0, 0],
1085
+ rotation: [0, 0, 0, 1],
1086
+ scale: [1, 1, 1]
1087
+ };
1088
+ function tuple3(arr, fallback) {
1089
+ if (arr === void 0 || arr.length < 3) return fallback;
1090
+ return [arr[0] ?? fallback[0], arr[1] ?? fallback[1], arr[2] ?? fallback[2]];
1091
+ }
1092
+ function tuple4(arr, fallback) {
1093
+ if (arr === void 0 || arr.length < 4) return fallback;
1094
+ return [
1095
+ arr[0] ?? fallback[0],
1096
+ arr[1] ?? fallback[1],
1097
+ arr[2] ?? fallback[2],
1098
+ arr[3] ?? fallback[3]
1099
+ ];
1100
+ }
1101
+ function decomposeNodeTransform(node, nodeIndex, diagnostics) {
1102
+ const hasMatrix = node.matrix !== void 0 && node.matrix.length === 16;
1103
+ const hasTrs = node.translation !== void 0 || node.rotation !== void 0 || node.scale !== void 0;
1104
+ if (hasMatrix) {
1105
+ if (hasTrs) {
1106
+ console.error(`[warn] node[${nodeIndex}] has both matrix and TRS, matrix takes precedence`);
1107
+ diagnostics.matrixTrsCoexistNodes.push(nodeIndex);
1108
+ }
1109
+ const out_t = vec3.create();
1110
+ const out_r = quat.create();
1111
+ const out_s = vec3.create(1, 1, 1);
1112
+ const m = node.matrix;
1113
+ mat4.decompose(out_t, out_r, out_s, m);
1114
+ return {
1115
+ translation: [out_t[0] ?? 0, out_t[1] ?? 0, out_t[2] ?? 0],
1116
+ rotation: [out_r[0] ?? 0, out_r[1] ?? 0, out_r[2] ?? 0, out_r[3] ?? 1],
1117
+ scale: [out_s[0] ?? 1, out_s[1] ?? 1, out_s[2] ?? 1]
1118
+ };
1119
+ }
1120
+ if (!hasTrs) {
1121
+ return IDENTITY;
1122
+ }
1123
+ return {
1124
+ translation: tuple3(node.translation, IDENTITY.translation),
1125
+ rotation: tuple4(node.rotation, IDENTITY.rotation),
1126
+ scale: tuple3(node.scale, IDENTITY.scale)
1127
+ };
1128
+ }
1129
+
1130
+ // src/parse-gltf.ts
1131
+ function decodeNodeInstancing(nodeIndex, attributes, accessors, bufferViews, buffers) {
1132
+ const tIdx = attributes.TRANSLATION;
1133
+ const rIdx = attributes.ROTATION;
1134
+ const sIdx = attributes.SCALE;
1135
+ let count;
1136
+ const setOrCheck = (label, n2) => {
1137
+ if (count === void 0) {
1138
+ count = n2;
1139
+ return null;
1140
+ }
1141
+ if (n2 !== count) {
1142
+ return gltfErr("gltf-instancing-count-mismatch", {
1143
+ nodeIndex,
1144
+ accessor: label,
1145
+ expectedCount: count,
1146
+ actualCount: n2
1147
+ });
1148
+ }
1149
+ return null;
1150
+ };
1151
+ let tValues;
1152
+ if (tIdx !== void 0) {
1153
+ const acc = accessors[tIdx];
1154
+ if (acc === void 0) return err(unknownAccessor(tIdx));
1155
+ const e = setOrCheck("TRANSLATION", acc.count);
1156
+ if (e !== null) return err(e);
1157
+ const decoded = decodeAttributeAccessor(tIdx, acc, bufferViews, buffers);
1158
+ if (!decoded.ok) return err(decoded.error);
1159
+ tValues = decoded.value;
1160
+ }
1161
+ let rValues;
1162
+ if (rIdx !== void 0) {
1163
+ const acc = accessors[rIdx];
1164
+ if (acc === void 0) return err(unknownAccessor(rIdx));
1165
+ const e = setOrCheck("ROTATION", acc.count);
1166
+ if (e !== null) return err(e);
1167
+ const decoded = decodeAttributeAccessor(rIdx, acc, bufferViews, buffers);
1168
+ if (!decoded.ok) return err(decoded.error);
1169
+ rValues = decoded.value;
1170
+ }
1171
+ let sValues;
1172
+ if (sIdx !== void 0) {
1173
+ const acc = accessors[sIdx];
1174
+ if (acc === void 0) return err(unknownAccessor(sIdx));
1175
+ const e = setOrCheck("SCALE", acc.count);
1176
+ if (e !== null) return err(e);
1177
+ const decoded = decodeAttributeAccessor(sIdx, acc, bufferViews, buffers);
1178
+ if (!decoded.ok) return err(decoded.error);
1179
+ sValues = decoded.value;
1180
+ }
1181
+ const n = count ?? 0;
1182
+ const transforms = new Float32Array(n * 16);
1183
+ const tmp = mat4.create();
1184
+ const tv = vec3.create();
1185
+ const rv = quat.create();
1186
+ rv[3] = 1;
1187
+ const sv = vec3.create(1, 1, 1);
1188
+ for (let i = 0; i < n; i++) {
1189
+ if (tValues !== void 0) {
1190
+ tv[0] = tValues[i * 3] ?? 0;
1191
+ tv[1] = tValues[i * 3 + 1] ?? 0;
1192
+ tv[2] = tValues[i * 3 + 2] ?? 0;
1193
+ } else {
1194
+ tv[0] = 0;
1195
+ tv[1] = 0;
1196
+ tv[2] = 0;
1197
+ }
1198
+ if (rValues !== void 0) {
1199
+ rv[0] = rValues[i * 4] ?? 0;
1200
+ rv[1] = rValues[i * 4 + 1] ?? 0;
1201
+ rv[2] = rValues[i * 4 + 2] ?? 0;
1202
+ rv[3] = rValues[i * 4 + 3] ?? 1;
1203
+ } else {
1204
+ rv[0] = 0;
1205
+ rv[1] = 0;
1206
+ rv[2] = 0;
1207
+ rv[3] = 1;
1208
+ }
1209
+ if (sValues !== void 0) {
1210
+ sv[0] = sValues[i * 3] ?? 1;
1211
+ sv[1] = sValues[i * 3 + 1] ?? 1;
1212
+ sv[2] = sValues[i * 3 + 2] ?? 1;
1213
+ } else {
1214
+ sv[0] = 1;
1215
+ sv[1] = 1;
1216
+ sv[2] = 1;
1217
+ }
1218
+ mat4.compose(tmp, tv, rv, sv);
1219
+ for (let k = 0; k < 16; k++) {
1220
+ transforms[i * 16 + k] = tmp[k] ?? 0;
1221
+ }
1222
+ }
1223
+ return ok({ count: n, transforms });
1224
+ }
1225
+ function unknownAccessor(accessorIndex) {
1226
+ return gltfErr("gltf-accessor-type-mismatch", {
1227
+ accessorIndex,
1228
+ reason: "unknownComponentType"
1229
+ });
1230
+ }
1231
+ function decodeAttributeAccessor(accessorIndex, accessor, bufferViews, buffers) {
1232
+ const view = bufferViews[accessor.bufferView ?? -1];
1233
+ if (view === void 0) return err(unknownAccessor(accessorIndex));
1234
+ const buf = buffers[view.buffer];
1235
+ if (buf === void 0) return err(unknownAccessor(accessorIndex));
1236
+ const decoded = decodeAccessor({
1237
+ accessorIndex,
1238
+ accessor,
1239
+ bufferView: view,
1240
+ buffer: buf,
1241
+ role: "attribute"
1242
+ });
1243
+ if (!decoded.ok) return err(decoded.error);
1244
+ if (decoded.value.kind !== "f32") return err(unknownAccessor(accessorIndex));
1245
+ return ok(decoded.value.data);
1246
+ }
1247
+ function tuple2(values) {
1248
+ if (values === void 0 || values.length < 2) return void 0;
1249
+ return [values[0] ?? 0, values[1] ?? 0];
1250
+ }
1251
+ function parseTextureInfo(info, textures) {
1252
+ const transformJson = info.extensions?.KHR_texture_transform;
1253
+ const offset = tuple2(transformJson?.offset);
1254
+ const scale = tuple2(transformJson?.scale);
1255
+ const transform = offset === void 0 && transformJson?.rotation === void 0 && scale === void 0 ? void 0 : {
1256
+ ...offset === void 0 ? {} : { offset },
1257
+ ...transformJson?.rotation === void 0 ? {} : { rotation: transformJson.rotation },
1258
+ ...scale === void 0 ? {} : { scale }
1259
+ };
1260
+ const sampler = textures[info.index]?.sampler;
1261
+ return {
1262
+ texture: info.index,
1263
+ ...sampler === void 0 ? {} : { sampler },
1264
+ ...info.texCoord === void 0 && transformJson?.texCoord === void 0 ? {} : { texCoord: transformJson?.texCoord ?? info.texCoord },
1265
+ ...transform === void 0 ? {} : { transform }
1266
+ };
1267
+ }
1268
+ async function resolveBuffer(buf, externalLoader, binChunk) {
1269
+ if (buf.uri === void 0) {
1270
+ if (binChunk === void 0) {
1271
+ throw new Error("parseGltf: buffer 0 has no uri and no GLB BIN chunk available");
1272
+ }
1273
+ return binChunk;
1274
+ }
1275
+ const dataPayload = dataUriBase64Payload(buf.uri);
1276
+ if (dataPayload !== void 0) {
1277
+ return decodeBase64(dataPayload);
1278
+ }
1279
+ const arrayBuffer = await externalLoader(buf.uri);
1280
+ return new Uint8Array(arrayBuffer);
1281
+ }
1282
+ function invalidBufferDataUriError(filePath, bufferIndex, cause) {
1283
+ return new ImportError({
1284
+ code: "source-validation-failed",
1285
+ expected: `glTF buffer ${bufferIndex} data URI to contain a valid base64 payload`,
1286
+ hint: IMPORT_ERROR_HINTS["source-validation-failed"],
1287
+ detail: {
1288
+ diagnostics: [
1289
+ {
1290
+ code: "gltf-buffer-data-uri-invalid",
1291
+ severity: "error",
1292
+ sourcePath: filePath,
1293
+ sourceRange: { start: 0, end: 0, line: 1, column: 1 },
1294
+ rule: "gltf-buffer-data-uri-base64",
1295
+ expected: "a valid base64 payload after ;base64,",
1296
+ actual: `buffer ${bufferIndex}: ${cause.message}`,
1297
+ hint: "repair the buffer data URI or provide a valid external .bin sibling"
1298
+ }
1299
+ ]
1300
+ }
1301
+ });
1302
+ }
1303
+ function parsePunctualLights(json, filePath) {
1304
+ const source = json.extensions?.KHR_lights_punctual?.lights ?? [];
1305
+ const lights = [];
1306
+ for (const light of source) {
1307
+ const type = light?.type;
1308
+ if (type !== "directional" && type !== "point" && type !== "spot") {
1309
+ return err(gltfErr("gltf-malformed-header", { filePath, byteOffset: 0 }));
1310
+ }
1311
+ const color = light.color ?? [1, 1, 1];
1312
+ if (color.length < 3 || color.slice(0, 3).some(
1313
+ (value) => typeof value !== "number" || !Number.isFinite(value) || value < 0 || value > 1
1314
+ )) {
1315
+ return err(gltfErr("gltf-malformed-header", { filePath, byteOffset: 0 }));
1316
+ }
1317
+ const intensity = light.intensity ?? 1;
1318
+ if (typeof intensity !== "number" || !Number.isFinite(intensity) || intensity < 0) {
1319
+ return err(gltfErr("gltf-malformed-header", { filePath, byteOffset: 0 }));
1320
+ }
1321
+ const range = light.range;
1322
+ if (range !== void 0 && (typeof range !== "number" || !Number.isFinite(range) || range < 0)) {
1323
+ return err(gltfErr("gltf-malformed-header", { filePath, byteOffset: 0 }));
1324
+ }
1325
+ let spot;
1326
+ if (type === "spot") {
1327
+ const innerConeAngle = light.spot?.innerConeAngle ?? 0;
1328
+ const outerConeAngle = light.spot?.outerConeAngle ?? Math.PI / 4;
1329
+ if (!Number.isFinite(innerConeAngle) || !Number.isFinite(outerConeAngle) || innerConeAngle < 0 || innerConeAngle >= outerConeAngle || outerConeAngle > Math.PI / 2) {
1330
+ return err(gltfErr("gltf-malformed-header", { filePath, byteOffset: 0 }));
1331
+ }
1332
+ spot = { innerConeAngle, outerConeAngle };
1333
+ }
1334
+ lights.push({
1335
+ type,
1336
+ color: [color[0] ?? 1, color[1] ?? 1, color[2] ?? 1],
1337
+ intensity,
1338
+ ...range === void 0 || range === 0 ? {} : { range },
1339
+ ...spot === void 0 ? {} : { spot }
1340
+ });
1341
+ }
1342
+ return ok(lights);
1343
+ }
1344
+ async function parseGltfWithBin(json, ctx) {
1345
+ const headerResult = parseGltfHeader(json, ctx.filePath);
1346
+ if (!headerResult.ok) return err(headerResult.error);
1347
+ const extResult = checkExtensions(json);
1348
+ if (!extResult.ok) return err(extResult.error);
1349
+ const unsupportedExtensions = extResult.value.unsupportedUsed;
1350
+ const lightsResult = parsePunctualLights(json, ctx.filePath);
1351
+ if (!lightsResult.ok) return err(lightsResult.error);
1352
+ const meshesJson = json.meshes ?? [];
1353
+ const buffersJson = json.buffers ?? [];
1354
+ const buffers = [];
1355
+ for (let i = 0; i < buffersJson.length; i++) {
1356
+ const bufJson = buffersJson[i];
1357
+ if (bufJson === void 0) continue;
1358
+ try {
1359
+ const bytes = await resolveBuffer(bufJson, ctx.externalLoader, ctx.binChunk);
1360
+ buffers.push(bytes);
1361
+ } catch (e) {
1362
+ if (e instanceof Base64DecodeError) {
1363
+ return err(invalidBufferDataUriError(ctx.filePath, i, e));
1364
+ }
1365
+ return err(
1366
+ gltfErr("gltf-malformed-header", {
1367
+ filePath: ctx.filePath,
1368
+ byteOffset: 0
1369
+ })
1370
+ );
1371
+ }
1372
+ }
1373
+ const accessors = json.accessors ?? [];
1374
+ const rawBufferViews = json.bufferViews ?? [];
1375
+ const projected = await projectMeshoptBufferViews(
1376
+ rawBufferViews,
1377
+ buffers,
1378
+ json.extensionsRequired ?? [],
1379
+ ctx.meshopt
1380
+ );
1381
+ if (!projected.ok) return err(projected.error);
1382
+ const bufferViews = projected.value.bufferViews;
1383
+ buffers.splice(0, buffers.length, ...projected.value.buffers);
1384
+ const meshes = [];
1385
+ const meshPrimitiveCount = /* @__PURE__ */ new Map();
1386
+ for (let meshIndex = 0; meshIndex < meshesJson.length; meshIndex++) {
1387
+ const meshJson = meshesJson[meshIndex];
1388
+ if (meshJson === void 0) continue;
1389
+ meshPrimitiveCount.set(meshIndex, meshJson.primitives.length);
1390
+ for (const prim of meshJson.primitives) {
1391
+ if (prim === void 0) continue;
1392
+ const positionAccessorIndex = prim.attributes?.POSITION;
1393
+ if (positionAccessorIndex === void 0) {
1394
+ return err(
1395
+ gltfErr("gltf-accessor-type-mismatch", {
1396
+ accessorIndex: -1,
1397
+ reason: "unknownComponentType"
1398
+ })
1399
+ );
1400
+ }
1401
+ const positionAccessor = accessors[positionAccessorIndex];
1402
+ const positionBufferView = bufferViews[positionAccessor?.bufferView ?? -1];
1403
+ if (positionAccessor === void 0 || positionBufferView === void 0) {
1404
+ return err(
1405
+ gltfErr("gltf-buffer-out-of-bounds", {
1406
+ accessor: positionAccessorIndex,
1407
+ byteOffset: 0,
1408
+ byteLength: 0,
1409
+ bufferIndex: positionBufferView?.buffer ?? 0
1410
+ })
1411
+ );
1412
+ }
1413
+ const positionBuffer = buffers[positionBufferView.buffer];
1414
+ if (positionBuffer === void 0) {
1415
+ return err(
1416
+ gltfErr("gltf-buffer-out-of-bounds", {
1417
+ accessor: positionAccessorIndex,
1418
+ byteOffset: positionBufferView.byteOffset ?? 0,
1419
+ byteLength: positionBufferView.byteLength,
1420
+ bufferIndex: positionBufferView.buffer
1421
+ })
1422
+ );
1423
+ }
1424
+ const positionDecoded = decodeAccessor({
1425
+ accessorIndex: positionAccessorIndex,
1426
+ accessor: positionAccessor,
1427
+ bufferView: positionBufferView,
1428
+ buffer: positionBuffer,
1429
+ role: "attribute"
1430
+ });
1431
+ if (!positionDecoded.ok) return err(positionDecoded.error);
1432
+ if (positionDecoded.value.kind !== "f32") {
1433
+ return err(
1434
+ gltfErr("gltf-accessor-type-mismatch", {
1435
+ accessorIndex: positionAccessorIndex,
1436
+ reason: "unknownComponentType"
1437
+ })
1438
+ );
1439
+ }
1440
+ const positionsDecoded = positionDecoded.value.data;
1441
+ const positions = new Float32Array(positionsDecoded.length);
1442
+ positions.set(positionsDecoded);
1443
+ const attrs = prim.attributes ?? {};
1444
+ let colors0;
1445
+ const colorIdx = attrs.COLOR_0;
1446
+ if (colorIdx !== void 0) {
1447
+ const colorAccessor = accessors[colorIdx];
1448
+ const colorBufferView = bufferViews[colorAccessor?.bufferView ?? -1];
1449
+ if (colorAccessor === void 0 || colorBufferView === void 0) {
1450
+ return err(
1451
+ gltfErr("gltf-color-accessor-malformed", {
1452
+ semantic: "COLOR_0",
1453
+ accessorIndex: colorIdx,
1454
+ reason: "reference"
1455
+ })
1456
+ );
1457
+ }
1458
+ const colorBuffer = buffers[colorBufferView.buffer];
1459
+ if (colorBuffer === void 0) {
1460
+ return err(
1461
+ gltfErr("gltf-color-accessor-malformed", {
1462
+ semantic: "COLOR_0",
1463
+ accessorIndex: colorIdx,
1464
+ reason: "reference"
1465
+ })
1466
+ );
1467
+ }
1468
+ const decoded = decodeColorAccessor({
1469
+ accessorIndex: colorIdx,
1470
+ accessor: colorAccessor,
1471
+ bufferView: colorBufferView,
1472
+ buffer: colorBuffer,
1473
+ bufferIndex: colorBufferView.buffer,
1474
+ semantic: "COLOR_0"
1475
+ });
1476
+ if (!decoded.ok) return err(decoded.error);
1477
+ if (decoded.value.length !== positions.length / 3 * 4) {
1478
+ return err(
1479
+ gltfErr("gltf-color-accessor-malformed", {
1480
+ semantic: "COLOR_0",
1481
+ accessorIndex: colorIdx,
1482
+ reason: "count"
1483
+ })
1484
+ );
1485
+ }
1486
+ colors0 = decoded.value;
1487
+ }
1488
+ let normals;
1489
+ const normalIdx = attrs.NORMAL;
1490
+ if (normalIdx !== void 0) {
1491
+ const acc = accessors[normalIdx];
1492
+ if (acc !== void 0) {
1493
+ const decoded = decodeAttributeAccessor(normalIdx, acc, bufferViews, buffers);
1494
+ if (decoded.ok) {
1495
+ const src = decoded.value;
1496
+ const owned = new Float32Array(src.length);
1497
+ owned.set(src);
1498
+ normals = owned;
1499
+ }
1500
+ }
1501
+ }
1502
+ let texcoord0;
1503
+ const texIdx = attrs.TEXCOORD_0;
1504
+ if (texIdx !== void 0) {
1505
+ const acc = accessors[texIdx];
1506
+ if (acc !== void 0) {
1507
+ const decoded = decodeAttributeAccessor(texIdx, acc, bufferViews, buffers);
1508
+ if (decoded.ok) {
1509
+ const src = decoded.value;
1510
+ const owned = new Float32Array(src.length);
1511
+ owned.set(src);
1512
+ texcoord0 = owned;
1513
+ }
1514
+ }
1515
+ }
1516
+ let texcoord1;
1517
+ let texcoord2;
1518
+ let texcoord3;
1519
+ let texcoord4;
1520
+ let texcoord5;
1521
+ let texcoord6;
1522
+ let texcoord7;
1523
+ for (let k = 1; k <= 7; k++) {
1524
+ const tcIdx = attrs[`TEXCOORD_${k}`];
1525
+ if (tcIdx === void 0) continue;
1526
+ const acc = accessors[tcIdx];
1527
+ if (acc !== void 0) {
1528
+ const decoded = decodeAttributeAccessor(tcIdx, acc, bufferViews, buffers);
1529
+ if (decoded.ok) {
1530
+ const src = decoded.value;
1531
+ const owned = new Float32Array(src.length);
1532
+ owned.set(src);
1533
+ if (k === 1) texcoord1 = owned;
1534
+ else if (k === 2) texcoord2 = owned;
1535
+ else if (k === 3) texcoord3 = owned;
1536
+ else if (k === 4) texcoord4 = owned;
1537
+ else if (k === 5) texcoord5 = owned;
1538
+ else if (k === 6) texcoord6 = owned;
1539
+ else texcoord7 = owned;
1540
+ }
1541
+ }
1542
+ }
1543
+ let tangents;
1544
+ const tanIdx = attrs.TANGENT;
1545
+ if (tanIdx !== void 0) {
1546
+ const acc = accessors[tanIdx];
1547
+ if (acc !== void 0) {
1548
+ const decoded = decodeAttributeAccessor(tanIdx, acc, bufferViews, buffers);
1549
+ if (decoded.ok) {
1550
+ const src = decoded.value;
1551
+ const owned = new Float32Array(src.length);
1552
+ owned.set(src);
1553
+ tangents = owned;
1554
+ }
1555
+ }
1556
+ }
1557
+ const rawTargets = prim.targets ?? [];
1558
+ for (const target of rawTargets) {
1559
+ const colorTarget = target.COLOR_0;
1560
+ if (colorTarget !== void 0) {
1561
+ return err(
1562
+ gltfErr("gltf-color-accessor-unsupported", {
1563
+ semantic: "COLOR_0",
1564
+ accessorIndex: colorTarget,
1565
+ reason: "morph"
1566
+ })
1567
+ );
1568
+ }
1569
+ }
1570
+ const morphAttributeCount = rawTargets.reduce(
1571
+ (count, target) => count + (target.POSITION === void 0 ? 0 : 1) + (target.NORMAL === void 0 ? 0 : 1) + (target.TANGENT === void 0 ? 0 : 1),
1572
+ 0
1573
+ );
1574
+ const morphError = (reason) => err(
1575
+ gltfErr("gltf-morph-invalid", {
1576
+ meshIndex,
1577
+ primitiveIndex: meshJson.primitives.indexOf(prim),
1578
+ reason,
1579
+ targetCount: rawTargets.length,
1580
+ attributeCount: morphAttributeCount,
1581
+ vertexCount: positions.length / 3
1582
+ })
1583
+ );
1584
+ if (rawTargets.length > 8) return morphError("target-count-exceeded");
1585
+ if (morphAttributeCount > 8) return morphError("attribute-count-exceeded");
1586
+ const morphTargets = [];
1587
+ for (const target of rawTargets) {
1588
+ const output = {};
1589
+ for (const [key, accessorIndex] of Object.entries(target)) {
1590
+ if (key !== "POSITION" && key !== "NORMAL" && key !== "TANGENT") continue;
1591
+ const targetAccessor = accessors[accessorIndex];
1592
+ if (targetAccessor === void 0) return morphError("attribute-length-mismatch");
1593
+ const decoded = decodeAttributeAccessor(
1594
+ accessorIndex,
1595
+ targetAccessor,
1596
+ bufferViews,
1597
+ buffers
1598
+ );
1599
+ if (!decoded.ok) {
1600
+ return morphError(
1601
+ decoded.error.code === "gltf-accessor-type-mismatch" ? "sparse-or-unsupported-accessor" : "attribute-length-mismatch"
1602
+ );
1603
+ }
1604
+ const expected = positions.length / 3 * (key === "TANGENT" ? 4 : 3);
1605
+ if (decoded.value.length !== expected) return morphError("attribute-length-mismatch");
1606
+ const owned = new Float32Array(decoded.value.length);
1607
+ owned.set(decoded.value);
1608
+ if (key === "POSITION") output.position = owned;
1609
+ else if (key === "NORMAL") output.normal = owned;
1610
+ else output.tangent = owned;
1611
+ }
1612
+ morphTargets.push(output);
1613
+ }
1614
+ const meshWeights = meshJson.weights;
1615
+ if (meshWeights !== void 0 && meshWeights.length !== rawTargets.length) {
1616
+ return morphError("weights-length-mismatch");
1617
+ }
1618
+ const jointsIdx = attrs.JOINTS_0;
1619
+ const weightsIdx = attrs.WEIGHTS_0;
1620
+ const hasJoints = jointsIdx !== void 0;
1621
+ const hasWeights = weightsIdx !== void 0;
1622
+ if (hasJoints !== hasWeights) {
1623
+ const primitiveIndex = meshJson.primitives.indexOf(prim);
1624
+ return err(
1625
+ gltfErr("gltf-skin-attr-asymmetric", {
1626
+ meshIndex,
1627
+ primitiveIndex,
1628
+ hasJoints,
1629
+ hasWeights
1630
+ })
1631
+ );
1632
+ }
1633
+ let joints0;
1634
+ let weights0;
1635
+ if (hasJoints && hasWeights) {
1636
+ const jointsAccessor = accessors[jointsIdx];
1637
+ const jointsBufferView = bufferViews[jointsAccessor?.bufferView ?? -1];
1638
+ if (jointsAccessor === void 0 || jointsBufferView === void 0) {
1639
+ return err(unknownAccessor(jointsIdx));
1640
+ }
1641
+ const jointsBuffer = buffers[jointsBufferView.buffer];
1642
+ if (jointsBuffer === void 0) return err(unknownAccessor(jointsIdx));
1643
+ const jointsDecoded = decodeAccessor({
1644
+ accessorIndex: jointsIdx,
1645
+ accessor: jointsAccessor,
1646
+ bufferView: jointsBufferView,
1647
+ buffer: jointsBuffer,
1648
+ role: "joints"
1649
+ });
1650
+ if (!jointsDecoded.ok) return err(jointsDecoded.error);
1651
+ if (jointsDecoded.value.kind !== "u16") {
1652
+ return err(
1653
+ gltfErr("gltf-accessor-type-mismatch", {
1654
+ accessorIndex: jointsIdx,
1655
+ reason: "unknownComponentType"
1656
+ })
1657
+ );
1658
+ }
1659
+ const src = jointsDecoded.value.data;
1660
+ const owned = new Uint16Array(src.length);
1661
+ owned.set(src);
1662
+ joints0 = owned;
1663
+ const weightsAccessor = accessors[weightsIdx];
1664
+ const weightsBufferView = bufferViews[weightsAccessor?.bufferView ?? -1];
1665
+ if (weightsAccessor === void 0 || weightsBufferView === void 0) {
1666
+ return err(unknownAccessor(weightsIdx));
1667
+ }
1668
+ const weightsBuffer = buffers[weightsBufferView.buffer];
1669
+ if (weightsBuffer === void 0) return err(unknownAccessor(weightsIdx));
1670
+ const weightsDecoded = decodeAccessor({
1671
+ accessorIndex: weightsIdx,
1672
+ accessor: weightsAccessor,
1673
+ bufferView: weightsBufferView,
1674
+ buffer: weightsBuffer,
1675
+ role: "attribute"
1676
+ });
1677
+ if (!weightsDecoded.ok) return err(weightsDecoded.error);
1678
+ if (weightsDecoded.value.kind !== "f32") {
1679
+ return err(
1680
+ gltfErr("gltf-accessor-type-mismatch", {
1681
+ accessorIndex: weightsIdx,
1682
+ reason: "unknownComponentType"
1683
+ })
1684
+ );
1685
+ }
1686
+ const wsrc = weightsDecoded.value.data;
1687
+ const wowned = new Float32Array(wsrc.length);
1688
+ wowned.set(wsrc);
1689
+ weights0 = wowned;
1690
+ }
1691
+ let indices;
1692
+ if (prim.indices !== void 0) {
1693
+ const indexAccessor = accessors[prim.indices];
1694
+ const indexBufferView = bufferViews[indexAccessor?.bufferView ?? -1];
1695
+ if (indexAccessor === void 0 || indexBufferView === void 0) {
1696
+ return err(
1697
+ gltfErr("gltf-buffer-out-of-bounds", {
1698
+ accessor: prim.indices,
1699
+ byteOffset: 0,
1700
+ byteLength: 0,
1701
+ bufferIndex: indexBufferView?.buffer ?? 0
1702
+ })
1703
+ );
1704
+ }
1705
+ const indexBuffer = buffers[indexBufferView.buffer];
1706
+ if (indexBuffer === void 0) {
1707
+ return err(
1708
+ gltfErr("gltf-buffer-out-of-bounds", {
1709
+ accessor: prim.indices,
1710
+ byteOffset: indexBufferView.byteOffset ?? 0,
1711
+ byteLength: indexBufferView.byteLength,
1712
+ bufferIndex: indexBufferView.buffer
1713
+ })
1714
+ );
1715
+ }
1716
+ const indexDecoded = decodeAccessor({
1717
+ accessorIndex: prim.indices,
1718
+ accessor: indexAccessor,
1719
+ bufferView: indexBufferView,
1720
+ buffer: indexBuffer,
1721
+ role: "indices"
1722
+ });
1723
+ if (!indexDecoded.ok) return err(indexDecoded.error);
1724
+ if (indexDecoded.value.kind === "u16") {
1725
+ const src = indexDecoded.value.data;
1726
+ const owned = new Uint16Array(src.length);
1727
+ owned.set(src);
1728
+ indices = owned;
1729
+ } else if (indexDecoded.value.kind === "u32") {
1730
+ const src = indexDecoded.value.data;
1731
+ const owned = new Uint32Array(src.length);
1732
+ owned.set(src);
1733
+ indices = owned;
1734
+ } else {
1735
+ return err(
1736
+ gltfErr("gltf-accessor-type-mismatch", {
1737
+ accessorIndex: prim.indices,
1738
+ reason: "unknownComponentType"
1739
+ })
1740
+ );
1741
+ }
1742
+ }
1743
+ const meshIr = {
1744
+ ...meshJson.name === void 0 ? {} : { name: meshJson.name },
1745
+ positions,
1746
+ ...normals === void 0 ? {} : { normals },
1747
+ ...texcoord0 === void 0 ? {} : { texcoord0 },
1748
+ ...texcoord1 === void 0 ? {} : { texcoord1 },
1749
+ ...texcoord2 === void 0 ? {} : { texcoord2 },
1750
+ ...texcoord3 === void 0 ? {} : { texcoord3 },
1751
+ ...texcoord4 === void 0 ? {} : { texcoord4 },
1752
+ ...texcoord5 === void 0 ? {} : { texcoord5 },
1753
+ ...texcoord6 === void 0 ? {} : { texcoord6 },
1754
+ ...texcoord7 === void 0 ? {} : { texcoord7 },
1755
+ ...tangents === void 0 ? {} : { tangents },
1756
+ ...colors0 === void 0 ? {} : { colors0 },
1757
+ ...joints0 === void 0 ? {} : { joints0 },
1758
+ ...weights0 === void 0 ? {} : { weights0 },
1759
+ ...morphTargets.length === 0 ? {} : { morphTargets },
1760
+ ...meshWeights === void 0 ? {} : { morphWeights: new Float32Array(meshWeights) },
1761
+ ...indices === void 0 ? {} : { indices },
1762
+ materialIndex: prim.material ?? null,
1763
+ meshIndex
1764
+ };
1765
+ meshes.push(meshIr);
1766
+ }
1767
+ }
1768
+ const texturesJson = json.textures ?? [];
1769
+ const textures = [];
1770
+ for (const texJson of texturesJson) {
1771
+ const texIr = {
1772
+ source: texJson.source ?? -1,
1773
+ ...texJson.sampler === void 0 ? {} : { sampler: texJson.sampler },
1774
+ ...texJson.name === void 0 ? {} : { name: texJson.name }
1775
+ };
1776
+ textures.push(texIr);
1777
+ }
1778
+ const imagesJson = json.images ?? [];
1779
+ const images = [];
1780
+ for (const imgJson of imagesJson) {
1781
+ const mimeType = imgJson.mimeType;
1782
+ if (mimeType !== void 0 && mimeType !== "image/jpeg" && mimeType !== "image/png") {
1783
+ return err(gltfErr("gltf-image-mime-unsupported", { mimeType }));
1784
+ }
1785
+ const imgIr = {
1786
+ ...imgJson.uri === void 0 ? {} : { uri: imgJson.uri },
1787
+ ...imgJson.mimeType === void 0 ? {} : { mimeType: imgJson.mimeType },
1788
+ ...imgJson.bufferView === void 0 ? {} : { bufferView: imgJson.bufferView },
1789
+ ...imgJson.name === void 0 ? {} : { name: imgJson.name }
1790
+ };
1791
+ images.push(imgIr);
1792
+ }
1793
+ for (const img of images) {
1794
+ if (img.uri !== void 0) {
1795
+ if (dataUriBase64Payload(img.uri) !== void 0) continue;
1796
+ try {
1797
+ await ctx.externalLoader(img.uri);
1798
+ } catch (_e) {
1799
+ return err(gltfErr("gltf-texture-load-failed", { uri: img.uri }));
1800
+ }
1801
+ }
1802
+ }
1803
+ const samplersJson = json.samplers ?? [];
1804
+ const samplers = [];
1805
+ for (const sampJson of samplersJson) {
1806
+ samplers.push({
1807
+ ...sampJson.magFilter === void 0 ? {} : { magFilter: sampJson.magFilter },
1808
+ ...sampJson.minFilter === void 0 ? {} : { minFilter: sampJson.minFilter },
1809
+ wrapS: sampJson.wrapS ?? 10497,
1810
+ // REPEAT (glTF default)
1811
+ wrapT: sampJson.wrapT ?? 10497,
1812
+ ...sampJson.name === void 0 ? {} : { name: sampJson.name }
1813
+ });
1814
+ }
1815
+ const materialsJson = json.materials ?? [];
1816
+ const materials = [];
1817
+ for (const matJson of materialsJson) {
1818
+ const pbr = matJson.pbrMetallicRoughness;
1819
+ const baseColor = pbr?.baseColorFactor ?? [1, 1, 1, 1];
1820
+ const baseColor4 = [
1821
+ baseColor[0] ?? 1,
1822
+ baseColor[1] ?? 1,
1823
+ baseColor[2] ?? 1,
1824
+ baseColor[3] ?? 1
1825
+ ];
1826
+ const alphaMode = matJson.alphaMode === "MASK" || matJson.alphaMode === "BLEND" ? matJson.alphaMode : void 0;
1827
+ const alphaCutoff = alphaMode === "MASK" ? matJson.alphaCutoff ?? 0.5 : void 0;
1828
+ const emissiveFactor = matJson.emissiveFactor;
1829
+ const emissiveFactor3 = [
1830
+ emissiveFactor?.[0] ?? 0,
1831
+ emissiveFactor?.[1] ?? 0,
1832
+ emissiveFactor?.[2] ?? 0
1833
+ ];
1834
+ materials.push({
1835
+ ...matJson.name === void 0 ? {} : { name: matJson.name },
1836
+ baseColorFactor: baseColor4,
1837
+ ...emissiveFactor === void 0 ? {} : { emissiveFactor: emissiveFactor3 },
1838
+ metallicFactor: pbr?.metallicFactor ?? 1,
1839
+ roughnessFactor: pbr?.roughnessFactor ?? 1,
1840
+ ...pbr?.baseColorTexture === void 0 ? {} : { baseColorTexture: parseTextureInfo(pbr.baseColorTexture, textures) },
1841
+ ...pbr?.metallicRoughnessTexture === void 0 ? {} : { metallicRoughnessTexture: parseTextureInfo(pbr.metallicRoughnessTexture, textures) },
1842
+ ...matJson.normalTexture === void 0 ? {} : {
1843
+ normalTexture: {
1844
+ ...parseTextureInfo(matJson.normalTexture, textures),
1845
+ ...matJson.normalTexture.scale === void 0 ? {} : { scale: matJson.normalTexture.scale }
1846
+ }
1847
+ },
1848
+ ...matJson.occlusionTexture === void 0 ? {} : {
1849
+ occlusionTexture: {
1850
+ ...parseTextureInfo(matJson.occlusionTexture, textures),
1851
+ ...matJson.occlusionTexture.strength === void 0 ? {} : { strength: matJson.occlusionTexture.strength }
1852
+ }
1853
+ },
1854
+ ...matJson.emissiveTexture === void 0 ? {} : { emissiveTexture: parseTextureInfo(matJson.emissiveTexture, textures) },
1855
+ ...alphaMode === void 0 ? {} : { alphaMode },
1856
+ ...alphaCutoff === void 0 ? {} : { alphaCutoff },
1857
+ ...matJson.doubleSided === true ? { doubleSided: true } : {},
1858
+ ...pbr?.baseColorTexture?.texCoord === void 0 || pbr.baseColorTexture.texCoord === 0 ? {} : { baseColorTexCoord: pbr.baseColorTexture.texCoord }
1859
+ });
1860
+ }
1861
+ const diagnostics = {
1862
+ nodeNames: [],
1863
+ unsupportedExtensions: [...unsupportedExtensions],
1864
+ matrixTrsCoexistNodes: []
1865
+ };
1866
+ const nodes = [];
1867
+ const nodesJson = json.nodes ?? [];
1868
+ for (let nodeIndex = 0; nodeIndex < nodesJson.length; nodeIndex++) {
1869
+ const nodeJson = nodesJson[nodeIndex];
1870
+ if (nodeJson === void 0) continue;
1871
+ const transform = decomposeNodeTransform(nodeJson, nodeIndex, diagnostics);
1872
+ if (nodeJson.name !== void 0) diagnostics.nodeNames.push(nodeJson.name);
1873
+ const instancingExt = nodeJson.extensions?.EXT_mesh_gpu_instancing;
1874
+ let instancing;
1875
+ if (instancingExt !== void 0) {
1876
+ const instancingResult = decodeNodeInstancing(
1877
+ nodeIndex,
1878
+ instancingExt.attributes ?? {},
1879
+ accessors,
1880
+ bufferViews,
1881
+ buffers
1882
+ );
1883
+ if (!instancingResult.ok) return err(instancingResult.error);
1884
+ instancing = instancingResult.value;
1885
+ }
1886
+ const nodeMorphWeights = nodeJson.weights === void 0 ? void 0 : new Float32Array(nodeJson.weights);
1887
+ nodes.push({
1888
+ ...nodeJson.name === void 0 ? {} : { name: nodeJson.name },
1889
+ transform,
1890
+ meshIndex: nodeJson.mesh ?? null,
1891
+ skinIndex: nodeJson.skin ?? null,
1892
+ children: nodeJson.children ?? [],
1893
+ camera: nodeJson.camera ?? null,
1894
+ lightIndex: nodeJson.extensions?.KHR_lights_punctual?.light ?? null,
1895
+ ...instancing === void 0 ? {} : { instancing },
1896
+ ...nodeMorphWeights === void 0 ? {} : { morphWeights: nodeMorphWeights }
1897
+ });
1898
+ }
1899
+ const scenesJson = json.scenes ?? [];
1900
+ const scenes = scenesJson.map((s) => ({
1901
+ ...s.name === void 0 ? {} : { name: s.name },
1902
+ nodes: s.nodes ?? []
1903
+ }));
1904
+ const defaultSceneIndex = json.scene ?? 0;
1905
+ const skinsJson = json.skins;
1906
+ const skinResult = parseSkin(skinsJson, nodesJson, accessors, bufferViews, buffers);
1907
+ if (!skinResult.ok) return err(skinResult.error);
1908
+ const skeletons = skinResult.value;
1909
+ const animationsJson = json.animations;
1910
+ const animResult = parseAnimation(animationsJson, nodesJson, accessors, bufferViews, buffers);
1911
+ if (!animResult.ok) return err(animResult.error);
1912
+ const animationClips = animResult.value;
1913
+ return ok({
1914
+ meshes,
1915
+ materials,
1916
+ nodes,
1917
+ scenes,
1918
+ skeletons,
1919
+ animationClips,
1920
+ textures: textures.length > 0 ? textures : void 0,
1921
+ images: images.length > 0 ? images : void 0,
1922
+ samplers: samplers.length > 0 ? samplers : void 0,
1923
+ defaultSceneIndex,
1924
+ diagnostics: {
1925
+ nodeNames: diagnostics.nodeNames,
1926
+ unsupportedExtensions: diagnostics.unsupportedExtensions,
1927
+ matrixTrsCoexistNodes: diagnostics.matrixTrsCoexistNodes
1928
+ },
1929
+ meshPrimitiveCount,
1930
+ lights: lightsResult.value
1931
+ });
1932
+ }
1933
+ async function parseGltfForImporter(json, externalLoader, filePath, options = {}) {
1934
+ if (json === null || typeof json !== "object") {
1935
+ return err(
1936
+ gltfErr("gltf-malformed-header", {
1937
+ filePath,
1938
+ byteOffset: 0
1939
+ })
1940
+ );
1941
+ }
1942
+ return parseGltfWithBin(json, {
1943
+ externalLoader,
1944
+ filePath,
1945
+ ...options.meshopt === void 0 ? {} : { meshopt: options.meshopt }
1946
+ });
1947
+ }
1948
+ function legacyParseResult(result, filePath, byteOffset) {
1949
+ if (result.ok) return ok(result.value);
1950
+ if (result.error instanceof ImportError) {
1951
+ return err(gltfErr("gltf-malformed-header", { filePath, byteOffset }));
1952
+ }
1953
+ return err(result.error);
1954
+ }
1955
+ async function parseGltf(json, externalLoader, filePath, options = {}) {
1956
+ const result = await parseGltfForImporter(json, externalLoader, filePath, options);
1957
+ return legacyParseResult(result, filePath, 0);
1958
+ }
1959
+ async function parseGlbForImporter(buffer, filePath, options = {}) {
1960
+ const chunksResult = parseGlbChunks(buffer, filePath);
1961
+ if (!chunksResult.ok) return err(chunksResult.error);
1962
+ let json;
1963
+ try {
1964
+ json = JSON.parse(new TextDecoder().decode(chunksResult.value.jsonChunk));
1965
+ } catch (_e) {
1966
+ return err(
1967
+ gltfErr("gltf-malformed-header", {
1968
+ filePath,
1969
+ byteOffset: 12
1970
+ })
1971
+ );
1972
+ }
1973
+ if (json === null || typeof json !== "object") {
1974
+ return err(
1975
+ gltfErr("gltf-malformed-header", {
1976
+ filePath,
1977
+ byteOffset: 12
1978
+ })
1979
+ );
1980
+ }
1981
+ const externalLoader = async (uri) => {
1982
+ throw new Error(`parseGlb: GLB containers must not reference external URIs (got ${uri})`);
1983
+ };
1984
+ return parseGltfWithBin(json, {
1985
+ externalLoader,
1986
+ ...chunksResult.value.binChunk === void 0 ? {} : { binChunk: chunksResult.value.binChunk },
1987
+ filePath,
1988
+ ...options.meshopt === void 0 ? {} : { meshopt: options.meshopt }
1989
+ });
1990
+ }
1991
+ async function parseGlb(buffer, filePath, options = {}) {
1992
+ const result = await parseGlbForImporter(buffer, filePath, options);
1993
+ return legacyParseResult(result, filePath, 12);
1994
+ }
1995
+ function toAssetPack(doc, existingMeta, source) {
1996
+ const items = [];
1997
+ const seenMeshIndices = /* @__PURE__ */ new Set();
1998
+ for (const m of doc.meshes) {
1999
+ if (m === void 0) continue;
2000
+ if (seenMeshIndices.has(m.meshIndex)) continue;
2001
+ seenMeshIndices.add(m.meshIndex);
2002
+ items.push({
2003
+ kind: "mesh",
2004
+ sourceIndex: m.meshIndex,
2005
+ ...m.name === void 0 ? {} : { name: m.name }
2006
+ });
2007
+ }
2008
+ for (let i = 0; i < doc.materials.length; i++) {
2009
+ const m = doc.materials[i];
2010
+ if (m === void 0) continue;
2011
+ items.push({
2012
+ kind: "material",
2013
+ sourceIndex: i,
2014
+ ...m.name === void 0 ? {} : { name: m.name }
2015
+ });
2016
+ }
2017
+ for (let i = 0; i < doc.scenes.length; i++) {
2018
+ const s = doc.scenes[i];
2019
+ if (s === void 0) continue;
2020
+ items.push({
2021
+ kind: "scene",
2022
+ sourceIndex: i,
2023
+ ...s.name === void 0 ? {} : { name: s.name }
2024
+ });
2025
+ }
2026
+ const images = doc.images ?? [];
2027
+ for (let i = 0; i < images.length; i++) {
2028
+ const img = images[i];
2029
+ if (img === void 0) continue;
2030
+ items.push({
2031
+ kind: "texture",
2032
+ sourceIndex: i,
2033
+ ...img.name === void 0 ? {} : { name: img.name }
2034
+ });
2035
+ }
2036
+ const samplers = doc.samplers ?? [];
2037
+ for (let i = 0; i < samplers.length; i++) {
2038
+ const sampler = samplers[i];
2039
+ if (sampler === void 0) continue;
2040
+ items.push({
2041
+ kind: "sampler",
2042
+ sourceIndex: i,
2043
+ ...sampler.name === void 0 ? {} : { name: sampler.name }
2044
+ });
2045
+ }
2046
+ for (let i = 0; i < doc.skeletons.length; i++) {
2047
+ items.push({ kind: "skeleton", sourceIndex: i });
2048
+ }
2049
+ for (let i = 0; i < doc.skeletons.length; i++) {
2050
+ items.push({ kind: "skin", sourceIndex: i });
2051
+ }
2052
+ for (let i = 0; i < doc.animationClips.length; i++) {
2053
+ const clip = doc.animationClips[i];
2054
+ if (clip === void 0) continue;
2055
+ items.push({
2056
+ kind: "animation-clip",
2057
+ sourceIndex: i,
2058
+ ...clip.name === void 0 ? {} : { name: clip.name }
2059
+ });
2060
+ }
2061
+ const reuse = reimportReuseMeta(items, existingMeta);
2062
+ if (!reuse.ok) return reuse;
2063
+ const subAssetByKindIndex = new Map(
2064
+ reuse.value.subAssets.map((entry) => [`${entry.kind}:${entry.sourceIndex}`, entry])
2065
+ );
2066
+ const sourceOverrides = {
2067
+ ...existingMeta?.sourceOverrides ?? {}
2068
+ };
2069
+ for (const meshIndex of seenMeshIndices) {
2070
+ const meshOutput = subAssetByKindIndex.get(`mesh:${meshIndex}`);
2071
+ if (meshOutput?.sourceKey === void 0) continue;
2072
+ const materialSlots = [];
2073
+ const slotByMaterial = /* @__PURE__ */ new Map();
2074
+ const usedNames = /* @__PURE__ */ new Set();
2075
+ const uniqueName = (raw) => {
2076
+ const base = raw.trim() || "Material";
2077
+ let candidate = base;
2078
+ let suffix = 2;
2079
+ while (usedNames.has(candidate)) candidate = `${base}_${suffix++}`;
2080
+ usedNames.add(candidate);
2081
+ return candidate;
2082
+ };
2083
+ for (const primitive of doc.meshes.filter((mesh) => mesh.meshIndex === meshIndex)) {
2084
+ const materialIndex = primitive.materialIndex;
2085
+ if (slotByMaterial.has(materialIndex)) continue;
2086
+ const materialOutput = materialIndex === null ? void 0 : subAssetByKindIndex.get(`material:${materialIndex}`);
2087
+ slotByMaterial.set(materialIndex, materialSlots.length);
2088
+ materialSlots.push({
2089
+ slotName: uniqueName(
2090
+ materialIndex === null ? "Default" : doc.materials[materialIndex]?.name ?? `Material_${materialIndex}`
2091
+ ),
2092
+ sourceKey: materialIndex === null ? "gltf:default" : materialOutput?.sourceKey ?? `gltf:material:${materialIndex}`,
2093
+ ...materialOutput === void 0 ? {} : { defaultMaterialGuid: materialOutput.guid }
2094
+ });
2095
+ }
2096
+ const previousPayload = existingMeta?.sourceOverrides?.[meshOutput.sourceKey];
2097
+ const previousRaw = previousPayload?.materialSlots;
2098
+ const previous = Array.isArray(previousRaw) ? previousRaw.filter(
2099
+ (slot) => slot !== null && typeof slot === "object" && !Array.isArray(slot) && typeof slot.slotName === "string"
2100
+ ) : [];
2101
+ const reconciled = reconcileMeshMaterialSlotTopology(materialSlots, previous);
2102
+ if (!reconciled.ok) {
2103
+ return err({
2104
+ code: "mesh-material-slot-topology-change",
2105
+ expected: `unambiguous material slot identity for mesh ${meshOutput.guid}`,
2106
+ hint: reconciled.error.hint,
2107
+ detail: {
2108
+ sourceIndices: reconciled.error.nextIndices,
2109
+ previousIndices: reconciled.error.previousIndices
2110
+ }
2111
+ });
2112
+ }
2113
+ sourceOverrides[meshOutput.sourceKey] = {
2114
+ ...previousPayload ?? {},
2115
+ materialSlots: reconciled.slots
2116
+ };
2117
+ }
2118
+ const meta = {
2119
+ schemaVersion: 1,
2120
+ kind: "external-asset-package",
2121
+ importer: "gltf",
2122
+ source,
2123
+ subAssets: reuse.value.subAssets,
2124
+ ...Object.keys(sourceOverrides).length === 0 ? {} : { sourceOverrides },
2125
+ sourceOverrideDescriptors: reuse.value.subAssets.filter((entry) => entry.kind === "mesh" && entry.sourceKey !== void 0).map((entry) => ({
2126
+ sourceKey: entry.sourceKey,
2127
+ semantic: "mesh-material-slot-defaults",
2128
+ payloadSchema: MESH_MATERIAL_SLOT_SOURCE_OVERRIDE_PAYLOAD_SCHEMA
2129
+ })),
2130
+ importSettings: {
2131
+ defaultSceneIndex: doc.defaultSceneIndex,
2132
+ ...typeof existingMeta?.importSettings.standardMaterialGuid === "string" ? { standardMaterialGuid: existingMeta.importSettings.standardMaterialGuid } : {},
2133
+ ...existingMeta?.importSettings.downscaleMaxDimension !== void 0 ? { downscaleMaxDimension: existingMeta.importSettings.downscaleMaxDimension } : {},
2134
+ diagnostics: {
2135
+ nodeNames: doc.diagnostics.nodeNames,
2136
+ unsupportedExtensions: doc.diagnostics.unsupportedExtensions,
2137
+ matrixTrsCoexistNodes: doc.diagnostics.matrixTrsCoexistNodes
2138
+ }
2139
+ }
2140
+ };
2141
+ return ok({ meta, subAssets: reuse.value.subAssets });
2142
+ }
2143
+
2144
+ // src/serialize-meta.ts
2145
+ function sortKeysDeep(value) {
2146
+ if (Array.isArray(value)) return value.map(sortKeysDeep);
2147
+ if (value !== null && typeof value === "object") {
2148
+ const obj = value;
2149
+ const sorted = {};
2150
+ for (const key of Object.keys(obj).sort()) {
2151
+ sorted[key] = sortKeysDeep(obj[key]);
2152
+ }
2153
+ return sorted;
2154
+ }
2155
+ return value;
2156
+ }
2157
+ function serializeMetaJson(meta) {
2158
+ return `${JSON.stringify(sortKeysDeep(meta), null, 2)}
2159
+ `;
2160
+ }
2161
+
2162
+ // src/cli-gltf.ts
2163
+ function emitError(ctx, err2) {
2164
+ const payload = {
2165
+ code: err2.code,
2166
+ expected: err2.expected,
2167
+ hint: err2.hint
2168
+ };
2169
+ if (err2.detail !== void 0) payload.detail = err2.detail;
2170
+ ctx.stderrWrite(JSON.stringify(payload));
2171
+ return 1;
2172
+ }
2173
+ function helpBody() {
2174
+ return [
2175
+ "forgeax-engine-remote-gltf \u2014 glTF / GLB sidecar importer (produces texture/mesh/material/scene sub-assets)",
2176
+ "",
2177
+ "Usage:",
2178
+ " forgeax-engine-remote-gltf import <path.gltf|path.glb>",
2179
+ " forgeax-engine-remote-gltf import --check <dir>",
2180
+ "",
2181
+ "Produces a sibling <source>.meta.json sidecar with sub-asset entries",
2182
+ "for every glTF asset category: texture, mesh, material, scene.",
2183
+ ""
2184
+ ].join("\n");
2185
+ }
2186
+ async function runCliGltf(rest, ctx) {
2187
+ const [sub, ...subRest] = rest;
2188
+ if (sub === void 0 || sub === "--help" || sub === "-h") {
2189
+ ctx.stdoutWrite(helpBody());
2190
+ return 0;
2191
+ }
2192
+ if (sub !== "import") {
2193
+ return emitError(ctx, {
2194
+ code: "unknown-subcommand",
2195
+ expected: "subcommand 'import'",
2196
+ hint: "run 'forgeax-engine-remote-gltf --help' for usage",
2197
+ detail: { subcommand: sub }
2198
+ });
2199
+ }
2200
+ return runImport(subRest, ctx);
2201
+ }
2202
+ async function runImport(rest, ctx) {
2203
+ let positionals;
2204
+ let check = false;
2205
+ try {
2206
+ const parsed = parseArgs({
2207
+ args: rest,
2208
+ allowPositionals: true,
2209
+ strict: true,
2210
+ options: { check: { type: "boolean" } }
2211
+ });
2212
+ positionals = [...parsed.positionals];
2213
+ check = parsed.values.check === true;
2214
+ } catch (e) {
2215
+ const message = e instanceof Error ? e.message : String(e);
2216
+ return emitError(ctx, {
2217
+ code: "cli-parse-error",
2218
+ expected: "forgeax-engine-remote-gltf import [--check] <path>",
2219
+ hint: "run 'forgeax-engine-remote-gltf --help' for usage",
2220
+ detail: { message }
2221
+ });
2222
+ }
2223
+ const target = positionals[0];
2224
+ if (target === void 0) {
2225
+ return emitError(ctx, {
2226
+ code: "cli-parse-error",
2227
+ expected: check ? "forgeax-engine-remote-gltf import --check <dir>" : "forgeax-engine-remote-gltf import <path.gltf|path.glb>",
2228
+ hint: "pass a positional <gltf-or-glb> argument; with --check pass a directory"
2229
+ });
2230
+ }
2231
+ if (check) {
2232
+ return runCheck(target, ctx);
2233
+ }
2234
+ return runWrite(target, ctx);
2235
+ }
2236
+ var SCANNER_BLACKLIST_FALLBACK = /* @__PURE__ */ new Set([
2237
+ "node_modules",
2238
+ ".forgeax-harness",
2239
+ ".git",
2240
+ "dist",
2241
+ ".forgeax-asset-cache",
2242
+ "forgeax-engine-assets",
2243
+ "coverage"
2244
+ ]);
2245
+ async function loadScannerBlacklist() {
2246
+ try {
2247
+ const mod = await import('@forgeax/engine-pack/scanner');
2248
+ return mod.SCANNER_BLACKLIST ?? SCANNER_BLACKLIST_FALLBACK;
2249
+ } catch {
2250
+ return SCANNER_BLACKLIST_FALLBACK;
2251
+ }
2252
+ }
2253
+ async function findGltfSources(dir, blacklist, out) {
2254
+ let entries;
2255
+ try {
2256
+ entries = await readdir(dir, { withFileTypes: true });
2257
+ } catch {
2258
+ return;
2259
+ }
2260
+ for (const entry of entries) {
2261
+ const full = join(dir, entry.name);
2262
+ if (entry.isDirectory()) {
2263
+ if (blacklist.has(entry.name)) continue;
2264
+ await findGltfSources(full, blacklist, out);
2265
+ } else if (entry.isFile()) {
2266
+ const lower = entry.name.toLowerCase();
2267
+ if (lower.endsWith(".gltf") || lower.endsWith(".glb")) {
2268
+ out.push(full);
2269
+ }
2270
+ }
2271
+ }
2272
+ }
2273
+ function expectedMetaPathFor(filePath) {
2274
+ return `${filePath}.meta.json`;
2275
+ }
2276
+ async function fileExists(path) {
2277
+ try {
2278
+ await stat(path);
2279
+ return true;
2280
+ } catch {
2281
+ return false;
2282
+ }
2283
+ }
2284
+ async function runCheck(target, ctx) {
2285
+ const root = resolve(target);
2286
+ let st;
2287
+ try {
2288
+ st = await stat(root);
2289
+ } catch (e) {
2290
+ const message = e instanceof Error ? e.message : String(e);
2291
+ return emitError(ctx, {
2292
+ code: "cli-parse-error",
2293
+ expected: "forgeax-engine-remote-gltf import --check <dir>",
2294
+ hint: "pass a directory that exists and is readable",
2295
+ detail: { path: target, message }
2296
+ });
2297
+ }
2298
+ const blacklist = await loadScannerBlacklist();
2299
+ const found = [];
2300
+ if (st.isDirectory()) {
2301
+ await findGltfSources(root, blacklist, found);
2302
+ } else if (st.isFile()) {
2303
+ const lower = root.toLowerCase();
2304
+ if (lower.endsWith(".gltf") || lower.endsWith(".glb")) {
2305
+ found.push(root);
2306
+ }
2307
+ }
2308
+ found.sort();
2309
+ for (const sourcePath of found) {
2310
+ const metaPath = expectedMetaPathFor(sourcePath);
2311
+ if (!await fileExists(metaPath)) {
2312
+ return emitError(ctx, {
2313
+ code: "gltf-meta-missing",
2314
+ expected: "sidecar <source>.meta.json (importer: 'gltf') present in same directory",
2315
+ hint: GLTF_ERROR_HINTS["gltf-meta-missing"],
2316
+ detail: { filePath: sourcePath, expectedMetaPath: metaPath }
2317
+ });
2318
+ }
2319
+ }
2320
+ return 0;
2321
+ }
2322
+ async function runWrite(target, ctx) {
2323
+ const sourcePath = resolve(target);
2324
+ const lower = sourcePath.toLowerCase();
2325
+ const isGlb = lower.endsWith(".glb");
2326
+ const isGltf = lower.endsWith(".gltf");
2327
+ if (!isGlb && !isGltf) {
2328
+ return emitError(ctx, {
2329
+ code: "cli-parse-error",
2330
+ expected: "<path>.gltf or <path>.glb",
2331
+ hint: "pass a .gltf / .glb source file; for batch dry-run use --check <dir>",
2332
+ detail: { path: target }
2333
+ });
2334
+ }
2335
+ let docResult;
2336
+ if (isGlb) {
2337
+ let buf;
2338
+ try {
2339
+ buf = await readFile(sourcePath);
2340
+ } catch (e) {
2341
+ const message = e instanceof Error ? e.message : String(e);
2342
+ return emitError(ctx, {
2343
+ code: "cli-parse-error",
2344
+ expected: "readable .glb at <path>",
2345
+ hint: "check the file exists and the process has read access",
2346
+ detail: { path: target, message }
2347
+ });
2348
+ }
2349
+ const ab = buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
2350
+ docResult = await parseGlb(ab, sourcePath);
2351
+ } else {
2352
+ let text;
2353
+ try {
2354
+ text = await readFile(sourcePath, "utf-8");
2355
+ } catch (e) {
2356
+ const message = e instanceof Error ? e.message : String(e);
2357
+ return emitError(ctx, {
2358
+ code: "cli-parse-error",
2359
+ expected: "readable .gltf at <path>",
2360
+ hint: "check the file exists and the process has read access",
2361
+ detail: { path: target, message }
2362
+ });
2363
+ }
2364
+ let json;
2365
+ try {
2366
+ json = JSON.parse(text);
2367
+ } catch (e) {
2368
+ const message = e instanceof Error ? e.message : String(e);
2369
+ return emitError(ctx, {
2370
+ code: "gltf-malformed-header",
2371
+ expected: "valid JSON document at the .gltf root",
2372
+ hint: GLTF_ERROR_HINTS["gltf-malformed-header"],
2373
+ detail: { filePath: sourcePath, byteOffset: 0, parseError: message }
2374
+ });
2375
+ }
2376
+ const baseDir = dirname(sourcePath);
2377
+ const externalLoader = async (uri) => {
2378
+ const abs = resolve(baseDir, uri);
2379
+ const buf = await readFile(abs);
2380
+ return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
2381
+ };
2382
+ docResult = await parseGltf(json, externalLoader, sourcePath);
2383
+ }
2384
+ if (!docResult.ok) {
2385
+ return emitError(ctx, {
2386
+ code: docResult.error.code,
2387
+ expected: docResult.error.expected,
2388
+ hint: docResult.error.hint,
2389
+ detail: docResult.error.detail
2390
+ });
2391
+ }
2392
+ const metaPath = expectedMetaPathFor(sourcePath);
2393
+ let existingMeta;
2394
+ if (await fileExists(metaPath)) {
2395
+ try {
2396
+ const raw = await readFile(metaPath, "utf-8");
2397
+ existingMeta = JSON.parse(raw);
2398
+ } catch {
2399
+ existingMeta = void 0;
2400
+ }
2401
+ }
2402
+ const sourceRelative = sourcePath.slice(dirname(sourcePath).length + 1);
2403
+ const pack = toAssetPack(
2404
+ docResult.value,
2405
+ existingMeta,
2406
+ sourceRelative
2407
+ );
2408
+ if (!pack.ok) {
2409
+ return emitError(ctx, pack.error);
2410
+ }
2411
+ await writeFile(metaPath, serializeMetaJson(pack.value.meta), "utf-8");
2412
+ return 0;
2413
+ }
2414
+ var isBinEntry = await (async () => {
2415
+ const argv1 = process.argv[1];
2416
+ if (typeof argv1 !== "string") return false;
2417
+ const argv1Real = await realpath(argv1).catch(() => argv1);
2418
+ const selfReal = await realpath(fileURLToPath(import.meta.url)).catch(
2419
+ () => fileURLToPath(import.meta.url)
2420
+ );
2421
+ return argv1Real === selfReal;
2422
+ })();
2423
+ if (isBinEntry) {
2424
+ const exitCode = await runCliGltf(process.argv.slice(2), {
2425
+ stdoutWrite: (line) => process.stdout.write(`${line}
2426
+ `),
2427
+ stderrWrite: (line) => process.stderr.write(`${line}
2428
+ `)
2429
+ });
2430
+ process.exit(exitCode);
2431
+ }
2432
+
2433
+ export { runCliGltf };
2434
+ //# sourceMappingURL=cli-gltf.mjs.map
2435
+ //# sourceMappingURL=cli-gltf.mjs.map