@forgeax/engine-fbx 0.1.4 → 0.1.6

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.
package/dist/index.mjs CHANGED
@@ -1,6 +1,9 @@
1
- import { ImportError, IMPORT_ERROR_HINTS, reconcileMeshMaterialSlotTopology, resolveMeshMaterialSlotDefaultGuid, err, ok } from '@forgeax/engine-types';
1
+ import { reconcileMeshMaterialSlotTopology, ImportError, IMPORT_ERROR_HINTS, resolveMeshMaterialSlotDefaultGuid, err, ok } from '@forgeax/engine-types';
2
2
  import { deriveAnimationTargetId } from '@forgeax/engine-animation/target-id';
3
+ import '@forgeax/engine-pack';
3
4
  import { box3 } from '@forgeax/engine-math';
5
+ import { AssetGuid } from '@forgeax/engine-pack/guid';
6
+ import { MESH_BIN_HEADER_V4_BYTES, writeMeshBinHeader } from '@forgeax/engine-pack/mesh-bin-contract';
4
7
 
5
8
  // src/errors.ts
6
9
  var fbxErrorPolicy = {
@@ -242,20 +245,6 @@ function phongRoughness(shininess, maxGloss = 100) {
242
245
  return clamp(1 - Math.sqrt(shininess / maxGloss), 0, 1);
243
246
  }
244
247
  function parseMaterial(raw, sourceIndex) {
245
- const textureBindings = raw.textureBindings?.map((binding) => ({
246
- slot: binding.slot,
247
- textureIndex: binding.textureIndex,
248
- ...binding.texCoord === void 0 ? {} : { texCoord: binding.texCoord }
249
- }));
250
- const textureFields = textureBindings === void 0 || textureBindings.length === 0 ? {} : {
251
- textureBindings,
252
- ...Object.fromEntries(
253
- textureBindings.map((binding) => [
254
- binding.slot === "baseColorTexture" ? "baseColorTextureIndex" : binding.slot === "metallicRoughnessTexture" ? "metallicRoughnessTextureIndex" : binding.slot === "normalTexture" ? "normalTextureIndex" : binding.slot === "occlusionTexture" ? "occlusionTextureIndex" : binding.slot === "emissiveTexture" ? "emissiveTextureIndex" : "specularTintTextureIndex",
255
- binding.textureIndex
256
- ])
257
- )
258
- };
259
248
  switch (raw.kind) {
260
249
  case "stingray-pbs": {
261
250
  const sp = raw.stingrayProps ?? {};
@@ -268,8 +257,7 @@ function parseMaterial(raw, sourceIndex) {
268
257
  1
269
258
  ],
270
259
  metallicFactor: sp.metallic ?? 0,
271
- roughnessFactor: sp.roughness ?? 0.5,
272
- ...textureFields
260
+ roughnessFactor: sp.roughness ?? 0.5
273
261
  };
274
262
  }
275
263
  case "phong": {
@@ -279,8 +267,7 @@ function parseMaterial(raw, sourceIndex) {
279
267
  ...raw.name !== void 0 && { name: raw.name },
280
268
  baseColorFactor: [d[0] ?? 0.5, d[1] ?? 0.5, d[2] ?? 0.5, 1],
281
269
  metallicFactor: 0,
282
- roughnessFactor: phongRoughness(gloss),
283
- ...textureFields
270
+ roughnessFactor: phongRoughness(gloss)
284
271
  };
285
272
  }
286
273
  case "lambert": {
@@ -289,17 +276,15 @@ function parseMaterial(raw, sourceIndex) {
289
276
  ...raw.name !== void 0 && { name: raw.name },
290
277
  baseColorFactor: [d[0] ?? 0.5, d[1] ?? 0.5, d[2] ?? 0.5, 1],
291
278
  metallicFactor: 0,
292
- roughnessFactor: 0.5,
279
+ roughnessFactor: 0.5
293
280
  // lambert has no specular → default roughness
294
- ...textureFields
295
281
  };
296
282
  }
297
283
  default: {
298
284
  return {
299
285
  baseColorFactor: [0.5, 0.5, 0.5, 1],
300
286
  metallicFactor: 0,
301
- roughnessFactor: 0.5,
302
- ...textureFields
287
+ roughnessFactor: 0.5
303
288
  };
304
289
  }
305
290
  }
@@ -510,511 +495,14 @@ function parseSkin(doc) {
510
495
  }
511
496
 
512
497
  // src/parse-texture.ts
513
- function normalizePath(path) {
514
- return path.replaceAll("\\", "/");
515
- }
516
498
  function parseTextures(raw) {
517
499
  const textures = raw.textures ?? [];
518
500
  return textures.map((t) => ({
519
- name: t.name ?? t.filePath ?? t.relativeFilePath ?? `texture-${t.sourceIndex}`,
520
- filePath: normalizePath(t.filePath ?? t.relativeFilePath ?? ""),
521
- ...t.relativeFilePath === void 0 ? {} : { relativeFilePath: normalizePath(t.relativeFilePath) },
522
- ...t.absoluteFilePath === void 0 ? {} : { absoluteFilePath: t.absoluteFilePath },
523
- ...t.embeddedBytes === void 0 ? {} : { embeddedBytes: Uint8Array.from(t.embeddedBytes) },
524
- ...t.type === void 0 ? {} : { type: t.type },
501
+ name: t.name ?? t.filePath,
502
+ filePath: t.filePath,
525
503
  sourceIndex: t.sourceIndex
526
504
  }));
527
505
  }
528
-
529
- // src/resolve-texture-path.ts
530
- function normalizeSourceRelativePath(raw) {
531
- const value = raw.replaceAll("\\", "/");
532
- if (value.startsWith("/") || /^[A-Za-z]:\//.test(value) || value.startsWith("//"))
533
- return void 0;
534
- const parts = [];
535
- for (const part of value.split("/")) {
536
- if (part === "" || part === ".") continue;
537
- if (part === "..") {
538
- if (parts.at(-1) !== void 0 && parts.at(-1) !== "..") parts.pop();
539
- else parts.push("..");
540
- } else {
541
- parts.push(part);
542
- }
543
- }
544
- return parts.join("/");
545
- }
546
- function requestSegments(raw) {
547
- const value = raw.replaceAll("\\", "/").replace(/^[A-Za-z]:\//, "").replace(/^\/+/, "");
548
- return value.split("/").filter((part) => part !== "" && part !== ".");
549
- }
550
- function uniqueMatches(paths, predicate) {
551
- return [...new Set(paths.filter(predicate))];
552
- }
553
- function longestSuffixLength(request, candidate) {
554
- let count = 0;
555
- while (count < request.length && count < candidate.length && request[request.length - 1 - count]?.toLowerCase() === candidate[candidate.length - 1 - count]?.toLowerCase()) {
556
- count++;
557
- }
558
- return count;
559
- }
560
- function resolveFbxTexturePath(sourcePath, request, candidates) {
561
- const candidatePaths = candidates.flatMap((candidate) => {
562
- const normalized = normalizeSourceRelativePath(candidate.relativePath);
563
- return normalized === void 0 || normalized.length === 0 ? [] : [normalized];
564
- });
565
- const requestedPath = request.declaredRelativePath ?? request.declaredFilename ?? request.declaredAbsolutePath ?? "";
566
- const choose = (matches, strategy) => {
567
- const unique = [...new Set(matches)];
568
- if (unique.length === 1) {
569
- const relativePath = unique[0];
570
- if (relativePath === void 0) return void 0;
571
- return {
572
- ok: true,
573
- relativePath,
574
- readUri: relativePath,
575
- strategy
576
- };
577
- }
578
- if (unique.length > 1) {
579
- return {
580
- ok: false,
581
- code: "fbx-external-texture-ambiguous",
582
- requestedPath,
583
- candidates: unique
584
- };
585
- }
586
- return void 0;
587
- };
588
- const relativeRequest = request.declaredRelativePath ?? request.declaredFilename;
589
- if (relativeRequest !== void 0 && !/^(?:[A-Za-z]:[\\/]|[\\/])/.test(relativeRequest)) {
590
- const normalizedRequest = normalizeSourceRelativePath(relativeRequest);
591
- if (normalizedRequest !== void 0) {
592
- const scopeExact = choose(
593
- uniqueMatches(candidatePaths, (path) => path === normalizedRequest),
594
- "exact"
595
- );
596
- if (scopeExact !== void 0) return scopeExact;
597
- const scopeFolded = choose(
598
- uniqueMatches(
599
- candidatePaths,
600
- (path) => path.toLowerCase() === normalizedRequest.toLowerCase()
601
- ),
602
- "case-folded"
603
- );
604
- if (scopeFolded !== void 0) return scopeFolded;
605
- }
606
- }
607
- const requestedSegments = requestSegments(requestedPath);
608
- if (requestedSegments.length > 0) {
609
- const scored = candidatePaths.map((path) => ({
610
- path,
611
- score: longestSuffixLength(requestedSegments, path.split("/"))
612
- }));
613
- const bestScore = Math.max(0, ...scored.map((entry) => entry.score));
614
- if (bestScore > 0) {
615
- const best = scored.filter((entry) => entry.score === bestScore).map((entry) => entry.path);
616
- const strategy = bestScore === 1 ? "basename" : "suffix";
617
- const suffix = choose(best, strategy);
618
- if (suffix !== void 0) return suffix;
619
- }
620
- }
621
- return {
622
- ok: false,
623
- code: "fbx-external-texture-missing",
624
- requestedPath,
625
- candidates: candidatePaths
626
- };
627
- }
628
-
629
- // ../../node_modules/.pnpm/uuidv7@1.2.1/node_modules/uuidv7/dist/index.js
630
- var DIGITS = "0123456789abcdef";
631
- var UUID = class _UUID {
632
- /** @param bytes - The 16-byte byte array representation. */
633
- constructor(bytes) {
634
- this.bytes = bytes;
635
- }
636
- /**
637
- * Creates an object from the internal representation, a 16-byte byte array
638
- * containing the binary UUID representation in the big-endian byte order.
639
- *
640
- * This method does NOT shallow-copy the argument, and thus the created object
641
- * holds the reference to the underlying buffer.
642
- *
643
- * @throws TypeError if the length of the argument is not 16.
644
- */
645
- static ofInner(bytes) {
646
- if (bytes.length !== 16) {
647
- throw new TypeError("not 128-bit length");
648
- } else {
649
- return new _UUID(bytes);
650
- }
651
- }
652
- /**
653
- * Builds a byte array from UUIDv7 field values.
654
- *
655
- * @param unixTsMs - A 48-bit `unix_ts_ms` field value.
656
- * @param randA - A 12-bit `rand_a` field value.
657
- * @param randBHi - The higher 30 bits of 62-bit `rand_b` field value.
658
- * @param randBLo - The lower 32 bits of 62-bit `rand_b` field value.
659
- * @throws RangeError if any field value is out of the specified range.
660
- */
661
- static fromFieldsV7(unixTsMs, randA, randBHi, randBLo) {
662
- if (!Number.isInteger(unixTsMs) || !Number.isInteger(randA) || !Number.isInteger(randBHi) || !Number.isInteger(randBLo) || unixTsMs < 0 || randA < 0 || randBHi < 0 || randBLo < 0 || unixTsMs > 281474976710655 || randA > 4095 || randBHi > 1073741823 || randBLo > 4294967295) {
663
- throw new RangeError("invalid field value");
664
- }
665
- const bytes = new Uint8Array(16);
666
- bytes[0] = unixTsMs / 2 ** 40;
667
- bytes[1] = unixTsMs / 2 ** 32;
668
- bytes[2] = unixTsMs / 2 ** 24;
669
- bytes[3] = unixTsMs / 2 ** 16;
670
- bytes[4] = unixTsMs / 2 ** 8;
671
- bytes[5] = unixTsMs;
672
- bytes[6] = 112 | randA >>> 8;
673
- bytes[7] = randA;
674
- bytes[8] = 128 | randBHi >>> 24;
675
- bytes[9] = randBHi >>> 16;
676
- bytes[10] = randBHi >>> 8;
677
- bytes[11] = randBHi;
678
- bytes[12] = randBLo >>> 24;
679
- bytes[13] = randBLo >>> 16;
680
- bytes[14] = randBLo >>> 8;
681
- bytes[15] = randBLo;
682
- return new _UUID(bytes);
683
- }
684
- /**
685
- * Builds a byte array from a string representation.
686
- *
687
- * This method accepts the following formats:
688
- *
689
- * - 32-digit hexadecimal format without hyphens: `0189dcd553117d408db09496a2eef37b`
690
- * - 8-4-4-4-12 hyphenated format: `0189dcd5-5311-7d40-8db0-9496a2eef37b`
691
- * - Hyphenated format with surrounding braces: `{0189dcd5-5311-7d40-8db0-9496a2eef37b}`
692
- * - RFC 9562 URN format: `urn:uuid:0189dcd5-5311-7d40-8db0-9496a2eef37b`
693
- *
694
- * Leading and trailing whitespaces represents an error.
695
- *
696
- * @throws SyntaxError if the argument could not parse as a valid UUID string.
697
- */
698
- static parse(uuid) {
699
- var _a, _b, _c, _d;
700
- let hex = void 0;
701
- switch (uuid.length) {
702
- case 32:
703
- hex = (_a = /^[0-9a-f]{32}$/i.exec(uuid)) === null || _a === void 0 ? void 0 : _a[0];
704
- break;
705
- case 36:
706
- hex = (_b = /^([0-9a-f]{8})-([0-9a-f]{4})-([0-9a-f]{4})-([0-9a-f]{4})-([0-9a-f]{12})$/i.exec(uuid)) === null || _b === void 0 ? void 0 : _b.slice(1, 6).join("");
707
- break;
708
- case 38:
709
- hex = (_c = /^\{([0-9a-f]{8})-([0-9a-f]{4})-([0-9a-f]{4})-([0-9a-f]{4})-([0-9a-f]{12})\}$/i.exec(uuid)) === null || _c === void 0 ? void 0 : _c.slice(1, 6).join("");
710
- break;
711
- case 45:
712
- hex = (_d = /^urn:uuid:([0-9a-f]{8})-([0-9a-f]{4})-([0-9a-f]{4})-([0-9a-f]{4})-([0-9a-f]{12})$/i.exec(uuid)) === null || _d === void 0 ? void 0 : _d.slice(1, 6).join("");
713
- break;
714
- }
715
- if (hex) {
716
- const inner = new Uint8Array(16);
717
- for (let i = 0; i < 16; i += 4) {
718
- const n = parseInt(hex.substring(2 * i, 2 * i + 8), 16);
719
- inner[i + 0] = n >>> 24;
720
- inner[i + 1] = n >>> 16;
721
- inner[i + 2] = n >>> 8;
722
- inner[i + 3] = n;
723
- }
724
- return new _UUID(inner);
725
- } else {
726
- throw new SyntaxError("could not parse UUID string");
727
- }
728
- }
729
- /**
730
- * @returns The 8-4-4-4-12 canonical hexadecimal string representation
731
- * (`0189dcd5-5311-7d40-8db0-9496a2eef37b`).
732
- */
733
- toString() {
734
- let text = "";
735
- for (let i = 0; i < this.bytes.length; i++) {
736
- text += DIGITS.charAt(this.bytes[i] >>> 4);
737
- text += DIGITS.charAt(this.bytes[i] & 15);
738
- if (i === 3 || i === 5 || i === 7 || i === 9) {
739
- text += "-";
740
- }
741
- }
742
- return text;
743
- }
744
- /**
745
- * @returns The 32-digit hexadecimal representation without hyphens
746
- * (`0189dcd553117d408db09496a2eef37b`).
747
- */
748
- toHex() {
749
- let text = "";
750
- for (let i = 0; i < this.bytes.length; i++) {
751
- text += DIGITS.charAt(this.bytes[i] >>> 4);
752
- text += DIGITS.charAt(this.bytes[i] & 15);
753
- }
754
- return text;
755
- }
756
- /** @returns The 8-4-4-4-12 canonical hexadecimal string representation. */
757
- toJSON() {
758
- return this.toString();
759
- }
760
- /**
761
- * Reports the variant field value of the UUID or, if appropriate, "NIL" or
762
- * "MAX".
763
- *
764
- * For convenience, this method reports "NIL" or "MAX" if `this` represents
765
- * the Nil or Max UUID, although the Nil and Max UUIDs are technically
766
- * subsumed under the variants `0b0` and `0b111`, respectively.
767
- */
768
- getVariant() {
769
- const n = this.bytes[8] >>> 4;
770
- if (n < 0) {
771
- throw new Error("unreachable");
772
- } else if (n <= 7) {
773
- return this.isNil() ? "NIL" : "VAR_0";
774
- } else if (n <= 11) {
775
- return "VAR_10";
776
- } else if (n <= 13) {
777
- return "VAR_110";
778
- } else if (n <= 15) {
779
- return this.isMax() ? "MAX" : "VAR_RESERVED";
780
- } else {
781
- throw new Error("unreachable");
782
- }
783
- }
784
- /**
785
- * Returns the version field value of the UUID or `undefined` if the UUID does
786
- * not have the variant field value of `0b10`.
787
- */
788
- getVersion() {
789
- return this.getVariant() === "VAR_10" ? this.bytes[6] >>> 4 : void 0;
790
- }
791
- /** Returns `true` if `this` is the Nil UUID. */
792
- isNil() {
793
- return this.bytes.every((e) => e === 0);
794
- }
795
- /** Returns `true` if `this` is the Max UUID. */
796
- isMax() {
797
- return this.bytes.every((e) => e === 255);
798
- }
799
- /** Creates an object from `this`. */
800
- clone() {
801
- return new _UUID(this.bytes.slice(0));
802
- }
803
- /** Returns true if `this` is equivalent to `other`. */
804
- equals(other) {
805
- return this.compareTo(other) === 0;
806
- }
807
- /**
808
- * Returns a negative integer, zero, or positive integer if `this` is less
809
- * than, equal to, or greater than `other`, respectively.
810
- */
811
- compareTo(other) {
812
- for (let i = 0; i < 16; i++) {
813
- const diff = this.bytes[i] - other.bytes[i];
814
- if (diff !== 0) {
815
- return Math.sign(diff);
816
- }
817
- }
818
- return 0;
819
- }
820
- };
821
- var V7Generator = class {
822
- /**
823
- * Creates a generator object with the default random number generator, or
824
- * with the specified one if passed as an argument. The specified random
825
- * number generator should be cryptographically strong and securely seeded.
826
- */
827
- constructor(randomNumberGenerator) {
828
- this.timestampBiased = 0;
829
- this.counter = 0;
830
- this.rollbackAllowance = 1e4;
831
- this.random = randomNumberGenerator !== null && randomNumberGenerator !== void 0 ? randomNumberGenerator : getDefaultRandom();
832
- }
833
- /**
834
- * Sets the `rollbackAllowance` parameter of the generator.
835
- *
836
- * The `rollbackAllowance` parameter specifies the amount of `unixTsMs`
837
- * rollback that is considered significant. The default value is `10_000`
838
- * (milliseconds). See the {@link generate} or {@link generateOrAbort}
839
- * documentation for the treatment of the significant rollback.
840
- *
841
- */
842
- setRollbackAllowance(rollbackAllowance) {
843
- if (rollbackAllowance < 0 || rollbackAllowance > 281474976710655) {
844
- throw new RangeError("`rollbackAllowance` out of reasonable range");
845
- }
846
- this.rollbackAllowance = rollbackAllowance;
847
- }
848
- /**
849
- * Generates a new UUIDv7 object from the current timestamp, or resets the
850
- * generator upon significant timestamp rollback.
851
- *
852
- * This method returns a monotonically increasing UUID by reusing the previous
853
- * timestamp even if the up-to-date timestamp is smaller than the immediately
854
- * preceding UUID's. However, when such a clock rollback is considered
855
- * significant (by default, more than ten seconds), this method resets the
856
- * generator and returns a new UUID based on the given timestamp, breaking the
857
- * increasing order of UUIDs.
858
- *
859
- * See {@link generateOrAbort} for the other mode of generation and
860
- * {@link generateOrResetWithTs} for the variant accepting a custom timestamp.
861
- */
862
- generate() {
863
- return this.generateOrResetWithTs(Date.now());
864
- }
865
- /**
866
- * Generates a new UUIDv7 object from the current timestamp, or returns
867
- * `undefined` upon significant timestamp rollback.
868
- *
869
- * This method returns a monotonically increasing UUID by reusing the previous
870
- * timestamp even if the up-to-date timestamp is smaller than the immediately
871
- * preceding UUID's. However, when such a clock rollback is considered
872
- * significant (by default, more than ten seconds), this method aborts and
873
- * returns `undefined` immediately.
874
- *
875
- * See {@link generate} for the other mode of generation and
876
- * {@link generateOrAbortWithTs} for the variant accepting a custom timestamp.
877
- */
878
- generateOrAbort() {
879
- return this.generateOrAbortWithTs(Date.now());
880
- }
881
- /**
882
- * Generates a new UUIDv7 object from the `unixTsMs` passed, or resets the
883
- * generator upon significant timestamp rollback.
884
- *
885
- * This method is equivalent to {@link generate} except that it takes a custom
886
- * timestamp.
887
- *
888
- * @throws RangeError if `unixTsMs` is not a 48-bit unsigned integer.
889
- */
890
- generateOrResetWithTs(unixTsMs) {
891
- let value = this.generateOrAbortWithTs(unixTsMs);
892
- if (value === void 0) {
893
- this.timestampBiased = 0;
894
- value = this.generateOrAbortWithTs(unixTsMs);
895
- }
896
- return value;
897
- }
898
- /**
899
- * Generates a new UUIDv7 object from the `unixTsMs` passed, or returns
900
- * `undefined` upon significant timestamp rollback.
901
- *
902
- * This method is equivalent to {@link generateOrAbort} except that it takes a
903
- * custom timestamp.
904
- *
905
- * @throws RangeError if `unixTsMs` is not a 48-bit unsigned integer.
906
- */
907
- generateOrAbortWithTs(unixTsMs) {
908
- const MAX_COUNTER = 4398046511103;
909
- if (!Number.isInteger(unixTsMs) || unixTsMs < 0 || unixTsMs > 281474976710655) {
910
- throw new RangeError("`unixTsMs` must be a 48-bit unsigned integer");
911
- }
912
- unixTsMs++;
913
- if (unixTsMs > this.timestampBiased) {
914
- this.timestampBiased = unixTsMs;
915
- this.resetCounter();
916
- } else if (unixTsMs + this.rollbackAllowance >= this.timestampBiased) {
917
- this.counter++;
918
- if (this.counter > MAX_COUNTER) {
919
- this.timestampBiased++;
920
- this.resetCounter();
921
- }
922
- } else {
923
- return void 0;
924
- }
925
- return UUID.fromFieldsV7(this.timestampBiased - 1, Math.trunc(this.counter / 2 ** 30), this.counter & 2 ** 30 - 1, this.random.nextUint32());
926
- }
927
- /**
928
- * Generates a new UUIDv7 object from the `unixTsMs` passed, or resets the
929
- * generator upon significant timestamp rollback.
930
- *
931
- * This method is a deprecated version of {@link generateOrResetWithTs} that
932
- * accepts the `rollbackAllowance` parameter as an argument, rather than using
933
- * the generator-level parameter.
934
- *
935
- * @param rollbackAllowance - The amount of `unixTsMs` rollback that is
936
- * considered significant. A suggested value is `10_000` (milliseconds).
937
- * @throws RangeError if `unixTsMs` is not a 48-bit unsigned integer.
938
- * @deprecated Since v1.2.0. Use {@link generateOrResetWithTs} instead.
939
- */
940
- generateOrResetCore(unixTsMs, rollbackAllowance) {
941
- const origRollbackAllowance = this.rollbackAllowance;
942
- try {
943
- this.setRollbackAllowance(rollbackAllowance);
944
- return this.generateOrResetWithTs(unixTsMs);
945
- } catch (e) {
946
- throw e;
947
- } finally {
948
- this.rollbackAllowance = origRollbackAllowance;
949
- }
950
- }
951
- /**
952
- * Generates a new UUIDv7 object from the `unixTsMs` passed, or returns
953
- * `undefined` upon significant timestamp rollback.
954
- *
955
- * This method is a deprecated version of {@link generateOrAbortWithTs} that
956
- * accepts the `rollbackAllowance` parameter as an argument, rather than using
957
- * the generator-level parameter.
958
- *
959
- * @param rollbackAllowance - The amount of `unixTsMs` rollback that is
960
- * considered significant. A suggested value is `10_000` (milliseconds).
961
- * @throws RangeError if `unixTsMs` is not a 48-bit unsigned integer.
962
- * @deprecated Since v1.2.0. Use {@link generateOrAbortWithTs} instead.
963
- */
964
- generateOrAbortCore(unixTsMs, rollbackAllowance) {
965
- const origRollbackAllowance = this.rollbackAllowance;
966
- try {
967
- this.setRollbackAllowance(rollbackAllowance);
968
- return this.generateOrAbortWithTs(unixTsMs);
969
- } catch (e) {
970
- throw e;
971
- } finally {
972
- this.rollbackAllowance = origRollbackAllowance;
973
- }
974
- }
975
- /** Initializes the counter at a 42-bit random integer. */
976
- resetCounter() {
977
- this.counter = this.random.nextUint32() * 1024 + (this.random.nextUint32() & 1023);
978
- }
979
- /**
980
- * Generates a new UUIDv4 object utilizing the random number generator inside.
981
- *
982
- * @internal
983
- */
984
- generateV4() {
985
- const bytes = new Uint8Array(Uint32Array.of(this.random.nextUint32(), this.random.nextUint32(), this.random.nextUint32(), this.random.nextUint32()).buffer);
986
- bytes[6] = 64 | bytes[6] >>> 4;
987
- bytes[8] = 128 | bytes[8] >>> 2;
988
- return UUID.ofInner(bytes);
989
- }
990
- };
991
- var getDefaultRandom = () => {
992
- if (typeof crypto !== "undefined" && typeof crypto.getRandomValues !== "undefined") {
993
- return new BufferedCryptoRandom();
994
- } else {
995
- if (typeof UUIDV7_DENY_WEAK_RNG !== "undefined" && UUIDV7_DENY_WEAK_RNG) {
996
- throw new Error("no cryptographically strong RNG available");
997
- }
998
- return {
999
- nextUint32: () => Math.trunc(Math.random() * 65536) * 65536 + Math.trunc(Math.random() * 65536)
1000
- };
1001
- }
1002
- };
1003
- var BufferedCryptoRandom = class {
1004
- constructor() {
1005
- this.buffer = new Uint32Array(8);
1006
- this.cursor = 65535;
1007
- }
1008
- nextUint32() {
1009
- if (this.cursor >= this.buffer.length) {
1010
- crypto.getRandomValues(this.buffer);
1011
- this.cursor = 0;
1012
- }
1013
- return this.buffer[this.cursor++];
1014
- }
1015
- };
1016
- var defaultGenerator;
1017
- var uuidv7obj = () => (defaultGenerator || (defaultGenerator = new V7Generator())).generate();
1018
506
  var ATTRIBUTE_FORMAT_MAP = {
1019
507
  position: "float32x3",
1020
508
  normal: "float32x3",
@@ -1084,117 +572,6 @@ function deriveVertexLayoutProjection(map) {
1084
572
  digest: fnv1a(digestInput)
1085
573
  });
1086
574
  }
1087
-
1088
- // ../pack/dist/guid.mjs
1089
- var PackError = class extends Error {
1090
- code;
1091
- expected;
1092
- hint;
1093
- detail;
1094
- constructor(args) {
1095
- super(`[PackError ${args.code}] expected: ${args.expected}; hint: ${args.hint}`);
1096
- this.name = "PackError";
1097
- this.code = args.code;
1098
- this.expected = args.expected;
1099
- this.hint = args.hint;
1100
- this.detail = args.detail;
1101
- }
1102
- };
1103
- function brand(bytes) {
1104
- return bytes;
1105
- }
1106
- var HEX_BYTE = Array.from({ length: 256 }, (_, i) => i.toString(16).padStart(2, "0"));
1107
- function bytesToDashForm(bytes) {
1108
- const h = HEX_BYTE;
1109
- return `${h[bytes[0]]}${h[bytes[1]]}${h[bytes[2]]}${h[bytes[3]]}-${h[bytes[4]]}${h[bytes[5]]}-${h[bytes[6]]}${h[bytes[7]]}-${h[bytes[8]]}${h[bytes[9]]}-${h[bytes[10]]}${h[bytes[11]]}${h[bytes[12]]}${h[bytes[13]]}${h[bytes[14]]}${h[bytes[15]]}`;
1110
- }
1111
- var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
1112
- function isValidAssetGuidString(value) {
1113
- return typeof value === "string" && UUID_RE.test(value);
1114
- }
1115
- function dashFormToBytes(dashForm) {
1116
- const hex = dashForm.replace(/-/g, "");
1117
- const bytes = new Uint8Array(16);
1118
- for (let i = 0; i < 16; i++) {
1119
- bytes[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
1120
- }
1121
- return bytes;
1122
- }
1123
- var AssetGuid = {
1124
- /**
1125
- * Parse a 36-char RFC 4122 dash-form UUID string into an AssetGuid.
1126
- * Returns Ok(AssetGuid) on success or Err(PackError) with code 'pack-guid-malformed' on failure.
1127
- * Never throws for expected failures (requirements §4.2 / §14 / charter proposition 4).
1128
- */
1129
- parse(dashForm) {
1130
- if (!isValidAssetGuidString(dashForm)) {
1131
- return {
1132
- ok: false,
1133
- error: new PackError({
1134
- code: "pack-guid-malformed",
1135
- expected: "36-char RFC 4122 dash-form UUID",
1136
- hint: "use AssetGuid.random() or a UUIDv7 generator; all GUID fields must be 36-char RFC 4122 dash-form",
1137
- detail: {
1138
- raw: dashForm,
1139
- reason: "expected 36-char RFC 4122 dash-form UUID"
1140
- }
1141
- })
1142
- };
1143
- }
1144
- return { ok: true, value: brand(dashFormToBytes(dashForm)) };
1145
- },
1146
- /**
1147
- * Format an AssetGuid as a 36-char RFC 4122 lowercase dash-form string.
1148
- */
1149
- format(guid) {
1150
- return bytesToDashForm(guid);
1151
- },
1152
- /**
1153
- * Test byte-by-byte equality between two AssetGuids.
1154
- */
1155
- equals(a, b) {
1156
- for (let i = 0; i < 16; i++) {
1157
- if (a[i] !== b[i]) return false;
1158
- }
1159
- return true;
1160
- },
1161
- /**
1162
- * Mint a new time-ordered UUIDv7 as an AssetGuid.
1163
- * Works in both Node.js and browser environments.
1164
- */
1165
- random() {
1166
- const uuid = uuidv7obj();
1167
- const bytes = new Uint8Array(16);
1168
- bytes.set(uuid.bytes);
1169
- return brand(bytes);
1170
- }
1171
- };
1172
-
1173
- // ../pack/dist/mesh-bin-contract.mjs
1174
- var MESH_BIN_HEADER_V4_BYTES = 80;
1175
- var MESH_BIN_DIGEST_BYTES = 32;
1176
- function digestBytes(digest) {
1177
- const bytes = new Uint8Array(MESH_BIN_DIGEST_BYTES);
1178
- bytes.set(new TextEncoder().encode(digest).subarray(0, MESH_BIN_DIGEST_BYTES));
1179
- return bytes;
1180
- }
1181
- function writeMeshBinHeader(header, out) {
1182
- if (out.byteLength < MESH_BIN_HEADER_V4_BYTES) {
1183
- throw new RangeError("mesh-bin v4 header output is truncated");
1184
- }
1185
- const view = new DataView(out.buffer, out.byteOffset, out.byteLength);
1186
- view.setUint32(0, header.version, true);
1187
- view.setUint32(4, header.projectionVersion, true);
1188
- view.setUint32(8, header.mask, true);
1189
- view.setUint32(12, header.stride, true);
1190
- view.setUint32(16, header.vertexCount, true);
1191
- view.setUint32(20, header.vertexBytes, true);
1192
- view.setUint32(24, header.indexCount, true);
1193
- view.setUint32(28, header.indexWidth, true);
1194
- view.setUint32(32, header.indexBytes, true);
1195
- view.setUint32(36, header.jsonBytes, true);
1196
- out.set(digestBytes(header.digest), 48);
1197
- }
1198
575
  function failure(sourceKey, expected, actual) {
1199
576
  return {
1200
577
  code: "mesh-bin-payload-invalid",
@@ -1596,44 +973,7 @@ function buildMeshAsset(pod, guid, influences, materialContext = {}) {
1596
973
  }
1597
974
  };
1598
975
  }
1599
- function materialBindings(pod) {
1600
- if (pod.textureBindings !== void 0) return pod.textureBindings;
1601
- const bindings = [];
1602
- const legacy = [
1603
- ["baseColorTexture", pod.baseColorTextureIndex],
1604
- ["metallicRoughnessTexture", pod.metallicRoughnessTextureIndex],
1605
- ["normalTexture", pod.normalTextureIndex],
1606
- ["specularTintTexture", pod.specularTintTextureIndex],
1607
- ["emissiveTexture", pod.emissiveTextureIndex],
1608
- ["occlusionTexture", pod.occlusionTextureIndex]
1609
- ];
1610
- for (const [slot, textureIndex] of legacy) {
1611
- if (textureIndex !== void 0) bindings.push({ slot, textureIndex });
1612
- }
1613
- return bindings;
1614
- }
1615
- function buildMaterialAsset(pod, guid, skinned = false, textureGuidByIndex = /* @__PURE__ */ new Map()) {
1616
- const values = {
1617
- baseColor: pod.baseColorFactor,
1618
- metallic: pod.metallicFactor,
1619
- roughness: pod.roughnessFactor
1620
- };
1621
- const refs = [];
1622
- for (const binding of materialBindings(pod)) {
1623
- const textureGuid = textureGuidByIndex.get(binding.textureIndex);
1624
- if (textureGuid === void 0) continue;
1625
- const textureValue = {
1626
- // Runtime material loading interprets texture handles as indexes into
1627
- // this asset's refs[]; the GUID is carried by the corresponding edge.
1628
- texture: refs.length,
1629
- ...binding.texCoord === void 0 ? {} : { coordinates: { set: binding.texCoord } }
1630
- };
1631
- values[binding.slot] = textureValue;
1632
- refs.push({
1633
- guid: textureGuid,
1634
- sourceField: { componentName: "<material>", fieldName: binding.slot }
1635
- });
1636
- }
976
+ function buildMaterialAsset(pod, guid, skinned = false) {
1637
977
  const mat = {
1638
978
  kind: "material",
1639
979
  colorSpace: "linear",
@@ -1644,14 +984,18 @@ function buildMaterialAsset(pod, guid, skinned = false, textureGuidByIndex = /*
1644
984
  renderState: { tags: { LightMode: "Forward" }, queue: 2e3 }
1645
985
  }
1646
986
  ],
1647
- values
987
+ values: {
988
+ baseColor: pod.baseColorFactor,
989
+ metallic: pod.metallicFactor,
990
+ roughness: pod.roughnessFactor
991
+ }
1648
992
  };
1649
993
  return {
1650
994
  guid,
1651
995
  kind: "material",
1652
996
  ...pod.name !== void 0 ? { name: pod.name } : {},
1653
997
  payload: mat,
1654
- refs,
998
+ refs: [],
1655
999
  artifacts: {}
1656
1000
  };
1657
1001
  }
@@ -1721,29 +1065,19 @@ function buildSceneAsset(pod, guid, ctx) {
1721
1065
  artifacts: {}
1722
1066
  };
1723
1067
  }
1724
- function buildTextureAsset(pod, guid, decoded) {
1068
+ function buildTextureNote(_pod, _guid) {
1725
1069
  return {
1726
- guid,
1070
+ guid: _guid,
1727
1071
  kind: "texture",
1728
- ...pod.name !== void 0 ? { name: pod.name } : {},
1729
- payload: decoded.texture,
1072
+ ..._pod.name !== void 0 ? { name: _pod.name } : {},
1073
+ payload: {},
1730
1074
  refs: [],
1731
- artifacts: {
1732
- body: {
1733
- mediaType: decoded.mediaType?.startsWith("image/ktx2") ? decoded.mediaType : "application/x-forgeax-rgba8",
1734
- ...decoded.assetCodec === void 0 ? {} : { assetCodec: decoded.assetCodec },
1735
- bytes: decoded.bytes
1736
- }
1737
- }
1075
+ artifacts: {}
1738
1076
  };
1739
1077
  }
1740
1078
  function toAssetPack(params) {
1741
1079
  const assets = [];
1742
1080
  const guidOf = makeGuidResolver(params.subAssets);
1743
- const textureGuidByIndex = /* @__PURE__ */ new Map();
1744
- for (const texture of params.subAssets) {
1745
- if (texture.kind === "texture") textureGuidByIndex.set(texture.sourceIndex, texture.guid);
1746
- }
1747
1081
  const materialGuidByIndex = /* @__PURE__ */ new Map();
1748
1082
  const materialNameByIndex = /* @__PURE__ */ new Map();
1749
1083
  const materialSourceKeyByIndex = /* @__PURE__ */ new Map();
@@ -1793,19 +1127,11 @@ function toAssetPack(params) {
1793
1127
  const mat = params.materials[i];
1794
1128
  if (!mat) continue;
1795
1129
  const guid = guidOf("material", i);
1796
- if (guid !== void 0) {
1797
- assets.push(buildMaterialAsset(mat, guid, hasSkin, textureGuidByIndex));
1798
- }
1130
+ if (guid !== void 0) assets.push(buildMaterialAsset(mat, guid, hasSkin));
1799
1131
  }
1800
1132
  for (const tex of params.textures) {
1801
1133
  const guid = guidOf("texture", tex.sourceIndex);
1802
- if (guid !== void 0) {
1803
- const decoded = params.decodedTextures?.get(tex.sourceIndex);
1804
- if (decoded === void 0) {
1805
- throw new Error(`fbx texture ${tex.sourceIndex} was declared but not decoded`);
1806
- }
1807
- assets.push(buildTextureAsset(tex, guid, decoded));
1808
- }
1134
+ if (guid !== void 0) assets.push(buildTextureNote(tex, guid));
1809
1135
  }
1810
1136
  const declaredByKind = (kind) => params.subAssets.filter((s) => s.kind === kind).slice().sort((a, b) => a.sourceIndex - b.sourceIndex).map((s) => s.guid);
1811
1137
  const meshGuids = declaredByKind("mesh");
@@ -1915,8 +1241,6 @@ function toAssetPack(params) {
1915
1241
  function sourceKeyForFbxOutput(output) {
1916
1242
  const kind = output.kind.trim();
1917
1243
  if (kind.length === 0) return void 0;
1918
- const explicit = output.sourceKey?.trim();
1919
- if (explicit !== void 0 && explicit.length > 0) return explicit;
1920
1244
  const name = output.name?.trim();
1921
1245
  return name === void 0 || name.length === 0 ? `fbx:${kind}` : `fbx:${kind}:${name}`;
1922
1246
  }
@@ -1936,96 +1260,18 @@ function deriveFbxSourceKeys(outputs) {
1936
1260
  }
1937
1261
  return { ok: true, keys };
1938
1262
  }
1939
- function validationError(ctx, textureIndex, code, actual, hint) {
1940
- const diagnostic = {
1941
- code,
1942
- severity: "error",
1943
- sourcePath: `${ctx.source}#textures[${textureIndex}]`,
1944
- sourceRange: { start: 0, end: 0, line: 1, column: 1 },
1945
- rule: "fbx-external-texture-dependency-closure",
1946
- expected: "every bound FBX texture resolves to one readable supported image in the authorized scope",
1947
- actual,
1948
- hint
1949
- };
1950
- return new ImportError({
1951
- code: "source-validation-failed",
1952
- expected: diagnostic.expected,
1953
- hint: IMPORT_ERROR_HINTS["source-validation-failed"],
1954
- detail: { diagnostics: [diagnostic] }
1955
- });
1956
- }
1957
- var FBX_PARSE_DIAGNOSTIC_MAX = 256;
1958
- var FBX_PARSE_EXPECTED = "a readable FBX source that ufbx can parse";
1959
- function boundedErrorMessage(error) {
1960
- const message = error instanceof Error ? error.message : String(error);
1961
- return message.length <= FBX_PARSE_DIAGNOSTIC_MAX ? message : `${message.slice(0, FBX_PARSE_DIAGNOSTIC_MAX - 3)}...`;
1962
- }
1963
- function parseValidationError(ctx, error) {
1964
- const actual = boundedErrorMessage(error);
1965
- const diagnostic = {
1966
- code: "fbx-source-parse-failed",
1967
- severity: "error",
1968
- sourcePath: ctx.source,
1969
- sourceRange: { start: 0, end: 0, line: 1, column: 1 },
1970
- rule: "fbx-source-parse",
1971
- expected: FBX_PARSE_EXPECTED,
1972
- actual,
1973
- hint: "repair or re-export the FBX source, then retry the same importer and source path"
1974
- };
1975
- return new ImportError({
1976
- code: "source-validation-failed",
1977
- expected: FBX_PARSE_EXPECTED,
1978
- actual,
1979
- hint: IMPORT_ERROR_HINTS["source-validation-failed"],
1980
- detail: { diagnostics: [diagnostic] }
1981
- });
1982
- }
1983
- function importCandidates(ctx) {
1984
- const raw = ctx.importSettings.fbxCandidatePaths;
1985
- if (!Array.isArray(raw)) return [];
1986
- return raw.flatMap(
1987
- (path) => typeof path === "string" ? [{ relativePath: path }] : []
1988
- );
1989
- }
1990
- function mimeForTexture(path, bytes) {
1991
- const lower = path.toLowerCase();
1992
- if (lower.endsWith(".png") || bytes[0] === 137 && bytes[1] === 80 && bytes[2] === 78) {
1993
- return "image/png";
1994
- }
1995
- if (lower.endsWith(".jpg") || lower.endsWith(".jpeg") || bytes[0] === 255 && bytes[1] === 216) {
1996
- return "image/jpeg";
1997
- }
1998
- return "image/x-tga";
1999
- }
2000
- function textureColorSpace(slot) {
2001
- return slot === "normalTexture" || slot === "metallicRoughnessTexture" || slot === "occlusionTexture" ? "linear" : "srgb";
2002
- }
2003
1263
  var fbxImporter = {
2004
1264
  key: "fbx",
2005
1265
  async import(ctx) {
2006
1266
  const read = await ctx.readSource();
2007
1267
  if (!read.ok) {
2008
- return {
2009
- ok: false,
2010
- error: new ImportError({
2011
- code: "source-read-failed",
2012
- expected: `readable source file at meta.source "${ctx.source}"`,
2013
- hint: IMPORT_ERROR_HINTS["source-read-failed"],
2014
- detail: {
2015
- source: ctx.source,
2016
- reason: read.error instanceof Error ? read.error.message : String(read.error)
2017
- }
2018
- })
2019
- };
1268
+ const wrapper = new Error(`fbx-source-unreadable: ${ctx.source}`);
1269
+ wrapper.cause = read.error;
1270
+ throw wrapper;
2020
1271
  }
2021
1272
  await initFbxWasm();
2022
- let doc;
2023
- try {
2024
- const jsonStr = parseFbx(read.value);
2025
- doc = JSON.parse(jsonStr);
2026
- } catch (error) {
2027
- return { ok: false, error: parseValidationError(ctx, error) };
2028
- }
1273
+ const jsonStr = parseFbx(read.value);
1274
+ const doc = JSON.parse(jsonStr);
2029
1275
  const maybeError = doc;
2030
1276
  if (maybeError.error?.code === "fbx-mesh-type-unsupported") {
2031
1277
  const e = fbxErr("fbx-mesh-type-unsupported", {
@@ -2057,101 +1303,6 @@ var fbxImporter = {
2057
1303
  throw wrapper;
2058
1304
  }
2059
1305
  const animationClips = parseAnimationClips(doc);
2060
- const textureSlots = /* @__PURE__ */ new Map();
2061
- for (const material of materials) {
2062
- for (const binding of material.textureBindings ?? []) {
2063
- const prior = textureSlots.get(binding.textureIndex);
2064
- const colorSpace = textureColorSpace(binding.slot);
2065
- if (prior !== void 0 && prior !== colorSpace) {
2066
- return {
2067
- ok: false,
2068
- error: validationError(
2069
- ctx,
2070
- binding.textureIndex,
2071
- "fbx-texture-color-space-conflict",
2072
- `texture ${binding.textureIndex} is used as both ${prior} and ${colorSpace}`,
2073
- "split the source texture or author one color-space semantic per FBX texture"
2074
- )
2075
- };
2076
- }
2077
- textureSlots.set(binding.textureIndex, colorSpace);
2078
- }
2079
- }
2080
- const decodedTextures = /* @__PURE__ */ new Map();
2081
- for (const texture of textures) {
2082
- const guidDeclared = ctx.subAssets.some(
2083
- (subAsset) => subAsset.kind === "texture" && subAsset.sourceIndex === texture.sourceIndex
2084
- );
2085
- if (!guidDeclared) continue;
2086
- let bytes;
2087
- let sourcePath = texture.filePath;
2088
- if (texture.embeddedBytes !== void 0 && texture.embeddedBytes.byteLength > 0) {
2089
- bytes = texture.embeddedBytes;
2090
- sourcePath = texture.relativeFilePath ?? texture.filePath;
2091
- } else {
2092
- if (texture.type !== void 0 && texture.type !== "file") {
2093
- return {
2094
- ok: false,
2095
- error: validationError(
2096
- ctx,
2097
- texture.sourceIndex,
2098
- "fbx-material-texture-slot-unsupported",
2099
- `texture type ${texture.type} is not a single file texture`,
2100
- "flatten layered/procedural FBX textures to a file texture before importing"
2101
- )
2102
- };
2103
- }
2104
- const resolution = resolveFbxTexturePath(
2105
- ctx.source,
2106
- {
2107
- ...texture.relativeFilePath === void 0 ? {} : { declaredRelativePath: texture.relativeFilePath },
2108
- ...texture.filePath.length === 0 ? {} : { declaredFilename: texture.filePath },
2109
- ...texture.absoluteFilePath === void 0 ? {} : { declaredAbsolutePath: texture.absoluteFilePath }
2110
- },
2111
- importCandidates(ctx)
2112
- );
2113
- if (!resolution.ok) {
2114
- return {
2115
- ok: false,
2116
- error: validationError(
2117
- ctx,
2118
- texture.sourceIndex,
2119
- resolution.code,
2120
- `${resolution.requestedPath}; candidates=${resolution.candidates.join(", ") || "<none>"}`,
2121
- resolution.code === "fbx-external-texture-ambiguous" ? "select a folder whose dependency path is unique; first-match guessing is disabled" : "select the containing folder so the FBX and its referenced texture are in the authorized candidate scope"
2122
- )
2123
- };
2124
- }
2125
- const read2 = await ctx.readSibling(resolution.readUri);
2126
- if (!read2.ok) return read2;
2127
- bytes = read2.value;
2128
- sourcePath = resolution.relativePath;
2129
- }
2130
- const slot = [...textureSlots.entries()].find(([index]) => index === texture.sourceIndex)?.[1] ?? "srgb";
2131
- const decoded = await ctx.decodeImage(bytes, mimeForTexture(sourcePath, bytes), {
2132
- ...ctx.importSettings,
2133
- colorSpace: slot
2134
- });
2135
- if (!decoded.ok) {
2136
- const reason = decoded.error.code === "image-decode-failed" ? decoded.error.detail.reason : `${decoded.error.code}: ${JSON.stringify(decoded.error.detail)}`;
2137
- return {
2138
- ok: false,
2139
- error: validationError(
2140
- ctx,
2141
- texture.sourceIndex,
2142
- "fbx-external-texture-decode-failed",
2143
- `${sourcePath}: ${reason}`,
2144
- "repair or re-export the referenced image, then retry the FBX import"
2145
- )
2146
- };
2147
- }
2148
- decodedTextures.set(texture.sourceIndex, {
2149
- texture: decoded.value.texture,
2150
- bytes: decoded.value.bytes,
2151
- ...decoded.value.mediaType === void 0 ? {} : { mediaType: decoded.value.mediaType },
2152
- ...decoded.value.assetCodec === void 0 ? {} : { assetCodec: decoded.value.assetCodec }
2153
- });
2154
- }
2155
1306
  return {
2156
1307
  ok: true,
2157
1308
  value: {
@@ -2164,7 +1315,6 @@ var fbxImporter = {
2164
1315
  skin,
2165
1316
  animationClips,
2166
1317
  subAssets: ctx.subAssets,
2167
- decodedTextures,
2168
1318
  ...ctx.sourceOverrides === void 0 ? {} : { sourceOverrides: ctx.sourceOverrides }
2169
1319
  }),
2170
1320
  sourceDependencies: []
@@ -2173,6 +1323,106 @@ var fbxImporter = {
2173
1323
  }
2174
1324
  };
2175
1325
 
1326
+ // src/resolve-texture-path.ts
1327
+ function normalizeSourceRelativePath(raw) {
1328
+ const value = raw.replaceAll("\\", "/");
1329
+ if (value.startsWith("/") || /^[A-Za-z]:\//.test(value) || value.startsWith("//"))
1330
+ return void 0;
1331
+ const parts = [];
1332
+ for (const part of value.split("/")) {
1333
+ if (part === "" || part === ".") continue;
1334
+ if (part === "..") {
1335
+ if (parts.at(-1) !== void 0 && parts.at(-1) !== "..") parts.pop();
1336
+ else parts.push("..");
1337
+ } else {
1338
+ parts.push(part);
1339
+ }
1340
+ }
1341
+ return parts.join("/");
1342
+ }
1343
+ function requestSegments(raw) {
1344
+ const value = raw.replaceAll("\\", "/").replace(/^[A-Za-z]:\//, "").replace(/^\/+/, "");
1345
+ return value.split("/").filter((part) => part !== "" && part !== ".");
1346
+ }
1347
+ function uniqueMatches(paths, predicate) {
1348
+ return [...new Set(paths.filter(predicate))];
1349
+ }
1350
+ function longestSuffixLength(request, candidate) {
1351
+ let count = 0;
1352
+ while (count < request.length && count < candidate.length && request[request.length - 1 - count]?.toLowerCase() === candidate[candidate.length - 1 - count]?.toLowerCase()) {
1353
+ count++;
1354
+ }
1355
+ return count;
1356
+ }
1357
+ function resolveFbxTexturePath(sourcePath, request, candidates) {
1358
+ const candidatePaths = candidates.flatMap((candidate) => {
1359
+ const normalized = normalizeSourceRelativePath(candidate.relativePath);
1360
+ return normalized === void 0 || normalized.length === 0 ? [] : [normalized];
1361
+ });
1362
+ const requestedPath = request.declaredRelativePath ?? request.declaredFilename ?? request.declaredAbsolutePath ?? "";
1363
+ const choose = (matches, strategy) => {
1364
+ const unique = [...new Set(matches)];
1365
+ if (unique.length === 1) {
1366
+ const relativePath = unique[0];
1367
+ if (relativePath === void 0) return void 0;
1368
+ return {
1369
+ ok: true,
1370
+ relativePath,
1371
+ readUri: relativePath,
1372
+ strategy
1373
+ };
1374
+ }
1375
+ if (unique.length > 1) {
1376
+ return {
1377
+ ok: false,
1378
+ code: "fbx-external-texture-ambiguous",
1379
+ requestedPath,
1380
+ candidates: unique
1381
+ };
1382
+ }
1383
+ return void 0;
1384
+ };
1385
+ const relativeRequest = request.declaredRelativePath ?? request.declaredFilename;
1386
+ if (relativeRequest !== void 0 && !/^(?:[A-Za-z]:[\\/]|[\\/])/.test(relativeRequest)) {
1387
+ const normalizedRequest = normalizeSourceRelativePath(relativeRequest);
1388
+ if (normalizedRequest !== void 0) {
1389
+ const scopeExact = choose(
1390
+ uniqueMatches(candidatePaths, (path) => path === normalizedRequest),
1391
+ "exact"
1392
+ );
1393
+ if (scopeExact !== void 0) return scopeExact;
1394
+ const scopeFolded = choose(
1395
+ uniqueMatches(
1396
+ candidatePaths,
1397
+ (path) => path.toLowerCase() === normalizedRequest.toLowerCase()
1398
+ ),
1399
+ "case-folded"
1400
+ );
1401
+ if (scopeFolded !== void 0) return scopeFolded;
1402
+ }
1403
+ }
1404
+ const requestedSegments = requestSegments(requestedPath);
1405
+ if (requestedSegments.length > 0) {
1406
+ const scored = candidatePaths.map((path) => ({
1407
+ path,
1408
+ score: longestSuffixLength(requestedSegments, path.split("/"))
1409
+ }));
1410
+ const bestScore = Math.max(0, ...scored.map((entry) => entry.score));
1411
+ if (bestScore > 0) {
1412
+ const best = scored.filter((entry) => entry.score === bestScore).map((entry) => entry.path);
1413
+ const strategy = bestScore === 1 ? "basename" : "suffix";
1414
+ const suffix = choose(best, strategy);
1415
+ if (suffix !== void 0) return suffix;
1416
+ }
1417
+ }
1418
+ return {
1419
+ ok: false,
1420
+ code: "fbx-external-texture-missing",
1421
+ requestedPath,
1422
+ candidates: candidatePaths
1423
+ };
1424
+ }
1425
+
2176
1426
  // src/index.ts
2177
1427
  var wasmModule = null;
2178
1428
  var initPromise = null;
@@ -2243,18 +1493,6 @@ function parseFbxToObject(fbxBytes) {
2243
1493
  function isFbxWasmReady() {
2244
1494
  return wasmModule !== null;
2245
1495
  }
2246
- /*! Bundled license information:
2247
-
2248
- uuidv7/dist/index.js:
2249
- (**
2250
- * uuidv7: A JavaScript implementation of UUID version 7
2251
- *
2252
- * Copyright 2021-2026 LiosK
2253
- *
2254
- * @license Apache-2.0
2255
- * @packageDocumentation
2256
- *)
2257
- */
2258
1496
 
2259
1497
  export { FBX_ERROR_HINTS, deriveFbxSourceKeys, fbxErr, fbxImporter, initFbxWasm, isFbxWasmReady, parseAnimationClips, parseFbx, parseFbxToObject, parseMaterial, parseMesh, parseScene, parseSkeleton, parseSkin, parseTextures, resolveFbxTexturePath, sourceKeyForFbxOutput, toAssetPack };
2260
1498
  //# sourceMappingURL=index.mjs.map