/*=========================================================================

  Program:   Visualization Toolkit
  Module:    vtkMeshQuality.cxx

  Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
  All rights reserved.
  See Copyright.txt or http://www.kitware.com/Copyright.htm for details.

     This software is distributed WITHOUT ANY WARRANTY; without even
     the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
     PURPOSE.  See the above copyright notice for more information.

=========================================================================*/
/*
 * Copyright 2004 Sandia Corporation.
 * Under the terms of Contract DE-AC04-94AL85000, there is a non-exclusive
 * license for use of this work by or on behalf of the
 * U.S. Government. Redistribution and use in source and binary forms, with
 * or without modification, are permitted provided that this Notice and any
 * statement of authorship are reproduced on all copies.
 */

#include "vtkMeshQuality.h"
#include "vtkInformation.h"
#include "vtkInformationVector.h"
#include "vtkObjectFactory.h"
#include "vtkDataSet.h"
#include "vtkCellData.h"
#include "vtkDataArray.h"
#include "vtkDoubleArray.h"
#include "vtkCell.h"
#include "vtkCellTypes.h"
#include "vtkPoints.h"
#include "vtkMath.h"
#include "vtkTetra.h"

vtkCxxRevisionMacro(vtkMeshQuality,"1.31");
vtkStandardNewMacro(vtkMeshQuality);

typedef double (*CellQualityType)( vtkCell* );

double TetVolume( vtkCell* cell );

const char* QualityMeasureNames[] =
{
  "EdgeRatio",
  "AspectRatio",
  "RadiusRatio",
  "FrobeniusNorm",
  "MedFrobeniusNorm",
  "MaxFrobeniusNorm",
  "MinAngle"
};

void vtkMeshQuality::PrintSelf(ostream& os, vtkIndent indent )
{
  const char onStr[] = "On";
  const char offStr[] = "Off";

  this->Superclass::PrintSelf( os, indent );

  os << indent << "SaveCellQuality:   "
     << (this->SaveCellQuality ? onStr : offStr) << endl;
  os << indent << "TriangleQualityMeasure: "
     << QualityMeasureNames[this->TriangleQualityMeasure] << endl;
  os << indent << "QuadQualityMeasure: "
     << QualityMeasureNames[this->QuadQualityMeasure] << endl;
  os << indent << "TetQualityMeasure: "
     << QualityMeasureNames[this->TetQualityMeasure] << endl;
  os << indent << "HexQualityMeasure: "
     << QualityMeasureNames[this->HexQualityMeasure] << endl;
  os << indent << "Volume: " 
     << (this->Volume ? onStr : offStr) << endl;
  os << indent << "CompatibilityMode: " 
     << (this->CompatibilityMode ? onStr : offStr) << endl;
}

vtkMeshQuality::vtkMeshQuality()
{
  this->SaveCellQuality = 1; // Default is On
  this->TriangleQualityMeasure = VTK_QUALITY_ASPECT_RATIO;
  this->QuadQualityMeasure = VTK_QUALITY_EDGE_RATIO;
  this->TetQualityMeasure = VTK_QUALITY_ASPECT_RATIO;
  this->HexQualityMeasure = VTK_QUALITY_EDGE_RATIO;
  this->Volume = 0;
  this->CompatibilityMode = 0;
}

vtkMeshQuality::~vtkMeshQuality()
{
  // Nothing yet.
}

