add Constructive Solid Geometry support #11
@ -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.
|
In any event, flamegraphs are a fun way to see what is going on.
|
||||||

|

|
||||||
|
|
||||||
|
## main/csg
|
||||||
|
|
||||||
|
Add some constructive solid geometry - aka CSG...this is a sphere with a cubice bite taken out of it.
|
||||||
|
|
||||||
|

|
BIN
images/csg.png
Normal file
BIN
images/csg.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 43 KiB |
@ -87,3 +87,11 @@ target_link_libraries(render_obj PRIVATE
|
|||||||
module_patterns
|
module_patterns
|
||||||
module_raytracer
|
module_raytracer
|
||||||
module_shapes)
|
module_shapes)
|
||||||
|
|
||||||
|
add_executable(csg
|
||||||
|
csg.c)
|
||||||
|
|
||||||
|
target_link_libraries(csg PRIVATE
|
||||||
|
module_patterns
|
||||||
|
module_raytracer
|
||||||
|
module_shapes)
|
||||||
|
115
main/csg.c
Normal file
115
main/csg.c
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
#include "logger.h"
|
||||||
|
|
||||||
|
#include "canvas.h"
|
||||||
|
#include "exceptions.h"
|
||||||
|
#include "ray.h"
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <sphere.h>
|
||||||
|
#include <plane.h>
|
||||||
|
#include <camera.h>
|
||||||
|
#include "pattern.h"
|
||||||
|
#include <math.h>
|
||||||
|
#include <cube.h>
|
||||||
|
#include <csg.h>
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
@ -33,7 +33,12 @@ void ARRLIST_add(ARRLIST_List* list, void* object) {
|
|||||||
list->count++;
|
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);
|
assert(list);
|
||||||
if (item >= list->count) {
|
if (item >= list->count) {
|
||||||
Throw(E_INDEX_OUT_OF_BOUNDS);
|
Throw(E_INDEX_OUT_OF_BOUNDS);
|
||||||
|
@ -15,7 +15,9 @@ void ARRLIST_add(ARRLIST_List* list, void* object);
|
|||||||
* @param item
|
* @param item
|
||||||
* @return
|
* @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.
|
* Removes an item from the list and moves the remaining ptrs up.
|
||||||
|
@ -1,98 +0,0 @@
|
|||||||
#include <stdlib.h>
|
|
||||||
#include <assert.h>
|
|
||||||
|
|
||||||
#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);
|
|
||||||
}
|
|
@ -1,54 +0,0 @@
|
|||||||
#ifndef DATA_STRUCTURES_ARRLIST_H
|
|
||||||
#define DATA_STRUCTURES_ARRLIST_H
|
|
||||||
|
|
||||||
#include <stdbool.h>
|
|
||||||
|
|
||||||
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
|
|
@ -7,18 +7,21 @@ add_library(module_shapes STATIC
|
|||||||
cylinder.c cylinder.h
|
cylinder.c cylinder.h
|
||||||
cone.c cone.h
|
cone.c cone.h
|
||||||
group.c group.h
|
group.c group.h
|
||||||
triangle.c triangle.h)
|
triangle.c triangle.h
|
||||||
|
csg.c csg.h)
|
||||||
|
|
||||||
target_include_directories(module_shapes PUBLIC
|
target_include_directories(module_shapes PUBLIC
|
||||||
${CMAKE_CURRENT_LIST_DIR}
|
${CMAKE_CURRENT_LIST_DIR}
|
||||||
module_raytracer
|
module_raytracer
|
||||||
module_utilities
|
module_utilities
|
||||||
|
module_datastructures
|
||||||
CException
|
CException
|
||||||
)
|
)
|
||||||
|
|
||||||
target_link_libraries(module_shapes
|
target_link_libraries(module_shapes
|
||||||
module_raytracer
|
module_raytracer
|
||||||
module_utilities
|
module_utilities
|
||||||
|
module_datastructures
|
||||||
CException
|
CException
|
||||||
m
|
m
|
||||||
)
|
)
|
||||||
|
@ -7,7 +7,8 @@
|
|||||||
const SHAPE_vtable CONE_vtable = {
|
const SHAPE_vtable CONE_vtable = {
|
||||||
&CONE_local_intersect,
|
&CONE_local_intersect,
|
||||||
&CYLINDER_delete_shape,
|
&CYLINDER_delete_shape,
|
||||||
&CONE_local_normal_at
|
&CONE_local_normal_at,
|
||||||
|
&SHAPE_default_shape_contains
|
||||||
};
|
};
|
||||||
|
|
||||||
CONE_Cone* CONE_new() {
|
CONE_Cone* CONE_new() {
|
||||||
|
124
module_shapes/csg.c
Normal file
124
module_shapes/csg.c
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
#include <assert.h>
|
||||||
|
#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; i<intersections->count; 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);
|
||||||
|
}
|
35
module_shapes/csg.h
Normal file
35
module_shapes/csg.h
Normal file
@ -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
|
@ -6,7 +6,8 @@
|
|||||||
const SHAPE_vtable CUBE_vtable = {
|
const SHAPE_vtable CUBE_vtable = {
|
||||||
&CUBE_local_intersect,
|
&CUBE_local_intersect,
|
||||||
&SHAPE_delete,
|
&SHAPE_delete,
|
||||||
&CUBE_local_normal_at
|
&CUBE_local_normal_at,
|
||||||
|
&SHAPE_default_shape_contains
|
||||||
};
|
};
|
||||||
|
|
||||||
static double max(double x, double y, double z) {
|
static double max(double x, double y, double z) {
|
||||||
|
@ -13,7 +13,8 @@ void CYLINDER_delete_shape(SHAPE_Shape* shape) {
|
|||||||
const SHAPE_vtable CYLINDER_vtable = {
|
const SHAPE_vtable CYLINDER_vtable = {
|
||||||
&CYLINDER_local_intersect,
|
&CYLINDER_local_intersect,
|
||||||
&CYLINDER_delete_shape,
|
&CYLINDER_delete_shape,
|
||||||
&CYLINDER_local_normal_at
|
&CYLINDER_local_normal_at,
|
||||||
|
&SHAPE_default_shape_contains
|
||||||
};
|
};
|
||||||
|
|
||||||
CYLINDER_Cylinder* CYLINDER_new() {
|
CYLINDER_Cylinder* CYLINDER_new() {
|
||||||
|
@ -1,11 +1,25 @@
|
|||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <exceptions.h>
|
#include <exceptions.h>
|
||||||
|
#include <arrlist.h>
|
||||||
#include "group.h"
|
#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 = {
|
const SHAPE_vtable GROUP_vtable = {
|
||||||
&GROUP_local_intersect,
|
&GROUP_local_intersect,
|
||||||
&GROUP_delete_shape,
|
&GROUP_delete_shape,
|
||||||
&GROUP_local_normal_at
|
&GROUP_local_normal_at,
|
||||||
|
&GROUP_shape_contains
|
||||||
};
|
};
|
||||||
|
|
||||||
GROUP_Group* GROUP_new() {
|
GROUP_Group* GROUP_new() {
|
||||||
|
@ -6,7 +6,8 @@
|
|||||||
const SHAPE_vtable PLANE_vtable = {
|
const SHAPE_vtable PLANE_vtable = {
|
||||||
&PLANE_local_intersect,
|
&PLANE_local_intersect,
|
||||||
&SHAPE_delete,
|
&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) {
|
void PLANE_local_normal_at(TUPLES_Vector* local_normal, SHAPE_Shape* shape, const TUPLES_Point* local_point, const RAY_Xs* hit) {
|
||||||
|
@ -147,3 +147,9 @@ void SHAPE_normal_to_world(TUPLES_Vector* result, const SHAPE_Shape* shape, cons
|
|||||||
SHAPE_normal_to_world(result, parent, result);
|
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;
|
||||||
|
}
|
||||||
|
@ -14,6 +14,7 @@ typedef struct SHAPE_vtable {
|
|||||||
void (*local_intersect)(RAY_Intersections* intersections, SHAPE_Shape* shape, const RAY_Ray* ray);
|
void (*local_intersect)(RAY_Intersections* intersections, SHAPE_Shape* shape, const RAY_Ray* ray);
|
||||||
void (*delete)(SHAPE_Shape*);
|
void (*delete)(SHAPE_Shape*);
|
||||||
void (*local_normal_at)(TUPLES_Vector* local_normal, SHAPE_Shape* shape, const TUPLES_Point* local_point, const RAY_Xs* hit);
|
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;
|
} 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_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);
|
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
|
#endif //DATA_STRUCTURES_SHAPE_H
|
||||||
|
@ -5,7 +5,8 @@
|
|||||||
const SHAPE_vtable SPHERE_vtable = {
|
const SHAPE_vtable SPHERE_vtable = {
|
||||||
&SPHERE_local_intersect,
|
&SPHERE_local_intersect,
|
||||||
&SHAPE_delete,
|
&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) {
|
void SPHERE_local_intersect(RAY_Intersections* intersections, SHAPE_Shape* shape, const RAY_Ray* local_ray) {
|
||||||
|
@ -5,7 +5,8 @@
|
|||||||
const SHAPE_vtable TESTSHAPE_vtable = {
|
const SHAPE_vtable TESTSHAPE_vtable = {
|
||||||
&TESTSHAPE_local_intersect,
|
&TESTSHAPE_local_intersect,
|
||||||
&SHAPE_delete,
|
&SHAPE_delete,
|
||||||
&TESTSHAPE_local_normal_at
|
&TESTSHAPE_local_normal_at,
|
||||||
|
&SHAPE_default_shape_contains
|
||||||
};
|
};
|
||||||
|
|
||||||
void TESTSHAPE_init(TESTSHAPE_TestShape* shape) {
|
void TESTSHAPE_init(TESTSHAPE_TestShape* shape) {
|
||||||
|
@ -5,13 +5,15 @@
|
|||||||
const SHAPE_vtable TRIANGLE_vtable = {
|
const SHAPE_vtable TRIANGLE_vtable = {
|
||||||
&TRIANGLE_local_intersect,
|
&TRIANGLE_local_intersect,
|
||||||
&TRIANGLE_delete_shape,
|
&TRIANGLE_delete_shape,
|
||||||
&TRIANGLE_local_normal_at
|
&TRIANGLE_local_normal_at,
|
||||||
|
&SHAPE_default_shape_contains
|
||||||
};
|
};
|
||||||
|
|
||||||
const SHAPE_vtable TRIANGLE_smooth_vtable = {
|
const SHAPE_vtable TRIANGLE_smooth_vtable = {
|
||||||
&TRIANGLE_local_intersect,
|
&TRIANGLE_local_intersect,
|
||||||
&TRIANGLE_delete_shape,
|
&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) {
|
TRIANGLE_Triangle* TRIANGLE_new_from_points(const TUPLES_Point* p1, const TUPLES_Point* p2, const TUPLES_Point* p3) {
|
||||||
|
@ -104,5 +104,6 @@ int main(void) {
|
|||||||
RUN_TEST(test_arrlist_remove);
|
RUN_TEST(test_arrlist_remove);
|
||||||
RUN_TEST(test_arrlist_last);
|
RUN_TEST(test_arrlist_last);
|
||||||
RUN_TEST(test_arrlist_safe_get_should_throw_on_bad_index);
|
RUN_TEST(test_arrlist_safe_get_should_throw_on_bad_index);
|
||||||
|
RUN_TEST(test_arrlist_safe_get);
|
||||||
UNITY_END();
|
UNITY_END();
|
||||||
}
|
}
|
||||||
|
@ -63,3 +63,11 @@ target_link_libraries(module_shapes_triangle
|
|||||||
Unity
|
Unity
|
||||||
m)
|
m)
|
||||||
add_test(shapes_triangle module_shapes_triangle)
|
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)
|
||||||
|
142
test/module_shapes/test_csg.c
Normal file
142
test/module_shapes/test_csg.c
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
#include <unity.h>
|
||||||
|
#include <csg.h>
|
||||||
|
#include <sphere.h>
|
||||||
|
#include <cube.h>
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user