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