// Created on: 2003-09-16
// Created by: Alexander SOLOVYOV
// Copyright (c) 2003-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 <Graphic3d_ArrayOfPoints.hxx>
#include <Graphic3d_ArrayOfPolygons.hxx>
#include <Graphic3d_ArrayOfPrimitives.hxx>
#include <Graphic3d_ArrayOfSegments.hxx>
#include <Graphic3d_ArrayOfTriangles.hxx>
#include <MeshVS_Buffer.hxx>
#include <MeshVS_DataSource.hxx>
#include <MeshVS_Drawer.hxx>
#include <MeshVS_DrawerAttribute.hxx>
#include <MeshVS_MapOfTwoNodes.hxx>
#include <MeshVS_Mesh.hxx>
#include <MeshVS_MeshPrsBuilder.hxx>
#include <MeshVS_SymmetricPairHasher.hxx>
#include <MeshVS_Tool.hxx>
#include <NCollection_Map.hxx>
#include <NCollection_Vector.hxx>
#include <Prs3d_LineAspect.hxx>
#include <Prs3d_PointAspect.hxx>
#include <Prs3d_Presentation.hxx>
#include <Prs3d_ShadingAspect.hxx>
#include <Quantity_NameOfColor.hxx>
#include <Select3D_SensitivePoint.hxx>
#include <Standard_Type.hxx>
#include <TColStd_Array1OfInteger.hxx>
#include <TColStd_Array1OfReal.hxx>
#include <TColStd_HPackedMapOfInteger.hxx>
#include <TColStd_MapIteratorOfPackedMapOfInteger.hxx>
#include <TColStd_PackedMapOfInteger.hxx>
#include <TColStd_SequenceOfInteger.hxx>

#ifdef _WIN32
  #include <malloc.h> // for alloca()
#endif

IMPLEMENT_STANDARD_RTTIEXT(MeshVS_MeshPrsBuilder, MeshVS_PrsBuilder)

namespace
{
//================================================================
// Function : ProcessFace
// Purpose  : Fill array with triangles for the face
//================================================================
static void ProcessFace(const TColStd_SequenceOfInteger&    theFaceNodes,
                        const TColStd_Array1OfReal&         theNodes,
                        const Standard_Real*                theCenter,
                        const Standard_Real                 theShrinkCoef,
                        const Standard_Boolean              theIsShrinked,
                        const Standard_Boolean              theIsShaded,
                        Handle(Graphic3d_ArrayOfPrimitives) theArray)
{
  const Standard_Integer aNbPolyNodes = theFaceNodes.Length();

  Standard_Real* aPolyNodesBuf =
    (Standard_Real*)alloca((3 * aNbPolyNodes + 1) * sizeof(Standard_Real));
  TColStd_Array1OfReal aPolyNodes(*aPolyNodesBuf, 0, 3 * aNbPolyNodes);

  for (Standard_Integer aNodeIdx = 0; aNodeIdx < aNbPolyNodes; ++aNodeIdx)
  {
    Standard_Integer anIdx = theFaceNodes.Value(aNodeIdx + 1);

    Standard_Real aX = theNodes.Value(theNodes.Lower() + 3 * anIdx + 0);
    Standard_Real aY = theNodes.Value(theNodes.Lower() + 3 * anIdx + 1);
    Standard_Real aZ = theNodes.Value(theNodes.Lower() + 3 * anIdx + 2);

    if (theIsShrinked)
    {
      aX = theCenter[0] + theShrinkCoef * (aX - theCenter[0]);
      aY = theCenter[1] + theShrinkCoef * (aY - theCenter[1]);
      aZ = theCenter[2] + theShrinkCoef * (aZ - theCenter[2]);
    }

    aPolyNodes.SetValue(3 * aNodeIdx + 1, aX);
    aPolyNodes.SetValue(3 * aNodeIdx + 2, aY);
    aPolyNodes.SetValue(3 * aNodeIdx + 3, aZ);
  }

  gp_Vec aNorm;

  if (theIsShaded)
  {
    aPolyNodes.SetValue(0, aNbPolyNodes);

    if (!MeshVS_Tool::GetAverageNormal(aPolyNodes, aNorm))
    {
      aNorm.SetCoord(0.0, 0.0, 1.0);
    }
  }

  // clang-format off
    for (Standard_Integer aNodeIdx = 0; aNodeIdx < aNbPolyNodes - 2; ++aNodeIdx) // triangulate polygon
  // clang-format on
  {
    for (Standard_Integer aSubIdx = 0; aSubIdx < 3; ++aSubIdx) // generate sub-triangle
    {
      if (theIsShaded)
      {
        theArray->AddVertex(aPolyNodes.Value(3 * (aSubIdx == 0 ? 0 : (aNodeIdx + aSubIdx)) + 1),
                            aPolyNodes.Value(3 * (aSubIdx == 0 ? 0 : (aNodeIdx + aSubIdx)) + 2),
                            aPolyNodes.Value(3 * (aSubIdx == 0 ? 0 : (aNodeIdx + aSubIdx)) + 3),
                            aNorm.X(),
                            aNorm.Y(),
                            aNorm.Z());
      }
      else
      {
        theArray->AddVertex(aPolyNodes.Value(3 * (aSubIdx == 0 ? 0 : (aNodeIdx + aSubIdx)) + 1),
                            aPolyNodes.Value(3 * (aSubIdx == 0 ? 0 : (aNodeIdx + aSubIdx)) + 2),
                            aPolyNodes.Value(3 * (aSubIdx == 0 ? 0 : (aNodeIdx + aSubIdx)) + 3));
      }
    }
  }
}
} // namespace

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

MeshVS_MeshPrsBuilder::MeshVS_MeshPrsBuilder(const Handle(MeshVS_Mesh)&       Parent,
                                             const Standard_Integer&          DisplayModeMask,
                                             const Handle(MeshVS_DataSource)& DS,
                                             const Standard_Integer           Id,
                                             const MeshVS_BuilderPriority&    Priority)
    : MeshVS_PrsBuilder(Parent, DisplayModeMask, DS, Id, Priority)
{
}

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

