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 #include <vcg/math/base.h>
24
25 #include <Eigen/Sparse>
26
27 #include <float.h>
28 #include <stdlib.h>
29 #include "filter_texture.h"
30 #include "pushpull.h"
31 #include "rastering.h"
32 #include <vcg/complex/algorithms/update/texture.h>
33 #include<wrap/io_trimesh/export_ply.h>
34 #include <vcg/complex/algorithms/parametrization/voronoi_atlas.h>
35
36 using namespace vcg;
37
FilterTexturePlugin()38 FilterTexturePlugin::FilterTexturePlugin()
39 {
40 typeList << FP_VORONOI_ATLAS
41 << FP_UV_WEDGE_TO_VERTEX
42 << FP_UV_VERTEX_TO_WEDGE
43 << FP_BASIC_TRIANGLE_MAPPING
44 << FP_SET_TEXTURE
45 << FP_PLANAR_MAPPING
46 << FP_COLOR_TO_TEXTURE
47 << FP_TRANSFER_TO_TEXTURE
48 << FP_TEX_TO_VCOLOR_TRANSFER;
49
50 foreach(FilterIDType tt , types())
51 actionList << new QAction(filterName(tt), this);
52 }
53
filterName(FilterIDType filterId) const54 QString FilterTexturePlugin::filterName(FilterIDType filterId) const
55 {
56 switch(filterId)
57 {
58 case FP_VORONOI_ATLAS : return QString("Parametrization: Voronoi Atlas");
59 case FP_UV_WEDGE_TO_VERTEX : return QString("Convert PerWedge UV into PerVertex UV");
60 case FP_UV_VERTEX_TO_WEDGE : return QString("Convert PerVertex UV into PerWedge UV");
61 case FP_BASIC_TRIANGLE_MAPPING : return QString("Parametrization: Trivial Per-Triangle");
62 case FP_PLANAR_MAPPING : return QString("Parametrization: Flat Plane");
63 case FP_SET_TEXTURE : return QString("Set Texture");
64 case FP_COLOR_TO_TEXTURE : return QString("Transfer: Vertex Color to Texture");
65 case FP_TRANSFER_TO_TEXTURE : return QString("Transfer: Vertex Attributes to Texture (1 or 2 meshes)");
66 case FP_TEX_TO_VCOLOR_TRANSFER : return QString("Transfer: Texture to Vertex Color (1 or 2 meshes)");
67 default : assert(0);
68 }
69 return {};
70 }
71
72 // Info() must return the longer string describing each filtering action
73 // (this string is used in the About plugin dialog)
filterInfo(FilterIDType filterId) const74 QString FilterTexturePlugin::filterInfo(FilterIDType filterId) const
75 {
76 switch(filterId)
77 {
78 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>\
79 <i>Computing Discrete Minimal Surfaces and Their Conjugates</i> <br>\
80 Experimental Mathematics, Vol 2 (1), 1993<br> .");
81 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.");
82 case FP_UV_VERTEX_TO_WEDGE : return QString("Converts per Vertex Texture Coordinates to per Wedge Texture Coordinates. It does not merge superfluous vertices...");
83 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.");
84 case FP_PLANAR_MAPPING : return QString("Builds a trivial flat-plane parametrization.");
85 case FP_SET_TEXTURE : return QString("Set a texture associated with current mesh parametrization.<br>"
86 "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.");
87 case FP_COLOR_TO_TEXTURE : return QString("Fills the specified texture using per-vertex color data of the mesh.");
88 case FP_TRANSFER_TO_TEXTURE : return QString("Transfer texture color, vertex color or normal from one mesh the texture of another mesh. This may be useful to restore detail lost in simplification, or resample a texture in a different parametrization.");
89 case FP_TEX_TO_VCOLOR_TRANSFER : return QString("Generates Vertex Color values picking color from a texture (same mesh or another mesh).");
90 default : assert(0);
91 }
92 return QString("Unknown Filter");
93 }
94
getPreConditions(QAction * a) const95 int FilterTexturePlugin::getPreConditions(QAction *a) const
96 {
97 switch (ID(a))
98 {
99 case FP_UV_WEDGE_TO_VERTEX : return MeshModel::MM_WEDGTEXCOORD;
100 case FP_UV_VERTEX_TO_WEDGE : return MeshModel::MM_VERTTEXCOORD;
101 case FP_BASIC_TRIANGLE_MAPPING :
102 case FP_VORONOI_ATLAS :
103 case FP_PLANAR_MAPPING : return MeshModel::MM_FACENUMBER;
104 case FP_SET_TEXTURE : return MeshModel::MM_WEDGTEXCOORD;
105 case FP_COLOR_TO_TEXTURE : return MeshModel::MM_VERTCOLOR | MeshModel::MM_WEDGTEXCOORD;
106 case FP_TRANSFER_TO_TEXTURE : return MeshModel::MM_NONE;
107 case FP_TEX_TO_VCOLOR_TRANSFER : return MeshModel::MM_NONE;
108 default: assert(0);
109 }
110 return MeshModel::MM_NONE;
111 }
112
getRequirements(QAction * a)113 int FilterTexturePlugin::getRequirements(QAction *a)
114 {
115 switch (ID(a))
116 {
117 case FP_VORONOI_ATLAS :
118 case FP_UV_WEDGE_TO_VERTEX :
119 case FP_UV_VERTEX_TO_WEDGE :
120 case FP_BASIC_TRIANGLE_MAPPING :
121 case FP_PLANAR_MAPPING :
122 case FP_SET_TEXTURE : return MeshModel::MM_NONE;
123 case FP_COLOR_TO_TEXTURE : return MeshModel::MM_FACEFACETOPO;
124 case FP_TRANSFER_TO_TEXTURE : return MeshModel::MM_NONE;
125 case FP_TEX_TO_VCOLOR_TRANSFER : return MeshModel::MM_NONE;
126 default: assert(0);
127 }
128 return MeshModel::MM_NONE;
129 }
130
postCondition(QAction * a) const131 int FilterTexturePlugin::postCondition( QAction *a) const
132 {
133 switch (ID(a))
134 {
135 case FP_VORONOI_ATLAS: return MeshModel::MM_WEDGTEXCOORD;
136 case FP_UV_WEDGE_TO_VERTEX: return MeshModel::MM_VERTTEXCOORD;
137 case FP_UV_VERTEX_TO_WEDGE : return MeshModel::MM_WEDGTEXCOORD;
138 case FP_PLANAR_MAPPING : return MeshModel::MM_WEDGTEXCOORD;
139 case FP_BASIC_TRIANGLE_MAPPING : return MeshModel::MM_WEDGTEXCOORD;
140 case FP_SET_TEXTURE : return MeshModel::MM_NONE;
141 case FP_COLOR_TO_TEXTURE : return MeshModel::MM_NONE;
142 case FP_TRANSFER_TO_TEXTURE : return MeshModel::MM_NONE;
143 case FP_TEX_TO_VCOLOR_TRANSFER: return MeshModel::MM_VERTCOLOR;
144 default: assert(0);
145 }
146 return MeshModel::MM_NONE;
147 }
148
149 // The FilterClass describes in which generic class of filters it fits.
150 // This choice affect the submenu in which each filter will be placed
151 // More than a single class can be chosen.
getClass(QAction * a)152 FilterTexturePlugin::FilterClass FilterTexturePlugin::getClass(QAction *a)
153 {
154 switch(ID(a))
155 {
156 case FP_VORONOI_ATLAS :
157 case FP_UV_WEDGE_TO_VERTEX :
158 case FP_UV_VERTEX_TO_WEDGE :
159 case FP_BASIC_TRIANGLE_MAPPING :
160 case FP_PLANAR_MAPPING :
161 case FP_SET_TEXTURE :
162 case FP_COLOR_TO_TEXTURE :
163 case FP_TRANSFER_TO_TEXTURE : return MeshFilterInterface::Texture;
164 case FP_TEX_TO_VCOLOR_TRANSFER : return FilterClass(MeshFilterInterface::VertexColoring + MeshFilterInterface::Texture);
165 default : assert(0);
166 }
167 return MeshFilterInterface::Generic;
168 }
169
extractFilenameWOExt(MeshModel * mm)170 static QString extractFilenameWOExt(MeshModel* mm)
171 {
172 QFileInfo fi(mm->fullName());
173 return fi.baseName();
174 }
175
176 // This function define the needed parameters for each filter. Return true if the filter has some parameters
177 // it is called every time, so you can set the default value of parameters according to the mesh
178 // For each parameter you need to define,
179 // - the name of the parameter,
180 // - the string shown in the dialog
181 // - the default value
182 // - a possibly long string describing the meaning of that parameter (shown as a popup help in the dialog)
initParameterSet(QAction * action,MeshDocument & md,RichParameterSet & parlst)183 void FilterTexturePlugin::initParameterSet(QAction *action, MeshDocument &md, RichParameterSet & parlst)
184 {
185 switch(ID(action)) {
186 case FP_VORONOI_ATLAS :
187 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."));
188 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"));
189 break;
190 case FP_UV_WEDGE_TO_VERTEX :
191 break;
192 case FP_PLANAR_MAPPING :
193 parlst.addParam(new RichEnum("projectionPlane", 0, QStringList("XY") << "XZ"<<"YZ","Projection plane","Choose the projection plane"));
194 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"));
195 parlst.addParam(new RichFloat("sideGutter", 0.0, "Side Gutter", "Leave an empty space around the parametrization area of the specified size (in texture space); accepted range [0.0 - 0.5]."));
196 break;
197 case FP_BASIC_TRIANGLE_MAPPING :
198 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"));
199 parlst.addParam(new RichInt("textdim", 1024, "Texture Dimension (px)", "Gives an indication on how big the texture is"));
200 parlst.addParam(new RichInt("border", 2, "Inter-Triangle border (px)", "Specifies how many pixels to be left between triangles in parametrization domain"));
201 parlst.addParam(new RichEnum("method", 1, QStringList("Basic") << "Space-optimizing", "Method", "Choose space optimizing to map smaller faces into smaller triangles in parametrizazion domain"));
202 break;
203 case FP_SET_TEXTURE : {
204 QString fileName = extractFilenameWOExt(md.mm());
205 fileName = fileName.append(".png");
206 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"));
207 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"));
208 }
209 break;
210 case FP_COLOR_TO_TEXTURE : {
211 QString fileName = extractFilenameWOExt(md.mm());
212 fileName = fileName.append("_tex.png");
213 parlst.addParam(new RichString("textName", fileName, "Texture file", "The texture file to be created"));
214 parlst.addParam(new RichInt("textW", 1024, "Texture width (px)", "The texture width"));
215 parlst.addParam(new RichInt("textH", 1024, "Texture height (px)", "The texture height"));
216 parlst.addParam(new RichBool("overwrite", false, "Overwrite texture", "if current mesh has a texture will be overwritten (with provided texture dimension)"));
217 parlst.addParam(new RichBool("assign", false, "Assign texture", "assign the newly created texture"));
218 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"));
219 }
220 break;
221 case FP_TRANSFER_TO_TEXTURE : {
222 QString fileName = extractFilenameWOExt(md.mm());
223 fileName = fileName.append("_tex.png");
224 parlst.addParam(new RichMesh ("sourceMesh",md.mm(),&md, "Source Mesh",
225 "The mesh that contains the source data that we want to transfer"));
226 parlst.addParam(new RichMesh ("targetMesh",md.mm(),&md, "Target Mesh",
227 "The mesh whose texture will be filled according to source mesh data"));
228 parlst.addParam(new RichEnum("AttributeEnum", 0, QStringList("Vertex Color") << "Vertex Normal" << "Vertex Quality"<< "Texture Color", "Color Data Source",
229 "Choose what attribute has to be transferred onto the target texture. You can choose bettween Per vertex attributes (clor,normal,quality) or to transfer color information from source mesh texture"));
230 parlst.addParam(new RichAbsPerc("upperBound", md.mm()->cm.bbox.Diag()/50.0, 0.0f, md.mm()->cm.bbox.Diag(),
231 tr("Max Dist Search"), tr("Sample points for which we do not find anything within this distance are rejected and not considered for recovering data")));
232 parlst.addParam(new RichString("textName", fileName, "Texture file", "The texture file to be created"));
233 parlst.addParam(new RichInt("textW", 1024, "Texture width (px)", "The texture width"));
234 parlst.addParam(new RichInt("textH", 1024, "Texture height (px)", "The texture height"));
235 parlst.addParam(new RichBool("overwrite", false, "Overwrite Target Mesh Texture", "if target mesh has a texture will be overwritten (with provided texture dimension)"));
236 parlst.addParam(new RichBool("assign", false, "Assign Texture", "assign the newly created texture to target mesh"));
237 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"));
238 }
239 break;
240 case FP_TEX_TO_VCOLOR_TRANSFER : {
241 parlst.addParam(new RichMesh ("sourceMesh",md.mm(),&md, "Source Mesh",
242 "The mesh with associated texture that we want to sample from"));
243 parlst.addParam(new RichMesh ("targetMesh",md.mm(),&md, "Target Mesh",
244 "The mesh whose vertex color will be filled according to source mesh texture"));
245 parlst.addParam(new RichAbsPerc("upperBound", md.mm()->cm.bbox.Diag()/50.0, 0.0f, md.mm()->cm.bbox.Diag(),
246 tr("Max Dist Search"), tr("Sample points for which we do not find anything within this distance are rejected and not considered for recovering color")));
247 }
248 break;
249 default: break; // do not add any parameter for the other filters
250 }
251 }
252
253
254 /////// FUNCTIONS NEEDED BY "UV WEDGE TO VERTEX" FILTER
ExtractVertex(const CMeshO & srcMesh,const CMeshO::FaceType & f,int whichWedge,const CMeshO & dstMesh,CMeshO::VertexType & v)255 inline void ExtractVertex(const CMeshO & srcMesh, const CMeshO::FaceType & f, int whichWedge, const CMeshO & dstMesh, CMeshO::VertexType & v)
256 {
257 (void)srcMesh;
258 (void)dstMesh;
259 // This is done to preserve every single perVertex property
260 // perVextex Texture Coordinate is instead obtained from perWedge one.
261 v.ImportData(*f.cV(whichWedge));
262 v.T() = f.cWT(whichWedge);
263 }
264
CompareVertex(const CMeshO & m,const CMeshO::VertexType & vA,const CMeshO::VertexType & vB)265 inline bool CompareVertex(const CMeshO & m, const CMeshO::VertexType & vA, const CMeshO::VertexType & vB)
266 {
267 (void)m;
268 return (vA.cT() == vB.cT());
269 }
270
271 /////// FUNCTIONS NEEDED BY "BASIC PARAMETRIZATION" FILTER
getLongestEdge(const CMeshO::FaceType & f)272 inline int getLongestEdge(const CMeshO::FaceType & f)
273 {
274 int res=0;
275 const CMeshO::CoordType &p0=f.cP(0), &p1=f.cP(1), p2=f.cP(2);
276 double maxd01 = SquaredDistance(p0,p1);
277 double maxd12 = SquaredDistance(p1,p2);
278 double maxd20 = SquaredDistance(p2,p0);
279 if(maxd01 > maxd12)
280 if(maxd01 > maxd20) res = 0;
281 else res = 2;
282 else
283 if(maxd12 > maxd20) res = 1;
284 else res = 2;
285 return res;
286 }
287
288 typedef Triangle2<CMeshO::FaceType::TexCoordType::ScalarType> Tri2;
289
buildTrianglesCache(std::vector<Tri2> & arr,int maxLevels,float border,float quadSize,int idx=-1)290 inline void buildTrianglesCache(std::vector<Tri2> &arr, int maxLevels, float border, float quadSize, int idx=-1)
291 {
292 assert(idx >= -1);
293 Tri2 &t0 = arr[2*idx+2];
294 Tri2 &t1 = arr[2*idx+3];
295 if (idx == -1)
296 {
297 // build triangle 0
298 t0.P(1).X() = quadSize - (0.5 + M_SQRT1_2)*border;
299 t0.P(0).X() = 0.5 * border;
300 t0.P(1).Y() = 1.0 - t0.P(0).X();
301 t0.P(0).Y() = 1.0 - t0.P(1).X();
302 t0.P(2).X() = t0.P(0).X();
303 t0.P(2).Y() = t0.P(1).Y();
304 // build triangle 1
305 t1.P(1).X() = (0.5 + M_SQRT1_2)*border;
306 t1.P(0).X() = quadSize - 0.5 * border;
307 t1.P(1).Y() = 1.0 - t1.P(0).X();
308 t1.P(0).Y() = 1.0 - t1.P(1).X();
309 t1.P(2).X() = t1.P(0).X();
310 t1.P(2).Y() = t1.P(1).Y();
311 }
312 else {
313 // split triangle idx in t0 t1
314 Tri2 &t = arr[idx];
315 Tri2::CoordType midPoint = (t.P(0) + t.P(1)) / 2;
316 Tri2::CoordType vec10 = (t.P(0) - t.P(1)).Normalize() * (border/2.0);
317 t0.P(1) = t.P(0);
318 t1.P(0) = t.P(1);
319 t0.P(2) = midPoint + vec10;
320 t1.P(2) = midPoint - vec10;
321 t0.P(0) = t.P(2) + ( (t.P(0)-t.P(2)).Normalize() * border / M_SQRT2 );
322 t1.P(1) = t.P(2) + ( (t.P(1)-t.P(2)).Normalize() * border / M_SQRT2 );
323 }
324 if (--maxLevels <= 0) return;
325 buildTrianglesCache (arr, maxLevels, border, quadSize, 2*idx+2);
326 buildTrianglesCache (arr, maxLevels, border, quadSize, 2*idx+3);
327 }
328
329 // ERROR CHECKING UTILITY
330 #define CheckError(x,y); if ((x)) {this->errorMessage = (y); return false;}
331 ///////////////////////////////////////////////////////
332
333 template<typename T>
log_2(const T num)334 T log_2(const T num)
335 {
336 return T(log(num) / log(T(2)));
337 }
338
339 // The Real Core Function doing the actual mesh processing.
applyFilter(QAction * filter,MeshDocument & md,RichParameterSet & par,CallBackPos * cb)340 bool FilterTexturePlugin::applyFilter(QAction *filter, MeshDocument &md, RichParameterSet &par, CallBackPos *cb)
341 {
342 MeshModel &m=*(md.mm());
343 switch(ID(filter)) {
344 case FP_VORONOI_ATLAS : {
345 MeshModel *baseModel=md.mm();
346 baseModel->updateDataMask(MeshModel::MM_FACEFACETOPO);
347 tri::UpdateTopology<CMeshO>::FaceFace(baseModel->cm);
348 int nonManifVertNum=tri::Clean<CMeshO>::CountNonManifoldVertexFF(baseModel->cm,false);
349 int nonManifEdgeNum=tri::Clean<CMeshO>::CountNonManifoldEdgeFF(baseModel->cm,false);
350
351 if(nonManifVertNum>0 || nonManifEdgeNum>0)
352 {
353 Log("Mesh is not manifold\n:%i non manifold Vertices\n%i nonmanifold Edges\n",nonManifVertNum,nonManifEdgeNum);
354 this->errorMessage = "Mesh is not manifold. See Log for details";
355 return false;
356 }
357
358 MeshModel *paraModel=md.addNewMesh("","VoroAtlas",false);
359 tri::VoronoiAtlas<CMeshO>::VoronoiAtlasParam pp;
360 pp.sampleNum =par.getInt("regionNum");
361 pp.overlap=par.getBool("overlapFlag");
362
363 paraModel->updateDataMask(MeshModel::MM_WEDGTEXCOORD);
364 // Note that CMesh has ocf texcoord and the append inside VoronoiAtlas class need that.
365 // This is a design bug of the VCGLib...
366 int bitToBeCleared =0;
367 if(!baseModel->hasDataMask(MeshModel::MM_WEDGTEXCOORD)) bitToBeCleared |=MeshModel::MM_WEDGTEXCOORD;
368 if(!baseModel->hasDataMask(MeshModel::MM_VERTTEXCOORD)) bitToBeCleared |=MeshModel::MM_VERTTEXCOORD;
369 baseModel->updateDataMask(MeshModel::MM_WEDGTEXCOORD | MeshModel::MM_VERTTEXCOORD);
370 tri::VoronoiAtlas<CMeshO>::Build(baseModel->cm,paraModel->cm, pp);
371 if(pp.overlap==false)
372 tri::Clean<CMeshO>::RemoveDuplicateVertex(paraModel->cm);
373
374 paraModel->UpdateBoxAndNormals();
375 baseModel->clearDataMask(bitToBeCleared);
376 Log("Voronoi Atlas: Completed Processing in %i iterations",pp.vas.iterNum);
377 Log("Asked %i generated %i regions",pp.sampleNum,pp.vas.regionNum);
378 Log("Unwrap Time %6.3f s", float(pp.vas.unwrapTime) / CLOCKS_PER_SEC);
379 Log("Voronoi Time %6.3f s", float(pp.vas.voronoiTime) / CLOCKS_PER_SEC);
380 Log("Sampling Time %6.3f s", float(pp.vas.samplingTime) / CLOCKS_PER_SEC);
381 }
382 break;
383
384 case FP_UV_WEDGE_TO_VERTEX : {
385 int vn = m.cm.vn;
386 m.updateDataMask(MeshModel::MM_VERTTEXCOORD);
387 tri::AttributeSeam::SplitVertex(m.cm, ExtractVertex, CompareVertex);
388 if (m.cm.vn != vn)
389 {
390 m.clearDataMask(MeshModel::MM_FACEFACETOPO);
391 m.clearDataMask(MeshModel::MM_VERTFACETOPO);
392 }
393 }
394 break;
395
396 case FP_UV_VERTEX_TO_WEDGE : {
397 m.updateDataMask(MeshModel::MM_WEDGTEXCOORD);
398 tri::UpdateTexture<CMeshO>::WedgeTexFromVertexTex(m.cm);
399 }
400 break;
401
402 case FP_PLANAR_MAPPING :
403 {
404 m.updateDataMask(MeshModel::MM_WEDGTEXCOORD);
405
406 // Get Parameters
407 Point3m planeVec[3][2] = {
408 {Point3m(1,0,0),Point3m(0,1,0)}, // XY
409 {Point3m(0,0,1),Point3m(1,0,0)}, // XZ
410 {Point3m(0,1,0),Point3m(0,0,1)} // YZ
411 };
412
413 int sideDim = par.getEnum("projectionPlane");
414 bool aspectRatio = par.getBool("aspectRatio");
415 float sideGutter = par.getFloat("sideGutter");
416
417 // the mesh has to be correctly transformed
418 if (m.cm.Tr != Matrix44m::Identity())
419 tri::UpdatePosition<CMeshO>::Matrix(m.cm, m.cm.Tr, true);
420
421 tri::UpdateTexture<CMeshO>::WedgeTexFromPlane(m.cm, planeVec[sideDim][0], planeVec[sideDim][1], aspectRatio, sideGutter);
422
423 // the mesh has to return to its original position
424 if (m.cm.Tr != Matrix44m::Identity())
425 tri::UpdatePosition<CMeshO>::Matrix(m.cm, Inverse(m.cm.Tr), true);
426 }
427 break;
428
429 case FP_BASIC_TRIANGLE_MAPPING :
430 {
431 m.updateDataMask(MeshModel::MM_WEDGTEXCOORD);
432
433 // Get Parameters
434 int sideDim = par.getInt("sidedim");
435 int textDim = par.getInt("textdim");
436 int pxBorder = par.getInt("border");
437 bool adv = false;
438 switch(par.getEnum("method")) {
439 case 0 : adv = false; break; // Basic
440 case 1 : adv = true; break; // Advanced
441 default : assert(0);
442 };
443
444 // Pre checks
445 CheckError(textDim <= 0, "Texture Dimension has an incorrect value");
446 CheckError(pxBorder < 0, "Inter-Triangle border has an incorrect value");
447 CheckError(sideDim < 0, "Quads per line border has an incorrect value");
448
449 if (adv) //ADVANCED SPACE-OPTIMIZING
450 {
451 float border = ((float)pxBorder) / textDim;
452
453 // Creates a vector of double areas
454 double maxArea = -1, minArea=DBL_MAX;
455 std::vector<double> areas;
456 int faceNo = 0;
457 for (uint i=0; i<m.cm.face.size(); ++i)
458 {
459 if (!m.cm.face[i].IsD())
460 {
461 double area = DoubleArea(m.cm.face[i]);
462 if (area == 0) area = DBL_MIN;
463 if (area > maxArea) maxArea = area;
464 if (area < minArea) minArea = area;
465 areas.push_back(area);
466 ++faceNo;
467 } else {
468 areas.push_back(-1.0);
469 }
470 }
471
472 // Creates buckets containing each halfening level triangles (a histogram)
473 int buckSize = (int)ceil(log_2(maxArea/minArea) + DBL_EPSILON);
474 std::vector<std::vector<uint> > buckets(buckSize);
475 for (uint i=0; i<areas.size(); ++i)
476 if (areas[i]>=0)
477 {
478 int slot = (int)ceil(log_2(maxArea/areas[i]) + DBL_EPSILON) - 1;
479 assert(slot < buckSize && slot >= 0);
480 buckets[slot].push_back(i);
481 }
482
483 // Determines correct dimension and accordingly max halfening levels
484 int dim = 0;
485 int halfeningLevels = 0;
486
487 double qn = 0., divisor = 2.0;
488 int rest = faceNo, oneFact = 1, sqrt2Fact = 1;
489 bool enough = false;
490 while (halfeningLevels < buckSize)
491 {
492 int tmp =(int)ceil(sqrt(qn + rest/divisor));
493 bool newenough = true;
494 if (sideDim != 0)
495 {
496 newenough = sideDim>=tmp;
497 tmp = sideDim;
498 }
499
500 // this check triangles dimension limit too
501 if (newenough && 1.0/tmp < (sqrt2Fact/M_SQRT2 + oneFact)*border +
502 (oneFact != sqrt2Fact ? oneFact*M_SQRT2*2.0/textDim : oneFact*2.0/textDim)) break;
503
504 enough = newenough;
505 rest -= buckets[halfeningLevels].size();
506 qn += buckets[halfeningLevels].size() / divisor;
507 divisor *= 2.0;
508
509 if (halfeningLevels%2)
510 oneFact *= 2;
511 else
512 sqrt2Fact *= 2;
513
514 dim = tmp;
515 halfeningLevels++;
516 }
517
518 // Post checks
519 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))));
520 CheckError(halfeningLevels==0 || !enough, "Inter-Triangle border is too much");
521
522 //Create cache of possible triangles (need only translation in correct position)
523 std::vector<Tri2> cache((1 << (halfeningLevels+1))-2);
524 buildTrianglesCache(cache, halfeningLevels, border, 1.0/dim);
525
526 // Setting texture coordinates (finally)
527 Tri2::CoordType origin;
528 Tri2::CoordType tmp;
529 int buckIdx=0, face=0;
530 std::vector<uint>::iterator it = buckets[buckIdx].begin();
531 int currLevel = 1;
532 for (int i=0; i<dim && face<faceNo; ++i)
533 {
534 origin.Y() = -((float)i)/dim;
535 for (int j=0; j<dim && face<faceNo; j++)
536 {
537 origin.X() = ((float)j)/dim;
538 for (int pos=(1<<currLevel)-2; pos<(1<<(currLevel+1))-2 && face<faceNo; ++pos, ++face)
539 {
540 while (it == buckets[buckIdx].end()) {
541 if (++buckIdx < halfeningLevels)
542 {
543 ++currLevel;
544 pos = 2*pos+2;
545 }
546 it = buckets[buckIdx].begin();
547 }
548 int fidx = *it;
549 int lEdge = getLongestEdge(m.cm.face[fidx]);
550 Tri2 &t = cache[pos];
551 tmp = t.P(0) + origin;
552 m.cm.face[fidx].WT(lEdge) = CFaceO::TexCoordType(tmp.X(), tmp.Y());
553 m.cm.face[fidx].WT(lEdge).N() = 0;
554 lEdge = (lEdge+1)%3;
555 tmp = t.P(1) + origin;
556 m.cm.face[fidx].WT(lEdge) = CFaceO::TexCoordType(tmp.X(), tmp.Y());
557 m.cm.face[fidx].WT(lEdge).N() = 0;
558 lEdge = (lEdge+1)%3;
559 tmp = t.P(2) + origin;
560 m.cm.face[fidx].WT(lEdge) = CFaceO::TexCoordType(tmp.X(), tmp.Y());
561 m.cm.face[fidx].WT(lEdge).N() = 0;
562 ++it;
563 cb(face*100/faceNo, "Generating parametrization...");
564 }
565 }
566 }
567 assert(face == faceNo);
568 assert(it == buckets[buckSize-1].end());
569 Log( "Biggest triangle's catheti are %.2f px long", (cache[0].P(0)-cache[0].P(2)).Norm() * textDim);
570 Log( "Smallest triangle's catheti are %.2f px long", (cache[cache.size()-1].P(0)-cache[cache.size()-1].P(2)).Norm() * textDim);
571
572 }
573 else //BASIC
574 {
575 //Get total faces and total undeleted face
576 int faceNo = m.cm.face.size();
577 int faceNotD = 0;
578 for (CMeshO::FaceIterator fi=m.cm.face.begin(); fi!=m.cm.face.end(); ++fi)
579 if (!fi->IsD()) ++faceNotD;
580
581 // Minimum side dimension to get correct halfsquared triangles
582 int optimalDim = ceilf(sqrtf(faceNotD/2.));
583 if (sideDim == 0) sideDim = optimalDim;
584 else {
585 CheckError(optimalDim > sideDim, QString("Quads per line aren't enough to obtain a correct parametrization\nTry setting at least ") + QString::number(optimalDim));
586 }
587
588 //Calculating border size in UV space
589 float border = ((float)pxBorder) / textDim;
590 CheckError(border*(1.0+M_SQRT2)+2.0/textDim > 1.0/sideDim, "Inter-Triangle border is too much");
591
592 float bordersq2 = border / M_SQRT2;
593 float halfborder = border / 2;
594
595 bool odd = true;
596 CFaceO::TexCoordType botl, topr;
597 int face=0;
598 botl.V() = 1.;
599 for (int i=0; i<sideDim && face<faceNo; ++i)
600 {
601 topr.V() = botl.V();
602 topr.U() = 0.;
603 botl.V() = 1.0 - 1.0/sideDim*(i+1);
604 for (int j=0; j<2*sideDim && face<faceNo; ++face)
605 {
606 if (!m.cm.face[face].IsD())
607 {
608 int lEdge = getLongestEdge(m.cm.face[face]);
609 if (odd) {
610 botl.U() = topr.U();
611 topr.U() = 1.0/sideDim*(j/2+1);
612 CFaceO::TexCoordType bl(botl.U()+halfborder, botl.V()+halfborder+bordersq2);
613 CFaceO::TexCoordType tr(topr.U()-(halfborder+bordersq2), topr.V()-halfborder);
614 bl.N() = 0;
615 tr.N() = 0;
616 m.cm.face[face].WT(lEdge) = bl;
617 m.cm.face[face].WT((++lEdge)%3) = tr;
618 m.cm.face[face].WT((++lEdge)%3) = CFaceO::TexCoordType(bl.U(), tr.V());
619 m.cm.face[face].WT(lEdge%3).N() = 0;
620 } else {
621 CFaceO::TexCoordType bl(botl.U()+(halfborder+bordersq2), botl.V()+halfborder);
622 CFaceO::TexCoordType tr(topr.U()-halfborder, topr.V()-(halfborder+bordersq2));
623 bl.N() = 0;
624 tr.N() = 0;
625 m.cm.face[face].WT(lEdge) = tr;
626 m.cm.face[face].WT((++lEdge)%3) = bl;
627 m.cm.face[face].WT((++lEdge)%3) = CFaceO::TexCoordType(tr.U(), bl.V());
628 m.cm.face[face].WT(lEdge%3).N() = 0;
629 }
630 cb(face*100/faceNo, "Generating parametrization...");
631 odd=!odd; ++j;
632 }
633 }
634 }
635 Log( "Triangles' catheti are %.2f px long", (1.0/sideDim-border-bordersq2)*textDim);
636 }
637 }
638 break;
639
640 case FP_SET_TEXTURE : {
641
642 #define CHECKERDIM 64
643
644 // Get parameters
645 QString textName = par.getString("textName");
646 int textDim = par.getInt("textDim");
647
648 CheckError(!QFile(m.fullName()).exists(), "Save the file before setting a texture");
649 CheckError(textDim <= 0, "Texture Dimension has an incorrect value");
650 CheckError(textName.length() == 0, "Texture file not specified");
651
652 // Check textName and eventually add .png ext
653 CheckError(std::max<int>(textName.lastIndexOf("\\"),textName.lastIndexOf("/")) != -1, "Path in Texture file not allowed");
654 if (!textName.endsWith(".png", Qt::CaseInsensitive) && !textName.endsWith(".dds", Qt::CaseInsensitive) && !textName.endsWith(".jpg", Qt::CaseInsensitive))
655 textName.append(".png");
656
657 // Creates path to texture file
658 QString fileName(m.fullName());
659 fileName = fileName.left(std::max<int>(fileName.lastIndexOf('\\'),fileName.lastIndexOf('/'))+1).append(textName);
660
661 QFile textFile(fileName);
662 if (!textFile.exists())
663 {
664 // Create dummy checkers texture image
665 QImage img(textDim, textDim, QImage::Format_RGB32);
666 img.fill(qRgb(255,255,255)); // white
667 QPainter p(&img);
668 QBrush gray(Qt::gray);
669 QRect rect(0,0,CHECKERDIM,CHECKERDIM);
670 bool odd = true;
671 for (int y=0; y<textDim; y+=CHECKERDIM, odd=!odd)
672 {
673 rect.moveTop(y);
674 for (int x=odd?0:CHECKERDIM; x<textDim; x+=2*CHECKERDIM)
675 {
676 rect.moveLeft(x);
677 p.fillRect(rect, gray);
678 }
679 }
680
681 // Save texture
682 CheckError(!img.save(fileName, NULL), "Specified file cannot be saved");
683 Log( "Dummy Texture \"%s\" Created ", fileName.toStdString().c_str());
684 assert(textFile.exists());
685 }
686
687 //Assign texture
688 m.cm.textures.clear();
689 m.cm.textures.push_back(textName.toStdString());
690 }
691 break;
692
693 case FP_COLOR_TO_TEXTURE :
694 {
695 QString textName = par.getString("textName");
696 int textW = par.getInt("textW");
697 int textH = par.getInt("textH");
698 bool overwrite = par.getBool("overwrite");
699 bool assign = par.getBool("assign");
700 bool pp = par.getBool("pullpush");
701
702 CheckError(!QFile(m.fullName()).exists(), "Save the file before creating a texture");
703 CheckError(textW <= 0, "Texture Width has an incorrect value");
704 CheckError(textH <= 0, "Texture Height has an incorrect value");
705 if (overwrite)
706 {
707 CheckError(m.cm.textures.empty(), "Mesh has no associated texture to overwrite");
708 }
709 else
710 {
711 CheckError(textName.length() == 0, "Texture file not specified");
712 CheckError(std::max<int>(textName.lastIndexOf("\\"), textName.lastIndexOf("/")) != -1, "Path in Texture file not allowed");
713 }
714
715 if (m.cm.textures.empty())
716 {
717 // Creates path to texture file
718 QString fileName(m.fullName());
719 fileName = fileName.left(std::max<int>(fileName.lastIndexOf('\\'), fileName.lastIndexOf('/')) + 1).append(textName);
720
721 QFile textFile(fileName);
722 if (!textFile.exists())
723 {
724 // Create dummy checkers texture image
725 QImage img(textW, textH, QImage::Format_RGB32);
726 img.fill(qRgb(255, 255, 255)); // white
727
728 // Save texture
729 CheckError(!img.save(fileName, "PNG"), "Specified file cannot be saved");
730 Log("Dummy Texture \"%s\" Created ", fileName.toStdString().c_str());
731 assert(textFile.exists());
732 }
733
734 //Assign texture
735 m.cm.textures.clear();
736 m.cm.textures.push_back(textName.toStdString());
737
738
739 }
740
741 QString filePath(m.fullName());
742 filePath = filePath.left(std::max<int>(filePath.lastIndexOf('\\'),filePath.lastIndexOf('/'))+1);
743 QString baseName(textName);
744 if (baseName.lastIndexOf(".") != -1)
745 if (baseName.endsWith("bmp", Qt::CaseInsensitive) || baseName.endsWith("jpg", Qt::CaseInsensitive) || baseName.endsWith("png", Qt::CaseInsensitive)
746 || baseName.endsWith("jpeg", Qt::CaseInsensitive) || baseName.endsWith("tif", Qt::CaseInsensitive) || baseName.endsWith("tiff", Qt::CaseInsensitive))
747 baseName.truncate(baseName.lastIndexOf("."));
748
749 int texInd;
750 int texNum;
751 texNum = m.cm.textures.size();
752 vector <QString> texFileNames;
753 texFileNames.resize(texNum);
754 vector <QImage> trgImgs;
755 trgImgs.reserve(m.cm.textures.size());
756
757 // Image texture creation
758 for (texInd = 0; texInd < texNum; texInd++)
759 {
760 if (overwrite)
761 {
762 texFileNames[texInd] = filePath + QString(m.cm.textures[texInd].c_str());
763 }
764 else
765 {
766 if (texNum==1)
767 texFileNames[texInd] = filePath + baseName + ".png";
768 else
769 texFileNames[texInd] = filePath + baseName + "_" + QString::number(texInd) + ".png";
770 }
771
772 trgImgs.push_back(QImage(QSize(textW, textH), QImage::Format_ARGB32));
773 trgImgs[texInd].fill(qRgba(0, 0, 0, 0)); // transparent black
774 }
775
776 // Compute (texture-space) border edges
777 tri::UpdateTopology<CMeshO>::FaceFaceFromTexCoord(m.cm);
778 tri::UpdateFlags<CMeshO>::FaceBorderFromFF(m.cm);
779
780 // Rasterizing triangles
781 RasterSampler rs(trgImgs);
782 rs.InitCallback(cb, m.cm.fn, 0, 80);
783 tri::SurfaceSampling<CMeshO,RasterSampler>::Texture(m.cm,rs,textW,textH,true);
784
785 // Undo topology changes
786 tri::UpdateTopology<CMeshO>::FaceFace(m.cm);
787 tri::UpdateFlags<CMeshO>::FaceBorderFromFF(m.cm);
788
789 for (texInd = 0; texInd < texNum; texInd++)
790 {
791 // Revert alpha values for border edge pixels to 255
792 cb(81, "Cleaning up texture ...");
793 for (int y = 0; y<textH; ++y)
794 for (int x = 0; x<textW; ++x)
795 {
796 QRgb px = trgImgs[texInd].pixel(x, y);
797 if (qAlpha(px) < 255 && (!pp || qAlpha(px) > 0))
798 trgImgs[texInd].setPixel(x, y, px | 0xff000000);
799 }
800
801 // PullPush
802 if (pp)
803 {
804 cb(85, "Filling texture holes...");
805 PullPush(trgImgs[texInd], qRgba(0, 0, 0, 0));
806 }
807
808 // Save texture
809 cb(90, "Saving texture ...");
810 CheckError(!trgImgs[texInd].save(texFileNames[texInd]), "Texture file cannot be saved");
811 Log("Texture \"%s\" Created", texFileNames[texInd].toStdString().c_str());
812 assert(QFile(texFileNames[texInd]).exists());
813 }
814
815 if (assign && !overwrite)
816 {
817 m.cm.textures.clear();
818 for (texInd = 0; texInd < texNum; texInd++)
819 m.cm.textures.push_back(textName.toStdString());
820 }
821
822 cb(100, "Done");
823 }
824 break;
825
826 case FP_TRANSFER_TO_TEXTURE :
827 {
828 MeshModel *srcMesh = par.getMesh("sourceMesh");
829 MeshModel *trgMesh = par.getMesh("targetMesh");
830 bool vertexSampling=false;
831 bool textureSampling=false;
832 int vertexMode= -1;
833 switch (par.getEnum("AttributeEnum"))
834 {
835 case 0: vertexSampling= true; vertexMode=0; break; // Color
836 case 1: vertexSampling= true; vertexMode=1; break; // Normal
837 case 2: vertexSampling= true; vertexMode=2; break; // Quality
838 case 3: textureSampling = true; break;
839 default: assert(0);
840 }
841 float upperbound = par.getAbsPerc("upperBound"); // maximum distance to stop search
842 QString textName = par.getString("textName");
843 int textW = par.getInt("textW");
844 int textH = par.getInt("textH");
845 bool overwrite = par.getBool("overwrite");
846 bool assign = par.getBool("assign");
847 bool pp = par.getBool("pullpush");
848
849 assert (srcMesh != NULL);
850 assert (trgMesh != NULL);
851 CheckError(!QFile(trgMesh->fullName()).exists(), "Save the target mesh before creating a texture");
852 CheckError(trgMesh->cm.fn == 0, "Target mesh needs to have faces");
853 CheckError(!trgMesh->hasDataMask(MeshModel::MM_WEDGTEXCOORD), "Target mesh does not have Per-Wedge Texture Coordinates");
854 CheckError(textW <= 0, "Texture Width has an incorrect value");
855 CheckError(textH <= 0, "Texture Height has an incorrect value");
856
857 if (vertexSampling) {
858 if (vertexMode == 0) { CheckError(!srcMesh->hasDataMask(MeshModel::MM_VERTCOLOR), "Source mesh doesn't have Per-Vertex Color"); }
859 if (vertexMode == 1) { CheckError(!srcMesh->hasDataMask(MeshModel::MM_VERTNORMAL), "Source mesh doesn't have Per-Vertex Normal"); }
860 if (vertexMode == 2) { CheckError(!srcMesh->hasDataMask(MeshModel::MM_VERTQUALITY), "Source mesh doesn't have Per-Vertex Quality"); }
861 }
862 else
863 {
864 CheckError(srcMesh->cm.fn == 0, "Source mesh needs to have faces");
865 CheckError(!srcMesh->hasDataMask(MeshModel::MM_WEDGTEXCOORD), "Source mesh does not have Per-Wedge Texture Coordinates");
866 CheckError(srcMesh->cm.textures.empty(), "Source mesh does not have any associated texture");
867 }
868
869 if (overwrite)
870 {
871 CheckError(trgMesh->cm.textures.empty(), "Mesh has no associated texture to overwrite");
872 }
873 else
874 {
875 CheckError(textName.length() == 0, "Texture file not specified");
876 CheckError(std::max<int>(textName.lastIndexOf("\\"), textName.lastIndexOf("/")) != -1, "Path in Texture file not allowed");
877 }
878
879 if (m.cm.textures.empty())
880 {
881 // Creates path to texture file
882 QString fileName(m.fullName());
883 fileName = fileName.left(std::max<int>(fileName.lastIndexOf('\\'), fileName.lastIndexOf('/')) + 1).append(textName);
884
885 QFile textFile(fileName);
886 if (!textFile.exists())
887 {
888 // Create dummy checkers texture image
889 QImage img(textW, textH, QImage::Format_RGB32);
890 img.fill(qRgb(255, 255, 255)); // white
891
892 // Save texture
893 CheckError(!img.save(fileName, "PNG"), "Specified file cannot be saved");
894 Log("Dummy Texture \"%s\" Created ", fileName.toStdString().c_str());
895 assert(textFile.exists());
896 }
897
898 //Assign texture
899 m.cm.textures.clear();
900 m.cm.textures.push_back(textName.toStdString());
901
902
903 }
904
905 // Source images (for texture to texture transfer)
906 int numSrcTex = srcMesh->cm.textures.size();
907 QString srcPath(srcMesh->fullName());
908 srcPath = srcPath.left(std::max<int>(srcPath.lastIndexOf('\\'), srcPath.lastIndexOf('/')) + 1);
909 vector <QImage> srcImgs;
910 vector <QString> srcTextureFileNames;
911 srcImgs.resize(numSrcTex);
912 srcTextureFileNames.resize(numSrcTex);
913 int srcTexInd = 0;
914 // Target images
915 int numTrgTex = trgMesh->cm.textures.size();
916 QString trgPath(trgMesh->fullName());
917 trgPath = trgPath.left(std::max<int>(trgPath.lastIndexOf('\\'), trgPath.lastIndexOf('/')) + 1);
918 QString baseName(textName);
919 if (baseName.lastIndexOf(".") != -1)
920 if (baseName.endsWith("bmp", Qt::CaseInsensitive) || baseName.endsWith("jpg", Qt::CaseInsensitive) || baseName.endsWith("png", Qt::CaseInsensitive)
921 || baseName.endsWith("jpeg", Qt::CaseInsensitive) || baseName.endsWith("tif", Qt::CaseInsensitive) || baseName.endsWith("tiff", Qt::CaseInsensitive))
922 baseName.truncate(baseName.lastIndexOf("."));
923 vector <QImage> trgImgs;
924 vector <QString> trgTextureFileNames;
925 trgImgs.reserve(numTrgTex);
926 trgTextureFileNames.resize(numTrgTex);
927 int trgTexInd = 0;
928
929 // Check whether is possible to access source mesh textures, and load them
930 if (textureSampling)
931 {
932 for (srcTexInd = 0; srcTexInd < numSrcTex; srcTexInd++)
933 {
934 srcTextureFileNames[srcTexInd] = srcPath + srcMesh->cm.textures[srcTexInd].c_str();
935 CheckError(!QFile(srcTextureFileNames[srcTexInd]).exists(), QString("Source texture \"").append(srcTextureFileNames[srcTexInd]).append("\" doesn't exists"));
936 CheckError(!srcImgs[srcTexInd].load(srcTextureFileNames[srcTexInd]), QString("Source texture \"").append(srcTextureFileNames[srcTexInd]).append("\" cannot be opened"));
937 }
938 }
939
940 // target textures naming and creation
941 for (trgTexInd = 0; trgTexInd < numTrgTex; trgTexInd++)
942 {
943 if (overwrite)
944 {
945 trgTextureFileNames[trgTexInd] = trgPath + QString(m.cm.textures[trgTexInd].c_str());
946 }
947 else
948 {
949 if (numTrgTex == 1)
950 trgTextureFileNames[trgTexInd] = trgPath + baseName + ".png";
951 else
952 trgTextureFileNames[trgTexInd] = trgPath + baseName + "_" + QString::number(trgTexInd) + ".png";
953 }
954
955 trgImgs.push_back(QImage(QSize(textW, textH), QImage::Format_ARGB32));
956 trgImgs[trgTexInd].fill(qRgba(0, 0, 0, 0)); // transparent black
957 }
958
959 // Compute (texture-space) border edges
960 trgMesh->updateDataMask(MeshModel::MM_FACEFACETOPO);
961 tri::UpdateTopology<CMeshO>::FaceFaceFromTexCoord(trgMesh->cm);
962 tri::UpdateFlags<CMeshO>::FaceBorderFromFF(trgMesh->cm);
963
964 // the meshes have to be transformed
965 // only if source different from target (if single mesh, it does not matter)
966 if (srcMesh != trgMesh)
967 {
968 if (srcMesh->cm.Tr != Matrix44m::Identity())
969 tri::UpdatePosition<CMeshO>::Matrix(srcMesh->cm, srcMesh->cm.Tr, true);
970 if (trgMesh->cm.Tr != Matrix44m::Identity())
971 tri::UpdatePosition<CMeshO>::Matrix(trgMesh->cm, trgMesh->cm.Tr, true);
972 }
973
974 // Rasterizing faces
975 srcMesh->updateDataMask(MeshModel::MM_FACEMARK);
976 tri::UpdateNormal<CMeshO>::PerFaceNormalized(srcMesh->cm);
977 if (vertexSampling)
978 {
979 TransferColorSampler sampler(srcMesh->cm, trgImgs, upperbound, vertexMode); // color sampling
980 sampler.InitCallback(cb, trgMesh->cm.fn, 0, 80);
981 tri::SurfaceSampling<CMeshO, TransferColorSampler>::Texture(trgMesh->cm, sampler, textW, textH, false);
982 }
983 else
984 {
985 TransferColorSampler sampler(srcMesh->cm, trgImgs, &srcImgs, upperbound); // texture sampling
986 sampler.InitCallback(cb, trgMesh->cm.fn, 0, 80);
987 tri::SurfaceSampling<CMeshO, TransferColorSampler>::Texture(trgMesh->cm, sampler, textW, textH, false);
988 }
989
990 // the meshes have to return to their original position
991 // only if source different from target (if single mesh, it does not matter)
992 if (srcMesh != trgMesh)
993 {
994 if (srcMesh->cm.Tr != Matrix44m::Identity())
995 tri::UpdatePosition<CMeshO>::Matrix(srcMesh->cm, Inverse(srcMesh->cm.Tr), true);
996 if (trgMesh->cm.Tr != Matrix44m::Identity())
997 tri::UpdatePosition<CMeshO>::Matrix(trgMesh->cm, Inverse(trgMesh->cm.Tr), true);
998 }
999
1000 // Undo topology changes
1001 tri::UpdateTopology<CMeshO>::FaceFace(trgMesh->cm);
1002 tri::UpdateFlags<CMeshO>::FaceBorderFromFF(trgMesh->cm);
1003
1004 for (trgTexInd = 0; trgTexInd < numTrgTex; trgTexInd++)
1005 {
1006 // Revert alpha values for border edge pixels to 255
1007 cb(81, "Cleaning up texture ...");
1008 for (int y = 0; y<textH; ++y)
1009 for (int x = 0; x<textW; ++x)
1010 {
1011 QRgb px = trgImgs[trgTexInd].pixel(x, y);
1012 if (qAlpha(px) < 255 && (!pp || qAlpha(px) > 0))
1013 trgImgs[trgTexInd].setPixel(x, y, px | 0xff000000);
1014 }
1015
1016 // PullPush
1017 if (pp)
1018 {
1019 cb(85, "Filling texture holes...");
1020 PullPush(trgImgs[trgTexInd], qRgba(0, 0, 0, 0));
1021 }
1022
1023 // Save texture
1024 cb(90, "Saving texture ...");
1025 CheckError(!trgImgs[trgTexInd].save(trgTextureFileNames[trgTexInd]), "Texture file cannot be saved");
1026 Log("Texture \"%s\" Created", trgTextureFileNames[trgTexInd].toStdString().c_str());
1027 assert(QFile(trgTextureFileNames[trgTexInd]).exists());
1028 }
1029
1030 if (assign && !overwrite)
1031 {
1032 m.cm.textures.clear();
1033 for (trgTexInd = 0; trgTexInd < numTrgTex; trgTexInd++)
1034 m.cm.textures.push_back(trgTextureFileNames[trgTexInd].toStdString());
1035 }
1036
1037 cb(100, "Done");
1038 }
1039 break;
1040
1041 case FP_TEX_TO_VCOLOR_TRANSFER :
1042 {
1043 MeshModel *srcMesh = par.getMesh("sourceMesh");
1044 MeshModel *trgMesh = par.getMesh("targetMesh");
1045 float upperbound = par.getAbsPerc("upperBound"); // maximum distance to stop search
1046
1047 assert(srcMesh!=NULL);
1048 assert(trgMesh!=NULL);
1049
1050 CheckError(srcMesh->cm.fn == 0, "Source mesh requires to have faces");
1051
1052 // Check whether is possible to access source mesh texture
1053 CheckError(!srcMesh->hasDataMask(MeshModel::MM_WEDGTEXCOORD), "Source mesh doesn't have Per Wedge Texture Coordinates");
1054 CheckError(srcMesh->cm.textures.empty(), "Source mesh doesn't have any associated texture");
1055
1056 vector <QImage> srcImgs;
1057 srcImgs.resize(srcMesh->cm.textures.size());
1058 QString path;
1059
1060 for (size_t textInd = 0; textInd < srcMesh->cm.textures.size(); textInd++)
1061 {
1062 path = m.fullName();
1063 path = path.left(std::max<int>(path.lastIndexOf('\\'), path.lastIndexOf('/')) + 1).append(srcMesh->cm.textures[textInd].c_str());
1064 CheckError(!QFile(path).exists(), QString("Source texture \"").append(path).append("\" doesn't exists"));
1065 CheckError(!srcImgs[textInd].load(path), QString("Source texture \"").append(path).append("\" cannot be opened"));
1066 }
1067
1068 trgMesh->updateDataMask(MeshModel::MM_VERTCOLOR);
1069 srcMesh->updateDataMask(MeshModel::MM_FACEMARK);
1070
1071 // the meshes have to be transformed
1072 // only if source different from target (if single mesh, it does not matter)
1073 if (srcMesh != trgMesh)
1074 {
1075 if (srcMesh->cm.Tr != Matrix44m::Identity())
1076 tri::UpdatePosition<CMeshO>::Matrix(srcMesh->cm, srcMesh->cm.Tr, true);
1077 if (trgMesh->cm.Tr != Matrix44m::Identity())
1078 tri::UpdatePosition<CMeshO>::Matrix(trgMesh->cm, trgMesh->cm.Tr, true);
1079 }
1080
1081 tri::UpdateNormal<CMeshO>::PerFaceNormalized(srcMesh->cm);
1082 // Colorizing vertices
1083 VertexSampler vs(srcMesh->cm, srcImgs, upperbound);
1084 vs.InitCallback(cb, trgMesh->cm.vn);
1085 tri::SurfaceSampling<CMeshO,VertexSampler>::VertexUniform(trgMesh->cm,vs,trgMesh->cm.vn);
1086
1087 // the meshes have to return to their original position
1088 // only if source different from target (if single mesh, it does not matter)
1089 if (srcMesh != trgMesh)
1090 {
1091 if (srcMesh->cm.Tr != Matrix44m::Identity())
1092 tri::UpdatePosition<CMeshO>::Matrix(srcMesh->cm, Inverse(srcMesh->cm.Tr), true);
1093 if (trgMesh->cm.Tr != Matrix44m::Identity())
1094 tri::UpdatePosition<CMeshO>::Matrix(trgMesh->cm, Inverse(trgMesh->cm.Tr), true);
1095 }
1096
1097 srcMesh->clearDataMask(MeshModel::MM_FACEMARK);
1098 }
1099 break;
1100
1101 default: assert(0);
1102 }
1103
1104 return true;
1105 }
1106
filterArity(QAction * filter) const1107 MeshFilterInterface::FILTER_ARITY FilterTexturePlugin::filterArity( QAction * filter ) const
1108 {
1109 switch(ID(filter))
1110 {
1111 case FP_VORONOI_ATLAS :
1112 case FP_UV_WEDGE_TO_VERTEX :
1113 case FP_UV_VERTEX_TO_WEDGE :
1114 case FP_BASIC_TRIANGLE_MAPPING :
1115 case FP_PLANAR_MAPPING :
1116 case FP_SET_TEXTURE :
1117 case FP_COLOR_TO_TEXTURE :
1118 return MeshFilterInterface::SINGLE_MESH;
1119 case FP_TRANSFER_TO_TEXTURE :
1120 case FP_TEX_TO_VCOLOR_TRANSFER :
1121 return MeshFilterInterface::FIXED;
1122 }
1123 return MeshFilterInterface::NONE;
1124 }
1125
1126
1127 MESHLAB_PLUGIN_NAME_EXPORTER(FilterTexturePlugin)
1128