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

unit GLMeshOptimizer;

(* Mesh optimization unit *)

interface

uses
  System.Classes,
  System.Sysutils,

  GLVectorTypes,
  GLVectorGeometry,
  GLVectorFileObjects;

type

  TGLMeshOptimizerOption = (mooStandardize, mooVertexCache, mooSortByMaterials,
    mooMergeObjects);
  TGLMeshOptimizerOptions = set of TGLMeshOptimizerOption;

var
  vDefaultMeshOptimizerOptions: TGLMeshOptimizerOptions = [mooStandardize,
    mooVertexCache, mooSortByMaterials, mooMergeObjects];

(* Optimize Mesh (list, default options) *)
procedure OptimizeMesh(aList: TGLMeshObjectList; options: TGLMeshOptimizerOptions); overload;
procedure OptimizeMesh(aList: TGLMeshObjectList); overload;
// OptimizeMesh (object, with options)
procedure OptimizeMesh(aMeshObject: TMeshObject; options: TGLMeshOptimizerOptions); overload;
// OptimizeMesh (object, default options)
procedure OptimizeMesh(aMeshObject: TMeshObject); overload;
procedure FacesSmooth(aMeshObj: TMeshObject;
  aWeldDistance: Single = 0.0000001; aThreshold: Single = 35.0;
  InvertNormals: boolean = false);

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

uses
  GLPersistentClasses, 
  GLVectorLists, 
  GLMeshUtils;

procedure OptimizeMesh(aList: TGLMeshObjectList);
begin
  OptimizeMesh(aList, vDefaultMeshOptimizerOptions);
end;

procedure OptimizeMesh(aList: TGLMeshObjectList;
  options: TGLMeshOptimizerOptions);
var
  i, k: Integer;
  mob, mo: TMeshObject;
  fg: TGLFaceGroup;
  fgvi: TFGVertexIndexList;
begin
  // optimize all mesh objects
  for i := 0 to aList.Count - 1 do
  begin
    OptimizeMesh(aList[i], options);
  end;
  if (mooStandardize in options) then
  begin
    // drop mesh objects that have become empty
    for i := aList.Count - 1 downto 0 do
    begin
      if (aList[i].Mode = momFaceGroups) and (aList[i].FaceGroups.Count = 0)
      then
        aList[i].Free;
    end;
  end;
  if (aList.Count > 0) and (mooMergeObjects in options) then
  begin
    mob := aList[0];
    Assert(mob.Mode = momFaceGroups);
    for i := 1 to aList.Count - 1 do
    begin
      mo := aList[i];
      Assert(mo.Mode = momFaceGroups);
      k := mob.Vertices.Count;
      mob.Vertices.Add(mo.Vertices);
      mob.Normals.Add(mo.Normals);
      mob.TexCoords.Add(mo.TexCoords);
      while mo.FaceGroups.Count > 0 do
      begin
        fg := mo.FaceGroups[0];
        fgvi := (fg as TFGVertexIndexList);
        fgvi.Owner := mob.FaceGroups;
        mob.FaceGroups.Add(fgvi);
        mo.FaceGroups.Delete(0);
        fgvi.VertexIndices.Offset(k);
      end;
    end;
    for i := aList.Count - 1 downto 1 do
      aList[i].Free;
  end;
end;

procedure OptimizeMesh(aMeshObject: TMeshObject);
begin
  OptimizeMesh(aMeshObject, vDefaultMeshOptimizerOptions);
end;

procedure OptimizeMesh(aMeshObject: TMeshObject;
  options: TGLMeshOptimizerOptions);
var
  i: Integer;
  fg: TGLFaceGroup;
  coords, TexCoords, Normals: TAffineVectorList;
  il: TIntegerList;
  materialName: String;
