0
votes

I am playing around with OpenGL and one thing I decided to do is create my own Matrix class, instead of using glm's matrices. The Matrix class has methods for translating, rotating and scaling the object, which are written below:

Matrix4 Matrix4::translate(Matrix4& matrix, Vector3& translation)
{
     Vector4 result(translation, 1.0f);

     result.multiply(matrix);

     matrix.mElements[3 * 4 + 0] = result.x;
     matrix.mElements[3 * 4 + 1] = result.y;
     matrix.mElements[3 * 4 + 2] = result.z;

     return matrix; 
}

Matrix4 Matrix4::rotate(Matrix4& matrix, float angle, Vector3& axis)
{
    if (axis.x == 0 && axis.y == 0 && axis.z == 0)
        return matrix;

    float r = angle;
    float s = sin(r);
    float c = cos(r);
    float omc = 1.0f - cos(r);

    float x = axis.x;
    float y = axis.y;
    float z = axis.z;

    matrix.mElements[0 + 0 * 4] = c + x * x * omc;
    matrix.mElements[1 + 0 * 4] = x * y * omc - z * s;
    matrix.mElements[2 + 0 * 4] = z * x * omc + y * s;

    matrix.mElements[0 + 1 * 4] = x * y * omc + z * s;
    matrix.mElements[1 + 1 * 4] = c + y * y * omc;
    matrix.mElements[2 + 1 * 4] = z * y * omc - x * s;

    matrix.mElements[0 + 2 * 4] = x * z * omc - y * s;
    matrix.mElements[1 + 2 * 4] = y * z * omc + x * s;
    matrix.mElements[2 + 2 * 4] = c + z * z * omc;

    return matrix;
}

Matrix4 Matrix4::scale(Matrix4& matrix, Vector3& scaler)
{

    matrix.mElements[0 + 0 * 4] *= scaler.x;
    matrix.mElements[1 + 0 * 4] *= scaler.x;
    matrix.mElements[2 + 0 * 4] *= scaler.x;

    matrix.mElements[0 + 1 * 4] *= scaler.y;
    matrix.mElements[1 + 1 * 4] *= scaler.y;
    matrix.mElements[2 + 1 * 4] *= scaler.y;

    matrix.mElements[0 + 2 * 4] *= scaler.z;
    matrix.mElements[1 + 2 * 4] *= scaler.z;
    matrix.mElements[2 + 2 * 4] *= scaler.z;

    matrix.mElements[3 + 3 * 4] = 1;

    return matrix;
}

When I call the translate, rotate and scale methods in while loop (in this particular order), it does what I want, which is translate the object, then rotate it around its local origin and scale it. However, when I want to switch order so I call rotation first and then translation, I want it to do this:

Intended way of rotating then translating

But my code dosen't do that. Instead, its doing this:

What my code does

What can I do so that my object only rotates around the center of the screen and not around it's local origin aswell? My only guess is that I am doing something wrong with adding the rotation calculation on transformed matrix, but I still can't tell what it is.

EDIT: One thing i need to point out is if i left out the rotation method and i only tackle with translation and scaling, they do what i expect them to do in translation first, rotation second and in rotation first, translation second order.

EDIT 2: Here is how i call these functions in while loop.

Matrix4 trans = Matrix4(1.0f);
trans = Matrix4::rotate(trans, (float)glfwGetTime(), Vector3(0.0f, 0.0f, 1.0f));
trans = Matrix4::translate(trans, Vector3(0.5f, -0.5f, 0.0f));
trans = Matrix4::scale(trans, Vector3(0.5f, 0.5f, 1.0f));

shader.setUniformMatrix4f("uTransform", trans);
2
Have you tried to write the most primitive operations as matrices down on paper and multiply them by hand to get the resulting matrix? Easy to make mistakes if you type it in directly. Btw, you use 3D coordinates ("z-axis") but you have only 3x3 matrices - why not homogeneous coordinates? How do you do stuff without those?Aziuth
I don't think your matrix multiplications are correct. In general, rotation and scaling also modifies the elements where translations are stored.BDL
@Aziuth, yes i have tried out simple multiplication of two matricies and i know my matrix multiplication method is right.Alexander
@BDL, i made sure all of the translation, rotation and scaling calculation is stored in one matrix.Alexander
Add the main code, where these two functions are called.Germán

2 Answers

1
votes

You have to concatenate the matrices by a matrix multiplication.

A matrix multiplication C = A * B works like this:

Matrix4x4 A, B, C;

// C = A * B
for ( int k = 0; k < 4; ++ k )
    for ( int j = 0; j < 4; ++ j )
        C[k][j] = A[0][j] * B[k][0] + A[1][j] * B[k][1] + A[2][j] * B[k][2] +  A[3][j] * B[k][3];

I recommend to create specify the matrix class somehow like this:

#include <array>

class Matrix4
{
public:

    std::array<float, 16> mElements{
      1, 0, 0, 0, 
      0, 1, 0, 0,
      0, 0, 1, 0,
      0, 0, 0, 1 };

    const float * dataPtr( void ) const { return mElements.data(); }

    Matrix4 & multiply( const Matrix4 &mat );
    Matrix4 & translate( const Vector3 &translation );
    Matrix4 & scale( const Vector3 &scaler );
    Matrix4 & rotate( float angle, const Vector3 &axis );
};

Implement the matrix multiplication. Note, you have to store the result in a buffer. If you would write the result back to the matrix member directly, then you would change elements, which will read again later in the nested loop and the result wouldn't be correct:

Matrix4& Matrix4::multiply( const Matrix4 &mat )
{
    // multiply the existing matrix by the new and store the result in a buffer
    const float           *A = dataPtr();
    const float           *B = mat.dataPtr();
    std::array<float, 16>  C;

    for ( int k = 0; k < 4; ++ k ) {
        for ( int j = 0; j < 4; ++ j ) {
            C[k*4+j] =
                A[0*4+j] * B[k*4+0] +
                A[1*4+j] * B[k*4+1] +
                A[2*4+j] * B[k*4+2] +
                A[3*4+j] * B[k*4+3];
        }
    }

    // copy the buffer to the attribute
    mElements = C;

    return *this;
}

Adapt the methods for translation, rotation and scaling like this:

Matrix4 & Matrix4::translate( const Vector3 &translation )
{
     float x = translation.x;
     float y = translation.y;
     float z = translation.z;

     Matrix4 transMat;
     transMat.mElements = {
          1.0f, 0.0f, 0.0f, 0.0f,
          0.0f, 1.0f, 0.0f, 0.0f,
          0.0f, 0.0f, 1.0f, 0.0f,
          x,    y,    z,    1.0f };

     return multiply(transMat);
}

Matrix4 & Matrix4::rotate( float angle, const Vector3 &axis )
{
    float x = axis.x;
    float y = axis.y;
    float z = axis.z;

    float c = cos(angle);
    float s = sin(angle);

    Matrix4 rotationMat;
    rotationMat.mElements = {
       x*x*(1.0f-c)+c,   x*y*(1.0f-c)-z*s, x*z*(1.0f-c)+y*s, 0.0f,
       y*x*(1.0f-c)+z*s, y*y*(1.0f-c)+c,   y*z*(1.0f-c)-x*s, 0.0f,
       z*x*(1.0f-c)-y*s, z*y*(1.0f-c)+x*s, z*z*(1.0f-c)+c,   0.0f,
       0.0f,             0.0f,             0.0f,             1.0f };

    return multiply(rotationMat);
}

Matrix4 & Matrix4::scale( const Vector3 &scaler )
{
    float x = scaler.x;
    float y = scaler.y;
    float z = scaler.z;

    Matrix4 scaleMat;
    scaleMat.mElements = {
        x,    0.0f, 0.0f, 0.0f,
        0.0f, y,    0.0f, 0.0f,
        0.0f, 0.0f, z,    0.0f,
        0.0f, 0.0f, 0.0f, 1.0f };

    return multiply(scaleMat);
}

If you use the matrix class like this,

float   angle_radians = ....;
Vector3 scaleVec{ 0.2f, 0.2f, 0.2f };
Vector3 transVec{ 0.3f, 0.3f, 0.0f };
Vector3 rotateVec{ 0.0f, 0.0f, 1.0f };

Matrix4 model;
model.rotate( angle_rad, rotateVec );
model.translate( transVec );
model.scale( scaleVec );

then the result would look like this:

preview

1
votes

The function rotate() isn't performing an actual rotation. Only generating a partial rotation matrix, and overwriting it over the original matrix.
You need to construct a complete one and multiply it to the original matrix.

Matrix4 Matrix4::rotate(const Matrix4& matrix, float angle, const Vector3& axis)
{
    if (axis.x == 0 && axis.y == 0 && axis.z == 0)
        return matrix;

    float r = angle;
    float s = sin(r);
    float c = cos(r);
    float omc = 1.0f - cos(r);

    float x = axis.x;
    float y = axis.y;
    float z = axis.z;

    Matrix4 r;

    r.mElements[0 + 0 * 4] = c + x * x * omc;
    r.mElements[1 + 0 * 4] = x * y * omc - z * s;
    r.mElements[2 + 0 * 4] = z * x * omc + y * s;
    r.mElements[3 + 0 * 4] = 0;

    r.mElements[0 + 1 * 4] = x * y * omc + z * s;
    r.mElements[1 + 1 * 4] = c + y * y * omc;
    r.mElements[2 + 1 * 4] = z * y * omc - x * s;
    r.mElements[3 + 1 * 4] = 0;

    r.mElements[0 + 2 * 4] = x * z * omc - y * s;
    r.mElements[1 + 2 * 4] = y * z * omc + x * s;
    r.mElements[2 + 2 * 4] = c + z * z * omc;
    r.mElements[3 + 2 * 4] = 0;

    r.mElements[0 + 3 * 4] = 0;
    r.mElements[1 + 3 * 4] = 0;
    r.mElements[2 + 3 * 4] = 0;
    r.mElements[3 + 3 * 4] = 1;

    return r * matrix;
}