Justin Taddei
Justin Taddei

Reputation: 2266

Ellipse - Mouse collision detection

I'm trying to figure out how to tell if the mouse is over an ellipse on an HTML5 Canvas. I need to be more precise than a bounding-box check.

Sorry this is such a vague question. I'm completely stumped.

Any help would be greatly appreciated.

So far I have this:

// 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();
    }

}

Upvotes: 3

Views: 2055

Answers (3)

treeno
treeno

Reputation: 2600

The 2DRenderingContext has a method to detect whether a point is in a path or not:

CanvasRenderingContext2D.isPointInPath()

You can use that with every kind of path, independently of the geometric form. One drawback: it can only used while drawing, so you have to redraw the scene in your mouseevent. This might be unwanted for static scenes, but if you have an animation, this shouldn't be a problem.

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

Upvotes: 1

markE
markE

Reputation: 105025

@pvg's duplicate answer is ok, but that duplicate answer should point out how the variables they use are calculated:

// 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);
}

Here's example code and a Demo:

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>

Upvotes: 4

Jeremy Thornton
Jeremy Thornton

Reputation: 11

Apologies for vague initial answer, that will need improvement, but to get the ball rolling...

Given that an ellipse is a curve on a plane that surrounds two focal points (F1 & F2) defined such that the sum of the distances to the two focal points is constant for every point on the curve.

That is, for any point P on the ellipse the two distances F1P + F2P is a constant. This constant is (obviously & helpfully) equal to length of the longer (major) axis A of said ellipse.

Therefore, for this problem, I suspect that the important parts of your ellipse are not width, height, x, y but rather its two foci F1 and F2 (the 2 points equidistant from the ellipse centre that define it) and the length of the major axis.

Then... for any given mouse click point M it follows that provided the sum of the 2 distances F1M + F2M < A then your mouse click point is within the ellipse.

Upvotes: 0

Related Questions