1.5 Добавляем поддержку базовой геометрии (Geometry.h)
Содержание
Intro
Заголовочный файл Geometry.h содержит множество вспомогательных функций для работы с базовыми геометрическими объектами и примитивами (различные виды вершин, рёбер, граней).1Также сюда входят DirectX-примитивы (например, куб и сфера). По математическим основам трёхмерной графики написана не одна сотня книг, да и одни только названия DirectX интерфейсов способны повергнуть в шок любого новичка. Код в Geometry.h может показаться слишком сложным для начинающего, даже несмотря на щедрые комментарии. К счастью, тебе, скорее всего, никогда не придётся напрямую использовать функции и классы из данного заголовка, так как данный код является вспомогательным (support code). На первых порах допускается просмотреть только объявления функций и классов + комментарии к ним. И учи вышмат! Вся компьютерная графика основана на нём.
Сейчас в Проекте нашего движка всего 4 файла: Engine.h, Engine.cpp, LinkedList.h, ResourceManagement.h .
Добавляем Geometry.h (Проект Engine)
В заголовочном файле Geomentry.h будут содержаться объявление и реализация вспомогательных функций и классов для работы с геометрическими примитивами (точка, ребро, грань, шар, куб и т.д.). Также присутствуют функции для детекции столкновений (collision detection) и проверки пересечений фигур друг с другом.ОК, приступаем.
- В "Обозревателе решений" главного окна MSVC++2010 щёлкни правой кнопкой мыши по папке (в терминологии Майкрософт это не папки, а фильтры!) "Заголовочные файлы" (Header Files).
- Во всплывающем меню Добавить->Создать элемент...
- В появившемся окне выбери "Заголовочный файл (.h)" и в поле "Имя" введи Geometry.h.
- Жмём "Добавить".
Добавленный файл сразу откроется в правой части MSVC++2010.
- В только что созданном и открытом файле Geometry.h набираем следующий код:
//----------------------------------------------------------------------------- // File: Geometry.h // Various geometry structures and processing functions. // Объявление и реализация вспомогательных функций и классов для работы // с геометрическими примитивами (точка, ребро, грань, шар, куб и т.д.). // // 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; // Translation of the vertex (in world space). D3DXVECTOR3 normal; // Vertex's normal vector. float tu, tv; // Texture UV coordinates. //------------------------------------------------------------------------- // 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; // Translation of the vertex (in screen space). D3DCOLOR diffuse; // Colour of the vertex. float tu, tv; // Texture UV coordinates. //------------------------------------------------------------------------- // 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; // Index of the edge's first vertex. unsigned short vertex1; // Index of the edge's second vertex. }; //----------------------------------------------------------------------------- // Face Structure // Структура грани (Face Structure) //----------------------------------------------------------------------------- struct Face { Vertex *vertex0; // First vertex of the face. // Первая вершина грани Vertex *vertex1; // Second vertex of the face. // Вторая вершина грани Vertex *vertex2; // Third vertex of the face. // Третья вершина грани //------------------------------------------------------------------------- // 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; // Index of the face's first vertex. // Индекс первой вершины unsigned short vertex1; // Index of the face's second vertex. // Индекс второй вершины unsigned short vertex2; // Index of the face's third vertex. // Индекс третьей вершины }; //----------------------------------------------------------------------------- // Returns true if the first given box is inside the second given box. // Возвращает 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; } //----------------------------------------------------------------------------- // Returns true if the given face is inside the given box. // Возвращает true, если данная грань находится внутри бокса. //----------------------------------------------------------------------------- inline bool IsFaceInBox( Vertex *vertex0, Vertex *vertex1, Vertex *vertex2, D3DXVECTOR3 boxMin, D3DXVECTOR3 boxMax ) { // Find the minimum and maximum points of the face along the x axis. Then // check if these two points are within the box's x axis extents. // Находим крайние (минимальную и максимальную) точки грани по оси 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; // Find the minimum and maximum points of the face along the y axis. Then // check if these two points are within the box's y axis extents. // Находим крайние (минимальную и максимальную) точки грани по оси 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; // Find the minimum and maximum points of the face along the z axis. Then // check if these two points are within the box's z axis extents. // Находим крайние (минимальную и максимальную) точки грани по оси 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; } //----------------------------------------------------------------------------- // Returns true if the given box is completely enclosed by the given volume. // Возвращает 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; } //----------------------------------------------------------------------------- // Returns true if the given sphere is overlapping the given volume. // Возвращает 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; } //----------------------------------------------------------------------------- // Returns true if the two given spheres collide. // Возвращает true при столкновении двух данных сфер друг с другом. //----------------------------------------------------------------------------- inline bool IsSphereCollidingWithSphere( float *collisionDistance, D3DXVECTOR3 translation1, D3DXVECTOR3 translation2, D3DXVECTOR3 velocitySum, float radiiSum ) { // Get the distance between the two spheres. // Вычисляем расстояние между сферами. float distanceBetween = D3DXVec3Length( &( translation1 - translation2 ) ) - radiiSum; // Get the length of the sum of the velocity vectors of the two spheres. // Вычисляем длину суммы векторов скорости (velocity vectors) двух сфер. float velocityLength = D3DXVec3Length( &velocitySum ); // If the spheres are not touching each other and the velocity length is // less than the distance between them, then they cannot collide. // Если сферы нигде не соприкасаются друг с другом и длина их общего вектора скорости // меньше, чем расстояние между ними, то они не могут сталкиваться. if( distanceBetween > 0.0f && velocityLength < distanceBetween ) return false; // Get the normalized sum of the velocity vectors. // Вычисляем нормализованную сумму векторов скорости двух сфер. D3DXVECTOR3 normalizedVelocity; D3DXVec3Normalize( &normalizedVelocity, &velocitySum ); // Get the direction vector from the second sphere to the first sphere. // Вычисляем вектор направления движения второй сферы к первой. D3DXVECTOR3 direction = translation1 - translation2; // Get the angle between the normalized velocity and direction vectors. // Вычисляем угол между нормализованными векторами скорости и направления движения. float angleBetween = D3DXVec3Dot( &normalizedVelocity, &direction ); // Check if the spheres are moving away from one another. // Проверяем, не удаляются ли сферы друг от друга. if( angleBetween <= 0.0f ) { // Check if they are touching (or inside) each other. If not then they // cannot collide since they are moving away from one another. // Проверяем, соприкасаются ли сферы друг с другом (или одна находится внутри другой). // Если нет, то они не могут сталкиваться, так как они двигаются прочь друг от друга. if( distanceBetween < 0.0f ) { // If the velocity length is greater than the distance between the // spheres then they are moving away from each other fast enough // that they will not be touching when they complete their move. // Если длина вектора скорости больше, чем расстояние между // сферами, то они удаляются друг от друга достаточно быстро, // и ни разу не столкнутся до завершения движения. if( velocityLength > -distanceBetween ) return false; } else return false; } // Get the length of the direction vector. // Вычисляем длину вектора направления движения. float directionLength = D3DXVec3Length( &direction ); // The vector between the two spheres and the velocity vector produce two // sides of a triangle. Now use Pythagorean Theorem to find the length of // the third side of the triangle (i.e. the hypotenuse). // Вектор между двумя сферами и вектор направления движэения образуют две // стороны треугольника. Теперь, применив теорему Пифагора, находим // третью сторону треугольника (т. е. гипотенузу). float hypotenuse = ( directionLength * directionLength ) - ( angleBetween * angleBetween ); // Ensure that the spheres come closer than the sum of their radii. // Проверяем, не подошли ли сферы друг к другу ближе, чем сумма их радиусов. float radiiSumSquared = radiiSum * radiiSum; if( hypotenuse >= radiiSumSquared ) return false; // Get the distance along the velocity vector that the spheres collide. // Then use this distance to calculate the distance to the collision. // Вычисляем расстояние по вектору скорости, при котором сферы столкнутся. // Затем используем это расстояние для расчёта расстояния до наступления столкновения. float distance = radiiSumSquared - hypotenuse; *collisionDistance = angleBetween - (float)sqrt( distance ); // Ensure that the sphere will not travel more than the velocity allows. // Проверяем, что сферы не переместятся в пространстве более, чем позволяет их скорость. if( velocityLength < *collisionDistance ) return false; return true; } #endif
- Сохрани Решение (Файл -> Сохранить все).
Исследуем код Geometry.h
Структуры вершин (Vertex Structures)
В начале листинга видим 3 структуры: Vertex, LVertex и TLVertex. Все они используются для определения точки в 3D-пространстве, называемой вершина. Меши (=полигональные сетки) и все другие трёхмерные геометрические формы состоят из таких вершин, соединённых друг с другом и образующих единую 3D-форму (см. Рис.1). Каждый из видов этих вершин объявлен в отдельной структуре, т.к. все они имеют свои особенности:
Vertex | Используется для вершин, которые трансформированы (имеют заранее вычисленное положение в 3D-пространстве), освещены и текстурированы средствами DirectX. |
LVertex | Используется для вершин, которые трансформированы (имеют заранее вычисленное положение в 3D-пространстве) и текстурированы средствами DirectX, но их освещением занимается игровой движок. |
TLVertex | Используется для вершин, которые текстурированы средствами DirectX, но их положение в 3D-пространстве и освещение просчитывает игровой движок. |
В нашем проекте чаще всего мы будем использовать структуру Vertex, так как в этом случае всю работу по их трансформации, освещению и текстурированию эффективно проделает DirectX. Если решишь использовать другие виды вершин, то в этом случае часть параметров придётся обрабатывать самостоятельно.
Структуры рёбер и граней (Edge & Face Structures)
Следующие 4 структуры: Edge, IndexedEdge, Face и IndexedFace используются для хранения информации о рёбрах и гранях трёхмерных геометрических форм, например мешей (=полигональных сеток).Ребро (Edge) - это линия между двумя вершинами. Поэтому структура Edge хранит указатель на каждую из этих двух вершин. Структура IndexedEdge, в свою очередь, хранит значения индексов этих вершин. Эти индексы часто используются, когда вершины хранятся в хранилищах данных (например, в массиве или буфере индексов (index buffer), который является специальным типом буфера, используемом DirectX, для хранения вершин в оптимизированном виде).
Грань (Face) состоит из трёх вершин и покрывает всю площадь между этими вершинами. Структура Face хранит указатели на эти вершины, в то время как структура IndexedFace хранит индексы вершин.
Примечание
Обрати внимание, что в классической геометрии грань фигуры может состоять из более чем трёх вершин (например гранью куба является квадрат, у которого 4 вершины). В терминологии 3D-моделирования принято приравнивать грань фигуры к полигону, который в большинстве систем состоит ровно из трёх вершин. Системы, оперирующие четырёхугольными полигонами встречаются нечасто. Одной из таких была игровая консоль Sega Saturn, увидевшая свет в 1995 г. Её создатели хотели как лучше, а на деле лишь прибавили головной боли разработчикам игр, которые ранее никогда не работали с четырёхугольными полигонами.
На Рис.1 видно, что боковая грань куба на самом деле состоит из двух треугольных граней (полигонов). Мы также будем придерживаться этого правила, разбивая грани фигур, лежащие в одной плоскости и имеющие более чем 3 вершины, на полигональные треугольники.
Примечание
Грань является односторонним объектом. Это означает, что у грани может быть видна (визуализирована) только одна сторона. Видимая сторона называется передней (фронтальной) стороной грани и определяется нормалью грани (вобщем-то, для этого нормали и применяются). Нормаль - это вектор, проведённый из точки на поверхности грани перпендикулярно её фронтальной (=видимой) стороне (плоскости). Нормаль определяется порядком, в котором указаны вершины грани (как правило, они указываются одна за другой, двигаясь по часовой стрелке).
Функции проверки столкновения геометрических форм
Вторую половину листинга занимает подборка встроенных (inline) функций, применяемых для проверки контакта между различными геометрическими формами. В C++ ключевое слово inline даёт команду компилятору заменять во время компиляции вызов данной функции немедленным выполнением конкретного кода из этой функции. Делая функцию встроенной (inline) программер убирает лишние вызовы функций. Недостатком данного подхода является увеличение объёма скомпилированного кода (то есть "на выходе" ты получишь чуть больший по размеру исполняемый файл). Рассмотрим эти встроенные функции по порядку:IsBoxInBox проверяет, соприкасается ли один 3D-бокс c другим 3D-боксом. В качестве параметров посылаем координаты проекций векторов этих боксов на координатные оси. Функция возвращает TRUE, если они сталкиваются (= имеют хотя бы одну общую точку), или FALSE, если нет. Вот её прототип:
inline bool IsBoxInBox( D3DXVECTOR3 boxlMin, D3DXVECTOR3 boxlMax, D3DXVECTOR3 box2Min, D3DXVECTOR3 box2Max );
IsFacelnBox проверяет, имеет ли данная грань контакт с 3D-боксом. В качестве параметров посылаем координаты вершин грани и координаты проекций векторов 3Р-бокса на координатные оси. Функция возвращает TRUE, если грань и 3Р-бокс сталкиваются (= имеют хотя бы одну общую точку), или FALSE, если нет. Вот её прототип:
inline bool IsFaceInBox( Vertex *vertex0, Vertex *vertex1, Vertex *vertex2, D3DXVECTOR3 boxMin, D3DXVECTOR3 boxMax );
IsBoxEnclosedByVolume проверяет, помещён ли данный бокс внутри некоторой ёмкости (3D-объекта). В качестве ёмкости может выступать любая 3D-форма, определённая набором плоскостей. В качестве параметров посылаем связный список (linked list), содержащий набор граней (плоскостей) ёмкости, а также координаты проекций векторов 3D-бокса на координатные оси. Функция возвращает TRUE, если полностью помещён внутрь данной ёмкости, или FALSE, если нет. Вот её прототип:
inline bool IsBoxEnclosedByVolume(LinkedList< D3DXPLANE> *planes, D3DXVECTOR3 min, D3DXVECTOR3 max );
IsSphereOverlappingVolume проверяет, контактирует ли (= имеет хотя бы одну общую точку) сфера с данной ёмкостью (volume). В качестве параметров посылаем связный список (linked list), содержащий набор граней (плоскостей) ёмкости, а также трансляцию (позицию в 3D-пространстве в данный момент времени) и радиус сферы. Функция возвращает TRUE, если сфера контактирует с ёмкостью. Иначе возвращается FALSE. Вот её прототип:
inline bool IsSphereOverlappingVolume(LinkedList< D3DXPLANE> *planes, D3DXVECTOR3 translation, float radius );
IsSphereCollidingWithSphere проверяет, сталкивается ли одна движущаяся сфера с другой (движущейся сферой). В качестве параметров посылаем указатель на переменную типа float, трансляцию обоих сфер, сумму векторов их скоростей, а также сумму их радиусов. Функция возвращает TRUE, если одна сфера касается другой. Иначе возвращается FALSE. Если сферы сталкиваются друг с другом, переменная типа float collisionDistance, которую ты посылаешь в качестве параметра, будет заполнена текущим значением расстояния, оставшегося до столкновения. Вот её прототип:
inline bool IsSphereCollidingWithSphere( float *collisionDistance, D3DXVECTOR3 translation1, D3DXVECTOR3 translation2, D3DXVECTOR3 velocitySum, D3DXVECTOR3 radiiSum );
Когда ты станешь более опытным программером, ты можешь более подробно изучить эти функции и даже написать свои собственные. Рассматривай Geometry.h как начало твоей растущей 3D-геометрической библиотеки. По ходу курса ты увидишь эти функции в деле и после этого поймёшь, как они нужны игрокодеру.
Интегрируем поддержку базовой геометрии в движок
"Подключим" к Проекту Engine заголовок Geometry.h .Изменения в Engine.h (Проект Engine)
- Добавь инструкцию #include "Geometry.h" в файл Engine.h, чуть ниже строки #include "ResourceManagement.h":
... //----------------------------- // Engine Includes //----------------------------- #include "LinkedList.h" #include "ResourceManagement.h" #include "Geometry.h" ...
- Сохрани Решение (Файл -> Сохранить все).
Источники
1. Young V. Programming a Multiplayer FPS in DirectX 9.0. - Charles River Media, 2005
ДАЛЕЕ ==> Кодим 3D FPS DX9. 1.6 Тестируем фреймворк движка
Последние изменения страницы Суббота 09 / Июль, 2022 01:47:07 MSK
Последние комментарии wiki