// Created on: 1996-12-05
// Created by: Arnaud BOUZY/Odile Olivier
// Copyright (c) 1996-1999 Matra Datavision
// Copyright (c) 1999-2014 OPEN CASCADE SAS
//
// This file is part of Open CASCADE Technology software library.
//
// This library is free software; you can redistribute it and/or modify it under
// the terms of the GNU Lesser General Public License version 2.1 as published
// by the Free Software Foundation, with special exception defined in the file
// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT
// distribution for complete text of the license and disclaimer of any warranty.
//
// Alternatively, this file may be used under the terms of Open CASCADE
// commercial license or contractual agreement.

#include <PrsDim_AngleDimension.hxx>

#include <PrsDim.hxx>
#include <BRepBuilderAPI_MakeFace.hxx>
#include <BRepAdaptor_Curve.hxx>
#include <BRepAdaptor_Surface.hxx>
#include <BRepLib_MakeVertex.hxx>
#include <BRep_Tool.hxx>
#include <ElCLib.hxx>
#include <GCPnts_UniformAbscissa.hxx>
#include <GC_MakeArcOfCircle.hxx>
#include <gce_MakeLin2d.hxx>
#include <gce_MakeLin.hxx>
#include <gce_MakeCirc.hxx>
#include <gce_MakeCone.hxx>
#include <gce_MakePln.hxx>
#include <gce_MakeDir.hxx>
#include <Geom_Circle.hxx>
#include <Geom_TrimmedCurve.hxx>
#include <Geom_ConicalSurface.hxx>
#include <Geom_SurfaceOfRevolution.hxx>
#include <Geom_OffsetSurface.hxx>
#include <Graphic3d_ArrayOfSegments.hxx>
#include <Graphic3d_Group.hxx>
#include <Graphic3d_ArrayOfPolylines.hxx>
#include <IntAna2d_AnaIntersection.hxx>
#include <ProjLib.hxx>
#include <Prs3d_ShadingAspect.hxx>
#include <PrsMgr_PresentationManager.hxx>
#include <Select3D_SensitiveGroup.hxx>
#include <Select3D_SensitiveSegment.hxx>
#include <SelectMgr_Selection.hxx>
#include <Standard_ProgramError.hxx>
#include <Geom_Line.hxx>
#include <Geom_Plane.hxx>

IMPLEMENT_STANDARD_RTTIEXT(PrsDim_AngleDimension, PrsDim_Dimension)

namespace
{
  static const TCollection_ExtendedString THE_EMPTY_LABEL_STRING;
  static const Standard_Real              THE_EMPTY_LABEL_WIDTH = 0.0;
  static const Standard_ExtCharacter      THE_DEGREE_SYMBOL (0x00B0);
  static const Standard_Real              THE_3D_TEXT_MARGIN = 0.1;

  //! Returns true if the given points lie on a same line.
  static Standard_Boolean isSameLine (const gp_Pnt& theFirstPoint,
                                      const gp_Pnt& theCenterPoint,
                                      const gp_Pnt& theSecondPoint)
  {
    gp_Vec aVec1 (theFirstPoint, theCenterPoint);
    gp_Vec aVec2 (theCenterPoint, theSecondPoint);

    return aVec1.IsParallel (aVec2, Precision::Angular());
  }
}

//=======================================================================
//function : Constructor
//purpose  : 
//=======================================================================
PrsDim_AngleDimension::PrsDim_AngleDimension (const TopoDS_Edge& theFirstEdge,
                                              const TopoDS_Edge& theSecondEdge)
: PrsDim_Dimension (PrsDim_KOD_PLANEANGLE)
{
  Init();
  SetMeasuredGeometry (theFirstEdge, theSecondEdge);
}

//=======================================================================
//function : Constructor
//purpose  : 
//=======================================================================
PrsDim_AngleDimension::PrsDim_AngleDimension (const gp_Pnt& theFirstPoint,
                                              const gp_Pnt& theSecondPoint,
                                              const gp_Pnt& theThirdPoint)
: PrsDim_Dimension (PrsDim_KOD_PLANEANGLE)
{
  Init();
  SetMeasuredGeometry (theFirstPoint, theSecondPoint, theThirdPoint);
}

//=======================================================================
//function : Constructor
//purpose  : 
//=======================================================================
PrsDim_AngleDimension::PrsDim_AngleDimension (const TopoDS_Vertex& theFirstVertex,
                                              const TopoDS_Vertex& theSecondVertex,
                                              const TopoDS_Vertex& theThirdVertex)
: PrsDim_Dimension (PrsDim_KOD_PLANEANGLE)
{
  Init();
  SetMeasuredGeometry (theFirstVertex, theSecondVertex, theThirdVertex);
}

//=======================================================================
//function : Constructor
//purpose  : 
//=======================================================================
PrsDim_AngleDimension::PrsDim_AngleDimension (const TopoDS_Face& theCone)
: PrsDim_Dimension (PrsDim_KOD_PLANEANGLE)
{
  Init();
  SetMeasuredGeometry (theCone);
}

//=======================================================================
//function : Constructor
//purpose  : 
//=======================================================================
PrsDim_AngleDimension::PrsDim_AngleDimension (const TopoDS_Face& theFirstFace,
                                              const TopoDS_Face& theSecondFace)
: PrsDim_Dimension (PrsDim_KOD_PLANEANGLE)
{
  Init();
  SetMeasuredGeometry (theFirstFace, theSecondFace);
}

//=======================================================================
//function : Constructor
//purpose  : 
//=======================================================================
PrsDim_AngleDimension::PrsDim_AngleDimension (const TopoDS_Face& theFirstFace,
                                              const TopoDS_Face& theSecondFace,
                                              const gp_Pnt& thePoint)
: PrsDim_Dimension (PrsDim_KOD_PLANEANGLE)
{
  Init();
  SetMeasuredGeometry (theFirstFace, theSecondFace, thePoint);
}

//=======================================================================
//function : SetMeasuredGeometry
//purpose  : 
//=======================================================================
void PrsDim_AngleDimension::SetMeasuredGeometry (const TopoDS_Edge& theFirstEdge,
                                                 const TopoDS_Edge& theSecondEdge)
{
  gp_Pln aComputedPlane;

  myFirstShape      = theFirstEdge;
  mySecondShape     = theSecondEdge;
  myThirdShape      = TopoDS_Shape();
  myGeometryType    = GeometryType_Edges;
  myIsGeometryValid = InitTwoEdgesAngle (aComputedPlane);

  if (myIsGeometryValid && !myIsPlaneCustom)
  {
    myPlane = aComputedPlane;
  }

  SetToUpdate();
}

//=======================================================================
//function : SetMeasuredGeometry
//purpose  : 
//=======================================================================
void PrsDim_AngleDimension::SetMeasuredGeometry (const gp_Pnt& theFirstPoint,
                                                 const gp_Pnt& theSecondPoint,
                                                 const gp_Pnt& theThirdPoint)
{
  myFirstPoint    = theFirstPoint;
  myCenterPoint   = theSecondPoint;
  mySecondPoint   = theThirdPoint;
  myFirstShape    = BRepLib_MakeVertex (myFirstPoint);
  mySecondShape   = BRepLib_MakeVertex (myCenterPoint);
  myThirdShape    = BRepLib_MakeVertex (mySecondPoint);
  myGeometryType  = GeometryType_Points;
  myIsGeometryValid       = IsValidPoints (myFirstPoint, myCenterPoint, mySecondPoint);

  Standard_Boolean anIsSameLine = isSameLine (myFirstPoint, myCenterPoint, mySecondPoint);
  if (myIsGeometryValid && !myIsPlaneCustom && !anIsSameLine)
  {
    ComputePlane();
  }

  SetToUpdate();
}

