1 /*=========================================================================
2 Program: Visualization Toolkit
3 Module: vtkOBJImporterInternals.cxx
4 Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
5 All rights reserved.
6 See Copyright.txt or http://www.kitware.com/Copyright.htm for details.
7
8 This software is distributed WITHOUT ANY WARRANTY; without even
9 the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
10 PURPOSE. See the above copyright notice for more information.
11 =========================================================================*/
12
13 #include "vtkOBJImporterInternals.h"
14 #include "vtkBMPReader.h"
15 #include "vtkJPEGReader.h"
16 #include "vtkOBJImporter.h"
17 #include "vtkPNGReader.h"
18 #include "vtkPolyDataMapper.h"
19 #include "vtkProperty.h"
20 #include "vtkRenderWindow.h"
21 #include "vtkRenderer.h"
22 #include "vtkSmartPointer.h"
23 #include "vtkTIFFReader.h"
24 #include "vtkTexture.h"
25 #include "vtkTransform.h"
26 #include "vtksys/FStream.hxx"
27 #include "vtksys/SystemTools.hxx"
28
29 #include <cstdio>
30 #include <cstdlib>
31 #include <cstring>
32 #include <iostream>
33 #include <map>
34
35 #if defined(_WIN32)
36 #pragma warning(disable : 4800)
37 #endif
38
39 namespace
40 {
41 int localVerbosity = 0;
42 }
43
obj_set_material_defaults(vtkOBJImportedMaterial * mtl)44 void obj_set_material_defaults(vtkOBJImportedMaterial* mtl)
45 {
46 mtl->amb[0] = 0.0;
47 mtl->amb[1] = 0.0;
48 mtl->amb[2] = 0.0;
49 mtl->diff[0] = 1.0;
50 mtl->diff[1] = 1.0;
51 mtl->diff[2] = 1.0;
52 mtl->spec[0] = 0.0;
53 mtl->spec[1] = 0.0;
54 mtl->spec[2] = 0.0;
55 mtl->map_Kd_scale[0] = 1.0;
56 mtl->map_Kd_scale[1] = 1.0;
57 mtl->map_Kd_scale[2] = 1.0;
58 mtl->illum = 2;
59 mtl->reflect = 0.0;
60 mtl->trans = 1;
61 mtl->glossy = 98;
62 mtl->specularPower = 0;
63 mtl->refract_index = 1;
64 mtl->texture_filename[0] = '\0';
65
66 if (localVerbosity > 0)
67 {
68 vtkGenericWarningMacro("Created a default vtkOBJImportedMaterial, texture filename is "
69 << std::string(mtl->texture_filename));
70 }
71 }
72
73 // check if the texture file referenced exists
74 // some files references png when they ship with jpg
75 // so check for that as well
checkTextureMapFile(vtkOBJImportedMaterial * current_mtl,std::string & texturePath)76 void checkTextureMapFile(vtkOBJImportedMaterial* current_mtl, std::string& texturePath)
77 {
78 // try texture as specified
79 bool bFileExistsNoPath = vtksys::SystemTools::FileExists(current_mtl->texture_filename);
80 std::vector<std::string> path_and_file(2);
81 path_and_file[0] = texturePath;
82 path_and_file[1] = std::string(current_mtl->texture_filename);
83 std::string joined = vtksys::SystemTools::JoinPath(path_and_file);
84 bool bFileExistsInPath = vtksys::SystemTools::FileExists(joined);
85 // if the file does not exist and it has a png extension try for jpg instead
86 if (!(bFileExistsNoPath || bFileExistsInPath))
87 {
88 if (vtksys::SystemTools::GetFilenameLastExtension(current_mtl->texture_filename) == ".png")
89 {
90 // try jpg
91 std::string jpgName =
92 vtksys::SystemTools::GetFilenameWithoutLastExtension(current_mtl->texture_filename) +
93 ".jpg";
94 bFileExistsNoPath = vtksys::SystemTools::FileExists(jpgName);
95 path_and_file[0] = texturePath;
96 path_and_file[1] = jpgName;
97 joined = vtksys::SystemTools::JoinPath(path_and_file);
98 bFileExistsInPath = vtksys::SystemTools::FileExists(joined);
99 if (bFileExistsInPath || bFileExistsNoPath)
100 {
101 current_mtl->texture_filename = jpgName;
102 }
103 }
104 if (!(bFileExistsNoPath || bFileExistsInPath))
105 {
106 vtkGenericWarningMacro(<< "mtl file " << current_mtl->name
107 << " requests texture file that appears not to exist: "
108 << current_mtl->texture_filename << "; texture path: " << texturePath
109 << "\n");
110 }
111 }
112 }
113
114 namespace
115 {
116
117 class Token
118 {
119 public:
120 enum TokenType
121 {
122 Number = 1,
123 String,
124 Space,
125 LineEnd
126 };
127
128 TokenType Type;
129 double NumberValue = 0.0;
130 std::string StringValue;
131 };
132
tokenGetString(size_t & t,std::vector<Token> & tokens,std::string & result)133 bool tokenGetString(size_t& t, std::vector<Token>& tokens, std::string& result)
134 {
135 // must have two more tokens and the next token must be a space
136 if (tokens.size() <= t + 2 || tokens[t + 1].Type != Token::Space ||
137 tokens[t + 2].Type != Token::String)
138 {
139 vtkGenericWarningMacro("bad syntax");
140 return false;
141 }
142 result = tokens[t + 2].StringValue;
143 t += 2;
144 return true;
145 }
146
tokenGetNumber(size_t & t,std::vector<Token> & tokens,double & result)147 bool tokenGetNumber(size_t& t, std::vector<Token>& tokens, double& result)
148 {
149 // must have two more tokens and the next token must be a space
150 if (tokens.size() <= t + 2 || tokens[t + 1].Type != Token::Space ||
151 tokens[t + 2].Type != Token::Number)
152 {
153 vtkGenericWarningMacro("bad syntax");
154 return false;
155 }
156 result = tokens[t + 2].NumberValue;
157 t += 2;
158 return true;
159 }
160
tokenGetVector(size_t & t,std::vector<Token> & tokens,double * result,size_t resultSize,size_t minNums)161 bool tokenGetVector(
162 size_t& t, std::vector<Token>& tokens, double* result, size_t resultSize, size_t minNums)
163 {
164 // must have two more tokens and the next token must be a space
165 if (tokens.size() <= t + 2 * minNums)
166 {
167 vtkGenericWarningMacro("bad syntax");
168 return false;
169 }
170 // parse the following numbers
171 size_t count = 0;
172 while (tokens.size() > t + 2 && tokens[t + 1].Type == Token::Space &&
173 tokens[t + 2].Type == Token::Number)
174 {
175 result[count] = tokens[t + 2].NumberValue;
176 t += 2;
177 count++;
178 }
179
180 // if any values provided then copy the first value to any missing values
181 if (count)
182 {
183 for (size_t i = count; i < resultSize; ++i)
184 {
185 result[i] = result[count - 1];
186 }
187 }
188
189 return true;
190 }
191
tokenGetTexture(size_t & t,std::vector<Token> & tokens,vtkOBJImportedMaterial * current_mtl,std::string & texturePath)192 bool tokenGetTexture(size_t& t, std::vector<Token>& tokens, vtkOBJImportedMaterial* current_mtl,
193 std::string& texturePath)
194 {
195 // parse the next tokens looking for
196 // texture options must all be on one line
197 current_mtl->texture_filename = "";
198 for (size_t tt = t + 1; tt < tokens.size(); ++tt)
199 {
200 if (tokens[tt].Type == Token::Space)
201 {
202 continue;
203 }
204 if (tokens[tt].Type == Token::LineEnd)
205 {
206 t = tt;
207 return false;
208 }
209
210 // string value
211 if (tokens[tt].StringValue == "-s")
212 {
213 tokenGetVector(tt, tokens, current_mtl->map_Kd_scale, 3, 1);
214 continue;
215 }
216 if (tokens[tt].StringValue == "-o")
217 {
218 tokenGetVector(tt, tokens, current_mtl->map_Kd_offset, 3, 1);
219 continue;
220 }
221 if (tokens[tt].StringValue == "-mm")
222 {
223 double tmp[2];
224 tokenGetVector(tt, tokens, tmp, 2, 1);
225 continue;
226 }
227
228 // if we got here then must be name of texture file
229 // or an unknown option, we combine all tokens
230 // form this point forward as they may be a filename
231 // with spaces in them
232 current_mtl->texture_filename += tokens[tt].StringValue;
233 ++tt;
234 while (tokens[tt].Type != Token::LineEnd)
235 {
236 current_mtl->texture_filename += tokens[tt].StringValue;
237 ++tt;
238 }
239 checkTextureMapFile(current_mtl, texturePath);
240 t = tt;
241 return true;
242 }
243
244 return false;
245 }
246 }
247
248 // NOLINTNEXTLINE(bugprone-suspicious-include)
249 #include "mtlsyntax.cxx"
ParseOBJandMTL(std::string Filename,int & result_code)250 std::vector<vtkOBJImportedMaterial*> vtkOBJPolyDataProcessor::ParseOBJandMTL(
251 std::string Filename, int& result_code)
252 {
253 std::vector<vtkOBJImportedMaterial*> listOfMaterials;
254 result_code = 0;
255
256 if (Filename.empty())
257 {
258 return listOfMaterials;
259 }
260
261 vtksys::ifstream in(Filename.c_str(), std::ios::in | std::ios::binary);
262 if (!in)
263 {
264 return listOfMaterials;
265 }
266
267 std::vector<Token> tokens;
268 std::string contents;
269 in.seekg(0, std::ios::end);
270 contents.resize(in.tellg());
271 in.seekg(0, std::ios::beg);
272 in.read(&contents[0], contents.size());
273 in.close();
274
275 // watch for BOM
276 if (contents[0] == -17 && contents[1] == -69 && contents[2] == -65)
277 {
278 result_code = parseMTL(contents.c_str() + 3, tokens);
279 }
280 else
281 {
282 result_code = parseMTL(contents.c_str(), tokens);
283 }
284
285 // now handle the token stream
286 vtkOBJImportedMaterial* current_mtl = nullptr;
287 for (size_t t = 0; t < tokens.size(); ++t)
288 {
289 if (tokens[t].Type == Token::Number)
290 {
291 vtkErrorMacro("Number found outside of a command or option on token# "
292 << t << " with number " << tokens[t].NumberValue);
293 break;
294 }
295 if (tokens[t].Type == Token::Space || tokens[t].Type == Token::LineEnd)
296 {
297 continue;
298 }
299
300 // string value
301 std::string lcstr = tokens[t].StringValue;
302 std::transform(lcstr.begin(), lcstr.end(), lcstr.begin(), ::tolower);
303 if (tokens[t].StringValue == "newmtl")
304 {
305 current_mtl = (new vtkOBJImportedMaterial);
306 listOfMaterials.push_back(current_mtl);
307 obj_set_material_defaults(current_mtl);
308 tokenGetString(t, tokens, current_mtl->name);
309 continue;
310 }
311 if (tokens[t].StringValue == "Ka")
312 {
313 tokenGetVector(t, tokens, current_mtl->amb, 3, 1);
314 continue;
315 }
316 if (tokens[t].StringValue == "Kd")
317 {
318 tokenGetVector(t, tokens, current_mtl->diff, 3, 1);
319 continue;
320 }
321 if (tokens[t].StringValue == "Ks")
322 {
323 tokenGetVector(t, tokens, current_mtl->spec, 3, 1);
324 continue;
325 }
326 if (tokens[t].StringValue == "Ns")
327 {
328 tokenGetNumber(t, tokens, current_mtl->specularPower);
329 continue;
330 }
331 if (tokens[t].StringValue == "d")
332 {
333 tokenGetNumber(t, tokens, current_mtl->trans);
334 continue;
335 }
336 if (tokens[t].StringValue == "illum")
337 {
338 double tmp;
339 if (tokenGetNumber(t, tokens, tmp))
340 {
341 current_mtl->illum = static_cast<int>(tmp);
342 }
343 continue;
344 }
345 if (lcstr == "map_ka" || lcstr == "map_kd")
346 {
347 tokenGetTexture(t, tokens, current_mtl, this->TexturePath);
348 continue;
349 }
350
351 // vtkErrorMacro("Unknown command in mtl file at token# " <<
352 // t << " and value " << tokens[t].StringValue);
353 // consume to the end of the line
354 while (t < tokens.size() && tokens[t].Type != Token::LineEnd)
355 {
356 ++t;
357 }
358 }
359
360 return listOfMaterials;
361 }
362
bindTexturedPolydataToRenderWindow(vtkRenderWindow * renderWindow,vtkRenderer * renderer,vtkOBJPolyDataProcessor * reader)363 void bindTexturedPolydataToRenderWindow(
364 vtkRenderWindow* renderWindow, vtkRenderer* renderer, vtkOBJPolyDataProcessor* reader)
365 {
366 if (nullptr == (renderWindow))
367 {
368 vtkErrorWithObjectMacro(reader, "RenderWindow is null, failure!");
369 return;
370 }
371 if (nullptr == (renderer))
372 {
373 vtkErrorWithObjectMacro(reader, "Renderer is null, failure!");
374 return;
375 }
376 if (nullptr == (reader))
377 {
378 vtkErrorWithObjectMacro(reader, "vtkOBJPolyDataProcessor is null, failure!");
379 return;
380 }
381
382 reader->actor_list.clear();
383 reader->actor_list.reserve(reader->GetNumberOfOutputPorts());
384
385 // keep track of textures used and if multiple parts use the same
386 // texture, then have the actors use the same texture. This saves memory
387 // etc and makes exporting more efficient.
388 std::map<std::string, vtkSmartPointer<vtkTexture>> knownTextures;
389
390 for (int port_idx = 0; port_idx < reader->GetNumberOfOutputPorts(); port_idx++)
391 {
392 vtkPolyData* objPoly = reader->GetOutput(port_idx);
393
394 vtkSmartPointer<vtkPolyDataMapper> mapper = vtkSmartPointer<vtkPolyDataMapper>::New();
395 mapper->SetInputData(objPoly);
396 mapper->SetColorModeToDirectScalars();
397
398 vtkSmartPointer<vtkActor> actor = vtkSmartPointer<vtkActor>::New();
399 actor->SetMapper(mapper);
400
401 vtkDebugWithObjectMacro(reader,
402 "Grabbed objPoly " << objPoly << ", port index " << port_idx << "\n"
403 << "numPolys = " << objPoly->GetNumberOfPolys()
404 << " numPoints = " << objPoly->GetNumberOfPoints());
405
406 // For each named material, load and bind the texture, add it to the renderer
407
408 std::string textureFilename = reader->GetTextureFilename(port_idx);
409
410 auto kti = knownTextures.find(textureFilename);
411 if (kti == knownTextures.end())
412 {
413 vtkSmartPointer<vtkTIFFReader> tex_tiff_Loader = vtkSmartPointer<vtkTIFFReader>::New();
414 vtkSmartPointer<vtkBMPReader> tex_bmp_Loader = vtkSmartPointer<vtkBMPReader>::New();
415 vtkSmartPointer<vtkJPEGReader> tex_jpg_Loader = vtkSmartPointer<vtkJPEGReader>::New();
416 vtkSmartPointer<vtkPNGReader> tex_png_Loader = vtkSmartPointer<vtkPNGReader>::New();
417 int bIsReadableBMP = tex_bmp_Loader->CanReadFile(textureFilename.c_str());
418 int bIsReadableJPEG = tex_jpg_Loader->CanReadFile(textureFilename.c_str());
419 int bIsReadablePNG = tex_png_Loader->CanReadFile(textureFilename.c_str());
420 int bIsReadableTIFF = tex_tiff_Loader->CanReadFile(textureFilename.c_str());
421
422 if (!textureFilename.empty())
423 {
424 if (bIsReadableJPEG)
425 {
426 tex_jpg_Loader->SetFileName(textureFilename.c_str());
427 tex_jpg_Loader->Update();
428 vtkSmartPointer<vtkTexture> vtk_texture = vtkSmartPointer<vtkTexture>::New();
429 vtk_texture->AddInputConnection(tex_jpg_Loader->GetOutputPort());
430 actor->SetTexture(vtk_texture);
431 knownTextures[textureFilename] = vtk_texture;
432 }
433 else if (bIsReadablePNG)
434 {
435 tex_png_Loader->SetFileName(textureFilename.c_str());
436 tex_png_Loader->Update();
437 vtkSmartPointer<vtkTexture> vtk_texture = vtkSmartPointer<vtkTexture>::New();
438 vtk_texture->AddInputConnection(tex_png_Loader->GetOutputPort());
439 actor->SetTexture(vtk_texture);
440 knownTextures[textureFilename] = vtk_texture;
441 }
442 else if (bIsReadableBMP)
443 {
444 tex_bmp_Loader->SetFileName(textureFilename.c_str());
445 tex_bmp_Loader->Update();
446 vtkSmartPointer<vtkTexture> vtk_texture = vtkSmartPointer<vtkTexture>::New();
447 vtk_texture->AddInputConnection(tex_bmp_Loader->GetOutputPort());
448 actor->SetTexture(vtk_texture);
449 knownTextures[textureFilename] = vtk_texture;
450 }
451 else if (bIsReadableTIFF)
452 {
453 tex_tiff_Loader->SetFileName(textureFilename.c_str());
454 tex_tiff_Loader->Update();
455 vtkSmartPointer<vtkTexture> vtk_texture = vtkSmartPointer<vtkTexture>::New();
456 vtk_texture->AddInputConnection(tex_tiff_Loader->GetOutputPort());
457 actor->SetTexture(vtk_texture);
458 knownTextures[textureFilename] = vtk_texture;
459 }
460 else
461 {
462 if (!textureFilename
463 .empty()) // OK to have no texture image, but if its not empty it ought to exist.
464 {
465 vtkErrorWithObjectMacro(
466 reader, "Nonexistent texture image type!? imagefile: " << textureFilename);
467 }
468 }
469 }
470 }
471 else // this is a texture we already have seen
472 {
473 actor->SetTexture(kti->second);
474 }
475
476 vtkSmartPointer<vtkProperty> properties = vtkSmartPointer<vtkProperty>::New();
477
478 vtkOBJImportedMaterial* raw_mtl_data = reader->GetMaterial(port_idx);
479 if (raw_mtl_data)
480 {
481 // handle texture coordinate transforms
482 if (actor->GetTexture() &&
483 (raw_mtl_data->map_Kd_scale[0] != 1 || raw_mtl_data->map_Kd_scale[1] != 1 ||
484 raw_mtl_data->map_Kd_scale[2] != 1))
485 {
486 vtkNew<vtkTransform> tf;
487 tf->Scale(raw_mtl_data->map_Kd_scale[0], raw_mtl_data->map_Kd_scale[1],
488 raw_mtl_data->map_Kd_scale[2]);
489 actor->GetTexture()->SetTransform(tf);
490 }
491
492 // When the material is created from a MTL file,
493 // the name is different than the default "x" name.
494 // in this case, we disable vertex coloring
495 if (raw_mtl_data->name != "x")
496 {
497 mapper->ScalarVisibilityOff();
498 }
499
500 properties->SetDiffuseColor(raw_mtl_data->diff);
501 properties->SetSpecularColor(raw_mtl_data->spec);
502 properties->SetAmbientColor(raw_mtl_data->amb);
503 properties->SetOpacity(raw_mtl_data->trans);
504 properties->SetInterpolationToPhong();
505 switch (raw_mtl_data->illum)
506 {
507 case 0:
508 properties->SetLighting(false);
509 properties->SetDiffuse(0);
510 properties->SetSpecular(0);
511 properties->SetAmbient(1.0);
512 properties->SetColor(properties->GetDiffuseColor());
513 break;
514 case 1:
515 properties->SetDiffuse(1.0);
516 properties->SetSpecular(0);
517 properties->SetAmbient(1.0);
518 break;
519 default:
520 case 2:
521 properties->SetDiffuse(1.0);
522 properties->SetSpecular(1.0);
523 properties->SetAmbient(1.0);
524 // blinn to phong ~= 4.0
525 properties->SetSpecularPower(raw_mtl_data->specularPower / 4.0);
526 break;
527 }
528 actor->SetProperty(properties);
529 }
530 renderer->AddActor(actor);
531
532 // properties->ShadingOn(); // use ShadingOn() if loading vtkMaterial from xml
533 // available in mtl parser are:
534 // double amb[3];
535 // double diff[3];
536 // double spec[3];
537 // double reflect;
538 // double refract;
539 // double trans;
540 // double shiny;
541 // double glossy;
542 // double refract_index;
543
544 reader->actor_list.push_back(actor); // keep a handle on actors to animate later
545 }
546 /** post-condition of this function: the renderer has had a bunch of actors added to it */
547 }
548
vtkOBJImportedMaterial()549 vtkOBJImportedMaterial::vtkOBJImportedMaterial()
550 {
551 this->name = "x";
552 obj_set_material_defaults(this);
553 }
554