Pull to refresh

OpenGL iBooks-like анимация перелистывания

Reading time 6 min
Views 9.5K
Помню, когда я в первый раз взял в руки iPad, более всего меня поразило приложение iBooks c его натуралистичным перелистыванием страниц. Все остальные «фишки» как-то не особо впечатляли, но это… это показалось воплощением полной интерактивности. По сравнению с обычными графическими интерфейсами, использующими стандартные элементы GUI, интерфейсы с использованием анимации OpenGL это новый шаг в развитии пользовательских интерфейсов.

Историческое отступление: первыми стали использовать элементы анимации в GUI рабочие станции Indy компании Silicon Graphics, которая и является основоположником языка OpenGL. На десктопах этих компьютеров были трехмерные кнопки меню, которые при нажатии переворачивались в 3D, открывая списки подменю из таких же кнопок. Это было здорово, и это было действительно красиво. Жаль, что Silicon Graphics ушла в небытие из-за неправильного менеджмента, эта компания была действительно первой во многом (OpenGL, STL, журналируемые файловые системы).
Поэтому не буду петь дифирамбы Apple, они лишь внедрили хорошо забытое старое. Но справедливости ради стоит отметить, что и это было прорывом, ведь они задали тенденцию и унылые статические интерфейсы, надеюсь, скоро останутся в прошлом.

Итак, OpenGL анимация и GUI.
По роду деятельности мне уже приходилось разрабатывать проекты с использованием OpenGL, поэтому когда я столкнулся с необходимостью создать анимацию, эмулирующую перелистывание бумажных страниц, передо мной встали две основные задачи:

• алгоритм деформации плоскости листа
• интеграция OpenGL и пользовательского интерфейса

проект разрабатывался для операционной системы MacOS X, используя основные фреймворки и язык Objective-C.

Алгоритм деформации бумажного листа
Идея взята отсюда. В качестве алгоритма деформации используется уравнение конуса. Этот алгоритм был разработан в исследовательских лабораториях Xerox PARC еще в 2004 году.
Изгиб бумажного листа определяется поверхностью некоего конуса, изменяя параметры которого можно добиться требуемого эффекта анимации.

image

Я не буду вдаваться в подробности описания самого алгоритма и тригонометрии. Скажу лишь что он пересчитывает любую 2D точку P(x,y) плоскости листа в 3D точку T(x,y,z), используя изменяемые параметры А и θ. Для тех, кто желает подробностей, есть исходники тестового проекта для iOS и OpenGL-ES.

Интеграция OpenGL и пользовательского интерфейса
Методы анимации, используемые в пользовательских интерфейсах, обычно выполнены по следующей схеме:

первоначальное состояние — фреймы анимации — конечное состояние

То есть берутся некие начальное и конечное состояния анимированного объекта интерфейса, сам объект на время анимации обычно либо прячется, либо перекрывается объектом, который рендерит фреймы перехода, по окончании рендерер прячется и показывается объект в конечном состоянии.

В применении к перелистываемым страницам первоначальное и конечное состояния соответствуют двум последовательным разворотам страниц книги.
То есть алгоритм анимации должен быть следующим:

— взять текстуры текущего разворота страниц
— выполнить прокрутку страниц offscreen
— взять текстуры следующего разворота страниц из offscreen buffer
— перекрыть разворот страниц OpenGL рендерером
— запустить анимацию
— по завершении спрятать OpenGL рендерер и показать следующий разворот, который находится в offscreen.

В качестве рендререра OpenGL я использовал класс
NSOpenGLView, обладающий собственным OpenGL контекстом и несколькими удобными методами для управления этим контекстом.

В зависимости от направления перелистывания, рендерер инициализируется четырьмя текстурами страниц, — две статические текстуры и две динамические, деформируемые с помощью вышеприведенного алгоритма.
Рассмотрим перелистывание страницы справа-налево:

Текущий разворот: левая страница – статическая, правая страница – динамическая.
Следующий разворот: левая страница – динамическая, правая – статическая.

Ниже приведен код контроллера, управляющего анимацией перелистывания. Код упрощен и управляет переворотом страницы только в одну сторону.

@implementation PageAnimationController
 
#pragma mark -
#pragma mark *** Animation ***
 
// поворот страницы
- (void)flipPage
{
    // инициализируем рендерер текстурами страниц
    [self prepareRenderer];
 
    // создаём таймер анимации (об этом объекте чуть ниже)
    // передаём ему указатель на наш рендерер
    FlipAnimation *animation = [[FlipAnimation alloc] initWithDuration:1.0f
                                                        animationCurve:NSAnimationEaseInOut
                                                                  view:rendererView];
    // это значит, что startAnimation не выходит, пока анимация
    // не закончена
    [animation setAnimationBlockingMode:NSAnimationBlocking];
    [animation startAnimation];
    [animation release];
 
    // анимация закончена, прячем рендерер
    [self endAnimation];
}
 
- (void) prepareRenderer
{
    // удостоверимся что выбран нужный текущий контекст OpenGL
    [[rendererView openGLContext] makeCurrentContext];
 
    // первый разворот страниц
    [rendererView setAnimatedFront:[self getPageTexture:PAGE_RIGHT]];
    [rendererView setStaticLeft:[self getPageTexture:PAGE_LEFT]];
 
    // переворачиваем страницу за кулисами
    [bookView flipPageOffScreen];
 
    // инициализируем текстуры следующего разворота страниц
    [rendererView setAnimatedBack:[self getPageTexture:PAGE_LEFT]];
    [rendererView setStaticRight:[self getPageTexture:PAGE_RIGHT]];
 
    // наконец-то показываем наш рендерер, перекрывая текущий разворот
    // книги такой же картинкой, только отрисованной в OpenGL
    [rendererView setHidden:NO];
}
 
- (void)endAnimation
{
    // прячем OpenGL рендерер
    [rendererView setHidden:YES];
}
 
@end


Стандартный таймер анимации. Вызывает updateTime: со значением относительного времени анимации в промежутке 0..1

@interface FlipAnimation : NSAnimation
{
    OpenGLPageCurlView * rendererView;
}
@end
 
 
@implementation FlipAnimation
 
- (id)initWithDuration:(NSTimeInterval)duration 
        animationCurve:(NSAnimationCurve)animationCurve
                  view:(OpenGLPageCurlView *)view 
{
    self = [super initWithDuration:duration animationCurve:animationCurve];
    if (self != nil)
    {
        rendererView = view;
    }
    return self;
}
 
- (void)setCurrentProgress:(NSAnimationProgress)progress
{
    // рендеринг анимации, значение progress меняется от 0 до 1
    [rendererView updateTime: progress];
}
 
@end


Анимацией легко управлять и без таймера, например используя события мыши или тачпада. Так достигается эффект «перетаскивания» страницы, достаточно посылать рендереру сообщения updateTime: из обработчика событий.

Код рендерера OpenGL я приводить не буду, так как он банален. Маппинг текстур выполнен страйпами треугольников, добавлена подсветка сцены анимации.
Ну и в заключение вот как это выглядит в работе (извиняюсь за качество):

Tags:
Hubs:
+41
Comments 24
Comments Comments 24

Articles