//=======================================================================
//function : SetMeasuredGeometry
//purpose  : 
//=======================================================================
void PrsDim_AngleDimension::SetMeasuredGeometry (const TopoDS_Vertex& theFirstVertex,
                                                 const TopoDS_Vertex& theSecondVertex,
                                                 const TopoDS_Vertex& theThirdVertex)
{
  myFirstShape      = theFirstVertex;
  mySecondShape     = theSecondVertex;
  myThirdShape      = theThirdVertex;
  myFirstPoint      = BRep_Tool::Pnt (theFirstVertex);
  myCenterPoint     = BRep_Tool::Pnt (theSecondVertex);
  mySecondPoint     = BRep_Tool::Pnt (theThirdVertex);
  myGeometryType    = GeometryType_Points;
  myIsGeometryValid = IsValidPoints (myFirstPoint, myCenterPoint, mySecondPoint);

  Standard_Boolean anIsSameLine = isSameLine (myFirstPoint, myCenterPoint, mySecondPoint);
  if (myIsGeometryValid && !myIsPlaneCustom && !anIsSameLine)
  {
    ComputePlane();
  }

  SetToUpdate();
}

//=======================================================================
//function : SetMeasuredGeometry
//purpose  : 
//=======================================================================
void PrsDim_AngleDimension::SetMeasuredGeometry (const TopoDS_Face& theCone)
{
  myFirstShape      = theCone;
  mySecondShape     = TopoDS_Shape();
  myThirdShape      = TopoDS_Shape();
  myGeometryType    = GeometryType_Face;
  myIsGeometryValid = InitConeAngle();

  if (myIsGeometryValid && !myIsPlaneCustom)
  {
    ComputePlane();
  }

  SetToUpdate();
}

//=======================================================================
//function : SetMeasuredGeometry
//purpose  : 
//=======================================================================
void PrsDim_AngleDimension::SetMeasuredGeometry (const TopoDS_Face& theFirstFace,
                                                 const TopoDS_Face& theSecondFace)
{
  myFirstShape      = theFirstFace;
  mySecondShape     = theSecondFace;
  myThirdShape      = TopoDS_Shape();
  myGeometryType    = GeometryType_Faces;
  myIsGeometryValid = InitTwoFacesAngle();

  if (myIsGeometryValid && !myIsPlaneCustom)
  {
    ComputePlane();
  }

  SetToUpdate();
}

//=======================================================================
//function : SetMeasuredGeometry
//purpose  : 
//=======================================================================
void PrsDim_AngleDimension::SetMeasuredGeometry (const TopoDS_Face& theFirstFace,
                                                 const TopoDS_Face& theSecondFace,
                                                 const gp_Pnt& thePoint)
{
  myFirstShape      = theFirstFace;
  mySecondShape     = theSecondFace;
  myThirdShape      = TopoDS_Shape();
  myGeometryType    = GeometryType_Faces;
  myIsGeometryValid = InitTwoFacesAngle (thePoint);

  if (myIsGeometryValid && !myIsPlaneCustom)
  {
    ComputePlane();
  }

  SetToUpdate();
}

//=======================================================================
//function : Init
//purpose  : 
//=======================================================================
void PrsDim_AngleDimension::Init()
{
  SetType (PrsDim_TypeOfAngle_Interior);
  SetArrowsVisibility (PrsDim_TypeOfAngleArrowVisibility_Both);
  SetSpecialSymbol (THE_DEGREE_SYMBOL);
  SetDisplaySpecialSymbol (PrsDim_DisplaySpecialSymbol_After);
  SetFlyout (15.0);
}

//=======================================================================
//function: GetCenterOnArc
//purpose :
//=======================================================================
gp_Pnt PrsDim_AngleDimension::GetCenterOnArc (const gp_Pnt& theFirstAttach,
                                              const gp_Pnt& theSecondAttach,
                                              const gp_Pnt& theCenter) const
{
  // construct plane where the circle and the arc are located
  gce_MakePln aConstructPlane (theFirstAttach, theSecondAttach, theCenter);
  if (!aConstructPlane.IsDone())
  {
    return gp::Origin();
  }
  
  gp_Pln aPlane = aConstructPlane.Value();
  // to have an exterior angle presentation, a plane for further constructed circle should be reversed
  if (myType == PrsDim_TypeOfAngle_Exterior)
  {
    gp_Ax1 anAxis = aPlane.Axis();
    gp_Dir aDir = anAxis.Direction();
    aDir.Reverse();
    aPlane.SetAxis(gp_Ax1(anAxis.Location(), aDir));
  }

  Standard_Real aRadius = theFirstAttach.Distance (theCenter);

  // construct circle forming the arc
  gce_MakeCirc aConstructCircle (theCenter, aPlane, aRadius);
  if (!aConstructCircle.IsDone())
  {
    return gp::Origin();
  }

  gp_Circ aCircle = aConstructCircle.Value();

  // compute angle parameters of arc end-points on circle
  Standard_Real aParamBeg = ElCLib::Parameter (aCircle, theFirstAttach);
  Standard_Real aParamEnd = ElCLib::Parameter (aCircle, theSecondAttach);
  ElCLib::AdjustPeriodic (0.0, M_PI * 2, Precision::PConfusion(), aParamBeg, aParamEnd);

  return ElCLib::Value ((aParamBeg + aParamEnd) * 0.5, aCircle);
}

//=======================================================================
//function : GetNormalForMinAngle
//purpose  :
//=======================================================================
gp_Dir PrsDim_AngleDimension::GetNormalForMinAngle() const
{
  const gp_Dir& aNormal = myPlane.Axis().Direction();
  gp_Dir aFirst (gp_Vec (myCenterPoint, myFirstPoint) );
  gp_Dir aSecond (gp_Vec (myCenterPoint, mySecondPoint) );

  return aFirst.AngleWithRef (aSecond, aNormal) < 0.0
    ? aNormal.Reversed()
    : aNormal;
}

//=======================================================================
//function : DrawArc
//purpose  : draws the arc between two attach points
//=======================================================================
void PrsDim_AngleDimension::DrawArc (const Handle(Prs3d_Presentation)& thePresentation,
                                     const gp_Pnt& theFirstAttach,
                                     const gp_Pnt& theSecondAttach,
                                     const gp_Pnt& theCenter,
                                     const Standard_Real theRadius,
                                     const Standard_Integer theMode)
{
  gp_Pln aPlane (myCenterPoint, GetNormalForMinAngle());

  // to have an exterior angle presentation, a plane for further constructed circle should be reversed
  if (myType == PrsDim_TypeOfAngle_Exterior)
  {
    gp_Ax1 anAxis = aPlane.Axis();
    gp_Dir aDir = anAxis.Direction();
    aDir.Reverse();
    aPlane.SetAxis(gp_Ax1(anAxis.Location(), aDir));
  }

  // construct circle forming the arc
  gce_MakeCirc aConstructCircle (theCenter, aPlane, theRadius);
  if (!aConstructCircle.IsDone())
  {
    return;
  }

  gp_Circ aCircle = aConstructCircle.Value();

  // construct the arc
  GC_MakeArcOfCircle aConstructArc (aCircle, theFirstAttach, theSecondAttach, Standard_True);
  if (!aConstructArc.IsDone())
  {
    return;
  }

  // generate points with specified deflection
  const Handle(Geom_TrimmedCurve)& anArcCurve = aConstructArc.Value();
  
  GeomAdaptor_Curve anArcAdaptor (anArcCurve, anArcCurve->FirstParameter(), anArcCurve->LastParameter());

  // compute number of discretization elements in old-fanshioned way
  gp_Vec aCenterToFirstVec  (theCenter, theFirstAttach);
  gp_Vec aCenterToSecondVec (theCenter, theSecondAttach);
  Standard_Real anAngle = aCenterToFirstVec.Angle (aCenterToSecondVec);
  if (myType == PrsDim_TypeOfAngle_Exterior)
    anAngle = 2.0 * M_PI - anAngle;
  // it sets 50 points on PI, and a part of points if angle is less
  const Standard_Integer aNbPoints = Max (4, Standard_Integer (50.0 * anAngle / M_PI));

  GCPnts_UniformAbscissa aMakePnts (anArcAdaptor, aNbPoints);
  if (!aMakePnts.IsDone())
  {
    return;
  }

  // init data arrays for graphical and selection primitives
  Handle(Graphic3d_ArrayOfPolylines) aPrimSegments = new Graphic3d_ArrayOfPolylines (aNbPoints);

  SelectionGeometry::Curve& aSensitiveCurve = mySelectionGeom.NewCurve();

  // load data into arrays
  for (Standard_Integer aPntIt = 1; aPntIt <= aMakePnts.NbPoints(); ++aPntIt)
  {
    gp_Pnt aPnt = anArcAdaptor.Value (aMakePnts.Parameter (aPntIt));

    aPrimSegments->AddVertex (aPnt);

    aSensitiveCurve.Append (aPnt);
  }

  // add display presentation
  if (!myDrawer->DimensionAspect()->IsText3d() && theMode == ComputeMode_All)
  {
    thePresentation->CurrentGroup()->SetStencilTestOptions (Standard_True);
  }
  Handle(Graphic3d_AspectLine3d) aDimensionLineStyle = myDrawer->DimensionAspect()->LineAspect()->Aspect();
  thePresentation->CurrentGroup()->SetPrimitivesAspect (aDimensionLineStyle);
  thePresentation->CurrentGroup()->AddPrimitiveArray (aPrimSegments);
  if (!myDrawer->DimensionAspect()->IsText3d() && theMode == ComputeMode_All)
  {
    thePresentation->CurrentGroup()->SetStencilTestOptions (Standard_False);
  }
}

