椭圆 - 鼠标碰撞检测

3

我正在尝试弄清楚如何判断鼠标是否悬浮在HTML5 Canvas中的椭圆上。我需要比较精确的方法进行检查,而不仅是边界框检查。

很抱歉这个问题比较模糊,我完全被难住了。

任何帮助都将不胜感激。

到目前为止,我有以下代码:

// This is a pretend object. m.x and m.y will have the position of the mouse.
var m = {
    x:245,
    y:341,
    onEnter: function(){}
};

m.onEnter = function () {
    console.log('The mouse entered the ellipse!');
};

// The important parts of my ellipse object
var e = {
    width:100,
    height:50,
    x:132,
    y:143
};

// This is already working - I use a bounding-box first to increase performance.
if (m.x >= e.x - (e.width/2) && m.x <= e.x + (e.width/2) && m.y >= e.y - (e.height/2) && m.y <= e.y + (e.height/2)) {

    if (/* I havn't got the slightest idea what to put here. */) {
        m.onEnter();
    }

}

3
确定一个点是否在椭圆内(包括边缘),需要进行以下步骤:
  1. 将椭圆的中心移动到坐标系的原点
  2. 将点也移动到以椭圆中心为原点的坐标系中
  3. 计算点的坐标是否符合椭圆方程,如果符合则点在椭圆内,否则在外。椭圆方程为: (x^2 / a^2) + (y^2 / b^2) <= 1,其中a和b分别是椭圆长轴和短轴的一半。 注意:此方法适用于所有椭圆,不仅限于水平或垂直的椭圆。
- pvg
你是如何在画布上描述椭圆的?是通过使用参数方程进行线性绘制,例如 x = a cos t, y = b sin t 或等效方式,还是通过使用一对 context.bezierCurveTo() 函数来模拟椭圆? - Brian Peacock
@BrianPeacock 我正在缩放一个圆:假设椭圆是100 X 50 然后我使用 context.scale(1,0.5) 然后 context.arc - Justin Taddei
3个回答

4

@pvg的重复答案是可以的,但那个重复答案应该指出他们使用的变量是如何计算的:

// Given cx,cy,a,b defining an ellipse 
function isInEllipse(mouseX,mouseY){
    var dx=mouseX-cx;
    var dy=mouseY-cy;
    return ((dx*dx)/(a*a)+(dy*dy)/(b*b)<=1);
}

以下是示例代码和演示:

var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
function reOffset(){
  var BB=canvas.getBoundingClientRect();
  offsetX=BB.left;
  offsetY=BB.top;        
}
var offsetX,offsetY;
reOffset();
window.onscroll=function(e){ reOffset(); }
window.onresize=function(e){ reOffset(); }

var PI=Math.PI;
var PI2=PI*2;
var cx=cw/2;
var cy=ch/2;
var a=120;
var b=80;
var points=getPointsOnEllipse(cx,cy,a,b);

$("#canvas").mousemove(function(e){handleMouseMove(e);});

drawEllipse(points);


function dot(x,y,color){
  ctx.beginPath();
  ctx.arc(x,y,3,0,PI2);
  ctx.closePath();
  ctx.fillStyle=color;
  ctx.fill();
}

function isInEllipse(x,y){
  var dx=x-cx;
  var dy=y-cy;
  return ((dx*dx)/(a*a)+(dy*dy)/(b*b)<=1);
}

function drawEllipse(points){
  ctx.beginPath();
  ctx.moveTo(points[0].x,points[0].y);
  for(var j=0;j<points.length;j++){
    ctx.lineTo(points[j].x,points[j].y);
  }
  ctx.closePath();
  ctx.lineWidth=1;
  ctx.strokeStyle='forestgreen';
  ctx.stroke();
}

