views:

1063

answers:

5

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;
}
A: 

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

Bernard
I've edited to provide the source of a reduced case.
Steven Canfield
A: 

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!

Drew Hall
This is a good idea, but removing the glutWarpPointer() call results in the expected smoothness (but obviously we lose the warp pointer action).
Steven Canfield
A: 

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.

Craig
A: 

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.

aib
+1  A: 

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;
}
Steven Canfield
Drew Hall