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         this._vertexWebGLBuffer = cc._renderContext.createBuffer();
 89 
 90         this.fastMode = false;
 91         this.startingPositionInitialized = false;
 92 
 93         this.texture = null;
 94 
 95         this._stroke = 0;
 96         this._fadeDelta = 0;
 97         this._minSeg = 0;
 98 
 99         this._maxPoints = 0;
100         this._nuPoints = 0;
101         this._previousNuPoints = 0;
102 
103         /** Pointers */
104         this._pointVertexes = null;
105         this._pointState = null;
106 
107         // webgl
108         this._vertices = null;
109         this._colorPointer = null;
110         this._texCoords = null;
111 
112         this._verticesBuffer = null;
113         this._colorPointerBuffer = null;
114         this._texCoordsBuffer = null;
115 
116         if(texture !== undefined)
117             this.initWithFade(fade, minSeg, stroke, color, texture);
118     },
119 
120     /**
121      * Gets the texture.
122      * @return {cc.Texture2D}
123      */
124     getTexture:function () {
125         return this.texture;
126     },
127 
128     /**
129      * Set the texture.
130      * @param {cc.Texture2D} texture
131      */
132     setTexture:function (texture) {
133         if (this.texture != texture)
134             this.texture = texture;
135     },
136 
137     /**
138      * Gets the blend func.
139      * @return {cc.BlendFunc}
140      */
141     getBlendFunc:function () {
142         return this._blendFunc;
143     },
144 
145     /**
146      * Set the blend func.
147      * @param {Number} src
148      * @param {Number} dst
149      */
150     setBlendFunc:function (src, dst) {
151         if (dst === undefined) {
152             this._blendFunc = src;
153         } else {
154             this._blendFunc.src = src;
155             this._blendFunc.dst = dst;
156         }
157     },
158 
159     /**
160      * Gets opacity.
161      * @warning cc.MotionStreak.getOpacity has not been supported.
162      * @returns {number}
163      */
164     getOpacity:function () {
165         cc.log("cc.MotionStreak.getOpacity has not been supported.");
166         return 0;
167     },
168 
169     /**
170      * Set opacity.
171      * @warning cc.MotionStreak.setOpacity has not been supported.
172      * @param opacity
173      */
174     setOpacity:function (opacity) {
175         cc.log("cc.MotionStreak.setOpacity has not been supported.");
176     },
177 
178     /**
179      * set opacity modify RGB.
180      * @warning cc.MotionStreak.setOpacityModifyRGB has not been supported.
181      * @param value
182      */
183     setOpacityModifyRGB:function (value) {
184     },
185 
186     /**
187      * Checking OpacityModifyRGB.
188      * @returns {boolean}
189      */
190     isOpacityModifyRGB:function () {
191         return false;
192     },
193 
194     /**
195      * <p>
196      * callback that is called every time the node leaves the 'stage'.                                         <br/>
197      * If the node leaves the 'stage' with a transition, this callback is called when the transition finishes. <br/>
198      * During onExit you can't access a sibling node.                                                             <br/>
199      * If you override onExit, you shall call its parent's onExit with this._super().
200      * </p>
201      * @function
202      */
203     onExit:function(){
204         cc.Node.prototype.onExit.call(this);
205         if(this._verticesBuffer)
206             cc._renderContext.deleteBuffer(this._verticesBuffer);
207         if(this._texCoordsBuffer)
208             cc._renderContext.deleteBuffer(this._texCoordsBuffer);
209         if(this._colorPointerBuffer)
210             cc._renderContext.deleteBuffer(this._colorPointerBuffer);
211     },
212 
213     /**
214      * Checking fast mode.
215      * @returns {boolean}
216      */
217     isFastMode:function () {
218         return this.fastMode;
219     },
220 
221     /**
222      * set fast mode
223      * @param {Boolean} fastMode
224      */
225     setFastMode:function (fastMode) {
226         this.fastMode = fastMode;
227     },
228 
229     /**
230      * Checking starting position initialized.
231      * @returns {boolean}
232      */
233     isStartingPositionInitialized:function () {
234         return this.startingPositionInitialized;
235     },
236 
237     /**
238      * Set Starting Position Initialized.
239      * @param {Boolean} startingPositionInitialized
240      */
241     setStartingPositionInitialized:function (startingPositionInitialized) {
242         this.startingPositionInitialized = startingPositionInitialized;
243     },
244 
245     /**
246      * initializes a motion streak with fade in seconds, minimum segments, stroke's width, color and texture filename or texture
247      * @param {Number} fade time to fade
248      * @param {Number} minSeg minimum segment size
249      * @param {Number} stroke stroke's width
250      * @param {Number} color
251      * @param {string|cc.Texture2D} texture texture filename or texture
252      * @return {Boolean}
253      */
254     initWithFade:function (fade, minSeg, stroke, color, texture) {
255         if(!texture)
256             throw "cc.MotionStreak.initWithFade(): Invalid filename or texture";
257 
258         if (typeof(texture) === "string")
259             texture = cc.textureCache.addImage(texture);
260 
261         cc.Node.prototype.setPosition.call(this, cc.p(0,0));
262         this.anchorX = 0;
263         this.anchorY = 0;
264         this.ignoreAnchor = true;
265         this.startingPositionInitialized = false;
266 
267         this.fastMode = true;
268         this._minSeg = (minSeg == -1.0) ? (stroke / 5.0) : minSeg;
269         this._minSeg *= this._minSeg;
270 
271         this._stroke = stroke;
272         this._fadeDelta = 1.0 / fade;
273 
274         var locMaxPoints = (0 | (fade * 60)) + 2;
275         this._nuPoints = 0;
276         this._pointState = new Float32Array(locMaxPoints);
277         this._pointVertexes = new Float32Array(locMaxPoints * 2);
278 
279         this._vertices = new Float32Array(locMaxPoints * 4);
280         this._texCoords = new Float32Array(locMaxPoints * 4);
281         this._colorPointer = new Uint8Array(locMaxPoints * 8);
282         this._maxPoints = locMaxPoints;
283 
284         var gl = cc._renderContext;
285 
286         this._verticesBuffer = gl.createBuffer();
287         this._texCoordsBuffer = gl.createBuffer();
288         this._colorPointerBuffer = gl.createBuffer();
289 
290         // Set blend mode
291         this._blendFunc.src = gl.SRC_ALPHA;
292         this._blendFunc.dst = gl.ONE_MINUS_SRC_ALPHA;
293 
294         // shader program
295         this.shaderProgram = cc.shaderCache.programForKey(cc.SHADER_POSITION_TEXTURECOLOR);
296 
297         this.texture = texture;
298         this.color = color;
299         this.scheduleUpdate();
300 
301         //bind buffer
302         gl.bindBuffer(gl.ARRAY_BUFFER, this._verticesBuffer);
303         gl.bufferData(gl.ARRAY_BUFFER, this._vertices, gl.DYNAMIC_DRAW);
304         gl.bindBuffer(gl.ARRAY_BUFFER, this._texCoordsBuffer);
305         gl.bufferData(gl.ARRAY_BUFFER, this._texCoords, gl.DYNAMIC_DRAW);
306         gl.bindBuffer(gl.ARRAY_BUFFER, this._colorPointerBuffer);
307         gl.bufferData(gl.ARRAY_BUFFER, this._colorPointer, gl.DYNAMIC_DRAW);
308 
309         return true;
310     },
311 
312     /**
313      * color used for the tint
314      * @param {cc.Color} color
315      */
316     tintWithColor:function (color) {
317         this.color = color;
318 
319         // Fast assignation
320         var locColorPointer = this._colorPointer;
321         for (var i = 0, len = this._nuPoints * 2; i < len; i++) {
322             locColorPointer[i * 4] = color.r;
323             locColorPointer[i * 4 + 1] = color.g;
324             locColorPointer[i * 4 + 2] = color.b;
325         }
326     },
327 
328     /**
329      * Remove all living segments of the ribbon
330      */
331     reset:function () {
332         this._nuPoints = 0;
333     },
334 
335     /**
336      * Set the position. <br />
337      *
338      * @param {cc.Point|Number} position
339      * @param {Number} [yValue=undefined] If not exists, the first parameter must be cc.Point.
340      */
341     setPosition:function (position, yValue) {
342         this.startingPositionInitialized = true;
343         if(yValue === undefined){
344             this._positionR.x = position.x;
345             this._positionR.y = position.y;
346         } else {
347             this._positionR.x = position;
348             this._positionR.y = yValue;
349         }
350     },
351 
352     /**
353      * Gets the position.x
354      * @return {Number}
355      */
356     getPositionX:function () {
357         return this._positionR.x;
358     },
359 
360     /**
361      * Set the position.x
362      * @param {Number} x
363      */
364     setPositionX:function (x) {
365         this._positionR.x = x;
366         if(!this.startingPositionInitialized)
367             this.startingPositionInitialized = true;
368     },
369 
370     /**
371      * Gets the position.y
372      * @return {Number}
373      */
374     getPositionY:function () {
375         return  this._positionR.y;
376     },
377 
378     /**
379      * Set the position.y
380      * @param {Number} y
381      */
382     setPositionY:function (y) {
383         this._positionR.y = y;
384         if(!this.startingPositionInitialized)
385             this.startingPositionInitialized = true;
386     },
387 
388     /**
389      * Render function using the canvas 2d context or WebGL context, internal usage only, please do not call this function
390      * @function
391      * @param {CanvasRenderingContext2D | WebGLRenderingContext} ctx The render context
392      */
393     draw:function (ctx) {
394         if (this._nuPoints <= 1)
395             return;
396 
397         if(this.texture && this.texture.isLoaded()){
398             ctx = ctx || cc._renderContext;
399             cc.nodeDrawSetup(this);
400             cc.glEnableVertexAttribs(cc.VERTEX_ATTRIB_FLAG_POS_COLOR_TEX);
401             cc.glBlendFunc(this._blendFunc.src, this._blendFunc.dst);
402 
403             cc.glBindTexture2D(this.texture);
404 
405             //position
406             ctx.bindBuffer(ctx.ARRAY_BUFFER, this._verticesBuffer);
407             ctx.bufferData(ctx.ARRAY_BUFFER, this._vertices, ctx.DYNAMIC_DRAW);
408             ctx.vertexAttribPointer(cc.VERTEX_ATTRIB_POSITION, 2, ctx.FLOAT, false, 0, 0);
409 
410             //texcoords
411             ctx.bindBuffer(ctx.ARRAY_BUFFER, this._texCoordsBuffer);
412             ctx.bufferData(ctx.ARRAY_BUFFER, this._texCoords, ctx.DYNAMIC_DRAW);
413             ctx.vertexAttribPointer(cc.VERTEX_ATTRIB_TEX_COORDS, 2, ctx.FLOAT, false, 0, 0);
414 
415             //colors
416             ctx.bindBuffer(ctx.ARRAY_BUFFER, this._colorPointerBuffer);
417             ctx.bufferData(ctx.ARRAY_BUFFER, this._colorPointer, ctx.DYNAMIC_DRAW);
418             ctx.vertexAttribPointer(cc.VERTEX_ATTRIB_COLOR, 4, ctx.UNSIGNED_BYTE, true, 0, 0);
419 
420             ctx.drawArrays(ctx.TRIANGLE_STRIP, 0, this._nuPoints * 2);
421             cc.g_NumberOfDraws ++;
422         }
423     },
424 
425     /**
426      * <p>schedules the "update" method.                                                                           <br/>
427      * It will use the order number 0. This method will be called every frame.                                  <br/>
428      * Scheduled methods with a lower order value will be called before the ones that have a higher order value.<br/>
429      * Only one "update" method could be scheduled per node.</p>
430      * @param {Number} delta
431      */
432     update:function (delta) {
433         if (!this.startingPositionInitialized)
434             return;
435 
436         delta *= this._fadeDelta;
437 
438         var newIdx, newIdx2, i, i2;
439         var mov = 0;
440 
441         // Update current points
442         var locNuPoints = this._nuPoints;
443         var locPointState = this._pointState, locPointVertexes = this._pointVertexes, locVertices = this._vertices;
444         var locColorPointer = this._colorPointer;
445 
446         for (i = 0; i < locNuPoints; i++) {
447             locPointState[i] -= delta;
448 
449             if (locPointState[i] <= 0)
450                 mov++;
451             else {
452                 newIdx = i - mov;
453                 if (mov > 0) {
454                     // Move data
455                     locPointState[newIdx] = locPointState[i];
456                     // Move point
457                     locPointVertexes[newIdx * 2] = locPointVertexes[i * 2];
458                     locPointVertexes[newIdx * 2 + 1] = locPointVertexes[i * 2 + 1];
459 
460                     // Move vertices
461                     i2 = i * 2;
462                     newIdx2 = newIdx * 2;
463                     locVertices[newIdx2 * 2] = locVertices[i2 * 2];
464                     locVertices[newIdx2 * 2 + 1] = locVertices[i2 * 2 + 1];
465                     locVertices[(newIdx2 + 1) * 2] = locVertices[(i2 + 1) * 2];
466                     locVertices[(newIdx2 + 1) * 2 + 1] = locVertices[(i2 + 1) * 2 + 1];
467 
468                     // Move color
469                     i2 *= 4;
470                     newIdx2 *= 4;
471                     locColorPointer[newIdx2 + 0] = locColorPointer[i2 + 0];
472                     locColorPointer[newIdx2 + 1] = locColorPointer[i2 + 1];
473                     locColorPointer[newIdx2 + 2] = locColorPointer[i2 + 2];
474                     locColorPointer[newIdx2 + 4] = locColorPointer[i2 + 4];
475                     locColorPointer[newIdx2 + 5] = locColorPointer[i2 + 5];
476                     locColorPointer[newIdx2 + 6] = locColorPointer[i2 + 6];
477                 } else
478                     newIdx2 = newIdx * 8;
479 
480                 var op = locPointState[newIdx] * 255.0;
481                 locColorPointer[newIdx2 + 3] = op;
482                 locColorPointer[newIdx2 + 7] = op;
483             }
484         }
485         locNuPoints -= mov;
486 
487         // Append new point
488         var appendNewPoint = true;
489         if (locNuPoints >= this._maxPoints)
490             appendNewPoint = false;
491         else if (locNuPoints > 0) {
492             var a1 = cc.pDistanceSQ(cc.p(locPointVertexes[(locNuPoints - 1) * 2], locPointVertexes[(locNuPoints - 1) * 2 + 1]),
493                 this._positionR) < this._minSeg;
494             var a2 = (locNuPoints == 1) ? false : (cc.pDistanceSQ(
495                 cc.p(locPointVertexes[(locNuPoints - 2) * 2], locPointVertexes[(locNuPoints - 2) * 2 + 1]), this._positionR) < (this._minSeg * 2.0));
496             if (a1 || a2)
497                 appendNewPoint = false;
498         }
499 
500         if (appendNewPoint) {
501             locPointVertexes[locNuPoints * 2] = this._positionR.x;
502             locPointVertexes[locNuPoints * 2 + 1] = this._positionR.y;
503             locPointState[locNuPoints] = 1.0;
504 
505             // Color assignment
506             var offset = locNuPoints * 8;
507 
508             var locDisplayedColor = this._displayedColor;
509             locColorPointer[offset] = locDisplayedColor.r;
510             locColorPointer[offset + 1] = locDisplayedColor.g;
511             locColorPointer[offset + 2] = locDisplayedColor.b;
512             //*((ccColor3B*)(m_pColorPointer + offset+4)) = this._color;
513             locColorPointer[offset + 4] = locDisplayedColor.r;
514             locColorPointer[offset + 5] = locDisplayedColor.g;
515             locColorPointer[offset + 6] = locDisplayedColor.b;
516 
517             // Opacity
518             locColorPointer[offset + 3] = 255;
519             locColorPointer[offset + 7] = 255;
520 
521             // Generate polygon
522             if (locNuPoints > 0 && this.fastMode) {
523                 if (locNuPoints > 1)
524                     cc.vertexLineToPolygon(locPointVertexes, this._stroke, this._vertices, locNuPoints, 1);
525                 else
526                     cc.vertexLineToPolygon(locPointVertexes, this._stroke, this._vertices, 0, 2);
527             }
528             locNuPoints++;
529         }
530 
531         if (!this.fastMode)
532             cc.vertexLineToPolygon(locPointVertexes, this._stroke, this._vertices, 0, locNuPoints);
533 
534         // Updated Tex Coords only if they are different than previous step
535         if (locNuPoints && this._previousNuPoints != locNuPoints) {
536             var texDelta = 1.0 / locNuPoints;
537             var locTexCoords = this._texCoords;
538             for (i = 0; i < locNuPoints; i++) {
539                 locTexCoords[i * 4] = 0;
540                 locTexCoords[i * 4 + 1] = texDelta * i;
541 
542                 locTexCoords[(i * 2 + 1) * 2] = 1;
543                 locTexCoords[(i * 2 + 1) * 2 + 1] = texDelta * i;
544             }
545 
546             this._previousNuPoints = locNuPoints;
547         }
548 
549         this._nuPoints = locNuPoints;
550     }
551 });
552 
553 /**
554  * Please use new cc.MotionStreak instead. <br />
555  * Creates and initializes a motion streak with fade in seconds, minimum segments, stroke's width, color, texture filename or texture
556  * @deprecated since v3.0 please use new cc.MotionStreak instead.
557  * @param {Number} fade time to fade
558  * @param {Number} minSeg minimum segment size
559  * @param {Number} stroke stroke's width
560  * @param {Number} color
561  * @param {string|cc.Texture2D} texture texture filename or texture
562  * @return {cc.MotionStreak}
563  * @example
564  * //example
565  * new cc.MotionStreak(2, 3, 32, cc.color.GREEN, s_streak);
566  */
567 cc.MotionStreak.create = function (fade, minSeg, stroke, color, texture) {
568     return new cc.MotionStreak(fade, minSeg, stroke, color, texture);
569 };
570