chapter 5

This commit is contained in:
Zack Buhman 2024-07-24 00:39:42 -05:00
parent 39e0d00e52
commit 70315ef0d8
10 changed files with 502 additions and 4 deletions

2
.gitignore vendored
View File

@ -4,4 +4,6 @@ test/test_tuples
test/test_canvas
test/test_matrices
test/test_transformations
test/test_intersections
test/test_rays
*.ppm

76
intersections.h Normal file
View File

@ -0,0 +1,76 @@
#pragma once
#include "spheres.h"
#include "rays.h"
struct intersection {
float t;
struct sphere const * const object;
};
struct intersection intersection(float t, struct sphere const * const object)
{
return (struct intersection){ t, object };
}
struct intersections {
int count;
struct intersection i[];
};
struct intersections2 {
int count;
struct intersection i[2];
};
struct intersections4 {
int count;
struct intersection i[4];
};
struct intersections2 intersections2(struct intersection a, struct intersection b)
{
return (struct intersections2){ 2, {a, b} };
}
struct intersections4 intersections4(struct intersection a, struct intersection b, struct intersection c, struct intersection d)
{
return (struct intersections4){ 4, {a, b, c, d} };
}
inline static struct intersections2 intersect(struct sphere const * const s, struct ray r)
{
struct mat4x4 m = mat4x4_inverse(&s->transform);
struct ray r2 = ray_transform(r, &m);
struct tuple sphere_to_ray = tuple_sub(r2.origin, point(0.0f, 0.0f, 0.0f));
float a = tuple_dot(r2.direction, r2.direction);
float b = 2 * tuple_dot(r2.direction, sphere_to_ray);
float c = tuple_dot(sphere_to_ray, sphere_to_ray) - 1;
float discriminant = b * b - 4 * a * c;
if (discriminant < 0) {
return (struct intersections2) { 0 };
} else {
float root = sqrtf(discriminant);
float t1 = (-b - root) / (2 * a);
float t2 = (-b + root) / (2 * a);
return
intersections2(intersection(t1, s),
intersection(t2, s));
}
}
inline static struct intersection * hit(struct intersections * xs)
{
struct intersection * i = NULL;
for (int n = 0; n < xs->count; n++) {
if (xs->i[n].t >= 0) {
if (i == NULL || i->t > xs->i[n].t)
i = &xs->i[n];
}
}
return i;
}

26
rays.h Normal file
View File

@ -0,0 +1,26 @@
#pragma once
#include "spheres.h"
#include "tuples.h"
#include "matrices.h"
struct ray {
struct tuple origin;
struct tuple direction;
};
inline static struct ray ray(struct tuple origin, struct tuple direction)
{
return (struct ray){origin, direction};
}
inline static struct tuple ray_position(struct ray ray, float t)
{
return tuple_add(ray.origin, tuple_mul(ray.direction, t));
}
inline static struct ray ray_transform(struct ray r, struct mat4x4 const * const m)
{
return ray(mat4x4_mul_t(m, &r.origin),
mat4x4_mul_t(m, &r.direction));
}

41
raytracer.c Normal file
View File

@ -0,0 +1,41 @@
#include "tuples.h"
#include "canvas.h"
#include "rays.h"
#include "spheres.h"
#include "intersections.h"
#include "transformations.h"
int main()
{
struct tuple ray_origin = point(0.0f, 0.0f, -5.0f);
float wall_z = 10.0f;
float wall_size = 7.0f;
int canvas_pixels = 100;
float pixel_size = wall_size / (float)canvas_pixels;
float half = wall_size / 2.0f;
struct canvas c = canvas(canvas_pixels, canvas_pixels);
struct tuple red = color(1.0f, 0.0f, 0.0f);
struct sphere shape = sphere();
struct mat4x4 shear = shearing(1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f);
struct mat4x4 scale = scaling(0.5f, 1.0f, 1.0f);
struct mat4x4 m = mat4x4_mul_m(&shear, &scale);
shape.transform = m;
for (int y = 0; y < canvas_pixels; y++) {
float world_y = half - pixel_size * y;
for (int x = 0; x < canvas_pixels; x++) {
float world_x = -half + pixel_size * x;
struct tuple position = point(world_x, world_y, wall_z);
struct ray r = ray(ray_origin, tuple_normalize(tuple_sub(position, ray_origin)));
struct intersections2 xs = intersect(&shape, r);
struct intersection * h = hit((struct intersections *)&xs);
if (h != NULL) {
canvas_write_pixel(c, x, y, red);
}
}
}
canvas_to_ppm(c, "test.ppm");
}

14
spheres.h Normal file
View File

@ -0,0 +1,14 @@
#pragma once
#include "matrices.h"
struct sphere {
struct mat4x4 transform;
};
inline static struct sphere sphere()
{
return (struct sphere){
mat4x4_identity()
};
}

View File

@ -2,7 +2,7 @@
set -eux
for name in tuples canvas matrices transformations; do
for name in tuples canvas matrices transformations rays intersections spheres; do
gcc -g -gdwarf-5 \
-Wall -Werror -Wfatal-errors \
-I. \

View File

@ -2,18 +2,26 @@
typedef bool (*test_t)(const char ** scenario);
#define ANSI_RED "\x1b[31m"
#define ANSI_GREEN "\x1b[32m"
#define ANSI_RESET "\x1b[0m"
#define RUNNER(tests) \
int main() \
{ \
int fail_count = 0; \
for (int i = 0; i < (sizeof (tests)) / (sizeof (test_t)); i++) { \
const char * scenario = NULL; \
const char * scenario = NULL; \
bool result = tests[i](&scenario); \
const char * result_s = result ? "ok" : "fail"; \
const char * result_s = result ? "ok" : ANSI_RED "fail" ANSI_RESET; \
fail_count += !result; \
fprintf(stderr, "%s: %s\n", scenario, result_s); \
} \
fprintf(stderr, "\nfailed tests: %d\n", fail_count); \
if (fail_count == 0) { \
fprintf(stderr, ANSI_GREEN "failed tests: %d\n\n" ANSI_RESET, fail_count); \
} else { \
fprintf(stderr, ANSI_RED "failed tests: %d\n\n" ANSI_RESET, fail_count); \
} \
\
return !(fail_count == 0); \
}

110
test/test_intersections.c Normal file
View File

@ -0,0 +1,110 @@
#include <stdbool.h>
#include <stdio.h>
#include "intersections.h"
#include "runner.h"
static bool intersections_test_0(const char ** scenario)
{
*scenario = "An intersection encapsulates t and object";
struct sphere s = sphere();
struct intersection i = intersection(3.5f, &s);
return
float_equal(i.t, 3.5f) &&
i.object == &s;
}
static bool intersections_test_1(const char ** scenario)
{
*scenario = "Aggregating intersections2";
struct sphere s = sphere();
struct intersection i1 = intersection(1.0f, &s);
struct intersection i2 = intersection(2.0f, &s);
struct intersections2 xs = intersections2(i1, i2);
return
xs.count == 2 &&
float_equal(xs.i[0].t, 1.0f) &&
float_equal(xs.i[1].t, 2.0f);
}
static bool intersections_test_2(const char ** scenario)
{
*scenario = "The hit, when all intersections2 have positive t";
struct sphere s = sphere();
struct intersection i1 = intersection(1.0f, &s);
struct intersection i2 = intersection(2.0f, &s);
struct intersections2 xs = intersections2(i2, i1);
struct intersection * i = hit((struct intersections *)&xs);
return
i != NULL &&
i->t == i1.t &&
i->object == i1.object;
}
static bool intersections_test_3(const char ** scenario)
{
*scenario = "The hit, when some intersections2 have negative t";
struct sphere s = sphere();
struct intersection i1 = intersection(-1.0f, &s);
struct intersection i2 = intersection( 1.0f, &s);
struct intersections2 xs = intersections2(i2, i1);
struct intersection * i = hit((struct intersections *)&xs);
return
i != NULL &&
i->t == i2.t &&
i->object == i2.object;
}
static bool intersections_test_4(const char ** scenario)
{
*scenario = "The hit, when all intersections2 have negative t";
struct sphere s = sphere();
struct intersection i1 = intersection(-2.0f, &s);
struct intersection i2 = intersection(-1.0f, &s);
struct intersections2 xs = intersections2(i2, i1);
struct intersection * i = hit((struct intersections *)&xs);
return i == NULL;
}
static bool intersections_test_5(const char ** scenario)
{
*scenario = "The hit is always the lowest nonnegative intersection";
struct sphere s = sphere();
struct intersection i1 = intersection( 5.0f, &s);
struct intersection i2 = intersection( 7.0f, &s);
struct intersection i3 = intersection(-3.0f, &s);
struct intersection i4 = intersection( 2.0f, &s);
struct intersections4 xs = intersections4(i1, i2, i3, i4);
struct intersection * i = hit((struct intersections *)&xs);
return
i != NULL &&
i->t == i4.t &&
i->object == i4.object;
}
test_t intersections_tests[] = {
intersections_test_0,
intersections_test_1,
intersections_test_2,
intersections_test_3,
intersections_test_4,
intersections_test_5,
};
RUNNER(intersections_tests);

68
test/test_rays.c Normal file
View File

@ -0,0 +1,68 @@
#include <stdbool.h>
#include <stdio.h>
#include "rays.h"
#include "runner.h"
#include "transformations.h"
static bool rays_test_0(const char ** scenario)
{
*scenario = "Creating and querying a ray";
struct tuple origin = point(1.0f, 2.0f, 3.0f);
struct tuple direction = vector(4.0f, 5.0f, 6.0f);
struct ray r = ray(origin, direction);
return
tuple_equal(r.origin, origin) &&
tuple_equal(r.direction, direction);
}
static bool rays_test_1(const char ** scenario)
{
*scenario = "Computing a point from a distance";
struct ray r = ray(point(2.0f, 3.0f, 4.0f), vector(1.0f, 0.0f, 0.0f));
return
tuple_equal(ray_position(r, 0.0f), point(2.0f, 3.0f, 4.0f)) &&
tuple_equal(ray_position(r, 1.0f), point(3.0f, 3.0f, 4.0f)) &&
tuple_equal(ray_position(r, -1.0f), point(1.0f, 3.0f, 4.0f)) &&
tuple_equal(ray_position(r, 2.5f), point(4.5f, 3.0f, 4.0f));
}
static bool rays_test_2(const char ** scenario)
{
*scenario = "Translating a ray";
struct ray r = ray(point(1.0f, 2.0f, 3.0f), vector(0.0f, 1.0f, 0.0f));
struct mat4x4 m = translation(3.0f, 4.0f, 5.0f);
struct ray r2 = ray_transform(r, &m);
return
tuple_equal(r2.origin, point(4.0f, 6.0f, 8.0f)) &&
tuple_equal(r2.direction, vector(0.0f, 1.0f, 0.0f));
}
static bool rays_test_3(const char ** scenario)
{
*scenario = "Scaling a ray";
struct ray r = ray(point(1.0f, 2.0f, 3.0f), vector(0.0f, 1.0f, 0.0f));
struct mat4x4 m = scaling(2.0f, 3.0f, 4.0f);
struct ray r2 = ray_transform(r, &m);
return
tuple_equal(r2.origin, point(2.0f, 6.0f, 12.0f)) &&
tuple_equal(r2.direction, vector(0.0f, 3.0f, 0.0f));
}
test_t rays_tests[] = {
rays_test_0,
rays_test_1,
rays_test_2,
rays_test_3,
};
RUNNER(rays_tests);

153
test/test_spheres.c Normal file
View File

