From e7e9a85f77db7971d4634bd771872e68c1422256 Mon Sep 17 00:00:00 2001 From: "Zachary D. Rowitsch" Date: Sun, 4 Oct 2020 20:26:09 -0400 Subject: [PATCH] add Constructive Solid Geometry support --- README.md | 6 + images/csg.png | Bin 0 -> 43572 bytes main/CMakeLists.txt | 8 ++ main/csg.c | 115 ++++++++++++++++++ module_datastructures/arrlist.c | 7 +- module_datastructures/arrlist.h | 4 +- module_raytracer/arrlist.c | 98 --------------- module_raytracer/arrlist.h | 54 -------- module_shapes/CMakeLists.txt | 5 +- module_shapes/cone.c | 3 +- module_shapes/csg.c | 124 +++++++++++++++++++ module_shapes/csg.h | 35 ++++++ module_shapes/cube.c | 3 +- module_shapes/cylinder.c | 3 +- module_shapes/group.c | 16 ++- module_shapes/plane.c | 3 +- module_shapes/shape.c | 6 + module_shapes/shape.h | 3 + module_shapes/sphere.c | 3 +- module_shapes/testshape.c | 3 +- module_shapes/triangle.c | 6 +- test/module_datastructures/test_arrlist.c | 1 + test/module_shapes/CMakeLists.txt | 8 ++ test/module_shapes/test_csg.c | 142 ++++++++++++++++++++++ 24 files changed, 492 insertions(+), 164 deletions(-) create mode 100644 images/csg.png create mode 100644 main/csg.c delete mode 100644 module_raytracer/arrlist.c delete mode 100644 module_raytracer/arrlist.h create mode 100644 module_shapes/csg.c create mode 100644 module_shapes/csg.h create mode 100644 test/module_shapes/test_csg.c 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 0000000000000000000000000000000000000000..a08e8b84da358bd231f48a097831af461a6b1e44 GIT binary patch literal 43572 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 literal 0 HcmV?d00001 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(); +} -- 2.39.5