Newer
Older
pixi.js / src / pixi / renderers / webgl / utils / WebGLStencilManager.js
@mathew groves mathew groves on 29 Jun 2014 7 KB Complex graphics and masking added
/**
 * @author Mat Groves http://matgroves.com/ @Doormat23
 */
 
//BA0285
//Intercontinental Hotel, 888 Howard Street
//San Francisco

/**
* @class WebGLStencilManager
* @constructor
* @param gl {WebGLContext} the current WebGL drawing context
* @private
*/
PIXI.WebGLStencilManager = function(gl)
{
   
    this.stencilStack = [];
    this.setContext(gl);
    this.reverse = true;
    this.count = 0;

};  

/**
* Sets the drawing context to the one given in parameter
* @method setContext 
* @param gl {WebGLContext} the current WebGL drawing context
*/
PIXI.WebGLStencilManager.prototype.setContext = function(gl)
{
    this.gl = gl;
};

/**
* Applies the Mask and adds it to the current filter stack
* @method pushMask
* @param maskData {Array}
* @param renderSession {RenderSession}
*/
PIXI.WebGLStencilManager.prototype.pushStencil = function(graphics, webGLData, renderSession)
{
    var gl = this.gl;
    this.bindGraphics(graphics, webGLData, renderSession);

    if(this.stencilStack.length === 0)
    {
    	gl.enable(gl.STENCIL_TEST);
    	gl.clear(gl.STENCIL_BUFFER_BIT);
    	this.reverse = true;
    	this.count = 0;

    }
    
    this.stencilStack.push(webGLData);

    var level = this.count;
	
	gl.colorMask(false, false, false, false); 
    
    gl.stencilFunc(gl.ALWAYS,0,0xFF);
    gl.stencilOp(gl.KEEP,gl.KEEP,gl.INVERT);

    // draw the triangle strip!

    if(webGLData.mode === 1)
    {

	    gl.drawElements(gl.TRIANGLE_FAN,  webGLData.indices.length - 4, gl.UNSIGNED_SHORT, 0 );
	   
	    if(this.reverse)
	    {
	        gl.stencilFunc(gl.EQUAL, 0xFF - level, 0xFF);
	        gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR);      
	    }
	    else
	    {
	        gl.stencilFunc(gl.EQUAL,level, 0xFF);
	        gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR);      
	    }

	    // draw a quad to increment..
	    gl.drawElements(gl.TRIANGLE_FAN, 4, gl.UNSIGNED_SHORT, ( webGLData.indices.length - 4 ) * 2 );
	           
	    if(this.reverse)
	    {
	        gl.stencilFunc(gl.EQUAL,0xFF-(level+1), 0xFF)
	    }
	    else
	    {
	        gl.stencilFunc(gl.EQUAL,level+1, 0xFF)
	    }

	    this.reverse = !this.reverse;
    }
    else
    {
    	if(!this.reverse)
	    {
	        gl.stencilFunc(gl.EQUAL, 0xFF - level, 0xFF);
	        gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR);      
	    }
	    else
	    {
	        gl.stencilFunc(gl.EQUAL,level, 0xFF);
	        gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR);      
	    }   

    	gl.drawElements(gl.TRIANGLE_STRIP,  webGLData.indices.length, gl.UNSIGNED_SHORT, 0 );

    	if(!this.reverse)
	    {
	        gl.stencilFunc(gl.EQUAL,0xFF-(level+1), 0xFF)
	    }
	    else
	    {
	        gl.stencilFunc(gl.EQUAL,level+1, 0xFF)
	    }
    }

    gl.colorMask(true, true, true, true);
    gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP);

    this.count++;
}