void MeshVS_MeshPrsBuilder::Build(const Handle(Prs3d_Presentation)& Prs,
                                  const TColStd_PackedMapOfInteger& IDs,
                                  TColStd_PackedMapOfInteger&       IDsToExclude,
                                  const Standard_Boolean            IsElement,
                                  const Standard_Integer            DisplayMode) const
{
  if (DisplayMode <= 0)
    return;

  Standard_Boolean HasHilightFlag = ((DisplayMode & MeshVS_DMF_HilightPrs) != 0);
  Standard_Integer Extent         = IDs.Extent();

  if (HasHilightFlag && Extent == 1)
    BuildHilightPrs(Prs, IDs, IsElement);
  else if (IsElement)
    BuildElements(Prs, IDs, IDsToExclude, DisplayMode);
  else
    BuildNodes(Prs, IDs, IDsToExclude, DisplayMode);
}

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

void MeshVS_MeshPrsBuilder::BuildNodes(const Handle(Prs3d_Presentation)& Prs,
                                       const TColStd_PackedMapOfInteger& IDs,
                                       TColStd_PackedMapOfInteger&       IDsToExclude,
                                       const Standard_Integer            DisplayMode) const
{
  Handle(MeshVS_DataSource)        aSource   = GetDataSource();
  Handle(MeshVS_Drawer)            aDrawer   = GetDrawer();
  Handle(Graphic3d_AspectMarker3d) aNodeMark = MeshVS_Tool::CreateAspectMarker3d(GetDrawer());
  if (aSource.IsNull() || aDrawer.IsNull() || aNodeMark.IsNull())
    return;

  Standard_Boolean DisplayFreeNodes = Standard_True;
  aDrawer->GetBoolean(MeshVS_DA_DisplayNodes, DisplayFreeNodes);
  Standard_Boolean HasSelectFlag  = ((DisplayMode & MeshVS_DMF_SelectionPrs) != 0);
  Standard_Boolean HasHilightFlag = ((DisplayMode & MeshVS_DMF_HilightPrs) != 0);

  Standard_Real        aCoordsBuf[3];
  TColStd_Array1OfReal aCoords(*aCoordsBuf, 1, 3);
  Standard_Integer     NbNodes;
  MeshVS_EntityType    aType;

  if (!DisplayFreeNodes)
    return;

  TColStd_PackedMapOfInteger anIDs;
  anIDs.Assign(IDs);
  if (!HasSelectFlag && !HasHilightFlag)
  {
    // subtract the hidden nodes and ids to exclude (to minimize allocated memory)
    Handle(TColStd_HPackedMapOfInteger) aHiddenNodes = myParentMesh->GetHiddenNodes();
    if (!aHiddenNodes.IsNull())
      anIDs.Subtract(aHiddenNodes->Map());
  }
  anIDs.Subtract(IDsToExclude);

  Standard_Integer upper = anIDs.Extent();
  if (upper <= 0)
    return;

  Handle(Graphic3d_ArrayOfPoints)         aNodePoints = new Graphic3d_ArrayOfPoints(upper);
  Standard_Integer                        k           = 0;
  TColStd_MapIteratorOfPackedMapOfInteger it(anIDs);
  for (; it.More(); it.Next())
  {
    Standard_Integer aKey = it.Key();
    if (aSource->GetGeom(aKey, Standard_False, aCoords, NbNodes, aType))
    {
      if (IsExcludingOn())
        IDsToExclude.Add(aKey);
      k++;
      aNodePoints->AddVertex(aCoords(1), aCoords(2), aCoords(3));
    }
  }

  if (k > 0)
  {
    Handle(Graphic3d_Group) aNodeGroup = Prs->NewGroup();
    aNodeGroup->SetPrimitivesAspect(aNodeMark);
    aNodeGroup->AddPrimitiveArray(aNodePoints);
  }
}

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

