CCClippingNode.js 22 KB

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