glutPassiveMotionFunc和glutWarpMousePointer

时间:2009-04-08 00:01:52

标签: macos opengl mouse glut

我想在OpenGL / GLUT窗口中实现自己的光标。通常的做法是冻结光标(因此无法触及屏幕边缘)并自行跟踪其位置。我可以使用

使屏幕上的光标不可见
glutSetCursor(GLUT_CURSOR_NONE);

然后在我的glutPassiveMotionFunc回调内部使用

将指针移动到窗口的中间位置
int centerX = (float)kWindowWidth / 2.0;
int centerY = (float)kWindowHeight / 2.0;

int deltaX = (x - centerX);
int deltaY = (y - centerY);

mouseX += deltaX / (float)kWindowWidth;
mouseY -= deltaY / (float)kWindowHeight;

glutWarpPointer( centerX, centerY );

这样可以使指针停留在窗口中间。问题是,当我绘制'OpenGL'鼠标(在glutDisplayFunc()回调中)时,它非常生涩。

我已经在线查看并发现可能存在一个问题,即glutWarpPointer()导致再次调用glutPassiveMotionFunc回调,从而导致循环,但这似乎不会发生在这里。

我在Mac OS X上发现了一篇帖子,说CGDisplayMoveCursorToPoint更适合这个。调用CGDisplayMoveCursorToPoint可以工作,但运动仍然非常不稳定(我似乎得到很多事件,其中x和y都是0)。在任何情况下,我都希望这能在Linux上运行,因此仅Mac解决方案并不理想(但我可以在不同的系统上做不同的事情)。

我已将其缩减为测试用例。

#include <stdio.h>
#include <OpenGL/OpenGL.h>
#include <GLUT/GLUT.h>

int curX = 0;
int curY = 0;

void display() {
    glClearColor( 0.0, 0.0, 0.0, 1.0 );
    glClear( GL_COLOR_BUFFER_BIT );

    float vx = (float)curX / 300.0 + 0.5;
    float vy = (float)curY / 300.0 + 0.5;

    glColor3f( 1.0, 0.0, 0.0 );
    glBegin( GL_POINTS );
        glVertex3f( vx, vy, 0.0 );
    glEnd();

    glutSwapBuffers();

}

void passivemotion( int x, int y ) {
    int centerX = 150;
    int centerY = 150;

    int deltaX = x - centerX;
    int deltaY = y - centerY;
    curX += deltaX;
    curY -= deltaY;

    glutWarpPointer( centerX, centerY );
}

void timer( int val ) {
    glutTimerFunc( 16, &timer,  0);
    glutPostRedisplay();
}

int main (int argc, char * argv[]) {
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_RGB);
    glutInitWindowSize(300,300);
    glutCreateWindow("FPS Mouse Sample");
    glutDisplayFunc(&display);
    glutPassiveMotionFunc(&passivemotion);
    glutSetCursor( GLUT_CURSOR_NONE );
    glutTimerFunc( 16, &timer, 0 );
    glutMainLoop();
    return 0;
}

6 个答案:

答案 0 :(得分:6)

感谢aib的提示。你让我看着glutWarpPointer的反汇编,很明显发生了什么。调用glutWarpPointer CGPostMouseEvent导致一堆无意义的事件(并且没有任何方法可以跳过它们,因为每帧只获得一次鼠标事件,新的“真实”事件将会延迟)。我发现的解决方案是仅在指针位于屏幕边缘时才会扭曲(毕竟,点是假装点永远不会到达屏幕边缘)。无论如何,这是代码。

int lastX = 150;
int lastY = 150;
void passivemotion( int x, int y ) {    
    int deltaX = x - lastX;
    int deltaY = y - lastY;

    lastX = x;
    lastY = y;

    if( deltaX == 0 && deltaY == 0 ) return;

    int windowX     = glutGet( GLUT_WINDOW_X );
    int windowY     = glutGet( GLUT_WINDOW_Y );
    int screenWidth     = glutGet( GLUT_SCREEN_WIDTH );
    int screenHeight    = glutGet( GLUT_SCREEN_HEIGHT );

    int screenLeft = -windowX;
    int screenTop = -windowY;
    int screenRight = screenWidth - windowX;
    int screenBottom = screenHeight - windowY;

    if( x <= screenLeft+10 || (y) <= screenTop+10 || x >= screenRight-10 || y >= screenBottom - 10) {
        lastX = 150;
        lastY = 150;
        glutWarpPointer( lastX, lastY );
        //  If on Mac OS X, the following will also work (and CGwarpMouseCursorPosition seems faster than glutWarpPointer).
        //  CGPoint centerPos = CGPointMake( windowX + lastX, windowY + lastY );
        //  CGWarpMouseCursorPosition( centerPos );
        // Have to re-hide if the user touched any UI element with the invisible pointer, like the Dock.
        //  CGDisplayHideCursor(kCGDirectMainDisplay);
    }

    curX += deltaX;
    curY -= deltaY;
}

答案 1 :(得分:5)

我找到了更好的方法。发生了什么事情,操作系统在扭曲鼠标后抑制事件约0.25秒。所以,只需致电:

#ifdef __APPLE__
CGSetLocalEventsSuppressionInterval(0.0);
#endif

然后一切都会顺利进行,没有任何口吃。

您可能需要包含:

#include <ApplicationServices/ApplicationServices.h>

并将此框架添加到您的项目或编译器选项中。

请注意,您可能会收到鼠标移动到屏幕中心的事件,所以如果它位于屏幕中间,我就会忽略该事件。

答案 2 :(得分:0)

除了红皮书的例子之外,我没有太多的过剩经验,但是因为你为光标画的是什么,或者你画的频率多少,它是不是很生涩?如果你只是画一个光标应该使用OpenGL调用的点,它还是生涩吗?您的计时代码可能是一个问题吗?

您打算在每次打勾时更新指针的代码是什么?我假设它不是列出的代码,因为你每次都会计算中心点,而不是调整大小事件。

我在这里盲目回答道歉(即有过多的过剩经历)。

答案 3 :(得分:0)

我猜这里,但我怀疑这个动作是生涩的,因为光标是在应用程序的绘图功能(display())中绘制的,而不是由操作系统处理。

通过将光标图像与帧缓冲区内容进行异或操作来处理驱动程序级别的正常鼠标指针 - 因此闪电般快速,并且在中断服务程序中由操作系统以非常高的优先级处理(以保持响应的错觉) )。

当您自己绘制时,您需要遵守操作系统的常规调度机制。通过常规清除&amp;重绘整个窗口的rigamarole。在这种情况下,由于上述原因,它速度快但不像我们习惯用鼠标指针那么快。

简而言之,我不确定你会不会像预期的那样快速(特别是当你的显示功能和应用程序逻辑变得更复杂时)。

祝你好运!

答案 4 :(得分:0)

你是否将鼠标移动平均几帧? 因为我在工作,我找不到我之前项目的代码。 但我想我之前将鼠标移动平均几帧 我这样做,运动非常生涩。

答案 5 :(得分:0)

可能是因为你在非双缓冲窗口上交换缓冲区吗?

您的示例在我的Win32系统上不起作用,除非我将GLUT_DOUBLE添加到glutInitDisplayMode()。

编辑:

你是对的。从运动函数中调用glutWarpPointer()似乎会导致我的[win32]系统出现循环。除非我点击按钮或其他东西,否则计时器甚至没有机会开火。我打赌消息队列充满了动作事件。

从动作功能直接调用display()似乎也不起作用 - 这次它无法记录任何类型的动作。

我能让你的例子工作的唯一方法是将被动动作回调更改为活动动作回调并直接从该函数调用display()。我知道这远不是你原本想要的,但至少我得到了一些平滑的动作。

您是否尝试使用glutIdleFunc()来触发显示更新?它可能仍然无法使用泛洪消息队列,但它可能值得一试。您还可以使用API​​调用捕获鼠标,而不是在每次运动时手动将光标包装到窗口的中心。