// File:	MeshVS_VectorPrsBuilder.cxx
// Created:	Fri Sep 19 2003
// Author:	Alexander SOLOVYOV
// Copyright:	 Open CASCADE 2003

#include <MeshVS_VectorPrsBuilder.ixx>

#include <MeshVS_DisplayModeFlags.hxx>

#include <Graphic3d_ArrayOfPolylines.hxx>
#include <Graphic3d_ArrayOfPolygons.hxx>
#include <Graphic3d_ArrayOfTriangleFans.hxx>
#include <Graphic3d_MaterialAspect.hxx>
#include <Graphic3d_AspectLine3d.hxx>
#include <Graphic3d_AspectFillArea3d.hxx>
#include <Graphic3d_Group.hxx>

#include <TColStd_MapIteratorOfPackedMapOfInteger.hxx>
#include <TColStd_Array1OfReal.hxx>
#include <TColgp_Array1OfPnt.hxx>

#include <Precision.hxx>

#include <gp_Ax3.hxx>

#include <Prs3d_Root.hxx>

#include <Aspect_TypeOfLine.hxx>

#include <MeshVS_DataSource.hxx>
#include <MeshVS_Drawer.hxx>
#include <MeshVS_DrawerAttribute.hxx>
#include <MeshVS_Mesh.hxx>
#include <MeshVS_EntityType.hxx>
#include <MeshVS_DataMapIteratorOfDataMapOfIntegerVector.hxx>

//================================================================
// Function : Constructor MeshVS_VectorPrsBuilder
// Purpose  :
//================================================================
MeshVS_VectorPrsBuilder::MeshVS_VectorPrsBuilder ( const Handle(MeshVS_Mesh)& Parent,
                                                   const Standard_Real MaxLength,
                                                   const Quantity_Color& VectorColor,
                                                   const MeshVS_DisplayModeFlags& Flags,
                                                   const Handle (MeshVS_DataSource)& DS,
                                                   const Standard_Integer Id,
                                                   const MeshVS_BuilderPriority& Priority )
: MeshVS_PrsBuilder ( Parent, Flags, DS, Id, Priority )
{
  Handle ( MeshVS_Drawer ) aDrawer = GetDrawer();
  if ( !aDrawer.IsNull() )
  {
    aDrawer->SetDouble ( MeshVS_DA_VectorMaxLength, MaxLength );
    aDrawer->SetColor  ( MeshVS_DA_VectorColor, VectorColor );
    aDrawer->SetDouble ( MeshVS_DA_VectorArrowPart, 0.1 );
  }
}

//================================================================
// Function : GetVectors
// Purpose  :
//================================================================
const MeshVS_DataMapOfIntegerVector& MeshVS_VectorPrsBuilder::GetVectors
  ( const Standard_Boolean IsElements ) const
{
  if ( IsElements )
    return myElemVectorMap;
  else
    return myNodeVectorMap;
}

//================================================================
// Function : SetVectors
// Purpose  :
//================================================================
void MeshVS_VectorPrsBuilder::SetVectors ( const Standard_Boolean IsElements,
                                           const MeshVS_DataMapOfIntegerVector& theMap )
{
  if ( IsElements )
    myElemVectorMap = theMap;
  else
    myNodeVectorMap = theMap;
}

//================================================================
// Function : HasVectors
// Purpose  :
//================================================================
Standard_Boolean MeshVS_VectorPrsBuilder::HasVectors ( const Standard_Boolean IsElement ) const
{
  Standard_Boolean aRes = (myNodeVectorMap.Extent()>0);
  if ( IsElement )
    aRes = (myElemVectorMap.Extent()>0);
  return aRes;

}

//================================================================
// Function : GetVector
// Purpose  :
//================================================================
Standard_Boolean MeshVS_VectorPrsBuilder::GetVector ( const Standard_Boolean IsElement,
                                                      const Standard_Integer ID,
                                                      gp_Vec& Vect ) const
{
  const MeshVS_DataMapOfIntegerVector* aMap = &myNodeVectorMap;
  if ( IsElement )
    aMap = &myElemVectorMap;

  Standard_Boolean aRes = aMap->IsBound ( ID );
  if ( aRes )
    Vect = aMap->Find ( ID );

  return aRes;
}

