Читайте также:
|
|
// ВКЛЮЧАЕМЫЕ ФАЙЛЫ ////////////////////////////////////
#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 существует, и это наилучший пример использования правильных методов для создания реального мира на компьютерном экране.)
Отсечения
Мне не хотелось бы углубляться в теорию, я собираюсь рассказать об отсечении просто для полноты изложения. Нам более интересны специальные способы отсечения, а не общая теория. Поэтому давайте изучим только основные концепции и их применение в наиболее общем виде.
Отсечение означает рисование только части видеообраза, ограниченного заранее заданными границами. Представьте себе объект, который перемещается по экрану. Дойдя до его границы, видимая часть объекта начинает уменьшаться и он постепенно уходит с экрана. Вы считаете это само собой разумеющимся, а программисту надо знать, какую часть образа он должен показать на: экране. Вычисления здесь могут быть сложными или простыми — все зависит от
ситуации.
Отсечения могут производится на двух; «уровнях»:
§ Уровень образа;
§ Уровень объекта.
Отсечение области образов основывается на проверке каждой точки, кото рая может быть нарисована на экране в отсекаемой области. Например, если мы имеем квадратную область, которая соприкасается с границами экрана в режиме 13п (320х200), то мы не будем рисовать точки, выходящие за границу. Точки, которые находятся внутри области и ограничены координатами 0-319 по оси Х и 0-199 по оси Y, будут нарисованы, остальные - нет.
Поскольку все объекты, которые могут появляться на экране компьютера состоят из точек, то этот метод наиболее прост в реализации. Какие геометрические фигуры рисуются, в этом случае не имеет значения. Будь то линии, треугольники, битовые карты и еще что-нибудь - все они используют вызов функции рисования точки. Если функция, рисующая точку, определяет, что точка выходит за границы области, то данная точка не ставится.
Основная проблема, возникающая при разработке алгоритмов отсечения -это быстродействие. Давайте рассмотрим процесс рисования линии. Для этого достаточно узнать, где линия находится в отсекаемой области. Вы можете определить точки пересечения линии с границами области - к чему разбираться с каждой точкой, если можно рассматривать область целиком. Некоторые вычисления помогут определить, где линия будет разорвана. Это основа алгоритмов, позволяющих выполнять отсечения области объектов.
Алгоритмы отсечения области объектов рассматривают геометрические характеристики отсекаемых объектов и преобразуют их в цельные объекты, которые не надо разрезать. Эти новые объекты затем передаются в счетную часть программы и рисуются безотносительно к частям, которые оказались за пределами отсекаемой области. Сложность с алгоритмами области объектов состоит в том, что их реализация довольно сложна и зависит от типов рисуемых объектов. Отсечения простых линий из области объектов не так сложно, гораздо сложнее придумать эффективные алгоритмы для сложных объектов, состоящих из многоугольников.
К счастью для нас, наиболее сложный образ, который нам придется разрезать, это битовый образ (мы узнаем о том, как с ним работать в главе седьмой, «Продвинутая битовая графика и специальные эффекты»). Мы будем стремиться использовать эти алгоритмы всегда, когда это только возможно, поскольку они дают максимальную скорость. Мы не можем себе позволить добавлять в наши функции вывода точек всевозможные проверки, пока это не является абсолютной необходимостью.
Чтобы уверенно чувствовать себя в двухмерном мире, давайте поговорим на тему, которую большинство людей почему-то не любит. Речь пойдет о матрицах.
Матрицы
Матрицы не очень сложны для понимания и использования. Более того, они нужны дли написания быстрых преобразований и очень полезны для представления математических операций в компактной форме.
Матрица — это множество чисел, сгруппированных в колонки и столбцы. Здесь изображены две матрицы: Матрица А и Матрица В.
Матрица А — это матрица 2х3 (то есть у нее две строки и три столбца), тогда как матрица В — это матрица 3х3. Мы можем получить доступ к элементу матрицы А, используя запись А[m,n], где m - это строка, а n - столбец. Элемент в левом верхнем углу матрицы А будет обозначаться А[0,0] и он равен, единице.
Произведение операций над матрицами
Вы можете производить большинство операций над матрицами так же, как вы оперируете и с нормальными числами. Например, вы можете их складывать или вычитать, соответственно складывая или вычитая каждый из компонентов.
Для примера, рассмотрим сложение двух матриц размерностью 2х3 - матрицы А и матрицы С:
При сложении матриц А и С нужно складывать каждый из элементов m,n. Суммы элементов займут в результирующей матрице сответствующие места:
Мы также можем умножить матрицу на скаляр k. Например, чтобы умножить матрицу А на 3, мы должны умножить на 3 каждый ее элемент:
Теперь поговорим об умножении двух матриц. Эта операция немного отличается от умножения на скалярную величину. Вы должны запомнить несколько правил:
§ Количество столбцов в первой матрице (n) должно быть равно количеству строк во второй (также n). Это значит, что если размерность первой матрицы (mxn), то размерность второй матрицы должна быть (nхr). Два остальных измерения m и r могут быть любыми;
§ Произведение матриц не коммутативно, то есть А х В не равно В х А.
Умножение матрицы mxn на матрицу nхr может быть описано алгоритмически следующим образом:
1. Для каждой строки первой матрицы:
§ Умножить строку на столбец другой матрицы поэлементно.
§ Сложить полученный результат;
2. Поместить результат в позицию [i,j] результирующей матрицы, где i - это строка первой матрицы, a j - столбец второй матрицы.
Для простоты посмотрим на рисунок 4.9:
Мы можем это сделать намного проще, написав программу на Си. Давайте определим матрицу 3х3 и напишем функцию, умножающую матрицы. В Листинге 4.9 показана соответствующая программа.
Дата добавления: 2015-07-12; просмотров: 99 | Нарушение авторских прав
<== предыдущая страница | | | следующая страница ==> |
Листинг 4.7. Вращение объекта. | | | Листинг 4.9. Определение и умножение двух матриц. |