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  * Base class for ccs.Tween objects.
 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     ctor:function () {
 48         ccs.ProcessBase.prototype.ctor.call(this);
 49         this._tweenData = null;
 50         this._to = null;
 51         this._from = null;
 52         this._between = null;
 53         this._bone = null;
 54         this._movementBoneData = null;
 55         this._frameTweenEasing = ccs.TweenType.linear;
 56         this._toIndex = 0;
 57         this._fromIndex = 0;
 58         this.animation = null;
 59         this._passLastFrame = false;
 60     },
 61 
 62     /**
 63      * init with a CCBone
 64      * @param {ccs.Bone} bone
 65      * @return {Boolean}
 66      */
 67     init:function (bone) {
 68         this._from = new ccs.FrameData();
 69         this._between = new ccs.FrameData();
 70 
 71         this._bone = bone;
 72         this._tweenData = this._bone.getTweenData();
 73         this._tweenData.displayIndex = -1;
 74          var armature = bone.getArmature();
 75         if (armature) this.animation = armature.getAnimation();
 76         return true;
 77     },
 78 
 79     /**
 80      * Start the Process
 81      * @param {ccs.MovementBoneData} movementBoneData
 82      * @param {Number} durationTo
 83      * @param {Number} durationTween
 84      * @param {Boolean} loop
 85      * @param {ccs.TweenType} tweenEasing
 86      */
 87     play:function (movementBoneData, durationTo, durationTween, loop, tweenEasing) {
 88         ccs.ProcessBase.prototype.play.call(this, durationTo, tweenEasing);
 89 
 90         if(loop){
 91             this._loopType = ccs.ANIMATION_TYPE_TO_LOOP_FRONT;
 92         }else{
 93             this._loopType = ccs.ANIMATION_TYPE_NO_LOOP;
 94         }
 95 
 96         this._totalDuration = 0;
 97         this._betweenDuration = 0;
 98         this._fromIndex = this._toIndex = 0;
 99 
100         var difMovement = movementBoneData != this._movementBoneData;
101         this._movementBoneData = movementBoneData;
102         this._rawDuration = this._movementBoneData.duration;
103         var nextKeyFrame = this._movementBoneData.getFrameData(0);
104         this._tweenData.displayIndex = nextKeyFrame.displayIndex;
105 
106         if (this._bone.getArmature().getArmatureData().dataVersion >= ccs.CONST_VERSION_COMBINED)        {
107             ccs.TransformHelp.nodeSub(this._tweenData, this._bone.getBoneData());
108             this._tweenData.scaleX += 1;
109             this._tweenData.scaleY += 1;
110         }
111 
112         if (this._rawDuration == 0 || this._movementBoneData.frameList.length == 1) {
113             this._loopType = ccs.ANIMATION_TYPE_SINGLE_FRAME;
114             if (durationTo == 0) {
115                 this.setBetween(nextKeyFrame, nextKeyFrame);
116             } else {
117                 this.setBetween(this._tweenData, nextKeyFrame);
118             }
119             this._frameTweenEasing = ccs.TweenType.linear;
120         }
121         else if (this._movementBoneData.frameList.length > 1) {
122             this._durationTween = durationTween * this._movementBoneData.scale;
123             if (loop && this._movementBoneData.delay != 0) {
124                 this.setBetween(this._tweenData, this.tweenNodeTo(this.updateFrameData(1 - this._movementBoneData.delay), this._between));
125             }
126             else {
127                 if (!difMovement || durationTo == 0)
128                     this.setBetween(nextKeyFrame, nextKeyFrame);
129                 else
130                     this.setBetween(this._tweenData, nextKeyFrame);
131             }
132         }
133         this.tweenNodeTo(0);
134     },
135 
136     gotoAndPlay: function (frameIndex) {
137         ccs.ProcessBase.prototype.gotoFrame.call(this, frameIndex);
138         this._totalDuration = 0;
139         this._betweenDuration = 0;
140         this._fromIndex = this._toIndex = 0;
141         this._isPlaying = true;
142         this._isComplete = this._isPause = false;
143         this._currentPercent = this._curFrameIndex / (this._rawDuration-1);
144         this._currentFrame = this._nextFrameIndex * this._currentPercent;
145     },
146 
147     gotoAndPause: function (frameIndex) {
148         this.gotoAndPlay(frameIndex);
149         this.pause();
150     },
151 
152     /**
153      * update will call this handler, you can handle your logic here
154      */
155     updateHandler:function () {
156         var locCurrentPercent = this._currentPercent;
157         var locLoopType = this._loopType;
158         if (locCurrentPercent >= 1) {
159             switch (locLoopType) {
160                 case ccs.ANIMATION_TYPE_SINGLE_FRAME:
161                     locCurrentPercent = 1;
162                     this._isComplete = true;
163                     this._isPlaying = false;
164                     break;
165                 case ccs.ANIMATION_TYPE_NO_LOOP:
166                     locLoopType = ccs.ANIMATION_TYPE_MAX;
167                     if (this._durationTween <= 0) {
168                         locCurrentPercent = 1;
169                     }
170                     else {
171                         locCurrentPercent = (locCurrentPercent - 1) * this._nextFrameIndex / this._durationTween;
172                     }
173                     if (locCurrentPercent >= 1) {
174                         locCurrentPercent = 1;
175                         this._isComplete = true;
176                         this._isPlaying = false;
177                         break;
178                     }
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                     if (this._movementBoneData.delay != 0) {
191                         this._currentFrame = (1 - this._movementBoneData.delay) * this._nextFrameIndex;
192                         locCurrentPercent = this._currentFrame / this._nextFrameIndex;
193 
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                     this._totalDuration = 0;
211                     this._betweenDuration = 0;
212                     break;
213             }
214         }
215 
216         if (locCurrentPercent < 1 && locLoopType < ccs.ANIMATION_TYPE_TO_LOOP_BACK) {
217             locCurrentPercent = Math.sin(locCurrentPercent * cc.PI / 2);
218         }
219 
220         this._currentPercent = locCurrentPercent;
221         this._loopType = locLoopType;
222 
223         if (locLoopType > ccs.ANIMATION_TYPE_TO_LOOP_BACK) {
224             locCurrentPercent = this.updateFrameData(locCurrentPercent);
225         }
226         if (this._frameTweenEasing != ccs.TweenType.tweenEasingMax) {
227             this.tweenNodeTo(locCurrentPercent);
228         }
229     },
230 
231     /**
232      * Calculate the between value of _from and _to, and give it to between frame data
233      * @param {ccs.FrameData} from
234      * @param {ccs.FrameData} to
235      * @param {Boolean} limit
236      */
237     setBetween:function (from, to, limit) {
238         if (typeof limit == "undefined") {
239             limit = true;
240         }
241         do
242         {
243             if (from.displayIndex < 0 && to.displayIndex >= 0) {
244                 this._from.copy(to);
245                 this._between.subtract(to, to, limit);
246                 break;
247             }
248             if (to.displayIndex < 0 && from.displayIndex >= 0) {
249                 this._from.copy(from);
250                 this._between.subtract(to, to, limit);
251                 break;
252             }
253             this._from.copy(from);
254             this._between.subtract(from, to, limit);
255         } while (0);
256         if (!from.isTween){
257             this._tweenData.copy(from);
258             this._tweenData.isTween = true;
259         }
260         this.arriveKeyFrame(from);
261     },
262 
263     /**
264      * Update display index and process the key frame event when arrived a key frame
265      * @param {ccs.FrameData} keyFrameData
266      */
267     arriveKeyFrame:function (keyFrameData) {
268         if (keyFrameData) {
269             var locBone = this._bone;
270             var displayIndex = keyFrameData.displayIndex;
271             var displayManager = locBone.getDisplayManager();
272             if (!displayManager.getForceChangeDisplay()) {
273                 displayManager.changeDisplayWithIndex(displayIndex, false);
274                 var locRenderNode = displayManager.getDisplayRenderNode();
275                 if(locRenderNode)
276                     locRenderNode.setBlendFunc(keyFrameData.blendFunc);
277             }
278             this._tweenData.zOrder = keyFrameData.zOrder;
279             locBone.updateZOrder();
280             var childAramture = locBone.getChildArmature();
281             if (childAramture) {
282                 if (keyFrameData.movement != "") {
283                     childAramture.getAnimation().play(keyFrameData.movement);
284                 }
285             }
286         }
287     },
288 
289     /**
290      * According to the percent to calculate current CCFrameData with tween effect
291      * @param {Number} percent
292      * @param {ccs.FrameData} node
293      * @return {ccs.FrameData}
294      */
295     tweenNodeTo:function (percent, node) {
296         if (!node) {
297             node = this._tweenData;
298         }
299         var locFrom = this._from;
300         var locBetween = this._between;
301         if (!locFrom.isTween){
302             percent = 0;
303         }
304         node.x = locFrom.x + percent * locBetween.x;
305         node.y = locFrom.y + percent * locBetween.y;
306         node.scaleX = locFrom.scaleX + percent * locBetween.scaleX;
307         node.scaleY = locFrom.scaleY + percent * locBetween.scaleY;
308         node.skewX = locFrom.skewX + percent * locBetween.skewX;
309         node.skewY = locFrom.skewY + percent * locBetween.skewY;
310 
311         this._bone.setTransformDirty(true);
312         if (node && locBetween.isUseColorInfo)
313             this.tweenColorTo(percent, node);
314 
315         return node;
316     },
317 
318     tweenColorTo:function(percent,node){
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) {
334         if (currentPercent > 1 && this._movementBoneData.delay != 0) {
335             currentPercent = ccs.fmodf(currentPercent,1);
336         }
337         var playedTime = (this._rawDuration-1) * currentPercent;
338         var from, to;
339         var locTotalDuration = this._totalDuration,locBetweenDuration = this._betweenDuration, locToIndex = this._toIndex;
340         // if play to current frame's front or back, then find current frame again
341         if (playedTime < locTotalDuration || playedTime >= locTotalDuration + locBetweenDuration) {
342             /*
343              *  get frame length, if this._toIndex >= _length, then set this._toIndex to 0, start anew.
344              *  this._toIndex is next index will play
345              */
346             var length = this._movementBoneData.frameList.length;
347             var frames = this._movementBoneData.frameList;
348             if (playedTime < frames[0].frameID){
349                 from = to = frames[0];
350                 this.setBetween(from, to);
351                 return currentPercent;
352             }
353             else if (playedTime >= frames[length - 1].frameID) {
354                 if (this._passLastFrame) {
355                     from = to = frames[length - 1];
356                     this.setBetween(from, to);
357                     return currentPercent;
358                 }
359                 this._passLastFrame = true;
360             } else {
361                 this._passLastFrame = false;
362             }
363 
364             do {
365                 this._fromIndex = locToIndex;
366                 from = frames[this._fromIndex];
367                 locTotalDuration = from.frameID;
368                 locToIndex = this._fromIndex + 1;
369                 if (locToIndex >= length) {
370                     locToIndex = 0;
371                 }
372                 to = frames[locToIndex];
373 
374                 //! Guaranteed to trigger frame event
375                 if(from.event&& !this.animation.isIgnoreFrameEvent()){
376                     this.animation.frameEvent(this._bone, from.event,from.frameID, playedTime);
377                 }
378 
379                 if (playedTime == from.frameID|| (this._passLastFrame && this._fromIndex == length-1)){
380                     break;
381                 }
382             }
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             this._totalDuration = locTotalDuration;
389             this._betweenDuration = locBetweenDuration;
390             this._toIndex = locToIndex;
391         }
392 
393         currentPercent = locBetweenDuration == 0 ? 0 : (playedTime - locTotalDuration) / locBetweenDuration;
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      * animation setter
407      * @param {ccs.ArmatureAnimation} animation
408      */
409     setAnimation:function (animation) {
410         this.animation = animation;
411     },
412 
413     /**
414      * animation getter
415      * @return {ccs.ArmatureAnimation}
416      */
417     getAnimation:function () {
418         return this.animation;
419     },
420 
421     release:function () {
422         this._from = null;
423         this._between = null;
424     }
425 });
426 
427 /**
428  * allocates and initializes a ArmatureAnimation.
429  * @constructs
430  * @param {ccs.Bone} bone
431  * @return {ccs.ArmatureAnimation}
432  * @example
433  * // example
434  * var animation = ccs.ArmatureAnimation.create();
435  */
436 ccs.Tween.create = function (bone) {
437     var tween = new ccs.Tween();
438     if (tween && tween.init(bone)) {
439         return tween;
440     }
441     return null;
442 };
443