Reputation: 7687
I need to read a block of pixels from the picking buffer to check the surrounding points for collisions, like in the picture below:
I am clicking on my canvas
at x
, y
- so I define a square region of, let say, 11x11 pixels, centered at cX
, cY
and then I read from the picking buffer just in one shot starting from x1
, y1
:
var w = 11, h = 11, cX = x, cY = cH - y, rc = ~~(0.5*w), x1 = cX - rc, y1 = cY + rc;
gl.readPixels(x1, y1, w, h, gl.RGBA, gl.UNSIGNED_BYTE, pickBuf);
Then, i am looping from the center of my buffer walking to the outside, following the red arrows, while checking for a positive response inside the picking buffer (collision found):
function readAt(i, pickBuf) {
return pickBuf[i] << 16 | pickBuf[i+1] << 8 | pickBuf[i+2];
}
var l = rc;
while(l--) {
/* Top-Left sector */
var r = rc+(rc-l), c = l, p = r*w + c, i = 4*p;
found = readAt(i, pickBuf) > 0;
/* Top-Right sector */
var r = rc+(rc-l), c = rc+(rc-l), p = r*w + c, i = 4*p;
found = readAt(i, pickBuf) > 0;
/* Bottom-Left sector */
var r = l, c = l, p = r*w + c, i = 4*p;
found = readAt(i, pickBuf) > 0;
/* Bottom-Right sector */
var r = l, c = rc+(rc-l), p = r*w + c, i = 4*p;
found = readAt(i, pickBuf) > 0;
}
The piece of code is working and I am able to pick a geometry, but now I believe I haven't understand correctly the whole coordinate system, because the collision result is always above and left of the point where it should be. Why this happens?
It seems to me that readPixels
should read the bytes starting from the bottom-left corner of the canvas
, to the top-right corner. Is that true, or I am missing something?
I am aware that the gl
coordinate system is Y-inverted against my canvas
, so I am defining the center of the picking buffer at cY = cH - y
and the Y starting point for the picking buffer at y1 = cY + rc
, but anyway this is giving me wrong results during my walk from the center to the outside of my picking square box. Where goes my interpretation of the coordinate system wrong?
Upvotes: 1
Views: 481
Reputation: 8123
It seems to me that readPixels should read the bytes starting from the bottom-left corner of the canvas, to the top-right corner. Is that true, or i am missing something?
Thats true
anyway this is giving me wrong results during my walk
With the exception of the middle pixel not being checked, it works for me, I suspect the issue to be elsewhere in your code.
// picking
c.addEventListener('click',function (e) {
var
cX = e.offsetX, // x within canvas
cY = ctx.drawingBufferHeight - e.offsetY // y within canvas inverted, drawingbufferheight is same as canvas height
;
var
area = 11,
halfArea = (area/2)|0,
sX = cX-halfArea,
sY = cY-halfArea
;
console.log('Picking from ', sX, sY, area,area);
// read data
var data = new Uint8Array(area*area*4);
ctx.readPixels(sX,sY,area,area,ctx.RGBA,ctx.UNSIGNED_BYTE,data);
// draw cross
(function (pickBuf,rc,w) {
function readAt(i, pickBuf) {
return pickBuf[i]=pickBuf[i+1]=pickBuf[i+2]=0;
}
// this is your unmodified loop
var l = rc;
while(l--) {
/* Top-Left sector */
var r = rc+(rc-l), c = l, p = r*w + c, i = 4*p;
found = readAt(i, pickBuf) > 0;
/* Top-Right sector */
var r = rc+(rc-l), c = rc+(rc-l), p = r*w + c, i = 4*p;
found = readAt(i, pickBuf) > 0;
/* Bottom-Left sector */
var r = l, c = l, p = r*w + c, i = 4*p;
found = readAt(i, pickBuf) > 0;
/* Bottom-Right sector */
var r = l, c = rc+(rc-l), p = r*w + c, i = 4*p;
found = readAt(i, pickBuf) > 0;
}
})(data,halfArea,area);
// create preview
var octx = document.createElement('canvas').getContext('2d');
octx.canvas.classList.add('preview');
octx.canvas.width = octx.canvas.height = area;
var idata = octx.createImageData(area,area);
idata.data.set(data);
octx.putImageData(idata,0,0);
ctx.canvas.parentElement.insertBefore(octx.canvas,ctx.canvas);
});
// context setup
var ctx = c.getContext('webgl',{preserveDrawingBuffer:true});
ctx.canvas.width = ctx.canvas.height = 256;
ctx.viewport(0,0,256,256);
// shader setup
var vs=ctx.createShader(ctx.VERTEX_SHADER),fs=ctx.createShader(ctx.FRAGMENT_SHADER),prg=ctx.createProgram();
ctx.shaderSource(vs,`
attribute vec2 vPos;
void main(){gl_Position=vec4(vPos,0,1);}
`);
ctx.shaderSource(fs,`
precision highp float;
void main(){
vec2 p=(gl_FragCoord.xy/256.)*2.-1.;
gl_FragColor=vec4(sign(p),sign(-p.r),1);
}
`);
ctx.compileShader(vs);
ctx.compileShader(fs);
ctx.attachShader(prg,vs);
ctx.attachShader(prg,fs);
ctx.linkProgram(prg);
// screenspace setup
var ssq = ctx.createBuffer();
ctx.bindBuffer(ctx.ARRAY_BUFFER,ssq);
ctx.bufferData(ctx.ARRAY_BUFFER,new Float32Array([-1,-1,1,-1,1,1,-1,1]),ctx.STATIC_DRAW);
ctx.vertexAttribPointer(0,2,ctx.FLOAT,false,0,0);
ctx.enableVertexAttribArray(0);
// draw
ctx.useProgram(prg);
ctx.drawArrays(ctx.TRIANGLE_FAN,0,4);
h2{text-align:center;font-family: Arial,sans-serif;}
#c{display:block;margin:0 auto;outline:1px solid red;cursor:crosshair;}
.preview {
width: 64px;
height: 64px;
outline: none;
display:inline;
image-rendering: pixelated;
transform:scaleY(-1);
margin: 10px;
}
<h2>Click 2 Pick</h2>
<canvas id="c"></canvas>
Upvotes: 1