// File:      VrmlData_ShapeConvert.cxx
// Created:   04.08.07 10:09
// Author:    Alexander GRIGORIEV
// Copyright: Open Cascade 2007


#include <VrmlData_ShapeConvert.hxx>
#include <VrmlData_Scene.hxx>
#include <VrmlData_Group.hxx>
#include <VrmlData_Coordinate.hxx>
#include <VrmlData_IndexedFaceSet.hxx>
#include <VrmlData_IndexedLineSet.hxx>
#include <VrmlData_ShapeNode.hxx>
#include <BRepMesh_IncrementalMesh.hxx>
#include <BRep_Builder.hxx>
#include <BRep_Tool.hxx>
#include <Geom_Surface.hxx>
#include <NCollection_DataMap.hxx>
#include <Poly_Triangulation.hxx>
#include <Poly_Connect.hxx>
#include <Poly_PolygonOnTriangulation.hxx>
#include <Poly_Polygon3D.hxx>
#include <Precision.hxx>
#include <TColgp_Array1OfPnt2d.hxx>
#include <TopExp_Explorer.hxx>
#include <TopoDS.hxx>
#include <TopoDS_Edge.hxx>
#include <TopoDS_Face.hxx>
#include <TopoDS_Shape.hxx>
#include <TopoDS_Wire.hxx>
#include <GCPnts_TangentialDeflection.hxx>
#include <BRepAdaptor_Curve.hxx>
#include <TColStd_Array1OfReal.hxx>
#include <TColStd_HArray1OfReal.hxx>
#include <TShort_Array1OfShortReal.hxx>
#include <GeomLib.hxx>
#include <TShort_HArray1OfShortReal.hxx>

//=======================================================================
//function : IsEqual
//purpose  : for NCollection_DataMap interface
//=======================================================================

inline Standard_Boolean IsEqual (const TopoDS_Shape& one,
                                 const TopoDS_Shape& two) 
{
  return one == two;
}

//=======================================================================
//function : AddShape
//purpose  : 
//=======================================================================

void VrmlData_ShapeConvert::AddShape (const TopoDS_Shape& theShape,
                                      const char *        theName)
{
  ShapeData aData;/* = { - compilation problem on SUN 
    TCollection_AsciiString(),
    theShape,
    NULL
  };*/
  aData.Shape = theShape;
  aData.Node = NULL;

  if (theName) {
    char buf[2048], * optr = &buf[0];
    char * eptr = &buf[sizeof(buf)-1];
    for (const char * ptr = theName;; ptr++) {
      int sym = *ptr;
      if (sym == '\0' || sym == '\n' || sym == '\r') {
        * optr = '\0';
        break;
      }
      if (sym == '\"' || sym == '\\')
        * optr = '/';
      else if (sym == '.')
        * optr = '_';
      else
        * optr = sym;
      if (++optr >= eptr) {
        *optr = '\0';
        break;
      }
    }
    aData.Name = buf;
  }
  myShapes.Append (aData);
}

//=======================================================================
//function : Convert
//purpose  : 
//=======================================================================

