import Base from './core/Base';
import Joint from './Joint';
import Texture2D from './Texture2D';
import Texture from './Texture';
import BoundingBox from './math/BoundingBox';
import Matrix4 from './math/Matrix4';
import mat4 from './glmatrix/mat4';
import vec3 from './glmatrix/vec3';
import quat from './glmatrix/quat';
var tmpBoundingBox = new BoundingBox();
var tmpMat4 = new Matrix4();
/**
* @constructor clay.Skeleton
*/
var Skeleton = Base.extend(function () {
return /** @lends clay.Skeleton# */{
/**
* Relative root node that not affect transform of joint.
* @type {clay.Node}
*/
relativeRootNode: null,
/**
* @type {string}
*/
name: '',
/**
* joints
* @type {Array.<clay.Joint>}
*/
joints: [],
/**
* bounding box with bound geometry.
* @type {clay.BoundingBox}
*/
boundingBox: null,
_clips: [],
// Matrix to joint space (relative to root joint)
_invBindPoseMatricesArray: null,
// Use subarray instead of copy back each time computing matrix
// http://jsperf.com/subarray-vs-copy-for-array-transform/5
_jointMatricesSubArrays: [],
// jointMatrix * currentPoseMatrix
// worldTransform is relative to the root bone
// still in model space not world space
_skinMatricesArray: null,
_skinMatricesSubArrays: [],
_subSkinMatricesArray: {}
};
},
/** @lends clay.Skeleton.prototype */
{
/**
* Add a skinning clip and create a map between clip and skeleton
* @param {clay.animation.SkinningClip} clip
* @param {Object} [mapRule] Map between joint name in skeleton and joint name in clip
*/
addClip: function (clip, mapRule) {
// Clip have been exists in
for (var i = 0; i < this._clips.length; i++) {
if (this._clips[i].clip === clip) {
return;
}
}
// Map the joint index in skeleton to joint pose index in clip
var maps = [];
for (var i = 0; i < this.joints.length; i++) {
maps[i] = -1;
}
// Create avatar
for (var i = 0; i < clip.tracks.length; i++) {
for (var j = 0; j < this.joints.length; j++) {
var joint = this.joints[j];
var track = clip.tracks[i];
var jointName = joint.name;
if (mapRule) {
jointName = mapRule[jointName];
}
if (track.name === jointName) {
maps[j] = i;
break;
}
}
}
this._clips.push({
maps: maps,
clip: clip
});
return this._clips.length - 1;
},
/**
* @param {clay.animation.SkinningClip} clip
*/
removeClip: function (clip) {
var idx = -1;
for (var i = 0; i < this._clips.length; i++) {
if (this._clips[i].clip === clip) {
idx = i;
break;
}
}
if (idx > 0) {
this._clips.splice(idx, 1);
}
},
/**
* Remove all clips
*/
removeClipsAll: function () {
this._clips = [];
},
/**
* Get clip by index
* @param {number} index
*/
getClip: function (index) {
if (this._clips[index]) {
return this._clips[index].clip;
}
},
/**
* @return {number}
*/
getClipNumber: function () {
return this._clips.length;
},
/**
* Calculate joint matrices from node transform
* @function
*/
updateJointMatrices: (function () {
var m4 = mat4.create();
return function () {
this._invBindPoseMatricesArray = new Float32Array(this.joints.length * 16);
this._skinMatricesArray = new Float32Array(this.joints.length * 16);
for (var i = 0; i < this.joints.length; i++) {
var joint = this.joints[i];
mat4.copy(m4, joint.node.worldTransform.array);
mat4.invert(m4, m4);
var offset = i * 16;
for (var j = 0; j < 16; j++) {
this._invBindPoseMatricesArray[offset + j] = m4[j];
}
}
this.updateMatricesSubArrays();
};
})(),
/**
* Update boundingBox of each joint bound to geometry.
* ASSUME skeleton and geometry joints are matched.
* @param {clay.Geometry} geometry
*/
updateJointsBoundingBoxes: function (geometry) {
var attributes = geometry.attributes;
var positionAttr = attributes.position;
var jointAttr = attributes.joint;
var weightAttr = attributes.weight;
var jointsBoundingBoxes = [];
for (var i = 0; i < this.joints.length; i++) {
jointsBoundingBoxes[i] = new BoundingBox();
jointsBoundingBoxes[i].__updated = false;
}
var vtxJoint = [];
var vtxPos = [];
var vtxWeight = [];
var maxJointIdx = 0;
for (var i = 0; i < geometry.vertexCount; i++) {
jointAttr.get(i, vtxJoint);
positionAttr.get(i, vtxPos);
weightAttr.get(i, vtxWeight);
for (var k = 0; k < 4; k++) {
if (vtxWeight[k] > 0.01) {
var jointIdx = vtxJoint[k];
maxJointIdx = Math.max(maxJointIdx, jointIdx);
var min = jointsBoundingBoxes[jointIdx].min.array;
var max = jointsBoundingBoxes[jointIdx].max.array;
jointsBoundingBoxes[jointIdx].__updated = true;
min = vec3.min(min, min, vtxPos);
max = vec3.max(max, max, vtxPos);
}
}
}
this._jointsBoundingBoxes = jointsBoundingBoxes;
this.boundingBox = new BoundingBox();
if (maxJointIdx < this.joints.length - 1) {
console.warn('Geometry joints and skeleton joints don\'t match');
}
},
setJointMatricesArray: function (arr) {
this._invBindPoseMatricesArray = arr;
this._skinMatricesArray = new Float32Array(arr.length);
this.updateMatricesSubArrays();
},
updateMatricesSubArrays: function () {
for (var i = 0; i < this.joints.length; i++) {
this._jointMatricesSubArrays[i] = this._invBindPoseMatricesArray.subarray(i * 16, (i+1) * 16);
this._skinMatricesSubArrays[i] = this._skinMatricesArray.subarray(i * 16, (i+1) * 16);
}
},
/**
* Update skinning matrices
*/
update: function () {
this._setPose();
var jointsBoundingBoxes = this._jointsBoundingBoxes;
for (var i = 0; i < this.joints.length; i++) {
var joint = this.joints[i];
mat4.multiply(
this._skinMatricesSubArrays[i],
joint.node.worldTransform.array,
this._jointMatricesSubArrays[i]
);
}
if (this.boundingBox) {
this.boundingBox.min.set(Infinity, Infinity, Infinity);
this.boundingBox.max.set(-Infinity, -Infinity, -Infinity);
for (var i = 0; i < this.joints.length; i++) {
var joint = this.joints[i];
var bbox = jointsBoundingBoxes[i];
if (bbox.__updated) {
tmpBoundingBox.copy(bbox);
tmpMat4.array = this._skinMatricesSubArrays[i];
tmpBoundingBox.applyTransform(tmpMat4);
this.boundingBox.union(tmpBoundingBox);
}
}
}
},
getSubSkinMatrices: function (meshId, joints) {
var subArray = this._subSkinMatricesArray[meshId];
if (!subArray) {
subArray
= this._subSkinMatricesArray[meshId]
= new Float32Array(joints.length * 16);
}
var cursor = 0;
for (var i = 0; i < joints.length; i++) {
var idx = joints[i];
for (var j = 0; j < 16; j++) {
subArray[cursor++] = this._skinMatricesArray[idx * 16 + j];
}
}
return subArray;
},
getSubSkinMatricesTexture: function (meshId, joints) {
var skinMatrices = this.getSubSkinMatrices(meshId, joints);
var size;
var numJoints = this.joints.length;
if (numJoints > 256) {
size = 64;
}
else if (numJoints > 64) {
size = 32;
}
else if (numJoints > 16) {
size = 16;
}
else {
size = 8;
}
var texture = this._skinMatricesTexture = this._skinMatricesTexture || new Texture2D({
type: Texture.FLOAT,
minFilter: Texture.NEAREST,
magFilter: Texture.NEAREST,
useMipmap: false,
flipY: false
});
texture.width = size;
texture.height = size;
if (!texture.pixels || texture.pixels.length !== size * size * 4) {
texture.pixels = new Float32Array(size * size * 4);
}
texture.pixels.set(skinMatrices);
texture.dirty();
return texture;
},
getSkinMatricesTexture: function () {
return this._skinMatricesTexture;
},
_setPose: function () {
if (this._clips[0]) {
var clip = this._clips[0].clip;
var maps = this._clips[0].maps;
for (var i = 0; i < this.joints.length; i++) {
var joint = this.joints[i];
if (maps[i] === -1) {
continue;
}
var pose = clip.tracks[maps[i]];
// Not update if there is no data.
// PENDING If sync pose.position, pose.rotation, pose.scale
if (pose.channels.position) {
vec3.copy(joint.node.position.array, pose.position);
}
if (pose.channels.rotation) {
quat.copy(joint.node.rotation.array, pose.rotation);
}
if (pose.channels.scale) {
vec3.copy(joint.node.scale.array, pose.scale);
}
joint.node.position._dirty = true;
joint.node.rotation._dirty = true;
joint.node.scale._dirty = true;
}
}
},
clone: function (clonedNodesMap) {
var skeleton = new Skeleton();
skeleton.name = this.name;
for (var i = 0; i < this.joints.length; i++) {
var newJoint = new Joint();
var joint = this.joints[i];
newJoint.name = joint.name;
newJoint.index = joint.index;
if (clonedNodesMap) {
var newNode = clonedNodesMap[joint.node.__uid__];
if (!newNode) {
// PENDING
console.warn('Can\'t find node');
}
newJoint.node = newNode || joint.node;
}
else {
newJoint.node = joint.node;
}
skeleton.joints.push(newJoint);
}
if (this._invBindPoseMatricesArray) {
var len = this._invBindPoseMatricesArray.length;
skeleton._invBindPoseMatricesArray = new Float32Array(len);
for (var i = 0; i < len; i++) {
skeleton._invBindPoseMatricesArray[i] = this._invBindPoseMatricesArray[i];
}
skeleton._skinMatricesArray = new Float32Array(len);
skeleton.updateMatricesSubArrays();
}
skeleton._jointsBoundingBoxe = (this._jointsBoundingBoxes || []).map(function (bbox) {
return bbox.clone();
});
skeleton.update();
return skeleton;
}
});
export default Skeleton;