const u64 MODEL_MAX = 2048; #include typedef MaterialMapIndex MMI; struct MaterialMap { Vec4 color; f32 value; }; struct MaterialSet { MaterialMap maps[MMI_Max]; }; ImageBuffer CreateDefaultTexture(u8 *ptr, u64 w, u64 h); MaterialSet CreateDefaultMaterialSet(); u8 DEFAULT_TEXTURE_DATA[32*32*4]; const ImageBuffer DEFAULT_TEXTURE = CreateDefaultTexture(DEFAULT_TEXTURE_DATA, 32, 32); const MaterialSet DEFAULT_MATERIAL = CreateDefaultMaterialSet(); struct Vertex { Vec4 color; Vec4 tangent; Vec3 pos; Vec3 normal; Vec2 uv[2]; }; struct Mesh { u32 start; u32 length; u32 index_start; u32 index_length; u32 material_index; }; struct Material { TextureID textures[MMI_Max]; ShaderModelState shader_state; PipelineID pipeline_id; BufferID buffer_id; BufferID shader_state_buffer_id; }; struct Model { ModelBuffers buffers; Array meshes; Array textures; Array materials; }; Model g_models[MODEL_MAX]; ModelBuffers CreateModelBuffers(Array vertices, Array indices) { ModelBuffers buffers = CreateModelBuffers(); glBindVertexArray(buffers.vertex_array); glBindBuffer(GL_ARRAY_BUFFER, buffers.vertex_buffer); glBufferData(GL_ARRAY_BUFFER, sizeof(Vertex)*vertices.length, vertices.ptr, GL_STATIC_DRAW); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffers.index_buffer); glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(u32)*indices.length, indices.ptr, GL_STATIC_DRAW); glEnableVertexAttribArray(0); glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void *)offsetof(Vertex, color)); glEnableVertexAttribArray(1); glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void *)offsetof(Vertex, tangent)); glEnableVertexAttribArray(2); glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void *)offsetof(Vertex, pos)); glEnableVertexAttribArray(3); glVertexAttribPointer(3, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void *)offsetof(Vertex, normal)); glEnableVertexAttribArray(4); glVertexAttribPointer(4, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void *)offsetof(Vertex, uv[0])); glEnableVertexAttribArray(5); glVertexAttribPointer(5, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void *)(offsetof(Vertex, uv[1]))); glBindVertexArray(0); return buffers; } Array OpenFile(String8 file_path) { // TODO: get rid of malloc Array buffer = {}; FILE *file = fopen((char *)file_path.ptr, "rb"); if(file) { fseek(file, 0, SEEK_END); int length = ftell(file); if(length) { buffer = Alloc(length+1); buffer.length = length; fseek(file, 0, SEEK_SET); fread(buffer.ptr, 1, length, file); buffer.ptr[buffer.length] = '\0'; } fflush(file); fclose(file); } else { perror("Unable to open file"); } return buffer; } extern "C" cgltf_result GLTFLoadCallback(const cgltf_memory_options *memory_opts, const cgltf_file_options *file_opts, const char *path, cgltf_size *size, void **data) { cgltf_result result = cgltf_result_io_error; Array file_data = OpenFile(String8(path)); if(file_data) { *size = (cgltf_size)file_data.length; *data = file_data.ptr; result = cgltf_result_success; } return result; } extern "C" void GLTFFreeCallback(const cgltf_memory_options *memory_opts, const cgltf_file_options *file_opts, void *data, cgltf_size size) { Free(&data); } void UnloadImage(ImageBuffer* image_buffer) { stbi_image_free(image_buffer->data.ptr); image_buffer->data.ptr = NULL; image_buffer->data.length = 0; } MaterialSet CreateDefaultMaterialSet() { MaterialSet set; set.maps[MMI_Albedo].color = Vec4(1.0); set.maps[MMI_Metallic].color = Vec4(1.0); return set; } ImageBuffer CreateDefaultTexture(u8 *ptr, u64 w, u64 h) { ImageBuffer image_buffer; image_buffer.data.ptr = ptr; image_buffer.data.length = w*h*4; image_buffer.w = w; image_buffer.h = h; image_buffer.ch = 4; u8 magenta[4] = {255, 0, 255, 255}; u8 black[4] = {0, 0, 0, 255}; u64 size = w*h*4; u64 half = size/2; for(u64 i = 0; i < size; i += 32) { bool swap = i <= half; u8 *color0 = swap ? magenta : black; u8 *color1 = swap ? black : magenta; for(u64 j = 0; j < 16; j += 4) { ptr[i+j+0] = color0[0]; ptr[i+j+1] = color0[1]; ptr[i+j+2] = color0[2]; ptr[i+j+3] = color0[3]; ptr[i+j+16+0] = color0[0]; ptr[i+j+16+1] = color0[1]; ptr[i+j+16+2] = color0[2]; ptr[i+j+16+3] = color0[3]; } } return image_buffer; } ImageBuffer LoadImage(void *data, i32 size) { ImageBuffer image_buffer; i32 w, h, ch, desired_channels = 4; image_buffer.data.ptr = stbi_load_from_memory((const u8 *)data, size, &w, &h, &ch, desired_channels); if(w > 0 && h > 0 && ch > 0) { image_buffer.data.length = w*h*ch; image_buffer.w = w; image_buffer.h = h; image_buffer.ch = ch; } else { printf("Failed to load image data\n"); image_buffer.data.ptr = NULL; } return image_buffer; } ImageBuffer LoadImage(cgltf_image *asset_image, String8 texture_path) { ImageBuffer image_buffer; if(asset_image) { String8 uri_path = String8(asset_image->uri); if(StartsWith(uri_path, String8Lit("data:"))) { u32 i; for(i = 0; uri_path[i] != ',' && i < uri_path.length; i += 1); if(uri_path[i] != 0 && i < uri_path.length-1) { u64 base64_length = strlen(asset_image->uri+i+1); for(; uri_path[i+base64_length] == '='; base64_length -= 1); u64 bit_count = base64_length*6 - (base64_length*6)%8; i32 out_size = (i32)(bit_count/8); void *data; cgltf_options options; options.file.read = GLTFLoadCallback; options.file.release = GLTFFreeCallback; cgltf_result result = cgltf_load_buffer_base64(&options, out_size, asset_image->uri+i+1, &data); if(result == cgltf_result_success) { image_buffer = LoadImage(data, out_size); cgltf_free((cgltf_data *)data); } } else { printf("GLTF data for URI is not a valid image\n"); } } else { u8 buffer[512]; String8 file_path = SPrintf(buffer, "%s%s", texture_path.ptr, uri_path.ptr); Array image_file = OpenFile(file_path); image_buffer = LoadImage(image_file.ptr, (i32)image_file.length); Free(&image_file); } } else if(asset_image->buffer_view != NULL && asset_image->buffer_view->buffer->data != NULL) { Array image_data = Alloc(asset_image->buffer_view->size); i32 offset = (i32)asset_image->buffer_view->offset; i32 stride = (i32)asset_image->buffer_view->stride ? asset_image->buffer_view->stride : 1; u64 length = asset_image->buffer_view->size - (asset_image->buffer_view->size%stride); u8 *buffer_data = (u8 *)(asset_image->buffer_view->buffer->data); memcpy(image_data.ptr, buffer_data, length); String8 mime_type = String8(asset_image->mime_type); String8 accepted_types[8] = { String8Lit("image\\/png"), String8Lit("image/png"), String8Lit("image\\/jpeg"), String8Lit("image/jpeg"), String8Lit("image\\/tga"), String8Lit("image/tga"), String8Lit("image\\/bmp"), String8Lit("image/bmp"), }; bool accepted; for(u64 i = 0; i < Length(accepted_types); i += 1) { if(mime_type == accepted_types[i]) { accepted = true; break; } } if(accepted) { image_buffer = LoadImage(buffer_data, length); } else printf("Unable to load image, unknown image type [%s]", mime_type.ptr); Free(&image_data); } if(image_buffer.data.ptr == NULL) { printf("Failed to load GLTF image data/file"); } return image_buffer; } TextureID LoadImageToTexture(cgltf_image *asset_image, String8 texture_path) { TextureID texture_id = g_renderer.default_texture; ImageBuffer image_buffer = LoadImage(asset_image, texture_path); if(image_buffer.data.ptr) { texture_id = CreateTexture(image_buffer); Free(&image_buffer.data); } else Logf("Unable to load texture %s, setting default", asset_image->name); return texture_id; } TextureID LoadImageToTexture(u8 *data, i32 width, i32 height, i32 channels) { assert(width > 0 && height > 0 && channels > 0); Array temp_buffer = {}; if(channels == 1) { u64 length = width*height*channels; temp_buffer = Alloc(length*4); for(u64 i = 0; i < length; i += 1) { temp_buffer[i*3 + 0] = data[i]; temp_buffer[i*3 + 1] = data[i]; temp_buffer[i*3 + 2] = data[i]; temp_buffer[i*3 + 3] = 255; } } else if(channels == 3) { temp_buffer = Alloc(width*height*4); u64 pixels = width*height; for(u64 i = 0; i < pixels; i += 1) { temp_buffer[i*4 + 0] = data[i*3 + 0]; temp_buffer[i*4 + 1] = data[i*3 + 1]; temp_buffer[i*4 + 2] = data[i*3 + 2]; temp_buffer[i*4 + 3] = 255; } } if(channels < 4) { data = temp_buffer.ptr; channels = 4; } ImageBuffer image_buffer = { .data = { .ptr = data, .length = (u64)(width*height*channels), }, .w = (u32)width, .h = (u32)height, .ch = (u32)channels, }; assert(image_buffer.data.length > 0); TextureID texture_id = CreateTexture(image_buffer); if(temp_buffer) { Free(&temp_buffer); } return texture_id; } TextureID FindTexture(cgltf_texture *texture, cgltf_data *data, Model *model) { TextureID result = g_renderer.default_texture; for(u64 i = 0; i < data->textures_count; i += 1) { if(texture == data->textures+i) { result = model->textures[i]; break; } } return result; } template T * GLTFBuffer(cgltf_accessor *accessor) { return (T *)(accessor->buffer_view->buffer->data) + (accessor->buffer_view->offset/sizeof(T)) + (accessor->offset/sizeof(T)); } void SetMeshData(Mesh *mesh, cgltf_accessor *accessor, u64 *vertex_count) { if(mesh->length == 0) { mesh->start = (u32)(*vertex_count); mesh->length = (u32)(accessor->count); (*vertex_count) += accessor->count; } } #define AdvanceBuffer(T, buffer, accessor) (T *)((u8 *)(buffer) + accessor->stride) void SetVerticesWithTransform(cgltf_accessor *accessor, Array vertices, Mesh *mesh, usize offset, Mat4 &world_matrix, u64 *vertex_count) { SetMeshData(mesh, accessor, vertex_count); f32 *buffer = GLTFBuffer(accessor); for(u64 i = 0; i < mesh->length; i += 1, buffer = AdvanceBuffer(f32, buffer, accessor)) { Vec3 *vtx_ptr = (Vec3 *)((u8 *)(&vertices[mesh->start+i]) + offset); Vec3 vertex = Vec3(buffer[0], buffer[1], buffer[2]); (*vtx_ptr) = Transform(vertex, world_matrix); } } template void SetIndices(cgltf_accessor *accessor, Array indices, Mesh *mesh) { T *buffer = GLTFBuffer(accessor); for(u64 i = 0; i < mesh->index_length; i += 1) { indices[mesh->index_start+i] = (u32)(mesh->start + buffer[i]); } } bool LoadGLTF(Arena* arena, Model* model_result, String8 file_name) { Model model = {}; TempArena temp_arena = {}; Array vertices = {}; Array indices = {}; Array file_data = OpenFile(file_name); assert(file_data.ptr); // TODO: handle errors cgltf_options opts = {}; cgltf_data *data = NULL; opts.file.read = GLTFLoadCallback; opts.file.release = GLTFFreeCallback; cgltf_result result = cgltf_parse(&opts, file_data.ptr, file_data.length, &data); if(result == cgltf_result_success) { result = cgltf_load_buffers(&opts, data, file_name.ch_ptr); if(result != cgltf_result_success) { Logf("LoadGLTF failure: Unable to load buffers"); } u64 primitive_count = 0, vertex_count = 0, index_count = 0; for(u64 i = 0; i < data->nodes_count; i += 1) { cgltf_node *node = data->nodes+i; if(!node->mesh) continue; for(u64 j = 0; j < node->mesh->primitives_count; j += 1) { auto p = node->mesh->primitives+j; if(p->type == cgltf_primitive_type_triangles) { primitive_count += 1; if(p->indices && p->indices->buffer_view) { index_count += p->indices->count; } for(u64 k = 0; k < p->attributes_count; k += 1) { if(p->attributes[k].type == cgltf_attribute_type_position) { vertex_count += p->attributes[k].data->count; } } } } } model.meshes = Alloc(arena, primitive_count); model.textures = Alloc(arena, data->textures_count); model.materials = Alloc(arena, data->materials_count); temp_arena = Begin(arena); String8 file_path = GetFilePath(file_name); vertices = Alloc(temp_arena, vertex_count); indices = Alloc(temp_arena, index_count); for(u64 i = 0; i < model.textures.length; i += 1) { model.textures[i] = LoadImageToTexture(data->textures[i].image, file_path); } for(u64 i = 0; i < model.materials.length; i += 1) { model.materials[i].buffer_id = g_renderer.default_material; for(u64 j = 0; j < MMI_Max; j += 1) { model.materials[i].textures[j] = g_renderer.default_texture; } } for(u64 i = 0; i < data->materials_count; i += 1) { MaterialSet material_set = {}; ShaderModelState shader_state = {}; TextureID *textures = model.materials[i].textures; cgltf_material *material = data->materials+i; if(material->has_pbr_metallic_roughness) { auto pbr_mr = &material->pbr_metallic_roughness; if(pbr_mr->base_color_texture.texture) { textures[MMI_Albedo] = FindTexture(pbr_mr->base_color_texture.texture, data, &model); shader_state.albedo_texture = true; } memcpy(material_set.maps[MMI_Albedo].color.v, pbr_mr->base_color_factor, sizeof(f32)*4); if(pbr_mr->metallic_roughness_texture.texture) { TextureID mr_texture = FindTexture(pbr_mr->metallic_roughness_texture.texture, data, &model); textures[MMI_Metallic] = mr_texture; textures[MMI_Roughness] = mr_texture; material_set.maps[MMI_Metallic].value = pbr_mr->metallic_factor; material_set.maps[MMI_Roughness].value = pbr_mr->roughness_factor; shader_state.metallic_roughness_texture = true; } if(material->normal_texture.texture) { textures[MMI_Normal] = FindTexture(material->normal_texture.texture, data, &model); shader_state.normal_texture = true; } if(material->occlusion_texture.texture) { textures[MMI_Occlusion] = FindTexture(material->occlusion_texture.texture, data, &model); shader_state.occlusion_texture = true; } if(material->emissive_texture.texture) { textures[MMI_Emission] = FindTexture(material->emissive_texture.texture, data, &model); memcpy(material_set.maps[MMI_Emission].color.v, material->emissive_factor, sizeof(f32)*3); material_set.maps[MMI_Emission].color.a = 1.0; shader_state.emission_texture = true; } } model.materials[i].buffer_id = CreateBuffer(&material_set, true); model.materials[i].shader_state = shader_state; model.materials[i].shader_state_buffer_id = CreateBuffer(&shader_state, true); } u64 mesh_index = 0, point_index = 0; vertex_count = 0; for(u64 i = 0; i < data->nodes_count; i += 1) { cgltf_node *node = data->nodes+i; cgltf_mesh *mesh = node->mesh; if(!mesh) continue; cgltf_float world_transform[16]; cgltf_node_transform_world(node, world_transform); Mat4 world_matrix = Mat4(world_transform); for(u64 p = 0; p < mesh->primitives_count; p += 1) { if(mesh->primitives[p].type != cgltf_primitive_type_triangles) { Logf("Unable to process primitive type [%d]", mesh->primitives[p].type); continue; } auto prim = mesh->primitives+p; Mesh *model_mesh = model.meshes.ptr+mesh_index; for(u64 j = 0; j < prim->attributes_count; j += 1) { cgltf_accessor *accessor = prim->attributes[j].data; switch(prim->attributes[j].type) { case cgltf_attribute_type_position: { SetVerticesWithTransform(accessor, vertices, model_mesh, offsetof(Vertex, pos), world_matrix, &vertex_count); } break; case cgltf_attribute_type_normal: { SetVerticesWithTransform(accessor, vertices, model_mesh, offsetof(Vertex, normal), world_matrix, &vertex_count); } break; case cgltf_attribute_type_tangent: { SetMeshData(model_mesh, accessor, &vertex_count); f32 *buffer = GLTFBuffer(accessor); for(u64 i = 0; i < model_mesh->length; i += 1, buffer = AdvanceBuffer(f32, buffer, accessor)) { vertices[model_mesh->start+i].tangent = world_matrix * Vec4(buffer[0], buffer[1], buffer[2], buffer[3]); } } break; case cgltf_attribute_type_texcoord: { if(accessor->type == cgltf_type_vec2) { SetMeshData(model_mesh, accessor, &vertex_count); u32 uv_index = prim->attributes[j].index; if(uv_index >= 2) // index into UV set { Logf("Unable to use more than two UVs, ignoring UV"); } switch(accessor->component_type) { case cgltf_component_type_r_8u: { u8 *buffer = GLTFBuffer(accessor); for(u64 k = 0; k < model_mesh->length; k += 1, buffer = AdvanceBuffer(u8, buffer, accessor)) { vertices[model_mesh->start+k].uv[uv_index] = Vec2((f32)(buffer[0]&0xFF)/255.0f, (f32)(buffer[1]&0xFF)/255.0f); } } break; case cgltf_component_type_r_16u: { u16 *buffer = GLTFBuffer(accessor); for(u64 k = 0; k < model_mesh->length; k += 1, buffer = AdvanceBuffer(u16, buffer, accessor)) { vertices[model_mesh->start+k].uv[uv_index] = Vec2((f32)(buffer[0]&0xFFFF)/65535.0f, (f32)(buffer[1]&0xFFFF)/65535.0f); } } break; case cgltf_component_type_r_32f: { f32 *buffer = GLTFBuffer(accessor); f32 *start_b = buffer; for(u64 k = 0; k < model_mesh->length; k += 1, buffer = AdvanceBuffer(f32, buffer, accessor)) { vertices[model_mesh->start+k].uv[uv_index] = Vec2(buffer[0], buffer[1]); } } break; default: Logf("Unsupported component type for UV [%llu], ignoring", accessor->component_type); break; } } } break; case cgltf_attribute_type_color: { if(accessor->type == cgltf_type_vec3 || accessor->type == cgltf_type_vec4) { SetMeshData(model_mesh, accessor, &vertex_count); if(accessor->type == cgltf_type_vec3) { switch(accessor->component_type) { case cgltf_component_type_r_8u: { u8 *buffer = GLTFBuffer(accessor); for(u64 k = 0; k < model_mesh->length; k += 1, buffer = AdvanceBuffer(u8, buffer, accessor)) { vertices[model_mesh->start+k].color = Vec4( (f32)(buffer[0]&0xFF)/255.0f, (f32)(buffer[1]&0xFF)/255.0f, (f32)(buffer[2]&0xFF)/255.0f, 1.0 ); } } break; case cgltf_component_type_r_16u: { u16 *buffer = GLTFBuffer(accessor); for(u64 k = 0; k < model_mesh->length; k += 1, buffer = AdvanceBuffer(u16, buffer, accessor)) { vertices[model_mesh->start+k].color = Vec4( (f32)(buffer[0]&0xFFFF)/65535.0f, (f32)(buffer[1]&0xFFFF)/65535.0f, (f32)(buffer[2]&0xFFFF)/65535.0f, 1.0 ); } } break; case cgltf_component_type_r_32f: { f32 *buffer = GLTFBuffer(accessor); for(u64 k = 0; k < model_mesh->length; k += 1, buffer = AdvanceBuffer(f32, buffer, accessor)) { vertices[model_mesh->start+k].color = Vec4(buffer[0], buffer[1], buffer[2], 1.0f); } } break; default: Logf("Color component type is not supported [%llu]", accessor->component_type); break; } } else if(accessor->type == cgltf_type_vec4) { switch(accessor->component_type) { case cgltf_component_type_r_8u: { u8 *buffer = GLTFBuffer(accessor); for(u64 k = 0; k < model_mesh->length; k += 1, buffer = AdvanceBuffer(u8, buffer, accessor)) { vertices[model_mesh->start+k].color = Vec4( (f32)(buffer[0]&0xFF)/255.0f, (f32)(buffer[1]&0xFF)/255.0f, (f32)(buffer[2]&0xFF)/255.0f, (f32)(buffer[3]&0xFF)/255.0f ); } } break; case cgltf_component_type_r_16u: { u16 *buffer = GLTFBuffer(accessor); for(u64 k = 0; k < model_mesh->length; k += 1, buffer = AdvanceBuffer(u16, buffer, accessor)) { vertices[model_mesh->start+k].color = Vec4( (f32)(buffer[0]&0xFFFF)/65535.0f, (f32)(buffer[1]&0xFFFF)/65535.0f, (f32)(buffer[2]&0xFFFF)/65535.0f, (f32)(buffer[3]&0xFFFF)/65535.0f ); } } break; case cgltf_component_type_r_32f: { f32 *buffer = GLTFBuffer(accessor); for(u64 k = 0; k < model_mesh->length; k += 1, buffer = AdvanceBuffer(f32, buffer, accessor)) { vertices[model_mesh->start+k].color = Vec4(buffer[0], buffer[1], buffer[2], buffer[3]); } } break; default: Logf("Color component type is not supported [%llu]", accessor->component_type); break; } } } } break; default: Logf("Unsupported attribute type [%llu]", prim->attributes[j].type); break; } } if(prim->indices && prim->indices->buffer_view) { cgltf_accessor *accessor = prim->indices; model_mesh->index_start = point_index; model_mesh->index_length = accessor->count; point_index += accessor->count; switch(accessor->component_type) { case cgltf_component_type_r_8u: SetIndices(accessor, indices, model_mesh); break; case cgltf_component_type_r_16u: SetIndices(accessor, indices, model_mesh); break; case cgltf_component_type_r_32u: SetIndices(accessor, indices, model_mesh); break; default: Logf("Unsupported index component type [%llu]", accessor->component_type); break; } } else // Unindexed mesh { Logf("Unindexed mesh are currently not supported, ignoring"); } for(u64 k = 0; k < data->materials_count; k += 1) { if(data->materials+k == prim->material) { model.meshes[mesh_index].material_index = k; break; } } mesh_index += 1; } } Free(&file_data); model.buffers = CreateModelBuffers(vertices, indices); End(&temp_arena); *model_result = model; cgltf_free(data); } return result == cgltf_result_success; } const char * M3DPropToStr(u8 type) { const char *result = "Unknown"; switch(type) { case m3dp_Kd: result = "Diffuse"; break; case m3dp_Ka: result = "Ambient"; break; case m3dp_Ks: result = "Specular Color"; break; case m3dp_Ns: result = "Specular Exponent"; break; case m3dp_Ke: result = "Emissive"; break; case m3dp_Tf: result = "Transmission"; break; case m3dp_Km: result = "Bump Strength"; break; case m3dp_d: result = "Alpha"; break; case m3dp_il: result = "Illumination"; break; case m3dp_Pr: result = "Roughness"; break; case m3dp_Pm: result = "Metallic"; break; case m3dp_Ps: result = "Sheen"; break; case m3dp_Ni: result = "Refraction"; break; case m3dp_Nt: result = "Face Thickness"; break; case m3dp_map_Kd: result = "Diffuse Texture"; break; case m3dp_map_Ka: result = "Ambient Texture"; break; case m3dp_map_Ks: result = "Specular Texture"; break; case m3dp_map_Ns: result = "Specular Exponent Texture"; break; case m3dp_map_Ke: result = "Emissive Texture"; break; case m3dp_map_Tf: result = "Transmission Texture"; break; case m3dp_map_Km: result = "Bump Map"; break; case m3dp_map_D: result = "Alpha Map"; break; case m3dp_map_N: result = "Normal Map"; break; case m3dp_map_Pr: result = "Roughness Map"; break; case m3dp_map_Pm: result = "Metallic Map"; break; case m3dp_map_Ps: result = "Sheen Map"; break; case m3dp_map_Ni: result = "Refraction Map"; break; case m3dp_map_Nt: result = "Thickness Map"; break; default: break; } return result; } bool LoadM3D(Arena* arena, Model* model_result, String8 file_name) { bool result = false; Array file_data = OpenFile(file_name); assert(file_data); m3d_t *m3d = m3d_load(file_data.ptr, NULL, NULL, NULL); assert(m3d); if(m3d->numface) { Model model = { textures: Alloc(arena, m3d->numtexture), materials: Alloc(arena, m3d->nummaterial), }; for(u64 i = 0; i < m3d->numtexture; i += 1) { model.textures[i] = LoadImageToTexture(m3d->texture[i].d, m3d->texture[i].w, m3d->texture[i].h, m3d->texture[i].f); } for(u64 i = 0; i < m3d->nummaterial; i += 1) { MaterialSet material_set = {}; ShaderModelState shader_state = {}; for(u64 j = 0; j < m3d->material[i].numprop; j += 1) { auto prop = m3d->material[i].prop + j; TextureID *textures = model.materials[i].textures; switch(prop->type) { case m3dp_Kd: material_set.maps[MMI_Albedo].color = U32ToVec4(prop->value.color); break; case m3dp_Ka: material_set.maps[MMI_Occlusion].color = U32ToVec4(prop->value.color); break; case m3dp_Ks: material_set.maps[MMI_Metallic].color = U32ToVec4(prop->value.color); break; case m3dp_Ns: material_set.maps[MMI_Roughness].value = prop->value.fnum; break; case m3dp_d: break; // Alpha case m3dp_map_Kd: { textures[MMI_Albedo] = model.textures[prop->value.textureid]; shader_state.albedo_texture = true; } break; case m3dp_map_Ka: { textures[MMI_Occlusion] = model.textures[prop->value.textureid]; shader_state.occlusion_texture = true; } break; case m3dp_map_Ks: { textures[MMI_Metallic] = model.textures[prop->value.textureid]; textures[MMI_Roughness] = model.textures[prop->value.textureid]; // Maybe incorrect.. shader_state.metallic_roughness_texture = true; } break; case m3dp_map_D: { // Alpha texture } break; default: break; // Logf("Unsupported property: %s", M3DPropToStr(prop->type)); break; } model.materials[i].buffer_id = CreateBuffer(&material_set, true); model.materials[i].shader_state = shader_state; model.materials[i].shader_state_buffer_id = CreateBuffer(&shader_state, true); } } u64 mesh_count = 0; u64 last = -2U; for(u64 i = 0; i < m3d->numface; i += 1) { if(m3d->face[i].materialid != last) { last = m3d->face[i].materialid; mesh_count += 1; } } model.meshes = Alloc(arena, mesh_count); u64 vertex_count = 0; u64 mesh_index = 0; if(mesh_count > 1) { last = -2U; for(u64 i = 0; i < m3d->numface; i += 1) { if(last == -2U) { model.meshes[mesh_index].material_index = m3d->face[i].materialid; model.meshes[mesh_index].start = 0; model.meshes[mesh_index].index_start = 0; last = m3d->face[i].materialid; } else if(m3d->face[i].materialid != last) { model.meshes[mesh_index].length = i*3-vertex_count; model.meshes[mesh_index].index_length = i*3-vertex_count; mesh_index += 1; vertex_count += model.meshes[mesh_index].length; model.meshes[mesh_index].start = vertex_count; model.meshes[mesh_index].index_start = vertex_count; model.meshes[mesh_index].material_index = m3d->face[i].materialid; last = m3d->face[i].materialid; } if(i == m3d->numface-1) { model.meshes[mesh_index].length = i*3 - vertex_count; model.meshes[mesh_index].index_length = i*3 - vertex_count; } } } else { model.meshes[0].start = 0; model.meshes[0].index_start = 0; model.meshes[0].length = m3d->numface*3; model.meshes[0].index_length = m3d->numface*3; model.meshes[0].material_index = m3d->face[0].materialid; } TempArena temp_arena = Begin(arena); Array vertices = Alloc(temp_arena, m3d->numface*3); Array indices = Alloc(temp_arena, m3d->numface*3); Array positions = Alloc(temp_arena, m3d->numface); Array position_indices = Alloc(temp_arena, m3d->numface); for(u64 i = 0; i < m3d->numface; i += 1) { u64 vi = i*3; memcpy(&vertices[vi+0].pos, &m3d->vertex[m3d->face[i].vertex[0]], sizeof(Vec3)); memcpy(&vertices[vi+1].pos, &m3d->vertex[m3d->face[i].vertex[1]], sizeof(Vec3)); memcpy(&vertices[vi+2].pos, &m3d->vertex[m3d->face[i].vertex[2]], sizeof(Vec3)); memcpy(&vertices[vi+0].normal, &m3d->vertex[m3d->face[i].normal[0]], sizeof(Vec3)); memcpy(&vertices[vi+1].normal, &m3d->vertex[m3d->face[i].normal[1]], sizeof(Vec3)); memcpy(&vertices[vi+2].normal, &m3d->vertex[m3d->face[i].normal[2]], sizeof(Vec3)); vertices[vi+0].color = U32ToVec4(m3d->vertex[m3d->face[i].vertex[0]].color); vertices[vi+1].color = U32ToVec4(m3d->vertex[m3d->face[i].vertex[1]].color); vertices[vi+2].color = U32ToVec4(m3d->vertex[m3d->face[i].vertex[2]].color); if(m3d->numtmap) { memcpy(&vertices[vi+0].uv[0], &m3d->tmap[m3d->face[i].texcoord[0]], sizeof(Vec2)); memcpy(&vertices[vi+1].uv[0], &m3d->tmap[m3d->face[i].texcoord[1]], sizeof(Vec2)); memcpy(&vertices[vi+2].uv[0], &m3d->tmap[m3d->face[i].texcoord[2]], sizeof(Vec2)); } indices[vi+0] = vi+0; indices[vi+1] = vi+1; indices[vi+2] = vi+2; positions[i] = (vertices[vi+0].pos + vertices[vi+1].pos + vertices[vi+2].pos) / 3.0f; position_indices[i] = i; } for(u64 i = 0; i < indices.length; i += 3) { u32 i0 = indices[i+0]; u32 i1 = indices[i+1]; u32 i2 = indices[i+2]; Vec3 edge1 = vertices[i1].pos - vertices[i0].pos; Vec3 edge2 = vertices[i2].pos - vertices[i0].pos; Vec2 delta_uv1 = vertices[i1].uv[0] - vertices[i0].uv[0]; Vec2 delta_uv2 = vertices[i2].uv[0] - vertices[i0].uv[0]; f32 dividend = delta_uv1.x*delta_uv2.y - delta_uv2.x*delta_uv1.y; f32 fc = 1.0f/dividend; Vec3 tangent = Vec3( fc * (delta_uv2.y * edge1.x - delta_uv1.y * edge2.x), fc * (delta_uv2.y * edge1.y - delta_uv1.y * edge2.y), fc * (delta_uv2.y * edge1.z - delta_uv1.y * edge2.z) ); tangent = Normalize(tangent); f32 handedness = ((delta_uv1.y*delta_uv2.x - delta_uv2.y*delta_uv1.x) < 0.0f) ? -1.0f : +1.0f; Vec3 t = tangent * handedness; vertices[i0].tangent = Vec4(t, handedness); vertices[i1].tangent = Vec4(t, handedness); vertices[i2].tangent = Vec4(t, handedness); } model.buffers = CreateModelBuffers(vertices, indices); End(&temp_arena); *model_result = model; result = true; } else { Logf("No faces in M3D model"); } m3d_free(m3d); Free(&file_data); return result; }