Читайте также:
|
|
#include <stdio.h>
#include<stdlib.h>
#include<string.h>
#include <time.h>
#include<dos.h>
#include "paral.h"
char *MemBuf, // указатель на буфер памяти
*BackGroundBmp, // указатель на скрытую битовую карту
*VideoRam; // указатель на память VGA
PcxFile pcx; // структура PCX-файла
int volatile KeyScan; // изменения клавиатурного обработчика
int frames=0, // количество нарисованных кадров
PrevMode; // сохраняет исходный видеорежим
int background;
void _interrupt (*OldInt9)(void); // указатель на клавиатурный
// обработчик BIOS
// Данная процедура загружает 256-цветный PCX-файл
int ReadPcxFile(char *filename,PcxFile *pcx)
{
long i;
int mode=NORMAL,nbytes;
char abyte,*p;
FILE *f;
f=fopen(filename,"rb");
if(f==NULL)
return PCX_NOFILE;
fread(&pcx->hdr,sizeof(PcxHeader),1,f);
pcx->width=1+pcx->hdr.xmax-pcx->hdr.xmin;
pcx->height=1+pcx->hdr.ymax-pcx->hdr.ymin;
pcx->imagebytes= (unsigned int) (pcx->width*pcx->height);
if(pcx->imagebytes > PCX_MAX_SIZE)
return PCX_TOOBIG;
pcx->bitmap= (char*)malloc (pcx->imagebytes);
if(pcx->bitmap == NULL)
return PCX_NOMEM;
p=pcx->bitmap;
for(i=0;i<pcx->imagebytes;i++)
{
if (mode == NORMAL)
{
abyte=fgetc(f);
if((unsigned char)abyte > Oxbf)
{
nbytes=abyte & Ox3f;
abyte=fgetc(f);
if(--nbytes > 0) mode=RLE;
}
}
else if(--nbytes == 0) mode=NORMAL;
*p++=abyte;
}
fseek(f,-768L,SEEK_END); // извлечь палитру из PCX-файла
fread(pcx->pal,768,1,f);
p=pcx->pal;
for(i=0;i<768;i++)
*p++=*p >>2;
fclose(f);
return PCX_OK; // успешное завершение
}
// Это новый обработчик прерывания 9h. Он позволяет осуществлять
// мягкий скроллинг. Если обработчик BIOS не будет запрещен,
// удержание клавиш управления курсором приведет к переполнению
// буфера клавиатуры и очень неприятному звуку из динамика.
void _interrupt NewInt9(void)
{
register char x;
KeyScan=inp(0х60); // прочитать символ с клавиатуры
x=inp(0x61); // сообщить клавиатуре, что символ обработан
outp(0x61,(х|0х80));
outp(0х61,x);
outp (0х20,0х20}; // сообщить контроллеру
// прерываний о завершении прерывания
if(KeyScan == RIGHT ARROW REL || // проверить кнопки
KeyScan == LEFT_ARROW_REL)
KeyScan=0;
}
// Функция восстанавливает прежний обработчик прерываний клавиатуры
void RestoreKeyboard(void) {
_dos_setvect (KEYBOARD, OldInt9); // восстановить прежний вектор
}
// Эта функция сохраняет указатель вектора клавиатурного прерывания
// bios, а затем инсталлирует новый вектор прерывания, определенный
//в программе.
void InitKeyboard(void)
{
OldInt9=_dos_getvect(KEYBOARD); // сохранить вектор BIOS
_dos_setvect (KEYBOARD,NewInt9);// инсталлировать новый вектор
}
// Функция вызывает видео BIOS и заполняет все необходимые регистры
// для работы с палитрой, задаваемой массивом раl[]
void SetAllRgbPalette(char *pal)
{
struct SREGS s;
union REGS r;
segread(&s); // получить значение сегмента
s.es=FP_SEG((void far*)pal); // ES указывает на pal
r.x.dx=FP_OFF((void far*)pal); // получить смещение pal
r.x.ax=0xl012; // int l0h, функция 12h
// (установка регистров DAC)
r.x.bx=0; // первый регистр DAC
r.x.cx=256; // количество регистров DAC
int86x(0х10,&r,&r,&s); // вызвать видео BIOS }
// Функция устанавливает видеорежим BIOS 13h
// это MCGA-совместимый режим 320х200х256 цветов
void InitVideo()
{
union REGS r;
r.h.ah=0x0f; // функция BIOS Ofh
int86(0х10,&r,&r); // вызывать видео BIOS
PrevMode=r.h.al; // сохранить текущий видеорежим
r.x.ax=0xl3; // установить режим 13h
int86(0х10,&r,&r); // вызвать видео BIOS
VideoRam=MK_FP(0xa000,0); // создать указатель на видеопамять
}
// Функция восстанавливает изначальный видеорежим
void RestoreVideo() {
union REGS r;
r.x.ax=PrevMode; // восстановить начальный видеорежиы
int86(0xl0,&r,&r); // вызвать видео BIOS
}
// Функция загружает битовые карты
int InitBitmaps()
{
int r;
background=l;
r=ReadPcxFile("backgrnd.pcx",&pcx); // прочитать битовую карту
if(r!= РСХ_ОК) // выход при возникновении ошибки return FALSE;
BackGroundBmp=pcx.bitmap; // сохранить указатель битовой
// карты
SetAllRgbPalette(pcx.pal); // установить палитру VGA
MemBuf=malloct(MEMBLK); // выделить память под буфер
if(MemBuf == NULL) // проверить на ошибки при
// выделении памяти
return FALSE;
memset(MemBuf,0,MEMBLK); // очистить
return TRUE; // Порядок!
}
// Функция освобождает выделенную программой память
void FreeMem()
{
free(MemBuf);
free(BackGroundBmp);
}
// функция рисует прокручиваемую битовую карту, не содержащую
// прозрачных пикселей; для скорости используется функция memcpyO;
// аргумент ScrollSplit задает столбец по которому битовая карта
// разбивается на две части
void OpaqueBlt(char *bmp,int StartY,int Height,int ScrollSplit)
{
char *dest;
int i;
dest=MemBuf+StartY*320; // вычисляем начальную позицию буфера
for(i=0;i<Height;i++)
{
// нарисовать левую половину битовой карты в правой половине буфера menicpy(dest+ScrollSplit,bmp,VIEW_WIDTH-ScrollSplit);
// нарисовать правую половину битовой карты в левой половине буфера memcpy(dest,bmp+VIEW_WIDTH-ScrollSplit,ScrollSplit);
bmp+=VIEW_WIDTH;
dest+=VIEW_WIDTH;
} } // конец функции
// Функция рисует смещающиеся слои
void DrawLayers()
{
OpaqueBlt(BackGroundBmp,0,100,background);
}
// Функция, обеспечивающая анимацию изображения.
// Наиболее критичная по времени выполнения.
// Для оптимизации как эту функцию, так и процедуры,
// которые она вызывает, рекомендуется переписать на ассемблере
// (В среднем это увеличивает производительность на 30%)
void AnimLoop()
{
while(KeyScan!= ESC_PRESSED) // цикл, пока не нажата ЕSС
{
switch(KeyScan) // обработать нажатую клавишу
{
case RIGHT_ARROW_PRESSED: // нажата правая стрелка
background-=1; // скроллировать фон на 2
// пикселя влево
if(background < 1) // еще не конец образа?
background+=VIEW_WIDTH; //...тогда, можно смещать
// фон дальше
break;
case LEFT ARROW_PRESSED: // нажата левая стрелка
background+=1; // скроллировать фон на 2
// пикселя вправо
if(background > VIEW_WIDTH-1) // еще не конец образа
background-=VIEW_WIDTH; //...тогда можно смещать
// фон дальше
break;
default: // обработать все остальные
// клавиши break;
} DrawLayers();
memcpy(VideoRam,MemBuf,MEMBLK); // копировать MemBuf в
// VGA-память
frames++;
} }
// Функция осуществляет полную инициализацию
void Initialize()
{
InitVideo(); // установить режим 13h
InitKeyboard(); // установить собственный обработчик
// прерываний клавиатуры
if(!InitBitmaps()) // прочитать битовые образы
Cleanup(); // освободить память
printf("\nError loading bitmaps\n");
exit(l);
} }
// функция восстанавливает исходное состояние системы
void Cleanup()
{
RestoreVideo(); // восстановить VGA
RestoreKeyboard(); // восстановить вектор клавиатуры
FreeMem(); // освободить память
}
// Начало основной программы
int main()
{
clock_t begin, fini;
Initialize();
begin=clock(); // получить "тики" часов при старте
AnimLoop(); // начать анимацию изображения
fini=clock(); // получить "тики" часов в конце
Cleanup(); // освободить память
printf("Frames: %d\nfps: %gf\n", frames,
(float)CLK_TCK*frames/(fini-begin));
return 0;
}
Несколько смещающихся слоев
Вы теперь знаете, как перемещать изображение влево и вправо, а также осуществлять его циклический возврат к границам экрана. Следующим шагом попробуем смещать не один, а сразу несколько слоев изображения, причем двигать их будем с различными скоростями, что обеспечит иллюзию трехмерной графики.
Прежде всего, нужно уметь определять скорости движения различных слоев. Для простоты можно предположить, что скорость перемещения слоев линейно уменьшается с их «удалением» от наблюдателя. Это предположение удовлетворяет тому условию, что ближние слои движутся быстрее, чем дальние. Это справедливо и для предельного случая, когда бесконечно удаленный слой совершенно не перемещается. Так могут выглядеть звезды, облака или далекие горные цепи. Обратите внимание, что предположение о линейном изменений скорости слоев не вполне корректно, но, тем не менее, это является разумным компромиссом.
Не стоит пытаться вычислять точные скорости смещения (если вы сделаете это, то, скорее всего, получите число с плавающей запятой, а по известным причинам скорость смещения должна иметь целочисленное значение). В действительности демонстрационная программа параллакса из Листинга 17.3 использует простое правило для скоростей смещения: каждый следующий слой смещается вдвое медленнее, чем слой впереди него. Запомните: важно только то, чтобы относительное движение между различными слоями обеспечивало ощущение глубины пространства.
Изображение строится слой за слоем от заднего к переднему плану. В результате ближние слои перекрывают и прячут некоторые части более удаленных (этот метод, известный также как Алгоритм Художника, детально обсуждался в шестой главе, «Третье измерение»). Такое сокрытие дальних слоев ближними создает ощущение перспективы. Что же касается площади перекрытия, то она зависит от изображений. Функция OpaqueBIt() может быть использована также и для рисования перекрывающихся слоев. Плохо только, что при выводе изображения она стирает имеющуюся картинку. Это не очень практично для большинства типов декорации. Поэтому нам нужно научиться рисовать образы так, чтобы они закрывали собой уже выведенное изображение не полностью, а лишь по контуру и чтобы вся внешняя область оставалась без изменения.
«Прозрачные» пиксели
«Прозрачными» будем называть такие пиксели, которые при выводе на экран пропускаются и не перекрывают имеющееся изображение. Один из методов получения такого результата заключается в проверке значения цвета каждого пикселя перед тем, как он будет нарисован. Если цвет пикселя совпадает с «прозрачным», мы пропускаем данный пиксель и переходим к следующему. Такое дополнение к алгоритму ложится тяжелым бременем на нашу борьбу за скорость работы программы в процессе выполнения, а тем более — при выводе на экран. Ведь теперь мы не можем воспользоваться функцией memcpy() для вывода целой строки пикселей на экран, а должны применить цикл for() для изображения каждой точки отдельно.
Листинг 17.3 содержит новую функцию, называемую TransparentBlt(). Она заменит нам OpaqueBIt(). Разница между ними состоит только в том, что TransparentBlt() пропускает «прозрачные» пиксели (и это тоже тормозит работу программы).
Но как же TransparentBlt() отличает «прозрачные» пиксели от «непрозрачных»? Я решил, что любой пиксель со значением цвета, равным 0 (обычно, это черный) будет «прозрачным», но вы можете назначить для этого другой цвет. Функция пропускает любой пиксель, у которого значение цвета равно объявленной константе TRANSPARENT. Программа из Листинга 17.3 (PARAL1.C) является демонстрацией смещения двух повторяющихся слоев. Дальний слой сплошной, в то время как ближний включает в себя «прозрачные» пиксели. Для вывода изображений используются функции OpaqueBIt() и TransparentBit() соответственно. Несмотря на то, что у нас имеется всего два движущихся слоя, эффект получается довольно реалистичным. Как и в программе из Листинга 17.2, курсорные клавиши «влево» и «вправо» перемещают изображение по горизонтали, а для завершения программы нужно нажать Esc.
Обратите внимание, что скорость смены кадров в этой программе значительно ниже, чем в предыдущей. Это происходит из-за использования функции для работы с «прозрачными» пикселями. На компьютере с процессором 386SX/25 я получил примерно 10 кадров в секунду. В принципе, это не так уж и плохо для программы, написанной полностью на Си.
Дата добавления: 2015-07-12; просмотров: 79 | Нарушение авторских прав
<== предыдущая страница | | | следующая страница ==> |
Листинг 17.1. Файл заголовка демонстрационной программы циклического скроллинга (PARAL.H). | | | Листинг 17.3. Простой двойной параллакс (PARAL1.C). |