chapter 7

This commit is contained in:
Zack Buhman 2024-08-07 23:10:00 -05:00
parent 15ac0aa031
commit dfee3f2964
15 changed files with 829 additions and 93 deletions

4
.gitignore vendored
View File

@ -10,6 +10,10 @@ test/test_rays
test/test_lights
test/test_materials
test/test_spheres
test/test_world
test/test_camera
raytracer
*.ppm
*.png
*.mp4
*.gif

75
camera.h Normal file
View File

@ -0,0 +1,75 @@
#pragma once
#include "math.h"
#include "matrices.h"
#include "rays.h"
#include "world.h"
#include "canvas.h"
struct camera {
float hsize;
float vsize;
float field_of_view;
struct mat4x4 transform;
float half_width;
float half_height;
float pixel_size;
};
struct camera camera(float hsize, float vsize, float field_of_view)
{
float half_view = tanf(field_of_view / 2.0f);
float aspect = hsize / vsize;
float half_width;
float half_height;
if (aspect >= 1.0f) {
half_width = half_view;
half_height = half_view / aspect;
} else {
half_width = half_view * aspect;
half_height = half_view;
}
float pixel_size = (half_width * 2.0f) / hsize;
return (struct camera){
hsize,
vsize,
field_of_view,
mat4x4_identity(),
half_width,
half_height,
pixel_size
};
}
struct ray camera_ray_for_pixel(struct camera * camera, float px, float py)
{
float xoffset = (px + 0.5f) * camera->pixel_size;
float yoffset = (py + 0.5f) * camera->pixel_size;
float world_x = camera->half_width - xoffset;
float world_y = camera->half_height - yoffset;
struct mat4x4 t_inverse = mat4x4_inverse(&camera->transform);
struct tuple world_point = point(world_x, world_y, -1.0f);
struct tuple pixel = mat4x4_mul_t(&t_inverse, &world_point);
struct tuple world_origin = point(0.0f, 0.0f, 0.0f);
struct tuple origin = mat4x4_mul_t(&t_inverse, &world_origin);
struct tuple direction = tuple_normalize(tuple_sub(pixel, origin));
return ray(origin, direction);
}
void camera_render(struct camera * camera, struct world * world, struct canvas * canvas)
{
canvas->width = camera->hsize;
canvas->height = camera->vsize;
for (int y = 0; y < camera->vsize; y++) {
for (int x = 0; x < camera->hsize; x++) {
struct ray ray = camera_ray_for_pixel(camera, x, y);
struct tuple color = color_at(world, ray);
canvas_write_pixel(canvas, x, y, color);
}
}
}

View File

