// 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 <ApproxInt_KnotTools.hxx>
#include <Geom2d_BSplineCurve.hxx>
#include <GeomInt_TheMultiLineOfWLApprox.hxx>
#include <GeomInt_WLApprox.hxx>
#include <Geom_BSplineCurve.hxx>
#include <NCollection_Sequence.hxx>
#include <NCollection_Vector.hxx>
#include <PLib.hxx>
#include <Precision.hxx>
#include <TColStd_Array1OfReal.hxx>
#include <TColgp_Array1OfPnt.hxx>
#include <TColgp_Array1OfPnt2d.hxx>
#include <math_Vector.hxx>

// (Sqrt(5.0) - 1.0) / 4.0
// static const Standard_Real aSinCoeff = 0.30901699437494742410229341718282;
static const Standard_Real aSinCoeff2 = 0.09549150281252627; // aSinCoeff^2 = (3. - Sqrt(5.)) / 8.
static const Standard_Integer aMaxPntCoeff = 15;

//=================================================================================================
// function : EvalCurv
// purpose  : Evaluate curvature in dim-dimension point.
//=================================================================================================

static Standard_Real EvalCurv(const Standard_Real  dim,
                              const Standard_Real* V1,
                              const Standard_Real* V2)
{
  // Really V1 and V2 are arrays of size dim;
  // V1 is first derivative, V2 is second derivative
  // of n-dimension curve
  // Curvature is curv = |V1^V2|/|V1|^3
  // V1^V2 is outer product of two vectors:
  // P(i,j) = V1(i)*V2(j) - V1(j)*V2(i);
  Standard_Real    mp = 0.;
  Standard_Integer i, j;
  Standard_Real    p;
  for (i = 1; i < dim; ++i)
  {
    for (j = 0; j < i; ++j)
    {
      p = V1[i] * V2[j] - V1[j] * V2[i];
      mp += p * p;
    }
  }
  //
  Standard_Real q = 0.;
  for (i = 0; i < dim; ++i)
  {
    q += V1[i] * V1[i];
  }

  if (q < 1 / Precision::Infinite())
  {
    // Indeed, if q is small then we can
    // obtain equivocation of "0/0" type.
    // In this case, local curvature can be
    // not equal to 0 or Infinity.
    // However, it is good solution to insert
    // knot in the place with such singularity.
    // Therefore, we need imitation of curvature
    // jumping. Return of Precision::Infinite() is
    // enough for it.

    return Precision::Infinite();
  }

  q = Min(q, Precision::Infinite());
  q *= q * q;

  //
  Standard_Real curv = Sqrt(mp / q);

  return curv;
}

//=================================================================================================

void ApproxInt_KnotTools::BuildCurvature(const NCollection_LocalArray<Standard_Real>& theCoords,
                                         const Standard_Integer                       theDim,
                                         const math_Vector&                           thePars,
                                         TColStd_Array1OfReal&                        theCurv,
                                         Standard_Real&                               theMaxCurv)
{
  // Arrays are allocated for max theDim = 7: 1 3d curve + 2 2d curves.
  Standard_Real    Val[21], Par[3], Res[21];
  Standard_Integer i, j, m, ic;
  Standard_Integer dim = theDim;
  //
  theMaxCurv = 0.;
  if (theCurv.Length() < 3)
  {
    theCurv.Init(0.);
    return;
  }
  i = theCurv.Lower();
  for (j = 0; j < 3; ++j)
  {
    Standard_Integer k = i + j;
    ic                 = (k - theCurv.Lower()) * dim;
    Standard_Integer l = dim * j;
    for (m = 0; m < dim; ++m)
    {
      Val[l + m] = theCoords[ic + m];
    }
    Par[j] = thePars(k);
  }
  PLib::EvalLagrange(Par[0], 2, 2, dim, *Val, *Par, *Res);
  //
  theCurv(i) = EvalCurv(dim, &Res[dim], &Res[2 * dim]);
  //
  if (theCurv(i) > theMaxCurv)
  {
    theMaxCurv = theCurv(i);
  }
  //
  for (i = theCurv.Lower() + 1; i < theCurv.Upper(); ++i)
  {
    for (j = 0; j < 3; ++j)
    {
      Standard_Integer k = i + j - 1;
      ic                 = (k - theCurv.Lower()) * dim;
      Standard_Integer l = dim * j;
      for (m = 0; m < dim; ++m)
      {
        Val[l + m] = theCoords[ic + m];
      }
      Par[j] = thePars(k);
    }
    PLib::EvalLagrange(Par[1], 2, 2, dim, *Val, *Par, *Res);
    //
    theCurv(i) = EvalCurv(dim, &Res[dim], &Res[2 * dim]);
    if (theCurv(i) > theMaxCurv)
    {
      theMaxCurv = theCurv(i);
    }
  }
  //
  i = theCurv.Upper();
  for (j = 0; j < 3; ++j)
  {
    Standard_Integer k = i + j - 2;
    ic                 = (k - theCurv.Lower()) * dim;
    Standard_Integer l = dim * j;
    for (m = 0; m < dim; ++m)
    {
      Val[l + m] = theCoords[ic + m];
    }
    Par[j] = thePars(k);
  }
  PLib::EvalLagrange(Par[2], 2, 2, dim, *Val, *Par, *Res);
  //
  theCurv(i) = EvalCurv(dim, &Res[dim], &Res[2 * dim]);
  if (theCurv(i) > theMaxCurv)
  {
    theMaxCurv = theCurv(i);
  }
}

//=================================================================================================

