summaryrefslogtreecommitdiffhomepage
path: root/src/models.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/models.c')
-rw-r--r--src/models.c1914
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