0029296: Data Exchange - implement import of mesh data from files in OBJ format
[occt.git] / src / RWObj / RWObj_MtlReader.cxx
CommitLineData
4151c94d 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
25namespace
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// =======================================================================
75RWObj_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// =======================================================================
87RWObj_MtlReader::~RWObj_MtlReader()
88{
89 if (myFile != NULL)
90 {
91 ::fclose (myFile);
92 }
93}
94
95// =======================================================================
96// function : Read
97// purpose :
98// =======================================================================
99bool 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// =======================================================================
299void 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// =======================================================================
326bool 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// =======================================================================
341bool 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}