Picking improvements
- Increased canvas border in demo.html to make it more obvious if future code forgets to compensate for the border. - Fixed picking code to compensate for a border around the canvas. - Completed support for picking with a touch larger than a single point.
This commit is contained in:
parent
c02d2a1f5b
commit
f6b78b2216
@ -5,7 +5,7 @@
|
||||
<title>Scratch WebGL rendering demo</title>
|
||||
</head>
|
||||
<body>
|
||||
<canvas id="scratch-stage" width="10" height="10" style="border:1px dashed black; margin-top: 9px;"></canvas>
|
||||
<canvas id="scratch-stage" width="10" height="10" style="border:3px dashed black"></canvas>
|
||||
<p>
|
||||
<input type="range" id="fudge" style="width:50%" value="90" min="-90" max="270" step="any" oninput="onFudgeChanged(this.value)" onchange="onFudgeChanged(this.value)">
|
||||
</p>
|
||||
@ -33,10 +33,44 @@
|
||||
window.fudge = newValue;
|
||||
}
|
||||
|
||||
// Adapted from code by Simon Sarris: http://stackoverflow.com/a/10450761
|
||||
function getMousePos(event, element) {
|
||||
var stylePaddingLeft = parseInt(document.defaultView.getComputedStyle(element, null)['paddingLeft'], 10) || 0;
|
||||
var stylePaddingTop = parseInt(document.defaultView.getComputedStyle(element, null)['paddingTop'], 10) || 0;
|
||||
var styleBorderLeft = parseInt(document.defaultView.getComputedStyle(element, null)['borderLeftWidth'], 10) || 0;
|
||||
var styleBorderTop = parseInt(document.defaultView.getComputedStyle(element, null)['borderTopWidth'], 10) || 0;
|
||||
|
||||
// Some pages have fixed-position bars at the top or left of the page
|
||||
// They will mess up mouse coordinates and this fixes that
|
||||
var html = document.body.parentNode;
|
||||
var htmlTop = html.offsetTop;
|
||||
var htmlLeft = html.offsetLeft;
|
||||
|
||||
// Compute the total offset. It's possible to cache this if you want
|
||||
var offsetX = 0, offsetY = 0;
|
||||
if (element.offsetParent !== undefined) {
|
||||
do {
|
||||
offsetX += element.offsetLeft;
|
||||
offsetY += element.offsetTop;
|
||||
} while ((element = element.offsetParent));
|
||||
}
|
||||
|
||||
// Add padding and border style widths to offset
|
||||
// Also add the <html> offsets in case there's a position:fixed bar
|
||||
// This part is not strictly necessary, it depends on your styling
|
||||
offsetX += stylePaddingLeft + styleBorderLeft + htmlLeft;
|
||||
offsetY += stylePaddingTop + styleBorderTop + htmlTop;
|
||||
|
||||
// We return a simple javascript object with x and y defined
|
||||
return {
|
||||
x: event.pageX - offsetX,
|
||||
y: event.pageY - offsetY
|
||||
};
|
||||
}
|
||||
|
||||
canvas.onclick = function(event) {
|
||||
var x = event.pageX - canvas.offsetLeft;
|
||||
var y = event.pageY - canvas.offsetTop;
|
||||
var pickID = renderer.pick(x,y);
|
||||
var mousePos = getMousePos(event, canvas);
|
||||
var pickID = renderer.pick(mousePos.x, mousePos.y);
|
||||
console.log('You clicked on ' + (pickID < 0 ? 'nothing' : 'ID# ' + pickID));
|
||||
};
|
||||
|
||||
|
@ -442,13 +442,16 @@ Drawable.color4fFromID = function(id) {
|
||||
* Calculate the ID number represented by the given color. If all components of
|
||||
* the color are zero, the result will be Drawable.NONE; otherwise the result
|
||||
* will be a valid ID.
|
||||
* @param {Array.<int>} rgba An array of [r,g,b,a], each component a byte.
|
||||
* @param {int} r The red value of the color, in the range [0,255].
|
||||
* @param {int} g The green value of the color, in the range [0,255].
|
||||
* @param {int} b The blue value of the color, in the range [0,255].
|
||||
* @param {int} a The alpha value of the color, in the range [0,255].
|
||||
* @returns {int} The ID represented by that color.
|
||||
*/
|
||||
Drawable.color4ubToID = function(rgba) {
|
||||
Drawable.color4ubToID = function(r, g, b, a) {
|
||||
var id;
|
||||
id = (rgba[0] & 255) << 0;
|
||||
id |= (rgba[1] & 255) << 8;
|
||||
id |= (rgba[2] & 255) << 16;
|
||||
id = (r & 255) << 0;
|
||||
id |= (g & 255) << 8;
|
||||
id |= (b & 255) << 16;
|
||||
return id + Drawable.NONE;
|
||||
};
|
||||
|
93
src/index.js
93
src/index.js
@ -44,6 +44,13 @@ function RenderWebGL(
|
||||
this._createQueryBuffers();
|
||||
}
|
||||
|
||||
/**
|
||||
* Maximum touch size for a picking check.
|
||||
* TODO: Figure out a reasonable max size. Maybe this should be configurable?
|
||||
* @type {int[]}
|
||||
*/
|
||||
RenderWebGL.MAX_TOUCH_SIZE = [3, 3];
|
||||
|
||||
/**
|
||||
* Inherit from EventEmitter
|
||||
*/
|
||||
@ -101,7 +108,7 @@ RenderWebGL.prototype.draw = function () {
|
||||
var gl = this._gl;
|
||||
|
||||
twgl.bindFramebufferInfo(gl, null);
|
||||
gl.viewport(0, 0, gl.canvas.clientWidth, gl.canvas.clientHeight);
|
||||
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
|
||||
gl.clearColor.apply(gl, this._backgroundColor);
|
||||
gl.clear(gl.COLOR_BUFFER_BIT);
|
||||
|
||||
@ -245,30 +252,44 @@ RenderWebGL.prototype._createQueryBuffers = function () {
|
||||
{format: gl.DEPTH_STENCIL }
|
||||
];
|
||||
|
||||
// TODO: consider larger sizes for multi-sample touch picking
|
||||
var pickBufferWidth = 1;
|
||||
var pickBufferHeight = 1;
|
||||
|
||||
this._pickBufferInfo = twgl.createFramebufferInfo(
|
||||
gl, attachments, pickBufferWidth, pickBufferHeight);
|
||||
gl, attachments,
|
||||
RenderWebGL.MAX_TOUCH_SIZE[0], RenderWebGL.MAX_TOUCH_SIZE[1]);
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {int} centerX The canvas x coordinate of the picking location.
|
||||
* @param {int} centerY The canvas y coordinate of the picking location.
|
||||
* Detect which sprite, if any, is at the given location.
|
||||
* @param {int} centerX The client x coordinate of the picking location.
|
||||
* @param {int} centerY The client y coordinate of the picking location.
|
||||
* @param {int} touchWidth The client width of the touch event (optional).
|
||||
* @param {int} touchHeight The client height of the touch event (optional).
|
||||
* @returns {int} The ID of the topmost Drawable under the picking location, or
|
||||
* Drawable.NONE if there is no Drawable at that location.
|
||||
*/
|
||||
RenderWebGL.prototype.pick = function (centerX, centerY) {
|
||||
RenderWebGL.prototype.pick = function (
|
||||
centerX, centerY, touchWidth, touchHeight) {
|
||||
var gl = this._gl;
|
||||
|
||||
// TODO: consider larger sizes for multi-sample touch picking
|
||||
var touchWidth = 1;
|
||||
var touchHeight = 1;
|
||||
var pixelLeft = centerX - Math.floor(touchWidth / 2);
|
||||
var pixelRight = centerX + Math.ceil(touchWidth / 2);
|
||||
var pixelTop = centerY - Math.floor(touchHeight / 2);
|
||||
var pixelBottom = centerY + Math.ceil(touchHeight / 2);
|
||||
touchWidth = touchWidth || 1;
|
||||
touchHeight = touchHeight || 1;
|
||||
|
||||
var clientToGLX = gl.canvas.width / gl.canvas.clientWidth;
|
||||
var clientToGLY = gl.canvas.height / gl.canvas.clientHeight;
|
||||
|
||||
centerX *= clientToGLX;
|
||||
centerY *= clientToGLY;
|
||||
touchWidth *= clientToGLX;
|
||||
touchHeight *= clientToGLY;
|
||||
|
||||
touchWidth =
|
||||
Math.max(1, Math.min(touchWidth, RenderWebGL.MAX_TOUCH_SIZE[0]));
|
||||
touchHeight =
|
||||
Math.max(1, Math.min(touchHeight, RenderWebGL.MAX_TOUCH_SIZE[1]));
|
||||
|
||||
var pixelLeft = Math.floor(centerX - Math.floor(touchWidth / 2) + 0.5);
|
||||
var pixelRight = Math.floor(centerX + Math.ceil(touchWidth / 2) + 0.5);
|
||||
var pixelTop = Math.floor(centerY - Math.floor(touchHeight / 2) + 0.5);
|
||||
var pixelBottom = Math.floor(centerY + Math.ceil(touchHeight / 2) + 0.5);
|
||||
|
||||
twgl.bindFramebufferInfo(gl, this._pickBufferInfo);
|
||||
gl.viewport(0, 0, touchWidth, touchHeight);
|
||||
@ -291,8 +312,42 @@ RenderWebGL.prototype.pick = function (centerX, centerY) {
|
||||
|
||||
this._drawExcept(Drawable.DRAW_MODE.pick, null, projection);
|
||||
|
||||
var pixels = new Uint8Array(1 * 1 * 4);
|
||||
gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
|
||||
var pixels = new Uint8Array(touchWidth * touchHeight * 4);
|
||||
gl.readPixels(
|
||||
0, 0, touchWidth, touchHeight, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
|
||||
|
||||
return Drawable.color4ubToID(pixels);
|
||||
// Uncomment this and make a canvas with id="pick-image" to debug picking
|
||||
/*
|
||||
var pickImage = document.getElementById('pick-image');
|
||||
pickImage.width = touchWidth;
|
||||
pickImage.height = touchHeight;
|
||||
var context = pickImage.getContext('2d');
|
||||
var imageData = context.getImageData(0, 0, touchWidth, touchHeight);
|
||||
for (var i = 0, bytes = pixels.length; i < bytes; ++i) {
|
||||
imageData.data[i] = pixels[i];
|
||||
}
|
||||
context.putImageData(imageData, 0, 0);
|
||||
*/
|
||||
|
||||
var hits = {};
|
||||
for (var pixelBase = 0; pixelBase < pixels.length; pixelBase += 4) {
|
||||
var pixelID = Drawable.color4ubToID(
|
||||
pixels[pixelBase],
|
||||
pixels[pixelBase + 1],
|
||||
pixels[pixelBase + 2],
|
||||
pixels[pixelBase + 3]);
|
||||
hits[pixelID] = (hits[pixelID] || 0) + 1;
|
||||
}
|
||||
|
||||
// Bias toward selecting anything over nothing
|
||||
hits[Drawable.NONE] = 0;
|
||||
|
||||
var hit = Drawable.NONE;
|
||||
for (var hitID in hits) {
|
||||
if (hits.hasOwnProperty(hitID) && (hits[hitID] > hits[hit])) {
|
||||
hit = hitID;
|
||||
}
|
||||
}
|
||||
|
||||
return hit | 0;
|
||||
};
|
||||
|
Loading…
x
Reference in New Issue
Block a user