void MeshVS_MeshPrsBuilder::BuildElements(const Handle(Prs3d_Presentation)& Prs,
                                          const TColStd_PackedMapOfInteger& IDs,
                                          TColStd_PackedMapOfInteger&       IDsToExclude,
                                          const Standard_Integer            DisplayMode) const
{
  Standard_Integer maxnodes;

  Handle(MeshVS_DataSource) aSource = GetDataSource();
  if (aSource.IsNull())
    return;

  Handle(MeshVS_Drawer) aDrawer = GetDrawer();
  if (aDrawer.IsNull() || !aDrawer->GetInteger(MeshVS_DA_MaxFaceNodes, maxnodes) || maxnodes <= 0)
    return;

  //----------- extract useful display mode flags ----------
  Standard_Integer aDispMode = (DisplayMode & GetFlags());
  if ((aDispMode & MeshVS_DMF_DeformedMask) != 0)
  {
    aDispMode /= MeshVS_DMF_DeformedPrsWireFrame;
    // This transformation turns deformed mesh flags to real display modes
  }
  aDispMode &= MeshVS_DMF_OCCMask;
  //--------------------------------------------------------

  Standard_Real aShrinkCoef = 0.0;
  aDrawer->GetDouble(MeshVS_DA_ShrinkCoeff, aShrinkCoef);

  Standard_Boolean IsWireFrame    = (aDispMode == MeshVS_DMF_WireFrame),
                   IsShading      = (aDispMode == MeshVS_DMF_Shading),
                   IsShrink       = (aDispMode == MeshVS_DMF_Shrink),
                   HasHilightFlag = ((DisplayMode & MeshVS_DMF_HilightPrs) != 0),
                   HasSelectFlag  = ((DisplayMode & MeshVS_DMF_SelectionPrs) != 0),
                   IsMeshReflect = Standard_False, IsMeshAllowOverlap = Standard_False,
                   IsMeshSmoothShading = Standard_False;

  aDrawer->GetBoolean(MeshVS_DA_Reflection, IsMeshReflect);
  aDrawer->GetBoolean(MeshVS_DA_IsAllowOverlapped, IsMeshAllowOverlap);
  const Standard_Boolean IsReflect = (IsMeshReflect && !HasHilightFlag);
  aDrawer->GetBoolean(MeshVS_DA_SmoothShading, IsMeshSmoothShading);

  // display mode for highlighted prs of groups
  IsShrink  = (IsShrink && !HasHilightFlag);
  IsShading = (IsShading || HasHilightFlag);

  //---------- Creating AspectFillArea3d and AspectLine3d -------------
  Graphic3d_MaterialAspect AMat;
  aDrawer->GetMaterial(MeshVS_DA_FrontMaterial, AMat);
  if (!IsReflect)
  {
    AMat.SetAmbientColor(Quantity_NOC_BLACK);
    AMat.SetDiffuseColor(Quantity_NOC_BLACK);
    AMat.SetSpecularColor(Quantity_NOC_BLACK);
    AMat.SetEmissiveColor(Quantity_NOC_BLACK);
  }
  Handle(Graphic3d_AspectFillArea3d) aFill = MeshVS_Tool::CreateAspectFillArea3d(GetDrawer(), AMat);
  Handle(Graphic3d_AspectLine3d)     aBeam = MeshVS_Tool::CreateAspectLine3d(GetDrawer());
  //-------------------------------------------------------------------

  Standard_Boolean IsOverlapControl =
    !IsMeshAllowOverlap && (IsWireFrame || IsShading) && !HasSelectFlag;

  // subtract the hidden elements and ids to exclude (to minimize allocated memory)
  TColStd_PackedMapOfInteger anIDs;
  anIDs.Assign(IDs);
  Handle(TColStd_HPackedMapOfInteger) aHiddenElems = myParentMesh->GetHiddenElems();
  if (!aHiddenElems.IsNull())
    anIDs.Subtract(aHiddenElems->Map());
  anIDs.Subtract(IDsToExclude);

  Handle(MeshVS_HArray1OfSequenceOfInteger) aTopo;
  TColStd_MapIteratorOfPackedMapOfInteger   it(anIDs);

  Standard_Boolean showEdges = Standard_True;
  aDrawer->GetBoolean(MeshVS_DA_ShowEdges, showEdges);

  showEdges = IsWireFrame || showEdges;

  Standard_Integer* aNodesBuf  = (Standard_Integer*)alloca(maxnodes * sizeof(Standard_Integer));
  Standard_Real*    aCoordsBuf = (Standard_Real*)alloca(3 * maxnodes * sizeof(Standard_Real));

  TColStd_Array1OfInteger aNodes(*aNodesBuf, 1, maxnodes);
  TColStd_Array1OfReal    aCoords(*aCoordsBuf, 1, 3 * maxnodes);

  Standard_Integer aNbFacePrimitives = 0;
  Standard_Integer aNbVolmPrimitives = 0;
  Standard_Integer aNbEdgePrimitives = 0;
  Standard_Integer aNbLinkPrimitives = 0;

  MeshVS_EntityType aType;

  for (it.Reset(); it.More(); it.Next())
  {
    Standard_Integer aNbNodes = 0;

    if (!aSource->GetGeom(it.Key(), Standard_True, aCoords, aNbNodes, aType))
      continue;

    if (aType == MeshVS_ET_Volume)
    {
      if (aSource->Get3DGeom(it.Key(), aNbNodes, aTopo))
      {
        for (Standard_Integer aFaceIdx = aTopo->Lower(); aFaceIdx <= aTopo->Upper(); ++aFaceIdx)
        {
          const TColStd_SequenceOfInteger& aFaceNodes = aTopo->Value(aFaceIdx);

          if (showEdges) // add edge segments
          {
            aNbEdgePrimitives += aFaceNodes.Length();
          }

          if (IsShading || IsShrink) // add volumetric cell triangles
          {
            if (!HasSelectFlag)
              aNbVolmPrimitives += aFaceNodes.Length() - 2;
          }
        }
      }
    }
    else if (aType == MeshVS_ET_Link)
    {
      if (showEdges)
      {
        aNbLinkPrimitives += 1; // add link segment
      }
    }
    else if (aType == MeshVS_ET_Face)
    {
      if (showEdges)
      {
        aNbEdgePrimitives += aNbNodes; // add edge segments
      }

      if (!IsOverlapControl || IsShading)
      {
        if ((IsShading || IsShrink) && !HasSelectFlag)
        {
          aNbFacePrimitives += aNbNodes - 2; // add face triangles
        }
      }
    }
  }

  // Here we do not use indices arrays because they are not effective for some mesh
  // drawing modes: shrinking mode (displaces the vertices inside the polygon), 3D
  // cell rendering (normal interpolation is not always applicable - flat shading),
  // elemental coloring (color interpolation is impossible)
  Handle(Graphic3d_ArrayOfTriangles) aVolmTriangles =
    new Graphic3d_ArrayOfTriangles(aNbVolmPrimitives * 3, 0, IsReflect);
  Handle(Graphic3d_ArrayOfTriangles) aFaceTriangles =
    new Graphic3d_ArrayOfTriangles(aNbFacePrimitives * 3, 0, IsReflect);

  Handle(Graphic3d_ArrayOfSegments) aLinkSegments;
  Handle(Graphic3d_ArrayOfSegments) aEdgeSegments;

  if (showEdges)
  {
    aLinkSegments = new Graphic3d_ArrayOfSegments(aNbLinkPrimitives * 2);
    aEdgeSegments = new Graphic3d_ArrayOfSegments(aNbEdgePrimitives * 2);
  }

  TColStd_PackedMapOfInteger aCustomElements;

  Quantity_Color       anOldEdgeColor;
  Quantity_Color       anEdgeColor = aFill->EdgeColor();
  MeshVS_MapOfTwoNodes aLinkNodes;

  // Forbid drawings of edges which overlap with some links
  if (showEdges && IsOverlapControl)
  {
    for (it.Reset(); it.More(); it.Next())
    {
      if (aSource->GetGeomType(it.Key(), Standard_True, aType) && aType == MeshVS_ET_Link)
      {
        Standard_Integer aNbNodes;

        if (aSource->GetNodesByElement(it.Key(), aNodes, aNbNodes) && aNbNodes == 2)
        {
          aLinkNodes.Add(MeshVS_TwoNodes(aNodes(1), aNodes(2)));
        }
      }
    }
  }

  NCollection_Map<MeshVS_NodePair, MeshVS_SymmetricPairHasher> aSegmentMap;

  for (it.Reset(); it.More(); it.Next())
  {
    const Standard_Integer aKey = it.Key();

    Standard_Integer NbNodes;
    if (!aSource->GetGeom(aKey, Standard_True, aCoords, NbNodes, aType))
      continue;

    if (!aSource->GetNodesByElement(aKey, aNodes, NbNodes))
      continue;

    switch (aType)
    {
      case MeshVS_ET_Volume: {
        if (IsExcludingOn())
          IDsToExclude.Add(aKey);

        if (aSource->Get3DGeom(aKey, NbNodes, aTopo))
        {
          // Add wire-frame presentation (draw edges for shading mode as well)
          if (showEdges)
          {
            AddVolumePrs(aTopo,
                         aCoords,
                         NbNodes,
                         aEdgeSegments,
                         IsReflect,
                         IsShrink,
                         HasSelectFlag,
                         aShrinkCoef);
          }

          // Add shading presentation
          if ((IsShading || IsShrink) && !HasSelectFlag)
          {
            AddVolumePrs(aTopo,
                         aCoords,
                         NbNodes,
                         aVolmTriangles,
                         IsReflect,
                         IsShrink,
                         HasSelectFlag,
                         aShrinkCoef);
          }
        }
      }
      break;

      case MeshVS_ET_Link: {
        if (IsExcludingOn())
          IDsToExclude.Add(aKey);

        if (showEdges)
        {
          AddLinkPrs(aCoords, aLinkSegments, IsShrink || HasSelectFlag, aShrinkCoef);
        }
      }
      break;

      case MeshVS_ET_Face: {
        if (IsExcludingOn())
          IDsToExclude.Add(aKey);

        if (showEdges && IsOverlapControl)
        {
          Standard_Integer Last = 0;

          MeshVS_TwoNodes aTwoNodes(aNodes(1));

          for (Standard_Integer i = 1; i <= NbNodes; ++i)
          {
            if (i > 1)
              aTwoNodes.First = aTwoNodes.Second;

            aTwoNodes.Second = (i < NbNodes) ? aNodes(i + 1) : aNodes(1);

            if (aLinkNodes.Contains(aTwoNodes))
            {
              for (Standard_Integer aNodeIdx = Last + 1; aNodeIdx < i; ++aNodeIdx)
              {
                const Standard_Integer aNextIdx = aNodeIdx + 1;

                aEdgeSegments->AddVertex(aCoords(3 * aNodeIdx - 2),
                                         aCoords(3 * aNodeIdx - 1),
                                         aCoords(3 * aNodeIdx));
                aEdgeSegments->AddVertex(aCoords(3 * aNextIdx - 2),
                                         aCoords(3 * aNextIdx - 1),
                                         aCoords(3 * aNextIdx));
              }

              Last = i;
            }
          }

          if (NbNodes - Last > 0)
          {
            for (Standard_Integer aNodeIdx = Last; aNodeIdx < NbNodes; ++aNodeIdx)
            {
              const Standard_Integer aNextIdx = (aNodeIdx + 1) % NbNodes;

              const MeshVS_NodePair aSegment(aNodes(aNodeIdx + 1), aNodes(aNextIdx + 1));

              if (!aSegmentMap.Contains(aSegment))
              {
                aEdgeSegments->AddVertex(aCoords(3 * aNodeIdx + 1),
                                         aCoords(3 * aNodeIdx + 2),
                                         aCoords(3 * aNodeIdx + 3));

                aEdgeSegments->AddVertex(aCoords(3 * aNextIdx + 1),
                                         aCoords(3 * aNextIdx + 2),
                                         aCoords(3 * aNextIdx + 3));

                aSegmentMap.Add(aSegment);
              }
            }
          }
        }

        if (!IsOverlapControl || IsShading)
        {
          if (!IsOverlapControl && showEdges)
          {
            AddFaceWirePrs(aCoords, NbNodes, aEdgeSegments, IsShrink || HasSelectFlag, aShrinkCoef);
          }

          if ((IsShading || IsShrink) && !HasSelectFlag)
          {
            AddFaceSolidPrs(aKey,
                            aCoords,
                            NbNodes,
                            maxnodes,
                            aFaceTriangles,
                            IsReflect,
                            IsShrink || HasSelectFlag,
                            aShrinkCoef,
                            IsMeshSmoothShading);
          }
        }
      }
      break;

      default: {
        aCustomElements.Add(aKey);
      }
    }
  }

  if (IsShrink)
  {
    anOldEdgeColor = anEdgeColor;
    aFill->SetEdgeColor(Quantity_NOC_BLACK);
  }

  // std::cout << "Actual extents: " << std::endl
  //   << "Face tris: " << aFaceTriangles->ItemNumber() << " from " << aNbFacePrimitives <<
  //   std::endl
  //   << "Volm tris: " << aVolmTriangles->ItemNumber()  << " from " << aNbVolmPrimitives <<
  //   std::endl
  //   << "Face segs: " << aEdgeSegments->ItemNumber()  << " from " << aNbEdgePrimitives <<
  //   std::endl
  //   << "Link segs: " << aLinkSegments->ItemNumber()  << " from " << aNbLinkPrimitives <<
  //   std::endl;

  DrawArrays(Prs,
             aFaceTriangles,
             aEdgeSegments,
             aLinkSegments,
             aVolmTriangles,
             !showEdges,
             HasSelectFlag,
             aFill,
             aBeam);

  if (!aCustomElements.IsEmpty())
    CustomBuild(Prs, aCustomElements, IDsToExclude, DisplayMode);

  if (IsShrink)
    aFill->SetEdgeColor(anOldEdgeColor);
}

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

