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