//
// This unit is part of the GLScene Project, http://glscene.org
//
{: GLFileQ3BSP<p>

    Support-code to load Q3BSP Files into TGLFreeForm-Components in GLScene.<p>
    Note that you must manually add this unit to one of your project's uses
    to enable support for OBJ & OBJF at run-time.<p>

 <b>History : </b><font size=-1><ul>
      <li>19/06/11 - Yar - Fixed problem with image converting in Lazarus (thanks to Johannes Pretorius, Bugtracker ID = 3322324)
      <li>06/06/10 - Yar - Added VectorTypes to uses
      <li>22/01/10 - Yar - Added GLTextureFormat to uses
      <li>31/03/07 - DaStr - Added $I GLScene.inc
      <li>31/01/03 - EG - Materials support
      <li>30/01/03 - EG - Creation
   </ul><p>
}
unit GLFileQ3BSP;

interface

{$I GLScene.inc}

uses
  {$IFDEF UNICODE}AnsiStrings,{$ENDIF}
  Classes,
  GLVectorFileObjects,
  ApplicationFileIO;

type

  // TGLQ3BSPVectorFile
  //
  {: The Q3BSP vector file (Quake III BSP).<p> }
  TGLQ3BSPVectorFile = class(TVectorFile)
  public
    { Public Declarations }
    class function Capabilities: TDataFileCapabilities; override;

    procedure LoadFromStream(aStream: TStream); override;
  end;

var
  // Q3 lightmaps are quite dark, we brighten them a lot by default
  vQ3BSPLightmapGammaCorrection: Single = 2.5;
  vQ3BSPLightmapBrightness: Single = 2; // scaling factor, 1.0 = unchanged
  vGLFileQ3BSPLoadMaterials: boolean = True; // Mrqzzz : Flag to avoid loading materials (useful for IDE Extentions like GlaredX)
  // ------------------------------------------------------------------
  // ------------------------------------------------------------------
  // ------------------------------------------------------------------
implementation
// ------------------------------------------------------------------
// ------------------------------------------------------------------
// ------------------------------------------------------------------

uses

  Q3BSP,
  VectorGeometry,
  VectorTypes,
  VectorLists,
  SysUtils,
  GLBSP,
  GLTexture,
  GLGraphics,
  GLCrossPlatform,
  GLState,
  GLUtils,
  GLMaterial,
  GLTextureFormat
  {$IFDEF FPC}
    ,intfgraphics
  {$ENDIF} ;

// ------------------
// ------------------ TGLSTLVectorFile ------------------
// ------------------

// Capabilities
//

class function TGLQ3BSPVectorFile.Capabilities: TDataFileCapabilities;
begin
  Result := [dfcRead];
end;

// LoadFromStream
//

procedure TGLQ3BSPVectorFile.LoadFromStream(aStream: TStream);

  function LocateTextureFile(const texName: string): string;
  begin
    if FileStreamExists(texName + '.bmp') then
      Result := texName + '.bmp'
    else if FileStreamExists(texName + '.jpg') then
      Result := texName + '.jpg'
    else if FileStreamExists(texName + '.tga') then
      Result := texName + '.tga'
    else
      Result := '';
  end;

  function GetOrAllocateMaterial(const matName: string): string;
  var
    matLib: TGLMaterialLibrary;
    libMat: TGLLibMaterial;
    texName: string;
  begin
    if GetOwner is TGLBaseMesh then
    begin
      // got a linked material library?
      matLib := TGLBaseMesh(GetOwner).MaterialLibrary;
      if Assigned(matLib) then
      begin
        Result := matName;
        libMat := matLib.Materials.GetLibMaterialByName(matName);
        if not Assigned(libMat) then
        begin
          if Pos('.', matName) < 1 then
          begin
            texName := LocateTextureFile(matName);
            if texName = '' then
              texName := LocateTextureFile(Copy(matName, LastDelimiter('\/', matName) + 1, MaxInt));
          end
          else
            texName := matName;
          with matLib.AddTextureMaterial(matName, texName) do
          begin
            Material.Texture.TextureMode := tmModulate;
          end;
        end;
      end
      else
        Result := '';
    end
    else
      Result := '';
  end;

