unit l_model;

interface

uses
 Windows, OpenGL, err,
 l_math, l_gl;

type
 //-- Ключевой кадр анимации --//
 TFrame = record
  Pos  : TVector;    // Позиция кости
  Rot  : TQuat;      // Ориентация объекта
 end;

 //-- Видимый объект (меш) --//
 TMesh = object
   V_Offset  : integer;            // Смещение индекса вершин
   F_Offset  : integer;            // Смещение индекса вершин
   V_Count   : integer;            // Кол-во вершин
   F_Count   : integer;            // Кол-во граней
   Color     : TRGB;
   Vertex    : array of TVector;   // Вершины
   Normal    : array of TVector;   // Нормали
   Face      : array of TFace;     // Грани
   Frame     : array of TFrame;    // Кадры анимации
   Matrix    : array of TMatrix4;  // Расчитанные кадры
   procedure Init;
   procedure Clear;
 end;

 //-- Модель --//
 TModel = object
   Loaded   : boolean;
   MaxTime  : integer;  // Максимальное время анимации
   CurTime  : integer;  // Текущее время анимации
   CurFrame : integer;
   Meshes   : integer;
   Frames   : integer;
   V_Size   : integer;
   F_Size   : integer;
   Radius   : single;
   Mesh     : array of TMesh;
   v, n     : array of TVector;
   f        : array of TFace;
   c        : array of array [0..2] of single;
   procedure Init;
   procedure Clear;
   function LoadFromFile(const FileName: string): boolean;
   procedure Render;
 end;

var
 spider_xmd : TModel; 
 
implementation

uses
 l_game;

procedure TMesh.Init;
var
 i, j   : integer;
 n      : TVector;
 Frames : integer;
begin
// Расчёт нормалей (они в массиве вершин)
SetLength(Normal, V_Count);
FillChar(Normal[0], V_Count*SizeOf(TVector), 0);
for i := 0 to F_Count - 1 do
 begin
 n := P_Normal(Vertex[Face[i][0]],
               Vertex[Face[i][1]],
               Vertex[Face[i][2]]);
 for j := 0 to 2 do
  Normal[Face[i][j]] := V_Add(Normal[Face[i][j]], n);
 end;

for i := 0 to V_Count - 1 do
 Normal[i] := V_Normalize(Normal[i]);

Frames := Length(Frame); 
// Предварительный расчёт матриц поворота-смещения
SetLength(Matrix, Frames);
for i := 0 to Frames - 1 do
 with Frame[i] do
  begin
  Q_Matrix(Rot, Matrix[i]);
  Matrix[i][3][0] := Pos.X;
  Matrix[i][3][1] := Pos.Y;
  Matrix[i][3][2] := Pos.Z;
  end;
end;

procedure TMesh.Clear;
begin
V_Count   := 0;
F_Count   := 0;
Vertex    := nil;
Face      := nil;
Frame     := nil;
end;

procedure TModel.Init;
begin
Loaded := false;
Mesh   := nil;
Meshes := 0;
end;

procedure TModel.Clear;
var
 i : integer;
begin
Loaded := false;
// Чистим засорённую нами видяху ;)
for i := 0 to Meshes - 1 do
 Mesh[i].Clear;
Mesh   := nil;
Meshes := 0;
end;

function TModel.LoadFromFile(const FileName: string): boolean;
var
 xmd     : File of Byte;
 Magic   : array [0..3] of Char;
 i, j, k : integer;
 vo      : integer;
 Center  : TVector;
 dist    : single;
