Files

1324 lines
35 KiB
C++
Raw Permalink Normal View History

2023-09-26 19:40:16 +02:00
#include "wavefront.h"
#include <assert.h>
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#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(&copy, 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);
}