void MeshVS_MeshPrsBuilder::BuildHilightPrs(const Handle(Prs3d_Presentation)& Prs,
                                            const TColStd_PackedMapOfInteger& IDs,
                                            const Standard_Boolean            IsElement) const
{
  Standard_Integer maxnodes;

  Handle(MeshVS_DataSource) aSource = GetDataSource();
  if (aSource.IsNull() || IDs.IsEmpty())
    return;

  Handle(MeshVS_Drawer) aDrawer = GetDrawer();
  if (aDrawer.IsNull() || !aDrawer->GetInteger(MeshVS_DA_MaxFaceNodes, maxnodes) || maxnodes <= 0)
    return;

  MeshVS_Buffer        aCoordsBuf(3 * maxnodes * sizeof(Standard_Real));
  TColStd_Array1OfReal aCoords(aCoordsBuf, 1, 3 * maxnodes);

  Graphic3d_MaterialAspect AMat;
  aDrawer->GetMaterial(MeshVS_DA_FrontMaterial, AMat);
  AMat.SetAmbientColor(Quantity_NOC_BLACK);
  AMat.SetDiffuseColor(Quantity_NOC_BLACK);
  AMat.SetSpecularColor(Quantity_NOC_BLACK);
  AMat.SetEmissiveColor(Quantity_NOC_BLACK);

  Handle(Graphic3d_AspectFillArea3d) aFill = MeshVS_Tool::CreateAspectFillArea3d(GetDrawer(), AMat);
  Handle(Graphic3d_AspectLine3d)     aBeam = MeshVS_Tool::CreateAspectLine3d(GetDrawer());
  Handle(Graphic3d_AspectMarker3d)   aNodeMark = MeshVS_Tool::CreateAspectMarker3d(GetDrawer());

  // Hilight one element or node
  TColStd_MapIteratorOfPackedMapOfInteger it(IDs);
  Standard_Integer                        ID = it.Key(), NbNodes;
  MeshVS_EntityType                       aType;

  if (!aSource->GetGeom(ID, IsElement, aCoords, NbNodes, aType))
    return;

  Handle(Graphic3d_Group) aHilightGroup = Prs->NewGroup();

  switch (aType)
  {
    case MeshVS_ET_Node: {
      aHilightGroup->SetPrimitivesAspect(aNodeMark);
      Handle(Graphic3d_ArrayOfPoints) anArrayOfPoints = new Graphic3d_ArrayOfPoints(1);
      anArrayOfPoints->AddVertex(aCoords(1), aCoords(2), aCoords(3));
      aHilightGroup->AddPrimitiveArray(anArrayOfPoints);
    }
    break;

    case MeshVS_ET_Link: {
      aHilightGroup->SetPrimitivesAspect(aBeam);
      Handle(Graphic3d_ArrayOfSegments) aPrims = new Graphic3d_ArrayOfSegments(2);
      aPrims->AddVertex(aCoords(1), aCoords(2), aCoords(3));
      aPrims->AddVertex(aCoords(4), aCoords(5), aCoords(6));
      aHilightGroup->AddPrimitiveArray(aPrims);
    }
    break;

    case MeshVS_ET_Face:
      if (NbNodes > 0)
      {
        aHilightGroup->SetPrimitivesAspect(aFill);
        Handle(Graphic3d_ArrayOfPolygons) aPrims = new Graphic3d_ArrayOfPolygons(NbNodes);
        for (Standard_Integer k = 1; k <= NbNodes; k++)
          aPrims->AddVertex(aCoords(3 * k - 2), aCoords(3 * k - 1), aCoords(3 * k));
        aHilightGroup->AddPrimitiveArray(aPrims);
      }
      break;

    case MeshVS_ET_Volume:
      if (NbNodes > 0)
      {
        Handle(MeshVS_HArray1OfSequenceOfInteger) aTopo;

        aHilightGroup->SetPrimitivesAspect(aFill);

        if (aSource->Get3DGeom(ID, NbNodes, aTopo))
        {
          const Standard_Integer up      = aTopo->Upper();
          const Standard_Integer lo      = aTopo->Lower();
          Standard_Integer       nbnodes = 0, i, j;
          for (i = lo; i <= up; i++)
            nbnodes += aTopo->Value(i).Length();

          Handle(Graphic3d_ArrayOfPolygons) aPrims =
            new Graphic3d_ArrayOfPolygons(nbnodes, aTopo->Length());
          for (i = lo; i <= up; i++)
          {
            const TColStd_SequenceOfInteger& aSeq = aTopo->Value(i);
            const Standard_Integer           m    = aSeq.Length();
            aPrims->AddBound(m);
            for (j = 1; j <= m; j++)
            {
              const Standard_Integer ind = 3 * aSeq.Value(j);
              aPrims->AddVertex(aCoords(ind + 1), aCoords(ind + 2), aCoords(ind + 3));
            }
          }
          aHilightGroup->AddPrimitiveArray(aPrims);
        }
      }
      break;

    default: {
      TColStd_PackedMapOfInteger tmp;
      CustomBuild(Prs, IDs, tmp, MeshVS_DMF_HilightPrs);
    }
    break;
  }
}

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

