0030691: Data Exchange - implement import of mesh data from files in glTF format
[occt.git] / src / RWGltf / RWGltf_CafReader.cxx
diff --git a/src/RWGltf/RWGltf_CafReader.cxx b/src/RWGltf/RWGltf_CafReader.cxx
new file mode 100644 (file)
index 0000000..f17c3db
--- /dev/null
@@ -0,0 +1,295 @@
+// Author: Kirill Gavrilov
+// Copyright (c) 2016-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_CafReader.hxx>
+
+#include "RWGltf_GltfJsonParser.pxx"
+#include <RWGltf_TriangulationReader.hxx>
+
+#include <BRep_Builder.hxx>
+#include <BRep_Tool.hxx>
+#include <Message.hxx>
+#include <Message_Messenger.hxx>
+#include <Message_ProgressSentry.hxx>
+#include <OSD_OpenFile.hxx>
+#include <OSD_ThreadPool.hxx>
+
+#include <fstream>
+
+IMPLEMENT_STANDARD_RTTIEXT(RWGltf_CafReader, RWMesh_CafReader)
+
+//! Functor for parallel execution.
+class RWGltf_CafReader::CafReader_GltfReaderFunctor
+{
+public:
+
+  struct GltfReaderTLS
+  {
+    Handle(RWGltf_PrimitiveArrayReader) Reader;
+  };
+
+  //! Main constructor.
+  CafReader_GltfReaderFunctor (RWGltf_CafReader* myCafReader,
+                               NCollection_Vector<TopoDS_Face>& theFaceList,
+                               Message_ProgressSentry& theSentry,
+                               const OSD_ThreadPool::Launcher& theThreadPool,
+                               const TCollection_AsciiString& theErrPrefix)
+  : myCafReader (myCafReader),
+    myFaceList  (&theFaceList),
+    mySentry    (&theSentry),
+    myErrPrefix (theErrPrefix),
+    myThreadPool(theThreadPool),
+    myTlsData   (theThreadPool.LowerThreadIndex(), theThreadPool.UpperThreadIndex())
+  {
+    //
+  }
+
+  //! Execute task for a face with specified index.
+  void operator() (int theThreadIndex,
+                   int theFaceIndex) const
+  {
+    GltfReaderTLS& aTlsData = myTlsData.ChangeValue (theThreadIndex);
+    if (aTlsData.Reader.IsNull())
+    {
+      aTlsData.Reader = myCafReader->createMeshReaderContext();
+      aTlsData.Reader->SetErrorPrefix (myErrPrefix);
+      aTlsData.Reader->SetCoordinateSystemConverter (myCafReader->myCoordSysConverter);
+    }
+
+    TopLoc_Location aDummyLoc;
+    TopoDS_Face& aFace = myFaceList->ChangeValue (theFaceIndex);
+    Handle(RWGltf_GltfLatePrimitiveArray) aLateData = Handle(RWGltf_GltfLatePrimitiveArray)::DownCast (BRep_Tool::Triangulation (aFace, aDummyLoc));
+    Handle(Poly_Triangulation) aPolyData = aTlsData.Reader->Load (aLateData);
+    BRep_Builder aBuilder;
+    aBuilder.UpdateFace (aFace, aPolyData);
+
+    if (myThreadPool.HasThreads())
+    {
+      Standard_Mutex::Sentry aLock (&myMutex);
+      mySentry->Next();
+    }
+    else
+    {
+      mySentry->Next();
+    }
+  }
+
+private:
+
+  RWGltf_CafReader* myCafReader;
+  NCollection_Vector<TopoDS_Face>* myFaceList;
+  Message_ProgressSentry*   mySentry;
+  TCollection_AsciiString   myErrPrefix;
+  mutable Standard_Mutex    myMutex;
+  const OSD_ThreadPool::Launcher& myThreadPool;
+  mutable NCollection_Array1<GltfReaderTLS>
+                            myTlsData;
+
+};
+
+//================================================================
+// Function : Constructor
+// Purpose  :
+//================================================================
+RWGltf_CafReader::RWGltf_CafReader()
+: myToParallel (false)
+{
+  myCoordSysConverter.SetInputLengthUnit (1.0); // glTF defines model in meters
+  myCoordSysConverter.SetInputCoordinateSystem (RWMesh_CoordinateSystem_glTF);
+}
+
+//================================================================
+// Function : performMesh
+// Purpose  :
+//================================================================
+Standard_Boolean RWGltf_CafReader::performMesh (const TCollection_AsciiString& theFile,
+                                                const Handle(Message_ProgressIndicator)& theProgress,
+                                                const Standard_Boolean theToProbe)
+{
+  std::ifstream aFile;
+  OSD_OpenStream (aFile, theFile.ToCString(), std::ios::in | std::ios::binary);
+  if (!aFile.is_open()
+   || !aFile.good())
+  {
+    Message::DefaultMessenger()->Send (TCollection_AsciiString ("File '") + theFile + "' is not found!", Message_Fail);
+    return false;
+  }
+
+  bool isBinaryFile = false;
+  char aGlbHeader[12] = {};
+  aFile.read (aGlbHeader, sizeof(aGlbHeader));
+  int64_t aBinBodyOffset  = 0;
+  int64_t aBinBodyLen     = 0;
+  int64_t aJsonBodyOffset = 0;
+  int64_t aJsonBodyLen    = 0;
+  if (::strncmp (aGlbHeader, "glTF", 4) == 0)
+  {
+    isBinaryFile = true;
+    const uint32_t* aVer = (const uint32_t* )(aGlbHeader + 4);
+    const uint32_t* aLen = (const uint32_t* )(aGlbHeader + 8);
+    if (*aVer == 1)
+    {
+      if (*aLen < 20)
+      {
+        Message::DefaultMessenger()->Send (TCollection_AsciiString ("File '") + theFile + "' has broken glTF format!", Message_Fail);
+        return false;
+      }
+
+      char aHeader1[8] = {};
+      aFile.read (aHeader1, sizeof(aHeader1));
+
+      const uint32_t* aSceneLen    = (const uint32_t* )(aHeader1 + 0);
+      const uint32_t* aSceneFormat = (const uint32_t* )(aHeader1 + 4);
+      aJsonBodyOffset = 20;
+      aJsonBodyLen    = int64_t(*aSceneLen);
+
+      aBinBodyOffset = aJsonBodyOffset + aJsonBodyLen;
+      aBinBodyLen    = int64_t(*aLen) - aBinBodyOffset;
+      if (*aSceneFormat != 0)
+      {
+        Message::DefaultMessenger()->Send (TCollection_AsciiString ("File '") + theFile + "' is written using unsupported Scene format!", Message_Fail);
+        return false;
+      }
+    }
+    else //if (*aVer == 2)
+    {
+      if (*aVer != 2)
+      {
+        Message::DefaultMessenger()->Send (TCollection_AsciiString ("File '") + theFile + "' is written using unknown version " + int(*aVer) + "!", Message_Warning);
+      }
+
+      for (int aChunkIter = 0; !aFile.eof() && aChunkIter < 2; ++aChunkIter)
+      {
+        char aChunkHeader2[8] = {};
+        if (int64_t(aFile.tellg()) + int64_t(sizeof(aChunkHeader2)) > int64_t(aLen))
+        {
+          break;
+        }
+
+        aFile.read (aChunkHeader2, sizeof(aChunkHeader2));
+        if (!aFile.good())
+        {
+          Message::DefaultMessenger()->Send (TCollection_AsciiString ("File '") + theFile + "' is written using unsupported format!", Message_Fail);
+          return false;
+        }
+
+        const uint32_t* aChunkLen  = (const uint32_t* )(aChunkHeader2 + 0);
+        const uint32_t* aChunkType = (const uint32_t* )(aChunkHeader2 + 4);
+        if (*aChunkType == 0x4E4F534A)
+        {
+          aJsonBodyOffset = int64_t(aFile.tellg());
+          aJsonBodyLen    = int64_t(*aChunkLen);
+        }
+        else if (*aChunkType == 0x004E4942)
+        {
+          aBinBodyOffset = int64_t(aFile.tellg());
+          aBinBodyLen    = int64_t(*aChunkLen);
+        }
+        if (*aChunkLen != 0)
+        {
+          aFile.seekg (*aChunkLen, std::ios_base::cur);
+        }
+      }
+
+      aFile.seekg ((std::streamoff )aJsonBodyOffset, std::ios_base::beg);
+    }
+  }
+  else
+  {
+    aFile.seekg (0, std::ios_base::beg);
+  }
+
+  TCollection_AsciiString anErrPrefix = TCollection_AsciiString ("File '") + theFile + "' defines invalid glTF!\n";
+  RWGltf_GltfJsonParser aDoc (myRootShapes);
+  aDoc.SetFilePath (theFile);
+  aDoc.SetProbeHeader (theToProbe);
+  aDoc.SetExternalFiles (myExternalFiles);
+  aDoc.SetErrorPrefix (anErrPrefix);
+  aDoc.SetCoordinateSystemConverter (myCoordSysConverter);
+  if (!theToProbe)
+  {
+    aDoc.SetAttributeMap (myAttribMap);
+  }
+  if (isBinaryFile)
+  {
+    aDoc.SetBinaryFormat (aBinBodyOffset, aBinBodyLen);
+  }
+
+#ifdef HAVE_RAPIDJSON
+  rapidjson::ParseResult aRes;
+  rapidjson::IStreamWrapper aFileStream (aFile);
+  if (isBinaryFile)
+  {
+    aRes = aDoc.ParseStream<rapidjson::kParseStopWhenDoneFlag, rapidjson::UTF8<>, rapidjson::IStreamWrapper> (aFileStream);
+  }
+  else
+  {
+    aRes = aDoc.ParseStream (aFileStream);
+  }
+  if (aRes.IsError())
+  {
+    if (aRes.Code() == rapidjson::kParseErrorDocumentEmpty)
+    {
+      Message::DefaultMessenger()->Send (TCollection_AsciiString ("File '") + theFile + "' is empty!", Message_Fail);
+      return false;
+    }
+    TCollection_AsciiString anErrDesc (RWGltf_GltfJsonParser::FormatParseError (aRes.Code()));
+    Message::DefaultMessenger()->Send (TCollection_AsciiString ("File '") + theFile + "' defines invalid JSON document!\n"
+                                     + anErrDesc + ".", Message_Fail);
+    return false;
+  }
+#endif
+
+  if (!aDoc.Parse (theProgress))
+  {
+    return false;
+  }
+
+  if (!theToProbe
+   && !readLateData (aDoc.FaceList(), theFile, theProgress))
+  {
+    return false;
+  }
+
+  return true;
+}
+
+//================================================================
+// Function : createMeshReaderContext
+// Purpose  :
+//================================================================
+Handle(RWGltf_PrimitiveArrayReader) RWGltf_CafReader::createMeshReaderContext()
+{
+  Handle(RWGltf_TriangulationReader) aReader = new RWGltf_TriangulationReader();
+  return aReader;
+}
+
+//================================================================
+// Function : readLateData
+// Purpose  :
+//================================================================
+Standard_Boolean RWGltf_CafReader::readLateData (NCollection_Vector<TopoDS_Face>& theFaces,
+                                                 const TCollection_AsciiString& theFile,
+                                                 const Handle(Message_ProgressIndicator)& theProgress)
+{
+  Message_ProgressSentry aPSentryTris (theProgress, "Loading glTF triangulation", 0, Max (1, theFaces.Size()), 1);
+  const Handle(OSD_ThreadPool)& aThreadPool = OSD_ThreadPool::DefaultPool();
+  const int aNbThreads = myToParallel ? Min (theFaces.Size(), aThreadPool->NbDefaultThreadsToLaunch()) : 1;
+  OSD_ThreadPool::Launcher aLauncher (*aThreadPool, aNbThreads);
+
+  CafReader_GltfReaderFunctor aFunctor (this, theFaces, aPSentryTris, aLauncher,
+                                        TCollection_AsciiString ("File '") + theFile + "' defines invalid glTF!\n");
+  aLauncher.Perform (theFaces.Lower(), theFaces.Upper() + 1, aFunctor);
+  return Standard_True;
+}