Identify if a point is within a polygon?
I'm making a custom control in Delphi (inherited from TCustomControl) which consists of a number of polygon list items (irregular shapes). I need to implement mouse events per item, but first I need to be able to detect if the mouse position is within a given polygon (array of TPoint). I am catching the Hit Test message (WM_NCHITTEST) and this is where I will need to do this validation. I have a number of polygons, I will do a loop through each polygon item and perform this check to see if the mouse's X/Y position is within this polygon.
procedure TMyControl.WMNCHitTest(var Message: TWMNCHitTest); var P: TPoint; //X/Y of Mouse Poly: TPoints; //array of TPoint X: Integer; //iterator I: TMyListItem; //my custom list item begin P.X:= Message.XPos; P.Y:= Message.YPos; for X := 0 to Items.Count - 1 do begin I:= Items[X]; //acquire my custom list item by index Poly:= I.Points; //acquire polygon points //Check if Point (P) is within Polygon (Poly)...? end; end;
You can use PtInRegion:
function PointInPolygon(Point: TPoint; const Polygon: array of TPoint): Boolean; var rgn: HRGN; begin rgn := CreatePolygonRgn(Polygon, Length(Polygon), WINDING); Result := PtInRegion(rgn, Point.X, Point.Y); DeleteObject(rgn); end;
You can use the ray casting algorithm found here: http://en.wikipedia.org/wiki/Point_in_polygon#Ray_casting_algorithm
Most computer graphics classes use this as an example.
Checking if point is inside of a polygon can be done by imagining a horizontal line through that point, then from left to right counting how many times this imagined line crosses a polygon. If number of polygon crosses before hitting a point is odd then point is inside, if even then point is outside of a polygon.
There is another technique that we use extensively, which doesn't involve any math at all and can handle extremely complex embedded controls of any shape at all. Simply have an off-screen image of the control with all the parts color-coded (as shown in the image below) that the user could click.
As they move their mouse, simply look at the color of the pixel underneath the mouse in our off-screen image and that tells us exactly what button/control they are over -- white for not over it, and any series of colors for the various parts.
function MouseOverControl(LocalMousePos:TPoint):ControlID; begin //sanity check Result:=IDNull; if (LocalMouse.X < 0) or (LocalMouse.X > ControlWidth) or (LocalMouse.Y < 0) or (LocalMouse.Y > ControlHeight) then exit; case OffScreenControlMask.Canvas.Pixels[LocalMousePos.X,LocalMousePos.Y] of clwhite:exit; clRed:result:=ControlIDOne; clGreen:result:=ControlIDTwo; clBlue:result:=ControlIDThree; ... etc end; end;
NOTE: The attached Color Mask image represents five identical circular controls broken up into quadrants with a center button (and since they all use the same colors we have constants for each color and we determine which one of the five the mouse is over by a simple XPosition) along with an additional irregular control to their right and a set or rectangular buttons beneath.