1 /****************************************************************************
  2  Copyright (c) 2013-2014 Chukong Technologies Inc.
  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  * timeline object
 27  * @class
 28  * @extend ccs.Class
 29  */
 30 ccs.Timeline = ccs.Class.extend({
 31 
 32     //{ccs.Frame}
 33     _frames: null,
 34     //{ccs.Frame}
 35     _currentKeyFrame: null,
 36     //{Number}
 37     _currentKeyFrameIndex: null,
 38     //{Number}
 39     _fromIndex: null,
 40     //{Number}
 41     _toIndex: null,
 42     //{Number}
 43     _betweenDuration: null,
 44     //{Number}
 45     _actionTag: null,
 46     //{ccs.ActionTimeline}
 47     _ActionTimeline: null,
 48     //{cc.Node}
 49     _node: null,
 50 
 51     ctor: function(){
 52         this._frames = [];
 53         this._currentKeyFrame = null;
 54         this._currentKeyFrameIndex = 0;
 55         this._fromIndex = 0;
 56         this._toIndex = 0;
 57         this._betweenDuration = 0;
 58         this._actionTag = 0;
 59         this._ActionTimeline = null;
 60         this._node = null;
 61     },
 62 
 63     _gotoFrame: function(frameIndex){
 64         if(this._frames.length === 0)
 65             return;
 66 
 67         this._binarySearchKeyFrame(frameIndex);
 68         this._apply(frameIndex);
 69     },
 70 
 71     _stepToFrame: function(frameIndex){
 72         if(this._frames.length === 0)
 73             return;
 74 
 75         this._updateCurrentKeyFrame(frameIndex);
 76         this._apply(frameIndex);
 77     },
 78 
 79     /**
 80      * Get the frame list
 81      * @returns {ccs.Frame}
 82      */
 83     getFrames: function(){
 84         return this._frames;
 85     },
 86 
 87     /**
 88      * push frame to frame list
 89      * @param {ccs.Frame} frame
 90      */
 91     addFrame: function(frame){
 92         this._frames.push(frame);
 93         frame.setTimeline(this)
 94     },
 95 
 96     /**
 97      * insert the frame to frame list
 98      * @param {ccs.Frame} frame
 99      * @param {Number} index
100      */
101     insertFrame: function(frame, index){
102         this._frames.splice(index, 0, frame);
103         frame.setTimeline(this);
104 
105     },
106 
107     /**
108      * remove frame
109      * @param {ccs.Frame} frame
110      */
111     removeFrame: function(frame){
112         cc.arrayRemoveObject(this._frames, frame);
113         frame.setTimeline(null);
114     },
115 
116     /**
117      * Set the action tag
118      * @param {Number} tag
119      */
120     setActionTag: function(tag){
121         this._actionTag = tag;
122     },
123 
124     /**
125      * Gets the action tag
126      * return {Number}
127      */
128     getActionTag: function(){
129         return this._actionTag;
130     },
131 
132     /**
133      * Set the node
134      * @param {cc.Node} node
135      */
136     setNode: function(node){
137         for (var i=0; i<this._frames.length; i++){
138             var frame = this._frames[i];
139             frame.setNode(node);
140         }
141     },
142 
143     /**
144      * Gets the node
145      * return {cc.Node}
146      */
147     getNode: function(){
148         return this._node;
149     },
150 
151     /**
152      * Set the action timeline
153      * @param {ccs.ActionTimeline} action
154      */
155     setActionTimeline: function(action){
156         this._ActionTimeline = action;
157     },
158 
159     /**
160      * get the action timeline
161      * return {cc.Action}
162      */
163     getActionTimeline: function(){
164         return this._ActionTimeline;
165     },
166 
167     /**
168      * to copy object with deep copy.
169      * returns a clone of action.
170      * @return {ccs.Timeline}
171      */
172     clone: function(){
173         var timeline = new ccs.Timeline();
174         timeline._actionTag = this._actionTag;
175 
176         for (var i=0;i<this._frames.length;i++)
177         {
178             var frame = this._frames[i];
179             var newFrame = frame.clone();
180             timeline.addFrame(newFrame);
181         }
182 
183         return timeline;
184 
185     },
186 
187     _apply: function(frameIndex){
188         if (this._currentKeyFrame)
189         {
190             var currentPercent = this._betweenDuration <= 0 ? 0 : (frameIndex - this._currentKeyFrameIndex) / this._betweenDuration;
191             this._currentKeyFrame.apply(currentPercent);
192         }
193     },
194 
195     _binarySearchKeyFrame: function(frameIndex){
196         var from = null;
197         var to   = null;
198 
199         var length = this._frames.length;
200         var needEnterFrame = false;
201 
202         do{
203             if (frameIndex < this._frames[0].getFrameIndex()){
204                 if(this._currentKeyFrameIndex >= this._frames[0].getFrameIndex())
205                     needEnterFrame = true;
206 
207                 this._fromIndex = 0;
208                 this._toIndex = 0;
209 
210                 from = to = this._frames[0];
211                 this._currentKeyFrameIndex = 0;
212                 this._betweenDuration = this._frames[0].getFrameIndex();
213                 break;
214             }else if(frameIndex >= this._frames[length - 1].getFrameIndex()){
215                 this._fromIndex = length - 1;
216                 this._toIndex = 0;
217 
218                 from = to = this._frames[length - 1];
219                 this._currentKeyFrameIndex = this._frames[length - 1].getFrameIndex();
220                 this._betweenDuration = 0;
221                 break;
222             }
223 
224             var target = -1;
225             var low = 0,
226                 high = length - 1,
227                 mid = 0;
228             while(low <= high){
229                 mid = Math.ceil(( low + high )/2);
230                 if(frameIndex >= this._frames[mid].getFrameIndex() && frameIndex < this._frames[mid + 1].getFrameIndex())
231                 {
232                     target = mid;
233                     break;
234                 }
235                 if(this._frames[mid].getFrameIndex()>frameIndex)
236                     high = mid - 1;
237                 else
238                     low = mid + 1;
239             }
240 
241             this._fromIndex = target;
242 
243             if(length > 1)
244                 this._toIndex = (target + 1) | 0;
245             else
246                 this._toIndex = (target) | 0;
247 
248             from = this._frames[this._fromIndex];
249             to   = this._frames[this._toIndex];
250 
251             from = this._frames[target];
252             to   = this._frames[target+1];
253 
254             if(target === 0 && this._currentKeyFrameIndex < from.getFrameIndex())
255                 needEnterFrame = true;
256 
257             this._currentKeyFrameIndex = from.getFrameIndex();
258             this._betweenDuration = to.getFrameIndex() - from.getFrameIndex();
259         } while (0);
260 
261         if(needEnterFrame || this._currentKeyFrame != from) {
262             this._currentKeyFrame = from;
263             this._currentKeyFrame.onEnter(to);
264         }
265 
266     },
267 
268     _updateCurrentKeyFrame: function(frameIndex){
269         if(frameIndex > 60)
270             var a = 0;
271         //! If play to current frame's front or back, then find current frame again
272         if (frameIndex < this._currentKeyFrameIndex || frameIndex >= this._currentKeyFrameIndex + this._betweenDuration)
273         {
274             var from = null;
275             var to = null;
276 
277             do
278             {
279                 var length = this._frames.length;
280 
281                 if (frameIndex < this._frames[0].getFrameIndex())
282                 {
283                     from = to = this._frames[0];
284                     this._currentKeyFrameIndex = 0;
285                     this._betweenDuration = this._frames[0].getFrameIndex();
286                     break;
287                 }
288                 else if(frameIndex >= this._frames[length - 1].getFrameIndex())
289                 {
290                     var lastFrameIndex = this._frames[length - 1].getFrameIndex();
291                     if(this._currentKeyFrameIndex >= lastFrameIndex)
292                         return;
293                     frameIndex = lastFrameIndex;
294                 }
295 
296                 do{
297                     this._fromIndex = this._toIndex;
298                     from = this._frames[this._fromIndex];
299                     this._currentKeyFrameIndex  = from.getFrameIndex();
300 
301                     this._toIndex = this._fromIndex + 1;
302                     if (this._toIndex >= length)
303                     {
304                         this._toIndex = 0;
305                     }
306 
307                     to = this._frames[this._toIndex];
308 
309                     if (frameIndex === from.getFrameIndex())
310                         break;
311                     if(frameIndex > from.getFrameIndex() && frameIndex < to.getFrameIndex())
312                         break;
313                     if(from.isEnterWhenPassed())
314                         from.onEnter(to);
315                 }while (true);
316 
317                 this._betweenDuration = to.getFrameIndex() - from.getFrameIndex();
318 
319             } while (0);
320 
321             this._currentKeyFrame = from;
322             this._currentKeyFrame.onEnter(to);
323         }
324     }
325 
326 });
327 
328 /**
329  * Create the Timeline
330  *
331  * @deprecated v3.0, please use new ccs.Timeline() instead.
332  * @returns {ccs.Timeline}
333  */
334 ccs.Timeline.create = function(){
335     return new ccs.Timeline();
336 };