Developer Documentation
RemesherPlugin.cc
1 /*===========================================================================*\
2 * *
3 * OpenFlipper *
4  * Copyright (c) 2001-2015, RWTH-Aachen University *
5  * Department of Computer Graphics and Multimedia *
6  * All rights reserved. *
7  * www.openflipper.org *
8  * *
9  *---------------------------------------------------------------------------*
10  * This file is part of OpenFlipper. *
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 * $LastChangedBy$ *
46 * $Date$ *
47 * *
48 \*===========================================================================*/
49 
50 
51 #include "RemesherPlugin.hh"
52 
53 #include "Algorithms/AdaptiveRemesherT.hh"
54 #include "Algorithms/UniformRemesherT.hh"
55 
56 /*
57  * STILL \TODO:
58  *
59  * - Emit status updates when remeshing
60  */
61 
62 // ----------------------------------------------------------------------------------------
63 
64 RemesherPlugin::RemesherPlugin() :
65 progress_(0) {
66 
67  if ( OpenFlipper::Options::gui() ) {
68  progress_ = new ProgressEmitter();
69 
70  connect(progress_, SIGNAL(signalJobState(QString,int)), this, SIGNAL(setJobState(QString,int)), Qt::QueuedConnection);
71  connect(progress_, SIGNAL(changeDescription(QString,QString)), this, SIGNAL(setJobDescription(QString,QString)), Qt::QueuedConnection);
72  }
73 }
74 
75 // ----------------------------------------------------------------------------------------
76 
77 RemesherPlugin::~RemesherPlugin() {
78  if ( OpenFlipper::Options::gui() ) {
79  delete progress_;
80  }
81 }
82 
83 // ----------------------------------------------------------------------------------------
84 
87 
88  emit setSlotDescription("adaptiveRemeshing(int,double,double,double,uint,bool)", "Adaptive Remeshing with vertex selection",
89  QString("object_id,error,min_edge_length,max_edge_length,iterations,use_projection").split(","),
90  QString("id of an object,error,minimal target edge length,maximal target edge length,iterations,use projection method").split(","));
91  emit setSlotDescription("adaptiveRemeshing(int,double,double,double,uint)", "Adaptive Remeshing with vertex selection and projection method",
92  QString("object_id,error,min_edge_length,max_edge_length,iterations").split(","),
93  QString("id of an object,error,minimal target edge length,maximal target edge length,iterations").split(","));
94 
95  emit setSlotDescription("adaptiveRemeshingFaceSelection(int,double,double,double,uint,bool)", "Adaptive Remeshing with face selection",
96  QString("object_id,error,min_edge_length,max_edge_length,iterations,use_projection").split(","),
97  QString("id of an object,error,minimal target edge length,maximal target edge length,iterations,use projection method").split(","));
98  emit setSlotDescription("adaptiveRemeshingFaceSelection(int,double,double,double,uint)", "Adaptive Remeshing with face selection and projection method",
99  QString("object_id,error,min_edge_length,max_edge_length,iterations").split(","),
100  QString("id of an object,error,minimal target edge length,maximal target edge length,iterations").split(","));
101 
102 
103  emit setSlotDescription("uniformRemeshing(int,double,uint,uint,bool)", "Uniform Remeshing with vertex selection",
104  QString("object_id,edge_length,iterations,area_iterations,use_projection").split(","),
105  QString("id of an object,target edge length,iterations,area iterations,use projection method").split(","));
106  emit setSlotDescription("uniformRemeshing(int,double,uint,uint)", "Uniform Remeshing with vertex selection and projection method",
107  QString("object_id,edge_length,iterations,area_iterations").split(","),
108  QString("id of an object,target edge length,iterations,area iterations").split(","));
109 
110  emit setSlotDescription("uniformRemeshingFaceSelection(int,double,uint,uint,bool)", "Uniform Remeshing with face selection",
111  QString("object_id,edge_length,iterations,area_iterations,use_projection").split(","),
112  QString("id of an object,target edge length,iterations,area iterations,use projection method").split(","));
113  emit setSlotDescription("uniformRemeshingFaceSelection(int,double,uint,uint)", "Uniform Remeshing with face selection and projection method",
114  QString("object_id,edge_length,iterations,area_iterations").split(","),
115  QString("id of an object,target edge length,iterations,area iterations").split(","));
116 }
117 
118 // ----------------------------------------------------------------------------------------
119 
122 
123  if ( OpenFlipper::Options::gui() ) {
124  tool_ = new RemesherToolBox();
125 
126  // Connect buttons
127  connect(tool_->adaptive_button, SIGNAL(clicked()), this, SLOT(adaptiveRemeshingButtonClicked()) );
128  //connect(tool_->anisotropic_button, SIGNAL(clicked()), this, SLOT(anisotropicRemeshingButtonClicked()) );
129  connect(tool_->uniform_button, SIGNAL(clicked()), this, SLOT(uniformRemeshingButtonClicked()) );
130 
131  connect(tool_->adaptive_initial_values, SIGNAL(clicked()), this, SLOT(computeInitValues()));
132  connect(tool_->uniform_initial_values, SIGNAL(clicked()), this, SLOT(computeInitValues()));
133 
134  tool_->tabWidget->setCurrentIndex(0);
135 
136  toolIcon_ = new QIcon(OpenFlipper::Options::iconDirStr()+OpenFlipper::Options::dirSeparator()+"remesher.png");
137  emit addToolbox( tr("Remesher") , tool_ , toolIcon_);
138  }
139 
140  connect(this, SIGNAL( finishJob(QString)), this, SLOT(threadFinished(QString)), Qt::QueuedConnection);
141 }
142 
143 // ----------------------------------------------------------------------------------------
144 
145 void RemesherPlugin::threadFinished(QString _jobId) {
146 
148  o_it != PluginFunctions::objectsEnd(); ++o_it) {
149 
150  if ( operation_ == REMESH_ADAPTIVE ) {
151  emit updatedObject(o_it->id(), UPDATE_TOPOLOGY );
152  emit createBackup(o_it->id(), "Adaptive remeshing", UPDATE_TOPOLOGY);
153  } else if ( operation_ == REMESH_UNIFORM ) {
154  emit updatedObject(o_it->id(), UPDATE_TOPOLOGY );
155  emit createBackup(o_it->id(), "Uniform remeshing", UPDATE_TOPOLOGY);
156  }
157  }
158 
159  // Detach job from progress emitter
160  progress_->detachJob();
161 }
162 
163 // ----------------------------------------------------------------------------------------
164 
166 
167  if(OpenFlipper::Options::nogui()) return;
168 
169  double mean_edge = 0.0;
170  double max_feature_angle = 0.0;
171 
172  //read one target objects
174  o_it != PluginFunctions::objectsEnd(); ++o_it) {
175 
176  if(o_it->dataType() == DATA_TRIANGLE_MESH) {
177 
178  TriMesh* mesh = PluginFunctions::triMesh(o_it->id());
179  if(!mesh) return;
180 
181  mesh->update_face_normals();
182 
183  for(TriMesh::EdgeIter e_it = mesh->edges_begin(); e_it != mesh->edges_end(); ++e_it) {
184 
185  TriMesh::HalfedgeHandle he = mesh->halfedge_handle(*e_it, 0);
186 
187  ACG::Vec3d vec_e = mesh->point(mesh->to_vertex_handle(he)) - mesh->point(mesh->from_vertex_handle(he));
188 
189  mean_edge += vec_e.length();
190 
191  // Estimate feature angle
192  TriMesh::FaceHandle fh1 = mesh->face_handle(he);
193  TriMesh::FaceHandle fh2 = mesh->face_handle(mesh->opposite_halfedge_handle(he));
194 
195  // Boundary halfedge?
196  if ( !fh1.is_valid() || !fh2.is_valid() )
197  continue;
198 
199  TriMesh::Normal n1 = mesh->normal(fh1);
200  TriMesh::Normal n2 = mesh->normal(fh2);
201 
202  double feature_angle = (1.0 - (n1 | n2));
203 
204  if(feature_angle > max_feature_angle) max_feature_angle = feature_angle;
205  }
206 
207  mean_edge /= (double)mesh->n_edges();
208 
209  } else {
210  // DATA_POLY_MESH
211 
212  PolyMesh* mesh = PluginFunctions::polyMesh(o_it->id());
213  if(!mesh) return;
214 
215  mesh->update_face_normals();
216 
217  for(PolyMesh::EdgeIter e_it = mesh->edges_begin(); e_it != mesh->edges_end(); ++e_it) {
218 
219  PolyMesh::HalfedgeHandle he = mesh->halfedge_handle(*e_it, 0);
220 
221  ACG::Vec3d vec_e = mesh->point(mesh->to_vertex_handle(he)) - mesh->point(mesh->from_vertex_handle(he));
222 
223  mean_edge += vec_e.length();
224 
225  // Estimate feature angle
226  PolyMesh::FaceHandle fh1 = mesh->face_handle(he);
227  PolyMesh::FaceHandle fh2 = mesh->face_handle(mesh->opposite_halfedge_handle(he));
228 
229  // Boundary halfedge?
230  if ( !fh1.is_valid() || !fh2.is_valid() )
231  continue;
232 
233  PolyMesh::Normal n1 = mesh->normal(fh1);
234  PolyMesh::Normal n2 = mesh->normal(fh2);
235 
236  double feature_angle = (1.0 - (n1 | n2));
237 
238  if(feature_angle > max_feature_angle) max_feature_angle = feature_angle;
239  }
240 
241  mean_edge /= (double)mesh->n_edges();
242  }
243 
244  // Convert feature angle to radian
245  max_feature_angle = max_feature_angle * (M_PI / 2.0);
246 
247  // Set adaptive values
248  tool_->adaptive_error->setValue(mean_edge * 0.1); // 10% of mean value
249  tool_->adaptive_min_edge->setValue(mean_edge - (mean_edge * 0.1)); // mean - 10%
250  tool_->adaptive_max_edge->setValue(mean_edge + (mean_edge * 0.1)); // mean + 10%
251 
252  // Set uniform values
253  tool_->uniform_edge_length->setValue(mean_edge);
254 
255  return; // Just take first object
256  }
257 }
258 
259 // ----------------------- Adaptive Remeshing ---------------------------------------------
260 
261 void RemesherPlugin::adaptiveRemeshingButtonClicked() {
262 
263  QString jobId = name() + "AdaptiveRemeshing";
264  OpenFlipperThread* thread = new OpenFlipperThread(jobId);
265 
266  connect(thread, SIGNAL( finished(QString)), this, SIGNAL(finishJob(QString)));
267  connect(thread, SIGNAL( function() ), this, SLOT(adaptiveRemeshing()),Qt::DirectConnection);
268 
269 
270  emit startJob( jobId, "Adaptive remeshing" , 0 , 100 , true);
271 
272  // Attach job to progress emitter
273  progress_->attachJob(jobId);
274 
275  thread->start();
276  thread->startProcessing();
277 
278 }
279 
280 // ----------------------------------------------------------------------------------------
281 
282 void RemesherPlugin::adaptiveRemeshing() {
283 
284  if(OpenFlipper::Options::nogui()) return;
285 
286  //read one target objects
288  o_it != PluginFunctions::objectsEnd(); ++o_it) {
289 
290  // Check data type.
291  // Note that adaptive remeshing is restricted
292  // to triangle meshes only since it relies
293  // on edge flips which are only defined
294  // for triangle configurations.
295 
296  // Get parameters from GUI
297  double error = tool_->adaptive_error->value();
298  double min_edge = tool_->adaptive_min_edge->value();
299  double max_edge = tool_->adaptive_max_edge->value();
300  unsigned int iters = tool_->adaptive_iters->text().toInt();
301  bool projection = tool_->adaptive_projection->isChecked();
302  bool vertexSelection = (tool_->adaptive_selection->currentIndex() == 0);
303 
304  slotAdaptiveRemeshing(o_it->id(), error, min_edge, max_edge, iters, projection,vertexSelection);
305 
306  }
307 }
308 
309 // ----------------------------------------------------------------------------------------
310 
311 void RemesherPlugin::slotAdaptiveRemeshing(int _objectID,
312  double _error,
313  double _min_edge_length,
314  double _max_edge_length,
315  unsigned int _iters,
316  bool _use_projection,
317  bool _vertex_selection) {
318 
319  operation_ = REMESH_ADAPTIVE;
320 
321  BaseObjectData* object = 0;
322 
323  if (PluginFunctions::getObject(_objectID, object)) {
324 
325  // Check data type
326  if (object->dataType(DATA_TRIANGLE_MESH)) {
327 
328  TriMesh* mesh = PluginFunctions::triMesh(object);
329 
330  Remeshing::AdaptiveRemesherT<TriMesh> remesher(*mesh, progress_);
331 
332  Remeshing::BaseRemesherT<TriMesh>::Selection selection = (_vertex_selection) ? Remeshing::BaseRemesherT<TriMesh>::VERTEX_SELECTION : Remeshing::BaseRemesherT<TriMesh>::FACE_SELECTION;
333 
334  remesher.remesh(_error, _min_edge_length, _max_edge_length, _iters, _use_projection, selection);
335 
336  mesh->update_normals();
337 
338 
339  QString projectionString = "\"FALSE\"";
340  if (_use_projection)
341  projectionString = "\"TRUE\"";
342 
343  emit scriptInfo("adaptiveRemeshing(" + QString::number(_objectID) + ", "
344  + QString::number(_error) + ", "
345  + QString::number(_min_edge_length) + ", "
346  + QString::number(_max_edge_length) + ", "
347  + QString::number(_iters) + ", "
348  + projectionString + ")");
349 
350  return;
351  }
352  }
353 }
354 
355 // ----------------------- Uniform Remeshing ---------------------------------------------
356 
357 void RemesherPlugin::uniformRemeshingButtonClicked() {
358 
359  OpenFlipperThread* thread = new OpenFlipperThread(name() + "UniformRemeshing");
360 
361  connect(thread, SIGNAL( state(QString, int)), this, SIGNAL(setJobState(QString, int)));
362  connect(thread, SIGNAL( finished(QString)), this, SIGNAL(finishJob(QString)));
363  connect(thread, SIGNAL( function() ), this, SLOT(uniformRemeshing()),Qt::DirectConnection);
364 
365  emit startJob( name() + "UniformRemeshing", "Uniform remeshing" , 0 , 100 , true);
366 
367  thread->start();
368  thread->startProcessing();
369 
370 }
371 
372 // ----------------------------------------------------------------------------------------
373 
374 void RemesherPlugin::uniformRemeshing(){
375 
376  if(OpenFlipper::Options::nogui()) return;
377 
378  // Get parameters from GUI
379  double edge_length = tool_->uniform_edge_length->value();
380  unsigned int iters = tool_->uniform_iters->text().toInt();
381  unsigned int area_iters = tool_->uniform_area_iters->text().toInt();
382  bool projection = tool_->uniform_projection->isChecked();
383  bool vertex_selection = (tool_->uniform_selection->currentIndex() == 0);
384 
385  //read one target objects
387  o_it != PluginFunctions::objectsEnd(); ++o_it) {
388 
389  // Set incident vertices to feature edges to be feature vertices
390  TriMesh* mesh = PluginFunctions::triMesh(o_it->id());
391  if(!mesh) continue;
392  for(TriMesh::EdgeIter e_it = mesh->edges_begin(); e_it != mesh->edges_end(); ++e_it) {
393  if(mesh->status(*e_it).feature()) {
394  mesh->status(mesh->to_vertex_handle(mesh->halfedge_handle(*e_it, 0))).set_feature(true);
395  mesh->status(mesh->from_vertex_handle(mesh->halfedge_handle(*e_it, 0))).set_feature(true);
396  }
397  }
398 
399  // Check data type.
400  // Note that uniform remeshing is restricted
401  // to triangle meshes only since it also relies
402  // on edge flips which are only defined
403  // for triangle configurations.
404 
405  slotUniformRemeshing(o_it->id(), edge_length, iters, area_iters, projection,vertex_selection);
406  }
407 }
408 
409 // ----------------------------------------------------------------------------------------
410 
411 void RemesherPlugin::slotUniformRemeshing(int _objectID,
412  double _edge_length,
413  unsigned int _iters,
414  unsigned int _area_iters,
415  bool _use_projection,
416  bool _vertex_selection) {
417 
418  operation_ = REMESH_UNIFORM;
419 
420  BaseObjectData* object = 0;
421 
422  if (PluginFunctions::getObject(_objectID, object)) {
423 
424  // Check data type
425  if (object->dataType(DATA_TRIANGLE_MESH)) {
426 
427  TriMesh* mesh = PluginFunctions::triMesh(object);
428 
429  Remeshing::UniformRemesherT<TriMesh> remesher(*mesh, progress_);
430 
431  Remeshing::BaseRemesherT<TriMesh>::Selection selection = (_vertex_selection) ? Remeshing::BaseRemesherT<TriMesh>::VERTEX_SELECTION : Remeshing::BaseRemesherT<TriMesh>::FACE_SELECTION;
432 
433  remesher.remesh(_edge_length, _iters, _area_iters, _use_projection, selection);
434 
435  mesh->update_normals();
436 
437  QString projectionString = "\"FALSE\"";
438  if (_use_projection)
439  projectionString = "\"TRUE\"";
440 
441  emit scriptInfo("uniformRemeshing(" + QString::number(_objectID) + ", "
442  + QString::number(_edge_length) + ", "
443  + QString::number(_iters) + ", "
444  + QString::number(_area_iters) + ", "
445  + QString::number(_iters) + ", "
446  + projectionString + ")");
447 
448  return;
449  }
450  }
451 
452 }
453 
454 // ----------------------------------------------------------------------------------------
455 
456 void RemesherPlugin::adaptiveRemeshing(int _objectID,
457  double _error,
458  double _min_edge_length,
459  double _max_edge_length,
460  unsigned int _iters,
461  bool _use_projection) {
462 
463  slotAdaptiveRemeshing(_objectID,_error,_min_edge_length,_max_edge_length,_iters,_use_projection);
464  emit updatedObject(_objectID, UPDATE_TOPOLOGY );
465  emit createBackup(_objectID, "Adaptive remeshing", UPDATE_TOPOLOGY);
466 
467 }
468 
469 // ----------------------------------------------------------------------------------------
470 
471 void RemesherPlugin::uniformRemeshing(int _objectID,
472  double _edge_length,
473  unsigned int _iters,
474  unsigned int _area_iters,
475  bool _use_projection) {
476 
477  slotUniformRemeshing(_objectID,_edge_length,_iters,_area_iters,_use_projection);
478  emit updatedObject(_objectID, UPDATE_TOPOLOGY );
479  emit createBackup(_objectID, "Uniform remeshing", UPDATE_TOPOLOGY);
480 
481 }
482 
483 // ----------------------------------------------------------------------------------------
484 
485 void RemesherPlugin::adaptiveRemeshingFaceSelection(int _objectID,
486  double _error,
487  double _min_edge_length,
488  double _max_edge_length,
489  unsigned int _iters,
490  bool _use_projection) {
491 
492  slotAdaptiveRemeshing(_objectID,_error,_min_edge_length,_max_edge_length,_iters,_use_projection,false);
493  emit updatedObject(_objectID, UPDATE_TOPOLOGY );
494  emit createBackup(_objectID, "Adaptive remeshing", UPDATE_TOPOLOGY);
495 
496 }
497 
498 // ----------------------------------------------------------------------------------------
499 
500 void RemesherPlugin::uniformRemeshingFaceSelection(int _objectID,
501  double _edge_length,
502  unsigned int _iters,
503  unsigned int _area_iters,
504  bool _use_projection) {
505 
506  slotUniformRemeshing(_objectID,_edge_length,_iters,_area_iters,_use_projection,false);
507  emit updatedObject(_objectID, UPDATE_TOPOLOGY );
508  emit createBackup(_objectID, "Uniform remeshing", UPDATE_TOPOLOGY);
509 
510 }
511 
512 // ----------------------------------------------------------------------------------------
513 
514 #if QT_VERSION < 0x050000
515  Q_EXPORT_PLUGIN2( remesherplugin, RemesherPlugin );
516 #endif
517 
void computeInitValues()
Compute mean edge length and set values.
auto length() const -> decltype(std::declval< VectorT< S, DIM >>().norm())
compute squared euclidean norm
Definition: Vector11T.hh:417
Predefined datatypes.
Definition: DataTypes.hh:96
void pluginsInitialized()
Initialize the plugin.
const UpdateType UPDATE_TOPOLOGY(UpdateTypeSet(1)<< 3)
Topology updated.
void update_normals()
Compute normals for all primitives.
Definition: PolyMeshT.cc:241
bool getObject(int _identifier, BSplineCurveObject *&_object)
PolyMesh * polyMesh(BaseObjectData *_object)
Get a poly mesh from an object.
bool dataType(DataType _type) const
Definition: BaseObject.cc:232
const QStringList TARGET_OBJECTS("target")
Iterable object range.
void startProcessing()
start processing
Thread handling class for OpenFlipper.
Kernel::Normal Normal
Normal type.
Definition: PolyMeshT.hh:117
void update_face_normals()
Update normal vectors for all faces.
Definition: PolyMeshT.cc:259
void initializePlugin()
init the Toolbox
#define DATA_POLY_MESH
Definition: PolyMesh.hh:65
#define DATA_TRIANGLE_MESH
Definition: TriangleMesh.hh:66
DLLEXPORT ObjectIterator objectsEnd()
Return Iterator to Object End.
TriMesh * triMesh(BaseObjectData *_object)
Get a triangle mesh from an object.