begin
Clear;
Loaded := false;
Result := false;
 try
 AssignFile(xmd, FileName);
 Reset(xmd);
 BlockRead(xmd, Magic, 4);
 if Magic <> 'XMD1' then Exit;
 BlockRead(xmd, Meshes, 1);
 BlockRead(xmd, Frames, 1);
 MaxTime := Frames * 100 div 3;
 SetLength(Mesh, Meshes);
 for i := 0 to Meshes - 1 do
  with Mesh[i] do
   begin
   BlockRead(xmd, V_Count, 4);
   BlockRead(xmd, F_Count, 4);
   BlockRead(xmd, Color, 3);
   SetLength(Vertex, V_Count);
   SetLength(Face, F_Count);
   SetLength(Frame, Frames);
   BlockRead(xmd, Vertex[0], V_Count*SizeOf(TVector));
   BlockRead(xmd, Face[0], F_Count*SizeOf(TFace));
   BlockRead(xmd, Frame[0], Frames*SizeOf(TFrame));
   // Немного уменьшаем модель :)
   for j := 0 to V_Count - 1 do
    with Vertex[j] do
     begin
     X := X * 0.1;
     Y := Y * 0.1;
     Z := Z * 0.1;
     end;
   for j := 0 to Frames - 1 do
    with Frame[j].Pos do
     begin
     X := X * 0.1;
     Y := Y * 0.1;
     Z := Z * 0.1;
     end;
   Init;
   end;
 CloseFile(xmd);

 // Структура файла модели позволяет хранить в качестве анимации всего
 // (3 + 4)*4 байта на меш. Это экономит его размер.
 // Но при выводе графики приходилось бы отрисовывать каждый меш отдельно
 // попутно изменяя матрицу вида.
 // Следующий кусок кода объединит все меши для каждого кадра
 // Пем самым, при отрисовки нового кадра понадобится всего один
 // вызов отрисовки всей модели, что сэкономит кучу времени, за
 // счёт увеличения памяти под модель...
 // Итак до данной оптимизации моя машина выдавала 21 FPS...
 // После - 30
 
 //Этап 1: склейка всех индексов граней в один большой массив
 // Индексирование фейсов от кадра к кадру не меняется, поэтому
 // достаточно одного массива на всю анимацию

 F_Size := 0;
 V_Size := 0;
 for i := 0 to Meshes - 1 do
  with Mesh[i] do
   begin
   V_Offset := V_Size;
   F_Offset := F_Size;
   inc(V_Size, V_Count);
   inc(F_Size, F_Count);
   end;

 // подготовка массивов
 SetLength(v, V_Size * Frames);
 SetLength(n, V_Size * Frames);
 SetLength(c, V_Size);
 SetLength(f, F_Size);
 // цвета
 for j := 0 to Meshes - 1 do
  with Mesh[j] do
   for k := 0 to V_Count - 1 do
    begin
    c[V_Offset + k][0] := Color.R/255;
    c[V_Offset + k][1] := Color.G/255;
    c[V_Offset + k][2] := Color.B/255;
    end;

 // склейка нормалей и вершин мешей в массивы
 Center := Vector(0, 0, 0); // центр модели
 Radius := 0;               // радиус ограничивающей сферы
 for i := 0 to Frames - 1 do
  for j := 0 to Meshes - 1 do
   with Mesh[j] do
    begin
    vo := i * V_Size + V_Offset;
    for k := 0 to V_Count - 1 do
     begin
     v[vo + k] := M_Mult(Matrix[i], Vertex[k]);
     with M_Mult(Matrix[i], Normal[k]) do
      begin
      n[vo + k].X := (X - Matrix[i][3][0]);
      n[vo + k].Y := (Y - Matrix[i][3][1]);
      n[vo + k].Z := (Z - Matrix[i][3][2]);
      end;
     // Расчёт радиуса ограничивающей сферы
     dist := V_Distq(v[vo + k], Center); // квадрат расстояния от вершины до центра
     if dist > Radius then
      Radius := dist;
     end;
    end;

 // Вычисляем радиус (из квадрата радиуса)
 Radius := sqrt(Radius);

 // перестройка индексов
 for i := 0 to Meshes - 1 do
  with Mesh[i] do
   for j := 0 to F_Count - 1 do
    for k := 0 to 2 do
     f[F_Offset + j, k] := Face[j][k] + V_Offset;

 Loaded := true;
 Result := true;
 except
 end;
end;

procedure TModel.Render;
begin
if not Loaded then Exit;
glColorPointer(3, GL_FLOAT, 0, @c[0]);
glNormalPointer(GL_FLOAT, 0, @n[CurFrame * V_Size]);
glVertexPointer(3, GL_FLOAT, 0, @v[CurFrame * V_Size]);
glDrawElements(GL_TRIANGLES, F_Size*3, GL_UNSIGNED_SHORT, @f[0]);
end;

end.
