Developer Documentation
decimater.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#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
103int gverbose = 0;
104int gdebug = 0;
105
106
107//--------------------------------------------------------------- forwards ----
108
109void 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//
210template <typename Mesh, typename DecimaterType>
211bool
212decimate(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
441int 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 std::clog << "Begin decimation" << std::endl;
498 }
499
500 bool rc = decimate<ArrayTriMesh, Decimater>( ifname, ofname, opt );
501
502 if (gverbose)
503 {
504 if (!rc)
505 std::clog << "Decimation failed!" << std::endl;
506 else
507 std::clog << "Decimation done." << std::endl;
508 }
509
510 //----------------------------------------
511
512 return 0;
513}
514
515
516//-----------------------------------------------------------------------------
517
518void usage_and_exit(int xcode)
519{
520 std::string errmsg;
521
522 switch(xcode)
523 {
524 case 1: errmsg = "Option not supported!"; break;
525 case 2: errmsg = "Invalid output file format!"; break;
526 }
527
528 std::cerr << std::endl;
529 if (xcode) {
530 std::cerr << "Error " << xcode << ": " << errmsg << std::endl << std::endl;
531 }
532 std::cerr << "Usage: decimator [Options] -i input-file -o output-file\n"
533 << " Decimating a mesh using quadrics and normal flipping.\n" << std::endl;
534 std::cerr << "Options\n" << std::endl;
535 std::cerr << " -M \"{Module-Name}[:Value]}\"\n"
536 << " Use named module with eventually given parameterization\n"
537 << " Several modules can also be used in order to introduce further constraints\n"
538 << " Note that -M has to be given before each new module \n"
539 << " An example with ModQuadric as a priority module\n"
540 << " and ModRoundness as a binary module could look like this:\n"
541 << " commandlineDecimater -M Q -M R:40.0 -n 0.1 -i inputfile.obj -o outputfile.obj\n" << std::endl;
542 std::cerr << " -n <N>\n"
543 << " N >= 1: do N halfedge collapses.\n"
544 << " N <=-1: decimate down to |N| vertices.\n"
545 << " 0 < N < 1: decimate down to N%.\n" << std::endl;
546 std::cerr << std::endl;
547 std::cerr << "Modules:\n\n";
548 std::cerr << " AR[:ratio] - ModAspectRatio\n";
549 std::cerr << " EL[:legth] - ModEdgeLength*\n";
550 std::cerr << " HD[:distance] - ModHausdorff\n";
551 std::cerr << " IS - ModIndependentSets\n";
552 std::cerr << " ND[:angle] - ModNormalDeviation*\n";
553 std::cerr << " NF[:angle] - ModNormalFlipping\n";
554 std::cerr << " PM[:file name] - ModProgMesh\n";
555 std::cerr << " Q[:error] - ModQuadric*\n";
556 std::cerr << " R[:angle] - ModRoundness\n";
557 std::cerr << " 0 < angle < 60\n";
558 std::cerr << " *: priority module. Decimater needs one of them (not more).\n";
559
560 exit( xcode );
561}
562
563
564
565// end of file
566//=============================================================================
Use aspect ratio to control decimation.
Use edge length to control decimation.
Use Normal deviation to control decimation.
Mesh decimation module computing collapse priority based on error quadrics.
Definition: ModQuadricT.hh:76
Use Roundness of triangles to control decimation.
Set options for reader/writer modules.
Definition: Options.hh:92
@ FaceNormal
Has (r) / store (w) face normals.
Definition: Options.hh:109
void update_face_normals()
Update normal vectors for all faces.
void stop(void)
Stop measurement.
double seconds(void) const
Returns measured time in seconds, if the timer is in state 'Stopped'.
std::string as_string(Format format=Automatic)
void start(void)
Start 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:190
bool read_mesh(Mesh &_mesh, const std::string &_filename)
Read a mesh from file _filename.
Definition: MeshIO.hh:95