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.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 * 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.NodeRGBA.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 * @param {Number} fade time to fade 499 * @param {Number} minSeg minimum segment size 500 * @param {Number} stroke stroke's width 501 * @param {Number} color 502 * @param {string|cc.Texture2D} texture texture filename or texture 503 * @return {cc.MotionStreak} 504 */ 505 cc.MotionStreak.create = function (fade, minSeg, stroke, color, texture) { 506 return new cc.MotionStreak(fade, minSeg, stroke, color, texture); 507 }; 508