Загрузка...
 
Печать
ИГРОКОДИНГ  »  ИГРОКОДИНГ: Учебный курс  »  Программируем 3D-шутер от первого лица (FPS) (Win32, Cpp, DirectX9)  »  Часть 1. Создание движка  »  1.5 Добавляем поддержку базовой геометрии (Geometry.h)
Программируем 3D-шутер от первого лица (FPS) (Win32, C++, DirectX9)

1.5 Добавляем поддержку базовой геометрии (Geometry.h)


Заголовочный файл Geometry.h содержит множество вспомогательных функций для работы с базовыми геометрическими объектами и примитивами (различные виды вершин, рёбер, граней). Также сюда входят DirectX-примитивы (например, куб и сфера). По математическим основам трёхмерной графики написана не одна сотня книг, да и одни только названия DirectX интерфейсов способны повергнуть в шок любого новичка. Код в Geometry.h может показаться слишком сложным для начинающего, даже несмотря на щедрые комментарии. К счастью, тебе, скорее всего, никогда не придётся напрямую использовать функции и классы из данного заголовка, так как данный код является вспомогательным (support code). На первых порах допускается просмотреть только объявления функций и классов + комментарии к ним.
И учи вышмат! Вся компьютерная графика основана на нём.

Рис. 1 Сейчас Проект нашего движка выглядит так
Рис. 1 Сейчас Проект нашего движка выглядит так

Сейчас в Проекте нашего движка всего 4 файла: Engine.h, Engine.cpp, LinkedList.h и ResourceManagement.h, которые мы создали в предыдущих главах (см. Рис. 1).

Создаём Geometry.h

В заголовочном файле Geomentry.h будут содержаться объявление и реализация вспомогательных функций и классов для работы с геометрическими примитивами (точка, ребро, грань, шар, куб и т.д.). Также присутствуют функции для детекции столкновений (collision detection) и проверки пересечений фигур друг с другом.

Закрыть
noteПримечание

Напомним, что директива #include "Geometry.h" (ссылка на заголовок) уже указана в Engine.h. Таким образом, для того, чтобы воспользоваться всем функционалом различных заголовочных файлов (в том числе Geomentry.h), достаточно в любом из файлов исходного кода (.cpp) указать всего 1 директиву: #include "Engine.h". Это соответствует концепции единой точки контакта.

ОК, приступаем.

  • Стартуй MSVC++ 2010 и открывай Решение GameProject01 (если не сделал это раньше).
  • В "Обозревателе решений" главного окна MSVC++2010 щёлкни правой кнопкой мыши по папке (в терминологии Майкрософт это не папки, а фильтры!) "Заголовочные файлы".
  • Во всплывающем меню Добавить->Создать элемент...
  • В появившемся окне выбери "Заголовочный файл (.h)" и в поле "Имя" введи "Geometry.h".
  • Жмём "Добавить".

Добавленный файл сразу откроется в правой части MSVC++2010.

  • В только что созданном и открытом файле Geometry.h набираем следующий код:
Geometry.h
//-----------------------------------------------------------------------------
// Файл: Geometry.h
// Объявление и реализация вспомогательных функций и классов для работы
// с геометрическими примитивами (точка, ребро, грань, шар, куб и т.д.)
//
// Original SourceCode:
// Programming a Multiplayer First Person Shooter in DirectX
// Copyright (c) 2004 Vaughan Young
//-----------------------------------------------------------------------------
#ifndef GEOMETRY_H
#define GEOMETRY_H

//-----------------------------------------------------------------------------
// Структура вершины (Vertex Structure)
//-----------------------------------------------------------------------------
struct Vertex
{
	D3DXVECTOR3 translation; // Трансляция вершины (в мировом пространстве).
	D3DXVECTOR3 normal; // Вектор нормали вершины.
	float tu, tv; // UV-координаты текстуры.

	//-------------------------------------------------------------------------
	// The vertex structure constructor.
	//-------------------------------------------------------------------------
	Vertex()
	{
		translation = D3DXVECTOR3( 0.0f, 0.0f, 0.0f );
		normal = D3DXVECTOR3( 0.0f, 0.0f, 0.0f );
		tu = 0.0f;
		tv = 0.0f;
	}

