384 lines
11 KiB
C
384 lines
11 KiB
C
#include "wavefrontobj.h"
|
|
#include "exceptions.h"
|
|
#include "tuples.h"
|
|
#include "triangle.h"
|
|
#include "logger.h"
|
|
|
|
#include <assert.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <math.h>
|
|
|
|
|
|
typedef struct parse_state {
|
|
unsigned int line_number;
|
|
GROUP_Group *current_group;
|
|
char *line;
|
|
} parse_state;
|
|
|
|
bool check_for_slash_char(char *str) { return (strchr(str, '/') != NULL); }
|
|
|
|
typedef struct entry {
|
|
unsigned int vertex;
|
|
unsigned int normal;
|
|
bool vertex_found;
|
|
bool normal_found;
|
|
} entry;
|
|
|
|
static entry parse_face_entry(char *str, parse_state *state) {
|
|
assert(str);
|
|
entry e;
|
|
e.normal_found = false;
|
|
e.vertex_found = false;
|
|
|
|
if (check_for_slash_char(str)) {
|
|
// should always be three values. we don't need the second since we don't implement textures yet
|
|
char *first = strsep(&str, "/");
|
|
strsep(&str, "/");
|
|
char *third = strsep(&str, "/");
|
|
|
|
char *err = NULL;
|
|
e.vertex = strtoul(first, &err, 10);
|
|
if (first == err) {
|
|
LOGGER_log(LOGGER_ERROR, "Error parsing face vertex, line(%u), err(%s)\n", state->line_number, err);
|
|
} else {
|
|
e.vertex_found = true;
|
|
};
|
|
|
|
e.normal = strtoul(third, &err, 10);
|
|
if (third == err) {
|
|
LOGGER_log(LOGGER_ERROR, "Error parsing face normal, line(%u), err(%s)\n", state->line_number, err);
|
|
} else {
|
|
e.normal_found = true;
|
|
};
|
|
} else {
|
|
char *err = NULL;
|
|
e.vertex = strtoul(str, &err, 10);
|
|
if (str == err) {
|
|
LOGGER_log(LOGGER_ERROR, "Error parsing face vertex (no slashes), line(%u), err(%s)\n", state->line_number, err);
|
|
} else {
|
|
e.vertex_found = true;
|
|
};
|
|
}
|
|
return e;
|
|
}
|
|
|
|
static void parse_face(WAVEFRONTOBJ_Obj *obj, parse_state *state) {
|
|
assert(obj);
|
|
assert(state);
|
|
char *line = state->line;
|
|
assert(line);
|
|
|
|
char *saveptr = NULL;
|
|
char *delim = " ";
|
|
strtok_r(line, delim, &saveptr);
|
|
|
|
char *first_vertex = strtok_r(NULL, delim, &saveptr);
|
|
entry first_e = parse_face_entry(first_vertex, state);
|
|
if (!first_e.vertex_found) {
|
|
LOGGER_log(LOGGER_ERROR, "Error parsing first vertex on line (%u)\n", state->line_number);
|
|
return;
|
|
}
|
|
unsigned int v1ndx = first_e.vertex;
|
|
unsigned int n1ndx = 0;
|
|
if (first_e.normal_found) {
|
|
n1ndx = first_e.normal;
|
|
}
|
|
|
|
char *second_vertex = strtok_r(NULL, delim, &saveptr);
|
|
entry second_e = parse_face_entry(second_vertex, state);
|
|
if (!second_e.vertex_found) {
|
|
LOGGER_log(LOGGER_ERROR, "Error parsing second vertex on line (%u)\n", state->line_number);
|
|
return;
|
|
}
|
|
unsigned int v2ndx = second_e.vertex;
|
|
unsigned int n2ndx = 0;
|
|
if (second_e.normal_found) {
|
|
n2ndx = second_e.normal;
|
|
}
|
|
|
|
CEXCEPTION_T e = E_NO_ERROR;
|
|
TUPLES_Point *first, *previous, *current;
|
|
TUPLES_Vector *first_n = NULL;
|
|
TUPLES_Vector *previous_n = NULL;
|
|
TUPLES_Vector *current_n = NULL;
|
|
Try {
|
|
first = WAVEFRONTOBJ_get_vertex(obj, v1ndx);
|
|
previous = WAVEFRONTOBJ_get_vertex(obj, v2ndx);
|
|
if (n1ndx) {
|
|
first_n = WAVEFRONTOBJ_get_normal(obj, n1ndx);
|
|
}
|
|
if (n2ndx) {
|
|
previous_n = WAVEFRONTOBJ_get_normal(obj, n2ndx);
|
|
}
|
|
|
|
char *current_vertex;
|
|
while ((current_vertex = strtok_r(NULL, delim, &saveptr))) {
|
|
entry current_vertex_e = parse_face_entry(current_vertex, state);
|
|
if (!current_vertex_e.vertex_found) {
|
|
LOGGER_log(LOGGER_ERROR, "Error parsing vertex on line (%u)\n", state->line_number);
|
|
return;
|
|
}
|
|
|
|
current = WAVEFRONTOBJ_get_vertex(obj, current_vertex_e.vertex);
|
|
if (current_vertex_e.normal_found) {
|
|
current_n = WAVEFRONTOBJ_get_normal(obj, current_vertex_e.normal);
|
|
TRIANGLE_SmoothTriangle *t = TRIANGLE_new_smooth_from_points(first, previous, current, first_n, previous_n, current_n);
|
|
GROUP_add_child(state->current_group, t);
|
|
previous_n = current_n;
|
|
} else {
|
|
TRIANGLE_Triangle *t = TRIANGLE_new_from_points(first, previous, current);
|
|
GROUP_add_child(state->current_group, t);
|
|
}
|
|
|
|
obj->triangle_count++;
|
|
previous = current;
|
|
}
|
|
}
|
|
Catch(e) {
|
|
if (e == E_INDEX_OUT_OF_BOUNDS) {
|
|
LOGGER_log(LOGGER_ERROR, "Error retrieving vertex due to %s, line(%u)\n", EXCEPTIONS_strings[e], state->line_number);
|
|
LOGGER_log(LOGGER_DEBUG, "Line: %s\n", state->line);
|
|
} else {
|
|
LOGGER_log(LOGGER_ERROR, "Unexpected error %s\n", EXCEPTIONS_strings[e]);
|
|
}
|
|
}
|
|
obj->face_count++;
|
|
}
|
|
|
|
static void parse_normal(WAVEFRONTOBJ_Obj *obj, parse_state *state) {
|
|
assert(obj);
|
|
assert(state);
|
|
char *line = state->line;
|
|
assert(line);
|
|
|
|
char *err = NULL;
|
|
char *saveptr = NULL;
|
|
char *delim = " ";
|
|
strtok_r(line, delim, &saveptr);
|
|
|
|
char *xstr = strtok_r(NULL, delim, &saveptr);
|
|
double x = strtod(xstr, &err);
|
|
if (xstr == err)
|
|
LOGGER_log(LOGGER_ERROR, "Error parsing normal x value, line(%u), err(%s)\n", state->line_number, err);
|
|
|
|
char *ystr = strtok_r(NULL, delim, &saveptr);
|
|
double y = strtod(ystr, &err);
|
|
if (ystr == err)
|
|
LOGGER_log(LOGGER_ERROR, "Error parsing normal y value, line(%u), err(%s)\n", state->line_number, err);
|
|
|
|
char *zstr = strtok_r(NULL, delim, &saveptr);
|
|
double z = strtod(zstr, &err);
|
|
if (zstr == err)
|
|
LOGGER_log(LOGGER_ERROR, "Error parsing normal z value, line(%u), err(%s)\n", state->line_number, err);
|
|
|
|
LIST_add(obj->normals, TUPLES_new_vector(x, y, z));
|
|
obj->normal_count++;
|
|
}
|
|
|
|
static void parse_vertex(WAVEFRONTOBJ_Obj *obj, parse_state *state) {
|
|
assert(obj);
|
|
assert(state);
|
|
char *line = state->line;
|
|
assert(line);
|
|
|
|
char *err = NULL;
|
|
char *saveptr = NULL;
|
|
char *delim = " ";
|
|
strtok_r(line, delim, &saveptr);
|
|
|
|
char *xstr = strtok_r(NULL, delim, &saveptr);
|
|
if (!xstr) {
|
|
LOGGER_log(LOGGER_ERROR, "Error parsing vertex x value - no token found, line(%u), err(%s)\n", state->line_number, err);
|
|
return;
|
|
}
|
|
double x = strtod(xstr, &err);
|
|
if (xstr == err) {
|
|
LOGGER_log(LOGGER_ERROR, "Error parsing vertex x value, line(%u), err(%s)\n", state->line_number, err);
|
|
return;
|
|
}
|
|
|
|
char *ystr = strtok_r(NULL, delim, &saveptr);
|
|
if (!ystr) {
|
|
LOGGER_log(LOGGER_ERROR, "Error parsing vertex y value - no token found, line(%u), err(%s)\n", state->line_number, err);
|
|
return;
|
|
}
|
|
double y = strtod(ystr, &err);
|
|
if (ystr == err) {
|
|
LOGGER_log(LOGGER_ERROR, "Error parsing vertex y value, line(%u), err(%s)\n", state->line_number, err);
|
|
return;
|
|
}
|
|
|
|
char *zstr = strtok_r(NULL, delim, &saveptr);
|
|
if (!zstr) {
|
|
LOGGER_log(LOGGER_ERROR, "Error parsing vertex z value - no token found, line(%u), err(%s)\n", state->line_number, err);
|
|
return;
|
|
}
|
|
double z = strtod(zstr, &err);
|
|
if (zstr == err) {
|
|
LOGGER_log(LOGGER_ERROR, "Error parsing vertex z value, line(%u), err(%s)\n", state->line_number, err);
|
|
return;
|
|
}
|
|
|
|
LIST_add(obj->vertices, TUPLES_new_point(x, y, z));
|
|
obj->vertex_count++;
|
|
}
|
|
|
|
static void parse_group(WAVEFRONTOBJ_Obj *obj, parse_state *state) {
|
|
assert(obj);
|
|
assert(state);
|
|
char *line = state->line;
|
|
assert(line);
|
|
|
|
char *saveptr = NULL;
|
|
char *delim = " \n";
|
|
strtok_r(line, delim, &saveptr);
|
|
|
|
char *group_name = strtok_r(NULL, delim, &saveptr);
|
|
LOGGER_log(LOGGER_DEBUG, "Found group (%s)\n", group_name);
|
|
|
|
GROUP_Group *g = GROUP_new();
|
|
state->current_group = g;
|
|
GROUP_add_child(obj->default_group, g);
|
|
obj->group_count++;
|
|
}
|
|
|
|
static void parse_stream(WAVEFRONTOBJ_Obj *obj, FILE *stream) {
|
|
char *line = NULL;
|
|
size_t len = 0;
|
|
ssize_t nread;
|
|
parse_state state = (parse_state){.current_group = obj->default_group, .line_number = 1, .line = NULL};
|
|
while ((nread = getline(&line, &len, stream)) != -1) {
|
|
if (nread > 0) {
|
|
state.line = line;
|
|
switch (line[0]) {
|
|
case 'g':
|
|
parse_group(obj, &state);
|
|
break;
|
|
case 'v':
|
|
if (line[1] == 'n') {
|
|
parse_normal(obj, &state);
|
|
} else if (line[1] == ' ') {
|
|
parse_vertex(obj, &state);
|
|
}
|
|
break;
|
|
case 'f':
|
|
parse_face(obj, &state);
|
|
break;
|
|
default:
|
|
obj->ignored_lines++;
|
|
}
|
|
state.line_number++;
|
|
}
|
|
}
|
|
free(line);
|
|
LOGGER_log(LOGGER_INFO, "Found groups(%u) faces(%u) triangles(%u) vertexes(%u) normals(%u)\n", obj->group_count, obj->face_count, obj->triangle_count,
|
|
obj->vertex_count, obj->normal_count);
|
|
}
|
|
|
|
void WAVEFRONTOBJ_init(WAVEFRONTOBJ_Obj *obj, FILE *file) {
|
|
assert(obj);
|
|
assert(file);
|
|
obj->ignored_lines = 0;
|
|
obj->group_count = 0;
|
|
obj->triangle_count = 0;
|
|
obj->vertex_count = 0;
|
|
obj->face_count = 0;
|
|
obj->normal_count = 0;
|
|
obj->vertices = LIST_new(obj->vertices);
|
|
obj->normals = LIST_new(obj->normals);
|
|
obj->default_group = GROUP_new();
|
|
parse_stream(obj, file);
|
|
}
|
|
|
|
WAVEFRONTOBJ_Obj *WAVEFRONTOBJ_parse_obj_stream(FILE *file) {
|
|
assert(file);
|
|
WAVEFRONTOBJ_Obj *obj = malloc(sizeof(WAVEFRONTOBJ_Obj));
|
|
if (!obj) {
|
|
Throw(E_MALLOC_FAILED);
|
|
}
|
|
WAVEFRONTOBJ_init(obj, file);
|
|
return obj;
|
|
}
|
|
|
|
static bool delete_tuple(void *vertex, void *context) {
|
|
assert(vertex);
|
|
UNUSED(context);
|
|
|
|
TUPLES_Point *p = (TUPLES_Point *)vertex;
|
|
TUPLES_delete(p);
|
|
return true;
|
|
}
|
|
|
|
WAVEFRONTOBJ_Obj *WAVEFRONTOBJ_parse_file_by_name(char *filename) {
|
|
assert(filename);
|
|
FILE *objfile = fopen(filename, "r");
|
|
if (!objfile) {
|
|
char *cwd = getcwd(NULL, 0);
|
|
LOGGER_log(LOGGER_ERROR, "fopen failed to open (%s), cwd is (%s)\n", filename, cwd);
|
|
free(cwd);
|
|
Throw(E_FILE_FAILED);
|
|
} else {
|
|
LOGGER_log(LOGGER_INFO, "Opened file %s for parsing as obj\n", filename);
|
|
}
|
|
WAVEFRONTOBJ_Obj *obj = WAVEFRONTOBJ_parse_obj_stream(objfile);
|
|
fclose(objfile);
|
|
return obj;
|
|
}
|
|
|
|
void WAVEFRONTOBJ_destroy(WAVEFRONTOBJ_Obj *obj) {
|
|
assert(obj);
|
|
LIST_iterator(obj->vertices, delete_tuple, NULL);
|
|
LIST_delete(obj->vertices);
|
|
LIST_iterator(obj->normals, delete_tuple, NULL);
|
|
LIST_delete(obj->normals);
|
|
GROUP_delete(obj->default_group);
|
|
}
|
|
|
|
void WAVEFRONTOBJ_delete(WAVEFRONTOBJ_Obj *obj) {
|
|
assert(obj);
|
|
WAVEFRONTOBJ_destroy(obj);
|
|
free(obj);
|
|
}
|
|
|
|
void WAVEFRONTOBJ_normalize(WAVEFRONTOBJ_Obj *obj) {
|
|
assert(obj);
|
|
BOUND_Box box;
|
|
SHAPE_bounds_of(obj->default_group, &box);
|
|
double dx = box.max.x - box.min.x;
|
|
double dy = box.max.y - box.min.y;
|
|
double dz = box.max.z - box.min.z;
|
|
LOGGER_log(LOGGER_DEBUG, "Obj normalization calculated dx(%.2f) dy(%.2f) dz(%.2f)\n", dx, dy, dz);
|
|
if (!isfinite(dx)) {
|
|
LOGGER_log(LOGGER_ERROR, "Obj normalization calculate non-finite dx\n");
|
|
return;
|
|
}
|
|
if (!isfinite(dy)) {
|
|
LOGGER_log(LOGGER_ERROR, "Obj normalization calculate non-finite dy\n");
|
|
return;
|
|
}
|
|
if (!isfinite(dz)) {
|
|
LOGGER_log(LOGGER_ERROR, "Obj normalization calculate non-finite dz\n");
|
|
return;
|
|
}
|
|
|
|
TUPLES_Vector delta;
|
|
TUPLES_Point origin;
|
|
TUPLES_init_point(&origin, 0, 0, 0);
|
|
TUPLES_Point middle_of_object;
|
|
TUPLES_init_point(&middle_of_object, box.min.x + (dx / 2), box.min.y, box.min.z + (dz / 2));
|
|
TUPLES_subtract(&delta, &origin, &middle_of_object);
|
|
MATRIX_Matrix *translation = MATRIX_new_translation(delta.x, delta.y, delta.z);
|
|
char *translation_str = MATRIX_to_string(translation);
|
|
LOGGER_log(LOGGER_DEBUG, "Obj normalization calculated translation matrix: %s", translation_str);
|
|
free(translation_str);
|
|
|
|
double scalefactor = 1 / (UTILITIES_max(dx, dy, dz));
|
|
LOGGER_log(LOGGER_DEBUG, "Obj normalization calculated scalefactor: %.2f\n", scalefactor);
|
|
MATRIX_Matrix *scale_matrix = MATRIX_new_scaling(scalefactor, scalefactor, scalefactor);
|
|
MATRIX_Matrix *new_transform = MATRIX_multiply_many(scale_matrix, translation);
|
|
GROUP_set_transform(obj->default_group, new_transform);
|
|
MATRIX_delete_all(scale_matrix, new_transform, translation);
|
|
}
|