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