int vtkMeshQuality::RequestData(
  vtkInformation *vtkNotUsed(request),
  vtkInformationVector **inputVector,
  vtkInformationVector *outputVector)
{
  // get the info objects
  vtkInformation *inInfo = inputVector[0]->GetInformationObject(0);
  vtkInformation *outInfo = outputVector->GetInformationObject(0);

  // get the input and ouptut
  vtkDataSet *in = vtkDataSet::SafeDownCast(
    inInfo->Get(vtkDataObject::DATA_OBJECT()));
  vtkDataSet *out = vtkDataSet::SafeDownCast(
    outInfo->Get(vtkDataObject::DATA_OBJECT()));

  CellQualityType TriangleQuality,QuadQuality,TetQuality,HexQuality;
  vtkDoubleArray* quality = 0;
  vtkDoubleArray* volume = 0;
  vtkIdType N = in->GetNumberOfCells();
  double qtrim,qtriM,Eqtri,Eqtri2;
  double qquam,qquaM,Eqqua,Eqqua2;
  double qtetm,qtetM,Eqtet,Eqtet2;
  double qhexm,qhexM,Eqhex,Eqhex2;
  double q;
  double V = 0.;
  vtkIdType ntri = 0;
  vtkIdType nqua = 0;
  vtkIdType ntet = 0;
  vtkIdType nhex = 0;
  vtkCell* cell;

  // Initialize the min and max values, std deviations, etc.
  qtriM = qquaM = qtetM = qhexM = VTK_DOUBLE_MIN;
  qtrim = qquam = qtetm = qhexm = VTK_DOUBLE_MAX;
  Eqtri = Eqtri2 = Eqqua = Eqqua2 = Eqtet = Eqtet2 = Eqhex = Eqhex2 = 0.;

  switch ( this->GetTriangleQualityMeasure() )
    {
    case VTK_QUALITY_EDGE_RATIO:
      TriangleQuality = TriangleEdgeRatio;
      break;
    case VTK_QUALITY_ASPECT_RATIO:
      TriangleQuality = TriangleAspectRatio;
      break;
    case VTK_QUALITY_RADIUS_RATIO:
      TriangleQuality = TriangleRadiusRatio;
      break;
    case VTK_QUALITY_FROBENIUS_NORM:
      TriangleQuality = TriangleFrobeniusNorm;
      break;
    case VTK_QUALITY_MIN_ANGLE:
      TriangleQuality = TriangleMinAngle;
      break;
    default:
      vtkWarningMacro( "Bad TriangleQualityMeasure ("
        << this->GetTriangleQualityMeasure() << "), using RadiusRatio instead");
      TriangleQuality = TriangleRadiusRatio;
      break;
    }

  switch ( this->GetQuadQualityMeasure() )
    {
    case VTK_QUALITY_EDGE_RATIO:
      QuadQuality = QuadEdgeRatio;
      break;
    case VTK_QUALITY_ASPECT_RATIO:
      QuadQuality = QuadAspectRatio;
      break;
    case VTK_QUALITY_RADIUS_RATIO:
      QuadQuality = QuadRadiusRatio;
      break;
    case VTK_QUALITY_MED_FROBENIUS_NORM:
      QuadQuality = QuadMedFrobeniusNorm;
      break;
    case VTK_QUALITY_MAX_FROBENIUS_NORM:
      QuadQuality = QuadMaxFrobeniusNorm;
      break;
    case VTK_QUALITY_MIN_ANGLE:
      QuadQuality = QuadMinAngle;
      break;
    default:
      vtkWarningMacro( "Bad QuadQualityMeasure ("
        << this->GetQuadQualityMeasure() << "), using EdgeRatio instead");
      QuadQuality = QuadEdgeRatio;
      break;
    }

  switch ( this->GetTetQualityMeasure() )
    {
    case VTK_QUALITY_EDGE_RATIO:
      TetQuality = TetEdgeRatio;
      break;
    case VTK_QUALITY_ASPECT_RATIO:
      TetQuality = TetAspectRatio;
      break;
    case VTK_QUALITY_RADIUS_RATIO:
      TetQuality = TetRadiusRatio;
      break;
    case VTK_QUALITY_FROBENIUS_NORM:
      TetQuality = TetFrobeniusNorm;
      break;
    case VTK_QUALITY_MIN_ANGLE:
      TetQuality = TetMinAngle;
      break;
    default:
      vtkWarningMacro( "Bad TetQualityMeasure ("
        << this->GetTetQualityMeasure() << "), using RadiusRatio instead");
      TetQuality = TetRadiusRatio;
      break;
    }

  switch ( this->GetHexQualityMeasure() )
    {
    case VTK_QUALITY_EDGE_RATIO:
      HexQuality = HexEdgeRatio;
      break;
    default:
      vtkWarningMacro( "Bad HexQualityMeasure ("
        << this->GetTetQualityMeasure() << "), using EdgeRatio instead");
      HexQuality = HexEdgeRatio;
      break;
    }

  out->ShallowCopy( in );

  if ( this->SaveCellQuality )
    {
    quality = vtkDoubleArray::New();
    if ( this->CompatibilityMode )
      {
      if ( this->Volume )
        {
        quality->SetNumberOfComponents(2);
        }
      else
        {
        quality->SetNumberOfComponents(1);
        }
      }
    else
      {
      quality->SetNumberOfComponents(1);
      }
    quality->SetNumberOfTuples( N );
    quality->SetName( "Quality" );
    out->GetCellData()->AddArray( quality );
    out->GetCellData()->SetActiveAttribute( "Quality", vtkDataSetAttributes::SCALARS );
    quality->Delete();

    if ( ! this->CompatibilityMode )
      {
      if ( this->Volume )
        {
        volume = vtkDoubleArray::New();
        volume->SetNumberOfComponents(1);
        volume->SetNumberOfTuples( N );
        volume->SetName( "Volume" );
        out->GetCellData()->AddArray( volume );
        volume->Delete();
        }
      }
    }

  int p;
  vtkIdType c = 0;
  vtkIdType sz = N < 19 ? 1 : N / 19;
  vtkIdType inner;
  this->UpdateProgress( 0.01 );
  for ( p = 0; p < 20; ++p )
    {
    for ( inner = 0; (inner < sz && c < N); ++c, ++inner )
      {
      cell = out->GetCell( c );
      V = 0.;
      switch ( cell->GetCellType() )
        {
      case VTK_TRIANGLE:
        q = TriangleQuality( cell );
        if ( q > qtriM )
          {
          if ( qtrim > qtriM )
            {
            qtrim = q;
            }
          qtriM = q;
          }
        else if ( q < qtrim )
          {
          qtrim = q;
          }
        Eqtri += q;
        Eqtri2 += q * q;
        ntri++;
        break;
      case VTK_QUAD:
        q = QuadQuality( cell );
        if ( q > qquaM )
          {
          if ( qquam > qquaM )
            {
            qquam = q;
            }
          qquaM = q;
          }
        else if ( q < qquam )
          {
          qquam = q;
          }
        Eqqua += q;
        Eqqua2 += q * q;
        nqua++;
        break;
      case VTK_TETRA:
        q = TetQuality( cell );
        if ( q > qtetM )
          {
          if ( qtetm > qtetM )
            {
            qtetm = q;
            }
          qtetM = q;
          }
        else if ( q < qtetm )
          {
          qtetm = q;
          }
        Eqtet += q;
        Eqtet2 += q * q;
        ntet++;
        if ( this->Volume )
          {
          V = TetVolume( cell );
          if ( ! this->CompatibilityMode )
            {
            volume->SetTuple1( 0, V );
            }
          }
        break;
      case VTK_HEXAHEDRON:
        q = HexQuality( cell );
        if ( q > qhexM )
          {
          if ( qhexm > qhexM )
            {
            qhexm = q;
            }
          qhexM = q;
          }
        else if ( q < qhexm )
          {
          qhexm = q;
          }
        Eqhex += q;
        Eqhex2 += q * q;
        nhex++;
        break;
      default:
        q = 0.;
        }

      if ( this->SaveCellQuality )
        {
        if ( this->CompatibilityMode && this->Volume )
          {
          quality->SetTuple2( c, V, q );
          }
        else
          {
          quality->SetTuple1( c, q );
          }
        }
      }
    this->UpdateProgress( double(p+1)/20. );
    }

  if ( ntri )
    {
    Eqtri  /= (double) ntri;
    Eqtri2 /= (double) ntri;
    }
  else
    {
    qtrim = Eqtri = qtriM = Eqtri2 = 0.;
    }
  
  if ( nqua )
    {
    Eqqua  /= (double) nqua;
    Eqqua2 /= (double) nqua;
    }
  else
    {
    qquam = Eqqua = qquaM = Eqqua2 = 0.;
    }
  
  if ( ntet )
    {
    Eqtet  /= (double) ntet;
    Eqtet2 /= (double) ntet;
    }
  else
    {
    qtetm = Eqtet = qtetM = Eqtet2 = 0.;
    }

  if ( nhex )
    {
    Eqhex  /= (double) nhex;
    Eqhex2 /= (double) nhex;
    }
  else
    {
    qhexm = Eqhex = qhexM = Eqhex2 = 0.;
    }

  double tuple[5];
  quality = vtkDoubleArray::New();
  quality->SetName( "Mesh Triangle Quality" );
  quality->SetNumberOfComponents(5);
  tuple[0] = qtrim;
  tuple[1] = Eqtri;
  tuple[2] = qtriM;
  tuple[3] = Eqtri2;
  tuple[4] = ntri;
  quality->InsertNextTuple( tuple );
  out->GetFieldData()->AddArray( quality );
  quality->Delete();

  quality = vtkDoubleArray::New();
  quality->SetName( "Mesh Quadrilateral Quality" );
  quality->SetNumberOfComponents(5);
  tuple[0] = qquam;
  tuple[1] = Eqqua;
  tuple[2] = qquaM;
  tuple[3] = Eqqua2;
  tuple[4] = nqua;
  quality->InsertNextTuple( tuple );
  out->GetFieldData()->AddArray( quality );
  quality->Delete();

  quality = vtkDoubleArray::New();
  quality->SetName( "Mesh Tetrahedron Quality" );
  quality->SetNumberOfComponents(5);
  tuple[0] = qtetm;
  tuple[1] = Eqtet;
  tuple[2] = qtetM;
  tuple[3] = Eqtet2;
  tuple[4] = ntet;
  quality->InsertNextTuple( tuple );
  out->GetFieldData()->AddArray( quality );
  quality->Delete();

  quality = vtkDoubleArray::New();
  quality->SetName( "Mesh Hexahedron Quality" );
  quality->SetNumberOfComponents(5);
  tuple[0] = qhexm;
  tuple[1] = Eqhex;
  tuple[2] = qhexM;
  tuple[3] = Eqhex2;
  tuple[4] = nhex;
  quality->InsertNextTuple( tuple );
  out->GetFieldData()->AddArray( quality );
  quality->Delete();

  return 1;
}

