File: filter_texture.cpp

package info (click to toggle)
meshlab 1.3.2+dfsg1-4
  • links: PTS, VCS
  • area: main
  • in suites: buster
  • size: 21,096 kB
  • ctags: 33,630
  • sloc: cpp: 224,813; ansic: 8,170; xml: 119; makefile: 80
file content (920 lines) | stat: -rw-r--r-- 45,789 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
/****************************************************************************
* MeshLab                                                           o o     *
* A versatile mesh processing toolbox                             o     o   *
*                                                                _   O  _   *
* Copyright(C) 2005                                                \/)\/    *
* Visual Computing Lab                                            /\/|      *
* ISTI - Italian National Research Council                           |      *
*                                                                    \      *
* All rights reserved.                                                      *
*                                                                           *
* This program is free software; you can redistribute it and/or modify      *   
* it under the terms of the GNU General Public License as published by      *
* the Free Software Foundation; either version 2 of the License, or         *
* (at your option) any later version.                                       *
*                                                                           *
* This program is distributed in the hope that it will be useful,           *
* but WITHOUT ANY WARRANTY; without even the implied warranty of            *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the             *
* GNU General Public License (http://www.gnu.org/licenses/gpl.txt)          *
* for more details.                                                         *
*                                                                           *
****************************************************************************/
#include <vcg/math/base.h>
#include <QtGui>
#define EIGEN_YES_I_KNOW_SPARSE_MODULE_IS_NOT_STABLE_YET
#include <eigen3/Eigen/Sparse>
#include <eigen3/Eigen/src/SparseCore/SparseMatrix.h>
#include <eigen3/unsupported/Eigen/src/SparseExtra/DynamicSparseMatrix.h>
#include <eigen3/unsupported/Eigen/SparseExtra>

#include <float.h>
#include <stdlib.h>
#include "filter_texture.h"
#include "pushpull.h"
#include "rastering.h"
#include <vcg/complex/algorithms/update/texture.h>
#include<wrap/io_trimesh/export_ply.h>
#include <vcg/complex/algorithms/parametrization/voronoi_atlas.h>

using namespace vcg;

FilterTexturePlugin::FilterTexturePlugin() 
{ 
    typeList << FP_VORONOI_ATLAS
            << FP_UV_WEDGE_TO_VERTEX
            << FP_UV_VERTEX_TO_WEDGE
            << FP_BASIC_TRIANGLE_MAPPING
            << FP_SET_TEXTURE
            << FP_PLANAR_MAPPING
            << FP_COLOR_TO_TEXTURE
            << FP_TRANSFER_TO_TEXTURE
            << FP_TEX_TO_VCOLOR_TRANSFER;

    foreach(FilterIDType tt , types())
        actionList << new QAction(filterName(tt), this);
}

QString FilterTexturePlugin::filterName(FilterIDType filterId) const 
{
    switch(filterId)
    {
    case FP_VORONOI_ATLAS : return QString("Parametrization: Voronoi Atlas");
    case FP_UV_WEDGE_TO_VERTEX : return QString("Convert PerWedge UV into PerVertex UV");
    case FP_UV_VERTEX_TO_WEDGE : return QString("Convert PerVertex UV into PerWedge UV");
    case FP_BASIC_TRIANGLE_MAPPING : return QString("Parametrization: Trivial Per-Triangle ");
    case FP_PLANAR_MAPPING : return QString("Parametrization: Flat Plane ");
    case FP_SET_TEXTURE : return QString("Set Texture");
    case FP_COLOR_TO_TEXTURE : return QString("Vertex Color to Texture");
    case FP_TRANSFER_TO_TEXTURE : return QString("Transfer Vertex Attributes to Texture (between 2 meshes)");
    case FP_TEX_TO_VCOLOR_TRANSFER : return QString("Texture to Vertex Color (between 2 meshes)"); // TODO Choose a name
    default : assert(0);
    }
}

// Info() must return the longer string describing each filtering action 
// (this string is used in the About plugin dialog)
QString FilterTexturePlugin::filterInfo(FilterIDType filterId) const
{
    switch(filterId)
    {
    case FP_VORONOI_ATLAS :  return QString("Build an atlased parametrization based on a geodesic voronoi partitioning of the surface and parametrizing each region using Harmonic Mapping. For the  parametrization of the disk like voronoi regions the used method is: <br><b>Ulrich Pinkall, Konrad Polthier</b><br>\
                                            <i>Computing Discrete Minimal Surfaces and Their Conjugates</i> <br>\
                                            Experimental Mathematics, Vol 2 (1), 1993<br> .");
    case FP_UV_WEDGE_TO_VERTEX : return QString("Converts per Wedge Texture Coordinates to per Vertex Texture Coordinates splitting vertices with not coherent Wedge coordinates.");
    case FP_UV_VERTEX_TO_WEDGE : return QString("Converts per Vertex Texture Coordinates to per Wedge Texture Coordinates. It does not merge superfluos vertices...");
    case FP_BASIC_TRIANGLE_MAPPING : return QString("Builds a trivial triangle-by-triangle parametrization. <br> Two methods are provided, the first maps maps all triangles into equal sized triangles, while the second one adapt the size of the triangles in texture space to their original size.");
    case FP_PLANAR_MAPPING : return QString("Builds a trivial flat plane parametrization.");
    case FP_SET_TEXTURE : return QString("Set a texture associated with current mesh parametrization.<br>"
                                         "If the texture provided exists it will be simply associated to the current mesh else a dummy texture will be created and saved in the same directory.");
    case FP_COLOR_TO_TEXTURE : return QString("Fills the specified texture accordingly to per vertex color.");
    case FP_TRANSFER_TO_TEXTURE : return QString("Transfer texture color, vertex color or normal from one mesh to another's texture. This is generally used to restore detail lost in simplification");
    case FP_TEX_TO_VCOLOR_TRANSFER : return QString("Generates Vertex Color values picking color from another mesh texture.");
    default : assert(0);
    }
    return QString("Unknown Filter");
}

