Подписка

27 декабря 2010

Пишем Cover Flow своими руками

Cover Flow


Думаю, многие видели красивую "листалку" обложек, используемую в продукции Apple. Да и не только у них. Для тех кто все-таки не слышал о таком - есть Википедия. К чему я завел этот разговор о Cover Flow? А к тому, что сегодня я расскажу вам как самому сделать нечто очень похожее на Cover Flow. Результат вы можете лицезреть на картинке выше. Если я заинтересовал вас, то добро пожаловать под кат.

Вступление

Какими средствами мы будем все это реализовывать? Начнём с того, что программа наша будет кроссплатформенная. Для работы с графикой будем использовать OpenGL и библиотеку GLUT. OpenGL есть в Windows, Mac OS X и Linux сразу. GLUT из коробки оказался только в Mac OS X. Для Windows можно взять здесь. Для Linux устанавливаем Freeglut (в Ubuntu доступен из репозиториев, в других дистрибутивах наверняка тоже). Писать будем на C. Ибо ничего не может быть лучше старого-доброго C. Для пущей кроссплатформенности фишки C99 использоваться не будут, т.к. их не поддерживает VC. И последнее. Для работы с каталогами я пользовался функциями из никсового dirent.h. В VS2010 такого заголовочного файла нету. Но зато есть его свободная реализация. Взять можно здесь. Скачанный заголовочный файл ложить в %Путь к Visual Studio%\VC\include\. Теперь можно приступать.

Вспомогательные модули

Для начала напишем пару небольших вспомогательных модулей. Модуль для работы с каталогом (для загрузки всех изображений из указанного каталога), модуль для загрузки изображений в формате BMP (можно воспользоваться готовыми библиотеками, но это не для истинных джедаев, да и условия работы изначально предполагали ручную загрузку) и модуль для вывода строк на экран (да, в OpenGL цивилизованный вывод текста не предусмотрен).

font.h

Начнём с самого простого. Тут будет всего одна функция. Но зато с наворотом в виде поддержки форматной строки.

font.h
#ifndef _FONT_H_
#define _FONT_H_

#include <stdio.h>
#include <stdarg.h>
#if defined(__linux) || defined(_WIN32)
#include <GL/glut.h>
#else
#include <GLUT/glut.h>
#endif

#define MAX_LENGTH 256    /*Max length of string*/

/*Draw string using OpenGL render. Supports format string*/
void glPrintString(float, float, float, short, short, const char*, ...);

#endif

font.c
#include "font.h"

//x, y - координаты, size - размер, - bold - жирность вкл/выкл, mono - моноширинный шрифт вкл/выкл, str - собственно строка
void glPrintString(float x, float y, float size, short bold, short mono, const char* str, ...)
{
    char* p;
    char text[MAX_LENGTH];
    va_list ap;
    
    //Преобразуем форматную строку в обычную
    va_start(ap, str);
    vsprintf(text, str, ap);
    va_end(ap);
    
    //Начинаем рисовать буквы, начиная с координаты (x, y)
    glPushMatrix();
    glTranslatef(x, y, 0.0f);
    //и масштабируем их в соответствии с размером size
    glScalef(0.01f * size, 0.01f * size, 1.0f);
    //Если включена жирность, то меняем толщину линии (OpenGL рисует буквы линиями)
    if(bold)
        glLineWidth(2 * size);
    //Собственно отрисовка текста по буквам обычным или моноширинным шрифтом
    for (p = text; *p; p++)
    {
        if(mono)
            glutStrokeCharacter(GLUT_STROKE_MONO_ROMAN, *p);
        else
            glutStrokeCharacter(GLUT_STROKE_ROMAN, *p);
    }
    glPopMatrix();
}

image.h

Напишем собственноручно загрузку изображений в форате BMP. Это очень просто, тем более, что мы будет загружать лишь 24-битные изображения без сжатия. Таких подавляющее большинство.

image.h
#ifndef _IMAGE_H_
#define _IMAGE_H_

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#if defined(__linux) || defined(_WIN32)
#include <GL/glut.h>
#else
#include <GLUT/GLUT.h>
#endif

unsigned int LoadTextureFromBitmap(char*);    /*Works only with 24-bit bitmap images w/o RLE-compression*/

#endif

image.c
#include "image.h"

