Вращение объектов
Для того чтобы вращать объект, мы должны повернуть его вокруг одной из координат. В двухмерной графике для этого обычно выбирается ось Z. Пока мы находимся в двухмерном мире, нас не беспокоит третье измерение - мы просто не придаем ему значения.
Если экран — это плоскость X-Y, то ось Z — это перпендикуляр к осям Х и Y. Таким образом, если мы описываем наши объекты относительно двухмерного мира, то у нас появляется возможность вращать их относительно оси Z,
Следующие формулы позволяют вращать произвольную точку (X,Y) относительно оси Z:
new_x = x*cos(angle) - y*sin(angle) new_у = y*cos(angle) + y*sin(angle)
где angle — это угол, на который вы хотите повернуть точку. Кроме этого вам стоит помнить еще пару вещей:
§ Положительные углы имеют эффект вращения по часовой стрелке;
§ Отрицательные углы имеют эффект вращения против часовой стрелки.
Надо также не забывать, что Си использует для своих функций радианы, а не градусы, и все вызовы тригонометрических функций должны передавать в параметрах также радианы. Для того чтобы перевести радианы в градусы, мы должны написать простые макросы.
Deg_To_Rad(deg) {pi*deg/180;}
Rad_To_Deg(rad) {180*rad/pi;}
Другими словами, это значит, что в круге 360 градусов или 2хPi радиан. Теперь нам нужно написать функцию для вращения объекта. Давайте просто используем формулы, не задумываясь о том, как и почему они работают. Функция в Листинге 4.7 делает именно то, что мы хотим.
Листинг 4.7. Вращение объекта.
void Rotate_0bject(object__ptr object, float angle)
{
int index;
float x_new, y_new,cs, sn;
// сначала вычислим синус и косинус угла
сs = cos(angle) ;
sn = sin(angle);
// поворачиваем каждую вершину на угол angle
for (index=0; index<object->num_vertices; index++)
{
x_new = object->vertices [index].x*cs-object->vertices[index].y*sn;
y_new = object->vertices [index].y*cs+object->vertices[index].x*sn;
// изменяем исходные координаты.на расчетные
object->vertices[index].x = x_new;
object->vertices[index].y = у_new;
} // конец цикла for
} // конец функции
Думаю, что надо кое-что объяснить. Я вычисляю заранее значения синуса и косинуса для данного угла. Зачем, спросите вы. Ответ прост — для скорости. Ибо заниматься вычислениями тригонометрических функций в процессе работы программы можно позволить себе только, имея математический сопроцессор.
Теперь настало время написать что-нибудь посерьезней. Мне кажется, что надо бы написать что-то более экстравагантное, чем одинокий астероид. Пусть это будут хотя бы несколько астероидов. Давайте сначала спланируем наши дальнейшие действия.
Я хотел бы иметь поле астероидов различных размеров в количестве более 100 штук. И так, чтобы они могли вращаться. Для этого программа должна иметь следующую структуру:
Шаг 1. - Инициировать поле астероидов;
Шаг 2. - Стереть поле астероидов;
Шаг 3. - Трансформировать поле астероидов;
Шаг 4. - Нарисовать поле астероидов;
Шаг 5. - Перейти к Шагу 2, пока пользователь не нажмет на кнопку.
Чтобы сделать это проще, я добавил три новых поля к нашей структуре: одно для угла поворота и два - для скорости (целиком программа представлена в Листинге 4.8).
Листинг 4.8. Программа, которая рисует поле астероидов (FIELD.С).
// ВКЛЮЧАЕМЫЕ ФАЙЛЫ ////////////////////////////////////
#include <stdio.h>
#include <graph.h>
#include <math.h>
// ОПРЕДЕЛЕНИЯ /////////////////////////////////////////
#define NUM_ASTEROIDS 10
#define ERASE 0
#define draw 1
// СТРУКТУРЫ ДАННЫХ ////////////////////////////////////
//определяем структуру "вершина"
typedef struct vertex_typ
{
float x,y; // координаты точки на плоскости
} vertex, *vertex__ptr;
// структура объекта
typedef struct object_typ
{
int num_vertices; // количество вершин объекта
int color; // цвет объекта
float xo,yo; // позиция объекта
float x_velocity; // скорость перемещения по осям Х
float y_velocity; // и y
float scale; // коэффициент масштабирования
float angle; // угол поворота
vertex vertices[16]; // 16 вершин
}object, *object_ptr;
// Глобальные переменные //////////////////////////////
object asteroids[NUM_ASTEROIDS];
// Функции ////////////////////////////////////////////
void Delay(int t)
{
// функция формирует некоторую временную задержку
float x = 1;
while(t—>0)
x=cos(x);
} // конец функции /////////////////////////////////////
void Scale_Object(object_ptr object,float scale)
{
int index;
// для всех вершин масштабируем координаты х и у
for (index = 0; index<object->num_vertices; index++)
{
object->vertices[index].x *= scale;
object->vertices[index].y *= scale;
}// end for index
// конец
функции ///////////////////////////////////////////
void Rotate_Object(object_ptr object, float angle)
{
int index;
float x_new, y_new,cs,sn;
// заранее вычислить синус и косинус
cs = cos(angle);
sn = sin(angle);
// поворачиваем каждую вершину на угол angle
for (index=0; index<object->num_vertices; index++)
{
x new = object->vertices[index].x * cs - object->vertices[index].y * sn;
у new = object->vertices[index].y * cs + object->vertices[index].x * sn;
object->vertices[index].x = x_new;
object->vertices[index].y = y_new;
} // конец цикла for
} // конец функции //////////////////////////////////////////////////
void Create_Field(void)
{
int index;
// формируем поле астероидов
for (index=0; index<NUM_ASTEROIDS; index++)
{
// заполнить
все поля
asteroids[index].num_vertices = 6;
asteroids[index].color = 1 + rand() % 14; // всегда
видимый
asteroids[index].xo = 41 + rand() % 599;
asteroids[index].yo = 41 + rand() % 439;
asteroids[index].x_velocity = -10 + rand() % 20;
asteroids[index].y_velocity = -10 + randO % 20;
asteroids[index].scale = (float)(rand() % 30) / 10;
asteroids[index].angle = (float) (-50+(float)(rand()%100))/100;
asteroids[index].vertices [0].x =4.0;
asteroids[index].vertices[0].у = 3.5;
asteroids[index].vertices[l].x=8.5;
asteroids[index].vertices[1].y = -3.0;
asteroids[index].vertices[2].x = 6;
asteroids[index].vertices[2].у = -5;
asteroids[index].vertices[3].x = 2;
asteroids[index].vertices[3].у =—3;
asteroids[index].vertices[4].x = -4;
asteroids[index].vertices[4].у = -6;
asteroids[index].vertices[5].x = -3.5;
asteroids[index].vertices[5].у =5.5;
// теперь масштабируем каждый астероид до нужного размера
Scale_Object((object_ptr)&asteroids [index],
asteroids[index].scale) ;
} // конец цикла for
} // конец функции ///////////////////////////////////////////////////////
void Draw_Asteroids(int erase)
{
int index,vertex;
float xo,yo;
for (index=0; index<NUM_ASTEROIDS; index++)
{
// рисуем астероид
if (erase==ERASE)
_setcolor(0);
else
_setcolor(asteroids[index].color);
// получить позицию объекта
xo = asteroids[index].xo;
yo = asteroids[index].yo;
// перемещаемся к первой вершине
_moveto((int)(xo+asteroids[index].vertices[0].x),
(int)(yo+asteroids[index],vertices[0].y));
for (vertex=1; vertex<asteroids[index].num_vertices; vertex++)
{
_lineto((int)(xo+asteroids[index].vertlces[vertex].x),(int) (yo+asteroids[index].vertices [vertex].y));
} // конец цикла for
по вершинам
// замыкаем
контур
_lineto((int)(xo+asteroids[index].vertices[0].x), (int)(yo+asteroids[index].vertices[0].y));
} // конец цикла for
по астероидам
} // конец
функции ///////////////////////////////////////////////////////////////////////////////////////////
void Translate_Asteroids()
{
int index;
for (index=0; index<NUM_ASTEROIDS; index++) {
// перемещаем
текущий астероид
asteroids[index].xo += asteroids[index].x_velocity;
asteroids[index].yo += asteroids[index].y_velocity;
if (asteroids[index].xo > 600 || asteroids[index].xo < 40)
{
asteroids[index].x_velocity = -asteroids[index].x_velocity;
asteroids[index].xo += asteroids[index].x_velocity;
}
if (asteroids[index].yo > 440 || asteroids[index].yo < 40)
{
asteroids [index].y_velocity = -asteroids[index] .y_velocity;
asteroids[index].yo += asteroids[index].y_velocity;
}
} // конец цикла for
} // конец функции
///////////////////////////////////////////////////////
void Rotate_Asteroids(void)
{
int index;
for (index=0; index<NUM_ASTEROIDS; index++)
{
// вращаем текущий астероид
Rotate_0bject ((object_ptr) &asteroids [index],
asteroids[index].angle);
} // конец цикла for
} // конец функции
///////////////////////////////////////////////////////
void main(void) {
_setvideomode(_VRES16COLOR); // 640х480, 16 цветов
// инициализируем поле астероидов
Create_Field{) ;
while(!kbhit())
{ // очищаем поле
Draw_Asteroids(ERASE) ;
// изменяем поле
Rotate_Asteroids();
Translate_Asteroids();
// рисуем поле
Draw_Asteroids(DRAW) ;
// небольшая задержка
Delay(500);
} // конец цикла while
// устанавливаем текстовый режим
_setvideomode( DEFAULTMODE);
} // конец функции main
Набрав, откомпилировав и запустив программу из Листинга 4.8, вы увидите на экране поле астероидов вместе со множеством разноцветных камней, отскакивающих от границ экрана (определение факта столкновения будет подробнее излагаться далее в этой главе, а также в главе одиннадцатой, «Алгоритмы, структуры данных и методология видеоигр»)
Теперь нам надо еще кое-что обсудить:
§ Первое, на что вы обратили внимание, это то, что образы слегка мелькают. Это связано с тем, что программа формирует изображение в тот же момент, когда происходит перерисовка экрана.
§ Экран — это множество линий, которые рисуются на мониторе слева направо и сверху вниз вашей видеокартой. Проблема заключается в том, что мы не можем изменять видеобуфер, пока на экране что-то рисуется. В пятой главе, «Секреты VGA-карт», мы обсудим рисование всего экрана целиком в отдельный буфер с последующим перемещением его в видеопамять;
§ Другая проблема, связанная с мерцанием, заключается в том, что мы используем графическую бибилотеку Microsoft С, которая не очень быстро работает. Вы должны понимать, что Microsoft не оптимизировал ее для высокой производительности и скорости работы;
§ Программа использует числа с плавающей запятой, которые впоследствии будут заменены числами с фиксированной запятой;
§ Вся программа совершенно неэффективна с точки зрения написания видеоигр. Все, что она делает, выполнено в классической, «книжной» манере. У разработчиков видеоигр есть правило номер 1: «Всегда есть способ сделать то, что кажется невозможным». Если б это было не так, то половина видеоигр вообще никогда не была бы написана, поскольку ПК не в состоянии обеспечить нужной производительности.
(Поверьте мне, DOOM — это лучший пример моим словам. Если бы я его не видел, то никогда не поверил бы, что такое возможно. Но, как мы знаем, DOOM существует, и это наилучший пример использования правильных методов для создания реального мира на компьютерном экране.)