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 | |
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 | } |
d9dd0754 |
66 | |
4151c94d |
67 | aPath = aFolder; |
d9dd0754 |
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 | } |
4151c94d |
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_RGB); |
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_RGB); |
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_RGB); |
d9dd0754 |
209 | hasAspect = true; |
4151c94d |
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); |
d9dd0754 |
222 | hasAspect = true; |
4151c94d |
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; |
d9dd0754 |
236 | hasAspect = true; |
4151c94d |
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); |
d9dd0754 |
250 | hasAspect = true; |
4151c94d |
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); |
d9dd0754 |
260 | hasAspect = true; |
4151c94d |
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); |
d9dd0754 |
270 | hasAspect = true; |
4151c94d |
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); |
d9dd0754 |
280 | hasAspect = true; |
4151c94d |
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 | } |