int FilterTexturePlugin::getPreConditions(QAction *a) const
{
    switch (ID(a))
    {
    case FP_UV_WEDGE_TO_VERTEX : return MeshModel::MM_WEDGTEXCOORD;
    case FP_UV_VERTEX_TO_WEDGE : return MeshModel::MM_VERTTEXCOORD;
    case FP_BASIC_TRIANGLE_MAPPING :
    case FP_VORONOI_ATLAS :
    case FP_PLANAR_MAPPING : return MeshModel::MM_FACENUMBER;
    case FP_SET_TEXTURE : return MeshModel::MM_WEDGTEXCOORD;
    case FP_COLOR_TO_TEXTURE : return MeshModel::MM_VERTCOLOR | MeshModel::MM_WEDGTEXCOORD;
    case FP_TRANSFER_TO_TEXTURE : return MeshModel::MM_NONE;
    case FP_TEX_TO_VCOLOR_TRANSFER : return MeshModel::MM_NONE;
    default: assert(0);
    }
    return MeshModel::MM_NONE;
}

int FilterTexturePlugin::getRequirements(QAction *a)
{
    switch (ID(a))
    {
    case FP_VORONOI_ATLAS :
    case FP_UV_WEDGE_TO_VERTEX :
    case FP_UV_VERTEX_TO_WEDGE :
    case FP_BASIC_TRIANGLE_MAPPING :
    case FP_PLANAR_MAPPING :
    case FP_SET_TEXTURE : return MeshModel::MM_NONE;
    case FP_COLOR_TO_TEXTURE : return MeshModel::MM_FACEFACETOPO;
    case FP_TRANSFER_TO_TEXTURE : return MeshModel::MM_NONE;
    case FP_TEX_TO_VCOLOR_TRANSFER : return MeshModel::MM_NONE;
    default: assert(0);
    }
    return MeshModel::MM_NONE;
}

int FilterTexturePlugin::postCondition( QAction *a) const
{
    switch (ID(a))
    {
    case FP_VORONOI_ATLAS : return MeshModel::MM_UNKNOWN;
    case FP_UV_WEDGE_TO_VERTEX : return MeshModel::MM_UNKNOWN;
    case FP_UV_VERTEX_TO_WEDGE : return MeshModel::MM_WEDGTEXCOORD;
    case FP_PLANAR_MAPPING : return MeshModel::MM_WEDGTEXCOORD;
    case FP_BASIC_TRIANGLE_MAPPING : return MeshModel::MM_WEDGTEXCOORD;
    case FP_SET_TEXTURE : return MeshModel::MM_UNKNOWN;
    case FP_COLOR_TO_TEXTURE : return MeshModel::MM_UNKNOWN;
    case FP_TRANSFER_TO_TEXTURE : return MeshModel::MM_UNKNOWN;
    case FP_TEX_TO_VCOLOR_TRANSFER : return MeshModel::MM_UNKNOWN;
    default: assert(0);
    }
    return MeshModel::MM_NONE;
}

// The FilterClass describes in which generic class of filters it fits. 
// This choice affect the submenu in which each filter will be placed 
// More than a single class can be choosen.
FilterTexturePlugin::FilterClass FilterTexturePlugin::getClass(QAction *a)
{
    switch(ID(a))
    {
    case FP_VORONOI_ATLAS :
    case FP_UV_WEDGE_TO_VERTEX :
    case FP_UV_VERTEX_TO_WEDGE :
    case FP_BASIC_TRIANGLE_MAPPING :
    case FP_PLANAR_MAPPING :
    case FP_SET_TEXTURE :
    case FP_COLOR_TO_TEXTURE :
    case FP_TRANSFER_TO_TEXTURE : return MeshFilterInterface::Texture;
    case FP_TEX_TO_VCOLOR_TRANSFER : return FilterClass(MeshFilterInterface::VertexColoring + MeshFilterInterface::Texture);
    default : assert(0);
    }
    return MeshFilterInterface::Generic;
}

static QString extractFilenameWOExt(MeshModel* mm)
{
    QFileInfo fi(mm->fullName());
    return fi.baseName();
}

// This function define the needed parameters for each filter. Return true if the filter has some parameters
// it is called every time, so you can set the default value of parameters according to the mesh
// For each parmeter you need to define, 
// - the name of the parameter, 
// - the string shown in the dialog 
// - the default value
// - a possibly long string describing the meaning of that parameter (shown as a popup help in the dialog)
void FilterTexturePlugin::initParameterSet(QAction *action, MeshDocument &md, RichParameterSet & parlst) 
{
    switch(ID(action)) {
    case FP_VORONOI_ATLAS :
      parlst.addParam(new RichInt("regionNum", 10, "Approx. Region Num", "An estimation of the number of regions that must be generated. Smaller regions could lead to parametrizations with smaller distortion."));
      parlst.addParam(new RichBool("overlapFlag", false, "Overlap", "If checked the resulting parametrization will be composed by <i>overlapping</i> regions, e.g. the resulting mesh will have duplicated faces: each region will have a ring of ovelapping duplicate faces that will ensure that border regions will be parametrized in the atlas twice. This is quite useful for building mipmap robust atlases"));
        break;
    case FP_UV_WEDGE_TO_VERTEX :
        break;
    case FP_PLANAR_MAPPING :
      parlst.addParam(new RichEnum("projectionPlane", 0, QStringList("XY") << "XZ"<<"YZ","Projection plane","Choose the projection plane"));
      parlst.addParam(new RichBool("aspectRatio", false, "Preserve Ratio", "If checked the resulting parametrization will preserve the original apsect ratio of the model otherwise it will fill up the whole 0..1 uv space"));
      break;
    case FP_BASIC_TRIANGLE_MAPPING :
        parlst.addParam(new RichInt("sidedim", 0, "Quads per line", "Indicates how many triangles have to be put on each line (every quad contains two triangles)\nLeave 0 for automatic calculation"));
        parlst.addParam(new RichInt("textdim", 1024, "Texture Dimension (px)", "Gives an indication on how big the texture is"));
        parlst.addParam(new RichInt("border", 2, "Inter-Triangle border (px)", "Specifies how many pixels to be left between triangles in parametrization domain"));
        parlst.addParam(new RichEnum("method", 1, QStringList("Basic") << "Space-optimizing", "Method", "Choose space optimizing to map smaller faces into smaller triangles in parametrizazion domain"));
        break;
    case FP_SET_TEXTURE : {
            QString fileName = extractFilenameWOExt(md.mm());
            fileName = fileName.append(".png");
            parlst.addParam(new RichString("textName", fileName, "Texture file", "If the file exists it will be associated to the mesh else a dummy one will be created"));
            parlst.addParam(new RichInt("textDim", 1024, "Texture Dimension (px)", "If the named texture doesn't exists the dummy one will be squared with this size"));
        }
        break;
    case FP_COLOR_TO_TEXTURE : {
            QString fileName = extractFilenameWOExt(md.mm());
            fileName = fileName.append("_color.png");
            parlst.addParam(new RichString("textName", fileName, "Texture file", "The texture file to be created"));
            parlst.addParam(new RichInt("textW", 1024, "Texture width (px)", "The texture width"));
            parlst.addParam(new RichInt("textH", 1024, "Texture height (px)", "The texture height"));
            parlst.addParam(new RichBool("overwrite", false, "Overwrite texture", "if current mesh has a texture will be overwritten (with provided texture dimension)"));
            parlst.addParam(new RichBool("assign", false, "Assign texture", "assign the newly created texture"));
            parlst.addParam(new RichBool("pullpush", true, "Fill texture", "if enabled the unmapped texture space is colored using a pull push filling algorithm, if false is set to black"));
        }
        break;
    case FP_TRANSFER_TO_TEXTURE : {
            QString fileName = extractFilenameWOExt(md.mm());
            fileName = fileName.append("_color.png");
            parlst.addParam(new RichMesh ("sourceMesh",md.mm(),&md, "Source Mesh",
                                          "The mesh that contains the source data that we want to transfer"));
            parlst.addParam(new RichMesh ("targetMesh",md.mm(),&md, "Target Mesh",
                                          "The mesh whose texture will be filled according to source mesh data"));
            parlst.addParam(new RichEnum("AttributeEnum", 0, QStringList("Vertex Color")  << "Vertex Normal" << "Vertex Quality"<< "Texture Color", "Color Data Source",
                                         "Choose what attribute has to be transferred onto the target texture. You can choos bettween Per vertex attributes (clor,normal,quality) or to transfer color information from source mesh texture"));
            parlst.addParam(new RichAbsPerc("upperBound", md.mm()->cm.bbox.Diag()/50.0, 0.0f, md.mm()->cm.bbox.Diag(),
                                            tr("Max Dist Search"), tr("Sample points for which we do not find anything whithin this distance are rejected and not considered for recovering data")));
            parlst.addParam(new RichString("textName", fileName, "Texture file", "The texture file to be created"));
            parlst.addParam(new RichInt("textW", 1024, "Texture width (px)", "The texture width"));
            parlst.addParam(new RichInt("textH", 1024, "Texture height (px)", "The texture height"));
            parlst.addParam(new RichBool("overwrite", false, "Overwrite Target Mesh Texture", "if target mesh has a texture will be overwritten (with provided texture dimension)"));
            parlst.addParam(new RichBool("assign", false, "Assign Texture", "assign the newly created texture to target mesh"));
            parlst.addParam(new RichBool("pullpush", true, "Fill texture", "if enabled the unmapped texture space is colored using a pull push filling algorithm, if false is set to black"));
        }
        break;
    case FP_TEX_TO_VCOLOR_TRANSFER : {
            parlst.addParam(new RichMesh ("sourceMesh",md.mm(),&md, "Source Mesh",
                                          "The mesh with associated texture that we want to sample from"));
            parlst.addParam(new RichMesh ("targetMesh",md.mm(),&md, "Target Mesh",
                                          "The mesh whose vertex color will be filled according to source mesh texture"));
            parlst.addParam(new RichAbsPerc("upperBound", md.mm()->cm.bbox.Diag()/50.0, 0.0f, md.mm()->cm.bbox.Diag(),
                                            tr("Max Dist Search"), tr("Sample points for which we do not find anything whithin this distance are rejected and not considered for recovering color")));
        }
        break;
    default: break; // do not add any parameter for the other filters
    }
}


/////// FUNCTIONS NEEDED BY "UV WEDGE TO VERTEX" FILTER
inline void ExtractVertex(const CMeshO & srcMesh, const CMeshO::FaceType & f, int whichWedge, const CMeshO & dstMesh, CMeshO::VertexType & v)
{
    (void)srcMesh;
    (void)dstMesh;
    // This is done to preserve every single perVertex property
    // perVextex Texture Coordinate is instead obtained from perWedge one.
    v.ImportData(*f.cV(whichWedge));
    v.T() = f.cWT(whichWedge);
}

inline bool CompareVertex(const CMeshO & m, const CMeshO::VertexType & vA, const CMeshO::VertexType & vB)
{
    (void)m;
    return (vA.cT() == vB.cT());
}

/////// FUNCTIONS NEEDED BY "BASIC PARAMETRIZATION" FILTER
inline int getLongestEdge(const CMeshO::FaceType & f)
{
    int res=0;
    const CMeshO::CoordType &p0=f.cP(0), &p1=f.cP(1), p2=f.cP(2);
    double  maxd01 = SquaredDistance(p0,p1);
    double  maxd12 = SquaredDistance(p1,p2);
    double  maxd20 = SquaredDistance(p2,p0);
    if(maxd01 > maxd12)
        if(maxd01 > maxd20)     res = 0;
    else                    res = 2;
    else
        if(maxd12 > maxd20)     res = 1;
    else                    res = 2;
    return res;
}