unsigned int LoadTextureFromBitmap(char* file)
{
    unsigned int Texture;
    FILE* img = NULL;
    int bWidth = 0;
    int bHeight = 0;
    unsigned int size = 0;
    unsigned char* data;

    //Читаем файл
    img = fopen(file,"rb");
    if (img == NULL)
    {
        fprintf(stderr, "Failed to open file: %s: %s\n", file, strerror(errno));
        return 0;
    }

    //Загружать весь заголовок в структуру мы не будем
    //Нам нужны лишь размеры изображения и собственно изображение
    //Читаем ширину и высоту изображения
    fseek(img, 18, SEEK_SET);
    fread(&bWidth, 4, 1, img);
    fread(&bHeight, 4, 1, img);
    if (bWidth == 0 || bHeight == 0)
    {
        fprintf(stderr, "Failed to read image size: %s: %s\n", file, strerror(errno));
        return 0;
    }
    //Получаем размер изображения (без заголовков)
    fseek(img, 0, SEEK_END);
    size = ftell(img) - 54;

    //Выделяем память
    data = (unsigned char*)calloc(size, sizeof(unsigned char*));

    //Читаем изображение
    fseek(img, 54, SEEK_SET);
    fread(data, size, 1, img);
    if (data == NULL)
    {
        fprintf(stderr, "Failed to read image data: %s: %s\n", file, strerror(errno));
        return 0;
    }

    fclose(img);

    //Создаем текстуру
    //OpenGL каждой текстуре присваивает уникальный номер
    //Затем мы сможем вызывать нужную текстуру по этому номеру
    glGenTextures(1, &Texture);
    glBindTexture(GL_TEXTURE_2D, Texture);
    //Заголовочные файлы, поставляемые вместе с VS, находятся на уровне OpenGL 1.1
    //Поэтому в Windows нужно использовать устаревшую константу GL_BGR_EXT вместо GL_BGR
    //Сама константа обозначает, что в Bitmap-файле порядок цветовых компонент обратный, т.е. Blue Green Red
#ifdef _WIN32
    gluBuild2DMipmaps(GL_TEXTURE_2D, GL_RGB, bWidth, bHeight, GL_BGR_EXT, GL_UNSIGNED_BYTE, data);
#else
    gluBuild2DMipmaps(GL_TEXTURE_2D, GL_RGB, bWidth, bHeight, GL_BGR, GL_UNSIGNED_BYTE, data);
#endif
    //Следующие строки указывают использовать линейную интерполяцию при масштабировании текстуры
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
    glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
    glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);

    free(data);
    return Texture;
}

filemanager.h

Модуль специально написанный для облагораживания программы к этой статье. Изначально загружался текстовый файл со списком путей к изображениям. Но теперь все как у цивилизованных людей - указывается папка и из неё выбираются все файлы с расширением bmp. В нагрузку к этому ещё три функции: функция, выделяющая имя файла из полного пути; функция, выделяющая расширение файла из полного пути или имени файла; функция, склеивающая две строки.

filemanager.h
#ifndef _FILEMANAGER_H_
#define _FILEMANAGER_H_

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dirent.h>
#include <errno.h>
#if defined(__linux) || defined(_WIN32)
#include <GL/glut.h>
#else
#include <GLUT/GLUT.h>
#endif

char** LoadCoversList(char*, int*);                    /*Load covers list from dir*/
char** LoadCoversListFromFile(char*, int*);            /*Load covers list from file*/
char* StripFilename(const char*);                    /*Return filename w/o path*/
char* StripFileType(const char*);                    /*Return filetype*/
char* JoinFilePath(const char*, const char*);        /*Join dir and file path*/

#endif

filemanager.c
#include "filemanager.h"

char** LoadCoversList(char* dirPath, int* count)
{
    DIR* dir;
    struct dirent* dent;
    char** files = NULL;
    int i = 0;
    printf("Func: %s Dir: %s\n", __FUNCTION__, dirPath);

    //Открываем папку
    dir = opendir(dirPath);
    if(dir == NULL)
    {
        fprintf(stderr, "Failed to open current dir: %s\n", strerror(errno));
        return NULL;
    }

    //Читаем содержимое каталога
    while(dent = readdir(dir))
    {
        //Пропускаем все кроме файлов с расширением bmp
        if(dent->d_type == DT_REG && strcmp(StripFileType(dent->d_name), "bmp") == 0)
        {
            //Реаллочим массив строк
            files = (char**)realloc(files, (i + 1) * sizeof(char*));
            if(files == NULL)
            {
                fprintf(stderr, "Failed to reallocate memory: %s\n", strerror(errno));
                return NULL;
            }
            //Копируем строку в массив
            files[i] = (char*)strdup(dent->d_name);
            if(files[i] == NULL)
            {
                fprintf(stderr, "Failed to copy string: %s\n", strerror(errno));
                return NULL;
            }
            i++;
        }
    }
    closedir(dir);
    *count = i;
    return files;
}

