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         cc.Assert(points.length > 0, "Invalid configuration. It must at least have one control point");
151         if (cc.ActionInterval.prototype.initWithDuration.call(this, duration)) {
152             this.setPoints(points);
153             this._tension = tension;
154             return true;
155         }
156         return false;
157     },
158 
159     /**
160      * returns a new clone of the action
161      * @returns {cc.CardinalSplineTo}
162      */
163     clone:function () {
164         var action = new cc.CardinalSplineTo();
165         action.initWithDuration(this._duration, cc.copyControlPoints(this._points), this._tension);
166         return action;
167     },
168 
169     /**
170      * @param {cc.Node} target
171      */
172     startWithTarget:function (target) {
173         cc.ActionInterval.prototype.startWithTarget.call(this, target);
174         // Issue #1441 from cocos2d-iphone
175         this._deltaT = 1 / (this._points.length - 1);
176 
177         this._previousPosition = this._target.getPosition();
178         this._accumulatedDiff = cc.p(0, 0);
179     },
180 
181     /**
182      * @param {Number} time
183      */
184     update:function (time) {
185         var p, lt;
186         var ps = this._points;
187         // eg.
188         // p..p..p..p..p..p..p
189         // 1..2..3..4..5..6..7
190         // want p to be 1, 2, 3, 4, 5, 6
191         if (time == 1) {
192             p = ps.length - 1;
193             lt = 1;
194         } else {
195             var locDT = this._deltaT;
196             p = 0 | (time / locDT);
197             lt = (time - locDT * p) / locDT;
198         }
199 
200         var newPos = cc.CardinalSplineAt(
201             cc.getControlPointAt(ps, p - 1),
202             cc.getControlPointAt(ps, p - 0),
203             cc.getControlPointAt(ps, p + 1),
204             cc.getControlPointAt(ps, p + 2),
205             this._tension, lt);
206 
207         if (cc.ENABLE_STACKABLE_ACTIONS) {
208             var tempX, tempY;
209             tempX = this._target.getPositionX() - this._previousPosition.x;
210             tempY = this._target.getPositionY() - this._previousPosition.y;
211             if (tempX != 0 || tempY != 0) {
212                 var locAccDiff = this._accumulatedDiff;
213                 tempX = locAccDiff.x + tempX;
214                 tempY = locAccDiff.y + tempY;
215                 locAccDiff.x = tempX;
216                 locAccDiff.y = tempY;
217                 newPos.x += tempX;
218                 newPos.y += tempY;
219             }
220         }
221         this.updatePosition(newPos);
222     },
223 
224     /**
225      * reverse a new cc.CardinalSplineTo
226      * @return {cc.CardinalSplineTo}
227      */
228     reverse:function () {
229         var reversePoints = cc.reverseControlPoints(this._points);
230         return cc.CardinalSplineTo.create(this._duration, reversePoints, this._tension);
231     },
232 
233     /**
234      * update position of target
235      * @param {cc.Point} newPos
236      */
237     updatePosition:function (newPos) {
238         this._target.setPosition(newPos);
239         this._previousPosition = newPos;
240     },
241 
242     /**
243      * Points getter
244      * @return {Array}
245      */
246     getPoints:function () {
247         return this._points;
248     },
249 
250     /**
251      * Points setter
252      * @param {Array} points
253      */
254     setPoints:function (points) {
255         this._points = points;
256     }
257 });
258 
259 /**
260  * creates an action with a Cardinal Spline array of points and tension
261  * @function
262  * @param {Number} duration
263  * @param {Array} points array of control points
264  * @param {Number} tension
265  * @return {cc.CardinalSplineTo}
266  *
267  * @example
268  * //create a cc.CardinalSplineTo
269  * var action1 = cc.CardinalSplineTo.create(3, array, 0);
270  */
271 cc.CardinalSplineTo.create = function (duration, points, tension) {
272     var ret = new cc.CardinalSplineTo();
273     if (ret.initWithDuration(duration, points, tension)) {
274         return ret;
275     }
276     return null;
277 };
278 
279 /**
280  * Cardinal Spline path.  http://en.wikipedia.org/wiki/Cubic_Hermite_spline#Cardinal_spline
281  * @class
282  * @extends cc.CardinalSplineTo
283  *
284  * @example
285  * //create a cc.CardinalSplineBy
286  * var action1 = cc.CardinalSplineBy.create(3, array, 0);
287  */
288 cc.CardinalSplineBy = cc.CardinalSplineTo.extend(/** @lends cc.CardinalSplineBy# */{
289     _startPosition:null,
290 
291     /**
292      * Constructor
293      */
294     ctor:function () {
295         cc.CardinalSplineTo.prototype.ctor.call(this);
296         this._startPosition = cc.p(0, 0);
297     },
298 
299     /**
300      * @param {cc.Node} target
301      */
302     startWithTarget:function (target) {
303         cc.CardinalSplineTo.prototype.startWithTarget.call(this, target);
304         this._startPosition = target.getPosition();
305     },
306 
307     /**
308      * reverse a new cc.CardinalSplineBy
309      * @return {cc.CardinalSplineBy}
310      */
311     reverse:function () {
312         var copyConfig = this._points.slice();
313         var current;
314         //
315         // convert "absolutes" to "diffs"
316         //
317         var p = copyConfig[0];
318         for (var i = 1; i < copyConfig.length; ++i) {
319             current = copyConfig[i];
320             copyConfig[i] = cc.pSub(current, p);
321             p = current;
322         }
323 
324         // convert to "diffs" to "reverse absolute"
325         var reverseArray = cc.reverseControlPoints(copyConfig);
326 
327         // 1st element (which should be 0,0) should be here too
328         p = reverseArray[ reverseArray.length - 1 ];
329         reverseArray.pop();
330 
331         p.x = -p.x;
332         p.y = -p.y;
333 
334         reverseArray.unshift(p);
335         for (var i = 1; i < reverseArray.length; ++i) {
336             current = reverseArray[i];
337             current.x = -current.x;
338             current.y = -current.y;
339             current.x += p.x;
340             current.y += p.y;
341             reverseArray[i] = current;
342             p = current;
343         }
344         return cc.CardinalSplineBy.create(this._duration, reverseArray, this._tension);
345     },
346 
347     /**
348      * update position of target
349      * @param {cc.Point} newPos
350      */
351     updatePosition:function (newPos) {
352         var pos = this._startPosition;
353         var posX = newPos.x + pos.x;
354         var posY = newPos.y + pos.y;
355         this._target.setPosition(posX, posY);
356         this._previousPosition.x = posX;
357         this._previousPosition.y = posY;
358     },
359 
360     /**
361      * returns a new clone of the action
362      * @returns {cc.CardinalSplineBy}
363      */
364     clone:function () {
365         var a = new cc.CardinalSplineBy();
366         a.initWithDuration(this._duration, cc.copyControlPoints(this._points), this._tension);
367         return a;
368     }
369 });
370 
371 /**
372  * creates an action with a Cardinal Spline array of points and tension
373  * @function
374  * @param {Number} duration
375  * @param {Array} points
376  * @param {Number} tension
377  * @return {cc.CardinalSplineBy}
378  */
379 cc.CardinalSplineBy.create = function (duration, points, tension) {
380     var ret = new cc.CardinalSplineBy();
381     if (ret.initWithDuration(duration, points, tension))
382         return ret;
383     return null;
384 };
385 
386 /**
387  * <p>
388  *   An action that moves the target with a CatmullRom curve to a destination point.<br/>
389  *   A Catmull Rom is a Cardinal Spline with a tension of 0.5.  <br/>
390  *   http://en.wikipedia.org/wiki/Cubic_Hermite_spline#Catmull.E2.80.93Rom_spline
391  * </p>
392  * @class
393  * @extends cc.CardinalSplineTo
394  *
395  * @example
396  * var action1 = cc.CatmullRomTo.create(3, array);
397  */
398 cc.CatmullRomTo = cc.CardinalSplineTo.extend(/** @lends cc.CatmullRomTo# */{
399     /**
400      *  initializes the action with a duration and an array of points
401      */
402     initWithDuration:function (dt, points) {
403         return cc.CardinalSplineTo.prototype.initWithDuration.call(this, dt, points, 0.5);
404     },
405 
406     /**
407      * returns a new clone of the action
408      * @returns {cc.CatmullRomTo}
409      */
410     clone:function () {
411         var action = new cc.CatmullRomTo();
412         action.initWithDuration(this._duration, cc.copyControlPoints(this._points));
413         return action;
414     }
415 });
416 
417 /**
418  * creates an action with a Cardinal Spline array of points and tension
419  * @param {Number} dt
420  * @param {Array} points
421  * @return {cc.CatmullRomTo}
422  *
423  * @example
424  * var action1 = cc.CatmullRomTo.create(3, array);
425  */
426 cc.CatmullRomTo.create = function (dt, points) {
427     var ret = new cc.CatmullRomTo();
428     if (ret.initWithDuration(dt, points))
429         return ret;
430     return null;
431 };
432 
433 /**
434  * <p>
435  *   An action that moves the target with a CatmullRom curve by a certain distance.  <br/>
436  *   A Catmull Rom is a Cardinal Spline with a tension of 0.5.<br/>
437  *   http://en.wikipedia.org/wiki/Cubic_Hermite_spline#Catmull.E2.80.93Rom_spline
438  * </p>
439  * @class
440  * @extends cc.CardinalSplineBy
441  *
442  * @example
443  * var action1 = cc.CatmullRomBy.create(3, array);
444  */
445 cc.CatmullRomBy = cc.CardinalSplineBy.extend({
446     /** initializes the action with a duration and an array of points */
447     initWithDuration:function (dt, points) {
448         return cc.CardinalSplineTo.prototype.initWithDuration.call(this, dt, points, 0.5);
449     },
450 
451     /**
452      * returns a new clone of the action
453      * @returns {cc.CatmullRomBy}
454      */
455     clone:function () {
456         var action = new cc.CatmullRomBy();
457         action.initWithDuration(this._duration, cc.copyControlPoints(this._points));
458         return action;
459     }
460 });
461 
462 /**
463  * creates an action with a Cardinal Spline array of points and tension
464  *
465  * @example
466  * var action1 = cc.CatmullRomBy.create(3, array);
467  */
468 cc.CatmullRomBy.create = function (dt, points) {
469     var ret = new cc.CatmullRomBy();
470     if (ret.initWithDuration(dt, points))
471         return ret;
472     return null;
473 };
474