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         this._previousPosition = cc.p(this.target.getPositionX(), this.target.getPositionY());
179         this._accumulatedDiff = cc.p(0, 0);
180     },
181 
182     /**
183      * @param {Number} time
184      */
185     update:function (time) {
186         var p, lt;
187         var ps = this._points;
188         // eg.
189         // p..p..p..p..p..p..p
190         // 1..2..3..4..5..6..7
191         // want p to be 1, 2, 3, 4, 5, 6
192         if (time == 1) {
193             p = ps.length - 1;
194             lt = 1;
195         } else {
196             var locDT = this._deltaT;
197             p = 0 | (time / locDT);
198             lt = (time - locDT * p) / locDT;
199         }
200 
201         var newPos = cc.CardinalSplineAt(
202             cc.getControlPointAt(ps, p - 1),
203             cc.getControlPointAt(ps, p - 0),
204             cc.getControlPointAt(ps, p + 1),
205             cc.getControlPointAt(ps, p + 2),
206             this._tension, lt);
207 
208         if (cc.ENABLE_STACKABLE_ACTIONS) {
209             var tempX, tempY;
210             tempX = this.target.getPositionX() - this._previousPosition.x;
211             tempY = this.target.getPositionY() - this._previousPosition.y;
212             if (tempX != 0 || tempY != 0) {
213                 var locAccDiff = this._accumulatedDiff;
214                 tempX = locAccDiff.x + tempX;
215                 tempY = locAccDiff.y + tempY;
216                 locAccDiff.x = tempX;
217                 locAccDiff.y = tempY;
218                 newPos.x += tempX;
219                 newPos.y += tempY;
220             }
221         }
222         this.updatePosition(newPos);
223     },
224 
225     /**
226      * reverse a new cc.CardinalSplineTo
227      * @return {cc.CardinalSplineTo}
228      */
229     reverse:function () {
230         var reversePoints = cc.reverseControlPoints(this._points);
231         return cc.CardinalSplineTo.create(this._duration, reversePoints, this._tension);
232     },
233 
234     /**
235      * update position of target
236      * @param {cc.Point} newPos
237      */
238     updatePosition:function (newPos) {
239         this.target.setPosition(newPos);
240         this._previousPosition = newPos;
241     },
242 
243     /**
244      * Points getter
245      * @return {Array}
246      */
247     getPoints:function () {
248         return this._points;
249     },
250 
251     /**
252      * Points setter
253      * @param {Array} points
254      */
255     setPoints:function (points) {
256         this._points = points;
257     }
258 });
259 
260 /**
261  * creates an action with a Cardinal Spline array of points and tension
262  * @function
263  * @param {Number} duration
264  * @param {Array} points array of control points
265  * @param {Number} tension
266  * @return {cc.CardinalSplineTo}
267  *
268  * @example
269  * //create a cc.CardinalSplineTo
270  * var action1 = cc.CardinalSplineTo.create(3, array, 0);
271  */
272 cc.CardinalSplineTo.create = function (duration, points, tension) {
273     var ret = new cc.CardinalSplineTo();
274     if (ret.initWithDuration(duration, points, tension)) {
275         return ret;
276     }
277     return null;
278 };
279 
280 /**
281  * Cardinal Spline path.  http://en.wikipedia.org/wiki/Cubic_Hermite_spline#Cardinal_spline
282  * @class
283  * @extends cc.CardinalSplineTo
284  *
285  * @example
286  * //create a cc.CardinalSplineBy
287  * var action1 = cc.CardinalSplineBy.create(3, array, 0);
288  */
289 cc.CardinalSplineBy = cc.CardinalSplineTo.extend(/** @lends cc.CardinalSplineBy# */{
290     _startPosition:null,
291 
292     /**
293      * Constructor
294      */
295     ctor:function () {
296         cc.CardinalSplineTo.prototype.ctor.call(this);
297         this._startPosition = cc.p(0, 0);
298     },
299 
300     /**
301      * @param {cc.Node} target
302      */
303     startWithTarget:function (target) {
304         cc.CardinalSplineTo.prototype.startWithTarget.call(this, target);
305         this._startPosition.x = target.getPositionX();
306         this._startPosition.y = target.getPositionY();
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._previousPosition.x = posX;
358 	    this._previousPosition.y = posY;
359 	    this.target.setPosition(posX, 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