	//-------------------------------------------------------------------------
	// The vertex structure constructor.
	//-------------------------------------------------------------------------
	Vertex( D3DXVECTOR3 t, D3DXVECTOR3 n, float u, float v )
	{
		translation = t;
		normal = n;
		tu = u;
		tv = v;
	}
};
#define VERTEX_FVF ( D3DFVF_XYZ | D3DFVF_NORMAL | D3DFVF_TEX1 )
#define VERTEX_FVF_SIZE D3DXGetFVFVertexSize( VERTEX_FVF )

//-----------------------------------------------------------------------------
// Структура освещаемой вершины (Lit Vertex Structure)
//-----------------------------------------------------------------------------
struct LVertex
{
	D3DXVECTOR3 translation; // Translation of the vertex (in world space).
	D3DCOLOR diffuse; // Colour of the vertex.
	float tu, tv; // Texture UV coordinates.

	//-------------------------------------------------------------------------
	// The lit vertex structure constructor.
	//-------------------------------------------------------------------------
	LVertex()
	{
		translation = D3DXVECTOR3( 0.0f, 0.0f, 0.0f );
		diffuse = 0xFFFFFFFF;
		tu = 0.0f;
		tv = 0.0f;
	}

	//-------------------------------------------------------------------------
	// The lit vertex structure constructor.
	//-------------------------------------------------------------------------
	LVertex( D3DXVECTOR3 t, D3DCOLOR d, float u, float v )
	{
		translation = t;
		diffuse = d;
		tu = u;
		tv = v;
	}
};
#define L_VERTEX_FVF ( D3DFVF_XYZ | D3DFVF_DIFFUSE | D3DFVF_TEX1 )
#define L_VERTEX_FVF_SIZE D3DXGetFVFVertexSize( L_VERTEX_FVF )

//-----------------------------------------------------------------------------
// Структура трансформированной и освещённой вершины (Transformed & Lit Vertex Structure)
//-----------------------------------------------------------------------------
struct TLVertex
{
	D3DXVECTOR4 translation; // Трансляция вершины (в экранном пространстве (screen space)).
	D3DCOLOR diffuse; // Цвет вершины.
	float tu, tv; // UV-координаты текстуры.

	//-------------------------------------------------------------------------
	// The transformed & lit vertex structure constructor.
	//-------------------------------------------------------------------------
	TLVertex()
	{
		translation = D3DXVECTOR4( 0.0f, 0.0f, 0.0f, 1.0f );
		diffuse = 0xFFFFFFFF;
		tu = 0.0f;
		tv = 0.0f;
	}

	//-------------------------------------------------------------------------
	// The transformed & lit vertex structure constructor.
	//-------------------------------------------------------------------------
	TLVertex( D3DXVECTOR4 t, D3DCOLOR d, float u, float v )
	{
		translation = t;
		diffuse = d;
		tu = u;
		tv = v;
	}
};
#define TL_VERTEX_FVF ( D3DFVF_XYZRHW | D3DFVF_DIFFUSE | D3DFVF_TEX1 )
#define TL_VERTEX_FVF_SIZE D3DXGetFVFVertexSize( TL_VERTEX_FVF )

//-----------------------------------------------------------------------------
// Сруктура ребра (Edge Structure)
//-----------------------------------------------------------------------------
struct Edge
{
	Vertex *vertex0; // First vertex of the edge.
	Vertex *vertex1; // Second vertex of the edge.

	//-------------------------------------------------------------------------
	// The edge structure constructor.
	//-------------------------------------------------------------------------
	Edge( Vertex *v0, Vertex *v1 )
	{
		vertex0 = v0;
		vertex1 = v1;
	}
};

//-----------------------------------------------------------------------------
// Структура индексированного ребра (Indexed Edge Structure)
//-----------------------------------------------------------------------------
struct IndexedEdge
{
	unsigned short vertex0; // Индекс первой вершины ребра.
	unsigned short vertex1; // Индекс второй вершины ребра.
};

//-----------------------------------------------------------------------------
// Структура грани (Face Structure)
//-----------------------------------------------------------------------------
struct Face
{
	Vertex *vertex0; // Первая вершина грани.
	Vertex *vertex1; // Вторая вершина грани.
	Vertex *vertex2; // Третья вершина грани.