void ApproxInt_KnotTools::ComputeKnotInds(const NCollection_LocalArray<Standard_Real>& theCoords,
                                          const Standard_Integer                       theDim,
                                          const math_Vector&                           thePars,
                                          NCollection_Sequence<Standard_Integer>&      theInds)
{
  // I: Create discrete curvature.
  NCollection_Sequence<Standard_Integer> aFeatureInds;
  TColStd_Array1OfReal                   aCurv(thePars.Lower(), thePars.Upper());
  Standard_Real                          aMaxCurv = 0.;
  BuildCurvature(theCoords, theDim, thePars, aCurv, aMaxCurv);
  //
  Standard_Integer i, j, dim = theDim;
#ifdef APPROXINT_KNOTTOOLS_DEBUG
  std::cout << "Discrete curvature array is" << std::endl;
  for (i = aCurv.Lower(); i <= aCurv.Upper(); ++i)
  {
    std::cout << i << " " << aCurv(i) << std::endl;
  }
#endif

  theInds.Append(aCurv.Lower());
  if (aMaxCurv <= Precision::Confusion())
  {
    // Linear case.
    theInds.Append(aCurv.Upper());
    return;
  }

  // II: Find extremas of curvature.
  // Not used Precision::PConfusion, by different from "param space" eps nature.
  Standard_Real eps = 1.0e-9, eps1 = 1.0e3 * eps;
  for (i = aCurv.Lower() + 1; i < aCurv.Upper(); ++i)
  {
    Standard_Real d1 = aCurv(i) - aCurv(i - 1), d2 = aCurv(i) - aCurv(i + 1), ad1 = Abs(d1),
                  ad2 = Abs(d2);

    if (d1 * d2 > 0. && ad1 > eps && ad2 > eps)
    {
      if (i != theInds.Last())
      {
        theInds.Append(i);
        aFeatureInds.Append(i);
      }
    }
    else if ((ad1 < eps && ad2 > eps1) || (ad1 > eps1 && ad2 < eps))
    {
      if (i != theInds.Last())
      {
        theInds.Append(i);
        aFeatureInds.Append(i);
      }
    }
  }
  if (aCurv.Upper() != theInds.Last())
  {
    theInds.Append(aCurv.Upper());
  }

#if defined(APPROXINT_KNOTTOOLS_DEBUG)
  {
    std::cout << "Feature indices new: " << std::endl;
    i;
    for (i = theInds.Lower(); i <= theInds.Upper(); ++i)
    {
      std::cout << i << " : " << theInds(i) << std::endl;
    }
  }
#endif

  // III: Put knots in monotone intervals of curvature.
  Standard_Boolean Ok;
  i = 1;
  do
  {
    i++;
    //
    Ok = InsKnotBefI(i, aCurv, theCoords, dim, theInds, Standard_True);
    if (Ok)
    {
      i--;
    }
  } while (i < theInds.Length());

  // IV: Checking feature points.
  j = 2;
  for (i = 1; i <= aFeatureInds.Length(); ++i)
  {
    Standard_Integer anInd = aFeatureInds(i);
    for (; j <= theInds.Length() - 1;)
    {
      if (theInds(j) == anInd)
      {
        Standard_Integer anIndPrev = theInds(j - 1);
        Standard_Integer anIndNext = theInds(j + 1);
        Standard_Integer ici       = (anIndPrev - aCurv.Lower()) * theDim,
                         ici1      = (anIndNext - aCurv.Lower()) * theDim,
                         icm       = (anInd - aCurv.Lower()) * theDim;
        NCollection_LocalArray<Standard_Real> V1(theDim), V2(theDim);
        Standard_Real                         mp = 0., m1 = 0., m2 = 0.;
        Standard_Real                         p;
        for (Standard_Integer k = 0; k < theDim; ++k)
        {
          V1[k] = theCoords[icm + k] - theCoords[ici + k];
          m1 += V1[k] * V1[k];
          V2[k] = theCoords[ici1 + k] - theCoords[icm + k];
          m2 += V2[k] * V2[k];
        }
        for (Standard_Integer k = 1; k < theDim; ++k)
        {
          for (Standard_Integer l = 0; l < k; ++l)
          {
            p = V1[k] * V2[l] - V1[l] * V2[k];
            mp += p * p;
          }
        }
        // mp *= 2.; //P(j,i) = -P(i,j);
        //

        if (mp > aSinCoeff2 * m1 * m2) // Sqrt (mp/(m1*m2)) > aSinCoeff
        {
          // Insert new knots
          Standard_Real d1 = Abs(aCurv(anInd) - aCurv(anIndPrev));
          Standard_Real d2 = Abs(aCurv(anInd) - aCurv(anIndNext));
          if (d1 > d2)
          {
            Ok = InsKnotBefI(j, aCurv, theCoords, dim, theInds, Standard_False);
            if (Ok)
            {
              j++;
            }
            else
            {
              break;
            }
          }
          else
          {
            Ok = InsKnotBefI(j + 1, aCurv, theCoords, dim, theInds, Standard_False);
            if (!Ok)
            {
              break;
            }
          }
        }
        else
        {
          j++;
          break;
        }
      }
      else
      {
        j++;
      }
    }
  }
  //
}

//=================================================================================================

