1
votes

I'm attempting to rotate a cube around an axis and it's definitely behaving incorrectly. I'm assuming the problem lies in my matrix rotation code as everything else seems to be working. I can translate the model correctly along the x, y or z axis, as well as scale. My camera view matrix is working as expected as well and so is my projection matrix. If I remove the view matrix and or the projection matrix implementations the problem remains.

If you wish to see what result I'm getting, it's the exact same output as the gif shown on this stackoverflow post: Rotating a cube in modern opengl... looks strange The cube appears to fold in on itself while rotating, then returns to normal after a full rotation and seems to rotate fine for about 20 degrees until folding in on itself again and repeating. My issue is the same as that in the linked to article, however my matrix class is not the same, so my problem, though the same, seemingly has a different solution.

Here's my stripped matrix declaration with possibly relevant operators

math.h

typedef struct matrix4x4
{
    //Elements stored in ROW MAJOR ORDER
    GLfloat matrix[16];

    void translate(Vector3f translation);
    void rotateX(GLfloat angle);
    void rotateY(GLfloat angle);
    void rotateZ(GLfloat angle);
    void rotate(Vector3f angles);
    void scale(Vector3f scales);
    void scale(GLfloat scale);

    inline matrix4x4& operator*=(const matrix4x4& rhs)
    {
        this->matrix[0] = this->matrix[0] * rhs.matrix[0] + this->matrix[1] * rhs.matrix[4] + this->matrix[2] * rhs.matrix[8] + this->matrix[3] * rhs.matrix[12];
        this->matrix[1] = this->matrix[0] * rhs.matrix[1] + this->matrix[1] * rhs.matrix[5] + this->matrix[2] * rhs.matrix[9] + this->matrix[3] * rhs.matrix[13];
        this->matrix[2] = this->matrix[0] * rhs.matrix[2] + this->matrix[1] * rhs.matrix[6] + this->matrix[2] * rhs.matrix[10] + this->matrix[3] * rhs.matrix[14];
        this->matrix[3] = this->matrix[0] * rhs.matrix[3] + this->matrix[1] * rhs.matrix[7] + this->matrix[2] * rhs.matrix[11] + this->matrix[3] * rhs.matrix[15];

        this->matrix[4] = this->matrix[4] * rhs.matrix[0] + this->matrix[5] * rhs.matrix[4] + this->matrix[6] * rhs.matrix[8] + this->matrix[7] * rhs.matrix[12];
        this->matrix[5] = this->matrix[4] * rhs.matrix[1] + this->matrix[5] * rhs.matrix[5] + this->matrix[6] * rhs.matrix[9] + this->matrix[7] * rhs.matrix[13];
        this->matrix[6] = this->matrix[4] * rhs.matrix[2] + this->matrix[5] * rhs.matrix[6] + this->matrix[6] * rhs.matrix[10] + this->matrix[7] * rhs.matrix[14];
        this->matrix[7] = this->matrix[4] * rhs.matrix[3] + this->matrix[5] * rhs.matrix[7] + this->matrix[6] * rhs.matrix[11] + this->matrix[7] * rhs.matrix[15];

        this->matrix[8] = this->matrix[8] * rhs.matrix[0] + this->matrix[9] * rhs.matrix[4] + this->matrix[10] * rhs.matrix[8] + this->matrix[11] * rhs.matrix[12];
        this->matrix[9] = this->matrix[8] * rhs.matrix[1] + this->matrix[9] * rhs.matrix[5] + this->matrix[10] * rhs.matrix[9] + this->matrix[11] * rhs.matrix[13];
        this->matrix[10] = this->matrix[8] * rhs.matrix[2] + this->matrix[9] * rhs.matrix[6] + this->matrix[10] * rhs.matrix[10] + this->matrix[11] * rhs.matrix[14];
        this->matrix[11] = this->matrix[8] * rhs.matrix[3] + this->matrix[9] * rhs.matrix[7] + this->matrix[10] * rhs.matrix[11] + this->matrix[11] * rhs.matrix[15];

        this->matrix[12] = this->matrix[12] * rhs.matrix[0] + this->matrix[13] * rhs.matrix[4] + this->matrix[14] * rhs.matrix[8] + this->matrix[15] * rhs.matrix[12];
        this->matrix[13] = this->matrix[12] * rhs.matrix[1] + this->matrix[13] * rhs.matrix[5] + this->matrix[14] * rhs.matrix[9] + this->matrix[15] * rhs.matrix[13];
        this->matrix[14] = this->matrix[12] * rhs.matrix[2] + this->matrix[13] * rhs.matrix[6] + this->matrix[14] * rhs.matrix[10] + this->matrix[15] * rhs.matrix[14];
        this->matrix[15] = this->matrix[12] * rhs.matrix[3] + this->matrix[13] * rhs.matrix[7] + this->matrix[14] * rhs.matrix[11] + this->matrix[15] * rhs.matrix[15];
        return *this;
    }

}matrix4x4;