void VrmlData_ShapeConvert::Convert (const Standard_Boolean theExtractFaces,
                                     const Standard_Boolean theExtractEdges,
                                     const Standard_Real    theDeflection,
                                     const Standard_Real    theDeflAngle)
{
  const Standard_Real aDeflection =
    theDeflection < 0.0001 ? 0.0001 : theDeflection;

  Standard_Boolean Extract[2] = {theExtractFaces, theExtractEdges};
  TopAbs_ShapeEnum ShapeType[2] = {TopAbs_FACE, TopAbs_EDGE};
  Standard_Integer i;

  const Handle(NCollection_IncAllocator) anAlloc = new NCollection_IncAllocator;

  // Relocation map for converted shapes. We should distinguish both TShape
  // and Orientation in this map.
  NCollection_DataMap <TopoDS_Shape,Handle(VrmlData_Geometry)>
    aRelMap (100, anAlloc);


  NCollection_List<ShapeData>::Iterator anIter (myShapes);
  for (; anIter.More(); anIter.Next()) {

    ShapeData& aData = anIter.ChangeValue();
    Handle(VrmlData_Group) aGroup =
      new VrmlData_Group (myScene, aData.Name.ToCString());
    myScene.AddNode (aGroup);

    for(i = 0; i < 2; ++i) {

      if(!Extract[i]) continue;

      TopExp_Explorer anExp (aData.Shape, ShapeType[i]);
      for (; anExp.More(); anExp.Next()) {
        const TopoDS_Shape& aShape = anExp.Current();
        TopLoc_Location aLoc;
        Handle(VrmlData_Geometry) aTShapeNode;
        const Standard_Boolean isReverse=(aShape.Orientation()==TopAbs_REVERSED); 

        TopoDS_Shape aTestedShape;
        aTestedShape.TShape (aShape.TShape());
        aTestedShape.Orientation (isReverse ? TopAbs_REVERSED : TopAbs_FORWARD);
        Standard_Boolean isTessellate (Standard_False);
        switch (ShapeType[i]) {
        case TopAbs_FACE:
          {
            const TopoDS_Face& aFace = TopoDS::Face (aShape);
            if (aFace.IsNull() == Standard_False) {
              Handle(Poly_Triangulation) aTri =
                BRep_Tool::Triangulation (aFace, aLoc);
      
              if (aRelMap.IsBound (aTestedShape)) {
                aTShapeNode = aRelMap(aTestedShape);
                break;
              }

              if (aTri.IsNull())
                isTessellate = Standard_True;
              // Check the existing deflection
              else if (aTri->Deflection() > aDeflection+ Precision::Confusion())
                isTessellate = Standard_True;
              if (isTessellate) {
                // Triangulate the face by the standard OCC mesher
                BRepMesh_IncrementalMesh IM (aFace, aDeflection, Standard_False, theDeflAngle);
                aTri = BRep_Tool::Triangulation (aFace, aLoc);
              }
              if (aTri.IsNull() == Standard_False) {
                TopoDS_Shape aTestedShapeRev = aTestedShape;
                aTestedShapeRev.Orientation (isReverse ?
                                             TopAbs_FORWARD : TopAbs_REVERSED);
                Handle(VrmlData_IndexedFaceSet) aFaceSetToReuse;
                if (aRelMap.IsBound (aTestedShapeRev))
                  aFaceSetToReuse = Handle(VrmlData_IndexedFaceSet)::DownCast
                    (aRelMap(aTestedShapeRev));

                Handle(VrmlData_Coordinate) aCoordToReuse;
                if (aFaceSetToReuse.IsNull() == Standard_False)
                  aCoordToReuse = aFaceSetToReuse->Coordinates();

                aTShapeNode = triToIndexedFaceSet (aTri, aFace, aCoordToReuse);
                myScene.AddNode (aTShapeNode, Standard_False);
                // Bind the converted face
                aRelMap.Bind (aTestedShape, aTShapeNode);
              }
            }
          }
          break;
        case TopAbs_WIRE:
          {
            const TopoDS_Wire& aWire = TopoDS::Wire (aShape);
            if (aWire.IsNull() == Standard_False) {
            }
          }
          break;
       case TopAbs_EDGE:
         {
           const TopoDS_Edge& aEdge = TopoDS::Edge (aShape);
            if (aEdge.IsNull() == Standard_False) {
              Handle(Poly_Polygon3D) aPol = BRep_Tool::Polygon3D (aEdge, aLoc);
              
              if (aRelMap.IsBound (aTestedShape)) {
                aTShapeNode = aRelMap(aTestedShape);
                break;
              }
              // Check the presence of reversly oriented Edge. It can also be used
              // because we do not distinguish the orientation for edges.
              aTestedShape.Orientation (isReverse ?
                                        TopAbs_FORWARD : TopAbs_REVERSED);
              if (aRelMap.IsBound (aTestedShape)) {
                aTShapeNode = aRelMap(aTestedShape);
                break;
              }
              
              if (aPol.IsNull())
                isTessellate = Standard_True;
              // Check the existing deflection
              else if (aPol->Deflection() > aDeflection+ Precision::Confusion()
                       && BRep_Tool::IsGeometric(aEdge))
                isTessellate = Standard_True;
              
              if (isTessellate && BRep_Tool::IsGeometric(aEdge)) {
                //try to find PolygonOnTriangulation
                Handle(Poly_PolygonOnTriangulation) aPT;
                Handle(Poly_Triangulation) aT;
                TopLoc_Location aL;

                Standard_Boolean found = Standard_False;
                for(i = 1; Standard_True; i++) {
                  
                  BRep_Tool::PolygonOnTriangulation(aEdge, aPT, aT, aL, i);

                  if(aPT.IsNull() || aT.IsNull()) break;

                  if(aPT->Deflection() <= aDeflection + Precision::Confusion() &&
                     aPT->HasParameters()) {
                    found = Standard_True;
                    break;
                  }

                }
                
                if(found) {

                  BRepAdaptor_Curve aCurve(aEdge);
                  Handle(TColStd_HArray1OfReal) aPrs = aPT->Parameters();
                  Standard_Integer nbNodes = aPT->NbNodes();
                  TColgp_Array1OfPnt arrNodes(1, nbNodes);
                  TColStd_Array1OfReal arrUVNodes(1, nbNodes);

                  for(i = 1; i <= nbNodes; i++) {
                    arrUVNodes(i) = aPrs->Value(aPrs->Lower() + i - 1);
                    arrNodes(i) = aCurve.Value(arrUVNodes(i));
                  }
                  aPol = new Poly_Polygon3D(arrNodes, arrUVNodes);
                  aPol->Deflection (aPT->Deflection());
                }
                else{
                  
                  BRepAdaptor_Curve aCurve(aEdge);
                  const Standard_Real aFirst = aCurve.FirstParameter();
                  const Standard_Real aLast  = aCurve.LastParameter();
                
                  GCPnts_TangentialDeflection TD (aCurve, aFirst, aLast,
                                                  theDeflAngle, aDeflection, 2);
                  const Standard_Integer nbNodes = TD.NbPoints();
                
                  TColgp_Array1OfPnt arrNodes(1, nbNodes);
                  TColStd_Array1OfReal arrUVNodes(1, nbNodes);
                  for (i = 1; i <= nbNodes; i++) {
                    arrNodes(i) = TD.Value(i);
                    arrUVNodes(i) = TD.Parameter(i);
                  }
                  aPol = new Poly_Polygon3D(arrNodes, arrUVNodes);
                  aPol->Deflection (aDeflection);
                }
                
                BRep_Builder aBld;
                aBld.UpdateEdge (aEdge, aPol);
              }
              aTShapeNode = polToIndexedLineSet (aPol);
              myScene.AddNode (aTShapeNode, Standard_False);
              // Bind the converted face
              aRelMap.Bind (aTestedShape, aTShapeNode);
            }
          }
          break;
        }
        
        if (aTShapeNode.IsNull() == Standard_False) {
          const Handle(VrmlData_ShapeNode) aShapeNode =
            new VrmlData_ShapeNode (myScene, 0L);
          aShapeNode->SetAppearance (ShapeType[i] == TopAbs_FACE ?
                                     defaultMaterialFace():defaultMaterialEdge());
          myScene.AddNode (aShapeNode, Standard_False);
          aShapeNode->SetGeometry (aTShapeNode);
          if (aLoc.IsIdentity())
            // Store the shape node directly into the main Group.
            aGroup->AddNode (aShapeNode);
          else {
            // Create a Transform grouping node
            Handle(VrmlData_Group) aTrans = new VrmlData_Group (myScene, 0L,
                                                                Standard_True);
            gp_Trsf aTrsf (aLoc);
            if (fabs(myScale - 1.) > Precision::Confusion()) {
              const gp_XYZ aTransl = aTrsf.TranslationPart() * myScale;
              aTrsf.SetTranslationPart (aTransl);
            }
            aTrans->SetTransform (aTrsf);
            myScene.AddNode (aTrans, Standard_False);
            aGroup->AddNode (aTrans);
            
            // Store the shape node under the transform.
            aTrans->AddNode (aShapeNode);
          }
        }
      }
    }
  }
  myShapes.Clear();
}