void MeshVS_MeshPrsBuilder::AddLinkPrs(const TColStd_Array1OfReal&              theCoords,
                                       const Handle(Graphic3d_ArrayOfSegments)& theSegments,
                                       const Standard_Boolean                   theIsShrinked,
                                       const Standard_Real                      theShrinkCoef) const
{
  Standard_Real aX1 = theCoords(1);
  Standard_Real aY1 = theCoords(2);
  Standard_Real aZ1 = theCoords(3);
  Standard_Real aX2 = theCoords(4);
  Standard_Real aY2 = theCoords(5);
  Standard_Real aZ2 = theCoords(6);

  if (theIsShrinked)
  {
    const Standard_Real xG = (aX1 + aX2) * 0.5;
    const Standard_Real yG = (aY1 + aY2) * 0.5;
    const Standard_Real zG = (aZ1 + aZ2) * 0.5;

    aX1 = (aX1 - xG) * theShrinkCoef + xG;
    aY1 = (aY1 - yG) * theShrinkCoef + yG;
    aZ1 = (aZ1 - zG) * theShrinkCoef + zG;

    aX2 = 2.0 * xG - aX1;
    aY2 = 2.0 * yG - aY1;
    aZ2 = 2.0 * zG - aZ1;
  }

  theSegments->AddVertex(aX1, aY1, aZ1);
  theSegments->AddVertex(aX2, aY2, aZ2);
}

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

