Developer Documentation
OBJWriter.cc
1 /* ========================================================================= *
2  * *
3  * OpenMesh *
4  * Copyright (c) 2001-2015, RWTH-Aachen University *
5  * Department of Computer Graphics and Multimedia *
6  * All rights reserved. *
7  * www.openmesh.org *
8  * *
9  *---------------------------------------------------------------------------*
10  * This file is part of OpenMesh. *
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 //== INCLUDES =================================================================
46 
47 
48 //STL
49 #include <fstream>
50 #include <limits>
51 
52 // OpenMesh
53 #include <OpenMesh/Core/IO/BinaryHelper.hh>
54 #include <OpenMesh/Core/IO/writer/OBJWriter.hh>
55 #include <OpenMesh/Core/IO/IOManager.hh>
56 #include <OpenMesh/Core/Utils/color_cast.hh>
57 
58 //=== NAMESPACES ==============================================================
59 
60 
61 namespace OpenMesh {
62 namespace IO {
63 
64 
65 //=== INSTANCIATE =============================================================
66 
67 
68 // register the OBJLoader singleton with MeshLoader
70 _OBJWriter_& OBJWriter() { return __OBJWriterinstance; }
71 
72 
73 //=== IMPLEMENTATION ==========================================================
74 
75 
76 _OBJWriter_::_OBJWriter_() { IOManager().register_module(this); }
77 
78 
79 //-----------------------------------------------------------------------------
80 
81 
82 bool
84 write(const std::string& _filename, BaseExporter& _be, Options _opt, std::streamsize _precision) const
85 {
86  std::fstream out(_filename.c_str(), std::ios_base::out );
87 
88  if (!out)
89  {
90  omerr() << "[OBJWriter] : cannot open file "
91  << _filename << std::endl;
92  return false;
93  }
94 
95  // Set precision on output stream. The default is set via IOManager and passed through to all writers.
96  out.precision(_precision);
97 
98  // Set fixed output to avoid problems with programs not reading scientific notation correctly
99  out << std::fixed;
100 
101  {
102 #if defined(WIN32)
103  std::string::size_type dotposition = _filename.find_last_of("\\/");
104 #else
105  std::string::size_type dotposition = _filename.rfind("/");
106 #endif
107 
108  if (dotposition == std::string::npos){
109  path_ = "./";
110  objName_ = _filename;
111  }else{
112  path_ = _filename.substr(0,dotposition+1);
113  objName_ = _filename.substr(dotposition+1);
114  }
115 
116  //remove the file extension
117  dotposition = objName_.find_last_of(".");
118 
119  if(dotposition != std::string::npos)
120  objName_ = objName_.substr(0,dotposition);
121  }
122 
123  bool result = write(out, _be, _opt, _precision);
124 
125  out.close();
126  return result;
127 }
128 
129 //-----------------------------------------------------------------------------
130 
131 size_t _OBJWriter_::getMaterial(OpenMesh::Vec3f _color) const
132 {
133  for (size_t i=0; i < material_.size(); i++)
134  if(material_[i] == _color)
135  return i;
136 
137  //not found add new material
138  material_.push_back( _color );
139  return material_.size()-1;
140 }
141 
142 //-----------------------------------------------------------------------------
143 
144 size_t _OBJWriter_::getMaterial(OpenMesh::Vec4f _color) const
145 {
146  for (size_t i=0; i < materialA_.size(); i++)
147  if(materialA_[i] == _color)
148  return i;
149 
150  //not found add new material
151  materialA_.push_back( _color );
152  return materialA_.size()-1;
153 }
154 
155 //-----------------------------------------------------------------------------
156 
157 bool
158 _OBJWriter_::
159 writeMaterial(std::ostream& _out, BaseExporter& _be, Options _opt) const
160 {
161  OpenMesh::Vec3f c;
162  OpenMesh::Vec4f cA;
163 
164  material_.clear();
165  materialA_.clear();
166 
167  //iterate over faces
168  for (size_t i=0, nF=_be.n_faces(); i<nF; ++i)
169  {
170  //color with alpha
171  if ( _opt.color_has_alpha() ){
172  cA = color_cast<OpenMesh::Vec4f> (_be.colorA( FaceHandle(int(i)) ));
173  getMaterial(cA);
174  }else{
175  //and without alpha
176  c = color_cast<OpenMesh::Vec3f> (_be.color( FaceHandle(int(i)) ));
177  getMaterial(c);
178  }
179  }
180 
181  //write the materials
182  if ( _opt.color_has_alpha() )
183  for (size_t i=0; i < materialA_.size(); i++){
184  _out << "newmtl " << "mat" << i << '\n';
185  _out << "Ka 0.5000 0.5000 0.5000" << '\n';
186  _out << "Kd " << materialA_[i][0] << ' ' << materialA_[i][1] << ' ' << materialA_[i][2] << '\n';
187  _out << "Tr " << materialA_[i][3] << '\n';
188  _out << "illum 1" << '\n';
189  }
190  else
191  for (size_t i=0; i < material_.size(); i++){
192  _out << "newmtl " << "mat" << i << '\n';
193  _out << "Ka 0.5000 0.5000 0.5000" << '\n';
194  _out << "Kd " << material_[i][0] << ' ' << material_[i][1] << ' ' << material_[i][2] << '\n';
195  _out << "illum 1" << '\n';
196  }
197 
198  return true;
199 }
200 
201 //-----------------------------------------------------------------------------
202 
203 
204 bool
206 write(std::ostream& _out, BaseExporter& _be, Options _opt, std::streamsize _precision) const
207 {
208  unsigned int idx;
209  Vec3f v, n;
210  Vec2f t;
211  VertexHandle vh;
212  std::vector<VertexHandle> vhandles;
213  bool useMatrial = false;
214  OpenMesh::Vec3f c;
215  OpenMesh::Vec4f cA;
216 
217  omlog() << "[OBJWriter] : write file\n";
218 
219  _out.precision(_precision);
220 
221  // check exporter features
222  if (!check( _be, _opt))
223  return false;
224 
225  // No binary mode for OBJ
226  if ( _opt.check(Options::Binary) ) {
227  omout() << "[OBJWriter] : Warning, Binary mode requested for OBJ Writer (No support for Binary mode), falling back to standard." << std::endl;
228  }
229 
230  // check for unsupported writer features
231  if (_opt.check(Options::FaceNormal) ) {
232  omerr() << "[OBJWriter] : FaceNormal not supported by OBJ Writer" << std::endl;
233  return false;
234  }
235 
236  // check for unsupported writer features
237  if (_opt.check(Options::VertexColor) ) {
238  omerr() << "[OBJWriter] : VertexColor not supported by OBJ Writer" << std::endl;
239  return false;
240  }
241 
242  //create material file if needed
243  if ( _opt.check(Options::FaceColor) ){
244 
245  std::string matFile = path_ + objName_ + ".mat";
246 
247  std::fstream matStream(matFile.c_str(), std::ios_base::out );
248 
249  if (!matStream)
250  {
251  omerr() << "[OBJWriter] : cannot write material file " << matFile << std::endl;
252 
253  }else{
254  useMatrial = writeMaterial(matStream, _be, _opt);
255 
256  matStream.close();
257  }
258  }
259 
260  // header
261  _out << "# " << _be.n_vertices() << " vertices, ";
262  _out << _be.n_faces() << " faces" << '\n';
263 
264  // material file
265  if (useMatrial && _opt.check(Options::FaceColor) )
266  _out << "mtllib " << objName_ << ".mat" << '\n';
267 
268  std::map<Vec2f,int> texMap;
269  //collect Texturevertices from halfedges
270  if(_opt.check(Options::FaceTexCoord))
271  {
272  std::vector<Vec2f> texCoords;
273  //add all texCoords to map
274  unsigned int num = _be.get_face_texcoords(texCoords);
275  for(size_t i = 0; i < num ; ++i)
276  {
277  texMap[texCoords[i]] = static_cast<int>(i);
278  }
279  }
280 
281  //collect Texture coordinates from vertices
282  if(_opt.check(Options::VertexTexCoord))
283  {
284  for (size_t i=0, nV=_be.n_vertices(); i<nV; ++i)
285  {
286  vh = VertexHandle(static_cast<int>(i));
287  t = _be.texcoord(vh);
288  texMap[t] = static_cast<int>(i);
289  }
290  }
291 
292  // assign each texcoord in the map its id
293  // and write the vt entries
294  if(_opt.check(Options::VertexTexCoord) || _opt.check(Options::FaceTexCoord))
295  {
296  int texCount = 0;
297  for(std::map<Vec2f,int>::iterator it = texMap.begin(); it != texMap.end() ; ++it)
298  {
299  _out << "vt " << it->first[0] << " " << it->first[1] << '\n';
300  it->second = ++texCount;
301  }
302  }
303 
304  // vertex data (point, normals, texcoords)
305  for (size_t i=0, nV=_be.n_vertices(); i<nV; ++i)
306  {
307  vh = VertexHandle(int(i));
308  v = _be.point(vh);
309  n = _be.normal(vh);
310  t = _be.texcoord(vh);
311 
312  _out << "v " << v[0] <<" "<< v[1] <<" "<< v[2] << '\n';
313 
314  if (_opt.check(Options::VertexNormal))
315  _out << "vn " << n[0] <<" "<< n[1] <<" "<< n[2] << '\n';
316  }
317 
318  size_t lastMat = std::numeric_limits<std::size_t>::max();
319 
320  // we do not want to write seperators if we only write vertex indices
321  bool onlyVertices = !_opt.check(Options::VertexTexCoord)
322  && !_opt.check(Options::VertexNormal)
323  && !_opt.check(Options::FaceTexCoord);
324 
325  // faces (indices starting at 1 not 0)
326  for (size_t i=0, nF=_be.n_faces(); i<nF; ++i)
327  {
328 
329  if (useMatrial && _opt.check(Options::FaceColor) ){
330  size_t material = std::numeric_limits<std::size_t>::max();
331 
332  //color with alpha
333  if ( _opt.color_has_alpha() ){
334  cA = color_cast<OpenMesh::Vec4f> (_be.colorA( FaceHandle(int(i)) ));
335  material = getMaterial(cA);
336  } else{
337  //and without alpha
338  c = color_cast<OpenMesh::Vec3f> (_be.color( FaceHandle(int(i)) ));
339  material = getMaterial(c);
340  }
341 
342  // if we are ina a new material block, specify in the file which material to use
343  if(lastMat != material) {
344  _out << "usemtl mat" << material << '\n';
345  lastMat = material;
346  }
347  }
348 
349  _out << "f";
350 
351  _be.get_vhandles(FaceHandle(int(i)), vhandles);
352 
353  for (size_t j=0; j< vhandles.size(); ++j)
354  {
355 
356  // Write vertex index
357  idx = vhandles[j].idx() + 1;
358  _out << " " << idx;
359 
360  if (!onlyVertices) {
361  // write separator
362  _out << "/" ;
363 
364  //write texCoords index from halfedge
365  if(_opt.check(Options::FaceTexCoord))
366  {
367  _out << texMap[_be.texcoord(_be.getHeh(FaceHandle(int(i)),vhandles[j]))];
368  }
369 
370  else
371  {
372  // write vertex texture coordinate index
373  if (_opt.check(Options::VertexTexCoord))
374  _out << texMap[_be.texcoord(vhandles[j])];
375  }
376 
377  // write vertex normal index
378  if ( _opt.check(Options::VertexNormal) ) {
379  // write separator
380  _out << "/" ;
381  _out << idx;
382  }
383  }
384  }
385 
386  _out << '\n';
387  }
388 
389  material_.clear();
390  materialA_.clear();
391 
392  return true;
393 }
394 
395 
396 //=============================================================================
397 } // namespace IO
398 } // namespace OpenMesh
399 //=============================================================================
Handle for a face entity.
Definition: Handles.hh:141
bool write(const std::string &, BaseExporter &, Options, std::streamsize _precision=6) const override
Definition: OBJWriter.cc:84
Has (r) / store (w) vertex colors.
Definition: Options.hh:105
Has (r) / store (w) face colors.
Definition: Options.hh:109
_OBJWriter_ __OBJWriterinstance
Declare the single entity of the OBJ writer.
Definition: OBJWriter.cc:69
virtual HalfedgeHandle getHeh(FaceHandle _fh, VertexHandle _vh) const =0
getHeh returns the HalfEdgeHandle that belongs to the face specified by _fh and has a toVertexHandle ...
Handle for a vertex entity.
Definition: Handles.hh:120
Has (r) / store (w) face texture coordinates.
Definition: Options.hh:110
bool register_module(BaseReader *_bl)
Definition: IOManager.hh:217
Set options for reader/writer modules.
Definition: Options.hh:90
Has (r) / store (w) vertex normals.
Definition: Options.hh:104
_IOManager_ & IOManager()
Definition: IOManager.cc:72
Has (r) / store (w) face normals.
Definition: Options.hh:108
Set binary mode for r/w.
Definition: Options.hh:100
Has (r) / store (w) texture coordinates.
Definition: Options.hh:106