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         var locPosition = this._target.getPosition();
179         this._previousPosition = cc.p(locPosition.x, locPosition.y);
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         var locPosition = target.getPosition();
307         this._startPosition.x = locPosition.x;
308         this._startPosition.y = locPosition.y;
309     },
310 
311     /**
312      * reverse a new cc.CardinalSplineBy
313      * @return {cc.CardinalSplineBy}
314      */
315     reverse:function () {
316         var copyConfig = this._points.slice();
317         var current;
318         //
319         // convert "absolutes" to "diffs"
320         //
321         var p = copyConfig[0];
322         for (var i = 1; i < copyConfig.length; ++i) {
323             current = copyConfig[i];
324             copyConfig[i] = cc.pSub(current, p);
325             p = current;
326         }
327 
328         // convert to "diffs" to "reverse absolute"
329         var reverseArray = cc.reverseControlPoints(copyConfig);
330 
331         // 1st element (which should be 0,0) should be here too
332         p = reverseArray[ reverseArray.length - 1 ];
333         reverseArray.pop();
334 
335         p.x = -p.x;
336         p.y = -p.y;
337 
338         reverseArray.unshift(p);
339         for (var i = 1; i < reverseArray.length; ++i) {
340             current = reverseArray[i];
341             current.x = -current.x;
342             current.y = -current.y;
343             current.x += p.x;
344             current.y += p.y;
345             reverseArray[i] = current;
346             p = current;
347         }
348         return cc.CardinalSplineBy.create(this._duration, reverseArray, this._tension);
349     },
350 
351     /**
352      * update position of target
353      * @param {cc.Point} newPos
354      */
355     updatePosition:function (newPos) {
356         var pos = this._startPosition;
357         var posX = newPos.x + pos.x;
358         var posY = newPos.y + pos.y;
359         this._target.setPosition(posX, posY);
360         this._previousPosition.x = posX;
361         this._previousPosition.y = posY;
362     },
363 
364     /**
365      * returns a new clone of the action
366      * @returns {cc.CardinalSplineBy}
367      */
368     clone:function () {
369         var a = new cc.CardinalSplineBy();
370         a.initWithDuration(this._duration, cc.copyControlPoints(this._points), this._tension);
371         return a;
372     }
373 });
374 
375 /**
376  * creates an action with a Cardinal Spline array of points and tension
377  * @function
378  * @param {Number} duration
379  * @param {Array} points
380  * @param {Number} tension
381  * @return {cc.CardinalSplineBy}
382  */
383 cc.CardinalSplineBy.create = function (duration, points, tension) {
384     var ret = new cc.CardinalSplineBy();
385     if (ret.initWithDuration(duration, points, tension))
386         return ret;
387     return null;
388 };
389 
390 /**
391  * <p>
392  *   An action that moves the target with a CatmullRom curve to a destination point.<br/>
393  *   A Catmull Rom is a Cardinal Spline with a tension of 0.5.  <br/>
394  *   http://en.wikipedia.org/wiki/Cubic_Hermite_spline#Catmull.E2.80.93Rom_spline
395  * </p>
396  * @class
397  * @extends cc.CardinalSplineTo
398  *
399  * @example
400  * var action1 = cc.CatmullRomTo.create(3, array);
401  */
402 cc.CatmullRomTo = cc.CardinalSplineTo.extend(/** @lends cc.CatmullRomTo# */{
403     /**
404      *  initializes the action with a duration and an array of points
405      */
406     initWithDuration:function (dt, points) {
407         return cc.CardinalSplineTo.prototype.initWithDuration.call(this, dt, points, 0.5);
408     },
409 
410     /**
411      * returns a new clone of the action
412      * @returns {cc.CatmullRomTo}
413      */
414     clone:function () {
415         var action = new cc.CatmullRomTo();
416         action.initWithDuration(this._duration, cc.copyControlPoints(this._points));
417         return action;
418     }
419 });
420 
421 /**
422  * creates an action with a Cardinal Spline array of points and tension
423  * @param {Number} dt
424  * @param {Array} points
425  * @return {cc.CatmullRomTo}
426  *
427  * @example
428  * var action1 = cc.CatmullRomTo.create(3, array);
429  */
430 cc.CatmullRomTo.create = function (dt, points) {
431     var ret = new cc.CatmullRomTo();
432     if (ret.initWithDuration(dt, points))
433         return ret;
434     return null;
435 };
436 
437 /**
438  * <p>
439  *   An action that moves the target with a CatmullRom curve by a certain distance.  <br/>
440  *   A Catmull Rom is a Cardinal Spline with a tension of 0.5.<br/>
441  *   http://en.wikipedia.org/wiki/Cubic_Hermite_spline#Catmull.E2.80.93Rom_spline
442  * </p>
443  * @class
444  * @extends cc.CardinalSplineBy
445  *
446  * @example
447  * var action1 = cc.CatmullRomBy.create(3, array);
448  */
449 cc.CatmullRomBy = cc.CardinalSplineBy.extend({
450     /** initializes the action with a duration and an array of points */
451     initWithDuration:function (dt, points) {
452         return cc.CardinalSplineTo.prototype.initWithDuration.call(this, dt, points, 0.5);
453     },
454 
455     /**
456      * returns a new clone of the action
457      * @returns {cc.CatmullRomBy}
458      */
459     clone:function () {
460         var action = new cc.CatmullRomBy();
461         action.initWithDuration(this._duration, cc.copyControlPoints(this._points));
462         return action;
463     }
464 });
465 
466 /**
467  * creates an action with a Cardinal Spline array of points and tension
468  *
469  * @example
470  * var action1 = cc.CatmullRomBy.create(3, array);
471  */
472 cc.CatmullRomBy.create = function (dt, points) {
473     var ret = new cc.CatmullRomBy();
474     if (ret.initWithDuration(dt, points))
475         return ret;
476     return null;
477 };
478