Developer Documentation
decimater.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 #if !defined(OM_USE_OSG)
45 # define OM_USE_OSG 0
46 #endif
47 
48 // ----------------------------------------------------------------------------
49 
50 #include <iostream>
51 #include <fstream>
52 #include <sstream>
53 #include <string>
54 #include <memory>
55 #include <map>
56 //--------------------
57 #include <OpenMesh/Core/IO/MeshIO.hh>
58 //--------------------
59 #if OM_USE_OSG
60 # include <OpenMesh/Tools/Kernel_OSG/TriMesh_OSGArrayKernelT.hh>
61 #else
62 # include <OpenMesh/Core/Mesh/TriMesh_ArrayKernelT.hh>
63 #endif
64 #include <OpenMesh/Core/Utils/vector_cast.hh>
65 //--------------------
66 #include <OpenMesh/Tools/Utils/getopt.h>
76 #include <OpenMesh/Tools/Decimater/ModIndependentSetsT.hh>
78 
79 //----------------------------------------------------------------- traits ----
80 
81 #if OM_USE_OSG
83 #else
85 #endif
86 
87 //------------------------------------------------------------------- mesh ----
88 
89 #if OM_USE_OSG
91 #else
93 #endif
94 
95 
96 //-------------------------------------------------------------- decimator ----
97 
99 
100 
101 //---------------------------------------------------------------- globals ----
102 
103 int gverbose = 0;
104 int gdebug = 0;
105 
106 
107 //--------------------------------------------------------------- forwards ----
108 
109 void usage_and_exit(int xcode);
110 
111 
112 //--------------------------------------------------- decimater arguments ----
113 
114 #include "CmdOption.hh"
115 
116 
118 {
119  DecOptions()
120  : n_collapses(0)
121  { }
122 
123  CmdOption<bool> decorate_name;
124  CmdOption<float> n_collapses;
125 
126  CmdOption<float> AR; // Aspect ratio
127  CmdOption<float> EL; // Edge length
128  CmdOption<float> HD; // Hausdorff distance
129  CmdOption<bool> IS; // Independent Sets
130  CmdOption<float> ND; // Normal deviation
131  CmdOption<float> NF; // Normal flipping
132  CmdOption<std::string> PM; // Progressive Mesh
133  CmdOption<float> Q; // Quadrics
134  CmdOption<float> R; // Roundness
135 
136  template <typename T>
137  bool init( CmdOption<T>& _o, const std::string& _val )
138  {
139  if ( _val.empty() )
140  _o.enable();
141  else
142  {
143  std::istringstream istr( _val );
144 
145  T v;
146 
147  if ( (istr >> v).fail() )
148  return false;
149 
150  _o = v;
151  }
152  return true;
153  }
154 
155 
156  bool parse_argument( const std::string& arg )
157  {
158  std::string::size_type pos = arg.find(':');
159 
160  std::string name;
161  std::string value;
162 
163  if (pos == std::string::npos)
164  name = arg;
165  else
166  {
167  name = arg.substr(0, pos);
168  value = arg.substr(pos+1, arg.size());
169  }
170  strip(name);
171  strip(value);
172 
173  if (name == "AR") return init(AR, value);
174  if (name == "EL") return init(EL, value);
175  if (name == "HD") return init(HD, value);
176  if (name == "IS") return init(IS, value);
177  if (name == "ND") return init(ND, value);
178  if (name == "NF") return init(NF, value);
179  if (name == "PM") return init(PM, value);
180  if (name == "Q") return init(Q, value);
181  if (name == "R") return init(R, value);
182  return false;
183  }
184 
185  std::string& strip(std::string & line)
186  {
187  std::string::size_type pos = 0;
188 
189  pos = line.find_last_not_of(" \t");
190 
191  if ( pos!=0 && pos!=std::string::npos )
192  {
193  ++pos;
194  line.erase( pos, line.length()-pos );
195  }
196 
197  pos = line.find_first_not_of(" \t");
198  if ( pos!=0 && pos!=std::string::npos )
199  {
200  line.erase(0,pos);
201  }
202 
203  return line;
204  }
205 
206 };
207 
208 //----------------------------------------------------- decimater wrapper ----
209 //
210 template <typename Mesh, typename DecimaterType>
211 bool
212 decimate(const std::string &_ifname,
213  const std::string &_ofname,
214  DecOptions &_opt)
215 {
216  using namespace std;
217 
218  Mesh mesh;
219  OpenMesh::IO::Options readopt;
221 
222  // ---------------------------------------- read source mesh
223  {
224  if (gverbose)
225  clog << "source mesh: ";
226  bool rc;
227 
228  if (gverbose)
229  clog << _ifname << endl;
230  if ( !(rc = OpenMesh::IO::read_mesh(mesh, _ifname, readopt)) )
231  {
232  cerr << " ERROR: read failed!" << endl;
233  return rc;
234  }
235  }
236 
237  // ---------------------------------------- do some decimation
238  {
239  // ---- 0 - For module NormalFlipping one needs face normals
240 
241  if ( !readopt.check( OpenMesh::IO::Options::FaceNormal ) )
242  {
243  if ( !mesh.has_face_normals() )
244  mesh.request_face_normals();
245 
246  if (gverbose)
247  clog << " updating face normals" << endl;
248  mesh.update_face_normals();
249  }
250 
251  // ---- 1 - create decimater instance
252  DecimaterType decimater( mesh );
253 
254  // ---- 2 - register modules
255  if (gverbose)
256  clog << " register modules" << endl;
257 
258 
259 
261 
262  if (_opt.AR.is_enabled())
263  {
264  decimater.add(modAR);
265  if (_opt.AR.has_value())
266  decimater.module( modAR ).set_aspect_ratio( _opt.AR ) ;
267  }
268 
270 
271  if (_opt.EL.is_enabled())
272  {
273  decimater.add(modEL);
274  if (_opt.EL.has_value())
275  decimater.module( modEL ).set_edge_length( _opt.EL ) ;
276  decimater.module(modEL).set_binary(false);
277  }
278 
279  typename OpenMesh::Decimater::ModHausdorffT <Mesh>::Handle modHD;
280 
281  if (_opt.HD.is_enabled())
282  {
283  decimater.add(modHD);
284  if (_opt.HD.has_value())
285  decimater.module( modHD ).set_tolerance( _opt.HD ) ;
286 
287  }
288 
290 
291  if ( _opt.IS.is_enabled() )
292  decimater.add(modIS);
293 
295 
296  if (_opt.ND.is_enabled())
297  {
298  decimater.add(modND);
299  if (_opt.ND.has_value())
300  decimater.module( modND ).set_normal_deviation( _opt.ND );
301  decimater.module( modND ).set_binary(false);
302  }
303 
305 
306  if (_opt.NF.is_enabled())
307  {
308  decimater.add(modNF);
309  if (_opt.NF.has_value())
310  decimater.module( modNF ).set_max_normal_deviation( _opt.NF );
311  }
312 
313 
315 
316  if ( _opt.PM.is_enabled() )
317  decimater.add(modPM);
318 
320 
321  if (_opt.Q.is_enabled())
322  {
323  decimater.add(modQ);
324  if (_opt.Q.has_value())
325  decimater.module( modQ ).set_max_err( _opt.Q );
326  decimater.module(modQ).set_binary(false);
327  }
328 
330 
331  if ( _opt.R.is_enabled() )
332  {
333  decimater.add( modR );
334  if ( _opt.R.has_value() )
335  decimater.module( modR ).set_min_angle( _opt.R,
336  !modQ.is_valid() ||
337  !decimater.module(modQ).is_binary());
338  }
339 
340  // ---- 3 - initialize decimater
341 
342  if (gverbose)
343  clog << "initializing mesh" << endl;
344 
345  {
346  bool rc;
347  timer.start();
348  rc = decimater.initialize();
349  timer.stop();
350  if (!rc)
351  {
352  std::cerr << " initializing failed!" << std::endl;
353  std::cerr << " maybe no priority module or more than one were defined!" << std::endl;
354  return false;
355  }
356  }
357  if (gverbose)
358  std::clog << " Elapsed time: " << timer.as_string() << std::endl;
359 
360  if (gverbose)
361  decimater.info( clog );
362 
363  // ---- 4 - do it
364 
365  if (gverbose)
366  {
367  std::clog << "decimating" << std::endl;
368  std::clog << " # vertices: " << mesh.n_vertices() << std::endl;
369  }
370 
371  float nv_before = float(mesh.n_vertices());
372 
373  timer.start();
374  size_t rc = 0;
375  if (_opt.n_collapses < 0.0)
376  rc = decimater.decimate_to( size_t(-_opt.n_collapses) );
377  else if (_opt.n_collapses >= 1.0 || _opt.n_collapses == 0.0)
378  rc = decimater.decimate( size_t(_opt.n_collapses) );
379  else if (_opt.n_collapses > 0.0f)
380  rc = decimater.decimate_to(size_t(mesh.n_vertices()*_opt.n_collapses));
381  timer.stop();
382 
383  // ---- 5 - write progmesh file for progviewer (before garbage collection!)
384 
385  if ( _opt.PM.has_value() )
386  decimater.module(modPM).write( _opt.PM );
387 
388  // ---- 6 - throw away all tagged edges
389 
390  mesh.garbage_collection();
391 
392  if (gverbose)
393  {
394  std::clog << " # executed collapses: " << rc << std::endl;
395  std::clog << " # vertices: " << mesh.n_vertices() << ", "
396  << ( 100.0*mesh.n_vertices()/nv_before ) << "%\n";
397  std::clog << " Elapsed time: " << timer.as_string() << std::endl;
398  std::clog << " collapses/s : " << rc/timer.seconds() << std::endl;
399  }
400 
401  }
402 
403  // write resulting mesh
404  if ( ! _ofname.empty() )
405  {
406  std::string ofname(_ofname);
407 
408  std::string::size_type pos = ofname.rfind('.');
409  if (pos == std::string::npos)
410  {
411  ofname += ".off";
412  pos = ofname.rfind('.');
413  }
414 
415  if ( _opt.decorate_name.is_enabled() )
416  {
417  std::stringstream s; s << mesh.n_vertices();
418  std::string n; s >> n;
419  ofname.insert( pos, "-");
420  ofname.insert(++pos, n );
421  }
422 
423  OpenMesh::IO::Options writeopt;
424 
425  //opt += OpenMesh::IO::Options::Binary;
426 
427  if ( !OpenMesh::IO::write_mesh(mesh, ofname, writeopt ) )
428  {
429  std::cerr << " Cannot write decimated mesh to file '"
430  << ofname << "'\n";
431  return false;
432  }
433  std::clog << " Exported decimated mesh to file '" << ofname << "'\n";
434  }
435 
436  return true;
437 }
438 
439 //------------------------------------------------------------------ main -----
440 
441 int main(int argc, char* argv[])
442 {
443  std::string ifname, ofname;
444 
445  DecOptions opt;
446 
447  //
448 #if OM_USE_OSG
449  osg::osgInit( argc, argv );
450 #endif
451 
452  //---------------------------------------- parse command line
453  {
454  int c;
455 
456  while ( (c=getopt( argc, argv, "dDhi:M:n:o:v")) != -1 )
457  {
458  switch (c)
459  {
460  case 'D': opt.decorate_name = true; break;
461  case 'd': gdebug = true; break;
462  case 'h': usage_and_exit(0); break;
463  case 'i': ifname = optarg; break;
464  case 'M': opt.parse_argument( optarg ); break;
465  case 'n': opt.n_collapses = float(atof(optarg)); break;
466  case 'o': ofname = optarg; break;
467  case 'v': gverbose = true; break;
468  case '?':
469  default:
470  std::cerr << "FATAL: cannot process command line option!"
471  << std::endl;
472  exit(-1);
473  }
474  }
475  }
476 
477  //----------------------------------------
478 
479  if ( (-1.0f < opt.n_collapses) && (opt.n_collapses < 0.0f) )
480  {
481  std::cerr << "Error: Option -n: invalid value argument!" << std::endl;
482  usage_and_exit(2);
483  }
484 
485  //----------------------------------------
486 
487  if (gverbose)
488  {
489  std::clog << " Input file: " << ifname << std::endl;
490  std::clog << " Output file: " << ofname << std::endl;
491  std::clog << " #collapses: " << opt.n_collapses << std::endl;
492  }
493 
494 
495  //----------------------------------------
496 
497 
498 
499  if (gverbose)
500  {
501  std::clog << "Begin decimation" << std::endl;
502  }
503 
504  bool rc = decimate<ArrayTriMesh, Decimater>( ifname, ofname, opt );
505 
506  if (gverbose)
507  {
508  if (!rc)
509  std::clog << "Decimation failed!" << std::endl;
510  else
511  std::clog << "Decimation done." << std::endl;
512  }
513 
514  //----------------------------------------
515  return 0;
516 }
517 
518 
519 //-----------------------------------------------------------------------------
520 
521 void usage_and_exit(int xcode)
522 {
523  std::string errmsg;
524 
525  switch(xcode)
526  {
527  case 1: errmsg = "Option not supported!"; break;
528  case 2: errmsg = "Invalid output file format!"; break;
529  }
530 
531  std::cerr << std::endl;
532  if (xcode) {
533  std::cerr << "Error " << xcode << ": " << errmsg << std::endl << std::endl;
534  }
535  std::cerr << "Usage: decimator [Options] -i input-file -o output-file\n"
536  << " Decimating a mesh using quadrics and normal flipping.\n" << std::endl;
537  std::cerr << "Options\n" << std::endl;
538  std::cerr << " -M \"{Module-Name}[:Value]}\"\n"
539  << " Use named module with eventually given parameterization\n"
540  << " Several modules can also be used in order to introduce further constraints\n"
541  << " Note that -M has to be given before each new module \n"
542  << " An example with ModQuadric as a priority module\n"
543  << " and ModRoundness as a binary module could look like this:\n"
544  << " commandlineDecimater -M Q -M R:40.0 -n 0.1 -i inputfile.obj -o outputfile.obj\n" << std::endl;
545  std::cerr << " -n <N>\n"
546  << " N >= 1: do N halfedge collapses.\n"
547  << " N <=-1: decimate down to |N| vertices.\n"
548  << " 0 < N < 1: decimate down to N%.\n" << std::endl;
549  std::cerr << std::endl;
550  std::cerr << "Modules:\n\n";
551  std::cerr << " AR[:ratio] - ModAspectRatio\n";
552  std::cerr << " EL[:legth] - ModEdgeLength*\n";
553  std::cerr << " HD[:distance] - ModHausdorff\n";
554  std::cerr << " IS - ModIndependentSets\n";
555  std::cerr << " ND[:angle] - ModNormalDeviation*\n";
556  std::cerr << " NF[:angle] - ModNormalFlipping\n";
557  std::cerr << " PM[:file name] - ModProgMesh\n";
558  std::cerr << " Q[:error] - ModQuadric*\n";
559  std::cerr << " R[:angle] - ModRoundness\n";
560  std::cerr << " 0 < angle < 60\n";
561  std::cerr << " *: priority module. Decimater needs one of them (not more).\n";
562 
563  exit( xcode );
564 }
565 
566 
567 
568 // end of file
569 //=============================================================================
void set_aspect_ratio(float _f)
set aspect ratio
bool write_mesh(const Mesh &_mesh, const std::string &_filename, Options _opt=Options::Default, std::streamsize _precision=6)
Write a mesh to the file _filename.
Definition: MeshIO.hh:207
STL namespace.
Use Normal deviation to control decimation.
void set_normal_deviation(Scalar _s)
Set normal deviation ( 0 .. 360 )
std::string as_string(Format format=Automatic)
double seconds(void) const
Returns measured time in seconds, if the timer is in state &#39;Stopped&#39;.
void stop(void)
Stop measurement.
bool read_mesh(Mesh &_mesh, const std::string &_filename)
Read a mesh from file _filename.
Definition: MeshIO.hh:112
void set_max_err(double _err, bool _binary=true)
Definition: ModQuadricT.hh:153
Mesh decimation module computing collapse priority based on error quadrics.
Definition: ModQuadricT.hh:75
Use Roundness of triangles to control decimation.
Set options for reader/writer modules.
Definition: Options.hh:90
bool is_binary(void) const
Returns true if criteria returns a binary value.
Definition: ModBaseT.hh:220
void update_face_normals()
Update normal vectors for all faces.
Use edge length to control decimation.
Has (r) / store (w) face normals.
Definition: Options.hh:108
void set_edge_length(typename Mesh::Scalar _f)
set edge_length
void start(void)
Start measurement.
Use aspect ratio to control decimation.