0031501: Foundation Classes, Message_Printer - remove theToPutEndl argument -- use...
[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_ProgressSentry.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                                Message_ProgressSentry& theSentry,
46                                const OSD_ThreadPool::Launcher& theThreadPool,
47                                const TCollection_AsciiString& theErrPrefix)
48   : myCafReader (myCafReader),
49     myFaceList  (&theFaceList),
50     mySentry    (&theSentry),
51     myErrPrefix (theErrPrefix),
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       mySentry->Next();
81     }
82     else
83     {
84       mySentry->Next();
85     }
86   }
87
88 private:
89
90   RWGltf_CafReader* myCafReader;
91   NCollection_Vector<TopoDS_Face>* myFaceList;
92   Message_ProgressSentry*   mySentry;
93   TCollection_AsciiString   myErrPrefix;
94   mutable Standard_Mutex    myMutex;
95   const OSD_ThreadPool::Launcher& myThreadPool;
96   mutable NCollection_Array1<GltfReaderTLS>
97                             myTlsData;
98
99 };
100
101 //================================================================
102 // Function : Constructor
103 // Purpose  :
104 //================================================================
105 RWGltf_CafReader::RWGltf_CafReader()
106 : myToParallel (false),
107   myToSkipEmptyNodes (true),
108   myUseMeshNameAsFallback (true)
109 {
110   myCoordSysConverter.SetInputLengthUnit (1.0); // glTF defines model in meters
111   myCoordSysConverter.SetInputCoordinateSystem (RWMesh_CoordinateSystem_glTF);
112 }
113
114 //================================================================
115 // Function : performMesh
116 // Purpose  :
117 //================================================================
118 Standard_Boolean RWGltf_CafReader::performMesh (const TCollection_AsciiString& theFile,
119                                                 const Handle(Message_ProgressIndicator)& theProgress,
120                                                 const Standard_Boolean theToProbe)
121 {
122   std::ifstream aFile;
123   OSD_OpenStream (aFile, theFile.ToCString(), std::ios::in | std::ios::binary);
124   if (!aFile.is_open()
125    || !aFile.good())
126   {
127     Message::SendFail (TCollection_AsciiString ("File '") + theFile + "' is not found");
128     return false;
129   }
130
131   bool isBinaryFile = false;
132   char aGlbHeader[12] = {};
133   aFile.read (aGlbHeader, sizeof(aGlbHeader));
134   int64_t aBinBodyOffset  = 0;
135   int64_t aBinBodyLen     = 0;
136   int64_t aJsonBodyOffset = 0;
137   int64_t aJsonBodyLen    = 0;
138   if (::strncmp (aGlbHeader, "glTF", 4) == 0)
139   {
140     isBinaryFile = true;
141     const uint32_t* aVer = (const uint32_t* )(aGlbHeader + 4);
142     const uint32_t* aLen = (const uint32_t* )(aGlbHeader + 8);
143     if (*aVer == 1)
144     {
145       if (*aLen < 20)
146       {
147         Message::SendFail (TCollection_AsciiString ("File '") + theFile + "' has broken glTF format");
148         return false;
149       }
150
151       char aHeader1[8] = {};
152       aFile.read (aHeader1, sizeof(aHeader1));
153
154       const uint32_t* aSceneLen    = (const uint32_t* )(aHeader1 + 0);
155       const uint32_t* aSceneFormat = (const uint32_t* )(aHeader1 + 4);
156       aJsonBodyOffset = 20;
157       aJsonBodyLen    = int64_t(*aSceneLen);
158
159       aBinBodyOffset = aJsonBodyOffset + aJsonBodyLen;
160       aBinBodyLen    = int64_t(*aLen) - aBinBodyOffset;
161       if (*aSceneFormat != 0)
162       {
163         Message::SendFail (TCollection_AsciiString ("File '") + theFile + "' is written using unsupported Scene format");
164         return false;
165       }
166     }
167     else //if (*aVer == 2)
168     {
169       if (*aVer != 2)
170       {
171         Message::SendWarning (TCollection_AsciiString ("File '") + theFile + "' is written using unknown version " + int(*aVer));
172       }
173
174       for (int aChunkIter = 0; !aFile.eof() && aChunkIter < 2; ++aChunkIter)
175       {
176         char aChunkHeader2[8] = {};
177         if (int64_t(aFile.tellg()) + int64_t(sizeof(aChunkHeader2)) > int64_t(*aLen))
178         {
179           break;
180         }
181
182         aFile.read (aChunkHeader2, sizeof(aChunkHeader2));
183         if (!aFile.good())
184         {
185           Message::SendFail (TCollection_AsciiString ("File '") + theFile + "' is written using unsupported format");
186           return false;
187         }
188
189         const uint32_t* aChunkLen  = (const uint32_t* )(aChunkHeader2 + 0);
190         const uint32_t* aChunkType = (const uint32_t* )(aChunkHeader2 + 4);
191         if (*aChunkType == 0x4E4F534A)
192         {
193           aJsonBodyOffset = int64_t(aFile.tellg());
194           aJsonBodyLen    = int64_t(*aChunkLen);
195         }
196         else if (*aChunkType == 0x004E4942)
197         {
198           aBinBodyOffset = int64_t(aFile.tellg());
199           aBinBodyLen    = int64_t(*aChunkLen);
200         }
201         if (*aChunkLen != 0)
202         {
203           aFile.seekg (*aChunkLen, std::ios_base::cur);
204         }
205       }
206
207       aFile.seekg ((std::streamoff )aJsonBodyOffset, std::ios_base::beg);
208     }
209   }
210   else
211   {
212     aFile.seekg (0, std::ios_base::beg);
213   }
214
215   TCollection_AsciiString anErrPrefix = TCollection_AsciiString ("File '") + theFile + "' defines invalid glTF!\n";
216   RWGltf_GltfJsonParser aDoc (myRootShapes);
217   aDoc.SetFilePath (theFile);
218   aDoc.SetProbeHeader (theToProbe);
219   aDoc.SetExternalFiles (myExternalFiles);
220   aDoc.SetMetadata (myMetadata);
221   aDoc.SetErrorPrefix (anErrPrefix);
222   aDoc.SetCoordinateSystemConverter (myCoordSysConverter);
223   aDoc.SetSkipEmptyNodes (myToSkipEmptyNodes);
224   aDoc.SetMeshNameAsFallback (myUseMeshNameAsFallback);
225   if (!theToProbe)
226   {
227     aDoc.SetAttributeMap (myAttribMap);
228   }
229   if (isBinaryFile)
230   {
231     aDoc.SetBinaryFormat (aBinBodyOffset, aBinBodyLen);
232   }
233
234 #ifdef HAVE_RAPIDJSON
235   rapidjson::ParseResult aRes;
236   rapidjson::IStreamWrapper aFileStream (aFile);
237   if (isBinaryFile)
238   {
239     aRes = aDoc.ParseStream<rapidjson::kParseStopWhenDoneFlag, rapidjson::UTF8<>, rapidjson::IStreamWrapper> (aFileStream);
240   }
241   else
242   {
243     aRes = aDoc.ParseStream (aFileStream);
244   }
245   if (aRes.IsError())
246   {
247     if (aRes.Code() == rapidjson::kParseErrorDocumentEmpty)
248     {
249       Message::SendFail (TCollection_AsciiString ("File '") + theFile + "' is empty");
250       return false;
251     }
252     TCollection_AsciiString anErrDesc (RWGltf_GltfJsonParser::FormatParseError (aRes.Code()));
253     Message::SendFail (TCollection_AsciiString ("File '") + theFile + "' defines invalid JSON document!\n"
254                      + anErrDesc + " [at offset " + (int )aRes.Offset() + "].");
255     return false;
256   }
257 #endif
258
259   if (!aDoc.Parse (theProgress))
260   {
261     return false;
262   }
263
264   if (!theToProbe
265    && !readLateData (aDoc.FaceList(), theFile, theProgress))
266   {
267     return false;
268   }
269
270   return true;
271 }
272
273 //================================================================
274 // Function : createMeshReaderContext
275 // Purpose  :
276 //================================================================
277 Handle(RWGltf_PrimitiveArrayReader) RWGltf_CafReader::createMeshReaderContext()
278 {
279   Handle(RWGltf_TriangulationReader) aReader = new RWGltf_TriangulationReader();
280   return aReader;
281 }
282
283 //================================================================
284 // Function : readLateData
285 // Purpose  :
286 //================================================================
287 Standard_Boolean RWGltf_CafReader::readLateData (NCollection_Vector<TopoDS_Face>& theFaces,
288                                                  const TCollection_AsciiString& theFile,
289                                                  const Handle(Message_ProgressIndicator)& theProgress)
290 {
291   Message_ProgressSentry aPSentryTris (theProgress, "Loading glTF triangulation", 0, Max (1, theFaces.Size()), 1);
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, aPSentryTris, aLauncher,
297                                         TCollection_AsciiString ("File '") + theFile + "' defines invalid glTF!\n");
298   aLauncher.Perform (theFaces.Lower(), theFaces.Upper() + 1, aFunctor);
299   return Standard_True;
300 }