void ApproxInt_KnotTools::FilterKnots(NCollection_Sequence<Standard_Integer>& theInds,
                                      const Standard_Integer                  theMinNbPnts,
                                      NCollection_Vector<Standard_Integer>&   theLKnots)
{
  // Maximum number of points per knot interval.
  Standard_Integer aMaxNbPnts = aMaxPntCoeff * theMinNbPnts;
  Standard_Integer i          = 1;
  Standard_Integer aMinNbStep = theMinNbPnts / 2;

  // I: Filter too big number of points per knot interval.
  while (i < theInds.Length())
  {
    Standard_Integer nbint = theInds(i + 1) - theInds(i) + 1;
    if (nbint <= aMaxNbPnts)
    {
      ++i;
      continue;
    }
    else
    {
      Standard_Integer ind = theInds(i) + nbint / 2;
      theInds.InsertAfter(i, ind);
    }
  }

  // II: Filter points with too small amount of points per knot interval.
  i = 1;
  theLKnots.Append(theInds(i));
  Standard_Integer anIndsPrev = theInds(i);
  for (i = 2; i <= theInds.Length(); ++i)
  {
    if (theInds(i) - anIndsPrev <= theMinNbPnts)
    {
      if (i != theInds.Length())
      {
        Standard_Integer anIdx = i + 1;
        for (; anIdx <= theInds.Length(); ++anIdx)
        {
          if (theInds(anIdx) - anIndsPrev >= theMinNbPnts)
            break;
        }
        anIdx--;

        Standard_Integer aMidIdx = (theInds(anIdx) + anIndsPrev) / 2;
        if (aMidIdx - anIndsPrev < theMinNbPnts && aMidIdx - theInds(anIdx) < theMinNbPnts
            && theInds(anIdx) - anIndsPrev >= aMinNbStep)
        {
          if (theInds(anIdx) - anIndsPrev > 2 * theMinNbPnts)
          {
            // Bad distribution points merge into one knot interval.
            theLKnots.Append(anIndsPrev + theMinNbPnts);
            anIndsPrev = anIndsPrev + theMinNbPnts;
            i          = anIdx - 1;
          }
          else
          {
            if (theInds(anIdx - 1) - anIndsPrev >= theMinNbPnts / 2)
            {
              // Bad distribution points merge into one knot interval.
              theLKnots.Append(theInds(anIdx - 1));
              anIndsPrev = theInds(anIdx - 1);
              i          = anIdx - 1;
              if (theInds(anIdx) - theInds(anIdx - 1) <= theMinNbPnts / 2)
              {
                theLKnots.SetValue(theLKnots.Upper(), theInds(anIdx));
                anIndsPrev = theInds(anIdx);
                i          = anIdx;
              }
            }
            else
            {
              // Bad distribution points merge into one knot interval.
              theLKnots.Append(theInds(anIdx));
              anIndsPrev = theInds(anIdx);
              i          = anIdx;
            }
          }
        }
        else if (anIdx == theInds.Upper() && // Last point obtained.
                 theLKnots.Length() >= 2)    // It is possible to modify last item.
        {
          // Current bad interval from i to last.
          // Trying to add knot to divide sequence on two parts:
          // Last good index -> Last index - theMinNbPnts -> Last index
          Standard_Integer aLastGoodIdx = theLKnots.Value(theLKnots.Upper() - 1);
          if (theInds.Last() - 2 * theMinNbPnts >= aLastGoodIdx)
          {
            theLKnots(theLKnots.Upper()) = theInds.Last() - theMinNbPnts;
            theLKnots.Append(theInds.Last());
            anIndsPrev = theInds(anIdx);
            i          = anIdx;
          }
        }
      } // if (i != theInds.Length())
      continue;
    }
    else
    {
      theLKnots.Append(theInds(i));
      anIndsPrev = theInds(i);
    }
  }

  // III: Fill Last Knot.
  if (theLKnots.Length() < 2)
  {
    theLKnots.Append(theInds.Last());
  }
  else
  {
    if (theLKnots.Last() < theInds.Last())
    {
      theLKnots(theLKnots.Upper()) = theInds.Last();
    }
  }
}

//=================================================================================================

