Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
263 changes: 263 additions & 0 deletions TensorField.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,62 @@ void TensorField::applyWaterMap(QString filename)
mWaterMapIsLoaded = true;
}

void TensorField::applyBoundaries(QString filename)
{
QImage waterMap = QImage(filename);
if(waterMap.isNull())
{
qCritical()<<"applyWaterMap(): File "<<filename<<" not found";
return;
}
if(waterMap.size() != mFieldSize)
{
qCritical()<<"applyWaterMap(): Watermap must be of same size as the tensor field";
return;
}
QImage mapSobelX, mapSobelY;
QColor pixSobelX, pixSobelY;
float theta, r;

mapSobelX = applySobelX(waterMap);
mapSobelY = applySobelY(waterMap);

for(int i=0; i<mFieldSize.width()-1 ; i++)
{
for(int j=0; j<mFieldSize.height()-1 ; j++)
{
QVector4D tensor;

pixSobelX = mapSobelX.pixel(j,i);
pixSobelY = mapSobelY.pixel(j,i);

theta = std::atan2(abs(pixSobelY.blue()),abs(pixSobelX.blue()))+ M_PI/2.0;
r = std::sqrt(std::pow(pixSobelY.blue(),2.0) + std::pow(pixSobelX.blue(),2.0));

tensor.setX(cos(2.0*theta));
tensor.setY(sin(2.0*theta));
tensor.setZ(sin(2.0*theta));
tensor.setW(-cos(2.0*theta));
tensor *= r;

if (!tensor.isNull()){
mData[mFieldSize.width() -1 -i][j] += tensor;
if (j<mFieldSize.height()-2 && j>0 && i<mFieldSize.width()-2 && i>0){
mData[mFieldSize.width() -i][j] += tensor;
mData[mFieldSize.width() -1 -i][j+1] += tensor;
mData[mFieldSize.width() -2 -i][j] += tensor;
mData[mFieldSize.width() -1 -i][j-1] += tensor;
mData[mFieldSize.width() -i][j+1] += tensor;
mData[mFieldSize.width() -2 -i][j+1] += tensor;
mData[mFieldSize.width() -2 -i][j-1] += tensor;
mData[mFieldSize.width() -i][j-1] += tensor;
}
}
}
}
mFieldIsFilled = true;
}

void TensorField::fillGridBasisField(float theta, float l)
{
for(int i=0; i<mFieldSize.height() ; i++)
Expand All @@ -89,6 +145,28 @@ void TensorField::fillGridBasisField(float theta, float l)
mFieldIsFilled = true;
}

void TensorField::fillGridBasisFieldLocal(float theta, float l, QPointF center, float decay)
{
float x;
float y;
for(int i=0; i<mFieldSize.height() ; i++)
{
for(int j=0; j<mFieldSize.width() ; j++)
{
QVector4D tensor;
x = ((float)j/(mFieldSize.height()-1) - center.x());
y = ((float)i/(mFieldSize.width()-1) - center.y());
tensor.setX(cos(2.0*theta));
tensor.setY(sin(2.0*theta));
tensor.setZ(sin(2.0*theta));
tensor.setW(-cos(2.0*theta));
tensor *= l;
mData[i][j] += (1.0-std::exp(-decay*(std::pow(x,2.0)+std::pow(y,2.0))))*tensor;
}
}
mFieldIsFilled = true;
}

void TensorField::fillRotatingField()
{
for(int i=0; i<mFieldSize.height() ; i++)
Expand All @@ -107,6 +185,28 @@ void TensorField::fillRotatingField()
mFieldIsFilled = true;
}

void TensorField::fillRotatingFieldLocal(QPointF center, float decay)
{
float x;
float y;
for(int i=0; i<mFieldSize.height() ; i++)
{
for(int j=0; j<mFieldSize.width() ; j++)
{
float theta = M_PI*j/(mFieldSize.width()-1) + i*M_PI/4/(mFieldSize.height()-1);
QVector4D tensor;
x = ((float)j/(mFieldSize.height()-1) - center.x());
y = ((float)i/(mFieldSize.width()-1) - center.y());
tensor.setX(cos(2.0*theta));
tensor.setY(sin(2.0*theta));
tensor.setZ(sin(2.0*theta));
tensor.setW(-cos(2.0*theta));
mData[i][j] += std::exp(-decay*(std::pow(x,2.0)+std::pow(y,2.0)))*tensor;
}
}
mFieldIsFilled = true;
}

void TensorField::fillGridBasisField(QVector2D direction)
{
float theta = std::atan2(direction.y(),direction.x());
Expand Down Expand Up @@ -162,6 +262,54 @@ void TensorField::fillHeightBasisField(QString filename)
mFieldIsFilled = true;
}