// Triangle quality measures:
// edge ratio, aspect ratio, radius ratio, Frobenius norm, minimal angle

double vtkMeshQuality::TriangleEdgeRatio( vtkCell* cell )
{
  double p0[3],p1[3],p2[3];
  double a[3],b[3],c[3];
  double a2,b2,c2,m2,M2;
 
  vtkPoints *p = cell->GetPoints();
  p->GetPoint(0, p0);
  p->GetPoint(1, p1);
  p->GetPoint(2, p2);

  a[0] = p1[0]-p0[0];
  a[1] = p1[1]-p0[1];
  a[2] = p1[2]-p0[2];
 
  b[0] = p2[0]-p1[0];
  b[1] = p2[1]-p1[1];
  b[2] = p2[2]-p1[2];
 
  c[0] = p2[0]-p0[0];
  c[1] = p2[1]-p0[1];
  c[2] = p2[2]-p0[2];
 
  a2 = vtkMath::Dot(a,a);
  b2 = vtkMath::Dot(b,b);
  c2 = vtkMath::Dot(c,c);

  if ( a2 < b2 )
    {
    if ( b2 < c2 )
      {
        m2 = a2;
        M2 = c2;
      }
    else // b2 <= a2
      {
      if ( a2 < c2 )
        {
        m2 = a2;
        M2 = b2;
        }
      else // c2 <= a2
        {
          m2 = c2;
          M2 = b2;
        }
      }
    }
  else // b2 <= a2
    {
    if ( a2 < c2 )
      {
        m2 = b2;
        M2 = c2;
      }
    else // c2 <= a2
      {
      if ( b2 < c2 )
        {
          m2 = b2;
          M2 = a2;
        }
      else // c2 <= b2
        {
          m2 = c2;
          M2 = a2;
        }
      }
    }

  return sqrt(M2 / m2);
}

double vtkMeshQuality::TriangleAspectRatio( vtkCell* cell )
{
  double p0[3],p1[3],p2[3];
  double a[3],b[3],c[3];
  double a1,b1,c1,hm;
  const double normal_coeff = sqrt(3.) / 6.;
 
  vtkPoints *p = cell->GetPoints();
  p->GetPoint(0, p0);
  p->GetPoint(1, p1);
  p->GetPoint(2, p2);

  a[0] = p1[0]-p0[0];
  a[1] = p1[1]-p0[1];
  a[2] = p1[2]-p0[2];
 
  b[0] = p2[0]-p1[0];
  b[1] = p2[1]-p1[1];
  b[2] = p2[2]-p1[2];
 
  c[0] = p2[0]-p0[0];
  c[1] = p2[1]-p0[1];
  c[2] = p2[2]-p0[2];
 
  a1 = sqrt(vtkMath::Dot(a,a));
  b1 = sqrt(vtkMath::Dot(b,b));
  c1 = sqrt(vtkMath::Dot(c,c));

  hm = a1 > b1 ? a1 : b1;
  hm = hm > c1 ? hm : c1;

  vtkMath::Cross(a,b,c);

  return normal_coeff * hm * (a1 + b1 + c1) / vtkMath::Norm(c);
}

double vtkMeshQuality::TriangleRadiusRatio( vtkCell* cell )
{
  double p0[3],p1[3],p2[3];
  double a[3],b[3],c[3];
  double a1,b1,c1,ab;
 
  vtkPoints *p = cell->GetPoints();
  p->GetPoint(0, p0);
  p->GetPoint(1, p1);
  p->GetPoint(2, p2);

  a[0] = p1[0]-p0[0];
  a[1] = p1[1]-p0[1];
  a[2] = p1[2]-p0[2];
 
  b[0] = p2[0]-p1[0];
  b[1] = p2[1]-p1[1];
  b[2] = p2[2]-p1[2];
 
  c[0] = p2[0]-p0[0];
  c[1] = p2[1]-p0[1];
  c[2] = p2[2]-p0[2];
 
  a1 = sqrt(vtkMath::Dot(a,a));
  b1 = sqrt(vtkMath::Dot(b,b));
  c1 = sqrt(vtkMath::Dot(c,c));

  vtkMath::Cross(a,b,c);
  ab = vtkMath::Norm(c);

  return .25 * a1 * b1 * c1 * (a1 + b1 + c1) / (ab * ab);
}

