Управляемая анимация изображения
демонстрируется в следующем примере. Его идея взята из примера OpenGL, множество которых можно найти в MSDN. ¨ Сначала загружаем растровое изображение из bmp -файла и демонстрируем его в окне OpenGL; ¨ Затем мышью указываем произвольное количество точек, которые описывают будущую траекторию движения одной (первой) точки. Траектория определяет цепь последующих трансформаций текстуры; ¨ Запоминаем координаты точек в контейнере STL типа vector; ¨ Для плавности анимации генерируем промежуточные точки, разбивая расстояния между последовательно идущими точками траектории на какое-то число частей. Оно определяется экспериментально и зависит от скорости передачи изображения (rendering), то есть от качества видео-карты; ¨ Двигаясь вдоль всех точек траектории, последовательно изображаем четыре треугольные текстуры, которые, будучи составленными вместе, дают прямоугольное изображение; ¨ Зацикливаем весь процесс, заставляя изображение бесконечно выполнять некий управляемый нами танец, пока не придет следующее сообщение о нажатии левой кнопки мыши.
Как видите, алгоритм демонстрации эффекта растяжения текстур достаточно сложен. В связи с этим выделим 4 режима, в которых будет работать приложение (выбор файла с изображением, вывод изображения, ввод точек траектории, анимация изображения). Нам понадобится некоторый набор переменных, который удобно определить глобально. С вершинами треугольников будут ассоциированы координаты двухмерной текстуры, поэтому в класс CVert кроме пространственных координат вершины (v) мы инкапсулировали текстурные координаты (g). GLUT_RGBImageRec *pImg; // Структура, описывающая RGB-изображение
enum eMode // Режим перерисовки { eNOP, // Ничего не делаем eANIMATE, // Запускаем анимацию ePOINTS, // Рисуем точки траектории eSTILL // Рисуем весь bitmap };
eMode gMode = eNOP;// Текущий режим
class Point2D { public: float x, y; Point2D () { x=0.; y=0.; } Point2D (float fx, float fy) { x=fx; y=fy; } Point2D operator- (const Point2D& pt) { return Point2D (x-pt.x, y-pt.y); } void operator+= (const Point2D& pt) { x += pt.x; y += pt.y; } };
class CVert { public: Point2D v, g; // Координаты вершины и текстуры в 2D static Point2D dv; // Сдвиг точки траектории (общий для всех объектов)
CVert(): v(), g() {} CVert (Point2D pv, Point2D pt): v(pv), g(pt) {}
CVert& operator= (const CVert& vt) { v = vt.v; g = vt.g; return *this; } void Shift() { v += dv; } // Сдвиг на dv };
Point2D CVert::dv; // Определение статической переменной класса CVert gVerts[5]; // 5 точек, разделяющих изображение на 4 треугольника vector<Point2D> gPoints;// Траектория движения первой точки
//==== Увеличение количества разбиений gSteps вносит плавность анимации, замедляя ее int gSteps = 10,// Количество шагов деления интервала между соседними точками траектории gCX, gCY, // Размеры bitmap giLast, // Индекс предыдущей точки траектории giNext, // Индекс следующей точки траектории giCur; // Индекс текущей точки траектории
char szFile[MAX_PATH]; // Файловый путь
char* FileDlg (bool bRead) // Работа с файловым диалогом (bRead – режим открытия файла) { TCHAR *szFilter = // Фильтр файловых расширений TEXT("BMP Files (*.bmp)\0*.bmp\0") TEXT("DIB Files (*.dib)\0*.dib\0");
char szCurDir[MAX_PATH]; // Начнем с текущей директории ::GetCurrentDirectory (MAX_PATH-1, szCurDir);
OPENFILENAME ofn; // Структура, используемая стандартным диалогом ZeroMemory (&ofn, sizeof(OPENFILENAME));
//====== Параметры настройки диалога ofn.lStructSize = sizeof(OPENFILENAME); ofn.hwndOwner = glutGetHWND(); // Окно, которому принадлежит диалог ofn.lpstrFilter = szFilter; ofn.nFilterIndex = 1; // Начальный индекс массива строк фильтра равен 1 ofn.lpstrFile = szFile; ofn.nMaxFile = sizeof(szFile); ofn.lpstrTitle = "Find a bitmap file";// Заголовок диалога ofn.nMaxFileTitle = sizeof (ofn.lpstrTitle); ofn.lpstrInitialDir = szCurDir; // Начальная директория ofn.Flags = OFN_EXPLORER|OFN_OVERWRITEPROMPT; // Стиль EXPLORER (работает только в Win2K) ofn.lpstrDefExt = "bmp";
//====== Создаем и открываем диалог (при неудаче возвращает 0) BOOL b = bRead? GetOpenFileName(&ofn): GetSaveFileName(&ofn);
return b? ofn.lpstrFile: 0; }
bool Read() // Чтение изображения из файла { char *fn; if ((fn = FileDlg (true))!= 0) { pImg = glutDIBImageLoad (fn); //или glutRGBImageLoad //==== Размеры bitmap в OpenGL должны быть степенью двойки gCX = int(pow (2., int(log (pImg->sizeX)/log(2.)))); gCY = int(pow (2., int(log (pImg->sizeY)/log(2.)))); return true; } else return false; }
void DrawImage() { glRasterPos2i(0, 0); glDrawPixels (pImg->sizeX, pImg->sizeY,GL_RGB,GL_UNSIGNED_BYTE, pImg->data); }
void DrawPoint() { glColor3f(1.0, 0.0, 1.0); glPointSize(3.0);
glBegin(GL_POINTS); for (int i = 0; i < gPoints.size(); i++) glVertex2f(gPoints[i].x, gPoints[i].y); glEnd(); }
void UpdateStep() // Вычисляем шаг смещения (для промежуточных точек) { Point2D pt = gPoints[giNext] - gPoints[giLast]; CVert::dv = Point2D (pt.x / gSteps, pt.y / gSteps); }
void Animate() { //==== Изображение составлено из 4-х текстурных треугольников //==== 4 угловых точки и одна точка, движущаяся по траектории
//==== Сначала растянем нижний треугольник glBegin(GL_TRIANGLES); // |------------| glTexCoord2f(gVerts[0].g.x, gVerts[0].g.y); // | | glVertex2f(gVerts[0].v.x, gVerts[0].v.y); // | | glTexCoord2f(gVerts[1].g.x, gVerts[1].g.y); // | 4 | glVertex2f(gVerts[1].v.x, gVerts[1].v.y); // | / \ | glTexCoord2f(gVerts[4].g.x, gVerts[4].g.y); // | / \ | glVertex2f(gVerts[4].v.x, gVerts[4].v.y); // 0___________\1 glEnd(); //==== Затем – правый треугольник glBegin(GL_TRIANGLES); // |------------2 glTexCoord2f(gVerts[1].g.x, gVerts[1].g.y); // | / | glVertex2f(gVerts[1].v.x, gVerts[1].v.y); // | / | glTexCoord2f(gVerts[2].g.x, gVerts[2].g.y); // | 4 | glVertex2f(gVerts[2].v.x, gVerts[2].v.y); // | \ | glTexCoord2f(gVerts[4].g.x, gVerts[4].g.y); // | \ | glVertex2f(gVerts[4].v.x, gVerts[4].v.y); // |____________1 glEnd(); //==== После этого – верхний треугольник glBegin(GL_TRIANGLES); // 3------------2 glTexCoord2f(gVerts[2].g.x, gVerts[2].g.y); // | \ / | glVertex2f(gVerts[2].v.x, gVerts[2].v.y); // | \ / | glTexCoord2f(gVerts[3].g.x, gVerts[3].g.y); // | 4 | glVertex2f(gVerts[3].v.x, gVerts[3].v.y); // | | glTexCoord2f(gVerts[4].g.x, gVerts[4].g.y); // | | glVertex2f(gVerts[4].v.x, gVerts[4].v.y); // |____________| glEnd(); //==== Наконец, левый треугольник glBegin(GL_TRIANGLES); // 3------------| glTexCoord2f(gVerts[3].g.x, gVerts[3].g.y); // | \ | glVertex2f(gVerts[3].v.x, gVerts[3].v.y); // | \ | glTexCoord2f(gVerts[0].g.x, gVerts[0].g.y); // | 4 | glVertex2f(gVerts[0].v.x, gVerts[0].v.y); // | / | glTexCoord2f(gVerts[4].g.x, gVerts[4].g.y); // | / | glVertex2f(gVerts[4].v.x, gVerts[4].v.y); // 0____________| glEnd();
//=== Смещаем пятую точку на шаг, но не трогаем ее текстурных координат!!! if (++giCur <= gSteps) gVerts[4].Shift(); else // Переходим к следующей точке траектории { giLast = giNext; if (++giNext == gPoints.size()) // Если последняя – зацикливаем giNext = 0;
UpdateStep(); giCur = 0; } }
void InitVList()// Инициализация массива точек (конверта) { giLast = 0; giNext = 1; UpdateStep();
gVerts[0] = CVert (); // Bottom Left corner gVerts[1] = CVert (Point2D(gCX,0.), Point2D(1.,0.)); // Bottom Right corner gVerts[2] = CVert (Point2D(gCX, gCY), Point2D(1.,1.)); // Top Right corner gVerts[3] = CVert (Point2D(0.,gCY), Point2D(0.,1.)); // Top Left corner
//==== Текстурные координаты первой точки траектории Point2D tex(gPoints[0].x / gCX, gPoints[0].y / gCY); gVerts[4] = CVert (gPoints[0], tex); // Пятая точка – это первая точка траектории }
void OnPaint() { switch (gMode) { case eANIMATE: Animate(); break; case ePOINTS: DrawPoint(); break; case eSTILL: DrawImage(); break; } glutSwapBuffers(); }
void OnKey (unsigned char ch, int x, int y) { switch (ch) { case 27: exit(0); break; case ' ': if (gPoints.size() > 1) { InitVList(); giCur = 0; glEnable(GL_TEXTURE_2D); gMode = eANIMATE; glutIdleFunc(OnPaint); } break; } glutPostRedisplay(); }
void OnMouse(int button, int state, int x, int y) { if (state == GLUT_DOWN) { if (gMode == eANIMATE) { glDisable(GL_TEXTURE_2D); gPoints.clear(); gMode = eSTILL; glutIdleFunc(0); } else { gPoints.push_back(Point2D(x, gCY - y)); gMode = ePOINTS; } } }
void Init() { glViewport(0, 0, gCX, gCY); gluOrtho2D(0, gCX, 0, gCY);
glPixelStorei(GL_UNPACK_ALIGNMENT, 1); glPixelStorei(GL_PACK_ALIGNMENT, 1);
BYTE *buf = (BYTE*) malloc (3 * gCX * gCY); gluScaleImage (GL_RGB, pImg->sizeX, pImg->sizeY, GL_UNSIGNED_BYTE, pImg->data, gCX, gCY, GL_UNSIGNED_BYTE, buf);
free (pImg->data); pImg->data = buf; pImg->sizeX = gCX; pImg->sizeY = gCY;
glTexEnvi (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL); glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP); glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP); glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexImage2D (GL_TEXTURE_2D, 0, 3, pImg->sizeX, pImg->sizeY, 0, GL_RGB, GL_UNSIGNED_BYTE, (BYTE*)pImg->data);
giLast = giNext = 0; gMode = eSTILL; }
|