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