//Функция-анахронизм, в программе уже не используется
char** LoadCoversListFromFile(char* filePath, int* count)
{
    //Её содержимое опустим для краткости
}

//Возвращает имя файла (без пути)
char* StripFilename(const char* str)
{
    printf("Func: %s STR: %s\n", __FUNCTION__, str);
    #if defined(_WIN32)
    if(strrchr(str, '+') != NULL)
        return strdup(strrchr(str, '+') + 1);
    else
        return str;
    #else
    if(strrchr(str, '/') != NULL)
        return strdup(strrchr(str, '/') + 1);
    else
        return str;
    #endif
}

//Возвращает расширение файла
char* StripFileType(const char* str)
{
    printf("Func: %s STR: %s\n", __FUNCTION__, str);
    return strdup(strrchr(str, '.') + 1);
}

//Склеивает две строки в одну (в нашем случае путь к файлу и имя файла)
char* JoinFilePath(const char* dir, const char* file)
{
    //Получаем длину новой строки
    int len = strlen(dir) + strlen(file) - 1;
    //Выделяем память
    char* path = (char*)calloc(len, sizeof(char));
    printf("Func: %s Dir: %s File: %s\n", __FUNCTION__, dir, file);
    
    //Копируем первую строку
    strcpy(path, dir);
    //Копируем вторую строку
    strcpy(path + strlen(dir), file);
    return path;
}

cover.h

Самый важный модуль. Использует в работе три предыдущих. 13 параметров вынесены в define. Объявлен enum для удобства указания направления движения изображений. И конечно же структура. Исчерпывающие комментарии по структуре и функциям даны в коде.

cover.h
#ifndef _COVER_H_
#define _COVER_H_

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include "image.h"
#include "filemanager.h"
#include "font.h"
#if defined(__linux) || defined(_WIN32)
#include <GL/glut.h>
#else
#include <GLUT/glut.h>
#endif

#define W_WIDTH 1000        /*Ширина окна*/
#define W_HEIGHT 400        /*Высота окна*/
#define X -27                /*Координата X точки начала отрисовки*/
#define Y 6                    /*Координата Y точки начала отрисовки*/
#define COVERS 11            /*Количество обложек, отображаемых на экране*/
#define OFFSET 3            /*Смещение координат обложек*/
#define TXT_OFFSET 2.5        /*Отступ текста от верхней границы обложки*/
#define TXT_SIZE 1            /*Размер текста*/
#define WIDTH 12            /*Размер обложки*/
#define ANGLE 80            /*Угол поворота обложек, стоящих боком*/
#define TRANSPARENT 0.4f    /*Коэффициент прозрачности отражений обложек*/
#define ANIM_TIME 250        /*Время анимации (в мс)*/
#define ANIM_STEP 16        /*Количество шагов анимации*/

typedef enum
{
    LEFT,
    RIGHT
} Direction;

typedef struct
{
    float angle;            /*Угол поворота*/
    short reflection;        /*Отражение вкл/выкл*/
    float transparent;        /*Коэффициент прозрачности отражения обложки*/
    unsigned int texture;    /*Номер текстуры*/
    char* name;                /*Файл*/
    short sign;                /*Показывать имя файла вкл/выкл*/
    float position;            /*Порядковый номер обложки в "потоке"*/
    float offset;            /*Смещение обложки относительно положения по умолчанию*/
} Cover;

void InitCoversArray();                /*Загружает массив обложек*/
void DrawAnimatedCovers(int);        /*Передвигает обложки с плавной анимацией*/
void DrawCovers();                    /*Рисует обложки*/
float CalculateX(float, float);        /*Подсчитывает текущую координату Х для обложки*/
void MoveCovers(Direction);            /*Передвигает обложку*/
void prints();                        /*Служебная функция для распечатки структуры Cover в консоль*/
void ClearCovers();                    /*Освобождает память занимаемую обложками*/

#endif

cover.c
#include "cover.h"