//=======================================================================
//function : triToIndexedFaceSet
//purpose  : 
//=======================================================================

Handle_VrmlData_Geometry VrmlData_ShapeConvert::triToIndexedFaceSet
                                  (const Handle_Poly_Triangulation&  theTri,
                                   const TopoDS_Face&                theFace,
                                   const Handle_VrmlData_Coordinate& theCoord)
{
  Standard_Integer i;
  const Standard_Integer nNodes         (theTri->NbNodes());
  const Standard_Integer nTriangles     (theTri->NbTriangles());
  const TColgp_Array1OfPnt&    arrPolyNodes = theTri->Nodes();
  const Poly_Array1OfTriangle& arrTriangles = theTri->Triangles();

  const Handle(VrmlData_IndexedFaceSet) aFaceSet =
    new VrmlData_IndexedFaceSet (myScene,
                                 0L,                    // no name
                                 Standard_True,         // IsCCW
                                 Standard_False,        // IsSolid
                                 Standard_False);       // IsConvex
  const Handle(NCollection_IncAllocator)& anAlloc = myScene.Allocator();
  const Standard_Boolean isReverse = (theFace.Orientation() == TopAbs_REVERSED);

  // Create the array of triangles
  const Standard_Integer ** arrPolygons = static_cast<const Standard_Integer **>
    (anAlloc->Allocate (nTriangles * sizeof(const Standard_Integer *)));
  aFaceSet->SetPolygons (nTriangles, arrPolygons);

  // Store the triangles
  for (i = 0; i < nTriangles; i++) {
    Standard_Integer * aPolygon = static_cast<Standard_Integer *>
      (anAlloc->Allocate (4*sizeof(Standard_Integer)));
    aPolygon[0] = 3;
    arrTriangles(i+1).Get (aPolygon[1],aPolygon[2],aPolygon[3]);
    aPolygon[1]--;
    if (isReverse) {
      const Standard_Integer aTmp = aPolygon[2]-1;
      aPolygon[2] = aPolygon[3]-1;
      aPolygon[3] = aTmp;
    } else {
      aPolygon[2]--;
      aPolygon[3]--;
    }
    arrPolygons[i] = aPolygon;
  }

  // Create the Coordinates node
  if (theCoord.IsNull() == Standard_False)
    aFaceSet->SetCoordinates (theCoord);
  else {
    gp_XYZ * arrNodes = static_cast <gp_XYZ *>
      (anAlloc->Allocate (nNodes * sizeof(gp_XYZ)));
    for  (i = 0; i < nNodes; i++)
      arrNodes[i] = arrPolyNodes(i+1).XYZ() * myScale;

    const Handle(VrmlData_Coordinate) aCoordNode =
      new VrmlData_Coordinate (myScene, 0L, nNodes, arrNodes);
    myScene.AddNode (aCoordNode, Standard_False);
    aFaceSet->SetCoordinates (aCoordNode);
  }

  // Create the Normals node if user-defined and if theTri has normals
  if (mySaveNormals) {
    if(theTri->HasNormals()) {
      gp_XYZ * arrVec = static_cast <gp_XYZ *>
        (anAlloc->Allocate (nNodes * sizeof(gp_XYZ)));
      const TShort_Array1OfShortReal& Norm = theTri->Normals();
      Standard_Integer j;
      for  (i = 0, j = 1; i < nNodes; i++, j += 3) {
      
        gp_XYZ aNormal(Norm(j), Norm(j+1), Norm(j+2));
        arrVec[i] = aNormal;
        if (isReverse)
          arrVec[i].Reverse();

      }
      const Handle(VrmlData_Normal) aNormalNode =
        new VrmlData_Normal (myScene, 0L, nNodes, arrVec);
      myScene.AddNode (aNormalNode, Standard_False);
      aFaceSet->SetNormals (aNormalNode);
      return aFaceSet;
    }

    Poly_Connect PC(theTri);
    // Create the Normals node (if UV- values are available)
    TopLoc_Location aLoc;
    const Standard_Real aConf2 = Precision::Confusion()*Precision::Confusion();
    const Handle(Geom_Surface) aSurface = BRep_Tool::Surface (theFace, aLoc);
    if (theTri->HasUVNodes() && aSurface.IsNull() == Standard_False) {
      if (aSurface->IsCNu(1) && aSurface->IsCNv(1))
      {
        Standard_Integer nbNormVal  = nNodes * 3; 
        Handle(TShort_HArray1OfShortReal) Normals = 
          new TShort_HArray1OfShortReal(1, nbNormVal);

        const TColgp_Array1OfPnt2d& arrUV = theTri->UVNodes();
        gp_XYZ * arrVec = static_cast <gp_XYZ *>
          (anAlloc->Allocate (nNodes * sizeof(gp_XYZ)));

        // Compute the normal vectors
        Standard_Real Tol = Sqrt(aConf2);
        for  (i = 0; i < nNodes; i++) {
          const gp_Pnt2d& aUV = arrUV(i+1);
          
          gp_Dir aNormal;
          
          if (GeomLib::NormEstim(aSurface, aUV, Tol, aNormal) > 1) {
            //Try to estimate as middle normal of adjacent triangles
            Standard_Integer n[3];

            gp_XYZ eqPlan(0., 0., 0.);
            for (PC.Initialize(i+1);  PC.More(); PC.Next()) {
              arrTriangles(PC.Value()).Get(n[0], n[1], n[2]);
              gp_XYZ v1(arrPolyNodes(n[1]).Coord()-arrPolyNodes(n[0]).Coord());
              gp_XYZ v2(arrPolyNodes(n[2]).Coord()-arrPolyNodes(n[1]).Coord());
              gp_XYZ vv = v1^v2;

              Standard_Real mod = vv.Modulus();
              if (mod < Tol)
                continue;

              eqPlan += vv/mod;
            }

            if (eqPlan.SquareModulus() > gp::Resolution())
              aNormal = gp_Dir(eqPlan);
          }
          if (isReverse)
            aNormal.Reverse();

          if (aNormal.X()*aNormal.X() < aConf2)
            aNormal.SetX(0.);
          if (aNormal.Y()*aNormal.Y() < aConf2)
            aNormal.SetY(0.);
          if (aNormal.Z()*aNormal.Z() < aConf2)
            aNormal.SetZ(0.);
          arrVec[i] = aNormal.XYZ();

          Standard_Integer j = i * 3;
          Normals->SetValue(j + 1, aNormal.X());
          Normals->SetValue(j + 2, aNormal.Y());
          Normals->SetValue(j + 3, aNormal.Z());

        }

        theTri->SetNormals(Normals);  

        const Handle(VrmlData_Normal) aNormalNode =
          new VrmlData_Normal (myScene, 0L, nNodes, arrVec);
        myScene.AddNode (aNormalNode, Standard_False);
        aFaceSet->SetNormals (aNormalNode);
      }
    }
  }

  return aFaceSet;
}

