我在画布元素上渲染了一个webgl。渲染后,我希望允许用户使用鼠标在其上绘画(例如,rect)。由于getContext第二次不起作用,因此我在webgl画布上添加了另一个透明画布,我想用鼠标在该透明画布上绘制一个矩形。问题在于,mousedown事件中的坐标与上下文相关元素非常不同
我的画布如下
<div id="container">
<canvas id="webglCanvas" tabindex='1'></canvas>
<canvas id="transCanvas" tabindex='1'></canvas>
</div>
获取上下文
var $canvas1 = document.getElementById('transCanvas');
var ctx = $canvas1.getContext("2d");
TransCanvas的鼠标按下事件。请注意,在按下鼠标事件时,我已经对该区域进行了硬编码。稍后,我将通过鼠标移动等操作。在画布上可以正常工作,并且可以在屏幕上看到矩形。但是鼠标坐标,例如e.clientX和e.clientY处于隐藏状态,并离开了屏幕?
function handleCanvasMouseMove(e) {
ctx.beginPath();
ctx.fillStyle = '#F30';
ctx.fillRect(75, 75, 75, 75);
}
答案 0 :(得分:0)
请记住,您要将每个轴上范围为[-1..1]的NormalizedDeviceCoords转换为屏幕上的位置。您应用的所有转换都将模型空间vert放在一个以原点为中心的大小为2的立方体中。
所以...我想您也想在相同的空间找回鼠标坐标。如果是这样,则只需构造一个矩阵,然后将屏幕空间位置乘以该矩阵即可得出[-1..1]
范围内的x,y。过去我做过类似的事情时,我使用了以下一系列转换:
function makeClipToPixMat(width,height)
{
// flip the Y
let s1 = mat4.scaling(1,-1,1);
// translate so space is now [0..2]
let t1 = mat4.translation(1,1,0);
// scale so space is now [0..width], [0..height]
let s2 = mat4.scaling(width/2,height/2,1);
// s1, then t1, then s2 are applied to the input coords
let result = mat4.matrixByMatrix(s1,t1);
result = result.multiply(s2);
return result;
}
但是从名称中您会注意到,这是一个错误方向的映射。我们希望将屏幕坐标映射到NDC,但是此代码却相反。那怎么办简单-颠倒矩阵或确定所需的一系列变换,然后构建一个矩阵即可一次完成所有操作。这是一个足够简单的转换,因此矩阵求逆似乎是执行如此简单操作的一种极其昂贵的方法。
实际上,这是我使用的功能。反转也可以正常工作,并且可以在运行时间上节省代码大小。
function pixelsToClipspace(width,height)
{
let scaleMat = mat4.scaling( 1/(width/2), -1/(height/2), 1);
let translateMat = mat4.translation(-width/2, -height/2, 0); //.transpose();
let mat = mat4.matrixByMatrix(translateMat, scaleMat);
return mat;
}
由于我目前有一段时间,因此为您整理了一个快速演示。不好意思,vec4和矩阵有380行代码,而演示仅约35行。 :laughs:也就是说,它完美地说明了矩阵.inverse()函数的昂贵和复杂。
最后:请注意,对于所包含但尚未使用的任何代码的准确性,我不做任何声明。这样的锻炼对我们每个人都有益。您获得了一些理解,我得到了更多的调试测试用例。 :)矩阵是大列矩阵(就像所有好的GL矩阵一样)
"use strict";
window.addEventListener('load', onLoaded, false);
let s2Clip = null;
function onLoaded(evt)
{
let can = document.querySelector('canvas');
s2Clip = pixelsToClipspace(can.clientWidth, can.clientHeight); // use clienWidth/clientHeight to avoid CSS scaling problems
can.addEventListener('mousemove', onMouse, false);
}
function onMouse(evt)
{
var rawPos = new vec4(evt.offsetX, evt.offsetY, 0, 1);
var trPos = s2Clip.timesVector(rawPos);
document.getElementById('rawMouse').innerText = `${rawPos.x}, ${rawPos.y}`
document.getElementById('transMouse').innerText = `${trPos.x.toFixed(2)}, ${trPos.y.toFixed(2)}`
}
function pixelsToClipspace(width,height)
{
let scaleMat = mat4.scaling( 1/(width/2), -1/(height/2), 1);
let translateMat = mat4.translation(-width/2, -height/2, 0); //.transpose();
let mat = mat4.matrixByMatrix(translateMat, scaleMat);
return mat;
}
// </script>
// <script origSrc='vector.js'>
class vec4
{
// w=0 for dir (cant translate), w=1 for pos (can)
constructor(x=0,y=0,z=0,w=0){this.values = [x,y,z,w];}
clone(){ return new vec4(this.x,this.y,this.z,this.w); }
get x(){return this.values[0];}
get y(){return this.values[1];}
get z(){return this.values[2];}
get w(){return this.values[3];}
set x(x){this.values[0]=x;}
set y(y){this.values[1]=y;}
set z(z){this.values[2]=z;}
set w(w){this.values[3]=w;}
get length(){return Math.hypot( ...this.values ); }
normalize(){ var l = this.length; if (l>1e-6) {this.x/=l;this.y/=l;this.z/=l;this.w/=l;} return this;}
scaleBy(scalar){this.x*=scalar;this.y*=scalar;this.z*=scalar;this.w*=scalar;return this;}
divBy(scalar){this.x/=scalar;this.y/=scalar;this.z/=scalar;this.w/=scalar;return this;}
add(other){return new vec4(this.x+other.x, this.y+other.y, this.z+other.z, this.w+other.w);}
sub(other){return new vec4(this.x-other.x, this.y-other.y, this.z-other.z, this.w-other.w);}
get xyz(){return new vec3(this.x,this.y,this.z);}
toStringN(n){return `[${pad(this.x,n)}, ${pad(this.y,n)}, ${pad(this.z,n)}, ${pad(this.w,n)}]`;}
timesMatrix(matrix)
{
let m0 = matrix.getCol(0), m1 = matrix.getCol(1), m2 = matrix.getCol(2), m3 = matrix.getCol(3);
return new vec4(
(m0.x*this.x) + (m1.x*this.y) + m2.x*this.z + m3.x*this.w,
(m0.y*this.x) + (m1.y*this.y) + m2.y*this.z + m3.y*this.w,
(m0.z*this.x) + (m1.z*this.y) + m2.z*this.z + m3.z*this.w,
(m0.w*this.x) + (m1.w*this.y) + m2.w*this.z + m3.w*this.w
);
}
vecByMatrix(m) /// operator * (matrix, vector)
{
let mc0 = m.getCol(0), mc1=m.getCol(1), mc2=m.getCol(2), mc3=m.getCol(3);
return new vec4(
(mc0.x * this.x) + (mc1.x * this.y) + (mc2.x * this.z) + (mc3.x * this.w),
(mc0.y * this.x) + (mc1.y * this.y) + (mc2.y * this.z) + (mc3.y * this.w),
(mc0.z * this.x) + (mc1.z * this.y) + (mc2.z * this.z) + (mc3.z * this.w),
(mc0.w * this.x) + (mc1.w * this.y) + (mc2.w * this.z) + (mc3.w * this.w),
);
}
matrixByVec(m) /// operator * (vector, matrix)
{
let mCol0 = m.getCol(0), mCol1=m.getCol(1), mCol2=m.getCol(2), mCol3=m.getCol(3);
return new vec4(
this.x*mCol0.x + this.y*mCol0.y + this.z*mCol0.z + this.w*mCol0.w,
this.x*mCol1.x + this.y*mCol1.y + this.z*mCol1.z + this.w*mCol1.w,
this.x*mCol2.x + this.y*mCol2.y + this.z*mCol2.z + this.w*mCol2.w,
this.x*mCol3.x + this.y*mCol3.y + this.z*mCol3.z + this.w*mCol3.w
);
}
}
class mat4
{
constructor(xVec4=new vec4(1,0,0,0), yVec4=new vec4(0,1,0,0), zVec4=new vec4(0,0,1,0), wVec4=new vec4(0,0,0,1) )
{
this.columns = [
xVec4.clone(),
yVec4.clone(),
zVec4.clone(),
wVec4.clone()
];
}
getCol(colIndex) {return this.columns[colIndex];}
setCol(colIndex, newVec) {this.columns[colIndex] = newVec.clone();}
setIdentity()
{
let x=new vec4(1,0,0,0);
let y=new vec4(0,1,0,0);
let z=new vec4(0,0,1,0);
let w=new vec4(0,0,0,1);
this.setCol(0,x);
this.setCol(0,y);
this.setCol(0,z);
this.setCol(0,w);
return this;
}
static clone(other)
{
var result = new mat4( other.columns[0], other.columns[1], other.columns[2], other.columns[3] );
return result;
}
clone()
{
return mat4.clone(this);
}
static scaling(sx=1,sy=1,sz=1)
{
let x = new vec4(sx,0,0,);
let y = new vec4(0,sy,0,);
let z = new vec4(0,0,sz,);
let w = new vec4(0,0,0,1);
return new mat4(x,y,z,w);
}
static translation(tx=0,ty=0,tz=0)
{
let X = new vec4(1,0,0,tx);
let Y = new vec4(0,1,0,ty);
let Z = new vec4(0,0,1,tz);
let W = new vec4(0,0,0,1);
return new mat4(X,Y,Z,W);
}
static matrixByMatrix(m1, m2)
{
let mCol0 = m2.getCol(0), mCol1=m2.getCol(1), mCol2=m2.getCol(2), mCol3=m2.getCol(3);
let X = mCol0.vecByMatrix(m1);
let Y = mCol1.vecByMatrix(m1);
let Z = mCol2.vecByMatrix(m1);
let W = mCol3.vecByMatrix(m1);
return new mat4(X,Y,Z,W);
}
static matTimeMat(m1,m2)
{
let mc0=m2.getCol(0),mc1=m2.getCol(1),mc2=m2.getCol(2),mc3=m2.getCol(3);
let x = m1.timesVector(mc0);
let y = m1.timesVector(mc1);
let z = m1.timesVector(mc2);
let w = m1.timesVector(mc3);
return new mat4(x,y,z,w);
}
multiply(other,shouldPrepend=false)
{
var a=this,b=other,c;
if (shouldPrepend===true){a=other;b=this;}
c = mat4.matrixByMatrix(a,b);
this.columns = c.columns.slice();
return this;
}
translate(tx=0,ty=0,tz=0)
{
return this.multiply( mat4.translation(tx,ty,tz) );
}
setScale(sx=1,sy=1,sz=1)
{
let x = new vec4(sx,0,0,0);
let y = new vec4(0,sy,0,0);
let z = new vec4(0,0,sz,0);
let w = new vec4(0,0,0,1);
let tmp = new mat4(x,y,z,w);
this.columns = tmp.columns.slice();
return this;
}
setTrans(tx=0,ty=0,tz=0)
{
let x = new vec4( 1, 0, 0, 0);
let y = new vec4( 0, 1, 0, 0);
let z = new vec4( 0, 0, 1, 0);
let w = new vec4( tx, ty, tz, 1);
var tmp = new mat4(x,y,z,w);
this.columns = tmp.columns.slice();
return this;
}
setRotX(degrees)
{
let cosa = Math.cos(degrees * 3.141/180);
let sina = Math.sin(degrees * 3.141/180);
let x = new vec4(1,0,0,0);
let y = new vec4(0,cosa,sina,0)
let z = new vec4(0,-sina,cosa,0);
let w = new vec4(0,0,0,1);
let tmp = new mat4(x,y,z,w);
this.columns = tmp.columns.slice();
return this;
}
setRotY(degrees)
{
let cosa = Math.cos(degrees * 3.141/180);
let sina = Math.sin(degrees * 3.141/180);
let x = new vec4( cosa, 0,-sina,0);
let y = new vec4( 0, 1, 0, 0)
let z = new vec4( sina, 0,cosa, 0);
let w = new vec4( 0, 0, 0, 1);
let tmp = new mat4(x,y,z,w);
this.columns = tmp.columns.slice();
return this;
}
setRotZ(degrees)
{
let cosa = Math.cos(degrees * 3.141/180);
let sina = Math.sin(degrees * 3.141/180);
let x = new vec4(cosa,sina,0,0);
let y = new vec4(-sina,cosa,0,0)
let z = new vec4(0,0,1,0);
let w = new vec4(0,0,0,1);
let tmp = new mat4(x,y,z,w);
this.columns = tmp.columns.slice();
return this;
}
scaleEach(sX=1,sY=1,sZ=1,shouldPrepend=false)
{
let tmp = new mat4();
let X = tmp.getCol(0);
X.x = sX;
tmp.setCol(0,X);
let Y = tmp.getCol(1);
Y.y = sY;
tmp.setCol(1,Y);
let Z = tmp.getCol(2);
Z.z = sZ;
tmp.setCol(2,Z);
return this.multiply(tmp, shouldPrepend);
//return this;
}
scaleAll(sXYZ, shouldPrepend=false)
{
return this.scaleEach(sXYZ,sXYZ,sXYZ,shouldPrepend);
//return this;
}
/*
translate(tX=0, tY=0, tZ=0, shouldPrepend=false)
{
let tmp = new mat4();
let W = tmp.getCol(3);
W.x = tX;
W.y = tY;
W.z = tZ;
tmp.setCol(3,W);
return this.multiply(tmp, shouldPrepend);
}
*/
timesVector(vector)
{
let m0=this.getCol(0), m1=this.getCol(1), m2=this.getCol(2), m3=this.getCol(3);
return new vec4(
(vector.x*m0.x) + (vector.y*m0.y) + (vector.z*m0.z) + (vector.w*m0.w),
(vector.x*m1.x) + (vector.y*m1.y) + (vector.z*m1.z) + (vector.w*m1.w),
(vector.x*m2.x) + (vector.y*m2.y) + (vector.z*m2.z) + (vector.w*m2.w),
(vector.x*m3.x) + (vector.y*m3.y) + (vector.z*m3.z) + (vector.w*m3.w)
);
}
toString()
{
let result = '', row=0,col=0;
result = `[ ${this.getCol(0).x}, ${this.getCol(1).x}, ${this.getCol(2).x}, ${this.getCol(3).x} ]\n`;
result += `[ ${this.getCol(0).y}, ${this.getCol(1).y}, ${this.getCol(2).y}, ${this.getCol(3).y} ]\n`;
result += `[ ${this.getCol(0).z}, ${this.getCol(1).z}, ${this.getCol(2).z}, ${this.getCol(3).z} ]\n`;
result += `[ ${this.getCol(0).w}, ${this.getCol(1).w}, ${this.getCol(2).w}, ${this.getCol(3).w} ]\n`;
return result;
}
toStrN(n)
{
return this.toStringN(n);
}
toStringN(nDigs)
{
let result = '';
let xVec=this.getCol(0).clone(),
yVec=this.getCol(1).clone(),
zVec=this.getCol(2).clone(),
wVec=this.getCol(3).clone();
let vs=[xVec,yVec,zVec,wVec];
for (var i=0,n=vs.length; i<n; i++)
{
vs[i].x = pad(vs[i].x, nDigs);
vs[i].y = pad(vs[i].y, nDigs);
vs[i].z = pad(vs[i].z, nDigs);
vs[i].w = pad(vs[i].w, nDigs);
}
result = `[ ${xVec.x}, ${yVec.x}, ${zVec.x}, ${wVec.x} ]\n`;
result += `[ ${xVec.y}, ${yVec.y}, ${zVec.y}, ${wVec.y} ]\n`;
result += `[ ${xVec.z}, ${yVec.z}, ${zVec.z}, ${wVec.z} ]\n`;
result += `[ ${xVec.w}, ${yVec.w}, ${zVec.w}, ${wVec.w} ]\n`;
return result;
}
asRows(nDigs=2)
{
let result = '',xVec=this.getCol(0),yVec=this.getCol(1),zVec=this.getCol(2),wVec=this.getCol(3);
result = `[${xVec.x.toFixed(nDigs)}, ${xVec.y.toFixed(nDigs)}, ${xVec.z.toFixed(nDigs)}, ${xVec.w.toFixed(nDigs)}]\n`;
result += `[${yVec.x.toFixed(nDigs)}, ${yVec.y.toFixed(nDigs)}, ${yVec.z.toFixed(nDigs)}, ${yVec.w.toFixed(nDigs)}]\n`;
result += `[${zVec.x.toFixed(nDigs)}, ${zVec.y.toFixed(nDigs)}, ${zVec.z.toFixed(nDigs)}, ${zVec.w.toFixed(nDigs)}]\n`;
result += `[${wVec.x.toFixed(nDigs)}, ${wVec.y.toFixed(nDigs)}, ${wVec.z.toFixed(nDigs)}, ${wVec.w.toFixed(nDigs)}]\n`;
return result;
}
transpose()
{
let X=this.getCol(0), Y=this.getCol(1), Z=this.getCol(2), W=this.getCol(3);
let tmp = new mat4(
new vec4(X.x,Y.x,Z.x,W.x),
new vec4(X.y,Y.y,Z.y,W.y),
new vec4(X.z,Y.z,Z.z,W.z),
new vec4(X.w,Y.w,Z.w,W.w),
);
this.setCol(0,X);
this.setCol(1,Y);
this.setCol(2,Z);
this.setCol(3,W);
return tmp; //this.copy(tmp);
}
inverse()
{
let X = this.getCol(0), Y = this.getCol(1), Z = this.getCol(2), W = this.getCol(3);
let m00=X.x, m01=X.y, m02=X.z, m03=X.w,
m10=Y.x, m11=Y.y, m12=Y.z, m13=Y.w,
m20=Z.x, m21=Z.y, m22=Z.z, m23=Z.w,
m30=W.x, m31=W.y, m32=W.z, m33=W.w;
let tmp_0=m22*m33, tmp_1=m32*m23, tmp_2=m12*m33,
tmp_3=m32*m13, tmp_4=m12*m23, tmp_5=m22*m13,
tmp_6=m02*m33, tmp_7=m32*m03, tmp_8=m02*m23,
tmp_9=m22*m03, tmp_10=m02*m13,tmp_11=m12*m03,
tmp_12=m20*m31,tmp_13=m30*m21,tmp_14=m10*m31,
tmp_15=m30*m11,tmp_16=m10*m21,tmp_17=m20*m11,
tmp_18=m00*m31,tmp_19=m30*m01,tmp_20=m00*m21,
tmp_21=m20*m01,tmp_22=m00*m11,tmp_23=m10*m01;
var t0 = (tmp_0 * m11 + tmp_3 * m21 + tmp_4 * m31) - (tmp_1 * m11 + tmp_2 * m21 + tmp_5 * m31);
var t1 = (tmp_1 * m01 + tmp_6 * m21 + tmp_9 * m31) - (tmp_0 * m01 + tmp_7 * m21 + tmp_8 * m31);
var t2 = (tmp_2 * m01 + tmp_7 * m11 + tmp_10 * m31) - (tmp_3 * m01 + tmp_6 * m11 + tmp_11 * m31);
var t3 = (tmp_5 * m01 + tmp_8 * m11 + tmp_11 * m21) - (tmp_4 * m01 + tmp_9 * m11 + tmp_10 * m21);
var d = 1.0 / (m00 * t0 + m10 * t1 + m20 * t2 + m30 * t3);
let Xo = new vec4(d*t0, d*t1, d*t2, d*t3);
// d * t0,
// d * t1,
// d * t2,
// d * t3,
let Yo = new vec4(
d * ((tmp_1 * m10 + tmp_2 * m20 + tmp_5 * m30) - (tmp_0 * m10 + tmp_3 * m20 + tmp_4 * m30)),
d * ((tmp_0 * m00 + tmp_7 * m20 + tmp_8 * m30) - (tmp_1 * m00 + tmp_6 * m20 + tmp_9 * m30)),
d * ((tmp_3 * m00 + tmp_6 * m10 + tmp_11 * m30) - (tmp_2 * m00 + tmp_7 * m10 + tmp_10 * m30)),
d * ((tmp_4 * m00 + tmp_9 * m10 + tmp_10 * m20) - (tmp_5 * m00 + tmp_8 * m10 + tmp_11 * m20))
);
let Zo = new vec4(
d * ((tmp_12 * m13 + tmp_15 * m23 + tmp_16 * m33) - (tmp_13 * m13 + tmp_14 * m23 + tmp_17 * m33)),
d * ((tmp_13 * m03 + tmp_18 * m23 + tmp_21 * m33) - (tmp_12 * m03 + tmp_19 * m23 + tmp_20 * m33)),
d * ((tmp_14 * m03 + tmp_19 * m13 + tmp_22 * m33) - (tmp_15 * m03 + tmp_18 * m13 + tmp_23 * m33)),
d * ((tmp_17 * m03 + tmp_20 * m13 + tmp_23 * m23) - (tmp_16 * m03 + tmp_21 * m13 + tmp_22 * m23))
);
let Wo = new vec4(
d * ((tmp_14 * m22 + tmp_17 * m32 + tmp_13 * m12) - (tmp_16 * m32 + tmp_12 * m12 + tmp_15 * m22)),
d * ((tmp_20 * m32 + tmp_12 * m02 + tmp_19 * m22) - (tmp_18 * m22 + tmp_21 * m32 + tmp_13 * m02)),
d * ((tmp_18 * m12 + tmp_23 * m32 + tmp_15 * m02) - (tmp_22 * m32 + tmp_14 * m02 + tmp_19 * m12)),
d * ((tmp_22 * m22 + tmp_16 * m02 + tmp_21 * m12) - (tmp_20 * m12 + tmp_23 * m22 + tmp_17 * m02))
);
this.columns = [Xo,Yo,Zo,Wo];
return this;
}
}
function pad(num, n)
{
let str = num.toFixed(n);
if (num >= 0)
str = " " + str;
return str;
}
canvas
{
background-color: #333;
cursor: crosshair;
}
<body>
<canvas width='300' height='300'></canvas><br>
<div>Screen Coords of mouse: <span id='rawMouse'></span></div>
<div>(2d) NDC of mouse: <span id='transMouse'></span></div>
</body>
答案 1 :(得分:0)
我已经在代码中添加了解释作为注释
// Canvas viewport (4:3)
const DRAW_WIDTH = 800;
const DRAW_HEIGHT = 600;
const RECT_SIZE = 10;
const RECT_FILL = 'black';
let canvas, ctx;
function init() {
canvas = document.querySelector("canvas");
// setup canvas drawing space
// this will give an aspect-ratio to the canvas
canvas.setAttribute('width', DRAW_WIDTH);
canvas.setAttribute('height', DRAW_HEIGHT);
ctx = canvas.getContext('2d');
// attach listener
canvas.addEventListener("click", onMouseDown);
}
function onMouseDown(e) {
// get canvas position and size infos:
const bbox = canvas.getBoundingClientRect();
const {
x: canvasX,
y: canvasY,
width: canvasW,
height: canvasH
} = bbox;
// mouse click position
const {
clientX: mouseX,
clientY: mouseY
} = e;
// compute ratio between drawing size (viewport) and actual size
const widthRatio = DRAW_WIDTH / canvasW;
// compute x relative to your canvas
const relativeX = (mouseX - canvasX);
// I advise you to use int values when drawing on canvas
// thus Math.round
const finalX = Math.round(widthRatio * relativeX);
// same for Y-axis
const heightRatio = DRAW_HEIGHT / canvasH;
const relativeY = (mouseY - canvasY);
const finalY = Math.round(heightRatio * relativeY);
// draw something with that:
ctx.fillStyle = RECT_FILL;
ctx.rect(finalX - RECT_SIZE / 2, finalY - RECT_SIZE / 2, RECT_SIZE, RECT_SIZE);
ctx.fill();
ctx.closePath();
}
init();
/* set canvas width in the document */
canvas {
width: 80vw;
margin-left: 5vw;
background: coral;
display: block;
}
<canvas></canvas>