Developer Documentation
FileOBJT.cc
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 * $Revision$ *
45 * $LastChangedBy$ *
46 * $Date$ *
47 * *
48 \*===========================================================================*/
49 
50 
51 #define FILEOBJPLUGIN_C
52 
53 #include "FileOBJ.hh"
54 
55 #include <OpenMesh/Core/Utils/color_cast.hh>
56 #include <OpenMesh/Core/Geometry/VectorT.hh>
57 
58 
59 //-----------------------------------------------------------------------------------------------------
60 
61 template< class MeshT >
62 bool FileOBJPlugin::writeMaterial(QString _filename, MeshT& _mesh, int _objId )
63 {
64  bool optionColorAlpha = false;
65  bool optionTextures = false;
66  bool optionCopyTextures = false;
67  bool optionCreateTexFolder = false;
68 
69  // check options
70  if ( !OpenFlipper::Options::savingSettings() && saveOptions_ != 0) {
71  optionColorAlpha = saveAlpha_->isChecked();
72  optionTextures = saveTextures_->isChecked();
73  optionCopyTextures = saveCopyTextures_->isChecked();
74  optionCreateTexFolder = saveCreateTexFolder_->isChecked();
75  }
76  // \TODO Fetch options from ini states if dialog box is not available
77 
78  std::fstream matStream( _filename.toStdString().c_str(), std::ios_base::out );
79 
80  if ( !matStream ){
81 
82  emit log(LOGERR, tr("writeMaterial : cannot not open file %1").arg(_filename) );
83  return false;
84  }
85 
86  // \TODO Implement setting of all colors (diffuse, ambient and specular)
87  // There's only diffuse colors so far
89 
90  materials_.clear();
91 
92  //iterate over faces
93  typename MeshT::FaceIter f_it;
94  typename MeshT::FaceIter f_end = _mesh.faces_end();
95 
96  // Prepare materials ( getMaterial handles a list that is set up by this call)
97  for (f_it = _mesh.faces_begin(); f_it != f_end; ++f_it){
98  getMaterial(_mesh, *f_it, _objId);
99  }
100 
101  //write the materials
102  for(MaterialList::iterator it = materials_.begin(); it != materials_.end(); ++it) {
103  Material& mat = (*it).second;
104  matStream << "newmtl " << mat << '\n';
105  matStream << "Ka 0.5000 0.5000 0.5000" << '\n';
106  ACG::Vec3f c = mat.Kd();
107  matStream << "Kd " << c[0] << " " << c[1] << " " << c[2] << '\n';
108  if(optionColorAlpha) {
109  matStream << "Tr " << mat.Tr() << '\n';
110  }
111  matStream << "illum 1" << '\n';
112 
113  // Write out texture info
114  if(optionTextures && mat.has_Texture()) {
115  if(optionCopyTextures) {
116  // Use file path in target folder (relative)
117  QFileInfo file(mat.map_Kd().c_str());
118  if(optionCreateTexFolder) {
119  QFileInfo materialFilename(_filename);
120 
121  matStream << "map_Kd " << materialFilename.baseName().toStdString() << "_textures" << QDir::separator().toLatin1()
122  << file.fileName().toStdString() << '\n';
123  } else {
124  matStream << "map_Kd " << file.fileName().toStdString() << '\n';
125  }
126  } else {
127  // Use original file path
128  matStream << "map_Kd " << mat.map_Kd() << '\n';
129  }
130  }
131 
132  matStream << '\n';
133  }
134 
135  matStream.close();
136 
137  return true;
138 }
139 
140 //-----------------------------------------------------------------------------------------------------
141 
142 template< class MeshT >
143 Material& FileOBJPlugin::getMaterial(MeshT& _mesh, const OpenMesh::FaceHandle& _fh, int _objId)
144 {
145  // check options
146  bool optionColorAlpha = false;
147  if ( !OpenFlipper::Options::savingSettings() && saveOptions_ != 0)
148  optionColorAlpha = saveAlpha_->isChecked();
149  // \TODO Fetch options from ini states if dialog box is not available
150 
151  OpenMesh::Vec4f c = _mesh.color( _fh );
152 
153  // First off, try to fetch texture index of current face/object...
154  if(!textureIndexPropFetched_) {
155  emit textureIndexPropertyName(_objId, textureIndexPropertyName_);
156  textureIndexPropFetched_ = true;
157  }
158 
159  int texIndex = -1;
160  OpenMesh::FPropHandleT< int > texture_index_property;
161  if ( _mesh.get_property_handle(texture_index_property, textureIndexPropertyName_.toStdString()) ) {
162  texIndex = _mesh.property(texture_index_property, _fh);
163  } else if ( _mesh.get_property_handle(texture_index_property, "f:textureindex") ) {
164  texIndex = _mesh.property(texture_index_property, _fh);
165  } else if(_mesh.has_face_texture_index()) {
166  texIndex = _mesh.texture_index(_fh);
167  } else {
168  QString texName;
169  emit getCurrentTexture(_objId, texName);
170  if(texName != "NONE")
171  emit textureIndex(texName, _objId, texIndex);
172  }
173 
174  QString filename;
175  bool hasTexture = false;
176 
177  if(texIndex != -1) {
178 
179  // Search for texture index in local map
180  std::map<int,QString>::iterator it = texIndexFileMap_.find(texIndex);
181 
182  if(it != texIndexFileMap_.end()) {
183  // We already know this file
184  filename = (*it).second;
185  hasTexture = true;
186  } else {
187  // A new texture file has been found
188  QString texName;
189  emit textureName(_objId, texIndex, texName);
190 
191  if(texName != "NOT_FOUND") {
192  emit textureFilename( _objId, texName, filename );
193  // => Add to local map
194  texIndexFileMap_.insert(std::pair<int,QString>(texIndex, filename));
195  hasTexture = true;
196  }
197  }
198  }
199 
200  for (MaterialList::iterator it = materials_.begin(); it != materials_.end(); ++it) {
201 
202  // No texture has been found
203  if(!hasTexture) {
204  // ... just look for diffuse color in materials list
205  if(((*it).second).Kd() == ACG::Vec3f(c[0], c[1], c[2]) &&
206  ((optionColorAlpha && ((*it).second).Tr() == c[3]) || !optionColorAlpha))
207  return (*it).second;
208  } else {
209  // Texture has been found, look for both, matching texture and color
210  QString mKd(((*it).second).map_Kd().c_str());
211  if((((*it).second).Kd() == ACG::Vec3f(c[0], c[1], c[2]) &&
212  ((optionColorAlpha && ((*it).second).Tr() == c[3]) || !optionColorAlpha)) &&
213  (filename == mKd && ((*it).second).map_Kd_index() == texIndex))
214  return (*it).second;
215  }
216  }
217 
218  // If not found, add new material(s)
219  Material mat;
220  // Set diffuse color
221  mat.set_Kd(c[0], c[1], c[2]);
222  // Add transparency if available
223  if(optionColorAlpha) mat.set_Tr(c[3]);
224  mat.material_number(materials_.size());
225  // Set texture info
226  if(hasTexture)
227  mat.set_map_Kd(filename.toStdString(), texIndex);
228 
229  materials_.insert(std::make_pair(QString("Material%1").arg(mat.material_number()).toStdString(), mat));
230  MaterialList::iterator it = materials_.end();
231  --it;
232  return (*it).second;
233 }
234 
235 
236 
237 
238 //-----------------------------------------------------------------------------------------------------
239 
240 template< class MeshT >
241 bool FileOBJPlugin::writeMesh(std::ostream& _out, QString _filename, MeshT& _mesh, int _objId){
242 
243  unsigned int i, nV, idx;
244  Vec3f v, n;
245  Vec2f t(0.0f,0.0f);
246  typename MeshT::VertexHandle vh;
247  bool useMaterial = false;
248  OpenMesh::Vec4f c;
249 
250  bool optionFaceColors = false;
251  bool optionVertexNormals = false;
252  bool optionVertexTexCoords = true;
253  bool optionTextures = false;
254  bool optionCopyTextures = false;
255  bool optionCreateTexFolder = false;
256 
257  QFileInfo fi(_filename);
258 
259  // check options
260  if ( !OpenFlipper::Options::savingSettings() && saveOptions_ != 0) {
261  optionFaceColors = saveFaceColor_->isChecked();
262  optionVertexNormals = saveNormals_->isChecked();
263  optionVertexTexCoords = saveTexCoords_->isChecked();
264  optionTextures = saveTextures_->isChecked();
265  optionCopyTextures = saveCopyTextures_->isChecked();
266  optionCreateTexFolder = saveCreateTexFolder_->isChecked();
267  _out.precision(savePrecision_->value());
268  };
269  // \TODO Fetch options from ini states if dialog box is not available
270 
271  //create material file if needed
272  if ( optionFaceColors || optionTextures ){
273 
274  QString matFile = fi.absolutePath() + QDir::separator() + fi.baseName() + ".mtl";
275 
276  useMaterial = writeMaterial(matFile, _mesh, _objId);
277  }
278 
279  // Header
280  _out << "# " << _mesh.n_vertices() << " vertices, ";
281  _out << _mesh.n_faces() << " faces" << '\n';
282 
283  // Material file
284  if (useMaterial && optionFaceColors )
285  _out << "mtllib " << fi.baseName().toStdString() << ".mtl" << '\n';
286 
287  // Store indices of vertices in a map such that
288  // they can easily be referenced for face definitions
289  // later on
290  std::map<typename MeshT::VertexHandle, int> vtMapV;
291 
292  int cf = 1;
293  // vertex data (point, normals, texcoords)
294  for (i=0, nV=_mesh.n_vertices(); i<nV; ++i)
295  {
296  vh = typename MeshT::VertexHandle(i);
297  v = _mesh.point(vh);
298  n = _mesh.normal(vh);
299 
300  if ( _mesh.has_vertex_texcoords2D() && !_mesh.has_halfedge_texcoords2D() )
301  t = _mesh.texcoord2D(vh);
302 
303  // Write out vertex coordinates
304  _out << "v " << v[0] <<" "<< v[1] <<" "<< v[2] << '\n';
305 
306  // Write out vertex coordinates
307  if ( optionVertexNormals)
308  _out << "vn " << n[0] <<" "<< n[1] <<" "<< n[2] << '\n';
309 
310  // Write out vertex texture coordinates
311  if ( optionVertexTexCoords && _mesh.has_vertex_texcoords2D() && !_mesh.has_halfedge_texcoords2D()) {
312  _out << "vt " << t[0] <<" "<< t[1] << '\n';
313  vtMapV.insert(std::pair<typename MeshT::VertexHandle, int>(vh, cf));
314  cf++;
315  }
316  }
317 
318  typename MeshT::FaceVertexIter fv_it;
319  typename MeshT::FaceHalfedgeIter fh_it;
320  typename MeshT::FaceIter f_it;
321 
322  // Store indices of vertex coordinate (in obj-file)
323  // in map such that the corresponding halfedge
324  // can easily be found later on
325  std::map<typename MeshT::HalfedgeHandle, int> vtMap;
326 
327  // If mesh has halfedge tex coords, write them out instead of vertex texcoords
328  if(optionVertexTexCoords && _mesh.has_halfedge_texcoords2D()) {
329  int count = 1;
330  for (f_it = _mesh.faces_begin(); f_it != _mesh.faces_end(); ++f_it) {
331  for(fh_it=_mesh.fh_iter(*f_it); fh_it.is_valid(); ++fh_it) {
332  typename MeshT::TexCoord2D t = _mesh.texcoord2D(*fh_it);
333  _out << "vt " << t[0] << " " << t[1] << '\n';
334  vtMap.insert(std::pair<typename MeshT::HalfedgeHandle, int>(*fh_it, count));
335  count++;
336  }
337  }
338  }
339 
340  Material lastMat;
341 
342  // we do not want to write seperators if we only write vertex indices
343  bool vertexOnly = !(optionVertexTexCoords && _mesh.has_halfedge_texcoords2D())
344  && !(optionVertexTexCoords && !_mesh.has_halfedge_texcoords2D() && _mesh.has_vertex_texcoords2D())
345  && !(optionVertexNormals);
346 
347  for (f_it = _mesh.faces_begin(); f_it != _mesh.faces_end(); ++f_it){
348 
349  if (useMaterial && optionFaceColors) {
350 
351  Material& material = getMaterial(_mesh, *f_it, _objId);
352 
353  // If we are ina a new material block, specify in the file which material to use
354  if(lastMat != material) {
355  _out << "usemtl " << material << '\n';
356  lastMat = material;
357  }
358  }
359 
360  _out << "f";
361 
362  // Write out face information
363  for(fh_it=_mesh.fh_iter(*f_it); fh_it.is_valid(); ++fh_it) {
364 
365  // Write vertex index
366  idx = _mesh.to_vertex_handle(*fh_it).idx() + 1;
367  _out << " " << idx;
368 
369  if (!vertexOnly) {
370 
371  // Write separator
372  _out << "/" ;
373 
374  if ( optionVertexTexCoords ) {
375  // Write vertex texture coordinate index
376  if ( optionVertexTexCoords && _mesh.has_halfedge_texcoords2D()) {
377  // Refer to halfedge texture coordinates
378  typename std::map<typename MeshT::HalfedgeHandle, int>::iterator it = vtMap.find(*fh_it);
379  if(it != vtMap.end())
380  _out << (*it).second;
381  } else if (optionVertexTexCoords && !_mesh.has_halfedge_texcoords2D() && _mesh.has_vertex_texcoords2D()) {
382  // Refer to vertex texture coordinates
383  typename std::map<typename MeshT::VertexHandle, int>::iterator it = vtMapV.find(_mesh.to_vertex_handle(*fh_it));
384  if(it != vtMapV.end())
385  _out << (*it).second;
386  }
387  }
388 
389  // Write vertex normal index
390  if ( optionVertexNormals ) {
391  // Write separator
392  _out << "/" ;
393 
394  _out << idx;
395  }
396  }
397  }
398 
399  _out << '\n';
400  }
401 
402  // Copy texture files (if demanded)
403  if(optionCopyTextures) {
404  // Only test existence of folder once
405  // (for multiple textures)
406  bool testedOnce = false;
407  for(MaterialList::iterator it = materials_.begin(); it != materials_.end(); ++it) {
408  Material& mat = (*it).second;
409 
410  if(!mat.has_Texture()) continue;
411 
412  QImage img(mat.map_Kd().c_str());
413  QFileInfo img_f(mat.map_Kd().c_str());
414 
415  if(img.isNull()) {
416  // Something happened wrong
417  emit log(LOGERR, tr("An error occurred when trying to copy a texture file."));
418  continue;
419  } else {
420  if(optionCreateTexFolder) {
421  // Create folder
422  QDir dir(fi.absolutePath());
423  if(!testedOnce && dir.exists(fi.absolutePath() + QDir::separator() + fi.baseName() + "_textures")) {
424  emit log(LOGERR, tr("The specified target folder already contains a subfolder called textures. Skipping!"));
425  continue;
426  } else {
427  dir.mkdir(fi.baseName() + "_textures");
428  img.save(fi.absolutePath() + QDir::separator() + fi.baseName() + "_textures" + QDir::separator() + img_f.fileName());
429  testedOnce = true;
430  }
431 
432  } else {
433  img.save(fi.absolutePath() + QDir::separator() + img_f.fileName());
434  }
435  }
436  }
437  }
438 
439  materials_.clear();
440  texIndexFileMap_.clear();
441  textureIndexPropFetched_ = false;
442 
443  return true;
444 }
445 
446 
447 
448 
MaterialList materials_
List that contains the material properties.
Definition: FileOBJ.hh:200
VectorT< float, 3 > Vec3f
Definition: VectorT.hh:125
bool writeMaterial(QString _filename, MeshT &_mesh, int _objId)
writer functions
Definition: FileOBJT.cc:62
Add 2D texture coordinates (vertices, halfedges)
Definition: Attributes.hh:92
Handle for a face entity.
Definition: Handles.hh:146