2009-04-08 27 views
8

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

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

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

很高兴你能工作。 +1发布您的解决方案和一个很好的解释。看起来我还没有接近! :) – 2009-04-10 03:32:59

+1

我解决了这个问题,先调用glutWarpPointer并忽略鼠标事件,直到下一行为止。 – 2011-01-01 09:15:14

0

我没有与过剩太多的经验,除红书的例子,但它是生涩的,因为你画什么光标,或多久你画的?如果你只是在光标应该使用OpenGL调用的地方绘制一个点,它是否仍然是生涩的?你的计时码可能是一个问题吗?

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

我在此盲目回答道歉(即有限的大量经验)。

+0

我编辑过,以提供减少案例的来源。 – 2009-04-08 02:06:47

0

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

正常的鼠标指针是在驱动程序级别通过将光标图像与帧缓冲区内容异或来处理的 - 因此闪电般快,并且由OS在中断服务程序中以非常高的优先级处理(以维持幻象响应)。

当您自己绘制时,您需要遵守操作系统的常规调度机制,通过定期清除&重绘整个窗口rigamarole。在这种情况下,由于上述原因,速度很快,但速度并不像我们习惯使用鼠标指针那么快。总之,我不确定你会不会像预期的那样快速(特别是当你的显示功能&应用逻辑变得更复杂时)。

祝你好运!

+0

这是一个好主意,但是除去glutWarpPointer()调用会导致预期的平滑度(但显然我们会失去warp指针操作)。 – 2009-04-08 02:29:32

0

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

0

难道是因为你在非双缓冲窗口上交换缓冲区?

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

编辑:

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

从运动函数调用display()似乎并不奏效 - 这次它无法注册任何类型的运动。

我能得到你的例子来工作的唯一途径是通过直接从功能改变被动运动回调至活跃运动回调和调用显示()。我知道这与你原先的想法并不相同,但至少我这样做了一些平滑的动作。

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

5

我发现了一个更好的方法。发生什么事是操作系统在扭曲鼠标之后约0.25秒内禁止事件。所以,而不是只是打电话:

#ifdef __APPLE__ 
CGSetLocalEventsSuppressionInterval(0.0); 
#endif 

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

您可能需要包括:

#include <ApplicationServices/ApplicationServices.h> 

并在这一框架添加到您的项目或你的编译器选项。

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