Программирование игр для Windows. Советы профессионала

Реализация отсектеля лучей


Я написал совершенно полную реализацию алгоритма отсечения лучей. Он закомментирован и даже содержит какую-то логику. Эта программа включена в комплекте поставки на дискете в файле RAY. С. Демонстрационная программа загружает двухмерную карту мира в виде ASCII-файла. На рисунке 6.30 приведена подобная карта. Она создается с помощью обычного текстового редактора.

Вся программа слишком длинна, чтобы включить ее в книгу, поэтому здесь приведена только ее самая интересная часть: процедура отсечения лучей. Попробуйте с ней разобраться и понять, что это и зачем так сделано.

1111111111111111

1              1

1   11111      1



1   1   1 1 1  1

1   1   1      1

1   1  11      1

1              1

1              1

1              1

1   11  111111 1

1   1        1 1

1   111      1 1

1   1        1 1

1   11111111 1 1

1              1

1111111111111111

 

Листинг 6.4. Процедура отсечения лучей (RAYLIST.C)

void Ray_Caster(long x, long y,long view_angle)

{

// эта функция выполняет расчет 320 лучей и строит игровой

// экран на основе их пересечений со стенами. Расчет производится

// таким образом, что все лучи отображаются на поле просмотра

// с углом 60 градусов

// после расчета траекторий лучей, рассчитываются координаты

// их пересечений со стенами. Координаты первой точки пересечения

// запоминаются. Ближайшие к игроку точки используются для

// построения битового образа изображения. Расстояния используются

// для определения высоты текстурных фрагментов и вертикальных линий

// Примечание: эта процедура использует функции стандартной

// библиотеки компилятора для работы с плавающей точкой

// (это работает медленно). Код не оптимизирован (еще медленнее),

// и в довершение всего обращается к функциям графической

// библиотеки компилятора фирмы Microsoft (что уж вовсе

// никуда не годится!) // Однако все это имеет определенную цель - легкость




// для понимания того, как работает процедура

int rcolor;

long xray=0,      // счетчик вертикальных пересечений

yray=0,      // счетчик горизонтальных пересечений

next у cell, // используются для вычисления

next_x cell, // номера следующей ячейки по ходу луча

cell_x,      // координаты текущей ячейки

се11_у,      // луча

x_bound,     // следующие вертикальная

у_bound,     // и горизонтальная точки пересечения

xb_save,     // в этих переменных запоминаются

yb_save,     // координаты точек пересечения

x_delta,     // эти переменные показывают, на сколько

y_delta,     // надо сместиться для перехода к следующей ячейке

ray,         // текущий луч для отсечения

casting=2,   // показывает компоненты Х и Y луча

x_hit_type,  // координаты блока, с которым пересекся луч

y_hit_type,  // используются при отрисовке

top,         // верхняя и нижняя координаты области,

bottom;      // отрисовываемой как стена (с помощью текстуры)

float xi,         // используется для определения х- и у-пересечений

yi,

xi_save,    // используется для сохранения

// точек пересечения Х и У

yi_save,

dist_x,     // расстояние до х- и у-пересечениЙ

dist_у,     //  от точки просмотра scale;    

// масштаб

//СЕКЦИЯ   1 ////////////////////////////////////////

// инициализация

// вычисляет начальный угол от игрока. Поле просмотра 60 градусов.

// Таким образом, рассматриваем только половину - 30 градусов

if ( (view_angle-=ANGLE_360) < 0)

// разворачиваем вектор направления взгляда

view_angle=ANGLE_360 + view_angle;

} // конец оператора if

// выбираем цвет для луча

rсо1оr=1 + rand()%14;

//СЕКЦИЯ   2 ////////////////////////////////////////   

// цикл для всех 320 лучей

for (ray=0; ray<320; ray++)               

// вычислить первое х-пересечение

if (view_angle >= ANGLE_0 && view_angle < ANGLE_180)

{

// вычислить первую линию, которая пересекается с лучом.

// Примечание: эта линия должна быть выше (впереди

// на игровом поле) игрока.                                 



y_bound = CELL_Y_SIZE + CELL_Y_Sf2E * (у / CELL_Y_SI2E);    

// вычислить смещение для перехода к следующей

// горизонтальной линии                                        

y_delta = CELL_Y_SIZE;   // размер ячейки по вертикали (ред.)

// основываясь на первой возможной горизонтальной линии отсечения,

// вычислить Х-пересечение и начать расчет

xi = inv_tan_table[view_angle] * (y_bound - у) + х;

// установить смещение                  

next_у_cell = 0;

} // конец обработки верхней половины плана

