/// /// [ ] - Cache bitmaps /// [ ] - Fix potential portability issues due to compiler struct alignment differences etc. /// struct Obj_Index { int vertex[3]; int tex[3]; int normal[3]; S32 material_id; S32 smoothing_group_id; }; struct Obj_Mesh { char name[64]; Array indices; }; struct Obj_Material { char name[64]; U32 name_len; Bitmap texture_ambient; // map_Ka Bitmap texture_diffuse; // map_Kd Bitmap texture_dissolve; // map_d Bitmap texture_displacement; // map_Disp F32 non_transparency; // d F32 transparency; // Tr F32 optical_density; // Ni F32 shininess; // Ns S32 illumination_model; // illum Vec3 ambient_color; // Ka Vec3 diffuse_color; // Kd Vec3 specular_color; // Ks }; struct Obj { String name; Array vertices; Array texture_coordinates; Array normals; Array mesh; Array materials; }; enum class Obj_Token_Type { none, word, number, whitespace, end }; struct Obj_Token { Obj_Token_Type type; double number; union { struct { char* s; int len; }; String s8; }; }; function Bitmap load_image(String path) { Scratch scratch; String file = os_read_file(scratch, path); int x, y, n; unsigned char* data = stbi_load_from_memory(file.str, file.len, &x, &y, &n, 4); Bitmap result = { (U32*)data, x, y }; #if PREMULTIPLIED_ALPHA_BLENDING if(data) { U32 *p = result.pixels; for (int Y = 0; Y < y; Y++) { for (int X = 0; X < x; X++) { Vec4 color = vec4abgr(*p); color.r *= color.a; color.g *= color.a; color.b *= color.a; *p++ = vec4_to_u32abgr(color); } } } #endif return result; } function Obj_Token next_token_raw(char** data) { Obj_Token result = {}; result.s = *data; *data += 1; if (is_alphabetic(*result.s)) { result.type = Obj_Token_Type::word; while (!is_whitespace(**data)) { *data += 1; } result.len = (int)(*data - result.s); } else if (is_number(*result.s) || *result.s == '-') { result.type = Obj_Token_Type::number; while (is_number(**data) || **data == '.' || **data == 'e' || **data == '-') { *data += 1; } result.number = atof(result.s); result.len = (int)(*data - result.s); } else if (*result.s == '#') { while (**data != '\n') *data += 1; result = next_token_raw(data); } else if (is_whitespace(*result.s)) { result.type = Obj_Token_Type::whitespace; while (is_whitespace(**data)) *data += 1; result.len = (int)(*data - result.s); } else if (*result.s == 0) { result.type = Obj_Token_Type::end; } else if (*result.s >= '!') { result.type = (Obj_Token_Type)*result.s; } return result; } function Obj_Token next_token(char** data) { Obj_Token result; do { result = next_token_raw(data); } while (result.type == Obj_Token_Type::whitespace); return result; } function double expect_number(char** data) { Obj_Token t = next_token(data); assert(t.type == Obj_Token_Type::number); // @Todo: Error handling, error flag return t.number; } function void expect_token(char** data, char token) { Obj_Token t = next_token(data); assert(t.type == (Obj_Token_Type)token); // @Todo: Error handling, error flag } function void debug_expect_raw(char** data, Obj_Token_Type type) { char* data_temp = *data; Obj_Token t = next_token_raw(&data_temp); assert(t.type == type); } function void parse_mtl(Obj* obj, String path_obj_folder, String mtl_file) { Scratch scratch; char *data = (char *)mtl_file.str; Obj_Material *m = 0; for (;;) { Obj_Token token = next_token(&data); if (token.type == Obj_Token_Type::end) break; else if (token.type == Obj_Token_Type::word) { if (string_compare(token.s8, "newmtl"_s)) { token = next_token(&data); m = obj->materials.push_empty_zero(); m->name_len = clamp_top(token.len, 64); memory_copy(m->name, token.s8.str, m->name_len); } else if (string_compare(token.s8, "Ns"_s)) { m->shininess = expect_number(&data); } else if (string_compare(token.s8, "Ka"_s)) { m->ambient_color.x = expect_number(&data); m->ambient_color.y = expect_number(&data); m->ambient_color.z = expect_number(&data); } else if (string_compare(token.s8, "Kd"_s)) { m->diffuse_color.x = expect_number(&data); m->diffuse_color.y = expect_number(&data); m->diffuse_color.z = expect_number(&data); } else if (string_compare(token.s8, "Ks"_s)) { m->specular_color.x = expect_number(&data); m->specular_color.y = expect_number(&data); m->specular_color.z = expect_number(&data); } else if (string_compare(token.s8, "Ni"_s)) { m->optical_density = expect_number(&data); } else if (string_compare(token.s8, "d"_s)) { m->non_transparency = expect_number(&data); } else if (string_compare(token.s8, "illum"_s)) { m->illumination_model = (S32)expect_number(&data); } else if (string_compare(token.s8, "map_Kd"_s)) { Obj_Token t = next_token(&data); String path = string_fmt(scratch, "%Q/%Q\0", path_obj_folder, t.s8); m->texture_diffuse = load_image(path); } else if (string_compare(token.s8, "map_Ka"_s)) { Obj_Token t = next_token(&data); String path = string_fmt(scratch, "%Q/%Q\0", path_obj_folder, t.s8); m->texture_ambient = load_image(path); } else if (string_compare(token.s8, "map_d"_s)) { Obj_Token t = next_token(&data); String path = string_fmt(scratch, "%Q/%Q\0", path_obj_folder, t.s8); m->texture_dissolve = load_image(path); } else if (string_compare(token.s8, "map_Disp"_s)) { Obj_Token t = next_token(&data); String path = string_fmt(scratch, "%Q/%Q\0", path_obj_folder, t.s8); m->texture_displacement = load_image(path); } } } } function Obj parse(Allocator *allocator, char* data, String path_obj_folder) { Set_Allocator(allocator); Scratch mtl_scratch; Obj result = {}; //result.vertices.init(allocator, 160000); //result.texture_coordinates.init(allocator, 160000); //result.normals.init(allocator, 160000); //result.mesh.init(allocator, 64); //result.materials.init(allocator, 64); int smoothing = 0; Obj_Mesh *mesh = result.mesh.push_empty_zero(); //mesh->indices.init(allocator); int material_id = -1; S64 debug_i = 0; for (;;debug_i++){ Obj_Token token = next_token(&data); if (token.type == Obj_Token_Type::end) break; else if (token.type == Obj_Token_Type::word) { if (string_compare(token.s8, "v"_s)) { Vec3 *vertex = result.vertices.push_empty_zero(); vertex->x = (float)expect_number(&data); vertex->y = (float)expect_number(&data); vertex->z = (float)expect_number(&data); debug_expect_raw(&data, Obj_Token_Type::whitespace); } else if (string_compare(token.s8, "vt"_s)) { Vec2 *tex = result.texture_coordinates.push_empty_zero(); tex->x = (float)expect_number(&data); tex->y = (float)expect_number(&data); debug_expect_raw(&data, Obj_Token_Type::whitespace); } else if (string_compare(token.s8, "vn"_s)) { Vec3 *norm = result.normals.push_empty_zero(); norm->x = (float)expect_number(&data); norm->y = (float)expect_number(&data); norm->z = (float)expect_number(&data); debug_expect_raw(&data, Obj_Token_Type::whitespace); } else if (string_compare(token.s8, "mtllib"_s)) { Obj_Token t = next_token(&data); String path = string_fmt(mtl_scratch, "%Q/%Q", path_obj_folder, t.s8); String mtl_file = os_read_file(mtl_scratch, path); if(mtl_file.str) { parse_mtl(&result, path_obj_folder, mtl_file); } } else if (string_compare(token.s8, "usemtl"_s)) { Obj_Token t = next_token(&data); assert(t.type == Obj_Token_Type::word); for(U64 i = 0; i < result.materials.len; i++) { Obj_Material *m = result.materials.data + i; if(string_compare({(U8 *)m->name, m->name_len}, t.s8)) { material_id = i; break; } } } else if (string_compare(token.s8, "o"_s)) { Obj_Token t = next_token(&data); assert(t.type == Obj_Token_Type::word); if (mesh->indices.len != 0) { mesh = result.mesh.push_empty_zero(); } else { U64 len = clamp_top(t.len, 64); memory_copy(mesh->name, t.s, len); } } else if (string_compare(token.s8, "s"_s)) { Obj_Token t = next_token(&data); if (t.type == Obj_Token_Type::number) { smoothing = (int)t.number; } else { assert(t.type == Obj_Token_Type::word); if (string_compare(t.s8, "on"_s)) { smoothing = 1; } else if (string_compare(t.s8, "off"_s)) { smoothing = 0; } else invalid_codepath; } } else if (string_compare(token.s8, "g"_s)) { Obj_Token t = next_token(&data); assert(t.type == Obj_Token_Type::word); } else if (string_compare(token.s8, "f"_s)) { Obj_Index *i = mesh->indices.push_empty_zero(); i->smoothing_group_id = smoothing; i->material_id = material_id; i->vertex[0] = (int)expect_number(&data); expect_token(&data, '/'); i->tex[0] = (int)expect_number(&data); expect_token(&data, '/'); i->normal[0] = (int)expect_number(&data); i->vertex[1] = (int)expect_number(&data); expect_token(&data, '/'); i->tex[1] = (int)expect_number(&data); expect_token(&data, '/'); i->normal[1] = (int)expect_number(&data); i->vertex[2] = (int)expect_number(&data); expect_token(&data, '/'); i->tex[2] = (int)expect_number(&data); expect_token(&data, '/'); i->normal[2] = (int)expect_number(&data); //debug_expect_raw(&data, Obj_Token_Type::whitespace); } } } return result; } function Obj load_obj(Allocator *arena, String file) { Scratch scratch; String data = os_read_file(scratch, file); assert(data.str); String path = string_chop_last_slash(file); Obj result = parse(arena, (char *)data.str, path); result.name = file; return result; } template void dump_array(String_Builder *sb, Array *arr){ sb->append_data(arr, sizeof(*arr)); sb->append_data(arr->data, sizeof(T)*arr->len); } function void dump_bitmap_image(String_Builder *sb, Bitmap *bm){ sb->append_data(bm->pixels, sizeof(U32)*bm->x*bm->y); } function B32 _os_write_file(String file, String data, B32 append = false) { B32 result = false; DWORD access = GENERIC_WRITE; DWORD creation_disposition = CREATE_ALWAYS; if (append) { access = FILE_APPEND_DATA; creation_disposition = OPEN_ALWAYS; } // @Todo(Krzosa): Unicode HANDLE handle = CreateFileA((const char *)file.str, access, 0, NULL, creation_disposition, FILE_ATTRIBUTE_NORMAL, NULL); if (handle != INVALID_HANDLE_VALUE) { DWORD bytes_written = 0; // @Todo: can only read 32 byte size files? assert_msg(data.len == (U32)data.len, "Max data size os_write can handle is 32 bytes, data to write is larger then 32 bytes!"); B32 error = WriteFile(handle, data.str, (U32)data.len, &bytes_written, NULL); if (error == false) log_error("Failed to write to file: %Q", file); else { if (bytes_written != data.len) log_error("Failed to write to file: %Q, mismatch between length requested to write and length written", file); else result = true; } CloseHandle(handle); } else { log_error("File not found when trying to write: %Q", file); } return result; } function B32 os_write_file(String file, String data) { return _os_write_file(file, data, false); } function B32 os_append_file(String file, String data) { return _os_write_file(file, data, true); } struct Stream{ U8 *cursor; U8 *end; }; #define stream_read_array(s,T,c) (T *)stream_read(s,sizeof(T)*(c)) #define stream_read_struct(s,T) stream_read_array(s,T,1) function void * stream_read(Stream *s, SizeU size){ U8 *result = s->cursor; s->cursor += size; assert(s->end >= s->cursor); return result; } function void dump_obj_to_file(Obj *obj){ obj->vertices.allocator = 0; obj->vertices.cap = obj->vertices.len; obj->texture_coordinates.allocator = 0; obj->texture_coordinates.cap = obj->texture_coordinates.len; obj->normals.allocator = 0; obj->normals.cap = obj->normals.len; obj->mesh.allocator = 0; obj->mesh.cap = obj->mesh.len; obj->materials.allocator = 0; obj->materials.cap = obj->materials.len; Iter(obj->mesh){ it->indices.allocator = 0; it->indices.cap = it->indices.len; } Scratch arena; String_Builder sb = string_builder_make(arena, mib(4)); sb.append_data(obj, sizeof(Obj)); sb.append_data(obj->name.str, obj->name.len); sb.append_data(obj->vertices.data, obj->vertices.len*sizeof(Vec3)); sb.append_data(obj->texture_coordinates.data, obj->texture_coordinates.len*sizeof(Vec2)); sb.append_data(obj->normals.data, obj->normals.len*sizeof(Vec3)); sb.append_data(obj->mesh.data, obj->mesh.len*sizeof(Obj_Mesh)); sb.append_data(obj->materials.data, obj->materials.len*sizeof(Obj_Material)); Iter(obj->mesh){ sb.append_data(it->indices.data, sizeof(Obj_Index)*it->indices.len); } Iter(obj->materials){ sb.append_data(it, sizeof(Obj_Material)); dump_bitmap_image(&sb, &it->texture_ambient); dump_bitmap_image(&sb, &it->texture_diffuse); dump_bitmap_image(&sb, &it->texture_dissolve); dump_bitmap_image(&sb, &it->texture_displacement); } String result = string_flatten(arena, &sb); os_write_file("sponza.bin"_s, result); } function Obj * load_obj_dump(Allocator *allocator, String filename){ String string = os_read_file(allocator, filename); Obj *obj = (Obj *)string.str; Stream stream = {(U8 *)(obj+1), string.str + string.len}; obj->name.str = stream_read_array(&stream, U8, obj->name.len); obj->vertices.data = stream_read_array(&stream, Vec3, obj->vertices.len); obj->texture_coordinates.data = stream_read_array(&stream, Vec2, obj->texture_coordinates.len); obj->normals.data = stream_read_array(&stream, Vec3, obj->normals.len); obj->mesh.data = stream_read_array(&stream, Obj_Mesh, obj->mesh.len); obj->materials.data = stream_read_array(&stream, Obj_Material, obj->materials.len); Iter(obj->mesh){ it->indices.data = stream_read_array(&stream, Obj_Index, it->indices.len); } Iter(obj->materials){ it->texture_ambient.pixels = stream_read_array(&stream, U32, it->texture_ambient.x*it->texture_ambient.y); it->texture_diffuse.pixels = stream_read_array(&stream, U32, it->texture_diffuse.x*it->texture_diffuse.y); it->texture_dissolve.pixels = stream_read_array(&stream, U32, it->texture_dissolve.x*it->texture_dissolve.y); it->texture_displacement.pixels = stream_read_array(&stream, U32, it->texture_displacement.x*it->texture_displacement.y); } return obj; }