	//-------------------------------------------------------------------------
	// The face structure constructor.
	//-------------------------------------------------------------------------
	Face( Vertex *v0, Vertex *v1, Vertex *v2 )
	{
		vertex0 = v0;
		vertex1 = v1;
		vertex2 = v2;
	}
};

//-----------------------------------------------------------------------------
// Структура индексированной грани (Indexed Face Structure)
//-----------------------------------------------------------------------------
struct IndexedFace
{
	unsigned short vertex0; // Индекс первой вершины грани.
	unsigned short vertex1; // Индекс второй вершины грани.
	unsigned short vertex2; // Индекс третьей вершины грани.
};

//-----------------------------------------------------------------------------
// Возвращает true, если один данный бокс (куб, шестигранник) находится внутри второго бокса.
//-----------------------------------------------------------------------------
inline bool IsBoxInBox( D3DXVECTOR3 box1Min, D3DXVECTOR3 box1Max, D3DXVECTOR3 box2Min, D3DXVECTOR3 box2Max )
{
	if( box1Min.x > box2Max.x )
		return false;
	if( box1Min.y > box2Max.y )
		return false;
	if( box1Min.z > box2Max.z )
		return false;
	if( box1Max.x < box2Min.x )
		return false;
	if( box1Max.y < box2Min.y )
		return false;
	if( box1Max.z < box2Min.z )
		return false;

	return true;
}

//-----------------------------------------------------------------------------
// Возвращает true, если данная грань находится внутри бокса.
//-----------------------------------------------------------------------------
inline bool IsFaceInBox( Vertex *vertex0, Vertex *vertex1, Vertex *vertex2, D3DXVECTOR3 boxMin, D3DXVECTOR3 boxMax )
{
	// Находим крайние (минимальную и максимальную) точки грани по оси x. Затем
	// проверяем, находятся ли эти 2 точки в пределах оси x бокса.
	float minX = min( vertex0->translation.x, min( vertex1->translation.x, vertex2->translation.x ) );
	float maxX = max( vertex0->translation.x, max( vertex1->translation.x, vertex2->translation.x ) );
	if( maxX < boxMin.x )
		return false;
	if( minX > boxMax.x )
		return false;

	// Находим крайние (минимальную и максимальную) точки грани по оси y. Затем
	// проверяем, находятся ли эти 2 точки в пределах оси y бокса.
	float minY = min( vertex0->translation.y, min( vertex1->translation.y, vertex2->translation.y ) );
	float maxY = max( vertex0->translation.y, max( vertex1->translation.y, vertex2->translation.y ) );
	if( maxY < boxMin.y )
		return false;
	if( minY > boxMax.y )
		return false;

	// Находим крайние (минимальную и максимальную) точки грани по оси z. Затем
	// проверяем, находятся ли эти 2 точки в пределах оси z бокса.
	float minZ = min( vertex0->translation.z, min( vertex1->translation.z, vertex2->translation.z ) );
	float maxZ = max( vertex0->translation.z, max( vertex1->translation.z, vertex2->translation.z ) );
	if( maxZ < boxMin.z )
		return false;
	if( minZ > boxMax.z )
		return false;

	return true;
}

//-----------------------------------------------------------------------------
// Возвращает true, если данный бокс полностью размещён внутри данной ёмкости (volume).
//-----------------------------------------------------------------------------
inline bool IsBoxEnclosedByVolume( LinkedList< D3DXPLANE > *planes, D3DXVECTOR3 min, D3DXVECTOR3 max )
{
	planes->Iterate( true );
	while( planes->Iterate() )
	{
		if( D3DXPlaneDotCoord( planes->GetCurrent(), &D3DXVECTOR3( min.x, min.y, min.z ) ) < 0.0f )
			return false;
		if( D3DXPlaneDotCoord( planes->GetCurrent(), &D3DXVECTOR3( max.x, min.y, min.z ) ) < 0.0f )
			return false;
		if( D3DXPlaneDotCoord( planes->GetCurrent(), &D3DXVECTOR3( min.x, max.y, min.z ) ) < 0.0f )
			return false;
		if( D3DXPlaneDotCoord( planes->GetCurrent(), &D3DXVECTOR3( max.x, max.y, min.z ) ) < 0.0f )
			return false;
		if( D3DXPlaneDotCoord( planes->GetCurrent(), &D3DXVECTOR3( min.x, min.y, max.z ) ) < 0.0f )
			return false;
		if( D3DXPlaneDotCoord( planes->GetCurrent(), &D3DXVECTOR3( max.x, min.y, max.z ) ) < 0.0f )
			return false;
		if( D3DXPlaneDotCoord( planes->GetCurrent(), &D3DXVECTOR3( min.x, max.y, max.z ) ) < 0.0f )
			return false;
		if( D3DXPlaneDotCoord( planes->GetCurrent(), &D3DXVECTOR3( max.x, max.y, max.z ) ) < 0.0f )
			return false;
	}

	return true;
}