//=======================================================================
//function : polToIndexedLineSet
//purpose  : single polygon3D => IndexedLineSet
//=======================================================================

Handle_VrmlData_Geometry VrmlData_ShapeConvert::polToIndexedLineSet
                                        (const Handle_Poly_Polygon3D& thePol)
{
  Standard_Integer i;
  const Standard_Integer    nNodes (thePol->NbNodes());
  const TColgp_Array1OfPnt& arrPolyNodes = thePol->Nodes();
  const Handle(NCollection_IncAllocator)& anAlloc = myScene.Allocator();

  const Handle(VrmlData_IndexedLineSet) aLineSet =
    new VrmlData_IndexedLineSet (myScene, 0L);

  // Create the array of polygons (1 member)
  const Standard_Integer ** arrPolygons = static_cast<const Standard_Integer **>
    (anAlloc->Allocate (sizeof(const Standard_Integer *)));
  aLineSet->SetPolygons (1, arrPolygons);

  // Store the polygon
  Standard_Integer * aPolygon = static_cast<Standard_Integer *>
    (anAlloc->Allocate ((nNodes+1)*sizeof(Standard_Integer)));
  aPolygon[0] = nNodes;
  for (i = 1; i <= nNodes; i++)
    aPolygon[i] = i-1;
  arrPolygons[0] = aPolygon;

  // Create the Coordinates node
  gp_XYZ * arrNodes = static_cast <gp_XYZ *>
    (anAlloc->Allocate (nNodes * sizeof(gp_XYZ)));
  for  (i = 0; i < nNodes; i++)
    arrNodes[i] = arrPolyNodes(i+1).XYZ() * myScale;

  const Handle(VrmlData_Coordinate) aCoordNode =
    new VrmlData_Coordinate (myScene, 0L, nNodes, arrNodes);
  myScene.AddNode (aCoordNode, Standard_False);
  aLineSet->SetCoordinates (aCoordNode);

  return aLineSet;
}