typedef Triangle2<CMeshO::FaceType::TexCoordType::ScalarType> Tri2;

inline void buildTrianglesCache(std::vector<Tri2> &arr, int maxLevels, float border, float quadSize, int idx=-1)
{
    assert(idx >= -1);
    Tri2 &t0 = arr[2*idx+2];
    Tri2 &t1 = arr[2*idx+3];
    if (idx == -1)
    {
        // build triangle 0
        t0.P(1).X() = quadSize - (0.5 + M_SQRT1_2)*border;
        t0.P(0).X() = 0.5 * border;
        t0.P(1).Y() = 1.0 - t0.P(0).X();
        t0.P(0).Y() = 1.0 - t0.P(1).X();
        t0.P(2).X() = t0.P(0).X();
        t0.P(2).Y() = t0.P(1).Y();
        // build triangle 1
        t1.P(1).X() = (0.5 + M_SQRT1_2)*border;
        t1.P(0).X() = quadSize - 0.5 * border;
        t1.P(1).Y() = 1.0 - t1.P(0).X();
        t1.P(0).Y() = 1.0 - t1.P(1).X();
        t1.P(2).X() = t1.P(0).X();
        t1.P(2).Y() = t1.P(1).Y();
    }
    else {
        // split triangle idx in t0 t1
        Tri2 &t = arr[idx];
        Tri2::CoordType midPoint = (t.P(0) + t.P(1)) / 2;
        Tri2::CoordType vec10 = (t.P(0) - t.P(1)).Normalize() * (border/2.0);
        t0.P(1) = t.P(0);
        t1.P(0) = t.P(1);
        t0.P(2) = midPoint + vec10;
        t1.P(2) = midPoint - vec10;
        t0.P(0) = t.P(2) + ( (t.P(0)-t.P(2)).Normalize() * border / M_SQRT2 );
        t1.P(1) = t.P(2) + ( (t.P(1)-t.P(2)).Normalize() * border / M_SQRT2 );
    }
    if (--maxLevels <= 0) return;
    buildTrianglesCache (arr, maxLevels, border, quadSize, 2*idx+2);
    buildTrianglesCache (arr, maxLevels, border, quadSize, 2*idx+3);
}

// ERROR CHECKING UTILITY
#define CheckError(x,y); if ((x)) {this->errorMessage = (y); return false;}
///////////////////////////////////////////////////////

template<typename T>
T log_2(const T num)
{
    return T(log(num) / log(T(2)));
}