//TODO this does not belong here!
PIXI.WebGLStencilManager.prototype.bindGraphics = function(graphics, webGLData, renderSession)
{
	var gl = this.gl;

	 // bind the graphics object..
    var projection = renderSession.projection,
        offset = renderSession.offset,
        shader = renderSession.shaderManager.primitiveShader;

    if(webGLData.mode === 1)
    {	
	    renderSession.shaderManager.activateShader( renderSession.shaderManager.complexPrimativeShader );
	    shader = renderSession.shaderManager.complexPrimativeShader;

	    gl.uniformMatrix3fv(shader.translationMatrix, false, graphics.worldTransform.toArray(true));

	    gl.uniform2f(shader.projectionVector, projection.x, -projection.y);
	    gl.uniform2f(shader.offsetVector, -offset.x, -offset.y);

	    gl.uniform3fv(shader.tintColor, PIXI.hex2rgb(graphics.tint));
	    gl.uniform3fv(shader.color, webGLData.color);

	    gl.uniform1f(shader.alpha, graphics.worldAlpha * graphics.fillAlpha);

	    gl.bindBuffer(gl.ARRAY_BUFFER, webGLData.buffer);

	    gl.vertexAttribPointer(shader.aVertexPosition, 2, gl.FLOAT, false, 4 * 2, 0);


	    // now do the rest..
	    // set the index buffer!
	    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, webGLData.indexBuffer);
    }
    else
    {
    	renderSession.shaderManager.activatePrimitiveShader();
        shader = renderSession.shaderManager.primitiveShader;
        gl.uniformMatrix3fv(shader.translationMatrix, false, graphics.worldTransform.toArray(true));

        gl.uniform2f(shader.projectionVector, projection.x, -projection.y);
        gl.uniform2f(shader.offsetVector, -offset.x, -offset.y);

        gl.uniform3fv(shader.tintColor, PIXI.hex2rgb(graphics.tint));

        gl.uniform1f(shader.alpha, graphics.worldAlpha);
        
        gl.bindBuffer(gl.ARRAY_BUFFER, webGLData.buffer);

        gl.vertexAttribPointer(shader.aVertexPosition, 2, gl.FLOAT, false, 4 * 6, 0);
        gl.vertexAttribPointer(shader.colorAttribute, 4, gl.FLOAT, false,4 * 6, 2 * 4);

        // set the index buffer!
        gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, webGLData.indexBuffer);
    }
}

PIXI.WebGLStencilManager.prototype.popStencil = function(graphics, webGLData, renderSession)
{
	var gl = this.gl;
    var webGLData = this.stencilStack.pop();
   
   
    this.count--;
    if(this.stencilStack.length === 0)
    {
    	// the stack is empty!
    	gl.disable(gl.STENCIL_TEST);
    	
    }
    else
    {

    	var level = this.count;
    	var reverse = !(level % 2);

    	this.bindGraphics(graphics, webGLData, renderSession);

    	gl.colorMask(false, false, false, false); 
    
	    if(webGLData.mode === 1)
	    {
		    this.reverse = !this.reverse;

		    if(this.reverse)
		    {
		        gl.stencilFunc(gl.EQUAL, 0xFF - (level+1), 0xFF);
		        gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR);      
		    }
		    else
		    {
		        gl.stencilFunc(gl.EQUAL,level+1, 0xFF);
		        gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR);      
		    }

		    // draw a quad to increment..
		    gl.drawElements(gl.TRIANGLE_FAN, 4, gl.UNSIGNED_SHORT, ( webGLData.indices.length - 4 ) * 2 );
		    
		    gl.stencilFunc(gl.ALWAYS,0,0xFF);
		    gl.stencilOp(gl.KEEP,gl.KEEP,gl.INVERT);

		    // draw the triangle strip!
		    gl.drawElements(gl.TRIANGLE_FAN,  webGLData.indices.length - 4, gl.UNSIGNED_SHORT, 0 );
		   

		    if(!this.reverse)
		    {
		        gl.stencilFunc(gl.EQUAL,0xFF-(level), 0xFF)
		    }
		    else
		    {
		        gl.stencilFunc(gl.EQUAL,level, 0xFF)
		    }

	    }
	    else
	    {
	    	
	    	if(!this.reverse)
		    {
		        gl.stencilFunc(gl.EQUAL, 0xFF - level, 0xFF);
		        gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR);      
		    }
		    else
		    {
		        gl.stencilFunc(gl.EQUAL,level, 0xFF);
		        gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR);      
		    }   

	    	gl.drawElements(gl.TRIANGLE_STRIP,  webGLData.indices.length, gl.UNSIGNED_SHORT, 0 );

	    	if(!this.reverse)
		    {
		        gl.stencilFunc(gl.EQUAL,0xFF-(level), 0xFF)
		    }
		    else
		    {
		        gl.stencilFunc(gl.EQUAL,level, 0xFF)
		    }
	    }

	    gl.colorMask(true, true, true, true);
	    gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP);


    }

    

    renderSession.shaderManager.deactivatePrimitiveShader();
}

/**
* Destroys the mask stack
* @method destroy
*/
PIXI.WebGLStencilManager.prototype.destroy = function()
{
    this.maskStack = null;
    this.gl = null;
};