【问题标题】:How can I determine the closest point to the mouse on a certain shape?如何确定某个形状上离鼠标最近的点?
【发布时间】:2012-01-03 14:26:55
【问题描述】:

我创建了一个基于黑白位图将鼠标限制在某个区域的程序。该程序按原样 100% 运行,但使用了一种不准确但速度很快的算法来在鼠标偏离区域时重新定位鼠标。

目前,当鼠标移出该区域时,基本上是这样的:

  1. 在区域内预定义的静态点和鼠标的新位置之间绘制一条线。
  2. 找到该线与允许区域边缘的交点。
  3. 鼠标移动到该点。

这有效,但仅适用于 完美 用于将预定义点设置在精确中心的完美圆。不幸的是,这将永远不会发生。该应用程序将用于各种矩形和不规则、无定形的形状。在这样的形状上,画线与边缘相交的点通常不是形状上离鼠标最近的点。

我需要创建一个新的算法来找到最近点到鼠标在允许区域边缘的新位置。我怎样才能做到这一点?优选地,该方法应该能够足够快地执行,以便在将鼠标拖动到区域边缘时提供平滑的鼠标移动。

(我在 OS X 10.7 上的 Objective C / Cocoa 中执行此操作,但是,如果您不想输入代码或不了解 Objective C / C,则可以使用伪代码)

谢谢!

这是我目前的算法:

#import <Cocoa/Cocoa.h>
#import "stuff.h"
#import <CoreMedia/CoreMedia.h>




bool
is_in_area(NSInteger x, NSInteger y, NSBitmapImageRep *mouse_mask){

    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

    NSUInteger pixel[4];
    [mouse_mask getPixel:pixel atX:x y:y];

    if(pixel[0]!= 0){
        [pool release];
        return false;
    }
    [pool release];
    return true;
}

CGEventRef 
mouse_filter(CGEventTapProxy proxy, CGEventType type, CGEventRef event, NSBitmapImageRep *mouse_mask) {


    CGPoint point = CGEventGetLocation(event);


    float tX = point.x;
    float tY = point.y;

    if( is_in_area(tX,tY, mouse_mask)){

        // target is inside O.K. area, do nothing
    }else{

    CGPoint target; 

    //point inside restricted region:
    float iX = 600; // inside x
    float iY = 500; // inside y


    // delta to midpoint between iX,iY and tX,tY
    float dX;
    float dY;

    float accuracy = .5; //accuracy to loop until reached

    do {
        dX = (tX-iX)/2;
        dY = (tY-iY)/2;

        if(is_in_area((tX-dX),(tY-dY),mouse_mask)){

            iX += dX;
            iY += dY;
        } else {

            tX -= dX;
            tY -= dY;
        }

    } while (abs(dX)>accuracy || abs(dY)>accuracy);

        target = CGPointMake(roundf(tX), roundf(tY));
        CGDisplayMoveCursorToPoint(CGMainDisplayID(),target);

    }


    return event;
}




int
main(int argc, char *argv[]) {


    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

    stuff *stuff_doer = [[stuff alloc] init];

    NSBitmapImageRep *mouse_mask= [stuff_doer get_mouse_mask]; 


    CFRunLoopSourceRef runLoopSource;
    CGEventMask event_mask;
    event_mask = CGEventMaskBit(kCGEventMouseMoved) | CGEventMaskBit(kCGEventLeftMouseDragged) | CGEventMaskBit(kCGEventRightMouseDragged) | CGEventMaskBit(kCGEventOtherMouseDragged);

        CGSetLocalEventsSuppressionInterval(0);

    CFMachPortRef eventTap = CGEventTapCreate(kCGHIDEventTap, kCGHeadInsertEventTap, 0, event_mask, mouse_filter, mouse_mask);

    if (!eventTap) {
        NSLog(@"Couldn't create event tap!");
        exit(1);
    }

    runLoopSource = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, eventTap, 0);

    CFRunLoopAddSource(CFRunLoopGetCurrent(), runLoopSource, kCFRunLoopCommonModes);

    CGEventTapEnable(eventTap, true);

    CFRunLoopRun();

    CFRelease(eventTap);
    CFRelease(runLoopSource);
    [pool release];

    exit(0);
}

这是可能使用的区域位图示例,黑色是允许的区域。 这说明了为什么转换为多边形并不方便,甚至不合情理。

