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  */
 43 cc.MotionStreak = cc.Node.extend(/** @lends cc.MotionStreak# */{
 44     texture:null,
 45     fastMode:false,
 46     startingPositionInitialized:false,
 47 
 48     _blendFunc:null,
 49 
 50     _stroke:0,
 51     _fadeDelta:0,
 52     _minSeg:0,
 53 
 54     _maxPoints:0,
 55     _nuPoints:0,
 56     _previousNuPoints:0,
 57 
 58     /** Pointers */
 59     _pointVertexes:null,
 60     _pointState:null,
 61 
 62     // webgl
 63     _vertices:null,
 64     _colorPointer:null,
 65     _texCoords:null,
 66 
 67     _verticesBuffer:null,
 68     _colorPointerBuffer:null,
 69     _texCoordsBuffer:null,
 70     _className:"MotionStreak",
 71 
 72     /**
 73      * creates and initializes a motion streak with fade in seconds, minimum segments, stroke's width, color, texture filename or texture   <br/>
 74      * Constructor of cc.MotionStreak
 75      * @param {Number} fade time to fade
 76      * @param {Number} minSeg minimum segment size
 77      * @param {Number} stroke stroke's width
 78      * @param {Number} color
 79      * @param {string|cc.Texture2D} texture texture filename or texture
 80      */
 81     ctor: function (fade, minSeg, stroke, color, texture) {
 82         cc.Node.prototype.ctor.call(this);
 83         this._positionR = cc.p(0, 0);
 84         this._blendFunc = new cc.BlendFunc(cc.SRC_ALPHA, cc.ONE_MINUS_SRC_ALPHA);
 85         this._vertexWebGLBuffer = cc._renderContext.createBuffer();
 86 
 87         this.fastMode = false;
 88         this.startingPositionInitialized = false;
 89 
 90         this.texture = null;
 91 
 92         this._stroke = 0;
 93         this._fadeDelta = 0;
 94         this._minSeg = 0;
 95 
 96         this._maxPoints = 0;
 97         this._nuPoints = 0;
 98         this._previousNuPoints = 0;
 99 
100         /** Pointers */
101         this._pointVertexes = null;
102         this._pointState = null;
103 
104         // webgl
105         this._vertices = null;
106         this._colorPointer = null;
107         this._texCoords = null;
108 
109         this._verticesBuffer = null;
110         this._colorPointerBuffer = null;
111         this._texCoordsBuffer = null;
112 
113         if(texture !== undefined)
114             this.initWithFade(fade, minSeg, stroke, color, texture);
115     },
116 
117     /**
118      * @return {cc.Texture2D}
119      */
120     getTexture:function () {
121         return this.texture;
122     },
123 
124     /**
125      * @param {cc.Texture2D} texture
126      */
127     setTexture:function (texture) {
128         if (this.texture != texture)
129             this.texture = texture;
130     },
131 
132     /**
133      * @return {cc.BlendFunc}
134      */
135     getBlendFunc:function () {
136         return this._blendFunc;
137     },
138 
139     /**
140      * @param {Number} src
141      * @param {Number} dst
142      */
143     setBlendFunc:function (src, dst) {
144         if (dst === undefined) {
145             this._blendFunc = src;
146         } else {
147             this._blendFunc.src = src;
148             this._blendFunc.dst = dst;
149         }
150     },
151 
152     getOpacity:function () {
153         cc.log("cc.MotionStreak.getOpacity has not been supported.");
154         return 0;
155     },
156 
157     setOpacity:function (opacity) {
158         cc.log("cc.MotionStreak.setOpacity has not been supported.");
159     },
160 
161     setOpacityModifyRGB:function (value) {
162     },
163 
164     isOpacityModifyRGB:function () {
165         return false;
166     },
167 
168     onExit:function(){
169         cc.Node.prototype.onExit.call(this);
170         if(this._verticesBuffer)
171             cc._renderContext.deleteBuffer(this._verticesBuffer);
172         if(this._texCoordsBuffer)
173             cc._renderContext.deleteBuffer(this._texCoordsBuffer);
174         if(this._colorPointerBuffer)
175             cc._renderContext.deleteBuffer(this._colorPointerBuffer);
176     },
177 
178     isFastMode:function () {
179         return this.fastMode;
180     },
181 
182     /**
183      * set fast mode
184      * @param {Boolean} fastMode
185      */
186     setFastMode:function (fastMode) {
187         this.fastMode = fastMode;
188     },
189 
190     isStartingPositionInitialized:function () {
191         return this.startingPositionInitialized;
192     },
193 
194     setStartingPositionInitialized:function (startingPositionInitialized) {
195         this.startingPositionInitialized = startingPositionInitialized;
196     },
197 
198     /**
199      * initializes a motion streak with fade in seconds, minimum segments, stroke's width, color and texture filename or texture
200      * @param {Number} fade time to fade
201      * @param {Number} minSeg minimum segment size
202      * @param {Number} stroke stroke's width
203      * @param {Number} color
204      * @param {string|cc.Texture2D} texture texture filename or texture
205      * @return {Boolean}
206      */
207     initWithFade:function (fade, minSeg, stroke, color, texture) {
208         if(!texture)
209             throw "cc.MotionStreak.initWithFade(): Invalid filename or texture";
210 
211         if (typeof(texture) === "string")
212             texture = cc.textureCache.addImage(texture);
213 
214         cc.Node.prototype.setPosition.call(this, cc.p(0,0));
215         this.anchorX = 0;
216         this.anchorY = 0;
217         this.ignoreAnchor = true;
218         this.startingPositionInitialized = false;
219 
220         this.fastMode = true;
221         this._minSeg = (minSeg == -1.0) ? (stroke / 5.0) : minSeg;
222         this._minSeg *= this._minSeg;
223 
224         this._stroke = stroke;
225         this._fadeDelta = 1.0 / fade;
226 
227         var locMaxPoints = (0 | (fade * 60)) + 2;
228         this._nuPoints = 0;
229         this._pointState = new Float32Array(locMaxPoints);
230         this._pointVertexes = new Float32Array(locMaxPoints * 2);
231 
232         this._vertices = new Float32Array(locMaxPoints * 4);
233         this._texCoords = new Float32Array(locMaxPoints * 4);
234         this._colorPointer = new Uint8Array(locMaxPoints * 8);
235         this._maxPoints = locMaxPoints;
236 
237         var gl = cc._renderContext;
238 
239         this._verticesBuffer = gl.createBuffer();
240         this._texCoordsBuffer = gl.createBuffer();
241         this._colorPointerBuffer = gl.createBuffer();
242 
243         // Set blend mode
244         this._blendFunc.src = gl.SRC_ALPHA;
245         this._blendFunc.dst = gl.ONE_MINUS_SRC_ALPHA;
246 
247         // shader program
248         this.shaderProgram = cc.shaderCache.programForKey(cc.SHADER_POSITION_TEXTURECOLOR);
249 
250         this.texture = texture;
251         this.color = color;
252         this.scheduleUpdate();
253 
254         //bind buffer
255         gl.bindBuffer(gl.ARRAY_BUFFER, this._verticesBuffer);
256         gl.bufferData(gl.ARRAY_BUFFER, this._vertices, gl.DYNAMIC_DRAW);
257         gl.bindBuffer(gl.ARRAY_BUFFER, this._texCoordsBuffer);
258         gl.bufferData(gl.ARRAY_BUFFER, this._texCoords, gl.DYNAMIC_DRAW);
259         gl.bindBuffer(gl.ARRAY_BUFFER, this._colorPointerBuffer);
260         gl.bufferData(gl.ARRAY_BUFFER, this._colorPointer, gl.DYNAMIC_DRAW);
261 
262         return true;
263     },
264 
265     /**
266      * color used for the tint
267      * @param {cc.Color} color
268      */
269     tintWithColor:function (color) {
270         this.color = color;
271 
272         // Fast assignation
273         var locColorPointer = this._colorPointer;
274         for (var i = 0, len = this._nuPoints * 2; i < len; i++) {
275             locColorPointer[i * 4] = color.r;
276             locColorPointer[i * 4 + 1] = color.g;
277             locColorPointer[i * 4 + 2] = color.b;
278         }
279     },
280 
281     /**
282      * Remove all living segments of the ribbon
283      */
284     reset:function () {
285         this._nuPoints = 0;
286     },
287 
288     /**
289      * @override
290      * @param {cc.Point} position
291      */
292     setPosition:function (position, yValue) {
293         this.startingPositionInitialized = true;
294         if(yValue === undefined){
295             this._positionR.x = position.x;
296             this._positionR.y = position.y;
297         } else {
298             this._positionR.x = position;
299             this._positionR.y = yValue;
300         }
301     },
302 
303     /**
304      * @return {Number}
305      */
306     getPositionX:function () {
307         return this._positionR.x;
308     },
309 
310     /**
311      * @param {Number} x
312      */
313     setPositionX:function (x) {
314         this._positionR.x = x;
315         if(!this.startingPositionInitialized)
316             this.startingPositionInitialized = true;
317     },
318 
319     /**
320      * @return {Number}
321      */
322     getPositionY:function () {
323         return  this._positionR.y;
324     },
325 
326     /**
327      * @param {Number} y
328      */
329     setPositionY:function (y) {
330         this._positionR.y = y;
331         if(!this.startingPositionInitialized)
332             this.startingPositionInitialized = true;
333     },
334 
335     /**
336      * @override
337      * @param {WebGLRenderingContext} ctx
338      */
339     draw:function (ctx) {
340         if (this._nuPoints <= 1)
341             return;
342 
343         if(this.texture && this.texture.isLoaded()){
344             ctx = ctx || cc._renderContext;
345             cc.nodeDrawSetup(this);
346             cc.glEnableVertexAttribs(cc.VERTEX_ATTRIB_FLAG_POS_COLOR_TEX);
347             cc.glBlendFunc(this._blendFunc.src, this._blendFunc.dst);
348 
349             cc.glBindTexture2D(this.texture);
350 
351             //position
352             ctx.bindBuffer(ctx.ARRAY_BUFFER, this._verticesBuffer);
353             ctx.bufferData(ctx.ARRAY_BUFFER, this._vertices, ctx.DYNAMIC_DRAW);
354             ctx.vertexAttribPointer(cc.VERTEX_ATTRIB_POSITION, 2, ctx.FLOAT, false, 0, 0);
355 
356             //texcoords
357             ctx.bindBuffer(ctx.ARRAY_BUFFER, this._texCoordsBuffer);
358             ctx.bufferData(ctx.ARRAY_BUFFER, this._texCoords, ctx.DYNAMIC_DRAW);
359             ctx.vertexAttribPointer(cc.VERTEX_ATTRIB_TEX_COORDS, 2, ctx.FLOAT, false, 0, 0);
360 
361             //colors
362             ctx.bindBuffer(ctx.ARRAY_BUFFER, this._colorPointerBuffer);
363             ctx.bufferData(ctx.ARRAY_BUFFER, this._colorPointer, ctx.DYNAMIC_DRAW);
364             ctx.vertexAttribPointer(cc.VERTEX_ATTRIB_COLOR, 4, ctx.UNSIGNED_BYTE, true, 0, 0);
365 
366             ctx.drawArrays(ctx.TRIANGLE_STRIP, 0, this._nuPoints * 2);
367             cc.g_NumberOfDraws ++;
368         }
369     },
370 
371     /**
372      * @override
373      * @param {Number} delta
374      */
375     update:function (delta) {
376         if (!this.startingPositionInitialized)
377             return;
378 
379         delta *= this._fadeDelta;
380 
381         var newIdx, newIdx2, i, i2;
382         var mov = 0;
383 
384         // Update current points
385         var locNuPoints = this._nuPoints;
386         var locPointState = this._pointState, locPointVertexes = this._pointVertexes, locVertices = this._vertices;
387         var locColorPointer = this._colorPointer;
388 
389         for (i = 0; i < locNuPoints; i++) {
390             locPointState[i] -= delta;
391 
392             if (locPointState[i] <= 0)
393                 mov++;
394             else {
395                 newIdx = i - mov;
396                 if (mov > 0) {
397                     // Move data
398                     locPointState[newIdx] = locPointState[i];
399                     // Move point
400                     locPointVertexes[newIdx * 2] = locPointVertexes[i * 2];
401                     locPointVertexes[newIdx * 2 + 1] = locPointVertexes[i * 2 + 1];
402 
403                     // Move vertices
404                     i2 = i * 2;
405                     newIdx2 = newIdx * 2;
406                     locVertices[newIdx2 * 2] = locVertices[i2 * 2];
407                     locVertices[newIdx2 * 2 + 1] = locVertices[i2 * 2 + 1];
408                     locVertices[(newIdx2 + 1) * 2] = locVertices[(i2 + 1) * 2];
409                     locVertices[(newIdx2 + 1) * 2 + 1] = locVertices[(i2 + 1) * 2 + 1];
410 
411                     // Move color
412                     i2 *= 4;
413                     newIdx2 *= 4;
414                     locColorPointer[newIdx2 + 0] = locColorPointer[i2 + 0];
415                     locColorPointer[newIdx2 + 1] = locColorPointer[i2 + 1];
416                     locColorPointer[newIdx2 + 2] = locColorPointer[i2 + 2];
417                     locColorPointer[newIdx2 + 4] = locColorPointer[i2 + 4];
418                     locColorPointer[newIdx2 + 5] = locColorPointer[i2 + 5];
419                     locColorPointer[newIdx2 + 6] = locColorPointer[i2 + 6];
420                 } else
421                     newIdx2 = newIdx * 8;
422 
423                 var op = locPointState[newIdx] * 255.0;
424                 locColorPointer[newIdx2 + 3] = op;
425                 locColorPointer[newIdx2 + 7] = op;
426             }
427         }
428         locNuPoints -= mov;
429 
430         // Append new point
431         var appendNewPoint = true;
432         if (locNuPoints >= this._maxPoints)
433             appendNewPoint = false;
434         else if (locNuPoints > 0) {
435             var a1 = cc.pDistanceSQ(cc.p(locPointVertexes[(locNuPoints - 1) * 2], locPointVertexes[(locNuPoints - 1) * 2 + 1]),
436                 this._positionR) < this._minSeg;
437             var a2 = (locNuPoints == 1) ? false : (cc.pDistanceSQ(
438                 cc.p(locPointVertexes[(locNuPoints - 2) * 2], locPointVertexes[(locNuPoints - 2) * 2 + 1]), this._positionR) < (this._minSeg * 2.0));
439             if (a1 || a2)
440                 appendNewPoint = false;
441         }
442 
443         if (appendNewPoint) {
444             locPointVertexes[locNuPoints * 2] = this._positionR.x;
445             locPointVertexes[locNuPoints * 2 + 1] = this._positionR.y;
446             locPointState[locNuPoints] = 1.0;
447 
448             // Color assignment
449             var offset = locNuPoints * 8;
450 
451             var locDisplayedColor = this._displayedColor;
452             locColorPointer[offset] = locDisplayedColor.r;
453             locColorPointer[offset + 1] = locDisplayedColor.g;
454             locColorPointer[offset + 2] = locDisplayedColor.b;
455             //*((ccColor3B*)(m_pColorPointer + offset+4)) = this._color;
456             locColorPointer[offset + 4] = locDisplayedColor.r;
457             locColorPointer[offset + 5] = locDisplayedColor.g;
458             locColorPointer[offset + 6] = locDisplayedColor.b;
459 
460             // Opacity
461             locColorPointer[offset + 3] = 255;
462             locColorPointer[offset + 7] = 255;
463 
464             // Generate polygon
465             if (locNuPoints > 0 && this.fastMode) {
466                 if (locNuPoints > 1)
467                     cc.vertexLineToPolygon(locPointVertexes, this._stroke, this._vertices, locNuPoints, 1);
468                 else
469                     cc.vertexLineToPolygon(locPointVertexes, this._stroke, this._vertices, 0, 2);
470             }
471             locNuPoints++;
472         }
473 
474         if (!this.fastMode)
475             cc.vertexLineToPolygon(locPointVertexes, this._stroke, this._vertices, 0, locNuPoints);
476 
477         // Updated Tex Coords only if they are different than previous step
478         if (locNuPoints && this._previousNuPoints != locNuPoints) {
479             var texDelta = 1.0 / locNuPoints;
480             var locTexCoords = this._texCoords;
481             for (i = 0; i < locNuPoints; i++) {
482                 locTexCoords[i * 4] = 0;
483                 locTexCoords[i * 4 + 1] = texDelta * i;
484 
485                 locTexCoords[(i * 2 + 1) * 2] = 1;
486                 locTexCoords[(i * 2 + 1) * 2 + 1] = texDelta * i;
487             }
488 
489             this._previousNuPoints = locNuPoints;
490         }
491 
492         this._nuPoints = locNuPoints;
493     }
494 });
495 
496 /**
497  * creates and initializes a motion streak with fade in seconds, minimum segments, stroke's width, color, texture filename or texture
498  * @deprecated
499  * @param {Number} fade time to fade
500  * @param {Number} minSeg minimum segment size
501  * @param {Number} stroke stroke's width
502  * @param {Number} color
503  * @param {string|cc.Texture2D} texture texture filename or texture
504  * @return {cc.MotionStreak}
505  */
506 cc.MotionStreak.create = function (fade, minSeg, stroke, color, texture) {
507     return new cc.MotionStreak(fade, minSeg, stroke, color, texture);
508 };
509