diff --git a/.gitignore b/.gitignore index 657a0dd..fd8c240 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,8 @@ test/test_materials test/test_spheres test/test_world test/test_camera +test/test_shapes +test/test_planes raytracer *.ppm *.png diff --git a/intersections.h b/intersections.h index d4185ab..b93ab63 100644 --- a/intersections.h +++ b/intersections.h @@ -1,29 +1,26 @@ #pragma once #include -#include -#include - -#include "spheres.h" -#include "rays.h" struct intersection { float t; - struct sphere const * object; + struct shape const * object; }; -struct intersection intersection(float t, struct sphere const * const object) -{ - return (struct intersection){ t, object }; -} - -#define MAX_INTERSECTIONS 1024 +#ifndef INTERSECTIONS_MAX +#define INTERSECTIONS_MAX 1024 +#endif struct intersections { int count; - struct intersection i[MAX_INTERSECTIONS]; + struct intersection i[INTERSECTIONS_MAX]; }; +struct intersection intersection(float t, struct shape const * const object) +{ + return (struct intersection){ t, object }; +} + inline static void intersections(struct intersections * intersections, int count, ...) { va_list ap; @@ -35,113 +32,3 @@ inline static void intersections(struct intersections * intersections, int count intersections->count = count; } - -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); - 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; - } else { - float root = sqrtf(discriminant); - float t1 = (-b - root) / (2 * a); - float t2 = (-b + root) / (2 * a); - - intersections->i[intersections->count++] = intersection(t1, s); - intersections->i[intersections->count++] = intersection(t2, s); - assert(intersections->count < MAX_INTERSECTIONS); - return; - } -} - -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; -} - - -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; - struct tuple over_point; -}; - -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); - } - - struct tuple over_point = tuple_add(point, tuple_mul(normalv, epsilon * 100)); - - return (struct computations){ - intersection.t, - intersection.object, - point, - eyev, - normalv, - inside, - over_point - }; -} diff --git a/intersections_shapes.h b/intersections_shapes.h new file mode 100644 index 0000000..c2c00d5 --- /dev/null +++ b/intersections_shapes.h @@ -0,0 +1,153 @@ +#pragma once + +#include +#include + +#include "rays.h" +#include "shapes.h" +#include "intersections.h" +#include "spheres.h" +#include "planes.h" + +#ifdef TEST +struct ray intersections_saved_ray; +#endif + +inline static void intersect(struct shape const * const s, struct ray r, struct intersections * intersections) +{ + struct mat4x4 m = mat4x4_inverse(&s->transform); + struct ray local_ray = ray_transform(r, &m); + + switch (s->type) { +#ifdef TEST + case SHAPE_TEST: + intersections_saved_ray = local_ray; + break; +#endif + case SHAPE_SPHERE: + sphere_intersect(s, local_ray, intersections); + break; + case SHAPE_PLANE: + plane_intersect(s, local_ray, intersections); + break; + default: + assert(false); + break; + } + assert(intersections->count < INTERSECTIONS_MAX); +} + +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; +} + + +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 shape const * object; + struct tuple point; + struct tuple eyev; + struct tuple normalv; + bool inside; + struct tuple over_point; +}; + +inline static struct tuple normal_at(struct shape const * const s, struct tuple world_point) +{ + struct mat4x4 inv = mat4x4_inverse(&s->transform); + struct mat4x4 inv_t = mat4x4_transpose(&inv); + + struct tuple local_point = mat4x4_mul_t(&inv, &world_point); + struct tuple local_normal; + + switch (s->type) { +#ifdef TEST + case SHAPE_TEST: + local_normal = vector(local_point.x, local_point.y, local_point.z); + break; +#endif + case SHAPE_SPHERE: + local_normal = sphere_normal_at(local_point); + break; + case SHAPE_PLANE: + local_normal = plane_normal_at(local_point); + break; + default: + assert(false); + break; + } + + struct tuple world_normal = mat4x4_mul_t(&inv_t, &local_normal); + world_normal.w = 0.0f; + + return tuple_normalize(world_normal); +} + +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 = normal_at(intersection.object, point); + + bool inside = false; + if (tuple_dot(normalv, eyev) < 0.0f) { + inside = true; + normalv = tuple_neg(normalv); + } + + struct tuple over_point = tuple_add(point, tuple_mul(normalv, epsilon * 100)); + + return (struct computations){ + intersection.t, + intersection.object, + point, + eyev, + normalv, + inside, + over_point + }; +} diff --git a/math.h b/math.h index 166cd90..698e9d0 100644 --- a/math.h +++ b/math.h @@ -6,6 +6,7 @@ #define sinf __builtin_sinf #define powf __builtin_powf #define tanf __builtin_tanf +#define fabsf __builtin_fabsf static const float tau = 6.283185307179586f; static const float pi = tau / 2.0f; diff --git a/planes.h b/planes.h new file mode 100644 index 0000000..cfefecc --- /dev/null +++ b/planes.h @@ -0,0 +1,35 @@ +#pragma once + +#include "math.h" +#include "matrices.h" +#include "materials.h" +#include "shapes.h" +#include "rays.h" +#include "intersections.h" + +inline static struct shape plane() +{ + return (struct shape){ + mat4x4_identity(), + material(), + SHAPE_PLANE, + }; +} + +inline static struct tuple plane_normal_at(struct tuple local_point) +{ + return vector(0.0f, 1.0f, 0.0f); +} + +inline static void plane_intersect(struct shape const * const s, struct ray local_ray, struct intersections * intersections) +{ + if (fabsf(local_ray.direction.y) < epsilon) { + return; + } + + float t = -local_ray.origin.y / local_ray.direction.y; + + intersections->i[intersections->count++] = intersection(t, s); + + return; +} diff --git a/rays.h b/rays.h index 7d814f2..b73f539 100644 --- a/rays.h +++ b/rays.h @@ -1,6 +1,5 @@ #pragma once -#include "spheres.h" #include "tuples.h" #include "matrices.h" diff --git a/raytracer.c b/raytracer.c index faaab52..a88bb4e 100644 --- a/raytracer.c +++ b/raytracer.c @@ -22,12 +22,12 @@ int main() float pixel_size = wall_size / (float)canvas_pixels; float half = wall_size / 2.0f; - struct sphere floor = sphere(); - floor.transform = scaling(10.0f, 0.01f, 10.0f); + struct shape floor = plane(); floor.material.color = color(1.0f, 0.9f, 0.9f); floor.material.specular = 0.0f; - struct sphere left_wall = sphere(); + /* + struct shape left_wall = sphere(); { struct mat4x4 _translation = translation(0.0f, 0.0f, 5.0f); struct mat4x4 _rotation_y = rotation_y(-pi / 4.0f); @@ -42,7 +42,7 @@ int main() } left_wall.material = floor.material; - struct sphere right_wall = sphere(); + struct shape right_wall = sphere(); { struct mat4x4 _translation = translation(0.0f, 0.0f, 5.0f); struct mat4x4 _rotation_y = rotation_y(pi / 4.0f); @@ -56,14 +56,15 @@ int main() right_wall.transform = t2; } right_wall.material = floor.material; + */ - struct sphere middle = sphere(); + struct shape 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 shape 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); @@ -71,7 +72,7 @@ int main() right.material.diffuse = 0.7; right.material.specular = 0.3; - struct sphere left = sphere(); + struct shape 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); @@ -85,22 +86,20 @@ int main() struct world world; world.light = light; - world.object_count = 6; + world.object_count = 4; 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; + world.objects[1] = middle; + world.objects[2] = right; + world.objects[3] = left; - struct camera _camera = camera(1600.0f, 800.0f, pi / 3.0f); + struct camera _camera = camera(400.0f, 200.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)); - struct mat4x4 middle_t = world.objects[3].transform; + struct mat4x4 middle_t = world.objects[1].transform; - for (int i = 0; i < 360; i++) { + for (int i = 10; i < 360; i++) { _camera.transform = view_transform(point(5.0f * cosf(pi / 360 * (float)i / 10), 1.5f, @@ -118,7 +117,7 @@ int main() 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); + world.objects[1].transform = mat4x4_mul_m(&middle_t, &middle_transform); camera_render(&_camera, &world, &_canvas); diff --git a/shapes.h b/shapes.h new file mode 100644 index 0000000..61a95d3 --- /dev/null +++ b/shapes.h @@ -0,0 +1,25 @@ +#pragma once + +#include "matrices.h" +#include "materials.h" + +enum shape_type { + SHAPE_TEST, + SHAPE_SPHERE, + SHAPE_PLANE, +}; + +struct shape { + struct mat4x4 transform; + struct material material; + enum shape_type type; +}; + +inline static struct shape shape() +{ + return (struct shape){ + mat4x4_identity(), + material(), + SHAPE_TEST, + }; +} diff --git a/spheres.h b/spheres.h index d912c29..4428c3d 100644 --- a/spheres.h +++ b/spheres.h @@ -2,27 +2,43 @@ #include "matrices.h" #include "materials.h" +#include "shapes.h" +#include "rays.h" +#include "intersections.h" -struct sphere { - struct mat4x4 transform; - struct material material; -}; - -inline static struct sphere sphere() +inline static struct shape sphere() { - return (struct sphere){ + return (struct shape){ mat4x4_identity(), - material() + material(), + SHAPE_SPHERE, }; } -inline static struct tuple sphere_normal_at(struct sphere const * const s, struct tuple world_point) +inline static struct tuple sphere_normal_at(struct tuple local_point) { - struct mat4x4 inv = mat4x4_inverse(&s->transform); - struct tuple object_point = mat4x4_mul_t(&inv, &world_point); - struct tuple object_normal = tuple_sub(object_point, point(0.0f, 0.0f, 0.0f)); - struct mat4x4 inv_t = mat4x4_transpose(&inv); - struct tuple world_normal = mat4x4_mul_t(&inv_t, &object_normal); - world_normal.w = 0.0f; - return tuple_normalize(world_normal); + return tuple_sub(local_point, point(0.0f, 0.0f, 0.0f)); +} + +inline static void sphere_intersect(struct shape const * const s, struct ray local_ray, struct intersections * intersections) +{ + struct tuple sphere_to_ray = tuple_sub(local_ray.origin, point(0.0f, 0.0f, 0.0f)); + + float a = tuple_dot(local_ray.direction, local_ray.direction); + float b = 2 * tuple_dot(local_ray.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; + } else { + float root = sqrtf(discriminant); + float t1 = (-b - root) / (2 * a); + float t2 = (-b + root) / (2 * a); + + intersections->i[intersections->count++] = intersection(t1, s); + intersections->i[intersections->count++] = intersection(t2, s); + return; + } } diff --git a/test/run.sh b/test/run.sh index ce50151..54db03e 100644 --- a/test/run.sh +++ b/test/run.sh @@ -2,10 +2,13 @@ set -eux -for name in tuples canvas matrices transformations rays intersections spheres lights materials world camera; do + +for name in tuples canvas matrices transformations rays intersections shapes spheres planes lights materials world camera; do gcc -g -gdwarf-5 \ -Wall -Werror -Wfatal-errors \ + -Wno-error=unused-variable \ -I. \ + -DTEST \ test/test_${name}.c -o test/test_${name} -O0 -lm ./test/test_${name} done diff --git a/test/test_intersections.c b/test/test_intersections.c index 98025dd..a42ff2a 100644 --- a/test/test_intersections.c +++ b/test/test_intersections.c @@ -1,7 +1,7 @@ #include #include -#include "intersections.h" +#include "intersections_shapes.h" #include "transformations.h" #include "runner.h" @@ -9,7 +9,7 @@ static bool intersections_test_0(const char ** scenario) { *scenario = "An intersection encapsulates t and object"; - struct sphere s = sphere(); + struct shape s = sphere(); struct intersection i = intersection(3.5f, &s); return @@ -21,7 +21,7 @@ static bool intersections_test_1(const char ** scenario) { *scenario = "Aggregating intersections"; - struct sphere s = sphere(); + struct shape s = sphere(); struct intersection i1 = intersection(1.0f, &s); struct intersection i2 = intersection(2.0f, &s); struct intersections xs; @@ -37,7 +37,7 @@ static bool intersections_test_2(const char ** scenario) { *scenario = "The hit, when all intersections have positive t"; - struct sphere s = sphere(); + struct shape s = sphere(); struct intersection i1 = intersection(1.0f, &s); struct intersection i2 = intersection(2.0f, &s); struct intersections xs; @@ -55,7 +55,7 @@ static bool intersections_test_3(const char ** scenario) { *scenario = "The hit, when some intersections have negative t"; - struct sphere s = sphere(); + struct shape s = sphere(); struct intersection i1 = intersection(-1.0f, &s); struct intersection i2 = intersection( 1.0f, &s); struct intersections xs; @@ -73,7 +73,7 @@ static bool intersections_test_4(const char ** scenario) { *scenario = "The hit, when all intersections have negative t"; - struct sphere s = sphere(); + struct shape s = sphere(); struct intersection i1 = intersection(-2.0f, &s); struct intersection i2 = intersection(-1.0f, &s); struct intersections xs; @@ -88,7 +88,7 @@ static bool intersections_test_5(const char ** scenario) { *scenario = "The hit is always the lowest nonnegative intersection"; - struct sphere s = sphere(); + struct shape s = sphere(); struct intersection i1 = intersection( 5.0f, &s); struct intersection i2 = intersection( 7.0f, &s); struct intersection i3 = intersection(-3.0f, &s); @@ -108,13 +108,13 @@ static bool intersections_test_6(const char ** scenario) { *scenario = "In-place ascending sort of intersections"; - struct sphere s1 = sphere(); + struct shape s1 = sphere(); s1.material.ambient = 1.0f; - struct sphere s2 = sphere(); + struct shape s2 = sphere(); s2.material.ambient = 2.0f; - struct sphere s3 = sphere(); + struct shape s3 = sphere(); s3.material.ambient = 3.0f; - struct sphere s4 = sphere(); + struct shape s4 = sphere(); s4.material.ambient = 4.0f; struct intersection i1 = intersection( 5.0f, &s1); struct intersection i2 = intersection( 7.0f, &s2); @@ -141,7 +141,7 @@ 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 shape shape = sphere(); struct intersection i = intersection(4.0f, &shape); struct computations comps = prepare_computations(i, r); @@ -158,7 +158,7 @@ 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 shape shape = sphere(); struct intersection i = intersection(4.0f, &shape); struct computations comps = prepare_computations(i, r); @@ -171,7 +171,7 @@ 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 shape shape = sphere(); struct intersection i = intersection(1.0f, &shape); struct computations comps = prepare_computations(i, r); @@ -187,7 +187,7 @@ static bool intersections_test_10(const char ** scenario) *scenario = "The hit should offset the point"; struct ray r = ray(point(0.0f, 0.0f, -5.0f), vector(0.0f, 0.0f, 1.0f)); - struct sphere shape = sphere(); + struct shape shape = sphere(); shape.transform = translation(0.0f, 0.0f, 1.0f); struct intersection i = intersection(5.0f, &shape); struct computations comps = prepare_computations(i, r); diff --git a/test/test_planes.c b/test/test_planes.c new file mode 100644 index 0000000..79a6b5f --- /dev/null +++ b/test/test_planes.c @@ -0,0 +1,87 @@ +#include +#include + +#include "rays.h" +#include "planes.h" +#include "intersections_shapes.h" +#include "runner.h" +#include "matrices.h" +#include "transformations.h" + +static bool planes_test_0(const char ** scenario) +{ + *scenario = "The normal of a plane is constant everywhere"; + + struct shape p = plane(); + struct tuple n1 = plane_normal_at(point(0.0f, 0.0f, 0.0f)); + struct tuple n2 = plane_normal_at(point(10.0f, 0.0f, -10.0f)); + struct tuple n3 = plane_normal_at(point(-5.0f, 0.0f, 150.0f)); + return + tuple_equal(n1, vector(0.0f, 1.0f, 0.0f)) && + tuple_equal(n2, vector(0.0f, 1.0f, 0.0f)) && + tuple_equal(n3, vector(0.0f, 1.0f, 0.0f)); +} + +static bool planes_test_1(const char ** scenario) +{ + *scenario = "Intersect with a ray parallel to the plane"; + + struct shape p = plane(); + struct ray r = ray(point(0.0f, 10.0f, 0.0f), vector(0.0f, 0.0f, 1.0f)); + struct intersections xs; + xs.count = 0; + plane_intersect(&p, r, &xs); + return xs.count == 0; +} + +static bool planes_test_2(const char ** scenario) +{ + *scenario = "Intersect with a coplanar ray"; + + struct shape p = plane(); + struct ray r = ray(point(0.0f, 0.0f, 0.0f), vector(0.0f, 0.0f, 1.0f)); + struct intersections xs; + xs.count = 0; + plane_intersect(&p, r, &xs); + return xs.count == 0; +} + +static bool planes_test_3(const char ** scenario) +{ + *scenario = "A ray intersecting a plane from above"; + + struct shape p = plane(); + struct ray r = ray(point(0.0f, 1.0f, 0.0f), vector(0.0f, -1.0f, 0.0f)); + struct intersections xs; + xs.count = 0; + plane_intersect(&p, r, &xs); + return + xs.count == 1 && + float_equal(xs.i[0].t, 1.0f) && + xs.i[0].object == &p; +} + +static bool planes_test_4(const char ** scenario) +{ + *scenario = "A ray intersecting a plane from below"; + + struct shape p = plane(); + struct ray r = ray(point(0.0f, -1.0f, 0.0f), vector(0.0f, 1.0f, 1.0f)); + struct intersections xs; + xs.count = 0; + plane_intersect(&p, r, &xs); + return + xs.count == 1 && + float_equal(xs.i[0].t, 1.0f) && + xs.i[0].object == &p; +} + +test_t planes_tests[] = { + planes_test_0, + planes_test_1, + planes_test_2, + planes_test_3, + planes_test_4, +}; + +RUNNER(planes_tests); diff --git a/test/test_shapes.c b/test/test_shapes.c new file mode 100644 index 0000000..63cb6e9 --- /dev/null +++ b/test/test_shapes.c @@ -0,0 +1,121 @@ +#include +#include + +#include "shapes.h" +#include "intersections_shapes.h" +#include "transformations.h" +#include "runner.h" +#include "rays.h" +#include "spheres.h" + +static bool shapes_test_0(const char ** scenario) +{ + *scenario = "A shape's default transformation"; + + struct shape s = shape(); + struct mat4x4 identity = mat4x4_identity(); + + return mat4x4_equal(&s.transform, &identity); +} + +static bool shapes_test_1(const char ** scenario) +{ + *scenario = "Changing a shape's transformation"; + + struct shape s = shape(); + struct mat4x4 t = translation(2.0f, 3.0f, 4.0f); + s.transform = t; + + return mat4x4_equal(&s.transform, &t); +} + +static bool shapes_test_2(const char ** scenario) +{ + *scenario = "A shape has a default material"; + + struct shape s = shape(); + struct material m = s.material; + return material_equal(m, material()); +} + +static bool shapes_test_3(const char ** scenario) +{ + *scenario = "A shape may be assigned a material"; + + struct shape s = shape(); + struct material m = material(); + m.ambient = 1.0f; + s.material = m; + return material_equal(s.material, m); +} + +static bool shapes_test_4(const char ** scenario) +{ + *scenario = "Intersecting a scaled shape with a ray"; + + struct ray r = ray(point(0.0f, 0.0f, -5.0f), vector(0.0f, 0.0f, 1.0f)); + struct shape s = shape(); + s.transform = scaling(2.0f, 2.0f, 2.0f); + struct intersections xs; + xs.count = 0; + intersect(&s, r, &xs); + + return + tuple_equal(intersections_saved_ray.origin, point(0.0f, 0.0f, -2.5f)) && + tuple_equal(intersections_saved_ray.direction, vector(0.0f, 0.0f, 0.5f)); +} + +static bool shapes_test_5(const char ** scenario) +{ + *scenario = "Intersecting a translated shape with a ray"; + + struct ray r = ray(point(0.0f, 0.0f, -5.0f), vector(0.0f, 0.0f, 1.0f)); + struct shape s = shape(); + s.transform = translation(5.0f, 0.0f, 0.0f); + struct intersections xs; + xs.count = 0; + intersect(&s, r, &xs); + + return + tuple_equal(intersections_saved_ray.origin, point(-5.0f, 0.0f, -5.0f)) && + tuple_equal(intersections_saved_ray.direction, vector(0.0f, 0.0f, 1.0f)); +} + +static bool shapes_test_6(const char ** scenario) +{ + *scenario = "Computing the normal on a translated shape"; + + struct shape s = shape(); + s.transform = translation(0.0f, 1.0f, 0.0f); + + struct tuple n = normal_at(&s, point(0.0f, 1.70711f, -0.70711)); + + return tuple_equal(n, vector(0.0f, 0.70711f, -0.70711f)); +} + +static bool shapes_test_7(const char ** scenario) +{ + *scenario = "Computing the normal on a transformed shape"; + struct shape s = shape(); + struct mat4x4 scale = scaling(1.0f, 0.5f, 1.0f); + struct mat4x4 rotate = rotation_z(pi / 5.0f); + struct mat4x4 m = mat4x4_mul_m(&scale, &rotate); + s.transform = m; + + struct tuple n = normal_at(&s, point(0.0f, 0.7071067811865476, -0.7071067811865476)); + + return tuple_equal(n, vector(0.0f, 0.97014f, -0.24254f)); +} + +test_t shape_tests[] = { + shapes_test_0, + shapes_test_1, + shapes_test_2, + shapes_test_3, + shapes_test_4, + shapes_test_5, + shapes_test_6, + shapes_test_7, +}; + +RUNNER(shape_tests) diff --git a/test/test_spheres.c b/test/test_spheres.c index 0a0afb5..c2849a3 100644 --- a/test/test_spheres.c +++ b/test/test_spheres.c @@ -3,7 +3,7 @@ #include "rays.h" #include "spheres.h" -#include "intersections.h" +#include "intersections_shapes.h" #include "runner.h" #include "matrices.h" #include "transformations.h" @@ -13,7 +13,7 @@ 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 shape s = sphere(); struct intersections xs; xs.count = 0; intersect(&s, r, &xs); @@ -29,7 +29,7 @@ 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 shape s = sphere(); struct intersections xs; xs.count = 0; intersect(&s, r, &xs); @@ -45,7 +45,7 @@ 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 shape s = sphere(); struct intersections xs; xs.count = 0; intersect(&s, r, &xs); @@ -58,7 +58,7 @@ 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 shape s = sphere(); struct intersections xs; xs.count = 0; intersect(&s, r, &xs); @@ -74,7 +74,7 @@ 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 shape s = sphere(); struct intersections xs; xs.count = 0; intersect(&s, r, &xs); @@ -90,7 +90,7 @@ 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 shape s = sphere(); struct intersections xs; xs.count = 0; intersect(&s, r, &xs); @@ -102,32 +102,11 @@ static bool spheres_test_5(const char ** scenario) } 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(); + struct shape s = sphere(); s.transform = scaling(2.0f, 2.0f, 2.0f); struct intersections xs; xs.count = 0; @@ -139,12 +118,12 @@ static bool spheres_test_8(const char ** scenario) float_equal(xs.i[1].t, 7.0f); } -static bool spheres_test_9(const char ** scenario) +static bool spheres_test_7(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(); + struct shape s = sphere(); s.transform = translation(5.0f, 0.0f, 0.0f); struct intersections xs; xs.count = 0; @@ -153,103 +132,83 @@ static bool spheres_test_9(const char ** scenario) return xs.count == 0; } -static bool spheres_test_10(const char ** scenario) +static bool spheres_test_8(const char ** scenario) { *scenario = "The normal on a sphere at a point on the x axis"; - struct sphere s = sphere(); - struct tuple n = sphere_normal_at(&s, point(1.0f, 0.0f, 0.0f)); + struct shape s = sphere(); + struct tuple n = normal_at(&s, point(1.0f, 0.0f, 0.0f)); return tuple_equal(n, vector(1.0f, 0.0f, 0.0f)); } +static bool spheres_test_9(const char ** scenario) +{ + *scenario = "The normal on a sphere at a point on the y axis"; + + struct shape s = sphere(); + struct tuple n = normal_at(&s, point(0.0f, 1.0f, 0.0f)); + return tuple_equal(n, vector(0.0f, 1.0f, 0.0f)); +} + +static bool spheres_test_10(const char ** scenario) +{ + *scenario = "The normal on a sphere at a point on the z axis"; + + struct shape s = sphere(); + struct tuple n = normal_at(&s, point(0.0f, 0.0f, 1.0f)); + return tuple_equal(n, vector(0.0f, 0.0f, 1.0f)); +} + static bool spheres_test_11(const char ** scenario) -{ - *scenario = "The normal on a sphere at a point on the y axis"; - - struct sphere s = sphere(); - struct tuple n = sphere_normal_at(&s, point(0.0f, 1.0f, 0.0f)); - return tuple_equal(n, vector(0.0f, 1.0f, 0.0f)); -} - -static bool spheres_test_12(const char ** scenario) -{ - *scenario = "The normal on a sphere at a point on the z axis"; - - struct sphere s = sphere(); - struct tuple n = sphere_normal_at(&s, point(0.0f, 0.0f, 1.0f)); - return tuple_equal(n, vector(0.0f, 0.0f, 1.0f)); -} - -static bool spheres_test_13(const char ** scenario) { *scenario = "The normal on a sphere at a nonaxial point"; - struct sphere s = sphere(); - struct tuple n = sphere_normal_at(&s, point(0.5773502691896257, - 0.5773502691896257, - 0.5773502691896257)); + struct shape s = sphere(); + struct tuple n = normal_at(&s, point(0.5773502691896257, + 0.5773502691896257, + 0.5773502691896257)); return tuple_equal(n, vector(0.5773502691896257, 0.5773502691896257, 0.5773502691896257)); } -static bool spheres_test_14(const char ** scenario) +static bool spheres_test_12(const char ** scenario) { *scenario = "The normal is a normalized vector"; - struct sphere s = sphere(); - struct tuple n = sphere_normal_at(&s, point(0.5773502691896257, - 0.5773502691896257, - 0.5773502691896257)); + struct shape s = sphere(); + struct tuple n = normal_at(&s, point(0.5773502691896257, + 0.5773502691896257, + 0.5773502691896257)); return tuple_equal(n, tuple_normalize(n)); } -static bool spheres_test_15(const char ** scenario) +static bool spheres_test_13(const char ** scenario) { *scenario = "Computing the normal on a translated sphere"; - struct sphere s = sphere(); + struct shape s = sphere(); s.transform = translation(0.0f, 1.0f, 0.0f); - struct tuple n = sphere_normal_at(&s, point(0.0f, 1.70711f, -0.70711)); + struct tuple n = normal_at(&s, point(0.0f, 1.70711f, -0.70711)); return tuple_equal(n, vector(0.0f, 0.70711f, -0.70711f)); } -static bool spheres_test_16(const char ** scenario) +static bool spheres_test_14(const char ** scenario) { *scenario = "Computing the normal on a transformed sphere"; - struct sphere s = sphere(); + struct shape s = sphere(); struct mat4x4 scale = scaling(1.0f, 0.5f, 1.0f); struct mat4x4 rotate = rotation_z(pi / 5.0f); struct mat4x4 m = mat4x4_mul_m(&scale, &rotate); s.transform = m; - struct tuple n = sphere_normal_at(&s, point(0.0f, 0.7071067811865476, -0.7071067811865476)); + struct tuple n = normal_at(&s, point(0.0f, 0.7071067811865476, -0.7071067811865476)); return tuple_equal(n, vector(0.0f, 0.97014f, -0.24254f)); } -static bool spheres_test_17(const char ** scenario) -{ - *scenario = "A sphere has a default material"; - - struct sphere s = sphere(); - struct material m = s.material; - return material_equal(m, material()); -} - -static bool spheres_test_18(const char ** scenario) -{ - *scenario = "A sphere may be assigned a material"; - - struct sphere s = sphere(); - struct material m = material(); - m.ambient = 1.0f; - s.material = m; - return material_equal(s.material, m); -} - test_t spheres_tests[] = { spheres_test_0, spheres_test_1, @@ -266,10 +225,6 @@ test_t spheres_tests[] = { spheres_test_12, spheres_test_13, spheres_test_14, - spheres_test_15, - spheres_test_16, - spheres_test_17, - spheres_test_18, }; RUNNER(spheres_tests); diff --git a/test/test_world.c b/test/test_world.c index c099367..126c822 100644 --- a/test/test_world.c +++ b/test/test_world.c @@ -20,11 +20,11 @@ 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(); + struct shape 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(); + struct shape s2 = sphere(); s2.transform = scaling(0.5f, 0.5f, 0.5f); struct world w = world_default(); return @@ -61,7 +61,7 @@ static bool world_test_3(const char ** scenario) struct world w = world_default(); 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 shape * shape = &w.objects[0]; struct intersection i = intersection(4.0f, shape); struct computations comps = prepare_computations(i, r); struct tuple c = world_shade_hit(&w, &comps); @@ -76,7 +76,7 @@ static bool world_test_4(const char ** scenario) struct world w = world_default(); 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 shape * shape = &w.objects[1]; struct intersection i = intersection(0.5f, shape); struct computations comps = prepare_computations(i, r); struct tuple c = world_shade_hit(&w, &comps); @@ -111,9 +111,9 @@ static bool world_test_7(const char ** scenario) *scenario = "The color with an intersection behind the ray"; struct world w = world_default(); - struct sphere * outer = &w.objects[0]; + struct shape * outer = &w.objects[0]; outer->material.ambient = 1.0f; - struct sphere * inner = &w.objects[1]; + struct shape * 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 = world_color_at(&w, r); @@ -167,9 +167,9 @@ static bool world_test_12(const char ** scenario) struct world w = world(); w.light = point_light(point(0.0f, 0.0f, -10.0f), color(1.0f, 1.0f, 1.0f)); - struct sphere s1 = sphere(); + struct shape s1 = sphere(); w.objects[0] = s1; - struct sphere s2 = sphere(); + struct shape s2 = sphere(); s2.transform = translation(0.0f, 0.0f, 10.0f); w.objects[1] = s2; w.object_count = 2; diff --git a/world.h b/world.h index 700ffe9..39922fe 100644 --- a/world.h +++ b/world.h @@ -3,7 +3,7 @@ #include "lights.h" #include "spheres.h" #include "transformations.h" -#include "intersections.h" +#include "intersections_shapes.h" #include "rays.h" #include "materials.h" @@ -12,7 +12,7 @@ struct world { struct light light; int object_count; - struct sphere objects[WORLD_MAX_OBJECTS]; + struct shape objects[WORLD_MAX_OBJECTS]; }; inline static struct world world() @@ -25,11 +25,11 @@ inline static struct world world() inline static struct world world_default() { - struct sphere s1 = sphere(); + struct shape 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(); + struct shape s2 = sphere(); s2.transform = scaling(0.5f, 0.5f, 0.5f); return (struct world){