diff options
Diffstat (limited to 'src/models.c')
| -rw-r--r-- | src/models.c | 1914 |
1 files changed, 1242 insertions, 672 deletions
diff --git a/src/models.c b/src/models.c index 8a498139..d8508492 100644 --- a/src/models.c +++ b/src/models.c @@ -17,7 +17,7 @@ * * LICENSE: zlib/libpng * -* Copyright (c) 2014-2018 Ramon Santamaria (@raysan5) +* Copyright (c) 2013-2019 Ramon Santamaria (@raysan5) * * This software is provided "as-is", without any express or implied warranty. In no event * will the authors be held liable for any damages arising from the use of this software. @@ -36,9 +36,13 @@ * **********************************************************************************************/ -#include "config.h" // Defines module configuration flags #include "raylib.h" // Declares module functions +// Check if config flags have been externally provided on compilation line +#if !defined(EXTERNAL_CONFIG_FLAGS) + #include "config.h" // Defines module configuration flags +#endif + #include "utils.h" // Required for: fopen() Android mapping #include <stdio.h> // Required for: FILE, fopen(), fclose(), fscanf(), feof(), rewind(), fgets() @@ -48,9 +52,9 @@ #include "rlgl.h" // raylib OpenGL abstraction layer to OpenGL 1.1, 2.1, 3.3+ or ES2 -#if defined(SUPPORT_FILEFORMAT_IQM) - #define RIQM_IMPLEMENTATION - #include "external/riqm.h" // IQM file format loading +#if defined(SUPPORT_FILEFORMAT_OBJ) || defined(SUPPORT_FILEFORMAT_MTL) + #define TINYOBJ_LOADER_C_IMPLEMENTATION + #include "external/tinyobj_loader_c.h" // OBJ/MTL file formats loading #endif #if defined(SUPPORT_FILEFORMAT_GLTF) @@ -82,16 +86,13 @@ // Module specific Functions Declaration //---------------------------------------------------------------------------------- #if defined(SUPPORT_FILEFORMAT_OBJ) -static Mesh LoadOBJ(const char *fileName); // Load OBJ mesh data -#endif -#if defined(SUPPORT_FILEFORMAT_MTL) -static Material LoadMTL(const char *fileName); // Load MTL material data +static Model LoadOBJ(const char *fileName); // Load OBJ mesh data #endif #if defined(SUPPORT_FILEFORMAT_GLTF) -static Mesh LoadIQM(const char *fileName); // Load IQM mesh data +static Model LoadIQM(const char *fileName); // Load IQM mesh data #endif #if defined(SUPPORT_FILEFORMAT_GLTF) -static Mesh LoadGLTF(const char *fileName); // Load GLTF mesh data +static Model LoadGLTF(const char *fileName); // Load GLTF mesh data #endif //---------------------------------------------------------------------------------- @@ -111,6 +112,8 @@ void DrawLine3D(Vector3 startPos, Vector3 endPos, Color color) // Draw a circle in 3D world space void DrawCircle3D(Vector3 center, float radius, Vector3 rotationAxis, float rotationAngle, Color color) { + if (rlCheckBufferLimit(2*36)) rlglDraw(); + rlPushMatrix(); rlTranslatef(center.x, center.y, center.z); rlRotatef(rotationAngle, rotationAxis.x, rotationAxis.y, rotationAxis.z); @@ -135,6 +138,8 @@ void DrawCube(Vector3 position, float width, float height, float length, Color c float y = 0.0f; float z = 0.0f; + if (rlCheckBufferLimit(36)) rlglDraw(); + rlPushMatrix(); // NOTE: Transformation is applied in inverse order (scale -> rotate -> translate) rlTranslatef(position.x, position.y, position.z); @@ -214,6 +219,8 @@ void DrawCubeWires(Vector3 position, float width, float height, float length, Co float y = 0.0f; float z = 0.0f; + if (rlCheckBufferLimit(36)) rlglDraw(); + rlPushMatrix(); rlTranslatef(position.x, position.y, position.z); @@ -275,6 +282,12 @@ void DrawCubeWires(Vector3 position, float width, float height, float length, Co rlPopMatrix(); } +// Draw cube wires (vector version) +void DrawCubeWiresV(Vector3 position, Vector3 size, Color color) +{ + DrawCubeWires(position, size.x, size.y, size.z, color); +} + // Draw cube // NOTE: Cube position is the center position void DrawCubeTexture(Texture2D texture, Vector3 position, float width, float height, float length, Color color) @@ -516,15 +529,12 @@ void DrawPlane(Vector3 centerPos, Vector2 size, Color color) rlTranslatef(centerPos.x, centerPos.y, centerPos.z); rlScalef(size.x, 1.0f, size.y); - rlBegin(RL_TRIANGLES); + rlBegin(RL_QUADS); rlColor4ub(color.r, color.g, color.b, color.a); rlNormal3f(0.0f, 1.0f, 0.0f); - rlVertex3f(0.5f, 0.0f, -0.5f); rlVertex3f(-0.5f, 0.0f, -0.5f); rlVertex3f(-0.5f, 0.0f, 0.5f); - - rlVertex3f(-0.5f, 0.0f, 0.5f); rlVertex3f(0.5f, 0.0f, 0.5f); rlVertex3f(0.5f, 0.0f, -0.5f); rlEnd(); @@ -605,9 +615,43 @@ Model LoadModel(const char *fileName) { Model model = { 0 }; - model.mesh = LoadMesh(fileName); +#if defined(SUPPORT_FILEFORMAT_OBJ) + if (IsFileExtension(fileName, ".obj")) model = LoadOBJ(fileName); +#endif +#if defined(SUPPORT_FILEFORMAT_GLTF) + if (IsFileExtension(fileName, ".gltf")) model = LoadGLTF(fileName); +#endif +#if defined(SUPPORT_FILEFORMAT_IQM) + if (IsFileExtension(fileName, ".iqm")) model = LoadIQM(fileName); +#endif + + // Make sure model transform is set to identity matrix! model.transform = MatrixIdentity(); - model.material = LoadMaterialDefault(); + + if (model.meshCount == 0) + { + TraceLog(LOG_WARNING, "[%s] No meshes can be loaded, default to cube mesh", fileName); + + model.meshCount = 1; + model.meshes = (Mesh *)calloc(model.meshCount, sizeof(Mesh)); + model.meshes[0] = GenMeshCube(1.0f, 1.0f, 1.0f); + } + else + { + // Upload vertex data to GPU (static mesh) + for (int i = 0; i < model.meshCount; i++) rlLoadMesh(&model.meshes[i], false); + } + + if (model.materialCount == 0) + { + TraceLog(LOG_WARNING, "[%s] No materials can be loaded, default to white material", fileName); + + model.materialCount = 1; + model.materials = (Material *)calloc(model.materialCount, sizeof(Material)); + model.materials[0] = LoadMaterialDefault(); + + model.meshMaterial = (int *)calloc(model.meshCount, sizeof(int)); + } return model; } @@ -619,10 +663,19 @@ Model LoadModel(const char *fileName) Model LoadModelFromMesh(Mesh mesh) { Model model = { 0 }; - - model.mesh = mesh; + model.transform = MatrixIdentity(); - model.material = LoadMaterialDefault(); + + model.meshCount = 1; + model.meshes = (Mesh *)malloc(model.meshCount*sizeof(Mesh)); + model.meshes[0] = mesh; + + model.materialCount = 1; + model.materials = (Material *)malloc(model.materialCount*sizeof(Material)); + model.materials[0] = LoadMaterialDefault(); + + model.meshMaterial = (int *)malloc(model.meshCount*sizeof(int)); + model.meshMaterial[0] = 0; // First material index return model; } @@ -630,32 +683,30 @@ Model LoadModelFromMesh(Mesh mesh) // Unload model from memory (RAM and/or VRAM) void UnloadModel(Model model) { - UnloadMesh(&model.mesh); - UnloadMaterial(model.material); - - TraceLog(LOG_INFO, "Unloaded model data (mesh and material) from RAM and VRAM"); -} + for (int i = 0; i < model.meshCount; i++) UnloadMesh(&model.meshes[i]); + for (int i = 0; i < model.materialCount; i++) UnloadMaterial(model.materials[i]); -// Load mesh from file -// NOTE: Mesh data loaded in CPU and GPU -Mesh LoadMesh(const char *fileName) -{ - Mesh mesh = { 0 }; + free(model.meshes); + free(model.materials); + free(model.meshMaterial); -#if defined(SUPPORT_FILEFORMAT_OBJ) - if (IsFileExtension(fileName, ".obj")) mesh = LoadOBJ(fileName); -#else - TraceLog(LOG_WARNING, "[%s] Mesh fileformat not supported, it can't be loaded", fileName); -#endif + // Unload animation data + free(model.bones); + free(model.bindPose); - if (mesh.vertexCount == 0) - { - TraceLog(LOG_WARNING, "Mesh could not be loaded! Let's load a cube to replace it!"); - mesh = GenMeshCube(1.0f, 1.0f, 1.0f); - } - else rlLoadMesh(&mesh, false); // Upload vertex data to GPU (static mesh) + TraceLog(LOG_INFO, "Unloaded model data from RAM and VRAM"); +} - return mesh; +// Load meshes from model file +Mesh *LoadMeshes(const char *fileName, int *meshCount) +{ + Mesh *meshes = NULL; + int count = 0; + + // TODO: Load meshes from file (OBJ, IQM, GLTF) + + *meshCount = count; + return meshes; } // Unload mesh from memory (RAM and/or VRAM) @@ -668,44 +719,50 @@ void UnloadMesh(Mesh *mesh) void ExportMesh(Mesh mesh, const char *fileName) { bool success = false; - + if (IsFileExtension(fileName, ".obj")) { FILE *objFile = fopen(fileName, "wt"); - - fprintf(objFile, "# raylib Mesh OBJ exporter v1.0\n\n"); - fprintf(objFile, "# Mesh exported as triangle faces and not optimized.\n"); - fprintf(objFile, "# Vertex Count: %i\n", mesh.vertexCount); - fprintf(objFile, "# Triangle Count: %i\n\n", mesh.triangleCount); - fprintf(objFile, "# LICENSE: zlib/libpng\n"); - fprintf(objFile, "# Copyright (c) 2018 Ramon Santamaria (@raysan5)\n\n"); - + + fprintf(objFile, "# //////////////////////////////////////////////////////////////////////////////////\n"); + fprintf(objFile, "# // //\n"); + fprintf(objFile, "# // rMeshOBJ exporter v1.0 - Mesh exported as triangle faces and not optimized //\n"); + fprintf(objFile, "# // //\n"); + fprintf(objFile, "# // more info and bugs-report: github.com/raysan5/raylib //\n"); + fprintf(objFile, "# // feedback and support: ray[at]raylib.com //\n"); + fprintf(objFile, "# // //\n"); + fprintf(objFile, "# // Copyright (c) 2018 Ramon Santamaria (@raysan5) //\n"); + fprintf(objFile, "# // //\n"); + fprintf(objFile, "# //////////////////////////////////////////////////////////////////////////////////\n\n"); + fprintf(objFile, "# Vertex Count: %i\n", mesh.vertexCount); + fprintf(objFile, "# Triangle Count: %i\n\n", mesh.triangleCount); + fprintf(objFile, "g mesh\n"); - + for (int i = 0, v = 0; i < mesh.vertexCount; i++, v += 3) { fprintf(objFile, "v %.2f %.2f %.2f\n", mesh.vertices[v], mesh.vertices[v + 1], mesh.vertices[v + 2]); } - + for (int i = 0, v = 0; i < mesh.vertexCount; i++, v += 2) { fprintf(objFile, "vt %.2f %.2f\n", mesh.texcoords[v], mesh.texcoords[v + 1]); } - + for (int i = 0, v = 0; i < mesh.vertexCount; i++, v += 3) { fprintf(objFile, "vn %.2f %.2f %.2f\n", mesh.normals[v], mesh.normals[v + 1], mesh.normals[v + 2]); } - + for (int i = 0; i < mesh.triangleCount; i += 3) { fprintf(objFile, "f %i/%i/%i %i/%i/%i %i/%i/%i\n", i, i, i, i + 1, i + 1, i + 1, i + 2, i + 2, i + 2); } - + fprintf(objFile, "\n"); - + fclose(objFile); - + success = true; } else if (IsFileExtension(fileName, ".raw")) { } // TODO: Support additional file formats to export mesh vertex data @@ -714,7 +771,449 @@ void ExportMesh(Mesh mesh, const char *fileName) else TraceLog(LOG_WARNING, "Mesh could not be exported."); } +// Load materials from model file +Material *LoadMaterials(const char *fileName, int *materialCount) +{ + Material *materials = NULL; + unsigned int count = 0; + + // TODO: Support IQM and GLTF for materials parsing + +#if defined(SUPPORT_FILEFORMAT_MTL) + if (IsFileExtension(fileName, ".mtl")) + { + tinyobj_material_t *mats; + + int result = tinyobj_parse_mtl_file(&mats, &count, fileName); + + // TODO: Process materials to return + + tinyobj_materials_free(mats, count); + } +#else + TraceLog(LOG_WARNING, "[%s] Materials file not supported", fileName); +#endif + + // Set materials shader to default (DIFFUSE, SPECULAR, NORMAL) + for (int i = 0; i < count; i++) materials[i].shader = GetShaderDefault(); + + *materialCount = count; + return materials; +} + +// Load default material (Supports: DIFFUSE, SPECULAR, NORMAL maps) +Material LoadMaterialDefault(void) +{ + Material material = { 0 }; + + material.shader = GetShaderDefault(); + material.maps[MAP_DIFFUSE].texture = GetTextureDefault(); // White texture (1x1 pixel) + //material.maps[MAP_NORMAL].texture; // NOTE: By default, not set + //material.maps[MAP_SPECULAR].texture; // NOTE: By default, not set + + material.maps[MAP_DIFFUSE].color = WHITE; // Diffuse color + material.maps[MAP_SPECULAR].color = WHITE; // Specular color + + return material; +} + +// Unload material from memory +void UnloadMaterial(Material material) +{ + // Unload material shader (avoid unloading default shader, managed by raylib) + if (material.shader.id != GetShaderDefault().id) UnloadShader(material.shader); + + // Unload loaded texture maps (avoid unloading default texture, managed by raylib) + for (int i = 0; i < MAX_MATERIAL_MAPS; i++) + { + if (material.maps[i].texture.id != GetTextureDefault().id) rlDeleteTextures(material.maps[i].texture.id); + } +} + +// Set texture for a material map type (MAP_DIFFUSE, MAP_SPECULAR...) +// NOTE: Previous texture should be manually unloaded +void SetMaterialTexture(Material *material, int mapType, Texture2D texture) +{ + material->maps[mapType].texture = texture; +} + +// Set the material for a mesh +void SetModelMeshMaterial(Model *model, int meshId, int materialId) +{ + if (meshId >= model->meshCount) TraceLog(LOG_WARNING, "Mesh id greater than mesh count"); + else if (materialId >= model->materialCount) TraceLog(LOG_WARNING,"Material id greater than material count"); + else model->meshMaterial[meshId] = materialId; +} + +// Load model animations from file +ModelAnimation *LoadModelAnimations(const char *filename, int *animCount) +{ + ModelAnimation *animations = (ModelAnimation *)malloc(1*sizeof(ModelAnimation)); + int count = 1; + + #define IQM_MAGIC "INTERQUAKEMODEL" // IQM file magic number + #define IQM_VERSION 2 // only IQM version 2 supported + + typedef struct IQMHeader { + char magic[16]; + unsigned int version; + unsigned int filesize; + unsigned int flags; + unsigned int num_text, ofs_text; + unsigned int num_meshes, ofs_meshes; + unsigned int num_vertexarrays, num_vertexes, ofs_vertexarrays; + unsigned int num_triangles, ofs_triangles, ofs_adjacency; + unsigned int num_joints, ofs_joints; + unsigned int num_poses, ofs_poses; + unsigned int num_anims, ofs_anims; + unsigned int num_frames, num_framechannels, ofs_frames, ofs_bounds; + unsigned int num_comment, ofs_comment; + unsigned int num_extensions, ofs_extensions; + } IQMHeader; + + typedef struct IQMPose { + int parent; + unsigned int mask; + float channeloffset[10]; + float channelscale[10]; + } IQMPose; + + typedef struct IQMAnim { + unsigned int name; + unsigned int first_frame, num_frames; + float framerate; + unsigned int flags; + } IQMAnim; + + ModelAnimation animation = { 0 }; + + FILE *iqmFile; + IQMHeader iqm; + + iqmFile = fopen(filename,"rb"); + + if (!iqmFile) + { + TraceLog(LOG_ERROR, "[%s] Unable to open file", filename); + } + + // header + fread(&iqm, sizeof(IQMHeader), 1, iqmFile); + + if (strncmp(iqm.magic, IQM_MAGIC, sizeof(IQM_MAGIC))) + { + TraceLog(LOG_ERROR, "Magic Number \"%s\"does not match.", iqm.magic); + fclose(iqmFile); + } + + if (iqm.version != IQM_VERSION) + { + TraceLog(LOG_ERROR, "IQM version %i is incorrect.", iqm.version); + fclose(iqmFile); + } + + // header + if (iqm.num_anims > 1) TraceLog(LOG_WARNING, "More than 1 animation in file, only the first one will be loaded"); + + // bones + IQMPose *poses; + poses = malloc(sizeof(IQMPose)*iqm.num_poses); + fseek(iqmFile, iqm.ofs_poses, SEEK_SET); + fread(poses, sizeof(IQMPose)*iqm.num_poses, 1, iqmFile); + + animation.boneCount = iqm.num_poses; + animation.bones = malloc(sizeof(BoneInfo)*iqm.num_poses); + + for (int j = 0; j < iqm.num_poses; j++) + { + strcpy(animation.bones[j].name, "ANIMJOINTNAME"); + animation.bones[j].parent = poses[j].parent; + } + + // animations + IQMAnim anim = {0}; + fseek(iqmFile, iqm.ofs_anims, SEEK_SET); + fread(&anim, sizeof(IQMAnim), 1, iqmFile); + + animation.frameCount = anim.num_frames; + //animation.framerate = anim.framerate; + + // frameposes + unsigned short *framedata = malloc(sizeof(unsigned short)*iqm.num_frames*iqm.num_framechannels); + fseek(iqmFile, iqm.ofs_frames, SEEK_SET); + fread(framedata, sizeof(unsigned short)*iqm.num_frames*iqm.num_framechannels, 1, iqmFile); + + animation.framePoses = malloc(sizeof(Transform*)*anim.num_frames); + for (int j = 0; j < anim.num_frames; j++) animation.framePoses[j] = malloc(sizeof(Transform)*iqm.num_poses); + + int dcounter = anim.first_frame*iqm.num_framechannels; + + for (int frame = 0; frame < anim.num_frames; frame++) + { + for (int i = 0; i < iqm.num_poses; i++) + { + animation.framePoses[frame][i].translation.x = poses[i].channeloffset[0]; + + if (poses[i].mask & 0x01) + { + animation.framePoses[frame][i].translation.x += framedata[dcounter]*poses[i].channelscale[0]; + dcounter++; + } + + animation.framePoses[frame][i].translation.y = poses[i].channeloffset[1]; + + if (poses[i].mask & 0x02) + { + animation.framePoses[frame][i].translation.y += framedata[dcounter]*poses[i].channelscale[1]; + dcounter++; + } + + animation.framePoses[frame][i].translation.z = poses[i].channeloffset[2]; + + if (poses[i].mask & 0x04) + { + animation.framePoses[frame][i].translation.z += framedata[dcounter]*poses[i].channelscale[2]; + dcounter++; + } + + animation.framePoses[frame][i].rotation.x = poses[i].channeloffset[3]; + + if (poses[i].mask & 0x08) + { + animation.framePoses[frame][i].rotation.x += framedata[dcounter]*poses[i].channelscale[3]; + dcounter++; + } + + animation.framePoses[frame][i].rotation.y = poses[i].channeloffset[4]; + + if (poses[i].mask & 0x10) + { + animation.framePoses[frame][i].rotation.y += framedata[dcounter]*poses[i].channelscale[4]; + dcounter++; + } + + animation.framePoses[frame][i].rotation.z = poses[i].channeloffset[5]; + + if (poses[i].mask & 0x20) + { + animation.framePoses[frame][i].rotation.z += framedata[dcounter]*poses[i].channelscale[5]; + dcounter++; + } + + animation.framePoses[frame][i].rotation.w = poses[i].channeloffset[6]; + + if (poses[i].mask & 0x40) + { + animation.framePoses[frame][i].rotation.w += framedata[dcounter]*poses[i].channelscale[6]; + dcounter++; + } + + animation.framePoses[frame][i].scale.x = poses[i].channeloffset[7]; + + if (poses[i].mask & 0x80) + { + animation.framePoses[frame][i].scale.x += framedata[dcounter]*poses[i].channelscale[7]; + dcounter++; + } + + animation.framePoses[frame][i].scale.y = poses[i].channeloffset[8]; + + if (poses[i].mask & 0x100) + { + animation.framePoses[frame][i].scale.y += framedata[dcounter]*poses[i].channelscale[8]; + dcounter++; + } + + animation.framePoses[frame][i].scale.z = poses[i].channeloffset[9]; + + if (poses[i].mask & 0x200) + { + animation.framePoses[frame][i].scale.z += framedata[dcounter]*poses[i].channelscale[9]; + dcounter++; + } + + animation.framePoses[frame][i].rotation = QuaternionNormalize(animation.framePoses[frame][i].rotation); + } + } + + // Build frameposes + for (int frame = 0; frame < anim.num_frames; frame++) + { + for (int i = 0; i < animation.boneCount; i++) + { + if (animation.bones[i].parent >= 0) + { + animation.framePoses[frame][i].rotation = QuaternionMultiply(animation.framePoses[frame][animation.bones[i].parent].rotation, animation.framePoses[frame][i].rotation); + animation.framePoses[frame][i].translation = Vector3RotateByQuaternion(animation.framePoses[frame][i].translation, animation.framePoses[frame][animation.bones[i].parent].rotation); + animation.framePoses[frame][i].translation = Vector3Add(animation.framePoses[frame][i].translation, animation.framePoses[frame][animation.bones[i].parent].translation); + animation.framePoses[frame][i].scale = Vector3MultiplyV(animation.framePoses[frame][i].scale, animation.framePoses[frame][animation.bones[i].parent].scale); + } + } + } + + free(framedata); + free(poses); + + fclose(iqmFile); + + animations[0] = animation; + + *animCount = count; + return animations; +} + +// Update model animated vertex data (positions and normals) for a given frame +// NOTE: Updated data is uploaded to GPU +void UpdateModelAnimation(Model model, ModelAnimation anim, int frame) +{ + if (frame >= anim.frameCount) frame = frame%anim.frameCount; + + for (int m = 0; m < model.meshCount; m++) + { + Vector3 animVertex = { 0 }; + Vector3 animNormal = { 0 }; + + Vector3 inTranslation = { 0 }; + Quaternion inRotation = { 0 }; + Vector3 inScale = { 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[m].vertexCount; i++) + { + boneId = model.meshes[m].boneIds[boneCounter]; + inTranslation = model.bindPose[boneId].translation; + inRotation = model.bindPose[boneId].rotation; + inScale = model.bindPose[boneId].scale; + outTranslation = anim.framePoses[frame][boneId].translation; + outRotation = anim.framePoses[frame][boneId].rotation; + outScale = anim.framePoses[frame][boneId].scale; + + // Vertices processing + // NOTE: We use meshes.vertices (default vertex position) to calculate meshes.animVertices (animated vertex position) + animVertex = (Vector3){ model.meshes[m].vertices[vCounter], model.meshes[m].vertices[vCounter + 1], model.meshes[m].vertices[vCounter + 2] }; + animVertex = Vector3MultiplyV(animVertex, outScale); + animVertex = Vector3Subtract(animVertex, inTranslation); + animVertex = Vector3RotateByQuaternion(animVertex, QuaternionMultiply(outRotation, QuaternionInvert(inRotation))); + animVertex = Vector3Add(animVertex, outTranslation); + model.meshes[m].animVertices[vCounter] = animVertex.x; + model.meshes[m].animVertices[vCounter + 1] = animVertex.y; + model.meshes[m].animVertices[vCounter + 2] = animVertex.z; + + // 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; + vCounter += 3; + + boneCounter += 4; + } + + // Upload new vertex data to GPU for model drawing + rlUpdateBuffer(model.meshes[m].vboId[0], model.meshes[m].animVertices, model.meshes[m].vertexCount*3*sizeof(float)); // Update vertex position + rlUpdateBuffer(model.meshes[m].vboId[2], model.meshes[m].animVertices, model.meshes[m].vertexCount*3*sizeof(float)); // Update vertex normals + } +} + +// Unload animation data +void UnloadModelAnimation(ModelAnimation anim) +{ + for (int i = 0; i < anim.frameCount; i++) free(anim.framePoses[i]); + + free(anim.bones); + free(anim.framePoses); +} + +// Check model animation skeleton match +// NOTE: Only number of bones and parent connections are checked +bool IsModelAnimationValid(Model model, ModelAnimation anim) +{ + int result = true; + + if (model.boneCount != anim.boneCount) result = false; + else + { + for (int i = 0; i < model.boneCount; i++) + { + if (model.bones[i].parent != anim.bones[i].parent) { result = false; break; } + } + } + + return result; +} + #if defined(SUPPORT_MESH_GENERATION) +// Generate polygonal mesh +Mesh GenMeshPoly(int sides, float radius) +{ + Mesh mesh = { 0 }; + int vertexCount = sides*3; + + // Vertices definition + Vector3 *vertices = (Vector3 *)malloc(vertexCount*sizeof(Vector3)); + for (int i = 0, v = 0; i < 360; i += 360/sides, v += 3) + { + vertices[v] = (Vector3){ 0.0f, 0.0f, 0.0f }; + vertices[v + 1] = (Vector3){ sinf(DEG2RAD*i)*radius, 0.0f, cosf(DEG2RAD*i)*radius }; + vertices[v + 2] = (Vector3){ sinf(DEG2RAD*(i + 360/sides))*radius, 0.0f, cosf(DEG2RAD*(i + 360/sides))*radius }; + } + + // Normals definition + Vector3 *normals = (Vector3 *)malloc(vertexCount*sizeof(Vector3)); + for (int n = 0; n < vertexCount; n++) normals[n] = (Vector3){ 0.0f, 1.0f, 0.0f }; // Vector3.up; + + // TexCoords definition + Vector2 *texcoords = (Vector2 *)malloc(vertexCount*sizeof(Vector2)); + for (int n = 0; n < vertexCount; n++) texcoords[n] = (Vector2){ 0.0f, 0.0f }; + + mesh.vertexCount = vertexCount; + mesh.triangleCount = sides; + mesh.vertices = (float *)malloc(mesh.vertexCount*3*sizeof(float)); + mesh.texcoords = (float *)malloc(mesh.vertexCount*2*sizeof(float)); + mesh.normals = (float *)malloc(mesh.vertexCount*3*sizeof(float)); + + // Mesh vertices position array + for (int i = 0; i < mesh.vertexCount; i++) + { + mesh.vertices[3*i] = vertices[i].x; + mesh.vertices[3*i + 1] = vertices[i].y; + mesh.vertices[3*i + 2] = vertices[i].z; + } + + // Mesh texcoords array + for (int i = 0; i < mesh.vertexCount; i++) + { + mesh.texcoords[2*i] = texcoords[i].x; + mesh.texcoords[2*i + 1] = texcoords[i].y; + } + + // Mesh normals array + for (int i = 0; i < mesh.vertexCount; i++) + { + mesh.normals[3*i] = normals[i].x; + mesh.normals[3*i + 1] = normals[i].y; + mesh.normals[3*i + 2] = normals[i].z; + } + + free(vertices); + free(normals); + free(texcoords); + + // Upload vertex data to GPU (static mesh) + rlLoadMesh(&mesh, false); + + return mesh; +} + // Generate plane mesh (with subdivisions) Mesh GenMeshPlane(float width, float length, int resX, int resZ) { @@ -724,7 +1223,7 @@ Mesh GenMeshPlane(float width, float length, int resX, int resZ) #if defined(CUSTOM_MESH_GEN_PLANE) resX++; resZ++; - + // Vertices definition int vertexCount = resX*resZ; // vertices get reused for the faces @@ -745,7 +1244,7 @@ Mesh GenMeshPlane(float width, float length, int resX, int resZ) Vector3 *normals = (Vector3 *)malloc(vertexCount*sizeof(Vector3)); for (int n = 0; n < vertexCount; n++) normals[n] = (Vector3){ 0.0f, 1.0f, 0.0f }; // Vector3.up; - // TexCoords definition + // TexCoords definition Vector2 *texcoords = (Vector2 *)malloc(vertexCount*sizeof(Vector2)); for (int v = 0; v < resZ; v++) { @@ -768,7 +1267,7 @@ Mesh GenMeshPlane(float width, float length, int resX, int resZ) triangles[t++] = i + 1; triangles[t++] = i; - triangles[t++] = i + resX; + triangles[t++] = i + resX; triangles[t++] = i + resX + 1; triangles[t++] = i + 1; } @@ -779,7 +1278,7 @@ Mesh GenMeshPlane(float width, float length, int resX, int resZ) mesh.texcoords = (float *)malloc(mesh.vertexCount*2*sizeof(float)); mesh.normals = (float *)malloc(mesh.vertexCount*3*sizeof(float)); mesh.indices = (unsigned short *)malloc(mesh.triangleCount*3*sizeof(unsigned short)); - + // Mesh vertices position array for (int i = 0; i < mesh.vertexCount; i++) { @@ -787,14 +1286,14 @@ Mesh GenMeshPlane(float width, float length, int resX, int resZ) mesh.vertices[3*i + 1] = vertices[i].y; mesh.vertices[3*i + 2] = vertices[i].z; } - + // Mesh texcoords array for (int i = 0; i < mesh.vertexCount; i++) { mesh.texcoords[2*i] = texcoords[i].x; mesh.texcoords[2*i + 1] = texcoords[i].y; } - + // Mesh normals array for (int i = 0; i < mesh.vertexCount; i++) { @@ -802,22 +1301,22 @@ Mesh GenMeshPlane(float width, float length, int resX, int resZ) mesh.normals[3*i + 1] = normals[i].y; mesh.normals[3*i + 2] = normals[i].z; } - + // Mesh indices array initialization for (int i = 0; i < mesh.triangleCount*3; i++) mesh.indices[i] = triangles[i]; - + free(vertices); free(normals); free(texcoords); free(triangles); - + #else // Use par_shapes library to generate plane mesh par_shapes_mesh *plane = par_shapes_create_plane(resX, resZ); // No normals/texcoords generated!!! par_shapes_scale(plane, width, length, 1.0f); par_shapes_rotate(plane, -PI/2.0f, (float[]){ 1, 0, 0 }); par_shapes_translate(plane, -width/2, 0.0f, length/2); - + mesh.vertices = (float *)malloc(plane->ntriangles*3*3*sizeof(float)); mesh.texcoords = (float *)malloc(plane->ntriangles*3*2*sizeof(float)); mesh.normals = (float *)malloc(plane->ntriangles*3*3*sizeof(float)); @@ -830,11 +1329,11 @@ Mesh GenMeshPlane(float width, float length, int resX, int resZ) mesh.vertices[k*3] = plane->points[plane->triangles[k]*3]; mesh.vertices[k*3 + 1] = plane->points[plane->triangles[k]*3 + 1]; mesh.vertices[k*3 + 2] = plane->points[plane->triangles[k]*3 + 2]; - + mesh.normals[k*3] = plane->normals[plane->triangles[k]*3]; mesh.normals[k*3 + 1] = plane->normals[plane->triangles[k]*3 + 1]; mesh.normals[k*3 + 2] = plane->normals[plane->triangles[k]*3 + 2]; - + mesh.texcoords[k*2] = plane->tcoords[plane->triangles[k]*2]; mesh.texcoords[k*2 + 1] = plane->tcoords[plane->triangles[k]*2 + 1]; } @@ -843,7 +1342,7 @@ Mesh GenMeshPlane(float width, float length, int resX, int resZ) #endif // Upload vertex data to GPU (static mesh) - rlLoadMesh(&mesh, false); + rlLoadMesh(&mesh, false); return mesh; } @@ -881,7 +1380,7 @@ Mesh GenMeshCube(float width, float height, float length) -width/2, height/2, length/2, -width/2, height/2, -length/2 }; - + float texcoords[] = { 0.0f, 0.0f, 1.0f, 0.0f, @@ -908,7 +1407,7 @@ Mesh GenMeshCube(float width, float height, float length) 1.0f, 1.0f, 0.0f, 1.0f }; - + float normals[] = { 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, @@ -938,15 +1437,15 @@ Mesh GenMeshCube(float width, float height, float length) mesh.vertices = (float *)malloc(24*3*sizeof(float)); memcpy(mesh.vertices, vertices, 24*3*sizeof(float)); - + mesh.texcoords = (float *)malloc(24*2*sizeof(float)); memcpy(mesh.texcoords, texcoords, 24*2*sizeof(float)); - + mesh.normals = (float *)malloc(24*3*sizeof(float)); memcpy(mesh.normals, normals, 24*3*sizeof(float)); - + mesh.indices = (unsigned short *)malloc(36*sizeof(unsigned short)); - + int k = 0; // Indices can be initialized right now @@ -961,10 +1460,10 @@ Mesh GenMeshCube(float width, float height, float length) k++; } - + mesh.vertexCount = 24; mesh.triangleCount = 12; - + #else // Use par_shapes library to generate cube mesh /* // Platonic solids: @@ -978,11 +1477,11 @@ par_shapes_mesh* par_shapes_create_icosahedron(); // 20 sides polyhedron // NOTE: No normals/texcoords generated by default par_shapes_mesh *cube = par_shapes_create_cube(); cube->tcoords = PAR_MALLOC(float, 2*cube->npoints); - for (int i = 0; i < 2*cube->npoints; i++) cube->tcoords[i] = 0.0f; + for (int i = 0; i < 2*cube->npoints; i++) cube->tcoords[i] = 0.0f; par_shapes_scale(cube, width, height, length); par_shapes_translate(cube, -width/2, 0.0f, -length/2); par_shapes_compute_normals(cube); - + mesh.vertices = (float *)malloc(cube->ntriangles*3*3*sizeof(float)); mesh.texcoords = (float *)malloc(cube->ntriangles*3*2*sizeof(float)); mesh.normals = (float *)malloc(cube->ntriangles*3*3*sizeof(float)); @@ -995,11 +1494,11 @@ par_shapes_mesh* par_shapes_create_icosahedron(); // 20 sides polyhedron mesh.vertices[k*3] = cube->points[cube->triangles[k]*3]; mesh.vertices[k*3 + 1] = cube->points[cube->triangles[k]*3 + 1]; mesh.vertices[k*3 + 2] = cube->points[cube->triangles[k]*3 + 2]; - + mesh.normals[k*3] = cube->normals[cube->triangles[k]*3]; mesh.normals[k*3 + 1] = cube->normals[cube->triangles[k]*3 + 1]; mesh.normals[k*3 + 2] = cube->normals[cube->triangles[k]*3 + 2]; - + mesh.texcoords[k*2] = cube->tcoords[cube->triangles[k]*2]; mesh.texcoords[k*2 + 1] = cube->tcoords[cube->triangles[k]*2 + 1]; } @@ -1008,7 +1507,7 @@ par_shapes_mesh* par_shapes_create_icosahedron(); // 20 sides polyhedron #endif // Upload vertex data to GPU (static mesh) - rlLoadMesh(&mesh, false); + rlLoadMesh(&mesh, false); return mesh; } @@ -1020,8 +1519,8 @@ RLAPI Mesh GenMeshSphere(float radius, int rings, int slices) par_shapes_mesh *sphere = par_shapes_create_parametric_sphere(slices, rings); par_shapes_scale(sphere, radius, radius, radius); - // NOTE: Soft normals are computed internally - + // NOTE: Soft normals are computed internally + mesh.vertices = (float *)malloc(sphere->ntriangles*3*3*sizeof(float)); mesh.texcoords = (float *)malloc(sphere->ntriangles*3*2*sizeof(float)); mesh.normals = (float *)malloc(sphere->ntriangles*3*3*sizeof(float)); @@ -1034,19 +1533,19 @@ RLAPI Mesh GenMeshSphere(float radius, int rings, int slices) mesh.vertices[k*3] = sphere->points[sphere->triangles[k]*3]; mesh.vertices[k*3 + 1] = sphere->points[sphere->triangles[k]*3 + 1]; mesh.vertices[k*3 + 2] = sphere->points[sphere->triangles[k]*3 + 2]; - + mesh.normals[k*3] = sphere->normals[sphere->triangles[k]*3]; mesh.normals[k*3 + 1] = sphere->normals[sphere->triangles[k]*3 + 1]; mesh.normals[k*3 + 2] = sphere->normals[sphere->triangles[k]*3 + 2]; - + mesh.texcoords[k*2] = sphere->tcoords[sphere->triangles[k]*2]; mesh.texcoords[k*2 + 1] = sphere->tcoords[sphere->triangles[k]*2 + 1]; } par_shapes_free_mesh(sphere); - + // Upload vertex data to GPU (static mesh) - rlLoadMesh(&mesh, false); + rlLoadMesh(&mesh, false); return mesh; } @@ -1058,8 +1557,8 @@ RLAPI Mesh GenMeshHemiSphere(float radius, int rings, int slices) par_shapes_mesh *sphere = par_shapes_create_hemisphere(slices, rings); par_shapes_scale(sphere, radius, radius, radius); - // NOTE: Soft normals are computed internally - + // NOTE: Soft normals are computed internally + mesh.vertices = (float *)malloc(sphere->ntriangles*3*3*sizeof(float)); mesh.texcoords = (float *)malloc(sphere->ntriangles*3*2*sizeof(float)); mesh.normals = (float *)malloc(sphere->ntriangles*3*3*sizeof(float)); @@ -1072,19 +1571,19 @@ RLAPI Mesh GenMeshHemiSphere(float radius, int rings, int slices) mesh.vertices[k*3] = sphere->points[sphere->triangles[k]*3]; mesh.vertices[k*3 + 1] = sphere->points[sphere->triangles[k]*3 + 1]; mesh.vertices[k*3 + 2] = sphere->points[sphere->triangles[k]*3 + 2]; - + mesh.normals[k*3] = sphere->normals[sphere->triangles[k]*3]; mesh.normals[k*3 + 1] = sphere->normals[sphere->triangles[k]*3 + 1]; mesh.normals[k*3 + 2] = sphere->normals[sphere->triangles[k]*3 + 2]; - + mesh.texcoords[k*2] = sphere->tcoords[sphere->triangles[k]*2]; mesh.texcoords[k*2 + 1] = sphere->tcoords[sphere->triangles[k]*2 + 1]; } par_shapes_free_mesh(sphere); - + // Upload vertex data to GPU (static mesh) - rlLoadMesh(&mesh, false); + rlLoadMesh(&mesh, false); return mesh; } @@ -1096,7 +1595,7 @@ Mesh GenMeshCylinder(float radius, float height, int slices) // Instance a cylinder that sits on the Z=0 plane using the given tessellation // levels across the UV domain. Think of "slices" like a number of pizza - // slices, and "stacks" like a number of stacked rings. + // slices, and "stacks" like a number of stacked rings. // Height and radius are both 1.0, but they can easily be changed with par_shapes_scale par_shapes_mesh *cylinder = par_shapes_create_cylinder(slices, 8); par_shapes_scale(cylinder, radius, radius, height); @@ -1108,16 +1607,16 @@ Mesh GenMeshCylinder(float radius, float height, int slices) for (int i = 0; i < 2*capTop->npoints; i++) capTop->tcoords[i] = 0.0f; par_shapes_rotate(capTop, -PI/2.0f, (float[]){ 1, 0, 0 }); par_shapes_translate(capTop, 0, height, 0); - + // Generate an orientable disk shape (bottom cap) par_shapes_mesh *capBottom = par_shapes_create_disk(radius, slices, (float[]){ 0, 0, 0 }, (float[]){ 0, 0, -1 }); capBottom->tcoords = PAR_MALLOC(float, 2*capBottom->npoints); for (int i = 0; i < 2*capBottom->npoints; i++) capBottom->tcoords[i] = 0.95f; par_shapes_rotate(capBottom, PI/2.0f, (float[]){ 1, 0, 0 }); - + par_shapes_merge_and_free(cylinder, capTop); par_shapes_merge_and_free(cylinder, capBottom); - + mesh.vertices = (float *)malloc(cylinder->ntriangles*3*3*sizeof(float)); mesh.texcoords = (float *)malloc(cylinder->ntriangles*3*2*sizeof(float)); mesh.normals = (float *)malloc(cylinder->ntriangles*3*3*sizeof(float)); @@ -1130,19 +1629,19 @@ Mesh GenMeshCylinder(float radius, float height, int slices) mesh.vertices[k*3] = cylinder->points[cylinder->triangles[k]*3]; mesh.vertices[k*3 + 1] = cylinder->points[cylinder->triangles[k]*3 + 1]; mesh.vertices[k*3 + 2] = cylinder->points[cylinder->triangles[k]*3 + 2]; - + mesh.normals[k*3] = cylinder->normals[cylinder->triangles[k]*3]; mesh.normals[k*3 + 1] = cylinder->normals[cylinder->triangles[k]*3 + 1]; mesh.normals[k*3 + 2] = cylinder->normals[cylinder->triangles[k]*3 + 2]; - + mesh.texcoords[k*2] = cylinder->tcoords[cylinder->triangles[k]*2]; mesh.texcoords[k*2 + 1] = cylinder->tcoords[cylinder->triangles[k]*2 + 1]; } par_shapes_free_mesh(cylinder); - + // Upload vertex data to GPU (static mesh) - rlLoadMesh(&mesh, false); + rlLoadMesh(&mesh, false); return mesh; } @@ -1154,7 +1653,7 @@ Mesh GenMeshTorus(float radius, float size, int radSeg, int sides) if (radius > 1.0f) radius = 1.0f; else if (radius < 0.1f) radius = 0.1f; - + // Create a donut that sits on the Z=0 plane with the specified inner radius // The outer radius can be controlled with par_shapes_scale par_shapes_mesh *torus = par_shapes_create_torus(radSeg, sides, radius); @@ -1172,19 +1671,19 @@ Mesh GenMeshTorus(float radius, float size, int radSeg, int sides) mesh.vertices[k*3] = torus->points[torus->triangles[k]*3]; mesh.vertices[k*3 + 1] = torus->points[torus->triangles[k]*3 + 1]; mesh.vertices[k*3 + 2] = torus->points[torus->triangles[k]*3 + 2]; - + mesh.normals[k*3] = torus->normals[torus->triangles[k]*3]; mesh.normals[k*3 + 1] = torus->normals[torus->triangles[k]*3 + 1]; mesh.normals[k*3 + 2] = torus->normals[torus->triangles[k]*3 + 2]; - + mesh.texcoords[k*2] = torus->tcoords[torus->triangles[k]*2]; mesh.texcoords[k*2 + 1] = torus->tcoords[torus->triangles[k]*2 + 1]; } par_shapes_free_mesh(torus); - + // Upload vertex data to GPU (static mesh) - rlLoadMesh(&mesh, false); + rlLoadMesh(&mesh, false); return mesh; } @@ -1193,7 +1692,7 @@ Mesh GenMeshTorus(float radius, float size, int radSeg, int sides) Mesh GenMeshKnot(float radius, float size, int radSeg, int sides) { Mesh mesh = { 0 }; - + if (radius > 3.0f) radius = 3.0f; else if (radius < 0.5f) radius = 0.5f; @@ -1212,19 +1711,19 @@ Mesh GenMeshKnot(float radius, float size, int radSeg, int sides) mesh.vertices[k*3] = knot->points[knot->triangles[k]*3]; mesh.vertices[k*3 + 1] = knot->points[knot->triangles[k]*3 + 1]; mesh.vertices[k*3 + 2] = knot->points[knot->triangles[k]*3 + 2]; - + mesh.normals[k*3] = knot->normals[knot->triangles[k]*3]; mesh.normals[k*3 + 1] = knot->normals[knot->triangles[k]*3 + 1]; mesh.normals[k*3 + 2] = knot->normals[knot->triangles[k]*3 + 2]; - + mesh.texcoords[k*2] = knot->tcoords[knot->triangles[k]*2]; mesh.texcoords[k*2 + 1] = knot->tcoords[knot->triangles[k]*2 + 1]; } par_shapes_free_mesh(knot); - + // Upload vertex data to GPU (static mesh) - rlLoadMesh(&mesh, false); + rlLoadMesh(&mesh, false); return mesh; } @@ -1332,7 +1831,7 @@ Mesh GenMeshHeightmap(Image heightmap, Vector3 size) } free(pixels); - + // Upload vertex data to GPU (static mesh) rlLoadMesh(&mesh, false); @@ -1692,57 +2191,135 @@ Mesh GenMeshCubicmap(Image cubicmap, Vector3 cubeSize) free(mapTexcoords); free(cubicmapPixels); // Free image pixel data - + // Upload vertex data to GPU (static mesh) - rlLoadMesh(&mesh, false); + rlLoadMesh(&mesh, false); return mesh; } #endif // SUPPORT_MESH_GENERATION -// Load material data (from file) -Material LoadMaterial(const char *fileName) +// Compute mesh bounding box limits +// NOTE: minVertex and maxVertex should be transformed by model transform matrix +BoundingBox MeshBoundingBox(Mesh mesh) { - Material material = { 0 }; + // Get min and max vertex to construct bounds (AABB) + Vector3 minVertex = { 0 }; + Vector3 maxVertex = { 0 }; -#if defined(SUPPORT_FILEFORMAT_MTL) - if (IsFileExtension(fileName, ".mtl")) material = LoadMTL(fileName); -#else - TraceLog(LOG_WARNING, "[%s] Material fileformat not supported, it can't be loaded", fileName); -#endif + if (mesh.vertices != NULL) + { + minVertex = (Vector3){ mesh.vertices[0], mesh.vertices[1], mesh.vertices[2] }; + maxVertex = (Vector3){ mesh.vertices[0], mesh.vertices[1], mesh.vertices[2] }; - // Our material uses the default shader (DIFFUSE, SPECULAR, NORMAL) - material.shader = GetShaderDefault(); + for (int i = 1; i < mesh.vertexCount; i++) + { + minVertex = Vector3Min(minVertex, (Vector3){ mesh.vertices[i*3], mesh.vertices[i*3 + 1], mesh.vertices[i*3 + 2] }); + maxVertex = Vector3Max(maxVertex, (Vector3){ mesh.vertices[i*3], mesh.vertices[i*3 + 1], mesh.vertices[i*3 + 2] }); + } + } - return material; + // Create the bounding box + BoundingBox box = { 0 }; + box.min = minVertex; + box.max = maxVertex; + + return box; } -// Load default material (Supports: DIFFUSE, SPECULAR, NORMAL maps) -Material LoadMaterialDefault(void) +// Compute mesh tangents +// NOTE: To calculate mesh tangents and binormals we need mesh vertex positions and texture coordinates +// Implementation base don: https://answers.unity.com/questions/7789/calculating-tangents-vector4.html +void MeshTangents(Mesh *mesh) { - Material material = { 0 }; + if (mesh->tangents == NULL) mesh->tangents = (float *)malloc(mesh->vertexCount*4*sizeof(float)); + else TraceLog(LOG_WARNING, "Mesh tangents already exist"); - material.shader = GetShaderDefault(); - material.maps[MAP_DIFFUSE].texture = GetTextureDefault(); // White texture (1x1 pixel) - //material.maps[MAP_NORMAL].texture; // NOTE: By default, not set - //material.maps[MAP_SPECULAR].texture; // NOTE: By default, not set + Vector3 *tan1 = (Vector3 *)malloc(mesh->vertexCount*sizeof(Vector3)); + Vector3 *tan2 = (Vector3 *)malloc(mesh->vertexCount*sizeof(Vector3)); - material.maps[MAP_DIFFUSE].color = WHITE; // Diffuse color - material.maps[MAP_SPECULAR].color = WHITE; // Specular color + for (int i = 0; i < mesh->vertexCount; i += 3) + { + // Get triangle vertices + Vector3 v1 = { mesh->vertices[(i + 0)*3 + 0], mesh->vertices[(i + 0)*3 + 1], mesh->vertices[(i + 0)*3 + 2] }; + Vector3 v2 = { mesh->vertices[(i + 1)*3 + 0], mesh->vertices[(i + 1)*3 + 1], mesh->vertices[(i + 1)*3 + 2] }; + Vector3 v3 = { mesh->vertices[(i + 2)*3 + 0], mesh->vertices[(i + 2)*3 + 1], mesh->vertices[(i + 2)*3 + 2] }; - return material; + // Get triangle texcoords + Vector2 uv1 = { mesh->texcoords[(i + 0)*2 + 0], mesh->texcoords[(i + 0)*2 + 1] }; + Vector2 uv2 = { mesh->texcoords[(i + 1)*2 + 0], mesh->texcoords[(i + 1)*2 + 1] }; + Vector2 uv3 = { mesh->texcoords[(i + 2)*2 + 0], mesh->texcoords[(i + 2)*2 + 1] }; + + float x1 = v2.x - v1.x; + float y1 = v2.y - v1.y; + float z1 = v2.z - v1.z; + float x2 = v3.x - v1.x; + float y2 = v3.y - v1.y; + float z2 = v3.z - v1.z; + + float s1 = uv2.x - uv1.x; + float t1 = uv2.y - uv1.y; + float s2 = uv3.x - uv1.x; + float t2 = uv3.y - uv1.y; + + float div = s1*t2 - s2*t1; + float r = (div == 0.0f)? 0.0f : 1.0f/div; + + Vector3 sdir = { (t2*x1 - t1*x2)*r, (t2*y1 - t1*y2)*r, (t2*z1 - t1*z2)*r }; + Vector3 tdir = { (s1*x2 - s2*x1)*r, (s1*y2 - s2*y1)*r, (s1*z2 - s2*z1)*r }; + + tan1[i + 0] = sdir; + tan1[i + 1] = sdir; + tan1[i + 2] = sdir; + + tan2[i + 0] = tdir; + tan2[i + 1] = tdir; + tan2[i + 2] = tdir; + } + + // Compute tangents considering normals + for (int i = 0; i < mesh->vertexCount; ++i) + { + Vector3 normal = { mesh->normals[i*3 + 0], mesh->normals[i*3 + 1], mesh->normals[i*3 + 2] }; + Vector3 tangent = tan1[i]; + + // TODO: Review, not sure if tangent computation is right, just used reference proposed maths... + #if defined(COMPUTE_TANGENTS_METHOD_01) + Vector3 tmp = Vector3Subtract(tangent, Vector3Multiply(normal, Vector3DotProduct(normal, tangent))); + tmp = Vector3Normalize(tmp); + mesh->tangents[i*4 + 0] = tmp.x; + mesh->tangents[i*4 + 1] = tmp.y; + mesh->tangents[i*4 + 2] = tmp.z; + mesh->tangents[i*4 + 3] = 1.0f; + #else + Vector3OrthoNormalize(&normal, &tangent); + mesh->tangents[i*4 + 0] = tangent.x; + mesh->tangents[i*4 + 1] = tangent.y; + mesh->tangents[i*4 + 2] = tangent.z; + mesh->tangents[i*4 + 3] = (Vector3DotProduct(Vector3CrossProduct(normal, tangent), tan2[i]) < 0.0f)? -1.0f : 1.0f; + #endif + } + + free(tan1); + free(tan2); + + // Load a new tangent attributes buffer + mesh->vboId[LOC_VERTEX_TANGENT] = rlLoadAttribBuffer(mesh->vaoId, LOC_VERTEX_TANGENT, mesh->tangents, mesh->vertexCount*4*sizeof(float), false); + + TraceLog(LOG_INFO, "Tangents computed for mesh"); } -// Unload material from memory -void UnloadMaterial(Material material) +// Compute mesh binormals (aka bitangent) +void MeshBinormals(Mesh *mesh) { - // Unload material shader (avoid unloading default shader, managed by raylib) - if (material.shader.id != GetShaderDefault().id) UnloadShader(material.shader); - - // Unload loaded texture maps (avoid unloading default texture, managed by raylib) - for (int i = 0; i < MAX_MATERIAL_MAPS; i++) + for (int i = 0; i < mesh->vertexCount; i++) { - if (material.maps[i].texture.id != GetTextureDefault().id) rlDeleteTextures(material.maps[i].texture.id); + Vector3 normal = { mesh->normals[i*3 + 0], mesh->normals[i*3 + 1], mesh->normals[i*3 + 2] }; + Vector3 tangent = { mesh->tangents[i*4 + 0], mesh->tangents[i*4 + 1], mesh->tangents[i*4 + 2] }; + float tangentW = mesh->tangents[i*4 + 3]; + + // TODO: Register computed binormal in mesh->binormal? + // Vector3 binormal = Vector3Multiply(Vector3CrossProduct(normal, tangent), tangentW); } } @@ -1750,7 +2327,7 @@ void UnloadMaterial(Material material) void DrawModel(Model model, Vector3 position, float scale, Color tint) { Vector3 vScale = { scale, scale, scale }; - Vector3 rotationAxis = { 0.0f, 0.0f, 0.0f }; + Vector3 rotationAxis = { 0.0f, 1.0f, 0.0f }; DrawModelEx(model, position, rotationAxis, 0.0f, vScale, tint); } @@ -1763,16 +2340,17 @@ void DrawModelEx(Model model, Vector3 position, Vector3 rotationAxis, float rota Matrix matScale = MatrixScale(scale.x, scale.y, scale.z); Matrix matRotation = MatrixRotate(rotationAxis, rotationAngle*DEG2RAD); Matrix matTranslation = MatrixTranslate(position.x, position.y, position.z); - + Matrix matTransform = MatrixMultiply(MatrixMultiply(matScale, matRotation), matTranslation); // Combine model transformation matrix (model.transform) with matrix generated by function parameters (matTransform) - //Matrix matModel = MatrixMultiply(model.transform, matTransform); // Transform to world-space coordinates - model.transform = MatrixMultiply(model.transform, matTransform); - model.material.maps[MAP_DIFFUSE].color = tint; // TODO: Multiply tint color by diffuse color? - rlDrawMesh(model.mesh, model.material, model.transform); + for (int i = 0; i < model.meshCount; i++) + { + model.materials[model.meshMaterial[i]].maps[MAP_DIFFUSE].color = tint; + rlDrawMesh(model.meshes[i], model.materials[model.meshMaterial[i]], model.transform); + } } // Draw a model wires (with texture if set) @@ -1958,7 +2536,7 @@ bool CheckCollisionRaySphereEx(Ray ray, Vector3 spherePosition, float sphereRadi if (distance < sphereRadius) collisionDistance = vector + sqrtf(d); else collisionDistance = vector - sqrtf(d); - + // Calculate collision point Vector3 cPoint = Vector3Add(ray.position, Vector3Scale(ray.direction, collisionDistance)); @@ -1994,41 +2572,45 @@ RayHitInfo GetCollisionRayModel(Ray ray, Model *model) { RayHitInfo result = { 0 }; - // If mesh doesn't have vertex data on CPU, can't test it. - if (!model->mesh.vertices) return result; + for (int m = 0; m < model->meshCount; m++) + { + // Check if meshhas vertex data on CPU for testing + if (model->meshes[m].vertices != NULL) + { + // model->mesh.triangleCount may not be set, vertexCount is more reliable + int triangleCount = model->meshes[m].vertexCount/3; - // model->mesh.triangleCount may not be set, vertexCount is more reliable - int triangleCount = model->mesh.vertexCount/3; + // Test against all triangles in mesh + for (int i = 0; i < triangleCount; i++) + { + Vector3 a, b, c; + Vector3 *vertdata = (Vector3 *)model->meshes[m].vertices; - // Test against all triangles in mesh - for (int i = 0; i < triangleCount; i++) - { - Vector3 a, b, c; - Vector3 *vertdata = (Vector3 *)model->mesh.vertices; + if (model->meshes[m].indices) + { + a = vertdata[model->meshes[m].indices[i*3 + 0]]; + b = vertdata[model->meshes[m].indices[i*3 + 1]]; + c = vertdata[model->meshes[m].indices[i*3 + 2]]; + } + else + { + a = vertdata[i*3 + 0]; + b = vertdata[i*3 + 1]; + c = vertdata[i*3 + 2]; + } - if (model->mesh.indices) - { - a = vertdata[model->mesh.indices[i*3 + 0]]; - b = vertdata[model->mesh.indices[i*3 + 1]]; - c = vertdata[model->mesh.indices[i*3 + 2]]; - } - else - { - a = vertdata[i*3 + 0]; - b = vertdata[i*3 + 1]; - c = vertdata[i*3 + 2]; - } - - a = Vector3Transform(a, model->transform); - b = Vector3Transform(b, model->transform); - c = Vector3Transform(c, model->transform); + a = Vector3Transform(a, model->transform); + b = Vector3Transform(b, model->transform); + c = Vector3Transform(c, model->transform); - RayHitInfo triHitInfo = GetCollisionRayTriangle(ray, a, b, c); + RayHitInfo triHitInfo = GetCollisionRayTriangle(ray, a, b, c); - if (triHitInfo.hit) - { - // Save the closest hit triangle - if ((!result.hit) || (result.distance > triHitInfo.distance)) result = triHitInfo; + if (triHitInfo.hit) + { + // Save the closest hit triangle + if ((!result.hit) || (result.distance > triHitInfo.distance)) result = triHitInfo; + } + } } } @@ -2117,572 +2699,532 @@ RayHitInfo GetCollisionRayGround(Ray ray, float groundHeight) return result; } -// Compute mesh bounding box limits -// NOTE: minVertex and maxVertex should be transformed by model transform matrix -BoundingBox MeshBoundingBox(Mesh mesh) -{ - // Get min and max vertex to construct bounds (AABB) - Vector3 minVertex = { 0 }; - Vector3 maxVertex = { 0 }; - - if (mesh.vertices != NULL) - { - minVertex = (Vector3){ mesh.vertices[0], mesh.vertices[1], mesh.vertices[2] }; - maxVertex = (Vector3){ mesh.vertices[0], mesh.vertices[1], mesh.vertices[2] }; - - for (int i = 1; i < mesh.vertexCount; i++) - { - minVertex = Vector3Min(minVertex, (Vector3){ mesh.vertices[i*3], mesh.vertices[i*3 + 1], mesh.vertices[i*3 + 2] }); - maxVertex = Vector3Max(maxVertex, (Vector3){ mesh.vertices[i*3], mesh.vertices[i*3 + 1], mesh.vertices[i*3 + 2] }); - } - } - - // Create the bounding box - BoundingBox box; - box.min = minVertex; - box.max = maxVertex; - - return box; -} - -// Compute mesh tangents -// NOTE: To calculate mesh tangents and binormals we need mesh vertex positions and texture coordinates -// Implementation base don: https://answers.unity.com/questions/7789/calculating-tangents-vector4.html -void MeshTangents(Mesh *mesh) -{ - if (mesh->tangents == NULL) mesh->tangents = (float *)malloc(mesh->vertexCount*4*sizeof(float)); - else TraceLog(LOG_WARNING, "Mesh tangents already exist"); - - Vector3 *tan1 = (Vector3 *)malloc(mesh->vertexCount*sizeof(Vector3)); - Vector3 *tan2 = (Vector3 *)malloc(mesh->vertexCount*sizeof(Vector3)); - - for (int i = 0; i < mesh->vertexCount; i += 3) - { - // Get triangle vertices - Vector3 v1 = { mesh->vertices[(i + 0)*3 + 0], mesh->vertices[(i + 0)*3 + 1], mesh->vertices[(i + 0)*3 + 2] }; - Vector3 v2 = { mesh->vertices[(i + 1)*3 + 0], mesh->vertices[(i + 1)*3 + 1], mesh->vertices[(i + 1)*3 + 2] }; - Vector3 v3 = { mesh->vertices[(i + 2)*3 + 0], mesh->vertices[(i + 2)*3 + 1], mesh->vertices[(i + 2)*3 + 2] }; - - // Get triangle texcoords - Vector2 uv1 = { mesh->texcoords[(i + 0)*2 + 0], mesh->texcoords[(i + 0)*2 + 1] }; - Vector2 uv2 = { mesh->texcoords[(i + 1)*2 + 0], mesh->texcoords[(i + 1)*2 + 1] }; - Vector2 uv3 = { mesh->texcoords[(i + 2)*2 + 0], mesh->texcoords[(i + 2)*2 + 1] }; - - float x1 = v2.x - v1.x; - float y1 = v2.y - v1.y; - float z1 = v2.z - v1.z; - float x2 = v3.x - v1.x; - float y2 = v3.y - v1.y; - float z2 = v3.z - v1.z; - - float s1 = uv2.x - uv1.x; - float t1 = uv2.y - uv1.y; - float s2 = uv3.x - uv1.x; - float t2 = uv3.y - uv1.y; - - float div = s1*t2 - s2*t1; - float r = (div == 0.0f) ? 0.0f : 1.0f/div; - - Vector3 sdir = { (t2*x1 - t1*x2)*r, (t2*y1 - t1*y2)*r, (t2*z1 - t1*z2)*r }; - Vector3 tdir = { (s1*x2 - s2*x1)*r, (s1*y2 - s2*y1)*r, (s1*z2 - s2*z1)*r }; - - tan1[i + 0] = sdir; - tan1[i + 1] = sdir; - tan1[i + 2] = sdir; - - tan2[i + 0] = tdir; - tan2[i + 1] = tdir; - tan2[i + 2] = tdir; - } - - // Compute tangents considering normals - for (int i = 0; i < mesh->vertexCount; ++i) - { - Vector3 normal = { mesh->normals[i*3 + 0], mesh->normals[i*3 + 1], mesh->normals[i*3 + 2] }; - Vector3 tangent = tan1[i]; - - // TODO: Review, not sure if tangent computation is right, just used reference proposed maths... - #if defined(COMPUTE_TANGENTS_METHOD_01) - Vector3 tmp = Vector3Subtract(tangent, Vector3Multiply(normal, Vector3DotProduct(normal, tangent))); - tmp = Vector3Normalize(tmp); - mesh->tangents[i*4 + 0] = tmp.x; - mesh->tangents[i*4 + 1] = tmp.y; - mesh->tangents[i*4 + 2] = tmp.z; - mesh->tangents[i*4 + 3] = 1.0f; - #else - Vector3OrthoNormalize(&normal, &tangent); - mesh->tangents[i*4 + 0] = tangent.x; - mesh->tangents[i*4 + 1] = tangent.y; - mesh->tangents[i*4 + 2] = tangent.z; - mesh->tangents[i*4 + 3] = (Vector3DotProduct(Vector3CrossProduct(normal, tangent), tan2[i]) < 0.0f) ? -1.0f : 1.0f; - #endif - } - - free(tan1); - free(tan2); - - TraceLog(LOG_INFO, "Tangents computed for mesh"); -} - -// Compute mesh binormals (aka bitangent) -void MeshBinormals(Mesh *mesh) -{ - for (int i = 0; i < mesh->vertexCount; i++) - { - Vector3 normal = { mesh->normals[i*3 + 0], mesh->normals[i*3 + 1], mesh->normals[i*3 + 2] }; - Vector3 tangent = { mesh->tangents[i*4 + 0], mesh->tangents[i*4 + 1], mesh->tangents[i*4 + 2] }; - float tangentW = mesh->tangents[i*4 + 3]; - - // TODO: Register computed binormal in mesh->binormal ? - // Vector3 binormal = Vector3Multiply(Vector3CrossProduct(normal, tangent), tangentW); - } -} - //---------------------------------------------------------------------------------- // Module specific Functions Definition //---------------------------------------------------------------------------------- #if defined(SUPPORT_FILEFORMAT_OBJ) // Load OBJ mesh data -static Mesh LoadOBJ(const char *fileName) +static Model LoadOBJ(const char *fileName) { - Mesh mesh = { 0 }; + Model model = { 0 }; - char dataType = 0; - char comments[200]; + tinyobj_attrib_t attrib; + tinyobj_shape_t *meshes = NULL; + unsigned int meshCount = 0; - int vertexCount = 0; - int normalCount = 0; - int texcoordCount = 0; - int triangleCount = 0; + tinyobj_material_t *materials = NULL; + unsigned int materialCount = 0; - FILE *objFile; + int dataLength = 0; + char *data = NULL; - objFile = fopen(fileName, "rt"); + // Load model data + FILE *objFile = fopen(fileName, "rb"); - if (objFile == NULL) + if (objFile != NULL) { - TraceLog(LOG_WARNING, "[%s] OBJ file could not be opened", fileName); - return mesh; - } + fseek(objFile, 0, SEEK_END); + long length = ftell(objFile); // Get file size + fseek(objFile, 0, SEEK_SET); // Reset file pointer - // First reading pass: Get vertexCount, normalCount, texcoordCount, triangleCount - // NOTE: vertex, texcoords and normals could be optimized (to be used indexed on faces definition) - // NOTE: faces MUST be defined as TRIANGLES (3 vertex per face) - while (!feof(objFile)) - { - dataType = 0; - fscanf(objFile, "%c", &dataType); + data = (char *)malloc(length); - switch (dataType) - { - case '#': // Comments - case 'o': // Object name (One OBJ file can contain multible named meshes) - case 'g': // Group name - case 's': // Smoothing level - case 'm': // mtllib [external .mtl file name] - case 'u': // usemtl [material name] - { - fgets(comments, 200, objFile); - } break; - case 'v': - { - fscanf(objFile, "%c", &dataType); - - if (dataType == 't') // Read texCoord - { - texcoordCount++; - fgets(comments, 200, objFile); - } - else if (dataType == 'n') // Read normals - { - normalCount++; - fgets(comments, 200, objFile); - } - else // Read vertex - { - vertexCount++; - fgets(comments, 200, objFile); - } - } break; - case 'f': - { - triangleCount++; - fgets(comments, 200, objFile); - } break; - default: break; - } + fread(data, length, 1, objFile); + dataLength = length; + fclose(objFile); } - TraceLog(LOG_DEBUG, "[%s] Model vertices: %i", fileName, vertexCount); - TraceLog(LOG_DEBUG, "[%s] Model texcoords: %i", fileName, texcoordCount); - TraceLog(LOG_DEBUG, "[%s] Model normals: %i", fileName, normalCount); - TraceLog(LOG_DEBUG, "[%s] Model triangles: %i", fileName, triangleCount); - - // Once we know the number of vertices to store, we create required arrays - Vector3 *midVertices = (Vector3 *)malloc(vertexCount*sizeof(Vector3)); - Vector3 *midNormals = NULL; - if (normalCount > 0) midNormals = (Vector3 *)malloc(normalCount*sizeof(Vector3)); - Vector2 *midTexCoords = NULL; - if (texcoordCount > 0) midTexCoords = (Vector2 *)malloc(texcoordCount*sizeof(Vector2)); - - int countVertex = 0; - int countNormals = 0; - int countTexCoords = 0; - - rewind(objFile); // Return to the beginning of the file, to read again - - // Second reading pass: Get vertex data to fill intermediate arrays - // NOTE: This second pass is required in case of multiple meshes defined in same OBJ - // TODO: Consider that different meshes can have different vertex data available (position, texcoords, normals) - while (!feof(objFile)) + if (data != NULL) { - fscanf(objFile, "%c", &dataType); - - switch (dataType) + unsigned int flags = TINYOBJ_FLAG_TRIANGULATE; + int ret = tinyobj_parse_obj(&attrib, &meshes, &meshCount, &materials, &materialCount, data, dataLength, flags); + + if (ret != TINYOBJ_SUCCESS) TraceLog(LOG_WARNING, "[%s] Model data could not be loaded", fileName); + else TraceLog(LOG_INFO, "[%s] Model data loaded successfully: %i meshes / %i materials", fileName, meshCount, materialCount); + + // Init model meshes array + model.meshCount = meshCount; + model.meshes = (Mesh *)malloc(model.meshCount*sizeof(Mesh)); + + // Init model materials array + model.materialCount = materialCount; + model.materials = (Material *)malloc(model.materialCount*sizeof(Material)); + model.meshMaterial = (int *)calloc(model.meshCount, sizeof(int)); + + /* + // Multiple meshes data reference + // NOTE: They are provided as a faces offset + typedef struct { + char *name; // group name or object name + unsigned int face_offset; + unsigned int length; + } tinyobj_shape_t; + */ + + // Init model meshes + for (int m = 0; m < 1; m++) { - case '#': case 'o': case 'g': case 's': case 'm': case 'u': case 'f': fgets(comments, 200, objFile); break; - case 'v': + Mesh mesh = { 0 }; + memset(&mesh, 0, sizeof(Mesh)); + mesh.vertexCount = attrib.num_faces*3; + mesh.triangleCount = attrib.num_faces; + mesh.vertices = (float *)malloc(mesh.vertexCount*3*sizeof(float)); + mesh.texcoords = (float *)malloc(mesh.vertexCount*2*sizeof(float)); + mesh.normals = (float *)malloc(mesh.vertexCount*3*sizeof(float)); + + int vCount = 0; + int vtCount = 0; + int vnCount = 0; + + for (int f = 0; f < attrib.num_faces; f++) { - fscanf(objFile, "%c", &dataType); + // Get indices for the face + tinyobj_vertex_index_t idx0 = attrib.faces[3*f + 0]; + tinyobj_vertex_index_t idx1 = attrib.faces[3*f + 1]; + tinyobj_vertex_index_t idx2 = attrib.faces[3*f + 2]; + + // TraceLog(LOG_DEBUG, "Face %i index: v %i/%i/%i . vt %i/%i/%i . vn %i/%i/%i\n", f, idx0.v_idx, idx1.v_idx, idx2.v_idx, idx0.vt_idx, idx1.vt_idx, idx2.vt_idx, idx0.vn_idx, idx1.vn_idx, idx2.vn_idx); + + // Fill vertices buffer (float) using vertex index of the face + for (int v = 0; v < 3; v++) { mesh.vertices[vCount + v] = attrib.vertices[idx0.v_idx*3 + v]; } vCount +=3; + for (int v = 0; v < 3; v++) { mesh.vertices[vCount + v] = attrib.vertices[idx1.v_idx*3 + v]; } vCount +=3; + for (int v = 0; v < 3; v++) { mesh.vertices[vCount + v] = attrib.vertices[idx2.v_idx*3 + v]; } vCount +=3; + + // Fill texcoords buffer (float) using vertex index of the face + // NOTE: Y-coordinate must be flipped upside-down + mesh.texcoords[vtCount + 0] = attrib.texcoords[idx0.vt_idx*2 + 0]; + mesh.texcoords[vtCount + 1] = 1.0f - attrib.texcoords[idx0.vt_idx*2 + 1]; vtCount += 2; + mesh.texcoords[vtCount + 0] = attrib.texcoords[idx1.vt_idx*2 + 0]; + mesh.texcoords[vtCount + 1] = 1.0f - attrib.texcoords[idx1.vt_idx*2 + 1]; vtCount += 2; + mesh.texcoords[vtCount + 0] = attrib.texcoords[idx2.vt_idx*2 + 0]; + mesh.texcoords[vtCount + 1] = 1.0f - attrib.texcoords[idx2.vt_idx*2 + 1]; vtCount += 2; + + // Fill normals buffer (float) using vertex index of the face + for (int v = 0; v < 3; v++) { mesh.normals[vnCount + v] = attrib.normals[idx0.vn_idx*3 + v]; } vnCount +=3; + for (int v = 0; v < 3; v++) { mesh.normals[vnCount + v] = attrib.normals[idx1.vn_idx*3 + v]; } vnCount +=3; + for (int v = 0; v < 3; v++) { mesh.normals[vnCount + v] = attrib.normals[idx2.vn_idx*3 + v]; } vnCount +=3; + } - if (dataType == 't') // Read texCoord - { - fscanf(objFile, "%f %f%*[^\n]s\n", &midTexCoords[countTexCoords].x, &midTexCoords[countTexCoords].y); - countTexCoords++; + model.meshes[m] = mesh; // Assign mesh data to model - fscanf(objFile, "%c", &dataType); - } - else if (dataType == 'n') // Read normals - { - fscanf(objFile, "%f %f %f", &midNormals[countNormals].x, &midNormals[countNormals].y, &midNormals[countNormals].z); - countNormals++; - - fscanf(objFile, "%c", &dataType); - } - else // Read vertex - { - fscanf(objFile, "%f %f %f", &midVertices[countVertex].x, &midVertices[countVertex].y, &midVertices[countVertex].z); - countVertex++; + // Assign mesh material for current mesh + model.meshMaterial[m] = attrib.material_ids[m]; + } - fscanf(objFile, "%c", &dataType); - } - } break; - default: break; + // Init model materials + for (int m = 0; m < materialCount; m++) + { + // Init material to default + // NOTE: Uses default shader, only MAP_DIFFUSE supported + model.materials[m] = LoadMaterialDefault(); + + /* + typedef struct { + char *name; + + float ambient[3]; + float diffuse[3]; + float specular[3]; + float transmittance[3]; + float emission[3]; + float shininess; + float ior; // index of refraction + float dissolve; // 1 == opaque; 0 == fully transparent + // illumination model (see http://www.fileformat.info/format/material/) + int illum; + + int pad0; + + char *ambient_texname; // map_Ka + char *diffuse_texname; // map_Kd + char *specular_texname; // map_Ks + char *specular_highlight_texname; // map_Ns + char *bump_texname; // map_bump, bump + char *displacement_texname; // disp + char *alpha_texname; // map_d + } tinyobj_material_t; + */ + + model.materials[m].maps[MAP_DIFFUSE].texture = GetTextureDefault(); // Get default texture, in case no texture is defined + + if (materials[m].diffuse_texname != NULL) model.materials[m].maps[MAP_DIFFUSE].texture = LoadTexture(materials[m].diffuse_texname); //char *diffuse_texname; // map_Kd + model.materials[m].maps[MAP_DIFFUSE].color = (Color){ (float)(materials[m].diffuse[0]*255.0f), (float)(materials[m].diffuse[1]*255.0f), (float)(materials[m].diffuse[2]*255.0f), 255 }; //float diffuse[3]; + model.materials[m].maps[MAP_DIFFUSE].value = 0.0f; + + if (materials[m].specular_texname != NULL) model.materials[m].maps[MAP_SPECULAR].texture = LoadTexture(materials[m].specular_texname); //char *specular_texname; // map_Ks + model.materials[m].maps[MAP_SPECULAR].color = (Color){ (float)(materials[m].specular[0]*255.0f), (float)(materials[m].specular[1]*255.0f), (float)(materials[m].specular[2]*255.0f), 255 }; //float specular[3]; + model.materials[m].maps[MAP_SPECULAR].value = 0.0f; + + if (materials[m].bump_texname != NULL) model.materials[m].maps[MAP_NORMAL].texture = LoadTexture(materials[m].bump_texname); //char *bump_texname; // map_bump, bump + model.materials[m].maps[MAP_NORMAL].color = WHITE; + model.materials[m].maps[MAP_NORMAL].value = materials[m].shininess; + + model.materials[m].maps[MAP_EMISSION].color = (Color){ (float)(materials[m].emission[0]*255.0f), (float)(materials[m].emission[1]*255.0f), (float)(materials[m].emission[2]*255.0f), 255 }; //float emission[3]; + + if (materials[m].displacement_texname != NULL) model.materials[m].maps[MAP_HEIGHT].texture = LoadTexture(materials[m].displacement_texname); //char *displacement_texname; // disp } + + tinyobj_attrib_free(&attrib); + tinyobj_shapes_free(meshes, meshCount); + tinyobj_materials_free(materials, materialCount); } - // At this point all vertex data (v, vt, vn) has been gathered on midVertices, midTexCoords, midNormals - // Now we can organize that data into our Mesh struct + // NOTE: At this point we have all model data loaded + TraceLog(LOG_INFO, "[%s] Model loaded successfully in RAM (CPU)", fileName); - mesh.vertexCount = triangleCount*3; + return model; +} +#endif - // Additional arrays to store vertex data as floats - mesh.vertices = (float *)malloc(mesh.vertexCount*3*sizeof(float)); - mesh.texcoords = (float *)malloc(mesh.vertexCount*2*sizeof(float)); - mesh.normals = (float *)malloc(mesh.vertexCount*3*sizeof(float)); - mesh.colors = NULL; +#if defined(SUPPORT_FILEFORMAT_IQM) +// Load IQM mesh data +static Model LoadIQM(const char *fileName) +{ + #define IQM_MAGIC "INTERQUAKEMODEL" // IQM file magic number + #define IQM_VERSION 2 // only IQM version 2 supported + + #define BONE_NAME_LENGTH 32 // BoneInfo name string length + #define MESH_NAME_LENGTH 32 // Mesh name string length + + // IQM file structs + //----------------------------------------------------------------------------------- + typedef struct IQMHeader { + char magic[16]; + unsigned int version; + unsigned int filesize; + unsigned int flags; + unsigned int num_text, ofs_text; + unsigned int num_meshes, ofs_meshes; + unsigned int num_vertexarrays, num_vertexes, ofs_vertexarrays; + unsigned int num_triangles, ofs_triangles, ofs_adjacency; + unsigned int num_joints, ofs_joints; + unsigned int num_poses, ofs_poses; + unsigned int num_anims, ofs_anims; + unsigned int num_frames, num_framechannels, ofs_frames, ofs_bounds; + unsigned int num_comment, ofs_comment; + unsigned int num_extensions, ofs_extensions; + } IQMHeader; + + typedef struct IQMMesh { + unsigned int name; + unsigned int material; + unsigned int first_vertex, num_vertexes; + unsigned int first_triangle, num_triangles; + } IQMMesh; + + typedef struct IQMTriangle { + unsigned int vertex[3]; + } IQMTriangle; + + // NOTE: Adjacency unused by default + typedef struct IQMAdjacency { + unsigned int triangle[3]; + } IQMAdjacency; + + typedef struct IQMJoint { + unsigned int name; + int parent; + float translate[3], rotate[4], scale[3]; + } IQMJoint; + + typedef struct IQMPose { + int parent; + unsigned int mask; + float channeloffset[10]; + float channelscale[10]; + } IQMPose; + + typedef struct IQMAnim { + unsigned int name; + unsigned int first_frame, num_frames; + float framerate; + unsigned int flags; + } IQMAnim; + + typedef struct IQMVertexArray { + unsigned int type; + unsigned int flags; + unsigned int format; + unsigned int size; + unsigned int offset; + } IQMVertexArray; + + // NOTE: Bounds unused by default + typedef struct IQMBounds { + float bbmin[3], bbmax[3]; + float xyradius, radius; + } IQMBounds; + //----------------------------------------------------------------------------------- + + // IQM vertex data types + typedef enum { + IQM_POSITION = 0, + IQM_TEXCOORD = 1, + IQM_NORMAL = 2, + IQM_TANGENT = 3, // NOTE: Tangents unused by default + IQM_BLENDINDEXES = 4, + IQM_BLENDWEIGHTS = 5, + IQM_COLOR = 6, // NOTE: Vertex colors unused by default + IQM_CUSTOM = 0x10 // NOTE: Custom vertex values unused by default + } IQMVertexType; - int vCounter = 0; // Used to count vertices float by float - int tcCounter = 0; // Used to count texcoords float by float - int nCounter = 0; // Used to count normals float by float + Model model = { 0 }; - int vCount[3], vtCount[3], vnCount[3]; // Used to store triangle indices for v, vt, vn + FILE *iqmFile; + IQMHeader iqm; - rewind(objFile); // Return to the beginning of the file, to read again + IQMMesh *imesh; + IQMTriangle *tri; + IQMVertexArray *va; + IQMJoint *ijoint; - if (normalCount == 0) TraceLog(LOG_INFO, "[%s] No normals data on OBJ, normals will be generated from faces data", fileName); + float *vertex = NULL; + float *normal = NULL; + float *text = NULL; + char *blendi = NULL; + unsigned char *blendw = NULL; - // Third reading pass: Get faces (triangles) data and fill VertexArray - while (!feof(objFile)) + iqmFile = fopen(fileName, "rb"); + + if (iqmFile == NULL) { - fscanf(objFile, "%c", &dataType); + TraceLog(LOG_WARNING, "[%s] IQM file could not be opened", fileName); + return model; + } - switch (dataType) - { - case '#': case 'o': case 'g': case 's': case 'm': case 'u': case 'v': fgets(comments, 200, objFile); break; - case 'f': - { - // NOTE: It could be that OBJ does not have normals or texcoords defined! - - if ((normalCount == 0) && (texcoordCount == 0)) fscanf(objFile, "%i %i %i", &vCount[0], &vCount[1], &vCount[2]); - else if (normalCount == 0) fscanf(objFile, "%i/%i %i/%i %i/%i", &vCount[0], &vtCount[0], &vCount[1], &vtCount[1], &vCount[2], &vtCount[2]); - else if (texcoordCount == 0) fscanf(objFile, "%i//%i %i//%i %i//%i", &vCount[0], &vnCount[0], &vCount[1], &vnCount[1], &vCount[2], &vnCount[2]); - else fscanf(objFile, "%i/%i/%i %i/%i/%i %i/%i/%i", &vCount[0], &vtCount[0], &vnCount[0], &vCount[1], &vtCount[1], &vnCount[1], &vCount[2], &vtCount[2], &vnCount[2]); - - mesh.vertices[vCounter] = midVertices[vCount[0]-1].x; - mesh.vertices[vCounter + 1] = midVertices[vCount[0]-1].y; - mesh.vertices[vCounter + 2] = midVertices[vCount[0]-1].z; - vCounter += 3; - mesh.vertices[vCounter] = midVertices[vCount[1]-1].x; - mesh.vertices[vCounter + 1] = midVertices[vCount[1]-1].y; - mesh.vertices[vCounter + 2] = midVertices[vCount[1]-1].z; - vCounter += 3; - mesh.vertices[vCounter] = midVertices[vCount[2]-1].x; - mesh.vertices[vCounter + 1] = midVertices[vCount[2]-1].y; - mesh.vertices[vCounter + 2] = midVertices[vCount[2]-1].z; - vCounter += 3; - - if (normalCount > 0) - { - mesh.normals[nCounter] = midNormals[vnCount[0]-1].x; - mesh.normals[nCounter + 1] = midNormals[vnCount[0]-1].y; - mesh.normals[nCounter + 2] = midNormals[vnCount[0]-1].z; - nCounter += 3; - mesh.normals[nCounter] = midNormals[vnCount[1]-1].x; - mesh.normals[nCounter + 1] = midNormals[vnCount[1]-1].y; - mesh.normals[nCounter + 2] = midNormals[vnCount[1]-1].z; - nCounter += 3; - mesh.normals[nCounter] = midNormals[vnCount[2]-1].x; - mesh.normals[nCounter + 1] = midNormals[vnCount[2]-1].y; - mesh.normals[nCounter + 2] = midNormals[vnCount[2]-1].z; - nCounter += 3; - } - else - { - // If normals not defined, they are calculated from the 3 vertices [N = (V2 - V1) x (V3 - V1)] - Vector3 norm = Vector3CrossProduct(Vector3Subtract(midVertices[vCount[1]-1], midVertices[vCount[0]-1]), Vector3Subtract(midVertices[vCount[2]-1], midVertices[vCount[0]-1])); - norm = Vector3Normalize(norm); - - mesh.normals[nCounter] = norm.x; - mesh.normals[nCounter + 1] = norm.y; - mesh.normals[nCounter + 2] = norm.z; - nCounter += 3; - mesh.normals[nCounter] = norm.x; - mesh.normals[nCounter + 1] = norm.y; - mesh.normals[nCounter + 2] = norm.z; - nCounter += 3; - mesh.normals[nCounter] = norm.x; - mesh.normals[nCounter + 1] = norm.y; - mesh.normals[nCounter + 2] = norm.z; - nCounter += 3; - } + fread(&iqm,sizeof(IQMHeader), 1, iqmFile); // Read IQM header - if (texcoordCount > 0) - { - // NOTE: If using negative texture coordinates with a texture filter of GL_CLAMP_TO_EDGE doesn't work! - // NOTE: Texture coordinates are Y flipped upside-down - mesh.texcoords[tcCounter] = midTexCoords[vtCount[0]-1].x; - mesh.texcoords[tcCounter + 1] = 1.0f - midTexCoords[vtCount[0]-1].y; - tcCounter += 2; - mesh.texcoords[tcCounter] = midTexCoords[vtCount[1]-1].x; - mesh.texcoords[tcCounter + 1] = 1.0f - midTexCoords[vtCount[1]-1].y; - tcCounter += 2; - mesh.texcoords[tcCounter] = midTexCoords[vtCount[2]-1].x; - mesh.texcoords[tcCounter + 1] = 1.0f - midTexCoords[vtCount[2]-1].y; - tcCounter += 2; - } - } break; - default: break; - } + if (strncmp(iqm.magic, IQM_MAGIC, sizeof(IQM_MAGIC))) + { + TraceLog(LOG_WARNING, "[%s] IQM file does not seem to be valid", fileName); + fclose(iqmFile); + return model; } - fclose(objFile); + if (iqm.version != IQM_VERSION) + { + TraceLog(LOG_WARNING, "[%s] IQM file version is not supported (%i).", fileName, iqm.version); + fclose(iqmFile); + return model; + } - // Now we can free temp mid* arrays - free(midVertices); - free(midNormals); - free(midTexCoords); + // Meshes data processing + imesh = malloc(sizeof(IQMMesh)*iqm.num_meshes); + fseek(iqmFile, iqm.ofs_meshes, SEEK_SET); + fread(imesh, sizeof(IQMMesh)*iqm.num_meshes, 1, iqmFile); - // NOTE: At this point we have all vertex, texcoord, normal data for the model in mesh struct - TraceLog(LOG_INFO, "[%s] Model loaded successfully in RAM (CPU)", fileName); + model.meshCount = iqm.num_meshes; + model.meshes = malloc(model.meshCount*sizeof(Mesh)); - return mesh; -} -#endif + char name[MESH_NAME_LENGTH]; -#if defined(SUPPORT_FILEFORMAT_MTL) -// Load MTL material data (specs: http://paulbourke.net/dataformats/mtl/) -// NOTE: Texture map parameters are not supported -static Material LoadMTL(const char *fileName) -{ - #define MAX_BUFFER_SIZE 128 + for (int i = 0; i < model.meshCount; i++) + { + fseek(iqmFile,iqm.ofs_text+imesh[i].name,SEEK_SET); + fread(name, sizeof(char)*MESH_NAME_LENGTH, 1, iqmFile); // Mesh name not used... + model.meshes[i].vertexCount = imesh[i].num_vertexes; - Material material = { 0 }; + model.meshes[i].vertices = malloc(sizeof(float)*model.meshes[i].vertexCount*3); // Default vertex positions + model.meshes[i].normals = malloc(sizeof(float)*model.meshes[i].vertexCount*3); // Default vertex normals + model.meshes[i].texcoords = malloc(sizeof(float)*model.meshes[i].vertexCount*2); // Default vertex texcoords + + model.meshes[i].boneIds = malloc(sizeof(int)*model.meshes[i].vertexCount*4); // Up-to 4 bones supported! + model.meshes[i].boneWeights = malloc(sizeof(float)*model.meshes[i].vertexCount*4); // Up-to 4 bones supported! - char buffer[MAX_BUFFER_SIZE]; - Vector3 color = { 1.0f, 1.0f, 1.0f }; - char mapFileName[128]; - int result = 0; + model.meshes[i].triangleCount = imesh[i].num_triangles; + model.meshes[i].indices = malloc(sizeof(unsigned short)*model.meshes[i].triangleCount*3); - FILE *mtlFile; + // Animated verted data, what we actually process for rendering + // NOTE: Animated vertex should be re-uploaded to GPU (if not using GPU skinning) + model.meshes[i].animVertices = malloc(sizeof(float)*model.meshes[i].vertexCount*3); + model.meshes[i].animNormals = malloc(sizeof(float)*model.meshes[i].vertexCount*3); + } - mtlFile = fopen(fileName, "rt"); + // Triangles data processing + tri = malloc(sizeof(IQMTriangle)*iqm.num_triangles); + fseek(iqmFile, iqm.ofs_triangles, SEEK_SET); + fread(tri, sizeof(IQMTriangle)*iqm.num_triangles, 1, iqmFile); - if (mtlFile == NULL) + for (int m = 0; m < model.meshCount; m++) { - TraceLog(LOG_WARNING, "[%s] MTL file could not be opened", fileName); - return material; + int tcounter = 0; + + for (int i = imesh[m].first_triangle; i < (imesh[m].first_triangle + imesh[m].num_triangles); i++) + { + // IQM triangles are stored counter clockwise, but raylib sets opengl to clockwise drawing, so we swap them around + model.meshes[m].indices[tcounter + 2] = tri[i].vertex[0] - imesh[m].first_vertex; + model.meshes[m].indices[tcounter + 1] = tri[i].vertex[1] - imesh[m].first_vertex; + model.meshes[m].indices[tcounter] = tri[i].vertex[2] - imesh[m].first_vertex; + tcounter += 3; + } } - while (!feof(mtlFile)) - { - fgets(buffer, MAX_BUFFER_SIZE, mtlFile); + // Vertex arrays data processing + va = malloc(sizeof(IQMVertexArray)*iqm.num_vertexarrays); + fseek(iqmFile, iqm.ofs_vertexarrays, SEEK_SET); + fread(va, sizeof(IQMVertexArray)*iqm.num_vertexarrays, 1, iqmFile); - switch (buffer[0]) + for (int i = 0; i < iqm.num_vertexarrays; i++) + { + switch (va[i].type) { - case 'n': // newmtl string Material name. Begins a new material description. + case IQM_POSITION: { - // TODO: Support multiple materials in a single .mtl - sscanf(buffer, "newmtl %127s", mapFileName); + vertex = malloc(sizeof(float)*iqm.num_vertexes*3); + fseek(iqmFile, va[i].offset, SEEK_SET); + fread(vertex, sizeof(float)*iqm.num_vertexes*3, 1, iqmFile); - TraceLog(LOG_INFO, "[%s] Loading material...", mapFileName); - } - case 'i': // illum int Illumination model - { - // illum = 1 if specular disabled - // illum = 2 if specular enabled (lambertian model) - // ... - } - case 'K': // Ka, Kd, Ks, Ke - { - switch (buffer[1]) + for (int m = 0; m < iqm.num_meshes; m++) { - case 'a': // Ka float float float Ambient color (RGB) - { - sscanf(buffer, "Ka %f %f %f", &color.x, &color.y, &color.z); - // TODO: Support ambient color - //material.colAmbient.r = (unsigned char)(color.x*255); - //material.colAmbient.g = (unsigned char)(color.y*255); - //material.colAmbient.b = (unsigned char)(color.z*255); - } break; - case 'd': // Kd float float float Diffuse color (RGB) + int vCounter = 0; + for (int i = imesh[m].first_vertex*3; i < (imesh[m].first_vertex + imesh[m].num_vertexes)*3; i++) { - sscanf(buffer, "Kd %f %f %f", &color.x, &color.y, &color.z); - material.maps[MAP_DIFFUSE].color.r = (unsigned char)(color.x*255); - material.maps[MAP_DIFFUSE].color.g = (unsigned char)(color.y*255); - material.maps[MAP_DIFFUSE].color.b = (unsigned char)(color.z*255); - } break; - case 's': // Ks float float float Specular color (RGB) - { - sscanf(buffer, "Ks %f %f %f", &color.x, &color.y, &color.z); - material.maps[MAP_SPECULAR].color.r = (unsigned char)(color.x*255); - material.maps[MAP_SPECULAR].color.g = (unsigned char)(color.y*255); - material.maps[MAP_SPECULAR].color.b = (unsigned char)(color.z*255); - } break; - case 'e': // Ke float float float Emmisive color (RGB) - { - // TODO: Support Ke ? - } break; - default: break; + model.meshes[m].vertices[vCounter] = vertex[i]; + model.meshes[m].animVertices[vCounter] = vertex[i]; + vCounter++; + } } } break; - case 'N': // Ns, Ni + case IQM_NORMAL: { - if (buffer[1] == 's') // Ns int Shininess (specular exponent). Ranges from 0 to 1000. - { - int shininess = 0; - sscanf(buffer, "Ns %i", &shininess); + normal = malloc(sizeof(float)*iqm.num_vertexes*3); + fseek(iqmFile, va[i].offset, SEEK_SET); + fread(normal, sizeof(float)*iqm.num_vertexes*3, 1, iqmFile); - //material.params[PARAM_GLOSSINES] = (float)shininess; - } - else if (buffer[1] == 'i') // Ni int Refraction index. + for (int m = 0; m < iqm.num_meshes; m++) { - // Not supported... + int vCounter = 0; + for (int i = imesh[m].first_vertex*3; i < (imesh[m].first_vertex + imesh[m].num_vertexes)*3; i++) + { + model.meshes[m].normals[vCounter] = normal[i]; + model.meshes[m].animNormals[vCounter] = normal[i]; + vCounter++; + } } } break; - case 'm': // map_Kd, map_Ks, map_Ka, map_Bump, map_d + case IQM_TEXCOORD: { - switch (buffer[4]) + text = malloc(sizeof(float)*iqm.num_vertexes*2); + fseek(iqmFile, va[i].offset, SEEK_SET); + fread(text, sizeof(float)*iqm.num_vertexes*2, 1, iqmFile); + + for (int m = 0; m < iqm.num_meshes; m++) { - case 'K': // Color texture maps + int vCounter = 0; + for (int i = imesh[m].first_vertex*2; i < (imesh[m].first_vertex + imesh[m].num_vertexes)*2; i++) { - if (buffer[5] == 'd') // map_Kd string Diffuse color texture map. - { - result = sscanf(buffer, "map_Kd %127s", mapFileName); - if (result != EOF) material.maps[MAP_DIFFUSE].texture = LoadTexture(mapFileName); - } - else if (buffer[5] == 's') // map_Ks string Specular color texture map. - { - result = sscanf(buffer, "map_Ks %127s", mapFileName); - if (result != EOF) material.maps[MAP_SPECULAR].texture = LoadTexture(mapFileName); - } - else if (buffer[5] == 'a') // map_Ka string Ambient color texture map. - { - // Not supported... - } - } break; - case 'B': // map_Bump string Bump texture map. - { - result = sscanf(buffer, "map_Bump %127s", mapFileName); - if (result != EOF) material.maps[MAP_NORMAL].texture = LoadTexture(mapFileName); - } break; - case 'b': // map_bump string Bump texture map. - { - result = sscanf(buffer, "map_bump %127s", mapFileName); - if (result != EOF) material.maps[MAP_NORMAL].texture = LoadTexture(mapFileName); - } break; - case 'd': // map_d string Opacity texture map. - { - // Not supported... - } break; - default: break; + model.meshes[m].texcoords[vCounter] = text[i]; + vCounter++; + } } } break; - case 'd': // d, disp + case IQM_BLENDINDEXES: { - if (buffer[1] == ' ') // d float Dissolve factor. d is inverse of Tr - { - float alpha = 1.0f; - sscanf(buffer, "d %f", &alpha); - material.maps[MAP_DIFFUSE].color.a = (unsigned char)(alpha*255); - } - else if (buffer[1] == 'i') // disp string Displacement map + blendi = malloc(sizeof(char)*iqm.num_vertexes*4); + fseek(iqmFile, va[i].offset, SEEK_SET); + fread(blendi, sizeof(char)*iqm.num_vertexes*4, 1, iqmFile); + + for (int m = 0; m < iqm.num_meshes; m++) { - // Not supported... + int boneCounter = 0; + for (int i = imesh[m].first_vertex*4; i < (imesh[m].first_vertex + imesh[m].num_vertexes)*4; i++) + { + model.meshes[m].boneIds[boneCounter] = blendi[i]; + boneCounter++; + } } } break; - case 'b': // bump string Bump texture map - { - result = sscanf(buffer, "bump %127s", mapFileName); - if (result != EOF) material.maps[MAP_NORMAL].texture = LoadTexture(mapFileName); - } break; - case 'T': // Tr float Transparency Tr (alpha). Tr is inverse of d + case IQM_BLENDWEIGHTS: { - float ialpha = 0.0f; - sscanf(buffer, "Tr %f", &ialpha); - material.maps[MAP_DIFFUSE].color.a = (unsigned char)((1.0f - ialpha)*255); + blendw = malloc(sizeof(unsigned char)*iqm.num_vertexes*4); + fseek(iqmFile,va[i].offset,SEEK_SET); + fread(blendw,sizeof(unsigned char)*iqm.num_vertexes*4,1,iqmFile); + for (int m = 0; m < iqm.num_meshes; m++) + { + int boneCounter = 0; + for (int i = imesh[m].first_vertex*4; i < (imesh[m].first_vertex + imesh[m].num_vertexes)*4; i++) + { + model.meshes[m].boneWeights[boneCounter] = blendw[i]/255.0f; + boneCounter++; + } + } } break; - case 'r': // refl string Reflection texture map - default: break; } } - fclose(mtlFile); + // Bones (joints) data processing + ijoint = malloc(sizeof(IQMJoint)*iqm.num_joints); + fseek(iqmFile, iqm.ofs_joints, SEEK_SET); + fread(ijoint, sizeof(IQMJoint)*iqm.num_joints, 1, iqmFile); - // NOTE: At this point we have all material data - TraceLog(LOG_INFO, "[%s] Material loaded successfully", fileName); + model.boneCount = iqm.num_joints; + model.bones = malloc(sizeof(BoneInfo)*iqm.num_joints); + model.bindPose = malloc(sizeof(Transform)*iqm.num_joints); - return material; + for (int i = 0; i < iqm.num_joints; i++) + { + // Bones + model.bones[i].parent = ijoint[i].parent; + fseek(iqmFile, iqm.ofs_text + ijoint[i].name, SEEK_SET); + fread(model.bones[i].name,sizeof(char)*BONE_NAME_LENGTH, 1, iqmFile); + + // Bind pose (base pose) + model.bindPose[i].translation.x = ijoint[i].translate[0]; + model.bindPose[i].translation.y = ijoint[i].translate[1]; + model.bindPose[i].translation.z = ijoint[i].translate[2]; + + model.bindPose[i].rotation.x = ijoint[i].rotate[0]; + model.bindPose[i].rotation.y = ijoint[i].rotate[1]; + model.bindPose[i].rotation.z = ijoint[i].rotate[2]; + model.bindPose[i].rotation.w = ijoint[i].rotate[3]; + + model.bindPose[i].scale.x = ijoint[i].scale[0]; + model.bindPose[i].scale.y = ijoint[i].scale[1]; + model.bindPose[i].scale.z = ijoint[i].scale[2]; + } + + // Build bind pose from parent joints + for (int i = 0; i < model.boneCount; i++) + { + if (model.bones[i].parent >= 0) + { + model.bindPose[i].rotation = QuaternionMultiply(model.bindPose[model.bones[i].parent].rotation, model.bindPose[i].rotation); + model.bindPose[i].translation = Vector3RotateByQuaternion(model.bindPose[i].translation, model.bindPose[model.bones[i].parent].rotation); + model.bindPose[i].translation = Vector3Add(model.bindPose[i].translation, model.bindPose[model.bones[i].parent].translation); + model.bindPose[i].scale = Vector3MultiplyV(model.bindPose[i].scale, model.bindPose[model.bones[i].parent].scale); + } + } + + fclose(iqmFile); + free(imesh); + free(tri); + free(va); + free(vertex); + free(normal); + free(text); + free(blendi); + free(blendw); + free(ijoint); + + return model; } #endif #if defined(SUPPORT_FILEFORMAT_GLTF) -// Load IQM mesh data -static Mesh LoadIQM(const char *fileName) +// Load glTF mesh data +static Model LoadGLTF(const char *fileName) { - Mesh mesh = { 0 }; - - // TODO: Load IQM file - - return mesh; -} -#endif + Model model = { 0 }; -#if defined(SUPPORT_FILEFORMAT_GLTF) -// Load GLTF mesh data -static Mesh LoadGLTF(const char *fileName) -{ - Mesh mesh = { 0 }; - - // GLTF file loading + // glTF file loading FILE *gltfFile = fopen(fileName, "rb"); - + if (gltfFile == NULL) { - TraceLog(LOG_WARNING, "[%s] GLTF file could not be opened", fileName); - return mesh; + TraceLog(LOG_WARNING, "[%s] glTF file could not be opened", fileName); + return model; } fseek(gltfFile, 0, SEEK_END); @@ -2691,25 +3233,53 @@ static Mesh LoadGLTF(const char *fileName) void *buffer = malloc(size); fread(buffer, size, 1, gltfFile); - + fclose(gltfFile); - // GLTF data loading - cgltf_options options = {0}; - cgltf_data data; + // glTF data loading + cgltf_options options = { 0 }; + cgltf_data *data; cgltf_result result = cgltf_parse(&options, buffer, size, &data); + free(buffer); + if (result == cgltf_result_success) { - printf("Type: %u\n", data.file_type); - printf("Version: %d\n", data.version); - printf("Meshes: %lu\n", data.meshes_count); + TraceLog(LOG_INFO, "[%s][%s] Model meshes/materials: %i/%i", (data->file_type == 2)? "glb" : "gltf", data->meshes_count, data->materials_count); + + // Read data buffers + result = cgltf_load_buffers(&options, data, fileName); + + // Process glTF data and map to model + model.meshCount = data->meshes_count; + model.meshes = malloc(model.meshCount*sizeof(Mesh)); + + for (int i = 0; i < model.meshCount; i++) + { + // NOTE: Only support meshes defined by triangle primitives + //if (data->meshes[i].primitives[n].type == cgltf_primitive_type_triangles) + { + // data.meshes[i].name not used + model.meshes[i].vertexCount = data->meshes[i].primitives_count*3; + model.meshes[i].triangleCount = data->meshes[i].primitives_count; + // data.meshes[i].weights not used (array of weights to be applied to the Morph Targets) + + model.meshes[i].vertices = malloc(sizeof(float)*model.meshes[i].vertexCount*3); // Default vertex positions + model.meshes[i].normals = malloc(sizeof(float)*model.meshes[i].vertexCount*3); // Default vertex normals + model.meshes[i].texcoords = malloc(sizeof(float)*model.meshes[i].vertexCount*2); // Default vertex texcoords + + model.meshes[i].indices = malloc(sizeof(unsigned short)*model.meshes[i].triangleCount*3); + + } + } + + // NOTE: data.buffers[] should be loaded to model.meshes and data.images[] should be loaded to model.materials + // Use buffers[n].uri and images[n].uri... or use cgltf_load_buffers(&options, data, fileName); + + cgltf_free(data); } - else TraceLog(LOG_WARNING, "[%s] GLTF data could not be loaded", fileName); + else TraceLog(LOG_WARNING, "[%s] glTF data could not be loaded", fileName); - free(buffer); - cgltf_free(&data); - - return mesh; + return model; } #endif |
