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