Implement color-touching-color

The `isTouchingColor` function now takes an optional mask parameter. If
provided, only the parts of the Drawable which match the mask color will
be used for the test. For example:
```
  isTouchingColor(4, red, blue);
```
This means "are there any parts of Drawable #4 which are blue and are
touching a red pixel on some other Drawable?"
This commit is contained in:
Christopher Willis-Ford 2016-06-06 16:21:24 -07:00
parent 6b0f3379bf
commit 9f59b42901
3 changed files with 77 additions and 22 deletions

View File

@ -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
*/

View File

@ -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])) {

View File

@ -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
}