@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/README.md +1 -1
- package/dist/animated-bounds.d.ts +4 -0
- package/dist/animated-bounds.d.ts.map +1 -0
- package/dist/bridge.d.ts +7 -0
- package/dist/bridge.d.ts.map +1 -1
- package/dist/cli-gltf.mjs +6 -4
- package/dist/cli-gltf.mjs.map +1 -1
- package/dist/gltf-importer.d.ts.map +1 -1
- package/dist/importer-entry.mjs +157 -47
- package/dist/importer-entry.mjs.map +1 -1
- package/dist/index.mjs +157 -47
- package/dist/index.mjs.map +1 -1
- package/dist/node-file-entry.mjs +6 -4
- package/dist/node-file-entry.mjs.map +1 -1
- package/dist/parse-gltf.d.ts.map +1 -1
- package/dist/parse-skin.d.ts +8 -16
- package/dist/parse-skin.d.ts.map +1 -1
- package/package.json +12 -12
- package/src/__tests__/bridge.unit.test.ts +70 -51
- package/src/__tests__/gltf.unit.test.ts +162 -15
- package/src/__tests__/morph-import.integration.test.ts +1 -1
- package/src/animated-bounds.ts +94 -0
- package/src/bridge.ts +34 -10
- package/src/gltf-importer.ts +30 -11
- package/src/parse-gltf.ts +5 -0
- package/src/parse-skin.ts +14 -41
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
|
-
|
|
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
|
|
841
|
-
|
|
842
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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, {
|
|
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
|
-
|
|
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") {
|