diff --git a/.gitignore b/.gitignore index 8be3252..59cbbf9 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ test/test_rays test/test_lights test/test_materials test/test_spheres +test/test_world raytracer *.ppm *.png \ No newline at end of file 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/test/run.sh b/test/run.sh index 951be59..0851ac9 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; do gcc -g -gdwarf-5 \ -Wall -Werror -Wfatal-errors \ -I. \ 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_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/world.h b/world.h new file mode 100644 index 0000000..07fb490 --- /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); + if (xs.count >= 1) { + struct intersection i = *hit(&xs); + 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); + } +}