@babylonjs/loaders 9.17.0 → 9.18.0

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.
@@ -87,6 +87,7 @@ export declare class FBXFileLoader implements ISceneLoaderPluginAsync, ISceneLoa
87
87
  private _setAssetContainer;
88
88
  private static _computeFBXAxisConversionMatrix;
89
89
  private _buildModel;
90
+ private _linkSkeletonsToTransformNodes;
90
91
  private static _modelSubtreeMatchesNameFilter;
91
92
  private static _applyModelMetadata;
92
93
  private _createMesh;
@@ -238,6 +238,7 @@ export class FBXFileLoader {
238
238
  for (const model of fbxScene.rootModels) {
239
239
  this._buildModel(model, scene, assetRoot, assetRoot, fbxWorldIdentity, materialCache, nameFilter, meshes, transformNodes, skeletonByGeometryId, skinByGeometryId, skinBindingByGeometryId, modelIdToNode, cullingConflictMaterialIds, cullingMaterialCloneCache);
240
240
  }
241
+ this._linkSkeletonsToTransformNodes(fbxScene.rigs, skeletonByRigId, modelIdToNode, transformNodes, scene);
241
242
  // Link non-skinned child meshes/nodes to their parent bones so they
242
243
  // follow skeletal animation. Preserve their current world matrix when
243
244
  // switching from the FBX model hierarchy to Babylon's bone parent.
@@ -246,9 +247,15 @@ export class FBXFileLoader {
246
247
  if (!skeleton) {
247
248
  continue;
248
249
  }
249
- const boneModelIds = new Set(rig.bones.map((b) => b.modelId));
250
250
  const skinnedMesh = meshes.find((m) => m.skeleton === skeleton) ?? null;
251
251
  const boneReferenceNode = skinnedMesh ?? rootNode;
252
+ const boneTransformNodes = new Set();
253
+ for (const skeletonBone of skeleton.bones) {
254
+ const transformNode = skeletonBone.getTransformNode();
255
+ if (transformNode) {
256
+ boneTransformNodes.add(transformNode);
257
+ }
258
+ }
252
259
  for (const boneData of rig.bones) {
253
260
  if (!boneData.isCluster) {
254
261
  continue;
@@ -261,15 +268,7 @@ export class FBXFileLoader {
261
268
  // Find direct children of this bone's TransformNode that aren't bones themselves
262
269
  for (const child of [...boneNode.getChildren()]) {
263
270
  const childTransform = child;
264
- // Check if this child is itself a bone — if so, skip it
265
- let childIsBone = false;
266
- for (const [modelId, node] of Array.from(modelIdToNode)) {
267
- if (node === childTransform && boneModelIds.has(modelId)) {
268
- childIsBone = true;
269
- break;
270
- }
271
- }
272
- if (!childIsBone) {
271
+ if (!boneTransformNodes.has(childTransform)) {
273
272
  const childWorld = childTransform.computeWorldMatrix(true).clone();
274
273
  const boneReferenceWorld = FBXFileLoader._getBoneReferenceWorldMatrix(skeleton, bone, boneReferenceNode, skinnedMesh);
275
274
  const boneReferenceWorldInv = new Matrix();
@@ -472,6 +471,35 @@ export class FBXFileLoader {
472
471
  }
473
472
  }
474
473
  }
474
+ _linkSkeletonsToTransformNodes(rigs, skeletonByRigId, modelIdToNode, transformNodes, scene) {
475
+ for (const rig of rigs) {
476
+ const skeleton = skeletonByRigId.get(rig.id);
477
+ if (!skeleton) {
478
+ continue;
479
+ }
480
+ for (const boneData of rig.bones) {
481
+ const bone = this._getSourceBone(skeleton, boneData.index);
482
+ const boneNode = modelIdToNode.get(boneData.modelId);
483
+ if (!bone || !boneNode) {
484
+ continue;
485
+ }
486
+ const scaleCompensationHelper = this._getScaleCompensationHelper(skeleton, boneData.index);
487
+ if (scaleCompensationHelper) {
488
+ const helperNode = new TransformNode(scaleCompensationHelper.name, scene);
489
+ helperNode.parent = boneNode.parent;
490
+ boneNode.parent = helperNode;
491
+ FBXFileLoader._applyMatrixToTransform(helperNode, scaleCompensationHelper.getLocalMatrix());
492
+ FBXFileLoader._applyMatrixToTransform(boneNode, bone.getLocalMatrix());
493
+ scaleCompensationHelper.linkTransformNode(helperNode);
494
+ transformNodes.push(helperNode);
495
+ }
496
+ else {
497
+ FBXFileLoader._applyMatrixToTransform(boneNode, bone.getLocalMatrix());
498
+ }
499
+ bone.linkTransformNode(boneNode);
500
+ }
501
+ }
502
+ }
475
503
  static _modelSubtreeMatchesNameFilter(model, nameFilter) {
476
504
  for (const child of model.children) {
477
505
  if (child.geometry && child.subType === "Mesh" && nameFilter(child.name)) {
@@ -1543,6 +1571,20 @@ export class FBXFileLoader {
1543
1571
  return null;
1544
1572
  }
1545
1573
  const animGroup = new AnimationGroup(animStack.name, scene);
1574
+ const animatedBoneTargetProperties = new Map();
1575
+ const addBoneAnimation = (animation, bone) => {
1576
+ const target = bone.getTransformNode() ?? bone;
1577
+ let targetProperties = animatedBoneTargetProperties.get(target);
1578
+ if (!targetProperties) {
1579
+ targetProperties = new Set();
1580
+ animatedBoneTargetProperties.set(target, targetProperties);
1581
+ }
1582
+ else if (targetProperties.has(animation.targetProperty)) {
1583
+ return;
1584
+ }
1585
+ targetProperties.add(animation.targetProperty);
1586
+ animGroup.addTargetedAnimation(animation, target);
1587
+ };
1546
1588
  // Build a map from model ID to resolved rig bones. A single FBX model ID
1547
1589
  // should only appear once per resolved rig, but keeping an array preserves
1548
1590
  // the previous animation fan-out behavior for any future duplicate rigs.
@@ -1609,7 +1651,7 @@ export class FBXFileLoader {
1609
1651
  }
1610
1652
  for (const { bone, animations } of this._buildInheritedRigBoneAnimations(rig, skeleton, boneCurves, modelIdToData, inheritType2ModelIds, animStack.startTime, animStack.stopTime)) {
1611
1653
  for (const animation of animations) {
1612
- animGroup.addTargetedAnimation(animation, bone);
1654
+ addBoneAnimation(animation, bone);
1613
1655
  }
1614
1656
  }
1615
1657
  }
@@ -1625,7 +1667,7 @@ export class FBXFileLoader {
1625
1667
  for (const bone of bones) {
1626
1668
  const animations = this._buildBoneAnimations(curveNodes, bone.name, modelData, animStack.startTime, animStack.stopTime, this._bindRestBones.has(bone) ? bone.getBindMatrix() : undefined);
1627
1669
  for (const animation of animations) {
1628
- animGroup.addTargetedAnimation(animation, bone);
1670
+ addBoneAnimation(animation, bone);
1629
1671
  }
1630
1672
  }
1631
1673
  }