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