I want to pass canvas mouse coordinates to a function that interactively generates a circle with the mouse's coordinates as its center. Therefore, I'm using the following function to normalize:
var mousePositionX = (2*ev.clientX/canvas.width) - 1;
var mousePositionY = (2*ev.clientY/(canvas.height*-1)) + 1;
However, this works fine only for the screen center. When moving the mouse around the cursor is not located in the circle's center any more: see the picture here
The farther the mouse cursor removes from the screen center, the more it is dislocated from the circle's center. Here's some relevant code:
HTML
body {
border: 0;
margin: 0;
}
/* make the canvas the size of the viewport */
canvas {
width: 100vw;
height: 100vh;
display: block;
}
...
<body onLoad="main()">
<canvas id="glContext"></canvas>
</body>
SHADER
<script id="vShaderCircle" type="notjs">
attribute vec4 a_position;
uniform mat4 u_viewMatrix;
void main(){
gl_Position = u_viewMatrix * a_position;
}
</script>
JS
function main(){
// PREPARING CANVAS AND WEBGL-CONTEXT
var canvas = document.getElementById("glContext");
var gl_Original = initWebGL(canvas);
var gl = WebGLDebugUtils.makeDebugContext(gl_Original);
resize(canvas);
gl.viewport(0, 0, canvas.width, canvas.height);
// ----------------------------------
...
// MATRIX SETUP
var viewMatrix = new Matrix4();
viewMatrix.setPerspective(30, canvas.width/canvas.height, 1, 100);
viewMatrix.lookAt(0, 0, 5, 0, 0, 0, 0, 1, 0);
// ----------------------------------
canvas.addEventListener("mousemove", function(){stencilTest(event)});
function stencilTest(ev){
var mousePositionX = (2*ev.clientX/canvas.width) - 1;
var mousePositionY = (2*ev.clientY/(canvas.height*(-1))) + 1;
...
...
drawCircle(..., mousePositionX, mousePositionY, viewMatrix);
...
drawCube(...);
}
}
How can I resolve this?
I want to pass canvas mouse coordinates to a function that interactively generates a circle with the mouse's coordinates as its center. Therefore, I'm using the following function to normalize:
var mousePositionX = (2*ev.clientX/canvas.width) - 1;
var mousePositionY = (2*ev.clientY/(canvas.height*-1)) + 1;
However, this works fine only for the screen center. When moving the mouse around the cursor is not located in the circle's center any more: see the picture here
The farther the mouse cursor removes from the screen center, the more it is dislocated from the circle's center. Here's some relevant code:
HTML
body {
border: 0;
margin: 0;
}
/* make the canvas the size of the viewport */
canvas {
width: 100vw;
height: 100vh;
display: block;
}
...
<body onLoad="main()">
<canvas id="glContext"></canvas>
</body>
SHADER
<script id="vShaderCircle" type="notjs">
attribute vec4 a_position;
uniform mat4 u_viewMatrix;
void main(){
gl_Position = u_viewMatrix * a_position;
}
</script>
JS
function main(){
// PREPARING CANVAS AND WEBGL-CONTEXT
var canvas = document.getElementById("glContext");
var gl_Original = initWebGL(canvas);
var gl = WebGLDebugUtils.makeDebugContext(gl_Original);
resize(canvas);
gl.viewport(0, 0, canvas.width, canvas.height);
// ----------------------------------
...
// MATRIX SETUP
var viewMatrix = new Matrix4();
viewMatrix.setPerspective(30, canvas.width/canvas.height, 1, 100);
viewMatrix.lookAt(0, 0, 5, 0, 0, 0, 0, 1, 0);
// ----------------------------------
canvas.addEventListener("mousemove", function(){stencilTest(event)});
function stencilTest(ev){
var mousePositionX = (2*ev.clientX/canvas.width) - 1;
var mousePositionY = (2*ev.clientY/(canvas.height*(-1))) + 1;
...
...
drawCircle(..., mousePositionX, mousePositionY, viewMatrix);
...
drawCube(...);
}
}
How can I resolve this?
Share Improve this question edited Feb 22, 2017 at 14:01 Muad asked Feb 18, 2017 at 0:38 MuadMuad 571 gold badge1 silver badge6 bronze badges 3- 3 You probably need to post some code. You mentioned you have a view matrix. If you have a view matrix in some ment in whic case you aren't working in WebGL's coordinate system which is clip space. Instead you're working in some other coordinate system you defined and we can't help you convert the mouse coordinates to that space unless you show us teh codez – user128511 Commented Feb 18, 2017 at 13:28
-
@gman So I provided a bit more code now. Can you figure out the problem by that? The individual functions (e.g. matrix functions) do what they're supposed to do. I tried your
getRelativeMousePosition()
functions as well, but without success... – Muad Commented Feb 18, 2017 at 15:26 - updated my answer – user128511 Commented Feb 18, 2017 at 15:47
3 Answers
Reset to default 14This is actually a far more plicated issue than it sounds. Is your canvas's display size the same as its drawing buffer? Do you have a border on your canvas?
Here's some code that will give you a canvas relative pixel coordinate assuming you don't have a border or any padding on your canvas.
function getRelativeMousePosition(event, target) {
target = target || event.target;
var rect = target.getBoundingClientRect();
return {
x: event.clientX - rect.left,
y: event.clientY - rect.top,
}
}
// assumes target or event.target is canvas
function getNoPaddingNoBorderCanvasRelativeMousePosition(event, target) {
target = target || event.target;
var pos = getRelativeMousePosition(event, target);
pos.x = pos.x * target.width / target.clientWidth;
pos.y = pos.y * target.height / target.clientHeight;
return pos;
}
To convert that to a WebGL coordinate
var pos = getRelativeMousePosition(event, target);
const x = pos.x / gl.canvas.width * 2 - 1;
const y = pos.y / gl.canvas.height * -2 + 1;
Working example:
function getRelativeMousePosition(event, target) {
target = target || event.target;
var rect = target.getBoundingClientRect();
return {
x: event.clientX - rect.left,
y: event.clientY - rect.top,
}
}
// assumes target or event.target is canvas
function getNoPaddingNoBorderCanvasRelativeMousePosition(event, target) {
target = target || event.target;
var pos = getRelativeMousePosition(event, target);
pos.x = pos.x * target.width / target.clientWidth;
pos.y = pos.y * target.height / target.clientHeight;
return pos;
}
const vs = `
attribute vec4 position;
void main() {
gl_Position = position;
gl_PointSize = 20.;
}
`;
const fs = `
void main() {
gl_FragColor = vec4(1,0,1,1);
}
`;
const gl = document.querySelector("canvas").getContext("webgl");
// piles and links shaders and assigns position to location
const program = twgl.createProgramFromSources(gl, [vs, fs]);
const positionLoc = gl.getAttribLocation(program, "position");
window.addEventListener('mousemove', e => {
const pos = getNoPaddingNoBorderCanvasRelativeMousePosition(e, gl.canvas);
// pos is in pixel coordinates for the canvas.
// so convert to WebGL clip space coordinates
const x = pos.x / gl.canvas.width * 2 - 1;
const y = pos.y / gl.canvas.height * -2 + 1;
gl.clear(gl.COLOR_BUFFER_BIT);
gl.useProgram(program);
// only drawing a single point so no need to use a buffer
gl.vertexAttrib2f(positionLoc, x, y);
gl.drawArrays(gl.POINTS, 0, 1);
});
canvas {
display: block;
width: 400px;
height: 100px;
}
div {
display: inline-block;
border: 1px solid black;
}
<div><canvas></canvas></div>
<p>move the mouse over the canvas</p>
<script src="https://twgljs/dist/3.x/twgl-full.min.js"></script>
Notice there's no matrices involved. If you're using matrices then you've defined your own space, not WebGL's space which is always clip space. In that case you either need to multiply by the inverse of your matrices and pick whatever Z value you want between -1 and +1. That way when your position is multiplied by the matrices used in your shader it will reverse the position back into the correct webgl clip space coordinates. Or, you need to get rid of your matrices or set them in the identity.
Here's an example, note I don't have/know your math library so you'll have to translate to your's
function getRelativeMousePosition(event, target) {
target = target || event.target;
var rect = target.getBoundingClientRect();
return {
x: event.clientX - rect.left,
y: event.clientY - rect.top,
}
}
// assumes target or event.target is canvas
function getNoPaddingNoBorderCanvasRelativeMousePosition(event, target) {
target = target || event.target;
var pos = getRelativeMousePosition(event, target);
pos.x = pos.x * target.width / target.clientWidth;
pos.y = pos.y * target.height / target.clientHeight;
return pos;
}
const vs = `
attribute vec4 position;
uniform mat4 matrix;
void main() {
gl_Position = matrix * position;
}
`;
const fs = `
void main() {
gl_FragColor = vec4(1,0,0,1);
}
`;
const m4 = twgl.m4;
const gl = document.querySelector("canvas").getContext("webgl");
const programInfo = twgl.createProgramInfo(gl, [vs, fs]);
const bufferInfo = twgl.primitives.createSphereBufferInfo(gl, .5, 12, 8);
window.addEventListener('mousemove', e => {
const pos = getNoPaddingNoBorderCanvasRelativeMousePosition(e, gl.canvas);
// pos is in pixel coordinates for the canvas.
// so convert to WebGL clip space coordinates
const x = pos.x / gl.canvas.width * 2 - 1;
const y = pos.y / gl.canvas.height * -2 + 1;
// use a projection and view matrix
const projection = m4.perspective(
30 * Math.PI / 180,
gl.canvas.clientWidth / gl.canvas.clientHeight,
1,
100);
const camera = m4.lookAt([0, 0, 15], [0, 0, 0], [0, 1, 0]);
const view = m4.inverse(camera);
const viewProjection = m4.multiply(projection, view);
// pick a clipsace Z value between -1 and 1
// we'll zNear to zFar and convert back to clip space
const viewZ = -5; // 5 units back from the camera
const clip = m4.transformPoint(projection, [0, 0, viewZ]);
const z = clip[2];
// pute the world space position needed to put the sphere
// under the cursor at this clipspace position
const inverseViewProjection = m4.inverse(viewProjection);
const worldPos = m4.transformPoint(inverseViewProjection, [x, y, z]);
// add that world position to our matrix
const mat = m4.translate(viewProjection, worldPos);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.useProgram(programInfo.program);
twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
twgl.setUniforms(programInfo, {
matrix: mat,
});
gl.drawElements(gl.LINES, bufferInfo.numElements, gl.UNSIGNED_SHORT, 0);
});
canvas {
display: block;
width: 400px;
height: 100px;
}
div {
display: inline-block;
border: 1px solid black;
}
<div><canvas></canvas></div>
<p>move the mouse over the canvas</p>
<script src="https://twgljs/dist/3.x/twgl-full.min.js"></script>
Also note I deliberately made the canvas's display size not match it's drawing buffer size to show the math works.
Just bind your mousemove event to the canvas itself and use offsetX and offsetY
var mouseX = (e.offsetX / canvas.clientWidth)*2-1;
var mouseY = ((canvas.clientHeight - e.offsetY) / canvas.clientHeight)*2-1;
Note that this all depends on what transforms you do in your shaders.
Assuming this is called in a mousemove
callback event and canvas
is defined as a proper reference to an HTML CANVAS
element, the relative position of the mouse pointer to the canvas space should be:
var rect = gl.canvas.getBoundingClientRect();
var mousePositionX = ev.clientX - rect.left;
var mousePositionY = ev.clientY - rect.top;
To convert from pixel coordinates to WebGL coordinate system:
var rect = gl.canvas.getBoundingClientRect();
var x = (ev.clientX - rect.left) / canvas.width * 2 - 1;
var y = (ev.clientY - rect.top) / canvas.height * -2 + 1;