Hey Daphna I can take a look if you like. Probably the best bet on looping is to grab the engine source code, look at pc.Skeleton.addTime and monkey patch it to fire an event or call a callback when it loops. That code is pretty simple to understand. I remember all of the nightmare of trying to get the looping working. My version is all messed up with looping split animations now (which I needed for my current project).
Here’s a monkey patch to fire events when the skeleton loops:
pc.Skeleton = pc.inherits(function Skeleton(graph) {
this._super.call(this, graph);
pc.events.attach(this);
}, pc.Skeleton);
/**
* @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.
* @author Will Eastcott
*/
pc.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;
}
this.fire('loop', true)
} 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;
}
this.fire('loop', false);
}
// 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 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;
}
}
this.fire('frame');
}
};
So with that you can do this.entity.animation.data.skeleton.on('loop', function(direction) { ... })
The loop event will happen before the animation is played out.
You could then wait for the animation to be played on the character. Or also monkey patch updateGraph…
var updateGraph = pc.Skeleton.prototype.updateGraph
pc.Skeleton.prototype.updateGraph = function() {
updateGraph.call(this);
this.fire('updated');
};
So now you can have an event for that too… this.entity.animation.data.skeleton.on('updated', function() { ... })
To make things easier you could just wait for that event after a loop like this:
var skeleton = this.entity.animation.data.skeleton
skeleton.on('loop', function(direction) {
//Loop has happened
skeleton.once('updated', function() {
//Model updated after the loop
})
})
That’s all a bit “cobbled together” out of my current stuff and not fully tested - let me know if you use it and something doesn’t work right!