summaryrefslogtreecommitdiff
path: root/libs/raylib/src/models.c
diff options
context:
space:
mode:
Diffstat (limited to 'libs/raylib/src/models.c')
-rw-r--r--libs/raylib/src/models.c2946
1 files changed, 2030 insertions, 916 deletions
diff --git a/libs/raylib/src/models.c b/libs/raylib/src/models.c
index 69d7856..feaadf8 100644
--- a/libs/raylib/src/models.c
+++ b/libs/raylib/src/models.c
@@ -17,7 +17,7 @@
*
* LICENSE: zlib/libpng
*
-* Copyright (c) 2013-2020 Ramon Santamaria (@raysan5)
+* Copyright (c) 2013-2021 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.
@@ -43,11 +43,11 @@
#include "config.h" // Defines module configuration flags
#endif
-#include "utils.h" // Required for: fopen() Android mapping
+#include "utils.h" // Required for: LoadFileData(), LoadFileText(), SaveFileText()
+#include <stdio.h> // Required for: sprintf()
#include <stdlib.h> // Required for: malloc(), free()
-#include <stdio.h> // Required for: FILE, fopen(), fclose()
-#include <string.h> // Required for: strncmp() [Used in LoadModelAnimations()], strlen() [Used in LoadTextureFromCgltfImage()]
+#include <string.h> // Required for: memcmp(), strlen()
#include <math.h> // Required for: sinf(), cosf(), sqrtf(), fabsf()
#if defined(_WIN32)
@@ -92,7 +92,7 @@
//----------------------------------------------------------------------------------
// Defines and Macros
//----------------------------------------------------------------------------------
-#define MAX_MESH_VBO 7 // Maximum number of vbo per mesh
+// ...
//----------------------------------------------------------------------------------
// Types and Structures Definition
@@ -112,9 +112,16 @@ static Model LoadOBJ(const char *fileName); // Load OBJ mesh data
#endif
#if defined(SUPPORT_FILEFORMAT_IQM)
static Model LoadIQM(const char *fileName); // Load IQM mesh data
+static ModelAnimation *LoadIQMModelAnimations(const char *fileName, int *animCount); // Load IQM animation data
#endif
#if defined(SUPPORT_FILEFORMAT_GLTF)
static Model LoadGLTF(const char *fileName); // Load GLTF mesh data
+static ModelAnimation *LoadGLTFModelAnimations(const char *fileName, int *animCount); // Load GLTF animation data
+static void LoadGLTFModelIndices(Model* model, cgltf_accessor* indexAccessor, int primitiveIndex);
+static void BindGLTFPrimitiveToBones(Model* model, const cgltf_data* data, int primitiveIndex);
+static void LoadGLTFBoneAttribute(Model* model, cgltf_accessor* jointsAccessor, const cgltf_data* data, int primitiveIndex);
+static void LoadGLTFMaterial(Model* model, const char* fileName, const cgltf_data* data);
+static void InitGLTFBones(Model* model, const cgltf_data* data);
#endif
//----------------------------------------------------------------------------------
@@ -124,6 +131,11 @@ static Model LoadGLTF(const char *fileName); // Load GLTF mesh data
// Draw a line in 3D world space
void DrawLine3D(Vector3 startPos, Vector3 endPos, Color color)
{
+ // WARNING: Be careful with internal buffer vertex alignment
+ // when using RL_LINES or RL_TRIANGLES, data is aligned to fit
+ // lines-triangles-quads in the same indexed buffers!!!
+ rlCheckRenderBatchLimit(8);
+
rlBegin(RL_LINES);
rlColor4ub(color.r, color.g, color.b, color.a);
rlVertex3f(startPos.x, startPos.y, startPos.z);
@@ -134,14 +146,14 @@ void DrawLine3D(Vector3 startPos, Vector3 endPos, Color color)
// Draw a point in 3D space, actually a small line
void DrawPoint3D(Vector3 position, Color color)
{
- if (rlCheckBufferLimit(8)) rlglDraw();
+ rlCheckRenderBatchLimit(8);
rlPushMatrix();
rlTranslatef(position.x, position.y, position.z);
rlBegin(RL_LINES);
rlColor4ub(color.r, color.g, color.b, color.a);
- rlVertex3f(0.0,0.0,0.0);
- rlVertex3f(0.0,0.0,0.1);
+ rlVertex3f(0.0f, 0.0f, 0.0f);
+ rlVertex3f(0.0f, 0.0f, 0.1f);
rlEnd();
rlPopMatrix();
}
@@ -149,7 +161,7 @@ void DrawPoint3D(Vector3 position, 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();
+ rlCheckRenderBatchLimit(2*36);
rlPushMatrix();
rlTranslatef(center.x, center.y, center.z);
@@ -167,6 +179,48 @@ void DrawCircle3D(Vector3 center, float radius, Vector3 rotationAxis, float rota
rlPopMatrix();
}
+// Draw a color-filled triangle (vertex in counter-clockwise order!)
+void DrawTriangle3D(Vector3 v1, Vector3 v2, Vector3 v3, Color color)
+{
+ rlCheckRenderBatchLimit(3);
+
+ rlBegin(RL_TRIANGLES);
+ rlColor4ub(color.r, color.g, color.b, color.a);
+ rlVertex3f(v1.x, v1.y, v1.z);
+ rlVertex3f(v2.x, v2.y, v2.z);
+ rlVertex3f(v3.x, v3.y, v3.z);
+ rlEnd();
+}
+
+// Draw a triangle strip defined by points
+void DrawTriangleStrip3D(Vector3 *points, int pointsCount, Color color)
+{
+ if (pointsCount >= 3)
+ {
+ rlCheckRenderBatchLimit(3*(pointsCount - 2));
+
+ rlBegin(RL_TRIANGLES);
+ rlColor4ub(color.r, color.g, color.b, color.a);
+
+ for (int i = 2; i < pointsCount; i++)
+ {
+ if ((i%2) == 0)
+ {
+ rlVertex3f(points[i].x, points[i].y, points[i].z);
+ rlVertex3f(points[i - 2].x, points[i - 2].y, points[i - 2].z);
+ rlVertex3f(points[i - 1].x, points[i - 1].y, points[i - 1].z);
+ }
+ else
+ {
+ rlVertex3f(points[i].x, points[i].y, points[i].z);
+ rlVertex3f(points[i - 1].x, points[i - 1].y, points[i - 1].z);
+ rlVertex3f(points[i - 2].x, points[i - 2].y, points[i - 2].z);
+ }
+ }
+ rlEnd();
+ }
+}
+
// Draw cube
// NOTE: Cube position is the center position
void DrawCube(Vector3 position, float width, float height, float length, Color color)
@@ -175,7 +229,7 @@ void DrawCube(Vector3 position, float width, float height, float length, Color c
float y = 0.0f;
float z = 0.0f;
- if (rlCheckBufferLimit(36)) rlglDraw();
+ rlCheckRenderBatchLimit(36);
rlPushMatrix();
// NOTE: Transformation is applied in inverse order (scale -> rotate -> translate)
@@ -256,7 +310,7 @@ void DrawCubeWires(Vector3 position, float width, float height, float length, Co
float y = 0.0f;
float z = 0.0f;
- if (rlCheckBufferLimit(36)) rlglDraw();
+ rlCheckRenderBatchLimit(36);
rlPushMatrix();
rlTranslatef(position.x, position.y, position.z);
@@ -333,9 +387,9 @@ void DrawCubeTexture(Texture2D texture, Vector3 position, float width, float hei
float y = position.y;
float z = position.z;
- if (rlCheckBufferLimit(36)) rlglDraw();
+ rlCheckRenderBatchLimit(36);
- rlEnableTexture(texture.id);
+ rlSetTexture(texture.id);
//rlPushMatrix();
// NOTE: Transformation is applied in inverse order (scale -> rotate -> translate)
@@ -384,7 +438,7 @@ void DrawCubeTexture(Texture2D texture, Vector3 position, float width, float hei
rlEnd();
//rlPopMatrix();
- rlDisableTexture();
+ rlSetTexture(0);
}
// Draw sphere
@@ -397,7 +451,7 @@ void DrawSphere(Vector3 centerPos, float radius, Color color)
void DrawSphereEx(Vector3 centerPos, float radius, int rings, int slices, Color color)
{
int numVertex = (rings + 2)*slices*6;
- if (rlCheckBufferLimit(numVertex)) rlglDraw();
+ rlCheckRenderBatchLimit(numVertex);
rlPushMatrix();
// NOTE: Transformation is applied in inverse order (scale -> translate)
@@ -440,7 +494,7 @@ void DrawSphereEx(Vector3 centerPos, float radius, int rings, int slices, Color
void DrawSphereWires(Vector3 centerPos, float radius, int rings, int slices, Color color)
{
int numVertex = (rings + 2)*slices*6;
- if (rlCheckBufferLimit(numVertex)) rlglDraw();
+ rlCheckRenderBatchLimit(numVertex);
rlPushMatrix();
// NOTE: Transformation is applied in inverse order (scale -> translate)
@@ -487,7 +541,7 @@ void DrawCylinder(Vector3 position, float radiusTop, float radiusBottom, float h
if (sides < 3) sides = 3;
int numVertex = sides*6;
- if (rlCheckBufferLimit(numVertex)) rlglDraw();
+ rlCheckRenderBatchLimit(numVertex);
rlPushMatrix();
rlTranslatef(position.x, position.y, position.z);
@@ -546,7 +600,7 @@ void DrawCylinderWires(Vector3 position, float radiusTop, float radiusBottom, fl
if (sides < 3) sides = 3;
int numVertex = sides*8;
- if (rlCheckBufferLimit(numVertex)) rlglDraw();
+ rlCheckRenderBatchLimit(numVertex);
rlPushMatrix();
rlTranslatef(position.x, position.y, position.z);
@@ -575,7 +629,7 @@ void DrawCylinderWires(Vector3 position, float radiusTop, float radiusBottom, fl
// Draw a plane
void DrawPlane(Vector3 centerPos, Vector2 size, Color color)
{
- if (rlCheckBufferLimit(4)) rlglDraw();
+ rlCheckRenderBatchLimit(4);
// NOTE: Plane is always created on XZ ground
rlPushMatrix();
@@ -613,7 +667,7 @@ void DrawGrid(int slices, float spacing)
{
int halfSlices = slices/2;
- if (rlCheckBufferLimit(slices*4)) rlglDraw();
+ rlCheckRenderBatchLimit((slices + 2)*4);
rlBegin(RL_LINES);
for (int i = -halfSlices; i <= halfSlices; i++)
@@ -642,29 +696,6 @@ void DrawGrid(int slices, float spacing)
rlEnd();
}
-// Draw gizmo
-void DrawGizmo(Vector3 position)
-{
- // NOTE: RGB = XYZ
- float length = 1.0f;
-
- rlPushMatrix();
- rlTranslatef(position.x, position.y, position.z);
- rlScalef(length, length, length);
-
- rlBegin(RL_LINES);
- rlColor3f(1.0f, 0.0f, 0.0f); rlVertex3f(0.0f, 0.0f, 0.0f);
- rlColor3f(1.0f, 0.0f, 0.0f); rlVertex3f(1.0f, 0.0f, 0.0f);
-
- rlColor3f(0.0f, 1.0f, 0.0f); rlVertex3f(0.0f, 0.0f, 0.0f);
- rlColor3f(0.0f, 1.0f, 0.0f); rlVertex3f(0.0f, 1.0f, 0.0f);
-
- rlColor3f(0.0f, 0.0f, 1.0f); rlVertex3f(0.0f, 0.0f, 0.0f);
- rlColor3f(0.0f, 0.0f, 1.0f); rlVertex3f(0.0f, 0.0f, 1.0f);
- rlEnd();
- rlPopMatrix();
-}
-
// Load model from files (mesh and material)
Model LoadModel(const char *fileName)
{
@@ -677,7 +708,7 @@ Model LoadModel(const char *fileName)
if (IsFileExtension(fileName, ".iqm")) model = LoadIQM(fileName);
#endif
#if defined(SUPPORT_FILEFORMAT_GLTF)
- if (IsFileExtension(fileName, ".gltf") || IsFileExtension(fileName, ".glb")) model = LoadGLTF(fileName);
+ if (IsFileExtension(fileName, ".gltf;.glb")) model = LoadGLTF(fileName);
#endif
// Make sure model transform is set to identity matrix!
@@ -697,7 +728,7 @@ Model LoadModel(const char *fileName)
else
{
// Upload vertex data to GPU (static mesh)
- for (int i = 0; i < model.meshCount; i++) rlLoadMesh(&model.meshes[i], false);
+ for (int i = 0; i < model.meshCount; i++) UploadMesh(&model.meshes[i], false);
}
if (model.materialCount == 0)
@@ -738,16 +769,21 @@ Model LoadModelFromMesh(Mesh mesh)
return model;
}
-// Unload model from memory (RAM and/or VRAM)
+// Unload model (meshes/materials) from memory (RAM and/or VRAM)
+// NOTE: This function takes care of all model elements, for a detailed control
+// over them, use UnloadMesh() and UnloadMaterial()
void UnloadModel(Model model)
{
+ // Unload meshes
for (int i = 0; i < model.meshCount; i++) UnloadMesh(model.meshes[i]);
- // As the user could be sharing shaders and textures between models,
- // we don't unload the material but just free it's maps, the user
- // is responsible for freeing models shaders and textures
+ // Unload materials maps
+ // NOTE: As the user could be sharing shaders and textures between models,
+ // we don't unload the material but just free it's maps,
+ // the user is responsible for freeing models shaders and textures
for (int i = 0; i < model.materialCount; i++) RL_FREE(model.materials[i].maps);
+ // Unload arrays
RL_FREE(model.meshes);
RL_FREE(model.materials);
RL_FREE(model.meshMaterial);
@@ -756,84 +792,525 @@ void UnloadModel(Model model)
RL_FREE(model.bones);
RL_FREE(model.bindPose);
- TRACELOG(LOG_INFO, "MODEL: Unloaded model from RAM and VRAM");
+ TRACELOG(LOG_INFO, "MODEL: Unloaded model (and meshes) from RAM and VRAM");
}
-// Load meshes from model file
-Mesh *LoadMeshes(const char *fileName, int *meshCount)
+// Unload model (but not meshes) from memory (RAM and/or VRAM)
+void UnloadModelKeepMeshes(Model model)
{
- Mesh *meshes = NULL;
- int count = 0;
+ // Unload materials maps
+ // NOTE: As the user could be sharing shaders and textures between models,
+ // we don't unload the material but just free it's maps,
+ // the user is responsible for freeing models shaders and textures
+ for (int i = 0; i < model.materialCount; i++) RL_FREE(model.materials[i].maps);
+
+ // Unload arrays
+ RL_FREE(model.meshes);
+ RL_FREE(model.materials);
+ RL_FREE(model.meshMaterial);
+
+ // Unload animation data
+ RL_FREE(model.bones);
+ RL_FREE(model.bindPose);
+
+ TRACELOG(LOG_INFO, "MODEL: Unloaded model (but not meshes) from RAM and VRAM");
+}
+
+// Upload vertex data into a VAO (if supported) and VBO
+void UploadMesh(Mesh *mesh, bool dynamic)
+{
+ if (mesh->vaoId > 0)
+ {
+ // Check if mesh has already been loaded in GPU
+ TRACELOG(LOG_WARNING, "VAO: [ID %i] Trying to re-load an already loaded mesh", mesh->vaoId);
+ return;
+ }
+
+ mesh->vboId = (unsigned int *)RL_CALLOC(MAX_MESH_VERTEX_BUFFERS, sizeof(unsigned int));
+
+ mesh->vaoId = 0; // Vertex Array Object
+ mesh->vboId[0] = 0; // Vertex buffer: positions
+ mesh->vboId[1] = 0; // Vertex buffer: texcoords
+ mesh->vboId[2] = 0; // Vertex buffer: normals
+ mesh->vboId[3] = 0; // Vertex buffer: colors
+ mesh->vboId[4] = 0; // Vertex buffer: tangents
+ mesh->vboId[5] = 0; // Vertex buffer: texcoords2
+ mesh->vboId[6] = 0; // Vertex buffer: indices
+
+#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)
+ mesh->vaoId = rlLoadVertexArray();
+ rlEnableVertexArray(mesh->vaoId);
+
+ // NOTE: Attributes must be uploaded considering default locations points
+
+ // Enable vertex attributes: position (shader-location = 0)
+ mesh->vboId[0] = rlLoadVertexBuffer(mesh->vertices, mesh->vertexCount*3*sizeof(float), dynamic);
+ rlSetVertexAttribute(0, 3, RL_FLOAT, 0, 0, 0);
+ rlEnableVertexAttribute(0);
+
+ // Enable vertex attributes: texcoords (shader-location = 1)
+ mesh->vboId[1] = rlLoadVertexBuffer(mesh->texcoords, mesh->vertexCount*2*sizeof(float), dynamic);
+ rlSetVertexAttribute(1, 2, RL_FLOAT, 0, 0, 0);
+ rlEnableVertexAttribute(1);
+
+ if (mesh->normals != NULL)
+ {
+ // Enable vertex attributes: normals (shader-location = 2)
+ mesh->vboId[2] = rlLoadVertexBuffer(mesh->normals, mesh->vertexCount*3*sizeof(float), dynamic);
+ rlSetVertexAttribute(2, 3, RL_FLOAT, 0, 0, 0);
+ rlEnableVertexAttribute(2);
+ }
+ else
+ {
+ // Default color vertex attribute set to WHITE
+ float value[3] = { 1.0f, 1.0f, 1.0f };
+ rlSetVertexAttributeDefault(2, value, SHADER_ATTRIB_VEC3, 3);
+ rlDisableVertexAttribute(2);
+ }
+
+ if (mesh->colors != NULL)
+ {
+ // Enable vertex attribute: color (shader-location = 3)
+ mesh->vboId[3] = rlLoadVertexBuffer(mesh->colors, mesh->vertexCount*4*sizeof(unsigned char), dynamic);
+ rlSetVertexAttribute(3, 4, RL_UNSIGNED_BYTE, 1, 0, 0);
+ rlEnableVertexAttribute(3);
+ }
+ else
+ {
+ // Default color vertex attribute set to WHITE
+ float value[4] = { 1.0f, 1.0f, 1.0f, 1.0f };
+ rlSetVertexAttributeDefault(3, value, SHADER_ATTRIB_VEC4, 4);
+ rlDisableVertexAttribute(3);
+ }
+
+ if (mesh->tangents != NULL)
+ {
+ // Enable vertex attribute: tangent (shader-location = 4)
+ mesh->vboId[4] = rlLoadVertexBuffer(mesh->tangents, mesh->vertexCount*4*sizeof(float), dynamic);
+ rlSetVertexAttribute(4, 4, RL_FLOAT, 0, 0, 0);
+ rlEnableVertexAttribute(4);
+ }
+ else
+ {
+ // Default tangents vertex attribute
+ float value[4] = { 0.0f, 0.0f, 0.0f, 0.0f };
+ rlSetVertexAttributeDefault(4, value, SHADER_ATTRIB_VEC4, 4);
+ rlDisableVertexAttribute(4);
+ }
+
+ if (mesh->texcoords2 != NULL)
+ {
+ // Enable vertex attribute: texcoord2 (shader-location = 5)
+ mesh->vboId[5] = rlLoadVertexBuffer(mesh->texcoords2, mesh->vertexCount*2*sizeof(float), dynamic);
+ rlSetVertexAttribute(5, 2, RL_FLOAT, 0, 0, 0);
+ rlEnableVertexAttribute(5);
+ }
+ else
+ {
+ // Default texcoord2 vertex attribute
+ float value[2] = { 0.0f, 0.0f };
+ rlSetVertexAttributeDefault(5, value, SHADER_ATTRIB_VEC2, 2);
+ rlDisableVertexAttribute(5);
+ }
+
+ if (mesh->indices != NULL)
+ {
+ mesh->vboId[6] = rlLoadVertexBufferElement(mesh->indices, mesh->triangleCount*3*sizeof(unsigned short), dynamic);
+ }
+
+ if (mesh->vaoId > 0) TRACELOG(LOG_INFO, "VAO: [ID %i] Mesh uploaded successfully to VRAM (GPU)", mesh->vaoId);
+ else TRACELOG(LOG_INFO, "VBO: Mesh uploaded successfully to VRAM (GPU)");
+
+ rlDisableVertexArray();
+#endif
+}
+
+// Update mesh vertex data in GPU for a specific buffer index
+void UpdateMeshBuffer(Mesh mesh, int index, void *data, int dataSize, int offset)
+{
+ rlUpdateVertexBuffer(mesh.vboId[index], data, dataSize, offset);
+}
+
+// Draw a 3d mesh with material and transform
+void DrawMesh(Mesh mesh, Material material, Matrix transform)
+{
+ DrawMeshInstanced(mesh, material, &transform, 1);
+}
+
+// Draw multiple mesh instances with material and different transforms
+void DrawMeshInstanced(Mesh mesh, Material material, Matrix *transforms, int instances)
+{
+#if defined(GRAPHICS_API_OPENGL_11)
+ #define GL_VERTEX_ARRAY 0x8074
+ #define GL_NORMAL_ARRAY 0x8075
+ #define GL_COLOR_ARRAY 0x8076
+ #define GL_TEXTURE_COORD_ARRAY 0x8078
+
+ rlEnableTexture(material.maps[MATERIAL_MAP_DIFFUSE].texture.id);
+
+ rlEnableStatePointer(GL_VERTEX_ARRAY, mesh.vertices);
+ rlEnableStatePointer(GL_TEXTURE_COORD_ARRAY, mesh.texcoords);
+ rlEnableStatePointer(GL_NORMAL_ARRAY, mesh.normals);
+ rlEnableStatePointer(GL_COLOR_ARRAY, mesh.colors);
+
+ rlPushMatrix();
+ rlMultMatrixf(MatrixToFloat(transforms[0]));
+ rlColor4ub(material.maps[MATERIAL_MAP_DIFFUSE].color.r,
+ material.maps[MATERIAL_MAP_DIFFUSE].color.g,
+ material.maps[MATERIAL_MAP_DIFFUSE].color.b,
+ material.maps[MATERIAL_MAP_DIFFUSE].color.a);
+
+ if (mesh.indices != NULL) rlDrawVertexArrayElements(0, mesh.triangleCount*3, mesh.indices);
+ else rlDrawVertexArray(0, mesh.vertexCount);
+ rlPopMatrix();
+
+ rlDisableStatePointer(GL_VERTEX_ARRAY);
+ rlDisableStatePointer(GL_TEXTURE_COORD_ARRAY);
+ rlDisableStatePointer(GL_NORMAL_ARRAY);
+ rlDisableStatePointer(GL_COLOR_ARRAY);
+
+ rlDisableTexture();
+#endif
+
+#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)
+ // Check instancing
+ bool instancing = false;
+ if (instances < 1) return;
+ else if (instances > 1) instancing = true;
+ float16 *instanceTransforms = NULL;
+ unsigned int instancesVboId = 0;
+
+ // Bind shader program
+ rlEnableShader(material.shader.id);
+
+ // Send required data to shader (matrices, values)
+ //-----------------------------------------------------
+ // Upload to shader material.colDiffuse
+ if (material.shader.locs[SHADER_LOC_COLOR_DIFFUSE] != -1)
+ {
+ float values[4] = {
+ (float)material.maps[MATERIAL_MAP_DIFFUSE].color.r/255.0f,
+ (float)material.maps[MATERIAL_MAP_DIFFUSE].color.g/255.0f,
+ (float)material.maps[MATERIAL_MAP_DIFFUSE].color.b/255.0f,
+ (float)material.maps[MATERIAL_MAP_DIFFUSE].color.a/255.0f
+ };
+
+ rlSetUniform(material.shader.locs[SHADER_LOC_COLOR_DIFFUSE], values, SHADER_UNIFORM_VEC4, 1);
+ }
+
+ // Upload to shader material.colSpecular (if location available)
+ if (material.shader.locs[SHADER_LOC_COLOR_SPECULAR] != -1)
+ {
+ float values[4] = {
+ (float)material.maps[SHADER_LOC_COLOR_SPECULAR].color.r/255.0f,
+ (float)material.maps[SHADER_LOC_COLOR_SPECULAR].color.g/255.0f,
+ (float)material.maps[SHADER_LOC_COLOR_SPECULAR].color.b/255.0f,
+ (float)material.maps[SHADER_LOC_COLOR_SPECULAR].color.a/255.0f
+ };
+
+ rlSetUniform(material.shader.locs[SHADER_LOC_COLOR_SPECULAR], values, SHADER_UNIFORM_VEC4, 1);
+ }
+
+ // Get a copy of current matrices to work with,
+ // just in case stereo render is required and we need to modify them
+ // NOTE: At this point the modelview matrix just contains the view matrix (camera)
+ // That's because BeginMode3D() sets it and there is no model-drawing function
+ // that modifies it, all use rlPushMatrix() and rlPopMatrix()
+ Matrix matView = rlGetMatrixModelview();
+ Matrix matModelView = matView;
+ Matrix matProjection = rlGetMatrixProjection();
+
+ // Upload view and projection matrices (if locations available)
+ if (material.shader.locs[SHADER_LOC_MATRIX_VIEW] != -1) rlSetUniformMatrix(material.shader.locs[SHADER_LOC_MATRIX_VIEW], matView);
+ if (material.shader.locs[SHADER_LOC_MATRIX_PROJECTION] != -1) rlSetUniformMatrix(material.shader.locs[SHADER_LOC_MATRIX_PROJECTION], matProjection);
+
+ if (instancing)
+ {
+ // Create instances buffer
+ instanceTransforms = RL_MALLOC(instances*sizeof(float16));
+
+ // Fill buffer with instances transformations as float16 arrays
+ for (int i = 0; i < instances; i++) instanceTransforms[i] = MatrixToFloatV(transforms[i]);
+
+ // Enable mesh VAO to attach new buffer
+ rlEnableVertexArray(mesh.vaoId);
+
+ // This could alternatively use a static VBO and either glMapBuffer() or glBufferSubData().
+ // It isn't clear which would be reliably faster in all cases and on all platforms,
+ // anecdotally glMapBuffer() seems very slow (syncs) while glBufferSubData() seems
+ // no faster, since we're transferring all the transform matrices anyway
+ instancesVboId = rlLoadVertexBuffer(instanceTransforms, instances*sizeof(float16), false);
+
+ // Instances transformation matrices are send to shader attribute location: SHADER_LOC_MATRIX_MODEL
+ for (unsigned int i = 0; i < 4; i++)
+ {
+ rlEnableVertexAttribute(material.shader.locs[SHADER_LOC_MATRIX_MODEL] + i);
+ rlSetVertexAttribute(material.shader.locs[SHADER_LOC_MATRIX_MODEL] + i, 4, RL_FLOAT, 0, sizeof(Matrix), (void *)(i*sizeof(Vector4)));
+ rlSetVertexAttributeDivisor(material.shader.locs[SHADER_LOC_MATRIX_MODEL] + i, 1);
+ }
+
+ rlDisableVertexBuffer();
+ rlDisableVertexArray();
+
+ // Accumulate internal matrix transform (push/pop) and view matrix
+ // NOTE: In this case, model instance transformation must be computed in the shader
+ matModelView = MatrixMultiply(rlGetMatrixTransform(), matView);
+ }
+ else
+ {
+ // Model transformation matrix is send to shader uniform location: SHADER_LOC_MATRIX_MODEL
+ if (material.shader.locs[SHADER_LOC_MATRIX_MODEL] != -1) rlSetUniformMatrix(material.shader.locs[SHADER_LOC_MATRIX_MODEL], transforms[0]);
+
+ // Accumulate several transformations:
+ // matView: rlgl internal modelview matrix (actually, just view matrix)
+ // rlGetMatrixTransform(): rlgl internal transform matrix due to push/pop matrix stack
+ // transform: function parameter transformation
+ matModelView = MatrixMultiply(transforms[0], MatrixMultiply(rlGetMatrixTransform(), matView));
+ }
+
+ // Upload model normal matrix (if locations available)
+ if (material.shader.locs[SHADER_LOC_MATRIX_NORMAL] != -1) rlSetUniformMatrix(material.shader.locs[SHADER_LOC_MATRIX_NORMAL], MatrixTranspose(MatrixInvert(matModelView)));
+ //-----------------------------------------------------
+
+ // Bind active texture maps (if available)
+ for (int i = 0; i < MAX_MATERIAL_MAPS; i++)
+ {
+ if (material.maps[i].texture.id > 0)
+ {
+ // Select current shader texture slot
+ rlActiveTextureSlot(i);
+
+ // Enable texture for active slot
+ if ((i == MATERIAL_MAP_IRRADIANCE) ||
+ (i == MATERIAL_MAP_PREFILTER) ||
+ (i == MATERIAL_MAP_CUBEMAP)) rlEnableTextureCubemap(material.maps[i].texture.id);
+ else rlEnableTexture(material.maps[i].texture.id);
+
+ rlSetUniform(material.shader.locs[SHADER_LOC_MAP_DIFFUSE + i], &i, SHADER_UNIFORM_INT, 1);
+ }
+ }
+
+ // Try binding vertex array objects (VAO)
+ // or use VBOs if not possible
+ if (!rlEnableVertexArray(mesh.vaoId))
+ {
+ // Bind mesh VBO data: vertex position (shader-location = 0)
+ rlEnableVertexBuffer(mesh.vboId[0]);
+ rlSetVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_POSITION], 3, RL_FLOAT, 0, 0, 0);
+ rlEnableVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_POSITION]);
+
+ rlEnableVertexBuffer(mesh.vboId[0]);
+ rlSetVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_POSITION], 3, RL_FLOAT, 0, 0, 0);
+ rlEnableVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_POSITION]);
- // TODO: Load meshes from file (OBJ, IQM, GLTF)
+ // Bind mesh VBO data: vertex texcoords (shader-location = 1)
+ rlEnableVertexBuffer(mesh.vboId[1]);
+ rlSetVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_TEXCOORD01], 2, RL_FLOAT, 0, 0, 0);
+ rlEnableVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_TEXCOORD01]);
- *meshCount = count;
- return meshes;
+ if (material.shader.locs[SHADER_LOC_VERTEX_NORMAL] != -1)
+ {
+ // Bind mesh VBO data: vertex normals (shader-location = 2)
+ rlEnableVertexBuffer(mesh.vboId[2]);
+ rlSetVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_NORMAL], 3, RL_FLOAT, 0, 0, 0);
+ rlEnableVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_NORMAL]);
+ }
+
+ // Bind mesh VBO data: vertex colors (shader-location = 3, if available)
+ if (material.shader.locs[SHADER_LOC_VERTEX_COLOR] != -1)
+ {
+ if (mesh.vboId[3] != 0)
+ {
+ rlEnableVertexBuffer(mesh.vboId[3]);
+ rlSetVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_COLOR], 4, RL_UNSIGNED_BYTE, 1, 0, 0);
+ rlEnableVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_COLOR]);
+ }
+ else
+ {
+ // Set default value for unused attribute
+ // NOTE: Required when using default shader and no VAO support
+ float value[4] = { 1.0f, 1.0f, 1.0f, 1.0f };
+ rlSetVertexAttributeDefault(material.shader.locs[SHADER_LOC_VERTEX_COLOR], value, SHADER_ATTRIB_VEC2, 4);
+ rlDisableVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_COLOR]);
+ }
+ }
+
+ // Bind mesh VBO data: vertex tangents (shader-location = 4, if available)
+ if (material.shader.locs[SHADER_LOC_VERTEX_TANGENT] != -1)
+ {
+ rlEnableVertexBuffer(mesh.vboId[4]);
+ rlSetVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_TANGENT], 4, RL_FLOAT, 0, 0, 0);
+ rlEnableVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_TANGENT]);
+ }
+
+ // Bind mesh VBO data: vertex texcoords2 (shader-location = 5, if available)
+ if (material.shader.locs[SHADER_LOC_VERTEX_TEXCOORD02] != -1)
+ {
+ rlEnableVertexBuffer(mesh.vboId[5]);
+ rlSetVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_TEXCOORD02], 2, RL_FLOAT, 0, 0, 0);
+ rlEnableVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_TEXCOORD02]);
+ }
+
+ if (mesh.indices != NULL) rlEnableVertexBufferElement(mesh.vboId[6]);
+ }
+
+ int eyesCount = 1;
+ if (rlIsStereoRenderEnabled()) eyesCount = 2;
+
+ for (int eye = 0; eye < eyesCount; eye++)
+ {
+ // Calculate model-view-projection matrix (MVP)
+ Matrix matMVP = MatrixIdentity();
+ if (eyesCount == 1) matMVP = MatrixMultiply(matModelView, matProjection);
+ else
+ {
+ // Setup current eye viewport (half screen width)
+ rlViewport(eye*rlGetFramebufferWidth()/2, 0, rlGetFramebufferWidth()/2, rlGetFramebufferHeight());
+ matMVP = MatrixMultiply(MatrixMultiply(matModelView, rlGetMatrixViewOffsetStereo(eye)), rlGetMatrixProjectionStereo(eye));
+ }
+
+ // Send combined model-view-projection matrix to shader
+ rlSetUniformMatrix(material.shader.locs[SHADER_LOC_MATRIX_MVP], matMVP);
+
+ if (instancing) // Draw mesh instanced
+ {
+ if (mesh.indices != NULL) rlDrawVertexArrayElementsInstanced(0, mesh.triangleCount*3, 0, instances);
+ else rlDrawVertexArrayInstanced(0, mesh.vertexCount, instances);
+ }
+ else // Draw mesh
+ {
+ if (mesh.indices != NULL) rlDrawVertexArrayElements(0, mesh.triangleCount*3, 0);
+ else rlDrawVertexArray(0, mesh.vertexCount);
+ }
+ }
+
+ // Unbind all binded texture maps
+ for (int i = 0; i < MAX_MATERIAL_MAPS; i++)
+ {
+ // Select current shader texture slot
+ rlActiveTextureSlot(i);
+
+ // Disable texture for active slot
+ if ((i == MATERIAL_MAP_IRRADIANCE) ||
+ (i == MATERIAL_MAP_PREFILTER) ||
+ (i == MATERIAL_MAP_CUBEMAP)) rlDisableTextureCubemap();
+ else rlDisableTexture();
+ }
+
+ // Disable all possible vertex array objects (or VBOs)
+ rlDisableVertexArray();
+ rlDisableVertexBuffer();
+ rlDisableVertexBufferElement();
+
+ // Disable shader program
+ rlDisableShader();
+
+ if (instancing)
+ {
+ // Remove instance transforms buffer
+ rlUnloadVertexBuffer(instancesVboId);
+ RL_FREE(instanceTransforms);
+ }
+ else
+ {
+ // Restore rlgl internal modelview and projection matrices
+ rlSetMatrixModelview(matView);
+ rlSetMatrixProjection(matProjection);
+ }
+#endif
}
-// Unload mesh from memory (RAM and/or VRAM)
+// Unload mesh from memory (RAM and VRAM)
void UnloadMesh(Mesh mesh)
{
- rlUnloadMesh(mesh);
+ // Unload rlgl mesh vboId data
+ rlUnloadVertexArray(mesh.vaoId);
+
+ for (int i = 0; i < MAX_MESH_VERTEX_BUFFERS; i++) rlUnloadVertexBuffer(mesh.vboId[i]);
RL_FREE(mesh.vboId);
+
+ RL_FREE(mesh.vertices);
+ RL_FREE(mesh.texcoords);
+ RL_FREE(mesh.normals);
+ RL_FREE(mesh.colors);
+ RL_FREE(mesh.tangents);
+ RL_FREE(mesh.texcoords2);
+ RL_FREE(mesh.indices);
+
+ RL_FREE(mesh.animVertices);
+ RL_FREE(mesh.animNormals);
+ RL_FREE(mesh.boneWeights);
+ RL_FREE(mesh.boneIds);
}
// Export mesh data to file
-void ExportMesh(Mesh mesh, const char *fileName)
+bool ExportMesh(Mesh mesh, const char *fileName)
{
bool success = false;
if (IsFileExtension(fileName, ".obj"))
{
- FILE *objFile = fopen(fileName, "wt");
-
- 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");
+ // Estimated data size, it should be enough...
+ int dataSize = mesh.vertexCount/3* (int)strlen("v 0000.00f 0000.00f 0000.00f") +
+ mesh.vertexCount/2* (int)strlen("vt 0.000f 0.00f") +
+ mesh.vertexCount/3* (int)strlen("vn 0.000f 0.00f 0.00f") +
+ mesh.triangleCount/3* (int)strlen("f 00000/00000/00000 00000/00000/00000 00000/00000/00000");
+
+ // NOTE: Text data buffer size is estimated considering mesh data size
+ char *txtData = (char *)RL_CALLOC(dataSize + 2000, sizeof(char));
+
+ int bytesCount = 0;
+ bytesCount += sprintf(txtData + bytesCount, "# //////////////////////////////////////////////////////////////////////////////////\n");
+ bytesCount += sprintf(txtData + bytesCount, "# // //\n");
+ bytesCount += sprintf(txtData + bytesCount, "# // rMeshOBJ exporter v1.0 - Mesh exported as triangle faces and not optimized //\n");
+ bytesCount += sprintf(txtData + bytesCount, "# // //\n");
+ bytesCount += sprintf(txtData + bytesCount, "# // more info and bugs-report: github.com/raysan5/raylib //\n");
+ bytesCount += sprintf(txtData + bytesCount, "# // feedback and support: ray[at]raylib.com //\n");
+ bytesCount += sprintf(txtData + bytesCount, "# // //\n");
+ bytesCount += sprintf(txtData + bytesCount, "# // Copyright (c) 2018 Ramon Santamaria (@raysan5) //\n");
+ bytesCount += sprintf(txtData + bytesCount, "# // //\n");
+ bytesCount += sprintf(txtData + bytesCount, "# //////////////////////////////////////////////////////////////////////////////////\n\n");
+ bytesCount += sprintf(txtData + bytesCount, "# Vertex Count: %i\n", mesh.vertexCount);
+ bytesCount += sprintf(txtData + bytesCount, "# Triangle Count: %i\n\n", mesh.triangleCount);
+
+ bytesCount += sprintf(txtData + bytesCount, "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]);
+ bytesCount += sprintf(txtData + bytesCount, "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]);
+ bytesCount += sprintf(txtData + bytesCount, "vt %.3f %.3f\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]);
+ bytesCount += sprintf(txtData + bytesCount, "vn %.3f %.3f %.3f\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);
+ bytesCount += sprintf(txtData + bytesCount, "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");
+ bytesCount += sprintf(txtData + bytesCount, "\n");
- fclose(objFile);
+ // NOTE: Text data length exported is determined by '\0' (NULL) character
+ success = SaveFileText(fileName, txtData);
- success = true;
+ RL_FREE(txtData);
+ }
+ else if (IsFileExtension(fileName, ".raw"))
+ {
+ // TODO: Support additional file formats to export mesh vertex data
}
- else if (IsFileExtension(fileName, ".raw")) { } // TODO: Support additional file formats to export mesh vertex data
- if (success) TRACELOG(LOG_INFO, "FILEIO: [%s] Mesh exported successfully", fileName);
- else TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to export mesh data", fileName);
+ return success;
}
+
// Load materials from model file
Material *LoadMaterials(const char *fileName, int *materialCount)
{
@@ -845,12 +1322,10 @@ Material *LoadMaterials(const char *fileName, int *materialCount)
#if defined(SUPPORT_FILEFORMAT_MTL)
if (IsFileExtension(fileName, ".mtl"))
{
- tinyobj_material_t *mats;
+ tinyobj_material_t *mats = NULL;
int result = tinyobj_parse_mtl_file(&mats, &count, fileName);
- if (result != TINYOBJ_SUCCESS) {
- TRACELOG(LOG_WARNING, "MATERIAL: [%s] Failed to parse materials file", fileName);
- }
+ if (result != TINYOBJ_SUCCESS) TRACELOG(LOG_WARNING, "MATERIAL: [%s] Failed to parse materials file", fileName);
// TODO: Process materials to return
@@ -861,7 +1336,10 @@ Material *LoadMaterials(const char *fileName, int *materialCount)
#endif
// Set materials shader to default (DIFFUSE, SPECULAR, NORMAL)
- for (int i = 0; i < count; i++) materials[i].shader = GetShaderDefault();
+ if (materials != NULL)
+ {
+ for (unsigned int i = 0; i < count; i++) materials[i].shader = rlGetShaderDefault();
+ }
*materialCount = count;
return materials;
@@ -873,13 +1351,13 @@ Material LoadMaterialDefault(void)
Material material = { 0 };
material.maps = (MaterialMap *)RL_CALLOC(MAX_MATERIAL_MAPS, sizeof(MaterialMap));
- 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.shader = rlGetShaderDefault();
+ material.maps[MATERIAL_MAP_DIFFUSE].texture = rlGetTextureDefault(); // White texture (1x1 pixel)
+ //material.maps[MATERIAL_MAP_NORMAL].texture; // NOTE: By default, not set
+ //material.maps[MATERIAL_MAP_SPECULAR].texture; // NOTE: By default, not set
- material.maps[MAP_DIFFUSE].color = WHITE; // Diffuse color
- material.maps[MAP_SPECULAR].color = WHITE; // Specular color
+ material.maps[MATERIAL_MAP_DIFFUSE].color = WHITE; // Diffuse color
+ material.maps[MATERIAL_MAP_SPECULAR].color = WHITE; // Specular color
return material;
}
@@ -888,18 +1366,18 @@ Material LoadMaterialDefault(void)
void UnloadMaterial(Material material)
{
// Unload material shader (avoid unloading default shader, managed by raylib)
- if (material.shader.id != GetShaderDefault().id) UnloadShader(material.shader);
+ if (material.shader.id != rlGetShaderDefault().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);
+ if (material.maps[i].texture.id != rlGetTextureDefault().id) rlUnloadTexture(material.maps[i].texture.id);
}
RL_FREE(material.maps);
}
-// Set texture for a material map type (MAP_DIFFUSE, MAP_SPECULAR...)
+// Set texture for a material map type (MATERIAL_MAP_DIFFUSE, MATERIAL_MAP_SPECULAR...)
// NOTE: Previous texture should be manually unloaded
void SetMaterialTexture(Material *material, int mapType, Texture2D texture)
{
@@ -915,214 +1393,16 @@ void SetModelMeshMaterial(Model *model, int meshId, int materialId)
}
// Load model animations from file
-ModelAnimation *LoadModelAnimations(const char *filename, int *animCount)
+ModelAnimation *LoadModelAnimations(const char *fileName, int *animCount)
{
- #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;
-
- FILE *iqmFile = NULL;
- IQMHeader iqm;
-
- iqmFile = fopen(filename,"rb");
-
- if (!iqmFile)
- {
- TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to open file", filename);
- return NULL;
- }
-
- // Read IQM header
- fread(&iqm, sizeof(IQMHeader), 1, iqmFile);
-
- if (strncmp(iqm.magic, IQM_MAGIC, sizeof(IQM_MAGIC)))
- {
- TRACELOG(LOG_WARNING, "MODEL: [%s] IQM file is not a valid model", filename);
- fclose(iqmFile);
- return NULL;
- }
-
- if (iqm.version != IQM_VERSION)
- {
- TRACELOG(LOG_WARNING, "MODEL: [%s] IQM file version incorrect", filename);
- fclose(iqmFile);
- return NULL;
- }
-
- // Get bones data
- IQMPose *poses = RL_MALLOC(iqm.num_poses*sizeof(IQMPose));
- fseek(iqmFile, iqm.ofs_poses, SEEK_SET);
- fread(poses, iqm.num_poses*sizeof(IQMPose), 1, iqmFile);
-
- // Get animations data
- *animCount = iqm.num_anims;
- IQMAnim *anim = RL_MALLOC(iqm.num_anims*sizeof(IQMAnim));
- fseek(iqmFile, iqm.ofs_anims, SEEK_SET);
- fread(anim, iqm.num_anims*sizeof(IQMAnim), 1, iqmFile);
- ModelAnimation *animations = RL_MALLOC(iqm.num_anims*sizeof(ModelAnimation));
-
- // frameposes
- unsigned short *framedata = RL_MALLOC(iqm.num_frames*iqm.num_framechannels*sizeof(unsigned short));
- fseek(iqmFile, iqm.ofs_frames, SEEK_SET);
- fread(framedata, iqm.num_frames*iqm.num_framechannels*sizeof(unsigned short), 1, iqmFile);
-
- for (int a = 0; a < iqm.num_anims; a++)
- {
- animations[a].frameCount = anim[a].num_frames;
- animations[a].boneCount = iqm.num_poses;
- animations[a].bones = RL_MALLOC(iqm.num_poses*sizeof(BoneInfo));
- animations[a].framePoses = RL_MALLOC(anim[a].num_frames*sizeof(Transform *));
- //animations[a].framerate = anim.framerate; // TODO: Use framerate?
-
- for (int j = 0; j < iqm.num_poses; j++)
- {
- strcpy(animations[a].bones[j].name, "ANIMJOINTNAME");
- animations[a].bones[j].parent = poses[j].parent;
- }
-
- for (int j = 0; j < anim[a].num_frames; j++) animations[a].framePoses[j] = RL_MALLOC(iqm.num_poses*sizeof(Transform));
-
- int dcounter = anim[a].first_frame*iqm.num_framechannels;
-
- for (int frame = 0; frame < anim[a].num_frames; frame++)
- {
- for (int i = 0; i < iqm.num_poses; i++)
- {
- animations[a].framePoses[frame][i].translation.x = poses[i].channeloffset[0];
-
- if (poses[i].mask & 0x01)
- {
- animations[a].framePoses[frame][i].translation.x += framedata[dcounter]*poses[i].channelscale[0];
- dcounter++;
- }
-
- animations[a].framePoses[frame][i].translation.y = poses[i].channeloffset[1];
-
- if (poses[i].mask & 0x02)
- {
- animations[a].framePoses[frame][i].translation.y += framedata[dcounter]*poses[i].channelscale[1];
- dcounter++;
- }
-
- animations[a].framePoses[frame][i].translation.z = poses[i].channeloffset[2];
+ ModelAnimation *animations = NULL;
- if (poses[i].mask & 0x04)
- {
- animations[a].framePoses[frame][i].translation.z += framedata[dcounter]*poses[i].channelscale[2];
- dcounter++;
- }
-
- animations[a].framePoses[frame][i].rotation.x = poses[i].channeloffset[3];
-
- if (poses[i].mask & 0x08)
- {
- animations[a].framePoses[frame][i].rotation.x += framedata[dcounter]*poses[i].channelscale[3];
- dcounter++;
- }
-
- animations[a].framePoses[frame][i].rotation.y = poses[i].channeloffset[4];
-
- if (poses[i].mask & 0x10)
- {
- animations[a].framePoses[frame][i].rotation.y += framedata[dcounter]*poses[i].channelscale[4];
- dcounter++;
- }
-
- animations[a].framePoses[frame][i].rotation.z = poses[i].channeloffset[5];
-
- if (poses[i].mask & 0x20)
- {
- animations[a].framePoses[frame][i].rotation.z += framedata[dcounter]*poses[i].channelscale[5];
- dcounter++;
- }
-
- animations[a].framePoses[frame][i].rotation.w = poses[i].channeloffset[6];
-
- if (poses[i].mask & 0x40)
- {
- animations[a].framePoses[frame][i].rotation.w += framedata[dcounter]*poses[i].channelscale[6];
- dcounter++;
- }
-
- animations[a].framePoses[frame][i].scale.x = poses[i].channeloffset[7];
-
- if (poses[i].mask & 0x80)
- {
- animations[a].framePoses[frame][i].scale.x += framedata[dcounter]*poses[i].channelscale[7];
- dcounter++;
- }
-
- animations[a].framePoses[frame][i].scale.y = poses[i].channeloffset[8];
-
- if (poses[i].mask & 0x100)
- {
- animations[a].framePoses[frame][i].scale.y += framedata[dcounter]*poses[i].channelscale[8];
- dcounter++;
- }
-
- animations[a].framePoses[frame][i].scale.z = poses[i].channeloffset[9];
-
- if (poses[i].mask & 0x200)
- {
- animations[a].framePoses[frame][i].scale.z += framedata[dcounter]*poses[i].channelscale[9];
- dcounter++;
- }
-
- animations[a].framePoses[frame][i].rotation = QuaternionNormalize(animations[a].framePoses[frame][i].rotation);
- }
- }
-
- // Build frameposes
- for (int frame = 0; frame < anim[a].num_frames; frame++)
- {
- for (int i = 0; i < animations[a].boneCount; i++)
- {
- if (animations[a].bones[i].parent >= 0)
- {
- animations[a].framePoses[frame][i].rotation = QuaternionMultiply(animations[a].framePoses[frame][animations[a].bones[i].parent].rotation, animations[a].framePoses[frame][i].rotation);
- animations[a].framePoses[frame][i].translation = Vector3RotateByQuaternion(animations[a].framePoses[frame][i].translation, animations[a].framePoses[frame][animations[a].bones[i].parent].rotation);
- animations[a].framePoses[frame][i].translation = Vector3Add(animations[a].framePoses[frame][i].translation, animations[a].framePoses[frame][animations[a].bones[i].parent].translation);
- animations[a].framePoses[frame][i].scale = Vector3Multiply(animations[a].framePoses[frame][i].scale, animations[a].framePoses[frame][animations[a].bones[i].parent].scale);
- }
- }
- }
- }
-
- RL_FREE(framedata);
- RL_FREE(poses);
- RL_FREE(anim);
-
- fclose(iqmFile);
+#if defined(SUPPORT_FILEFORMAT_IQM)
+ if (IsFileExtension(fileName, ".iqm")) animations = LoadIQMModelAnimations(fileName, animCount);
+#endif
+#if defined(SUPPORT_FILEFORMAT_GLTF)
+ if (IsFileExtension(fileName, ".gltf;.glb")) animations = LoadGLTFModelAnimations(fileName, animCount);
+#endif
return animations;
}
@@ -1151,47 +1431,69 @@ void UpdateModelAnimation(Model model, ModelAnimation anim, int frame)
int vCounter = 0;
int boneCounter = 0;
int boneId = 0;
+ float boneWeight = 0.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 = Vector3Multiply(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;
+ model.meshes[m].animVertices[vCounter] = 0;
+ model.meshes[m].animVertices[vCounter + 1] = 0;
+ model.meshes[m].animVertices[vCounter + 2] = 0;
+
+ model.meshes[m].animNormals[vCounter] = 0;
+ model.meshes[m].animNormals[vCounter + 1] = 0;
+ model.meshes[m].animNormals[vCounter + 2] = 0;
- boneCounter += 4;
+ for (int j = 0; j < 4; j++)
+ {
+ boneId = model.meshes[m].boneIds[boneCounter];
+ boneWeight = model.meshes[m].boneWeights[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 = Vector3Multiply(animVertex, outScale);
+ animVertex = Vector3Subtract(animVertex, inTranslation);
+ animVertex = Vector3RotateByQuaternion(animVertex, QuaternionMultiply(outRotation, QuaternionInvert(inRotation)));
+ animVertex = Vector3Add(animVertex, outTranslation);
+ model.meshes[m].animVertices[vCounter] += animVertex.x * boneWeight;
+ model.meshes[m].animVertices[vCounter + 1] += animVertex.y * boneWeight;
+ model.meshes[m].animVertices[vCounter + 2] += animVertex.z * boneWeight;
+
+ // Normals processing
+ // NOTE: We use meshes.baseNormals (default normal) to calculate meshes.normals (animated normals)
+ if (model.meshes[m].normals != NULL)
+ {
+ animNormal = (Vector3){ model.meshes[m].normals[vCounter], model.meshes[m].normals[vCounter + 1], model.meshes[m].normals[vCounter + 2] };
+ animNormal = Vector3RotateByQuaternion(animNormal, QuaternionMultiply(outRotation, QuaternionInvert(inRotation)));
+ model.meshes[m].animNormals[vCounter] += animNormal.x * boneWeight;
+ model.meshes[m].animNormals[vCounter + 1] += animNormal.y * boneWeight;
+ model.meshes[m].animNormals[vCounter + 2] += animNormal.z * boneWeight;
+ }
+ boneCounter += 1;
+ }
+ vCounter += 3;
}
// 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].animNormals, model.meshes[m].vertexCount*3*sizeof(float)); // Update vertex normals
+ rlUpdateVertexBuffer(model.meshes[m].vboId[0], model.meshes[m].animVertices, model.meshes[m].vertexCount*3*sizeof(float), 0); // Update vertex position
+ rlUpdateVertexBuffer(model.meshes[m].vboId[2], model.meshes[m].animNormals, model.meshes[m].vertexCount*3*sizeof(float), 0); // Update vertex normals
}
}
}
+// Unload animation array data
+void UnloadModelAnimations(ModelAnimation* animations, unsigned int count)
+{
+ for (unsigned int i = 0; i < count; i++) UnloadModelAnimation(animations[i]);
+ RL_FREE(animations);
+}
+
// Unload animation data
void UnloadModelAnimation(ModelAnimation anim)
{
@@ -1224,16 +1526,21 @@ bool IsModelAnimationValid(Model model, ModelAnimation anim)
Mesh GenMeshPoly(int sides, float radius)
{
Mesh mesh = { 0 };
- mesh.vboId = (unsigned int *)RL_CALLOC(MAX_MESH_VBO, sizeof(unsigned int));
+
+ if (sides < 3) return mesh;
+
int vertexCount = sides*3;
// Vertices definition
Vector3 *vertices = (Vector3 *)RL_MALLOC(vertexCount*sizeof(Vector3));
- for (int i = 0, v = 0; i < 360; i += 360/sides, v += 3)
+
+ float d = 0.0f, dStep = 360.0f/sides;
+ for (int v = 0; v < vertexCount; 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 };
+ vertices[v + 1] = (Vector3){ sinf(DEG2RAD*d)*radius, 0.0f, cosf(DEG2RAD*d)*radius };
+ vertices[v + 2] = (Vector3){sinf(DEG2RAD*(d+dStep))*radius, 0.0f, cosf(DEG2RAD*(d+dStep))*radius };
+ d += dStep;
}
// Normals definition
@@ -1278,7 +1585,8 @@ Mesh GenMeshPoly(int sides, float radius)
RL_FREE(texcoords);
// Upload vertex data to GPU (static mesh)
- rlLoadMesh(&mesh, false);
+ // NOTE: mesh.vboId array is allocated inside UploadMesh()
+ UploadMesh(&mesh, false);
return mesh;
}
@@ -1287,7 +1595,6 @@ Mesh GenMeshPoly(int sides, float radius)
Mesh GenMeshPlane(float width, float length, int resX, int resZ)
{
Mesh mesh = { 0 };
- mesh.vboId = (unsigned int *)RL_CALLOC(MAX_MESH_VBO, sizeof(unsigned int));
#define CUSTOM_MESH_GEN_PLANE
#if defined(CUSTOM_MESH_GEN_PLANE)
@@ -1390,7 +1697,6 @@ Mesh GenMeshPlane(float width, float length, int resX, int resZ)
mesh.vertices = (float *)RL_MALLOC(plane->ntriangles*3*3*sizeof(float));
mesh.texcoords = (float *)RL_MALLOC(plane->ntriangles*3*2*sizeof(float));
mesh.normals = (float *)RL_MALLOC(plane->ntriangles*3*3*sizeof(float));
- mesh.vboId = (unsigned int *)RL_CALLOC(MAX_MESH_VBO, sizeof(unsigned int));
mesh.vertexCount = plane->ntriangles*3;
mesh.triangleCount = plane->ntriangles;
@@ -1413,7 +1719,7 @@ Mesh GenMeshPlane(float width, float length, int resX, int resZ)
#endif
// Upload vertex data to GPU (static mesh)
- rlLoadMesh(&mesh, false);
+ UploadMesh(&mesh, false);
return mesh;
}
@@ -1422,7 +1728,6 @@ Mesh GenMeshPlane(float width, float length, int resX, int resZ)
Mesh GenMeshCube(float width, float height, float length)
{
Mesh mesh = { 0 };
- mesh.vboId = (unsigned int *)RL_CALLOC(MAX_MESH_VBO, sizeof(unsigned int));
#define CUSTOM_MESH_GEN_CUBE
#if defined(CUSTOM_MESH_GEN_CUBE)
@@ -1579,85 +1884,93 @@ par_shapes_mesh* par_shapes_create_icosahedron(); // 20 sides polyhedron
#endif
// Upload vertex data to GPU (static mesh)
- rlLoadMesh(&mesh, false);
+ UploadMesh(&mesh, false);
return mesh;
}
// Generate sphere mesh (standard sphere)
-RLAPI Mesh GenMeshSphere(float radius, int rings, int slices)
+Mesh GenMeshSphere(float radius, int rings, int slices)
{
Mesh mesh = { 0 };
- mesh.vboId = (unsigned int *)RL_CALLOC(MAX_MESH_VBO, sizeof(unsigned int));
- par_shapes_mesh *sphere = par_shapes_create_parametric_sphere(slices, rings);
- par_shapes_scale(sphere, radius, radius, radius);
- // NOTE: Soft normals are computed internally
+ if ((rings >= 3) && (slices >= 3))
+ {
+ par_shapes_mesh *sphere = par_shapes_create_parametric_sphere(slices, rings);
+ par_shapes_scale(sphere, radius, radius, radius);
+ // NOTE: Soft normals are computed internally
- mesh.vertices = (float *)RL_MALLOC(sphere->ntriangles*3*3*sizeof(float));
- mesh.texcoords = (float *)RL_MALLOC(sphere->ntriangles*3*2*sizeof(float));
- mesh.normals = (float *)RL_MALLOC(sphere->ntriangles*3*3*sizeof(float));
+ mesh.vertices = (float *)RL_MALLOC(sphere->ntriangles*3*3*sizeof(float));
+ mesh.texcoords = (float *)RL_MALLOC(sphere->ntriangles*3*2*sizeof(float));
+ mesh.normals = (float *)RL_MALLOC(sphere->ntriangles*3*3*sizeof(float));
- mesh.vertexCount = sphere->ntriangles*3;
- mesh.triangleCount = sphere->ntriangles;
+ mesh.vertexCount = sphere->ntriangles*3;
+ mesh.triangleCount = sphere->ntriangles;
- for (int k = 0; k < mesh.vertexCount; k++)
- {
- 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];
+ for (int k = 0; k < mesh.vertexCount; k++)
+ {
+ 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.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];
- }
+ 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);
+ par_shapes_free_mesh(sphere);
- // Upload vertex data to GPU (static mesh)
- rlLoadMesh(&mesh, false);
+ // Upload vertex data to GPU (static mesh)
+ UploadMesh(&mesh, false);
+ }
+ else TRACELOG(LOG_WARNING, "MESH: Failed to generate mesh: sphere");
return mesh;
}
// Generate hemi-sphere mesh (half sphere, no bottom cap)
-RLAPI Mesh GenMeshHemiSphere(float radius, int rings, int slices)
+Mesh GenMeshHemiSphere(float radius, int rings, int slices)
{
Mesh mesh = { 0 };
- mesh.vboId = (unsigned int *)RL_CALLOC(MAX_MESH_VBO, sizeof(unsigned int));
- par_shapes_mesh *sphere = par_shapes_create_hemisphere(slices, rings);
- par_shapes_scale(sphere, radius, radius, radius);
- // NOTE: Soft normals are computed internally
+ if ((rings >= 3) && (slices >= 3))
+ {
+ if (radius < 0.0f) radius = 0.0f;
+
+ par_shapes_mesh *sphere = par_shapes_create_hemisphere(slices, rings);
+ par_shapes_scale(sphere, radius, radius, radius);
+ // NOTE: Soft normals are computed internally
- mesh.vertices = (float *)RL_MALLOC(sphere->ntriangles*3*3*sizeof(float));
- mesh.texcoords = (float *)RL_MALLOC(sphere->ntriangles*3*2*sizeof(float));
- mesh.normals = (float *)RL_MALLOC(sphere->ntriangles*3*3*sizeof(float));
+ mesh.vertices = (float *)RL_MALLOC(sphere->ntriangles*3*3*sizeof(float));
+ mesh.texcoords = (float *)RL_MALLOC(sphere->ntriangles*3*2*sizeof(float));
+ mesh.normals = (float *)RL_MALLOC(sphere->ntriangles*3*3*sizeof(float));
- mesh.vertexCount = sphere->ntriangles*3;
- mesh.triangleCount = sphere->ntriangles;
+ mesh.vertexCount = sphere->ntriangles*3;
+ mesh.triangleCount = sphere->ntriangles;
- for (int k = 0; k < mesh.vertexCount; k++)
- {
- 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];
+ for (int k = 0; k < mesh.vertexCount; k++)
+ {
+ 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.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];
- }
+ 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);
+ par_shapes_free_mesh(sphere);
- // Upload vertex data to GPU (static mesh)
- rlLoadMesh(&mesh, false);
+ // Upload vertex data to GPU (static mesh)
+ UploadMesh(&mesh, false);
+ }
+ else TRACELOG(LOG_WARNING, "MESH: Failed to generate mesh: hemisphere");
return mesh;
}
@@ -1666,58 +1979,61 @@ RLAPI Mesh GenMeshHemiSphere(float radius, int rings, int slices)
Mesh GenMeshCylinder(float radius, float height, int slices)
{
Mesh mesh = { 0 };
- mesh.vboId = (unsigned int *)RL_CALLOC(MAX_MESH_VBO, sizeof(unsigned int));
-
- // 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.
- // 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);
- par_shapes_rotate(cylinder, -PI/2.0f, (float[]){ 1, 0, 0 });
- par_shapes_rotate(cylinder, PI/2.0f, (float[]){ 0, 1, 0 });
-
- // Generate an orientable disk shape (top cap)
- par_shapes_mesh *capTop = par_shapes_create_disk(radius, slices, (float[]){ 0, 0, 0 }, (float[]){ 0, 0, 1 });
- capTop->tcoords = PAR_MALLOC(float, 2*capTop->npoints);
- 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 *)RL_MALLOC(cylinder->ntriangles*3*3*sizeof(float));
- mesh.texcoords = (float *)RL_MALLOC(cylinder->ntriangles*3*2*sizeof(float));
- mesh.normals = (float *)RL_MALLOC(cylinder->ntriangles*3*3*sizeof(float));
-
- mesh.vertexCount = cylinder->ntriangles*3;
- mesh.triangleCount = cylinder->ntriangles;
- for (int k = 0; k < mesh.vertexCount; k++)
+ if (slices >= 3)
{
- 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];
+ // 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.
+ // 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);
+ par_shapes_rotate(cylinder, -PI/2.0f, (float[]){ 1, 0, 0 });
+ par_shapes_rotate(cylinder, PI/2.0f, (float[]){ 0, 1, 0 });
+
+ // Generate an orientable disk shape (top cap)
+ par_shapes_mesh *capTop = par_shapes_create_disk(radius, slices, (float[]){ 0, 0, 0 }, (float[]){ 0, 0, 1 });
+ capTop->tcoords = PAR_MALLOC(float, 2*capTop->npoints);
+ 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 *)RL_MALLOC(cylinder->ntriangles*3*3*sizeof(float));
+ mesh.texcoords = (float *)RL_MALLOC(cylinder->ntriangles*3*2*sizeof(float));
+ mesh.normals = (float *)RL_MALLOC(cylinder->ntriangles*3*3*sizeof(float));
+
+ mesh.vertexCount = cylinder->ntriangles*3;
+ mesh.triangleCount = cylinder->ntriangles;
+
+ for (int k = 0; k < mesh.vertexCount; k++)
+ {
+ 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.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];
- }
+ 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);
+ par_shapes_free_mesh(cylinder);
- // Upload vertex data to GPU (static mesh)
- rlLoadMesh(&mesh, false);
+ // Upload vertex data to GPU (static mesh)
+ UploadMesh(&mesh, false);
+ }
+ else TRACELOG(LOG_WARNING, "MESH: Failed to generate mesh: cylinder");
return mesh;
}
@@ -1726,41 +2042,44 @@ Mesh GenMeshCylinder(float radius, float height, int slices)
Mesh GenMeshTorus(float radius, float size, int radSeg, int sides)
{
Mesh mesh = { 0 };
- mesh.vboId = (unsigned int *)RL_CALLOC(MAX_MESH_VBO, sizeof(unsigned int));
- if (radius > 1.0f) radius = 1.0f;
- else if (radius < 0.1f) radius = 0.1f;
+ if ((sides >= 3) && (radSeg >= 3))
+ {
+ 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);
- par_shapes_scale(torus, size/2, size/2, size/2);
+ // 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);
+ par_shapes_scale(torus, size/2, size/2, size/2);
- mesh.vertices = (float *)RL_MALLOC(torus->ntriangles*3*3*sizeof(float));
- mesh.texcoords = (float *)RL_MALLOC(torus->ntriangles*3*2*sizeof(float));
- mesh.normals = (float *)RL_MALLOC(torus->ntriangles*3*3*sizeof(float));
+ mesh.vertices = (float *)RL_MALLOC(torus->ntriangles*3*3*sizeof(float));
+ mesh.texcoords = (float *)RL_MALLOC(torus->ntriangles*3*2*sizeof(float));
+ mesh.normals = (float *)RL_MALLOC(torus->ntriangles*3*3*sizeof(float));
- mesh.vertexCount = torus->ntriangles*3;
- mesh.triangleCount = torus->ntriangles;
+ mesh.vertexCount = torus->ntriangles*3;
+ mesh.triangleCount = torus->ntriangles;
- for (int k = 0; k < mesh.vertexCount; k++)
- {
- 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];
+ for (int k = 0; k < mesh.vertexCount; k++)
+ {
+ 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.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];
- }
+ 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);
+ par_shapes_free_mesh(torus);
- // Upload vertex data to GPU (static mesh)
- rlLoadMesh(&mesh, false);
+ // Upload vertex data to GPU (static mesh)
+ UploadMesh(&mesh, false);
+ }
+ else TRACELOG(LOG_WARNING, "MESH: Failed to generate mesh: torus");
return mesh;
}
@@ -1769,39 +2088,42 @@ Mesh GenMeshTorus(float radius, float size, int radSeg, int sides)
Mesh GenMeshKnot(float radius, float size, int radSeg, int sides)
{
Mesh mesh = { 0 };
- mesh.vboId = (unsigned int *)RL_CALLOC(MAX_MESH_VBO, sizeof(unsigned int));
- if (radius > 3.0f) radius = 3.0f;
- else if (radius < 0.5f) radius = 0.5f;
+ if ((sides >= 3) && (radSeg >= 3))
+ {
+ if (radius > 3.0f) radius = 3.0f;
+ else if (radius < 0.5f) radius = 0.5f;
- par_shapes_mesh *knot = par_shapes_create_trefoil_knot(radSeg, sides, radius);
- par_shapes_scale(knot, size, size, size);
+ par_shapes_mesh *knot = par_shapes_create_trefoil_knot(radSeg, sides, radius);
+ par_shapes_scale(knot, size, size, size);
- mesh.vertices = (float *)RL_MALLOC(knot->ntriangles*3*3*sizeof(float));
- mesh.texcoords = (float *)RL_MALLOC(knot->ntriangles*3*2*sizeof(float));
- mesh.normals = (float *)RL_MALLOC(knot->ntriangles*3*3*sizeof(float));
+ mesh.vertices = (float *)RL_MALLOC(knot->ntriangles*3*3*sizeof(float));
+ mesh.texcoords = (float *)RL_MALLOC(knot->ntriangles*3*2*sizeof(float));
+ mesh.normals = (float *)RL_MALLOC(knot->ntriangles*3*3*sizeof(float));
- mesh.vertexCount = knot->ntriangles*3;
- mesh.triangleCount = knot->ntriangles;
+ mesh.vertexCount = knot->ntriangles*3;
+ mesh.triangleCount = knot->ntriangles;
- for (int k = 0; k < mesh.vertexCount; k++)
- {
- 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];
+ for (int k = 0; k < mesh.vertexCount; k++)
+ {
+ 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.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];
- }
+ 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);
+ par_shapes_free_mesh(knot);
- // Upload vertex data to GPU (static mesh)
- rlLoadMesh(&mesh, false);
+ // Upload vertex data to GPU (static mesh)
+ UploadMesh(&mesh, false);
+ }
+ else TRACELOG(LOG_WARNING, "MESH: Failed to generate mesh: knot");
return mesh;
}
@@ -1813,12 +2135,11 @@ Mesh GenMeshHeightmap(Image heightmap, Vector3 size)
#define GRAY_VALUE(c) ((c.r+c.g+c.b)/3)
Mesh mesh = { 0 };
- mesh.vboId = (unsigned int *)RL_CALLOC(MAX_MESH_VBO, sizeof(unsigned int));
int mapX = heightmap.width;
int mapZ = heightmap.height;
- Color *pixels = GetImageData(heightmap);
+ Color *pixels = LoadImageColors(heightmap);
// NOTE: One vertex per pixel
mesh.triangleCount = (mapX-1)*(mapZ-1)*2; // One quad every four pixels
@@ -1934,10 +2255,10 @@ Mesh GenMeshHeightmap(Image heightmap, Vector3 size)
}
}
- RL_FREE(pixels);
+ UnloadImageColors(pixels); // Unload pixels color data
// Upload vertex data to GPU (static mesh)
- rlLoadMesh(&mesh, false);
+ UploadMesh(&mesh, false);
return mesh;
}
@@ -1946,10 +2267,11 @@ Mesh GenMeshHeightmap(Image heightmap, Vector3 size)
// NOTE: Vertex data is uploaded to GPU
Mesh GenMeshCubicmap(Image cubicmap, Vector3 cubeSize)
{
+ #define COLOR_EQUAL(col1, col2) ((col1.r == col2.r)&&(col1.g == col2.g)&&(col1.b == col2.b)&&(col1.a == col2.a))
+
Mesh mesh = { 0 };
- mesh.vboId = (unsigned int *)RL_CALLOC(MAX_MESH_VBO, sizeof(unsigned int));
- Color *cubicmapPixels = GetImageData(cubicmap);
+ Color *pixels = LoadImageColors(cubicmap);
int mapWidth = cubicmap.width;
int mapHeight = cubicmap.height;
@@ -1974,8 +2296,8 @@ Mesh GenMeshCubicmap(Image cubicmap, Vector3 cubeSize)
Vector3 n2 = { -1.0f, 0.0f, 0.0f };
Vector3 n3 = { 0.0f, 1.0f, 0.0f };
Vector3 n4 = { 0.0f, -1.0f, 0.0f };
- Vector3 n5 = { 0.0f, 0.0f, 1.0f };
- Vector3 n6 = { 0.0f, 0.0f, -1.0f };
+ Vector3 n5 = { 0.0f, 0.0f, -1.0f };
+ Vector3 n6 = { 0.0f, 0.0f, 1.0f };
// NOTE: We use texture rectangles to define different textures for top-bottom-front-back-right-left (6)
typedef struct RectangleF {
@@ -2006,15 +2328,14 @@ Mesh GenMeshCubicmap(Image cubicmap, Vector3 cubeSize)
Vector3 v7 = { w*(x - 0.5f), 0, h*(z + 0.5f) };
Vector3 v8 = { w*(x + 0.5f), 0, h*(z + 0.5f) };
- // We check pixel color to be WHITE, we will full cubes
- if ((cubicmapPixels[z*cubicmap.width + x].r == 255) &&
- (cubicmapPixels[z*cubicmap.width + x].g == 255) &&
- (cubicmapPixels[z*cubicmap.width + x].b == 255))
+ // We check pixel color to be WHITE -> draw full cube
+ if (COLOR_EQUAL(pixels[z*cubicmap.width + x], WHITE))
{
- // Define triangles (Checking Collateral Cubes!)
- //----------------------------------------------
+ // Define triangles and checking collateral cubes
+ //------------------------------------------------
// Define top triangles (2 tris, 6 vertex --> v1-v2-v3, v1-v3-v4)
+ // WARNING: Not required for a WHITE cubes, created to allow seeing the map from outside
mapVertices[vCounter] = v1;
mapVertices[vCounter + 1] = v2;
mapVertices[vCounter + 2] = v3;
@@ -2064,10 +2385,8 @@ Mesh GenMeshCubicmap(Image cubicmap, Vector3 cubeSize)
mapTexcoords[tcCounter + 5] = (Vector2){ bottomTexUV.x, bottomTexUV.y + bottomTexUV.height };
tcCounter += 6;
- if (((z < cubicmap.height - 1) &&
- (cubicmapPixels[(z + 1)*cubicmap.width + x].r == 0) &&
- (cubicmapPixels[(z + 1)*cubicmap.width + x].g == 0) &&
- (cubicmapPixels[(z + 1)*cubicmap.width + x].b == 0)) || (z == cubicmap.height - 1))
+ // Checking cube on bottom of current cube
+ if (((z < cubicmap.height - 1) && COLOR_EQUAL(pixels[(z + 1)*cubicmap.width + x], BLACK)) || (z == cubicmap.height - 1))
{
// Define front triangles (2 tris, 6 vertex) --> v2 v7 v3, v3 v7 v8
// NOTE: Collateral occluded faces are not generated
@@ -2096,10 +2415,8 @@ Mesh GenMeshCubicmap(Image cubicmap, Vector3 cubeSize)
tcCounter += 6;
}
- if (((z > 0) &&
- (cubicmapPixels[(z - 1)*cubicmap.width + x].r == 0) &&
- (cubicmapPixels[(z - 1)*cubicmap.width + x].g == 0) &&
- (cubicmapPixels[(z - 1)*cubicmap.width + x].b == 0)) || (z == 0))
+ // Checking cube on top of current cube
+ if (((z > 0) && COLOR_EQUAL(pixels[(z - 1)*cubicmap.width + x], BLACK)) || (z == 0))
{
// Define back triangles (2 tris, 6 vertex) --> v1 v5 v6, v1 v4 v5
// NOTE: Collateral occluded faces are not generated
@@ -2128,10 +2445,8 @@ Mesh GenMeshCubicmap(Image cubicmap, Vector3 cubeSize)
tcCounter += 6;
}
- if (((x < cubicmap.width - 1) &&
- (cubicmapPixels[z*cubicmap.width + (x + 1)].r == 0) &&
- (cubicmapPixels[z*cubicmap.width + (x + 1)].g == 0) &&
- (cubicmapPixels[z*cubicmap.width + (x + 1)].b == 0)) || (x == cubicmap.width - 1))
+ // Checking cube on right of current cube
+ if (((x < cubicmap.width - 1) && COLOR_EQUAL(pixels[z*cubicmap.width + (x + 1)], BLACK)) || (x == cubicmap.width - 1))
{
// Define right triangles (2 tris, 6 vertex) --> v3 v8 v4, v4 v8 v5
// NOTE: Collateral occluded faces are not generated
@@ -2160,10 +2475,8 @@ Mesh GenMeshCubicmap(Image cubicmap, Vector3 cubeSize)
tcCounter += 6;
}
- if (((x > 0) &&
- (cubicmapPixels[z*cubicmap.width + (x - 1)].r == 0) &&
- (cubicmapPixels[z*cubicmap.width + (x - 1)].g == 0) &&
- (cubicmapPixels[z*cubicmap.width + (x - 1)].b == 0)) || (x == 0))
+ // Checking cube on left of current cube
+ if (((x > 0) && COLOR_EQUAL(pixels[z*cubicmap.width + (x - 1)], BLACK)) || (x == 0))
{
// Define left triangles (2 tris, 6 vertex) --> v1 v7 v2, v1 v6 v7
// NOTE: Collateral occluded faces are not generated
@@ -2193,9 +2506,7 @@ Mesh GenMeshCubicmap(Image cubicmap, Vector3 cubeSize)
}
}
// We check pixel color to be BLACK, we will only draw floor and roof
- else if ((cubicmapPixels[z*cubicmap.width + x].r == 0) &&
- (cubicmapPixels[z*cubicmap.width + x].g == 0) &&
- (cubicmapPixels[z*cubicmap.width + x].b == 0))
+ else if (COLOR_EQUAL(pixels[z*cubicmap.width + x], BLACK))
{
// Define top triangles (2 tris, 6 vertex --> v1-v2-v3, v1-v3-v4)
mapVertices[vCounter] = v1;
@@ -2295,10 +2606,10 @@ Mesh GenMeshCubicmap(Image cubicmap, Vector3 cubeSize)
RL_FREE(mapNormals);
RL_FREE(mapTexcoords);
- RL_FREE(cubicmapPixels); // Free image pixel data
+ UnloadImageColors(pixels); // Unload pixels color data
// Upload vertex data to GPU (static mesh)
- rlLoadMesh(&mesh, false);
+ UploadMesh(&mesh, false);
return mesh;
}
@@ -2409,7 +2720,7 @@ void MeshTangents(Mesh *mesh)
RL_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);
+ mesh->vboId[SHADER_LOC_VERTEX_TANGENT] = rlLoadVertexBuffer(mesh->tangents, mesh->vertexCount*4*sizeof(float), false);
TRACELOG(LOG_INFO, "MESH: Tangents data computed for provided mesh");
}
@@ -2452,18 +2763,17 @@ void DrawModelEx(Model model, Vector3 position, Vector3 rotationAxis, float rota
for (int i = 0; i < model.meshCount; i++)
{
- // TODO: Review color + tint premultiplication mechanism
- Color color = model.materials[model.meshMaterial[i]].maps[MAP_DIFFUSE].color;
+ Color color = model.materials[model.meshMaterial[i]].maps[MATERIAL_MAP_DIFFUSE].color;
Color colorTint = WHITE;
- colorTint.r = (((float)color.r/255.0)*((float)tint.r/255.0))*255;
- colorTint.g = (((float)color.g/255.0)*((float)tint.g/255.0))*255;
- colorTint.b = (((float)color.b/255.0)*((float)tint.b/255.0))*255;
- colorTint.a = (((float)color.a/255.0)*((float)tint.a/255.0))*255;
-
- model.materials[model.meshMaterial[i]].maps[MAP_DIFFUSE].color = colorTint;
- rlDrawMesh(model.meshes[i], model.materials[model.meshMaterial[i]], model.transform);
- model.materials[model.meshMaterial[i]].maps[MAP_DIFFUSE].color = color;
+ colorTint.r = (unsigned char)((((float)color.r/255.0)*((float)tint.r/255.0))*255.0f);
+ colorTint.g = (unsigned char)((((float)color.g/255.0)*((float)tint.g/255.0))*255.0f);
+ colorTint.b = (unsigned char)((((float)color.b/255.0)*((float)tint.b/255.0))*255.0f);
+ colorTint.a = (unsigned char)((((float)color.a/255.0)*((float)tint.a/255.0))*255.0f);
+
+ model.materials[model.meshMaterial[i]].maps[MATERIAL_MAP_DIFFUSE].color = colorTint;
+ DrawMesh(model.meshes[i], model.materials[model.meshMaterial[i]], model.transform);
+ model.materials[model.meshMaterial[i]].maps[MATERIAL_MAP_DIFFUSE].color = color;
}
}
@@ -2490,16 +2800,16 @@ void DrawModelWiresEx(Model model, Vector3 position, Vector3 rotationAxis, float
// Draw a billboard
void DrawBillboard(Camera camera, Texture2D texture, Vector3 center, float size, Color tint)
{
- Rectangle sourceRec = { 0.0f, 0.0f, (float)texture.width, (float)texture.height };
+ Rectangle source = { 0.0f, 0.0f, (float)texture.width, (float)texture.height };
- DrawBillboardRec(camera, texture, sourceRec, center, size, tint);
+ DrawBillboardRec(camera, texture, source, center, size, tint);
}
// Draw a billboard (part of a texture defined by a rectangle)
-void DrawBillboardRec(Camera camera, Texture2D texture, Rectangle sourceRec, Vector3 center, float size, Color tint)
+void DrawBillboardRec(Camera camera, Texture2D texture, Rectangle source, Vector3 center, float size, Color tint)
{
- // NOTE: Billboard size will maintain sourceRec aspect ratio, size will represent billboard width
- Vector2 sizeRatio = { size, size*(float)sourceRec.height/sourceRec.width };
+ // NOTE: Billboard size will maintain source rectangle aspect ratio, size will represent billboard width
+ Vector2 sizeRatio = { size, size*(float)source.height/source.width };
Matrix matView = MatrixLookAt(camera.position, camera.target, camera.up);
@@ -2526,31 +2836,31 @@ void DrawBillboardRec(Camera camera, Texture2D texture, Rectangle sourceRec, Vec
Vector3 c = Vector3Add(center, p2);
Vector3 d = Vector3Subtract(center, p1);
- if (rlCheckBufferLimit(4)) rlglDraw();
+ rlCheckRenderBatchLimit(4);
- rlEnableTexture(texture.id);
+ rlSetTexture(texture.id);
rlBegin(RL_QUADS);
rlColor4ub(tint.r, tint.g, tint.b, tint.a);
// Bottom-left corner for texture and quad
- rlTexCoord2f((float)sourceRec.x/texture.width, (float)sourceRec.y/texture.height);
+ rlTexCoord2f((float)source.x/texture.width, (float)source.y/texture.height);
rlVertex3f(a.x, a.y, a.z);
// Top-left corner for texture and quad
- rlTexCoord2f((float)sourceRec.x/texture.width, (float)(sourceRec.y + sourceRec.height)/texture.height);
+ rlTexCoord2f((float)source.x/texture.width, (float)(source.y + source.height)/texture.height);
rlVertex3f(d.x, d.y, d.z);
// Top-right corner for texture and quad
- rlTexCoord2f((float)(sourceRec.x + sourceRec.width)/texture.width, (float)(sourceRec.y + sourceRec.height)/texture.height);
+ rlTexCoord2f((float)(source.x + source.width)/texture.width, (float)(source.y + source.height)/texture.height);
rlVertex3f(c.x, c.y, c.z);
// Bottom-right corner for texture and quad
- rlTexCoord2f((float)(sourceRec.x + sourceRec.width)/texture.width, (float)sourceRec.y/texture.height);
+ rlTexCoord2f((float)(source.x + source.width)/texture.width, (float)source.y/texture.height);
rlVertex3f(b.x, b.y, b.z);
rlEnd();
- rlDisableTexture();
+ rlSetTexture(0);
}
// Draw a bounding box with wires
@@ -2568,24 +2878,24 @@ void DrawBoundingBox(BoundingBox box, Color color)
}
// Detect collision between two spheres
-bool CheckCollisionSpheres(Vector3 centerA, float radiusA, Vector3 centerB, float radiusB)
+bool CheckCollisionSpheres(Vector3 center1, float radius1, Vector3 center2, float radius2)
{
bool collision = false;
// Simple way to check for collision, just checking distance between two points
// Unfortunately, sqrtf() is a costly operation, so we avoid it with following solution
/*
- float dx = centerA.x - centerB.x; // X distance between centers
- float dy = centerA.y - centerB.y; // Y distance between centers
- float dz = centerA.z - centerB.z; // Z distance between centers
+ float dx = center1.x - center2.x; // X distance between centers
+ float dy = center1.y - center2.y; // Y distance between centers
+ float dz = center1.z - center2.z; // Z distance between centers
float distance = sqrtf(dx*dx + dy*dy + dz*dz); // Distance between centers
- if (distance <= (radiusA + radiusB)) collision = true;
+ if (distance <= (radius1 + radius2)) collision = true;
*/
// Check for distances squared to avoid sqrtf()
- if (Vector3DotProduct(Vector3Subtract(centerB, centerA), Vector3Subtract(centerB, centerA)) <= (radiusA + radiusB)*(radiusA + radiusB)) collision = true;
+ if (Vector3DotProduct(Vector3Subtract(center2, center1), Vector3Subtract(center2, center1)) <= (radius1 + radius2)*(radius1 + radius2)) collision = true;
return collision;
}
@@ -2689,53 +2999,66 @@ bool CheckCollisionRayBox(Ray ray, BoundingBox box)
return collision;
}
-
-// Get collision info between ray and model
-RayHitInfo GetCollisionRayModel(Ray ray, Model model)
+// Get collision info between ray and mesh
+RayHitInfo GetCollisionRayMesh(Ray ray, Mesh mesh, Matrix transform)
{
RayHitInfo result = { 0 };
- for (int m = 0; m < model.meshCount; m++)
+ // Check if mesh vertex data on CPU for testing
+ if (mesh.vertices != NULL)
{
- // Check if meshhas vertex data on CPU for testing
- if (model.meshes[m].vertices != NULL)
+ int triangleCount = mesh.triangleCount;
+
+ // Test against all triangles in mesh
+ for (int i = 0; i < triangleCount; i++)
{
- // model->mesh.triangleCount may not be set, vertexCount is more reliable
- int triangleCount = model.meshes[m].vertexCount/3;
+ Vector3 a, b, c;
+ Vector3* vertdata = (Vector3*)mesh.vertices;
- // Test against all triangles in mesh
- for (int i = 0; i < triangleCount; i++)
+ if (mesh.indices)
{
- Vector3 a, b, c;
- Vector3 *vertdata = (Vector3 *)model.meshes[m].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];
- }
+ a = vertdata[mesh.indices[i*3 + 0]];
+ b = vertdata[mesh.indices[i*3 + 1]];
+ c = vertdata[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, transform);
+ b = Vector3Transform(b, transform);
+ c = Vector3Transform(c, 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;
}
}
}
+ return result;
+}
+
+// Get collision info between ray and model
+RayHitInfo GetCollisionRayModel(Ray ray, Model model)
+{
+ RayHitInfo result = { 0 };
+
+ for (int m = 0; m < model.meshCount; m++)
+ {
+ RayHitInfo meshHitInfo = GetCollisionRayMesh(ray, model.meshes[m], model.transform);
+
+ if (meshHitInfo.hit)
+ {
+ // Save the closest hit mesh
+ if ((!result.hit) || (result.distance > meshHitInfo.distance)) result = meshHitInfo;
+ }
+ }
return result;
}
@@ -2816,6 +3139,7 @@ RayHitInfo GetCollisionRayGround(Ray ray, float groundHeight)
result.distance = distance;
result.normal = (Vector3){ 0.0, 1.0, 0.0 };
result.position = Vector3Add(ray.position, Vector3Scale(ray.direction, distance));
+ result.position.y = groundHeight;
}
}
@@ -2832,7 +3156,7 @@ static Model LoadOBJ(const char *fileName)
{
Model model = { 0 };
- tinyobj_attrib_t attrib;
+ tinyobj_attrib_t attrib = { 0 };
tinyobj_shape_t *meshes = NULL;
unsigned int meshCount = 0;
@@ -2843,10 +3167,14 @@ static Model LoadOBJ(const char *fileName)
if (fileData != NULL)
{
- int dataSize = strlen(fileData);
+ unsigned int dataSize = (unsigned int)strlen(fileData);
char currentDir[1024] = { 0 };
strcpy(currentDir, GetWorkingDirectory());
- chdir(GetDirectoryPath(fileName));
+ const char *workingDir = GetDirectoryPath(fileName);
+ if (CHDIR(workingDir) != 0)
+ {
+ TRACELOG(LOG_WARNING, "MODEL: [%s] Failed to change working directory", workingDir);
+ }
unsigned int flags = TINYOBJ_FLAG_TRIANGULATE;
int ret = tinyobj_parse_obj(&attrib, &meshes, &meshCount, &materials, &materialCount, fileData, dataSize, flags);
@@ -2854,143 +3182,140 @@ static Model LoadOBJ(const char *fileName)
if (ret != TINYOBJ_SUCCESS) TRACELOG(LOG_WARNING, "MODEL: [%s] Failed to load OBJ data", fileName);
else TRACELOG(LOG_INFO, "MODEL: [%s] OBJ data loaded successfully: %i meshes / %i materials", fileName, meshCount, materialCount);
- // Init model meshes array
- // TODO: Support multiple meshes... in the meantime, only one mesh is returned
- //model.meshCount = meshCount;
- model.meshCount = 1;
- model.meshes = (Mesh *)RL_CALLOC(model.meshCount, sizeof(Mesh));
+ model.meshCount = materialCount;
// Init model materials array
if (materialCount > 0)
{
model.materialCount = materialCount;
model.materials = (Material *)RL_CALLOC(model.materialCount, sizeof(Material));
+ TraceLog(LOG_INFO, "MODEL: model has %i material meshes", materialCount);
+ }
+ else
+ {
+ model.meshCount = 1;
+ TraceLog(LOG_INFO, "MODEL: No materials, putting all meshes in a default material");
}
+ model.meshes = (Mesh *)RL_CALLOC(model.meshCount, sizeof(Mesh));
model.meshMaterial = (int *)RL_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++)
+ // count the faces for each material
+ int *matFaces = RL_CALLOC(meshCount, sizeof(int));
+
+ for (unsigned int mi = 0; mi < meshCount; mi++)
{
- Mesh mesh = { 0 };
- memset(&mesh, 0, sizeof(Mesh));
- mesh.vertexCount = attrib.num_faces*3;
- mesh.triangleCount = attrib.num_faces;
- mesh.vertices = (float *)RL_CALLOC(mesh.vertexCount*3, sizeof(float));
- mesh.texcoords = (float *)RL_CALLOC(mesh.vertexCount*2, sizeof(float));
- mesh.normals = (float *)RL_CALLOC(mesh.vertexCount*3, sizeof(float));
- mesh.vboId = (unsigned int *)RL_CALLOC(MAX_MESH_VBO, sizeof(unsigned int));
-
- int vCount = 0;
- int vtCount = 0;
- int vnCount = 0;
-
- for (int f = 0; f < attrib.num_faces; f++)
+ for (unsigned int fi = 0; fi < meshes[mi].length; fi++)
{
- // 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];
+ int idx = attrib.material_ids[meshes[mi].face_offset + fi];
+ if (idx == -1) idx = 0; // for no material face (which could be the whole model)
+ matFaces[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;
+ //--------------------------------------
+ // create the material meshes
- // 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;
+ // running counts / indexes for each material mesh as we are
+ // building them at the same time
+ int *vCount = RL_CALLOC(model.meshCount, sizeof(int));
+ int *vtCount = RL_CALLOC(model.meshCount, sizeof(int));
+ int *vnCount = RL_CALLOC(model.meshCount, sizeof(int));
+ int *faceCount = RL_CALLOC(model.meshCount, sizeof(int));
- // 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;
- }
+ // allocate space for each of the material meshes
+ for (int mi = 0; mi < model.meshCount; mi++)
+ {
+ model.meshes[mi].vertexCount = matFaces[mi]*3;
+ model.meshes[mi].triangleCount = matFaces[mi];
+ model.meshes[mi].vertices = (float *)RL_CALLOC(model.meshes[mi].vertexCount*3, sizeof(float));
+ model.meshes[mi].texcoords = (float *)RL_CALLOC(model.meshes[mi].vertexCount*2, sizeof(float));
+ model.meshes[mi].normals = (float *)RL_CALLOC(model.meshes[mi].vertexCount*3, sizeof(float));
+ model.meshMaterial[mi] = mi;
+ }
- model.meshes[m] = mesh; // Assign mesh data to model
+ // scan through the combined sub meshes and pick out each material mesh
+ for (unsigned int af = 0; af < attrib.num_faces; af++)
+ {
+ int mm = attrib.material_ids[af]; // mesh material for this face
+ if (mm == -1) { mm = 0; } // no material object..
+
+ // Get indices for the face
+ tinyobj_vertex_index_t idx0 = attrib.faces[3*af + 0];
+ tinyobj_vertex_index_t idx1 = attrib.faces[3*af + 1];
+ tinyobj_vertex_index_t idx2 = attrib.faces[3*af + 2];
- // Assign mesh material for current mesh
- model.meshMaterial[m] = attrib.material_ids[m];
+ // Fill vertices buffer (float) using vertex index of the face
+ for (int v = 0; v < 3; v++) { model.meshes[mm].vertices[vCount[mm] + v] = attrib.vertices[idx0.v_idx*3 + v]; } vCount[mm] +=3;
+ for (int v = 0; v < 3; v++) { model.meshes[mm].vertices[vCount[mm] + v] = attrib.vertices[idx1.v_idx*3 + v]; } vCount[mm] +=3;
+ for (int v = 0; v < 3; v++) { model.meshes[mm].vertices[vCount[mm] + v] = attrib.vertices[idx2.v_idx*3 + v]; } vCount[mm] +=3;
+
+ if (attrib.num_texcoords > 0)
+ {
+ // Fill texcoords buffer (float) using vertex index of the face
+ // NOTE: Y-coordinate must be flipped upside-down to account for
+ // raylib's upside down textures...
+ model.meshes[mm].texcoords[vtCount[mm] + 0] = attrib.texcoords[idx0.vt_idx*2 + 0];
+ model.meshes[mm].texcoords[vtCount[mm] + 1] = 1.0f - attrib.texcoords[idx0.vt_idx*2 + 1]; vtCount[mm] += 2;
+ model.meshes[mm].texcoords[vtCount[mm] + 0] = attrib.texcoords[idx1.vt_idx*2 + 0];
+ model.meshes[mm].texcoords[vtCount[mm] + 1] = 1.0f - attrib.texcoords[idx1.vt_idx*2 + 1]; vtCount[mm] += 2;
+ model.meshes[mm].texcoords[vtCount[mm] + 0] = attrib.texcoords[idx2.vt_idx*2 + 0];
+ model.meshes[mm].texcoords[vtCount[mm] + 1] = 1.0f - attrib.texcoords[idx2.vt_idx*2 + 1]; vtCount[mm] += 2;
+ }
- // Set unfound materials to default
- if (model.meshMaterial[m] == -1) model.meshMaterial[m] = 0;
+ if (attrib.num_normals > 0)
+ {
+ // Fill normals buffer (float) using vertex index of the face
+ for (int v = 0; v < 3; v++) { model.meshes[mm].normals[vnCount[mm] + v] = attrib.normals[idx0.vn_idx*3 + v]; } vnCount[mm] +=3;
+ for (int v = 0; v < 3; v++) { model.meshes[mm].normals[vnCount[mm] + v] = attrib.normals[idx1.vn_idx*3 + v]; } vnCount[mm] +=3;
+ for (int v = 0; v < 3; v++) { model.meshes[mm].normals[vnCount[mm] + v] = attrib.normals[idx2.vn_idx*3 + v]; } vnCount[mm] +=3;
+ }
}
// Init model materials
- for (int m = 0; m < materialCount; m++)
+ for (unsigned int m = 0; m < materialCount; m++)
{
// Init material to default
- // NOTE: Uses default shader, only MAP_DIFFUSE supported
+ // NOTE: Uses default shader, which only supports MATERIAL_MAP_DIFFUSE
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 (Ref: 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
+ model.materials[m].maps[MATERIAL_MAP_DIFFUSE].texture = rlGetTextureDefault(); // Get default texture, in case no texture is defined
+
+ if (materials[m].diffuse_texname != NULL) model.materials[m].maps[MATERIAL_MAP_DIFFUSE].texture = LoadTexture(materials[m].diffuse_texname); //char *diffuse_texname; // map_Kd
+ else model.materials[m].maps[MATERIAL_MAP_DIFFUSE].texture = rlGetTextureDefault();
+
+ model.materials[m].maps[MATERIAL_MAP_DIFFUSE].color = (Color){ (unsigned char)(materials[m].diffuse[0]*255.0f), (unsigned char)(materials[m].diffuse[1]*255.0f), (unsigned char)(materials[m].diffuse[2]*255.0f), 255 }; //float diffuse[3];
+ model.materials[m].maps[MATERIAL_MAP_DIFFUSE].value = 0.0f;
+
+ if (materials[m].specular_texname != NULL) model.materials[m].maps[MATERIAL_MAP_SPECULAR].texture = LoadTexture(materials[m].specular_texname); //char *specular_texname; // map_Ks
+ model.materials[m].maps[MATERIAL_MAP_SPECULAR].color = (Color){ (unsigned char)(materials[m].specular[0]*255.0f), (unsigned char)(materials[m].specular[1]*255.0f), (unsigned char)(materials[m].specular[2]*255.0f), 255 }; //float specular[3];
+ model.materials[m].maps[MATERIAL_MAP_SPECULAR].value = 0.0f;
+
+ if (materials[m].bump_texname != NULL) model.materials[m].maps[MATERIAL_MAP_NORMAL].texture = LoadTexture(materials[m].bump_texname); //char *bump_texname; // map_bump, bump
+ model.materials[m].maps[MATERIAL_MAP_NORMAL].color = WHITE;
+ model.materials[m].maps[MATERIAL_MAP_NORMAL].value = materials[m].shininess;
+
+ model.materials[m].maps[MATERIAL_MAP_EMISSION].color = (Color){ (unsigned char)(materials[m].emission[0]*255.0f), (unsigned char)(materials[m].emission[1]*255.0f), (unsigned char)(materials[m].emission[2]*255.0f), 255 }; //float emission[3];
+
+ if (materials[m].displacement_texname != NULL) model.materials[m].maps[MATERIAL_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);
-
+
RL_FREE(fileData);
+ RL_FREE(matFaces);
- chdir(currentDir);
+ RL_FREE(vCount);
+ RL_FREE(vtCount);
+ RL_FREE(vnCount);
+ RL_FREE(faceCount);
+
+ if (CHDIR(currentDir) != 0)
+ {
+ TRACELOG(LOG_WARNING, "MODEL: [%s] Failed to change working directory", currentDir);
+ }
}
return model;
@@ -3001,11 +3326,16 @@ static Model LoadOBJ(const char *fileName)
// 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 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
+ #define MATERIAL_NAME_LENGTH 32 // Material name string length
+
+ unsigned int fileSize = 0;
+ unsigned char *fileData = LoadFileData(fileName, &fileSize);
+ unsigned char *fileDataPtr = fileData;
// IQM file structs
//-----------------------------------------------------------------------------------
@@ -3092,13 +3422,10 @@ static Model LoadIQM(const char *fileName)
Model model = { 0 };
- FILE *iqmFile = NULL;
- IQMHeader iqm;
-
- IQMMesh *imesh;
- IQMTriangle *tri;
- IQMVertexArray *va;
- IQMJoint *ijoint;
+ IQMMesh *imesh = NULL;
+ IQMTriangle *tri = NULL;
+ IQMVertexArray *va = NULL;
+ IQMJoint *ijoint = NULL;
float *vertex = NULL;
float *normal = NULL;
@@ -3106,44 +3433,56 @@ static Model LoadIQM(const char *fileName)
char *blendi = NULL;
unsigned char *blendw = NULL;
- iqmFile = fopen(fileName, "rb");
+ // In case file can not be read, return an empty model
+ if (fileDataPtr == NULL) return model;
- if (iqmFile == NULL)
- {
- TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to open IQM file", fileName);
- return model;
- }
-
- fread(&iqm, sizeof(IQMHeader), 1, iqmFile); // Read IQM header
+ // Read IQM header
+ IQMHeader *iqmHeader = (IQMHeader *)fileDataPtr;
- if (strncmp(iqm.magic, IQM_MAGIC, sizeof(IQM_MAGIC)))
+ if (memcmp(iqmHeader->magic, IQM_MAGIC, sizeof(IQM_MAGIC)) != 0)
{
TRACELOG(LOG_WARNING, "MODEL: [%s] IQM file is not a valid model", fileName);
- fclose(iqmFile);
return model;
}
- if (iqm.version != IQM_VERSION)
+ if (iqmHeader->version != IQM_VERSION)
{
- TRACELOG(LOG_WARNING, "MODEL: [%s] IQM file version not supported (%i)", fileName, iqm.version);
- fclose(iqmFile);
+ TRACELOG(LOG_WARNING, "MODEL: [%s] IQM file version not supported (%i)", fileName, iqmHeader->version);
return model;
}
+ //fileDataPtr += sizeof(IQMHeader); // Move file data pointer
+
// Meshes data processing
- imesh = RL_MALLOC(sizeof(IQMMesh)*iqm.num_meshes);
- fseek(iqmFile, iqm.ofs_meshes, SEEK_SET);
- fread(imesh, sizeof(IQMMesh)*iqm.num_meshes, 1, iqmFile);
+ imesh = RL_MALLOC(sizeof(IQMMesh)*iqmHeader->num_meshes);
+ //fseek(iqmFile, iqmHeader->ofs_meshes, SEEK_SET);
+ //fread(imesh, sizeof(IQMMesh)*iqmHeader->num_meshes, 1, iqmFile);
+ memcpy(imesh, fileDataPtr + iqmHeader->ofs_meshes, iqmHeader->num_meshes*sizeof(IQMMesh));
- model.meshCount = iqm.num_meshes;
+ model.meshCount = iqmHeader->num_meshes;
model.meshes = RL_CALLOC(model.meshCount, sizeof(Mesh));
+ model.materialCount = model.meshCount;
+ model.materials = (Material *)RL_CALLOC(model.materialCount, sizeof(Material));
+ model.meshMaterial = (int *)RL_CALLOC(model.meshCount, sizeof(int));
+
char name[MESH_NAME_LENGTH] = { 0 };
+ char material[MATERIAL_NAME_LENGTH] = { 0 };
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...
+ //fseek(iqmFile, iqmHeader->ofs_text + imesh[i].name, SEEK_SET);
+ //fread(name, sizeof(char)*MESH_NAME_LENGTH, 1, iqmFile);
+ memcpy(name, fileDataPtr + iqmHeader->ofs_text + imesh[i].name, MESH_NAME_LENGTH*sizeof(char));
+
+ //fseek(iqmFile, iqmHeader->ofs_text + imesh[i].material, SEEK_SET);
+ //fread(material, sizeof(char)*MATERIAL_NAME_LENGTH, 1, iqmFile);
+ memcpy(material, fileDataPtr + iqmHeader->ofs_text + imesh[i].material, MATERIAL_NAME_LENGTH*sizeof(char));
+
+ model.materials[i] = LoadMaterialDefault();
+
+ TRACELOG(LOG_DEBUG, "MODEL: [%s] mesh name (%s), material (%s)", fileName, name, material);
+
model.meshes[i].vertexCount = imesh[i].num_vertexes;
model.meshes[i].vertices = RL_CALLOC(model.meshes[i].vertexCount*3, sizeof(float)); // Default vertex positions
@@ -3160,22 +3499,23 @@ static Model LoadIQM(const char *fileName)
// NOTE: Animated vertex should be re-uploaded to GPU (if not using GPU skinning)
model.meshes[i].animVertices = RL_CALLOC(model.meshes[i].vertexCount*3, sizeof(float));
model.meshes[i].animNormals = RL_CALLOC(model.meshes[i].vertexCount*3, sizeof(float));
-
- model.meshes[i].vboId = (unsigned int *)RL_CALLOC(MAX_MESH_VBO, sizeof(unsigned int));
}
// Triangles data processing
- tri = RL_MALLOC(iqm.num_triangles*sizeof(IQMTriangle));
- fseek(iqmFile, iqm.ofs_triangles, SEEK_SET);
- fread(tri, iqm.num_triangles*sizeof(IQMTriangle), 1, iqmFile);
+ tri = RL_MALLOC(iqmHeader->num_triangles*sizeof(IQMTriangle));
+ //fseek(iqmFile, iqmHeader->ofs_triangles, SEEK_SET);
+ //fread(tri, iqmHeader->num_triangles*sizeof(IQMTriangle), 1, iqmFile);
+ memcpy(tri, fileDataPtr + iqmHeader->ofs_triangles, iqmHeader->num_triangles*sizeof(IQMTriangle));
for (int m = 0; m < model.meshCount; m++)
{
int tcounter = 0;
- for (int i = imesh[m].first_triangle; i < (imesh[m].first_triangle + imesh[m].num_triangles); i++)
+ for (unsigned 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
+ // IQM triangles indexes are stored in counter-clockwise, but raylib processes the index in linear order,
+ // expecting they point to the counter-clockwise vertex triangle, so we need to reverse triangle indexes
+ // NOTE: raylib renders vertex data in counter-clockwise order (standard convention) by default
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;
@@ -3184,24 +3524,26 @@ static Model LoadIQM(const char *fileName)
}
// Vertex arrays data processing
- va = RL_MALLOC(iqm.num_vertexarrays*sizeof(IQMVertexArray));
- fseek(iqmFile, iqm.ofs_vertexarrays, SEEK_SET);
- fread(va, iqm.num_vertexarrays*sizeof(IQMVertexArray), 1, iqmFile);
+ va = RL_MALLOC(iqmHeader->num_vertexarrays*sizeof(IQMVertexArray));
+ //fseek(iqmFile, iqmHeader->ofs_vertexarrays, SEEK_SET);
+ //fread(va, iqmHeader->num_vertexarrays*sizeof(IQMVertexArray), 1, iqmFile);
+ memcpy(va, fileDataPtr + iqmHeader->ofs_vertexarrays, iqmHeader->num_vertexarrays*sizeof(IQMVertexArray));
- for (int i = 0; i < iqm.num_vertexarrays; i++)
+ for (unsigned int i = 0; i < iqmHeader->num_vertexarrays; i++)
{
switch (va[i].type)
{
case IQM_POSITION:
{
- vertex = RL_MALLOC(iqm.num_vertexes*3*sizeof(float));
- fseek(iqmFile, va[i].offset, SEEK_SET);
- fread(vertex, iqm.num_vertexes*3*sizeof(float), 1, iqmFile);
+ vertex = RL_MALLOC(iqmHeader->num_vertexes*3*sizeof(float));
+ //fseek(iqmFile, va[i].offset, SEEK_SET);
+ //fread(vertex, iqmHeader->num_vertexes*3*sizeof(float), 1, iqmFile);
+ memcpy(vertex, fileDataPtr + va[i].offset, iqmHeader->num_vertexes*3*sizeof(float));
- for (int m = 0; m < iqm.num_meshes; m++)
+ for (unsigned int m = 0; m < iqmHeader->num_meshes; m++)
{
int vCounter = 0;
- for (int i = imesh[m].first_vertex*3; i < (imesh[m].first_vertex + imesh[m].num_vertexes)*3; i++)
+ for (unsigned int i = imesh[m].first_vertex*3; i < (imesh[m].first_vertex + imesh[m].num_vertexes)*3; i++)
{
model.meshes[m].vertices[vCounter] = vertex[i];
model.meshes[m].animVertices[vCounter] = vertex[i];
@@ -3211,14 +3553,15 @@ static Model LoadIQM(const char *fileName)
} break;
case IQM_NORMAL:
{
- normal = RL_MALLOC(iqm.num_vertexes*3*sizeof(float));
- fseek(iqmFile, va[i].offset, SEEK_SET);
- fread(normal, iqm.num_vertexes*3*sizeof(float), 1, iqmFile);
+ normal = RL_MALLOC(iqmHeader->num_vertexes*3*sizeof(float));
+ //fseek(iqmFile, va[i].offset, SEEK_SET);
+ //fread(normal, iqmHeader->num_vertexes*3*sizeof(float), 1, iqmFile);
+ memcpy(normal, fileDataPtr + va[i].offset, iqmHeader->num_vertexes*3*sizeof(float));
- for (int m = 0; m < iqm.num_meshes; m++)
+ for (unsigned int m = 0; m < iqmHeader->num_meshes; m++)
{
int vCounter = 0;
- for (int i = imesh[m].first_vertex*3; i < (imesh[m].first_vertex + imesh[m].num_vertexes)*3; i++)
+ for (unsigned 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];
@@ -3228,14 +3571,15 @@ static Model LoadIQM(const char *fileName)
} break;
case IQM_TEXCOORD:
{
- text = RL_MALLOC(iqm.num_vertexes*2*sizeof(float));
- fseek(iqmFile, va[i].offset, SEEK_SET);
- fread(text, iqm.num_vertexes*2*sizeof(float), 1, iqmFile);
+ text = RL_MALLOC(iqmHeader->num_vertexes*2*sizeof(float));
+ //fseek(iqmFile, va[i].offset, SEEK_SET);
+ //fread(text, iqmHeader->num_vertexes*2*sizeof(float), 1, iqmFile);
+ memcpy(text, fileDataPtr + va[i].offset, iqmHeader->num_vertexes*2*sizeof(float));
- for (int m = 0; m < iqm.num_meshes; m++)
+ for (unsigned int m = 0; m < iqmHeader->num_meshes; m++)
{
int vCounter = 0;
- for (int i = imesh[m].first_vertex*2; i < (imesh[m].first_vertex + imesh[m].num_vertexes)*2; i++)
+ for (unsigned int i = imesh[m].first_vertex*2; i < (imesh[m].first_vertex + imesh[m].num_vertexes)*2; i++)
{
model.meshes[m].texcoords[vCounter] = text[i];
vCounter++;
@@ -3244,14 +3588,15 @@ static Model LoadIQM(const char *fileName)
} break;
case IQM_BLENDINDEXES:
{
- blendi = RL_MALLOC(iqm.num_vertexes*4*sizeof(char));
- fseek(iqmFile, va[i].offset, SEEK_SET);
- fread(blendi, iqm.num_vertexes*4*sizeof(char), 1, iqmFile);
+ blendi = RL_MALLOC(iqmHeader->num_vertexes*4*sizeof(char));
+ //fseek(iqmFile, va[i].offset, SEEK_SET);
+ //fread(blendi, iqmHeader->num_vertexes*4*sizeof(char), 1, iqmFile);
+ memcpy(blendi, fileDataPtr + va[i].offset, iqmHeader->num_vertexes*4*sizeof(char));
- for (int m = 0; m < iqm.num_meshes; m++)
+ for (unsigned int m = 0; m < iqmHeader->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++)
+ for (unsigned 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++;
@@ -3260,14 +3605,15 @@ static Model LoadIQM(const char *fileName)
} break;
case IQM_BLENDWEIGHTS:
{
- blendw = RL_MALLOC(iqm.num_vertexes*4*sizeof(unsigned char));
- fseek(iqmFile, va[i].offset, SEEK_SET);
- fread(blendw, iqm.num_vertexes*4*sizeof(unsigned char), 1, iqmFile);
+ blendw = RL_MALLOC(iqmHeader->num_vertexes*4*sizeof(unsigned char));
+ //fseek(iqmFile, va[i].offset, SEEK_SET);
+ //fread(blendw, iqmHeader->num_vertexes*4*sizeof(unsigned char), 1, iqmFile);
+ memcpy(blendw, fileDataPtr + va[i].offset, iqmHeader->num_vertexes*4*sizeof(unsigned char));
- for (int m = 0; m < iqm.num_meshes; m++)
+ for (unsigned int m = 0; m < iqmHeader->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++)
+ for (unsigned 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++;
@@ -3278,20 +3624,22 @@ static Model LoadIQM(const char *fileName)
}
// Bones (joints) data processing
- ijoint = RL_MALLOC(iqm.num_joints*sizeof(IQMJoint));
- fseek(iqmFile, iqm.ofs_joints, SEEK_SET);
- fread(ijoint, iqm.num_joints*sizeof(IQMJoint), 1, iqmFile);
+ ijoint = RL_MALLOC(iqmHeader->num_joints*sizeof(IQMJoint));
+ //fseek(iqmFile, iqmHeader->ofs_joints, SEEK_SET);
+ //fread(ijoint, iqmHeader->num_joints*sizeof(IQMJoint), 1, iqmFile);
+ memcpy(ijoint, fileDataPtr + iqmHeader->ofs_joints, iqmHeader->num_joints*sizeof(IQMJoint));
- model.boneCount = iqm.num_joints;
- model.bones = RL_MALLOC(iqm.num_joints*sizeof(BoneInfo));
- model.bindPose = RL_MALLOC(iqm.num_joints*sizeof(Transform));
+ model.boneCount = iqmHeader->num_joints;
+ model.bones = RL_MALLOC(iqmHeader->num_joints*sizeof(BoneInfo));
+ model.bindPose = RL_MALLOC(iqmHeader->num_joints*sizeof(Transform));
- for (int i = 0; i < iqm.num_joints; i++)
+ for (unsigned int i = 0; i < iqmHeader->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, BONE_NAME_LENGTH*sizeof(char), 1, iqmFile);
+ //fseek(iqmFile, iqmHeader->ofs_text + ijoint[i].name, SEEK_SET);
+ //fread(model.bones[i].name, BONE_NAME_LENGTH*sizeof(char), 1, iqmFile);
+ memcpy(model.bones[i].name, fileDataPtr + iqmHeader->ofs_text + ijoint[i].name, BONE_NAME_LENGTH*sizeof(char));
// Bind pose (base pose)
model.bindPose[i].translation.x = ijoint[i].translate[0];
@@ -3320,7 +3668,8 @@ static Model LoadIQM(const char *fileName)
}
}
- fclose(iqmFile);
+ RL_FREE(fileData);
+
RL_FREE(imesh);
RL_FREE(tri);
RL_FREE(va);
@@ -3333,6 +3682,218 @@ static Model LoadIQM(const char *fileName)
return model;
}
+
+// Load IQM animation data
+static ModelAnimation* LoadIQMModelAnimations(const char* fileName, int* animCount)
+{
+#define IQM_MAGIC "INTERQUAKEMODEL" // IQM file magic number
+#define IQM_VERSION 2 // only IQM version 2 supported
+
+ unsigned int fileSize = 0;
+ unsigned char *fileData = LoadFileData(fileName, &fileSize);
+ unsigned char *fileDataPtr = fileData;
+
+ 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;
+
+ // In case file can not be read, return an empty model
+ if (fileDataPtr == NULL) return NULL;
+
+ // Read IQM header
+ IQMHeader *iqmHeader = (IQMHeader *)fileDataPtr;
+
+ if (memcmp(iqmHeader->magic, IQM_MAGIC, sizeof(IQM_MAGIC)) != 0)
+ {
+ TRACELOG(LOG_WARNING, "MODEL: [%s] IQM file is not a valid model", fileName);
+ return NULL;
+ }
+
+ if (iqmHeader->version != IQM_VERSION)
+ {
+ TRACELOG(LOG_WARNING, "MODEL: [%s] IQM file version not supported (%i)", fileName, iqmHeader->version);
+ return NULL;
+ }
+
+ // Get bones data
+ IQMPose *poses = RL_MALLOC(iqmHeader->num_poses*sizeof(IQMPose));
+ //fseek(iqmFile, iqmHeader->ofs_poses, SEEK_SET);
+ //fread(poses, iqmHeader->num_poses*sizeof(IQMPose), 1, iqmFile);
+ memcpy(poses, fileDataPtr + iqmHeader->ofs_poses, iqmHeader->num_poses*sizeof(IQMPose));
+
+ // Get animations data
+ *animCount = iqmHeader->num_anims;
+ IQMAnim *anim = RL_MALLOC(iqmHeader->num_anims*sizeof(IQMAnim));
+ //fseek(iqmFile, iqmHeader->ofs_anims, SEEK_SET);
+ //fread(anim, iqmHeader->num_anims*sizeof(IQMAnim), 1, iqmFile);
+ memcpy(anim, fileDataPtr + iqmHeader->ofs_anims, iqmHeader->num_anims*sizeof(IQMAnim));
+
+ ModelAnimation *animations = RL_MALLOC(iqmHeader->num_anims*sizeof(ModelAnimation));
+
+ // frameposes
+ unsigned short *framedata = RL_MALLOC(iqmHeader->num_frames*iqmHeader->num_framechannels*sizeof(unsigned short));
+ //fseek(iqmFile, iqmHeader->ofs_frames, SEEK_SET);
+ //fread(framedata, iqmHeader->num_frames*iqmHeader->num_framechannels*sizeof(unsigned short), 1, iqmFile);
+ memcpy(framedata, fileDataPtr + iqmHeader->ofs_frames, iqmHeader->num_frames*iqmHeader->num_framechannels*sizeof(unsigned short));
+
+ for (unsigned int a = 0; a < iqmHeader->num_anims; a++)
+ {
+ animations[a].frameCount = anim[a].num_frames;
+ animations[a].boneCount = iqmHeader->num_poses;
+ animations[a].bones = RL_MALLOC(iqmHeader->num_poses*sizeof(BoneInfo));
+ animations[a].framePoses = RL_MALLOC(anim[a].num_frames*sizeof(Transform *));
+ // animations[a].framerate = anim.framerate; // TODO: Use framerate?
+
+ for (unsigned int j = 0; j < iqmHeader->num_poses; j++)
+ {
+ strcpy(animations[a].bones[j].name, "ANIMJOINTNAME");
+ animations[a].bones[j].parent = poses[j].parent;
+ }
+
+ for (unsigned int j = 0; j < anim[a].num_frames; j++) animations[a].framePoses[j] = RL_MALLOC(iqmHeader->num_poses*sizeof(Transform));
+
+ int dcounter = anim[a].first_frame*iqmHeader->num_framechannels;
+
+ for (unsigned int frame = 0; frame < anim[a].num_frames; frame++)
+ {
+ for (unsigned int i = 0; i < iqmHeader->num_poses; i++)
+ {
+ animations[a].framePoses[frame][i].translation.x = poses[i].channeloffset[0];
+
+ if (poses[i].mask & 0x01)
+ {
+ animations[a].framePoses[frame][i].translation.x += framedata[dcounter]*poses[i].channelscale[0];
+ dcounter++;
+ }
+
+ animations[a].framePoses[frame][i].translation.y = poses[i].channeloffset[1];
+
+ if (poses[i].mask & 0x02)
+ {
+ animations[a].framePoses[frame][i].translation.y += framedata[dcounter]*poses[i].channelscale[1];
+ dcounter++;
+ }
+
+ animations[a].framePoses[frame][i].translation.z = poses[i].channeloffset[2];
+
+ if (poses[i].mask & 0x04)
+ {
+ animations[a].framePoses[frame][i].translation.z += framedata[dcounter]*poses[i].channelscale[2];
+ dcounter++;
+ }
+
+ animations[a].framePoses[frame][i].rotation.x = poses[i].channeloffset[3];
+
+ if (poses[i].mask & 0x08)
+ {
+ animations[a].framePoses[frame][i].rotation.x += framedata[dcounter]*poses[i].channelscale[3];
+ dcounter++;
+ }
+
+ animations[a].framePoses[frame][i].rotation.y = poses[i].channeloffset[4];
+
+ if (poses[i].mask & 0x10)
+ {
+ animations[a].framePoses[frame][i].rotation.y += framedata[dcounter]*poses[i].channelscale[4];
+ dcounter++;
+ }
+
+ animations[a].framePoses[frame][i].rotation.z = poses[i].channeloffset[5];
+
+ if (poses[i].mask & 0x20)
+ {
+ animations[a].framePoses[frame][i].rotation.z += framedata[dcounter]*poses[i].channelscale[5];
+ dcounter++;
+ }
+
+ animations[a].framePoses[frame][i].rotation.w = poses[i].channeloffset[6];
+
+ if (poses[i].mask & 0x40)
+ {
+ animations[a].framePoses[frame][i].rotation.w += framedata[dcounter]*poses[i].channelscale[6];
+ dcounter++;
+ }
+
+ animations[a].framePoses[frame][i].scale.x = poses[i].channeloffset[7];
+
+ if (poses[i].mask & 0x80)
+ {
+ animations[a].framePoses[frame][i].scale.x += framedata[dcounter]*poses[i].channelscale[7];
+ dcounter++;
+ }
+
+ animations[a].framePoses[frame][i].scale.y = poses[i].channeloffset[8];
+
+ if (poses[i].mask & 0x100)
+ {
+ animations[a].framePoses[frame][i].scale.y += framedata[dcounter]*poses[i].channelscale[8];
+ dcounter++;
+ }
+
+ animations[a].framePoses[frame][i].scale.z = poses[i].channeloffset[9];
+
+ if (poses[i].mask & 0x200)
+ {
+ animations[a].framePoses[frame][i].scale.z += framedata[dcounter]*poses[i].channelscale[9];
+ dcounter++;
+ }
+
+ animations[a].framePoses[frame][i].rotation = QuaternionNormalize(animations[a].framePoses[frame][i].rotation);
+ }
+ }
+
+ // Build frameposes
+ for (unsigned int frame = 0; frame < anim[a].num_frames; frame++)
+ {
+ for (int i = 0; i < animations[a].boneCount; i++)
+ {
+ if (animations[a].bones[i].parent >= 0)
+ {
+ animations[a].framePoses[frame][i].rotation = QuaternionMultiply(animations[a].framePoses[frame][animations[a].bones[i].parent].rotation, animations[a].framePoses[frame][i].rotation);
+ animations[a].framePoses[frame][i].translation = Vector3RotateByQuaternion(animations[a].framePoses[frame][i].translation, animations[a].framePoses[frame][animations[a].bones[i].parent].rotation);
+ animations[a].framePoses[frame][i].translation = Vector3Add(animations[a].framePoses[frame][i].translation, animations[a].framePoses[frame][animations[a].bones[i].parent].translation);
+ animations[a].framePoses[frame][i].scale = Vector3Multiply(animations[a].framePoses[frame][i].scale, animations[a].framePoses[frame][animations[a].bones[i].parent].scale);
+ }
+ }
+ }
+ }
+
+ RL_FREE(fileData);
+
+ RL_FREE(framedata);
+ RL_FREE(poses);
+ RL_FREE(anim);
+
+ return animations;
+}
+
#endif
#if defined(SUPPORT_FILEFORMAT_GLTF)
@@ -3430,13 +3991,18 @@ static Image LoadImageFromCgltfImage(cgltf_image *image, const char *texPath, Co
if (image->uri[i] == 0) TRACELOG(LOG_WARNING, "IMAGE: glTF data URI is not a valid image");
else
{
- int size;
+ int size = 0;
unsigned char *data = DecodeBase64(image->uri + i + 1, &size);
- int w, h;
- unsigned char *raw = stbi_load_from_memory(data, size, &w, &h, NULL, 4);
+ int width, height;
+ unsigned char *raw = stbi_load_from_memory(data, size, &width, &height, NULL, 4);
+ RL_FREE(data);
- rimage = LoadImagePro(raw, w, h, UNCOMPRESSED_R8G8B8A8);
+ rimage.data = raw;
+ rimage.width = width;
+ rimage.height = height;
+ rimage.format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8;
+ rimage.mipmaps = 1;
// TODO: Tint shouldn't be applied here!
ImageColorTint(&rimage, tint);
@@ -3453,31 +4019,52 @@ static Image LoadImageFromCgltfImage(cgltf_image *image, const char *texPath, Co
else if (image->buffer_view)
{
unsigned char *data = RL_MALLOC(image->buffer_view->size);
- int n = image->buffer_view->offset;
- int stride = image->buffer_view->stride ? image->buffer_view->stride : 1;
+ int n = (int)image->buffer_view->offset;
+ int stride = (int)image->buffer_view->stride ? (int)image->buffer_view->stride : 1;
- for (int i = 0; i < image->buffer_view->size; i++)
+ for (unsigned int i = 0; i < image->buffer_view->size; i++)
{
data[i] = ((unsigned char *)image->buffer_view->buffer->data)[n];
n += stride;
}
- int w, h;
- unsigned char *raw = stbi_load_from_memory(data, image->buffer_view->size, &w, &h, NULL, 4);
- free(data);
+ int width, height;
+ unsigned char *raw = stbi_load_from_memory(data, (int)image->buffer_view->size, &width, &height, NULL, 4);
+ RL_FREE(data);
- rimage = LoadImagePro(raw, w, h, UNCOMPRESSED_R8G8B8A8);
- free(raw);
+ rimage.data = raw;
+ rimage.width = width;
+ rimage.height = height;
+ rimage.format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8;
+ rimage.mipmaps = 1;
// TODO: Tint shouldn't be applied here!
ImageColorTint(&rimage, tint);
}
- else
+ else rimage = GenImageColor(1, 1, tint);
+
+ return rimage;
+}
+
+
+static bool GLTFReadValue(cgltf_accessor* acc, unsigned int index, void *variable, unsigned int elements, unsigned int size)
+{
+ if (acc->count == 2)
{
- rimage = LoadImageEx(&tint, 1, 1);
+ if (index > 1) return false;
+
+ memcpy(variable, index == 0 ? acc->min : acc->max, elements*size);
+ return true;
}
- return rimage;
+ unsigned int stride = size*elements;
+ memset(variable, 0, stride);
+
+ if (acc->buffer_view == NULL || acc->buffer_view->buffer == NULL || acc->buffer_view->buffer->data == NULL) return false;
+
+ void* readPosition = ((char *)acc->buffer_view->buffer->data) + (index*stride) + acc->buffer_view->offset + acc->offset;
+ memcpy(variable, readPosition, stride);
+ return true;
}
// LoadGLTF loads in model data from given filename, supporting both .gltf and .glb
@@ -3485,7 +4072,7 @@ static Model LoadGLTF(const char *fileName)
{
/***********************************************************************************
- Function implemented by Wilhem Barbier(@wbrbr), with modifications by Tyler Bezera(@gamerfiend)
+ Function implemented by Wilhem Barbier(@wbrbr), with modifications by Tyler Bezera(@gamerfiend) and Hristo Stamenov(@object71)
Features:
- Supports .gltf and .glb files
@@ -3501,47 +4088,23 @@ static Model LoadGLTF(const char *fileName)
*************************************************************************************/
- #define LOAD_ACCESSOR(type, nbcomp, acc, dst) \
- { \
- int n = 0; \
- type* buf = (type*)acc->buffer_view->buffer->data+acc->buffer_view->offset/sizeof(type)+acc->offset/sizeof(type); \
- for (int k = 0; k < acc->count; k++) {\
- for (int l = 0; l < nbcomp; l++) {\
- dst[nbcomp*k+l] = buf[n+l];\
- }\
- n += acc->stride/sizeof(type);\
- }\
- }
-
Model model = { 0 };
// glTF file loading
- FILE *gltfFile = fopen(fileName, "rb");
-
- if (gltfFile == NULL)
- {
- TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to open glTF file", fileName);
- return model;
- }
+ unsigned int dataSize = 0;
+ unsigned char *fileData = LoadFileData(fileName, &dataSize);
- fseek(gltfFile, 0, SEEK_END);
- int size = ftell(gltfFile);
- fseek(gltfFile, 0, SEEK_SET);
-
- void *buffer = RL_MALLOC(size);
- fread(buffer, size, 1, gltfFile);
-
- fclose(gltfFile);
+ if (fileData == NULL) return model;
// glTF data loading
cgltf_options options = { 0 };
cgltf_data *data = NULL;
- cgltf_result result = cgltf_parse(&options, buffer, size, &data);
+ cgltf_result result = cgltf_parse(&options, fileData, dataSize, &data);
if (result == cgltf_result_success)
{
- TRACELOG(LOG_INFO, "MODEL: [%s] glTF meshes (%s) count: %i", fileName, (data->file_type == 2)? "glb" : "gltf", data->meshes_count, data->materials_count);
- TRACELOG(LOG_INFO, "MODEL: [%s] glTF materials (%s) count: %i", fileName, (data->file_type == 2)? "glb" : "gltf", data->meshes_count, data->materials_count);
+ TRACELOG(LOG_INFO, "MODEL: [%s] glTF meshes (%s) count: %i", fileName, (data->file_type == 2)? "glb" : "gltf", data->meshes_count);
+ TRACELOG(LOG_INFO, "MODEL: [%s] glTF materials (%s) count: %i", fileName, (data->file_type == 2)? "glb" : "gltf", data->materials_count);
// Read data buffers
result = cgltf_load_buffers(&options, data, fileName);
@@ -3549,107 +4112,97 @@ static Model LoadGLTF(const char *fileName)
int primitivesCount = 0;
- for (int i = 0; i < data->meshes_count; i++) primitivesCount += (int)data->meshes[i].primitives_count;
+ for (unsigned int i = 0; i < data->meshes_count; i++)
+ primitivesCount += (int)data->meshes[i].primitives_count;
// Process glTF data and map to model
model.meshCount = primitivesCount;
model.meshes = RL_CALLOC(model.meshCount, sizeof(Mesh));
- model.materialCount = data->materials_count + 1;
+ model.materialCount = (int)data->materials_count + 1;
model.materials = RL_MALLOC(model.materialCount*sizeof(Material));
model.meshMaterial = RL_MALLOC(model.meshCount*sizeof(int));
+ model.boneCount = (int)data->nodes_count;
+ model.bones = RL_CALLOC(model.boneCount, sizeof(BoneInfo));
+ model.bindPose = RL_CALLOC(model.boneCount, sizeof(Transform));
- for (int i = 0; i < model.meshCount; i++) model.meshes[i].vboId = (unsigned int *)RL_CALLOC(MAX_MESH_VBO, sizeof(unsigned int));
-
- for (int i = 0; i < model.materialCount - 1; i++)
- {
- model.materials[i] = LoadMaterialDefault();
- Color tint = (Color){ 255, 255, 255, 255 };
- const char *texPath = GetDirectoryPath(fileName);
-
- //Ensure material follows raylib support for PBR (metallic/roughness flow)
- if (data->materials[i].has_pbr_metallic_roughness)
- {
- tint.r = (unsigned char)(data->materials[i].pbr_metallic_roughness.base_color_factor[0] * 255);
- tint.g = (unsigned char)(data->materials[i].pbr_metallic_roughness.base_color_factor[1] * 255);
- tint.b = (unsigned char)(data->materials[i].pbr_metallic_roughness.base_color_factor[2] * 255);
- tint.a = (unsigned char)(data->materials[i].pbr_metallic_roughness.base_color_factor[3] * 255);
-
- model.materials[i].maps[MAP_ALBEDO].color = tint;
-
- if (data->materials[i].pbr_metallic_roughness.base_color_texture.texture)
- {
- Image albedo = LoadImageFromCgltfImage(data->materials[i].pbr_metallic_roughness.base_color_texture.texture->image, texPath, tint);
- model.materials[i].maps[MAP_ALBEDO].texture = LoadTextureFromImage(albedo);
- UnloadImage(albedo);
- }
-
- tint = WHITE; // Set tint to white after it's been used by Albedo
-
- if (data->materials[i].pbr_metallic_roughness.metallic_roughness_texture.texture)
- {
- Image metallicRoughness = LoadImageFromCgltfImage(data->materials[i].pbr_metallic_roughness.metallic_roughness_texture.texture->image, texPath, tint);
- model.materials[i].maps[MAP_ROUGHNESS].texture = LoadTextureFromImage(metallicRoughness);
-
- float roughness = data->materials[i].pbr_metallic_roughness.roughness_factor;
- model.materials[i].maps[MAP_ROUGHNESS].value = roughness;
-
- float metallic = data->materials[i].pbr_metallic_roughness.metallic_factor;
- model.materials[i].maps[MAP_METALNESS].value = metallic;
-
- UnloadImage(metallicRoughness);
- }
-
- if (data->materials[i].normal_texture.texture)
- {
- Image normalImage = LoadImageFromCgltfImage(data->materials[i].normal_texture.texture->image, texPath, tint);
- model.materials[i].maps[MAP_NORMAL].texture = LoadTextureFromImage(normalImage);
- UnloadImage(normalImage);
- }
-
- if (data->materials[i].occlusion_texture.texture)
- {
- Image occulsionImage = LoadImageFromCgltfImage(data->materials[i].occlusion_texture.texture->image, texPath, tint);
- model.materials[i].maps[MAP_OCCLUSION].texture = LoadTextureFromImage(occulsionImage);
- UnloadImage(occulsionImage);
- }
-
- if (data->materials[i].emissive_texture.texture)
- {
- Image emissiveImage = LoadImageFromCgltfImage(data->materials[i].emissive_texture.texture->image, texPath, tint);
- model.materials[i].maps[MAP_EMISSION].texture = LoadTextureFromImage(emissiveImage);
- tint.r = (unsigned char)(data->materials[i].emissive_factor[0]*255);
- tint.g = (unsigned char)(data->materials[i].emissive_factor[1]*255);
- tint.b = (unsigned char)(data->materials[i].emissive_factor[2]*255);
- model.materials[i].maps[MAP_EMISSION].color = tint;
- UnloadImage(emissiveImage);
- }
- }
- }
-
- model.materials[model.materialCount - 1] = LoadMaterialDefault();
+ InitGLTFBones(&model, data);
+ LoadGLTFMaterial(&model, fileName, data);
int primitiveIndex = 0;
- for (int i = 0; i < data->meshes_count; i++)
+ for (unsigned int i = 0; i < data->meshes_count; i++)
{
- for (int p = 0; p < data->meshes[i].primitives_count; p++)
+ for (unsigned int p = 0; p < data->meshes[i].primitives_count; p++)
{
- for (int j = 0; j < data->meshes[i].primitives[p].attributes_count; j++)
+ for (unsigned int j = 0; j < data->meshes[i].primitives[p].attributes_count; j++)
{
if (data->meshes[i].primitives[p].attributes[j].type == cgltf_attribute_type_position)
{
cgltf_accessor *acc = data->meshes[i].primitives[p].attributes[j].data;
- model.meshes[primitiveIndex].vertexCount = acc->count;
- model.meshes[primitiveIndex].vertices = RL_MALLOC(sizeof(float)*model.meshes[primitiveIndex].vertexCount*3);
+ model.meshes[primitiveIndex].vertexCount = (int)acc->count;
+ int bufferSize = model.meshes[primitiveIndex].vertexCount*3*sizeof(float);
+ model.meshes[primitiveIndex].vertices = RL_MALLOC(bufferSize);
+ model.meshes[primitiveIndex].animVertices = RL_MALLOC(bufferSize);
- LOAD_ACCESSOR(float, 3, acc, model.meshes[primitiveIndex].vertices)
+ if (acc->component_type == cgltf_component_type_r_32f)
+ {
+ for (int a = 0; a < acc->count; a++)
+ {
+ GLTFReadValue(acc, a, model.meshes[primitiveIndex].vertices + (a*3), 3, sizeof(float));
+ }
+ }
+ else if (acc->component_type == cgltf_component_type_r_32u)
+ {
+ int readValue[3];
+ for (int a = 0; a < acc->count; a++)
+ {
+ GLTFReadValue(acc, a, readValue, 3, sizeof(int));
+ model.meshes[primitiveIndex].vertices[(a*3) + 0] = (float)readValue[0];
+ model.meshes[primitiveIndex].vertices[(a*3) + 1] = (float)readValue[1];
+ model.meshes[primitiveIndex].vertices[(a*3) + 2] = (float)readValue[2];
+ }
+ }
+ else
+ {
+ // TODO: Support normalized unsigned byte/unsigned short vertices
+ TRACELOG(LOG_WARNING, "MODEL: [%s] glTF vertices must be float or int", fileName);
+ }
+
+ memcpy(model.meshes[primitiveIndex].animVertices, model.meshes[primitiveIndex].vertices, bufferSize);
}
else if (data->meshes[i].primitives[p].attributes[j].type == cgltf_attribute_type_normal)
{
cgltf_accessor *acc = data->meshes[i].primitives[p].attributes[j].data;
- model.meshes[primitiveIndex].normals = RL_MALLOC(sizeof(float)*acc->count*3);
- LOAD_ACCESSOR(float, 3, acc, model.meshes[primitiveIndex].normals)
+ int bufferSize = (int)(acc->count*3*sizeof(float));
+ model.meshes[primitiveIndex].normals = RL_MALLOC(bufferSize);
+ model.meshes[primitiveIndex].animNormals = RL_MALLOC(bufferSize);
+
+ if (acc->component_type == cgltf_component_type_r_32f)
+ {
+ for (int a = 0; a < acc->count; a++)
+ {
+ GLTFReadValue(acc, a, model.meshes[primitiveIndex].normals + (a*3), 3, sizeof(float));
+ }
+ }
+ else if (acc->component_type == cgltf_component_type_r_32u)
+ {
+ int readValue[3];
+ for (int a = 0; a < acc->count; a++)
+ {
+ GLTFReadValue(acc, a, readValue, 3, sizeof(int));
+ model.meshes[primitiveIndex].normals[(a*3) + 0] = (float)readValue[0];
+ model.meshes[primitiveIndex].normals[(a*3) + 1] = (float)readValue[1];
+ model.meshes[primitiveIndex].normals[(a*3) + 2] = (float)readValue[2];
+ }
+ }
+ else
+ {
+ // TODO: Support normalized unsigned byte/unsigned short normals
+ TRACELOG(LOG_WARNING, "MODEL: [%s] glTF normals must be float or int", fileName);
+ }
+
+ memcpy(model.meshes[primitiveIndex].animNormals, model.meshes[primitiveIndex].normals, bufferSize);
}
else if (data->meshes[i].primitives[p].attributes[j].type == cgltf_attribute_type_texcoord)
{
@@ -3657,8 +4210,12 @@ static Model LoadGLTF(const char *fileName)
if (acc->component_type == cgltf_component_type_r_32f)
{
- model.meshes[primitiveIndex].texcoords = RL_MALLOC(sizeof(float)*acc->count*2);
- LOAD_ACCESSOR(float, 2, acc, model.meshes[primitiveIndex].texcoords)
+ model.meshes[primitiveIndex].texcoords = RL_MALLOC(acc->count*2*sizeof(float));
+
+ for (int a = 0; a < acc->count; a++)
+ {
+ GLTFReadValue(acc, a, model.meshes[primitiveIndex].texcoords + (a*2), 2, sizeof(float));
+ }
}
else
{
@@ -3666,50 +4223,607 @@ static Model LoadGLTF(const char *fileName)
TRACELOG(LOG_WARNING, "MODEL: [%s] glTF texture coordinates must be float", fileName);
}
}
+ else if (data->meshes[i].primitives[p].attributes[j].type == cgltf_attribute_type_joints)
+ {
+ cgltf_accessor *acc = data->meshes[i].primitives[p].attributes[j].data;
+ LoadGLTFBoneAttribute(&model, acc, data, primitiveIndex);
+ }
+ else if (data->meshes[i].primitives[p].attributes[j].type == cgltf_attribute_type_weights)
+ {
+ cgltf_accessor *acc = data->meshes[i].primitives[p].attributes[j].data;
+
+ model.meshes[primitiveIndex].boneWeights = RL_MALLOC(acc->count*4*sizeof(float));
+
+ if (acc->component_type == cgltf_component_type_r_32f)
+ {
+ for (int a = 0; a < acc->count; a++)
+ {
+ GLTFReadValue(acc, a, model.meshes[primitiveIndex].boneWeights + (a*4), 4, sizeof(float));
+ }
+ }
+ else if (acc->component_type == cgltf_component_type_r_32u)
+ {
+ unsigned int readValue[4];
+ for (int a = 0; a < acc->count; a++)
+ {
+ GLTFReadValue(acc, a, readValue, 4, sizeof(unsigned int));
+ model.meshes[primitiveIndex].normals[(a*4) + 0] = (float)readValue[0];
+ model.meshes[primitiveIndex].normals[(a*4) + 1] = (float)readValue[1];
+ model.meshes[primitiveIndex].normals[(a*4) + 2] = (float)readValue[2];
+ model.meshes[primitiveIndex].normals[(a*4) + 3] = (float)readValue[3];
+ }
+ }
+ else
+ {
+ // TODO: Support normalized unsigned byte/unsigned short weights
+ TRACELOG(LOG_WARNING, "MODEL: [%s] glTF normals must be float or int", fileName);
+ }
+ }
}
cgltf_accessor *acc = data->meshes[i].primitives[p].indices;
+ LoadGLTFModelIndices(&model, acc, primitiveIndex);
- if (acc)
+ if (data->meshes[i].primitives[p].material)
+ {
+ // Compute the offset
+ model.meshMaterial[primitiveIndex] = (int)(data->meshes[i].primitives[p].material - data->materials);
+ }
+ else
{
- if (acc->component_type == cgltf_component_type_r_16u)
+ model.meshMaterial[primitiveIndex] = model.materialCount - 1;
+ }
+
+ BindGLTFPrimitiveToBones(&model, data, primitiveIndex);
+
+ primitiveIndex++;
+ }
+
+ }
+
+ cgltf_free(data);
+ }
+ else TRACELOG(LOG_WARNING, "MODEL: [%s] Failed to load glTF data", fileName);
+
+ RL_FREE(fileData);
+
+ return model;
+}
+
+static void InitGLTFBones(Model* model, const cgltf_data* data)
+{
+ for (unsigned int j = 0; j < data->nodes_count; j++)
+ {
+ strcpy(model->bones[j].name, data->nodes[j].name == 0 ? "ANIMJOINT" : data->nodes[j].name);
+ model->bones[j].parent = (data->nodes[j].parent != NULL) ? (int)(data->nodes[j].parent - data->nodes) : -1;
+ }
+
+ for (unsigned int i = 0; i < data->nodes_count; i++)
+ {
+ if (data->nodes[i].has_translation) memcpy(&model->bindPose[i].translation, data->nodes[i].translation, 3*sizeof(float));
+ else model->bindPose[i].translation = Vector3Zero();
+
+ if (data->nodes[i].has_rotation) memcpy(&model->bindPose[i].rotation, data->nodes[i].rotation, 4*sizeof(float));
+ else model->bindPose[i].rotation = QuaternionIdentity();
+
+ model->bindPose[i].rotation = QuaternionNormalize(model->bindPose[i].rotation);
+
+ if (data->nodes[i].has_scale) memcpy(&model->bindPose[i].scale, data->nodes[i].scale, 3*sizeof(float));
+ else model->bindPose[i].scale = Vector3One();
+ }
+
+ {
+ bool* completedBones = RL_CALLOC(model->boneCount, sizeof(bool));
+ int numberCompletedBones = 0;
+
+ while (numberCompletedBones < model->boneCount) {
+ for (int i = 0; i < model->boneCount; i++)
+ {
+ if (completedBones[i]) continue;
+
+ if (model->bones[i].parent < 0) {
+ completedBones[i] = true;
+ numberCompletedBones++;
+ continue;
+ }
+
+ if (!completedBones[model->bones[i].parent]) continue;
+
+ Transform* currentTransform = &model->bindPose[i];
+ BoneInfo* currentBone = &model->bones[i];
+ int root = currentBone->parent;
+ if (root >= model->boneCount)
+ root = 0;
+ Transform* parentTransform = &model->bindPose[root];
+
+ currentTransform->rotation = QuaternionMultiply(parentTransform->rotation, currentTransform->rotation);
+ currentTransform->translation = Vector3RotateByQuaternion(currentTransform->translation, parentTransform->rotation);
+ currentTransform->translation = Vector3Add(currentTransform->translation, parentTransform->translation);
+ currentTransform->scale = Vector3Multiply(currentTransform->scale, parentTransform->scale);
+ completedBones[i] = true;
+ numberCompletedBones++;
+ }
+ }
+
+ RL_FREE(completedBones);
+ }
+}
+
+static void LoadGLTFMaterial(Model* model, const char* fileName, const cgltf_data* data)
+{
+ for (int i = 0; i < model->materialCount - 1; i++)
+ {
+ model->materials[i] = LoadMaterialDefault();
+ Color tint = (Color){ 255, 255, 255, 255 };
+ const char *texPath = GetDirectoryPath(fileName);
+
+ // Ensure material follows raylib support for PBR (metallic/roughness flow)
+ if (data->materials[i].has_pbr_metallic_roughness)
+ {
+ tint.r = (unsigned char)(data->materials[i].pbr_metallic_roughness.base_color_factor[0]*255);
+ tint.g = (unsigned char)(data->materials[i].pbr_metallic_roughness.base_color_factor[1]*255);
+ tint.b = (unsigned char)(data->materials[i].pbr_metallic_roughness.base_color_factor[2]*255);
+ tint.a = (unsigned char)(data->materials[i].pbr_metallic_roughness.base_color_factor[3]*255);
+
+ model->materials[i].maps[MATERIAL_MAP_ALBEDO].color = tint;
+
+ if (data->materials[i].pbr_metallic_roughness.base_color_texture.texture)
+ {
+ Image albedo = LoadImageFromCgltfImage(data->materials[i].pbr_metallic_roughness.base_color_texture.texture->image, texPath, tint);
+ model->materials[i].maps[MATERIAL_MAP_ALBEDO].texture = LoadTextureFromImage(albedo);
+ UnloadImage(albedo);
+ }
+
+ tint = WHITE; // Set tint to white after it's been used by Albedo
+
+ if (data->materials[i].pbr_metallic_roughness.metallic_roughness_texture.texture)
+ {
+ Image metallicRoughness = LoadImageFromCgltfImage(data->materials[i].pbr_metallic_roughness.metallic_roughness_texture.texture->image, texPath, tint);
+ model->materials[i].maps[MATERIAL_MAP_ROUGHNESS].texture = LoadTextureFromImage(metallicRoughness);
+
+ float roughness = data->materials[i].pbr_metallic_roughness.roughness_factor;
+ model->materials[i].maps[MATERIAL_MAP_ROUGHNESS].value = roughness;
+
+ float metallic = data->materials[i].pbr_metallic_roughness.metallic_factor;
+ model->materials[i].maps[MATERIAL_MAP_METALNESS].value = metallic;
+
+ UnloadImage(metallicRoughness);
+ }
+
+ if (data->materials[i].normal_texture.texture)
+ {
+ Image normalImage = LoadImageFromCgltfImage(data->materials[i].normal_texture.texture->image, texPath, tint);
+ model->materials[i].maps[MATERIAL_MAP_NORMAL].texture = LoadTextureFromImage(normalImage);
+ UnloadImage(normalImage);
+ }
+
+ if (data->materials[i].occlusion_texture.texture)
+ {
+ Image occulsionImage = LoadImageFromCgltfImage(data->materials[i].occlusion_texture.texture->image, texPath, tint);
+ model->materials[i].maps[MATERIAL_MAP_OCCLUSION].texture = LoadTextureFromImage(occulsionImage);
+ UnloadImage(occulsionImage);
+ }
+
+ if (data->materials[i].emissive_texture.texture)
+ {
+ Image emissiveImage = LoadImageFromCgltfImage(data->materials[i].emissive_texture.texture->image, texPath, tint);
+ model->materials[i].maps[MATERIAL_MAP_EMISSION].texture = LoadTextureFromImage(emissiveImage);
+ tint.r = (unsigned char)(data->materials[i].emissive_factor[0]*255);
+ tint.g = (unsigned char)(data->materials[i].emissive_factor[1]*255);
+ tint.b = (unsigned char)(data->materials[i].emissive_factor[2]*255);
+ model->materials[i].maps[MATERIAL_MAP_EMISSION].color = tint;
+ UnloadImage(emissiveImage);
+ }
+ }
+ }
+
+ model->materials[model->materialCount - 1] = LoadMaterialDefault();
+}
+
+static void LoadGLTFBoneAttribute(Model* model, cgltf_accessor* jointsAccessor, const cgltf_data* data, int primitiveIndex)
+{
+ if (jointsAccessor->component_type == cgltf_component_type_r_16u)
+ {
+ model->meshes[primitiveIndex].boneIds = RL_MALLOC(sizeof(int)*jointsAccessor->count*4);
+ short* bones = RL_MALLOC(sizeof(short)*jointsAccessor->count*4);
+
+ for (int a = 0; a < jointsAccessor->count; a++)
+ {
+ GLTFReadValue(jointsAccessor, a, bones + (a*4), 4, sizeof(short));
+ }
+
+ for (unsigned int a = 0; a < jointsAccessor->count*4; a++)
+ {
+ cgltf_node* skinJoint = data->skins->joints[bones[a]];
+
+ for (unsigned int k = 0; k < data->nodes_count; k++)
+ {
+ if (&(data->nodes[k]) == skinJoint)
+ {
+ model->meshes[primitiveIndex].boneIds[a] = k;
+ break;
+ }
+ }
+ }
+ RL_FREE(bones);
+ }
+ else if (jointsAccessor->component_type == cgltf_component_type_r_8u)
+ {
+ model->meshes[primitiveIndex].boneIds = RL_MALLOC(sizeof(int)*jointsAccessor->count*4);
+ unsigned char* bones = RL_MALLOC(sizeof(unsigned char)*jointsAccessor->count*4);
+
+ for (int a = 0; a < jointsAccessor->count; a++)
+ {
+ GLTFReadValue(jointsAccessor, a, bones + (a*4), 4, sizeof(unsigned char));
+ }
+
+ for (unsigned int a = 0; a < jointsAccessor->count*4; a++)
+ {
+ cgltf_node* skinJoint = data->skins->joints[bones[a]];
+
+ for (unsigned int k = 0; k < data->nodes_count; k++)
+ {
+ if (&(data->nodes[k]) == skinJoint)
+ {
+ model->meshes[primitiveIndex].boneIds[a] = k;
+ break;
+ }
+ }
+ }
+ RL_FREE(bones);
+ }
+ else
+ {
+ // TODO: Support other size of bone index?
+ TRACELOG(LOG_WARNING, "MODEL: glTF bones in unexpected format");
+ }
+}
+
+static void BindGLTFPrimitiveToBones(Model* model, const cgltf_data* data, int primitiveIndex)
+{
+ if (model->meshes[primitiveIndex].boneIds == NULL && data->nodes_count > 0)
+ {
+ for (int nodeId = 0; nodeId < data->nodes_count; nodeId++)
+ {
+ if (data->nodes[nodeId].mesh == &(data->meshes[primitiveIndex]))
+ {
+ model->meshes[primitiveIndex].boneIds = RL_CALLOC(4*model->meshes[primitiveIndex].vertexCount, sizeof(int));
+ model->meshes[primitiveIndex].boneWeights = RL_CALLOC(4*model->meshes[primitiveIndex].vertexCount, sizeof(float));
+
+ for (int b = 0; b < 4*model->meshes[primitiveIndex].vertexCount; b++)
+ {
+ if (b%4 == 0)
{
- model.meshes[primitiveIndex].triangleCount = acc->count/3;
- model.meshes[primitiveIndex].indices = RL_MALLOC(sizeof(unsigned short)*model.meshes[primitiveIndex].triangleCount*3);
- LOAD_ACCESSOR(unsigned short, 1, acc, model.meshes[primitiveIndex].indices)
+ model->meshes[primitiveIndex].boneIds[b] = nodeId;
+ model->meshes[primitiveIndex].boneWeights[b] = 1.0f;
}
else
{
- // TODO: Support unsigned byte/unsigned int
- TRACELOG(LOG_WARNING, "MODEL: [%s] glTF index data must be unsigned short", fileName);
+ model->meshes[primitiveIndex].boneIds[b] = 0;
+ model->meshes[primitiveIndex].boneWeights[b] = 0.0f;
}
+
}
- else
+
+ Vector3 boundVertex = { 0 };
+ Vector3 boundNormal = { 0 };
+
+ Vector3 outTranslation = { 0 };
+ Quaternion outRotation = { 0 };
+ Vector3 outScale = { 0 };
+
+ int vCounter = 0;
+ int boneCounter = 0;
+ int boneId = 0;
+
+ for (int i = 0; i < model->meshes[primitiveIndex].vertexCount; i++)
{
- // Unindexed mesh
- model.meshes[primitiveIndex].triangleCount = model.meshes[primitiveIndex].vertexCount/3;
+ boneId = model->meshes[primitiveIndex].boneIds[boneCounter];
+ outTranslation = model->bindPose[boneId].translation;
+ outRotation = model->bindPose[boneId].rotation;
+ outScale = model->bindPose[boneId].scale;
+
+ // Vertices processing
+ boundVertex = (Vector3){ model->meshes[primitiveIndex].vertices[vCounter], model->meshes[primitiveIndex].vertices[vCounter + 1], model->meshes[primitiveIndex].vertices[vCounter + 2] };
+ boundVertex = Vector3Multiply(boundVertex, outScale);
+ boundVertex = Vector3RotateByQuaternion(boundVertex, outRotation);
+ boundVertex = Vector3Add(boundVertex, outTranslation);
+ model->meshes[primitiveIndex].vertices[vCounter] = boundVertex.x;
+ model->meshes[primitiveIndex].vertices[vCounter + 1] = boundVertex.y;
+ model->meshes[primitiveIndex].vertices[vCounter + 2] = boundVertex.z;
+
+ // Normals processing
+ if (model->meshes[primitiveIndex].normals != NULL)
+ {
+ boundNormal = (Vector3){ model->meshes[primitiveIndex].normals[vCounter], model->meshes[primitiveIndex].normals[vCounter + 1], model->meshes[primitiveIndex].normals[vCounter + 2] };
+ boundNormal = Vector3RotateByQuaternion(boundNormal, outRotation);
+ model->meshes[primitiveIndex].normals[vCounter] = boundNormal.x;
+ model->meshes[primitiveIndex].normals[vCounter + 1] = boundNormal.y;
+ model->meshes[primitiveIndex].normals[vCounter + 2] = boundNormal.z;
+ }
+
+ vCounter += 3;
+ boneCounter += 4;
}
+ }
+ }
+ }
+}
- if (data->meshes[i].primitives[p].material)
+static void LoadGLTFModelIndices(Model* model, cgltf_accessor* indexAccessor, int primitiveIndex)
+{
+ if (indexAccessor)
+ {
+ if (indexAccessor->component_type == cgltf_component_type_r_16u || indexAccessor->component_type == cgltf_component_type_r_16)
+ {
+ model->meshes[primitiveIndex].triangleCount = (int)indexAccessor->count/3;
+ model->meshes[primitiveIndex].indices = RL_MALLOC(model->meshes[primitiveIndex].triangleCount*3*sizeof(unsigned short));
+
+ unsigned short readValue = 0;
+ for (int a = 0; a < indexAccessor->count; a++)
+ {
+ GLTFReadValue(indexAccessor, a, &readValue, 1, sizeof(short));
+ model->meshes[primitiveIndex].indices[a] = readValue;
+ }
+ }
+ else if (indexAccessor->component_type == cgltf_component_type_r_8u || indexAccessor->component_type == cgltf_component_type_r_8)
+ {
+ model->meshes[primitiveIndex].triangleCount = (int)indexAccessor->count/3;
+ model->meshes[primitiveIndex].indices = RL_MALLOC(model->meshes[primitiveIndex].triangleCount*3*sizeof(unsigned short));
+
+ unsigned char readValue = 0;
+ for (int a = 0; a < indexAccessor->count; a++)
+ {
+ GLTFReadValue(indexAccessor, a, &readValue, 1, sizeof(char));
+ model->meshes[primitiveIndex].indices[a] = (unsigned short)readValue;
+ }
+ }
+ else if (indexAccessor->component_type == cgltf_component_type_r_32u)
+ {
+ model->meshes[primitiveIndex].triangleCount = (int)indexAccessor->count/3;
+ model->meshes[primitiveIndex].indices = RL_MALLOC(model->meshes[primitiveIndex].triangleCount*3*sizeof(unsigned short));
+
+ unsigned int readValue;
+ for (int a = 0; a < indexAccessor->count; a++)
+ {
+ GLTFReadValue(indexAccessor, a, &readValue, 1, sizeof(unsigned int));
+ model->meshes[primitiveIndex].indices[a] = (unsigned short)readValue;
+ }
+ }
+ }
+ else
+ {
+ // Unindexed mesh
+ model->meshes[primitiveIndex].triangleCount = model->meshes[primitiveIndex].vertexCount/3;
+ }
+}
+
+// LoadGLTF loads in animation data from given filename
+static ModelAnimation *LoadGLTFModelAnimations(const char *fileName, int *animCount)
+{
+ /***********************************************************************************
+
+ Function implemented by Hristo Stamenov (@object71)
+
+ Features:
+ - Supports .gltf and .glb files
+
+ Some restrictions (not exhaustive):
+ - ...
+
+ *************************************************************************************/
+
+ // glTF file loading
+ unsigned int dataSize = 0;
+ unsigned char *fileData = LoadFileData(fileName, &dataSize);
+
+ ModelAnimation *animations = NULL;
+
+ if (fileData == NULL) return animations;
+
+ // glTF data loading
+ cgltf_options options = { 0 };
+ cgltf_data *data = NULL;
+ cgltf_result result = cgltf_parse(&options, fileData, dataSize, &data);
+
+ if (result == cgltf_result_success)
+ {
+ TRACELOG(LOG_INFO, "MODEL: [%s] glTF animations (%s) count: %i", fileName, (data->file_type == 2)? "glb" :
+ "gltf", data->animations_count);
+
+ result = cgltf_load_buffers(&options, data, fileName);
+ if (result != cgltf_result_success) TRACELOG(LOG_WARNING, "MODEL: [%s] unable to load glTF animations data", fileName);
+ animations = RL_MALLOC(data->animations_count*sizeof(ModelAnimation));
+ *animCount = (int)data->animations_count;
+
+ for (unsigned int a = 0; a < data->animations_count; a++)
+ {
+ // gltf animation consists of the following structures:
+ // - nodes - bones
+ // - channels - single transformation type on a single bone
+ // - node - bone
+ // - transformation type (path) - translation, rotation, scale
+ // - sampler - animation samples
+ // - input - points in time this transformation happens
+ // - output - the transformation amount at the given input points in time
+ // - interpolation - the type of interpolation to use between the frames
+
+ cgltf_animation *animation = data->animations + a;
+
+ ModelAnimation *output = animations + a;
+
+ // 30 frames sampled per second
+ const float timeStep = (1.0f/30.0f);
+ float animationDuration = 0.0f;
+
+ // Getting the max animation time to consider for animation duration
+ for (unsigned int i = 0; i < animation->channels_count; i++)
+ {
+ cgltf_animation_channel* channel = animation->channels + i;
+ int frameCounts = (int)channel->sampler->input->count;
+ float lastFrameTime = 0.0f;
+
+ if (GLTFReadValue(channel->sampler->input, frameCounts - 1, &lastFrameTime, 1, sizeof(float)))
{
- // Compute the offset
- model.meshMaterial[primitiveIndex] = data->meshes[i].primitives[p].material - data->materials;
+ animationDuration = fmaxf(lastFrameTime, animationDuration);
}
- else
+ }
+
+ output->frameCount = (int)(animationDuration / timeStep);
+ output->boneCount = (int)data->nodes_count;
+ output->bones = RL_MALLOC(output->boneCount*sizeof(BoneInfo));
+ output->framePoses = RL_MALLOC(output->frameCount*sizeof(Transform *));
+ // output->framerate = // TODO: Use framerate instead of const timestep
+
+ // Name and parent bones
+ for (unsigned int j = 0; j < data->nodes_count; j++)
+ {
+ strcpy(output->bones[j].name, data->nodes[j].name == 0 ? "ANIMJOINT" : data->nodes[j].name);
+ output->bones[j].parent = (data->nodes[j].parent != NULL) ? (int)(data->nodes[j].parent - data->nodes) : -1;
+ }
+
+ // Allocate data for frames
+ // Initiate with zero bone translations
+ for (int frame = 0; frame < output->frameCount; frame++)
+ {
+ output->framePoses[frame] = RL_MALLOC(output->frameCount*data->nodes_count*sizeof(Transform));
+
+ for (unsigned int i = 0; i < data->nodes_count; i++)
{
- model.meshMaterial[primitiveIndex] = model.materialCount - 1;;
+ output->framePoses[frame][i].translation = Vector3Zero();
+ output->framePoses[frame][i].rotation = QuaternionIdentity();
+ output->framePoses[frame][i].rotation = QuaternionNormalize(output->framePoses[frame][i].rotation);
+ output->framePoses[frame][i].scale = Vector3One();
}
+ }
- primitiveIndex++;
+ // for each single transformation type on single bone
+ for (unsigned int channelId = 0; channelId < animation->channels_count; channelId++)
+ {
+ cgltf_animation_channel* channel = animation->channels + channelId;
+ cgltf_animation_sampler* sampler = channel->sampler;
+
+ int boneId = (int)(channel->target_node - data->nodes);
+
+ for (int frame = 0; frame < output->frameCount; frame++)
+ {
+ bool shouldSkipFurtherTransformation = true;
+ int outputMin = 0;
+ int outputMax = 0;
+ float frameTime = frame*timeStep;
+ float lerpPercent = 0.0f;
+
+ // For this transformation:
+ // getting between which input values the current frame time position
+ // and also what is the percent to use in the linear interpolation later
+ for (unsigned int j = 0; j < sampler->input->count; j++)
+ {
+ float inputFrameTime;
+ if (GLTFReadValue(sampler->input, j, &inputFrameTime, 1, sizeof(float)))
+ {
+ if (frameTime < inputFrameTime)
+ {
+ shouldSkipFurtherTransformation = false;
+ outputMin = (j == 0) ? 0 : j - 1;
+ outputMax = j;
+
+ float previousInputTime = 0.0f;
+ if (GLTFReadValue(sampler->input, outputMin, &previousInputTime, 1, sizeof(float)))
+ {
+ if ((inputFrameTime - previousInputTime) != 0)
+ {
+ lerpPercent = (frameTime - previousInputTime)/(inputFrameTime - previousInputTime);
+ }
+ }
+
+ break;
+ }
+ }
+ else break;
+ }
+
+ // If the current transformation has no information for the current frame time point
+ if (shouldSkipFurtherTransformation) continue;
+
+ if (channel->target_path == cgltf_animation_path_type_translation)
+ {
+ Vector3 translationStart;
+ Vector3 translationEnd;
+
+ bool success = GLTFReadValue(sampler->output, outputMin, &translationStart, 3, sizeof(float));
+ success = GLTFReadValue(sampler->output, outputMax, &translationEnd, 3, sizeof(float)) || success;
+
+ if (success) output->framePoses[frame][boneId].translation = Vector3Lerp(translationStart, translationEnd, lerpPercent);
+ }
+ if (channel->target_path == cgltf_animation_path_type_rotation)
+ {
+ Quaternion rotationStart;
+ Quaternion rotationEnd;
+
+ bool success = GLTFReadValue(sampler->output, outputMin, &rotationStart, 4, sizeof(float));
+ success = GLTFReadValue(sampler->output, outputMax, &rotationEnd, 4, sizeof(float)) || success;
+
+ if (success)
+ {
+ output->framePoses[frame][boneId].rotation = QuaternionLerp(rotationStart, rotationEnd, lerpPercent);
+ output->framePoses[frame][boneId].rotation = QuaternionNormalize(output->framePoses[frame][boneId].rotation);
+ }
+ }
+ if (channel->target_path == cgltf_animation_path_type_scale)
+ {
+ Vector3 scaleStart;
+ Vector3 scaleEnd;
+
+ bool success = GLTFReadValue(sampler->output, outputMin, &scaleStart, 3, sizeof(float));
+ success = GLTFReadValue(sampler->output, outputMax, &scaleEnd, 3, sizeof(float)) || success;
+
+ if (success) output->framePoses[frame][boneId].scale = Vector3Lerp(scaleStart, scaleEnd, lerpPercent);
+ }
+ }
}
+
+ // Build frameposes
+ for (int frame = 0; frame < output->frameCount; frame++)
+ {
+ bool *completedBones = RL_CALLOC(output->boneCount, sizeof(bool));
+ int numberCompletedBones = 0;
+
+ while (numberCompletedBones < output->boneCount)
+ {
+ for (int i = 0; i < output->boneCount; i++)
+ {
+ if (completedBones[i]) continue;
+
+ if (output->bones[i].parent < 0)
+ {
+ completedBones[i] = true;
+ numberCompletedBones++;
+ continue;
+ }
+
+ if (!completedBones[output->bones[i].parent]) continue;
+
+ output->framePoses[frame][i].rotation = QuaternionMultiply(output->framePoses[frame][output->bones[i].parent].rotation, output->framePoses[frame][i].rotation);
+ output->framePoses[frame][i].translation = Vector3RotateByQuaternion(output->framePoses[frame][i].translation, output->framePoses[frame][output->bones[i].parent].rotation);
+ output->framePoses[frame][i].translation = Vector3Add(output->framePoses[frame][i].translation, output->framePoses[frame][output->bones[i].parent].translation);
+ output->framePoses[frame][i].scale = Vector3Multiply(output->framePoses[frame][i].scale, output->framePoses[frame][output->bones[i].parent].scale);
+ completedBones[i] = true;
+ numberCompletedBones++;
+ }
+ }
+
+ RL_FREE(completedBones);
+ }
+
}
cgltf_free(data);
}
- else TRACELOG(LOG_WARNING, "MODEL: [%s] Failed to load glTF data", fileName);
+ else TRACELOG(LOG_WARNING, ": [%s] Failed to load glTF data", fileName);
- RL_FREE(buffer);
+ RL_FREE(fileData);
- return model;
+ return animations;
}
+
#endif