0

I have implemented a programm that translate, rotate and scle a 2D texture in openGL. Thes 3 transformation work well on there own, but when I throw the translation with the others, things get wacky.

For the scaling, all seems well when the scale is above 1.0, but as soon as I get under, the texture get outta the screen. Same sad story for the rotation. No apparent problem when the angle is small, but above 20° or something, on both side, things get strange.

Here is my code . The interresting functions are at the end.

#include "glwidget.h"

#include <GL/glu.h>
#include <QMouseEvent>
#include <QKeyEvent>
#include <tgmath.h>
#include <QtDebug>
#ifdef WIN32
    #include <GL/glext.h>
       PFNGLACTIVETEXTUREPROC pGlActiveTexture = NULL;
    #define glActiveTexture pGlActiveTexture
#endif //WIN32

GlWidget::GlWidget(QWidget *parent) :
    QGLWidget(QGLFormat(), parent),
    scale(0.5),
    angle(0),
    translate{0,0,0},
    keyLock(false)
{
}

GlWidget::~GlWidget(){}

QSize GlWidget::sizeHint() const
{
    return QSize(640,480);
}

void GlWidget::resizeGL(int w, int h){

    h = (h<=0?1:h);

    pMatrix.setToIdentity();
    pMatrix.perspective(60.0,(float)w /(float)h,2,5);

    glViewport(0,0,w,h);
    glScissor(w*0.8,0,w,h);
    glDisable(GL_SCISSOR_TEST);
}

void GlWidget::initializeGL(){

    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

    #ifdef WIN32
        glActiveTexture = (PFNGLACTIVETEXTUREPROC) wglGetProcAddress((LPCSTR) "glActiveTexture");
    #endif

    glEnable(GL_DEPTH_TEST);
    glEnable(GL_CULL_FACE);

    qglClearColor(QColor(Qt::black));


    shaderProgram.addShaderFromSourceFile(QGLShader::Vertex, ":/vertexShader.vsh");
    shaderProgram.addShaderFromSourceFile(QGLShader::Fragment, ":/fragmentShader.fsh");
    shaderProgram.link();

    vertices << QVector3D(-1,-1,-2) << QVector3D(1,-1,-2) << QVector3D(-1,1,-2)
             << QVector3D(-1,1,-2) << QVector3D(1,-1,-2) << QVector3D(1,1,-2);

    textureCoordinates << QVector2D(0, 0) << QVector2D(1, 0) << QVector2D(0, 1)
                       << QVector2D(0, 1) << QVector2D(1, 0) << QVector2D(1, 1);

    texture = bindTexture(QPixmap(":/icone.png"));
    //mMatrix.setToIdentity();
    mMatrix.scale(scale,scale);


}

void GlWidget::paintGL(){

    qglClearColor(QColor(Qt::white));
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    QMatrix4x4 vMatrix;
    shaderProgram.bind();
    shaderProgram.setUniformValue("mvpMatrix", pMatrix*vMatrix*mMatrix);
    shaderProgram.setUniformValue("texture", 0);

    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, texture);
    glActiveTexture(0);

    shaderProgram.setAttributeArray("vertex", vertices.constData());
    shaderProgram.enableAttributeArray("vertex");
    shaderProgram.setAttributeArray("textureCoordinate", textureCoordinates.constData());
    shaderProgram.enableAttributeArray("textureCoordinate");

    glEnable(GL_BLEND);

    glDrawArrays(GL_TRIANGLES,0,vertices.size());

    glDisable(GL_BLEND);

    shaderProgram.disableAttributeArray("vertex");
    shaderProgram.disableAttributeArray("textureCoordinate");
    shaderProgram.release();

    qglClearColor(QColor(Qt::black));
    glEnable(GL_SCISSOR_TEST);
    glClear(GL_COLOR_BUFFER_BIT);
    glDisable(GL_SCISSOR_TEST);

}

