chapter 9

This commit is contained in:
Zack Buhman 2024-08-09 19:57:21 -05:00
parent e312755e3d
commit 9514c2d1e7
16 changed files with 559 additions and 276 deletions

2
.gitignore vendored
View File

@ -12,6 +12,8 @@ test/test_materials
test/test_spheres test/test_spheres
test/test_world test/test_world
test/test_camera test/test_camera
test/test_shapes
test/test_planes
raytracer raytracer
*.ppm *.ppm
*.png *.png

View File

@ -1,29 +1,26 @@
#pragma once #pragma once
#include <stdarg.h> #include <stdarg.h>
#include <assert.h>
#include <stdbool.h>
#include "spheres.h"
#include "rays.h"
struct intersection { struct intersection {
float t; float t;
struct sphere const * object; struct shape const * object;
}; };
struct intersection intersection(float t, struct sphere const * const object) #ifndef INTERSECTIONS_MAX
{ #define INTERSECTIONS_MAX 1024
return (struct intersection){ t, object }; #endif
}
#define MAX_INTERSECTIONS 1024
struct intersections { struct intersections {
int count; 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, ...) inline static void intersections(struct intersections * intersections, int count, ...)
{ {
va_list ap; va_list ap;
@ -35,113 +32,3 @@ inline static void intersections(struct intersections * intersections, int count
intersections->count = 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
};
}

153
intersections_shapes.h Normal file
View File

@ -0,0 +1,153 @@
#pragma once
#include <assert.h>
#include <stdbool.h>
#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
};
}

1
math.h
View File

@ -6,6 +6,7 @@
#define sinf __builtin_sinf #define sinf __builtin_sinf
#define powf __builtin_powf #define powf __builtin_powf
#define tanf __builtin_tanf #define tanf __builtin_tanf
#define fabsf __builtin_fabsf
static const float tau = 6.283185307179586f; static const float tau = 6.283185307179586f;
static const float pi = tau / 2.0f; static const float pi = tau / 2.0f;

35
planes.h Normal file
View File

@ -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;
}

1
rays.h
View File

@ -1,6 +1,5 @@
#pragma once #pragma once
#include "spheres.h"
#include "tuples.h" #include "tuples.h"
#include "matrices.h" #include "matrices.h"

View File

@ -22,12 +22,12 @@ int main()
float pixel_size = wall_size / (float)canvas_pixels; float pixel_size = wall_size / (float)canvas_pixels;
float half = wall_size / 2.0f; float half = wall_size / 2.0f;
struct sphere floor = sphere(); struct shape floor = plane();
floor.transform = scaling(10.0f, 0.01f, 10.0f);
floor.material.color = color(1.0f, 0.9f, 0.9f); floor.material.color = color(1.0f, 0.9f, 0.9f);
floor.material.specular = 0.0f; 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 _translation = translation(0.0f, 0.0f, 5.0f);
struct mat4x4 _rotation_y = rotation_y(-pi / 4.0f); struct mat4x4 _rotation_y = rotation_y(-pi / 4.0f);
@ -42,7 +42,7 @@ int main()
} }
left_wall.material = floor.material; 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 _translation = translation(0.0f, 0.0f, 5.0f);
struct mat4x4 _rotation_y = rotation_y(pi / 4.0f); struct mat4x4 _rotation_y = rotation_y(pi / 4.0f);
@ -56,14 +56,15 @@ int main()
right_wall.transform = t2; right_wall.transform = t2;
} }
right_wall.material = floor.material; right_wall.material = floor.material;
*/
struct sphere middle = sphere(); struct shape middle = sphere();
middle.transform = translation(-0.5f, 1.0f, 0.5f); 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.color = color((float)0xfc / 255.f , (float)0x6d / 255.f, (float)0x09 / 255.f);
middle.material.diffuse = 0.7; middle.material.diffuse = 0.7;
middle.material.specular = 0.3; 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_t = translation(1.5f, 0.5f, -0.5f);
struct mat4x4 right_s = scaling(0.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.transform = mat4x4_mul_m(&right_t, &right_s);
@ -71,7 +72,7 @@ int main()
right.material.diffuse = 0.7; right.material.diffuse = 0.7;
right.material.specular = 0.3; 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_t = translation(-1.5f, 0.33f, -0.75f);
struct mat4x4 left_s = scaling(0.33f, 0.33f, 0.33f); struct mat4x4 left_s = scaling(0.33f, 0.33f, 0.33f);
left.transform = mat4x4_mul_m(&left_t, &left_s); left.transform = mat4x4_mul_m(&left_t, &left_s);
@ -85,22 +86,20 @@ int main()
struct world world; struct world world;
world.light = light; world.light = light;
world.object_count = 6; world.object_count = 4;
world.objects[0] = floor; world.objects[0] = floor;
world.objects[1] = left_wall; world.objects[1] = middle;
world.objects[2] = right_wall; world.objects[2] = right;
world.objects[3] = middle; world.objects[3] = left;
world.objects[4] = right;
world.objects[5] = 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), _camera.transform = view_transform(point(0.0f, 1.5f, -5.0f),
point(0.0f, 1.0f, 0.0f), point(0.0f, 1.0f, 0.0f),
vector(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), _camera.transform = view_transform(point(5.0f * cosf(pi / 360 * (float)i / 10),
1.5f, 1.5f,
@ -118,7 +117,7 @@ int main()
struct mat4x4 middle_ry = rotation_y(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_r = mat4x4_mul_m(&middle_rz, &middle_ry);
struct mat4x4 middle_transform = mat4x4_mul_m(&middle_r, &middle_s); 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); camera_render(&_camera, &world, &_canvas);

25
shapes.h Normal file
View File

@ -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,
};
}

View File

@ -2,27 +2,43 @@
#include "matrices.h" #include "matrices.h"
#include "materials.h" #include "materials.h"
#include "shapes.h"
#include "rays.h"
#include "intersections.h"
struct sphere { inline static struct shape sphere()
struct mat4x4 transform;
struct material material;
};
inline static struct sphere sphere()
{ {
return (struct sphere){ return (struct shape){
mat4x4_identity(), 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); return tuple_sub(local_point, point(0.0f, 0.0f, 0.0f));
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); inline static void sphere_intersect(struct shape const * const s, struct ray local_ray, struct intersections * intersections)
struct tuple world_normal = mat4x4_mul_t(&inv_t, &object_normal); {
world_normal.w = 0.0f; struct tuple sphere_to_ray = tuple_sub(local_ray.origin, point(0.0f, 0.0f, 0.0f));
return tuple_normalize(world_normal);
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;
}
} }

View File

@ -2,10 +2,13 @@
set -eux 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 \ gcc -g -gdwarf-5 \
-Wall -Werror -Wfatal-errors \ -Wall -Werror -Wfatal-errors \
-Wno-error=unused-variable \
-I. \ -I. \
-DTEST \
test/test_${name}.c -o test/test_${name} -O0 -lm test/test_${name}.c -o test/test_${name} -O0 -lm
./test/test_${name} ./test/test_${name}
done done

View File

@ -1,7 +1,7 @@
#include <stdbool.h> #include <stdbool.h>
#include <stdio.h> #include <stdio.h>
#include "intersections.h" #include "intersections_shapes.h"
#include "transformations.h" #include "transformations.h"
#include "runner.h" #include "runner.h"
@ -9,7 +9,7 @@ static bool intersections_test_0(const char ** scenario)
{ {
*scenario = "An intersection encapsulates t and object"; *scenario = "An intersection encapsulates t and object";
struct sphere s = sphere(); struct shape s = sphere();
struct intersection i = intersection(3.5f, &s); struct intersection i = intersection(3.5f, &s);
return return
@ -21,7 +21,7 @@ static bool intersections_test_1(const char ** scenario)
{ {
*scenario = "Aggregating intersections"; *scenario = "Aggregating intersections";
struct sphere s = sphere(); struct shape s = sphere();
struct intersection i1 = intersection(1.0f, &s); struct intersection i1 = intersection(1.0f, &s);
struct intersection i2 = intersection(2.0f, &s); struct intersection i2 = intersection(2.0f, &s);
struct intersections xs; struct intersections xs;
@ -37,7 +37,7 @@ static bool intersections_test_2(const char ** scenario)
{ {
*scenario = "The hit, when all intersections have positive t"; *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 i1 = intersection(1.0f, &s);
struct intersection i2 = intersection(2.0f, &s); struct intersection i2 = intersection(2.0f, &s);
struct intersections xs; struct intersections xs;
@ -55,7 +55,7 @@ static bool intersections_test_3(const char ** scenario)
{ {
*scenario = "The hit, when some intersections have negative t"; *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 i1 = intersection(-1.0f, &s);
struct intersection i2 = intersection( 1.0f, &s); struct intersection i2 = intersection( 1.0f, &s);
struct intersections xs; struct intersections xs;
@ -73,7 +73,7 @@ static bool intersections_test_4(const char ** scenario)
{ {
*scenario = "The hit, when all intersections have negative t"; *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 i1 = intersection(-2.0f, &s);
struct intersection i2 = intersection(-1.0f, &s); struct intersection i2 = intersection(-1.0f, &s);
struct intersections xs; struct intersections xs;
@ -88,7 +88,7 @@ static bool intersections_test_5(const char ** scenario)
{ {
*scenario = "The hit is always the lowest nonnegative intersection"; *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 i1 = intersection( 5.0f, &s);
struct intersection i2 = intersection( 7.0f, &s); struct intersection i2 = intersection( 7.0f, &s);
struct intersection i3 = intersection(-3.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"; *scenario = "In-place ascending sort of intersections";
struct sphere s1 = sphere(); struct shape s1 = sphere();
s1.material.ambient = 1.0f; s1.material.ambient = 1.0f;
struct sphere s2 = sphere(); struct shape s2 = sphere();
s2.material.ambient = 2.0f; s2.material.ambient = 2.0f;
struct sphere s3 = sphere(); struct shape s3 = sphere();
s3.material.ambient = 3.0f; s3.material.ambient = 3.0f;
struct sphere s4 = sphere(); struct shape s4 = sphere();
s4.material.ambient = 4.0f; s4.material.ambient = 4.0f;
struct intersection i1 = intersection( 5.0f, &s1); struct intersection i1 = intersection( 5.0f, &s1);
struct intersection i2 = intersection( 7.0f, &s2); 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"; *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 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 intersection i = intersection(4.0f, &shape);
struct computations comps = prepare_computations(i, r); 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"; *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 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 intersection i = intersection(4.0f, &shape);
struct computations comps = prepare_computations(i, r); 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"; *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 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 intersection i = intersection(1.0f, &shape);
struct computations comps = prepare_computations(i, r); 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"; *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 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); shape.transform = translation(0.0f, 0.0f, 1.0f);
struct intersection i = intersection(5.0f, &shape); struct intersection i = intersection(5.0f, &shape);
struct computations comps = prepare_computations(i, r); struct computations comps = prepare_computations(i, r);

87
test/test_planes.c Normal file
View File

@ -0,0 +1,87 @@
#include <stdbool.h>
#include <stdio.h>
#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);

121
test/test_shapes.c Normal file
View File

@ -0,0 +1,121 @@
#include <stdbool.h>
#include <stdio.h>
#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)

View File

@ -3,7 +3,7 @@
#include "rays.h" #include "rays.h"
#include "spheres.h" #include "spheres.h"
#include "intersections.h" #include "intersections_shapes.h"
#include "runner.h" #include "runner.h"
#include "matrices.h" #include "matrices.h"
#include "transformations.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"; *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 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; struct intersections xs;
xs.count = 0; xs.count = 0;
intersect(&s, r, &xs); 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"; *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 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; struct intersections xs;
xs.count = 0; xs.count = 0;
intersect(&s, r, &xs); intersect(&s, r, &xs);
@ -45,7 +45,7 @@ static bool spheres_test_2(const char ** scenario)
*scenario = "A ray misses a sphere"; *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 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; struct intersections xs;
xs.count = 0; xs.count = 0;
intersect(&s, r, &xs); intersect(&s, r, &xs);
@ -58,7 +58,7 @@ static bool spheres_test_3(const char ** scenario)
*scenario = "A ray originates in a sphere"; *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 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; struct intersections xs;
xs.count = 0; xs.count = 0;
intersect(&s, r, &xs); intersect(&s, r, &xs);
@ -74,7 +74,7 @@ static bool spheres_test_4(const char ** scenario)
*scenario = "A sphere is behind a ray"; *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 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; struct intersections xs;
xs.count = 0; xs.count = 0;
intersect(&s, r, &xs); intersect(&s, r, &xs);
@ -90,7 +90,7 @@ static bool spheres_test_5(const char ** scenario)
*scenario = "Intersect sets the object on the intersection"; *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 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; struct intersections xs;
xs.count = 0; xs.count = 0;
intersect(&s, r, &xs); intersect(&s, r, &xs);
@ -102,32 +102,11 @@ static bool spheres_test_5(const char ** scenario)
} }
static bool spheres_test_6(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"; *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 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); s.transform = scaling(2.0f, 2.0f, 2.0f);
struct intersections xs; struct intersections xs;
xs.count = 0; xs.count = 0;
@ -139,12 +118,12 @@ static bool spheres_test_8(const char ** scenario)
float_equal(xs.i[1].t, 7.0f); 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"; *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 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); s.transform = translation(5.0f, 0.0f, 0.0f);
struct intersections xs; struct intersections xs;
xs.count = 0; xs.count = 0;
@ -153,39 +132,39 @@ static bool spheres_test_9(const char ** scenario)
return xs.count == 0; 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"; *scenario = "The normal on a sphere at a point on the x axis";
struct sphere s = sphere(); struct shape s = sphere();
struct tuple n = sphere_normal_at(&s, point(1.0f, 0.0f, 0.0f)); struct tuple n = normal_at(&s, point(1.0f, 0.0f, 0.0f));
return tuple_equal(n, vector(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) 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"; *scenario = "The normal on a sphere at a nonaxial point";
struct sphere s = sphere(); struct shape s = sphere();
struct tuple n = sphere_normal_at(&s, point(0.5773502691896257, struct tuple n = normal_at(&s, point(0.5773502691896257,
0.5773502691896257, 0.5773502691896257,
0.5773502691896257)); 0.5773502691896257));
return tuple_equal(n, vector(0.5773502691896257, return tuple_equal(n, vector(0.5773502691896257,
@ -193,63 +172,43 @@ static bool spheres_test_13(const char ** scenario)
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"; *scenario = "The normal is a normalized vector";
struct sphere s = sphere(); struct shape s = sphere();
struct tuple n = sphere_normal_at(&s, point(0.5773502691896257, struct tuple n = normal_at(&s, point(0.5773502691896257,
0.5773502691896257, 0.5773502691896257,
0.5773502691896257)); 0.5773502691896257));
return tuple_equal(n, tuple_normalize(n)); 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"; *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); 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)); 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"; *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 scale = scaling(1.0f, 0.5f, 1.0f);
struct mat4x4 rotate = rotation_z(pi / 5.0f); struct mat4x4 rotate = rotation_z(pi / 5.0f);
struct mat4x4 m = mat4x4_mul_m(&scale, &rotate); struct mat4x4 m = mat4x4_mul_m(&scale, &rotate);
s.transform = m; 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)); 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[] = { test_t spheres_tests[] = {
spheres_test_0, spheres_test_0,
spheres_test_1, spheres_test_1,
@ -266,10 +225,6 @@ test_t spheres_tests[] = {
spheres_test_12, spheres_test_12,
spheres_test_13, spheres_test_13,
spheres_test_14, spheres_test_14,
spheres_test_15,
spheres_test_16,
spheres_test_17,
spheres_test_18,
}; };
RUNNER(spheres_tests); RUNNER(spheres_tests);

View File

@ -20,11 +20,11 @@ static bool world_test_1(const char ** scenario)
*scenario = "The default world"; *scenario = "The default world";
struct light light = point_light(point(-10.0f, 10.0f, -10.0f), color(1.0f, 1.0f, 1.0f)); 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.color = color(0.8f, 1.0f, 0.6f);
s1.material.diffuse = 0.7f; s1.material.diffuse = 0.7f;
s1.material.specular = 0.2f; s1.material.specular = 0.2f;
struct sphere s2 = sphere(); struct shape s2 = sphere();
s2.transform = scaling(0.5f, 0.5f, 0.5f); s2.transform = scaling(0.5f, 0.5f, 0.5f);
struct world w = world_default(); struct world w = world_default();
return return
@ -61,7 +61,7 @@ static bool world_test_3(const char ** scenario)
struct world w = world_default(); struct world w = world_default();
struct ray r = ray(point(0.0f, 0.0f, -5.0f), vector(0.0f, 0.0f, 1.0f)); 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 intersection i = intersection(4.0f, shape);
struct computations comps = prepare_computations(i, r); struct computations comps = prepare_computations(i, r);
struct tuple c = world_shade_hit(&w, &comps); 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(); struct world w = world_default();
w.light = point_light(point(0.0f, 0.25f, 0.0f), color(1.0f, 1.0f, 1.0f)); 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 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 intersection i = intersection(0.5f, shape);
struct computations comps = prepare_computations(i, r); struct computations comps = prepare_computations(i, r);
struct tuple c = world_shade_hit(&w, &comps); 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"; *scenario = "The color with an intersection behind the ray";
struct world w = world_default(); struct world w = world_default();
struct sphere * outer = &w.objects[0]; struct shape * outer = &w.objects[0];
outer->material.ambient = 1.0f; outer->material.ambient = 1.0f;
struct sphere * inner = &w.objects[1]; struct shape * inner = &w.objects[1];
inner->material.ambient = 1.0f; inner->material.ambient = 1.0f;
struct ray r = ray(point(0.0f, 0.0f, 0.75f), vector(0.0f, 0.0f, -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); struct tuple c = world_color_at(&w, r);
@ -167,9 +167,9 @@ static bool world_test_12(const char ** scenario)
struct world w = world(); struct world w = world();
w.light = point_light(point(0.0f, 0.0f, -10.0f), color(1.0f, 1.0f, 1.0f)); 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; w.objects[0] = s1;
struct sphere s2 = sphere(); struct shape s2 = sphere();
s2.transform = translation(0.0f, 0.0f, 10.0f); s2.transform = translation(0.0f, 0.0f, 10.0f);
w.objects[1] = s2; w.objects[1] = s2;
w.object_count = 2; w.object_count = 2;

View File

@ -3,7 +3,7 @@
#include "lights.h" #include "lights.h"
#include "spheres.h" #include "spheres.h"
#include "transformations.h" #include "transformations.h"
#include "intersections.h" #include "intersections_shapes.h"
#include "rays.h" #include "rays.h"
#include "materials.h" #include "materials.h"
@ -12,7 +12,7 @@
struct world { struct world {
struct light light; struct light light;
int object_count; int object_count;
struct sphere objects[WORLD_MAX_OBJECTS]; struct shape objects[WORLD_MAX_OBJECTS];
}; };
inline static struct world world() inline static struct world world()
@ -25,11 +25,11 @@ inline static struct world world()
inline static struct world world_default() 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.color = color(0.8f, 1.0f, 0.6f);
s1.material.diffuse = 0.7f; s1.material.diffuse = 0.7f;
s1.material.specular = 0.2f; s1.material.specular = 0.2f;
struct sphere s2 = sphere(); struct shape s2 = sphere();
s2.transform = scaling(0.5f, 0.5f, 0.5f); s2.transform = scaling(0.5f, 0.5f, 0.5f);
return (struct world){ return (struct world){