else

{ // вычислить первую горизонтальную линию, которая может

// пересекаться с лучом. Это будет позади игрока

y_bound = CELL_Y_SI2E * (у / CELL_Y_SIZE);

// вычислить смещение для следующей горизонтальной линии

y_delta = -CELL_Y_SIZE;

// основываясь на первой возможной горизонтальной линии отсечения,

// вычислить Х-пересечение и начать расчет

xi = inv_tan_table[view_angle] * (y_bound - у) + х;

next_y_cell = -1;

} // конец обработки нижней половины плана

//СЕКЦИЯ   3 ////////////////////////////////////////

// вычислить первое х-пересечение

if (view_angle < ANGLE_90 || view_angle >= ANGLE_270) {

// вычислить первую вертикальную линию, которая будет

// пересекаться с лучом. Она должна быть справа от игрока

x_bound = CELL_X_SIZE + CELL_X_SIZE * (х / CELL_X__SIZE);

// вычислить смещение

x_delta = CELL_X_SIZE;

// основываясь на первой возможной вертикальной линии отсечения,

// вычислить Y-пересечение и начать расчет

yi = tan_table[view_angle] * (x_bound - х) + у;

next_x_cell = 0;

} // конец обработки правой половины плана

else

{

// вычисляем первую вертикальную линию, которая может быть

// пересечена лучом. Она должна быть слева от игрока

x_bound = CELL_X_SIZE * (х / CELL_X_SIZE);

// вычислить расстояние до следующей вертикальной линии

x_delta = -CELL_X_SIZE;

// основываясь на первой возможной вертикальной линии отсечения,

// вычислить Y-пересечение

yi = tan_table[view_angle] * (x__bound - x) + у;

next_x_cell = -1;

}

// начать отсечение                            



casting      =2;   // два луча для одновременного отсечения

хrау = уrау = 0;   // сбросить флаги пересечения

//СЕКЦИЯ   4 ////////////////////////////////////////

while(casting)

{

// продолжить отсечение лучей

if (xray!=INTERSECTION_FOUND)

{

// тест на совпадение луча с асимптотой

if (fabs (y_step[view_angle])==0)

xrау = INTERSECTION_FOUND;

casting--;

dist_x = 1e+8;                                      

} // конец проверки на совпадение с асимптотой

// вычислить текущую позицию карты для проверки

сеll_х

= ( (x_bound+next_x_cell) / CELL_X_SIZE); 

cell_y = (long)(yi / CELL_Y_SIZE) ;

// проверить, есть ли в этом месте блок

if ((x_hit_type = world[(WORLD_ROWS-1) - cell_y][cell_x])!=0)

{

// вычислить

расстояние

dist_x = (yi - y) * inv_sin__table[view angle];

yi_save == yi;

xb_save = x_bound;

// закончить х-отсечение

хrау = INTERSECTION_FOUND;

casting--;

} // конец проверки попадания луча на стену блока

else

{

// вычислить следующее Y-пересечение

yi += y_step[view_angle];

} //конец оператора else

} // конец проверки на отсечение по оси Х

//СЕКЦИЯ   5 ////////////////////////////////////////

if (yray!=INTERSECTION_FOUND)

{

// тест на попадание луча на асимптоту

if (fabs(x_step[view_angle])==0)

{

уrау = INTERSECTION_FOUND;

casting--;

dist_y=1e+8;

}

// вычислить

позицию карты

ceil_x = (long)(xi / CELL_X_SI2E);

cell_y = ( (y_bound + next_y_cell) / CELL_Y_SIZE) ;

// проверить, находится ли в этом месте блок

if ((y_hit_type = world[(WORLD_ROWS-1) - cell_y] [cell_x] ) !=0)

{

// вычислить

расстояние

dist_y = (xi - х) * inv_cos_table[view angle];

xi_save = xi;

yb_save = y_bound;

// закончить вычисление Y-пересечения              

yray = INTERSECTION_FOUND;

casting--;                                           

} // конец обработки попадания луча на блок

else

{

// вычислить следующее Х-пересечение

xi += x_step,[view_angle];

} // конец оператора

else

} // конец проверки на отсечение по оси У

