mirror of
https://github.com/lucaspalomodevelop/meshlab.git
synced 2026-03-20 11:26:11 +00:00
- implemented Vertex Color to Texture transfer (cross-mesh)
- bugfix in "basic parametrization" filter (space optimizing method). Now face longest edge is ACTUALLY mapped to longest edge in parametrization domain - tons of changes in rasterization functions - minor optimizations - lots of code refactoring
This commit is contained in:
parent
7a36660750
commit
d42e1b0c33
@ -35,6 +35,7 @@
|
||||
|
||||
#include "filter_texture.h"
|
||||
#include "pushpull.h"
|
||||
#include "rastering.h"
|
||||
|
||||
|
||||
FilterTexturePlugin::FilterTexturePlugin()
|
||||
@ -43,7 +44,8 @@ FilterTexturePlugin::FilterTexturePlugin()
|
||||
<< FP_UV_WEDGE_TO_VERTEX
|
||||
<< FP_BASIC_TRIANGLE_MAPPING
|
||||
<< FP_SET_TEXTURE
|
||||
<< FP_COLOR_TO_TEXTURE;
|
||||
<< FP_COLOR_TO_TEXTURE
|
||||
<< FP_MESH_TEXCOLOR_TRANSFER;
|
||||
|
||||
foreach(FilterIDType tt , types())
|
||||
actionList << new QAction(filterName(tt), this);
|
||||
@ -56,7 +58,8 @@ QString FilterTexturePlugin::filterName(FilterIDType filterId) const
|
||||
case FP_UV_WEDGE_TO_VERTEX : return QString("Convert PerWedge UV into PerVertex UV");
|
||||
case FP_BASIC_TRIANGLE_MAPPING : return QString("Basic Triangle Mapping");
|
||||
case FP_SET_TEXTURE : return QString("Set Texture");
|
||||
case FP_COLOR_TO_TEXTURE : return QString("Vertex Color to Texture transfer");
|
||||
case FP_COLOR_TO_TEXTURE : return QString("Vertex Color to Texture");
|
||||
case FP_MESH_TEXCOLOR_TRANSFER : return QString("VertexColor/Texture to Texture transfer");
|
||||
default : assert(0);
|
||||
}
|
||||
}
|
||||
@ -73,6 +76,7 @@ QString FilterTexturePlugin::filterInfo(FilterIDType filterId) const
|
||||
case FP_SET_TEXTURE : return QString("Set a texture associated with current mesh parametrization.<br>"
|
||||
"If the texture provided exists it will be simply set else a dummy one will be created in the same directory");
|
||||
case FP_COLOR_TO_TEXTURE : return QString("Fills the specified texture accordingly to per vertex color");
|
||||
case FP_MESH_TEXCOLOR_TRANSFER : return QString("Transfer vertex color or texture color from one mesh to another's texture.");
|
||||
default : assert(0);
|
||||
}
|
||||
return QString("Unknown Filter");
|
||||
@ -87,6 +91,7 @@ int FilterTexturePlugin::getPreConditions(QAction *a) const
|
||||
case FP_BASIC_TRIANGLE_MAPPING : return MeshFilterInterface::FP_Face;
|
||||
case FP_SET_TEXTURE : return MeshFilterInterface::FP_WedgeTexCoord;
|
||||
case FP_COLOR_TO_TEXTURE : return MeshFilterInterface::FP_WedgeTexCoord + MeshFilterInterface::FP_VertexColor;
|
||||
case FP_MESH_TEXCOLOR_TRANSFER : return MeshFilterInterface::FP_WedgeTexCoord;
|
||||
default: assert(0);
|
||||
}
|
||||
return MeshFilterInterface::FP_Generic;
|
||||
@ -101,6 +106,7 @@ int FilterTexturePlugin::getRequirements(QAction *a)
|
||||
case FP_BASIC_TRIANGLE_MAPPING :
|
||||
case FP_SET_TEXTURE : return MeshModel::MM_NONE;
|
||||
case FP_COLOR_TO_TEXTURE : return MeshModel::MM_FACEFACETOPO;
|
||||
case FP_MESH_TEXCOLOR_TRANSFER : return MeshModel::MM_NONE;
|
||||
default: assert(0);
|
||||
}
|
||||
return MeshModel::MM_NONE;
|
||||
@ -115,6 +121,7 @@ int FilterTexturePlugin::postCondition( QAction *a) const
|
||||
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_MESH_TEXCOLOR_TRANSFER : return MeshModel::MM_UNKNOWN;
|
||||
default: assert(0);
|
||||
}
|
||||
return MeshModel::MM_NONE;
|
||||
@ -132,12 +139,28 @@ FilterTexturePlugin::FilterClass FilterTexturePlugin::getClass(QAction *a)
|
||||
case FP_BASIC_TRIANGLE_MAPPING :
|
||||
case FP_SET_TEXTURE :
|
||||
case FP_COLOR_TO_TEXTURE :
|
||||
case FP_MESH_TEXCOLOR_TRANSFER :
|
||||
return MeshFilterInterface::Texture;
|
||||
default : assert(0);
|
||||
}
|
||||
return MeshFilterInterface::Generic;
|
||||
}
|
||||
|
||||
static QString extractFilenameWOExt(MeshModel* mm)
|
||||
{
|
||||
QString fileName(mm->fileName.c_str());
|
||||
int lastPoint = fileName.lastIndexOf(".");
|
||||
if (lastPoint <= 0)
|
||||
fileName.clear();
|
||||
else {
|
||||
fileName = fileName.left(lastPoint);
|
||||
lastPoint = std::max<int>(fileName.lastIndexOf('\\'),fileName.lastIndexOf('/'));
|
||||
if (lastPoint > 0)
|
||||
fileName = fileName.right(fileName.size() - 1 - lastPoint);
|
||||
}
|
||||
return fileName;
|
||||
}
|
||||
|
||||
// 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,
|
||||
@ -145,7 +168,7 @@ FilterTexturePlugin::FilterClass FilterTexturePlugin::getClass(QAction *a)
|
||||
// - 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, MeshModel &m, RichParameterSet & parlst)
|
||||
void FilterTexturePlugin::initParameterSet(QAction *action, MeshDocument &md, RichParameterSet & parlst)
|
||||
{
|
||||
switch(ID(action)) {
|
||||
case FP_UV_TO_COLOR :
|
||||
@ -156,36 +179,18 @@ void FilterTexturePlugin::initParameterSet(QAction *action, MeshModel &m, RichPa
|
||||
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", 1, "Inter-Triangle border (px)", "Specifies how many pixels to be left between triangles in parametrization domain"));
|
||||
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", 0, 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(m.fileName.c_str());
|
||||
int lastPoint = fileName.lastIndexOf(".");
|
||||
if (lastPoint <= 0)
|
||||
fileName = QString("");
|
||||
else {
|
||||
fileName = fileName.left(lastPoint);
|
||||
lastPoint = std::max<int>(fileName.lastIndexOf('\\'),fileName.lastIndexOf('/'));
|
||||
if (lastPoint > 0)
|
||||
fileName = fileName.right(fileName.size() - 1 - lastPoint);
|
||||
}
|
||||
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(m.fileName.c_str());
|
||||
int lastPoint = fileName.lastIndexOf(".");
|
||||
if (lastPoint <= 0)
|
||||
fileName = QString("");
|
||||
else {
|
||||
fileName = fileName.left(lastPoint);
|
||||
lastPoint = std::max<int>(fileName.lastIndexOf('\\'),fileName.lastIndexOf('/'));
|
||||
if (lastPoint > 0)
|
||||
fileName = fileName.right(fileName.size() - 1 - lastPoint);
|
||||
}
|
||||
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"));
|
||||
@ -194,6 +199,24 @@ void FilterTexturePlugin::initParameterSet(QAction *action, MeshModel &m, RichPa
|
||||
parlst.addParam(new RichBool("assign", false, "Assign texture", "assign the newly created texture"));
|
||||
}
|
||||
break;
|
||||
case FP_MESH_TEXCOLOR_TRANSFER : {
|
||||
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 RichEnum("data", 0, QStringList("Vertex Color") << "Texture Color", "Color Data Source",
|
||||
"Choose to transfer color information from source mesh texture or vertex color"));
|
||||
parlst.addParam(new RichMesh ("targetMesh",md.mm(),&md, "Target Mesh",
|
||||
"The mesh whose texture will be filled according to source mesh texture or vertex color"));
|
||||
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")));
|
||||
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"));
|
||||
}
|
||||
break;
|
||||
default : assert(0);
|
||||
}
|
||||
}
|
||||
@ -272,41 +295,9 @@ inline void buildTrianglesCache(std::vector<Tri2> &arr, int maxLevels, float bor
|
||||
if (--maxLevels <= 0) return;
|
||||
buildTrianglesCache (arr, maxLevels, border, quadSize, 2*idx+2);
|
||||
buildTrianglesCache (arr, maxLevels, border, quadSize, 2*idx+3);
|
||||
|
||||
}
|
||||
|
||||
/////// "COLOR TO TEXTURE" FILTER NEEDED STUFF
|
||||
class RasterSampler
|
||||
{
|
||||
public:
|
||||
RasterSampler(QImage &_img, int _th) : img(_img), texH(_th) {assert(texH>0);};
|
||||
|
||||
QImage &img;
|
||||
int texH;
|
||||
|
||||
void AddTextureSample(const CMeshO::FaceType &f, const CMeshO::CoordType &p, const vcg::Point2i &tp)
|
||||
{
|
||||
CMeshO::VertexType::ColorType c;
|
||||
c.lerp(f.V(0)->cC(), f.V(1)->cC(), f.V(2)->cC(), p);
|
||||
img.setPixel(tp.X(), texH - tp.Y(), qRgba(c[0], c[1], c[2], 255));
|
||||
}
|
||||
};
|
||||
|
||||
template <class MetroMesh, class VertexSampler>
|
||||
static void TextureCorrected(MetroMesh & m, VertexSampler &ps, int textureWidth, int textureHeight)
|
||||
{
|
||||
typedef typename MetroMesh::FaceIterator FaceIterator;
|
||||
FaceIterator fi;
|
||||
|
||||
printf("Similar Triangles face sampling\n");
|
||||
for(fi=m.face.begin(); fi != m.face.end(); fi++)
|
||||
{
|
||||
vcg::Point2f ti[3];
|
||||
for(int i=0;i<3;++i)
|
||||
ti[i]=vcg::Point2f((*fi).WT(i).U() * textureWidth - 0.5, (*fi).WT(i).V() * textureHeight + 0.5);
|
||||
vcg::tri::SurfaceSampling<MetroMesh,VertexSampler>::SingleFaceRaster(*fi, ps, ti[0],ti[1],ti[2]);
|
||||
}
|
||||
}
|
||||
|
||||
// ERROR CHECKING UTILITY
|
||||
#define CheckError(x,y); if ((x)) {this->errorMessage = (y); return false;}
|
||||
@ -494,15 +485,17 @@ bool FilterTexturePlugin::applyFilter(QAction *filter, MeshModel &m, RichParamet
|
||||
int fidx = *it;
|
||||
int lEdge = getLongestEdge(m.cm.face[fidx]);
|
||||
Tri2 &t = cache[pos];
|
||||
tmp = t.P0(lEdge) + origin;
|
||||
m.cm.face[fidx].WT(0) = CFaceO::TexCoordType(tmp.X(), tmp.Y());
|
||||
m.cm.face[fidx].WT(0).N() = 0;
|
||||
tmp = t.P1(lEdge) + origin;
|
||||
m.cm.face[fidx].WT(1) = CFaceO::TexCoordType(tmp.X(), tmp.Y());
|
||||
m.cm.face[fidx].WT(1).N() = 0;
|
||||
tmp = t.P2(lEdge) + origin;
|
||||
m.cm.face[fidx].WT(2) = CFaceO::TexCoordType(tmp.X(), tmp.Y());
|
||||
m.cm.face[fidx].WT(2).N() = 0;
|
||||
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...");
|
||||
}
|
||||
@ -600,18 +593,14 @@ bool FilterTexturePlugin::applyFilter(QAction *filter, MeshModel &m, RichParamet
|
||||
|
||||
// Creates path to texture file
|
||||
QString fileName(m.fileName.c_str());
|
||||
int lastPoint = std::max<int>(fileName.lastIndexOf('\\'),fileName.lastIndexOf('/'));
|
||||
if (lastPoint < 0)
|
||||
fileName = textName;
|
||||
else
|
||||
fileName = fileName.left(lastPoint).append(QDir::separator()).append(textName);
|
||||
fileName = fileName.left(std::max<int>(fileName.lastIndexOf('\\'),fileName.lastIndexOf('/'))+1).append(textName);
|
||||
|
||||
QFile textFile(fileName);
|
||||
if (!textFile.exists())
|
||||
{
|
||||
//create dummy checkers texture image
|
||||
// Create dummy checkers texture image
|
||||
QImage img(textDim, textDim, QImage::Format_RGB32);
|
||||
img.fill(Qt::white);
|
||||
img.fill(qRgb(255,255,255)); // white
|
||||
QPainter p(&img);
|
||||
QBrush gray(Qt::gray);
|
||||
QRect rect(0,0,CHECKERDIM,CHECKERDIM);
|
||||
@ -625,12 +614,14 @@ bool FilterTexturePlugin::applyFilter(QAction *filter, MeshModel &m, RichParamet
|
||||
p.fillRect(rect, gray);
|
||||
}
|
||||
}
|
||||
//save
|
||||
|
||||
// Save texture
|
||||
CheckError(!img.save(fileName, "PNG"), "Specified file cannot be saved");
|
||||
Log(GLLogStream::FILTER, "Dummy Texture \"%s\" Created ", fileName.toStdString().c_str());
|
||||
assert(textFile.exists());
|
||||
}
|
||||
//set
|
||||
|
||||
//Assign texture
|
||||
m.cm.textures.clear();
|
||||
m.cm.textures.push_back(textName.toStdString());
|
||||
}
|
||||
@ -644,7 +635,7 @@ bool FilterTexturePlugin::applyFilter(QAction *filter, MeshModel &m, RichParamet
|
||||
bool overwrite = par.getBool("overwrite");
|
||||
bool assign = par.getBool("assign");
|
||||
|
||||
CheckError(!QFile(m.fileName.c_str()).exists(), "Save the file before creting a texture");
|
||||
CheckError(!QFile(m.fileName.c_str()).exists(), "Save the file before creating a texture");
|
||||
|
||||
QString filePath(m.fileName.c_str());
|
||||
filePath = filePath.left(std::max<int>(filePath.lastIndexOf('\\'),filePath.lastIndexOf('/'))+1);
|
||||
@ -667,66 +658,159 @@ bool FilterTexturePlugin::applyFilter(QAction *filter, MeshModel &m, RichParamet
|
||||
|
||||
// Image creation
|
||||
QImage img(QSize(textW,textH), QImage::Format_ARGB32);
|
||||
img.fill(Qt::transparent);
|
||||
img.fill(qRgba(0,0,0,0)); // transparent
|
||||
|
||||
// Compute (texture-space) border edges
|
||||
vcg::tri::UpdateTopology<CMeshO>::FaceFaceFromTexCoord(m.cm);
|
||||
vcg::tri::UpdateFlags<CMeshO>::FaceBorderFromFF(m.cm);
|
||||
|
||||
// Rasterizing safety buffer area along border edges
|
||||
QPainter p(&img);
|
||||
QGradientStops gs;
|
||||
QLinearGradient grad;
|
||||
QPen pen(Qt::SolidLine);
|
||||
grad.setCoordinateMode(QGradient::LogicalMode);
|
||||
pen.setCapStyle(Qt::RoundCap);
|
||||
pen.setWidthF(2.0);
|
||||
gs << QGradientStop(0.0,Qt::transparent) << QGradientStop(1.0,Qt::transparent);
|
||||
|
||||
CMeshO::FaceIterator fi;
|
||||
for (fi=m.cm.face.begin(); fi!=m.cm.face.end(); ++fi)
|
||||
for(int i=0;i<3;++i)
|
||||
if (fi->IsB(i))
|
||||
{
|
||||
QPointF t[2];
|
||||
CMeshO::VertexType::ColorType c[2];
|
||||
t[0] = QPointF(fi->cWT(i).U() * textW - 0.5, (1.0 - fi->cWT(i).V()) * textH - 0.5);
|
||||
t[1] = QPointF(fi->cWT((i+1)%3).U() * textW - 0.5, (1.0 - fi->cWT((i+1)%3).V()) * textH - 0.5);
|
||||
c[0] = fi->V(i)->cC();
|
||||
c[1] = fi->V((i+1)%3)->cC();
|
||||
grad.setStart(t[0].x()+0.5, t[0].y()+0.5);
|
||||
grad.setFinalStop(t[1].x()+0.5, t[1].y()+0.5);
|
||||
gs[0].second = QColor(c[0][0],c[0][1], c[0][2]);
|
||||
gs[1].second = QColor(c[1][0],c[1][1], c[1][2]);
|
||||
grad.setStops(gs);
|
||||
pen.setBrush(QBrush(grad));
|
||||
p.setPen(pen);
|
||||
p.drawLine(t[0], t[1]);
|
||||
}
|
||||
|
||||
|
||||
// Rasterizing triangles
|
||||
RasterSampler rs(img, textH);
|
||||
RasterSampler rs(img);
|
||||
rs.InitCallback(cb, m.cm.fn, 0, 80);
|
||||
TextureCorrected<CMeshO,RasterSampler>(m.cm,rs,textW,textH);
|
||||
|
||||
// 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 && qAlpha(px) > 0)
|
||||
img.setPixel(x,y, px | 0xff000000);
|
||||
}
|
||||
|
||||
// PullPush
|
||||
vcg::PullPush(img, Qt::transparent);
|
||||
cb(85, "Filling texture holes...");
|
||||
vcg::PullPush(img, qRgba(0,0,0,0));
|
||||
|
||||
// Undo topology changes
|
||||
vcg::tri::UpdateTopology<CMeshO>::FaceFace(m.cm);
|
||||
vcg::tri::UpdateFlags<CMeshO>::FaceBorderFromFF(m.cm);
|
||||
|
||||
// Save
|
||||
// Save texture
|
||||
cb(90, "Saving texture ...");
|
||||
CheckError(!img.save(filePath), "Texture file cannot be saved");
|
||||
Log(GLLogStream::FILTER, "Texture \"%s\" Created", filePath.toStdString().c_str());
|
||||
assert(QFile(filePath).exists());
|
||||
|
||||
// Assign
|
||||
// Assign texture
|
||||
if (assign && !overwrite) {
|
||||
m.cm.textures.clear();
|
||||
m.cm.textures.push_back(textName.toStdString());
|
||||
}
|
||||
cb(100, "Done");
|
||||
}
|
||||
break;
|
||||
|
||||
case FP_MESH_TEXCOLOR_TRANSFER : {
|
||||
MeshModel *srcMesh = par.getMesh("sourceMesh");
|
||||
bool colorSampling;
|
||||
switch (par.getEnum("data")) {
|
||||
case 0: colorSampling = true; break;
|
||||
case 1: colorSampling = false; break;
|
||||
default: assert(0);
|
||||
}
|
||||
MeshModel *trgMesh = par.getMesh("targetMesh");
|
||||
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");
|
||||
|
||||
CheckError(!QFile(trgMesh->fileName.c_str()).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");
|
||||
|
||||
QImage srcImg;
|
||||
|
||||
if (colorSampling) {
|
||||
CheckError(!srcMesh->hasDataMask(MeshModel::MM_VERTCOLOR), "Source mesh doesn't have Per Vertex Color");
|
||||
} else {
|
||||
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->fileName.c_str());
|
||||
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->fileName.c_str());
|
||||
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);
|
||||
vcg::tri::UpdateTopology<CMeshO>::FaceFaceFromTexCoord(trgMesh->cm);
|
||||
vcg::tri::UpdateFlags<CMeshO>::FaceBorderFromFF(trgMesh->cm);
|
||||
|
||||
// Rasterizing faces
|
||||
srcMesh->updateDataMask(MeshModel::MM_FACEMARK);
|
||||
vcg::tri::UpdateNormals<CMeshO>::PerFaceNormalized(srcMesh->cm);
|
||||
vcg::tri::UpdateFlags<CMeshO>::FaceProjection(srcMesh->cm);
|
||||
if (colorSampling)
|
||||
{
|
||||
TransferColorSampler sampler(srcMesh->cm, img, upperbound); // color sampling
|
||||
sampler.InitCallback(cb, trgMesh->cm.fn, 0, 80);
|
||||
TextureCorrected<CMeshO,TransferColorSampler>(trgMesh->cm,sampler,img.width(),img.height());
|
||||
} else {
|
||||
TransferColorSampler sampler(srcMesh->cm, img, &srcImg, upperbound); // texture sampling
|
||||
sampler.InitCallback(cb, trgMesh->cm.fn, 0, 80);
|
||||
TextureCorrected<CMeshO,TransferColorSampler>(trgMesh->cm,sampler,img.width(),img.height());
|
||||
}
|
||||
|
||||
// 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 && qAlpha(px) > 0)
|
||||
img.setPixel(x,y, px | 0xff000000);
|
||||
}
|
||||
|
||||
// PullPush
|
||||
cb(85, "Filling texture holes...");
|
||||
vcg::PullPush(img, qRgba(0,0,0,0));
|
||||
|
||||
// Undo topology changes
|
||||
vcg::tri::UpdateTopology<CMeshO>::FaceFace(trgMesh->cm);
|
||||
vcg::tri::UpdateFlags<CMeshO>::FaceBorderFromFF(trgMesh->cm);
|
||||
|
||||
// Save texture
|
||||
cb(90, "Saving texture ...");
|
||||
CheckError(!img.save(filePath), "Texture file cannot be saved");
|
||||
Log(GLLogStream::FILTER, "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;
|
||||
default: assert(0);
|
||||
}
|
||||
return true;
|
||||
|
||||
@ -40,7 +40,8 @@ public:
|
||||
FP_UV_WEDGE_TO_VERTEX,
|
||||
FP_BASIC_TRIANGLE_MAPPING,
|
||||
FP_SET_TEXTURE,
|
||||
FP_COLOR_TO_TEXTURE
|
||||
FP_COLOR_TO_TEXTURE,
|
||||
FP_MESH_TEXCOLOR_TRANSFER
|
||||
};
|
||||
|
||||
FilterTexturePlugin();
|
||||
@ -48,7 +49,7 @@ public:
|
||||
virtual QString filterName(FilterIDType filter) const;
|
||||
virtual QString filterInfo(FilterIDType filter) const;
|
||||
virtual bool autoDialog(QAction *) {return true;}
|
||||
virtual void initParameterSet(QAction *,MeshModel &/*m*/, RichParameterSet & /*parent*/);
|
||||
virtual void initParameterSet(QAction *,MeshDocument &/*m*/, RichParameterSet & /*parent*/);
|
||||
virtual bool applyFilter(QAction *filter, MeshModel &m, RichParameterSet & /*parent*/, vcg::CallBackPos * cb);
|
||||
virtual int getRequirements(QAction *);
|
||||
virtual int getPreConditions(QAction *) const;
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
include (../../sharedfilter.pri)
|
||||
|
||||
HEADERS += filter_texture.h \
|
||||
pushpull.h
|
||||
pushpull.h \
|
||||
rastering.h
|
||||
|
||||
SOURCES += filter_texture.cpp
|
||||
|
||||
|
||||
328
src/meshlabplugins/filter_texture/rastering.h
Normal file
328
src/meshlabplugins/filter_texture/rastering.h
Normal file
@ -0,0 +1,328 @@
|
||||
/****************************************************************************
|
||||
* 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. *
|
||||
* *
|
||||
****************************************************************************/
|
||||
|
||||
#ifndef _RASTERING_H
|
||||
#define _RASTERING_H
|
||||
|
||||
#include <QtGui>
|
||||
#include <meshlab/interfaces.h>
|
||||
#include <vcg/complex/trimesh/point_sampling.h>
|
||||
#include <vcg/space/triangle2.h>
|
||||
|
||||
class RasterSampler
|
||||
{
|
||||
QImage &trgImg;
|
||||
|
||||
// Callback stuff
|
||||
vcg::CallBackPos *cb;
|
||||
const CMeshO::FaceType *currFace;
|
||||
int faceNo, faceCnt, start, offset;
|
||||
|
||||
public:
|
||||
RasterSampler(QImage &_img) : trgImg(_img) {}
|
||||
|
||||
void InitCallback(vcg::CallBackPos *_cb, int _faceNo, int _start=0, int _offset=100)
|
||||
{
|
||||
assert(_faceNo > 0);
|
||||
assert(_start>=0);
|
||||
assert(_offset>=0 && _offset <= 100-_start);
|
||||
cb = _cb;
|
||||
faceNo = _faceNo;
|
||||
faceCnt = 0;
|
||||
start = _start;
|
||||
offset = _offset;
|
||||
currFace = NULL;
|
||||
}
|
||||
|
||||
// expects points outside face (face affecting) with baycentric coords = (bX, bY, -<distance from closest edge>)
|
||||
void AddTextureSample(const CMeshO::FaceType &f, const CMeshO::CoordType &p, const vcg::Point2i &tp)
|
||||
{
|
||||
CMeshO::VertexType::ColorType c;
|
||||
CMeshO::CoordType bary = p;
|
||||
int alpha = 255;
|
||||
if (fabs(p[0]+p[1]+p[2]-1)>=0.00001)
|
||||
if (p[0] <.0) {alpha = 254+p[0]*128; bary[0] = 0.;} else
|
||||
if (p[1] <.0) {alpha = 254+p[1]*128; bary[1] = 0.;} else
|
||||
if (p[2] <.0) {alpha = 254+p[2]*128; bary[2] = 0.;}
|
||||
if (alpha==255 || qAlpha(trgImg.pixel(tp.X(), trgImg.height() - tp.Y())) < alpha)
|
||||
{
|
||||
c.lerp(f.V(0)->cC(), f.V(1)->cC(), f.V(2)->cC(), bary);
|
||||
trgImg.setPixel(tp.X(), trgImg.height() - tp.Y(), qRgba(c[0], c[1], c[2], alpha));
|
||||
}
|
||||
if (cb)
|
||||
{
|
||||
if (&f != currFace) {currFace = &f; ++faceCnt;}
|
||||
cb(start + faceCnt*offset/faceNo, "Rasterizing faces ...");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class TransferColorSampler
|
||||
{
|
||||
typedef vcg::GridStaticPtr<CMeshO::FaceType, CMeshO::ScalarType > MetroMeshGrid;
|
||||
|
||||
QImage &trgImg;
|
||||
QImage *srcImg;
|
||||
float dist_upper_bound;
|
||||
bool fromTexture;
|
||||
MetroMeshGrid unifGridFace;
|
||||
|
||||
// Callback stuff
|
||||
vcg::CallBackPos *cb;
|
||||
const CMeshO::FaceType *currFace;
|
||||
int faceNo, faceCnt, start, offset;
|
||||
|
||||
typedef vcg::tri::FaceTmark<CMeshO> MarkerFace;
|
||||
MarkerFace markerFunctor;
|
||||
|
||||
public:
|
||||
TransferColorSampler(CMeshO &_srcMesh, QImage &_trgImg, float upperBound)
|
||||
: trgImg(_trgImg), dist_upper_bound(upperBound)
|
||||
{
|
||||
unifGridFace.Set(_srcMesh.face.begin(),_srcMesh.face.end());
|
||||
markerFunctor.SetMesh(&_srcMesh);
|
||||
fromTexture = false;
|
||||
}
|
||||
|
||||
TransferColorSampler(CMeshO &_srcMesh, QImage &_trgImg, QImage *_srcImg, float upperBound)
|
||||
: trgImg(_trgImg), dist_upper_bound(upperBound)
|
||||
{
|
||||
assert(_srcImg != NULL);
|
||||
srcImg = _srcImg;
|
||||
unifGridFace.Set(_srcMesh.face.begin(),_srcMesh.face.end());
|
||||
markerFunctor.SetMesh(&_srcMesh);
|
||||
fromTexture = true;
|
||||
}
|
||||
|
||||
void InitCallback(vcg::CallBackPos *_cb, int _faceNo, int _start=0, int _offset=100)
|
||||
{
|
||||
assert(_faceNo > 0);
|
||||
assert(_start>=0);
|
||||
assert(_offset>=0 && _offset <= 100-_start);
|
||||
cb = _cb;
|
||||
faceNo = _faceNo;
|
||||
faceCnt = 0;
|
||||
start = _start;
|
||||
offset = _offset;
|
||||
currFace = NULL;
|
||||
}
|
||||
// expects points outside face (face affecting) with baycentric coords = (bX, bY, -<distance from closest edge instead of 0>)
|
||||
void AddTextureSample(const CMeshO::FaceType &f, const CMeshO::CoordType &p, const vcg::Point2i &tp)
|
||||
{
|
||||
// Calculate correct brycentric coords
|
||||
CMeshO::CoordType bary = p;
|
||||
int alpha = 255;
|
||||
if (fabs(p[0]+p[1]+p[2]-1)>=0.00001)
|
||||
if (p[0] <.0) {alpha = 254+p[0]*128; bary[0] = 0.;} else
|
||||
if (p[1] <.0) {alpha = 254+p[1]*128; bary[1] = 0.;} else
|
||||
if (p[2] <.0) {alpha = 254+p[2]*128; bary[2] = 0.;}
|
||||
|
||||
// Get point on face
|
||||
CMeshO::CoordType startPt;
|
||||
startPt[0] = bary[0]*f.V(0)->P().X()+bary[1]*f.V(1)->P().X()+bary[2]*f.V(2)->P().X();
|
||||
startPt[1] = bary[0]*f.V(0)->P().Y()+bary[1]*f.V(1)->P().Y()+bary[2]*f.V(2)->P().Y();
|
||||
startPt[2] = bary[0]*f.V(0)->P().Z()+bary[1]*f.V(1)->P().Z()+bary[2]*f.V(2)->P().Z();
|
||||
|
||||
// Retrieve closest point on source mesh
|
||||
CMeshO::CoordType closestPt;
|
||||
vcg::face::PointDistanceBaseFunctor<CMeshO::ScalarType> PDistFunct;
|
||||
float dist=dist_upper_bound;
|
||||
CMeshO::FaceType *nearestF;
|
||||
nearestF = unifGridFace.GetClosest(PDistFunct, markerFunctor, startPt, dist_upper_bound, dist, closestPt);
|
||||
if (dist == dist_upper_bound) return;
|
||||
|
||||
// Convert point to barycentric coords
|
||||
vcg::Point3f interp;
|
||||
int axis = 0;
|
||||
float tmp = -1;
|
||||
for (int i=0; i<3; ++i)
|
||||
if (fabs(nearestF->cN()[i]) > tmp) {tmp = fabs(nearestF->cN()[i]); axis = i;}
|
||||
bool ret = InterpolationParameters(*nearestF, axis, closestPt, interp);
|
||||
assert(ret);
|
||||
interp[2]=1.0-interp[1]-interp[0];
|
||||
|
||||
if (fromTexture)
|
||||
{
|
||||
// NOT IMPLEMENTED YET
|
||||
}
|
||||
else
|
||||
{
|
||||
// Calculate and set color
|
||||
if (alpha==255 || qAlpha(trgImg.pixel(tp.X(), trgImg.height() - tp.Y())) < alpha)
|
||||
{
|
||||
CMeshO::VertexType::ColorType c;
|
||||
c.lerp(nearestF->V(0)->cC(), nearestF->V(1)->cC(), nearestF->V(2)->cC(), interp);
|
||||
trgImg.setPixel(tp.X(), trgImg.height() - tp.Y(), qRgba(c[0], c[1], c[2], alpha));
|
||||
}
|
||||
}
|
||||
|
||||
if (cb)
|
||||
{
|
||||
if (&f != currFace) {currFace = &f; ++faceCnt;}
|
||||
cb(start + faceCnt*offset/faceNo, "Rasterizing faces ...");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <class MetroMesh, class VertexSampler>
|
||||
static void SingleFaceRasterWEdge(typename MetroMesh::FaceType &f, VertexSampler &ps,
|
||||
const vcg::Point2<typename MetroMesh::ScalarType> & v0,
|
||||
const vcg::Point2<typename MetroMesh::ScalarType> & v1,
|
||||
const vcg::Point2<typename MetroMesh::ScalarType> & v2)
|
||||
{
|
||||
typedef typename MetroMesh::ScalarType S;
|
||||
// Calcolo bounding box
|
||||
vcg::Box2i bbox;
|
||||
|
||||
if(v0[0]<v1[0]) { bbox.min[0]=int(v0[0]); bbox.max[0]=int(v1[0]); }
|
||||
else { bbox.min[0]=int(v1[0]); bbox.max[0]=int(v0[0]); }
|
||||
if(v0[1]<v1[1]) { bbox.min[1]=int(v0[1]); bbox.max[1]=int(v1[1]); }
|
||||
else { bbox.min[1]=int(v1[1]); bbox.max[1]=int(v0[1]); }
|
||||
if(bbox.min[0]>int(v2[0])) bbox.min[0]=int(v2[0]);
|
||||
else if(bbox.max[0]<int(v2[0])) bbox.max[0]=int(v2[0]);
|
||||
if(bbox.min[1]>int(v2[1])) bbox.min[1]=int(v2[1]);
|
||||
else if(bbox.max[1]<int(v2[1])) bbox.max[1]=int(v2[1]);
|
||||
|
||||
|
||||
// Calcolo versori degli spigoli
|
||||
vcg::Point2<S> d10 = v1 - v0;
|
||||
vcg::Point2<S> d21 = v2 - v1;
|
||||
vcg::Point2<S> d02 = v0 - v2;
|
||||
|
||||
// Preparazione prodotti scalari
|
||||
S b0 = (bbox.min[0]-v0[0])*d10[1] - (bbox.min[1]-v0[1])*d10[0];
|
||||
S b1 = (bbox.min[0]-v1[0])*d21[1] - (bbox.min[1]-v1[1])*d21[0];
|
||||
S b2 = (bbox.min[0]-v2[0])*d02[1] - (bbox.min[1]-v2[1])*d02[0];
|
||||
// Preparazione degli steps
|
||||
S db0 = d10[1];
|
||||
S db1 = d21[1];
|
||||
S db2 = d02[1];
|
||||
// Preparazione segni
|
||||
S dn0 = -d10[0];
|
||||
S dn1 = -d21[0];
|
||||
S dn2 = -d02[0];
|
||||
|
||||
//Calcolo orientamento
|
||||
bool flipped = d02 * vcg::Point2<S>(-d10[1], d10[0]) >= 0;
|
||||
|
||||
// Precalcolo Calcolo edge di bordo
|
||||
vcg::Segment2<S> borderEdges[3];
|
||||
S edgeLength[3];
|
||||
unsigned char edgeMask = 0;
|
||||
if (f.IsB(0)) {
|
||||
borderEdges[0] = vcg::Segment2<S>(v0, v1);
|
||||
edgeLength[0] = borderEdges[0].Length();
|
||||
edgeMask |= 1;
|
||||
}
|
||||
if (f.IsB(1)) {
|
||||
borderEdges[1] = vcg::Segment2<S>(v1, v2);
|
||||
edgeLength[1] = borderEdges[1].Length();
|
||||
edgeMask |= 2;
|
||||
}
|
||||
if (f.IsB(2)) {
|
||||
borderEdges[2] = vcg::Segment2<S>(v2, v0);
|
||||
edgeLength[2] = borderEdges[2].Length();
|
||||
edgeMask |= 4;
|
||||
}
|
||||
|
||||
// Rasterizzazione
|
||||
double de = v0[0]*v1[1]-v0[0]*v2[1]-v1[0]*v0[1]+v1[0]*v2[1]-v2[0]*v1[1]+v2[0]*v0[1];
|
||||
|
||||
for(int x=bbox.min[0]-1;x<=bbox.max[0]+1;++x)
|
||||
{
|
||||
bool in = false;
|
||||
S n[3] = { b0-db0-dn0, b1-db1-dn1, b2-db2-dn2};
|
||||
for(int y=bbox.min[1]-1;y<=bbox.max[1]+1;++y)
|
||||
{
|
||||
if((n[0]>=0 && n[1]>=0 && n[2]>=0) || (n[0]<=0 && n[1]<=0 && n[2]<=0))
|
||||
{
|
||||
typename MetroMesh::CoordType baryCoord;
|
||||
baryCoord[0] = double(-y*v1[0]+v2[0]*y+v1[1]*x-v2[0]*v1[1]+v1[0]*v2[1]-x*v2[1])/de;
|
||||
baryCoord[1] = -double( x*v0[1]-x*v2[1]-v0[0]*y+v0[0]*v2[1]-v2[0]*v0[1]+v2[0]*y)/de;
|
||||
baryCoord[2] = 1-baryCoord[0]-baryCoord[1];
|
||||
|
||||
ps.AddTextureSample(f, baryCoord, vcg::Point2i(x,y));
|
||||
in = true;
|
||||
} else {
|
||||
// Check wheter a pixel outside (on a border edge side) triangle affects color inside it
|
||||
vcg::Point2<S> px(x, y);
|
||||
vcg::Point2<S> closePoint;
|
||||
int closeEdge = -1;
|
||||
S minDst = FLT_MAX;
|
||||
|
||||
for (int i=0, t=0; t<2 && i<3 && (edgeMask>>i)%2 ; ++i)
|
||||
{
|
||||
vcg::Point2<S> close;
|
||||
S dst;
|
||||
if ( (flipped && n[i]<0 || !flipped && n[i]>0) &&
|
||||
(dst = ((close = ClosestPoint(borderEdges[i], px)) - px).Norm()) < minDst &&
|
||||
close.X() > px.X()-1 && close.X() < px.X()+1 &&
|
||||
close.Y() > px.Y()-1 && close.Y() < px.Y()+1)
|
||||
{
|
||||
minDst = dst;
|
||||
closePoint = close;
|
||||
closeEdge = i;
|
||||
++t;
|
||||
}
|
||||
}
|
||||
|
||||
if (closeEdge >= 0)
|
||||
{
|
||||
// Add x,y sample with closePoint barycentric coords and -min
|
||||
typename MetroMesh::CoordType baryCoord;
|
||||
baryCoord[closeEdge] = (closePoint - borderEdges[closeEdge].P(1)).Norm()/edgeLength[closeEdge];
|
||||
baryCoord[(closeEdge+1)%3] = 1 - baryCoord[closeEdge];
|
||||
baryCoord[(closeEdge+2)%3] = -minDst;
|
||||
ps.AddTextureSample(f, baryCoord, vcg::Point2i(x,y));
|
||||
in = true;
|
||||
} else if (in) break;
|
||||
}
|
||||
n[0] += dn0;
|
||||
n[1] += dn1;
|
||||
n[2] += dn2;
|
||||
}
|
||||
b0 += db0;
|
||||
b1 += db1;
|
||||
b2 += db2;
|
||||
}
|
||||
}
|
||||
|
||||
template <class MetroMesh, class VertexSampler>
|
||||
static void TextureCorrected(MetroMesh & m, VertexSampler &ps, int textureWidth, int textureHeight)
|
||||
{
|
||||
typedef typename MetroMesh::FaceIterator FaceIterator;
|
||||
FaceIterator fi;
|
||||
|
||||
printf("Similar Triangles face sampling\n");
|
||||
for(fi=m.face.begin(); fi != m.face.end(); fi++)
|
||||
if (!fi->IsD())
|
||||
{
|
||||
vcg::Point2f ti[3];
|
||||
for(int i=0;i<3;++i)
|
||||
ti[i]=vcg::Point2f((*fi).WT(i).U() * textureWidth - 0.5, (*fi).WT(i).V() * textureHeight + 0.5);
|
||||
//vcg::tri::SurfaceSampling<MetroMesh,VertexSampler>::SingleFaceRaster(*fi, ps, ti[0],ti[1],ti[2]);
|
||||
SingleFaceRasterWEdge<MetroMesh,VertexSampler>(*fi, ps, ti[0],ti[1],ti[2]);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
Loading…
x
Reference in New Issue
Block a user