unit ant_mesh;

//
//  Loads *.antmesh files and builds vertex buffers
//
//  Format of .antmesh is simple:
//
//    ".antmesh"                  8 bytes
//    Flags: UInt32               reserved 4 bytes for flags, should be 0
//    ICount: UInt32              number of indices, 4 bytes
//    VCount: UInt32              number of vertices, 4 bytes
//    Indices[ICount]: UInt16     ICount indices, each index is UInt16
//    Vertices[VCount]: TVec3f    VCount vertex coordinates, 3 32-floats each
//    Normals[VCount]: TVec3f     VCount normal coordinates, 3 32-floats each
//    TexCoords[VCount]: TVec2f   VCount texture coordinates, 2 32-floats each
//    ... extensions              unknown extensions
//
//  The TAntBuffer supports up to High(UInt16) vertices.
//

{$MODE FPC}
{$MODESWITCH DEFAULTPARAMETERS}
{$MODESWITCH OUT}
{$MODESWITCH RESULT}

interface

uses
  dstream,
  dstreamfile,
  dvector2f,
  dvector3f,
  sts_vertex_cache_optimizer,
  deng,
  DDrawVertexBuffer;

type
PAntBuffer = ^TAntBuffer;
PAntMesh = ^TAntMesh;

TAntMeshHeader = packed record
  Magic: array[0 .. 8 - 1] of AnsiChar;
  Flags: UInt32;
  ICount: UInt32;
  VCount: UInt32;
end;

TAntBuffer = object
private
  FVertexBuffer: TVertexBuffer;
  FIndexBuffer: TIndexBuffer;
  FTotalVCount: UInt32;
  FTotalICount: UInt32;
  FIndices: array of UInt16;
  FVertices: array of TVec3f;
  FTexCoords: array of TVec2f;
  FNormals: array of TVec3f;
  procedure Refresh;
public
  procedure Init;
  procedure Done;

  procedure Bind;
  procedure Unbind;

  property TotalVCount: UInt32 read FTotalVCount;
  property TotalICount: UInt32 read FTotalICount;
end;

TAntMesh = object
private
  FBuffer: PAntBuffer;
  FIndexFirst: UInt32; // first face index inside the buffer
  FIndexCount: UInt32; // number of faces indices
  FVertexFirst: UInt32; // first vertex index inside the buffer
  FVertexCount: UInt32; // number of vertices
  FError: AnsiString;
public
  procedure Init(Buffer: PAntBuffer; const FileName: AnsiString);
  procedure Done;

  procedure Render;

  function Loaded: Boolean;

  property Error: AnsiString read FError;
end;

implementation

procedure TAntBuffer.Refresh;
begin
  FIndexBuffer.ReInit(FIndices);
  FVertexBuffer.SetVertices(FVertices);
  FVertexBuffer.SetNormals(FNormals);
  FVertexBuffer.SetTexCoords(FTexCoords);
  FVertexBuffer.UploadToGPUStorage;
end;

procedure TAntBuffer.Init;
begin
  FTotalVCount := 0;
  FTotalICount := 0;
  SetLength(FIndices, 0);
  // Anyway we are supporting maximum, no need for incremental allocations
  SetLength(FVertices, High(UInt16));
  SetLength(FTexCoords, High(UInt16));
  SetLength(FNormals, High(UInt16));
  FVertexBuffer.Init;
  FIndexBuffer.Init;
end;

procedure TAntBuffer.Done;
begin
  FIndexBuffer.Done;
  FVertexBuffer.Done;
  FTotalVCount := 0;
  FTotalICount := 0;
  SetLength(FIndices, 0);
  SetLength(FVertices, 0);
  SetLength(FTexCoords, 0);
  SetLength(FNormals, 0);
end;

procedure TAntBuffer.Bind;
begin
  FVertexBuffer.Enable;
  // FIndexBuffer.Enable;
end;

procedure TAntBuffer.Unbind;
begin
  // FIndexBuffer.Disable;
  FVertexBuffer.Disable;
end;

procedure TAntMesh.Init(Buffer: PAntBuffer; const FileName: AnsiString);
label
  LError;
var
  Stream: TStreamFile;
  Header: TAntMeshHeader;
  I, IEnd: PUInt16;
  V: PVec3f;
  T: PVec2f;
  N: PVec3f;
begin
  FIndexFirst := 0;
  FIndexCount := 0;
  FVertexFirst := 0;
  FVertexCount := 0;
  FBuffer := Buffer;
  FError := '';

  Stream.Init(FileName);
  if not Stream.IsReadable then begin
    FError := 'FAILED opening ' + FileName;
    Log(FError);
    Stream.Done;
    Exit;
  end;

  if not Stream.ReadBuffer(Header, SizeOf(TAntMeshHeader)) then begin
    FError := 'FAILED reading ' + FileName;
    goto LError;
  end;

  if CompareByte(Header.Magic[0], '.antmesh', 8) <> 0 then begin
    FError := 'FAILED checking magic of ' + FileName;
    goto LError;
  end;

  if CompareByte(Header.Magic[0], '.antmesh', 8) <> 0 then begin
    FError := 'FAILED checking magic of ' + FileName;
    goto LError;
  end;

  if Header.Flags <> 0 then begin
    FError := 'FAILED checking flags of ' + FileName;
    goto LError;
  end;

  if Header.VCount > High(UInt16) - Buffer^.TotalVCount then begin
    FError := 'FAILED ' + FileName + ': buffer overflow';
    goto LError;
  end;

  SetLength(Buffer^.FIndices, Length(Buffer^.FIndices) + Header.ICount);
  I := @Buffer^.FIndices[Length(Buffer^.FIndices) - Header.ICount];
  V := @Buffer^.FVertices[Buffer^.TotalVCount];
  T := @Buffer^.FTexCoords[Buffer^.TotalVCount];
  N := @Buffer^.FNormals[Buffer^.TotalVCount];

  if not Stream.ReadBuffer(I^, Header.ICount * SizeOf(UInt16))
  or not Stream.ReadBuffer(V^, Header.VCount * SizeOf(TVec3f))
  or not Stream.ReadBuffer(N^, Header.VCount * SizeOf(TVec3f))
  or not Stream.ReadBuffer(T^, Header.VCount * SizeOf(TVec2f))
  then begin
    FError := 'FAILED reading ' + FileName;
    goto LError;
  end;

  FIndexFirst := Buffer^.FTotalVCount;
  FIndexCount := Header.ICount;
  FVertexFirst := Buffer^.FTotalVCount;
  FVertexCount := Header.VCount;

  Buffer^.FTotalICount := Length(Buffer^.FIndices);
  Buffer^.FTotalVCount := Buffer^.FTotalVCount + Header.VCount;

  // now recompute all indices
  IEnd := PUInt16(@Buffer^.FIndices[0]) + Buffer^.FTotalICount;
  while I < IEnd do begin
    I^ := I^ + FVertexFirst;
    Inc(I);
  end;

  Buffer^.Refresh;

LError:
  if FError <> '' then begin
    Log(FError);
  end else
    Log('LOADED ' + Filename);
  Stream.Done;
end;

procedure TAntMesh.Done;
begin
  // removing mesh data from the buffer is not simple task
  // anyway we are not gonna free meshes in runtime
end;

procedure TAntMesh.Render;
begin
  FBuffer^.FVertexBuffer.DrawTriangles(FBuffer^.FIndexBuffer, FIndexFirst, FIndexCount);
end;

function TAntMesh.Loaded: Boolean;
begin
  Exit(FError  = '');
end;

end.
