@forgeax/engine-gltf 0.1.28 → 0.1.30

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.
@@ -1 +1 @@
1
- {"version":3,"file":"gltf-importer.d.ts","sourceRoot":"","sources":["../src/gltf-importer.ts"],"names":[],"mappings":"AAqCA,OAAO,KAAK,EAEV,QAAQ,EAIR,QAAQ,EAMT,MAAM,uBAAuB,CAAC;AAkB/B,OAAO,KAAK,EAAE,8BAA8B,EAAE,MAAM,qBAAqB,CAAC;AAC1E,OAAO,KAAK,EAAE,OAAO,EAAE,cAAc,EAAqB,MAAM,iBAAiB,CAAC;AAwTlF,iFAAiF;AACjF,wBAAgB,mBAAmB,CACjC,GAAG,EAAE,cAAc,EACnB,GAAG,EAAE,OAAO,EACZ,kBAAkB,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,EAC/C,kBAAkB,GAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAa,GAC1D,SAAS,QAAQ,EAAE,CA2CrB;AA4oBD;;;;;;;;;;;GAWG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,CAAC,EAAE,8BAA8B,GAAG,QAAQ,CAMrF;AAED,8EAA8E;AAC9E,eAAO,MAAM,YAAY,EAAE,QAA+B,CAAC"}
1
+ {"version":3,"file":"gltf-importer.d.ts","sourceRoot":"","sources":["../src/gltf-importer.ts"],"names":[],"mappings":"AAqCA,OAAO,KAAK,EAEV,QAAQ,EAIR,QAAQ,EAMT,MAAM,uBAAuB,CAAC;AAoB/B,OAAO,KAAK,EAAE,8BAA8B,EAAE,MAAM,qBAAqB,CAAC;AAC1E,OAAO,KAAK,EAAE,OAAO,EAAE,cAAc,EAAqB,MAAM,iBAAiB,CAAC;AAwUlF,iFAAiF;AACjF,wBAAgB,mBAAmB,CACjC,GAAG,EAAE,cAAc,EACnB,GAAG,EAAE,OAAO,EACZ,kBAAkB,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,EAC/C,kBAAkB,GAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAa,GAC1D,SAAS,QAAQ,EAAE,CA2CrB;AA6oBD;;;;;;;;;;;GAWG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,CAAC,EAAE,8BAA8B,GAAG,QAAQ,CAMrF;AAED,8EAA8E;AAC9E,eAAO,MAAM,YAAY,EAAE,QAA+B,CAAC"}
@@ -2,12 +2,125 @@ import { MeshoptDecoder } from 'meshoptimizer';
2
2
  import { deriveDefaultLodScreenCoverages } from '@forgeax/engine-import';
3
3
  import { packMeshBinV4 } from '@forgeax/engine-import/mesh-bin';
4
4
  import { AssetGuid } from '@forgeax/engine-pack/guid';
5
- import { ImportError, toShared, err, ok, reconcileMeshMaterialSlotTopology, IMPORT_ERROR_HINTS, resolveMeshMaterialSlotDefaultGuid, createMaterialError, standardMaterialParameters, STANDARD_MATERIAL_PARAM_SCHEMA, STANDARD_PHYSICAL_PARAMETER_NAMES, STANDARD_TRANSMISSION_PARAMETER_NAMES, STANDARD_LAYER_PARAMETER_GROUPS } from '@forgeax/engine-types';
5
+ import { ImportError, readConservativeAnimatedBounds, toShared, err, ok, reconcileMeshMaterialSlotTopology, IMPORT_ERROR_HINTS, resolveMeshMaterialSlotDefaultGuid, createMaterialError, standardMaterialParameters, STANDARD_MATERIAL_PARAM_SCHEMA, STANDARD_PHYSICAL_PARAMETER_NAMES, STANDARD_TRANSMISSION_PARAMETER_NAMES, STANDARD_LAYER_PARAMETER_GROUPS, parseConservativeAnimatedBounds } from '@forgeax/engine-types';
6
+ import { deriveConservativeAnimatedBounds } from '@forgeax/engine-animation/animated-bounds';
6
7
  import { computeTangentVec4, packInterleavedVertexAttributes } from '@forgeax/engine-geometry';
