From 5303ce8014f89239f604f1b001f43bf34f992d87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ars=C3=A8ne=20P=C3=A9rard-Gayot?= Date: Mon, 20 May 2024 14:51:53 +0200 Subject: [PATCH] Add test for C API bindings --- src/bvh/v2/CMakeLists.txt | 5 +- src/bvh/v2/c_api/CMakeLists.txt | 3 + src/bvh/v2/c_api/bvh.cpp | 4 +- src/bvh/v2/c_api/bvh.h | 17 +- src/bvh/v2/c_api/bvh_impl.h | 46 ++-- test/CMakeLists.txt | 24 ++- test/benchmark.cpp | 2 +- test/c_api_example.c | 366 ++++++++++++++++++++++++++++++++ test/load_obj.cpp | 24 +++ test/load_obj.h | 17 ++ test/scenes/cornell_box.obj | 168 +++++++++++++++ 11 files changed, 638 insertions(+), 38 deletions(-) create mode 100644 test/c_api_example.c create mode 100644 test/scenes/cornell_box.obj diff --git a/src/bvh/v2/CMakeLists.txt b/src/bvh/v2/CMakeLists.txt index 636bbf75..8bbfd345 100644 --- a/src/bvh/v2/CMakeLists.txt +++ b/src/bvh/v2/CMakeLists.txt @@ -6,7 +6,10 @@ if (Threads_FOUND) target_link_libraries(bvh INTERFACE Threads::Threads) endif() -target_include_directories(bvh INTERFACE ../..) +target_include_directories(bvh INTERFACE + $ + $) + set_target_properties(bvh PROPERTIES CXX_STANDARD 20) install( diff --git a/src/bvh/v2/c_api/CMakeLists.txt b/src/bvh/v2/c_api/CMakeLists.txt index 570bdfc8..b2c0b309 100644 --- a/src/bvh/v2/c_api/CMakeLists.txt +++ b/src/bvh/v2/c_api/CMakeLists.txt @@ -1,6 +1,9 @@ add_library(bvh_c SHARED bvh.cpp) target_link_libraries(bvh_c PRIVATE bvh) target_compile_definitions(bvh_c PRIVATE -DBVH_BUILD_API) +target_include_directories(bvh_c INTERFACE + $ + $) set_target_properties(bvh_c PROPERTIES CXX_STANDARD 20 CXX_VISIBILITY_PRESET hidden diff --git a/src/bvh/v2/c_api/bvh.cpp b/src/bvh/v2/c_api/bvh.cpp index df837c5a..df48f93e 100644 --- a/src/bvh/v2/c_api/bvh.cpp +++ b/src/bvh/v2/c_api/bvh.cpp @@ -11,11 +11,11 @@ BVH_TYPES(double, 3, 3d) extern "C" { -struct bvh_thread_pool* bvh_thread_pool_create(size_t thread_count) { +BVH_EXPORT struct bvh_thread_pool* bvh_thread_pool_create(size_t thread_count) { return reinterpret_cast(new bvh::v2::ThreadPool(thread_count)); } -void bvh_thread_pool_destroy(bvh_thread_pool* thread_pool) { +BVH_EXPORT void bvh_thread_pool_destroy(bvh_thread_pool* thread_pool) { return delete reinterpret_cast(thread_pool); } diff --git a/src/bvh/v2/c_api/bvh.h b/src/bvh/v2/c_api/bvh.h index bdc7b525..819b0f9d 100644 --- a/src/bvh/v2/c_api/bvh.h +++ b/src/bvh/v2/c_api/bvh.h @@ -16,15 +16,17 @@ extern "C" { #endif #ifdef _MSC_VER -#ifdef BVH_BUILD_API -#define BVH_API __declspec(dllexport) +#define BVH_EXPORT __declspec(dllexport) +#define BVH_IMPORT __declspec(dllimport) #else -#define BVH_API __declspec(dllimport) +#define BVH_EXPORT __attribute__((visibility("default"))) +#define BVH_IMPORT BVH_EXPORT #endif -#elif defined(__GCC__) || defined(__CLANG__) -#define BVH_API __attribute__((visibility("default"))) + +#ifdef BVH_BUILD_API +#define BVH_API BVH_EXPORT #else -#define BVH_API +#define BVH_API BVH_IMPORT #endif #define BVH_ROOT_INDEX 0 @@ -80,9 +82,6 @@ struct bvh_intersect_callbackd { bool (*user_fn)(void*, double*, size_t begin, size_t end); }; -struct bvh_tri3f { struct bvh_vec3f v[3]; }; -struct bvh_tri3d { struct bvh_vec3d v[3]; }; - // Thread Pool ------------------------------------------------------------------------------------ // A thread count of zero instructs the thread pool to detect the number of threads available on the diff --git a/src/bvh/v2/c_api/bvh_impl.h b/src/bvh/v2/c_api/bvh_impl.h index 2f459b8c..cae5b3c2 100644 --- a/src/bvh/v2/c_api/bvh_impl.h +++ b/src/bvh/v2/c_api/bvh_impl.h @@ -260,7 +260,7 @@ static void bvh_intersect_ray( }; #define BVH_IMPL(T, Dim, suffix) \ - typename BvhTypes::Bvh* bvh##suffix##_build( \ + BVH_EXPORT typename BvhTypes::Bvh* bvh##suffix##_build( \ bvh_thread_pool* thread_pool, \ const typename BvhTypes::BBox* bboxes, \ const typename BvhTypes::Vec* centers, \ @@ -269,82 +269,82 @@ static void bvh_intersect_ray( { \ return bvh_build(thread_pool, bboxes, centers, prim_count, config); \ } \ - void bvh##suffix##_destroy(typename BvhTypes::Bvh* bvh) { \ + BVH_EXPORT void bvh##suffix##_destroy(typename BvhTypes::Bvh* bvh) { \ delete reinterpret_cast>*>(bvh); \ } \ - void bvh##suffix##_save(const typename BvhTypes::Bvh* bvh, FILE* file) { \ + BVH_EXPORT void bvh##suffix##_save(const typename BvhTypes::Bvh* bvh, FILE* file) { \ bvh_save(bvh, file); \ } \ - typename BvhTypes::Bvh* bvh##suffix##_load(FILE* file) { \ + BVH_EXPORT typename BvhTypes::Bvh* bvh##suffix##_load(FILE* file) { \ return bvh_load(file); \ } \ - typename BvhTypes::Node* bvh##suffix##_get_node(typename BvhTypes::Bvh* bvh, size_t node_id) { \ + BVH_EXPORT typename BvhTypes::Node* bvh##suffix##_get_node(typename BvhTypes::Bvh* bvh, size_t node_id) { \ return bvh_get_node(bvh, node_id); \ } \ - size_t bvh##suffix##_get_prim_id(const typename BvhTypes::Bvh* bvh, size_t i) { \ + BVH_EXPORT size_t bvh##suffix##_get_prim_id(const typename BvhTypes::Bvh* bvh, size_t i) { \ return bvh_get_prim_id(bvh, i); \ } \ - size_t bvh##suffix##_get_prim_count(const typename BvhTypes::Bvh* bvh) { \ + BVH_EXPORT size_t bvh##suffix##_get_prim_count(const typename BvhTypes::Bvh* bvh) { \ return bvh_get_prim_count(bvh); \ } \ - size_t bvh##suffix##_get_node_count(const typename BvhTypes::Bvh* bvh) { \ + BVH_EXPORT size_t bvh##suffix##_get_node_count(const typename BvhTypes::Bvh* bvh) { \ return bvh_get_node_count(bvh); \ } \ - bool bvh_node##suffix##_is_leaf(const typename BvhTypes::Node* node) { \ + BVH_EXPORT bool bvh_node##suffix##_is_leaf(const typename BvhTypes::Node* node) { \ return bvh_node_is_leaf(node); \ } \ - size_t bvh_node##suffix##_get_prim_count(const typename BvhTypes::Node* node) { \ + BVH_EXPORT size_t bvh_node##suffix##_get_prim_count(const typename BvhTypes::Node* node) { \ return bvh_node_get_prim_count(node); \ } \ - void bvh_node##suffix##_set_prim_count(typename BvhTypes::Node* node, size_t prim_count) { \ + BVH_EXPORT void bvh_node##suffix##_set_prim_count(typename BvhTypes::Node* node, size_t prim_count) { \ bvh_node_set_prim_count(node, prim_count); \ } \ - size_t bvh_node##suffix##_get_first_id(const typename BvhTypes::Node* node) { \ + BVH_EXPORT size_t bvh_node##suffix##_get_first_id(const typename BvhTypes::Node* node) { \ return bvh_node_get_first_id(node); \ } \ - void bvh_node##suffix##_set_first_id(typename BvhTypes::Node* node, size_t first_id) { \ + BVH_EXPORT void bvh_node##suffix##_set_first_id(typename BvhTypes::Node* node, size_t first_id) { \ bvh_node_set_first_id(node, first_id); \ } \ - typename BvhTypes::BBox bvh_node##suffix##_get_bbox(const typename BvhTypes::Node* node) { \ + BVH_EXPORT typename BvhTypes::BBox bvh_node##suffix##_get_bbox(const typename BvhTypes::Node* node) { \ return bvh_node_get_bbox(node); \ } \ - void bvh_node##suffix##_set_bbox(typename BvhTypes::Node* node, const typename BvhTypes::BBox* bbox) { \ + BVH_EXPORT void bvh_node##suffix##_set_bbox(typename BvhTypes::Node* node, const typename BvhTypes::BBox* bbox) { \ bvh_node_set_bbox(node, bbox); \ } \ - void bvh##suffix##_append_node(typename BvhTypes::Bvh* bvh) { \ + BVH_EXPORT void bvh##suffix##_append_node(typename BvhTypes::Bvh* bvh) { \ bvh_append_node(bvh); \ } \ - void bvh##suffix##_remove_last_node(typename BvhTypes::Bvh* bvh) { \ + BVH_EXPORT void bvh##suffix##_remove_last_node(typename BvhTypes::Bvh* bvh) { \ bvh_remove_last_node(bvh); \ } \ - void bvh##suffix##_refit(typename BvhTypes::Bvh* bvh) { \ + BVH_EXPORT void bvh##suffix##_refit(typename BvhTypes::Bvh* bvh) { \ bvh_refit(bvh); \ } \ - void bvh##suffix##_optimize(bvh_thread_pool* thread_pool, typename BvhTypes::Bvh* bvh) { \ + BVH_EXPORT void bvh##suffix##_optimize(bvh_thread_pool* thread_pool, typename BvhTypes::Bvh* bvh) { \ bvh_optimize(thread_pool, bvh); \ } \ - void bvh##suffix##_intersect_ray_any( \ + BVH_EXPORT void bvh##suffix##_intersect_ray_any( \ const typename BvhTypes::Bvh* bvh, \ const typename BvhTypes::Ray* ray, \ const typename BvhCallback::Type* callback) \ { \ bvh_intersect_ray(bvh, ray, callback); \ } \ - void bvh##suffix##_intersect_ray_any_robust( \ + BVH_EXPORT void bvh##suffix##_intersect_ray_any_robust( \ const typename BvhTypes::Bvh* bvh, \ const typename BvhTypes::Ray* ray, \ const typename BvhCallback::Type* callback) \ { \ bvh_intersect_ray(bvh, ray, callback); \ } \ - void bvh##suffix##_intersect_ray( \ + BVH_EXPORT void bvh##suffix##_intersect_ray( \ const typename BvhTypes::Bvh* bvh, \ const typename BvhTypes::Ray* ray, \ const typename BvhCallback::Type* callback) \ { \ bvh_intersect_ray(bvh, ray, callback); \ } \ - void bvh##suffix##_intersect_ray_robust( \ + BVH_EXPORT void bvh##suffix##_intersect_ray_robust( \ const typename BvhTypes::Bvh* bvh, \ const typename BvhTypes::Ray* ray, \ const typename BvhCallback::Type* callback) \ diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index bdf92254..93b81c7e 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -5,14 +5,34 @@ endif() add_executable(simple_example simple_example.cpp) target_link_libraries(simple_example PUBLIC bvh) set_target_properties(simple_example PROPERTIES CXX_STANDARD 20) +add_test(NAME simple_example COMMAND simple_example) add_executable(serialize serialize.cpp) target_link_libraries(serialize PUBLIC bvh) set_target_properties(serialize PROPERTIES CXX_STANDARD 20) +add_test(NAME serialize COMMAND serialize) add_executable(benchmark benchmark.cpp load_obj.cpp) target_link_libraries(benchmark PUBLIC bvh) set_target_properties(benchmark PROPERTIES CXX_STANDARD 20) +add_test( + NAME benchmark + COMMAND + benchmark ${CMAKE_CURRENT_SOURCE_DIR}/scenes/cornell_box.obj + --eye 0 1 2 + --dir 0 0 -1 + --up 0 1 0) -add_test(NAME simple_example COMMAND simple_example) -add_test(NAME serialize COMMAND serialize) +if (BVH_BUILD_C_API) + add_executable(c_api_example c_api_example.c load_obj.cpp) + target_link_libraries(c_api_example PUBLIC bvh_c) + + add_test( + NAME c_api_example + COMMAND + c_api_example + ${CMAKE_CURRENT_SOURCE_DIR}/scenes/cornell_box.obj + --eye 0 1 2 + --dir 0 0 -1 + --up 0 1 0) +endif() diff --git a/test/benchmark.cpp b/test/benchmark.cpp index 22b75109..0f46ead5 100644 --- a/test/benchmark.cpp +++ b/test/benchmark.cpp @@ -429,6 +429,6 @@ int main(int argc, char** argv) { } image.save(options.output_image); - std::cout << "Image saved as " << options.output_image << std::endl; + std::cout << "Image saved as '" << options.output_image << "'" << std::endl; return 0; } diff --git a/test/c_api_example.c b/test/c_api_example.c new file mode 100644 index 00000000..ea516db0 --- /dev/null +++ b/test/c_api_example.c @@ -0,0 +1,366 @@ +#include + +#include "load_obj.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define INVALID_TRI_ID UINT32_MAX + +struct scene { + struct bvh3f* bvh; + struct tri* tris; + size_t tri_count; +}; + +struct options { + const char* input_scene; + const char* output_image; + uint32_t width, height; + struct bvh_vec3f eye, dir, up; +}; + +struct image { + uint8_t* pixels; + uint32_t width, height; +}; + +struct hit { + float t, u, v; + uint32_t tri_id; +}; + +struct intersect_user_data { + const struct bvh_ray3f* ray; + const struct scene* scene; + struct hit* hit; +}; + +static inline float dot(struct bvh_vec3f v, struct bvh_vec3f w) { + return v.x * w.x + v.y * w.y + v.z * w.z; +} + +static inline float length(struct bvh_vec3f v) { + return dot(v, v); +} + +static inline struct bvh_vec3f sub(struct bvh_vec3f v, struct bvh_vec3f w) { + return (struct bvh_vec3f) { + .x = v.x - w.x, + .y = v.y - w.y, + .z = v.z - w.z, + }; +} + +static inline struct bvh_vec3f cross(struct bvh_vec3f v, struct bvh_vec3f w) { + return (struct bvh_vec3f) { + .x = v.y * w.z - v.z * w.y, + .y = v.z * w.x - v.x * w.z, + .z = v.x * w.y - v.y * w.x, + }; +} + +static inline struct bvh_vec3f normalize(struct bvh_vec3f v) { + const float inv_len = 1.f / length(v); + return (struct bvh_vec3f) { + .x = v.x * inv_len, + .y = v.y * inv_len, + .z = v.z * inv_len, + }; +} + +static inline struct bvh_vec3f tri_center(const struct tri* tri) { + return (struct bvh_vec3f) { + .x = (tri->v[0].x + tri->v[1].x + tri->v[2].x) * (1.f / 3.f), + .y = (tri->v[0].y + tri->v[1].y + tri->v[2].y) * (1.f / 3.f), + .z = (tri->v[0].z + tri->v[1].z + tri->v[2].z) * (1.f / 3.f) + }; +} + +static inline struct bvh_bbox3f tri_bbox(const struct tri* tri) { + struct bvh_vec3f min = tri->v[0], max = min; + for (int i = 1; i < 3; ++i) { + min.x = min.x < tri->v[i].x ? min.x : tri->v[i].x; + min.y = min.y < tri->v[i].y ? min.y : tri->v[i].y; + min.z = min.z < tri->v[i].z ? min.z : tri->v[i].z; + max.x = max.x > tri->v[i].x ? max.x : tri->v[i].x; + max.y = max.y > tri->v[i].y ? max.y : tri->v[i].y; + max.z = max.z > tri->v[i].z ? max.z : tri->v[i].z; + } + return (struct bvh_bbox3f) { .min = min, .max = max }; +} + +static inline void build_bvh(struct scene* scene) { + assert(!scene->bvh); + struct bvh_bbox3f* bboxes = malloc(sizeof(struct bvh_bbox3f) * scene->tri_count); + struct bvh_vec3f* centers = malloc(sizeof(struct bvh_vec3f) * scene->tri_count); + for (size_t i = 0; i < scene->tri_count; ++i) { + bboxes[i] = tri_bbox(&scene->tris[i]); + centers[i] = tri_center(&scene->tris[i]); + } + + struct bvh_thread_pool* thread_pool = bvh_thread_pool_create(0); + struct timespec ts0, ts1; + timespec_get(&ts0, TIME_UTC); + scene->bvh = bvh3f_build(thread_pool, bboxes, centers, scene->tri_count, NULL); + timespec_get(&ts1, TIME_UTC); + bvh_thread_pool_destroy(thread_pool); + free(bboxes); + free(centers); + const uint64_t build_time = (ts1.tv_sec - ts0.tv_sec) * 1000 + (ts1.tv_nsec - ts0.tv_nsec) / 1000000; + printf("Built BVH with %zu node(s) in %lums\n", bvh3f_get_node_count(scene->bvh), build_time); +} + +static inline void destroy_scene(struct scene* scene) { + bvh3f_destroy(scene->bvh); + free(scene->tris); + memset(scene, 0, sizeof(struct scene)); +} + +struct image alloc_image(size_t width, size_t height) { + return (struct image) { + .pixels = malloc(sizeof(uint8_t) * width * height * 3), + .width = width, + .height = height + }; +} + +static void free_image(struct image* image) { + free(image->pixels); + memset(image, 0, sizeof(struct image)); +} + +static bool save_image(const struct image* image, const char* file_name) { + FILE* file = fopen(file_name, "wb"); + if (!file) + return false; + fprintf(file, "P6 %"PRIu32" %"PRIu32" 255\n", image->width, image->height); + for (uint32_t y = image->height; y-- > 0;) + fwrite(image->pixels + y * 3 * image->width, sizeof(uint8_t), 3 * image->width, file); + fclose(file); + return true; +} + +static void set_pixel(struct image* image, uint32_t x, uint32_t y, uint8_t r, uint8_t g, uint8_t b) { + uint8_t* p = &image->pixels[(y * image->width + x) * 3]; + p[0] = r; + p[1] = g; + p[2] = b; +} + +static void usage() { + printf( + "Usage: c_api_example [options] file.obj\n" + "\nOptions:\n" + " -h --help Shows this message.\n" + " -e --eye Sets the position of the camera.\n" + " -d --dir Sets the direction of the camera.\n" + " -u --up Sets the up vector of the camera.\n" + " -w --width Sets the image width.\n" + " -h --height Sets the image height.\n" + " -o Sets the output file name (defaults to 'render.ppm').\n"); +} + +static inline bool check_arg(int i, int n, int argc, char** argv) { + if (i + n > argc) { + fprintf(stderr, "Missing argument(s) for '%s'\n", argv[i]); + return false; + } + return true; +} + +static inline bool parse_options(int argc, char** argv, struct options* options) { + for (int i = 1; i < argc; ++i) { + if (argv[i][0] == '-') { + if (!strcmp(argv[i], "-e") || !strcmp(argv[i], "--eye")) { + if (!check_arg(i, 3, argc, argv)) + return false; + options->eye.x = strtof(argv[++i], NULL); + options->eye.y = strtof(argv[++i], NULL); + options->eye.z = strtof(argv[++i], NULL); + } else if (!strcmp(argv[i], "-d") || !strcmp(argv[i], "--dir")) { + if (!check_arg(i, 3, argc, argv)) + return false; + options->dir.x = strtof(argv[++i], NULL); + options->dir.y = strtof(argv[++i], NULL); + options->dir.z = strtof(argv[++i], NULL); + } else if (!strcmp(argv[i], "-u") || !strcmp(argv[i], "--up")) { + if (!check_arg(i, 3, argc, argv)) + return false; + options->up.x = strtof(argv[++i], NULL); + options->up.y = strtof(argv[++i], NULL); + options->up.z = strtof(argv[++i], NULL); + } else if (!strcmp(argv[i], "-w") || !strcmp(argv[i], "--width")) { + if (!check_arg(i, 1, argc, argv)) + return false; + options->width = strtoul(argv[++i], NULL, 10); + } else if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "--height")) { + if (!check_arg(i, 1, argc, argv)) + return false; + options->height = strtoul(argv[++i], NULL, 10); + } else if (!strcmp(argv[i], "-o")) { + if (!check_arg(i, 1, argc, argv)) + return false; + options->output_image = argv[++i]; + } else if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "--help")) { + usage(); + return false; + } + } else { + if (options->input_scene) { + fprintf(stderr, "Input scene file specified twice\n"); + return false; + } + options->input_scene = argv[i]; + } + } + if (!options->input_scene) { + fprintf(stderr, "Missing input scene file\n"); + return false; + } + return true; +} + +static inline bool intersect_ray_tri(const struct bvh_ray3f* ray, const struct tri* tri, struct hit* hit) { + const struct bvh_vec3f e1 = sub(tri->v[0], tri->v[1]); + const struct bvh_vec3f e2 = sub(tri->v[2], tri->v[0]); + const struct bvh_vec3f n = cross(e1, e2); + const struct bvh_vec3f c = sub(tri->v[0], ray->org); + const struct bvh_vec3f r = cross(ray->dir, c); + + const float inv_det = 1.f / dot(n, ray->dir); + + const float u = dot(r, e2) * inv_det; + const float v = dot(r, e1) * inv_det; + const float w = 1.f - u - v; + + static const float tolerance = -FLT_EPSILON; + if (u >= tolerance && v >= tolerance && w >= tolerance) { + const float t = dot(n, c) * inv_det; + if (t >= ray->tmin && t <= hit->t) { + hit->t = t; + hit->u = u; + hit->v = v; + return true; + } + } + return false; +} + +static bool intersect_bvh_leaf(void* user_data, float* t, size_t begin, size_t end) { + const struct bvh_ray3f* ray = ((struct intersect_user_data*)user_data)->ray; + const struct scene* scene = ((struct intersect_user_data*)user_data)->scene; + struct hit* hit = ((struct intersect_user_data*)user_data)->hit; + bool was_hit = false; + for (size_t i = begin; i < end; ++i) { + const uint32_t tri_id = bvh3f_get_prim_id(scene->bvh, i); + if (intersect_ray_tri(ray, &scene->tris[tri_id], hit)) { + *t = hit->t; + hit->tri_id = tri_id; + was_hit = true; + } + } + return was_hit; +} + +static inline void render_image( + const struct scene* scene, + const struct options* options, + struct image* image) +{ + const struct bvh_vec3f dir = normalize(options->dir); + const struct bvh_vec3f right = normalize(cross(dir, options->up)); + const struct bvh_vec3f up = cross(right, dir); + + struct intersect_user_data user_data = { .scene = scene }; + const struct bvh_intersect_callbackf callback = { + .user_data = &user_data, + .user_fn = intersect_bvh_leaf + }; + + size_t intr_count = 0; + struct timespec ts0, ts1; + timespec_get(&ts0, TIME_UTC); + for (uint32_t y = 0; y < options->height; ++y) { + for (uint32_t x = 0; x < options->width; ++x) { + const float u = 2.f * ((float)x) / ((float)options->width) - 1.f; + const float v = 2.f * ((float)y) / ((float)options->height) - 1.f; + + const struct bvh_ray3f ray = { + .org = options->eye, + .dir = { + .x = options->dir.x + u * right.x + v * up.x, + .y = options->dir.y + u * right.y + v * up.y, + .z = options->dir.z + u * right.z + v * up.z + }, + .tmin = 0.f, + .tmax = FLT_MAX + }; + + struct hit hit = { .tri_id = INVALID_TRI_ID, .t = FLT_MAX }; + + user_data.ray = &ray; + user_data.hit = &hit; + bvh3f_intersect_ray(scene->bvh, &ray, &callback); + + uint8_t r = 0; + uint8_t g = 0; + uint8_t b = 0; + if (hit.tri_id != INVALID_TRI_ID) { + r = (hit.tri_id * 79) % 255 + 1; + g = (hit.tri_id * 43) % 255 + 1; + b = (hit.tri_id * 57) % 255 + 1; + intr_count++; + } + set_pixel(image, x, y, r, g, b); + } + } + timespec_get(&ts1, TIME_UTC); + const uint64_t render_time = (ts1.tv_sec - ts0.tv_sec) * 1000 + (ts1.tv_nsec - ts0.tv_nsec) / 1000000; + printf("%zu intersection(s) found in %lums\n", intr_count, render_time); +} + +int main(int argc, char** argv) { + struct options options = { + .eye = (struct bvh_vec3f) { 0, 0, 0 }, + .dir = (struct bvh_vec3f) { 0, 0, 1 }, + .up = (struct bvh_vec3f) { 0, 1, 0 }, + .output_image = "render.ppm", + .width = 1024, + .height = 1024 + }; + if (!parse_options(argc, argv, &options)) + return 1; + + struct scene scene = { 0 }; + scene.tris = load_obj(argv[1], &scene.tri_count); + if (!scene.tris) { + fprintf(stderr, "Invalid or empty OBJ file\n"); + return 1; + } + printf("Loaded file with %zu triangle(s)\n", scene.tri_count); + + build_bvh(&scene); + + struct image image = alloc_image(options.width, options.height); + render_image(&scene, &options, &image); + if (!save_image(&image, options.output_image)) { + fprintf(stderr, "Could not save rendered image to '%s'\n", options.output_image); + return 1; + } else { + printf("Image saved as '%s'\n", options.output_image); + } + + free_image(&image); + destroy_scene(&scene); + return 0; +} diff --git a/test/load_obj.cpp b/test/load_obj.cpp index 13018b1d..5f77083f 100644 --- a/test/load_obj.cpp +++ b/test/load_obj.cpp @@ -112,3 +112,27 @@ std::vector> load_obj(const std::string& file) { template std::vector> load_obj(const std::string&); template std::vector> load_obj(const std::string&); + +extern "C" { + +tri* load_obj(const char* file_name, size_t* tri_count) { + auto tri_vec = load_obj(file_name); + *tri_count = tri_vec.size(); + if (tri_vec.size() == 0) + return NULL; + tri* tris = reinterpret_cast(malloc(sizeof(tri) * tri_vec.size())); + for (size_t i = 0; i < tri_vec.size(); ++i) { + tris[i].v[0].x = tri_vec[i].p0[0]; + tris[i].v[0].y = tri_vec[i].p0[1]; + tris[i].v[0].z = tri_vec[i].p0[2]; + tris[i].v[1].x = tri_vec[i].p1[0]; + tris[i].v[1].y = tri_vec[i].p1[1]; + tris[i].v[1].z = tri_vec[i].p1[2]; + tris[i].v[2].x = tri_vec[i].p2[0]; + tris[i].v[2].y = tri_vec[i].p2[1]; + tris[i].v[2].z = tri_vec[i].p2[2]; + } + return tris; +} + +} // extern "C" diff --git a/test/load_obj.h b/test/load_obj.h index 830f78dc..97a24446 100644 --- a/test/load_obj.h +++ b/test/load_obj.h @@ -1,6 +1,22 @@ #ifndef LOAD_OBJ_H #define LOAD_OBJ_H +#include + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +struct tri { struct bvh_vec3f v[3]; }; +struct tri* load_obj(const char*, size_t*); + +#ifdef __cplusplus +} +#endif + +#ifdef __cplusplus #include #include @@ -8,5 +24,6 @@ template std::vector> load_obj(const std::string& file); +#endif #endif diff --git a/test/scenes/cornell_box.obj b/test/scenes/cornell_box.obj new file mode 100644 index 00000000..6309495d --- /dev/null +++ b/test/scenes/cornell_box.obj @@ -0,0 +1,168 @@ +# The original Cornell Box in OBJ format. +# Note that the real box is not a perfect cube, so +# the faces are imperfect in this data set. +# +# Created by Guedis Cardenas and Morgan McGuire at Williams College, 2011 +# Released into the Public Domain. +# +# http://graphics.cs.williams.edu/data +# http://www.graphics.cornell.edu/online/box/data.html +# + +mtllib cornell_box.mtl + +## Object floor +v -1.01 0.00 0.99 +v 1.00 0.00 0.99 +v 1.00 0.00 -1.04 +v -0.99 0.00 -1.04 + +g floor +usemtl floor +f -4 -3 -2 -1 + +## Object ceiling +v -1.02 1.99 0.99 +v -1.02 1.99 -1.04 +v 1.00 1.99 -1.04 +v 1.00 1.99 0.99 + +g ceiling +usemtl ceiling +f -4 -3 -2 -1 + +## Object backwall +v -0.99 0.00 -1.04 +v 1.00 0.00 -1.04 +v 1.00 1.99 -1.04 +v -1.02 1.99 -1.04 + +g backWall +usemtl backWall +f -4 -3 -2 -1 + +## Object rightwall +v 1.00 0.00 -1.04 +v 1.00 0.00 0.99 +v 1.00 1.99 0.99 +v 1.00 1.99 -1.04 + +g rightWall +usemtl rightWall +f -4 -3 -2 -1 + +## Object leftWall +v -1.01 0.00 0.99 +v -0.99 0.00 -1.04 +v -1.02 1.99 -1.04 +v -1.02 1.99 0.99 + +g leftWall +usemtl leftWall +f -4 -3 -2 -1 + +## Object shortBox +usemtl shortBox + +# Top Face +v 0.53 0.60 0.75 +v 0.70 0.60 0.17 +v 0.13 0.60 0.00 +v -0.05 0.60 0.57 +f -4 -3 -2 -1 + +# Left Face +v -0.05 0.00 0.57 +v -0.05 0.60 0.57 +v 0.13 0.60 0.00 +v 0.13 0.00 0.00 +f -4 -3 -2 -1 + +# Front Face +v 0.53 0.00 0.75 +v 0.53 0.60 0.75 +v -0.05 0.60 0.57 +v -0.05 0.00 0.57 +f -4 -3 -2 -1 + +# Right Face +v 0.70 0.00 0.17 +v 0.70 0.60 0.17 +v 0.53 0.60 0.75 +v 0.53 0.00 0.75 +f -4 -3 -2 -1 + +# Back Face +v 0.13 0.00 0.00 +v 0.13 0.60 0.00 +v 0.70 0.60 0.17 +v 0.70 0.00 0.17 +f -4 -3 -2 -1 + +# Bottom Face +v 0.53 0.00 0.75 +v 0.70 0.00 0.17 +v 0.13 0.00 0.00 +v -0.05 0.00 0.57 +f -12 -11 -10 -9 + +g shortBox +usemtl shortBox + +## Object tallBox +usemtl tallBox + +# Top Face +v -0.53 1.20 0.09 +v 0.04 1.20 -0.09 +v -0.14 1.20 -0.67 +v -0.71 1.20 -0.49 +f -4 -3 -2 -1 + +# Left Face +v -0.53 0.00 0.09 +v -0.53 1.20 0.09 +v -0.71 1.20 -0.49 +v -0.71 0.00 -0.49 +f -4 -3 -2 -1 + +# Back Face +v -0.71 0.00 -0.49 +v -0.71 1.20 -0.49 +v -0.14 1.20 -0.67 +v -0.14 0.00 -0.67 +f -4 -3 -2 -1 + +# Right Face +v -0.14 0.00 -0.67 +v -0.14 1.20 -0.67 +v 0.04 1.20 -0.09 +v 0.04 0.00 -0.09 +f -4 -3 -2 -1 + +# Front Face +v 0.04 0.00 -0.09 +v 0.04 1.20 -0.09 +v -0.53 1.20 0.09 +v -0.53 0.00 0.09 +f -4 -3 -2 -1 + +# Bottom Face +v -0.53 0.00 0.09 +v 0.04 0.00 -0.09 +v -0.14 0.00 -0.67 +v -0.71 0.00 -0.49 +f -8 -7 -6 -5 + +g tallBox +usemtl tallBox + +## Object light +v -0.24 1.98 0.16 +v -0.24 1.98 -0.22 +v 0.23 1.98 -0.22 +v 0.23 1.98 0.16 + +g light +usemtl light +f -4 -3 -2 -1