1 /****************************************************************************
2 * MeshLab o o *
3 * A versatile mesh processing toolbox o o *
4 * _ O _ *
5 * Copyright(C) 2005 \/)\/ *
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
24 #include "filter_img_patch_param.h"
25 #include <common/GLExtensionsManager.h>
26 #include <QtGui>
27 #include <wrap/gl/shot.h>
28 #include <vcg/space/rect_packer.h>
29 #include "VisibleSet.h"
30 #include "VisibilityCheck.h"
31 #include "TexturePainter.h"
32 #include <cmath>
33
34
35
36
FilterImgPatchParamPlugin()37 FilterImgPatchParamPlugin::FilterImgPatchParamPlugin() : m_Context(NULL)
38 {
39 typeList << FP_PATCH_PARAM_ONLY
40 << FP_PATCH_PARAM_AND_TEXTURING
41 << FP_RASTER_VERT_COVERAGE
42 << FP_RASTER_FACE_COVERAGE;
43
44 foreach( FilterIDType tt , types() )
45 actionList << new QAction(filterName(tt), this);
46 }
47
48
~FilterImgPatchParamPlugin()49 FilterImgPatchParamPlugin::~FilterImgPatchParamPlugin()
50 {
51 delete m_Context;
52 m_Context = NULL;
53 }
54
55
filterName(FilterIDType id) const56 QString FilterImgPatchParamPlugin::filterName( FilterIDType id ) const
57 {
58 switch( id )
59 {
60 case FP_PATCH_PARAM_ONLY: return QString( "Parameterization from registered rasters" );
61 case FP_PATCH_PARAM_AND_TEXTURING: return QString( "Parameterization + texturing from registered rasters" );
62 case FP_RASTER_VERT_COVERAGE: return QString( "Quality from raster coverage (Vertex)" );
63 case FP_RASTER_FACE_COVERAGE: return QString( "Quality from raster coverage (Face)" );
64 default: assert(0); return QString();
65 }
66 }
67
68
filterInfo(FilterIDType id) const69 QString FilterImgPatchParamPlugin::filterInfo( FilterIDType id ) const
70 {
71 switch( id )
72 {
73 case FP_PATCH_PARAM_ONLY: return QString( "The mesh is parameterized by creating some patches that correspond to projection of portions of surfaces onto the set of registered rasters.");
74 case FP_PATCH_PARAM_AND_TEXTURING: return QString("The mesh is parameterized and textured by creating some patches that correspond to projection of portions of surfaces onto the set of registered rasters.");
75 case FP_RASTER_VERT_COVERAGE: return QString( "Compute a quality value representing the number of images into which each vertex of the active mesh is visible." );
76 case FP_RASTER_FACE_COVERAGE: return QString( "Compute a quality value representing the number of images into which each face of the active mesh is visible." );
77 default: assert(0); return QString();
78 }
79 }
80
81
getRequirements(QAction * act)82 int FilterImgPatchParamPlugin::getRequirements( QAction *act )
83 {
84 switch( ID(act) )
85 {
86 case FP_PATCH_PARAM_ONLY:
87 case FP_PATCH_PARAM_AND_TEXTURING: return MeshModel::MM_WEDGTEXCOORD | MeshModel::MM_FACEFACETOPO | MeshModel::MM_VERTFACETOPO;
88 case FP_RASTER_VERT_COVERAGE: return MeshModel::MM_VERTQUALITY;
89 case FP_RASTER_FACE_COVERAGE: return MeshModel::MM_FACEQUALITY;
90 default: assert(0); return 0;
91 }
92 }
93
94
getClass(QAction * act)95 MeshFilterInterface::FilterClass FilterImgPatchParamPlugin::getClass( QAction *act )
96 {
97 switch( ID(act) )
98 {
99 case FP_PATCH_PARAM_ONLY:
100 case FP_PATCH_PARAM_AND_TEXTURING: return Texture;
101 case FP_RASTER_VERT_COVERAGE:
102 case FP_RASTER_FACE_COVERAGE: return FilterClass(Quality + Camera + Texture);
103 default: assert(0); return MeshFilterInterface::Generic;
104 }
105 }
106
107
108 //int FilterImgPatchParamPlugin::postCondition( QAction *act ) const
109 //{
110 // switch( ID(act) )
111 // {
112 // case FP_PATCH_PARAM_ONLY:
113 // case FP_PATCH_PARAM_AND_TEXTURING: return MeshModel::MM_WEDGTEXCOORD;
114 // case FP_RASTER_COVERAGE:
115 // {
116 // return QString( );
117 // }
118 // default: assert(0); return 0;
119 // }
120 //}
121
122
initParameterSet(QAction * act,MeshDocument &,RichParameterSet & par)123 void FilterImgPatchParamPlugin::initParameterSet( QAction *act,
124 MeshDocument &/*md*/,
125 RichParameterSet &par )
126 {
127 switch( ID(act) )
128 {
129 case FP_PATCH_PARAM_AND_TEXTURING:
130 {
131 par.addParam( new RichInt( "textureSize",
132 1024,
133 "Texture size",
134 "Specifies the dimension of the generated texture" ) );
135 par.addParam( new RichString( "textureName",
136 "texture.png",
137 "Texture name",
138 "Specifies the name of the file into which the texture image will be saved" ) );
139 par.addParam( new RichBool( "colorCorrection",
140 true,
141 "Color correction",
142 "If true, the final texture is corrected so as to ensure seamless transitions" ) );
143 par.addParam( new RichInt( "colorCorrectionFilterSize",
144 1,
145 "Color correction filter",
146 "It is the radius (in pixel) of the kernel that is used to compute the difference between corresponding texels in different rasters. Default is 1 that generate a 3x3 kernel. Highest values increase the robustness of the color correction process in the case of strong image-to-geometry misalignments" ) );
147 }
148 case FP_PATCH_PARAM_ONLY:
149 {
150 par.addParam( new RichBool( "useDistanceWeight",
151 true,
152 "Use distance weight",
153 "Includes a weight accounting for the distance to the camera during the computation of reference images" ) );
154 par.addParam( new RichBool( "useImgBorderWeight",
155 true,
156 "Use image border weight",
157 "Includes a weight accounting for the distance to the image border during the computation of reference images" ) );
158 par.addParam( new RichBool( "useAlphaWeight",
159 false,
160 "Use image alpha weight",
161 "If true, alpha channel of the image is used as additional weight. In this way it is possible to mask-out parts of the images that should not be projected on the mesh. Please note this is not a transparency effect, but just influences the weigthing between different images" ) );
162 par.addParam( new RichBool( "cleanIsolatedTriangles",
163 true,
164 "Clean isolated triangles",
165 "Remove all patches compound of a single triangle by aggregating them to adjacent patches" ) );
166 par.addParam( new RichBool( "stretchingAllowed",
167 false,
168 "UV stretching",
169 "If true, texture coordinates are stretched so as to cover the full interval [0,1] for both directions" ) );
170 par.addParam( new RichInt( "textureGutter",
171 4,
172 "Texture gutter",
173 "Extra boundary to add to each patch before packing in texture space (in pixels)" ) );
174 break;
175 }
176 case FP_RASTER_VERT_COVERAGE:
177 case FP_RASTER_FACE_COVERAGE:
178 {
179 par.addParam( new RichBool( "normalizeQuality",
180 false,
181 "Normalize",
182 "Rescale quality values to the range [0,1]" ) );
183 break;
184 }
185 }
186 }
187
188
applyFilter(QAction * act,MeshDocument & md,RichParameterSet & par,vcg::CallBackPos *)189 bool FilterImgPatchParamPlugin::applyFilter( QAction *act,
190 MeshDocument &md,
191 RichParameterSet &par,
192 vcg::CallBackPos * /*cb*/ )
193 {
194
195
196 glContext->makeCurrent();
197 if( !GLExtensionsManager::initializeGLextensions_notThrowing() )
198 {
199 this->errorMessage="Failed GLEW initialization";
200 return false;
201 }
202
203 glPushAttrib(GL_ALL_ATTRIB_BITS);
204
205 delete m_Context;
206 m_Context = new glw::Context();
207 m_Context->acquire();
208
209 if( !VisibilityCheck::GetInstance(*m_Context) )
210 {
211 this->errorMessage="VisibilityCheck failed";
212 return false;
213 }
214 VisibilityCheck::ReleaseInstance();
215
216
217 bool retValue = true;
218
219 CMeshO &mesh = md.mm()->cm;
220
221 std::list<Shotm> initialShots;
222 QList<RasterModel*> activeRasters;
223 foreach( RasterModel *rm, md.rasterList )
224 {
225 initialShots.push_back( rm->shot );
226 rm->shot.ApplyRigidTransformation( vcg::Inverse(mesh.Tr) );
227 if( rm->visible )
228 activeRasters.push_back( rm );
229 }
230
231 if( activeRasters.empty() ) {
232 this->errorMessage="No active Raster";
233 {
234 glContext->doneCurrent();
235 errorMessage = "You need to have at least one valid raster layer in your project, to apply this filter"; // text
236 return false;
237 }
238 }
239
240 switch( ID(act) )
241 {
242 case FP_PATCH_PARAM_ONLY:
243 {
244 if (vcg::tri::Clean<CMeshO>::CountNonManifoldEdgeFF(md.mm()->cm)>0)
245 {
246 glContext->doneCurrent();
247 errorMessage = "Mesh has some not 2-manifold faces, this filter requires manifoldness"; // text
248 return false; // can't continue, mesh can't be processed
249 }
250 vcg::tri::Allocator<CMeshO>::CompactFaceVector(md.mm()->cm);
251 vcg::tri::Allocator<CMeshO>::CompactVertexVector(md.mm()->cm);
252 vcg::tri::UpdateTopology<CMeshO>::FaceFace(md.mm()->cm);
253 vcg::tri::UpdateTopology<CMeshO>::VertexFace(md.mm()->cm);
254 glContext->meshAttributesUpdated(md.mm()->id(),true,MLRenderingData::RendAtts());
255 RasterPatchMap patches;
256 PatchVec nullPatches;
257 patchBasedTextureParameterization( patches,
258 nullPatches,
259 md.mm()->id(),
260 mesh,
261 activeRasters,
262 par );
263
264 break;
265 }
266 case FP_PATCH_PARAM_AND_TEXTURING:
267 {
268 if (vcg::tri::Clean<CMeshO>::CountNonManifoldEdgeFF(md.mm()->cm)>0)
269 {
270 glContext->doneCurrent();
271 errorMessage = "Mesh has some not 2-manifold faces, this filter requires manifoldness"; // text
272 return false; // can't continue, mesh can't be processed
273 }
274 vcg::tri::Allocator<CMeshO>::CompactEveryVector(md.mm()->cm);
275 vcg::tri::UpdateTopology<CMeshO>::FaceFace(md.mm()->cm);
276 vcg::tri::UpdateTopology<CMeshO>::VertexFace(md.mm()->cm);
277 glContext->meshAttributesUpdated(md.mm()->id(),true,MLRenderingData::RendAtts());
278 QString texName = par.getString( "textureName" ).simplified();
279 int pathEnd = std::max( texName.lastIndexOf('/'), texName.lastIndexOf('\\') );
280 if( pathEnd != -1 )
281 texName = texName.right( texName.size()-pathEnd-1 );
282
283 if( (retValue = texName.size()!=0) )
284 {
285 RasterPatchMap patches;
286 PatchVec nullPatches;
287 patchBasedTextureParameterization( patches,
288 nullPatches,
289 md.mm()->id(),
290 mesh,
291 activeRasters,
292 par );
293
294 TexturePainter painter( *m_Context, par.getInt("textureSize") );
295 if( (retValue = painter.isInitialized()) )
296 {
297 QElapsedTimer t; t.start();
298 painter.paint( patches );
299 if( par.getBool("colorCorrection") )
300 painter.rectifyColor( patches, par.getInt("colorCorrectionFilterSize") );
301 Log( "TEXTURE PAINTING: %.3f sec.", 0.001f*t.elapsed() );
302
303 QImage tex = painter.getTexture();
304 if( tex.save(texName) )
305 {
306 mesh.textures.clear();
307 mesh.textures.push_back( texName.toStdString() );
308 }
309 }
310 }
311
312 break;
313 }
314 case FP_RASTER_VERT_COVERAGE:
315 {
316 VisibilityCheck &visibility = *VisibilityCheck::GetInstance( *m_Context );
317 visibility.setMesh(md.mm()->id(),&mesh );
318 visibility.m_plugcontext = glContext;
319 for( CMeshO::VertexIterator vi=mesh.vert.begin(); vi!=mesh.vert.end(); ++vi )
320 vi->Q() = 0.0f;
321
322 foreach( RasterModel *rm, activeRasters )
323 {
324 visibility.setRaster( rm );
325 visibility.checkVisibility();
326 for( CMeshO::VertexIterator vi=mesh.vert.begin(); vi!=mesh.vert.end(); ++vi )
327 if( visibility.isVertVisible(vi) )
328 vi->Q() += 1.0f;
329 }
330
331 if( par.getBool("normalizeQuality") )
332 {
333 const float normFactor = 1.0f / md.rasterList.size();
334 for( CMeshO::VertexIterator vi=mesh.vert.begin(); vi!=mesh.vert.end(); ++vi )
335 vi->Q() *= normFactor;
336 }
337
338 break;
339 }
340 case FP_RASTER_FACE_COVERAGE:
341 {
342 VisibilityCheck &visibility = *VisibilityCheck::GetInstance( *m_Context );
343 visibility.setMesh(md.mm()->id(),&mesh );
344 visibility.m_plugcontext = glContext;
345
346 for( CMeshO::FaceIterator fi=mesh.face.begin(); fi!=mesh.face.end(); ++fi )
347 fi->Q() = 0.0f;
348
349 foreach( RasterModel *rm, activeRasters )
350 {
351 visibility.setRaster( rm );
352 visibility.checkVisibility();
353 for( CMeshO::FaceIterator fi=mesh.face.begin(); fi!=mesh.face.end(); ++fi )
354 if( visibility.isFaceVisible(fi) )
355 fi->Q() += 1.0f;
356 }
357
358 if( par.getBool("normalizeQuality") )
359 {
360 const float normFactor = 1.0f / md.rasterList.size();
361 for( CMeshO::FaceIterator fi=mesh.face.begin(); fi!=mesh.face.end(); ++fi )
362 fi->Q() *= normFactor;
363 }
364
365 break;
366 }
367 }
368
369
370 foreach( RasterModel *rm, md.rasterList )
371 {
372 rm->shot = *initialShots.begin();
373 initialShots.erase( initialShots.begin() );
374 }
375
376 VisibilityCheck::ReleaseInstance();
377
378
379 delete m_Context;
380 m_Context = NULL;
381
382 glPopAttrib();
383 glContext->doneCurrent();
384
385
386 return retValue;
387 }
388
389
getNeighbors(CVertexO * v,NeighbSet & neighb) const390 void FilterImgPatchParamPlugin::getNeighbors( CVertexO *v,
391 NeighbSet &neighb ) const
392 {
393 vcg::face::Pos<CFaceO> p( v->VFp(), v ), ori = p;
394 do
395 {
396 neighb.insert( p.F() );
397 p.FlipF();
398 p.FlipE();
399 } while( ori != p );
400 }
401
402
getFaceNeighbors(CFaceO * f,NeighbSet & neighb) const403 void FilterImgPatchParamPlugin::getFaceNeighbors( CFaceO *f,
404 NeighbSet &neighb ) const
405 {
406 getNeighbors( f->V(0), neighb );
407 getNeighbors( f->V(1), neighb );
408 getNeighbors( f->V(2), neighb );
409 }
410
411
boundaryOptimization(CMeshO & mesh,VisibleSet & faceVis,bool mostFrontFacing)412 void FilterImgPatchParamPlugin::boundaryOptimization( CMeshO &mesh,
413 VisibleSet &faceVis,
414 bool mostFrontFacing )
415 {
416 std::set<CFaceO*> toOptim;
417
418
419 vcg::tri::UpdateFlags<CMeshO>::FaceClearV(mesh);
420
421 // Collects the faces belonging to boundaries (namely faces for which at least one adjacent
422 // face has a different reference image), so as to initialize the optimization step.
423 for( CMeshO::FaceIterator f=mesh.face.begin(); f!=mesh.face.end(); ++f )
424 {
425 // Checks each of the three edges of the current face. If the opposite face has a different
426 // reference image, the 1-ring neighborhood of this edge is added to the processing queue.
427 vcg::face::Pos<CFaceO> p( &*f, f->V(0) );
428 for( int i=0; i<3; ++i )
429 {
430 const CFaceO *f2 = p.FFlip();
431 if( !f2->IsV() )
432 if( faceVis[f2].ref() != faceVis[f].ref() )
433 {
434 NeighbSet neighb;
435 getNeighbors( p.V(), neighb );
436 getNeighbors( p.VFlip(), neighb );
437 for( NeighbSet::iterator n=neighb.begin(); n!=neighb.end(); ++n )
438 toOptim.insert( *n );
439 }
440 p.FlipV();
441 p.FlipE();
442 }
443 f->SetV();
444 }
445
446
447 // The optimization is a greedy approach that changes the reference image of a face in order to reduce
448 // the number of different images adjacent to that face.
449 while( !toOptim.empty() )
450 {
451 // Extract a face from the queue.
452 CFaceO *f = *toOptim.begin();
453 toOptim.erase( toOptim.begin() );
454
455
456 // Counts how many times appears each reference image in the 1-ring neighborhood of this face.
457 NeighbSet neighb;
458 getFaceNeighbors( f, neighb );
459
460 QMap<RasterModel*,int> neighbRefCount;
461
462 for( NeighbSet::iterator n=neighb.begin(); n!=neighb.end(); ++n )
463 if( *n && *n!=f )
464 {
465 RasterModel *neighbRef = faceVis[*n].ref();
466 QMap<RasterModel*,int>::iterator nFound = neighbRefCount.find( neighbRef );
467 if( nFound == neighbRefCount.end() )
468 neighbRefCount[neighbRef] = 1;
469 else
470 (*nFound) ++;
471 }
472
473
474 if( mostFrontFacing )
475 {
476 // Look for the one that appears the most, and that belongs to the list of visible rasters
477 // of the considered face.
478 std::vector<RasterModel*> appearsMost;
479 int nbMaxAppear = 0;
480
481 for( QMap<RasterModel*,int>::iterator n=neighbRefCount.begin(); n!=neighbRefCount.end(); ++n )
482 if( n.value()>=nbMaxAppear && faceVis[f].contains(n.key()) )
483 {
484 if( n.value() > nbMaxAppear )
485 appearsMost.clear();
486
487 nbMaxAppear = n.value();
488 appearsMost.push_back( n.key() );
489 }
490
491
492 // If multiple neighboring reference images have the same number of occurrences, the one with the highest
493 // weight with respect to the current face is chosen.
494 RasterModel *candidate = faceVis[f].ref();
495
496 if( appearsMost.size() > 1 )
497 {
498 float maxWeight = -std::numeric_limits<float>::max();
499 for( std::vector<RasterModel*>::iterator r=appearsMost.begin(); r!=appearsMost.end(); ++r )
500 {
501 float weight = faceVis.getWeight( *r, *f );
502 if( weight > maxWeight )
503 {
504 maxWeight = weight;
505 candidate = *r;
506 }
507 }
508 }
509 else if( appearsMost.size() == 1 )
510 candidate = appearsMost.front();
511
512
513 // If the reference image of the current face is different from the candidate image, change it accordingly.
514 // Triangles of its neighborhood are reintroduced in the queue only if their reference images is different
515 // from the new one.
516 if( candidate != faceVis[f].ref() )
517 {
518 faceVis[f].setRef( candidate );
519 for( NeighbSet::iterator n=neighb.begin(); n!=neighb.end(); ++n )
520 if( *n && *n!=f && faceVis[*n].ref()!=candidate )
521 toOptim.insert( *n );
522 }
523 }
524 else
525 {
526 // Look for the one that appears the most, and that belongs to the list of visible rasters
527 // of the considered face.
528 RasterModel *appearsMost = faceVis[f].ref();
529 int nbMaxAppear = 0;
530
531 for( QMap<RasterModel*,int>::iterator n=neighbRefCount.begin(); n!=neighbRefCount.end(); ++n )
532 if( n.value()>nbMaxAppear && faceVis[f].contains(n.key()) )
533 {
534 nbMaxAppear = n.value();
535 appearsMost = n.key();
536 }
537
538
539 // If the reference image of the current face is different from the candidate image, change it accordingly.
540 // Triangles of its neighborhood are reintroduced in the queue only if their reference images is different
541 // from the new one.
542 if( appearsMost != faceVis[f].ref() )
543 {
544 faceVis[f].setRef( appearsMost );
545 for( NeighbSet::iterator n=neighb.begin(); n!=neighb.end(); ++n )
546 if( *n && *n!=f && faceVis[*n].ref()!=appearsMost )
547 toOptim.insert( *n );
548 }
549 }
550 }
551 }
552
553
cleanIsolatedTriangles(CMeshO & mesh,VisibleSet & faceVis)554 int FilterImgPatchParamPlugin::cleanIsolatedTriangles( CMeshO &mesh,
555 VisibleSet &faceVis )
556 {
557 int nbTrianglesChanged = 0;
558
559
560 // For each triangle T...
561 for( CMeshO::FaceIterator f=mesh.face.begin(); f!=mesh.face.end(); ++f )
562 {
563 // Each reference image in the immediate edge neighborhood of T is gathered and counted.
564 QMap<RasterModel*,int> neighb;
565 for( int i=0; i<3; ++i )
566 if( f->FFp(i) )
567 {
568 RasterModel *r = faceVis[ f->FFp(i) ].ref();
569 if( neighb.contains(r) )
570 neighb[r] ++;
571 else
572 neighb[r] = 1;
573 }
574
575 // If the reference image of T doesn't appear in its neighborhood, it seems that T is isolated.
576 // In that case, the reference image that appears the most in its neighborhood is chosen as
577 // the new reference image of T.
578 if( !neighb.contains(faceVis[f].ref()) )
579 {
580 RasterModel *appearsMost = NULL;
581 int nAppearanceMax = 0;
582
583 for( QMap<RasterModel*,int>::iterator n=neighb.begin(); n!=neighb.end(); ++n )
584 if( n.value() > nAppearanceMax )
585 {
586 appearsMost = n.key();
587 nAppearanceMax = n.value();
588 }
589
590 if( appearsMost )
591 {
592 faceVis[f].setRef( appearsMost );
593 nbTrianglesChanged ++;
594 }
595 }
596 }
597
598
599 return nbTrianglesChanged;
600 }
601
602
extractPatches(RasterPatchMap & patches,PatchVec & nullPatches,CMeshO & mesh,VisibleSet & faceVis,QList<RasterModel * > & rasterList)603 int FilterImgPatchParamPlugin::extractPatches( RasterPatchMap &patches,
604 PatchVec &nullPatches,
605 CMeshO &mesh,
606 VisibleSet &faceVis,
607 QList<RasterModel*> &rasterList )
608 {
609 int nbPatches = 0;
610
611 foreach( RasterModel *rm, rasterList )
612 patches[rm] = PatchVec();
613
614 for( CMeshO::FaceIterator fSeed=mesh.face.begin(); fSeed!=mesh.face.end(); ++fSeed )
615 if( fSeed->IsV() )
616 {
617 std::queue<CFaceO*> seedFillQueue;
618 seedFillQueue.push( &*fSeed );
619 fSeed->ClearV();
620
621 Patch patch;
622 patch.ref = faceVis[fSeed].ref();
623
624 do
625 {
626 CFaceO *f = seedFillQueue.front();
627 seedFillQueue.pop();
628
629 patch.faces.push_back( f );
630
631 for( int i=0; i<3; ++i )
632 {
633 CFaceO *fAdj = f->FFp(i);
634 if( fAdj && fAdj->IsV() && faceVis[fAdj].ref()==patch.ref )
635 {
636 fAdj->ClearV();
637 seedFillQueue.push( fAdj );
638 }
639 }
640 } while( !seedFillQueue.empty() );
641
642 if( patch.ref )
643 {
644 patches[patch.ref].push_back( patch );
645 ++ nbPatches;
646 }
647 else
648 nullPatches.push_back( patch );
649 }
650
651 return nbPatches;
652 }
653
654
constructPatchBoundary(Patch & p,VisibleSet & faceVis)655 void FilterImgPatchParamPlugin::constructPatchBoundary( Patch &p,
656 VisibleSet &faceVis )
657 {
658 for( std::vector<CFaceO*>::iterator f=p.faces.begin(); f!=p.faces.end(); ++f )
659 {
660 RasterModel *fRef = faceVis[*f].ref();
661 vcg::face::Pos<CFaceO> pos( *f, (*f)->V(0) );
662
663 for( int i=0; i<3; ++i )
664 {
665 const CFaceO *f2 = pos.FFlip();
666 if(faceVis[f2].ref() && faceVis[f2].ref()!=fRef )
667 {
668 NeighbSet neighb;
669 getNeighbors( pos.V(), neighb );
670 getNeighbors( pos.VFlip(), neighb );
671 for( NeighbSet::iterator n=neighb.begin(); n!=neighb.end(); ++n )
672 if( !(*n)->IsV() && faceVis[*n].ref()!=fRef && faceVis[*n].contains(fRef))
673 {
674 p.boundary.push_back( *n );
675 (*n)->SetV();
676 }
677 }
678 pos.FlipV();
679 pos.FlipE();
680 }
681 }
682
683 for( std::vector<CFaceO*>::iterator f=p.boundary.begin(); f!=p.boundary.end(); ++f )
684 (*f)->ClearV();
685 }
686
687
computePatchUV(CMeshO & mesh,RasterModel * rm,PatchVec & patches)688 void FilterImgPatchParamPlugin::computePatchUV( CMeshO &mesh,
689 RasterModel *rm,
690 PatchVec &patches )
691 {
692 // Recovers the view frustum of the current raster.
693 CMeshO::ScalarType zNear, zFar;
694 GlShot< Shotm >::GetNearFarPlanes( rm->shot, mesh.bbox, zNear, zFar );
695 if( zNear < 0.0001f )
696 zNear = 0.1f;
697 if( zFar < zNear )
698 zFar = zNear + 1000.0f;
699
700 CMeshO::ScalarType l, r, b, t, focal;
701 rm->shot.Intrinsics.GetFrustum( l, r, b, t, focal );
702
703 // Computes the camera perspective projection matrix from the frustum values.
704 Matrix44m camProj;
705 camProj.SetZero();
706 camProj[0][0] = 2.0f*focal / (r-l);
707 camProj[0][2] = (r+l) / (r-l);
708 camProj[1][1] = 2.0f*focal / (t-b);
709 camProj[1][2] = (t+b) / (t-b);
710 camProj[2][2] = (zNear+zFar) / (zNear-zFar);
711 camProj[2][3] = 2.0f*zNear*zFar / (zNear-zFar);
712 camProj[3][2] = -1.0f;
713
714 Matrix44m cam2clip;
715 cam2clip.SetZero();
716 cam2clip[0][0] = cam2clip[0][3] = 0.5f * rm->shot.Intrinsics.ViewportPx.X();
717 cam2clip[1][1] = cam2clip[1][3] = 0.5f * rm->shot.Intrinsics.ViewportPx.Y();
718 cam2clip[2][2] = cam2clip[3][3] = 1.0f;
719
720 // Computes the full transform that goes from the mesh local space to the camera clipping space.
721 Matrix44m mesh2clip = cam2clip * camProj * rm->shot.GetWorldToExtrinsicsMatrix();
722
723 for( PatchVec::iterator p=patches.begin(); p!=patches.end(); ++p )
724 {
725 // Resets the UV bounding box of the patch, and allocate the array containing the UV coordinates
726 // for the boundary faces.
727 p->bbox.SetNull();
728 p->boundaryUV.clear();
729 p->boundaryUV.reserve( p->boundary.size() );
730
731 // Computes UV coordinates for internal patch faces, and update the bounding box accordingly.
732 for( std::vector<CFaceO*>::iterator f=p->faces.begin(); f!=p->faces.end(); ++f )
733 for( int i=0; i<3; ++i )
734 {
735 Point3m &vp = (*f)->V(i)->P();
736
737 (*f)->WT(i).U() = mesh2clip.GetRow3(0)*vp + mesh2clip[0][3];
738 (*f)->WT(i).V() = mesh2clip.GetRow3(1)*vp + mesh2clip[1][3];
739 (*f)->WT(i).P() *= 1.0f / (mesh2clip.GetRow3(3)*vp + mesh2clip[3][3]);
740
741 p->bbox.Add( (*f)->WT(i).P() );
742 }
743
744 // Computes UV coordinates for boundary patch faces, and update the bounding box accordingly.
745 for( std::vector<CFaceO*>::iterator f=p->boundary.begin(); f!=p->boundary.end(); ++f )
746 {
747 TriangleUV fuv;
748 for( int i=0; i<3; ++i )
749 {
750 Point3m &vp = (*f)->V(i)->P();
751
752 fuv.v[i].U() = mesh2clip.GetRow3(0)*vp + mesh2clip[0][3];
753 fuv.v[i].V() = mesh2clip.GetRow3(1)*vp + mesh2clip[1][3];
754 fuv.v[i].P() *= 1.0f / (mesh2clip.GetRow3(3)*vp + mesh2clip[3][3]);
755
756 p->bbox.Add( fuv.v[i].P() );
757 }
758 p->boundaryUV.push_back( fuv );
759 }
760 }
761 }
762
763
mergeOverlappingPatches(PatchVec & patches)764 void FilterImgPatchParamPlugin::mergeOverlappingPatches( PatchVec &patches )
765 {
766 if( patches.size() <= 1 )
767 return;
768
769
770 for( PatchVec::iterator p=patches.begin(); p!=patches.end(); ++p )
771 p->valid = true;
772
773
774 float globalGain = 0.0f;
775 for( PatchVec::iterator p1=patches.begin(); p1!=patches.end(); ++p1 )
776 if( p1->valid )
777 {
778 float maxOccupancyGain = -globalGain;
779 PatchVec::iterator candidate = patches.end();
780
781 for( PatchVec::iterator p2=patches.begin(); p2!=patches.end(); ++p2 )
782 if( p2!=p1 && p2->valid && p2->bbox.Collide(p1->bbox) )
783 {
784 vcg::Box2f boxMerge = p1->bbox;
785 boxMerge.Add( p2->bbox );
786 float occupancyGain = p1->bbox.Area() + p2->bbox.Area() - boxMerge.Area();
787
788 if( occupancyGain > maxOccupancyGain )
789 {
790 maxOccupancyGain = occupancyGain;
791 candidate = p2;
792 }
793 }
794
795 if( candidate != patches.end() )
796 {
797 p1->faces.insert( p1->faces.end(), candidate->faces.begin(), candidate->faces.end() );
798 p1->boundary.insert( p1->boundary.end(), candidate->boundary.begin(), candidate->boundary.end() );
799 p1->boundaryUV.insert( p1->boundaryUV.end(), candidate->boundaryUV.begin(), candidate->boundaryUV.end() );
800 p1->bbox.Add( candidate->bbox );
801 candidate->valid = false;
802 globalGain += maxOccupancyGain;
803 }
804 }
805
806
807 for( PatchVec::iterator p=patches.begin(); p!=patches.end(); )
808 if( p->valid )
809 ++ p;
810 else
811 {
812 *p = patches.back();
813 patches.pop_back();
814 }
815 }
816
817
patchPacking(RasterPatchMap & patches,int textureGutter,bool allowUVStretching)818 void FilterImgPatchParamPlugin::patchPacking( RasterPatchMap &patches,
819 int textureGutter,
820 bool allowUVStretching )
821 {
822 std::vector<vcg::Box2f> patchRect;
823 std::vector<vcg::Similarity2f> patchPackingTr;
824
825
826 // Computes the foreseen texture edge length based on the total area covered by patches' boxes.
827 float totalArea = 0;
828
829 for( RasterPatchMap::iterator rp=patches.begin(); rp!=patches.end(); ++rp )
830 for( PatchVec::iterator p=rp->begin(); p!=rp->end(); ++p )
831 {
832 p->bbox.Offset( vcg::Point2f(textureGutter,textureGutter) );
833 patchRect.push_back( p->bbox );
834 totalArea += p->bbox.Area();
835 }
836
837 if( patchRect.empty() )
838 return;
839
840 float edgeLen = std::sqrt( totalArea );
841
842
843 // Performs the packing.
844 vcg::Point2f coveredArea(0,0);
845 vcg::RectPacker<float>::Pack( patchRect, vcg::Point2i(edgeLen,edgeLen), patchPackingTr, coveredArea );
846
847
848 // Applies to the UV coordinates the transformations computed by the packing algorithm, as well as a scaling
849 // so as to make them ranging the interval [0,1]x[0,1].
850 float scaleU, scaleV;
851
852 if( allowUVStretching )
853 {
854 scaleU = 1.0f / coveredArea.X();
855 scaleV = 1.0f / coveredArea.Y();
856 }
857 else
858 scaleU = scaleV = 1.0f / std::max( coveredArea.X(), coveredArea.Y() );
859
860 int n = 0;
861 for( RasterPatchMap::iterator rp=patches.begin(); rp!=patches.end(); ++rp )
862 for( PatchVec::iterator p=rp->begin(); p!=rp->end(); ++p, ++n )
863 {
864 vcg::Similarity2f &tr = patchPackingTr[n];
865 float c = std::cos( tr.rotRad );
866 float s = std::sin( tr.rotRad );
867
868 p->img2tex.SetIdentity();
869 p->img2tex[0][0] = c * tr.sca * scaleU;
870 p->img2tex[0][1] = -s * tr.sca * scaleU;
871 p->img2tex[0][3] = tr.tra.X() * scaleU;
872 p->img2tex[1][0] = s * tr.sca * scaleV;
873 p->img2tex[1][1] = c * tr.sca * scaleV;
874 p->img2tex[1][3] = tr.tra.Y() * scaleV;
875
876 for( std::vector<CFaceO*>::iterator f=p->faces.begin(); f!=p->faces.end(); ++f )
877 for( int i=0; i<3; ++i )
878 {
879 (*f)->WT(i).P() = tr * (*f)->WT(i).P();
880 (*f)->WT(i).U() *= scaleU;
881 (*f)->WT(i).V() *= scaleV;
882 }
883
884 for( std::vector<TriangleUV>::iterator f=p->boundaryUV.begin(); f!=p->boundaryUV.end(); ++f )
885 for( int i=0; i<3; ++i )
886 {
887 f->v[i].P() = tr * f->v[i].P();
888 f->v[i].U() *= scaleU;
889 f->v[i].V() *= scaleV;
890 }
891 }
892 }
893
894
patchBasedTextureParameterization(RasterPatchMap & patches,PatchVec & nullPatches,int meshid,CMeshO & mesh,QList<RasterModel * > & rasterList,RichParameterSet & par)895 void FilterImgPatchParamPlugin::patchBasedTextureParameterization( RasterPatchMap &patches,
896 PatchVec &nullPatches,
897 int meshid,
898 CMeshO &mesh,
899 QList<RasterModel*> &rasterList,
900 RichParameterSet &par )
901 {
902 // Computes the visibility set for all mesh faces. It contains the set of all images
903 // into which the face is visible, as well as a reference image, namely the one with
904 // the most orthogonal viewing angle.
905 QElapsedTimer t; t.start();
906 int weightMask = VisibleSet::W_ORIENTATION;
907 if( par.getBool("useDistanceWeight") )
908 weightMask |= VisibleSet::W_DISTANCE;
909 if( par.getBool("useImgBorderWeight") )
910 weightMask |= VisibleSet::W_IMG_BORDER;
911 if( par.getBool("useAlphaWeight") )
912 weightMask |= VisibleSet::W_IMG_ALPHA;
913 VisibleSet faceVis( *m_Context,glContext,meshid, mesh, rasterList, weightMask );
914 Log( "VISIBILITY CHECK: %.3f sec.", 0.001f*t.elapsed() );
915
916
917 // Boundary optimization: the goal is to produce more regular boundaries between surface regions
918 // associated to different reference images.
919 t.start();
920 boundaryOptimization( mesh, faceVis, true );
921 Log( "BOUNDARY OPTIMIZATION: %.3f sec.", 0.001f*t.elapsed() );
922
923
924 // Incorporates patches compounds of only one triangles to one of their neighbours.
925 if( par.getBool("cleanIsolatedTriangles") )
926 {
927 t.start();
928 int triCleaned = cleanIsolatedTriangles( mesh, faceVis );
929 Log( "CLEANING ISOLATED TRIANGLES: %.3f sec.", 0.001f*t.elapsed() );
930 Log( " * %i triangles cleaned.", triCleaned );
931 }
932
933
934 // Recovers patches by extracting connected components of faces having the same reference image.
935 t.start();
936 float oldArea = computeTotalPatchArea( patches );
937 int nbPatches = extractPatches( patches, nullPatches, mesh, faceVis, rasterList );
938 Log( "PATCH EXTRACTION: %.3f sec.", 0.001f*t.elapsed() );
939 Log( " * %i patches extracted, %i null patches.", nbPatches, nullPatches.size() );
940
941
942 // Extends each patch so as to include faces that belong to the other side of its boundary.
943 t.start();
944 oldArea = computeTotalPatchArea( patches );
945 for( RasterPatchMap::iterator rp=patches.begin(); rp!=patches.end(); ++rp )
946 for( PatchVec::iterator p=rp->begin(); p!=rp->end(); ++p )
947 constructPatchBoundary( *p, faceVis );
948 Log( "PATCH EXTENSION: %.3f sec.", 0.001f*t.elapsed() );
949
950
951 // Compute the UV coordinates of all patches by projecting them onto their reference images.
952 // UV are then defined in image space, ranging from [0,0] to [w,h].
953 t.start();
954 oldArea = computeTotalPatchArea( patches );
955 for( RasterPatchMap::iterator rp=patches.begin(); rp!=patches.end(); ++rp )
956 computePatchUV( mesh, rp.key(), rp.value() );
957 Log( "PATCHES UV COMPUTATION: %.3f sec.", 0.001f*t.elapsed() );
958
959
960 // Merge patches so as to reduce the occupied texture area when their bounding boxes overlap.
961 t.start();
962 oldArea = computeTotalPatchArea( patches );
963 for( RasterPatchMap::iterator rp=patches.begin(); rp!=patches.end(); ++rp )
964 mergeOverlappingPatches( *rp );
965 Log( "PATCH MERGING: %.3f sec.", 0.001f*t.elapsed() );
966 Log( " * Area reduction: %.1f%%.", 100.0f*computeTotalPatchArea(patches)/oldArea );
967 Log( " * Patches number reduced from %i to %i.", nbPatches, computePatchCount(patches) );
968
969
970 // Patches' bounding boxes are packed in texture space. After this operation, boxes are still defined
971 // in the space of their patches' reference images but UV coordinates are all defined in a common texture
972 // space, ranging from [0,0] to [1,1].
973 t.start();
974 patchPacking( patches, par.getInt("textureGutter"), par.getBool("stretchingAllowed") );
975 Log( "PATCH TEXTURE PACKING: %.3f sec.", 0.001f*t.elapsed() );
976
977
978 // Clear the UV coordinates for patches that are not visible in any image.
979 for( PatchVec::iterator p=nullPatches.begin(); p!=nullPatches.end(); ++p )
980 for( std::vector<CFaceO*>::iterator f=p->faces.begin(); f!=p->faces.end(); ++f )
981 for( int i=0; i<3; ++i )
982 (*f)->WT(i).P() = vcg::Point2f(0.0f,0.0f);
983
984 for(CMeshO::FaceIterator fi=mesh.face.begin(); fi!=mesh.face.end();++fi)
985 for(int i=0;i<3;++i) fi->WT(i).N()=0;
986 }
987
988
computeTotalPatchArea(RasterPatchMap & patches)989 float FilterImgPatchParamPlugin::computeTotalPatchArea( RasterPatchMap &patches )
990 {
991 float totalArea = 0;
992
993 for( RasterPatchMap::iterator rp=patches.begin(); rp!=patches.end(); ++rp )
994 for( PatchVec::iterator p=rp->begin(); p!=rp->end(); ++p )
995 totalArea += p->bbox.Area();
996
997 return totalArea;
998 }
999
1000
computePatchCount(RasterPatchMap & patches)1001 int FilterImgPatchParamPlugin::computePatchCount( RasterPatchMap &patches )
1002 {
1003 int nbPatches = 0;
1004
1005 for( RasterPatchMap::iterator rp=patches.begin(); rp!=patches.end(); ++rp )
1006 nbPatches += rp->size();
1007
1008 return nbPatches;
1009 }
1010
1011
1012
1013
1014 MESHLAB_PLUGIN_NAME_EXPORTER(FilterImgPatchParamPlugin)
1015