1324 lines
35 KiB
C++
1324 lines
35 KiB
C++
|
|
#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(©, 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);
|
||
|
|
}
|