From 18db2c4f0135db3016a918e41e006b790a47755f Mon Sep 17 00:00:00 2001 From: raysan5 Date: Fri, 22 Oct 2021 21:27:11 +0200 Subject: [PATCH] REVIEWED: `LoadGLTF()` Reorganized code and added some feature and listed restrictions. Some gltf models do not work yet. --- src/rmodels.c | 422 ++++++++++++++++++++++++++++++++------------------ 1 file changed, 269 insertions(+), 153 deletions(-) diff --git a/src/rmodels.c b/src/rmodels.c index 79146f2d..8c68a026 100644 --- a/src/rmodels.c +++ b/src/rmodels.c @@ -4494,109 +4494,111 @@ static ModelAnimation* LoadModelAnimationsIQM(const char *fileName, unsigned int #endif #if defined(SUPPORT_FILEFORMAT_GLTF) -static Image LoadImageFromCgltfImage(cgltf_image *image, const char *texPath, Color tint) +// Load image from different glTF provided methods (uri, path, buffer_view) +static Image LoadImageFromCgltfImage(cgltf_image *cgltfImage, const char *texPath) { - Image rimage = { 0 }; + Image image = { 0 }; - if (image->uri) + if (cgltfImage->uri != NULL) // Check if image data is provided as a uri (base64 or path) { - if ((strlen(image->uri) > 5) && - (image->uri[0] == 'd') && - (image->uri[1] == 'a') && - (image->uri[2] == 't') && - (image->uri[3] == 'a') && - (image->uri[4] == ':')) + if ((strlen(cgltfImage->uri) > 5) && + (cgltfImage->uri[0] == 'd') && + (cgltfImage->uri[1] == 'a') && + (cgltfImage->uri[2] == 't') && + (cgltfImage->uri[3] == 'a') && + (cgltfImage->uri[4] == ':')) // Check if image is provided as base64 text data { - // Data URI - // Format: data:;base64, + // Data URI Format: data:;base64, // Find the comma int i = 0; - while ((image->uri[i] != ',') && (image->uri[i] != 0)) i++; + while ((cgltfImage->uri[i] != ',') && (cgltfImage->uri[i] != 0)) i++; - if (image->uri[i] == 0) TRACELOG(LOG_WARNING, "IMAGE: glTF data URI is not a valid image"); + if (cgltfImage->uri[i] == 0) TRACELOG(LOG_WARNING, "IMAGE: glTF data URI is not a valid image"); else { - int base64Size = strlen(image->uri + i + 1); + int base64Size = strlen(cgltfImage->uri + i + 1); int outSize = 3*(base64Size/4); // TODO: Consider padding (-numberOfPaddingCharacters) char *data = NULL; cgltf_options options = { 0 }; - cgltf_result result = cgltf_load_buffer_base64(&options, outSize, image->uri + i + 1, &data); + cgltf_result result = cgltf_load_buffer_base64(&options, outSize, cgltfImage->uri + i + 1, &data); if (result == cgltf_result_success) { - rimage = LoadImageFromMemory(".png", data, outSize); + image = LoadImageFromMemory(".png", data, outSize); cgltf_free(data); } - - // TODO: Tint shouldn't be applied here! - ImageColorTint(&rimage, tint); } } - else + else // Check if image is provided as image path { - rimage = LoadImage(TextFormat("%s/%s", texPath, image->uri)); - - // TODO: Tint shouldn't be applied here! - ImageColorTint(&rimage, tint); + image = LoadImage(TextFormat("%s/%s", texPath, cgltfImage->uri)); } } - else if (image->buffer_view) + else if (cgltfImage->buffer_view->buffer->data != NULL) // Check if image is provided as data buffer { - unsigned char *data = RL_MALLOC(image->buffer_view->size); - int n = (int)image->buffer_view->offset; - int stride = (int)image->buffer_view->stride ? (int)image->buffer_view->stride : 1; + unsigned char *data = RL_MALLOC(cgltfImage->buffer_view->size); + int offset = (int)cgltfImage->buffer_view->offset; + int stride = (int)cgltfImage->buffer_view->stride? (int)cgltfImage->buffer_view->stride : 1; - for (unsigned int i = 0; i < image->buffer_view->size; i++) + // Copy buffer data to memory for loading + for (unsigned int i = 0; i < cgltfImage->buffer_view->size; i++) { - data[i] = ((unsigned char *)image->buffer_view->buffer->data)[n]; - n += stride; + data[i] = ((unsigned char *)cgltfImage->buffer_view->buffer->data)[offset]; + offset += stride; } - rimage = LoadImageFromMemory(".png", data, (int)image->buffer_view->size); + // Check mime_type for image: (cgltfImage->mime_type == "image\\/png") + if (strcmp(cgltfImage->mime_type, "image\\/png") == 0) image = LoadImageFromMemory(".png", data, (int)cgltfImage->buffer_view->size); + else if (strcmp(cgltfImage->mime_type, "image\\/jpeg") == 0) image = LoadImageFromMemory(".jpg", data, (int)cgltfImage->buffer_view->size); + else TRACELOG(LOG_WARNING, "MODEL: glTF image data MIME type not recognized", TextFormat("%s/%s", texPath, cgltfImage->uri)); + RL_FREE(data); - - // TODO: Tint shouldn't be applied here! - ImageColorTint(&rimage, tint); } - else rimage = GenImageColor(1, 1, tint); - return rimage; + return image; } -// LoadGLTF loads in model data from given filename, supporting both .gltf and .glb +// Load glTF file into model struct, .gltf and .glb supported static Model LoadGLTF(const char *fileName) { - /*********************************************************************************** + /********************************************************************************************* Function implemented by Wilhem Barbier(@wbrbr), with modifications by Tyler Bezera(@gamerfiend) + Reviewed by Ramon Santamaria (@raysan5) - Features: + FEATURES: - Supports .gltf and .glb files - Supports embedded (base64) or external textures - - Loads all raylib supported material textures, values and colors - - Supports multiple mesh per model and multiple primitives per model + - Supports PBR metallic/roughness flow, loads material textures, values and colors + PBR specular/glossiness flow and extended texture flows not supported + - Supports multiple meshes per model (every primitives is loaded as a separate mesh) - Some restrictions (not exhaustive): - - Triangle-only meshes - - Not supported node hierarchies or transforms - - Only supports unsigned short indices (no byte/unsigned int) - - Only supports float for texture coordinates (no byte/unsigned short) + RESTRICTIONS: + - Only triangle meshes supported + - Vertex attibute types and formats supported: + > Vertices (position): vec3: float + > Normals: vec3: float + > Texcoords: vec2: float + > Colors: vec4: u8, u16, f32 (normalized) + > Indices: u16, u32 (truncated to u16) + - Node hierarchies or transforms not supported - *************************************************************************************/ + ***********************************************************************************************/ - #define LOAD_ACCESSOR(type, nbcomp, acc, dst) \ + // Macro to simplify attributes loading code + #define LOAD_ATTRIBUTE(accesor, numComp, dataType, dstPtr) \ { \ int n = 0; \ - type *buffer = (type *)acc->buffer_view->buffer->data + acc->buffer_view->offset/sizeof(type) + acc->offset/sizeof(type); \ - for (unsigned int k = 0; k < acc->count; k++) \ + dataType *buffer = (dataType *)accesor->buffer_view->buffer->data + accesor->buffer_view->offset/sizeof(dataType) + accesor->offset/sizeof(dataType); \ + for (unsigned int k = 0; k < accesor->count; k++) \ {\ - for (int l = 0; l < nbcomp; l++) \ + for (int l = 0; l < numComp; l++) \ {\ - dst[nbcomp*k + l] = buffer[n + l];\ + dstPtr[numComp*k + l] = buffer[n + l];\ }\ - n += (int)(acc->stride/sizeof(type));\ + n += (int)(accesor->stride/sizeof(dataType));\ }\ } @@ -4612,178 +4614,292 @@ static Model LoadGLTF(const char *fileName) cgltf_options options = { 0 }; cgltf_data *data = NULL; cgltf_result result = cgltf_parse(&options, fileData, dataSize, &data); - + if (result == cgltf_result_success) { - TRACELOG(LOG_INFO, "MODEL: [%s] glTF meshes (%s) count: %i", fileName, (data->file_type == 2)? "glb" : "gltf", data->meshes_count); - TRACELOG(LOG_INFO, "MODEL: [%s] glTF materials (%s) count: %i", fileName, (data->file_type == 2)? "glb" : "gltf", data->materials_count); + if (data->file_type == cgltf_file_type_glb) TRACELOG(LOG_INFO, "MODEL: [%s] Model basic data (glb) loaded successfully", fileName); + else if (data->file_type == cgltf_file_type_gltf) TRACELOG(LOG_INFO, "MODEL: [%s] Model basic data (glTF) loaded successfully", fileName); + else TRACELOG(LOG_WARNING, "MODEL: [%s] Model format not recognized", fileName); - // Read data buffers + TRACELOG(LOG_INFO, " > Meshes count: %i", data->meshes_count); + TRACELOG(LOG_INFO, " > Materials count: %i", data->materials_count); + TRACELOG(LOG_DEBUG, " > Buffers count: %i", data->buffers_count); + TRACELOG(LOG_DEBUG, " > Images count: %i", data->images_count); + TRACELOG(LOG_DEBUG, " > Textures count: %i", data->textures_count); + + // Force reading data buffers (fills buffer_view->buffer->data) + // NOTE: If an uri is defined to base64 data or external path, it's automatically loaded -> TODO: Verify this assumption result = cgltf_load_buffers(&options, data, fileName); if (result != cgltf_result_success) TRACELOG(LOG_INFO, "MODEL: [%s] Failed to load mesh/material buffers", fileName); int primitivesCount = 0; - + // NOTE: We will load every primitive in the glTF as a separate raylib mesh for (unsigned int i = 0; i < data->meshes_count; i++) primitivesCount += (int)data->meshes[i].primitives_count; - // Process glTF data and map to model + // Load our model data: meshes and materials model.meshCount = primitivesCount; model.meshes = RL_CALLOC(model.meshCount, sizeof(Mesh)); - model.materialCount = (int)data->materials_count + 1; - model.materials = RL_MALLOC(model.materialCount*sizeof(Material)); - model.meshMaterial = RL_MALLOC(model.meshCount*sizeof(int)); + for (int i = 0; i < model.meshCount; i++) model.meshes[i].vboId = (unsigned int*)RL_CALLOC(MAX_MESH_VERTEX_BUFFERS, sizeof(unsigned int)); - for (int i = 0; i < model.meshCount; i++) model.meshes[i].vboId = (unsigned int *)RL_CALLOC(MAX_MESH_VERTEX_BUFFERS, sizeof(unsigned int)); + // NOTE: We keep an extra slot for default material, in case some mesh requires it + model.materialCount = (int)data->materials_count + 1; + model.materials = RL_CALLOC(model.materialCount, sizeof(Material)); + model.materials[0] = LoadMaterialDefault(); // Load default material (index: 0) - for (int i = 0; i < model.materialCount - 1; i++) + // Load mesh-material indices, by default all meshes are mapped to material index: 0 + model.meshMaterial = RL_CALLOC(model.meshCount, sizeof(int)); + + // Load materials data + for (unsigned int i = 0, j = 1; i < data->materials_count; i++, j++) { - model.materials[i] = LoadMaterialDefault(); - Color tint = (Color){ 255, 255, 255, 255 }; + model.materials[j] = LoadMaterialDefault(); const char *texPath = GetDirectoryPath(fileName); - //Ensure material follows raylib support for PBR (metallic/roughness flow) + // Check glTF material flow: PBR metallic/roughness flow + // NOTE: Alternatively, materials can follow PBR specular/glossiness flow if (data->materials[i].has_pbr_metallic_roughness) { - tint.r = (unsigned char)(data->materials[i].pbr_metallic_roughness.base_color_factor[0] * 255); - tint.g = (unsigned char)(data->materials[i].pbr_metallic_roughness.base_color_factor[1] * 255); - tint.b = (unsigned char)(data->materials[i].pbr_metallic_roughness.base_color_factor[2] * 255); - tint.a = (unsigned char)(data->materials[i].pbr_metallic_roughness.base_color_factor[3] * 255); - - model.materials[i].maps[MATERIAL_MAP_ALBEDO].color = tint; - + // Load base color texture (albedo) if (data->materials[i].pbr_metallic_roughness.base_color_texture.texture) { - Image albedo = LoadImageFromCgltfImage(data->materials[i].pbr_metallic_roughness.base_color_texture.texture->image, texPath, tint); - model.materials[i].maps[MATERIAL_MAP_ALBEDO].texture = LoadTextureFromImage(albedo); - UnloadImage(albedo); + Image imAlbedo = LoadImageFromCgltfImage(data->materials[i].pbr_metallic_roughness.base_color_texture.texture->image, texPath); + if (imAlbedo.data != NULL) + { + model.materials[j].maps[MATERIAL_MAP_ALBEDO].texture = LoadTextureFromImage(imAlbedo); + UnloadImage(imAlbedo); + } + + // Load base color factor (tint) + model.materials[j].maps[MATERIAL_MAP_ALBEDO].color.r = (unsigned char)(data->materials[i].pbr_metallic_roughness.base_color_factor[0]*255); + model.materials[j].maps[MATERIAL_MAP_ALBEDO].color.g = (unsigned char)(data->materials[i].pbr_metallic_roughness.base_color_factor[1]*255); + model.materials[j].maps[MATERIAL_MAP_ALBEDO].color.b = (unsigned char)(data->materials[i].pbr_metallic_roughness.base_color_factor[2]*255); + model.materials[j].maps[MATERIAL_MAP_ALBEDO].color.a = (unsigned char)(data->materials[i].pbr_metallic_roughness.base_color_factor[3]*255); } - tint = WHITE; // Set tint to white after it's been used by Albedo - + // Load metallic/roughness texture if (data->materials[i].pbr_metallic_roughness.metallic_roughness_texture.texture) { - Image metallicRoughness = LoadImageFromCgltfImage(data->materials[i].pbr_metallic_roughness.metallic_roughness_texture.texture->image, texPath, tint); - model.materials[i].maps[MATERIAL_MAP_ROUGHNESS].texture = LoadTextureFromImage(metallicRoughness); - + Image imMetallicRoughness = LoadImageFromCgltfImage(data->materials[i].pbr_metallic_roughness.metallic_roughness_texture.texture->image, texPath); + if (imMetallicRoughness.data != NULL) + { + model.materials[j].maps[MATERIAL_MAP_ROUGHNESS].texture = LoadTextureFromImage(imMetallicRoughness); + UnloadImage(imMetallicRoughness); + } + + // Load metallic/roughness material properties float roughness = data->materials[i].pbr_metallic_roughness.roughness_factor; - model.materials[i].maps[MATERIAL_MAP_ROUGHNESS].value = roughness; + model.materials[j].maps[MATERIAL_MAP_ROUGHNESS].value = roughness; float metallic = data->materials[i].pbr_metallic_roughness.metallic_factor; - model.materials[i].maps[MATERIAL_MAP_METALNESS].value = metallic; - - UnloadImage(metallicRoughness); + model.materials[j].maps[MATERIAL_MAP_METALNESS].value = metallic; } + // Load normal texture if (data->materials[i].normal_texture.texture) { - Image normalImage = LoadImageFromCgltfImage(data->materials[i].normal_texture.texture->image, texPath, tint); - model.materials[i].maps[MATERIAL_MAP_NORMAL].texture = LoadTextureFromImage(normalImage); - UnloadImage(normalImage); + Image imNormal = LoadImageFromCgltfImage(data->materials[i].normal_texture.texture->image, texPath); + if (imNormal.data != NULL) + { + model.materials[j].maps[MATERIAL_MAP_NORMAL].texture = LoadTextureFromImage(imNormal); + UnloadImage(imNormal); + } } + // Load ambient occlusion texture if (data->materials[i].occlusion_texture.texture) { - Image occulsionImage = LoadImageFromCgltfImage(data->materials[i].occlusion_texture.texture->image, texPath, tint); - model.materials[i].maps[MATERIAL_MAP_OCCLUSION].texture = LoadTextureFromImage(occulsionImage); - UnloadImage(occulsionImage); + Image imOcclusion = LoadImageFromCgltfImage(data->materials[i].occlusion_texture.texture->image, texPath); + if (imOcclusion.data != NULL) + { + model.materials[j].maps[MATERIAL_MAP_OCCLUSION].texture = LoadTextureFromImage(imOcclusion); + UnloadImage(imOcclusion); + } } + // Load emissive texture if (data->materials[i].emissive_texture.texture) { - Image emissiveImage = LoadImageFromCgltfImage(data->materials[i].emissive_texture.texture->image, texPath, tint); - model.materials[i].maps[MATERIAL_MAP_EMISSION].texture = LoadTextureFromImage(emissiveImage); - tint.r = (unsigned char)(data->materials[i].emissive_factor[0]*255); - tint.g = (unsigned char)(data->materials[i].emissive_factor[1]*255); - tint.b = (unsigned char)(data->materials[i].emissive_factor[2]*255); - model.materials[i].maps[MATERIAL_MAP_EMISSION].color = tint; - UnloadImage(emissiveImage); + Image imEmissive = LoadImageFromCgltfImage(data->materials[i].emissive_texture.texture->image, texPath); + if (imEmissive.data != NULL) + { + model.materials[j].maps[MATERIAL_MAP_EMISSION].texture = LoadTextureFromImage(imEmissive); + UnloadImage(imEmissive); + } + + // Load emissive color factor + model.materials[j].maps[MATERIAL_MAP_EMISSION].color.r = (unsigned char)(data->materials[i].emissive_factor[0]*255); + model.materials[j].maps[MATERIAL_MAP_EMISSION].color.g = (unsigned char)(data->materials[i].emissive_factor[1]*255); + model.materials[j].maps[MATERIAL_MAP_EMISSION].color.b = (unsigned char)(data->materials[i].emissive_factor[2]*255); + model.materials[j].maps[MATERIAL_MAP_EMISSION].color.a = 255; } } + + // Other possible materials not supported by raylib pipeline: + // has_clearcoat, has_transmission, has_volume, has_ior, has specular, has_sheen } - model.materials[model.materialCount - 1] = LoadMaterialDefault(); - - int primitiveIndex = 0; - + // Load meshes data for (unsigned int i = 0; i < data->meshes_count; i++) { - for (unsigned int p = 0; p < data->meshes[i].primitives_count; p++) + for (unsigned int p = 0, primitiveIndex = 0; p < data->meshes[i].primitives_count; p++) { + // NOTE: We only support primitives defined by triangles + // Other alternatives: points, lines, line_strip, triangle_strip + if (data->meshes[i].primitives[p].type != cgltf_primitive_type_triangles) continue; + + // NOTE: Attributes data could be provided in several data formats (8, 8u, 16u, 32...), + // Only some formats for each attribute type are supported, read info at the top of this function! + for (unsigned int j = 0; j < data->meshes[i].primitives[p].attributes_count; j++) { - if (data->meshes[i].primitives[p].attributes[j].type == cgltf_attribute_type_position) + // Check the different attributes for every pimitive + if (data->meshes[i].primitives[p].attributes[j].type == cgltf_attribute_type_position) // POSITION { - cgltf_accessor *acc = data->meshes[i].primitives[p].attributes[j].data; - model.meshes[primitiveIndex].vertexCount = (int)acc->count; - model.meshes[primitiveIndex].vertices = RL_MALLOC(model.meshes[primitiveIndex].vertexCount*3*sizeof(float)); + cgltf_accessor *attribute = data->meshes[i].primitives[p].attributes[j].data; - LOAD_ACCESSOR(float, 3, acc, model.meshes[primitiveIndex].vertices) - } - else if (data->meshes[i].primitives[p].attributes[j].type == cgltf_attribute_type_normal) - { - cgltf_accessor *acc = data->meshes[i].primitives[p].attributes[j].data; - model.meshes[primitiveIndex].normals = RL_MALLOC(acc->count*3*sizeof(float)); - - LOAD_ACCESSOR(float, 3, acc, model.meshes[primitiveIndex].normals) - } - else if (data->meshes[i].primitives[p].attributes[j].type == cgltf_attribute_type_texcoord) - { - cgltf_accessor *acc = data->meshes[i].primitives[p].attributes[j].data; - - if (acc->component_type == cgltf_component_type_r_32f) + if ((attribute->component_type == cgltf_component_type_r_32f) && (attribute->type == cgltf_type_vec3)) { - model.meshes[primitiveIndex].texcoords = RL_MALLOC(acc->count*2*sizeof(float)); - LOAD_ACCESSOR(float, 2, acc, model.meshes[primitiveIndex].texcoords) + // Init raylib mesh vertices to copy glTF attribute data + model.meshes[primitiveIndex].vertexCount = (int)attribute->count; + model.meshes[primitiveIndex].vertices = RL_MALLOC(attribute->count*3*sizeof(float)); + + // Load 3 components of float data type into mesh.vertices + LOAD_ATTRIBUTE(attribute, 3, float, model.meshes[primitiveIndex].vertices) } - else + else TRACELOG(LOG_WARNING, "MODEL: [%s] Vertices attribute data format not supported, use vec3 float", fileName); + } + else if (data->meshes[i].primitives[p].attributes[j].type == cgltf_attribute_type_normal) // NORMAL + { + cgltf_accessor *attribute = data->meshes[i].primitives[p].attributes[j].data; + + if ((attribute->component_type == cgltf_component_type_r_32f) && (attribute->type == cgltf_type_vec3)) { - // TODO: Support normalized unsigned byte/unsigned short texture coordinates - TRACELOG(LOG_WARNING, "MODEL: [%s] glTF texture coordinates must be float", fileName); + // Init raylib mesh normals to copy glTF attribute data + model.meshes[primitiveIndex].normals = RL_MALLOC(attribute->count*3*sizeof(float)); + + // Load 3 components of float data type into mesh.normals + LOAD_ATTRIBUTE(attribute, 3, float, model.meshes[primitiveIndex].normals) } + else TRACELOG(LOG_WARNING, "MODEL: [%s] Normal attribute data format not supported, use vec3 float", fileName); } - } - - cgltf_accessor *acc = data->meshes[i].primitives[p].indices; - - if (acc) - { - if (acc->component_type == cgltf_component_type_r_16u) + else if (data->meshes[i].primitives[p].attributes[j].type == cgltf_attribute_type_texcoord) // TEXCOORD_0 { - model.meshes[primitiveIndex].triangleCount = (int)acc->count/3; - model.meshes[primitiveIndex].indices = RL_MALLOC(model.meshes[primitiveIndex].triangleCount*3*sizeof(unsigned short)); - LOAD_ACCESSOR(unsigned short, 1, acc, model.meshes[primitiveIndex].indices) + cgltf_accessor *attribute = data->meshes[i].primitives[p].attributes[j].data; + + if ((attribute->component_type == cgltf_component_type_r_32f) && (attribute->type == cgltf_type_vec2)) + { + // Init raylib mesh texcoords to copy glTF attribute data + model.meshes[primitiveIndex].texcoords = RL_MALLOC(attribute->count*2*sizeof(float)); + + // Load 3 components of float data type into mesh.texcoords + LOAD_ATTRIBUTE(attribute, 2, float, model.meshes[primitiveIndex].texcoords) + } + else TRACELOG(LOG_WARNING, "MODEL: [%s] Texcoords attribute data format not supported, use vec2 float", fileName); } - else + else if (data->meshes[i].primitives[p].attributes[j].type == cgltf_attribute_type_color) // COLOR_0 { - // TODO: Support unsigned byte/unsigned int - TRACELOG(LOG_WARNING, "MODEL: [%s] glTF index data must be unsigned short", fileName); + cgltf_accessor *attribute = data->meshes[i].primitives[p].attributes[j].data; + + if ((attribute->component_type == cgltf_component_type_r_8u) && (attribute->type == cgltf_type_vec4)) + { + // Init raylib mesh color to copy glTF attribute data + model.meshes[primitiveIndex].colors = RL_MALLOC(attribute->count*4*sizeof(unsigned char)); + + // Load 4 components of unsigned char data type into mesh.colors + LOAD_ATTRIBUTE(attribute, 4, unsigned char, model.meshes[primitiveIndex].colors) + } + else if ((attribute->component_type == cgltf_component_type_r_16u) && (attribute->type == cgltf_type_vec4)) + { + // Init raylib mesh color to copy glTF attribute data + model.meshes[primitiveIndex].colors = RL_MALLOC(attribute->count*4*sizeof(unsigned char)); + + // Load data into a temp buffer to be converted to raylib data type + unsigned short *temp = RL_MALLOC(attribute->count*4*sizeof(unsigned short)); + LOAD_ATTRIBUTE(attribute, 4, unsigned short, temp); + + // Convert data to raylib color data type (4 bytes) + for (int c = 0; c < attribute->count*4; c++) model.meshes[primitiveIndex].colors[c] = (unsigned char)(((float)temp[c]/65535.0f)*255.0f); + + RL_FREE(temp); + } + else if ((attribute->component_type == cgltf_component_type_r_32f) && (attribute->type == cgltf_type_vec4)) + { + // Init raylib mesh color to copy glTF attribute data + model.meshes[primitiveIndex].colors = RL_MALLOC(attribute->count*4*sizeof(unsigned char)); + + // Load data into a temp buffer to be converted to raylib data type + float *temp = RL_MALLOC(attribute->count*4*sizeof(float)); + LOAD_ATTRIBUTE(attribute, 4, float, temp); + + // Convert data to raylib color data type (4 bytes), we expect the color data normalized + for (int c = 0; c < attribute->count*4; c++) model.meshes[primitiveIndex].colors[c] = (unsigned char)(temp[c]*255.0f); + + RL_FREE(temp); + } + else TRACELOG(LOG_WARNING, "MODEL: [%s] Color attribute data format not supported", fileName); } - } - else - { - // Unindexed mesh - model.meshes[primitiveIndex].triangleCount = model.meshes[primitiveIndex].vertexCount/3; + + // TODO: Additional attributes that could be supported (some related to animations): + // cgltf_attribute_type_tangent, cgltf_attribute_type_joints, cgltf_attribute_type_weights } - if (data->meshes[i].primitives[p].material) + // Load primitive indices data (if provided) + cgltf_accessor *attribute = data->meshes[i].primitives[p].indices; + + if (attribute != NULL) { - // Compute the offset - model.meshMaterial[primitiveIndex] = (int)(data->meshes[i].primitives[p].material - data->materials); + model.meshes[primitiveIndex].triangleCount = (int)attribute->count/3; + + if (attribute->component_type == cgltf_component_type_r_16u) + { + // Init raylib mesh indices to copy glTF attribute data + model.meshes[primitiveIndex].indices = RL_MALLOC(attribute->count*sizeof(unsigned short)); + + // Load unsigned short data type into mesh.indices + LOAD_ATTRIBUTE(attribute, 1, unsigned short, model.meshes[primitiveIndex].indices) + } + else if (attribute->component_type == cgltf_component_type_r_32u) + { + // Init raylib mesh indices to copy glTF attribute data + model.meshes[primitiveIndex].indices = RL_MALLOC(attribute->count*sizeof(unsigned short)); + + // Load data into a temp buffer to be converted to raylib data type + unsigned int *temp = RL_MALLOC(attribute->count*sizeof(unsigned int)); + LOAD_ATTRIBUTE(attribute, 1, unsigned int, temp); + + // Convert data to raylib indices data type (unsigned short) + for (int d = 0; d < attribute->count; d++) model.meshes[primitiveIndex].indices[d] = (unsigned short)temp[d]; + + TRACELOG(LOG_WARNING, "MODEL: [%s] Indices data converted from u32 to u16, possible loss of data", fileName); + + RL_FREE(temp); + } + else TRACELOG(LOG_WARNING, "MODEL: [%s] Indices data format not supported, use u16", fileName); } - else + else model.meshes[primitiveIndex].triangleCount = model.meshes[primitiveIndex].vertexCount/3; // Unindexed mesh + + // Assign to the primitive mesh the corresponding material index + // NOTE: If no material defined, mesh uses the already assigned default material (index: 0) + for (int m = 0; m < data->materials_count; m++) { - model.meshMaterial[primitiveIndex] = model.materialCount - 1;; + // The primitive actually keeps the pointer to the corresponding material, + // raylib instead assigns to the mesh the by its index, as loaded in model.materials array + // To get the index, we check if material pointers match and we assign the corresponding index, + // skipping index 0, the default material + if (&data->materials[i] == data->meshes[i].primitives[p].material) model.meshMaterial[primitiveIndex] = m + 1; } - primitiveIndex++; + primitiveIndex++; // Move to next primitive } } + // Free all cgltf loaded data cgltf_free(data); } else TRACELOG(LOG_WARNING, "MODEL: [%s] Failed to load glTF data", fileName); - RL_FREE(fileData); + // WARNING: cgltf requires the file pointer available while reading data + UnloadFileData(fileData); return model; }