@ -0,0 +1,153 @@
#include <stdbool.h>
#include <stdio.h>
#include "rays.h"
#include "spheres.h"
#include "intersections.h"
#include "runner.h"
#include "matrices.h"
#include "transformations.h"
static bool spheres_test_0(const char ** scenario)
{
*scenario = "A ray intersects a sphere at two points";
struct ray r = ray(point(0.0f, 0.0f, -5.0f), vector(0.0f, 0.0f, 1.0f));
struct sphere s = sphere();
struct intersections2 xs = intersect(&s, r);
return
xs.count == 2 &&
float_equal(xs.i[0].t, 4.0f) &&
float_equal(xs.i[1].t, 6.0f);
}
static bool spheres_test_1(const char ** scenario)
{
*scenario = "A ray intersects a sphere at a tangent";
struct ray r = ray(point(0.0f, 1.0f, -5.0f), vector(0.0f, 0.0f, 1.0f));
struct sphere s = sphere();
struct intersections2 xs = intersect(&s, r);
return
xs.count == 2 &&
float_equal(xs.i[0].t, 5.0f) &&
float_equal(xs.i[1].t, 5.0f);
}
static bool spheres_test_2(const char ** scenario)
{
*scenario = "A ray misses a sphere";
struct ray r = ray(point(0.0f, 2.0f, -5.0f), vector(0.0f, 0.0f, 1.0f));
struct sphere s = sphere();
struct intersections2 xs = intersect(&s, r);
return xs.count == 0;
}
static bool spheres_test_3(const char ** scenario)
{
*scenario = "A ray originates in a sphere";
struct ray r = ray(point(0.0f, 0.0f, 0.0f), vector(0.0f, 0.0f, 1.0f));
struct sphere s = sphere();
struct intersections2 xs = intersect(&s, r);
return
xs.count == 2 &&
float_equal(xs.i[0].t, -1.0f) &&
float_equal(xs.i[1].t, 1.0f);
}
static bool spheres_test_4(const char ** scenario)
{
*scenario = "A sphere is behind a ray";
struct ray r = ray(point(0.0f, 0.0f, 5.0f), vector(0.0f, 0.0f, 1.0f));
struct sphere s = sphere();
struct intersections2 xs = intersect(&s, r);
return
xs.count == 2 &&
float_equal(xs.i[0].t, -6.0f) &&
float_equal(xs.i[1].t, -4.0f);
}
static bool spheres_test_5(const char ** scenario)
{
*scenario = "Intersect sets the object on the intersection";
struct ray r = ray(point(0.0f, 0.0f, -5.0f), vector(0.0f, 0.0f, 1.0f));
struct sphere s = sphere();
struct intersections2 xs = intersect(&s, r);
return
xs.count == 2 &&
xs.i[0].object == &s &&
xs.i[1].object == &s;
}
static bool spheres_test_6(const char ** scenario)
{
*scenario = "A sphere's default transformation";
struct sphere s = sphere();
struct mat4x4 identity = mat4x4_identity();
return mat4x4_equal(&s.transform, &identity);
}
static bool spheres_test_7(const char ** scenario)
{
*scenario = "Changing a sphere's transformation";
struct sphere s = sphere();
struct mat4x4 t = translation(2.0f, 3.0f, 4.0f);
s.transform = t;
return mat4x4_equal(&s.transform, &t);
}
static bool spheres_test_8(const char ** scenario)
{
*scenario = "Intersecting a scaled sphere with a ray";
struct ray r = ray(point(0.0f, 0.0f, -5.0f), vector(0.0f, 0.0f, 1.0f));
struct sphere s = sphere();
s.transform = scaling(2.0f, 2.0f, 2.0f);
struct intersections2 xs = intersect(&s, r);
return
xs.count == 2 &&
float_equal(xs.i[0].t, 3.0f) &&
float_equal(xs.i[1].t, 7.0f);
}
static bool spheres_test_9(const char ** scenario)
{
*scenario = "Intersecting a translated sphere with a ray";
struct ray r = ray(point(0.0f, 0.0f, 5.0f), vector(0.0f, 0.0f, 1.0f));
struct sphere s = sphere();
s.transform = translation(5.0f, 0.0f, 0.0f);
struct intersections2 xs = intersect(&s, r);
return xs.count == 0;
}
test_t spheres_tests[] = {
spheres_test_0,
spheres_test_1,
spheres_test_2,
spheres_test_3,
spheres_test_4,
spheres_test_5,
spheres_test_6,
spheres_test_7,
spheres_test_8,
spheres_test_9,
};
RUNNER(spheres_tests);