var
  bsp: TQ3BSP;
  mo: TBSPMeshObject;
  fg, lastfg: TFGBSPNode;
  i, j, n, y: Integer;
  facePtr: PBSPFace;
  lightmapLib: TGLMaterialLibrary;
  lightmapBmp: TGLBitmap;
  {$IFDEF FPC}lightbitInfo : TLazIntfImage; {$ENDIF}
  libMat: TGLLibMaterial;
  bspLightMap: PBSPLightmap;
  plane: THmgPlane;
begin
  bsp := TQ3BSP.Create(aStream);
  try
    mo := TBSPMeshObject.CreateOwned(Owner.MeshObjects);

    // import all materials
    if vGLFileQ3BSPLoadMaterials then
    begin
      for i := 0 to High(bsp.Textures) do
      begin
        GetOrAllocateMaterial(Trim(string({$IFDEF UNICODE}AnsiStrings.{$ENDIF}StrPas(bsp.Textures[i].TextureName))));
      end;
    end;

    // import all lightmaps
    lightmapLib := Owner.LightmapLibrary;
    if Assigned(lightmapLib) and vGLFileQ3BSPLoadMaterials then
    begin
      // import lightmaps
      n := bsp.NumOfLightmaps;
      lightmapBmp := TGLBitmap.Create;
      try
        lightmapBmp.PixelFormat := glpf24bit;
        lightmapBmp.Width := 128;
        lightmapBmp.Height := 128;
        for i := 0 to n - 1 do
        begin
          bspLightMap := @bsp.Lightmaps[i];
          // apply brightness correction if ant
          if vQ3BSPLightmapBrightness <> 1 then
            BrightenRGBArray(@bspLightMap.imageBits[0], 128 * 128,
              vQ3BSPLightmapBrightness);
          // apply gamma correction if any
          if vQ3BSPLightmapGammaCorrection <> 1 then
            GammaCorrectRGBArray(@bspLightMap.imageBits[0], 128 * 128,
              vQ3BSPLightmapGammaCorrection);
          // convert RAW RGB to BMP
{$IFDEF FPC}
            lightbitInfo :=  TLazIntfImage.Create(lightmapBmp.RawImage,true);
{$ENDIF}
          for y := 0 to 127 do
{$IFDEF FPC}
            BGR24ToRGB24(@bspLightMap.imageBits[y * 128 * 3],
            lightbitInfo.GetDataLineStart(127 - y), 128);

{$ELSE}
            BGR24ToRGB24(@bspLightMap.imageBits[y * 128 * 3],
            lightmapBmp.ScanLine[127 - y], 128);