@ -1,35 +1,48 @@
#pragma once
#include <stdlib.h>
#include <stdio.h>
#include <assert.h>
#include "tuples.h"
#ifndef CANVAS_MAX
#define CANVAS_MAX 32
#endif
struct canvas {
int width;
int height;
struct tuple * pixels;
struct tuple pixels[CANVAS_MAX * CANVAS_MAX];
};
inline static struct canvas canvas(int width, int height)
{
return (struct canvas){
width,
height,
calloc(width * height, (sizeof (struct tuple)))
};
struct canvas c;
assert(width < CANVAS_MAX);
assert(height < CANVAS_MAX);
c.width = width;
c.height = height;
for (int i = 0; i < c.width * c.height; i++) {
c.pixels[i] = tuple(0.0f, 0.0f, 0.0f, 0.0f);
}
return c;
}
inline static void canvas_write_pixel(struct canvas canvas, int x, int y, struct tuple color)
inline static void canvas_write_pixel(struct canvas * canvas, int x, int y, struct tuple color)
{
struct tuple * pixel = &canvas.pixels[y * canvas.width + x];
struct tuple * pixel = &canvas->pixels[y * canvas->width + x];
pixel->r = color.r;
pixel->g = color.g;
pixel->b = color.b;
pixel->a = color.a;
}
inline static struct tuple canvas_pixel_at(struct canvas canvas, int x, int y)
inline static struct tuple canvas_pixel_at(struct canvas * canvas, int x, int y)
{
return canvas.pixels[y * canvas.width + x];
return canvas->pixels[y * canvas->width + x];
}
inline static int clamp_0_255(float n)
@ -43,7 +56,7 @@ inline static int clamp_0_255(float n)
}
}
inline static void canvas_to_ppm(struct canvas canvas, const char * pathname)
inline static void canvas_to_ppm(struct canvas * canvas, const char * pathname)
{
FILE * f = fopen(pathname, "w");
if (f == NULL) {
@ -51,11 +64,11 @@ inline static void canvas_to_ppm(struct canvas canvas, const char * pathname)
}
fwrite("P6\n", 3, 1, f);
fprintf(f, "%d %d\n", canvas.width, canvas.height);
fprintf(f, "%d %d\n", canvas->width, canvas->height);
fwrite("255\n", 4, 1, f);
for (int i = 0; i < canvas.width * canvas.height; i++) {
struct tuple * pixel = &canvas.pixels[i];
for (int i = 0; i < canvas->width * canvas->height; i++) {
struct tuple * pixel = &canvas->pixels[i];
char buf[3];
buf[0] = clamp_0_255(pixel->r);
buf[1] = clamp_0_255(pixel->g);

View File

@ -1,11 +1,14 @@
#pragma once
#include <stdarg.h>
#include <assert.h>
#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
};
}

4
math.h
View File

@ -5,3 +5,7 @@
#define cosf __builtin_cosf
#define sinf __builtin_sinf
#define powf __builtin_powf
#define tanf __builtin_tanf
static const float tau = 6.283185307179586f;
static const float pi = tau / 2.0f;

View File

@ -1,3 +1,5 @@
#define CANVAS_MAX 2048
#include "tuples.h"
#include "canvas.h"
#include "rays.h"
@ -5,6 +7,10 @@
#include "intersections.h"
#include "transformations.h"
#include "materials.h"
#include "world.h"
#include "camera.h"
static struct canvas _canvas;
int main()
{
@ -15,40 +21,109 @@ int main()
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();
shape.material = material();
shape.material.color = color(1.0f, 0.2f, 1.0f);
//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;
struct sphere floor = sphere();
floor.transform = scaling(10.0f, 0.01f, 10.0f);
floor.material.color = color(1.0f, 0.9f, 0.9f);
floor.material.specular = 0.0f;
struct sphere left_wall = sphere();
{
struct mat4x4 _translation = translation(0.0f, 0.0f, 5.0f);
struct mat4x4 _rotation_y = rotation_y(-pi / 4.0f);
struct mat4x4 _rotation_x = rotation_x(pi / 2.0f);
struct mat4x4 _scaling = scaling(10.0f, 0.01f, 10.0f);
struct mat4x4 t0 = mat4x4_mul_m(&_rotation_x, &_scaling);
struct mat4x4 t1 = mat4x4_mul_m(&_rotation_y, &t0);
struct mat4x4 t2 = mat4x4_mul_m(&_translation, &t1);
left_wall.transform = t2;
}
left_wall.material = floor.material;
struct sphere right_wall = sphere();
{
struct mat4x4 _translation = translation(0.0f, 0.0f, 5.0f);
struct mat4x4 _rotation_y = rotation_y(pi / 4.0f);
struct mat4x4 _rotation_x = rotation_x(pi / 2.0f);
struct mat4x4 _scaling = scaling(10.0f, 0.01f, 10.0f);
struct mat4x4 t0 = mat4x4_mul_m(&_rotation_x, &_scaling);
struct mat4x4 t1 = mat4x4_mul_m(&_rotation_y, &t0);
struct mat4x4 t2 = mat4x4_mul_m(&_translation, &t1);
right_wall.transform = t2;
}
right_wall.material = floor.material;
struct sphere 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 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);
right.material.color = color(0.5f, 1.0f, 0.1f);
right.material.diffuse = 0.7;
right.material.specular = 0.3;
struct sphere 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);
left.material.color = color((float)0x12 / 255.f, (float)0xc9 / 255.f, (float)0xcc / 255.f);
left.material.diffuse = 0.7;
left.material.specular = 0.3;
struct tuple light_position = point(-10.0f, 10.0f, -10.0f);
struct tuple light_color = color(1.0f, 1.0f, 1.0f);
struct light light = point_light(light_position, light_color);
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 world world;
world.light = light;
world.object_count = 6;
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;
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);
struct camera _camera = camera(1600.0f, 800.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));
if (h != NULL) {
struct tuple point = ray_position(r, h->t);
struct tuple normal = sphere_normal_at(h->object, point);
struct tuple eye = tuple_neg(r.direction);
for (int i = 0; i < 1; i++) {
struct tuple color = lighting(h->object->material, light, point, eye, normal);
canvas_write_pixel(c, x, y, color);
}
}
/*
_camera.transform = view_transform(point(5.0f * cosf(pi / 360 * (float)i),
1.5f,
-5.0f * sinf(pi / 360 * (float)i)),
point(0.0f, 1.0f, 0.0f),
vector(0.0f, 1.0f, 0.0f));
struct tuple light_position = point(-10.0f * cosf(pi / 360 * (float)i),
10.0f,
-10.0f * sinf(pi / 360 * (float)i));
world.light.position = light_position;
struct mat4x4 middle_rz = rotation_z(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_transform = mat4x4_mul_m(&middle_r, &middle_s);
world.objects[3].transform = mat4x4_mul_m(&middle_t, &middle_transform);
*/
camera_render(&_camera, &world, &_canvas);
char s[128];
snprintf(s, 128, "test%03d.ppm", i);
printf("%s\n", s);
canvas_to_ppm(&_canvas, s);
}
canvas_to_ppm(c, "test.ppm");
}

