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