void GlWidget::mousePressEvent(QMouseEvent *event){
    if(event->button()==Qt::LeftButton){
        lastPosF = QVector3D(event->pos().x(),event->pos().y(),0);
        lastPos = getModelCoordinates(event->pos());
        //keyLock=true;
    }
    event->accept();
    //update();
}

void GlWidget::mouseMoveEvent(QMouseEvent *event){
    if(!event->buttons().testFlag(Qt::LeftButton)) return;
    currentPosF = QVector3D(event->pos().x(),event->pos().y(),0);
    QVector3D currentPos(getModelCoordinates(event->pos()));

    //qDebug()<<currentPos;

    if(keysPressed.contains(Qt::Key_Control) && !keysPressed.contains(Qt::Key_Shift)){
        resizeTexture(currentPos);
    }
    else if(!keysPressed.contains(Qt::Key_Control) && keysPressed.contains(Qt::Key_Shift)){
        rotateTexture(currentPos);
    }
    else if(!keysPressed.contains(Qt::Key_Control) && !keysPressed.contains(Qt::Key_Shift)){
        dragTexture(currentPos);
    }

    mMatrix.setToIdentity();
    mMatrix.translate(translate);
    mMatrix.scale(scale,scale);
    mMatrix.rotate(angle,0,0,1);

    //lastPos = currentPos;
    update();

}

void GlWidget::mouseReleaseEvent(QMouseEvent *event)
{
    if(event->button()==Qt::LeftButton)
        keyLock=false;

}

void GlWidget::keyPressEvent(QKeyEvent *event){
    if(!keyLock) keysPressed.append(event->key());

}

void GlWidget::keyReleaseEvent(QKeyEvent *event){
    if(!keyLock) keysPressed.remove(keysPressed.lastIndexOf(event->key()));
}

QVector3D GlWidget::getModelCoordinates(QPoint mousePos){
    QVector3D modelPos;

    double X,Y,Z;
    GLdouble modelMat[16],projMat[16];
    GLint viewport[4]={0,0,this->width(),this->height()};
    //glGetDoublev(GL_MODELVIEW_MATRIX ,modelMat);
    //glGetDoublev(GL_PROJECTION_MATRIX,projMat);
    //glGetIntegerv(GL_VIEWPORT,viewport);
    for(int i=0; i<4;i++){
        modelMat[i*4]=(double)mMatrix.column(i).x();
        modelMat[1+i*4]=(double)mMatrix.column(i).y();
        modelMat[2+i*4]=(double)mMatrix.column(i).z();
        modelMat[3+i*4]=(double)mMatrix.column(i).w();
    }
    for(int i=0; i<4;i++){
        projMat[i*4]=(double)pMatrix.column(i).x();
        projMat[1+i*4]=(double)pMatrix.column(i).y();
        projMat[2+i*4]=(double)pMatrix.column(i).z();
        projMat[3+i*4]=(double)pMatrix.column(i).w();
    }
    //GLdouble z;

    gluUnProject((double)mousePos.x(),(double)viewport[3]-(double)mousePos.y(),0,modelMat,projMat,viewport,&X,&Y,&Z);
    modelPos.setX(fabs(X)<1e-8?1e-8:static_cast<float>(X));
    modelPos.setY(static_cast<float>(Y));
    modelPos.setZ(static_cast<float>(Z));

    qDebug();
    qDebug()<<"first :"<<lastPos<<" - Now :"<<modelPos;


    return modelPos;
}

void GlWidget::resizeTexture(QVector3D currentPos)
{
    float diff = sqrt(pow(currentPos.x(),2)+pow(currentPos.y(),2)) / sqrt(pow(lastPos.x(),2)+pow(lastPos.y(),2));
    //diff=diff>1?1:diff;
    //diff /= sqrt(pow(lastPos.x(),2)+pow(lastPos.y(),2));
    scale*= diff;
    scale=scale<0?-scale:scale;
    qDebug()<<"diff : "<<diff<<"  - scale : "<<scale;
    /*mMatrix.setToIdentity();
    mMatrix.scale(scale,scale);
    mMatrix.rotate(angle,0,0,1);*/
}

