diff --git a/src/stb_image.h b/3p/stb/stb_image.h similarity index 100% rename from src/stb_image.h rename to 3p/stb/stb_image.h diff --git a/src/stb_image_write.h b/3p/stb/stb_image_write.h similarity index 100% rename from src/stb_image_write.h rename to 3p/stb/stb_image_write.h diff --git a/Makefile b/Makefile index 5968f71..ddef145 100644 --- a/Makefile +++ b/Makefile @@ -1,13 +1,13 @@ ifeq ($(OS),Windows_NT) EXT = .exe - CFLAGS = -O2 -DNDEBUG -I3p/glad/include -I3p/glfw-3.4.bin.WIN64/include -L3p/glfw-3.4.bin.WIN64/lib-mingw-w64 + CFLAGS = -O2 -DNDEBUG -I3p -I3p/glad/include -I3p/glfw-3.4.bin.WIN64/include -L3p/glfw-3.4.bin.WIN64/lib-mingw-w64 LDFLAGS = -lglfw3 -lopengl32 -lgdi32 else UNAME_S := $(shell uname -s) ifeq ($(UNAME_S),Linux) EXT = - CFLAGS = -O2 -DNDEBUG -I3p/glad/include + CFLAGS = -O2 -DNDEBUG -I3p/glad/include -I3p LDFLAGS = -lglfw -lm endif ifeq ($(UNAME_S),Darwin) @@ -20,7 +20,7 @@ endif all: ray_trace$(EXT) ray_trace$(EXT): Makefile $(wildcard src/*.c src/*.h) - gcc -o $@ src/main.c src/utils.c src/camera.c src/mesh.c src/vector.c src/thread.c src/sync.c src/clock.c src/profile.c 3p/glad/src/glad.c -std=c11 $(CFLAGS) $(LDFLAGS) + gcc -o $@ src/main.c src/utils.c src/scene.c src/camera.c src/vector.c src/os.c src/gpu_and_windowing.c 3p/glad/src/glad.c -std=c11 $(CFLAGS) $(LDFLAGS) clean: rm ray_trace ray_trace.exe diff --git a/README.md b/README.md index ed77c27..f2e6469 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,3 @@ You should use a number of threads equal to the number of CPU cores. The `--init ![scene 0](assets/screenshot_1.png) ![scene 1](assets/screenshot_2.png) ![scene 2](assets/screenshot_3.png) - -# License -The ray tracer itself (main.c, camera.c, and shaders) are MIT licensed. Everything else (other than 3p code) is released in the public domain. \ No newline at end of file diff --git a/src/camera.h b/src/camera.h index 8b3ab93..e01c281 100644 --- a/src/camera.h +++ b/src/camera.h @@ -23,7 +23,7 @@ typedef enum { } Direction; Matrix4 camera_pov(void); -void move_camera(Direction dir, float speed); -void rotate_camera(double mouse_x, double mouse_y); +void move_camera(Direction dir, float speed); +void rotate_camera(double mouse_x, double mouse_y); Vector3 get_camera_pos(void); -Ray ray_through_screen_at(float u, float v, float aspect_ratio); \ No newline at end of file +Ray ray_through_screen_at(float u, float v, float aspect_ratio); \ No newline at end of file diff --git a/src/clock.c b/src/clock.c deleted file mode 100644 index 3db3eec..0000000 --- a/src/clock.c +++ /dev/null @@ -1,115 +0,0 @@ -/* -This is free and unencumbered software released into the public domain. - -Anyone is free to copy, modify, publish, use, compile, sell, or -distribute this software, either in source code form or as a compiled -binary, for any purpose, commercial or non-commercial, and by any -means. - -In jurisdictions that recognize copyright laws, the author or authors -of this software dedicate any and all copyright interest in the -software to the public domain. We make this dedication for the benefit -of the public at large and to the detriment of our heirs and -successors. We intend this dedication to be an overt act of -relinquishment in perpetuity of all present and future rights to this -software under copyright law. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR -OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, -ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -OTHER DEALINGS IN THE SOFTWARE. - -For more information, please refer to -*/ - -#define _GNU_SOURCE -#define _POSIX_C_SOURCE 1999309L - -#include "clock.h" - -#ifdef _WIN32 -#define WIN32_MEAN_AND_LEAN -#include -#else -#include -#include -#include -#endif - -/* - * Returns the current absolute time in microsecods - * TODO: Specify since when the time is calculated - */ -uint64_t get_absolute_time_us(void) -{ - #ifdef _WIN32 - FILETIME filetime; - GetSystemTimePreciseAsFileTime(&filetime); - uint64_t time = (uint64_t) filetime.dwLowDateTime | ((uint64_t) filetime.dwHighDateTime << 32); - time /= 10; - return time; - #else - struct timespec buffer; - if (clock_gettime(CLOCK_REALTIME, &buffer)) - abort(); - uint64_t time = buffer.tv_sec * 1000000 + buffer.tv_nsec / 1000; - return time; - #endif -} - -/* - * Returns the current time in nanoseconds since - * an unspecified point in time. - */ -uint64_t get_relative_time_ns(void) -{ - #ifdef _WIN32 - { - int64_t count; - int64_t freq; - int ok; - - ok = QueryPerformanceCounter((LARGE_INTEGER*) &count); - if (!ok) abort(); - - ok = QueryPerformanceFrequency((LARGE_INTEGER*) &freq); - if (!ok) abort(); - - uint64_t res = 1000000000 * (double) count / freq; - return res; - } - #else - { - struct timespec time; - - if (clock_gettime(CLOCK_REALTIME, &time)) - abort(); - - uint64_t res; - - uint64_t sec = time.tv_sec; - if (sec > UINT64_MAX / 1000000000) - abort(); - res = sec * 1000000000; - - uint64_t nsec = time.tv_nsec; - if (res > UINT64_MAX - nsec) - abort(); - res += nsec; - - return res; - } - #endif -} - -void sleep_ms(float ms) -{ -#ifdef _WIN32 - Sleep(ms); -#else - usleep(ms*1000); -#endif -} \ No newline at end of file diff --git a/src/clock.h b/src/clock.h deleted file mode 100644 index 9524be8..0000000 --- a/src/clock.h +++ /dev/null @@ -1,32 +0,0 @@ -/* -This is free and unencumbered software released into the public domain. - -Anyone is free to copy, modify, publish, use, compile, sell, or -distribute this software, either in source code form or as a compiled -binary, for any purpose, commercial or non-commercial, and by any -means. - -In jurisdictions that recognize copyright laws, the author or authors -of this software dedicate any and all copyright interest in the -software to the public domain. We make this dedication for the benefit -of the public at large and to the detriment of our heirs and -successors. We intend this dedication to be an overt act of -relinquishment in perpetuity of all present and future rights to this -software under copyright law. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR -OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, -ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -OTHER DEALINGS IN THE SOFTWARE. - -For more information, please refer to -*/ - -#include - -uint64_t get_absolute_time_us(void); -uint64_t get_relative_time_ns(void); -void sleep_ms(float ms); \ No newline at end of file diff --git a/src/gpu_and_windowing.c b/src/gpu_and_windowing.c new file mode 100644 index 0000000..61e221f --- /dev/null +++ b/src/gpu_and_windowing.c @@ -0,0 +1,423 @@ +#include +#include +#include + +#define STB_IMAGE_IMPLEMENTATION +#include + +#include "utils.h" +#include "gpu_and_windowing.h" + +static GLFWwindow *window; +static unsigned int screen_program; +static unsigned int frame_texture; +static unsigned int vao; +static unsigned int vbo; +static int screen_w; +static int screen_h; + +#define MAX_EVENTS 512 +int event_queue[MAX_EVENTS]; +int event_queue_head = 0; +int event_queue_size = 0; + +void load_cubemap(Cubemap *c, const char *files[6]) +{ + for (int i = 0; i < 6; i++) { + c->data[i] = stbi_load(files[i], &c->w, &c->h, &c->chan, 0); + if (c->data[i] == NULL) { + fprintf(stderr, "Couldn't load image '%s'\n", files[i]); + abort(); + } + } +} + +void free_cubemap(Cubemap *c) +{ + for (int i = 0; i < 6; i++) { + stbi_image_free(c->data[i]); + } +} + +Vector3 sample_cubemap(Cubemap *c, Vector3 dir) +{ + float abs_x = absf(dir.x); + float abs_y = absf(dir.y); + float abs_z = absf(dir.z); + + CubeFace face; + + float u; + float v; + float eps = 0; + + if (abs_x > abs_y && abs_x > abs_z) { + // X dominant + if (dir.x > 0) { + // right face + face = CF_RIGHT; + u = -dir.z / (abs_x + eps); + v = -dir.y / (abs_x + eps); + } else { + // left face + face = CF_LEFT; + u = dir.z / (abs_x + eps); + v = -dir.y / (abs_x + eps); + } + } else if (abs_y > abs_x && abs_y > abs_z) { + // Y dominant + assert(abs_y > 0); + if (dir.y > 0) { + // top face + face = CF_TOP; + u = dir.x / (abs_y + eps); + v = dir.z / (abs_y + eps); + } else { + // bottom face + face = CF_BOTTOM; + u = dir.x / (abs_y + eps); + v = -dir.z / (abs_y + eps); + } + } else { + // Z dominant + if (dir.z > 0) { + // front face + face = CF_FRONT; + u = dir.x / (abs_z + eps); + v = -dir.y / (abs_z + eps); + } else { + // back face + face = CF_BACK; + u = -dir.x / (abs_z + eps); + v = -dir.y / (abs_z + eps); + } + } + + u = clamp(u, -1, 1); + v = clamp(v, -1, 1); + + u = 0.5f * (u + 1.0f); + v = 0.5f * (v + 1.0f); + + // Pixel coordinates + int x = u * (c->w - 1); + int y = v * (c->h - 1); + + uint8_t *color = &c->data[face][(y * c->w + x) * c->chan]; + return (Vector3) { + (float) color[0] / 255, + (float) color[1] / 255, + (float) color[2] / 255, + }; +} + +static unsigned int +compile_shader(const char *vertex_file, const char *fragment_file) +{ + int success; + char infolog[512]; + + char *vertex_str = load_file(vertex_file, NULL); + if (vertex_str == NULL) { + fprintf(stderr, "Couldn't load file '%s'\n", vertex_file); + return 0; + } + + char *fragment_str = load_file(fragment_file, NULL); + if (fragment_str == NULL) { + fprintf(stderr, "Couldn't load file '%s'\n", fragment_file); + free(vertex_str); + return 0; + } + + unsigned int vertex_shader = glCreateShader(GL_VERTEX_SHADER); + glShaderSource(vertex_shader, 1, (const GLchar * const *) &vertex_str, NULL); + glCompileShader(vertex_shader); + + glGetShaderiv(vertex_shader, GL_COMPILE_STATUS, &success); + if(!success) { + glGetShaderInfoLog(vertex_shader, sizeof(infolog), NULL, infolog); + fprintf(stderr, "Couldn't compile vertex shader '%s' (%s)\n", vertex_file, infolog); + free(vertex_str); + free(fragment_str); + return 0; + } + + unsigned int fragment_shader = glCreateShader(GL_FRAGMENT_SHADER); + glShaderSource(fragment_shader, 1, (const GLchar * const *) &fragment_str, NULL); + glCompileShader(fragment_shader); + + glGetShaderiv(fragment_shader, GL_COMPILE_STATUS, &success); + if(!success) { + glGetShaderInfoLog(fragment_shader, sizeof(infolog), NULL, infolog); + fprintf(stderr, "Couldn't compile fragment shader '%s' (%s)\n", fragment_file, infolog); + free(vertex_str); + free(fragment_str); + return 0; + } + + unsigned int shader_program = glCreateProgram(); + glAttachShader(shader_program, vertex_shader); + glAttachShader(shader_program, fragment_shader); + glLinkProgram(shader_program); + + glGetProgramiv(shader_program, GL_LINK_STATUS, &success); + if(!success) { + glGetProgramInfoLog(shader_program, sizeof(infolog), NULL, infolog); + fprintf(stderr, "Couldn't link shader program (%s)\n", infolog); + free(vertex_str); + free(fragment_str); + return 0; + } + + glDeleteShader(vertex_shader); + glDeleteShader(fragment_shader); + free(vertex_str); + free(fragment_str); + return shader_program; +} + +static void set_uniform_m4(unsigned int program, const char *name, Matrix4 value) +{ + int location = glGetUniformLocation(program, name); + if (location < 0) { + printf("Can't set uniform '%s'\n", name); + abort(); + } + glUniformMatrix4fv(location, 1, GL_FALSE, (float*) &value); +} + +static void set_uniform_v3(unsigned int program, const char *name, Vector3 value) +{ + int location = glGetUniformLocation(program, name); + if (location < 0) { + printf("Can't set uniform '%s' (program %d, location %d)\n", name, program, location); + abort(); + } + glUniform3f(location, value.x, value.y, value.z); +} + +static void set_uniform_i(unsigned int program, const char *name, int value) +{ + int location = glGetUniformLocation(program, name); + if (location < 0) { + printf("Can't set uniform '%s'\n", name); + abort(); + } + glUniform1i(location, value); +} + +static void set_uniform_f(unsigned int program, const char *name, float value) +{ + int location = glGetUniformLocation(program, name); + if (location < 0) { + printf("Can't set uniform '%s'\n", name); + abort(); + } + glUniform1f(location, value); +} + +static void push_event(int event) +{ + if (event_queue_size == MAX_EVENTS) { + fprintf(stderr, "Event queue full. An event has been lost\n"); + return; + } + int tail = (event_queue_head + event_queue_size) % MAX_EVENTS; + event_queue[tail] = event; + event_queue_size++; +} + +int pop_event(double *mouse_x, double *mouse_y) +{ + if (glfwWindowShouldClose(window)) + return EVENT_CLOSE; + + if (event_queue_size == 0) + return EVENT_EMPTY; + + int event = event_queue[event_queue_head]; + event_queue_head = (event_queue_head + 1) % MAX_EVENTS; + event_queue_size--; + + if (event == EVENT_MOVE_MOUSE) + glfwGetCursorPos(window, mouse_x, mouse_y); + return event; +} + +static void error_callback(int error, const char* description) +{ + fprintf(stderr, "Error: %s\n", description); +} + +static void key_callback(GLFWwindow* window, int key, int scancode, int action, int mods) +{ + switch (key) { + case GLFW_KEY_SPACE: + if (0) {} + else if (action == GLFW_PRESS) push_event(EVENT_PRESS_SPACE); + else if (action == GLFW_REPEAT) push_event(EVENT_AGAIN_SPACE); + break; + + case GLFW_KEY_ESCAPE: + if (0) {} + else if (action == GLFW_PRESS) push_event(EVENT_PRESS_ESC); + else if (action == GLFW_REPEAT) push_event(EVENT_AGAIN_ESC); + break; + + case GLFW_KEY_W: + if (0) {} + else if (action == GLFW_PRESS) push_event(EVENT_PRESS_W); + else if (action == GLFW_REPEAT) push_event(EVENT_AGAIN_W); + break; + + case GLFW_KEY_A: + if (0) {} + else if (action == GLFW_PRESS) push_event(EVENT_PRESS_W); + else if (action == GLFW_REPEAT) push_event(EVENT_AGAIN_W); + break; + + case GLFW_KEY_S: + if (0) {} + else if (action == GLFW_PRESS) push_event(EVENT_PRESS_W); + else if (action == GLFW_REPEAT) push_event(EVENT_AGAIN_W); + break; + + case GLFW_KEY_D: + if (0) {} + else if (action == GLFW_PRESS) push_event(EVENT_PRESS_W); + else if (action == GLFW_REPEAT) push_event(EVENT_AGAIN_W); + break; + } +} + +static void cursor_callback(GLFWwindow *window, double x, double y) +{ + push_event(EVENT_MOVE_MOUSE); +} + +static void framebuffer_size_callback(GLFWwindow* window, int w, int h) +{ + glViewport(0, 0, w, h); + screen_w = w; + screen_h = h; +} + +void startup_window_and_opengl_context_or_exit(int window_w, int window_h, const char *title) +{ + glfwSetErrorCallback(error_callback); + + if (!glfwInit()) + exit(-1); + + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); + glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); + + window = glfwCreateWindow(window_w, window_h, title, NULL, NULL); + if (!window) { + glfwTerminate(); + exit(-1); + } + + glfwSetKeyCallback(window, key_callback); + glfwSetFramebufferSizeCallback(window, framebuffer_size_callback); + glfwSetCursorPosCallback(window, cursor_callback); + glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED); + + glfwMakeContextCurrent(window); + + if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) { + printf("Failed to initialize GLAD\n"); + exit(-1); + } + + glfwSwapInterval(1); + + glfwGetWindowSize(window, &screen_w, &screen_h); + + screen_program = compile_shader("assets/screen.vs", "assets/screen.fs"); + if (!screen_program) { + printf("Couldn't compile program\n"); + exit(-1); + } + set_uniform_i(screen_program, "screenTexture", 0); + + { + float vertices[] = { + // positions // texCoords + -1.0f, 1.0f, 0.0f, 1.0f, + -1.0f, -1.0f, 0.0f, 0.0f, + 1.0f, -1.0f, 1.0f, 0.0f, + + -1.0f, 1.0f, 0.0f, 1.0f, + 1.0f, -1.0f, 1.0f, 0.0f, + 1.0f, 1.0f, 1.0f, 1.0f + }; + + glGenVertexArrays(1, &vao); + glGenBuffers(1, &vbo); + + glBindVertexArray(vao); + + glBindBuffer(GL_ARRAY_BUFFER, vbo); + glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), &vertices, GL_STATIC_DRAW); + + glEnableVertexAttribArray(0); + glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*)0); + + glEnableVertexAttribArray(1); + glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*)(2 * sizeof(float))); + } + + glGenTextures(1, &frame_texture); + glBindTexture(GL_TEXTURE_2D, frame_texture); + 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); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); +} + +void cleanup_window_and_opengl_context(void) +{ + glfwDestroyWindow(window); + glfwTerminate(); +} + +int get_screen_w(void) +{ + return screen_w; +} + +int get_screen_h(void) +{ + return screen_h; +} + +void move_frame_to_the_gpu(int w, int h, Vector3 *data) +{ + glBindTexture(GL_TEXTURE_2D, frame_texture); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, w, h, 0, GL_RGB, GL_FLOAT, data); + glBindTexture(GL_TEXTURE_2D, 0); +} + +void draw_frame(void) +{ + Vector3 clear_color = {1, 1, 1}; + //glViewport(0, 0, screen_w, screen_h); + glClearColor(clear_color.x, clear_color.y, clear_color.z, 1.0f); + glClearStencil(0); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); + + glUseProgram(screen_program); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, frame_texture); + glBindVertexArray(vao); + glDrawArrays(GL_TRIANGLES, 0, 6); + glBindVertexArray(0); + + glfwSwapBuffers(window); + glfwPollEvents(); +} \ No newline at end of file diff --git a/src/gpu_and_windowing.h b/src/gpu_and_windowing.h new file mode 100644 index 0000000..282e33d --- /dev/null +++ b/src/gpu_and_windowing.h @@ -0,0 +1,49 @@ +#include +#include "vector.h" + +typedef struct { + uint8_t *data[6]; + int w, h, chan; +} Cubemap; + +typedef enum { + CF_FRONT, + CF_BACK, + CF_LEFT, + CF_RIGHT, + CF_TOP, + CF_BOTTOM, +} CubeFace; + +enum { + EVENT_EMPTY = 0, + EVENT_CLOSE, + EVENT_PRESS_SPACE, + EVENT_PRESS_ESC, + EVENT_PRESS_W, + EVENT_PRESS_A, + EVENT_PRESS_S, + EVENT_PRESS_D, + EVENT_AGAIN_SPACE, + EVENT_AGAIN_ESC, + EVENT_AGAIN_W, + EVENT_AGAIN_A, + EVENT_AGAIN_S, + EVENT_AGAIN_D, + EVENT_MOVE_MOUSE, +}; + +int pop_event(double *mouse_x, double *mouse_y); + +void startup_window_and_opengl_context_or_exit(int window_w, int window_h, const char *title); +void cleanup_window_and_opengl_context(void); + +int get_screen_w(void); +int get_screen_h(void); + +void move_frame_to_the_gpu(int w, int h, Vector3 *data); +void draw_frame(void); + +void load_cubemap(Cubemap *c, const char *files[6]); +void free_cubemap(Cubemap *c); +Vector3 sample_cubemap(Cubemap *c, Vector3 dir); diff --git a/src/main.c b/src/main.c index 85fff3c..b625f77 100644 --- a/src/main.c +++ b/src/main.c @@ -23,607 +23,56 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include #include #include -#include // FLT_MAX -#include -//#define GLFW_INCLUDE_NONE -#include #include -#define STB_IMAGE_IMPLEMENTATION -#include "stb_image.h" - #define STB_IMAGE_WRITE_IMPLEMENTATION -#include "stb_image_write.h" +#include -#include "clock.h" +#include "os.h" #include "utils.h" #include "camera.h" -#include "vector.h" -#include "thread.h" -#include "sync.h" -#include "mesh.h" +#include "scene.h" +#include "gpu_and_windowing.h" -typedef struct { - Vector3 albedo; - float roughness; - float reflectance; - float metallic; - float emission_power; - Vector3 emission_color; -} Material; +#define MAX_COLUMNS 32 -#ifndef M_PI -#define M_PI 3.1415926538 -#endif +os_mutex_t frame_mutex; +Scene scene; +Cubemap skybox; -int screen_w; -int screen_h; -os_mutex_t screen_mutex; +int num_columns = 1; +int init_scale = 2; -float maxf(float x, float y) { return x > y ? x : y; } -float minf(float x, float y) { return x < y ? x : y; } -float absf(float x) { return x < 0 ? -x : x; } +bool workers_should_stop = false; +_Atomic uint32_t accum_generation = 0; +Vector3 *accum = NULL; +Vector3 *frame = NULL; +int frame_w = 0; +int frame_h = 0; +float accum_counts[MAX_COLUMNS] = {0}; +os_mutex_t frame_mutex; +os_condvar_t accum_conds[MAX_COLUMNS]; -float clamp(float x, float min, float max) -{ - assert(min <= max); - if (x < min) return min; - if (x > max) return max; - return x; -} - -Vector3 maxv(Vector3 a, Vector3 b) -{ - return (Vector3) { - maxf(a.x, b.x), - maxf(a.y, b.y), - maxf(a.z, b.z), - }; -} - -Vector3 vec_from_scalar(float s) -{ - return (Vector3) {s, s, s}; -} - -Vector3 fresnelSchlickRoughness(float cosTheta, Vector3 F0, float roughness) -{ - return combine(F0, combine(maxv(vec_from_scalar(1.0 - roughness), F0), F0, 1, -1), 1, pow(clamp(1.0 - cosTheta, 0.0, 1.0), 5.0)); -} - -Vector3 fresnelSchlick(float u, Vector3 f0) { - return combine(f0, combine(vec_from_scalar(1.0), f0, 1, -1), 1, pow(1.0 - u, 5.0)); -} - -float geometrySmith(float NoV, float NoL, float a) { - float a2 = a * a; - float GGXL = NoV * sqrt((-NoL * a2 + NoL) * NoL + a2); - float GGXV = NoL * sqrt((-NoV * a2 + NoV) * NoV + a2); - return 0.5 / (GGXV + GGXL); -} - -float distribGGX(float NoH, float roughness) { - float a = NoH * roughness; - float k = roughness / (1.0 - NoH * NoH + a * a); - return k * k * (1.0 / M_PI); -} - -typedef struct { - uint8_t *data[6]; - int w, h, chan; -} Cubemap; - -typedef enum { - CF_FRONT, - CF_BACK, - CF_LEFT, - CF_RIGHT, - CF_TOP, - CF_BOTTOM, -} CubeFace; - -void load_cubemap(Cubemap *c, const char *files[6]) -{ - for (int i = 0; i < 6; i++) { - c->data[i] = stbi_load(files[i], &c->w, &c->h, &c->chan, 0); - if (c->data[i] == NULL) { - fprintf(stderr, "Couldn't load image '%s'\n", files[i]); - abort(); - } - } -} - -void free_cubemap(Cubemap *c) -{ - for (int i = 0; i < 6; i++) { - stbi_image_free(c->data[i]); - } -} - -Vector3 sample_cubemap(Cubemap *c, Vector3 dir) -{ - float abs_x = absf(dir.x); - float abs_y = absf(dir.y); - float abs_z = absf(dir.z); - - CubeFace face; - - float u; - float v; - float eps = 0; - - if (abs_x > abs_y && abs_x > abs_z) { - // X dominant - if (dir.x > 0) { - // right face - face = CF_RIGHT; - u = -dir.z / (abs_x + eps); - v = -dir.y / (abs_x + eps); - } else { - // left face - face = CF_LEFT; - u = dir.z / (abs_x + eps); - v = -dir.y / (abs_x + eps); - } - } else if (abs_y > abs_x && abs_y > abs_z) { - // Y dominant - assert(abs_y > 0); - if (dir.y > 0) { - // top face - face = CF_TOP; - u = dir.x / (abs_y + eps); - v = dir.z / (abs_y + eps); - } else { - // bottom face - face = CF_BOTTOM; - u = dir.x / (abs_y + eps); - v = -dir.z / (abs_y + eps); - } - } else { - // Z dominant - if (dir.z > 0) { - // front face - face = CF_FRONT; - u = dir.x / (abs_z + eps); - v = -dir.y / (abs_z + eps); - } else { - // back face - face = CF_BACK; - u = -dir.x / (abs_z + eps); - v = -dir.y / (abs_z + eps); - } - } - - u = clamp(u, -1, 1); - v = clamp(v, -1, 1); - - u = 0.5f * (u + 1.0f); - v = 0.5f * (v + 1.0f); - - // Pixel coordinates - int x = u * (c->w - 1); - int y = v * (c->h - 1); - - uint8_t *color = &c->data[face][(y * c->w + x) * c->chan]; - return (Vector3) { - (float) color[0] / 255, - (float) color[1] / 255, - (float) color[2] / 255, - }; -} - -static unsigned int -compile_shader(const char *vertex_file, - const char *fragment_file) -{ - int success; - char infolog[512]; - - char *vertex_str = load_file(vertex_file, NULL); - if (vertex_str == NULL) { - fprintf(stderr, "Couldn't load file '%s'\n", vertex_file); - return 0; - } - - char *fragment_str = load_file(fragment_file, NULL); - if (fragment_str == NULL) { - fprintf(stderr, "Couldn't load file '%s'\n", fragment_file); - free(vertex_str); - return 0; - } - - unsigned int vertex_shader = glCreateShader(GL_VERTEX_SHADER); - glShaderSource(vertex_shader, 1, (const GLchar * const *) &vertex_str, NULL); - glCompileShader(vertex_shader); - - glGetShaderiv(vertex_shader, GL_COMPILE_STATUS, &success); - if(!success) { - glGetShaderInfoLog(vertex_shader, sizeof(infolog), NULL, infolog); - fprintf(stderr, "Couldn't compile vertex shader '%s' (%s)\n", vertex_file, infolog); - free(vertex_str); - free(fragment_str); - return 0; - } - - unsigned int fragment_shader = glCreateShader(GL_FRAGMENT_SHADER); - glShaderSource(fragment_shader, 1, (const GLchar * const *) &fragment_str, NULL); - glCompileShader(fragment_shader); - - glGetShaderiv(fragment_shader, GL_COMPILE_STATUS, &success); - if(!success) { - glGetShaderInfoLog(fragment_shader, sizeof(infolog), NULL, infolog); - fprintf(stderr, "Couldn't compile fragment shader '%s' (%s)\n", fragment_file, infolog); - free(vertex_str); - free(fragment_str); - return 0; - } - - unsigned int shader_program = glCreateProgram(); - glAttachShader(shader_program, vertex_shader); - glAttachShader(shader_program, fragment_shader); - glLinkProgram(shader_program); - - glGetProgramiv(shader_program, GL_LINK_STATUS, &success); - if(!success) { - glGetProgramInfoLog(shader_program, sizeof(infolog), NULL, infolog); - fprintf(stderr, "Couldn't link shader program (%s)\n", infolog); - free(vertex_str); - free(fragment_str); - return 0; - } - - glDeleteShader(vertex_shader); - glDeleteShader(fragment_shader); - free(vertex_str); - free(fragment_str); - return shader_program; -} - -static void set_uniform_m4(unsigned int program, const char *name, Matrix4 value) -{ - int location = glGetUniformLocation(program, name); - if (location < 0) { - printf("Can't set uniform '%s'\n", name); - abort(); - } - glUniformMatrix4fv(location, 1, GL_FALSE, (float*) &value); -} - -static void set_uniform_v3(unsigned int program, const char *name, Vector3 value) -{ - int location = glGetUniformLocation(program, name); - if (location < 0) { - printf("Can't set uniform '%s' (program %d, location %d)\n", name, program, location); - abort(); - } - glUniform3f(location, value.x, value.y, value.z); -} - -static void set_uniform_i(unsigned int program, const char *name, int value) -{ - int location = glGetUniformLocation(program, name); - if (location < 0) { - printf("Can't set uniform '%s'\n", name); - abort(); - } - glUniform1i(location, value); -} - -static void set_uniform_f(unsigned int program, const char *name, float value) -{ - int location = glGetUniformLocation(program, name); - if (location < 0) { - printf("Can't set uniform '%s'\n", name); - abort(); - } - glUniform1f(location, value); -} - -static void error_callback(int error, const char* description) -{ - fprintf(stderr, "Error: %s\n", description); -} +void start_workers(os_thread *workers); +void stop_workers(os_thread *workers); void screenshot(void); +void parse_arguments_or_exit(int argc, char **argv, int *num_columns, int *init_scale, char **scene_file); -static void key_callback(GLFWwindow* window, int key, int scancode, int action, int mods) +void invalidate_accumulation(void) { - if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS) - glfwSetWindowShouldClose(window, GLFW_TRUE); - if (key == GLFW_KEY_SPACE && action == GLFW_PRESS) - screenshot(); + os_mutex_lock(&frame_mutex); + for (int i = 0; i < num_columns; i++) + accum_counts[i] = 0; + atomic_fetch_add(&accum_generation, 1); + memset(accum, 0, sizeof(Vector3) * frame_w * frame_h); + memset(frame, 0, sizeof(Vector3) * frame_w * frame_h); + os_mutex_unlock(&frame_mutex); } -void framebuffer_size_callback(GLFWwindow* window, int width, int height) +Vector3 fresnelSchlick(float u, Vector3 f0) { - glViewport(0, 0, width, height); -} - -void invalidate_accumulation(void); - -void cursor_pos_callback(GLFWwindow *window, double x, double y) -{ - invalidate_accumulation(); - rotate_camera(x, y); -} - -typedef struct { - Vector3 origin; - Vector3 size; -} Cube; - -bool intersect_cube(Ray r, Cube c, float *tnear, float *tfar, Vector3 *normal) -{ - float txmin, txmax; - float tymin, tymax; - float tzmin, tzmax; - - float tn; - float tf; - - Vector3 a = c.origin; - Vector3 b = combine(c.origin, c.size, 1, 1); - - int hit_axis = 0; // 0=x, 1=y, 2=z - - if (r.direction.x >= 0) { - txmin = (a.x - r.origin.x) / r.direction.x; - txmax = (b.x - r.origin.x) / r.direction.x; - } else { - txmax = (a.x - r.origin.x) / r.direction.x; - txmin = (b.x - r.origin.x) / r.direction.x; - } - - if (r.direction.y >= 0) { - tymin = (a.y - r.origin.y) / r.direction.y; - tymax = (b.y - r.origin.y) / r.direction.y; - } else { - tymax = (a.y - r.origin.y) / r.direction.y; - tymin = (b.y - r.origin.y) / r.direction.y; - } - - if (txmin > tymax || tymin > txmax) - return false; - - if (tymin > txmin) { txmin = tymin; hit_axis = 1; } - if (tymax < txmax) txmax = tymax; - - if (r.direction.z >= 0) { - tzmin = (a.z - r.origin.z) / r.direction.z; - tzmax = (b.z - r.origin.z) / r.direction.z; - } else { - tzmax = (a.z - r.origin.z) / r.direction.z; - tzmin = (b.z - r.origin.z) / r.direction.z; - } - - if (txmin > tzmax || tzmin > txmax) - return false; - - if (tzmin > txmin) { txmin = tzmin; hit_axis = 2; }; - if (tzmax < txmax) txmax = tzmax; - - if (tnear) *tnear = txmin; - if (tfar) *tfar = txmax; - if (normal) { - switch (hit_axis) { - case 0: *normal = r.direction.x > 0 ? (Vector3) {-1, 0, 0} : (Vector3) {1, 0, 0}; break; - case 1: *normal = r.direction.y > 0 ? (Vector3) {0, -1, 0} : (Vector3) {0, 1, 0}; break; - case 2: *normal = r.direction.z > 0 ? (Vector3) {0, 0, -1} : (Vector3) {0, 0, 1}; break; - } - } - return true; -} - -bool intersect_sphere(Ray r, Sphere s, float *t) -{ - /* - * Any point of the ray can be written as - * - * P(t) = O + t * D - * - * with O origin and D direction. - * - * All points P=(x,y,z) of a sphere can be described as - * those (and only those) that satisfy the equation - * - * x^2 + y^2 + z^2 = R^2 - * P^2 - R^2 = 0 - * - * with R radius of the sphere. The sphere here is centered - * at the origin. - * - * Intersection points of the ray with the sphere must satisfy - * both: - * - * P(t) = O + t * D - * P^2 - R^2 = 0 - * - * => (O + tD)^2 - R^2 = 0 - * => t^2 * D^2 + t * 2OD + O^2 - R^2 = 0 - * - * we can use the quadratic formula here, and more specifically - * the discriminant to check if solutions exist and how many - */ - Vector3 oc = combine(s.center, r.origin, 1, -1); - float a = dotv(r.direction, r.direction); - float b = -2 * dotv(oc, r.direction); - float c = dotv(oc, oc) - s.radius * s.radius; - - float discr = b*b - 4*a*c; - - if (discr > 0) { - float s0 = (- b + sqrt(discr)) / (2 * a); - float s1 = (- b - sqrt(discr)) / (2 * a); - if (s0 > s1) { - float tmp = s0; - s0 = s1; - s1 = tmp; - } - if (s0 < 0) { - s0 = s1; - if (s0 < 0) return false; - } - if (t) *t = s0; - return true; - } - - // Zero solutions - return false; -} - -typedef enum { - OBJECT_CUBE, - OBJECT_SPHERE, -} ObjectType; - -typedef struct { - ObjectType type; - union { - Sphere sphere; - Cube cube; - }; - Material material; -} Object; - -Object cube(Material material, Vector3 origin, Vector3 size) { return (Object) {.material=material, .type=OBJECT_CUBE, .cube=(Cube) {.origin=origin, .size=size}}; } -Object sphere(Material material, Vector3 origin, float radius) { return (Object) {.material=material, .type=OBJECT_SPHERE, .sphere=(Sphere) {.center=origin, .radius=radius}}; } - -bool intersect_object(Ray r, Object o, float *t, Vector3 *normal) -{ - switch (o.type) { - - case OBJECT_CUBE: - return intersect_cube(r, o.cube, t, NULL, normal); - - case OBJECT_SPHERE: - if (intersect_sphere(r, o.sphere, t)) { - if (normal) { - Vector3 hit_point = combine(r.origin, r.direction, 1, *t); - *normal = normalize(combine(hit_point, o.sphere.center, 1, -1)); - } - return true; - } - return false; - } - return false; -} - -_Thread_local uint64_t wyhash64_x = 0; - -uint64_t wyhash64(void) { - wyhash64_x += 0x60bee2bee120fc15; - __uint128_t tmp; - tmp = (__uint128_t) wyhash64_x * 0xa3b195354a39b70d; - uint64_t m1 = (tmp >> 64) ^ tmp; - tmp = (__uint128_t)m1 * 0x1b03738712fad5c9; - uint64_t m2 = (tmp >> 64) ^ tmp; - return m2; -} - -float random_float(void) -{ - return (float) wyhash64() / UINT64_MAX; -} - -Vector3 random_vector(void) -{ - return (Vector3) { - .x = random_float() * 2 - 1, - .y = random_float() * 2 - 1, - .z = random_float() * 2 - 1, - }; -} - -Vector3 random_direction(void) -{ - return normalize(random_vector()); -} - -Vector3 reflect(Vector3 dir, Vector3 normal) -{ - float f = -2 * dotv(normal, dir); - return combine(dir, normal, 1, f); -} - -#define MAX_OBJECTS 1024 -typedef struct { - Object objects[MAX_OBJECTS]; - int num_objects; -} Scene; - -Scene scene; - -typedef struct { - float distance; - Vector3 point; - Vector3 normal; - int object; -} HitInfo; - -HitInfo trace_ray(Ray ray) -{ - ray.direction = normalize(ray.direction); - - float nearest_t = FLT_MAX; - int nearest_object = -1; - Vector3 nearest_normal; - for (int i = 0; i < scene.num_objects; i++) { - float t; - Vector3 n; - if (!intersect_object(ray, scene.objects[i], &t, &n)) - continue; - if (t >= 0 && t < nearest_t) { - nearest_t = t; - nearest_object = i; - nearest_normal = n; - } - } - - if (nearest_object == -1) { - HitInfo result; - result.distance = -1; - result.normal = (Vector3) {0, 0, 0}; - result.point = (Vector3) {0, 0, 0}; - result.object = -1; - return result; - } else { - HitInfo result; - result.distance = nearest_t; - result.normal = nearest_normal; - result.point = combine(ray.origin, ray.direction, 1, nearest_t); - result.object = nearest_object; - return result; - } -} - -Vector3 origin_of(Object o) -{ - if (o.type == OBJECT_SPHERE) - return o.sphere.center; - return combine(o.cube.origin, o.cube.size, 1, 0.5); -} - -Cubemap skybox; - -Vector3 F_Schlick(float u, Vector3 f0) -{ - float f = pow(1.0 - u, 5.0); - return combine(vec_from_scalar(f), f0, 1, (1.0 - f)); -} - -bool iszerof(float f) -{ - return f < 0.0001 && f > -0.0001; -} - -bool iszerov(Vector3 v) -{ - return iszerof(v.x) && iszerof(v.y) && iszerof(v.z); -} - -float avgv(Vector3 v) -{ - return (v.x + v.y + v.z) / 3; + return combine(f0, combine(vec_from_scalar(1.0), f0, 1, -1), 1, pow(1.0 - u, 5.0)); } Vector3 pixel(float x, float y, float aspect_ratio) @@ -648,7 +97,7 @@ Vector3 pixel(float x, float y, float aspect_ratio) int bounces = 10; for (int i = 0; i < bounces; i++) { - HitInfo hit = trace_ray(in_ray); + HitInfo hit = trace_ray(in_ray, &scene); if (hit.object == -1) { //Vector3 sky_color = {0.6, 0.7, 0.9}; //Vector3 sky_color = {0, 0, 0}; @@ -670,7 +119,7 @@ Vector3 pixel(float x, float y, float aspect_ratio) if (dotv(rand_dir, hit.normal) > 0) { Vector3 sample_dir = normalize(combine(rand_dir, dir_to_light_source, spread, 1)); Ray sample_ray = { combine(hit.point, sample_dir, 1, 0.001), sample_dir }; - HitInfo hit2 = trace_ray(sample_ray); + HitInfo hit2 = trace_ray(sample_ray, &scene); if (hit2.object != -1) sampled_light_color = combine(sampled_light_color, scene.objects[hit2.object].material.emission_color, 1, scene.objects[hit2.object].material.emission_power); num_samples++; @@ -725,22 +174,7 @@ Vector3 pixel(float x, float y, float aspect_ratio) return result; } -int num_columns = 1; -int init_scale = 2; -#define MAX_COLUMNS 32 - -bool stop_workers = false; -_Atomic uint32_t accum_generation = 0; -Vector3 *accum = NULL; -Vector3 *frame = NULL; -int frame_w = 0; -int frame_h = 0; -unsigned int frame_texture; -float accum_counts[MAX_COLUMNS] = {0}; -os_mutex_t frame_mutex; -os_condvar_t accum_conds[MAX_COLUMNS]; - -float render_to_column(Vector3 *data, int scale_, int column_w, int column_i, int frame_w, int frame_h, uint64_t cached_generation) +float render_column(Vector3 *data, int scale_, int column_w, int column_i, int frame_w, int frame_h, uint64_t cached_generation) { float scale2inv = 1.0f / (scale_ * scale_); @@ -793,7 +227,7 @@ os_threadreturn worker(void *arg) int scale_ = init_scale; os_mutex_lock(&frame_mutex); - while (!stop_workers) { + while (!workers_should_stop) { bool resize = false; if (column_data == NULL || cached_generation != atomic_load(&accum_generation)) @@ -810,7 +244,7 @@ os_threadreturn worker(void *arg) if (!column_data) abort(); } - column_data_weight += render_to_column(column_data, scale_, column_w, column_i, cached_frame_w, cached_frame_h, cached_generation); + column_data_weight += render_column(column_data, scale_, column_w, column_i, cached_frame_w, cached_frame_h, cached_generation); os_mutex_lock(&frame_mutex); @@ -838,25 +272,14 @@ os_threadreturn worker(void *arg) os_mutex_unlock(&frame_mutex); } -void invalidate_accumulation(void) -{ - os_mutex_lock(&frame_mutex); - for (int i = 0; i < num_columns; i++) - accum_counts[i] = 0; - atomic_fetch_add(&accum_generation, 1); - memset(accum, 0, sizeof(Vector3) * frame_w * frame_h); - memset(frame, 0, sizeof(Vector3) * frame_w * frame_h); - os_mutex_unlock(&frame_mutex); -} - void update_frame_texture(void) { os_mutex_lock(&frame_mutex); - if (frame_w != screen_w || frame_h != screen_h) { + if (frame_w != get_screen_w() || frame_h != get_screen_h()) { - frame_w = screen_w; - frame_h = screen_h; + frame_w = get_screen_w(); + frame_h = get_screen_h(); if (frame) free(frame); if (accum) free(accum); @@ -895,13 +318,140 @@ void update_frame_texture(void) frame[pixel_index] = scale(accum[pixel_index], 1.0f / accum_counts[i / column_w]); } - glBindTexture(GL_TEXTURE_2D, frame_texture); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, frame_w, frame_h, 0, GL_RGB, GL_FLOAT, frame); - glBindTexture(GL_TEXTURE_2D, 0); - + move_frame_to_the_gpu(frame_w, frame_h, frame); os_mutex_unlock(&frame_mutex); } +int main(int argc, char **argv) +{ + char *scene_file; + parse_arguments_or_exit(argc, argv, &num_columns, &init_scale, &scene_file); + + if (!parse_scene_file(scene_file, &scene)) + return -1; + + const char *faces[] = { + [CF_RIGHT] = "assets/skybox/right.jpg", + [CF_LEFT] = "assets/skybox/left.jpg", + [CF_TOP] = "assets/skybox/top.jpg", + [CF_BOTTOM] = "assets/skybox/bottom.jpg", + [CF_FRONT] = "assets/skybox/front.jpg", + [CF_BACK] = "assets/skybox/back.jpg", + }; + load_cubemap(&skybox, faces); + + startup_window_and_opengl_context_or_exit(2 * 640, 2 * 480, "Ray Tracing"); + + os_thread workers[MAX_COLUMNS]; + start_workers(workers); + + for (;;) { + + bool exit = false; + for (;;) { + + double mouse_x; + double mouse_y; + int event = pop_event(&mouse_x, &mouse_y); + + float speed = 0.5; + if (event == EVENT_CLOSE || event == EVENT_PRESS_ESC) { + + exit = true; + break; + + } else if (event == EVENT_PRESS_W || event == EVENT_AGAIN_W) { + + move_camera(UP, speed); + invalidate_accumulation(); + + } else if (event == EVENT_PRESS_A || event == EVENT_AGAIN_A) { + + move_camera(LEFT, speed); + invalidate_accumulation(); + + } else if (event == EVENT_PRESS_S || event == EVENT_AGAIN_S) { + + move_camera(DOWN, speed); + invalidate_accumulation(); + + } else if (event == EVENT_PRESS_D || event == EVENT_AGAIN_D) { + + move_camera(RIGHT, speed); + invalidate_accumulation(); + + } else if (event == EVENT_MOVE_MOUSE) { + + rotate_camera(mouse_x, mouse_y); + invalidate_accumulation(); + + } else if (event == EVENT_PRESS_SPACE) { + screenshot(); + } + } + if (exit) break; + + update_frame_texture(); + draw_frame(); + } + + stop_workers(workers); + free_cubemap(&skybox); + cleanup_window_and_opengl_context(); + return 0; +} + +void parse_arguments_or_exit(int argc, char **argv, int *num_columns, int *init_scale, char **scene_file) +{ + *scene_file = NULL; + *num_columns = -1; + *init_scale = 8; + for (int i = 1; i < argc; i++) { + if (!strcmp(argv[i], "--init-scale")) { + i++; + if (i == argc) { + fprintf(stderr, "Error: --threads option is missing the count\n"); + exit(-1); + } + *init_scale = atoi(argv[i]); + if (*init_scale != 1 && *init_scale != 2 && *init_scale != 4 && *init_scale != 8 && *init_scale != 16) { + fprintf(stderr, "Error: Invalid value for --init-scale. It must be a power of 2 between 1 and 16 (included)\n"); + exit(-1); + } + } else if (!strcmp(argv[i], "--threads")) { + i++; + if (i == argc) { + fprintf(stderr, "Error: --threads option is missing the count\n"); + exit(-1); + } + *num_columns = atoi(argv[i]); + if (*num_columns == 0) { + fprintf(stderr, "Error: Invalid count for --threads\n"); + exit(-1); + } + } else if (!strcmp(argv[i], "--scene")) { + i++; + if (i == argc) { + fprintf(stderr, "Error: --scene option is missing the file path\n"); + exit(-1); + } + *scene_file = argv[i]; + } else { + fprintf(stderr, "Warning: Ignoring option %s\n", argv[i]); + } + } + if (*scene_file == NULL) { + fprintf(stderr, "Error: No scene specified (you should use --scene )\n"); + exit(-1); + } + if (*num_columns < 0) { + fprintf(stderr, "Error: Missing --threads option\n"); + exit(-1); + } + if (*num_columns > MAX_COLUMNS) + *num_columns = MAX_COLUMNS; +} + // Must be executed on the main thread void screenshot(void) { @@ -946,629 +496,25 @@ void screenshot(void) fprintf(stderr, "Took screenshot! (%s)\n", file); } -bool parse_scene_file(char *file, Scene *scene); - -int main(int argc, char **argv) +void start_workers(os_thread *workers) { - char *scene_file = NULL; - num_columns = -1; - init_scale = 8; - for (int i = 1; i < argc; i++) { - if (!strcmp(argv[i], "--init-scale")) { - i++; - if (i == argc) { - fprintf(stderr, "Error: --threads option is missing the count\n"); - return -1; - } - init_scale = atoi(argv[i]); - if (init_scale != 1 && init_scale != 2 && init_scale != 4 && init_scale != 8 && init_scale != 16) { - fprintf(stderr, "Error: Invalid value for --init-scale. It must be a power of 2 between 1 and 16 (included)\n"); - return -1; - } - } else if (!strcmp(argv[i], "--threads")) { - i++; - if (i == argc) { - fprintf(stderr, "Error: --threads option is missing the count\n"); - return -1; - } - num_columns = atoi(argv[i]); - if (num_columns == 0) { - fprintf(stderr, "Error: Invalid count for --threads\n"); - return -1; - } - } else if (!strcmp(argv[i], "--scene")) { - i++; - if (i == argc) { - fprintf(stderr, "Error: --scene option is missing the file path\n"); - return -1; - } - scene_file = argv[i]; - } else { - fprintf(stderr, "Warning: Ignoring option %s\n", argv[i]); - } - } - if (scene_file == NULL) { - fprintf(stderr, "Error: No scene specified (you should use --scene )\n"); - return -1; - } - if (num_columns < 0) { - fprintf(stderr, "Error: Missing --threads option\n"); - return -1; - } - if (num_columns > MAX_COLUMNS) - num_columns = MAX_COLUMNS; - - if (!parse_scene_file(scene_file, &scene)) - return -1; - - const char *faces[] = { - [CF_RIGHT] = "assets/skybox/right.jpg", - [CF_LEFT] = "assets/skybox/left.jpg", - [CF_TOP] = "assets/skybox/top.jpg", - [CF_BOTTOM] = "assets/skybox/bottom.jpg", - [CF_FRONT] = "assets/skybox/front.jpg", - [CF_BACK] = "assets/skybox/back.jpg", - }; - load_cubemap(&skybox, faces); - - glfwSetErrorCallback(error_callback); - - if (!glfwInit()) - return -1; - - glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); - glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); - glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); - - int window_w = 2 * 640; - int window_h = 2 * 480; - GLFWwindow *window = glfwCreateWindow(window_w, window_h, "Path Trace", NULL, NULL); - if (!window) { - glfwTerminate(); - return -1; - } - - glfwSetKeyCallback(window, key_callback); - glfwSetFramebufferSizeCallback(window, framebuffer_size_callback); - glfwSetCursorPosCallback(window, cursor_pos_callback); - glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED); - - glfwMakeContextCurrent(window); - - if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) { - printf("Failed to initialize GLAD\n"); - return -1; - } - - glfwSwapInterval(1); - - glfwGetWindowSize(window, &screen_w, &screen_h); - os_mutex_create(&frame_mutex); - os_thread workers[MAX_COLUMNS]; - for (int i = 0; i < num_columns; i++) os_condvar_create(&accum_conds[i]); for (int i = 0; i < num_columns; i++) os_thread_create(&workers[i], (void*) i, worker); +} - unsigned int screen_program = compile_shader("assets/screen.vs", "assets/screen.fs"); - if (!screen_program) { printf("Couldn't compile program\n"); return -1; } - set_uniform_i(screen_program, "screenTexture", 0); - - unsigned int vao, vbo; - { - float vertices[] = { - // positions // texCoords - -1.0f, 1.0f, 0.0f, 1.0f, - -1.0f, -1.0f, 0.0f, 0.0f, - 1.0f, -1.0f, 1.0f, 0.0f, - - -1.0f, 1.0f, 0.0f, 1.0f, - 1.0f, -1.0f, 1.0f, 0.0f, - 1.0f, 1.0f, 1.0f, 1.0f - }; - - glGenVertexArrays(1, &vao); - glGenBuffers(1, &vbo); - - glBindVertexArray(vao); - - glBindBuffer(GL_ARRAY_BUFFER, vbo); - glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), &vertices, GL_STATIC_DRAW); - - glEnableVertexAttribArray(0); - glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*)0); - - glEnableVertexAttribArray(1); - glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*)(2 * sizeof(float))); - } - - glGenTextures(1, &frame_texture); - glBindTexture(GL_TEXTURE_2D, frame_texture); - 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); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - - while (!glfwWindowShouldClose(window)) { - - glfwGetWindowSize(window, &screen_w, &screen_h); - - float speed = 0.5; - if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS) { move_camera(UP, speed); invalidate_accumulation(); } - if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS) { move_camera(DOWN, speed); invalidate_accumulation(); } - if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS) { move_camera(LEFT, speed); invalidate_accumulation(); } - if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS) { move_camera(RIGHT, speed); invalidate_accumulation(); } - - Vector3 clear_color = {1, 1, 1}; - - update_frame_texture(); - - glViewport(0, 0, screen_w, screen_h); - glClearColor(clear_color.x, clear_color.y, clear_color.z, 1.0f); - glClearStencil(0); - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); - - glUseProgram(screen_program); - glActiveTexture(GL_TEXTURE0); - glBindTexture(GL_TEXTURE_2D, frame_texture); - glBindVertexArray(vao); - glDrawArrays(GL_TRIANGLES, 0, 6); - glBindVertexArray(0); - - glfwSwapBuffers(window); - glfwPollEvents(); - } - +void stop_workers(os_thread *workers) +{ os_mutex_lock(&frame_mutex); - stop_workers = true; + workers_should_stop = true; os_mutex_unlock(&frame_mutex); for (int i = 0; i < num_columns; i++) os_thread_join(workers[i]); for (int i = 0; i < num_columns; i++) os_condvar_delete(&accum_conds[i]); - - free_cubemap(&skybox); - glfwDestroyWindow(window); - glfwTerminate(); - return 0; -} - -typedef enum { - PROP_ALBEDO, - PROP_ROUGHNESS, - PROP_REFLECTANCE, - PROP_METALLIC, - PROP_EMISSION_POWER, - PROP_EMISSION_COLOR, - PROP_RADIUS, - PROP_CENTER, - PROP_ORIGIN, - PROP_SIZE, -} Property; - -bool parse_scene_string(char *src, size_t len, Scene *scene) -{ - scene->num_objects = 0; - - int line = 1; - size_t i = 0; - for (;;) { - - while (i < len && is_space(src[i])) { - if (src[i] == '\n') line++; - i++; - } - - if (i == len) - break; - - Object object; - - if (5 < len - i - && src[i+0] == 's' - && src[i+1] == 'p' - && src[i+2] == 'h' - && src[i+3] == 'e' - && src[i+4] == 'r' - && src[i+5] == 'e') { - object.type = OBJECT_SPHERE; - object.sphere.center = (Vector3) {0, 0, 0}; - object.sphere.radius = 1; - object.material.albedo = (Vector3) {0.44, 0.68, 0.84}; - object.material.roughness = 0; - object.material.reflectance = 0.2; - object.material.metallic = 0; - object.material.emission_power = 0; - object.material.emission_color = (Vector3) {1, 1, 1}; - i += 6; - } else if (3 < len - i - && src[i+0] == 'c' - && src[i+1] == 'u' - && src[i+2] == 'b' - && src[i+3] == 'e') { - object.type = OBJECT_CUBE; - object.cube.origin = (Vector3) {0, 0, 0}; - object.cube.size = (Vector3) {1, 1, 1}; - object.material.albedo = (Vector3) {0.44, 0.68, 0.84}; - object.material.roughness = 0; - object.material.reflectance = 0.2; - object.material.metallic = 0; - object.material.emission_power = 0; - object.material.emission_color = (Vector3) {1, 1, 1}; - i += 4; - } else { - fprintf(stderr, "Error: Invalid character (line %d)\n", line); - return false; - } - - for (;;) { - // Skip spaces before property - while (i < len && is_space(src[i])) { - if (src[i] == '\n') line++; - i++; - } - - int valuetype; // 0 for float, 1 for color (3 floats) - - Property prop; - if (6 < len - i - && src[i+0] == 'a' - && src[i+1] == 'l' - && src[i+2] == 'b' - && src[i+3] == 'e' - && src[i+4] == 'd' - && src[i+5] == 'o') { - valuetype = 1; - prop = PROP_ALBEDO; - i += 9; - } else if (8 < len - i - && src[i+0] == 'r' - && src[i+1] == 'o' - && src[i+2] == 'u' - && src[i+3] == 'g' - && src[i+4] == 'h' - && src[i+5] == 'n' - && src[i+6] == 'e' - && src[i+7] == 's' - && src[i+8] == 's') { - valuetype = 0; - prop = PROP_ROUGHNESS; - i += 9; - } else if (10 < len - i - && src[i+0] == 'r' - && src[i+1] == 'e' - && src[i+2] == 'f' - && src[i+3] == 'l' - && src[i+4] == 'e' - && src[i+5] == 'c' - && src[i+6] == 't' - && src[i+7] == 'a' - && src[i+8] == 'n' - && src[i+9] == 'c' - && src[i+10] == 'e') { - valuetype = 0; - prop = PROP_REFLECTANCE; - i += 11; - } else if (7 < len - i - && src[i+0] == 'm' - && src[i+1] == 'e' - && src[i+2] == 't' - && src[i+3] == 'a' - && src[i+4] == 'l' - && src[i+5] == 'l' - && src[i+6] == 'i' - && src[i+7] == 'c') { - valuetype = 0; - prop = PROP_METALLIC; - i += 11; - } else if (13 < len - i - && src[i+0] == 'e' - && src[i+1] == 'm' - && src[i+2] == 'i' - && src[i+3] == 's' - && src[i+4] == 's' - && src[i+5] == 'i' - && src[i+6] == 'o' - && src[i+7] == 'n' - && src[i+8] == '_' - && src[i+9] == 'p' - && src[i+10] == 'o' - && src[i+11] == 'w' - && src[i+12] == 'e' - && src[i+13] == 'r') { - valuetype = 0; - prop = PROP_EMISSION_POWER; - i += 14; - } else if (13 < len - i - && src[i+0] == 'e' - && src[i+1] == 'm' - && src[i+2] == 'i' - && src[i+3] == 's' - && src[i+4] == 's' - && src[i+5] == 'i' - && src[i+6] == 'o' - && src[i+7] == 'n' - && src[i+8] == '_' - && src[i+9] == 'c' - && src[i+10] == 'o' - && src[i+11] == 'l' - && src[i+12] == 'o' - && src[i+13] == 'r') { - valuetype = 1; - prop = PROP_EMISSION_COLOR; - i += 14; - } else if (5 < len - i - && src[i+0] == 'r' - && src[i+1] == 'a' - && src[i+2] == 'd' - && src[i+3] == 'i' - && src[i+4] == 'u' - && src[i+5] == 's') { - if (object.type != OBJECT_SPHERE) { - fprintf(stderr, "Poperty 'radius' only allowed on spheres (line %d)\n", line); - return false; - } - valuetype = 0; - prop = PROP_RADIUS; - i += 6; - } else if (5 < len - i - && src[i+0] == 'c' - && src[i+1] == 'e' - && src[i+2] == 'n' - && src[i+3] == 't' - && src[i+4] == 'e' - && src[i+5] == 'r') { - if (object.type != OBJECT_SPHERE) { - fprintf(stderr, "Poperty 'center' only allowed on spheres (line %d)\n", line); - return false; - } - valuetype = 1; - prop = PROP_CENTER; - i += 6; - } else if (5 < len - i - && src[i+0] == 'o' - && src[i+1] == 'r' - && src[i+2] == 'i' - && src[i+3] == 'g' - && src[i+4] == 'i' - && src[i+5] == 'n') { - if (object.type != OBJECT_CUBE) { - fprintf(stderr, "Poperty 'origin' only allowed on cubes (line %d)\n", line); - return false; - } - valuetype = 1; - prop = PROP_ORIGIN; - i += 6; - } else if (3 < len - i - && src[i+0] == 's' - && src[i+1] == 'i' - && src[i+2] == 'z' - && src[i+3] == 'e') { - if (object.type != OBJECT_CUBE) { - fprintf(stderr, "Poperty 'size' only allowed on cubes (line %d)\n", line); - return false; - } - valuetype = 1; - prop = PROP_SIZE; - i += 4; - } else - // Not a valid property name - break; - - // Consume spaces before the value - while (i < len && is_space(src[i])) { - if (src[i] == '\n') line++; - i++; - } - if (i == len) { - fprintf(stderr, "Error: Property value is missing (line %d)\n", line); - return false; - } - - float value0; - Vector3 value1; - if (valuetype == 0) { - // Parse a single float - int sign = 1; - if (src[i] == '-') { - sign = -1; - i++; - if (i == len || !is_digit(src[i])) { - fprintf(stderr, "Error: Missing number after minus sign (line %d)\n", line); - return false; - } - } else if (!is_digit(src[i])) { - fprintf(stderr, "Error: Missing number after property name (line %d)\n", line); - return false; - } - value0 = 0; - do { - int d = src[i] - '0'; - value0 = value0 * 10 + d; - i++; - } while (i < len && is_digit(src[i])); - if (i < len && src[i] == '.') { - i++; // Skip the dot - if (i == len || !is_digit(src[i])) { - fprintf(stderr, "Error: Missing decimal part after dot (line %d)\n", line); - return false; - } - float q = 1.0f / 10; - do { - int d = src[i] - '0'; - value0 += q * d; - q /= 10; - i++; - } while (i < len && is_digit(src[i])); - } - value0 *= sign; - } else { - assert(valuetype == 1); - - if (src[i] != '{') { - fprintf(stderr, "Error: Missing '{' after property name (line %d)\n", line); - return false; - } - i++; - - float temp[3]; - for (int j = 0; j < 3; j++) { - - while (i < len && is_space(src[i])) { - if (src[i] == '\n') line++; - i++; - } - - int sign = 1; - if (src[i] == '-') { - sign = -1; - i++; - if (i == len || !is_digit(src[i])) { - fprintf(stderr, "Error: Missing number after minus sign (line %d)\n", line); - return false; - } - } else if (!is_digit(src[i])) { - fprintf(stderr, "Error: Missing number %d in vector value (line %d)\n", j, line); - return false; - } - temp[j] = 0; - do { - int d = src[i] - '0'; - temp[j] = temp[j] * 10 + d; - i++; - } while (i < len && is_digit(src[i])); - if (i < len && src[i] == '.') { - i++; // Skip the dot - if (i == len || !is_digit(src[i])) { - fprintf(stderr, "Error: Missing decimal part after dot (line %d)\n", line); - return false; - } - float q = 1.0f / 10; - do { - int d = src[i] - '0'; - temp[j] += q * d; - q /= 10; - i++; - } while (i < len && is_digit(src[i])); - } - temp[j] *= sign; - } - - while (i < len && is_space(src[i])) { - if (src[i] == '\n') line++; - i++; - } - - if (i == len || src[i] != '}') { - fprintf(stderr, "Error: Missing '}' after property value (line %d)\n", line); - return false; - } - i++; - - value1.x = temp[0]; - value1.y = temp[1]; - value1.z = temp[2]; - } - - switch (prop) { - - case PROP_ALBEDO: - if (value1.x < 0 || value1.x > 1 || - value1.y < 0 || value1.y > 1 || - value1.z < 0 || value1.z > 1) { - fprintf(stderr, "Error: albedo values must be between 0 and 1 (line %d)\n", line); - return false; - } - object.material.albedo = value1; - break; - - case PROP_ROUGHNESS: - if (value0 < 0 || value0 > 1) { - fprintf(stderr, "Error: Roughness must be between 0 and 1 (line %d)\n", line); - return false; - } - object.material.roughness = value0; - break; - - case PROP_REFLECTANCE: - if (value0 < 0 || value0 > 1) { - fprintf(stderr, "Error: Reflectance must be between 0 and 1 (line %d)\n", line); - return false; - } - object.material.reflectance = value0; - break; - - case PROP_METALLIC: - if (value0 < 0 || value0 > 1) { - fprintf(stderr, "Error: Metallic must be between 0 and 1 (line %d)\n", line); - return false; - } - object.material.metallic = value0; - break; - - case PROP_EMISSION_POWER: - object.material.emission_power = value0; - break; - - case PROP_EMISSION_COLOR: - if (value1.x < 0 || value1.x > 1 || - value1.y < 0 || value1.y > 1 || - value1.z < 0 || value1.z > 1) { - fprintf(stderr, "Error: Emission color values must be between 0 and 1 (line %d)\n", line); - return false; - } - object.material.emission_color = value1; - break; - - case PROP_RADIUS: - object.sphere.radius = value0; - break; - - case PROP_CENTER: - object.sphere.center = value1; - break; - - case PROP_ORIGIN: - object.cube.origin = value1; - break; - - case PROP_SIZE: - if (value1.x < 0 || value1.y < 0 || value1.z < 0) { - fprintf(stderr, "Error: Size values must be positive (line %d)\n", line); - return false; - } - object.cube.size = value1; - break; - } - } - - if (scene->num_objects == MAX_OBJECTS) - fprintf(stderr, "Warning: Ignoring object because the scene is too big (line %d)\n", line); - else - scene->objects[scene->num_objects++] = object; - } - - return true; -} - -bool parse_scene_file(char *file, Scene *scene) -{ - size_t len; - char *src = load_file(file, &len); - if (src == NULL) { - fprintf(stderr, "Error: Couldn't open scene file\n"); - return false; - } - - bool ok = parse_scene_string(src, len, scene); - - free(src); - return ok; } diff --git a/src/mesh.c b/src/mesh.c deleted file mode 100644 index 829a6c2..0000000 --- a/src/mesh.c +++ /dev/null @@ -1,372 +0,0 @@ -/* -This is free and unencumbered software released into the public domain. - -Anyone is free to copy, modify, publish, use, compile, sell, or -distribute this software, either in source code form or as a compiled -binary, for any purpose, commercial or non-commercial, and by any -means. - -In jurisdictions that recognize copyright laws, the author or authors -of this software dedicate any and all copyright interest in the -software to the public domain. We make this dedication for the benefit -of the public at large and to the detriment of our heirs and -successors. We intend this dedication to be an overt act of -relinquishment in perpetuity of all present and future rights to this -software under copyright law. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR -OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, -ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -OTHER DEALINGS IN THE SOFTWARE. - -For more information, please refer to -*/ - -#include -#include -#include -#include "utils.h" -#include "mesh.h" - -#define TINYOBJ_LOADER_C_IMPLEMENTATION -#include "tinyobj_loader_c.h" - -void append_vertex(VertexArray *array, Vertex v) -{ - if (array->size == array->capacity) { - if (array->capacity == 0) { - array->data = malloc(8 * sizeof(Vertex)); - array->capacity = 8; - } else { - array->data = realloc(array->data, 2 * array->capacity * sizeof(Vertex)); - array->capacity *= 2; - } - if (!array->data) { - printf("OUT OF MEMORY\n"); - abort(); - } - } - array->data[array->size++] = v; -} - -static Vector3 get_sphere_point(float angle_x, float angle_y, float radius) -{ - Vector3 p; - p.x = radius * sin(angle_y) * cos(2 * angle_x); - p.y = radius * cos(angle_y); - p.z = radius * sin(angle_y) * sin(2 * angle_x); - return p; -} - -typedef struct { - Vertex a; - Vertex b; - Vertex c; -} Triangle; - -static void calculate_and_set_normals(Triangle *T) -{ - #define X1 (T->b.x - T->a.x) - #define Y1 (T->b.y - T->a.y) - #define Z1 (T->b.z - T->a.z) - - #define X2 (T->c.x - T->a.x) - #define Y2 (T->c.y - T->a.y) - #define Z2 (T->c.z - T->a.z) - - /* - * xnormal = y1*z2 - z1*y2 - * ynormal = z1*x2 - x1*z2 - * znormal = x1*y2 - y1*x2 - */ - - Vector3 n; - n.x = Y1 * Z2 - Z1 * Y2; - n.y = Z1 * X2 - X1 * Z2; - n.z = X1 * Y2 - Y1 * X2; - - T->a.nx = n.x; - T->a.ny = n.y; - T->a.nz = n.z; - - T->b.nx = n.x; - T->b.ny = n.y; - T->b.nz = n.z; - - T->c.nx = n.x; - T->c.ny = n.y; - T->c.nz = n.z; - - #undef X1 - #undef Y1 - #undef Z1 - #undef X2 - #undef Y2 - #undef Z2 -} - -static Triangle make_triangle(Vector3 a, Vector3 b, Vector3 c) -{ - Triangle T; - T.a = (Vertex) {a.x, a.y, a.z}; - T.b = (Vertex) {b.x, b.y, b.z}; - T.c = (Vertex) {c.x, c.y, c.z}; - calculate_and_set_normals(&T); - return T; -} - -VertexArray make_sphere_mesh_2(float radius, int num_segms, bool fake_normals) -{ - VertexArray vertices = {0, 0, 0}; - - int x_num_segms = num_segms; - int y_num_segms = num_segms; - for (int i = 0; i < x_num_segms; i++) - for (int j = 0; j < y_num_segms; j++) { - - int g = j; - if (g == y_num_segms-1) - g = 0; - - Vector3 p1 = get_sphere_point((i + 0) * 3.14 / x_num_segms, (g + 0) * 3.14 / y_num_segms, radius); - Vector3 p2 = get_sphere_point((i + 1) * 3.14 / x_num_segms, (g + 0) * 3.14 / y_num_segms, radius); - Vector3 p3 = get_sphere_point((i + 1) * 3.14 / x_num_segms, (g + 1) * 3.14 / y_num_segms, radius); - Vector3 p4 = get_sphere_point((i + 0) * 3.14 / x_num_segms, (g + 1) * 3.14 / y_num_segms, radius); - - Triangle t1 = make_triangle(p1, p2, p3); - - if (fake_normals) { - t1.a.nx = p1.x; - t1.a.ny = p1.y; - t1.a.nz = p1.z; - - t1.b.nx = p2.x; - t1.b.ny = p2.y; - t1.b.nz = p2.z; - - t1.c.nx = p3.x; - t1.c.ny = p3.y; - t1.c.nz = p3.z; - } - - append_vertex(&vertices, t1.a); - append_vertex(&vertices, t1.b); - append_vertex(&vertices, t1.c); - - Triangle t2 = make_triangle(p4, p1, p3); - - if (fake_normals) { - t2.a.nx = p4.x; - t2.a.ny = p4.y; - t2.a.nz = p4.z; - - t2.b.nx = p1.x; - t2.b.ny = p1.y; - t2.b.nz = p1.z; - - t2.c.nx = p3.x; - t2.c.ny = p3.y; - t2.c.nz = p3.z; - } - - append_vertex(&vertices, t2.a); - append_vertex(&vertices, t2.b); - append_vertex(&vertices, t2.c); - } - return vertices; -} - -VertexArray make_sphere_mesh(float radius) -{ - return make_sphere_mesh_2(radius, 32, true); -} - -VertexArray make_cube_mesh(void) -{ - float vertices[] = { - - 1.0, 1.0, 0.0, 0.0, 0.0, -1.0, - 1.0, 0.0, 0.0, 0.0, 0.0, -1.0, - 0.0, 0.0, 0.0, 0.0, 0.0, -1.0, - 0.0, 1.0, 0.0, 0.0, 0.0, -1.0, - 1.0, 1.0, 0.0, 0.0, 0.0, -1.0, - 0.0, 0.0, 0.0, 0.0, 0.0, -1.0, - - 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, - 1.0, 0.0, 1.0, 0.0, 0.0, 1.0, - 1.0, 1.0, 1.0, 0.0, 0.0, 1.0, - 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, - 1.0, 1.0, 1.0, 0.0, 0.0, 1.0, - 0.0, 1.0, 1.0, 0.0, 0.0, 1.0, - - 0.0, 0.0, 0.0, 0.0, -1.0, 0.0, - 1.0, 0.0, 0.0, 0.0, -1.0, 0.0, - 1.0, 0.0, 1.0, 0.0, -1.0, 0.0, - 0.0, 0.0, 0.0, 0.0, -1.0, 0.0, - 1.0, 0.0, 1.0, 0.0, -1.0, 0.0, - 0.0, 0.0, 1.0, 0.0, -1.0, 0.0, - - 1.0, 1.0, 1.0, 0.0, 1.0, 0.0, - 1.0, 1.0, 0.0, 0.0, 1.0, 0.0, - 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, - 0.0, 1.0, 1.0, 0.0, 1.0, 0.0, - 1.0, 1.0, 1.0, 0.0, 1.0, 0.0, - 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, - - 0.0, 1.0, 1.0, -1.0, 0.0, 0.0, - 0.0, 1.0, 0.0, -1.0, 0.0, 0.0, - 0.0, 0.0, 0.0, -1.0, 0.0, 0.0, - - 0.0, 0.0, 1.0, -1.0, 0.0, 0.0, - 0.0, 1.0, 1.0, -1.0, 0.0, 0.0, - 0.0, 0.0, 0.0, -1.0, 0.0, 0.0, - - 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, - 1.0, 1.0, 0.0, 1.0, 0.0, 0.0, - 1.0, 1.0, 1.0, 1.0, 0.0, 0.0, - 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, - 1.0, 1.0, 1.0, 1.0, 0.0, 0.0, - 1.0, 0.0, 1.0, 1.0, 0.0, 0.0, - }; - - VertexArray result = {0, 0, 0}; - for (int i = 0; i < (int) (sizeof(vertices)/sizeof(vertices[0])); i += 6) { - Vertex v; - v.x = vertices[i + 0]; - v.y = vertices[i + 1]; - v.z = vertices[i + 2]; - v.nx = vertices[i + 3]; - v.ny = vertices[i + 4]; - v.nz = vertices[i + 5]; - v.tx = 0; - v.ty = 0; - append_vertex(&result, v); - } - - return result; -} - -static void -get_file_data_callback(void *context, const char *filename, - const int is_mtl, const char *obj_filename, char **data, size_t *len) -{ - (void) context; - - if (filename == NULL) { - fprintf(stderr, "null filename\n"); - (*data) = NULL; - (*len) = 0; - return; - } - - size_t data_len = 0; - - printf("Reading file '%s'\n", filename); - *data = load_file(filename, &data_len); - (*len) = data_len; - - char **free_me = context; - if (*free_me == NULL) *free_me = *data; -} - -bool load_mesh_from_file(const char *file, VertexArray *result) -{ - - tinyobj_attrib_t attrib; - - tinyobj_shape_t* shapes = NULL; - size_t num_shapes; - - tinyobj_material_t* materials = NULL; - size_t num_materials; - - char *free_me = NULL; - - unsigned int flags = TINYOBJ_FLAG_TRIANGULATE; - int ret = tinyobj_parse_obj(&attrib, &shapes, &num_shapes, &materials, &num_materials, file, get_file_data_callback, &free_me, flags); - if (ret != TINYOBJ_SUCCESS) { - if (free_me) free(free_me); - printf("Failed loading '%s'\n", file); - return false; - } - - *result = (VertexArray) {0, 0, 0}; - - size_t face_offset = 0; - for (int i = 0; i < (int) attrib.num_face_num_verts; i++) { - - assert(attrib.face_num_verts[i] % 3 == 0); /* assume all triangle faces. */ - for (size_t f = 0; f < (size_t)attrib.face_num_verts[i] / 3; f++) { - - tinyobj_vertex_index_t idx0 = attrib.faces[face_offset + f * 3 + 0]; - tinyobj_vertex_index_t idx1 = attrib.faces[face_offset + f * 3 + 1]; - tinyobj_vertex_index_t idx2 = attrib.faces[face_offset + f * 3 + 2]; - - Vertex v0; - Vertex v1; - Vertex v2; - - /* - * Positions - */ - - v0.x = attrib.vertices[idx0.v_idx * 3 + 0]; - v0.y = attrib.vertices[idx0.v_idx * 3 + 1]; - v0.z = attrib.vertices[idx0.v_idx * 3 + 2]; - - v1.x = attrib.vertices[idx1.v_idx * 3 + 0]; - v1.y = attrib.vertices[idx1.v_idx * 3 + 1]; - v1.z = attrib.vertices[idx1.v_idx * 3 + 2]; - - v2.x = attrib.vertices[idx2.v_idx * 3 + 0]; - v2.y = attrib.vertices[idx2.v_idx * 3 + 1]; - v2.z = attrib.vertices[idx2.v_idx * 3 + 2]; - - /* - * Normals - */ - - v0.nx = attrib.normals[idx0.vn_idx * 3 + 0]; - v0.ny = attrib.normals[idx0.vn_idx * 3 + 1]; - v0.nz = attrib.normals[idx0.vn_idx * 3 + 2]; - - v1.nx = attrib.normals[idx1.vn_idx * 3 + 0]; - v1.ny = attrib.normals[idx1.vn_idx * 3 + 1]; - v1.nz = attrib.normals[idx1.vn_idx * 3 + 2]; - - v2.nx = attrib.normals[idx2.vn_idx * 3 + 0]; - v2.ny = attrib.normals[idx2.vn_idx * 3 + 1]; - v2.nz = attrib.normals[idx2.vn_idx * 3 + 2]; - - /* - * Texture coordinates - */ - - v0.tx = attrib.normals[idx0.vt_idx * 2 + 0]; - v0.ty = attrib.normals[idx0.vt_idx * 2 + 1]; - - v1.tx = attrib.normals[idx1.vt_idx * 2 + 0]; - v1.ty = attrib.normals[idx1.vt_idx * 2 + 1]; - - v2.tx = attrib.normals[idx2.vt_idx * 2 + 0]; - v2.ty = attrib.normals[idx2.vt_idx * 2 + 1]; - - append_vertex(result, v0); - append_vertex(result, v1); - append_vertex(result, v2); - } - - face_offset += (size_t)attrib.face_num_verts[i]; - } - - if (free_me) - free(free_me); - - tinyobj_attrib_free(&attrib); - tinyobj_shapes_free(shapes, num_shapes); - tinyobj_materials_free(materials, num_materials); - return true; -} \ No newline at end of file diff --git a/src/mesh.h b/src/mesh.h deleted file mode 100644 index 088be60..0000000 --- a/src/mesh.h +++ /dev/null @@ -1,48 +0,0 @@ -/* -This is free and unencumbered software released into the public domain. - -Anyone is free to copy, modify, publish, use, compile, sell, or -distribute this software, either in source code form or as a compiled -binary, for any purpose, commercial or non-commercial, and by any -means. - -In jurisdictions that recognize copyright laws, the author or authors -of this software dedicate any and all copyright interest in the -software to the public domain. We make this dedication for the benefit -of the public at large and to the detriment of our heirs and -successors. We intend this dedication to be an overt act of -relinquishment in perpetuity of all present and future rights to this -software under copyright law. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR -OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, -ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -OTHER DEALINGS IN THE SOFTWARE. - -For more information, please refer to -*/ - -#include "vector.h" - -typedef struct { - float x, y, z; - float nx, ny, nz; - float tx, ty; -} Vertex; - -typedef struct { - Vertex *data; - int size; - int capacity; -} VertexArray; - -void append_vertex(VertexArray *array, Vertex v); - -VertexArray make_sphere_mesh(float radius); -VertexArray make_sphere_mesh_2(float radius, int num_segms, bool fake_normals); -VertexArray make_cube_mesh(void); - -bool load_mesh_from_file(const char *file, VertexArray *result); \ No newline at end of file diff --git a/src/os.c b/src/os.c new file mode 100644 index 0000000..97acfc6 --- /dev/null +++ b/src/os.c @@ -0,0 +1,419 @@ +/* +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to +*/ +#define _GNU_SOURCE +#define _POSIX_C_SOURCE 1999309L + +#include +#include +#include + +#ifdef _WIN32 +#define WIN32_MEAN_AND_LEAN +#include +#endif + +#ifdef __linux__ +#include +#include +#include +#endif + +//#define SYNC_PRINT_ERRORS +#ifdef SYNC_PRINT_ERRORS +#include +#include +#endif + +#include "os.h" + +/* + * Returns the current absolute time in microsecods + * TODO: Specify since when the time is calculated + */ +uint64_t get_absolute_time_us(void) +{ +#ifdef _WIN32 + FILETIME filetime; + GetSystemTimePreciseAsFileTime(&filetime); + uint64_t time = (uint64_t) filetime.dwLowDateTime | ((uint64_t) filetime.dwHighDateTime << 32); + time /= 10; + return time; +#else + struct timespec buffer; + if (clock_gettime(CLOCK_REALTIME, &buffer)) + abort(); + uint64_t time = buffer.tv_sec * 1000000 + buffer.tv_nsec / 1000; + return time; +#endif +} + +/* + * Returns the current time in nanoseconds since + * an unspecified point in time. + */ +uint64_t get_relative_time_ns(void) +{ +#ifdef _WIN32 + { + int64_t count; + int64_t freq; + int ok; + + ok = QueryPerformanceCounter((LARGE_INTEGER*) &count); + if (!ok) abort(); + + ok = QueryPerformanceFrequency((LARGE_INTEGER*) &freq); + if (!ok) abort(); + + uint64_t res = 1000000000 * (double) count / freq; + return res; + } +#else + { + struct timespec time; + + if (clock_gettime(CLOCK_REALTIME, &time)) + abort(); + + uint64_t res; + + uint64_t sec = time.tv_sec; + if (sec > UINT64_MAX / 1000000000) + abort(); + res = sec * 1000000000; + + uint64_t nsec = time.tv_nsec; + if (res > UINT64_MAX - nsec) + abort(); + res += nsec; + + return res; + } +#endif +} + +void sleep_ms(float ms) +{ +#ifdef _WIN32 + Sleep(ms); +#else + usleep(ms*1000); +#endif +} + +void os_mutex_create(os_mutex_t *mutex) +{ + +#if defined(_WIN32) + InitializeCriticalSection(mutex); +#elif defined(__linux__) + if (pthread_mutex_init(mutex, NULL)) + abort(); +#else + (void) mutex; +#endif + +} + +void os_mutex_delete(os_mutex_t *mutex) +{ + +#if defined(_WIN32) + DeleteCriticalSection(mutex); +#elif defined(__linux__) + if (pthread_mutex_destroy(mutex)) + abort(); +#else + (void) mutex; +#endif + +} + +void os_mutex_lock(os_mutex_t *mutex) +{ + +#if defined(_WIN32) + EnterCriticalSection(mutex); +#elif defined(__linux__) + if (pthread_mutex_lock(mutex)) + abort(); +#else + (void) mutex; +#endif + +} + +void os_mutex_unlock(os_mutex_t *mutex) +{ + +#if defined(_WIN32) + LeaveCriticalSection(mutex); +#elif defined(__linux__) + if (pthread_mutex_unlock(mutex)) + abort(); +#else + (void) mutex; +#endif + +} + +void os_condvar_create(os_condvar_t *condvar) +{ + +#if defined(_WIN32) + InitializeConditionVariable(condvar); +#elif defined(__linux__) + if (pthread_cond_init(condvar, NULL)) + abort(); +#else + (void) condvar; +#endif + +} + +void os_condvar_delete(os_condvar_t *condvar) +{ + +#if defined(__linux__) + if (pthread_cond_destroy(condvar)) + abort(); +#else + (void) condvar; +#endif + +} + +bool os_condvar_wait(os_condvar_t *condvar, os_mutex_t *mutex, int timeout_ms) +{ + +#if defined(_WIN32) + + DWORD timeout = INFINITE; + if (timeout_ms >= 0) timeout = timeout_ms; + if (!SleepConditionVariableCS(condvar, mutex, timeout)) { + if (GetLastError() == ERROR_TIMEOUT) { + return false; + } + abort(); + } + + return true; + +#elif defined(__linux__) + + int err; + if (timeout_ms < 0) + err = pthread_cond_wait(condvar, mutex); + else { + uint64_t wakeup_ms = (uint64_t) timeout_ms + get_absolute_time_us() / 1000; + struct timespec abstime = { + .tv_sec = wakeup_ms / 1000, + .tv_nsec = (wakeup_ms % 1000) * 1000000, + }; + err = pthread_cond_timedwait(condvar, mutex, &abstime); + } + if (err) { + if (err == ETIMEDOUT) { + return false; + } + #ifdef SYNC_PRINT_ERRORS + fprintf(stderr, "ERROR!! pthread_cond_wait/timedwait: %s\n", strerror(err)); + #endif + abort(); + } + + return true; + +#else + (void) condvar; +#endif +} + +void os_condvar_signal(os_condvar_t *condvar) +{ + +#if defined(_WIN32) + WakeConditionVariable(condvar); +#elif defined(__linux__) + if (pthread_cond_signal(condvar)) + abort(); +#else + (void) condvar; +#endif + +} + +void semaphore_create(semaphore_t *sem, int count) +{ + sem->count = count; + os_mutex_create(&sem->mutex); + os_condvar_create(&sem->cond); + +} + +void semaphore_delete(semaphore_t *sem) +{ + os_mutex_delete(&sem->mutex); + os_condvar_delete(&sem->cond); +} + +bool semaphore_wait(semaphore_t *sem, int count, int timeout_ms) +{ + assert(count > 0); + + uint64_t start_time_ms = get_relative_time_ns() / 1000000; + + os_mutex_lock(&sem->mutex); + while (sem->count < count) { + + uint64_t current_time_ms = get_relative_time_ns() / 1000000; + + int remaining_ms = -1; + if (timeout_ms >= 0) + remaining_ms = timeout_ms - (int) (current_time_ms - start_time_ms); + + if (!os_condvar_wait(&sem->cond, &sem->mutex, remaining_ms)) { + os_mutex_unlock(&sem->mutex); + return false; + } + } + sem->count -= count; + os_mutex_unlock(&sem->mutex); + + return true; +} + +void semaphore_signal(semaphore_t *sem, int count) +{ + assert(count > 0); + + os_mutex_lock(&sem->mutex); + sem->count += count; + if (sem->count > 0) + os_condvar_signal(&sem->cond); + os_mutex_unlock(&sem->mutex); +} + +bool os_semaphore_create(os_semaphore_t *sem, int count, int max) +{ + int ok; + +#ifdef _WIN32 + SECURITY_ATTRIBUTES *attr = NULL; // Default + const char *name = NULL; // No name + void *handle = CreateSemaphoreA(attr, count, max, name); + if (handle == NULL) + return false; + sem->data = handle; + ok = 1; +#else + (void) max; // POSIX doesn't use this + ok = sem_init(&sem->data, 0, count) == 0; +#endif + + return ok; +} + +bool os_semaphore_delete(os_semaphore_t *sem) +{ + int ok; + +#ifdef _WIN32 + CloseHandle(sem->data); + ok = 1; +#else + ok = sem_destroy(&sem->data) == 0; +#endif + + return ok; +} + +bool os_semaphore_wait(os_semaphore_t *sem) +{ + int ok; + +#ifdef _WIN32 + ok = WaitForSingleObject(sem->data, INFINITE) == WAIT_OBJECT_0; +#else + ok = sem_wait(&sem->data) == 0; +#endif + + return ok; +} + +bool os_semaphore_signal(os_semaphore_t *sem) +{ + int ok; + +#ifdef _WIN32 + ok = ReleaseSemaphore(sem->data, 1, NULL); +#else + ok = sem_post(&sem->data) == 0; +#endif + + return ok; +} + +void os_thread_create(os_thread *thread, void *arg, os_threadreturn (*func)(void*)) +{ +#ifdef _WIN32 + os_thread thread_ = CreateThread(NULL, 0, func, arg, 0, NULL); + if (thread_ == INVALID_HANDLE_VALUE) + abort(); + *thread = thread_; +#elif defined(__linux__) + int ret = pthread_create(thread, NULL, func, arg); + if (ret) abort(); +#endif +} + +os_threadreturn os_thread_join(os_thread thread) +{ +#ifdef _WIN32 + os_threadreturn result; + WaitForSingleObject(thread, INFINITE); + if (!GetExitCodeThread(thread, &result)) + abort(); + CloseHandle(thread); + return result; +#elif defined(__linux__) + os_threadreturn result; + int ret = pthread_join(thread, &result); + if (ret) abort(); + return result; +#else + (void) thread; +#endif +} + +uint64_t get_thread_id(void) +{ + static _Atomic uint64_t next_id = 1; + static _Thread_local uint64_t id = 0; + if (id == 0) id = atomic_fetch_add(&next_id, 1); + return id; +} \ No newline at end of file diff --git a/src/sync.h b/src/os.h similarity index 81% rename from src/sync.h rename to src/os.h index ca90d5c..d5db01b 100644 --- a/src/sync.h +++ b/src/os.h @@ -28,6 +28,8 @@ For more information, please refer to #include #include "profile.h" +// TODO: Clean up this file + #ifdef _WIN32 #define WIN32_MEAN_AND_LEAN #include @@ -54,6 +56,10 @@ typedef struct { os_condvar_t cond; } semaphore_t; +uint64_t get_absolute_time_us(void); +uint64_t get_relative_time_ns(void); +void sleep_ms(float ms); + void os_mutex_create(os_mutex_t *mutex); void os_mutex_delete(os_mutex_t *mutex); void os_mutex_lock (os_mutex_t *mutex); @@ -74,4 +80,21 @@ bool os_semaphore_delete(os_semaphore_t *sem); bool os_semaphore_wait (os_semaphore_t *sem); bool os_semaphore_signal(os_semaphore_t *sem); -profile_results_t sync_profile_results(void); \ No newline at end of file +#include +#include + +#if defined(_WIN32) +#define WIN32_MEAN_AND_LEAN +#include +typedef void *os_thread; +typedef unsigned long os_threadreturn; +#elif defined(__linux__) +#include +typedef pthread_t os_thread; +typedef void *os_threadreturn; +#endif + +uint64_t get_thread_id(void); + +void os_thread_create(os_thread *thread, void *arg, os_threadreturn (*func)(void*)); +os_threadreturn os_thread_join(os_thread thread); \ No newline at end of file diff --git a/src/profile.c b/src/profile.c deleted file mode 100644 index 77d2aa4..0000000 --- a/src/profile.c +++ /dev/null @@ -1,97 +0,0 @@ -/* -This is free and unencumbered software released into the public domain. - -Anyone is free to copy, modify, publish, use, compile, sell, or -distribute this software, either in source code form or as a compiled -binary, for any purpose, commercial or non-commercial, and by any -means. - -In jurisdictions that recognize copyright laws, the author or authors -of this software dedicate any and all copyright interest in the -software to the public domain. We make this dedication for the benefit -of the public at large and to the detriment of our heirs and -successors. We intend this dedication to be an overt act of -relinquishment in perpetuity of all present and future rights to this -software under copyright law. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR -OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, -ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -OTHER DEALINGS IN THE SOFTWARE. - -For more information, please refer to -*/ - -#include -#include "profile.h" - -void human_readable_time_interval(uint64_t ns, char *dst, size_t max) -{ - if (ns > 1000000000) - snprintf(dst, max, "%.1Lf s", (long double) ns / 1000000000); - else if (ns > 1000000) - snprintf(dst, max, "%.1Lf ms", (long double) ns / 1000000); - else if (ns > 1000) - snprintf(dst, max, "%.1Lf us", (long double) ns / 1000); - else - snprintf(dst, max, "%.1Lf ns", (long double) ns); -} - -static int -sort_callback(const void *a, const void *b) -{ - const profile_t *p1 = a; - const profile_t *p2 = b; - uint64_t n1 = atomic_load(&p1->elapsed_cycles); - uint64_t n2 = atomic_load(&p2->elapsed_cycles); - if (n1 > n2) return +1; - if (n1 < n2) return -1; - return 0; -} - -void print_profile_results(profile_results_t res_list[], int num_results, long double ns_per_cycle) -{ - int entry_count = 0; - for (int i = 0; i < num_results; i++) - entry_count += res_list[i].count; - - profile_t entries_[128]; - profile_t *entries = entries_; - - if (entry_count >= (int) (sizeof(entries_)/sizeof(profile_t))) { - entries = malloc(entry_count * sizeof(profile_t)); - if (entries == NULL) abort(); - } - - for (int i = 0, k = 0; i < num_results; i++) - for (int j = 0; j < res_list[i].count; j++) - entries[k++] = res_list[i].array[j]; - - qsort(entries, entry_count, sizeof(profile_t), sort_callback); - - if (entry_count > 0) - fprintf(stderr, "| %-30s | %-10s | %-10s | %-10s |\n", - "Name", "Calls", "Total", "Latency"); - - - for (int i = 0; i < entry_count; i++) { - if (!entries[i].name) continue; - int call_count = entries[i].call_count; - uint64_t elapsed_cycles = entries[i].elapsed_cycles; - - long double total_ns = ns_per_cycle * elapsed_cycles; - long double latency_ns = total_ns / call_count; - - char total[128]; - human_readable_time_interval(total_ns, total, sizeof(total)); - - char latency[128]; - human_readable_time_interval(latency_ns, latency, sizeof(latency)); - - fprintf(stderr, "| %-30s | %-10d | %-10s | %-10s |\n", - entries[i].name, call_count, total, latency); - } -} \ No newline at end of file diff --git a/src/profile.h b/src/profile.h deleted file mode 100644 index 18bc992..0000000 --- a/src/profile.h +++ /dev/null @@ -1,74 +0,0 @@ -/* -This is free and unencumbered software released into the public domain. - -Anyone is free to copy, modify, publish, use, compile, sell, or -distribute this software, either in source code form or as a compiled -binary, for any purpose, commercial or non-commercial, and by any -means. - -In jurisdictions that recognize copyright laws, the author or authors -of this software dedicate any and all copyright interest in the -software to the public domain. We make this dedication for the benefit -of the public at large and to the detriment of our heirs and -successors. We intend this dedication to be an overt act of -relinquishment in perpetuity of all present and future rights to this -software under copyright law. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR -OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, -ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -OTHER DEALINGS IN THE SOFTWARE. - -For more information, please refer to -*/ -#ifndef PROFILE_H -#define PROFILE_H - -#include -#include -#include - -typedef struct { - const char *_Atomic name; - _Atomic uint64_t elapsed_cycles; - _Atomic uint64_t call_count; -} profile_t; - -typedef struct { - profile_t *array; - int count; -} profile_results_t; - -#ifdef PROFILE - -#define PROFILE_START \ - profile_t *profile__ = &profile_table__[__COUNTER__]; \ - profile__->name = __func__; \ - uint64_t profile_start__ = __rdtsc(); - -#define PROFILE_END \ - atomic_fetch_add(&profile__->elapsed_cycles, __rdtsc() - profile_start__); \ - atomic_fetch_add(&profile__->call_count, 1); - -#define PROFILE_GLOBAL_START \ - static profile_t profile_table__[]; - -#define PROFILE_GLOBAL_END \ - static profile_t profile_table__[__COUNTER__]; - -#define PROFILE_RESULTS (profile_results_t) {profile_table__, sizeof(profile_table__) / sizeof(profile_table__[0])} - -#else -#define PROFILE_START -#define PROFILE_END -#define PROFILE_GLOBAL_START -#define PROFILE_GLOBAL_END -#define PROFILE_RESULTS (profile_results_t) {NULL, 0} -#endif -#endif - -void human_readable_time_interval(uint64_t ns, char *dst, size_t max); -void print_profile_results(profile_results_t res_list[], int num_results, long double ns_per_cycle); \ No newline at end of file diff --git a/src/scene.c b/src/scene.c new file mode 100644 index 0000000..57747dd --- /dev/null +++ b/src/scene.c @@ -0,0 +1,624 @@ +#include +#include +#include +#include +#include + +#include "utils.h" +#include "scene.h" + +Vector3 origin_of(Object o) +{ + if (o.type == OBJECT_SPHERE) + return o.sphere.center; + return combine(o.cube.origin, o.cube.size, 1, 0.5); +} + +static bool intersect_cube(Ray r, Cube c, float *tnear, float *tfar, Vector3 *normal) +{ + float txmin, txmax; + float tymin, tymax; + float tzmin, tzmax; + + float tn; + float tf; + + Vector3 a = c.origin; + Vector3 b = combine(c.origin, c.size, 1, 1); + + int hit_axis = 0; // 0=x, 1=y, 2=z + + if (r.direction.x >= 0) { + txmin = (a.x - r.origin.x) / r.direction.x; + txmax = (b.x - r.origin.x) / r.direction.x; + } else { + txmax = (a.x - r.origin.x) / r.direction.x; + txmin = (b.x - r.origin.x) / r.direction.x; + } + + if (r.direction.y >= 0) { + tymin = (a.y - r.origin.y) / r.direction.y; + tymax = (b.y - r.origin.y) / r.direction.y; + } else { + tymax = (a.y - r.origin.y) / r.direction.y; + tymin = (b.y - r.origin.y) / r.direction.y; + } + + if (txmin > tymax || tymin > txmax) + return false; + + if (tymin > txmin) { txmin = tymin; hit_axis = 1; } + if (tymax < txmax) txmax = tymax; + + if (r.direction.z >= 0) { + tzmin = (a.z - r.origin.z) / r.direction.z; + tzmax = (b.z - r.origin.z) / r.direction.z; + } else { + tzmax = (a.z - r.origin.z) / r.direction.z; + tzmin = (b.z - r.origin.z) / r.direction.z; + } + + if (txmin > tzmax || tzmin > txmax) + return false; + + if (tzmin > txmin) { txmin = tzmin; hit_axis = 2; }; + if (tzmax < txmax) txmax = tzmax; + + if (tnear) *tnear = txmin; + if (tfar) *tfar = txmax; + if (normal) { + switch (hit_axis) { + case 0: *normal = r.direction.x > 0 ? (Vector3) {-1, 0, 0} : (Vector3) {1, 0, 0}; break; + case 1: *normal = r.direction.y > 0 ? (Vector3) {0, -1, 0} : (Vector3) {0, 1, 0}; break; + case 2: *normal = r.direction.z > 0 ? (Vector3) {0, 0, -1} : (Vector3) {0, 0, 1}; break; + } + } + return true; +} + +static bool intersect_sphere(Ray r, Sphere s, float *t) +{ + /* + * Any point of the ray can be written as + * + * P(t) = O + t * D + * + * with O origin and D direction. + * + * All points P=(x,y,z) of a sphere can be described as + * those (and only those) that satisfy the equation + * + * x^2 + y^2 + z^2 = R^2 + * P^2 - R^2 = 0 + * + * with R radius of the sphere. The sphere here is centered + * at the origin. + * + * Intersection points of the ray with the sphere must satisfy + * both: + * + * P(t) = O + t * D + * P^2 - R^2 = 0 + * + * => (O + tD)^2 - R^2 = 0 + * => t^2 * D^2 + t * 2OD + O^2 - R^2 = 0 + * + * we can use the quadratic formula here, and more specifically + * the discriminant to check if solutions exist and how many + */ + Vector3 oc = combine(s.center, r.origin, 1, -1); + float a = dotv(r.direction, r.direction); + float b = -2 * dotv(oc, r.direction); + float c = dotv(oc, oc) - s.radius * s.radius; + + float discr = b*b - 4*a*c; + + if (discr > 0) { + float s0 = (- b + sqrt(discr)) / (2 * a); + float s1 = (- b - sqrt(discr)) / (2 * a); + if (s0 > s1) { + float tmp = s0; + s0 = s1; + s1 = tmp; + } + if (s0 < 0) { + s0 = s1; + if (s0 < 0) return false; + } + if (t) *t = s0; + return true; + } + + // Zero solutions + return false; +} + +static bool intersect_object(Ray r, Object o, float *t, Vector3 *normal) +{ + switch (o.type) { + + case OBJECT_CUBE: + return intersect_cube(r, o.cube, t, NULL, normal); + + case OBJECT_SPHERE: + if (intersect_sphere(r, o.sphere, t)) { + if (normal) { + Vector3 hit_point = combine(r.origin, r.direction, 1, *t); + *normal = normalize(combine(hit_point, o.sphere.center, 1, -1)); + } + return true; + } + return false; + } + return false; +} + +HitInfo trace_ray(Ray ray, Scene *scene) +{ + ray.direction = normalize(ray.direction); + + float nearest_t = FLT_MAX; + int nearest_object = -1; + Vector3 nearest_normal; + for (int i = 0; i < scene->num_objects; i++) { + float t; + Vector3 n; + if (!intersect_object(ray, scene->objects[i], &t, &n)) + continue; + if (t >= 0 && t < nearest_t) { + nearest_t = t; + nearest_object = i; + nearest_normal = n; + } + } + + if (nearest_object == -1) { + HitInfo result; + result.distance = -1; + result.normal = (Vector3) {0, 0, 0}; + result.point = (Vector3) {0, 0, 0}; + result.object = -1; + return result; + } else { + HitInfo result; + result.distance = nearest_t; + result.normal = nearest_normal; + result.point = combine(ray.origin, ray.direction, 1, nearest_t); + result.object = nearest_object; + return result; + } +} + + +typedef enum { + PROP_ALBEDO, + PROP_ROUGHNESS, + PROP_REFLECTANCE, + PROP_METALLIC, + PROP_EMISSION_POWER, + PROP_EMISSION_COLOR, + PROP_RADIUS, + PROP_CENTER, + PROP_ORIGIN, + PROP_SIZE, +} Property; + +static bool parse_scene_string(char *src, size_t len, Scene *scene) +{ + scene->num_objects = 0; + + int line = 1; + size_t i = 0; + for (;;) { + + while (i < len && is_space(src[i])) { + if (src[i] == '\n') line++; + i++; + } + + if (i == len) + break; + + Object object; + + if (5 < len - i + && src[i+0] == 's' + && src[i+1] == 'p' + && src[i+2] == 'h' + && src[i+3] == 'e' + && src[i+4] == 'r' + && src[i+5] == 'e') { + object.type = OBJECT_SPHERE; + object.sphere.center = (Vector3) {0, 0, 0}; + object.sphere.radius = 1; + object.material.albedo = (Vector3) {0.44, 0.68, 0.84}; + object.material.roughness = 0; + object.material.reflectance = 0.2; + object.material.metallic = 0; + object.material.emission_power = 0; + object.material.emission_color = (Vector3) {1, 1, 1}; + i += 6; + } else if (3 < len - i + && src[i+0] == 'c' + && src[i+1] == 'u' + && src[i+2] == 'b' + && src[i+3] == 'e') { + object.type = OBJECT_CUBE; + object.cube.origin = (Vector3) {0, 0, 0}; + object.cube.size = (Vector3) {1, 1, 1}; + object.material.albedo = (Vector3) {0.44, 0.68, 0.84}; + object.material.roughness = 0; + object.material.reflectance = 0.2; + object.material.metallic = 0; + object.material.emission_power = 0; + object.material.emission_color = (Vector3) {1, 1, 1}; + i += 4; + } else { + fprintf(stderr, "Error: Invalid character (line %d)\n", line); + return false; + } + + for (;;) { + // Skip spaces before property + while (i < len && is_space(src[i])) { + if (src[i] == '\n') line++; + i++; + } + + int valuetype; // 0 for float, 1 for color (3 floats) + + Property prop; + if (6 < len - i + && src[i+0] == 'a' + && src[i+1] == 'l' + && src[i+2] == 'b' + && src[i+3] == 'e' + && src[i+4] == 'd' + && src[i+5] == 'o') { + valuetype = 1; + prop = PROP_ALBEDO; + i += 9; + } else if (8 < len - i + && src[i+0] == 'r' + && src[i+1] == 'o' + && src[i+2] == 'u' + && src[i+3] == 'g' + && src[i+4] == 'h' + && src[i+5] == 'n' + && src[i+6] == 'e' + && src[i+7] == 's' + && src[i+8] == 's') { + valuetype = 0; + prop = PROP_ROUGHNESS; + i += 9; + } else if (10 < len - i + && src[i+0] == 'r' + && src[i+1] == 'e' + && src[i+2] == 'f' + && src[i+3] == 'l' + && src[i+4] == 'e' + && src[i+5] == 'c' + && src[i+6] == 't' + && src[i+7] == 'a' + && src[i+8] == 'n' + && src[i+9] == 'c' + && src[i+10] == 'e') { + valuetype = 0; + prop = PROP_REFLECTANCE; + i += 11; + } else if (7 < len - i + && src[i+0] == 'm' + && src[i+1] == 'e' + && src[i+2] == 't' + && src[i+3] == 'a' + && src[i+4] == 'l' + && src[i+5] == 'l' + && src[i+6] == 'i' + && src[i+7] == 'c') { + valuetype = 0; + prop = PROP_METALLIC; + i += 11; + } else if (13 < len - i + && src[i+0] == 'e' + && src[i+1] == 'm' + && src[i+2] == 'i' + && src[i+3] == 's' + && src[i+4] == 's' + && src[i+5] == 'i' + && src[i+6] == 'o' + && src[i+7] == 'n' + && src[i+8] == '_' + && src[i+9] == 'p' + && src[i+10] == 'o' + && src[i+11] == 'w' + && src[i+12] == 'e' + && src[i+13] == 'r') { + valuetype = 0; + prop = PROP_EMISSION_POWER; + i += 14; + } else if (13 < len - i + && src[i+0] == 'e' + && src[i+1] == 'm' + && src[i+2] == 'i' + && src[i+3] == 's' + && src[i+4] == 's' + && src[i+5] == 'i' + && src[i+6] == 'o' + && src[i+7] == 'n' + && src[i+8] == '_' + && src[i+9] == 'c' + && src[i+10] == 'o' + && src[i+11] == 'l' + && src[i+12] == 'o' + && src[i+13] == 'r') { + valuetype = 1; + prop = PROP_EMISSION_COLOR; + i += 14; + } else if (5 < len - i + && src[i+0] == 'r' + && src[i+1] == 'a' + && src[i+2] == 'd' + && src[i+3] == 'i' + && src[i+4] == 'u' + && src[i+5] == 's') { + if (object.type != OBJECT_SPHERE) { + fprintf(stderr, "Poperty 'radius' only allowed on spheres (line %d)\n", line); + return false; + } + valuetype = 0; + prop = PROP_RADIUS; + i += 6; + } else if (5 < len - i + && src[i+0] == 'c' + && src[i+1] == 'e' + && src[i+2] == 'n' + && src[i+3] == 't' + && src[i+4] == 'e' + && src[i+5] == 'r') { + if (object.type != OBJECT_SPHERE) { + fprintf(stderr, "Poperty 'center' only allowed on spheres (line %d)\n", line); + return false; + } + valuetype = 1; + prop = PROP_CENTER; + i += 6; + } else if (5 < len - i + && src[i+0] == 'o' + && src[i+1] == 'r' + && src[i+2] == 'i' + && src[i+3] == 'g' + && src[i+4] == 'i' + && src[i+5] == 'n') { + if (object.type != OBJECT_CUBE) { + fprintf(stderr, "Poperty 'origin' only allowed on cubes (line %d)\n", line); + return false; + } + valuetype = 1; + prop = PROP_ORIGIN; + i += 6; + } else if (3 < len - i + && src[i+0] == 's' + && src[i+1] == 'i' + && src[i+2] == 'z' + && src[i+3] == 'e') { + if (object.type != OBJECT_CUBE) { + fprintf(stderr, "Poperty 'size' only allowed on cubes (line %d)\n", line); + return false; + } + valuetype = 1; + prop = PROP_SIZE; + i += 4; + } else + // Not a valid property name + break; + + // Consume spaces before the value + while (i < len && is_space(src[i])) { + if (src[i] == '\n') line++; + i++; + } + if (i == len) { + fprintf(stderr, "Error: Property value is missing (line %d)\n", line); + return false; + } + + float value0; + Vector3 value1; + if (valuetype == 0) { + // Parse a single float + int sign = 1; + if (src[i] == '-') { + sign = -1; + i++; + if (i == len || !is_digit(src[i])) { + fprintf(stderr, "Error: Missing number after minus sign (line %d)\n", line); + return false; + } + } else if (!is_digit(src[i])) { + fprintf(stderr, "Error: Missing number after property name (line %d)\n", line); + return false; + } + value0 = 0; + do { + int d = src[i] - '0'; + value0 = value0 * 10 + d; + i++; + } while (i < len && is_digit(src[i])); + if (i < len && src[i] == '.') { + i++; // Skip the dot + if (i == len || !is_digit(src[i])) { + fprintf(stderr, "Error: Missing decimal part after dot (line %d)\n", line); + return false; + } + float q = 1.0f / 10; + do { + int d = src[i] - '0'; + value0 += q * d; + q /= 10; + i++; + } while (i < len && is_digit(src[i])); + } + value0 *= sign; + } else { + assert(valuetype == 1); + + if (src[i] != '{') { + fprintf(stderr, "Error: Missing '{' after property name (line %d)\n", line); + return false; + } + i++; + + float temp[3]; + for (int j = 0; j < 3; j++) { + + while (i < len && is_space(src[i])) { + if (src[i] == '\n') line++; + i++; + } + + int sign = 1; + if (src[i] == '-') { + sign = -1; + i++; + if (i == len || !is_digit(src[i])) { + fprintf(stderr, "Error: Missing number after minus sign (line %d)\n", line); + return false; + } + } else if (!is_digit(src[i])) { + fprintf(stderr, "Error: Missing number %d in vector value (line %d)\n", j, line); + return false; + } + temp[j] = 0; + do { + int d = src[i] - '0'; + temp[j] = temp[j] * 10 + d; + i++; + } while (i < len && is_digit(src[i])); + if (i < len && src[i] == '.') { + i++; // Skip the dot + if (i == len || !is_digit(src[i])) { + fprintf(stderr, "Error: Missing decimal part after dot (line %d)\n", line); + return false; + } + float q = 1.0f / 10; + do { + int d = src[i] - '0'; + temp[j] += q * d; + q /= 10; + i++; + } while (i < len && is_digit(src[i])); + } + temp[j] *= sign; + } + + while (i < len && is_space(src[i])) { + if (src[i] == '\n') line++; + i++; + } + + if (i == len || src[i] != '}') { + fprintf(stderr, "Error: Missing '}' after property value (line %d)\n", line); + return false; + } + i++; + + value1.x = temp[0]; + value1.y = temp[1]; + value1.z = temp[2]; + } + + switch (prop) { + + case PROP_ALBEDO: + if (value1.x < 0 || value1.x > 1 || + value1.y < 0 || value1.y > 1 || + value1.z < 0 || value1.z > 1) { + fprintf(stderr, "Error: albedo values must be between 0 and 1 (line %d)\n", line); + return false; + } + object.material.albedo = value1; + break; + + case PROP_ROUGHNESS: + if (value0 < 0 || value0 > 1) { + fprintf(stderr, "Error: Roughness must be between 0 and 1 (line %d)\n", line); + return false; + } + object.material.roughness = value0; + break; + + case PROP_REFLECTANCE: + if (value0 < 0 || value0 > 1) { + fprintf(stderr, "Error: Reflectance must be between 0 and 1 (line %d)\n", line); + return false; + } + object.material.reflectance = value0; + break; + + case PROP_METALLIC: + if (value0 < 0 || value0 > 1) { + fprintf(stderr, "Error: Metallic must be between 0 and 1 (line %d)\n", line); + return false; + } + object.material.metallic = value0; + break; + + case PROP_EMISSION_POWER: + object.material.emission_power = value0; + break; + + case PROP_EMISSION_COLOR: + if (value1.x < 0 || value1.x > 1 || + value1.y < 0 || value1.y > 1 || + value1.z < 0 || value1.z > 1) { + fprintf(stderr, "Error: Emission color values must be between 0 and 1 (line %d)\n", line); + return false; + } + object.material.emission_color = value1; + break; + + case PROP_RADIUS: + object.sphere.radius = value0; + break; + + case PROP_CENTER: + object.sphere.center = value1; + break; + + case PROP_ORIGIN: + object.cube.origin = value1; + break; + + case PROP_SIZE: + if (value1.x < 0 || value1.y < 0 || value1.z < 0) { + fprintf(stderr, "Error: Size values must be positive (line %d)\n", line); + return false; + } + object.cube.size = value1; + break; + } + } + + if (scene->num_objects == MAX_OBJECTS) + fprintf(stderr, "Warning: Ignoring object because the scene is too big (line %d)\n", line); + else + scene->objects[scene->num_objects++] = object; + } + + return true; +} + +bool parse_scene_file(char *file, Scene *scene) +{ + size_t len; + char *src = load_file(file, &len); + if (src == NULL) { + fprintf(stderr, "Error: Couldn't open scene file\n"); + return false; + } + + bool ok = parse_scene_string(src, len, scene); + + free(src); + return ok; +} diff --git a/src/scene.h b/src/scene.h new file mode 100644 index 0000000..3777aa6 --- /dev/null +++ b/src/scene.h @@ -0,0 +1,47 @@ +#include "vector.h" + +#define MAX_OBJECTS 1024 + +typedef struct { + Vector3 albedo; + float roughness; + float reflectance; + float metallic; + float emission_power; + Vector3 emission_color; +} Material; + +typedef struct { + Vector3 origin; + Vector3 size; +} Cube; + +typedef enum { + OBJECT_CUBE, + OBJECT_SPHERE, +} ObjectType; + +typedef struct { + ObjectType type; + union { + Sphere sphere; + Cube cube; + }; + Material material; +} Object; + +typedef struct { + Object objects[MAX_OBJECTS]; + int num_objects; +} Scene; + +typedef struct { + float distance; + Vector3 point; + Vector3 normal; + int object; +} HitInfo; + +Vector3 origin_of(Object o); +HitInfo trace_ray(Ray ray, Scene *scene); +bool parse_scene_file(char *file, Scene *scene); \ No newline at end of file diff --git a/src/sync.c b/src/sync.c deleted file mode 100644 index ed9cb64..0000000 --- a/src/sync.c +++ /dev/null @@ -1,352 +0,0 @@ -/* -This is free and unencumbered software released into the public domain. - -Anyone is free to copy, modify, publish, use, compile, sell, or -distribute this software, either in source code form or as a compiled -binary, for any purpose, commercial or non-commercial, and by any -means. - -In jurisdictions that recognize copyright laws, the author or authors -of this software dedicate any and all copyright interest in the -software to the public domain. We make this dedication for the benefit -of the public at large and to the detriment of our heirs and -successors. We intend this dedication to be an overt act of -relinquishment in perpetuity of all present and future rights to this -software under copyright law. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR -OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, -ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -OTHER DEALINGS IN THE SOFTWARE. - -For more information, please refer to -*/ -#include -#include - -#if defined(__linux__) -#include -#endif - -#include "sync.h" -#include "clock.h" - -//#define SYNC_PRINT_ERRORS -#ifdef SYNC_PRINT_ERRORS -#include -#include -#endif - -PROFILE_GLOBAL_START; - -void os_mutex_create(os_mutex_t *mutex) -{ - PROFILE_START; - -#if defined(_WIN32) - InitializeCriticalSection(mutex); -#elif defined(__linux__) - if (pthread_mutex_init(mutex, NULL)) - abort(); -#else - (void) mutex; -#endif - - PROFILE_END; -} - -void os_mutex_delete(os_mutex_t *mutex) -{ - PROFILE_START; - -#if defined(_WIN32) - DeleteCriticalSection(mutex); -#elif defined(__linux__) - if (pthread_mutex_destroy(mutex)) - abort(); -#else - (void) mutex; -#endif - - PROFILE_END; -} - -void os_mutex_lock(os_mutex_t *mutex) -{ - PROFILE_START; - -#if defined(_WIN32) - EnterCriticalSection(mutex); -#elif defined(__linux__) - if (pthread_mutex_lock(mutex)) - abort(); -#else - (void) mutex; -#endif - - PROFILE_END; -} - -void os_mutex_unlock(os_mutex_t *mutex) -{ - PROFILE_START; - -#if defined(_WIN32) - LeaveCriticalSection(mutex); -#elif defined(__linux__) - if (pthread_mutex_unlock(mutex)) - abort(); -#else - (void) mutex; -#endif - - PROFILE_END; -} - -void os_condvar_create(os_condvar_t *condvar) -{ - PROFILE_START; - -#if defined(_WIN32) - InitializeConditionVariable(condvar); -#elif defined(__linux__) - if (pthread_cond_init(condvar, NULL)) - abort(); -#else - (void) condvar; -#endif - - PROFILE_END; -} - -void os_condvar_delete(os_condvar_t *condvar) -{ - PROFILE_START; - -#if defined(__linux__) - if (pthread_cond_destroy(condvar)) - abort(); -#else - (void) condvar; -#endif - - PROFILE_END; -} - -bool os_condvar_wait(os_condvar_t *condvar, os_mutex_t *mutex, int timeout_ms) -{ - PROFILE_START; - -#if defined(_WIN32) - - DWORD timeout = INFINITE; - if (timeout_ms >= 0) timeout = timeout_ms; - if (!SleepConditionVariableCS(condvar, mutex, timeout)) { - if (GetLastError() == ERROR_TIMEOUT) { - PROFILE_END; - return false; - } - abort(); - } - - PROFILE_END; - return true; - -#elif defined(__linux__) - - int err; - if (timeout_ms < 0) - err = pthread_cond_wait(condvar, mutex); - else { - uint64_t wakeup_ms = (uint64_t) timeout_ms + get_absolute_time_us() / 1000; - struct timespec abstime = { - .tv_sec = wakeup_ms / 1000, - .tv_nsec = (wakeup_ms % 1000) * 1000000, - }; - err = pthread_cond_timedwait(condvar, mutex, &abstime); - } - if (err) { - if (err == ETIMEDOUT) { - PROFILE_END; - return false; - } -#ifdef SYNC_PRINT_ERRORS - fprintf(stderr, "ERROR!! pthread_cond_wait/timedwait: %s\n", strerror(err)); -#endif - abort(); - } - - PROFILE_END; - return true; - -#else - PROFILE_END; - (void) condvar; -#endif -} - -void os_condvar_signal(os_condvar_t *condvar) -{ - PROFILE_START; - -#if defined(_WIN32) - WakeConditionVariable(condvar); -#elif defined(__linux__) - if (pthread_cond_signal(condvar)) - abort(); -#else - (void) condvar; -#endif - - PROFILE_END; -} - -void semaphore_create(semaphore_t *sem, int count) -{ - PROFILE_START; - - sem->count = count; - os_mutex_create(&sem->mutex); - os_condvar_create(&sem->cond); - - PROFILE_END; -} - -void semaphore_delete(semaphore_t *sem) -{ - PROFILE_START; - - os_mutex_delete(&sem->mutex); - os_condvar_delete(&sem->cond); - - PROFILE_END; -} - -bool semaphore_wait(semaphore_t *sem, int count, int timeout_ms) -{ - PROFILE_START; - - assert(count > 0); - - uint64_t start_time_ms = get_relative_time_ns() / 1000000; - - os_mutex_lock(&sem->mutex); - while (sem->count < count) { - - uint64_t current_time_ms = get_relative_time_ns() / 1000000; - - int remaining_ms = -1; - if (timeout_ms >= 0) - remaining_ms = timeout_ms - (int) (current_time_ms - start_time_ms); - - if (!os_condvar_wait(&sem->cond, &sem->mutex, remaining_ms)) { - os_mutex_unlock(&sem->mutex); - PROFILE_END; - return false; - } - } - sem->count -= count; - os_mutex_unlock(&sem->mutex); - - PROFILE_END; - return true; -} - -void semaphore_signal(semaphore_t *sem, int count) -{ - PROFILE_START; - - assert(count > 0); - - os_mutex_lock(&sem->mutex); - sem->count += count; - if (sem->count > 0) - os_condvar_signal(&sem->cond); - os_mutex_unlock(&sem->mutex); - - PROFILE_END; -} - -bool os_semaphore_create(os_semaphore_t *sem, int count, int max) -{ - PROFILE_START; - - int ok; - - #ifdef _WIN32 - SECURITY_ATTRIBUTES *attr = NULL; // Default - const char *name = NULL; // No name - void *handle = CreateSemaphoreA(attr, count, max, name); - if (handle == NULL) { - PROFILE_END; - return false; - } - sem->data = handle; - ok = 1; - #else - (void) max; // POSIX doesn't use this - ok = sem_init(&sem->data, 0, count) == 0; - #endif - - PROFILE_END; - return ok; -} - -bool os_semaphore_delete(os_semaphore_t *sem) -{ - PROFILE_START; - - int ok; - - #ifdef _WIN32 - CloseHandle(sem->data); - ok = 1; - #else - ok = sem_destroy(&sem->data) == 0; - #endif - - PROFILE_END; - return ok; -} - -bool os_semaphore_wait(os_semaphore_t *sem) -{ - PROFILE_START; - - int ok; - - #ifdef _WIN32 - ok = WaitForSingleObject(sem->data, INFINITE) == WAIT_OBJECT_0; - #else - ok = sem_wait(&sem->data) == 0; - #endif - - PROFILE_END; - return ok; -} - -bool os_semaphore_signal(os_semaphore_t *sem) -{ - PROFILE_START; - - int ok; - - #ifdef _WIN32 - ok = ReleaseSemaphore(sem->data, 1, NULL); - #else - ok = sem_post(&sem->data) == 0; - #endif - - PROFILE_END; - return ok; -} - -PROFILE_GLOBAL_END; - -profile_results_t sync_profile_results(void) -{ - return PROFILE_RESULTS; -} \ No newline at end of file diff --git a/src/thread.c b/src/thread.c deleted file mode 100644 index 87215a0..0000000 --- a/src/thread.c +++ /dev/null @@ -1,74 +0,0 @@ -/* -This is free and unencumbered software released into the public domain. - -Anyone is free to copy, modify, publish, use, compile, sell, or -distribute this software, either in source code form or as a compiled -binary, for any purpose, commercial or non-commercial, and by any -means. - -In jurisdictions that recognize copyright laws, the author or authors -of this software dedicate any and all copyright interest in the -software to the public domain. We make this dedication for the benefit -of the public at large and to the detriment of our heirs and -successors. We intend this dedication to be an overt act of -relinquishment in perpetuity of all present and future rights to this -software under copyright law. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR -OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, -ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -OTHER DEALINGS IN THE SOFTWARE. - -For more information, please refer to -*/ -#include - -#ifdef _WIN32 -#else -#include -#endif - -#include "thread.h" - -void os_thread_create(os_thread *thread, void *arg, os_threadreturn (*func)(void*)) -{ - #if defined(_WIN32) - os_thread thread_ = CreateThread(NULL, 0, func, arg, 0, NULL); - if (thread_ == INVALID_HANDLE_VALUE) - abort(); - *thread = thread_; - #elif defined(__linux__) - int ret = pthread_create(thread, NULL, func, arg); - if (ret) abort(); - #endif -} - -os_threadreturn os_thread_join(os_thread thread) -{ - #if defined(_WIN32) - os_threadreturn result; - WaitForSingleObject(thread, INFINITE); - if (!GetExitCodeThread(thread, &result)) - abort(); - CloseHandle(thread); - return result; - #elif defined(__linux__) - os_threadreturn result; - int ret = pthread_join(thread, &result); - if (ret) abort(); - return result; - #else - (void) thread; - #endif -} - -uint64_t get_thread_id(void) -{ - static _Atomic uint64_t next_id = 1; - static _Thread_local uint64_t id = 0; - if (id == 0) id = atomic_fetch_add(&next_id, 1); - return id; -} \ No newline at end of file diff --git a/src/thread.h b/src/thread.h deleted file mode 100644 index 99b2c4f..0000000 --- a/src/thread.h +++ /dev/null @@ -1,44 +0,0 @@ -/* -This is free and unencumbered software released into the public domain. - -Anyone is free to copy, modify, publish, use, compile, sell, or -distribute this software, either in source code form or as a compiled -binary, for any purpose, commercial or non-commercial, and by any -means. - -In jurisdictions that recognize copyright laws, the author or authors -of this software dedicate any and all copyright interest in the -software to the public domain. We make this dedication for the benefit -of the public at large and to the detriment of our heirs and -successors. We intend this dedication to be an overt act of -relinquishment in perpetuity of all present and future rights to this -software under copyright law. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR -OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, -ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -OTHER DEALINGS IN THE SOFTWARE. - -For more information, please refer to -*/ -#include -#include - -#if defined(_WIN32) -#define WIN32_MEAN_AND_LEAN -#include -typedef void *os_thread; -typedef unsigned long os_threadreturn; -#elif defined(__linux__) -#include -typedef pthread_t os_thread; -typedef void *os_threadreturn; -#endif - -uint64_t get_thread_id(void); - -void os_thread_create(os_thread *thread, void *arg, os_threadreturn (*func)(void*)); -os_threadreturn os_thread_join(os_thread thread); \ No newline at end of file diff --git a/src/tinyobj_loader_c.h b/src/tinyobj_loader_c.h deleted file mode 100644 index 29fe8b1..0000000 --- a/src/tinyobj_loader_c.h +++ /dev/null @@ -1,1735 +0,0 @@ -/* - The MIT License (MIT) - - Copyright (c) 2016 - 2019 Syoyo Fujita and many contributors. - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - THE SOFTWARE. - */ -#ifndef TINOBJ_LOADER_C_H_ -#define TINOBJ_LOADER_C_H_ - -/* @todo { Remove stddef dependency. size_t? } */ -#include - -#ifdef __cplusplus -extern "C" { -#endif - -typedef struct { - char *name; - - float ambient[3]; - float diffuse[3]; - float specular[3]; - float transmittance[3]; - float emission[3]; - float shininess; - float ior; /* index of refraction */ - float dissolve; /* 1 == opaque; 0 == fully transparent */ - /* illumination model (see http://www.fileformat.info/format/material/) */ - int illum; - - int pad0; - - char *ambient_texname; /* map_Ka */ - char *diffuse_texname; /* map_Kd */ - char *specular_texname; /* map_Ks */ - char *specular_highlight_texname; /* map_Ns */ - char *bump_texname; /* map_bump, bump */ - char *displacement_texname; /* disp */ - char *alpha_texname; /* map_d */ -} tinyobj_material_t; - -typedef struct { - char *name; /* group name or object name. */ - unsigned int face_offset; - unsigned int length; -} tinyobj_shape_t; - -typedef struct { int v_idx, vt_idx, vn_idx; } tinyobj_vertex_index_t; - -typedef struct { - unsigned int num_vertices; - unsigned int num_normals; - unsigned int num_texcoords; - unsigned int num_faces; - unsigned int num_face_num_verts; - - int pad0; - - float *vertices; - float *normals; - float *texcoords; - tinyobj_vertex_index_t *faces; - int *face_num_verts; - int *material_ids; -} tinyobj_attrib_t; - - -#define TINYOBJ_FLAG_TRIANGULATE (1 << 0) - -#define TINYOBJ_INVALID_INDEX (0x80000000) - -#define TINYOBJ_SUCCESS (0) -#define TINYOBJ_ERROR_EMPTY (-1) -#define TINYOBJ_ERROR_INVALID_PARAMETER (-2) -#define TINYOBJ_ERROR_FILE_OPERATION (-3) - -/* Provide a callback that can read text file without any parsing or modification. - * The obj and mtl parser is going to read all the necessary data: - * tinyobj_parse_obj - * tinyobj_parse_mtl_file - * - * @param[in] ctx User provided context. - * @param[in] filename Filename to be loaded. - * @param[in] is_mtl 1 when the callback is invoked for loading .mtl. 0 for .obj - * @param[in] obj_filename .obj filename. Useful when you load .mtl from same location of .obj. When the callback is called to load .obj, `filename` and `obj_filename` are same. - * @param[out] buf Content of loaded file - * @param[out] len Size of content(file) - */ -typedef void (*file_reader_callback)(void *ctx, const char *filename, int is_mtl, const char *obj_filename, char **buf, size_t *len); - -/* Parse wavefront .obj - * @param[out] attrib Attibutes - * @param[out] shapes Array of parsed shapes - * @param[out] num_shapes Array length of `shapes` - * @param[out] materials Array of parsed materials - * @param[out] num_materials Array length of `materials` - * @param[in] file_name File name of .obj - * @param[in] file_reader File reader callback function(to read .obj and .mtl). - * @param[in] ctx Context pointer passed to the file_reader_callback. - * @param[in] flags combination of TINYOBJ_FLAG_*** - * - * Returns TINYOBJ_SUCCESS if things goes well. - * Returns TINYOBJ_ERROR_*** when there is an error. - */ -extern int tinyobj_parse_obj(tinyobj_attrib_t *attrib, tinyobj_shape_t **shapes, - size_t *num_shapes, tinyobj_material_t **materials, - size_t *num_materials, const char *file_name, file_reader_callback file_reader, - void *ctx, unsigned int flags); - -/* Parse wavefront .mtl - * - * @param[out] materials_out - * @param[out] num_materials_out - * @param[in] filename .mtl filename - * @param[in] filename of .obj filename. could be NULL if you just want to parse .mtl file. - * @param[in] file_reader File reader callback - * @param[in[ ctx Context pointer passed to the file_reader callack. - - * Returns TINYOBJ_SUCCESS if things goes well. - * Returns TINYOBJ_ERROR_*** when there is an error. - */ -extern int tinyobj_parse_mtl_file(tinyobj_material_t **materials_out, - size_t *num_materials_out, - const char *filename, const char *obj_filename, file_reader_callback file_reader, - void *ctx); - -extern void tinyobj_attrib_init(tinyobj_attrib_t *attrib); -extern void tinyobj_attrib_free(tinyobj_attrib_t *attrib); -extern void tinyobj_shapes_free(tinyobj_shape_t *shapes, size_t num_shapes); -extern void tinyobj_materials_free(tinyobj_material_t *materials, - size_t num_materials); - -#ifdef __cplusplus -} -#endif -#endif /* TINOBJ_LOADER_C_H_ */ - -#ifdef TINYOBJ_LOADER_C_IMPLEMENTATION -#include -#include -#include -#include - -#if defined(TINYOBJ_MALLOC) && defined(TINYOBJ_CALLOC) && defined(TINYOBJ_FREE) && (defined(TINYOBJ_REALLOC) || defined(TINYOBJ_REALLOC_SIZED)) -/* ok */ -#elif !defined(TINYOBJ_MALLOC) && !defined(TINYOBJ_CALLOC) && !defined(TINYOBJ_FREE) && !defined(TINYOBJ_REALLOC) && !defined(TINYOBJ_REALLOC_SIZED) -/* ok */ -#else -#error "Must define all or none of TINYOBJ_MALLOC, TINYOBJ_CALLOC, TINYOBJ_FREE, and TINYOBJ_REALLOC (or TINYOBJ_REALLOC_SIZED)." -#endif - -#ifndef TINYOBJ_MALLOC -#include -#define TINYOBJ_MALLOC malloc -#define TINYOBJ_REALLOC realloc -#define TINYOBJ_CALLOC calloc -#define TINYOBJ_FREE free -#endif - -#ifndef TINYOBJ_REALLOC_SIZED -#define TINYOBJ_REALLOC_SIZED(p,oldsz,newsz) TINYOBJ_REALLOC(p,newsz) -#endif - -#define TINYOBJ_MAX_FACES_PER_F_LINE (16) -#define TINYOBJ_MAX_FILEPATH (8192) - -#define IS_SPACE(x) (((x) == ' ') || ((x) == '\t')) -#define IS_DIGIT(x) ((unsigned int)((x) - '0') < (unsigned int)(10)) -#define IS_NEW_LINE(x) (((x) == '\r') || ((x) == '\n') || ((x) == '\0')) - -static void skip_space(const char **token) { - while ((*token)[0] == ' ' || (*token)[0] == '\t') { - (*token)++; - } -} - -static void skip_space_and_cr(const char **token) { - while ((*token)[0] == ' ' || (*token)[0] == '\t' || (*token)[0] == '\r') { - (*token)++; - } -} - -static int until_space(const char *token) { - const char *p = token; - while (p[0] != '\0' && p[0] != ' ' && p[0] != '\t' && p[0] != '\r') { - p++; - } - - return (int)(p - token); -} - -static size_t length_until_newline(const char *token, size_t n) { - size_t len = 0; - - /* Assume token[n-1] = '\0' */ - for (len = 0; len < n - 1; len++) { - if (token[len] == '\n') { - break; - } - if ((token[len] == '\r') && ((len < (n - 2)) && (token[len + 1] != '\n'))) { - break; - } - } - - return len; -} - -static size_t length_until_line_feed(const char *token, size_t n) { - size_t len = 0; - - /* Assume token[n-1] = '\0' */ - for (len = 0; len < n; len++) { - if ((token[len] == '\n') || (token[len] == '\r')) { - break; - } - } - - return len; -} - -/* http://stackoverflow.com/questions/5710091/how-does-atoi-function-in-c-work -*/ -static int my_atoi(const char *c) { - int value = 0; - int sign = 1; - if (*c == '+' || *c == '-') { - if (*c == '-') sign = -1; - c++; - } - while (((*c) >= '0') && ((*c) <= '9')) { /* isdigit(*c) */ - value *= 10; - value += (int)(*c - '0'); - c++; - } - return value * sign; -} - -/* Make index zero-base, and also support relative index. */ -static int fixIndex(int idx, size_t n) { - if (idx > 0) return idx - 1; - if (idx == 0) return 0; - return (int)n + idx; /* negative value = relative */ -} - -/* Parse raw triples: i, i/j/k, i//k, i/j */ -static tinyobj_vertex_index_t parseRawTriple(const char **token) { - tinyobj_vertex_index_t vi; - /* 0x80000000 = -2147483648 = invalid */ - vi.v_idx = (int)(0x80000000); - vi.vn_idx = (int)(0x80000000); - vi.vt_idx = (int)(0x80000000); - - vi.v_idx = my_atoi((*token)); - while ((*token)[0] != '\0' && (*token)[0] != '/' && (*token)[0] != ' ' && - (*token)[0] != '\t' && (*token)[0] != '\r') { - (*token)++; - } - if ((*token)[0] != '/') { - return vi; - } - (*token)++; - - /* i//k */ - if ((*token)[0] == '/') { - (*token)++; - vi.vn_idx = my_atoi((*token)); - while ((*token)[0] != '\0' && (*token)[0] != '/' && (*token)[0] != ' ' && - (*token)[0] != '\t' && (*token)[0] != '\r') { - (*token)++; - } - return vi; - } - - /* i/j/k or i/j */ - vi.vt_idx = my_atoi((*token)); - while ((*token)[0] != '\0' && (*token)[0] != '/' && (*token)[0] != ' ' && - (*token)[0] != '\t' && (*token)[0] != '\r') { - (*token)++; - } - if ((*token)[0] != '/') { - return vi; - } - - /* i/j/k */ - (*token)++; /* skip '/' */ - vi.vn_idx = my_atoi((*token)); - while ((*token)[0] != '\0' && (*token)[0] != '/' && (*token)[0] != ' ' && - (*token)[0] != '\t' && (*token)[0] != '\r') { - (*token)++; - } - return vi; -} - -static int parseInt(const char **token) { - int i = 0; - skip_space(token); - i = my_atoi((*token)); - (*token) += until_space((*token)); - return i; -} - -/* - * Tries to parse a floating point number located at s. - * - * s_end should be a location in the string where reading should absolutely - * stop. For example at the end of the string, to prevent buffer overflows. - * - * Parses the following EBNF grammar: - * sign = "+" | "-" ; - * END = ? anything not in digit ? - * digit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" ; - * integer = [sign] , digit , {digit} ; - * decimal = integer , ["." , integer] ; - * float = ( decimal , END ) | ( decimal , ("E" | "e") , integer , END ) ; - * - * Valid strings are for example: - * -0 +3.1417e+2 -0.0E-3 1.0324 -1.41 11e2 - * - * If the parsing is a success, result is set to the parsed value and true - * is returned. - * - * The function is greedy and will parse until any of the following happens: - * - a non-conforming character is encountered. - * - s_end is reached. - * - * The following situations triggers a failure: - * - s >= s_end. - * - parse failure. - */ -static int tryParseDouble(const char *s, const char *s_end, double *result) { - double mantissa = 0.0; - /* This exponent is base 2 rather than 10. - * However the exponent we parse is supposed to be one of ten, - * thus we must take care to convert the exponent/and or the - * mantissa to a * 2^E, where a is the mantissa and E is the - * exponent. - * To get the final double we will use ldexp, it requires the - * exponent to be in base 2. - */ - int exponent = 0; - - /* NOTE: THESE MUST BE DECLARED HERE SINCE WE ARE NOT ALLOWED - * TO JUMP OVER DEFINITIONS. - */ - char sign = '+'; - char exp_sign = '+'; - char const *curr = s; - - /* How many characters were read in a loop. */ - int read = 0; - /* Tells whether a loop terminated due to reaching s_end. */ - int end_not_reached = 0; - - /* - BEGIN PARSING. - */ - - if (s >= s_end) { - return 0; /* fail */ - } - - /* Find out what sign we've got. */ - if (*curr == '+' || *curr == '-') { - sign = *curr; - curr++; - } else if (IS_DIGIT(*curr)) { /* Pass through. */ - } else { - goto fail; - } - - /* Read the integer part. */ - end_not_reached = (curr != s_end); - while (end_not_reached && IS_DIGIT(*curr)) { - mantissa *= 10; - mantissa += (int)(*curr - 0x30); - curr++; - read++; - end_not_reached = (curr != s_end); - } - - /* We must make sure we actually got something. */ - if (read == 0) goto fail; - /* We allow numbers of form "#", "###" etc. */ - if (!end_not_reached) goto assemble; - - /* Read the decimal part. */ - if (*curr == '.') { - curr++; - read = 1; - end_not_reached = (curr != s_end); - while (end_not_reached && IS_DIGIT(*curr)) { - /* pow(10.0, -read) */ - double frac_value = 1.0; - int f; - for (f = 0; f < read; f++) { - frac_value *= 0.1; - } - mantissa += (int)(*curr - 0x30) * frac_value; - read++; - curr++; - end_not_reached = (curr != s_end); - } - } else if (*curr == 'e' || *curr == 'E') { - } else { - goto assemble; - } - - if (!end_not_reached) goto assemble; - - /* Read the exponent part. */ - if (*curr == 'e' || *curr == 'E') { - curr++; - /* Figure out if a sign is present and if it is. */ - end_not_reached = (curr != s_end); - if (end_not_reached && (*curr == '+' || *curr == '-')) { - exp_sign = *curr; - curr++; - } else if (IS_DIGIT(*curr)) { /* Pass through. */ - } else { - /* Empty E is not allowed. */ - goto fail; - } - - read = 0; - end_not_reached = (curr != s_end); - while (end_not_reached && IS_DIGIT(*curr)) { - exponent *= 10; - exponent += (int)(*curr - 0x30); - curr++; - read++; - end_not_reached = (curr != s_end); - } - if (read == 0) goto fail; - } - -assemble : - - { - double a = 1.0; /* = pow(5.0, exponent); */ - double b = 1.0; /* = 2.0^exponent */ - int i; - for (i = 0; i < exponent; i++) { - a = a * 5.0; - } - - for (i = 0; i < exponent; i++) { - b = b * 2.0; - } - - if (exp_sign == '-') { - a = 1.0 / a; - b = 1.0 / b; - } - - *result = - /* (sign == '+' ? 1 : -1) * ldexp(mantissa * pow(5.0, exponent), - exponent); */ - (sign == '+' ? 1 : -1) * (mantissa * a * b); - } - - return 1; -fail: - return 0; -} - -static float parseFloat(const char **token) { - const char *end; - double val = 0.0; - float f = 0.0f; - skip_space(token); - end = (*token) + until_space((*token)); - val = 0.0; - tryParseDouble((*token), end, &val); - f = (float)(val); - (*token) = end; - return f; -} - -static void parseFloat2(float *x, float *y, const char **token) { - (*x) = parseFloat(token); - (*y) = parseFloat(token); -} - -static void parseFloat3(float *x, float *y, float *z, const char **token) { - (*x) = parseFloat(token); - (*y) = parseFloat(token); - (*z) = parseFloat(token); -} - -static size_t my_strnlen(const char *s, size_t n) { - const char *p = (char *)memchr(s, 0, n); - return p ? (size_t)(p - s) : n; -} - -static char *my_strdup(const char *s, size_t max_length) { - char *d; - size_t len; - - if (s == NULL) return NULL; - - /* Do not consider CRLF line ending(#19) */ - len = length_until_line_feed(s, max_length); - /* len = strlen(s); */ - - /* trim line ending and append '\0' */ - d = (char *)TINYOBJ_MALLOC(len + 1); /* + '\0' */ - memcpy(d, s, (size_t)(len)); - d[len] = '\0'; - - return d; -} - -static char *my_strndup(const char *s, size_t len) { - char *d; - size_t slen; - - if (s == NULL) return NULL; - if (len == 0) return NULL; - - slen = my_strnlen(s, len); - d = (char *)TINYOBJ_MALLOC(slen + 1); /* + '\0' */ - if (!d) { - return NULL; - } - memcpy(d, s, slen); - d[slen] = '\0'; - - return d; -} - -char *dynamic_fgets(char **buf, size_t *size, FILE *file) { - char *offset; - char *ret; - size_t old_size; - - if (!(ret = fgets(*buf, (int)*size, file))) { - return ret; - } - - if (NULL != strchr(*buf, '\n')) { - return ret; - } - - do { - old_size = *size; - *size *= 2; - *buf = (char*)TINYOBJ_REALLOC_SIZED(*buf, old_size, *size); - offset = &((*buf)[old_size - 1]); - - ret = fgets(offset, (int)(old_size + 1), file); - } while(ret && (NULL == strchr(*buf, '\n'))); - - return ret; -} - -static void initMaterial(tinyobj_material_t *material) { - int i; - material->name = NULL; - material->ambient_texname = NULL; - material->diffuse_texname = NULL; - material->specular_texname = NULL; - material->specular_highlight_texname = NULL; - material->bump_texname = NULL; - material->displacement_texname = NULL; - material->alpha_texname = NULL; - for (i = 0; i < 3; i++) { - material->ambient[i] = 0.f; - material->diffuse[i] = 0.f; - material->specular[i] = 0.f; - material->transmittance[i] = 0.f; - material->emission[i] = 0.f; - } - material->illum = 0; - material->dissolve = 1.f; - material->shininess = 1.f; - material->ior = 1.f; -} - -/* Implementation of string to int hashtable */ - -#define HASH_TABLE_ERROR 1 -#define HASH_TABLE_SUCCESS 0 - -#define HASH_TABLE_DEFAULT_SIZE 10 - -typedef struct hash_table_entry_t -{ - unsigned long hash; - int filled; - int pad0; - long value; - - struct hash_table_entry_t* next; -} hash_table_entry_t; - -typedef struct -{ - unsigned long* hashes; - hash_table_entry_t* entries; - size_t capacity; - size_t n; -} hash_table_t; - -static unsigned long hash_djb2(const unsigned char* str) -{ - unsigned long hash = 5381; - int c; - - while ((c = *str++)) { - hash = ((hash << 5) + hash) + (unsigned long)(c); - } - - return hash; -} - -static void create_hash_table(size_t start_capacity, hash_table_t* hash_table) -{ - if (start_capacity < 1) - start_capacity = HASH_TABLE_DEFAULT_SIZE; - hash_table->hashes = (unsigned long*) TINYOBJ_MALLOC(start_capacity * sizeof(unsigned long)); - hash_table->entries = (hash_table_entry_t*) TINYOBJ_CALLOC(start_capacity, sizeof(hash_table_entry_t)); - hash_table->capacity = start_capacity; - hash_table->n = 0; -} - -static void destroy_hash_table(hash_table_t* hash_table) -{ - TINYOBJ_FREE(hash_table->entries); - TINYOBJ_FREE(hash_table->hashes); -} - -/* Insert with quadratic probing */ -static int hash_table_insert_value(unsigned long hash, long value, hash_table_t* hash_table) -{ - /* Insert value */ - size_t start_index = hash % hash_table->capacity; - size_t index = start_index; - hash_table_entry_t* start_entry = hash_table->entries + start_index; - size_t i; - hash_table_entry_t* entry; - - for (i = 1; hash_table->entries[index].filled; i++) - { - if (i >= hash_table->capacity) - return HASH_TABLE_ERROR; - index = (start_index + (i * i)) % hash_table->capacity; - } - - entry = hash_table->entries + index; - entry->hash = hash; - entry->filled = 1; - entry->value = value; - - if (index != start_index) { - /* This is a new entry, but not the start entry, hence we need to add a next pointer to our entry */ - entry->next = start_entry->next; - start_entry->next = entry; - } - - return HASH_TABLE_SUCCESS; -} - -static int hash_table_insert(unsigned long hash, long value, hash_table_t* hash_table) -{ - int ret = hash_table_insert_value(hash, value, hash_table); - if (ret == HASH_TABLE_SUCCESS) - { - hash_table->hashes[hash_table->n] = hash; - hash_table->n++; - } - return ret; -} - -static hash_table_entry_t* hash_table_find(unsigned long hash, hash_table_t* hash_table) -{ - hash_table_entry_t* entry = hash_table->entries + (hash % hash_table->capacity); - while (entry) - { - if (entry->hash == hash && entry->filled) - { - return entry; - } - entry = entry->next; - } - return NULL; -} - -static void hash_table_grow(hash_table_t* hash_table) -{ - size_t new_capacity; - hash_table_t new_hash_table; - size_t i; - - new_capacity = 2 * hash_table->capacity; - /* Create a new hash table. We're not calling create_hash_table because we want to realloc the hash array */ - new_hash_table.hashes = hash_table->hashes = (unsigned long*) TINYOBJ_REALLOC_SIZED( - (void*) hash_table->hashes, sizeof(unsigned long) * hash_table->capacity, sizeof(unsigned long) * new_capacity); - new_hash_table.entries = (hash_table_entry_t*) TINYOBJ_CALLOC(new_capacity, sizeof(hash_table_entry_t)); - new_hash_table.capacity = new_capacity; - new_hash_table.n = hash_table->n; - - /* Rehash */ - for (i = 0; i < hash_table->capacity; i++) - { - hash_table_entry_t* entry = &hash_table->entries[i]; - if (entry->filled) { - hash_table_insert_value(entry->hash, entry->value, &new_hash_table); - } - } - - TINYOBJ_FREE(hash_table->entries); - (*hash_table) = new_hash_table; -} - -static int hash_table_exists(const char* name, hash_table_t* hash_table) -{ - return hash_table_find(hash_djb2((const unsigned char*)name), hash_table) != NULL; -} - -static void hash_table_set(const char* name, size_t val, hash_table_t* hash_table) -{ - /* Hash name */ - unsigned long hash = hash_djb2((const unsigned char *)name); - - hash_table_entry_t* entry = hash_table_find(hash, hash_table); - if (entry) - { - entry->value = (long)val; - return; - } - - /* Expand if necessary - * Grow until the element has been added - */ - while (hash_table_insert(hash, (long)val, hash_table) != HASH_TABLE_SUCCESS) { - hash_table_grow(hash_table); - } -} - -static long hash_table_get(const char* name, hash_table_t* hash_table) -{ - hash_table_entry_t* ret = hash_table_find(hash_djb2((const unsigned char*)(name)), hash_table); - return ret->value; -} - -static tinyobj_material_t *tinyobj_material_add(tinyobj_material_t *prev, - size_t num_materials, - tinyobj_material_t *new_mat) { - tinyobj_material_t *dst; - size_t num_bytes = sizeof(tinyobj_material_t) * num_materials; - dst = (tinyobj_material_t *)TINYOBJ_REALLOC_SIZED( - prev, num_bytes, num_bytes + sizeof(tinyobj_material_t)); - - dst[num_materials] = (*new_mat); /* Just copy pointer for char* members */ - return dst; -} - -static int is_line_ending(const char *p, size_t i, size_t end_i) { - if (p[i] == '\0') return 1; - if (p[i] == '\n') return 1; /* this includes \r\n */ - if (p[i] == '\r') { - if (((i + 1) < end_i) && (p[i + 1] != '\n')) { /* detect only \r case */ - return 1; - } - } - return 0; -} - -typedef struct { - size_t pos; - size_t len; -} LineInfo; - -/* Find '\n' and create line data. */ -static int get_line_infos(const char *buf, size_t buf_len, LineInfo **line_infos, size_t *num_lines) -{ - size_t i = 0; - size_t end_idx = buf_len; - size_t prev_pos = 0; - size_t line_no = 0; - size_t last_line_ending = 0; - - /* Count # of lines. */ - for (i = 0; i < end_idx; i++) { - if (is_line_ending(buf, i, end_idx)) { - (*num_lines)++; - last_line_ending = i; - } - } - /* The last char from the input may not be a line - * ending character so add an extra line if there - * are more characters after the last line ending - * that was found. */ - if (end_idx - last_line_ending > 1) { - (*num_lines)++; - } - - if (*num_lines == 0) return TINYOBJ_ERROR_EMPTY; - - *line_infos = (LineInfo *)TINYOBJ_MALLOC(sizeof(LineInfo) * (*num_lines)); - - /* Fill line infos. */ - for (i = 0; i < end_idx; i++) { - if (is_line_ending(buf, i, end_idx)) { - (*line_infos)[line_no].pos = prev_pos; - (*line_infos)[line_no].len = i - prev_pos; - prev_pos = i + 1; - line_no++; - } - } - if (end_idx - last_line_ending > 1) { - (*line_infos)[line_no].pos = prev_pos; - (*line_infos)[line_no].len = end_idx - 1 - last_line_ending; - } - - return 0; -} - -static int tinyobj_parse_and_index_mtl_file(tinyobj_material_t **materials_out, - size_t *num_materials_out, - const char *mtl_filename, const char *obj_filename, file_reader_callback file_reader, void *ctx, - hash_table_t* material_table) { - tinyobj_material_t material; - size_t num_materials = 0; - tinyobj_material_t *materials = NULL; - int has_previous_material = 0; - const char *line_end = NULL; - size_t num_lines = 0; - LineInfo *line_infos = NULL; - size_t i = 0; - char *buf = NULL; - size_t len = 0; - - if (materials_out == NULL) { - return TINYOBJ_ERROR_INVALID_PARAMETER; - } - - if (num_materials_out == NULL) { - return TINYOBJ_ERROR_INVALID_PARAMETER; - } - - (*materials_out) = NULL; - (*num_materials_out) = 0; - - file_reader(ctx, mtl_filename, 1, obj_filename, &buf, &len); - if (len < 1) return TINYOBJ_ERROR_INVALID_PARAMETER; - if (buf == NULL) return TINYOBJ_ERROR_INVALID_PARAMETER; - - if (get_line_infos(buf, len, &line_infos, &num_lines) != 0) { - TINYOBJ_FREE(line_infos); - return TINYOBJ_ERROR_EMPTY; - } - - /* Create a default material */ - initMaterial(&material); - - for (i = 0; i < num_lines; i++) { - const char *p = &buf[line_infos[i].pos]; - size_t p_len = line_infos[i].len; - - char linebuf[4096]; - const char *token; - assert(p_len < 4095); - - memcpy(linebuf, p, p_len); - linebuf[p_len] = '\0'; - - token = linebuf; - line_end = token + p_len; - - /* Skip leading space. */ - token += strspn(token, " \t"); - - assert(token); - if (token[0] == '\0') continue; /* empty line */ - - if (token[0] == '#') continue; /* comment line */ - - /* new mtl */ - if ((0 == strncmp(token, "newmtl", 6)) && IS_SPACE((token[6]))) { - char namebuf[4096]; - - /* flush previous material. */ - if (has_previous_material) { - materials = tinyobj_material_add(materials, num_materials, &material); - num_materials++; - } else { - has_previous_material = 1; - } - - /* initial temporary material */ - initMaterial(&material); - - /* set new mtl name */ - token += 7; -#ifdef _MSC_VER - sscanf_s(token, "%s", namebuf, (unsigned)_countof(namebuf)); -#else - sscanf(token, "%s", namebuf); -#endif - material.name = my_strdup(namebuf, (size_t) (line_end - token)); - - /* Add material to material table */ - if (material_table) - hash_table_set(material.name, num_materials, material_table); - - continue; - } - - /* ambient */ - if (token[0] == 'K' && token[1] == 'a' && IS_SPACE((token[2]))) { - float r, g, b; - token += 2; - parseFloat3(&r, &g, &b, &token); - material.ambient[0] = r; - material.ambient[1] = g; - material.ambient[2] = b; - continue; - } - - /* diffuse */ - if (token[0] == 'K' && token[1] == 'd' && IS_SPACE((token[2]))) { - float r, g, b; - token += 2; - parseFloat3(&r, &g, &b, &token); - material.diffuse[0] = r; - material.diffuse[1] = g; - material.diffuse[2] = b; - continue; - } - - /* specular */ - if (token[0] == 'K' && token[1] == 's' && IS_SPACE((token[2]))) { - float r, g, b; - token += 2; - parseFloat3(&r, &g, &b, &token); - material.specular[0] = r; - material.specular[1] = g; - material.specular[2] = b; - continue; - } - - /* transmittance */ - if (token[0] == 'K' && token[1] == 't' && IS_SPACE((token[2]))) { - float r, g, b; - token += 2; - parseFloat3(&r, &g, &b, &token); - material.transmittance[0] = r; - material.transmittance[1] = g; - material.transmittance[2] = b; - continue; - } - - /* ior(index of refraction) */ - if (token[0] == 'N' && token[1] == 'i' && IS_SPACE((token[2]))) { - token += 2; - material.ior = parseFloat(&token); - continue; - } - - /* emission */ - if (token[0] == 'K' && token[1] == 'e' && IS_SPACE(token[2])) { - float r, g, b; - token += 2; - parseFloat3(&r, &g, &b, &token); - material.emission[0] = r; - material.emission[1] = g; - material.emission[2] = b; - continue; - } - - /* shininess */ - if (token[0] == 'N' && token[1] == 's' && IS_SPACE(token[2])) { - token += 2; - material.shininess = parseFloat(&token); - continue; - } - - /* illum model */ - if (0 == strncmp(token, "illum", 5) && IS_SPACE(token[5])) { - token += 6; - material.illum = parseInt(&token); - continue; - } - - /* dissolve */ - if ((token[0] == 'd' && IS_SPACE(token[1]))) { - token += 1; - material.dissolve = parseFloat(&token); - continue; - } - if (token[0] == 'T' && token[1] == 'r' && IS_SPACE(token[2])) { - token += 2; - /* Invert value of Tr(assume Tr is in range [0, 1]) */ - material.dissolve = 1.0f - parseFloat(&token); - continue; - } - - /* ambient texture */ - if ((0 == strncmp(token, "map_Ka", 6)) && IS_SPACE(token[6])) { - token += 7; - material.ambient_texname = my_strdup(token, (size_t) (line_end - token)); - continue; - } - - /* diffuse texture */ - if ((0 == strncmp(token, "map_Kd", 6)) && IS_SPACE(token[6])) { - token += 7; - material.diffuse_texname = my_strdup(token, (size_t) (line_end - token)); - continue; - } - - /* specular texture */ - if ((0 == strncmp(token, "map_Ks", 6)) && IS_SPACE(token[6])) { - token += 7; - material.specular_texname = my_strdup(token, (size_t) (line_end - token)); - continue; - } - - /* specular highlight texture */ - if ((0 == strncmp(token, "map_Ns", 6)) && IS_SPACE(token[6])) { - token += 7; - material.specular_highlight_texname = my_strdup(token, (size_t) (line_end - token)); - continue; - } - - /* bump texture */ - if ((0 == strncmp(token, "map_bump", 8)) && IS_SPACE(token[8])) { - token += 9; - material.bump_texname = my_strdup(token, (size_t) (line_end - token)); - continue; - } - - /* alpha texture */ - if ((0 == strncmp(token, "map_d", 5)) && IS_SPACE(token[5])) { - token += 6; - material.alpha_texname = my_strdup(token, (size_t) (line_end - token)); - continue; - } - - /* bump texture */ - if ((0 == strncmp(token, "bump", 4)) && IS_SPACE(token[4])) { - token += 5; - material.bump_texname = my_strdup(token, (size_t) (line_end - token)); - continue; - } - - /* displacement texture */ - if ((0 == strncmp(token, "disp", 4)) && IS_SPACE(token[4])) { - token += 5; - material.displacement_texname = my_strdup(token, (size_t) (line_end - token)); - continue; - } - - /* @todo { unknown parameter } */ - } - - TINYOBJ_FREE(line_infos); - - if (material.name) { - /* Flush last material element */ - materials = tinyobj_material_add(materials, num_materials, &material); - num_materials++; - } - - (*num_materials_out) = num_materials; - (*materials_out) = materials; - - return TINYOBJ_SUCCESS; -} - -int tinyobj_parse_mtl_file(tinyobj_material_t **materials_out, - size_t *num_materials_out, - const char *mtl_filename, const char *obj_filename, file_reader_callback file_reader, - void *ctx) { - return tinyobj_parse_and_index_mtl_file(materials_out, num_materials_out, mtl_filename, obj_filename, file_reader, ctx, NULL); -} - - -typedef enum { - COMMAND_EMPTY, - COMMAND_V, - COMMAND_VN, - COMMAND_VT, - COMMAND_F, - COMMAND_G, - COMMAND_O, - COMMAND_USEMTL, - COMMAND_MTLLIB - -} CommandType; - -typedef struct { - float vx, vy, vz; - float nx, ny, nz; - float tx, ty; - - /* @todo { Use dynamic array } */ - tinyobj_vertex_index_t f[TINYOBJ_MAX_FACES_PER_F_LINE]; - size_t num_f; - - int f_num_verts[TINYOBJ_MAX_FACES_PER_F_LINE]; - size_t num_f_num_verts; - - const char *group_name; - unsigned int group_name_len; - int pad0; - - const char *object_name; - unsigned int object_name_len; - int pad1; - - const char *material_name; - unsigned int material_name_len; - int pad2; - - const char *mtllib_name; - unsigned int mtllib_name_len; - - CommandType type; -} Command; - -static int parseLine(Command *command, const char *p, size_t p_len, - int triangulate) { - char linebuf[4096]; - const char *token; - assert(p_len < 4095); - - memcpy(linebuf, p, p_len); - linebuf[p_len] = '\0'; - - token = linebuf; - - command->type = COMMAND_EMPTY; - - /* Skip leading space. */ - skip_space(&token); - - assert(token); - if (token[0] == '\0') { /* empty line */ - return 0; - } - - if (token[0] == '#') { /* comment line */ - return 0; - } - - /* vertex */ - if (token[0] == 'v' && IS_SPACE((token[1]))) { - float x, y, z; - token += 2; - parseFloat3(&x, &y, &z, &token); - command->vx = x; - command->vy = y; - command->vz = z; - command->type = COMMAND_V; - return 1; - } - - /* normal */ - if (token[0] == 'v' && token[1] == 'n' && IS_SPACE((token[2]))) { - float x, y, z; - token += 3; - parseFloat3(&x, &y, &z, &token); - command->nx = x; - command->ny = y; - command->nz = z; - command->type = COMMAND_VN; - return 1; - } - - /* texcoord */ - if (token[0] == 'v' && token[1] == 't' && IS_SPACE((token[2]))) { - float x, y; - token += 3; - parseFloat2(&x, &y, &token); - command->tx = x; - command->ty = y; - command->type = COMMAND_VT; - return 1; - } - - /* face */ - if (token[0] == 'f' && IS_SPACE((token[1]))) { - size_t num_f = 0; - - tinyobj_vertex_index_t f[TINYOBJ_MAX_FACES_PER_F_LINE]; - token += 2; - skip_space(&token); - - while (!IS_NEW_LINE(token[0])) { - tinyobj_vertex_index_t vi = parseRawTriple(&token); - skip_space_and_cr(&token); - - f[num_f] = vi; - num_f++; - } - - command->type = COMMAND_F; - - if (triangulate) { - size_t k; - size_t n = 0; - - tinyobj_vertex_index_t i0 = f[0]; - tinyobj_vertex_index_t i1; - tinyobj_vertex_index_t i2 = f[1]; - - assert(3 * num_f < TINYOBJ_MAX_FACES_PER_F_LINE); - - for (k = 2; k < num_f; k++) { - i1 = i2; - i2 = f[k]; - command->f[3 * n + 0] = i0; - command->f[3 * n + 1] = i1; - command->f[3 * n + 2] = i2; - - command->f_num_verts[n] = 3; - n++; - } - command->num_f = 3 * n; - command->num_f_num_verts = n; - - } else { - size_t k = 0; - assert(num_f < TINYOBJ_MAX_FACES_PER_F_LINE); - for (k = 0; k < num_f; k++) { - command->f[k] = f[k]; - } - - command->num_f = num_f; - command->f_num_verts[0] = (int)num_f; - command->num_f_num_verts = 1; - } - - return 1; - } - - /* use mtl */ - if ((0 == strncmp(token, "usemtl", 6)) && IS_SPACE((token[6]))) { - token += 7; - - skip_space(&token); - command->material_name = p + (token - linebuf); - command->material_name_len = (unsigned int)length_until_newline( - token, (p_len - (size_t)(token - linebuf)) + 1); - command->type = COMMAND_USEMTL; - - return 1; - } - - /* load mtl */ - if ((0 == strncmp(token, "mtllib", 6)) && IS_SPACE((token[6]))) { - /* By specification, `mtllib` should be appear only once in .obj */ - token += 7; - - skip_space(&token); - command->mtllib_name = p + (token - linebuf); - command->mtllib_name_len = (unsigned int)length_until_newline( - token, p_len - (size_t)(token - linebuf)) + - 1; - command->type = COMMAND_MTLLIB; - - return 1; - } - - /* group name */ - if (token[0] == 'g' && IS_SPACE((token[1]))) { - /* @todo { multiple group name. } */ - token += 2; - - command->group_name = p + (token - linebuf); - command->group_name_len = (unsigned int)length_until_newline( - token, p_len - (size_t)(token - linebuf)) + - 1; - command->type = COMMAND_G; - - return 1; - } - - /* object name */ - if (token[0] == 'o' && IS_SPACE((token[1]))) { - /* @todo { multiple object name? } */ - token += 2; - - command->object_name = p + (token - linebuf); - command->object_name_len = (unsigned int)length_until_newline( - token, p_len - (size_t)(token - linebuf)) + - 1; - command->type = COMMAND_O; - - return 1; - } - - return 0; -} - -static size_t basename_len(const char *filename, size_t filename_length) { - /* Count includes NUL terminator. */ - const char *p = &filename[filename_length - 1]; - size_t count = 1; - - /* On Windows, the directory delimiter is '\' and both it and '/' is - * reserved by the filesystem. On *nix platforms, only the '/' character - * is reserved, so account for the two cases separately. */ - #if _WIN32 - while (p[-1] != '/' && p[-1] != '\\') { - if (p == filename) { - count = filename_length; - return count; - } - count++; - p--; - } - p++; - return count; - #else - while (*(--p) != '/') { - if (p == filename) { - count = filename_length; - return count; - } - count++; - } - return count; - #endif -} - -static char *generate_mtl_filename(const char *obj_filename, - size_t obj_filename_length, - const char *mtllib_name, - size_t mtllib_name_length) { - /* Create a dynamically-allocated material filename. This allows the material - * and obj files to be separated, however the mtllib name in the OBJ file - * must be a relative path to the material file from the OBJ's directory. - * This does not support the matllib name as an absolute address. */ - char *mtl_filename; - char *p; - size_t mtl_filename_length; - size_t obj_basename_length; - - /* Calculate required size of mtl_filename and allocate */ - obj_basename_length = basename_len(obj_filename, obj_filename_length); - mtl_filename_length = (obj_filename_length - obj_basename_length) + mtllib_name_length; - mtl_filename = (char *)TINYOBJ_MALLOC(mtl_filename_length); - - /* Copy over the obj's path */ - memcpy(mtl_filename, obj_filename, (obj_filename_length - obj_basename_length)); - - /* Overwrite the obj basename with the mtllib name, filling the string */ - p = &mtl_filename[mtl_filename_length - mtllib_name_length]; - strcpy(p, mtllib_name); - return mtl_filename; -} - -int tinyobj_parse_obj(tinyobj_attrib_t *attrib, tinyobj_shape_t **shapes, - size_t *num_shapes, tinyobj_material_t **materials_out, - size_t *num_materials_out, const char *obj_filename, - file_reader_callback file_reader, void *ctx, - unsigned int flags) { - LineInfo *line_infos = NULL; - Command *commands = NULL; - size_t num_lines = 0; - - size_t num_v = 0; - size_t num_vn = 0; - size_t num_vt = 0; - size_t num_f = 0; - size_t num_faces = 0; - - int mtllib_line_index = -1; - - tinyobj_material_t *materials = NULL; - size_t num_materials = 0; - - hash_table_t material_table; - - char *buf = NULL; - size_t len = 0; - file_reader(ctx, obj_filename, /* is_mtl */0, obj_filename, &buf, &len); - - if (len < 1) return TINYOBJ_ERROR_INVALID_PARAMETER; - if (attrib == NULL) return TINYOBJ_ERROR_INVALID_PARAMETER; - if (shapes == NULL) return TINYOBJ_ERROR_INVALID_PARAMETER; - if (num_shapes == NULL) return TINYOBJ_ERROR_INVALID_PARAMETER; - if (buf == NULL) return TINYOBJ_ERROR_INVALID_PARAMETER; - if (materials_out == NULL) return TINYOBJ_ERROR_INVALID_PARAMETER; - if (num_materials_out == NULL) return TINYOBJ_ERROR_INVALID_PARAMETER; - - tinyobj_attrib_init(attrib); - - /* 1. create line data */ - if (get_line_infos(buf, len, &line_infos, &num_lines) != 0) { - return TINYOBJ_ERROR_EMPTY; - } - - commands = (Command *)TINYOBJ_MALLOC(sizeof(Command) * num_lines); - - create_hash_table(HASH_TABLE_DEFAULT_SIZE, &material_table); - - /* 2. parse each line */ - { - size_t i = 0; - for (i = 0; i < num_lines; i++) { - int ret = parseLine(&commands[i], &buf[line_infos[i].pos], - line_infos[i].len, flags & TINYOBJ_FLAG_TRIANGULATE); - if (ret) { - if (commands[i].type == COMMAND_V) { - num_v++; - } else if (commands[i].type == COMMAND_VN) { - num_vn++; - } else if (commands[i].type == COMMAND_VT) { - num_vt++; - } else if (commands[i].type == COMMAND_F) { - num_f += commands[i].num_f; - num_faces += commands[i].num_f_num_verts; - } - - if (commands[i].type == COMMAND_MTLLIB) { - mtllib_line_index = (int)i; - } - } - } - } - - /* line_infos are not used anymore. Release memory. */ - if (line_infos) { - TINYOBJ_FREE(line_infos); - } - - /* Load material (if it exists) */ - if (mtllib_line_index >= 0 && commands[mtllib_line_index].mtllib_name && - commands[mtllib_line_index].mtllib_name_len > 0) { - /* Maximum length allowed by Linux - higher than Windows and macOS */ - size_t obj_filename_len = my_strnlen(obj_filename, 4096 + 255) + 1; - char *mtl_filename; - char *mtllib_name; - size_t mtllib_name_len = 0; - int ret; - - mtllib_name_len = length_until_line_feed(commands[mtllib_line_index].mtllib_name, - commands[mtllib_line_index].mtllib_name_len); - - mtllib_name = my_strndup(commands[mtllib_line_index].mtllib_name, - mtllib_name_len); - - /* allow for NUL terminator */ - mtllib_name_len++; - mtl_filename = generate_mtl_filename(obj_filename, obj_filename_len, - mtllib_name, mtllib_name_len); - - ret = tinyobj_parse_and_index_mtl_file(&materials, &num_materials, - mtl_filename, obj_filename, - file_reader, ctx, - &material_table); - - if (ret != TINYOBJ_SUCCESS) { - /* warning. */ - fprintf(stderr, "TINYOBJ: Failed to parse material file '%s': %d\n", mtl_filename, ret); - } - TINYOBJ_FREE(mtl_filename); - TINYOBJ_FREE(mtllib_name); - } - - /* Construct attributes */ - - { - size_t v_count = 0; - size_t n_count = 0; - size_t t_count = 0; - size_t f_count = 0; - size_t face_count = 0; - int material_id = -1; /* -1 = default unknown material. */ - size_t i = 0; - - attrib->vertices = (float *)TINYOBJ_MALLOC(sizeof(float) * num_v * 3); - attrib->num_vertices = (unsigned int)num_v; - attrib->normals = (float *)TINYOBJ_MALLOC(sizeof(float) * num_vn * 3); - attrib->num_normals = (unsigned int)num_vn; - attrib->texcoords = (float *)TINYOBJ_MALLOC(sizeof(float) * num_vt * 2); - attrib->num_texcoords = (unsigned int)num_vt; - attrib->faces = (tinyobj_vertex_index_t *)TINYOBJ_MALLOC( - sizeof(tinyobj_vertex_index_t) * num_f); - attrib->num_faces = (unsigned int)num_f; - attrib->face_num_verts = (int *)TINYOBJ_MALLOC(sizeof(int) * num_faces); - attrib->material_ids = (int *)TINYOBJ_MALLOC(sizeof(int) * num_faces); - attrib->num_face_num_verts = (unsigned int)num_faces; - - for (i = 0; i < num_lines; i++) { - if (commands[i].type == COMMAND_EMPTY) { - continue; - } else if (commands[i].type == COMMAND_USEMTL) { - /* @todo - if (commands[t][i].material_name && - commands[t][i].material_name_len > 0) { - std::string material_name(commands[t][i].material_name, - commands[t][i].material_name_len); - - if (material_map.find(material_name) != material_map.end()) { - material_id = material_map[material_name]; - } else { - // Assign invalid material ID - material_id = -1; - } - } - */ - if (commands[i].material_name && - commands[i].material_name_len >0) - { - /* Create a null terminated string */ - char* material_name_null_term = (char*) TINYOBJ_MALLOC(commands[i].material_name_len + 1); - memcpy((void*) material_name_null_term, (const void*) commands[i].material_name, commands[i].material_name_len); - material_name_null_term[commands[i].material_name_len] = 0; - - if (hash_table_exists(material_name_null_term, &material_table)) - material_id = (int)hash_table_get(material_name_null_term, &material_table); - else - material_id = -1; - - TINYOBJ_FREE(material_name_null_term); - } - } else if (commands[i].type == COMMAND_V) { - attrib->vertices[3 * v_count + 0] = commands[i].vx; - attrib->vertices[3 * v_count + 1] = commands[i].vy; - attrib->vertices[3 * v_count + 2] = commands[i].vz; - v_count++; - } else if (commands[i].type == COMMAND_VN) { - attrib->normals[3 * n_count + 0] = commands[i].nx; - attrib->normals[3 * n_count + 1] = commands[i].ny; - attrib->normals[3 * n_count + 2] = commands[i].nz; - n_count++; - } else if (commands[i].type == COMMAND_VT) { - attrib->texcoords[2 * t_count + 0] = commands[i].tx; - attrib->texcoords[2 * t_count + 1] = commands[i].ty; - t_count++; - } else if (commands[i].type == COMMAND_F) { - size_t k = 0; - for (k = 0; k < commands[i].num_f; k++) { - tinyobj_vertex_index_t vi = commands[i].f[k]; - int v_idx = fixIndex(vi.v_idx, v_count); - int vn_idx = fixIndex(vi.vn_idx, n_count); - int vt_idx = fixIndex(vi.vt_idx, t_count); - attrib->faces[f_count + k].v_idx = v_idx; - attrib->faces[f_count + k].vn_idx = vn_idx; - attrib->faces[f_count + k].vt_idx = vt_idx; - } - - for (k = 0; k < commands[i].num_f_num_verts; k++) { - attrib->material_ids[face_count + k] = material_id; - attrib->face_num_verts[face_count + k] = commands[i].f_num_verts[k]; - } - - f_count += commands[i].num_f; - face_count += commands[i].num_f_num_verts; - } - } - } - - /* 5. Construct shape information. */ - { - unsigned int face_count = 0; - size_t i = 0; - size_t n = 0; - size_t shape_idx = 0; - - const char *shape_name = NULL; - unsigned int shape_name_len = 0; - const char *prev_shape_name = NULL; - unsigned int prev_shape_name_len = 0; - unsigned int prev_shape_face_offset = 0; - unsigned int prev_face_offset = 0; - tinyobj_shape_t prev_shape = {NULL, 0, 0}; - - /* Find the number of shapes in .obj */ - for (i = 0; i < num_lines; i++) { - if (commands[i].type == COMMAND_O || commands[i].type == COMMAND_G) { - n++; - } - } - - /* Allocate array of shapes with maximum possible size(+1 for unnamed - * group/object). - * Actual # of shapes found in .obj is determined in the later */ - (*shapes) = (tinyobj_shape_t*)TINYOBJ_MALLOC(sizeof(tinyobj_shape_t) * (n + 1)); - - for (i = 0; i < num_lines; i++) { - if (commands[i].type == COMMAND_O || commands[i].type == COMMAND_G) { - if (commands[i].type == COMMAND_O) { - shape_name = commands[i].object_name; - shape_name_len = commands[i].object_name_len; - } else { - shape_name = commands[i].group_name; - shape_name_len = commands[i].group_name_len; - } - - if (face_count == 0) { - /* 'o' or 'g' appears before any 'f' */ - prev_shape_name = shape_name; - prev_shape_name_len = shape_name_len; - prev_shape_face_offset = face_count; - prev_face_offset = face_count; - } else { - if (shape_idx == 0) { - /* 'o' or 'g' after some 'v' lines. */ - (*shapes)[shape_idx].name = my_strndup( - prev_shape_name, prev_shape_name_len); /* may be NULL */ - (*shapes)[shape_idx].face_offset = prev_shape.face_offset; - (*shapes)[shape_idx].length = face_count - prev_face_offset; - shape_idx++; - - prev_face_offset = face_count; - - } else { - if ((face_count - prev_face_offset) > 0) { - (*shapes)[shape_idx].name = - my_strndup(prev_shape_name, prev_shape_name_len); - (*shapes)[shape_idx].face_offset = prev_face_offset; - (*shapes)[shape_idx].length = face_count - prev_face_offset; - shape_idx++; - prev_face_offset = face_count; - } - } - - /* Record shape info for succeeding 'o' or 'g' command. */ - prev_shape_name = shape_name; - prev_shape_name_len = shape_name_len; - prev_shape_face_offset = face_count; - } - } - if (commands[i].type == COMMAND_F) { - face_count++; - } - } - - if ((face_count - prev_face_offset) > 0) { - size_t length = face_count - prev_shape_face_offset; - if (length > 0) { - (*shapes)[shape_idx].name = - my_strndup(prev_shape_name, prev_shape_name_len); - (*shapes)[shape_idx].face_offset = prev_face_offset; - (*shapes)[shape_idx].length = face_count - prev_face_offset; - shape_idx++; - } - } else { - /* Guess no 'v' line occurrence after 'o' or 'g', so discards current - * shape information. */ - } - - (*num_shapes) = shape_idx; - } - - if (commands) { - TINYOBJ_FREE(commands); - } - - destroy_hash_table(&material_table); - - (*materials_out) = materials; - (*num_materials_out) = num_materials; - - return TINYOBJ_SUCCESS; -} - -void tinyobj_attrib_init(tinyobj_attrib_t *attrib) { - attrib->vertices = NULL; - attrib->num_vertices = 0; - attrib->normals = NULL; - attrib->num_normals = 0; - attrib->texcoords = NULL; - attrib->num_texcoords = 0; - attrib->faces = NULL; - attrib->num_faces = 0; - attrib->face_num_verts = NULL; - attrib->num_face_num_verts = 0; - attrib->material_ids = NULL; -} - -void tinyobj_attrib_free(tinyobj_attrib_t *attrib) { - if (attrib->vertices) TINYOBJ_FREE(attrib->vertices); - if (attrib->normals) TINYOBJ_FREE(attrib->normals); - if (attrib->texcoords) TINYOBJ_FREE(attrib->texcoords); - if (attrib->faces) TINYOBJ_FREE(attrib->faces); - if (attrib->face_num_verts) TINYOBJ_FREE(attrib->face_num_verts); - if (attrib->material_ids) TINYOBJ_FREE(attrib->material_ids); -} - -void tinyobj_shapes_free(tinyobj_shape_t *shapes, size_t num_shapes) { - size_t i; - if (shapes == NULL) return; - - for (i = 0; i < num_shapes; i++) { - if (shapes[i].name) TINYOBJ_FREE(shapes[i].name); - } - - TINYOBJ_FREE(shapes); -} - -void tinyobj_materials_free(tinyobj_material_t *materials, - size_t num_materials) { - size_t i; - if (materials == NULL) return; - - for (i = 0; i < num_materials; i++) { - if (materials[i].name) TINYOBJ_FREE(materials[i].name); - if (materials[i].ambient_texname) TINYOBJ_FREE(materials[i].ambient_texname); - if (materials[i].diffuse_texname) TINYOBJ_FREE(materials[i].diffuse_texname); - if (materials[i].specular_texname) TINYOBJ_FREE(materials[i].specular_texname); - if (materials[i].specular_highlight_texname) - TINYOBJ_FREE(materials[i].specular_highlight_texname); - if (materials[i].bump_texname) TINYOBJ_FREE(materials[i].bump_texname); - if (materials[i].displacement_texname) - TINYOBJ_FREE(materials[i].displacement_texname); - if (materials[i].alpha_texname) TINYOBJ_FREE(materials[i].alpha_texname); - } - - TINYOBJ_FREE(materials); -} -#endif /* TINYOBJ_LOADER_C_IMPLEMENTATION */ diff --git a/src/utils.c b/src/utils.c index d7fb185..a510717 100644 --- a/src/utils.c +++ b/src/utils.c @@ -25,33 +25,51 @@ OTHER DEALINGS IN THE SOFTWARE. For more information, please refer to */ #include +#include #include #include "utils.h" char *load_file(const char *file, size_t *size) { - FILE *stream = fopen(file, "rb"); - if (stream == NULL) return NULL; + FILE *stream = fopen(file, "rb"); + if (stream == NULL) return NULL; - fseek(stream, 0, SEEK_END); - long size2 = ftell(stream); - fseek(stream, 0, SEEK_SET); + fseek(stream, 0, SEEK_END); + long size2 = ftell(stream); + fseek(stream, 0, SEEK_SET); - char *dst = (char*) malloc(size2+1); - if (dst == NULL) { - fclose(stream); - return NULL; - } + char *dst = (char*) malloc(size2+1); + if (dst == NULL) { + fclose(stream); + return NULL; + } - fread(dst, 1, size2, stream); - if (ferror(stream)) { - free(dst); - fclose(stream); - return NULL; - } - dst[size2] = '\0'; + fread(dst, 1, size2, stream); + if (ferror(stream)) { + free(dst); + fclose(stream); + return NULL; + } + dst[size2] = '\0'; - fclose(stream); - if (size) *size = size2; - return dst; + fclose(stream); + if (size) *size = size2; + return dst; +} + +static _Thread_local uint64_t wyhash64_x = 0; + +static uint64_t wyhash64(void) { + wyhash64_x += 0x60bee2bee120fc15; + __uint128_t tmp; + tmp = (__uint128_t) wyhash64_x * 0xa3b195354a39b70d; + uint64_t m1 = (tmp >> 64) ^ tmp; + tmp = (__uint128_t)m1 * 0x1b03738712fad5c9; + uint64_t m2 = (tmp >> 64) ^ tmp; + return m2; +} + +float random_float(void) +{ + return (float) wyhash64() / UINT64_MAX; } diff --git a/src/utils.h b/src/utils.h index e6e1cd8..42ef42b 100644 --- a/src/utils.h +++ b/src/utils.h @@ -32,4 +32,6 @@ For more information, please refer to char *load_file(const char *file, size_t *size); inline bool is_space(char c) { return c == ' ' || c == '\r' || c == '\t' || c == '\n'; } -inline bool is_digit(char c) { return c >= '0' && c <= '9'; } \ No newline at end of file +inline bool is_digit(char c) { return c >= '0' && c <= '9'; } + +float random_float(void); \ No newline at end of file diff --git a/src/vector.c b/src/vector.c index 8f91be9..90f27f8 100644 --- a/src/vector.c +++ b/src/vector.c @@ -29,20 +29,93 @@ For more information, please refer to #include #include #include +#include "utils.h" #include "vector.h" #define EPSILON 0.00001 +float maxf(float x, float y) +{ + return x > y ? x : y; +} + +float minf(float x, float y) +{ + return x < y ? x : y; +} + +float absf(float x) +{ + return x < 0 ? -x : x; +} + +float clamp(float x, float min, float max) +{ + assert(min <= max); + if (x < min) return min; + if (x > max) return max; + return x; +} + +Vector3 maxv(Vector3 a, Vector3 b) +{ + return (Vector3) { + maxf(a.x, b.x), + maxf(a.y, b.y), + maxf(a.z, b.z), + }; +} + +Vector3 vec_from_scalar(float s) +{ + return (Vector3) {s, s, s}; +} + bool isnanv(Vector3 v) { return isnan(v.x) || isnan(v.y) || isnan(v.z); } +bool iszerof(float f) +{ + return f < 0.0001 && f > -0.0001; +} + +bool iszerov(Vector3 v) +{ + return iszerof(v.x) && iszerof(v.y) && iszerof(v.z); +} + +float avgv(Vector3 v) +{ + return (v.x + v.y + v.z) / 3; +} + float deg2rad(float deg) { return 3.14159265358979323846 * deg / 180; } +Vector3 random_vector(void) +{ + return (Vector3) { + .x = random_float() * 2 - 1, + .y = random_float() * 2 - 1, + .z = random_float() * 2 - 1, + }; +} + +Vector3 random_direction(void) +{ + return normalize(random_vector()); +} + +Vector3 reflect(Vector3 dir, Vector3 normal) +{ + float f = -2 * dotv(normal, dir); + return combine(dir, normal, 1, f); +} + float norm2_of(Vector3 v) { return v.x * v.x + v.y * v.y + v.z * v.z; @@ -409,276 +482,6 @@ void print_matrix3(Matrix3 m) m.data[2][2]); } -/* -void lina_transpose(float *A, float *B, int m, int n) -{ - assert(m > 0 && n > 0); - assert(A != NULL && B != NULL); - - if(m == 1 || n == 1) { - // For a matrix with height or width of 1 - // row-major and column-major order coincide, - // so the stransposition doesn't change the - // the memory representation. A simple copy - // does the job. - - if(A != B) // Does the copy or the branch cost more? - memcpy(B, A, sizeof(A[0]) * m * n); - - } else if(m == n) { - - // Iterate over the upper triangular portion of - // the matrix and switch each element with the - // corresponding one in the lower triangular portion. - // NOTE: We're assuming A,B might be the same matrix. - // If A,B are the same matrix, then the diagonal - // is copied onto itself. By removing the +1 in - // the inner loop, the copying of the diagonal - // is avoided. - - for(int i = 0; i < n; i += 1) - for(int j = 0; j < i+1; j += 1) { - float temp = A[i*n + j]; - B[i*n + j] = A[j*n + i]; - B[j*n + i] = temp; - } - - } else { - // Not only the matrix needs to be transposed - // assuming the destination matrix is the same - // as the source matrix, but the memory representation - // of the matrix needs to switch from row-major - // to col-major, so it's not as simple as switching - // value's positions. - // This algorithm starts from the A[0][1] value and - // moves it where it needs to go, then gets the value - // that was at that position and puts that in it's - // new position. This process is iterated until the - // starting point A[0][1] is overwritten with the - // new value. In this process the first and last - // value of the matrix never move. - - B[0] = A[0]; - B[m*n - 1] = A[m*n - 1]; - - float item = A[1]; - int next = m; - - while(next != 1) { - float temp = A[next]; - B[next] = item; - item = temp; - next = (next % n) * m + (next / n); - } - - B[1] = item; - } -} - -int lina_decompLUP(float *A, float *L, - float *U, int *P, - int n) -{ - assert(n > 0); - assert(A != L && A != U && L != U); - - for (int i = 0; i < n; i++) - P[i] = i; - - int swaps = 0; - for (int i = 0; i < n; i++) { - - int v = P[i]; - float max_v = A[v * n + i]; - int max_i = i; - - for (int j = i+1; j < n; j++) { - int u = P[j]; - float abs = fabs(A[u * n + j]); - if (abs > max_v) { - max_v = abs; - max_i = j; - } - } - - if (max_i != i) { - - // Swap rows - int temp = P[i]; - P[i] = P[max_i]; - P[max_i] = temp; - - swaps++; - } - } - - for (int i = 0; i < n; i++) - for (int j = 0; j < n; j++) - U[i * n + j] = A[P[i] * n + j]; - - memset(L, 0, sizeof(float) * n * n); - for (int i = 0; i < n; i++) - L[i * n + i] = 1; - - for (int i = 0; i < n; i++) - for (int j = i+1; j < n; j++) { - float u = U[i * n + i]; - L[j * n + i] = U[j * n + i] / u; - for (int k = 0; k < n; k++) - U[j * n + k] -= L[j * n + i] * U[i * n + k]; - } - - return swaps; -} - -// Function: lina_det -// -// Calculates the determinant of the n by n matrix A -// and returns it throught the output parameter [det]. -// -// If not enough memory is available, false is returned, -// else true is returned. -// -// Notes: -// - The output parameter [det] is optional. (you can -// ignore the result by passing NULL). -// -bool lina_det(float *A, int n, float *det) -{ - // Allocate the space for the L,U matrices. - // I can't think of a version of this algorithm - // where a temporary buffer isn't necessary. - float *T = (float*) malloc(sizeof(float) * n * n * 2 + sizeof(int) * n); - if (T == NULL) - return false; - - // Do the decomposition - float *L = T; - float *U = L + (n * n); - int *P = (int*) (U + (n * n)); - - int swaps = lina_decompLUP(A, L, U, P, n); - if (swaps < 0) { - free(T); - return false; - } - - // Knowing that - // - // A = LU - // - // then - // - // det(A) = det(LU) = det(L)det(U) - // - // Since L and U are triangular, their - // determinant is the product of their - // diagonals, so the product of the - // determinants is the product of both - // the diagonals. - - float prod = 1; - for (int i = 0; i < n; i++) { - float l = L[i * n + i]; - float u = U[i * n + i]; - prod *= l * u; - } - - if (swaps & 1) - prod = -prod; - - if (det) - *det = prod; - - free(T); - return true; -} - -// Create the n-1 by n-1 matrix D obtained by -// removing the [del_col] column and [del_row] -// frow the n by n matrix M. -static void -copyMatrixWithoutRowAndCol(float *M, float *D, int n, - int del_col, int del_row) -{ - // Copy the upper-left portion of matrix M - // that comes before the deleted column and - // row. - for (int i = 0; i < del_row; i++) - for (int j = 0; j < del_col; j++) - D[i * (n-1) + j] = M[i * n + j]; - - // Copy the lower left portion that comes - // after both the deleted column and row. - for (int i = del_row+1; i < n; i++) - for (int j = del_col+1; j < n; j++) - D[(i-1) * (n-1) + (j-1)] = M[i * n + j]; - - // Copy the bottom portion that comes after - // the deleted row but before the deleted column. - for (int i = del_row+1; i < n; i++) - for (int j = 0; j < del_col; j++) - D[(i-1) * (n-1) + j] = M[i * n + j]; - - // Copy the right portion that comes after - // the deleted column but before the deleted row. - for (int i = 0; i < del_row; i++) - for (int j = del_col+1; j < n; j++) - D[i * (n-1) + (j-1)] = M[i * n + j]; -} - -bool lina_inverse(Matrix4 M, Matrix4 *D) -{ - float det; - if (!lina_det((float*) &M, 4, &det)) - return false; - - printf("det=%f\n", det); - - if (det == 0) - return false; // The matrix can't be inverted - - Matrix3 T; - Matrix4 M_t = transpose(M); - - for (int i = 0; i < 4; i++) - for (int j = 0; j < 4; j++) { - - copyMatrixWithoutRowAndCol((float*) &M_t, (float*)&T, 4, j, i); - - float det2; - if (!lina_det((float*) &T, 3, &det2)) - return false; - - printf("-----------------\n"); - - print_matrix3(T); - printf("det2=%f\n", det2); - - // If the determinant of M isn't zero, - // neither is this! - assert(det2 != 0); - - bool i_is_odd = i & 1; - bool j_is_odd = j & 1; - int sign = (i_is_odd == j_is_odd) ? 1 : -1; - - D->data[i][j] = sign * det2 / det; - } - return true; -} - -Matrix4 invert(Matrix4 m) -{ - Matrix4 r; - if (!lina_inverse(m, &r)) { - printf("Couldn't invert!\n"); - abort(); - } - return r; -} -*/ - static bool gluInvertMatrix(const float m[16], float invOut[16]) { float inv[16], det; diff --git a/src/vector.h b/src/vector.h index dc18f71..a303289 100644 --- a/src/vector.h +++ b/src/vector.h @@ -61,7 +61,18 @@ typedef struct { } Sphere; float deg2rad(float deg); + +float maxf(float x, float y); +float minf(float x, float y); +float absf(float x); +float clamp(float x, float min, float max); +Vector3 maxv(Vector3 a, Vector3 b); bool isnanv(Vector3 v); +bool iszerof(float f); +bool iszerov(Vector3 v); +float avgv(Vector3 v); + +Vector3 vec_from_scalar(float s); Matrix4 translate_matrix(Vector3 v, float f); Matrix4 identity_matrix(void); @@ -92,4 +103,12 @@ Matrix4 dotm(Matrix4 a, Matrix4 b); Matrix4 transpose(Matrix4 m); bool invert(Matrix4 a, Matrix4 *inv); +Vector3 random_vector(void); +Vector3 random_direction(void); +Vector3 reflect(Vector3 dir, Vector3 normal); + +#ifndef M_PI +#define M_PI 3.1415926538 +#endif + #endif \ No newline at end of file