/****************************************************************************
|
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
|
|
https://www.cocos.com/
|
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
of this software and associated engine source code (the "Software"), a limited,
|
worldwide, royalty-free, non-assignable, revocable and non-exclusive license
|
to use Cocos Creator solely to develop games on your target platforms. You shall
|
not use Cocos Creator software for developing other software or tools that's
|
used for developing games. You are not granted to publish, distribute,
|
sublicense, and/or sell copies of Cocos Creator.
|
|
The software or tools in this License Agreement are licensed, not sold.
|
Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you.
|
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
THE SOFTWARE.
|
****************************************************************************/
|
|
|
var bezierByTime = require('./bezier').bezierByTime;
|
|
var binarySearch = require('../core/utils/binary-search').binarySearchEpsilon;
|
var WrapModeMask = require('./types').WrapModeMask;
|
var WrappedInfo = require('./types').WrappedInfo;
|
|
/**
|
* Compute a new ratio by curve type
|
* @param {Number} ratio - The origin ratio
|
* @param {Array|String} type - If it's Array, then ratio will be computed with bezierByTime. If it's string, then ratio will be computed with cc.easing function
|
*/
|
function computeRatioByType (ratio, type) {
|
if (typeof type === 'string') {
|
var func = cc.easing[type];
|
if (func) {
|
ratio = func(ratio);
|
}
|
else {
|
cc.errorID(3906, type);
|
}
|
}
|
else if (Array.isArray(type)) {
|
// bezier curve
|
ratio = bezierByTime(type, ratio);
|
}
|
|
return ratio;
|
}
|
|
//
|
// 动画数据类,相当于 AnimationClip。
|
// 虽然叫做 AnimCurve,但除了曲线,可以保存任何类型的值。
|
//
|
// @class AnimCurve
|
//
|
//
|
var AnimCurve = cc.Class({
|
name: 'cc.AnimCurve',
|
|
//
|
// @method sample
|
// @param {number} time
|
// @param {number} ratio - The normalized time specified as a number between 0.0 and 1.0 inclusive.
|
// @param {AnimationState} state
|
//
|
sample: function (time, ratio, state) {},
|
|
onTimeChangedManually: undefined
|
});
|
|
/**
|
* 当每两帧之前的间隔都一样的时候可以使用此函数快速查找 index
|
*/
|
function quickFindIndex (ratios, ratio) {
|
var length = ratios.length - 1;
|
|
if (length === 0) return 0;
|
|
var start = ratios[0];
|
if (ratio < start) return 0;
|
|
var end = ratios[length];
|
if (ratio > end) return length;
|
|
ratio = (ratio - start) / (end - start);
|
|
var eachLength = 1 / length;
|
var index = ratio / eachLength;
|
var floorIndex = index | 0;
|
var EPSILON = 1e-6;
|
|
if ((index - floorIndex) < EPSILON) {
|
return floorIndex;
|
}
|
else if ((floorIndex + 1 - index) < EPSILON) {
|
return floorIndex + 1;
|
}
|
|
return ~(floorIndex + 1);
|
}
|
|
//
|
//
|
// @class DynamicAnimCurve
|
//
|
// @extends AnimCurve
|
//
|
var DynamicAnimCurve = cc.Class({
|
name: 'cc.DynamicAnimCurve',
|
extends: AnimCurve,
|
|
properties: {
|
|
// The object being animated.
|
// @property target
|
// @type {object}
|
target: null,
|
|
// The name of the property being animated.
|
// @property prop
|
// @type {string}
|
prop: '',
|
|
// The values of the keyframes. (y)
|
// @property values
|
// @type {any[]}
|
values: [],
|
|
// The keyframe ratio of the keyframe specified as a number between 0.0 and 1.0 inclusive. (x)
|
// @property ratios
|
// @type {number[]}
|
ratios: [],
|
|
// @property types
|
// @param {object[]}
|
// Each array item maybe type:
|
// - [x, x, x, x]: Four control points for bezier
|
// - null: linear
|
types: [],
|
|
// @property {string[]} subProps - The path of sub property being animated.
|
subProps: null
|
},
|
|
_findFrameIndex: binarySearch,
|
|
sample: function (time, ratio, state) {
|
var values = this.values;
|
var ratios = this.ratios;
|
var frameCount = ratios.length;
|
|
if (frameCount === 0) {
|
return;
|
}
|
|
// evaluate value
|
var value;
|
var index = this._findFrameIndex(ratios, ratio);
|
|
if (index < 0) {
|
index = ~index;
|
|
if (index <= 0) {
|
value = values[0];
|
}
|
else if (index >= frameCount) {
|
value = values[frameCount - 1];
|
}
|
else {
|
var fromVal = values[index - 1];
|
|
var isNumber = typeof fromVal === 'number';
|
var canLerp = fromVal && fromVal.lerp;
|
|
if (!isNumber && !canLerp) {
|
value = fromVal;
|
}
|
else {
|
var fromRatio = ratios[index - 1];
|
var toRatio = ratios[index];
|
var type = this.types[index - 1];
|
var ratioBetweenFrames = (ratio - fromRatio) / (toRatio - fromRatio);
|
|
if (type) {
|
ratioBetweenFrames = computeRatioByType(ratioBetweenFrames, type);
|
}
|
|
// calculate value
|
var toVal = values[index];
|
|
// lerp
|
if (isNumber) {
|
value = fromVal + (toVal - fromVal) * ratioBetweenFrames;
|
}
|
else if (canLerp) {
|
value = fromVal.lerp(toVal, ratioBetweenFrames);
|
}
|
}
|
}
|
}
|
else {
|
value = values[index];
|
}
|
|
var subProps = this.subProps;
|
if (subProps) {
|
// create batched value dynamically
|
var mainProp = this.target[this.prop];
|
var subProp = mainProp;
|
|
for (var i = 0; i < subProps.length - 1; i++) {
|
var subPropName = subProps[i];
|
if (subProp) {
|
subProp = subProp[subPropName];
|
}
|
else {
|
return;
|
}
|
}
|
|
var propName = subProps[subProps.length - 1];
|
|
if (subProp) {
|
subProp[propName] = value;
|
}
|
else {
|
return;
|
}
|
|
value = mainProp;
|
}
|
|
// apply value
|
this.target[this.prop] = value;
|
}
|
});
|
|
DynamicAnimCurve.Linear = null;
|
DynamicAnimCurve.Bezier = function (controlPoints) {
|
return controlPoints;
|
};
|
|
|
|
/**
|
* Event information,
|
* @class EventInfo
|
*
|
*/
|
var EventInfo = function () {
|
this.events = [];
|
};
|
|
/**
|
* @param {Function} [func] event function
|
* @param {Object[]} [params] event params
|
*/
|
EventInfo.prototype.add = function (func, params) {
|
this.events.push({
|
func: func || '',
|
params: params || []
|
});
|
};
|
|
|
/**
|
*
|
* @class EventAnimCurve
|
*
|
* @extends AnimCurve
|
*/
|
var EventAnimCurve = cc.Class({
|
name: 'cc.EventAnimCurve',
|
extends: AnimCurve,
|
|
properties: {
|
/**
|
* The object being animated.
|
* @property target
|
* @type {object}
|
*/
|
target: null,
|
|
/** The keyframe ratio of the keyframe specified as a number between 0.0 and 1.0 inclusive. (x)
|
* @property ratios
|
* @type {number[]}
|
*/
|
ratios: [],
|
|
/**
|
* @property events
|
* @type {EventInfo[]}
|
*/
|
events: [],
|
|
_wrappedInfo: {
|
default: function () {
|
return new WrappedInfo();
|
}
|
},
|
|
_lastWrappedInfo: null,
|
|
_ignoreIndex: NaN
|
},
|
|
_wrapIterations: function (iterations) {
|
if (iterations - (iterations | 0) === 0) iterations -= 1;
|
return iterations | 0;
|
},
|
|
sample: function (time, ratio, state) {
|
var length = this.ratios.length;
|
|
var currentWrappedInfo = state.getWrappedInfo(state.time, this._wrappedInfo);
|
var direction = currentWrappedInfo.direction;
|
var currentIndex = binarySearch(this.ratios, currentWrappedInfo.ratio);
|
if (currentIndex < 0) {
|
currentIndex = ~currentIndex - 1;
|
|
// if direction is inverse, then increase index
|
if (direction < 0) currentIndex += 1;
|
}
|
|
if (this._ignoreIndex !== currentIndex) {
|
this._ignoreIndex = NaN;
|
}
|
|
currentWrappedInfo.frameIndex = currentIndex;
|
|
if (!this._lastWrappedInfo) {
|
this._fireEvent(currentIndex);
|
this._lastWrappedInfo = new WrappedInfo(currentWrappedInfo);
|
return;
|
}
|
|
var wrapMode = state.wrapMode;
|
var currentIterations = this._wrapIterations(currentWrappedInfo.iterations);
|
|
var lastWrappedInfo = this._lastWrappedInfo;
|
var lastIterations = this._wrapIterations(lastWrappedInfo.iterations);
|
var lastIndex = lastWrappedInfo.frameIndex;
|
var lastDirection = lastWrappedInfo.direction;
|
|
var interationsChanged = lastIterations !== -1 && currentIterations !== lastIterations;
|
|
if (lastIndex === currentIndex && interationsChanged && length === 1) {
|
this._fireEvent(0);
|
}
|
else if (lastIndex !== currentIndex || interationsChanged) {
|
direction = lastDirection;
|
|
do {
|
if (lastIndex !== currentIndex) {
|
if (direction === -1 && lastIndex === 0 && currentIndex > 0) {
|
if ((wrapMode & WrapModeMask.PingPong) === WrapModeMask.PingPong) {
|
direction *= -1;
|
}
|
else {
|
lastIndex = length;
|
}
|
|
lastIterations ++;
|
}
|
else if (direction === 1 && lastIndex === length - 1 && currentIndex < length - 1) {
|
if ((wrapMode & WrapModeMask.PingPong) === WrapModeMask.PingPong) {
|
direction *= -1;
|
}
|
else {
|
lastIndex = -1;
|
}
|
|
lastIterations ++;
|
}
|
|
if (lastIndex === currentIndex) break;
|
if (lastIterations > currentIterations) break;
|
}
|
|
lastIndex += direction;
|
|
cc.director.getAnimationManager().pushDelayEvent(this, '_fireEvent', [lastIndex]);
|
} while (lastIndex !== currentIndex && lastIndex > -1 && lastIndex < length);
|
}
|
|
this._lastWrappedInfo.set(currentWrappedInfo);
|
},
|
|
_fireEvent: function (index) {
|
if (index < 0 || index >= this.events.length || this._ignoreIndex === index) return;
|
|
var eventInfo = this.events[index];
|
var events = eventInfo.events;
|
|
if ( !this.target.isValid ) {
|
return;
|
}
|
|
var components = this.target._components;
|
|
for (var i = 0; i < events.length; i++) {
|
var event = events[i];
|
var funcName = event.func;
|
|
for (var j = 0; j < components.length; j++) {
|
var component = components[j];
|
var func = component[funcName];
|
|
if (func) func.apply(component, event.params);
|
}
|
}
|
},
|
|
onTimeChangedManually: function (time, state) {
|
this._lastWrappedInfo = null;
|
this._ignoreIndex = NaN;
|
|
var info = state.getWrappedInfo(time, this._wrappedInfo);
|
var direction = info.direction;
|
var frameIndex = binarySearch(this.ratios, info.ratio);
|
|
// only ignore when time not on a frame index
|
if (frameIndex < 0) {
|
frameIndex = ~frameIndex - 1;
|
|
// if direction is inverse, then increase index
|
if (direction < 0) frameIndex += 1;
|
|
this._ignoreIndex = frameIndex;
|
}
|
}
|
});
|
|
|
if (CC_TEST) {
|
cc._Test.DynamicAnimCurve = DynamicAnimCurve;
|
cc._Test.EventAnimCurve = EventAnimCurve;
|
cc._Test.quickFindIndex = quickFindIndex;
|
}
|
|
module.exports = {
|
AnimCurve: AnimCurve,
|
DynamicAnimCurve: DynamicAnimCurve,
|
EventAnimCurve: EventAnimCurve,
|
EventInfo: EventInfo,
|
computeRatioByType: computeRatioByType,
|
quickFindIndex: quickFindIndex
|
};
|