Skip to content

Lab 3: 2D Transformations and Mouse Input

drebain edited this page May 24, 2017 · 1 revision

Lab 3: 2D Transformations and Mouse Input

In this lab, we will practice (1) making a simple 2D animation with hierarchies of transforms and (2) using the GLFW library for mouse interaction. Please go to the GitHub repo and look at the project directory "transform" and "mouse".

Getting set up

Important Note: You must now clone the repository using git from the command line (Not downloading the .zip) to correctly acquire all dependencies, with the command git clone --recursive https://github.com/drebain/icg.git.

2D transform matrix and hierarchy

This programming example should be filled in /transform/main.cpp.

We use the Eigen math library to calculate all the transform matrices. Keep in mind that we practise everything on a 2D plane in this practise, so there should be no change on the Z-axis. There are several important matrix functions being used in this practise:

  • Transform::Identity() returns an identity 4x4 matrix.
  • Eigen::Translation3f(x, y, z) returns the translation matrix of a 3D translation. We should keep z == 0 in our example.
  • Eigen::AngleAxisf(angle, axis) creates a rotational matrix of angle around the axis. Because we only make animation in 2D space in this example, axis here is the z axis, which is represented by Eigen::Vector3f::UnitZ().
  • Eigen::AlignedScaling3f(x, y, z) returns the scaling matrix of a 3D scaling transform. We should keep z == 1 in our example.

The following code segment creates an animation of an earth orbiting around the sun, and a moon going around the earth. Note that many simple animation effects can be created with sinusoidal or circular movements, which is implemented by the std::sin and std::cos functions.

float freq = M_PI*time_s*SPEED_FACTOR;

// **** Sun transform
Transform sun_M = Transform::Identity();
sun_M *= Eigen::Translation3f(0.2, 0.0, 0.0);
sun_M *= Eigen::AngleAxisf(-freq/SUN_ROT_PERIOD, Eigen::Vector3f::UnitZ());
//scale_t: make the sun become bigger and smaller over the time!
float scale_t = 0.01*std::sin(freq);
sun_M *= Eigen::AlignedScaling3f(0.2 +scale_t, 0.2 +scale_t, 1.0);

// **** Earth transform
Transform earth_M = Transform::Identity();
//calculate the earth's orbit as an ellipse around the sun
float x_earth_orbit = 0.7*std::cos(-freq/EARTH_ORBITAL_PERIOD);
float y_earth_orbit = 0.5*std::sin(-freq/EARTH_ORBITAL_PERIOD);
earth_M *= Eigen::Translation3f(x_earth_orbit, y_earth_orbit, 0.0);
//save the earth's transform before spinning, so we don't spin the moon
//with the earth!
Transform earth_M_prespin = earth_M;
earth_M *= Eigen::AngleAxisf(-freq/EARTH_ROT_PERIOD, Eigen::Vector3f::UnitZ());
//make the picture of earth smaller
earth_M *= Eigen::AlignedScaling3f(0.08, 0.08, 1.0);

// **** Moon transform
Transform moon_M = earth_M_prespin;
// Make the moon orbit around the earth with 0.2 units of distance
moon_M *= Eigen::AngleAxisf(freq/MOON_ORBITAL_PERIOD, Eigen::Vector3f::UnitZ());
moon_M *= Eigen::Translation3f(0.2, 0.0, 0.0);
// Make the moon spining according to MOON_ROT_PERIOD
moon_M *= Eigen::AngleAxisf(-freq/MOON_ROT_PERIOD, -Eigen::Vector3f::UnitZ());
// Make the picture of moon smaller!
moon_M *= Eigen::AlignedScaling3f(0.04, 0.04, 1.0);

// draw the sun, the earth and the moon
sun.draw(sun_M.matrix());
earth.draw(earth_M.matrix());
moon.draw(moon_M.matrix());

Mouse Interaction with GLFW

This programming example should be filled in /mouse/main.cpp.

The GLFW is a supporting library for OpenGL to create windows and receiving inputs and events. In our example, we create a multi-line segment with 4 control points, and use the mouse to move one of the control points. The program decides which control point is being picked by rendering a special image of the control points, using pixel colour values to denote the ID of the control point at a specific location. If there is no control point at a pixel, that pixel will have a value of zero. Then, the program examines what is the pixel value under the mouse cursor -- which is returned by GLFW. If that pixel value is greater than zero, the pixel value corresponds to the control point ID. The program then moves that specific control point according to mouse input.

The following code segment (provided in your downloaded code) illustrates this process. Please follow this example when working on your own mouse-picking algorithms.

void selection_button(GLFWwindow *window, int button, int action, int mods) {

    if (button == GLFW_MOUSE_BUTTON_LEFT && action == GLFW_PRESS) {
        double x = 0, y = 0;
        glfwGetCursorPos(OpenGP::window, &x, &y);
        x_last = x; y_last = y;
        //render the image with color code
        render_selection();

        glFlush();
        glFinish();
        //picking pixel
        unsigned char res[4];
        glReadPixels(x, window_height - y, 1,1,GL_RGBA, GL_UNSIGNED_BYTE, &res);
        selected_point = res[0];

        if (selected_point >= 0 && selected_point < cam_pos_points.size())
            cam_pos_points[selected_point].selected() = true;
    }

    if (button == GLFW_MOUSE_BUTTON_LEFT && action == GLFW_RELEASE) {
        if (selected_point >= 0 && selected_point < cam_pos_points.size()) {
            cam_pos_points[selected_point].selected() = false;

        double x = 0, y = 0;
        glfwGetCursorPos(OpenGP::window, &x, &y);
        //if the mouse is not moved at all, don't bother redraw
        if (x != x_last || y != y_last) {
        //caculate the world coordiante from the pixel coordinate on screen
        unproject(x, y, cam_pos_points[selected_point].position());
        cam_pos_curve.set_points(cam_pos_points[0].position(),
                cam_pos_points[1].position(),
                cam_pos_points[2].position(),
                cam_pos_points[3].position());
            }
        }
        selected_point = -1;
    }
}

Then, the following code should be implemented in void mousemove(x, y) to make the control point move when one is pressing and dragging the mouse.

void mousemove(GLFWwindow *window, double x, double y) {
    if (selected_point >= 0 && selected_point < cam_pos_points.size()) {
        if (x != x_last || y != y_last) {

            unproject(x, y, cam_pos_points[selected_point].position());
            cam_pos_curve.set_points(cam_pos_points[0].position(),
                cam_pos_points[1].position(),
                cam_pos_points[2].position(),
                cam_pos_points[3].position());
            x_last = x;
            y_last = y;

        }
    }
}

Finally, these two methods need to be registered with GLFW as a call-back for mouse events.

int main(int, char**){
   ...
    glfwSetMouseButtonCallback(OpenGP::window, selection_button);
    glfwSetCursorPosCallback(OpenGP::window, mousemove);   
   ...
}

References

Eigen Math Library: http://eigen.tuxfamily.org/index.php?title=Main_Page

GLFW: http://www.glfw.org/

We use glfw 3 in our programming excises, its documentation can be found at: http://www.glfw.org/docs/latest/