Delphi 确定点是否在多边形内?

Delphi 确定点是否在多边形内?,delphi,custom-controls,delphi-xe2,polygon,Delphi,Custom Controls,Delphi Xe2,Polygon,我正在Delphi中创建一个自定义控件(继承自TCustomControl),它由许多多边形列表项(不规则形状)组成。我需要为每个项目实现鼠标事件,但首先我需要能够检测鼠标位置是否在给定多边形(TPoint数组)内。我正在捕获命中测试消息(WM\n chittest),这就是我需要进行验证的地方。我有许多多边形,我将通过每个多边形项目进行循环,并执行此检查,以查看鼠标的X/Y位置是否在此多边形内 procedure TMyControl.WMNCHitTest(var Message: TWMN

我正在Delphi中创建一个自定义控件(继承自
TCustomControl
),它由许多多边形列表项(不规则形状)组成。我需要为每个项目实现鼠标事件,但首先我需要能够检测鼠标位置是否在给定多边形(
TPoint数组
)内。我正在捕获命中测试消息(
WM\n chittest
),这就是我需要进行验证的地方。我有许多多边形,我将通过每个多边形项目进行循环,并执行此检查,以查看鼠标的X/Y位置是否在此多边形内

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;
您可以使用:


可以使用此处找到的光线投射算法:


大多数计算机图形学课程都以此为例。

检查点是否在多边形内部可以通过想象一条穿过该点的水平线来完成,然后从左到右计算该想象线穿过多边形的次数。如果击中一个点之前的多边形交叉数是奇数,则点在内部,如果偶数,则点在多边形外部。

我们广泛使用另一种技术,它完全不涉及任何数学,可以处理任何形状的极其复杂的嵌入式控件。只需将控件的屏幕外图像与用户可以单击的所有零件进行颜色编码(如下图所示)

当他们移动鼠标时,只需查看屏幕外图像中鼠标下方像素的颜色,就可以准确地告诉我们它们在哪个按钮/控件上——白色表示不在其上,任何一系列颜色表示各个部分

//伪码

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;
函数MouseOverControl(LocalMousePos:TPoint):ControlID;
开始
//健康检查
结果:=IDNull;
如果(LocalMouse.X<0)或(LocalMouse.X>ControlWidth)或
(LocalMouse.Y<0)或(LocalMouse.Y>ControlHeight)然后
出口
大小写OffScreenControlMask.Canvas.Pixels[LocalMousePos.X,LocalMousePos.Y]的
clwhite:退出;
clRed:result:=ControlIDOne;
clGreen:result:=ControlIDTwo;
clBlue:结果:=ControlIDThree;
... 等
结束;
结束;

注意:所附的彩色蒙版图像表示五个相同的圆形控件,它们用一个中心按钮分成四个象限(由于它们都使用相同的颜色,我们对每种颜色都有常数,我们通过简单的X定位确定鼠标在五个控件中的哪一个上)另外,右边还有一个不规则的控件,下面还有一组或矩形按钮。

这也是我的第一个想法。我假设创建GDI区域的开销不是太大(?)@Andreas我不认为开销是坏的。GDI区域应该非常轻。如果这是一个问题,那么你可以缓存多边形旁边的区域。太好了!我不会有太大的开销问题,因为我不希望这个控件有超过20个列表项(这对于这个控件来说已经是一个很大的数字)。请注意,这种方法对于直线多边形(区域包含很少的内部矩形)工作得很快,对于具有弯曲或倾斜边界的大区域则会减速(当区域包含许多内部矩形时)我要指出的是,在分配
P.X
P.Y
之后,我缺少了一行代码
P:=ScreenToClient(P);
。这将这些点从相对于屏幕转换为相对于控件。当然,这可能与
P:=ScreenToClient一样简单(Point(Message.XPos,Message.YPos));
(将三行代码转换为一行)
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;