Object.assign(pc, function () {
function InterpolatedKey() {
this._written = false;
this._name = "";
this._keyFrames = [];
// Result of interpolation
this._quat = new pc.Quat();
this._pos = new pc.Vec3();
this._scale = new pc.Vec3();
// Optional destination for interpolated keyframe
this._targetNode = null;
}
Object.assign(InterpolatedKey.prototype, {
getTarget: function () {
return this._targetNode;
},
setTarget: function (node) {
this._targetNode = node;
}
});
/**
* @constructor
* @name pc.Skeleton
* @classdesc Represents a skeleton used to play animations.
* @param {pc.GraphNode} graph The root pc.GraphNode of the skeleton.
* @property {Boolean} looping Determines whether skeleton is looping its animation.
*/
var Skeleton = function Skeleton(graph) {
this._animation = null;
this._time = 0;
this.looping = true;
this._interpolatedKeys = [];
this._interpolatedKeyDict = {};
this._currKeyIndices = {};
this.graph = null;
var self = this;
function addInterpolatedKeys(node) {
var interpKey = new InterpolatedKey();
interpKey._name = node.name;
self._interpolatedKeys.push(interpKey);
self._interpolatedKeyDict[node.name] = interpKey;
self._currKeyIndices[node.name] = 0;
for (var i = 0; i < node._children.length; i++)
addInterpolatedKeys(node._children[i]);
}
addInterpolatedKeys(graph);
};
/**
* @function
* @name pc.Skeleton#addTime
* @description Progresses the animation assigned to the specified skeleton by the
* supplied time delta. If the delta takes the animation passed its end point, if
* the skeleton is set to loop, the animation will continue from the beginning.
* Otherwise, the animation's current time will remain at its duration (i.e. the
* end).
* @param {Number} delta The time in seconds to progress the skeleton's animation.
*/
Skeleton.prototype.addTime = function (delta) {
if (this._animation !== null) {
var i;
var node, nodeName;
var keys, interpKey;
var k1, k2, alpha;
var nodes = this._animation._nodes;
var duration = this._animation.duration;
// Check if we can early out
if ((this._time === duration) && !this.looping) {
return;
}
// Step the current time and work out if we need to jump ahead, clamp or wrap around
this._time += delta;
if (this._time > duration) {
this._time = this.looping ? 0.0 : duration;
for (i = 0; i < nodes.length; i++) {
node = nodes[i];
nodeName = node._name;
this._currKeyIndices[nodeName] = 0;
}
} else if (this._time < 0) {
this._time = this.looping ? duration : 0.0;
for (i = 0; i < nodes.length; i++) {
node = nodes[i];
nodeName = node._name;
this._currKeyIndices[nodeName] = node._keys.length - 2;
}
}
// For each animated node...
// keys index offset
var offset = (delta >= 0 ? 1 : -1);
var foundKey;
for (i = 0; i < nodes.length; i++) {
node = nodes[i];
nodeName = node._name;
keys = node._keys;
// Determine the interpolated keyframe for this animated node
interpKey = this._interpolatedKeyDict[nodeName];
if (interpKey === undefined) {
// #ifdef DEBUG
console.warn('Unknown skeleton node name: ' + nodeName);
// #endif
continue;
}
// If there's only a single key, just copy the key to the interpolated key...
foundKey = false;
if (keys.length !== 1) {
// Otherwise, find the keyframe pair for this node
for (var currKeyIndex = this._currKeyIndices[nodeName]; currKeyIndex < keys.length - 1 && currKeyIndex >= 0; currKeyIndex += offset) {
k1 = keys[currKeyIndex];
k2 = keys[currKeyIndex + 1];
if ((k1.time <= this._time) && (k2.time >= this._time)) {
alpha = (this._time - k1.time) / (k2.time - k1.time);
interpKey._pos.lerp(k1.position, k2.position, alpha);
interpKey._quat.slerp(k1.rotation, k2.rotation, alpha);
interpKey._scale.lerp(k1.scale, k2.scale, alpha);
interpKey._written = true;
this._currKeyIndices[nodeName] = currKeyIndex;
foundKey = true;
break;
}
}
}
if (keys.length === 1 || (!foundKey && this._time === 0.0 && this.looping)) {
interpKey._pos.copy(keys[0].position);
interpKey._quat.copy(keys[0].rotation);
interpKey._scale.copy(keys[0].scale);
interpKey._written = true;
}
}
}
};
/**
* @function
* @name pc.Skeleton#blend
* @description Blends two skeletons together.
* @param {pc.Skeleton} skel1 Skeleton holding the first pose to be blended.
* @param {pc.Skeleton} skel2 Skeleton holding the second pose to be blended.
* @param {Number} alpha The value controlling the interpolation in relation to the two input
* skeletons. The value is in the range 0 to 1, 0 generating skel1, 1 generating skel2 and anything
* in between generating a spherical interpolation between the two.
*/
Skeleton.prototype.blend = function (skel1, skel2, alpha) {
var numNodes = this._interpolatedKeys.length;
for (var i = 0; i < numNodes; i++) {
var key1 = skel1._interpolatedKeys[i];
var key2 = skel2._interpolatedKeys[i];
var dstKey = this._interpolatedKeys[i];
if (key1._written && key2._written) {
dstKey._quat.slerp(key1._quat, skel2._interpolatedKeys[i]._quat, alpha);
dstKey._pos.lerp(key1._pos, skel2._interpolatedKeys[i]._pos, alpha);
dstKey._scale.lerp(key1._scale, key2._scale, alpha);
dstKey._written = true;
} else if (key1._written) {
dstKey._quat.copy(key1._quat);
dstKey._pos.copy(key1._pos);
dstKey._scale.copy(key1._scale);
dstKey._written = true;
} else if (key2._written) {
dstKey._quat.copy(key2._quat);
dstKey._pos.copy(key2._pos);
dstKey._scale.copy(key2._scale);
dstKey._written = true;
}
}
};
/**
* @name pc.Skeleton#animation
* @type pc.Animation
* @description Animation currently assigned to skeleton.
*/
Object.defineProperty(Skeleton.prototype, 'animation', {
get: function () {
return this._animation;
},
set: function (value) {
this._animation = value;
this.currentTime = 0;
}
});
/**
* @private
* @deprecated
* @function
* @name pc.Skeleton#getAnimation
* @description Returns the animation currently assigned to the specified skeleton.
* @returns {pc.Animation} The animation set on the skeleton.
*/
Skeleton.prototype.getAnimation = function () {
return this._animation;
};
/**
* @name pc.Skeleton#currentTime
* @type Number
* @description Current time of currently active animation in seconds.
* This value is between zero and the duration of the animation.
*/
Object.defineProperty(Skeleton.prototype, 'currentTime', {
get: function () {
return this._time;
},
set: function (value) {
this._time = value;
var numNodes = this._interpolatedKeys.length;
for (var i = 0; i < numNodes; i++) {
var node = this._interpolatedKeys[i];
var nodeName = node._name;
this._currKeyIndices[nodeName] = 0;
}
this.addTime(0);
this.updateGraph();
}
});
/**
* @private
* @deprecated
* @function
* @name pc.Skeleton#getCurrentTime
* @description Returns the current time of the currently active animation as set on
* the specified skeleton. This value will be between zero and the duration of the
* animation.
* @returns {Number} The current time of the animation set on the skeleton.
*/
Skeleton.prototype.getCurrentTime = function () {
return this._time;
};
/**
* @private
* @deprecated
* @function
* @name pc.Skeleton#setCurrentTime
* @description Sets the current time of the currently active animation as set on
* the specified skeleton. This value must be between zero and the duration of the
* animation.
* @param {Number} time The current time of the animation set on the skeleton.
*/
Skeleton.prototype.setCurrentTime = function (time) {
this.currentTime = time;
};
/**
* @readonly
* @name pc.Skeleton#numNodes
* @type Number
* @description Read-only property that returns number of nodes of a skeleton.
*/
Object.defineProperty(Skeleton.prototype, 'numNodes', {
get: function () {
return this._interpolatedKeys.length;
}
});
/**
* @private
* @deprecated
* @function
* @name pc.Skeleton#getNumNodes
* @description Returns the number of nodes held by the specified skeleton.
* @returns {Number} The number of nodes held by the specified skeleton.
*/
Skeleton.prototype.getNumNodes = function () {
return this._interpolatedKeys.length;
};
/**
* @private
* @deprecated
* @function
* @name pc.Skeleton#setAnimation
* @description Sets an animation on the specified skeleton.
* @param {pc.Animation} animation The animation to set on the skeleton.
*/
Skeleton.prototype.setAnimation = function (animation) {
this.animation = animation;
};
/**
* @function
* @name pc.Skeleton#setGraph
* @description Links a skeleton to a node hierarchy. The nodes animated skeleton are
* then subsequently used to drive the local transformation matrices of the node
* hierarchy.
* @param {pc.GraphNode} graph The root node of the graph that the skeleton is to drive.
*/
Skeleton.prototype.setGraph = function (graph) {
var i;
this.graph = graph;
if (graph) {
for (i = 0; i < this._interpolatedKeys.length; i++) {
var interpKey = this._interpolatedKeys[i];
var graphNode = graph.findByName(interpKey._name);
this._interpolatedKeys[i].setTarget(graphNode);
}
} else {
for (i = 0; i < this._interpolatedKeys.length; i++) {
this._interpolatedKeys[i].setTarget(null);
}
}
};
/**
* @function
* @name pc.Skeleton#updateGraph
* @description Synchronizes the currently linked node hierarchy with the current state of the
* skeleton. Internally, this function converts the interpolated keyframe at each node in the
* skeleton into the local transformation matrix at each corresponding node in the linked node
* hierarchy.
*/
Skeleton.prototype.updateGraph = function () {
if (this.graph) {
for (var i = 0; i < this._interpolatedKeys.length; i++) {
var interpKey = this._interpolatedKeys[i];
if (interpKey._written) {
var transform = interpKey.getTarget();
transform.localPosition.copy(interpKey._pos);
transform.localRotation.copy(interpKey._quat);
transform.localScale.copy(interpKey._scale);
if (!transform._dirtyLocal)
transform._dirtifyLocal();
interpKey._written = false;
}
}
}
};
/**
* @private
* @deprecated
* @function
* @name pc.Skeleton#setLooping
* @description Specified whether a skeleton should loop its animation or not. If the animation
* loops, it will wrap back to the start when adding time to the skeleton beyond the duration
* of the animation. Otherwise, the animation stops at its end after a single play through.
* @param {Boolean} looping True to cause the animation to loop back to the start on completion
* and false otherwise.
*/
Skeleton.prototype.setLooping = function (looping) {
this.looping = looping;
};
/**
* @private
* @deprecated
* @function
* @name pc.Skeleton#getLooping
* @description Queries the specified skeleton to determine whether it is looping its animation.
* @returns {Boolean} True if the skeleton is looping the animation, false otherwise.
*/
Skeleton.prototype.getLooping = function () {
return this.looping;
};
return {
Skeleton: Skeleton
};
}());