//=======================================================================
//function: DrawArcWithText
//purpose :
//=======================================================================
void PrsDim_AngleDimension::DrawArcWithText (const Handle(Prs3d_Presentation)& thePresentation,
                                             const gp_Pnt& theFirstAttach,
                                             const gp_Pnt& theSecondAttach,
                                             const gp_Pnt& theCenter,
                                             const TCollection_ExtendedString& theText,
                                             const Standard_Real theTextWidth,
                                             const Standard_Integer theMode,
                                             const Standard_Integer theLabelPosition)
{
  gp_Pln aPlane (myCenterPoint, GetNormalForMinAngle());
  
  Standard_Real aRadius = theFirstAttach.Distance (myCenterPoint);

  // construct circle forming the arc
  gce_MakeCirc aConstructCircle (theCenter, aPlane, aRadius);
  if (!aConstructCircle.IsDone())
  {
    return;
  }

  gp_Circ aCircle = aConstructCircle.Value();

  // compute angle parameters of arc end-points on circle
  Standard_Real aParamBeg = ElCLib::Parameter (aCircle, theFirstAttach);
  Standard_Real aParamEnd = ElCLib::Parameter (aCircle, theSecondAttach);
  ElCLib::AdjustPeriodic (0.0, M_PI * 2, Precision::PConfusion(), aParamBeg, aParamEnd);

  // middle point of arc parameter on circle
  Standard_Real aParamMid = (aParamBeg + aParamEnd) * 0.5;

  // add text graphical primitives
  if (theMode == ComputeMode_All || theMode == ComputeMode_Text)
  {
    gp_Pnt aTextPos = ElCLib::Value (aParamMid, aCircle);
    gp_Dir aTextDir = gce_MakeDir (theFirstAttach, theSecondAttach);

    // Drawing text
    drawText (thePresentation,
              aTextPos,
              aTextDir,
              theText,
              theLabelPosition);
  }

  if (theMode != ComputeMode_All && theMode != ComputeMode_Line)
  {
    return;
  }

  Handle(Prs3d_DimensionAspect) aDimensionAspect = myDrawer->DimensionAspect();

  Standard_Boolean isLineBreak = aDimensionAspect->TextVerticalPosition() == Prs3d_DTVP_Center
                              && aDimensionAspect->IsText3d();

  if (isLineBreak)
  {
    // compute gap for label as parameteric size of sector on circle segment
    Standard_Real aSectorOfText = theTextWidth / aRadius;
    Standard_Real aTextBegin = aParamMid - aSectorOfText * 0.5;
    Standard_Real aTextEnd = aParamMid + aSectorOfText * 0.5;
    gp_Pnt aTextPntBeg = ElCLib::Value (aTextBegin, aCircle);
    gp_Pnt aTextPntEnd = ElCLib::Value (aTextEnd, aCircle);

    // Drawing arcs
    if (aTextBegin > aParamBeg)
    {
      DrawArc (thePresentation, theFirstAttach, aTextPntBeg, theCenter, aRadius, theMode);
    }
    if (aTextEnd < aParamEnd)
    {
      DrawArc (thePresentation, aTextPntEnd, theSecondAttach, theCenter, aRadius, theMode);
    }
  }
  else
  {
    DrawArc (thePresentation, theFirstAttach, theSecondAttach, theCenter, aRadius, theMode);
  }
}

//=======================================================================
//function : CheckPlane
//purpose  : 
//=======================================================================
Standard_Boolean PrsDim_AngleDimension::CheckPlane (const gp_Pln& thePlane)const
{
  if (!thePlane.Contains (myFirstPoint, Precision::Confusion()) &&
      !thePlane.Contains (mySecondPoint, Precision::Confusion()) &&
      !thePlane.Contains (myCenterPoint, Precision::Confusion()))
  {
    return Standard_False;
  }

  return Standard_True;
}

//=======================================================================
//function : ComputePlane
//purpose  : 
//=======================================================================
void PrsDim_AngleDimension::ComputePlane()
{
  if (!myIsGeometryValid)
  {
    return;
  }

  // Compute working plane so that Y axis is codirectional
  // with Y axis of text coordinate system (necessary for text alignment)
  gp_Vec aFirstVec   = gp_Vec (myCenterPoint, myFirstPoint);
  gp_Vec aSecondVec  = gp_Vec (myCenterPoint, mySecondPoint);
  gp_Vec aDirectionN = aSecondVec ^ aFirstVec;
  gp_Vec aDirectionY = aFirstVec + aSecondVec;
  gp_Vec aDirectionX = aDirectionY ^ aDirectionN;

  myPlane = gp_Pln (gp_Ax3 (myCenterPoint, gp_Dir (aDirectionN), gp_Dir (aDirectionX)));
}

//=======================================================================
//function : GetModelUnits
//purpose  :
//=======================================================================
const TCollection_AsciiString& PrsDim_AngleDimension::GetModelUnits() const
{
  return myDrawer->DimAngleModelUnits();
}

//=======================================================================
//function : GetDisplayUnits
//purpose  :
//=======================================================================
const TCollection_AsciiString& PrsDim_AngleDimension::GetDisplayUnits() const
{
  return myDrawer->DimAngleDisplayUnits();
}

//=======================================================================
//function : SetModelUnits
//purpose  :
//=======================================================================
void PrsDim_AngleDimension::SetModelUnits (const TCollection_AsciiString& theUnits)
{
  myDrawer->SetDimAngleModelUnits (theUnits);
}

//=======================================================================
//function : SetDisplayUnits
//purpose  :
//=======================================================================
void PrsDim_AngleDimension::SetDisplayUnits (const TCollection_AsciiString& theUnits)
{
  myDrawer->SetDimAngleDisplayUnits (theUnits);
}