begin
  if (mooMergeObjects in options) then
  begin
    if aMeshObject.Mode = momFaceGroups then
    begin
      // remove empty facegroups
      for i := aMeshObject.FaceGroups.Count - 1 downto 0 do
      begin
        fg := aMeshObject.FaceGroups[i];
        if fg.TriangleCount = 0 then
          fg.Free;
      end;
    end;
  end;

  if (mooStandardize in options) then
  begin
    if (aMeshObject.Mode <> momFaceGroups) or (aMeshObject.FaceGroups.Count <= 1)
    then
    begin
      if aMeshObject.FaceGroups.Count = 1 then
        materialName := aMeshObject.FaceGroups[0].materialName;
      TexCoords := TAffineVectorList.Create;
      Normals := TAffineVectorList.Create;
      coords := aMeshObject.ExtractTriangles(TexCoords, Normals);
      try
        il := BuildVectorCountOptimizedIndices(coords, Normals, TexCoords);
        try
          aMeshObject.Clear;
          if il.Count > 0 then
          begin
            RemapReferences(Normals, il);
            RemapReferences(TexCoords, il);
            RemapAndCleanupReferences(coords, il);
            aMeshObject.Vertices := coords;
            aMeshObject.Normals := Normals;
            aMeshObject.TexCoords := TexCoords;
            fg := TFGVertexIndexList.CreateOwned(aMeshObject.FaceGroups);
            fg.materialName := materialName;
            TFGVertexIndexList(fg).VertexIndices := il;
          end;
        finally
          il.Free;
        end;
      finally
        coords.Free;
        Normals.Free;
        TexCoords.Free;
      end;
    end
    else
      Assert(false,
        'Standardization with multiple facegroups not supported... yet.');
  end;

  if (mooVertexCache in options) and (aMeshObject.Mode = momFaceGroups) then
  begin
    for i := 0 to aMeshObject.FaceGroups.Count - 1 do
    begin
      fg := aMeshObject.FaceGroups[i];
      if fg.ClassType = TFGVertexIndexList then
        with TFGVertexIndexList(fg) do
        begin
          if Mode in [fgmmTriangles, fgmmFlatTriangles] then
            IncreaseCoherency(VertexIndices, 12);
        end;
    end;
  end;

  if mooSortByMaterials in options then
    aMeshObject.FaceGroups.SortByMaterial;
end;

procedure FacesSmooth(aMeshObj: TMeshObject;
  aWeldDistance: Single = 0.0000001; aThreshold: Single = 35.0;
  InvertNormals: boolean = false);
Var
  i, J, k, L: Integer;
  WeldedVertex: TAffineVectorList;
  TmpIntegerList: TIntegerList;
  IndexMap: TStringList;
  n: TAffineVector;
  indicesMap: TIntegerList;
  Index: Integer;
  FaceList: TIntegerList;
  NormalList: TAffineVectorList;
  FaceNormalList: TAffineVectorList;
  FaceGroup: TGLFaceGroup;
  fg, FG1: TFGVertexIndexList;
  Threshold: Single;
  Angle: Single;
  ReferenceMap: TIntegerList;
  ID1, ID2: Integer;
  Index1, Index2, Index3: Integer;

  function FindReferenceIndex(aID: Integer): Integer;
  begin
    Result := ReferenceMap[aID];
  end;
  function iMin(a, b: Integer): Integer;
  begin
    if a < b then
      Result := a
    else
      Result := b;
  end;
  function iMax(a, b: Integer): Integer;
  begin
    if a > b then
      Result := a
    else
      Result := b;
  end;

