@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.
package/dist/index.mjs CHANGED
@@ -1,10 +1,11 @@
1
- import { err, ok, createMaterialError, standardMaterialParameters, reconcileMeshMaterialSlotTopology, MESH_MATERIAL_SLOT_SOURCE_OVERRIDE_PAYLOAD_SCHEMA, STANDARD_MATERIAL_PARAM_SCHEMA, STANDARD_PHYSICAL_PARAMETER_NAMES, STANDARD_TRANSMISSION_PARAMETER_NAMES, ImportError, STANDARD_LAYER_PARAMETER_GROUPS, IMPORT_ERROR_HINTS, toShared, resolveMeshMaterialSlotDefaultGuid } from '@forgeax/engine-types';
1
+ import { err, ok, createMaterialError, standardMaterialParameters, reconcileMeshMaterialSlotTopology, MESH_MATERIAL_SLOT_SOURCE_OVERRIDE_PAYLOAD_SCHEMA, STANDARD_MATERIAL_PARAM_SCHEMA, STANDARD_PHYSICAL_PARAMETER_NAMES, STANDARD_TRANSMISSION_PARAMETER_NAMES, ImportError, STANDARD_LAYER_PARAMETER_GROUPS, IMPORT_ERROR_HINTS, parseConservativeAnimatedBounds, readConservativeAnimatedBounds, toShared, resolveMeshMaterialSlotDefaultGuid } from '@forgeax/engine-types';
2
2
  export { err, ok } from '@forgeax/engine-types';
3
3
  import { computeTangentVec4, packInterleavedVertexAttributes } from '@forgeax/engine-geometry';
4
4
  import { box3, mat4, vec3, quat } from '@forgeax/engine-math';
5
5
  import { AssetGuid } from '@forgeax/engine-pack/guid';
6
6
  import { deriveDefaultLodScreenCoverages, reconcileMeshLodMeta } from '@forgeax/engine-import';
7
7
  import { packMeshBinV4 } from '@forgeax/engine-import/mesh-bin';
8
+ import { deriveConservativeAnimatedBounds } from '@forgeax/engine-animation/animated-bounds';
8
9
  import { deriveAnimationTargetId } from '@forgeax/engine-animation/target-id';
9
10
 
10
11
  // src/errors.ts
