0029528: Visualization, TKOpenGl - allow defining sRGB textures
[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
67       aPath = aFolder;
68       for (; aPath.Length() >= 2;)
69       {
70         if (aPath.Value (aPath.Length()) == '/'
71          || aPath.Value (aPath.Length()) == '\\')
72         {
73           aPath = aPath.SubString (1, aPath.Length() - 1);
74           continue;
75         }
76         break;
77       }
78     }
79   }
80 }
81
82 // =======================================================================
83 // function : RWObj_MtlReader
84 // purpose  :
85 // =======================================================================
86 RWObj_MtlReader::RWObj_MtlReader (NCollection_DataMap<TCollection_AsciiString, RWObj_Material>& theMaterials)
87 : myFile (NULL),
88   myMaterials (&theMaterials),
89   myNbLines (0)
90 {
91   //
92 }
93
94 // =======================================================================
95 // function : ~RWObj_MtlReader
96 // purpose  :
97 // =======================================================================
98 RWObj_MtlReader::~RWObj_MtlReader()
99 {
100   if (myFile != NULL)
101   {
102     ::fclose (myFile);
103   }
104 }
105
106 // =======================================================================
107 // function : Read
108 // purpose  :
109 // =======================================================================
110 bool RWObj_MtlReader::Read (const TCollection_AsciiString& theFolder,
111                             const TCollection_AsciiString& theFile)
112 {
113   myPath = theFolder + theFile;
114   myFile = OSD_OpenFile (myPath.ToCString(), "rb");
115   if (myFile == NULL)
116   {
117     Message::DefaultMessenger()->Send (TCollection_AsciiString ("OBJ material file '") + myPath + "' is not found!", Message_Warning);
118     return Standard_False;
119   }
120
121   char aLine[256] = {};
122   TCollection_AsciiString aMatName;
123   RWObj_Material aMat;
124   const Standard_Integer aNbMatOld = myMaterials->Extent();
125   bool hasAspect = false;
126   for (; ::feof (myFile) == 0 && ::fgets (aLine, 255, myFile) != NULL; )
127   {
128     ++myNbLines;
129
130     const char* aPos = aLine;
131
132     // skip spaces
133     for (; IsSpace(*aPos);)
134     {
135       ++aPos;
136     }
137
138     if (*aPos == '#'
139      || *aPos == '\n'
140      || *aPos == '\0')
141     {
142       continue;
143     }
144
145     if (::memcmp (aPos, "newmtl", 6) == 0)
146     {
147       aPos += 7;
148       if (!aMatName.IsEmpty())
149       {
150         if (hasAspect)
151         {
152           aMat.Name = aMatName;
153         }
154         else
155         {
156           // reset incomplete material definition
157           aMat = RWObj_Material();
158         }
159         myMaterials->Bind (aMatName, aMat);
160         hasAspect = false;
161       }
162
163       aMatName = TCollection_AsciiString(aPos);
164       aMat = RWObj_Material();
165       if (!RWObj_Tools::ReadName (aPos, aMatName))
166       {
167         Message::DefaultMessenger()->Send (TCollection_AsciiString("Empty OBJ material at line ") + myNbLines + " in file " + myPath, Message_Warning);
168       }
169     }
170     else if (::memcmp (aPos, "Ka", 2) == 0
171           && IsSpace (aPos[2]))
172     {
173       aPos += 3;
174       char* aNext = NULL;
175       Graphic3d_Vec3 aColor;
176       RWObj_Tools::ReadVec3 (aPos, aNext, aColor);
177       aPos = aNext;
178       if (validateColor (aColor))
179       {
180         aMat.AmbientColor = Quantity_Color (aColor.r(), aColor.g(), aColor.b(), Quantity_TOC_sRGB);
181         hasAspect = true;
182       }
183     }
184     else if (::memcmp (aPos, "Kd", 2) == 0
185           && IsSpace (aPos[2]))
186     {
187       aPos += 3;
188       char* aNext = NULL;
189       Graphic3d_Vec3 aColor;
190       RWObj_Tools::ReadVec3 (aPos, aNext, aColor);
191       aPos = aNext;
192       if (validateColor (aColor))
193       {
194         aMat.DiffuseColor = Quantity_Color (aColor.r(), aColor.g(), aColor.b(), Quantity_TOC_sRGB);
195         hasAspect = true;
196       }
197     }
198     else if (::memcmp (aPos, "Ks", 2) == 0
199           && IsSpace (aPos[2]))
200     {
201       aPos += 3;
202       char* aNext = NULL;
203       Graphic3d_Vec3 aColor;
204       RWObj_Tools::ReadVec3 (aPos, aNext, aColor);
205       aPos = aNext;
206       if (validateColor (aColor))
207       {
208         aMat.SpecularColor = Quantity_Color (aColor.r(), aColor.g(), aColor.b(), Quantity_TOC_sRGB);
209         hasAspect = true;
210       }
211     }
212     else if (::memcmp (aPos, "Ns", 2) == 0
213           && IsSpace (aPos[2]))
214     {
215       aPos += 3;
216       char* aNext = NULL;
217       double aSpecular = Strtod (aPos, &aNext);
218       aPos = aNext;
219       if (aSpecular >= 0.0)
220       {
221         aMat.Shininess = (float )Min (aSpecular / 1000.0, 1.0);
222         hasAspect = true;
223       }
224     }
225     else if (::memcmp (aPos, "Tr", 2) == 0
226           && IsSpace (aPos[2]))
227     {
228       aPos += 3;
229       char* aNext = NULL;
230       double aTransp = Strtod (aPos, &aNext);
231       aPos = aNext;
232       if (validateScalar (aTransp)
233        && aTransp <= 0.99)
234       {
235         aMat.Transparency = (float )aTransp;
236         hasAspect = true;
237       }
238     }
239     else if (*aPos == 'd' && IsSpace (aPos[1]))
240     {
241       // dissolve
242       aPos += 2;
243       char* aNext = NULL;
244       double anAlpha = Strtod (aPos, &aNext);
245       aPos = aNext;
246       if (validateScalar (anAlpha)
247        && anAlpha >= 0.01)
248       {
249         aMat.Transparency = float(1.0 - anAlpha);
250         hasAspect = true;
251       }
252     }
253     else if (::memcmp (aPos, "map_Kd", 6) == 0
254           && IsSpace (aPos[6]))
255     {
256       aPos += 7;
257       if (RWObj_Tools::ReadName (aPos, aMat.DiffuseTexture))
258       {
259         processTexturePath (aMat.DiffuseTexture, theFolder);
260         hasAspect = true;
261       }
262     }
263     else if (::memcmp (aPos, "map_Ks", 6) == 0
264           && IsSpace (aPos[6]))
265     {
266       aPos += 7;
267       if (RWObj_Tools::ReadName (aPos, aMat.SpecularTexture))
268       {
269         processTexturePath (aMat.SpecularTexture, theFolder);
270         hasAspect = true;
271       }
272     }
273     else if (::memcmp (aPos, "map_Bump", 8) == 0
274           && IsSpace (aPos[8]))
275     {
276       aPos += 9;
277       if (RWObj_Tools::ReadName (aPos, aMat.BumpTexture))
278       {
279         processTexturePath (aMat.BumpTexture, theFolder);
280         hasAspect = true;
281       }
282     }
283     /*else if (::memcmp (aPos, "illum", 5) == 0)
284     {
285       aPos += 6;
286       char* aNext = NULL;
287       const int aModel = strtol (aPos, &aNext, 10);
288       aPos = aNext;
289       if (aModel < 0 || aModel > 10)
290       {
291         // unknown model
292       }
293     }*/
294   }
295
296   if (!aMatName.IsEmpty())
297   {
298     if (hasAspect)
299     {
300       aMat.Name = aMatName;
301     }
302     else
303     {
304       // reset incomplete material definition
305       aMat = RWObj_Material();
306     }
307     myMaterials->Bind (aMatName, aMat);
308   }
309
310   return myMaterials->Extent() != aNbMatOld;
311 }
312
313 // =======================================================================
314 // function : processTexturePath
315 // purpose  :
316 // =======================================================================
317 void RWObj_MtlReader::processTexturePath (TCollection_AsciiString& theTexturePath,
318                                           const TCollection_AsciiString& theFolder)
319 {
320   if (OSD_Path::IsAbsolutePath (theTexturePath.ToCString()))
321   {
322     Message::DefaultMessenger()->Send (TCollection_AsciiString("OBJ file specifies absolute path to the texture image file which may be inaccessible on another device\n")
323                                       + theTexturePath, Message_Warning);
324     if (!OSD_File (theTexturePath).Exists())
325     {
326       // workaround absolute filenames - try to find the same file at the OBJ file location
327       TCollection_AsciiString aRelativePath;
328       if (findRelativePath (theTexturePath, theFolder, aRelativePath))
329       {
330         theTexturePath = theFolder + aRelativePath;
331       }
332     }
333   }
334   else
335   {
336     theTexturePath = theFolder + theTexturePath;
337   }
338 }
339
340 // =======================================================================
341 // function : validateScalar
342 // purpose  :
343 // =======================================================================
344 bool RWObj_MtlReader::validateScalar (const Standard_Real theValue)
345 {
346   if (theValue < 0.0
347    || theValue > 1.0)
348   {
349     Message::DefaultMessenger()->Send (TCollection_AsciiString("Invalid scalar in OBJ material at line ") + myNbLines + " in file " + myPath, Message_Warning);
350     return false;
351   }
352   return true;
353 }
354
355 // =======================================================================
356 // function : validateColor
357 // purpose  :
358 // =======================================================================
359 bool RWObj_MtlReader::validateColor (const Graphic3d_Vec3& theVec)
360 {
361   if (theVec.r() < 0.0f || theVec.r() > 1.0f
362    || theVec.g() < 0.0f || theVec.g() > 1.0f
363    || theVec.b() < 0.0f || theVec.b() > 1.0f)
364   {
365     Message::DefaultMessenger()->Send (TCollection_AsciiString("Invalid color in OBJ material at line ") + myNbLines + " in file " + myPath, Message_Warning);
366     return false;
367   }
368   return true;
369 }