begin
  Threshold := aThreshold * Pi / 180.0;
  // build the vectices reference map
  ReferenceMap := TIntegerList.Create;
  WeldedVertex := TAffineVectorList.Create;
  WeldedVertex.Assign(aMeshObj.Vertices);
  indicesMap := TIntegerList.Create;
  // first of all, weld the very closed vertices
  WeldVertices(WeldedVertex, indicesMap, aWeldDistance);
  // then, rebuild the map list
  IndexMap := TStringList.Create;
  for i := 0 to WeldedVertex.Count - 1 do
  begin
    ReferenceMap.Assign(indicesMap);
    TmpIntegerList := TIntegerList.Create;
    Index := ReferenceMap.IndexOf(i);
    while Index >= 0 do
    begin
      TmpIntegerList.Add(Index);
      ReferenceMap[Index] := -99999;
      Index := ReferenceMap.IndexOf(i);
    end;
    IndexMap.AddObject(IntToStr(i), TmpIntegerList);
  end;
  ReferenceMap.Assign(indicesMap);
  // never used these, free them all
  WeldedVertex.Free;
  indicesMap.Free;
  // creates a TexPoint list for save face infomation, where s=facegroup index, t=face index
  FaceList := TIntegerList.Create;
  NormalList := TAffineVectorList.Create;
  FaceNormalList := TAffineVectorList.Create;
  // NormalIndex := TIntegerList.Create;
  for i := 0 to aMeshObj.FaceGroups.Count - 1 do
  begin
    FaceGroup := aMeshObj.FaceGroups[i];
    TmpIntegerList := TFGVertexIndexList(FaceGroup).VertexIndices;
    for J := 0 to (TmpIntegerList.Count div 3) - 1 do
    begin
      FaceList.Add(i);
      FaceList.Add(J);
      CalcPlaneNormal(aMeshObj.Vertices[TmpIntegerList[J * 3 + 0]],
        aMeshObj.Vertices[TmpIntegerList[J * 3 + 1]],
        aMeshObj.Vertices[TmpIntegerList[J * 3 + 2]], n);
      // add three normals for one trangle
      FaceNormalList.Add(n);
      NormalList.Add(n);
      NormalList.Add(n);
      NormalList.Add(n);
    end;
  end;
  // do smooth
  for i := 0 to (FaceList.Count div 2) - 1 do
  begin
    Index := FaceList[i * 2 + 0];
    Index1 := FaceList[i * 2 + 1];
    fg := TFGVertexIndexList(aMeshObj.FaceGroups[Index]);
    for J := 0 to 2 do
    begin
      for k := 0 to (FaceList.Count div 2) - 1 do
      begin
        Index2 := FaceList[k * 2 + 0];
        Index3 := FaceList[k * 2 + 1];
        FG1 := TFGVertexIndexList(aMeshObj.FaceGroups[Index2]);
        if i <> k then
        begin
          for L := 0 to 2 do
          begin
            // two face contain the same vertex
            ID1 := FindReferenceIndex(fg.VertexIndices[Index1 * 3 + J]);
            ID2 := FindReferenceIndex(FG1.VertexIndices[Index3 * 3 + L]);
            if ID1 = ID2 then
            begin
              Angle := VectorDotProduct(FaceNormalList[i], FaceNormalList[k]);
              if Angle > Threshold then
                NormalList[i * 3 + J] := VectorAdd(NormalList[i * 3 + J],
                  FaceNormalList[k]);
            end;
          end;
        end;
      end;
      n := NormalList[i * 3 + J];
      NormalizeVector(n);
      NormalList[i * 3 + J] := n;
    end;
  end;
  for i := 0 to (FaceList.Count div 2) - 1 do
  begin
    Index := FaceList[i * 2 + 0];
    fg := TFGVertexIndexList(aMeshObj.FaceGroups[Index]);
    Index := FaceList[i * 2 + 1];
    aMeshObj.Normals[fg.VertexIndices[(Index * 3 + 0)]] :=
      NormalList[(i * 3 + 0)];
    aMeshObj.Normals[fg.VertexIndices[(Index * 3 + 1)]] :=
      NormalList[(i * 3 + 1)];
    aMeshObj.Normals[fg.VertexIndices[(Index * 3 + 2)]] :=
      NormalList[(i * 3 + 2)];
    if InvertNormals then
    begin
      aMeshObj.Normals[fg.VertexIndices[(Index * 3 + 0)]] :=
        VectorNegate(aMeshObj.Normals[fg.VertexIndices[(Index * 3 + 0)]]);
      aMeshObj.Normals[fg.VertexIndices[(Index * 3 + 1)]] :=
        VectorNegate(aMeshObj.Normals[fg.VertexIndices[(Index * 3 + 1)]]);
      aMeshObj.Normals[fg.VertexIndices[(Index * 3 + 2)]] :=
        VectorNegate(aMeshObj.Normals[fg.VertexIndices[(Index * 3 + 2)]]);
    end;
  end;
  FaceList.Free;
  NormalList.Free;
  FaceNormalList.Free;
  ReferenceMap.Free;
  for i := 0 to IndexMap.Count - 1 do
    IndexMap.Objects[i].Free;
  IndexMap.Free;
end;

end.