Standard_Boolean ApproxInt_KnotTools::InsKnotBefI(
  const Standard_Integer                       theI,
  const TColStd_Array1OfReal&                  theCurv,
  const NCollection_LocalArray<Standard_Real>& theCoords,
  const Standard_Integer                       theDim,
  NCollection_Sequence<Standard_Integer>&      theInds,
  const Standard_Boolean                       ChkCurv)
{
  Standard_Integer anInd1 = theInds(theI);
  Standard_Integer anInd  = theInds(theI - 1);
  //
  if ((anInd1 - anInd) == 1)
  {
    return Standard_False;
  }
  //
  Standard_Real       curv                  = 0.5 * (theCurv(anInd) + theCurv(anInd1));
  Standard_Integer    mid                   = 0, j, jj;
  const Standard_Real aLimitCurvatureChange = 3.0;
  for (j = anInd + 1; j < anInd1; ++j)
  {
    mid = 0;

    // I: Curvature change criteria:
    // Non-null curvature.
    if (theCurv(j) > Precision::Confusion() && theCurv(anInd) > Precision::Confusion())
    {
      if (theCurv(j) / theCurv(anInd) > aLimitCurvatureChange
          || theCurv(j) / theCurv(anInd) < 1.0 / aLimitCurvatureChange)
      {
        // Curvature on current interval changed more than 3 times.
        mid = j;
        theInds.InsertBefore(theI, mid);
        return Standard_True;
      }
    }

    // II: Angular criteria:
    Standard_Real ac = theCurv(j - 1), ac1 = theCurv(j);
    if ((curv >= ac && curv <= ac1) || (curv >= ac1 && curv <= ac))
    {
      if (Abs(curv - ac) < Abs(curv - ac1))
      {
        mid = j - 1;
      }
      else
      {
        mid = j;
      }
    }
    if (mid == anInd)
    {
      mid++;
    }
    if (mid == anInd1)
    {
      mid--;
    }
    if (mid > 0)
    {
      if (ChkCurv)
      {
        Standard_Integer ici  = (anInd - theCurv.Lower()) * theDim,
                         ici1 = (anInd1 - theCurv.Lower()) * theDim,
                         icm  = (mid - theCurv.Lower()) * theDim;
        NCollection_LocalArray<Standard_Real> V1(theDim), V2(theDim);
        Standard_Integer                      i;
        Standard_Real                         mp = 0., m1 = 0., m2 = 0.;
        Standard_Real                         p;
        for (i = 0; i < theDim; ++i)
        {
          V1[i] = theCoords[icm + i] - theCoords[ici + i];
          m1 += V1[i] * V1[i];
          V2[i] = theCoords[ici1 + i] - theCoords[icm + i];
          m2 += V2[i] * V2[i];
        }
        for (i = 1; i < theDim; ++i)
        {
          for (jj = 0; jj < i; ++jj)
          {
            p = V1[i] * V2[jj] - V1[jj] * V2[i];
            mp += p * p;
          }
        }
        // mp *= 2.; //P(j,i) = -P(i,j);
        //

        if (mp > aSinCoeff2 * m1 * m2) // Sqrt (mp / m1m2) > aSinCoeff
        {
          theInds.InsertBefore(theI, mid);
          return Standard_True;
        }
      }
      else
      {
        theInds.InsertBefore(theI, mid);
        return Standard_True;
      }
    }
  }

  return Standard_False;
}

//=================================================================================================

