2

I created a simple 3D cube with a camera. I am attempting to implement zooming, orbiting and panning of the cube object using a keyboard modifier and mouse input. The requirements for this functionality are as follows:

  • Holding ALT and the LEFT mouse button while moving the mouse left to pan left or right to pan right.
  • Holding ALT and LEFT mouse button while moving the mouse up or down will orbit the cube vertically.
  • Holding ALT and Right mouse button while moving the mouse up will zoom in and moving the mouse down will zoom out.

The functionality of these events does indeed work.

However, the following issues exist:

  1. The cube only updates after I hold ALT, click and release the mouse button. I would like to be able to have the image rendered continuously. I think it would provide a smoother user experience.
  2. When the program first starts up it appears that the cube is up close but this then resets to the location that I intended as soon as the ALT button is pressed.

The libraries required for this code are as follows: glu32, glew32, freeglut, opengl32

The code is as follows:

    // Header Inclusions
#include <iostream>
#include <GL/glew.h>
#include <GL/freeglut.h>
#include <GL/glcorearb.h> // Used for glClear

// GLM Math Header inclusions
#include <glm/glm.hpp> // Located in TDM-GCC-64
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>

using namespace std; // Standard namespace

#define WINDOW_TITLE "Modern OpenGL" // Window title macro

// Shader program macro
#ifndef GLSL
#define GLSL(Version, Source) "#version " #Version "\n" #Source
#endif

/* Variable declarations for shader, window size initialization,
 * buffer and array objects */
GLint shaderProgram, WindowWidth = 800, WindowHeight = 600;
GLuint VBO, VAO;

GLfloat cameraSpeed = 0.5f; // Movement speed per frame

GLfloat lastMouseX = 400, lastMouseY = 300; // Locks mouse cursor to center of screen
GLfloat mouseXOffset, mouseYOffset, yaw = 0.0f, pitch = 0.0f; // mouse offset, yaw, and pitch variables
GLfloat sensitivity = 0.01f; // Used for mouse camera rotation sensitivity
GLfloat zoom = 10.0f; // Zoom multiple set to 10.

bool mouseDetected = true; // Initially true when mouse movement is detected
bool leftMouseClicked = false; // Set to false until button is clicked
bool rightMouseClicked = false; // Set to false until button is clicked


/* Will store key pressed: Used to determine if orthogonal view should be used instead of 3D
* when the letter z is pressed down */
GLchar currentKey;

// Global vector declarations
glm::vec3 cameraPosition = glm::vec3(0.0f, 0.0f,  1.0f); // Initial camera position. Placed 5 units in z
glm::vec3 CameraUpY      = glm::vec3(0.0f, 1.0f,  0.0f); // Temporary y unit vector
glm::vec3 CameraForwardZ = glm::vec3(0.0f, 0.0f, -1.0f); // Temporary z unit vector
glm::vec3 front          = glm::vec3(0.0f, 0.0f,  0.0f); // Temp z unit vector for mouse

// Function prototypes
void UResizeWindow(int, int);
void URenderGraphics(void);
void UCreateShader(void);
void UCreateBuffers(void);
void UMouseMove(int x, int y);
void OnMouseClick(int button, int state, int x, int y);

// Vertex Shader Source Code
const GLchar * vertexShaderSource = GLSL(330,
    layout (location = 0) in vec3 position; // Vertex data from Vertex Attrib Pointer 0
    layout (location = 1) in vec3 color; // Color data from Vertex Attrib Pointer 1

    out vec3 mobileColor; // variable to transfer color data to the fragment shader

    // Global variables for the transform matrices.
    uniform mat4 model;
    uniform mat4 view;
    uniform mat4 projection;

void main(){
    gl_Position = projection * view * model * vec4(position, 1.0f); // transform vertices to clip coordinates
    mobileColor = color; // references incoming color data
    }
);

