//
// This unit is part of the GLScene Engine, http://glscene.org
//

unit FileGL2;

(*
  Ghoul2 (GLM/GLA) file format loading structures
  Note: Also referred to as MDX (MDXM/MDXA) format in C source.
*)

interface

uses
  System.Classes,
  System.SysUtils,

  GLVectorTypes,
  GLVectorGeometry;

type
  TGLMHeader = record
    fileID           : array[0..3] of char;
    version          : integer;
    strFile,
    animName         : array[0..63] of char;
    animIndex,
    numBones,
    numLODs,
    ofsLODs,
    numSurfaces,
    ofsSurfHierarchy,
    ofsEnd           : integer;
  end;

  TGLMSurfaceHeirachyOffsets = array of integer;

  TGLMSurfaceHeirachy = record
    name         : array[0..63] of Char;
    flags        : LongWord;
    shader       : array[0..63] of Char;
    shaderIndex,
    parentIndex,
    numChildren  : integer;
    childIndices : array of integer;
  end;

  TGLMSurfaceHeader = record
    ident,
    thisSurfaceIndex,
    ofsHeader,
    numVerts,
    ofsVerts,
    numTriangles,
    ofsTriangles,
    numBoneReferences,
    ofsBoneReferences,
    ofsEnd            : integer;
  end;

  TGLMTriangle = record
    indices: array [0 .. 2] of integer;
  end;

  TGLMVertex = record
    normal, vertex: TVector3f;
    uiNumWeightsAndBoneIndices: Cardinal; // packed int
    BoneWeightings: array [0 .. 3] of Byte;
  end;

  TGLMSurface = record
    SurfaceHeader: TGLMSurfaceHeader;
    Triangles: array of TGLMTriangle;
    Vertices: array of TGLMVertex;
    TexCoords: array of TVector2f;
    BoneReferences: array of integer;
  end;

  TGLMLODInfo = record
    ofsEnd: integer;
  end;

  TGLMLODSurfaceOffsets = array of integer;

  TGLMLODs = record
    LODInfo: TGLMLODInfo;
    LODSurfaceOffsets: TGLMLODSurfaceOffsets;
    Surfaces: array of TGLMSurface;
  end;

  TGLAHeader = record
    fileID: array [0 .. 3] of char;
    version: integer;
    name: array [0 .. 63] of char;
    fScale: single;
    numFrames, ofsFrames, numBones, ofsCompBonePool, ofsSkel, ofsEnd: integer;
  end;

  TGLABone = array [0 .. 2] of TVector4f;
  TGLACompQuatBone = array [0 .. 6] of Word; { 14 bytes }

  TGLASkeletonOffsets = array of integer;

  TGLASkeleton = record
    name: array [0 .. 63] of char;
    flags: LongWord;
    parent: integer;
    BasePoseMat, BasePoseMatInv: TGLABone;
    numChildren: integer;
    children: array of integer;
  end;

  // Ghoul2 Model structure
  TFileGLM = class
  public
    ModelHeader: TGLMHeader;
    SurfaceHeirachyOffsets: TGLMSurfaceHeirachyOffsets;
    SurfaceHeirachy: array of TGLMSurfaceHeirachy;
    LODs: array of TGLMLODs;
    procedure LoadFromStream(aStream: TStream);
  end;

  // Ghoul2 Animation structure
  TFileGLA = class
  public
    AnimHeader: TGLAHeader;
    SkeletonOffsets: TGLASkeletonOffsets;
    Skeleton: array of TGLASkeleton;
    BoneIndices: array of integer;
    CompBonePool: array of TGLACompQuatBone;
    function GetCompressedMatrix(Frame, Bone: integer): TGLACompQuatBone;
    function GetUnCompressedMatrix(Frame, Bone: integer): TMatrix;
    procedure LoadFromStream(aStream: TStream);
  end;

function G2_GetVertWeights(const vert: TGLMVertex): integer;
function G2_GetVertBoneIndex(const vert: TGLMVertex;
  iWeightNum: integer): integer;
function G2_GetVertBoneWeight(const vert: TGLMVertex; iWeightNum: Cardinal;
  var fTotalWeight: single; const iNumWeights: Cardinal): single;

procedure MC_UnCompressQuat(var mat: TMatrix; const comp: TGLACompQuatBone);

// ------------------------------------------------------------------
implementation
// ------------------------------------------------------------------

// ------------------
// ------------------ Misc routines ------------------
// ------------------