//=======================================================================
//function : ComputeValue
//purpose  : 
//=======================================================================
Standard_Real PrsDim_AngleDimension::ComputeValue() const
{
  if (!IsValid())
  {
    return 0.0;
  }

  gp_Vec aVec1 (myCenterPoint, myFirstPoint);
  gp_Vec aVec2 (myCenterPoint, mySecondPoint);

  Standard_Real anAngle = aVec1.AngleWithRef (aVec2, GetNormalForMinAngle());

  return anAngle > 0.0 ? anAngle : (2.0 * M_PI + anAngle);
}

//=======================================================================
//function : Compute
//purpose  : Having three gp_Pnt points compute presentation
//=======================================================================
void PrsDim_AngleDimension::Compute (const Handle(PrsMgr_PresentationManager)& ,
                                     const Handle(Prs3d_Presentation)& thePresentation,
                                     const Standard_Integer theMode)
{
  mySelectionGeom.Clear (theMode);

  if (!IsValid())
  {
    return;
  }

  // Parameters for presentation
  Handle(Prs3d_DimensionAspect) aDimensionAspect = myDrawer->DimensionAspect();

  thePresentation->CurrentGroup()->SetPrimitivesAspect (aDimensionAspect->LineAspect()->Aspect());

  Standard_Real anArrowLength = aDimensionAspect->ArrowAspect()->Length();

  // prepare label string and compute its geometrical width
  Standard_Real aLabelWidth;
  TCollection_ExtendedString aLabelString = GetValueString (aLabelWidth);

  // add margins to label width
  if (aDimensionAspect->IsText3d())
  {
    aLabelWidth += aDimensionAspect->TextAspect()->Height() * THE_3D_TEXT_MARGIN * 2.0;
  }

  // Get parameters from aspect or adjust it according with custom text position
  Standard_Real anExtensionSize = aDimensionAspect->ExtensionSize();
  Prs3d_DimensionTextHorizontalPosition aHorisontalTextPos = aDimensionAspect->TextHorizontalPosition();

  if (IsTextPositionCustom())
  {
    AdjustParameters (myFixedTextPosition,anExtensionSize, aHorisontalTextPos, myFlyout);
  }

  // Handle user-defined and automatic arrow placement
  Standard_Boolean isArrowsExternal = Standard_False;
  Standard_Integer aLabelPosition = LabelPosition_None;

  FitTextAlignment (aHorisontalTextPos, aLabelPosition, isArrowsExternal);

  gp_Pnt aFirstAttach = myCenterPoint.Translated (gp_Vec(myCenterPoint, myFirstPoint).Normalized() * GetFlyout());
  gp_Pnt aSecondAttach = myCenterPoint.Translated (gp_Vec(myCenterPoint, mySecondPoint).Normalized() * GetFlyout());

  //Arrows positions and directions
  gp_Vec aWorkingPlaneDir (GetNormalForMinAngle());

  gp_Dir aFirstExtensionDir  = aWorkingPlaneDir.Reversed() ^ gp_Vec (myCenterPoint, aFirstAttach);
  gp_Dir aSecondExtensionDir = aWorkingPlaneDir            ^ gp_Vec (myCenterPoint, aSecondAttach);

  gp_Vec aFirstArrowVec  = gp_Vec (aFirstExtensionDir)  * anArrowLength;
  gp_Vec aSecondArrowVec = gp_Vec (aSecondExtensionDir) * anArrowLength;

  if (isArrowsExternal)
  {
    aFirstArrowVec.Reverse();
    aSecondArrowVec.Reverse();
  }

  gp_Pnt aFirstArrowBegin  (0.0, 0.0, 0.0);
  gp_Pnt aFirstArrowEnd    (0.0, 0.0, 0.0);
  gp_Pnt aSecondArrowBegin (0.0, 0.0, 0.0);
  gp_Pnt aSecondArrowEnd   (0.0, 0.0, 0.0);

  aFirstArrowBegin  = aFirstAttach;
  aSecondArrowBegin = aSecondAttach;
  aFirstArrowEnd    = aFirstAttach;
  aSecondArrowEnd   = aSecondAttach;

  if (aDimensionAspect->ArrowAspect()->IsZoomable())
  {
    aFirstArrowEnd.Translate (-aFirstArrowVec);
    aSecondArrowEnd.Translate (-aSecondArrowVec);
  }

  // Group1: stenciling text and the angle dimension arc
  thePresentation->NewGroup();

  Standard_Integer aHPosition = aLabelPosition & LabelPosition_HMask;

  // draw text label
  switch (aHPosition)
  {
    case LabelPosition_HCenter :
    {
      Standard_Boolean isLineBreak = aDimensionAspect->TextVerticalPosition() == Prs3d_DTVP_Center
                                  && aDimensionAspect->IsText3d();

      if (isLineBreak)
      {
        DrawArcWithText (thePresentation,
                         aFirstAttach,
                         aSecondAttach,
                         myCenterPoint,
                         aLabelString,
                         aLabelWidth,
                         theMode,
                         aLabelPosition);
        break;
      }

      // compute text primitives
      if (theMode == ComputeMode_All || theMode == ComputeMode_Text)
      {
        gp_Vec aDimensionDir (aFirstAttach, aSecondAttach);
        gp_Pnt aTextPos = IsTextPositionCustom() ? myFixedTextPosition
                                                : GetCenterOnArc (aFirstAttach, aSecondAttach, myCenterPoint);
        gp_Dir aTextDir = aDimensionDir;

        drawText (thePresentation,
                  aTextPos,
                  aTextDir,
                  aLabelString,
                  aLabelPosition);
      }

      if (theMode == ComputeMode_All || theMode == ComputeMode_Line)
      {
        DrawArc (thePresentation,
                 (isArrowsExternal || !isArrowVisible(PrsDim_TypeOfAngleArrowVisibility_First))  ? aFirstAttach  : aFirstArrowEnd,
                 (isArrowsExternal || !isArrowVisible(PrsDim_TypeOfAngleArrowVisibility_Second)) ? aSecondAttach : aSecondArrowEnd,
                 myCenterPoint,
                 Abs (GetFlyout()),
                 theMode);
      }
    }
    break;

    case LabelPosition_Left :
    {
      DrawExtension (thePresentation,
                     anExtensionSize,
                     (isArrowsExternal && isArrowVisible(PrsDim_TypeOfAngleArrowVisibility_First)) ? aFirstArrowEnd : aFirstAttach,
                     aFirstExtensionDir,
                     aLabelString,
                     aLabelWidth,
                     theMode,
                     aLabelPosition);
    }
    break;

    case LabelPosition_Right :
    {
      DrawExtension (thePresentation,
                     anExtensionSize,
                     (isArrowsExternal && isArrowVisible(PrsDim_TypeOfAngleArrowVisibility_Second)) ? aSecondArrowEnd : aSecondAttach,
                     aSecondExtensionDir,
                     aLabelString,
                     aLabelWidth,
                     theMode,
                     aLabelPosition);
    }
    break;
  }

  // dimension arc without text
  if ((theMode == ComputeMode_All || theMode == ComputeMode_Line) && aHPosition != LabelPosition_HCenter)
  {
    thePresentation->NewGroup();

    DrawArc (thePresentation,
             (isArrowsExternal || !isArrowVisible(PrsDim_TypeOfAngleArrowVisibility_First)) ? aFirstAttach  : aFirstArrowEnd,
             (isArrowsExternal || !isArrowVisible(PrsDim_TypeOfAngleArrowVisibility_Second)) ? aSecondAttach : aSecondArrowEnd,
             myCenterPoint,
             Abs(GetFlyout ()),
             theMode);
  }

  // arrows and arrow extensions
  if (theMode == ComputeMode_All || theMode == ComputeMode_Line)
  {
    thePresentation->NewGroup();

    if (isArrowVisible(PrsDim_TypeOfAngleArrowVisibility_First))
      DrawArrow (thePresentation, aFirstArrowBegin,  gp_Dir (aFirstArrowVec));
    if (isArrowVisible(PrsDim_TypeOfAngleArrowVisibility_Second))
      DrawArrow (thePresentation, aSecondArrowBegin, gp_Dir (aSecondArrowVec));
  }

  if ((theMode == ComputeMode_All || theMode == ComputeMode_Line) && isArrowsExternal)
  {
    thePresentation->NewGroup();

    if (aHPosition != LabelPosition_Left && isArrowVisible(PrsDim_TypeOfAngleArrowVisibility_First))
    {
      DrawExtension (thePresentation,
                     aDimensionAspect->ArrowTailSize(),
                     aFirstArrowEnd,
                     aFirstExtensionDir,
                     THE_EMPTY_LABEL_STRING,
                     THE_EMPTY_LABEL_WIDTH,
                     theMode,
                     LabelPosition_None);
    }

    if (aHPosition != LabelPosition_Right && isArrowVisible(PrsDim_TypeOfAngleArrowVisibility_Second))
    {
      DrawExtension (thePresentation,
                     aDimensionAspect->ArrowTailSize(),
                     aSecondArrowEnd,
                     aSecondExtensionDir,
                     THE_EMPTY_LABEL_STRING,
                     THE_EMPTY_LABEL_WIDTH,
                     theMode,
                     LabelPosition_None);
    }
  }

  // flyouts
  if (theMode == ComputeMode_All)
  {
    thePresentation->NewGroup();

    Handle(Graphic3d_ArrayOfSegments) aPrimSegments = new Graphic3d_ArrayOfSegments (4);
    aPrimSegments->AddVertex (myCenterPoint);
    aPrimSegments->AddVertex (aFirstAttach);
    aPrimSegments->AddVertex (myCenterPoint);
    aPrimSegments->AddVertex (aSecondAttach);

    Handle(Graphic3d_AspectLine3d) aFlyoutStyle = myDrawer->DimensionAspect()->LineAspect()->Aspect();
    thePresentation->CurrentGroup()->SetPrimitivesAspect (aFlyoutStyle);
    thePresentation->CurrentGroup()->AddPrimitiveArray (aPrimSegments);
  }

  mySelectionGeom.IsComputed = Standard_True;
}

