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) 2012 Pierre-David Bélanger 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 * the value of stencil bits. 30 * @type Number 31 */ 32 cc.stencilBits = -1; 33 34 /** 35 * <p> 36 * Sets the shader program for this node 37 * 38 * Since v2.0, each rendering node must set its shader program. 39 * It should be set in initialize phase. 40 * </p> 41 * @function 42 * @param {cc.Node} node 43 * @param {cc.GLProgram} program The shader program which fetchs from CCShaderCache. 44 * @example 45 * cc.setGLProgram(node, cc.shaderCache.programForKey(cc.SHADER_POSITION_TEXTURECOLOR)); 46 */ 47 cc.setProgram = function (node, program) { 48 node.shaderProgram = program; 49 50 var children = node.children; 51 if (!children) 52 return; 53 54 for (var i = 0; i < children.length; i++) 55 cc.setProgram(children[i], program); 56 }; 57 58 /** 59 * <p> 60 * cc.ClippingNode is a subclass of cc.Node. <br/> 61 * It draws its content (childs) clipped using a stencil. <br/> 62 * The stencil is an other cc.Node that will not be drawn. <br/> 63 * The clipping is done using the alpha part of the stencil (adjusted with an alphaThreshold). 64 * </p> 65 * @class 66 * @extends cc.Node 67 * @param {cc.Node} [stencil=null] 68 * 69 * @property {Number} alphaThreshold - Threshold for alpha value. 70 * @property {Boolean} inverted - Indicate whether in inverted mode. 71 */ 72 //@property {cc.Node} stencil - he cc.Node to use as a stencil to do the clipping. 73 cc.ClippingNode = cc.Node.extend(/** @lends cc.ClippingNode# */{ 74 alphaThreshold: 0, 75 inverted: false, 76 77 _stencil: null, 78 _godhelpme: false, 79 80 /** 81 * Constructor function, override it to extend the construction behavior, remember to call "this._super()" in the extended "ctor" function. 82 * @param {cc.Node} [stencil=null] 83 */ 84 ctor: function (stencil) { 85 cc.Node.prototype.ctor.call(this); 86 this._stencil = null; 87 this.alphaThreshold = 0; 88 this.inverted = false; 89 90 stencil = stencil || null; 91 cc.ClippingNode.prototype.init.call(this, stencil); 92 }, 93 94 /** 95 * Initialization of the node, please do not call this function by yourself, you should pass the parameters to constructor to initialize it . 96 * @function 97 * @param {cc.Node} [stencil=null] 98 */ 99 init: null, 100 101 _className: "ClippingNode", 102 103 _initForWebGL: function (stencil) { 104 this._stencil = stencil; 105 106 this.alphaThreshold = 1; 107 this.inverted = false; 108 // get (only once) the number of bits of the stencil buffer 109 cc.ClippingNode._init_once = true; 110 if (cc.ClippingNode._init_once) { 111 cc.stencilBits = cc._renderContext.getParameter(cc._renderContext.STENCIL_BITS); 112 if (cc.stencilBits <= 0) 113 cc.log("Stencil buffer is not enabled."); 114 cc.ClippingNode._init_once = false; 115 } 116 return true; 117 }, 118 119 _initForCanvas: function (stencil) { 120 this._stencil = stencil; 121 this.alphaThreshold = 1; 122 this.inverted = false; 123 }, 124 125 /** 126 * <p> 127 * Event callback that is invoked every time when node enters the 'stage'. <br/> 128 * If the CCNode enters the 'stage' with a transition, this event is called when the transition starts. <br/> 129 * During onEnter you can't access a "sister/brother" node. <br/> 130 * If you override onEnter, you must call its parent's onEnter function with this._super(). 131 * </p> 132 * @function 133 */ 134 onEnter: function () { 135 cc.Node.prototype.onEnter.call(this); 136 this._stencil.onEnter(); 137 }, 138 139 /** 140 * <p> 141 * Event callback that is invoked when the node enters in the 'stage'. <br/> 142 * If the node enters the 'stage' with a transition, this event is called when the transition finishes. <br/> 143 * If you override onEnterTransitionDidFinish, you shall call its parent's onEnterTransitionDidFinish with this._super() 144 * </p> 145 * @function 146 */ 147 onEnterTransitionDidFinish: function () { 148 cc.Node.prototype.onEnterTransitionDidFinish.call(this); 149 this._stencil.onEnterTransitionDidFinish(); 150 }, 151 152 /** 153 * <p> 154 * callback that is called every time the node leaves the 'stage'. <br/> 155 * If the node leaves the 'stage' with a transition, this callback is called when the transition starts. <br/> 156 * If you override onExitTransitionDidStart, you shall call its parent's onExitTransitionDidStart with this._super() 157 * </p> 158 * @function 159 */ 160 onExitTransitionDidStart: function () { 161 this._stencil.onExitTransitionDidStart(); 162 cc.Node.prototype.onExitTransitionDidStart.call(this); 163 }, 164 165 /** 166 * <p> 167 * callback that is called every time the node leaves the 'stage'. <br/> 168 * If the node leaves the 'stage' with a transition, this callback is called when the transition finishes. <br/> 169 * During onExit you can't access a sibling node. <br/> 170 * If you override onExit, you shall call its parent's onExit with this._super(). 171 * </p> 172 * @function 173 */ 174 onExit: function () { 175 this._stencil.onExit(); 176 cc.Node.prototype.onExit.call(this); 177 }, 178 179 /** 180 * Recursive method that visit its children and draw them 181 * @function 182 * @param {CanvasRenderingContext2D|WebGLRenderingContext} ctx 183 */ 184 visit: null, 185 186 _visitForWebGL: function (ctx) { 187 var gl = ctx || cc._renderContext; 188 189 // if stencil buffer disabled 190 if (cc.stencilBits < 1) { 191 // draw everything, as if there where no stencil 192 cc.Node.prototype.visit.call(this, ctx); 193 return; 194 } 195 196 // return fast (draw nothing, or draw everything if in inverted mode) if: 197 // - nil stencil node 198 // - or stencil node invisible: 199 if (!this._stencil || !this._stencil.visible) { 200 if (this.inverted) 201 cc.Node.prototype.visit.call(this, ctx); // draw everything 202 return; 203 } 204 205 // store the current stencil layer (position in the stencil buffer), 206 // this will allow nesting up to n CCClippingNode, 207 // where n is the number of bits of the stencil buffer. 208 209 // all the _stencilBits are in use? 210 if (cc.ClippingNode._layer + 1 == cc.stencilBits) { 211 // warn once 212 cc.ClippingNode._visit_once = true; 213 if (cc.ClippingNode._visit_once) { 214 cc.log("Nesting more than " + cc.stencilBits + "stencils is not supported. Everything will be drawn without stencil for this node and its childs."); 215 cc.ClippingNode._visit_once = false; 216 } 217 // draw everything, as if there where no stencil 218 cc.Node.prototype.visit.call(this, ctx); 219 return; 220 } 221 222 /////////////////////////////////// 223 // INIT 224 225 // increment the current layer 226 cc.ClippingNode._layer++; 227 228 // mask of the current layer (ie: for layer 3: 00000100) 229 var mask_layer = 0x1 << cc.ClippingNode._layer; 230 // mask of all layers less than the current (ie: for layer 3: 00000011) 231 var mask_layer_l = mask_layer - 1; 232 // mask of all layers less than or equal to the current (ie: for layer 3: 00000111) 233 var mask_layer_le = mask_layer | mask_layer_l; 234 235 // manually save the stencil state 236 var currentStencilEnabled = gl.isEnabled(gl.STENCIL_TEST); 237 var currentStencilWriteMask = gl.getParameter(gl.STENCIL_WRITEMASK); 238 var currentStencilFunc = gl.getParameter(gl.STENCIL_FUNC); 239 var currentStencilRef = gl.getParameter(gl.STENCIL_REF); 240 var currentStencilValueMask = gl.getParameter(gl.STENCIL_VALUE_MASK); 241 var currentStencilFail = gl.getParameter(gl.STENCIL_FAIL); 242 var currentStencilPassDepthFail = gl.getParameter(gl.STENCIL_PASS_DEPTH_FAIL); 243 var currentStencilPassDepthPass = gl.getParameter(gl.STENCIL_PASS_DEPTH_PASS); 244 245 // enable stencil use 246 gl.enable(gl.STENCIL_TEST); 247 // check for OpenGL error while enabling stencil test 248 //cc.checkGLErrorDebug(); 249 250 // all bits on the stencil buffer are readonly, except the current layer bit, 251 // this means that operation like glClear or glStencilOp will be masked with this value 252 gl.stencilMask(mask_layer); 253 254 // manually save the depth test state 255 //GLboolean currentDepthTestEnabled = GL_TRUE; 256 //currentDepthTestEnabled = glIsEnabled(GL_DEPTH_TEST); 257 var currentDepthWriteMask = gl.getParameter(gl.DEPTH_WRITEMASK); 258 259 // disable depth test while drawing the stencil 260 //glDisable(GL_DEPTH_TEST); 261 // disable update to the depth buffer while drawing the stencil, 262 // as the stencil is not meant to be rendered in the real scene, 263 // it should never prevent something else to be drawn, 264 // only disabling depth buffer update should do 265 gl.depthMask(false); 266 267 /////////////////////////////////// 268 // CLEAR STENCIL BUFFER 269 270 // manually clear the stencil buffer by drawing a fullscreen rectangle on it 271 // setup the stencil test func like this: 272 // for each pixel in the fullscreen rectangle 273 // never draw it into the frame buffer 274 // if not in inverted mode: set the current layer value to 0 in the stencil buffer 275 // if in inverted mode: set the current layer value to 1 in the stencil buffer 276 gl.stencilFunc(gl.NEVER, mask_layer, mask_layer); 277 gl.stencilOp(!this.inverted ? gl.ZERO : gl.REPLACE, gl.KEEP, gl.KEEP); 278 279 // draw a fullscreen solid rectangle to clear the stencil buffer 280 cc.kmGLMatrixMode(cc.KM_GL_PROJECTION); 281 cc.kmGLPushMatrix(); 282 cc.kmGLLoadIdentity(); 283 cc.kmGLMatrixMode(cc.KM_GL_MODELVIEW); 284 cc.kmGLPushMatrix(); 285 cc.kmGLLoadIdentity(); 286 cc._drawingUtil.drawSolidRect(cc.p(-1,-1), cc.p(1,1), cc.color(255, 255, 255, 255)); 287 cc.kmGLMatrixMode(cc.KM_GL_PROJECTION); 288 cc.kmGLPopMatrix(); 289 cc.kmGLMatrixMode(cc.KM_GL_MODELVIEW); 290 cc.kmGLPopMatrix(); 291 292 /////////////////////////////////// 293 // DRAW CLIPPING STENCIL 294 295 // setup the stencil test func like this: 296 // for each pixel in the stencil node 297 // never draw it into the frame buffer 298 // if not in inverted mode: set the current layer value to 1 in the stencil buffer 299 // if in inverted mode: set the current layer value to 0 in the stencil buffer 300 gl.stencilFunc(gl.NEVER, mask_layer, mask_layer); 301 gl.stencilOp(!this.inverted ? gl.REPLACE : gl.ZERO, gl.KEEP, gl.KEEP); 302 303 if (this.alphaThreshold < 1) { 304 // since glAlphaTest do not exists in OES, use a shader that writes 305 // pixel only if greater than an alpha threshold 306 var program = cc.shaderCache.programForKey(cc.SHADER_POSITION_TEXTURECOLORALPHATEST); 307 var alphaValueLocation = gl.getUniformLocation(program.getProgram(), cc.UNIFORM_ALPHA_TEST_VALUE_S); 308 // set our alphaThreshold 309 cc.glUseProgram(program.getProgram()); 310 program.setUniformLocationWith1f(alphaValueLocation, this.alphaThreshold); 311 // we need to recursively apply this shader to all the nodes in the stencil node 312 // XXX: we should have a way to apply shader to all nodes without having to do this 313 cc.setProgram(this._stencil, program); 314 } 315 316 // draw the stencil node as if it was one of our child 317 // (according to the stencil test func/op and alpha (or alpha shader) test) 318 cc.kmGLPushMatrix(); 319 this.transform(); 320 this._stencil.visit(); 321 cc.kmGLPopMatrix(); 322 323 // restore alpha test state 324 //if (this.alphaThreshold < 1) { 325 // XXX: we need to find a way to restore the shaders of the stencil node and its childs 326 //} 327 328 // restore the depth test state 329 gl.depthMask(currentDepthWriteMask); 330 331 /////////////////////////////////// 332 // DRAW CONTENT 333 334 // setup the stencil test func like this: 335 // for each pixel of this node and its childs 336 // if all layers less than or equals to the current are set to 1 in the stencil buffer 337 // draw the pixel and keep the current layer in the stencil buffer 338 // else 339 // do not draw the pixel but keep the current layer in the stencil buffer 340 gl.stencilFunc(gl.EQUAL, mask_layer_le, mask_layer_le); 341 gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); 342 343 // draw (according to the stencil test func) this node and its childs 344 cc.Node.prototype.visit.call(this, ctx); 345 346 /////////////////////////////////// 347 // CLEANUP 348 349 // manually restore the stencil state 350 gl.stencilFunc(currentStencilFunc, currentStencilRef, currentStencilValueMask); 351 gl.stencilOp(currentStencilFail, currentStencilPassDepthFail, currentStencilPassDepthPass); 352 gl.stencilMask(currentStencilWriteMask); 353 if (!currentStencilEnabled) 354 gl.disable(gl.STENCIL_TEST); 355 356 // we are done using this layer, decrement 357 cc.ClippingNode._layer--; 358 }, 359 360 _visitForCanvas: function (ctx) { 361 // return fast (draw nothing, or draw everything if in inverted mode) if: 362 // - nil stencil node 363 // - or stencil node invisible: 364 if (!this._stencil || !this._stencil.visible) { 365 if (this.inverted) 366 cc.Node.prototype.visit.call(this, ctx); // draw everything 367 return; 368 } 369 370 var context = ctx || cc._renderContext; 371 var canvas = context.canvas; 372 // Composition mode, costy but support texture stencil 373 if (this._cangodhelpme() || this._stencil instanceof cc.Sprite) { 374 // Cache the current canvas, for later use (This is a little bit heavy, replace this solution with other walkthrough) 375 var locCache = cc.ClippingNode._getSharedCache(); 376 locCache.width = canvas.width; 377 locCache.height = canvas.height; 378 var locCacheCtx = locCache.getContext("2d"); 379 locCacheCtx.drawImage(canvas, 0, 0); 380 381 context.save(); 382 // Draw everything first using node visit function 383 cc.Node.prototype.visit.call(this, context); 384 385 context.globalCompositeOperation = this.inverted ? "destination-out" : "destination-in"; 386 387 this.transform(context); 388 this._stencil.visit(); 389 390 context.restore(); 391 392 // Redraw the cached canvas, so that the cliped area shows the background etc. 393 context.save(); 394 context.setTransform(1, 0, 0, 1, 0, 0); 395 context.globalCompositeOperation = "destination-over"; 396 context.drawImage(locCache, 0, 0); 397 context.restore(); 398 } 399 // Clip mode, fast, but only support cc.DrawNode 400 else { 401 var i, children = this._children, locChild; 402 403 context.save(); 404 this.transform(context); 405 this._stencil.visit(context); 406 if (this.inverted) { 407 context.save(); 408 409 context.setTransform(1, 0, 0, 1, 0, 0); 410 411 context.moveTo(0, 0); 412 context.lineTo(0, canvas.height); 413 context.lineTo(canvas.width, canvas.height); 414 context.lineTo(canvas.width, 0); 415 context.lineTo(0, 0); 416 417 context.restore(); 418 } 419 context.clip(); 420 421 // Clip mode doesn't support recusive stencil, so once we used a clip stencil, 422 // so if it has ClippingNode as a child, the child must uses composition stencil. 423 this._cangodhelpme(true); 424 var len = children.length; 425 if (len > 0) { 426 this.sortAllChildren(); 427 // draw children zOrder < 0 428 for (i = 0; i < len; i++) { 429 locChild = children[i]; 430 if (locChild._localZOrder < 0) 431 locChild.visit(context); 432 else 433 break; 434 } 435 this.draw(context); 436 for (; i < len; i++) { 437 children[i].visit(context); 438 } 439 } else 440 this.draw(context); 441 this._cangodhelpme(false); 442 443 context.restore(); 444 } 445 }, 446 447 /** 448 * The cc.Node to use as a stencil to do the clipping. <br/> 449 * The stencil node will be retained. This default to nil. 450 * @return {cc.Node} 451 */ 452 getStencil: function () { 453 return this._stencil; 454 }, 455 456 /** 457 * Set stencil. 458 * @function 459 * @param {cc.Node} stencil 460 */ 461 setStencil: null, 462 463 _setStencilForWebGL: function (stencil) { 464 this._stencil = stencil; 465 }, 466 467 _setStencilForCanvas: function (stencil) { 468 this._stencil = stencil; 469 var locContext = cc._renderContext; 470 // For texture stencil, use the sprite itself 471 if (stencil instanceof cc.Sprite) { 472 return; 473 } 474 // For shape stencil, rewrite the draw of stencil ,only init the clip path and draw nothing. 475 else if (stencil instanceof cc.DrawNode) { 476 stencil.draw = function () { 477 var locEGL_ScaleX = cc.view.getScaleX(), locEGL_ScaleY = cc.view.getScaleY(); 478 locContext.beginPath(); 479 for (var i = 0; i < stencil._buffer.length; i++) { 480 var element = stencil._buffer[i]; 481 var vertices = element.verts; 482 483 //cc.assert(cc.vertexListIsClockwise(vertices), 484 // "Only clockwise polygons should be used as stencil"); 485 486 var firstPoint = vertices[0]; 487 locContext.moveTo(firstPoint.x * locEGL_ScaleX, -firstPoint.y * locEGL_ScaleY); 488 for (var j = 1, len = vertices.length; j < len; j++) 489 locContext.lineTo(vertices[j].x * locEGL_ScaleX, -vertices[j].y * locEGL_ScaleY); 490 } 491 } 492 } 493 }, 494 495 /** 496 * <p> 497 * The alpha threshold. <br/> 498 * The content is drawn only where the stencil have pixel with alpha greater than the alphaThreshold. <br/> 499 * Should be a float between 0 and 1. <br/> 500 * This default to 1 (so alpha test is disabled). 501 * </P> 502 * @return {Number} 503 */ 504 getAlphaThreshold: function () { 505 return this.alphaThreshold; 506 }, 507 508 /** 509 * set alpha threshold. 510 * @param {Number} alphaThreshold 511 */ 512 setAlphaThreshold: function (alphaThreshold) { 513 this.alphaThreshold = alphaThreshold; 514 }, 515 516 /** 517 * <p> 518 * Inverted. If this is set to YES, <br/> 519 * the stencil is inverted, so the content is drawn where the stencil is NOT drawn. <br/> 520 * This default to NO. 521 * </p> 522 * @return {Boolean} 523 */ 524 isInverted: function () { 525 return this.inverted; 526 }, 527 528 /** 529 * set whether or not invert of stencil 530 * @param {Boolean} inverted 531 */ 532 setInverted: function (inverted) { 533 this.inverted = inverted; 534 }, 535 536 _cangodhelpme: function (godhelpme) { 537 if (godhelpme === true || godhelpme === false) 538 cc.ClippingNode.prototype._godhelpme = godhelpme; 539 return cc.ClippingNode.prototype._godhelpme; 540 } 541 }); 542 543 var _p = cc.ClippingNode.prototype; 544 545 if (cc._renderType === cc._RENDER_TYPE_WEBGL) { 546 //WebGL 547 _p.init = _p._initForWebGL; 548 _p.visit = _p._visitForWebGL; 549 _p.setStencil = _p._setStencilForWebGL; 550 } else { 551 _p.init = _p._initForCanvas; 552 _p.visit = _p._visitForCanvas; 553 _p.setStencil = _p._setStencilForCanvas; 554 } 555 556 // Extended properties 557 cc.defineGetterSetter(_p, "stencil", _p.getStencil, _p.setStencil); 558 /** @expose */ 559 _p.stencil; 560 561 562 cc.ClippingNode._init_once = null; 563 cc.ClippingNode._visit_once = null; 564 cc.ClippingNode._layer = -1; 565 cc.ClippingNode._sharedCache = null; 566 567 cc.ClippingNode._getSharedCache = function () { 568 return (cc.ClippingNode._sharedCache) || (cc.ClippingNode._sharedCache = document.createElement("canvas")); 569 }; 570 571 /** 572 * Creates and initializes a clipping node with an other node as its stencil. <br/> 573 * The stencil node will be retained. 574 * @deprecated since v3.0, please use getNodeToParentTransform instead 575 * @param {cc.Node} [stencil=null] 576 * @return {cc.ClippingNode} 577 * @example 578 * //example 579 * new cc.ClippingNode(stencil); 580 */ 581 cc.ClippingNode.create = function (stencil) { 582 return new cc.ClippingNode(stencil); 583 }; 584