diff --git a/src/Drawable.js b/src/Drawable.js index 2bab1ded..532af120 100644 --- a/src/Drawable.js +++ b/src/Drawable.js @@ -119,8 +119,20 @@ Drawable.EFFECTS = Object.keys(Drawable._effectConverter); * @enum {string} */ Drawable.DRAW_MODE = { + /** + * Draw normally. + */ default: 'default', - pick: 'pick' + + /** + * Draw the Drawable's silhouette using a particular color. + */ + silhouette: 'silhouette', + + /** + * Draw only the parts of the drawable which match a particular color. + */ + colorMask: 'colorMask' }; /** @@ -238,7 +250,7 @@ Drawable.prototype._useSkin = function( /** * Fetch the shader for this Drawable's set of active effects. * Build the shader if necessary. - * @param {Drawable.DRAW_MODE} drawMode Draw normally or for picking, etc. + * @param {Drawable.DRAW_MODE} drawMode Draw normally, silhouette, etc. * @returns {module:twgl.ProgramInfo?} The shader's program info. */ Drawable.prototype.getShader = function (drawMode) { @@ -255,7 +267,7 @@ Drawable.prototype.getShader = function (drawMode) { /** * Build the shader for this Drawable's set of active effects. - * @param {Drawable.DRAW_MODE} drawMode Draw normally or for picking, etc. + * @param {Drawable.DRAW_MODE} drawMode Draw normally, silhouette, etc. * @returns {module:twgl.ProgramInfo?} The new shader's program info. * @private */ diff --git a/src/RenderWebGL.js b/src/RenderWebGL.js index 73753894..31753b6d 100644 --- a/src/RenderWebGL.js +++ b/src/RenderWebGL.js @@ -128,13 +128,14 @@ RenderWebGL.prototype.draw = function () { /** * Draw all Drawables, with the possible exception of * @param {int[]} drawables The Drawable IDs to draw, possibly this._drawables. - * @param {Drawable.DRAW_MODE} drawMode Draw normally or for picking, etc. + * @param {Drawable.DRAW_MODE} drawMode Draw normally, silhouette, etc. * @param {module:twgl/m4.Mat4} projection The projection matrix to use. * @param {Drawable~idFilterFunc} [filter] An optional filter function. + * @param {Object.<string,*>} [extraUniforms] Extra uniforms for the shaders. * @private */ RenderWebGL.prototype._drawThese = function( - drawables, drawMode, projection, filter) { + drawables, drawMode, projection, filter, extraUniforms) { var gl = this._gl; var currentShader = null; @@ -156,14 +157,20 @@ RenderWebGL.prototype._drawThese = function( twgl.setBuffersAndAttributes(gl, currentShader, this._bufferInfo); twgl.setUniforms(currentShader, {u_projectionMatrix: projection}); twgl.setUniforms(currentShader, {u_fudge: window.fudge || 0}); + + // TODO: should these be set after the Drawable's uniforms? + // That would allow Drawable-scope uniforms to be overridden... + if (extraUniforms) { + twgl.setUniforms(currentShader, extraUniforms); + } } twgl.setUniforms(currentShader, drawable.getUniforms()); - // TODO: consider moving u_pickColor into Drawable's getUniforms()... - if (drawMode == Drawable.DRAW_MODE.pick) { + // TODO: move u_silhouetteColor into Drawable's getUniforms() + if (drawMode == Drawable.DRAW_MODE.silhouette) { twgl.setUniforms(currentShader, - {u_pickColor: Drawable.color4fFromID(drawableID)}); + {u_silhouetteColor: Drawable.color4fFromID(drawableID)}); } twgl.drawBufferInfo(gl, gl.TRIANGLES, this._bufferInfo); @@ -329,7 +336,7 @@ RenderWebGL.prototype.pick = function ( var projection = twgl.m4.ortho( pickLeft, pickRight, pickTop, pickBottom, -1, 1); - this._drawThese(candidateIDs, Drawable.DRAW_MODE.pick, projection); + this._drawThese(candidateIDs, Drawable.DRAW_MODE.silhouette, projection); var pixels = new Buffer(touchWidth * touchHeight * 4); gl.readPixels( @@ -371,7 +378,15 @@ RenderWebGL.prototype.pick = function ( return hit | 0; }; -RenderWebGL.prototype.isTouchingColor = function(drawableID, color3ub) { +/** + * Check if a particular Drawable is touching a particular color. + * @param {int} drawableID The ID of the Drawable to check. + * @param {int[]} color3ub Test if the Drawable is touching this color. + * @param {float[]} [mask3f] Optionally mask the check to this part of Drawable. + * @returns {boolean} True iff the Drawable is touching the color. + */ +RenderWebGL.prototype.isTouchingColor = function(drawableID, color3ub, mask3f) { + var gl = this._gl; twgl.bindFramebufferInfo(gl, this._queryBufferInfo); @@ -385,13 +400,26 @@ RenderWebGL.prototype.isTouchingColor = function(drawableID, color3ub) { gl.clearColor.apply(gl, this._backgroundColor); gl.clear(gl.COLOR_BUFFER_BIT | gl.STENCIL_BUFFER_BIT); + var extraUniforms; + if (mask3f) { + extraUniforms = { + u_colorMask: mask3f, + u_colorMaskTolerance: 1 / 255 + }; + } + try { gl.enable(gl.STENCIL_TEST); gl.stencilFunc(gl.ALWAYS, 1, 1); gl.stencilOp(gl.KEEP, gl.KEEP, gl.REPLACE); gl.colorMask(false, false, false, false); this._drawThese( - [drawableID], Drawable.DRAW_MODE.pick, this._projection); + [drawableID], + mask3f ? + Drawable.DRAW_MODE.colorMask : Drawable.DRAW_MODE.silhouette, + this._projection, + undefined, + extraUniforms); gl.stencilFunc(gl.EQUAL, 1, 1); gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); @@ -432,6 +460,7 @@ RenderWebGL.prototype.isTouchingColor = function(drawableID, color3ub) { for (var pixelBase = 0; pixelBase < pixels.length; pixelBase += 4) { // TODO: tolerance? + // TODO: use u_colorMask to make this test something like "pixel != 0" if ((pixels[pixelBase] == color3ub[0]) && (pixels[pixelBase + 1] == color3ub[1]) && (pixels[pixelBase + 2] == color3ub[2])) { diff --git a/src/shaders/sprite.frag b/src/shaders/sprite.frag index f2ac62e7..4c901e2a 100644 --- a/src/shaders/sprite.frag +++ b/src/shaders/sprite.frag @@ -2,16 +2,21 @@ precision mediump float; uniform float u_fudge; -#ifdef DRAW_MODE_pick -uniform vec4 u_pickColor; -#else // DRAW_MODE_pick +#ifdef DRAW_MODE_silhouette +uniform vec4 u_silhouetteColor; +#else // DRAW_MODE_silhouette # ifdef ENABLE_color uniform float u_color; # endif // ENABLE_color # ifdef ENABLE_brightness uniform float u_brightness; # endif // ENABLE_brightness -#endif // DRAW_MODE_pick +#endif // DRAW_MODE_silhouette + +#ifdef DRAW_MODE_colorMask +uniform vec3 u_colorMask; +uniform float u_colorMaskTolerance; +#endif // DRAW_MODE_colorMask #ifdef ENABLE_fisheye uniform float u_fisheye; @@ -34,7 +39,7 @@ uniform sampler2D u_skin; varying vec2 v_texCoord; -#if !defined(DRAW_MODE_pick) && (defined(ENABLE_color) || defined(ENABLE_brightness)) +#if !defined(DRAW_MODE_silhouette) && (defined(ENABLE_color) || defined(ENABLE_brightness)) // Branchless color conversions based on code from: // http://www.chilliant.com/rgb2hsv.html by Ian Taylor // Based in part on work by Sam Hocevar and Emil Persson @@ -72,7 +77,7 @@ vec3 convertHSL2RGB(vec3 hsl) float c = (1.0 - abs(2.0 * hsl.z - 1.0)) * hsl.y; return (rgb - 0.5) * c + hsl.z; } -#endif // !defined(DRAW_MODE_pick) && (defined(ENABLE_color) || defined(ENABLE_brightness)) +#endif // !defined(DRAW_MODE_silhouette) && (defined(ENABLE_color) || defined(ENABLE_brightness)) const vec2 kCenter = vec2(0.5, 0.5); @@ -140,10 +145,10 @@ void main() discard; } - #ifdef DRAW_MODE_pick - // switch to u_pickColor only AFTER the alpha test - gl_FragColor = u_pickColor; - #else // DRAW_MODE_pick + #ifdef DRAW_MODE_silhouette + // switch to u_silhouetteColor only AFTER the alpha test + gl_FragColor = u_silhouetteColor; + #else // DRAW_MODE_silhouette #if defined(ENABLE_color) || defined(ENABLE_brightness) { @@ -171,8 +176,17 @@ void main() } #endif // defined(ENABLE_color) || defined(ENABLE_brightness) + #ifdef DRAW_MODE_colorMask + vec3 maskDistance = abs(gl_FragColor.rgb - u_colorMask); + vec3 colorMaskTolerance = vec3(u_colorMaskTolerance, u_colorMaskTolerance, u_colorMaskTolerance); + if (any(greaterThan(maskDistance, colorMaskTolerance))) + { + discard; + } + #endif // DRAW_MODE_colorMask + // WebGL defaults to premultiplied alpha gl_FragColor.rgb *= gl_FragColor.a; - #endif // DRAW_MODE_pick + #endif // DRAW_MODE_silhouette }