// Author: Kirill Gavrilov
// Copyright (c) 2019 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 <RWGltf_TriangulationReader.hxx>

#include <Message.hxx>
#include <NCollection_LinearVector.hxx>
#include <OSD_FileSystem.hxx>
#include <RWGltf_GltfLatePrimitiveArray.hxx>
#include <RWGltf_GltfPrimArrayData.hxx>
#include <Standard_ArrayStreamBuffer.hxx>
#include <Standard_ReadBuffer.hxx>

#ifdef HAVE_DRACO
  #include <Standard_WarningsDisable.hxx>
  #include <draco/compression/decode.h>
  #include <Standard_WarningsRestore.hxx>
#endif

namespace
{
static const int   THE_LOWER_TRI_INDEX  = 1;
static const int   THE_LOWER_NODE_INDEX = 1;
static const float THE_NORMAL_PREC2     = 0.001f;

#ifdef HAVE_DRACO
//! Return array type from Draco attribute type.
static RWGltf_GltfArrayType arrayTypeFromDraco(draco::GeometryAttribute::Type theType)
{
  switch (theType)
  {
    case draco::GeometryAttribute::POSITION:
      return RWGltf_GltfArrayType_Position;
    case draco::GeometryAttribute::NORMAL:
      return RWGltf_GltfArrayType_Normal;
    case draco::GeometryAttribute::COLOR:
      return RWGltf_GltfArrayType_Color;
    case draco::GeometryAttribute::TEX_COORD:
      return RWGltf_GltfArrayType_TCoord0;
    default:
      return RWGltf_GltfArrayType_UNKNOWN;
  }
}

//! Return layout from Draco number of components.
static RWGltf_GltfAccessorLayout layoutFromDraco(int8_t theNbComps)
{
  switch (theNbComps)
  {
    case 1:
      return RWGltf_GltfAccessorLayout_Scalar;
    case 2:
      return RWGltf_GltfAccessorLayout_Vec2;
    case 3:
      return RWGltf_GltfAccessorLayout_Vec3;
    case 4:
      return RWGltf_GltfAccessorLayout_Vec4;
  }
  return RWGltf_GltfAccessorLayout_UNKNOWN;
}

//! Return component type from Draco data type.
static RWGltf_GltfAccessorCompType compTypeFromDraco(draco::DataType theType)
{
  switch (theType)
  {
    case draco::DT_INT8:
      return RWGltf_GltfAccessorCompType_Int8;
    case draco::DT_UINT8:
      return RWGltf_GltfAccessorCompType_UInt8;
    case draco::DT_INT16:
      return RWGltf_GltfAccessorCompType_Int16;
    case draco::DT_UINT16:
      return RWGltf_GltfAccessorCompType_UInt16;
    case draco::DT_INT32:
    case draco::DT_UINT32:
      return RWGltf_GltfAccessorCompType_UInt32;
    // case draco::DT_INT64:
    // case draco::DT_UINT64:
    case draco::DT_FLOAT32:
      return RWGltf_GltfAccessorCompType_Float32;
    // case draco::DT_FLOAT64:
    // case draco::DT_BOOL:
    default:
      return RWGltf_GltfAccessorCompType_UNKNOWN;
  }
}
#endif
} // namespace

IMPLEMENT_STANDARD_RTTIEXT(RWGltf_TriangulationReader, RWMesh_TriangulationReader)

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

RWGltf_TriangulationReader::RWGltf_TriangulationReader() = default;

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

void RWGltf_TriangulationReader::reportError(const TCollection_AsciiString& theText) const
{
  Message::SendFail(TCollection_AsciiString("File '") + myFileName + "' defines invalid glTF!\n"
                    + theText);
}

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

bool RWGltf_TriangulationReader::LoadStreamData(
  const occ::handle<RWMesh_TriangulationSource>& theSourceMesh,
  const occ::handle<Poly_Triangulation>&         theDestMesh) const
{
  Standard_ASSERT_RETURN(!theDestMesh.IsNull(),
                         "The destination mesh should be initialized before loading data to it",
                         false);
  theDestMesh->Clear();
  theDestMesh->SetDoublePrecision(myIsDoublePrecision);

  if (!loadStreamData(theSourceMesh, theDestMesh))
  {
    theDestMesh->Clear();
    return false;
  }
  if (!finalizeLoading(theSourceMesh, theDestMesh))
  {
    theDestMesh->Clear();
    return false;
  }
  return true;
}

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

