2021SC@SDUSC
目录
9.updateJointsBoundingBoxes(geometry)
clay.Skeleton类能够构造物体或人的骨架,绑定关节,从而可以进行相应的动作
clay.Skeleton类的作用有获取,添加或移除skinning剪辑,通过更新数组来更新相应的骨架,绑定关节和骨架等。
该函数是骨骼的构造函数,能够构造物体或人物的骨架,从而实现相关的动作。
该类的参数,类型及作用如下:
name | type | 是否必须 | 默认值 | 作用 |
relativeRootNode | clay.Node | 否 | null | 不影响关节变换的相对根节点。 |
name | string | 否 | '' | 骨骼的名称 |
joints | Array.<clay.Joint> | 否 | [] | 关节 |
boundingBox | clay.BoundingBox | 否 | null | 具有约束几何的边界框。 |
_clips | array | 否 | [] | 剪辑 |
_invBindPoseMatricesArray | array | 否 | null | 矩阵到关节空间(相对于根关节) |
_jointMatricesSubArrays | array | 否 | [] | 每次计算矩阵时使用子数组而不是复制回 |
_skinMatricesArray | array | 否 | null | skin的矩阵数组 |
_skinMatricesSubArrays | array | 否 | [] | skin的矩阵子数组 |
_subSkinMatricesArray | object | 否 | {} | skin的子矩阵数组 |
其源代码如下:
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: {}
};
}
从源代码中可以看出,clay.Skeleton共有10个成员变量,成员变量的类型及作用如上表所示。
该函数的作用是添加蒙皮剪辑并在剪辑和骨架之间创建贴图。
参数有clip,类型为clay.animation.SkinningClip,表示添加的skinning clip,mapRule,类型为object,表示骨架中的关节名称和剪辑中的关节名称之间的映射。
其源代码为:
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;
}
从源代码中可以看出,该函数对本对象的_clips进行遍历,判断clip是否存在,若_clips中存在clip剪辑,则返回。
接着,创建一个maps数组,对本对象的joints进行遍历,获取joints的长度,并将maps[i]初始化为-1。然后,继续对clip的tracks进行遍历,对于joints的每一个对象,创建一个局部变量joint获取,同时创建一个track对象,获取clip的track中的每一个对象,创建jointName设置为joint的name,如果mapRule存在映射,则设置jointName为mapRule的jointName,然后判断track的name是否等于jointName,通过之前的for循环,若找到相等的,则将map的第j个设置为i,退出joints的循环。并不断进行下去。
最后,在_clips中添加对象maps和clip并返回_clips的长度减一。从而实现添加skinning剪辑,并设置clip和skeleton的对应关系。
该函数的作用是通过索引获取一个剪辑。
参数为index,类型为number,表示获取剪辑的索引。
其源代码为:
getClip: function (index) {
if (this._clips[index]) {
return this._clips[index].clip;
}
}
从源代码中可以看出,该函数判断本对象的_clips数组中是否包含索引为index的对象,若包含,则返回_clips中索引为index的clip,从而获取特定索引的剪辑。
该函数的作用是获取剪辑的长度。
无参数。
其源代码为:
getClipNumber: function () {
return this._clips.length;
}
从源代码中,该函数将_clips数组的长度返回,从而获取剪辑的长度。
该函数的作用是移除指定的clip剪辑。
参数为clip,类型为clay.animation.SkinningClip,表示需要移除的剪辑。
其源代码为:
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);
}
}
从源代码中可以看出,该函数创建一个局部变量idx并初始化为-1,然后对本对象的_clips数组进行遍历,若其中包含clip,则设置idx为_clips数组中clip的索引,退出遍历。若idx大于0,即存在该剪辑,则调用splice函数将idx位置上的剪辑移除。从而实现移除特定的剪辑。
该函数的作用是移除所有的剪辑。
无参数。
其源代码为:
removeClipsAll: function () {
this._clips = [];
}
从源代码中可以看出,该函数将本对象的_clips数组设置为空数组,从而移除所有的剪辑。
该函数的作用是更新skinning矩阵。
无参数。
其源代码为:
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);
}
}
}
}
从源代码中可以看出,该函数首先调用本对象的_setPose方法。setPose方法如下:
_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;
}
}
}
该函数判断_clips是否有剪辑,有则将第1个skinning剪辑的clip和maps赋给新建的局部变量clip和maps,然后对joints进行遍历,获取每一个数,如果maps中无相应的映射,则进行下一次循环。若有,则获取clip的tracks中的第maps[i]个对象。最后,判断pose的channels中的position位置,rotation旋转和scale缩放,并将其赋值给joint中的node,最后设置joint中node的_dirty为true。从而设置相应的属性。
然后,获取_jointsBoundingBoxes并赋给jointsBoundingBoxes,对joints进行遍历,对this._skinMatricesSubArrays[i],joint.node.worldTransform.array和this._jointMatricesSubArrays[i]进行乘法操作。
最后,判断本对象是否有boundingBox,若有,则设置最大值为无穷大,最小值为无穷小,然后对joints进行遍历,获取其中每一个对象赋给joint,获取jointsBoundingBoxes的每一个对象赋给bbox,若bbox的__updated为真,则对bbox执行tmpBoundingBox的copy方法,并将本对象的_skinMatricesSubArrays[i]赋给tmpMat4.array,最后调用tmpBoundingBox的applyTransform方法处理tmpMat4,最后对tmpBoundingBox进行union处理,实现矩阵的更新。
该函数的作用是从节点变换计算联合矩阵。
无参数。
其源代码为:
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();
};
})()
从源代码可以看出,该函数首先创建4维数组m4。
接着,将_invBindPoseMatricesArray和_skinMatricesArray设置为Float32Array,长度为joints的长度乘以16。然后,第joints进行遍历,获取每一个对象,调用mat4的copy方法复制joint.node.worldTransform.array到m4,同时对m4进行invert,并且设置offset为遍历的次数乘以16,对_invBindPoseMatricesArray的offset+j个对象设置为m4[j]。当遍历结束后,调用updateMatricesSubArrays方法对子数组也更新,从而进行更新。
该函数的作用是更新绑定到几何体的每个关节的 boundingBox(假设骨架和几何关节匹配)。
参数为geometry,类型为clay.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');
}
}
从源代码中可以看出,首先获取geometry的attributes,并获取attributes的position,joint和weight。并初始化jointsBoundingBoxes为空数组,其长度为joints的长度,其每一个对象均为BoundingBox,其__updated为false。
然后初始化vtxJoint,vtxPos,vtxWeight为空数组,maxJointIdx为0,对geometry进行遍历,获取相应位置的vtxJoint,vtxPos,vtxWeight,并进行循环遍历,进行4次,如果vtxWeight的第k个数大于0.01,则获取vtxJoint第k个数赋值jointIdx,和maxJointIdx相比,取大值赋给maxJointIdx,并获得jointsBoundingBoxes的最小值和最大值,完成后,设置jointsBoundingBoxes的第jointIdx的__updated为true,最后获取vec3中的最小值和最大值。
接着,将jointsBoundingBoxes赋值给_jointsBoundingBoxes,初始化boundingBox,判断maxJointIdx是否小于joints的长度减1,若不小于则报错,从而实现关节和骨架的绑定。