244 lines
7.4 KiB
C
244 lines
7.4 KiB
C
#include "camera.h"
|
|
#include "matrix.h"
|
|
#include "tuples.h"
|
|
#include "yamlloader.h"
|
|
|
|
#include <assert.h>
|
|
#include <logger.h>
|
|
#include <math.h>
|
|
#include <memory.h>
|
|
#include <pthread.h>
|
|
#include <stdatomic.h>
|
|
|
|
static void calculate_pixel_size(CAMERA_Camera *camera) {
|
|
assert(camera);
|
|
double half_view = tan(camera->field_of_view / 2.0);
|
|
double aspect = camera->hsize / (double)camera->vsize;
|
|
|
|
if (aspect >= 1) {
|
|
camera->half_width = half_view;
|
|
camera->half_height = half_view / aspect;
|
|
} else {
|
|
camera->half_width = half_view * aspect;
|
|
camera->half_height = half_view;
|
|
}
|
|
camera->pixel_size = (camera->half_width * 2.0) / camera->hsize;
|
|
}
|
|
|
|
CAMERA_Camera *CAMERA_new(unsigned int hsize, unsigned int vsize, double field_of_view) {
|
|
assert(hsize > 0);
|
|
assert(vsize > 0);
|
|
CAMERA_Camera *camera = malloc(sizeof(CAMERA_Camera));
|
|
camera->hsize = hsize;
|
|
camera->vsize = vsize;
|
|
camera->field_of_view = field_of_view;
|
|
camera->transform = MATRIX_new_identity(4);
|
|
camera->inv_transform = MATRIX_new_identity(4);
|
|
MATRIX_inverse(camera->inv_transform, camera->transform);
|
|
calculate_pixel_size(camera);
|
|
return camera;
|
|
}
|
|
|
|
void CAMERA_delete(CAMERA_Camera *camera) {
|
|
assert(camera);
|
|
assert(camera->transform);
|
|
MATRIX_delete_all(camera->transform, camera->inv_transform);
|
|
free(camera);
|
|
}
|
|
|
|
MATRIX_Matrix *CAMERA_view_transform(const TUPLES_Point *from, const TUPLES_Point *to, const TUPLES_Vector *up) {
|
|
assert(from);
|
|
assert(to);
|
|
assert(up);
|
|
TUPLES_Vector forward;
|
|
TUPLES_subtract(&forward, to, from);
|
|
TUPLES_normalize(&forward);
|
|
|
|
TUPLES_Vector upn;
|
|
TUPLES_copy(&upn, up);
|
|
TUPLES_normalize(&upn);
|
|
|
|
TUPLES_Vector left;
|
|
TUPLES_cross(&left, &forward, &upn);
|
|
|
|
TUPLES_Vector true_up;
|
|
TUPLES_cross(&true_up, &left, &forward);
|
|
|
|
MATRIX_Matrix orientation;
|
|
MATRIX_init(&orientation, 4, 4);
|
|
MATRIX_fill(&orientation, left.x, left.y, left.z, 0.0, true_up.x, true_up.y, true_up.z, 0.0, -forward.x, -forward.y, -forward.z, 0.0, 0.0, 0.0, 0.0, 1.0);
|
|
|
|
MATRIX_Matrix *translation = MATRIX_new_translation(-from->x, -from->y, -from->z);
|
|
MATRIX_Matrix *view = MATRIX_multiply(&orientation, translation);
|
|
MATRIX_delete(translation);
|
|
return view;
|
|
}
|
|
|
|
void CAMERA_set_transform(CAMERA_Camera *camera, MATRIX_Matrix *transform) {
|
|
assert(camera);
|
|
assert(transform);
|
|
assert(MATRIX_is_invertible(transform));
|
|
MATRIX_copy(camera->transform, transform);
|
|
MATRIX_inverse(camera->inv_transform, camera->transform);
|
|
}
|
|
|
|
void CAMERA_ray_for_pixel(RAY_Ray *dest, const CAMERA_Camera *camera, unsigned int px, unsigned int py) {
|
|
assert(dest);
|
|
assert(camera);
|
|
assert(px < camera->hsize);
|
|
assert(py < camera->vsize);
|
|
|
|
double xoffset = (px + 0.5) * camera->pixel_size;
|
|
double yoffset = (py + 0.5) * camera->pixel_size;
|
|
|
|
double world_x = camera->half_width - xoffset;
|
|
double world_y = camera->half_height - yoffset;
|
|
|
|
TUPLES_Point ptmp, pixel, origin;
|
|
TUPLES_init_point(&ptmp, world_x, world_y, -1.0);
|
|
MATRIX_multiply_tuple(&pixel, camera->inv_transform, &ptmp);
|
|
|
|
TUPLES_init_point(&ptmp, 0, 0, 0);
|
|
MATRIX_multiply_tuple(&origin, camera->inv_transform, &ptmp);
|
|
|
|
TUPLES_Vector direction;
|
|
TUPLES_subtract(&direction, &pixel, &origin);
|
|
TUPLES_normalize(&direction);
|
|
|
|
RAY_init_from_tuples(dest, &origin, &direction);
|
|
}
|
|
|
|
typedef struct {
|
|
const CAMERA_Camera *camera;
|
|
const WORLD_World *world;
|
|
CANVAS_Canvas *canvas;
|
|
uint thread_num;
|
|
uint total_threads;
|
|
atomic_uint pixels_processed;
|
|
} render_thread_args;
|
|
|
|
static void *render_thread(void *args) {
|
|
assert(args);
|
|
render_thread_args *rta = (render_thread_args *)args;
|
|
assert(rta->camera);
|
|
assert(rta->world);
|
|
for (uint y = 0; y < rta->camera->vsize; y++) {
|
|
for (uint x = 0; x < rta->camera->hsize; x++) {
|
|
const uint this_pixel = y * rta->camera->hsize + x;
|
|
if (this_pixel % rta->total_threads != rta->thread_num) {
|
|
continue;
|
|
}
|
|
RAY_Ray ray;
|
|
TUPLES_Color color;
|
|
|
|
CAMERA_ray_for_pixel(&ray, rta->camera, x, y);
|
|
WORLD_color_at(&color, rta->world, &ray, WORLD_default_ttl);
|
|
CANVAS_write_pixel(rta->canvas, x, y, &color);
|
|
rta->pixels_processed++;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
CANVAS_Canvas *CAMERA_render(const CAMERA_Camera *camera, const WORLD_World *world) {
|
|
assert(camera);
|
|
assert(world);
|
|
CANVAS_Canvas *canvas = CANVAS_new(camera->hsize, camera->vsize);
|
|
long thread_count = UTILITIES_get_thread_count();
|
|
LOGGER_log(LOGGER_INFO, "Rendering with %i thread(s)...\n", thread_count);
|
|
pthread_t threads[thread_count];
|
|
render_thread_args args[thread_count];
|
|
for (uint i = 0; i < thread_count; i++) {
|
|
args[i].camera = camera;
|
|
args[i].world = world;
|
|
args[i].canvas = canvas;
|
|
args[i].thread_num = i;
|
|
args[i].total_threads = thread_count;
|
|
args[i].pixels_processed = 0;
|
|
pthread_create(&threads[i], NULL, &render_thread, &args[i]);
|
|
}
|
|
|
|
const uint total_pixels = camera->vsize * camera->hsize;
|
|
uint pixel_count = 0;
|
|
while (pixel_count != total_pixels) {
|
|
pixel_count = 0;
|
|
for (uint i = 0; i < thread_count; i++) {
|
|
pixel_count += args[i].pixels_processed;
|
|
}
|
|
const uint percent = pixel_count * 100 / total_pixels;
|
|
LOGGER_log(LOGGER_INFO, "Rendering %u%% complete (%u/%u)\n", percent, pixel_count, total_pixels);
|
|
UTILITIES_msleep(500);
|
|
}
|
|
for (uint i = 0; i < thread_count; i++) {
|
|
pthread_join(threads[i], NULL);
|
|
}
|
|
return canvas;
|
|
}
|
|
|
|
struct camera_context {
|
|
unsigned int width, height;
|
|
double field_of_view;
|
|
TUPLES_Point from, to;
|
|
TUPLES_Vector up;
|
|
|
|
bool found_width, found_height, found_field_of_view,
|
|
found_from, found_to, found_up;
|
|
};
|
|
|
|
static void parse_camera_map_entry(char *key, char *value, void *context) {
|
|
assert(key);
|
|
assert(value);
|
|
assert(context);
|
|
struct camera_context *cc = (struct camera_context *)context;
|
|
|
|
if (strcmp("up", key) == 0) {
|
|
YAMLLOADER_parse_vector(value, &cc->up);
|
|
cc->found_up = true;
|
|
} else if (strcmp("from", key) == 0) {
|
|
YAMLLOADER_parse_point(value, &cc->from);
|
|
cc->found_from = true;
|
|
} else if (strcmp("to", key) == 0) {
|
|
YAMLLOADER_parse_point(value, &cc->to);
|
|
cc->found_to = true;
|
|
} else if (strcmp("width", key) == 0) {
|
|
YAMLLOADER_parse_uint(value, &cc->width);
|
|
cc->found_width = true;
|
|
} else if (strcmp("height", key) == 0) {
|
|
YAMLLOADER_parse_uint(value, &cc->height);
|
|
cc->found_height = true;
|
|
} else if (strcmp("field-of-view", key) == 0 ||
|
|
strcmp("field_of_view", key) == 0) {
|
|
YAMLLOADER_parse_double(value, &cc->field_of_view);
|
|
cc->found_field_of_view = true;
|
|
} else {
|
|
LOGGER_log(LOGGER_WARN, "Unrecognized map key while parsing camera: %s", key);
|
|
}
|
|
}
|
|
|
|
static bool validate_camera_context(struct camera_context *cc) {
|
|
assert(cc);
|
|
return cc->found_width && cc->found_height && cc->found_field_of_view && cc->found_from
|
|
&& cc->found_to && cc->found_up;
|
|
}
|
|
|
|
CAMERA_Camera *CAMERA_parse_camera(char *data) {
|
|
assert(data);
|
|
|
|
struct camera_context cc = {0};
|
|
|
|
YAMLLOADER_parse_map_entries(data, parse_camera_map_entry, &cc);
|
|
|
|
if (!validate_camera_context(&cc)) {
|
|
LOGGER_log(LOGGER_WARN, "Failed to parse camera. width, height, field of view, from, to, and up all must be specified");
|
|
return NULL;
|
|
}
|
|
|
|
CAMERA_Camera *camera = CAMERA_new(cc.width, cc.height, cc.field_of_view);
|
|
MATRIX_Matrix* camera_transform = CAMERA_view_transform(&cc.from, &cc.to, &cc.up);
|
|
CAMERA_set_transform(camera, camera_transform);
|
|
MATRIX_delete(camera_transform);
|
|
|
|
return camera;
|
|
}
|
|
|