raytracer-c/module_raytracer/yamlloader.c
2023-10-07 19:39:43 -04:00

391 lines
11 KiB
C

#include "yamlloader.h"
#include "configuration.h"
#include <assert.h>
#include <ctype.h>
#include <cube.h>
#include <plane.h>
#include <logger.h>
#include <string.h>
#include <world.h>
/* ***********************************
*
* What follows is some terrible code
* to parse yaml.
*
* I wanted to hack something together
* and avoid pulling in any dependencies,
* so here it is. It is likely very unsafe
* and will fail in all but the simplest
* of cases.
*
* ***********************************/
// trim functions from https://stackoverflow.com/a/1431206
static char *ltrim(char *s)
{
while(isspace(*s)) s++;
return s;
}
static char *rtrim(char *s)
{
char* back = s + strlen(s);
while(isspace(*--back) && back > s);
*(back+1) = '\0';
return s;
}
static char *trim(char *s)
{
return rtrim(ltrim(s));
}
static void parse_map_entry(char *data, char **key, char **value) {
assert(data);
assert(key);
assert(value);
char *saveptr = NULL;
char *colon_delim = ":";
*key = strtok_r(data, colon_delim, &saveptr);
if (!*key) {
LOGGER_log(LOGGER_ERROR, "Failed parsing - colon delim not found (%s)\n", data);
Throw(E_PARSE_FAILED);
} else {
*key = trim(*key);
}
*value = strtok_r(NULL, colon_delim, &saveptr);
if (!*value) {
LOGGER_log(LOGGER_ERROR, "Failed parsing - colon delim not found (%s)\n", data);
Throw(E_PARSE_FAILED);
} else {
*value = trim(*value);
}
}
static void parse_tuple_values(char* data, double *x, double *y, double *z) {
assert(data);
assert(x);
assert(y);
assert(z);
//skip opening [
char *skip_opening_bracket = strchr(data, '[');
if (skip_opening_bracket) {
data = skip_opening_bracket + 1;
}
size_t data_len = strlen(data);
if (data[data_len - 1] != ']') {
//handle parse error
LOGGER_log(LOGGER_ERROR, "Parse error - expected ] at end of data in parse_point while parsing(%s)\n", data);
Throw(E_PARSE_FAILED);
}
data[data_len - 1] = '\0';
char *err = NULL;
char *saveptr = NULL;
char *comma_delim = ",";
char *x_str = strtok_r(data, comma_delim, &saveptr);
if (!x_str) {
LOGGER_log(LOGGER_ERROR, "Error parsing vertex x value while parsing(%s)\n", data);
Throw(E_PARSE_FAILED);
}
*x = strtod(x_str, &err);
char *y_str = strtok_r(NULL, comma_delim, &saveptr);
if (!y_str) {
LOGGER_log(LOGGER_ERROR, "Error parsing vertex y value while parsing(%s)\n", data);
Throw(E_PARSE_FAILED);
}
*y = strtod(y_str, &err);
char *z_str = strtok_r(NULL, comma_delim, &saveptr);
if (!z_str) {
LOGGER_log(LOGGER_ERROR, "Error parsing vertex z value while parsing(%s)\n", data);
Throw(E_PARSE_FAILED);
}
*z = strtod(z_str, &err);
}
void YAMLLOADER_parse_color(char *data, TUPLES_Color *color) {
assert(data);
assert(color);
double x, y, z;
parse_tuple_values(data, &x, &y, &z);
TUPLES_init_color(color, x, y, z);
}
void YAMLLOADER_parse_point(char *data, TUPLES_Point *point) {
assert(data);
assert(point);
double x, y, z;
parse_tuple_values(data, &x, &y, &z);
TUPLES_init_point(point, x, y, z);
}
void YAMLLOADER_parse_vector(char *data, TUPLES_Vector *vector) {
assert(data);
assert(vector);
double x, y, z;
parse_tuple_values(data, &x, &y, &z);
TUPLES_init_vector(vector, x, y, z);
}
void YAMLLOADER_parse_bool(char *data, bool *value) {
assert(data);
assert(value);
*value = (strcasecmp("true", data) == 0);
}
void YAMLLOADER_parse_uint(char *data, unsigned int *value) {
assert(data);
assert(value);
//TODO check err
char *err = NULL;
*value = strtoul(data, &err, 10);
}
void YAMLLOADER_parse_double(char *data, double *value) {
assert(data);
assert(value);
//TODO check err
char *err = NULL;
*value = strtod(data, &err);
}
void YAMLLOADER_parse_map_entries(char *data, void (*process_entry)(char *key, char *value, void *context), void *context) {
assert(data);
assert(process_entry);
char *current_line = data;
while(current_line) {
char *next_line = strchr(current_line, '\n');
if (next_line) {
*next_line = '\0';
}
if (*current_line) {
char *key = NULL;
char *value = NULL;
parse_map_entry(current_line, &key, &value);
process_entry(key, value, context);
}
current_line = next_line ? (next_line + 1) : NULL;
}
}
MATRIX_Matrix *YAMLLOADER_parse_transform_entry(char *data) {
assert(data);
//skip opening [
char *skip_opening_bracket = strchr(data, '[');
if (skip_opening_bracket) {
data = skip_opening_bracket + 1;
}
char *saveptr = NULL;
char *comma_delim = ",";
char *type_str = strtok_r(data, comma_delim, &saveptr);
if (!type_str) {
LOGGER_log(LOGGER_ERROR, "Error parsing transform entry type (scale, translate, etc) value: %s\n", data);
Throw(E_PARSE_FAILED);
}
type_str = trim(type_str);
MATRIX_Matrix *mat = NULL;
double x, y, z;
if (strcmp("scale", type_str) == 0) {
parse_tuple_values(saveptr, &x, &y, &z);
mat = MATRIX_new_scaling(x, y, z);
} else if (strcmp("translate", type_str) == 0 ) {
parse_tuple_values(saveptr, &x, &y, &z);
mat = MATRIX_new_translation(x, y, z);
} else if (strcmp("rotate-x", type_str) == 0){
YAMLLOADER_parse_double(saveptr, &x);
mat = MATRIX_new_rotation_x(x);
} else if (strcmp("rotate-y", type_str) == 0){
YAMLLOADER_parse_double(saveptr, &y);
mat = MATRIX_new_rotation_y(y);
} else if (strcmp("rotate-z", type_str) == 0){
YAMLLOADER_parse_double(saveptr, &z);
mat = MATRIX_new_rotation_z(z);
} else {
LOGGER_log(LOGGER_ERROR, "Error parsing transform entry type (scale, translate, etc) unknown value: %s\n", type_str);
Throw(E_PARSE_FAILED);
}
return mat;
}
MATRIX_Matrix *YAMLLOADER_parse_transform(char *data) {
assert(data);
MATRIX_Matrix *mat = MATRIX_new_identity(4);
char *current_line = data;
while(current_line && (strncmp("material:", ltrim(current_line), 9) != 0)) {
char *next_line = strchr(current_line, '\n');
if (next_line) {
*next_line = '\0';
}
if (*current_line) {
MATRIX_Matrix *line_matrix = YAMLLOADER_parse_transform_entry(current_line);
MATRIX_Matrix *tmp = mat;
mat = MATRIX_multiply(line_matrix, mat);
MATRIX_delete_all(tmp, line_matrix);
}
current_line = next_line ? (next_line + 1) : NULL;
}
return mat;
}
void YAMLLOADER_parse_shape_info(SHAPE_Shape *shape, char *buffer) {
assert(shape);
assert(buffer);
if (!*trim(buffer)) {
//do nothing if we are handed nothing but whitespace
return;
}
char *next_line = NULL;
char *transform_txt = strstr(buffer, "transform:");
char *material_txt = strstr(buffer, "material:");
if (transform_txt && (next_line = strchr(transform_txt, ':'))) {
MATRIX_Matrix* transform = YAMLLOADER_parse_transform(next_line + 1);
SHAPE_set_transform(shape, transform);
MATRIX_delete(transform);
}
if (material_txt && (next_line = strchr(material_txt, ':'))) {
MATERIAL_Material *material = MATERIAL_parse_material(next_line + 1);
SHAPE_set_material(shape, material);
MATERIAL_delete(material);
}
/* TODO What are the failure modes here?
* else {
LOGGER_log(LOGGER_ERROR, "Error parsing shape information (%s)\n", buffer);
Throw(E_PARSE_FAILED);
return;
} */
}
static void create_object(char *type, char *buffer, CONFIGURATION_Config *config) {
assert(type);
assert(buffer);
assert(config);
if (strcmp("light", type) == 0) {
LIGHTS_Light *light = WORLD_get_light(config->world);
if (light) {
LIGHTS_delete(light);
}
WORLD_set_light(config->world, LIGHTS_parse_light(buffer));
} else if (strcmp("camera", type) == 0) {
config->camera = CAMERA_parse_camera(buffer);
} else if (strcmp("cube", type) == 0) {
CUBE_Cube *cube = CUBE_new();
YAMLLOADER_parse_shape_info(cube, buffer);
WORLD_add_object(config->world, cube);
} else if (strcmp("sphere", type) == 0) {
SPHERE_Sphere *sphere = SPHERE_new();
YAMLLOADER_parse_shape_info(sphere, buffer);
WORLD_add_object(config->world, sphere);
} else if (strcmp("plane", type) == 0) {
PLANE_Plane *plane = PLANE_new();
YAMLLOADER_parse_shape_info(plane, buffer);
WORLD_add_object(config->world, plane);
} else {
LOGGER_log(LOGGER_ERROR, "Unknown type found when parsing: %s\n", type);
free(buffer);
Throw(E_PARSE_FAILED);
return;
}
free(buffer);
}
static char *handle_array_item(char *first_entry, char *data, CONFIGURATION_Config *config) {
assert(first_entry);
assert(data);
assert(config);
// make sure we are handling an add (only thing supported now)
char *first_function, *first_type;
parse_map_entry(first_entry, &first_function, &first_type);
if (!first_function || !first_type || (strcmp("- add", first_function) != 0)) {
if (first_type) {
LOGGER_log(LOGGER_ERROR, "Unknown yaml function: %s\n", first_function);
} else {
LOGGER_log(LOGGER_ERROR, "Failed parsing yaml: %s\n", first_entry);
}
Throw(E_PARSE_FAILED);
return NULL;
}
char *buffer = NULL;
UTILITIES_sasprintf(buffer, "\n");
char *current_line = data;
while(current_line) {
char *next_line = strchr(current_line, '\n');
if (*current_line) {
char *trimmed_current_line = ltrim(current_line);
if (strncmp("- add", trimmed_current_line, 5) == 0) {
//reached next object, parse the buffer and move on
create_object(first_type, buffer, config);
return current_line;
} else {
if (next_line) {
*next_line = '\0';
}
}
if (*trimmed_current_line == '#') {
//found a comment
} else {
//add line to buffer to be interpreted by specific parser (ie for light or cube or ...)
UTILITIES_sasprintf(buffer, "%s\n%s", buffer, current_line);
}
}
current_line = next_line ? (next_line + 1) : NULL;
}
//hit the end of data
create_object(first_type, buffer, config);
return current_line;
}
CONFIGURATION_Config *YAMLLOADER_parse(char *data) {
assert(data);
CONFIGURATION_Config *config = malloc(sizeof(CONFIGURATION_Config));
if (!config) {
Throw(E_MALLOC_FAILED);
}
//TODO this will leak on a throw of E_PARSE_FAILED in deeper code
config->world = WORLD_new(NULL);
char *current_line = data;
while(current_line) {
char *next_line = strchr(current_line, '\n');
if (next_line) {
*next_line = '\0';
}
if (*current_line) {
char *first_char = ltrim(current_line);
if (*first_char == '#') {
//NOP - it's a comment
} else if (*first_char == '-') {
if (next_line) {
current_line = handle_array_item(current_line, next_line + 1, config);
continue;
} else {
LOGGER_log(LOGGER_ERROR, "Error parsing (%s), expected multiple line object\n", current_line);
Throw(E_PARSE_FAILED);
}
}
}
current_line = next_line ? (next_line + 1) : NULL;
}
return config;
}