Developer Documentation
Loading...
Searching...
No Matches
OBJWriter.cc
1/* ========================================================================= *
2 * *
3 * OpenMesh *
4 * Copyright (c) 2001-2025, 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
61namespace OpenMesh {
62namespace 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
82bool
84write(const std::string& _filename, BaseExporter& _be, const Options& _writeOptions, 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, _writeOptions, _precision);
124
125 out.close();
126 return result;
127}
128
129//-----------------------------------------------------------------------------
130
131size_t _OBJWriter_::getMaterial(OpenMesh::Vec3f _color) const
132{
133 auto idx_it = material_idx_.find(_color);
134 if (idx_it != material_idx_.end()) {
135 return idx_it->second;
136 } else {
137 size_t idx = material_.size();
138 material_.push_back(_color);
139 material_idx_[_color] = idx;
140
141 return idx;
142 }
143}
144
145//-----------------------------------------------------------------------------
146
147size_t _OBJWriter_::getMaterial(OpenMesh::Vec4f _color) const
148{
149 auto idx_it = materialA_idx_.find(_color);
150 if (idx_it != materialA_idx_.end()) {
151 return idx_it->second;
152 } else {
153 size_t idx = materialA_.size();
154 materialA_.push_back(_color);
155 materialA_idx_[_color] = idx;
156 return idx;
157 }
158}
159
160//-----------------------------------------------------------------------------
161
162bool
163_OBJWriter_::
164writeMaterial(std::ostream& _out, BaseExporter& _be, Options _opt) const
165{
168
169 material_.clear();
170 material_idx_.clear();
171 materialA_.clear();
172 materialA_idx_.clear();
173
174 //iterate over faces
175 for (size_t i=0, nF=_be.n_faces(); i<nF; ++i)
176 {
177 //color with alpha
178 if ( _opt.color_has_alpha() ){
179 cA = color_cast<OpenMesh::Vec4f> (_be.colorA( FaceHandle(int(i)) ));
180 getMaterial(cA);
181 }else{
182 //and without alpha
183 c = color_cast<OpenMesh::Vec3f> (_be.color( FaceHandle(int(i)) ));
184 getMaterial(c);
185 }
186 }
187
188 //write the materials
189 if ( _opt.color_has_alpha() )
190 for (size_t i=0; i < materialA_.size(); i++){
191 _out << "newmtl " << "mat" << i << '\n';
192 _out << "Ka 0.5000 0.5000 0.5000" << '\n';
193 _out << "Kd " << materialA_[i][0] << ' ' << materialA_[i][1] << ' ' << materialA_[i][2] << '\n';
194 _out << "Tr " << materialA_[i][3] << '\n';
195 _out << "illum 1" << '\n';
196 }
197 else
198 for (size_t i=0; i < material_.size(); i++){
199 _out << "newmtl " << "mat" << i << '\n';
200 _out << "Ka 0.5000 0.5000 0.5000" << '\n';
201 _out << "Kd " << material_[i][0] << ' ' << material_[i][1] << ' ' << material_[i][2] << '\n';
202 _out << "illum 1" << '\n';
203 }
204
205 if (_opt.texture_file != "") {
206 _out << "map_Kd " << _opt.texture_file << std::endl;
207 }
208 return true;
209}
210
211//-----------------------------------------------------------------------------
212
213
214bool
216write(std::ostream& _out, BaseExporter& _be, const Options& _writeOptions, std::streamsize _precision) const
217{
218 unsigned int idx;
219 VertexHandle vh;
220 std::vector<VertexHandle> vhandles;
221 bool useMatrial = false;
224
225 omlog() << "[OBJWriter] : write file\n";
226
227 _out.precision(_precision);
228
229 // check exporter features
230 if (!check( _be, _writeOptions)) {
231 return false;
232 }
233
234
235 // No binary mode for OBJ
236 if ( _writeOptions.check(Options::Binary) ) {
237 omout() << "[OBJWriter] : Warning, Binary mode requested for OBJ Writer (No support for Binary mode), falling back to standard." << std::endl;
238 }
239
240 // check for unsupported writer features
241 if (_writeOptions.check(Options::FaceNormal) ) {
242 omerr() << "[OBJWriter] : FaceNormal not supported by OBJ Writer" << std::endl;
243 return false;
244 }
245
246 // check for unsupported writer features
247 if (_writeOptions.check(Options::VertexColor) ) {
248 omerr() << "[OBJWriter] : VertexColor not supported by OBJ Writer" << std::endl;
249 return false;
250 }
251
252 //create material file if needed
253 if ( _writeOptions.check(Options::FaceColor) || _writeOptions.texture_file != ""){
254
255 std::string matFile = path_ + objName_ + _writeOptions.material_file_extension;
256
257 std::fstream matStream(matFile.c_str(), std::ios_base::out );
258
259 if (!matStream)
260 {
261 omerr() << "[OBJWriter] : cannot write material file " << matFile << std::endl;
262
263 }else{
264 useMatrial = writeMaterial(matStream, _be, _writeOptions);
265
266 matStream.close();
267 }
268 }
269
270 // header
271 _out << "# " << _be.n_vertices() << " vertices, ";
272 _out << _be.n_faces() << " faces" << '\n';
273
274 // material file
275 if ( (useMatrial && _writeOptions.check(Options::FaceColor)) || _writeOptions.texture_file != "")
276 _out << "mtllib " << objName_ << _writeOptions.material_file_extension << '\n';
277
278 std::map<Vec2f,int> texMap;
279 //collect Texturevertices from halfedges
280 if(_writeOptions.check(Options::FaceTexCoord))
281 {
282 std::vector<Vec2f> texCoords;
283 //add all texCoords to map
284 unsigned int num = _be.get_face_texcoords(texCoords);
285 for(size_t i = 0; i < num ; ++i)
286 {
287 texMap[texCoords[i]] = static_cast<int>(i);
288 }
289 }
290
291 //collect Texture coordinates from vertices
292 if(_writeOptions.check(Options::VertexTexCoord))
293 {
294 for (size_t i=0, nV=_be.n_vertices(); i<nV; ++i)
295 {
296 vh = VertexHandle(static_cast<int>(i));
297 Vec2f t = _be.texcoord(vh);
298 texMap[t] = static_cast<int>(i);
299 }
300 }
301
302 // assign each texcoord in the map its id
303 // and write the vt entries
304 if(_writeOptions.check(Options::VertexTexCoord) || _writeOptions.check(Options::FaceTexCoord))
305 {
306 int texCount = 0;
307 for(std::map<Vec2f,int>::iterator it = texMap.begin(); it != texMap.end() ; ++it)
308 {
309 _out << "vt " << it->first[0] << " " << it->first[1] << '\n';
310 it->second = ++texCount;
311 }
312 }
313
314 const bool normal_double = _be.is_normal_double();
315 const bool point_double = _be.is_point_double();
316 for (size_t i=0, nV=_be.n_vertices(); i<nV; ++i)
317 {
318 vh = VertexHandle(int(i));
319 if (point_double) {
320 auto v = _be.pointd(vh);
321 _out << "v " << v[0] <<" "<< v[1] <<" "<< v[2] << '\n';
322 } else {
323 auto v = _be.point(vh);
324 _out << "v " << v[0] <<" "<< v[1] <<" "<< v[2] << '\n';
325 }
326 if (_writeOptions.check(Options::VertexNormal)) {
327 if (normal_double) {
328 auto n = _be.normald(vh);
329 _out << "vn " << n[0] <<" "<< n[1] <<" "<< n[2] << '\n';
330 } else {
331 auto n = _be.normal(vh);
332 _out << "vn " << n[0] <<" "<< n[1] <<" "<< n[2] << '\n';
333 }
334 }
335 }
336
337 size_t lastMat = std::numeric_limits<std::size_t>::max();
338
339 // we do not want to write seperators if we only write vertex indices
340 bool onlyVertices = !_writeOptions.check(Options::VertexTexCoord)
341 && !_writeOptions.check(Options::VertexNormal)
342 && !_writeOptions.check(Options::FaceTexCoord);
343
344 // faces (indices starting at 1 not 0)
345 for (size_t i=0, nF=_be.n_faces(); i<nF; ++i)
346 {
347
348 if (useMatrial && _writeOptions.check(Options::FaceColor) ){
349 size_t material;
350
351 //color with alpha
352 if ( _writeOptions.color_has_alpha() ){
353 cA = color_cast<OpenMesh::Vec4f> (_be.colorA( FaceHandle(int(i)) ));
354 material = getMaterial(cA);
355 } else{
356 //and without alpha
357 c = color_cast<OpenMesh::Vec3f> (_be.color( FaceHandle(int(i)) ));
358 material = getMaterial(c);
359 }
360
361 // if we are ina a new material block, specify in the file which material to use
362 if(lastMat != material) {
363 _out << "usemtl mat" << material << '\n';
364 lastMat = material;
365 }
366 }
367
368 _out << "f";
369
370 _be.get_vhandles(FaceHandle(int(i)), vhandles);
371
372 for (size_t j=0; j< vhandles.size(); ++j)
373 {
374
375 // Write vertex index
376 idx = vhandles[j].idx() + 1;
377 _out << " " << idx;
378
379 if (!onlyVertices) {
380 // write separator
381 _out << "/" ;
382
383 //write texCoords index from halfedge
384 if(_writeOptions.check(Options::FaceTexCoord))
385 {
386 _out << texMap[_be.texcoord(_be.getHeh(FaceHandle(int(i)),vhandles[j]))];
387 }
388
389 else
390 {
391 // write vertex texture coordinate index
392 if (_writeOptions.check(Options::VertexTexCoord))
393 _out << texMap[_be.texcoord(vhandles[j])];
394 }
395
396 // write vertex normal index
397 if ( _writeOptions.check(Options::VertexNormal) ) {
398 // write separator
399 _out << "/" ;
400 _out << idx;
401 }
402 }
403 }
404
405 _out << '\n';
406 }
407
408 material_.clear();
409 material_idx_.clear();
410 materialA_.clear();
411 materialA_idx_.clear();
412
413 return true;
414}
415
416
417//=============================================================================
418} // namespace IO
419} // namespace OpenMesh
420//=============================================================================
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 ...
Set options for reader/writer modules.
Definition Options.hh:92
std::string material_file_extension
Definition Options.hh:126
@ FaceNormal
Has (r) / store (w) face normals.
Definition Options.hh:109
@ FaceColor
Has (r) / store (w) face colors.
Definition Options.hh:110
@ FaceTexCoord
Has (r) / store (w) face texture coordinates.
Definition Options.hh:111
@ Binary
Set binary mode for r/w.
Definition Options.hh:101
@ VertexNormal
Has (r) / store (w) vertex normals.
Definition Options.hh:105
@ VertexTexCoord
Has (r) / store (w) texture coordinates.
Definition Options.hh:107
@ VertexColor
Has (r) / store (w) vertex colors.
Definition Options.hh:106
std::string texture_file
Definition Options.hh:122
bool register_module(BaseReader *_bl)
Definition IOManager.hh:217
bool write(const std::string &, BaseExporter &, const Options &_writeOptions, std::streamsize _precision=6) const override
Definition OBJWriter.cc:84
_IOManager_ & IOManager()
Definition IOManager.cc:72
_OBJWriter_ __OBJWriterinstance
Declare the single entity of the OBJ writer.
Definition OBJWriter.cc:69
Handle for a face entity.
Definition Handles.hh:142
Handle for a vertex entity.
Definition Handles.hh:121