void MeshVS_MeshPrsBuilder::AddFaceWirePrs(const TColStd_Array1OfReal&              theCoords,
                                           const Standard_Integer                   theNbNodes,
                                           const Handle(Graphic3d_ArrayOfSegments)& theSegments,
                                           const Standard_Boolean                   theIsShrinked,
                                           const Standard_Real theShrinkingCoef) const
{
  Standard_Real aCenterX = 0.0;
  Standard_Real aCenterY = 0.0;
  Standard_Real aCenterZ = 0.0;

  if (theIsShrinked)
  {
    CalculateCenter(theCoords, theNbNodes, aCenterX, aCenterY, aCenterZ);
  }

  NCollection_Vector<gp_XYZ> aNodes(theNbNodes);

  for (Standard_Integer aNodeIdx = 0; aNodeIdx < theNbNodes; ++aNodeIdx)
  {
    gp_XYZ aPnt(theCoords(3 * aNodeIdx + 1),
                theCoords(3 * aNodeIdx + 2),
                theCoords(3 * aNodeIdx + 3));

    if (theIsShrinked)
    {
      aPnt.SetX((aPnt.X() - aCenterX) * theShrinkingCoef + aCenterX);
      aPnt.SetY((aPnt.Y() - aCenterY) * theShrinkingCoef + aCenterY);
      aPnt.SetZ((aPnt.Z() - aCenterZ) * theShrinkingCoef + aCenterZ);
    }

    aNodes.Append(aPnt);
  }

  for (Standard_Integer aNodeIdx = 0; aNodeIdx < theNbNodes; ++aNodeIdx)
  {
    theSegments->AddVertex(aNodes.Value(aNodeIdx).X(),
                           aNodes.Value(aNodeIdx).Y(),
                           aNodes.Value(aNodeIdx).Z());

    const Standard_Integer aNextIdx = (aNodeIdx + 1) % theNbNodes;

    theSegments->AddVertex(aNodes.Value(aNextIdx).X(),
                           aNodes.Value(aNextIdx).Y(),
                           aNodes.Value(aNextIdx).Z());
  }
}

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

void MeshVS_MeshPrsBuilder::AddFaceSolidPrs(const Standard_Integer                    theID,
                                            const TColStd_Array1OfReal&               theCoords,
                                            const Standard_Integer                    theNbNodes,
                                            const Standard_Integer                    theMaxNodes,
                                            const Handle(Graphic3d_ArrayOfTriangles)& theTriangles,
                                            const Standard_Boolean                    theIsShaded,
                                            const Standard_Boolean                    theIsShrinked,
                                            const Standard_Real    theShrinkingCoef,
                                            const Standard_Boolean theIsSmoothShading) const
{
  Handle(MeshVS_DataSource) aDataSource = myParentMesh->GetDataSource();

  if (aDataSource.IsNull())
    return;

  Standard_Real aCenterX = 0.0;
  Standard_Real aCenterY = 0.0;
  Standard_Real aCenterZ = 0.0;
  Standard_Real aNormalX = 0.0;
  Standard_Real aNormalY = 0.0;
  Standard_Real aNormalZ = 0.0;

  if (theIsShrinked)
  {
    CalculateCenter(theCoords, theNbNodes, aCenterX, aCenterY, aCenterZ);
  }

  NCollection_Vector<gp_XYZ> aVertexNormals(theMaxNodes);

  if (theIsShaded)
  {
    if (theIsSmoothShading)
    {
      for (Standard_Integer aNodeIdx = 1; aNodeIdx <= theNbNodes; ++aNodeIdx)
      {
        if (!aDataSource->GetNodeNormal(aNodeIdx, theID, aNormalX, aNormalY, aNormalZ))
          break;

        aVertexNormals.Append(gp_XYZ(aNormalX, aNormalY, aNormalZ));
      }
    }

    if (!theIsSmoothShading || aVertexNormals.Size() != theNbNodes)
    {
      aDataSource->GetNormal(theID, theMaxNodes, aNormalX, aNormalY, aNormalZ);
    }
  }

  NCollection_Vector<gp_XYZ> aNodes(theMaxNodes);

  for (Standard_Integer aNodeIdx = 0; aNodeIdx < theNbNodes; ++aNodeIdx)
  {
    gp_XYZ aPnt(theCoords(3 * aNodeIdx + 1),
                theCoords(3 * aNodeIdx + 2),
                theCoords(3 * aNodeIdx + 3));

    if (theIsShrinked)
    {
      aPnt.SetX((aPnt.X() - aCenterX) * theShrinkingCoef + aCenterX);
      aPnt.SetY((aPnt.Y() - aCenterY) * theShrinkingCoef + aCenterY);
      aPnt.SetZ((aPnt.Z() - aCenterZ) * theShrinkingCoef + aCenterZ);
    }

    aNodes.Append(aPnt);
  }

  // Triangulate polygon
  for (Standard_Integer aNodeIdx = 0; aNodeIdx < theNbNodes - 2; ++aNodeIdx)
  {
    for (Standard_Integer aSubIdx = 0; aSubIdx < 3; ++aSubIdx)
    {
      if (theIsShaded)
      {
        if (theIsSmoothShading && aVertexNormals.Size() == theNbNodes)
        {
          aNormalX = aVertexNormals.Value(aSubIdx == 0 ? 0 : (aNodeIdx + aSubIdx)).X();
          aNormalY = aVertexNormals.Value(aSubIdx == 0 ? 0 : (aNodeIdx + aSubIdx)).Y();
          aNormalZ = aVertexNormals.Value(aSubIdx == 0 ? 0 : (aNodeIdx + aSubIdx)).Z();
        }

        theTriangles->AddVertex(aNodes.Value(aSubIdx == 0 ? 0 : (aNodeIdx + aSubIdx)).X(),
                                aNodes.Value(aSubIdx == 0 ? 0 : (aNodeIdx + aSubIdx)).Y(),
                                aNodes.Value(aSubIdx == 0 ? 0 : (aNodeIdx + aSubIdx)).Z(),
                                aNormalX,
                                aNormalY,
                                aNormalZ);
      }
      else
      {
        theTriangles->AddVertex(aNodes.Value(aSubIdx == 0 ? 0 : (aNodeIdx + aSubIdx)).X(),
                                aNodes.Value(aSubIdx == 0 ? 0 : (aNodeIdx + aSubIdx)).Y(),
                                aNodes.Value(aSubIdx == 0 ? 0 : (aNodeIdx + aSubIdx)).Z());
      }
    }
  }
}

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