//================================================================
// Function : SetVector
// Purpose  :
//================================================================
void MeshVS_VectorPrsBuilder::SetVector ( const Standard_Boolean IsElement,
                                          const Standard_Integer ID,
                                          const gp_Vec& Vect )
{
  MeshVS_DataMapOfIntegerVector* aMap = &myNodeVectorMap;
  if ( IsElement )
    aMap = &myElemVectorMap;

  Standard_Boolean aRes = aMap->IsBound ( ID );
  if ( aRes )
    aMap->ChangeFind ( ID ) = Vect;
  else
    aMap->Bind ( ID, Vect );
}

//================================================================
// Function : GetMaxVectorValue
// Purpose  :
//================================================================
void MeshVS_VectorPrsBuilder::GetMinMaxVectorValue ( const Standard_Boolean IsElement,
                                                     Standard_Real& MinValue,
                                                     Standard_Real& MaxValue ) const
{
  const MeshVS_DataMapOfIntegerVector* aMap = &myNodeVectorMap;
  if ( IsElement )
    aMap = &myElemVectorMap;

  MeshVS_DataMapIteratorOfDataMapOfIntegerVector anIt ( *aMap );
  if ( anIt.More() )
    MinValue = MaxValue = anIt.Value().Magnitude();

  Standard_Real aCurValue;

  for ( ; anIt.More(); anIt.Next() )
  {
    aCurValue = anIt.Value().Magnitude();
    if ( MinValue > aCurValue )
      MinValue = aCurValue;
    if ( MaxValue < aCurValue )
      MaxValue = aCurValue;
  }
}

//================================================================
// Function : Build
// Purpose  :
//================================================================

#define NB_VERTICES 2
#define NB_BOUNDS 1
#define NB_TRIANGLES 6
#define NB_FANS 1

