Developer Documentation
FileOBJT_impl.hh
1 /*===========================================================================*\
2 * *
3 * OpenFlipper *
4  * Copyright (c) 2001-2015, RWTH-Aachen University *
5  * Department of Computer Graphics and Multimedia *
6  * All rights reserved. *
7  * www.openflipper.org *
8  * *
9  *---------------------------------------------------------------------------*
10  * This file is part of OpenFlipper. *
11  *---------------------------------------------------------------------------*
12  * *
13  * Redistribution and use in source and binary forms, with or without *
14  * modification, are permitted provided that the following conditions *
15  * are met: *
16  * *
17  * 1. Redistributions of source code must retain the above copyright notice, *
18  * this list of conditions and the following disclaimer. *
19  * *
20  * 2. Redistributions in binary form must reproduce the above copyright *
21  * notice, this list of conditions and the following disclaimer in the *
22  * documentation and/or other materials provided with the distribution. *
23  * *
24  * 3. Neither the name of the copyright holder nor the names of its *
25  * contributors may be used to endorse or promote products derived from *
26  * this software without specific prior written permission. *
27  * *
28  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS *
29  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED *
30  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A *
31  * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER *
32  * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, *
33  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, *
34  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR *
35  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF *
36  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING *
37  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS *
38  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *
39 * *
40 \*===========================================================================*/
41 
42 
43 
44 
45 #define FILEOBJPLUGIN_C
46 
47 #include "FileOBJ.hh"
48 
49 #include <OpenMesh/Core/Utils/color_cast.hh>
50 #include <OpenMesh/Core/Geometry/VectorT.hh>
51 
52 
53 //-----------------------------------------------------------------------------------------------------
54 
55 template< class MeshT >
56 bool FileOBJPlugin::writeMaterial(QString _filename, MeshT& _mesh, int _objId )
57 {
58  bool optionFaceColors = false;
59  bool optionFaceColorsOverride = false;
60  bool optionColorAlpha = false;
61  bool optionTextures = false;
62  bool optionCopyTextures = false;
63  bool optionCreateTexFolder = false;
64 
65  // check options
66  if ( !OpenFlipper::Options::savingSettings() && saveOptions_ != 0) {
67 
68  optionColorAlpha = saveAlpha_->isChecked();
69  optionTextures = saveTextures_->isChecked();
70  optionCopyTextures = saveCopyTextures_->isChecked();
71  optionCreateTexFolder = saveCreateTexFolder_->isChecked();
72  }
73  // \TODO Fetch options from ini states if dialog box is not available
74 
75  std::fstream matStream( _filename.toStdString().c_str(), std::ios_base::out );
76 
77  if ( !matStream ){
78 
79  emit log(LOGERR, tr("writeMaterial : cannot not open file %1").arg(_filename) );
80  return false;
81  }
82 
83  // \TODO Implement setting of all colors (diffuse, ambient and specular)
84  // There's only diffuse colors so far
86 
87  materials_.clear();
88 
89  //iterate over faces
90  typename MeshT::FaceIter f_it;
91  typename MeshT::FaceIter f_end = _mesh.faces_end();
92 
93  // Prepare materials ( getMaterial handles a list that is set up by this call)
94  for (f_it = _mesh.faces_begin(); f_it != f_end; ++f_it){
95  getMaterial(_mesh, *f_it, _objId);
96  }
97 
98  //write the materials
99  for(MaterialList::iterator it = materials_.begin(); it != materials_.end(); ++it) {
100  Material& mat = (*it).second;
101  matStream << "newmtl " << mat << '\n';
102  matStream << "Ka 0.5000 0.5000 0.5000" << '\n';
103  ACG::Vec3f c = mat.Kd();
104  matStream << "Kd " << c[0] << " " << c[1] << " " << c[2] << '\n';
105  if(optionColorAlpha) {
106  matStream << "Tr " << mat.Tr() << '\n';
107  }
108  matStream << "illum 1" << '\n';
109 
110  // Write out texture info
111  if(optionTextures && mat.has_Texture()) {
112  if(optionCopyTextures) {
113  // Use file path in target folder (relative)
114  QFileInfo file(mat.map_Kd().c_str());
115  if(optionCreateTexFolder) {
116  QFileInfo materialFilename(_filename);
117 
118  matStream << "map_Kd " << materialFilename.baseName().toStdString() << "_textures" << QDir::separator().toLatin1()
119  << file.fileName().toStdString() << '\n';
120  } else {
121  matStream << "map_Kd " << file.fileName().toStdString() << '\n';
122  }
123  } else {
124  // Use original file path
125  matStream << "map_Kd " << mat.map_Kd() << '\n';
126  }
127  }
128 
129  matStream << '\n';
130  }
131 
132  matStream.close();
133 
134  return true;
135 }
136 
137 //-----------------------------------------------------------------------------------------------------
138 
139 template< class MeshT >
140 Material& FileOBJPlugin::getMaterial(MeshT& _mesh, const OpenMesh::FaceHandle& _fh, int _objId)
141 {
142  bool optionFaceColors = false;
143  bool optionFaceColorsOverride = false;
144  bool optionColorAlpha = false;
145 
146  if ( !OpenFlipper::Options::savingSettings() && saveOptions_ != 0) {
147  optionFaceColors = saveFaceColor_->isChecked();
148  optionFaceColorsOverride = saveFaceColorOverride_->isChecked();
149  optionColorAlpha = saveAlpha_->isChecked();
150  }
151  // \TODO Fetch options from ini states if dialog box is not available
152 
153  OpenMesh::Vec4f c = _mesh.color( _fh );
154 
155  if ( optionFaceColors && !optionFaceColorsOverride ) {
156 
157  // If one of the entries is out of range, assume uninitialized color and set to default
158  // Ugly hack to ensure that materials will only be generated if initialized
159  if ( c[0] > 255.0 || c[1] > 255.0 || c[2] > 255.0 || c[0] < 0.0 || c[1] < 0.0 || c[2] < 0.0 || std::isnan(c[0]) || std::isnan(c[1]) || std::isnan(c[2]) ) {
160  c[0] = 155.0;
161  c[1] = 155.0;
162  c[2] = 155.0;
163  c[3] = 1.0;
164 
165  ++materialErrors_;
166 
167  // If we have too many errors, we always return the first material
168  // Except, if the override option is set.
169  if (materialErrors_ > 10)
170  return (*materials_.begin()).second;
171 
172  }
173 
174  }
175 
176  // First off, try to fetch texture index of current face/object...
177  if(!textureIndexPropFetched_) {
178  emit textureIndexPropertyName(_objId, textureIndexPropertyName_);
179  textureIndexPropFetched_ = true;
180  }
181 
182  int texIndex = -1;
183  OpenMesh::FPropHandleT< int > texture_index_property;
184  if ( _mesh.get_property_handle(texture_index_property, textureIndexPropertyName_.toStdString()) ) {
185  texIndex = _mesh.property(texture_index_property, _fh);
186  } else if ( _mesh.get_property_handle(texture_index_property, "f:textureindex") ) {
187  texIndex = _mesh.property(texture_index_property, _fh);
188  } else if(_mesh.has_face_texture_index()) {
189  texIndex = _mesh.texture_index(_fh);
190  } else {
191  QString texName;
192  emit getCurrentTexture(_objId, texName);
193  if(texName != "NONE")
194  emit textureIndex(texName, _objId, texIndex);
195  }
196 
197  QString filename;
198  bool hasTexture = false;
199 
200  if(texIndex != -1) {
201 
202  // Search for texture index in local map
203  std::map<int,QString>::iterator it = texIndexFileMap_.find(texIndex);
204 
205  if(it != texIndexFileMap_.end()) {
206  // We already know this file
207  filename = (*it).second;
208  hasTexture = true;
209  } else {
210  // A new texture file has been found
211  QString texName;
212  emit textureName(_objId, texIndex, texName);
213 
214  if(texName != "NOT_FOUND") {
215  emit textureFilename( _objId, texName, filename );
216  // => Add to local map
217  texIndexFileMap_.insert(std::pair<int,QString>(texIndex, filename));
218  hasTexture = true;
219  }
220  }
221  }
222 
223  for (MaterialList::iterator it = materials_.begin(); it != materials_.end(); ++it) {
224 
225  // No texture has been found
226  if(!hasTexture) {
227  // ... just look for diffuse color in materials list
228  if(((*it).second).Kd() == ACG::Vec3f(c[0], c[1], c[2]) &&
229  ((optionColorAlpha && ((*it).second).Tr() == c[3]) || !optionColorAlpha))
230  return (*it).second;
231  } else {
232  // Texture has been found, look for both, matching texture and color
233  QString mKd(((*it).second).map_Kd().c_str());
234  if((((*it).second).Kd() == ACG::Vec3f(c[0], c[1], c[2]) &&
235  ((optionColorAlpha && ((*it).second).Tr() == c[3]) || !optionColorAlpha)) &&
236  (filename == mKd && ((*it).second).map_Kd_index() == texIndex))
237  return (*it).second;
238  }
239  }
240 
241  // If not found, add new material(s)
242  Material mat;
243  // Set diffuse color
244  mat.set_Kd(c[0], c[1], c[2]);
245  // Add transparency if available
246  if(optionColorAlpha) mat.set_Tr(c[3]);
247  mat.material_number(materials_.size());
248  // Set texture info
249  if(hasTexture)
250  mat.set_map_Kd(filename.toStdString(), texIndex);
251 
252  materials_.insert(std::make_pair(QString("Material%1").arg(mat.material_number()).toStdString(), mat));
253  MaterialList::iterator it = materials_.end();
254  --it;
255  return (*it).second;
256 }
257 
258 
259 
260 
261 //-----------------------------------------------------------------------------------------------------
262 
263 template< class MeshT >
264 bool FileOBJPlugin::writeMesh(std::ostream& _out, QString _filename, MeshT& _mesh, int _objId){
265 
266  unsigned int i, nV, idx;
267  Vec3f v, n;
268  Vec2f t(0.0f,0.0f);
269  typename MeshT::VertexHandle vh;
270  bool useMaterial = false;
271  OpenMesh::Vec4f c;
272 
273  bool optionFaceColors = false;
274  bool optionVertexNormals = false;
275  bool optionVertexTexCoords = true;
276  bool optionTextures = false;
277  bool optionCopyTextures = false;
278  bool optionCreateTexFolder = false;
279 
280  QFileInfo fi(_filename);
281 
282  // check options
283  if ( !OpenFlipper::Options::savingSettings() && saveOptions_ != 0) {
284  optionFaceColors = saveFaceColor_->isChecked();
285  optionVertexNormals = saveNormals_->isChecked();
286  optionVertexTexCoords = saveTexCoords_->isChecked();
287  optionTextures = saveTextures_->isChecked();
288  optionCopyTextures = saveCopyTextures_->isChecked();
289  optionCreateTexFolder = saveCreateTexFolder_->isChecked();
290  _out.precision(savePrecision_->value());
291  };
292  // \TODO Fetch options from ini states if dialog box is not available
293 
294  //create material file if needed
295  if ( optionFaceColors || optionTextures ){
296 
297  QString matFile = fi.absolutePath() + QDir::separator() + fi.baseName() + ".mtl";
298 
299  useMaterial = writeMaterial(matFile, _mesh, _objId);
300  }
301 
302  // Header
303  _out << "# " << _mesh.n_vertices() << " vertices, ";
304  _out << _mesh.n_faces() << " faces" << '\n';
305 
306  // Material file
307  if (useMaterial && optionFaceColors )
308  _out << "mtllib " << fi.baseName().toStdString() << ".mtl" << '\n';
309 
310  // Store indices of vertices in a map such that
311  // they can easily be referenced for face definitions
312  // later on
313  std::map<typename MeshT::VertexHandle, int> vtMapV;
314 
315  int cf = 1;
316  // vertex data (point, normals, texcoords)
317  for (i=0, nV=_mesh.n_vertices(); i<nV; ++i)
318  {
319  vh = typename MeshT::VertexHandle(i);
320  v = _mesh.point(vh);
321  n = _mesh.normal(vh);
322 
323  if ( _mesh.has_vertex_texcoords2D() && !_mesh.has_halfedge_texcoords2D() )
324  t = _mesh.texcoord2D(vh);
325 
326  // Write out vertex coordinates
327  _out << "v " << v[0] <<" "<< v[1] <<" "<< v[2] << '\n';
328 
329  // Write out vertex coordinates
330  if ( optionVertexNormals)
331  _out << "vn " << n[0] <<" "<< n[1] <<" "<< n[2] << '\n';
332 
333  // Write out vertex texture coordinates
334  if ( optionVertexTexCoords && _mesh.has_vertex_texcoords2D() && !_mesh.has_halfedge_texcoords2D()) {
335  _out << "vt " << t[0] <<" "<< t[1] << '\n';
336  vtMapV.insert(std::pair<typename MeshT::VertexHandle, int>(vh, cf));
337  cf++;
338  }
339  }
340 
341  typename MeshT::FaceVertexIter fv_it;
342  typename MeshT::FaceHalfedgeIter fh_it;
343  typename MeshT::FaceIter f_it;
344 
345  // Store indices of vertex coordinate (in obj-file)
346  // in map such that the corresponding halfedge
347  // can easily be found later on
348  std::map<typename MeshT::HalfedgeHandle, int> vtMap;
349 
350  // If mesh has halfedge tex coords, write them out instead of vertex texcoords
351  if(optionVertexTexCoords && _mesh.has_halfedge_texcoords2D()) {
352  int count = 1;
353  for ( auto f_it : _mesh.faces() ) {
354  for(fh_it=_mesh.fh_iter(f_it); fh_it.is_valid(); ++fh_it) {
355  typename MeshT::TexCoord2D t = _mesh.texcoord2D(*fh_it);
356  _out << "vt " << t[0] << " " << t[1] << '\n';
357  vtMap.insert(std::pair<typename MeshT::HalfedgeHandle, int>(*fh_it, count));
358  count++;
359  }
360  }
361  }
362 
363  Material lastMat;
364 
365  // we do not want to write separators if we only write vertex indices
366  bool vertexOnly = !(optionVertexTexCoords && _mesh.has_halfedge_texcoords2D())
367  && !(optionVertexTexCoords && !_mesh.has_halfedge_texcoords2D() && _mesh.has_vertex_texcoords2D())
368  && !(optionVertexNormals);
369 
370  for ( auto f_it : _mesh.faces() ){
371 
372  if (useMaterial && optionFaceColors) {
373 
374  Material& material = getMaterial(_mesh, f_it, _objId);
375 
376  // If we are ina a new material block, specify in the file which material to use
377  if(lastMat.material_number() != material.material_number() ) {
378  _out << "usemtl " << material << '\n';
379  lastMat = material;
380  }
381  }
382 
383  _out << "f";
384 
385  // Write out face information
386  for(fh_it=_mesh.fh_iter(f_it); fh_it.is_valid(); ++fh_it) {
387 
388  // Write vertex index
389  idx = _mesh.to_vertex_handle(*fh_it).idx() + 1;
390  _out << " " << idx;
391 
392  if (!vertexOnly) {
393 
394  // Write separator
395  _out << "/" ;
396 
397  if ( optionVertexTexCoords ) {
398  // Write vertex texture coordinate index
399  if ( optionVertexTexCoords && _mesh.has_halfedge_texcoords2D()) {
400  // Refer to halfedge texture coordinates
401  typename std::map<typename MeshT::HalfedgeHandle, int>::iterator it = vtMap.find(*fh_it);
402  if(it != vtMap.end())
403  _out << (*it).second;
404  } else if (optionVertexTexCoords && !_mesh.has_halfedge_texcoords2D() && _mesh.has_vertex_texcoords2D()) {
405  // Refer to vertex texture coordinates
406  typename std::map<typename MeshT::VertexHandle, int>::iterator it = vtMapV.find(_mesh.to_vertex_handle(*fh_it));
407  if(it != vtMapV.end())
408  _out << (*it).second;
409  }
410  }
411 
412  // Write vertex normal index
413  if ( optionVertexNormals ) {
414  // Write separator
415  _out << "/" ;
416 
417  _out << idx;
418  }
419  }
420  }
421 
422  _out << '\n';
423  }
424 
425  // Copy texture files (if demanded)
426  if(optionCopyTextures) {
427  // Only test existence of folder once
428  // (for multiple textures)
429  bool testedOnce = false;
430  for(MaterialList::iterator it = materials_.begin(); it != materials_.end(); ++it) {
431  Material& mat = (*it).second;
432 
433  if(!mat.has_Texture()) continue;
434 
435  QImage img(mat.map_Kd().c_str());
436  QFileInfo img_f(mat.map_Kd().c_str());
437 
438  if(img.isNull()) {
439  // Something happened wrong
440  emit log(LOGERR, tr("An error occurred when trying to copy a texture file."));
441  continue;
442  } else {
443  if(optionCreateTexFolder) {
444  // Create folder
445  QDir dir(fi.absolutePath());
446  if(!testedOnce && dir.exists(fi.absolutePath() + QDir::separator() + fi.baseName() + "_textures")) {
447  emit log(LOGERR, tr("The specified target folder already contains a subfolder called textures. Skipping!"));
448  continue;
449  } else {
450  dir.mkdir(fi.baseName() + "_textures");
451  img.save(fi.absolutePath() + QDir::separator() + fi.baseName() + "_textures" + QDir::separator() + img_f.fileName());
452  testedOnce = true;
453  }
454 
455  } else {
456  img.save(fi.absolutePath() + QDir::separator() + img_f.fileName());
457  }
458  }
459  }
460  }
461 
462  materials_.clear();
463  materialErrors_ = 0;
464  texIndexFileMap_.clear();
465  textureIndexPropFetched_ = false;
466 
467  return true;
468 }
469 
470 
471 
472 
Handle for a face entity.
Definition: Handles.hh:141
Add 2D texture coordinates (vertices, halfedges)
Definition: Attributes.hh:87
MaterialList materials_
List that contains the material properties.
Definition: FileOBJ.hh:200
VectorT< float, 3 > Vec3f
Definition: VectorT.hh:119
bool writeMaterial(QString _filename, MeshT &_mesh, int _objId)
writer functions
unsigned int materialErrors_
number of defect materials encountered during writing
Definition: FileOBJ.hh:203