void MeshVS_MeshPrsBuilder::AddVolumePrs(const Handle(MeshVS_HArray1OfSequenceOfInteger)& theTopo,
                                         const TColStd_Array1OfReal&                      theNodes,
                                         const Standard_Integer                     theNbNodes,
                                         const Handle(Graphic3d_ArrayOfPrimitives)& theArray,
                                         const Standard_Boolean                     theIsShaded,
                                         const Standard_Boolean                     theIsShrinked,
                                         const Standard_Boolean                     theIsSelected,
                                         const Standard_Real                        theShrinkCoef)
{
  Standard_Real aCenter[] = {0.0, 0.0, 0.0};

  Standard_Integer aLow = theNodes.Lower();

  if (theTopo.IsNull() || theArray.IsNull())
    return;

  if (theIsShrinked)
  {
    for (Standard_Integer aNodeIdx = 0; aNodeIdx < 3 * theNbNodes; ++aNodeIdx)
    {
      aCenter[aNodeIdx % 3] += theNodes.Value(aLow + aNodeIdx);
    }

    aCenter[0] /= theNbNodes;
    aCenter[1] /= theNbNodes;
    aCenter[2] /= theNbNodes;
  }

  Standard_Boolean aIsPolygons = theArray->IsKind(STANDARD_TYPE(Graphic3d_ArrayOfTriangles));

  if (aIsPolygons)
  {
    for (Standard_Integer aFaceIdx = theTopo->Lower(); aFaceIdx <= theTopo->Upper(); ++aFaceIdx)
    {
      ProcessFace(theTopo->Value(aFaceIdx),
                  theNodes,
                  aCenter,
                  theShrinkCoef,
                  theIsShrinked,
                  theIsShaded,
                  theArray);
    }
  }
  else if (theIsSelected)
  {
    for (Standard_Integer aFaceIdx = theTopo->Lower(), topoup = theTopo->Upper();
         aFaceIdx <= topoup;
         ++aFaceIdx)
    {
      const TColStd_SequenceOfInteger& aFaceNodes = theTopo->Value(aFaceIdx);

      Standard_Real aFaceCenter[] = {0.0, 0.0, 0.0};

      for (Standard_Integer aNodeIdx = 1; aNodeIdx <= aFaceNodes.Length(); ++aNodeIdx)
      {
        for (Standard_Integer aAxisIdx = 0; aAxisIdx < 3; ++aAxisIdx)
        {
          aFaceCenter[aAxisIdx] += theNodes.Value(aLow + 3 * aFaceNodes.Value(aNodeIdx) + aAxisIdx);
        }
      }

      aFaceCenter[0] /= aFaceNodes.Length();
      aFaceCenter[1] /= aFaceNodes.Length();
      aFaceCenter[2] /= aFaceNodes.Length();

      for (Standard_Integer aNodeIdx = 0, aNbNodes = aFaceNodes.Length(); aNodeIdx < aNbNodes;
           ++aNodeIdx)
      {
        for (Standard_Integer aSubIdx = 0; aSubIdx < 2; ++aSubIdx) // add segment
        {
          Standard_Integer anIdx = aFaceNodes.Value((aNodeIdx + aSubIdx) % aNbNodes + 1);

          Standard_Real aX = theNodes.Value(aLow + 3 * anIdx + 0);
          Standard_Real aY = theNodes.Value(aLow + 3 * anIdx + 1);
          Standard_Real aZ = theNodes.Value(aLow + 3 * anIdx + 2);

          theArray->AddVertex(aFaceCenter[0] + theShrinkCoef * (aX - aFaceCenter[0]),
                              aFaceCenter[1] + theShrinkCoef * (aY - aFaceCenter[1]),
                              aFaceCenter[2] + theShrinkCoef * (aZ - aFaceCenter[2]));
        }
      }
    }
  }
  else
  {
    // Find all pairs of nodes (edges) to draw (will be drawn only once)
    NCollection_Map<MeshVS_NodePair, MeshVS_SymmetricPairHasher> aEdgeMap;

    for (Standard_Integer aFaceIdx = theTopo->Lower(), topoup = theTopo->Upper();
         aFaceIdx <= topoup;
         ++aFaceIdx)
    {
      const TColStd_SequenceOfInteger& aFaceNodes = theTopo->Value(aFaceIdx);

      for (Standard_Integer aNodeIdx = 0, aNbNodes = aFaceNodes.Length(); aNodeIdx < aNbNodes;
           ++aNodeIdx)
      {
        const Standard_Integer aNextIdx = (aNodeIdx + 1) % aNbNodes;

        aEdgeMap.Add(
          MeshVS_NodePair(aFaceNodes.Value(aNodeIdx + 1), aFaceNodes.Value(aNextIdx + 1)));
      }
    }

    // Draw edges
    for (NCollection_Map<MeshVS_NodePair, MeshVS_SymmetricPairHasher>::Iterator anIt(aEdgeMap);
         anIt.More();
         anIt.Next())
    {
      const Standard_Integer anIdx1 = aLow + 3 * anIt.Key().first;
      const Standard_Integer anIdx2 = aLow + 3 * anIt.Key().second;

      Standard_Real aX[] = {theNodes.Value(anIdx1 + 0), theNodes.Value(anIdx2 + 0)};
      Standard_Real aY[] = {theNodes.Value(anIdx1 + 1), theNodes.Value(anIdx2 + 1)};
      Standard_Real aZ[] = {theNodes.Value(anIdx1 + 2), theNodes.Value(anIdx2 + 2)};

      if (theIsShrinked)
      {
        for (Standard_Integer anAxisIdx = 0; anAxisIdx < 2; ++anAxisIdx)
        {
          aX[anAxisIdx] = aCenter[0] + theShrinkCoef * (aX[anAxisIdx] - aCenter[0]);
          aY[anAxisIdx] = aCenter[1] + theShrinkCoef * (aY[anAxisIdx] - aCenter[1]);
          aZ[anAxisIdx] = aCenter[2] + theShrinkCoef * (aZ[anAxisIdx] - aCenter[2]);
        }
      }

      theArray->AddVertex(aX[0], aY[0], aZ[0]);
      theArray->AddVertex(aX[1], aY[1], aZ[1]);
    }
  }
}

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

void MeshVS_MeshPrsBuilder::HowManyPrimitives(const Handle(MeshVS_HArray1OfSequenceOfInteger)& Topo,
                                              const Standard_Boolean AsPolygons,
                                              const Standard_Boolean IsSelect,
                                              const Standard_Integer NbNodes,
                                              Standard_Integer&      Vertices,
                                              Standard_Integer&      Bounds)
{
  if (!Topo.IsNull())
  {
    if (AsPolygons || IsSelect)
    {
      Standard_Integer B = Topo->Upper() - Topo->Lower() + 1;
      Bounds += B;
      for (Standard_Integer i = Topo->Lower(), n = Topo->Upper(); i <= n; i++)
        Vertices += Topo->Value(i).Length();

      if (IsSelect)
        Vertices += B;
    }
    else
    {
      Standard_Integer F = Topo->Upper() - Topo->Lower() + 1, E = NbNodes + F - 2;
      // number of edges is obtained by Euler's expression for polyhedrons

      Bounds += E;
      Vertices += 2 * E;
    }
  }
}

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

