Теперь вы можете подготавливать модели с помощью любого профессионального редактора - формат dxf является открытым и стандартным.
Замечание
Саму поверхность не обязательно строить по отдельным треугольникам, главное, чтобы используемый редактор мог разбивать поверхность на такие треугольники при экспорте в dxf-формат.
Я соглашусь с вами, что некоторые примеры, хоть и являются очень интересными, годятся только для того, чтобы пару раз ими полюбоваться. Этот же пример является действительно полезным и достоин подробного разбора.
Программа является универсальной, в частности, в ней не ограничивается число точек поверхности. В таких случаях можно использовать динамические массивы или, как в этом Примере, списки (в терминах Delphi).
Списки введены в программе для хранения точек модели и нормалей к каждому треугольнику:
Model, Normals : TList;
Следующий тип введен для хранения координат отдельной точки:
type
Vector = record
х, у, z : GLfloat;
end;
Одной из ключевых процедур примера является процедура чтения данных из файла формата dxf:
I$WARNINGS OFF} // отключить предупреждения компилятора о возможной
// неинициализации переменных
procedure TfrmGL.LoadDXF (st : String);
var
f : TextFile;
wrkString : String;
group, err : GLint;
x1, x2, y1, y2, z1, z2, x3, y3, z3 : GLfloat;
// вспомогательная процедура добавления вектора в список
Model procedure AddToList (х, у, z : GLfloat);
var
wrkVector : Vector; // рабочая переменная, вектор
pwrkVector : PVector; // указатель на вектор begin
wrkVector.х := х; // заполняем поля вектора
wrkVector.у := у; wrkVector.z := z;
New (pwrkVector); // выделение памяти для нового элемента списка
pwrkVectorA := wrkVector; // задаем указатель
Model.Add (pwrkVector); // собственно добавление вектора в список
end;
begin
AssignFile(f,st); // открываем файл
Reset(f);
repeat // пропускаем файл до секции объектов "ENTITIES"
ReadLn(f, wrkString);
until (wrkString = 'ENTITIES') or eof(f);
While not eof (f) do begin
ReadLn (f, group); // маркер
ReadLn (f, wrkString); // идентификатор либо координата
case group of
0: begin // начался следующий объект
AddToList (х3, у3, z3); // добавляем в список треугольник
AddToList (x2, у2, 2.2); AddToList (x1, y1, z1);
end;
10: val(wrkString, x1, err) // считываем вершины треугольника
20: val(wrkString, y1, err) 30: val(wrkString, z1, err)
11: val(wrkString, x2, err) 21: val(wrkString, y2, err)
31: val(wrkString, z2, err) 12: val(wrkString, x3, err)
22: val(wrkString, y3, err) 32: val(wrkString, z3, err)
end;
end;
CloseFile(f);
end;
{$WARNINGS ON}
После того как считаны координаты вершин треугольников, образующих поверхность, необходимо рассчитать векторы нормалей к каждому треугольнику:
($HINTS OFF) // отключаем замечания компилятора о неиспользовании
// переменной pwrkVector procedure TfrmGL.CalcNormals;
var
i : Integer;
wrk1, vx1, vy1, vz1, vx2, vy2, vz2 : GLfloat;
nx, ny, nz : GLfloat;
wrkVector : Vector;
pwrkVector : ^Vector;
wrkVector1, wrkVector2, wrkVectorS : Vector;
pwrkVector1, pwrkVector2, pwrkVector3 : ^Vector; begin
New (pwrkVector1); // выделение памяти под указатели
New (pwrkVector2);
New (pwrkVector3);
For i := 0 to round (Model.Count / 3) - 1 do begin
pwrkVector1 := Model [i * 3]; // считываем по три вершины из списка
wrkVector1 := pwrkVector1^; // модели
pwrkVector2 := Model [i * 3 -t- 1] ;
wrkVector2 := pwrkVector2^;
pwrkVector3 := Model [i * 3 + 2];
wrkVector3 := pwrkVectorS";
// приращения координат вершин по осям
vx1 := wrkVector1.х - wrkVector2.x;
vy1 := wrkVector1.y - wrkVector2.у;
vz1 := wrkVectorl.z - wrkVector2.z; 1
vx2 := wrkVector2.x - wrkVector3.x; 1
vy2 := wrkVector2.y - wrkVector3.y;
vz2 := wrkVector2.z - wrkVector3.z;
// вектор-перпендикуляр к центру треугольника
nx := vy1 * vz2 - vz1 * vy2;
ny := vz1 * vx2 - vx1 * vz2;
nz := vx1 * vy2 - vy1 * vx2; // получаем унитарный вектор единичной длины
wrk1 := sqrt (nx * nx + ny * ny + nz * nz);
If wrk1 = 0 then wrk1 := 1; // для предотвращения деления на ноль
wrkVector.x := nx / wrk1;
wrkVector.y := ny / wrk1;
wrkVector.z := nz / wrk1;
New (pwrkVector); // указатель на очередную нормаль
pwrkVector4 := wrkVector;
Normals.Add (pwrkVector); // добавляем нормаль в список Normals
end;
end;
{$HINTS ON}
Собственно поверхность описывается в дисплейном списке:
Model := TList.Create; // создаем список модели
Normals := TList.Create; // создаем список нормалей
LoadDxf ('Dolphin.dxf'); // считываем модель
CalcNormals; // расчет нормалей
glNewList (SURFACE, GL_COMPILE); // поверхность хранится в списке
For i := 0 to round (Model.Count / 3) - 1 do begin // по три вершины
glBegin(GL_TRIANGLES);
glNormal3fv(Normals.Items [i]); // задаем текущую нормаль
glvertex3fv(Model.Items [i * 3]); // вершины треугольника
glvertex3fv(Model.Items [i * 3 + 1]); glvertex3fv(Model.Items [i * 3 + 2]);
glEnd;
end;
glEndList;
Model.Free; // списки больше не нужны, удаляем их
Normals.Free;
Замечание
Я не мог привести этот пример в предыдущей главе, поскольку в этом примере необходимо задать модель освещения с расчетом обеих сторон многоугольников у нас нет гарантии, что все треугольники поверхности повернуты к наблюдателю лицевой стороной
В программе все треугольники раскрашены одним цветом, можно добавить еще один список, аналогичный списку нормалей, но хранящий данные о цвете каждого отдельного треугольника.
Замечание
Обратите внимание, что в этом примере я меняю скорость поворота модели в зависимости от того, как быстро пользователь перемещает указатель при нажатой кнопке мыши.