// gcc `pkg-config --cflags --libs gtk4 epoxy glib-2.0` -lm -o instanced_cubes instanced_cubes.c #include #include #include #include #include // General GDK events and types #include // 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; }