0029296: Data Exchange - implement import of mesh data from files in OBJ format
[occt.git] / src / RWObj / RWObj_MtlReader.cxx
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_MtlReader.hxx>
16
17 #include <RWObj_Tools.hxx>
18
19 #include <Message.hxx>
20 #include <Message_Messenger.hxx>
21 #include <OSD_File.hxx>
22 #include <OSD_OpenFile.hxx>
23 #include <OSD_Path.hxx>
24
25 namespace
26 {
27   //! Try to find a new location of the file relative to specified folder from absolute path.
28   //! @param theAbsolutePath original absolute file path
29   //! @param theNewFoler     the new folder to look for the file
30   //! @param theRelativePath result file path relative to theNewFoler
31   //! @return true if relative file has been found
32   static bool findRelativePath (const TCollection_AsciiString& theAbsolutePath,
33                                 const TCollection_AsciiString& theNewFoler,
34                                 TCollection_AsciiString& theRelativePath)
35   {
36     TCollection_AsciiString aNewFoler = (theNewFoler.EndsWith ("\\") || theNewFoler.EndsWith ("/"))
37                                       ?  theNewFoler
38                                       : (theNewFoler + "/");
39
40     TCollection_AsciiString aRelPath;
41     TCollection_AsciiString aPath = theAbsolutePath;
42     for (;;)
43     {
44       TCollection_AsciiString aFolder, aFileName;
45       OSD_Path::FolderAndFileFromPath (aPath, aFolder, aFileName);
46       if (aFolder.IsEmpty()
47        || aFileName.IsEmpty())
48       {
49         return false;
50       }
51
52       if (aRelPath.IsEmpty())
53       {
54         aRelPath = aFileName;
55       }
56       else
57       {
58         aRelPath = aFileName + "/" + aRelPath;
59       }
60
61       if (OSD_File (aNewFoler + aRelPath).Exists())
62       {
63         theRelativePath = aRelPath;
64         return true;
65       }
66       aPath = aFolder;
67     }
68   }
69 }
70
71 // =======================================================================
72 // function : RWObj_MtlReader
73 // purpose  :
74 // =======================================================================
75 RWObj_MtlReader::RWObj_MtlReader (NCollection_DataMap<TCollection_AsciiString, RWObj_Material>& theMaterials)
76 : myFile (NULL),
77   myMaterials (&theMaterials),
78   myNbLines (0)
79 {
80   //
81 }
82
83 // =======================================================================
84 // function : ~RWObj_MtlReader
85 // purpose  :
86 // =======================================================================
87 RWObj_MtlReader::~RWObj_MtlReader()
88 {
89   if (myFile != NULL)
90   {
91     ::fclose (myFile);
92   }
93 }
94
95 // =======================================================================
96 // function : Read
97 // purpose  :
98 // =======================================================================
99 bool RWObj_MtlReader::Read (const TCollection_AsciiString& theFolder,
100                             const TCollection_AsciiString& theFile)
101 {
102   myPath = theFolder + theFile;
103   myFile = OSD_OpenFile (myPath.ToCString(), "rb");
104   if (myFile == NULL)
105   {
106     Message::DefaultMessenger()->Send (TCollection_AsciiString ("OBJ material file '") + myPath + "' is not found!", Message_Warning);
107     return Standard_False;
108   }
109
110   char aLine[256] = {};
111   TCollection_AsciiString aMatName;
112   RWObj_Material aMat;
113   const Standard_Integer aNbMatOld = myMaterials->Extent();
114   bool hasAspect = false;
115   for (; ::feof (myFile) == 0 && ::fgets (aLine, 255, myFile) != NULL; )
116   {
117     ++myNbLines;
118
119     const char* aPos = aLine;
120
121     // skip spaces
122     for (; IsSpace(*aPos);)
123     {
124       ++aPos;
125     }
126
127     if (*aPos == '#'
128      || *aPos == '\n'
129      || *aPos == '\0')
130     {
131       continue;
132     }
133
134     if (::memcmp (aPos, "newmtl", 6) == 0)
135     {
136       aPos += 7;
137       if (!aMatName.IsEmpty())
138       {
139         if (hasAspect)
140         {
141           aMat.Name = aMatName;
142         }
143         else
144         {
145           // reset incomplete material definition
146           aMat = RWObj_Material();
147         }
148         myMaterials->Bind (aMatName, aMat);
149         hasAspect = false;
150       }
151
152       aMatName = TCollection_AsciiString(aPos);
153       aMat = RWObj_Material();
154       if (!RWObj_Tools::ReadName (aPos, aMatName))
155       {
156         Message::DefaultMessenger()->Send (TCollection_AsciiString("Empty OBJ material at line ") + myNbLines + " in file " + myPath, Message_Warning);
157       }
158     }
159     else if (::memcmp (aPos, "Ka", 2) == 0
160           && IsSpace (aPos[2]))
161     {
162       aPos += 3;
163       char* aNext = NULL;
164       Graphic3d_Vec3 aColor;
165       RWObj_Tools::ReadVec3 (aPos, aNext, aColor);
166       aPos = aNext;
167       if (validateColor (aColor))
168       {
169         aMat.AmbientColor = Quantity_Color (aColor.r(), aColor.g(), aColor.b(), Quantity_TOC_RGB);
170         hasAspect = true;
171       }
172     }
173     else if (::memcmp (aPos, "Kd", 2) == 0
174           && IsSpace (aPos[2]))
175     {
176       aPos += 3;
177       char* aNext = NULL;
178       Graphic3d_Vec3 aColor;
179       RWObj_Tools::ReadVec3 (aPos, aNext, aColor);
180       aPos = aNext;
181       if (validateColor (aColor))
182       {
183         aMat.DiffuseColor = Quantity_Color (aColor.r(), aColor.g(), aColor.b(), Quantity_TOC_RGB);
184         hasAspect = true;
185       }
186     }
187     else if (::memcmp (aPos, "Ks", 2) == 0
188           && IsSpace (aPos[2]))
189     {
190       aPos += 3;
191       char* aNext = NULL;
192       Graphic3d_Vec3 aColor;
193       RWObj_Tools::ReadVec3 (aPos, aNext, aColor);
194       aPos = aNext;
195       if (validateColor (aColor))
196       {
197         aMat.SpecularColor = Quantity_Color (aColor.r(), aColor.g(), aColor.b(), Quantity_TOC_RGB);
198       }
199     }
200     else if (::memcmp (aPos, "Ns", 2) == 0
201           && IsSpace (aPos[2]))
202     {
203       aPos += 3;
204       char* aNext = NULL;
205       double aSpecular = Strtod (aPos, &aNext);
206       aPos = aNext;
207       if (aSpecular >= 0.0)
208       {
209         aMat.Shininess = (float )Min (aSpecular / 1000.0, 1.0);
210       }
211     }
212     else if (::memcmp (aPos, "Tr", 2) == 0
213           && IsSpace (aPos[2]))
214     {
215       aPos += 3;
216       char* aNext = NULL;
217       double aTransp = Strtod (aPos, &aNext);
218       aPos = aNext;
219       if (validateScalar (aTransp)
220        && aTransp <= 0.99)
221       {
222         aMat.Transparency = (float )aTransp;
223       }
224     }
225     else if (*aPos == 'd' && IsSpace (aPos[1]))
226     {
227       // dissolve
228       aPos += 2;
229       char* aNext = NULL;
230       double anAlpha = Strtod (aPos, &aNext);
231       aPos = aNext;
232       if (validateScalar (anAlpha)
233        && anAlpha >= 0.01)
234       {
235         aMat.Transparency = float(1.0 - anAlpha);
236       }
237     }
238     else if (::memcmp (aPos, "map_Kd", 6) == 0
239           && IsSpace (aPos[6]))
240     {
241       aPos += 7;
242       if (RWObj_Tools::ReadName (aPos, aMat.DiffuseTexture))
243       {
244         processTexturePath (aMat.DiffuseTexture, theFolder);
245       }
246     }
247     else if (::memcmp (aPos, "map_Ks", 6) == 0
248           && IsSpace (aPos[6]))
249     {
250       aPos += 7;
251       if (RWObj_Tools::ReadName (aPos, aMat.SpecularTexture))
252       {
253         processTexturePath (aMat.SpecularTexture, theFolder);
254       }
255     }
256     else if (::memcmp (aPos, "map_Bump", 8) == 0
257           && IsSpace (aPos[8]))
258     {
259       aPos += 9;
260       if (RWObj_Tools::ReadName (aPos, aMat.BumpTexture))
261       {
262         processTexturePath (aMat.BumpTexture, theFolder);
263       }
264     }
265     /*else if (::memcmp (aPos, "illum", 5) == 0)
266     {
267       aPos += 6;
268       char* aNext = NULL;
269       const int aModel = strtol (aPos, &aNext, 10);
270       aPos = aNext;
271       if (aModel < 0 || aModel > 10)
272       {
273         // unknown model
274       }
275     }*/
276   }
277
278   if (!aMatName.IsEmpty())
279   {
280     if (hasAspect)
281     {
282       aMat.Name = aMatName;
283     }
284     else
285     {
286       // reset incomplete material definition
287       aMat = RWObj_Material();
288     }
289     myMaterials->Bind (aMatName, aMat);
290   }
291
292   return myMaterials->Extent() != aNbMatOld;
293 }
294
295 // =======================================================================
296 // function : processTexturePath
297 // purpose  :
298 // =======================================================================
299 void RWObj_MtlReader::processTexturePath (TCollection_AsciiString& theTexturePath,
300                                           const TCollection_AsciiString& theFolder)
301 {
302   if (OSD_Path::IsAbsolutePath (theTexturePath.ToCString()))
303   {
304     Message::DefaultMessenger()->Send (TCollection_AsciiString("OBJ file specifies absolute path to the texture image file which may be inaccessible on another device\n")
305                                       + theTexturePath, Message_Warning);
306     if (!OSD_File (theTexturePath).Exists())
307     {
308       // workaround absolute filenames - try to find the same file at the OBJ file location
309       TCollection_AsciiString aRelativePath;
310       if (findRelativePath (theTexturePath, theFolder, aRelativePath))
311       {
312         theTexturePath = theFolder + aRelativePath;
313       }
314     }
315   }
316   else
317   {
318     theTexturePath = theFolder + theTexturePath;
319   }
320 }
321
322 // =======================================================================
323 // function : validateScalar
324 // purpose  :
325 // =======================================================================
326 bool RWObj_MtlReader::validateScalar (const Standard_Real theValue)
327 {
328   if (theValue < 0.0
329    || theValue > 1.0)
330   {
331     Message::DefaultMessenger()->Send (TCollection_AsciiString("Invalid scalar in OBJ material at line ") + myNbLines + " in file " + myPath, Message_Warning);
332     return false;
333   }
334   return true;
335 }
336
337 // =======================================================================
338 // function : validateColor
339 // purpose  :
340 // =======================================================================
341 bool RWObj_MtlReader::validateColor (const Graphic3d_Vec3& theVec)
342 {
343   if (theVec.r() < 0.0f || theVec.r() > 1.0f
344    || theVec.g() < 0.0f || theVec.g() > 1.0f
345    || theVec.b() < 0.0f || theVec.b() > 1.0f)
346   {
347     Message::DefaultMessenger()->Send (TCollection_AsciiString("Invalid color in OBJ material at line ") + myNbLines + " in file " + myPath, Message_Warning);
348     return false;
349   }
350   return true;
351 }