void GlWidget::rotateTexture(QVector3D currentPos)
{
    float angle1 = atan((float)currentPos.y()/(float)currentPos.x());
    float angle2 = atan((float)lastPos.y()/(float)lastPos.x());
    float delta =angle1-angle2*(angle1*angle2>=0?1:-1);
    delta*=180/M_PI;
    angle+=delta;
    while(angle>360|| angle < 0){
        angle = (angle>360?angle-360:angle);
        angle = (angle<0?angle+360:angle);
    }
    qDebug()<<"angle 1 : "<<atan((float)lastPos.y()/(float)lastPos.x())
           << "angle 2 : "<<atan((float)currentPos.y()/(float)currentPos.x());
    qDebug()<<"delta : "<<delta<<" - Angle : "<<angle;
    //qDebug()<<angle;
    /*mMatrix.setToIdentity();
    mMatrix.scale(scale,scale);
    mMatrix.rotate(angle,0,0,1);*/
    //lastPos = currentPos;
}

void GlWidget::dragTexture(QVector3D currentPos)
{
    if(lastPos.x() > 1.0 || lastPos.x() < -1.0 || lastPos.y() > 1.0 || lastPos.y() < -1.0)
        return;
    translate += (currentPos-lastPos);
    /*mMatrix.setToIdentity();
    mMatrix.translate(translate);*/
    //translate += currentPosF-lastPosF;
    lastPosF=currentPosF;
    qDebug()<<translate;
}

Here are my vertex and fragment shaders :

#version 330

uniform mat4 mvpMatrix;

in vec4 vertex;
in vec2 textureCoordinate;

out vec2 varyingTextureCoordinate;

void main(void)
{
    varyingTextureCoordinate = textureCoordinate;
    gl_Position = mvpMatrix * vertex;
}


#version 330

uniform sampler2D texture;

in vec2 varyingTextureCoordinate;

out vec4 fragColor;

void main(void)
{
    fragColor = texture2D(texture, varyingTextureCoordinate);
}

Thank for your answers.

