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.
RWObj_ReaderFile__anona7c4e3f20111::RWObj_ReaderFile58 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.
~RWObj_ReaderFile__anona7c4e3f20111::RWObj_ReaderFile72 ~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.
isClockwisePolygon(const Handle (BRepMesh_DataStructureOfDelaun)& theMesh,const IMeshData::VectorOfInteger & theIndexes)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 // ================================================================
RWObj_Reader()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 // ================================================================
read(const TCollection_AsciiString & theFile,const Message_ProgressRange & theProgress,const Standard_Boolean theToProbe)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 // =======================================================================
pushIndices(const char * thePos)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 //================================================================
triangulatePolygonFan(const NCollection_Array1<Standard_Integer> & theIndices)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 //================================================================
polygonCenter(const NCollection_Array1<Standard_Integer> & theIndices)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 //================================================================
polygonNormal(const NCollection_Array1<Standard_Integer> & theIndices)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 //================================================================
triangulatePolygon(const NCollection_Array1<Standard_Integer> & theIndices)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 // =======================================================================
pushObject(const char * theObjectName)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 // =======================================================================
pushGroup(const char * theGroupName)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 // =======================================================================
pushSmoothGroup(const char * theSmoothGroupIndex)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 // =======================================================================
pushMaterial(const char * theMaterialName)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 // =======================================================================
readMaterialLib(const char * theFileName)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 // =======================================================================
checkMemory()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 }
786