Skip to content

Instantly share code, notes, and snippets.

@faustinoaq
Last active January 26, 2025 18:39
Show Gist options
  • Save faustinoaq/2d4330ba04b5ffda48feef2d46af5d98 to your computer and use it in GitHub Desktop.
Save faustinoaq/2d4330ba04b5ffda48feef2d46af5d98 to your computer and use it in GitHub Desktop.
OpenGL animation of a bouncing ball inside a transparent rotating cube that changes direction on click
/**
* @file bounce.cpp
* @brief OpenGL animation of a bouncing ball inside a transparent cube
* @author @faustinoaq
*
* This program creates a 3D animation using OpenGL and GLFW showing a red sphere
* bouncing inside a transparent cube with colored faces. The animation includes:
* - A rotating view of the scene
* - Sphere-cube collision detection and response
* - Transparent cube faces with colored edges
* - Fullscreen display options
*
* Usage:
* ./bounce 1 # Display 1
* ./bounce 2 # Display 2
*
* Controls:
* - ESC: Exit the program
*
* Dependencies:
* - GLFW3
* - OpenGL
* - GLU
* Ubuntu 22.04: sudo apt-get install libglfw3-dev libglu1-mesa-dev
*
* Classes:
* - Ball: Represents the bouncing sphere with position, velocity and collision logic
*
* Key functions:
* - drawSphere(): Renders the bouncing ball
* - drawCube(): Renders the transparent cube with colored faces
* - main(): Program entry point and main rendering loop
*
*/
#include <GLFW/glfw3.h>
#include <GL/glu.h>
#include <vector>
#include <cmath>
#include <iostream>
#include <cstring>
class Ball {
public:
float pos[3] = {0.0f, 0.0f, 0.0f};
float vel[3] = {0.05f, 0.07f, 0.03f};
float radius = 0.2f;
void update() {
for (int i = 0; i < 3; i++) {
pos[i] += vel[i];
if (std::abs(pos[i]) + radius > 1.0f) {
pos[i] = (1.0f - radius) * (pos[i] > 0 ? 1 : -1);
vel[i] *= -1;
}
}
}
};
void drawSphere(const Ball& ball) {
glPushMatrix();
glTranslatef(ball.pos[0], ball.pos[1], ball.pos[2]);
glColor4f(1.0f, 0.0f, 0.0f, 0.7f);
GLUquadric* quad = gluNewQuadric();
gluQuadricDrawStyle(quad, GLU_FILL);
gluSphere(quad, ball.radius, 32, 32);
gluDeleteQuadric(quad);
glPopMatrix();
}
void drawCube() {
const float vertices[][3] = {
{1, -1, -1}, {1, 1, -1}, {-1, 1, -1}, {-1, -1, -1},
{1, -1, 1}, {1, 1, 1}, {-1, -1, 1}, {-1, 1, 1}
};
const int surfaces[][4] = {
{0,1,2,3}, {3,2,7,6}, {6,7,5,4}, {4,5,1,0}, {1,5,7,2}, {4,0,3,6}
};
const float colors[][4] = {
{0,1,1,0.1f}, {1,0,1,0.1f}, {1,0,0,0.1f},
{0,1,0,0.1f}, {0,0,1,0.1f}, {1,1,0,0.1f}
};
const int edges[][2] = {
{0,1}, {1,2}, {2,3}, {3,0}, {4,5}, {5,7},
{7,6}, {6,4}, {0,4}, {1,5}, {2,7}, {3,6}
};
glDepthMask(GL_FALSE);
glBegin(GL_QUADS);
for (int i = 0; i < 6; i++) {
glColor4fv(colors[i]);
for (int j = 0; j < 4; j++) {
glVertex3fv(vertices[surfaces[i][j]]);
}
}
glEnd();
glDepthMask(GL_TRUE);
glColor4f(0, 0, 0, 0.5f);
glBegin(GL_LINES);
for (const auto& edge : edges) {
glVertex3fv(vertices[edge[0]]);
glVertex3fv(vertices[edge[1]]);
}
glEnd();
}
// Global variable to track rotation direction
float rotationDirection = 1.0f;
// Mouse button callback function
void mouseButtonCallback(GLFWwindow* window, int button, int action, int mods) {
if (button == GLFW_MOUSE_BUTTON_LEFT && action == GLFW_PRESS) {
rotationDirection *= -1.0f;
}
}
int main(int argc, char* argv[]) {
if (!glfwInit()) return -1;
// Get primary monitor resolution and position
GLFWmonitor* primaryMonitor = glfwGetPrimaryMonitor();
const GLFWvidmode* mode = glfwGetVideoMode(primaryMonitor);
int totalWidth = mode->width; //* 2; // For dual monitors
int height = mode->height;
// Set window hints for borderless fullscreen
glfwWindowHint(GLFW_DECORATED, GLFW_FALSE); // Remove window decorations
glfwWindowHint(GLFW_FLOATING, GLFW_TRUE); // Keep window on top
glfwWindowHint(GLFW_AUTO_ICONIFY, GLFW_FALSE); // Prevent minimizing
glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); // Prevent resizing
// Create borderless window
// glfwCreateWindow(width, height, title, monitor, share)
GLFWwindow* window;
if (argc < 2) {
std::cout << "Please provide an argument '1' or '2'." << std::endl;
return -1;
}
if (strcmp(argv[1], "2") == 0) {
window = glfwCreateWindow(totalWidth, height, "Cube Bounce",
primaryMonitor, nullptr);
} else if (strcmp(argv[1], "1") == 0) {
window = glfwCreateWindow(totalWidth, height, "Cube Bounce",
nullptr, nullptr);
} else {
std::cout << "Invalid argument. Please use '1' or '2'." << std::endl;
return -1;
}
if (!window) {
glfwTerminate();
return -1;
}
// Position window at (0,0) to span monitors
glfwSetWindowPos(window, 0, 0);
// Make this window the current OpenGL context
glfwMakeContextCurrent(window);
// Register mouse button callback
glfwSetMouseButtonCallback(window, mouseButtonCallback);
// Rest of the code remains the same...
// Setup perspective
float aspectRatio = (float)totalWidth / height;
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(45, aspectRatio, 0.1, 50.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glTranslatef(0.0f, 0.0f, -8.0f);
// Enable needed GL features
glEnable(GL_DEPTH_TEST);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
Ball ball;
float rotationAngle = 0.0f;
while (!glfwWindowShouldClose(window)) {
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glPushMatrix();
glRotatef(rotationAngle, 1, 1, 1);
ball.update();
drawSphere(ball);
drawCube();
glPopMatrix();
rotationAngle += rotationDirection;
if (rotationAngle > 360.0f) rotationAngle -= 360.0f;
if (rotationAngle < 0.0f) rotationAngle += 360.0f;
glfwSwapBuffers(window);
glfwPollEvents();
if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
glfwSetWindowShouldClose(window, GLFW_TRUE);
glfwWaitEventsTimeout(0.01);
}
glfwTerminate();
return 0;
}
@faustinoaq
Copy link
Author

bounce.mp4

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment