mirror of
https://github.com/raysan5/raylib.git
synced 2025-09-06 19:38:15 +00:00
[rmodels] Initial work to correctly handle the node hierarchy in a glTF file (#4037)
* Initial work to correctly handle the node hierarchy in a glTF file. Static meshes seem to work fine in my tests. Haven't tried anything animated yet, but it's almost certainly broken. * Fix variable naming, some comment tweaks * Only count primitives made up of triangles in glTF meshes * Update processing of gltf mesh animation data, to match earlier changes to vertex/normal/tangent data
This commit is contained in:
143
src/rmodels.c
143
src/rmodels.c
@@ -4895,6 +4895,7 @@ 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)
|
||||||
|
Transform handling implemented by Paul Melis (@paulmelis).
|
||||||
Reviewed by Ramon Santamaria (@raysan5)
|
Reviewed by Ramon Santamaria (@raysan5)
|
||||||
|
|
||||||
FEATURES:
|
FEATURES:
|
||||||
@@ -4904,6 +4905,10 @@ static Model LoadGLTF(const char *fileName)
|
|||||||
PBR specular/glossiness flow and extended texture flows not supported
|
PBR specular/glossiness flow and extended texture flows not supported
|
||||||
- Supports multiple meshes per model (every primitives is loaded as a separate mesh)
|
- Supports multiple meshes per model (every primitives is loaded as a separate mesh)
|
||||||
- Supports basic animations
|
- Supports basic animations
|
||||||
|
- Transforms, including parent-child relations, are applied on the mesh data, but the
|
||||||
|
hierarchy is not kept (as it can't be represented).
|
||||||
|
- Mesh instances in the glTF file (i.e. same mesh linked from multiple nodes)
|
||||||
|
are turned into separate raylib Meshes.
|
||||||
|
|
||||||
RESTRICTIONS:
|
RESTRICTIONS:
|
||||||
- Only triangle meshes supported
|
- Only triangle meshes supported
|
||||||
@@ -4913,7 +4918,8 @@ static Model LoadGLTF(const char *fileName)
|
|||||||
> Texcoords: vec2: float
|
> Texcoords: vec2: float
|
||||||
> Colors: vec4: u8, u16, f32 (normalized)
|
> Colors: vec4: u8, u16, f32 (normalized)
|
||||||
> Indices: u16, u32 (truncated to u16)
|
> Indices: u16, u32 (truncated to u16)
|
||||||
- Node hierarchies or transforms not supported
|
- Scenes defined in the glTF file are ignored. All nodes in the file
|
||||||
|
are used.
|
||||||
|
|
||||||
***********************************************************************************************/
|
***********************************************************************************************/
|
||||||
|
|
||||||
@@ -4965,8 +4971,22 @@ static Model LoadGLTF(const char *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
|
// 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;
|
// Determine total number of meshes needed from the node hierarchy.
|
||||||
|
for (unsigned int i = 0; i < data->nodes_count; i++)
|
||||||
|
{
|
||||||
|
cgltf_node *node = &(data->nodes[i]);
|
||||||
|
cgltf_mesh *mesh = node->mesh;
|
||||||
|
if (!mesh)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
for (unsigned int p = 0; p < mesh->primitives_count; p++)
|
||||||
|
{
|
||||||
|
if (mesh->primitives[p].type == cgltf_primitive_type_triangles)
|
||||||
|
primitivesCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TRACELOG(LOG_DEBUG, " > Primitives (triangles only) count based on hierarchy : %i", primitivesCount);
|
||||||
|
|
||||||
// Load our model data: meshes and materials
|
// Load our model data: meshes and materials
|
||||||
model.meshCount = primitivesCount;
|
model.meshCount = primitivesCount;
|
||||||
@@ -5069,27 +5089,51 @@ static Model LoadGLTF(const char *fileName)
|
|||||||
// has_clearcoat, has_transmission, has_volume, has_ior, has specular, has_sheen
|
// has_clearcoat, has_transmission, has_volume, has_ior, has specular, has_sheen
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load meshes data
|
// Visit each node in the hierarchy and process any mesh linked from it.
|
||||||
|
// Each primitive within a glTF node becomes a Raylib Mesh.
|
||||||
|
// The local-to-world transform of each node is used to transform the
|
||||||
|
// points/normals/tangents of the created Mesh(es).
|
||||||
|
// Any glTF mesh linked from more than one Node (i.e. instancing)
|
||||||
|
// is turned into multiple Mesh's, as each Node will have its own
|
||||||
|
// transform applied.
|
||||||
|
// Note: the code below disregards the scenes defined in the file, all nodes are used.
|
||||||
//----------------------------------------------------------------------------------------------------
|
//----------------------------------------------------------------------------------------------------
|
||||||
for (unsigned int i = 0, meshIndex = 0; i < data->meshes_count; i++)
|
int meshIndex = 0;
|
||||||
|
for (unsigned int i = 0; i < data->nodes_count; i++)
|
||||||
{
|
{
|
||||||
// NOTE: meshIndex accumulates primitives
|
cgltf_node *node = &(data->nodes[i]);
|
||||||
|
|
||||||
for (unsigned int p = 0; p < data->meshes[i].primitives_count; p++)
|
cgltf_mesh *mesh = node->mesh;
|
||||||
|
if (!mesh)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
cgltf_float worldTransform[16];
|
||||||
|
cgltf_node_transform_world(node, worldTransform);
|
||||||
|
|
||||||
|
Matrix worldMatrix = {
|
||||||
|
worldTransform[0], worldTransform[4], worldTransform[8], worldTransform[12],
|
||||||
|
worldTransform[1], worldTransform[5], worldTransform[9], worldTransform[13],
|
||||||
|
worldTransform[2], worldTransform[6], worldTransform[10], worldTransform[14],
|
||||||
|
worldTransform[3], worldTransform[7], worldTransform[11], worldTransform[15]
|
||||||
|
};
|
||||||
|
|
||||||
|
Matrix worldMatrixNormals = MatrixTranspose(MatrixInvert(worldMatrix));
|
||||||
|
|
||||||
|
for (unsigned int p = 0; p < mesh->primitives_count; p++)
|
||||||
{
|
{
|
||||||
// NOTE: We only support primitives defined by triangles
|
// NOTE: We only support primitives defined by triangles
|
||||||
// Other alternatives: points, lines, line_strip, triangle_strip
|
// Other alternatives: points, lines, line_strip, triangle_strip
|
||||||
if (data->meshes[i].primitives[p].type != cgltf_primitive_type_triangles) continue;
|
if (mesh->primitives[p].type != cgltf_primitive_type_triangles) continue;
|
||||||
|
|
||||||
// NOTE: Attributes data could be provided in several data formats (8, 8u, 16u, 32...),
|
// 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!
|
// 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 < mesh->primitives[p].attributes_count; j++)
|
||||||
{
|
{
|
||||||
// Check the different attributes for every primitive
|
// Check the different attributes for every primitive
|
||||||
if (data->meshes[i].primitives[p].attributes[j].type == cgltf_attribute_type_position) // POSITION, vec3, float
|
if (mesh->primitives[p].attributes[j].type == cgltf_attribute_type_position) // POSITION, vec3, float
|
||||||
{
|
{
|
||||||
cgltf_accessor *attribute = data->meshes[i].primitives[p].attributes[j].data;
|
cgltf_accessor *attribute = mesh->primitives[p].attributes[j].data;
|
||||||
|
|
||||||
// WARNING: SPECS: POSITION accessor MUST have its min and max properties defined
|
// WARNING: SPECS: POSITION accessor MUST have its min and max properties defined
|
||||||
|
|
||||||
@@ -5101,12 +5145,22 @@ static Model LoadGLTF(const char *fileName)
|
|||||||
|
|
||||||
// Load 3 components of float data type into mesh.vertices
|
// Load 3 components of float data type into mesh.vertices
|
||||||
LOAD_ATTRIBUTE(attribute, 3, float, model.meshes[meshIndex].vertices)
|
LOAD_ATTRIBUTE(attribute, 3, float, model.meshes[meshIndex].vertices)
|
||||||
|
|
||||||
|
// Transform the vertices
|
||||||
|
float *vertices = model.meshes[meshIndex].vertices;
|
||||||
|
for (int k = 0; k < attribute->count; k++)
|
||||||
|
{
|
||||||
|
Vector3 vt = Vector3Transform((Vector3){ vertices[3*k], vertices[3*k+1], vertices[3*k+2] }, worldMatrix);
|
||||||
|
vertices[3*k] = vt.x;
|
||||||
|
vertices[3*k+1] = vt.y;
|
||||||
|
vertices[3*k+2] = vt.z;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else TRACELOG(LOG_WARNING, "MODEL: [%s] Vertices attribute data format not supported, use vec3 float", fileName);
|
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, vec3, float
|
else if (mesh->primitives[p].attributes[j].type == cgltf_attribute_type_normal) // NORMAL, vec3, float
|
||||||
{
|
{
|
||||||
cgltf_accessor *attribute = data->meshes[i].primitives[p].attributes[j].data;
|
cgltf_accessor *attribute = mesh->primitives[p].attributes[j].data;
|
||||||
|
|
||||||
if ((attribute->type == cgltf_type_vec3) && (attribute->component_type == cgltf_component_type_r_32f))
|
if ((attribute->type == cgltf_type_vec3) && (attribute->component_type == cgltf_component_type_r_32f))
|
||||||
{
|
{
|
||||||
@@ -5115,12 +5169,22 @@ static Model LoadGLTF(const char *fileName)
|
|||||||
|
|
||||||
// Load 3 components of float data type into mesh.normals
|
// Load 3 components of float data type into mesh.normals
|
||||||
LOAD_ATTRIBUTE(attribute, 3, float, model.meshes[meshIndex].normals)
|
LOAD_ATTRIBUTE(attribute, 3, float, model.meshes[meshIndex].normals)
|
||||||
|
|
||||||
|
// Transform the normals
|
||||||
|
float *normals = model.meshes[meshIndex].normals;
|
||||||
|
for (int k = 0; k < attribute->count; k++)
|
||||||
|
{
|
||||||
|
Vector3 nt = Vector3Transform((Vector3){ normals[3*k], normals[3*k+1], normals[3*k+2] }, worldMatrixNormals);
|
||||||
|
normals[3*k] = nt.x;
|
||||||
|
normals[3*k+1] = nt.y;
|
||||||
|
normals[3*k+2] = nt.z;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else TRACELOG(LOG_WARNING, "MODEL: [%s] Normal attribute data format not supported, use vec3 float", fileName);
|
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_tangent) // TANGENT, vec3, float
|
else if (mesh->primitives[p].attributes[j].type == cgltf_attribute_type_tangent) // TANGENT, vec3, float
|
||||||
{
|
{
|
||||||
cgltf_accessor *attribute = data->meshes[i].primitives[p].attributes[j].data;
|
cgltf_accessor *attribute = mesh->primitives[p].attributes[j].data;
|
||||||
|
|
||||||
if ((attribute->type == cgltf_type_vec4) && (attribute->component_type == cgltf_component_type_r_32f))
|
if ((attribute->type == cgltf_type_vec4) && (attribute->component_type == cgltf_component_type_r_32f))
|
||||||
{
|
{
|
||||||
@@ -5129,15 +5193,25 @@ static Model LoadGLTF(const char *fileName)
|
|||||||
|
|
||||||
// Load 4 components of float data type into mesh.tangents
|
// Load 4 components of float data type into mesh.tangents
|
||||||
LOAD_ATTRIBUTE(attribute, 4, float, model.meshes[meshIndex].tangents)
|
LOAD_ATTRIBUTE(attribute, 4, float, model.meshes[meshIndex].tangents)
|
||||||
|
|
||||||
|
// Transform the tangents
|
||||||
|
float *tangents = model.meshes[meshIndex].tangents;
|
||||||
|
for (int k = 0; k < attribute->count; k++)
|
||||||
|
{
|
||||||
|
Vector3 tt = Vector3Transform((Vector3){ tangents[3*k], tangents[3*k+1], tangents[3*k+2] }, worldMatrix);
|
||||||
|
tangents[3*k] = tt.x;
|
||||||
|
tangents[3*k+1] = tt.y;
|
||||||
|
tangents[3*k+2] = tt.z;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else TRACELOG(LOG_WARNING, "MODEL: [%s] Tangent attribute data format not supported, use vec4 float", fileName);
|
else TRACELOG(LOG_WARNING, "MODEL: [%s] Tangent attribute data format not supported, use vec4 float", fileName);
|
||||||
}
|
}
|
||||||
else if (data->meshes[i].primitives[p].attributes[j].type == cgltf_attribute_type_texcoord) // TEXCOORD_n, vec2, float/u8n/u16n
|
else if (mesh->primitives[p].attributes[j].type == cgltf_attribute_type_texcoord) // TEXCOORD_n, vec2, float/u8n/u16n
|
||||||
{
|
{
|
||||||
// Support up to 2 texture coordinates attributes
|
// Support up to 2 texture coordinates attributes
|
||||||
float *texcoordPtr = NULL;
|
float *texcoordPtr = NULL;
|
||||||
|
|
||||||
cgltf_accessor *attribute = data->meshes[i].primitives[p].attributes[j].data;
|
cgltf_accessor *attribute = mesh->primitives[p].attributes[j].data;
|
||||||
|
|
||||||
if (attribute->type == cgltf_type_vec2)
|
if (attribute->type == cgltf_type_vec2)
|
||||||
{
|
{
|
||||||
@@ -5181,7 +5255,7 @@ static Model LoadGLTF(const char *fileName)
|
|||||||
}
|
}
|
||||||
else TRACELOG(LOG_WARNING, "MODEL: [%s] Texcoords attribute data format not supported, use vec2 float", fileName);
|
else TRACELOG(LOG_WARNING, "MODEL: [%s] Texcoords attribute data format not supported, use vec2 float", fileName);
|
||||||
|
|
||||||
int index = data->meshes[i].primitives[p].attributes[j].index;
|
int index = mesh->primitives[p].attributes[j].index;
|
||||||
if (index == 0) model.meshes[meshIndex].texcoords = texcoordPtr;
|
if (index == 0) model.meshes[meshIndex].texcoords = texcoordPtr;
|
||||||
else if (index == 1) model.meshes[meshIndex].texcoords2 = texcoordPtr;
|
else if (index == 1) model.meshes[meshIndex].texcoords2 = texcoordPtr;
|
||||||
else
|
else
|
||||||
@@ -5190,9 +5264,9 @@ static Model LoadGLTF(const char *fileName)
|
|||||||
if (texcoordPtr != NULL) RL_FREE(texcoordPtr);
|
if (texcoordPtr != NULL) RL_FREE(texcoordPtr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (data->meshes[i].primitives[p].attributes[j].type == cgltf_attribute_type_color) // COLOR_n, vec3/vec4, float/u8n/u16n
|
else if (mesh->primitives[p].attributes[j].type == cgltf_attribute_type_color) // COLOR_n, vec3/vec4, float/u8n/u16n
|
||||||
{
|
{
|
||||||
cgltf_accessor *attribute = data->meshes[i].primitives[p].attributes[j].data;
|
cgltf_accessor *attribute = mesh->primitives[p].attributes[j].data;
|
||||||
|
|
||||||
// WARNING: SPECS: All components of each COLOR_n accessor element MUST be clamped to [0.0, 1.0] range
|
// WARNING: SPECS: All components of each COLOR_n accessor element MUST be clamped to [0.0, 1.0] range
|
||||||
|
|
||||||
@@ -5309,9 +5383,9 @@ static Model LoadGLTF(const char *fileName)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Load primitive indices data (if provided)
|
// Load primitive indices data (if provided)
|
||||||
if (data->meshes[i].primitives[p].indices != NULL)
|
if (mesh->primitives[p].indices != NULL)
|
||||||
{
|
{
|
||||||
cgltf_accessor *attribute = data->meshes[i].primitives[p].indices;
|
cgltf_accessor *attribute = mesh->primitives[p].indices;
|
||||||
|
|
||||||
model.meshes[meshIndex].triangleCount = (int)attribute->count/3;
|
model.meshes[meshIndex].triangleCount = (int)attribute->count/3;
|
||||||
|
|
||||||
@@ -5351,7 +5425,7 @@ static Model LoadGLTF(const char *fileName)
|
|||||||
// raylib instead assigns to the mesh the by its index, as loaded in model.materials array
|
// 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,
|
// To get the index, we check if material pointers match, and we assign the corresponding index,
|
||||||
// skipping index 0, the default material
|
// skipping index 0, the default material
|
||||||
if (&data->materials[m] == data->meshes[i].primitives[p].material)
|
if (&data->materials[m] == mesh->primitives[p].material)
|
||||||
{
|
{
|
||||||
model.meshMaterial[meshIndex] = m + 1;
|
model.meshMaterial[meshIndex] = m + 1;
|
||||||
break;
|
break;
|
||||||
@@ -5401,20 +5475,27 @@ static Model LoadGLTF(const char *fileName)
|
|||||||
TRACELOG(LOG_ERROR, "MODEL: [%s] can only load one skin (armature) per model, but gltf skins_count == %i", fileName, data->skins_count);
|
TRACELOG(LOG_ERROR, "MODEL: [%s] can only load one skin (armature) per model, but gltf skins_count == %i", fileName, data->skins_count);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (unsigned int i = 0, meshIndex = 0; i < data->meshes_count; i++)
|
meshIndex = 0;
|
||||||
|
for (unsigned int i = 0; i < data->nodes_count; i++)
|
||||||
{
|
{
|
||||||
for (unsigned int p = 0; p < data->meshes[i].primitives_count; p++)
|
cgltf_node *node = &(data->nodes[i]);
|
||||||
|
|
||||||
|
cgltf_mesh *mesh = node->mesh;
|
||||||
|
if (!mesh)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
for (unsigned int p = 0; p < mesh->primitives_count; p++)
|
||||||
{
|
{
|
||||||
// NOTE: We only support primitives defined by triangles
|
// NOTE: We only support primitives defined by triangles
|
||||||
if (data->meshes[i].primitives[p].type != cgltf_primitive_type_triangles) continue;
|
if (mesh->primitives[p].type != cgltf_primitive_type_triangles) continue;
|
||||||
|
|
||||||
for (unsigned int j = 0; j < data->meshes[i].primitives[p].attributes_count; j++)
|
for (unsigned int j = 0; j < mesh->primitives[p].attributes_count; j++)
|
||||||
{
|
{
|
||||||
// NOTE: JOINTS_1 + WEIGHT_1 will be used for +4 joints influencing a vertex -> Not supported by raylib
|
// NOTE: JOINTS_1 + WEIGHT_1 will be used for +4 joints influencing a vertex -> Not supported by raylib
|
||||||
|
|
||||||
if (data->meshes[i].primitives[p].attributes[j].type == cgltf_attribute_type_joints) // JOINTS_n (vec4: 4 bones max per vertex / u8, u16)
|
if (mesh->primitives[p].attributes[j].type == cgltf_attribute_type_joints) // JOINTS_n (vec4: 4 bones max per vertex / u8, u16)
|
||||||
{
|
{
|
||||||
cgltf_accessor *attribute = data->meshes[i].primitives[p].attributes[j].data;
|
cgltf_accessor *attribute = mesh->primitives[p].attributes[j].data;
|
||||||
|
|
||||||
// NOTE: JOINTS_n can only be vec4 and u8/u16
|
// NOTE: JOINTS_n can only be vec4 and u8/u16
|
||||||
// SPECS: https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#meshes-overview
|
// SPECS: https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#meshes-overview
|
||||||
@@ -5462,9 +5543,9 @@ static Model LoadGLTF(const char *fileName)
|
|||||||
}
|
}
|
||||||
else TRACELOG(LOG_WARNING, "MODEL: [%s] Joint attribute data format not supported", fileName);
|
else TRACELOG(LOG_WARNING, "MODEL: [%s] Joint attribute data format not supported", fileName);
|
||||||
}
|
}
|
||||||
else if (data->meshes[i].primitives[p].attributes[j].type == cgltf_attribute_type_weights) // WEIGHTS_n (vec4, u8n/u16n/f32)
|
else if (mesh->primitives[p].attributes[j].type == cgltf_attribute_type_weights) // WEIGHTS_n (vec4, u8n/u16n/f32)
|
||||||
{
|
{
|
||||||
cgltf_accessor *attribute = data->meshes[i].primitives[p].attributes[j].data;
|
cgltf_accessor *attribute = mesh->primitives[p].attributes[j].data;
|
||||||
|
|
||||||
if (attribute->type == cgltf_type_vec4)
|
if (attribute->type == cgltf_type_vec4)
|
||||||
{
|
{
|
||||||
|
Reference in New Issue
Block a user