Загрузка файла формата GMS в Delphi
Пример загрузки файла GMS находится в папке Ch01. В проекте присутствует два модуля: frmMain.pas и Mesh.pas. Откомпилировав и запустив проект на выполнение вы должны увидеть вращающийся Тор (по-нашему: "Баранка"). Несмотря на то, что объект можно считать стандартным, он был в 3D Studio преобразован в сетку, поэтому в данном случае это именно сетчатый объект. Нажав пункт меню "загрузить", вы можете посмотреть любой объект из папки GMS или загрузить свою сферу, которую сделали сами, если правильно руководствовались моими инструкциями в разделе: Знакомство с утилитой MEGA V1.0. Теперь рассмотрим данный пример подробно. Почти весь код модуля frmMain.pas написан не мной. Он взят из книги "OpenGL графика в проектах Delphi" Михаила Краснова. Этот модуль выполняет инициализацию приложения и циклическую функцию отрисовки окна, поэтому подробно мы его рассматривать не будем. Если код покажется Вам непонятным, значит Вы недостаточно знакомы с OpenGL, в этом случае Вам надлежит обратится к первоисточнику (в смысле - к книге). Код модуля Mesh.pas выполняет загрузку данных из файла и отображение объектов в окне. Рассмотрим его подробнее: Type // Объявление типов данных PGLVertex = ^TGLVertex; // Указатель на вершину TGLVertex = record x,y,z : GLFloat; // Вершина, как три // значения с плавающей точкой end; PGLVector = ^TGLVector; // Указатель на вектор // Вектор, как массив из трех элементов // с плавающей точкой TGLVector = array[0..2] of GLFloat; PGLFace = ^TGLFace; // Указатель на грань // Грань, как массив из трех целочисленных значений TGLFace = array[0..2] of GLInt; // Указатель на массив вершин PGLVertexArray = ^TGLVertexArray; // Массив вершин TGLVertexArray = array[Word] of TGLVertex; // Указатель на массив граней PGLFacesArray = ^TGLFacesArray; // Массив граней TGLFacesArray = array[word] of TGLFace;
Здесь требуется небольшое пояснение. Как вы заметили, грань объявлена, как массив из трех целочисленных чисел. Дело в том, что граней почти всегда больше чем вершин. Поэтому все вершины запоминаются в отдельном массиве, а грань - это три индекса в этом массиве, указывающие на вершины принадлежащие грани. Одна вершина может принадлежать нескольким граням.
Теперь рассмотрим описание объекта сетка: TGLMesh = class // Массив вершин объекта - сетка Vertices : PGLVertexArray; // Массив граней Faces : PGLFacesArray; // Массив фасетных нормалей FasetNormals : PGLVertexArray; // Количество вершин VertexCount : Integer; // Количество граней FacesCount : Integer; // Коэффициент масштабирования fExtent : GLFloat; // Флаг масштабирования Extent : GLBoolean; public // Загрузка procedure LoadFromFile( const FileName : String ); procedure CalcNormals; // Расчет нормалей procedure Draw; // Отрисовка // Уничтожение с очисткой массивов destructor Destroy; override; end;
Здесь пояснений практически не требуется. Можно лишь отметить, что Extent служит для того, чтобы объект загнать в размеры в пределах (-1, 1), я сделал это для того, чтобы объект любого размера не мог вылезти за пределы окна. Вообще говоря, в 3D Studio Max не сложно масштабировать объект так, чтобы координаты вершин попали в интервал (-1, 1), но на этапе создания модели думать об этом совсем не хочется. procedure TGLMesh.LoadFromFile; // Загрузка файла var f : TextFile; S : String; i : Integer; Vertex : TGLVertex; Face : TGLFace; MaxVertex : GLFloat; begin AssignFile(f,FileName); Reset(f); // Пропускаем строки, пока не попадется // 'numverts numfaces' repeat ReadLn(f, S); until (S = 'numverts numfaces') or eof(f); // Читаем количество вершин и граней Readln(f,VertexCount,FacesCount); // Выделяем память для хранения сетки GetMem(Vertices,VertexCount*SizeOf(TGLVertex)); GetMem(Faces,FacesCount*SizeOf(TGLFace)); GetMem(FasetNormals,FacesCount*SizeOf(TGLVector)); ReadLn(f, S); // Пропускаем строку "Mesh vertices" // Считываем вершины for i := 0 to VertexCount - 1 do begin Readln(f,Vertex.x,Vertex.y,Vertex.z); Vertices[i] := Vertex; end; ReadLn(f, S); // Пропускаем строку "end vertices" ReadLn(f, S); // Пропускаем строку "Mesh faces" // Считываем грани for i := 0 to FacesCount - 1 do begin Readln(f,Face[0],Face[1],Face[2]); Face[0] := Face[0] - 1; Face[1] := Face[1] - 1; Face[2] := Face[2] - 1; Faces[i] := Face; end; CloseFile(f); // Рассчитываем масштаб MaxVertex := 0; for i := 0 to VertexCount - 1 do begin MaxVertex := Max(MaxVertex,Vertices[i].x); MaxVertex := Max(MaxVertex,Vertices[i].y); MaxVertex := Max(MaxVertex,Vertices[i].z); end; fExtent := 1/MaxVertex; CalcNormals; end;
Здесь могут быть непонятны следующие моменты: В блоке считывания граней я вычитаю единицу из каждого индекса вершины, считанного из файла. Делается это потому, что в программе индексы нумеруются, начиная с нуля, а в файле GMS - начиная с единицы. Процедура CalcNormals служит для расчета нормалей и взята из книги "OpenGL графика в проектах Delphi" Михаила Краснова. О том, что такое нормали и зачем они нужны я расскажу в разделах "Фасетные нормали" и "Сглаживающие нормали". procedure TGLMesh.Draw; var i : Integer; Face : TGLFace; begin if Extent then glScalef(fExtent,fExtent,fExtent); for i := 0 to FacesCount - 1 do begin glBegin(GL_TRIANGLES); Face := Faces[i]; glNormal3fv(@FasetNormals[i]); glVertex3fv(@Vertices[Face[0]]); glVertex3fv(@Vertices[Face[1]]); glVertex3fv(@Vertices[Face[2]]); glEnd; end; end;
Здесь все понятно. Сначала, если установлен флаг масштабирования, устанавливается масштаб одинаковый по всем осям, затем в цикле рисуются треугольники. Перед началом рисования треугольника объявляется нормаль к нему. В качестве параметров передаются не конкретные значения, а указатели на них. destructor TGLMesh.Destroy; begin FreeMem(Vertices,VertexCount*SizeOf(TGLVertex)); FreeMem(Faces,FacesCount*SizeOf(TGLFace)); FreeMem(FasetNormals,FacesCount*SizeOf(TGLVector)); end;
Здесь тоже все понятно, просто освобождается память, занятая объектом. Вызовы процедур загрузки и отрисовки объекта находятся в модуле frmMain и не представляют ничего интересного.