//
// 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>31/01/03 - EG - Materials support
      <li>30/01/03 - EG - Creation
   </ul><p>
}
unit GLFileQ3BSP;

interface

uses Classes, GLVectorFileObjects, ApplicationFileIO, GLMisc;

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, VectorLists, SysUtils, GLBSP, GLTexture, GLGraphics,
   GLCrossPlatform, GLState, GLUtils;

// ------------------
// ------------------ 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;
   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(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
               for y:=0 to 127 do
                  BGR24ToRGB24(@bspLightMap.imageBits[y*128*3],
                               lightmapBmp.ScanLine[127-y], 128);
               // 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[0], plane[1], plane[2], plane[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(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.
