1 /****************************************************************************
2 * MeshLab o o *
3 * An extendible mesh processor o o *
4 * _ O _ *
5 * Copyright(C) 2005, 2006 \/)\/ *
6 * Visual Computing Lab /\/| *
7 * ISTI - Italian National Research Council | *
8 * \ *
9 * All rights reserved. *
10 * *
11 * This program is free software; you can redistribute it and/or modify *
12 * it under the terms of the GNU General Public License as published by *
13 * the Free Software Foundation; either version 2 of the License, or *
14 * (at your option) any later version. *
15 * *
16 * This program is distributed in the hope that it will be useful, *
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
19 * GNU General Public License (http://www.gnu.org/licenses/gpl.txt) *
20 * for more details. *
21 * *
22 ****************************************************************************/
23 #include "filter_isoparametrization.h"
24
25 #include <QStringList>
26 #include <QFileInfo>
27 #include "defines.h"
28
29 #include "../../common/meshmodel.h"
30 #include <iso_transfer.h>
31
32
33 #include <stdlib.h>
34 #include <time.h>
35
36 using namespace std;
37 using namespace vcg;
38
FilterIsoParametrization()39 FilterIsoParametrization::FilterIsoParametrization()
40 {
41 typeList << ISOP_PARAM
42 << ISOP_REMESHING
43 << ISOP_DIAMPARAM
44 << ISOP_TRANSFER;
45
46 FilterIDType tt;
47 foreach(tt , types())
48 actionList << new QAction(filterName(tt), this);
49
50 }
51
~FilterIsoParametrization()52 FilterIsoParametrization::~FilterIsoParametrization()
53 {
54 for (int i = 0; i < actionList.count() ; i++ )
55 delete actionList.at(i);
56 }
57
filterName(FilterIDType filter) const58 QString FilterIsoParametrization::filterName(FilterIDType filter) const
59 {
60 switch(filter)
61 {
62 case ISOP_PARAM : return "Iso Parametrization: Main";
63 case ISOP_REMESHING : return "Iso Parametrization Remeshing";
64 case ISOP_DIAMPARAM : return "Iso Parametrization Build Atlased Mesh";
65 case ISOP_TRANSFER: return "Iso Parametrization transfer between meshes";
66 default: assert(0);
67 }
68 return QString("error!");
69 }
70 static const QString bibRef("For more details see: <br>"
71 "<b>N. Pietroni, M. Tarini and P. Cignoni</b>, <br><a href=\"http://vcg.isti.cnr.it/Publications/2010/PTC10/\">'Almost isometric mesh parameterization through abstract domains'</a> <br>"
72 "IEEE Transaction of Visualization and Computer Graphics, 2010");
73
filterInfo(FilterIDType filterId) const74 QString FilterIsoParametrization::filterInfo(FilterIDType filterId) const
75 {
76 switch(filterId)
77 {
78 case ISOP_PARAM : return "The filter builds the abstract domain mesh representing the Isoparameterization of a watertight two-manifold triangular mesh. <br>"
79 "This abstract mesh can be used to uniformly remesh the input mesh, or to build a atlased texture parametrization. Abstract Mesh can be also loaded and saved. <br>"
80 "In short this filter build a very coarse almost regular triangulation such that original mesh can be reprojected from this abstract mesh with minimal distortion.<br>"+bibRef;
81 case ISOP_REMESHING : return "Uniform Remeshing based on Isoparameterization, each triangle of the domain is recursively subdivided. <br>"
82 +bibRef;
83 case ISOP_DIAMPARAM : return "The filter build a new mesh with a standard atlased per wedge texture. The atlas is simply done by "
84 "exploiting the low distortion, coarse, regular, mesh of the abstract domain<br>" +bibRef;
85 case ISOP_TRANSFER:return "Transfer the Isoparametrization between two meshes, the two meshes must be reasonably similar and well aligned."
86 " It is useful to transfer back an isoparam onto the original mesh after having computed it on a dummy, clean watertight model.<br>"
87 +bibRef;
88 default: assert(0);
89 }
90 return QString("error!");
91 }
92
getRequirements(QAction *)93 int FilterIsoParametrization::getRequirements(QAction *)
94 {
95 return MeshModel::MM_NONE;
96 }
97
initParameterSet(QAction * a,MeshDocument & md,RichParameterSet & par)98 void FilterIsoParametrization::initParameterSet(QAction *a, MeshDocument& md, RichParameterSet & par)
99 {
100
101 switch(ID(a))
102 {
103 case ISOP_PARAM:
104 {
105 par.addParam(new RichInt("targetAbstractMinFaceNum",150,"AM Min Size",
106 "This number and the following one indicate the range face number of the abstract mesh that is used for the parametrization process.<br>"
107 "The algorithm will choose the best abstract mesh with the number of triangles within the specified interval.<br>"
108 "If the mesh has a very simple structure this range can be very low and strict;"
109 "for a roughly spherical object if you can specify a range of [8,8] faces you get a octahedral abstract mesh, e.g. a geometry image.<br>"
110 "Large numbers (greater than 400) are usually not of practical use."));
111 par.addParam(new RichInt("targetAbstractMaxFaceNum",200,"AM Max Size", "Please notice that a large interval requires huge amount of memory to be allocated, in order save the intermediate results. <br>"
112 "An interval of 50 should be fine."));
113 QStringList stopCriteriaList;
114 stopCriteriaList.push_back("Best Heuristic");
115 stopCriteriaList.push_back("Area + Angle");
116 stopCriteriaList.push_back("Regularity");
117 stopCriteriaList.push_back("L2");
118
119 par.addParam(new RichEnum("stopCriteria", 1, stopCriteriaList, tr("Optimization Criteria"),
120 tr(//"<p style=\'white-space:pre\'>"
121 "Choose a metric to stop the parametrization within the interval<br>"
122 "1: Best Heuristic : stop considering both isometry and number of faces of base domain<br>"
123 "2: Area + Angle : stop at minimum area and angle distorsion<br>"
124 "3: Regularity : stop at minimum number of irregular vertices<br>"
125 "4: L2 : stop at minimum OneWay L2 Stretch Eff")));
126
127 par.addParam(new RichInt("convergenceSpeed",1, "Convergence Precision", "This parameter controls the convergence speed/precision of the optimization of the texture coordinates. Larger the number slower the processing and ,eventually, slightly better results"));
128 par.addParam(new RichBool("DoubleStep",true,"Double Step","Use this bool to divide the parameterization in 2 steps. Double step makes the overall process faster and robust."
129 "<br> Consider to disable this bool in case the object has topologycal noise or small handles."));
130 par.addParam(new RichString("AbsLoadName", "", "Load AM", "The filename of the abstract mesh that has to be loaded. If empty, the abstract mesh will be computed according to the above parameters (suggested extension '.abs')."));
131 par.addParam(new RichString("AbsSaveName", "", "Save AM", "The filename where the computed abstract mesh will be saved. If empty, nothing will be done."));
132 break;
133 }
134 case ISOP_REMESHING :
135 {
136 par.addParam(new RichInt("SamplingRate",10,"Sampling Rate", "This specify the sampling rate for remeshing. Must be greater than 2"));
137 break;
138 }
139 case ISOP_DIAMPARAM :
140 {
141 par.addParam(new RichDynamicFloat("BorderSize",0.1f,0.01f,0.5f,"BorderSize ratio",
142 "This parameter controls the amount of space that must be left between each diamond when building the atlas."
143 "It directly affects how many triangle are split during this conversion. <br>"
144 "In abstract parametrization mesh triangles can naturally cross the triangles of the abstract domain, so when converting "
145 "to a standard parametrization we must cut all the triangles that protrudes outside each diamond more than the specified threshold."
146 "The unit of the threshold is in percentage of the size of the diamond,"
147 "The bigger the threshold the less triangles are split, but the more UV space is used (wasted)."));
148 break;
149 }
150 case ISOP_TRANSFER:
151 {
152 par.addParam(new RichMesh ("sourceMesh",md.mm(),&md, "Source Mesh", "The mesh already having an Isoparameterization"));
153 par.addParam(new RichMesh ("targetMesh",md.mm(),&md, "Target Mesh", "The mesh to be Isoparameterized"));
154 }
155 }
156 }
157
PrintStats(CMeshO * mesh)158 void FilterIsoParametrization::PrintStats(CMeshO *mesh)
159 {
160 tri::UpdateTopology<CMeshO>::FaceFace(*mesh);
161 tri::UpdateTopology<CMeshO>::VertexFace(*mesh);
162 int non_reg=NumRegular<CMeshO>(*mesh);
163 CMeshO::ScalarType minE,maxE,avE,stdE;
164 CMeshO::ScalarType minAr,maxAr,avAr,stdAr;
165 CMeshO::ScalarType minAn,maxAn,avAn,stdAn;
166
167 StatEdge<CMeshO>(*mesh,minE,maxE,avE,stdE);
168 StatArea<CMeshO>(*mesh,minAr,maxAr,avAr,stdAr);
169 StatAngle<CMeshO>(*mesh,minAn,maxAn,avAn,stdAn);
170 Log(" REMESHED ");
171 Log("Irregular Vertices:%d ",non_reg);
172 Log("stdDev Area: %5.2f",stdAr/avAr);
173 Log("stdDev Angle: %5.2f",stdAn/avAn);
174 Log("stdDev Edge: %5.2f",stdE/avE);
175 }
176
applyFilter(QAction * filter,MeshDocument & md,RichParameterSet & par,vcg::CallBackPos * cb)177 bool FilterIsoParametrization::applyFilter(QAction *filter, MeshDocument& md, RichParameterSet & par, vcg::CallBackPos *cb)
178 {
179 MeshModel* m = md.mm(); //get current mesh from document
180 CMeshO *mesh=&m->cm;
181 switch(ID(filter))
182 {
183 case ISOP_PARAM : //////////////// the main filter //////////////////////
184 {
185 int targetAbstractMinFaceNum = par.getInt("targetAbstractMinFaceNum");
186 int targetAbstractMaxFaceNum = par.getInt("targetAbstractMaxFaceNum");
187 int convergenceSpeed = par.getInt("convergenceSpeed");
188 int stopCriteria=par.getEnum("stopCriteria");
189 bool doublestep=par.getBool("DoubleStep");
190
191 m->updateDataMask(MeshModel::MM_FACEFACETOPO);
192 m->updateDataMask(MeshModel::MM_VERTQUALITY); // needed to store patch index
193 m->updateDataMask(MeshModel::MM_VERTMARK | MeshModel::MM_FACEMARK);
194
195 bool isTXTenabled=m->hasDataMask(MeshModel::MM_VERTTEXCOORD);
196 if (!isTXTenabled)
197 m->updateDataMask(MeshModel::MM_VERTTEXCOORD);
198
199 bool isVColorenabled=m->hasDataMask(MeshModel::MM_VERTCOLOR);
200 if (!isVColorenabled)
201 m->updateDataMask(MeshModel::MM_VERTCOLOR);
202
203 bool isFColorenabled=m->hasDataMask(MeshModel::MM_FACECOLOR);
204 if (!isFColorenabled)
205 m->updateDataMask(MeshModel::MM_FACECOLOR);
206 int tolerance = targetAbstractMaxFaceNum-targetAbstractMinFaceNum;
207
208 CMeshO::PerMeshAttributeHandle<IsoParametrization> isoPHandle =
209 tri::Allocator<CMeshO>::GetPerMeshAttribute<IsoParametrization>(*mesh,"isoparametrization");
210
211 QString AbsLoadName = par.getString("AbsLoadName");
212 if(AbsLoadName.isEmpty())
213 {
214 IsoParametrizator Parametrizator;
215 switch (stopCriteria)
216 {
217 case 0:Parametrizator.SetParameters(cb,targetAbstractMinFaceNum,tolerance,IsoParametrizator::SM_Euristic,convergenceSpeed);break;
218 case 1:Parametrizator.SetParameters(cb,targetAbstractMinFaceNum,tolerance,IsoParametrizator::SM_Corr,convergenceSpeed);break;
219 case 2:Parametrizator.SetParameters(cb,targetAbstractMinFaceNum,tolerance,IsoParametrizator::SM_Reg,convergenceSpeed);break;
220 case 3:Parametrizator.SetParameters(cb,targetAbstractMinFaceNum,tolerance,IsoParametrizator::SM_L2,convergenceSpeed);break;
221 default:Parametrizator.SetParameters(cb,targetAbstractMinFaceNum,tolerance,IsoParametrizator::SM_Euristic,convergenceSpeed);break;
222 }
223 tri::ParamEdgeCollapseParameter pecp;
224 IsoParametrizator::ReturnCode ret=Parametrizator.Parametrize<CMeshO>(mesh,pecp,doublestep);
225
226 if (ret==IsoParametrizator::Done)
227 {
228 Parametrizator.PrintAttributes();
229 float aggregate,L2;
230 int n_faces;
231 Parametrizator.getValues(aggregate,L2,n_faces);
232 Log("Num Faces of Abstract Domain: %d, One way stretch efficiency: %.4f, Area+Angle Distorsion %.4f ",n_faces,L2,aggregate*100.f);
233 }
234 else
235 {
236 if (!isTXTenabled) m->clearDataMask(MeshModel::MM_VERTTEXCOORD);
237 if (!isVColorenabled) m->clearDataMask(MeshModel::MM_VERTCOLOR);
238 if (!isFColorenabled) m->clearDataMask(MeshModel::MM_FACECOLOR);
239 switch(ret)
240 {
241 case IsoParametrizator::MultiComponent:
242 this->errorMessage="non possible parameterization because of multi component mesh"; return false;
243 case IsoParametrizator::NonSizeCons:
244 this->errorMessage="non possible parameterization because of non size consistent mesh"; return false;
245 case IsoParametrizator::NonManifoldE:
246 this->errorMessage="non possible parameterization because of non manifold edges"; return false;
247 case IsoParametrizator::NonManifoldV:
248 this->errorMessage="non possible parameterization because of non manifold vertices";return false;
249 case IsoParametrizator::NonWatertigh:
250 this->errorMessage="non possible parameterization because of non watertight mesh"; return false;
251 case IsoParametrizator::FailParam:
252 this->errorMessage="non possible parameterization cause one of the following reasons:\n Topologycal noise \n Too Low resolution mesh \n Too Bad triangulation \n"; return false;
253 default:
254 this->errorMessage="unknown error"; return false;
255 }
256 }
257
258 // At this point we are sure that everything went ok so we can allocate surely the abstract
259 AbstractMesh *abs_mesh = new AbstractMesh();
260 ParamMesh *para_mesh = new ParamMesh();
261 Parametrizator.ExportMeshes(*para_mesh,*abs_mesh);
262
263 bool isOK=isoPHandle().Init(abs_mesh,para_mesh);
264 if (!isOK) {
265 this->errorMessage="Problems gathering parameterization \n";
266 return false;
267 }
268
269 isoPHandle().CopyParametrization<CMeshO>(mesh); ///copy back to original mesh
270 }
271 else
272 {
273 AbstractMesh *abs_mesh = new AbstractMesh();
274 ParamMesh *para_mesh = new ParamMesh();
275 bool Done=isoPHandle().LoadBaseDomain<CMeshO>(qUtf8Printable(AbsLoadName),mesh,para_mesh,abs_mesh,true);
276 if (!Done)
277 {
278 this->errorMessage="Abstract domain doesn't fit well with the parametrized mesh";
279 delete para_mesh;
280 delete abs_mesh;
281 return false;
282 }
283 }
284
285 QString AbsSaveName = par.getString("AbsSaveName");
286 if(!AbsSaveName.isEmpty())
287 {
288 isoPHandle().SaveBaseDomain(qUtf8Printable(AbsSaveName));
289 }
290 return true;
291 }
292 case ISOP_REMESHING :
293 {
294 CMeshO::PerMeshAttributeHandle<IsoParametrization> isoPHandle =
295 tri::Allocator<CMeshO>::FindPerMeshAttribute<IsoParametrization>(*mesh,"isoparametrization");
296
297 bool b=tri::Allocator<CMeshO>::IsValidHandle<IsoParametrization>(*mesh,isoPHandle);
298 if (!b)
299 {
300 this->errorMessage="You must compute the abstract mesh before remeshing. Use the Isoparametrization main filter.";
301 return false;
302 }
303
304
305 int SamplingRate=par.getInt("SamplingRate");
306 if (SamplingRate<2)
307 {
308 this->errorMessage="Sampling rate must be >1";
309 return false;
310 }
311 MeshModel* mm=md.addNewMesh("","Re-meshed");
312
313 CMeshO *rem=&mm->cm;
314 DiamSampler DiamSampl;
315 DiamSampl.Init(&isoPHandle());
316 bool done=DiamSampl.SamplePos(SamplingRate);
317 assert(done);
318 DiamSampl.GetMesh<CMeshO>(*rem);
319
320 int n_diamonds,inFace,inEdge,inStar,n_merged;
321 DiamSampl.getResData(n_diamonds,inFace,inEdge,inStar,n_merged);
322
323 Log("INTERPOLATION DOMAINS");
324 Log("In Face: %d \n",inFace);
325 Log("In Diamond: %d \n",inEdge);
326 Log("In Star: %d \n",inStar);
327 Log("Merged %d vertices\n",n_merged);
328 mm->updateDataMask(MeshModel::MM_FACEFACETOPO);
329 mm->updateDataMask(MeshModel::MM_VERTFACETOPO);
330 PrintStats(rem);
331 mm->UpdateBoxAndNormals();
332 return true;
333 }
334 case ISOP_DIAMPARAM :
335 {
336 CMeshO::PerMeshAttributeHandle<IsoParametrization> isoPHandle =
337 tri::Allocator<CMeshO>::FindPerMeshAttribute<IsoParametrization>(*mesh,"isoparametrization");
338 bool b=tri::Allocator<CMeshO>::IsValidHandle<IsoParametrization>(*mesh,isoPHandle);
339 if (!b)
340 {
341 this->errorMessage="You must compute the abstract mesh before remeshing. Use the Isoparametrization main filter.";
342 return false;
343 }
344
345 float border_size=par.getDynamicFloat("BorderSize");
346 MeshModel* mm=md.addNewMesh("","Diam-Parameterized");
347 mm->updateDataMask(MeshModel::MM_WEDGTEXCOORD);
348 mm->updateDataMask(MeshModel::MM_VERTCOLOR);
349 CMeshO *rem=&mm->cm;
350 DiamondParametrizator DiaPara;
351 DiaPara.Init(&isoPHandle());
352 DiaPara.SetCoordinates<CMeshO>(*rem,border_size);
353 tri::UpdateNormal<CMeshO>::PerFace(*rem);
354 return true;
355 }
356 // case ISOP_LOAD :
357 // {
358 // QString AbsName = par.getString("AbsName");
359 // m->updateDataMask(MeshModel::MM_WEDGTEXCOORD);
360 // m->updateDataMask(MeshModel::MM_VERTTEXCOORD);
361 // m->updateDataMask(MeshModel::MM_FACECOLOR);
362 // m->updateDataMask(MeshModel::MM_VERTQUALITY);
363 // m->updateDataMask(MeshModel::MM_FACEMARK);
364 // if(!QFile(m->fullName()).exists())
365 // {
366 // this->errorMessage="File not exists";
367 // return false;
368 // }
369 // CMeshO::PerMeshAttributeHandle<IsoParametrization> isoPHandle =
370 // tri::Allocator<CMeshO>::FindPerMeshAttribute<IsoParametrization>(*mesh,"isoparametrization");
371
372 // bool b=tri::Allocator<CMeshO>::IsValidHandle<IsoParametrization>(*mesh,isoPHandle);
373 // if (!b)
374 // isoPHandle=tri::Allocator<CMeshO>::AddPerMeshAttribute<IsoParametrization>(*mesh,"isoparametrization");
375
376 // QByteArray ba = AbsName.toLatin1();
377 // char *path=ba.data();
378 // AbstractMesh *abs_mesh = new AbstractMesh();
379 // ParamMesh *para_mesh = new ParamMesh();
380 // bool Done=isoPHandle().LoadBaseDomain<CMeshO>(path,mesh,para_mesh,abs_mesh,true);
381 // if (!Done)
382 // {
383 // this->errorMessage="Abstract domain doesn't fit well with the parametrized mesh";
384 // delete para_mesh;
385 // delete abs_mesh;
386 // return false;
387 // }
388 // return true;
389 // }
390 // case ISOP_SAVE :
391 // {
392 // m->updateDataMask(MeshModel::MM_VERTQUALITY);
393 // CMeshO::PerMeshAttributeHandle<IsoParametrization> isoPHandle =
394 // tri::Allocator<CMeshO>::FindPerMeshAttribute<IsoParametrization>(*mesh,"isoparametrization");
395
396 // bool b=tri::Allocator<CMeshO>::IsValidHandle<IsoParametrization>(*mesh,isoPHandle);
397 // if (!b)
398 // {
399 // this->errorMessage="You must compute the Base domain before remeshing. Use the Isoparametrization command.";
400 // return false;
401 // }
402 // /*QString Qpath=m->fullName();*/
403
404 // QString AbsName = par.getString("AbsName");
405
406 // QByteArray ba = AbsName.toLatin1();
407 // char *path=ba.data();
408 // isoPHandle().SaveBaseDomain(path);
409 // return true;
410 // }
411 case ISOP_TRANSFER:
412 {
413 MeshModel *mmtrg = par.getMesh("targetMesh");
414 MeshModel *mmsrc = par.getMesh("targetMesh");
415 CMeshO *trgMesh=&mmtrg->cm;
416 CMeshO *srcMesh=&mmsrc->cm;
417
418 CMeshO::PerMeshAttributeHandle<IsoParametrization> isoPHandle =
419 tri::Allocator<CMeshO>::FindPerMeshAttribute<IsoParametrization>(*mesh,"isoparametrization");
420
421 bool b=tri::Allocator<CMeshO>::IsValidHandle<IsoParametrization>(*srcMesh,isoPHandle);
422 if (!b)
423 {
424 this->errorMessage="Your source mesh must have the abstract isoparametrization. Use the Isoparametrization command.";
425 return false;
426 }
427 IsoTransfer IsoTr;
428 AbstractMesh *abs_mesh = isoPHandle().AbsMesh();
429 ParamMesh *para_mesh = isoPHandle().ParaMesh();
430
431 mmtrg->updateDataMask(MeshModel::MM_WEDGTEXCOORD);
432 mmtrg->updateDataMask(MeshModel::MM_VERTTEXCOORD);
433 mmtrg->updateDataMask(MeshModel::MM_FACECOLOR);
434 mmtrg->updateDataMask(MeshModel::MM_VERTQUALITY);
435 mmtrg->updateDataMask(MeshModel::MM_FACEMARK);
436 IsoTr.Transfer<CMeshO>(isoPHandle(),*trgMesh);
437
438 isoPHandle().Clear();
439 tri::Allocator<CMeshO>::DeletePerMeshAttribute(*srcMesh,isoPHandle);
440
441 isoPHandle=tri::Allocator<CMeshO>::AddPerMeshAttribute<IsoParametrization>(*trgMesh,"isoparametrization");
442 isoPHandle().AbsMesh()=abs_mesh;
443 isoPHandle().SetParamMesh<CMeshO>(trgMesh,para_mesh);
444
445 return true;
446 }
447 }
448 return false;
449 }
450
getClass(QAction *)451 MeshFilterInterface::FilterClass FilterIsoParametrization::getClass(QAction *)
452 {
453 return MeshFilterInterface::Remeshing;
454 }
455
postCondition(QAction *) const456 int FilterIsoParametrization::postCondition( QAction* /*filter*/ ) const
457 {
458 return MeshModel::MM_WEDGTEXCOORD | MeshModel::MM_VERTTEXCOORD;
459 }
460
filterArity(QAction * filter) const461 MeshFilterInterface::FILTER_ARITY FilterIsoParametrization::filterArity( QAction* filter) const
462 {
463 switch(ID(filter))
464 {
465 case ISOP_PARAM :
466 case ISOP_REMESHING :
467 case ISOP_DIAMPARAM :
468 return MeshFilterInterface::SINGLE_MESH;
469 case ISOP_TRANSFER:
470 return MeshFilterInterface::FIXED;
471 }
472 return MeshFilterInterface::NONE;
473 }
474
475
476 MESHLAB_PLUGIN_NAME_EXPORTER(FilterIsoParametrization)
477