Графика для Windows средствами DirectDraw


Функция DrawScene()


Классы, производные от DirectDrawWin, реализуют функцию DrawScene(), в которой происходит обновление экрана. Версия DrawScene() из класса BounceWin выглядит так:


void BounceWin::DrawScene() { CRect limitrect=GetDisplayRect(); x+=xinc; y+=yinc; if (x<-160 || x>limitrect.right-160) { xinc=-xinc; x+=xinc; } if (y<-100 || y>limitrect.bottom-120) { yinc=-yinc; y+=yinc; } ClearSurface( backsurf, 0);

BltSurface( backsurf, surf1, x, y );

primsurf->Flip( 0, DDFLIP_WAIT ); }


Сначала функция GetDisplayRect() получает объект CRect, хранящий ширину и высоту текущего видеорежима. Эти размеры будут использоваться для ограничения перемещений растрового изображения в соответствии с видеорежимом. Далее вычисляются значения переменных x и y класса BounceWin, определяющих местонахождение растра на экране.

Затем мы вызываем функцию ClearSurface() и передаем ей два аргумента: указатель backsurf и 0. Это приводит к тому, что вторичный буфер заполняется черным цветом. Хотя я упоминал о том, что использование ClearSurface() иногда осложняется различными форматами пикселей, заполнение поверхностей черным работает надежно. Для палитровых поверхностей 0 означает черный цвет, потому что по умолчанию он стоит в палитре на первом месте; для беспалитровых поверхностей 0 всегда соответствует черному цвету.

Функция DrawScene() использует функцию DirectDrawWin::BltSurface() для копирования поверхности surf1 на поверхность backsurf. Два последних аргумента BltSurface() определяют точку поверхности-приемника, куда должно быть скопировано содержимое источника. Для выполнения этой операции можно было бы воспользоваться функцией Blt() или BltFast() интерфейса DirectDrawSurface, но мы не делаем этого из-за возможного отсечения. Обратите внимание - код, определяющий положение растра, позволяет источнику выйти за пределы приемника, в результате чего может потребоваться отсечение. Мы не можем воспользоваться функцией Blt(), потому что тогда потребовалось бы присоединить к приемнику объект DirectDrawClipper, чего мы не делаем. Функция BltFast() тоже не подходит, потому что она вообще не поддерживает отсечения. Функция BltSurface() автоматически выполняет отсечение, а функции Blt() и BltFast() вызываются внутри нее.

Но перед тем, как переходить к функции BltSurface(), мы закончим рассмотрение функции DrawScene(). Она завершается вызовом функции Flip(). При этом происходит переключение страниц, и подготовленный нами кадр отображается на экране. Функция Flip() получает два аргумента: указатель на поверхность и переменную типа DWORD, предназначенную для установки флагов. Указатель на поверхность необходим лишь в нестандартных ситуациях, когда в переключении поверхностей участвует несколько вторичных буферов. Второй аргумент обычно содержит флаг DDFLIP_WAIT, показывающий, что возврат из функции должен происходить только после того, как переключение страниц завершится.


Функция DrawScene() отвечает за подготовку нового кадра во вторичном буфере, обновление курсора и переключение страниц. Функция DrawScene()

выполняется в основном потоке, поэтому она должна синхронизировать доступ к первичной поверхности и очереди событий мыши с потоком ввода. Функция DrawScene() приведена в листинге7.4.

Листинг 7.4. Функция DrawScene()




