blob: 9056389b6a27f52d70c6c12d3bb57d5f63163203 [file] [log] [blame]
import Clip from './Clip';
import { isArrayLike } from '../core/util';
import * as color from '../tool/color';
var arraySlice = Array.prototype.slice;
/**
* @param {Object} target
* @param {string} propName
* @param {Array.<Object>} keyframes
* [{
* time: number,
* value: number | color string | Array.<number> | Array.<Array.<number>>
* }, ...]
* [Caveat]:
* (1) The order should ensured by time.
* (2) If `value` is `Array`, it must not be shared outside (espaciall el.shape, el.style),
* in case that it be modified outside and cause incorrect interpolate result.
* @param {string} easing
* @param {boolean} [delay=false]
* @param {boolean} [loop=false]
* @param {boolean} [forceAnimate=false]
* @param {Function} [getter=defaultGetter]
* @param {Function} [setter=defaultSetter]
* @return {module:zrender/animation/Clip} clip
*/
export function createTrackClip(target, propName, keyframes, easing, delay, loop, forceAnimate, getter, setter) {
var useSpline = easing === 'spline';
getter = getter || defaultGetter;
setter = setter || defaultSetter;
var trackLen = keyframes.length;
if (!trackLen) {
return;
} // Guess data type
var firstVal = keyframes[0].value;
var isValueArray = isArrayLike(firstVal);
var isValueColor = false;
var isValueString = false; // For vertices morphing
var arrDim = isValueArray ? getArrayDim(keyframes) : 0;
var trackMaxTime;
trackMaxTime = keyframes[trackLen - 1].time; // Percents of each keyframe
var kfPercents = []; // Value of each keyframe
var kfValues = [];
var prevValue = keyframes[0].value;
var isAllValueEqual = true;
for (var i = 0; i < trackLen; i++) {
kfPercents.push(keyframes[i].time / trackMaxTime); // Assume value is a color when it is a string
var value = keyframes[i].value; // Check if value is equal, deep check if value is array
if (!(isValueArray && isArraySame(value, prevValue, arrDim) || !isValueArray && value === prevValue)) {
isAllValueEqual = false;
}
prevValue = value; // Try converting a string to a color array
if (typeof value === 'string') {
var colorArray = color.parse(value);
if (colorArray) {
value = colorArray;
isValueColor = true;
} else {
isValueString = true;
}
}
kfValues.push(value);
}
if (!forceAnimate && isAllValueEqual) {
return;
}
var lastValue = kfValues[trackLen - 1]; // Polyfill array and NaN value
for (var i = 0; i < trackLen - 1; i++) {
if (isValueArray) {
fillArr(kfValues[i], lastValue, arrDim);
} else {
if (isNaN(kfValues[i]) && !isNaN(lastValue) && !isValueString && !isValueColor) {
kfValues[i] = lastValue;
}
}
}
isValueArray && fillArr(getter(target, propName), lastValue, arrDim); // Cache the key of last frame to speed up when
// animation playback is sequency
var lastFrame = 0;
var lastFramePercent = 0;
var start;
var w;
var p0;
var p1;
var p2;
var p3;
if (isValueColor) {
var rgba = [0, 0, 0, 0];
}
function hanleFrame(target, percent) {
// Find the range keyframes
// kf1-----kf2---------current--------kf3
// find kf2 and kf3 and do interpolation
var frame; // In the easing function like elasticOut, percent may less than 0
if (percent < 0) {
frame = 0;
} else if (percent < lastFramePercent) {
// Start from next key
// PENDING start from lastFrame ?
start = Math.min(lastFrame + 1, trackLen - 1);
for (frame = start; frame >= 0; frame--) {
if (kfPercents[frame] <= percent) {
break;
}
} // PENDING really need to do this ?
frame = Math.min(frame, trackLen - 2);
} else {
for (frame = lastFrame; frame < trackLen; frame++) {
if (kfPercents[frame] > percent) {
break;
}
}
frame = Math.min(frame - 1, trackLen - 2);
}
lastFrame = frame;
lastFramePercent = percent;
var range = kfPercents[frame + 1] - kfPercents[frame];
if (range === 0) {
return;
} else {
w = (percent - kfPercents[frame]) / range;
}
if (useSpline) {
p1 = kfValues[frame];
p0 = kfValues[frame === 0 ? frame : frame - 1];
p2 = kfValues[frame > trackLen - 2 ? trackLen - 1 : frame + 1];
p3 = kfValues[frame > trackLen - 3 ? trackLen - 1 : frame + 2];
if (isValueArray) {
catmullRomInterpolateArray(p0, p1, p2, p3, w, w * w, w * w * w, getter(target, propName), arrDim);
} else {
var value;
if (isValueColor) {
value = catmullRomInterpolateArray(p0, p1, p2, p3, w, w * w, w * w * w, rgba, 1);
value = rgba2String(rgba);
} else if (isValueString) {
// String is step(0.5)
return interpolateString(p1, p2, w);
} else {
value = catmullRomInterpolate(p0, p1, p2, p3, w, w * w, w * w * w);
}
setter(target, propName, value);
}
} else {
if (isValueArray) {
interpolateArray(kfValues[frame], kfValues[frame + 1], w, getter(target, propName), arrDim);
} else {
var value;
if (isValueColor) {
interpolateArray(kfValues[frame], kfValues[frame + 1], w, rgba, 1);
value = rgba2String(rgba);
} else if (isValueString) {
// String is step(0.5)
return interpolateString(kfValues[frame], kfValues[frame + 1], w);
} else {
value = interpolateNumber(kfValues[frame], kfValues[frame + 1], w);
}
if (target.aaaa != null) {
console.log(target.uuid, value, propName);
}
setter(target, propName, value);
}
}
}
var clip = new Clip({
target: target,
life: trackMaxTime,
loop: loop,
delay: delay,
onframe: hanleFrame
});
if (easing && easing !== 'spline') {
clip.easing = easing;
}
return clip;
}
function getArrayDim(keyframes) {
var lastValue = keyframes[keyframes.length - 1].value;
return isArrayLike(lastValue && lastValue[0]) ? 2 : 1;
}
/**
* @param {Array} arr0
* @param {Array} arr1
* @param {number} arrDim
* @return {boolean}
*/
function isArraySame(arr0, arr1, arrDim) {
if (arr0 === arr1) {
return true;
}
var len = arr0.length;
if (len !== arr1.length) {
return false;
}
if (arrDim === 1) {
for (var i = 0; i < len; i++) {
if (arr0[i] !== arr1[i]) {
return false;
}
}
} else {
var len2 = arr0[0].length;
for (var i = 0; i < len; i++) {
for (var j = 0; j < len2; j++) {
if (arr0[i][j] !== arr1[i][j]) {
return false;
}
}
}
}
return true;
} // arr0 is source array, arr1 is target array.
// Do some preprocess to avoid error happened when interpolating from arr0 to arr1
function fillArr(arr0, arr1, arrDim) {
var arr0Len = arr0.length;
var arr1Len = arr1.length;
if (arr0Len !== arr1Len) {
// FIXME Not work for TypedArray
var isPreviousLarger = arr0Len > arr1Len;
if (isPreviousLarger) {
// Cut the previous
arr0.length = arr1Len;
} else {
// Fill the previous
for (var i = arr0Len; i < arr1Len; i++) {
arr0.push(arrDim === 1 ? arr1[i] : arraySlice.call(arr1[i]));
}
}
} // Handling NaN value
var len2 = arr0[0] && arr0[0].length;
for (var i = 0; i < arr0.length; i++) {
if (arrDim === 1) {
if (isNaN(arr0[i])) {
arr0[i] = arr1[i];
}
} else {
for (var j = 0; j < len2; j++) {
if (isNaN(arr0[i][j])) {
arr0[i][j] = arr1[i][j];
}
}
}
}
}
/**
* Catmull Rom interpolate array
* @param {Array} p0
* @param {Array} p1
* @param {Array} p2
* @param {Array} p3
* @param {number} t
* @param {number} t2
* @param {number} t3
* @param {Array} out
* @param {number} arrDim
*/
function catmullRomInterpolateArray(p0, p1, p2, p3, t, t2, t3, out, arrDim) {
var len = p0.length;
if (arrDim === 1) {
for (var i = 0; i < len; i++) {
out[i] = catmullRomInterpolate(p0[i], p1[i], p2[i], p3[i], t, t2, t3);
}
} else {
var len2 = p0[0].length;
for (var i = 0; i < len; i++) {
for (var j = 0; j < len2; j++) {
out[i][j] = catmullRomInterpolate(p0[i][j], p1[i][j], p2[i][j], p3[i][j], t, t2, t3);
}
}
}
}
/**
* Catmull Rom interpolate number
* @param {number} p0
* @param {number} p1
* @param {number} p2
* @param {number} p3
* @param {number} t
* @param {number} t2
* @param {number} t3
* @return {number}
*/
function catmullRomInterpolate(p0, p1, p2, p3, t, t2, t3) {
var v0 = (p2 - p0) * 0.5;
var v1 = (p3 - p1) * 0.5;
return (2 * (p1 - p2) + v0 + v1) * t3 + (-3 * (p1 - p2) - 2 * v0 - v1) * t2 + v0 * t + p1;
}
/**
* @param {number} p0
* @param {number} p1
* @param {number} percent
* @return {number}
*/
function interpolateNumber(p0, p1, percent) {
return (p1 - p0) * percent + p0;
}
/**
* @param {string} p0
* @param {string} p1
* @param {number} percent
* @return {string}
*/
function interpolateString(p0, p1, percent) {
return percent > 0.5 ? p1 : p0;
}
/**
* @param {Array} p0
* @param {Array} p1
* @param {number} percent
* @param {Array} out
* @param {number} arrDim
*/
function interpolateArray(p0, p1, percent, out, arrDim) {
var len = p0.length;
if (arrDim === 1) {
for (var i = 0; i < len; i++) {
out[i] = interpolateNumber(p0[i], p1[i], percent);
}
} else {
var len2 = len && p0[0].length;
for (var i = 0; i < len; i++) {
for (var j = 0; j < len2; j++) {
out[i][j] = interpolateNumber(p0[i][j], p1[i][j], percent);
}
}
}
}
function rgba2String(rgba) {
rgba[0] = Math.floor(rgba[0]);
rgba[1] = Math.floor(rgba[1]);
rgba[2] = Math.floor(rgba[2]);
return 'rgba(' + rgba.join(',') + ')';
}
export function cloneFrameValue(value) {
if (isArrayLike(value)) {
var len = value.length;
if (isArrayLike(value[0])) {
var ret = [];
for (var i = 0; i < len; i++) {
ret.push(arraySlice.call(value[i]));
}
return ret;
}
return arraySlice.call(value);
}
return value;
}
export function defaultGetter(target, key) {
return target[key];
}
export function defaultSetter(target, key, value) {
target[key] = value;
}