#ifndef COMMON_H #define COMMON_H #define STB_IMAGE_IMPLEMENTATION #include "stb_image.h" #define TINYOBJ_LOADER_C_IMPLEMENTATION #include "tinyobj_loader_c.h" #include #include #include #include #include "pwyazh.h" void die(const char *fmt, ...) { fprintf(stderr, "error: "); va_list args; va_start(args, fmt); vprintf(fmt, args); va_end(args); fprintf(stderr, "\n"); exit(1); } void info(const char *fmt, ...) { printf("info: "); va_list args; va_start(args, fmt); vprintf(fmt, args); va_end(args); printf("\n"); } char *read_entire_file(const char *filename) { char *result = 0; if (!filename) return(result); FILE *file = fopen(filename, "rb"); if (!file) return(result); fseek(file, 0, SEEK_END); long file_size = ftell(file); fseek(file, 0, SEEK_SET); result = malloc(file_size+1); fread(result, file_size, 1, file); fclose(file); result[file_size] = 0; return(result); } void *mmap_file(size_t *len, const char *filename) { int fd = open(filename, O_RDONLY); if (fd == -1) { perror("open"); return NULL; } struct stat sb; if (fstat(fd, &sb) == -1) { perror("fstat"); return NULL; } if (!S_ISREG(sb.st_mode)) { fprintf(stderr, "%s is not a file\n", filename); return NULL; } char *p = (char*)mmap(0, sb.st_size, PROT_READ, MAP_SHARED, fd, 0); if (p == MAP_FAILED) { perror("mmap"); return NULL; } if (close(fd) == -1) { perror("close"); return NULL; } (*len) = sb.st_size; return p; } MAT4 mat4_change_basis(V3F x, V3F y, V3F z) { MAT4 result = mat4_identity(); result.m0 = v4f(x.x, x.y, x.z, 0.0f); result.m1 = v4f(y.x, y.y, y.z, 0.0f); result.m2 = v4f(z.x, z.y, z.z, 0.0f); return(result); } MAT4 mat4_make_rotate(V3F angles) { F32 angle, cx, sx, cy, sy, cz, sz; MAT4 result; V3F newx, newy, newz; angle = DEG2RAD*angles.x; cx = f32_cos(angle); sx = f32_sin(angle); angle = DEG2RAD*angles.y; cy = f32_cos(angle); sy = f32_sin(angle); angle = DEG2RAD*angles.z; cz = f32_cos(angle); sz = f32_sin(angle); newx = v3f(cy*cz, sx*sy*cz+cx*sz, -cx*sy*cz+sx*sz); newy = v3f(-cy*sz, -sx*sy*sz+cx*cz, cx*sy*sz+sx*cz); newz = v3f(sy, -sx*cy, cx*cy); result = mat4_change_basis(newx, newy, newz); return(result); } MAT4 mat4_rotate_angles(MAT4 source, V3F angles) { MAT4 rotate = mat4_make_rotate(angles); MAT4 result = mat4_mul(rotate, source); return(result); } enum KeyState_Enum { KeyState_RELEASE = 0, KeyState_PRESS = 1 }; typedef struct { enum KeyState_Enum last; enum KeyState_Enum state; } Key; typedef struct { Key move_right; Key move_forward; Key move_left; Key move_backward; Key move_up; Key move_down; Key jump; Key action_right; Key action_up; Key action_left; Key action_down; Key exit; V2F last_mouse_pos; V2F mouse_offset; B32 first_mouse; } Input; typedef struct { V3F pos; F32 fovx; F32 near; F32 far; /* NOTE(pryazha): In degrees */ F32 yaw; F32 pitch; } Camera; typedef struct { Arena *arena; Input input; Camera camera; GLFWwindow *window; S32 width; S32 height; S32 debug; } state_t; Input input_init() { Input input = {0}; input.first_mouse = 1; return(input); } state_t init_state(S32 width, S32 height, S32 debug) { state_t state = { .arena = arena_alloc(Megabytes(64)), .input = input_init(), .camera = { .pos = v3f(0.0f, 1.0f, 5.0f), .fovx = 90.0f, .near = 0.1f, .far = 100.0f, .yaw = 0.0f, .pitch = 0.0f }, .width = width, .height = height, .debug = debug }; return state; } void read_entire_file_mmap(void* ctx, const char* filename, const int is_mtl, const char* obj_filename, char** data, size_t* len) { if (ctx) { printf("info: user context was provided, but not processed\n"); } if (!filename) { fprintf(stderr, "[ERROR]: Filename not provided (null)\n"); *data = 0; *len = 0; return; } if (is_mtl) printf("info: %s is material\n", filename); if (obj_filename) printf("info: obj_filename %s\n", obj_filename); size_t data_len = 0; *data = mmap_file(&data_len, filename); *len = data_len; } Mesh *mesh_gen_quad(Arena *arena) { Vertex verts[4] = { vertex(v3f( 1.0f, 1.0f, 0.0f), v3f_zero(), v2f(1.0f, 1.0f)), vertex(v3f(-1.0f, 1.0f, 0.0f), v3f_zero(), v2f(0.0f, 1.0f)), vertex(v3f(-1.0f, -1.0f, 0.0f), v3f_zero(), v2f(0.0f, 0.0f)), vertex(v3f( 1.0f, -1.0f, 0.0f), v3f_zero(), v2f(1.0f, 0.0f)), }; U32 ids[6] = { 0, 1, 2, 0, 2, 3 }; Mesh *mesh = mesh_init(arena, verts, 4, ids, 6); return mesh; } Mesh *mesh_load_obj(Arena *arena, const char *filename) { tinyobj_attrib_t attrib; tinyobj_shape_t *shapes = 0; tinyobj_material_t *materials = 0; size_t num_shapes, num_materials, num_triangles; size_t i, j, face_offset; U32 flags; Mesh *mesh = 0; flags = TINYOBJ_FLAG_TRIANGULATE; S32 status = tinyobj_parse_obj(&attrib, &shapes, &num_shapes, &materials, &num_materials, filename, read_entire_file_mmap, 0, flags); if (status != TINYOBJ_SUCCESS) { fprintf(stderr, "[ERROR]: Failed to parse \"%s\"\n", filename); if (status == TINYOBJ_ERROR_INVALID_PARAMETER) fprintf(stderr, "[ERROR]: TINYOBJ_ERROR_INVALID_PARAMETER\n"); return(mesh); } num_triangles = attrib.num_face_num_verts; face_offset = 0; Vertex *vertices = arena_push_size(arena, sizeof(Vertex)*num_triangles*3); U32 *indices = arena_push_size(arena, num_triangles*3*sizeof(U32)); U32 vertex_count = 0; U32 index_count = 0, index_index = 0; for (i = 0; i < attrib.num_face_num_verts; ++i) { tinyobj_vertex_index_t idx; V3F pos, normal; V2F tex_coords; Assert(attrib.face_num_verts[i]%3 == 0); Assert(attrib.face_num_verts[i]/3 > 0); Assert(attrib.num_texcoords); for (j = 0; j < 3; ++j) { idx = attrib.faces[face_offset+j]; Assert(idx.v_idx >= 0); pos = v3f(attrib.vertices[3*idx.v_idx+0], attrib.vertices[3*idx.v_idx+1], attrib.vertices[3*idx.v_idx+2]); Assert(idx.vn_idx < (S32)attrib.num_normals); normal = v3f(attrib.normals[3*idx.vn_idx+0], attrib.normals[3*idx.vn_idx+1], attrib.normals[3*idx.vn_idx+2]); Assert(idx.vt_idx < (S32)attrib.num_texcoords); tex_coords = v2f(attrib.texcoords[2*idx.vt_idx+0], attrib.texcoords[2*idx.vt_idx+1]); vertices[vertex_count++] = vertex(pos, normal, tex_coords); Assert(index_count < attrib.num_faclibe_num_verts); indices[index_index++] = index_count++; } face_offset += 3; } mesh = mesh_init(arena, vertices, vertex_count, indices, num_triangles*3); tinyobj_attrib_free(&attrib); tinyobj_shapes_free(shapes, num_shapes); tinyobj_materials_free(materials, num_materials); return(mesh); } U32 compile_shader(GLenum type, const char *filename) { const char *source = (const char *)read_entire_file(filename); if (!source) { fprintf(stderr, "[ERROR]: Failed to read the file \"%s\"\n", filename); return(0); } U32 shader = glCreateShader(type); glShaderSource(shader, 1, &source, 0); free((void *)source); glCompileShader(shader); S32 status; glGetShaderiv(shader, GL_COMPILE_STATUS, &status); if (status == GL_FALSE) { char log[512]; glGetShaderInfoLog(shader, 512, 0, log); fprintf(stderr, "error: failed to compile: \"%s\"\n%s", filename, log); } else { fprintf(stdout, "info: \"%s\" compiled successfuly.\n", filename); } return(shader); } U32 load_shader(const char *vert_filename, const char *frag_filename) { U32 vert = compile_shader(GL_VERTEX_SHADER, vert_filename); U32 frag = compile_shader(GL_FRAGMENT_SHADER, frag_filename); U32 program = glCreateProgram(); glAttachShader(program, vert); glAttachShader(program, frag); glLinkProgram(program); S32 success; glGetProgramiv(program, GL_LINK_STATUS, &success); if (success == GL_FALSE) { char log[512]; glGetProgramInfoLog(program, 512, 0, log); fprintf(stderr, "error: failed to link program:\n%s\n", log); } else { fprintf(stdout, "info: compiled successully\n\n"); } glDeleteShader(vert); glDeleteShader(frag); return(program); } U32 load_shader_geom(const char *vert_filename, const char *frag_filename, const char *geom_filename) { U32 vert, frag, geom; vert = compile_shader(GL_VERTEX_SHADER, vert_filename); frag = compile_shader(GL_FRAGMENT_SHADER, frag_filename); if (geom_filename) geom = compile_shader(GL_GEOMETRY_SHADER, geom_filename); U32 program = glCreateProgram(); glAttachShader(program, vert); glAttachShader(program, frag); if (geom_filename) glAttachShader(program, geom); glLinkProgram(program); S32 success; glGetProgramiv(program, GL_LINK_STATUS, &success); if (success == GL_FALSE) { char log[512]; glGetProgramInfoLog(program, 512, 0, log); fprintf(stderr, "error: failed to link shader program:\n%s\n", log); } glDeleteShader(vert); glDeleteShader(frag); if (geom_filename) glDeleteShader(geom); return(program); } void shader_set_3f(U32 shader_program, char *uniform_name, F32 x, F32 y, F32 z) { U32 uniform_location = glGetUniformLocation(shader_program, uniform_name); glUniform3f(uniform_location, x, y, z); } void shader_set_1f(U32 shader_program, char *uniform_name, F32 value) { U32 uniform_location = glGetUniformLocation(shader_program, uniform_name); glUniform1f(uniform_location, value); } void shader_set_3fv(U32 shader_program, char *uniform_name, V3F value) { U32 uniform_location = glGetUniformLocation(shader_program, uniform_name); glUniform3fv(uniform_location, 1, (const GLfloat *)&value); } void shader_set_2fv(U32 shader_program, char *uniform_name, V2F value) { U32 uniform_location = glGetUniformLocation(shader_program, uniform_name); glUniform2fv(uniform_location, 1, (const GLfloat *)&value); } void shader_set_mat4fv(U32 shader_program, char *uniform_name, MAT4 value) { U32 uniform_location = glGetUniformLocation(shader_program, uniform_name); glUniformMatrix4fv(uniform_location, 1, GL_FALSE, (F32 *)&value); } void shader_set_1i(U32 shader_program, char *uniform_name, S32 value) { U32 uniform_location = glGetUniformLocation(shader_program, uniform_name); glUniform1i(uniform_location, value); } typedef struct { S32 loaded; U32 id; char *name; } shader_t; #define MAX_SHADERS 8 shader_t shaders[MAX_SHADERS] = {0}; void add_shader(const char *vert_filename, const char *frag_filename, const char *geom_filename) { if (!vert_filename) { info("vertex shader filename not specified"); return; } const char *dot = strrchr(vert_filename, '.'); if (!dot) { info("shader \"%s\" was not loaded .vert or .vs expected for vertex shader", vert_filename); return; } U64 len = dot - vert_filename; char *name = malloc(len + 1); memmove(name, vert_filename, len); name[len] = '\0'; for (S32 i = 0; i < MAX_SHADERS; i++) { if (!shaders[i].loaded) { shaders[i].loaded = 1; shaders[i].id = load_shader_geom(vert_filename, frag_filename, geom_filename); if (!shaders[i].id) die("failed to load shader \"%s\"", name); shaders[i].name = name; info("shader \"%s\" loaded successfully", name); return; } } info("max shaders reached, can't load \"%s\"", name); } void remove_shader(const char *name) { S32 i; for (i = 0; i < MAX_SHADERS; i++) { shader_t *shader = shaders + i; if (shader->name && !strcmp(name, shader->name)) { free(shader->name); glDeleteProgram(shader->id); memset(shader, 0, sizeof(shader_t)); return; } } info("failed to find shader \"%s\"", name); } U32 find_shader(const char *name) { for (S32 i = 0; i < MAX_SHADERS; i++) { shader_t *shader = shaders + i; if (shader->name && !strcmp(name, shader->name)) { return shader->id; } } return 0; } void list_shaders(void) { info("-----------------------"); for (S32 i = 0; i < MAX_SHADERS; i++) info("loaded = %d, id = %d, name = \"%s\"", shaders[i].loaded, shaders[i].id, shaders[i].name); info("-----------------------"); } U32 load_texture(char *texture_filename) { U32 texture_id; glGenTextures(1, &texture_id); stbi_set_flip_vertically_on_load(1); S32 width, height, number_channels; U8 *data = stbi_load(texture_filename, &width, &height, &number_channels, 0); if (data) { GLenum format = 0; if (number_channels == 1) format = GL_RED; else if (number_channels == 3) format = GL_RGB; else if (number_channels == 4) format = GL_RGBA; glBindTexture(GL_TEXTURE_2D, texture_id); glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, GL_UNSIGNED_BYTE, data); glGenerateMipmap(GL_TEXTURE_2D); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glBindTexture(GL_TEXTURE_2D, 0); fprintf(stdout, "info: texture (\"%s\") is loaded successfully\n", texture_filename); } else { fprintf(stderr, "error: failed to load texture: \"%s\"\n", texture_filename); } stbi_image_free(data); return(texture_id); } U32 load_texture_gamma(char *filename, B32 gamma_correction) { S32 width, height, number_channels; U32 texture_id; glGenTextures(1, &texture_id); stbi_set_flip_vertically_on_load(1); U8 *data = stbi_load(filename, &width, &height, &number_channels, 0); if (data) { GLenum internal_format, data_format; if (number_channels == 1) { internal_format = data_format = GL_RED; } else if (number_channels == 3) { internal_format = gamma_correction ? GL_SRGB : GL_RGB; data_format = GL_RGB; } else if (number_channels == 4) { internal_format = gamma_correction ? GL_SRGB_ALPHA : GL_RGBA; data_format = GL_RGBA; } glBindTexture(GL_TEXTURE_2D, texture_id); glTexImage2D(GL_TEXTURE_2D, 0, internal_format, width, height, 0, data_format, GL_UNSIGNED_BYTE, data); glGenerateMipmap(GL_TEXTURE_2D); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glBindTexture(GL_TEXTURE_2D, 0); fprintf(stdout, "[INFO]: Texture (\"%s\") is loaded successfully\n", filename); } else { fprintf(stderr, "[ERROR]: Failed to load texture: \"%s\"\n", filename); } stbi_image_free(data); return(texture_id); } U32 load_cubemap(const char *texture_filenames[6]) { U32 texture_id; glGenTextures(1, &texture_id); glBindTexture(GL_TEXTURE_CUBE_MAP, texture_id); S32 width, height, number_channels; U8 *data = 0; stbi_set_flip_vertically_on_load(0); for (U32 i = 0; i < 6; ++i) { data = stbi_load(texture_filenames[i], &width, &height, &number_channels, 0); if (data) { glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X+i, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data); fprintf(stdout, "[INFO]: Texture (\"%s\") is loaded successfully\n", texture_filenames[i]); } else { fprintf(stderr, "[ERROR]: Failed to load texture: \"%s\"\n", texture_filenames[i]); } stbi_image_free(data); } glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); return(texture_id); } U32 load_hdr_texture(const char *filename) { U32 id = 0; stbi_set_flip_vertically_on_load(1); S32 width, height, number_channels; F32 *data = stbi_loadf(filename, &width, &height, &number_channels, 0); if (data) { glGenTextures(1, &id); glBindTexture(GL_TEXTURE_2D, id); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB16F, width, height, 0, GL_RGB, GL_FLOAT, data); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glBindTexture(GL_TEXTURE_2D, 0); info("texture (\"%s\") is loaded successfully", filename); } else { info("failed to load texture: \"%s\"", filename); } stbi_image_free(data); return id; } void init_glfw(state_t *state) { if (glfwInit() == GLFW_FALSE) die("failed to initialize glfw"); GLFWmonitor* monitor = glfwGetPrimaryMonitor(); if (!monitor) die("failed to get primary monitor"); S32 xpos = 0; S32 ypos = 0; S32 monitor_width = 0; S32 monitor_height = 0; glfwGetMonitorWorkarea(monitor, &xpos, &ypos, &monitor_width, &monitor_height); glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); if (state->debug) glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, 1); glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); glfwWindowHint(GLFW_POSITION_X, xpos + monitor_width / 2.0f - state->width / 2.0f); glfwWindowHint(GLFW_POSITION_Y, ypos + monitor_height / 2.0f - state->height / 2.0f); state->window = glfwCreateWindow(state->width, state->height, "prb lighting", 0, 0); if (!state->window) die("failed to create window"); glfwSetInputMode(state->window, GLFW_CURSOR, GLFW_CURSOR_DISABLED); glfwMakeContextCurrent(state->window); glfwSwapInterval(0); } void gl_debug_output(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar *message, const void *userParam) { /* ignore these non-significant error codes */ if(id == 131169 || id == 131185 || id == 131218 || id == 131204) return; info("debug message (%d): %s", id, message); switch (source) { case GL_DEBUG_SOURCE_API: info("source: api"); break; case GL_DEBUG_SOURCE_WINDOW_SYSTEM: info("source: window system"); break; case GL_DEBUG_SOURCE_SHADER_COMPILER: info("source: shader compiler"); break; case GL_DEBUG_SOURCE_THIRD_PARTY: info("source: third party"); break; case GL_DEBUG_SOURCE_APPLICATION: info("source: application"); break; case GL_DEBUG_SOURCE_OTHER: info("source: other"); break; } switch (type) { case GL_DEBUG_TYPE_ERROR: info("type: error"); break; case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR: info("type: deprecated behaviour"); break; case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR: info("type: undefined behaviour"); break; case GL_DEBUG_TYPE_PORTABILITY: info("type: portability"); break; case GL_DEBUG_TYPE_PERFORMANCE: info("type: performance"); break; case GL_DEBUG_TYPE_MARKER: info("type: marker"); break; case GL_DEBUG_TYPE_PUSH_GROUP: info("type: push group"); break; case GL_DEBUG_TYPE_POP_GROUP: info("type: pop group"); break; case GL_DEBUG_TYPE_OTHER: info("type: other"); break; } switch (severity) { case GL_DEBUG_SEVERITY_HIGH: info("severity: high"); break; case GL_DEBUG_SEVERITY_MEDIUM: info("severity: medium"); break; case GL_DEBUG_SEVERITY_LOW: info("severity: low"); break; case GL_DEBUG_SEVERITY_NOTIFICATION: info("severity: notification"); break; } putc('\n', stdout); } void init_gl(void) { if (glewInit() != GLEW_OK) die("failed to initialize glew"); int flags; glGetIntegerv(GL_CONTEXT_FLAGS, &flags); if (flags & GL_CONTEXT_FLAG_DEBUG_BIT) { info("debug context initialized successfully"); glEnable(GL_DEBUG_OUTPUT); glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS); glDebugMessageCallback(gl_debug_output, 0); glDebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, 0, GL_TRUE); } glEnable(GL_DEPTH_TEST); } F32 lock_framerate(F32 fps) { static F32 last = 0; if (!last) last = glfwGetTime(); F32 elapsed = (glfwGetTime() - last) * 1000.0f; F32 target = 1000.0f / fps; if (elapsed < target) { U32 to_sleep = (U32)(target - elapsed - 1) * 1000; if (to_sleep > 0) usleep(to_sleep); while (((glfwGetTime() - last) * 1000.0f) < target) ; } F32 current = glfwGetTime(); F32 dt = current - last; last = current; return dt; } void fps_info(F32 dt, S32 again) { static F32 time = 0; static S32 lastsec = 0; static F32 sumdt = 0; static S32 sumfps = 0; static F32 frame_count = 0; time += dt; sumdt += dt; sumfps += (S32)(1.0f / dt); frame_count++; S32 sec = (S32)time; if (sec != 0 && sec % again == 0) { if (lastsec != sec) { F32 mean_dt = sumdt / frame_count; F32 mean_fps = sumfps / frame_count; info("time: %d dt: %f fps: %f", sec, mean_dt, mean_fps); lastsec = sec; sumdt = 0; sumfps = 0; frame_count = 0; } } } void input_update_last_state(Input *input) { input->move_right.last = input->move_right.state; input->move_forward.last = input->move_forward.state; input->move_left.last = input->move_left.state; input->move_backward.last = input->move_backward.state; input->move_up.last = input->move_up.state; input->move_down.last = input->move_down.state; input->jump.last = input->jump.state; input->action_right.last = input->action_right.state; input->action_up.last = input->action_up.state; input->action_left.last = input->action_left.state; input->action_down.last = input->action_down.state; input->exit.last = input->exit.state; } void process_glfw_key(GLFWwindow *window, S32 glfw_keycode, Key *key) { if (glfwGetKey(window, glfw_keycode) == GLFW_PRESS) key->state = KeyState_PRESS; else key->state = KeyState_RELEASE; } void process_glfw_keyboard(GLFWwindow *window, Input *input) { process_glfw_key(window, GLFW_KEY_D, &input->move_right); process_glfw_key(window, GLFW_KEY_W, &input->move_forward); process_glfw_key(window, GLFW_KEY_A, &input->move_left); process_glfw_key(window, GLFW_KEY_S, &input->move_backward); process_glfw_key(window, GLFW_KEY_E, &input->move_up); process_glfw_key(window, GLFW_KEY_Q, &input->move_down); process_glfw_key(window, GLFW_KEY_SPACE, &input->jump); process_glfw_key(window, GLFW_KEY_RIGHT, &input->action_right); process_glfw_key(window, GLFW_KEY_UP, &input->action_up); process_glfw_key(window, GLFW_KEY_LEFT, &input->action_left); process_glfw_key(window, GLFW_KEY_DOWN, &input->action_down); process_glfw_key(window, GLFW_KEY_ESCAPE, &input->exit); } void process_glfw_mouse_pos(GLFWwindow *window, Input *input) { F64 xpos, ypos; glfwGetCursorPos(window, &xpos, &ypos); if (input->first_mouse) { input->last_mouse_pos = v2f((F32)xpos, (F32)ypos); input->first_mouse = 0; } input->mouse_offset = v2f(input->mouse_offset.x+((F32)xpos-input->last_mouse_pos.x), input->mouse_offset.y+((F32)ypos-input->last_mouse_pos.y)); input->last_mouse_pos = v2f((F32)xpos, (F32)ypos); } B32 key_is_pressed(Key key) { B32 result = (key.state == KeyState_PRESS); return(result); } B32 key_first_press(Key key) { B32 result = ((key.last == KeyState_RELEASE) && (key.state == KeyState_PRESS)); return(result); } B32 key_was_pressed(Key key) { B32 result = ((key.last == KeyState_PRESS) && (key.state == KeyState_RELEASE)); return(result); } void handle_glfw_events(GLFWwindow *window, Input *input) { input_update_last_state(input); glfwPollEvents(); process_glfw_keyboard(window, input); process_glfw_mouse_pos(window, input); if (key_first_press(input->exit)) glfwSetWindowShouldClose(window, GLFW_TRUE); } MAT4 get_view_matrix(Camera *camera) { MAT4 view; view = mat4_make_translate(v3f_negate(camera->pos)); view = mat4_rotate_angles(view, v3f(camera->pitch, camera->yaw, 0.0f)); return(view); } void get_camera_vectors(Camera *camera, V3F *l, V3F *u, V3F *f) { MAT4 view; view = get_view_matrix(camera); *l = v3f(-view.m0.x, -view.m1.x, -view.m2.x); *u = v3f(view.m0.y, view.m1.y, view.m2.y); *f = v3f(-view.m0.z, -view.m1.z, -view.m2.z); } V3F get_dv_camera_first_person(Input *input, Camera *camera, F32 acceleration, F32 dt) { V3F f, l, u, dv; get_camera_vectors(camera, &l, &u, &f); dv = v3f_zero(); if (key_is_pressed(input->move_right)) dv = v3f_sub(dv, l); if (key_is_pressed(input->move_forward)) dv = v3f_add(dv, f); if (key_is_pressed(input->move_left)) dv = v3f_add(dv, l); if (key_is_pressed(input->move_backward)) dv = v3f_sub(dv, f); if (key_is_pressed(input->move_up)) dv = v3f_add(dv, u); if (key_is_pressed(input->move_down)) dv = v3f_sub(dv, u); dv = v3f_norm(dv); dv = v3f_scalef(dv, acceleration*dt); return(dv); } void update_camera_first_person(Camera *camera, Input *input, F32 dt, F32 speed) { V3F a = get_dv_camera_first_person(input, camera, speed, dt); static V3F v = {0.0f, 0.0f, 0.0f}; v = v3f_add(v, a); v = v3f_scalef(v, 0.8f); camera->pos = v3f_add(camera->pos, v); F32 sensitivity = 0.1f; input->mouse_offset = v2f_scalef(input->mouse_offset, sensitivity); camera->yaw += input->mouse_offset.x; camera->pitch += input->mouse_offset.y; if (camera->pitch > 89.0f) camera->pitch = 89.0f; if (camera->pitch < -89.0f) camera->pitch = -89.0f; } typedef struct { V3F translate; V3F scale; V3F rotate; } Transform; Transform transform_make(V3F translate, V3F scale, V3F rotate) { Transform result; result.translate = translate; result.scale = scale; result.rotate = rotate; return(result); } Transform transform_default() { Transform result = transform_make(v3f_zero(), v3f_one(), v3f_zero()); return(result); } Transform transform_make_translate(V3F translate) { Transform result = transform_default(); result.translate = translate; return(result); } Transform transform_make_scale(V3F scale) { Transform result = transform_default(); result.scale = scale; return(result); } Transform transform_make_rotate(V3F angles) { Transform result = transform_default(); result.rotate = angles; return(result); } Transform transform_translate(Transform source, V3F translate) { Transform result = source; result.translate = v3f_add(source.translate, translate); return(result); } Transform transform_scale(Transform source, V3F scale) { Transform result = source; result.scale = v3f_dot(source.scale, scale); return(result); } Transform transform_rotate(Transform source, V3F angles) { Transform result; result.translate = source.translate; result.scale = source.scale; result.rotate = v3f_add(source.rotate, angles); return(result); } Transform transform_make_scale_translate(V3F scale, V3F translate) { Transform result = transform_default(); result.translate = translate; result.scale = scale; return(result); } MAT4 transform_apply(Transform transform) { MAT4 result = mat4_identity(); MAT4 translate = mat4_make_translate(transform.translate); MAT4 scale = mat4_make_scale(transform.scale); MAT4 rotate = mat4_make_rotate(transform.rotate); result = mat4_mul(mat4_mul(translate, scale), rotate); return(result); } MAT4 ortho(F32 l, F32 r, F32 b, F32 t, F32 n, F32 f) { MAT4 result = mat4_identity(); result.m0.x = 2.0f/(r-l); result.m1.y = 2.0f/(t-b); result.m2.z = -2.0f/(f-n); result.m3.x = -(r+l)/(r-l); result.m3.y = -(t+b)/(t-b); result.m3.z = -(f+n)/(f-n); return(result); } MAT4 perspective(F32 fovx, F32 ar, F32 n, F32 f) { F32 r = n*f32_tan(fovx/2.0f*DEG2RAD); F32 t = r/ar; MAT4 result = mat4_identity(); result.m0.x = n/r; result.m1.y = n/t; result.m2.z = -(f+n)/(f-n); result.m2.w = -1.0f; result.m3.z = (-2.0f*f*n)/(f-n); result.m3.w = 0.0f; return(result); } MAT4 look_at(V3F eye, V3F target, V3F up) { V3F f = v3f_norm(v3f_sub(eye, target)); V3F l = v3f_norm(v3f_cross(up, f)); V3F u = v3f_cross(f, l); MAT4 translate = mat4_make_translate(v3f_negate(eye)); MAT4 rotate = mat4_change_basis(l, u, f); MAT4 result = mat4_mul(mat4_transpose(rotate), translate); return(result); } V3F get_dv_camera_orbital(Input *input, V3F pos, V3F target, F32 dt, F32 acceleration) { V3F up, f, l, u, dv; up = v3f(0.0f, 1.0f, 0.0f); f = v3f_norm(v3f_sub(target, pos)); l = v3f_norm(v3f_cross(up, f)); u = v3f_cross(f, l); dv = v3f_zero(); if (key_is_pressed(input->move_right)) dv = v3f_sub(dv, l); if (key_is_pressed(input->move_forward)) dv = v3f_add(dv, f); if (key_is_pressed(input->move_left)) dv = v3f_add(dv, l); if (key_is_pressed(input->move_backward)) dv = v3f_sub(dv, f); if (key_is_pressed(input->move_up)) dv = v3f_add(dv, u); if (key_is_pressed(input->move_down)) dv = v3f_sub(dv, u); dv = v3f_norm(dv); dv = v3f_scalef(dv, acceleration*dt); return(dv); } MAT4 camera_persp(Camera camera, F32 ar) { MAT4 result; result = perspective(camera.fovx, ar, camera.near, camera.far); return result; } typedef struct { V3F position; V3F color; } light_t; typedef struct { Arena *arena; Input input; Camera camera; V3F camera_dp; F32 dt; } State; F32 lerp(F32 a, F32 b, F32 f) { return a + f * (b - a); } U32 check_gl_errors_(const char *file, int line) { U32 code; while ((code = glGetError()) != GL_NO_ERROR) { char *error; switch (code) { case GL_INVALID_ENUM: error = "invalid enum"; break; case GL_INVALID_VALUE: error = "invalid value"; break; case GL_INVALID_OPERATION: error = "invalid operation"; break; case GL_STACK_OVERFLOW: error = "stack overflow"; break; case GL_STACK_UNDERFLOW: error = "stack underflow"; break; case GL_OUT_OF_MEMORY: error = "out of memory"; break; case GL_INVALID_FRAMEBUFFER_OPERATION: error = "invalid framebuffer operation"; break; default: error = "undefined error"; break; } info("%s:%d: %s", file, line, error); } return code; } #define check_gl_errors() check_gl_errors_(__FILE__, __LINE__) #endif /* COMMON_H */