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
82 OpenMesh::Vec4f c;
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
LOGERR
Definition: LoggingInterface.hh:96
OpenMesh::FaceHandle
Handle for a face entity.
Definition: Handles.hh:141
OpenMesh::VectorT< float, 4 >
OpenMesh::Attributes::TexCoord2D
Add 2D texture coordinates (vertices, halfedges)
Definition: Attributes.hh:87
FileOBJPlugin::materials_
MaterialList materials_
List that contains the material properties.
Definition: FileOBJ.hh:200
ACG::Vec3f
VectorT< float, 3 > Vec3f
Definition: VectorT.hh:119
FileOBJPlugin::writeMaterial
bool writeMaterial(QString _filename, MeshT &_mesh, int _objId)
writer functions
Definition: FileOBJT_impl.hh:56
OpenMesh::FPropHandleT
Definition: Property.hh:529