2009-04-08 25 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> 

並在這一框架添加到您的項目或你的編譯器選項。

請注意,您可能會收到一個鼠標移動到屏幕中心的事件,因此如果它位於屏幕中間,我將忽略該事件。