//=======================================================================
//function : defaultMaterialFace
//purpose  : 
//=======================================================================

Handle(VrmlData_Appearance) VrmlData_ShapeConvert::defaultMaterialFace () const
{
  static char aNodeName[] = "__defaultMaterialFace";
  Handle(VrmlData_Appearance) anAppearance =
    Handle(VrmlData_Appearance)::DownCast(myScene.FindNode(aNodeName));
  if (anAppearance.IsNull()) {
    Graphic3d_MaterialAspect anAspect = defaultFaceMaterial();
    const Handle(VrmlData_Material) aMaterial =
      new VrmlData_Material (myScene, anAspect.MaterialName(),
      anAspect.Ambient(),
      anAspect.Shininess(),
      0.);
    aMaterial->SetDiffuseColor (anAspect.DiffuseColor());
    //Graphic3d_MaterialAspect uses AmbientColor(), not EmissiveColor()
    aMaterial->SetEmissiveColor(anAspect.AmbientColor());
    aMaterial->SetSpecularColor(anAspect.SpecularColor());
    myScene.AddNode (aMaterial, Standard_False);
    anAppearance = new VrmlData_Appearance (myScene, aNodeName);
    anAppearance->SetMaterial (aMaterial);
    myScene.AddNode (anAppearance, Standard_False);
  }
  return anAppearance;
}

