unit MX3;
/////////////////////////////////////
// Skinned Mesh Model Animetion unit
// by XProger
/////////////////////////////////////
interface

uses
  Windows, OpenGL, eXgine, l_math;

type
// кадр анимации
  TFrame = record
    Pos : TVector;  // позиция
    Rot : TQuat;    // поворот
  end;

// Вес вершины
  TWeight = record
    ID    : Integer; // Номер кости
    Value : Single;  // Значение
  end;

// Кость
  TBone = record
    Matrix : TMatrix;
    Frame  : array of TFrame;
  end;
  
// Skinned Mesh модель
  TModel = class
    constructor Create(const Name: string);
   private
    Texture  : TTexture;
    FPS      : Integer;   
    K_Count  : Integer;
    B_Count  : Integer;
    V_Count  : Integer;
    F_Count  : Integer;
    T_Count  : Integer;
    Vertex   : array of TVector;
    Face     : array of TFace;
    TexCoord : array of TVector2D;
    TexFace  : array of TFace;
    Weight   : array of array of TWeight; // веса вершин
    RVertex  : array of TVector; // вершины после применения костей
    Bone     : array of TBone;   // список костей и их анимации
   public
    procedure Render;
  end;

implementation

procedure V_Add(var v1: TVector; v2: TVector);
begin
  v1.X := v1.X + v2.X;
  v1.Y := v1.Y + v2.Y;
  v1.Z := v1.Z + v2.Z;
end;

function M_Mult(var m: TMatrix; v: TVector): TVector; overload;
begin
  Result.X := m[0, 0] * v.X + m[1, 0] * v.Y + m[2, 0] * v.Z + m[3, 0];
  Result.Y := m[0, 1] * v.X + m[1, 1] * v.Y + m[2, 1] * v.Z + m[3, 1];
  Result.Z := m[0, 2] * v.X + m[1, 2] * v.Y + m[2, 2] * v.Z + m[3, 2];
end;

procedure M_Mult(var Result, m: TMatrix; x: Single); overload;
var
  i, j : Integer;
begin
  for i := 0 to 3 do
    for j := 0 to 3 do
      Result[i, j] := M[i, j] * x;
end;

constructor TModel.Create(const Name: string);
var
  F       : File;
  i       : Integer;
  W_Count : Integer;
begin
  AssignFile(F, Name + '.mx3');
  Reset(F, 1);
// Чтение заголовка
  BlockRead(F, K_Count, SizeOf(K_Count));
  BlockRead(F, FPS, SizeOf(FPS));
  BlockRead(F, B_Count, SizeOf(B_Count));
  BlockRead(F, V_Count, SizeOf(V_Count));
  BlockRead(F, F_Count, SizeOf(F_Count));
  BlockRead(F, T_Count, SizeOf(T_Count));
// Геометрия
  SetLength(Vertex, V_Count);
  SetLength(Face, F_Count);
  SetLength(TexCoord, T_Count);
  SetLength(TexFace, F_Count);
  BlockRead(F, Vertex[0], V_Count * SizeOf(TVector));
  BlockRead(F, Face[0], F_Count * SizeOf(TFace));
  BlockRead(F, TexCoord[0], T_Count * SizeOf(TVector2D));
  BlockRead(F, TexFace[0], F_Count * SizeOf(TFace));
// веса вершин
  SetLength(Weight, V_Count);
  for i := 0 to V_Count - 1 do
  begin
    BlockRead(F, W_Count, SizeOf(W_Count));
    SetLength(Weight[i], W_Count);
    BlockRead(F, Weight[i][0], W_Count * SizeOf(TWeight));
  end;
// Загрузка анимации костей
  SetLength(Bone, B_Count);
  for i := 0 to B_Count - 1 do
  begin
    SetLength(Bone[i].Frame, K_Count);
    BlockRead(F, Bone[i].Frame[0], K_Count * SizeOf(TFrame));
  end;
  CloseFile(F);
// Выделяем память под вершины - результат применения костей
  SetLength(RVertex, V_Count);
// Загрузка текстуры (eXgine функция)
  Texture := tex.Load(PChar(Name + '.jpg'));
end;

procedure TModel.Render;
const
  null_vec : TVector = (X: 0; Y: 0; Z: 0);
var
  i, j     : Integer;
  lf, nf   : Integer;
  dt, t    : Single;
  p        : TVector;
  r        : TQuat;
  W_Matrix : TMatrix;
begin
  dt := 1000 div FPS;                       // длительность одного кадра
  lf := trunc(GetTickCount/dt) mod K_Count; // вычисляем предыдущий кадр
  nf := (lf + 1) mod K_Count;               // и следующий
  t  := frac(GetTickCount/dt);              // временной коэффициент

// расчёт интерполированных матриц костей
  for i := 0 to B_Count - 1 do // цикл по всем костям
    with Bone[i] do
    begin
    // линейная интерполяция кватерниона и позиции
      r := Q_Lerp(Frame[lf].Rot, Frame[nf].Rot, t);
      p := V_Lerp(Frame[lf].Pos, Frame[nf].Pos, t);
    // расчёт матрицы трансформации для объекта
      Q_Matrix(r, Matrix);    // перевод кватерниона в матрицу
      Matrix[3][0] := p.X;    // дописываем позицию в последнюю строку
      Matrix[3][1] := p.Y;
      Matrix[3][2] := p.Z;
    end;

// Расчёт положения вершин в соответствии с текущим состоянием костей
  for i := 0 to V_Count - 1 do
  begin
    RVertex[i] := null_vec;
    for j := 0 to Length(Weight[i]) - 1 do
      with Weight[i][j] do
      begin
        M_Mult(W_Matrix, Bone[ID].Matrix, Value); // Получение "взвешенной" матрицы
        V_Add(RVertex[i], M_Mult(W_Matrix, Vertex[i]));
      end;
  end;

// отрисовка
  tex.Enable(Texture); // Включение текстуры (eXgine функция)
  glBegin(GL_TRIANGLES);
  for i := 0 to F_Count - 1 do
  begin
    glTexCoord2fv(@TexCoord[TexFace[i][0]]); glVertex3fv(@RVertex[Face[i][0]]);
    glTexCoord2fv(@TexCoord[TexFace[i][1]]); glVertex3fv(@RVertex[Face[i][1]]);
    glTexCoord2fv(@TexCoord[TexFace[i][2]]); glVertex3fv(@RVertex[Face[i][2]]);
  end;
  glEnd;
end;

end.
