- 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:
Luigi Malomo malomo 2009-12-01 19:47:48 +00:00
parent 7a36660750
commit d42e1b0c33
4 changed files with 529 additions and 115 deletions

View File

@ -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;

View File

@ -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;

View File

@ -1,7 +1,8 @@
include (../../sharedfilter.pri)
HEADERS += filter_texture.h \
pushpull.h
pushpull.h \
rastering.h
SOURCES += filter_texture.cpp

View 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