UNPKG

@awayjs/graphics

Version:
542 lines (541 loc) 23.9 kB
import { __extends } from "tslib"; import { ElementsEvent } from '@awayjs/renderer'; import { AnimationStateEvent } from '../events/AnimationStateEvent'; import { JointPose } from './data/JointPose'; import { SkeletonPose } from './data/SkeletonPose'; import { AnimatorBase } from './AnimatorBase'; /** * Provides an interface for assigning skeleton-based animation data sets to sprite-based entity objects * and controlling the various available states of animation through an interative playhead that can be * automatically updated or manually triggered. */ var SkeletonAnimator = /** @class */ (function (_super) { __extends(SkeletonAnimator, _super); /** * Creates a new <code>SkeletonAnimator</code> object. * * @param skeletonAnimationSet The animation data set containing the skeleton animations used by the animator. * @param skeleton The skeleton object used for calculating the resulting global matrices for transforming skinned sprite data. * @param forceCPU Optional value that only allows the animator to perform calculation on the CPU. Defaults to false. */ function SkeletonAnimator(animationSet, skeleton, forceCPU) { if (forceCPU === void 0) { forceCPU = false; } var _this = _super.call(this, animationSet) || this; _this._globalPose = new SkeletonPose(); _this._morphedElements = new Object(); _this._morphedElementsDirty = new Object(); _this._skeletonAnimationSet = animationSet; _this._skeleton = skeleton; _this._forceCPU = forceCPU; _this._jointsPerVertex = animationSet.jointsPerVertex; _this._numJoints = _this._skeleton.numJoints; _this._globalMatrices = new Float32Array(_this._numJoints * 12); var j = 0; for (var i = 0; i < _this._numJoints; ++i) { _this._globalMatrices[j++] = 1; _this._globalMatrices[j++] = 0; _this._globalMatrices[j++] = 0; _this._globalMatrices[j++] = 0; _this._globalMatrices[j++] = 0; _this._globalMatrices[j++] = 1; _this._globalMatrices[j++] = 0; _this._globalMatrices[j++] = 0; _this._globalMatrices[j++] = 0; _this._globalMatrices[j++] = 0; _this._globalMatrices[j++] = 1; _this._globalMatrices[j++] = 0; } _this._onTransitionCompleteDelegate = function (event) { return _this.onTransitionComplete(event); }; _this._onIndicesUpdateDelegate = function (event) { return _this.onIndicesUpdate(event); }; _this._onVerticesUpdateDelegate = function (event) { return _this.onVerticesUpdate(event); }; return _this; } Object.defineProperty(SkeletonAnimator.prototype, "globalMatrices", { /** * returns the calculated global matrices of the current skeleton pose. * * @see #globalPose */ get: function () { if (this._globalPropertiesDirty) this.updateGlobalProperties(); return this._globalMatrices; }, enumerable: false, configurable: true }); Object.defineProperty(SkeletonAnimator.prototype, "globalPose", { /** * returns the current skeleton pose output from the animator. * * @see away.animators.data.SkeletonPose */ get: function () { if (this._globalPropertiesDirty) this.updateGlobalProperties(); return this._globalPose; }, enumerable: false, configurable: true }); Object.defineProperty(SkeletonAnimator.prototype, "skeleton", { /** * Returns the skeleton object in use by the animator - this defines the number and heirarchy of joints used by the * skinned geoemtry to which skeleon animator is applied. */ get: function () { return this._skeleton; }, enumerable: false, configurable: true }); Object.defineProperty(SkeletonAnimator.prototype, "forceCPU", { /** * Indicates whether the skeleton animator is disabled by default for GPU rendering, something that allows the animator to perform calculation on the GPU. * Defaults to false. */ get: function () { return this._forceCPU; }, enumerable: false, configurable: true }); Object.defineProperty(SkeletonAnimator.prototype, "useCondensedIndices", { /** * Offers the option of enabling GPU accelerated animation on skeletons larger than 32 joints * by condensing the number of joint index values required per sprite. Only applicable to * skeleton animations that utilise more than one sprite object. Defaults to false. */ get: function () { return this._useCondensedIndices; }, set: function (value) { this._useCondensedIndices = value; }, enumerable: false, configurable: true }); /** * @inheritDoc */ SkeletonAnimator.prototype.clone = function () { return new SkeletonAnimator(this._skeletonAnimationSet, this._skeleton, this._forceCPU); }; /** * Plays an animation state registered with the given name in the animation data set. * * @param name The data set name of the animation state to be played. * @param transition An optional transition object that determines how the animator will transition from the currently active animation state. * @param offset An option offset time (in milliseconds) that resets the state's internal clock to the absolute time of the animator plus the offset value. Required for non-looping animation states. */ SkeletonAnimator.prototype.play = function (name, transition, offset) { if (transition === void 0) { transition = null; } if (offset === void 0) { offset = NaN; } if (this._pActiveAnimationName == name) return; this._pActiveAnimationName = name; if (!this._pAnimationSet.hasAnimation(name)) throw new Error('Animation root node ' + name + ' not found!'); if (transition && this._pActiveNode) { //setup the transition this._pActiveNode = transition.getAnimationNode(this, this._pActiveNode, this._pAnimationSet.getAnimation(name), this._pAbsoluteTime); this._pActiveNode.addEventListener(AnimationStateEvent.TRANSITION_COMPLETE, this._onTransitionCompleteDelegate); } else this._pActiveNode = this._pAnimationSet.getAnimation(name); this._pActiveState = this.getAnimationState(this._pActiveNode); if (this.updatePosition) { //update straight away to reset position deltas this._pActiveState.update(this._pAbsoluteTime); this._pActiveState.positionDelta; } this._activeSkeletonState = this._pActiveState; this.start(); //apply a time offset if specified if (!isNaN(offset)) this.reset(name, offset); }; /** * @inheritDoc */ SkeletonAnimator.prototype.setRenderState = function (shader, renderable) { // do on request of globalProperties if (this._globalPropertiesDirty) this.updateGlobalProperties(); var elements = renderable.shape.elements; elements.useCondensedIndices = this._useCondensedIndices; if (this._useCondensedIndices) { // using a condensed data set this.updateCondensedMatrices(elements.condensedIndexLookUp); shader.setVertexConstFromArray(this._skeletonAnimationSet.matricesIndex, this._condensedMatrices); } else { if (this._pAnimationSet.usesCPU) { if (this._morphedElementsDirty[elements.id]) this.morphElements(renderable, elements); return; } shader.setVertexConstFromArray(this._skeletonAnimationSet.matricesIndex, this._globalMatrices); } }; /** * @inheritDoc */ SkeletonAnimator.prototype.testGPUCompatibility = function (shader) { if (!this._useCondensedIndices && (this._forceCPU || this._jointsPerVertex > 4 || shader.numUsedVertexConstants + this._numJoints * 3 > 128)) this._pAnimationSet.cancelGPUCompatibility(); }; /** * Applies the calculated time delta to the active animation state node or state transition object. */ SkeletonAnimator.prototype._pUpdateDeltaTime = function (dt) { _super.prototype._pUpdateDeltaTime.call(this, dt); //invalidate pose matrices this._globalPropertiesDirty = true; //trigger geometry invalidation if using CPU animation if (this._pAnimationSet.usesCPU) this.invalidateElements(); }; SkeletonAnimator.prototype.updateCondensedMatrices = function (condensedIndexLookUp) { var j = 0, k = 0; var len = condensedIndexLookUp.length; var srcIndex; this._condensedMatrices = new Float32Array(len * 12); for (var i = 0; i < len; i++) { srcIndex = condensedIndexLookUp[i] * 12; //12 required for the three 4-component vectors that store the matrix k = 12; // copy into condensed while (k--) this._condensedMatrices[j++] = this._globalMatrices[srcIndex++]; } }; SkeletonAnimator.prototype.updateGlobalProperties = function () { this._globalPropertiesDirty = false; //get global pose this.localToGlobalPose(this._activeSkeletonState.getSkeletonPose(this._skeleton), this._globalPose, this._skeleton); // convert pose to matrix var mtxOffset = 0; var globalPoses = this._globalPose.jointPoses; var raw; var ox, oy, oz, ow; var xy2, xz2, xw2; var yz2, yw2, zw2; var n11, n12, n13; var n21, n22, n23; var n31, n32, n33; var m11, m12, m13, m14; var m21, m22, m23, m24; var m31, m32, m33, m34; var joints = this._skeleton.joints; var pose; var quat; var vec; var t; for (var i = 0; i < this._numJoints; ++i) { pose = globalPoses[i]; quat = pose.orientation; vec = pose.translation; ox = quat.x; oy = quat.y; oz = quat.z; ow = quat.w; xy2 = (t = 2.0 * ox) * oy; xz2 = t * oz; xw2 = t * ow; yz2 = (t = 2.0 * oy) * oz; yw2 = t * ow; zw2 = 2.0 * oz * ow; yz2 = 2.0 * oy * oz; yw2 = 2.0 * oy * ow; zw2 = 2.0 * oz * ow; ox *= ox; oy *= oy; oz *= oz; ow *= ow; n11 = (t = ox - oy) - oz + ow; n12 = xy2 - zw2; n13 = xz2 + yw2; n21 = xy2 + zw2; n22 = -t - oz + ow; n23 = yz2 - xw2; n31 = xz2 - yw2; n32 = yz2 + xw2; n33 = -ox - oy + oz + ow; // prepend inverse bind pose raw = joints[i].inverseBindPose; m11 = raw[0]; m12 = raw[4]; m13 = raw[8]; m14 = raw[12]; m21 = raw[1]; m22 = raw[5]; m23 = raw[9]; m24 = raw[13]; m31 = raw[2]; m32 = raw[6]; m33 = raw[10]; m34 = raw[14]; this._globalMatrices[mtxOffset] = n11 * m11 + n12 * m21 + n13 * m31; this._globalMatrices[mtxOffset + 1] = n11 * m12 + n12 * m22 + n13 * m32; this._globalMatrices[mtxOffset + 2] = n11 * m13 + n12 * m23 + n13 * m33; this._globalMatrices[mtxOffset + 3] = n11 * m14 + n12 * m24 + n13 * m34 + vec.x; this._globalMatrices[mtxOffset + 4] = n21 * m11 + n22 * m21 + n23 * m31; this._globalMatrices[mtxOffset + 5] = n21 * m12 + n22 * m22 + n23 * m32; this._globalMatrices[mtxOffset + 6] = n21 * m13 + n22 * m23 + n23 * m33; this._globalMatrices[mtxOffset + 7] = n21 * m14 + n22 * m24 + n23 * m34 + vec.y; this._globalMatrices[mtxOffset + 8] = n31 * m11 + n32 * m21 + n33 * m31; this._globalMatrices[mtxOffset + 9] = n31 * m12 + n32 * m22 + n33 * m32; this._globalMatrices[mtxOffset + 10] = n31 * m13 + n32 * m23 + n33 * m33; this._globalMatrices[mtxOffset + 11] = n31 * m14 + n32 * m24 + n33 * m34 + vec.z; mtxOffset = mtxOffset + 12; } }; SkeletonAnimator.prototype.getRenderableElements = function (renderable, sourceElements) { this._morphedElementsDirty[sourceElements.id] = true; //early out for GPU animations if (!this._pAnimationSet.usesCPU) return sourceElements; var targetElements; if (!(targetElements = this._morphedElements[sourceElements.id])) { //not yet stored sourceElements.normals; sourceElements.tangents; targetElements = (this._morphedElements[sourceElements.id] = sourceElements.clone()); //turn off auto calculations on the morphed geometry targetElements.autoDeriveNormals = false; targetElements.autoDeriveTangents = false; //add event listeners for any changes in UV values on the source geometry sourceElements.addEventListener(ElementsEvent.INVALIDATE_INDICES, this._onIndicesUpdateDelegate); sourceElements.addEventListener(ElementsEvent.INVALIDATE_VERTICES, this._onVerticesUpdateDelegate); } return targetElements; }; /** * If the animation can't be performed on GPU, transform vertices manually * @param subGeom The subgeometry containing the weights and joint index data per vertex. * @param pass The material pass for which we need to transform the vertices */ SkeletonAnimator.prototype.morphElements = function (renderable, sourceElements) { this._morphedElementsDirty[sourceElements.id] = false; var numVertices = sourceElements.numVertices; var sourcePositions = sourceElements.positions.get(numVertices); var sourceNormals = sourceElements.normals.get(numVertices); var sourceTangents = sourceElements.tangents.get(numVertices); var posDim = sourceElements.positions.dimensions; var posStride = sourceElements.positions.stride; var normalStride = sourceElements.normals.stride; var tangentStride = sourceElements.tangents.stride; var jointIndices = sourceElements.jointIndices.get(numVertices); var jointWeights = sourceElements.jointWeights.get(numVertices); var jointStride = sourceElements.jointIndices.stride; var targetElements = this._morphedElements[sourceElements.id]; var targetPositions = targetElements.positions.get(numVertices); var targetNormals = targetElements.normals.get(numVertices); var targetTangents = targetElements.tangents.get(numVertices); targetElements.positions.attributesBuffer.invalidate(); targetElements.normals.attributesBuffer.invalidate(); targetElements.tangents.attributesBuffer.invalidate(); var index = 0; var i0 = 0; var i1 = 0; var i2 = 0; var i3 = 0; var k; var vx, vy, vz; var nx, ny, nz; var tx, ty, tz; var weight; var vertX, vertY, vertZ; var normX, normY, normZ; var tangX, tangY, tangZ; var m11, m12, m13, m14; var m21, m22, m23, m24; var m31, m32, m33, m34; while (index < numVertices) { i0 = index * posStride; vertX = sourcePositions[i0]; vertY = sourcePositions[i0 + 1]; vertZ = (posDim == 3) ? sourcePositions[i0 + 2] : 0; i1 = index * normalStride; normX = sourceNormals[i1]; normY = sourceNormals[i1 + 1]; normZ = sourceNormals[i1 + 2]; i2 = index * tangentStride; tangX = sourceTangents[i2]; tangY = sourceTangents[i2 + 1]; tangZ = sourceTangents[i2 + 2]; vx = 0; vy = 0; vz = 0; nx = 0; ny = 0; nz = 0; tx = 0; ty = 0; tz = 0; k = 0; i3 = index * jointStride; while (k < this._jointsPerVertex) { weight = jointWeights[i3 + k]; if (weight > 0) { // implicit /3*12 (/3 because indices are multiplied by 3 for gpu matrix access, *12 because it's the matrix size) var mtxOffset = jointIndices[i3 + k] << 2; m11 = this._globalMatrices[mtxOffset]; m12 = this._globalMatrices[mtxOffset + 1]; m13 = this._globalMatrices[mtxOffset + 2]; m14 = this._globalMatrices[mtxOffset + 3]; m21 = this._globalMatrices[mtxOffset + 4]; m22 = this._globalMatrices[mtxOffset + 5]; m23 = this._globalMatrices[mtxOffset + 6]; m24 = this._globalMatrices[mtxOffset + 7]; m31 = this._globalMatrices[mtxOffset + 8]; m32 = this._globalMatrices[mtxOffset + 9]; m33 = this._globalMatrices[mtxOffset + 10]; m34 = this._globalMatrices[mtxOffset + 11]; vx += weight * (m11 * vertX + m12 * vertY + m13 * vertZ + m14); vy += weight * (m21 * vertX + m22 * vertY + m23 * vertZ + m24); vz += weight * (m31 * vertX + m32 * vertY + m33 * vertZ + m34); nx += weight * (m11 * normX + m12 * normY + m13 * normZ); ny += weight * (m21 * normX + m22 * normY + m23 * normZ); nz += weight * (m31 * normX + m32 * normY + m33 * normZ); tx += weight * (m11 * tangX + m12 * tangY + m13 * tangZ); ty += weight * (m21 * tangX + m22 * tangY + m23 * tangZ); tz += weight * (m31 * tangX + m32 * tangY + m33 * tangZ); k++; } else { //if zero weight encountered, skip to the next vertex k = this._jointsPerVertex; } } targetPositions[i0] = vx; targetPositions[i0 + 1] = vy; if (posDim == 3) targetPositions[i0 + 2] = vz; targetNormals[i1] = nx; targetNormals[i1 + 1] = ny; targetNormals[i1 + 2] = nz; targetTangents[i2] = tx; targetTangents[i2 + 1] = ty; targetTangents[i2 + 2] = tz; index++; } }; /** * Converts a local hierarchical skeleton pose to a global pose * @param targetPose The SkeletonPose object that will contain the global pose. * @param skeleton The skeleton containing the joints, and as such, the hierarchical data to transform to global poses. */ SkeletonAnimator.prototype.localToGlobalPose = function (sourcePose, targetPose, skeleton) { var globalPoses = targetPose.jointPoses; var globalJointPose; var joints = skeleton.joints; var len = sourcePose.numJointPoses; var jointPoses = sourcePose.jointPoses; var parentIndex; var joint; var parentPose; var pose; var or; var tr; var t; var q; var x1, y1, z1, w1; var x2, y2, z2, w2; var x3, y3, z3; // :s if (globalPoses.length != len) globalPoses.length = len; for (var i = 0; i < len; ++i) { globalJointPose = globalPoses[i]; if (globalJointPose == null) globalJointPose = globalPoses[i] = new JointPose(); joint = joints[i]; parentIndex = joint.parentIndex; pose = jointPoses[i]; q = globalJointPose.orientation; t = globalJointPose.translation; if (parentIndex < 0) { tr = pose.translation; or = pose.orientation; q.x = or.x; q.y = or.y; q.z = or.z; q.w = or.w; t.x = tr.x; t.y = tr.y; t.z = tr.z; } else { // append parent pose parentPose = globalPoses[parentIndex]; // rotate point or = parentPose.orientation; tr = pose.translation; x2 = or.x; y2 = or.y; z2 = or.z; w2 = or.w; x3 = tr.x; y3 = tr.y; z3 = tr.z; w1 = -x2 * x3 - y2 * y3 - z2 * z3; x1 = w2 * x3 + y2 * z3 - z2 * y3; y1 = w2 * y3 - x2 * z3 + z2 * x3; z1 = w2 * z3 + x2 * y3 - y2 * x3; // append parent translation tr = parentPose.translation; t.x = -w1 * x2 + x1 * w2 - y1 * z2 + z1 * y2 + tr.x; t.y = -w1 * y2 + x1 * z2 + y1 * w2 - z1 * x2 + tr.y; t.z = -w1 * z2 - x1 * y2 + y1 * x2 + z1 * w2 + tr.z; // append parent orientation x1 = or.x; y1 = or.y; z1 = or.z; w1 = or.w; or = pose.orientation; x2 = or.x; y2 = or.y; z2 = or.z; w2 = or.w; q.w = w1 * w2 - x1 * x2 - y1 * y2 - z1 * z2; q.x = w1 * x2 + x1 * w2 + y1 * z2 - z1 * y2; q.y = w1 * y2 - x1 * z2 + y1 * w2 + z1 * x2; q.z = w1 * z2 + x1 * y2 - y1 * x2 + z1 * w2; } } }; SkeletonAnimator.prototype.onTransitionComplete = function (event) { if (event.type == AnimationStateEvent.TRANSITION_COMPLETE) { event.animationNode.removeEventListener(AnimationStateEvent.TRANSITION_COMPLETE, this._onTransitionCompleteDelegate); //if this is the current active state transition, revert control to the active node if (this._pActiveState == event.animationState) { this._pActiveNode = this._pAnimationSet.getAnimation(this._pActiveAnimationName); this._pActiveState = this.getAnimationState(this._pActiveNode); this._activeSkeletonState = this._pActiveState; } } }; SkeletonAnimator.prototype.onIndicesUpdate = function (event) { var elements = event.target; this._morphedElements[elements.id].setIndices(elements.indices); }; SkeletonAnimator.prototype.onVerticesUpdate = function (event) { var elements = event.target; //only update uvs if (event.attributesView != elements.uvs && event.attributesView != elements.getCustomAtributes('secondaryUVs')) return; var morphElements = this._morphedElements[elements.id]; var morphUVs = morphElements.uvs.get(elements.numVertices); morphElements.invalidateVertices(morphElements.uvs); var uvStride = morphElements.uvs.stride; var uvs = event.attributesView.get(elements.numVertices); var len = elements.numVertices * uvStride; for (var i = 0; i < len; i += uvStride) { morphUVs[i] = uvs[i]; morphUVs[i + 1] = uvs[i + 1]; } }; return SkeletonAnimator; }(AnimatorBase)); export { SkeletonAnimator };