//=======================================================================
//function : ComputeFlyoutSelection
//purpose  : computes selection for flyouts
//=======================================================================
void PrsDim_AngleDimension::ComputeFlyoutSelection (const Handle(SelectMgr_Selection)& theSelection,
                                                    const Handle(SelectMgr_EntityOwner)& theOwner)
{
  gp_Pnt aFirstAttach  = myCenterPoint.Translated (gp_Vec (myCenterPoint, myFirstPoint).Normalized()  * GetFlyout());
  gp_Pnt aSecondAttach = myCenterPoint.Translated (gp_Vec (myCenterPoint, mySecondPoint).Normalized() * GetFlyout());

  Handle(Select3D_SensitiveGroup) aSensitiveEntity = new Select3D_SensitiveGroup (theOwner);
  aSensitiveEntity->Add (new Select3D_SensitiveSegment (theOwner, myCenterPoint, aFirstAttach));
  aSensitiveEntity->Add (new Select3D_SensitiveSegment (theOwner, myCenterPoint, aSecondAttach));

  theSelection->Add (aSensitiveEntity);
}

//=======================================================================
//function : InitTwoEdgesAngle
//purpose  : 
//=======================================================================
Standard_Boolean PrsDim_AngleDimension::InitTwoEdgesAngle (gp_Pln& theComputedPlane)
{
  TopoDS_Edge aFirstEdge  = TopoDS::Edge (myFirstShape);
  TopoDS_Edge aSecondEdge = TopoDS::Edge (mySecondShape);

  BRepAdaptor_Curve aMakeFirstLine  (aFirstEdge);
  BRepAdaptor_Curve aMakeSecondLine (aSecondEdge);

  if (aMakeFirstLine.GetType() != GeomAbs_Line || aMakeSecondLine.GetType() != GeomAbs_Line)
  {
    return  Standard_False;
  }

  Handle(Geom_Line) aFirstLine  = new Geom_Line (aMakeFirstLine.Line());
  Handle(Geom_Line) aSecondLine = new Geom_Line (aMakeSecondLine.Line());

  gp_Lin aFirstLin  = aFirstLine->Lin();
  gp_Lin aSecondLin = aSecondLine->Lin();

  Standard_Boolean isParallelLines = aFirstLin.Direction().IsParallel (aSecondLin.Direction(), Precision::Angular());

  theComputedPlane = isParallelLines ? gp_Pln(gp::XOY())
                                     : gp_Pln (aSecondLin.Location(), gp_Vec (aFirstLin.Direction()) ^ gp_Vec (aSecondLin.Direction()));

  // Compute geometry for this plane and edges
  Standard_Boolean isInfinite1,isInfinite2;
  gp_Pnt aFirstPoint1, aLastPoint1, aFirstPoint2, aLastPoint2;
  Handle(Geom_Curve) aFirstCurve = aFirstLine, aSecondCurve = aSecondLine;
  if (!PrsDim::ComputeGeometry (aFirstEdge, aSecondEdge,
                                aFirstCurve, aSecondCurve,
                                aFirstPoint1, aLastPoint1,
                                aFirstPoint2, aLastPoint2,
                                isInfinite1, isInfinite2))
  {
    return Standard_False;
  }

  Standard_Boolean isSameLines = aFirstLin.Direction().IsEqual (aSecondLin.Direction(), Precision::Angular())
                              && aFirstLin.Location().IsEqual (aSecondLin.Location(),Precision::Confusion());

  // It can be the same gp_Lin geometry but the different begin and end parameters
  Standard_Boolean isSameEdges =
    (aFirstPoint1.IsEqual (aFirstPoint2, Precision::Confusion()) && aLastPoint1.IsEqual (aLastPoint2, Precision::Confusion()))
    || (aFirstPoint1.IsEqual (aLastPoint2, Precision::Confusion()) && aLastPoint1.IsEqual (aFirstPoint2, Precision::Confusion()));

  if (isParallelLines)
  {
    // Zero angle, it could not handle this geometry
    if (isSameLines && isSameEdges)
    {
      return Standard_False;
    }

    // Handle the case of Pi angle
    const Standard_Real aParam11 = ElCLib::Parameter (aFirstLin, aFirstPoint1);
    const Standard_Real aParam12 = ElCLib::Parameter (aFirstLin, aLastPoint1);
    const Standard_Real aParam21 = ElCLib::Parameter (aFirstLin, aFirstPoint2);
    const Standard_Real aParam22 = ElCLib::Parameter (aFirstLin, aLastPoint2);
    myCenterPoint = ElCLib::Value ( (Min (aParam11, aParam12) + Max (aParam21, aParam22)) * 0.5, aFirstLin);
    myFirstPoint = myCenterPoint.Translated (gp_Vec (aFirstLin.Direction()) * Abs (GetFlyout()));
    mySecondPoint = myCenterPoint.XYZ() + (aFirstLin.Direction().IsEqual (aSecondLin.Direction(), Precision::Angular())
      ? aFirstLin.Direction().Reversed().XYZ() * Abs (GetFlyout())
      : aSecondLin.Direction().XYZ() * Abs (GetFlyout()));
  }
  else
  {
    // Find intersection
    gp_Lin2d aFirstLin2d  = ProjLib::Project (theComputedPlane, aFirstLin);
    gp_Lin2d aSecondLin2d = ProjLib::Project (theComputedPlane, aSecondLin);

    IntAna2d_AnaIntersection anInt2d (aFirstLin2d, aSecondLin2d);
    gp_Pnt2d anIntersectPoint;
    if (!anInt2d.IsDone() || anInt2d.IsEmpty())
    {
      return Standard_False;
    }

    anIntersectPoint = gp_Pnt2d (anInt2d.Point(1).Value());
    myCenterPoint = ElCLib::To3d (theComputedPlane.Position().Ax2(), anIntersectPoint);

    if (isInfinite1 || isInfinite2)
    {
      myFirstPoint  = myCenterPoint.Translated (gp_Vec (aFirstLin.Direction()) * Abs (GetFlyout()));
      mySecondPoint = myCenterPoint.Translated (gp_Vec (aSecondLin.Direction()) * Abs (GetFlyout()));

      return IsValidPoints (myFirstPoint, myCenterPoint, mySecondPoint);
    }

    // |
    // | <- dimension should be here
    // *----
    myFirstPoint  = myCenterPoint.Distance (aFirstPoint1) > myCenterPoint.Distance (aLastPoint1)
                  ? aFirstPoint1
                  : aLastPoint1;

    mySecondPoint = myCenterPoint.Distance (aFirstPoint2) > myCenterPoint.Distance (aLastPoint2)
                  ? aFirstPoint2
                  : aLastPoint2;
  }

  return IsValidPoints (myFirstPoint, myCenterPoint, mySecondPoint);
}