bool RWGltf_TriangulationReader::readStreamData(
  const occ::handle<RWGltf_GltfLatePrimitiveArray>& theSourceGltfMesh,
  const RWGltf_GltfPrimArrayData&                   theGltfData,
  const occ::handle<Poly_Triangulation>&            theDestMesh) const
{
  Standard_ArrayStreamBuffer aStreamBuffer((const char*)theGltfData.StreamData->Data(),
                                           theGltfData.StreamData->Size());
  std::istream               aStream(&aStreamBuffer);
  aStream.seekg((std::streamoff)theGltfData.StreamOffset, std::ios_base::beg);
  return readBuffer(theSourceGltfMesh,
                    theDestMesh,
                    aStream,
                    theGltfData.Accessor,
                    theGltfData.Type);
}

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

bool RWGltf_TriangulationReader::readFileData(
  const occ::handle<RWGltf_GltfLatePrimitiveArray>& theSourceGltfMesh,
  const RWGltf_GltfPrimArrayData&                   theGltfData,
  const occ::handle<Poly_Triangulation>&            theDestMesh,
  const occ::handle<OSD_FileSystem>&                theFileSystem) const
{
  const occ::handle<OSD_FileSystem>& aFileSystem =
    !theFileSystem.IsNull() ? theFileSystem : OSD_FileSystem::DefaultFileSystem();
  std::shared_ptr<std::istream> aSharedStream =
    aFileSystem->OpenIStream(theGltfData.StreamUri,
                             std::ios::in | std::ios::binary,
                             theGltfData.StreamOffset);
  if (aSharedStream.get() == nullptr)
  {
    reportError(TCollection_AsciiString("Buffer '") + theSourceGltfMesh->Id()
                + "' refers to invalid file '" + theGltfData.StreamUri + "'.");
    return false;
  }

  if (!readBuffer(theSourceGltfMesh,
                  theDestMesh,
                  *aSharedStream.get(),
                  theGltfData.Accessor,
                  theGltfData.Type))
  {
    return false;
  }
  return true;
}

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

bool RWGltf_TriangulationReader::loadStreamData(
  const occ::handle<RWMesh_TriangulationSource>& theSourceMesh,
  const occ::handle<Poly_Triangulation>&         theDestMesh,
  bool                                           theToResetStream) const
{
  const occ::handle<RWGltf_GltfLatePrimitiveArray> aSourceGltfMesh =
    occ::down_cast<RWGltf_GltfLatePrimitiveArray>(theSourceMesh);
  if (aSourceGltfMesh.IsNull()
      || aSourceGltfMesh->PrimitiveMode() == RWGltf_GltfPrimitiveMode_UNKNOWN)
  {
    return false;
  }
  bool wasLoaded = false;
  for (NCollection_Sequence<RWGltf_GltfPrimArrayData>::Iterator aDataIter(aSourceGltfMesh->Data());
       aDataIter.More();
       aDataIter.Next())
  {
    RWGltf_GltfPrimArrayData& aData = aDataIter.ChangeValue();
    if (aData.StreamData.IsNull())
    {
      continue;
    }
    if (!readStreamData(aSourceGltfMesh, aData, theDestMesh))
    {
      return false;
    }
    if (theToResetStream)
    {
      aData.StreamData.Nullify();
    }
    wasLoaded = true;
  }
  return wasLoaded;
}

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

