gem-graph-client/wip/instanced_cubes.c

576 lines
19 KiB
C
Raw Normal View History

// gcc `pkg-config --cflags --libs gtk4 epoxy glib-2.0` -lm -o instanced_cubes instanced_cubes.c
#include <epoxy/gl.h>
#include <cglm/cglm.h>
#include <stdlib.h>
#include <time.h>
#include <gdk/gdk.h> // General GDK events and types
#include <gtk/gtk.h> // GTK types
#define N_INSTANCE 5
#define N_SPACE_SIZE 20
GtkWidget *window;
// OpenGL objects
GLuint vao, vbo, ebo, instance_vbo, arrow_instance_vbo;
GLuint arrow_vao, arrow_vbo, arrow_ebo;
GLuint edge_ebo;
GLuint shader_program;
unsigned int num_instances = N_INSTANCE;
mat4 instance_matrices[N_INSTANCE];
static float pitch = 0.0f, yaw = -90.0f; // Initial angles for camera orientation
static const float sensitivity = 4.0f; // Adjust this for smoother mouse control
static float lastX = 400, lastY = 300; // Initial cursor position (center of the screen)
static float fov = 45.0f; // Field of View for zoom control
static gboolean firstMouse = TRUE; // To handle first mouse movement
static float rotation_angle_x = 0.0f; // Rotation angle around the X-axis
static float rotation_angle_y = 0.0f; // Rotation angle around the Y-axis
static float rotation_angle_z = 0.0f; // Rotation angle around the Z-axis
static mat4 rotation_matrix;
static bool rotate;
// Projection and view matrices
mat4 projection, view;
// Function Prototypes
GLuint create_shader_program ();
void setup_buffers ();
void setup_arrow_buffers ();
void setup_instance_data ();
void setup_arrow_instance_data();
static void on_realize (GtkGLArea *area);
static gboolean on_render (GtkGLArea *area, GdkGLContext *context);
static void on_activate (GtkApplication *app, gpointer user_data);
static gboolean on_mouse_scroll(GtkEventControllerScroll *controller, gdouble dx, gdouble dy, gpointer user_data);
static gboolean on_mouse_move(GtkEventControllerMotion *controller, gdouble x, gdouble y, gpointer user_data);
// Vertex and fragment shader source code
const char *vertex_shader_src = "#version 460 core\n"
"layout (location = 0) in vec3 aPos;\n"
"layout (location = 2) in mat4 instanceMatrix;\n"
"uniform mat4 projection;\n"
"uniform mat4 view;\n"
"void main() {\n"
" gl_Position = projection * view * instanceMatrix * vec4(aPos, 1.0);\n"
"}\n";
const char *fragment_shader_src = "#version 460 core\n"
"uniform vec4 FragColor;\n"
"out vec4 finalColor;\n"
"void main() {\n"
" finalColor = vec4(FragColor.rgb, 0.3); // Set alpha to 30%\n"
"}\n";
// Cube vertices and indices
GLfloat vertices[] = {
-0.5f, -0.5f, -0.5f, // 0
0.5f, -0.5f, -0.5f, // 1
0.5f, 0.5f, -0.5f, // 2
-0.5f, 0.5f, -0.5f, // 3
-0.5f, -0.5f, 0.5f, // 4
0.5f, -0.5f, 0.5f, // 5
0.5f, 0.5f, 0.5f, // 6
-0.5f, 0.5f, 0.5f // 7
};
GLuint indices[] = {
4, 5, 6, 6, 7, 4, // Front face
0, 3, 2, 2, 1, 0, // Back face
0, 4, 7, 7, 3, 0, // Left face
1, 2, 6, 6, 5, 1, // Right face
3, 7, 6, 6, 2, 3, // Top face
0, 1, 5, 5, 4, 0 // Bottom face
};
GLuint edge_indices[] = {
// Bottom edges
0, 1,
1, 5,
5, 4,
4, 0,
// Top edges
3, 2,
2, 6,
6, 7,
7, 3,
// Vertical edges
0, 3,
1, 2,
5, 6,
4, 7
};
GLfloat arrow_vertices[] = {
0.0f, 0.0f, 0.0f, // Center of cube
0.5f, 0.0f, 0.0f, // Right face
-0.5f, 0.0f, 0.0f, // Left face
0.0f, 0.5f, 0.0f, // Top face
0.0f, -0.5f, 0.0f, // Bottom face
0.0f, 0.0f, 0.5f, // Front face
0.0f, 0.0f, -0.5f // Back face
};
GLuint arrow_indices[] = {
0, 1, // Center to Right face
0, 2, // Center to Left face
0, 3, // Center to Top face
0, 4, // Center to Bottom face
0, 5, // Center to Front face
0, 6 // Center to Back face
};
// Maps a 2D point on the screen to a 3D point on a virtual trackball (sphere)
void trackball_map(float x, float y, int width, int height, vec3 out)
{
// Normalize x and y to the range [-1, 1]
float normalized_x = (2.0f * x - width) / width;
float normalized_y = (height - 2.0f * y) / height; // Inverted y axis
// Compute the distance from the origin
float d = sqrtf(normalized_x * normalized_x + normalized_y * normalized_y);
// Clamp the distance to 1 (the edge of the virtual trackball)
if (d > 1.0f) d = 1.0f;
// Compute the z coordinate based on the spherical projection
float z = sqrtf(1.0f - d * d);
// The output 3D point
out[0] = normalized_x;
out[1] = normalized_y;
out[2] = z;
}
static gboolean on_mouse_move(GtkEventControllerMotion *controller, gdouble x, gdouble y, gpointer user_data)
{
GdkModifierType state = gtk_event_controller_get_current_event_state(GTK_EVENT_CONTROLLER(controller));
if (state & GDK_BUTTON1_MASK) { // If the left mouse button is held
// Define screen width and height (adjust accordingly)
int width = gtk_widget_get_width(window);
int height = gtk_widget_get_height(window);
// Variables to store trackball positions
vec3 last_pos, cur_pos;
// Map the last and current mouse positions to the virtual trackball
trackball_map(lastX, lastY, width, height, last_pos);
trackball_map(x, y, width, height, cur_pos);
lastX = x;
lastY = y;
// Calculate the rotation axis as the cross product between the last and current positions
vec3 axis;
glm_vec3_cross(last_pos, cur_pos, axis);
// Calculate the angle of rotation based on the distance moved by the mouse
float angle = acosf(fmin(1.0f, glm_vec3_dot(last_pos, cur_pos)));
// Apply the rotation using the axis-angle method
if (angle > 0.001f) { // Avoid very small rotations
mat4 temp_rotation;
glm_mat4_identity(temp_rotation); // Initialize a temporary rotation matrix
glm_rotate(temp_rotation, angle * sensitivity, axis); // Rotate around the computed axis
glm_mul(temp_rotation, rotation_matrix, rotation_matrix); // Accumulate the rotation
}
// Request to redraw the scene with the updated rotation
gtk_widget_queue_draw(GTK_WIDGET(user_data));
}
rotate = TRUE;
return TRUE;
}
static gboolean on_mouse_scroll(GtkEventControllerScroll *controller, gdouble dx, gdouble dy, gpointer user_data)
{
if (dy > 0)
fov -= 2.0f;
else if (dy < 0)
fov += 2.0f;
// Clamp the field of view to avoid extreme zooms
if (fov < 10.0f) fov = 10.0f;
if (fov > 90.0f) fov = 90.0f;
// Update the projection matrix with the new FOV
glm_perspective(glm_rad(fov), 800.0f / 600.0f, 0.1f, 100.0f, projection);
// Recalculate the view matrix for zoom (moving the camera in/out)
glm_lookat((vec3){0.0f, 0.0f, 20.0f}, (vec3){0.0f, 0.0f, 0.0f}, (vec3){0.0f, 1.0f, 0.0f}, view);
gtk_widget_queue_draw(GTK_WIDGET(user_data)); // Request to redraw the scene
return TRUE;
}
// Called when GtkApplication is activated
static void on_activate (GtkApplication *app, gpointer user_data)
{
g_message("[%s] activation in progress...",
__func__);
window = gtk_application_window_new(app);
gtk_window_set_title(GTK_WINDOW(window), "OpenGL 4.6 experiment");
gtk_window_set_default_size(GTK_WINDOW(window), 800, 600);
g_message("[%s] window created",
__func__);
GtkWidget *gl_area = gtk_gl_area_new();
gtk_gl_area_set_required_version(GTK_GL_AREA(gl_area), 4, 6);
gtk_gl_area_set_has_depth_buffer(GTK_GL_AREA(gl_area), TRUE);
gtk_gl_area_set_auto_render(GTK_GL_AREA(gl_area), TRUE);
g_message("[%s] GLArea created",
__func__);
g_signal_connect(gl_area, "realize", G_CALLBACK(on_realize), NULL);
g_signal_connect(gl_area, "render", G_CALLBACK(on_render), NULL);
g_message("[%s] GLArea signals linked",
__func__);
// Create and add motion event controller
GtkEventController *motion_controller = gtk_event_controller_motion_new();
g_signal_connect(motion_controller, "motion", G_CALLBACK(on_mouse_move), gl_area); // Pass gl_area as user_data
gtk_widget_add_controller(gl_area, GTK_EVENT_CONTROLLER(motion_controller));
// Create and add scroll event controller
GtkEventController *scroll_controller = gtk_event_controller_scroll_new(GTK_EVENT_CONTROLLER_SCROLL_BOTH_AXES);
g_signal_connect(scroll_controller, "scroll", G_CALLBACK(on_mouse_scroll), gl_area); // Pass gl_area as user_data
gtk_widget_add_controller(gl_area, GTK_EVENT_CONTROLLER(scroll_controller));
g_message("[%s] Mouse events now linked to signals",
__func__);
gtk_window_set_child(GTK_WINDOW(window), gl_area);
gtk_widget_set_visible(window, TRUE);
g_message("[%s] GLArea set visible",
__func__);
}
// Called when GtkGLArea is realized (OpenGL context is created)
static void on_realize(GtkGLArea *area)
{
g_message("[%s] GLArea realization in progress...", __func__);
gtk_gl_area_make_current(GTK_GL_AREA(area));
g_message("[%s] GLArea made current", __func__);
if (gtk_gl_area_get_error(GTK_GL_AREA(area)) != NULL) return;
g_message("[%s] OpenGL context successfully created", __func__);
// Enable depth testing
glEnable(GL_DEPTH_TEST);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glDepthFunc(GL_LESS);
// Initialize cglm projection and view matrices
glm_mat4_identity(projection);
glm_perspective(glm_rad(60.0f), 800.0f / 600.0f, 0.1f, 100.0f, projection);
glm_mat4_identity(view);
glm_lookat((vec3){0.0f, 0.0f, 20.0f}, (vec3){0.0f, 0.0f, 0.0f}, (vec3){0.0f, 1.0f, 0.0f}, view);
// Initialize rotation matrix
glm_mat4_identity(rotation_matrix); // Initialize to identity
// Create shader program and setup buffers
shader_program = create_shader_program();
setup_buffers();
setup_arrow_buffers();
setup_instance_data();
setup_arrow_instance_data(); // Setup arrow data
// Set the background color
glClearColor(0.1f, 0.1f, 0.1f, 1.0f); // Dark gray background
glLineWidth(2.0f); // Increase the line width for cube edges
}
// Called on each frame to render the scene
static gboolean on_render(GtkGLArea *area, GdkGLContext *context)
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LESS);
glUseProgram(shader_program);
// Pass projection and view matrices to the shader
GLint projectionLoc = glGetUniformLocation(shader_program, "projection");
GLint viewLoc = glGetUniformLocation(shader_program, "view");
glUniformMatrix4fv(projectionLoc, 1, GL_FALSE, (const GLfloat *)projection);
glUniformMatrix4fv(viewLoc, 1, GL_FALSE, (const GLfloat *)view);
// Update instance matrices with the accumulated rotation matrix
if (rotate) {
for (unsigned int i = 0; i < num_instances; i++) {
mat4 model;
glm_mat4_copy(rotation_matrix, model); // Copy the accumulated rotation matrix
// Combine the rotation with the instance's individual matrix
glm_mul(model, instance_matrices[i], instance_matrices[i]);
}
rotate = FALSE;
}
// Update the instance matrix buffer with the modified instance matrices
glBindBuffer(GL_ARRAY_BUFFER, instance_vbo);
glBufferSubData(GL_ARRAY_BUFFER, 0, num_instances * sizeof(mat4), instance_matrices);
// Draw the cubes with the updated instance matrices
glUniform4f(glGetUniformLocation(shader_program, "FragColor"), 0.4f, 0.6f, 0.9f, 1.0f);
glBindVertexArray(vao);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);
glDrawElementsInstanced(GL_TRIANGLES, 36, GL_UNSIGNED_INT, 0, num_instances);
// Draw the cube edges
glUniform4f(glGetUniformLocation(shader_program, "FragColor"), 1.0f, 1.0f, 1.0f, 1.0f);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, edge_ebo);
glDrawElementsInstanced(GL_LINES, 24, GL_UNSIGNED_INT, 0, num_instances);
glDisable(GL_DEPTH_TEST);
// Set arrow color (e.g., red) and draw arrows
glUniform4f(glGetUniformLocation(shader_program, "FragColor"), 1.0f, 0.0f, 0.0f, 1.0f); // Red, opaque
glBindVertexArray(arrow_vao);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, arrow_ebo);
glDrawElementsInstanced(GL_LINES, 12, GL_UNSIGNED_INT, 0, num_instances);
glEnable(GL_DEPTH_TEST);
glBindVertexArray(0);
// Reset the rotation matrix to prevent continuous accumulation
glm_mat4_identity(rotation_matrix);
return TRUE;
}
// Create the shader program
GLuint create_shader_program ()
{
GLuint vertex_shader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertex_shader, 1, &vertex_shader_src, NULL);
glCompileShader(vertex_shader);
GLuint fragment_shader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragment_shader, 1, &fragment_shader_src, NULL);
glCompileShader(fragment_shader);
GLuint program = glCreateProgram();
glAttachShader(program, vertex_shader);
glAttachShader(program, fragment_shader);
glLinkProgram(program);
glDeleteShader(vertex_shader);
glDeleteShader(fragment_shader);
return program;
}
// Setup OpenGL buffers (VBO, VAO, EBO for cubes and edges)
void setup_buffers ()
{
glGenVertexArrays(1, &vao);
glBindVertexArray(vao);
// Vertex buffer
glGenBuffers(1, &vbo);
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// Face Index buffer (EBO for cube faces)
glGenBuffers(1, &ebo);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
// Vertex attributes
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
// Edge Index buffer (EBO for cube edges)
glGenBuffers(1, &edge_ebo);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, edge_ebo);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(edge_indices), edge_indices, GL_STATIC_DRAW);
glBindVertexArray(0);
}
void setup_arrow_buffers() {
glGenVertexArrays(1, &arrow_vao);
glBindVertexArray(arrow_vao);
// Vertex buffer for arrows
glGenBuffers(1, &arrow_vbo);
glBindBuffer(GL_ARRAY_BUFFER, arrow_vbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(arrow_vertices), arrow_vertices, GL_STATIC_DRAW);
// Index buffer for arrows
glGenBuffers(1, &arrow_ebo);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, arrow_ebo);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(arrow_indices), arrow_indices, GL_STATIC_DRAW);
// Vertex attributes
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
glBindVertexArray(0);
}
// Setup instance data for cubes (random positions)
void setup_instance_data ()
{
g_message("[%s] setting up cube instance data...",
__func__);
srand(time(NULL));
g_message("[%s] random seed initialized",
__func__);
glGenBuffers(1, &instance_vbo);
glBindBuffer(GL_ARRAY_BUFFER, instance_vbo);
g_message("[%s] instance_vbo buffer bound",
__func__);
for (unsigned int i = 0; i < num_instances; i++) {
glm_mat4_identity(instance_matrices[i]);
// Randomize positions
vec3 position = {
(float)(rand() % N_SPACE_SIZE) - 10.0f, // Random x from -10 to 10
(float)(rand() % N_SPACE_SIZE) - 10.0f, // Random y from -10 to 10
(float)(rand() % N_SPACE_SIZE) - 10.0f // Random z from -10 to 10
};
glm_translate(instance_matrices[i], position);
}
g_message("[%s] generated %d cube instances",
__func__,
num_instances);
// Pass the instance matrices to the instance VBO
glBufferData(GL_ARRAY_BUFFER, num_instances * sizeof(mat4), instance_matrices, GL_STATIC_DRAW);
g_message("[%s] instance matrices passed to VBO",
__func__);
// Setup vertex attribute for the model matrix (location = 2)
// Setup vertex attribute for the model matrix (location = 2)
glBindVertexArray(vao);
for (int i = 0; i < 4; i++) {
glVertexAttribPointer(2 + i, 4, GL_FLOAT, GL_FALSE, sizeof(mat4), (void*)(sizeof(vec4) * i));
glEnableVertexAttribArray(2 + i);
glVertexAttribDivisor(2 + i, 1); // Tell OpenGL this is per-instance data
}
glBindVertexArray(0);
g_message("[%s] finalized model matrix vertex attribute ",
__func__);
}
void setup_arrow_instance_data() {
// Define indices for the arrows (lines from center to cube faces)
GLuint arrow_indices[] = {
0, 1, // Arrow pointing to right face
0, 2, // Arrow pointing upwards
0, 3, // Arrow pointing to front face
0, 4, // Arrow pointing to left face
0, 5, // Arrow pointing downwards
0, 6 // Arrow pointing to back face
};
// Generate Vertex Array Object for arrows
glGenVertexArrays(1, &arrow_vao);
glBindVertexArray(arrow_vao);
// Generate Vertex Buffer Object for arrows
GLuint arrow_vbo;
glGenBuffers(1, &arrow_vbo);
glBindBuffer(GL_ARRAY_BUFFER, arrow_vbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(arrow_vertices), arrow_vertices, GL_STATIC_DRAW);
// Generate Element Buffer Object for arrows
GLuint arrow_ebo;
glGenBuffers(1, &arrow_ebo);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, arrow_ebo);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(arrow_indices), arrow_indices, GL_STATIC_DRAW);
// Define arrow vertex attributes
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
// Arrow instance matrices (positions) -- same as cubes
glGenBuffers(1, &arrow_instance_vbo);
glBindBuffer(GL_ARRAY_BUFFER, arrow_instance_vbo);
glBufferData(GL_ARRAY_BUFFER, num_instances * sizeof(mat4), instance_matrices, GL_STATIC_DRAW);
// Define vertex attribute for arrow instance matrix
for (int i = 0; i < 4; i++) {
glVertexAttribPointer(2 + i, 4, GL_FLOAT, GL_FALSE, sizeof(mat4), (void*)(sizeof(vec4) * i));
glEnableVertexAttribArray(2 + i);
glVertexAttribDivisor(2 + i, 1); // Make this per-instance data
}
glBindVertexArray(0);
}
// Main Function
int main (int argc, char *argv[])
{
GtkApplication *app;
int status;
g_message("[%s] welcome in this experiment with OpenGL 4.6 in GTK4",
__func__);
// Create a new GtkApplication
app = gtk_application_new("org.example.instanced_cubes", G_APPLICATION_DEFAULT_FLAGS);
g_message("[%s] application created",
__func__);
// Connect the "activate" signal to the activate callback
g_signal_connect(app, "activate", G_CALLBACK(on_activate), NULL);
g_message("[%s] activate callback linked",
__func__);
// Run the application
status = g_application_run(G_APPLICATION(app), argc, argv);
g_message("[%s] application terminated",
__func__);
// Free the application object
g_object_unref(app);
g_message("[%s] bye!",
__func__);
return status;
}