//-----------------------------------------------------------------------------
// Возвращает true, если данная сфера (sphere) полностью перекрывает данную ёмкость (volume).
//-----------------------------------------------------------------------------
inline bool IsSphereOverlappingVolume( LinkedList< D3DXPLANE > *planes, D3DXVECTOR3 translation, float radius )
{
	planes->Iterate( true );
	while( planes->Iterate() )
		if( D3DXPlaneDotCoord( planes->GetCurrent(), &translation ) < -radius )
			return false;

	return true;
}

//-----------------------------------------------------------------------------
// Возвращает true при столкновении двух данных сфер друг с другом.
//-----------------------------------------------------------------------------
inline bool IsSphereCollidingWithSphere( float *collisionDistance, D3DXVECTOR3 translation1, D3DXVECTOR3 translation2, D3DXVECTOR3 velocitySum, float radiiSum )
{
	// Вычисляем расстояние между сферами.
	float distanceBetween = D3DXVec3Length( &( translation1 - translation2 ) ) - radiiSum;

	// Вычисляем длину суммы векторов скорости (velocity vectors) двух сфер.
	float velocityLength = D3DXVec3Length( &velocitySum );

	// Если сферы нигде не соприкасаются друг с другом и длина их общего вектора скорости
	// меньше, чем расстояние между ними, то они не могут сталкиваться.
	if( distanceBetween > 0.0f && velocityLength < distanceBetween )
		return false;

	// Вычисляем нормализованную сумму векторов скорости двух сфер.
	D3DXVECTOR3 normalizedVelocity;
	D3DXVec3Normalize( &normalizedVelocity, &velocitySum );

	// Вычисляем вектор направления движения второй сферы к первой.
	D3DXVECTOR3 direction = translation1 - translation2;

	// Вычисляем угол между нормализованными векторами скорости и направления движения.
	float angleBetween = D3DXVec3Dot( &normalizedVelocity, &direction );

	// Проверяем, не удаляются ли сферы друг от друга.
	if( angleBetween <= 0.0f )
	{
		// Проверяем, соприкасаются ли сферы друг с другом (или одна находится внутри другой). Если нет,
		// то они не могут сталкиваться, так как они двигаются прочь друг от друга.
		if( distanceBetween < 0.0f )
		{
			// Если длина вектора скорости больше, чем расстояние между
			// сферами, то они удаляются друг от друга достаточно быстро,
			// и ни разу не столкнутся до завершения движения.
			if( velocityLength > -distanceBetween )
				return false;
		}
		else
			return false;
	}

	// Вычисляем длину вектора направления движения.
	float directionLength = D3DXVec3Length( &direction );

	// Вектор между двумя сферами и вектор направления движэения образуют две
	// стороны треугольника. Теперь, применив теорему Пифагора, находим
	// третью сторону треугольника (т.е. гипотенузу).
	float hypotenuse = ( directionLength * directionLength ) - ( angleBetween * angleBetween );

	// Проверяем, не подошли ли сферы друг к другу ближе, чем сумма их радиусов.
	float radiiSumSquared = radiiSum * radiiSum;
	if( hypotenuse >= radiiSumSquared )
		return false;

	// Вычисляем расстояние по вектору скорости, при котором сферы столкнутся.
	// Затем используем это расстояние для расчёта расстояния до наступления столкновения.
	float distance = radiiSumSquared - hypotenuse;
	*collisionDistance = angleBetween - (float)sqrt( distance );

	// Проверяем, что сферы не переместятся в пространстве более, чем позволяет их скорость.
	if( velocityLength < *collisionDistance )
		return false;

	return true;
}