void ApproxInt_KnotTools::BuildKnots(const TColgp_Array1OfPnt&             thePntsXYZ,
                                     const TColgp_Array1OfPnt2d&           thePntsU1V1,
                                     const TColgp_Array1OfPnt2d&           thePntsU2V2,
                                     const math_Vector&                    thePars,
                                     const Standard_Boolean                theApproxXYZ,
                                     const Standard_Boolean                theApproxU1V1,
                                     const Standard_Boolean                theApproxU2V2,
                                     const Standard_Integer                theMinNbPnts,
                                     NCollection_Vector<Standard_Integer>& theKnots)
{
  NCollection_Sequence<Standard_Integer> aKnots;
  Standard_Integer                       aDim = 0;

  // I: Convert input data to the corresponding format.
  if (theApproxXYZ)
    aDim += 3;
  if (theApproxU1V1)
    aDim += 2;
  if (theApproxU2V2)
    aDim += 2;

  NCollection_LocalArray<Standard_Real> aCoords(thePars.Length() * aDim);
  Standard_Integer                      i, j;
  for (i = thePars.Lower(); i <= thePars.Upper(); ++i)
  {
    j = (i - thePars.Lower()) * aDim;
    if (theApproxXYZ)
    {
      aCoords[j] = thePntsXYZ.Value(i).X();
      ++j;
      aCoords[j] = thePntsXYZ.Value(i).Y();
      ++j;
      aCoords[j] = thePntsXYZ.Value(i).Z();
      ++j;
    }
    if (theApproxU1V1)
    {
      aCoords[j] = thePntsU1V1.Value(i).X();
      ++j;
      aCoords[j] = thePntsU1V1.Value(i).Y();
      ++j;
    }
    if (theApproxU2V2)
    {
      aCoords[j] = thePntsU2V2.Value(i).X();
      ++j;
      aCoords[j] = thePntsU2V2.Value(i).Y();
      ++j;
    }
  }

  // II: Build draft knot sequence.
  ComputeKnotInds(aCoords, aDim, thePars, aKnots);

#if defined(APPROXINT_KNOTTOOLS_DEBUG)
  std::cout << "Draft knot sequence: " << std::endl;
  for (i = aKnots.Lower(); i <= aKnots.Upper(); ++i)
  {
    std::cout << i << " : " << aKnots(i) << std::endl;
  }
#endif

  // III: Build output knot sequence.
  FilterKnots(aKnots, theMinNbPnts, theKnots);

#if defined(APPROXINT_KNOTTOOLS_DEBUG)
  std::cout << "Result knot sequence: " << std::endl;
  for (i = theKnots.Lower(); i <= theKnots.Upper(); ++i)
  {
    std::cout << i << " : " << theKnots(i) << std::endl;
  }
#endif
}

//=================================================================================================

static Standard_Real MaxParamRatio(const math_Vector& thePars)
{
  Standard_Integer i;
  Standard_Real    aMaxRatio = 0.;
  //
  for (i = thePars.Lower() + 1; i < thePars.Upper(); ++i)
  {
    Standard_Real aRat = (thePars(i + 1) - thePars(i)) / (thePars(i) - thePars(i - 1));
    if (aRat < 1.)
      aRat = 1. / aRat;

    aMaxRatio = Max(aMaxRatio, aRat);
  }
  return aMaxRatio;
}

//=================================================================================================

