diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..7d81679 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,44 @@ +# CMake entry point +cmake_minimum_required (VERSION 3.27.1) +project(OpenGL_Analog_Clock C) +cmake_policy(SET CMP0072 NEW) + +find_package(OpenGL REQUIRED) +find_package(GLEW REQUIRED) +find_package(glfw3 REQUIRED) + +include_directories( + include/ + . +) + +set(ALL_LIBS + ${OPENGL_LIBRARY} + glfw + GLEW + m +) + +set(CMAKE_C_FLAGS "-O0 -ggdb -Wall") + +add_definitions( + -DTW_STATIC + -DTW_NO_LIB_PRAGMA + -DTW_NO_DIRECT3D + -DGLEW_STATIC + -D_CRT_SECURE_NO_WARNINGS +) + +add_executable( + cx + src/main.c + src/cx.c + src/tensor.c + src/model.c + src/shader.c +) + +target_link_libraries( + cx + ${ALL_LIBS} +) diff --git a/include/cx.h b/include/cx.h new file mode 100644 index 0000000..3a04775 --- /dev/null +++ b/include/cx.h @@ -0,0 +1,30 @@ +#ifndef CX_H +#define CX_H + +// Include standard headers +#include +#include +#include +#include +#include +#include + +// Include GLEW +#include + +// Include GLFW +#include + +// Include project headers +#include +#include +#include +#include + +// Declare functions + +int cx_glinit(GLFWwindow **); +int cx_glrun(GLFWwindow *); + +#endif + diff --git a/include/model.h b/include/model.h new file mode 100644 index 0000000..126eb3b --- /dev/null +++ b/include/model.h @@ -0,0 +1,12 @@ +#ifndef MODEL_LOADER_H +#define MODEL_LOADER_H + +typedef struct _model { + GLfloat *object; + size_t bufsize; +} Model; + +Model * model_load(const char *); + +#endif + diff --git a/include/shader.h b/include/shader.h new file mode 100644 index 0000000..9d5eb58 --- /dev/null +++ b/include/shader.h @@ -0,0 +1,7 @@ +#ifndef SHADER_H +#define SHADER_H + +int LoadShaders(GLuint *, const char *, const char *); + +#endif + diff --git a/include/tensor.h b/include/tensor.h new file mode 100644 index 0000000..ca01589 --- /dev/null +++ b/include/tensor.h @@ -0,0 +1,12 @@ +#ifndef MATRIX_H +#define MATRIX_H + +GLfloat *matrix_new(void); + +GLfloat *matrix_multip(GLfloat *, GLfloat *); + +GLfloat *matrix_transform(GLfloat *, int, + GLfloat *); + +#endif + diff --git a/shaders/SimpleFragmentShader.fragmentshader b/shaders/SimpleFragmentShader.fragmentshader new file mode 100644 index 0000000..c5f05be --- /dev/null +++ b/shaders/SimpleFragmentShader.fragmentshader @@ -0,0 +1,12 @@ +#version 330 core + +in float colorF; +out vec3 color; + +void main() { + if (colorF == 0) + color = vec3(1, 1, 1); + else + color = vec3(0, 0, 0); +} + diff --git a/shaders/SimpleVertexShader.vertexshader b/shaders/SimpleVertexShader.vertexshader new file mode 100644 index 0000000..2f13fbf --- /dev/null +++ b/shaders/SimpleVertexShader.vertexshader @@ -0,0 +1,15 @@ +#version 330 core + +// Input vertex data, different for all executions of this shader. +layout(location = 0) in vec4 position; +out float colorF; + +void main() { + if (position.z <= 0.0) + colorF = 0; + else + colorF = 1; + + gl_Position = position; +} + diff --git a/src/cx.c b/src/cx.c new file mode 100644 index 0000000..198819f --- /dev/null +++ b/src/cx.c @@ -0,0 +1,174 @@ +#include + +int cx_glinit(GLFWwindow **window) { + // Initialise GLFW + if(!glfwInit()) { + fprintf(stderr, "Failed to initialize GLFW\n"); + getchar(); + return -1; + } + + glfwWindowHint(GLFW_SAMPLES, 4); + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); + glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // To make MacOS happy; should not be needed + glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); + + // Open a window and create its OpenGL context + *window = glfwCreateWindow(1280, 720, "OpenGL Clock", NULL, NULL); + if (*window == NULL) { + fprintf(stderr, "Failed to open GLFW window.\n"); + glfwTerminate(); + return -1; + } + + glfwMakeContextCurrent(*window); + + // Initialize GLEW + if (glewInit() != GLEW_OK) { + fprintf(stderr, "Failed to initialize GLEW\n"); + glfwTerminate(); + return -1; + } + + // Ensure we can capture the escape key being pressed below + glfwSetInputMode(*window, GLFW_STICKY_KEYS, GL_TRUE); + + // Dark grey background + glClearColor(0.15f, 0.15f, 0.15f, 0.0f); + + return 0; +} + +int cx_glrun(GLFWwindow *window) { + GLuint VertexArrayID; + GLuint programID; + + + glGenVertexArrays(1, &VertexArrayID); + glBindVertexArray(VertexArrayID); + + // Create and compile our GLSL program from the shaders + if (LoadShaders(&programID, + "../shaders/SimpleVertexShader.vertexshader", + "../shaders/SimpleFragmentShader.fragmentshader")) { + fprintf(stderr, "Could not load shaders.\n"); + return -1; + } + + Model *model; + model = model_load("../triangle.obj"); + + GLuint vertexbuffer; + glGenBuffers(1, &vertexbuffer); + glBindBuffer(GL_ARRAY_BUFFER, vertexbuffer); + glBufferData(GL_ARRAY_BUFFER, model->bufsize*4*sizeof(GLfloat), model->object, GL_STATIC_DRAW); + + double xpos, ypos; + glfwGetCursorPos(window, &xpos, &ypos); + + GLfloat *matrix; + GLfloat *temp = matrix_new(); + + matrix = temp; + time_t t = time(NULL); + + temp = matrix_new(); + + temp[0] = cos(M_PI*2/60*(t%60)); + temp[4] = -sin(M_PI*2/60*(t%60)); + temp[1] = sin(M_PI*2/60*(t%60)); + temp[5] = cos(M_PI*2/60*(t%60)); + + matrix = temp; + t /= 60; + + + GLfloat *projection = matrix_new(); + GLfloat *buffer; + projection[14] = -1.0f; + buffer = matrix_new(); + buffer[0] = (GLfloat)9/16; + temp = matrix_multip(projection, buffer); + free(buffer); + free(projection); + projection = temp; + temp = malloc(model->bufsize * 4 * sizeof(GLfloat)); + buffer = malloc(model->bufsize * 4 * sizeof(GLfloat)); + memcpy(temp, model->object, model->bufsize * 4 * sizeof(GLfloat)); + + GLfloat *orig; + orig = malloc(model->bufsize * 4 * sizeof(GLfloat)); + memcpy(orig, model->object, model->bufsize * 4 * sizeof(GLfloat)); + + do { + // Clear the screen. It's not mentioned before Tutorial 02, + // but it can cause flickering, so it's there nonetheless. + glClear(GL_COLOR_BUFFER_BIT); + + // Use our shader + glUseProgram(programID); + time_t t = time(NULL); + + GLfloat *temp_mat; + temp_mat = matrix_new(); + + temp_mat[0] = cos(M_PI*2/60*(t%60)); + temp_mat[4] = -sin(M_PI*2/60*(t%60)); + temp_mat[1] = sin(M_PI*2/60*(t%60)); + temp_mat[5] = cos(M_PI*2/60*(t%60)); + + matrix = temp_mat; + t /= 60; + + for (int i = 2; i < 5; i++) { + GLfloat *slice; + slice = matrix_transform(orig, model->bufsize, matrix); + memcpy(temp, slice, model->bufsize* 4 * sizeof(GLfloat)); + free(slice); + } + free(buffer); + + buffer = matrix_transform(temp, model->bufsize, projection); + memcpy(model->object, buffer, model->bufsize * 4 * sizeof(GLfloat)); + + GLuint vertexbuffer; + glGenBuffers(1, &vertexbuffer); + glBindBuffer(GL_ARRAY_BUFFER, vertexbuffer); + glBufferData(GL_ARRAY_BUFFER, model->bufsize * 4 * sizeof(GLfloat), model->object, GL_STATIC_DRAW); + + + // 1rst attribute buffer : vertices + glEnableVertexAttribArray(0); + glBindBuffer(GL_ARRAY_BUFFER, vertexbuffer); + glVertexAttribPointer( + 0, // attribute 0. No particular reason for 0, but must match the layout in the shader. + 4, // size + GL_FLOAT, // type + GL_FALSE, // normalized? + 0, // stride + NULL // array buffer offset + ); + + + // Draw! + glDrawArrays(GL_TRIANGLES, 0, model->bufsize); // 3 indices starting at 0 -> 1 triangle + + glDisableVertexAttribArray(0); + + // Swap buffers + glfwSwapBuffers(window); + glfwPollEvents(); + + // Check if the ESC key was pressed or the window was closed + usleep(1000000/60); + } while(glfwGetKey(window, GLFW_KEY_ESCAPE) != GLFW_PRESS && + !glfwWindowShouldClose(window)); + + // Close OpenGL window and terminate GLFW + free(matrix); + glfwTerminate(); + + return 0; +} + diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..f96b306 --- /dev/null +++ b/src/main.c @@ -0,0 +1,18 @@ +// Include standard headers +#include +#include + + +// Include project headers +#include + + +int +main(void) { + GLFWwindow *window; + if (cx_glinit(&window)) { + return -1; + } + + return cx_glrun(window); +} diff --git a/src/model.c b/src/model.c new file mode 100644 index 0000000..99d87b4 --- /dev/null +++ b/src/model.c @@ -0,0 +1,70 @@ +#include + +Model * +model_new(size_t size) { + Model *model = calloc(1, sizeof(Model)); + model->object = calloc((size ? size : 1) *4 , sizeof(GLfloat)); + model->bufsize = size; + return model; +} + +void +model_free(Model *self) { + free(self->object); + free(self); +} + +Model * +model_load(const char *path) { + Model *model; + GLfloat *vertices; + int *faces; + size_t vertcount, facecount; + char type, check; + FILE *f; + + f = fopen(path, "r"); + vertices = malloc(3 * sizeof(GLfloat)); + faces = malloc(3*sizeof(int *)); + type = 0; + vertcount = 0; + facecount = 0; + + do { + check = fscanf(f, "%c", &type); + if (check == EOF) { + break; + } + else if (type == 'v') { + vertices = realloc(vertices, (vertcount+1)*3*sizeof(GLfloat)); + check = fscanf(f, "%f %f %f\n", &(vertices[vertcount*3]), + &(vertices[vertcount*3+1]), + &(vertices[vertcount*3+2])); + vertcount++; + } + else if (type == 'f') { + faces = realloc(faces, (facecount+1)*3*sizeof(GLfloat)); + check = fscanf(f, "%d %d %d\n", &(faces[facecount*3]), + &(faces[facecount*3+1]), + &(faces[facecount*3+2])); + facecount++; + } + else { + check = fscanf(f, "%*[^\n]\n"); + continue; + } + } while(check != EOF); + + model = model_new(facecount*3); + for (int i = 0; i < facecount; i++) { + for (int j = 0; j < 3; j++) { + for (int k = 0; k < 3; k++) { + model->object[i*12+j*4+k] = vertices[(faces[i*3+j]-1)*3+k]; + } + model->object[i*12+j*4+3] = 1; + } + model->bufsize = facecount*3; + } + return model; +} + diff --git a/src/shader.c b/src/shader.c new file mode 100644 index 0000000..b57930b --- /dev/null +++ b/src/shader.c @@ -0,0 +1,118 @@ +#include "cx.h" + +static int +load_code(const char *filepath, char **code) { + FILE *file; + int cursor; + int c; + + file = fopen(filepath, "r"); + if (file == NULL) { + fprintf(stderr, "Could not open %s.\n", filepath); + return 1; + } + + *code = malloc(256 * sizeof(char)); + if (code == NULL) { + fprintf(stderr, "Out of memory"); + return 1; + } + cursor = 0; + + while ((c = fgetc(file)) != EOF) { + (*code)[cursor] = c; + cursor++; + } + (*code)[cursor] = '\0'; + + return 0; +} + +static int +compile_code(const char *filepath, const char *code, + GLuint ShaderID, GLint *Result) { + int InfoLogLength; + // Compile Shader + printf("Compiling shader : %s\n", filepath); + glShaderSource(ShaderID, 1, (const char **)&code, NULL); + glCompileShader(ShaderID); + + // Check Shader + glGetShaderiv(ShaderID, GL_COMPILE_STATUS, Result); + glGetShaderiv(ShaderID, GL_INFO_LOG_LENGTH, &InfoLogLength); + if (InfoLogLength > 0) { + char *ShaderErrorMessage = malloc(InfoLogLength+1); + glGetShaderInfoLog(ShaderID, InfoLogLength, NULL, ShaderErrorMessage); + printf("%s\n", ShaderErrorMessage); + free(ShaderErrorMessage); + return -1; + } + return 0; +} + +int +LoadShaders(GLuint *programID, const char *vertex_file_path, + const char *fragment_file_path) { + int retval = -1; + char *vertex_code = NULL; + char *fragment_code = NULL; + + // Create the shaders + GLuint VertexShaderID = glCreateShader(GL_VERTEX_SHADER); + GLuint FragmentShaderID = glCreateShader(GL_FRAGMENT_SHADER); + + // Read the Vertex Shader code from the file + if (load_code(vertex_file_path, (char **)&vertex_code)) { + goto end; + } + + // Read the Fragment Shader code from the file + if (load_code(fragment_file_path, (char **)&fragment_code)) { + goto end; + } + + GLint Result = GL_FALSE; + + // Compile Vertex Shader + if (compile_code(vertex_file_path, vertex_code, VertexShaderID, &Result)) { + goto end; + } + + // Compile Fragment Shader + if (compile_code(fragment_file_path, fragment_code, FragmentShaderID, &Result)) { + goto end; + } + + // Link the program + printf("Linking program\n"); + *programID = glCreateProgram(); + glAttachShader(*programID, VertexShaderID); + glAttachShader(*programID, FragmentShaderID); + glLinkProgram(*programID); + + GLint InfoLogLength; + // Check the program + glGetProgramiv(*programID, GL_LINK_STATUS, &Result); + glGetProgramiv(*programID, GL_INFO_LOG_LENGTH, &InfoLogLength); + if (InfoLogLength > 0) { + char *ProgramErrorMessage = malloc(InfoLogLength+1); + glGetProgramInfoLog(*programID, InfoLogLength, NULL, ProgramErrorMessage); + printf("%s\n", ProgramErrorMessage); + } + + + glDetachShader(*programID, VertexShaderID); + glDetachShader(*programID, FragmentShaderID); + + glDeleteShader(VertexShaderID); + glDeleteShader(FragmentShaderID); + + // If code got here, it means it was successful + retval = 0; + +end: + free(vertex_code); + free(fragment_code); + return retval; +} + diff --git a/src/tensor.c b/src/tensor.c new file mode 100644 index 0000000..6d36517 --- /dev/null +++ b/src/tensor.c @@ -0,0 +1,63 @@ +#include "cx.h" + +GLfloat * +matrix_new() { + GLfloat *mat; + + mat = calloc(16, sizeof(GLfloat)); + + for (int i = 0; i < 4; i++) { + mat[i*4+i] = 1; + } + + return mat; +} + +GLfloat * +matrix_multip(GLfloat *mat1, GLfloat *mat2) { + GLfloat *result; + GLfloat dot_prod; + + result = matrix_new(); + + for (int i = 0; i < 4; i++) { + + for (int j = 0; j < 4; j++) { + dot_prod = 0; + for (int k = 0; k < 4; k++) { + dot_prod += mat1[i*4+k] * mat2[j+k*4]; + } + result[j+i*4] = dot_prod; + } + } + + return result; +} + +GLfloat * +matrix_transform(GLfloat *vects, int vectcount, + GLfloat *mat) { + GLfloat dot_prod; + GLfloat *result; + + result = calloc(vectcount*4, sizeof(GLfloat)); + + + for (int k = 0; k < vectcount; k++) { + for (int j = 0; j < 4; j++) { + dot_prod = 0; + for (int i = 0; i < 4; i++) { + dot_prod += vects[k*4+i] * mat[i+j*4]; + } + result[j+k*4] = dot_prod; + } + if (result[k*4+3] != 0.0f) { + GLfloat div = result[k*4+3]; + for (int i = 0; i < 4; i++) { + result[k*4+i] /= div; + } + } + } + return result; +} + diff --git a/triangle.obj b/triangle.obj new file mode 100644 index 0000000..7dac3b1 --- /dev/null +++ b/triangle.obj @@ -0,0 +1,7 @@ +o Triangle.001 +v -0.250000 -0.200000 0.000000 +v 0.250000 -0.200000 0.000000 +v 0.000000 0.400000 0.000000 +s off +f 1 2 3 +