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