0025748: Parallel version of progress indicator
[occt.git] / src / RWObj / RWObj_Reader.cxx
1 // Author: Kirill Gavrilov
2 // Copyright (c) 2017-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 <RWObj_Reader.hxx>
16
17 #include <RWObj_MtlReader.hxx>
18
19 #include <BRepMesh_DataStructureOfDelaun.hxx>
20 #include <BRepMesh_Delaun.hxx>
21 #include <gp_XY.hxx>
22 #include <Message.hxx>
23 #include <Message_Messenger.hxx>
24 #include <Message_ProgressScope.hxx>
25 #include <NCollection_IncAllocator.hxx>
26 #include <OSD_OpenFile.hxx>
27 #include <OSD_Path.hxx>
28 #include <OSD_Timer.hxx>
29 #include <Precision.hxx>
30 #include <Standard_CLocaleSentry.hxx>
31 #include <Standard_ReadLineBuffer.hxx>
32
33 #include <algorithm>
34 #include <limits>
35
36 #if defined(_WIN32)
37   #define ftell64(a)     _ftelli64(a)
38   #define fseek64(a,b,c) _fseeki64(a,b,c)
39 #else
40   #define ftell64(a)     ftello(a)
41   #define fseek64(a,b,c) fseeko(a,b,c)
42 #endif
43
44 IMPLEMENT_STANDARD_RTTIEXT(RWObj_Reader, Standard_Transient)
45
46 namespace
47 {
48   // The length of buffer to read (in bytes)
49   static const size_t THE_BUFFER_SIZE = 4 * 1024;
50
51   //! Simple wrapper.
52   struct RWObj_ReaderFile
53   {
54     FILE*   File;
55     int64_t FileLen;
56
57     //! Constructor opening the file.
58     RWObj_ReaderFile (const TCollection_AsciiString& theFile)
59     : File (OSD_OpenFile (theFile.ToCString(), "rb")),
60       FileLen (0)
61     {
62       if (this->File != NULL)
63       {
64         // determine length of file
65         ::fseek64 (this->File, 0, SEEK_END);
66         FileLen = ::ftell64 (this->File);
67         ::fseek64 (this->File, 0, SEEK_SET);
68       }
69     }
70
71     //! Destructor closing the file.
72     ~RWObj_ReaderFile()
73     {
74       if (File != NULL)
75       {
76         ::fclose (File);
77       }
78     }
79   };
80
81   //! Return TRUE if given polygon has clockwise node order.
82   static bool isClockwisePolygon (const Handle(BRepMesh_DataStructureOfDelaun)& theMesh,
83                                   const IMeshData::VectorOfInteger& theIndexes)
84   {
85     double aPtSum = 0;
86     const int aNbElemNodes = theIndexes.Size();
87     for (int aNodeIter = theIndexes.Lower(); aNodeIter <= theIndexes.Upper(); ++aNodeIter)
88     {
89       int aNodeNext = theIndexes.Lower() + ((aNodeIter + 1) % aNbElemNodes);
90       const BRepMesh_Vertex& aVert1 = theMesh->GetNode (theIndexes.Value (aNodeIter));
91       const BRepMesh_Vertex& aVert2 = theMesh->GetNode (theIndexes.Value (aNodeNext));
92       aPtSum += (aVert2.Coord().X() - aVert1.Coord().X())
93               * (aVert2.Coord().Y() + aVert1.Coord().Y());
94     }
95     return aPtSum < 0.0;
96   }
97 }
98
99 // ================================================================
100 // Function : Read
101 // Purpose  :
102 // ================================================================
103 RWObj_Reader::RWObj_Reader()
104 : myMemLimitBytes (Standard_Size(-1)),
105   myMemEstim (0),
106   myNbLines (0),
107   myNbProbeNodes (0),
108   myNbProbeElems (0),
109   myNbElemsBig (0),
110   myToAbort (false)
111 {
112   //
113 }
114
115 // ================================================================
116 // Function : read
117 // Purpose  :
118 // ================================================================
119 Standard_Boolean RWObj_Reader::read (const TCollection_AsciiString& theFile,
120                                      const Message_ProgressRange& theProgress,
121                                      const Standard_Boolean theToProbe)
122 {
123   myMemEstim = 0;
124   myNbLines = 0;
125   myNbProbeNodes = 0;
126   myNbProbeElems = 0;
127   myNbElemsBig = 0;
128   myToAbort = false;
129   myObjVerts.Reset();
130   myObjVertsUV.Clear();
131   myObjNorms.Clear();
132   myPackedIndices.Clear();
133   myMaterials.Clear();
134   myFileComments.Clear();
135   myExternalFiles.Clear();
136   myActiveSubMesh = RWObj_SubMesh();
137
138   // determine file location to load associated files
139   TCollection_AsciiString aFileName;
140   OSD_Path::FolderAndFileFromPath (theFile, myFolder, aFileName);
141   myCurrElem.resize (1024, -1);
142
143   Standard_CLocaleSentry aLocaleSentry;
144   RWObj_ReaderFile aFile (theFile);
145   if (aFile.File == NULL)
146   {
147     Message::SendFail (TCollection_AsciiString ("Error: file '") + theFile + "' is not found");
148     return Standard_False;
149   }
150
151   // determine length of file
152   const int64_t aFileLen = aFile.FileLen;
153   if (aFileLen <= 0L)
154   {
155     Message::SendFail (TCollection_AsciiString ("Error: file '") + theFile + "' is empty");
156     return Standard_False;
157   }
158
159   Standard_ReadLineBuffer aBuffer (THE_BUFFER_SIZE);
160   aBuffer.SetMultilineMode (true);
161
162   const Standard_Integer aNbMiBTotal  = Standard_Integer(aFileLen / (1024 * 1024));
163   Standard_Integer       aNbMiBPassed = 0;
164   Message_ProgressScope aPS (theProgress, "Reading text OBJ file", aNbMiBTotal);
165   OSD_Timer aTimer;
166   aTimer.Start();
167
168   bool isStart = true;
169   int64_t aPosition = 0;
170   size_t aLineLen = 0;
171   int64_t aReadBytes = 0;
172   const char* aLine = NULL;
173   for (;;)
174   {
175     aLine = aBuffer.ReadLine (aFile.File, aLineLen, aReadBytes);
176     if (aLine == NULL)
177     {
178       break;
179     }
180     ++myNbLines;
181     aPosition += aReadBytes;
182     if (aTimer.ElapsedTime() > 1.0)
183     {
184       if (!aPS.More())
185       {
186         return false;
187       }
188
189       const Standard_Integer aNbMiBRead = Standard_Integer(aPosition / (1024 * 1024));
190       aPS.Next (aNbMiBRead - aNbMiBPassed);
191       aNbMiBPassed = aNbMiBRead;
192       aTimer.Reset();
193       aTimer.Start();
194     }
195
196     if (*aLine == '#')
197     {
198       if (isStart)
199       {
200         TCollection_AsciiString aComment (aLine + 1);
201         aComment.LeftAdjust();
202         aComment.RightAdjust();
203         if (!aComment.IsEmpty())
204         {
205           if (!myFileComments.IsEmpty())
206           {
207             myFileComments += "\n";
208           }
209           myFileComments += aComment;
210         }
211       }
212       continue;
213     }
214     else if (*aLine == '\n'
215           || *aLine == '\0')
216     {
217
218       continue;
219     }
220     isStart = false;
221
222     if (theToProbe)
223     {
224       if (::strncmp (aLine, "mtllib", 6) == 0)
225       {
226         readMaterialLib (IsSpace (aLine[6]) ? aLine + 7 : "");
227       }
228       else if (aLine[0] == 'v' && RWObj_Tools::isSpaceChar (aLine[1]))
229       {
230         ++myNbProbeNodes;
231       }
232       else if (aLine[0] == 'f' && RWObj_Tools::isSpaceChar (aLine[1]))
233       {
234         ++myNbProbeElems;
235       }
236       continue;
237     }
238
239     if (aLine[0] == 'v' && RWObj_Tools::isSpaceChar (aLine[1]))
240     {
241       ++myNbProbeNodes;
242       pushVertex (aLine + 2);
243     }
244     else if (aLine[0] == 'v'
245           && aLine[1] == 'n'
246           && RWObj_Tools::isSpaceChar (aLine[2]))
247     {
248       pushNormal (aLine + 3);
249     }
250     else if (aLine[0] == 'v'
251           && aLine[1] == 't'
252           && RWObj_Tools::isSpaceChar (aLine[2]))
253     {
254       pushTexel (aLine + 3);
255     }
256     else if (aLine[0] == 'f' && RWObj_Tools::isSpaceChar (aLine[1]))
257     {
258       ++myNbProbeElems;
259       pushIndices (aLine + 2);
260     }
261     else if (aLine[0] == 'g' && IsSpace (aLine[1]))
262     {
263       pushGroup (aLine + 2);
264     }
265     else if (aLine[0] == 's' && IsSpace (aLine[1]))
266     {
267       pushSmoothGroup (aLine + 2);
268     }
269     else if (aLine[0] == 'o' && IsSpace (aLine[1]))
270     {
271       pushObject (aLine + 2);
272     }
273     else if (::strncmp (aLine, "mtllib", 6) == 0)
274     {
275       readMaterialLib (IsSpace (aLine[6]) ? aLine + 7 : "");
276     }
277     else if (::strncmp (aLine, "usemtl", 6) == 0)
278     {
279       pushMaterial (IsSpace (aLine[6]) ? aLine + 7 : "");
280     }
281
282     if (!checkMemory())
283     {
284       addMesh (myActiveSubMesh, RWObj_SubMeshReason_NewObject);
285       return false;
286     }
287   }
288
289   // collect external references
290   for (NCollection_DataMap<TCollection_AsciiString, RWObj_Material>::Iterator aMatIter (myMaterials); aMatIter.More(); aMatIter.Next())
291   {
292     const RWObj_Material& aMat = aMatIter.Value();
293     if (!aMat.DiffuseTexture.IsEmpty())
294     {
295       myExternalFiles.Add (aMat.DiffuseTexture);
296     }
297     if (!aMat.SpecularTexture.IsEmpty())
298     {
299       myExternalFiles.Add (aMat.SpecularTexture);
300     }
301     if (!aMat.BumpTexture.IsEmpty())
302     {
303       myExternalFiles.Add (aMat.BumpTexture);
304     }
305   }
306
307   // flush the last group
308   if (!theToProbe)
309   {
310     addMesh (myActiveSubMesh, RWObj_SubMeshReason_NewObject);
311   }
312   if (myNbElemsBig != 0)
313   {
314     Message::SendWarning (TCollection_AsciiString("Warning: OBJ reader, ") + myNbElemsBig + " polygon(s) have been split into triangles");
315   }
316
317   return true;
318 }
319
320 // =======================================================================
321 // function : pushIndices
322 // purpose  :
323 // =======================================================================
324 void RWObj_Reader::pushIndices (const char* thePos)
325 {
326   char* aNext = NULL;
327
328   Standard_Integer aNbElemNodes = 0;
329   for (Standard_Integer aNode = 0;; ++aNode)
330   {
331     Graphic3d_Vec3i a3Indices (-1, -1, -1);
332     a3Indices[0] = strtol (thePos, &aNext, 10) - 1;
333     if (aNext == thePos)
334     {
335       break;
336     }
337
338     // parse UV index
339     thePos = aNext;
340     if (*thePos == '/')
341     {
342       ++thePos;
343       a3Indices[1] = strtol (thePos, &aNext, 10) - 1;
344       thePos = aNext;
345
346       // parse Normal index
347       if (*thePos == '/')
348       {
349         ++thePos;
350         a3Indices[2] = strtol (thePos, &aNext, 10) - 1;
351         thePos = aNext;
352       }
353     }
354
355     // handle negative indices
356     if (a3Indices[0] < -1)
357     {
358       a3Indices[0] += myObjVerts.Upper() + 2;
359     }
360     if (a3Indices[1] < -1)
361     {
362       a3Indices[1] += myObjVertsUV.Upper() + 2;
363     }
364     if (a3Indices[2] < -1)
365     {
366       a3Indices[2] += myObjNorms.Upper() + 2;
367     }
368
369     Standard_Integer anIndex = -1;
370     if (!myPackedIndices.Find (a3Indices, anIndex))
371     {
372       if (a3Indices[0] >= 0)
373       {
374         myMemEstim += sizeof(Graphic3d_Vec3);
375       }
376       if (a3Indices[1] >= 0)
377       {
378         myMemEstim += sizeof(Graphic3d_Vec2);
379       }
380       if (a3Indices[2] >= 0)
381       {
382         myMemEstim += sizeof(Graphic3d_Vec3);
383       }
384       myMemEstim += sizeof(Graphic3d_Vec4i) + sizeof(Standard_Integer); // naive map
385       if (a3Indices[0] < myObjVerts.Lower() || a3Indices[0] > myObjVerts.Upper())
386       {
387         myToAbort = true;
388         Message::SendFail (TCollection_AsciiString("Error: invalid OBJ syntax at line ") + myNbLines + ": vertex index is out of range");
389         return;
390       }
391
392       anIndex = addNode (myObjVerts.Value (a3Indices[0]));
393       myPackedIndices.Bind (a3Indices, anIndex);
394       if (a3Indices[1] >= 0)
395       {
396         if (myObjVertsUV.IsEmpty())
397         {
398           Message::SendWarning (TCollection_AsciiString("Warning: invalid OBJ syntax at line ") + myNbLines
399                               + ": UV index is specified but no UV nodes are defined");
400         }
401         else if (a3Indices[1] < myObjVertsUV.Lower() || a3Indices[1] > myObjVertsUV.Upper())
402         {
403           Message::SendWarning (TCollection_AsciiString("Warning: invalid OBJ syntax at line ") + myNbLines
404                               + ": UV index is out of range");
405           setNodeUV (anIndex,Graphic3d_Vec2 (0.0f, 0.0f));
406         }
407         else
408         {
409           setNodeUV (anIndex, myObjVertsUV.Value (a3Indices[1]));
410         }
411       }
412       if (a3Indices[2] >= 0)
413       {
414         if (myObjNorms.IsEmpty())
415         {
416           Message::SendWarning (TCollection_AsciiString("Warning: invalid OBJ syntax at line ") + myNbLines
417                               + ": Normal index is specified but no Normals nodes are defined");
418         }
419         else if (a3Indices[2] < myObjNorms.Lower() || a3Indices[2] > myObjNorms.Upper())
420         {
421           Message::SendWarning (TCollection_AsciiString("Warning: invalid OBJ syntax at line ") + myNbLines
422                               + ": Normal index is out of range");
423           setNodeNormal (anIndex, Graphic3d_Vec3 (0.0f, 0.0f, 1.0f));
424         }
425         else
426         {
427           setNodeNormal (anIndex, myObjNorms.Value (a3Indices[2]));
428         }
429       }
430     }
431
432     if (myCurrElem.size() < size_t(aNode))
433     {
434       myCurrElem.resize (aNode * 2, -1);
435     }
436     myCurrElem[aNode] = anIndex;
437     aNbElemNodes = aNode + 1;
438
439     if (*thePos == '\n'
440      || *thePos == '\0')
441     {
442       break;
443     }
444
445     if (*thePos != ' ')
446     {
447       ++thePos;
448     }
449   }
450
451   if (myCurrElem[0] < 0
452    || myCurrElem[1] < 0
453    || myCurrElem[2] < 0
454    || aNbElemNodes  < 3)
455   {
456     return;
457   }
458
459   if (aNbElemNodes == 3)
460   {
461     myMemEstim += sizeof(Graphic3d_Vec4i);
462     addElement (myCurrElem[0], myCurrElem[1], myCurrElem[2], -1);
463   }
464   else if (aNbElemNodes == 4)
465   {
466     myMemEstim += sizeof(Graphic3d_Vec4i);
467     addElement (myCurrElem[0], myCurrElem[1], myCurrElem[2], myCurrElem[3]);
468   }
469   else
470   {
471     const NCollection_Array1<Standard_Integer> aCurrElemArray1 (myCurrElem[0], 1, aNbElemNodes);
472     const Standard_Integer aNbAdded = triangulatePolygon (aCurrElemArray1);
473     if (aNbAdded < 1)
474     {
475       return;
476     }
477     ++myNbElemsBig;
478     myMemEstim += sizeof(Graphic3d_Vec4i) * aNbAdded;
479   }
480 }
481
482 //================================================================
483 // Function : triangulatePolygonFan
484 // Purpose  :
485 //================================================================
486 Standard_Integer RWObj_Reader::triangulatePolygonFan (const NCollection_Array1<Standard_Integer>& theIndices)
487 {
488   const Standard_Integer aNbElemNodes = theIndices.Size();
489   for (Standard_Integer aNodeIter = 0; aNodeIter < aNbElemNodes - 2; ++aNodeIter)
490   {
491     Graphic3d_Vec4i aTriNodes (-1, -1, -1, -1);
492     for (Standard_Integer aNodeInSubTriIter = 0; aNodeInSubTriIter < 3; ++aNodeInSubTriIter)
493     {
494       const Standard_Integer aCurrNodeIndex = (aNodeInSubTriIter == 0) ? 0 : (aNodeIter + aNodeInSubTriIter);
495       aTriNodes[aNodeInSubTriIter] = theIndices.Value (theIndices.Lower() + aCurrNodeIndex);
496     }
497     addElement (aTriNodes[0], aTriNodes[1], aTriNodes[2], -1);
498   }
499   return aNbElemNodes - 2;
500 }
501
502 //================================================================
503 // Function : polygonCenter
504 // Purpose  :
505 //================================================================
506 gp_XYZ RWObj_Reader::polygonCenter (const NCollection_Array1<Standard_Integer>& theIndices)
507 {
508   if (theIndices.Size() < 3)
509   {
510     return gp_XYZ (0.0, 0.0, 0.0);
511   }
512   else if (theIndices.Size() == 4)
513   {
514     gp_XYZ aCenter = getNode (theIndices.Value (theIndices.Lower() + 0)).XYZ()
515                    + getNode (theIndices.Value (theIndices.Lower() + 2)).XYZ();
516     aCenter /= 2.0;
517     return aCenter;
518   }
519
520   gp_XYZ aCenter (0, 0, 0);
521   for (NCollection_Array1<Standard_Integer>::Iterator aPntIter (theIndices); aPntIter.More(); aPntIter.Next())
522   {
523     aCenter += getNode (aPntIter.Value()).XYZ();
524   }
525
526   aCenter /= (Standard_Real )theIndices.Size();
527   return aCenter;
528 }
529
530 //================================================================
531 // Function : polygonNormal
532 // Purpose  :
533 //================================================================
534 gp_XYZ RWObj_Reader::polygonNormal (const NCollection_Array1<Standard_Integer>& theIndices)
535 {
536   const gp_XYZ aCenter = polygonCenter (theIndices);
537   gp_XYZ aMaxDir = getNode (theIndices.First()).XYZ() - aCenter;
538   gp_XYZ aNormal = (getNode (theIndices.Last()).XYZ() - aCenter).Crossed (aMaxDir);
539   for (int aPntIter = theIndices.Lower(); aPntIter < theIndices.Upper(); ++aPntIter)
540   {
541     const gp_XYZ aTmpDir2 = getNode (theIndices.Value (aPntIter + 1)).XYZ() - aCenter;
542     if (aTmpDir2.SquareModulus() > aMaxDir.SquareModulus())
543     {
544       aMaxDir = aTmpDir2;
545     }
546
547     const gp_XYZ aTmpDir1 = getNode (theIndices.Value (aPntIter)).XYZ() - aCenter;
548     gp_XYZ aDelta = aTmpDir1.Crossed (aTmpDir2);
549     if (aNormal.Dot (aDelta) < 0.0)
550     {
551       aDelta *= -1.0;
552     }
553     aNormal += aDelta;
554   }
555
556   const Standard_Real aMod = aNormal.Modulus();
557   if (aMod > gp::Resolution())
558   {
559     aNormal /= aMod;
560   }
561   return aNormal;
562 }
563
564 //================================================================
565 // Function : triangulatePolygon
566 // Purpose  :
567 //================================================================
568 Standard_Integer RWObj_Reader::triangulatePolygon (const NCollection_Array1<Standard_Integer>& theIndices)
569 {
570   const Standard_Integer aNbElemNodes = theIndices.Size();
571   if (aNbElemNodes < 3)
572   {
573     return 0;
574   }
575
576   const gp_XYZ aPolygonNorm = polygonNormal (theIndices);
577
578   // map polygon onto plane
579   gp_XYZ aXDir;
580   {
581     const double aAbsXYZ[] = { Abs(aPolygonNorm.X()), Abs(aPolygonNorm.Y()), Abs(aPolygonNorm.Z()) };
582     Standard_Integer aMinI = (aAbsXYZ[0] < aAbsXYZ[1]) ? 0 : 1;
583     aMinI = (aAbsXYZ[aMinI] < aAbsXYZ[2]) ? aMinI : 2;
584     const Standard_Integer aI1 = (aMinI + 1) % 3 + 1;
585     const Standard_Integer aI2 = (aMinI + 2) % 3 + 1;
586     aXDir.ChangeCoord (aMinI + 1) = 0;
587     aXDir.ChangeCoord (aI1) =  aPolygonNorm.Coord (aI2);
588     aXDir.ChangeCoord (aI2) = -aPolygonNorm.Coord (aI1);
589   }
590   const gp_XYZ aYDir = aPolygonNorm ^ aXDir;
591
592   Handle(NCollection_IncAllocator) anAllocator = new NCollection_IncAllocator();
593   Handle(BRepMesh_DataStructureOfDelaun) aMeshStructure = new BRepMesh_DataStructureOfDelaun (anAllocator);
594   IMeshData::VectorOfInteger anIndexes (aNbElemNodes, anAllocator);
595   for (Standard_Integer aNodeIter = 0; aNodeIter < aNbElemNodes; ++aNodeIter)
596   {
597     const Standard_Integer aNodeIndex = theIndices.Value (theIndices.Lower() + aNodeIter);
598     const gp_XYZ aPnt3d = getNode (aNodeIndex).XYZ();
599     gp_XY aPnt2d (aXDir * aPnt3d, aYDir * aPnt3d);
600     BRepMesh_Vertex aVertex (aPnt2d, aNodeIndex, BRepMesh_Frontier);
601     anIndexes.Append (aMeshStructure->AddNode (aVertex));
602   }
603
604   const bool isClockwiseOrdered = isClockwisePolygon (aMeshStructure, anIndexes);
605   for (Standard_Integer aIdx = anIndexes.Lower(); aIdx <= anIndexes.Upper(); ++aIdx)
606   {
607     const Standard_Integer aPtIdx     = isClockwiseOrdered ? aIdx : (aIdx + 1) % anIndexes.Length();
608     const Standard_Integer aNextPtIdx = isClockwiseOrdered ? (aIdx + 1) % anIndexes.Length() : aIdx;
609     BRepMesh_Edge anEdge (anIndexes.Value (aPtIdx),
610                           anIndexes.Value (aNextPtIdx),
611                           BRepMesh_Frontier);
612     aMeshStructure->AddLink (anEdge);
613   }
614
615   try
616   {
617     BRepMesh_Delaun aTriangulation (aMeshStructure, anIndexes);
618     const IMeshData::MapOfInteger& aTriangles = aMeshStructure->ElementsOfDomain();
619     if (aTriangles.Extent() < 1)
620     {
621       return triangulatePolygonFan (theIndices);
622     }
623
624     Standard_Integer aNbTrisAdded = 0;
625     for (IMeshData::MapOfInteger::Iterator aTriIter (aTriangles); aTriIter.More(); aTriIter.Next())
626     {
627       const Standard_Integer aTriangleId = aTriIter.Key();
628       const BRepMesh_Triangle& aTriangle = aMeshStructure->GetElement (aTriangleId);
629       if (aTriangle.Movability() == BRepMesh_Deleted)
630       {
631         continue;
632       }
633
634       int aTri2d[3];
635       aMeshStructure->ElementNodes (aTriangle, aTri2d);
636       if (!isClockwiseOrdered)
637       {
638         std::swap (aTri2d[1], aTri2d[2]);
639       }
640       const BRepMesh_Vertex& aVertex1 = aMeshStructure->GetNode (aTri2d[0]);
641       const BRepMesh_Vertex& aVertex2 = aMeshStructure->GetNode (aTri2d[1]);
642       const BRepMesh_Vertex& aVertex3 = aMeshStructure->GetNode (aTri2d[2]);
643       addElement (aVertex1.Location3d(), aVertex2.Location3d(), aVertex3.Location3d(), -1);
644       ++aNbTrisAdded;
645     }
646     return aNbTrisAdded;
647   }
648   catch (Standard_Failure const& theFailure)
649   {
650     Message::SendWarning (TCollection_AsciiString ("Error: exception raised during polygon split\n[") + theFailure.GetMessageString() + "]");
651   }
652   return triangulatePolygonFan (theIndices);
653 }
654
655 // =======================================================================
656 // function : pushObject
657 // purpose  :
658 // =======================================================================
659 void RWObj_Reader::pushObject (const char* theObjectName)
660 {
661   TCollection_AsciiString aNewObject;
662   if (!RWObj_Tools::ReadName (theObjectName, aNewObject))
663   {
664     // empty group name is OK
665   }
666   if (addMesh (myActiveSubMesh, RWObj_SubMeshReason_NewObject))
667   {
668     myPackedIndices.Clear(); // vertices might be duplicated after this point...
669   }
670   myActiveSubMesh.Object = aNewObject;
671 }
672
673 // =======================================================================
674 // function : pushGroup
675 // purpose  :
676 // =======================================================================
677 void RWObj_Reader::pushGroup (const char* theGroupName)
678 {
679   TCollection_AsciiString aNewGroup;
680   if (!RWObj_Tools::ReadName (theGroupName, aNewGroup))
681   {
682     // empty group name is OK
683   }
684   if (addMesh (myActiveSubMesh, RWObj_SubMeshReason_NewGroup))
685   {
686     myPackedIndices.Clear(); // vertices might be duplicated after this point...
687   }
688   myActiveSubMesh.Group = aNewGroup;
689 }
690
691 // =======================================================================
692 // function : pushSmoothGroup
693 // purpose  :
694 // =======================================================================
695 void RWObj_Reader::pushSmoothGroup (const char* theSmoothGroupIndex)
696 {
697   TCollection_AsciiString aNewSmoothGroup;
698   RWObj_Tools::ReadName (theSmoothGroupIndex, aNewSmoothGroup);
699   if (aNewSmoothGroup == "off"
700    || aNewSmoothGroup == "0")
701   {
702     aNewSmoothGroup.Clear();
703   }
704   if (myActiveSubMesh.SmoothGroup.IsEqual (aNewSmoothGroup))
705   {
706     // Ignore duplicated statements to workaround some weird OBJ files.
707     // Note that smooth groups are handled in different manner than groups and objects,
708     // which always flushed even with equal names.
709     return;
710   }
711
712   if (addMesh (myActiveSubMesh, RWObj_SubMeshReason_NewSmoothGroup))
713   {
714     myPackedIndices.Clear(); // vertices might be duplicated after this point...
715   }
716   myActiveSubMesh.SmoothGroup = aNewSmoothGroup;
717 }
718
719 // =======================================================================
720 // function : pushMaterial
721 // purpose  :
722 // =======================================================================
723 void RWObj_Reader::pushMaterial (const char* theMaterialName)
724 {
725   TCollection_AsciiString aNewMat;
726   if (!RWObj_Tools::ReadName (theMaterialName, aNewMat))
727   {
728     // empty material name is allowed by specs
729   }
730   else if (!myMaterials.IsBound (aNewMat))
731   {
732     Message::SendWarning (TCollection_AsciiString("Warning: use of undefined OBJ material at line ") + myNbLines);
733     return;
734   }
735   if (myActiveSubMesh.Material.IsEqual (aNewMat))
736   {
737     return; // ignore
738   }
739
740   // implicitly create a new group to split materials
741   if (addMesh (myActiveSubMesh, RWObj_SubMeshReason_NewMaterial))
742   {
743     myPackedIndices.Clear(); // vertices might be duplicated after this point...
744   }
745   myActiveSubMesh.Material = aNewMat;
746 }
747
748 // =======================================================================
749 // function : readMaterialLib
750 // purpose  :
751 // =======================================================================
752 void RWObj_Reader::readMaterialLib (const char* theFileName)
753 {
754   TCollection_AsciiString aMatPath;
755   if (!RWObj_Tools::ReadName (theFileName, aMatPath))
756   {
757     Message::SendWarning (TCollection_AsciiString("Warning: invalid OBJ syntax at line ") + myNbLines);
758     return;
759   }
760
761   RWObj_MtlReader aMatReader (myMaterials);
762   if (aMatReader.Read (myFolder, aMatPath))
763   {
764     myExternalFiles.Add (myFolder + aMatPath);
765   }
766 }
767
768 // =======================================================================
769 // function : checkMemory
770 // purpose  :
771 // =======================================================================
772 bool RWObj_Reader::checkMemory()
773 {
774   if (myMemEstim < myMemLimitBytes
775    || myToAbort)
776   {
777     return true;
778   }
779
780   Message::SendFail (TCollection_AsciiString("Error: OBJ file content does not fit into ")
781                    + Standard_Integer(myMemLimitBytes / (1024 * 1024)) + " MiB limit."
782                    + "\nMesh data will be truncated.");
783   myToAbort = true;
784   return false;
785 }