// Adapted from mdx_format.h
// static inline int G2_GetVertWeights( const mdxmVertex_t *pVert )
// static inline int G2_GetVertBoneIndex( const mdxmVertex_t *pVert, const int iWeightNum)
// static inline float G2_GetVertBoneWeight( const mdxmVertex_t *pVert, const int iWeightNum, float &fTotalWeight, int iNumWeights )

function G2_GetVertWeights(const vert: TGLMVertex): integer;
begin
  // Get number of bones per vertex (0..3)+1 = (1..4)
  result := (vert.uiNumWeightsAndBoneIndices shr 30) + 1;
end;

function G2_GetVertBoneIndex(const vert: TGLMVertex;
  iWeightNum: integer): integer;
begin
  // Extract the bone reference array index, a 5-bit integer
  result := (vert.uiNumWeightsAndBoneIndices shr (5 * iWeightNum)) and 31;
end;

function G2_GetVertBoneWeight(const vert: TGLMVertex; iWeightNum: Cardinal;
  var fTotalWeight: single; const iNumWeights: Cardinal): single;
var
  fBoneWeight: single;
  iTemp: Cardinal;
begin
  if (iWeightNum = iNumWeights - 1) then
  begin
    // No need to calculate final weight value, return the
    // weight left over out of 1
    fBoneWeight := 1 - fTotalWeight;
  end
  else
  begin
    // Get the initial 8-bit bone weight
    iTemp := vert.BoneWeightings[iWeightNum];
    // Get the 2-bit overflow and 'or' it to the front of the
    // weight to get 10-bit integer weight (0..1023)
    iTemp := iTemp or
      ((vert.uiNumWeightsAndBoneIndices shr (12 + (iWeightNum * 2))) and $300);
    // Convert to floating point weight (0..1)
    fBoneWeight := iTemp / 1023;
    // Accumulate total weight
    fTotalWeight := fTotalWeight + fBoneWeight;
  end;
  result := fBoneWeight;
end;

// Adapted from matcomp.c
// void MC_UnCompressQuat(float mat[3][4],const unsigned char * comp)

procedure MC_UnCompressQuat(var mat: TMatrix; const comp: TGLACompQuatBone);
begin
  mat := QuaternionToMatrix(QuaternionMake([comp[1] - 32726, comp[2] - 32726,
    comp[3] - 32726], comp[0] - 32726));
  mat.V[3] := VectorMake(comp[4] / 64 - 512, comp[5] / 64 - 512,
    comp[6] / 64 - 512, 1);
end;


// ------------------
// ------------------ TFileGLM ------------------
// ------------------

procedure TFileGLM.LoadFromStream(aStream: TStream);
var
  idstr: array [0 .. 3] of char;
  i, j: integer;
  ofs, LODofs: int64;
begin
  aStream.Read(idstr, sizeof(idstr));
  aStream.Position := 0;

  if not(idstr = '2LGM') then
  begin
    raise Exception.Create(Format('Unknown or incorrect identity tag: [%s]',
      [idstr]));
    exit;
  end;

  aStream.Read(ModelHeader,SizeOf(ModelHeader));

  if ModelHeader.version <> 6 then
    raise Exception.Create
      (Format('Only GLM (MDXM) version 6 is supported. File is version %d.',
      [ModelHeader.version]));

  SetLength(SurfaceHeirachyOffsets, ModelHeader.numSurfaces);
  aStream.Read(SurfaceHeirachyOffsets[0], sizeof(integer) *
    ModelHeader.numSurfaces);

  SetLength(SurfaceHeirachy, ModelHeader.numSurfaces);
  for i := 0 to ModelHeader.numSurfaces - 1 do
    with SurfaceHeirachy[i] do
    begin
      aStream.Read(name, Length(name));
      aStream.Read(flags, sizeof(LongWord));
      aStream.Read(shader, Length(shader));
      aStream.Read(shaderIndex, sizeof(integer));
      aStream.Read(parentIndex, sizeof(integer));
      aStream.Read(numChildren, sizeof(integer));
      if numChildren > 0 then
      begin
        SetLength(childIndices, numChildren);
        aStream.Read(childIndices[0], numChildren * sizeof(integer));
      end
      else
        SetLength(childIndices, 0);
    end;

  SetLength(LODs, ModelHeader.numLODs);
  for i := 0 to ModelHeader.numLODs - 1 do
    with LODs[i] do
    begin
      LODofs := aStream.Position;
      aStream.Read(LODInfo, sizeof(LODInfo));
      SetLength(LODSurfaceOffsets, ModelHeader.numSurfaces);
      aStream.Read(LODSurfaceOffsets[0], sizeof(integer) *
        ModelHeader.numSurfaces);
      SetLength(Surfaces, ModelHeader.numSurfaces);
      for j := 0 to ModelHeader.numSurfaces - 1 do
        with Surfaces[j] do
        begin
          ofs := aStream.Position;
          aStream.Read(SurfaceHeader, sizeof(TGLMSurfaceHeader));
          SetLength(Triangles, SurfaceHeader.numTriangles);
          SetLength(Vertices, SurfaceHeader.numVerts);
          SetLength(TexCoords, SurfaceHeader.numVerts);
          SetLength(BoneReferences, SurfaceHeader.numBoneReferences);
          aStream.Position := ofs + SurfaceHeader.ofsTriangles;
          aStream.Read(Triangles[0], SurfaceHeader.numTriangles *
            sizeof(TGLMTriangle));
          aStream.Position := ofs + SurfaceHeader.ofsVerts;
          aStream.Read(Vertices[0], SurfaceHeader.numVerts *
            sizeof(TGLMVertex));
          aStream.Read(TexCoords[0], SurfaceHeader.numVerts *
            sizeof(TVector2f));
          aStream.Position := ofs + SurfaceHeader.ofsBoneReferences;
          aStream.Read(BoneReferences[0], SurfaceHeader.numBoneReferences *
            sizeof(integer));
          aStream.Position := ofs + SurfaceHeader.ofsEnd;
        end;
      aStream.Position := LODofs + LODInfo.ofsEnd;
    end;
