2
votes

I've been stuck on this for two days now, I'm unsure where else to look. I'm rendering two 3d cubes using OpenGL, and trying to apply a local rotation to each cube in these scene in response to me pressing a button.

I've got to the point where my cubes rotate in 3d space, but their both rotating about the world-space origin, instead of their own local origins.

(couple second video) https://www.youtube.com/watch?v=3mrK4_cCvUw

After scouring the internet, the appropriate formula for calculating the MVP is as follow:

auto const model = TranslationMatrix * RotationMatrix * ScaleMatrix;
auto const modelview = projection * view * model;

Each of my cube's has it's own "model", which is defined as follows:

struct model
{
    glm::vec3 translation;
    glm::quat rotation;
    glm::vec3 scale = glm::vec3{1.0f};
};

When I press a button on my keyboard, I create a quaternion representing the new angle and multiply it with the previous rotation quaternion, updating it in place.

The function looks like this:

template<typename TData>
void rotate_entity(TData &data, ecst::entity_id const eid, float const angle,
  glm::vec3 const& axis) const
{
    auto &m = data.get(ct::model, eid);
    auto const q = glm::angleAxis(glm::degrees(angle), axis);
    m.rotation = q * m.rotation;

    // I'm a bit unsure on this last line above, I've also tried the following without fully understanding the difference
    // m.rotation = m.rotation * q;
}

The axis is provided by the user like so:

// inside user-input handling function
float constexpr ANGLE = 0.2f;

...

// y-rotation
case SDLK_u: {
    auto constexpr ROTATION_VECTOR = glm::vec3{0.0f, 1.0f, 0.0f};
    rotate_entities(data, ANGLE, ROTATION_VECTOR);
    break;
}
case SDLK_i: {
    auto constexpr ROTATION_VECTOR = glm::vec3{0.0f, -1.0f, 0.0f};
    rotate_entities(data, ANGLE, ROTATION_VECTOR);
    break;
}

My GLSL vertex shader is pretty straight forward from what I've found in the example code out there:

// attributes input to the vertex shader
in vec4 a_position; // position value

// output of the vertex shader - input to fragment
// shader
out vec3 v_uv;

uniform mat4 u_mvmatrix;

void main()
{
  gl_Position = u_mvmatrix * a_position;
  v_uv = vec3(a_position.x, a_position.y, a_position.z);
}

Inside my draw code, the exact code I'm using to calculate the MVP for each cube is:

...
auto const& model = shape.model();

auto const tmatrix = glm::translate(glm::mat4{}, model.translation);
auto const rmatrix = glm::toMat4(model.rotation);
auto const smatrix = glm::scale(glm::mat4{}, model.scale);
auto const mmatrix = tmatrix * rmatrix * smatrix;
auto const mvmatrix = projection * view * mmatrix;

// simple wrapper that does logging and forwards to glUniformMatrix4fv()
p.set_uniform_matrix_4fv(logger, "u_mvmatrix", mvmatrix);

Earlier in my program, I calculate my view/projection matrices like so:

auto const windowheight = static_cast<GLfloat>(hw.h);
auto const windowwidth = static_cast<GLfloat>(hw.w);
auto projection = glm::perspective(60.0f, (windowwidth / windowheight), 0.1f, 100.0f);
auto view = glm::lookAt(
  glm::vec3(0.0f, 0.0f, 1.0f), // camera position
  glm::vec3(0.0f, 0.0f, -1.0f),  // look at origin
  glm::vec3(0.0f, 1.0f, 0.0f)); // "up" vector

The positions of my cube's in world-space are on the Z axis, so they should be visible:

cube0.set_world_position(0.0f, 0.0f, 0.0f, 1.0f);
cube1.set_world_position(-0.7f, 0.7f, 0.0f, 1.0f);

// I call set_world_position() exactly once before my game enter's it's main loop.
// I never call this again, it just modifies the vertex used as the center of the shape.
// It doesn't modify the model matrix at all.
// I call it once before my game enter's it's game loop, and I never modify it after that.

So, my question is, is the appropriate way to update a rotation for an object?

Should I be storing a quaternion directly in my object's "model"?

Should I be storing my translation and scaling as separate vec3's?

Is there an easier way to do this? I've been reading and re-reading anything I can find, but I don't see anyone doing this in the same way.

This tutorial is a bit short on details, specifically how to apply a rotation to an existing rotation (I believe this is just multiplying the quaternions together, which is what I'm doing inside rotate_entity(...) above). http://www.opengl-tutorial.org/intermediate-tutorials/tutorial-17-quaternions/ https://github.com/opengl-tutorials/ogl/blob/master/tutorial17_rotations/tutorial17.cpp#L306-L311

Does it make more sense to store the resulting "MVP" matrix myself as my "model" and apply glm::transform/glm::scale/glm::rotate operations on the MVP matrix directly? (I tried this last option earlier, but I couldn't figure out how to get that to work too).

Thanks!

edit: better link

1
I have no idea how to produce a self-containing example, as this uses OpenGL.Short
What does cube*.set_world_position() do? Does it modify the vertex coordinates of the cubes before you send them to the GPU? Does it modify the model matrix for the object?user1118321
I made up the function, your exactly correct. It modifies the vertex coordinates of the cubes, before they are sent to the GPU. It doesn't modify the model matrix at all. I call it once before my game enter's it's game loop, and I never modify it after that. Sorry I had to reread your comment, I didn't understand it correctly the first time.Short
Please explain the downvote, I'm unsure how to make the question better without removing details.Short

1 Answers

5
votes

Generally, you don't want to modify the position of your model's individual vertices on the CPU. That's the entire purpose of the vertex program. The purpose of the model matrix is to position the model in the world in the vertex program.

To rotate a model around its center, you need to first move the center to the origin, then rotate it, then move the center to its final position. So let's say you have a cube that stretches from (0,0,0) to (1,1,1). You need to:

  1. Translate the cube by (-0.5, -0.5, -0.5)
  2. Rotate by the angle
  3. Translate the cube by (0.5, 0.5, 0.5)
  4. Translate the cube to wherever it belongs in the scene

You can combine the last 2 translations into a single one, and of course, you can collapse all of these transformations into a single matrix that is your model matrix.