bool RWGltf_TriangulationReader::readDracoBuffer(
  const occ::handle<RWGltf_GltfLatePrimitiveArray>& theSourceGltfMesh,
  const RWGltf_GltfPrimArrayData&                   theGltfData,
  const occ::handle<Poly_Triangulation>&            theDestMesh,
  const occ::handle<OSD_FileSystem>&                theFileSystem) const
{
  const TCollection_AsciiString&     aName = theSourceGltfMesh->Id();
  const occ::handle<OSD_FileSystem>& aFileSystem =
    !theFileSystem.IsNull() ? theFileSystem : OSD_FileSystem::DefaultFileSystem();
  std::shared_ptr<std::istream> aSharedStream =
    aFileSystem->OpenIStream(theGltfData.StreamUri,
                             std::ios::in | std::ios::binary,
                             theGltfData.StreamOffset);
  if (aSharedStream.get() == nullptr)
  {
    reportError(TCollection_AsciiString("Buffer '") + aName + "' refers to invalid file '"
                + theGltfData.StreamUri + "'.");
    return false;
  }

#ifdef HAVE_DRACO
  NCollection_LinearVector<char> aReadData;
  aReadData.Resize((size_t)theGltfData.StreamLength);
  aSharedStream->read(aReadData.begin(), (std::streamsize)theGltfData.StreamLength);
  if (!aSharedStream->good())
  {
    reportError(TCollection_AsciiString("Buffer '") + aName
                + "' refers to file that cannot be read '" + theGltfData.StreamUri + "'.");
    return false;
  }

  draco::DecoderBuffer aDracoBuf;
  aDracoBuf.Init(aReadData.begin(), aReadData.Size());

  draco::Decoder                                aDracoDecoder;
  draco::StatusOr<std::unique_ptr<draco::Mesh>> aDracoStat =
    aDracoDecoder.DecodeMeshFromBuffer(&aDracoBuf);
  if (!aDracoStat.ok() || aDracoStat.value().get() == nullptr)
  {
    reportError(TCollection_AsciiString("Buffer '") + aName
                + "' refers to Draco data that cannot be decoded '" + theGltfData.StreamUri + "'.");
    return false;
  }

  const int aNbNodes = (int)aDracoStat.value()->num_points();
  const int aNbTris  = (int)aDracoStat.value()->num_faces();
  if (aNbNodes < 0)
  {
    reportError(TCollection_AsciiString("Buffer '") + aName + "' defines an empty array.");
    return false;
  }
  if ((int64_t)aDracoStat.value()->num_points() > std::numeric_limits<int>::max())
  {
    reportError(TCollection_AsciiString("Buffer '") + aName + "' defines too big array.");
    return false;
  }
  if (!setNbPositionNodes(theDestMesh, aNbNodes))
  {
    return false;
  }

  if (aNbTris > 0 && !setNbTriangles(theDestMesh, aNbTris))
  {
    return false;
  }

  // copy vertex attributes
  for (int32_t anAttrIter = 0; anAttrIter < aDracoStat.value()->num_attributes(); ++anAttrIter)
  {
    const draco::PointAttribute*      anAttrib    = aDracoStat.value()->attribute(anAttrIter);
    const RWGltf_GltfArrayType        aWrapType   = arrayTypeFromDraco(anAttrib->attribute_type());
    const RWGltf_GltfAccessorLayout   aWrapLayout = layoutFromDraco(anAttrib->num_components());
    const RWGltf_GltfAccessorCompType aWrapCompType = compTypeFromDraco(anAttrib->data_type());
    switch (aWrapType)
    {
      case RWGltf_GltfArrayType_Position: {
        if (aWrapCompType != RWGltf_GltfAccessorCompType_Float32
            || aWrapLayout != RWGltf_GltfAccessorLayout_Vec3)
        {
          reportError(TCollection_AsciiString("Buffer '") + aName
                      + "' has unsupported position data type.");
          return false;
        }

        for (int aVertIter = 0; aVertIter < aNbNodes; ++aVertIter)
        {
          const NCollection_Vec3<float>* aVec3 = reinterpret_cast<const NCollection_Vec3<float>*>(
            anAttrib->GetAddressOfMappedIndex(draco::PointIndex(aVertIter)));
          if (aVec3 == nullptr)
          {
            reportError(TCollection_AsciiString("Buffer '") + aName + "' reading error.");
            return false;
          }

          gp_Pnt anXYZ(aVec3->x(), aVec3->y(), aVec3->z());
          myCoordSysConverter.TransformPosition(anXYZ.ChangeCoord());
          setNodePosition(theDestMesh, THE_LOWER_NODE_INDEX + aVertIter, anXYZ);
        }
        break;
      }
      case RWGltf_GltfArrayType_Normal: {
        if (aWrapCompType != RWGltf_GltfAccessorCompType_Float32
            || aWrapLayout != RWGltf_GltfAccessorLayout_Vec3)
        {
          Message::SendTrace(TCollection_AsciiString()
                             + "Vertex normals in unsupported format have been skipped while "
                               "reading glTF triangulation '"
                             + aName + "'");
          break;
        }

        if (!setNbNormalNodes(theDestMesh, aNbNodes))
        {
          return false;
        }

        for (int aVertIter = 0; aVertIter < aNbNodes; ++aVertIter)
        {
          const NCollection_Vec3<float>* aVec3 = reinterpret_cast<const NCollection_Vec3<float>*>(
            anAttrib->GetAddressOfMappedIndex(draco::PointIndex(aVertIter)));
          if (aVec3 == nullptr)
          {
            reportError(TCollection_AsciiString("Buffer '") + aName + "' reading error.");
            return false;
          }
          if (aVec3->SquareModulus() >= THE_NORMAL_PREC2)
          {
            NCollection_Vec3<float> aVec3Copy = *aVec3;
            myCoordSysConverter.TransformNormal(aVec3Copy);
            setNodeNormal(theDestMesh, THE_LOWER_NODE_INDEX + aVertIter, aVec3Copy);
          }
          else
          {
            setNodeNormal(theDestMesh,
                          THE_LOWER_NODE_INDEX + aVertIter,
                          NCollection_Vec3<float>(0.0, 0.0, 1.0));
          }
        }
        break;
      }
      case RWGltf_GltfArrayType_TCoord0: {
        if (aWrapCompType != RWGltf_GltfAccessorCompType_Float32
            || aWrapLayout != RWGltf_GltfAccessorLayout_Vec2)
        {
          Message::SendTrace(TCollection_AsciiString()
                             + "Vertex UV coordinates in unsupported format have been skipped "
                               "while reading glTF triangulation '"
                             + aName + "'");
          break;
        }

        if (!setNbUVNodes(theDestMesh, aNbNodes))
        {
          return false;
        }

        for (int aVertIter = 0; aVertIter < aNbNodes; ++aVertIter)
        {
          const NCollection_Vec2<float>* aVec2 = reinterpret_cast<const NCollection_Vec2<float>*>(
            anAttrib->GetAddressOfMappedIndex(draco::PointIndex(aVertIter)));
          if (aVec2 == nullptr)
          {
            reportError(TCollection_AsciiString("Buffer '") + aName + "' reading error.");
            return false;
          }

          // Y should be flipped (relative to image layout used by OCCT)
          float aTexY = 1.0f - aVec2->y();
          setNodeUV(theDestMesh, THE_LOWER_NODE_INDEX + aVertIter, gp_Pnt2d(aVec2->x(), aTexY));
        }
        break;
      }
      default: {
        break;
      }
    }
  }

  // copy triangles
  int aLastTriIndex = 0;
  for (int aFaceIter = 0; aFaceIter < aNbTris; ++aFaceIter)
  {
    const draco::Mesh::Face& aFace = aDracoStat.value()->face(draco::FaceIndex(aFaceIter));
    Poly_Triangle            aVec3;
    aVec3.ChangeValue(1) = THE_LOWER_NODE_INDEX + aFace[0].value();
    aVec3.ChangeValue(2) = THE_LOWER_NODE_INDEX + aFace[1].value();
    aVec3.ChangeValue(3) = THE_LOWER_NODE_INDEX + aFace[2].value();
    const int wasSet     = setTriangle(theDestMesh, THE_LOWER_TRI_INDEX + aLastTriIndex, aVec3);
    if (!wasSet)
    {
      reportError(TCollection_AsciiString("Buffer '") + aName + "' refers to invalid indices.");
    }
    if (wasSet > 0)
    {
      ++aLastTriIndex;
    }
  }

  const int aNbDegenerate = aNbTris - aLastTriIndex;
  if (aNbDegenerate > 0)
  {
    if (aNbDegenerate == aNbTris)
    {
      Message::SendWarning(TCollection_AsciiString("Buffer '") + aName
                           + "' has been skipped (all elements are degenerative in)");
      return false;
    }
    theSourceGltfMesh->ChangeDegeneratedTriNb() += aNbDegenerate;
    if (myLoadingStatistic == nullptr && myToPrintDebugMessages)
    {
      Message::SendTrace(
        TCollection_AsciiString() + aNbDegenerate
        + " degenerate triangles have been skipped while reading glTF triangulation '" + aName
        + "'");
    }
    if (!setNbTriangles(theDestMesh, aLastTriIndex, true))
    {
      return false;
    }
  }
  return true;
#else
  (void)theDestMesh;
  reportError(TCollection_AsciiString("Buffer '") + aName
              + "' refers to unsupported compressed data.");
  return false;
#endif
}

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

