mirror of
				https://github.com/raysan5/raylib.git
				synced 2025-10-26 12:27:01 +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
	 Paul Melis
					Paul Melis