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         {
204             if (frameIndex <= this._frames[0].getFrameIndex())
205             {
206                 if(this._currentKeyFrameIndex >= this._frames[0].getFrameIndex())
207                     needEnterFrame = true;
208 
209                 this._fromIndex = 0;
210 
211                 if(length > 1)
212                     this._toIndex = 1;
213                 else
214                     this._toIndex = 0;
215 
216                 from = to = this._frames[0];
217                 this._currentKeyFrameIndex = 0;
218                 this._betweenDuration = this._frames[0].getFrameIndex();
219                 break;
220             }
221             else if(frameIndex >= this._frames[length - 1].getFrameIndex())
222             {
223                 this._fromIndex = length - 1;
224                 this._toIndex = 0;
225 
226                 from = to = this._frames[length - 1];
227                 this._currentKeyFrameIndex = this._frames[length - 1].getFrameIndex();
228                 this._betweenDuration = 0;
229                 break;
230             }
231 
232             var target = -1;
233             var low = 0,
234                 high = length - 1,
235                 mid = 0;
236             while(low <= high){
237                 mid = Math.ceil(( low + high )/2);
238                 if(frameIndex >= this._frames[mid].getFrameIndex() && frameIndex < this._frames[mid + 1].getFrameIndex())
239                 {
240                     target = mid;
241                     break;
242                 }
243                 if(this._frames[mid].getFrameIndex()>frameIndex)
244                     high = mid - 1;
245                 else
246                     low = mid + 1;
247             }
248 
249             this._fromIndex = target;
250 
251             if(length > 1)
252                 this._toIndex = (target + 1) | 0;
253             else
254                 this._toIndex = (target) | 0;
255 
256             from = this._frames[this._fromIndex];
257             to   = this._frames[this._toIndex];
258 
259             from = this._frames[target];
260             to   = this._frames[target+1];
261 
262             if(target === 0 && this._currentKeyFrameIndex < from.getFrameIndex())
263                 needEnterFrame = true;
264 
265             this._currentKeyFrameIndex = from.getFrameIndex();
266             this._betweenDuration = to.getFrameIndex() - from.getFrameIndex();
267         } while (0);
268 
269         if(needEnterFrame || this._currentKeyFrame != from) {
270             this._currentKeyFrame = from;
271             this._currentKeyFrame.onEnter(to);
272         }
273 
274     },
275 
276     _updateCurrentKeyFrame: function(frameIndex){
277         if(frameIndex > 60)
278             var a = 0;
279         //! If play to current frame's front or back, then find current frame again
280         if (frameIndex < this._currentKeyFrameIndex || frameIndex >= this._currentKeyFrameIndex + this._betweenDuration)
281         {
282             var from = null;
283             var to = null;
284 
285             do
286             {
287                 var length = this._frames.length;
288 
289                 if (frameIndex < this._frames[0].getFrameIndex())
290                 {
291                     from = to = this._frames[0];
292                     this._currentKeyFrameIndex = 0;
293                     this._betweenDuration = this._frames[0].getFrameIndex();
294                     break;
295                 }
296                 else if(frameIndex >= this._frames[length - 1].getFrameIndex())
297                 {
298                     var lastFrameIndex = this._frames[length - 1].getFrameIndex();
299                     if(this._currentKeyFrameIndex >= lastFrameIndex)
300                         return;
301                     frameIndex = lastFrameIndex;
302                 }
303 
304                 do{
305                     this._fromIndex = this._toIndex;
306                     from = this._frames[this._fromIndex];
307                     this._currentKeyFrameIndex  = from.getFrameIndex();
308 
309                     this._toIndex = this._fromIndex + 1;
310                     if (this._toIndex >= length)
311                     {
312                         this._toIndex = 0;
313                     }
314 
315                     to = this._frames[this._toIndex];
316 
317                     if (frameIndex === from.getFrameIndex())
318                         break;
319                     if(frameIndex > from.getFrameIndex() && frameIndex < to.getFrameIndex())
320                         break;
321                     if(from.isEnterWhenPassed())
322                         from.onEnter(to);
323                 }while (true);
324 
325                 this._betweenDuration = to.getFrameIndex() - from.getFrameIndex();
326 
327             } while (0);
328 
329             this._currentKeyFrame = from;
330             this._currentKeyFrame.onEnter(to);
331         }
332     }
333 
334 });
335 
336 /**
337  * Create the Timeline
338  *
339  * @deprecated v3.0, please use new ccs.Timeline() instead.
340  * @returns {ccs.Timeline}
341  */
342 ccs.Timeline.create = function(){
343     return new ccs.Timeline();
344 };