0030899: Data Exchange, RWObj_Reader - reading multiline file with more than one...
[occt.git] / src / RWObj / RWObj_Reader.cxx
CommitLineData
4151c94d 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
43IMPLEMENT_STANDARD_RTTIEXT(RWObj_Reader, Standard_Transient)
44
45namespace
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();
5e2477a7 113 if (LineBuffLen < aFullLen + MaxLineLen)
4151c94d 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// ================================================================
162RWObj_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// ================================================================
178Standard_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// =======================================================================
372void 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//================================================================
535Standard_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//================================================================
555gp_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//================================================================
583gp_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//================================================================
617Standard_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// =======================================================================
709void 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// =======================================================================
727void 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// =======================================================================
745void 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// =======================================================================
765void 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// =======================================================================
795void 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// =======================================================================
816bool 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}