bool RWGltf_TriangulationReader::load(const occ::handle<RWMesh_TriangulationSource>& theSourceMesh,
                                      const occ::handle<Poly_Triangulation>&         theDestMesh,
                                      const occ::handle<OSD_FileSystem>& theFileSystem) const
{
  const occ::handle<RWGltf_GltfLatePrimitiveArray> aSourceGltfMesh =
    occ::down_cast<RWGltf_GltfLatePrimitiveArray>(theSourceMesh);
  if (aSourceGltfMesh.IsNull()
      || aSourceGltfMesh->PrimitiveMode() == RWGltf_GltfPrimitiveMode_UNKNOWN)
  {
    return false;
  }

  bool hasCompressed = false;
  for (NCollection_Sequence<RWGltf_GltfPrimArrayData>::Iterator aDataIter(aSourceGltfMesh->Data());
       aDataIter.More();
       aDataIter.Next())
  {
    const RWGltf_GltfPrimArrayData& aData = aDataIter.Value();
    const TCollection_AsciiString&  aName = aSourceGltfMesh->Id();
    if (!aData.StreamData.IsNull())
    {
      Message::SendWarning(
        TCollection_AsciiString("Buffer '") + aName
        + "' contains stream data that cannot be loaded during deferred data loading.");
      continue;
    }
    else if (aData.StreamUri.IsEmpty())
    {
      reportError(TCollection_AsciiString("Buffer '") + aName + "' does not define uri.");
      return false;
    }

    if (aData.Accessor.IsCompressed)
    {
      if (hasCompressed)
      {
        // already decoded (compressed stream defines all attributes at once)
        continue;
      }
      if (!readDracoBuffer(aSourceGltfMesh, aData, theDestMesh, theFileSystem))
      {
        return false;
      }

      // keep decoding - there are might be uncompressed attributes in addition to compressed
      hasCompressed = true;
    }
    else if (!readFileData(aSourceGltfMesh, aData, theDestMesh, theFileSystem))
    {
      return false;
    }
  }
  return true;
}

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

