Created
September 5, 2021 12:40
-
-
Save skejeton/cf1832be14d0269de38507a029677b1b to your computer and use it in GitHub Desktop.
obj parser
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <stdio.h> | |
#include <string.h> | |
#include <stdbool.h> | |
#include <stdint.h> | |
#include <assert.h> | |
#include <stdlib.h> | |
typedef struct { | |
const char *src; | |
} _obj_Parser; | |
#define VERTEX_COUNT (1 << 15) | |
#define INDEX_COUNT (1 << 16) | |
typedef struct { | |
uint16_t v; // Vertex idx | |
uint16_t vt; // Texture idx | |
uint16_t vn; // Normal idx | |
} obj_Triplet; | |
// The result for parsing | |
typedef struct { | |
size_t vertex_count; | |
float *vertices; | |
size_t uv_count; | |
float *uvs; | |
size_t normal_count; | |
float *normals; | |
size_t index_count; | |
obj_Triplet *indices; | |
} obj_Result; | |
typedef struct { | |
float *vertices; | |
uint16_t *indices; | |
} obj_Unrolled; | |
#define Parser _obj_Parser | |
#define chk _obj_chk | |
#define verify _obj_verify | |
#define oneof _obj_oneof | |
#define next _obj_next | |
static void _obj_index(obj_Result *self, obj_Triplet index) { | |
assert(self->index_count <= INDEX_COUNT && "Obj: Index buffer overflow"); | |
self->indices[self->index_count++] = index; | |
} | |
static void _obj_vertex(obj_Result *self, float vertex) { | |
assert(self->vertex_count <= VERTEX_COUNT && "Obj: Vertex buffer overflow"); | |
self->vertices[self->vertex_count++] = vertex; | |
} | |
static void _obj_normal(obj_Result *self, float vertex) { | |
assert(self->normal_count <= VERTEX_COUNT && "Obj: Normal vertex buffer overflow"); | |
self->normals[self->normal_count++] = vertex; | |
} | |
static void _obj_uv(obj_Result *self, float vertex) { | |
assert(self->uv_count <= VERTEX_COUNT && "Obj: Uv vertex buffer overflow"); | |
self->uvs[self->uv_count++] = vertex; | |
} | |
static bool _obj_chk(Parser *self, char c) { | |
if (*self->src == 0) return false; | |
return *self->src == c; | |
} | |
static bool _obj_verify(Parser *self, const char *s) { | |
if (strncmp(self->src, s, strlen(s)) == 0) { | |
self->src += strlen(s); | |
return true; | |
} | |
return false; | |
} | |
static bool _obj_oneof(Parser *self, const char *chars) { | |
for (;*chars; chars += 1) | |
if (chk(self, *chars)) return true; | |
return false; | |
} | |
static char _obj_next(Parser *self) { | |
char val = *self->src; | |
if (*self->src != 0) | |
self->src += 1; | |
return val; | |
} | |
static bool _obj_empty(Parser *self) { | |
const char *begin = self->src; | |
while (oneof(self, "\n\r\t ")) next(self); | |
return begin != self->src; | |
} | |
static bool _obj_comment(Parser *self) { | |
const char *begin = self->src; | |
if (chk(self, '#')) { | |
do next(self); while (!chk(self, '\n')); | |
} | |
return begin != self->src; | |
} | |
static void _obj_skip(Parser *self) { | |
while (_obj_empty(self) || _obj_comment(self)); | |
} | |
static bool _obj_isfloat(Parser *self) { | |
_obj_skip(self); | |
char *end = (char*)self->src; | |
// Todo(bumbread): don't waste time parsing the float, | |
// just forward scan for the '.' symbol, or use state-based | |
// automation. | |
strtod(self->src, &end); | |
return end != self->src; | |
} | |
static bool _obj_isint(Parser *self) { | |
_obj_skip(self); | |
char *end = (char*)self->src; | |
strtol(self->src, &end, 10); | |
return end != self->src; | |
} | |
static float _obj_float(Parser *self) { | |
_obj_skip(self); | |
char *end = (char*)self->src; | |
float r = strtof(self->src, &end); | |
assert(end != self->src && "Obj: Supplied empty float"); | |
self->src = end; | |
return r; | |
} | |
static uint16_t _obj_int(Parser *self) { | |
_obj_skip(self); | |
char *end = (char*)self->src; | |
long r = strtol(self->src, &end, 10); | |
assert(end != self->src && "Obj: Supplied empty int"); | |
self->src = end; | |
return (uint16_t)r; | |
} | |
static obj_Triplet _obj_triplet(Parser *self) { | |
uint16_t v = 0, t = 0, n = 0; | |
v = _obj_int(self); | |
if (chk(self, '/')) { | |
next(self); | |
if (chk(self, '/')) goto normal; | |
t = _obj_int(self); | |
if (chk(self, '/')) { | |
normal: | |
next(self); | |
n = _obj_int(self); | |
} | |
} | |
return (obj_Triplet) { v, t, n }; | |
} | |
static void _obj_cmd_vertex(Parser *self, obj_Result *res, void via(obj_Result *res, float vertex), size_t n) { | |
for (size_t i = 0; i < n; i += 1) | |
via(res, _obj_float(self)); | |
assert(!_obj_isfloat(self) && "Obj: Passed too much parameters to vertex"); | |
} | |
static void _obj_cmd_index(Parser *self, obj_Result *res) { | |
_obj_index(res, _obj_triplet(self)); | |
_obj_index(res, _obj_triplet(self)); | |
_obj_index(res, _obj_triplet(self)); | |
assert(!_obj_isint(self) && "Obj: More than 3 indices unsupported, export with \"Triangulate faces\" flag in blender"); | |
} | |
__attribute__((unused)) | |
static void obj_dump(obj_Result *self) { | |
printf("-- Vertices --\n"); | |
for (size_t i = 0; i < self->vertex_count; i += 1) { | |
printf("%g", self->vertices[i]); | |
if (i != self->vertex_count-1) printf(", "); | |
else printf("\n"); | |
} | |
printf("-- Normals --\n"); | |
for (size_t i = 0; i < self->normal_count; i += 1) { | |
printf("%g", self->normals[i]); | |
if (i != self->normal_count-1) printf(", "); | |
else printf("\n"); | |
} | |
printf("-- Uvs --\n"); | |
for (size_t i = 0; i < self->uv_count; i += 1) { | |
printf("%g", self->uvs[i]); | |
if (i != self->uv_count-1) printf(", "); | |
else printf("\n"); | |
} | |
printf("-- Indices --\n"); | |
for (size_t i = 0; i < self->index_count; i += 1) { | |
printf("%d/%d/%d", self->indices[i].v, self->indices[i].vt, self->indices[i].vn); | |
if (i != self->index_count-1) printf(", "); | |
else printf("\n"); | |
} | |
} | |
static void _obj_ignore(Parser *self, const char *cmd) { | |
printf("Obj: %s command, ignoring\n", cmd); | |
while (*self->src && !chk(self, '\n')) { | |
next(self); | |
} | |
} | |
__attribute__((unused)) | |
static obj_Result obj_parse(const char *src) { | |
Parser self = { .src = src }; | |
obj_Result res = { | |
.indices = (obj_Triplet*)malloc(INDEX_COUNT*sizeof(obj_Triplet)), | |
.vertices = (float*)malloc(VERTEX_COUNT*sizeof(float)), | |
.normals = (float*)malloc(VERTEX_COUNT*sizeof(float)), | |
.uvs = (float*)malloc(VERTEX_COUNT*sizeof(float)), | |
}; | |
while (*self.src) { | |
_obj_skip(&self); | |
if (verify(&self, "mtllib")) | |
_obj_ignore(&self, "mtllib"); | |
else if (verify(&self, "o")) | |
_obj_ignore(&self, "o"); | |
else if (verify(&self, "vt")) | |
_obj_cmd_vertex(&self, &res, _obj_uv, 2); | |
else if (verify(&self, "vn")) | |
_obj_cmd_vertex(&self, &res, _obj_normal, 3); | |
else if (verify(&self, "vp")) | |
_obj_ignore(&self, "vp"); | |
else if (verify(&self, "v")) | |
_obj_cmd_vertex(&self, &res, _obj_vertex, 3); | |
else if (verify(&self, "usemtl")) | |
_obj_ignore(&self, "usemtl"); | |
else if (verify(&self, "s")) | |
_obj_ignore(&self, "s"); | |
else if (verify(&self, "f")) | |
_obj_cmd_index(&self, &res); | |
else if (*self.src) | |
assert(false && "Obj: Supplied invalid command"); | |
} | |
return res; | |
} | |
__attribute__((unused)) | |
static void obj_dispose(obj_Result *res) { | |
free(res->indices); | |
free(res->uvs); | |
free(res->normals); | |
free(res->vertices); | |
} | |
static inline bool _obj_triplet_eq(obj_Triplet a, obj_Triplet b) { | |
return a.v == b.v && a.vn == b.vn && a.vt == b.vt; | |
} | |
typedef struct { | |
obj_Triplet chk; | |
size_t index; | |
} _obj_MemoNode; | |
static _obj_MemoNode _obj_memo[1 << 12] = { 0 }; | |
static inline size_t _obj_memo_insert(obj_Triplet triplet, size_t index) { | |
size_t hash = (triplet.v+triplet.vn*32+triplet.vt*128*(1<<12))&((1 << 12)-1); | |
if (_obj_memo[hash].index == 0) { | |
_obj_memo[hash] = (_obj_MemoNode) { triplet, index+1 }; | |
} | |
return index; | |
} | |
static inline size_t _obj_memo_get(obj_Triplet triplet) { | |
size_t hash = (triplet.v+triplet.vn*32+triplet.vt*128*(1<<12))&((1 << 12)-1); | |
if (_obj_memo[hash].index != 0 && _obj_triplet_eq(_obj_memo[hash].chk, triplet)) | |
return _obj_memo[hash].index; | |
return 0; | |
} | |
// This function sucks | |
__attribute__((unused)) | |
static size_t _obj_triplet_pos(obj_Result *res, obj_Triplet triplet, size_t nd) { | |
size_t index = 0; | |
if ((index = _obj_memo_get(triplet))) { | |
return index-1; | |
} | |
for (size_t i = 0; i < nd; i += 1) { | |
for (size_t j = 0; j < i; j += 1) | |
if (_obj_triplet_eq(res->indices[i], res->indices[j])) { | |
index -= 1; | |
break; | |
} | |
if (_obj_triplet_eq(res->indices[i], triplet)) | |
return _obj_memo_insert(triplet, index); | |
index += 1; | |
} | |
assert(false && "Triplet not found"); | |
} | |
// Unrolls as (POSITION3, UV2, NORMAL3) | |
__attribute__((unused)) | |
static obj_Unrolled obj_unroll_pun(obj_Result *res, size_t *vertex_count) { | |
#ifdef NDEBUG | |
memset(_obj_memo, 0, sizeof(_obj_memo)); | |
#endif | |
obj_Unrolled unrolled = { | |
.indices = (uint16_t*)malloc(res->index_count*sizeof(uint16_t)), | |
.vertices = (float*)malloc(res->index_count*sizeof(float)*8), // POSITION 3 + UV 2 + NORMAL 3 | |
}; | |
size_t maxpos = 0; | |
for (size_t i = 0; i < res->index_count; i += 1) { | |
obj_Triplet trp = res->indices[i]; | |
assert(trp.v != 0 && trp.vn != 0 && trp.vt != 0 && "Obj: Cannot consturct PUN format without according vertices present (malformed index)"); | |
assert(trp.v <= res->vertex_count && trp.vn <= res->normal_count && trp.vt <= res->uv_count && "Obj: Cannot consturct PUN format with index pointing to a vertex out of bounds (malformed index)"); | |
#ifdef NDEBUG | |
const size_t triplet_pos = _obj_triplet_pos(res, trp, i+1); | |
#else | |
const size_t triplet_pos = i; | |
#endif | |
maxpos = triplet_pos > maxpos ? triplet_pos : maxpos; | |
trp.v -= 1; // (1 indexed fix) | |
trp.vn -= 1; // (1 indexed fix) | |
trp.vt -= 1; // (1 indexed fix) | |
// Might have some redundant writes but it doesn't matter cuz triplet pos function already sucks | |
unrolled.vertices[triplet_pos*8+0] = res->vertices[trp.v*3+0]; | |
unrolled.vertices[triplet_pos*8+1] = res->vertices[trp.v*3+1]; | |
unrolled.vertices[triplet_pos*8+2] = res->vertices[trp.v*3+2]; | |
// Uv | |
unrolled.vertices[triplet_pos*8+3] = res->uvs[trp.vt*2+0]; | |
unrolled.vertices[triplet_pos*8+4] = res->uvs[trp.vt*2+1]; | |
// Normals | |
unrolled.vertices[triplet_pos*8+5] = res->normals[trp.vn*3+0]; | |
unrolled.vertices[triplet_pos*8+6] = res->normals[trp.vn*3+1]; | |
unrolled.vertices[triplet_pos*8+7] = res->normals[trp.vn*3+2]; | |
unrolled.indices[i] = (uint16_t)triplet_pos; | |
} | |
if (vertex_count != NULL) | |
*vertex_count = maxpos+1; | |
printf("Vertex count is %zu\n", *vertex_count); | |
return unrolled; | |
} | |
static void obj_dispose_unrolled(obj_Unrolled *unrolled) { | |
free(unrolled->indices); | |
free(unrolled->vertices); | |
} | |
#undef Parser | |
#undef chk | |
#undef oneof | |
#undef next | |
#undef verify | |
#undef VERTEX_COUNT | |
#undef INDEX_COUNT |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment