0025748: Parallel version of progress indicator
[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.pxx"
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_OpenFile.hxx>
26 #include <OSD_ThreadPool.hxx>
27
28 #include <fstream>
29
30 IMPLEMENT_STANDARD_RTTIEXT(RWGltf_CafReader, RWMesh_CafReader)
31
32 //! Functor for parallel execution.
33 class RWGltf_CafReader::CafReader_GltfReaderFunctor
34 {
35 public:
36
37   struct GltfReaderTLS
38   {
39     Handle(RWGltf_PrimitiveArrayReader) Reader;
40   };
41
42   //! Main constructor.
43   CafReader_GltfReaderFunctor (RWGltf_CafReader* myCafReader,
44                                NCollection_Vector<TopoDS_Face>& theFaceList,
45                                const Message_ProgressRange& theProgress,
46                                const OSD_ThreadPool::Launcher& theThreadPool,
47                                const TCollection_AsciiString& theErrPrefix)
48   : myCafReader (myCafReader),
49     myFaceList  (&theFaceList),
50     myErrPrefix (theErrPrefix),
51     myProgress  (theProgress, "Loading glTF triangulation", Max (1, theFaceList.Size())),
52     myThreadPool(theThreadPool),
53     myTlsData   (theThreadPool.LowerThreadIndex(), theThreadPool.UpperThreadIndex())
54   {
55     //
56   }
57
58   //! Execute task for a face with specified index.
59   void operator() (int theThreadIndex,
60                    int theFaceIndex) const
61   {
62     GltfReaderTLS& aTlsData = myTlsData.ChangeValue (theThreadIndex);
63     if (aTlsData.Reader.IsNull())
64     {
65       aTlsData.Reader = myCafReader->createMeshReaderContext();
66       aTlsData.Reader->SetErrorPrefix (myErrPrefix);
67       aTlsData.Reader->SetCoordinateSystemConverter (myCafReader->myCoordSysConverter);
68     }
69
70     TopLoc_Location aDummyLoc;
71     TopoDS_Face& aFace = myFaceList->ChangeValue (theFaceIndex);
72     Handle(RWGltf_GltfLatePrimitiveArray) aLateData = Handle(RWGltf_GltfLatePrimitiveArray)::DownCast (BRep_Tool::Triangulation (aFace, aDummyLoc));
73     Handle(Poly_Triangulation) aPolyData = aTlsData.Reader->Load (aLateData);
74     BRep_Builder aBuilder;
75     aBuilder.UpdateFace (aFace, aPolyData);
76
77     if (myThreadPool.HasThreads())
78     {
79       Standard_Mutex::Sentry aLock (&myMutex);
80       myProgress.Next();
81     }
82     else
83     {
84       myProgress.Next();
85     }
86   }
87
88 private:
89
90   RWGltf_CafReader* myCafReader;
91   NCollection_Vector<TopoDS_Face>* myFaceList;
92   TCollection_AsciiString   myErrPrefix;
93   mutable Standard_Mutex    myMutex;
94   mutable Message_ProgressScope myProgress;
95   const OSD_ThreadPool::Launcher& myThreadPool;
96   mutable NCollection_Array1<GltfReaderTLS> myTlsData;
97 };
98
99 //================================================================
100 // Function : Constructor
101 // Purpose  :
102 //================================================================
103 RWGltf_CafReader::RWGltf_CafReader()
104 : myToParallel (false),
105   myToSkipEmptyNodes (true),
106   myUseMeshNameAsFallback (true)
107 {
108   myCoordSysConverter.SetInputLengthUnit (1.0); // glTF defines model in meters
109   myCoordSysConverter.SetInputCoordinateSystem (RWMesh_CoordinateSystem_glTF);
110 }
111
112 //================================================================
113 // Function : performMesh
114 // Purpose  :
115 //================================================================
116 Standard_Boolean RWGltf_CafReader::performMesh (const TCollection_AsciiString& theFile,
117                                                 const Message_ProgressRange& theProgress,
118                                                 const Standard_Boolean theToProbe)
119 {
120   Message_ProgressScope aPSentry (theProgress, "Reading glTF", 2);
121   aPSentry.Show();
122
123   std::ifstream aFile;
124   OSD_OpenStream (aFile, theFile.ToCString(), std::ios::in | std::ios::binary);
125   if (!aFile.is_open()
126    || !aFile.good())
127   {
128     Message::SendFail (TCollection_AsciiString ("File '") + theFile + "' is not found");
129     return false;
130   }
131
132   bool isBinaryFile = false;
133   char aGlbHeader[12] = {};
134   aFile.read (aGlbHeader, sizeof(aGlbHeader));
135   int64_t aBinBodyOffset  = 0;
136   int64_t aBinBodyLen     = 0;
137   int64_t aJsonBodyOffset = 0;
138   int64_t aJsonBodyLen    = 0;
139   if (::strncmp (aGlbHeader, "glTF", 4) == 0)
140   {
141     isBinaryFile = true;
142     const uint32_t* aVer = (const uint32_t* )(aGlbHeader + 4);
143     const uint32_t* aLen = (const uint32_t* )(aGlbHeader + 8);
144     if (*aVer == 1)
145     {
146       if (*aLen < 20)
147       {
148         Message::SendFail (TCollection_AsciiString ("File '") + theFile + "' has broken glTF format");
149         return false;
150       }
151
152       char aHeader1[8] = {};
153       aFile.read (aHeader1, sizeof(aHeader1));
154
155       const uint32_t* aSceneLen    = (const uint32_t* )(aHeader1 + 0);
156       const uint32_t* aSceneFormat = (const uint32_t* )(aHeader1 + 4);
157       aJsonBodyOffset = 20;
158       aJsonBodyLen    = int64_t(*aSceneLen);
159
160       aBinBodyOffset = aJsonBodyOffset + aJsonBodyLen;
161       aBinBodyLen    = int64_t(*aLen) - aBinBodyOffset;
162       if (*aSceneFormat != 0)
163       {
164         Message::SendFail (TCollection_AsciiString ("File '") + theFile + "' is written using unsupported Scene format");
165         return false;
166       }
167     }
168     else //if (*aVer == 2)
169     {
170       if (*aVer != 2)
171       {
172         Message::SendWarning (TCollection_AsciiString ("File '") + theFile + "' is written using unknown version " + int(*aVer));
173       }
174
175       for (int aChunkIter = 0; !aFile.eof() && aChunkIter < 2; ++aChunkIter)
176       {
177         char aChunkHeader2[8] = {};
178         if (int64_t(aFile.tellg()) + int64_t(sizeof(aChunkHeader2)) > int64_t(*aLen))
179         {
180           break;
181         }
182
183         aFile.read (aChunkHeader2, sizeof(aChunkHeader2));
184         if (!aFile.good())
185         {
186           Message::SendFail (TCollection_AsciiString ("File '") + theFile + "' is written using unsupported format");
187           return false;
188         }
189
190         const uint32_t* aChunkLen  = (const uint32_t* )(aChunkHeader2 + 0);
191         const uint32_t* aChunkType = (const uint32_t* )(aChunkHeader2 + 4);
192         if (*aChunkType == 0x4E4F534A)
193         {
194           aJsonBodyOffset = int64_t(aFile.tellg());
195           aJsonBodyLen    = int64_t(*aChunkLen);
196         }
197         else if (*aChunkType == 0x004E4942)
198         {
199           aBinBodyOffset = int64_t(aFile.tellg());
200           aBinBodyLen    = int64_t(*aChunkLen);
201         }
202         if (*aChunkLen != 0)
203         {
204           aFile.seekg (*aChunkLen, std::ios_base::cur);
205         }
206       }
207
208       aFile.seekg ((std::streamoff )aJsonBodyOffset, std::ios_base::beg);
209     }
210   }
211   else
212   {
213     aFile.seekg (0, std::ios_base::beg);
214   }
215
216   TCollection_AsciiString anErrPrefix = TCollection_AsciiString ("File '") + theFile + "' defines invalid glTF!\n";
217   RWGltf_GltfJsonParser aDoc (myRootShapes);
218   aDoc.SetFilePath (theFile);
219   aDoc.SetProbeHeader (theToProbe);
220   aDoc.SetExternalFiles (myExternalFiles);
221   aDoc.SetMetadata (myMetadata);
222   aDoc.SetErrorPrefix (anErrPrefix);
223   aDoc.SetCoordinateSystemConverter (myCoordSysConverter);
224   aDoc.SetSkipEmptyNodes (myToSkipEmptyNodes);
225   aDoc.SetMeshNameAsFallback (myUseMeshNameAsFallback);
226   if (!theToProbe)
227   {
228     aDoc.SetAttributeMap (myAttribMap);
229   }
230   if (isBinaryFile)
231   {
232     aDoc.SetBinaryFormat (aBinBodyOffset, aBinBodyLen);
233   }
234
235 #ifdef HAVE_RAPIDJSON
236   rapidjson::ParseResult aRes;
237   rapidjson::IStreamWrapper aFileStream (aFile);
238   if (isBinaryFile)
239   {
240     aRes = aDoc.ParseStream<rapidjson::kParseStopWhenDoneFlag, rapidjson::UTF8<>, rapidjson::IStreamWrapper> (aFileStream);
241   }
242   else
243   {
244     aRes = aDoc.ParseStream (aFileStream);
245   }
246   if (aRes.IsError())
247   {
248     if (aRes.Code() == rapidjson::kParseErrorDocumentEmpty)
249     {
250       Message::SendFail (TCollection_AsciiString ("File '") + theFile + "' is empty");
251       return false;
252     }
253     TCollection_AsciiString anErrDesc (RWGltf_GltfJsonParser::FormatParseError (aRes.Code()));
254     Message::SendFail (TCollection_AsciiString ("File '") + theFile + "' defines invalid JSON document!\n"
255                      + anErrDesc + " [at offset " + (int )aRes.Offset() + "].");
256     return false;
257   }
258 #endif
259
260   if (!aDoc.Parse (aPSentry.Next()))
261   {
262     return false;
263   }
264
265   if (!theToProbe
266    && !readLateData (aDoc.FaceList(), theFile, aPSentry.Next()))
267   {
268     return false;
269   }
270
271   return true;
272 }
273
274 //================================================================
275 // Function : createMeshReaderContext
276 // Purpose  :
277 //================================================================
278 Handle(RWGltf_PrimitiveArrayReader) RWGltf_CafReader::createMeshReaderContext()
279 {
280   Handle(RWGltf_TriangulationReader) aReader = new RWGltf_TriangulationReader();
281   return aReader;
282 }
283
284 //================================================================
285 // Function : readLateData
286 // Purpose  :
287 //================================================================
288 Standard_Boolean RWGltf_CafReader::readLateData (NCollection_Vector<TopoDS_Face>& theFaces,
289                                                  const TCollection_AsciiString& theFile,
290                                                  const Message_ProgressRange& theProgress)
291 {
292   const Handle(OSD_ThreadPool)& aThreadPool = OSD_ThreadPool::DefaultPool();
293   const int aNbThreads = myToParallel ? Min (theFaces.Size(), aThreadPool->NbDefaultThreadsToLaunch()) : 1;
294   OSD_ThreadPool::Launcher aLauncher (*aThreadPool, aNbThreads);
295
296   CafReader_GltfReaderFunctor aFunctor (this, theFaces, theProgress, aLauncher,
297                                         TCollection_AsciiString ("File '") + theFile + "' defines invalid glTF!\n");
298   aLauncher.Perform (theFaces.Lower(), theFaces.Upper() + 1, aFunctor);
299   return Standard_True;
300 }