Laetan
  • 879
  • 9
  • 26
  • I think you'll find that order matters. I typically rotate->translate->scale. – jwlaughton Jan 18 '15 at 01:47
  • When I translate after another transformation, the translation is not hindered by this transformation, but this transformation is not done around the center of the model but the original 0,0,0. – Laetan Jan 18 '15 at 09:51
  • I'm not sure I understand your comment, but if you rotate->translate, the object will be rotated about it's own axis then translated. If you translate->rotate, the object will first be translated then rotated about the origin. – jwlaughton Jan 18 '15 at 09:55
  • I am saying this after experimentation : when I rotate->translate (in the code), the translation works as expected, but the rotation is done around the original center of the texture, not the one after translation. When I do it the other way, the rotation works as expected, but the translation become crazy when I rotate the image more than a certain angle. – Laetan Jan 18 '15 at 10:42
  • Sorry if I'm off base; I'm coming at this from experience in different languages, but OpenGL is OpenGL. It looks from your statement mMatrix.rotate(angle,0,0,1); that the rotation you're defining is a quaternion (angle and a vector about which to rotate, in this case about the z axis). Quaternion angles are generally specified as radians, not degrees. Could this be a matter of degrees verses radians? – jwlaughton Jan 18 '15 at 11:17
  • I asked myself the same question, and according to some documentation I found, it is in degree. So I made the conversion. Futhermore, the problem I have exclusivly comes from the interaction between the translation and the other 2 transformation. Each ones works on their own. – Laetan Jan 18 '15 at 11:22
  • Degrees vs Radians aside, order matters and you need to think about what's happening. If you rotate 90 degrees then translate (1.0, 0.0, 0.0) the object will first be rotated about it's own axis by 90 degrees then translated to (1.0, 0.0, 0.0). If you translate (1.0, 0.0, 0.0) then rotate 90 degrees, the object will first be translated to (1.0, 0.0, 0.0) then rotated about the origin to end up at (0.0, 0.0, 1.0). Two completely different results. – jwlaughton Jan 18 '15 at 11:53
  • Tes, I know about that, and my code is written so that the transformation are done in the correct order. The problem I have is that every translation I do after rotating or scaling above a certain amount goes wrong. I suspect a problem around the transformation of physical coordinates to model coordinate, but I do not know how to correct it. – Laetan Jan 18 '15 at 12:14
  • Scaling should be done last. – jwlaughton Jan 18 '15 at 12:20
  • The problem does not come from the order. I've tried. It is a bug on translation after one of the others transformation. – Laetan Jan 18 '15 at 14:07
  • Can you print the matrices you're getting for each process to see what they are? Maybe the solution is for you to do the matrix math yourself and simply pass a single resultant matrix. That's what I do. – jwlaughton Jan 18 '15 at 14:28
  • Basically, what you're trying to come up with are Model, View and Projection matrices. The View and Projection Matrices are pretty straight forward. It's the Model matrix you're having trouble with. – jwlaughton Jan 18 '15 at 14:36
  • Which reminds me. Have you set glMatrixMode before loading the matrices? – jwlaughton Jan 18 '15 at 14:39
  • Yeah i can and i think it made me understand what actually happen. When the translation vector is calculated, it is done in model axis after rotation and scaling, but the actual translation is done in the original axis. So when I rotate it all the way, the translation goes in the opposite direction. To fix that, I should either recalculate the translation vector so it includes the scaling and rotation, or find why the translation is not done on the correct axis. Any idea? – Laetan Jan 18 '15 at 14:43
  • I've seen other tutorial use this glMatrixMode, but the one I read did not, because it fed the shader a projection, view and model matrix it instanciate and modify itself. – Laetan Jan 18 '15 at 14:50
  • Do you have your own shaders? – jwlaughton Jan 18 '15 at 14:59
  • yes, i'll write them in my first post – Laetan Jan 18 '15 at 15:16
  • OK IC. You don't need to worry about glMatrixMode. I still think it's a good idea to look at each matrix to see what they look like. The translation and scale matrices are pretty easy. I'll post an answer for what your rotation matrix should be for a 15 degree rotation about the z axis. – jwlaughton Jan 18 '15 at 15:42

1 Answers1

0

For a 15 degree rotation about the z axis, your rotation matrix should be:

0.965926, -0.258819, 0.0, 0.0
0.258819, 0.965926, 0.0, 0.0
0.0, 0.0, 1.0, 0.0
0.0, 0.0, 0.0, 1.0

These matrices are all 4 X 4 matrices. The translation matrix looks like:

1.0, 0.0, 0.0, transX
0.0, 1.0, 0.0, transY
0.0, 0.0, 1.0, transZ
0.0, 0.0, 0.0, 1.0

The scale matrix looks like:

scaleX, 0.0, 0.0, 0.0
0.0, scaleY, 0.0, 0.0
0.0, 0.0, scaleZ, 0.0
0.0, 0.0, 0.0, 1.0

Multiply these matrices in the order that gives you the effect you want, but remember that order matters.

jwlaughton
  • 905
  • 1
  • 6
  • 11
  • The way i see it, for a translation, I have to calculate the translation vector, rotate it with a 2*2 matrix, which look like the one you gave me, add it to the total translation vector, and that should correct the problem with rotation+translation. Imma see how it goes. – Laetan Jan 18 '15 at 16:09
  • Gowd! It works! Next is the resizing, which still bugs. – Laetan Jan 18 '15 at 16:43
  • Okay, I just needed to multiplie the translation vector by the scale factor. Easy after the rotation! – Laetan Jan 18 '15 at 16:52