void CursorWin::DrawScene() { //------ Проверить клавишу ESCAPE ------- static char key[256]; keyboard->GetDeviceState( sizeof(key), &key ); if ( key[DIK_ESCAPE] & 0x80 ) PostMessage( WM_CLOSE );

//------ Обычные задачи ------ ClearSurface( backsurf, 0 );

BltSurface( backsurf, dm_surf, 539, 0 );

static coil_idx; BltSurface( backsurf, coil[coil_idx], coilx, coily ); coil_idx=(coil_idx+1)%coil_frames;

//------ Начало синхронизированной секции ------ critsection.Lock();

//------ Сохранить область вторичного буфера под курсором RECT src; src.left=curx; src.top=cury; src.right=curx+cursor_width; src.bottom=cury+cursor_height; cursor_under->BltFast( 0, 0, backsurf, &src, DDBLTFAST_WAIT );

//------ Нарисовать курсор во вторичном буфере backsurf->BltFast( curx, cury, cursor, 0, DDBLTFAST_SRCCOLORKEY | DDBLTFAST_WAIT );

primsurf->Flip( 0, DDFLIP_WAIT ); while (primsurf->GetFlipStatus(DDGFS_ISFLIPDONE)!=DD_OK) // ничего не делать (ждать, пока закончится // переключение страниц)

int x, y; BOOL newclick=FALSE; int count=mouseclickqueue.GetCount(); while (count--) { MouseClickData mc=mouseclickqueue.RemoveTail(); if (mc.button==0) { x=mc.x; y=mc.y; newclick=TRUE; } }

critsection.Unlock(); //------ Конец синхронизированной секции -------

//------ Сделать паузу в соответствии с выбранной задержкой ---- if ( delay_value[dm_index]!=0) Sleep( delay_value[dm_index] );

//------ Обновить меню задержки -------- if (newclick) { int max_index=sizeof(delay_value)/sizeof(int)-1; int menux=screen_width-dm_width+dm_margin; int menuw=dm_width-dm_margin*2; if (x>=menux && x<=menux+menuw) { int index=(y-dm_header)/dm_entrysize; if (index>=0 && index<=max_index && index!=dm_index) { dm_index=index; UpdateDelaySurface(); } } } }

<


Наконец, все готово к отображению кадров видеоролика. Для этого мы подготавливаем и выводим очередной кадр при каждом вызове функции DrawScene() классом DirectDrawWin. Функция DrawScene() выглядит так:


void AviPlayWin::DrawScene() { long r; r=AVIStreamRead( avistream, curframe, 1, rawdata, buflen, 0, 0 ); if (r) { TRACE("AVIStreamRead failed: "); switch (r) { case AVIERR_BUFFERTOOSMALL: TRACE("BUFFERTOOSMALL\n"); break; case AVIERR_MEMORY: TRACE("MEMORY\n"); break; case AVIERR_FILEREAD: TRACE("FILEREAD\n"); break; } }

r=ICDecompress( decomp, 0, srcfmt, rawdata, dstfmt, finaldata); UpdateAviSurface(); backsurf->BltFast( x, y, avisurf, 0, DDBLTFAST_WAIT );

curframe=(curframe<endframe) ? curframe+1 : startframe;

primsurf->Flip( 0, DDFLIP_WAIT ); }


Функция DrawScene() с помощью функции AVIStreamRead() извлекает очередной кадр из AVI-потока, после чего сохраняет полученные данные в буфере rawdata. Я оставил в ней несколько макросов TRACE(), которые пригодились мне при отладке, но надеюсь, что вам они не понадобятся.

Затем мы вызываем функцию ICDecompress() и передаем ей логический номер декомпрессора, ранее полученный от функции LoadAvi(). Аргументами функции ICDecompress() являются два буфера — первый содержит необработанные (сжатые) данные, а второй — восстановленное изображение.

Функция UpdateAviSurface() копирует восстановленный кадр на поверхность AVI. Эта функция рассматривается ниже.

Подготовленная поверхность AVI копируется во вторичный буфер функцией BltFast() интерфейса DirectDrawSurface. После этого переменная curframe увеличивается или сбрасывается в зависимости от ее значения и количества кадров в ролике. Наконец, функция Flip() интерфейса DirectDrawSurface выводит кадр на экран.




Инициализация приложения завершена, теперь можно заняться функцией DrawScene(). Эта функция выполняет проверку столкновений, строит кадр во вторичном буфере и переключает страницы. В программе Bumper() функция DrawScene() выглядит так:


void BumperWin::DrawScene() { ASSERT(nsprites>0); ASSERT(text);

for (int s1=0;s1<nsprites;s1++) for (int s2=s1+1;s2<nsprites;s2++) if (SpritesCollide( sprite[s1], sprite[s2] )) { sprite[s1]->Hit( sprite[s2] ); sprite[s2]->Hit( sprite[s1] ); }

for (int i=0;i<nsprites;i++) sprite[i]->Update();

ClearSurface( backsurf, 0 ); for (i=0;i<nsprites;i++) { Sprite* s=sprite[i]; BltSurface( backsurf, *s, s->GetX(), s->GetY(), TRUE ); }

BltSurface( backsurf, text, 0, 448, TRUE );

primsurf->Flip( 0, DDFLIP_WAIT ); }


Проверка столкновений осуществляется во вложенном цикле. Для каждой пары спрайтов вызывается функция SpritesCollide(), а при обнаруженном столкновении вызывается функция Hit(), которой в качестве аргументов передаются оба столкнувшихся спрайта. Напомню, что функция Sprite::Hit()

реализует стадию подтверждения в нашей модели проверки столкновений. Она сохраняет данные о столкновении, но не вносит никаких изменений в состояние спрайтов.

В отдельном цикле для каждого спрайта вызывается функция Update(). На этом шаге реализуется стадия реакции. При обнаруженном столкновении функция Update() определяет новую траекторию спрайта по сохраненным ранее данным. Кроме того, функция Update() изменяет текущее положение спрайта.

После того как все столкновения будут обнаружены и обработаны, мы стираем вторичный буфер функцией DirectDrawWin::ClearSurface() и выводим каждый спрайт функцией BltSurface(). Обратите внимание на то, что вторым аргументом BltSurface() является указатель на сам объект Sprite. В данном случае оператор LPDIRECTDRAWSURFACE() преобразует объект Sprite

в указатель на поверхность, соответствующую данному спрайту. Также стоит заметить, что координаты спрайтов определяются функциями GetX() и GetY(). После прорисовки всех спрайтов в левом нижнем углу вторичного буфера выводится поверхность меню. Функция Flip() переключает страницы и отображает кадр на экране.



Содержание раздела