Approx_ParametrizationType ApproxInt_KnotTools::DefineParType(const Handle(IntPatch_WLine)& theWL,
                                                              const Standard_Integer        theFpar,
                                                              const Standard_Integer        theLpar,
                                                              const Standard_Boolean theApproxXYZ,
                                                              const Standard_Boolean theApproxU1V1,
                                                              const Standard_Boolean theApproxU2V2)
{
  if (theLpar - theFpar == 1)
    return Approx_IsoParametric;

  const Standard_Integer nbp3d = theApproxXYZ ? 1 : 0,
                         nbp2d = (theApproxU1V1 ? 1 : 0) + (theApproxU2V2 ? 1 : 0);

  GeomInt_TheMultiLineOfWLApprox aTestLine(theWL,
                                           nbp3d,
                                           nbp2d,
                                           theApproxU1V1,
                                           theApproxU2V2,
                                           0.,
                                           0.,
                                           0.,
                                           0.,
                                           0.,
                                           0.,
                                           0.,
                                           theApproxU1V1,
                                           theFpar,
                                           theLpar);

  TColgp_Array1OfPnt   aTabPnt3d(1, Max(1, nbp3d));
  TColgp_Array1OfPnt2d aTabPnt2d(1, Max(1, nbp2d));
  TColgp_Array1OfPnt   aPntXYZ(theFpar, theLpar);
  TColgp_Array1OfPnt2d aPntU1V1(theFpar, theLpar);
  TColgp_Array1OfPnt2d aPntU2V2(theFpar, theLpar);

  Standard_Integer i, j;

  for (i = theFpar; i <= theLpar; ++i)
  {
    if (nbp3d != 0 && nbp2d != 0)
      aTestLine.Value(i, aTabPnt3d, aTabPnt2d);
    else if (nbp2d != 0)
      aTestLine.Value(i, aTabPnt2d);
    else if (nbp3d != 0)
      aTestLine.Value(i, aTabPnt3d);
    //
    if (nbp3d > 0)
    {
      aPntXYZ(i) = aTabPnt3d(1);
    }
    if (nbp2d > 1)
    {
      aPntU1V1(i) = aTabPnt2d(1);
      aPntU2V2(i) = aTabPnt2d(2);
    }
    else if (nbp2d > 0)
    {
      if (theApproxU1V1)
      {
        aPntU1V1(i) = aTabPnt2d(1);
      }
      else
      {
        aPntU2V2(i) = aTabPnt2d(1);
      }
    }
  }

  Standard_Integer aDim = 0;

  if (theApproxXYZ)
    aDim += 3;
  if (theApproxU1V1)
    aDim += 2;
  if (theApproxU2V2)
    aDim += 2;

  Standard_Integer                      aLength = theLpar - theFpar + 1;
  NCollection_LocalArray<Standard_Real> aCoords(aLength * aDim);
  for (i = theFpar; i <= theLpar; ++i)
  {
    j = (i - theFpar) * aDim;
    if (theApproxXYZ)
    {
      aCoords[j] = aPntXYZ.Value(i).X();
      ++j;
      aCoords[j] = aPntXYZ.Value(i).Y();
      ++j;
      aCoords[j] = aPntXYZ.Value(i).Z();
      ++j;
    }
    if (theApproxU1V1)
    {
      aCoords[j] = aPntU1V1.Value(i).X();
      ++j;
      aCoords[j] = aPntU1V1.Value(i).Y();
      ++j;
    }
    if (theApproxU2V2)
    {
      aCoords[j] = aPntU2V2.Value(i).X();
      ++j;
      aCoords[j] = aPntU2V2.Value(i).Y();
      ++j;
    }
  }

  // Analysis of curvature
  const Standard_Real        aCritRat    = 500.;
  const Standard_Real        aCritParRat = 100.;
  math_Vector                aPars(theFpar, theLpar);
  Approx_ParametrizationType aParType = Approx_ChordLength;
  GeomInt_WLApprox::Parameters(aTestLine, theFpar, theLpar, aParType, aPars);
  TColStd_Array1OfReal aCurv(aPars.Lower(), aPars.Upper());
  Standard_Real        aMaxCurv = 0.;
  BuildCurvature(aCoords, aDim, aPars, aCurv, aMaxCurv);

  if (aMaxCurv < Precision::PConfusion() || Precision::IsPositiveInfinite(aMaxCurv))
  {
    // Linear case
    return aParType;
  }

  Standard_Real aMidCurv = 0.;
  Standard_Real eps      = Epsilon(1.);
  j                      = 0;
  for (i = aCurv.Lower(); i <= aCurv.Upper(); ++i)
  {
    if (aMaxCurv - aCurv(i) < eps)
    {
      continue;
    }
    ++j;
    aMidCurv += aCurv(i);
  }

  if (j > 1)
  {
    aMidCurv /= j;
  }

  if (aMidCurv <= eps)
    return aParType;

  Standard_Real aRat = aMaxCurv / aMidCurv;

  if (aRat > aCritRat)
  {
    if (aRat > 5. * aCritRat)
      aParType = Approx_Centripetal;
    else
    {
      Standard_Real aParRat = MaxParamRatio(aPars);
      if (aParRat > aCritParRat)
        aParType = Approx_Centripetal;
    }
  }

  return aParType;
}
