Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

HTML5 canvas: find out if click co-ordinates are inside a given rectangle

May be some one has had similar experiences on this dilemma and can help me out here...

Basically, I have a canvas element on which I draw several rectangles in a loop using

context.fillRect (x, y, width, height)

Now, I want some of the rectangles to be hotspots and respond to click events. I can find out the exact (x,y) of a click event using event.layerX and event.layerY.

Given that I know the following:

  • the exact x,y of the click
  • the x,y,width and height of every rectangle

how do I find out if the click event occurred inside the perimeter a certain rectangle or not?
and,
which rectangle the click event occurred 0n?

Is there like a mathematical formula for this?

Any help would be much appreciated, and if I'm not clear enough, let me know...

Thanks

EDIT
Is there no better way than to loop through all rectangles and check their position and dimensions?

like image 409
ekhaled Avatar asked Jul 04 '10 01:07

ekhaled


3 Answers

This is a general topology problem frequently encountered when using maps.

The algorithm for deciding whether a point is within ANY polygon (rectangles, circles, irregular shapes) is as follows:

  • From the point being checked construct any line in any direction until the edge of the screen area in which your polygons are located

  • if the line intersects any polygon boundary at an odd number of places then it is within that polygon

  • if the line intersects any polygon boundary an even number of places it is outside that polygon.

           ------------
           |           |
           |        o--|-------------------    1 intersection: inside
           |           |
           -------------
           ------------
           |           |
    o------|-----------|-------------------    2 intersections: outside
           |           |
           -------------
    

Notes:

  • The direction of the line is irrelevant

  • Will not work if a polygon is cut at the side of the screen without closure

  • If the polygon cuts over itself then the algorithm can fail if the line happens to pass through the cutpoint (eg in the figure 8 considered as a single polygon where the line passes exactly through where upper and lower parts connect of the figure connect)

like image 161
Avron Polakow Avatar answered Nov 04 '22 15:11

Avron Polakow


I hate to post a ton of code here, but here's a js class that might help you...

function Point(x,y){
    this.x=x;
    this.y=y;
}

function Polygon(){
    this.points=[];
    this.x_min=undefined;
    this.x_max=undefined;
    this.y_min=undefined;
    this.y_max=undefined;

    this.add = function(p){
        this.points=this.points.concat(p);
        if (p.x<this.x_min){this.x_min=p.x;}
        if (p.x>this.x_max){this.x_max=p.x;}
        if (p.y<this.y_min){this.y_min=p.y;}
        if (p.y>this.y_min){this.y_max=p.y;}
    }

    this.pointInPoly = function(p){
        var j=(this.points.length-1);  //start by testing the link from the last point to the first point
        var isOdd=false;

        //check the bounding box conditions
        if (p.x < this.x_min || p.x > this.x_max || p.y < this.y_min || p.y > this.y_max){
            return false;
        }

        //if necessary use the line crossing algorithm
        for(var i=0; i<this.points.length; i++){
            if ((this.points[i].y<p.y && this.points[j].y>=p.y) ||  
                (this.points[j].y<p.y && this.points[i].y>=p.y)) {
                    if (this.points[i].x+(p.y-this.points[i].y)/(this.points[j].y-
                        this.points[i].y)*(this.points[j].x-this.points[i].x)<p.x)
                    { isOdd=(!isOdd);} }
            j=i;
        }
        return isOdd;
    }
}

PS: you'll need to convert your click event to local coordinates using something like the following function (where e is your click event):

function getPosition(e){
    var p = new Point();
    if (e.pageX != undefined && e.pageY != undefined) {
        p.x = e.pageX;
        p.y = e.pageY;
     }
     else {
        p.x = e.clientX + document.body.scrollLeft +
                document.documentElement.scrollLeft;
        p.y = e.clientY + document.body.scrollTop +
                document.documentElement.scrollTop;
    }
    p.x -= gCanvas.offsetLeft;
    p.y -= gCanvas.offsetTop;


    return p;
}
like image 30
prauchfuss Avatar answered Nov 04 '22 13:11

prauchfuss


Roughly, you can do it like this:

var click_x = event.layerX;
var click_y = event.layerY;

for ( var i = 0; i < rects.length; i++ ) {
    var rect = rects[i];
    if ( click_x >= rect.x && click_x <= rect.x + rect.width
    &&   click_y >= rect.y && click_y <= rect.y + rect.height ) {
        // The click was inside the rectange
    }
}

Assuming we're dealing with non-negative widths and heights :)

like image 25
tomit Avatar answered Nov 04 '22 15:11

tomit