//Путь к папке с обложками
char dirPath[] = "./covers/";
//Массив обложек
Cover* covers = NULL;
int i, middle, count = 0;
Direction move = LEFT;
//Блокировка одновременного движения в противоположную сторону
short leftLocked = 0, rightLocked = 0;
float nextPos;

void InitCoversArray()
{
    //Загружаем список изображений
    char** coverList = LoadCoversList(dirPath, &count);
    //Выделяем память
    covers = (Cover*)calloc(count, sizeof(Cover));
    //Считаем номер центральной обложки на экране
    middle = (COVERS + 1) >> 1;
    printf("Func: %s\n", __FUNCTION__);

    for(i = 0; i < count; i++)
    {
        //Инициализируем обложки
        Cover c = {
            (i + 1) <= middle ? ((i + 1) != middle ? ANGLE : 0.0f) : -ANGLE, /*Выбираем угол: обложки слева под углом ANGLE, центральная - 0, обложки справа под углом -ANGLE*/
            1,
            TRANSPARENT,
            LoadTextureFromBitmap(JoinFilePath(dirPath, coverList[i])), /*Загружаем изображение из файла, сохраняем номер текстуры*/
            StripFilename(coverList[i]), /*Получаем имя файла для отображения*/
            (i + 1) == middle ? 1 : 0, /*Включаем отображения названия только для центральной обложки*/
            (float)(i + 1),
            (i + 1) < middle ? 0.0f : ((i + 1) != middle ? 4.0f : 2.0f) /*Устанавливаем персональное смещение для обложек (обложки слева - 0 центральная - 2 обложки справа - 4)*/
        };
        covers[i] = c;
    }
}

void DrawAnimatedCovers(int p)
{
    //Статический счетчик шагов анимации
    static int counter = 0;
    //Флаг остановки
    static short stop = 0;
    printf("Func: %s\n", __FUNCTION__);

    //Если движемся влево, то блокируем на это время движение вправо. Если вправо, то блокируем движение влево.
    if(p == LEFT)
        rightLocked = 1;
    else if(p == RIGHT)
        leftLocked = 1;
    //Если анимация закончилась, то обнуляем счетчик
    if(stop)
    {
        counter = 0;
        stop = 0;
    }
    //Двигаем обложки в нужном направлении
    MoveCovers(p);
    counter++;
    //Запускаем таймер, который вызывает этот метод для осуществления анимации
    if(counter < ANIM_STEP)
        glutTimerFunc(ANIM_TIME / ANIM_STEP, DrawAnimatedCovers, p);
    else
    {
        stop = 1;
        rightLocked = 0;
        leftLocked = 0;
    }
    glutPostRedisplay();
}

void DrawCovers()
{
    for(i = 0; i < count; i++)
    {
        Cover c = covers[i];
        if(c.position <= COVERS)
        {
            //Текущщий Х
            float x = CalculateX(c.position, c.offset);
            //Середина картинки
            float mid = x + (WIDTH >> 1);
            //Смещение от середины для центровки текста
            float center = mid - ((strlen(c.name) >> 1) * TXT_SIZE);

            glPushMatrix();
            //Накладываем текстуру
            glBindTexture(GL_TEXTURE_2D, c.texture);
            //Передвигаем обложку в начало координат
            //Это нужно для того, чтобы поворачивать обложку вокруг собственной оси
            //Т.к. в OpenGL все вращения происходят на самом деле вокруг центральной оси
            glTranslatef(mid, 0, 0);
            //Поворачиваем обложку (если нужно)
            glRotatef(c.angle, 0, 1, 0);
            //Возвращаем обложку обратно
            glTranslatef(-mid, 0, 0);
            //Начинаем рисовать примитив четырёхугольника
            glBegin(GL_QUADS);
            glColor4f(1.0, 1.0, 1.0, 1.0);
            //Устанавливаем текстурные координаты (привязка углов текстуры к углам четырёхугольника)
            glTexCoord2f(0, 1); glVertex3i(x, Y, 0);
            glTexCoord2f(0, 0); glVertex3i(x, Y - WIDTH, 0);
            glTexCoord2f(1, 0); glVertex3i(x + WIDTH, Y - WIDTH, 0);
            glTexCoord2f(1, 1); glVertex3i(x + WIDTH, Y, 0);
            glEnd();
            //Если включено отражение, рисуем четырехугольник с перевернутой текстурой и полупрозрачной верхней половиной
            if(c.reflection)
            {
                glBegin(GL_QUADS);
                glColor4f(1.0, 1.0, 1.0, c.transparent);
                glTexCoord2f(0, 0); glVertex3i(x, Y - WIDTH, 0);
                glTexCoord2f(1, 0); glVertex3i(x + WIDTH, Y - WIDTH, 0);
                //Следующие две вершины неведимые (полностью прозрачные). Благодаря этому получается эффект отражения
                glColor4f(1.0, 1.0, 1.0, 0.0);
                glTexCoord2f(1, 1); glVertex3i(x + WIDTH, Y - (WIDTH << 1), 0);
                glTexCoord2f(0, 1); glVertex3i(x, Y - (WIDTH << 1), 0);
                glEnd();
            }
            //Выводим название обложки, если эта опция включена
            if(c.sign)
            {
                //Отключаем текстуру (иначе надпись отобразится неправильно, т.к. буквы нарисованы линиями и OpenGL попытается "натянуть" на них текстуру)
                glBindTexture(GL_TEXTURE_2D, 0);
                glColor4f(1.0, 1.0, 1.0, 1.0);
                //Для вывода текста используем написанную нами ранее функцию
                glPrintString(center, Y + (TXT_OFFSET * TXT_SIZE), TXT_SIZE, 1, 1, c.name);
            }
            glPopMatrix();
        }
    }
}

