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



-
-# 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