REVIEWED: LoadGLTF()

Reorganized code and added some feature and listed restrictions. Some gltf models do not work yet.
This commit is contained in:
raysan5 2021-10-22 21:27:11 +02:00
parent 1fd8052527
commit 18db2c4f01

View file

@ -4494,109 +4494,111 @@ static ModelAnimation* LoadModelAnimationsIQM(const char *fileName, unsigned int
#endif #endif
#if defined(SUPPORT_FILEFORMAT_GLTF) #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) && if ((strlen(cgltfImage->uri) > 5) &&
(image->uri[0] == 'd') && (cgltfImage->uri[0] == 'd') &&
(image->uri[1] == 'a') && (cgltfImage->uri[1] == 'a') &&
(image->uri[2] == 't') && (cgltfImage->uri[2] == 't') &&
(image->uri[3] == 'a') && (cgltfImage->uri[3] == 'a') &&
(image->uri[4] == ':')) (cgltfImage->uri[4] == ':')) // Check if image is provided as base64 text data
{ {
// Data URI // Data URI Format: data:<mediatype>;base64,<data>
// Format: data:<mediatype>;base64,<data>
// Find the comma // Find the comma
int i = 0; 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 else
{ {
int base64Size = strlen(image->uri + i + 1); int base64Size = strlen(cgltfImage->uri + i + 1);
int outSize = 3*(base64Size/4); // TODO: Consider padding (-numberOfPaddingCharacters) int outSize = 3*(base64Size/4); // TODO: Consider padding (-numberOfPaddingCharacters)
char *data = NULL; char *data = NULL;
cgltf_options options = { 0 }; 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) if (result == cgltf_result_success)
{ {
rimage = LoadImageFromMemory(".png", data, outSize); image = LoadImageFromMemory(".png", data, outSize);
cgltf_free(data); 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)); image = LoadImage(TextFormat("%s/%s", texPath, cgltfImage->uri));
// TODO: Tint shouldn't be applied here!
ImageColorTint(&rimage, tint);
} }
} }
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); unsigned char *data = RL_MALLOC(cgltfImage->buffer_view->size);
int n = (int)image->buffer_view->offset; int offset = (int)cgltfImage->buffer_view->offset;
int stride = (int)image->buffer_view->stride ? (int)image->buffer_view->stride : 1; 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]; data[i] = ((unsigned char *)cgltfImage->buffer_view->buffer->data)[offset];
n += stride; 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); 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) static Model LoadGLTF(const char *fileName)
{ {
/*********************************************************************************** /*********************************************************************************************
Function implemented by Wilhem Barbier(@wbrbr), with modifications by Tyler Bezera(@gamerfiend) 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 .gltf and .glb files
- Supports embedded (base64) or external textures - Supports embedded (base64) or external textures
- Loads all raylib supported material textures, values and colors - Supports PBR metallic/roughness flow, loads material textures, values and colors
- Supports multiple mesh per model and multiple primitives per model 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): RESTRICTIONS:
- Triangle-only meshes - Only triangle meshes supported
- Not supported node hierarchies or transforms - Vertex attibute types and formats supported:
- Only supports unsigned short indices (no byte/unsigned int) > Vertices (position): vec3: float
- Only supports float for texture coordinates (no byte/unsigned short) > 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; \ int n = 0; \
type *buffer = (type *)acc->buffer_view->buffer->data + acc->buffer_view->offset/sizeof(type) + acc->offset/sizeof(type); \ dataType *buffer = (dataType *)accesor->buffer_view->buffer->data + accesor->buffer_view->offset/sizeof(dataType) + accesor->offset/sizeof(dataType); \
for (unsigned int k = 0; k < acc->count; k++) \ 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_options options = { 0 };
cgltf_data *data = NULL; cgltf_data *data = NULL;
cgltf_result result = cgltf_parse(&options, fileData, dataSize, &data); cgltf_result result = cgltf_parse(&options, fileData, dataSize, &data);
if (result == cgltf_result_success) 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); if (data->file_type == cgltf_file_type_glb) TRACELOG(LOG_INFO, "MODEL: [%s] Model basic data (glb) loaded successfully", fileName);
TRACELOG(LOG_INFO, "MODEL: [%s] glTF materials (%s) count: %i", fileName, (data->file_type == 2)? "glb" : "gltf", data->materials_count); 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); result = cgltf_load_buffers(&options, data, fileName);
if (result != cgltf_result_success) TRACELOG(LOG_INFO, "MODEL: [%s] Failed to load mesh/material buffers", fileName); if (result != cgltf_result_success) TRACELOG(LOG_INFO, "MODEL: [%s] Failed to load mesh/material buffers", fileName);
int primitivesCount = 0; 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; 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.meshCount = primitivesCount;
model.meshes = RL_CALLOC(model.meshCount, sizeof(Mesh)); model.meshes = RL_CALLOC(model.meshCount, sizeof(Mesh));
model.materialCount = (int)data->materials_count + 1; for (int i = 0; i < model.meshCount; i++) model.meshes[i].vboId = (unsigned int*)RL_CALLOC(MAX_MESH_VERTEX_BUFFERS, sizeof(unsigned int));
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)); // 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(); model.materials[j] = LoadMaterialDefault();
Color tint = (Color){ 255, 255, 255, 255 };
const char *texPath = GetDirectoryPath(fileName); 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) if (data->materials[i].has_pbr_metallic_roughness)
{ {
tint.r = (unsigned char)(data->materials[i].pbr_metallic_roughness.base_color_factor[0] * 255); // Load base color texture (albedo)
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;
if (data->materials[i].pbr_metallic_roughness.base_color_texture.texture) 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); Image imAlbedo = LoadImageFromCgltfImage(data->materials[i].pbr_metallic_roughness.base_color_texture.texture->image, texPath);
model.materials[i].maps[MATERIAL_MAP_ALBEDO].texture = LoadTextureFromImage(albedo); if (imAlbedo.data != NULL)
UnloadImage(albedo); {
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) 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); Image imMetallicRoughness = LoadImageFromCgltfImage(data->materials[i].pbr_metallic_roughness.metallic_roughness_texture.texture->image, texPath);
model.materials[i].maps[MATERIAL_MAP_ROUGHNESS].texture = LoadTextureFromImage(metallicRoughness); 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; 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; float metallic = data->materials[i].pbr_metallic_roughness.metallic_factor;
model.materials[i].maps[MATERIAL_MAP_METALNESS].value = metallic; model.materials[j].maps[MATERIAL_MAP_METALNESS].value = metallic;
UnloadImage(metallicRoughness);
} }
// Load normal texture
if (data->materials[i].normal_texture.texture) if (data->materials[i].normal_texture.texture)
{ {
Image normalImage = LoadImageFromCgltfImage(data->materials[i].normal_texture.texture->image, texPath, tint); Image imNormal = LoadImageFromCgltfImage(data->materials[i].normal_texture.texture->image, texPath);
model.materials[i].maps[MATERIAL_MAP_NORMAL].texture = LoadTextureFromImage(normalImage); if (imNormal.data != NULL)
UnloadImage(normalImage); {
model.materials[j].maps[MATERIAL_MAP_NORMAL].texture = LoadTextureFromImage(imNormal);
UnloadImage(imNormal);
}
} }
// Load ambient occlusion texture
if (data->materials[i].occlusion_texture.texture) if (data->materials[i].occlusion_texture.texture)
{ {
Image occulsionImage = LoadImageFromCgltfImage(data->materials[i].occlusion_texture.texture->image, texPath, tint); Image imOcclusion = LoadImageFromCgltfImage(data->materials[i].occlusion_texture.texture->image, texPath);
model.materials[i].maps[MATERIAL_MAP_OCCLUSION].texture = LoadTextureFromImage(occulsionImage); if (imOcclusion.data != NULL)
UnloadImage(occulsionImage); {
model.materials[j].maps[MATERIAL_MAP_OCCLUSION].texture = LoadTextureFromImage(imOcclusion);
UnloadImage(imOcclusion);
}
} }
// Load emissive texture
if (data->materials[i].emissive_texture.texture) if (data->materials[i].emissive_texture.texture)
{ {
Image emissiveImage = LoadImageFromCgltfImage(data->materials[i].emissive_texture.texture->image, texPath, tint); Image imEmissive = LoadImageFromCgltfImage(data->materials[i].emissive_texture.texture->image, texPath);
model.materials[i].maps[MATERIAL_MAP_EMISSION].texture = LoadTextureFromImage(emissiveImage); if (imEmissive.data != NULL)
tint.r = (unsigned char)(data->materials[i].emissive_factor[0]*255); {
tint.g = (unsigned char)(data->materials[i].emissive_factor[1]*255); model.materials[j].maps[MATERIAL_MAP_EMISSION].texture = LoadTextureFromImage(imEmissive);
tint.b = (unsigned char)(data->materials[i].emissive_factor[2]*255); UnloadImage(imEmissive);
model.materials[i].maps[MATERIAL_MAP_EMISSION].color = tint; }
UnloadImage(emissiveImage);
// 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(); // Load meshes data
int primitiveIndex = 0;
for (unsigned int i = 0; i < data->meshes_count; i++) 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++) 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; cgltf_accessor *attribute = 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));
LOAD_ACCESSOR(float, 3, acc, model.meshes[primitiveIndex].vertices) if ((attribute->component_type == cgltf_component_type_r_32f) && (attribute->type == cgltf_type_vec3))
}
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)
{ {
model.meshes[primitiveIndex].texcoords = RL_MALLOC(acc->count*2*sizeof(float)); // Init raylib mesh vertices to copy glTF attribute data
LOAD_ACCESSOR(float, 2, acc, model.meshes[primitiveIndex].texcoords) 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 // Init raylib mesh normals to copy glTF attribute data
TRACELOG(LOG_WARNING, "MODEL: [%s] glTF texture coordinates must be float", fileName); 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);
} }
} else if (data->meshes[i].primitives[p].attributes[j].type == cgltf_attribute_type_texcoord) // TEXCOORD_0
cgltf_accessor *acc = data->meshes[i].primitives[p].indices;
if (acc)
{
if (acc->component_type == cgltf_component_type_r_16u)
{ {
model.meshes[primitiveIndex].triangleCount = (int)acc->count/3; cgltf_accessor *attribute = data->meshes[i].primitives[p].attributes[j].data;
model.meshes[primitiveIndex].indices = RL_MALLOC(model.meshes[primitiveIndex].triangleCount*3*sizeof(unsigned short));
LOAD_ACCESSOR(unsigned short, 1, acc, model.meshes[primitiveIndex].indices) 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 cgltf_accessor *attribute = data->meshes[i].primitives[p].attributes[j].data;
TRACELOG(LOG_WARNING, "MODEL: [%s] glTF index data must be unsigned short", fileName);
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 // TODO: Additional attributes that could be supported (some related to animations):
{ // cgltf_attribute_type_tangent, cgltf_attribute_type_joints, cgltf_attribute_type_weights
// Unindexed mesh
model.meshes[primitiveIndex].triangleCount = model.meshes[primitiveIndex].vertexCount/3;
} }
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.meshes[primitiveIndex].triangleCount = (int)attribute->count/3;
model.meshMaterial[primitiveIndex] = (int)(data->meshes[i].primitives[p].material - data->materials);
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); cgltf_free(data);
} }
else TRACELOG(LOG_WARNING, "MODEL: [%s] Failed to load glTF data", fileName); 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; return model;
} }