0030691: Data Exchange - implement import of mesh data from files in glTF format
[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 {
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 Handle(Message_ProgressIndicator)& theProgress,
118                                                 const Standard_Boolean theToProbe)
119 {
120   std::ifstream aFile;
121   OSD_OpenStream (aFile, theFile.ToCString(), std::ios::in | std::ios::binary);
122   if (!aFile.is_open()
123    || !aFile.good())
124   {
125     Message::DefaultMessenger()->Send (TCollection_AsciiString ("File '") + theFile + "' is not found!", Message_Fail);
126     return false;
127   }
128
129   bool isBinaryFile = false;
130   char aGlbHeader[12] = {};
131   aFile.read (aGlbHeader, sizeof(aGlbHeader));
132   int64_t aBinBodyOffset  = 0;
133   int64_t aBinBodyLen     = 0;
134   int64_t aJsonBodyOffset = 0;
135   int64_t aJsonBodyLen    = 0;
136   if (::strncmp (aGlbHeader, "glTF", 4) == 0)
137   {
138     isBinaryFile = true;
139     const uint32_t* aVer = (const uint32_t* )(aGlbHeader + 4);
140     const uint32_t* aLen = (const uint32_t* )(aGlbHeader + 8);
141     if (*aVer == 1)
142     {
143       if (*aLen < 20)
144       {
145         Message::DefaultMessenger()->Send (TCollection_AsciiString ("File '") + theFile + "' has broken glTF format!", Message_Fail);
146         return false;
147       }
148
149       char aHeader1[8] = {};
150       aFile.read (aHeader1, sizeof(aHeader1));
151
152       const uint32_t* aSceneLen    = (const uint32_t* )(aHeader1 + 0);
153       const uint32_t* aSceneFormat = (const uint32_t* )(aHeader1 + 4);
154       aJsonBodyOffset = 20;
155       aJsonBodyLen    = int64_t(*aSceneLen);
156
157       aBinBodyOffset = aJsonBodyOffset + aJsonBodyLen;
158       aBinBodyLen    = int64_t(*aLen) - aBinBodyOffset;
159       if (*aSceneFormat != 0)
160       {
161         Message::DefaultMessenger()->Send (TCollection_AsciiString ("File '") + theFile + "' is written using unsupported Scene format!", Message_Fail);
162         return false;
163       }
164     }
165     else //if (*aVer == 2)
166     {
167       if (*aVer != 2)
168       {
169         Message::DefaultMessenger()->Send (TCollection_AsciiString ("File '") + theFile + "' is written using unknown version " + int(*aVer) + "!", Message_Warning);
170       }
171
172       for (int aChunkIter = 0; !aFile.eof() && aChunkIter < 2; ++aChunkIter)
173       {
174         char aChunkHeader2[8] = {};
175         if (int64_t(aFile.tellg()) + int64_t(sizeof(aChunkHeader2)) > int64_t(aLen))
176         {
177           break;
178         }
179
180         aFile.read (aChunkHeader2, sizeof(aChunkHeader2));
181         if (!aFile.good())
182         {
183           Message::DefaultMessenger()->Send (TCollection_AsciiString ("File '") + theFile + "' is written using unsupported format!", Message_Fail);
184           return false;
185         }
186
187         const uint32_t* aChunkLen  = (const uint32_t* )(aChunkHeader2 + 0);
188         const uint32_t* aChunkType = (const uint32_t* )(aChunkHeader2 + 4);
189         if (*aChunkType == 0x4E4F534A)
190         {
191           aJsonBodyOffset = int64_t(aFile.tellg());
192           aJsonBodyLen    = int64_t(*aChunkLen);
193         }
194         else if (*aChunkType == 0x004E4942)
195         {
196           aBinBodyOffset = int64_t(aFile.tellg());
197           aBinBodyLen    = int64_t(*aChunkLen);
198         }
199         if (*aChunkLen != 0)
200         {
201           aFile.seekg (*aChunkLen, std::ios_base::cur);
202         }
203       }
204
205       aFile.seekg ((std::streamoff )aJsonBodyOffset, std::ios_base::beg);
206     }
207   }
208   else
209   {
210     aFile.seekg (0, std::ios_base::beg);
211   }
212
213   TCollection_AsciiString anErrPrefix = TCollection_AsciiString ("File '") + theFile + "' defines invalid glTF!\n";
214   RWGltf_GltfJsonParser aDoc (myRootShapes);
215   aDoc.SetFilePath (theFile);
216   aDoc.SetProbeHeader (theToProbe);
217   aDoc.SetExternalFiles (myExternalFiles);
218   aDoc.SetErrorPrefix (anErrPrefix);
219   aDoc.SetCoordinateSystemConverter (myCoordSysConverter);
220   if (!theToProbe)
221   {
222     aDoc.SetAttributeMap (myAttribMap);
223   }
224   if (isBinaryFile)
225   {
226     aDoc.SetBinaryFormat (aBinBodyOffset, aBinBodyLen);
227   }
228
229 #ifdef HAVE_RAPIDJSON
230   rapidjson::ParseResult aRes;
231   rapidjson::IStreamWrapper aFileStream (aFile);
232   if (isBinaryFile)
233   {
234     aRes = aDoc.ParseStream<rapidjson::kParseStopWhenDoneFlag, rapidjson::UTF8<>, rapidjson::IStreamWrapper> (aFileStream);
235   }
236   else
237   {
238     aRes = aDoc.ParseStream (aFileStream);
239   }
240   if (aRes.IsError())
241   {
242     if (aRes.Code() == rapidjson::kParseErrorDocumentEmpty)
243     {
244       Message::DefaultMessenger()->Send (TCollection_AsciiString ("File '") + theFile + "' is empty!", Message_Fail);
245       return false;
246     }
247     TCollection_AsciiString anErrDesc (RWGltf_GltfJsonParser::FormatParseError (aRes.Code()));
248     Message::DefaultMessenger()->Send (TCollection_AsciiString ("File '") + theFile + "' defines invalid JSON document!\n"
249                                      + anErrDesc + ".", Message_Fail);
250     return false;
251   }
252 #endif
253
254   if (!aDoc.Parse (theProgress))
255   {
256     return false;
257   }
258
259   if (!theToProbe
260    && !readLateData (aDoc.FaceList(), theFile, theProgress))
261   {
262     return false;
263   }
264
265   return true;
266 }
267
268 //================================================================
269 // Function : createMeshReaderContext
270 // Purpose  :
271 //================================================================
272 Handle(RWGltf_PrimitiveArrayReader) RWGltf_CafReader::createMeshReaderContext()
273 {
274   Handle(RWGltf_TriangulationReader) aReader = new RWGltf_TriangulationReader();
275   return aReader;
276 }
277
278 //================================================================
279 // Function : readLateData
280 // Purpose  :
281 //================================================================
282 Standard_Boolean RWGltf_CafReader::readLateData (NCollection_Vector<TopoDS_Face>& theFaces,
283                                                  const TCollection_AsciiString& theFile,
284                                                  const Handle(Message_ProgressIndicator)& theProgress)
285 {
286   Message_ProgressSentry aPSentryTris (theProgress, "Loading glTF triangulation", 0, Max (1, theFaces.Size()), 1);
287   const Handle(OSD_ThreadPool)& aThreadPool = OSD_ThreadPool::DefaultPool();
288   const int aNbThreads = myToParallel ? Min (theFaces.Size(), aThreadPool->NbDefaultThreadsToLaunch()) : 1;
289   OSD_ThreadPool::Launcher aLauncher (*aThreadPool, aNbThreads);
290
291   CafReader_GltfReaderFunctor aFunctor (this, theFaces, aPSentryTris, aLauncher,
292                                         TCollection_AsciiString ("File '") + theFile + "' defines invalid glTF!\n");
293   aLauncher.Perform (theFaces.Lower(), theFaces.Upper() + 1, aFunctor);
294   return Standard_True;
295 }