matrix4x4 createTransformationMatrix(Vector3f translation, Vector3f rotation, Vector3f scale);
matrix4x4 createPerspectiveProjectionMatrix(GLfloat width, GLfloat height, GLfloat fov, GLfloat nearPlane, GLfloat farPlane);
matrix4x4 createViewMatrix(Vector3f cameraPosition, GLfloat cameraPitch, GLfloat cameraYaw, GLfloat cameraRoll);

and it's relevant implementations math.cpp

matrix4x4::matrix4x4(GLfloat elements[])
{
    //Elements stored in ROW MAJOR ORDER
    for (unsigned int i = 0; i <= elementCount; i++)
    {
        matrix[i] = elements[i];
    }
}

void matrix4x4::setIdentity()
{
    std::fill(matrix, matrix + sizeof(matrix) / sizeof(GLfloat), 0.0f);
    matrix[0] = 1;
    matrix[5] = 1;
    matrix[10] = 1;
    matrix[15] = 1;
}

/*/////////////////////////////////////////////////////
    math
/////////////////////////////////////////////////////*/

void matrix4x4::translate(Vector3f translation)
{
    GLfloat transformElements[16] =
    {
        1.0f, 0.0f, 0.0f, translation.x,
        0.0f, 1.0f, 0.0f, translation.y,
        0.0f, 0.0f, 1.0f, translation.z,
        0.0f, 0.0f, 0.0f, 1.0f
    };
    matrix4x4 transform = matrix4x4(transformElements);

    *this *= transform;
}

void matrix4x4::rotateX(GLfloat angle)
{
    angle = degreesToRadians(angle);

    GLfloat transformElements[16] =
    {
        1.0f,       0.0f,               0.0f,           0.0f,
        0.0f,   std::cos(-angle),   -std::sin(-angle),  0.0f,
        0.0f,   std::sin(-angle),   std::cos(-angle),   0.0f,
        0.0f,       0.0f,               0.0f,           1.0f
    };
    matrix4x4 transform = matrix4x4(transformElements);

    *this *= transform;
}

void matrix4x4::rotateY(GLfloat angle)
{
    angle = degreesToRadians(angle);

    GLfloat transformElements[16] =
    {
        std::cos(-angle),   0.0f,   std::sin(-angle),   0.0f,
        0.0f,               1.0f,       0.0f,           0.0f,
        -std::sin(-angle),  0.0f,   std::cos(-angle),   0.0f,
        0.0f,               0.0f,       0.0f,           1.0f
    };
    matrix4x4 transform = matrix4x4(transformElements);

    *this *= transform;
}

void matrix4x4::rotateZ(GLfloat angle)
{
    angle = degreesToRadians(angle);

    GLfloat transformElements[16] =
    {
        std::cos(-angle),   -std::sin(-angle),  0.0f,   0.0f,
        std::sin(-angle),   std::cos(-angle),   0.0f,   0.0f,
        0.0f,                   0.0f,           1.0f,   0.0f,
        0.0f,                   0.0f,           0.0f,   1.0f
    };
    matrix4x4 transform = matrix4x4(transformElements);

    *this *= transform;
}

void matrix4x4::rotate(Vector3f angles)
{
    matrix4x4 transform = matrix4x4();
    transform.setIdentity();

    transform.rotateX(angles.x);
    transform.rotateY(angles.y);
    transform.rotateZ(angles.z);

    *this *= transform;
}

void matrix4x4::scale(Vector3f scales)
{
    GLfloat transformElements[16] =
    {
        scales.x,   0.0f,       0.0f,       0.0f,
        0.0f,       scales.y,   0.0f,       0.0f,
        0.0f,       0.0f,       scales.z,   0.0f,
        0.0f,       0.0f,       0.0f,       1.0f
    };
    matrix4x4 transform = matrix4x4(transformElements);

    *this *= transform;
}

