当前位置: 首页 > 工具软件 > ClayGL > 使用案例 >

2021SC@SDUSC山东大学软件学院软件工程应用与实践——claygl(源代码分析11)

葛成济
2023-12-01

2021SC@SDUSC

目录

一.clay.Skeleton类概述

二.clay.Skeleton类的作用

三.clay.Skeleton类源码分析

1.new Skeleton()

2.addClip(clip, mapRule opt)

3.getClip(index)

4.getClipNumber() → {number}

5.removeClip(clip)

6.removeClipsAll()

7.update()

8.updateJointMatrices()

9.updateJointsBoundingBoxes(geometry)


一.clay.Skeleton类概述

    clay.Skeleton类能够构造物体或人的骨架,绑定关节,从而可以进行相应的动作

二.clay.Skeleton类的作用

    clay.Skeleton类的作用有获取,添加或移除skinning剪辑,通过更新数组来更新相应的骨架,绑定关节和骨架等。

三.clay.Skeleton类源码分析

1.new Skeleton()

    该函数是骨骼的构造函数,能够构造物体或人物的骨架,从而实现相关的动作。

    该类的参数,类型及作用如下:

nametype是否必须默认值作用
relativeRootNodeclay.Nodenull不影响关节变换的相对根节点。
namestring''骨骼的名称
jointsArray.<clay.Joint>[]关节
boundingBoxclay.BoundingBoxnull具有约束几何的边界框。
_clipsarray[]剪辑
_invBindPoseMatricesArrayarraynull矩阵到关节空间(相对于根关节)
_jointMatricesSubArraysarray[]每次计算矩阵时使用子数组而不是复制回
_skinMatricesArrayarraynullskin的矩阵数组
_skinMatricesSubArraysarray[]skin的矩阵子数组
_subSkinMatricesArrayobject{}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个成员变量,成员变量的类型及作用如上表所示。

2.addClip(clip, mapRule opt)

    该函数的作用是添加蒙皮剪辑并在剪辑和骨架之间创建贴图。

    参数有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的对应关系。

3.getClip(index)

    该函数的作用是通过索引获取一个剪辑。

    参数为index,类型为number,表示获取剪辑的索引。

    其源代码为:

getClip: function (index) {
        if (this._clips[index]) {
            return this._clips[index].clip;
        }
    }

    从源代码中可以看出,该函数判断本对象的_clips数组中是否包含索引为index的对象,若包含,则返回_clips中索引为index的clip,从而获取特定索引的剪辑。

4.getClipNumber() → {number}

    该函数的作用是获取剪辑的长度。

    无参数。

    其源代码为:

getClipNumber: function () {
        return this._clips.length;
    }

      从源代码中,该函数将_clips数组的长度返回,从而获取剪辑的长度。

5.removeClip(clip)

    该函数的作用是移除指定的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位置上的剪辑移除。从而实现移除特定的剪辑。

6.removeClipsAll()

    该函数的作用是移除所有的剪辑。

    无参数。

    其源代码为:

removeClipsAll: function () {
        this._clips = [];
    }

    从源代码中可以看出,该函数将本对象的_clips数组设置为空数组,从而移除所有的剪辑。

7.update()

    该函数的作用是更新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处理,实现矩阵的更新。

8.updateJointMatrices()

    该函数的作用是从节点变换计算联合矩阵。

    无参数。

    其源代码为:

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方法对子数组也更新,从而进行更新。

9.updateJointsBoundingBoxes(geometry)

    该函数的作用是更新绑定到几何体的每个关节的 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,若不小于则报错,从而实现关节和骨架的绑定。

 类似资料: