diff --git a/README.md b/README.md index b58da65..cc95866 100644 --- a/README.md +++ b/README.md @@ -6,3 +6,4 @@ C implementation of the [*Ray tracing in one week-end*](https://raytracing.githu ![Ray tracing in one week-end book cover](examples/book-cover.png) ![Earth](examples/earth.png) +![Cornell box](examples/cornell.png) diff --git a/examples/cornell.png b/examples/cornell.png new file mode 100644 index 0000000..c97a734 Binary files /dev/null and b/examples/cornell.png differ diff --git a/hittable.c b/hittable.c index fc5a1a7..49c0e78 100644 --- a/hittable.c +++ b/hittable.c @@ -8,6 +8,7 @@ #include "vec3.h" #include +#include #include #include #include @@ -194,6 +195,60 @@ Hittable *hittable_create_box(Point3 p0, Point3 p1, const Material *material, return result; } +Hittable *hittable_create_translation(Hittable *ptr, Vec3 offset, + Arena *arena) { + Hittable *result = arena_alloc(arena, sizeof(Hittable)); + result->type = HITTABLE_TRANSLATION; + result->translation.ptr = ptr; + result->translation.offset = offset; + return result; +} + +Hittable *hittable_create_y_rotation(Hittable *ptr, double angle, + Arena *arena) { + Hittable *result = arena_alloc(arena, sizeof(Hittable)); + result->type = HITTABLE_Y_ROTATION; + result->y_rotation.ptr = ptr; + + double radians = degrees_to_radians(angle); + double sin_theta = sin(radians); + double cos_theta = cos(radians); + result->y_rotation.sin_theta = sin_theta; + result->y_rotation.cos_theta = cos_theta; + result->y_rotation.has_box = + hittable_bounding_box(ptr, 0.0, 1.0, &result->y_rotation.bounding_box); + + Point3 min = {DBL_MAX, DBL_MAX, DBL_MAX}; + Point3 max = {-DBL_MAX, -DBL_MAX, -DBL_MAX}; + + for (int i = 0; i < 2; ++i) { + for (int j = 0; j < 2; ++j) { + for (int k = 0; k < 2; ++k) { + const AABB *bbox = &result->y_rotation.bounding_box; + double x = i * bbox->max.x + (1 - i) * bbox->min.x; + double y = j * bbox->max.y + (1 - j) * bbox->min.y; + double z = k * bbox->max.z + (1 - k) * bbox->min.z; + + double new_x = cos_theta * x + sin_theta * z; + double new_z = -sin_theta * x + cos_theta * z; + + min.x = fmin(min.x, new_x); + max.x = fmax(max.x, new_x); + min.y = fmin(min.y, y); + max.y = fmax(max.y, y); + min.z = fmin(min.z, new_z); + max.z = fmax(max.z, new_z); + } + } + } + + result->y_rotation.bounding_box = (AABB){min, max}; + + return result; +} + +/*===----------------------------------------------------------------------===*/ + static bool hittable_list_hit(const HittableList *list, Ray r, double t_min, double t_max, HitRecord *record) { bool hit_anything = false; @@ -367,6 +422,53 @@ static bool box_hit(const Box *box, Ray r, double t_min, double t_max, return hittable_list_hit(&box->sides, r, t_min, t_max, record); } +static bool translation_hit(const Translation *translation, Ray r, double t_min, + double t_max, HitRecord *record) { + Ray moved_r = {point3_add(r.origin, vec3_neg(translation->offset)), + r.direction, r.time}; + if (!hittable_hit(translation->ptr, moved_r, t_min, t_max, record)) + return false; + + record->p = point3_add(record->p, translation->offset); + hit_record_set_face_normal(record, moved_r, record->normal); + return true; +} + +static bool y_rotation_hit(const YRotation *rotation, Ray r, double t_min, + double t_max, HitRecord *record) { + Point3 origin = { + rotation->cos_theta * r.origin.x - rotation->sin_theta * r.origin.z, + r.origin.y, + rotation->sin_theta * r.origin.x + rotation->cos_theta * r.origin.z, + }; + Vec3 direction = { + rotation->cos_theta * r.direction.x - rotation->sin_theta * r.direction.z, + r.direction.y, + rotation->sin_theta * r.direction.x + rotation->cos_theta * r.direction.z, + }; + + Ray rotated_r = {origin, direction}; + if (!hittable_hit(rotation->ptr, rotated_r, t_min, t_max, record)) + return false; + + record->p = (Point3){ + rotation->cos_theta * record->p.x + rotation->sin_theta * record->p.z, + record->p.y, + -rotation->sin_theta * record->p.x + rotation->cos_theta * record->p.z, + }; + + Vec3 normal = { + rotation->cos_theta * record->normal.x + + rotation->sin_theta * record->normal.z, + record->normal.y, + -rotation->sin_theta * record->normal.x + + rotation->cos_theta * record->normal.z, + }; + hit_record_set_face_normal(record, rotated_r, normal); + + return true; +} + bool hittable_hit(const Hittable *hittable, Ray r, double t_min, double t_max, HitRecord *record) { switch (hittable->type) { @@ -386,10 +488,16 @@ bool hittable_hit(const Hittable *hittable, Ray r, double t_min, double t_max, return yz_rectangle_hit(&hittable->yz_rectangle, r, t_min, t_max, record); case HITTABLE_BOX: return box_hit(&hittable->box, r, t_min, t_max, record); + case HITTABLE_TRANSLATION: + return translation_hit(&hittable->translation, r, t_min, t_max, record); + case HITTABLE_Y_ROTATION: + return y_rotation_hit(&hittable->y_rotation, r, t_min, t_max, record); } return false; } +/*===----------------------------------------------------------------------===*/ + static bool hittable_list_bounding_box(const HittableList *list, double time_start, double time_end, AABB *bounding_box) { @@ -482,6 +590,24 @@ static bool box_bounding_box(const Box *box, AABB *bounding_box) { return true; } +static bool translation_bounding_box(const Translation *translation, + double time_start, double time_end, + AABB *bounding_box) { + if (!hittable_bounding_box(translation->ptr, time_start, time_end, + bounding_box)) + return false; + + *bounding_box = (AABB){point3_add(bounding_box->min, translation->offset), + point3_add(bounding_box->max, translation->offset)}; + return true; +} + +static bool y_rotation_bounding_box(const YRotation *rotation, + AABB *bounding_box) { + *bounding_box = rotation->bounding_box; + return true; +} + bool hittable_bounding_box(const Hittable *hittable, double time_start, double time_end, AABB *bounding_box) { switch (hittable->type) { @@ -503,6 +629,11 @@ bool hittable_bounding_box(const Hittable *hittable, double time_start, return yz_rectangle_bounding_box(&hittable->yz_rectangle, bounding_box); case HITTABLE_BOX: return box_bounding_box(&hittable->box, bounding_box); + case HITTABLE_TRANSLATION: + return translation_bounding_box(&hittable->translation, time_start, + time_end, bounding_box); + case HITTABLE_Y_ROTATION: + return y_rotation_bounding_box(&hittable->y_rotation, bounding_box); } return false; } diff --git a/hittable.h b/hittable.h index 84f7d00..796545c 100644 --- a/hittable.h +++ b/hittable.h @@ -31,6 +31,8 @@ typedef enum HittableType { HITTABLE_XZ_RECTANGLE, HITTABLE_YZ_RECTANGLE, HITTABLE_BOX, + HITTABLE_TRANSLATION, + HITTABLE_Y_ROTATION, } HittableType; typedef struct Hittable Hittable; @@ -81,6 +83,19 @@ typedef struct Box { Point3 max; } Box; +typedef struct Translation { + const Hittable *ptr; + Vec3 offset; +} Translation; + +typedef struct YRotation { + const Hittable *ptr; + double sin_theta; + double cos_theta; + bool has_box; + AABB bounding_box; +} YRotation; + struct Hittable { HittableType type; union { @@ -92,6 +107,8 @@ struct Hittable { XZRectangle xz_rectangle; YZRectangle yz_rectangle; Box box; + Translation translation; + YRotation y_rotation; }; }; @@ -115,6 +132,8 @@ Hittable *hittable_create_yz_rectangle(double y0, double y1, double z0, const Material *material, Arena *arena); Hittable *hittable_create_box(Point3 p0, Point3 p1, const Material *material, Arena *arena); +Hittable *hittable_create_translation(Hittable *ptr, Vec3 offset, Arena *arena); +Hittable *hittable_create_y_rotation(Hittable *ptr, double angle, Arena *arena); bool hittable_hit(const Hittable *hittable, Ray r, double t_min, double t_max, HitRecord *record); diff --git a/main.c b/main.c index 2a7f73e..9438e2a 100644 --- a/main.c +++ b/main.c @@ -219,14 +219,17 @@ static Hittable *cornell_box(Arena *arena) { &world->list, hittable_create_xy_rectangle(0, 555, 0, 555, 555, white, arena), arena); - hittable_list_add(&world->list, - hittable_create_box((Point3){130, 0, 65}, - (Point3){295, 165, 230}, white, arena), - arena); - hittable_list_add(&world->list, - hittable_create_box((Point3){265, 0, 295}, - (Point3){430, 330, 460}, white, arena), - arena); + Hittable *box1 = hittable_create_box((Point3){0, 0, 0}, + (Point3){165, 330, 165}, white, arena); + box1 = hittable_create_y_rotation(box1, 15, arena); + box1 = hittable_create_translation(box1, (Vec3){265, 0, 295}, arena); + hittable_list_add(&world->list, box1, arena); + + Hittable *box2 = hittable_create_box((Point3){0, 0, 0}, + (Point3){165, 165, 165}, white, arena); + box2 = hittable_create_y_rotation(box2, -18, arena); + box2 = hittable_create_translation(box2, (Vec3){130, 0, 65}, arena); + hittable_list_add(&world->list, box2, arena); Hittable *bvh_root = hittable_create_bvh_node( world->list.objects, 0, world->list.size, 0.0, 1.0, arena); @@ -310,7 +313,7 @@ int main(int argc, char *argv[]) { world = cornell_box(&arena); aspect_ratio = 1.0; image_width = 600; - samples_per_pixel = 200; + samples_per_pixel = 1000; background_color = (Color){0.0, 0.0, 0.0}; look_from = (Point3){278.0, 278.0, -800.0}; look_at = (Point3){278.0, 278.0, 0.0};