double vtkMeshQuality::TriangleFrobeniusNorm( vtkCell* cell )
{
  double p0[3],p1[3],p2[3];
  double a[3],b[3],c[3];
  double t22;
  const double normal_coeff = .5 / sqrt(3.);
 
  vtkPoints *p = cell->GetPoints();
  p->GetPoint(0, p0);
  p->GetPoint(1, p1);
  p->GetPoint(2, p2);

  a[0] = p1[0]-p0[0];
  a[1] = p1[1]-p0[1];
  a[2] = p1[2]-p0[2];
 
  b[0] = p2[0]-p1[0];
  b[1] = p2[1]-p1[1];
  b[2] = p2[2]-p1[2];
 
  c[0] = p2[0]-p0[0];
  c[1] = p2[1]-p0[1];
  c[2] = p2[2]-p0[2];
 
  t22  = vtkMath::Dot(a,a);
  t22 += vtkMath::Dot(b,b);
  t22 += vtkMath::Dot(c,c);

  vtkMath::Cross(a,b,c);

  return normal_coeff * t22 / vtkMath::Norm(c);
}

double vtkMeshQuality::TriangleMinAngle( vtkCell* cell )
{
  double p0[3],p1[3],p2[3];
  double a[3],b[3],c[3];
  double a2,b2,c2,alpha,beta,gamma;
  const double normal_coeff = .3183098861837906715377675267450287;

  vtkPoints *p = cell->GetPoints();
  p->GetPoint(0, p0);
  p->GetPoint(1, p1);
  p->GetPoint(2, p2);

  a[0] = p1[0]-p0[0];
  a[1] = p1[1]-p0[1];
  a[2] = p1[2]-p0[2];
 
  b[0] = p2[0]-p1[0];
  b[1] = p2[1]-p1[1];
  b[2] = p2[2]-p1[2];
 
  c[0] = p2[0]-p0[0];
  c[1] = p2[1]-p0[1];
  c[2] = p2[2]-p0[2];
 
  a2 = vtkMath::Dot(a,a);
  b2 = vtkMath::Dot(b,b);
  c2 = vtkMath::Dot(c,c);

  alpha = acos(vtkMath::Dot(b,c) / sqrt(b2 * c2));
  beta  = acos(vtkMath::Dot(c,a) / sqrt(c2 * a2));
  gamma = acos(vtkMath::Dot(a,b) / sqrt(a2 * b2));

  alpha = alpha < beta ? alpha : beta;

  return  (alpha < gamma ? alpha : gamma) * 180. * normal_coeff;
}

// Quadrangle quality measures:
// edge ratio, aspect ratio, radius ratio, average Frobenius norm, 
// maximal Frobenius norm, minimal angle
// (only edge ratio and minimal angle are intended for nonplanar quads)

double vtkMeshQuality::QuadEdgeRatio( vtkCell* cell )
{
  double p0[3],p1[3],p2[3],p3[3];
  double a[3],b[3],c[3],d[3];
  double a2,b2,c2,d2,mab,Mab,mcd,Mcd,m2,M2;
 
  vtkPoints *p = cell->GetPoints();
  p->GetPoint(0, p0);
  p->GetPoint(1, p1);
  p->GetPoint(2, p2);
  p->GetPoint(3, p3);

  a[0] = p1[0]-p0[0];
  a[1] = p1[1]-p0[1];
  a[2] = p1[2]-p0[2];
 
  b[0] = p2[0]-p1[0];
  b[1] = p2[1]-p1[1];
  b[2] = p2[2]-p1[2];
 
  c[0] = p3[0]-p2[0];
  c[1] = p3[1]-p2[1];
  c[2] = p3[2]-p2[2];
 
  d[0] = p0[0]-p3[0];
  d[1] = p0[1]-p3[1];
  d[2] = p0[2]-p3[2];
 
  a2 = vtkMath::Dot(a,a);
  b2 = vtkMath::Dot(b,b);
  c2 = vtkMath::Dot(c,c);
  d2 = vtkMath::Dot(d,d);

  if ( a2 < b2 )
    {
      mab = a2;
      Mab = b2;
    }
  else // b2 <= a2
    {
      mab = b2;
      Mab = a2;
    }
  if ( c2 < d2 )
    {
      mcd = c2;
      Mcd = d2;
    }
  else // d2 <= c2
    {
      mcd = d2;
      Mcd = c2;
    }
  m2 = mab < mcd ? mab : mcd;
  M2 = Mab > Mcd ? Mab : Mcd;

  return sqrt(M2 / m2);
}

double vtkMeshQuality::QuadAspectRatio( vtkCell* cell )
{
  double p0[3],p1[3],p2[3],p3[3];
  double a[3],b[3],c[3],d[3],ab[3],cd[3];
  double a1,b1,c1,d1;
  double ma,mb,hm;
 
  vtkPoints *p = cell->GetPoints();
  p->GetPoint(0, p0);
  p->GetPoint(1, p1);
  p->GetPoint(2, p2);
  p->GetPoint(3, p3);

  a[0] = p1[0]-p0[0];
  a[1] = p1[1]-p0[1];
  a[2] = p1[2]-p0[2];
 
  b[0] = p2[0]-p1[0];
  b[1] = p2[1]-p1[1];
  b[2] = p2[2]-p1[2];
 
  c[0] = p3[0]-p2[0];
  c[1] = p3[1]-p2[1];
  c[2] = p3[2]-p2[2];
 
  d[0] = p0[0]-p3[0];
  d[1] = p0[1]-p3[1];
  d[2] = p0[2]-p3[2];
 
  a1 = sqrt(vtkMath::Dot(a,a));
  b1 = sqrt(vtkMath::Dot(b,b));
  c1 = sqrt(vtkMath::Dot(c,c));
  d1 = sqrt(vtkMath::Dot(d,d));

  ma = a1 > b1 ? a1 : b1;
  mb = c1 > d1 ? c1 : d1;
  hm = ma > mb ? ma : mb;

  vtkMath::Cross(a,b,ab);
  vtkMath::Cross(c,d,cd);

  return .5 * hm * (a1 + b1 + c1 + d1) / (vtkMath::Norm(ab) + vtkMath::Norm(cd));
}

double vtkMeshQuality::QuadRadiusRatio( vtkCell* cell )
{
  double p0[3],p1[3],p2[3],p3[3];
  double a[3],b[3],c[3],d[3],m[3],n[3];
  double ab[3],bc[3],cd[3],da[3];
  double a2,b2,c2,d2,m2,n2,h2;
  double t0,t1,t2,t3;
  const double normal_coeff = 1. / (2.*sqrt(2.));
 
  vtkPoints *p = cell->GetPoints();
  p->GetPoint(0, p0);
  p->GetPoint(1, p1);
  p->GetPoint(2, p2);
  p->GetPoint(3, p3);

  a[0] = p1[0]-p0[0];
  a[1] = p1[1]-p0[1];
  a[2] = p1[2]-p0[2];
 
  b[0] = p2[0]-p1[0];
  b[1] = p2[1]-p1[1];
  b[2] = p2[2]-p1[2];
 
  c[0] = p3[0]-p2[0];
  c[1] = p3[1]-p2[1];
  c[2] = p3[2]-p2[2];
 
  d[0] = p0[0]-p3[0];
  d[1] = p0[1]-p3[1];
  d[2] = p0[2]-p3[2];
 
  m[0] = p2[0]-p0[0];
  m[1] = p2[1]-p0[1];
  m[2] = p2[2]-p0[2];
 
  n[0] = p3[0]-p1[0];
  n[1] = p3[1]-p1[1];
  n[2] = p3[2]-p1[2];
 
  a2 = vtkMath::Dot(a,a);
  b2 = vtkMath::Dot(b,b);
  c2 = vtkMath::Dot(c,c);
  d2 = vtkMath::Dot(d,d);
  m2 = vtkMath::Dot(m,m);
  n2 = vtkMath::Dot(n,n);

  t0 = a2 > b2 ? a2 : b2;
  t1 = c2 > d2 ? c2 : d2;
  t2 = m2 > n2 ? m2 : n2;
  h2 = t0 > t1 ? t0 : t1;
  h2 = h2 > t2 ? h2 : t2;

  vtkMath::Cross(a,b,ab);
  vtkMath::Cross(b,c,bc);
  vtkMath::Cross(c,d,cd);
  vtkMath::Cross(d,a,da);

  t0 = vtkMath::Norm(da);
  t1 = vtkMath::Norm(ab);
  t2 = vtkMath::Norm(bc);
  t3 = vtkMath::Norm(cd);

  t0 = t0 < t1 ? t0 : t1;
  t2 = t2 < t3 ? t2 : t3;
  t0 = t0 < t2 ? t0 : t2;

  return normal_coeff * sqrt((a2 + b2 + c2 + d2) * h2) / t0;
}

double vtkMeshQuality::QuadMedFrobeniusNorm( vtkCell* cell )
{
  double p0[3],p1[3],p2[3],p3[3];
  double a[3],b[3],c[3],d[3],ab[3],bc[3],cd[3],da[3];
  double a2,b2,c2,d2;
  double kappa2;
 
  vtkPoints *p = cell->GetPoints();
  p->GetPoint(0, p0);
  p->GetPoint(1, p1);
  p->GetPoint(2, p2);
  p->GetPoint(3, p3);

  a[0] = p1[0]-p0[0];
  a[1] = p1[1]-p0[1];
  a[2] = p1[2]-p0[2];
 
  b[0] = p2[0]-p1[0];
  b[1] = p2[1]-p1[1];
  b[2] = p2[2]-p1[2];
 
  c[0] = p3[0]-p2[0];
  c[1] = p3[1]-p2[1];
  c[2] = p3[2]-p2[2];
 
  d[0] = p0[0]-p3[0];
  d[1] = p0[1]-p3[1];
  d[2] = p0[2]-p3[2];
 
  vtkMath::Cross(a,b,ab);
  vtkMath::Cross(b,c,bc);
  vtkMath::Cross(c,d,cd);
  vtkMath::Cross(d,a,da);

  a2 = vtkMath::Dot(a,a);
  b2 = vtkMath::Dot(b,b);
  c2 = vtkMath::Dot(c,c);
  d2 = vtkMath::Dot(d,d);

  kappa2  = (a2 + b2) / vtkMath::Norm(ab);
  kappa2 += (b2 + c2) / vtkMath::Norm(bc);
  kappa2 += (c2 + d2) / vtkMath::Norm(cd);
  kappa2 += (d2 + a2) / vtkMath::Norm(da);

  return .125 * kappa2;
}

double vtkMeshQuality::QuadMaxFrobeniusNorm( vtkCell* cell )
{
  double p0[3],p1[3],p2[3],p3[3];
  double a[3],b[3],c[3],d[3],ab[3],bc[3],cd[3],da[3];
  double a2,b2,c2,d2;
  double kmax,kcur;
 
  vtkPoints *p = cell->GetPoints();
  p->GetPoint(0, p0);
  p->GetPoint(1, p1);
  p->GetPoint(2, p2);
  p->GetPoint(3, p3);

  a[0] = p1[0]-p0[0];
  a[1] = p1[1]-p0[1];
  a[2] = p1[2]-p0[2];
 
  b[0] = p2[0]-p1[0];
  b[1] = p2[1]-p1[1];
  b[2] = p2[2]-p1[2];
 
  c[0] = p3[0]-p2[0];
  c[1] = p3[1]-p2[1];
  c[2] = p3[2]-p2[2];
 
  d[0] = p0[0]-p3[0];
  d[1] = p0[1]-p3[1];
  d[2] = p0[2]-p3[2];
 
  vtkMath::Cross(a,b,ab);
  vtkMath::Cross(b,c,bc);
  vtkMath::Cross(c,d,cd);
  vtkMath::Cross(d,a,da);

  a2 = vtkMath::Dot(a,a);
  b2 = vtkMath::Dot(b,b);
  c2 = vtkMath::Dot(c,c);
  d2 = vtkMath::Dot(d,d);

  kmax = (a2 + b2) / vtkMath::Norm(ab);

  kcur = (b2 + c2) / vtkMath::Norm(bc);
  kmax = kmax > kcur ? kmax : kcur;

  kcur = (c2 + d2) / vtkMath::Norm(cd);
  kmax = kmax > kcur ? kmax : kcur;

  kcur = (d2 + a2) / vtkMath::Norm(da);
  kmax = kmax > kcur ? kmax : kcur;

  return .5 * kmax;
}