//=======================================================================
//function : InitTwoFacesAngle
//purpose  : initialization of angle dimension between two faces
//=======================================================================
Standard_Boolean PrsDim_AngleDimension::InitTwoFacesAngle()
{
  TopoDS_Face aFirstFace = TopoDS::Face (myFirstShape);
  TopoDS_Face aSecondFace = TopoDS::Face (mySecondShape);

  gp_Dir aFirstDir, aSecondDir;
  gp_Pln aFirstPln, aSecondPln;
  Handle(Geom_Surface) aFirstBasisSurf, aSecondBasisSurf;
  PrsDim_KindOfSurface aFirstSurfType, aSecondSurfType;
  Standard_Real aFirstOffset, aSecondOffset;

  PrsDim::GetPlaneFromFace (aFirstFace, aFirstPln,
                            aFirstBasisSurf,aFirstSurfType,aFirstOffset);

  PrsDim::GetPlaneFromFace (aSecondFace, aSecondPln,
                            aSecondBasisSurf, aSecondSurfType, aSecondOffset);

  if (aFirstSurfType == PrsDim_KOS_Plane && aSecondSurfType == PrsDim_KOS_Plane)
  {
    //Planar faces angle
    Handle(Geom_Plane) aFirstPlane = Handle(Geom_Plane)::DownCast (aFirstBasisSurf);
    Handle(Geom_Plane) aSecondPlane = Handle(Geom_Plane)::DownCast (aSecondBasisSurf);
    return PrsDim::InitAngleBetweenPlanarFaces (aFirstFace, aSecondFace,
                                                myCenterPoint, myFirstPoint, mySecondPoint)
        && IsValidPoints (myFirstPoint, myCenterPoint, mySecondPoint);
  }
  else
  {
    // Curvilinear faces angle
    return PrsDim::InitAngleBetweenCurvilinearFaces (aFirstFace, aSecondFace,
                                                     aFirstSurfType, aSecondSurfType,
                                                     myCenterPoint, myFirstPoint, mySecondPoint)
       && IsValidPoints (myFirstPoint, myCenterPoint, mySecondPoint);
  }
}

//=======================================================================
//function : InitTwoFacesAngle
//purpose  : initialization of angle dimension between two faces
//=======================================================================
Standard_Boolean PrsDim_AngleDimension::InitTwoFacesAngle (const gp_Pnt& thePointOnFirstFace)
{
  TopoDS_Face aFirstFace = TopoDS::Face (myFirstShape);
  TopoDS_Face aSecondFace = TopoDS::Face (mySecondShape);

  gp_Dir aFirstDir, aSecondDir;
  gp_Pln aFirstPln, aSecondPln;
  Handle(Geom_Surface) aFirstBasisSurf, aSecondBasisSurf;
  PrsDim_KindOfSurface aFirstSurfType, aSecondSurfType;
  Standard_Real aFirstOffset, aSecondOffset;

  PrsDim::GetPlaneFromFace (aFirstFace, aFirstPln,
                            aFirstBasisSurf,aFirstSurfType,aFirstOffset);

  PrsDim::GetPlaneFromFace (aSecondFace, aSecondPln,
                            aSecondBasisSurf, aSecondSurfType, aSecondOffset);

  myFirstPoint = thePointOnFirstFace;
  if (aFirstSurfType == PrsDim_KOS_Plane && aSecondSurfType == PrsDim_KOS_Plane)
  {
    //Planar faces angle
    Handle(Geom_Plane) aFirstPlane = Handle(Geom_Plane)::DownCast (aFirstBasisSurf);
    Handle(Geom_Plane) aSecondPlane = Handle(Geom_Plane)::DownCast (aSecondBasisSurf);
    return PrsDim::InitAngleBetweenPlanarFaces (aFirstFace, aSecondFace,
                                                myCenterPoint, myFirstPoint, mySecondPoint,
                                                Standard_True)
        && IsValidPoints (myFirstPoint, myCenterPoint, mySecondPoint);
  }
  else
  {
    // Curvilinear faces angle
    return PrsDim::InitAngleBetweenCurvilinearFaces (aFirstFace, aSecondFace,
                                                     aFirstSurfType, aSecondSurfType,
                                                     myCenterPoint, myFirstPoint, mySecondPoint,
                                                     Standard_True)
        && IsValidPoints (myFirstPoint, myCenterPoint, mySecondPoint);
  }
}

//=======================================================================
//function : InitConeAngle
//purpose  : initialization of the cone angle
//=======================================================================
Standard_Boolean PrsDim_AngleDimension::InitConeAngle()
{
  if (myFirstShape.IsNull())
  {
    return Standard_False;
  }

  TopoDS_Face aConeShape = TopoDS::Face (myFirstShape);
  gp_Pln aPln;
  gp_Cone aCone;
  gp_Circ aCircle;
  // A surface from the Face
  Handle(Geom_Surface) aSurf;
  Handle(Geom_OffsetSurface) aOffsetSurf; 
  Handle(Geom_ConicalSurface) aConicalSurf;
  Handle(Geom_SurfaceOfRevolution) aRevSurf;
  Handle(Geom_Line) aLine;
  BRepAdaptor_Surface aConeAdaptor (aConeShape);
  TopoDS_Face aFace;
  PrsDim_KindOfSurface aSurfType;
  Standard_Real anOffset = 0.;
  Handle(Standard_Type) aType;

  const Standard_Real aMaxV = aConeAdaptor.FirstVParameter();
  const Standard_Real aMinV = aConeAdaptor.LastVParameter();
  PrsDim::GetPlaneFromFace (aConeShape, aPln, aSurf, aSurfType, anOffset);
  if (aSurfType == PrsDim_KOS_Revolution)
  {
    // Surface of revolution
    aRevSurf = Handle(Geom_SurfaceOfRevolution)::DownCast(aSurf);
    gp_Lin aLin (aRevSurf->Axis());
    Handle(Geom_Curve) aBasisCurve = aRevSurf->BasisCurve();
    //Must be a part of line (basis curve should be linear)
    if (aBasisCurve ->DynamicType() != STANDARD_TYPE(Geom_Line))
      return Standard_False;

    gp_Pnt aFirst1 = aConeAdaptor.Value (0., aMinV);
    gp_Pnt aLast1 = aConeAdaptor.Value (0., aMaxV);
    gp_Vec aVec1 (aFirst1, aLast1);

    //Projection <aFirst> on <aLin>
    gp_Pnt aFirst2 = ElCLib::Value (ElCLib::Parameter (aLin, aFirst1), aLin);
    // Projection <aLast> on <aLin>
    gp_Pnt aLast2 = ElCLib::Value (ElCLib::Parameter (aLin, aLast1), aLin);

    gp_Vec aVec2 (aFirst2, aLast2);

    // Check if two parts of revolution are parallel (it's a cylinder) or normal (it's a circle).
    if (aVec1.IsParallel (aVec2, Precision::Angular())
        || aVec1.IsNormal (aVec2,Precision::Angular()))
      return Standard_False;

    gce_MakeCone aMkCone (aRevSurf->Axis(), aFirst1, aLast1);
    aCone =  aMkCone.Value();
    myCenterPoint = aCone.Apex();
  }
  else
  {
    aType = aSurf->DynamicType();
    if (aType == STANDARD_TYPE(Geom_OffsetSurface) || anOffset > 0.01)
    {
      // Offset surface
      aOffsetSurf = new Geom_OffsetSurface (aSurf, anOffset);
      aSurf = aOffsetSurf->Surface();
      BRepBuilderAPI_MakeFace aMkFace(aSurf, Precision::Confusion());
      aMkFace.Build();
      if (!aMkFace.IsDone())
        return Standard_False;
      aConeAdaptor.Initialize (aMkFace.Face());
    }
    aCone = aConeAdaptor.Cone();
    aConicalSurf = Handle(Geom_ConicalSurface)::DownCast (aSurf);
    myCenterPoint =  aConicalSurf->Apex();
  }

  // A circle where the angle is drawn
  Handle(Geom_Curve) aCurve;
  Standard_Real aMidV = ( aMinV + aMaxV ) / 2.5;
  aCurve = aSurf->VIso (aMidV);
  aCircle = Handle(Geom_Circle)::DownCast (aCurve)->Circ();

  aCurve = aSurf->VIso(aMaxV);
  gp_Circ aCircVmax = Handle(Geom_Circle)::DownCast(aCurve)->Circ();
  aCurve = aSurf->VIso(aMinV);
  gp_Circ aCircVmin = Handle(Geom_Circle)::DownCast(aCurve)->Circ();

  if (aCircVmax.Radius() < aCircVmin.Radius())
  {
   gp_Circ aTmpCirc = aCircVmax;
   aCircVmax = aCircVmin;
   aCircVmin = aTmpCirc;
  }

  myFirstPoint  = ElCLib::Value (0, aCircle);
  mySecondPoint = ElCLib::Value (M_PI, aCircle);
  return Standard_True;
}