bool RWGltf_TriangulationReader::finalizeLoading(
  const occ::handle<RWMesh_TriangulationSource>& theSourceMesh,
  const occ::handle<Poly_Triangulation>&         theDestMesh) const
{
  if (theDestMesh->NbNodes() < 1)
  {
    return false;
  }
  if (theDestMesh->NbTriangles() < 1)
  {
    const occ::handle<RWGltf_GltfLatePrimitiveArray> aSourceGltfMesh =
      occ::down_cast<RWGltf_GltfLatePrimitiveArray>(theSourceMesh);
    if (!aSourceGltfMesh.IsNull()
        && aSourceGltfMesh->PrimitiveMode() == RWGltf_GltfPrimitiveMode_Triangles)
    {
      // reconstruct indexes
      const int aNbTris = theDestMesh->NbNodes() / 3;
      if (!setNbTriangles(theDestMesh, aNbTris))
      {
        return false;
      }
      for (int aTriIter = 0; aTriIter < aNbTris; ++aTriIter)
      {
        if (!setTriangle(theDestMesh,
                         THE_LOWER_TRI_INDEX + aTriIter,
                         Poly_Triangle(THE_LOWER_NODE_INDEX + aTriIter * 3 + 0,
                                       THE_LOWER_NODE_INDEX + aTriIter * 3 + 1,
                                       THE_LOWER_NODE_INDEX + aTriIter * 3 + 2)))
        {
          return false;
        }
      }
    }
  }
  return RWMesh_TriangulationReader::finalizeLoading(theSourceMesh, theDestMesh);
}

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

bool RWGltf_TriangulationReader::readBuffer(
  const occ::handle<RWGltf_GltfLatePrimitiveArray>& theSourceMesh,
  const occ::handle<Poly_Triangulation>&            theDestMesh,
  std::istream&                                     theStream,
  const RWGltf_GltfAccessor&                        theAccessor,
  RWGltf_GltfArrayType                              theType) const

{
  return ReadStream(theSourceMesh, theDestMesh, theStream, theAccessor, theType);
}

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

bool RWGltf_TriangulationReader::ReadStream(
  const occ::handle<RWGltf_GltfLatePrimitiveArray>& theSourceMesh,
  const occ::handle<Poly_Triangulation>&            theDestMesh,
  std::istream&                                     theStream,
  const RWGltf_GltfAccessor&                        theAccessor,
  RWGltf_GltfArrayType                              theType) const

