glutPassiveMotionFunc 和 glutWarpMousePointer

发布于 2024-07-16 11:06:28 字数 2052 浏览 9 评论 0原文

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

I want to implement my own cursor in an OpenGL / GLUT window. The usual way to do this is to freeze the cursor (so it can't hit the edges of the screen) and keep track of its position yourself. I can make the onscreen cursor invisible using

glutSetCursor(GLUT_CURSOR_NONE);

and then inside of my glutPassiveMotionFunc callback move the pointer to the middle of the window using

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 );

This works in that it keeps the pointer stuck to the middle of the window. The problem is that when I am drawing the 'OpenGL' mouse (inside of the glutDisplayFunc() callback) it is extremely jerky.

I have looked online and found that there can be an issue where glutWarpPointer() causes the glutPassiveMotionFunc callback to be called again, resulting in a loop, but this doesn't seem to happen here.

I'm on Mac OS X and I found a post saying that CGDisplayMoveCursorToPoint was a better fit for this. Calling CGDisplayMoveCursorToPoint works but the movement is still very jerky (and I seem to get a lot of events where x and y are both 0). In any case, I'd like this to work on Linux as well so a Mac only solution is not ideal (but I'm okay having to do different things on the different systems).

I've reduced this to a testcase.

#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;
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

絕版丫頭 2024-07-23 11:06:28

感谢 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;
}

Thanks aib for the tips. You got me looking into the disassembly of glutWarpPointer and it became obvious what was going on. Calling glutWarpPointer CGPostMouseEvent which results in a bunch of nonsense events (and there isn't any way to skip them since you only get Mouse Events once per frame, the new "real" events will be late). The solution I've found is to only warp when the pointer is at the edge of the screen ( the point, after all, is to pretend like the point can never reach the edge of the screen ). In any case, here is the code.

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;
}
沧笙踏歌 2024-07-23 11:06:28

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

#ifdef __APPLE__
CGSetLocalEventsSuppressionInterval(0.0);
#endif

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

您可能需要包含:

#include <ApplicationServices/ApplicationServices.h>

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

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

I found a better approach. What is going on is that the OS suppresses events for about 0.25 seconds after you warp the mouse. So, instead just call:

#ifdef __APPLE__
CGSetLocalEventsSuppressionInterval(0.0);
#endif

Then everything will go smoothy without any stuttering.

You may need to include:

#include <ApplicationServices/ApplicationServices.h>

and add this framework to your project or to your compiler options.

Note that you may get an event for the mouse moving to the center of the screen, so I just ignore an event if it is to the middle of the screen.

做个ˇ局外人 2024-07-23 11:06:28

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

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

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

I don't have too much experience with glut, save for red-book examples, but is it jerky because of what you're drawing for the cursor, or how often you're drawing it? If you just draw a point where the cursor is supposed to be using OpenGL calls, is it still jerky? Could your timing code be an issue?

What code are you calling to update the pointer every tick? I assume it isn't the code listed as you would be calculating the centre point every time, instead of on a resize event.

My apologies for blindly answering here (i.e. with limited glut experience).

风苍溪 2024-07-23 11:06:28

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

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

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

简而言之,我不确定您能否让它像您期望的那样快(特别是当您的显示功能和应用程序逻辑变得更加复杂时)。

祝你好运!

I'm guessing here, but I suspect that the motion is jerky because the cursor is drawn in your application's draw function (display()), rather than handled by the OS.

A normal mouse pointer is handled at the driver level by XORing the cursor image with the frame buffer contents--thus lightning quick, and handled with very high priority by the OS in an interrupt service routine (to maintain the illusion of responsiveness).

When you draw it yourself, you're subject to the regular scheduling mechanism of your OS & go through the regular clear & redraw the whole window rigamarole. In this case, it's fast but not as fast as we're used to with a mouse pointer due to the above.

In short, I'm not sure you'll ever get it to be as fast as you expect it to be (particularly as your display function & app logic gets more complex).

Good luck!

那些过往 2024-07-23 11:06:28

您是否对几帧内鼠标的移动进行平均?
我找不到以前项目的代码,因为我正在工作。
但我想我之前对几帧的鼠标移动进行了平均
我这样做的时候动作非常生涩。

Are you averaging the movement of the mouse over a few frames?
I can't find my code for my previous project because i am at work.
But i think i averaged the mouse movements over a few frames, before
i did that the movement was very jerky.

情绪操控生活 2024-07-23 11:06:28

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

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

编辑:

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

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

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

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

Could it be because you're swapping buffers on a non-double buffered window?

Your example doesn't work on my Win32 system, unless I add GLUT_DOUBLE to glutInitDisplayMode().

Edit:

You are correct. Calling glutWarpPointer() from within the motion function seems to cause a loop on my [win32] system. The timer doesn't even get a chance to fire unless I click a button or something. I'm betting the message queue is being flooded with motion events.

Calling display() right from the motion function doesn't seem to work, either - this time it fails to register any kind of motion.

The only way I could get your example to work was by changing the passive motion callback to an active motion callback and calling display() directly from that function. I know this is far from what you've originally intended, but at least I got some smooth motion this way.

Have you tried using a glutIdleFunc() to trigger your display updates for you? It may still not work with a flooded message queue but it may be worth a try. You could also look into capturing the mouse using an API call instead of manually wrapping the cursor to the center of the window at every motion.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文