diff --git a/instanced_cubes.c b/instanced_cubes.c new file mode 100644 index 0000000..3c70128 --- /dev/null +++ b/instanced_cubes.c @@ -0,0 +1,573 @@ +#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; +}