0030964: Data Exchange - use Standard_ReadLineBuffer within OBJ reader
[occt.git] / src / RWStl / RWStl_Reader.cxx
1 // Created: 2016-05-01
2 // Author: Andrey Betenev
3 // Copyright: Open CASCADE 2016
4 //
5 // This file is part of Open CASCADE Technology software library.
6 //
7 // This library is free software; you can redistribute it and/or modify it under
8 // the terms of the GNU Lesser General Public License version 2.1 as published
9 // by the Free Software Foundation, with special exception defined in the file
10 // OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT
11 // distribution for complete text of the license and disclaimer of any warranty.
12 //
13 // Alternatively, this file may be used under the terms of Open CASCADE
14 // commercial license or contractual agreement.
15
16 #include <RWStl_Reader.hxx>
17
18 #include <gp_XY.hxx>
19 #include <Message.hxx>
20 #include <Message_Messenger.hxx>
21 #include <Message_ProgressSentry.hxx>
22 #include <NCollection_DataMap.hxx>
23 #include <NCollection_IncAllocator.hxx>
24 #include <FSD_BinaryFile.hxx>
25 #include <OSD_OpenFile.hxx>
26 #include <OSD_Timer.hxx>
27 #include <Precision.hxx>
28 #include <Standard_CLocaleSentry.hxx>
29 #include <Standard_ReadLineBuffer.hxx>
30
31 #include <algorithm>
32 #include <limits>
33
34 IMPLEMENT_STANDARD_RTTIEXT(RWStl_Reader, Standard_Transient)
35
36 namespace
37 {
38   // Binary STL sizes
39   static const size_t THE_STL_HEADER_SIZE   = 84;
40   static const size_t THE_STL_SIZEOF_FACET  = 50;
41   static const size_t THE_STL_MIN_FILE_SIZE = THE_STL_HEADER_SIZE + THE_STL_SIZEOF_FACET;
42
43   // The length of buffer to read (in bytes)
44   static const size_t THE_BUFFER_SIZE = 1024;
45
46   // Buffer to read
47   Standard_ReadLineBuffer THE_BUFFER (THE_BUFFER_SIZE);
48
49   //! Auxiliary tool for merging nodes during STL reading.
50   class MergeNodeTool
51   {
52   public:
53
54     //! Constructor
55     MergeNodeTool (RWStl_Reader* theReader)
56     : myReader (theReader),
57       myMap (1024, new NCollection_IncAllocator (1024 * 1024))
58     {
59     }
60
61     //! Add new triangle
62     int AddNode (double theX, double theY, double theZ)
63     {
64       // use existing node if found at the same point
65       gp_XYZ aPnt (theX, theY, theZ);
66
67       Standard_Integer anIndex = -1;
68       if (myMap.Find (aPnt, anIndex))
69       {
70         return anIndex;
71       }
72
73       anIndex = myReader->AddNode (aPnt);
74       myMap.Bind (aPnt, anIndex);
75       return anIndex;
76     }
77
78   public:
79
80     static Standard_Boolean IsEqual (const gp_XYZ& thePnt1, const gp_XYZ& thePnt2)
81     {
82       return (thePnt1 - thePnt2).SquareModulus() < Precision::SquareConfusion();
83     }
84
85     //! Computes a hash code for the point, in the range [1, theUpperBound]
86     //! @param thePoint the point which hash code is to be computed
87     //! @param theUpperBound the upper bound of the range a computing hash code must be within
88     //! @return a computed hash code, in the range [1, theUpperBound]
89     static Standard_Integer HashCode (const gp_XYZ& thePoint, const Standard_Integer theUpperBound)
90     {
91       return ::HashCode (thePoint.X() * M_LN10 + thePoint.Y() * M_PI + thePoint.Z() * M_E, theUpperBound);
92     }
93
94   private:
95     RWStl_Reader* myReader;
96     NCollection_DataMap<gp_XYZ, Standard_Integer, MergeNodeTool> myMap;
97   };
98
99   //! Read a Little Endian 32 bits float
100   inline static float readStlFloat (const char* theData)
101   {
102   #if OCCT_BINARY_FILE_DO_INVERSE
103     // on big-endian platform, map values byte-per-byte
104     union
105     {
106       uint32_t i;
107       float    f;
108     } bidargum;
109     bidargum.i  =  theData[0] & 0xFF;
110     bidargum.i |= (theData[1] & 0xFF) << 0x08;
111     bidargum.i |= (theData[2] & 0xFF) << 0x10;
112     bidargum.i |= (theData[3] & 0xFF) << 0x18;
113     return bidargum.f;
114   #else
115     // on little-endian platform, use plain cast
116     return *reinterpret_cast<const float*>(theData);
117   #endif
118   }
119
120   //! Read a Little Endian 32 bits float
121   inline static gp_XYZ readStlFloatVec3 (const char* theData)
122   {
123     return gp_XYZ (readStlFloat (theData),
124                    readStlFloat (theData + sizeof(float)),
125                    readStlFloat (theData + sizeof(float) * 2));
126   }
127
128 }
129
130 //==============================================================================
131 //function : Read
132 //purpose  :
133 //==============================================================================
134
135 Standard_Boolean RWStl_Reader::Read (const char* theFile,
136                                      const Handle(Message_ProgressIndicator)& theProgress)
137 {
138   std::filebuf aBuf;
139   OSD_OpenStream (aBuf, theFile, std::ios::in | std::ios::binary);
140   if (!aBuf.is_open())
141   {
142     return Standard_False;
143   }
144
145   Standard_IStream aStream (&aBuf);
146
147   // get length of file to feed progress indicator in Ascii mode
148   aStream.seekg (0, aStream.end);
149   std::streampos theEnd = aStream.tellg();
150   aStream.seekg (0, aStream.beg);
151
152   // binary STL files cannot be shorter than 134 bytes 
153   // (80 bytes header + 4 bytes facet count + 50 bytes for one facet);
154   // thus assume files shorter than 134 as Ascii without probing
155   // (probing may bring stream to fail state if EOF is reached)
156   bool isAscii = ((size_t)theEnd < THE_STL_MIN_FILE_SIZE || IsAscii (aStream));
157
158   while (aStream.good())
159   {
160     if (isAscii)
161     {
162       if (!ReadAscii (aStream, theEnd, theProgress))
163       {
164         break;
165       }
166     }
167     else
168     {
169       if (!ReadBinary (aStream, theProgress))
170       {
171         break;
172       }
173     }
174     aStream >> std::ws; // skip any white spaces
175   }
176   return ! aStream.fail();
177 }
178
179 //==============================================================================
180 //function : IsAscii
181 //purpose  :
182 //==============================================================================
183
184 Standard_Boolean RWStl_Reader::IsAscii (Standard_IStream& theStream)
185 {
186   // read first 134 bytes to detect file format
187   char aBuffer[THE_STL_MIN_FILE_SIZE];
188   std::streamsize aNbRead = theStream.read (aBuffer, THE_STL_MIN_FILE_SIZE).gcount();
189   if (! theStream)
190   {
191     Message::DefaultMessenger()->Send ("Error: Cannot read file", Message_Fail);
192     return true;
193   }
194
195   // put back the read symbols
196   for (std::streamsize aByteIter = aNbRead; aByteIter > 0; --aByteIter)
197   {
198     theStream.unget();
199   }
200
201   // if file is shorter than size of binary file with 1 facet, it must be ascii
202   if (aNbRead < std::streamsize(THE_STL_MIN_FILE_SIZE))
203   {
204     return true;
205   }
206
207   // otherwise, detect binary format by presence of non-ascii symbols in first 128 bytes
208   // (note that binary STL file may start with the same bytes "solid " as Ascii one)
209   for (Standard_Integer aByteIter = 0; aByteIter < aNbRead; ++aByteIter)
210   {
211     if ((unsigned char )aBuffer[aByteIter] > (unsigned char )'~')
212     {
213       return false;
214     }
215   }
216   return true;
217 }
218
219 // adapted from Standard_CString.cxx
220 #ifdef __APPLE__
221   // There are a lot of *_l functions availalbe on Mac OS X - we use them
222   #define SAVE_TL()
223 #elif defined(_MSC_VER)
224   // MSVCRT has equivalents with slightly different syntax
225   #define SAVE_TL()
226   #define sscanf_l(theBuffer, theLocale, theFormat, ...) _sscanf_s_l(theBuffer, theFormat, theLocale, __VA_ARGS__)
227 #else
228   // glibc provides only limited xlocale implementation:
229   // strtod_l/strtol_l/strtoll_l functions with explicitly specified locale
230   // and newlocale/uselocale/freelocale to switch locale within current thread only.
231   // So we switch to C locale temporarily
232   #define SAVE_TL() Standard_CLocaleSentry aLocaleSentry;
233   #define sscanf_l(theBuffer, theLocale, theFormat, ...) sscanf(theBuffer, theFormat, __VA_ARGS__)
234 #endif
235
236 // Macro to get 64-bit position of the file from std::streampos
237 #if defined(_MSC_VER) && _MSC_VER < 1700
238   // In MSVC 2010, cast of std::streampos to 64-bit int is implemented incorrectly;
239   // work-around (relevant for files larger than 4 GB) is to use internal function seekpos(). 
240   // Since MSVC 15.8, seekpos() is deprecated and is said to always return 0.
241   #define GETPOS(aPos) aPos.seekpos()
242 #else
243   #define GETPOS(aPos) ((int64_t)aPos)
244 #endif
245
246 # if defined(_MSC_VER) && ! defined(strncasecmp)
247 #  define strncasecmp _strnicmp
248 # endif
249
250 static inline bool str_starts_with (const char* theStr, const char* theWord, int theN)
251 {
252   while (isspace (*theStr) && *theStr != '\0') theStr++;
253   return !strncasecmp (theStr, theWord, theN); 
254 }
255
256 static bool ReadVertex (const char* theStr, double& theX, double& theY, double& theZ)
257 {
258   const char *aStr = theStr;
259
260   // skip 'vertex'
261   while (isspace ((unsigned char)*aStr) || isalpha ((unsigned char)*aStr)) 
262     ++aStr;
263
264   // read values
265   char *aEnd;
266   theX = Strtod (aStr, &aEnd);
267   theY = Strtod (aStr = aEnd, &aEnd);
268   theZ = Strtod (aStr = aEnd, &aEnd);
269
270   return aEnd != aStr;
271 }
272
273 //==============================================================================
274 //function : ReadAscii
275 //purpose  :
276 //==============================================================================
277 Standard_Boolean RWStl_Reader::ReadAscii (Standard_IStream& theStream,
278                                           const std::streampos theUntilPos,
279                                           const Handle(Message_ProgressIndicator)& theProgress)
280 {
281   // use method seekpos() to get true 64-bit offset to enable
282   // handling of large files (VS 2010 64-bit)
283   const int64_t aStartPos = GETPOS(theStream.tellg());
284   size_t aLineLen = 0;
285   const char* aLine;
286
287   // skip header "solid ..."
288   aLine = THE_BUFFER.ReadLine (theStream, aLineLen);
289   if (aLine == NULL)
290   {
291     Message::DefaultMessenger()->Send ("Error: premature end of file", Message_Fail);
292     return false;
293   }
294
295   MergeNodeTool aMergeTool (this);
296   Standard_CLocaleSentry::clocale_t aLocale = Standard_CLocaleSentry::GetCLocale();
297   (void)aLocale; // to avoid warning on GCC where it is actually not used
298   SAVE_TL() // for GCC only, set C locale globally
299
300   // report progress every 1 MiB of read data
301   const int aStepB = 1024 * 1024;
302   const Standard_Integer aNbSteps = 1 + Standard_Integer((GETPOS(theUntilPos) - aStartPos) / aStepB);
303   Message_ProgressSentry aPSentry (theProgress, "Reading text STL file", 0, aNbSteps, 1);
304   int64_t aProgressPos = aStartPos + aStepB;
305   int aNbLine = 1;
306
307   while (aPSentry.More())
308   {
309     if (GETPOS(theStream.tellg()) > aProgressPos)
310     {
311       aPSentry.Next();
312       aProgressPos += aStepB;
313     }
314
315     aLine = THE_BUFFER.ReadLine (theStream, aLineLen); // "facet normal nx ny nz"
316     if (aLine == NULL)
317     {
318       Message::DefaultMessenger()->Send ("Error: premature end of file", Message_Fail);
319       return false;
320     }
321     if (str_starts_with (aLine, "endsolid", 8))
322     {
323       // end of STL code
324       break;
325     }
326     if (!str_starts_with (aLine, "facet", 5))
327     {
328       TCollection_AsciiString aStr ("Error: unexpected format of facet at line ");
329       aStr += aNbLine + 1;
330       Message::DefaultMessenger()->Send (aStr, Message_Fail);
331       return false;
332     }
333
334     aLine = THE_BUFFER.ReadLine (theStream, aLineLen);  // "outer loop"
335     if (aLine == NULL || !str_starts_with (aLine, "outer", 5))
336     {
337       TCollection_AsciiString aStr ("Error: unexpected format of facet at line ");
338       aStr += aNbLine + 1;
339       Message::DefaultMessenger()->Send (aStr, Message_Fail);
340       return false;
341     }
342
343     gp_XYZ aVertex[3];
344     Standard_Boolean isEOF = false;
345     for (Standard_Integer i = 0; i < 3; i++)
346     {
347       aLine = THE_BUFFER.ReadLine (theStream, aLineLen);
348       if (aLine == NULL)
349       {
350         isEOF = true;
351         break;
352       }
353       gp_XYZ aReadVertex;
354       if (!ReadVertex (aLine, aReadVertex.ChangeCoord (1), aReadVertex.ChangeCoord (2), aReadVertex.ChangeCoord (3)))
355       {
356         TCollection_AsciiString aStr ("Error: cannot read vertex co-ordinates at line ");
357         aStr += aNbLine;
358         Message::DefaultMessenger()->Send (aStr, Message_Fail);
359         return false;
360       }
361       aVertex[i] = aReadVertex;
362     }
363
364     // stop reading if end of file is reached;
365     // note that well-formatted file never ends by the vertex line
366     if (isEOF)
367     {
368       break;
369     }
370
371     aNbLine += 5;
372
373     // add triangle
374     int n1 = aMergeTool.AddNode (aVertex[0].X(), aVertex[0].Y(), aVertex[0].Z());
375     int n2 = aMergeTool.AddNode (aVertex[1].X(), aVertex[1].Y(), aVertex[1].Z());
376     int n3 = aMergeTool.AddNode (aVertex[2].X(), aVertex[2].Y(), aVertex[2].Z());
377     if (n1 != n2 && n2 != n3 && n3 != n1)
378     {
379       AddTriangle (n1, n2, n3);
380     }
381
382     THE_BUFFER.ReadLine (theStream, aLineLen); // skip "endloop"
383     THE_BUFFER.ReadLine (theStream, aLineLen); // skip "endfacet"
384
385     aNbLine += 2;
386   }
387
388   return aPSentry.More();
389 }
390
391 //==============================================================================
392 //function : readStlBinary
393 //purpose  :
394 //==============================================================================
395
396 Standard_Boolean RWStl_Reader::ReadBinary (Standard_IStream& theStream,
397                                            const Handle(Message_ProgressIndicator)& theProgress)
398 {
399 /*
400   // the size of the file (minus the header size)
401   // must be a multiple of SIZEOF_STL_FACET
402   if ((theFileLen - THE_STL_HEADER_SIZE) % THE_STL_SIZEOF_FACET != 0
403    || (theFileLen < THE_STL_MIN_FILE_SIZE))
404   {
405     Message::DefaultMessenger()->Send ("Error: Corrupted binary STL file (inconsistent file size)!", Message_Fail);
406     return Standard_False;
407   }
408   const Standard_Integer  aNbFacets = Standard_Integer((theFileLen - THE_STL_HEADER_SIZE) / THE_STL_SIZEOF_FACET);
409 */
410
411   // read file header at first
412   char aHeader[THE_STL_HEADER_SIZE + 1];
413   if (theStream.read (aHeader, THE_STL_HEADER_SIZE).gcount() != std::streamsize(THE_STL_HEADER_SIZE))
414   {
415     Message::DefaultMessenger()->Send ("Error: Corrupted binary STL file!", Message_Fail);
416     return false;
417   }
418
419   // number of facets is stored as 32-bit integer at position 80
420   const Standard_Integer aNbFacets = *(int32_t*)(aHeader + 80);
421
422   MergeNodeTool aMergeTool (this);
423
424   // don't trust the number of triangles which is coded in the file
425   // sometimes it is wrong, and with this technique we don't need to swap endians for integer
426   Message_ProgressSentry  aPSentry (theProgress, "Reading binary STL file", 0, aNbFacets, 1);
427   Standard_Integer        aNbRead = 0;
428
429   // allocate buffer for 80 triangles
430   const int THE_CHUNK_NBFACETS = 80;
431   char aBuffer[THE_STL_SIZEOF_FACET * THE_CHUNK_NBFACETS];
432
433   // normal + 3 nodes + 2 extra bytes
434   const size_t aVec3Size    = sizeof(float) * 3;
435   const size_t aFaceDataLen = aVec3Size * 4 + 2;
436   const char*  aBufferPtr   = aBuffer;
437   Standard_Integer aNbFacesInBuffer = 0;
438   for (Standard_Integer aNbFacetRead = 0; aNbFacetRead < aNbFacets && aPSentry.More();
439        ++aNbFacetRead, ++aNbRead, --aNbFacesInBuffer, aBufferPtr += aFaceDataLen, aPSentry.Next())
440   {
441     // read more data
442     if (aNbFacesInBuffer <= 0)
443     {
444       aNbFacesInBuffer = Min (THE_CHUNK_NBFACETS, aNbFacets - aNbFacetRead);
445       const std::streamsize aDataToRead = aNbFacesInBuffer * aFaceDataLen;
446       if (theStream.read (aBuffer, aDataToRead).gcount() != aDataToRead)
447       {
448         Message::DefaultMessenger()->Send ("Error: binary STL read failed", Message_Fail);
449         return false;
450       }
451       aBufferPtr = aBuffer;
452     }
453
454     // get points from buffer
455 //    readStlFloatVec3 (aBufferPtr); // skip normal
456     gp_XYZ aP1 = readStlFloatVec3 (aBufferPtr + aVec3Size);
457     gp_XYZ aP2 = readStlFloatVec3 (aBufferPtr + aVec3Size * 2);
458     gp_XYZ aP3 = readStlFloatVec3 (aBufferPtr + aVec3Size * 3);
459
460     // add triangle
461     int n1 = aMergeTool.AddNode (aP1.X(), aP1.Y(), aP1.Z());
462     int n2 = aMergeTool.AddNode (aP2.X(), aP2.Y(), aP2.Z());
463     int n3 = aMergeTool.AddNode (aP3.X(), aP3.Y(), aP3.Z());
464     if (n1 != n2 && n2 != n3 && n3 != n1)
465     {
466       AddTriangle (n1, n2, n3);
467     }
468   }
469
470   return true;
471 }