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