matrix4x4 createTransformationMatrix(Vector3f translation, Vector3f rotation, Vector3f scale)
{
    matrix4x4 transformationMatrix;
    transformationMatrix.setIdentity();

    //I've tried changing the order of these around, as well as only
    //doing one operation (skipping translate and scale, or everything but a single axis rotation
    transformationMatrix.translate(translation);
    transformationMatrix.rotate(rotation);
    transformationMatrix.scale(scale);

    return transformationMatrix;
}

matrix4x4 createPerspectiveProjectionMatrix(GLfloat width, GLfloat height, GLfloat fov, GLfloat nearPlane, GLfloat farPlane)
{
    matrix4x4 projectionMatrix;
    projectionMatrix.setIdentity();

    GLfloat aspectRatio = width / height;

    projectionMatrix.matrix[0] = (1.0f / std::tan((degreesToRadians(fov)) / 2.0f) / aspectRatio);
    projectionMatrix.matrix[5] = 1.0f / std::tan((degreesToRadians(fov)) / 2.0f);
    projectionMatrix.matrix[10] = (farPlane + nearPlane) / (nearPlane - farPlane);
    projectionMatrix.matrix[11] = (2.0f * farPlane * nearPlane) / (nearPlane - farPlane);
    projectionMatrix.matrix[14] = -1.0f;

    return projectionMatrix;
}

I know my matrix/vector implementations are quick and dirty, but I'm just trying to get something set up. I've got plans to make the math methods (scale, translate, etc) static methods that don't affect the contents of the matrix, but instead accept a matrix as input and return a new one... but that's not the issue right now.

Here's my vertex shader

#version 330 core

//declare inputs
in vec3 position;
in vec2 textureCoords;

//declare output
out vec2 pass_textureCoords;

//uniforms
uniform mat4 transformationMatrix;
uniform mat4 projectionMatrix;
uniform mat4 viewMatrix;

void main(void)
{
    //tell OpenGL where to render the vertex on screen
    gl_Position = projectionMatrix * viewMatrix * transformationMatrix * vec4(position.x, position.y, position.z, 1.0);

    pass_textureCoords = textureCoords;
}

My render method...

void Renderer::render(Entity entity, Shader* shader)
{
    ...

    RawModel* rawModel = texturedModel->getRawModel();

    glBindVertexArray(rawModel->getVaoID());
    ...

    matrix4x4 transformationMatrix = createTransformationMatrix(entity.getPosition(), entity.getRotation(), entity.getScale());
    shader->loadTransformationMatrix(transformationMatrix);

    ...

    glDrawElements(GL_TRIANGLES, rawModel->getVertexCount(), GL_UNSIGNED_INT, 0);
    ...
}

And finally the relevant pieces from my main. The cube definitions and so on

//This is a simple cube
    std::vector<GLfloat> vertices = 
    {
        -0.5f,0.5f,-0.5f,
        -0.5f,-0.5f,-0.5f,
        0.5f,-0.5f,-0.5f,
        0.5f,0.5f,-0.5f,

        -0.5f,0.5f,0.5f,
        -0.5f,-0.5f,0.5f,
        0.5f,-0.5f,0.5f,
        0.5f,0.5f,0.5f,

        0.5f,0.5f,-0.5f,
        0.5f,-0.5f,-0.5f,
        0.5f,-0.5f,0.5f,
        0.5f,0.5f,0.5f,

        -0.5f,0.5f,-0.5f,
        -0.5f,-0.5f,-0.5f,
        -0.5f,-0.5f,0.5f,
        -0.5f,0.5f,0.5f,

        -0.5f,0.5f,0.5f,
        -0.5f,0.5f,-0.5f,
        0.5f,0.5f,-0.5f,
        0.5f,0.5f,0.5f,

        -0.5f,-0.5f,0.5f,
        -0.5f,-0.5f,-0.5f,
        0.5f,-0.5f,-0.5f,
        0.5f,-0.5f,0.5f
    };
    std::vector<GLfloat> textureCoords =
    {
        ...
    };
    std::vector<GLuint> indices = 
    {
        0,1,3,
        3,1,2,
        4,5,7,
        7,5,6,
        8,9,11,
        11,9,10,
        12,13,15,
        15,13,14,
        16,17,19,
        19,17,18,
        20,21,23,
        23,21,22
    };

    //parameters are (model, pos, rotation, scale)
    Entity entity = Entity(&texturedModel, Vector3f(0.0f, 0.0f, -2.0f), Vector3f(0.0f, 0.0f, 0.0f), 1.0f);

    //SHADER STUFF
    Shader textureShader = Shader("uniformVarTextureShader");
    textureShader.loadProjectionMatrix(display.getProjectionMatrix());

    Camera cam;

    //draw in wireframe mode
    glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
    //glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);

    while (display.checkForClose() == 0)
    {
        glfwPollEvents();

        //TO DO: update logic here
        //entity.varyPosition(+0.005f, 0.0f, -0.002f); //this works, as does scaling and camera movement
        //entity.varyRotation(0.25f, 0.18f, 0.0f);
        entity.setYRotation(entity.getYRotation() + 0.25f); //any sort of rotation operation ends up with the strange behaivor

        //rendering commands here
        display.prepare();

        textureShader.bind();
        textureShader.loadViewMatrix(cam);
        display.render(entity, &textureShader);
        textureShader.stop();

        display.swapBuffers();
    }

So, to recap; I'm not having any issues with translating, scaling, "camera movement" and the projection matrix appears to work as well. Any time I attempt to rotate however, I get the exact same behavior as the linked to article above.

Final notes: I have depth testing enabled and clear the depth buffer each frame. I also pass GL_TRUE to transpose any matrix data I give to glUniformMatrix4fv. I've checked the locations of each of the uniforms and they are passing correctly; 0, 1 and 2 respectively. No -1.

I'm stumped, any help would be appreciated. I can post more code if need be, but I'm pretty sure this covers the entirety of where the problem most likely lies. Thanks again

1
Edit that down to a minimal reproducible example. Recommend using a known-good library like GLM for matrix-slinging. - genpfault
Edited down, I feel I should leave in the projection matrix construction in case the problem has something to do with that, as well as my translate and scale methods to show that what I'm trying to do there does indeed work - ErnieB
I don't want to use GLM or another library simply because I want to be able to port this code across many platforms, including some which use their own graphics systems. GLM can't be used in those circumstances without porting all of OpenGL. A "roll your own" matrix handling class is preferred by me to prevent using several vastly different code bases for the same project across platforms. I've been able to avoid most external libraries so far and wish to keep the number to an absolute minimum - ErnieB

1 Answers

2
votes

The major issue is the matrix multipolication operation. Since you manipulate the matrix (you read from the matrix and you write to it), are some elements already manipulated, before you read it.

e.g. In the first line this->matrix[0] is written to

this->matrix[0] = this->matrix[0] * rhs.matrix[0] + this->matrix[1] * rhs.matrix[4] + this->matrix[2] * rhs.matrix[8] + this->matrix[3] * rhs.matrix[12];

and in the second line this->matrix[0] is read again:

this->matrix[1] = this->matrix[0] * rhs.matrix[1] + this->matrix[1] * rhs.matrix[5] + this->matrix[2] * rhs.matrix[9] + this->matrix[3] * rhs.matrix[13];

Copy the matrix array to a local variable, to solve the issue:

matrix4x4& operator*=(const matrix4x4& rhs)
{
    matrix4x4 act( this->matrix );

    this->matrix[0] = act.matrix[0] * rhs.matrix[0] + act.matrix[1] * rhs.matrix[4] + act.matrix[2] * rhs.matrix[8] + act.matrix[3] * rhs.matrix[12];
    this->matrix[1] = act.matrix[0] * rhs.matrix[1] + act.matrix[1] * rhs.matrix[5] + act.matrix[2] * rhs.matrix[9] + act.matrix[3] * rhs.matrix[13];

    ....

    return *this;
}



By the way, since you multiply a vector to the matrix from the right, in the shader

gl_Position = projectionMatrix * viewMatrix * transformationMatrix * vec4(position.x, position.y, position.z, 1.0);

the matrix has to be initilized in column major order:

mat4 m44 = mat4(
    vec4( Xx, Xy, Xz, 0.0),
    vec4( Yx, Xy, Yz, 0.0),
    vec4( Zx  Zy  Zz, 0.0),
    vec4( Tx, Ty, Tz, 1.0) );

Note your matrices are initialized in row major order e.g. matrix4x4::translate:

GLfloat transformElements[16] =
{
    1.0f, 0.0f, 0.0f, translation.x,
    0.0f, 1.0f, 0.0f, translation.y,
    0.0f, 0.0f, 1.0f, translation.z,
    0.0f, 0.0f, 0.0f, 1.0f
};

So you have to transpose the matrix when you set it to the uniform glUniformMatrix4fv:

glUniformMatrix4fv( ..., ..., GL_TRUE, ... );