double vtkMeshQuality::QuadMinAngle( vtkCell* cell )
{
  double p0[3],p1[3],p2[3],p3[3];
  double a[3],b[3],c[3],d[3];
  double a2,b2,c2,d2,alpha,beta,gamma,delta;
  const double normal_coeff = .3183098861837906715377675267450287;

  vtkPoints *p = cell->GetPoints();
  p->GetPoint(0, p0);
  p->GetPoint(1, p1);
  p->GetPoint(2, p2);
  p->GetPoint(3, p3);

  a[0] = p1[0]-p0[0];
  a[1] = p1[1]-p0[1];
  a[2] = p1[2]-p0[2];
 
  b[0] = p2[0]-p1[0];
  b[1] = p2[1]-p1[1];
  b[2] = p2[2]-p1[2];
 
  c[0] = p3[0]-p2[0];
  c[1] = p3[1]-p2[1];
  c[2] = p3[2]-p2[2];
 
  d[0] = p0[0]-p3[0];
  d[1] = p0[1]-p3[1];
  d[2] = p0[2]-p3[2];
 
  a2 = vtkMath::Dot(a,a);
  b2 = vtkMath::Dot(b,b);
  c2 = vtkMath::Dot(c,c);
  d2 = vtkMath::Dot(d,d);

  alpha = acos(vtkMath::Dot(b,c) / sqrt(b2 * c2));
  beta  = acos(vtkMath::Dot(c,d) / sqrt(c2 * d2));
  gamma = acos(vtkMath::Dot(d,a) / sqrt(d2 * a2));
  delta = acos(vtkMath::Dot(a,b) / sqrt(a2 * b2));

  alpha = alpha < beta  ? alpha : beta;
  gamma = gamma < delta ? gamma : delta;

  return  (alpha < gamma ? alpha : gamma) * 180. * normal_coeff;
}

// Volume of a tetrahedron, for compatibility with the original vtkMeshQuality

double TetVolume( vtkCell* cell )
{
  double x0[3];
  double x1[3];
  double x2[3];
  double x3[3];
  cell->Points->GetPoint( 0, x0 );
  cell->Points->GetPoint( 1, x1 );
  cell->Points->GetPoint( 2, x2 );
  cell->Points->GetPoint( 3, x3 );
  return vtkTetra::ComputeVolume( x0, x1, x2, x3 );
}

// Tetrahedron quality measures:
// edge ratio, aspect ratio, radius ratio, Frobenius norm, minimal angle

double vtkMeshQuality::TetEdgeRatio( vtkCell* cell )
{
  double p0[3],p1[3],p2[3],p3[3];
  double a[3],b[3],c[3],d[3],e[3],f[3];
  double a2,b2,c2,d2,e2,f2;
  double m2,M2,mab,mcd,mef,Mab,Mcd,Mef;
 
  vtkPoints *p = cell->GetPoints();
  p->GetPoint(0, p0);
  p->GetPoint(1, p1);
  p->GetPoint(2, p2);
  p->GetPoint(3, p3);

  a[0] = p1[0]-p0[0];
  a[1] = p1[1]-p0[1];
  a[2] = p1[2]-p0[2];
 
  b[0] = p2[0]-p1[0];
  b[1] = p2[1]-p1[1];
  b[2] = p2[2]-p1[2];
 
  c[0] = p2[0]-p0[0];
  c[1] = p2[1]-p0[1];
  c[2] = p2[2]-p0[2];
 
  d[0] = p3[0]-p0[0];
  d[1] = p3[1]-p0[1];
  d[2] = p3[2]-p0[2];
 
  e[0] = p3[0]-p1[0];
  e[1] = p3[1]-p1[1];
  e[2] = p3[2]-p1[2];
 
  f[0] = p3[0]-p2[0];
  f[1] = p3[1]-p2[1];
  f[2] = p3[2]-p2[2];

  a2 = vtkMath::Dot(a,a);
  b2 = vtkMath::Dot(b,b);
  c2 = vtkMath::Dot(c,c);
  d2 = vtkMath::Dot(d,d);
  e2 = vtkMath::Dot(e,e);
  f2 = vtkMath::Dot(f,f);

  if ( a2 < b2 )
    {
      mab = a2;
      Mab = b2;
    }
  else // b2 <= a2
    {
      mab = b2;
      Mab = a2;
    }
  if ( c2 < d2 )
    {
      mcd = c2;
      Mcd = d2;
    }
  else // d2 <= c2
    {
      mcd = d2;
      Mcd = c2;
    }
  if ( e2 < f2 )
    {
      mef = e2;
      Mef = f2;
    }
  else // f2 <= e2
    {
      mef = f2;
      Mef = e2;
    }

  m2 = mab < mcd ? mab : mcd;
  m2 = m2  < mef ? m2  : mef;
  M2 = Mab > Mcd ? Mab : Mcd;
  M2 = M2  > Mef ? M2  : Mef;

  return sqrt(M2 / m2);
}

double vtkMeshQuality::TetAspectRatio( vtkCell* cell )
{
  double p0[3],p1[3],p2[3],p3[3];
  double ab[3],bc[3],ac[3],ad[3],bd[3],cd[3];
  double t0,t1,t2,t3,t4,t5;
  double ma,mb,mc,hm;
  const double normal_coeff = sqrt(6.) / 12.;
 
  vtkPoints *p = cell->GetPoints();
  p->GetPoint(0, p0);
  p->GetPoint(1, p1);
  p->GetPoint(2, p2);
  p->GetPoint(3, p3);

  ab[0] = p1[0]-p0[0];
  ab[1] = p1[1]-p0[1];
  ab[2] = p1[2]-p0[2];

  bc[0] = p2[0]-p1[0];
  bc[1] = p2[1]-p1[1];
  bc[2] = p2[2]-p1[2];
 
  ac[0] = p2[0]-p0[0];
  ac[1] = p2[1]-p0[1];
  ac[2] = p2[2]-p0[2];
 
  ad[0] = p3[0]-p0[0];
  ad[1] = p3[1]-p0[1];
  ad[2] = p3[2]-p0[2];
 
  bd[0] = p3[0]-p1[0];
  bd[1] = p3[1]-p1[1];
  bd[2] = p3[2]-p1[2];
 
  cd[0] = p3[0]-p2[0];
  cd[1] = p3[1]-p2[1];
  cd[2] = p3[2]-p2[2];

  t0 = vtkMath::Dot(ab,ab);
  t1 = vtkMath::Dot(bc,bc);
  t2 = vtkMath::Dot(ac,ac);
  t3 = vtkMath::Dot(ad,ad);
  t4 = vtkMath::Dot(bd,bd);
  t5 = vtkMath::Dot(cd,cd);

  ma = t0 > t1 ? t0 : t1;
  mb = t2 > t3 ? t2 : t3;
  mc = t4 > t5 ? t4 : t5;
  hm = ma > mb ? ma : mb;
  hm = hm > mc ? sqrt(hm) : sqrt(mc);

  vtkMath::Cross(ab,bc,bd);
  t0 = vtkMath::Norm(bd);
  vtkMath::Cross(ab,ad,bd);
  t1 = vtkMath::Norm(bd);
  vtkMath::Cross(ac,ad,bd);
  t2 = vtkMath::Norm(bd);
  vtkMath::Cross(bc,cd,bd);
  t3 = vtkMath::Norm(bd);

  t4 = fabs(vtkMath::Determinant3x3(ab,ac,ad));
 
  return normal_coeff * hm * (t0 + t1 + t2 + t3) / t4;
}