View File

@ -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 camera; do
gcc -g -gdwarf-5 \
-Wall -Werror -Wfatal-errors \
-I. \

112
test/test_camera.c Normal file
View File

@ -0,0 +1,112 @@
#include <stdbool.h>
#include <stdio.h>
#include "camera.h"
#include "transformations.h"
#include "runner.h"
static bool camera_test_0(const char ** scenario)
{
*scenario = "Constructing a camera";
float hsize = 160.0f;
float vsize = 120.0f;
float field_of_view = pi / 2.0f;
struct camera c = camera(hsize, vsize, field_of_view);
struct mat4x4 identity_matrix = mat4x4_identity();
return
float_equal(c.hsize, 160.0f) &&
float_equal(c.vsize, 120.0f) &&
float_equal(c.field_of_view, pi / 2.0f) &&
mat4x4_equal(&c.transform, &identity_matrix);
}
static bool camera_test_1(const char ** scenario)
{
*scenario = "The pixel size for a horizontal camera";
struct camera c = camera(200.0f, 125.0f, pi / 2.0f);
return float_equal(c.pixel_size, 0.01f);
}
static bool camera_test_2(const char ** scenario)
{
*scenario = "The pixel size for a vertical camera";
struct camera c = camera(125.0f, 200.0f, pi / 2.0f);
return float_equal(c.pixel_size, 0.01f);
}
static bool camera_test_3(const char ** scenario)
{
*scenario = "Constructing a ray through the center of the canvas";
struct camera c = camera(201.0f, 101.0f, pi / 2.0f);
struct ray r = camera_ray_for_pixel(&c, 100.0f, 50.0f);
return
tuple_equal(r.origin, point(0.0f, 0.0f, 0.0f)) &&
tuple_equal(r.direction, vector(0.0f, 0.0f, -1.0f));
}
static bool camera_test_4(const char ** scenario)
{
*scenario = "Constructing a ray through the corner of the canvas";
struct camera c = camera(201.0f, 101.0f, pi / 2.0f);
struct ray r = camera_ray_for_pixel(&c, 0.0f, 0.0f);
return
tuple_equal(r.origin, point(0.0f, 0.0f, 0.0f)) &&
tuple_equal(r.direction, vector(0.66519f, 0.33259f, -0.66851f));
}
static bool camera_test_5(const char ** scenario)
{
*scenario = "Constructing a ray when the camera is transformed";
struct camera c = camera(201.0f, 101.0f, pi / 2.0f);
struct mat4x4 rotate = rotation_y(pi / 4.0f);
struct mat4x4 translate = translation(0.0f, -2.0f, 5.0f);
c.transform = mat4x4_mul_m(&rotate, &translate);
struct ray r = camera_ray_for_pixel(&c, 100.0f, 50.0f);
return
tuple_equal(r.origin, point(0.0f, 2.0f, -5.0f)) &&
tuple_equal(r.direction, vector(0.7071067811865476f, 0.0f, -0.7071067811865476f));
}
static bool camera_test_6(const char ** scenario)
{
*scenario = "Rendering a world with a camera";
struct world w = default_world();
struct camera c = camera(11.0f, 11.0f, pi / 2.0f);
struct tuple from = point(0.0f, 0.0f, -5.0f);
struct tuple to = point(0.0f, 0.0f, 0.0f);
struct tuple up = vector(0.0f, 1.0f, 0.0f);
c.transform = view_transform(from, to, up);
struct canvas image;
camera_render(&c, &w, &image);
struct tuple col = canvas_pixel_at(&image, 5, 5);
return
tuple_equal(col, color(0.38066, 0.47583, 0.2855));
}
test_t camera_tests[] = {
camera_test_0,
camera_test_1,
camera_test_2,
camera_test_3,
camera_test_4,
camera_test_5,
camera_test_6,
};
RUNNER(camera_tests)

