Developer Documentation
Loading...
Searching...
No Matches
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.
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