1 /****************************************************************************
  2  Copyright (c) 2010-2012 cocos2d-x.org
  3  Copyright (c) 2008-2010 Ricardo Quesada
  4  Copyright (c) 2011      Zynga Inc.
  5  Copyright (c) 2008 Radu Gruian
  6  Copyright (c) 2011 Vit Valentin
  7 
  8  http://www.cocos2d-x.org
  9 
 10  Permission is hereby granted, free of charge, to any person obtaining a copy
 11  of this software and associated documentation files (the "Software"), to deal
 12  in the Software without restriction, including without limitation the rights
 13  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 14  copies of the Software, and to permit persons to whom the Software is
 15  furnished to do so, subject to the following conditions:
 16 
 17  The above copyright notice and this permission notice shall be included in
 18  all copies or substantial portions of the Software.
 19 
 20  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 21  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 22  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 23  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 24  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 25  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 26  THE SOFTWARE.
 27 
 28  Orignal code by Radu Gruian: http://www.codeproject.com/Articles/30838/Overhauser-Catmull-Rom-Splines-for-Camera-Animatio.So
 29 
 30  Adapted to cocos2d-x by Vit Valentin
 31 
 32  Adapted from cocos2d-x to cocos2d-iphone by Ricardo Quesada
 33  ****************************************************************************/
 34 
 35 /**
 36  * <p>Returns the Cardinal Spline position for a given set of control points, tension and time CatmullRom Spline formula: <br/>
 37  *   s(-ttt + 2tt - t)P1 + s(-ttt + tt)P2 + (2ttt - 3tt + 1)P2 + s(ttt - 2tt + t)P3 + (-2ttt + 3tt)P3 + s(ttt - tt)P4
 38  * </p>
 39  * @function
 40  * @param {cc.Point} p0
 41  * @param {cc.Point} p1
 42  * @param {cc.Point} p2
 43  * @param {cc.Point} p3
 44  * @param {Number} tension
 45  * @param {Number} t
 46  * @return {cc.Point}
 47  */
 48 cc.CardinalSplineAt = function (p0, p1, p2, p3, tension, t) {
 49     var t2 = t * t;
 50     var t3 = t2 * t;
 51 
 52     /*
 53      * Formula: s(-ttt + 2tt - t)P1 + s(-ttt + tt)P2 + (2ttt - 3tt + 1)P2 + s(ttt - 2tt + t)P3 + (-2ttt + 3tt)P3 + s(ttt - tt)P4
 54      */
 55     var s = (1 - tension) / 2;
 56 
 57     var b1 = s * ((-t3 + (2 * t2)) - t);                      // s(-t3 + 2 t2 - t)P1
 58     var b2 = s * (-t3 + t2) + (2 * t3 - 3 * t2 + 1);          // s(-t3 + t2)P2 + (2 t3 - 3 t2 + 1)P2
 59     var b3 = s * (t3 - 2 * t2 + t) + (-2 * t3 + 3 * t2);      // s(t3 - 2 t2 + t)P3 + (-2 t3 + 3 t2)P3
 60     var b4 = s * (t3 - t2);                                   // s(t3 - t2)P4
 61 
 62     var x = (p0.x * b1 + p1.x * b2 + p2.x * b3 + p3.x * b4);
 63     var y = (p0.y * b1 + p1.y * b2 + p2.y * b3 + p3.y * b4);
 64     return cc.p(x, y);
 65 };
 66 
 67 
 68 /**
 69  * returns a new copy of the array reversed.
 70  * @return {Array}
 71  */
 72 cc.reverseControlPoints = function (controlPoints) {
 73     var newArray = [];
 74     for (var i = controlPoints.length - 1; i >= 0; i--) {
 75         newArray.push(cc.p(controlPoints[i].x, controlPoints[i].y));
 76     }
 77     return newArray;
 78 };
 79 
 80 cc.copyControlPoints = function (controlPoints) {
 81     var newArray = [];
 82     for (var i = 0; i < controlPoints.length; i++)
 83         newArray.push(cc.p(controlPoints[i].x, controlPoints[i].y));
 84     return newArray;
 85 };
 86 
 87 /**
 88  * returns a point from the array
 89  * @param {Array} controlPoints
 90  * @param {Number} pos
 91  * @return {Array}
 92  */
 93 cc.getControlPointAt = function (controlPoints, pos) {
 94     var p = Math.min(controlPoints.length - 1, Math.max(pos, 0));
 95     return controlPoints[p];
 96 };
 97 
 98 /**
 99  * reverse the current control point array inline, without generating a new one
100  */
101 cc.reverseControlPointsInline = function (controlPoints) {
102     var len = controlPoints.length;
103     var mid = 0 | (len / 2);
104     for (var i = 0; i < mid; ++i) {
105         var temp = controlPoints[i];
106         controlPoints[i] = controlPoints[len - i - 1];
107         controlPoints[len - i - 1] = temp;
108     }
109 };
110 
111 
112 /**
113  * Cardinal Spline path. http://en.wikipedia.org/wiki/Cubic_Hermite_spline#Cardinal_spline
114  * @class
115  * @extends cc.ActionInterval
116  *
117  * @example
118  * //create a cc.CardinalSplineTo
119  * var action1 = cc.CardinalSplineTo.create(3, array, 0);
120  */
121 cc.CardinalSplineTo = cc.ActionInterval.extend(/** @lends cc.CardinalSplineTo# */{
122     /** Array of control points */
123     _points:null,
124     _deltaT:0,
125     _tension:0,
126     _previousPosition:null,
127     _accumulatedDiff:null,
128 
129     /**
130      * Constructor
131      */
132     ctor:function () {
133         cc.ActionInterval.prototype.ctor.call(this);
134 
135         this._points = [];
136         this._deltaT = 0;
137         this._tension = 0;
138         this._previousPosition = null;
139         this._accumulatedDiff = null;
140     },
141 
142     /**
143      * initializes the action with a duration and an array of points
144      * @param {Number} duration
145      * @param {Array} points array of control points
146      * @param {Number} tension
147      * @return {Boolean}
148      */
149     initWithDuration:function (duration, points, tension) {
150         if(!points || points.length == 0)
151             throw "Invalid configuration. It must at least have one control point";
152 
153         if (cc.ActionInterval.prototype.initWithDuration.call(this, duration)) {
154             this.setPoints(points);
155             this._tension = tension;
156             return true;
157         }
158         return false;
159     },
160 
161     /**
162      * returns a new clone of the action
163      * @returns {cc.CardinalSplineTo}
164      */
165     clone:function () {
166         var action = new cc.CardinalSplineTo();
167         action.initWithDuration(this._duration, cc.copyControlPoints(this._points), this._tension);
168         return action;
169     },
170 
171     /**
172      * @param {cc.Node} target
173      */
174     startWithTarget:function (target) {
175         cc.ActionInterval.prototype.startWithTarget.call(this, target);
176         // Issue #1441 from cocos2d-iphone
177         this._deltaT = 1 / (this._points.length - 1);
178 
179         this._previousPosition = this._target.getPosition();
180         this._accumulatedDiff = cc.p(0, 0);
181     },
182 
183     /**
184      * @param {Number} time
185      */
186     update:function (time) {
187         var p, lt;
188         var ps = this._points;
189         // eg.
190         // p..p..p..p..p..p..p
191         // 1..2..3..4..5..6..7
192         // want p to be 1, 2, 3, 4, 5, 6
193         if (time == 1) {
194             p = ps.length - 1;
195             lt = 1;
196         } else {
197             var locDT = this._deltaT;
198             p = 0 | (time / locDT);
199             lt = (time - locDT * p) / locDT;
200         }
201 
202         var newPos = cc.CardinalSplineAt(
203             cc.getControlPointAt(ps, p - 1),
204             cc.getControlPointAt(ps, p - 0),
205             cc.getControlPointAt(ps, p + 1),
206             cc.getControlPointAt(ps, p + 2),
207             this._tension, lt);
208 
209         if (cc.ENABLE_STACKABLE_ACTIONS) {
210             var tempX, tempY;
211             tempX = this._target.getPositionX() - this._previousPosition.x;
212             tempY = this._target.getPositionY() - this._previousPosition.y;
213             if (tempX != 0 || tempY != 0) {
214                 var locAccDiff = this._accumulatedDiff;
215                 tempX = locAccDiff.x + tempX;
216                 tempY = locAccDiff.y + tempY;
217                 locAccDiff.x = tempX;
218                 locAccDiff.y = tempY;
219                 newPos.x += tempX;
220                 newPos.y += tempY;
221             }
222         }
223         this.updatePosition(newPos);
224     },
225 
226     /**
227      * reverse a new cc.CardinalSplineTo
228      * @return {cc.CardinalSplineTo}
229      */
230     reverse:function () {
231         var reversePoints = cc.reverseControlPoints(this._points);
232         return cc.CardinalSplineTo.create(this._duration, reversePoints, this._tension);
233     },
234 
235     /**
236      * update position of target
237      * @param {cc.Point} newPos
238      */
239     updatePosition:function (newPos) {
240         this._target.setPosition(newPos);
241         this._previousPosition = newPos;
242     },
243 
244     /**
245      * Points getter
246      * @return {Array}
247      */
248     getPoints:function () {
249         return this._points;
250     },
251 
252     /**
253      * Points setter
254      * @param {Array} points
255      */
256     setPoints:function (points) {
257         this._points = points;
258     }
259 });
260 
261 /**
262  * creates an action with a Cardinal Spline array of points and tension
263  * @function
264  * @param {Number} duration
265  * @param {Array} points array of control points
266  * @param {Number} tension
267  * @return {cc.CardinalSplineTo}
268  *
269  * @example
270  * //create a cc.CardinalSplineTo
271  * var action1 = cc.CardinalSplineTo.create(3, array, 0);
272  */
273 cc.CardinalSplineTo.create = function (duration, points, tension) {
274     var ret = new cc.CardinalSplineTo();
275     if (ret.initWithDuration(duration, points, tension)) {
276         return ret;
277     }
278     return null;
279 };
280 
281 /**
282  * Cardinal Spline path.  http://en.wikipedia.org/wiki/Cubic_Hermite_spline#Cardinal_spline
283  * @class
284  * @extends cc.CardinalSplineTo
285  *
286  * @example
287  * //create a cc.CardinalSplineBy
288  * var action1 = cc.CardinalSplineBy.create(3, array, 0);
289  */
290 cc.CardinalSplineBy = cc.CardinalSplineTo.extend(/** @lends cc.CardinalSplineBy# */{
291     _startPosition:null,
292 
293     /**
294      * Constructor
295      */
296     ctor:function () {
297         cc.CardinalSplineTo.prototype.ctor.call(this);
298         this._startPosition = cc.p(0, 0);
299     },
300 
301     /**
302      * @param {cc.Node} target
303      */
304     startWithTarget:function (target) {
305         cc.CardinalSplineTo.prototype.startWithTarget.call(this, target);
306         this._startPosition = target.getPosition();
307     },
308 
309     /**
310      * reverse a new cc.CardinalSplineBy
311      * @return {cc.CardinalSplineBy}
312      */
313     reverse:function () {
314         var copyConfig = this._points.slice();
315         var current;
316         //
317         // convert "absolutes" to "diffs"
318         //
319         var p = copyConfig[0];
320         for (var i = 1; i < copyConfig.length; ++i) {
321             current = copyConfig[i];
322             copyConfig[i] = cc.pSub(current, p);
323             p = current;
324         }
325 
326         // convert to "diffs" to "reverse absolute"
327         var reverseArray = cc.reverseControlPoints(copyConfig);
328 
329         // 1st element (which should be 0,0) should be here too
330         p = reverseArray[ reverseArray.length - 1 ];
331         reverseArray.pop();
332 
333         p.x = -p.x;
334         p.y = -p.y;
335 
336         reverseArray.unshift(p);
337         for (var i = 1; i < reverseArray.length; ++i) {
338             current = reverseArray[i];
339             current.x = -current.x;
340             current.y = -current.y;
341             current.x += p.x;
342             current.y += p.y;
343             reverseArray[i] = current;
344             p = current;
345         }
346         return cc.CardinalSplineBy.create(this._duration, reverseArray, this._tension);
347     },
348 
349     /**
350      * update position of target
351      * @param {cc.Point} newPos
352      */
353     updatePosition:function (newPos) {
354         var pos = this._startPosition;
355         var posX = newPos.x + pos.x;
356         var posY = newPos.y + pos.y;
357         this._target.setPosition(posX, posY);
358         this._previousPosition.x = posX;
359         this._previousPosition.y = posY;
360     },
361 
362     /**
363      * returns a new clone of the action
364      * @returns {cc.CardinalSplineBy}
365      */
366     clone:function () {
367         var a = new cc.CardinalSplineBy();
368         a.initWithDuration(this._duration, cc.copyControlPoints(this._points), this._tension);
369         return a;
370     }
371 });
372 
373 /**
374  * creates an action with a Cardinal Spline array of points and tension
375  * @function
376  * @param {Number} duration
377  * @param {Array} points
378  * @param {Number} tension
379  * @return {cc.CardinalSplineBy}
380  */
381 cc.CardinalSplineBy.create = function (duration, points, tension) {
382     var ret = new cc.CardinalSplineBy();
383     if (ret.initWithDuration(duration, points, tension))
384         return ret;
385     return null;
386 };
387 
388 /**
389  * <p>
390  *   An action that moves the target with a CatmullRom curve to a destination point.<br/>
391  *   A Catmull Rom is a Cardinal Spline with a tension of 0.5.  <br/>
392  *   http://en.wikipedia.org/wiki/Cubic_Hermite_spline#Catmull.E2.80.93Rom_spline
393  * </p>
394  * @class
395  * @extends cc.CardinalSplineTo
396  *
397  * @example
398  * var action1 = cc.CatmullRomTo.create(3, array);
399  */
400 cc.CatmullRomTo = cc.CardinalSplineTo.extend(/** @lends cc.CatmullRomTo# */{
401     /**
402      *  initializes the action with a duration and an array of points
403      */
404     initWithDuration:function (dt, points) {
405         return cc.CardinalSplineTo.prototype.initWithDuration.call(this, dt, points, 0.5);
406     },
407 
408     /**
409      * returns a new clone of the action
410      * @returns {cc.CatmullRomTo}
411      */
412     clone:function () {
413         var action = new cc.CatmullRomTo();
414         action.initWithDuration(this._duration, cc.copyControlPoints(this._points));
415         return action;
416     }
417 });
418 
419 /**
420  * creates an action with a Cardinal Spline array of points and tension
421  * @param {Number} dt
422  * @param {Array} points
423  * @return {cc.CatmullRomTo}
424  *
425  * @example
426  * var action1 = cc.CatmullRomTo.create(3, array);
427  */
428 cc.CatmullRomTo.create = function (dt, points) {
429     var ret = new cc.CatmullRomTo();
430     if (ret.initWithDuration(dt, points))
431         return ret;
432     return null;
433 };
434 
435 /**
436  * <p>
437  *   An action that moves the target with a CatmullRom curve by a certain distance.  <br/>
438  *   A Catmull Rom is a Cardinal Spline with a tension of 0.5.<br/>
439  *   http://en.wikipedia.org/wiki/Cubic_Hermite_spline#Catmull.E2.80.93Rom_spline
440  * </p>
441  * @class
442  * @extends cc.CardinalSplineBy
443  *
444  * @example
445  * var action1 = cc.CatmullRomBy.create(3, array);
446  */
447 cc.CatmullRomBy = cc.CardinalSplineBy.extend({
448     /** initializes the action with a duration and an array of points */
449     initWithDuration:function (dt, points) {
450         return cc.CardinalSplineTo.prototype.initWithDuration.call(this, dt, points, 0.5);
451     },
452 
453     /**
454      * returns a new clone of the action
455      * @returns {cc.CatmullRomBy}
456      */
457     clone:function () {
458         var action = new cc.CatmullRomBy();
459         action.initWithDuration(this._duration, cc.copyControlPoints(this._points));
460         return action;
461     }
462 });
463 
464 /**
465  * creates an action with a Cardinal Spline array of points and tension
466  *
467  * @example
468  * var action1 = cc.CatmullRomBy.create(3, array);
469  */
470 cc.CatmullRomBy.create = function (dt, points) {
471     var ret = new cc.CatmullRomBy();
472     if (ret.initWithDuration(dt, points))
473         return ret;
474     return null;
475 };
476