Главная страница Случайная страница Разделы сайта АвтомобилиАстрономияБиологияГеографияДом и садДругие языкиДругоеИнформатикаИсторияКультураЛитератураЛогикаМатематикаМедицинаМеталлургияМеханикаОбразованиеОхрана трудаПедагогикаПолитикаПравоПсихологияРелигияРиторикаСоциологияСпортСтроительствоТехнологияТуризмФизикаФилософияФинансыХимияЧерчениеЭкологияЭкономикаЭлектроника |
Листинг 8.2. Демонстрационная программа новой функции масштабирования (VYREN.C).
// ВКЛЮЧАЕМЫЕ ФАЙЛЫ //////////////////////////////////////// #include < io.h> #include < conio.h> #include < stdio.h> #include < stdlib.h> #include < dos.h> #include < bios.h> #include < fcntl.h> #include < memory.h> #include < malloc.h> #include < math.h> #include < string.h> #include < graph.h> #include " graphics.h" // включаем нашу графическую библиотеку // ПРОТОТИПЫ //////////////////////////////////////////////////////// void Create_Scale_Data_X(int scale, int far *row); void Create_Scale_Data_Y(int scale, int * row); void Build_Scale_Table(void); void Scale_Sprite(sprite_ptr sprite, int scale); void Clear_Double_Buffer(void); // ОПРЕДЕЛЕНИЯ ///////////////////////////////////////////// #define MAX_SCALE 200 // число звезд на звездном небе #define SPRITE_X_SIZE 80 // максимальные размеры #define SPRITE_Y_SIZE 48 // растрового изображения // ГЛОБАЛЬНЫЕ ПЕРЕМЕННЫЕ /////////////////////////////////// sprite object; // обобщенный спрайт, который // содержит кадры с космическим // кораблем pcx_picture text_cells; // PCX-файл с изображениями int *scale_table_y[MAX_SCALE+l]; // таблица с предварительно // рассчитанными коэффициентами // масштабирования int far *scale_table_x[MAX_SCALE+l]; // таблица с предварительно // рассчитанными коэффициентами // масштабирования // ФУНКЦИИ ///////////////////////////////////////////////// void Create_Scale_Data_X(int scale, int far *row) { // эта функция масштабирует полосу текстуры для всех возможных // размеров и создает огромную таблицу соответствий int х; float x_scale_index=0, x_scale_step; // рассчитываем шаг масштабирования или число исходных пикселей // для отображения на результирующее изображение за цикл x_scale_step = (float)(sprite_width)/(float)scale; x_scale_index+=x_scale_step; for (x=0; x< scale; x++) { // помещаем данные в массив для последующего использования row[x] = (int)(x_scale index+, 5); if (row[x] > (SPRITE_X_SIZE-1)) row[x] = (SPRITE_X_SIZE-1); // рассчитываем следующий индекс x_scale index+=x_scale_step; } // конец цикла } // конец Create_Scale_Data_X /////////////////////////////////////////////////////////// void Create_Scale_Data Y(int scale, int *row) { // эта функция масштабирует полосу текстуры для всех возможных // размеров и создает огромную таблицу соответствий int у; float y_scale_index=0, у scale_step; // рассчитываем шаг масштабирования или число исходных пикселей // для отображения на результирующее изображение за цикл у_scale_step = (float)(sprite_height)/(float)scale; y_scale index+=y_scale_step; for (y=0; y< scale; y++) { // помещаем данные в-массив для последующего использования row[y] = ((int)(y_scale_index+.5)) * SPRITE_X_SIZE; if (row[y] > (SPRITE_Y_SIZE-1)*SPRITE_X_SIZE) row[y] = (SPRITE_Y_SIZE-1)*SPRITE_X_SIZE; // рассчитываем следующий индекс y_scale_index+==y_scale_step; } // конец цикла } // конец Create_Scale_Data_Y ////////////////////////////////////////// void Build_Scale_Table (void) { // эта функция строит таблицу масштабирования путем расчета // коэффициентов масштабирования для всех возможных размеров // от 1 до 200 пикселей int scale; // резервируем память for (scale=l; scale< =MAX_SCALE; scale++) { scale_table_y[scale] = (int *)malloc(scale*sizeof(int)+1); scale_table_x[scale] = (int far *)_fmalloc(scale*sizeof(int)+l); } // конец цикла // создаем таблицу масштабирования для осей X и Y for (scale=l; scale< =MAX_SCALE; scale++) { // рассчитываем коэффициент для данного масштаба Create_Scale_Data_Y(scale, (int *)scale_table_y[scale]); Create_Scale_Data_X(scale, (int far *)scale_table_x[scale]); }// конец цикла }// конец Build_Scale_Table //////////////////////////////////////////////////////////// void Scale_Sprite(sprite_ptr sprite, int scale) { // эта функция масштабирует спрайт (без отсечения). Масштабирование производится с //использованием заранее рассчитанной таблицы, которая определяет, как будет изменяться //каждый вертикальный столбец. Затем другая таблица используется для учета //масштабирования этих столбцов по оси Х char far *work_sprite; // текстура спрайта int *row_y; // указатель на масштабированные // по оси Y данные (заметьте, что // это ближний указатель) int far *row_x; // указатель на масштабированные // по оси Х данные (заметьте, что // это дальний указатель) unsigned char pixel; // текущий текстель lnt x, // рабочие переменные У, column, work_offset, video_offset, video_start; // если объект слишком мал, то и рисовать его не стоит if (scale< 1) return; // рассчитываем необходимые для масштабирования данные row_y = scale_table_y[scale]; row_x = scale table_x[scale]; // выбираем соответствующий кадр спрайта work_sprite = sprite-> frames[sprite-> curr_frame]; // рассчитываем начальное смещение video_start = (sprite-> y < < 8) + (sprite-> y < < 6) + sprite-> x; // изображение рисуется слева направо и сверху вниз for (x=0; x< scale; х++) { // пересчитываем адрес следующего столбца video_offset = video_start + х; // определяем, какой столбец должен быть отображен, // исходя из индекса масштабирования по оси Х column = row_x[x]; // Наконец рисуем столбец обычным образом for (y=0; y< scale; y++) { // проверка на " прозрачность" pixel = work_sprite[work_offset+column]; if (pixel) double buffer[video_offset] = pixel; // индекс следующей строки экрана и смещение //в области хранения текстуры video_offset += screen_width; work_offset = row_y[y]; } // конец цикла по Y } // конец цикла ро Х } // конец Scale_Sprite ////////////////////////////////////////// void Clear Double_Buffer(void) { // угадали что это? _fmemset(double_buffer, 0, SCREEN__WIDTH * SCREEN_HEIGHT + 1); } // конец Clear_Double_Buffer // ОСНОВНАЯ ПРОГРАММА ////////////////////////////////////// void main(void) { // Загружаем 12 предварительно отсканированных кадров спрайта // и последовательно меняем их до тех пор пока игрок не изменит // координату Z объекта, нажав клавишу ", " или "." int done=0, // флаг завершения count=0, // счетчик времени изменения кадра scale=64; // текущий масштаб спрайта float scale_distance = 24000, // произвольная константа // для согласования // плоской текстуры и трассированного // пространства view_distance = 256, // дистанция до объекта х=0, // позиция корабля в трехмерном // пространстве у=0, z=1024; // установка видеорежима 320х200х256 _setvideomode(_MRES256COLOR); sprite_width = 80; sprite_height =48;
// создание таблицы для подсистемы масштабирования Build_Scale_Table (); // инициализация файла PCX, содержащего кадры PCX_Init((pcx_picture_ptr)& text cells); // загрузка файла PCX, содержащего кадры PCX_Load(" vyrentxt.pcx", (pcx_picture_ptr)& text_cells, 1); // резервируем память под дублирующий буфер Init_Double_Buffer (); Sprite_Init((sprite_ptr)& object, 0, 0, 0, 0, 0, 0); // загружаем 12 кадров с космическим кораблем PCX_Grap_Bitmap ((pcx_picture_ptr) & text_cells, (sprite_ptr)& object, 0, 0, 0); PCX_Grap_Bitmap((pcx_picture_ptr)& text_cells, (sprite_ptr)& object, 1, 1, 0); PCX_Grap_Bitmap((pcx_picture_ptr)& text_cells, (sprite_ptr)& object, 2, 2, 0); PCX_Grap_Bitmap ((pcx_picture_ptr)& text_cells, (sprite_ptr)& object, 3, 0, 1); PCX_Grap_Bitmap((pcx_picture_ptr)& text_cells, (sprite_ptr)& object, 4, 1, 1); PCX_Grap_Bitmap ((pcx_picture_ptr)& text_cells, (sprite_ptr)& object, 5, 2, 1); PCX_Grap_Bitmap((pcx_picture_ptr)& text_cells, (sprite_ptr)& object, 6, 0, 2); PCX_Grap_Bitmap((pcx_picture_ptr)& text_cells, (sprite_ptr)& object, 7, 1, 2); PCX_Grap_Bitmap((pcx_picture_ptr)& text_cells, (sprite_ptr)& object, 8, 2, 2); PCX_Grap_Bitinap((pcx_picture_ptr)& text_cells, (sprite_ptr)& object, 9, 0, 3); PCX_Grap_Bitmap((pcx_picture_ptr)& text_cells, (sprite_ptr)& object, 10, 1, 3); PCX__Grap_Bitmap((pcx_picture_ptr) & text_cells, (sprite_ptr)& object, 11, 2, 3); // начальная позиция корабля object.curr_frame =0; object.x = 0; object, у =0; Clear_Double_Buffer(); // ожидаем нажатия клавиш и рисуем корабль while(! done) { // нажал ли игрок клавишу? if (kbhit()) { switch(getch()) { case '.': // отдаляем корабль { z+=16; } break; case ', ': // приближаем корабль { z-=l6; //не позволяем кораблю подойти слишком близко if(Z< 256) z=256; } break; case 'q': // выход из программы { done=1; } break; default: break; } // конец оператора switch } // конец оператора if //рассчитываем размер растрового изображения scale = (int)(scale_distance/z); // исходя из размера растрового изображения, // рассчитываем проекции координат Х и Y object.x= (int)((float)x*view_distance / (float)z) + 160 - (scale> > 1); object.y = 100 - (((int)((float y*view_distanc=e / (float)z) + (scale> > 1))); // увеличиваем счетчик кадров if (++count==2) { count=0; if (++object.curr_frame==12) object.curr_frame=0; } // конец оператора if // очищаем дублирующий буфер Clear_Double_Buffer(); // масштабируем спрайт Scale_Sprite((sprite_ptr)& object, scale); Show_Double_Buffer(double_buffer); // выводим информацию на экран _settextposition(24, 0); printf(" z Coordinate is %f", z); } // конец оператора while // Удаляем файл PCX PCX_Delete((pcx_picture_ptr) & text_cells); // восстанавливаем текстовый режим _setvideomode(_DEFAULTMODE); } // конец функции main После выполнения программы из Листинга 8.2, вы, возможно, будете удивлены возможностями оцифровки изображений макетов и приведенным вариантом трехмерной мультипликации. Кто знает, может быть вы создадите что-нибудь именно по типу игры Wing Commander, а вовсе не очередную вариацию DOOM? Однако пора переходить к алгоритму отсечения. Отсечение спрайтов в трехмерном пространстве После построения аксонометрической проекции спрайта отсечение выполняется довольно легко. Алгоритм просто тестирует, не выходит ли проекция отмасштабированного спрайта за границы экрана, и, кроме того, проверяет, находится ли Z-координата спрайта внутри наблюдаемой области пространства. Следовательно, проблема отсечения сводится к проверке расположения прямоугольника относительно границ экрана. Решение этой проблемы мы уже рассмотрели раньше (в четвертой главе, «Механизмы двухмерной графики»). Как вы помните, существует два подхода к этой проблеме: можно использовать алгоритм пространства образов и алгоритм пространства объектов. Первый путь намного проще. Перед обновлением каждого пикселя спрайта, мы проверяем, находится ли он внутри границ экрана (или окна просмотра), и если это так, то замещаем пиксель. Недостаток этого метода — низкая производи тельность (хотя иногда все же приходится прибегать к алгоритму пространства образов из-за чересчур сложной геометрической формы визуализируемого объекта). В общем же случае алгоритм пространства образов работает медленнее алгоритма пространства объектов. При использовании объектно-пространственного алгоритма мы должны каким-то образом до прорисовки определить, какая часть спрайта будет нарисована и на основании этого вновь вычислить его проекцию. По существу, мы должны отсечь границами экрана прямоугольник, который получается в результате масштабирования спрайта. Это кажется несложным. Мы разбирали текст такой программы в предыдущей главе (Листинг 7.3), но я повторю этот алгоритм еще один раз! Такой уж я. Алгоритм 8.1 предполагает, что: § Экран ограничивается точками (0, 0) и (scrfeen_x, screen_y); § Верхняя левая точка спрайта (sprifce_x, sprite_y); § Спрайт имеет размеры width и height. Предположим, отсечение пространства по оси Z уже было сделано и внешне образ выглядит вполне правдиво. Пусть также были сосчитаны масштаб объекта и координаты проекций по осям Х и Y.
|