void MeshVS_MeshPrsBuilder::DrawArrays(const Handle(Prs3d_Presentation)&          Prs,
                                       const Handle(Graphic3d_ArrayOfPrimitives)& thePolygons,
                                       const Handle(Graphic3d_ArrayOfPrimitives)& theLines,
                                       const Handle(Graphic3d_ArrayOfPrimitives)& theLinkLines,
                                       const Handle(Graphic3d_ArrayOfPrimitives)& theVolumesInShad,
                                       const Standard_Boolean                    IsPolygonsEdgesOff,
                                       const Standard_Boolean                    IsSelected,
                                       const Handle(Graphic3d_AspectFillArea3d)& theFillAsp,
                                       const Handle(Graphic3d_AspectLine3d)&     theLineAsp) const
{
  if (theFillAsp.IsNull())
    return;

  Standard_Boolean IsFacePolygons = (!thePolygons.IsNull() && thePolygons->ItemNumber() > 0),
                   IsVolumePolygons =
                     (!theVolumesInShad.IsNull() && theVolumesInShad->ItemNumber() > 0),
                   IsPolygons      = IsFacePolygons || IsVolumePolygons,
                   IsPolylines     = (!theLines.IsNull() && theLines->ItemNumber() > 0),
                   IsLinkPolylines = (!theLinkLines.IsNull() && theLinkLines->ItemNumber() > 0);

  Quantity_Color anIntColor  = theFillAsp->InteriorColor();
  Quantity_Color aBackColor  = theFillAsp->BackInteriorColor();
  Quantity_Color anEdgeColor = theFillAsp->EdgeColor();
  Standard_Real  aWidth      = theFillAsp->EdgeWidth();

  Standard_Boolean      isSupressBackFaces = Standard_False;
  Handle(MeshVS_Drawer) aDrawer            = GetDrawer();
  if (!aDrawer.IsNull())
  {
    aDrawer->GetBoolean(MeshVS_DA_SupressBackFaces, isSupressBackFaces);
  }

  if (IsPolygons && theFillAsp->FrontMaterial().Transparency() < 0.01)
  {
    Handle(Graphic3d_Group) aGroup = Prs->NewGroup();
    aGroup->SetClosed(isSupressBackFaces == Standard_True);
    Handle(Graphic3d_AspectFillArea3d) aFillAsp = new Graphic3d_AspectFillArea3d(*theFillAsp);
    // if ( IsPolygonsEdgesOff )
    aFillAsp->SetEdgeOff();
    // else
    //   aFillAsp->SetEdgeOn ();

    if (anIntColor != aBackColor)
      aFillAsp->SetDistinguishOn();
    else
      aFillAsp->SetDistinguishOff();

    aGroup->SetPrimitivesAspect(aFillAsp);

    if (IsFacePolygons)
    {
      aGroup->AddPrimitiveArray(thePolygons);
    }

    if (IsVolumePolygons)
    {
      aGroup->AddPrimitiveArray(theVolumesInShad);
    }
  }

  if (IsPolylines && !IsPolygonsEdgesOff)
  {
    Handle(Graphic3d_Group) aLGroup = Prs->NewGroup();

    if (IsSelected)
      aLGroup->SetPrimitivesAspect(theLineAsp);
    else
    {
      aLGroup->SetPrimitivesAspect(theFillAsp);
      aLGroup->SetPrimitivesAspect(
        new Graphic3d_AspectLine3d(anEdgeColor, Aspect_TOL_SOLID, aWidth));
    }
    aLGroup->AddPrimitiveArray(theLines);
  }

  if (IsLinkPolylines)
  {
    Handle(Graphic3d_Group) aBeamGroup = Prs->NewGroup();
    if (!IsSelected)
      aBeamGroup->SetPrimitivesAspect(theFillAsp);
    aBeamGroup->SetPrimitivesAspect(theLineAsp);
    aBeamGroup->AddPrimitiveArray(theLinkLines);
  }

  if (IsPolygons && theFillAsp->FrontMaterial().Transparency() >= 0.01)
  {
    Handle(Graphic3d_Group) aGroup = Prs->NewGroup();
    aGroup->SetClosed(isSupressBackFaces == Standard_True);
    Handle(Graphic3d_AspectFillArea3d) aFillAsp = new Graphic3d_AspectFillArea3d(*theFillAsp);
    // if ( IsPolygonsEdgesOff )
    aFillAsp->SetEdgeOff();
    // else
    //   aFillAsp->SetEdgeOn ();

    if (anIntColor != aBackColor)
      aFillAsp->SetDistinguishOn();
    else
      aFillAsp->SetDistinguishOff();

    aGroup->SetPrimitivesAspect(aFillAsp);

    if (IsFacePolygons)
    {
      aGroup->AddPrimitiveArray(thePolygons);
    }

    if (IsVolumePolygons)
    {
      aGroup->AddPrimitiveArray(theVolumesInShad);
    }
  }
}

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

void MeshVS_MeshPrsBuilder::CalculateCenter(const TColStd_Array1OfReal& theCoords,
                                            const Standard_Integer      NbNodes,
                                            Standard_Real&              xG,
                                            Standard_Real&              yG,
                                            Standard_Real&              zG)
{
  xG = yG = zG = 0;
  if (NbNodes < 4)
  {
    for (Standard_Integer k = 1; k <= NbNodes; k++)
    {
      xG += theCoords(3 * k - 2);
      yG += theCoords(3 * k - 1);
      zG += theCoords(3 * k);
    }
    xG /= Standard_Real(NbNodes);
    yG /= Standard_Real(NbNodes);
    zG /= Standard_Real(NbNodes);
  }
  else
  {
    Standard_Integer a = 1, b = 3;
    xG = (theCoords(3 * a - 2) + theCoords(3 * b - 2)) / 2.0;
    yG = (theCoords(3 * a - 1) + theCoords(3 * b - 1)) / 2.0;
    zG = (theCoords(3 * a) + theCoords(3 * b)) / 2.0;
  }
}
