0030964: Data Exchange - use Standard_ReadLineBuffer within OBJ reader
[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_ProgressSentry.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 Handle(Message_ProgressIndicator)& 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::DefaultMessenger()->Send (TCollection_AsciiString ("Error: file '") + theFile + "' is not found!", Message_Fail);
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::DefaultMessenger()->Send (TCollection_AsciiString ("Error: file '") + theFile + "' is empty!", Message_Fail);
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_ProgressSentry aPSentry (theProgress, "Reading text OBJ file", 0, aNbMiBTotal, 1);
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 (!aPSentry.More())
185       {
186         return false;
187       }
188
189       const Standard_Integer aNbMiBRead = Standard_Integer(aPosition / (1024 * 1024));
190       for (; aNbMiBPassed < aNbMiBRead; ++aNbMiBPassed) { aPSentry.Next(); }
191       aTimer.Reset();
192       aTimer.Start();
193     }
194
195     if (*aLine == '#')
196     {
197       if (isStart)
198       {
199         TCollection_AsciiString aComment (aLine + 1);
200         aComment.LeftAdjust();
201         aComment.RightAdjust();
202         if (!aComment.IsEmpty())
203         {
204           if (!myFileComments.IsEmpty())
205           {
206             myFileComments += "\n";
207           }
208           myFileComments += aComment;
209         }
210       }
211       continue;
212     }
213     else if (*aLine == '\n'
214           || *aLine == '\0')
215     {
216
217       continue;
218     }
219     isStart = false;
220
221     if (theToProbe)
222     {
223       if (::memcmp (aLine, "mtllib", 6) == 0)
224       {
225         readMaterialLib (aLine + 7);
226       }
227       else if (aLine[0] == 'v' && RWObj_Tools::isSpaceChar (aLine[1]))
228       {
229         ++myNbProbeNodes;
230       }
231       else if (aLine[0] == 'f' && RWObj_Tools::isSpaceChar (aLine[1]))
232       {
233         ++myNbProbeElems;
234       }
235       continue;
236     }
237
238     if (aLine[0] == 'v' && RWObj_Tools::isSpaceChar (aLine[1]))
239     {
240       ++myNbProbeNodes;
241       pushVertex (aLine + 2);
242     }
243     else if (aLine[0] == 'v'
244           && aLine[1] == 'n'
245           && RWObj_Tools::isSpaceChar (aLine[2]))
246     {
247       pushNormal (aLine + 3);
248     }
249     else if (aLine[0] == 'v'
250           && aLine[1] == 't'
251           && RWObj_Tools::isSpaceChar (aLine[2]))
252     {
253       pushTexel (aLine + 3);
254     }
255     else if (aLine[0] == 'f' && RWObj_Tools::isSpaceChar (aLine[1]))
256     {
257       ++myNbProbeElems;
258       pushIndices (aLine + 2);
259     }
260     else if (aLine[0] == 'g' && IsSpace (aLine[1]))
261     {
262       pushGroup (aLine + 2);
263     }
264     else if (aLine[0] == 's' && IsSpace (aLine[1]))
265     {
266       pushSmoothGroup (aLine + 2);
267     }
268     else if (aLine[0] == 'o' && IsSpace (aLine[1]))
269     {
270       pushObject (aLine + 2);
271     }
272     else if (::memcmp (aLine, "mtllib", 6) == 0)
273     {
274       readMaterialLib (aLine + 7);
275     }
276     else if (::memcmp (aLine, "usemtl", 6) == 0)
277     {
278       pushMaterial (aLine + 7);
279     }
280
281     if (!checkMemory())
282     {
283       addMesh (myActiveSubMesh, RWObj_SubMeshReason_NewObject);
284       return false;
285     }
286   }
287
288   // collect external references
289   for (NCollection_DataMap<TCollection_AsciiString, RWObj_Material>::Iterator aMatIter (myMaterials); aMatIter.More(); aMatIter.Next())
290   {
291     const RWObj_Material& aMat = aMatIter.Value();
292     if (!aMat.DiffuseTexture.IsEmpty())
293     {
294       myExternalFiles.Add (aMat.DiffuseTexture);
295     }
296     if (!aMat.SpecularTexture.IsEmpty())
297     {
298       myExternalFiles.Add (aMat.SpecularTexture);
299     }
300     if (!aMat.BumpTexture.IsEmpty())
301     {
302       myExternalFiles.Add (aMat.BumpTexture);
303     }
304   }
305
306   // flush the last group
307   if (!theToProbe)
308   {
309     addMesh (myActiveSubMesh, RWObj_SubMeshReason_NewObject);
310   }
311   if (myNbElemsBig != 0)
312   {
313     Message::DefaultMessenger()->Send (TCollection_AsciiString("Warning: OBJ reader, ") + myNbElemsBig
314                                        + " polygon(s) have been split into triangles.", Message_Warning);
315   }
316
317   for (; aNbMiBPassed < aNbMiBTotal; ++aNbMiBPassed) { aPSentry.Next(); }
318   return true;
319 }
320
321 // =======================================================================
322 // function : pushIndices
323 // purpose  :
324 // =======================================================================
325 void RWObj_Reader::pushIndices (const char* thePos)
326 {
327   char* aNext = NULL;
328
329   Standard_Integer aNbElemNodes = 0;
330   for (Standard_Integer aNode = 0;; ++aNode)
331   {
332     Graphic3d_Vec3i a3Indices (-1, -1, -1);
333     a3Indices[0] = strtol (thePos, &aNext, 10) - 1;
334     if (aNext == thePos)
335     {
336       break;
337     }
338
339     // parse UV index
340     thePos = aNext;
341     if (*thePos == '/')
342     {
343       ++thePos;
344       a3Indices[1] = strtol (thePos, &aNext, 10) - 1;
345       thePos = aNext;
346
347       // parse Normal index
348       if (*thePos == '/')
349       {
350         ++thePos;
351         a3Indices[2] = strtol (thePos, &aNext, 10) - 1;
352         thePos = aNext;
353       }
354     }
355
356     // handle negative indices
357     if (a3Indices[0] < -1)
358     {
359       a3Indices[0] += myObjVerts.Upper() + 2;
360     }
361     if (a3Indices[1] < -1)
362     {
363       a3Indices[1] += myObjVertsUV.Upper() + 2;
364     }
365     if (a3Indices[2] < -1)
366     {
367       a3Indices[2] += myObjNorms.Upper() + 2;
368     }
369
370     Standard_Integer anIndex = -1;
371     if (!myPackedIndices.Find (a3Indices, anIndex))
372     {
373       if (a3Indices[0] >= 0)
374       {
375         myMemEstim += sizeof(Graphic3d_Vec3);
376       }
377       if (a3Indices[1] >= 0)
378       {
379         myMemEstim += sizeof(Graphic3d_Vec2);
380       }
381       if (a3Indices[2] >= 0)
382       {
383         myMemEstim += sizeof(Graphic3d_Vec3);
384       }
385       myMemEstim += sizeof(Graphic3d_Vec4i) + sizeof(Standard_Integer); // naive map
386       if (a3Indices[0] < myObjVerts.Lower() || a3Indices[0] > myObjVerts.Upper())
387       {
388         myToAbort = true;
389         Message::DefaultMessenger()->Send (TCollection_AsciiString("Error: invalid OBJ syntax at line ") + myNbLines
390                                            + ": vertex index is out of range.", Message_Fail);
391         return;
392       }
393
394       anIndex = addNode (myObjVerts.Value (a3Indices[0]));
395       myPackedIndices.Bind (a3Indices, anIndex);
396       if (a3Indices[1] >= 0)
397       {
398         if (myObjVertsUV.IsEmpty())
399         {
400           Message::DefaultMessenger()->Send (TCollection_AsciiString("Warning: invalid OBJ syntax at line ") + myNbLines
401                                              + ": UV index is specified but no UV nodes are defined.", Message_Warning);
402         }
403         else if (a3Indices[1] < myObjVertsUV.Lower() || a3Indices[1] > myObjVertsUV.Upper())
404         {
405           Message::DefaultMessenger()->Send (TCollection_AsciiString("Warning: invalid OBJ syntax at line ") + myNbLines
406                                              + ": UV index is out of range.", Message_Warning);
407           setNodeUV (anIndex,Graphic3d_Vec2 (0.0f, 0.0f));
408         }
409         else
410         {
411           setNodeUV (anIndex, myObjVertsUV.Value (a3Indices[1]));
412         }
413       }
414       if (a3Indices[2] >= 0)
415       {
416         if (myObjNorms.IsEmpty())
417         {
418           Message::DefaultMessenger()->Send (TCollection_AsciiString("Warning: invalid OBJ syntax at line ") + myNbLines
419                                              + ": Normal index is specified but no Normals nodes are defined.", Message_Warning);
420         }
421         else if (a3Indices[2] < myObjNorms.Lower() || a3Indices[2] > myObjNorms.Upper())
422         {
423           Message::DefaultMessenger()->Send (TCollection_AsciiString("Warning: invalid OBJ syntax at line ") + myNbLines
424                                              + ": Normal index is out of range.", Message_Warning);
425           setNodeNormal (anIndex, Graphic3d_Vec3 (0.0f, 0.0f, 1.0f));
426         }
427         else
428         {
429           setNodeNormal (anIndex, myObjNorms.Value (a3Indices[2]));
430         }
431       }
432     }
433
434     if (myCurrElem.size() < size_t(aNode))
435     {
436       myCurrElem.resize (aNode * 2, -1);
437     }
438     myCurrElem[aNode] = anIndex;
439     aNbElemNodes = aNode + 1;
440
441     if (*thePos == '\n'
442      || *thePos == '\0')
443     {
444       break;
445     }
446
447     if (*thePos != ' ')
448     {
449       ++thePos;
450     }
451   }
452
453   if (myCurrElem[0] < 0
454    || myCurrElem[1] < 0
455    || myCurrElem[2] < 0
456    || aNbElemNodes  < 3)
457   {
458     return;
459   }
460
461   if (aNbElemNodes == 3)
462   {
463     myMemEstim += sizeof(Graphic3d_Vec4i);
464     addElement (myCurrElem[0], myCurrElem[1], myCurrElem[2], -1);
465   }
466   else if (aNbElemNodes == 4)
467   {
468     myMemEstim += sizeof(Graphic3d_Vec4i);
469     addElement (myCurrElem[0], myCurrElem[1], myCurrElem[2], myCurrElem[3]);
470   }
471   else
472   {
473     const NCollection_Array1<Standard_Integer> aCurrElemArray1 (myCurrElem[0], 1, aNbElemNodes);
474     const Standard_Integer aNbAdded = triangulatePolygon (aCurrElemArray1);
475     if (aNbAdded < 1)
476     {
477       return;
478     }
479     ++myNbElemsBig;
480     myMemEstim += sizeof(Graphic3d_Vec4i) * aNbAdded;
481   }
482 }
483
484 //================================================================
485 // Function : triangulatePolygonFan
486 // Purpose  :
487 //================================================================
488 Standard_Integer RWObj_Reader::triangulatePolygonFan (const NCollection_Array1<Standard_Integer>& theIndices)
489 {
490   const Standard_Integer aNbElemNodes = theIndices.Size();
491   for (Standard_Integer aNodeIter = 0; aNodeIter < aNbElemNodes - 2; ++aNodeIter)
492   {
493     Graphic3d_Vec4i aTriNodes (-1, -1, -1, -1);
494     for (Standard_Integer aNodeInSubTriIter = 0; aNodeInSubTriIter < 3; ++aNodeInSubTriIter)
495     {
496       const Standard_Integer aCurrNodeIndex = (aNodeInSubTriIter == 0) ? 0 : (aNodeIter + aNodeInSubTriIter);
497       aTriNodes[aNodeInSubTriIter] = theIndices.Value (theIndices.Lower() + aCurrNodeIndex);
498     }
499     addElement (aTriNodes[0], aTriNodes[1], aTriNodes[2], -1);
500   }
501   return aNbElemNodes - 2;
502 }
503
504 //================================================================
505 // Function : polygonCenter
506 // Purpose  :
507 //================================================================
508 gp_XYZ RWObj_Reader::polygonCenter (const NCollection_Array1<Standard_Integer>& theIndices)
509 {
510   if (theIndices.Size() < 3)
511   {
512     return gp_XYZ (0.0, 0.0, 0.0);
513   }
514   else if (theIndices.Size() == 4)
515   {
516     gp_XYZ aCenter = getNode (theIndices.Value (theIndices.Lower() + 0)).XYZ()
517                    + getNode (theIndices.Value (theIndices.Lower() + 2)).XYZ();
518     aCenter /= 2.0;
519     return aCenter;
520   }
521
522   gp_XYZ aCenter (0, 0, 0);
523   for (NCollection_Array1<Standard_Integer>::Iterator aPntIter (theIndices); aPntIter.More(); aPntIter.Next())
524   {
525     aCenter += getNode (aPntIter.Value()).XYZ();
526   }
527
528   aCenter /= (Standard_Real )theIndices.Size();
529   return aCenter;
530 }
531
532 //================================================================
533 // Function : polygonNormal
534 // Purpose  :
535 //================================================================
536 gp_XYZ RWObj_Reader::polygonNormal (const NCollection_Array1<Standard_Integer>& theIndices)
537 {
538   const gp_XYZ aCenter = polygonCenter (theIndices);
539   gp_XYZ aMaxDir = getNode (theIndices.First()).XYZ() - aCenter;
540   gp_XYZ aNormal = (getNode (theIndices.Last()).XYZ() - aCenter).Crossed (aMaxDir);
541   for (int aPntIter = theIndices.Lower(); aPntIter < theIndices.Upper(); ++aPntIter)
542   {
543     const gp_XYZ aTmpDir2 = getNode (theIndices.Value (aPntIter + 1)).XYZ() - aCenter;
544     if (aTmpDir2.SquareModulus() > aMaxDir.SquareModulus())
545     {
546       aMaxDir = aTmpDir2;
547     }
548
549     const gp_XYZ aTmpDir1 = getNode (theIndices.Value (aPntIter)).XYZ() - aCenter;
550     gp_XYZ aDelta = aTmpDir1.Crossed (aTmpDir2);
551     if (aNormal.Dot (aDelta) < 0.0)
552     {
553       aDelta *= -1.0;
554     }
555     aNormal += aDelta;
556   }
557
558   const Standard_Real aMod = aNormal.Modulus();
559   if (aMod > gp::Resolution())
560   {
561     aNormal /= aMod;
562   }
563   return aNormal;
564 }
565
566 //================================================================
567 // Function : triangulatePolygon
568 // Purpose  :
569 //================================================================
570 Standard_Integer RWObj_Reader::triangulatePolygon (const NCollection_Array1<Standard_Integer>& theIndices)
571 {
572   const Standard_Integer aNbElemNodes = theIndices.Size();
573   if (aNbElemNodes < 3)
574   {
575     return 0;
576   }
577
578   const gp_XYZ aPolygonNorm = polygonNormal (theIndices);
579
580   // map polygon onto plane
581   gp_XYZ aXDir;
582   {
583     const double aAbsXYZ[] = { Abs(aPolygonNorm.X()), Abs(aPolygonNorm.Y()), Abs(aPolygonNorm.Z()) };
584     Standard_Integer aMinI = (aAbsXYZ[0] < aAbsXYZ[1]) ? 0 : 1;
585     aMinI = (aAbsXYZ[aMinI] < aAbsXYZ[2]) ? aMinI : 2;
586     const Standard_Integer aI1 = (aMinI + 1) % 3 + 1;
587     const Standard_Integer aI2 = (aMinI + 2) % 3 + 1;
588     aXDir.ChangeCoord (aMinI + 1) = 0;
589     aXDir.ChangeCoord (aI1) =  aPolygonNorm.Coord (aI2);
590     aXDir.ChangeCoord (aI2) = -aPolygonNorm.Coord (aI1);
591   }
592   const gp_XYZ aYDir = aPolygonNorm ^ aXDir;
593
594   Handle(NCollection_IncAllocator) anAllocator = new NCollection_IncAllocator();
595   Handle(BRepMesh_DataStructureOfDelaun) aMeshStructure = new BRepMesh_DataStructureOfDelaun (anAllocator);
596   IMeshData::VectorOfInteger anIndexes (aNbElemNodes, anAllocator);
597   for (Standard_Integer aNodeIter = 0; aNodeIter < aNbElemNodes; ++aNodeIter)
598   {
599     const Standard_Integer aNodeIndex = theIndices.Value (theIndices.Lower() + aNodeIter);
600     const gp_XYZ aPnt3d = getNode (aNodeIndex).XYZ();
601     gp_XY aPnt2d (aXDir * aPnt3d, aYDir * aPnt3d);
602     BRepMesh_Vertex aVertex (aPnt2d, aNodeIndex, BRepMesh_Frontier);
603     anIndexes.Append (aMeshStructure->AddNode (aVertex));
604   }
605
606   const bool isClockwiseOrdered = isClockwisePolygon (aMeshStructure, anIndexes);
607   for (Standard_Integer aIdx = anIndexes.Lower(); aIdx <= anIndexes.Upper(); ++aIdx)
608   {
609     const Standard_Integer aPtIdx     = isClockwiseOrdered ? aIdx : (aIdx + 1) % anIndexes.Length();
610     const Standard_Integer aNextPtIdx = isClockwiseOrdered ? (aIdx + 1) % anIndexes.Length() : aIdx;
611     BRepMesh_Edge anEdge (anIndexes.Value (aPtIdx),
612                           anIndexes.Value (aNextPtIdx),
613                           BRepMesh_Frontier);
614     aMeshStructure->AddLink (anEdge);
615   }
616
617   try
618   {
619     BRepMesh_Delaun aTriangulation (aMeshStructure, anIndexes);
620     const IMeshData::MapOfInteger& aTriangles = aMeshStructure->ElementsOfDomain();
621     if (aTriangles.Extent() < 1)
622     {
623       return triangulatePolygonFan (theIndices);
624     }
625
626     Standard_Integer aNbTrisAdded = 0;
627     for (IMeshData::MapOfInteger::Iterator aTriIter (aTriangles); aTriIter.More(); aTriIter.Next())
628     {
629       const Standard_Integer aTriangleId = aTriIter.Key();
630       const BRepMesh_Triangle& aTriangle = aMeshStructure->GetElement (aTriangleId);
631       if (aTriangle.Movability() == BRepMesh_Deleted)
632       {
633         continue;
634       }
635
636       int aTri2d[3];
637       aMeshStructure->ElementNodes (aTriangle, aTri2d);
638       if (!isClockwiseOrdered)
639       {
640         std::swap (aTri2d[1], aTri2d[2]);
641       }
642       const BRepMesh_Vertex& aVertex1 = aMeshStructure->GetNode (aTri2d[0]);
643       const BRepMesh_Vertex& aVertex2 = aMeshStructure->GetNode (aTri2d[1]);
644       const BRepMesh_Vertex& aVertex3 = aMeshStructure->GetNode (aTri2d[2]);
645       addElement (aVertex1.Location3d(), aVertex2.Location3d(), aVertex3.Location3d(), -1);
646       ++aNbTrisAdded;
647     }
648     return aNbTrisAdded;
649   }
650   catch (Standard_Failure const& theFailure)
651   {
652     Message::DefaultMessenger()->Send (TCollection_AsciiString ("Error: exception raised during polygon split\n[")
653                                        + theFailure.GetMessageString() + "]", Message_Warning);
654   }
655   return triangulatePolygonFan (theIndices);
656 }
657
658 // =======================================================================
659 // function : pushObject
660 // purpose  :
661 // =======================================================================
662 void RWObj_Reader::pushObject (const char* theObjectName)
663 {
664   TCollection_AsciiString aNewObject;
665   if (!RWObj_Tools::ReadName (theObjectName, aNewObject))
666   {
667     // empty group name is OK
668   }
669   if (addMesh (myActiveSubMesh, RWObj_SubMeshReason_NewObject))
670   {
671     myPackedIndices.Clear(); // vertices might be duplicated after this point...
672   }
673   myActiveSubMesh.Object = aNewObject;
674 }
675
676 // =======================================================================
677 // function : pushGroup
678 // purpose  :
679 // =======================================================================
680 void RWObj_Reader::pushGroup (const char* theGroupName)
681 {
682   TCollection_AsciiString aNewGroup;
683   if (!RWObj_Tools::ReadName (theGroupName, aNewGroup))
684   {
685     // empty group name is OK
686   }
687   if (addMesh (myActiveSubMesh, RWObj_SubMeshReason_NewGroup))
688   {
689     myPackedIndices.Clear(); // vertices might be duplicated after this point...
690   }
691   myActiveSubMesh.Group = aNewGroup;
692 }
693
694 // =======================================================================
695 // function : pushSmoothGroup
696 // purpose  :
697 // =======================================================================
698 void RWObj_Reader::pushSmoothGroup (const char* theSmoothGroupIndex)
699 {
700   TCollection_AsciiString aNewSmoothGroup;
701   RWObj_Tools::ReadName (theSmoothGroupIndex, aNewSmoothGroup);
702   if (aNewSmoothGroup == "off"
703    || aNewSmoothGroup == "0")
704   {
705     aNewSmoothGroup.Clear();
706   }
707   if (addMesh (myActiveSubMesh, RWObj_SubMeshReason_NewSmoothGroup))
708   {
709     myPackedIndices.Clear(); // vertices might be duplicated after this point...
710   }
711   myActiveSubMesh.SmoothGroup = aNewSmoothGroup;
712 }
713
714 // =======================================================================
715 // function : pushMaterial
716 // purpose  :
717 // =======================================================================
718 void RWObj_Reader::pushMaterial (const char* theMaterialName)
719 {
720   TCollection_AsciiString aNewMat;
721   if (!RWObj_Tools::ReadName (theMaterialName, aNewMat))
722   {
723     // empty material name is allowed by specs
724   }
725   else if (!myMaterials.IsBound (aNewMat))
726   {
727     Message::DefaultMessenger()->Send (TCollection_AsciiString("Warning: use of undefined OBJ material at line ")
728                                        + myNbLines, Message_Warning);
729     return;
730   }
731   if (myActiveSubMesh.Material.IsEqual (aNewMat))
732   {
733     return; // ignore
734   }
735
736   // implicitly create a new group to split materials
737   if (addMesh (myActiveSubMesh, RWObj_SubMeshReason_NewMaterial))
738   {
739     myPackedIndices.Clear(); // vertices might be duplicated after this point...
740   }
741   myActiveSubMesh.Material = aNewMat;
742 }
743
744 // =======================================================================
745 // function : readMaterialLib
746 // purpose  :
747 // =======================================================================
748 void RWObj_Reader::readMaterialLib (const char* theFileName)
749 {
750   TCollection_AsciiString aMatPath;
751   if (!RWObj_Tools::ReadName (theFileName, aMatPath))
752   {
753     Message::DefaultMessenger()->Send (TCollection_AsciiString("Warning: invalid OBJ syntax at line ")
754                                        + myNbLines, Message_Warning);
755     return;
756   }
757
758   RWObj_MtlReader aMatReader (myMaterials);
759   if (aMatReader.Read (myFolder, aMatPath))
760   {
761     myExternalFiles.Add (myFolder + aMatPath);
762   }
763 }
764
765 // =======================================================================
766 // function : checkMemory
767 // purpose  :
768 // =======================================================================
769 bool RWObj_Reader::checkMemory()
770 {
771   if (myMemEstim < myMemLimitBytes
772    || myToAbort)
773   {
774     return true;
775   }
776
777   Message::DefaultMessenger()->Send (TCollection_AsciiString("Error: OBJ file content does not fit into ")
778                                      + Standard_Integer(myMemLimitBytes / (1024 * 1024)) + " MiB limit."
779                                    + "\nMesh data will be truncated.", Message_Fail);
780   myToAbort = true;
781   return false;
782 }