void TensorField::fillHeightBasisFieldLocal(QString filename, QPointF center, float decay)
{
QImage mHeightMap = QImage(filename);
float x;
float y;
if(mHeightMap.isNull())
{
qCritical()<<"fillHeightBasisField(): File "<<filename<<" not found";
return;
}
this->setFieldSize(mHeightMap.size());
QRgb currentPixel, nextPixelHoriz, nextPixelVert;
QVector2D grad;
float theta, r;
// Origin is top-left in the image
// Origin is bottom-left in the tensor matrix
// We have to swap the vertical axis
for(int i=0; i<mFieldSize.height()-1 ; i++)
{
for(int j=0; j<mFieldSize.width()-1 ; j++)
{
QVector4D tensor;
x = ((float)j/(mFieldSize.height()-1) - center.x());
y = ((float)i/(mFieldSize.width()-1) - center.y());
currentPixel = mHeightMap.pixel(j,i);
nextPixelHoriz = mHeightMap.pixel(j+1,i);
nextPixelVert = mHeightMap.pixel(j,i+1);
// If gradient is null, set tensor to default instead
// of degenerate

grad.setX(qBlue(currentPixel)-qBlue(nextPixelHoriz));
grad.setY(qBlue(currentPixel)-qBlue(nextPixelVert));
// Invert y
theta = std::atan2(-grad.y(), grad.x()) + M_PI/2.0;
r = std::sqrt(std::pow(grad.y(),2.0) + std::pow(grad.x(),2.0));

tensor.setX(cos(2.0*theta));
tensor.setY(sin(2.0*theta));
tensor.setZ(sin(2.0*theta));
tensor.setW(-cos(2.0*theta));
tensor *= r;

mData[mFieldSize.height()-1-i][j] += std::exp(-decay*(std::pow(x,2.0)+std::pow(y,2.0)))*tensor;
}
}
mFieldIsFilled = true;
}

void TensorField::fillHeightBasisFieldSobel(QString filename)
{
QImage mHeightMap = QImage(filename);
Expand Down Expand Up @@ -200,6 +348,49 @@ void TensorField::fillHeightBasisFieldSobel(QString filename)
mFieldIsFilled = true;
}

void TensorField::fillHeightBasisFieldSobelLocal(QString filename,QPointF center, float decay)
{
QImage mHeightMap = QImage(filename);
float x;
float y;
if(mHeightMap.isNull())
{
qCritical()<<"fillHeightBasisField(): File "<<filename<<" not found";
return;
}
this->setFieldSize(mHeightMap.size());
QImage mapSobelX, mapSobelY;
QColor pixSobelX, pixSobelY;
float theta, r;

mapSobelX = applySobelX(mHeightMap);
mapSobelY = applySobelY(mHeightMap);

for(int i=0; i<mFieldSize.width()-1 ; i++)
{
for(int j=0; j<mFieldSize.height()-1 ; j++)
{
QVector4D tensor;
x = ((float)j/(mFieldSize.height()-1) - center.x());
y = ((float)i/(mFieldSize.width()-1) - center.y());

pixSobelX = mapSobelX.pixel(j,i);
pixSobelY = mapSobelY.pixel(j,i);

theta = std::atan2(abs(pixSobelY.blue()),abs(pixSobelX.blue()))+ M_PI/2.0;
r = std::sqrt(std::pow(pixSobelY.blue(),2.0) + std::pow(pixSobelX.blue(),2.0));

tensor.setX(cos(2.0*theta));
tensor.setY(sin(2.0*theta));
tensor.setZ(sin(2.0*theta));
tensor.setW(-cos(2.0*theta));
tensor *= r;
mData[mFieldSize.width() -1 -i][j] += std::exp(-decay*(std::pow(x,2.0)+std::pow(y,2.0)))*tensor;
}
}
mFieldIsFilled = true;
}

void TensorField::fillRadialBasisField(QPointF center)
{
float x;
Expand All @@ -221,6 +412,27 @@ void TensorField::fillRadialBasisField(QPointF center)
mFieldIsFilled = true;
}

void TensorField::fillRadialBasisFieldLocal(QPointF center, float decay)
{
float x;
float y;
for(int i=0; i<mFieldSize.height() ; i++)
{
for(int j=0; j<mFieldSize.width() ; j++)
{
x = ((float)j/(mFieldSize.height()-1) - center.x());
y = ((float)i/(mFieldSize.width()-1) - center.y());
QVector4D tensor;
tensor.setX((std::pow(y,2.0)-std::pow(x,2.0)));
tensor.setY(-2*x*y);
tensor.setZ(-2*x*y);
tensor.setW(-(std::pow(y,2.0)-std::pow(x,2.0)));
mData[i][j] += std::exp(-decay*(std::pow(x,2.0)+std::pow(y,2.0)))*tensor;
}
}
mFieldIsFilled = true;
}

void TensorField::actionAddWatermap()
{
if(!mFieldIsFilled)
Expand All @@ -234,6 +446,7 @@ void TensorField::actionAddWatermap()
return;
}
this->applyWaterMap(filename);
this->applyBoundaries(filename);