void MeshVS_VectorPrsBuilder::Build ( const Handle(Prs3d_Presentation)& Prs,
                                      MeshVS_Array1OfIntegerBoolean& IDs,
                                      const Standard_Boolean IsElement,
                                      const Standard_Integer theDisplayMode ) const
{
  Handle ( MeshVS_Drawer ) aDrawer = GetDrawer();
  Handle (MeshVS_DataSource) aSource = GetDataSource();
  if ( aSource.IsNull() || aDrawer.IsNull() || !HasVectors( IsElement ) ||
       ( theDisplayMode & GetFlags() )==0 )
    return;

  Standard_Integer aMaxFaceNodes;
  Standard_Real aMaxLen, anArrowPart = 0.1;

  if ( !aDrawer->GetInteger ( MeshVS_DA_MaxFaceNodes, aMaxFaceNodes ) ||
       aMaxFaceNodes <= 0 ||
       !aDrawer->GetDouble  ( MeshVS_DA_VectorMaxLength, aMaxLen )    ||
       aMaxLen <= 0       ||
       !aDrawer->GetDouble ( MeshVS_DA_VectorArrowPart, anArrowPart ) ||
       anArrowPart <= 0
     )
    return;

  TColStd_Array1OfReal aCoords (1, 3*aMaxFaceNodes);
  Standard_Integer NbNodes;
  MeshVS_EntityType aType;

  // DECLARE ARRAYS OF PRIMITIVES
  const MeshVS_DataMapOfIntegerVector& aMap = GetVectors ( IsElement );
  Standard_Integer aNbVectors = aMap.Extent();

  if ( aNbVectors <= 0 )
    return;

  // polylines
  Standard_Integer aNbVertices         = aNbVectors  * NB_VERTICES;
  Standard_Integer aNbBounds           = aNbVectors  * NB_BOUNDS;

  // fans
  Standard_Integer aNbTriangleVertices = aNbVectors  * (NB_TRIANGLES + 2);
  Standard_Integer aNbFans             = aNbVectors  * NB_TRIANGLES;

  Handle(Graphic3d_ArrayOfPrimitives) aLineArray =
    new Graphic3d_ArrayOfPolylines(aNbVertices, aNbBounds);

  Handle(Graphic3d_ArrayOfTriangleFans) aTriangleArray =
    new Graphic3d_ArrayOfTriangleFans(aNbTriangleVertices, aNbFans);

  TColgp_Array1OfPnt anArrowPnt(1,8);
  Standard_Real k, b, aMaxValue, aMinValue, aValue, X, Y, Z;
  
  Standard_Real aMinLength = calculateArrow( anArrowPnt, aMaxLen, anArrowPart );
  gp_Vec aVec; gp_Trsf aTrsf;

  GetMinMaxVectorValue ( IsElement, aMinValue, aMaxValue );

  if ( aMaxValue - aMinValue > Precision::Confusion() )
  {
    k = 0.8 * aMaxLen / ( aMaxValue - aMinValue );
    b = aMaxLen - k * aMaxValue;
  }
  else
  {
    k = 0;
    b = aMaxLen;
  }

  TColStd_PackedMapOfInteger aCustomElements;

  for ( Standard_Integer index=IDs.Lower(), up=IDs.Upper(); index<=up; index++ )
  {
    MeshVS_IntegerBoolean& str = IDs.ChangeValue( index );
    if( str.Value )
      if( GetVector ( IsElement, str.Key, aVec ) &&
          (!IsElement || !myParentMesh->IsHiddenElem( str.Key ) ) )
      {
        if( IsElement && IsExcludingOn() )
          str.Value = Standard_False;

        aValue = aVec.Magnitude();

        if ( Abs( aValue ) < Precision::Confusion() )
          continue;

        if( aSource->GetGeom ( str.Key, IsElement, aCoords, NbNodes, aType ) )
        {
          if( aType == MeshVS_ET_Node )
          {
            X = aCoords(1);
            Y = aCoords(2);
            Z = aCoords(3);
          }
          else if( aType == MeshVS_ET_Link || 
                   aType == MeshVS_ET_Face || 
                   aType == MeshVS_ET_Volume )
          {
            X = Y = Z = 0;
            for ( Standard_Integer i=1; i<=NbNodes; i++ )
            {
              X += aCoords (3*i-2);
              Y += aCoords (3*i-1);
              Z += aCoords (3*i);
            }
            X /= Standard_Real ( NbNodes );
            Y /= Standard_Real ( NbNodes );
            Z /= Standard_Real ( NbNodes );
          }
          else
          {
            aCustomElements.Add( str.Key );
            continue;
          }

          aTrsf.SetDisplacement ( gp_Ax3 ( gp_Pnt ( 0, 0, 0 ), gp_Dir(0, 0, 1)),
                                  gp_Ax3 ( gp_Pnt ( X, Y, Z ), aVec ) );

          DrawVector ( aTrsf, Max( k * fabs( aValue ) + b, aMinLength), anArrowPnt, aLineArray,
                       aTriangleArray );
        }
      }
  }

  Prs3d_Root::NewGroup ( Prs );
  Handle (Graphic3d_Group) aVGroup = Prs3d_Root::CurrentGroup ( Prs );

  Quantity_Color aColor;
  aDrawer->GetColor ( MeshVS_DA_VectorColor, aColor );

  // Add primitive arrays to group
  Handle(Graphic3d_AspectLine3d) aLinAspect =
    new Graphic3d_AspectLine3d ( aColor, Aspect_TOL_SOLID, 1.5 );

  Graphic3d_MaterialAspect aMatAspect;
  aMatAspect.SetAmbient( 1 );
  aMatAspect.SetDiffuse( 0 );
  aMatAspect.SetEmissive( 0 );
  aMatAspect.SetShininess( 1 );
  aMatAspect.SetSpecular( 0 );
  Handle(Graphic3d_AspectFillArea3d) aFillAspect =
    new Graphic3d_AspectFillArea3d (Aspect_IS_HOLLOW, aColor, aColor, Aspect_TOL_SOLID,
                                    1., aMatAspect, aMatAspect );

  aVGroup->SetPrimitivesAspect( aLinAspect );
  aVGroup->SetPrimitivesAspect( aFillAspect );

  aVGroup->BeginPrimitives();
  aVGroup->AddPrimitiveArray( aLineArray );
  aVGroup->AddPrimitiveArray( aTriangleArray );
  aVGroup->EndPrimitives();

  if( aCustomElements.Extent()>0 )
  {
    MeshVS_Array1OfIntegerBoolean aCustomArray( 1, aCustomElements.Extent() );
    MeshVS_Mesh::TColStdMapToMeshVSArray( aCustomElements, aCustomArray );
    CustomBuild ( Prs, aCustomArray, theDisplayMode );
  }
}

//=======================================================================
// name    : DrawVector
// Purpose : Fill arrays of primitives for drawing force
//=======================================================================
void MeshVS_VectorPrsBuilder::DrawVector ( const gp_Trsf& theTrsf,
                                           const Standard_Real Length,
                                           const TColgp_Array1OfPnt& ArrowPoints,
                                           const Handle(Graphic3d_ArrayOfPrimitives)& Lines,
                                           const Handle(Graphic3d_ArrayOfPrimitives)& Triangles) const
{
  const int PntNum = 8;
  // draw line
  gp_Pnt aLinePnt[ 2 ] = { gp_Pnt( 0, 0, 0 ) , gp_Pnt( 0, 0, Length ) };
  theTrsf.Transforms( aLinePnt[ 0 ].ChangeCoord() );
  theTrsf.Transforms( aLinePnt[ 1 ].ChangeCoord() );

  Lines->AddBound( 2 );
  Lines->AddVertex( aLinePnt[ 0 ] );
  Lines->AddVertex( aLinePnt[ 1 ] );

  Standard_Integer l = ArrowPoints.Lower(),
                   u = ArrowPoints.Upper(),
                   i;
  if ( u-l < PntNum-1 )
    return;

  // draw arrow
  TColgp_Array1OfPnt anArrowPnt( l, u );
  for ( i = l; i < l+PntNum; i++ )
  {
    anArrowPnt( i ).ChangeCoord() = ArrowPoints ( i ).Coord() + gp_XYZ( 0, 0, Length );
    theTrsf.Transforms( anArrowPnt( i ).ChangeCoord() );
  }

  Triangles->AddBound(8);
  for ( i = 0; i < PntNum; i++ )
    Triangles->AddVertex( anArrowPnt( l+i ) );
}

//=======================================================================
// name    : calculateArrow
// Purpose : Calculate points of arrow ( 8 pnts )
//=======================================================================
Standard_Real MeshVS_VectorPrsBuilder::calculateArrow ( TColgp_Array1OfPnt& Points,
                                                        const Standard_Real Length,
                                                        const Standard_Real ArrowPart )
{
  Standard_Real h = Length * ArrowPart;
  Standard_Real w = h / 5.;

  Standard_Integer f = Points.Lower();
  Points( f   ) = gp_Pnt( 0,  0, 0 );
  Points( f+1 ) = gp_Pnt( 0, -w, -h );
  Points( f+2 ) = gp_Pnt( w * 0.866, -w * 0.5, -h );
  Points( f+3 ) = gp_Pnt( w * 0.866,  w * 0.5, -h );
  Points( f+4 ) = gp_Pnt( 0 , w, -h );
  Points( f+5 ) = gp_Pnt( -w * 0.866,  w * 0.5, -h );
  Points( f+6 ) = gp_Pnt( -w * 0.866, -w * 0.5, -h );
  Points( f+7 ) = gp_Pnt( 0, -w, -h );

  return h;
}