function getPointsOnEllipse(cx,cy,a,b){
  var startAngle=-PI/2;
  var lastX=cx-(a*Math.cos(startAngle));
  var lastY=cy+(b*Math.sin(startAngle));
  var points=[];
  for(var i=0;i<1000;i++){
    var angle=startAngle+PI2/1000*i;
    var x=cx-(a*Math.cos(angle));
    var y=cy+(b*Math.sin(angle));
    var dx=x-lastX;
    var dy=y-lastY;
    var length=parseInt(Math.sqrt(dx*dx+dy*dy));
    var eAngle=(Math.atan2(dy,dx)+PI2)%PI2;
    if(length>0){
      points.push({x:x,y:y,angle:eAngle});
      lastX=x;
      lastY=y;
    }
  }
  return(points);
}

function handleMouseMove(e){
  // tell the browser we're handling this event
  e.preventDefault();
  e.stopPropagation();

  mouseX=parseInt(e.clientX-offsetX);
  mouseY=parseInt(e.clientY-offsetY);

  var hit=isInEllipse(mouseX,mouseY);
  var color=(hit)?'red':'green';
  dot(mouseX,mouseY,color);

}
body{ background-color: ivory; }
#canvas{border:1px solid red; }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<h4>Move mouse in & out of ellipse.<br>Red mouse dots are inside ellipse, Green outside.</h4>
<canvas id="canvas" width=300 height=300></canvas>


ab是椭圆的半径吗?如果是,a始终会比b大还是它们分别代表x和y轴? - Justin Taddei

1
2DRenderingContext有一个方法可以检测一个点是否在路径中:
CanvasRenderingContext2D.isPointInPath()

你可以在任何路径上使用它,无论几何形状如何。缺点是它只能在绘制时使用,因此您必须在鼠标事件中重新绘制场景。对于静态场景可能不需要,但如果有动画,则不应该是问题。

https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/isPointInPath


1
"isPointInPath" 是一个我经常在不规则形状上使用的好方法。但是,它比数学命中测试慢得多。因此,出于性能考虑,只要可以,我就会使用数学命中测试。 :-) - markE

0

对于初始回答的含糊不清,我深表歉意,需要改进,但为了让事情开始……

考虑到椭圆是平面上围绕两个焦点(F1和F2)定义的曲线,使得曲线上每个点到两个焦点的距离之和对于每个点都是恒定的。

也就是说,对于椭圆上的任何一点P,两个距离F1P + F2P是一个常数。这个常数(显然且有用)等于所述椭圆的长轴A的长度。

因此,对于这个问题,我怀疑你的椭圆的重要部分不是宽度、高度、x、y,而是它的两个焦点F1和F2(与椭圆中心等距离定义它的两个点)以及长轴的长度。

那么……对于任何给定的鼠标点击点M,只要两个距离F1M + F2M < A,则您的鼠标点击点在椭圆内。


我想我明白了。这是我第一次处理椭圆(我在绘制它时作弊了,请参见此评论)。我在foci上找到了这个(我不知道那些是什么)。但是我对F1M + F2M < A有点困惑。你能再解释一下吗?我接近了。我能感觉到!谢谢。 - Justin Taddei
M是您的鼠标点击点,F1M是您的鼠标点击点与椭圆第一个焦点之间的距离(两点之间的距离方程由毕达哥拉斯提供),而F2M是从鼠标点击点到第二个焦点的距离。现在,如果这两个距离的和小于椭圆长轴的长度,则您的鼠标点击点必须在椭圆内。您可以通过省略平方根并仅比较平方距离来加快此过程。或者采用markE所做的以cx、cy为中心的椭圆公式,无需担心焦点 :) - Jeremy Thornton
非常感谢大家!@markE,你的isInEllipse函数正是我所需要的。也感谢Jeremy Thornton的精彩解释!我已经点赞+1了两个答案(当我声望提高时它们会显示)。 - Justin Taddei
请看“园丁椭圆”。对于水平椭圆,点(相对于中点)(-e,0)和(-e,0)是F1/2,则要到达(a,0),您需要A=a-e+a+e=2a,要到达(0,b),您需要e²=a²-b²,以轴长度定义e。 - Lutz Lehmann
@LutzL 谢谢,我会查阅! - Justin Taddei

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接