【问题讨论】:

    标签: objective-c c xcode macos cocoa


    【解决方案1】:

    如果用户正在移动鼠标指针并且需要限制在某个区域,那么我认为最好的解决方案不是找到该区域内的最近点。取而代之的是,对用户来说更直观的做法是将鼠标带回到有效区域退出它的点。这更容易实现,前提是您可以以足够快的速度监控鼠标位置。

    现在,您可能有理由按照自己的意愿去做,我尊重这一点。在这种情况下,我可以提出以下想法:

    1. 如果您可以将定义有效鼠标区域的方式从位图更改为多边形(标记区域角的二维点列表),那么任务会变得更加简单。只需找到最接近鼠标位置的段即可。作为该计算的一部分,您可以获得该段内最近的点,这就是您要重新定位鼠标指针的位置。

    2. 蛮力解决方案也应该有效。从当前鼠标位置开始,向外工作。首先检查它周围的八个像素。然后是 16 到 8 左右,以此类推。找到有效区域内的点后,记录其到当前鼠标位置的距离。继续,继续寻找更近的像素,或者直到当前外层中的所有像素的距离都大于您记录的最小距离。

    我希望这会有所帮助。

    【讨论】:

    • 好答案。不仅仅是将点移回它退出的点的原因是,当它以一定角度向外拖动时,我需要光标沿着该区域的边缘平滑滑动。将其移回退出点将不允许这样做。至于您的其他解决方案,1:我无法将其重新定义为多边形,我将在上面包含一个示例形状来说明原因。 2:这似乎是一个很好的解决方案,实现起来很简单,但我想知道这是否会花费太多开销? (鼠标一次最多可以移动 200 像素。)
    • @JonathonG:开销将是相对的,这取决于。如果您必须每分钟进行一次此计算,那么我会说没问题。如果你每秒做 30 次,那可能太贵了,但是,我可能想知道用户怎么能这么快地将鼠标指针移开 200 像素。另外,考虑到您可以创建比我描述的蛮力方法更有效的优化搜索,相当于二分搜索。它可能不会给你最好的结果,但也许 close 对于你的目的来说已经足够了。如果您知道鼠标区域是凸面区域,您也可以进行优化。
    • @Miguel 我已在我可能正在使用的示例区域的问题中添加了一张图片(这些区域通常与此类似。)这说明了为什么您的答案 #1 不起作用我。此外,这需要实时完成以在拖动形状外部时创建平滑的鼠标移动,这就是为什么开销非常重要的原因,平滑鼠标移动可能需要每秒 20-30 次,尽管类似10个可以接受。用户可以将光标移开 200 像素,因为如果鼠标移动得足够快,光标就会开始越跳越远。
    • @JonathonG:为什么你说形状不能转换成多边形?用所有连接的边缘点制作一个多边形,然后对于每组三个顶点,如果中间一个非常靠近其他两个顶点之间的线,则删除它。继续这样操作,将顶点数量减少到可管理的数量。
    • @Miguel 虽然这样做只会牺牲少量的准确性(在大多数情况下几乎不明显),但它仍然会牺牲准确性。此外,将要使用的各种位图(它可以定期更改)转换为多边形的过程可能与找到位图上最近点的解决方案一样麻烦。不过,我确实明白你在说什么。
    【解决方案2】:

    一些想法:

    • 一种非常标准的解决形式问题的方法:“给定一组 2D 点 S(在您的情况下是一组边缘点)和一个查询点 P(在您的情况下是鼠标位置),在P中找到离S最近的点”,就是使用四叉树。它们可以看作是二分搜索对二维的推广。四叉树在电子游戏中很流行用于碰撞检测,因此您可以在 google 中找到很多教程。

    • 形状是变化还是静止的?在第二种情况下,如果内存不是问题,我只需预先计算每个像素的最近边缘点并将其放入查找表中。 (实际上,我只使用两个数组,一个用于 x 坐标,另一个用于 y 坐标)。使用类似于 Floyd-Warshall 算法的方法可以消除冗余计算,在这种情况下,该算法具有非常简单的形式。

    【讨论】:

    • 这些似乎是非常好的选择。看起来查找表实际上可能是两者中更容易实现的。我不确定你在说什么冗余计算和弗洛伊德战争。我会仔细看看的。由于该表将存储与 130 万像素中的每一个像素最近的点的 x 和 y,这将占用多少空间?我的计算结果是 4MB。 (每 x 11 位,每 y 11 位,130 万像素)这看起来对吗?
    【解决方案3】:

    我认为在细节上问题不是很简单(至少如果您想要效率和精确度)。有传言称Qt Graphics View 在这方面做得很好。也许使用它,或者浏览一下它的源代码(它是免费软件)应该会有所帮助?

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-09-12
      • 1970-01-01
      • 2011-04-27
      • 2021-12-24
      • 1970-01-01
      • 1970-01-01
      • 2011-02-02
      相关资源
      最近更新 更多