From 12cae75ef136bc6c7e26072bdde70bee2b45883a Mon Sep 17 00:00:00 2001 From: Zack Buhman Date: Wed, 24 Jul 2024 00:39:42 -0500 Subject: [PATCH] chapter 5 --- .gitignore | 2 + intersections.h | 76 +++++++++++++++++++ rays.h | 26 +++++++ raytracer | Bin 0 -> 15952 bytes raytracer.c | 41 ++++++++++ spheres.h | 14 ++++ test/run.sh | 2 +- test/runner.h | 14 +++- test/test_intersections.c | 110 +++++++++++++++++++++++++++ test/test_rays.c | 68 +++++++++++++++++ test/test_spheres | Bin 0 -> 35560 bytes test/test_spheres.c | 153 ++++++++++++++++++++++++++++++++++++++ 12 files changed, 502 insertions(+), 4 deletions(-) create mode 100644 intersections.h create mode 100644 rays.h create mode 100755 raytracer create mode 100644 raytracer.c create mode 100644 spheres.h create mode 100644 test/test_intersections.c create mode 100644 test/test_rays.c create mode 100755 test/test_spheres create mode 100644 test/test_spheres.c diff --git a/.gitignore b/.gitignore index 1b57dab..bbe7f47 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,6 @@ test/test_tuples test/test_canvas test/test_matrices test/test_transformations +test/test_intersections +test/test_rays *.ppm \ No newline at end of file diff --git a/intersections.h b/intersections.h new file mode 100644 index 0000000..37df9a1 --- /dev/null +++ b/intersections.h @@ -0,0 +1,76 @@ +#pragma once + +#include "spheres.h" +#include "rays.h" + +struct intersection { + float t; + struct sphere const * const object; +}; + +struct intersection intersection(float t, struct sphere const * const object) +{ + return (struct intersection){ t, object }; +} + +struct intersections { + int count; + struct intersection i[]; +}; + +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) +{ + return (struct intersections2){ 2, {a, b} }; +} + +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) +{ + struct mat4x4 m = mat4x4_inverse(&s->transform); + struct ray r2 = ray_transform(r, &m); + struct tuple sphere_to_ray = tuple_sub(r2.origin, point(0.0f, 0.0f, 0.0f)); + + float a = tuple_dot(r2.direction, r2.direction); + float b = 2 * tuple_dot(r2.direction, sphere_to_ray); + float c = tuple_dot(sphere_to_ray, sphere_to_ray) - 1; + + float discriminant = b * b - 4 * a * c; + + if (discriminant < 0) { + return (struct intersections2) { 0 }; + } 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)); + } +} + +inline static struct intersection * hit(struct intersections * xs) +{ + struct intersection * i = NULL; + for (int n = 0; n < xs->count; n++) { + if (xs->i[n].t >= 0) { + if (i == NULL || i->t > xs->i[n].t) + i = &xs->i[n]; + } + } + return i; +} diff --git a/rays.h b/rays.h new file mode 100644 index 0000000..7d814f2 --- /dev/null +++ b/rays.h @@ -0,0 +1,26 @@ +#pragma once + +#include "spheres.h" +#include "tuples.h" +#include "matrices.h" + +struct ray { + struct tuple origin; + struct tuple direction; +}; + +inline static struct ray ray(struct tuple origin, struct tuple direction) +{ + return (struct ray){origin, direction}; +} + +inline static struct tuple ray_position(struct ray ray, float t) +{ + return tuple_add(ray.origin, tuple_mul(ray.direction, t)); +} + +inline static struct ray ray_transform(struct ray r, struct mat4x4 const * const m) +{ + return ray(mat4x4_mul_t(m, &r.origin), + mat4x4_mul_t(m, &r.direction)); +} diff --git a/raytracer b/raytracer new file mode 100755 index 0000000000000000000000000000000000000000..71aa8590b4d526639f1bf97180a20a898cf9583c GIT binary patch literal 15952 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 literal 0 HcmV?d00001 diff --git a/raytracer.c b/raytracer.c new file mode 100644 index 0000000..2692887 --- /dev/null +++ b/raytracer.c @@ -0,0 +1,41 @@ +#include "tuples.h" +#include "canvas.h" +#include "rays.h" +#include "spheres.h" +#include "intersections.h" +#include "transformations.h" + +int main() +{ + struct tuple ray_origin = point(0.0f, 0.0f, -5.0f); + + float wall_z = 10.0f; + float wall_size = 7.0f; + 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(); + 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; + + 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 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); + if (h != NULL) { + canvas_write_pixel(c, x, y, red); + } + } + } + + canvas_to_ppm(c, "test.ppm"); +} diff --git a/spheres.h b/spheres.h new file mode 100644 index 0000000..6ee9dba --- /dev/null +++ b/spheres.h @@ -0,0 +1,14 @@ +#pragma once + +#include "matrices.h" + +struct sphere { + struct mat4x4 transform; +}; + +inline static struct sphere sphere() +{ + return (struct sphere){ + mat4x4_identity() + }; +} diff --git a/test/run.sh b/test/run.sh index b0c7a9d..8611f48 100644 --- a/test/run.sh +++ b/test/run.sh @@ -2,7 +2,7 @@ set -eux -for name in tuples canvas matrices transformations; do +for name in tuples canvas matrices transformations rays intersections spheres; do gcc -g -gdwarf-5 \ -Wall -Werror -Wfatal-errors \ -I. \ diff --git a/test/runner.h b/test/runner.h index 1986355..88630b1 100644 --- a/test/runner.h +++ b/test/runner.h @@ -2,18 +2,26 @@ typedef bool (*test_t)(const char ** scenario); +#define ANSI_RED "\x1b[31m" +#define ANSI_GREEN "\x1b[32m" +#define ANSI_RESET "\x1b[0m" + #define RUNNER(tests) \ int main() \ { \ int fail_count = 0; \ for (int i = 0; i < (sizeof (tests)) / (sizeof (test_t)); i++) { \ - const char * scenario = NULL; \ + const char * scenario = NULL; \ bool result = tests[i](&scenario); \ - const char * result_s = result ? "ok" : "fail"; \ + const char * result_s = result ? "ok" : ANSI_RED "fail" ANSI_RESET; \ fail_count += !result; \ fprintf(stderr, "%s: %s\n", scenario, result_s); \ } \ - fprintf(stderr, "\nfailed tests: %d\n", fail_count); \ + if (fail_count == 0) { \ + fprintf(stderr, ANSI_GREEN "failed tests: %d\n\n" ANSI_RESET, fail_count); \ + } else { \ + fprintf(stderr, ANSI_RED "failed tests: %d\n\n" ANSI_RESET, fail_count); \ + } \ \ return !(fail_count == 0); \ } diff --git a/test/test_intersections.c b/test/test_intersections.c new file mode 100644 index 0000000..84ed675 --- /dev/null +++ b/test/test_intersections.c @@ -0,0 +1,110 @@ +#include +#include + +#include "intersections.h" +#include "runner.h" + +static bool intersections_test_0(const char ** scenario) +{ + *scenario = "An intersection encapsulates t and object"; + + struct sphere s = sphere(); + struct intersection i = intersection(3.5f, &s); + + return + float_equal(i.t, 3.5f) && + i.object == &s; +} + +static bool intersections_test_1(const char ** scenario) +{ + *scenario = "Aggregating intersections2"; + + struct sphere s = sphere(); + struct intersection i1 = intersection(1.0f, &s); + struct intersection i2 = intersection(2.0f, &s); + struct intersections2 xs = intersections2(i1, i2); + + return + xs.count == 2 && + float_equal(xs.i[0].t, 1.0f) && + float_equal(xs.i[1].t, 2.0f); +} + +static bool intersections_test_2(const char ** scenario) +{ + *scenario = "The hit, when all intersections2 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 intersection * i = hit((struct intersections *)&xs); + + return + i != NULL && + i->t == i1.t && + i->object == i1.object; +} + +static bool intersections_test_3(const char ** scenario) +{ + *scenario = "The hit, when some intersections2 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 intersection * i = hit((struct intersections *)&xs); + + return + i != NULL && + i->t == i2.t && + i->object == i2.object; +} + +static bool intersections_test_4(const char ** scenario) +{ + *scenario = "The hit, when all intersections2 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 intersection * i = hit((struct intersections *)&xs); + + return i == NULL; +} + +static bool intersections_test_5(const char ** scenario) +{ + *scenario = "The hit is always the lowest nonnegative intersection"; + + struct sphere s = sphere(); + struct intersection i1 = intersection( 5.0f, &s); + 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 intersection * i = hit((struct intersections *)&xs); + + return + i != NULL && + i->t == i4.t && + i->object == i4.object; +} + +test_t intersections_tests[] = { + intersections_test_0, + intersections_test_1, + intersections_test_2, + intersections_test_3, + intersections_test_4, + intersections_test_5, +}; + +RUNNER(intersections_tests); diff --git a/test/test_rays.c b/test/test_rays.c new file mode 100644 index 0000000..d861a42 --- /dev/null +++ b/test/test_rays.c @@ -0,0 +1,68 @@ +#include +#include + +#include "rays.h" +#include "runner.h" +#include "transformations.h" + +static bool rays_test_0(const char ** scenario) +{ + *scenario = "Creating and querying a ray"; + + struct tuple origin = point(1.0f, 2.0f, 3.0f); + struct tuple direction = vector(4.0f, 5.0f, 6.0f); + struct ray r = ray(origin, direction); + + return + tuple_equal(r.origin, origin) && + tuple_equal(r.direction, direction); +} + +static bool rays_test_1(const char ** scenario) +{ + *scenario = "Computing a point from a distance"; + + struct ray r = ray(point(2.0f, 3.0f, 4.0f), vector(1.0f, 0.0f, 0.0f)); + + return + tuple_equal(ray_position(r, 0.0f), point(2.0f, 3.0f, 4.0f)) && + tuple_equal(ray_position(r, 1.0f), point(3.0f, 3.0f, 4.0f)) && + tuple_equal(ray_position(r, -1.0f), point(1.0f, 3.0f, 4.0f)) && + tuple_equal(ray_position(r, 2.5f), point(4.5f, 3.0f, 4.0f)); +} + + +static bool rays_test_2(const char ** scenario) +{ + *scenario = "Translating a ray"; + + struct ray r = ray(point(1.0f, 2.0f, 3.0f), vector(0.0f, 1.0f, 0.0f)); + struct mat4x4 m = translation(3.0f, 4.0f, 5.0f); + struct ray r2 = ray_transform(r, &m); + + return + tuple_equal(r2.origin, point(4.0f, 6.0f, 8.0f)) && + tuple_equal(r2.direction, vector(0.0f, 1.0f, 0.0f)); +} + +static bool rays_test_3(const char ** scenario) +{ + *scenario = "Scaling a ray"; + + struct ray r = ray(point(1.0f, 2.0f, 3.0f), vector(0.0f, 1.0f, 0.0f)); + struct mat4x4 m = scaling(2.0f, 3.0f, 4.0f); + struct ray r2 = ray_transform(r, &m); + + return + tuple_equal(r2.origin, point(2.0f, 6.0f, 12.0f)) && + tuple_equal(r2.direction, vector(0.0f, 3.0f, 0.0f)); +} + +test_t rays_tests[] = { + rays_test_0, + rays_test_1, + rays_test_2, + rays_test_3, +}; + +RUNNER(rays_tests); diff --git a/test/test_spheres b/test/test_spheres new file mode 100755 index 0000000000000000000000000000000000000000..645ad53efdd1696e6489335e346ea4a1ecd59050 GIT binary patch literal 35560 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 literal 0 HcmV?d00001 diff --git a/test/test_spheres.c b/test/test_spheres.c new file mode 100644 index 0000000..3d1f954 --- /dev/null +++ b/test/test_spheres.c @@ -0,0 +1,153 @@ +#include +#include + +#include "rays.h" +#include "spheres.h" +#include "intersections.h" +#include "runner.h" +#include "matrices.h" +#include "transformations.h" + +static bool spheres_test_0(const char ** scenario) +{ + *scenario = "A ray intersects a sphere at two points"; + + 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); + + return + xs.count == 2 && + float_equal(xs.i[0].t, 4.0f) && + float_equal(xs.i[1].t, 6.0f); +} + +static bool spheres_test_1(const char ** scenario) +{ + *scenario = "A ray intersects a sphere at a tangent"; + + 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); + + return + xs.count == 2 && + float_equal(xs.i[0].t, 5.0f) && + float_equal(xs.i[1].t, 5.0f); +} + +static bool spheres_test_2(const char ** scenario) +{ + *scenario = "A ray misses a sphere"; + + 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); + + return xs.count == 0; +} + +static bool spheres_test_3(const char ** scenario) +{ + *scenario = "A ray originates in a sphere"; + + 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); + + return + xs.count == 2 && + float_equal(xs.i[0].t, -1.0f) && + float_equal(xs.i[1].t, 1.0f); +} + +static bool spheres_test_4(const char ** scenario) +{ + *scenario = "A sphere is behind a ray"; + + 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); + + return + xs.count == 2 && + float_equal(xs.i[0].t, -6.0f) && + float_equal(xs.i[1].t, -4.0f); +} + +static bool spheres_test_5(const char ** scenario) +{ + *scenario = "Intersect sets the object on the intersection"; + + 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); + + return + xs.count == 2 && + xs.i[0].object == &s && + xs.i[1].object == &s; +} + +static bool spheres_test_6(const char ** scenario) +{ + *scenario = "A sphere's default transformation"; + + struct sphere s = sphere(); + struct mat4x4 identity = mat4x4_identity(); + + return mat4x4_equal(&s.transform, &identity); +} + +static bool spheres_test_7(const char ** scenario) +{ + *scenario = "Changing a sphere's transformation"; + + struct sphere s = sphere(); + struct mat4x4 t = translation(2.0f, 3.0f, 4.0f); + s.transform = t; + + return mat4x4_equal(&s.transform, &t); +} + +static bool spheres_test_8(const char ** scenario) +{ + *scenario = "Intersecting a scaled sphere with a ray"; + + 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); + + return + xs.count == 2 && + float_equal(xs.i[0].t, 3.0f) && + float_equal(xs.i[1].t, 7.0f); +} + +static bool spheres_test_9(const char ** scenario) +{ + *scenario = "Intersecting a translated sphere with a ray"; + + 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); + + return xs.count == 0; +} + +test_t spheres_tests[] = { + spheres_test_0, + spheres_test_1, + spheres_test_2, + spheres_test_3, + spheres_test_4, + spheres_test_5, + spheres_test_6, + spheres_test_7, + spheres_test_8, + spheres_test_9, +}; + +RUNNER(spheres_tests);