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