简体   繁体   English

将鼠标坐标转换为HTML5 Canvas转换后上下文的最佳方法

[英]Best way to transform mouse coordinates to HTML5 Canvas's transformed context

I am testing to see if the mouse is located on an object. 我正在测试以查看鼠标是否位于对象上。 The problem is the object has been transformed. 问题是对象已被转换。 I have graph of objects, mainly the camera, then the slider object, and finally the shape object. 我有对象图,主要是相机,然后是滑块对象,最后是形状对象。 I need to be able to see if the mouse coordinates are inside a specified rectangle relative to the shape object. 我需要能够查看鼠标坐标是否在相对于shape对象的指定矩形内。

Here I have my game loop which transforms the clears the canvas then transforms the camera. 在这里,我有一个游戏循环,该循环转换透明画布,然后转换摄影机。 I then go into a for loop and loop through all the objects calling their specific "draw" method, passing in the context that has been transformed. 然后,我进入一个for循环,循环遍历所有调用其特定“绘制”方法的对象,并传递已转换的上下文。

Game.prototype.gameLoop = function()
{
    this.context.clearRect(0,0,this.canvas.width, this.canvas.height);
    this.context.save();



    this.context.translate(this.canvas.width/2, this.canvas.height/2);
    this.context.scale(this.camera.scale,this.camera.scale);
    this.context.rotate(this.camera.rotate);
    this.context.translate(this.camera.x,this.camera.y);


    for(var i=0;i<this.objects.length;i++)
    {
        this.objects[i].update();
        this.objects[i].draw(this.context);         
    }
    this.context.restore();
}

Here is one of the objects draw method. 这是对象绘制方法之一。 The object is called a Slider. 该对象称为滑块。 It successfully is called and performs a transformation based on it's x,y, and rotate values. 它被成功调用并基于其x,y进行变换并旋转值。

Slider.prototype.draw = function(ctx)
{
    ctx.save();

    ctx.translate(this.x,this.y);
    ctx.rotate(this.rotate);

    this.pointer.draw(ctx);

    ctx.fillStyle = "black";
    ctx.beginPath();
    ctx.moveTo(-(this.width/2),0);
    ctx.lineTo((this.width/2),0);
    ctx.lineTo((this.width/2),5);
    ctx.lineTo(-(this.width/2),5);
    ctx.fill();


    ctx.restore();

}

Finally I have the Shape's draw method which successfully is called and transforms the context yet again. 最后,我有了Shape的draw方法,该方法成功调用并再次转换了上下文。

Shape.prototype.draw = function(ctx)
{
    ctx.save();

    ctx.translate(this.x,this.y);
    ctx.rotate(this.rotate);

    if(this.isMouseOver)
        ctx.fillStyle = this.color;
    else
        ctx.fillStyle = this.mouseOverFillColor;
    ctx.fill(this.shape);   

    ctx.restore();
}

And lastly, here is the method that gets called when the mouse moves called "mouseEventListener". 最后,这是鼠标移动时称为“ mouseEventListener”的方法。 I need to be able to transform the coordinates to see them relative to the shape. 我需要能够转换坐标以查看相对于形状的坐标。

Shape.prototype.mouseEventListener = function(evt,type)
{
    console.log(evt.clientX+" "+evt.clientY);
}

Any ideas? 有任何想法吗? If needed I can create a parent pointer object and have the shape point to the slider and the slider point to the camera to access each parent's x,y, rotate vales. 如果需要,我可以创建一个父指针对象,并使其形状指向滑块,而滑块指向相机以访问每个父对象的x,y,旋转值。

I am kind of looking for the equivalent of Android's mappoints method, which transforms points based off a matrix. 我正在寻找一种等效于Android的mappoints方法,该方法基于矩阵来转换点。 In this case the context has been transformed multiple times and I need a way to capture that state for each object, and then transform some points. 在这种情况下,上下文已多次转换,我需要一种方法来捕获每个对象的状态,然后转换一些点。

I would also like to do all this easily without any other libraries. 我也想在没有任何其他库的情况下轻松完成所有这些操作。

Thank you. 谢谢。

HOT OFF THE PRESS 热销新闻

While writing this answer I noticed that the standard has just changed. 在编写此答案时,我注意到标准刚刚更改。 HTML standard doc 28 Nov 2016 HTML标准文档2016年11月28日

There are now two new calls to the canvas 2D API 😀👍 现在有两个对canvas 2D API的新调用😀👍

  • ctx.getTransform()
  • ctx.resetTransform()

GetTransform returns a DOMMatrix which is different from the existing currentTransform that returns a SVGMatrix . GetTransform返回的DOMMatrix与现有的返回SVGMatrix currentTransform不同。

The adoption and use of these two new (most welcome and needed) calls is unknown by me at this time. 目前,我尚不清楚这两个新的(最受欢迎和最需要的)呼叫的采用和使用。

Now back to my regular belligerency answer. 现在回到我的常规 好战 答案。

In the perfect world you would just use 在理想世界中,您只需要使用

