4151c94d |
1 | // Author: Kirill Gavrilov |
2 | // Copyright (c) 2015-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 | #ifndef _RWObj_Reader_HeaderFile |
16 | #define _RWObj_Reader_HeaderFile |
17 | |
4151c94d |
18 | #include <Message.hxx> |
19 | #include <Message_Messenger.hxx> |
7e785937 |
20 | #include <Message_ProgressRange.hxx> |
4151c94d |
21 | #include <NCollection_Array1.hxx> |
22 | #include <NCollection_DataMap.hxx> |
23 | #include <NCollection_IndexedMap.hxx> |
24 | #include <NCollection_Vector.hxx> |
25 | #include <NCollection_Shared.hxx> |
0435edfe |
26 | #include <OSD_OpenFile.hxx> |
4151c94d |
27 | #include <RWMesh_CoordinateSystemConverter.hxx> |
28 | #include <RWObj_Material.hxx> |
29 | #include <RWObj_SubMesh.hxx> |
30 | #include <RWObj_SubMeshReason.hxx> |
31 | #include <RWObj_Tools.hxx> |
1103eb60 |
32 | #include <Standard_HashUtils.hxx> |
4151c94d |
33 | |
34 | #include <vector> |
35 | |
36 | //! An abstract class implementing procedure to read OBJ file. |
37 | //! |
38 | //! This class is not bound to particular data structure |
39 | //! and can be used to read the file directly into arbitrary data model. |
40 | //! To use it, create descendant class and implement interface methods. |
41 | //! |
42 | //! Call method Read() to read the file. |
43 | class RWObj_Reader : public Standard_Transient |
44 | { |
45 | DEFINE_STANDARD_RTTIEXT(RWObj_Reader, Standard_Transient) |
46 | public: |
47 | |
48 | //! Empty constructor. |
49 | Standard_EXPORT RWObj_Reader(); |
50 | |
0435edfe |
51 | //! Open stream and pass it to Read method |
52 | //! Returns true if success, false on error. |
53 | Standard_Boolean Read (const TCollection_AsciiString& theFile, |
54 | const Message_ProgressRange& theProgress) |
55 | { |
56 | std::ifstream aStream; |
57 | OSD_OpenStream(aStream, theFile, std::ios_base::in | std::ios_base::binary); |
58 | return Read(aStream, theFile, theProgress); |
59 | } |
60 | |
4151c94d |
61 | //! Reads data from OBJ file. |
62 | //! Unicode paths can be given in UTF-8 encoding. |
63 | //! Returns true if success, false on error or user break. |
0435edfe |
64 | Standard_Boolean Read (std::istream& theStream, |
65 | const TCollection_AsciiString& theFile, |
7e785937 |
66 | const Message_ProgressRange& theProgress) |
4151c94d |
67 | { |
0435edfe |
68 | return read(theStream, theFile, theProgress, Standard_False); |
69 | } |
70 | |
71 | //! Open stream and pass it to Probe method. |
72 | //! @param theFile path to the file |
73 | //! @param theProgress progress indicator |
74 | //! @return TRUE if success, FALSE on error or user break. |
75 | //! @sa FileComments(), ExternalFiles(), NbProbeNodes(), NbProbeElems(). |
76 | Standard_Boolean Probe (const TCollection_AsciiString& theFile, |
77 | const Message_ProgressRange& theProgress) |
78 | { |
79 | std::ifstream aStream; |
80 | OSD_OpenStream(aStream, theFile, std::ios_base::in | std::ios_base::binary); |
81 | return Probe(aStream, theFile, theProgress); |
4151c94d |
82 | } |
83 | |
84 | //! Probe data from OBJ file (comments, external references) without actually reading mesh data. |
85 | //! Although mesh data will not be collected, the full file content will be parsed, due to OBJ format limitations. |
0435edfe |
86 | //! @param theStream input stream |
4151c94d |
87 | //! @param theFile path to the file |
88 | //! @param theProgress progress indicator |
89 | //! @return TRUE if success, FALSE on error or user break. |
90 | //! @sa FileComments(), ExternalFiles(), NbProbeNodes(), NbProbeElems(). |
0435edfe |
91 | Standard_Boolean Probe (std::istream& theStream, |
92 | const TCollection_AsciiString& theFile, |
7e785937 |
93 | const Message_ProgressRange& theProgress) |
4151c94d |
94 | { |
0435edfe |
95 | return read(theStream, theFile, theProgress, Standard_True); |
4151c94d |
96 | } |
97 | |
98 | //! Returns file comments (lines starting with # at the beginning of file). |
99 | const TCollection_AsciiString& FileComments() const { return myFileComments; } |
100 | |
101 | //! Return the list of external file references. |
102 | const NCollection_IndexedMap<TCollection_AsciiString>& ExternalFiles() const { return myExternalFiles; } |
103 | |
104 | //! Number of probed nodes. |
105 | Standard_Integer NbProbeNodes() const { return myNbProbeNodes; } |
106 | |
107 | //!< number of probed polygon elements (of unknown size). |
108 | Standard_Integer NbProbeElems() const { return myNbProbeElems; } |
109 | |
110 | //! Returns memory limit in bytes; -1 (no limit) by default. |
111 | Standard_Size MemoryLimit() const { return myMemLimitBytes; } |
112 | |
113 | //! Specify memory limit in bytes, so that import will be aborted |
114 | //! by specified limit before memory allocation error occurs. |
115 | void SetMemoryLimit (Standard_Size theMemLimit) { myMemLimitBytes = theMemLimit; } |
116 | |
117 | //! Return transformation from one coordinate system to another; no transformation by default. |
118 | const RWMesh_CoordinateSystemConverter& Transformation() const { return myCSTrsf; } |
119 | |
120 | //! Setup transformation from one coordinate system to another. |
121 | //! OBJ file might be exported following various coordinate system conventions, |
122 | //! so that it might be useful automatically transform data during file reading. |
123 | void SetTransformation (const RWMesh_CoordinateSystemConverter& theCSConverter) { myCSTrsf = theCSConverter; } |
124 | |
125 | //! Return single precision flag for reading vertex data (coordinates); FALSE by default. |
126 | Standard_Boolean IsSinglePrecision() const { return myObjVerts.IsSinglePrecision(); } |
127 | |
128 | //! Setup single/double precision flag for reading vertex data (coordinates). |
129 | void SetSinglePrecision (Standard_Boolean theIsSinglePrecision) { myObjVerts.SetSinglePrecision (theIsSinglePrecision); } |
130 | |
131 | protected: |
132 | |
133 | //! Reads data from OBJ file. |
134 | //! Unicode paths can be given in UTF-8 encoding. |
135 | //! Returns true if success, false on error or user break. |
0435edfe |
136 | Standard_EXPORT Standard_Boolean read (std::istream& theStream, |
137 | const TCollection_AsciiString& theFile, |
7e785937 |
138 | const Message_ProgressRange& theProgress, |
4151c94d |
139 | const Standard_Boolean theToProbe); |
140 | |
141 | //! @name interface methods which should be implemented by sub-class |
142 | protected: |
143 | |
144 | //! Add new sub-mesh. |
145 | //! Basically, this method will be called multiple times for the same group with different reason, |
146 | //! so that implementation should decide if previously allocated sub-mesh should be used or new one to be allocated. |
147 | //! Sub-mesh command can be skipped if previous sub-mesh is empty, |
148 | //! or if the reason is out of interest for particular reader |
149 | //! (e.g. if materials are ignored, reader may ignore RWObj_SubMeshReason_NewMaterial reason). |
150 | //! @param theMesh mesh definition |
151 | //! @param theReason reason to create new sub-mesh |
152 | //! @return TRUE if new sub-mesh should be started since this point |
153 | virtual Standard_Boolean addMesh (const RWObj_SubMesh& theMesh, |
154 | const RWObj_SubMeshReason theReason) = 0; |
155 | |
156 | //! Retrieve sub-mesh node position, added by addNode(). |
157 | virtual gp_Pnt getNode (Standard_Integer theIndex) const = 0; |
158 | |
159 | //! Callback function to be implemented in descendant. |
160 | //! Should create new node with specified coordinates in the target model, and return its ID as integer. |
161 | virtual Standard_Integer addNode (const gp_Pnt& thePnt) = 0; |
162 | |
163 | //! Callback function to be implemented in descendant. |
164 | //! Should set normal coordinates for specified node. |
165 | //! @param theIndex node ID as returned by addNode() |
166 | //! @param theNorm normal vector |
167 | virtual void setNodeNormal (const Standard_Integer theIndex, |
168 | const Graphic3d_Vec3& theNorm) = 0; |
169 | |
170 | //! Callback function to be implemented in descendant. |
171 | //! Should set texture coordinates for specified node. |
172 | //! @param theIndex node ID as returned by addNode() |
173 | //! @param theUV UV texture coordinates |
174 | virtual void setNodeUV (const Standard_Integer theIndex, |
175 | const Graphic3d_Vec2& theUV) = 0; |
176 | |
177 | //! Callback function to be implemented in descendant. |
178 | //! Should create new element (triangle or quad if 4th index is != -1) built on specified nodes in the target model. |
179 | virtual void addElement (Standard_Integer theN1, |
180 | Standard_Integer theN2, |
181 | Standard_Integer theN3, |
182 | Standard_Integer theN4) = 0; |
183 | |
184 | //! @name implementation details |
185 | private: |
186 | |
187 | //! Handle "v X Y Z". |
188 | void pushVertex (const char* theXYZ) |
189 | { |
190 | char* aNext = NULL; |
191 | gp_Pnt anXYZ; |
192 | RWObj_Tools::ReadVec3 (theXYZ, aNext, anXYZ.ChangeCoord()); |
193 | myCSTrsf.TransformPosition (anXYZ.ChangeCoord()); |
194 | |
195 | myMemEstim += myObjVerts.IsSinglePrecision() ? sizeof(Graphic3d_Vec3) : sizeof(gp_Pnt); |
196 | myObjVerts.Append (anXYZ); |
197 | } |
198 | |
199 | //! Handle "vn NX NY NZ". |
200 | void pushNormal (const char* theXYZ) |
201 | { |
202 | char* aNext = NULL; |
203 | Graphic3d_Vec3 aNorm; |
204 | RWObj_Tools::ReadVec3 (theXYZ, aNext, aNorm); |
205 | myCSTrsf.TransformNormal (aNorm); |
206 | |
207 | myMemEstim += sizeof(Graphic3d_Vec3); |
208 | myObjNorms.Append (aNorm); |
209 | } |
210 | |
211 | //! Handle "vt U V". |
212 | void pushTexel (const char* theUV) |
213 | { |
214 | char* aNext = NULL; |
215 | Graphic3d_Vec2 anUV; |
216 | anUV.x() = (float )Strtod (theUV, &aNext); |
217 | theUV = aNext; |
218 | anUV.y() = (float )Strtod (theUV, &aNext); |
219 | |
220 | myMemEstim += sizeof(Graphic3d_Vec2); |
221 | myObjVertsUV.Append (anUV); |
222 | } |
223 | |
224 | //! Handle "f indices". |
225 | void pushIndices (const char* thePos); |
226 | |
227 | //! Compute the center of planar polygon. |
228 | //! @param theIndices polygon indices |
229 | //! @return center of polygon |
230 | gp_XYZ polygonCenter (const NCollection_Array1<Standard_Integer>& theIndices); |
231 | |
232 | //! Compute the normal to planar polygon. |
233 | //! The logic is similar to ShapeAnalysis_Curve::IsPlanar(). |
234 | //! @param theIndices polygon indices |
235 | //! @return polygon normal |
236 | gp_XYZ polygonNormal (const NCollection_Array1<Standard_Integer>& theIndices); |
237 | |
238 | //! Create triangle fan from specified polygon. |
239 | //! @param theIndices polygon nodes |
240 | //! @return number of added triangles |
241 | Standard_Integer triangulatePolygonFan (const NCollection_Array1<Standard_Integer>& theIndices); |
242 | |
243 | //! Triangulate specified polygon. |
244 | //! @param theIndices polygon nodes |
245 | //! @return number of added triangles |
246 | Standard_Integer triangulatePolygon (const NCollection_Array1<Standard_Integer>& theIndices); |
247 | |
248 | //! Handle "o ObjectName". |
249 | void pushObject (const char* theObjectName); |
250 | |
251 | //! Handle "g GroupName". |
252 | void pushGroup (const char* theGroupName); |
253 | |
254 | //! Handle "s SmoothGroupIndex". |
255 | void pushSmoothGroup (const char* theSmoothGroupIndex); |
256 | |
257 | //! Handle "usemtl MaterialName". |
258 | void pushMaterial (const char* theMaterialName); |
259 | |
260 | //! Handle "mtllib FileName". |
261 | void readMaterialLib (const char* theFileName); |
262 | |
263 | //! Check memory limits. |
264 | //! @return FALSE on out of memory |
265 | bool checkMemory(); |
266 | |
267 | protected: |
268 | |
269 | //! Hasher for 3 ordered integers. |
270 | struct ObjVec3iHasher |
271 | { |
1103eb60 |
272 | std::size_t operator()(const Graphic3d_Vec3i& theKey) const noexcept |
4151c94d |
273 | { |
1103eb60 |
274 | return opencascade::hashBytes(&theKey[0], 3 * sizeof(int)); |
4151c94d |
275 | } |
276 | |
1103eb60 |
277 | bool operator()(const Graphic3d_Vec3i& theKey1, |
278 | const Graphic3d_Vec3i& theKey2) const noexcept |
4151c94d |
279 | { |
280 | return theKey1[0] == theKey2[0] |
281 | && theKey1[1] == theKey2[1] |
282 | && theKey1[2] == theKey2[2]; |
283 | } |
284 | }; |
285 | |
286 | //! Auxiliary structure holding vertex data either with single or double floating point precision. |
287 | class VectorOfVertices |
288 | { |
289 | public: |
290 | //! Empty constructor. |
291 | VectorOfVertices() : myIsSinglePrecision (Standard_False) {} |
292 | |
293 | //! Return single precision flag; FALSE by default. |
294 | bool IsSinglePrecision() const { return myIsSinglePrecision; } |
295 | |
296 | //! Setup single/double precision flag. |
297 | void SetSinglePrecision (Standard_Boolean theIsSinglePrecision) |
298 | { |
299 | myIsSinglePrecision = theIsSinglePrecision; |
300 | myPntVec.Nullify(); |
301 | myVec3Vec.Nullify(); |
302 | } |
303 | |
304 | //! Reset and (re)allocate buffer. |
305 | void Reset() |
306 | { |
307 | if (myIsSinglePrecision) |
308 | { |
309 | myVec3Vec = new NCollection_Shared<NCollection_Vector<Graphic3d_Vec3> >(); |
310 | } |
311 | else |
312 | { |
313 | myPntVec = new NCollection_Shared<NCollection_Vector<gp_Pnt> >(); |
314 | } |
315 | } |
316 | |
317 | //! Return vector lower index. |
318 | Standard_Integer Lower() const { return 0; } |
319 | |
320 | //! Return vector upper index. |
321 | Standard_Integer Upper() const { return myIsSinglePrecision ? myVec3Vec->Upper() : myPntVec->Upper(); } |
322 | |
323 | //! Return point with the given index. |
324 | gp_Pnt Value (Standard_Integer theIndex) const |
325 | { |
326 | if (myIsSinglePrecision) |
327 | { |
328 | const Graphic3d_Vec3& aPnt = myVec3Vec->Value (theIndex); |
329 | return gp_Pnt (aPnt.x(), aPnt.y(), aPnt.z()); |
330 | } |
331 | else |
332 | { |
333 | return myPntVec->Value (theIndex); |
334 | } |
335 | } |
336 | |
337 | //! Append new point. |
338 | void Append (const gp_Pnt& thePnt) |
339 | { |
340 | if (myIsSinglePrecision) |
341 | { |
342 | myVec3Vec->Append (Graphic3d_Vec3 ((float )thePnt.X(), (float )thePnt.Y(), (float )thePnt.Z())); |
343 | } |
344 | else |
345 | { |
346 | myPntVec->Append (thePnt); |
347 | } |
348 | } |
349 | private: |
350 | Handle(NCollection_Shared<NCollection_Vector<gp_Pnt> >) myPntVec; |
351 | Handle(NCollection_Shared<NCollection_Vector<Graphic3d_Vec3> >) myVec3Vec; |
352 | Standard_Boolean myIsSinglePrecision; |
353 | }; |
354 | |
355 | protected: |
356 | |
357 | NCollection_IndexedMap<TCollection_AsciiString> |
358 | myExternalFiles; //!< list of external file references |
359 | TCollection_AsciiString myFileComments; //!< file header comments |
360 | TCollection_AsciiString myFolder; //!< folder containing the OBJ file |
361 | RWMesh_CoordinateSystemConverter myCSTrsf; //!< coordinate system flipper |
362 | Standard_Size myMemLimitBytes; //!< memory limit in bytes |
363 | Standard_Size myMemEstim; //!< estimated memory occupation in bytes |
364 | Standard_Integer myNbLines; //!< number of parsed lines (e.g. current line) |
365 | Standard_Integer myNbProbeNodes; //!< number of probed nodes |
366 | Standard_Integer myNbProbeElems; //!< number of probed elements |
367 | Standard_Integer myNbElemsBig; //!< number of big elements (polygons with 5+ nodes) |
368 | Standard_Boolean myToAbort; //!< flag indicating abort state (e.g. syntax error) |
369 | |
370 | // Each node in the Element specifies independent indices of Vertex position, Texture coordinates and Normal. |
371 | // This scheme does not match natural definition of Primitive Array |
372 | // where each unique set of nodal properties defines Vertex |
373 | // (thus node at the same location but with different normal should be duplicated). |
374 | // The following code converts OBJ definition of nodal properties to Primitive Array definition. |
375 | VectorOfVertices myObjVerts; //!< temporary vector of vertices |
376 | NCollection_Vector<Graphic3d_Vec2> myObjVertsUV; //!< temporary vector of UV parameters |
377 | NCollection_Vector<Graphic3d_Vec3> myObjNorms; //!< temporary vector of normals |
378 | NCollection_DataMap<Graphic3d_Vec3i, Standard_Integer, ObjVec3iHasher> |
379 | myPackedIndices; |
380 | NCollection_DataMap<TCollection_AsciiString, RWObj_Material> |
381 | myMaterials; //!< map of known materials |
382 | |
383 | RWObj_SubMesh myActiveSubMesh; //!< active sub-mesh definition |
384 | std::vector<Standard_Integer> myCurrElem; //!< indices for the current element |
4151c94d |
385 | }; |
386 | |
387 | #endif // _RWObj_Reader_HeaderFile |