/**************************************************************************** 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 js = cc.js; var Playable = require('./playable'); var DynamicAnimCurve = require('./animation-curves').DynamicAnimCurve; var quickFindIndex = require('./animation-curves').quickFindIndex; var sampleMotionPaths = require('./motion-path-helper').sampleMotionPaths; var EventAnimCurve = require('./animation-curves').EventAnimCurve; var EventInfo = require('./animation-curves').EventInfo; var WrapModeMask = require('./types').WrapModeMask; var binarySearch = require('../core/utils/binary-search').binarySearchEpsilon; // The actual animator for Animation Component function AnimationAnimator (target, animation) { Playable.call(this); this.target = target; this.animation = animation; this._anims = new js.array.MutableForwardIterator([]); } js.extend(AnimationAnimator, Playable); var p = AnimationAnimator.prototype; p.playState = function (state, startTime) { if (!state.clip) { return; } if (!state.curveLoaded) { initClipData(this.target, state); } state.animator = this; state.play(); if (typeof startTime === 'number') { state.setTime(startTime); } this.play(); }; p.stopStatesExcept = function (state) { var iterator = this._anims; var array = iterator.array; for (iterator.i = 0; iterator.i < array.length; ++iterator.i) { var anim = array[iterator.i]; if (anim === state) { continue; } this.stopState(anim); } }; p.addAnimation = function (anim) { var index = this._anims.array.indexOf(anim); if (index === -1) { this._anims.push(anim); } anim._setEventTarget(this.animation); }; p.removeAnimation = function (anim) { var index = this._anims.array.indexOf(anim); if (index >= 0) { this._anims.fastRemoveAt(index); if (this._anims.array.length === 0) { this.stop(); } } else { cc.errorID(3908); } anim.animator = null; }; p.sample = function () { var iterator = this._anims; var array = iterator.array; for (iterator.i = 0; iterator.i < array.length; ++iterator.i) { var anim = array[iterator.i]; anim.sample(); } }; p.stopState = function (state) { if (state) { state.stop(); } }; p.pauseState = function (state) { if (state) { state.pause(); } }; p.resumeState = function (state) { if (state) { state.resume(); } if (this.isPaused) { this.resume(); } }; p.setStateTime = function (state, time) { if (time !== undefined) { if (state) { state.setTime(time); state.sample(); } } else { time = state; var array = this._anims.array; for (var i = 0; i < array.length; ++i) { var anim = array[i]; anim.setTime(time); anim.sample(); } } }; p.onStop = function () { var iterator = this._anims; var array = iterator.array; for (iterator.i = 0; iterator.i < array.length; ++iterator.i) { var anim = array[iterator.i]; anim.stop(); } }; p.onPause = function () { var array = this._anims.array; for (var i = 0; i < array.length; ++i) { var anim = array[i]; anim.pause(); // need to unbind animator to anim, or it maybe cannot be gc. anim.animator = null; } }; p.onResume = function () { var array = this._anims.array; for (var i = 0; i < array.length; ++i) { var anim = array[i]; // rebind animator to anim anim.animator = this; anim.resume(); } }; p._reloadClip = function (state) { initClipData(this.target, state); }; // 这个方法应该是 SampledAnimCurve 才能用 function createBatchedProperty (propPath, firstDotIndex, mainValue, animValue) { mainValue = mainValue.clone(); var nextValue = mainValue; var leftIndex = firstDotIndex + 1; var rightIndex = propPath.indexOf('.', leftIndex); // scan property path while (rightIndex !== -1) { var nextName = propPath.slice(leftIndex, rightIndex); nextValue = nextValue[nextName]; leftIndex = rightIndex + 1; rightIndex = propPath.indexOf('.', leftIndex); } var lastPropName = propPath.slice(leftIndex); nextValue[lastPropName] = animValue; return mainValue; } if (CC_TEST) { cc._Test.createBatchedProperty = createBatchedProperty; } function splitPropPath (propPath) { var array = propPath.split('.'); array.shift(); //array = array.filter(function (item) { return !!item; }); return array.length > 0 ? array : null; } function initClipData (root, state) { var clip = state.clip; var curves = state.curves; curves.length = 0; state.duration = clip.duration; state.speed = clip.speed; state.wrapMode = clip.wrapMode; state.frameRate = clip.sample; if ((state.wrapMode & WrapModeMask.Loop) === WrapModeMask.Loop) { state.repeatCount = Infinity; } else { state.repeatCount = 1; } // create curves function checkMotionPath(motionPath) { if (!Array.isArray(motionPath)) return false; for (let i = 0, l = motionPath.length; i < l; i++) { var controls = motionPath[i]; if (!Array.isArray(controls) || controls.length !== 6) return false; } return true; } function createPropCurve (target, propPath, keyframes) { var isMotionPathProp = (target instanceof cc.Node) && (propPath === 'position') && (keyframes[0] && Array.isArray(keyframes[0].value)); var motionPaths = []; var curve = new DynamicAnimCurve(); // 缓存目标对象,所以 Component 必须一开始都创建好并且不能运行时动态替换…… curve.target = target; var propName, propValue; var dotIndex = propPath.indexOf('.'); var hasSubProp = dotIndex !== -1; if (hasSubProp) { propName = propPath.slice(0, dotIndex); propValue = target[propName]; // if (!(propValue instanceof cc.ValueType)) { // cc.error('Only support sub animation property which is type cc.ValueType'); // continue; // } } else { propName = propPath; } curve.prop = propName; curve.subProps = splitPropPath(propPath); // for each keyframes for (let i = 0, l = keyframes.length; i < l; i++) { var keyframe = keyframes[i]; var ratio = keyframe.frame / state.duration; curve.ratios.push(ratio); if (isMotionPathProp) { var motionPath = keyframe.motionPath; if (motionPath && !checkMotionPath(motionPath)) { cc.errorID(3904, target.name, propPath, i); motionPath = null; } motionPaths.push(motionPath); } var curveValue = keyframe.value; //if (hasSubProp) { // curveValue = createBatchedProperty(propPath, dotIndex, propValue, curveValue); //} curve.values.push(curveValue); var curveTypes = keyframe.curve; if (curveTypes) { if (typeof curveTypes === 'string') { curve.types.push(curveTypes); continue; } else if (Array.isArray(curveTypes)) { if (curveTypes[0] === curveTypes[1] && curveTypes[2] === curveTypes[3]) { curve.types.push(DynamicAnimCurve.Linear); } else { curve.types.push(DynamicAnimCurve.Bezier(curveTypes)); } continue; } } curve.types.push(DynamicAnimCurve.Linear); } if (isMotionPathProp) { sampleMotionPaths(motionPaths, curve, clip.duration, clip.sample); } // if every piece of ratios are the same, we can use the quick function to find frame index. var ratios = curve.ratios; var currRatioDif, lastRatioDif; var canOptimize = true; var EPSILON = 1e-6; for (let i = 1, l = ratios.length; i < l; i++) { currRatioDif = ratios[i] - ratios[i-1]; if (i === 1) { lastRatioDif = currRatioDif; } else if (Math.abs(currRatioDif - lastRatioDif) > EPSILON) { canOptimize = false; break; } } curve._findFrameIndex = canOptimize ? quickFindIndex : binarySearch; return curve; } function createTargetCurves (target, curveData) { var propsData = curveData.props; var compsData = curveData.comps; if (propsData) { for (var propPath in propsData) { var data = propsData[propPath]; var curve = createPropCurve(target, propPath, data); curves.push(curve); } } if (compsData) { for (var compName in compsData) { var comp = target.getComponent(compName); if (!comp) { continue; } var compData = compsData[compName]; for (var propPath in compData) { var data = compData[propPath]; var curve = createPropCurve(comp, propPath, data); curves.push(curve); } } } } // property curves var curveData = clip.curveData; var childrenCurveDatas = curveData.paths; createTargetCurves(root, curveData); for (var namePath in childrenCurveDatas) { var target = cc.find(namePath, root); if (!target) { continue; } var childCurveDatas = childrenCurveDatas[namePath]; createTargetCurves(target, childCurveDatas); } // events curve var events = clip.events; if (!CC_EDITOR && events) { var curve; for (let i = 0, l = events.length; i < l; i++) { if (!curve) { curve = new EventAnimCurve(); curve.target = root; curves.push(curve); } var eventData = events[i]; var ratio = eventData.frame / state.duration; var eventInfo; var index = binarySearch(curve.ratios, ratio); if (index >= 0) { eventInfo = curve.events[index]; } else { eventInfo = new EventInfo(); curve.ratios.push(ratio); curve.events.push(eventInfo); } eventInfo.add(eventData.func, eventData.params); } } } if (CC_TEST) { cc._Test.initClipData = initClipData; } module.exports = AnimationAnimator;