1 /****************************************************************************
  2  Copyright (c) 2008-2010 Ricardo Quesada
  3  Copyright (c) 2011-2012 cocos2d-x.org
  4  Copyright (c) 2013-2014 Chukong Technologies Inc.
  5  Copyright (c) 2008-2009 Jason Booth
  6 
  7  http://www.cocos2d-x.org
  8 
  9  Permission is hereby granted, free of charge, to any person obtaining a copy
 10  of this software and associated documentation files (the "Software"), to deal
 11  in the Software without restriction, including without limitation the rights
 12  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 13  copies of the Software, and to permit persons to whom the Software is
 14  furnished to do so, subject to the following conditions:
 15 
 16  The above copyright notice and this permission notice shall be included in
 17  all copies or substantial portions of the Software.
 18 
 19  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 20  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 21  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 22  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 23  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 24  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 25  THE SOFTWARE.
 26  ****************************************************************************/
 27 
 28 /**
 29  * cc.MotionStreak manages a Ribbon based on it's motion in absolute space.                 <br/>
 30  * You construct it with a fadeTime, minimum segment size, texture path, texture            <br/>
 31  * length and color. The fadeTime controls how long it takes each vertex in                 <br/>
 32  * the streak to fade out, the minimum segment size it how many pixels the                  <br/>
 33  * streak will move before adding a new ribbon segment, and the texture                     <br/>
 34  * length is the how many pixels the texture is stretched across. The texture               <br/>
 35  * is vertically aligned along the streak segment.
 36  * @class
 37  * @extends cc.Node
 38  *
 39  * @property {cc.Texture2D} texture                         - Texture used for the motion streak.
 40  * @property {Boolean}      fastMode                        - Indicate whether use fast mode.
 41  * @property {Boolean}      startingPositionInitialized     - Indicate whether starting position initialized.
 42  * @example
 43  * //example
 44  * new cc.MotionStreak(2, 3, 32, cc.color.GREEN, s_streak);
 45  */
 46 cc.MotionStreak = cc.Node.extend(/** @lends cc.MotionStreak# */{
 47     texture:null,
 48     fastMode:false,
 49     startingPositionInitialized:false,
 50 
 51     _blendFunc:null,
 52 
 53     _stroke:0,
 54     _fadeDelta:0,
 55     _minSeg:0,
 56 
 57     _maxPoints:0,
 58     _nuPoints:0,
 59     _previousNuPoints:0,
 60 
 61     /* Pointers */
 62     _pointVertexes:null,
 63     _pointState:null,
 64 
 65     // webgl
 66     _vertices:null,
 67     _colorPointer:null,
 68     _texCoords:null,
 69 
 70     _verticesBuffer:null,
 71     _colorPointerBuffer:null,
 72     _texCoordsBuffer:null,
 73     _className:"MotionStreak",
 74 
 75     /**
 76      * creates and initializes a motion streak with fade in seconds, minimum segments, stroke's width, color, texture filename or texture   <br/>
 77      * Constructor of cc.MotionStreak
 78      * @param {Number} fade time to fade
 79      * @param {Number} minSeg minimum segment size
 80      * @param {Number} stroke stroke's width
 81      * @param {Number} color
 82      * @param {string|cc.Texture2D} texture texture filename or texture
 83      */
 84     ctor: function (fade, minSeg, stroke, color, texture) {
 85         cc.Node.prototype.ctor.call(this);
 86         this._positionR = cc.p(0, 0);
 87         this._blendFunc = new cc.BlendFunc(cc.SRC_ALPHA, cc.ONE_MINUS_SRC_ALPHA);
 88 
 89         this.fastMode = false;
 90         this.startingPositionInitialized = false;
 91 
 92         this.texture = null;
 93 
 94         this._stroke = 0;
 95         this._fadeDelta = 0;
 96         this._minSeg = 0;
 97 
 98         this._maxPoints = 0;
 99         this._nuPoints = 0;
100         this._previousNuPoints = 0;
101 
102         /** Pointers */
103         this._pointVertexes = null;
104         this._pointState = null;
105 
106         // webgl
107         this._vertices = null;
108         this._colorPointer = null;
109         this._texCoords = null;
110 
111         this._verticesBuffer = null;
112         this._colorPointerBuffer = null;
113         this._texCoordsBuffer = null;
114 
115         if(texture !== undefined)
116             this.initWithFade(fade, minSeg, stroke, color, texture);
117     },
118 
119     /**
120      * Gets the texture.
121      * @return {cc.Texture2D}
122      */
123     getTexture:function () {
124         return this.texture;
125     },
126 
127     /**
128      * Set the texture.
129      * @param {cc.Texture2D} texture
130      */
131     setTexture:function (texture) {
132         if (this.texture !== texture)
133             this.texture = texture;
134     },
135 
136     /**
137      * Gets the blend func.
138      * @return {cc.BlendFunc}
139      */
140     getBlendFunc:function () {
141         return this._blendFunc;
142     },
143 
144     /**
145      * Set the blend func.
146      * @param {Number} src
147      * @param {Number} dst
148      */
149     setBlendFunc:function (src, dst) {
150         if (dst === undefined) {
151             this._blendFunc = src;
152         } else {
153             this._blendFunc.src = src;
154             this._blendFunc.dst = dst;
155         }
156     },
157 
158     /**
159      * Gets opacity.
160      * @warning cc.MotionStreak.getOpacity has not been supported.
161      * @returns {number}
162      */
163     getOpacity:function () {
164         cc.log("cc.MotionStreak.getOpacity has not been supported.");
165         return 0;
166     },
167 
168     /**
169      * Set opacity.
170      * @warning cc.MotionStreak.setOpacity has not been supported.
171      * @param opacity
172      */
173     setOpacity:function (opacity) {
174         cc.log("cc.MotionStreak.setOpacity has not been supported.");
175     },
176 
177     /**
178      * set opacity modify RGB.
179      * @warning cc.MotionStreak.setOpacityModifyRGB has not been supported.
180      * @param value
181      */
182     setOpacityModifyRGB:function (value) {
183     },
184 
185     /**
186      * Checking OpacityModifyRGB.
187      * @returns {boolean}
188      */
189     isOpacityModifyRGB:function () {
190         return false;
191     },
192 
193     /**
194      * Checking fast mode.
195      * @returns {boolean}
196      */
197     isFastMode:function () {
198         return this.fastMode;
199     },
200 
201     /**
202      * set fast mode
203      * @param {Boolean} fastMode
204      */
205     setFastMode:function (fastMode) {
206         this.fastMode = fastMode;
207     },
208 
209     /**
210      * Checking starting position initialized.
211      * @returns {boolean}
212      */
213     isStartingPositionInitialized:function () {
214         return this.startingPositionInitialized;
215     },
216 
217     /**
218      * Set Starting Position Initialized.
219      * @param {Boolean} startingPositionInitialized
220      */
221     setStartingPositionInitialized:function (startingPositionInitialized) {
222         this.startingPositionInitialized = startingPositionInitialized;
223     },
224 
225     /**
226      * Get stroke.
227      * @returns {Number} stroke
228      */
229     getStroke:function () {
230         return this._stroke;
231     },
232 
233     /**
234      * Set stroke.
235      * @param {Number} stroke
236      */
237     setStroke:function (stroke) {
238         this._stroke = stroke;
239     },
240 
241     /**
242      * initializes a motion streak with fade in seconds, minimum segments, stroke's width, color and texture filename or texture
243      * @param {Number} fade time to fade
244      * @param {Number} minSeg minimum segment size
245      * @param {Number} stroke stroke's width
246      * @param {Number} color
247      * @param {string|cc.Texture2D} texture texture filename or texture
248      * @return {Boolean}
249      */
250     initWithFade:function (fade, minSeg, stroke, color, texture) {
251         if(!texture)
252             throw "cc.MotionStreak.initWithFade(): Invalid filename or texture";
253 
254         if (cc.isString(texture))
255             texture = cc.textureCache.addImage(texture);
256 
257         cc.Node.prototype.setPosition.call(this, cc.p(0,0));
258         this.anchorX = 0;
259         this.anchorY = 0;
260         this.ignoreAnchor = true;
261         this.startingPositionInitialized = false;
262 
263         this.fastMode = true;
264         this._minSeg = (minSeg === -1.0) ? (stroke / 5.0) : minSeg;
265         this._minSeg *= this._minSeg;
266 
267         this._stroke = stroke;
268         this._fadeDelta = 1.0 / fade;
269 
270         var locMaxPoints = (0 | (fade * 60)) + 2;
271         this._maxPoints = locMaxPoints;
272         this._nuPoints = 0;
273         this._pointState = new Float32Array(locMaxPoints);
274         this._pointVertexes = new Float32Array(locMaxPoints * 2);
275 
276         this._vertices = new Float32Array(locMaxPoints * 4);
277         this._texCoords = new Float32Array(locMaxPoints * 4);
278         this._colorPointer = new Uint8Array(locMaxPoints * 8);
279 
280         this._verticesBuffer = gl.createBuffer();
281         this._texCoordsBuffer = gl.createBuffer();
282         this._colorPointerBuffer = gl.createBuffer();
283 
284         // Set blend mode
285         this._blendFunc.src = gl.SRC_ALPHA;
286         this._blendFunc.dst = gl.ONE_MINUS_SRC_ALPHA;
287 
288         this.texture = texture;
289         this.color = color;
290         this.scheduleUpdate();
291 
292         //bind buffer
293         gl.bindBuffer(gl.ARRAY_BUFFER, this._verticesBuffer);
294         gl.bufferData(gl.ARRAY_BUFFER, this._vertices, gl.DYNAMIC_DRAW);
295         gl.bindBuffer(gl.ARRAY_BUFFER, this._texCoordsBuffer);
296         gl.bufferData(gl.ARRAY_BUFFER, this._texCoords, gl.DYNAMIC_DRAW);
297         gl.bindBuffer(gl.ARRAY_BUFFER, this._colorPointerBuffer);
298         gl.bufferData(gl.ARRAY_BUFFER, this._colorPointer, gl.DYNAMIC_DRAW);
299 
300         return true;
301     },
302 
303     /**
304      * color used for the tint
305      * @param {cc.Color} color
306      */
307     tintWithColor:function (color) {
308         this.color = color;
309 
310         // Fast assignation
311         var locColorPointer = this._colorPointer;
312         for (var i = 0, len = this._nuPoints * 2; i < len; i++) {
313             locColorPointer[i * 4] = color.r;
314             locColorPointer[i * 4 + 1] = color.g;
315             locColorPointer[i * 4 + 2] = color.b;
316         }
317     },
318 
319     /**
320      * Remove all living segments of the ribbon
321      */
322     reset:function () {
323         this._nuPoints = 0;
324     },
325 
326     /**
327      * Set the position. <br />
328      *
329      * @param {cc.Point|Number} position
330      * @param {Number} [yValue=undefined] If not exists, the first parameter must be cc.Point.
331      */
332     setPosition:function (position, yValue) {
333         this.startingPositionInitialized = true;
334         if(yValue === undefined){
335             this._positionR.x = position.x;
336             this._positionR.y = position.y;
337         } else {
338             this._positionR.x = position;
339             this._positionR.y = yValue;
340         }
341     },
342 
343     /**
344      * Gets the position.x
345      * @return {Number}
346      */
347     getPositionX:function () {
348         return this._positionR.x;
349     },
350 
351     /**
352      * Set the position.x
353      * @param {Number} x
354      */
355     setPositionX:function (x) {
356         this._positionR.x = x;
357         if(!this.startingPositionInitialized)
358             this.startingPositionInitialized = true;
359     },
360 
361     /**
362      * Gets the position.y
363      * @return {Number}
364      */
365     getPositionY:function () {
366         return  this._positionR.y;
367     },
368 
369     /**
370      * Set the position.y
371      * @param {Number} y
372      */
373     setPositionY:function (y) {
374         this._positionR.y = y;
375         if(!this.startingPositionInitialized)
376             this.startingPositionInitialized = true;
377     },
378 
379     /**
380      * <p>schedules the "update" method.                                                                           <br/>
381      * It will use the order number 0. This method will be called every frame.                                  <br/>
382      * Scheduled methods with a lower order value will be called before the ones that have a higher order value.<br/>
383      * Only one "update" method could be scheduled per node.</p>
384      * @param {Number} delta
385      */
386     update:function (delta) {
387         if (!this.startingPositionInitialized)
388             return;
389 
390         //TODO update the color    (need move to render cmd)
391         this._renderCmd._updateDisplayColor();
392 
393         delta *= this._fadeDelta;
394 
395         var newIdx, newIdx2, i, i2;
396         var mov = 0;
397 
398         // Update current points
399         var locNuPoints = this._nuPoints;
400         var locPointState = this._pointState, locPointVertexes = this._pointVertexes, locVertices = this._vertices;
401         var locColorPointer = this._colorPointer;
402 
403         for (i = 0; i < locNuPoints; i++) {
404             locPointState[i] -= delta;
405 
406             if (locPointState[i] <= 0)
407                 mov++;
408             else {
409                 newIdx = i - mov;
410                 if (mov > 0) {
411                     // Move data
412                     locPointState[newIdx] = locPointState[i];
413                     // Move point
414                     locPointVertexes[newIdx * 2] = locPointVertexes[i * 2];
415                     locPointVertexes[newIdx * 2 + 1] = locPointVertexes[i * 2 + 1];
416 
417                     // Move vertices
418                     i2 = i * 2;
419                     newIdx2 = newIdx * 2;
420                     locVertices[newIdx2 * 2] = locVertices[i2 * 2];
421                     locVertices[newIdx2 * 2 + 1] = locVertices[i2 * 2 + 1];
422                     locVertices[(newIdx2 + 1) * 2] = locVertices[(i2 + 1) * 2];
423                     locVertices[(newIdx2 + 1) * 2 + 1] = locVertices[(i2 + 1) * 2 + 1];
424 
425                     // Move color
426                     i2 *= 4;
427                     newIdx2 *= 4;
428                     locColorPointer[newIdx2 + 0] = locColorPointer[i2 + 0];
429                     locColorPointer[newIdx2 + 1] = locColorPointer[i2 + 1];
430                     locColorPointer[newIdx2 + 2] = locColorPointer[i2 + 2];
431                     locColorPointer[newIdx2 + 4] = locColorPointer[i2 + 4];
432                     locColorPointer[newIdx2 + 5] = locColorPointer[i2 + 5];
433                     locColorPointer[newIdx2 + 6] = locColorPointer[i2 + 6];
434                 } else
435                     newIdx2 = newIdx * 8;
436 
437                 var op = locPointState[newIdx] * 255.0;
438                 locColorPointer[newIdx2 + 3] = op;
439                 locColorPointer[newIdx2 + 7] = op;
440             }
441         }
442         locNuPoints -= mov;
443 
444         // Append new point
445         var appendNewPoint = true;
446         if (locNuPoints >= this._maxPoints)
447             appendNewPoint = false;
448         else if (locNuPoints > 0) {
449             var a1 = cc.pDistanceSQ(cc.p(locPointVertexes[(locNuPoints - 1) * 2], locPointVertexes[(locNuPoints - 1) * 2 + 1]),
450                 this._positionR) < this._minSeg;
451             var a2 = (locNuPoints === 1) ? false : (cc.pDistanceSQ(
452                 cc.p(locPointVertexes[(locNuPoints - 2) * 2], locPointVertexes[(locNuPoints - 2) * 2 + 1]), this._positionR) < (this._minSeg * 2.0));
453             if (a1 || a2)
454                 appendNewPoint = false;
455         }
456 
457         if (appendNewPoint) {
458             locPointVertexes[locNuPoints * 2] = this._positionR.x;
459             locPointVertexes[locNuPoints * 2 + 1] = this._positionR.y;
460             locPointState[locNuPoints] = 1.0;
461 
462             // Color assignment
463             var offset = locNuPoints * 8;
464 
465             var locDisplayedColor = this.getDisplayedColor();
466             locColorPointer[offset] = locDisplayedColor.r;
467             locColorPointer[offset + 1] = locDisplayedColor.g;
468             locColorPointer[offset + 2] = locDisplayedColor.b;
469             //*((ccColor3B*)(m_pColorPointer + offset+4)) = this._color;
470             locColorPointer[offset + 4] = locDisplayedColor.r;
471             locColorPointer[offset + 5] = locDisplayedColor.g;
472             locColorPointer[offset + 6] = locDisplayedColor.b;
473 
474             // Opacity
475             locColorPointer[offset + 3] = 255;
476             locColorPointer[offset + 7] = 255;
477 
478             // Generate polygon
479             if (locNuPoints > 0 && this.fastMode) {
480                 if (locNuPoints > 1)
481                     cc.vertexLineToPolygon(locPointVertexes, this._stroke, this._vertices, locNuPoints, 1);
482                 else
483                     cc.vertexLineToPolygon(locPointVertexes, this._stroke, this._vertices, 0, 2);
484             }
485             locNuPoints++;
486         }
487 
488         if (!this.fastMode)
489             cc.vertexLineToPolygon(locPointVertexes, this._stroke, this._vertices, 0, locNuPoints);
490 
491         // Updated Tex Coords only if they are different than previous step
492         if (locNuPoints && this._previousNuPoints !== locNuPoints) {
493             var texDelta = 1.0 / locNuPoints;
494             var locTexCoords = this._texCoords;
495             for (i = 0; i < locNuPoints; i++) {
496                 locTexCoords[i * 4] = 0;
497                 locTexCoords[i * 4 + 1] = texDelta * i;
498 
499                 locTexCoords[(i * 2 + 1) * 2] = 1;
500                 locTexCoords[(i * 2 + 1) * 2 + 1] = texDelta * i;
501             }
502 
503             this._previousNuPoints = locNuPoints;
504         }
505 
506         this._nuPoints = locNuPoints;
507     },
508 
509     _createRenderCmd: function(){
510         if(cc._renderType === cc._RENDER_TYPE_WEBGL)
511             return new cc.MotionStreak.WebGLRenderCmd(this);
512         else
513             return null;  //MotionStreak doesn't support Canvas mode
514     }
515 });
516 
517 /**
518  * Please use new cc.MotionStreak instead. <br />
519  * Creates and initializes a motion streak with fade in seconds, minimum segments, stroke's width, color, texture filename or texture
520  * @deprecated since v3.0 please use new cc.MotionStreak instead.
521  * @param {Number} fade time to fade
522  * @param {Number} minSeg minimum segment size
523  * @param {Number} stroke stroke's width
524  * @param {Number} color
525  * @param {string|cc.Texture2D} texture texture filename or texture
526  * @return {cc.MotionStreak}
527  * @example
528  * //example
529  * new cc.MotionStreak(2, 3, 32, cc.color.GREEN, s_streak);
530  */
531 cc.MotionStreak.create = function (fade, minSeg, stroke, color, texture) {
532     return new cc.MotionStreak(fade, minSeg, stroke, color, texture);
533 };
534