1093 lines
31 KiB
C++
1093 lines
31 KiB
C++
const u64 MODEL_MAX = 2048;
|
|
|
|
#include <bit>
|
|
|
|
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<Mesh> meshes;
|
|
Array<TextureID> textures;
|
|
Array<Material> materials;
|
|
};
|
|
|
|
Model g_models[MODEL_MAX];
|
|
|
|
ModelBuffers
|
|
CreateModelBuffers(Array<Vertex> vertices, Array<u32> 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<u8>
|
|
OpenFile(String8 file_path)
|
|
{
|
|
// TODO: get rid of malloc
|
|
Array<u8> buffer = {};
|
|
|
|
FILE *file = fopen((char *)file_path.ptr, "rb");
|
|
if(file)
|
|
{
|
|
fseek(file, 0, SEEK_END);
|
|
int length = ftell(file);
|
|
if(length)
|
|
{
|
|
buffer = Alloc<u8>(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<u8> 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<u8> 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<u8> image_data = Alloc<u8>(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<u8> temp_buffer = {};
|
|
if(channels == 1)
|
|
{
|
|
u64 length = width*height*channels;
|
|
temp_buffer = Alloc<u8>(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<u8>(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<typename T> 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<Vertex> vertices, Mesh *mesh, usize offset, Mat4 &world_matrix, u64 *vertex_count)
|
|
{
|
|
SetMeshData(mesh, accessor, vertex_count);
|
|
|
|
f32 *buffer = GLTFBuffer<f32>(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<typename T> void
|
|
SetIndices(cgltf_accessor *accessor, Array<u32> indices, Mesh *mesh)
|
|
{
|
|
T *buffer = GLTFBuffer<T>(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<Vertex> vertices = {};
|
|
Array<u32> indices = {};
|
|
|
|
Array<u8> 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<Mesh>(arena, primitive_count);
|
|
model.textures = Alloc<TextureID>(arena, data->textures_count);
|
|
model.materials = Alloc<Material>(arena, data->materials_count);
|
|
|
|
temp_arena = Begin(arena);
|
|
String8 file_path = GetFilePath(file_name);
|
|
|
|
vertices = Alloc<Vertex>(temp_arena, vertex_count);
|
|
indices = Alloc<u32>(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<f32>(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<u8>(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<u16>(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<f32>(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<u8>(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<u16>(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<f32>(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<u8>(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<u16>(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<f32>(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<u8>(accessor, indices, model_mesh); break;
|
|
case cgltf_component_type_r_16u: SetIndices<u16>(accessor, indices, model_mesh); break;
|
|
case cgltf_component_type_r_32u: SetIndices<u32>(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<u8> 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<TextureID>(arena, m3d->numtexture),
|
|
materials: Alloc<Material>(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<Mesh>(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<Vertex> vertices = Alloc<Vertex>(temp_arena, m3d->numface*3);
|
|
Array<u32> indices = Alloc<u32>(temp_arena, m3d->numface*3);
|
|
Array<Vec3> positions = Alloc<Vec3>(temp_arena, m3d->numface);
|
|
Array<u32> position_indices = Alloc<u32>(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;
|
|
}
|