From 71700254b41c84184db42dcfb3162943cf7aab27 Mon Sep 17 00:00:00 2001 From: Hristo Stamenov Date: Sat, 26 Jun 2021 14:22:00 +0300 Subject: [PATCH] Major revamp gltf (#1849) * Added my own model and license to Raylib exclusively created by me and provided for use in the examples (and other projects if anyone decides to) * Use animation vertices on initial load if possible. * Added girl model to model example * Revamped GLTF model loading as it was wrong by default. Also updated some comments. GLTF models were loaded only by mesh but they should be loaded recursively by hierarchical nodes because tehre are some static node transformations that are to be applied to the vertices. It also resulted in more meshes being included in some models. It is the correct way of loading GLTF and what is suggested in the official examples. Currenlty limiting to only one scene but more can be included later. * Refactored the new names and structure of extracted functions. * Safer and easier read value. * Made reading easier for accessor->bufferView->buffer in GLTF. Now there is no need to check for supported types or anything. * Correct inclusion of limits.h in the cases of MSVC based compilers vs the world * Removed weird example file --- examples/models/models_gltf_model.c | 3 +- examples/models/resources/gltf/LICENSE | 3 + examples/models/resources/gltf/girl.glb | Bin 0 -> 135000 bytes src/models.c | 1166 +++++++++++++++-------- 4 files changed, 790 insertions(+), 382 deletions(-) create mode 100644 examples/models/resources/gltf/girl.glb diff --git a/examples/models/models_gltf_model.c b/examples/models/models_gltf_model.c index 9889ad25a..3f843c9d4 100644 --- a/examples/models/models_gltf_model.c +++ b/examples/models/models_gltf_model.c @@ -21,7 +21,7 @@ #include -#define MAX_MODELS 7 +#define MAX_MODELS 8 int main(void) { @@ -49,6 +49,7 @@ int main(void) model[4] = LoadModel("resources/gltf/AnimatedTriangle.gltf"); model[5] = LoadModel("resources/gltf/AnimatedMorphCube.glb"); model[6] = LoadModel("resources/gltf/vertex_colored_object.glb"); + model[7] = LoadModel("resources/gltf/girl.glb"); int currentModel = 0; diff --git a/examples/models/resources/gltf/LICENSE b/examples/models/resources/gltf/LICENSE index 8617385fa..305079cfb 100644 --- a/examples/models/resources/gltf/LICENSE +++ b/examples/models/resources/gltf/LICENSE @@ -15,6 +15,9 @@ Animated Triangle model is licensed as CC0 Universal Public Domain Gearbox Assy model has been provided by Okino Computer Graphics, using Okino Polytrans Software. no license information was provided +Girl model has been provided by Hristo Stamenov (https://thatonegamedev.com/) +and licensed as CC0 Universal Public Domain + Check for details on CC0: https://creativecommons.org/publicdomain/zero/1.0/ Check for details on CC4: http://creativecommons.org/licenses/by/4.0/ GLTF sample models for testing are taken from: https://github.com/KhronosGroup/glTF-Sample-Models/ diff --git a/examples/models/resources/gltf/girl.glb b/examples/models/resources/gltf/girl.glb new file mode 100644 index 0000000000000000000000000000000000000000..c368590c856e593fe5176f1975132c36bd37325b GIT binary patch literal 135000 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�n?Q39Sub|s^gFV>$bLk&_mqt$*zanqw;Cdc2{Iem4anj1 z&wT^f{cOfAn|4-of!+MT(QISiTxqc1D<-Vnh#Wr1amTe<)~+x5(cUTAGwr5k zzIXPkg?0ija_zl%W$hfBUhH+!U2J!BU9SC=A2N3P9bWD|p0dP_xjffC@TrX5+0QTc ze(YRoC-0PN?|xavPGq87v#S%#wRbx#V;8ITdhh&(<#uh)a_sMHm9cZ0`+Dyc z=M{E~59Qd;TrFeQ^8NK*nHwwYoR{a=b1s#!yJYZYZ=l&qy9-lu?Ds8_v3ue2W^Zu# zO1n+{Irf*9$k-h)db79Ma;2Ts)ExW6t7PoX{CmAu_{j=8?xi{QGFxQqcCC56*DiL2 z-Iu*N_HhSf?5rJL?-g3T+-~>19D9NDGIl@qyxP0<@-jP3wp{z2_hsysslD23e|M=J zt8uRV-H$SM?;2k2y?SKPJ&etyA-g$|vUH#%mdnFdkv{MMpxA$Br zYp0+0Xzu~fnRY$N`S$4P4O9++$~SZjDtkZ}Bo3p|*&sd)!^TWNe2|&2F%=LWhGF6` zK8yy5!`L9ZU>GD0G8ZNXGLsM+BoERD!XWh^8iYaOFboohv5{$*I7mH+4Z_IsAaRh} zKw>Z�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 literal 0 HcmV?d00001 diff --git a/src/models.c b/src/models.c index b243a45e7..4ad7d32b6 100644 --- a/src/models.c +++ b/src/models.c @@ -50,6 +50,12 @@ #include // Required for: memcmp(), strlen() #include // Required for: sinf(), cosf(), sqrtf(), fabsf() +#if defined(_MSC_VER) + #include // Required for numerical limit constants like CHAR_MAX +#else + #include // Required for numerical limit constants like CHAR_MAX +#endif + #if defined(_WIN32) #include // Required for: _chdir() [Used in LoadOBJ()] #define CHDIR _chdir @@ -117,11 +123,14 @@ static ModelAnimation *LoadIQMModelAnimations(const char *fileName, int *animCou #if defined(SUPPORT_FILEFORMAT_GLTF) static Model LoadGLTF(const char *fileName); // Load GLTF mesh data static ModelAnimation *LoadGLTFModelAnimations(const char *fileName, int *animCount); // Load GLTF animation data -static void LoadGLTFModelIndices(Model *model, cgltf_accessor *indexAccessor, int primitiveIndex); -static void BindGLTFPrimitiveToBones(Model *model, const cgltf_data *data, int primitiveIndex); -static void LoadGLTFBoneAttribute(Model *model, cgltf_accessor *jointsAccessor, const cgltf_data *data, int primitiveIndex); static void LoadGLTFMaterial(Model *model, const char *fileName, const cgltf_data *data); +static void LoadGLTFMesh(cgltf_data *data, cgltf_mesh* mesh, Model* outModel, Matrix currentTransform, int* primitiveIndex, const char* fileName); +static void LoadGLTFNode(cgltf_data *data, cgltf_node* node, Model* outModel, Matrix currentTransform, int* primitiveIndex, const char* fileName); static void InitGLTFBones(Model *model, const cgltf_data *data); +static void BindGLTFPrimitiveToBones(Model *model, const cgltf_data *data, int primitiveIndex); +static void GetGLTFPrimitiveCount(cgltf_node* node, int* outCount); +static bool GLTFReadValue(cgltf_accessor* acc, unsigned int index, void *variable); +static void* GLTFReadValuesAs(cgltf_accessor* acc, cgltf_component_type type, bool adjustOnDownCasting); #endif //---------------------------------------------------------------------------------- @@ -844,7 +853,8 @@ void UploadMesh(Mesh *mesh, bool dynamic) // NOTE: Attributes must be uploaded considering default locations points // Enable vertex attributes: position (shader-location = 0) - mesh->vboId[0] = rlLoadVertexBuffer(mesh->vertices, mesh->vertexCount*3*sizeof(float), dynamic); + void* vertices = mesh->animVertices != NULL ? mesh->animVertices : mesh->vertices; + mesh->vboId[0] = rlLoadVertexBuffer(vertices, mesh->vertexCount*3*sizeof(float), dynamic); rlSetVertexAttribute(0, 3, RL_FLOAT, 0, 0, 0); rlEnableVertexAttribute(0); @@ -856,7 +866,8 @@ void UploadMesh(Mesh *mesh, bool dynamic) if (mesh->normals != NULL) { // Enable vertex attributes: normals (shader-location = 2) - mesh->vboId[2] = rlLoadVertexBuffer(mesh->normals, mesh->vertexCount*3*sizeof(float), dynamic); + void* normals = mesh->animNormals != NULL ? mesh->animNormals : mesh->vertices; + mesh->vboId[2] = rlLoadVertexBuffer(normals, mesh->vertexCount*3*sizeof(float), dynamic); rlSetVertexAttribute(2, 3, RL_FLOAT, 0, 0, 0); rlEnableVertexAttribute(2); } @@ -4132,26 +4143,542 @@ static Image LoadImageFromCgltfImage(cgltf_image *image, const char *texPath, Co } -static bool GLTFReadValue(cgltf_accessor* acc, unsigned int index, void *variable, unsigned int elements, unsigned int size) +static bool GLTFReadValue(cgltf_accessor* acc, unsigned int index, void *variable) { + unsigned int typeElements = 0; + switch(acc->type) { + case cgltf_type_scalar: typeElements = 1; break; + case cgltf_type_vec2: typeElements = 2; break; + case cgltf_type_vec3: typeElements = 3; break; + case cgltf_type_vec4: + case cgltf_type_mat2: typeElements = 4; break; + case cgltf_type_mat3: typeElements = 9; break; + case cgltf_type_mat4: typeElements = 16; break; + case cgltf_type_invalid: typeElements = 0; break; + } + + unsigned int typeSize = 0; + switch(acc->component_type) { + case cgltf_component_type_r_8u: + case cgltf_component_type_r_8: typeSize = 1; break; + case cgltf_component_type_r_16u: + case cgltf_component_type_r_16: typeSize = 2; break; + case cgltf_component_type_r_32f: + case cgltf_component_type_r_32u: typeSize = 4; break; + case cgltf_component_type_invalid: typeSize = 0; break; + } + + unsigned int singleElementSize = typeSize * typeElements; + if (acc->count == 2) { if (index > 1) return false; - memcpy(variable, index == 0 ? acc->min : acc->max, elements*size); + memcpy(variable, index == 0 ? acc->min : acc->max, singleElementSize); return true; } - - unsigned int stride = size*elements; - memset(variable, 0, stride); + + memset(variable, 0, singleElementSize); if (acc->buffer_view == NULL || acc->buffer_view->buffer == NULL || acc->buffer_view->buffer->data == NULL) return false; - - void* readPosition = ((char *)acc->buffer_view->buffer->data) + (index*stride) + acc->buffer_view->offset + acc->offset; - memcpy(variable, readPosition, stride); + + if(!acc->buffer_view->stride) + { + void* readPosition = ((char *)acc->buffer_view->buffer->data) + (index * singleElementSize) + acc->buffer_view->offset + acc->offset; + memcpy(variable, readPosition, singleElementSize); + } + else + { + void* readPosition = ((char *)acc->buffer_view->buffer->data) + (index * acc->buffer_view->stride) + acc->buffer_view->offset + acc->offset; + memcpy(variable, readPosition, singleElementSize); + } + return true; } +static void* GLTFReadValuesAs(cgltf_accessor* acc, cgltf_component_type type, bool adjustOnDownCasting) +{ + unsigned int count = acc->count; + unsigned int typeSize = 0; + switch(type) { + case cgltf_component_type_r_8u: + case cgltf_component_type_r_8: typeSize = 1; break; + case cgltf_component_type_r_16u: + case cgltf_component_type_r_16: typeSize = 2; break; + case cgltf_component_type_r_32f: + case cgltf_component_type_r_32u: typeSize = 4; break; + case cgltf_component_type_invalid: typeSize = 0; break; + } + + unsigned int typeElements = 0; + switch(acc->type) { + case cgltf_type_scalar: typeElements = 1; break; + case cgltf_type_vec2: typeElements = 2; break; + case cgltf_type_vec3: typeElements = 3; break; + case cgltf_type_vec4: + case cgltf_type_mat2: typeElements = 4; break; + case cgltf_type_mat3: typeElements = 9; break; + case cgltf_type_mat4: typeElements = 16; break; + case cgltf_type_invalid: typeElements = 0; break; + } + + if(acc->component_type == type) + { + void* array = RL_MALLOC(count * typeElements * typeSize); + + for(unsigned int i = 0; i < count; i++) + { + GLTFReadValue(acc, i, (char*)array + i * typeElements * typeSize); + } + + return array; + + } else { + + unsigned int accTypeSize = 0; + switch(acc->component_type) { + case cgltf_component_type_r_8u: + case cgltf_component_type_r_8: accTypeSize = 1; break; + case cgltf_component_type_r_16u: + case cgltf_component_type_r_16: accTypeSize = 2; break; + case cgltf_component_type_r_32f: + case cgltf_component_type_r_32u: accTypeSize = 4; break; + case cgltf_component_type_invalid: accTypeSize = 0; break; + } + + void* array = RL_MALLOC(count * typeElements * typeSize); + void* additionalArray = RL_MALLOC(count * typeElements * accTypeSize); + + for(unsigned int i = 0; i < count; i++) + { + GLTFReadValue(acc, i, (char*)additionalArray + i * typeElements * accTypeSize); + } + + switch(acc->component_type) { + case cgltf_component_type_r_8u: + { + unsigned char* typedAdditionalArray = (unsigned char*)additionalArray; + switch(type) { + case cgltf_component_type_r_8: + { + char* typedArray = (char*) array; + for (unsigned int i = 0; i < count * typeElements; i++) + { + if(adjustOnDownCasting) + { + typedArray[i] = (char)(typedAdditionalArray[i] / (UCHAR_MAX / CHAR_MAX)); + } + else + { + typedArray[i] = (char)typedAdditionalArray[i]; + } + } + break; + } + case cgltf_component_type_r_16u: + { + unsigned short* typedArray = (unsigned short*) array; + for (unsigned int i = 0; i < count * typeElements; i++) + { + typedArray[i] = (unsigned short)typedAdditionalArray[i]; + } + break; + } + case cgltf_component_type_r_16: + { + short* typedArray = (short*) array; + for (unsigned int i = 0; i < count * typeElements; i++) + { + typedArray[i] = (short)typedAdditionalArray[i]; + } + break; + } + case cgltf_component_type_r_32f: + { + float* typedArray = (float*) array; + for (unsigned int i = 0; i < count * typeElements; i++) + { + typedArray[i] = (float)typedAdditionalArray[i]; + } + break; + } + case cgltf_component_type_r_32u: + { + unsigned int* typedArray = (unsigned int*) array; + for (unsigned int i = 0; i < count * typeElements; i++) + { + typedArray[i] = (unsigned int)typedAdditionalArray[i]; + } + break; + } + default: { + RL_FREE(array); + RL_FREE(additionalArray); + return NULL; + } + } + break; + } + case cgltf_component_type_r_8: + { + char* typedAdditionalArray = (char*)additionalArray; + switch(type) { + case cgltf_component_type_r_8u: + { + unsigned char* typedArray = (unsigned char*) array; + for (unsigned int i = 0; i < count * typeElements; i++) + { + typedArray[i] = (unsigned char)typedAdditionalArray[i]; + } + break; + } + case cgltf_component_type_r_16u: + { + unsigned short* typedArray = (unsigned short*) array; + for (unsigned int i = 0; i < count * typeElements; i++) + { + typedArray[i] = (unsigned short)typedAdditionalArray[i]; + } + break; + } + case cgltf_component_type_r_16: + { + short* typedArray = (short*) array; + for (unsigned int i = 0; i < count * typeElements; i++) + { + typedArray[i] = (short)typedAdditionalArray[i]; + } + break; + } + case cgltf_component_type_r_32f: + { + float* typedArray = (float*) array; + for (unsigned int i = 0; i < count * typeElements; i++) + { + typedArray[i] = (float)typedAdditionalArray[i]; + } + break; + } + case cgltf_component_type_r_32u: + { + unsigned int* typedArray = (unsigned int*) array; + for (unsigned int i = 0; i < count * typeElements; i++) + { + typedArray[i] = (unsigned int)typedAdditionalArray[i]; + } + break; + } + default: { + RL_FREE(array); + RL_FREE(additionalArray); + return NULL; + } + } + break; + } + case cgltf_component_type_r_16u: + { + unsigned short* typedAdditionalArray = (unsigned short*)additionalArray; + switch(type) { + case cgltf_component_type_r_8u: + { + unsigned char* typedArray = (unsigned char*) array; + for (unsigned int i = 0; i < count * typeElements; i++) + { + if(adjustOnDownCasting) + { + typedArray[i] = (unsigned char)(typedAdditionalArray[i] / (USHRT_MAX / UCHAR_MAX)); + } + else + { + typedArray[i] = (unsigned char)typedAdditionalArray[i]; + } + } + break; + } + case cgltf_component_type_r_8: + { + char* typedArray = (char*) array; + for (unsigned int i = 0; i < count * typeElements; i++) + { + if(adjustOnDownCasting) + { + typedArray[i] = (char)(typedAdditionalArray[i] / (USHRT_MAX / CHAR_MAX)); + } + else + { + typedArray[i] = (char)typedAdditionalArray[i]; + } + } + break; + } + case cgltf_component_type_r_16: + { + short* typedArray = (short*) array; + for (unsigned int i = 0; i < count * typeElements; i++) + { + if(adjustOnDownCasting) + { + typedArray[i] = (short)(typedAdditionalArray[i] / (USHRT_MAX / SHRT_MAX)); + } + else + { + typedArray[i] = (short)typedAdditionalArray[i]; + } + } + break; + } + case cgltf_component_type_r_32f: + { + float* typedArray = (float*) array; + for (unsigned int i = 0; i < count * typeElements; i++) + { + typedArray[i] = (float)typedAdditionalArray[i]; + } + break; + } + case cgltf_component_type_r_32u: + { + unsigned int* typedArray = (unsigned int*) array; + for (unsigned int i = 0; i < count * typeElements; i++) + { + typedArray[i] = (unsigned int)typedAdditionalArray[i]; + } + break; + } + default: { + RL_FREE(array); + RL_FREE(additionalArray); + return NULL; + } + } + break; + } + case cgltf_component_type_r_16: + { + short* typedAdditionalArray = (short*)additionalArray; + switch(type) { + case cgltf_component_type_r_8u: + { + unsigned char* typedArray = (unsigned char*) array; + for (unsigned int i = 0; i < count * typeElements; i++) + { + if(adjustOnDownCasting) + { + typedArray[i] = (unsigned char)(typedAdditionalArray[i] / (SHRT_MAX / UCHAR_MAX)); + } + else + { + typedArray[i] = (unsigned char)typedAdditionalArray[i]; + } + } + break; + } + case cgltf_component_type_r_8: + { + char* typedArray = (char*) array; + for (unsigned int i = 0; i < count * typeElements; i++) + { + if(adjustOnDownCasting) + { + typedArray[i] = (char)(typedAdditionalArray[i] / (SHRT_MAX / CHAR_MAX)); + } + else + { + typedArray[i] = (char)typedAdditionalArray[i]; + } + } + break; + } + case cgltf_component_type_r_16u: + { + unsigned short* typedArray = (unsigned short*) array; + for (unsigned int i = 0; i < count * typeElements; i++) + { + typedArray[i] = (unsigned short)typedAdditionalArray[i]; + } + break; + } + case cgltf_component_type_r_32f: + { + float* typedArray = (float*) array; + for (unsigned int i = 0; i < count * typeElements; i++) + { + typedArray[i] = (float)typedAdditionalArray[i]; + } + break; + } + case cgltf_component_type_r_32u: + { + unsigned int* typedArray = (unsigned int*) array; + for (unsigned int i = 0; i < count * typeElements; i++) + { + typedArray[i] = (unsigned int)typedAdditionalArray[i]; + } + break; + } + default: { + RL_FREE(array); + RL_FREE(additionalArray); + return NULL; + } + } + break; + } + case cgltf_component_type_r_32f: + { + float* typedAdditionalArray = (float*)additionalArray; + switch(type) { + case cgltf_component_type_r_8u: + { + unsigned char* typedArray = (unsigned char*) array; + for (unsigned int i = 0; i < count * typeElements; i++) + { + typedArray[i] = (unsigned char)typedAdditionalArray[i]; + } + break; + } + case cgltf_component_type_r_8: + { + char* typedArray = (char*) array; + for (unsigned int i = 0; i < count * typeElements; i++) + { + typedArray[i] = (char)typedAdditionalArray[i]; + } + break; + } + case cgltf_component_type_r_16u: + { + unsigned short* typedArray = (unsigned short*) array; + for (unsigned int i = 0; i < count * typeElements; i++) + { + typedArray[i] = (unsigned short)typedAdditionalArray[i]; + } + break; + } + case cgltf_component_type_r_16: + { + short* typedArray = (short*) array; + for (unsigned int i = 0; i < count * typeElements; i++) + { + typedArray[i] = (short)typedAdditionalArray[i]; + } + break; + } + case cgltf_component_type_r_32u: + { + unsigned int* typedArray = (unsigned int*) array; + for (unsigned int i = 0; i < count * typeElements; i++) + { + typedArray[i] = (unsigned int)typedAdditionalArray[i]; + } + break; + } + default: { + RL_FREE(array); + RL_FREE(additionalArray); + return NULL; + } + } + break; + } + case cgltf_component_type_r_32u: + { + unsigned int* typedAdditionalArray = (unsigned int*)additionalArray; + switch(type) { + case cgltf_component_type_r_8u: + { + unsigned char* typedArray = (unsigned char*) array; + for (unsigned int i = 0; i < count * typeElements; i++) + { + if(adjustOnDownCasting) + { + typedArray[i] = (unsigned char)(typedAdditionalArray[i] / (UINT_MAX / UCHAR_MAX)); + } + else + { + typedArray[i] = (unsigned char)typedAdditionalArray[i]; + } + } + break; + } + case cgltf_component_type_r_8: + { + char* typedArray = (char*) array; + for (unsigned int i = 0; i < count * typeElements; i++) + { + if(adjustOnDownCasting) + { + typedArray[i] = (char)(typedAdditionalArray[i] / (UINT_MAX / CHAR_MAX)); + } + else + { + typedArray[i] = (char)typedAdditionalArray[i]; + } + } + break; + } + case cgltf_component_type_r_16u: + { + unsigned short* typedArray = (unsigned short*) array; + for (unsigned int i = 0; i < count * typeElements; i++) + { + if(adjustOnDownCasting) + { + typedArray[i] = (unsigned short)(typedAdditionalArray[i] / (UINT_MAX / USHRT_MAX)); + } + else + { + typedArray[i] = (unsigned short)typedAdditionalArray[i]; + } + } + break; + } + case cgltf_component_type_r_16: + { + short* typedArray = (short*) array; + for (unsigned int i = 0; i < count * typeElements; i++) + { + if(adjustOnDownCasting) + { + typedArray[i] = (short)(typedAdditionalArray[i] / (UINT_MAX / SHRT_MAX)); + } + else + { + typedArray[i] = (short)typedAdditionalArray[i]; + } + } + break; + } + case cgltf_component_type_r_32f: + { + float* typedArray = (float*) array; + for (unsigned int i = 0; i < count * typeElements; i++) + { + typedArray[i] = (float)typedAdditionalArray[i]; + } + break; + } + default: { + RL_FREE(array); + RL_FREE(additionalArray); + return NULL; + } + } + break; + } + default: { + RL_FREE(array); + RL_FREE(additionalArray); + return NULL; + } + } + + RL_FREE(additionalArray); + return array; + } +} + // LoadGLTF loads in model data from given filename, supporting both .gltf and .glb static Model LoadGLTF(const char *fileName) { @@ -4194,12 +4721,15 @@ static Model LoadGLTF(const char *fileName) // Read data buffers 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(data->scenes_count > 1) TRACELOG(LOG_INFO, "MODEL: [%s] Has multiple scenes but only the first one will be loaded", fileName); int primitivesCount = 0; - - for (unsigned int i = 0; i < data->meshes_count; i++) - primitivesCount += (int)data->meshes[i].primitives_count; - + for (unsigned int i = 0; i < data->scene->nodes_count; i++) + { + GetGLTFPrimitiveCount(data->scene->nodes[i], &primitivesCount); + } + // Process glTF data and map to model model.meshCount = primitivesCount; model.meshes = RL_CALLOC(model.meshCount, sizeof(Mesh)); @@ -4209,195 +4739,15 @@ static Model LoadGLTF(const char *fileName) model.boneCount = (int)data->nodes_count; model.bones = RL_CALLOC(model.boneCount, sizeof(BoneInfo)); model.bindPose = RL_CALLOC(model.boneCount, sizeof(Transform)); - + InitGLTFBones(&model, data); LoadGLTFMaterial(&model, fileName, data); - + int primitiveIndex = 0; - - for (unsigned int i = 0; i < data->meshes_count; i++) + for(unsigned int i = 0; i < data->scene->nodes_count; i++) { - for (unsigned int p = 0; p < data->meshes[i].primitives_count; p++) - { - 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) - { - cgltf_accessor *acc = data->meshes[i].primitives[p].attributes[j].data; - model.meshes[primitiveIndex].vertexCount = (int)acc->count; - int bufferSize = model.meshes[primitiveIndex].vertexCount*3*sizeof(float); - model.meshes[primitiveIndex].vertices = RL_MALLOC(bufferSize); - model.meshes[primitiveIndex].animVertices = RL_MALLOC(bufferSize); - - if (acc->component_type == cgltf_component_type_r_32f) - { - for (unsigned int a = 0; a < acc->count; a++) - { - GLTFReadValue(acc, a, model.meshes[primitiveIndex].vertices + (a*3), 3, sizeof(float)); - } - } - else if (acc->component_type == cgltf_component_type_r_32u) - { - int readValue[3]; - for (unsigned int a = 0; a < acc->count; a++) - { - GLTFReadValue(acc, a, readValue, 3, sizeof(int)); - model.meshes[primitiveIndex].vertices[(a*3) + 0] = (float)readValue[0]; - model.meshes[primitiveIndex].vertices[(a*3) + 1] = (float)readValue[1]; - model.meshes[primitiveIndex].vertices[(a*3) + 2] = (float)readValue[2]; - } - } - else - { - // TODO: Support normalized unsigned byte/unsigned short vertices - TRACELOG(LOG_WARNING, "MODEL: [%s] glTF vertices must be float or int", fileName); - } - - memcpy(model.meshes[primitiveIndex].animVertices, model.meshes[primitiveIndex].vertices, bufferSize); - } - 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; - - int bufferSize = (int)(acc->count*3*sizeof(float)); - model.meshes[primitiveIndex].normals = RL_MALLOC(bufferSize); - model.meshes[primitiveIndex].animNormals = RL_MALLOC(bufferSize); - - if (acc->component_type == cgltf_component_type_r_32f) - { - for (unsigned int a = 0; a < acc->count; a++) - { - GLTFReadValue(acc, a, model.meshes[primitiveIndex].normals + (a*3), 3, sizeof(float)); - } - } - else if (acc->component_type == cgltf_component_type_r_32u) - { - int readValue[3]; - for (unsigned int a = 0; a < acc->count; a++) - { - GLTFReadValue(acc, a, readValue, 3, sizeof(int)); - model.meshes[primitiveIndex].normals[(a*3) + 0] = (float)readValue[0]; - model.meshes[primitiveIndex].normals[(a*3) + 1] = (float)readValue[1]; - model.meshes[primitiveIndex].normals[(a*3) + 2] = (float)readValue[2]; - } - } - else - { - // TODO: Support normalized unsigned byte/unsigned short normals - TRACELOG(LOG_WARNING, "MODEL: [%s] glTF normals must be float or int", fileName); - } - - memcpy(model.meshes[primitiveIndex].animNormals, model.meshes[primitiveIndex].normals, bufferSize); - } - 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)); - - for (unsigned int a = 0; a < acc->count; a++) - { - GLTFReadValue(acc, a, model.meshes[primitiveIndex].texcoords + (a*2), 2, sizeof(float)); - } - } - else - { - // TODO: Support normalized unsigned byte/unsigned short texture coordinates - TRACELOG(LOG_WARNING, "MODEL: [%s] glTF texture coordinates must be float", fileName); - } - } - else if (data->meshes[i].primitives[p].attributes[j].type == cgltf_attribute_type_joints) - { - cgltf_accessor *acc = data->meshes[i].primitives[p].attributes[j].data; - LoadGLTFBoneAttribute(&model, acc, data, primitiveIndex); - } - else if (data->meshes[i].primitives[p].attributes[j].type == cgltf_attribute_type_weights) - { - cgltf_accessor *acc = data->meshes[i].primitives[p].attributes[j].data; - - model.meshes[primitiveIndex].boneWeights = RL_MALLOC(acc->count*4*sizeof(float)); - - if (acc->component_type == cgltf_component_type_r_32f) - { - for (unsigned int a = 0; a < acc->count; a++) - { - GLTFReadValue(acc, a, model.meshes[primitiveIndex].boneWeights + (a*4), 4, sizeof(float)); - } - } - else if (acc->component_type == cgltf_component_type_r_32u) - { - unsigned int readValue[4]; - for (unsigned int a = 0; a < acc->count; a++) - { - GLTFReadValue(acc, a, readValue, 4, sizeof(unsigned int)); - model.meshes[primitiveIndex].boneWeights[(a*4) + 0] = (float)readValue[0]; - model.meshes[primitiveIndex].boneWeights[(a*4) + 1] = (float)readValue[1]; - model.meshes[primitiveIndex].boneWeights[(a*4) + 2] = (float)readValue[2]; - model.meshes[primitiveIndex].boneWeights[(a*4) + 3] = (float)readValue[3]; - } - } - else - { - // TODO: Support normalized unsigned byte/unsigned short weights - TRACELOG(LOG_WARNING, "MODEL: [%s] glTF normals must be float or int", fileName); - } - } - else if (data->meshes[i].primitives[p].attributes[j].type == cgltf_attribute_type_color) - { - cgltf_accessor *acc = data->meshes[i].primitives[p].attributes[j].data; - model.meshes[primitiveIndex].colors = RL_MALLOC(acc->count*4*sizeof(unsigned char)); - - if (acc->component_type == cgltf_component_type_r_8u) - { - for (int a = 0; a < acc->count; a++) - { - GLTFReadValue(acc, a, model.meshes[primitiveIndex].colors + (a*4), 4, sizeof(unsigned char)); - } - } - if (acc->component_type == cgltf_component_type_r_16u) - { - TRACELOG(LOG_WARNING, "MODEL: [%s] converting glTF colors to unsigned char", fileName); - for (int a = 0; a < acc->count; a++) - { - unsigned short readValue[4]; - for (int a = 0; a < acc->count; a++) - { - GLTFReadValue(acc, a, readValue, 4, sizeof(unsigned short)); - // 257 = 65535/255 - model.meshes[primitiveIndex].colors[(a*4) + 0] = (unsigned char)(readValue[0]/257); - model.meshes[primitiveIndex].colors[(a*4) + 1] = (unsigned char)(readValue[1]/257); - model.meshes[primitiveIndex].colors[(a*4) + 2] = (unsigned char)(readValue[2]/257); - model.meshes[primitiveIndex].colors[(a*4) + 3] = (unsigned char)(readValue[3]/257); - } - } - } - else - { - TRACELOG(LOG_WARNING, "MODEL: [%s] glTF colors must be uchar or ushort", fileName); - } - } - } - - cgltf_accessor *acc = data->meshes[i].primitives[p].indices; - LoadGLTFModelIndices(&model, acc, primitiveIndex); - - if (data->meshes[i].primitives[p].material) - { - // Compute the offset - model.meshMaterial[primitiveIndex] = (int)(data->meshes[i].primitives[p].material - data->materials); - } - else - { - model.meshMaterial[primitiveIndex] = model.materialCount - 1; - } - - BindGLTFPrimitiveToBones(&model, data, primitiveIndex); - - primitiveIndex++; - } - + Matrix staticTransform = MatrixIdentity(); + LoadGLTFNode(data, data->scene->nodes[i], &model, staticTransform, &primitiveIndex, fileName); } cgltf_free(data); @@ -4540,79 +4890,20 @@ static void LoadGLTFMaterial(Model *model, const char *fileName, const cgltf_dat model->materials[model->materialCount - 1] = LoadMaterialDefault(); } -static void LoadGLTFBoneAttribute(Model *model, cgltf_accessor *jointsAccessor, const cgltf_data *data, int primitiveIndex) -{ - if (jointsAccessor->component_type == cgltf_component_type_r_16u) - { - model->meshes[primitiveIndex].boneIds = RL_MALLOC(jointsAccessor->count*4*sizeof(int)); - short* bones = RL_MALLOC(jointsAccessor->count*4*sizeof(short)); - - for (unsigned int a = 0; a < jointsAccessor->count; a++) - { - GLTFReadValue(jointsAccessor, a, bones + (a*4), 4, sizeof(short)); - } - - for (unsigned int a = 0; a < jointsAccessor->count*4; a++) - { - cgltf_node* skinJoint = data->skins->joints[bones[a]]; - - for (unsigned int k = 0; k < data->nodes_count; k++) - { - if (&(data->nodes[k]) == skinJoint) - { - model->meshes[primitiveIndex].boneIds[a] = k; - break; - } - } - } - RL_FREE(bones); - } - else if (jointsAccessor->component_type == cgltf_component_type_r_8u) - { - model->meshes[primitiveIndex].boneIds = RL_MALLOC(jointsAccessor->count*4*sizeof(int)); - unsigned char *bones = RL_MALLOC(jointsAccessor->count*4*sizeof(unsigned char)); - - for (unsigned int a = 0; a < jointsAccessor->count; a++) - { - GLTFReadValue(jointsAccessor, a, bones + (a*4), 4, sizeof(unsigned char)); - } - - for (unsigned int a = 0; a < jointsAccessor->count*4; a++) - { - cgltf_node* skinJoint = data->skins->joints[bones[a]]; - - for (unsigned int k = 0; k < data->nodes_count; k++) - { - if (&(data->nodes[k]) == skinJoint) - { - model->meshes[primitiveIndex].boneIds[a] = k; - break; - } - } - } - RL_FREE(bones); - } - else - { - // TODO: Support other size of bone index? - TRACELOG(LOG_WARNING, "MODEL: glTF bones in unexpected format"); - } -} - static void BindGLTFPrimitiveToBones(Model* model, const cgltf_data* data, int primitiveIndex) { - if (model->meshes[primitiveIndex].boneIds == NULL && data->nodes_count > 0) + for (unsigned int nodeId = 0; nodeId < data->nodes_count; nodeId++) { - for (unsigned int nodeId = 0; nodeId < data->nodes_count; nodeId++) + if (data->nodes[nodeId].mesh == &(data->meshes[primitiveIndex])) { - if (data->nodes[nodeId].mesh == &(data->meshes[primitiveIndex])) + if (model->meshes[primitiveIndex].boneIds == NULL) { - model->meshes[primitiveIndex].boneIds = RL_CALLOC(4*model->meshes[primitiveIndex].vertexCount, sizeof(int)); - model->meshes[primitiveIndex].boneWeights = RL_CALLOC(4*model->meshes[primitiveIndex].vertexCount, sizeof(float)); + model->meshes[primitiveIndex].boneIds = RL_CALLOC(4 * model->meshes[primitiveIndex].vertexCount, sizeof(int)); + model->meshes[primitiveIndex].boneWeights = RL_CALLOC(4 * model->meshes[primitiveIndex].vertexCount, sizeof(float)); - for (int b = 0; b < 4*model->meshes[primitiveIndex].vertexCount; b++) + for (int b = 0; b < 4 * model->meshes[primitiveIndex].vertexCount; b++) { - if (b%4 == 0) + if (b % 4 == 0) { model->meshes[primitiveIndex].boneIds[b] = nodeId; model->meshes[primitiveIndex].boneWeights[b] = 1.0f; @@ -4622,102 +4913,12 @@ static void BindGLTFPrimitiveToBones(Model* model, const cgltf_data* data, int p model->meshes[primitiveIndex].boneIds[b] = 0; model->meshes[primitiveIndex].boneWeights[b] = 0.0f; } - - } - - Vector3 boundVertex = { 0 }; - Vector3 boundNormal = { 0 }; - - Vector3 outTranslation = { 0 }; - Quaternion outRotation = { 0 }; - Vector3 outScale = { 0 }; - - int vCounter = 0; - int boneCounter = 0; - int boneId = 0; - - for (int i = 0; i < model->meshes[primitiveIndex].vertexCount; i++) - { - boneId = model->meshes[primitiveIndex].boneIds[boneCounter]; - outTranslation = model->bindPose[boneId].translation; - outRotation = model->bindPose[boneId].rotation; - outScale = model->bindPose[boneId].scale; - - // Vertices processing - boundVertex = (Vector3){ model->meshes[primitiveIndex].vertices[vCounter], model->meshes[primitiveIndex].vertices[vCounter + 1], model->meshes[primitiveIndex].vertices[vCounter + 2] }; - boundVertex = Vector3Multiply(boundVertex, outScale); - boundVertex = Vector3RotateByQuaternion(boundVertex, outRotation); - boundVertex = Vector3Add(boundVertex, outTranslation); - model->meshes[primitiveIndex].vertices[vCounter] = boundVertex.x; - model->meshes[primitiveIndex].vertices[vCounter + 1] = boundVertex.y; - model->meshes[primitiveIndex].vertices[vCounter + 2] = boundVertex.z; - - // Normals processing - if (model->meshes[primitiveIndex].normals != NULL) - { - boundNormal = (Vector3){ model->meshes[primitiveIndex].normals[vCounter], model->meshes[primitiveIndex].normals[vCounter + 1], model->meshes[primitiveIndex].normals[vCounter + 2] }; - boundNormal = Vector3RotateByQuaternion(boundNormal, outRotation); - model->meshes[primitiveIndex].normals[vCounter] = boundNormal.x; - model->meshes[primitiveIndex].normals[vCounter + 1] = boundNormal.y; - model->meshes[primitiveIndex].normals[vCounter + 2] = boundNormal.z; - } - - vCounter += 3; - boneCounter += 4; } } } } } -static void LoadGLTFModelIndices(Model* model, cgltf_accessor* indexAccessor, int primitiveIndex) -{ - if (indexAccessor) - { - if (indexAccessor->component_type == cgltf_component_type_r_16u || indexAccessor->component_type == cgltf_component_type_r_16) - { - model->meshes[primitiveIndex].triangleCount = (int)indexAccessor->count/3; - model->meshes[primitiveIndex].indices = RL_MALLOC(model->meshes[primitiveIndex].triangleCount*3*sizeof(unsigned short)); - - unsigned short readValue = 0; - for (unsigned int a = 0; a < indexAccessor->count; a++) - { - GLTFReadValue(indexAccessor, a, &readValue, 1, sizeof(short)); - model->meshes[primitiveIndex].indices[a] = readValue; - } - } - else if (indexAccessor->component_type == cgltf_component_type_r_8u || indexAccessor->component_type == cgltf_component_type_r_8) - { - model->meshes[primitiveIndex].triangleCount = (int)indexAccessor->count/3; - model->meshes[primitiveIndex].indices = RL_MALLOC(model->meshes[primitiveIndex].triangleCount*3*sizeof(unsigned short)); - - unsigned char readValue = 0; - for (unsigned int a = 0; a < indexAccessor->count; a++) - { - GLTFReadValue(indexAccessor, a, &readValue, 1, sizeof(char)); - model->meshes[primitiveIndex].indices[a] = (unsigned short)readValue; - } - } - else if (indexAccessor->component_type == cgltf_component_type_r_32u) - { - model->meshes[primitiveIndex].triangleCount = (int)indexAccessor->count/3; - model->meshes[primitiveIndex].indices = RL_MALLOC(model->meshes[primitiveIndex].triangleCount*3*sizeof(unsigned short)); - - unsigned int readValue; - for (unsigned int a = 0; a < indexAccessor->count; a++) - { - GLTFReadValue(indexAccessor, a, &readValue, 1, sizeof(unsigned int)); - model->meshes[primitiveIndex].indices[a] = (unsigned short)readValue; - } - } - } - else - { - // Unindexed mesh - model->meshes[primitiveIndex].triangleCount = model->meshes[primitiveIndex].vertexCount/3; - } -} - // LoadGLTF loads in animation data from given filename static ModelAnimation *LoadGLTFModelAnimations(const char *fileName, int *animCount) { @@ -4759,9 +4960,9 @@ static ModelAnimation *LoadGLTFModelAnimations(const char *fileName, int *animCo for (unsigned int a = 0; a < data->animations_count; a++) { // gltf animation consists of the following structures: - // - nodes - bones + // - nodes - bones are part of the node system (the whole node system is animatable) // - channels - single transformation type on a single bone - // - node - bone + // - node - animatable node // - transformation type (path) - translation, rotation, scale // - sampler - animation samples // - input - points in time this transformation happens @@ -4773,7 +4974,7 @@ static ModelAnimation *LoadGLTFModelAnimations(const char *fileName, int *animCo ModelAnimation *output = animations + a; // 30 frames sampled per second - const float timeStep = (1.0f/30.0f); + const float timeStep = (1.0f/60.0f); float animationDuration = 0.0f; // Getting the max animation time to consider for animation duration @@ -4783,7 +4984,7 @@ static ModelAnimation *LoadGLTFModelAnimations(const char *fileName, int *animCo int frameCounts = (int)channel->sampler->input->count; float lastFrameTime = 0.0f; - if (GLTFReadValue(channel->sampler->input, frameCounts - 1, &lastFrameTime, 1, sizeof(float))) + if (GLTFReadValue(channel->sampler->input, frameCounts - 1, &lastFrameTime)) { animationDuration = fmaxf(lastFrameTime, animationDuration); } @@ -4808,12 +5009,18 @@ static ModelAnimation *LoadGLTFModelAnimations(const char *fileName, int *animCo { output->framePoses[frame] = RL_MALLOC(output->boneCount*sizeof(Transform)); - for (int i = 0; i < output->boneCount; i++) + for (unsigned int i = 0; i < output->boneCount; i++) { - output->framePoses[frame][i].translation = Vector3Zero(); - output->framePoses[frame][i].rotation = QuaternionIdentity(); + if (data->nodes[i].has_translation) memcpy(&output->framePoses[frame][i].translation, data->nodes[i].translation, 3 * sizeof(float)); + else output->framePoses[frame][i].translation = Vector3Zero(); + + if (data->nodes[i].has_rotation) memcpy(&output->framePoses[frame][i], data->nodes[i].rotation, 4 * sizeof(float)); + else output->framePoses[frame][i].rotation = QuaternionIdentity(); + output->framePoses[frame][i].rotation = QuaternionNormalize(output->framePoses[frame][i].rotation); - output->framePoses[frame][i].scale = Vector3One(); + + if (data->nodes[i].has_scale) memcpy(&output->framePoses[frame][i].scale, data->nodes[i].scale, 3 * sizeof(float)); + else output->framePoses[frame][i].scale = Vector3One(); } } @@ -4839,7 +5046,7 @@ static ModelAnimation *LoadGLTFModelAnimations(const char *fileName, int *animCo for (unsigned int j = 0; j < sampler->input->count; j++) { float inputFrameTime; - if (GLTFReadValue(sampler->input, j, &inputFrameTime, 1, sizeof(float))) + if (GLTFReadValue(sampler->input, j, &inputFrameTime)) { if (frameTime < inputFrameTime) { @@ -4848,7 +5055,7 @@ static ModelAnimation *LoadGLTFModelAnimations(const char *fileName, int *animCo outputMax = j; float previousInputTime = 0.0f; - if (GLTFReadValue(sampler->input, outputMin, &previousInputTime, 1, sizeof(float))) + if (GLTFReadValue(sampler->input, outputMin, &previousInputTime)) { if ((inputFrameTime - previousInputTime) != 0) { @@ -4864,38 +5071,72 @@ static ModelAnimation *LoadGLTFModelAnimations(const char *fileName, int *animCo // If the current transformation has no information for the current frame time point if (shouldSkipFurtherTransformation) continue; - + if (channel->target_path == cgltf_animation_path_type_translation) { Vector3 translationStart; Vector3 translationEnd; - bool success = GLTFReadValue(sampler->output, outputMin, &translationStart, 3, sizeof(float)); - success = GLTFReadValue(sampler->output, outputMax, &translationEnd, 3, sizeof(float)) || success; + float values[3]; + + bool success = GLTFReadValue(sampler->output, outputMin, values); + + translationStart.x = values[0]; + translationStart.y = values[1]; + translationStart.z = values[2]; + + success = GLTFReadValue(sampler->output, outputMax, values) || success; + + translationEnd.x = values[0]; + translationEnd.y = values[1]; + translationEnd.z = values[2]; if (success) output->framePoses[frame][boneId].translation = Vector3Lerp(translationStart, translationEnd, lerpPercent); } if (channel->target_path == cgltf_animation_path_type_rotation) { - Quaternion rotationStart; - Quaternion rotationEnd; - - bool success = GLTFReadValue(sampler->output, outputMin, &rotationStart, 4, sizeof(float)); - success = GLTFReadValue(sampler->output, outputMax, &rotationEnd, 4, sizeof(float)) || success; + Vector4 rotationStart; + Vector4 rotationEnd; + + float values[4]; + + bool success = GLTFReadValue(sampler->output, outputMin, &values); + + rotationStart.x = values[0]; + rotationStart.y = values[1]; + rotationStart.z = values[2]; + rotationStart.w = values[3]; + + success = GLTFReadValue(sampler->output, outputMax, &values) || success; + + rotationEnd.x = values[0]; + rotationEnd.y = values[1]; + rotationEnd.z = values[2]; + rotationEnd.w = values[3]; if (success) { - output->framePoses[frame][boneId].rotation = QuaternionLerp(rotationStart, rotationEnd, lerpPercent); - output->framePoses[frame][boneId].rotation = QuaternionNormalize(output->framePoses[frame][boneId].rotation); + output->framePoses[frame][boneId].rotation = QuaternionNlerp(rotationStart, rotationEnd, lerpPercent); } } if (channel->target_path == cgltf_animation_path_type_scale) { Vector3 scaleStart; Vector3 scaleEnd; - - bool success = GLTFReadValue(sampler->output, outputMin, &scaleStart, 3, sizeof(float)); - success = GLTFReadValue(sampler->output, outputMax, &scaleEnd, 3, sizeof(float)) || success; + + float values[3]; + + bool success = GLTFReadValue(sampler->output, outputMin, &values); + + scaleStart.x = values[0]; + scaleStart.y = values[1]; + scaleStart.z = values[2]; + + success = GLTFReadValue(sampler->output, outputMax, &values) || success; + + scaleEnd.x = values[0]; + scaleEnd.y = values[1]; + scaleEnd.z = values[2]; if (success) output->framePoses[frame][boneId].scale = Vector3Lerp(scaleStart, scaleEnd, lerpPercent); } @@ -4946,4 +5187,167 @@ static ModelAnimation *LoadGLTFModelAnimations(const char *fileName, int *animCo return animations; } +void LoadGLTFMesh(cgltf_data* data, cgltf_mesh* mesh, Model* outModel, Matrix currentTransform, int* primitiveIndex, const char* fileName) +{ + for (unsigned int p = 0; p < mesh->primitives_count; p++) + { + for (unsigned int j = 0; j < mesh->primitives[p].attributes_count; j++) + { + if (mesh->primitives[p].attributes[j].type == cgltf_attribute_type_position) + { + cgltf_accessor *acc = mesh->primitives[p].attributes[j].data; + outModel->meshes[(*primitiveIndex)].vertexCount = (int)acc->count; + int bufferSize = outModel->meshes[(*primitiveIndex)].vertexCount*3*sizeof(float); + outModel->meshes[(*primitiveIndex)].animVertices = RL_MALLOC(bufferSize); + + outModel->meshes[(*primitiveIndex)].vertices = GLTFReadValuesAs(acc, cgltf_component_type_r_32f, false); + + // Transform using the nodes matrix attributes + for (unsigned int v = 0; v < outModel->meshes[(*primitiveIndex)].vertexCount; v++) + { + Vector3 vertex = { + outModel->meshes[(*primitiveIndex)].vertices[(v * 3 + 0)], + outModel->meshes[(*primitiveIndex)].vertices[(v * 3 + 1)], + outModel->meshes[(*primitiveIndex)].vertices[(v * 3 + 2)], + }; + + vertex = Vector3Transform(vertex, currentTransform); + + outModel->meshes[(*primitiveIndex)].vertices[(v * 3 + 0)] = vertex.x; + outModel->meshes[(*primitiveIndex)].vertices[(v * 3 + 1)] = vertex.y; + outModel->meshes[(*primitiveIndex)].vertices[(v * 3 + 2)] = vertex.z; + } + + memcpy(outModel->meshes[(*primitiveIndex)].animVertices, outModel->meshes[(*primitiveIndex)].vertices, bufferSize); + } + else if (mesh->primitives[p].attributes[j].type == cgltf_attribute_type_normal) + { + cgltf_accessor *acc = mesh->primitives[p].attributes[j].data; + + int bufferSize = (int)(acc->count*3*sizeof(float)); + outModel->meshes[(*primitiveIndex)].animNormals = RL_MALLOC(bufferSize); + + outModel->meshes[(*primitiveIndex)].normals = GLTFReadValuesAs(acc, cgltf_component_type_r_32f, false); + + // Transform using the nodes matrix attributes + for (unsigned int v = 0; v < outModel->meshes[(*primitiveIndex)].vertexCount; v++) + { + Vector3 normal = { + outModel->meshes[(*primitiveIndex)].normals[(v * 3 + 0)], + outModel->meshes[(*primitiveIndex)].normals[(v * 3 + 1)], + outModel->meshes[(*primitiveIndex)].normals[(v * 3 + 2)], + }; + + normal = Vector3Transform(normal, currentTransform); + + outModel->meshes[(*primitiveIndex)].normals[(v * 3 + 0)] = normal.x; + outModel->meshes[(*primitiveIndex)].normals[(v * 3 + 1)] = normal.y; + outModel->meshes[(*primitiveIndex)].normals[(v * 3 + 2)] = normal.z; + } + + memcpy(outModel->meshes[(*primitiveIndex)].animNormals, outModel->meshes[(*primitiveIndex)].normals, bufferSize); + } + else if (mesh->primitives[p].attributes[j].type == cgltf_attribute_type_texcoord) + { + cgltf_accessor *acc = mesh->primitives[p].attributes[j].data; + outModel->meshes[(*primitiveIndex)].texcoords = GLTFReadValuesAs(acc, cgltf_component_type_r_32f, false); + } + else if (mesh->primitives[p].attributes[j].type == cgltf_attribute_type_joints) + { + cgltf_accessor *acc = mesh->primitives[p].attributes[j].data; + unsigned int boneCount = acc->count; + unsigned int totalBoneWeights = boneCount * 4; + + outModel->meshes[(*primitiveIndex)].boneIds = RL_MALLOC(totalBoneWeights * sizeof(int)); + short* bones = GLTFReadValuesAs(acc, cgltf_component_type_r_16, false); + for (unsigned int a = 0; a < totalBoneWeights; a++) + { + outModel->meshes[(*primitiveIndex)].boneIds[a] = 0; + if(bones[a] < 0) continue; + + cgltf_node* skinJoint = data->skins->joints[bones[a]]; + for (unsigned int k = 0; k < data->nodes_count; k++) + { + if (data->nodes + k == skinJoint) + { + outModel->meshes[(*primitiveIndex)].boneIds[a] = k; + break; + } + } + } + RL_FREE(bones); + } + else if (mesh->primitives[p].attributes[j].type == cgltf_attribute_type_weights) + { + cgltf_accessor *acc = mesh->primitives[p].attributes[j].data; + outModel->meshes[(*primitiveIndex)].boneWeights = GLTFReadValuesAs(acc, cgltf_component_type_r_32f, false); + } + else if (mesh->primitives[p].attributes[j].type == cgltf_attribute_type_color) + { + cgltf_accessor *acc = mesh->primitives[p].attributes[j].data; + outModel->meshes[(*primitiveIndex)].colors = GLTFReadValuesAs(acc, cgltf_component_type_r_8u, true); + } + } + + cgltf_accessor *acc = mesh->primitives[p].indices; + if (acc) + { + outModel->meshes[(*primitiveIndex)].triangleCount = acc->count/3; + outModel->meshes[(*primitiveIndex)].indices = GLTFReadValuesAs(acc, cgltf_component_type_r_16u, false); + } + else + { + // Unindexed mesh + outModel->meshes[(*primitiveIndex)].triangleCount = outModel->meshes[(*primitiveIndex)].vertexCount/3; + } + + if (mesh->primitives[p].material) + { + // Compute the offset + outModel->meshMaterial[(*primitiveIndex)] = (int)(mesh->primitives[p].material - data->materials); + } + else + { + outModel->meshMaterial[(*primitiveIndex)] = outModel->materialCount - 1; + } + + BindGLTFPrimitiveToBones(outModel, data, *primitiveIndex); + + (*primitiveIndex) = (*primitiveIndex) + 1; + } +} + +void LoadGLTFNode(cgltf_data* data, cgltf_node* node, Model* outModel, Matrix currentTransform, int* primitiveIndex, const char* fileName) +{ + Matrix nodeTransform = { node->matrix[0], node->matrix[4], node->matrix[8], node->matrix[12], + node->matrix[1], node->matrix[5], node->matrix[9], node->matrix[13], + node->matrix[2], node->matrix[6], node->matrix[10], node->matrix[14], + node->matrix[3], node->matrix[7], node->matrix[11], node->matrix[15] }; + + currentTransform = MatrixMultiply(nodeTransform, currentTransform); + + if(node->mesh != NULL) + { + LoadGLTFMesh(data, node->mesh, outModel, currentTransform, primitiveIndex, fileName); + } + + for(unsigned int i = 0; i < node->children_count; i++) + { + LoadGLTFNode(data, node->children[i], outModel, currentTransform, primitiveIndex, fileName); + } +} + +static void GetGLTFPrimitiveCount(cgltf_node* node, int* outCount) +{ + if(node->mesh != NULL) + { + *outCount += node->mesh->primitives_count; + } + + for(unsigned int i = 0; i < node->children_count; i++) + { + GetGLTFPrimitiveCount(node->children[i], outCount); + } +} + #endif