View File

@ -7,6 +7,7 @@
static bool canvas_test_0(const char ** scenario)
{
*scenario = "Creating a canvas";
struct canvas c = canvas(10, 20);
bool zeroized = true;
for (int i = 0; i < c.width * c.height; i++) {
@ -21,10 +22,11 @@ static bool canvas_test_0(const char ** scenario)
static bool canvas_test_1(const char ** scenario)
{
*scenario = "Writing pixels to the screen";
struct canvas c = canvas(10, 20);
struct tuple red = color(1.0f, 0.0f, 0.0f);
canvas_write_pixel(c, 2, 3, red);
return tuple_equal(canvas_pixel_at(c, 2, 3), red);
canvas_write_pixel(&c, 2, 3, red);
return tuple_equal(canvas_pixel_at(&c, 2, 3), red);
}
static bool canvas_test_2(const char ** scenario)
@ -35,10 +37,10 @@ static bool canvas_test_2(const char ** scenario)
struct tuple c1 = color(1.5f, 0.0f, 0.0f);
struct tuple c2 = color(0.0f, 0.5f, 0.0f);
struct tuple c3 = color(-0.5f, 0.0f, 1.0f);
canvas_write_pixel(c, 0, 0, c1);
canvas_write_pixel(c, 2, 1, c2);
canvas_write_pixel(c, 4, 2, c3);
canvas_to_ppm(c, "canvas_test_2.ppm");
canvas_write_pixel(&c, 0, 0, c1);
canvas_write_pixel(&c, 2, 1, c2);
canvas_write_pixel(&c, 4, 2, c3);
canvas_to_ppm(&c, "canvas_test_2.ppm");
return true;
}

View File

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

View File

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

View File

@ -210,6 +210,62 @@ static bool transformations_test_17(const char ** scenario)
return tuple_equal(p1, point(15.0f, 0.0f, 7.0f));
}
static bool transformations_test_18(const char ** scenario)
{
*scenario = "The transformation matrix for the default orientation";
struct tuple from = point(0.0f, 0.0f, 0.0f);
struct tuple to = point(0.0f, 0.0f, -1.0f);
struct tuple up = vector(0.0f, 1.0f, 0.0f);
struct mat4x4 t = view_transform(from, to, up);
struct mat4x4 identity_matrix = mat4x4_identity();
return mat4x4_equal(&t, &identity_matrix);
}
static bool transformations_test_19(const char ** scenario)
{
*scenario = "A view transformation matrix looking in positive Z direction";
struct tuple from = point(0.0f, 0.0f, 0.0f);
struct tuple to = point(0.0f, 0.0f, 1.0f);
struct tuple up = vector(0.0f, 1.0f, 0.0f);
struct mat4x4 t = view_transform(from, to, up);
struct mat4x4 scaled = scaling(-1.0f, 1.0f, -1.0f);
return mat4x4_equal(&t, &scaled);
}
static bool transformations_test_20(const char ** scenario)
{
*scenario = "The view transformation moves the world";
struct tuple from = point(0.0f, 0.0f, 8.0f);
struct tuple to = point(0.0f, 0.0f, 0.0f);
struct tuple up = vector(0.0f, 1.0f, 0.0f);
struct mat4x4 t = view_transform(from, to, up);
struct mat4x4 translated = translation(0.0f, 0.0f, -8.0f);
return mat4x4_equal(&t, &translated);
}
static bool transformations_test_21(const char ** scenario)
{
*scenario = "An arbitrary view transformation";
struct tuple from = point(1.0f, 3.0f, 2.0f);
struct tuple to = point(4.0f, -2.0f, 8.0f);
struct tuple up = vector(1.0f, 1.0f, 0.0f);
struct mat4x4 t = view_transform(from, to, up);
struct mat4x4 expected = mat4x4(-0.50709f, 0.50709f, 0.67612f, -2.36643f,
0.76772f, 0.60609f, 0.12122f, -2.82843f,
-0.35857f, 0.59761f, -0.71714f, 0.00000f,
0.00000f, 0.00000f, 0.00000f, 1.00000f
);
return mat4x4_equal(&t, &expected);
}
test_t transformations_tests[] = {
transformations_test_0,
transformations_test_1,
@ -229,6 +285,10 @@ test_t transformations_tests[] = {
transformations_test_15,
transformations_test_16,
transformations_test_17,
transformations_test_18,
transformations_test_19,
transformations_test_20,
transformations_test_21,
};
RUNNER(transformations_tests)

135
test/test_world.c Normal file
View File

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

View File

@ -19,9 +19,6 @@ inline static struct mat4x4 scaling(float x, float y, float z)
0.0f, 0.0f, 0.0f, 1.0f);
}
static const float tau = 6.283185307179586;
static const float pi = tau / 2.0f;
inline static struct mat4x4 rotation_x(float r)
{
return mat4x4(1.0f, 0.0f, 0.0f, 0.0f,
@ -58,3 +55,20 @@ inline static struct mat4x4 shearing(float xy,
zx, zy, 1.0f, 0.0f,
0.0f, 0.0f, 0.0f, 1.0f);
}
inline static struct mat4x4 view_transform(struct tuple from,
struct tuple to,
struct tuple up)
{
struct tuple forward = tuple_normalize(tuple_sub(to, from));
struct tuple left = tuple_cross(forward, tuple_normalize(up));
struct tuple true_up = tuple_cross(left, forward);
struct mat4x4 orientation = mat4x4(left.x, left.y, left.z, 0.0f,
true_up.x, true_up.y, true_up.z, 0.0f,
-forward.x, -forward.y, -forward.z, 0.0f,
0.0f, 0.0f, 0.0f, 1.0f);
struct mat4x4 translate = translation(-from.x, -from.y, -from.z);
return mat4x4_mul_m(&orientation, &translate);
}

73
world.h Normal file
View File

@ -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);
struct intersection * i = hit(&xs);
if (i != NULL) {
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);
}
}