From f9b79403d1142d61ea0ed6f41456600bb06a7318 Mon Sep 17 00:00:00 2001 From: Hristo Stamenov Date: Sun, 14 Mar 2021 13:09:31 +0200 Subject: [PATCH] Improve gltf support (#1647) * Implement a load values from accessor function. Added some more value types for the different GLTF attributes. Fixed crash when loading animated triangle. * Split GLTF model loading into separate functions for readability. * Fixed the already working models that I broke when introducing GLTFReadValue. Improved the example for gltf models to be able to switch between a few models. * Removed license from screen. It is pu inside a license file anyway. * Small improvements on the naming of functions Removed (*model). and replaced it with model-> --- examples/models/models_gltf_model.c | 42 +- .../resources/gltf/Textures/raylib_32x32.png | Bin 0 -> 189 bytes .../models/resources/gltf/raylib_32x32.glb | Bin 0 -> 266724 bytes src/models.c | 753 ++++++++++-------- 4 files changed, 478 insertions(+), 317 deletions(-) create mode 100644 examples/models/resources/gltf/Textures/raylib_32x32.png create mode 100644 examples/models/resources/gltf/raylib_32x32.glb diff --git a/examples/models/models_gltf_model.c b/examples/models/models_gltf_model.c index c064e34f..2e9e8456 100644 --- a/examples/models/models_gltf_model.c +++ b/examples/models/models_gltf_model.c @@ -39,7 +39,18 @@ int main(void) camera.fovy = 45.0f; // Camera field-of-view Y camera.type = CAMERA_PERSPECTIVE; // Camera mode type - Model model = LoadModel("resources/gltf/Avocado.glb"); // Load the animated model mesh and + Model model[7]; + + model[0] = LoadModel("resources/gltf/raylib_32x32.glb"); + model[1] = LoadModel("resources/gltf/rigged_figure.glb"); + model[2] = LoadModel("resources/gltf/Avocado.glb"); + model[3] = LoadModel("resources/gltf/GearboxAssy.glb"); + model[4] = LoadModel("resources/gltf/BoxAnimated.glb"); + model[5] = LoadModel("resources/gltf/AnimatedTriangle.gltf"); + model[6] = LoadModel("resources/gltf/AnimatedMorphCube.glb"); + + int currentModel = 0; + int modelCount = 7; Vector3 position = { 0.0f, 0.0f, 0.0f }; // Set model position @@ -54,23 +65,39 @@ int main(void) // Update //---------------------------------------------------------------------------------- UpdateCamera(&camera); + + if(IsKeyReleased(KEY_RIGHT)) + { + currentModel++; + if(currentModel == modelCount) + { + currentModel = 0; + } + } + + if(IsKeyReleased(KEY_LEFT)) + { + currentModel--; + if(currentModel < 0) + { + currentModel = modelCount - 1; + } + } // Draw //---------------------------------------------------------------------------------- BeginDrawing(); - ClearBackground(RAYWHITE); + ClearBackground(SKYBLUE); BeginMode3D(camera); - DrawModelEx(model, position, (Vector3){ 0.0f, 1.0f, 0.0f }, 180.0f, (Vector3){ 15.0f, 15.0f, 15.0f }, WHITE); + DrawModelEx(model[currentModel], position, (Vector3){ 0.0f, 1.0f, 0.0f }, 180.0f, (Vector3){ 2.0f, 2.0f, 2.0f }, WHITE); DrawGrid(10, 1.0f); // Draw a grid EndMode3D(); - DrawText("(cc0) Avocado by @Microsoft", screenWidth - 200, screenHeight - 20, 10, GRAY); - EndDrawing(); //---------------------------------------------------------------------------------- } @@ -78,7 +105,10 @@ int main(void) // De-Initialization //-------------------------------------------------------------------------------------- - UnloadModel(model); // Unload model + for(int i = 0; i < modelCount; i++) + { + UnloadModel(model[i]); // Unload model + } CloseWindow(); // Close window and OpenGL context //-------------------------------------------------------------------------------------- diff --git a/examples/models/resources/gltf/Textures/raylib_32x32.png b/examples/models/resources/gltf/Textures/raylib_32x32.png new file mode 100644 index 0000000000000000000000000000000000000000..203ff7f706f236ac255f728bd39c74f1864cf5e5 GIT binary patch literal 189 zcmeAS@N?(olHy`uVBq!ia0vp^93afW1|*O0@9PFqEuJopArg|wHry;c6%4|1mK#K# za!E)^O8jrWP(PvZzog{3dA7xTH>2Dpe7L?o-cdxLX{zyu51$xzI@kqRA6Aj;{;$8) z>3{d_Z-1_zwEc14cG-*nhh)Y7XTOsAxBvH-yY4?_)hP6GypIBBDUPx9iOf$m}OboFyt=akR{0Q8|sBLDyZ literal 0 HcmV?d00001 diff --git a/examples/models/resources/gltf/raylib_32x32.glb b/examples/models/resources/gltf/raylib_32x32.glb new file mode 100644 index 0000000000000000000000000000000000000000..4fc56ad48fe4b710e1b45a44ea0cb17a4591a867 GIT binary patch literal 266724 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

s{?l*orOK z>}qerPVB&TS9=%qduI=JyV`qk0Q<4e)zZ(Z#V_>52Z=xTq#cYMQFSNjKk;}?Fq+JEq0bj~aEFI4RS2#i1o=xPT+ za0Ek8S33kkBNRfq+F=kL;Sko!Y?E)x_LMZ5J7eR3pLs3_|1WKb6O1j!*P#)z_*43_n%BX~ju67kvM>SM+wQHa@ zYN4j9T?h414|QGb255{%Xy|G;L31=iQ&+nMTB8+Oy4r2f9_`T9)$Rbj4&I4&g!;eN z#k+Fc1)W{(Zs>vT&}-tVc2D$%UJLK#YWG2Z^g~})djJMw5C*#1Logh}Fx1r^fzcR+ zk*@X_jK?^Pb+sp8GA3c7t33tNF%45)?HQPjS(xc+&%u1m!(3N;0TyEs7P{I?upG;< z)YV>r)mVj1yA>ecZ!c zSNj1T;}IUZ+E4Ht&+ydMeu39`g_o}O8@$Imymhrd;4?nqqpSS|-|-D!UF{$EjbHfb zYX8B1F*qO4zfiRUATRWzLwM-x|L^!Y?E)x_LMZ5J7eR3pLs3_| z1WKb6O1j!*P#)z_*43_n%BX~ju67kvM>SM+wQHa@YN4j9T?h414|QGb255{%Xy|G; zL31=iQ&+nMTB8+Oy4r2f9_`T9)$V}K=!A~0b{BLJV z7>q#}=xPtaa16sxS9=6TV-!ZZ+G8*t<1p6Mo`A`igo&>96iml7Om($qU^Zr9rmH;% z^Dz%|UF`)}j73=JYA?ZZEW=V)dj(cw6;`_1Yp@>cu-4VyfXjjr|a31Gy*44hC&2xlH^di*% z{kiv5j<4Xdt9=bOa9x-2&#BrsaU1%x?pv<*9o)w~+;z1d;4vQIp{xA_dR^=peF{}u zua~{#_ywN3+OO~iuc6n-RPDET54|?_&ei^a&-jFouJ#vv$2WX+wSV9@e&MI9{RjWW z5@LLsE99R}eM4q;vG2#AbGi0EoZL3BhzR98C& zVj~t}y4rCNAMp^^)lPuKNQ8v0b`m57i<8q*;&|nOtqXUY4C^LsnNi2XZ49a=O}ikRSPw*VQh7!YG7-u67X=M==z2wM(Eh zN};5yT?XY*4rN{K3aE@qsOV}}fu8qNqgA2)?|D#7j%%R0t6d9qP#by!y4p?99L>EY7f9bWaRorP_+kPFq*QnLDe3D z;TVRYuJ#Cw#wd(*wZ}l8>sg0?zBW{CectDIt{aE3uJ!~>L_=<;&-PTcCt))5*`Cd~ zOx2!(>6nJ8uJ#Pf#w^TqwdX*O%ldrL|9#G=wjQVFbKN}5b+s2@A$D_nJvOV_i?A4a zY~Ighs`e5r$1*H+wO3#@R$-;9y#^oo|1hniYoTg?!bXlaV7;ro30trkzqpO6y%pQ> zm&>-f+B>ityRg&M-h=(vhrO=$0i?ti{ym?eYNx_s9Ku0Y`v{KWC%4PsY9GUKWWqnL zQ?*avG*029t9=INaSmr)?F-QTJPlI2+PdFg#wA>IwXfhR(xbMkeGS*4`+8*uZi{oJLU3Xi$&5gxkQPw*7_*((#bQMI4pIkIw@eg;#u zU*I)f;iaqn2Ji6>Z(Z#V_#A-$4$jN}`*SbtFZj%LpYYMu{(`U2pMCw|HmdeFe8+zQ z`S0d3Rr?2i;}?Fq+JEq0EPie2U#Qvv@R7q0c<*X|!dHC3XIJ|he&PqdyV?)&48QTq z)&7J3_`d&k0n{*C8+j literal 0 HcmV?d00001 diff --git a/src/models.c b/src/models.c index 93146b7c..145e08b3 100644 --- a/src/models.c +++ b/src/models.c @@ -117,6 +117,11 @@ 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 InitGLTFBones(Model* model, const cgltf_data* data); #endif //---------------------------------------------------------------------------------- @@ -1087,13 +1092,16 @@ void UpdateModelAnimation(Model model, ModelAnimation anim, int frame) // Normals processing // NOTE: We use meshes.baseNormals (default normal) to calculate meshes.normals (animated normals) - animNormal = (Vector3){ model.meshes[m].normals[vCounter], model.meshes[m].normals[vCounter + 1], model.meshes[m].normals[vCounter + 2] }; - animNormal = Vector3RotateByQuaternion(animNormal, QuaternionMultiply(outRotation, QuaternionInvert(inRotation))); - model.meshes[m].animNormals[vCounter] = animNormal.x; - model.meshes[m].animNormals[vCounter + 1] = animNormal.y; - model.meshes[m].animNormals[vCounter + 2] = animNormal.z; + if(model.meshes[m].normals != NULL) + { + animNormal = (Vector3){ model.meshes[m].normals[vCounter], model.meshes[m].normals[vCounter + 1], model.meshes[m].normals[vCounter + 2] }; + animNormal = Vector3RotateByQuaternion(animNormal, QuaternionMultiply(outRotation, QuaternionInvert(inRotation))); + model.meshes[m].animNormals[vCounter] = animNormal.x; + model.meshes[m].animNormals[vCounter + 1] = animNormal.y; + model.meshes[m].animNormals[vCounter + 2] = animNormal.z; + } + vCounter += 3; - boneCounter += 4; } @@ -3650,12 +3658,37 @@ static Image LoadImageFromCgltfImage(cgltf_image *image, const char *texPath, Co return rimage; } + +static bool GLTFReadValue(cgltf_accessor* acc, unsigned int index, void* variable, unsigned int elements, unsigned int size) +{ + if (acc->count == 2) + { + if (index > 1) + { + return false; + } + + memcpy(variable, index == 0 ? acc->min : acc->max, elements * size); + return true; + } + + unsigned int stride = size * elements; + memset(variable, 0, stride); + + 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); + return true; +} + // LoadGLTF loads in model data from given filename, supporting both .gltf and .glb 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) and Hristo Stamenov(@object71) Features: - Supports .gltf and .glb files @@ -3671,18 +3704,6 @@ static Model LoadGLTF(const char *fileName) *************************************************************************************/ - #define LOAD_ACCESSOR(type, nbcomp, acc, dst) \ - { \ - int n = 0; \ - type* buf = (type*)acc->buffer_view->buffer->data + acc->buffer_view->offset/sizeof(type) + acc->offset/sizeof(type); \ - for (unsigned int k = 0; k < acc->count; k++) {\ - for (int l = 0; l < nbcomp; l++) {\ - dst[nbcomp*k + l] = buf[n + l];\ - }\ - n += (int)(acc->stride/sizeof(type));\ - }\ - } - Model model = { 0 }; // glTF file loading @@ -3719,131 +3740,10 @@ 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)); - - for (unsigned int j = 0; j < data->nodes_count; j++) - { - strcpy(model.bones[j].name, data->nodes[j].name == 0 ? "ANIMJOINT" : data->nodes[j].name); - model.bones[j].parent = (data->nodes[j].parent != NULL) ? data->nodes[j].parent - data->nodes : -1; - } - - for (unsigned int i = 0; i < data->nodes_count; i++) - { - if (data->nodes[i].has_translation) memcpy(&model.bindPose[i].translation, data->nodes[i].translation, 3 * sizeof(float)); - else model.bindPose[i].translation = Vector3Zero(); - - if (data->nodes[i].has_rotation) memcpy(&model.bindPose[i].rotation, data->nodes[i].rotation, 4 * sizeof(float)); - else model.bindPose[i].rotation = QuaternionIdentity(); - - model.bindPose[i].rotation = QuaternionNormalize(model.bindPose[i].rotation); - - if (data->nodes[i].has_scale) memcpy(&model.bindPose[i].scale, data->nodes[i].scale, 3 * sizeof(float)); - else model.bindPose[i].scale = Vector3One(); - } - - { - bool* completedBones = RL_CALLOC(model.boneCount, sizeof(bool)); - int numberCompletedBones = 0; - - while (numberCompletedBones < model.boneCount) { - for (int i = 0; i < model.boneCount; i++) - { - if (completedBones[i]) continue; - - if (model.bones[i].parent < 0) { - completedBones[i] = true; - numberCompletedBones++; - continue; - } - - if (!completedBones[model.bones[i].parent]) continue; - - Transform* currentTransform = &model.bindPose[i]; - BoneInfo* currentBone = &model.bones[i]; - int root = currentBone->parent; - if (root >= model.boneCount) - root = 0; - Transform* parentTransform = &model.bindPose[root]; - - currentTransform->rotation = QuaternionMultiply(parentTransform->rotation, currentTransform->rotation); - currentTransform->translation = Vector3RotateByQuaternion(currentTransform->translation, parentTransform->rotation); - currentTransform->translation = Vector3Add(currentTransform->translation, parentTransform->translation); - currentTransform->scale = Vector3Multiply(parentTransform->scale, parentTransform->scale); - completedBones[i] = true; - numberCompletedBones++; - } - } - - RL_FREE(completedBones); - } - - for (int i = 0; i < model.materialCount - 1; i++) - { - model.materials[i] = LoadMaterialDefault(); - Color tint = (Color){ 255, 255, 255, 255 }; - const char *texPath = GetDirectoryPath(fileName); - - // Ensure material follows raylib support for PBR (metallic/roughness flow) - if (data->materials[i].has_pbr_metallic_roughness) - { - tint.r = (unsigned char)(data->materials[i].pbr_metallic_roughness.base_color_factor[0] * 255); - tint.g = (unsigned char)(data->materials[i].pbr_metallic_roughness.base_color_factor[1] * 255); - tint.b = (unsigned char)(data->materials[i].pbr_metallic_roughness.base_color_factor[2] * 255); - tint.a = (unsigned char)(data->materials[i].pbr_metallic_roughness.base_color_factor[3] * 255); - - model.materials[i].maps[MATERIAL_MAP_ALBEDO].color = tint; - - if (data->materials[i].pbr_metallic_roughness.base_color_texture.texture) - { - Image albedo = LoadImageFromCgltfImage(data->materials[i].pbr_metallic_roughness.base_color_texture.texture->image, texPath, tint); - model.materials[i].maps[MATERIAL_MAP_ALBEDO].texture = LoadTextureFromImage(albedo); - UnloadImage(albedo); - } - - tint = WHITE; // Set tint to white after it's been used by Albedo - - if (data->materials[i].pbr_metallic_roughness.metallic_roughness_texture.texture) - { - Image metallicRoughness = LoadImageFromCgltfImage(data->materials[i].pbr_metallic_roughness.metallic_roughness_texture.texture->image, texPath, tint); - model.materials[i].maps[MATERIAL_MAP_ROUGHNESS].texture = LoadTextureFromImage(metallicRoughness); - - float roughness = data->materials[i].pbr_metallic_roughness.roughness_factor; - model.materials[i].maps[MATERIAL_MAP_ROUGHNESS].value = roughness; - - float metallic = data->materials[i].pbr_metallic_roughness.metallic_factor; - model.materials[i].maps[MATERIAL_MAP_METALNESS].value = metallic; - - UnloadImage(metallicRoughness); - } - - if (data->materials[i].normal_texture.texture) - { - Image normalImage = LoadImageFromCgltfImage(data->materials[i].normal_texture.texture->image, texPath, tint); - model.materials[i].maps[MATERIAL_MAP_NORMAL].texture = LoadTextureFromImage(normalImage); - UnloadImage(normalImage); - } - - if (data->materials[i].occlusion_texture.texture) - { - Image occulsionImage = LoadImageFromCgltfImage(data->materials[i].occlusion_texture.texture->image, texPath, tint); - model.materials[i].maps[MATERIAL_MAP_OCCLUSION].texture = LoadTextureFromImage(occulsionImage); - UnloadImage(occulsionImage); - } - - if (data->materials[i].emissive_texture.texture) - { - Image emissiveImage = LoadImageFromCgltfImage(data->materials[i].emissive_texture.texture->image, texPath, tint); - model.materials[i].maps[MATERIAL_MAP_EMISSION].texture = LoadTextureFromImage(emissiveImage); - tint.r = (unsigned char)(data->materials[i].emissive_factor[0]*255); - tint.g = (unsigned char)(data->materials[i].emissive_factor[1]*255); - tint.b = (unsigned char)(data->materials[i].emissive_factor[2]*255); - model.materials[i].maps[MATERIAL_MAP_EMISSION].color = tint; - UnloadImage(emissiveImage); - } - } - } - - model.materials[model.materialCount - 1] = LoadMaterialDefault(); - + + InitGLTFBones(&model, data); + LoadGLTFMaterial(&model, fileName, data); + int primitiveIndex = 0; for (unsigned int i = 0; i < data->meshes_count; i++) @@ -3859,18 +3759,65 @@ static Model LoadGLTF(const char *fileName) int bufferSize = model.meshes[primitiveIndex].vertexCount * 3 * sizeof(float); model.meshes[primitiveIndex].vertices = RL_MALLOC(bufferSize); model.meshes[primitiveIndex].animVertices = RL_MALLOC(bufferSize); - - LOAD_ACCESSOR(float, 3, acc, model.meshes[primitiveIndex].vertices); + + if(acc->component_type == cgltf_component_type_r_32f) + { + for(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(int a = 0; a < acc->count; a++) + { + GLTFReadValue(acc, a, readValue, 3, sizeof(int)); + model.meshes[primitiveIndex].vertices[(a * 3) + 0] = readValue[0]; + model.meshes[primitiveIndex].vertices[(a * 3) + 1] = readValue[1]; + model.meshes[primitiveIndex].vertices[(a * 3) + 2] = 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); - - LOAD_ACCESSOR(float, 3, acc, model.meshes[primitiveIndex].normals); + + if(acc->component_type == cgltf_component_type_r_32f) + { + for(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(int a = 0; a < acc->count; a++) + { + GLTFReadValue(acc, a, readValue, 3, sizeof(int)); + model.meshes[primitiveIndex].normals[(a * 3) + 0] = readValue[0]; + model.meshes[primitiveIndex].normals[(a * 3) + 1] = readValue[1]; + model.meshes[primitiveIndex].normals[(a * 3) + 2] = 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) @@ -3880,7 +3827,11 @@ static Model LoadGLTF(const char *fileName) if (acc->component_type == cgltf_component_type_r_32f) { model.meshes[primitiveIndex].texcoords = RL_MALLOC(acc->count*2*sizeof(float)); - LOAD_ACCESSOR(float, 2, acc, model.meshes[primitiveIndex].texcoords) + + for(int a = 0; a < acc->count; a++) + { + GLTFReadValue(acc, a, model.meshes[primitiveIndex].texcoords + (a * 2), 2, sizeof(float)); + } } else { @@ -3891,87 +3842,44 @@ static Model LoadGLTF(const char *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; - - if (acc->component_type == cgltf_component_type_r_16u) - { - model.meshes[primitiveIndex].boneIds = RL_MALLOC(sizeof(int) * acc->count * 4); - short* bones = RL_MALLOC(sizeof(short) * acc->count * 4); - - LOAD_ACCESSOR(short, 4, acc, bones); - for (unsigned int a = 0; a < acc->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 (acc->component_type == cgltf_component_type_r_8u) - { - model.meshes[primitiveIndex].boneIds = RL_MALLOC(sizeof(int) * acc->count * 4); - unsigned char* bones = RL_MALLOC(sizeof(unsigned char) * acc->count * 4); - - LOAD_ACCESSOR(unsigned char, 4, acc, bones); - for (unsigned int a = 0; a < acc->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: [%s] glTF bones in unexpected format", fileName); - } - + 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)); - LOAD_ACCESSOR(float, 4, acc, model.meshes[primitiveIndex].boneWeights) + + if(acc->component_type == cgltf_component_type_r_32f) + { + for(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(int a = 0; a < acc->count; a++) + { + GLTFReadValue(acc, a, readValue, 4, sizeof(unsigned int)); + model.meshes[primitiveIndex].normals[(a * 4) + 0] = readValue[0]; + model.meshes[primitiveIndex].normals[(a * 4) + 1] = readValue[1]; + model.meshes[primitiveIndex].normals[(a * 4) + 2] = readValue[2]; + model.meshes[primitiveIndex].normals[(a * 4) + 3] = readValue[3]; + } + } + else + { + // TODO: Support normalized unsigned byte/unsigned short weights + TRACELOG(LOG_WARNING, "MODEL: [%s] glTF normals must be float or int", fileName); + } } } cgltf_accessor *acc = data->meshes[i].primitives[p].indices; - - if (acc) - { - if (acc->component_type == cgltf_component_type_r_16u) - { - model.meshes[primitiveIndex].triangleCount = (int)acc->count/3; - model.meshes[primitiveIndex].indices = RL_MALLOC(model.meshes[primitiveIndex].triangleCount*3*sizeof(unsigned short)); - LOAD_ACCESSOR(unsigned short, 1, acc, model.meshes[primitiveIndex].indices) - } - else - { - // TODO: Support unsigned byte/unsigned int - TRACELOG(LOG_WARNING, "MODEL: [%s] glTF index data must be unsigned short", fileName); - } - } - else - { - // Unindexed mesh - model.meshes[primitiveIndex].triangleCount = model.meshes[primitiveIndex].vertexCount/3; - } - + LoadGLTFModelIndices(&model, acc, primitiveIndex); + if (data->meshes[i].primitives[p].material) { // Compute the offset @@ -3981,79 +3889,14 @@ static Model LoadGLTF(const char *fileName) { model.meshMaterial[primitiveIndex] = model.materialCount - 1; } - -// if (data->meshes[i].) - - if (model.meshes[primitiveIndex].boneIds == NULL && data->nodes_count > 0) - { - for (int nodeId = 0; nodeId < data->nodes_count; nodeId++) - { - if (data->nodes[nodeId].mesh == &(data->meshes[i])) - { - 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++) - { - if(b % 4 == 0) - { - model.meshes[primitiveIndex].boneIds[b] = nodeId; - model.meshes[primitiveIndex].boneWeights[b] = 1.0f; - } - else - { - model.meshes[primitiveIndex].boneIds[b] = 0; - model.meshes[primitiveIndex].boneWeights[b] = 0.0f; - } - - } - Vector3 boundVertex = { 0 }; - Vector3 boundNormal = { 0 }; + BindGLTFPrimitiveToBones(&model, data, primitiveIndex); - 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 - 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; - } - } - } - } - primitiveIndex++; } - + } - + cgltf_free(data); } else TRACELOG(LOG_WARNING, "MODEL: [%s] Failed to load glTF data", fileName); @@ -4063,24 +3906,312 @@ static Model LoadGLTF(const char *fileName) return model; } -static bool GltfReadFloat(cgltf_accessor* acc, unsigned int index, float* variable, unsigned int elements) +static void InitGLTFBones(Model* model, const cgltf_data* data) { - if (acc->count == 2) + for (unsigned int j = 0; j < data->nodes_count; j++) { - if (index > 1) - { - return false; - } - - memcpy(variable, index == 0 ? acc->min : acc->max, elements * sizeof(float)); - return true; - } - else if (cgltf_accessor_read_float(acc, index, variable, elements)) - { - return true; + strcpy(model->bones[j].name, data->nodes[j].name == 0 ? "ANIMJOINT" : data->nodes[j].name); + model->bones[j].parent = (data->nodes[j].parent != NULL) ? data->nodes[j].parent - data->nodes : -1; } - return false; + for (unsigned int i = 0; i < data->nodes_count; i++) + { + if (data->nodes[i].has_translation) memcpy(&model->bindPose[i].translation, data->nodes[i].translation, 3 * sizeof(float)); + else model->bindPose[i].translation = Vector3Zero(); + + if (data->nodes[i].has_rotation) memcpy(&model->bindPose[i].rotation, data->nodes[i].rotation, 4 * sizeof(float)); + else model->bindPose[i].rotation = QuaternionIdentity(); + + model->bindPose[i].rotation = QuaternionNormalize(model->bindPose[i].rotation); + + if (data->nodes[i].has_scale) memcpy(&model->bindPose[i].scale, data->nodes[i].scale, 3 * sizeof(float)); + else model->bindPose[i].scale = Vector3One(); + } + + { + bool* completedBones = RL_CALLOC(model->boneCount, sizeof(bool)); + int numberCompletedBones = 0; + + while (numberCompletedBones < model->boneCount) { + for (int i = 0; i < model->boneCount; i++) + { + if (completedBones[i]) continue; + + if (model->bones[i].parent < 0) { + completedBones[i] = true; + numberCompletedBones++; + continue; + } + + if (!completedBones[model->bones[i].parent]) continue; + + Transform* currentTransform = &model->bindPose[i]; + BoneInfo* currentBone = &model->bones[i]; + int root = currentBone->parent; + if (root >= model->boneCount) + root = 0; + Transform* parentTransform = &model->bindPose[root]; + + currentTransform->rotation = QuaternionMultiply(parentTransform->rotation, currentTransform->rotation); + currentTransform->translation = Vector3RotateByQuaternion(currentTransform->translation, parentTransform->rotation); + currentTransform->translation = Vector3Add(currentTransform->translation, parentTransform->translation); + currentTransform->scale = Vector3Multiply(parentTransform->scale, parentTransform->scale); + completedBones[i] = true; + numberCompletedBones++; + } + } + + RL_FREE(completedBones); + } +} + +static void LoadGLTFMaterial(Model* model, const char* fileName, const cgltf_data* data) +{ + for (int i = 0; i < model->materialCount - 1; i++) + { + model->materials[i] = LoadMaterialDefault(); + Color tint = (Color){ 255, 255, 255, 255 }; + const char *texPath = GetDirectoryPath(fileName); + + // Ensure material follows raylib support for PBR (metallic/roughness flow) + if (data->materials[i].has_pbr_metallic_roughness) + { + tint.r = (unsigned char)(data->materials[i].pbr_metallic_roughness.base_color_factor[0] * 255); + tint.g = (unsigned char)(data->materials[i].pbr_metallic_roughness.base_color_factor[1] * 255); + tint.b = (unsigned char)(data->materials[i].pbr_metallic_roughness.base_color_factor[2] * 255); + tint.a = (unsigned char)(data->materials[i].pbr_metallic_roughness.base_color_factor[3] * 255); + + model->materials[i].maps[MATERIAL_MAP_ALBEDO].color = tint; + + if (data->materials[i].pbr_metallic_roughness.base_color_texture.texture) + { + Image albedo = LoadImageFromCgltfImage(data->materials[i].pbr_metallic_roughness.base_color_texture.texture->image, texPath, tint); + model->materials[i].maps[MATERIAL_MAP_ALBEDO].texture = LoadTextureFromImage(albedo); + UnloadImage(albedo); + } + + tint = WHITE; // Set tint to white after it's been used by Albedo + + if (data->materials[i].pbr_metallic_roughness.metallic_roughness_texture.texture) + { + Image metallicRoughness = LoadImageFromCgltfImage(data->materials[i].pbr_metallic_roughness.metallic_roughness_texture.texture->image, texPath, tint); + model->materials[i].maps[MATERIAL_MAP_ROUGHNESS].texture = LoadTextureFromImage(metallicRoughness); + + float roughness = data->materials[i].pbr_metallic_roughness.roughness_factor; + model->materials[i].maps[MATERIAL_MAP_ROUGHNESS].value = roughness; + + float metallic = data->materials[i].pbr_metallic_roughness.metallic_factor; + model->materials[i].maps[MATERIAL_MAP_METALNESS].value = metallic; + + UnloadImage(metallicRoughness); + } + + if (data->materials[i].normal_texture.texture) + { + Image normalImage = LoadImageFromCgltfImage(data->materials[i].normal_texture.texture->image, texPath, tint); + model->materials[i].maps[MATERIAL_MAP_NORMAL].texture = LoadTextureFromImage(normalImage); + UnloadImage(normalImage); + } + + if (data->materials[i].occlusion_texture.texture) + { + Image occulsionImage = LoadImageFromCgltfImage(data->materials[i].occlusion_texture.texture->image, texPath, tint); + model->materials[i].maps[MATERIAL_MAP_OCCLUSION].texture = LoadTextureFromImage(occulsionImage); + UnloadImage(occulsionImage); + } + + if (data->materials[i].emissive_texture.texture) + { + Image emissiveImage = LoadImageFromCgltfImage(data->materials[i].emissive_texture.texture->image, texPath, tint); + model->materials[i].maps[MATERIAL_MAP_EMISSION].texture = LoadTextureFromImage(emissiveImage); + tint.r = (unsigned char)(data->materials[i].emissive_factor[0]*255); + tint.g = (unsigned char)(data->materials[i].emissive_factor[1]*255); + tint.b = (unsigned char)(data->materials[i].emissive_factor[2]*255); + model->materials[i].maps[MATERIAL_MAP_EMISSION].color = tint; + UnloadImage(emissiveImage); + } + } + } + + 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(sizeof(int) * jointsAccessor->count * 4); + short* bones = RL_MALLOC(sizeof(short) * jointsAccessor->count * 4); + + for(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(sizeof(int) * jointsAccessor->count * 4); + unsigned char* bones = RL_MALLOC(sizeof(unsigned char) * jointsAccessor->count * 4); + + for(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 (int nodeId = 0; nodeId < data->nodes_count; nodeId++) + { + if (data->nodes[nodeId].mesh == &(data->meshes[primitiveIndex])) + { + 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++) + { + if(b % 4 == 0) + { + model->meshes[primitiveIndex].boneIds[b] = nodeId; + model->meshes[primitiveIndex].boneWeights[b] = 1.0f; + } + else + { + 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(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(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(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 @@ -4148,7 +4279,7 @@ static ModelAnimation *LoadGLTFModelAnimations(const char *fileName, int *animCo int frameCounts = (int)channel->sampler->input->count; float lastFrameTime = 0.0f; - if (GltfReadFloat(channel->sampler->input, frameCounts - 1, &lastFrameTime, 1)) + if (GLTFReadValue(channel->sampler->input, frameCounts - 1, &lastFrameTime, 1, sizeof(float))) { animationDuration = fmaxf(lastFrameTime, animationDuration); } @@ -4204,7 +4335,7 @@ static ModelAnimation *LoadGLTFModelAnimations(const char *fileName, int *animCo for (unsigned int j = 0; j < sampler->input->count; j++) { float inputFrameTime; - if (GltfReadFloat(sampler->input, j, (float *)&inputFrameTime, 1)) + if (GLTFReadValue(sampler->input, j, &inputFrameTime, 1, sizeof(float))) { if (frameTime < inputFrameTime) { @@ -4213,7 +4344,7 @@ static ModelAnimation *LoadGLTFModelAnimations(const char *fileName, int *animCo outputMax = j; float previousInputTime = 0.0f; - if (GltfReadFloat(sampler->input, outputMin, (float *)&previousInputTime, 1)) + if (GLTFReadValue(sampler->input, outputMin, &previousInputTime, 1, sizeof(float))) { if((inputFrameTime - previousInputTime) != 0) { @@ -4235,8 +4366,8 @@ static ModelAnimation *LoadGLTFModelAnimations(const char *fileName, int *animCo Vector3 translationStart; Vector3 translationEnd; - bool success = GltfReadFloat(sampler->output, outputMin, (float *)&translationStart, 3); - success = GltfReadFloat(sampler->output, outputMax, (float *)&translationEnd, 3) || success; + bool success = GLTFReadValue(sampler->output, outputMin, &translationStart, 3, sizeof(float)); + success = GLTFReadValue(sampler->output, outputMax, &translationEnd, 3, sizeof(float)) || success; if (success) output->framePoses[frame][boneId].translation = Vector3Lerp(translationStart, translationEnd, lerpPercent); } @@ -4245,8 +4376,8 @@ static ModelAnimation *LoadGLTFModelAnimations(const char *fileName, int *animCo Quaternion rotationStart; Quaternion rotationEnd; - bool success = GltfReadFloat(sampler->output, outputMin, (float *)&rotationStart, 4); - success = GltfReadFloat(sampler->output, outputMax, (float *)&rotationEnd, 4) || success; + bool success = GLTFReadValue(sampler->output, outputMin, &rotationStart, 4, sizeof(float)); + success = GLTFReadValue(sampler->output, outputMax, &rotationEnd, 4, sizeof(float)) || success; if (success) { @@ -4259,8 +4390,8 @@ static ModelAnimation *LoadGLTFModelAnimations(const char *fileName, int *animCo Vector3 scaleStart; Vector3 scaleEnd; - bool success = GltfReadFloat(sampler->output, outputMin, (float *)&scaleStart, 3); - success = GltfReadFloat(sampler->output, outputMax, (float *)&scaleEnd, 3) || success; + bool success = GLTFReadValue(sampler->output, outputMin, &scaleStart, 3, sizeof(float)); + success = GLTFReadValue(sampler->output, outputMax, &scaleEnd, 3, sizeof(float)) || success; if (success) output->framePoses[frame][boneId].scale = Vector3Lerp(scaleStart, scaleEnd, lerpPercent); }