Object.assign(pc, function () {
/**
* @constructor
* @name pc.Skin
* @classdesc A skin contains data about the bones in a hierarchy that drive a skinned mesh animation.
* Specifically, the skin stores the bone name and inverse bind matrix and for each bone.
* Inverse bind matrices are instrumental in the mathematics of vertex skinning.
* @param {pc.GraphicsDevice} graphicsDevice The graphics device used to manage this skin.
* @param {pc.Mat4[]} ibp The array of inverse bind matrices.
* @param {String[]} boneNames The array of bone names for the bones referenced by this skin.
*/
var _invMatrix = new pc.Mat4();
var Skin = function (graphicsDevice, ibp, boneNames) {
// Constant between clones
this.device = graphicsDevice;
this.inverseBindPose = ibp;
this.boneNames = boneNames;
};
/**
* @constructor
* @name pc.SkinInstance
* @classdesc A skin instance is responsible for generating the matrix palette that is used to
* skin vertices from object space to world space.
* @param {pc.Skin} skin The skin that will provide the inverse bind pose matrices to
* generate the final matrix palette.
*/
var SkinInstance = function (skin) {
this.skin = skin;
this._dirty = true;
// Unique per clone
this.bones = [];
var numBones = skin.inverseBindPose.length;
var device = skin.device;
if (device.supportsBoneTextures) {
// Calculate a square texture dimension to hold bone matrices
// where a matrix takes up 4 texels:
// RGBA (Row 1), RGBA (Row 2), RGBA (Row 3), RGBA (Row 4)
// So:
// 8x8 holds: 64 / 4 = Up to 16 bones
// 16x16 holds: 256 / 4 = Up to 64 bones
// 32x32 holds: 1024 / 4 = Up to 256 bones
// 64x64 holds: 4096 / 4 = Up to 1024 bones
// Let's assume for now no one will create a hierarchy of more
// than 1024 bones!
var size;
if (numBones > 256)
size = 64;
else if (numBones > 64)
size = 32;
else if (numBones > 16)
size = 16;
else
size = 8;
this.boneTexture = new pc.Texture(device, {
width: size,
height: size,
format: pc.PIXELFORMAT_RGBA32F,
mipmaps: false,
minFilter: pc.FILTER_NEAREST,
magFilter: pc.FILTER_NEAREST
});
this.boneTexture.name = 'skin';
this.matrixPalette = this.boneTexture.lock();
} else {
this.matrixPalette = new Float32Array(numBones * 16);
}
this.matrices = [];
for (var i = 0; i < numBones; i++) {
this.matrices[i] = new pc.Mat4();
}
};
Object.assign(SkinInstance.prototype, {
updateMatrices: function (rootNode) {
_invMatrix.copy(rootNode.getWorldTransform()).invert();
for (var i = this.bones.length - 1; i >= 0; i--) {
this.matrices[i].mul2(_invMatrix, this.bones[i].getWorldTransform()); // world space -> rootNode space
this.matrices[i].mul2(this.matrices[i], this.skin.inverseBindPose[i]); // rootNode space -> bind space
}
},
updateMatrixPalette: function () {
var pe;
var mp = this.matrixPalette;
var base;
for (var i = this.bones.length - 1; i >= 0; i--) {
pe = this.matrices[i].data;
// Copy the matrix into the palette, ready to be sent to the vertex shader
base = i * 16;
mp[base] = pe[0];
mp[base + 1] = pe[1];
mp[base + 2] = pe[2];
mp[base + 3] = pe[3];
mp[base + 4] = pe[4];
mp[base + 5] = pe[5];
mp[base + 6] = pe[6];
mp[base + 7] = pe[7];
mp[base + 8] = pe[8];
mp[base + 9] = pe[9];
mp[base + 10] = pe[10];
mp[base + 11] = pe[11];
mp[base + 12] = pe[12];
mp[base + 13] = pe[13];
mp[base + 14] = pe[14];
mp[base + 15] = pe[15];
}
// TODO: this is a bit strange looking. Change the Texture API to do a reupload
if (this.skin.device.supportsBoneTextures) {
this.boneTexture.lock();
this.boneTexture.unlock();
}
}
});
return {
Skin: Skin,
SkinInstance: SkinInstance
};
}());