7
8
  import { box3, mat4, vec3, quat } from '@forgeax/engine-math';
8
9
  import { deriveAnimationTargetId } from '@forgeax/engine-animation/target-id';
9
10
 
10
11
  // src/importer-entry.ts
12
+
13
+ // src/node-path.ts
14
+ function buildNodeParentMap(nodes) {
15
+ const parents = /* @__PURE__ */ new Map();
16
+ for (let index = 0; index < nodes.length; index++) {
17
+ for (const child of nodes[index]?.children ?? []) parents.set(child, index);
18
+ }
19
+ return parents;
20
+ }
21
+ function resolveNamedNodePath(nodes, parents, nodeIndex) {
22
+ const reversed = [];
23
+ const visited = /* @__PURE__ */ new Set();
24
+ let current = nodeIndex;
25
+ while (current !== void 0) {
26
+ if (visited.has(current)) {
27
+ return { ok: false, reason: "hierarchy-cycle", nodeIndex: current };
28
+ }
29
+ visited.add(current);
30
+ const name = nodes[current]?.name;
31
+ if (name === void 0 || name.length === 0) {
32
+ return { ok: false, reason: "name-missing", nodeIndex: current };
33
+ }
34
+ reversed.push(name);
35
+ current = parents.get(current);
36
+ }
37
+ return { ok: true, value: reversed.reverse() };
38
+ }
39
+
40
+ // src/animated-bounds.ts
41
+ function deriveGltfAnimatedBounds(doc) {
42
+ const parents = buildNodeParentMap(doc.nodes);
43
+ const paths = doc.nodes.map((_, index) => {
44
+ const path = resolveNamedNodePath(doc.nodes, parents, index);
45
+ return path.ok ? path.value.join("/") : void 0;
46
+ });
47
+ const nodes = doc.nodes.map((node, index) => ({
48
+ ...node.transform,
49
+ parent: parents.get(index) ?? null
50
+ }));
51
+ const channels = doc.animationClips.flatMap(
52
+ (clip) => clip.channels.flatMap(
53
+ (channel) => channel.property === "weights" ? [] : [
54
+ {
55
+ node: channel.targetNodeIndex,
56
+ property: channel.property,
57
+ values: channel.sampler.output,
58
+ interpolation: channel.sampler.interpolation
59
+ }
60
+ ]
61
+ )
62
+ );
63
+ const primitiveStarts = [];
64
+ let offset = 0;
65
+ for (const count of doc.meshPrimitiveCount?.values() ?? doc.meshes.map(() => 1)) {
66
+ primitiveStarts.push(offset);
67
+ offset += count;
68
+ }
69
+ const skeletons = doc.skeletons.map((skeleton, skinIndex) => {
70
+ if (skeleton.bounds !== void 0) return skeleton;
71
+ const meshes = [];
72
+ for (let index = 0; index < doc.nodes.length; index++) {
73
+ const node = doc.nodes[index];
74
+ if (node === void 0) return skeleton;
75
+ if (node.skinIndex !== skinIndex || node.meshIndex === null) continue;
76
+ const start = primitiveStarts[node.meshIndex];
77
+ if (start === void 0) return skeleton;
78
+ for (const mesh of doc.meshes.slice(
79
+ start,
80
+ start + (doc.meshPrimitiveCount?.get(node.meshIndex) ?? 1)
81
+ )) {
82
+ if (mesh.joints0 === void 0 || mesh.weights0 === void 0) return skeleton;
83
+ let maxMorphWeight = Math.max(
84
+ 0,
85
+ ...Array.from(node.morphWeights ?? mesh.morphWeights ?? [], Math.abs)
86
+ );
87
+ for (const clip of doc.animationClips)
88
+ for (const channel of clip.channels)
89
+ if (channel.targetNodeIndex === index && channel.property === "weights")
90
+ for (const weight of channel.sampler.output)
91
+ maxMorphWeight = Math.max(maxMorphWeight, Math.abs(weight));
92
+ const morphExtent = Float32Array.from(
93
+ mesh.positions,
94
+ (_, component) => (mesh.morphTargets ?? []).reduce(
95
+ (sum, target) => sum + Math.abs(target.position?.[component] ?? 0) * maxMorphWeight,
96
+ 0
97
+ )
98
+ );
99
+ meshes.push({
100
+ node: index,
101
+ positions: mesh.positions,
102
+ joints: mesh.joints0,
103
+ weights: mesh.weights0,
104
+ morphExtent
105
+ });
106
+ }
107
+ }
108
+ const jointNodes = skeleton.jointPaths.map((path) => paths.indexOf(path));
109
+ if (jointNodes.some(
110
+ (node, index) => node < 0 || paths.lastIndexOf(skeleton.jointPaths[index]) !== node
111
+ ))
112
+ return skeleton;
113
+ const bounds = deriveConservativeAnimatedBounds({
114
+ nodes,
115
+ channels,
116
+ jointNodes,
117
+ inverseBindMatrices: skeleton.inverseBindMatrices,
118
+ meshes
119
+ });
120
+ return bounds === void 0 ? skeleton : { ...skeleton, bounds };
121
+ });
122
+ return { ...doc, skeletons };
123
+ }
11
124
  var GLTF_MESHOPT_MODES = ["ATTRIBUTES", "TRIANGLES", "INDICES"];