double vtkMeshQuality::TetRadiusRatio( vtkCell* cell )
{
  double p0[3],p1[3],p2[3],p3[3];
  double ab[3],bc[3],ac[3],ad[3],bd[3],cd[3],u[3];
  double abc,abd,acd,bcd,a,b,c,det;
  const double normal_coeff = 1. / 12.;

  vtkPoints *p = cell->GetPoints();
  p->GetPoint(0, p0);
  p->GetPoint(1, p1);
  p->GetPoint(2, p2);
  p->GetPoint(3, p3);

  ab[0] = p1[0]-p0[0];
  ab[1] = p1[1]-p0[1];
  ab[2] = p1[2]-p0[2];
 
  bc[0] = p2[0]-p1[0];
  bc[1] = p2[1]-p1[1];
  bc[2] = p2[2]-p1[2];
 
  ac[0] = p2[0]-p0[0];
  ac[1] = p2[1]-p0[1];
  ac[2] = p2[2]-p0[2];
 
  ad[0] = p3[0]-p0[0];
  ad[1] = p3[1]-p0[1];
  ad[2] = p3[2]-p0[2];
 
  bd[0] = p3[0]-p1[0];
  bd[1] = p3[1]-p1[1];
  bd[2] = p3[2]-p1[2];
 
  cd[0] = p3[0]-p2[0];
  cd[1] = p3[1]-p2[1];
  cd[2] = p3[2]-p2[2];

  a = sqrt(vtkMath::Dot(ab,ab) * vtkMath::Dot(cd,cd));
  b = sqrt(vtkMath::Dot(ac,ac) * vtkMath::Dot(bd,bd));
  c = sqrt(vtkMath::Dot(ad,ad) * vtkMath::Dot(bc,bc));

  vtkMath::Cross(ab,bc,u);
  abc = vtkMath::Norm(u);
  vtkMath::Cross(ab,ad,u);
  abd = vtkMath::Norm(u);
  vtkMath::Cross(ac,ad,u);
  acd = vtkMath::Norm(u);
  vtkMath::Cross(bc,cd,u);
  bcd = vtkMath::Norm(u);

  det = vtkMath::Determinant3x3(ab,ac,ad);

  return normal_coeff * sqrt((a+b+c) * (a+b-c) * (a+c-b) * (b+c-a)) * (abc + abd + acd + bcd) \
    / (det * det);
}

double vtkMeshQuality::TetFrobeniusNorm( vtkCell* cell )
{
  double p0[3],p1[3],p2[3],p3[3];
  double u[3],v[3],w[3];
  double numerator,radicand;
  const double normal_exp = 1./3.;
 
  vtkPoints *p = cell->GetPoints();
  p->GetPoint(0, p0);
  p->GetPoint(1, p1);
  p->GetPoint(2, p2);
  p->GetPoint(3, p3);

  u[0] = p1[0]-p0[0];
  u[1] = p1[1]-p0[1];
  u[2] = p1[2]-p0[2];
 
  v[0] = p2[0]-p0[0];
  v[1] = p2[1]-p0[1];
  v[2] = p2[2]-p0[2];
 
  w[0] = p3[0]-p0[0];
  w[1] = p3[1]-p0[1];
  w[2] = p3[2]-p0[2];

  numerator  = u[0] * u[0] + u[1] * u[1] + u[2] * u[2];
  numerator += v[0] * v[0] + v[1] * v[1] + v[2] * v[2];
  numerator += w[0] * w[0] + w[1] * w[1] + w[2] * w[2];
  numerator *= 1.5;
  numerator -= v[0] * u[0] + v[1] * u[1] + v[2] * u[2];
  numerator -= w[0] * u[0] + w[1] * u[1] + w[2] * u[2];
  numerator -= w[0] * v[0] + w[1] * v[1] + w[2] * v[2];

  radicand = vtkMath::Determinant3x3(u,v,w);
  radicand *= radicand;
  radicand *= 2.;

  return numerator / (3. * pow(radicand,normal_exp));
}