float CalculateX(float pos, float offset)
{
    //Считает текущую координату как смещение от стартовой координаты, используя позицию обложки, её персональное смещение и общее смещение
    return X + ((pos + offset - 1) * OFFSET);
}

void MoveCovers(Direction d)
{
    move = d;
    //Передвигает обложки с плавной анимацией
    //Пошагово сдвигает позицию каждой обложки, выполняет поворот для центральной и следующей за ней обложки
    //Обеспечивает цикличность движения обложек
    if(d == RIGHT)
    {
        for(i = 0; i < count; i++)
        {
            covers[i].position += 1.0f / ANIM_STEP;
            covers[i].sign = covers[i].position == middle ? 1 : 0;
            nextPos = ceilf(covers[i].position);
            if(nextPos == middle || nextPos == middle + 1)
            {
                covers[i].angle -= ANGLE / ANIM_STEP;
                covers[i].offset += 2.0f / ANIM_STEP;
            }
            if(covers[i].position == nextPos && nextPos == count + 1)
            {
                covers[i].position = 1.0f;
                covers[i].angle = ANGLE;
                covers[i].offset = 0.0f;
            }
        }
    }
    else if(d == LEFT)
    {
        for(i = 0; i < count; i++)
        {
            covers[i].position -= 1.0f / ANIM_STEP;
            covers[i].sign = covers[i].position == middle ? 1 : 0;
            nextPos = floorf(covers[i].position);
            if(nextPos == middle || nextPos == middle - 1)
            {
                covers[i].angle += ANGLE / ANIM_STEP;
                covers[i].offset -= 2.0f / ANIM_STEP;
            }
            if(covers[i].position == nextPos && nextPos == 0)
            {
                covers[i].position = count;
                covers[i].angle = -ANGLE;
                covers[i].offset = 4.0f;
            }
        }
    }
}

void prints()
{
    for(i = 0; i < count; i++)
    {
        printf("Cover #%d\n", i + 1);
        printf("----------------------------\n");
        printf("Position = %f\n", covers[i].position);
        printf("Angle = %f\n", covers[i].angle);
        covers[i].reflection ? printf("Transparent coeff = %f\n", covers[i].transparent) : 0;
        printf("Name = %s\n", covers[i].name);
        printf("----------------------------\n");
    }
}

void ClearCovers()
{
    printf("Func: %s\n", __FUNCTION__);
    for(i = 0; i < count; i++)
        free(covers[i].name);
    free(covers);
}

Последний шаг

Осталось последнее усилие. Написать инициализацию окна OpenGL, отклик на клавиатуру и мышь, функцию перерисовки окна и немного настроек OpenGL.

main.c
#include "cover.h"

extern short leftLocked, rightLocked;