#endif

  • Сохрани изменения в Проектах (Файл -> Сохранить).

Исследуем код Geometry.h

Структуры вершин (Vertex Structures)

В начале листинга видим 3 структуры: Vertex, LVertex и TLVertex. Все они используются для определения точки в 3D-пространстве, называемой вершина. Меши (=полигональные сетки) и все другие трёхмерные геометрические формы состоят из таких вершин, соединённых друг с другом и образующих единую 3D-форму (см. Рис. 2).
Каждый из видов этих вершин объявлен в отдельной структуре, т.к. все они имеют свои особенности:

Vertex Используется для вершин, которые трансформированы (имеют заранее вычисленное положение в 3D-пространстве), освещены и текстурированы средствами DirectX.
LVertex Используется для вершин, которые трансформированы (имеют заранее вычисленное положение в 3D-пространстве) и текстурированы средствами DirectX, но их освещением занимается игровой движок.
TLVertex Используется для вершин, которые текстурированы средствами DirectX, но их положение в 3D-пространстве и освещение просчитывает игровой движок.

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

Структуры рёбер и граней (Edge & Face Structures)

Рис. 2 Вершины, рёбра и грани - основные элементы для создания 3D-объектов
Рис. 2 Вершины, рёбра и грани - основные элементы для создания 3D-объектов

Следующие 4 структуры: Edge, IndexedEdge, Face и IndexedFace используются для хранения информации о рёбрах и гранях трёхмерных геометрических форм, например мешей (=полигональных сеток).
Ребро (Edge) - это линия между двумя вершинами. Поэтому структура Edge хранит указатель на каждую из этих двух вершин.
Структура IndexedEdge, в свою очередь, хранит значения индексов этих вершин. Эти индексы часто используются, когда вершины хранятся в хранилищах данных (например, в массиве или буфере индексов (index buffer), который является специальным типом буфера, используемом DirectX, для хранения вершин в оптимизированном виде).
Грань (Face) состоит из трёх вершин и покрывает всю площадь между этими вершинами. Структура Face хранит указатели на эти вершины, в то время как структура IndexedFace хранит индексы вершин.

Закрыть
noteПримечание

Обрати внимание, что в классической геометрии грань фигуры может состоять из более чем трёх вершин (например гранью куба является квадрат, у которого 4 вершины). В терминологии 3D-моделирования принято приравнивать грань фигуры к полигону, который в большинстве систем состоит ровно из трёх вершин. Системы, оперирующие четырёхугольными полигонами встречаются нечасто. Одной из таких была игровая консоль Sega Saturn, увидевшая свет в 1995 г. Её создатели хотели как лучше, а на деле лишь прибавили головной боли разработчикам игр, которые ранее никогда не работали с четырёхугольными полигонами.
На Рис. 2 видно, что боковая грань куба на самом деле состоит из двух треугольных граней (полигонов). Мы также будем придерживаться этого правила, разбивая грани фигур, лежащие в одной плоскости и имеющие более чем 3 вершины, на полигональные треугольники.

Закрыть
noteПримечание

Грань является односторонним объектом. Это означает, что у грани может быть видна (визуализирована) только одна сторона. Видимая сторона называется передней (фронтальной) стороной грани и определяется нормалью грани (вобщем-то, для этого нормали и применяются). Нормаль - это вектор, проведённый из точки на поверхности грани перпендикулярно её фронтальной (=видимой) стороне (плоскости). Нормаль определяется порядком, в котором указаны вершины грани (как правило, они указываются одна за другой, двигаясь по часовой стрелке).

Функции проверки столкновения геометрических форм

Вторую половину листинга занимает подборка встроенных(external link) (inline) функций, применяемых для проверки контакта между различными геометрическими формами. В C++ ключевое слово inline даёт команду компилятору заменять во время компиляции вызов данной функции немедленным выполнением конкретного кода из этой функции. Делая функцию встроенной (inline) программер убирает лишние вызовы функций. Недостатком данного подхода является увеличение объёма скомпилированного кода (то есть "на выходе" ты получишь чуть больший по размеру исполняемый файл).
Рассмотрим эти встроенные функции по порядку:

