0032455: Data Exchange - replace OSD_OpenStream() usage with OSD_FileSystem::DefaultF...
[occt.git] / src / RWGltf / RWGltf_CafReader.cxx
1 // Author: Kirill Gavrilov
2 // Copyright (c) 2016-2019 OPEN CASCADE SAS
3 //
4 // This file is part of Open CASCADE Technology software library.
5 //
6 // This library is free software; you can redistribute it and/or modify it under
7 // the terms of the GNU Lesser General Public License version 2.1 as published
8 // by the Free Software Foundation, with special exception defined in the file
9 // OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT
10 // distribution for complete text of the license and disclaimer of any warranty.
11 //
12 // Alternatively, this file may be used under the terms of Open CASCADE
13 // commercial license or contractual agreement.
14
15 #include <RWGltf_CafReader.hxx>
16
17 #include "RWGltf_GltfJsonParser.hxx"
18 #include <RWGltf_TriangulationReader.hxx>
19
20 #include <BRep_Builder.hxx>
21 #include <BRep_Tool.hxx>
22 #include <Message.hxx>
23 #include <Message_Messenger.hxx>
24 #include <Message_ProgressScope.hxx>
25 #include <OSD_CachedFileSystem.hxx>
26 #include <OSD_FileSystem.hxx>
27 #include <OSD_ThreadPool.hxx>
28
29 #include <fstream>
30
31 IMPLEMENT_STANDARD_RTTIEXT(RWGltf_CafReader, RWMesh_CafReader)
32
33 //! Abstract base functor for parallel execution of glTF data loading.
34 class RWGltf_CafReader::CafReader_GltfBaseLoadingFunctor
35 {
36 public:
37
38   //! Main constructor.
39   CafReader_GltfBaseLoadingFunctor (NCollection_Vector<TopoDS_Face>& theFaceList,
40                                     const Message_ProgressRange& theProgress,
41                                     const OSD_ThreadPool::Launcher& theThreadPool)
42   : myFaceList  (&theFaceList),
43     myProgress  (theProgress, "Loading glTF triangulation", Max (1, theFaceList.Size())),
44     myThreadPool(theThreadPool)
45   {
46     //
47   }
48
49   //! Execute task for a face with specified index.
50   void operator() (int theThreadIndex,
51                    int theFaceIndex) const
52   {
53     TopLoc_Location aDummyLoc;
54     TopoDS_Face& aFace = myFaceList->ChangeValue (theFaceIndex);
55     Handle(RWGltf_GltfLatePrimitiveArray) aLateData = Handle(RWGltf_GltfLatePrimitiveArray)::DownCast (BRep_Tool::Triangulation (aFace, aDummyLoc));
56     Handle(Poly_Triangulation) aPolyData = loadData (aLateData, theThreadIndex);
57     if (!aPolyData.IsNull())
58     {
59       BRep_Builder aBuilder;
60       aBuilder.UpdateFace (aFace, aPolyData); // replace all "proxy"-triangulations of face by loaded active one.
61     }
62     if (myThreadPool.HasThreads())
63     {
64       Standard_Mutex::Sentry aLock (&myMutex);
65       myProgress.Next();
66     }
67     else
68     {
69       myProgress.Next();
70     }
71   }
72
73 protected:
74
75   //! Load primitive array.
76   virtual Handle(Poly_Triangulation) loadData (const Handle(RWGltf_GltfLatePrimitiveArray)& theLateData,
77                                                int theThreadIndex) const = 0;
78
79 protected:
80
81   NCollection_Vector<TopoDS_Face>* myFaceList;
82   mutable Standard_Mutex           myMutex;
83   mutable Message_ProgressScope    myProgress;
84   const OSD_ThreadPool::Launcher&  myThreadPool;
85 };
86 //! Functor for parallel execution of all glTF data loading.
87 class RWGltf_CafReader::CafReader_GltfFullDataLoadingFunctor : public RWGltf_CafReader::CafReader_GltfBaseLoadingFunctor
88 {
89 public:
90
91   struct GltfReaderTLS
92   {
93     Handle(OSD_FileSystem) FileSystem;
94   };
95
96   //! Main constructor.
97   CafReader_GltfFullDataLoadingFunctor (RWGltf_CafReader* myCafReader,
98                                         NCollection_Vector<TopoDS_Face>& theFaceList,
99                                         const Message_ProgressRange& theProgress,
100                                         const OSD_ThreadPool::Launcher& theThreadPool)
101   : CafReader_GltfBaseLoadingFunctor (theFaceList, theProgress, theThreadPool),
102     myCafReader (myCafReader),
103     myTlsData   (theThreadPool.LowerThreadIndex(), theThreadPool.UpperThreadIndex())
104   {
105     //
106   }
107
108 protected:
109
110   //! Load primitive array.
111   virtual Handle(Poly_Triangulation) loadData (const Handle(RWGltf_GltfLatePrimitiveArray)& theLateData,
112                                                int theThreadIndex) const Standard_OVERRIDE
113   {
114     GltfReaderTLS& aTlsData = myTlsData.ChangeValue (theThreadIndex);
115     if (aTlsData.FileSystem.IsNull())
116     {
117       aTlsData.FileSystem = new OSD_CachedFileSystem();
118     }
119     // Load stream data if exists
120     if (Handle(Poly_Triangulation) aStreamLoadedData = theLateData->LoadStreamData())
121     {
122       return aStreamLoadedData;
123     }
124     // Load file data
125     if (myCafReader->ToKeepLateData())
126     {
127       theLateData->LoadDeferredData (aTlsData.FileSystem);
128       return Handle(Poly_Triangulation)();
129     }
130     return theLateData->DetachedLoadDeferredData (aTlsData.FileSystem);
131   }
132
133 private:
134
135   RWGltf_CafReader* myCafReader;
136   mutable NCollection_Array1<GltfReaderTLS> myTlsData;
137 };
138
139 //! Functor for parallel execution of loading of only glTF data saved in stream buffers.
140 class RWGltf_CafReader::CafReader_GltfStreamDataLoadingFunctor : public RWGltf_CafReader::CafReader_GltfBaseLoadingFunctor
141 {
142 public:
143
144   //! Main constructor.
145   CafReader_GltfStreamDataLoadingFunctor (NCollection_Vector<TopoDS_Face>& theFaceList,
146                                           const Message_ProgressRange& theProgress,
147                                           const OSD_ThreadPool::Launcher& theThreadPool)
148   : CafReader_GltfBaseLoadingFunctor (theFaceList, theProgress, theThreadPool)
149   {
150     //
151   }
152
153 protected:
154
155   //! Load primitive array.
156   virtual Handle(Poly_Triangulation) loadData (const Handle(RWGltf_GltfLatePrimitiveArray)& theLateData,
157                                                int theThreadIndex) const Standard_OVERRIDE
158   {
159     (void )theThreadIndex;
160     return theLateData->LoadStreamData();
161   }
162 };
163
164 //================================================================
165 // Function : Constructor
166 // Purpose  :
167 //================================================================
168 RWGltf_CafReader::RWGltf_CafReader()
169 : myToParallel (false),
170   myToSkipEmptyNodes (true),
171   myUseMeshNameAsFallback (true),
172   myIsDoublePrecision (false),
173   myToSkipLateDataLoading (false),
174   myToKeepLateData (true),
175   myToPrintDebugMessages (false)
176 {
177   myCoordSysConverter.SetInputLengthUnit (1.0); // glTF defines model in meters
178   myCoordSysConverter.SetInputCoordinateSystem (RWMesh_CoordinateSystem_glTF);
179 }
180
181 //================================================================
182 // Function : performMesh
183 // Purpose  :
184 //================================================================
185 Standard_Boolean RWGltf_CafReader::performMesh (const TCollection_AsciiString& theFile,
186                                                 const Message_ProgressRange& theProgress,
187                                                 const Standard_Boolean theToProbe)
188 {
189   Message_ProgressScope aPSentry (theProgress, "Reading glTF", 2);
190   aPSentry.Show();
191
192   const Handle(OSD_FileSystem)& aFileSystem = OSD_FileSystem::DefaultFileSystem();
193   opencascade::std::shared_ptr<std::istream> aFile = aFileSystem->OpenIStream (theFile, std::ios::in | std::ios::binary);
194   if (aFile.get() == NULL || !aFile->good())
195   {
196     Message::SendFail (TCollection_AsciiString ("File '") + theFile + "' is not found");
197     return false;
198   }
199
200   bool isBinaryFile = false;
201   char aGlbHeader[12] = {};
202   aFile->read (aGlbHeader, sizeof (aGlbHeader));
203   int64_t aBinBodyOffset  = 0;
204   int64_t aBinBodyLen     = 0;
205   int64_t aJsonBodyOffset = 0;
206   int64_t aJsonBodyLen    = 0;
207   if (::strncmp (aGlbHeader, "glTF", 4) == 0)
208   {
209     isBinaryFile = true;
210     const uint32_t* aVer = (const uint32_t* )(aGlbHeader + 4);
211     const uint32_t* aLen = (const uint32_t* )(aGlbHeader + 8);
212     if (*aVer == 1)
213     {
214       if (*aLen < 20)
215       {
216         Message::SendFail (TCollection_AsciiString ("File '") + theFile + "' has broken glTF format");
217         return false;
218       }
219
220       char aHeader1[8] = {};
221       aFile->read (aHeader1, sizeof (aHeader1));
222
223       const uint32_t* aSceneLen    = (const uint32_t* )(aHeader1 + 0);
224       const uint32_t* aSceneFormat = (const uint32_t* )(aHeader1 + 4);
225       aJsonBodyOffset = 20;
226       aJsonBodyLen    = int64_t(*aSceneLen);
227
228       aBinBodyOffset = aJsonBodyOffset + aJsonBodyLen;
229       aBinBodyLen    = int64_t(*aLen) - aBinBodyOffset;
230       if (*aSceneFormat != 0)
231       {
232         Message::SendFail (TCollection_AsciiString ("File '") + theFile + "' is written using unsupported Scene format");
233         return false;
234       }
235     }
236     else //if (*aVer == 2)
237     {
238       if (*aVer != 2)
239       {
240         Message::SendWarning (TCollection_AsciiString ("File '") + theFile + "' is written using unknown version " + int(*aVer));
241       }
242
243       for (int aChunkIter = 0; !aFile->eof() && aChunkIter < 2; ++aChunkIter)
244       {
245         char aChunkHeader2[8] = {};
246         if (int64_t (aFile->tellg()) + int64_t (sizeof (aChunkHeader2)) > int64_t (*aLen))
247         {
248           break;
249         }
250
251         aFile->read (aChunkHeader2, sizeof (aChunkHeader2));
252         if (!aFile->good())
253         {
254           Message::SendFail (TCollection_AsciiString ("File '") + theFile + "' is written using unsupported format");
255           return false;
256         }
257
258         const uint32_t* aChunkLen  = (const uint32_t* )(aChunkHeader2 + 0);
259         const uint32_t* aChunkType = (const uint32_t* )(aChunkHeader2 + 4);
260         if (*aChunkType == 0x4E4F534A)
261         {
262           aJsonBodyOffset = int64_t (aFile->tellg());
263           aJsonBodyLen    = int64_t (*aChunkLen);
264         }
265         else if (*aChunkType == 0x004E4942)
266         {
267           aBinBodyOffset = int64_t (aFile->tellg());
268           aBinBodyLen    = int64_t (*aChunkLen);
269         }
270         if (*aChunkLen != 0)
271         {
272           aFile->seekg (*aChunkLen, std::ios_base::cur);
273         }
274       }
275
276       aFile->seekg ((std::streamoff )aJsonBodyOffset, std::ios_base::beg);
277     }
278   }
279   else
280   {
281     aFile->seekg (0, std::ios_base::beg);
282   }
283
284   TCollection_AsciiString anErrPrefix = TCollection_AsciiString ("File '") + theFile + "' defines invalid glTF!\n";
285   RWGltf_GltfJsonParser aDoc (myRootShapes);
286   aDoc.SetFilePath (theFile);
287   aDoc.SetProbeHeader (theToProbe);
288   aDoc.SetExternalFiles (myExternalFiles);
289   aDoc.SetMetadata (myMetadata);
290   aDoc.SetErrorPrefix (anErrPrefix);
291   aDoc.SetCoordinateSystemConverter (myCoordSysConverter);
292   aDoc.SetSkipEmptyNodes (myToSkipEmptyNodes);
293   aDoc.SetMeshNameAsFallback (myUseMeshNameAsFallback);
294   if (!theToProbe)
295   {
296     aDoc.SetAttributeMap (myAttribMap);
297   }
298   if (isBinaryFile)
299   {
300     aDoc.SetBinaryFormat (aBinBodyOffset, aBinBodyLen);
301   }
302
303 #ifdef HAVE_RAPIDJSON
304   rapidjson::ParseResult aRes;
305   rapidjson::IStreamWrapper aFileStream (*aFile);
306   if (isBinaryFile)
307   {
308     aRes = aDoc.ParseStream<rapidjson::kParseStopWhenDoneFlag, rapidjson::UTF8<>, rapidjson::IStreamWrapper> (aFileStream);
309   }
310   else
311   {
312     aRes = aDoc.ParseStream (aFileStream);
313   }
314   if (aRes.IsError())
315   {
316     if (aRes.Code() == rapidjson::kParseErrorDocumentEmpty)
317     {
318       Message::SendFail (TCollection_AsciiString ("File '") + theFile + "' is empty");
319       return false;
320     }
321     TCollection_AsciiString anErrDesc (RWGltf_GltfJsonParser::FormatParseError (aRes.Code()));
322     Message::SendFail (TCollection_AsciiString ("File '") + theFile + "' defines invalid JSON document!\n"
323                      + anErrDesc + " [at offset " + (int )aRes.Offset() + "].");
324     return false;
325   }
326 #endif
327
328   if (!aDoc.Parse (aPSentry.Next()))
329   {
330     return false;
331   }
332
333   if (!theToProbe
334    && !readLateData (aDoc.FaceList(), theFile, aPSentry.Next()))
335   {
336     return false;
337   }
338
339   return true;
340 }
341
342 //================================================================
343 // Function : createMeshReaderContext
344 // Purpose  :
345 //================================================================
346 Handle(RWMesh_TriangulationReader) RWGltf_CafReader::createMeshReaderContext() const
347 {
348   Handle(RWGltf_TriangulationReader) aReader = new RWGltf_TriangulationReader();
349   aReader->SetDoublePrecision (myIsDoublePrecision);
350   aReader->SetCoordinateSystemConverter (myCoordSysConverter);
351   aReader->SetToSkipDegenerates (false);
352   aReader->SetToPrintDebugMessages (myToPrintDebugMessages);
353   return aReader;
354 }
355
356 //================================================================
357 // Function : readLateData
358 // Purpose  :
359 //================================================================
360 Standard_Boolean RWGltf_CafReader::readLateData (NCollection_Vector<TopoDS_Face>& theFaces,
361                                                  const TCollection_AsciiString& theFile,
362                                                  const Message_ProgressRange& theProgress)
363 {
364   Handle(RWGltf_TriangulationReader) aReader = Handle(RWGltf_TriangulationReader)::DownCast(createMeshReaderContext());
365   aReader->SetFileName (theFile);
366   updateLateDataReader (theFaces, aReader);
367
368   if (myToSkipLateDataLoading)
369   {
370     // Load glTF data encoded in base64. It should not be skipped and saved in "proxy" object to be loaded later.
371     const Handle(OSD_ThreadPool)& aThreadPool = OSD_ThreadPool::DefaultPool();
372     const int aNbThreads = myToParallel ? Min (theFaces.Size(), aThreadPool->NbDefaultThreadsToLaunch()) : 1;
373     OSD_ThreadPool::Launcher aLauncher(*aThreadPool, aNbThreads);
374     CafReader_GltfStreamDataLoadingFunctor aFunctor(theFaces, theProgress, aLauncher);
375     aLauncher.Perform (theFaces.Lower(), theFaces.Upper() + 1, aFunctor);
376
377     return Standard_True;
378   }
379
380   aReader->StartStatistic();
381
382   const Handle(OSD_ThreadPool)& aThreadPool = OSD_ThreadPool::DefaultPool();
383   const int aNbThreads = myToParallel ? Min (theFaces.Size(), aThreadPool->NbDefaultThreadsToLaunch()) : 1;
384   OSD_ThreadPool::Launcher aLauncher (*aThreadPool, aNbThreads);
385
386   CafReader_GltfFullDataLoadingFunctor aFunctor (this, theFaces, theProgress, aLauncher);
387   aLauncher.Perform (theFaces.Lower(), theFaces.Upper() + 1, aFunctor);
388
389   aReader->PrintStatistic();
390   aReader->StopStatistic();
391
392   return Standard_True;
393 }
394
395 //================================================================
396 // Function : updateLateDataReader
397 // Purpose  :
398 //================================================================
399 void RWGltf_CafReader::updateLateDataReader (NCollection_Vector<TopoDS_Face>& theFaces,
400                                              const Handle(RWMesh_TriangulationReader)& theReader) const
401 {
402   TopLoc_Location aDummyLoc;
403   for (NCollection_Vector<TopoDS_Face>::Iterator aFaceIter(theFaces); aFaceIter.More(); aFaceIter.Next())
404   {
405     const TopoDS_Face& aFace = aFaceIter.Value();
406     for (Poly_ListOfTriangulation::Iterator anIter(BRep_Tool::Triangulations (aFace, aDummyLoc)); anIter.More(); anIter.Next())
407     {
408       Handle(RWGltf_GltfLatePrimitiveArray) aData = Handle(RWGltf_GltfLatePrimitiveArray)::DownCast(anIter.Value());
409       if (!aData.IsNull())
410       {
411         aData->SetReader (theReader);
412       }
413     }
414   }
415 }