// перейти к следующей точке пересечения



x_bound += x__delta;

y_bound += y_delta;         

}

//СЕКЦИЯ   6 ////////////////////////////////////////  

// выяснить, какая из стен ближе - вертикальная или горизонтальная 

// и затем нарисовать ее                                           

// Примечание: в дальнейшем мы заменим вертикальную линию на

// текстурный блок, пока же достаточно того, что есть

if (dist_x < dist_y)

{

sline(x,y,(long)xb_save,(long)yi_save, rcolor);

// вертикальная стена ближе горизонтальной

// вычислить масштаб и умножить на поправочный коэффициент

// для устранения сферических искажений

scale = cos_table[ray]*15000/(1e-10 + dist_x);

// вычислить координаты верха и низа

if ( (top    = 100 - scale/2) < 1) top =1; 

if ( (bottom = top+scale) > 200) bottom=200;

// нарисовать

фрагмент стены

if ( ((long)yi_save) % CELL_Y_SIZE <= 1 ) _setcolor(15);

else

_setcolor(10);

_moveto((int)(638-ray),(int)top);

_lineto((int)(638-ray),(int)bottom);

}

else // сначала надо нарисовать горизонтальную стену

{

sline(x,y,(long)xi_save,(long)yb_save,rcolor) ;

// вычислить

масштаб

scale = cos_table[ray]*15000/(le-10 + dist_y);

// вычислить координаты верха и низа

if ( (top    = 100 - scale/2) < 1)

top = 1;

if ( (bottom = top+scale) > 200) bottom=200;

// нарисовать

фрагмент стены

if (((long)xi_save) % CELL_X_SIZE <= 1 )

_setcolor(15) ;

else

_setcolor(2);

_moveto((int)(638-ray),(int)top);

_lineto((int)(638-ray),(int)bottom) ;

} //конец оператора else

//СЕКЦИЯ   7 //////////////////////////////////////

//отсечь

следующий луч

if (++view_angle>=ANGLE_360)

{

// установить угол в 0

view_angle=0;             

} // конец оператора if

} // конец цикла for

по лучам

} // конец функции

Реализация алгоритма отсечения лучей разделена на семь основных частей, чтобы легче было понять предназначение каждой из них.

§          Первая часть произведет инициализацию всех массивов и внутренних переменных. В этой части определяется направление взгляда игрока и на его основании вычисляется стартовый угол.


Также в этой секции выбирается случайное число, задающее цвет трассируемого луча;

§          Вторая и третья части вычисляют Х- и Y-пересечения текущего луча с периметром ячейки, в которой находится игрок. После того как найдено первое пересечение как с осью X, так и с осью Y, устанавливается значение нескольких переменных для определения траектории луча по отношению к осям координат. Информация, полученная в этих частях, используется в четвертой и пятой части;

§          Четвертая и пятая части продолжают проверку пересечений. Каждое из пересечений с координатной осью проверяется на пересечение с объектом. Если это происходит, то вычисляется дистанция и запоминается для дальнейшего использования. Эта информация может быть использована для тексту рирования объектов. Хотя рассмотренный нами трассировщик лучей и не делает этого, он предоставляет достаточно информации для текстурирования. Например, если луч пересекает середину стены блока, это означает, что мы должны вывести на экран 32-ю вертикальную полосу соответствующей текстуры.  Более подробно этот вопрос будет рассмотрен позднее, в главе, посвященной описанию игры Warlock;                   

§          В шестой части заканчивается обработка луча. К этому моменту мы уже вычислили горизонтальные и вертикальные пересечения и запомнили расстояния до них. Следовательно, мы готовы нарисовать на экране соответствующую лучу вертикальную полосу. Для этого мы определяем, которое из пересечений находится ближе всего. Горизонтальная позиция для отрисовки соответствует номеру текущего луча и меняется в диапазоне от 0 до 319. Высота рисуемого фрагмента вычисляется на основании расстояния до игрока и с некоторыми корректировками для улучшения впечатления;

§          Седьмая часть увеличивает текущий угол и осуществляет переход к первой части. Цикл выполняется до тех пор, пока все 320 лучей не будут отсечены.

Вообще, то, что мы написали - неплохая штука. Она не рисует фактур и не просчитывает освещение, но это несложно реализовать. Теперь, когда у нас появилась работающая программа, есть смысл поговорить об оптимизации.


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