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 c064e34f8..2e9e84563 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 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Û-{h>c+mR6STPvYE*40fi4Vu0eAR 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 == 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); }