this->computeTensorsEigenDecomposition();
this->exportEigenVectorsImage(true, true);
Expand All @@ -247,6 +460,14 @@ void TensorField::generateGridTensorField()
this->exportEigenVectorsImage(true, true);
}

void TensorField::generateGridTensorFieldLocal()
{
this->fillGridBasisFieldLocal(M_PI/3, 1, QPointF(0.2f,0.3f), 0.01f);

this->computeTensorsEigenDecomposition();
this->exportEigenVectorsImage(true, true);
}

void TensorField::generateHeightmapTensorField()
{
QString filename = QFileDialog::getOpenFileName(0, QString("Open Image"));
Expand All @@ -260,6 +481,19 @@ void TensorField::generateHeightmapTensorField()
this->exportEigenVectorsImage(true, true);
}

void TensorField::generateHeightmapTensorFieldLocal()
{
QString filename = QFileDialog::getOpenFileName(0, QString("Open Image"));
if(filename.isEmpty())
{
return;
}
this->fillHeightBasisFieldLocal(filename, QPointF(0.2f,0.3f), 0.0f);

this->computeTensorsEigenDecomposition();
this->exportEigenVectorsImage(true, true);
}

void TensorField::generateMultiRotationTensorField()
{
this->fillRotatingField();
Expand All @@ -268,6 +502,14 @@ void TensorField::generateMultiRotationTensorField()
this->exportEigenVectorsImage(true, true);
}

void TensorField::generateMultiRotationTensorFieldLocal()
{
this->fillRotatingFieldLocal(QPointF(0.7f,0.8f), 100.0f);

this->computeTensorsEigenDecomposition();
this->exportEigenVectorsImage(true, true);
}

void TensorField::generateRadialTensorField()
{
this->fillRadialBasisField(QPointF(0.5,0.5));
Expand All @@ -276,6 +518,27 @@ void TensorField::generateRadialTensorField()
this->exportEigenVectorsImage(true, true);
}

void TensorField::generateRadialTensorFieldLocal()
{
this->fillRadialBasisFieldLocal(QPointF(0.2,0.3), 100.0f);

this->computeTensorsEigenDecomposition();
this->exportEigenVectorsImage(true, true);
}

void TensorField::resetTensorField()
{
mData = QVector<QVector<QVector4D> >();
mData.resize(mFieldSize.height());
for(int i=0 ; i < mFieldSize.height() ; i++)
{
mData[i].resize(mFieldSize.width());
}
mFieldIsFilled = false;
mEigenIsComputed = false;
mWaterMapIsLoaded = false;
}

void TensorField::outputTensorField()
{
for(int i=0; i<mFieldSize.height() ; i++)
Expand Down
11 changes: 11 additions & 0 deletions TensorField.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,24 +47,30 @@ class TensorField : public QObject
// Changes the stored tensor field and put tensor to null
// in areas where there is water
void applyWaterMap(QString filename);
void applyBoundaries(QString filename);
// Generate a grid basis field from a 2D vector
// Don't normalize the vector as this function
// integrates the vector's norm in the tensor
void fillGridBasisField(QVector2D direction);
void fillGridBasisFieldLocal(float theta, float l, QPointF center, float decay);
// Generate a grid basis function from an angle and an amplitude.
// There is no direction, so theta and theta + pi give the same result
void fillGridBasisField(float theta, float l);
// Generate a heightmap basis function from a filename
void fillHeightBasisField(QString filename);
void fillHeightBasisFieldLocal(QString filename, QPointF center, float decay);
// Same thing, using a sobel filter to approximate the gradient
void fillHeightBasisFieldSobel(QString filename);
void fillHeightBasisFieldSobelLocal(QString filename,QPointF center, float decay);
// Generate a radial basis field
// The center coordinates must be in [0,1], considering that
// The bottom left corner of the image is (0,0) and bottom right
// corner is (1,0)
void fillRadialBasisField(QPointF center);
void fillRadialBasisFieldLocal(QPointF center, float decay);
// Test function to check the different angles
void fillRotatingField();
void fillRotatingFieldLocal(QPointF center, float decay);

// Output the tensor field to QDebug
void outputTensorField();
Expand Down Expand Up @@ -102,18 +108,23 @@ public slots:
void actionAddWatermap();
// Generates a grid tensor field with default parameters
void generateGridTensorField();
void generateGridTensorFieldLocal();
// Generates a tensor field from a heightmap
void generateHeightmapTensorField();
void generateHeightmapTensorFieldLocal();
// Generates a multi-rotation tensor field (linear variation)
void generateMultiRotationTensorField();
void generateMultiRotationTensorFieldLocal();
// Generates a radial tensor field with default parameters
void generateRadialTensorField();
void generateRadialTensorFieldLocal();
// Compute the eigen vectors and values of each tensor in the field,
// and store them internally.
// Return the number of degenerate points (null eigenvectors)
int computeTensorsEigenDecomposition();
// Tensor field smoothing using a Gaussian filter
void smoothTensorField();
void resetTensorField();

private:

Expand Down
Loading