{$ENDIF}
          // spawn lightmap
          libMat := lightmapLib.AddTextureMaterial(IntToStr(i), lightmapBmp);
          with libMat.Material.Texture do
          begin
            MinFilter := miLinear;
            TextureWrap := twNone;
            TextureFormat := tfRGB;
          end;
        end;
      finally
        lightmapBmp.Free;
      end;
    end;

    // import all geometry
    mo.Vertices.AdjustCapacityToAtLeast(bsp.NumOfVerts);
    mo.Normals.AdjustCapacityToAtLeast(bsp.NumOfVerts);
    mo.TexCoords.AdjustCapacityToAtLeast(bsp.NumOfVerts);
    for i := 0 to bsp.NumOfVerts - 1 do
    begin
      mo.Vertices.Add(bsp.Vertices[i].Position);
      mo.Normals.Add(bsp.Vertices[i].Normal);
      mo.TexCoords.Add(bsp.Vertices[i].TextureCoord);
      if Assigned(lightMapLib) and vGLFileQ3BSPLoadMaterials then
        mo.LightMapTexCoords.Add(bsp.Vertices[i].LightmapCoord)
    end;
    mo.TexCoords.Scale(AffineVectorMake(1, -1, 0));
    mo.TexCoords.Translate(YVector);
    mo.RenderSort := rsBackToFront;
    // Q3 BSP separates tree nodes from leafy nodes, we don't,
    // so we place nodes first, then all leafs afterwards
    for i := 0 to bsp.NumOfNodes - 1 do
    begin
      fg := TFGBSPNode.CreateOwned(mo.FaceGroups);
      plane := bsp.Planes[bsp.Nodes[i].plane];
      plane := VectorMake(plane.V[0], plane.V[1], plane.V[2], plane.V[3]);
      fg.SplitPlane := plane;
      fg.PositiveSubNodeIndex := bsp.Nodes[i].Children[0];
      if fg.PositiveSubNodeIndex < 0 then
        fg.PositiveSubNodeIndex := bsp.NumOfNodes - fg.PositiveSubNodeIndex - 1;
      Assert(fg.PositiveSubNodeIndex < bsp.NumOfNodes + bsp.NumOfLeaves);
      Assert(fg.PositiveSubNodeIndex > 0);
      fg.NegativeSubNodeIndex := bsp.Nodes[i].Children[1];
      if fg.NegativeSubNodeIndex < 0 then
        fg.NegativeSubNodeIndex := bsp.NumOfNodes - fg.NegativeSubNodeIndex - 1;
      Assert(fg.NegativeSubNodeIndex < bsp.NumOfNodes + bsp.NumOfLeaves);
      Assert(fg.NegativeSubNodeIndex > 0);
    end;
    // import all leaves
    for i := 0 to bsp.NumOfLeaves - 1 do
      TFGBSPNode.CreateOwned(mo.FaceGroups);
    // import all faces into leaves & subnodes
    for i := 0 to bsp.NumOfLeaves - 1 do
    begin
      lastfg := nil;
      for j := 0 to bsp.Leaves[i].NumFaces - 1 do
      begin
        n := bsp.Leaves[i].FirstFace + j;
        if n >= bsp.NumOfFaces then
          Break; // corrupted BSP?
        facePtr := @bsp.Faces[n];
        if facePtr.FaceType = FACE_POLYGON then
        begin
          if lastfg = nil then
            fg := TFGBSPNode(mo.FaceGroups[i + bsp.NumOfNodes])
          else
          begin
            lastfg.PositiveSubNodeIndex := mo.FaceGroups.Count;
            fg := TFGBSPNode.CreateOwned(mo.FaceGroups);
          end;
          // check for BSP corruption
          if Cardinal(facePtr.textureID) <= Cardinal(bsp.NumOfTextures) then
            fg.MaterialName := Trim(string({$IFDEF UNICODE}AnsiStrings.{$ENDIF}StrPas(bsp.Textures[facePtr.textureID].TextureName)));
          if Assigned(lightmapLib) and vGLFileQ3BSPLoadMaterials then
            fg.LightMapIndex := facePtr.lightmapID;
          lastfg := fg;
          // Q3 Polygon Faces are actually fans, but winded the other way around!
          fg.Mode := fgmmTriangleFan;
          fg.VertexIndices.Add(facePtr.startVertIndex);
          fg.VertexIndices.AddSerie(facePtr.startVertIndex + facePtr.numOfVerts - 1,
            -1,
            facePtr.numOfVerts - 1);
          // there are also per-leaf mesh references... dunno what they
          // are for, did not encounter them so far... If you have a BSP
          // that has some, and if you know how to make use of them, shout!

          // Copy the cluster index, used for visibility determination
          fg.Cluster := bsp.Leaves[i].Cluster;
        end;
      end;
    end;

    // Copy the visibility data
    if bsp.VisData.numOfClusters > 0 then
      mo.ClusterVisibility.SetData(
        @bsp.VisData.bitSets[0], bsp.VisData.numOfClusters);
  finally
    bsp.Free;
  end;
  // Some BSP end up with empty nodes/leaves (information unused, incorrept BSP...)
  // This call takes care of cleaning up all the empty nodes
  mo.CleanupUnusedNodes;
end;

// ------------------------------------------------------------------
// ------------------------------------------------------------------
// ------------------------------------------------------------------
initialization
  // ------------------------------------------------------------------
  // ------------------------------------------------------------------
  // ------------------------------------------------------------------

  RegisterVectorFileFormat('q3bsp', 'Quake3 BSP files', TGLQ3BSPVectorFile);

  // registering this extension too might be a little abusive right now...
  RegisterVectorFileFormat('bsp', 'BSP files', TGLQ3BSPVectorFile);
end.