{
  const TCollection_AsciiString& aName     = theSourceMesh->Id();
  const RWGltf_GltfPrimitiveMode aPrimMode = theSourceMesh->PrimitiveMode();
  if (aPrimMode != RWGltf_GltfPrimitiveMode_Triangles && aPrimMode != RWGltf_GltfPrimitiveMode_Lines
      && aPrimMode != RWGltf_GltfPrimitiveMode_Points)
  {
    Message::SendWarning(TCollection_AsciiString("Buffer '") + aName
                         + "' skipped unsupported primitive array");
    return true;
  }

  switch (theType)
  {
    case RWGltf_GltfArrayType_Indices: {
      if (theAccessor.Type != RWGltf_GltfAccessorLayout_Scalar)
      {
        break;
      }

      Poly_Triangle aVec3;
      if (theAccessor.ComponentType == RWGltf_GltfAccessorCompType_UInt16)
      {
        if ((theAccessor.Count / 3) > std::numeric_limits<int>::max())
        {
          reportError(TCollection_AsciiString("Buffer '") + aName + "' defines too big array.");
          return false;
        }

        const bool isTriangles = aPrimMode == RWGltf_GltfPrimitiveMode_Triangles;
        const int  aCounter = isTriangles ? (int)(theAccessor.Count / 3) : (int)(theAccessor.Count);
        if ((isTriangles && !setNbTriangles(theDestMesh, aCounter))
            || !setNbEdges(theDestMesh, aCounter))
        {
          return false;
        }
        const size_t aStride =
          theAccessor.ByteStride != 0 ? theAccessor.ByteStride : sizeof(uint16_t);
        Standard_ReadBuffer aBuffer(theAccessor.Count * aStride, aStride);
        int                 aLastIndex = 0;
        for (int aTriIter = 0; aTriIter < aCounter; ++aTriIter)
        {
          int wasSet = false;
          if (isTriangles)
          {
            for (int anIter = 1; anIter <= 3; ++anIter)
            {
              const uint16_t* anIndex = aBuffer.ReadChunk<uint16_t>(theStream);
              if (anIndex == nullptr)
              {
                reportError(TCollection_AsciiString("Buffer '") + aName + "' reading error.");
                return false;
              }
              aVec3.ChangeValue(anIter) = THE_LOWER_NODE_INDEX + *anIndex;
            }
            wasSet = setTriangle(theDestMesh, THE_LOWER_TRI_INDEX + aLastIndex, aVec3);
          }
          else
          {
            const uint16_t* anIndex = aBuffer.ReadChunk<uint16_t>(theStream);
            if (anIndex == nullptr)
            {
              reportError(TCollection_AsciiString("Buffer '") + aName + "' reading error.");
              return false;
            }
            wasSet = setEdge(theDestMesh,
                             THE_LOWER_TRI_INDEX + aLastIndex,
                             THE_LOWER_NODE_INDEX + *anIndex);
          }

          if (!wasSet)
          {
            reportError(TCollection_AsciiString("Buffer '") + aName
                        + "' refers to invalid indices.");
          }
          if (wasSet > 0)
          {
            aLastIndex++;
          }
        }
        const int aNbDegenerate = aCounter - aLastIndex;
        if (aNbDegenerate > 0)
        {
          if (aNbDegenerate == aCounter)
          {
            Message::SendWarning(TCollection_AsciiString("Buffer '") + aName
                                 + "' has been skipped (all elements are degenerative in)");
            return false;
          }
          theSourceMesh->ChangeDegeneratedTriNb() += aNbDegenerate;
          if ((myLoadingStatistic == nullptr) && myToPrintDebugMessages)
          {
            Message::SendTrace(
              TCollection_AsciiString() + aNbDegenerate
              + " degenerate triangles have been skipped while reading glTF triangulation '" + aName
              + "'");
          }
          if (!setNbTriangles(theDestMesh, aLastIndex, true))
          {
            return false;
          }
        }
      }
      else if (theAccessor.ComponentType == RWGltf_GltfAccessorCompType_UInt32)
      {
        if ((theAccessor.Count / 3) > std::numeric_limits<int>::max())
        {
          reportError(TCollection_AsciiString("Buffer '") + aName + "' defines too big array.");
          return false;
        }

        const int aNbTris = (int)(theAccessor.Count / 3);
        if (!setNbTriangles(theDestMesh, aNbTris))
        {
          return false;
        }
        const size_t aStride =
          theAccessor.ByteStride != 0 ? theAccessor.ByteStride : sizeof(uint32_t);
        Standard_ReadBuffer aBuffer(theAccessor.Count * aStride, aStride);
        int                 aLastTriIndex = 0;
        for (int aTriIter = 0; aTriIter < aNbTris; ++aTriIter)
        {
          if (const uint32_t* anIndex0 = aBuffer.ReadChunk<uint32_t>(theStream))
          {
            aVec3.ChangeValue(1) = THE_LOWER_NODE_INDEX + *anIndex0;
          }
          if (const uint32_t* anIndex1 = aBuffer.ReadChunk<uint32_t>(theStream))
          {
            aVec3.ChangeValue(2) = THE_LOWER_NODE_INDEX + *anIndex1;
          }
          if (const uint32_t* anIndex2 = aBuffer.ReadChunk<uint32_t>(theStream))
          {
            aVec3.ChangeValue(3) = THE_LOWER_NODE_INDEX + *anIndex2;
          }
          else
          {
            reportError(TCollection_AsciiString("Buffer '") + aName + "' reading error.");
            return false;
          }

          const int wasSet = setTriangle(theDestMesh, THE_LOWER_TRI_INDEX + aLastTriIndex, aVec3);
          if (!wasSet)
          {
            reportError(TCollection_AsciiString("Buffer '") + aName
                        + "' refers to invalid indices.");
          }
          if (wasSet > 0)
          {
            aLastTriIndex++;
          }
        }
        const int aNbDegenerate = aNbTris - aLastTriIndex;
        if (aNbDegenerate > 0)
        {
          if (aNbDegenerate == aNbTris)
          {
            Message::SendWarning(TCollection_AsciiString("Buffer '") + aName
                                 + "' has been skipped (all elements are degenerative in)");
            return false;
          }
          theSourceMesh->ChangeDegeneratedTriNb() += aNbDegenerate;
          if (myLoadingStatistic == nullptr && myToPrintDebugMessages)
          {
            Message::SendTrace(
              TCollection_AsciiString() + aNbDegenerate
              + " degenerate triangles have been skipped while reading glTF triangulation '" + aName
              + "'");
          }
          if (!setNbTriangles(theDestMesh, aLastTriIndex, true))
          {
            return false;
          }
        }
      }
      else if (theAccessor.ComponentType == RWGltf_GltfAccessorCompType_UInt8)
      {
        if ((theAccessor.Count / 3) > std::numeric_limits<int>::max())
        {
          reportError(TCollection_AsciiString("Buffer '") + aName + "' defines too big array.");
          return false;
        }

        const int aNbTris = (int)(theAccessor.Count / 3);
        if (!setNbTriangles(theDestMesh, aNbTris))
        {
          return false;
        }
        const size_t aStride =
          theAccessor.ByteStride != 0 ? theAccessor.ByteStride : sizeof(uint8_t);
        Standard_ReadBuffer aBuffer(theAccessor.Count * aStride, aStride);
        int                 aLastTriIndex = 0;
        for (int aTriIter = 0; aTriIter < aNbTris; ++aTriIter)
        {
          if (const uint8_t* anIndex0 = aBuffer.ReadChunk<uint8_t>(theStream))
          {
            aVec3.ChangeValue(1) = THE_LOWER_NODE_INDEX + (int)*anIndex0;
          }
          if (const uint8_t* anIndex1 = aBuffer.ReadChunk<uint8_t>(theStream))
          {
            aVec3.ChangeValue(2) = THE_LOWER_NODE_INDEX + (int)*anIndex1;
          }
          if (const uint8_t* anIndex2 = aBuffer.ReadChunk<uint8_t>(theStream))
          {
            aVec3.ChangeValue(3) = THE_LOWER_NODE_INDEX + (int)*anIndex2;
          }
          else
          {
            reportError(TCollection_AsciiString("Buffer '") + aName + "' reading error.");
            return false;
          }

          const int wasSet = setTriangle(theDestMesh, THE_LOWER_TRI_INDEX + aLastTriIndex, aVec3);
          if (!wasSet)
          {
            reportError(TCollection_AsciiString("Buffer '") + aName
                        + "' refers to invalid indices.");
          }
          if (wasSet > 0)
          {
            aLastTriIndex++;
          }
        }
        const int aNbDegenerate = aNbTris - aLastTriIndex;
        if (aNbDegenerate > 0)
        {
          if (aNbDegenerate == aNbTris)
          {
            Message::SendWarning(TCollection_AsciiString("Buffer '") + aName
                                 + "' has been skipped (all elements are degenerative in)");
            return false;
          }
          theSourceMesh->ChangeDegeneratedTriNb() += aNbDegenerate;
          if (myLoadingStatistic == nullptr && myToPrintDebugMessages)
          {
            Message::SendTrace(
              TCollection_AsciiString() + aNbDegenerate
              + " degenerate triangles have been skipped while reading glTF triangulation '" + aName
              + "'");
          }
          if (!setNbTriangles(theDestMesh, aLastTriIndex, true))
          {
            return false;
          }
        }
      }
      else
      {
        break;
      }

      break;
    }
    case RWGltf_GltfArrayType_Position: {
      if (theAccessor.ComponentType != RWGltf_GltfAccessorCompType_Float32
          || theAccessor.Type != RWGltf_GltfAccessorLayout_Vec3)
      {
        break;
      }
      else if (theAccessor.Count > std::numeric_limits<int>::max())
      {
        reportError(TCollection_AsciiString("Buffer '") + aName + "' defines too big array.");
        return false;
      }

      const size_t aStride =
        theAccessor.ByteStride != 0 ? theAccessor.ByteStride : sizeof(NCollection_Vec3<float>);
      const int aNbNodes = (int)theAccessor.Count;
      if (!setNbPositionNodes(theDestMesh, aNbNodes))
      {
        return false;
      }

      Standard_ReadBuffer aBuffer(theAccessor.Count * aStride
                                    - (aStride - sizeof(NCollection_Vec3<float>)),
                                  aStride,
                                  true);
      if (!myCoordSysConverter.IsEmpty())
      {
        for (int aVertIter = 0; aVertIter < aNbNodes; ++aVertIter)
        {
          const NCollection_Vec3<float>* aVec3 =
            aBuffer.ReadChunk<NCollection_Vec3<float>>(theStream);
          if (aVec3 == nullptr)
          {
            reportError(TCollection_AsciiString("Buffer '") + aName + "' reading error.");
            return false;
          }

          gp_Pnt anXYZ(aVec3->x(), aVec3->y(), aVec3->z());
          myCoordSysConverter.TransformPosition(anXYZ.ChangeCoord());
          setNodePosition(theDestMesh, THE_LOWER_NODE_INDEX + aVertIter, anXYZ);
        }
      }
      else
      {
        for (int aVertIter = 0; aVertIter < aNbNodes; ++aVertIter)
        {
          const NCollection_Vec3<float>* aVec3 =
            aBuffer.ReadChunk<NCollection_Vec3<float>>(theStream);
          if (aVec3 == nullptr)
          {
            reportError(TCollection_AsciiString("Buffer '") + aName + "' reading error.");
            return false;
          }
          setNodePosition(theDestMesh,
                          THE_LOWER_NODE_INDEX + aVertIter,
                          gp_Pnt(aVec3->x(), aVec3->y(), aVec3->z()));
        }
      }
      break;
    }
    case RWGltf_GltfArrayType_Normal: {
      if (theAccessor.ComponentType != RWGltf_GltfAccessorCompType_Float32
          || theAccessor.Type != RWGltf_GltfAccessorLayout_Vec3)
      {
        break;
      }
      else if (theAccessor.Count > std::numeric_limits<int>::max())
      {
        reportError(TCollection_AsciiString("Buffer '") + aName + "' defines too big array.");
        return false;
      }

      const size_t aStride =
        theAccessor.ByteStride != 0 ? theAccessor.ByteStride : sizeof(NCollection_Vec3<float>);
      const int aNbNodes = (int)theAccessor.Count;
      if (!setNbNormalNodes(theDestMesh, aNbNodes))
      {
        return false;
      }
      Standard_ReadBuffer aBuffer(theAccessor.Count * aStride
                                    - (aStride - sizeof(NCollection_Vec3<float>)),
                                  aStride,
                                  true);
      if (!myCoordSysConverter.IsEmpty())
      {
        for (int aVertIter = 0; aVertIter < aNbNodes; ++aVertIter)
        {
          NCollection_Vec3<float>* aVec3 = aBuffer.ReadChunk<NCollection_Vec3<float>>(theStream);
          if (aVec3 == nullptr)
          {
            reportError(TCollection_AsciiString("Buffer '") + aName + "' reading error.");
            return false;
          }
          if (aVec3->SquareModulus() >= THE_NORMAL_PREC2)
          {
            myCoordSysConverter.TransformNormal(*aVec3);
            setNodeNormal(theDestMesh, THE_LOWER_NODE_INDEX + aVertIter, *aVec3);
          }
          else
          {
            setNodeNormal(theDestMesh,
                          THE_LOWER_NODE_INDEX + aVertIter,
                          NCollection_Vec3<float>(0.0, 0.0, 1.0));
          }
        }
      }
      else
      {
        for (int aVertIter = 0; aVertIter < aNbNodes; ++aVertIter)
        {
          const NCollection_Vec3<float>* aVec3 =
            aBuffer.ReadChunk<NCollection_Vec3<float>>(theStream);
          if (aVec3 == nullptr)
          {
            reportError(TCollection_AsciiString("Buffer '") + aName + "' reading error.");
            return false;
          }
          if (aVec3->SquareModulus() >= THE_NORMAL_PREC2)
          {
            setNodeNormal(theDestMesh, THE_LOWER_NODE_INDEX + aVertIter, *aVec3);
          }
          else
          {
            setNodeNormal(theDestMesh,
                          THE_LOWER_NODE_INDEX + aVertIter,
                          NCollection_Vec3<float>(0.0, 0.0, 1.0));
          }
        }
      }
      break;
    }
    case RWGltf_GltfArrayType_TCoord0: {
      if (theAccessor.ComponentType != RWGltf_GltfAccessorCompType_Float32
          || theAccessor.Type != RWGltf_GltfAccessorLayout_Vec2)
      {
        break;
      }
      else if (theAccessor.Count > std::numeric_limits<int>::max())
      {
        reportError(TCollection_AsciiString("Buffer '") + aName + "' defines too big array.");
        return false;
      }

      const size_t aStride =
        theAccessor.ByteStride != 0 ? theAccessor.ByteStride : sizeof(NCollection_Vec2<float>);
      const int aNbNodes = (int)theAccessor.Count;
      if (!setNbUVNodes(theDestMesh, aNbNodes))
      {
        return false;
      }

      Standard_ReadBuffer aBuffer(theAccessor.Count * aStride
                                    - (aStride - sizeof(NCollection_Vec2<float>)),
                                  aStride,
                                  true);
      for (int aVertIter = 0; aVertIter < aNbNodes; ++aVertIter)
      {
        NCollection_Vec2<float>* aVec2 = aBuffer.ReadChunk<NCollection_Vec2<float>>(theStream);
        if (aVec2 == nullptr)
        {
          reportError(TCollection_AsciiString("Buffer '") + aName + "' reading error.");
          return false;
        }

        // Y should be flipped (relative to image layout used by OCCT)
        aVec2->y() = 1.0f - aVec2->y();
        setNodeUV(theDestMesh, THE_LOWER_NODE_INDEX + aVertIter, gp_Pnt2d(aVec2->x(), aVec2->y()));
      }
      break;
    }
    case RWGltf_GltfArrayType_Color:
    case RWGltf_GltfArrayType_TCoord1:
    case RWGltf_GltfArrayType_Joint:
    case RWGltf_GltfArrayType_Weight: {
      return true;
    }
    case RWGltf_GltfArrayType_UNKNOWN: {
      return false;
    }
  }
  return true;
}
