phaser
Version:
A fast, free and fun HTML5 Game Framework for Desktop and Mobile web browsers from the team at Phaser Studio Inc.
310 lines (263 loc) • 10.2 kB
JavaScript
/**
* @author Richard Davey <rich@phaser.io>
* @copyright 2013-2025 Phaser Studio Inc.
* @license {@link https://opensource.org/licenses/MIT|MIT License}
*/
var Class = require('../../utils/Class');
/**
* @classdesc
* A Geometry Mask can be applied to a Game Object to hide any pixels of it which don't intersect
* a visible pixel from the geometry mask. The mask is essentially a clipping path which can only
* make a masked pixel fully visible or fully invisible without changing its alpha (opacity).
*
* A Geometry Mask uses a Graphics Game Object to determine which pixels of the masked Game Object(s)
* should be clipped. For any given point of a masked Game Object's texture, the pixel will only be displayed
* if the Graphics Game Object of the Geometry Mask has a visible pixel at the same position. The color and
* alpha of the pixel from the Geometry Mask do not matter.
*
* The Geometry Mask's location matches the location of its Graphics object, not the location of the masked objects.
* Moving or transforming the underlying Graphics object will change the mask (and affect the visibility
* of any masked objects), whereas moving or transforming a masked object will not affect the mask.
* You can think of the Geometry Mask (or rather, of its Graphics object) as an invisible curtain placed
* in front of all masked objects which has its own visual properties and, naturally, respects the camera's
* visual properties, but isn't affected by and doesn't follow the masked objects by itself.
*
* @class GeometryMask
* @memberof Phaser.Display.Masks
* @constructor
* @since 3.0.0
*
* @param {Phaser.Scene} scene - This parameter is not used.
* @param {Phaser.GameObjects.Graphics} graphicsGeometry - The Graphics Game Object to use for the Geometry Mask. Doesn't have to be in the Display List.
*/
var GeometryMask = new Class({
initialize:
function GeometryMask (scene, graphicsGeometry)
{
/**
* The Graphics object which describes the Geometry Mask.
*
* @name Phaser.Display.Masks.GeometryMask#geometryMask
* @type {Phaser.GameObjects.Graphics}
* @since 3.0.0
*/
this.geometryMask = graphicsGeometry;
/**
* Similar to the BitmapMasks invertAlpha setting this to true will then hide all pixels
* drawn to the Geometry Mask.
*
* This is a WebGL only feature.
*
* @name Phaser.Display.Masks.GeometryMask#invertAlpha
* @type {boolean}
* @since 3.16.0
*/
this.invertAlpha = false;
/**
* Is this mask a stencil mask?
*
* @name Phaser.Display.Masks.GeometryMask#isStencil
* @type {boolean}
* @readonly
* @since 3.17.0
*/
this.isStencil = true;
/**
* The current stencil level. This can change dynamically at runtime
* and is set in the applyStencil method.
*
* @name Phaser.Display.Masks.GeometryMask#level
* @type {boolean}
* @since 3.17.0
*/
this.level = 0;
},
/**
* Sets a new Graphics object for the Geometry Mask.
*
* @method Phaser.Display.Masks.GeometryMask#setShape
* @since 3.0.0
*
* @param {Phaser.GameObjects.Graphics} graphicsGeometry - The Graphics object which will be used for the Geometry Mask.
*
* @return {this} This Geometry Mask
*/
setShape: function (graphicsGeometry)
{
this.geometryMask = graphicsGeometry;
return this;
},
/**
* Sets the `invertAlpha` property of this Geometry Mask.
*
* Inverting the alpha essentially flips the way the mask works.
*
* This is a WebGL only feature.
*
* @method Phaser.Display.Masks.GeometryMask#setInvertAlpha
* @since 3.17.0
*
* @param {boolean} [value=true] - Invert the alpha of this mask?
*
* @return {this} This Geometry Mask
*/
setInvertAlpha: function (value)
{
if (value === undefined) { value = true; }
this.invertAlpha = value;
return this;
},
/**
* Renders the Geometry Mask's underlying Graphics object to the OpenGL stencil buffer and enables the stencil test, which clips rendered pixels according to the mask.
*
* @method Phaser.Display.Masks.GeometryMask#preRenderWebGL
* @since 3.0.0
*
* @param {Phaser.Renderer.WebGL.WebGLRenderer} renderer - The WebGL Renderer instance to draw to.
* @param {Phaser.GameObjects.GameObject} child - The Game Object being rendered.
* @param {Phaser.Cameras.Scene2D.Camera} camera - The camera the Game Object is being rendered through.
*/
preRenderWebGL: function (renderer, child, camera)
{
var gl = renderer.gl;
// Force flushing before drawing to stencil buffer
renderer.flush();
if (renderer.maskStack.length === 0)
{
gl.enable(gl.STENCIL_TEST);
gl.clear(gl.STENCIL_BUFFER_BIT);
renderer.maskCount = 0;
}
if (renderer.currentCameraMask.mask !== this)
{
renderer.currentMask.mask = this;
}
renderer.maskStack.push({ mask: this, camera: camera });
this.applyStencil(renderer, camera, true);
renderer.maskCount++;
},
/**
* Applies the current stencil mask to the renderer.
*
* @method Phaser.Display.Masks.GeometryMask#applyStencil
* @since 3.17.0
*
* @param {Phaser.Renderer.WebGL.WebGLRenderer} renderer - The WebGL Renderer instance to draw to.
* @param {Phaser.Cameras.Scene2D.Camera} camera - The camera the Game Object is being rendered through.
* @param {boolean} inc - Is this an INCR stencil or a DECR stencil?
*/
applyStencil: function (renderer, camera, inc)
{
var gl = renderer.gl;
var geometryMask = this.geometryMask;
var level = renderer.maskCount;
var mask = 0xff;
gl.colorMask(false, false, false, false);
if (inc)
{
gl.stencilFunc(gl.EQUAL, level, mask);
gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR);
// Do this _after_ we set the stencilFunc
level++;
}
else
{
gl.stencilFunc(gl.EQUAL, level + 1, mask);
gl.stencilOp(gl.KEEP, gl.KEEP, gl.DECR);
}
this.level = level;
// Write stencil buffer
geometryMask.renderWebGL(renderer, geometryMask, camera);
renderer.flush();
gl.colorMask(true, true, true, true);
gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP);
if (this.invertAlpha)
{
gl.stencilFunc(gl.NOTEQUAL, level, mask);
}
else
{
gl.stencilFunc(gl.EQUAL, level, mask);
}
},
/**
* Flushes all rendered pixels and disables the stencil test of a WebGL context, thus disabling the mask for it.
*
* @method Phaser.Display.Masks.GeometryMask#postRenderWebGL
* @since 3.0.0
*
* @param {Phaser.Renderer.WebGL.WebGLRenderer} renderer - The WebGL Renderer instance to draw flush.
*/
postRenderWebGL: function (renderer)
{
var gl = renderer.gl;
renderer.maskStack.pop();
renderer.maskCount--;
// Force flush before disabling stencil test
renderer.flush();
var current = renderer.currentMask;
if (renderer.maskStack.length === 0)
{
// If this is the only mask in the stack, flush and disable
current.mask = null;
gl.disable(gl.STENCIL_TEST);
}
else
{
var prev = renderer.maskStack[renderer.maskStack.length - 1];
prev.mask.applyStencil(renderer, prev.camera, false);
if (renderer.currentCameraMask.mask !== prev.mask)
{
current.mask = prev.mask;
current.camera = prev.camera;
}
else
{
current.mask = null;
}
}
},
/**
* Sets the clipping path of a 2D canvas context to the Geometry Mask's underlying Graphics object.
*
* @method Phaser.Display.Masks.GeometryMask#preRenderCanvas
* @since 3.0.0
*
* @param {Phaser.Renderer.Canvas.CanvasRenderer} renderer - The Canvas Renderer instance to set the clipping path on.
* @param {Phaser.GameObjects.GameObject} mask - The Game Object being rendered.
* @param {Phaser.Cameras.Scene2D.Camera} camera - The camera the Game Object is being rendered through.
*/
preRenderCanvas: function (renderer, mask, camera)
{
var geometryMask = this.geometryMask;
renderer.currentContext.save();
geometryMask.renderCanvas(renderer, geometryMask, camera, null, null, true);
renderer.currentContext.clip();
},
/**
* Restore the canvas context's previous clipping path, thus turning off the mask for it.
*
* @method Phaser.Display.Masks.GeometryMask#postRenderCanvas
* @since 3.0.0
*
* @param {Phaser.Renderer.Canvas.CanvasRenderer} renderer - The Canvas Renderer instance being restored.
*/
postRenderCanvas: function (renderer)
{
renderer.currentContext.restore();
},
/**
* Destroys this GeometryMask and nulls any references it holds.
*
* Note that if a Game Object is currently using this mask it will _not_ automatically detect you have destroyed it,
* so be sure to call `clearMask` on any Game Object using it, before destroying it.
*
* @method Phaser.Display.Masks.GeometryMask#destroy
* @since 3.7.0
*/
destroy: function ()
{
this.geometryMask = null;
}
});
module.exports = GeometryMask;