From dfee3f29648725075c72a3402ed978902e159b7a Mon Sep 17 00:00:00 2001 From: Zack Buhman Date: Wed, 7 Aug 2024 23:10:00 -0500 Subject: [PATCH] chapter 7 --- .gitignore | 6 +- camera.h | 75 ++++++++++++++++++++ canvas.h | 41 +++++++---- intersections.h | 114 +++++++++++++++++++++++------- math.h | 4 ++ raytracer.c | 129 ++++++++++++++++++++++++++-------- test/run.sh | 2 +- test/test_camera.c | 112 ++++++++++++++++++++++++++++++ test/test_canvas.c | 14 ++-- test/test_intersections.c | 105 +++++++++++++++++++++++++--- test/test_spheres.c | 32 ++++++--- test/test_transformations.c | 60 ++++++++++++++++ test/test_world.c | 135 ++++++++++++++++++++++++++++++++++++ transformations.h | 20 +++++- world.h | 73 +++++++++++++++++++ 15 files changed, 829 insertions(+), 93 deletions(-) create mode 100644 camera.h create mode 100644 test/test_camera.c create mode 100644 test/test_world.c create mode 100644 world.h diff --git a/.gitignore b/.gitignore index 8be3252..657a0dd 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,10 @@ test/test_rays test/test_lights test/test_materials test/test_spheres +test/test_world +test/test_camera raytracer *.ppm -*.png \ No newline at end of file +*.png +*.mp4 +*.gif \ No newline at end of file diff --git a/camera.h b/camera.h new file mode 100644 index 0000000..7912923 --- /dev/null +++ b/camera.h @@ -0,0 +1,75 @@ +#pragma once + +#include "math.h" +#include "matrices.h" +#include "rays.h" +#include "world.h" +#include "canvas.h" + +struct camera { + float hsize; + float vsize; + float field_of_view; + struct mat4x4 transform; + float half_width; + float half_height; + float pixel_size; +}; + +struct camera camera(float hsize, float vsize, float field_of_view) +{ + float half_view = tanf(field_of_view / 2.0f); + float aspect = hsize / vsize; + float half_width; + float half_height; + if (aspect >= 1.0f) { + half_width = half_view; + half_height = half_view / aspect; + } else { + half_width = half_view * aspect; + half_height = half_view; + } + float pixel_size = (half_width * 2.0f) / hsize; + + return (struct camera){ + hsize, + vsize, + field_of_view, + mat4x4_identity(), + half_width, + half_height, + pixel_size + }; +} + +struct ray camera_ray_for_pixel(struct camera * camera, float px, float py) +{ + float xoffset = (px + 0.5f) * camera->pixel_size; + float yoffset = (py + 0.5f) * camera->pixel_size; + + float world_x = camera->half_width - xoffset; + float world_y = camera->half_height - yoffset; + + struct mat4x4 t_inverse = mat4x4_inverse(&camera->transform); + struct tuple world_point = point(world_x, world_y, -1.0f); + struct tuple pixel = mat4x4_mul_t(&t_inverse, &world_point); + struct tuple world_origin = point(0.0f, 0.0f, 0.0f); + struct tuple origin = mat4x4_mul_t(&t_inverse, &world_origin); + struct tuple direction = tuple_normalize(tuple_sub(pixel, origin)); + + return ray(origin, direction); +} + +void camera_render(struct camera * camera, struct world * world, struct canvas * canvas) +{ + canvas->width = camera->hsize; + canvas->height = camera->vsize; + + for (int y = 0; y < camera->vsize; y++) { + for (int x = 0; x < camera->hsize; x++) { + struct ray ray = camera_ray_for_pixel(camera, x, y); + struct tuple color = color_at(world, ray); + canvas_write_pixel(canvas, x, y, color); + } + } +} diff --git a/canvas.h b/canvas.h index 5810594..74d9b1d 100644 --- a/canvas.h +++ b/canvas.h @@ -1,35 +1,48 @@ +#pragma once + #include #include +#include #include "tuples.h" +#ifndef CANVAS_MAX +#define CANVAS_MAX 32 +#endif + struct canvas { int width; int height; - struct tuple * pixels; + struct tuple pixels[CANVAS_MAX * CANVAS_MAX]; }; inline static struct canvas canvas(int width, int height) { - return (struct canvas){ - width, - height, - calloc(width * height, (sizeof (struct tuple))) - }; + struct canvas c; + assert(width < CANVAS_MAX); + assert(height < CANVAS_MAX); + c.width = width; + c.height = height; + + for (int i = 0; i < c.width * c.height; i++) { + c.pixels[i] = tuple(0.0f, 0.0f, 0.0f, 0.0f); + } + + return c; } -inline static void canvas_write_pixel(struct canvas canvas, int x, int y, struct tuple color) +inline static void canvas_write_pixel(struct canvas * canvas, int x, int y, struct tuple color) { - struct tuple * pixel = &canvas.pixels[y * canvas.width + x]; + struct tuple * pixel = &canvas->pixels[y * canvas->width + x]; pixel->r = color.r; pixel->g = color.g; pixel->b = color.b; pixel->a = color.a; } -inline static struct tuple canvas_pixel_at(struct canvas canvas, int x, int y) +inline static struct tuple canvas_pixel_at(struct canvas * canvas, int x, int y) { - return canvas.pixels[y * canvas.width + x]; + return canvas->pixels[y * canvas->width + x]; } inline static int clamp_0_255(float n) @@ -43,7 +56,7 @@ inline static int clamp_0_255(float n) } } -inline static void canvas_to_ppm(struct canvas canvas, const char * pathname) +inline static void canvas_to_ppm(struct canvas * canvas, const char * pathname) { FILE * f = fopen(pathname, "w"); if (f == NULL) { @@ -51,11 +64,11 @@ inline static void canvas_to_ppm(struct canvas canvas, const char * pathname) } fwrite("P6\n", 3, 1, f); - fprintf(f, "%d %d\n", canvas.width, canvas.height); + fprintf(f, "%d %d\n", canvas->width, canvas->height); fwrite("255\n", 4, 1, f); - for (int i = 0; i < canvas.width * canvas.height; i++) { - struct tuple * pixel = &canvas.pixels[i]; + for (int i = 0; i < canvas->width * canvas->height; i++) { + struct tuple * pixel = &canvas->pixels[i]; char buf[3]; buf[0] = clamp_0_255(pixel->r); buf[1] = clamp_0_255(pixel->g); diff --git a/intersections.h b/intersections.h index 37df9a1..a68d98a 100644 --- a/intersections.h +++ b/intersections.h @@ -1,11 +1,14 @@ #pragma once +#include +#include + #include "spheres.h" #include "rays.h" struct intersection { float t; - struct sphere const * const object; + struct sphere const * object; }; struct intersection intersection(float t, struct sphere const * const object) @@ -13,32 +16,26 @@ struct intersection intersection(float t, struct sphere const * const object) return (struct intersection){ t, object }; } +#define MAX_INTERSECTIONS 1024 + struct intersections { int count; - struct intersection i[]; + struct intersection i[MAX_INTERSECTIONS]; }; -struct intersections2 { - int count; - struct intersection i[2]; -}; - -struct intersections4 { - int count; - struct intersection i[4]; -}; - -struct intersections2 intersections2(struct intersection a, struct intersection b) +inline static void intersections(struct intersections * intersections, int count, ...) { - return (struct intersections2){ 2, {a, b} }; + va_list ap; + va_start(ap, count); + for (int i = 0; i < count; i++) { + intersections->i[i] = va_arg(ap, struct intersection); + } + va_end(ap); + + intersections->count = count; } -struct intersections4 intersections4(struct intersection a, struct intersection b, struct intersection c, struct intersection d) -{ - return (struct intersections4){ 4, {a, b, c, d} }; -} - -inline static struct intersections2 intersect(struct sphere const * const s, struct ray r) +inline static void intersect(struct sphere const * const s, struct ray r, struct intersections * intersections) { struct mat4x4 m = mat4x4_inverse(&s->transform); struct ray r2 = ray_transform(r, &m); @@ -51,15 +48,16 @@ inline static struct intersections2 intersect(struct sphere const * const s, str float discriminant = b * b - 4 * a * c; if (discriminant < 0) { - return (struct intersections2) { 0 }; + return; } else { float root = sqrtf(discriminant); float t1 = (-b - root) / (2 * a); float t2 = (-b + root) / (2 * a); - return - intersections2(intersection(t1, s), - intersection(t2, s)); + intersections->i[intersections->count++] = intersection(t1, s); + intersections->i[intersections->count++] = intersection(t2, s); + assert(intersections->count < MAX_INTERSECTIONS); + return; } } @@ -74,3 +72,71 @@ inline static struct intersection * hit(struct intersections * xs) } return i; } + + +inline static void intersections_sort(struct intersections * xs) +{ +#define left_child(i) (2 * i + 1) + + int start = xs->count / 2; + int end = xs->count; + while (end > 1) { + if (start > 0) { + start = start - 1; + } else { + end = end - 1; + struct intersection tmp = xs->i[0]; + xs->i[0] = xs->i[end]; + xs->i[end] = tmp; + } + + int root = start; + while (left_child(root) < end) { + int child = left_child(root); + if (child + 1 < end && xs->i[child].t < xs->i[child+1].t) { + child = child + 1; + } + if (xs->i[root].t < xs->i[child].t) { + struct intersection tmp = xs->i[root]; + xs->i[root] = xs->i[child]; + xs->i[child] = tmp; + root = child; + } else { + break; + } + } + } + +#undef left_child +} + +struct computations { + float t; + struct sphere const * object; + struct tuple point; + struct tuple eyev; + struct tuple normalv; + bool inside; +}; + +inline static struct computations prepare_computations(struct intersection intersection, struct ray ray) +{ + struct tuple point = ray_position(ray, intersection.t); + struct tuple eyev = tuple_neg(ray.direction); + struct tuple normalv = sphere_normal_at(intersection.object, point); + + bool inside = false; + if (tuple_dot(normalv, eyev) < 0.0f) { + inside = true; + normalv = tuple_neg(normalv); + } + + return (struct computations){ + intersection.t, + intersection.object, + point, + eyev, + normalv, + inside + }; +} diff --git a/math.h b/math.h index 15e497a..166cd90 100644 --- a/math.h +++ b/math.h @@ -5,3 +5,7 @@ #define cosf __builtin_cosf #define sinf __builtin_sinf #define powf __builtin_powf +#define tanf __builtin_tanf + +static const float tau = 6.283185307179586f; +static const float pi = tau / 2.0f; diff --git a/raytracer.c b/raytracer.c index bc66e3e..4d5bcb9 100644 --- a/raytracer.c +++ b/raytracer.c @@ -1,3 +1,5 @@ +#define CANVAS_MAX 2048 + #include "tuples.h" #include "canvas.h" #include "rays.h" @@ -5,6 +7,10 @@ #include "intersections.h" #include "transformations.h" #include "materials.h" +#include "world.h" +#include "camera.h" + +static struct canvas _canvas; int main() { @@ -15,40 +21,109 @@ int main() int canvas_pixels = 100; float pixel_size = wall_size / (float)canvas_pixels; float half = wall_size / 2.0f; - struct canvas c = canvas(canvas_pixels, canvas_pixels); - //struct tuple red = color(1.0f, 0.0f, 0.0f); - struct sphere shape = sphere(); - shape.material = material(); - shape.material.color = color(1.0f, 0.2f, 1.0f); - //struct mat4x4 shear = shearing(1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f); - //struct mat4x4 scale = scaling(0.5f, 1.0f, 1.0f); - //struct mat4x4 m = mat4x4_mul_m(&shear, &scale); - //shape.transform = m; + + struct sphere floor = sphere(); + floor.transform = scaling(10.0f, 0.01f, 10.0f); + floor.material.color = color(1.0f, 0.9f, 0.9f); + floor.material.specular = 0.0f; + + struct sphere left_wall = sphere(); + { + struct mat4x4 _translation = translation(0.0f, 0.0f, 5.0f); + struct mat4x4 _rotation_y = rotation_y(-pi / 4.0f); + struct mat4x4 _rotation_x = rotation_x(pi / 2.0f); + struct mat4x4 _scaling = scaling(10.0f, 0.01f, 10.0f); + + struct mat4x4 t0 = mat4x4_mul_m(&_rotation_x, &_scaling); + struct mat4x4 t1 = mat4x4_mul_m(&_rotation_y, &t0); + struct mat4x4 t2 = mat4x4_mul_m(&_translation, &t1); + + left_wall.transform = t2; + } + left_wall.material = floor.material; + + struct sphere right_wall = sphere(); + { + struct mat4x4 _translation = translation(0.0f, 0.0f, 5.0f); + struct mat4x4 _rotation_y = rotation_y(pi / 4.0f); + struct mat4x4 _rotation_x = rotation_x(pi / 2.0f); + struct mat4x4 _scaling = scaling(10.0f, 0.01f, 10.0f); + + struct mat4x4 t0 = mat4x4_mul_m(&_rotation_x, &_scaling); + struct mat4x4 t1 = mat4x4_mul_m(&_rotation_y, &t0); + struct mat4x4 t2 = mat4x4_mul_m(&_translation, &t1); + + right_wall.transform = t2; + } + right_wall.material = floor.material; + + struct sphere middle = sphere(); + middle.transform = translation(-0.5f, 1.0f, 0.5f); + middle.material.color = color((float)0xfc / 255.f , (float)0x6d / 255.f, (float)0x09 / 255.f); + middle.material.diffuse = 0.7; + middle.material.specular = 0.3; + + struct sphere right = sphere(); + struct mat4x4 right_t = translation(1.5f, 0.5f, -0.5f); + struct mat4x4 right_s = scaling(0.5f, 0.5f, 0.5f); + right.transform = mat4x4_mul_m(&right_t, &right_s); + right.material.color = color(0.5f, 1.0f, 0.1f); + right.material.diffuse = 0.7; + right.material.specular = 0.3; + + struct sphere left = sphere(); + struct mat4x4 left_t = translation(-1.5f, 0.33f, -0.75f); + struct mat4x4 left_s = scaling(0.33f, 0.33f, 0.33f); + left.transform = mat4x4_mul_m(&left_t, &left_s); + left.material.color = color((float)0x12 / 255.f, (float)0xc9 / 255.f, (float)0xcc / 255.f); + left.material.diffuse = 0.7; + left.material.specular = 0.3; struct tuple light_position = point(-10.0f, 10.0f, -10.0f); struct tuple light_color = color(1.0f, 1.0f, 1.0f); struct light light = point_light(light_position, light_color); - for (int y = 0; y < canvas_pixels; y++) { - float world_y = half - pixel_size * y; - for (int x = 0; x < canvas_pixels; x++) { - float world_x = -half + pixel_size * x; + struct world world; + world.light = light; + world.object_count = 6; + world.objects[0] = floor; + world.objects[1] = left_wall; + world.objects[2] = right_wall; + world.objects[3] = middle; + world.objects[4] = right; + world.objects[5] = left; - struct tuple position = point(world_x, world_y, wall_z); - struct ray r = ray(ray_origin, tuple_normalize(tuple_sub(position, ray_origin))); - struct intersections2 xs = intersect(&shape, r); - struct intersection * h = hit((struct intersections *)&xs); + struct camera _camera = camera(1600.0f, 800.0f, pi / 3.0f); + _camera.transform = view_transform(point(0.0f, 1.5f, -5.0f), + point(0.0f, 1.0f, 0.0f), + vector(0.0f, 1.0f, 0.0f)); - if (h != NULL) { - struct tuple point = ray_position(r, h->t); - struct tuple normal = sphere_normal_at(h->object, point); - struct tuple eye = tuple_neg(r.direction); + for (int i = 0; i < 1; i++) { - struct tuple color = lighting(h->object->material, light, point, eye, normal); - canvas_write_pixel(c, x, y, color); - } - } + /* + _camera.transform = view_transform(point(5.0f * cosf(pi / 360 * (float)i), + 1.5f, + -5.0f * sinf(pi / 360 * (float)i)), + point(0.0f, 1.0f, 0.0f), + vector(0.0f, 1.0f, 0.0f)); + + struct tuple light_position = point(-10.0f * cosf(pi / 360 * (float)i), + 10.0f, + -10.0f * sinf(pi / 360 * (float)i)); + world.light.position = light_position; + + struct mat4x4 middle_rz = rotation_z(tau / 360.f * (float)i); + struct mat4x4 middle_ry = rotation_y(tau / 360.f * (float)i); + struct mat4x4 middle_r = mat4x4_mul_m(&middle_rz, &middle_ry); + struct mat4x4 middle_transform = mat4x4_mul_m(&middle_r, &middle_s); + world.objects[3].transform = mat4x4_mul_m(&middle_t, &middle_transform); + */ + + camera_render(&_camera, &world, &_canvas); + + char s[128]; + snprintf(s, 128, "test%03d.ppm", i); + printf("%s\n", s); + canvas_to_ppm(&_canvas, s); } - - canvas_to_ppm(c, "test.ppm"); } diff --git a/test/run.sh b/test/run.sh index 951be59..ce50151 100644 --- a/test/run.sh +++ b/test/run.sh @@ -2,7 +2,7 @@ set -eux -for name in tuples canvas matrices transformations rays intersections spheres lights materials; do +for name in tuples canvas matrices transformations rays intersections spheres lights materials world camera; do gcc -g -gdwarf-5 \ -Wall -Werror -Wfatal-errors \ -I. \ diff --git a/test/test_camera.c b/test/test_camera.c new file mode 100644 index 0000000..20bb923 --- /dev/null +++ b/test/test_camera.c @@ -0,0 +1,112 @@ +#include +#include + +#include "camera.h" +#include "transformations.h" +#include "runner.h" + +static bool camera_test_0(const char ** scenario) +{ + *scenario = "Constructing a camera"; + + float hsize = 160.0f; + float vsize = 120.0f; + float field_of_view = pi / 2.0f; + + struct camera c = camera(hsize, vsize, field_of_view); + + struct mat4x4 identity_matrix = mat4x4_identity(); + + return + float_equal(c.hsize, 160.0f) && + float_equal(c.vsize, 120.0f) && + float_equal(c.field_of_view, pi / 2.0f) && + mat4x4_equal(&c.transform, &identity_matrix); +} + +static bool camera_test_1(const char ** scenario) +{ + *scenario = "The pixel size for a horizontal camera"; + + struct camera c = camera(200.0f, 125.0f, pi / 2.0f); + + return float_equal(c.pixel_size, 0.01f); +} + +static bool camera_test_2(const char ** scenario) +{ + *scenario = "The pixel size for a vertical camera"; + + struct camera c = camera(125.0f, 200.0f, pi / 2.0f); + + return float_equal(c.pixel_size, 0.01f); +} + +static bool camera_test_3(const char ** scenario) +{ + *scenario = "Constructing a ray through the center of the canvas"; + + struct camera c = camera(201.0f, 101.0f, pi / 2.0f); + struct ray r = camera_ray_for_pixel(&c, 100.0f, 50.0f); + + return + tuple_equal(r.origin, point(0.0f, 0.0f, 0.0f)) && + tuple_equal(r.direction, vector(0.0f, 0.0f, -1.0f)); +} + +static bool camera_test_4(const char ** scenario) +{ + *scenario = "Constructing a ray through the corner of the canvas"; + + struct camera c = camera(201.0f, 101.0f, pi / 2.0f); + struct ray r = camera_ray_for_pixel(&c, 0.0f, 0.0f); + + return + tuple_equal(r.origin, point(0.0f, 0.0f, 0.0f)) && + tuple_equal(r.direction, vector(0.66519f, 0.33259f, -0.66851f)); +} + +static bool camera_test_5(const char ** scenario) +{ + *scenario = "Constructing a ray when the camera is transformed"; + + struct camera c = camera(201.0f, 101.0f, pi / 2.0f); + struct mat4x4 rotate = rotation_y(pi / 4.0f); + struct mat4x4 translate = translation(0.0f, -2.0f, 5.0f); + c.transform = mat4x4_mul_m(&rotate, &translate); + struct ray r = camera_ray_for_pixel(&c, 100.0f, 50.0f); + + return + tuple_equal(r.origin, point(0.0f, 2.0f, -5.0f)) && + tuple_equal(r.direction, vector(0.7071067811865476f, 0.0f, -0.7071067811865476f)); +} + +static bool camera_test_6(const char ** scenario) +{ + *scenario = "Rendering a world with a camera"; + + struct world w = default_world(); + struct camera c = camera(11.0f, 11.0f, pi / 2.0f); + struct tuple from = point(0.0f, 0.0f, -5.0f); + struct tuple to = point(0.0f, 0.0f, 0.0f); + struct tuple up = vector(0.0f, 1.0f, 0.0f); + c.transform = view_transform(from, to, up); + struct canvas image; + camera_render(&c, &w, &image); + + struct tuple col = canvas_pixel_at(&image, 5, 5); + return + tuple_equal(col, color(0.38066, 0.47583, 0.2855)); +} + +test_t camera_tests[] = { + camera_test_0, + camera_test_1, + camera_test_2, + camera_test_3, + camera_test_4, + camera_test_5, + camera_test_6, +}; + +RUNNER(camera_tests) diff --git a/test/test_canvas.c b/test/test_canvas.c index e0adfcd..5771897 100644 --- a/test/test_canvas.c +++ b/test/test_canvas.c @@ -7,6 +7,7 @@ static bool canvas_test_0(const char ** scenario) { *scenario = "Creating a canvas"; + struct canvas c = canvas(10, 20); bool zeroized = true; for (int i = 0; i < c.width * c.height; i++) { @@ -21,10 +22,11 @@ static bool canvas_test_0(const char ** scenario) static bool canvas_test_1(const char ** scenario) { *scenario = "Writing pixels to the screen"; + struct canvas c = canvas(10, 20); struct tuple red = color(1.0f, 0.0f, 0.0f); - canvas_write_pixel(c, 2, 3, red); - return tuple_equal(canvas_pixel_at(c, 2, 3), red); + canvas_write_pixel(&c, 2, 3, red); + return tuple_equal(canvas_pixel_at(&c, 2, 3), red); } static bool canvas_test_2(const char ** scenario) @@ -35,10 +37,10 @@ static bool canvas_test_2(const char ** scenario) struct tuple c1 = color(1.5f, 0.0f, 0.0f); struct tuple c2 = color(0.0f, 0.5f, 0.0f); struct tuple c3 = color(-0.5f, 0.0f, 1.0f); - canvas_write_pixel(c, 0, 0, c1); - canvas_write_pixel(c, 2, 1, c2); - canvas_write_pixel(c, 4, 2, c3); - canvas_to_ppm(c, "canvas_test_2.ppm"); + canvas_write_pixel(&c, 0, 0, c1); + canvas_write_pixel(&c, 2, 1, c2); + canvas_write_pixel(&c, 4, 2, c3); + canvas_to_ppm(&c, "canvas_test_2.ppm"); return true; } diff --git a/test/test_intersections.c b/test/test_intersections.c index 84ed675..9ce984a 100644 --- a/test/test_intersections.c +++ b/test/test_intersections.c @@ -18,12 +18,13 @@ static bool intersections_test_0(const char ** scenario) static bool intersections_test_1(const char ** scenario) { - *scenario = "Aggregating intersections2"; + *scenario = "Aggregating intersections"; struct sphere s = sphere(); struct intersection i1 = intersection(1.0f, &s); struct intersection i2 = intersection(2.0f, &s); - struct intersections2 xs = intersections2(i1, i2); + struct intersections xs; + intersections(&xs, 2, i1, i2); return xs.count == 2 && @@ -33,12 +34,13 @@ static bool intersections_test_1(const char ** scenario) static bool intersections_test_2(const char ** scenario) { - *scenario = "The hit, when all intersections2 have positive t"; + *scenario = "The hit, when all intersections have positive t"; struct sphere s = sphere(); struct intersection i1 = intersection(1.0f, &s); struct intersection i2 = intersection(2.0f, &s); - struct intersections2 xs = intersections2(i2, i1); + struct intersections xs; + intersections(&xs, 2, i2, i1); struct intersection * i = hit((struct intersections *)&xs); @@ -50,12 +52,13 @@ static bool intersections_test_2(const char ** scenario) static bool intersections_test_3(const char ** scenario) { - *scenario = "The hit, when some intersections2 have negative t"; + *scenario = "The hit, when some intersections have negative t"; struct sphere s = sphere(); struct intersection i1 = intersection(-1.0f, &s); struct intersection i2 = intersection( 1.0f, &s); - struct intersections2 xs = intersections2(i2, i1); + struct intersections xs; + intersections(&xs, 2, i2, i1); struct intersection * i = hit((struct intersections *)&xs); @@ -67,12 +70,13 @@ static bool intersections_test_3(const char ** scenario) static bool intersections_test_4(const char ** scenario) { - *scenario = "The hit, when all intersections2 have negative t"; + *scenario = "The hit, when all intersections have negative t"; struct sphere s = sphere(); struct intersection i1 = intersection(-2.0f, &s); struct intersection i2 = intersection(-1.0f, &s); - struct intersections2 xs = intersections2(i2, i1); + struct intersections xs; + intersections(&xs, 2, i2, i1); struct intersection * i = hit((struct intersections *)&xs); @@ -88,7 +92,8 @@ static bool intersections_test_5(const char ** scenario) struct intersection i2 = intersection( 7.0f, &s); struct intersection i3 = intersection(-3.0f, &s); struct intersection i4 = intersection( 2.0f, &s); - struct intersections4 xs = intersections4(i1, i2, i3, i4); + struct intersections xs; + intersections(&xs, 4, i1, i2, i3, i4); struct intersection * i = hit((struct intersections *)&xs); @@ -98,6 +103,84 @@ static bool intersections_test_5(const char ** scenario) i->object == i4.object; } +static bool intersections_test_6(const char ** scenario) +{ + *scenario = "In-place ascending sort of intersections"; + + struct sphere s1 = sphere(); + s1.material.ambient = 1.0f; + struct sphere s2 = sphere(); + s2.material.ambient = 2.0f; + struct sphere s3 = sphere(); + s3.material.ambient = 3.0f; + struct sphere s4 = sphere(); + s4.material.ambient = 4.0f; + struct intersection i1 = intersection( 5.0f, &s1); + struct intersection i2 = intersection( 7.0f, &s2); + struct intersection i3 = intersection(-3.0f, &s3); + struct intersection i4 = intersection( 2.0f, &s4); + struct intersections xs; + intersections(&xs, 4, i1, i2, i3, i4); + + intersections_sort(&xs); + + return + float_equal(xs.i[0].t, -3.0f) && + float_equal(xs.i[0].object->material.ambient, 3.0f) && + float_equal(xs.i[1].t, 2.0f) && + float_equal(xs.i[1].object->material.ambient, 4.0f) && + float_equal(xs.i[2].t, 5.0f) && + float_equal(xs.i[2].object->material.ambient, 1.0f) && + float_equal(xs.i[3].t, 7.0f) && + float_equal(xs.i[3].object->material.ambient, 2.0f); +} + +static bool intersections_test_7(const char ** scenario) +{ + *scenario = "Precomputing the state of an intersection"; + + struct ray r = ray(point(0.0f, 0.0f, -5.0f), vector(0.0f, 0.0f, 1.0f)); + struct sphere shape = sphere(); + struct intersection i = intersection(4.0f, &shape); + struct computations comps = prepare_computations(i, r); + + return + float_equal(comps.t, i.t) && + comps.object == &shape && + tuple_equal(comps.point, point(0.0f, 0.0f, -1.0f)) && + tuple_equal(comps.eyev, vector(0.0f, 0.0f, -1.0f)) && + tuple_equal(comps.normalv, vector(0.0f, 0.0f, -1.0f)); +} + +static bool intersections_test_8(const char ** scenario) +{ + *scenario = "The hit, when an intersection occurs on the outside"; + + struct ray r = ray(point(0.0f, 0.0f, -5.0f), vector(0.0f, 0.0f, 1.0f)); + struct sphere shape = sphere(); + struct intersection i = intersection(4.0f, &shape); + struct computations comps = prepare_computations(i, r); + + return + comps.inside == false; +} + +static bool intersections_test_9(const char ** scenario) +{ + *scenario = "The hit, when an intersection occurs on the inside"; + + struct ray r = ray(point(0.0f, 0.0f, 0.0f), vector(0.0f, 0.0f, 1.0f)); + struct sphere shape = sphere(); + struct intersection i = intersection(1.0f, &shape); + struct computations comps = prepare_computations(i, r); + + return + tuple_equal(comps.point, point(0.0f, 0.0f, 1.0f)) && + tuple_equal(comps.eyev, vector(0.0f, 0.0f, -1.0f)) && + comps.inside == true && + tuple_equal(comps.normalv, vector(0.0f, 0.0f, -1.0f)); +} + test_t intersections_tests[] = { intersections_test_0, intersections_test_1, @@ -105,6 +188,10 @@ test_t intersections_tests[] = { intersections_test_3, intersections_test_4, intersections_test_5, + intersections_test_6, + intersections_test_7, + intersections_test_8, + intersections_test_9, }; RUNNER(intersections_tests); diff --git a/test/test_spheres.c b/test/test_spheres.c index 5020da2..0a0afb5 100644 --- a/test/test_spheres.c +++ b/test/test_spheres.c @@ -14,7 +14,9 @@ static bool spheres_test_0(const char ** scenario) struct ray r = ray(point(0.0f, 0.0f, -5.0f), vector(0.0f, 0.0f, 1.0f)); struct sphere s = sphere(); - struct intersections2 xs = intersect(&s, r); + struct intersections xs; + xs.count = 0; + intersect(&s, r, &xs); return xs.count == 2 && @@ -28,7 +30,9 @@ static bool spheres_test_1(const char ** scenario) struct ray r = ray(point(0.0f, 1.0f, -5.0f), vector(0.0f, 0.0f, 1.0f)); struct sphere s = sphere(); - struct intersections2 xs = intersect(&s, r); + struct intersections xs; + xs.count = 0; + intersect(&s, r, &xs); return xs.count == 2 && @@ -42,7 +46,9 @@ static bool spheres_test_2(const char ** scenario) struct ray r = ray(point(0.0f, 2.0f, -5.0f), vector(0.0f, 0.0f, 1.0f)); struct sphere s = sphere(); - struct intersections2 xs = intersect(&s, r); + struct intersections xs; + xs.count = 0; + intersect(&s, r, &xs); return xs.count == 0; } @@ -53,7 +59,9 @@ static bool spheres_test_3(const char ** scenario) struct ray r = ray(point(0.0f, 0.0f, 0.0f), vector(0.0f, 0.0f, 1.0f)); struct sphere s = sphere(); - struct intersections2 xs = intersect(&s, r); + struct intersections xs; + xs.count = 0; + intersect(&s, r, &xs); return xs.count == 2 && @@ -67,7 +75,9 @@ static bool spheres_test_4(const char ** scenario) struct ray r = ray(point(0.0f, 0.0f, 5.0f), vector(0.0f, 0.0f, 1.0f)); struct sphere s = sphere(); - struct intersections2 xs = intersect(&s, r); + struct intersections xs; + xs.count = 0; + intersect(&s, r, &xs); return xs.count == 2 && @@ -81,7 +91,9 @@ static bool spheres_test_5(const char ** scenario) struct ray r = ray(point(0.0f, 0.0f, -5.0f), vector(0.0f, 0.0f, 1.0f)); struct sphere s = sphere(); - struct intersections2 xs = intersect(&s, r); + struct intersections xs; + xs.count = 0; + intersect(&s, r, &xs); return xs.count == 2 && @@ -117,7 +129,9 @@ static bool spheres_test_8(const char ** scenario) struct ray r = ray(point(0.0f, 0.0f, -5.0f), vector(0.0f, 0.0f, 1.0f)); struct sphere s = sphere(); s.transform = scaling(2.0f, 2.0f, 2.0f); - struct intersections2 xs = intersect(&s, r); + struct intersections xs; + xs.count = 0; + intersect(&s, r, &xs); return xs.count == 2 && @@ -132,7 +146,9 @@ static bool spheres_test_9(const char ** scenario) struct ray r = ray(point(0.0f, 0.0f, 5.0f), vector(0.0f, 0.0f, 1.0f)); struct sphere s = sphere(); s.transform = translation(5.0f, 0.0f, 0.0f); - struct intersections2 xs = intersect(&s, r); + struct intersections xs; + xs.count = 0; + intersect(&s, r, &xs); return xs.count == 0; } diff --git a/test/test_transformations.c b/test/test_transformations.c index b8ff5df..e0442e0 100644 --- a/test/test_transformations.c +++ b/test/test_transformations.c @@ -210,6 +210,62 @@ static bool transformations_test_17(const char ** scenario) return tuple_equal(p1, point(15.0f, 0.0f, 7.0f)); } +static bool transformations_test_18(const char ** scenario) +{ + *scenario = "The transformation matrix for the default orientation"; + + struct tuple from = point(0.0f, 0.0f, 0.0f); + struct tuple to = point(0.0f, 0.0f, -1.0f); + struct tuple up = vector(0.0f, 1.0f, 0.0f); + struct mat4x4 t = view_transform(from, to, up); + + struct mat4x4 identity_matrix = mat4x4_identity(); + return mat4x4_equal(&t, &identity_matrix); +} + +static bool transformations_test_19(const char ** scenario) +{ + *scenario = "A view transformation matrix looking in positive Z direction"; + + struct tuple from = point(0.0f, 0.0f, 0.0f); + struct tuple to = point(0.0f, 0.0f, 1.0f); + struct tuple up = vector(0.0f, 1.0f, 0.0f); + struct mat4x4 t = view_transform(from, to, up); + + struct mat4x4 scaled = scaling(-1.0f, 1.0f, -1.0f); + return mat4x4_equal(&t, &scaled); +} + +static bool transformations_test_20(const char ** scenario) +{ + *scenario = "The view transformation moves the world"; + + struct tuple from = point(0.0f, 0.0f, 8.0f); + struct tuple to = point(0.0f, 0.0f, 0.0f); + struct tuple up = vector(0.0f, 1.0f, 0.0f); + struct mat4x4 t = view_transform(from, to, up); + + struct mat4x4 translated = translation(0.0f, 0.0f, -8.0f); + return mat4x4_equal(&t, &translated); +} + +static bool transformations_test_21(const char ** scenario) +{ + *scenario = "An arbitrary view transformation"; + + struct tuple from = point(1.0f, 3.0f, 2.0f); + struct tuple to = point(4.0f, -2.0f, 8.0f); + struct tuple up = vector(1.0f, 1.0f, 0.0f); + struct mat4x4 t = view_transform(from, to, up); + + struct mat4x4 expected = mat4x4(-0.50709f, 0.50709f, 0.67612f, -2.36643f, + 0.76772f, 0.60609f, 0.12122f, -2.82843f, + -0.35857f, 0.59761f, -0.71714f, 0.00000f, + 0.00000f, 0.00000f, 0.00000f, 1.00000f + ); + return mat4x4_equal(&t, &expected); +} + test_t transformations_tests[] = { transformations_test_0, transformations_test_1, @@ -229,6 +285,10 @@ test_t transformations_tests[] = { transformations_test_15, transformations_test_16, transformations_test_17, + transformations_test_18, + transformations_test_19, + transformations_test_20, + transformations_test_21, }; RUNNER(transformations_tests) diff --git a/test/test_world.c b/test/test_world.c new file mode 100644 index 0000000..24bec0d --- /dev/null +++ b/test/test_world.c @@ -0,0 +1,135 @@ +#include +#include + +#include "world.h" +#include "runner.h" + +static bool world_test_0(const char ** scenario) +{ + *scenario = "Creating a world"; + + struct world w = world(); + + return + w.object_count == 0 && + tuple_equal(w.light.intensity, tuple(0.0f, 0.0f, 0.0f, 0.0f)); +} + +static bool world_test_1(const char ** scenario) +{ + *scenario = "The default world"; + + struct light light = point_light(point(-10.0f, 10.0f, -10.0f), color(1.0f, 1.0f, 1.0f)); + struct sphere s1 = sphere(); + s1.material.color = color(0.8f, 1.0f, 0.6f); + s1.material.diffuse = 0.7f; + s1.material.specular = 0.2f; + struct sphere s2 = sphere(); + s2.transform = scaling(0.5f, 0.5f, 0.5f); + struct world w = default_world(); + return + tuple_equal(w.light.position, light.position) && + tuple_equal(w.light.intensity, light.intensity) && + w.object_count == 2 && + tuple_equal(w.objects[0].material.color, s1.material.color) && + float_equal(w.objects[0].material.diffuse, s1.material.diffuse) && + float_equal(w.objects[0].material.specular, s1.material.specular) && + mat4x4_equal(&w.objects[1].transform, &s2.transform); +} + +static bool world_test_2(const char ** scenario) +{ + *scenario = "Intersect a world with a ray"; + + struct world w = default_world(); + + struct ray r = ray(point(0.0f, 0.0f, -5.0f), vector(0.0f, 0.0f, 1.0f)); + struct intersections xs; + intersect_world(&w, r, &xs); + + return + xs.count == 4 && + float_equal(xs.i[0].t, 4.0f) && + float_equal(xs.i[1].t, 4.5f) && + float_equal(xs.i[2].t, 5.5f) && + float_equal(xs.i[3].t, 6.0f); +} + +static bool world_test_3(const char ** scenario) +{ + *scenario = "Shading an intersection"; + + struct world w = default_world(); + struct ray r = ray(point(0.0f, 0.0f, -5.0f), vector(0.0f, 0.0f, 1.0f)); + struct sphere * shape = &w.objects[0]; + struct intersection i = intersection(4.0f, shape); + struct computations comps = prepare_computations(i, r); + struct tuple c = shade_hit(&w, &comps); + + return tuple_equal(c, color(0.38066, 0.47583, 0.2855)); +} + +static bool world_test_4(const char ** scenario) +{ + *scenario = "Shading an intersection from the inside"; + + struct world w = default_world(); + w.light = point_light(point(0.0f, 0.25f, 0.0f), color(1.0f, 1.0f, 1.0f)); + struct ray r = ray(point(0.0f, 0.0f, 0.0f), vector(0.0f, 0.0f, 1.0f)); + struct sphere * shape = &w.objects[1]; + struct intersection i = intersection(0.5f, shape); + struct computations comps = prepare_computations(i, r); + struct tuple c = shade_hit(&w, &comps); + + return tuple_equal(c, color(0.90498, 0.90498, 0.90498)); +} + +static bool world_test_5(const char ** scenario) +{ + *scenario = "The color when a ray misses"; + + struct world w = default_world(); + struct ray r = ray(point(0.0f, 0.0f, -5.0f), vector(0.0f, 1.0f, 0.0f)); + struct tuple c = color_at(&w, r); + + return tuple_equal(c, color(0.0f, 0.0f, 0.0f)); +} + +static bool world_test_6(const char ** scenario) +{ + *scenario = "The color when a ray hits"; + + struct world w = default_world(); + struct ray r = ray(point(0.0f, 0.0f, -5.0f), vector(0.0f, 0.0f, 1.0f)); + struct tuple c = color_at(&w, r); + + return tuple_equal(c, color(0.38066f, 0.47583f, 0.2855f)); +} + +static bool world_test_7(const char ** scenario) +{ + *scenario = "The color with an intersection behind the ray"; + + struct world w = default_world(); + struct sphere * outer = &w.objects[0]; + outer->material.ambient = 1.0f; + struct sphere * inner = &w.objects[1]; + inner->material.ambient = 1.0f; + struct ray r = ray(point(0.0f, 0.0f, 0.75f), vector(0.0f, 0.0f, -1.0f)); + struct tuple c = color_at(&w, r); + + return tuple_equal(c, inner->material.color); +} + +test_t world_tests[] = { + world_test_0, + world_test_1, + world_test_2, + world_test_3, + world_test_4, + world_test_5, + world_test_6, + world_test_7, +}; + +RUNNER(world_tests) diff --git a/transformations.h b/transformations.h index 09a0ab4..c07c9ec 100644 --- a/transformations.h +++ b/transformations.h @@ -19,9 +19,6 @@ inline static struct mat4x4 scaling(float x, float y, float z) 0.0f, 0.0f, 0.0f, 1.0f); } -static const float tau = 6.283185307179586; -static const float pi = tau / 2.0f; - inline static struct mat4x4 rotation_x(float r) { return mat4x4(1.0f, 0.0f, 0.0f, 0.0f, @@ -58,3 +55,20 @@ inline static struct mat4x4 shearing(float xy, zx, zy, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f); } + +inline static struct mat4x4 view_transform(struct tuple from, + struct tuple to, + struct tuple up) +{ + struct tuple forward = tuple_normalize(tuple_sub(to, from)); + struct tuple left = tuple_cross(forward, tuple_normalize(up)); + struct tuple true_up = tuple_cross(left, forward); + + struct mat4x4 orientation = mat4x4(left.x, left.y, left.z, 0.0f, + true_up.x, true_up.y, true_up.z, 0.0f, + -forward.x, -forward.y, -forward.z, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f); + + struct mat4x4 translate = translation(-from.x, -from.y, -from.z); + return mat4x4_mul_m(&orientation, &translate); +} diff --git a/world.h b/world.h new file mode 100644 index 0000000..4f28cf3 --- /dev/null +++ b/world.h @@ -0,0 +1,73 @@ +#pragma once + +#include "lights.h" +#include "spheres.h" +#include "transformations.h" +#include "intersections.h" +#include "rays.h" +#include "materials.h" + +#define WORLD_MAX_OBJECTS 128 + +struct world { + struct light light; + int object_count; + struct sphere objects[WORLD_MAX_OBJECTS]; +}; + +inline static struct world world() +{ + return (struct world){ + .light = (struct light){{{{0}}}}, + .object_count = 0, + }; +} + +inline static struct world default_world() +{ + struct sphere s1 = sphere(); + s1.material.color = color(0.8f, 1.0f, 0.6f); + s1.material.diffuse = 0.7f; + s1.material.specular = 0.2f; + struct sphere s2 = sphere(); + s2.transform = scaling(0.5f, 0.5f, 0.5f); + + return (struct world){ + .light = point_light(point(-10.0f, 10.0f, -10.0f), color(1.0f, 1.0f, 1.0f)), + .object_count = 2, + .objects = { s1, s2 } + }; +} + +inline static void intersect_world(struct world * world, struct ray ray, struct intersections * intersections) +{ + intersections->count = 0; + for (int i = 0; i < world->object_count; i++) { + intersect(&world->objects[i], ray, intersections); + } + intersections_sort(intersections); +} + +inline static struct tuple shade_hit(struct world * world, struct computations * computations) +{ + struct tuple color = lighting(computations->object->material, + world->light, + computations->point, + computations->eyev, + computations->normalv); + return color; +} + +inline static struct tuple color_at(struct world * world, struct ray ray) +{ + struct intersections xs; + intersect_world(world, ray, &xs); + struct intersection * i = hit(&xs); + if (i != NULL) { + struct computations computations = prepare_computations(*i, ray); + struct tuple color = shade_hit(world, &computations); + return color; + } else { + return color(0.0f, 0.0f, 0.0f); + } +}