// 1D Blend clip of blend tree
// http://docs.unity3d.com/Documentation/Manual/1DBlending.html
import Clip from './Clip';
var clipSortFunc = function (a, b) {
return a.position < b.position;
};
/**
* @typedef {Object} clay.animation.Blend1DClip.IClipInput
* @property {number} position
* @property {clay.animation.Clip} clip
* @property {number} offset
*/
/**
* 1d blending node in animation blend tree.
* output clip must have blend1D and copy method
* @constructor
* @alias clay.animation.Blend1DClip
* @extends clay.animation.Clip
*
* @param {Object} [opts]
* @param {string} [opts.name]
* @param {Object} [opts.target]
* @param {number} [opts.life]
* @param {number} [opts.delay]
* @param {number} [opts.gap]
* @param {number} [opts.playbackRatio]
* @param {boolean|number} [opts.loop] If loop is a number, it indicate the loop count of animation
* @param {string|Function} [opts.easing]
* @param {Function} [opts.onframe]
* @param {Function} [opts.onfinish]
* @param {Function} [opts.onrestart]
* @param {object[]} [opts.inputs]
* @param {number} [opts.position]
* @param {clay.animation.Clip} [opts.output]
*/
var Blend1DClip = function (opts) {
opts = opts || {};
Clip.call(this, opts);
/**
* Output clip must have blend1D and copy method
* @type {clay.animation.Clip}
*/
this.output = opts.output || null;
/**
* @type {clay.animation.Blend1DClip.IClipInput[]}
*/
this.inputs = opts.inputs || [];
/**
* @type {number}
*/
this.position = 0;
this._cacheKey = 0;
this._cachePosition = -Infinity;
this.inputs.sort(clipSortFunc);
};
Blend1DClip.prototype = new Clip();
Blend1DClip.prototype.constructor = Blend1DClip;
/**
* @param {number} position
* @param {clay.animation.Clip} inputClip
* @param {number} [offset]
* @return {clay.animation.Blend1DClip.IClipInput}
*/
Blend1DClip.prototype.addInput = function (position, inputClip, offset) {
var obj = {
position: position,
clip: inputClip,
offset: offset || 0
};
this.life = Math.max(inputClip.life, this.life);
if (!this.inputs.length) {
this.inputs.push(obj);
return obj;
}
var len = this.inputs.length;
if (this.inputs[0].position > position) {
this.inputs.unshift(obj);
} else if (this.inputs[len - 1].position <= position) {
this.inputs.push(obj);
} else {
var key = this._findKey(position);
this.inputs.splice(key, obj);
}
return obj;
};
Blend1DClip.prototype.step = function (time, dTime, silent) {
var ret = Clip.prototype.step.call(this, time);
if (ret !== 'finish') {
this.setTime(this.getElapsedTime());
}
// PENDING Schedule
if (!silent && ret !== 'paused') {
this.fire('frame');
}
return ret;
};
Blend1DClip.prototype.setTime = function (time) {
var position = this.position;
var inputs = this.inputs;
var len = inputs.length;
var min = inputs[0].position;
var max = inputs[len-1].position;
if (position <= min || position >= max) {
var in0 = position <= min ? inputs[0] : inputs[len-1];
var clip = in0.clip;
var offset = in0.offset;
clip.setTime((time + offset) % clip.life);
// Input clip is a blend clip
// PENDING
if (clip.output instanceof Clip) {
this.output.copy(clip.output);
} else {
this.output.copy(clip);
}
} else {
var key = this._findKey(position);
var in1 = inputs[key];
var in2 = inputs[key + 1];
var clip1 = in1.clip;
var clip2 = in2.clip;
// Set time on input clips
clip1.setTime((time + in1.offset) % clip1.life);
clip2.setTime((time + in2.offset) % clip2.life);
var w = (this.position - in1.position) / (in2.position - in1.position);
var c1 = clip1.output instanceof Clip ? clip1.output : clip1;
var c2 = clip2.output instanceof Clip ? clip2.output : clip2;
this.output.blend1D(c1, c2, w);
}
};
/**
* Clone a new Blend1D clip
* @param {boolean} cloneInputs True if clone the input clips
* @return {clay.animation.Blend1DClip}
*/
Blend1DClip.prototype.clone = function (cloneInputs) {
var clip = Clip.prototype.clone.call(this);
clip.output = this.output.clone();
for (var i = 0; i < this.inputs.length; i++) {
var inputClip = cloneInputs ? this.inputs[i].clip.clone(true) : this.inputs[i].clip;
clip.addInput(this.inputs[i].position, inputClip, this.inputs[i].offset);
}
return clip;
};
// Find the key where position in range [inputs[key].position, inputs[key+1].position)
Blend1DClip.prototype._findKey = function (position) {
var key = -1;
var inputs = this.inputs;
var len = inputs.length;
if (this._cachePosition < position) {
for (var i = this._cacheKey; i < len-1; i++) {
if (position >= inputs[i].position && position < inputs[i+1].position) {
key = i;
}
}
} else {
var s = Math.min(len-2, this._cacheKey);
for (var i = s; i >= 0; i--) {
if (position >= inputs[i].position && position < inputs[i+1].position) {
key = i;
}
}
}
if (key >= 0) {
this._cacheKey = key;
this._cachePosition = position;
}
return key;
};
export default Blend1DClip;