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