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