// Fragment Shader Source Code
const GLchar * fragmentShaderSource = GLSL(330,
    in vec3 mobileColor; // Variable to hold incoming color data from vertex shader

    out vec4 gpuColor; // Variable to pass color data to the GPU

    void main(){
        gpuColor = vec4(mobileColor, 1.0); // Sends color data to the GPU for rendering
    }
);

/* MAIN PROGRAM */
int main(int argc, char* argv[])
{
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_DEPTH | GLUT_DOUBLE | GLUT_RGBA);
    glutInitWindowSize(WindowWidth, WindowHeight);
    glutCreateWindow(WINDOW_TITLE);

    glutReshapeFunc(UResizeWindow);

    glewExperimental = GL_TRUE;
        if (glewInit() != GLEW_OK){
            std::cout << "Failed to initialize GLEW" << std::endl;
            return -1;
        }

    UCreateShader();
    UCreateBuffers();

    // Use the Shader program
    glUseProgram(shaderProgram);

    glClearColor(0.0f,0.0f, 0.0f, 1.0f); // set background color

    glutDisplayFunc(URenderGraphics);  // Renders graphics


    glutPassiveMotionFunc(UMouseMove); // Detects mouse movement

    glutMouseFunc(OnMouseClick); // Detects mouse button clicked

    glutMainLoop();

    // Destroys Buffer objects once used
    glDeleteVertexArrays(1, &VAO);
    glDeleteBuffers(1, &VBO);

    return 0;
}

// Resize the window
void UResizeWindow(int w, int h){
    WindowWidth = w;
    WindowHeight = h;
    glViewport(0, 0, WindowWidth, WindowHeight);
}

// Renders graphics
void URenderGraphics(void){
    glEnable(GL_DEPTH_TEST); //Enable z-depth

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Clears the screen

    glBindVertexArray(VAO); // Activate the Vertex Array Object before rendering and transforming them

    CameraForwardZ = front; // Replaces camera forward vector with Radians normalized as a unit vector

    // Transform the object
    glm::mat4 model;
    model = glm::translate(model, glm::vec3(0.0, 0.0f, 0.0f)); // Place the object at the center of the viewport
    model = glm::rotate(model, 45.0f, glm::vec3(0.0, 1.0f, 0.0f)); // Rotate the object 45 degrees on the X axis
    model = glm::scale(model, glm::vec3(2.0f, 2.0f, 2.0f)); // Increase the object size by a scale of 2

    // Transform the camera
    glm::mat4 view;
    view = glm::lookAt(CameraForwardZ, cameraPosition, CameraUpY);

    // Display perspective
    glm::mat4 projection;
    // Creates an perspective projection
    projection = glm::perspective(45.0f, (GLfloat)WindowWidth / (GLfloat)WindowHeight, 0.1f, 100.0f);

    // Retrieves and passes transform matrices to the shader program
    GLint modelLoc = glGetUniformLocation(shaderProgram, "model");
    GLint viewLoc = glGetUniformLocation(shaderProgram, "view");
    GLint projLoc = glGetUniformLocation(shaderProgram, "projection");

    glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(model));
    glUniformMatrix4fv(viewLoc, 1, GL_FALSE, glm::value_ptr(view));
    glUniformMatrix4fv(projLoc, 1, GL_FALSE, glm::value_ptr(projection));

    glutPostRedisplay();

    // Draws the triangles
    glDrawArrays(GL_TRIANGLES, 0, 36);

    glBindVertexArray(0); // Deactivate the Vertex Array Object

    glutSwapBuffers(); // Flips the back buffer with the from buffer every frame. Similar to GL Flush.
}

// Creates the shader program
void UCreateShader(){
    // vertex shader
    GLint vertexShader = glCreateShader(GL_VERTEX_SHADER); // Create the vertex shader
    glShaderSource(vertexShader, 1, &vertexShaderSource, NULL); // Attaches the Vertex shader to the source code
    glCompileShader(vertexShader); // Compiles the Vertex shader

    // Fragment shader
    GLint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER); // Create the fragment shader
    glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL); // Attaches fragment shader to source code
    glCompileShader(fragmentShader); // Compiles the fragment shader

    // Shader program
    shaderProgram = glCreateProgram(); // Create the shader program and return an id
    glAttachShader(shaderProgram, vertexShader); // Attach Vertex shader to the shader program
    glAttachShader(shaderProgram, fragmentShader); // Attach fragment shader to the shader program
    glLinkProgram(shaderProgram); // Link vertex and fragment shader to shader program

    // Delete the vertex and fragment shader once linked
    glDeleteShader(vertexShader);
    glDeleteShader(fragmentShader);
}

// Create the buffer and array objects
void UCreateBuffers(){
    // Position and color data
    GLfloat vertices[] = {
       // Vertex positions     // colors
               -0.5f, -0.5f, -0.5f,   1.0f, 0.0f, 0.0f,
                0.5f, -0.5f, -0.5f,   1.0f, 0.0f, 0.0f,
                0.5f,  0.5f, -0.5f,   1.0f, 0.0f, 0.0f,
                0.5f,  0.5f, -0.5f,   1.0f, 0.0f, 0.0f,
               -0.5f,  0.5f, -0.5f,   1.0f, 0.0f, 0.0f,
               -0.5f, -0.5f, -0.5f,   1.0f, 0.0f, 0.0f,

               -0.5f, -0.5f, 0.5f,    0.0f, 1.0f, 0.0f,
                0.5f, -0.5f, 0.5f,    0.0f, 1.0f, 0.0f,
                0.5f,  0.5f, 0.5f,    0.0f, 1.0f, 0.0f,
                0.5f,  0.5f, 0.5f,    0.0f, 1.0f, 0.0f,
               -0.5f,  0.5f, 0.5f,    0.0f, 1.0f, 0.0f,
               -0.5f, -0.5f, 0.5f,    0.0f, 1.0f, 0.0f,

               -0.5f,  0.5f,  0.5f,    0.0f, 0.0f, 1.0f,
               -0.5f,  0.5f, -0.5f,    0.0f, 0.0f, 1.0f,
               -0.5f, -0.5f, -0.5f,    0.0f, 0.0f, 1.0f,
               -0.5f, -0.5f, -0.5f,    0.0f, 0.0f, 1.0f,
               -0.5f, -0.5f,  0.5f,    0.0f, 0.0f, 1.0f,
               -0.5f,  0.5f,  0.5f,    0.0f, 0.0f, 1.0f,

                0.5f,  0.5f,  0.5f,    1.0f, 1.0f, 0.0f,
                0.5f,  0.5f, -0.5f,    1.0f, 1.0f, 0.0f,
                0.5f, -0.5f, -0.5f,    1.0f, 1.0f, 0.0f,
                0.5f, -0.5f, -0.5f,    1.0f, 1.0f, 0.0f,
                0.5f, -0.5f,  0.5f,    1.0f, 1.0f, 0.0f,
                0.5f,  0.5f,  0.5f,    1.0f, 1.0f, 0.0f,

               -0.5f, -0.5f, -0.5f,    0.0f, 1.0f, 1.0f,
                0.5f, -0.5f, -0.5f,    0.0f, 1.0f, 1.0f,
                0.5f, -0.5f,  0.5f,    0.0f, 1.0f, 1.0f,
                0.5f, -0.5f,  0.5f,    0.0f, 1.0f, 1.0f,
               -0.5f, -0.5f,  0.5f,    0.0f, 1.0f, 1.0f,
               -0.5f, -0.5f, -0.5f,    0.0f, 1.0f, 1.0f,

               -0.5f,  0.5f, -0.5f,    1.0f, 0.0f, 1.0f,
                0.5f,  0.5f, -0.5f,    1.0f, 0.0f, 1.0f,
                0.5f,  0.5f,  0.5f,    1.0f, 0.0f, 1.0f,
                0.5f,  0.5f,  0.5f,    1.0f, 0.0f, 1.0f,
               -0.5f,  0.5f,  0.5f,    1.0f, 0.0f, 1.0f,
               -0.5f,  0.5f, -0.5f,    1.0f, 0.0f, 1.0f
    };

    // Generate buffer Ids
    glGenVertexArrays(1, &VAO);
    glGenBuffers(1, &VBO);

    // Activate the Vertex Array Object before binding and setting any VBO's and Vertex Attribute Pointers
    glBindVertexArray(VAO);

    // Activate the VBO
    glBindBuffer(GL_ARRAY_BUFFER, VBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); // Copy vertices to VBO

    // Set attribute pointer 0 to hold Position data
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), (GLvoid*)0);
    glEnableVertexAttribArray(0); // Enables vertex attribute

    // Set attribute pointer 1 to hold Position data
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), (GLvoid*)(3 * sizeof(GLfloat)));
    glEnableVertexAttribArray(1); // Enables vertex attribute

    glBindVertexArray(0); // Deactivates the VAO which is good practice
}

// Implements the UMouseMove function
void UMouseMove(int x, int y) {
/* If mouse is clicked and left alt is held down then the cube
 * will be rotated in the direction of the mouse.*/
    if(glutGetModifiers() == GLUT_ACTIVE_ALT) {
       mouseXOffset = x - lastMouseX;
       mouseYOffset = lastMouseY - y;
       lastMouseX = x;
       lastMouseY = y;
       mouseXOffset *= sensitivity;
       mouseYOffset *= sensitivity;

       // Rotate Object if left mouse clicked
       while(leftMouseClicked) {
           yaw += mouseXOffset;
           pitch -= mouseYOffset;
           pitch = max(-0.5f, pitch);
           pitch = min(0.5f, pitch);
           leftMouseClicked = false; // resets leftMouseClicked to default state
       }

       // Zoom object if right mouse clicked
       while(rightMouseClicked) {
           zoom += mouseYOffset * cameraSpeed;  // Zoom into object
           rightMouseClicked = false; // resets rightMouseClicked to default state
       }

       front.x = zoom * cos(yaw);
       front.y = zoom * sin(pitch);
       front.z = sin(yaw) * cos(pitch) * zoom;
    }
       lastMouseX = x;
       lastMouseY = y;
}

// Check the mouse button click state to verify which button is pressed
void OnMouseClick(int button, int state, int x, int y){
    // Set leftMouseClicked State to true
    if(state == GLUT_DOWN && button == GLUT_LEFT_BUTTON){
        leftMouseClicked = true;
    }
    // Set rightMouseClicked State to true
    if(state == GLUT_DOWN && button == GLUT_RIGHT_BUTTON){
        rightMouseClicked = true;
    }
}
genpfault
  • 51,148
  • 11
  • 85
  • 139
user9477548
  • 87
  • 11
  • I don't know very much about GLUT, and I'm not sure what's causing the specific issues you describe, but there are several things in your code that aren't immediately clear to me. It doesn't look like you're handling mouse button up events anywhere, which I expect would be an important part of the logic. You seem to be calling `glutMouseFunc()` redundantly in `UMouseMove()`. And some other things. Here's what I'd suggest. Create a new test project, and just focus on handling mouse input, using logging to see what's going on. Get that logic worked out, then return to the full project. – scg Dec 06 '20 at 23:59
  • Rather than handle mouse up buttons i'm using a boolean variable as a trigger. When the mouse button is down it is set to true and when it is up it is set back to false which is the default value. Even when I handle mouse up events the movement is still choppy and not continuous with the mouse movement. Movement only occurs once the mouse button is released. – user9477548 Dec 07 '20 at 00:18
  • 1
    Maybe I'm missing something, but the logic still seems wrong to me. Someone may identify the problem for you as is, but even a basic OpenGL program includes so much boilerplate that it can be hard to sift through. I still think it might be helpful to create a temporary, simpler version that doesn't do anything but change a value in response to mouse events, and print that value in the render function. Then you can watch the console and see if the render function is running and if the value is being updated as you expect. And, you could post the smaller example if you still need help. – scg Dec 07 '20 at 00:21
  • @user9477548 [LearnOpenGL - Camera](https://learnopengl.com/Getting-started/Camera) – Rabbid76 Dec 07 '20 at 09:08
  • see [Understanding 4x4 homogenous transform matrices](https://stackoverflow.com/a/28084380/2521214) then look at the links at the end there are examples on player and camera control (different styles and methods) – Spektre Dec 07 '20 at 09:31

1 Answers1

1

The problem here is the use of glutPassiveMotionFunc within the main program function. This function executes only when the mouse is moving with no buttons being clicked. glutMouseFunc generates a callback each time a mouse button is pressed and released. This triggers the function "OnMouseClick" that only sets the variable of leftMouseClicked or rightMouseClicked to true. Nothing is rendered until the mouse button is no longer being clicked (mouse up condition). At this point no buttons are being pressed which causes glutPassiveMotionFunc to send a call back triggering the "UMouseMove" function. The "UMouseMove" function then calculates the change in y or x and updates the front focus of the camera. This is why the object does not render while the mouse button is being pressed down. Only when the mouse button is released (the mouse is moving with no buttons being pressed) do the coordinates update for rendering.

The solution to this is to use glutMotionFunc instead of glutPassiveMotionFunc. glutMotionFunc executes the callback when the mouse moves while a mouse button is clicked (mouse down position). This executes the "UMouseMove" function while the mouse button is pressed down and concurrently updates the front focus of the camera. Recommendations:

  1. Change glutPassiveMotionFunc to glutMotionFunc in main as follows:

    glutDisplayFunc(URenderGraphics);  // Renders graphics
    
    glutMotionFunc(UMouseMove); // <--- Change this line //Detects mouse movement
    
    glutMouseFunc(OnMouseClick); // Detects mouse button clicked
    
    glutMainLoop();
    
  2. Edit the "UMouseMove" function to first check if the ALT key modifier is being pressed. Remove handling the leftMouseClicked and rightMouseClicked variables within this method. Move the last two lines of this function within the if statement as shown:

    // Implements the UMouseMove function
    void UMouseMove(int x, int y) {
      /* If mouse is clicked and left alt is held down then the cube
       * will be rotated in the direction of the mouse.*/
      if(glutGetModifiers() == GLUT_ACTIVE_ALT) {
         mouseXOffset = x - lastMouseX;
         mouseYOffset = lastMouseY - y;
         lastMouseX = x;
         lastMouseY = y;
         mouseXOffset *= sensitivity;
         mouseYOffset *= sensitivity;
    
         // Rotate Object if left mouse clicked
         if(leftMouseClicked) {
             yaw += mouseXOffset;
             pitch -= mouseYOffset;
             pitch = max(-0.5f, pitch);
             pitch = min(0.5f, pitch);
         }
    
         // Zoom object if right mouse clicked
         else if(rightMouseClicked) {
             zoom += mouseYOffset * cameraSpeed;  // Zoom into object
         }
    
         front.x = zoom * cos(yaw);
         front.y = zoom * sin(pitch);
         front.z = sin(yaw) * cos(pitch) * zoom;
    
         lastMouseX = x;
         lastMouseY = y;
      }
    }
    
  3. The OnMouseClick function should handle all mouse button click events for good housekeeping practice. This can be something as simple as the following:

    // Check the mouse button click state to verify which button is pressed
    void OnMouseClick(int button, int state, int x, int y){
      // Set leftMouseClicked State to true
      if(state == GLUT_DOWN && button == GLUT_LEFT_BUTTON){
        leftMouseClicked = true;
      }
      else{
        leftMouseClicked = false;
      }
    
      // Set rightMouseClicked State to true
      if(state == GLUT_DOWN && button == GLUT_RIGHT_BUTTON){
        rightMouseClicked = true;
      }
      else{
        rightMouseClicked = false;
      }
    }
    
user9477548
  • 87
  • 11