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