Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-09-13 09:17:39

0001 // Author: Kirill Gavrilov
0002 // Copyright (c) 2015-2019 OPEN CASCADE SAS
0003 //
0004 // This file is part of Open CASCADE Technology software library.
0005 //
0006 // This library is free software; you can redistribute it and/or modify it under
0007 // the terms of the GNU Lesser General Public License version 2.1 as published
0008 // by the Free Software Foundation, with special exception defined in the file
0009 // OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT
0010 // distribution for complete text of the license and disclaimer of any warranty.
0011 //
0012 // Alternatively, this file may be used under the terms of Open CASCADE
0013 // commercial license or contractual agreement.
0014 
0015 #ifndef _RWObj_Reader_HeaderFile
0016 #define _RWObj_Reader_HeaderFile
0017 
0018 #include <Message.hxx>
0019 #include <Message_Messenger.hxx>
0020 #include <Message_ProgressRange.hxx>
0021 #include <NCollection_Array1.hxx>
0022 #include <NCollection_DataMap.hxx>
0023 #include <NCollection_IndexedMap.hxx>
0024 #include <NCollection_DynamicArray.hxx>
0025 #include <NCollection_Shared.hxx>
0026 #include <OSD_OpenFile.hxx>
0027 #include <RWMesh_CoordinateSystemConverter.hxx>
0028 #include <RWObj_Material.hxx>
0029 #include <RWObj_SubMesh.hxx>
0030 #include <RWObj_SubMeshReason.hxx>
0031 #include <RWObj_Tools.hxx>
0032 #include <Standard_HashUtils.hxx>
0033 #include <NCollection_LinearVector.hxx>
0034 
0035 //! An abstract class implementing procedure to read OBJ file.
0036 //!
0037 //! This class is not bound to particular data structure
0038 //! and can be used to read the file directly into arbitrary data model.
0039 //! To use it, create descendant class and implement interface methods.
0040 //!
0041 //! Call method Read() to read the file.
0042 class RWObj_Reader : public Standard_Transient
0043 {
0044   DEFINE_STANDARD_RTTIEXT(RWObj_Reader, Standard_Transient)
0045 public:
0046   //! Empty constructor.
0047   Standard_EXPORT RWObj_Reader();
0048 
0049   //! Open stream and pass it to Read method
0050   //! Returns true if success, false on error.
0051   bool Read(const TCollection_AsciiString& theFile, const Message_ProgressRange& theProgress)
0052   {
0053     std::ifstream aStream;
0054     OSD_OpenStream(aStream, theFile, std::ios_base::in | std::ios_base::binary);
0055     return Read(aStream, theFile, theProgress);
0056   }
0057 
0058   //! Reads data from OBJ file.
0059   //! Unicode paths can be given in UTF-8 encoding.
0060   //! Returns true if success, false on error or user break.
0061   bool Read(std::istream&                  theStream,
0062             const TCollection_AsciiString& theFile,
0063             const Message_ProgressRange&   theProgress)
0064   {
0065     return read(theStream, theFile, theProgress, false);
0066   }
0067 
0068   //! Open stream and pass it to Probe method.
0069   //! @param theFile     path to the file
0070   //! @param theProgress progress indicator
0071   //! @return TRUE if success, FALSE on error or user break.
0072   //! @sa FileComments(), ExternalFiles(), NbProbeNodes(), NbProbeElems().
0073   bool Probe(const TCollection_AsciiString& theFile, const Message_ProgressRange& theProgress)
0074   {
0075     std::ifstream aStream;
0076     OSD_OpenStream(aStream, theFile, std::ios_base::in | std::ios_base::binary);
0077     return Probe(aStream, theFile, theProgress);
0078   }
0079 
0080   //! Probe data from OBJ file (comments, external references) without actually reading mesh data.
0081   //! Although mesh data will not be collected, the full file content will be parsed, due to OBJ
0082   //! format limitations.
0083   //! @param theStream   input stream
0084   //! @param theFile     path to the file
0085   //! @param theProgress progress indicator
0086   //! @return TRUE if success, FALSE on error or user break.
0087   //! @sa FileComments(), ExternalFiles(), NbProbeNodes(), NbProbeElems().
0088   bool Probe(std::istream&                  theStream,
0089              const TCollection_AsciiString& theFile,
0090              const Message_ProgressRange&   theProgress)
0091   {
0092     return read(theStream, theFile, theProgress, true);
0093   }
0094 
0095   //! Returns file comments (lines starting with # at the beginning of file).
0096   const TCollection_AsciiString& FileComments() const { return myFileComments; }
0097 
0098   //! Return the list of external file references.
0099   const NCollection_IndexedMap<TCollection_AsciiString>& ExternalFiles() const
0100   {
0101     return myExternalFiles;
0102   }
0103 
0104   //! Number of probed nodes.
0105   int NbProbeNodes() const { return myNbProbeNodes; }
0106 
0107   //!< number of probed polygon elements (of unknown size).
0108   int NbProbeElems() const { return myNbProbeElems; }
0109 
0110   //! Returns memory limit in bytes; -1 (no limit) by default.
0111   size_t MemoryLimit() const { return myMemLimitBytes; }
0112 
0113   //! Specify memory limit in bytes, so that import will be aborted
0114   //! by specified limit before memory allocation error occurs.
0115   void SetMemoryLimit(size_t theMemLimit) { myMemLimitBytes = theMemLimit; }
0116 
0117   //! Return transformation from one coordinate system to another; no transformation by default.
0118   const RWMesh_CoordinateSystemConverter& Transformation() const { return myCSTrsf; }
0119 
0120   //! Setup transformation from one coordinate system to another.
0121   //! OBJ file might be exported following various coordinate system conventions,
0122   //! so that it might be useful automatically transform data during file reading.
0123   void SetTransformation(const RWMesh_CoordinateSystemConverter& theCSConverter)
0124   {
0125     myCSTrsf = theCSConverter;
0126   }
0127 
0128   //! Return single precision flag for reading vertex data (coordinates); FALSE by default.
0129   bool IsSinglePrecision() const { return myObjVerts.IsSinglePrecision(); }
0130 
0131   //! Setup single/double precision flag for reading vertex data (coordinates).
0132   void SetSinglePrecision(bool theIsSinglePrecision)
0133   {
0134     myObjVerts.SetSinglePrecision(theIsSinglePrecision);
0135   }
0136 
0137 protected:
0138   //! Reads data from OBJ file.
0139   //! Unicode paths can be given in UTF-8 encoding.
0140   //! Returns true if success, false on error or user break.
0141   Standard_EXPORT bool read(std::istream&                  theStream,
0142                             const TCollection_AsciiString& theFile,
0143                             const Message_ProgressRange&   theProgress,
0144                             const bool                     theToProbe);
0145 
0146   //! @name interface methods which should be implemented by sub-class
0147 protected:
0148   //! Add new sub-mesh.
0149   //! Basically, this method will be called multiple times for the same group with different reason,
0150   //! so that implementation should decide if previously allocated sub-mesh should be used or new
0151   //! one to be allocated. Sub-mesh command can be skipped if previous sub-mesh is empty, or if the
0152   //! reason is out of interest for particular reader (e.g. if materials are ignored, reader may
0153   //! ignore RWObj_SubMeshReason_NewMaterial reason).
0154   //! @param theMesh   mesh definition
0155   //! @param theReason reason to create new sub-mesh
0156   //! @return TRUE if new sub-mesh should be started since this point
0157   virtual bool addMesh(const RWObj_SubMesh& theMesh, const RWObj_SubMeshReason theReason) = 0;
0158 
0159   //! Retrieve sub-mesh node position, added by addNode().
0160   virtual gp_Pnt getNode(int theIndex) const = 0;
0161 
0162   //! Callback function to be implemented in descendant.
0163   //! Should create new node with specified coordinates in the target model, and return its ID as
0164   //! integer.
0165   virtual int addNode(const gp_Pnt& thePnt) = 0;
0166 
0167   //! Callback function to be implemented in descendant.
0168   //! Should set normal coordinates for specified node.
0169   //! @param theIndex node ID as returned by addNode()
0170   //! @param theNorm  normal vector
0171   virtual void setNodeNormal(const int theIndex, const NCollection_Vec3<float>& theNorm) = 0;
0172 
0173   //! Callback function to be implemented in descendant.
0174   //! Should set texture coordinates for specified node.
0175   //! @param theIndex node ID as returned by addNode()
0176   //! @param theUV    UV texture coordinates
0177   virtual void setNodeUV(const int theIndex, const NCollection_Vec2<float>& theUV) = 0;
0178 
0179   //! Callback function to be implemented in descendant.
0180   //! Should create new element (triangle or quad if 4th index is != -1) built on specified nodes in
0181   //! the target model.
0182   virtual void addElement(int theN1, int theN2, int theN3, int theN4) = 0;
0183 
0184   //! @name implementation details
0185 private:
0186   //! Handle "v X Y Z".
0187   void pushVertex(const char* theXYZ)
0188   {
0189     char*  aNext = nullptr;
0190     gp_Pnt anXYZ;
0191     RWObj_Tools::ReadVec3(theXYZ, aNext, anXYZ.ChangeCoord());
0192     myCSTrsf.TransformPosition(anXYZ.ChangeCoord());
0193 
0194     myMemEstim += myObjVerts.IsSinglePrecision() ? sizeof(NCollection_Vec3<float>) : sizeof(gp_Pnt);
0195     myObjVerts.Append(anXYZ);
0196   }
0197 
0198   //! Handle "vn NX NY NZ".
0199   void pushNormal(const char* theXYZ)
0200   {
0201     char*                   aNext = nullptr;
0202     NCollection_Vec3<float> aNorm;
0203     RWObj_Tools::ReadVec3(theXYZ, aNext, aNorm);
0204     myCSTrsf.TransformNormal(aNorm);
0205 
0206     myMemEstim += sizeof(NCollection_Vec3<float>);
0207     myObjNorms.Append(aNorm);
0208   }
0209 
0210   //! Handle "vt U V".
0211   void pushTexel(const char* theUV)
0212   {
0213     char*                   aNext = nullptr;
0214     NCollection_Vec2<float> anUV;
0215     anUV.x() = (float)Strtod(theUV, &aNext);
0216     theUV    = aNext;
0217     anUV.y() = (float)Strtod(theUV, &aNext);
0218 
0219     myMemEstim += sizeof(NCollection_Vec2<float>);
0220     myObjVertsUV.Append(anUV);
0221   }
0222 
0223   //! Handle "f indices".
0224   void pushIndices(const char* thePos);
0225 
0226   //! Compute the center of planar polygon.
0227   //! @param theIndices polygon indices
0228   //! @return center of polygon
0229   gp_XYZ polygonCenter(const NCollection_Array1<int>& theIndices);
0230 
0231   //! Compute the normal to planar polygon.
0232   //! The logic is similar to ShapeAnalysis_Curve::IsPlanar().
0233   //! @param theIndices polygon indices
0234   //! @return polygon normal
0235   gp_XYZ polygonNormal(const NCollection_Array1<int>& theIndices);
0236 
0237   //! Create triangle fan from specified polygon.
0238   //! @param theIndices polygon nodes
0239   //! @return number of added triangles
0240   int triangulatePolygonFan(const NCollection_Array1<int>& theIndices);
0241 
0242   //! Triangulate specified polygon.
0243   //! @param theIndices polygon nodes
0244   //! @return number of added triangles
0245   int triangulatePolygon(const NCollection_Array1<int>& theIndices);
0246 
0247   //! Handle "o ObjectName".
0248   void pushObject(const char* theObjectName);
0249 
0250   //! Handle "g GroupName".
0251   void pushGroup(const char* theGroupName);
0252 
0253   //! Handle "s SmoothGroupIndex".
0254   void pushSmoothGroup(const char* theSmoothGroupIndex);
0255 
0256   //! Handle "usemtl MaterialName".
0257   void pushMaterial(const char* theMaterialName);
0258 
0259   //! Handle "mtllib FileName".
0260   void readMaterialLib(const char* theFileName);
0261 
0262   //! Check memory limits.
0263   //! @return FALSE on out of memory
0264   bool checkMemory();
0265 
0266 protected:
0267   //! Hasher for 3 ordered integers.
0268   struct ObjVec3iHasher
0269   {
0270     std::size_t operator()(const NCollection_Vec3<int>& theKey) const noexcept
0271     {
0272       return opencascade::hashBytes(&theKey[0], 3 * sizeof(int));
0273     }
0274 
0275     bool operator()(const NCollection_Vec3<int>& theKey1,
0276                     const NCollection_Vec3<int>& theKey2) const noexcept
0277     {
0278       return theKey1[0] == theKey2[0] && theKey1[1] == theKey2[1] && theKey1[2] == theKey2[2];
0279     }
0280   };
0281 
0282   //! Auxiliary structure holding vertex data either with single or double floating point precision.
0283   class VectorOfVertices
0284   {
0285   public:
0286     //! Empty constructor.
0287     VectorOfVertices()
0288         : myIsSinglePrecision(false)
0289     {
0290     }
0291 
0292     //! Return single precision flag; FALSE by default.
0293     bool IsSinglePrecision() const { return myIsSinglePrecision; }
0294 
0295     //! Setup single/double precision flag.
0296     void SetSinglePrecision(bool theIsSinglePrecision)
0297     {
0298       myIsSinglePrecision = theIsSinglePrecision;
0299       myPntVec.Nullify();
0300       myVec3Vec.Nullify();
0301     }
0302 
0303     //! Reset and (re)allocate buffer.
0304     void Reset()
0305     {
0306       if (myIsSinglePrecision)
0307       {
0308         myVec3Vec = new NCollection_Shared<NCollection_DynamicArray<NCollection_Vec3<float>>>();
0309       }
0310       else
0311       {
0312         myPntVec = new NCollection_Shared<NCollection_DynamicArray<gp_Pnt>>();
0313       }
0314     }
0315 
0316     //! Return vector lower index.
0317     int Lower() const { return 0; }
0318 
0319     //! Return vector upper index.
0320     int Upper() const { return myIsSinglePrecision ? myVec3Vec->Upper() : myPntVec->Upper(); }
0321 
0322     //! Return point with the given index.
0323     gp_Pnt Value(int theIndex) const
0324     {
0325       if (myIsSinglePrecision)
0326       {
0327         const NCollection_Vec3<float>& aPnt = myVec3Vec->Value(theIndex);
0328         return gp_Pnt(aPnt.x(), aPnt.y(), aPnt.z());
0329       }
0330       else
0331       {
0332         return myPntVec->Value(theIndex);
0333       }
0334     }
0335 
0336     //! Append new point.
0337     void Append(const gp_Pnt& thePnt)
0338     {
0339       if (myIsSinglePrecision)
0340       {
0341         myVec3Vec->Append(
0342           NCollection_Vec3<float>((float)thePnt.X(), (float)thePnt.Y(), (float)thePnt.Z()));
0343       }
0344       else
0345       {
0346         myPntVec->Append(thePnt);
0347       }
0348     }
0349 
0350   private:
0351     Handle(NCollection_Shared<NCollection_DynamicArray<gp_Pnt>>)                  myPntVec;
0352     Handle(NCollection_Shared<NCollection_DynamicArray<NCollection_Vec3<float>>>) myVec3Vec;
0353     bool myIsSinglePrecision;
0354   };
0355 
0356 protected:
0357   NCollection_IndexedMap<TCollection_AsciiString>
0358                                    myExternalFiles; //!< list of external file references
0359   TCollection_AsciiString          myFileComments;  //!< file header comments
0360   TCollection_AsciiString          myFolder;        //!< folder containing the OBJ file
0361   RWMesh_CoordinateSystemConverter myCSTrsf;        //!< coordinate system flipper
0362   size_t                           myMemLimitBytes; //!< memory limit in bytes
0363   size_t                           myMemEstim;      //!< estimated memory occupation in bytes
0364                                                     // clang-format off
0365   int                   myNbLines;       //!< number of parsed lines (e.g. current line)
0366   int                   myNbProbeNodes;  //!< number of probed nodes
0367   int                   myNbProbeElems;  //!< number of probed elements
0368   int                   myNbElemsBig;    //!< number of big elements (polygons with 5+ nodes)
0369   bool                   myToAbort;       //!< flag indicating abort state (e.g. syntax error)
0370                                                     // clang-format on
0371 
0372   // Each node in the Element specifies independent indices of Vertex position, Texture coordinates
0373   // and Normal. This scheme does not match natural definition of Primitive Array where each unique
0374   // set of nodal properties defines Vertex (thus node at the same location but with different
0375   // normal should be duplicated). The following code converts OBJ definition of nodal properties to
0376   // Primitive Array definition.
0377   VectorOfVertices myObjVerts; //!< temporary vector of vertices
0378   NCollection_DynamicArray<NCollection_Vec2<float>>
0379     myObjVertsUV; //!< temporary vector of UV parameters
0380   NCollection_DynamicArray<NCollection_Vec3<float>> myObjNorms; //!< temporary vector of normals
0381   NCollection_DataMap<NCollection_Vec3<int>, int, ObjVec3iHasher> myPackedIndices;
0382   NCollection_DataMap<TCollection_AsciiString, RWObj_Material>
0383     myMaterials; //!< map of known materials
0384 
0385   RWObj_SubMesh                 myActiveSubMesh; //!< active sub-mesh definition
0386   NCollection_LinearVector<int> myCurrElem;      //!< indices for the current element
0387 };
0388 
0389 #endif // _RWObj_Reader_HeaderFile