diff --git a/README.md b/README.md index 8bc5dc7..09401a5 100644 --- a/README.md +++ b/README.md @@ -90,3 +90,9 @@ An obvious performance improvement would be to add some group bounding, so we do In any event, flamegraphs are a fun way to see what is going on. ![](images/glass_teapot_flamegraph.svg) + +## main/csg + +Add some constructive solid geometry - aka CSG...this is a sphere with a cubice bite taken out of it. + +![](images/csg.png) \ No newline at end of file diff --git a/images/csg.png b/images/csg.png new file mode 100644 index 0000000..a08e8b8 Binary files /dev/null and b/images/csg.png differ diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index 334e856..0bd8a68 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -87,3 +87,11 @@ target_link_libraries(render_obj PRIVATE module_patterns module_raytracer module_shapes) + +add_executable(csg + csg.c) + +target_link_libraries(csg PRIVATE + module_patterns + module_raytracer + module_shapes) diff --git a/main/csg.c b/main/csg.c new file mode 100644 index 0000000..6f2f37e --- /dev/null +++ b/main/csg.c @@ -0,0 +1,115 @@ +#include "logger.h" + +#include "canvas.h" +#include "exceptions.h" +#include "ray.h" +#include +#include +#include +#include +#include "pattern.h" +#include +#include +#include + +CEXCEPTION_T e; +void build_world(WORLD_World* world) { + TUPLES_Color red, green, blue; + TUPLES_init_color(&red, 1, 0, 0); + TUPLES_init_color(&green, 0, 1, 0); + TUPLES_init_color(&blue, 0, 0, 1); + PLANE_Plane* floor = PLANE_new(); + WORLD_add_object(world, floor); + MATERIAL_Material* material = MATERIAL_new(); + material->specular = 0; + PATTERN_Pattern* floor_pattern = PATTERN_new_stripe(&red, &green); + MATRIX_Matrix* pattern_transform = MATRIX_new_rotation_y(-M_PI / 5); + PATTERN_set_transform(floor_pattern, pattern_transform); + MATRIX_delete(pattern_transform); + MATERIAL_set_pattern(material, floor_pattern); + PATTERN_delete(floor_pattern); + PLANE_set_material(floor, material); + MATERIAL_delete(material); + + SPHERE_Sphere* middle = SPHERE_new(); + MATRIX_Matrix* middle_transform = MATRIX_new_translation(-0.5, 1, 0.5); + SPHERE_set_transform(middle, middle_transform); + MATRIX_delete(middle_transform); + MATERIAL_Material* middle_material = MATERIAL_new(); + PATTERN_Pattern* middle_pattern = PATTERN_new_gradient(&red, &blue); + MATRIX_Matrix* middle_pattern_translate = MATRIX_new_translation(0.5, 0, 0); + MATRIX_Matrix* middle_pattern_scale = MATRIX_new_scaling(2, 2, 2); + MATRIX_Matrix* middle_pattern_transform = MATRIX_multiply(middle_pattern_scale, middle_pattern_translate); + PATTERN_set_transform(middle_pattern, middle_pattern_transform); + MATRIX_delete_all(middle_pattern_scale, middle_pattern_transform, middle_pattern_translate); + MATERIAL_set_pattern(middle_material, middle_pattern); + PATTERN_delete(middle_pattern); + middle_material->diffuse = 0.7; + middle_material->specular = 0.3; + SPHERE_set_material(middle, middle_material); + MATERIAL_delete(middle_material); + + CUBE_Cube* cube = CUBE_new(); + MATRIX_Matrix* cube_translate = MATRIX_new_translation(0.5, 0.5, 0.35); + MATRIX_Matrix* cube_rot = MATRIX_new_rotation_y(M_PI_4); + MATRIX_Matrix* cube_transform = MATRIX_multiply_many(cube_rot, cube_translate); + CUBE_set_transform(cube, cube_transform); + MATRIX_delete_all(cube_transform, cube_rot, cube_translate); + MATERIAL_Material* cube_mat = MATERIAL_new(); + cube_mat->transparency = 1.0; + cube_mat->ambient = 0.0; + cube_mat->diffuse = 0.0; + cube_mat->specular = 0.0; + cube_mat->shadow_calc = false; + CUBE_set_material(cube, cube_mat); + MATERIAL_delete(cube_mat); + + CSG_Csg* csg = CSG_new(CSG_Difference, middle, cube); + WORLD_add_object(world, csg); +} + +int main(void) { + Try { + LOGGER_log(LOGGER_INFO, "Building world...\n"); + TUPLES_Point* light_position = TUPLES_new_point(10, 10, -10); + TUPLES_Color* light_color = TUPLES_new_color(1, 1, 1); + LIGHTS_PointLight* light = LIGHTS_new_pointlight(light_position, light_color); + TUPLES_delete_all(light_position, light_color); + + CAMERA_Camera* camera = CAMERA_new(1000, 500, M_PI / 3.0); + TUPLES_Point* from = TUPLES_new_point(0, 1.5, -5); + TUPLES_Point* to = TUPLES_new_point(0, 1, 0); + TUPLES_Vector* up = TUPLES_new_vector(0, 1, 0); + MATRIX_Matrix* camera_transform = CAMERA_view_transform(from, to, up); + CAMERA_set_transform(camera, camera_transform); + TUPLES_delete_all(from, to, up); + MATRIX_delete(camera_transform); + + WORLD_World* world = WORLD_new(light); + build_world(world); + LOGGER_log(LOGGER_INFO, "Rendering...\n"); + UTILITIES_Timer* render_timer = UTILITIES_Timer_start(); + CANVAS_Canvas* canvas = CAMERA_render(camera, world); + UTILITIES_Timer_Results render_results = UTILITIES_Timer_stop(render_timer); + + char *filename = "csg.ppm"; + LOGGER_log(LOGGER_INFO, "Writing file %s...\n", filename); + CANVAS_write_to_file(canvas, filename); + LOGGER_log(LOGGER_INFO, "Cleaning up...\n"); + WORLD_delete_all_objects(world); + WORLD_delete(world); + LIGHTS_delete_pointlight(light); + CAMERA_delete(camera); + CANVAS_delete(canvas); + LOGGER_log(LOGGER_INFO, "Wall: %.2f User: %.2f System: %.2f\n", render_results.wall_time_seconds, + render_results.user_time_seconds, render_results.system_time_seconds); + } Catch(e) { + if (e == E_MALLOC_FAILED) + printf("Malloc failed. Exiting\n"); + else if (e == E_FILE_FAILED) + printf("Failed to open test.ppm\n"); + else + printf("Unknown exception %i occurred\n", e); + } + return 0; +} diff --git a/module_datastructures/arrlist.c b/module_datastructures/arrlist.c index 2a79634..7d1e713 100644 --- a/module_datastructures/arrlist.c +++ b/module_datastructures/arrlist.c @@ -33,7 +33,12 @@ void ARRLIST_add(ARRLIST_List* list, void* object) { list->count++; } -void* ARRLIST_safe_get(ARRLIST_List* list, unsigned int item) { +unsigned int ARRLIST_item_count(const ARRLIST_List* list) { + assert(list); + return list->count; +} + +void* ARRLIST_safe_get(const ARRLIST_List* list, unsigned int item) { assert(list); if (item >= list->count) { Throw(E_INDEX_OUT_OF_BOUNDS); diff --git a/module_datastructures/arrlist.h b/module_datastructures/arrlist.h index 8386f12..a3be274 100644 --- a/module_datastructures/arrlist.h +++ b/module_datastructures/arrlist.h @@ -15,7 +15,9 @@ void ARRLIST_add(ARRLIST_List* list, void* object); * @param item * @return */ -void* ARRLIST_safe_get(ARRLIST_List* list, unsigned int item); +void* ARRLIST_safe_get(const ARRLIST_List* list, unsigned int item); + +unsigned int ARRLIST_item_count(const ARRLIST_List* list); /** * Removes an item from the list and moves the remaining ptrs up. diff --git a/module_raytracer/arrlist.c b/module_raytracer/arrlist.c deleted file mode 100644 index 2a79634..0000000 --- a/module_raytracer/arrlist.c +++ /dev/null @@ -1,98 +0,0 @@ -#include -#include - -#include "exceptions.h" - -#include "arrlist.h" - -typedef struct ARRLIST_List { - void** items; - unsigned int count; -} ARRLIST_List; - -ARRLIST_List* ARRLIST_new() { - ARRLIST_List* list = malloc(sizeof(ARRLIST_List)); - if (!list) { - Throw(E_MALLOC_FAILED); - } - list->items = NULL; - list->count = 0; - return list; -} - -void ARRLIST_add(ARRLIST_List* list, void* object) { - assert(list); - assert(object); - void** tmpptr = reallocarray(list->items, sizeof(void*), list->count + 1); - if (!tmpptr) { - Throw(E_MALLOC_FAILED); - } else { - list->items = tmpptr; - } - list->items[list->count] = object; - list->count++; -} - -void* ARRLIST_safe_get(ARRLIST_List* list, unsigned int item) { - assert(list); - if (item >= list->count) { - Throw(E_INDEX_OUT_OF_BOUNDS); - } - return list->items[item]; -} - -void ARRLIST_remove(ARRLIST_List* list, const void* object) { - assert(list); - assert(object); - bool found = false; - for (unsigned int ndx = 0; ndx < list->count; ndx++) { - if (list->items[ndx] == object) { - found = true; - } - if (found) { - if (ndx + 1 != list->count) { - list->items[ndx] = list->items[ndx + 1]; - } else { - list->items[ndx] = NULL; - } - } - } - if (found) { - list->count--; - } -} - -void ARRLIST_iterator(ARRLIST_List* list, void (*apply_each_ptr)(void* ptr, void* context), void* context) { - assert(list); - assert(apply_each_ptr); - for (unsigned int ndx = 0; ndx < list->count; ndx++) { - apply_each_ptr(list->items[ndx], context); - } -} - -bool ARRLIST_is_empty(const ARRLIST_List* list) { - assert(list); - return (list->count == 0); -} - -bool ARRLIST_contains(const ARRLIST_List* list, const void* object) { - assert(list); - assert(object); - for (unsigned int ndx = 0; ndx < list->count; ndx++) { - if (list->items[ndx] == object) { - return true; - } - } - return false; -} - -void* ARRLIST_last(const ARRLIST_List* list) { - assert(list); - return list->items[list->count - 1]; -} - -void ARRLIST_delete(ARRLIST_List* list) { - assert(list); - free(list->items); - free(list); -} diff --git a/module_raytracer/arrlist.h b/module_raytracer/arrlist.h deleted file mode 100644 index 8386f12..0000000 --- a/module_raytracer/arrlist.h +++ /dev/null @@ -1,54 +0,0 @@ -#ifndef DATA_STRUCTURES_ARRLIST_H -#define DATA_STRUCTURES_ARRLIST_H - -#include - -typedef struct ARRLIST_List ARRLIST_List; - -ARRLIST_List* ARRLIST_new(); -void ARRLIST_add(ARRLIST_List* list, void* object); - -/** - * gets a point to an item in the list. throws a E_INDEX_OUT_OF_BOUNDS - * when an item that doesn't exist is accessed. - * @param list - * @param item - * @return - */ -void* ARRLIST_safe_get(ARRLIST_List* list, unsigned int item); - -/** - * Removes an item from the list and moves the remaining ptrs up. - * Doesn't bother to resize the array. - * @param list - * @param object - */ -void ARRLIST_remove(ARRLIST_List* list, const void* object); - -bool ARRLIST_is_empty(const ARRLIST_List* list); - -void ARRLIST_iterator(ARRLIST_List* list, void (*apply_each_ptr)(void* ptr, void* context), void* context); - -/** - * Performs linear search of the list. Comparing only the ptr values! - * @param list - * @param object - * @return - */ -bool ARRLIST_contains(const ARRLIST_List* list, const void* object); - -/** - * Returns the last item in the list - * @param ARRLIST_List - * @return - */ -void* ARRLIST_last(const ARRLIST_List* list); - -/** - * Frees the list and backing array, but doesn't free anything pointed to - * the ptrs that have been added to the list. - * @param list - */ -void ARRLIST_delete(ARRLIST_List* list); - -#endif //DATA_STRUCTURES_ARRLIST_H diff --git a/module_shapes/CMakeLists.txt b/module_shapes/CMakeLists.txt index 5196abd..b1358d2 100644 --- a/module_shapes/CMakeLists.txt +++ b/module_shapes/CMakeLists.txt @@ -7,18 +7,21 @@ add_library(module_shapes STATIC cylinder.c cylinder.h cone.c cone.h group.c group.h - triangle.c triangle.h) + triangle.c triangle.h + csg.c csg.h) target_include_directories(module_shapes PUBLIC ${CMAKE_CURRENT_LIST_DIR} module_raytracer module_utilities + module_datastructures CException ) target_link_libraries(module_shapes module_raytracer module_utilities + module_datastructures CException m ) diff --git a/module_shapes/cone.c b/module_shapes/cone.c index 135697c..1d39897 100644 --- a/module_shapes/cone.c +++ b/module_shapes/cone.c @@ -7,7 +7,8 @@ const SHAPE_vtable CONE_vtable = { &CONE_local_intersect, &CYLINDER_delete_shape, - &CONE_local_normal_at + &CONE_local_normal_at, + &SHAPE_default_shape_contains }; CONE_Cone* CONE_new() { diff --git a/module_shapes/csg.c b/module_shapes/csg.c new file mode 100644 index 0000000..a9a2e89 --- /dev/null +++ b/module_shapes/csg.c @@ -0,0 +1,124 @@ +#include +#include "csg.h" + +bool CSG_shape_contains(const SHAPE_Shape* a, const SHAPE_Shape* b) { + assert(a); + assert(b); + CSG_Csg* acsg = (CSG_Csg*) a; + return acsg->left->vtable->contains(acsg->left, b) || + acsg->right->vtable->contains(acsg->right, b); +} + +const SHAPE_vtable CSG_vtable = { + &CSG_local_intersect, + &CSG_delete_shape, + &CSG_local_normal_at, + &CSG_shape_contains +}; + +bool CSG_intersection_allowed(CSG_Operation op, bool lhit, bool inl, bool inr) { + switch(op) { + case CSG_Union: + return (lhit && !inr) || (!lhit && !inl); + case CSG_Intersection: + return (lhit && inr) || (!lhit && inl); + case CSG_Difference: + return (lhit && !inr) || (!lhit && inl); + } + assert(0); // should never get here + return false; +} + +RAY_Intersections* CSG_filter_intersections(const CSG_Csg* csg, const RAY_Intersections* intersections) { + assert(csg); + assert(intersections); + + bool inl = false; + bool inr = false; + + RAY_Intersections* result = RAY_new_intersections(); + + for (uint i=0; icount; i++) { + const RAY_Xs* xs = &intersections->xs[i]; + bool lhit = csg->left->vtable->contains(csg->left, xs->object); + + if (CSG_intersection_allowed(csg->operation, lhit, inl, inr)) { + RAY_add_intersection_tri(result, xs->t, xs->object, xs->u, xs->v); + } + + if (lhit) { + inl = !inl; + } else { + inr = !inr; + } + } + + return result; +} + +CSG_Csg* CSG_new(CSG_Operation op, SHAPE_Shape* left, SHAPE_Shape* right) { + assert(left); + assert(right); + CSG_Csg* csg = malloc(sizeof(CSG_Csg)); + if (!csg) { + Throw(E_MALLOC_FAILED); + } + CSG_init(csg, op, left, right); + return csg; +} + +void CSG_init(CSG_Csg* csg, CSG_Operation op, SHAPE_Shape* left, SHAPE_Shape* right) { + assert(csg); + assert(left); + assert(right); + SHAPE_init(&csg->shape, &CSG_vtable); + csg->left = left; + SHAPE_set_parent(left, csg); + csg->right = right; + SHAPE_set_parent(right, csg); + csg->operation = op; +} + +void CSG_destroy(CSG_Csg* csg) { + assert(csg); + SHAPE_destroy(&csg->shape); +} + +void CSG_delete(CSG_Csg* csg) { + assert(csg); + CSG_destroy(csg); + free(csg); +} + +void CSG_delete_shape(SHAPE_Shape* shape) { + assert(shape); + CSG_Csg* csg = (CSG_Csg*) shape; + CSG_delete(csg); +} + +void CSG_local_normal_at(TUPLES_Vector* local_normal, SHAPE_Shape* csg, const TUPLES_Point* local_point, const RAY_Xs* hit) { + assert(local_normal); + assert(csg); + assert(local_point); + assert(hit); + UNUSED(local_normal); + UNUSED(csg); + UNUSED(local_point); + UNUSED(hit); + assert(0); +} + +void CSG_local_intersect(RAY_Intersections* intersections, SHAPE_Shape* shape, const RAY_Ray* local_ray) { + assert(intersections); + assert(shape); + assert(local_ray); + CSG_Csg* csg = (CSG_Csg*) shape; + RAY_Intersections* temp_xs = RAY_new_intersections(); + SHAPE_intersect(temp_xs, csg->left, local_ray); + SHAPE_intersect(temp_xs, csg->right, local_ray); + RAY_sort_intersections(temp_xs); + RAY_Intersections* filtered_xs = CSG_filter_intersections(csg, temp_xs); + RAY_add_intersections(intersections, filtered_xs); + RAY_delete_intersections(temp_xs); + RAY_delete_intersections(filtered_xs); +} diff --git a/module_shapes/csg.h b/module_shapes/csg.h new file mode 100644 index 0000000..475586e --- /dev/null +++ b/module_shapes/csg.h @@ -0,0 +1,35 @@ +#ifndef SIMPLE_RAYTRACER_CSG_H +#define SIMPLE_RAYTRACER_CSG_H + +#include "shape.h" + +typedef enum CSG_Operation { + CSG_Union, + CSG_Intersection, + CSG_Difference +} CSG_Operation; + +/** + * \extends SHAPE_Shape + */ +typedef struct CSG_Csg { + SHAPE_Shape shape; + SHAPE_Shape* left; + SHAPE_Shape* right; + CSG_Operation operation; +} CSG_Csg; + +const SHAPE_vtable CSG_vtable; + +CSG_Csg* CSG_new(CSG_Operation op, SHAPE_Shape* left, SHAPE_Shape* right); +void CSG_init(CSG_Csg* csg, CSG_Operation op, SHAPE_Shape* left, SHAPE_Shape* right); +void CSG_destroy(CSG_Csg* csg); +void CSG_delete(CSG_Csg* csg); +void CSG_delete_shape(SHAPE_Shape* shape); + +void CSG_local_normal_at(TUPLES_Vector* local_normal, SHAPE_Shape* csg, const TUPLES_Point* local_point, const RAY_Xs* hit); +void CSG_local_intersect(RAY_Intersections* intersections, SHAPE_Shape* csg, const RAY_Ray* local_ray); +bool CSG_intersection_allowed(CSG_Operation op, bool lhit, bool inl, bool inr); +RAY_Intersections* CSG_filter_intersections(const CSG_Csg* csg, const RAY_Intersections* intersections); + +#endif //SIMPLE_RAYTRACER_CSG_H diff --git a/module_shapes/cube.c b/module_shapes/cube.c index adf72fd..40e4666 100644 --- a/module_shapes/cube.c +++ b/module_shapes/cube.c @@ -6,7 +6,8 @@ const SHAPE_vtable CUBE_vtable = { &CUBE_local_intersect, &SHAPE_delete, - &CUBE_local_normal_at + &CUBE_local_normal_at, + &SHAPE_default_shape_contains }; static double max(double x, double y, double z) { diff --git a/module_shapes/cylinder.c b/module_shapes/cylinder.c index 9af91fd..ecfe134 100644 --- a/module_shapes/cylinder.c +++ b/module_shapes/cylinder.c @@ -13,7 +13,8 @@ void CYLINDER_delete_shape(SHAPE_Shape* shape) { const SHAPE_vtable CYLINDER_vtable = { &CYLINDER_local_intersect, &CYLINDER_delete_shape, - &CYLINDER_local_normal_at + &CYLINDER_local_normal_at, + &SHAPE_default_shape_contains }; CYLINDER_Cylinder* CYLINDER_new() { diff --git a/module_shapes/group.c b/module_shapes/group.c index e886519..c877609 100644 --- a/module_shapes/group.c +++ b/module_shapes/group.c @@ -1,11 +1,25 @@ #include #include +#include #include "group.h" +bool GROUP_shape_contains(const SHAPE_Shape* a, const SHAPE_Shape* b) { + if (a == b) return true; + GROUP_Group* a_group = (GROUP_Group*) a; + for (unsigned int i=0; i < ARRLIST_item_count(a_group->list); i++) { + SHAPE_Shape* shape = (SHAPE_Shape*)ARRLIST_safe_get(a_group->list, i); + if (shape->vtable->contains(shape, b)) { + return true; + } + } + return false; +} + const SHAPE_vtable GROUP_vtable = { &GROUP_local_intersect, &GROUP_delete_shape, - &GROUP_local_normal_at + &GROUP_local_normal_at, + &GROUP_shape_contains }; GROUP_Group* GROUP_new() { diff --git a/module_shapes/plane.c b/module_shapes/plane.c index c2109f9..b72b6d1 100644 --- a/module_shapes/plane.c +++ b/module_shapes/plane.c @@ -6,7 +6,8 @@ const SHAPE_vtable PLANE_vtable = { &PLANE_local_intersect, &SHAPE_delete, - &PLANE_local_normal_at + &PLANE_local_normal_at, + &SHAPE_default_shape_contains }; void PLANE_local_normal_at(TUPLES_Vector* local_normal, SHAPE_Shape* shape, const TUPLES_Point* local_point, const RAY_Xs* hit) { diff --git a/module_shapes/shape.c b/module_shapes/shape.c index 67cf13a..b89a74c 100644 --- a/module_shapes/shape.c +++ b/module_shapes/shape.c @@ -147,3 +147,9 @@ void SHAPE_normal_to_world(TUPLES_Vector* result, const SHAPE_Shape* shape, cons SHAPE_normal_to_world(result, parent, result); } } + +bool SHAPE_default_shape_contains(const SHAPE_Shape* a, const SHAPE_Shape* b) { + assert(a); + assert(b); + return a == b; +} diff --git a/module_shapes/shape.h b/module_shapes/shape.h index 9ac9830..5d287e5 100644 --- a/module_shapes/shape.h +++ b/module_shapes/shape.h @@ -14,6 +14,7 @@ typedef struct SHAPE_vtable { void (*local_intersect)(RAY_Intersections* intersections, SHAPE_Shape* shape, const RAY_Ray* ray); void (*delete)(SHAPE_Shape*); void (*local_normal_at)(TUPLES_Vector* local_normal, SHAPE_Shape* shape, const TUPLES_Point* local_point, const RAY_Xs* hit); + bool (*contains)(const SHAPE_Shape* a, const SHAPE_Shape* b); } SHAPE_vtable; /** @@ -65,4 +66,6 @@ void SHAPE_normal_at(TUPLES_Vector* world_normal, SHAPE_Shape* shape, const TUPL void SHAPE_world_to_object(TUPLES_Point* result, const SHAPE_Shape* shape, const TUPLES_Point* world_point); void SHAPE_normal_to_world(TUPLES_Vector* result, const SHAPE_Shape* shape, const TUPLES_Vector* normal); +bool SHAPE_default_shape_contains(const SHAPE_Shape* a, const SHAPE_Shape* b); + #endif //DATA_STRUCTURES_SHAPE_H diff --git a/module_shapes/sphere.c b/module_shapes/sphere.c index 2b70774..e55e7d8 100644 --- a/module_shapes/sphere.c +++ b/module_shapes/sphere.c @@ -5,7 +5,8 @@ const SHAPE_vtable SPHERE_vtable = { &SPHERE_local_intersect, &SHAPE_delete, - &SPHERE_local_normal_at + &SPHERE_local_normal_at, + &SHAPE_default_shape_contains }; void SPHERE_local_intersect(RAY_Intersections* intersections, SHAPE_Shape* shape, const RAY_Ray* local_ray) { diff --git a/module_shapes/testshape.c b/module_shapes/testshape.c index 1493480..119004b 100644 --- a/module_shapes/testshape.c +++ b/module_shapes/testshape.c @@ -5,7 +5,8 @@ const SHAPE_vtable TESTSHAPE_vtable = { &TESTSHAPE_local_intersect, &SHAPE_delete, - &TESTSHAPE_local_normal_at + &TESTSHAPE_local_normal_at, + &SHAPE_default_shape_contains }; void TESTSHAPE_init(TESTSHAPE_TestShape* shape) { diff --git a/module_shapes/triangle.c b/module_shapes/triangle.c index a095422..905706c 100644 --- a/module_shapes/triangle.c +++ b/module_shapes/triangle.c @@ -5,13 +5,15 @@ const SHAPE_vtable TRIANGLE_vtable = { &TRIANGLE_local_intersect, &TRIANGLE_delete_shape, - &TRIANGLE_local_normal_at + &TRIANGLE_local_normal_at, + &SHAPE_default_shape_contains }; const SHAPE_vtable TRIANGLE_smooth_vtable = { &TRIANGLE_local_intersect, &TRIANGLE_delete_shape, - &TRIANGLE_smooth_local_normal_at + &TRIANGLE_smooth_local_normal_at, + &SHAPE_default_shape_contains }; TRIANGLE_Triangle* TRIANGLE_new_from_points(const TUPLES_Point* p1, const TUPLES_Point* p2, const TUPLES_Point* p3) { diff --git a/test/module_datastructures/test_arrlist.c b/test/module_datastructures/test_arrlist.c index e8e1bbb..6fbb075 100644 --- a/test/module_datastructures/test_arrlist.c +++ b/test/module_datastructures/test_arrlist.c @@ -104,5 +104,6 @@ int main(void) { RUN_TEST(test_arrlist_remove); RUN_TEST(test_arrlist_last); RUN_TEST(test_arrlist_safe_get_should_throw_on_bad_index); + RUN_TEST(test_arrlist_safe_get); UNITY_END(); } diff --git a/test/module_shapes/CMakeLists.txt b/test/module_shapes/CMakeLists.txt index db46d00..bc59c5b 100644 --- a/test/module_shapes/CMakeLists.txt +++ b/test/module_shapes/CMakeLists.txt @@ -63,3 +63,11 @@ target_link_libraries(module_shapes_triangle Unity m) add_test(shapes_triangle module_shapes_triangle) + +add_executable(module_shapes_csg + test_csg.c) +target_link_libraries(module_shapes_csg + module_shapes + Unity + m) +add_test(shapes_csg module_shapes_csg) diff --git a/test/module_shapes/test_csg.c b/test/module_shapes/test_csg.c new file mode 100644 index 0000000..0027769 --- /dev/null +++ b/test/module_shapes/test_csg.c @@ -0,0 +1,142 @@ +#include +#include +#include +#include + +void setUp() {} +void tearDown() {} + +void test_create_csg() { + SPHERE_Sphere* s = SPHERE_new(); + CUBE_Cube* c = CUBE_new(); + CSG_Csg* csg = CSG_new(CSG_Union, s, c); + + TEST_ASSERT_EQUAL(CSG_Union, csg->operation); + TEST_ASSERT_EQUAL_PTR(c, csg->right); + TEST_ASSERT_EQUAL_PTR(s, csg->left); + TEST_ASSERT_EQUAL_PTR(csg, SHAPE_get_parent(s)); + TEST_ASSERT_EQUAL_PTR(csg, SHAPE_get_parent(c)); + + SPHERE_delete(s); + CUBE_delete(c); + CSG_delete(csg); +} + +void test_csg_intersection_allowed(CSG_Operation op, bool lhit, bool inl, bool inr, bool expected) { + bool result = CSG_intersection_allowed(op, lhit, inl, inr); + if (expected != result) { + printf("Testing intersection allowed op(%u) lhit(%d) inl(%d) inr(%d) expected(%d) got(%d)\n", op, lhit, inl, inr, expected, result); + TEST_FAIL(); + } +} + +void test_csg_intersection_allowed_union_op() { + test_csg_intersection_allowed(CSG_Union, true, true, true, false); + test_csg_intersection_allowed(CSG_Union, true, true, false, true); + test_csg_intersection_allowed(CSG_Union, true, false, true, false); + test_csg_intersection_allowed(CSG_Union, true, false, false, true); + test_csg_intersection_allowed(CSG_Union, false, true, true, false); + test_csg_intersection_allowed(CSG_Union, false, true, false, false); + test_csg_intersection_allowed(CSG_Union, false, false, true, true); + test_csg_intersection_allowed(CSG_Union, false, false, false, true); + + test_csg_intersection_allowed(CSG_Intersection, true, true, true, true); + test_csg_intersection_allowed(CSG_Intersection, true, true, false, false); + test_csg_intersection_allowed(CSG_Intersection, true, false, true, true); + test_csg_intersection_allowed(CSG_Intersection, true, false, false, false); + test_csg_intersection_allowed(CSG_Intersection, false, true, true, true); + test_csg_intersection_allowed(CSG_Intersection, false, true, false, true); + test_csg_intersection_allowed(CSG_Intersection, false, false, true, false); + test_csg_intersection_allowed(CSG_Intersection, false, false, false, false); + + test_csg_intersection_allowed(CSG_Difference, true, true, true, false); + test_csg_intersection_allowed(CSG_Difference, true, true, false, true); + test_csg_intersection_allowed(CSG_Difference, true, false, true, false); + test_csg_intersection_allowed(CSG_Difference, true, false, false, true); + test_csg_intersection_allowed(CSG_Difference, false, true, true, true); + test_csg_intersection_allowed(CSG_Difference, false, true, false, true); + test_csg_intersection_allowed(CSG_Difference, false, false, true, false); + test_csg_intersection_allowed(CSG_Difference, false, false, false, false); +} + +void filter_list_of_intersections(CSG_Operation op, uint first, uint second) { + SPHERE_Sphere* s1 = SPHERE_new(); + CUBE_Cube* s2 = CUBE_new(); + CSG_Csg* csg = CSG_new(op, s1, s2); + RAY_Intersections* xs = RAY_new_intersections(); + RAY_add_intersection(xs, 1, s1); + RAY_add_intersection(xs, 2, s2); + RAY_add_intersection(xs, 3, s1); + RAY_add_intersection(xs, 4, s2); + RAY_Intersections* filtered_xs = CSG_filter_intersections(csg, xs); + TEST_ASSERT_EQUAL(2, filtered_xs->count); + TEST_ASSERT_EQUAL_PTR(xs->xs[first].object, filtered_xs->xs[0].object); + TEST_ASSERT_EQUAL_DOUBLE(xs->xs[first].t, filtered_xs->xs[0].t); + TEST_ASSERT_EQUAL_PTR(xs->xs[second].object, filtered_xs->xs[1].object); + TEST_ASSERT_EQUAL_DOUBLE(xs->xs[second].t, filtered_xs->xs[1].t); + RAY_delete_intersections(xs); + RAY_delete_intersections(filtered_xs); + CSG_delete(csg); + SPHERE_delete(s1); + CUBE_delete(s2); +} + +void test_filter_list_of_intersections() { + filter_list_of_intersections(CSG_Union, 0, 3); + filter_list_of_intersections(CSG_Intersection, 1, 2); + filter_list_of_intersections(CSG_Difference, 0, 1); +} + +void test_ray_misses_a_csg_object() { + SPHERE_Sphere* s = SPHERE_new(); + CUBE_Cube* c = CUBE_new(); + CSG_Csg* csg = CSG_new(CSG_Union, s, c); + + RAY_Ray ray; + RAY_init(&ray, 0, 2, -5, 0, 0, 1); + RAY_Intersections *xs = RAY_new_intersections(); + + CSG_local_intersect(xs, (SHAPE_Shape*)csg, &ray); + + TEST_ASSERT_EQUAL(0, xs->count); + RAY_delete_intersections(xs); + CSG_delete(csg); + CUBE_delete(c); + SPHERE_delete(s); +} + +void test_ray_hits_csg_object() { + SPHERE_Sphere* s1 = SPHERE_new(); + SPHERE_Sphere* s2 = SPHERE_new(); + MATRIX_Matrix* s2_transform = MATRIX_new_translation(0, 0, 0.5); + SPHERE_set_transform(s2, s2_transform); + CSG_Csg* csg = CSG_new(CSG_Union, s1, s2); + MATRIX_delete(s2_transform); + + RAY_Ray ray; + RAY_init(&ray, 0, 0, -5, 0, 0, 1); + RAY_Intersections* xs = RAY_new_intersections(); + + CSG_local_intersect(xs, (SHAPE_Shape*)csg, &ray); + + TEST_ASSERT_EQUAL(2, xs->count); + TEST_ASSERT_EQUAL_DOUBLE(4, xs->xs[0].t); + TEST_ASSERT_EQUAL_PTR(s1, xs->xs[0].object); + TEST_ASSERT_EQUAL_DOUBLE(6.5, xs->xs[1].t); + TEST_ASSERT_EQUAL_PTR(s2, xs->xs[1].object); + + RAY_delete_intersections(xs); + CSG_delete(csg); + SPHERE_delete_all(s1, s2); +} + +int main(void) +{ + UNITY_BEGIN(); + RUN_TEST(test_create_csg); + RUN_TEST(test_csg_intersection_allowed_union_op); + RUN_TEST(test_filter_list_of_intersections); + RUN_TEST(test_ray_misses_a_csg_object); + RUN_TEST(test_ray_hits_csg_object); + return UNITY_END(); +}