9

我想在 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;
}
4

6 回答 6

7

感谢 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;
}
于 2009-04-08T15:55:08.663 回答
5

我找到了更好的方法。发生的事情是,在您扭曲鼠标后,操作系统会抑制事件约 0.25 秒。因此,只需调用:

#ifdef __APPLE__
CGSetLocalEventsSuppressionInterval(0.0);
#endif

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

您可能需要包括:

#include <ApplicationServices/ApplicationServices.h>

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

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

于 2011-03-16T21:13:03.077 回答
0

除了红皮书的例子,我对过剩没有太多经验,但它是不是因为你为光标绘制的内容而生涩,或者你绘制它的频率如何?如果你只是在光标应该使用 OpenGL 调用的地方画一个点,它仍然是生涩的吗?您的计时码可能有问题吗?

你调用什么代码来更新指针每次滴答?我认为它不是列出的代码,因为您每次都会计算中心点,而不是在调整大小事件上。

我很抱歉在这里盲目回答(即有限的过剩经验)。

于 2009-04-08T01:29:04.780 回答
0

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

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

当你自己绘制它时,你会受到操作系统的定期调度机制的影响,并通过定期清除和重绘整个窗口 rigamarole。在这种情况下,由于上述原因,它的速度很快,但不如我们习惯使用鼠标指针的速度快。

简而言之,我不确定您是否能达到预期的速度(尤其是当您的显示功能和应用程序逻辑变得更加复杂时)。

祝你好运!

于 2009-04-08T02:21:41.083 回答
0

您是否在几帧上平均了鼠标的移动?因为我在工作,所以找不到我以前项目的代码。但我认为我平均了几帧鼠标的移动,在我这样做之前,移动非常生涩。

于 2009-04-08T03:08:24.950 回答
0

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

您的示例不适用于我的 Win32 系统,除非我将 GLUT_DOUBLE 添加到 glutInitDisplayMode()。

编辑:

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

直接从运动函数调用 display() 似乎也不起作用 - 这一次它无法注册任何类型的运动。

我可以让您的示例正常工作的唯一方法是将被动运动回调更改为主动运动回调并直接从该函数调用 display() 。我知道这与您最初的意图相去甚远,但至少我以这种方式获得了一些平稳的动作。

您是否尝试过使用 glutIdleFunc() 为您触发显示更新?它可能仍然不适用于被淹没的消息队列,但它可能值得一试。您还可以考虑使用 API 调用来捕获鼠标,而不是在每次动作时手动将光标环绕到窗口的中心。

于 2009-04-08T12:57:37.310 回答