1 /****************************************************************************
  2  Copyright (c) 2011-2012 cocos2d-x.org
  3  Copyright (c) 2013-2014 Chukong Technologies Inc.
  4 
  5  http://www.cocos2d-x.org
  6 
  7  Permission is hereby granted, free of charge, to any person obtaining a copy
  8  of this software and associated documentation files (the "Software"), to deal
  9  in the Software without restriction, including without limitation the rights
 10  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 11  copies of the Software, and to permit persons to whom the Software is
 12  furnished to do so, subject to the following conditions:
 13 
 14  The above copyright notice and this permission notice shall be included in
 15  all copies or substantial portions of the Software.
 16 
 17  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 18  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 19  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 20  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 21  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 22  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 23  THE SOFTWARE.
 24  ****************************************************************************/
 25 
 26 /**
 27  * The tween class for Armature.
 28  * @class
 29  * @extends ccs.ProcessBase
 30  *
 31  * @param {ccs.Bone} The bone to be animated
 32  *
 33  * @property {ccs.ArmatureAnimation}    animation   - The animation
 34  */
 35 ccs.Tween = ccs.ProcessBase.extend(/** @lends ccs.Tween# */{
 36     _tweenData:null,
 37     _to:null,
 38     _from:null,
 39     _between:null,
 40     _movementBoneData:null,
 41     _bone:null,
 42     _frameTweenEasing:0,
 43     _betweenDuration:0,
 44     _totalDuration:0,
 45     _toIndex:0,
 46     _fromIndex:0,
 47     _animation:null,
 48     _passLastFrame:false,
 49 
 50     ctor:function (bone) {
 51         ccs.ProcessBase.prototype.ctor.call(this);
 52         this._frameTweenEasing = ccs.TweenType.LINEAR;
 53 
 54         ccs.Tween.prototype.init.call(this, bone);
 55     },
 56 
 57     /**
 58      * initializes a ccs.Tween with a CCBone
 59      * @param {ccs.Bone} bone
 60      * @return {Boolean}
 61      */
 62     init:function (bone) {
 63         this._from = new ccs.FrameData();
 64         this._between = new ccs.FrameData();
 65 
 66         this._bone = bone;
 67         this._tweenData = this._bone.getTweenData();
 68         this._tweenData.displayIndex = -1;
 69 
 70         this._animation = (this._bone !== null && this._bone.getArmature() !== null) ?
 71             this._bone.getArmature().getAnimation() :
 72             null;
 73         return true;
 74     },
 75 
 76     /**
 77      * Plays the tween.
 78      * @param {ccs.MovementBoneData} movementBoneData
 79      * @param {Number} durationTo
 80      * @param {Number} durationTween
 81      * @param {Boolean} loop
 82      * @param {ccs.TweenType} tweenEasing
 83      */
 84     play:function (movementBoneData, durationTo, durationTween, loop, tweenEasing) {
 85         ccs.ProcessBase.prototype.play.call(this, durationTo, durationTween, loop, tweenEasing);
 86         this._loopType = (loop)?ccs.ANIMATION_TYPE_TO_LOOP_FRONT:ccs.ANIMATION_TYPE_NO_LOOP;
 87 
 88         this._totalDuration = 0;
 89         this._betweenDuration = 0;
 90         this._fromIndex = this._toIndex = 0;
 91 
 92         var difMovement = movementBoneData !== this._movementBoneData;
 93 
 94         this.setMovementBoneData(movementBoneData);
 95         this._rawDuration = this._movementBoneData.duration;
 96 
 97         var nextKeyFrame = this._movementBoneData.getFrameData(0);
 98         this._tweenData.displayIndex = nextKeyFrame.displayIndex;
 99 
100         if (this._bone.getArmature().getArmatureData().dataVersion >= ccs.CONST_VERSION_COMBINED)        {
101             ccs.TransformHelp.nodeSub(this._tweenData, this._bone.getBoneData());
102             this._tweenData.scaleX += 1;
103             this._tweenData.scaleY += 1;
104         }
105 
106         if (this._rawDuration === 0) {
107             this._loopType = ccs.ANIMATION_TYPE_SINGLE_FRAME;
108             if (durationTo === 0)
109                 this.setBetween(nextKeyFrame, nextKeyFrame);
110             else
111                 this.setBetween(this._tweenData, nextKeyFrame);
112             this._frameTweenEasing = ccs.TweenType.LINEAR;
113         }
114         else if (this._movementBoneData.frameList.length > 1) {
115             this._durationTween = durationTween * this._movementBoneData.scale;
116             if (loop && this._movementBoneData.delay !== 0)
117                 this.setBetween(this._tweenData, this.tweenNodeTo(this.updateFrameData(1 - this._movementBoneData.delay), this._between));
118             else {
119                 if (!difMovement || durationTo === 0)
120                     this.setBetween(nextKeyFrame, nextKeyFrame);
121                 else
122                     this.setBetween(this._tweenData, nextKeyFrame);
123             }
124         }
125         this.tweenNodeTo(0);
126     },
127 
128     /**
129      * Goes to specified frame and plays frame.
130      * @param {Number} frameIndex
131      */
132     gotoAndPlay: function (frameIndex) {
133         ccs.ProcessBase.prototype.gotoFrame.call(this, frameIndex);
134 
135         this._totalDuration = 0;
136         this._betweenDuration = 0;
137         this._fromIndex = this._toIndex = 0;
138 
139         this._isPlaying = true;
140         this._isComplete = this._isPause = false;
141 
142         this._currentPercent = this._curFrameIndex / (this._rawDuration-1);
143         this._currentFrame = this._nextFrameIndex * this._currentPercent;
144     },
145 
146     /**
147      * Goes to specified frame and pauses frame.
148      * @param {Number} frameIndex
149      */
150     gotoAndPause: function (frameIndex) {
151         this.gotoAndPlay(frameIndex);
152         this.pause();
153     },
154 
155     /**
156      * update will call this handler, you can handle your logic here
157      */
158     updateHandler:function () {
159         var locCurrentPercent = this._currentPercent == null ? 1 : this._currentPercent;
160         var locLoopType = this._loopType;
161         if (locCurrentPercent >= 1) {
162             switch (locLoopType) {
163                 case ccs.ANIMATION_TYPE_SINGLE_FRAME:
164                     locCurrentPercent = 1;
165                     this._isComplete = true;
166                     this._isPlaying = false;
167                     break;
168                 case ccs.ANIMATION_TYPE_NO_LOOP:
169                     locLoopType = ccs.ANIMATION_TYPE_MAX;
170                     if (this._durationTween <= 0)
171                         locCurrentPercent = 1;
172                     else
173                         locCurrentPercent = (locCurrentPercent - 1) * this._nextFrameIndex / this._durationTween;
174                     if (locCurrentPercent >= 1) {
175                         locCurrentPercent = 1;
176                         this._isComplete = true;
177                         this._isPlaying = false;
178                         break;
179                     } else {
180                         this._nextFrameIndex = this._durationTween;
181                         this._currentFrame = locCurrentPercent * this._nextFrameIndex;
182                         this._totalDuration = 0;
183                         this._betweenDuration = 0;
184                         this._fromIndex = this._toIndex = 0;
185                         break;
186                     }
187                 case ccs.ANIMATION_TYPE_TO_LOOP_FRONT:
188                     locLoopType = ccs.ANIMATION_TYPE_LOOP_FRONT;
189                     this._nextFrameIndex = this._durationTween > 0 ? this._durationTween : 1;
190 
191                     if (this._movementBoneData.delay !== 0) {
192                         this._currentFrame = (1 - this._movementBoneData.delay) * this._nextFrameIndex;
193                         locCurrentPercent = this._currentFrame / this._nextFrameIndex;
194                     } else {
195                         locCurrentPercent = 0;
196                         this._currentFrame = 0;
197                     }
198 
199                     this._totalDuration = 0;
200                     this._betweenDuration = 0;
201                     this._fromIndex = this._toIndex = 0;
202                     break;
203                 case ccs.ANIMATION_TYPE_MAX:
204                     locCurrentPercent = 1;
205                     this._isComplete = true;
206                     this._isPlaying = false;
207                     break;
208                 default:
209                     this._currentFrame = ccs.fmodf(this._currentFrame, this._nextFrameIndex);
210                     break;
211             }
212         }
213 
214         if (locCurrentPercent < 1 && locLoopType < ccs.ANIMATION_TYPE_TO_LOOP_BACK)
215             locCurrentPercent = Math.sin(locCurrentPercent * cc.PI / 2);
216 
217         this._currentPercent = locCurrentPercent;
218         this._loopType = locLoopType;
219 
220         if (locLoopType > ccs.ANIMATION_TYPE_TO_LOOP_BACK)
221             locCurrentPercent = this.updateFrameData(locCurrentPercent);
222         if (this._frameTweenEasing !== ccs.TweenType.TWEEN_EASING_MAX)
223             this.tweenNodeTo(locCurrentPercent);
224     },
225 
226     /**
227      * Calculate the between value of _from and _to, and give it to between frame data
228      * @param {ccs.FrameData} from
229      * @param {ccs.FrameData} to
230      * @param {Boolean} [limit=true]
231      */
232     setBetween:function (from, to, limit) {   //TODO set tweenColorTo to protected in v3.1
233         if(limit === undefined)
234             limit = true;
235         do {
236             if (from.displayIndex < 0 && to.displayIndex >= 0) {
237                 this._from.copy(to);
238                 this._between.subtract(to, to, limit);
239                 break;
240             }
241             if (to.displayIndex < 0 && from.displayIndex >= 0) {
242                 this._from.copy(from);
243                 this._between.subtract(to, to, limit);
244                 break;
245             }
246             this._from.copy(from);
247             this._between.subtract(from, to, limit);
248         } while (0);
249         if (!from.isTween){
250             this._tweenData.copy(from);
251             this._tweenData.isTween = true;
252         }
253         this.arriveKeyFrame(from);
254     },
255 
256     /**
257      * Update display index and process the key frame event when arrived a key frame
258      * @param {ccs.FrameData} keyFrameData
259      */
260     arriveKeyFrame:function (keyFrameData) {                         //TODO set tweenColorTo to protected in v3.1
261         if (keyFrameData) {
262             var locBone = this._bone;
263             var displayManager = locBone.getDisplayManager();
264 
265             //! Change bone's display
266             var displayIndex = keyFrameData.displayIndex;
267 
268             if (!displayManager.getForceChangeDisplay())
269                 displayManager.changeDisplayWithIndex(displayIndex, false);
270 
271             //! Update bone zorder, bone's zorder is determined by frame zorder and bone zorder
272             this._tweenData.zOrder = keyFrameData.zOrder;
273             locBone.updateZOrder();
274 
275             //! Update blend type
276             this._bone.setBlendFunc(keyFrameData.blendFunc);
277 
278             var childAramture = locBone.getChildArmature();
279             if (childAramture) {
280                 if (keyFrameData.movement !== "")
281                     childAramture.getAnimation().play(keyFrameData.movement);
282             }
283         }
284     },
285 
286     /**
287      * According to the percent to calculate current CCFrameData with tween effect
288      * @param {Number} percent
289      * @param {ccs.FrameData} [node]
290      * @return {ccs.FrameData}
291      */
292     tweenNodeTo:function (percent, node) {         //TODO set tweenColorTo to protected in v3.1
293         if (!node)
294             node = this._tweenData;
295 
296         var locFrom = this._from;
297         var locBetween = this._between;
298         if (!locFrom.isTween)
299             percent = 0;
300         node.x = locFrom.x + percent * locBetween.x;
301         node.y = locFrom.y + percent * locBetween.y;
302         node.scaleX = locFrom.scaleX + percent * locBetween.scaleX;
303         node.scaleY = locFrom.scaleY + percent * locBetween.scaleY;
304         node.skewX = locFrom.skewX + percent * locBetween.skewX;
305         node.skewY = locFrom.skewY + percent * locBetween.skewY;
306 
307         this._bone.setTransformDirty(true);
308         if (node && locBetween.isUseColorInfo)
309             this.tweenColorTo(percent, node);
310 
311         return node;
312     },
313 
314     /**
315      * According to the percent to calculate current color with tween effect
316      * @param {Number} percent
317      * @param {ccs.FrameData} node
318      */
319     tweenColorTo:function(percent,node){    //TODO set tweenColorTo to protected in v3.1
320         var locFrom = this._from;
321         var locBetween = this._between;
322         node.a = locFrom.a + percent * locBetween.a;
323         node.r = locFrom.r + percent * locBetween.r;
324         node.g = locFrom.g + percent * locBetween.g;
325         node.b = locFrom.b + percent * locBetween.b;
326         this._bone.updateColor();
327     },
328 
329     /**
330      * Calculate which frame arrived, and if current frame have event, then call the event listener
331      * @param {Number} currentPercent
332      * @return {Number}
333      */
334     updateFrameData:function (currentPercent) {                             //TODO set tweenColorTo to protected in v3.1
335         if (currentPercent > 1 && this._movementBoneData.delay !== 0)
336             currentPercent = ccs.fmodf(currentPercent,1);
337 
338         var playedTime = (this._rawDuration-1) * currentPercent;
339 
340         var from, to;
341         var locTotalDuration = this._totalDuration,locBetweenDuration = this._betweenDuration, locToIndex = this._toIndex;
342         // if play to current frame's front or back, then find current frame again
343         if (playedTime < locTotalDuration || playedTime >= locTotalDuration + locBetweenDuration) {
344             /*
345              *  get frame length, if this._toIndex >= _length, then set this._toIndex to 0, start anew.
346              *  this._toIndex is next index will play
347              */
348             var frames = this._movementBoneData.frameList;
349             var length = frames.length;
350 
351             if (playedTime < frames[0].frameID){
352                 from = to = frames[0];
353                 this.setBetween(from, to);
354                 return this._currentPercent;
355             }
356 
357             if (playedTime >= frames[length - 1].frameID) {
358                 // If _passLastFrame is true and playedTime >= frames[length - 1]->frameID, then do not need to go on.
359                 if (this._passLastFrame) {
360                     from = to = frames[length - 1];
361                     this.setBetween(from, to);
362                     return this._currentPercent;
363                 }
364                 this._passLastFrame = true;
365             } else
366                 this._passLastFrame = false;
367 
368             do {
369                 this._fromIndex = locToIndex;
370                 from = frames[this._fromIndex];
371                 locTotalDuration = from.frameID;
372 
373                 locToIndex = this._fromIndex + 1;
374                 if (locToIndex >= length)
375                     locToIndex = 0;
376                 to = frames[locToIndex];
377 
378                 //! Guaranteed to trigger frame event
379                 if(from.strEvent && !this._animation.isIgnoreFrameEvent())
380                     this._animation.frameEvent(this._bone, from.strEvent,from.frameID, playedTime);
381 
382                 if (playedTime === from.frameID|| (this._passLastFrame && this._fromIndex === length-1))
383                     break;
384             } while  (playedTime < from.frameID || playedTime >= to.frameID);
385 
386             locBetweenDuration = to.frameID - from.frameID;
387             this._frameTweenEasing = from.tweenEasing;
388             this.setBetween(from, to, false);
389 
390             this._totalDuration = locTotalDuration;
391             this._betweenDuration = locBetweenDuration;
392             this._toIndex = locToIndex;
393         }
394         currentPercent = locBetweenDuration === 0 ? 0 : (playedTime - this._totalDuration) / this._betweenDuration;
395 
396         /*
397          *  if frame tween easing equal to TWEEN_EASING_MAX, then it will not do tween.
398          */
399         var tweenType = (this._frameTweenEasing !== ccs.TweenType.LINEAR) ? this._frameTweenEasing : this._tweenEasing;
400         if (tweenType !== ccs.TweenType.TWEEN_EASING_MAX && tweenType !== ccs.TweenType.LINEAR && !this._passLastFrame) {
401             currentPercent = ccs.TweenFunction.tweenTo(currentPercent, tweenType, this._from.easingParams);
402         }
403         return currentPercent;
404     },
405 
406     /**
407      * Sets Armature animation to ccs.Tween.
408      * @param {ccs.ArmatureAnimation} animation
409      */
410     setAnimation:function (animation) {
411         this._animation = animation;
412     },
413 
414     /**
415      * Returns Armature animation of ccs.Tween.
416      * @return {ccs.ArmatureAnimation}
417      */
418     getAnimation:function () {
419         return this._animation;
420     },
421 
422     /**
423      * Sets movement bone data to ccs.Tween.
424      * @param data
425      */
426     setMovementBoneData: function(data){
427         this._movementBoneData = data;
428     }
429 });
430 
431 var _p = ccs.Tween.prototype;
432 
433 // Extended properties
434 /** @expose */
435 _p.animation;
436 cc.defineGetterSetter(_p, "animation", _p.getAnimation, _p.setAnimation);
437 
438 _p = null;
439 
440 /**
441  * Allocates and initializes a ArmatureAnimation.
442  * @param {ccs.Bone} bone
443  * @return {ccs.Tween}
444  * @deprecated since v3.1, please use new construction instead
445  */
446 ccs.Tween.create = function (bone) {
447     return new ccs.Tween(bone);
448 };
449