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