double vtkMeshQuality::TetMinAngle( vtkCell* cell )
{
  double p0[3],p1[3],p2[3],p3[3];
  double ab[3],bc[3],ad[3],cd[3];
  double abc[3],abd[3],acd[3],bcd[3];
  double nabc,nabd,nacd,nbcd;
  double alpha,beta,gamma,delta,epsilon,zeta;
  const double normal_coeff = .3183098861837906715377675267450287;

  vtkPoints *p = cell->GetPoints();
  p->GetPoint(0, p0);
  p->GetPoint(1, p1);
  p->GetPoint(2, p2);
  p->GetPoint(3, p3);

  ab[0] = p1[0]-p0[0];
  ab[1] = p1[1]-p0[1];
  ab[2] = p1[2]-p0[2];
 
  bc[0] = p2[0]-p1[0];
  bc[1] = p2[1]-p1[1];
  bc[2] = p2[2]-p1[2];
 
  ad[0] = p3[0]-p0[0];
  ad[1] = p3[1]-p0[1];
  ad[2] = p3[2]-p0[2];
 
  cd[0] = p3[0]-p2[0];
  cd[1] = p3[1]-p2[1];
  cd[2] = p3[2]-p2[2];

  vtkMath::Cross(ab,bc,abc);
  nabc = vtkMath::Norm(abc);
  vtkMath::Cross(ab,ad,abd);
  nabd = vtkMath::Norm(abd);
  vtkMath::Cross(ad,cd,acd);
  nacd = vtkMath::Norm(acd);
  vtkMath::Cross(bc,cd,bcd);
  nbcd = vtkMath::Norm(bcd);

  alpha   = acos(vtkMath::Dot(abc,abd) / (nabc * nabd));
  beta    = acos(vtkMath::Dot(abc,acd) / (nabc * nacd));
  gamma   = acos(vtkMath::Dot(abc,bcd) / (nabc * nbcd));
  delta   = acos(vtkMath::Dot(abd,acd) / (nabd * nacd));
  epsilon = acos(vtkMath::Dot(abd,bcd) / (nabd * nbcd));
  zeta    = acos(vtkMath::Dot(acd,bcd) / (nacd * nbcd));

  alpha = alpha < beta    ? alpha : beta;
  alpha = alpha < gamma   ? alpha : gamma;
  alpha = alpha < delta   ? alpha : delta;
  alpha = alpha < epsilon ? alpha : epsilon;
  
  return  (alpha < zeta ? alpha : zeta) * 180. * normal_coeff;
}

// Hexahedron quality measure:
// edge ratio

double vtkMeshQuality::HexEdgeRatio( vtkCell* cell)
{
  double p0[3],p1[3],p2[3],p3[3];
  double p4[3],p5[3],p6[3],p7[3];
  double a[3],b[3],c[3],d[3],e[3],f[3];
  double g[3],h[3],i[3],j[3],k[3],l[3];
  double a2,b2,c2,d2,e2,f2;
  double g2,h2,i2,j2,k2,l2;
  double mab,mcd,mef,Mab,Mcd,Mef;
  double mgh,mij,mkl,Mgh,Mij,Mkl;
  double m2,M2;
 
  vtkPoints *p = cell->GetPoints();
  p->GetPoint(0, p0);
  p->GetPoint(1, p1);
  p->GetPoint(2, p2);
  p->GetPoint(3, p3);
  p->GetPoint(4, p4);
  p->GetPoint(5, p5);
  p->GetPoint(6, p6);
  p->GetPoint(7, p7);

  a[0] = p1[0]-p0[0];
  a[1] = p1[1]-p0[1];
  a[2] = p1[2]-p0[2];
 
  b[0] = p2[0]-p1[0];
  b[1] = p2[1]-p1[1];
  b[2] = p2[2]-p1[2];
 
  c[0] = p3[0]-p2[0];
  c[1] = p3[1]-p2[1];
  c[2] = p3[2]-p2[2];
 
  d[0] = p0[0]-p3[0];
  d[1] = p0[1]-p3[1];
  d[2] = p0[2]-p3[2];
 
  e[0] = p4[0]-p0[0];
  e[1] = p4[1]-p0[1];
  e[2] = p4[2]-p0[2];
 
  f[0] = p5[0]-p1[0];
  f[1] = p5[1]-p1[1];
  f[2] = p5[2]-p1[2];

  g[0] = p6[0]-p2[0];
  g[1] = p6[1]-p2[1];
  g[2] = p6[2]-p2[2];

  h[0] = p7[0]-p3[0];
  h[1] = p7[1]-p3[1];
  h[2] = p7[2]-p3[2];

  i[0] = p5[0]-p4[0];
  i[1] = p5[1]-p4[1];
  i[2] = p5[2]-p4[2];
 
  j[0] = p6[0]-p5[0];
  j[1] = p6[1]-p5[1];
  j[2] = p6[2]-p5[2];

  k[0] = p7[0]-p6[0];
  k[1] = p7[1]-p6[1];
  k[2] = p7[2]-p6[2];

  l[0] = p4[0]-p7[0];
  l[1] = p4[1]-p7[1];
  l[2] = p4[2]-p7[2];

  a2 = vtkMath::Dot(a,a);
  b2 = vtkMath::Dot(b,b);
  c2 = vtkMath::Dot(c,c);
  d2 = vtkMath::Dot(d,d);
  e2 = vtkMath::Dot(e,e);
  f2 = vtkMath::Dot(f,f);
  g2 = vtkMath::Dot(g,g);
  h2 = vtkMath::Dot(h,h);
  i2 = vtkMath::Dot(i,i);
  j2 = vtkMath::Dot(j,j);
  k2 = vtkMath::Dot(k,k);
  l2 = vtkMath::Dot(l,l);

  if ( a2 < b2 )
    {
      mab = a2;
      Mab = b2;
    }
  else // b2 <= a2
    {
      mab = b2;
      Mab = a2;
    }
  if ( c2 < d2 )
    {
      mcd = c2;
      Mcd = d2;
    }
  else // d2 <= c2
    {
      mcd = d2;
      Mcd = c2;
    }
  if ( e2 < f2 )
    {
      mef = e2;
      Mef = f2;
    }
  else // f2 <= e2
    {
      mef = f2;
      Mef = e2;
    }
  if ( g2 < h2 )
    {
      mgh = g2;
      Mgh = h2;
    }
  else // h2 <= g2
    {
      mgh = h2;
      Mgh = g2;
    }
  if ( i2 < j2 )
    {
      mij = i2;
      Mij = j2;
    }
  else // j2 <= i2
    {
      mij = j2;
      Mij = i2;
    }
  if ( k2 < l2 )
    {
      mkl = k2;
      Mkl = l2;
    }
  else // l2 <= k2
    {
      mkl = l2;
      Mkl = k2;
    }

  m2 = mab < mcd ? mab : mcd;
  m2 = m2  < mef ? m2  : mef;
  m2 = m2  < mgh ? m2  : mgh;
  m2 = m2  < mij ? m2  : mij;
  m2 = m2  < mkl ? m2  : mkl;
  M2 = Mab > Mcd ? Mab : Mcd;
  M2 = M2  > Mef ? M2  : Mef;
  M2 = M2  > Mgh ? M2  : Mgh;
  M2 = M2  > Mij ? M2  : Mij;
  M2 = M2  > Mkl ? M2  : Mkl;

  return sqrt(M2 / m2);
}

