Skip to content

Commit

Permalink
Merge pull request #5513 from bghgary/gltf-skin
Browse files Browse the repository at this point in the history
Add support to override mesh for skeleton and link bones to transform nodes
  • Loading branch information
deltakosh committed Nov 15, 2018
2 parents 39456d5 + 03ea237 commit 4050689
Show file tree
Hide file tree
Showing 8 changed files with 94 additions and 57 deletions.
6 changes: 6 additions & 0 deletions dist/preview release/what's new.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,12 +67,18 @@
- Added `Tools.CustomRequestHeaders`, `Tools.UseCustomRequestHeaders`, `Tools.InjectCustomRequestHeaders` to send Custom Request Headers alongside XMLHttpRequest's i.e. when loading files (Tools.Loadfile) from resources requiring special headers like 'Authorization' ([susares](https://github.com/susares))
- Added `.serialize` and `.Parse` functions in `ReflectionProbe` to retrieve reflection probes when parsing a previously serialized material ([julien-moreau](https://github.com/julien-moreau))
- GizmoManager clearGizmoOnEmptyPointerEvent options and onAttachedToMeshObservable event ([TrevorDev](https://github.com/TrevorDev))
- Added support for overriding the mesh used for the world matrix for a mesh with a skeleton ([bghgary](https://github.com/bghgary))
- Added support for linking a bone to a transform node ([bghgary](https://github.com/bghgary))

### glTF Loader

- Added support for mesh instancing for improved performance when multiple nodes point to the same mesh ([bghgary](https://github.com/bghgary))
- Create `TransformNode` objects instead of `Mesh` objects for glTF nodes without geometry ([bghgary](https://github.com/bghgary))
- Added glTF JSON pointers to metadata of nodes, materials, and textures ([bghgary](https://github.com/bghgary))
- Load KTX textures in the gltf2 loader when textureFormat is set on engine ([TrevorDev](https://github.com/TrevorDev))
- Skinned meshes now behave as intended by glTF ([bghgary](https://github.com/bghgary))
- Skinned meshes now set an override mesh instead of reparenting to the `__root__` transform node
- Loaded bones are linked with the transform node created for the corresponding glTF node

### glTF Serializer

Expand Down
62 changes: 25 additions & 37 deletions loaders/src/glTF/2.0/babylon.glTFLoader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -432,6 +432,18 @@ module BABYLON.GLTF2 {
}
}

// Link all Babylon bones for each glTF node with the corresponding Babylon transform node.
// A glTF joint is a pointer to a glTF node in the glTF node hierarchy similar to Unity3D.
if (this.gltf.nodes) {
for (const node of this.gltf.nodes) {
if (node._babylonTransformNode && node._babylonBones) {
for (const babylonBone of node._babylonBones) {
babylonBone.linkTransformNode(node._babylonTransformNode);
}
}
}
}

promises.push(this._loadAnimationsAsync());

this.logClose();
Expand All @@ -445,9 +457,6 @@ module BABYLON.GLTF2 {
callback(babylonMesh);
}
}
else if (node._babylonTransformNode instanceof AbstractMesh) {
callback(node._babylonTransformNode);
}
}

private _getMeshes(): AbstractMesh[] {
Expand Down Expand Up @@ -561,12 +570,6 @@ module BABYLON.GLTF2 {
for (const index of node.children) {
const childNode = ArrayItem.Get(`${context}/children/${index}`, this.gltf.nodes, index);
promises.push(this.loadNodeAsync(`/nodes/${childNode.index}`, childNode, (childBabylonMesh) => {
// See https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#skins (second implementation note)
if (childNode.skin != undefined) {
childBabylonMesh.parent = this._rootBabylonMesh;
return;
}

childBabylonMesh.parent = babylonTransformNode;
}));
}
Expand Down Expand Up @@ -616,16 +619,16 @@ module BABYLON.GLTF2 {
const primitive = mesh.primitives[0];
promises.push(this._loadMeshPrimitiveAsync(`${context}/primitives/${primitive.index}`, name, node, mesh, primitive, (babylonMesh) => {
node._babylonTransformNode = babylonMesh;
node._primitiveBabylonMeshes = [babylonMesh];
}));
}
else {
const babylonTransformNode = new TransformNode(name, this.babylonScene);
node._babylonTransformNode = babylonTransformNode;
node._babylonTransformNode = new TransformNode(name, this.babylonScene);
node._primitiveBabylonMeshes = [];
for (const primitive of primitives) {
promises.push(this._loadMeshPrimitiveAsync(`${context}/primitives/${primitive.index}`, `${name}_primitive${primitive.index}`, node, mesh, primitive, (babylonMesh) => {
babylonMesh.parent = babylonTransformNode;
node._primitiveBabylonMeshes = node._primitiveBabylonMeshes || [];
node._primitiveBabylonMeshes.push(babylonMesh);
babylonMesh.parent = node._babylonTransformNode!;
node._primitiveBabylonMeshes!.push(babylonMesh);
}));
}
}
Expand Down Expand Up @@ -893,14 +896,16 @@ module BABYLON.GLTF2 {
};

if (skin._data) {
const data = skin._data;
return data.promise.then(() => {
assignSkeleton(data.babylonSkeleton);
});
assignSkeleton(skin._data.babylonSkeleton);
return skin._data.promise;
}

const skeletonId = `skeleton${skin.index}`;
const babylonSkeleton = new Skeleton(skin.name || skeletonId, skeletonId, this.babylonScene);

// See https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#skins (second implementation note)
babylonSkeleton.overrideMesh = this._rootBabylonMesh;

this._loadBones(context, skin, babylonSkeleton);
assignSkeleton(babylonSkeleton);

Expand Down Expand Up @@ -1102,12 +1107,6 @@ module BABYLON.GLTF2 {
return Promise.resolve();
}

// Ignore animations targeting TRS of skinned nodes.
// See https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#skins (second implementation note)
if (targetNode.skin != undefined && channel.target.path !== AnimationChannelTargetPath.WEIGHTS) {
return Promise.resolve();
}

const sampler = ArrayItem.Get(`${context}/sampler`, animation.samplers, channel.sampler);
return this._loadAnimationSamplerAsync(`${animationContext}/samplers/${channel.sampler}`, sampler).then((data) => {
let targetPath: string;
Expand Down Expand Up @@ -1234,19 +1233,8 @@ module BABYLON.GLTF2 {
const babylonAnimation = new Animation(animationName, targetPath, 1, animationType);
babylonAnimation.setKeys(keys);

const babylonTransformNode = targetNode._babylonTransformNode!;
const babylonBones = targetNode._babylonBones;
if (babylonBones) {
const babylonAnimationTargets = [babylonTransformNode, ...babylonBones];
for (const babylonAnimationTarget of babylonAnimationTargets) {
babylonAnimationTarget.animations.push(babylonAnimation);
}
babylonAnimationGroup.addTargetedAnimation(babylonAnimation, babylonAnimationTargets);
}
else {
babylonTransformNode.animations.push(babylonAnimation);
babylonAnimationGroup.addTargetedAnimation(babylonAnimation, babylonTransformNode);
}
targetNode._babylonTransformNode!.animations.push(babylonAnimation);
babylonAnimationGroup.addTargetedAnimation(babylonAnimation, targetNode._babylonTransformNode!);
}
});
}
Expand Down
20 changes: 20 additions & 0 deletions src/Bones/babylon.bone.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ module BABYLON {
private _needToDecompose = true;
private _needToCompose = false;

/** @hidden */
public _linkedTransformNode: Nullable<TransformNode> = null;

/** @hidden */
get _matrix(): Matrix {
this._compose();
Expand Down Expand Up @@ -193,6 +196,23 @@ module BABYLON {
return this._absoluteTransform;
}

/**
* Links with the given transform node.
* The local matrix of this bone is copied from the transform node every frame.
* @param transformNode defines the transform node to link to
*/
public linkTransformNode(transformNode: Nullable<TransformNode>): void {
if (this._linkedTransformNode) {
this._skeleton._numBonesWithLinkedTransformNode--;
}

this._linkedTransformNode = transformNode;

if (this._linkedTransformNode) {
this._skeleton._numBonesWithLinkedTransformNode++;
}
}

// Properties (matches AbstractMesh properties)

/** Gets or sets current position (in local space) */
Expand Down
25 changes: 22 additions & 3 deletions src/Bones/babylon.skeleton.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,21 @@ module BABYLON {
*/
export class Skeleton implements IAnimatable {
/**
* Gets the list of child bones
* Defines the list of child bones
*/
public bones = new Array<Bone>();
/**
* Gets an estimate of the dimension of the skeleton at rest
* Defines an estimate of the dimension of the skeleton at rest
*/
public dimensionsAtRest: Vector3;
/**
* Gets a boolean indicating if the root matrix is provided by meshes or by the current skeleton (this is the default value)
* Defines a boolean indicating if the root matrix is provided by meshes or by the current skeleton (this is the default value)
*/
public needInitialSkinMatrix = false;
/**
* Defines a mesh that override the matrix used to get the world matrix (null by default).
*/
public overrideMesh: Nullable<AbstractMesh> = null;

/**
* Gets the list of animations attached to this skeleton
Expand All @@ -37,6 +41,9 @@ module BABYLON {

private _canUseTextureForBones = false;

/** @hidden */
public _numBonesWithLinkedTransformNode = 0;

/**
* Specifies if the skeleton should be serialized
*/
Expand Down Expand Up @@ -370,6 +377,18 @@ module BABYLON {
* Build all resources required to render a skeleton
*/
public prepare(): void {
// Update the local matrix of bones with linked transform nodes.
if (this._numBonesWithLinkedTransformNode > 0) {
for (const bone of this.bones) {
if (bone._linkedTransformNode) {
// Computing the world matrix also computes the local matrix.
bone._linkedTransformNode.computeWorldMatrix();
bone._matrix = bone._linkedTransformNode._localMatrix;
bone.markAsDirty();
}
}
}

if (!this._isDirty) {
return;
}
Expand Down
9 changes: 5 additions & 4 deletions src/Mesh/babylon.abstractMesh.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1136,18 +1136,19 @@ module BABYLON {

/** @hidden */
public _updateBoundingInfo(): AbstractMesh {
const effectiveMesh = (this.skeleton && this.skeleton.overrideMesh) || this;
if (this._boundingInfo) {
this._boundingInfo.update(this.worldMatrixFromCache);
this._boundingInfo.update(effectiveMesh.worldMatrixFromCache);
}
else {
this._boundingInfo = new BoundingInfo(this.absolutePosition, this.absolutePosition, this.worldMatrixFromCache);
this._boundingInfo = new BoundingInfo(this.absolutePosition, this.absolutePosition, effectiveMesh.worldMatrixFromCache);
}
this._updateSubMeshesBoundingInfo(this.worldMatrixFromCache);
this._updateSubMeshesBoundingInfo(effectiveMesh.worldMatrixFromCache);
return this;
}

/** @hidden */
public _updateSubMeshesBoundingInfo(matrix: Matrix): AbstractMesh {
public _updateSubMeshesBoundingInfo(matrix: DeepImmutable<Matrix>): AbstractMesh {
if (!this.subMeshes) {
return this;
}
Expand Down
6 changes: 4 additions & 2 deletions src/Mesh/babylon.mesh.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1505,10 +1505,12 @@ module BABYLON {
return this;
}

const effectiveMesh = (this.skeleton && this.skeleton.overrideMesh) || this;

var sideOrientation = this.overrideMaterialSideOrientation;
if (sideOrientation == null) {
sideOrientation = this._effectiveMaterial.sideOrientation;
if (this._getWorldMatrixDeterminant() < 0) {
if (effectiveMesh._getWorldMatrixDeterminant() < 0) {
sideOrientation = (sideOrientation === Material.ClockWiseSideOrientation ? Material.CounterClockWiseSideOrientation : Material.ClockWiseSideOrientation);
}
}
Expand All @@ -1526,7 +1528,7 @@ module BABYLON {
this._bind(subMesh, effect, fillMode);
}

var world = this.getWorldMatrix();
var world = effectiveMesh.getWorldMatrix();

if (this._effectiveMaterial._storeEffectOnSubMeshes) {
this._effectiveMaterial.bindForSubMesh(world, this, subMesh);
Expand Down
2 changes: 1 addition & 1 deletion src/Mesh/babylon.subMesh.ts
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,7 @@ module BABYLON {
* @param world defines the world matrix to use to update the bounding info
* @returns the submesh
*/
public updateBoundingInfo(world: Matrix): SubMesh {
public updateBoundingInfo(world: DeepImmutable<Matrix>): SubMesh {
let boundingInfo = this.getBoundingInfo();

if (!boundingInfo) {
Expand Down
21 changes: 11 additions & 10 deletions src/Mesh/babylon.transformNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,8 @@ module BABYLON {
// Cache
/** @hidden */
public _poseMatrix: Matrix;
private _localWorld = Matrix.Zero();
/** @hidden */
public _localMatrix = Matrix.Zero();

private _absolutePosition = Vector3.Zero();
private _pivotMatrix = Matrix.Identity();
Expand Down Expand Up @@ -413,7 +414,7 @@ module BABYLON {
*/
public setPositionWithLocalVector(vector3: Vector3): TransformNode {
this.computeWorldMatrix();
this.position = Vector3.TransformNormal(vector3, this._localWorld);
this.position = Vector3.TransformNormal(vector3, this._localMatrix);
return this;
}

Expand All @@ -424,7 +425,7 @@ module BABYLON {
public getPositionExpressedInLocalSpace(): Vector3 {
this.computeWorldMatrix();
const invLocalWorldMatrix = Tmp.Matrix[0];
this._localWorld.invertToRef(invLocalWorldMatrix);
this._localMatrix.invertToRef(invLocalWorldMatrix);
return Vector3.TransformNormal(this.position, invLocalWorldMatrix);
}

Expand All @@ -435,7 +436,7 @@ module BABYLON {
*/
public locallyTranslate(vector3: Vector3): TransformNode {
this.computeWorldMatrix(true);
this.position = Vector3.TransformCoordinates(vector3, this._localWorld);
this.position = Vector3.TransformCoordinates(vector3, this._localMatrix);
return this;
}

Expand Down Expand Up @@ -940,7 +941,7 @@ module BABYLON {
}

// Local world
Tmp.Matrix[5].multiplyToRef(Tmp.Matrix[2], this._localWorld);
Tmp.Matrix[5].multiplyToRef(Tmp.Matrix[2], this._localMatrix);

// Parent
if (this.parent && this.parent.getWorldMatrix) {
Expand All @@ -952,22 +953,22 @@ module BABYLON {
Tmp.Matrix[5].copyFrom(this.parent.getWorldMatrix());
}

this._localWorld.getTranslationToRef(Tmp.Vector3[5]);
this._localMatrix.getTranslationToRef(Tmp.Vector3[5]);
Vector3.TransformCoordinatesToRef(Tmp.Vector3[5], Tmp.Matrix[5], Tmp.Vector3[5]);
this._worldMatrix.copyFrom(this._localWorld);
this._worldMatrix.copyFrom(this._localMatrix);
this._worldMatrix.setTranslation(Tmp.Vector3[5]);

} else {
if (this._transformToBoneReferal) {
this._localWorld.multiplyToRef(this.parent.getWorldMatrix(), Tmp.Matrix[6]);
this._localMatrix.multiplyToRef(this.parent.getWorldMatrix(), Tmp.Matrix[6]);
Tmp.Matrix[6].multiplyToRef(this._transformToBoneReferal.getWorldMatrix(), this._worldMatrix);
} else {
this._localWorld.multiplyToRef(this.parent.getWorldMatrix(), this._worldMatrix);
this._localMatrix.multiplyToRef(this.parent.getWorldMatrix(), this._worldMatrix);
}
}
this._markSyncedWithParent();
} else {
this._worldMatrix.copyFrom(this._localWorld);
this._worldMatrix.copyFrom(this._localMatrix);
}

// Normal matrix
Expand Down

0 comments on commit 4050689

Please sign in to comment.