//=======================================================================
//function : IsValidPoints
//purpose  : 
//=======================================================================
Standard_Boolean PrsDim_AngleDimension::IsValidPoints (const gp_Pnt& theFirstPoint,
                                                       const gp_Pnt& theCenterPoint,
                                                       const gp_Pnt& theSecondPoint) const
{
  return theFirstPoint.Distance (theCenterPoint) > Precision::Confusion()
      && theSecondPoint.Distance (theCenterPoint) > Precision::Confusion()
      && gp_Vec (theCenterPoint, theFirstPoint).Angle (
           gp_Vec (theCenterPoint, theSecondPoint)) > Precision::Angular();
}

//=======================================================================
//function : isArrowVisible
//purpose  : compares given and internal arrows types, returns true if the type should be shown
//=======================================================================
Standard_Boolean PrsDim_AngleDimension::isArrowVisible(const PrsDim_TypeOfAngleArrowVisibility theArrowType) const
{
  switch (theArrowType)
  {
    case PrsDim_TypeOfAngleArrowVisibility_Both:
      return myArrowsVisibility == PrsDim_TypeOfAngleArrowVisibility_Both;
    case PrsDim_TypeOfAngleArrowVisibility_First:
      return myArrowsVisibility == PrsDim_TypeOfAngleArrowVisibility_Both || myArrowsVisibility == PrsDim_TypeOfAngleArrowVisibility_First;
    case PrsDim_TypeOfAngleArrowVisibility_Second:
      return myArrowsVisibility == PrsDim_TypeOfAngleArrowVisibility_Both || myArrowsVisibility == PrsDim_TypeOfAngleArrowVisibility_Second;
    case PrsDim_TypeOfAngleArrowVisibility_None:
      return false;
  }
  return false;
}

//=======================================================================
//function : GetTextPosition
//purpose  : 
//=======================================================================
gp_Pnt PrsDim_AngleDimension::GetTextPosition() const
{
  if (!IsValid())
  {
    return gp::Origin();
  }

  if (IsTextPositionCustom())
  {
    return myFixedTextPosition;
  }

  // Counts text position according to the dimension parameters
  gp_Pnt aTextPosition (gp::Origin());

  Handle(Prs3d_DimensionAspect) aDimensionAspect = myDrawer->DimensionAspect();

  // Prepare label string and compute its geometrical width
  Standard_Real aLabelWidth;
  TCollection_ExtendedString aLabelString = GetValueString (aLabelWidth);

  gp_Pnt aFirstAttach = myCenterPoint.Translated (gp_Vec(myCenterPoint, myFirstPoint).Normalized() * GetFlyout());
  gp_Pnt aSecondAttach = myCenterPoint.Translated (gp_Vec(myCenterPoint, mySecondPoint).Normalized() * GetFlyout());

  // Handle user-defined and automatic arrow placement
  Standard_Boolean isArrowsExternal = Standard_False;
  Standard_Integer aLabelPosition = LabelPosition_None;
  FitTextAlignment (aDimensionAspect->TextHorizontalPosition(),
                    aLabelPosition, isArrowsExternal);

  // Get text position
  switch (aLabelPosition & LabelPosition_HMask)
  {
  case LabelPosition_HCenter:
    {
      aTextPosition = GetCenterOnArc (aFirstAttach, aSecondAttach, myCenterPoint);
    }
    break;
  case LabelPosition_Left:
    {
      gp_Dir aPlaneNormal = gp_Vec (aFirstAttach, aSecondAttach) ^ gp_Vec (myCenterPoint, aFirstAttach);
      gp_Dir anExtensionDir = aPlaneNormal ^ gp_Vec (myCenterPoint, aFirstAttach);
      Standard_Real anExtensionSize = aDimensionAspect->ExtensionSize();
      Standard_Real anOffset = isArrowsExternal
          ? anExtensionSize + aDimensionAspect->ArrowAspect()->Length()
          : anExtensionSize;
      gp_Vec anExtensionVec  = gp_Vec (anExtensionDir) * -anOffset;
      aTextPosition = aFirstAttach.Translated (anExtensionVec);
    }
    break;
  case LabelPosition_Right:
    {
      gp_Dir aPlaneNormal = gp_Vec (aFirstAttach, aSecondAttach) ^ gp_Vec (myCenterPoint, aFirstAttach);
      gp_Dir anExtensionDir = aPlaneNormal ^ gp_Vec (myCenterPoint, aSecondAttach);
      Standard_Real anExtensionSize = aDimensionAspect->ExtensionSize();
      Standard_Real anOffset = isArrowsExternal
          ? anExtensionSize + aDimensionAspect->ArrowAspect()->Length()
          : anExtensionSize;
      gp_Vec anExtensionVec  = gp_Vec (anExtensionDir) * anOffset;
      aTextPosition = aSecondAttach.Translated (anExtensionVec);
    }
    break;
  }

  return aTextPosition;
}

//=======================================================================
//function : SetTextPosition
//purpose  : 
//=======================================================================
void PrsDim_AngleDimension::SetTextPosition (const gp_Pnt& theTextPos)
{
  if (!IsValid())
  {
    return;
  }

  // The text position point for angle dimension should belong to the working plane.
  if (!GetPlane().Contains (theTextPos, Precision::Confusion()))
  {
    throw Standard_ProgramError("The text position point for angle dimension doesn't belong to the working plane.");
  }

  myIsTextPositionFixed = Standard_True;
  myFixedTextPosition = theTextPos;
}

//=======================================================================
//function : AdjustParameters
//purpose  : 
//=======================================================================
void PrsDim_AngleDimension::AdjustParameters (const gp_Pnt& theTextPos,
                                              Standard_Real& theExtensionSize,
                                              Prs3d_DimensionTextHorizontalPosition& theAlignment,
                                              Standard_Real& theFlyout) const
{
  Handle(Prs3d_DimensionAspect) aDimensionAspect = myDrawer->DimensionAspect();
  Standard_Real anArrowLength = aDimensionAspect->ArrowAspect()->Length();

  // Build circle with radius that is equal to distance from text position to the center point.
  Standard_Real aRadius = gp_Vec (myCenterPoint, theTextPos).Magnitude();

  // Set attach points in positive direction of the flyout.
  gp_Pnt aFirstAttach = myCenterPoint.Translated (gp_Vec (myCenterPoint, myFirstPoint).Normalized() * aRadius);
  gp_Pnt aSecondAttach = myCenterPoint.Translated (gp_Vec (myCenterPoint, mySecondPoint).Normalized() * aRadius);

  gce_MakeCirc aConstructCircle (myCenterPoint, GetPlane(), aRadius);
  if (!aConstructCircle.IsDone())
  {
    return;
  }
  gp_Circ aCircle = aConstructCircle.Value();

  // Default values
  theExtensionSize = aDimensionAspect->ArrowAspect()->Length();
  theAlignment = Prs3d_DTHP_Center;

  Standard_Real aParamBeg = ElCLib::Parameter (aCircle, aFirstAttach);
  Standard_Real aParamEnd = ElCLib::Parameter (aCircle, aSecondAttach);
  if (aParamEnd < aParamBeg)
  {
    Standard_Real aParam = aParamEnd;
    aParamEnd = aParamBeg;
    aParamBeg = aParam;
  }

  ElCLib::AdjustPeriodic (0.0, M_PI * 2, Precision::PConfusion(), aParamBeg, aParamEnd);
  Standard_Real aTextPar = ElCLib::Parameter (aCircle , theTextPos);

  // Horizontal center
  if (aTextPar > aParamBeg && aTextPar < aParamEnd)
  {
    theFlyout = aRadius;
    return;
  }

  aParamBeg += M_PI;
  aParamEnd += M_PI;
  ElCLib::AdjustPeriodic (0.0, M_PI * 2, Precision::PConfusion(), aParamBeg, aParamEnd);

  if (aTextPar > aParamBeg  && aTextPar < aParamEnd)
  {
    theFlyout = -aRadius;
    return;
  }

  // Text on the extensions
  gp_Lin aFirstLine = gce_MakeLin (myCenterPoint, myFirstPoint);
  gp_Lin aSecondLine = gce_MakeLin (myCenterPoint, mySecondPoint);
  gp_Pnt aFirstTextProj  = PrsDim::Nearest (aFirstLine,  theTextPos);
  gp_Pnt aSecondTextProj = PrsDim::Nearest (aSecondLine, theTextPos);
  Standard_Real aFirstDist = aFirstTextProj.Distance (theTextPos);
  Standard_Real aSecondDist = aSecondTextProj.Distance (theTextPos);

  if (aFirstDist <= aSecondDist)
  {
    aRadius = myCenterPoint.Distance (aFirstTextProj);
    Standard_Real aNewExtensionSize = aFirstDist - anArrowLength;
    theExtensionSize = aNewExtensionSize < 0.0 ? 0.0 : aNewExtensionSize;

    theAlignment = Prs3d_DTHP_Left;

    gp_Vec aPosFlyoutDir = gp_Vec (myCenterPoint, myFirstPoint).Normalized().Scaled (aRadius);

    theFlyout = aFirstTextProj.Distance (myCenterPoint.Translated (aPosFlyoutDir)) > Precision::Confusion()
                ? -aRadius : aRadius;
  }
  else
  {
    aRadius = myCenterPoint.Distance (aSecondTextProj);

    Standard_Real aNewExtensionSize = aSecondDist - anArrowLength;

    theExtensionSize = aNewExtensionSize < 0.0 ? 0.0 : aNewExtensionSize;

    theAlignment = Prs3d_DTHP_Right;

    gp_Vec aPosFlyoutDir = gp_Vec (myCenterPoint, mySecondPoint).Normalized().Scaled (aRadius);

    theFlyout = aSecondTextProj.Distance (myCenterPoint.Translated (aPosFlyoutDir)) > Precision::Confusion()
                ? -aRadius : aRadius;
  }
}

//=======================================================================
//function : FitTextAlignment
//purpose  : 
//=======================================================================
void PrsDim_AngleDimension::FitTextAlignment (const Prs3d_DimensionTextHorizontalPosition& theHorizontalTextPos,
                                              Standard_Integer& theLabelPosition,
                                              Standard_Boolean& theIsArrowsExternal) const
{
  Handle(Prs3d_DimensionAspect) aDimensionAspect = myDrawer->DimensionAspect();

  Standard_Real anArrowLength = aDimensionAspect->ArrowAspect()->Length();

  // Prepare label string and compute its geometrical width
  Standard_Real aLabelWidth;
  TCollection_ExtendedString aLabelString = GetValueString (aLabelWidth);

  // add margins to label width
  if (aDimensionAspect->IsText3d())
  {
    aLabelWidth += aDimensionAspect->TextAspect()->Height() * THE_3D_TEXT_MARGIN * 2.0;
  }

  gp_Pnt aFirstAttach = myCenterPoint.Translated (gp_Vec (myCenterPoint, myFirstPoint).Normalized() * GetFlyout());
  gp_Pnt aSecondAttach = myCenterPoint.Translated (gp_Vec (myCenterPoint, mySecondPoint).Normalized() * GetFlyout());

  // Handle user-defined and automatic arrow placement
  switch (aDimensionAspect->ArrowOrientation())
  {
    case Prs3d_DAO_External: theIsArrowsExternal = true; break;
    case Prs3d_DAO_Internal: theIsArrowsExternal = false; break;
    case Prs3d_DAO_Fit:
    {
      gp_Vec anAttachVector (aFirstAttach, aSecondAttach);
      Standard_Real aDimensionWidth = anAttachVector.Magnitude();

      // Add margin to ensure a small tail between text and arrow
      Standard_Real anArrowMargin   = aDimensionAspect->IsText3d() 
                                    ? aDimensionAspect->TextAspect()->Height() * THE_3D_TEXT_MARGIN
                                    : 0.0;

      Standard_Real anArrowsWidth   = (anArrowLength + anArrowMargin) * 2.0;

      theIsArrowsExternal = aDimensionWidth < aLabelWidth + anArrowsWidth;
      break;
    }
  }

  // Handle user-defined and automatic text placement
  switch (theHorizontalTextPos)
  {
    case Prs3d_DTHP_Left  : theLabelPosition |= LabelPosition_Left; break;
    case Prs3d_DTHP_Right : theLabelPosition |= LabelPosition_Right; break;
    case Prs3d_DTHP_Center: theLabelPosition |= LabelPosition_HCenter; break;
    case Prs3d_DTHP_Fit:
    {
      gp_Vec anAttachVector (aFirstAttach, aSecondAttach);
      Standard_Real aDimensionWidth = anAttachVector.Magnitude();
      Standard_Real anArrowsWidth   = anArrowLength * 2.0;
      Standard_Real aContentWidth   = theIsArrowsExternal ? aLabelWidth : aLabelWidth + anArrowsWidth;

      theLabelPosition |= aDimensionWidth < aContentWidth ? LabelPosition_Left : LabelPosition_HCenter;
      break;
    }
  }

  switch (aDimensionAspect->TextVerticalPosition())
  {
    case Prs3d_DTVP_Above  : theLabelPosition |= LabelPosition_Above; break;
    case Prs3d_DTVP_Below  : theLabelPosition |= LabelPosition_Below; break;
    case Prs3d_DTVP_Center : theLabelPosition |= LabelPosition_VCenter; break;
  }
}