//Собственная функция инициализации
void init()
{
    //Настройки света
    float light_position[] = { 0.0, 1.0, 1.0, 0.0 };
    float light_ambient[] = { 0.2, 0.2, 0.2, 1.0 };
    glLightfv (GL_LIGHT0, GL_POSITION, light_position);
    glLightfv (GL_LIGHT0, GL_AMBIENT, light_ambient);
    //Антиалиасинг (рекомендуем OpenGL сглаживать линии, полигоны и устранять неровности перспективной проекции)
    glHint(GL_POLYGON_SMOOTH_HINT, GL_NICEST);
    glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);
    glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
    //Сглаживание линий
    glEnable(GL_LINE_SMOOTH);
    //Включаем буфер глубины
    glClearDepth(1.0f);
    glEnable(GL_DEPTH_TEST);
    glDepthFunc(GL_LEQUAL);
    //Включаем освещение
    glEnable(GL_LIGHTING);
    glEnable(GL_LIGHT0);
    glEnable(GL_COLOR_MATERIAL);
    //Включаем альфа-смешивание (прозрачность)
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
    //Включаем текстуры
    glEnable(GL_TEXTURE_2D);

    //Инициализируем массив обложек
    InitCoversArray();
}

//Функция, вызываемая при изменении размеров окна
void reshape(int w, int h)
{
    //Устанавливаем рабочую область размером во всё окно
    glViewport(0, 0, w, h);
    //Устанавливаем перспективную проекцию с углом обзора в 45 градусов
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(45.0, (GLdouble)w/(GLdouble)h, 0.1, 150);
    //Устанавливаем модельно-видовую матрицу
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    //Устанавливаем камеру (первые три числа), точку куда смотрим (вторая тройка чисел) и нормаль (последние три числа)
    gluLookAt(0.0, 0.0, 30.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);
}

//Функция, вызываемая при перерисовке экрана
void display()
{
    //Очищаем буфер цвета и буфер глубины
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    //Вызываем нашу функцию рисования обложек
    DrawCovers();
    //Меняем экранные буферы местами (у нас включена двойная буферизация)
    glutSwapBuffers();
}

void keyboardSpec(int key, int x, int y)
{
    //Вызывает функцию движения обложек по нажатию клавиш
    switch(key)
    {
        case GLUT_KEY_LEFT:
            if(!leftLocked)
                DrawAnimatedCovers(LEFT);
            break;
        case GLUT_KEY_RIGHT:
            if(!rightLocked)
                DrawAnimatedCovers(RIGHT);
            break;
    }
    //Принудительно вызывает перерисовку экрана
    glutPostRedisplay();
}

void keyboard(unsigned char key, int x, int y)
{
    switch(key)
    {
        //Выход по Esc
        case 27:
            ClearCovers();
            exit(0);
            break;
    }
    //Принудительно вызывает перерисовку экрана
    glutPostRedisplay();
}

void mouse(int x, int y)
{
    //Обрабатывает движение мышь при нажатии любой кнопки
    static int prevX = 500;
    if(x - prevX > 0 && !rightLocked)
        DrawAnimatedCovers(RIGHT);
    else if(x - prevX < 0 && !leftLocked)
        DrawAnimatedCovers(LEFT);
    prevX = x;
    //Принудительно вызывает перерисовку экрана
    glutPostRedisplay();
}

int main (int argc, char * argv[])
{
    glutInit(&argc, argv);
    //Включаем двойную буферизацию, четырехкомпонентный цвет, z-буфер и мультисемплинг (сглаживание)
    glutInitDisplayMode(GLUT_DOUBLE|GLUT_RGBA|GLUT_DEPTH|GLUT_MULTISAMPLE);
    //Задаем размеры окна
    glutInitWindowSize(W_WIDTH, W_HEIGHT);
    //Задаем стартовое положение окна
    glutInitWindowPosition(100, 100);
    //Название окна
    glutCreateWindow("Cower Flow");
#ifdef _WIN32
    //Скрываем окно консоли в Windows
    FreeConsole();
#endif
    //Вызываем свою функцию инициализации
    init();
    
    //Задаем callback'и
    glutReshapeFunc(reshape);
    glutDisplayFunc(display);
    glutMotionFunc(mouse);
    glutKeyboardFunc(keyboard);
    glutSpecialFunc(keyboardSpec);
    
    glutMainLoop();
    return 0;
}

Терминология: Двойная буферизация Альфа-смешивание Мультисемплинг (антиалиасинг) Z-буферизация

Итог



Для тех, кто не смог (или не захотел) осилить статью: код, бинарные файлы для Windows, Linux и Mac OS X (все 32-битные), обложки для теста.

2 комментария: