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