@babylonjs/loaders 9.11.0 → 9.12.1
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/FBX/fbxFileLoader.d.ts +194 -0
- package/FBX/fbxFileLoader.js +2440 -0
- package/FBX/fbxFileLoader.js.map +1 -0
- package/FBX/fbxFileLoader.metadata.d.ts +11 -0
- package/FBX/fbxFileLoader.metadata.js +11 -0
- package/FBX/fbxFileLoader.metadata.js.map +1 -0
- package/FBX/index.d.ts +3 -0
- package/FBX/index.js +3 -0
- package/FBX/index.js.map +1 -0
- package/FBX/interpreter/animation.d.ts +122 -0
- package/FBX/interpreter/animation.js +648 -0
- package/FBX/interpreter/animation.js.map +1 -0
- package/FBX/interpreter/blendShapes.d.ts +44 -0
- package/FBX/interpreter/blendShapes.js +192 -0
- package/FBX/interpreter/blendShapes.js.map +1 -0
- package/FBX/interpreter/connections.d.ts +95 -0
- package/FBX/interpreter/connections.js +233 -0
- package/FBX/interpreter/connections.js.map +1 -0
- package/FBX/interpreter/fbxInterpreter.d.ts +149 -0
- package/FBX/interpreter/fbxInterpreter.js +496 -0
- package/FBX/interpreter/fbxInterpreter.js.map +1 -0
- package/FBX/interpreter/geometry.d.ts +55 -0
- package/FBX/interpreter/geometry.js +573 -0
- package/FBX/interpreter/geometry.js.map +1 -0
- package/FBX/interpreter/materials.d.ts +50 -0
- package/FBX/interpreter/materials.js +144 -0
- package/FBX/interpreter/materials.js.map +1 -0
- package/FBX/interpreter/propertyTemplates.d.ts +22 -0
- package/FBX/interpreter/propertyTemplates.js +125 -0
- package/FBX/interpreter/propertyTemplates.js.map +1 -0
- package/FBX/interpreter/rig.d.ts +20 -0
- package/FBX/interpreter/rig.js +259 -0
- package/FBX/interpreter/rig.js.map +1 -0
- package/FBX/interpreter/sceneDiagnostics.d.ts +14 -0
- package/FBX/interpreter/sceneDiagnostics.js +55 -0
- package/FBX/interpreter/sceneDiagnostics.js.map +1 -0
- package/FBX/interpreter/skeleton.d.ts +93 -0
- package/FBX/interpreter/skeleton.js +515 -0
- package/FBX/interpreter/skeleton.js.map +1 -0
- package/FBX/interpreter/transform.d.ts +21 -0
- package/FBX/interpreter/transform.js +92 -0
- package/FBX/interpreter/transform.js.map +1 -0
- package/FBX/parsers/fbxAsciiParser.d.ts +5 -0
- package/FBX/parsers/fbxAsciiParser.js +330 -0
- package/FBX/parsers/fbxAsciiParser.js.map +1 -0
- package/FBX/parsers/fbxBinaryParser.d.ts +6 -0
- package/FBX/parsers/fbxBinaryParser.js +255 -0
- package/FBX/parsers/fbxBinaryParser.js.map +1 -0
- package/FBX/parsers/zlibInflate.d.ts +7 -0
- package/FBX/parsers/zlibInflate.js +350 -0
- package/FBX/parsers/zlibInflate.js.map +1 -0
- package/FBX/types/fbxTypes.d.ts +54 -0
- package/FBX/types/fbxTypes.js +66 -0
- package/FBX/types/fbxTypes.js.map +1 -0
- package/SPLAT/gaussianSplattingStream.d.ts +341 -0
- package/SPLAT/gaussianSplattingStream.js +976 -0
- package/SPLAT/gaussianSplattingStream.js.map +1 -0
- package/SPLAT/gaussianSplattingWorkBuffer.d.ts +51 -0
- package/SPLAT/gaussianSplattingWorkBuffer.js +159 -0
- package/SPLAT/gaussianSplattingWorkBuffer.js.map +1 -0
- package/SPLAT/gaussianSplattingWorkBufferShaders.d.ts +25 -0
- package/SPLAT/gaussianSplattingWorkBufferShaders.js +255 -0
- package/SPLAT/gaussianSplattingWorkBufferShaders.js.map +1 -0
- package/SPLAT/index.d.ts +1 -0
- package/SPLAT/index.js +1 -0
- package/SPLAT/index.js.map +1 -1
- package/SPLAT/sog.js +18 -16
- package/SPLAT/sog.js.map +1 -1
- package/SPLAT/splatFileLoader.d.ts +8 -0
- package/SPLAT/splatFileLoader.js +49 -0
- package/SPLAT/splatFileLoader.js.map +1 -1
- package/dynamic.js +9 -0
- package/dynamic.js.map +1 -1
- package/index.d.ts +1 -0
- package/index.js +1 -0
- package/index.js.map +1 -1
- package/package.json +3 -3
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
import { extractBoneTransform, isSkeletonModel } from "./skeleton.js";
|
|
2
|
+
import { cleanFBXName, getPropertyValue } from "../types/fbxTypes.js";
|
|
3
|
+
export function resolveRigs(objectMap, skins) {
|
|
4
|
+
if (skins.length === 0) {
|
|
5
|
+
return [];
|
|
6
|
+
}
|
|
7
|
+
const groupByRoot = new Map();
|
|
8
|
+
for (const skin of skins) {
|
|
9
|
+
const clusterModelIds = skin.bones.filter((bone) => bone.isCluster).map((bone) => bone.modelId);
|
|
10
|
+
if (clusterModelIds.length === 0) {
|
|
11
|
+
continue;
|
|
12
|
+
}
|
|
13
|
+
const rootModelId = findRigGroupingRoot(clusterModelIds, objectMap);
|
|
14
|
+
const group = groupByRoot.get(rootModelId);
|
|
15
|
+
if (group) {
|
|
16
|
+
group.push(skin);
|
|
17
|
+
}
|
|
18
|
+
else {
|
|
19
|
+
groupByRoot.set(rootModelId, [skin]);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return Array.from(groupByRoot.entries())
|
|
23
|
+
.sort(([a], [b]) => compareNumber(a, b))
|
|
24
|
+
.map(([rootModelId, groupSkins]) => buildRig(rootModelId, groupSkins, objectMap));
|
|
25
|
+
}
|
|
26
|
+
function buildRig(rootModelId, skins, objectMap) {
|
|
27
|
+
const clusterModelIds = new Set();
|
|
28
|
+
const rigModelIds = new Set();
|
|
29
|
+
const sourceBonesByModelId = new Map();
|
|
30
|
+
const sourceOrderByModelId = new Map();
|
|
31
|
+
for (const skin of skins) {
|
|
32
|
+
for (const bone of skin.bones) {
|
|
33
|
+
if (!sourceOrderByModelId.has(bone.modelId)) {
|
|
34
|
+
sourceOrderByModelId.set(bone.modelId, sourceOrderByModelId.size);
|
|
35
|
+
}
|
|
36
|
+
let sources = sourceBonesByModelId.get(bone.modelId);
|
|
37
|
+
if (!sources) {
|
|
38
|
+
sources = [];
|
|
39
|
+
sourceBonesByModelId.set(bone.modelId, sources);
|
|
40
|
+
}
|
|
41
|
+
sources.push(bone);
|
|
42
|
+
if (!bone.isCluster) {
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
clusterModelIds.add(bone.modelId);
|
|
46
|
+
for (const ancestorId of getModelAncestorChain(bone.modelId, objectMap)) {
|
|
47
|
+
rigModelIds.add(ancestorId);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
const warnings = collectTransformLinkWarnings(sourceBonesByModelId);
|
|
52
|
+
const preferredBoneByModelId = new Map();
|
|
53
|
+
for (const [modelId, sources] of Array.from(sourceBonesByModelId)) {
|
|
54
|
+
preferredBoneByModelId.set(modelId, choosePreferredBoneSource(sources));
|
|
55
|
+
}
|
|
56
|
+
const parentByModelId = buildParentMap(rigModelIds, objectMap);
|
|
57
|
+
const orderedModelIds = orderParentsBeforeChildren(rigModelIds, parentByModelId, sourceOrderByModelId);
|
|
58
|
+
const bones = [];
|
|
59
|
+
const modelIdToBoneIndex = new Map();
|
|
60
|
+
for (const modelId of orderedModelIds) {
|
|
61
|
+
const sourceBone = preferredBoneByModelId.get(modelId) ?? createFallbackBone(modelId, objectMap);
|
|
62
|
+
if (!sourceBone) {
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
const parentModelId = parentByModelId.get(modelId);
|
|
66
|
+
const parentIndex = parentModelId === undefined ? -1 : (modelIdToBoneIndex.get(parentModelId) ?? -1);
|
|
67
|
+
const index = bones.length;
|
|
68
|
+
const bone = {
|
|
69
|
+
...sourceBone,
|
|
70
|
+
index,
|
|
71
|
+
parentIndex,
|
|
72
|
+
isCluster: clusterModelIds.has(modelId),
|
|
73
|
+
};
|
|
74
|
+
bones.push(bone);
|
|
75
|
+
modelIdToBoneIndex.set(modelId, index);
|
|
76
|
+
}
|
|
77
|
+
const skinBindings = skins.map((skin) => buildSkinBinding(skin, `rig_${rootModelId.toString()}`, modelIdToBoneIndex));
|
|
78
|
+
return {
|
|
79
|
+
id: `rig_${rootModelId.toString()}`,
|
|
80
|
+
rootModelIds: bones.filter((bone) => bone.parentIndex < 0).map((bone) => bone.modelId),
|
|
81
|
+
bones,
|
|
82
|
+
modelIdToBoneIndex,
|
|
83
|
+
clusterModelIds,
|
|
84
|
+
skinBindings,
|
|
85
|
+
warnings,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
function buildSkinBinding(skin, rigId, modelIdToBoneIndex) {
|
|
89
|
+
const skinBoneIndexToRigBoneIndex = skin.bones.map((bone) => {
|
|
90
|
+
const rigBoneIndex = modelIdToBoneIndex.get(bone.modelId);
|
|
91
|
+
if (rigBoneIndex === undefined && bone.isCluster) {
|
|
92
|
+
throw new Error(`FBX rig resolver: cluster bone ${bone.name} is missing from resolved rig ${rigId}`);
|
|
93
|
+
}
|
|
94
|
+
return rigBoneIndex ?? -1;
|
|
95
|
+
});
|
|
96
|
+
return {
|
|
97
|
+
skinId: skin.id,
|
|
98
|
+
geometryId: skin.geometryId,
|
|
99
|
+
rigId,
|
|
100
|
+
skinBoneIndexToRigBoneIndex,
|
|
101
|
+
clusterModelIds: new Set(skin.bones.filter((bone) => bone.isCluster).map((bone) => bone.modelId)),
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
function findRigGroupingRoot(clusterModelIds, objectMap) {
|
|
105
|
+
const lca = findLowestCommonAncestor(clusterModelIds, objectMap) ?? clusterModelIds[0];
|
|
106
|
+
let root = lca;
|
|
107
|
+
let parentId = findModelParentId(root, objectMap);
|
|
108
|
+
while (parentId !== undefined) {
|
|
109
|
+
const parentNode = objectMap.objects.get(parentId);
|
|
110
|
+
if (!parentNode || parentNode.name !== "Model" || !isSkeletonModel(parentNode)) {
|
|
111
|
+
break;
|
|
112
|
+
}
|
|
113
|
+
root = parentId;
|
|
114
|
+
parentId = findModelParentId(parentId, objectMap);
|
|
115
|
+
}
|
|
116
|
+
return root;
|
|
117
|
+
}
|
|
118
|
+
function findLowestCommonAncestor(modelIds, objectMap) {
|
|
119
|
+
if (modelIds.length === 0) {
|
|
120
|
+
return undefined;
|
|
121
|
+
}
|
|
122
|
+
const chains = modelIds.map((modelId) => getModelAncestorChain(modelId, objectMap));
|
|
123
|
+
const common = new Set(chains[0]);
|
|
124
|
+
for (const chain of chains.slice(1)) {
|
|
125
|
+
for (const modelId of Array.from(common)) {
|
|
126
|
+
if (!chain.includes(modelId)) {
|
|
127
|
+
common.delete(modelId);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return chains[0].find((modelId) => common.has(modelId));
|
|
132
|
+
}
|
|
133
|
+
function getModelAncestorChain(modelId, objectMap) {
|
|
134
|
+
const chain = [];
|
|
135
|
+
let currentId = modelId;
|
|
136
|
+
while (currentId !== undefined) {
|
|
137
|
+
const node = objectMap.objects.get(currentId);
|
|
138
|
+
if (!node || node.name !== "Model") {
|
|
139
|
+
break;
|
|
140
|
+
}
|
|
141
|
+
chain.push(currentId);
|
|
142
|
+
currentId = findModelParentId(currentId, objectMap);
|
|
143
|
+
}
|
|
144
|
+
return chain;
|
|
145
|
+
}
|
|
146
|
+
function buildParentMap(modelIds, objectMap) {
|
|
147
|
+
const parentByModelId = new Map();
|
|
148
|
+
for (const modelId of Array.from(modelIds)) {
|
|
149
|
+
const parentId = findModelParentId(modelId, objectMap);
|
|
150
|
+
if (parentId !== undefined && modelIds.has(parentId)) {
|
|
151
|
+
parentByModelId.set(modelId, parentId);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return parentByModelId;
|
|
155
|
+
}
|
|
156
|
+
function orderParentsBeforeChildren(modelIds, parentByModelId, sourceOrderByModelId) {
|
|
157
|
+
const childrenByModelId = new Map();
|
|
158
|
+
for (const modelId of Array.from(modelIds)) {
|
|
159
|
+
const parentId = parentByModelId.get(modelId);
|
|
160
|
+
if (parentId === undefined) {
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
let children = childrenByModelId.get(parentId);
|
|
164
|
+
if (!children) {
|
|
165
|
+
children = [];
|
|
166
|
+
childrenByModelId.set(parentId, children);
|
|
167
|
+
}
|
|
168
|
+
children.push(modelId);
|
|
169
|
+
}
|
|
170
|
+
for (const children of Array.from(childrenByModelId.values())) {
|
|
171
|
+
children.sort((a, b) => compareSourceOrder(a, b, sourceOrderByModelId));
|
|
172
|
+
}
|
|
173
|
+
const roots = Array.from(modelIds)
|
|
174
|
+
.filter((modelId) => !parentByModelId.has(modelId))
|
|
175
|
+
.sort((a, b) => compareSourceOrder(a, b, sourceOrderByModelId));
|
|
176
|
+
const ordered = [];
|
|
177
|
+
const queue = [...roots];
|
|
178
|
+
while (queue.length > 0) {
|
|
179
|
+
const modelId = queue.shift();
|
|
180
|
+
ordered.push(modelId);
|
|
181
|
+
queue.push(...(childrenByModelId.get(modelId) ?? []));
|
|
182
|
+
}
|
|
183
|
+
return ordered;
|
|
184
|
+
}
|
|
185
|
+
function findModelParentId(modelId, objectMap) {
|
|
186
|
+
const parentConnection = objectMap.connections.find((conn) => conn.type === "OO" && conn.childId === modelId && objectMap.objects.get(conn.parentId)?.name === "Model");
|
|
187
|
+
return parentConnection?.parentId;
|
|
188
|
+
}
|
|
189
|
+
function choosePreferredBoneSource(sources) {
|
|
190
|
+
return (sources.find((bone) => bone.isCluster && bone.transformLinkMatrix) ??
|
|
191
|
+
sources.find((bone) => bone.isCluster) ??
|
|
192
|
+
sources.find((bone) => bone.modelBindPoseMatrix) ??
|
|
193
|
+
sources[0]);
|
|
194
|
+
}
|
|
195
|
+
function collectTransformLinkWarnings(sourceBonesByModelId) {
|
|
196
|
+
const warnings = [];
|
|
197
|
+
for (const [modelId, sources] of Array.from(sourceBonesByModelId)) {
|
|
198
|
+
const matrices = sources.filter((bone) => bone.isCluster && bone.transformLinkMatrix).map((bone) => bone.transformLinkMatrix);
|
|
199
|
+
if (matrices.length < 2) {
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
const first = matrices[0];
|
|
203
|
+
if (matrices.some((matrix) => !areMatricesEquivalent(first, matrix, 1e-5))) {
|
|
204
|
+
warnings.push(`Model ${modelId.toString()} has differing Cluster.TransformLink matrices across skins`);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
return warnings;
|
|
208
|
+
}
|
|
209
|
+
function areMatricesEquivalent(a, b, epsilon) {
|
|
210
|
+
if (a.length !== b.length) {
|
|
211
|
+
return false;
|
|
212
|
+
}
|
|
213
|
+
for (let i = 0; i < a.length; i++) {
|
|
214
|
+
if (Math.abs(a[i] - b[i]) > epsilon) {
|
|
215
|
+
return false;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
return true;
|
|
219
|
+
}
|
|
220
|
+
function createFallbackBone(modelId, objectMap) {
|
|
221
|
+
const modelNode = objectMap.objects.get(modelId);
|
|
222
|
+
if (!modelNode || modelNode.name !== "Model") {
|
|
223
|
+
return null;
|
|
224
|
+
}
|
|
225
|
+
const transform = extractBoneTransform(modelNode);
|
|
226
|
+
return {
|
|
227
|
+
modelId,
|
|
228
|
+
name: cleanFBXName(getPropertyValue(modelNode, 1) ?? `Bone${modelId.toString()}`),
|
|
229
|
+
index: -1,
|
|
230
|
+
parentIndex: -1,
|
|
231
|
+
isCluster: false,
|
|
232
|
+
translation: transform.translation,
|
|
233
|
+
rotation: transform.rotation,
|
|
234
|
+
preRotation: transform.preRotation,
|
|
235
|
+
postRotation: transform.postRotation,
|
|
236
|
+
rotationPivot: transform.rotationPivot,
|
|
237
|
+
scalingPivot: transform.scalingPivot,
|
|
238
|
+
rotationOffset: transform.rotationOffset,
|
|
239
|
+
scalingOffset: transform.scalingOffset,
|
|
240
|
+
scale: transform.scale,
|
|
241
|
+
rotationOrder: transform.rotationOrder,
|
|
242
|
+
inheritType: transform.inheritType,
|
|
243
|
+
clusterMode: "Unknown",
|
|
244
|
+
bindPoseMatrix: null,
|
|
245
|
+
transformLinkMatrix: null,
|
|
246
|
+
transformAssociateModelMatrix: null,
|
|
247
|
+
modelBindPoseMatrix: null,
|
|
248
|
+
diagnostics: [],
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
function compareNumber(a, b) {
|
|
252
|
+
return a < b ? -1 : a > b ? 1 : 0;
|
|
253
|
+
}
|
|
254
|
+
function compareSourceOrder(a, b, sourceOrderByModelId) {
|
|
255
|
+
const aOrder = sourceOrderByModelId.get(a) ?? Number.MAX_SAFE_INTEGER;
|
|
256
|
+
const bOrder = sourceOrderByModelId.get(b) ?? Number.MAX_SAFE_INTEGER;
|
|
257
|
+
return aOrder - bOrder || compareNumber(a, b);
|
|
258
|
+
}
|
|
259
|
+
//# sourceMappingURL=rig.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rig.js","sourceRoot":"","sources":["../../../../../dev/loaders/src/FBX/interpreter/rig.ts"],"names":[],"mappings":"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","sourcesContent":["/* eslint-disable @typescript-eslint/naming-convention, jsdoc/require-param, jsdoc/require-returns */\r\nimport { type FBXObjectMap } from \"./connections\";\r\nimport { type FBXBoneData, type FBXSkinData, extractBoneTransform, isSkeletonModel } from \"./skeleton\";\r\n\r\nimport { cleanFBXName, getPropertyValue } from \"../types/fbxTypes\";\r\n\r\nexport type FBXRigBoneData = FBXBoneData;\r\n\r\nexport interface FBXSkinBindingData {\r\n skinId: number;\r\n geometryId: number;\r\n rigId: string;\r\n skinBoneIndexToRigBoneIndex: number[];\r\n clusterModelIds: Set<number>;\r\n}\r\n\r\nexport interface FBXRigData {\r\n id: string;\r\n rootModelIds: number[];\r\n bones: FBXRigBoneData[];\r\n modelIdToBoneIndex: Map<number, number>;\r\n clusterModelIds: Set<number>;\r\n skinBindings: FBXSkinBindingData[];\r\n warnings: string[];\r\n}\r\n\r\nexport function resolveRigs(objectMap: FBXObjectMap, skins: FBXSkinData[]): FBXRigData[] {\r\n if (skins.length === 0) {\r\n return [];\r\n }\r\n\r\n const groupByRoot = new Map<number, FBXSkinData[]>();\r\n\r\n for (const skin of skins) {\r\n const clusterModelIds = skin.bones.filter((bone) => bone.isCluster).map((bone) => bone.modelId);\r\n if (clusterModelIds.length === 0) {\r\n continue;\r\n }\r\n\r\n const rootModelId = findRigGroupingRoot(clusterModelIds, objectMap);\r\n const group = groupByRoot.get(rootModelId);\r\n if (group) {\r\n group.push(skin);\r\n } else {\r\n groupByRoot.set(rootModelId, [skin]);\r\n }\r\n }\r\n\r\n return Array.from(groupByRoot.entries())\r\n .sort(([a], [b]) => compareNumber(a, b))\r\n .map(([rootModelId, groupSkins]) => buildRig(rootModelId, groupSkins, objectMap));\r\n}\r\n\r\nfunction buildRig(rootModelId: number, skins: FBXSkinData[], objectMap: FBXObjectMap): FBXRigData {\r\n const clusterModelIds = new Set<number>();\r\n const rigModelIds = new Set<number>();\r\n const sourceBonesByModelId = new Map<number, FBXBoneData[]>();\r\n const sourceOrderByModelId = new Map<number, number>();\r\n\r\n for (const skin of skins) {\r\n for (const bone of skin.bones) {\r\n if (!sourceOrderByModelId.has(bone.modelId)) {\r\n sourceOrderByModelId.set(bone.modelId, sourceOrderByModelId.size);\r\n }\r\n\r\n let sources = sourceBonesByModelId.get(bone.modelId);\r\n if (!sources) {\r\n sources = [];\r\n sourceBonesByModelId.set(bone.modelId, sources);\r\n }\r\n sources.push(bone);\r\n\r\n if (!bone.isCluster) {\r\n continue;\r\n }\r\n\r\n clusterModelIds.add(bone.modelId);\r\n for (const ancestorId of getModelAncestorChain(bone.modelId, objectMap)) {\r\n rigModelIds.add(ancestorId);\r\n }\r\n }\r\n }\r\n\r\n const warnings = collectTransformLinkWarnings(sourceBonesByModelId);\r\n const preferredBoneByModelId = new Map<number, FBXBoneData>();\r\n for (const [modelId, sources] of Array.from(sourceBonesByModelId)) {\r\n preferredBoneByModelId.set(modelId, choosePreferredBoneSource(sources));\r\n }\r\n\r\n const parentByModelId = buildParentMap(rigModelIds, objectMap);\r\n const orderedModelIds = orderParentsBeforeChildren(rigModelIds, parentByModelId, sourceOrderByModelId);\r\n const bones: FBXRigBoneData[] = [];\r\n const modelIdToBoneIndex = new Map<number, number>();\r\n\r\n for (const modelId of orderedModelIds) {\r\n const sourceBone = preferredBoneByModelId.get(modelId) ?? createFallbackBone(modelId, objectMap);\r\n if (!sourceBone) {\r\n continue;\r\n }\r\n\r\n const parentModelId = parentByModelId.get(modelId);\r\n const parentIndex = parentModelId === undefined ? -1 : (modelIdToBoneIndex.get(parentModelId) ?? -1);\r\n const index = bones.length;\r\n const bone: FBXRigBoneData = {\r\n ...sourceBone,\r\n index,\r\n parentIndex,\r\n isCluster: clusterModelIds.has(modelId),\r\n };\r\n bones.push(bone);\r\n modelIdToBoneIndex.set(modelId, index);\r\n }\r\n\r\n const skinBindings = skins.map((skin) => buildSkinBinding(skin, `rig_${rootModelId.toString()}`, modelIdToBoneIndex));\r\n\r\n return {\r\n id: `rig_${rootModelId.toString()}`,\r\n rootModelIds: bones.filter((bone) => bone.parentIndex < 0).map((bone) => bone.modelId),\r\n bones,\r\n modelIdToBoneIndex,\r\n clusterModelIds,\r\n skinBindings,\r\n warnings,\r\n };\r\n}\r\n\r\nfunction buildSkinBinding(skin: FBXSkinData, rigId: string, modelIdToBoneIndex: Map<number, number>): FBXSkinBindingData {\r\n const skinBoneIndexToRigBoneIndex = skin.bones.map((bone) => {\r\n const rigBoneIndex = modelIdToBoneIndex.get(bone.modelId);\r\n if (rigBoneIndex === undefined && bone.isCluster) {\r\n throw new Error(`FBX rig resolver: cluster bone ${bone.name} is missing from resolved rig ${rigId}`);\r\n }\r\n return rigBoneIndex ?? -1;\r\n });\r\n\r\n return {\r\n skinId: skin.id,\r\n geometryId: skin.geometryId,\r\n rigId,\r\n skinBoneIndexToRigBoneIndex,\r\n clusterModelIds: new Set(skin.bones.filter((bone) => bone.isCluster).map((bone) => bone.modelId)),\r\n };\r\n}\r\n\r\nfunction findRigGroupingRoot(clusterModelIds: number[], objectMap: FBXObjectMap): number {\r\n const lca = findLowestCommonAncestor(clusterModelIds, objectMap) ?? clusterModelIds[0];\r\n let root = lca;\r\n let parentId = findModelParentId(root, objectMap);\r\n\r\n while (parentId !== undefined) {\r\n const parentNode = objectMap.objects.get(parentId);\r\n if (!parentNode || parentNode.name !== \"Model\" || !isSkeletonModel(parentNode)) {\r\n break;\r\n }\r\n\r\n root = parentId;\r\n parentId = findModelParentId(parentId, objectMap);\r\n }\r\n\r\n return root;\r\n}\r\n\r\nfunction findLowestCommonAncestor(modelIds: number[], objectMap: FBXObjectMap): number | undefined {\r\n if (modelIds.length === 0) {\r\n return undefined;\r\n }\r\n\r\n const chains = modelIds.map((modelId) => getModelAncestorChain(modelId, objectMap));\r\n const common = new Set(chains[0]);\r\n for (const chain of chains.slice(1)) {\r\n for (const modelId of Array.from(common)) {\r\n if (!chain.includes(modelId)) {\r\n common.delete(modelId);\r\n }\r\n }\r\n }\r\n\r\n return chains[0].find((modelId) => common.has(modelId));\r\n}\r\n\r\nfunction getModelAncestorChain(modelId: number, objectMap: FBXObjectMap): number[] {\r\n const chain: number[] = [];\r\n let currentId: number | undefined = modelId;\r\n\r\n while (currentId !== undefined) {\r\n const node = objectMap.objects.get(currentId);\r\n if (!node || node.name !== \"Model\") {\r\n break;\r\n }\r\n\r\n chain.push(currentId);\r\n currentId = findModelParentId(currentId, objectMap);\r\n }\r\n\r\n return chain;\r\n}\r\n\r\nfunction buildParentMap(modelIds: Set<number>, objectMap: FBXObjectMap): Map<number, number> {\r\n const parentByModelId = new Map<number, number>();\r\n\r\n for (const modelId of Array.from(modelIds)) {\r\n const parentId = findModelParentId(modelId, objectMap);\r\n if (parentId !== undefined && modelIds.has(parentId)) {\r\n parentByModelId.set(modelId, parentId);\r\n }\r\n }\r\n\r\n return parentByModelId;\r\n}\r\n\r\nfunction orderParentsBeforeChildren(modelIds: Set<number>, parentByModelId: Map<number, number>, sourceOrderByModelId: Map<number, number>): number[] {\r\n const childrenByModelId = new Map<number, number[]>();\r\n for (const modelId of Array.from(modelIds)) {\r\n const parentId = parentByModelId.get(modelId);\r\n if (parentId === undefined) {\r\n continue;\r\n }\r\n\r\n let children = childrenByModelId.get(parentId);\r\n if (!children) {\r\n children = [];\r\n childrenByModelId.set(parentId, children);\r\n }\r\n children.push(modelId);\r\n }\r\n\r\n for (const children of Array.from(childrenByModelId.values())) {\r\n children.sort((a, b) => compareSourceOrder(a, b, sourceOrderByModelId));\r\n }\r\n\r\n const roots = Array.from(modelIds)\r\n .filter((modelId) => !parentByModelId.has(modelId))\r\n .sort((a, b) => compareSourceOrder(a, b, sourceOrderByModelId));\r\n const ordered: number[] = [];\r\n const queue = [...roots];\r\n\r\n while (queue.length > 0) {\r\n const modelId = queue.shift()!;\r\n ordered.push(modelId);\r\n queue.push(...(childrenByModelId.get(modelId) ?? []));\r\n }\r\n\r\n return ordered;\r\n}\r\n\r\nfunction findModelParentId(modelId: number, objectMap: FBXObjectMap): number | undefined {\r\n const parentConnection = objectMap.connections.find((conn) => conn.type === \"OO\" && conn.childId === modelId && objectMap.objects.get(conn.parentId)?.name === \"Model\");\r\n return parentConnection?.parentId;\r\n}\r\n\r\nfunction choosePreferredBoneSource(sources: FBXBoneData[]): FBXBoneData {\r\n return (\r\n sources.find((bone) => bone.isCluster && bone.transformLinkMatrix) ??\r\n sources.find((bone) => bone.isCluster) ??\r\n sources.find((bone) => bone.modelBindPoseMatrix) ??\r\n sources[0]\r\n );\r\n}\r\n\r\nfunction collectTransformLinkWarnings(sourceBonesByModelId: Map<number, FBXBoneData[]>): string[] {\r\n const warnings: string[] = [];\r\n\r\n for (const [modelId, sources] of Array.from(sourceBonesByModelId)) {\r\n const matrices = sources.filter((bone) => bone.isCluster && bone.transformLinkMatrix).map((bone) => bone.transformLinkMatrix!);\r\n if (matrices.length < 2) {\r\n continue;\r\n }\r\n\r\n const first = matrices[0];\r\n if (matrices.some((matrix) => !areMatricesEquivalent(first, matrix, 1e-5))) {\r\n warnings.push(`Model ${modelId.toString()} has differing Cluster.TransformLink matrices across skins`);\r\n }\r\n }\r\n\r\n return warnings;\r\n}\r\n\r\nfunction areMatricesEquivalent(a: Float64Array, b: Float64Array, epsilon: number): boolean {\r\n if (a.length !== b.length) {\r\n return false;\r\n }\r\n for (let i = 0; i < a.length; i++) {\r\n if (Math.abs(a[i] - b[i]) > epsilon) {\r\n return false;\r\n }\r\n }\r\n return true;\r\n}\r\n\r\nfunction createFallbackBone(modelId: number, objectMap: FBXObjectMap): FBXBoneData | null {\r\n const modelNode = objectMap.objects.get(modelId);\r\n if (!modelNode || modelNode.name !== \"Model\") {\r\n return null;\r\n }\r\n\r\n const transform = extractBoneTransform(modelNode);\r\n return {\r\n modelId,\r\n name: cleanFBXName(getPropertyValue<string>(modelNode, 1) ?? `Bone${modelId.toString()}`),\r\n index: -1,\r\n parentIndex: -1,\r\n isCluster: false,\r\n translation: transform.translation,\r\n rotation: transform.rotation,\r\n preRotation: transform.preRotation,\r\n postRotation: transform.postRotation,\r\n rotationPivot: transform.rotationPivot,\r\n scalingPivot: transform.scalingPivot,\r\n rotationOffset: transform.rotationOffset,\r\n scalingOffset: transform.scalingOffset,\r\n scale: transform.scale,\r\n rotationOrder: transform.rotationOrder,\r\n inheritType: transform.inheritType,\r\n clusterMode: \"Unknown\",\r\n bindPoseMatrix: null,\r\n transformLinkMatrix: null,\r\n transformAssociateModelMatrix: null,\r\n modelBindPoseMatrix: null,\r\n diagnostics: [],\r\n };\r\n}\r\n\r\nfunction compareNumber(a: number, b: number): number {\r\n return a < b ? -1 : a > b ? 1 : 0;\r\n}\r\n\r\nfunction compareSourceOrder(a: number, b: number, sourceOrderByModelId: Map<number, number>): number {\r\n const aOrder = sourceOrderByModelId.get(a) ?? Number.MAX_SAFE_INTEGER;\r\n const bOrder = sourceOrderByModelId.get(b) ?? Number.MAX_SAFE_INTEGER;\r\n return aOrder - bOrder || compareNumber(a, b);\r\n}\r\n"]}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type FBXObjectMap } from "./connections.js";
|
|
2
|
+
export type FBXSceneDiagnosticType = "unsupported-constraint" | "unsupported-helper" | "unsupported-deformer" | "unsupported-node-attribute" | "unsupported-pose" | "unsupported-layered-texture" | "connection-graph";
|
|
3
|
+
export interface FBXSceneDiagnostic {
|
|
4
|
+
type: FBXSceneDiagnosticType;
|
|
5
|
+
message: string;
|
|
6
|
+
objectId?: number;
|
|
7
|
+
objectName?: string;
|
|
8
|
+
nodeName?: string;
|
|
9
|
+
subType?: string;
|
|
10
|
+
/** Number of accepted parent graph edges for objectId, when objectId is known. */
|
|
11
|
+
parentCount?: number;
|
|
12
|
+
childCount?: number;
|
|
13
|
+
}
|
|
14
|
+
export declare function extractSceneDiagnostics(objectMap: FBXObjectMap): FBXSceneDiagnostic[];
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/naming-convention, jsdoc/require-param, jsdoc/require-returns */
|
|
2
|
+
import { cleanFBXName, getPropertyValue } from "../types/fbxTypes.js";
|
|
3
|
+
const HELPER_NODE_NAMES = new Set(["Character", "CharacterPose", "ControlSet", "ControlSetPlug", "SelectionSet", "CollectionExclusive"]);
|
|
4
|
+
export function extractSceneDiagnostics(objectMap) {
|
|
5
|
+
const diagnostics = objectMap.diagnostics.map((diagnostic) => ({
|
|
6
|
+
type: "connection-graph",
|
|
7
|
+
message: diagnostic.message,
|
|
8
|
+
objectId: diagnostic.childId,
|
|
9
|
+
subType: diagnostic.reason,
|
|
10
|
+
parentCount: diagnostic.childId === undefined ? undefined : objectMap.connections.filter((connection) => connection.childId === diagnostic.childId).length,
|
|
11
|
+
}));
|
|
12
|
+
for (const [id, node] of Array.from(objectMap.objects)) {
|
|
13
|
+
const subType = getPropertyValue(node, 2) ?? "";
|
|
14
|
+
if (node.name === "Constraint") {
|
|
15
|
+
diagnostics.push(createObjectDiagnostic(objectMap, id, node, "unsupported-constraint", `Constraint '${subType || cleanFBXName(getPropertyValue(node, 1) ?? "")}' is preserved as diagnostic data but not evaluated at runtime.`));
|
|
16
|
+
continue;
|
|
17
|
+
}
|
|
18
|
+
if (HELPER_NODE_NAMES.has(node.name)) {
|
|
19
|
+
diagnostics.push(createObjectDiagnostic(objectMap, id, node, "unsupported-helper", `${node.name} helper data is preserved as diagnostic data but not evaluated at runtime.`));
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
if (node.name === "LayeredTexture") {
|
|
23
|
+
diagnostics.push(createObjectDiagnostic(objectMap, id, node, "unsupported-layered-texture", "LayeredTexture is preserved as diagnostic data; runtime texture layer blending is not implemented."));
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
if (node.name === "Pose" && subType !== "BindPose") {
|
|
27
|
+
diagnostics.push(createObjectDiagnostic(objectMap, id, node, "unsupported-pose", `Pose subtype '${subType}' is preserved as diagnostic data but not evaluated at runtime.`));
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
if (node.name === "Deformer" && !isSupportedDeformer(subType)) {
|
|
31
|
+
diagnostics.push(createObjectDiagnostic(objectMap, id, node, "unsupported-deformer", `Deformer subtype '${subType}' is preserved as diagnostic data but not evaluated at runtime.`));
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
if (node.name === "NodeAttribute" && subType && subType !== "Camera" && subType !== "Light") {
|
|
35
|
+
diagnostics.push(createObjectDiagnostic(objectMap, id, node, "unsupported-node-attribute", `NodeAttribute subtype '${subType}' is preserved as diagnostic data but not converted to a Babylon object.`));
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return diagnostics;
|
|
39
|
+
}
|
|
40
|
+
function isSupportedDeformer(subType) {
|
|
41
|
+
return subType === "Skin" || subType === "Cluster" || subType === "BlendShape" || subType === "BlendShapeChannel";
|
|
42
|
+
}
|
|
43
|
+
function createObjectDiagnostic(objectMap, id, node, type, message) {
|
|
44
|
+
return {
|
|
45
|
+
type,
|
|
46
|
+
message,
|
|
47
|
+
objectId: id,
|
|
48
|
+
objectName: cleanFBXName(getPropertyValue(node, 1) ?? node.name),
|
|
49
|
+
nodeName: node.name,
|
|
50
|
+
subType: getPropertyValue(node, 2) ?? "",
|
|
51
|
+
parentCount: objectMap.connections.filter((connection) => connection.childId === id).length,
|
|
52
|
+
childCount: objectMap.childrenOf.get(id)?.length ?? 0,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=sceneDiagnostics.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sceneDiagnostics.js","sourceRoot":"","sources":["../../../../../dev/loaders/src/FBX/interpreter/sceneDiagnostics.ts"],"names":[],"mappings":"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","sourcesContent":["/* eslint-disable @typescript-eslint/naming-convention, jsdoc/require-param, jsdoc/require-returns */\r\nimport { type FBXNode, cleanFBXName, getPropertyValue } from \"../types/fbxTypes\";\r\n\r\nimport { type FBXObjectMap } from \"./connections\";\r\n\r\nexport type FBXSceneDiagnosticType =\r\n | \"unsupported-constraint\"\r\n | \"unsupported-helper\"\r\n | \"unsupported-deformer\"\r\n | \"unsupported-node-attribute\"\r\n | \"unsupported-pose\"\r\n | \"unsupported-layered-texture\"\r\n | \"connection-graph\";\r\n\r\nexport interface FBXSceneDiagnostic {\r\n type: FBXSceneDiagnosticType;\r\n message: string;\r\n objectId?: number;\r\n objectName?: string;\r\n nodeName?: string;\r\n subType?: string;\r\n /** Number of accepted parent graph edges for objectId, when objectId is known. */\r\n parentCount?: number;\r\n childCount?: number;\r\n}\r\n\r\nconst HELPER_NODE_NAMES = new Set([\"Character\", \"CharacterPose\", \"ControlSet\", \"ControlSetPlug\", \"SelectionSet\", \"CollectionExclusive\"]);\r\n\r\nexport function extractSceneDiagnostics(objectMap: FBXObjectMap): FBXSceneDiagnostic[] {\r\n const diagnostics: FBXSceneDiagnostic[] = objectMap.diagnostics.map((diagnostic) => ({\r\n type: \"connection-graph\",\r\n message: diagnostic.message,\r\n objectId: diagnostic.childId,\r\n subType: diagnostic.reason,\r\n parentCount: diagnostic.childId === undefined ? undefined : objectMap.connections.filter((connection) => connection.childId === diagnostic.childId).length,\r\n }));\r\n\r\n for (const [id, node] of Array.from(objectMap.objects)) {\r\n const subType = getPropertyValue<string>(node, 2) ?? \"\";\r\n if (node.name === \"Constraint\") {\r\n diagnostics.push(\r\n createObjectDiagnostic(\r\n objectMap,\r\n id,\r\n node,\r\n \"unsupported-constraint\",\r\n `Constraint '${subType || cleanFBXName(getPropertyValue<string>(node, 1) ?? \"\")}' is preserved as diagnostic data but not evaluated at runtime.`\r\n )\r\n );\r\n continue;\r\n }\r\n\r\n if (HELPER_NODE_NAMES.has(node.name)) {\r\n diagnostics.push(\r\n createObjectDiagnostic(objectMap, id, node, \"unsupported-helper\", `${node.name} helper data is preserved as diagnostic data but not evaluated at runtime.`)\r\n );\r\n continue;\r\n }\r\n\r\n if (node.name === \"LayeredTexture\") {\r\n diagnostics.push(\r\n createObjectDiagnostic(\r\n objectMap,\r\n id,\r\n node,\r\n \"unsupported-layered-texture\",\r\n \"LayeredTexture is preserved as diagnostic data; runtime texture layer blending is not implemented.\"\r\n )\r\n );\r\n continue;\r\n }\r\n\r\n if (node.name === \"Pose\" && subType !== \"BindPose\") {\r\n diagnostics.push(\r\n createObjectDiagnostic(objectMap, id, node, \"unsupported-pose\", `Pose subtype '${subType}' is preserved as diagnostic data but not evaluated at runtime.`)\r\n );\r\n continue;\r\n }\r\n\r\n if (node.name === \"Deformer\" && !isSupportedDeformer(subType)) {\r\n diagnostics.push(\r\n createObjectDiagnostic(objectMap, id, node, \"unsupported-deformer\", `Deformer subtype '${subType}' is preserved as diagnostic data but not evaluated at runtime.`)\r\n );\r\n continue;\r\n }\r\n\r\n if (node.name === \"NodeAttribute\" && subType && subType !== \"Camera\" && subType !== \"Light\") {\r\n diagnostics.push(\r\n createObjectDiagnostic(\r\n objectMap,\r\n id,\r\n node,\r\n \"unsupported-node-attribute\",\r\n `NodeAttribute subtype '${subType}' is preserved as diagnostic data but not converted to a Babylon object.`\r\n )\r\n );\r\n }\r\n }\r\n\r\n return diagnostics;\r\n}\r\n\r\nfunction isSupportedDeformer(subType: string): boolean {\r\n return subType === \"Skin\" || subType === \"Cluster\" || subType === \"BlendShape\" || subType === \"BlendShapeChannel\";\r\n}\r\n\r\nfunction createObjectDiagnostic(objectMap: FBXObjectMap, id: number, node: FBXNode, type: FBXSceneDiagnosticType, message: string): FBXSceneDiagnostic {\r\n return {\r\n type,\r\n message,\r\n objectId: id,\r\n objectName: cleanFBXName(getPropertyValue<string>(node, 1) ?? node.name),\r\n nodeName: node.name,\r\n subType: getPropertyValue<string>(node, 2) ?? \"\",\r\n parentCount: objectMap.connections.filter((connection) => connection.childId === id).length,\r\n childCount: objectMap.childrenOf.get(id)?.length ?? 0,\r\n };\r\n}\r\n"]}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { type FBXNode } from "../types/fbxTypes.js";
|
|
2
|
+
import { type FBXObjectMap } from "./connections.js";
|
|
3
|
+
export type FBXClusterMode = "Normalize" | "Additive" | "TotalOne" | "Unknown";
|
|
4
|
+
export interface FBXSkinDiagnostic {
|
|
5
|
+
type: "cluster-mode-runtime-unsupported" | "missing-cluster-transform" | "missing-cluster-transform-link" | "missing-bind-pose-matrix" | "associate-model-present";
|
|
6
|
+
message: string;
|
|
7
|
+
boneModelId?: number;
|
|
8
|
+
boneName?: string;
|
|
9
|
+
clusterMode?: FBXClusterMode;
|
|
10
|
+
}
|
|
11
|
+
/** Represents a single bone (cluster) in the FBX skeleton */
|
|
12
|
+
export interface FBXBoneData {
|
|
13
|
+
/** The Model node ID for this bone */
|
|
14
|
+
modelId: number;
|
|
15
|
+
/** Bone name (from the Model node) */
|
|
16
|
+
name: string;
|
|
17
|
+
/** Index of this bone in the skeleton */
|
|
18
|
+
index: number;
|
|
19
|
+
/** Index of the parent bone (-1 for root) */
|
|
20
|
+
parentIndex: number;
|
|
21
|
+
/** Whether this bone corresponds to an FBX Cluster with vertex weights */
|
|
22
|
+
isCluster: boolean;
|
|
23
|
+
/** Local translation from parent (Lcl Translation) */
|
|
24
|
+
translation: [number, number, number];
|
|
25
|
+
/** Local rotation in degrees (Lcl Rotation) */
|
|
26
|
+
rotation: [number, number, number];
|
|
27
|
+
/** Pre-rotation in degrees (applied before Lcl Rotation) */
|
|
28
|
+
preRotation: [number, number, number];
|
|
29
|
+
/** Post-rotation in degrees (applied after Lcl Rotation, inverted) */
|
|
30
|
+
postRotation: [number, number, number];
|
|
31
|
+
/** Rotation pivot point */
|
|
32
|
+
rotationPivot: [number, number, number];
|
|
33
|
+
/** Scaling pivot point */
|
|
34
|
+
scalingPivot: [number, number, number];
|
|
35
|
+
/** Rotation offset */
|
|
36
|
+
rotationOffset: [number, number, number];
|
|
37
|
+
/** Scaling offset */
|
|
38
|
+
scalingOffset: [number, number, number];
|
|
39
|
+
/** Local scale (Lcl Scaling) */
|
|
40
|
+
scale: [number, number, number];
|
|
41
|
+
/** Rotation order: 0=XYZ, 1=XZY, 2=YZX, 3=YXZ, 4=ZXY, 5=ZYX */
|
|
42
|
+
rotationOrder: number;
|
|
43
|
+
/** FBX transform inheritance mode. 0=RrSs, 1=RSrs, 2=Rrs */
|
|
44
|
+
inheritType: number;
|
|
45
|
+
/** Cluster skinning mode */
|
|
46
|
+
clusterMode: FBXClusterMode;
|
|
47
|
+
/** Bind pose transform matrix (cluster Transform, 4x4) */
|
|
48
|
+
bindPoseMatrix: Float64Array | null;
|
|
49
|
+
/** Bone's world transform at bind time (cluster TransformLink, 4x4) */
|
|
50
|
+
transformLinkMatrix: Float64Array | null;
|
|
51
|
+
/** Associate model world transform at bind time (cluster TransformAssociateModel, 4x4) */
|
|
52
|
+
transformAssociateModelMatrix: Float64Array | null;
|
|
53
|
+
/** Model's absolute matrix from the FBX BindPose, when present */
|
|
54
|
+
modelBindPoseMatrix: Float64Array | null;
|
|
55
|
+
/** Recoverable bind/skinning diagnostics for this bone */
|
|
56
|
+
diagnostics: FBXSkinDiagnostic[];
|
|
57
|
+
}
|
|
58
|
+
/** Represents a skin deformer with its clusters */
|
|
59
|
+
export interface FBXSkinData {
|
|
60
|
+
/** Skin deformer ID */
|
|
61
|
+
id: number;
|
|
62
|
+
/** Geometry ID this skin is attached to */
|
|
63
|
+
geometryId: number;
|
|
64
|
+
/** Mesh model world matrix from the FBX BindPose, when present */
|
|
65
|
+
meshBindPoseMatrix: Float64Array | null;
|
|
66
|
+
/** Bones in this skeleton */
|
|
67
|
+
bones: FBXBoneData[];
|
|
68
|
+
/** Per-vertex bone indices, sorted by descending weight and capped for Babylon skinning */
|
|
69
|
+
boneIndices: number[][];
|
|
70
|
+
/** Per-vertex bone weights, matching boneIndices */
|
|
71
|
+
boneWeights: number[][];
|
|
72
|
+
/** Recoverable skinning/bind diagnostics */
|
|
73
|
+
diagnostics: FBXSkinDiagnostic[];
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Extract all skin deformers from the FBX scene.
|
|
77
|
+
* Returns skin data including bone hierarchy and vertex weights.
|
|
78
|
+
*/
|
|
79
|
+
export declare function extractSkins(objectMap: FBXObjectMap): FBXSkinData[];
|
|
80
|
+
export declare function isSkeletonModel(modelNode: FBXNode): boolean;
|
|
81
|
+
export declare function extractBoneTransform(modelNode: FBXNode): {
|
|
82
|
+
translation: [number, number, number];
|
|
83
|
+
rotation: [number, number, number];
|
|
84
|
+
preRotation: [number, number, number];
|
|
85
|
+
postRotation: [number, number, number];
|
|
86
|
+
rotationPivot: [number, number, number];
|
|
87
|
+
scalingPivot: [number, number, number];
|
|
88
|
+
rotationOffset: [number, number, number];
|
|
89
|
+
scalingOffset: [number, number, number];
|
|
90
|
+
scale: [number, number, number];
|
|
91
|
+
rotationOrder: number;
|
|
92
|
+
inheritType: number;
|
|
93
|
+
};
|