Search code examples
iosopengl-escadisplaylink

Constant-velocity rotation in OpenGL ES view using CADisplayLink on iPhone


My OpenGL ES classes and code are derived from Apple's GLES2Sample code sample. I use them to show 3D objects rotating smoothly around one axis, at what I expect to be a constant rotation speed. Currently, the app uses a frame interval of 1, and each time the OpenGL view is drawn (in the EAGLView's drawView method), I rotate the model by a certain angle.

In practice, this gives decent results, but not perfect: during the rotation, when large parts of the object go out of sight, rendering becomes faster and the rotation thus does not have constant angular velocity. My question is: how do I make it smoother?

Although I welcome all suggestions, I already have one idea: measure the rendering FPS every half-second, and adjust the rotation angle at each redraw based on that. It does not sound very good, however, so: what do you think of this, and how would you handle the issue?


Solution

  • I tend to use a CADisplayLink to trigger new frames and a simple time calculation within the frame request to figure out how far to advance my logic.

    Suppose you have a member variable, timeOfLastDraw, of type NSTimeInterval. You want your logic to tick at, say, 60 beats per second. Then (with a whole bunch of variables to make the code more clear):

    - (void)displayLinkDidTick
    {
        // get the time now
        NSTimeInterval timeNow = [NSDate timeIntervalSinceReferenceDate];
    
        // work out how many quantums (rounded down to the nearest integer) of
        // time have elapsed since last we drew
        NSTimeInterval timeSinceLastDraw = timeNow - timeOfLastDraw;
        NSTimeInterval desiredBeatsPerSecond = 60.0;
        NSTimeInterval desiredTimeInterval = 1.0 / desiredBeatsPerSecond;
    
        NSUInteger numberOfTicks = (NSUInteger)(timeSinceLastDraw / desiredTimeInterval);
    
        if(numberOfTicks > 8)
        {
            // if we're more than 8 ticks behind then just do 8 and catch up
            // instantly to the correct time
            numberOfTicks = 8;
            timeOfLastDraw = timeNow;
        }
        else
        {
            // otherwise, advance timeOfLastDraw according to the number of quantums
            // we're about to apply. Don't update it all the way to now, or we'll lose
            // part quantums
            timeOfLastDraw += numberOfTicks * desiredTimeInterval;
        }
    
        // do the number of updates
        while(numberOfTicks--)
            [self updateLogic];
    
        // and draw
        [self draw];
    }
    

    In your case, updateLogic would apply a fixed amount of rotation. If constant rotation is really all you want then you could just multiply the rotation constant by numberOfTicks, or even skip this whole approach and do something like:

    glRotatef([NSDate timeIntervalSinceReferenceData] * rotationsPerSecond, 0, 0, 1);
    

    instead of keeping your own variable. In anything but the most trivial case though, you usually want to do a whole bunch of complicated things per time quantum.