12
125
  var GLTF_MESHOPT_FILTERS = ["NONE", "OCTAHEDRAL", "QUATERNION", "EXPONENTIAL"];
13
126
  var gltfErrorPolicy = {
@@ -443,7 +556,7 @@ function composeMat4(out, tx, ty, tz, qx, qy, qz, qw, sx, sy, sz) {
443
556
  function gltfDocToSceneAsset(doc, ctx) {
444
557
  const sceneIr = doc.scenes[doc.defaultSceneIndex];
445
558
  const resultNodes = [];
446
- if (sceneIr === void 0) return { kind: "scene", entities: [] };
559
+ if (sceneIr === void 0) return { kind: "scene", entities: {} };
447
560
  const importedLights = doc.lights ?? doc.extensions?.KHR_lights_punctual?.lights ?? [];
448
561
  const animationTargetIds = /* @__PURE__ */ new Map();
449
562
  for (const clip of doc.animationClips) {
@@ -536,7 +649,10 @@ function gltfDocToSceneAsset(doc, ctx) {
536
649
  components.MeshRenderer = { materials: [] };
537
650
  }
538
651
  if (ir.instancing !== void 0) {
539
- components.Instances = { transforms: ir.instancing.transforms };
652
+ const collectionId = ctx.instanceCollectionIdsByNodeIndex?.get(gltfNodeIdx);
653
+ if (collectionId !== void 0) {
654
+ components.Instances = { collectionId };
655
+ }
540
656
  }
541
657
  if (isCamera) {
542
658
  components.Camera = {
@@ -564,10 +680,20 @@ function gltfDocToSceneAsset(doc, ctx) {
564
680
  for (let i = 0; i < 16; i++) parentWorld[i] = savedParent[i] ?? 0;
565
681
  };
566
682
  for (const rootIdx of sceneIr.nodes) visit(rootIdx, null);
567
- const frozen = resultNodes.map((n) => ({
568
- localId: n.localId,
569
- components: n.components
570
- }));
683
+ const keyByLocalId = /* @__PURE__ */ new Map();
684
+ for (const node of resultNodes) keyByLocalId.set(node.localIdx, `node-${node.localIdx}`);
685
+ const entities = {};
686
+ for (const node of resultNodes) {
687
+ const components = { ...node.components };
688
+ const childOf = components.ChildOf;
689
+ if (childOf !== void 0 && typeof childOf.parent === "number") {
690
+ const parentKey = keyByLocalId.get(childOf.parent);
691
+ if (parentKey === void 0)
692
+ throw new Error(`gltfDocToSceneAsset: missing parent node ${childOf.parent}`);
693
+ components.ChildOf = { ...childOf, parent: parentKey };
694
+ }
695
+ entities[`node-${node.localIdx}`] = { components };
696
+ }
571
697
  const lightFacts = importedLights.map((light) => ({
572
698
  kind: light.type,
573
699
  intensity: light.intensity,
@@ -576,7 +702,7 @@ function gltfDocToSceneAsset(doc, ctx) {
576
702
  }));
577
703
  return {
578
704
  kind: "scene",
579
- entities: frozen,
705
+ entities,
580
706
  ...lightFacts.length === 0 ? {} : { lights: lightFacts }
581
707
  };
582
708
  }
@@ -2089,35 +2215,6 @@ async function projectMeshoptBufferViews(inputViews, inputBuffers, extensionsReq
2089
2215
  }
2090
2216
  return ok({ bufferViews, buffers, decodedCount });
2091
2217
  }
2092
-
2093
- // src/node-path.ts
2094
- function buildNodeParentMap(nodes) {
2095
- const parents = /* @__PURE__ */ new Map();
2096
- for (let index = 0; index < nodes.length; index++) {
2097
- for (const child of nodes[index]?.children ?? []) parents.set(child, index);
2098
- }
2099
- return parents;
2100
- }
2101
- function resolveNamedNodePath(nodes, parents, nodeIndex) {
2102
- const reversed = [];
2103
- const visited = /* @__PURE__ */ new Set();
2104
- let current = nodeIndex;
2105
- while (current !== void 0) {
2106
- if (visited.has(current)) {
2107
- return { ok: false, reason: "hierarchy-cycle", nodeIndex: current };
2108
- }
2109
- visited.add(current);
2110
- const name = nodes[current]?.name;
2111
- if (name === void 0 || name.length === 0) {
2112
- return { ok: false, reason: "name-missing", nodeIndex: current };
2113
- }
2114
- reversed.push(name);
2115
- current = parents.get(current);
2116
- }
2117
- return { ok: true, value: reversed.reverse() };
2118
- }
2119
-
2120
- // src/parse-animation.ts
2121
2218
  var ANIMATION_ACCESSOR_TYPES = ["SCALAR", "VEC2", "VEC3", "VEC4"];
2122
2219
  function parseAnimation(animationsJson, nodesJson, accessors, bufferViews, buffers) {
2123
2220
  if (animationsJson === void 0 || animationsJson.length === 0) {
@@ -2283,8 +2380,6 @@ function parseAnimation(animationsJson, nodesJson, accessors, bufferViews, buffe
2283
2380
  }
2284
2381
  return ok(clips);
2285
2382
  }
2286
-
2287
- // src/parse-skin.ts
2288
2383
  var MAX_JOINTS = 256;
2289
2384
  var SKIN_ACCESSOR_TYPES = ["MAT4"];
2290
2385
  function identityMat4() {
@@ -2365,10 +2460,14 @@ function parseSkin(skinsJson, nodesJson, accessors, bufferViews, buffers) {
2365
2460
  if (!pathResult.ok) return err(pathResult.error);
2366
2461
  jointPaths.push(pathResult.value.join("/"));
2367
2462
  }
2463
+ const bounds = parseConservativeAnimatedBounds(
2464
+ skin.extras?.forgeax?.conservativeAnimatedBounds
2465
+ );
2368
2466
  records.push({
2369
2467
  jointCount: joints.length,
2370
2468
  inverseBindMatrices: ibm,
2371
- jointPaths
2469
+ jointPaths,
2470
+ ...bounds === void 0 ? {} : { bounds }
2372
2471
  });
2373
2472
  }
2374
2473
  return ok(records);
@@ -3220,6 +3319,16 @@ function isGlbBytes(source) {
3220
3319
  function publishesCatalogProduct(input) {
3221
3320
  return input.importSettings.geometry !== "procedural";
3222
3321
  }
3322
+ function applyImportSettingsBounds(doc, importSettings) {
3323
+ let changed = false;
3324
+ const skeletons = doc.skeletons.map((record, sourceIndex) => {
3325
+ const bounds = readConservativeAnimatedBounds(importSettings, sourceIndex);
3326
+ if (bounds === void 0 || record.bounds !== void 0) return record;
3327
+ changed = true;
3328
+ return { ...record, bounds };
3329
+ });
3330
+ return changed ? { ...doc, skeletons } : doc;
3331
+ }
3223
3332
  function previousMaterialSlotTopology(ctx, meshSourceKey) {
3224
3333
  if (meshSourceKey === void 0) return void 0;
3225
3334
  const value = ctx.sourceOverrides?.[meshSourceKey]?.materialSlots;
@@ -3539,7 +3648,7 @@ async function importGltf(ctx, meshopt) {
3539
3648
  }
3540
3649
  const parsed = await parseDoc(ctx.source, read.value, ctx, meshopt);
3541
3650
  if (!parsed.ok) return parsed;
3542
- const doc = parsed.value;
3651
+ const doc = deriveGltfAnimatedBounds(applyImportSettingsBounds(parsed.value, ctx.importSettings));
3543
3652
  const maps = buildHandleMaps(ctx.subAssets, doc);
3544
3653
  const imageColorSpaces = deriveTextureColorSpace({
3545
3654
  imageCount: (doc.images ?? []).length,
@@ -3838,7 +3947,7 @@ async function importGltf(ctx, meshopt) {
3838
3947
  fieldName: prov.fieldName,
3839
3948
  ...prov.arrayIndex !== void 0 ? { arrayIndex: prov.arrayIndex } : {}
3840
3949
  },
3841
- sceneEntityId: prov.sceneEntityId
3950
+ sceneEntityKey: prov.sceneEntityKey
3842
3951
  };
3843
3952
  }
3844
3953
  return { guid };
@@ -3849,19 +3958,19 @@ async function importGltf(ctx, meshopt) {
3849
3958
  });
3850
3959
  const handleValueProvenance = /* @__PURE__ */ new Map();
3851
3960
  const skeletonGuidProvenance = /* @__PURE__ */ new Map();
3852
- for (const entity of scene.entities) {
3961
+ for (const [sceneEntityKey, entity] of Object.entries(scene.entities)) {
3853
3962
  const comps = entity.components;
3854
3963
  const mf = comps.MeshFilter;
3855
3964
  if (mf !== void 0 && typeof mf.assetHandle === "number") {
3856
3965
  handleValueProvenance.set(mf.assetHandle, {
3857
- sceneEntityId: entity.localId,
3966
+ sceneEntityKey,
3858
3967
  componentName: "MeshFilter",
3859
3968
  fieldName: "assetHandle"
3860
3969
  });
3861
3970
  }
3862
3971
  const skin = comps.Skin;
3863
3972
  if (skin !== void 0 && typeof skin.skeleton === "string") {
3864
- skeletonGuidProvenance.set(skin.skeleton, { sceneEntityId: entity.localId });
3973
+ skeletonGuidProvenance.set(skin.skeleton, { sceneEntityKey });
3865
3974
  }
3866
3975
  }
3867
3976
  const meshGuidList = [...maps.meshGuidByIndex.values()];
@@ -3879,7 +3988,7 @@ async function importGltf(ctx, meshopt) {
3879
3988
  skProv !== void 0 ? {
3880
3989
  guid,
3881
3990
  sourceField: { componentName: "Skin", fieldName: "skeleton" },
3882
- sceneEntityId: skProv.sceneEntityId
3991
+ sceneEntityKey: skProv.sceneEntityKey
3883
3992
  } : { guid }
3884
3993
  );
3885
3994
  cursor++;
@@ -3909,7 +4018,8 @@ async function importGltf(ctx, meshopt) {
3909
4018
  const payload = {
3910
4019
  kind: "skeleton",
3911
4020
  inverseBindMatrices: rec.inverseBindMatrices,
3912
- jointCount: rec.jointCount
4021
+ jointCount: rec.jointCount,
4022
+ ...rec.bounds === void 0 ? {} : { bounds: rec.bounds }
3913
4023
  };
3914
4024
  out.push({ guid: sub.guid, kind: "skeleton", payload, refs: [], artifacts: {} });
3915
4025
  } else if (sub.kind === "skin") {