end;


// ------------------
// ------------------ TFileGLA ------------------
// ------------------

function TFileGLA.GetCompressedMatrix(Frame, Bone: integer): TGLACompQuatBone;
begin
  result := CompBonePool[BoneIndices[Frame * AnimHeader.numBones + Bone]];
end;

// GetUnCompressedMatrix
//
function TFileGLA.GetUnCompressedMatrix(Frame, Bone: integer): TMatrix;
begin
  MC_UnCompressQuat(result, CompBonePool[BoneIndices[Frame * AnimHeader.numBones
    + Bone]]);
end;

procedure TFileGLA.LoadFromStream(aStream: TStream);
var
  idstr: array [0 .. 3] of char;
  i, temp: integer;
  buf: array of array [0 .. 2] of Byte;
begin
  aStream.Read(idstr, sizeof(idstr));
  aStream.Position := 0;

  if not(idstr = '2LGA') then
  begin
    raise Exception.Create(Format('Unknown or incorrect identity tag: [%s]',
      [idstr]));
    exit;
  end;

  aStream.Read(AnimHeader,SizeOf(AnimHeader));

  if AnimHeader.version <> 6 then
    raise Exception.Create
      (Format('Only GLA (MDXA) version 6 is supported. File is version %d.',
      [AnimHeader.version]));

  SetLength(SkeletonOffsets, AnimHeader.numBones);
  aStream.Read(SkeletonOffsets[0], sizeof(integer) * AnimHeader.numBones);

  SetLength(Skeleton, AnimHeader.numBones);
  for i := 0 to AnimHeader.numBones - 1 do
    with Skeleton[i] do
    begin
      aStream.Read(name, Length(name));
      aStream.Read(flags, sizeof(LongWord));
      aStream.Read(Parent,SizeOf(Integer));
      aStream.Read(BasePoseMat, sizeof(TGLABone));
      aStream.Read(BasePoseMatInv, sizeof(TGLABone));
      aStream.Read(numChildren, sizeof(integer));
      if numChildren > 0 then
      begin
        SetLength(children, numChildren);
        aStream.Read(children[0], numChildren * sizeof(integer));
      end
      else
        SetLength(children, 0);
    end;

  aStream.Position := AnimHeader.ofsFrames;
  SetLength(BoneIndices, AnimHeader.numFrames * AnimHeader.numBones);
  SetLength(buf, AnimHeader.numFrames * AnimHeader.numBones * 3);
  aStream.Read(buf[0], AnimHeader.numFrames * AnimHeader.numBones * 3);
  for i := 0 to AnimHeader.numFrames * AnimHeader.numBones - 1 do
    BoneIndices[i] := (buf[i][2] shl 16) or (buf[i][1] shl 8) or buf[i][0];
  SetLength(buf, 0);

  aStream.Position := AnimHeader.ofsCompBonePool;
  temp := AnimHeader.ofsEnd - AnimHeader.ofsCompBonePool;
  SetLength(CompBonePool, temp div sizeof(TGLACompQuatBone));
  aStream.Read(CompBonePool[0], temp);
end;

end.
