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