#include "wavefront.h" #include #include #include #include #include #include "../debug/logger.h" wf_alloc_t wf_alloc = malloc; wf_realloc_t wf_realloc = realloc; wf_free_t wf_free = free; #define WF_FREE_OPTIONAL(x) { if(x) wf_free(x); } struct wf_parser { char *data; u64 size; u64 cursor; u64 line; u64 line_start; const char *name; // Used for debug messages. It's not needed otherwise. }; enum wf_token_type { WF_NONE, WF_STRING, WF_INTEGER, WF_REAL, WF_SLASH, WF_EOF }; #define CASE_STRING(x) case x: return #x; const char *wf_token_type_string(wf_token_type tt) { switch(tt) { CASE_STRING(WF_NONE) CASE_STRING(WF_STRING) CASE_STRING(WF_INTEGER) CASE_STRING(WF_REAL) CASE_STRING(WF_SLASH) CASE_STRING(WF_EOF) default: return "UNKNOWN"; } } struct wf_string { char *data; u32 size; }; struct wf_token { wf_token_type type; union { wf_string string; s64 integer; f64 real; }; u64 line; u64 line_start; u64 start; u64 size; }; #define WF_PRINT_ERROR(parser, message, ...) \ { \ u64 column = (parser)->cursor - (parser)->line_start + 1; \ \ u64 tmp_cursor = (parser)->cursor; \ while(tmp_cursor < (parser)->size && !wf_isnewline((parser)->data[tmp_cursor])) \ tmp_cursor++; \ u64 line_lenght = tmp_cursor - (parser)->line_start; \ LOG(LOG_ERROR, "Wavefront parsing error %s %lu,%lu: \n" message "\nLine: \"%.*s\"", (parser)->name, (parser)->line, column, ##__VA_ARGS__, (int)line_lenght, (parser)->data + (parser)->line_start); \ } #define WF_PRINT_ERROR_TOKEN(parser, token, message, ...) \ { \ u64 column = (token)->start - (token)->line_start + 1; \ \ u64 tmp_cursor = (token)->line_start; \ while(tmp_cursor < (parser)->size && !wf_isnewline((parser)->data[tmp_cursor])) \ tmp_cursor++; \ u64 line_lenght = tmp_cursor - (token)->line_start; \ LOG(LOG_ERROR, "Wavefront parsing error %s %lu,%lu: \n" message "\nLine: \"%.*s\"", (parser)->name, (token)->line, column, ##__VA_ARGS__, (int)line_lenght, (parser)->data + (token)->line_start); \ } static void wf_parser_init(wf_parser *p, char *data, u64 size, const char *name) { p->data = data; p->size = size; p->cursor = 0; p->line = 1; p->line_start = 0; p->name = name; } static bool wf_isnewline(char c) { // 10 '\n' line feed --- 13 '\r' carriage return // For simplicity, we consider only \n as newline. // \n will be eaten as normal whitespace. return c == 10; } static void wf_eat_newline(wf_parser *p) { assert(p->cursor < p->size); assert(wf_isnewline(p->data[p->cursor])); p->cursor++; while(p->cursor < p->size) { char c = p->data[p->cursor]; if(c != 13) break; p->cursor++; } p->line++; p->line_start = p->cursor; } static void wf_skip_to_next_line(wf_parser *p) { while(p->cursor < p->size) { char c = p->data[p->cursor]; if(wf_isnewline(c)) { wf_eat_newline(p); break; } p->cursor++; } } // Puts cursor at the newline (end of comment, not start of next token) static void wf_eat_comment(wf_parser *p) { assert(p->cursor < p->size); assert(p->data[p->cursor] == '#'); wf_skip_to_next_line(p); } static void wf_eat_whitespace_and_comments(wf_parser *p) { while(p->cursor < p->size) { char c = p->data[p->cursor]; if(c == '#') // Comment { wf_eat_comment(p); continue; } if(wf_isnewline(c)) { wf_eat_newline(p); continue; } if(!isspace(c)) break; p->cursor++; } } static s64 wf_parse_integer(wf_parser *p) { assert(p->cursor < p->size); assert(isdigit(p->data[p->cursor])); s64 integer = 0; while(p->cursor < p->size) { char c = p->data[p->cursor]; if(!isdigit(c)) break; integer = integer * 10 + (c - '0'); p->cursor++; } return integer; } static f64 wf_parse_decimal_part(wf_parser *p) { assert(p->cursor < p->size); assert(isdigit(p->data[p->cursor])); f64 decimal = 0; f64 factor = 0.1; while(p->cursor < p->size) { char c = p->data[p->cursor]; if(!isdigit(c)) break; decimal += factor * (c - '0'); factor *= 0.1; p->cursor++; } return decimal; } static wf_string wf_parse_string(wf_parser *p) { assert(p->cursor < p->size); wf_string s; s.data = p->data + p->cursor; s.size = 0; while(p->cursor < p->size) { char c = p->data[p->cursor]; if(isspace(c) || c == '#') break; p->cursor++; } s.size = p->data + p->cursor - s.data; return s; } #define WF_PARSE_FORCE_STRING 1 static wf_token wf_parse_next(wf_parser *p, u32 flags = 0) { wf_token token; token.type = WF_NONE; wf_eat_whitespace_and_comments(p); if(p->cursor >= p->size) { token.type = WF_EOF; return token; } token.line = p->line; token.line_start = p->line_start; token.start = p->cursor; token.size = 0; char c = p->data[p->cursor]; if(flags & WF_PARSE_FORCE_STRING) { token.type = WF_STRING; token.string = wf_parse_string(p); } else if(c == '-' || isdigit(c)) // Integer or real { s64 sign = 1; if(c == '-') { sign = -1; p->cursor++; if(p->cursor >= p->size) { WF_PRINT_ERROR(p, "End of file reached while parsing number."); token.type = WF_NONE; return token; } c = p->data[p->cursor]; if(!isdigit(c)) { WF_PRINT_ERROR(p, "Unexpected character after '-' while parsing number."); token.type = WF_NONE; return token; } } // Parse integer part first, then if it's a real, we convert it and add the decimal part token.type = WF_INTEGER; token.integer = sign * wf_parse_integer(p); if(p->cursor < p->size) { if(p->data[p->cursor] == '.') // Real { token.type = WF_REAL; token.real = token.integer; p->cursor++; // We are parsing a real number. We should have at least 1 digit after the '.' if(p->cursor >= p->size) { WF_PRINT_ERROR(p, "End of file reached while parsing real number."); token.type = WF_NONE; return token; } if(!isdigit(p->data[p->cursor])) { WF_PRINT_ERROR(p, "Unexpected character after '.' while parsing real number."); token.type = WF_NONE; return token; } token.real += sign * wf_parse_decimal_part(p); } } } else if(isalpha(c)) // Text { token.type = WF_STRING; token.string = wf_parse_string(p); } else if(c == '/') { token.type = WF_SLASH; p->cursor++; } else { WF_PRINT_ERROR(p, "Unrecognized token (starting with \'%c\').", c); } token.size = p->cursor - token.start; return token; } static wf_token wf_peek_next(wf_parser *p, u32 flags = 0) { wf_parser copy = *p; return wf_parse_next(©, flags); } static bool wf_string_equal(wf_string *wfs, const char *s) { u64 i = 0; while(i < wfs->size && s[i] != 0) { if(wfs->data[i] != s[i]) return false; i++; } if(i != wfs->size || s[i] != 0) return false; return true; } bool wf_parse_obj(char *data, u64 size, const char *name, wf_obj_file *return_obj) { wf_obj_file result; wf_object *curr_object; wf_parser p; wf_token token; wf_parser_init(&p, data, size, name); { u64 size = strlen(name); result.name = (char*)wf_alloc(size + 1); memcpy(result.name, name, size); result.name[size] = 0; // zero-terminated string } { result.mtllib_names = NULL; result.mtllib_names_count = 0; result.objects = NULL; result.objects_count = 0; } wf_size vertices_capacity; wf_size texture_coords_capacity; wf_size normals_capacity; wf_size faces_capacity; { result.objects_count = 1; result.objects = (wf_object*)wf_alloc(1 * sizeof(wf_object)); curr_object = result.objects; curr_object->name = NULL; curr_object->material_name = NULL; vertices_capacity = 32; texture_coords_capacity = 32; normals_capacity = 32; faces_capacity = 32; curr_object->vertices = (v4*)wf_alloc(vertices_capacity * sizeof(v4)); curr_object->texture_coords = (v3*)wf_alloc(texture_coords_capacity * sizeof(v3)); curr_object->normals = (v3*)wf_alloc(normals_capacity * sizeof(v3)); curr_object->faces = (wf_face*)wf_alloc(faces_capacity * sizeof(wf_face)); curr_object->vertices_count = 0; curr_object->texture_coords_count = 0; curr_object->normals_count = 0; curr_object->faces_count = 0; } token = wf_parse_next(&p); while(token.type != WF_EOF && token.type != WF_NONE) { // At the start of the line we expect to find a string/command, // followed by its parameters. if(token.type != WF_STRING) { WF_PRINT_ERROR_TOKEN(&p, &token, "Expected string at start of line. Found: %s \"%.*s\"", wf_token_type_string(token.type), (int)token.size, p.data + token.start); goto error_cleanup_obj; } if(wf_string_equal(&token.string, "o")) // Object name { bool prev_object_empty = true; prev_object_empty &= (curr_object->name == NULL); prev_object_empty &= (curr_object->material_name == NULL); prev_object_empty &= (curr_object->vertices_count == 0); prev_object_empty &= (curr_object->texture_coords_count == 0); prev_object_empty &= (curr_object->normals_count == 0); prev_object_empty &= (curr_object->faces_count == 0); if(prev_object_empty) { // Use previously allocated space } else { // Resize the old object to the smallest size possible if(vertices_capacity != curr_object->vertices_count) curr_object->vertices = (v4*)wf_realloc(curr_object->vertices, curr_object->vertices_count * sizeof(v4)); if(texture_coords_capacity != curr_object->texture_coords_count) curr_object->texture_coords = (v3*)wf_realloc(curr_object->texture_coords, curr_object->texture_coords_count * sizeof(v3)); if(normals_capacity != curr_object->normals_count) curr_object->normals = (v3*)wf_realloc(curr_object->normals, curr_object->normals_count * sizeof(v3)); if(faces_capacity != curr_object->faces_count) curr_object->faces = (wf_face*)wf_realloc(curr_object->faces, curr_object->faces_count * sizeof(wf_face)); // Allocate space for new object result.objects_count++; result.objects = (wf_object*)wf_realloc(result.objects, result.objects_count * sizeof(wf_object)); curr_object = result.objects + (result.objects_count - 1); curr_object->name = NULL; curr_object->material_name = NULL; vertices_capacity = 32; texture_coords_capacity = 32; normals_capacity = 32; faces_capacity = 32; curr_object->vertices = (v4*)wf_alloc(vertices_capacity * sizeof(v4)); curr_object->texture_coords = (v3*)wf_alloc(texture_coords_capacity * sizeof(v3)); curr_object->normals = (v3*)wf_alloc(normals_capacity * sizeof(v3)); curr_object->faces = (wf_face*)wf_alloc(faces_capacity * sizeof(wf_face)); curr_object->vertices_count = 0; curr_object->texture_coords_count = 0; curr_object->normals_count = 0; curr_object->faces_count = 0; } token = wf_parse_next(&p, WF_PARSE_FORCE_STRING); if(token.type != WF_STRING) { WF_PRINT_ERROR_TOKEN(&p, &token, "Expected object name after \"o\". Found: \"%.*s\".", (int)token.size, p.data + token.start); goto error_cleanup_obj; } curr_object->name = (char*)wf_alloc(token.string.size + 1); memcpy(curr_object->name, token.string.data, token.string.size); curr_object->name[token.string.size] = 0; // zero-terminated string wf_skip_to_next_line(&p); } else if(wf_string_equal(&token.string, "v")) // Vertices { // A vertex is specified by 3 or 4 real numbers (x y z [w]) v4 vertex; bool error = false; if(!error) { token = wf_parse_next(&p); if(token.type == WF_REAL) vertex.x = token.real; else error = true; } if(!error) { token = wf_parse_next(&p); if(token.type == WF_REAL) vertex.y = token.real; else error = true; } if(!error) { token = wf_parse_next(&p); if(token.type == WF_REAL) vertex.z = token.real; else error = true; } // w optional if(!error) { wf_token peeked = wf_peek_next(&p); if(peeked.type == WF_REAL) { token = wf_parse_next(&p); vertex.w = token.real; } else { vertex.w = 1.0; // Default value if omitted } } if(error) { // @Correctness: This prints the line after the one that has the actual error (instead of the correct one). WF_PRINT_ERROR_TOKEN(&p, &token, "Expected 3 or 4 real numbers when reading vertex data. Found: \"%.*s\".", (int)token.size, p.data + token.start); goto error_cleanup_obj; } // @Correctness: Right now, we skip everything we find after reading the last component. // We should report an error if there is any data before the next line. wf_skip_to_next_line(&p); if(vertices_capacity < curr_object->vertices_count + 1) { vertices_capacity *= 2; curr_object->vertices = (v4*)wf_realloc(curr_object->vertices, vertices_capacity * sizeof(v4)); } curr_object->vertices[curr_object->vertices_count] = vertex; curr_object->vertices_count++; } else if(wf_string_equal(&token.string, "vt")) // Texture coordinates { // A texture coordinate is specified by 1, 2 or 3 real numbers (u [v] [w]) v3 texture_coord; bool error = false; if(!error) { token = wf_parse_next(&p); if(token.type == WF_REAL) texture_coord.u = token.real; else error = true; } // v optional if(!error) { wf_token peeked = wf_peek_next(&p); if(peeked.type == WF_REAL) { token = wf_parse_next(&p); texture_coord.v = token.real; } else { texture_coord.v = 0.0; // Default value if omitted texture_coord.w_ = 0.0; // Default value if omitted } } // w optional if(!error) { wf_token peeked = wf_peek_next(&p); if(peeked.type == WF_REAL) { token = wf_parse_next(&p); texture_coord.w_ = token.real; } else { texture_coord.w_ = 0.0; // Default value if omitted } } if(error) { // @Correctness: This prints the line after the one that has the actual error (instead of the correct one). WF_PRINT_ERROR_TOKEN(&p, &token, "Expected 1, 2 or 3 real numbers when reading texture coordinates. Found: \"%.*s\".", (int)token.size, p.data + token.start); goto error_cleanup_obj; } // @Correctness: Right now, we skip everything we find after reading the last component. // We should report an error if there is any data before the next line. wf_skip_to_next_line(&p); if(texture_coords_capacity < curr_object->texture_coords_count + 1) { texture_coords_capacity *= 2; curr_object->texture_coords = (v3*)wf_realloc(curr_object->texture_coords, texture_coords_capacity * sizeof(v3)); } curr_object->texture_coords[curr_object->texture_coords_count] = texture_coord; curr_object->texture_coords_count++; } else if(wf_string_equal(&token.string, "vn")) // Normals { // A normal is specified by 3 real numbers (x y z) v3 normal; bool error = false; if(!error) { token = wf_parse_next(&p); if(token.type == WF_REAL) normal.x = token.real; else error = true; } if(!error) { token = wf_parse_next(&p); if(token.type == WF_REAL) normal.y = token.real; else error = true; } if(!error) { token = wf_parse_next(&p); if(token.type == WF_REAL) normal.z = token.real; else error = true; } if(error) { // @Correctness: This prints the line after the one that has the actual error (instead of the correct one). WF_PRINT_ERROR_TOKEN(&p, &token, "Expected 3 real numbers when reading normal data. Found: \"%.*s\".", (int)token.size, p.data + token.start); goto error_cleanup_obj; } wf_skip_to_next_line(&p); if(normals_capacity < curr_object->normals_count + 1) { normals_capacity *= 2; curr_object->normals = (v3*)wf_realloc(curr_object->normals, normals_capacity * sizeof(v3)); } curr_object->normals[curr_object->normals_count] = normal; curr_object->normals_count++; } else if(wf_string_equal(&token.string, "f")) // Faces { // A face is specified by 3 or more vertices/texture_coords/normal. Only the vertex index is mandatory. // For now we read only 3, ignoring the rest. wf_face face; u32 count = 0; token = wf_peek_next(&p); while(token.type == WF_INTEGER) { token = wf_parse_next(&p); // We peeked _token_, so now we have to actually advance the parser. wf_face_index fi; fi.vertex = 0; fi.texture = 0; fi.normal = 0; fi.vertex = token.integer; // Check for "/" separator (optional) token = wf_peek_next(&p); if(token.type == WF_SLASH) { token = wf_parse_next(&p); // Check for texture coordinate index (optional) token = wf_peek_next(&p); if(token.type != WF_INTEGER && token.type != WF_SLASH) { WF_PRINT_ERROR_TOKEN(&p, &token, "Expected texture coordinate index after vertex index. Found: \"%.*s\".", (int)token.size, p.data + token.start); goto error_cleanup_obj; } if(token.type == WF_INTEGER) { token = wf_parse_next(&p); fi.texture = token.integer; token = wf_peek_next(&p); } // Check for "/" separator (optional) if(token.type == WF_SLASH) { token = wf_parse_next(&p); // Check for normal index (optional) token = wf_peek_next(&p); if(token.type != WF_INTEGER) { WF_PRINT_ERROR_TOKEN(&p, &token, "Expected normal index after texture coordinate index. Found: \"%.*s\".", (int)token.size, p.data + token.start); goto error_cleanup_obj; } if(!isdigit(p.data[p.cursor])) { WF_PRINT_ERROR(&p, "Expected normal index after texture coordinate index. Found nothing."); goto error_cleanup_obj; } token = wf_parse_next(&p); fi.normal = token.integer; } } if(count < 3) face.indices[count] = fi; count++; token = wf_peek_next(&p); } if(count < 3) { WF_PRINT_ERROR(&p, "Expected 3 or more indices numbers when reading face data. Found only %u.", count); goto error_cleanup_obj; } // @Correctness: Right now, we skip everything we find after reading the last component. // We should report an error if there is any data before the next line. wf_skip_to_next_line(&p); if(faces_capacity < curr_object->faces_count + 1) { faces_capacity *= 2; curr_object->faces = (wf_face*)wf_realloc(curr_object->faces, faces_capacity * sizeof(wf_face)); } curr_object->faces[curr_object->faces_count] = face; curr_object->faces_count++; } else if(wf_string_equal(&token.string, "mtllib")) // Material library file name { token = wf_parse_next(&p, WF_PARSE_FORCE_STRING); if(token.type != WF_STRING) { WF_PRINT_ERROR_TOKEN(&p, &token, "Expected file name after \"mtllib\". Found: \"%.*s\".", (int)token.size, p.data + token.start); goto error_cleanup_obj; } if(result.mtllib_names == NULL) result.mtllib_names = (char**)wf_alloc((result.mtllib_names_count + 1) * sizeof(char*)); else result.mtllib_names = (char**)wf_realloc(result.mtllib_names, (result.mtllib_names_count + 1) * sizeof(char*)); result.mtllib_names[result.mtllib_names_count] = (char*)wf_alloc(token.string.size + 1); memcpy(result.mtllib_names[result.mtllib_names_count], token.string.data, token.string.size); result.mtllib_names[result.mtllib_names_count][token.string.size] = 0; // zero-terminated result.mtllib_names_count++; wf_skip_to_next_line(&p); } else if(wf_string_equal(&token.string, "usemtl")) // Material name { token = wf_parse_next(&p, WF_PARSE_FORCE_STRING); if(token.type != WF_STRING) { WF_PRINT_ERROR_TOKEN(&p, &token, "Expected material name after \"usemtl\". Found: \"%.*s\".", (int)token.size, p.data + token.start); goto error_cleanup_obj; } curr_object->material_name = (char*)wf_alloc(token.string.size + 1); memcpy(curr_object->material_name, token.string.data, token.string.size); curr_object->material_name[token.string.size] = 0; // zero-terminated string wf_skip_to_next_line(&p); } else { WF_PRINT_ERROR_TOKEN(&p, &token, "Unrecognized command \"%.*s\". Skipping to next line.", (int)token.string.size, token.string.data); wf_skip_to_next_line(&p); } token = wf_parse_next(&p); } // Resize the object to the smallest size possible { if(vertices_capacity != curr_object->vertices_count) curr_object->vertices = (v4*)wf_realloc(curr_object->vertices, curr_object->vertices_count * sizeof(v4)); if(texture_coords_capacity != curr_object->texture_coords_count) curr_object->texture_coords = (v3*)wf_realloc(curr_object->texture_coords, curr_object->texture_coords_count * sizeof(v3)); if(normals_capacity != curr_object->normals_count) curr_object->normals = (v3*)wf_realloc(curr_object->normals, curr_object->normals_count * sizeof(v3)); if(faces_capacity != curr_object->faces_count) curr_object->faces = (wf_face*)wf_realloc(curr_object->faces, curr_object->faces_count * sizeof(wf_face)); } *return_obj = result; return true; error_cleanup_obj: wf_cleanup_obj(&result); return false; } bool wf_parse_mtl(char *data, u64 size, const char *name, wf_mtl_file *return_mtl) { wf_mtl_file result; wf_material *curr_material; wf_parser p; wf_token token; wf_parser_init(&p, data, size, name); { u64 size = strlen(name); result.name = (char*)wf_alloc(size + 1); memcpy(result.name, name, size); result.name[size] = 0; // zero-terminated string } { result.materials = NULL; result.materials_count = 0; } token = wf_parse_next(&p); while(token.type != WF_EOF && token.type != WF_NONE) { // At the start of the line we expect to find a string/command, // followed by its parameters. if(token.type != WF_STRING) { WF_PRINT_ERROR_TOKEN(&p, &token, "Expected string at start of line. Found: %s \"%.*s\"", wf_token_type_string(token.type), (int)token.size, p.data + token.start); goto error_cleanup_mtl; } if(wf_string_equal(&token.string, "newmtl")) // Material name { // Initialize new material { result.materials_count++; if(result.materials == NULL) result.materials = (wf_material*)wf_alloc(result.materials_count * sizeof(wf_material)); else result.materials = (wf_material*)wf_realloc(result.materials, result.materials_count * sizeof(wf_material)); curr_material = result.materials + (result.materials_count - 1); } { curr_material->name = NULL; curr_material->Ka = v3{0, 0, 0}; curr_material->Kd = v3{0, 0, 0}; curr_material->Ke = v3{0, 0, 0}; curr_material->Ks = v3{0, 0, 0}; curr_material->Ns = 0; curr_material->d = 1.0; curr_material->Ni = 1.0; curr_material->illum = 0; curr_material->map_Ka = NULL; curr_material->map_Kd = NULL; curr_material->map_Ke = NULL; curr_material->map_Ks = NULL; curr_material->map_Ns = NULL; curr_material->map_d = NULL; curr_material->bump = NULL; curr_material->disp = NULL; curr_material->decal = NULL; } token = wf_parse_next(&p, WF_PARSE_FORCE_STRING); if(token.type != WF_STRING) { WF_PRINT_ERROR_TOKEN(&p, &token, "Expected material name after \"newmtl\". Found: \"%.*s\".", (int)token.size, p.data + token.start); goto error_cleanup_mtl; } curr_material->name = (char*)wf_alloc(token.string.size + 1); memcpy(curr_material->name, token.string.data, token.string.size); curr_material->name[token.string.size] = 0; // zero-terminated string wf_skip_to_next_line(&p); } else if(wf_string_equal(&token.string, "Ka")) { // A color is specified by 3 real numbers (x y z) v3 color; bool error = false; if(!error) { token = wf_parse_next(&p); if(token.type == WF_REAL) color.r = token.real; else error = true; } if(!error) { token = wf_parse_next(&p); if(token.type == WF_REAL) color.g = token.real; else error = true; } if(!error) { token = wf_parse_next(&p); if(token.type == WF_REAL) color.b = token.real; else error = true; } if(error) { // @Correctness: This prints the line after the one that has the actual error (instead of the correct one). WF_PRINT_ERROR_TOKEN(&p, &token, "Expected 3 real numbers when reading normal data. Found: \"%.*s\".", (int)token.size, p.data + token.start); goto error_cleanup_mtl; } wf_skip_to_next_line(&p); curr_material->Ka = color; } else if(wf_string_equal(&token.string, "Kd")) { // A color is specified by 3 real numbers (x y z) v3 color; bool error = false; if(!error) { token = wf_parse_next(&p); if(token.type == WF_REAL) color.r = token.real; else error = true; } if(!error) { token = wf_parse_next(&p); if(token.type == WF_REAL) color.g = token.real; else error = true; } if(!error) { token = wf_parse_next(&p); if(token.type == WF_REAL) color.b = token.real; else error = true; } if(error) { // @Correctness: This prints the line after the one that has the actual error (instead of the correct one). WF_PRINT_ERROR_TOKEN(&p, &token, "Expected 3 real numbers when reading normal data. Found: \"%.*s\".", (int)token.size, p.data + token.start); goto error_cleanup_mtl; } wf_skip_to_next_line(&p); curr_material->Kd = color; } else if(wf_string_equal(&token.string, "Ke")) { // A color is specified by 3 real numbers (x y z) v3 color; bool error = false; if(!error) { token = wf_parse_next(&p); if(token.type == WF_REAL) color.r = token.real; else error = true; } if(!error) { token = wf_parse_next(&p); if(token.type == WF_REAL) color.g = token.real; else error = true; } if(!error) { token = wf_parse_next(&p); if(token.type == WF_REAL) color.b = token.real; else error = true; } if(error) { // @Correctness: This prints the line after the one that has the actual error (instead of the correct one). WF_PRINT_ERROR_TOKEN(&p, &token, "Expected 3 real numbers when reading normal data. Found: \"%.*s\".", (int)token.size, p.data + token.start); goto error_cleanup_mtl; } wf_skip_to_next_line(&p); curr_material->Ke = color; } else if(wf_string_equal(&token.string, "Ks")) { // A color is specified by 3 real numbers (x y z) v3 color; bool error = false; if(!error) { token = wf_parse_next(&p); if(token.type == WF_REAL) color.r = token.real; else error = true; } if(!error) { token = wf_parse_next(&p); if(token.type == WF_REAL) color.g = token.real; else error = true; } if(!error) { token = wf_parse_next(&p); if(token.type == WF_REAL) color.b = token.real; else error = true; } if(error) { // @Correctness: This prints the line after the one that has the actual error (instead of the correct one). WF_PRINT_ERROR_TOKEN(&p, &token, "Expected 3 real numbers when reading normal data. Found: \"%.*s\".", (int)token.size, p.data + token.start); goto error_cleanup_mtl; } wf_skip_to_next_line(&p); curr_material->Ks = color; } else if(wf_string_equal(&token.string, "Ns")) { token = wf_parse_next(&p); if(token.type != WF_REAL) { WF_PRINT_ERROR_TOKEN(&p, &token, "Expected real number after \"Ks\". Found: \"%.*s\".", (int)token.size, p.data + token.start); goto error_cleanup_mtl; } curr_material->Ns = token.real; wf_skip_to_next_line(&p); } else if(wf_string_equal(&token.string, "d")) { token = wf_parse_next(&p); if(token.type != WF_REAL) { WF_PRINT_ERROR_TOKEN(&p, &token, "Expected real number after \"d\". Found: \"%.*s\".", (int)token.size, p.data + token.start); goto error_cleanup_mtl; } curr_material->d = token.real; wf_skip_to_next_line(&p); } else if(wf_string_equal(&token.string, "Ni")) { token = wf_parse_next(&p); if(token.type != WF_REAL) { WF_PRINT_ERROR_TOKEN(&p, &token, "Expected real number after \"Ni\". Found: \"%.*s\".", (int)token.size, p.data + token.start); goto error_cleanup_mtl; } curr_material->Ni = token.real; wf_skip_to_next_line(&p); } else if(wf_string_equal(&token.string, "illum")) { token = wf_parse_next(&p); if(token.type != WF_INTEGER) { WF_PRINT_ERROR_TOKEN(&p, &token, "Expected integer number after \"illum\". Found: \"%.*s\".", (int)token.size, p.data + token.start); goto error_cleanup_mtl; } curr_material->illum = token.integer; wf_skip_to_next_line(&p); } else if(wf_string_equal(&token.string, "map_Ka")) { token = wf_parse_next(&p, WF_PARSE_FORCE_STRING); if(token.type != WF_STRING) { WF_PRINT_ERROR_TOKEN(&p, &token, "Expected texture name after \"map_Ka\". Found: \"%.*s\".", (int)token.size, p.data + token.start); goto error_cleanup_mtl; } curr_material->map_Ka = (char*)wf_alloc(token.string.size + 1); memcpy(curr_material->map_Ka, token.string.data, token.string.size); curr_material->map_Ka[token.string.size] = 0; // zero-terminated string wf_skip_to_next_line(&p); } else if(wf_string_equal(&token.string, "map_Kd")) { token = wf_parse_next(&p, WF_PARSE_FORCE_STRING); if(token.type != WF_STRING) { WF_PRINT_ERROR_TOKEN(&p, &token, "Expected texture name after \"map_Kd\". Found: \"%.*s\".", (int)token.size, p.data + token.start); goto error_cleanup_mtl; } curr_material->map_Kd = (char*)wf_alloc(token.string.size + 1); memcpy(curr_material->map_Kd, token.string.data, token.string.size); curr_material->map_Kd[token.string.size] = 0; // zero-terminated string wf_skip_to_next_line(&p); } else if(wf_string_equal(&token.string, "map_Ke")) { token = wf_parse_next(&p, WF_PARSE_FORCE_STRING); if(token.type != WF_STRING) { WF_PRINT_ERROR_TOKEN(&p, &token, "Expected texture name after \"map_Ke\". Found: \"%.*s\".", (int)token.size, p.data + token.start); goto error_cleanup_mtl; } curr_material->map_Ke = (char*)wf_alloc(token.string.size + 1); memcpy(curr_material->map_Ke, token.string.data, token.string.size); curr_material->map_Ke[token.string.size] = 0; // zero-terminated string wf_skip_to_next_line(&p); } else if(wf_string_equal(&token.string, "map_Ks")) { token = wf_parse_next(&p, WF_PARSE_FORCE_STRING); if(token.type != WF_STRING) { WF_PRINT_ERROR_TOKEN(&p, &token, "Expected texture name after \"map_Ks\". Found: \"%.*s\".", (int)token.size, p.data + token.start); goto error_cleanup_mtl; } curr_material->map_Ks = (char*)wf_alloc(token.string.size + 1); memcpy(curr_material->map_Ks, token.string.data, token.string.size); curr_material->map_Ks[token.string.size] = 0; // zero-terminated string wf_skip_to_next_line(&p); } else if(wf_string_equal(&token.string, "map_Ns")) { token = wf_parse_next(&p, WF_PARSE_FORCE_STRING); if(token.type != WF_STRING) { WF_PRINT_ERROR_TOKEN(&p, &token, "Expected texture name after \"map_Ns\". Found: \"%.*s\".", (int)token.size, p.data + token.start); goto error_cleanup_mtl; } curr_material->map_Ns = (char*)wf_alloc(token.string.size + 1); memcpy(curr_material->map_Ns, token.string.data, token.string.size); curr_material->map_Ns[token.string.size] = 0; // zero-terminated string wf_skip_to_next_line(&p); } else if(wf_string_equal(&token.string, "map_d")) { token = wf_parse_next(&p, WF_PARSE_FORCE_STRING); if(token.type != WF_STRING) { WF_PRINT_ERROR_TOKEN(&p, &token, "Expected texture name after \"map_d\". Found: \"%.*s\".", (int)token.size, p.data + token.start); goto error_cleanup_mtl; } curr_material->map_d = (char*)wf_alloc(token.string.size + 1); memcpy(curr_material->map_d, token.string.data, token.string.size); curr_material->map_d[token.string.size] = 0; // zero-terminated string wf_skip_to_next_line(&p); } else if(wf_string_equal(&token.string, "bump")) { token = wf_parse_next(&p, WF_PARSE_FORCE_STRING); if(token.type != WF_STRING) { WF_PRINT_ERROR_TOKEN(&p, &token, "Expected texture name after \"bump\". Found: \"%.*s\".", (int)token.size, p.data + token.start); goto error_cleanup_mtl; } curr_material->bump = (char*)wf_alloc(token.string.size + 1); memcpy(curr_material->bump, token.string.data, token.string.size); curr_material->bump[token.string.size] = 0; // zero-terminated string wf_skip_to_next_line(&p); } else if(wf_string_equal(&token.string, "disp")) { token = wf_parse_next(&p, WF_PARSE_FORCE_STRING); if(token.type != WF_STRING) { WF_PRINT_ERROR_TOKEN(&p, &token, "Expected texture name after \"disp\". Found: \"%.*s\".", (int)token.size, p.data + token.start); goto error_cleanup_mtl; } curr_material->disp = (char*)wf_alloc(token.string.size + 1); memcpy(curr_material->disp, token.string.data, token.string.size); curr_material->disp[token.string.size] = 0; // zero-terminated string wf_skip_to_next_line(&p); } else if(wf_string_equal(&token.string, "decal")) { token = wf_parse_next(&p, WF_PARSE_FORCE_STRING); if(token.type != WF_STRING) { WF_PRINT_ERROR_TOKEN(&p, &token, "Expected texture name after \"decal\". Found: \"%.*s\".", (int)token.size, p.data + token.start); goto error_cleanup_mtl; } curr_material->decal = (char*)wf_alloc(token.string.size + 1); memcpy(curr_material->decal, token.string.data, token.string.size); curr_material->decal[token.string.size] = 0; // zero-terminated string wf_skip_to_next_line(&p); } else { WF_PRINT_ERROR_TOKEN(&p, &token, "Unrecognized command \"%.*s\". Skipping to next line.", (int)token.string.size, token.string.data); wf_skip_to_next_line(&p); } token = wf_parse_next(&p); } *return_mtl = result; return true; error_cleanup_mtl: wf_cleanup_mtl(&result); return false; } void wf_cleanup_obj(wf_obj_file *obj_file) { // @Performance: Batch allocation for fast alloc/free for(wf_size i = 0; i < obj_file->objects_count; i++) { WF_FREE_OPTIONAL(obj_file->objects[i].name); wf_free(obj_file->objects[i].vertices); wf_free(obj_file->objects[i].texture_coords); wf_free(obj_file->objects[i].normals); wf_free(obj_file->objects[i].faces); WF_FREE_OPTIONAL(obj_file->objects[i].material_name); } for(wf_size i = 0; i < obj_file->mtllib_names_count; i++) wf_free(obj_file->mtllib_names[i]); WF_FREE_OPTIONAL(obj_file->mtllib_names); wf_free(obj_file->name); wf_free(obj_file->objects); } void wf_cleanup_mtl(wf_mtl_file *mtl_file) { // @Performance: Batch allocation for fast alloc/free for(wf_size i = 0; i < mtl_file->materials_count; i++) { WF_FREE_OPTIONAL(mtl_file->materials[i].name); WF_FREE_OPTIONAL(mtl_file->materials[i].map_Ka); WF_FREE_OPTIONAL(mtl_file->materials[i].map_Kd); WF_FREE_OPTIONAL(mtl_file->materials[i].map_Ke); WF_FREE_OPTIONAL(mtl_file->materials[i].map_Ks); WF_FREE_OPTIONAL(mtl_file->materials[i].map_Ns); WF_FREE_OPTIONAL(mtl_file->materials[i].map_d); WF_FREE_OPTIONAL(mtl_file->materials[i].bump); WF_FREE_OPTIONAL(mtl_file->materials[i].disp); WF_FREE_OPTIONAL(mtl_file->materials[i].decal); } wf_free(mtl_file->name); wf_free(mtl_file->materials); }