@@ -716,7 +717,7 @@ function composeMat4(out, tx, ty, tz, qx, qy, qz, qw, sx, sy, sz) {
716
717
  function gltfDocToSceneAsset(doc, ctx) {
717
718
  const sceneIr = doc.scenes[doc.defaultSceneIndex];
718
719
  const resultNodes = [];
719
- if (sceneIr === void 0) return { kind: "scene", entities: [] };
720
+ if (sceneIr === void 0) return { kind: "scene", entities: {} };
720
721
  const importedLights = doc.lights ?? doc.extensions?.KHR_lights_punctual?.lights ?? [];
721
722
  const animationTargetIds = /* @__PURE__ */ new Map();
722
723
  for (const clip of doc.animationClips) {
@@ -809,7 +810,10 @@ function gltfDocToSceneAsset(doc, ctx) {
809
810
  components.MeshRenderer = { materials: [] };
810
811
  }
811
812
  if (ir.instancing !== void 0) {
812
- components.Instances = { transforms: ir.instancing.transforms };
813
+ const collectionId = ctx.instanceCollectionIdsByNodeIndex?.get(gltfNodeIdx);
814
+ if (collectionId !== void 0) {
815
+ components.Instances = { collectionId };
816
+ }
813
817
  }
814
818
  if (isCamera) {
815
819
  components.Camera = {
@@ -837,10 +841,20 @@ function gltfDocToSceneAsset(doc, ctx) {
837
841
  for (let i = 0; i < 16; i++) parentWorld[i] = savedParent[i] ?? 0;
838
842
  };
839
843
  for (const rootIdx of sceneIr.nodes) visit(rootIdx, null);
840
- const frozen = resultNodes.map((n) => ({
841
- localId: n.localId,
842
- components: n.components
843
- }));
844
+ const keyByLocalId = /* @__PURE__ */ new Map();
845
+ for (const node of resultNodes) keyByLocalId.set(node.localIdx, `node-${node.localIdx}`);
846
+ const entities = {};
847
+ for (const node of resultNodes) {
848
+ const components = { ...node.components };
849
+ const childOf = components.ChildOf;
850
+ if (childOf !== void 0 && typeof childOf.parent === "number") {
851
+ const parentKey = keyByLocalId.get(childOf.parent);
852
+ if (parentKey === void 0)
853
+ throw new Error(`gltfDocToSceneAsset: missing parent node ${childOf.parent}`);
854
+ components.ChildOf = { ...childOf, parent: parentKey };
855
+ }
856
+ entities[`node-${node.localIdx}`] = { components };
857
+ }
844
858
  const lightFacts = importedLights.map((light) => ({
845
859
  kind: light.type,
846
860
  intensity: light.intensity,
@@ -849,7 +863,7 @@ function gltfDocToSceneAsset(doc, ctx) {
849
863
  }));
850
864
  return {
851
865
  kind: "scene",
852
- entities: frozen,
866
+ entities,
853
867
  ...lightFacts.length === 0 ? {} : { lights: lightFacts }
854
868
  };
855
869
  }
@@ -1156,6 +1170,118 @@ function checkExtensions(json) {
1156
1170
  return ok({ unsupportedUsed });
1157
1171
  }
1158
1172
 
1173
+ // src/node-path.ts
1174
+ function buildNodeParentMap(nodes) {
1175
+ const parents = /* @__PURE__ */ new Map();
1176
+ for (let index = 0; index < nodes.length; index++) {
1177
+ for (const child of nodes[index]?.children ?? []) parents.set(child, index);
1178
+ }
1179
+ return parents;
1180
+ }
1181
+ function resolveNamedNodePath(nodes, parents, nodeIndex) {
1182
+ const reversed = [];
1183
+ const visited = /* @__PURE__ */ new Set();
1184
+ let current = nodeIndex;
1185
+ while (current !== void 0) {
1186
+ if (visited.has(current)) {
1187
+ return { ok: false, reason: "hierarchy-cycle", nodeIndex: current };
1188
+ }
1189
+ visited.add(current);
1190
+ const name = nodes[current]?.name;
1191
+ if (name === void 0 || name.length === 0) {
1192
+ return { ok: false, reason: "name-missing", nodeIndex: current };
1193
+ }
1194
+ reversed.push(name);
1195
+ current = parents.get(current);
1196
+ }
1197
+ return { ok: true, value: reversed.reverse() };
1198
+ }
1199
+
1200
+ // src/animated-bounds.ts
1201
+ function deriveGltfAnimatedBounds(doc) {
1202
+ const parents = buildNodeParentMap(doc.nodes);
1203
+ const paths = doc.nodes.map((_, index) => {
1204
+ const path = resolveNamedNodePath(doc.nodes, parents, index);
1205
+ return path.ok ? path.value.join("/") : void 0;
1206
+ });
1207
+ const nodes = doc.nodes.map((node, index) => ({
1208
+ ...node.transform,
1209
+ parent: parents.get(index) ?? null
1210
+ }));
1211
+ const channels = doc.animationClips.flatMap(
1212
+ (clip) => clip.channels.flatMap(
1213
+ (channel) => channel.property === "weights" ? [] : [
1214
+ {
1215
+ node: channel.targetNodeIndex,
1216
+ property: channel.property,
1217
+ values: channel.sampler.output,
1218
+ interpolation: channel.sampler.interpolation
1219
+ }
1220
+ ]
1221
+ )
1222
+ );
1223
+ const primitiveStarts = [];
1224
+ let offset = 0;
1225
+ for (const count of doc.meshPrimitiveCount?.values() ?? doc.meshes.map(() => 1)) {
1226
+ primitiveStarts.push(offset);
1227
+ offset += count;
1228
+ }
1229
+ const skeletons = doc.skeletons.map((skeleton, skinIndex) => {
1230
+ if (skeleton.bounds !== void 0) return skeleton;
1231
+ const meshes = [];
1232
+ for (let index = 0; index < doc.nodes.length; index++) {
1233
+ const node = doc.nodes[index];
1234
+ if (node === void 0) return skeleton;
1235
+ if (node.skinIndex !== skinIndex || node.meshIndex === null) continue;
1236
+ const start = primitiveStarts[node.meshIndex];
1237
+ if (start === void 0) return skeleton;
1238
+ for (const mesh of doc.meshes.slice(
1239
+ start,
1240
+ start + (doc.meshPrimitiveCount?.get(node.meshIndex) ?? 1)
1241
+ )) {
1242
+ if (mesh.joints0 === void 0 || mesh.weights0 === void 0) return skeleton;
1243
+ let maxMorphWeight = Math.max(
1244
+ 0,
1245
+ ...Array.from(node.morphWeights ?? mesh.morphWeights ?? [], Math.abs)
1246
+ );
1247
+ for (const clip of doc.animationClips)
1248
+ for (const channel of clip.channels)
1249
+ if (channel.targetNodeIndex === index && channel.property === "weights")
1250
+ for (const weight of channel.sampler.output)
1251
+ maxMorphWeight = Math.max(maxMorphWeight, Math.abs(weight));
1252
+ const morphExtent = Float32Array.from(
1253
+ mesh.positions,
1254
+ (_, component) => (mesh.morphTargets ?? []).reduce(
1255
+ (sum, target) => sum + Math.abs(target.position?.[component] ?? 0) * maxMorphWeight,
1256
+ 0
1257
+ )
1258
+ );
1259
+ meshes.push({
1260
+ node: index,
1261
+ positions: mesh.positions,
1262
+ joints: mesh.joints0,
1263
+ weights: mesh.weights0,
1264
+ morphExtent
1265
+ });
1266
+ }
1267
+ }
1268
+ const jointNodes = skeleton.jointPaths.map((path) => paths.indexOf(path));
1269
+ if (jointNodes.some(
1270
+ (node, index) => node < 0 || paths.lastIndexOf(skeleton.jointPaths[index]) !== node
1271
+ ))
1272
+ return skeleton;
1273
+ const bounds = deriveConservativeAnimatedBounds({
1274
+ nodes,
1275
+ channels,
1276
+ jointNodes,
1277
+ inverseBindMatrices: skeleton.inverseBindMatrices,
1278
+ meshes
1279
+ });
1280
+ return bounds === void 0 ? skeleton : { ...skeleton, bounds };
1281
+ });
1282
+ return { ...doc, skeletons };
1283
+ }
1284
+
1159
1285
  // src/data-uri.ts
1160
1286
  var DATA_URI_BASE64_RE = /^data:[^;,]*(?:;[^,;]+)*;base64,(.*)$/;
1161
1287
  var Base64DecodeError = class extends Error {
@@ -2109,35 +2235,6 @@ async function projectMeshoptBufferViews(inputViews, inputBuffers, extensionsReq
2109
2235
  }
2110
2236
  return ok({ bufferViews, buffers, decodedCount });
2111
2237
  }
2112
-
2113
- // src/node-path.ts
2114
- function buildNodeParentMap(nodes) {
2115
- const parents = /* @__PURE__ */ new Map();
2116
- for (let index = 0; index < nodes.length; index++) {
2117
- for (const child of nodes[index]?.children ?? []) parents.set(child, index);
2118
- }
2119
- return parents;
2120
- }
2121
- function resolveNamedNodePath(nodes, parents, nodeIndex) {
2122
- const reversed = [];
2123
- const visited = /* @__PURE__ */ new Set();
2124
- let current = nodeIndex;
2125
- while (current !== void 0) {
2126
- if (visited.has(current)) {
2127
- return { ok: false, reason: "hierarchy-cycle", nodeIndex: current };
2128
- }
2129
- visited.add(current);
2130
- const name = nodes[current]?.name;
2131
- if (name === void 0 || name.length === 0) {
2132
- return { ok: false, reason: "name-missing", nodeIndex: current };
2133
- }
2134
- reversed.push(name);
2135
- current = parents.get(current);
2136
- }
2137
- return { ok: true, value: reversed.reverse() };
2138
- }
2139
-
2140
- // src/parse-animation.ts
2141
2238
  var ANIMATION_ACCESSOR_TYPES = ["SCALAR", "VEC2", "VEC3", "VEC4"];
2142
2239
  function parseAnimation(animationsJson, nodesJson, accessors, bufferViews, buffers) {
2143
2240
  if (animationsJson === void 0 || animationsJson.length === 0) {
@@ -2303,8 +2400,6 @@ function parseAnimation(animationsJson, nodesJson, accessors, bufferViews, buffe
2303
2400
  }
2304
2401
  return ok(clips);
2305
2402
  }
2306
-
2307
- // src/parse-skin.ts
2308
2403
  var MAX_JOINTS = 256;
2309
2404
  var SKIN_ACCESSOR_TYPES = ["MAT4"];
2310
2405
  function identityMat4() {
@@ -2385,10 +2480,14 @@ function parseSkin(skinsJson, nodesJson, accessors, bufferViews, buffers) {
2385
2480
  if (!pathResult.ok) return err(pathResult.error);
2386
2481
  jointPaths.push(pathResult.value.join("/"));
2387
2482
  }
2483
+ const bounds = parseConservativeAnimatedBounds(
2484
+ skin.extras?.forgeax?.conservativeAnimatedBounds
2485
+ );
2388
2486
  records.push({
2389
2487
  jointCount: joints.length,
2390
2488
  inverseBindMatrices: ibm,
2391
- jointPaths
2489
+ jointPaths,
2490
+ ...bounds === void 0 ? {} : { bounds }
2392
2491
  });
2393
2492
  }
2394
2493
  return ok(records);
@@ -3599,6 +3698,16 @@ function isGlbBytes(source) {
3599
3698
  function publishesCatalogProduct(input) {
3600
3699
  return input.importSettings.geometry !== "procedural";
3601
3700
  }
3701
+ function applyImportSettingsBounds(doc, importSettings) {
3702
+ let changed = false;
3703
+ const skeletons = doc.skeletons.map((record, sourceIndex) => {
3704
+ const bounds = readConservativeAnimatedBounds(importSettings, sourceIndex);
3705
+ if (bounds === void 0 || record.bounds !== void 0) return record;
3706
+ changed = true;
3707
+ return { ...record, bounds };
3708
+ });
3709
+ return changed ? { ...doc, skeletons } : doc;
3710
+ }
3602
3711
  function previousMaterialSlotTopology(ctx, meshSourceKey) {
3603
3712
  if (meshSourceKey === void 0) return void 0;
3604
3713
  const value = ctx.sourceOverrides?.[meshSourceKey]?.materialSlots;
@@ -3918,7 +4027,7 @@ async function importGltf(ctx, meshopt) {
3918
4027
  }
3919
4028
  const parsed = await parseDoc(ctx.source, read.value, ctx);
3920
4029
  if (!parsed.ok) return parsed;
3921
- const doc = parsed.value;
4030
+ const doc = deriveGltfAnimatedBounds(applyImportSettingsBounds(parsed.value, ctx.importSettings));
3922
4031
  const maps = buildHandleMaps(ctx.subAssets, doc);
3923
4032
  const imageColorSpaces = deriveTextureColorSpace({
3924
4033
  imageCount: (doc.images ?? []).length,
@@ -4217,7 +4326,7 @@ async function importGltf(ctx, meshopt) {
4217
4326
  fieldName: prov.fieldName,
4218
4327
  ...prov.arrayIndex !== void 0 ? { arrayIndex: prov.arrayIndex } : {}
4219
4328
  },
4220
- sceneEntityId: prov.sceneEntityId
4329
+ sceneEntityKey: prov.sceneEntityKey
4221
4330
  };
4222
4331
  }
4223
4332
  return { guid };
@@ -4228,19 +4337,19 @@ async function importGltf(ctx, meshopt) {
4228
4337
  });
4229
4338
  const handleValueProvenance = /* @__PURE__ */ new Map();
4230
4339
  const skeletonGuidProvenance = /* @__PURE__ */ new Map();
4231
- for (const entity of scene.entities) {
4340
+ for (const [sceneEntityKey, entity] of Object.entries(scene.entities)) {
4232
4341
  const comps = entity.components;
4233
4342
  const mf = comps.MeshFilter;
4234
4343
  if (mf !== void 0 && typeof mf.assetHandle === "number") {
4235
4344
  handleValueProvenance.set(mf.assetHandle, {
4236
- sceneEntityId: entity.localId,
4345
+ sceneEntityKey,
4237
4346
  componentName: "MeshFilter",
4238
4347
  fieldName: "assetHandle"
4239
4348
  });
4240
4349
  }
4241
4350
  const skin = comps.Skin;
4242
4351
  if (skin !== void 0 && typeof skin.skeleton === "string") {
4243
- skeletonGuidProvenance.set(skin.skeleton, { sceneEntityId: entity.localId });
4352
+ skeletonGuidProvenance.set(skin.skeleton, { sceneEntityKey });
4244
4353
  }
4245
4354
  }
4246
4355
  const meshGuidList = [...maps.meshGuidByIndex.values()];
@@ -4258,7 +4367,7 @@ async function importGltf(ctx, meshopt) {
4258
4367
  skProv !== void 0 ? {
4259
4368
  guid,
4260
4369
  sourceField: { componentName: "Skin", fieldName: "skeleton" },
4261
- sceneEntityId: skProv.sceneEntityId
4370
+ sceneEntityKey: skProv.sceneEntityKey
4262
4371
  } : { guid }
4263
4372
  );
4264
4373
  cursor++;
@@ -4288,7 +4397,8 @@ async function importGltf(ctx, meshopt) {
4288
4397
  const payload = {
4289
4398
  kind: "skeleton",
4290
4399
  inverseBindMatrices: rec.inverseBindMatrices,
4291
- jointCount: rec.jointCount
4400
+ jointCount: rec.jointCount,
4401
+ ...rec.bounds === void 0 ? {} : { bounds: rec.bounds }
4292
4402
  };
4293
4403
  out.push({ guid: sub.guid, kind: "skeleton", payload, refs: [], artifacts: {} });
4294
4404
  } else if (sub.kind === "skin") {