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          var armature = bone.getArmature();
 72         if (armature) this._animation = armature.getAnimation();
 73         return true;
 74     },
 75 
 76     /**
 77      * Start the Process
 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, tweenEasing);
 86 
 87         if(loop){
 88             this._loopType = CC_ANIMATION_TYPE_TO_LOOP_FRONT;
 89         }else{
 90             this._loopType = CC_ANIMATION_TYPE_NO_LOOP;
 91         }
 92 
 93         this._totalDuration = 0;
 94         this._betweenDuration = 0;
 95         this._fromIndex = this._toIndex = 0;
 96 
 97         var difMovement = movementBoneData != this._movementBoneData;
 98         this._movementBoneData = movementBoneData;
 99         this._rawDuration = this._movementBoneData.duration;
100         var nextKeyFrame = this._movementBoneData.getFrameData(0);
101         this._tweenData.displayIndex = nextKeyFrame.displayIndex;
102 
103         if (this._bone.getArmature().getArmatureData().dataVersion >= ccs.CONST_VERSION_COMBINED)        {
104             ccs.TransformHelp.nodeSub(this._tweenData, this._bone.getBoneData());
105             this._tweenData.scaleX += 1;
106             this._tweenData.scaleY += 1;
107         }
108 
109         if (this._rawDuration == 0 || this._movementBoneData.frameList.length == 1) {
110             this._loopType = CC_ANIMATION_TYPE_SINGLE_FRAME;
111             if (durationTo == 0) {
112                 this.setBetween(nextKeyFrame, nextKeyFrame);
113             } else {
114                 this.setBetween(this._tweenData, nextKeyFrame);
115             }
116             this._frameTweenEasing = ccs.TweenType.linear;
117         }
118         else if (this._movementBoneData.frameList.length > 1) {
119             this._durationTween = durationTween * this._movementBoneData.scale;
120             if (loop && this._movementBoneData.delay != 0) {
121                 this.setBetween(this._tweenData, this.tweenNodeTo(this.updateFrameData(1 - this._movementBoneData.delay), this._between));
122             }
123             else {
124                 if (!difMovement || durationTo == 0)
125                     this.setBetween(nextKeyFrame, nextKeyFrame);
126                 else
127                     this.setBetween(this._tweenData, nextKeyFrame);
128             }
129         }
130         this.tweenNodeTo(0);
131     },
132 
133     gotoAndPlay: function (frameIndex) {
134         ccs.ProcessBase.prototype.gotoFrame.call(this, frameIndex);
135         this._totalDuration = 0;
136         this._betweenDuration = 0;
137         this._fromIndex = this._toIndex = 0;
138         this._isPlaying = true;
139         this._isComplete = this._isPause = false;
140         this._currentPercent = this._curFrameIndex / (this._rawDuration-1);
141         this._currentFrame = this._nextFrameIndex * this._currentPercent;
142     },
143 
144     gotoAndPause: function (frameIndex) {
145         this.gotoAndPlay(frameIndex);
146         this.pause();
147     },
148 
149     /**
150      * update will call this handler, you can handle your logic here
151      */
152     updateHandler:function () {
153         var locCurrentPercent = this._currentPercent;
154         var locLoopType = this._loopType;
155         if (locCurrentPercent >= 1) {
156             switch (locLoopType) {
157                 case CC_ANIMATION_TYPE_SINGLE_FRAME:
158                     locCurrentPercent = 1;
159                     this._isComplete = true;
160                     this._isPlaying = false;
161                     break;
162                 case CC_ANIMATION_TYPE_NO_LOOP:
163                     locLoopType = CC_ANIMATION_TYPE_MAX;
164                     if (this._durationTween <= 0) {
165                         locCurrentPercent = 1;
166                     }
167                     else {
168                         locCurrentPercent = (locCurrentPercent - 1) * this._nextFrameIndex / this._durationTween;
169                     }
170                     if (locCurrentPercent >= 1) {
171                         locCurrentPercent = 1;
172                         this._isComplete = true;
173                         this._isPlaying = false;
174                         break;
175                     }
176                     else {
177                         this._nextFrameIndex = this._durationTween;
178                         this._currentFrame = locCurrentPercent * this._nextFrameIndex;
179                         this._totalDuration = 0;
180                         this._betweenDuration = 0;
181                         this._fromIndex = this._toIndex = 0;
182                         break;
183                     }
184                 case CC_ANIMATION_TYPE_TO_LOOP_FRONT:
185                     locLoopType = CC_ANIMATION_TYPE_LOOP_FRONT;
186                     this._nextFrameIndex = this._durationTween > 0 ? this._durationTween : 1;
187                     if (this._movementBoneData.delay != 0) {
188                         this._currentFrame = (1 - this._movementBoneData.delay) * this._nextFrameIndex;
189                         locCurrentPercent = this._currentFrame / this._nextFrameIndex;
190 
191                     } else {
192                         locCurrentPercent = 0;
193                         this._currentFrame = 0;
194                     }
195 
196                     this._totalDuration = 0;
197                     this._betweenDuration = 0;
198                     this._fromIndex = this._toIndex = 0;
199                     break;
200                 case CC_ANIMATION_TYPE_MAX:
201                     locCurrentPercent = 1;
202                     this._isComplete = true;
203                     this._isPlaying = false;
204                     break;
205                 default:
206                     this._currentFrame = ccs.fmodf(this._currentFrame, this._nextFrameIndex);
207                     this._totalDuration = 0;
208                     this._betweenDuration = 0;
209                     this._fromIndex = this._toIndex = 0;
210                     break;
211             }
212         }
213 
214         if (locCurrentPercent < 1 && locLoopType < CC_ANIMATION_TYPE_TO_LOOP_BACK) {
215             locCurrentPercent = Math.sin(locCurrentPercent * cc.PI / 2);
216         }
217 
218         this._currentPercent = locCurrentPercent;
219         this._loopType = locLoopType;
220 
221         if (locLoopType > CC_ANIMATION_TYPE_TO_LOOP_BACK) {
222             locCurrentPercent = this.updateFrameData(locCurrentPercent);
223         }
224         if (this._frameTweenEasing != ccs.TweenType.tweenEasingMax) {
225             this.tweenNodeTo(locCurrentPercent);
226         }
227     },
228 
229     /**
230      * Calculate the between value of _from and _to, and give it to between frame data
231      * @param {ccs.FrameData} from
232      * @param {ccs.FrameData} to
233      * @param {Boolean} limit
234      */
235     setBetween:function (from, to, limit) {
236         if (typeof limit == "undefined") {
237             limit = true;
238         }
239         do
240         {
241             if (from.displayIndex < 0 && to.displayIndex >= 0) {
242                 this._from.copy(to);
243                 this._between.subtract(to, to, limit);
244                 break;
245             }
246             if (to.displayIndex < 0 && from.displayIndex >= 0) {
247                 this._from.copy(from);
248                 this._between.subtract(to, to, limit);
249                 break;
250             }
251             this._from.copy(from);
252             this._between.subtract(from, to, limit);
253         } while (0);
254         if (!from.isTween){
255             this._tweenData.copy(from);
256             this._tweenData.isTween = true;
257         }
258         this.arriveKeyFrame(from);
259     },
260 
261     /**
262      * Update display index and process the key frame event when arrived a key frame
263      * @param {ccs.FrameData} keyFrameData
264      */
265     arriveKeyFrame:function (keyFrameData) {
266         if (keyFrameData) {
267             var locBone = this._bone;
268             var displayIndex = keyFrameData.displayIndex;
269             var displayManager = locBone.getDisplayManager();
270             if (!displayManager.getForceChangeDisplay()) {
271                 displayManager.changeDisplayWithIndex(displayIndex, false);
272 
273             }
274             this._tweenData.zOrder = keyFrameData.zOrder;
275             locBone.updateZOrder();
276             locBone.setBlendFunc(keyFrameData.blendFunc);
277             var childAramture = locBone.getChildArmature();
278             if (childAramture) {
279                 if (keyFrameData.movement != "") {
280                     childAramture.getAnimation().play(keyFrameData.movement);
281                 }
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) {
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         }
301         node.x = locFrom.x + percent * locBetween.x;
302         node.y = locFrom.y + percent * locBetween.y;
303         node.scaleX = locFrom.scaleX + percent * locBetween.scaleX;
304         node.scaleY = locFrom.scaleY + percent * locBetween.scaleY;
305         node.skewX = locFrom.skewX + percent * locBetween.skewX;
306         node.skewY = locFrom.skewY + percent * locBetween.skewY;
307 
308         this._bone.setTransformDirty(true);
309         if (node && locBetween.isUseColorInfo)
310             this.tweenColorTo(percent, node);
311 
312         return node;
313     },
314 
315     tweenColorTo:function(percent,node){
316         var locFrom = this._from;
317         var locBetween = this._between;
318         node.a = locFrom.a + percent * locBetween.a;
319         node.r = locFrom.r + percent * locBetween.r;
320         node.g = locFrom.g + percent * locBetween.g;
321         node.b = locFrom.b + percent * locBetween.b;
322         this._bone.updateColor();
323     },
324 
325     /**
326      * Calculate which frame arrived, and if current frame have event, then call the event listener
327      * @param {Number} currentPercent
328      * @return {Number}
329      */
330     updateFrameData:function (currentPercent) {
331         if (currentPercent > 1 && this._movementBoneData.delay != 0) {
332             currentPercent = ccs.fmodf(currentPercent,1);
333         }
334         var playedTime = (this._rawDuration-1) * currentPercent;
335         var from, to;
336         var locTotalDuration = this._totalDuration,locBetweenDuration = this._betweenDuration, locToIndex = this._toIndex;
337         // if play to current frame's front or back, then find current frame again
338         if (playedTime < locTotalDuration || playedTime >= locTotalDuration + locBetweenDuration) {
339             /*
340              *  get frame length, if this._toIndex >= _length, then set this._toIndex to 0, start anew.
341              *  this._toIndex is next index will play
342              */
343             var length = this._movementBoneData.frameList.length;
344             var frames = this._movementBoneData.frameList;
345             if (playedTime < frames[0].frameID){
346                 from = to = frames[0];
347                 this.setBetween(from, to);
348                 return currentPercent;
349             }
350             else if (playedTime >= frames[length - 1].frameID) {
351                 if (this._passLastFrame) {
352                     from = to = frames[length - 1];
353                     this.setBetween(from, to);
354                     return currentPercent;
355                 }
356                 this._passLastFrame = true;
357             } else {
358                 this._passLastFrame = false;
359             }
360 
361             do {
362                 this._fromIndex = locToIndex;
363                 from = frames[this._fromIndex];
364                 locTotalDuration = from.frameID;
365                 locToIndex = this._fromIndex + 1;
366                 if (locToIndex >= length) {
367                     locToIndex = 0;
368                 }
369                 to = frames[locToIndex];
370 
371                 //! Guaranteed to trigger frame event
372                 if(from.event&& !this._animation.isIgnoreFrameEvent()){
373                     this._animation.frameEvent(this._bone, from.event,from.frameID, playedTime);
374                 }
375 
376                 if (playedTime == from.frameID|| (this._passLastFrame && this._fromIndex == length-1)){
377                     break;
378                 }
379             }
380             while  (playedTime < from.frameID || playedTime >= to.frameID);
381 
382             locBetweenDuration = to.frameID - from.frameID;
383             this._frameTweenEasing = from.tweenEasing;
384             this.setBetween(from, to, false);
385             this._totalDuration = locTotalDuration;
386             this._betweenDuration = locBetweenDuration;
387             this._toIndex = locToIndex;
388         }
389 
390         currentPercent = locBetweenDuration == 0 ? 0 : (playedTime - locTotalDuration) / locBetweenDuration;
391 
392         /*
393          *  if frame tween easing equal to TWEEN_EASING_MAX, then it will not do tween.
394          */
395         var tweenType = (this._frameTweenEasing != ccs.TweenType.linear) ? this._frameTweenEasing : this._tweenEasing;
396         if (tweenType != ccs.TweenType.tweenEasingMax&&tweenType != ccs.TweenType.linear&& !this._passLastFrame) {
397             currentPercent = ccs.TweenFunction.tweenTo(currentPercent, tweenType, this._from.easingParams);
398         }
399         return currentPercent;
400     },
401 
402     /**
403      * animation setter
404      * @param {ccs.ArmatureAnimation} animation
405      */
406     setAnimation:function (animation) {
407         this._animation = animation;
408     },
409 
410     /**
411      * animation getter
412      * @return {ccs.ArmatureAnimation}
413      */
414     getAnimation:function () {
415         return this._animation;
416     },
417 
418     release:function () {
419         this._from = null;
420         this._between = null;
421     }
422 });
423 
424 /**
425  * allocates and initializes a ArmatureAnimation.
426  * @constructs
427  * @param {ccs.Bone} bone
428  * @return {ccs.ArmatureAnimation}
429  * @example
430  * // example
431  * var animation = ccs.ArmatureAnimation.create();
432  */
433 ccs.Tween.create = function (bone) {
434     var tween = new ccs.Tween();
435     if (tween && tween.init(bone)) {
436         return tween;
437     }
438     return null;
439 };
440