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 | |
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(); |
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 | // ================================================================ |
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 | } |