//=======================================================================
//function : defaultFaceMaterial
//purpose  : 
//=======================================================================

Graphic3d_MaterialAspect VrmlData_ShapeConvert::defaultFaceMaterial () const
{
  return Graphic3d_NOM_BRASS;
}

//=======================================================================
//function : defaultMaterialEdge
//purpose  : 
//=======================================================================

Handle(VrmlData_Appearance) VrmlData_ShapeConvert::defaultMaterialEdge () const
{
  static char aNodeName[] = "__defaultMaterialEdge";
  Handle(VrmlData_Appearance) anAppearance =
    Handle(VrmlData_Appearance)::DownCast(myScene.FindNode(aNodeName));
  if (anAppearance.IsNull()) { 
    Graphic3d_MaterialAspect anAspect = defaultFaceMaterial();
    const Handle(VrmlData_Material) aMaterial =
      new VrmlData_Material (myScene, anAspect.MaterialName(),
      anAspect.Ambient(),
      anAspect.Shininess(),
      0.);
    aMaterial->SetDiffuseColor (anAspect.DiffuseColor());
    //Graphic3d_MaterialAspect uses AmbientColor(), not EmissiveColor()
    aMaterial->SetEmissiveColor(anAspect.AmbientColor());
    aMaterial->SetSpecularColor(anAspect.SpecularColor());
    myScene.AddNode (aMaterial, Standard_False);
    anAppearance = new VrmlData_Appearance (myScene, aNodeName);
    anAppearance->SetMaterial (aMaterial);
    myScene.AddNode (anAppearance, Standard_False);
  }
  return anAppearance;
}

//=======================================================================
//function : defaultEdgeMaterial
//purpose  : 
//=======================================================================

Graphic3d_MaterialAspect VrmlData_ShapeConvert::defaultEdgeMaterial () const
{
  Graphic3d_MaterialAspect anAspect;
  anAspect.SetAmbient (0.2);
  anAspect.SetShininess (0.2);
  anAspect.SetDiffuseColor (Quantity_Color (0.2, 0.7, 0.2, Quantity_TOC_RGB));
  anAspect.SetAmbientColor (Quantity_Color (0.2, 0.7, 0.2, Quantity_TOC_RGB));
  anAspect.SetSpecularColor (Quantity_Color (0.2, 0.7, 0.2, Quantity_TOC_RGB));
  return anAspect;
}
