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> |
51ee6a7d |
31 | #include <Standard_ReadLineBuffer.hxx> |
4151c94d |
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 | { |
51ee6a7d |
48 | // The length of buffer to read (in bytes) |
49 | static const size_t THE_BUFFER_SIZE = 4 * 1024; |
4151c94d |
50 | |
51 | //! Simple wrapper. |
52 | struct RWObj_ReaderFile |
53 | { |
54 | FILE* File; |
4151c94d |
55 | int64_t FileLen; |
56 | |
57 | //! Constructor opening the file. |
51ee6a7d |
58 | RWObj_ReaderFile (const TCollection_AsciiString& theFile) |
4151c94d |
59 | : File (OSD_OpenFile (theFile.ToCString(), "rb")), |
4151c94d |
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 | } |
4151c94d |
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 | |
51ee6a7d |
159 | Standard_ReadLineBuffer aBuffer (THE_BUFFER_SIZE); |
160 | aBuffer.SetMultilineMode (true); |
161 | |
4151c94d |
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; |
51ee6a7d |
169 | int64_t aPosition = 0; |
170 | size_t aLineLen = 0; |
171 | int64_t aReadBytes = 0; |
172 | const char* aLine = NULL; |
173 | for (;;) |
4151c94d |
174 | { |
51ee6a7d |
175 | aLine = aBuffer.ReadLine (aFile.File, aLineLen, aReadBytes); |
176 | if (aLine == NULL) |
177 | { |
178 | break; |
179 | } |
4151c94d |
180 | ++myNbLines; |
51ee6a7d |
181 | aPosition += aReadBytes; |
4151c94d |
182 | if (aTimer.ElapsedTime() > 1.0) |
183 | { |
184 | if (!aPSentry.More()) |
185 | { |
186 | return false; |
187 | } |
188 | |
51ee6a7d |
189 | const Standard_Integer aNbMiBRead = Standard_Integer(aPosition / (1024 * 1024)); |
4151c94d |
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 | } |