// The Real Core Function doing the actual mesh processing.
bool FilterTexturePlugin::applyFilter(QAction *filter, MeshDocument &md, RichParameterSet &par, CallBackPos *cb)
{
    MeshModel &m=*(md.mm());
    switch(ID(filter))     {
    case FP_VORONOI_ATLAS : {
      MeshModel *baseModel=md.mm();
      baseModel->updateDataMask(MeshModel::MM_FACEFACETOPO);
      tri::UpdateTopology<CMeshO>::FaceFace(baseModel->cm);
      int nonManifVertNum=tri::Clean<CMeshO>::CountNonManifoldVertexFF(baseModel->cm,false);
      int nonManifEdgeNum=tri::Clean<CMeshO>::CountNonManifoldEdgeFF(baseModel->cm,false);

      if(nonManifVertNum>0 || nonManifEdgeNum>0)
      {
        Log("Mesh is not manifold\n:%i non manifold Vertices\n%i nonmanifold Edges\n",nonManifVertNum,nonManifEdgeNum);
        this->errorMessage = "Mesh is not manifold. See Log for details";
        return false;
      }

      MeshModel *paraModel=md.addNewMesh("","VoroAtlas",false);
      tri::VoronoiAtlas<CMeshO>::VoronoiAtlasParam pp;
      pp.sampleNum =par.getInt("regionNum");
      pp.overlap=par.getBool("overlapFlag");

      paraModel->updateDataMask(MeshModel::MM_WEDGTEXCOORD);
      // Note that CMesh has ocf texcoord and the append inside VoronoiAtlas class need that.
      // This is a design bug of the VCGLib...
      int bitToBeCleared =0;
      if(!baseModel->hasDataMask(MeshModel::MM_WEDGTEXCOORD)) bitToBeCleared |=MeshModel::MM_WEDGTEXCOORD;
      if(!baseModel->hasDataMask(MeshModel::MM_VERTTEXCOORD)) bitToBeCleared |=MeshModel::MM_VERTTEXCOORD;
      baseModel->updateDataMask(MeshModel::MM_WEDGTEXCOORD | MeshModel::MM_VERTTEXCOORD);
      tri::VoronoiAtlas<CMeshO>::Build(baseModel->cm,paraModel->cm, pp);
      if(pp.overlap==false)
        tri::Clean<CMeshO>::RemoveDuplicateVertex(paraModel->cm);

      tri::UpdateNormals<CMeshO>::PerVertexPerFace(paraModel->cm);

      baseModel->clearDataMask(bitToBeCleared);
      Log("Voronoi Atlas: Completed Processing in %i iterations",pp.vas.iterNum);
      Log("Asked %i generated %i regions",pp.sampleNum,pp.vas.regionNum);
      Log("Unwrap Time   %6.3f s", float(pp.vas.unwrapTime) / CLOCKS_PER_SEC);
      Log("Voronoi Time  %6.3f s", float(pp.vas.voronoiTime) / CLOCKS_PER_SEC);
      Log("Sampling Time %6.3f s", float(pp.vas.samplingTime) / CLOCKS_PER_SEC);
      }
      break;

    case FP_UV_WEDGE_TO_VERTEX : {
            int vn = m.cm.vn;
            m.updateDataMask(MeshModel::MM_VERTTEXCOORD);
            tri::AttributeSeam::SplitVertex(m.cm, ExtractVertex, CompareVertex);
            if (m.cm.vn != vn)
            {
                m.clearDataMask(MeshModel::MM_FACEFACETOPO);
                m.clearDataMask(MeshModel::MM_VERTFACETOPO);
            }
        }
        break;

    case FP_UV_VERTEX_TO_WEDGE : {
            m.updateDataMask(MeshModel::MM_WEDGTEXCOORD);
            tri::UpdateTexture<CMeshO>::WedgeTexFromVertexTex(m.cm);
        }
        break;

    case FP_PLANAR_MAPPING : {
      m.updateDataMask(MeshModel::MM_WEDGTEXCOORD);
      // Get Parameters
      Point3f planeVec[3][2] = {
        {Point3f(1,0,0),Point3f(0,1,0)},  // XY
        {Point3f(0,0,1),Point3f(1,0,0)},  // XZ
        {Point3f(0,1,0),Point3f(0,0,1)}   // YZ
      };

      int sideDim = par.getEnum("projectionPlane");
      bool aspectRatio = par.getBool("aspectRatio");
      tri::UpdateTexture<CMeshO>::WedgeTexFromPlane(m.cm, planeVec[sideDim][0], planeVec[sideDim][1], aspectRatio);
    }
    break;
    case FP_BASIC_TRIANGLE_MAPPING :
    {
            m.updateDataMask(MeshModel::MM_WEDGTEXCOORD);

            // Get Parameters
            int sideDim = par.getInt("sidedim");
            int textDim = par.getInt("textdim");
            int pxBorder = par.getInt("border");
            bool adv;
            switch(par.getEnum("method")) {
            case 0 : adv = false; break; // Basic
            case 1 : adv = true; break;  // Advanced
            default : assert(0);
        };

            // Pre checks
            CheckError(textDim <= 0, "Texture Dimension has an incorrect value");
            CheckError(pxBorder < 0,   "Inter-Triangle border has an incorrect value");
            CheckError(sideDim < 0,  "Quads per line border has an incorrect value");

            if (adv) //ADVANCED SPACE-OPTIMIZING
            {
                float border = ((float)pxBorder) / textDim;

                // Creates a vector of double areas
                double maxArea = -1, minArea=DBL_MAX;
                std::vector<double> areas;
                int faceNo = 0;
                for (uint i=0; i<m.cm.face.size(); ++i)
                {
                    if (!m.cm.face[i].IsD())
                    {
                        double area = DoubleArea(m.cm.face[i]);
                        if (area == 0) area = DBL_MIN;
                        if (area > maxArea) maxArea = area;
                        if (area < minArea) minArea = area;
                        areas.push_back(area);
                        ++faceNo;
                    } else {
                        areas.push_back(-1.0);
                    }
                }

                // Creates buckets containing each halfening level triangles (a histogram)
                int    buckSize = (int)ceil(log_2(maxArea/minArea) + DBL_EPSILON);
                std::vector<std::vector<uint> > buckets(buckSize);
                for (uint i=0; i<areas.size(); ++i)
                    if (areas[i]>=0)
                    {
                    int slot = (int)ceil(log_2(maxArea/areas[i]) + DBL_EPSILON) - 1;
                    assert(slot < buckSize && slot >= 0);
                    buckets[slot].push_back(i);
                }

                // Determines correct dimension and accordingly max halfening levels
                int dim = 0;
                int halfeningLevels = 0;

                double qn = 0., divisor = 2.0;
                int rest = faceNo, oneFact = 1, sqrt2Fact = 1;
                bool enough = false;
                while (halfeningLevels < buckSize)
                {
                    int tmp =(int)ceil(sqrt(qn + rest/divisor));
                    bool newenough = true;
                    if (sideDim != 0)
                    {
                        newenough = sideDim>=tmp;
                        tmp = sideDim;
                    }

                    // this check triangles dimension limit too
                    if (newenough && 1.0/tmp < (sqrt2Fact/M_SQRT2 + oneFact)*border +
                        (oneFact != sqrt2Fact ? oneFact*M_SQRT2*2.0/textDim : oneFact*2.0/textDim)) break;

                    enough = newenough;
                    rest -= buckets[halfeningLevels].size();
                    qn += buckets[halfeningLevels].size() / divisor;
                    divisor *= 2.0;

                    if (halfeningLevels%2)
                        oneFact *= 2;
                    else
                        sqrt2Fact *= 2;

                    dim = tmp;
                    halfeningLevels++;
                }

                // Post checks
                CheckError(!enough && halfeningLevels==buckSize, QString("Quads per line aren't enough to obtain a correct parametrization\nTry setting at least ") + QString::number((int)ceil(sqrt(qn))));
                CheckError(halfeningLevels==0  || !enough, "Inter-Triangle border is too much");

                //Create cache of possible triangles (need only translation in correct position)
                std::vector<Tri2> cache((1 << (halfeningLevels+1))-2);
                buildTrianglesCache(cache, halfeningLevels, border, 1.0/dim);

                // Setting texture coordinates (finally)
                Tri2::CoordType origin;
                Tri2::CoordType tmp;
                int buckIdx=0, face=0;
                std::vector<uint>::iterator it = buckets[buckIdx].begin();
                int currLevel = 1;
                for (int i=0; i<dim && face<faceNo; ++i)
                {
                    origin.Y() = -((float)i)/dim;
                    for (int j=0; j<dim && face<faceNo; j++)
                    {
                        origin.X() = ((float)j)/dim;
                        for (int pos=(1<<currLevel)-2; pos<(1<<(currLevel+1))-2 && face<faceNo; ++pos, ++face)
                        {
                            while (it == buckets[buckIdx].end()) {
                                if (++buckIdx < halfeningLevels)
                                {
                                    ++currLevel;
                                    pos = 2*pos+2;
                                }
                                it = buckets[buckIdx].begin();
                            }
                            int fidx = *it;
                            int lEdge = getLongestEdge(m.cm.face[fidx]);
                            Tri2 &t = cache[pos];
                            tmp = t.P(0) + origin;
                            m.cm.face[fidx].WT(lEdge) = CFaceO::TexCoordType(tmp.X(), tmp.Y());
                            m.cm.face[fidx].WT(lEdge).N() = 0;
                            lEdge = (lEdge+1)%3;
                            tmp = t.P(1) + origin;
                            m.cm.face[fidx].WT(lEdge) = CFaceO::TexCoordType(tmp.X(), tmp.Y());
                            m.cm.face[fidx].WT(lEdge).N() = 0;
                            lEdge = (lEdge+1)%3;
                            tmp = t.P(2) + origin;
                            m.cm.face[fidx].WT(lEdge) = CFaceO::TexCoordType(tmp.X(), tmp.Y());
                            m.cm.face[fidx].WT(lEdge).N() = 0;
                            ++it;
                            cb(face*100/faceNo, "Generating parametrization...");
                        }
                    }
                }
                assert(face == faceNo);
                assert(it == buckets[buckSize-1].end());
                Log( "Biggest triangle's catheti are %.2f px long", (cache[0].P(0)-cache[0].P(2)).Norm() * textDim);
                Log( "Smallest triangle's catheti are %.2f px long", (cache[cache.size()-1].P(0)-cache[cache.size()-1].P(2)).Norm() * textDim);

            }
            else //BASIC
            {
                //Get total faces and total undeleted face
                int faceNo = m.cm.face.size();
                int faceNotD = 0;
                for (CMeshO::FaceIterator fi=m.cm.face.begin(); fi!=m.cm.face.end(); ++fi)
                    if (!fi->IsD()) ++faceNotD;

                // Minimum side dimension to get correct halfsquared triangles
                int optimalDim = ceilf(sqrtf(faceNotD/2.));
                if (sideDim == 0) sideDim = optimalDim;
                else {
                    CheckError(optimalDim > sideDim, QString("Quads per line aren't enough to obtain a correct parametrization\nTry setting at least ") + QString::number(optimalDim));
                }

                //Calculating border size in UV space
                float border = ((float)pxBorder) / textDim;
                CheckError(border*(1.0+M_SQRT2)+2.0/textDim > 1.0/sideDim, "Inter-Triangle border is too much");

                float bordersq2 = border / M_SQRT2;
                float halfborder = border / 2;

                bool odd = true;
                CFaceO::TexCoordType botl, topr;
                int face=0;
                botl.V() = 1.;
                for (int i=0; i<sideDim && face<faceNo; ++i)
                {
                    topr.V() = botl.V();
                    topr.U() = 0.;
                    botl.V() = 1.0 - 1.0/sideDim*(i+1);
                    for (int j=0; j<2*sideDim && face<faceNo; ++face)
                    {
                        if (!m.cm.face[face].IsD())
                        {
                            int lEdge = getLongestEdge(m.cm.face[face]);
                            if (odd) {
                                botl.U() = topr.U();
                                topr.U() = 1.0/sideDim*(j/2+1);
                                CFaceO::TexCoordType bl(botl.U()+halfborder, botl.V()+halfborder+bordersq2);
                                CFaceO::TexCoordType tr(topr.U()-(halfborder+bordersq2), topr.V()-halfborder);
                                bl.N() = 0;
                                tr.N() = 0;
                                m.cm.face[face].WT(lEdge) = bl;
                                m.cm.face[face].WT((++lEdge)%3) = tr;
                                m.cm.face[face].WT((++lEdge)%3) = CFaceO::TexCoordType(bl.U(), tr.V());
                                m.cm.face[face].WT(lEdge%3).N() = 0;
                            } else {
                                CFaceO::TexCoordType bl(botl.U()+(halfborder+bordersq2), botl.V()+halfborder);
                                CFaceO::TexCoordType tr(topr.U()-halfborder, topr.V()-(halfborder+bordersq2));
                                bl.N() = 0;
                                tr.N() = 0;
                                m.cm.face[face].WT(lEdge) = tr;
                                m.cm.face[face].WT((++lEdge)%3) = bl;
                                m.cm.face[face].WT((++lEdge)%3) = CFaceO::TexCoordType(tr.U(), bl.V());
                                m.cm.face[face].WT(lEdge%3).N() = 0;
                            }
                            cb(face*100/faceNo, "Generating parametrization...");
                            odd=!odd; ++j;
                        }
                    }
                }
                Log( "Triangles' catheti are %.2f px long", (1.0/sideDim-border-bordersq2)*textDim);
            }
        }
        break;

    case FP_SET_TEXTURE : {

#define CHECKERDIM 64

            // Get parameters
            QString textName = par.getString("textName");
            int textDim = par.getInt("textDim");

            CheckError(!QFile(m.fullName()).exists(), "Save the file before setting a texture");
            CheckError(textDim <= 0, "Texture Dimension has an incorrect value");
            CheckError(textName.length() == 0, "Texture file not specified");

            // Check textName and eventually add .png ext
            CheckError(std::max<int>(textName.lastIndexOf("\\"),textName.lastIndexOf("/")) != -1, "Path in Texture file not allowed");
            if (!textName.endsWith(".png", Qt::CaseInsensitive) && !textName.endsWith(".dds", Qt::CaseInsensitive) )
                textName.append(".png");

            // Creates path to texture file
            QString fileName(m.fullName());
            fileName = fileName.left(std::max<int>(fileName.lastIndexOf('\\'),fileName.lastIndexOf('/'))+1).append(textName);

            QFile textFile(fileName);
            if (!textFile.exists())
            {
                // Create dummy checkers texture image
                QImage img(textDim, textDim, QImage::Format_RGB32);
                img.fill(qRgb(255,255,255)); // white
                QPainter p(&img);
                QBrush gray(Qt::gray);
                QRect rect(0,0,CHECKERDIM,CHECKERDIM);
                bool odd = true;
                for (int y=0; y<textDim; y+=CHECKERDIM, odd=!odd)
                {
                    rect.moveTop(y);
                    for (int x=odd?0:CHECKERDIM; x<textDim; x+=2*CHECKERDIM)
                    {
                        rect.moveLeft(x);
                        p.fillRect(rect, gray);
                    }
                }

                // Save texture
                CheckError(!img.save(fileName, "PNG"), "Specified file cannot be saved");
                Log( "Dummy Texture \"%s\" Created ", fileName.toStdString().c_str());
                assert(textFile.exists());
            }

            //Assign texture
            m.cm.textures.clear();
            m.cm.textures.push_back(textName.toStdString());
        }
        break;

    case FP_COLOR_TO_TEXTURE : {
            QString textName = par.getString("textName");
            int textW = par.getInt("textW");
            int textH = par.getInt("textH");
            bool overwrite = par.getBool("overwrite");
            bool assign = par.getBool("assign");
            bool pp = par.getBool("pullpush");

            CheckError(!QFile(m.fullName()).exists(), "Save the file before creating a texture");

            QString filePath(m.fullName());
            filePath = filePath.left(std::max<int>(filePath.lastIndexOf('\\'),filePath.lastIndexOf('/'))+1);

            if (!overwrite)
            {
                // Check textName and eventually add .png ext
                CheckError(textName.length() == 0, "Texture file not specified");
                CheckError(std::max<int>(textName.lastIndexOf("\\"),textName.lastIndexOf("/")) != -1, "Path in Texture file not allowed");
                if (!textName.endsWith(".png", Qt::CaseInsensitive))
                    textName.append(".png");
                filePath.append(textName);
            } else {
                CheckError(m.cm.textures.empty(), "Mesh has no associated texture to overwrite");
                filePath.append(m.cm.textures[0].c_str());
            }

            CheckError(textW <= 0, "Texture Width has an incorrect value");
            CheckError(textH <= 0, "Texture Height has an incorrect value");

            // Image creation
            QImage img(QSize(textW,textH), QImage::Format_ARGB32);
            img.fill(qRgba(0,0,0,0)); // transparent black

            // Compute (texture-space) border edges
            tri::UpdateTopology<CMeshO>::FaceFaceFromTexCoord(m.cm);
            tri::UpdateFlags<CMeshO>::FaceBorderFromFF(m.cm);

            // Rasterizing triangles
            RasterSampler rs(img);
            rs.InitCallback(cb, m.cm.fn, 0, 80);
            tri::SurfaceSampling<CMeshO,RasterSampler>::Texture(m.cm,rs,textW,textH,true);

            // Revert alpha values for border edge pixels to 255
            cb(81, "Cleaning up texture ...");
            for (int y=0; y<textH; ++y)
                for (int x=0; x<textW; ++x)
                {
                QRgb px = img.pixel(x,y);
                if (qAlpha(px) < 255 && (!pp || qAlpha(px) > 0))
                    img.setPixel(x,y, px | 0xff000000);
            }

            // PullPush
            if (pp)
            {
                cb(85, "Filling texture holes...");
                PullPush(img, qRgba(0,0,0,0));
            }

            // Undo topology changes
            tri::UpdateTopology<CMeshO>::FaceFace(m.cm);
            tri::UpdateFlags<CMeshO>::FaceBorderFromFF(m.cm);

            // Save texture
            cb(90, "Saving texture ...");
            CheckError(!img.save(filePath), "Texture file cannot be saved");
            Log( "Texture \"%s\" Created", filePath.toStdString().c_str());
            assert(QFile(filePath).exists());

            // Assign texture
            if (assign && !overwrite) {
                m.cm.textures.clear();
                m.cm.textures.push_back(textName.toStdString());
            }
            cb(100, "Done");
        }
        break;

    case FP_TRANSFER_TO_TEXTURE : {
            MeshModel *srcMesh = par.getMesh("sourceMesh");
            MeshModel *trgMesh = par.getMesh("targetMesh");
            bool vertexSampling=false;
            bool textureSampling=false;
            int  vertexMode= -1;
            switch (par.getEnum("AttributeEnum")) {
            case 0: vertexSampling= true; vertexMode=0; break;  // Color
            case 1: vertexSampling= true; vertexMode=1; break;  // Normal
            case 2: vertexSampling= true; vertexMode=2; break;  // Quality
            case 3: textureSampling = true; break;
            default: assert(0);
            }
            float upperbound = par.getAbsPerc("upperBound"); // maximum distance to stop search
            QString textName = par.getString("textName");
            int textW = par.getInt("textW");
            int textH = par.getInt("textH");
            bool overwrite = par.getBool("overwrite");
            bool assign = par.getBool("assign");
            bool pp = par.getBool("pullpush");

            assert (srcMesh != NULL);
            assert (trgMesh != NULL);
            CheckError(!QFile(trgMesh->fullName()).exists(), "Save the target mesh before creating a texture");
            CheckError(trgMesh->cm.fn == 0 || trgMesh->cm.fn == 0, "Both meshes require to have faces");
            CheckError(!trgMesh->hasDataMask(MeshModel::MM_WEDGTEXCOORD), "Target mesh doesn't have Per Wedge Texture Coordinates");

            // Source image (for texture to texture transfer)
            QImage srcImg;

            if (vertexSampling) {
                if(vertexMode==0) {CheckError(!srcMesh->hasDataMask(MeshModel::MM_VERTCOLOR), "Source mesh doesn't have Per Vertex Color");}
                if(vertexMode==1) {CheckError(!srcMesh->hasDataMask(MeshModel::MM_VERTNORMAL), "Source mesh doesn't have Per Vertex Normal");}
                if(vertexMode==2) {CheckError(!srcMesh->hasDataMask(MeshModel::MM_VERTQUALITY), "Source mesh doesn't have Per Vertex Quality");}
            } else { assert(textureSampling);
                // Check whether is possible to access source mesh texture
                CheckError(!srcMesh->hasDataMask(MeshModel::MM_WEDGTEXCOORD), "Source mesh doesn't have Per Wedge Texture Coordinates");
                CheckError(srcMesh->cm.textures.empty(), "Source mesh doesn't have any associated texture");
                QString path(srcMesh->fullName());
                path = path.left(std::max<int>(path.lastIndexOf('\\'),path.lastIndexOf('/'))+1).append(srcMesh->cm.textures[0].c_str());
                CheckError(!QFile(path).exists(), QString("Source texture \"").append(path).append("\" doesn't exists"));
                CheckError(!srcImg.load(path), QString("Source texture \"").append(path).append("\" cannot be opened"));
            }

            QString filePath(trgMesh->fullName());
            filePath = filePath.left(std::max<int>(filePath.lastIndexOf('\\'),filePath.lastIndexOf('/'))+1);

            if (!overwrite)
            {
                // Check textName and eventually add .png ext
                CheckError(textName.length() == 0, "Texture file not specified");
                CheckError(std::max<int>(textName.lastIndexOf("\\"),textName.lastIndexOf("/")) != -1, "Path in Texture file not allowed");
                if (!textName.endsWith(".png", Qt::CaseInsensitive))
                    textName.append(".png");
                filePath.append(textName);
            } else {
                CheckError(trgMesh->cm.textures.empty(), "Mesh has no associated texture to overwrite");
                filePath.append(trgMesh->cm.textures[0].c_str());
            }

            CheckError(textW <= 0, "Texture Width has an incorrect value");
            CheckError(textH <= 0, "Texture Height has an incorrect value");

            // Image creation
            QImage img(QSize(textW,textH), QImage::Format_ARGB32);
            img.fill(qRgba(0,0,0,0));

            // Compute (texture-space) border edges
            trgMesh->updateDataMask(MeshModel::MM_FACEFACETOPO);
            tri::UpdateTopology<CMeshO>::FaceFaceFromTexCoord(trgMesh->cm);
            tri::UpdateFlags<CMeshO>::FaceBorderFromFF(trgMesh->cm);

            // Rasterizing faces
            srcMesh->updateDataMask(MeshModel::MM_FACEMARK);
            tri::UpdateNormals<CMeshO>::PerFaceNormalized(srcMesh->cm);
            if (vertexSampling)
            {
                TransferColorSampler sampler(srcMesh->cm, img, upperbound,vertexMode); // color sampling
                sampler.InitCallback(cb, trgMesh->cm.fn, 0, 80);
                tri::SurfaceSampling<CMeshO,TransferColorSampler>::Texture(trgMesh->cm,sampler,img.width(),img.height(),false);
            } else { assert(textureSampling);
                TransferColorSampler sampler(srcMesh->cm, img, &srcImg, upperbound); // texture sampling
                sampler.InitCallback(cb, trgMesh->cm.fn, 0, 80);
                tri::SurfaceSampling<CMeshO,TransferColorSampler>::Texture(trgMesh->cm,sampler,img.width(),img.height(),false);
            }

            // Revert alpha values from border edge pixel to 255
            cb(81, "Cleaning up texture ...");
            for (int y=0; y<textH; ++y)
                for (int x=0; x<textW; ++x)
                {
                QRgb px = img.pixel(x,y);
                if (qAlpha(px) < 255 && (!pp || qAlpha(px) > 0))
                    img.setPixel(x, y, px | 0xff000000);
            }

            // PullPush
            if (pp)
            {
                cb(85, "Filling texture holes...");
                PullPush(img, qRgba(0,0,0,0));
            }

            // Undo topology changes
            tri::UpdateTopology<CMeshO>::FaceFace(trgMesh->cm);
            tri::UpdateFlags<CMeshO>::FaceBorderFromFF(trgMesh->cm);

            // Save texture
            cb(90, "Saving texture ...");
            CheckError(!img.save(filePath), "Texture file cannot be saved");
            Log( "Texture \"%s\" Created", filePath.toStdString().c_str());
            assert(QFile(filePath).exists());

            // Assign texture
            if (assign && !overwrite) {
                trgMesh->cm.textures.clear();
                trgMesh->cm.textures.push_back(textName.toStdString());
            }
            cb(100, "Done");
        }
        break;

        case FP_TEX_TO_VCOLOR_TRANSFER :
        {
            MeshModel *srcMesh = par.getMesh("sourceMesh");
            MeshModel *trgMesh = par.getMesh("targetMesh");
            float upperbound = par.getAbsPerc("upperBound"); // maximum distance to stop search

            assert(srcMesh!=NULL);
            assert(trgMesh!=NULL);

            CheckError(trgMesh->cm.fn == 0, "Source mesh requires to have faces");

            // Check whether is possible to access source mesh texture
            CheckError(!srcMesh->hasDataMask(MeshModel::MM_WEDGTEXCOORD), "Source mesh doesn't have Per Wedge Texture Coordinates");
            CheckError(srcMesh->cm.textures.empty(), "Source mesh doesn't have any associated texture");
            QString path(srcMesh->fullName());
            path = path.left(std::max<int>(path.lastIndexOf('\\'),path.lastIndexOf('/'))+1).append(srcMesh->cm.textures[0].c_str());
            CheckError(!QFile(path).exists(), QString("Source texture \"").append(path).append("\" doesn't exists"));
            QImage srcImg;
            CheckError(!srcImg.load(path), QString("Source texture \"").append(path).append("\" cannot be opened"));

            trgMesh->updateDataMask(MeshModel::MM_VERTCOLOR);

            srcMesh->updateDataMask(MeshModel::MM_FACEMARK);
            tri::UpdateNormals<CMeshO>::PerFaceNormalized(srcMesh->cm);

            // Colorizing vertices
            VertexSampler vs(srcMesh->cm, srcImg, upperbound);
            vs.InitCallback(cb, trgMesh->cm.vn);
            tri::SurfaceSampling<CMeshO,VertexSampler>::VertexUniform(trgMesh->cm,vs,trgMesh->cm.vn);
        }
        break;

        default: assert(0);
        }
    return true;
}

Q_EXPORT_PLUGIN(FilterTexturePlugin)