var mat = ctx.currentTransform;

 var mat = ctx.getTransform();

which returns a SVGMatrix DOMMatrix which you then get the inverse matrix of. 它返回一个 SVGMatrix DOMMatrix ,您将得到它的逆矩阵。

var invMat = mat.inverse();

 mat.invertSelf();

Then to convert from screen coordinates to world coordinates 然后从屏幕坐标转换为世界坐标

I am now guessing as the DOMMatrix has a,b,c,d,e,f but I d'ont know if they are 2D as the DOMMatrix is a 3D matrix. 我现在正在猜测,因为DOMMatrix具有a,b,c,d,e,f,但我不知道它们是否为2D,因为DOMMatrix是3D矩阵。

 var wX = screenX * invMat.a + screenY * invMat.c + invMat.e;
 var wY = screenX * invMat.b + screenY * invMat.d + invMat.f;

And that is it. 就是这样。

BUT for some reason browsers seem to think that our lives are better off without this standard object. 但是出于某种原因,浏览器似乎认为没有这个标准对象,我们的生活就会更好。 That writing a huge amount of slow javascript is better. 写大量的慢速javascript更好。

ctx.currentTransform is hidden behind prefixes (moz) and flags (chrome experimental canvas API) and ignored IE (don't know what Edge does??) ctx.currentTransform隐藏在前缀(moz)和标志(chrome实验画布API)的后面,并忽略了IE(不知道Edge在做什么?)


Till browsers catch up. 直到浏览器赶上了。

To solve your problem you need to replace all your transformations with your own so that you can track the current transform. 要解决您的问题,您需要用自己的替换所有转换,以便可以跟踪当前转换。 There are plenty of JS libs to do this for you. 有很多JS库可以为您完成此操作。 Be aware that most are not aware of static memory/object pooling and can add undue performance hits on your code. 请注意,大多数人都不了解静态内存/对象池,并且会在代码上增加不必要的性能影响。 I will not endorse any lib at this time. 目前,我不会支持任何lib。

Example of a matrix math module 矩阵数学模块的示例

 var matrixMath =( function(){ var _a,_b,_c,_d,_e,_f; var xdx,xdy; var stack = []; var stackPos = 0; var API = { // matrix = create() // creates identity matrix 1,0,0,1,0,0 // matrix = create([1,0,0,1,0,0]); // creates from array // matrix = create(matrix); // creates from matrix (copy) // matrix = create(x, y, scale, rotate); // creates from x,y uniform scale and rotation // matrix = create(x, y, scaleX, scaleY, rotate); // creates from x,y, scaleX,scaleY, rotation // matrix = create(a,b,c,d,e,f); create : function(a,b,c,d,e,f){ var mat = {}; if(a === undefined){ mat.a = mat.d = 1; mat.b = mat.c = mat.e = mat.f = 0; return mat; } if(Array.isArray(a)){ mat.a = a[0]; mat.b = a[1]; mat.c = a[2]; mat.d = a[3]; mat.e = a[4]; mat.f = a[5]; return mat; } if(aa !== undefined){ mat.a = aa; mat.b = ab; mat.c = ac; mat.d = ad; mat.e = ae; mat.f = af; return mat } if(f === undefined){ mat.e = a; mat.f = b; if(e === undefined){ mat.d = mat.a = Math.cos(d) * c; mat.c = -(mat.b = Math.sin(d) * c); return mat; } mat.d = (mat.a = Math.cos(e)) * d; mat.c = -(mat.b = Math.sin(e)) * d; mat.a *= c; mat.b *= c; return mat; } mat.a = a; mat.b = b; mat.c = c; mat.d = d; mat.e = e; mat.f = f; return mat }, // point = matrixMultPoint(matrix,x,y,returnPoint) // returnPoint is optional. matrixMultPoint : function(mA,x,y,point){ if(point === undefined){ point = {}; } point.x = x * mA.a + y * mA.c + mA.e; point.y = x * mA.b + y * mA.d + mA.f; return point; }, // C = A * B // resultMatrix = matrixMult(matrixA, matrixB, returnMatrix); // returnMatrix is optional matrixMult : function(mA,mB,mat){ // mat = mA * mB if(mat === undefined){ mat = {}; } mat.a = mA.a * mB.a + mA.c * mB.b; mat.b = mA.b * mB.a + mA.d * mB.b; mat.c = mA.a * mB.c + mA.c * mB.d; mat.d = mA.b * mB.c + mA.d * mB.d; mat.e = mA.a * mB.e + mA.c * mB.f + mA.e; mat.f = mA.b * mB.e + mA.d * mB.f + mA.f; return mat; }, transform : function(mA,a,b,c,d,e,f){ // mat = mA * mB _a = mA.a * a + mA.c * b; _b = mA.b * a + mA.d * b; _c = mA.a * c + mA.c * d; _d = mA.b * c + mA.d * d; mA.e = mA.a * e + mA.c * f + mA.e; mA.f = mA.b * e + mA.d * f + mA.f; mA.a = _a; mA.b = _b; mA.c = _c; mA.d = _d; return mA; }, setTransform : function(mA,a,b,c,d,e,f){ // mat = mA * mB mA.a = a; mA.b = b; mA.c = c; mA.d = d; mA.e = e; mA.f = f; return mA; }, resetStack : function(){ // in case you do not match save and restores // because you have re inited the 2D context stackPos = 0; stack.length = 0; }, save : function(mA){ var mat; if(stack.length <= stackPos){ stack[stackPos++] = API.create(mA); return; } mat = stack[stackPos++]; mat.a = mA.a; mat.b = mA.b; mat.c = mA.c; mat.d = mA.d; mat.e = mA.e; mat.f = mA.f; }, restore : function(mA){ var mat; if(stackPos > 0){ stackPos -= 1; mat = stack[stackPos]; mA.a = mat.a; mA.b = mat.b; mA.c = mat.c; mA.d = mat.d; mA.e = mat.e; mA.f = mat.f; } return mA; }, identity : function(mA){ mA.a = mA.d = 1; mA.b = mA.c = mA.e = mA.f = 0; return mA; }, translate : function(mA, x,y){ // mat = mat * translate(x,y) mA.e = mA.a * x + mA.c * y + mA.e; mA.f = mA.b * x + mA.d * y + mA.f; return mA; }, rotate : function(mA,rotate){ xdx = Math.cos(rotate); xdy = Math.sin(rotate); _a = mA.a * xdx + mA.c * xdy; _b = mA.b * xdx + mA.d * xdy; _c = mA.c * xdx - mA.a * xdy; _d = mA.d * xdx - mA.b * xdy; mA.a = _a; mA.b = _b; mA.c = _c; mA.d = _d; return mA; }, // Scales mA // matrix = scale(mA,scale); // uniform scale // matrix = scale(mA,scaleX,scaleY); // scaleX,scaleY scale : function(mA,scaleX,scaleY){ if(scaleY === undefined){ scaleY = scaleX; } mA.a *= scaleX; mA.b *= scaleX; mA.c *= scaleY; mA.d *= scaleY; return mA; }, // invert(matrix,returnMatrix); // returnMatrix is optional invert : function(mA,mat){ if(mat === undefined){ mat = {}; } var d = mA.a * mA.d - mA.b * mA.c; mat.a = mA.d / d; mat.b = -mA.b / d; mat.c = -mA.c / d; mat.d = mA.a / d; mat.e = (mA.c * mA.f - mA.d * mA.e) / d; mat.f = -(mA.a * mA.f - mA.b * mA.e) / d; return mat; }, getRotate : function(mA,fromY){ return !fromY ? Math.atan2(mA.b,mA.a) : Math.atan2(mA.d,mA.c) ; }, getScale : function(mA,fromY){ return !fromY ? Math.sqrt(mA.b * mA.b + mA.a * mA.a) : Math.sqrt(mA.c * mA.c + mA.d * mA.d) ; }, getOrigin : function(mA,point){ if(point === undefined){ point = {}; } point.x = mA.e; point.y = mA.f; return point; }, } return API; })(); 

To use the above code mirror all your transformations 要使用上面的代码镜像所有转换

// do once only
var cMatrix = matrixMath.create(); // create default identity matrix
var cIMatrix = matrixMath.create(); // create a matrix to hold the inverse
var worldP = {x:0,y:0}; // to hold world coordinates.

// in  main loop
matrixMath.identity(cMatrix);  // resets to default transform.
matrixMath.translate(cMatrix,x,y);  // same as ctx.translate
matrixMath.rotate(cMatrix,angle);  // same as ctx.rotate
matrixMath.scale(cMatrix,scaleX,scaleY);  // same as ctx.scale.. Has variant see code.
matrixMath.transform(cMatrix,a,b,c,d,e,f);  // same as ctx.transform
matrixMath.setTransform(cMatrix,a,b,c,d,e,f); // same as ctx.setTransform 
matrixMath.save(cMatrix);  // save and restores to matrixMath internal stack
matrixMath.restore(cMatrix); // NOTE very simple stack look at code to understand its function.


// set the context to the transform 
ctx.setTransfrom(cMatrix.a, cMatrix.b, cMatrix.c, cMatrix.d, cMatrix.e, cMatrix.f);

// Draw what you need.

To get the world coordinates

cIMatrix = matrixMath.invert(cMatrix,cIMatrix);
// screenX,screenY are the screen position you wish to covert to world.
worldP = matrixMath.matrixMultPoint(cIMatrix,screenX,screenY,worldP);
// worldP.x and worldP.y are now in transformed space.

Please note the matrixMath object was just written for this answer and is untested. 请注意matrixMath对象只是为此答案而编写的,未经测试。 It may contain some typos though it has passed basic parsing test. 尽管它已通过基本的解析测试,但可能包含一些错字。 It is intended only as a How To example of matrix math. 它仅作为一个如何例如矩阵数学的。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM