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:
- 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.
- 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;
}
}