IsBoxInBox проверяет, соприкасается ли один 3D-бокс c другим 3D-боксом. В качестве параметров посылаем координаты проекций векторов этих боксов на координатные оси. Функция возвращает TRUE, если они сталкиваются (= имеют хотя бы одну общую точку), или FALSE, если нет.
Вот её прототип:

Прототип IsBoxInBox
inline bool IsBoxInBox( D3DXVECTOR3 box1Min, D3DXVECTOR3 box1Max, D3DXVECTOR3 box2Min, D3DXVECTOR3 box2Max )


IsFaceInBox проверяет, имеет ли данная грань контакт с 3D-боксом. В качестве параметров посылаем координаты вершин грани и координаты проекций векторов 3D-бокса на координатные оси. Функция возвращает TRUE, если грань и 3D-бокс сталкиваются (= имеют хотя бы одну общую точку), или FALSE, если нет.
Вот её прототип:

Прототип IsFaceInBox
inline bool IsFaceInBox( Vertex *vertex0, Vertex *vertex1, Vertex *vertex2, D3DXVECTOR3 boxMin, D3DXVECTOR3 boxMax )


IsBoxEnclosedByVolume проверяет, помещён ли данный бокс внутри некоторой ёмкости (3D-объекта). В качестве ёмкости может выступать любая 3D-форма, определённая набором плоскостей. В качестве параметров посылаем связный список (linked list), содержащий набор граней (плоскостей) ёмкости, а также координаты проекций векторов 3D-бокса на координатные оси. Функция возвращает TRUE, если 3D-бокс полностью помещён внутрь данной ёмкости, или FALSE, если нет.
Вот её прототип:

Прототип IsBoxEnclosedByVolume
inline bool IsBoxEnclosedByVolume( 
	LinkedList< D3DXPLANE> *planes, D3DXVECTOR3 min,
									D3DXVECTOR3 max )


IsSphereOverlappingVolume проверяет, контактирует ли (= имеет хотя бы одну общую точку) сфера с данной ёмкостью (volume). В качестве параметров посылаем связный список (linked list), содержащий набор граней (плоскостей) ёмкости, а также трансляцию (позицию в 3D-пространстве в данный момент времени) и радиус сферы. Функция возвращает TRUE, если сфера контактирует с ёмкостью. Иначе возвращается FALSE.
Вот её прототип:

Прототип IsSphereOverlappingVolume
inline bool IsSphereOverlappingVolume( 
	LinkedList< D3DXPLANE> *planes, D3DXVECTOR3 translation, float radius )


IsSphereCollidingWithSphere проверяет, сталкивается ли одна движущаяся сфера с другой (движущейся сферой). В качестве параметров посылаем указатель на переменную типа float, трансляцию обоих сфер, сумму векторов их скоростей, а также сумму их радиусов. Функция возвращает TRUE, если одна сфера касается другой. Иначе возвращается FALSE.
Если сферы сталкиваются друг с другом, переменная типа float collisionDistance, которую ты посылаешь в качестве параметра, будет заполнена текущим значением расстояния, оставшегося до столкновения.
Вот её прототип:

Прототип IsSphereCollidingWithSphere
inline bool IsSphereCollidingWithSphere( 
	float *collisionDistance,
D3DXVECTOR3 translation1, D3DXVECTOR3 translation2, D3DXVECTOR3 velocitySum, D3DXVECTOR3 radiiSum )


Когда ты станешь более опытным программером, ты можешь более подробно изучить эти функции и даже написать свои собственные. Рассматривай Geometry.h как начало твоей растущей 3D-геометрической библиотеки. По ходу курса ты увидишь эти функции в деле и после этого поймёшь, как они нужны игрокодеру.


ИГРОКОДИНГ  »  ИГРОКОДИНГ: Учебный курс  »  Программируем 3D-шутер от первого лица (FPS) (Win32, Cpp, DirectX9)  »  Часть 1. Создание движка  »  1.5 Добавляем поддержку базовой геометрии (Geometry.h)

Contributors to this page: slymentat .
Последнее изменение страницы Вторник 29 / Ноябрь, 2016 13:59:25 MSK автор slymentat.

Последние комментарии

No records to display