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