Рисунок 1. Окно программы.
Обратите внимание на то, что мы должны создать в данном окне меню для отображения окна выбора файла, который в дальнейшем будет загружен (рис. 2).
Рисунок 2. Меню загрузки файла 3D модели.
В остальном код оболочки программы остался неизменным за некоторыми исключениями, которые мы рассмотрим после того, как рассмотрим реализацию класса для загрузки 3D-модели.
Добавьте к проекту файл anModelLoader.cs. В нем будут реализованы классы, которые будут отвечать за загрузку, хранение и отрисовку 3D-модели.
Сейчас мы подробно его рассмотрим. Для начала посмотрите на рисунок 3. На нем представлено изображение 2-х полигонов, имеющих одно общее ребро.
Рисунок 3. Пример полигонов.
На рисунке прокомментированы две вершины, которые не однократно используются для описания (построения) отрезков, образующих полигоны. Не сложно догадаться о том, что описывая каждый полигон, из которых состоит трехмерная модель, мы получим огромную избыточность данных. Поэтому для хранения информации о геометрии трехмерного массива, как правило, используют два массива:
- Первый массив содержит координаты всех вершин (каждая вершина описывается 3 координатами).
- Второй массив содержит последовательности из номеров вершин, которые мы должны соединять, чтобы восстановить геометрию загружаемой 3D-модели.
Также мы можем считать из файла информацию о текстуре (файл, который должен быть загружен в качестве текстуры, а также текстурные координаты), после чего мы можем загрузить и визуализировать трехмерную модель с текстурой. Таким образом, любая сцена, созданная в 3D Studio Max или другом пакете трехмерного моделирования может быть загружена нами в программу.
Дальше, реализовав камеру для перемещения по трехмерной сцене или какие-либо другие опции просмотра данной модели, мы можем добиться высокого уровня интерактивности создаваемого приложения. Стоит отметить вопрос визуализации данной 3D-модели.
После загрузки модели геометрия будет загружена в массив объектов limb, каждый из элементов которого будет отвечать за один из логических элементов сцены. Т.е. если в трехмерной сцене было экспортировано два объекта - куб и 3D-модель чайника, то мы получим массив из 2-х экземпляров класса limb, один из которых будет описывать геометрию чайника, второй – геометрию куба.
После загрузки производится визуализация данной модели в дисплейный список. После этого нам достаточно будет вызывать данный дисплейный список для отрисовки геометрии, что сильно сократит нагрузку на графический адаптер. Объектно-ориентированное программирование позволит нам удобно загружать любое количество 3D-моделей, а также легко управлять перемещением данных 3D-моделей в сцене и их отрисовкой.
Теперь вернемся к рассмотрению кода, реализующего данную функциональность нашей программы:
1. Класс LIMB:
/*http://esate.ru, Anvi*/
// класс LIMB отвечает за логические единицы 3D объектов в загружаемой сцене
class LIMB
{
// при инициализации мы должны указать количество вершин (vertex) и полигонов
// (face) которые описывают геометрию подобъекта
public LIMB( int a, int b)
{
if (temp[0] == 0)
temp[0] = 1;
// записываем количество вершин и полигонов
VandF[0] = a;
VandF[1] = b;
// выделяем память
memcompl();
}
public int Itog; // флаг успешности
// массивы для хранения данных (геометрии и текстурных координат)
public float[,] vert;
public int[,] face;
public float[,] t_vert;
public int[,] t_face;
// номер материала (текстуры) данного подобъекта
public int MaterialNom = -1;
// временное хранение информации
public int[] VandF = new int[4];
private int[] temp = new int[2];
// флаг, говорящий о том, что модель использует текстуру
private bool ModelHasTexture = false ;
// функция для определения значения флага (о наличии текстуры)
public bool NeedTexture()
{
// возвращаем значение флага
return ModelHasTexture;
}
// массивы для текстурных координат
public void createTextureVertexMem( int a)
{
VandF[2] = a;
t_vert = new float[3, VandF[2]];
}
// привязка значений текстурных координат к полигонам
public void createTextureFaceMem( int b)
{
VandF[3] = b;
t_face = new int[3, VandF[3]];
// отмечаем флаг о наличии текстуры
ModelHasTexture = true;
}
// память для геометрии
private void memcompl()
{
vert = new float[3, VandF[0]];
face = new int[3, VandF[1]];
}
// номер текстуры
public int GetTextureNom()
{
return MaterialNom;
}
};
// данный класс относился к немного другой реализации, будем считать его заготовкой реализации на будущее
public class Model_Prop
{
public Model_Prop()
{
pos_abs[0] = 0;
pos_abs[1] = 0;
pos_abs[2] = 0;
maximum[0] = 0;
maximum[1] = 0;
maximum[2] = 0;
minimum[0] = 0;
minimum[1] = 0;
minimum[2] = 0;
rotating_angles[0] = 0;
rotating_angles[1] = 0;
rotating_angles[2] = 0;
}
public float[] pos_abs = new float[3];
public float[] maximum = new float[3];
public float[] minimum = new float[3];
public float[] rotating_angles = new float[3];
};
Методы использования библиотеки DevIL мы уже изучали. Теперь мы должны немного изменить реализацию таким образом, чтобы завернуть ее в класс, который позволит быстро загружать и использовать текстуру прямо при чтении файла с геометрией сцены.
/*http://esate.ru, Anvi*/
// класс для работы с текстурами
public class TexturesForObjects
{
public TexturesForObjects()
{
}
// имя текстуры
private string texture_name = "";
// ее ID
private int imageId = 0;
// идентификатор текстуры в памяти openGL
private uint mGlTextureObject = 0;
// получение этого идентификатора
public uint GetTextureObj()
{
return mGlTextureObject;
}
// загрузка текстуры
public void LoadTextureForModel( string FileName)
{
// запоминаем имя файла
texture_name = FileName;
// создаем изображение с идентификатором imageId
Il.ilGenImages(1, out imageId);
// делаем изображение текущим
Il.ilBindImage(imageId);
string url = "";
// получаем адрес текущей директории
url = Directory.GetCurrentDirectory();
url += "";
// добавляем имя текстуры
url += texture_name;
// если загрузка удалась
if ( Il.ilLoadImage(url))
{
// если загрузка прошла успешно
// сохраняем размеры изображения
int width = Il.ilGetInteger( Il.IL_IMAGE_WIDTH);
int height = Il.ilGetInteger( Il.IL_IMAGE_HEIGHT);
// определяем число бит на пиксель
int bitspp = Il.ilGetInteger( Il.IL_IMAGE_BITS_PER_PIXEL);
switch (bitspp) // в зависимости от полученного результата
{
// создаем текстуру используя режим GL_RGB или GL_RGBA
case 24:
mGlTextureObject = MakeGlTexture( Gl.GL_RGB, Il.ilGetData(), width, height);
break ;
case 32:
mGlTextureObject = MakeGlTexture( Gl.GL_RGBA, Il.ilGetData(), width, height);
break ;
}
// очищаем память
Il.ilDeleteImages(1, ref imageId);
}
}
// создание текстуры в памяти openGL
private static uint MakeGlTexture( int Format, IntPtr pixels, int w, int h)
{
// идентификатор текстурного объекта
uint texObject;
// генерируем текстурный объект
Gl.glGenTextures(1, out texObject);
// устанавливаем режим упаковки пикселей
Gl.glPixelStorei( Gl.GL_UNPACK_ALIGNMENT, 1);
// создаем привязку к только что созданной текстуре
Gl.glBindTexture( Gl.GL_TEXTURE_2D, texObject);
// устанавливаем режим фильтрации и повторения текстуры
Gl.glTexParameteri( Gl.GL_TEXTURE_2D, Gl.GL_TEXTURE_WRAP_S, Gl.GL_REPEAT);
Gl.glTexParameteri( Gl.GL_TEXTURE_2D, Gl.GL_TEXTURE_WRAP_T, Gl.GL_REPEAT);
Gl.glTexParameteri( Gl.GL_TEXTURE_2D, Gl.GL_TEXTURE_MAG_FILTER, Gl.GL_LINEAR);
Gl.glTexParameteri( Gl.GL_TEXTURE_2D, Gl.GL_TEXTURE_MIN_FILTER, Gl.GL_LINEAR);
Gl.glTexEnvf( Gl.GL_TEXTURE_ENV, Gl.GL_TEXTURE_ENV_MODE, Gl.GL_REPLACE);
// создаем RGB или RGBA текстуру
switch (Format)
{
case Gl.GL_RGB:
Gl.glTexImage2D( Gl.GL_TEXTURE_2D, 0, Gl.GL_RGB, w, h, 0, Gl.GL_RGB, Gl.GL_UNSIGNED_BYTE, pixels);
break ;
case Gl.GL_RGBA:
Gl.glTexImage2D( Gl.GL_TEXTURE_2D, 0, Gl.GL_RGBA, w, h, 0, Gl.GL_RGBA, Gl.GL_UNSIGNED_BYTE, pixels);
break ;
}
// возвращаем идентификатор текстурного объекта
return texObject;
}
}
Немного рассмотрим формат ASE, в который мы экспортировали геометрию и будем использовать для хранения и загрузки наших трехмерных сцен. Небольшой отрывок файла в формате ASE (формат является текстовым, благодаря чему вы можете без труда изучить его внутреннее устройство).
/*http://esate.ru, Anvi*/
*3DSMAX_ASCIIEXPORT 200
*COMMENT "AsciiExport Version 2,00 - Mon Jan 18 01:42:41 2010"
*SCENE {
*SCENE_FILENAME ""
*SCENE_FIRSTFRAME 0
*SCENE_LASTFRAME 100
*SCENE_FRAMESPEED 30
*SCENE_TICKSPERFRAME 160
*SCENE_BACKGROUND_STATIC 0.0000 0.0000 0.0000
*SCENE_AMBIENT_STATIC 0.0000 0.0000 0.0000
}
*MATERIAL_LIST {
*MATERIAL_COUNT 0
}
*GEOMOBJECT {
*NODE_NAME "Box01"
*NODE_TM {
*NODE_NAME "Box01"
*INHERIT_POS 0 0 0
*INHERIT_ROT 0 0 0
*INHERIT_SCL 0 0 0
*TM_ROW0 0.3309 0.0000 0.0000
*TM_ROW1 0.0000 0.3309 0.0000
*TM_ROW2 0.0000 0.0000 0.3309
*TM_ROW3 -21.0978 -6.6046 7.9985
*TM_POS -21.0978 -6.6046 7.9985
*TM_ROTAXIS 0.0000 0.0000 0.0000
*TM_ROTANGLE 0.0000
*TM_SCALE 0.3309 0.3309 0.3309
*TM_SCALEAXIS 0.0000 0.0000 0.0000
*TM_SCALEAXISANG 0.0000
}
*MESH {
*TIMEVALUE 0
*MESH_NUMVERTEX 8
*MESH_NUMFACES 12
*MESH_VERTEX_LIST {
*MESH_VERTEX 0 -25.6486 -11.3866 7.9985
*MESH_VERTEX 1 -16.5471 -11.3866 7.9985
*MESH_VERTEX 2 -25.6486 -1.8227 7.9985
*MESH_VERTEX 3 -16.5471 -1.8227 7.9985
*MESH_VERTEX 4 -25.6486 -11.3866 15.3495
*MESH_VERTEX 5 -16.5471 -11.3866 15.3495
*MESH_VERTEX 6 -25.6486 -1.8227 15.3495
*MESH_VERTEX 7 -16.5471 -1.8227 15.3495
}
*MESH_FACE_LIST {
*MESH_FACE 0: A: 0 B: 2 C: 3 AB: 1 BC: 1 CA: 0 *MESH_SMOOTHING 2 *MESH_MTLID 1
*MESH_FACE 1: A: 3 B: 1 C: 0 AB: 1 BC: 1 CA: 0 *MESH_SMOOTHING 2 *MESH_MTLID 1
*MESH_FACE 2: A: 4 B: 5 C: 7 AB: 1 BC: 1 CA: 0 *MESH_SMOOTHING 3 *MESH_MTLID 0
*MESH_FACE 3: A: 7 B: 6 C: 4 AB: 1 BC: 1 CA: 0 *MESH_SMOOTHING 3 *MESH_MTLID 0
*MESH_FACE 4: A: 0 B: 1 C: 5 AB: 1 BC: 1 CA: 0 *MESH_SMOOTHING 4 *MESH_MTLID 4
*MESH_FACE 5: A: 5 B: 4 C: 0 AB: 1 BC: 1 CA: 0 *MESH_SMOOTHING 4 *MESH_MTLID 4
*MESH_FACE 6: A: 1 B: 3 C: 7 AB: 1 BC: 1 CA: 0 *MESH_SMOOTHING 5 *MESH_MTLID 3
*MESH_FACE 7: A: 7 B: 5 C: 1 AB: 1 BC: 1 CA: 0 *MESH_SMOOTHING 5 *MESH_MTLID 3
*MESH_FACE 8: A: 3 B: 2 C: 6 AB: 1 BC: 1 CA: 0 *MESH_SMOOTHING 6 *MESH_MTLID 5
*MESH_FACE 9: A: 6 B: 7 C: 3 AB: 1 BC: 1 CA: 0 *MESH_SMOOTHING 6 *MESH_MTLID 5
*MESH_FACE 10: A: 2 B: 0 C: 4 AB: 1 BC: 1 CA: 0 *MESH_SMOOTHING 7 *MESH_MTLID 2
*MESH_FACE 11: A: 4 B: 6 C: 2 AB: 1 BC: 1 CA: 0 *MESH_SMOOTHING 7 *MESH_MTLID 2
}
*MESH_NUMTVERTEX 12
*MESH_TVERTLIST {
*MESH_TVERT 0 0.0000 0.0000 0.0000
*MESH_TVERT 1 1.0000 0.0000 0.0000
*MESH_TVERT 2 0.0000 1.0000 0.0000
*MESH_TVERT 3 1.0000 1.0000 0.0000
*MESH_TVERT 4 0.0000 0.0000 0.0000
*MESH_TVERT 5 1.0000 0.0000 0.0000
*MESH_TVERT 6 0.0000 1.0000 0.0000
*MESH_TVERT 7 1.0000 1.0000 0.0000
*MESH_TVERT 8 0.0000 0.0000 0.0000
*MESH_TVERT 9 1.0000 0.0000 0.0000
*MESH_TVERT 10 0.0000 1.0000 0.0000
*MESH_TVERT 11 1.0000 1.0000 0.0000
}
*MESH_NUMTVFACES 12
*MESH_TFACELIST {
*MESH_TFACE 0 9 11 10
*MESH_TFACE 1 10 8 9
*MESH_TFACE 2 8 9 11
*MESH_TFACE 3 11 10 8
*MESH_TFACE 4 4 5 7
*MESH_TFACE 5 7 6 4
*MESH_TFACE 6 0 1 3
*MESH_TFACE 7 3 2 0
*MESH_TFACE 8 4 5 7
*MESH_TFACE 9 7 6 4
*MESH_TFACE 10 0 1 3
*MESH_TFACE 11 3 2 0
}
}
*PROP_MOTIONBLUR 0
*PROP_CASTSHADOW 1
*PROP_RECVSHADOW 1
Как видно из кода, файл представляет собой управляющие конструкции. Ключевые слова, указывающие на новую управляющую конструкцию, отмечены знаком «*» в начале слова.
Алгоритм чтения файла и организация файла модели в памяти программы будут выглядеть следующим образом:
1. Начинается построчное чтение файла (вся необходимая информация для любой управляющей конструкции будет находиться в рамках одной строки, поэтому построчное чтение нам подходит)
2. Отсечение первого слова в строке.
3. Проверка первого символа в полученном первом слове – если оно равно «*», то мы имеем управляющее слово.
4. На основе управляющего слова мы проверяем, что это за управляющее слово, и если оно относится к тем, которые необходимы нам (описывают геометрию, текстурные координаты), то мы обрабатываем данную строку, отрезая первые слова и затем переводя их из строкового представления к числовому.
Как видите, все довольно просто. Теперь немного опишем те управляющие слова, которые нас интересуют:
*MATERIAL_COUNT – количество текстур, используемое в данной 3D-сцене.
Если значение этого параметра не равно 0, то дальше будет идти описание каждого материала. Среди параметров ambient, diffuse, specular, которые отражают свойства материала (и обработку которых мы не рассматривали в рамках данной программы) в блоке описания материала будет также указана информация о текстуре.
*MATERIAL – начало блока описания материала. Далее следует указание номера материала.
Пример блока описания материала:
/*http://esate.ru, Anvi*/
*MATERIAL_LIST {
*MATERIAL_COUNT 1
*MATERIAL 0 {
*MATERIAL_NAME "01 - Default"
*MATERIAL_CLASS "Standard"
*MATERIAL_AMBIENT 0.5882 0.5882 0.5882
*MATERIAL_DIFFUSE 0.5882 0.5882 0.5882
*MATERIAL_SPECULAR 0.9000 0.9000 0.9000
*MATERIAL_SHINE 0.1000
*MATERIAL_SHINESTRENGTH 0.0000
*MATERIAL_TRANSPARENCY 0.0000
*MATERIAL_WIRESIZE 1.0000
*MATERIAL_SHADING Blinn
*MATERIAL_XP_FALLOFF 0.0000
*MATERIAL_SELFILLUM 0.0000
*MATERIAL_FALLOFF In
*MATERIAL_XP_TYPE Filter
*MAP_DIFFUSE {
*MAP_NAME "Map #1"
*MAP_CLASS "Bitmap"
*MAP_SUBNO 1
*MAP_AMOUNT 1.0000
*BITMAP "C:UsersanviDesktop13-3.png"
*MAP_TYPE Screen
*UVW_U_OFFSET 0.0000
*UVW_V_OFFSET 0.0000
*UVW_U_TILING 1.0000
*UVW_V_TILING 1.0000
*UVW_ANGLE 0.0000
*UVW_BLUR 1.0000
*UVW_BLUR_OFFSET 0.0000
*UVW_NOUSE_AMT 1.0000
*UVW_NOISE_SIZE 1.0000
*UVW_NOISE_LEVEL 1
*UVW_NOISE_PHASE 0.0000
*BITMAP_FILTER Pyramidal
}
}
}
*BITMAP – адрес текстуры для данного материала.
*MATERIAL_REF – указание на то, какой номер материала назначен данному объекту.
Теперь рассмотрим описание геометрии и текстурных координат.
*GEOMOBJECT – указывает на новый геометрический подобъект в загружаемой из файла трехмерной сцене. Этот блок также описывает довольно большое количество информации – имя объекта, его начальные координаты, ориентация и т.д.
*MESH_NUMVERTEX – указывает на количество вершин, которые будут описывать геометрию данного 3D-объекта (подобъекта). Мы будем использовать это значение (и значение, описывающие количество полигонов) для выделения памяти при создании экземпляра, описывающего геометрию подобъекта.
*MESH_NUMFACES – указывает количество полигонов, в данном геометрическом подобъекте.
*MESH_NUMTVERTEX, *MESH_NUMTVFACES – описание текстурных координат и их привязка к полигонам.
*MESH_VERTEX, *MESH_FACE, *MESH_TVERT, *MESH_TFACE – непосредственное описание вершин и полигонов (геометрии и текстурных координат). Самые часто встречаемые ключевые слова.
За данными ключевыми словами следует ряд параметров. В случае вершин - это номер вершины и три ее координаты. В случае описания полигонов – это номер полигона (после которого стоит знак «:», который необходимо отрезать) и далее номера вершин, координаты которых должны быть использованы для построения полигона. Обратите внимание на то, что часть информации из данной строки мы не используем.
Теперь, разобравшись с данными в описании 3D-модели, мы переходим к алгоритму загрузки 3D-модели, а также ее визуализации. Первым делом, объявляются необходимые для дальнейшей работы переменные, массивы для объектов и т.д.
/*http://esate.ru, Anvi*/
// класс, выполняющий загрузку 3D модели
public class anModelLoader
{
public anModelLoader()
{
}
// имя файла
public string FName = "";
// загружен ли (флаг)
private bool isLoad = false ;
// счетчик подобъектов
private int count_limbs;
// переменная для хранения номера текстуры
private int mat_nom = 0;
// номер дисплейного списка с данной моделью
private int thisList = 0;
// данная переменная будет указывать на количество
// прочитанных символов в строке при чтении информации из файла
private int GlobalStringFrom = 0;
// массив подобъектов
LIMB[] limbs = null;
// массив для хранения текстур
TexturesForObjects[] text_objects = null;
// описание ориентации модели
Model_Prop coord = new Model_Prop();
…
Функция загрузки 3D модели, как мы уже говорили, это построчное чтение файла и поиск управляющих слов, а также последующее внесение описываемых ими данных в объекты описания геометрии и создание и загрузка текстурных объектов в память OpenGL:
/*http://esate.ru, Anvi*/
// загрузка модели
public int LoadModel(string FileName)
{
// модель может содержать до 256 подобъектов
limbs = new LIMB[256];
// счетчик скинут
int limb_ = -1;
// имя файла
FName = FileName;
// начинаем чтение файла
StreamReader sw = new StreamReader(FileName, Encoding.Default);
// временные буферы
string a_buff = "";
string b_buff = "";
string c_buff = "";
// счетчики вершин и полигонов
int ver = 0, fac = 0;
// если строка успешно прочитана
while ((a_buff = sw.ReadLine()) != null)
{
// получаем первое слово
b_buff = GetFirstWord(a_buff, 0);
if (b_buff[0] == '*') // определяем, является ли первый символ звездочкой
{
switch (b_buff) // если да, то проверяем какое управляющее
// слово содержится в первом прочитанном слове
{
case "*MATERIAL_COUNT": // счетчик материалов
{
// получаем первое слово от символа указанного в GlobalStringFrom
c_buff = GetFirstWord(a_buff, GlobalStringFrom);
int mat = System.Convert.ToInt32(c_buff);
// создаем объект для текстуры в памяти
text_objects = new TexturesForObjects[mat];
continue;
}
case "*MATERIAL_REF": // номер текстуры
{
// записываем для текущего подобъекта номер текстуры
c_buff = GetFirstWord(a_buff, GlobalStringFrom);
int mat_ref = System.Convert.ToInt32(c_buff);
// устанавливаем номер материала, соответствующий данной модели.
limbs[limb_].SetMaterialNom(mat_ref);
continue;
}
case "*MATERIAL": // указание на материал
{
c_buff = GetFirstWord(a_buff, GlobalStringFrom);
mat_nom = System.Convert.ToInt32(c_buff);
continue;
}
case "*GEOMOBJECT": // начинается описание геометрии подобъекта
{
limb_++; // записываем в счетчик подобъектов
continue;
}
case "*MESH_NUMVERTEX": // количество вершин в подобъекте
{
c_buff = GetFirstWord(a_buff, GlobalStringFrom);
ver = System.Convert.ToInt32(c_buff);
continue;
}
case "*BITMAP": // имя текстуры
{
c_buff = ""; // обнуляем временный буфер
for ( int ax = GlobalStringFrom + 2; ax < a_buff.Length - 1; ax++)
c_buff += a_buff[ax]; // считываем имя текстуры
text_objects[mat_nom] = new TexturesForObjects(); // новый объект для текстуры
text_objects[mat_nom].LoadTextureForModel(c_buff); // загружаем текстуру
continue;
}
case "*MESH_NUMTVERTEX": // количество текстурных координат
// данное слово говорит о наличии текстурных координат,
// следовательно, мы должны выделить память для них
{
c_buff = GetFirstWord(a_buff, GlobalStringFrom);
if (limbs[limb_] != null)
{
limbs[limb_].createTextureVertexMem( System.Convert.ToInt32(c_buff));
}
continue;
}
case "*MESH_NUMTVFACES": // память для текстурных координат (faces)
{
c_buff = GetFirstWord(a_buff, GlobalStringFrom);
if (limbs[limb_] != null)
{
// выделяем память для текстурных координат
limbs[limb_].createTextureFaceMem( System.Convert.ToInt32(c_buff));
}
continue;
}
case "*MESH_NUMFACES": // количество полигонов в подобъекте
{
c_buff = GetFirstWord(a_buff, GlobalStringFrom);
fac = System.Convert.ToInt32(c_buff);
// если было объявляющее слово *GEOMOBJECT
// (гарантия выполнения условия limb_ > -1) и были указаны количество вершин
if (limb_ > -1 && ver > -1 && fac > -1)
{
// создаем новый подобъект в памяти
limbs[limb_] = new LIMB(ver, fac);
}
else
{
// иначе завершаем неудачей
return -1;
}
continue;
}
case "*MESH_VERTEX": // информация о вершине
{
// подобъект создан в памяти
if (limb_ == -1)
return -2;
if (limbs[limb_] == null)
return -3;
string a1 = "", a2 = "", a3 = "", a4 = "";
// получаем информацию о координатах и номере вершины
// (получаем все слова в строке)
a1 = GetFirstWord(a_buff, GlobalStringFrom);
a2 = GetFirstWord(a_buff, GlobalStringFrom);
a3 = GetFirstWord(a_buff, GlobalStringFrom);
a4 = GetFirstWord(a_buff, GlobalStringFrom);
// преобразовываем в целое число
int NomVertex = System.Convert.ToInt32(a1);
// заменяем точки в представлении числа с плавающей точкой
// на запятые, чтобы правильно выполнилась функция
// преобразования строки в дробное число
a2 = a2.Replace('.', ',');
a3 = a3.Replace('.', ',');
a4 = a4.Replace('.', ',');
// записываем информацию о вершине
limbs[limb_].vert[0, NomVertex] = (float) System.Convert.ToDouble(a2); // x
limbs[limb_].vert[1, NomVertex] = (float) System.Convert.ToDouble(a3); // y
limbs[limb_].vert[2, NomVertex] = (float) System.Convert.ToDouble(a4); // z
continue;
}
case "*MESH_FACE": // информация о полигоне
{
// подобъект создан в памяти
if (limb_ == -1)
return -2;
if (limbs[limb_] == null)
return -3;
// временные переменные
string a1 = "", a2 = "", a3 = "", a4 = "", a5 = "", a6 = "", a7 = "";
// получаем все слова в строке
a1 = GetFirstWord(a_buff, GlobalStringFrom);
a2 = GetFirstWord(a_buff, GlobalStringFrom);
a3 = GetFirstWord(a_buff, GlobalStringFrom);
a4 = GetFirstWord(a_buff, GlobalStringFrom);
a5 = GetFirstWord(a_buff, GlobalStringFrom);
a6 = GetFirstWord(a_buff, GlobalStringFrom);
a7 = GetFirstWord(a_buff, GlobalStringFrom);
// получаем номер полигона из первого слова в строке, заменив
// последний символ ':' после номера на флаг окончания строки.
int NomFace = System.Convert.ToInt32(a1.Replace(':', ''));
// записываем номера вершин, которые нас интересуют
limbs[limb_].face[0, NomFace] = System.Convert.ToInt32(a3);
limbs[limb_].face[1, NomFace] = System.Convert.ToInt32(a5);
limbs[limb_].face[2, NomFace] = System.Convert.ToInt32(a7);
continue;
}
// текстурные координаты
case "*MESH_TVERT":
{
// подобъект создан в памяти
if (limb_ == -1)
return -2;
if (limbs[limb_] == null)
return -3;
// временные переменные
string a1 = "", a2 = "", a3 = "", a4 = "";
// получаем все слова в строке
a1 = GetFirstWord(a_buff, GlobalStringFrom);
a2 = GetFirstWord(a_buff, GlobalStringFrom);
a3 = GetFirstWord(a_buff, GlobalStringFrom);
a4 = GetFirstWord(a_buff, GlobalStringFrom);
// преобразуем первое слово в номер вершины
int NomVertex = System.Convert.ToInt32(a1);
// заменяем точки в представлении числа с плавающей точкой,
// на запятые, чтобы правильно выполнилась функция
// преобразования строки в дробное число
a2 = a2.Replace('.', ',');
a3 = a3.Replace('.', ',');
a4 = a4.Replace('.', ',');
// записываем значение вершины
limbs[limb_].t_vert[0, NomVertex] = (float) System.Convert.ToDouble(a2); // x
limbs[limb_].t_vert[1, NomVertex] = (float) System.Convert.ToDouble(a3); // y
limbs[limb_].t_vert[2, NomVertex] = (float) System.Convert.ToDouble(a4); // z
continue;
}
// привязка текстурных координат к полигонам
case "*MESH_TFACE":
{
// подобъект создан в памяти
if (limb_ == -1)
return -2;
if (limbs[limb_] == null)
return -3;
// временные переменные
string a1 = "", a2 = "", a3 = "", a4 = "";
// получаем все слова в строке
a1 = GetFirstWord(a_buff, GlobalStringFrom);
a2 = GetFirstWord(a_buff, GlobalStringFrom);
a3 = GetFirstWord(a_buff, GlobalStringFrom);
a4 = GetFirstWord(a_buff, GlobalStringFrom);
// преобразуем первое слово в номер полигона
int NomFace = System.Convert.ToInt32(a1);
// записываем номера вершин, которые описывают полигон
limbs[limb_].t_face[0, NomFace] = System.Convert.ToInt32(a2);
limbs[limb_].t_face[1, NomFace] = System.Convert.ToInt32(a3);
limbs[limb_].t_face[2, NomFace] = System.Convert.ToInt32(a4);
continue;
}
}
}
}
// пересохраняем количество полигонов
count_limbs = limb_;
// получаем ID для создаваемого дисплейного списка
int nom_l = Gl.glGenLists(1);
thisList = nom_l;
// генерируем новый дисплейный список
Gl.glNewList(nom_l, Gl.GL_COMPILE);
// отрисовываем геометрию
CreateList();
// завершаем дисплейный список
Gl.glEndList();
// загрузка завершена
isLoad = true ;
return 0;
}
Как было видно из кода, последний этап - это генерация дисплейного списка. Мы сгенерировали его номер, затем начали создание дисплейного списка и вызвали функцию CreateList();. В данной функции производилась визуализация полученной геометрии 3D-модели с учетом установленных текстур, текстурных координат.
Все довольно просто – мы в цикле отрисовываем массивы вершин, перебирая массив подобъектов, затем массив полигонов. В случае необходимости мы включаем режим текстурирования и указываем текстурные координаты.