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