#include "raylib.h"
#include "config.h"
#if SUPPORT_MODULE_RMODELS
#include "rlgl.h"
#include "raymath.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#if SUPPORT_FILEFORMAT_OBJ || SUPPORT_FILEFORMAT_MTL
#define TINYOBJ_MALLOC RL_MALLOC
#define TINYOBJ_CALLOC RL_CALLOC
#define TINYOBJ_REALLOC RL_REALLOC
#define TINYOBJ_FREE RL_FREE
#define TINYOBJ_LOADER_C_IMPLEMENTATION
#include "external/tinyobj_loader_c.h"
#endif
#if SUPPORT_FILEFORMAT_GLTF
#define CGLTF_MALLOC RL_MALLOC
#define CGLTF_FREE RL_FREE
#define CGLTF_IMPLEMENTATION
#include "external/cgltf.h"
#endif
#if SUPPORT_FILEFORMAT_GLTF_WRITE
#define CGLTF_WRITE_IMPLEMENTATION
#include "external/cgltf_write.h"
#endif
#if SUPPORT_FILEFORMAT_VOX
#define VOX_MALLOC RL_MALLOC
#define VOX_CALLOC RL_CALLOC
#define VOX_REALLOC RL_REALLOC
#define VOX_FREE RL_FREE
#define VOX_LOADER_IMPLEMENTATION
#include "external/vox_loader.h"
#endif
#if SUPPORT_FILEFORMAT_M3D
#define M3D_MALLOC RL_MALLOC
#define M3D_REALLOC RL_REALLOC
#define M3D_FREE RL_FREE
#define M3D_IMPLEMENTATION
#include "external/m3d.h"
#endif
#if SUPPORT_MESH_GENERATION
#define PAR_MALLOC(T, N) ((T *)RL_MALLOC(N*sizeof(T)))
#define PAR_CALLOC(T, N) ((T *)RL_CALLOC(N*sizeof(T), 1))
#define PAR_REALLOC(T, BUF, N) ((T *)RL_REALLOC(BUF, sizeof(T)*(N)))
#define PAR_FREE RL_FREE
#if defined(_MSC_VER)
#pragma warning(push)
#pragma warning(disable : 4244)
#pragma warning(disable : 4305)
#endif
#define PAR_SHAPES_IMPLEMENTATION
#include "external/par_shapes.h"
#if defined(_MSC_VER)
#pragma warning(pop)
#endif
#endif
#if defined(_WIN32)
#include <direct.h>
#define CHDIR _chdir
#else
#include <unistd.h>
#define CHDIR chdir
#endif
#ifndef MAX_MATERIAL_MAPS
#define MAX_MATERIAL_MAPS 12
#endif
#ifndef MAX_MESH_VERTEX_BUFFERS
#if SUPPORT_GPU_SKINNING
#define MAX_MESH_VERTEX_BUFFERS 9
#else
#define MAX_MESH_VERTEX_BUFFERS 7
#endif
#endif
#ifndef MAX_FILEPATH_LENGTH
#define MAX_FILEPATH_LENGTH 4096
#endif
#if SUPPORT_FILEFORMAT_OBJ
static Model LoadOBJ(const char *fileName); #endif
#if SUPPORT_FILEFORMAT_IQM
static Model LoadIQM(const char *fileName); static ModelAnimation *LoadModelAnimationsIQM(const char *fileName, int *animCount); #endif
#if SUPPORT_FILEFORMAT_GLTF
static Model LoadGLTF(const char *fileName); static ModelAnimation *LoadModelAnimationsGLTF(const char *fileName, int *animCount); #endif
#if SUPPORT_FILEFORMAT_VOX
static Model LoadVOX(const char *filename); #endif
#if SUPPORT_FILEFORMAT_M3D
static Model LoadM3D(const char *filename); static ModelAnimation *LoadModelAnimationsM3D(const char *fileName, int *animCount); #endif
#if SUPPORT_FILEFORMAT_OBJ || SUPPORT_FILEFORMAT_MTL
static void ProcessMaterialsOBJ(Material *rayMaterials, tinyobj_material_t *materials, int materialCount); #endif
static void UpdateModelAnimationVertexBuffers(Model model);
void DrawLine3D(Vector3 startPos, Vector3 endPos, Color color)
{
rlBegin(RL_LINES);
rlColor4ub(color.r, color.g, color.b, color.a);
rlVertex3f(startPos.x, startPos.y, startPos.z);
rlVertex3f(endPos.x, endPos.y, endPos.z);
rlEnd();
}
void DrawPoint3D(Vector3 position, Color color)
{
rlPushMatrix();
rlTranslatef(position.x, position.y, position.z);
rlBegin(RL_LINES);
rlColor4ub(color.r, color.g, color.b, color.a);
rlVertex3f(0.0f, 0.0f, 0.0f);
rlVertex3f(0.0f, 0.0f, 0.1f);
rlEnd();
rlPopMatrix();
}
void DrawCircle3D(Vector3 center, float radius, Vector3 rotationAxis, float rotationAngle, Color color)
{
rlPushMatrix();
rlTranslatef(center.x, center.y, center.z);
rlRotatef(rotationAngle, rotationAxis.x, rotationAxis.y, rotationAxis.z);
rlBegin(RL_LINES);
for (int i = 0; i < 360; i += 10)
{
rlColor4ub(color.r, color.g, color.b, color.a);
rlVertex3f(sinf(DEG2RAD*i)*radius, cosf(DEG2RAD*i)*radius, 0.0f);
rlVertex3f(sinf(DEG2RAD*(i + 10))*radius, cosf(DEG2RAD*(i + 10))*radius, 0.0f);
}
rlEnd();
rlPopMatrix();
}
void DrawTriangle3D(Vector3 v1, Vector3 v2, Vector3 v3, Color color)
{
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();
}
void DrawTriangleStrip3D(const Vector3 *points, int pointCount, Color color)
{
if (pointCount < 3) return;
rlBegin(RL_TRIANGLES);
rlColor4ub(color.r, color.g, color.b, color.a);
for (int i = 2; i < pointCount; 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();
}
void DrawCube(Vector3 position, float width, float height, float length, Color color)
{
float x = 0.0f;
float y = 0.0f;
float z = 0.0f;
rlPushMatrix();
rlTranslatef(position.x, position.y, position.z);
rlBegin(RL_TRIANGLES);
rlColor4ub(color.r, color.g, color.b, color.a);
rlNormal3f(0.0f, 0.0f, 1.0f);
rlVertex3f(x - width/2, y - height/2, z + length/2); rlVertex3f(x + width/2, y - height/2, z + length/2); rlVertex3f(x - width/2, y + height/2, z + length/2);
rlVertex3f(x + width/2, y + height/2, z + length/2); rlVertex3f(x - width/2, y + height/2, z + length/2); rlVertex3f(x + width/2, y - height/2, z + length/2);
rlNormal3f(0.0f, 0.0f, -1.0f);
rlVertex3f(x - width/2, y - height/2, z - length/2); rlVertex3f(x - width/2, y + height/2, z - length/2); rlVertex3f(x + width/2, y - height/2, z - length/2);
rlVertex3f(x + width/2, y + height/2, z - length/2); rlVertex3f(x + width/2, y - height/2, z - length/2); rlVertex3f(x - width/2, y + height/2, z - length/2);
rlNormal3f(0.0f, 1.0f, 0.0f);
rlVertex3f(x - width/2, y + height/2, z - length/2); rlVertex3f(x - width/2, y + height/2, z + length/2); rlVertex3f(x + width/2, y + height/2, z + length/2);
rlVertex3f(x + width/2, y + height/2, z - length/2); rlVertex3f(x - width/2, y + height/2, z - length/2); rlVertex3f(x + width/2, y + height/2, z + length/2);
rlNormal3f(0.0f, -1.0f, 0.0f);
rlVertex3f(x - width/2, y - height/2, z - length/2); rlVertex3f(x + width/2, y - height/2, z + length/2); rlVertex3f(x - width/2, y - height/2, z + length/2);
rlVertex3f(x + width/2, y - height/2, z - length/2); rlVertex3f(x + width/2, y - height/2, z + length/2); rlVertex3f(x - width/2, y - height/2, z - length/2);
rlNormal3f(1.0f, 0.0f, 0.0f);
rlVertex3f(x + width/2, y - height/2, z - length/2); rlVertex3f(x + width/2, y + height/2, z - length/2); rlVertex3f(x + width/2, y + height/2, z + length/2);
rlVertex3f(x + width/2, y - height/2, z + length/2); rlVertex3f(x + width/2, y - height/2, z - length/2); rlVertex3f(x + width/2, y + height/2, z + length/2);
rlNormal3f(-1.0f, 0.0f, 0.0f);
rlVertex3f(x - width/2, y - height/2, z - length/2); rlVertex3f(x - width/2, y + height/2, z + length/2); rlVertex3f(x - width/2, y + height/2, z - length/2);
rlVertex3f(x - width/2, y - height/2, z + length/2); rlVertex3f(x - width/2, y + height/2, z + length/2); rlVertex3f(x - width/2, y - height/2, z - length/2); rlEnd();
rlPopMatrix();
}
void DrawCubeV(Vector3 position, Vector3 size, Color color)
{
DrawCube(position, size.x, size.y, size.z, color);
}
void DrawCubeWires(Vector3 position, float width, float height, float length, Color color)
{
float x = 0.0f;
float y = 0.0f;
float z = 0.0f;
rlPushMatrix();
rlTranslatef(position.x, position.y, position.z);
rlBegin(RL_LINES);
rlColor4ub(color.r, color.g, color.b, color.a);
rlVertex3f(x - width/2, y - height/2, z + length/2); rlVertex3f(x + width/2, y - height/2, z + length/2);
rlVertex3f(x + width/2, y - height/2, z + length/2); rlVertex3f(x + width/2, y + height/2, z + length/2);
rlVertex3f(x + width/2, y + height/2, z + length/2); rlVertex3f(x - width/2, y + height/2, z + length/2);
rlVertex3f(x - width/2, y + height/2, z + length/2); rlVertex3f(x - width/2, y - height/2, z + length/2);
rlVertex3f(x - width/2, y - height/2, z - length/2); rlVertex3f(x + width/2, y - height/2, z - length/2);
rlVertex3f(x + width/2, y - height/2, z - length/2); rlVertex3f(x + width/2, y + height/2, z - length/2);
rlVertex3f(x + width/2, y + height/2, z - length/2); rlVertex3f(x - width/2, y + height/2, z - length/2);
rlVertex3f(x - width/2, y + height/2, z - length/2); rlVertex3f(x - width/2, y - height/2, z - length/2);
rlVertex3f(x - width/2, y + height/2, z + length/2); rlVertex3f(x - width/2, y + height/2, z - length/2);
rlVertex3f(x + width/2, y + height/2, z + length/2); rlVertex3f(x + width/2, y + height/2, z - length/2);
rlVertex3f(x - width/2, y - height/2, z + length/2); rlVertex3f(x - width/2, y - height/2, z - length/2);
rlVertex3f(x + width/2, y - height/2, z + length/2); rlVertex3f(x + width/2, y - height/2, z - length/2); rlEnd();
rlPopMatrix();
}
void DrawCubeWiresV(Vector3 position, Vector3 size, Color color)
{
DrawCubeWires(position, size.x, size.y, size.z, color);
}
void DrawSphere(Vector3 centerPos, float radius, Color color)
{
DrawSphereEx(centerPos, radius, 16, 16, color);
}
void DrawSphereEx(Vector3 centerPos, float radius, int rings, int slices, Color color)
{
#if 0#endif
rlPushMatrix();
rlTranslatef(centerPos.x, centerPos.y, centerPos.z);
rlScalef(radius, radius, radius);
rlBegin(RL_TRIANGLES);
rlColor4ub(color.r, color.g, color.b, color.a);
float ringangle = DEG2RAD*(180.0f/(rings + 1)); float sliceangle = DEG2RAD*(360.0f/slices);
float cosring = cosf(ringangle);
float sinring = sinf(ringangle);
float cosslice = cosf(sliceangle);
float sinslice = sinf(sliceangle);
Vector3 vertices[4] = { 0 }; vertices[2] = (Vector3){ 0, 1, 0 };
vertices[3] = (Vector3){ sinring, cosring, 0 };
for (int i = 0; i < rings + 1; i++)
{
for (int j = 0; j < slices; j++)
{
vertices[0] = vertices[2]; vertices[1] = vertices[3];
vertices[2] = (Vector3){ cosslice*vertices[2].x - sinslice*vertices[2].z, vertices[2].y, sinslice*vertices[2].x + cosslice*vertices[2].z }; vertices[3] = (Vector3){ cosslice*vertices[3].x - sinslice*vertices[3].z, vertices[3].y, sinslice*vertices[3].x + cosslice*vertices[3].z };
rlNormal3f(vertices[0].x, vertices[0].y, vertices[0].z);
rlVertex3f(vertices[0].x, vertices[0].y, vertices[0].z);
rlNormal3f(vertices[3].x, vertices[3].y, vertices[3].z);
rlVertex3f(vertices[3].x, vertices[3].y, vertices[3].z);
rlNormal3f(vertices[1].x, vertices[1].y, vertices[1].z);
rlVertex3f(vertices[1].x, vertices[1].y, vertices[1].z);
rlNormal3f(vertices[0].x, vertices[0].y, vertices[0].z);
rlVertex3f(vertices[0].x, vertices[0].y, vertices[0].z);
rlNormal3f(vertices[2].x, vertices[2].y, vertices[2].z);
rlVertex3f(vertices[2].x, vertices[2].y, vertices[2].z);
rlNormal3f(vertices[3].x, vertices[3].y, vertices[3].z);
rlVertex3f(vertices[3].x, vertices[3].y, vertices[3].z);
}
vertices[2] = vertices[3]; vertices[3] = (Vector3){ cosring*vertices[3].x + sinring*vertices[3].y, -sinring*vertices[3].x + cosring*vertices[3].y, vertices[3].z }; }
rlEnd();
rlPopMatrix();
}
void DrawSphereWires(Vector3 centerPos, float radius, int rings, int slices, Color color)
{
rlPushMatrix();
rlTranslatef(centerPos.x, centerPos.y, centerPos.z);
rlScalef(radius, radius, radius);
rlBegin(RL_LINES);
rlColor4ub(color.r, color.g, color.b, color.a);
for (int i = 0; i < (rings + 2); i++)
{
for (int j = 0; j < slices; j++)
{
rlVertex3f(cosf(DEG2RAD*(270 + (180.0f/(rings + 1))*i))*sinf(DEG2RAD*(360.0f*j/slices)),
sinf(DEG2RAD*(270 + (180.0f/(rings + 1))*i)),
cosf(DEG2RAD*(270 + (180.0f/(rings + 1))*i))*cosf(DEG2RAD*(360.0f*j/slices)));
rlVertex3f(cosf(DEG2RAD*(270 + (180.0f/(rings + 1))*(i + 1)))*sinf(DEG2RAD*(360.0f*(j + 1)/slices)),
sinf(DEG2RAD*(270 + (180.0f/(rings + 1))*(i + 1))),
cosf(DEG2RAD*(270 + (180.0f/(rings + 1))*(i + 1)))*cosf(DEG2RAD*(360.0f*(j + 1)/slices)));
rlVertex3f(cosf(DEG2RAD*(270 + (180.0f/(rings + 1))*(i + 1)))*sinf(DEG2RAD*(360.0f*(j + 1)/slices)),
sinf(DEG2RAD*(270 + (180.0f/(rings + 1))*(i + 1))),
cosf(DEG2RAD*(270 + (180.0f/(rings + 1))*(i + 1)))*cosf(DEG2RAD*(360.0f*(j + 1)/slices)));
rlVertex3f(cosf(DEG2RAD*(270 + (180.0f/(rings + 1))*(i + 1)))*sinf(DEG2RAD*(360.0f*j/slices)),
sinf(DEG2RAD*(270 + (180.0f/(rings + 1))*(i + 1))),
cosf(DEG2RAD*(270 + (180.0f/(rings + 1))*(i + 1)))*cosf(DEG2RAD*(360.0f*j/slices)));
rlVertex3f(cosf(DEG2RAD*(270 + (180.0f/(rings + 1))*(i + 1)))*sinf(DEG2RAD*(360.0f*j/slices)),
sinf(DEG2RAD*(270 + (180.0f/(rings + 1))*(i + 1))),
cosf(DEG2RAD*(270 + (180.0f/(rings + 1))*(i + 1)))*cosf(DEG2RAD*(360.0f*j/slices)));
rlVertex3f(cosf(DEG2RAD*(270 + (180.0f/(rings + 1))*i))*sinf(DEG2RAD*(360.0f*j/slices)),
sinf(DEG2RAD*(270 + (180.0f/(rings + 1))*i)),
cosf(DEG2RAD*(270 + (180.0f/(rings + 1))*i))*cosf(DEG2RAD*(360.0f*j/slices)));
}
}
rlEnd();
rlPopMatrix();
}
void DrawCylinder(Vector3 position, float radiusTop, float radiusBottom, float height, int sides, Color color)
{
if (sides < 3) sides = 3;
const float angleStep = 360.0f/sides;
rlPushMatrix();
rlTranslatef(position.x, position.y, position.z);
rlBegin(RL_TRIANGLES);
rlColor4ub(color.r, color.g, color.b, color.a);
if (radiusTop > 0)
{
for (int i = 0; i < sides; i++)
{
rlVertex3f(sinf(DEG2RAD*i*angleStep)*radiusBottom, 0, cosf(DEG2RAD*i*angleStep)*radiusBottom); rlVertex3f(sinf(DEG2RAD*(i+1)*angleStep)*radiusBottom, 0, cosf(DEG2RAD*(i+1)*angleStep)*radiusBottom); rlVertex3f(sinf(DEG2RAD*(i+1)*angleStep)*radiusTop, height, cosf(DEG2RAD*(i+1)*angleStep)*radiusTop);
rlVertex3f(sinf(DEG2RAD*i*angleStep)*radiusTop, height, cosf(DEG2RAD*i*angleStep)*radiusTop); rlVertex3f(sinf(DEG2RAD*i*angleStep)*radiusBottom, 0, cosf(DEG2RAD*i*angleStep)*radiusBottom); rlVertex3f(sinf(DEG2RAD*(i+1)*angleStep)*radiusTop, height, cosf(DEG2RAD*(i+1)*angleStep)*radiusTop); }
for (int i = 0; i < sides; i++)
{
rlVertex3f(0, height, 0);
rlVertex3f(sinf(DEG2RAD*i*angleStep)*radiusTop, height, cosf(DEG2RAD*i*angleStep)*radiusTop);
rlVertex3f(sinf(DEG2RAD*(i+1)*angleStep)*radiusTop, height, cosf(DEG2RAD*(i+1)*angleStep)*radiusTop);
}
}
else
{
for (int i = 0; i < sides; i++)
{
rlVertex3f(0, height, 0);
rlVertex3f(sinf(DEG2RAD*i*angleStep)*radiusBottom, 0, cosf(DEG2RAD*i*angleStep)*radiusBottom);
rlVertex3f(sinf(DEG2RAD*(i+1)*angleStep)*radiusBottom, 0, cosf(DEG2RAD*(i+1)*angleStep)*radiusBottom);
}
}
for (int i = 0; i < sides; i++)
{
rlVertex3f(0, 0, 0);
rlVertex3f(sinf(DEG2RAD*(i+1)*angleStep)*radiusBottom, 0, cosf(DEG2RAD*(i+1)*angleStep)*radiusBottom);
rlVertex3f(sinf(DEG2RAD*i*angleStep)*radiusBottom, 0, cosf(DEG2RAD*i*angleStep)*radiusBottom);
}
rlEnd();
rlPopMatrix();
}
void DrawCylinderEx(Vector3 startPos, Vector3 endPos, float startRadius, float endRadius, int sides, Color color)
{
if (sides < 3) sides = 3;
Vector3 direction = { endPos.x - startPos.x, endPos.y - startPos.y, endPos.z - startPos.z };
if ((direction.x == 0) && (direction.y == 0) && (direction.z == 0)) return;
Vector3 b1 = Vector3Normalize(Vector3Perpendicular(direction));
Vector3 b2 = Vector3Normalize(Vector3CrossProduct(b1, direction));
float baseAngle = (2.0f*PI)/sides;
rlBegin(RL_TRIANGLES);
rlColor4ub(color.r, color.g, color.b, color.a);
for (int i = 0; i < sides; i++)
{
float s1 = sinf(baseAngle*(i + 0))*startRadius;
float c1 = cosf(baseAngle*(i + 0))*startRadius;
Vector3 w1 = { startPos.x + s1*b1.x + c1*b2.x, startPos.y + s1*b1.y + c1*b2.y, startPos.z + s1*b1.z + c1*b2.z };
float s2 = sinf(baseAngle*(i + 1))*startRadius;
float c2 = cosf(baseAngle*(i + 1))*startRadius;
Vector3 w2 = { startPos.x + s2*b1.x + c2*b2.x, startPos.y + s2*b1.y + c2*b2.y, startPos.z + s2*b1.z + c2*b2.z };
float s3 = sinf(baseAngle*(i + 0))*endRadius;
float c3 = cosf(baseAngle*(i + 0))*endRadius;
Vector3 w3 = { endPos.x + s3*b1.x + c3*b2.x, endPos.y + s3*b1.y + c3*b2.y, endPos.z + s3*b1.z + c3*b2.z };
float s4 = sinf(baseAngle*(i + 1))*endRadius;
float c4 = cosf(baseAngle*(i + 1))*endRadius;
Vector3 w4 = { endPos.x + s4*b1.x + c4*b2.x, endPos.y + s4*b1.y + c4*b2.y, endPos.z + s4*b1.z + c4*b2.z };
if (startRadius > 0)
{
rlVertex3f(startPos.x, startPos.y, startPos.z); rlVertex3f(w2.x, w2.y, w2.z); rlVertex3f(w1.x, w1.y, w1.z); }
rlVertex3f(w1.x, w1.y, w1.z); rlVertex3f(w2.x, w2.y, w2.z); rlVertex3f(w3.x, w3.y, w3.z); rlVertex3f(w2.x, w2.y, w2.z); rlVertex3f(w4.x, w4.y, w4.z); rlVertex3f(w3.x, w3.y, w3.z); if (endRadius > 0) {
rlVertex3f(endPos.x, endPos.y, endPos.z); rlVertex3f(w3.x, w3.y, w3.z); rlVertex3f(w4.x, w4.y, w4.z); } }
rlEnd();
}
void DrawCylinderWires(Vector3 position, float radiusTop, float radiusBottom, float height, int sides, Color color)
{
if (sides < 3) sides = 3;
const float angleStep = 360.0f/sides;
rlPushMatrix();
rlTranslatef(position.x, position.y, position.z);
rlBegin(RL_LINES);
rlColor4ub(color.r, color.g, color.b, color.a);
for (int i = 0; i < sides; i++)
{
rlVertex3f(sinf(DEG2RAD*i*angleStep)*radiusBottom, 0, cosf(DEG2RAD*i*angleStep)*radiusBottom);
rlVertex3f(sinf(DEG2RAD*(i+1)*angleStep)*radiusBottom, 0, cosf(DEG2RAD*(i+1)*angleStep)*radiusBottom);
rlVertex3f(sinf(DEG2RAD*(i+1)*angleStep)*radiusBottom, 0, cosf(DEG2RAD*(i+1)*angleStep)*radiusBottom);
rlVertex3f(sinf(DEG2RAD*(i+1)*angleStep)*radiusTop, height, cosf(DEG2RAD*(i+1)*angleStep)*radiusTop);
rlVertex3f(sinf(DEG2RAD*(i+1)*angleStep)*radiusTop, height, cosf(DEG2RAD*(i+1)*angleStep)*radiusTop);
rlVertex3f(sinf(DEG2RAD*i*angleStep)*radiusTop, height, cosf(DEG2RAD*i*angleStep)*radiusTop);
rlVertex3f(sinf(DEG2RAD*i*angleStep)*radiusTop, height, cosf(DEG2RAD*i*angleStep)*radiusTop);
rlVertex3f(sinf(DEG2RAD*i*angleStep)*radiusBottom, 0, cosf(DEG2RAD*i*angleStep)*radiusBottom);
}
rlEnd();
rlPopMatrix();
}
void DrawCylinderWiresEx(Vector3 startPos, Vector3 endPos, float startRadius, float endRadius, int sides, Color color)
{
if (sides < 3) sides = 3;
Vector3 direction = { endPos.x - startPos.x, endPos.y - startPos.y, endPos.z - startPos.z };
if ((direction.x == 0) && (direction.y == 0) && (direction.z == 0)) return;
Vector3 b1 = Vector3Normalize(Vector3Perpendicular(direction));
Vector3 b2 = Vector3Normalize(Vector3CrossProduct(b1, direction));
float baseAngle = (2.0f*PI)/sides;
rlBegin(RL_LINES);
rlColor4ub(color.r, color.g, color.b, color.a);
for (int i = 0; i < sides; i++)
{
float s1 = sinf(baseAngle*(i + 0))*startRadius;
float c1 = cosf(baseAngle*(i + 0))*startRadius;
Vector3 w1 = { startPos.x + s1*b1.x + c1*b2.x, startPos.y + s1*b1.y + c1*b2.y, startPos.z + s1*b1.z + c1*b2.z };
float s2 = sinf(baseAngle*(i + 1))*startRadius;
float c2 = cosf(baseAngle*(i + 1))*startRadius;
Vector3 w2 = { startPos.x + s2*b1.x + c2*b2.x, startPos.y + s2*b1.y + c2*b2.y, startPos.z + s2*b1.z + c2*b2.z };
float s3 = sinf(baseAngle*(i + 0))*endRadius;
float c3 = cosf(baseAngle*(i + 0))*endRadius;
Vector3 w3 = { endPos.x + s3*b1.x + c3*b2.x, endPos.y + s3*b1.y + c3*b2.y, endPos.z + s3*b1.z + c3*b2.z };
float s4 = sinf(baseAngle*(i + 1))*endRadius;
float c4 = cosf(baseAngle*(i + 1))*endRadius;
Vector3 w4 = { endPos.x + s4*b1.x + c4*b2.x, endPos.y + s4*b1.y + c4*b2.y, endPos.z + s4*b1.z + c4*b2.z };
rlVertex3f(w1.x, w1.y, w1.z);
rlVertex3f(w2.x, w2.y, w2.z);
rlVertex3f(w1.x, w1.y, w1.z);
rlVertex3f(w3.x, w3.y, w3.z);
rlVertex3f(w3.x, w3.y, w3.z);
rlVertex3f(w4.x, w4.y, w4.z);
}
rlEnd();
}
void DrawCapsule(Vector3 startPos, Vector3 endPos, float radius, int slices, int rings, Color color)
{
if (slices < 3) slices = 3;
Vector3 direction = { endPos.x - startPos.x, endPos.y - startPos.y, endPos.z - startPos.z };
bool sphereCase = (direction.x == 0) && (direction.y == 0) && (direction.z == 0);
if (sphereCase) direction = (Vector3){0.0f, 1.0f, 0.0f};
Vector3 b0 = Vector3Normalize(direction);
Vector3 b1 = Vector3Normalize(Vector3Perpendicular(direction));
Vector3 b2 = Vector3Normalize(Vector3CrossProduct(b1, direction));
Vector3 capCenter = endPos;
float baseSliceAngle = (2.0f*PI)/slices;
float baseRingAngle = PI*0.5f/rings;
rlBegin(RL_TRIANGLES);
rlColor4ub(color.r, color.g, color.b, color.a);
for (int c = 0; c < 2; c++)
{
for (int i = 0; i < rings; i++)
{
for (int j = 0; j < slices; j++)
{
float ringSin1 = sinf(baseSliceAngle*(j + 0))*cosf(baseRingAngle*( i + 0 ));
float ringCos1 = cosf(baseSliceAngle*(j + 0))*cosf(baseRingAngle*( i + 0 ));
Vector3 w1 = (Vector3){
capCenter.x + (sinf(baseRingAngle*( i + 0 ))*b0.x + ringSin1*b1.x + ringCos1*b2.x)*radius,
capCenter.y + (sinf(baseRingAngle*( i + 0 ))*b0.y + ringSin1*b1.y + ringCos1*b2.y)*radius,
capCenter.z + (sinf(baseRingAngle*( i + 0 ))*b0.z + ringSin1*b1.z + ringCos1*b2.z)*radius
};
float ringSin2 = sinf(baseSliceAngle*(j + 1))*cosf(baseRingAngle*( i + 0 ));
float ringCos2 = cosf(baseSliceAngle*(j + 1))*cosf(baseRingAngle*( i + 0 ));
Vector3 w2 = (Vector3){
capCenter.x + (sinf(baseRingAngle*( i + 0 ))*b0.x + ringSin2*b1.x + ringCos2*b2.x)*radius,
capCenter.y + (sinf(baseRingAngle*( i + 0 ))*b0.y + ringSin2*b1.y + ringCos2*b2.y)*radius,
capCenter.z + (sinf(baseRingAngle*( i + 0 ))*b0.z + ringSin2*b1.z + ringCos2*b2.z)*radius
};
float ringSin3 = sinf(baseSliceAngle*(j + 0))*cosf(baseRingAngle*( i + 1 ));
float ringCos3 = cosf(baseSliceAngle*(j + 0))*cosf(baseRingAngle*( i + 1 ));
Vector3 w3 = (Vector3){
capCenter.x + (sinf(baseRingAngle*( i + 1 ))*b0.x + ringSin3*b1.x + ringCos3*b2.x)*radius,
capCenter.y + (sinf(baseRingAngle*( i + 1 ))*b0.y + ringSin3*b1.y + ringCos3*b2.y)*radius,
capCenter.z + (sinf(baseRingAngle*( i + 1 ))*b0.z + ringSin3*b1.z + ringCos3*b2.z)*radius
};
float ringSin4 = sinf(baseSliceAngle*(j + 1))*cosf(baseRingAngle*( i + 1 ));
float ringCos4 = cosf(baseSliceAngle*(j + 1))*cosf(baseRingAngle*( i + 1 ));
Vector3 w4 = (Vector3){
capCenter.x + (sinf(baseRingAngle*( i + 1 ))*b0.x + ringSin4*b1.x + ringCos4*b2.x)*radius,
capCenter.y + (sinf(baseRingAngle*( i + 1 ))*b0.y + ringSin4*b1.y + ringCos4*b2.y)*radius,
capCenter.z + (sinf(baseRingAngle*( i + 1 ))*b0.z + ringSin4*b1.z + ringCos4*b2.z)*radius
};
if (c == 0)
{
rlVertex3f(w1.x, w1.y, w1.z);
rlVertex3f(w2.x, w2.y, w2.z);
rlVertex3f(w3.x, w3.y, w3.z);
rlVertex3f(w2.x, w2.y, w2.z);
rlVertex3f(w4.x, w4.y, w4.z);
rlVertex3f(w3.x, w3.y, w3.z);
}
else
{
rlVertex3f(w1.x, w1.y, w1.z);
rlVertex3f(w3.x, w3.y, w3.z);
rlVertex3f(w2.x, w2.y, w2.z);
rlVertex3f(w2.x, w2.y, w2.z);
rlVertex3f(w3.x, w3.y, w3.z);
rlVertex3f(w4.x, w4.y, w4.z);
}
}
}
capCenter = startPos;
b0 = Vector3Scale(b0, -1.0f);
}
if (!sphereCase)
{
for (int j = 0; j < slices; j++)
{
float ringSin1 = sinf(baseSliceAngle*(j + 0))*radius;
float ringCos1 = cosf(baseSliceAngle*(j + 0))*radius;
Vector3 w1 = {
startPos.x + ringSin1*b1.x + ringCos1*b2.x,
startPos.y + ringSin1*b1.y + ringCos1*b2.y,
startPos.z + ringSin1*b1.z + ringCos1*b2.z
};
float ringSin2 = sinf(baseSliceAngle*(j + 1))*radius;
float ringCos2 = cosf(baseSliceAngle*(j + 1))*radius;
Vector3 w2 = {
startPos.x + ringSin2*b1.x + ringCos2*b2.x,
startPos.y + ringSin2*b1.y + ringCos2*b2.y,
startPos.z + ringSin2*b1.z + ringCos2*b2.z
};
float ringSin3 = sinf(baseSliceAngle*(j + 0))*radius;
float ringCos3 = cosf(baseSliceAngle*(j + 0))*radius;
Vector3 w3 = {
endPos.x + ringSin3*b1.x + ringCos3*b2.x,
endPos.y + ringSin3*b1.y + ringCos3*b2.y,
endPos.z + ringSin3*b1.z + ringCos3*b2.z
};
float ringSin4 = sinf(baseSliceAngle*(j + 1))*radius;
float ringCos4 = cosf(baseSliceAngle*(j + 1))*radius;
Vector3 w4 = {
endPos.x + ringSin4*b1.x + ringCos4*b2.x,
endPos.y + ringSin4*b1.y + ringCos4*b2.y,
endPos.z + ringSin4*b1.z + ringCos4*b2.z
};
rlVertex3f(w1.x, w1.y, w1.z); rlVertex3f(w2.x, w2.y, w2.z); rlVertex3f(w3.x, w3.y, w3.z); rlVertex3f(w2.x, w2.y, w2.z); rlVertex3f(w4.x, w4.y, w4.z); rlVertex3f(w3.x, w3.y, w3.z); }
}
rlEnd();
}
void DrawCapsuleWires(Vector3 startPos, Vector3 endPos, float radius, int slices, int rings, Color color)
{
if (slices < 3) slices = 3;
Vector3 direction = { endPos.x - startPos.x, endPos.y - startPos.y, endPos.z - startPos.z };
bool sphereCase = (direction.x == 0) && (direction.y == 0) && (direction.z == 0);
if (sphereCase) direction = (Vector3){0.0f, 1.0f, 0.0f};
Vector3 b0 = Vector3Normalize(direction);
Vector3 b1 = Vector3Normalize(Vector3Perpendicular(direction));
Vector3 b2 = Vector3Normalize(Vector3CrossProduct(b1, direction));
Vector3 capCenter = endPos;
float baseSliceAngle = (2.0f*PI)/slices;
float baseRingAngle = PI*0.5f/rings;
rlBegin(RL_LINES);
rlColor4ub(color.r, color.g, color.b, color.a);
for (int c = 0; c < 2; c++)
{
for (int i = 0; i < rings; i++)
{
for (int j = 0; j < slices; j++)
{
float ringSin1 = sinf(baseSliceAngle*(j + 0))*cosf(baseRingAngle*( i + 0 ));
float ringCos1 = cosf(baseSliceAngle*(j + 0))*cosf(baseRingAngle*( i + 0 ));
Vector3 w1 = (Vector3){
capCenter.x + (sinf(baseRingAngle*( i + 0 ))*b0.x + ringSin1*b1.x + ringCos1*b2.x)*radius,
capCenter.y + (sinf(baseRingAngle*( i + 0 ))*b0.y + ringSin1*b1.y + ringCos1*b2.y)*radius,
capCenter.z + (sinf(baseRingAngle*( i + 0 ))*b0.z + ringSin1*b1.z + ringCos1*b2.z)*radius
};
float ringSin2 = sinf(baseSliceAngle*(j + 1))*cosf(baseRingAngle*( i + 0 ));
float ringCos2 = cosf(baseSliceAngle*(j + 1))*cosf(baseRingAngle*( i + 0 ));
Vector3 w2 = (Vector3){
capCenter.x + (sinf(baseRingAngle*( i + 0 ))*b0.x + ringSin2*b1.x + ringCos2*b2.x)*radius,
capCenter.y + (sinf(baseRingAngle*( i + 0 ))*b0.y + ringSin2*b1.y + ringCos2*b2.y)*radius,
capCenter.z + (sinf(baseRingAngle*( i + 0 ))*b0.z + ringSin2*b1.z + ringCos2*b2.z)*radius
};
float ringSin3 = sinf(baseSliceAngle*(j + 0))*cosf(baseRingAngle*( i + 1 ));
float ringCos3 = cosf(baseSliceAngle*(j + 0))*cosf(baseRingAngle*( i + 1 ));
Vector3 w3 = (Vector3){
capCenter.x + (sinf(baseRingAngle*( i + 1 ))*b0.x + ringSin3*b1.x + ringCos3*b2.x)*radius,
capCenter.y + (sinf(baseRingAngle*( i + 1 ))*b0.y + ringSin3*b1.y + ringCos3*b2.y)*radius,
capCenter.z + (sinf(baseRingAngle*( i + 1 ))*b0.z + ringSin3*b1.z + ringCos3*b2.z)*radius
};
float ringSin4 = sinf(baseSliceAngle*(j + 1))*cosf(baseRingAngle*( i + 1 ));
float ringCos4 = cosf(baseSliceAngle*(j + 1))*cosf(baseRingAngle*( i + 1 ));
Vector3 w4 = (Vector3){
capCenter.x + (sinf(baseRingAngle*( i + 1 ))*b0.x + ringSin4*b1.x + ringCos4*b2.x)*radius,
capCenter.y + (sinf(baseRingAngle*( i + 1 ))*b0.y + ringSin4*b1.y + ringCos4*b2.y)*radius,
capCenter.z + (sinf(baseRingAngle*( i + 1 ))*b0.z + ringSin4*b1.z + ringCos4*b2.z)*radius
};
rlVertex3f(w1.x, w1.y, w1.z);
rlVertex3f(w2.x, w2.y, w2.z);
rlVertex3f(w2.x, w2.y, w2.z);
rlVertex3f(w3.x, w3.y, w3.z);
rlVertex3f(w1.x, w1.y, w1.z);
rlVertex3f(w3.x, w3.y, w3.z);
rlVertex3f(w2.x, w2.y, w2.z);
rlVertex3f(w4.x, w4.y, w4.z);
rlVertex3f(w3.x, w3.y, w3.z);
rlVertex3f(w4.x, w4.y, w4.z);
}
}
capCenter = startPos;
b0 = Vector3Scale(b0, -1.0f);
}
if (!sphereCase)
{
for (int j = 0; j < slices; j++)
{
float ringSin1 = sinf(baseSliceAngle*(j + 0))*radius;
float ringCos1 = cosf(baseSliceAngle*(j + 0))*radius;
Vector3 w1 = {
startPos.x + ringSin1*b1.x + ringCos1*b2.x,
startPos.y + ringSin1*b1.y + ringCos1*b2.y,
startPos.z + ringSin1*b1.z + ringCos1*b2.z
};
float ringSin2 = sinf(baseSliceAngle*(j + 1))*radius;
float ringCos2 = cosf(baseSliceAngle*(j + 1))*radius;
Vector3 w2 = {
startPos.x + ringSin2*b1.x + ringCos2*b2.x,
startPos.y + ringSin2*b1.y + ringCos2*b2.y,
startPos.z + ringSin2*b1.z + ringCos2*b2.z
};
float ringSin3 = sinf(baseSliceAngle*(j + 0))*radius;
float ringCos3 = cosf(baseSliceAngle*(j + 0))*radius;
Vector3 w3 = {
endPos.x + ringSin3*b1.x + ringCos3*b2.x,
endPos.y + ringSin3*b1.y + ringCos3*b2.y,
endPos.z + ringSin3*b1.z + ringCos3*b2.z
};
float ringSin4 = sinf(baseSliceAngle*(j + 1))*radius;
float ringCos4 = cosf(baseSliceAngle*(j + 1))*radius;
Vector3 w4 = {
endPos.x + ringSin4*b1.x + ringCos4*b2.x,
endPos.y + ringSin4*b1.y + ringCos4*b2.y,
endPos.z + ringSin4*b1.z + ringCos4*b2.z
};
rlVertex3f(w1.x, w1.y, w1.z);
rlVertex3f(w3.x, w3.y, w3.z);
rlVertex3f(w2.x, w2.y, w2.z);
rlVertex3f(w4.x, w4.y, w4.z);
rlVertex3f(w2.x, w2.y, w2.z);
rlVertex3f(w3.x, w3.y, w3.z);
}
}
rlEnd();
}
void DrawPlane(Vector3 centerPos, Vector2 size, Color color)
{
rlPushMatrix();
rlTranslatef(centerPos.x, centerPos.y, centerPos.z);
rlScalef(size.x, 1.0f, size.y);
rlBegin(RL_QUADS);
rlColor4ub(color.r, color.g, color.b, color.a);
rlNormal3f(0.0f, 1.0f, 0.0f);
rlVertex3f(-0.5f, 0.0f, -0.5f);
rlVertex3f(-0.5f, 0.0f, 0.5f);
rlVertex3f(0.5f, 0.0f, 0.5f);
rlVertex3f(0.5f, 0.0f, -0.5f);
rlEnd();
rlPopMatrix();
}
void DrawRay(Ray ray, Color color)
{
float scale = 10000;
rlBegin(RL_LINES);
rlColor4ub(color.r, color.g, color.b, color.a);
rlColor4ub(color.r, color.g, color.b, color.a);
rlVertex3f(ray.position.x, ray.position.y, ray.position.z);
rlVertex3f(ray.position.x + ray.direction.x*scale, ray.position.y + ray.direction.y*scale, ray.position.z + ray.direction.z*scale);
rlEnd();
}
void DrawGrid(int slices, float spacing)
{
int halfSlices = slices/2;
rlBegin(RL_LINES);
for (int i = -halfSlices; i <= halfSlices; i++)
{
if (i == 0)
{
rlColor3f(0.5f, 0.5f, 0.5f);
}
else
{
rlColor3f(0.75f, 0.75f, 0.75f);
}
rlVertex3f((float)i*spacing, 0.0f, (float)-halfSlices*spacing);
rlVertex3f((float)i*spacing, 0.0f, (float)halfSlices*spacing);
rlVertex3f((float)-halfSlices*spacing, 0.0f, (float)i*spacing);
rlVertex3f((float)halfSlices*spacing, 0.0f, (float)i*spacing);
}
rlEnd();
}
Model LoadModel(const char *fileName)
{
Model model = { 0 };
#if SUPPORT_FILEFORMAT_OBJ
if (IsFileExtension(fileName, ".obj")) model = LoadOBJ(fileName);
#endif
#if SUPPORT_FILEFORMAT_IQM
if (IsFileExtension(fileName, ".iqm")) model = LoadIQM(fileName);
#endif
#if SUPPORT_FILEFORMAT_GLTF
if (IsFileExtension(fileName, ".gltf") || IsFileExtension(fileName, ".glb")) model = LoadGLTF(fileName);
#endif
#if SUPPORT_FILEFORMAT_VOX
if (IsFileExtension(fileName, ".vox")) model = LoadVOX(fileName);
#endif
#if SUPPORT_FILEFORMAT_M3D
if (IsFileExtension(fileName, ".m3d")) model = LoadM3D(fileName);
#endif
model.transform = MatrixIdentity();
if ((model.meshCount != 0) && (model.meshes != NULL))
{
for (int i = 0; i < model.meshCount; i++) UploadMesh(&model.meshes[i], false);
}
else TRACELOG(LOG_WARNING, "MESH: [%s] Failed to load model mesh(es) data", fileName);
if (model.materialCount == 0)
{
TRACELOG(LOG_WARNING, "MATERIAL: [%s] Failed to load model material data, default to white material", fileName);
model.materialCount = 1;
model.materials = (Material *)RL_CALLOC(model.materialCount, sizeof(Material));
model.materials[0] = LoadMaterialDefault();
if (model.meshMaterial == NULL) model.meshMaterial = (int *)RL_CALLOC(model.meshCount, sizeof(int));
}
return model;
}
Model LoadModelFromMesh(Mesh mesh)
{
Model model = { 0 };
model.transform = MatrixIdentity();
model.meshCount = 1;
model.meshes = (Mesh *)RL_CALLOC(model.meshCount, sizeof(Mesh));
model.meshes[0] = mesh;
model.materialCount = 1;
model.materials = (Material *)RL_CALLOC(model.materialCount, sizeof(Material));
model.materials[0] = LoadMaterialDefault();
model.meshMaterial = (int *)RL_CALLOC(model.meshCount, sizeof(int));
model.meshMaterial[0] = 0;
return model;
}
bool IsModelValid(Model model)
{
bool result = false;
if ((model.meshes != NULL) && (model.materials != NULL) && (model.meshMaterial != NULL) && (model.meshCount > 0) && (model.materialCount > 0)) result = true;
for (int i = 0; i < model.meshCount; i++)
{
if ((model.meshes[i].vertices != NULL) && (model.meshes[i].vboId[0] == 0)) { result = false; break; } if ((model.meshes[i].texcoords != NULL) && (model.meshes[i].vboId[1] == 0)) { result = false; break; } if ((model.meshes[i].normals != NULL) && (model.meshes[i].vboId[2] == 0)) { result = false; break; } if ((model.meshes[i].colors != NULL) && (model.meshes[i].vboId[3] == 0)) { result = false; break; } if ((model.meshes[i].tangents != NULL) && (model.meshes[i].vboId[4] == 0)) { result = false; break; } if ((model.meshes[i].texcoords2 != NULL) && (model.meshes[i].vboId[5] == 0)) { result = false; break; } if ((model.meshes[i].indices != NULL) && (model.meshes[i].vboId[6] == 0)) { result = false; break; } if ((model.meshes[i].boneIndices != NULL) && (model.meshes[i].vboId[7] == 0)) { result = false; break; } if ((model.meshes[i].boneWeights != NULL) && (model.meshes[i].vboId[8] == 0)) { result = false; break; }
}
return result;
}
void UnloadModel(Model model)
{
for (int i = 0; i < model.meshCount; i++) UnloadMesh(model.meshes[i]);
for (int i = 0; i < model.materialCount; i++) RL_FREE(model.materials[i].maps);
RL_FREE(model.meshes);
RL_FREE(model.materials);
RL_FREE(model.meshMaterial);
RL_FREE(model.skeleton.bones);
RL_FREE(model.skeleton.bindPose);
TRACELOG(LOG_INFO, "MODEL: Unloaded model (and meshes) from RAM and VRAM");
}
BoundingBox GetModelBoundingBox(Model model)
{
BoundingBox bounds = { 0 };
if (model.meshCount > 0)
{
Vector3 temp = { 0 };
bounds = GetMeshBoundingBox(model.meshes[0]);
for (int i = 1; i < model.meshCount; i++)
{
BoundingBox tempBounds = GetMeshBoundingBox(model.meshes[i]);
temp.x = (bounds.min.x < tempBounds.min.x)? bounds.min.x : tempBounds.min.x;
temp.y = (bounds.min.y < tempBounds.min.y)? bounds.min.y : tempBounds.min.y;
temp.z = (bounds.min.z < tempBounds.min.z)? bounds.min.z : tempBounds.min.z;
bounds.min = temp;
temp.x = (bounds.max.x > tempBounds.max.x)? bounds.max.x : tempBounds.max.x;
temp.y = (bounds.max.y > tempBounds.max.y)? bounds.max.y : tempBounds.max.y;
temp.z = (bounds.max.z > tempBounds.max.z)? bounds.max.z : tempBounds.max.z;
bounds.max = temp;
}
}
bounds.min = Vector3Transform(bounds.min, model.transform);
bounds.max = Vector3Transform(bounds.max, model.transform);
return bounds;
}
void UploadMesh(Mesh *mesh, bool dynamic)
{
if (mesh->vaoId > 0)
{
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; mesh->vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_POSITION] = 0; mesh->vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD] = 0; mesh->vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_NORMAL] = 0; mesh->vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_COLOR] = 0; mesh->vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_TANGENT] = 0; mesh->vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD2] = 0; mesh->vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_INDICES] = 0; #if SUPPORT_GPU_SKINNING
mesh->vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEINDICES] = 0; mesh->vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEWEIGHTS] = 0; #endif
#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)
mesh->vaoId = rlLoadVertexArray();
rlEnableVertexArray(mesh->vaoId);
void *vertices = (mesh->animVertices != NULL)? mesh->animVertices : mesh->vertices;
mesh->vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_POSITION] = rlLoadVertexBuffer(vertices, mesh->vertexCount*3*sizeof(float), dynamic);
rlSetVertexAttribute(RL_DEFAULT_SHADER_ATTRIB_LOCATION_POSITION, 3, RL_FLOAT, 0, 0, 0);
rlEnableVertexAttribute(RL_DEFAULT_SHADER_ATTRIB_LOCATION_POSITION);
if (mesh->texcoords != NULL)
{
mesh->vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD] = rlLoadVertexBuffer(mesh->texcoords, mesh->vertexCount*2*sizeof(float), dynamic);
rlSetVertexAttribute(RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD, 2, RL_FLOAT, 0, 0, 0);
rlEnableVertexAttribute(RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD);
}
else
{
float value[2] = { 0.0f, 0.0f };
rlSetVertexAttributeDefault(RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD, value, SHADER_ATTRIB_VEC2, 2);
rlDisableVertexAttribute(RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD);
}
if (mesh->normals != NULL)
{
void *normals = (mesh->animNormals != NULL)? mesh->animNormals : mesh->normals;
mesh->vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_NORMAL] = rlLoadVertexBuffer(normals, mesh->vertexCount*3*sizeof(float), dynamic);
rlSetVertexAttribute(RL_DEFAULT_SHADER_ATTRIB_LOCATION_NORMAL, 3, RL_FLOAT, 0, 0, 0);
rlEnableVertexAttribute(RL_DEFAULT_SHADER_ATTRIB_LOCATION_NORMAL);
}
else
{
float value[3] = { 0.0f, 0.0f, 1.0f };
rlSetVertexAttributeDefault(RL_DEFAULT_SHADER_ATTRIB_LOCATION_NORMAL, value, SHADER_ATTRIB_VEC3, 3);
rlDisableVertexAttribute(RL_DEFAULT_SHADER_ATTRIB_LOCATION_NORMAL);
}
if (mesh->colors != NULL)
{
mesh->vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_COLOR] = rlLoadVertexBuffer(mesh->colors, mesh->vertexCount*4*sizeof(unsigned char), dynamic);
rlSetVertexAttribute(RL_DEFAULT_SHADER_ATTRIB_LOCATION_COLOR, 4, RL_UNSIGNED_BYTE, 1, 0, 0);
rlEnableVertexAttribute(RL_DEFAULT_SHADER_ATTRIB_LOCATION_COLOR);
}
else
{
float value[4] = { 1.0f, 1.0f, 1.0f, 1.0f }; rlSetVertexAttributeDefault(RL_DEFAULT_SHADER_ATTRIB_LOCATION_COLOR, value, SHADER_ATTRIB_VEC4, 4);
rlDisableVertexAttribute(RL_DEFAULT_SHADER_ATTRIB_LOCATION_COLOR);
}
if (mesh->tangents != NULL)
{
mesh->vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_TANGENT] = rlLoadVertexBuffer(mesh->tangents, mesh->vertexCount*4*sizeof(float), dynamic);
rlSetVertexAttribute(RL_DEFAULT_SHADER_ATTRIB_LOCATION_TANGENT, 4, RL_FLOAT, 0, 0, 0);
rlEnableVertexAttribute(RL_DEFAULT_SHADER_ATTRIB_LOCATION_TANGENT);
}
else
{
float value[4] = { 1.0f, 0.0f, 0.0f, 1.0f };
rlSetVertexAttributeDefault(RL_DEFAULT_SHADER_ATTRIB_LOCATION_TANGENT, value, SHADER_ATTRIB_VEC4, 4);
rlDisableVertexAttribute(RL_DEFAULT_SHADER_ATTRIB_LOCATION_TANGENT);
}
if (mesh->texcoords2 != NULL)
{
mesh->vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD2] = rlLoadVertexBuffer(mesh->texcoords2, mesh->vertexCount*2*sizeof(float), dynamic);
rlSetVertexAttribute(RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD2, 2, RL_FLOAT, 0, 0, 0);
rlEnableVertexAttribute(RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD2);
}
else
{
float value[2] = { 0.0f, 0.0f };
rlSetVertexAttributeDefault(RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD2, value, SHADER_ATTRIB_VEC2, 2);
rlDisableVertexAttribute(RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD2);
}
#if SUPPORT_GPU_SKINNING
if (mesh->boneIndices != NULL)
{
mesh->vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEINDICES] = rlLoadVertexBuffer(mesh->boneIndices, mesh->vertexCount*4*sizeof(unsigned char), dynamic);
rlSetVertexAttribute(RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEINDICES, 4, RL_UNSIGNED_BYTE, 0, 0, 0);
rlEnableVertexAttribute(RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEINDICES);
}
else
{
float value[4] = { 0.0f, 0.0f, 0.0f, 0.0f };
rlSetVertexAttributeDefault(RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEINDICES, value, SHADER_ATTRIB_VEC4, 4);
rlDisableVertexAttribute(RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEINDICES);
}
if (mesh->boneWeights != NULL)
{
mesh->vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEWEIGHTS] = rlLoadVertexBuffer(mesh->boneWeights, mesh->vertexCount*4*sizeof(float), dynamic);
rlSetVertexAttribute(RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEWEIGHTS, 4, RL_FLOAT, 0, 0, 0);
rlEnableVertexAttribute(RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEWEIGHTS);
}
else
{
float value[4] = { 0.0f, 0.0f, 0.0f, 0.0f };
rlSetVertexAttributeDefault(RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEWEIGHTS, value, SHADER_ATTRIB_VEC4, 2);
rlDisableVertexAttribute(RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEWEIGHTS);
}
#endif
if (mesh->indices != NULL)
{
mesh->vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_INDICES] = 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
}
void UpdateMeshBuffer(Mesh mesh, int index, const void *data, int dataSize, int offset)
{
rlUpdateVertexBuffer(mesh.vboId[index], data, dataSize, offset);
}
void DrawMesh(Mesh mesh, Material material, Matrix transform)
{
#if defined(GRAPHICS_API_OPENGL_11) || defined(GRAPHICS_API_OPENGL_SOFTWARE)
#define GL_VERTEX_ARRAY 0x8074
#define GL_NORMAL_ARRAY 0x8075
#define GL_COLOR_ARRAY 0x8076
#define GL_TEXTURE_COORD_ARRAY 0x8078
if ((mesh.texcoords != NULL) && (material.maps[MATERIAL_MAP_DIFFUSE].texture.id > 0)) rlEnableTexture(material.maps[MATERIAL_MAP_DIFFUSE].texture.id);
if (mesh.animVertices != NULL) rlEnableStatePointer(GL_VERTEX_ARRAY, mesh.animVertices);
else rlEnableStatePointer(GL_VERTEX_ARRAY, mesh.vertices);
if (mesh.texcoords) rlEnableStatePointer(GL_TEXTURE_COORD_ARRAY, mesh.texcoords);
if (mesh.animNormals != NULL) rlEnableStatePointer(GL_NORMAL_ARRAY, mesh.animNormals);
else if (mesh.normals != NULL) rlEnableStatePointer(GL_NORMAL_ARRAY, mesh.normals);
if (mesh.colors != NULL) rlEnableStatePointer(GL_COLOR_ARRAY, mesh.colors);
rlPushMatrix();
rlMultMatrixf(MatrixToFloat(transform));
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)
rlEnableShader(material.shader.id);
if (material.shader.locs == NULL) return;
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);
}
if (material.shader.locs[SHADER_LOC_COLOR_SPECULAR] != -1)
{
float values[4] = {
(float)material.maps[MATERIAL_MAP_SPECULAR].color.r/255.0f,
(float)material.maps[MATERIAL_MAP_SPECULAR].color.g/255.0f,
(float)material.maps[MATERIAL_MAP_SPECULAR].color.b/255.0f,
(float)material.maps[MATERIAL_MAP_SPECULAR].color.a/255.0f
};
rlSetUniform(material.shader.locs[SHADER_LOC_COLOR_SPECULAR], values, SHADER_UNIFORM_VEC4, 1);
}
Matrix matModel = MatrixIdentity();
Matrix matView = rlGetMatrixModelview();
Matrix matModelView = MatrixIdentity();
Matrix matProjection = rlGetMatrixProjection();
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);
matModel = MatrixMultiply(transform, rlGetMatrixTransform());
if (material.shader.locs[SHADER_LOC_MATRIX_MODEL] != -1) rlSetUniformMatrix(material.shader.locs[SHADER_LOC_MATRIX_MODEL], matModel);
matModelView = MatrixMultiply(matModel, matView);
if (material.shader.locs[SHADER_LOC_MATRIX_NORMAL] != -1) rlSetUniformMatrix(material.shader.locs[SHADER_LOC_MATRIX_NORMAL], MatrixTranspose(MatrixInvert(matModel)));
for (int i = 0; i < MAX_MATERIAL_MAPS; i++)
{
if (material.maps[i].texture.id > 0)
{
rlActiveTextureSlot(i);
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);
}
}
if (!rlEnableVertexArray(mesh.vaoId))
{
rlEnableVertexBuffer(mesh.vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_POSITION]);
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[RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD]);
rlSetVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_TEXCOORD01], 2, RL_FLOAT, 0, 0, 0);
rlEnableVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_TEXCOORD01]);
if (material.shader.locs[SHADER_LOC_VERTEX_NORMAL] != -1)
{
rlEnableVertexBuffer(mesh.vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_NORMAL]);
rlSetVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_NORMAL], 3, RL_FLOAT, 0, 0, 0);
rlEnableVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_NORMAL]);
}
if (material.shader.locs[SHADER_LOC_VERTEX_COLOR] != -1)
{
if (mesh.vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_COLOR] != 0)
{
rlEnableVertexBuffer(mesh.vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_COLOR]);
rlSetVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_COLOR], 4, RL_UNSIGNED_BYTE, 1, 0, 0);
rlEnableVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_COLOR]);
}
else
{
float value[4] = { 1.0f, 1.0f, 1.0f, 1.0f };
rlSetVertexAttributeDefault(material.shader.locs[SHADER_LOC_VERTEX_COLOR], value, SHADER_ATTRIB_VEC4, 4);
rlDisableVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_COLOR]);
}
}
if (material.shader.locs[SHADER_LOC_VERTEX_TANGENT] != -1)
{
rlEnableVertexBuffer(mesh.vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_TANGENT]);
rlSetVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_TANGENT], 4, RL_FLOAT, 0, 0, 0);
rlEnableVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_TANGENT]);
}
if (material.shader.locs[SHADER_LOC_VERTEX_TEXCOORD02] != -1)
{
rlEnableVertexBuffer(mesh.vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD2]);
rlSetVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_TEXCOORD02], 2, RL_FLOAT, 0, 0, 0);
rlEnableVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_TEXCOORD02]);
}
#if SUPPORT_GPU_SKINNING
if (material.shader.locs[SHADER_LOC_VERTEX_BONEIDS] != -1)
{
rlEnableVertexBuffer(mesh.vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEINDICES]);
rlSetVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_BONEIDS], 4, RL_UNSIGNED_BYTE, 0, 0, 0);
rlEnableVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_BONEIDS]);
}
if (material.shader.locs[SHADER_LOC_VERTEX_BONEWEIGHTS] != -1)
{
rlEnableVertexBuffer(mesh.vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEWEIGHTS]);
rlSetVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_BONEWEIGHTS], 4, RL_FLOAT, 0, 0, 0);
rlEnableVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_BONEWEIGHTS]);
}
#endif
if (mesh.indices != NULL) rlEnableVertexBufferElement(mesh.vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_INDICES]);
}
int eyeCount = 1;
if (rlIsStereoRenderEnabled()) eyeCount = 2;
for (int eye = 0; eye < eyeCount; eye++)
{
Matrix matModelViewProjection = MatrixIdentity();
if (eyeCount == 1) matModelViewProjection = MatrixMultiply(matModelView, matProjection);
else
{
rlViewport(eye*rlGetFramebufferWidth()/2, 0, rlGetFramebufferWidth()/2, rlGetFramebufferHeight());
matModelViewProjection = MatrixMultiply(MatrixMultiply(matModelView, rlGetMatrixViewOffsetStereo(eye)), rlGetMatrixProjectionStereo(eye));
}
rlSetUniformMatrix(material.shader.locs[SHADER_LOC_MATRIX_MVP], matModelViewProjection);
if (mesh.indices != NULL) rlDrawVertexArrayElements(0, mesh.triangleCount*3, 0);
else rlDrawVertexArray(0, mesh.vertexCount);
}
for (int i = 0; i < MAX_MATERIAL_MAPS; i++)
{
if (material.maps[i].texture.id > 0)
{
rlActiveTextureSlot(i);
if ((i == MATERIAL_MAP_IRRADIANCE) ||
(i == MATERIAL_MAP_PREFILTER) ||
(i == MATERIAL_MAP_CUBEMAP)) rlDisableTextureCubemap();
else rlDisableTexture();
}
}
rlDisableVertexArray();
rlDisableVertexBuffer();
rlDisableVertexBufferElement();
rlDisableShader();
rlSetMatrixModelview(matView);
rlSetMatrixProjection(matProjection);
#endif
}
void DrawMeshInstanced(Mesh mesh, Material material, const Matrix *transforms, int instances)
{
#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)
float16 *instanceTransform = NULL;
unsigned int instancesVboId = 0;
rlEnableShader(material.shader.id);
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);
}
if (material.shader.locs[SHADER_LOC_COLOR_SPECULAR] != -1)
{
float values[4] = {
(float)material.maps[MATERIAL_MAP_SPECULAR].color.r/255.0f,
(float)material.maps[MATERIAL_MAP_SPECULAR].color.g/255.0f,
(float)material.maps[MATERIAL_MAP_SPECULAR].color.b/255.0f,
(float)material.maps[MATERIAL_MAP_SPECULAR].color.a/255.0f
};
rlSetUniform(material.shader.locs[SHADER_LOC_COLOR_SPECULAR], values, SHADER_UNIFORM_VEC4, 1);
}
Matrix matModel = MatrixIdentity();
Matrix matView = rlGetMatrixModelview();
Matrix matModelView = MatrixIdentity();
Matrix matProjection = rlGetMatrixProjection();
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);
instanceTransform = (float16 *)RL_CALLOC(instances, sizeof(float16));
for (int i = 0; i < instances; i++) instanceTransform[i] = MatrixToFloatV(transforms[i]);
rlEnableVertexArray(mesh.vaoId);
instancesVboId = rlLoadVertexBuffer(instanceTransform, instances*sizeof(float16), false);
if (material.shader.locs[SHADER_LOC_VERTEX_INSTANCETRANSFORM] != -1)
{
for (unsigned int i = 0; i < 4; i++)
{
rlEnableVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_INSTANCETRANSFORM] + i);
rlSetVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_INSTANCETRANSFORM] + i, 4, RL_FLOAT, 0, sizeof(Matrix), i*sizeof(Vector4));
rlSetVertexAttributeDivisor(material.shader.locs[SHADER_LOC_VERTEX_INSTANCETRANSFORM] + i, 1);
}
}
rlDisableVertexBuffer();
rlDisableVertexArray();
matModelView = MatrixMultiply(rlGetMatrixTransform(), matView);
if (material.shader.locs[SHADER_LOC_MATRIX_NORMAL] != -1) rlSetUniformMatrix(material.shader.locs[SHADER_LOC_MATRIX_NORMAL], MatrixTranspose(MatrixInvert(matModel)));
for (int i = 0; i < MAX_MATERIAL_MAPS; i++)
{
if (material.maps[i].texture.id > 0)
{
rlActiveTextureSlot(i);
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);
}
}
if (!rlEnableVertexArray(mesh.vaoId))
{
rlEnableVertexBuffer(mesh.vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_POSITION]);
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[RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD]);
rlSetVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_TEXCOORD01], 2, RL_FLOAT, 0, 0, 0);
rlEnableVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_TEXCOORD01]);
if (material.shader.locs[SHADER_LOC_VERTEX_NORMAL] != -1)
{
rlEnableVertexBuffer(mesh.vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_NORMAL]);
rlSetVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_NORMAL], 3, RL_FLOAT, 0, 0, 0);
rlEnableVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_NORMAL]);
}
if (material.shader.locs[SHADER_LOC_VERTEX_COLOR] != -1)
{
if (mesh.vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_COLOR] != 0)
{
rlEnableVertexBuffer(mesh.vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_COLOR]);
rlSetVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_COLOR], 4, RL_UNSIGNED_BYTE, 1, 0, 0);
rlEnableVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_COLOR]);
}
else
{
float value[4] = { 1.0f, 1.0f, 1.0f, 1.0f };
rlSetVertexAttributeDefault(material.shader.locs[SHADER_LOC_VERTEX_COLOR], value, SHADER_ATTRIB_VEC4, 4);
rlDisableVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_COLOR]);
}
}
if (material.shader.locs[SHADER_LOC_VERTEX_TANGENT] != -1)
{
rlEnableVertexBuffer(mesh.vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_TANGENT]);
rlSetVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_TANGENT], 4, RL_FLOAT, 0, 0, 0);
rlEnableVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_TANGENT]);
}
if (material.shader.locs[SHADER_LOC_VERTEX_TEXCOORD02] != -1)
{
rlEnableVertexBuffer(mesh.vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD2]);
rlSetVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_TEXCOORD02], 2, RL_FLOAT, 0, 0, 0);
rlEnableVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_TEXCOORD02]);
}
#if SUPPORT_GPU_SKINNING
if (material.shader.locs[SHADER_LOC_VERTEX_BONEIDS] != -1)
{
rlEnableVertexBuffer(mesh.vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEINDICES]);
rlSetVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_BONEIDS], 4, RL_UNSIGNED_BYTE, 0, 0, 0);
rlEnableVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_BONEIDS]);
}
if (material.shader.locs[SHADER_LOC_VERTEX_BONEWEIGHTS] != -1)
{
rlEnableVertexBuffer(mesh.vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEWEIGHTS]);
rlSetVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_BONEWEIGHTS], 4, RL_FLOAT, 0, 0, 0);
rlEnableVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_BONEWEIGHTS]);
}
#endif
if (mesh.indices != NULL) rlEnableVertexBufferElement(mesh.vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_INDICES]);
}
int eyeCount = 1;
if (rlIsStereoRenderEnabled()) eyeCount = 2;
for (int eye = 0; eye < eyeCount; eye++)
{
Matrix matModelViewProjection = MatrixIdentity();
if (eyeCount == 1) matModelViewProjection = MatrixMultiply(matModelView, matProjection);
else
{
rlViewport(eye*rlGetFramebufferWidth()/2, 0, rlGetFramebufferWidth()/2, rlGetFramebufferHeight());
matModelViewProjection = MatrixMultiply(MatrixMultiply(matModelView, rlGetMatrixViewOffsetStereo(eye)), rlGetMatrixProjectionStereo(eye));
}
rlSetUniformMatrix(material.shader.locs[SHADER_LOC_MATRIX_MVP], matModelViewProjection);
if (mesh.indices != NULL) rlDrawVertexArrayElementsInstanced(0, mesh.triangleCount*3, 0, instances);
else rlDrawVertexArrayInstanced(0, mesh.vertexCount, instances);
}
for (int i = 0; i < MAX_MATERIAL_MAPS; i++)
{
if (material.maps[i].texture.id > 0)
{
rlActiveTextureSlot(i);
if ((i == MATERIAL_MAP_IRRADIANCE) ||
(i == MATERIAL_MAP_PREFILTER) ||
(i == MATERIAL_MAP_CUBEMAP)) rlDisableTextureCubemap();
else rlDisableTexture();
}
}
rlDisableVertexArray();
rlDisableVertexBuffer();
rlDisableVertexBufferElement();
rlDisableShader();
rlUnloadVertexBuffer(instancesVboId);
RL_FREE(instanceTransform);
#endif
}
void UnloadMesh(Mesh mesh)
{
rlUnloadVertexArray(mesh.vaoId);
if (mesh.vboId != NULL) 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.boneWeights);
RL_FREE(mesh.boneIndices);
RL_FREE(mesh.animVertices);
RL_FREE(mesh.animNormals);
}
bool ExportMesh(Mesh mesh, const char *fileName)
{
bool result = false;
if (IsFileExtension(fileName, ".obj"))
{
int vc = mesh.vertexCount;
int dataSize = vc*(int)strlen("v -0000.000000f -0000.000000f -0000.000000f\n") +
vc*(int)strlen("vt -0.000000f -0.000000f\n") +
vc*(int)strlen("vn -0.0000f -0.0000f -0.0000f\n") +
mesh.triangleCount*snprintf(NULL, 0, "f %i/%i/%i %i/%i/%i %i/%i/%i\n", vc, vc, vc, vc, vc, vc, vc, vc, vc);
char *txtData = (char *)RL_CALLOC(dataSize + 1000, sizeof(char));
int byteCount = 0;
byteCount += sprintf(txtData + byteCount, "# //////////////////////////////////////////////////////////////////////////////////\n");
byteCount += sprintf(txtData + byteCount, "# // //\n");
byteCount += sprintf(txtData + byteCount, "# // rMeshOBJ exporter v1.0 - Mesh exported as triangle faces and not optimized //\n");
byteCount += sprintf(txtData + byteCount, "# // //\n");
byteCount += sprintf(txtData + byteCount, "# // more info and bugs-report: github.com/raysan5/raylib //\n");
byteCount += sprintf(txtData + byteCount, "# // feedback and support: ray[at]raylib.com //\n");
byteCount += sprintf(txtData + byteCount, "# // //\n");
byteCount += sprintf(txtData + byteCount, "# // Copyright (c) 2018-2026 Ramon Santamaria (@raysan5) //\n");
byteCount += sprintf(txtData + byteCount, "# // //\n");
byteCount += sprintf(txtData + byteCount, "# //////////////////////////////////////////////////////////////////////////////////\n\n");
byteCount += sprintf(txtData + byteCount, "# Vertex Count: %i\n", mesh.vertexCount);
byteCount += sprintf(txtData + byteCount, "# Triangle Count: %i\n\n", mesh.triangleCount);
byteCount += sprintf(txtData + byteCount, "g mesh\n");
for (int i = 0, v = 0; i < mesh.vertexCount; i++, v += 3)
{
byteCount += sprintf(txtData + byteCount, "v %.6f %.6f %.6f\n", mesh.vertices[v], mesh.vertices[v + 1], mesh.vertices[v + 2]);
}
for (int i = 0, v = 0; i < mesh.vertexCount; i++, v += 2)
{
byteCount += sprintf(txtData + byteCount, "vt %.6f %.6f\n", mesh.texcoords[v], mesh.texcoords[v + 1]);
}
for (int i = 0, v = 0; i < mesh.vertexCount; i++, v += 3)
{
byteCount += sprintf(txtData + byteCount, "vn %.4f %.4f %.4f\n", mesh.normals[v], mesh.normals[v + 1], mesh.normals[v + 2]);
}
if (mesh.indices != NULL)
{
for (int i = 0, v = 0; i < mesh.triangleCount; i++, v += 3)
{
byteCount += sprintf(txtData + byteCount, "f %i/%i/%i %i/%i/%i %i/%i/%i\n",
mesh.indices[v] + 1, mesh.indices[v] + 1, mesh.indices[v] + 1,
mesh.indices[v + 1] + 1, mesh.indices[v + 1] + 1, mesh.indices[v + 1] + 1,
mesh.indices[v + 2] + 1, mesh.indices[v + 2] + 1, mesh.indices[v + 2] + 1);
}
}
else
{
for (int i = 0, v = 1; i < mesh.triangleCount; i++, v += 3)
{
byteCount += sprintf(txtData + byteCount, "f %i/%i/%i %i/%i/%i %i/%i/%i\n", v, v, v, v + 1, v + 1, v + 1, v + 2, v + 2, v + 2);
}
}
result = SaveFileText(fileName, txtData);
RL_FREE(txtData);
}
else if (IsFileExtension(fileName, ".gltf")) {
}
else if (IsFileExtension(fileName, ".raw"))
{
}
return result;
}
bool ExportMeshAsCode(Mesh mesh, const char *fileName)
{
bool result = false;
#ifndef TEXT_BYTES_PER_LINE
#define TEXT_BYTES_PER_LINE 20
#endif
char *txtData = (char *)RL_CALLOC(64*1024*1024, sizeof(char));
int byteCount = 0;
byteCount += sprintf(txtData + byteCount, "////////////////////////////////////////////////////////////////////////////////////////\n");
byteCount += sprintf(txtData + byteCount, "// //\n");
byteCount += sprintf(txtData + byteCount, "// MeshAsCode exporter v1.0 - Mesh vertex data exported as arrays //\n");
byteCount += sprintf(txtData + byteCount, "// //\n");
byteCount += sprintf(txtData + byteCount, "// more info and bugs-report: github.com/raysan5/raylib //\n");
byteCount += sprintf(txtData + byteCount, "// feedback and support: ray[at]raylib.com //\n");
byteCount += sprintf(txtData + byteCount, "// //\n");
byteCount += sprintf(txtData + byteCount, "// Copyright (c) 2023 Ramon Santamaria (@raysan5) //\n");
byteCount += sprintf(txtData + byteCount, "// //\n");
byteCount += sprintf(txtData + byteCount, "////////////////////////////////////////////////////////////////////////////////////////\n\n");
char varFileName[256] = { 0 };
strncpy(varFileName, GetFileNameWithoutExt(fileName), 256 - 1); for (int i = 0; varFileName[i] != '\0'; i++) if ((varFileName[i] >= 'a') && (varFileName[i] <= 'z')) { varFileName[i] = varFileName[i] - 32; }
byteCount += sprintf(txtData + byteCount, "// Mesh basic information\n");
byteCount += sprintf(txtData + byteCount, "#define %s_VERTEX_COUNT %i\n", varFileName, mesh.vertexCount);
byteCount += sprintf(txtData + byteCount, "#define %s_TRIANGLE_COUNT %i\n\n", varFileName, mesh.triangleCount);
if (mesh.vertices != NULL) {
byteCount += sprintf(txtData + byteCount, "static float %s_VERTEX_DATA[%i] = { ", varFileName, mesh.vertexCount*3);
for (int i = 0; i < mesh.vertexCount*3 - 1; i++) byteCount += sprintf(txtData + byteCount, ((i%TEXT_BYTES_PER_LINE == 0)? "%.3ff,\n" : "%.3ff, "), mesh.vertices[i]);
byteCount += sprintf(txtData + byteCount, "%.3ff };\n\n", mesh.vertices[mesh.vertexCount*3 - 1]);
}
if (mesh.texcoords != NULL) {
byteCount += sprintf(txtData + byteCount, "static float %s_TEXCOORD_DATA[%i] = { ", varFileName, mesh.vertexCount*2);
for (int i = 0; i < mesh.vertexCount*2 - 1; i++) byteCount += sprintf(txtData + byteCount, ((i%TEXT_BYTES_PER_LINE == 0)? "%.3ff,\n" : "%.3ff, "), mesh.texcoords[i]);
byteCount += sprintf(txtData + byteCount, "%.3ff };\n\n", mesh.texcoords[mesh.vertexCount*2 - 1]);
}
if (mesh.texcoords2 != NULL) {
byteCount += sprintf(txtData + byteCount, "static float %s_TEXCOORD2_DATA[%i] = { ", varFileName, mesh.vertexCount*2);
for (int i = 0; i < mesh.vertexCount*2 - 1; i++) byteCount += sprintf(txtData + byteCount, ((i%TEXT_BYTES_PER_LINE == 0)? "%.3ff,\n" : "%.3ff, "), mesh.texcoords2[i]);
byteCount += sprintf(txtData + byteCount, "%.3ff };\n\n", mesh.texcoords2[mesh.vertexCount*2 - 1]);
}
if (mesh.normals != NULL) {
byteCount += sprintf(txtData + byteCount, "static float %s_NORMAL_DATA[%i] = { ", varFileName, mesh.vertexCount*3);
for (int i = 0; i < mesh.vertexCount*3 - 1; i++) byteCount += sprintf(txtData + byteCount, ((i%TEXT_BYTES_PER_LINE == 0)? "%.3ff,\n" : "%.3ff, "), mesh.normals[i]);
byteCount += sprintf(txtData + byteCount, "%.3ff };\n\n", mesh.normals[mesh.vertexCount*3 - 1]);
}
if (mesh.tangents != NULL) {
byteCount += sprintf(txtData + byteCount, "static float %s_TANGENT_DATA[%i] = { ", varFileName, mesh.vertexCount*4);
for (int i = 0; i < mesh.vertexCount*4 - 1; i++) byteCount += sprintf(txtData + byteCount, ((i%TEXT_BYTES_PER_LINE == 0)? "%.3ff,\n" : "%.3ff, "), mesh.tangents[i]);
byteCount += sprintf(txtData + byteCount, "%.3ff };\n\n", mesh.tangents[mesh.vertexCount*4 - 1]);
}
if (mesh.colors != NULL) {
byteCount += sprintf(txtData + byteCount, "static unsigned char %s_COLOR_DATA[%i] = { ", varFileName, mesh.vertexCount*4);
for (int i = 0; i < mesh.vertexCount*4 - 1; i++) byteCount += sprintf(txtData + byteCount, ((i%TEXT_BYTES_PER_LINE == 0)? "0x%x,\n" : "0x%x, "), mesh.colors[i]);
byteCount += sprintf(txtData + byteCount, "0x%x };\n\n", mesh.colors[mesh.vertexCount*4 - 1]);
}
if (mesh.indices != NULL) {
byteCount += sprintf(txtData + byteCount, "static unsigned short %s_INDEX_DATA[%i] = { ", varFileName, mesh.triangleCount*3);
for (int i = 0; i < mesh.triangleCount*3 - 1; i++) byteCount += sprintf(txtData + byteCount, ((i%TEXT_BYTES_PER_LINE == 0)? "%i,\n" : "%i, "), mesh.indices[i]);
byteCount += sprintf(txtData + byteCount, "%i };\n", mesh.indices[mesh.triangleCount*3 - 1]);
}
result = SaveFileText(fileName, txtData);
RL_FREE(txtData);
return result;
}
#if SUPPORT_FILEFORMAT_OBJ || SUPPORT_FILEFORMAT_MTL
static void ProcessMaterialsOBJ(Material *materials, tinyobj_material_t *mats, int materialCount)
{
for (int m = 0; m < materialCount; m++)
{
materials[m] = LoadMaterialDefault();
if (mats == NULL) continue;
materials[m].maps[MATERIAL_MAP_DIFFUSE].texture = (Texture2D){ rlGetTextureIdDefault(), 1, 1, 1, PIXELFORMAT_UNCOMPRESSED_R8G8B8A8 };
if (mats[m].diffuse_texname != NULL) materials[m].maps[MATERIAL_MAP_DIFFUSE].texture = LoadTexture(mats[m].diffuse_texname); else materials[m].maps[MATERIAL_MAP_DIFFUSE].color = (Color){ (unsigned char)(mats[m].diffuse[0]*255.0f), (unsigned char)(mats[m].diffuse[1]*255.0f), (unsigned char)(mats[m].diffuse[2]*255.0f), 255 }; materials[m].maps[MATERIAL_MAP_DIFFUSE].value = 0.0f;
if (mats[m].specular_texname != NULL) materials[m].maps[MATERIAL_MAP_SPECULAR].texture = LoadTexture(mats[m].specular_texname); materials[m].maps[MATERIAL_MAP_SPECULAR].color = (Color){ (unsigned char)(mats[m].specular[0]*255.0f), (unsigned char)(mats[m].specular[1]*255.0f), (unsigned char)(mats[m].specular[2]*255.0f), 255 }; materials[m].maps[MATERIAL_MAP_SPECULAR].value = 0.0f;
if (mats[m].bump_texname != NULL) materials[m].maps[MATERIAL_MAP_NORMAL].texture = LoadTexture(mats[m].bump_texname); materials[m].maps[MATERIAL_MAP_NORMAL].color = WHITE;
materials[m].maps[MATERIAL_MAP_NORMAL].value = mats[m].shininess;
materials[m].maps[MATERIAL_MAP_EMISSION].color = (Color){ (unsigned char)(mats[m].emission[0]*255.0f), (unsigned char)(mats[m].emission[1]*255.0f), (unsigned char)(mats[m].emission[2]*255.0f), 255 };
if (mats[m].displacement_texname != NULL) materials[m].maps[MATERIAL_MAP_HEIGHT].texture = LoadTexture(mats[m].displacement_texname); }
}
#endif
Material *LoadMaterials(const char *fileName, int *materialCount)
{
Material *materials = NULL;
unsigned int count = 0;
#if SUPPORT_FILEFORMAT_MTL
if (IsFileExtension(fileName, ".mtl"))
{
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);
materials = (Material *)RL_CALLOC(count, sizeof(Material));
ProcessMaterialsOBJ(materials, mats, count);
tinyobj_materials_free(mats, count);
}
#else
TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to load material file", fileName);
#endif
*materialCount = count;
return materials;
}
Material LoadMaterialDefault(void)
{
Material material = { 0 };
material.maps = (MaterialMap *)RL_CALLOC(MAX_MATERIAL_MAPS, sizeof(MaterialMap));
material.shader.id = rlGetShaderIdDefault();
material.shader.locs = rlGetShaderLocsDefault();
material.maps[MATERIAL_MAP_DIFFUSE].texture = (Texture2D){ rlGetTextureIdDefault(), 1, 1, 1, PIXELFORMAT_UNCOMPRESSED_R8G8B8A8 };
material.maps[MATERIAL_MAP_DIFFUSE].color = WHITE; material.maps[MATERIAL_MAP_SPECULAR].color = WHITE;
return material;
}
bool IsMaterialValid(Material material)
{
bool result = false;
if ((material.maps != NULL) && (material.shader.id > 0)) result = true;
return result;
}
void UnloadMaterial(Material material)
{
if (material.shader.id != rlGetShaderIdDefault()) UnloadShader(material.shader);
if (material.maps != NULL)
{
for (int i = 0; i < MAX_MATERIAL_MAPS; i++)
{
if (material.maps[i].texture.id != rlGetTextureIdDefault()) rlUnloadTexture(material.maps[i].texture.id);
}
}
RL_FREE(material.maps);
}
void SetMaterialTexture(Material *material, int mapType, Texture2D texture)
{
material->maps[mapType].texture = texture;
}
void SetModelMeshMaterial(Model *model, int meshId, int materialId)
{
if (meshId >= model->meshCount) TRACELOG(LOG_WARNING, "MESH: Id greater than mesh count");
else if (materialId >= model->materialCount) TRACELOG(LOG_WARNING, "MATERIAL: Id greater than material count");
else model->meshMaterial[meshId] = materialId;
}
ModelAnimation *LoadModelAnimations(const char *fileName, int *animCount)
{
ModelAnimation *animations = NULL;
#if SUPPORT_FILEFORMAT_IQM
if (IsFileExtension(fileName, ".iqm")) animations = LoadModelAnimationsIQM(fileName, animCount);
#endif
#if SUPPORT_FILEFORMAT_M3D
if (IsFileExtension(fileName, ".m3d")) animations = LoadModelAnimationsM3D(fileName, animCount);
#endif
#if SUPPORT_FILEFORMAT_GLTF
if (IsFileExtension(fileName, ".gltf;.glb")) animations = LoadModelAnimationsGLTF(fileName, animCount);
#endif
return animations;
}
void UpdateModelAnimation(Model model, ModelAnimation anim, float frame)
{
if (model.boneMatrices == NULL) return;
if ((anim.keyframeCount > 0) && (model.skeleton.bones != NULL) && (anim.keyframePoses != NULL))
{
int currentFrame = (int)frame;
int nextFrame = currentFrame + 1;
float blend = frame - currentFrame;
blend = Clamp(blend, 0.0f, 1.0f);
if (currentFrame >= anim.keyframeCount) currentFrame = currentFrame%anim.keyframeCount;
if (nextFrame >= anim.keyframeCount) nextFrame = nextFrame%anim.keyframeCount;
Matrix bindPoseMatrix = { 0 };
Matrix currentPoseMatrix = { 0 };
for (int boneIndex = 0; boneIndex < model.skeleton.boneCount; boneIndex++)
{
model.currentPose[boneIndex].translation = Vector3Lerp(
anim.keyframePoses[currentFrame][boneIndex].translation,
anim.keyframePoses[nextFrame][boneIndex].translation, blend);
model.currentPose[boneIndex].rotation = QuaternionSlerp(
anim.keyframePoses[currentFrame][boneIndex].rotation,
anim.keyframePoses[nextFrame][boneIndex].rotation, blend);
model.currentPose[boneIndex].scale = Vector3Lerp(
anim.keyframePoses[currentFrame][boneIndex].scale,
anim.keyframePoses[nextFrame][boneIndex].scale, blend);
Transform *bindPoseTransform = &model.skeleton.bindPose[boneIndex];
bindPoseMatrix = MatrixMultiply(
MatrixMultiply(MatrixScale(bindPoseTransform->scale.x, bindPoseTransform->scale.y, bindPoseTransform->scale.z),
QuaternionToMatrix(bindPoseTransform->rotation)),
MatrixTranslate(bindPoseTransform->translation.x, bindPoseTransform->translation.y, bindPoseTransform->translation.z));
Transform *currentPoseTransform = &model.currentPose[boneIndex];
currentPoseMatrix = MatrixMultiply(
MatrixMultiply(MatrixScale(currentPoseTransform->scale.x, currentPoseTransform->scale.y, currentPoseTransform->scale.z),
QuaternionToMatrix(currentPoseTransform->rotation)),
MatrixTranslate(currentPoseTransform->translation.x, currentPoseTransform->translation.y, currentPoseTransform->translation.z));
model.boneMatrices[boneIndex] = MatrixMultiply(MatrixInvert(bindPoseMatrix), currentPoseMatrix);
}
UpdateModelAnimationVertexBuffers(model);
}
}
void UpdateModelAnimationEx(Model model, ModelAnimation animA, float frameA, ModelAnimation animB, float frameB, float blend)
{
if (model.boneMatrices == NULL) return;
if ((animA.keyframeCount > 0) && (animA.keyframePoses != NULL) &&
(animB.keyframeCount > 0) && (animB.keyframePoses != NULL) &&
(blend >= 0.0f) && (blend <= 1.0f))
{
int currentFrameA = (int)frameA%animA.keyframeCount;
int nextFrameA = currentFrameA + 1;
float blendA = frameA - currentFrameA;
blendA = Clamp(blendA, 0.0f, 1.0f);
if (currentFrameA >= animA.keyframeCount) currentFrameA = currentFrameA%animA.keyframeCount;
if (nextFrameA >= animA.keyframeCount) nextFrameA = nextFrameA%animA.keyframeCount;
int currentFrameB = (int)frameB%animB.keyframeCount;
int nextFrameB = currentFrameB + 1;
float blendB = frameB - currentFrameB;
blendB = Clamp(blendB, 0.0f, 1.0f);
if (currentFrameB >= animB.keyframeCount) currentFrameB = currentFrameB%animB.keyframeCount;
if (nextFrameB >= animB.keyframeCount) nextFrameB = nextFrameB%animB.keyframeCount;
Matrix bindPoseMatrix = { 0 };
Matrix currentPoseMatrix = { 0 };
for (int boneIndex = 0; boneIndex < model.skeleton.boneCount; boneIndex++)
{
Vector3 frameATranslation = Vector3Lerp(
animA.keyframePoses[currentFrameA][boneIndex].translation,
animA.keyframePoses[nextFrameA][boneIndex].translation, blendA);
Quaternion frameARotation = QuaternionSlerp(
animA.keyframePoses[currentFrameA][boneIndex].rotation,
animA.keyframePoses[nextFrameA][boneIndex].rotation, blendA);
Vector3 frameAScale = Vector3Lerp(
animA.keyframePoses[currentFrameA][boneIndex].scale,
animA.keyframePoses[nextFrameA][boneIndex].scale, blendA);
Vector3 frameBTranslation = Vector3Lerp(
animB.keyframePoses[currentFrameB][boneIndex].translation,
animB.keyframePoses[nextFrameB][boneIndex].translation, blendB);
Quaternion frameBRotation = QuaternionSlerp(
animB.keyframePoses[currentFrameB][boneIndex].rotation,
animB.keyframePoses[nextFrameB][boneIndex].rotation, blendB);
Vector3 frameBScale = Vector3Lerp(
animB.keyframePoses[currentFrameB][boneIndex].scale,
animB.keyframePoses[nextFrameB][boneIndex].scale, blendB);
model.currentPose[boneIndex].translation = Vector3Lerp(frameATranslation, frameBTranslation, blend);
model.currentPose[boneIndex].rotation = QuaternionSlerp(frameARotation, frameBRotation, blend);
model.currentPose[boneIndex].scale = Vector3Lerp(frameAScale, frameBScale, blend);
Transform *bindPoseTransform = &model.skeleton.bindPose[boneIndex];
bindPoseMatrix = MatrixMultiply(
MatrixMultiply(MatrixScale(bindPoseTransform->scale.x, bindPoseTransform->scale.y, bindPoseTransform->scale.z),
QuaternionToMatrix(bindPoseTransform->rotation)),
MatrixTranslate(bindPoseTransform->translation.x, bindPoseTransform->translation.y, bindPoseTransform->translation.z));
Transform *currentPoseTransform = &model.currentPose[boneIndex];
currentPoseMatrix = MatrixMultiply(
MatrixMultiply(MatrixScale(currentPoseTransform->scale.x, currentPoseTransform->scale.y, currentPoseTransform->scale.z),
QuaternionToMatrix(currentPoseTransform->rotation)),
MatrixTranslate(currentPoseTransform->translation.x, currentPoseTransform->translation.y, currentPoseTransform->translation.z));
model.boneMatrices[boneIndex] = MatrixMultiply(MatrixInvert(bindPoseMatrix), currentPoseMatrix);
}
UpdateModelAnimationVertexBuffers(model);
}
}
static void UpdateModelAnimationVertexBuffers(Model model)
{
for (int m = 0; m < model.meshCount; m++)
{
Mesh mesh = model.meshes[m];
Vector3 animVertex = { 0 };
Vector3 animNormal = { 0 };
const int vertexValuesCount = mesh.vertexCount*3;
int boneIndex = 0;
int boneCounter = 0;
float boneWeight = 0.0f;
bool bufferUpdateRequired = false;
if ((mesh.boneWeights == NULL) || (mesh.boneIndices == NULL) ||
(mesh.animVertices == NULL) || (mesh.animNormals == NULL)) continue;
for (int vCounter = 0; vCounter < vertexValuesCount; vCounter += 3)
{
mesh.animVertices[vCounter] = 0;
mesh.animVertices[vCounter + 1] = 0;
mesh.animVertices[vCounter + 2] = 0;
if (mesh.animNormals != NULL)
{
mesh.animNormals[vCounter] = 0;
mesh.animNormals[vCounter + 1] = 0;
mesh.animNormals[vCounter + 2] = 0;
}
for (int j = 0; j < 4; j++, boneCounter++)
{
boneWeight = mesh.boneWeights[boneCounter];
boneIndex = mesh.boneIndices[boneCounter];
if (boneWeight == 0.0f) continue;
animVertex = (Vector3){ mesh.vertices[vCounter], mesh.vertices[vCounter + 1], mesh.vertices[vCounter + 2] };
animVertex = Vector3Transform(animVertex, model.boneMatrices[boneIndex]);
mesh.animVertices[vCounter] += animVertex.x*boneWeight;
mesh.animVertices[vCounter + 1] += animVertex.y*boneWeight;
mesh.animVertices[vCounter + 2] += animVertex.z*boneWeight;
bufferUpdateRequired = true;
if ((mesh.normals != NULL) && (mesh.animNormals != NULL ))
{
animNormal = (Vector3){ mesh.normals[vCounter], mesh.normals[vCounter + 1], mesh.normals[vCounter + 2] };
animNormal = Vector3Transform(animNormal, MatrixTranspose(MatrixInvert(model.boneMatrices[boneIndex])));
mesh.animNormals[vCounter] += animNormal.x*boneWeight;
mesh.animNormals[vCounter + 1] += animNormal.y*boneWeight;
mesh.animNormals[vCounter + 2] += animNormal.z*boneWeight;
}
}
}
if (bufferUpdateRequired)
{
rlUpdateVertexBuffer(mesh.vboId[SHADER_LOC_VERTEX_POSITION], mesh.animVertices, mesh.vertexCount*3*sizeof(float), 0);
if (mesh.normals != NULL) rlUpdateVertexBuffer(mesh.vboId[SHADER_LOC_VERTEX_NORMAL], mesh.animNormals, mesh.vertexCount*3*sizeof(float), 0);
}
}
}
void UnloadModelAnimations(ModelAnimation *animations, int animCount)
{
for (int a = 0; a < animCount; a++)
{
for (int i = 0; i < animations[a].keyframeCount; i++)
RL_FREE(animations[a].keyframePoses[i]);
RL_FREE(animations[a].keyframePoses);
}
RL_FREE(animations);
}
bool IsModelAnimationValid(Model model, ModelAnimation anim)
{
int result = true;
if (model.skeleton.boneCount != anim.boneCount) result = false;
return result;
}
#if SUPPORT_MESH_GENERATION
Mesh GenMeshPoly(int sides, float radius)
{
Mesh mesh = { 0 };
if (sides < 3) return mesh;
int vertexCount = sides*3;
Vector3 *vertices = (Vector3 *)RL_MALLOC(vertexCount*sizeof(Vector3));
float d = 0.0f, dStep = 360.0f/sides;
for (int v = 0; v < vertexCount - 2; v += 3)
{
vertices[v] = (Vector3){ 0.0f, 0.0f, 0.0f };
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;
}
Vector3 *normals = (Vector3 *)RL_MALLOC(vertexCount*sizeof(Vector3));
for (int n = 0; n < vertexCount; n++) normals[n] = (Vector3){ 0.0f, 1.0f, 0.0f };
Vector2 *texcoords = (Vector2 *)RL_MALLOC(vertexCount*sizeof(Vector2));
for (int n = 0; n < vertexCount; n++) texcoords[n] = (Vector2){ 0.0f, 0.0f };
mesh.vertexCount = vertexCount;
mesh.triangleCount = sides;
mesh.vertices = (float *)RL_MALLOC(mesh.vertexCount*3*sizeof(float));
mesh.texcoords = (float *)RL_MALLOC(mesh.vertexCount*2*sizeof(float));
mesh.normals = (float *)RL_MALLOC(mesh.vertexCount*3*sizeof(float));
for (int i = 0; i < mesh.vertexCount; i++)
{
mesh.vertices[3*i] = vertices[i].x;
mesh.vertices[3*i + 1] = vertices[i].y;
mesh.vertices[3*i + 2] = vertices[i].z;
}
for (int i = 0; i < mesh.vertexCount; i++)
{
mesh.texcoords[2*i] = texcoords[i].x;
mesh.texcoords[2*i + 1] = texcoords[i].y;
}
for (int i = 0; i < mesh.vertexCount; i++)
{
mesh.normals[3*i] = normals[i].x;
mesh.normals[3*i + 1] = normals[i].y;
mesh.normals[3*i + 2] = normals[i].z;
}
RL_FREE(vertices);
RL_FREE(normals);
RL_FREE(texcoords);
UploadMesh(&mesh, false);
return mesh;
}
Mesh GenMeshPlane(float width, float length, int resX, int resZ)
{
Mesh mesh = { 0 };
#define CUSTOM_MESH_GEN_PLANE
#if defined(CUSTOM_MESH_GEN_PLANE)
resX++;
resZ++;
int vertexCount = resX*resZ;
Vector3 *vertices = (Vector3 *)RL_MALLOC(vertexCount*sizeof(Vector3));
for (int z = 0; z < resZ; z++)
{
float zPos = ((float)z/(resZ - 1) - 0.5f)*length;
for (int x = 0; x < resX; x++)
{
float xPos = ((float)x/(resX - 1) - 0.5f)*width;
vertices[x + z*resX] = (Vector3){ xPos, 0.0f, zPos };
}
}
Vector3 *normals = (Vector3 *)RL_MALLOC(vertexCount*sizeof(Vector3));
for (int n = 0; n < vertexCount; n++) normals[n] = (Vector3){ 0.0f, 1.0f, 0.0f };
Vector2 *texcoords = (Vector2 *)RL_MALLOC(vertexCount*sizeof(Vector2));
for (int v = 0; v < resZ; v++)
{
for (int u = 0; u < resX; u++)
{
texcoords[u + v*resX] = (Vector2){ (float)u/(resX - 1), (float)v/(resZ - 1) };
}
}
int numFaces = (resX - 1)*(resZ - 1);
int *triangles = (int *)RL_MALLOC(numFaces*6*sizeof(int));
int t = 0;
for (int face = 0; face < numFaces; face++)
{
int i = face + face/(resX - 1);
triangles[t++] = i + resX;
triangles[t++] = i + 1;
triangles[t++] = i;
triangles[t++] = i + resX;
triangles[t++] = i + resX + 1;
triangles[t++] = i + 1;
}
mesh.vertexCount = vertexCount;
mesh.triangleCount = numFaces*2;
mesh.vertices = (float *)RL_MALLOC(mesh.vertexCount*3*sizeof(float));
mesh.texcoords = (float *)RL_MALLOC(mesh.vertexCount*2*sizeof(float));
mesh.normals = (float *)RL_MALLOC(mesh.vertexCount*3*sizeof(float));
mesh.indices = (unsigned short *)RL_MALLOC(mesh.triangleCount*3*sizeof(unsigned short));
for (int i = 0; i < mesh.vertexCount; i++)
{
mesh.vertices[3*i] = vertices[i].x;
mesh.vertices[3*i + 1] = vertices[i].y;
mesh.vertices[3*i + 2] = vertices[i].z;
}
for (int i = 0; i < mesh.vertexCount; i++)
{
mesh.texcoords[2*i] = texcoords[i].x;
mesh.texcoords[2*i + 1] = texcoords[i].y;
}
for (int i = 0; i < mesh.vertexCount; i++)
{
mesh.normals[3*i] = normals[i].x;
mesh.normals[3*i + 1] = normals[i].y;
mesh.normals[3*i + 2] = normals[i].z;
}
for (int i = 0; i < mesh.triangleCount*3; i++) mesh.indices[i] = triangles[i];
RL_FREE(vertices);
RL_FREE(normals);
RL_FREE(texcoords);
RL_FREE(triangles);
#else
par_shapes_mesh *plane = par_shapes_create_plane(resX, resZ); par_shapes_scale(plane, width, length, 1.0f);
par_shapes_rotate(plane, -PI/2.0f, (float[]){ 1, 0, 0 });
par_shapes_translate(plane, -width/2, 0.0f, length/2);
mesh.vertices = (float *)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.vertexCount = plane->ntriangles*3;
mesh.triangleCount = plane->ntriangles;
for (int k = 0; k < mesh.vertexCount; k++)
{
mesh.vertices[k*3] = plane->points[plane->triangles[k]*3];
mesh.vertices[k*3 + 1] = plane->points[plane->triangles[k]*3 + 1];
mesh.vertices[k*3 + 2] = plane->points[plane->triangles[k]*3 + 2];
mesh.normals[k*3] = plane->normals[plane->triangles[k]*3];
mesh.normals[k*3 + 1] = plane->normals[plane->triangles[k]*3 + 1];
mesh.normals[k*3 + 2] = plane->normals[plane->triangles[k]*3 + 2];
mesh.texcoords[k*2] = plane->tcoords[plane->triangles[k]*2];
mesh.texcoords[k*2 + 1] = plane->tcoords[plane->triangles[k]*2 + 1];
}
par_shapes_free_mesh(plane);
#endif
UploadMesh(&mesh, false);
return mesh;
}
Mesh GenMeshCube(float width, float height, float length)
{
Mesh mesh = { 0 };
#define CUSTOM_MESH_GEN_CUBE
#if defined(CUSTOM_MESH_GEN_CUBE)
float vertices[] = {
-width/2, -height/2, length/2,
width/2, -height/2, length/2,
width/2, height/2, length/2,
-width/2, height/2, length/2,
-width/2, -height/2, -length/2,
-width/2, height/2, -length/2,
width/2, height/2, -length/2,
width/2, -height/2, -length/2,
-width/2, height/2, -length/2,
-width/2, height/2, length/2,
width/2, height/2, length/2,
width/2, height/2, -length/2,
-width/2, -height/2, -length/2,
width/2, -height/2, -length/2,
width/2, -height/2, length/2,
-width/2, -height/2, length/2,
width/2, -height/2, -length/2,
width/2, height/2, -length/2,
width/2, height/2, length/2,
width/2, -height/2, length/2,
-width/2, -height/2, -length/2,
-width/2, -height/2, length/2,
-width/2, height/2, length/2,
-width/2, height/2, -length/2
};
float texcoords[] = {
0.0f, 0.0f,
1.0f, 0.0f,
1.0f, 1.0f,
0.0f, 1.0f,
1.0f, 0.0f,
1.0f, 1.0f,
0.0f, 1.0f,
0.0f, 0.0f,
0.0f, 1.0f,
0.0f, 0.0f,
1.0f, 0.0f,
1.0f, 1.0f,
1.0f, 1.0f,
0.0f, 1.0f,
0.0f, 0.0f,
1.0f, 0.0f,
1.0f, 0.0f,
1.0f, 1.0f,
0.0f, 1.0f,
0.0f, 0.0f,
0.0f, 0.0f,
1.0f, 0.0f,
1.0f, 1.0f,
0.0f, 1.0f
};
float normals[] = {
0.0f, 0.0f, 1.0f,
0.0f, 0.0f, 1.0f,
0.0f, 0.0f, 1.0f,
0.0f, 0.0f, 1.0f,
0.0f, 0.0f,-1.0f,
0.0f, 0.0f,-1.0f,
0.0f, 0.0f,-1.0f,
0.0f, 0.0f,-1.0f,
0.0f, 1.0f, 0.0f,
0.0f, 1.0f, 0.0f,
0.0f, 1.0f, 0.0f,
0.0f, 1.0f, 0.0f,
0.0f,-1.0f, 0.0f,
0.0f,-1.0f, 0.0f,
0.0f,-1.0f, 0.0f,
0.0f,-1.0f, 0.0f,
1.0f, 0.0f, 0.0f,
1.0f, 0.0f, 0.0f,
1.0f, 0.0f, 0.0f,
1.0f, 0.0f, 0.0f,
-1.0f, 0.0f, 0.0f,
-1.0f, 0.0f, 0.0f,
-1.0f, 0.0f, 0.0f,
-1.0f, 0.0f, 0.0f
};
mesh.vertices = (float *)RL_MALLOC(24*3*sizeof(float));
memcpy(mesh.vertices, vertices, 24*3*sizeof(float));
mesh.texcoords = (float *)RL_MALLOC(24*2*sizeof(float));
memcpy(mesh.texcoords, texcoords, 24*2*sizeof(float));
mesh.normals = (float *)RL_MALLOC(24*3*sizeof(float));
memcpy(mesh.normals, normals, 24*3*sizeof(float));
mesh.indices = (unsigned short *)RL_MALLOC(36*sizeof(unsigned short));
int k = 0;
for (int i = 0; i < 36; i += 6)
{
mesh.indices[i] = 4*k;
mesh.indices[i + 1] = 4*k + 1;
mesh.indices[i + 2] = 4*k + 2;
mesh.indices[i + 3] = 4*k;
mesh.indices[i + 4] = 4*k + 2;
mesh.indices[i + 5] = 4*k + 3;
k++;
}
mesh.vertexCount = 24;
mesh.triangleCount = 12;
#else
par_shapes_mesh *cube = par_shapes_create_cube();
cube->tcoords = PAR_MALLOC(float, 2*cube->npoints);
for (int i = 0; i < 2*cube->npoints; i++) cube->tcoords[i] = 0.0f;
par_shapes_scale(cube, width, height, length);
par_shapes_translate(cube, -width/2, 0.0f, -length/2);
par_shapes_compute_normals(cube);
mesh.vertices = (float *)RL_MALLOC(cube->ntriangles*3*3*sizeof(float));
mesh.texcoords = (float *)RL_MALLOC(cube->ntriangles*3*2*sizeof(float));
mesh.normals = (float *)RL_MALLOC(cube->ntriangles*3*3*sizeof(float));
mesh.vertexCount = cube->ntriangles*3;
mesh.triangleCount = cube->ntriangles;
for (int k = 0; k < mesh.vertexCount; k++)
{
mesh.vertices[k*3] = cube->points[cube->triangles[k]*3];
mesh.vertices[k*3 + 1] = cube->points[cube->triangles[k]*3 + 1];
mesh.vertices[k*3 + 2] = cube->points[cube->triangles[k]*3 + 2];
mesh.normals[k*3] = cube->normals[cube->triangles[k]*3];
mesh.normals[k*3 + 1] = cube->normals[cube->triangles[k]*3 + 1];
mesh.normals[k*3 + 2] = cube->normals[cube->triangles[k]*3 + 2];
mesh.texcoords[k*2] = cube->tcoords[cube->triangles[k]*2];
mesh.texcoords[k*2 + 1] = cube->tcoords[cube->triangles[k]*2 + 1];
}
par_shapes_free_mesh(cube);
#endif
UploadMesh(&mesh, false);
return mesh;
}
Mesh GenMeshSphere(float radius, int rings, int slices)
{
Mesh mesh = { 0 };
if ((rings >= 3) && (slices >= 3))
{
par_shapes_set_epsilon_degenerate_sphere(0.0);
par_shapes_mesh *sphere = par_shapes_create_parametric_sphere(slices, rings);
par_shapes_scale(sphere, radius, radius, radius);
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;
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.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);
UploadMesh(&mesh, false);
}
else TRACELOG(LOG_WARNING, "MESH: Failed to generate mesh: sphere");
return mesh;
}
Mesh GenMeshHemiSphere(float radius, int rings, int slices)
{
Mesh mesh = { 0 };
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);
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;
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.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);
UploadMesh(&mesh, false);
}
else TRACELOG(LOG_WARNING, "MESH: Failed to generate mesh: hemisphere");
return mesh;
}
Mesh GenMeshCylinder(float radius, float height, int slices)
{
Mesh mesh = { 0 };
if (slices >= 3)
{
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_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_rotate(capTop, 90*DEG2RAD, (float[]){ 0, 1, 0 });
par_shapes_translate(capTop, 0, height, 0);
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_rotate(capBottom, -90*DEG2RAD, (float[]){ 0, 1, 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.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);
UploadMesh(&mesh, false);
}
else TRACELOG(LOG_WARNING, "MESH: Failed to generate mesh: cylinder");
return mesh;
}
Mesh GenMeshCone(float radius, float height, int slices)
{
Mesh mesh = { 0 };
if (slices >= 3)
{
par_shapes_mesh *cone = par_shapes_create_cone(slices, 8);
par_shapes_scale(cone, radius, radius, height);
par_shapes_rotate(cone, -PI/2.0f, (float[]){ 1, 0, 0 });
par_shapes_rotate(cone, PI/2.0f, (float[]){ 0, 1, 0 });
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(cone, capBottom);
mesh.vertices = (float *)RL_MALLOC(cone->ntriangles*3*3*sizeof(float));
mesh.texcoords = (float *)RL_MALLOC(cone->ntriangles*3*2*sizeof(float));
mesh.normals = (float *)RL_MALLOC(cone->ntriangles*3*3*sizeof(float));
mesh.vertexCount = cone->ntriangles*3;
mesh.triangleCount = cone->ntriangles;
for (int k = 0; k < mesh.vertexCount; k++)
{
mesh.vertices[k*3] = cone->points[cone->triangles[k]*3];
mesh.vertices[k*3 + 1] = cone->points[cone->triangles[k]*3 + 1];
mesh.vertices[k*3 + 2] = cone->points[cone->triangles[k]*3 + 2];
mesh.normals[k*3] = cone->normals[cone->triangles[k]*3];
mesh.normals[k*3 + 1] = cone->normals[cone->triangles[k]*3 + 1];
mesh.normals[k*3 + 2] = cone->normals[cone->triangles[k]*3 + 2];
mesh.texcoords[k*2] = cone->tcoords[cone->triangles[k]*2];
mesh.texcoords[k*2 + 1] = cone->tcoords[cone->triangles[k]*2 + 1];
}
par_shapes_free_mesh(cone);
UploadMesh(&mesh, false);
}
else TRACELOG(LOG_WARNING, "MESH: Failed to generate mesh: cone");
return mesh;
}
Mesh GenMeshTorus(float radius, float size, int radSeg, int sides)
{
Mesh mesh = { 0 };
if ((sides >= 3) && (radSeg >= 3))
{
if (radius > 1.0f) radius = 1.0f;
else if (radius < 0.1f) radius = 0.1f;
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.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];
mesh.normals[k*3] = torus->normals[torus->triangles[k]*3];
mesh.normals[k*3 + 1] = torus->normals[torus->triangles[k]*3 + 1];
mesh.normals[k*3 + 2] = torus->normals[torus->triangles[k]*3 + 2];
mesh.texcoords[k*2] = torus->tcoords[torus->triangles[k]*2];
mesh.texcoords[k*2 + 1] = torus->tcoords[torus->triangles[k]*2 + 1];
}
par_shapes_free_mesh(torus);
UploadMesh(&mesh, false);
}
else TRACELOG(LOG_WARNING, "MESH: Failed to generate mesh: torus");
return mesh;
}
Mesh GenMeshKnot(float radius, float size, int radSeg, int sides)
{
Mesh mesh = { 0 };
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);
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;
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.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);
UploadMesh(&mesh, false);
}
else TRACELOG(LOG_WARNING, "MESH: Failed to generate mesh: knot");
return mesh;
}
Mesh GenMeshHeightmap(Image heightmap, Vector3 size)
{
#define GRAY_VALUE(c) ((float)(c.r + c.g + c.b)/3.0f)
Mesh mesh = { 0 };
int mapX = heightmap.width;
int mapZ = heightmap.height;
Color *pixels = LoadImageColors(heightmap);
mesh.triangleCount = (mapX - 1)*(mapZ - 1)*2;
mesh.vertexCount = mesh.triangleCount*3;
mesh.vertices = (float *)RL_MALLOC(mesh.vertexCount*3*sizeof(float));
mesh.normals = (float *)RL_MALLOC(mesh.vertexCount*3*sizeof(float));
mesh.texcoords = (float *)RL_MALLOC(mesh.vertexCount*2*sizeof(float));
mesh.colors = NULL;
int vCounter = 0; int tcCounter = 0; int nCounter = 0;
Vector3 scaleFactor = { size.x/(mapX - 1), size.y/255.0f, size.z/(mapZ - 1) };
Vector3 vA = { 0 };
Vector3 vB = { 0 };
Vector3 vC = { 0 };
Vector3 vN = { 0 };
for (int z = 0; z < mapZ-1; z++)
{
for (int x = 0; x < mapX-1; x++)
{
mesh.vertices[vCounter] = (float)x*scaleFactor.x;
mesh.vertices[vCounter + 1] = GRAY_VALUE(pixels[x + z*mapX])*scaleFactor.y;
mesh.vertices[vCounter + 2] = (float)z*scaleFactor.z;
mesh.vertices[vCounter + 3] = (float)x*scaleFactor.x;
mesh.vertices[vCounter + 4] = GRAY_VALUE(pixels[x + (z + 1)*mapX])*scaleFactor.y;
mesh.vertices[vCounter + 5] = (float)(z + 1)*scaleFactor.z;
mesh.vertices[vCounter + 6] = (float)(x + 1)*scaleFactor.x;
mesh.vertices[vCounter + 7] = GRAY_VALUE(pixels[(x + 1) + z*mapX])*scaleFactor.y;
mesh.vertices[vCounter + 8] = (float)z*scaleFactor.z;
mesh.vertices[vCounter + 9] = mesh.vertices[vCounter + 6];
mesh.vertices[vCounter + 10] = mesh.vertices[vCounter + 7];
mesh.vertices[vCounter + 11] = mesh.vertices[vCounter + 8];
mesh.vertices[vCounter + 12] = mesh.vertices[vCounter + 3];
mesh.vertices[vCounter + 13] = mesh.vertices[vCounter + 4];
mesh.vertices[vCounter + 14] = mesh.vertices[vCounter + 5];
mesh.vertices[vCounter + 15] = (float)(x + 1)*scaleFactor.x;
mesh.vertices[vCounter + 16] = GRAY_VALUE(pixels[(x + 1) + (z + 1)*mapX])*scaleFactor.y;
mesh.vertices[vCounter + 17] = (float)(z + 1)*scaleFactor.z;
vCounter += 18;
mesh.texcoords[tcCounter] = (float)x/(mapX - 1);
mesh.texcoords[tcCounter + 1] = (float)z/(mapZ - 1);
mesh.texcoords[tcCounter + 2] = (float)x/(mapX - 1);
mesh.texcoords[tcCounter + 3] = (float)(z + 1)/(mapZ - 1);
mesh.texcoords[tcCounter + 4] = (float)(x + 1)/(mapX - 1);
mesh.texcoords[tcCounter + 5] = (float)z/(mapZ - 1);
mesh.texcoords[tcCounter + 6] = mesh.texcoords[tcCounter + 4];
mesh.texcoords[tcCounter + 7] = mesh.texcoords[tcCounter + 5];
mesh.texcoords[tcCounter + 8] = mesh.texcoords[tcCounter + 2];
mesh.texcoords[tcCounter + 9] = mesh.texcoords[tcCounter + 3];
mesh.texcoords[tcCounter + 10] = (float)(x + 1)/(mapX - 1);
mesh.texcoords[tcCounter + 11] = (float)(z + 1)/(mapZ - 1);
tcCounter += 12;
for (int i = 0; i < 18; i += 9)
{
vA.x = mesh.vertices[nCounter + i];
vA.y = mesh.vertices[nCounter + i + 1];
vA.z = mesh.vertices[nCounter + i + 2];
vB.x = mesh.vertices[nCounter + i + 3];
vB.y = mesh.vertices[nCounter + i + 4];
vB.z = mesh.vertices[nCounter + i + 5];
vC.x = mesh.vertices[nCounter + i + 6];
vC.y = mesh.vertices[nCounter + i + 7];
vC.z = mesh.vertices[nCounter + i + 8];
vN = Vector3Normalize(Vector3CrossProduct(Vector3Subtract(vB, vA), Vector3Subtract(vC, vA)));
mesh.normals[nCounter + i] = vN.x;
mesh.normals[nCounter + i + 1] = vN.y;
mesh.normals[nCounter + i + 2] = vN.z;
mesh.normals[nCounter + i + 3] = vN.x;
mesh.normals[nCounter + i + 4] = vN.y;
mesh.normals[nCounter + i + 5] = vN.z;
mesh.normals[nCounter + i + 6] = vN.x;
mesh.normals[nCounter + i + 7] = vN.y;
mesh.normals[nCounter + i + 8] = vN.z;
}
nCounter += 18; }
}
UnloadImageColors(pixels);
UploadMesh(&mesh, false);
return mesh;
}
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 };
Color *pixels = LoadImageColors(cubicmap);
int maxTriangles = cubicmap.width*cubicmap.height*12;
int vCounter = 0; int tcCounter = 0; int nCounter = 0;
float w = cubeSize.x;
float h = cubeSize.z;
float h2 = cubeSize.y;
Vector3 *mapVertices = (Vector3 *)RL_MALLOC(maxTriangles*3*sizeof(Vector3));
Vector2 *mapTexcoords = (Vector2 *)RL_MALLOC(maxTriangles*3*sizeof(Vector2));
Vector3 *mapNormals = (Vector3 *)RL_MALLOC(maxTriangles*3*sizeof(Vector3));
Vector3 n1 = { 1.0f, 0.0f, 0.0f };
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 };
typedef struct RectangleF {
float x;
float y;
float width;
float height;
} RectangleF;
RectangleF rightTexUV = { 0.0f, 0.0f, 0.5f, 0.5f };
RectangleF leftTexUV = { 0.5f, 0.0f, 0.5f, 0.5f };
RectangleF frontTexUV = { 0.0f, 0.0f, 0.5f, 0.5f };
RectangleF backTexUV = { 0.5f, 0.0f, 0.5f, 0.5f };
RectangleF topTexUV = { 0.0f, 0.5f, 0.5f, 0.5f };
RectangleF bottomTexUV = { 0.5f, 0.5f, 0.5f, 0.5f };
for (int z = 0; z < cubicmap.height; ++z)
{
for (int x = 0; x < cubicmap.width; x++)
{
Vector3 v1 = { w*(x - 0.5f), h2, h*(z - 0.5f) };
Vector3 v2 = { w*(x - 0.5f), h2, h*(z + 0.5f) };
Vector3 v3 = { w*(x + 0.5f), h2, h*(z + 0.5f) };
Vector3 v4 = { w*(x + 0.5f), h2, h*(z - 0.5f) };
Vector3 v5 = { w*(x + 0.5f), 0, h*(z - 0.5f) };
Vector3 v6 = { w*(x - 0.5f), 0, h*(z - 0.5f) };
Vector3 v7 = { w*(x - 0.5f), 0, h*(z + 0.5f) };
Vector3 v8 = { w*(x + 0.5f), 0, h*(z + 0.5f) };
if (COLOR_EQUAL(pixels[z*cubicmap.width + x], WHITE))
{
mapVertices[vCounter] = v1;
mapVertices[vCounter + 1] = v2;
mapVertices[vCounter + 2] = v3;
mapVertices[vCounter + 3] = v1;
mapVertices[vCounter + 4] = v3;
mapVertices[vCounter + 5] = v4;
vCounter += 6;
mapNormals[nCounter] = n3;
mapNormals[nCounter + 1] = n3;
mapNormals[nCounter + 2] = n3;
mapNormals[nCounter + 3] = n3;
mapNormals[nCounter + 4] = n3;
mapNormals[nCounter + 5] = n3;
nCounter += 6;
mapTexcoords[tcCounter] = (Vector2){ topTexUV.x, topTexUV.y };
mapTexcoords[tcCounter + 1] = (Vector2){ topTexUV.x, topTexUV.y + topTexUV.height };
mapTexcoords[tcCounter + 2] = (Vector2){ topTexUV.x + topTexUV.width, topTexUV.y + topTexUV.height };
mapTexcoords[tcCounter + 3] = (Vector2){ topTexUV.x, topTexUV.y };
mapTexcoords[tcCounter + 4] = (Vector2){ topTexUV.x + topTexUV.width, topTexUV.y + topTexUV.height };
mapTexcoords[tcCounter + 5] = (Vector2){ topTexUV.x + topTexUV.width, topTexUV.y };
tcCounter += 6;
mapVertices[vCounter] = v6;
mapVertices[vCounter + 1] = v8;
mapVertices[vCounter + 2] = v7;
mapVertices[vCounter + 3] = v6;
mapVertices[vCounter + 4] = v5;
mapVertices[vCounter + 5] = v8;
vCounter += 6;
mapNormals[nCounter] = n4;
mapNormals[nCounter + 1] = n4;
mapNormals[nCounter + 2] = n4;
mapNormals[nCounter + 3] = n4;
mapNormals[nCounter + 4] = n4;
mapNormals[nCounter + 5] = n4;
nCounter += 6;
mapTexcoords[tcCounter] = (Vector2){ bottomTexUV.x + bottomTexUV.width, bottomTexUV.y };
mapTexcoords[tcCounter + 1] = (Vector2){ bottomTexUV.x, bottomTexUV.y + bottomTexUV.height };
mapTexcoords[tcCounter + 2] = (Vector2){ bottomTexUV.x + bottomTexUV.width, bottomTexUV.y + bottomTexUV.height };
mapTexcoords[tcCounter + 3] = (Vector2){ bottomTexUV.x + bottomTexUV.width, bottomTexUV.y };
mapTexcoords[tcCounter + 4] = (Vector2){ bottomTexUV.x, bottomTexUV.y };
mapTexcoords[tcCounter + 5] = (Vector2){ bottomTexUV.x, bottomTexUV.y + bottomTexUV.height };
tcCounter += 6;
if (((z < cubicmap.height - 1) && COLOR_EQUAL(pixels[(z + 1)*cubicmap.width + x], BLACK)) || (z == cubicmap.height - 1))
{
mapVertices[vCounter] = v2;
mapVertices[vCounter + 1] = v7;
mapVertices[vCounter + 2] = v3;
mapVertices[vCounter + 3] = v3;
mapVertices[vCounter + 4] = v7;
mapVertices[vCounter + 5] = v8;
vCounter += 6;
mapNormals[nCounter] = n6;
mapNormals[nCounter + 1] = n6;
mapNormals[nCounter + 2] = n6;
mapNormals[nCounter + 3] = n6;
mapNormals[nCounter + 4] = n6;
mapNormals[nCounter + 5] = n6;
nCounter += 6;
mapTexcoords[tcCounter] = (Vector2){ frontTexUV.x, frontTexUV.y };
mapTexcoords[tcCounter + 1] = (Vector2){ frontTexUV.x, frontTexUV.y + frontTexUV.height };
mapTexcoords[tcCounter + 2] = (Vector2){ frontTexUV.x + frontTexUV.width, frontTexUV.y };
mapTexcoords[tcCounter + 3] = (Vector2){ frontTexUV.x + frontTexUV.width, frontTexUV.y };
mapTexcoords[tcCounter + 4] = (Vector2){ frontTexUV.x, frontTexUV.y + frontTexUV.height };
mapTexcoords[tcCounter + 5] = (Vector2){ frontTexUV.x + frontTexUV.width, frontTexUV.y + frontTexUV.height };
tcCounter += 6;
}
if (((z > 0) && COLOR_EQUAL(pixels[(z - 1)*cubicmap.width + x], BLACK)) || (z == 0))
{
mapVertices[vCounter] = v1;
mapVertices[vCounter + 1] = v5;
mapVertices[vCounter + 2] = v6;
mapVertices[vCounter + 3] = v1;
mapVertices[vCounter + 4] = v4;
mapVertices[vCounter + 5] = v5;
vCounter += 6;
mapNormals[nCounter] = n5;
mapNormals[nCounter + 1] = n5;
mapNormals[nCounter + 2] = n5;
mapNormals[nCounter + 3] = n5;
mapNormals[nCounter + 4] = n5;
mapNormals[nCounter + 5] = n5;
nCounter += 6;
mapTexcoords[tcCounter] = (Vector2){ backTexUV.x + backTexUV.width, backTexUV.y };
mapTexcoords[tcCounter + 1] = (Vector2){ backTexUV.x, backTexUV.y + backTexUV.height };
mapTexcoords[tcCounter + 2] = (Vector2){ backTexUV.x + backTexUV.width, backTexUV.y + backTexUV.height };
mapTexcoords[tcCounter + 3] = (Vector2){ backTexUV.x + backTexUV.width, backTexUV.y };
mapTexcoords[tcCounter + 4] = (Vector2){ backTexUV.x, backTexUV.y };
mapTexcoords[tcCounter + 5] = (Vector2){ backTexUV.x, backTexUV.y + backTexUV.height };
tcCounter += 6;
}
if (((x < cubicmap.width - 1) && COLOR_EQUAL(pixels[z*cubicmap.width + (x + 1)], BLACK)) || (x == cubicmap.width - 1))
{
mapVertices[vCounter] = v3;
mapVertices[vCounter + 1] = v8;
mapVertices[vCounter + 2] = v4;
mapVertices[vCounter + 3] = v4;
mapVertices[vCounter + 4] = v8;
mapVertices[vCounter + 5] = v5;
vCounter += 6;
mapNormals[nCounter] = n1;
mapNormals[nCounter + 1] = n1;
mapNormals[nCounter + 2] = n1;
mapNormals[nCounter + 3] = n1;
mapNormals[nCounter + 4] = n1;
mapNormals[nCounter + 5] = n1;
nCounter += 6;
mapTexcoords[tcCounter] = (Vector2){ rightTexUV.x, rightTexUV.y };
mapTexcoords[tcCounter + 1] = (Vector2){ rightTexUV.x, rightTexUV.y + rightTexUV.height };
mapTexcoords[tcCounter + 2] = (Vector2){ rightTexUV.x + rightTexUV.width, rightTexUV.y };
mapTexcoords[tcCounter + 3] = (Vector2){ rightTexUV.x + rightTexUV.width, rightTexUV.y };
mapTexcoords[tcCounter + 4] = (Vector2){ rightTexUV.x, rightTexUV.y + rightTexUV.height };
mapTexcoords[tcCounter + 5] = (Vector2){ rightTexUV.x + rightTexUV.width, rightTexUV.y + rightTexUV.height };
tcCounter += 6;
}
if (((x > 0) && COLOR_EQUAL(pixels[z*cubicmap.width + (x - 1)], BLACK)) || (x == 0))
{
mapVertices[vCounter] = v1;
mapVertices[vCounter + 1] = v7;
mapVertices[vCounter + 2] = v2;
mapVertices[vCounter + 3] = v1;
mapVertices[vCounter + 4] = v6;
mapVertices[vCounter + 5] = v7;
vCounter += 6;
mapNormals[nCounter] = n2;
mapNormals[nCounter + 1] = n2;
mapNormals[nCounter + 2] = n2;
mapNormals[nCounter + 3] = n2;
mapNormals[nCounter + 4] = n2;
mapNormals[nCounter + 5] = n2;
nCounter += 6;
mapTexcoords[tcCounter] = (Vector2){ leftTexUV.x, leftTexUV.y };
mapTexcoords[tcCounter + 1] = (Vector2){ leftTexUV.x + leftTexUV.width, leftTexUV.y + leftTexUV.height };
mapTexcoords[tcCounter + 2] = (Vector2){ leftTexUV.x + leftTexUV.width, leftTexUV.y };
mapTexcoords[tcCounter + 3] = (Vector2){ leftTexUV.x, leftTexUV.y };
mapTexcoords[tcCounter + 4] = (Vector2){ leftTexUV.x, leftTexUV.y + leftTexUV.height };
mapTexcoords[tcCounter + 5] = (Vector2){ leftTexUV.x + leftTexUV.width, leftTexUV.y + leftTexUV.height };
tcCounter += 6;
}
}
else if (COLOR_EQUAL(pixels[z*cubicmap.width + x], BLACK))
{
mapVertices[vCounter] = v1;
mapVertices[vCounter + 1] = v3;
mapVertices[vCounter + 2] = v2;
mapVertices[vCounter + 3] = v1;
mapVertices[vCounter + 4] = v4;
mapVertices[vCounter + 5] = v3;
vCounter += 6;
mapNormals[nCounter] = n4;
mapNormals[nCounter + 1] = n4;
mapNormals[nCounter + 2] = n4;
mapNormals[nCounter + 3] = n4;
mapNormals[nCounter + 4] = n4;
mapNormals[nCounter + 5] = n4;
nCounter += 6;
mapTexcoords[tcCounter] = (Vector2){ topTexUV.x, topTexUV.y };
mapTexcoords[tcCounter + 1] = (Vector2){ topTexUV.x + topTexUV.width, topTexUV.y + topTexUV.height };
mapTexcoords[tcCounter + 2] = (Vector2){ topTexUV.x, topTexUV.y + topTexUV.height };
mapTexcoords[tcCounter + 3] = (Vector2){ topTexUV.x, topTexUV.y };
mapTexcoords[tcCounter + 4] = (Vector2){ topTexUV.x + topTexUV.width, topTexUV.y };
mapTexcoords[tcCounter + 5] = (Vector2){ topTexUV.x + topTexUV.width, topTexUV.y + topTexUV.height };
tcCounter += 6;
mapVertices[vCounter] = v6;
mapVertices[vCounter + 1] = v7;
mapVertices[vCounter + 2] = v8;
mapVertices[vCounter + 3] = v6;
mapVertices[vCounter + 4] = v8;
mapVertices[vCounter + 5] = v5;
vCounter += 6;
mapNormals[nCounter] = n3;
mapNormals[nCounter + 1] = n3;
mapNormals[nCounter + 2] = n3;
mapNormals[nCounter + 3] = n3;
mapNormals[nCounter + 4] = n3;
mapNormals[nCounter + 5] = n3;
nCounter += 6;
mapTexcoords[tcCounter] = (Vector2){ bottomTexUV.x + bottomTexUV.width, bottomTexUV.y };
mapTexcoords[tcCounter + 1] = (Vector2){ bottomTexUV.x + bottomTexUV.width, bottomTexUV.y + bottomTexUV.height };
mapTexcoords[tcCounter + 2] = (Vector2){ bottomTexUV.x, bottomTexUV.y + bottomTexUV.height };
mapTexcoords[tcCounter + 3] = (Vector2){ bottomTexUV.x + bottomTexUV.width, bottomTexUV.y };
mapTexcoords[tcCounter + 4] = (Vector2){ bottomTexUV.x, bottomTexUV.y + bottomTexUV.height };
mapTexcoords[tcCounter + 5] = (Vector2){ bottomTexUV.x, bottomTexUV.y };
tcCounter += 6;
}
}
}
mesh.vertexCount = vCounter;
mesh.triangleCount = vCounter/3;
mesh.vertices = (float *)RL_MALLOC(mesh.vertexCount*3*sizeof(float));
mesh.normals = (float *)RL_MALLOC(mesh.vertexCount*3*sizeof(float));
mesh.texcoords = (float *)RL_MALLOC(mesh.vertexCount*2*sizeof(float));
mesh.colors = NULL;
int fCounter = 0;
for (int i = 0; i < vCounter; i++)
{
mesh.vertices[fCounter] = mapVertices[i].x;
mesh.vertices[fCounter + 1] = mapVertices[i].y;
mesh.vertices[fCounter + 2] = mapVertices[i].z;
fCounter += 3;
}
fCounter = 0;
for (int i = 0; i < nCounter; i++)
{
mesh.normals[fCounter] = mapNormals[i].x;
mesh.normals[fCounter + 1] = mapNormals[i].y;
mesh.normals[fCounter + 2] = mapNormals[i].z;
fCounter += 3;
}
fCounter = 0;
for (int i = 0; i < tcCounter; i++)
{
mesh.texcoords[fCounter] = mapTexcoords[i].x;
mesh.texcoords[fCounter + 1] = mapTexcoords[i].y;
fCounter += 2;
}
RL_FREE(mapVertices);
RL_FREE(mapNormals);
RL_FREE(mapTexcoords);
UnloadImageColors(pixels);
UploadMesh(&mesh, false);
return mesh;
}
#endif
BoundingBox GetMeshBoundingBox(Mesh mesh)
{
Vector3 minVertex = { 0 };
Vector3 maxVertex = { 0 };
if (mesh.vertices != NULL)
{
minVertex = (Vector3){ mesh.vertices[0], mesh.vertices[1], mesh.vertices[2] };
maxVertex = (Vector3){ mesh.vertices[0], mesh.vertices[1], mesh.vertices[2] };
for (int i = 1; i < mesh.vertexCount; i++)
{
minVertex = Vector3Min(minVertex, (Vector3){ mesh.vertices[i*3], mesh.vertices[i*3 + 1], mesh.vertices[i*3 + 2] });
maxVertex = Vector3Max(maxVertex, (Vector3){ mesh.vertices[i*3], mesh.vertices[i*3 + 1], mesh.vertices[i*3 + 2] });
}
}
BoundingBox box = { 0 };
box.min = minVertex;
box.max = maxVertex;
return box;
}
void GenMeshTangents(Mesh *mesh)
{
if ((mesh == NULL) || (mesh->vertices == NULL) || (mesh->texcoords == NULL) || (mesh->normals == NULL))
{
TRACELOG(LOG_WARNING, "MESH: Tangents generation requires vertices, texcoords and normals vertex attribute data");
return;
}
if (mesh->tangents == NULL) mesh->tangents = (float *)RL_MALLOC(mesh->vertexCount*4*sizeof(float));
else
{
RL_FREE(mesh->tangents);
mesh->tangents = (float *)RL_MALLOC(mesh->vertexCount*4*sizeof(float));
}
Vector3 *tan1 = (Vector3 *)RL_CALLOC(mesh->vertexCount, sizeof(Vector3));
Vector3 *tan2 = (Vector3 *)RL_CALLOC(mesh->vertexCount, sizeof(Vector3));
if (tan1 == NULL || tan2 == NULL)
{
TRACELOG(LOG_WARNING, "MESH: Failed to allocate temporary memory for tangent calculation");
if (tan1) RL_FREE(tan1);
if (tan2) RL_FREE(tan2);
return;
}
for (int t = 0; t < mesh->triangleCount; t++)
{
int i0 = 0, i1 = 0, i2 = 0;
if (mesh->indices != NULL)
{
i0 = mesh->indices[t*3 + 0];
i1 = mesh->indices[t*3 + 1];
i2 = mesh->indices[t*3 + 2];
}
else
{
i0 = t*3 + 0;
i1 = t*3 + 1;
i2 = t*3 + 2;
}
Vector3 v1 = { mesh->vertices[i0*3 + 0], mesh->vertices[i0*3 + 1], mesh->vertices[i0*3 + 2] };
Vector3 v2 = { mesh->vertices[i1*3 + 0], mesh->vertices[i1*3 + 1], mesh->vertices[i1*3 + 2] };
Vector3 v3 = { mesh->vertices[i2*3 + 0], mesh->vertices[i2*3 + 1], mesh->vertices[i2*3 + 2] };
Vector2 uv1 = { mesh->texcoords[i0*2 + 0], mesh->texcoords[i0*2 + 1] };
Vector2 uv2 = { mesh->texcoords[i1*2 + 0], mesh->texcoords[i1*2 + 1] };
Vector2 uv3 = { mesh->texcoords[i2*2 + 0], mesh->texcoords[i2*2 + 1] };
float x1 = v2.x - v1.x;
float y1 = v2.y - v1.y;
float z1 = v2.z - v1.z;
float x2 = v3.x - v1.x;
float y2 = v3.y - v1.y;
float z2 = v3.z - v1.z;
float s1 = uv2.x - uv1.x;
float t1 = uv2.y - uv1.y;
float s2 = uv3.x - uv1.x;
float t2 = uv3.y - uv1.y;
float div = s1*t2 - s2*t1;
float r = (fabsf(div) < 0.0001f)? 0.0f : 1.0f/div;
Vector3 sdir = { (t2*x1 - t1*x2)*r, (t2*y1 - t1*y2)*r, (t2*z1 - t1*z2)*r };
Vector3 tdir = { (s1*x2 - s2*x1)*r, (s1*y2 - s2*y1)*r, (s1*z2 - s2*z1)*r };
tan1[i0] = Vector3Add(tan1[i0], sdir);
tan1[i1] = Vector3Add(tan1[i1], sdir);
tan1[i2] = Vector3Add(tan1[i2], sdir);
tan2[i0] = Vector3Add(tan2[i0], tdir);
tan2[i1] = Vector3Add(tan2[i1], tdir);
tan2[i2] = Vector3Add(tan2[i2], tdir);
}
for (int i = 0; i < mesh->vertexCount; i++)
{
Vector3 normal = { mesh->normals[i*3 + 0], mesh->normals[i*3 + 1], mesh->normals[i*3 + 2] };
Vector3 tangent = tan1[i];
if (Vector3Length(tangent) < 0.0001f)
{
if (fabsf(normal.z) > 0.707f) tangent = (Vector3){ 1.0f, 0.0f, 0.0f };
else tangent = Vector3Normalize((Vector3){ -normal.y, normal.x, 0.0f });
mesh->tangents[i*4 + 0] = tangent.x;
mesh->tangents[i*4 + 1] = tangent.y;
mesh->tangents[i*4 + 2] = tangent.z;
mesh->tangents[i*4 + 3] = 1.0f;
continue;
}
Vector3 orthogonalized = Vector3Subtract(tangent, Vector3Scale(normal, Vector3DotProduct(normal, tangent)));
if (Vector3Length(orthogonalized) < 0.0001f)
{
if (fabsf(normal.z) > 0.707f) orthogonalized = (Vector3){ 1.0f, 0.0f, 0.0f };
else orthogonalized = Vector3Normalize((Vector3){ -normal.y, normal.x, 0.0f });
}
else
{
orthogonalized = Vector3Normalize(orthogonalized);
}
mesh->tangents[i*4 + 0] = orthogonalized.x;
mesh->tangents[i*4 + 1] = orthogonalized.y;
mesh->tangents[i*4 + 2] = orthogonalized.z;
mesh->tangents[i*4 + 3] = (Vector3DotProduct(Vector3CrossProduct(normal, orthogonalized), tan2[i]) < 0.0f)? -1.0f : 1.0f;
}
RL_FREE(tan1);
RL_FREE(tan2);
if (mesh->vboId != NULL)
{
if (mesh->vboId[SHADER_LOC_VERTEX_TANGENT] != 0)
{
rlUpdateVertexBuffer(mesh->vboId[SHADER_LOC_VERTEX_TANGENT], mesh->tangents, mesh->vertexCount*4*sizeof(float), 0);
}
else
{
mesh->vboId[SHADER_LOC_VERTEX_TANGENT] = rlLoadVertexBuffer(mesh->tangents, mesh->vertexCount*4*sizeof(float), false);
}
rlEnableVertexArray(mesh->vaoId);
rlSetVertexAttribute(RL_DEFAULT_SHADER_ATTRIB_LOCATION_TANGENT, 4, RL_FLOAT, 0, 0, 0);
rlEnableVertexAttribute(RL_DEFAULT_SHADER_ATTRIB_LOCATION_TANGENT);
rlDisableVertexArray();
}
TRACELOG(LOG_INFO, "MESH: Tangents data computed and uploaded for provided mesh");
}
void DrawModel(Model model, Vector3 position, float scale, Color tint)
{
Vector3 vScale = { scale, scale, scale };
Vector3 rotationAxis = { 0.0f, 1.0f, 0.0f };
DrawModelEx(model, position, rotationAxis, 0.0f, vScale, tint);
}
void DrawModelEx(Model model, Vector3 position, Vector3 rotationAxis, float rotationAngle, Vector3 scale, Color tint)
{
Matrix matScale = MatrixScale(scale.x, scale.y, scale.z);
Matrix matRotation = MatrixRotate(rotationAxis, rotationAngle*DEG2RAD);
Matrix matTranslation = MatrixTranslate(position.x, position.y, position.z);
Matrix matTransform = MatrixMultiply(MatrixMultiply(matScale, matRotation), matTranslation);
model.transform = MatrixMultiply(model.transform, matTransform);
for (int i = 0; i < model.meshCount; i++)
{
Material mat = model.materials[model.meshMaterial[i]];
Color colDiffuse = mat.maps[MATERIAL_MAP_DIFFUSE].color;
Color colTinted = { 0 };
colTinted.r = (unsigned char)(((int)colDiffuse.r*(int)tint.r)/255);
colTinted.g = (unsigned char)(((int)colDiffuse.g*(int)tint.g)/255);
colTinted.b = (unsigned char)(((int)colDiffuse.b*(int)tint.b)/255);
colTinted.a = (unsigned char)(((int)colDiffuse.a*(int)tint.a)/255);
mat.maps[MATERIAL_MAP_DIFFUSE].color = colTinted;
if ((mat.shader.locs != NULL) && (mat.shader.locs[SHADER_LOC_MATRIX_BONETRANSFORMS] != -1) && (model.boneMatrices != NULL))
{
rlEnableShader(mat.shader.id); rlSetUniformMatrices(mat.shader.locs[SHADER_LOC_MATRIX_BONETRANSFORMS], model.boneMatrices, model.skeleton.boneCount);
}
DrawMesh(model.meshes[i], mat, model.transform);
mat.maps[MATERIAL_MAP_DIFFUSE].color = colDiffuse;
}
}
void DrawModelWires(Model model, Vector3 position, float scale, Color tint)
{
rlEnableWireMode();
DrawModel(model, position, scale, tint);
rlDisableWireMode();
}
void DrawModelWiresEx(Model model, Vector3 position, Vector3 rotationAxis, float rotationAngle, Vector3 scale, Color tint)
{
rlEnableWireMode();
DrawModelEx(model, position, rotationAxis, rotationAngle, scale, tint);
rlDisableWireMode();
}
void DrawBillboard(Camera camera, Texture2D texture, Vector3 position, float scale, Color tint)
{
Rectangle source = { 0.0f, 0.0f, (float)texture.width, (float)texture.height };
DrawBillboardRec(camera, texture, source, position, (Vector2){ scale*fabsf((float)source.width/source.height), scale }, tint);
}
void DrawBillboardRec(Camera camera, Texture2D texture, Rectangle source, Vector3 position, Vector2 size, Color tint)
{
Vector3 up = { 0.0f, 1.0f, 0.0f };
DrawBillboardPro(camera, texture, source, position, up, size, Vector2Scale(size, 0.5), 0.0f, tint);
}
void DrawBillboardPro(Camera camera, Texture2D texture, Rectangle source, Vector3 position, Vector3 up, Vector2 size, Vector2 origin, float rotation, Color tint)
{
Matrix matView = MatrixLookAt(camera.position, camera.target, camera.up);
Vector3 right = { matView.m0, matView.m4, matView.m8 };
right = Vector3Scale(right, size.x);
up = Vector3Scale(up, size.y);
if (size.x < 0.0f)
{
source.x -= size.x;
source.width *= -1.0;
right = Vector3Negate(right);
origin.x *= -1.0f;
}
if (size.y < 0.0f)
{
source.y -= size.y;
source.height *= -1.0;
up = Vector3Negate(up);
origin.y *= -1.0f;
}
Vector3 forward;
if (rotation != 0.0) forward = Vector3CrossProduct(right, up);
Vector3 origin3D = Vector3Add(Vector3Scale(Vector3Normalize(right), origin.x), Vector3Scale(Vector3Normalize(up), origin.y));
Vector3 points[4];
points[0] = Vector3Zero();
points[1] = right;
points[2] = Vector3Add(up, right);
points[3] = up;
for (int i = 0; i < 4; i++)
{
points[i] = Vector3Subtract(points[i], origin3D);
if (rotation != 0.0) points[i] = Vector3RotateByAxisAngle(points[i], forward, rotation*DEG2RAD);
points[i] = Vector3Add(points[i], position);
}
Vector2 texcoords[4];
texcoords[0] = (Vector2){ (float)source.x/texture.width, (float)(source.y + source.height)/texture.height };
texcoords[1] = (Vector2){ (float)(source.x + source.width)/texture.width, (float)(source.y + source.height)/texture.height };
texcoords[2] = (Vector2){ (float)(source.x + source.width)/texture.width, (float)source.y/texture.height };
texcoords[3] = (Vector2){ (float)source.x/texture.width, (float)source.y/texture.height };
rlSetTexture(texture.id);
rlBegin(RL_QUADS);
rlColor4ub(tint.r, tint.g, tint.b, tint.a);
for (int i = 0; i < 4; i++)
{
rlTexCoord2f(texcoords[i].x, texcoords[i].y);
rlVertex3f(points[i].x, points[i].y, points[i].z);
}
rlEnd();
rlSetTexture(0);
}
void DrawBoundingBox(BoundingBox box, Color color)
{
Vector3 size = { 0 };
size.x = fabsf(box.max.x - box.min.x);
size.y = fabsf(box.max.y - box.min.y);
size.z = fabsf(box.max.z - box.min.z);
Vector3 center = { box.min.x + size.x/2.0f, box.min.y + size.y/2.0f, box.min.z + size.z/2.0f };
DrawCubeWires(center, size.x, size.y, size.z, color);
}
bool CheckCollisionSpheres(Vector3 center1, float radius1, Vector3 center2, float radius2)
{
bool collision = false;
float radSum = radius1 + radius2;
if (Vector3DistanceSqr(center1, center2) <= (radSum*radSum)) collision = true;
return collision;
}
bool CheckCollisionBoxes(BoundingBox box1, BoundingBox box2)
{
bool collision = true;
if ((box1.max.x >= box2.min.x) && (box1.min.x <= box2.max.x))
{
if ((box1.max.y < box2.min.y) || (box1.min.y > box2.max.y)) collision = false;
if ((box1.max.z < box2.min.z) || (box1.min.z > box2.max.z)) collision = false;
}
else collision = false;
return collision;
}
bool CheckCollisionBoxSphere(BoundingBox box, Vector3 center, float radius)
{
bool collision = false;
Vector3 closestPoint = {
Clamp(center.x, box.min.x, box.max.x),
Clamp(center.y, box.min.y, box.max.y),
Clamp(center.z, box.min.z, box.max.z)
};
if (Vector3DistanceSqr(center, closestPoint) <= (radius*radius)) collision = true;
return collision;
}
RayCollision GetRayCollisionSphere(Ray ray, Vector3 center, float radius)
{
RayCollision collision = { 0 };
Vector3 raySpherePos = Vector3Subtract(center, ray.position);
float vector = Vector3DotProduct(raySpherePos, ray.direction);
float distance = Vector3Length(raySpherePos);
float d = radius*radius - (distance*distance - vector*vector);
collision.hit = d >= 0.0f;
if (distance < radius)
{
collision.distance = vector + sqrtf(d);
collision.point = Vector3Add(ray.position, Vector3Scale(ray.direction, collision.distance));
collision.normal = Vector3Negate(Vector3Normalize(Vector3Subtract(collision.point, center)));
}
else
{
collision.distance = vector - sqrtf(d);
collision.point = Vector3Add(ray.position, Vector3Scale(ray.direction, collision.distance));
collision.normal = Vector3Normalize(Vector3Subtract(collision.point, center));
}
return collision;
}
RayCollision GetRayCollisionBox(Ray ray, BoundingBox box)
{
RayCollision collision = { 0 };
bool insideBox = (ray.position.x > box.min.x) && (ray.position.x < box.max.x) &&
(ray.position.y > box.min.y) && (ray.position.y < box.max.y) &&
(ray.position.z > box.min.z) && (ray.position.z < box.max.z);
if (insideBox) ray.direction = Vector3Negate(ray.direction);
float t[11] = { 0 };
t[8] = 1.0f/ray.direction.x;
t[9] = 1.0f/ray.direction.y;
t[10] = 1.0f/ray.direction.z;
t[0] = (box.min.x - ray.position.x)*t[8];
t[1] = (box.max.x - ray.position.x)*t[8];
t[2] = (box.min.y - ray.position.y)*t[9];
t[3] = (box.max.y - ray.position.y)*t[9];
t[4] = (box.min.z - ray.position.z)*t[10];
t[5] = (box.max.z - ray.position.z)*t[10];
t[6] = (float)fmax(fmax(fmin(t[0], t[1]), fmin(t[2], t[3])), fmin(t[4], t[5]));
t[7] = (float)fmin(fmin(fmax(t[0], t[1]), fmax(t[2], t[3])), fmax(t[4], t[5]));
collision.hit = !((t[7] < 0) || (t[6] > t[7]));
collision.distance = t[6];
collision.point = Vector3Add(ray.position, Vector3Scale(ray.direction, collision.distance));
collision.normal = Vector3Lerp(box.min, box.max, 0.5f);
collision.normal = Vector3Subtract(collision.point, collision.normal);
collision.normal = Vector3Scale(collision.normal, 2.01f);
collision.normal = Vector3Divide(collision.normal, Vector3Subtract(box.max, box.min));
collision.normal.x = (float)((int)collision.normal.x);
collision.normal.y = (float)((int)collision.normal.y);
collision.normal.z = (float)((int)collision.normal.z);
collision.normal = Vector3Normalize(collision.normal);
if (insideBox)
{
ray.direction = Vector3Negate(ray.direction);
collision.distance *= -1.0f;
collision.normal = Vector3Negate(collision.normal);
}
return collision;
}
RayCollision GetRayCollisionMesh(Ray ray, Mesh mesh, Matrix transform)
{
RayCollision collision = { 0 };
if (mesh.vertices != NULL)
{
int triangleCount = mesh.triangleCount;
for (int i = 0; i < triangleCount; i++)
{
Vector3 a = { 0 };
Vector3 b = { 0 };
Vector3 c = { 0 };
Vector3 *vertdata = (Vector3 *)mesh.vertices;
if (mesh.indices)
{
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, transform);
b = Vector3Transform(b, transform);
c = Vector3Transform(c, transform);
RayCollision triHitInfo = GetRayCollisionTriangle(ray, a, b, c);
if (triHitInfo.hit)
{
if ((!collision.hit) || (collision.distance > triHitInfo.distance)) collision = triHitInfo;
}
}
}
return collision;
}
RayCollision GetRayCollisionTriangle(Ray ray, Vector3 p1, Vector3 p2, Vector3 p3)
{
#define EPSILON 0.000001f
RayCollision collision = { 0 };
Vector3 edge1 = { 0 };
Vector3 edge2 = { 0 };
Vector3 p = { 0 };
Vector3 q = { 0 };
Vector3 tv = { 0 };
float det = 0.0f, invDet = 0.0f, u = 0.0f, v = 0.0f, t = 0.0f;
edge1 = Vector3Subtract(p2, p1);
edge2 = Vector3Subtract(p3, p1);
p = Vector3CrossProduct(ray.direction, edge2);
det = Vector3DotProduct(edge1, p);
if ((det > -EPSILON) && (det < EPSILON)) return collision;
invDet = 1.0f/det;
tv = Vector3Subtract(ray.position, p1);
u = Vector3DotProduct(tv, p)*invDet;
if ((u < 0.0f) || (u > 1.0f)) return collision;
q = Vector3CrossProduct(tv, edge1);
v = Vector3DotProduct(ray.direction, q)*invDet;
if ((v < 0.0f) || ((u + v) > 1.0f)) return collision;
t = Vector3DotProduct(edge2, q)*invDet;
if (t > EPSILON)
{
collision.hit = true;
collision.distance = t;
collision.normal = Vector3Normalize(Vector3CrossProduct(edge1, edge2));
collision.point = Vector3Add(ray.position, Vector3Scale(ray.direction, t));
}
return collision;
}
RayCollision GetRayCollisionQuad(Ray ray, Vector3 p1, Vector3 p2, Vector3 p3, Vector3 p4)
{
RayCollision collision = { 0 };
collision = GetRayCollisionTriangle(ray, p1, p2, p4);
if (!collision.hit) collision = GetRayCollisionTriangle(ray, p2, p3, p4);
return collision;
}
#if SUPPORT_FILEFORMAT_IQM || SUPPORT_FILEFORMAT_GLTF
static void BuildPoseFromParentJoints(BoneInfo *bones, int boneCount, Transform *transforms)
{
for (int i = 0; i < boneCount; i++)
{
if (bones[i].parent >= 0)
{
if (bones[i].parent > i)
{
TRACELOG(LOG_WARNING, "Skipping bone not topologically sorted: Bone %d has parent %d", i, bones[i].parent);
continue;
}
transforms[i].rotation = QuaternionMultiply(transforms[bones[i].parent].rotation, transforms[i].rotation);
transforms[i].scale = Vector3Multiply(transforms[i].scale, transforms[bones[i].parent].scale);
transforms[i].translation = Vector3Multiply(transforms[i].translation, transforms[bones[i].parent].scale);
transforms[i].translation = Vector3RotateByQuaternion(transforms[i].translation, transforms[bones[i].parent].rotation);
transforms[i].translation = Vector3Add(transforms[i].translation, transforms[bones[i].parent].translation);
}
}
}
#endif
#if SUPPORT_FILEFORMAT_OBJ
static Model LoadOBJ(const char *fileName)
{
tinyobj_attrib_t objAttributes = { 0 };
tinyobj_shape_t *objShapes = NULL;
unsigned int objShapeCount = 0;
tinyobj_material_t *objMaterials = NULL;
unsigned int objMaterialCount = 0;
Model model = { 0 };
model.transform = MatrixIdentity();
char *fileText = LoadFileText(fileName);
if (fileText == NULL)
{
TRACELOG(LOG_WARNING, "MODEL: [%s] Unable to read obj file", fileName);
return model;
}
char currentDir[MAX_FILEPATH_LENGTH] = { 0 };
strncpy(currentDir, GetWorkingDirectory(), MAX_FILEPATH_LENGTH - 1); const char *workingDir = GetDirectoryPath(fileName); if (CHDIR(workingDir) != 0) TRACELOG(LOG_WARNING, "MODEL: [%s] Failed to change working directory", workingDir);
unsigned int dataSize = (unsigned int)strlen(fileText);
unsigned int flags = TINYOBJ_FLAG_TRIANGULATE;
int ret = tinyobj_parse_obj(&objAttributes, &objShapes, &objShapeCount, &objMaterials, &objMaterialCount, fileText, dataSize, flags);
if (ret != TINYOBJ_SUCCESS)
{
TRACELOG(LOG_WARNING, "MODEL: Unable to read obj data %s", fileName);
return model;
}
UnloadFileText(fileText);
unsigned int faceVertIndex = 0;
unsigned int nextShape = 1;
int lastMaterial = -1;
unsigned int meshIndex = 0;
unsigned int nextShapeEnd = objAttributes.num_face_num_verts;
if (objShapeCount > 1) nextShapeEnd = objShapes[nextShape].face_offset;
for (unsigned int faceId = 0; faceId < objAttributes.num_faces; faceId++)
{
if (faceId >= nextShapeEnd)
{
nextShape++;
if (nextShape < objShapeCount) nextShapeEnd = objShapes[nextShape].face_offset;
else nextShapeEnd = objAttributes.num_face_num_verts; meshIndex++;
}
else if ((lastMaterial != -1) && (objAttributes.material_ids[faceId] != lastMaterial))
{
meshIndex++; }
lastMaterial = objAttributes.material_ids[faceId];
faceVertIndex += objAttributes.face_num_verts[faceId];
}
model.meshCount = meshIndex + 1;
model.meshes = (Mesh *)MemAlloc(sizeof(Mesh)*model.meshCount);
if (objMaterialCount > 0)
{
model.materialCount = objMaterialCount;
model.materials = (Material *)MemAlloc(sizeof(Material)*objMaterialCount);
}
else {
model.materialCount = 1;
model.materials = (Material *)MemAlloc(sizeof(Material)*1);
}
model.meshMaterial = (int *)MemAlloc(sizeof(int)*model.meshCount);
unsigned int *localMeshVertexCounts = (unsigned int *)MemAlloc(sizeof(unsigned int)*model.meshCount);
faceVertIndex = 0;
nextShapeEnd = objAttributes.num_face_num_verts;
lastMaterial = -1;
meshIndex = 0;
unsigned int localMeshVertexCount = 0;
nextShape = 1;
if (objShapeCount > 1) nextShapeEnd = objShapes[nextShape].face_offset;
for (unsigned int faceId = 0; faceId < objAttributes.num_faces; faceId++)
{
bool newMesh = false; if (faceId >= nextShapeEnd)
{
nextShape++;
if (nextShape < objShapeCount) nextShapeEnd = objShapes[nextShape].face_offset;
else nextShapeEnd = objAttributes.num_face_num_verts;
newMesh = true;
}
else if ((lastMaterial != -1) && (objAttributes.material_ids[faceId] != lastMaterial))
{
newMesh = true;
}
lastMaterial = objAttributes.material_ids[faceId];
if (newMesh)
{
localMeshVertexCounts[meshIndex] = localMeshVertexCount;
localMeshVertexCount = 0;
meshIndex++;
}
faceVertIndex += objAttributes.face_num_verts[faceId];
localMeshVertexCount += objAttributes.face_num_verts[faceId];
}
localMeshVertexCounts[meshIndex] = localMeshVertexCount;
for (int i = 0; i < model.meshCount; i++)
{
unsigned int vertexCount = localMeshVertexCounts[i];
model.meshes[i].vertexCount = vertexCount;
model.meshes[i].triangleCount = vertexCount/3;
model.meshes[i].vertices = (float *)MemAlloc(sizeof(float)*vertexCount*3);
model.meshes[i].normals = (float *)MemAlloc(sizeof(float)*vertexCount*3);
#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)
model.meshes[i].texcoords = (float *)MemAlloc(sizeof(float)*vertexCount*2);
model.meshes[i].colors = (unsigned char *)MemAlloc(sizeof(unsigned char)*vertexCount*4);
#else
if (objAttributes.texcoords != NULL && objAttributes.num_texcoords > 0)
model.meshes[i].texcoords = (float *)MemAlloc(sizeof(float)*vertexCount*2);
else model.meshes[i].texcoords = NULL;
model.meshes[i].colors = NULL;
#endif
}
MemFree(localMeshVertexCounts);
localMeshVertexCounts = NULL;
faceVertIndex = 0;
nextShapeEnd = objAttributes.num_face_num_verts;
nextShape = 1;
if (objShapeCount > 1) nextShapeEnd = objShapes[nextShape].face_offset;
lastMaterial = -1;
meshIndex = 0;
localMeshVertexCount = 0;
for (unsigned int faceId = 0; faceId < objAttributes.num_faces; faceId++)
{
bool newMesh = false; if (faceId >= nextShapeEnd)
{
nextShape++;
if (nextShape < objShapeCount) nextShapeEnd = objShapes[nextShape].face_offset;
else nextShapeEnd = objAttributes.num_face_num_verts; newMesh = true;
}
if (lastMaterial != -1 && objAttributes.material_ids[faceId] != lastMaterial) newMesh = true;
lastMaterial = objAttributes.material_ids[faceId];
if (newMesh)
{
localMeshVertexCount = 0;
meshIndex++;
}
int matId = 0;
if ((lastMaterial >= 0) && (lastMaterial < (int)objMaterialCount)) matId = lastMaterial;
model.meshMaterial[meshIndex] = matId;
for (int f = 0; f < objAttributes.face_num_verts[faceId]; f++)
{
int vertIndex = objAttributes.faces[faceVertIndex].v_idx;
int normalIndex = objAttributes.faces[faceVertIndex].vn_idx;
int texcordIndex = objAttributes.faces[faceVertIndex].vt_idx;
for (int i = 0; i < 3; i++) model.meshes[meshIndex].vertices[localMeshVertexCount*3 + i] = objAttributes.vertices[vertIndex*3 + i];
if ((objAttributes.texcoords != NULL) && (texcordIndex != TINYOBJ_INVALID_INDEX) && (texcordIndex >= 0) && (model.meshes[meshIndex].texcoords))
{
for (int i = 0; i < 2; i++) model.meshes[meshIndex].texcoords[localMeshVertexCount*2 + i] = objAttributes.texcoords[texcordIndex*2 + i];
model.meshes[meshIndex].texcoords[localMeshVertexCount*2 + 1] = 1.0f - model.meshes[meshIndex].texcoords[localMeshVertexCount*2 + 1];
}
if ((objAttributes.normals != NULL) && (normalIndex != TINYOBJ_INVALID_INDEX) && (normalIndex >= 0))
{
for (int i = 0; i < 3; i++) model.meshes[meshIndex].normals[localMeshVertexCount*3 + i] = objAttributes.normals[normalIndex*3 + i];
}
else
{
model.meshes[meshIndex].normals[localMeshVertexCount*3 + 0] = 0.0f;
model.meshes[meshIndex].normals[localMeshVertexCount*3 + 1] = 1.0f;
model.meshes[meshIndex].normals[localMeshVertexCount*3 + 2] = 0.0f;
}
#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)
for (int i = 0; i < 4; i++) model.meshes[meshIndex].colors[localMeshVertexCount*4 + i] = 255;
#endif
faceVertIndex++;
localMeshVertexCount++;
}
}
if (objMaterialCount > 0) ProcessMaterialsOBJ(model.materials, objMaterials, objMaterialCount);
else model.materials[0] = LoadMaterialDefault();
tinyobj_attrib_free(&objAttributes);
tinyobj_shapes_free(objShapes, objShapeCount);
tinyobj_materials_free(objMaterials, objMaterialCount);
if (CHDIR(currentDir) != 0)
{
TRACELOG(LOG_WARNING, "MODEL: [%s] Failed to change working directory", currentDir);
}
return model;
}
#endif
#if SUPPORT_FILEFORMAT_IQM
static Model LoadIQM(const char *fileName)
{
#define IQM_MAGIC "INTERQUAKEMODEL"
#define IQM_VERSION 2
#define BONE_NAME_LENGTH 32
#define MESH_NAME_LENGTH 32
#define MATERIAL_NAME_LENGTH 32
int dataSize = 0;
unsigned char *fileData = LoadFileData(fileName, &dataSize);
unsigned char *fileDataPtr = fileData;
typedef struct IQMHeader {
char magic[16];
unsigned int version;
unsigned int dataSize;
unsigned int flags;
unsigned int num_text, ofs_text;
unsigned int num_meshes, ofs_meshes;
unsigned int num_vertexarrays, num_vertexes, ofs_vertexarrays;
unsigned int num_triangles, ofs_triangles, ofs_adjacency;
unsigned int num_joints, ofs_joints;
unsigned int num_poses, ofs_poses;
unsigned int num_anims, ofs_anims;
unsigned int num_frames, num_framechannels, ofs_frames, ofs_bounds;
unsigned int num_comment, ofs_comment;
unsigned int num_extensions, ofs_extensions;
} IQMHeader;
typedef struct IQMMesh {
unsigned int name;
unsigned int material;
unsigned int first_vertex, num_vertexes;
unsigned int first_triangle, num_triangles;
} IQMMesh;
typedef struct IQMTriangle {
unsigned int vertex[3];
} IQMTriangle;
typedef struct IQMJoint {
unsigned int name;
int parent;
float translate[3], rotate[4], scale[3];
} IQMJoint;
typedef struct IQMVertexArray {
unsigned int type;
unsigned int flags;
unsigned int format;
unsigned int size;
unsigned int offset;
} IQMVertexArray;
enum {
IQM_POSITION = 0,
IQM_TEXCOORD = 1,
IQM_NORMAL = 2,
IQM_TANGENT = 3, IQM_BLENDINDEXES = 4,
IQM_BLENDWEIGHTS = 5,
IQM_COLOR = 6,
IQM_CUSTOM = 0x10 };
Model model = { 0 };
IQMMesh *imesh = NULL;
IQMTriangle *tri = NULL;
IQMVertexArray *va = NULL;
IQMJoint *ijoint = NULL;
float *vertex = NULL;
float *normal = NULL;
float *text = NULL;
char *blendi = NULL;
unsigned char *blendw = NULL;
unsigned char *color = NULL;
if (fileDataPtr == NULL) return model;
const char *basePath = GetDirectoryPath(fileName);
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);
UnloadFileData(fileData);
return model;
}
if (iqmHeader->version != IQM_VERSION)
{
TRACELOG(LOG_WARNING, "MODEL: [%s] IQM file version not supported (%i)", fileName, iqmHeader->version);
UnloadFileData(fileData);
return model;
}
imesh = (IQMMesh *)RL_MALLOC(iqmHeader->num_meshes*sizeof(IQMMesh));
memcpy(imesh, fileDataPtr + iqmHeader->ofs_meshes, iqmHeader->num_meshes*sizeof(IQMMesh));
model.meshCount = iqmHeader->num_meshes;
model.meshes = (Mesh *)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++)
{
memcpy(name, fileDataPtr + iqmHeader->ofs_text + imesh[i].name, MESH_NAME_LENGTH*sizeof(char));
memcpy(material, fileDataPtr + iqmHeader->ofs_text + imesh[i].material, MATERIAL_NAME_LENGTH*sizeof(char));
model.materials[i] = LoadMaterialDefault();
model.materials[i].maps[MATERIAL_MAP_ALBEDO].texture = LoadTexture(TextFormat("%s/%s", basePath, material));
model.meshMaterial[i] = i;
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 = (float *)RL_CALLOC(model.meshes[i].vertexCount*3, sizeof(float)); model.meshes[i].normals = (float *)RL_CALLOC(model.meshes[i].vertexCount*3, sizeof(float)); model.meshes[i].texcoords = (float *)RL_CALLOC(model.meshes[i].vertexCount*2, sizeof(float));
model.meshes[i].boneIndices = (unsigned char *)RL_CALLOC(model.meshes[i].vertexCount*4, sizeof(unsigned char)); model.meshes[i].boneWeights = (float *)RL_CALLOC(model.meshes[i].vertexCount*4, sizeof(float));
model.meshes[i].triangleCount = imesh[i].num_triangles;
model.meshes[i].indices = (unsigned short *)RL_CALLOC(model.meshes[i].triangleCount*3, sizeof(unsigned short));
#if !SUPPORT_GPU_SKINNING
model.meshes[i].animVertices = (float *)RL_CALLOC(model.meshes[i].vertexCount*3, sizeof(float));
model.meshes[i].animNormals = (float *)RL_CALLOC(model.meshes[i].vertexCount*3, sizeof(float));
#endif
}
tri = (IQMTriangle *)RL_MALLOC(iqmHeader->num_triangles*sizeof(IQMTriangle));
memcpy(tri, fileDataPtr + iqmHeader->ofs_triangles, iqmHeader->num_triangles*sizeof(IQMTriangle));
for (int m = 0; m < model.meshCount; m++)
{
int tcounter = 0;
for (unsigned int i = imesh[m].first_triangle; i < (imesh[m].first_triangle + imesh[m].num_triangles); i++)
{
model.meshes[m].indices[tcounter + 2] = tri[i].vertex[0] - imesh[m].first_vertex;
model.meshes[m].indices[tcounter + 1] = tri[i].vertex[1] - imesh[m].first_vertex;
model.meshes[m].indices[tcounter] = tri[i].vertex[2] - imesh[m].first_vertex;
tcounter += 3;
}
}
va = (IQMVertexArray *)RL_MALLOC(iqmHeader->num_vertexarrays*sizeof(IQMVertexArray));
memcpy(va, fileDataPtr + iqmHeader->ofs_vertexarrays, iqmHeader->num_vertexarrays*sizeof(IQMVertexArray));
for (unsigned int i = 0; i < iqmHeader->num_vertexarrays; i++)
{
switch (va[i].type)
{
case IQM_POSITION:
{
vertex = (float *)RL_MALLOC(iqmHeader->num_vertexes*3*sizeof(float));
memcpy(vertex, fileDataPtr + va[i].offset, iqmHeader->num_vertexes*3*sizeof(float));
for (unsigned int m = 0; m < iqmHeader->num_meshes; m++)
{
int vCounter = 0;
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];
if (model.meshes[m].animVertices != NULL) model.meshes[m].animVertices[vCounter] = vertex[i];
vCounter++;
}
}
} break;
case IQM_NORMAL:
{
normal = (float *)RL_MALLOC(iqmHeader->num_vertexes*3*sizeof(float));
memcpy(normal, fileDataPtr + va[i].offset, iqmHeader->num_vertexes*3*sizeof(float));
for (unsigned int m = 0; m < iqmHeader->num_meshes; m++)
{
int vCounter = 0;
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];
if (model.meshes[m].animNormals != NULL) model.meshes[m].animNormals[vCounter] = normal[i];
vCounter++;
}
}
} break;
case IQM_TEXCOORD:
{
text = (float *)RL_MALLOC(iqmHeader->num_vertexes*2*sizeof(float));
memcpy(text, fileDataPtr + va[i].offset, iqmHeader->num_vertexes*2*sizeof(float));
for (unsigned int m = 0; m < iqmHeader->num_meshes; m++)
{
int vCounter = 0;
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++;
}
}
} break;
case IQM_BLENDINDEXES:
{
blendi = (char *)RL_MALLOC(iqmHeader->num_vertexes*4*sizeof(char));
memcpy(blendi, fileDataPtr + va[i].offset, iqmHeader->num_vertexes*4*sizeof(char));
for (unsigned int m = 0; m < iqmHeader->num_meshes; m++)
{
int boneCounter = 0;
for (unsigned int i = imesh[m].first_vertex*4; i < (imesh[m].first_vertex + imesh[m].num_vertexes)*4; i++)
{
model.meshes[m].boneIndices[boneCounter] = blendi[i];
boneCounter++;
}
}
} break;
case IQM_BLENDWEIGHTS:
{
blendw = (unsigned char *)RL_MALLOC(iqmHeader->num_vertexes*4*sizeof(unsigned char));
memcpy(blendw, fileDataPtr + va[i].offset, iqmHeader->num_vertexes*4*sizeof(unsigned char));
for (unsigned int m = 0; m < iqmHeader->num_meshes; m++)
{
int boneCounter = 0;
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++;
}
}
} break;
case IQM_COLOR:
{
color = (unsigned char *)RL_MALLOC(iqmHeader->num_vertexes*4*sizeof(unsigned char));
memcpy(color, fileDataPtr + va[i].offset, iqmHeader->num_vertexes*4*sizeof(unsigned char));
for (unsigned int m = 0; m < iqmHeader->num_meshes; m++)
{
model.meshes[m].colors = (unsigned char *)RL_CALLOC(model.meshes[m].vertexCount*4, sizeof(unsigned char));
int vCounter = 0;
for (unsigned int i = imesh[m].first_vertex*4; i < (imesh[m].first_vertex + imesh[m].num_vertexes)*4; i++)
{
model.meshes[m].colors[vCounter] = color[i];
vCounter++;
}
}
} break;
}
}
ijoint = (IQMJoint *)RL_MALLOC(iqmHeader->num_joints*sizeof(IQMJoint));
memcpy(ijoint, fileDataPtr + iqmHeader->ofs_joints, iqmHeader->num_joints*sizeof(IQMJoint));
model.skeleton.boneCount = iqmHeader->num_joints;
model.skeleton.bones = (BoneInfo *)RL_CALLOC(iqmHeader->num_joints, sizeof(BoneInfo));
model.skeleton.bindPose = (Transform *)RL_CALLOC(iqmHeader->num_joints, sizeof(Transform));
for (unsigned int i = 0; i < iqmHeader->num_joints; i++)
{
model.skeleton.bones[i].parent = ijoint[i].parent;
memcpy(model.skeleton.bones[i].name, fileDataPtr + iqmHeader->ofs_text + ijoint[i].name, BONE_NAME_LENGTH*sizeof(char));
model.skeleton.bindPose[i].translation.x = ijoint[i].translate[0];
model.skeleton.bindPose[i].translation.y = ijoint[i].translate[1];
model.skeleton.bindPose[i].translation.z = ijoint[i].translate[2];
model.skeleton.bindPose[i].rotation.x = ijoint[i].rotate[0];
model.skeleton.bindPose[i].rotation.y = ijoint[i].rotate[1];
model.skeleton.bindPose[i].rotation.z = ijoint[i].rotate[2];
model.skeleton.bindPose[i].rotation.w = ijoint[i].rotate[3];
model.skeleton.bindPose[i].scale.x = ijoint[i].scale[0];
model.skeleton.bindPose[i].scale.y = ijoint[i].scale[1];
model.skeleton.bindPose[i].scale.z = ijoint[i].scale[2];
}
BuildPoseFromParentJoints(model.skeleton.bones, model.skeleton.boneCount, model.skeleton.bindPose);
model.currentPose = (Transform *)RL_CALLOC(model.skeleton.boneCount, sizeof(Transform));
model.boneMatrices = (Matrix *)RL_CALLOC(model.skeleton.boneCount, sizeof(Matrix));
for (int j = 0; j < model.skeleton.boneCount; j++) model.boneMatrices[j] = MatrixIdentity();
UnloadFileData(fileData);
RL_FREE(imesh);
RL_FREE(tri);
RL_FREE(va);
RL_FREE(vertex);
RL_FREE(normal);
RL_FREE(text);
RL_FREE(blendi);
RL_FREE(blendw);
RL_FREE(ijoint);
RL_FREE(color);
return model;
}
static ModelAnimation *LoadModelAnimationsIQM(const char *fileName, int *animCount)
{
#define IQM_MAGIC "INTERQUAKEMODEL"
#define IQM_VERSION 2
int dataSize = 0;
unsigned char *fileData = LoadFileData(fileName, &dataSize);
unsigned char *fileDataPtr = fileData;
typedef struct IQMHeader {
char magic[16];
unsigned int version;
unsigned int dataSize;
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 IQMJoint {
unsigned int name;
int parent;
float translate[3], rotate[4], scale[3];
} IQMJoint;
typedef struct IQMPose {
int parent;
unsigned int mask;
float channeloffset[10];
float channelscale[10];
} IQMPose;
typedef struct IQMAnim {
unsigned int name;
unsigned int first_frame, num_frames;
float framerate;
unsigned int flags;
} IQMAnim;
if (fileDataPtr == NULL) return NULL;
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);
UnloadFileData(fileData);
return NULL;
}
if (iqmHeader->version != IQM_VERSION)
{
TRACELOG(LOG_WARNING, "MODEL: [%s] IQM file version not supported (%i)", fileName, iqmHeader->version);
UnloadFileData(fileData);
return NULL;
}
IQMPose *poses = (IQMPose *)RL_MALLOC(iqmHeader->num_poses*sizeof(IQMPose));
memcpy(poses, fileDataPtr + iqmHeader->ofs_poses, iqmHeader->num_poses*sizeof(IQMPose));
*animCount = iqmHeader->num_anims;
IQMAnim *anim = (IQMAnim *)RL_MALLOC(iqmHeader->num_anims*sizeof(IQMAnim));
memcpy(anim, fileDataPtr + iqmHeader->ofs_anims, iqmHeader->num_anims*sizeof(IQMAnim));
ModelAnimation *animations = (ModelAnimation *)RL_CALLOC(iqmHeader->num_anims, sizeof(ModelAnimation));
unsigned short *framedata = (unsigned short *)RL_CALLOC(iqmHeader->num_frames*iqmHeader->num_framechannels, sizeof(unsigned short));
memcpy(framedata, fileDataPtr + iqmHeader->ofs_frames, iqmHeader->num_frames*iqmHeader->num_framechannels*sizeof(unsigned short));
IQMJoint *joints = (IQMJoint *)RL_CALLOC(iqmHeader->num_joints, sizeof(IQMJoint));
memcpy(joints, fileDataPtr + iqmHeader->ofs_joints, iqmHeader->num_joints*sizeof(IQMJoint));
for (unsigned int a = 0; a < iqmHeader->num_anims; a++)
{
animations[a].boneCount = iqmHeader->num_poses;
BoneInfo *bones = (BoneInfo *)RL_CALLOC(iqmHeader->num_poses, sizeof(BoneInfo));
animations[a].keyframeCount = anim[a].num_frames;
animations[a].keyframePoses = (Transform **)RL_CALLOC(anim[a].num_frames, sizeof(Transform *));
memcpy(animations[a].name, fileDataPtr + iqmHeader->ofs_text + anim[a].name, 32);
TRACELOG(LOG_INFO, "MODEL: [%s] Loaded animation: %s | Frames: %d | Framerate: %f", fileName, animations[a].name, animations[a].keyframeCount, anim[a].framerate);
for (unsigned int j = 0; j < iqmHeader->num_poses; j++)
{
bones[j].parent = poses[j].parent;
}
for (unsigned int j = 0; j < anim[a].num_frames; j++)
animations[a].keyframePoses[j] = (Transform *)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].keyframePoses[frame][i].translation.x = poses[i].channeloffset[0];
if (poses[i].mask & 0x01)
{
animations[a].keyframePoses[frame][i].translation.x += framedata[dcounter]*poses[i].channelscale[0];
dcounter++;
}
animations[a].keyframePoses[frame][i].translation.y = poses[i].channeloffset[1];
if (poses[i].mask & 0x02)
{
animations[a].keyframePoses[frame][i].translation.y += framedata[dcounter]*poses[i].channelscale[1];
dcounter++;
}
animations[a].keyframePoses[frame][i].translation.z = poses[i].channeloffset[2];
if (poses[i].mask & 0x04)
{
animations[a].keyframePoses[frame][i].translation.z += framedata[dcounter]*poses[i].channelscale[2];
dcounter++;
}
animations[a].keyframePoses[frame][i].rotation.x = poses[i].channeloffset[3];
if (poses[i].mask & 0x08)
{
animations[a].keyframePoses[frame][i].rotation.x += framedata[dcounter]*poses[i].channelscale[3];
dcounter++;
}
animations[a].keyframePoses[frame][i].rotation.y = poses[i].channeloffset[4];
if (poses[i].mask & 0x10)
{
animations[a].keyframePoses[frame][i].rotation.y += framedata[dcounter]*poses[i].channelscale[4];
dcounter++;
}
animations[a].keyframePoses[frame][i].rotation.z = poses[i].channeloffset[5];
if (poses[i].mask & 0x20)
{
animations[a].keyframePoses[frame][i].rotation.z += framedata[dcounter]*poses[i].channelscale[5];
dcounter++;
}
animations[a].keyframePoses[frame][i].rotation.w = poses[i].channeloffset[6];
if (poses[i].mask & 0x40)
{
animations[a].keyframePoses[frame][i].rotation.w += framedata[dcounter]*poses[i].channelscale[6];
dcounter++;
}
animations[a].keyframePoses[frame][i].scale.x = poses[i].channeloffset[7];
if (poses[i].mask & 0x80)
{
animations[a].keyframePoses[frame][i].scale.x += framedata[dcounter]*poses[i].channelscale[7];
dcounter++;
}
animations[a].keyframePoses[frame][i].scale.y = poses[i].channeloffset[8];
if (poses[i].mask & 0x100)
{
animations[a].keyframePoses[frame][i].scale.y += framedata[dcounter]*poses[i].channelscale[8];
dcounter++;
}
animations[a].keyframePoses[frame][i].scale.z = poses[i].channeloffset[9];
if (poses[i].mask & 0x200)
{
animations[a].keyframePoses[frame][i].scale.z += framedata[dcounter]*poses[i].channelscale[9];
dcounter++;
}
animations[a].keyframePoses[frame][i].rotation = QuaternionNormalize(animations[a].keyframePoses[frame][i].rotation);
}
}
for (unsigned int frame = 0; frame < anim[a].num_frames; frame++)
{
for (int i = 0; i < animations[a].boneCount; i++)
{
if (bones[i].parent >= 0)
{
animations[a].keyframePoses[frame][i].rotation = QuaternionMultiply(animations[a].keyframePoses[frame][bones[i].parent].rotation, animations[a].keyframePoses[frame][i].rotation);
animations[a].keyframePoses[frame][i].translation = Vector3RotateByQuaternion(animations[a].keyframePoses[frame][i].translation, animations[a].keyframePoses[frame][bones[i].parent].rotation);
animations[a].keyframePoses[frame][i].translation = Vector3Add(animations[a].keyframePoses[frame][i].translation, animations[a].keyframePoses[frame][bones[i].parent].translation);
animations[a].keyframePoses[frame][i].scale = Vector3Multiply(animations[a].keyframePoses[frame][i].scale, animations[a].keyframePoses[frame][bones[i].parent].scale);
}
}
}
RL_FREE(bones);
}
UnloadFileData(fileData);
RL_FREE(joints);
RL_FREE(framedata);
RL_FREE(poses);
RL_FREE(anim);
return animations;
}
#endif
#if SUPPORT_FILEFORMAT_GLTF
static cgltf_result LoadFileGLTFCallback(const struct cgltf_memory_options *memoryOptions, const struct cgltf_file_options *fileOptions, const char *path, cgltf_size *size, void **data)
{
int filesize;
unsigned char *filedata = LoadFileData(path, &filesize);
if (filedata == NULL) return cgltf_result_io_error;
*size = filesize;
*data = filedata;
return cgltf_result_success;
}
static void ReleaseFileGLTFCallback(const struct cgltf_memory_options *memoryOptions, const struct cgltf_file_options *fileOptions, void *data, cgltf_size size)
{
UnloadFileData((unsigned char *)data);
}
static Image LoadImageFromCgltfImage(cgltf_image *cgltfImage, const char *texPath)
{
Image image = { 0 };
if (cgltfImage == NULL) return image;
if (cgltfImage->uri != NULL) {
if ((strlen(cgltfImage->uri) > 5) &&
(cgltfImage->uri[0] == 'd') &&
(cgltfImage->uri[1] == 'a') &&
(cgltfImage->uri[2] == 't') &&
(cgltfImage->uri[3] == 'a') &&
(cgltfImage->uri[4] == ':')) {
int i = 0;
while ((cgltfImage->uri[i] != ',') && (cgltfImage->uri[i] != 0)) i++;
if (cgltfImage->uri[i] == 0) TRACELOG(LOG_WARNING, "IMAGE: glTF data URI is not a valid image");
else
{
int base64Size = (int)strlen(cgltfImage->uri + i + 1);
while (cgltfImage->uri[i + base64Size] == '=') base64Size--; int numberOfEncodedBits = base64Size*6 - (base64Size*6) % 8 ; int outSize = numberOfEncodedBits/8 ; void *data = NULL;
cgltf_options options = { 0 };
options.file.read = LoadFileGLTFCallback;
options.file.release = ReleaseFileGLTFCallback;
cgltf_result result = cgltf_load_buffer_base64(&options, outSize, cgltfImage->uri + i + 1, &data);
if (result == cgltf_result_success)
{
image = LoadImageFromMemory(".png", (unsigned char *)data, outSize);
RL_FREE(data);
}
}
}
else {
image = LoadImage(TextFormat("%s/%s", texPath, cgltfImage->uri));
}
}
else if ((cgltfImage->buffer_view != NULL) && (cgltfImage->buffer_view->buffer->data != NULL)) {
unsigned char *data = (unsigned char *)RL_MALLOC(cgltfImage->buffer_view->size);
int offset = (int)cgltfImage->buffer_view->offset;
int stride = (int)cgltfImage->buffer_view->stride? (int)cgltfImage->buffer_view->stride : 1;
for (unsigned int i = 0; i < cgltfImage->buffer_view->size; i++)
{
data[i] = ((unsigned char *)cgltfImage->buffer_view->buffer->data)[offset];
offset += stride;
}
if ((strcmp(cgltfImage->mime_type, "image\\/png") == 0) || (strcmp(cgltfImage->mime_type, "image/png") == 0))
{
image = LoadImageFromMemory(".png", data, (int)cgltfImage->buffer_view->size);
}
else if ((strcmp(cgltfImage->mime_type, "image\\/jpeg") == 0) || (strcmp(cgltfImage->mime_type, "image/jpeg") == 0))
{
image = LoadImageFromMemory(".jpg", data, (int)cgltfImage->buffer_view->size);
}
else TRACELOG(LOG_WARNING, "MODEL: glTF image data MIME type not recognized", TextFormat("%s/%s", texPath, cgltfImage->uri));
RL_FREE(data);
}
return image;
}
static BoneInfo *LoadBoneInfoGLTF(cgltf_skin skin, int *boneCount)
{
*boneCount = (int)skin.joints_count;
BoneInfo *bones = (BoneInfo *)RL_CALLOC(skin.joints_count, sizeof(BoneInfo));
for (unsigned int i = 0; i < skin.joints_count; i++)
{
cgltf_node node = *skin.joints[i];
if (node.name != NULL) strncpy(bones[i].name, node.name, sizeof(bones[i].name) - 1);
int parentIndex = -1;
for (unsigned int j = 0; j < skin.joints_count; j++)
{
if (skin.joints[j] == node.parent)
{
parentIndex = (int)j;
break;
}
}
bones[i].parent = parentIndex;
}
return bones;
}
static Model LoadGLTF(const char *fileName)
{
#define LOAD_ATTRIBUTE(accesor, numComp, srcType, dstPtr) LOAD_ATTRIBUTE_CAST(accesor, numComp, srcType, dstPtr, srcType)
#define LOAD_ATTRIBUTE_CAST(accesor, numComp, srcType, dstPtr, dstType) \
{ \
int n = 0; \
srcType *buffer = (srcType *)accesor->buffer_view->buffer->data + accesor->buffer_view->offset/sizeof(srcType) + accesor->offset/sizeof(srcType); \
for (unsigned int k = 0; k < accesor->count; k++) \
{\
for (int l = 0; l < numComp; l++) \
{\
dstPtr[numComp*k + l] = (dstType)buffer[n + l];\
}\
n += (int)(accesor->stride/sizeof(srcType));\
}\
}
Model model = { 0 };
int dataSize = 0;
unsigned char *fileData = LoadFileData(fileName, &dataSize);
if (fileData == NULL) return model;
cgltf_options options = { 0 };
options.file.read = LoadFileGLTFCallback;
options.file.release = ReleaseFileGLTFCallback;
cgltf_data *data = NULL;
cgltf_result result = cgltf_parse(&options, fileData, dataSize, &data);
if (result == cgltf_result_success)
{
if (data->file_type == cgltf_file_type_glb) TRACELOG(LOG_INFO, "MODEL: [%s] Model basic data (glb) loaded successfully", fileName);
else if (data->file_type == cgltf_file_type_gltf) TRACELOG(LOG_INFO, "MODEL: [%s] Model basic data (glTF) loaded successfully", fileName);
else TRACELOG(LOG_WARNING, "MODEL: [%s] Model format not recognized", fileName);
TRACELOG(LOG_INFO, " > Meshes count: %i", data->meshes_count);
TRACELOG(LOG_INFO, " > Materials count: %i (+1 default)", data->materials_count);
TRACELOG(LOG_DEBUG, " > Buffers count: %i", data->buffers_count);
TRACELOG(LOG_DEBUG, " > Images count: %i", data->images_count);
TRACELOG(LOG_DEBUG, " > Textures count: %i", data->textures_count);
result = cgltf_load_buffers(&options, data, fileName);
if (result != cgltf_result_success) TRACELOG(LOG_INFO, "MODEL: [%s] Failed to load mesh/material buffers", fileName);
int primitivesCount = 0;
bool dracoCompression = false;
for (unsigned int i = 0; i < data->nodes_count; i++)
{
cgltf_node *node = &(data->nodes[i]);
cgltf_mesh *mesh = node->mesh;
if (!mesh) continue;
for (unsigned int p = 0; p < mesh->primitives_count; p++)
{
if (mesh->primitives[p].has_draco_mesh_compression)
{
dracoCompression = true;
TRACELOG(LOG_WARNING, "MODEL: [%s] Failed to load mesh data, Draco compression not supported", fileName);
break;
}
else if (mesh->primitives[p].type == cgltf_primitive_type_triangles) primitivesCount++;
}
}
if (dracoCompression)
{
TRACELOG(LOG_WARNING, "MODEL: [%s] Failed to load glTF data", fileName);
cgltf_free(data);
UnloadFileData(fileData);
return model;
}
TRACELOG(LOG_DEBUG, " > Primitives (triangles only) count based on hierarchy : %i", primitivesCount);
model.meshCount = primitivesCount;
model.meshes = (Mesh *)RL_CALLOC(model.meshCount, sizeof(Mesh));
model.materialCount = (int)data->materials_count + 1;
model.materials = (Material *)RL_CALLOC(model.materialCount, sizeof(Material));
model.materials[0] = LoadMaterialDefault();
model.meshMaterial = (int *)RL_CALLOC(model.meshCount, sizeof(int));
for (unsigned int i = 0, j = 1; i < data->materials_count; i++, j++)
{
model.materials[j] = LoadMaterialDefault();
const char *texPath = GetDirectoryPath(fileName);
if (data->materials[i].has_pbr_metallic_roughness)
{
if (data->materials[i].pbr_metallic_roughness.base_color_texture.texture)
{
Image imAlbedo = LoadImageFromCgltfImage(data->materials[i].pbr_metallic_roughness.base_color_texture.texture->image, texPath);
if (imAlbedo.data != NULL)
{
model.materials[j].maps[MATERIAL_MAP_ALBEDO].texture = LoadTextureFromImage(imAlbedo);
UnloadImage(imAlbedo);
}
}
model.materials[j].maps[MATERIAL_MAP_ALBEDO].color.r = (unsigned char)(data->materials[i].pbr_metallic_roughness.base_color_factor[0]*255);
model.materials[j].maps[MATERIAL_MAP_ALBEDO].color.g = (unsigned char)(data->materials[i].pbr_metallic_roughness.base_color_factor[1]*255);
model.materials[j].maps[MATERIAL_MAP_ALBEDO].color.b = (unsigned char)(data->materials[i].pbr_metallic_roughness.base_color_factor[2]*255);
model.materials[j].maps[MATERIAL_MAP_ALBEDO].color.a = (unsigned char)(data->materials[i].pbr_metallic_roughness.base_color_factor[3]*255);
if (data->materials[i].pbr_metallic_roughness.metallic_roughness_texture.texture)
{
Image imMetallicRoughness = LoadImageFromCgltfImage(data->materials[i].pbr_metallic_roughness.metallic_roughness_texture.texture->image, texPath);
if (imMetallicRoughness.data != NULL)
{
Image imMetallic = { 0 };
Image imRoughness = { 0 };
imMetallic.data = RL_MALLOC(imMetallicRoughness.width*imMetallicRoughness.height);
imRoughness.data = RL_MALLOC(imMetallicRoughness.width*imMetallicRoughness.height);
imMetallic.width = imRoughness.width = imMetallicRoughness.width;
imMetallic.height = imRoughness.height = imMetallicRoughness.height;
imMetallic.format = imRoughness.format = PIXELFORMAT_UNCOMPRESSED_GRAYSCALE;
imMetallic.mipmaps = imRoughness.mipmaps = 1;
for (int x = 0; x < imRoughness.width; x++)
{
for (int y = 0; y < imRoughness.height; y++)
{
Color color = GetImageColor(imMetallicRoughness, x, y);
((unsigned char *)imRoughness.data)[y*imRoughness.width + x] = color.g; ((unsigned char *)imMetallic.data)[y*imMetallic.width + x] = color.b; }
}
model.materials[j].maps[MATERIAL_MAP_ROUGHNESS].texture = LoadTextureFromImage(imRoughness);
model.materials[j].maps[MATERIAL_MAP_METALNESS].texture = LoadTextureFromImage(imMetallic);
UnloadImage(imRoughness);
UnloadImage(imMetallic);
UnloadImage(imMetallicRoughness);
}
float roughness = data->materials[i].pbr_metallic_roughness.roughness_factor;
model.materials[j].maps[MATERIAL_MAP_ROUGHNESS].value = roughness;
float metallic = data->materials[i].pbr_metallic_roughness.metallic_factor;
model.materials[j].maps[MATERIAL_MAP_METALNESS].value = metallic;
}
if (data->materials[i].normal_texture.texture)
{
Image imNormal = LoadImageFromCgltfImage(data->materials[i].normal_texture.texture->image, texPath);
if (imNormal.data != NULL)
{
model.materials[j].maps[MATERIAL_MAP_NORMAL].texture = LoadTextureFromImage(imNormal);
UnloadImage(imNormal);
}
}
if (data->materials[i].occlusion_texture.texture)
{
Image imOcclusion = LoadImageFromCgltfImage(data->materials[i].occlusion_texture.texture->image, texPath);
if (imOcclusion.data != NULL)
{
model.materials[j].maps[MATERIAL_MAP_OCCLUSION].texture = LoadTextureFromImage(imOcclusion);
UnloadImage(imOcclusion);
}
}
if (data->materials[i].emissive_texture.texture)
{
Image imEmissive = LoadImageFromCgltfImage(data->materials[i].emissive_texture.texture->image, texPath);
if (imEmissive.data != NULL)
{
model.materials[j].maps[MATERIAL_MAP_EMISSION].texture = LoadTextureFromImage(imEmissive);
UnloadImage(imEmissive);
}
model.materials[j].maps[MATERIAL_MAP_EMISSION].color.r = (unsigned char)(data->materials[i].emissive_factor[0]*255);
model.materials[j].maps[MATERIAL_MAP_EMISSION].color.g = (unsigned char)(data->materials[i].emissive_factor[1]*255);
model.materials[j].maps[MATERIAL_MAP_EMISSION].color.b = (unsigned char)(data->materials[i].emissive_factor[2]*255);
model.materials[j].maps[MATERIAL_MAP_EMISSION].color.a = 255;
}
}
}
int meshIndex = 0;
for (unsigned int i = 0; i < data->nodes_count; i++)
{
cgltf_node *node = &(data->nodes[i]);
cgltf_mesh *mesh = node->mesh;
if (!mesh) continue;
cgltf_float worldTransform[16];
cgltf_node_transform_world(node, worldTransform);
Matrix worldMatrix = {
worldTransform[0], worldTransform[4], worldTransform[8], worldTransform[12],
worldTransform[1], worldTransform[5], worldTransform[9], worldTransform[13],
worldTransform[2], worldTransform[6], worldTransform[10], worldTransform[14],
worldTransform[3], worldTransform[7], worldTransform[11], worldTransform[15]
};
Matrix worldMatrixNormals = MatrixTranspose(MatrixInvert(worldMatrix));
for (unsigned int p = 0; p < mesh->primitives_count; p++)
{
if (mesh->primitives[p].type != cgltf_primitive_type_triangles) continue;
for (unsigned int j = 0; j < mesh->primitives[p].attributes_count; j++)
{
if (mesh->primitives[p].attributes[j].type == cgltf_attribute_type_position) {
cgltf_accessor *attribute = mesh->primitives[p].attributes[j].data;
if (model.meshes[meshIndex].vertices != NULL) TRACELOG(LOG_WARNING, "MODEL: [%s] Vertices attribute data already loaded", fileName);
else
{
if ((attribute->type == cgltf_type_vec3) && (attribute->component_type == cgltf_component_type_r_32f))
{
model.meshes[meshIndex].vertexCount = (int)attribute->count;
model.meshes[meshIndex].vertices = (float *)RL_MALLOC(attribute->count*3*sizeof(float));
LOAD_ATTRIBUTE(attribute, 3, float, model.meshes[meshIndex].vertices)
float *vertices = model.meshes[meshIndex].vertices;
for (unsigned int k = 0; k < attribute->count; k++)
{
Vector3 vt = Vector3Transform((Vector3){ vertices[3*k], vertices[3*k+1], vertices[3*k+2] }, worldMatrix);
vertices[3*k] = vt.x;
vertices[3*k+1] = vt.y;
vertices[3*k+2] = vt.z;
}
}
else if ((attribute->type == cgltf_type_vec3) && (attribute->component_type == cgltf_component_type_r_16u))
{
model.meshes[meshIndex].vertexCount = (int)attribute->count;
model.meshes[meshIndex].vertices = (float *)RL_MALLOC(attribute->count*3*sizeof(float));
unsigned short *temp = (unsigned short *)RL_MALLOC(attribute->count*3*sizeof(unsigned short));
LOAD_ATTRIBUTE(attribute, 3, unsigned short, temp);
for (unsigned int t = 0; t < attribute->count*3; t++) model.meshes[meshIndex].vertices[t] = (float)temp[t];
RL_FREE(temp);
float *vertices = model.meshes[meshIndex].vertices;
for (unsigned int k = 0; k < attribute->count; k++)
{
Vector3 vt = Vector3Transform((Vector3){ vertices[3*k], vertices[3*k + 1], vertices[3*k + 2] }, worldMatrix);
vertices[3*k] = vt.x;
vertices[3*k + 1] = vt.y;
vertices[3*k + 2] = vt.z;
}
}
else if ((attribute->type == cgltf_type_vec3) && (attribute->component_type == cgltf_component_type_r_16))
{
model.meshes[meshIndex].vertexCount = (int)attribute->count;
model.meshes[meshIndex].vertices = (float *)RL_MALLOC(attribute->count*3*sizeof(float));
short *temp = (short *)RL_MALLOC(attribute->count*3*sizeof(short));
LOAD_ATTRIBUTE(attribute, 3, short, temp);
for (unsigned int t = 0; t < attribute->count*3; t++) model.meshes[meshIndex].vertices[t] = (float)temp[t];
RL_FREE(temp);
float *vertices = model.meshes[meshIndex].vertices;
for (unsigned int k = 0; k < attribute->count; k++)
{
Vector3 vt = Vector3Transform((Vector3){ vertices[3*k], vertices[3*k + 1], vertices[3*k + 2] }, worldMatrix);
vertices[3*k] = vt.x;
vertices[3*k + 1] = vt.y;
vertices[3*k + 2] = vt.z;
}
}
else TRACELOG(LOG_WARNING, "MODEL: [%s] Vertices attribute data format not supported, use vec3 float", fileName);
}
}
else if (mesh->primitives[p].attributes[j].type == cgltf_attribute_type_normal) {
cgltf_accessor *attribute = mesh->primitives[p].attributes[j].data;
if (model.meshes[meshIndex].normals != NULL) TRACELOG(LOG_WARNING, "MODEL: [%s] Normals attribute data already loaded", fileName);
else
{
if ((attribute->type == cgltf_type_vec3) && (attribute->component_type == cgltf_component_type_r_32f))
{
model.meshes[meshIndex].normals = (float *)RL_MALLOC(attribute->count*3*sizeof(float));
LOAD_ATTRIBUTE(attribute, 3, float, model.meshes[meshIndex].normals)
float *normals = model.meshes[meshIndex].normals;
for (unsigned int k = 0; k < attribute->count; k++)
{
Vector3 nt = Vector3Transform((Vector3){ normals[3*k], normals[3*k+1], normals[3*k+2] }, worldMatrixNormals);
normals[3*k] = nt.x;
normals[3*k+1] = nt.y;
normals[3*k+2] = nt.z;
}
}
else if ((attribute->type == cgltf_type_vec3) && (attribute->component_type == cgltf_component_type_r_16))
{
model.meshes[meshIndex].normals = (float *)RL_MALLOC(attribute->count*3*sizeof(float));
short *temp = (short *)RL_MALLOC(attribute->count*3*sizeof(short));
LOAD_ATTRIBUTE(attribute, 3, short, temp);
for (unsigned int t = 0; t < attribute->count*3; t++) model.meshes[meshIndex].normals[t] = (float)temp[t];
RL_FREE(temp);
float *normals = model.meshes[meshIndex].normals;
for (unsigned int k = 0; k < attribute->count; k++)
{
Vector3 nt = Vector3Normalize(Vector3Transform((Vector3){ normals[3*k], normals[3*k + 1], normals[3*k + 2] }, worldMatrixNormals));
normals[3*k] = nt.x;
normals[3*k + 1] = nt.y;
normals[3*k + 2] = nt.z;
}
}
else if ((attribute->type == cgltf_type_vec3) && (attribute->component_type == cgltf_component_type_r_8u))
{
model.meshes[meshIndex].normals = (float *)RL_MALLOC(attribute->count*3*sizeof(float));
unsigned char *temp = (unsigned char *)RL_MALLOC(attribute->count*3*sizeof(unsigned char));
LOAD_ATTRIBUTE(attribute, 3, unsigned char, temp);
for (unsigned int t = 0; t < attribute->count*3; t++) model.meshes[meshIndex].normals[t] = (float)temp[t];
RL_FREE(temp);
float *normals = model.meshes[meshIndex].normals;
for (unsigned int k = 0; k < attribute->count; k++)
{
Vector3 nt = Vector3Normalize(Vector3Transform((Vector3){ normals[3*k], normals[3*k + 1], normals[3*k + 2] }, worldMatrixNormals));
normals[3*k] = nt.x;
normals[3*k + 1] = nt.y;
normals[3*k + 2] = nt.z;
}
}
else if ((attribute->type == cgltf_type_vec3) && (attribute->component_type == cgltf_component_type_r_8))
{
model.meshes[meshIndex].normals = (float *)RL_MALLOC(attribute->count*3*sizeof(float));
char *temp = (char *)RL_MALLOC(attribute->count*3*sizeof(char));
LOAD_ATTRIBUTE(attribute, 3, char, temp);
for (unsigned int t = 0; t < attribute->count*3; t++) model.meshes[meshIndex].normals[t] = (float)temp[t];
RL_FREE(temp);
float *normals = model.meshes[meshIndex].normals;
for (unsigned int k = 0; k < attribute->count; k++)
{
Vector3 nt = Vector3Normalize(Vector3Transform((Vector3){ normals[3*k], normals[3*k + 1], normals[3*k + 2] }, worldMatrixNormals));
normals[3*k] = nt.x;
normals[3*k + 1] = nt.y;
normals[3*k + 2] = nt.z;
}
}
else TRACELOG(LOG_WARNING, "MODEL: [%s] Normals attribute data format not supported, use vec3 float", fileName);
}
}
else if (mesh->primitives[p].attributes[j].type == cgltf_attribute_type_tangent) {
cgltf_accessor *attribute = mesh->primitives[p].attributes[j].data;
if (model.meshes[meshIndex].tangents != NULL) TRACELOG(LOG_WARNING, "MODEL: [%s] Tangents attribute data already loaded", fileName);
else
{
if ((attribute->type == cgltf_type_vec4) && (attribute->component_type == cgltf_component_type_r_32f))
{
model.meshes[meshIndex].tangents = (float *)RL_MALLOC(attribute->count*4*sizeof(float));
LOAD_ATTRIBUTE(attribute, 4, float, model.meshes[meshIndex].tangents)
float *tangents = model.meshes[meshIndex].tangents;
for (unsigned int k = 0; k < attribute->count; k++)
{
Vector3 tt = Vector3Transform((Vector3){ tangents[4*k], tangents[4*k+1], tangents[4*k+2] }, worldMatrix);
tangents[4*k] = tt.x;
tangents[4*k+1] = tt.y;
tangents[4*k+2] = tt.z;
}
}
else TRACELOG(LOG_WARNING, "MODEL: [%s] Tangents attribute data format not supported, use vec4 float", fileName);
}
}
else if (mesh->primitives[p].attributes[j].type == cgltf_attribute_type_texcoord) {
float *texcoordPtr = NULL;
cgltf_accessor *attribute = mesh->primitives[p].attributes[j].data;
if (attribute->type == cgltf_type_vec2)
{
if (attribute->component_type == cgltf_component_type_r_32f) {
texcoordPtr = (float *)RL_MALLOC(attribute->count*2*sizeof(float));
LOAD_ATTRIBUTE(attribute, 2, float, texcoordPtr)
}
else if (attribute->component_type == cgltf_component_type_r_8u) {
texcoordPtr = (float *)RL_MALLOC(attribute->count*2*sizeof(float));
unsigned char *temp = (unsigned char *)RL_MALLOC(attribute->count*2*sizeof(unsigned char));
LOAD_ATTRIBUTE(attribute, 2, unsigned char, temp);
for (unsigned int t = 0; t < attribute->count*2; t++) texcoordPtr[t] = (float)temp[t]/255.0f;
RL_FREE(temp);
}
else if (attribute->component_type == cgltf_component_type_r_16u) {
texcoordPtr = (float *)RL_MALLOC(attribute->count*2*sizeof(float));
unsigned short *temp = (unsigned short *)RL_MALLOC(attribute->count*2*sizeof(unsigned short));
LOAD_ATTRIBUTE(attribute, 2, unsigned short, temp);
for (unsigned int t = 0; t < attribute->count*2; t++) texcoordPtr[t] = (float)temp[t]/65535.0f;
RL_FREE(temp);
}
else TRACELOG(LOG_WARNING, "MODEL: [%s] Texcoords attribute data format not supported", fileName);
}
else TRACELOG(LOG_WARNING, "MODEL: [%s] Texcoords attribute data format not supported, use vec2 float", fileName);
int index = mesh->primitives[p].attributes[j].index;
if (index == 0) model.meshes[meshIndex].texcoords = texcoordPtr;
else if (index == 1) model.meshes[meshIndex].texcoords2 = texcoordPtr;
else
{
TRACELOG(LOG_WARNING, "MODEL: [%s] No more than 2 texture coordinates attributes supported", fileName);
if (texcoordPtr != NULL) RL_FREE(texcoordPtr);
}
}
else if (mesh->primitives[p].attributes[j].type == cgltf_attribute_type_color) {
cgltf_accessor *attribute = mesh->primitives[p].attributes[j].data;
if (model.meshes[meshIndex].colors != NULL) TRACELOG(LOG_WARNING, "MODEL: [%s] Colors attribute data already loaded", fileName);
else
{
if (attribute->type == cgltf_type_vec3) {
if (attribute->component_type == cgltf_component_type_r_8u)
{
model.meshes[meshIndex].colors = (unsigned char *)RL_MALLOC(attribute->count*4*sizeof(unsigned char));
unsigned char *temp = (unsigned char *)RL_MALLOC(attribute->count*3*sizeof(unsigned char));
LOAD_ATTRIBUTE(attribute, 3, unsigned char, temp);
for (unsigned int c = 0, k = 0; c < (attribute->count*4 - 3); c += 4, k += 3)
{
model.meshes[meshIndex].colors[c] = temp[k];
model.meshes[meshIndex].colors[c + 1] = temp[k + 1];
model.meshes[meshIndex].colors[c + 2] = temp[k + 2];
model.meshes[meshIndex].colors[c + 3] = 255;
}
RL_FREE(temp);
}
else if (attribute->component_type == cgltf_component_type_r_16u)
{
model.meshes[meshIndex].colors = (unsigned char *)RL_MALLOC(attribute->count*4*sizeof(unsigned char));
unsigned short *temp = (unsigned short *)RL_MALLOC(attribute->count*3*sizeof(unsigned short));
LOAD_ATTRIBUTE(attribute, 3, unsigned short, temp);
for (unsigned int c = 0, k = 0; c < (attribute->count*4 - 3); c += 4, k += 3)
{
model.meshes[meshIndex].colors[c] = (unsigned char)(((float)temp[k]/65535.0f)*255.0f);
model.meshes[meshIndex].colors[c + 1] = (unsigned char)(((float)temp[k + 1]/65535.0f)*255.0f);
model.meshes[meshIndex].colors[c + 2] = (unsigned char)(((float)temp[k + 2]/65535.0f)*255.0f);
model.meshes[meshIndex].colors[c + 3] = 255;
}
RL_FREE(temp);
}
else if (attribute->component_type == cgltf_component_type_r_32f)
{
model.meshes[meshIndex].colors = (unsigned char *)RL_MALLOC(attribute->count*4*sizeof(unsigned char));
float *temp = (float *)RL_MALLOC(attribute->count*3*sizeof(float));
LOAD_ATTRIBUTE(attribute, 3, float, temp);
for (unsigned int c = 0, k = 0; c < (attribute->count*4 - 3); c += 4, k += 3)
{
model.meshes[meshIndex].colors[c] = (unsigned char)(temp[k]*255.0f);
model.meshes[meshIndex].colors[c + 1] = (unsigned char)(temp[k + 1]*255.0f);
model.meshes[meshIndex].colors[c + 2] = (unsigned char)(temp[k + 2]*255.0f);
model.meshes[meshIndex].colors[c + 3] = 255;
}
RL_FREE(temp);
}
else TRACELOG(LOG_WARNING, "MODEL: [%s] Color attribute data format not supported", fileName);
}
else if (attribute->type == cgltf_type_vec4) {
if (attribute->component_type == cgltf_component_type_r_8u)
{
model.meshes[meshIndex].colors = (unsigned char *)RL_MALLOC(attribute->count*4*sizeof(unsigned char));
LOAD_ATTRIBUTE(attribute, 4, unsigned char, model.meshes[meshIndex].colors)
}
else if (attribute->component_type == cgltf_component_type_r_16u)
{
model.meshes[meshIndex].colors = (unsigned char *)RL_MALLOC(attribute->count*4*sizeof(unsigned char));
unsigned short *temp = (unsigned short *)RL_MALLOC(attribute->count*4*sizeof(unsigned short));
LOAD_ATTRIBUTE(attribute, 4, unsigned short, temp);
for (unsigned int c = 0; c < attribute->count*4; c++) model.meshes[meshIndex].colors[c] = (unsigned char)(((float)temp[c]/65535.0f)*255.0f);
RL_FREE(temp);
}
else if (attribute->component_type == cgltf_component_type_r_32f)
{
model.meshes[meshIndex].colors = (unsigned char *)RL_MALLOC(attribute->count*4*sizeof(unsigned char));
float *temp = (float *)RL_MALLOC(attribute->count*4*sizeof(float));
LOAD_ATTRIBUTE(attribute, 4, float, temp);
for (unsigned int c = 0; c < attribute->count*4; c++) model.meshes[meshIndex].colors[c] = (unsigned char)(temp[c]*255.0f);
RL_FREE(temp);
}
else TRACELOG(LOG_WARNING, "MODEL: [%s] Color attribute data format not supported", fileName);
}
else TRACELOG(LOG_WARNING, "MODEL: [%s] Color attribute data format not supported", fileName);
}
}
}
if ((mesh->primitives[p].indices != NULL) && (mesh->primitives[p].indices->buffer_view != NULL))
{
cgltf_accessor *attribute = mesh->primitives[p].indices;
model.meshes[meshIndex].triangleCount = (int)attribute->count/3;
if (model.meshes[meshIndex].indices != NULL) TRACELOG(LOG_WARNING, "MODEL: [%s] Indices attribute data already loaded", fileName);
else
{
if (attribute->component_type == cgltf_component_type_r_16u)
{
model.meshes[meshIndex].indices = (unsigned short *)RL_MALLOC(attribute->count*sizeof(unsigned short));
LOAD_ATTRIBUTE(attribute, 1, unsigned short, model.meshes[meshIndex].indices)
}
else if (attribute->component_type == cgltf_component_type_r_8u)
{
model.meshes[meshIndex].indices = (unsigned short *)RL_MALLOC(attribute->count*sizeof(unsigned short));
LOAD_ATTRIBUTE_CAST(attribute, 1, unsigned char, model.meshes[meshIndex].indices, unsigned short)
}
else if (attribute->component_type == cgltf_component_type_r_32u)
{
model.meshes[meshIndex].indices = (unsigned short *)RL_MALLOC(attribute->count*sizeof(unsigned short));
LOAD_ATTRIBUTE_CAST(attribute, 1, unsigned int, model.meshes[meshIndex].indices, unsigned short);
TRACELOG(LOG_WARNING, "MODEL: [%s] Indices data converted from u32 to u16, possible loss of data", fileName);
}
else TRACELOG(LOG_WARNING, "MODEL: [%s] Indices data format not supported, use u16", fileName);
}
}
else model.meshes[meshIndex].triangleCount = model.meshes[meshIndex].vertexCount/3;
for (unsigned int m = 0; m < data->materials_count; m++)
{
if (&data->materials[m] == mesh->primitives[p].material)
{
model.meshMaterial[meshIndex] = m + 1;
break;
}
}
meshIndex++; }
}
if (data->skins_count > 0)
{
cgltf_skin skin = data->skins[0];
model.skeleton.bones = LoadBoneInfoGLTF(skin, &model.skeleton.boneCount);
model.skeleton.bindPose = (Transform *)RL_CALLOC(model.skeleton.boneCount, sizeof(Transform));
for (int i = 0; i < model.skeleton.boneCount; i++)
{
cgltf_node *node = skin.joints[i];
cgltf_float worldTransform[16];
cgltf_node_transform_world(node, worldTransform);
Matrix worldMatrix = {
worldTransform[0], worldTransform[4], worldTransform[8], worldTransform[12],
worldTransform[1], worldTransform[5], worldTransform[9], worldTransform[13],
worldTransform[2], worldTransform[6], worldTransform[10], worldTransform[14],
worldTransform[3], worldTransform[7], worldTransform[11], worldTransform[15]
};
MatrixDecompose(worldMatrix,
&(model.skeleton.bindPose[i].translation),
&(model.skeleton.bindPose[i].rotation),
&(model.skeleton.bindPose[i].scale));
}
if (data->skins_count > 1) TRACELOG(LOG_WARNING, "MODEL: [%s] can only load one skin (armature) per model, but gltf skins_count == %i", fileName, data->skins_count);
}
meshIndex = 0;
for (unsigned int i = 0; i < data->nodes_count; i++)
{
cgltf_node *node = &(data->nodes[i]);
cgltf_mesh *mesh = node->mesh;
if (!mesh) continue;
for (unsigned int p = 0; p < mesh->primitives_count; p++)
{
bool hasJoints = false;
if (mesh->primitives[p].type != cgltf_primitive_type_triangles) continue;
for (unsigned int j = 0; j < mesh->primitives[p].attributes_count; j++)
{
if (mesh->primitives[p].attributes[j].type == cgltf_attribute_type_joints) {
hasJoints = true;
cgltf_accessor *attribute = mesh->primitives[p].attributes[j].data;
if (attribute->type == cgltf_type_vec4)
{
if (attribute->component_type == cgltf_component_type_r_8u)
{
model.meshes[meshIndex].boneIndices = (unsigned char *)RL_CALLOC(model.meshes[meshIndex].vertexCount*4, sizeof(unsigned char));
LOAD_ATTRIBUTE(attribute, 4, unsigned char, model.meshes[meshIndex].boneIndices)
}
else if (attribute->component_type == cgltf_component_type_r_16u)
{
model.meshes[meshIndex].boneIndices = (unsigned char *)RL_CALLOC(model.meshes[meshIndex].vertexCount*4, sizeof(unsigned char));
unsigned short *temp = (unsigned short *)RL_CALLOC(model.meshes[meshIndex].vertexCount*4, sizeof(unsigned short));
LOAD_ATTRIBUTE(attribute, 4, unsigned short, temp);
bool boneIdOverflowWarning = false;
for (int b = 0; b < model.meshes[meshIndex].vertexCount*4; b++)
{
if ((temp[b] > 255) && !boneIdOverflowWarning)
{
TRACELOG(LOG_WARNING, "MODEL: [%s] Joint attribute data format (u16) overflow", fileName);
boneIdOverflowWarning = true;
}
model.meshes[meshIndex].boneIndices[b] = (unsigned char)temp[b];
}
RL_FREE(temp);
}
else TRACELOG(LOG_WARNING, "MODEL: [%s] Joint attribute data format not supported", fileName);
}
else TRACELOG(LOG_WARNING, "MODEL: [%s] Joint attribute data format not supported", fileName);
}
else if (mesh->primitives[p].attributes[j].type == cgltf_attribute_type_weights) {
cgltf_accessor *attribute = mesh->primitives[p].attributes[j].data;
if (attribute->type == cgltf_type_vec4)
{
if (attribute->component_type == cgltf_component_type_r_8u)
{
model.meshes[meshIndex].boneWeights = (float *)RL_CALLOC(model.meshes[meshIndex].vertexCount*4, sizeof(float));
unsigned char *temp = (unsigned char *)RL_MALLOC(attribute->count*4*sizeof(unsigned char));
LOAD_ATTRIBUTE(attribute, 4, unsigned char, temp);
for (unsigned int b = 0; b < attribute->count*4; b++) model.meshes[meshIndex].boneWeights[b] = (float)temp[b]/255.0f;
RL_FREE(temp);
}
else if (attribute->component_type == cgltf_component_type_r_16u)
{
model.meshes[meshIndex].boneWeights = (float *)RL_CALLOC(model.meshes[meshIndex].vertexCount*4, sizeof(float));
unsigned short *temp = (unsigned short *)RL_MALLOC(attribute->count*4*sizeof(unsigned short));
LOAD_ATTRIBUTE(attribute, 4, unsigned short, temp);
for (unsigned int b = 0; b < attribute->count*4; b++) model.meshes[meshIndex].boneWeights[b] = (float)temp[b]/65535.0f;
RL_FREE(temp);
}
else if (attribute->component_type == cgltf_component_type_r_32f)
{
model.meshes[meshIndex].boneWeights = (float *)RL_CALLOC(model.meshes[meshIndex].vertexCount*4, sizeof(float));
LOAD_ATTRIBUTE(attribute, 4, float, model.meshes[meshIndex].boneWeights)
}
else TRACELOG(LOG_WARNING, "MODEL: [%s] Joint weight attribute data format not supported, use vec4 float", fileName);
}
else TRACELOG(LOG_WARNING, "MODEL: [%s] Joint weight attribute data format not supported, use vec4 float", fileName);
}
}
if ((data->skins_count > 0) && !hasJoints && (node->parent != NULL) && (node->parent->mesh == NULL))
{
int parentBoneId = -1;
for (int joint = 0; joint < model.skeleton.boneCount; joint++)
{
if (data->skins[0].joints[joint] == node->parent)
{
parentBoneId = joint;
break;
}
}
if (parentBoneId >= 0)
{
model.meshes[meshIndex].boneIndices = (unsigned char *)RL_CALLOC(model.meshes[meshIndex].vertexCount*4, sizeof(unsigned char));
model.meshes[meshIndex].boneWeights = (float *)RL_CALLOC(model.meshes[meshIndex].vertexCount*4, sizeof(float));
for (int vertexIndex = 0; vertexIndex < model.meshes[meshIndex].vertexCount*4; vertexIndex += 4)
{
model.meshes[meshIndex].boneIndices[vertexIndex] = (unsigned char)parentBoneId;
model.meshes[meshIndex].boneWeights[vertexIndex] = 1.0f;
}
}
}
#if !SUPPORT_GPU_SKINNING
model.meshes[meshIndex].animVertices = (float *)RL_CALLOC(model.meshes[meshIndex].vertexCount*3, sizeof(float));
memcpy(model.meshes[meshIndex].animVertices, model.meshes[meshIndex].vertices, model.meshes[meshIndex].vertexCount*3*sizeof(float));
model.meshes[meshIndex].animNormals = (float *)RL_CALLOC(model.meshes[meshIndex].vertexCount*3, sizeof(float));
if (model.meshes[meshIndex].normals != NULL) memcpy(model.meshes[meshIndex].animNormals, model.meshes[meshIndex].normals, model.meshes[meshIndex].vertexCount*3*sizeof(float));
#endif
model.meshes[meshIndex].boneCount = model.skeleton.boneCount;
meshIndex++; }
}
model.currentPose = (Transform *)RL_CALLOC(model.skeleton.boneCount, sizeof(Transform));
model.boneMatrices = (Matrix *)RL_CALLOC(model.skeleton.boneCount, sizeof(Matrix));
for (int j = 0; j < model.skeleton.boneCount; j++) model.boneMatrices[j] = MatrixIdentity();
if (model.skeleton.boneCount == 0)
{
RL_FREE(model.currentPose);
RL_FREE(model.boneMatrices);
model.currentPose = NULL;
model.boneMatrices = NULL;
}
cgltf_free(data);
}
else TRACELOG(LOG_WARNING, "MODEL: [%s] Failed to load glTF data", fileName);
UnloadFileData(fileData);
return model;
}
static bool GetPoseAtTimeGLTF(cgltf_interpolation_type interpolationType, cgltf_accessor *input, cgltf_accessor *output, float time, void *data)
{
if (interpolationType >= cgltf_interpolation_type_max_enum) return false;
float tstart = 0.0f;
float tend = 0.0f;
int keyframe = 0;
for (int i = 0; i < (int)input->count - 1; i++)
{
cgltf_bool r1 = cgltf_accessor_read_float(input, i, &tstart, 1);
if (!r1) return false;
cgltf_bool r2 = cgltf_accessor_read_float(input, i + 1, &tend, 1);
if (!r2) return false;
if ((tstart <= time) && (time < tend))
{
keyframe = i;
break;
}
}
if (FloatEquals(tend, tstart))
{
interpolationType = cgltf_interpolation_type_step;
}
float duration = fmaxf((tend - tstart), EPSILON);
float t = (time - tstart)/duration;
t = (t < 0.0f)? 0.0f : t;
t = (t > 1.0f)? 1.0f : t;
if (output->component_type != cgltf_component_type_r_32f) return false;
if (output->type == cgltf_type_vec3)
{
switch (interpolationType)
{
case cgltf_interpolation_type_step:
{
float tmp[3] = { 0.0f };
cgltf_accessor_read_float(output, keyframe, tmp, 3);
Vector3 v1 = {tmp[0], tmp[1], tmp[2]};
Vector3 *r = (Vector3 *)data;
*r = v1;
} break;
case cgltf_interpolation_type_linear:
{
float tmp[3] = { 0.0f };
cgltf_accessor_read_float(output, keyframe, tmp, 3);
Vector3 v1 = {tmp[0], tmp[1], tmp[2]};
cgltf_accessor_read_float(output, keyframe+1, tmp, 3);
Vector3 v2 = {tmp[0], tmp[1], tmp[2]};
Vector3 *r = (Vector3 *)data;
*r = Vector3Lerp(v1, v2, t);
} break;
case cgltf_interpolation_type_cubic_spline:
{
float tmp[3] = { 0.0f };
cgltf_accessor_read_float(output, 3*keyframe+1, tmp, 3);
Vector3 v1 = {tmp[0], tmp[1], tmp[2]};
cgltf_accessor_read_float(output, 3*keyframe+2, tmp, 3);
Vector3 tangent1 = {tmp[0], tmp[1], tmp[2]};
cgltf_accessor_read_float(output, 3*(keyframe+1)+1, tmp, 3);
Vector3 v2 = {tmp[0], tmp[1], tmp[2]};
cgltf_accessor_read_float(output, 3*(keyframe+1), tmp, 3);
Vector3 tangent2 = {tmp[0], tmp[1], tmp[2]};
Vector3 *r = (Vector3 *)data;
*r = Vector3CubicHermite(v1, tangent1, v2, tangent2, t);
} break;
default: break;
}
}
else if (output->type == cgltf_type_vec4)
{
switch (interpolationType)
{
case cgltf_interpolation_type_step:
{
float tmp[4] = { 0.0f };
cgltf_accessor_read_float(output, keyframe, tmp, 4);
Vector4 v1 = {tmp[0], tmp[1], tmp[2], tmp[3]};
Vector4 *r = (Vector4 *)data;
*r = v1;
} break;
case cgltf_interpolation_type_linear:
{
float tmp[4] = { 0.0f };
cgltf_accessor_read_float(output, keyframe, tmp, 4);
Vector4 v1 = {tmp[0], tmp[1], tmp[2], tmp[3]};
cgltf_accessor_read_float(output, keyframe+1, tmp, 4);
Vector4 v2 = {tmp[0], tmp[1], tmp[2], tmp[3]};
Vector4 *r = (Vector4 *)data;
*r = QuaternionSlerp(v1, v2, t);
} break;
case cgltf_interpolation_type_cubic_spline:
{
float tmp[4] = { 0.0f };
cgltf_accessor_read_float(output, 3*keyframe+1, tmp, 4);
Vector4 v1 = {tmp[0], tmp[1], tmp[2], tmp[3]};
cgltf_accessor_read_float(output, 3*keyframe+2, tmp, 4);
Vector4 outTangent1 = {tmp[0], tmp[1], tmp[2], 0.0f};
cgltf_accessor_read_float(output, 3*(keyframe+1)+1, tmp, 4);
Vector4 v2 = {tmp[0], tmp[1], tmp[2], tmp[3]};
cgltf_accessor_read_float(output, 3*(keyframe+1), tmp, 4);
Vector4 inTangent2 = {tmp[0], tmp[1], tmp[2], 0.0f};
Vector4 *r = (Vector4 *)data;
v1 = QuaternionNormalize(v1);
v2 = QuaternionNormalize(v2);
if (Vector4DotProduct(v1, v2) < 0.0f)
{
v2 = Vector4Negate(v2);
}
outTangent1 = Vector4Scale(outTangent1, duration);
inTangent2 = Vector4Scale(inTangent2, duration);
*r = QuaternionCubicHermiteSpline(v1, outTangent1, v2, inTangent2, t);
} break;
default: break;
}
}
return true;
}
#define GLTF_FRAMERATE 60.0f
static ModelAnimation *LoadModelAnimationsGLTF(const char *fileName, int *animCount)
{
int dataSize = 0;
unsigned char *fileData = LoadFileData(fileName, &dataSize);
ModelAnimation *animations = NULL;
cgltf_options options = { 0 };
options.file.read = LoadFileGLTFCallback;
options.file.release = ReleaseFileGLTFCallback;
cgltf_data *data = NULL;
cgltf_result result = cgltf_parse(&options, fileData, dataSize, &data);
if (result != cgltf_result_success)
{
TRACELOG(LOG_WARNING, "MODEL: [%s] Failed to load glTF data", fileName);
*animCount = 0;
return NULL;
}
result = cgltf_load_buffers(&options, data, fileName);
if (result != cgltf_result_success) TRACELOG(LOG_INFO, "MODEL: [%s] Failed to load animation buffers", fileName);
if (result == cgltf_result_success)
{
if (data->skins_count > 0)
{
cgltf_skin skin = data->skins[0];
*animCount = (int)data->animations_count;
animations = (ModelAnimation *)RL_CALLOC(data->animations_count, sizeof(ModelAnimation));
Transform worldTransform = { 0 };
cgltf_float cgltf_worldTransform[16] = { 0 };
cgltf_node *node = skin.joints[0];
cgltf_node_transform_world(node->parent, cgltf_worldTransform);
Matrix worldMatrix = {
cgltf_worldTransform[0], cgltf_worldTransform[4], cgltf_worldTransform[8], cgltf_worldTransform[12],
cgltf_worldTransform[1], cgltf_worldTransform[5], cgltf_worldTransform[9], cgltf_worldTransform[13],
cgltf_worldTransform[2], cgltf_worldTransform[6], cgltf_worldTransform[10], cgltf_worldTransform[14],
cgltf_worldTransform[3], cgltf_worldTransform[7], cgltf_worldTransform[11], cgltf_worldTransform[15]
};
MatrixDecompose(worldMatrix, &worldTransform.translation, &worldTransform.rotation, &worldTransform.scale);
for (unsigned int a = 0; a < data->animations_count; a++)
{
BoneInfo *bones = LoadBoneInfoGLTF(skin, &animations[a].boneCount);
cgltf_animation animData = data->animations[a];
struct Channels {
cgltf_animation_channel *translate;
cgltf_animation_channel *rotate;
cgltf_animation_channel *scale;
cgltf_interpolation_type interpolationType;
};
struct Channels *boneChannels = (struct Channels *)RL_CALLOC(animations[a].boneCount, sizeof(struct Channels));
float animDuration = 0.0f;
for (unsigned int j = 0; j < animData.channels_count; j++)
{
cgltf_animation_channel channel = animData.channels[j];
int boneIndex = -1;
for (unsigned int k = 0; k < skin.joints_count; k++)
{
if (animData.channels[j].target_node == skin.joints[k])
{
boneIndex = k;
break;
}
}
if (boneIndex == -1) continue;
boneChannels[boneIndex].interpolationType = animData.channels[j].sampler->interpolation;
if (animData.channels[j].sampler->interpolation != cgltf_interpolation_type_max_enum)
{
if (channel.target_path == cgltf_animation_path_type_translation)
{
boneChannels[boneIndex].translate = &animData.channels[j];
}
else if (channel.target_path == cgltf_animation_path_type_rotation)
{
boneChannels[boneIndex].rotate = &animData.channels[j];
}
else if (channel.target_path == cgltf_animation_path_type_scale)
{
boneChannels[boneIndex].scale = &animData.channels[j];
}
else
{
TRACELOG(LOG_WARNING, "MODEL: [%s] Unsupported target_path on channel %d's sampler for animation %d. Skipping.", fileName, j, a);
}
}
else TRACELOG(LOG_WARNING, "MODEL: [%s] Invalid interpolation curve encountered for GLTF animation.", fileName);
float time = 0.0f;
cgltf_bool result = cgltf_accessor_read_float(channel.sampler->input, channel.sampler->input->count - 1, &time, 1);
if (!result)
{
TRACELOG(LOG_WARNING, "MODEL: [%s] Failed to load input time", fileName);
continue;
}
animDuration = (time > animDuration)? time : animDuration;
}
if (animData.name != NULL) strncpy(animations[a].name, animData.name, sizeof(animations[a].name) - 1);
animations[a].keyframeCount = (int)(animDuration*GLTF_FRAMERATE) + 1;
animations[a].keyframePoses = (Transform **)RL_CALLOC(animations[a].keyframeCount, sizeof(Transform *));
for (int j = 0; j < animations[a].keyframeCount; j++)
{
animations[a].keyframePoses[j] = (Transform *)RL_CALLOC(animations[a].boneCount, sizeof(Transform));
float time = (float)j / GLTF_FRAMERATE;
for (int k = 0; k < animations[a].boneCount; k++)
{
Vector3 translation = {skin.joints[k]->translation[0], skin.joints[k]->translation[1], skin.joints[k]->translation[2]};
Quaternion rotation = {skin.joints[k]->rotation[0], skin.joints[k]->rotation[1], skin.joints[k]->rotation[2], skin.joints[k]->rotation[3]};
Vector3 scale = {skin.joints[k]->scale[0], skin.joints[k]->scale[1], skin.joints[k]->scale[2]};
if (boneChannels[k].translate)
{
if (!GetPoseAtTimeGLTF(boneChannels[k].interpolationType, boneChannels[k].translate->sampler->input, boneChannels[k].translate->sampler->output, time, &translation))
{
TRACELOG(LOG_INFO, "MODEL: [%s] Failed to load translate pose data for bone %s", fileName, bones[k].name);
}
}
if (boneChannels[k].rotate)
{
if (!GetPoseAtTimeGLTF(boneChannels[k].interpolationType, boneChannels[k].rotate->sampler->input, boneChannels[k].rotate->sampler->output, time, &rotation))
{
TRACELOG(LOG_INFO, "MODEL: [%s] Failed to load rotate pose data for bone %s", fileName, bones[k].name);
}
}
if (boneChannels[k].scale)
{
if (!GetPoseAtTimeGLTF(boneChannels[k].interpolationType, boneChannels[k].scale->sampler->input, boneChannels[k].scale->sampler->output, time, &scale))
{
TRACELOG(LOG_INFO, "MODEL: [%s] Failed to load scale pose data for bone %s", fileName, bones[k].name);
}
}
animations[a].keyframePoses[j][k] = (Transform){
.translation = translation,
.rotation = rotation,
.scale = scale
};
}
Transform *root = &animations[a].keyframePoses[j][0];
root->rotation = QuaternionMultiply(worldTransform.rotation, root->rotation);
root->scale = Vector3Multiply(root->scale, worldTransform.scale);
root->translation = Vector3Multiply(root->translation, worldTransform.scale);
root->translation = Vector3RotateByQuaternion(root->translation, worldTransform.rotation);
root->translation = Vector3Add(root->translation, worldTransform.translation);
BuildPoseFromParentJoints(bones, animations[a].boneCount, animations[a].keyframePoses[j]);
}
TRACELOG(LOG_INFO, "MODEL: [%s] Loaded animation: %s | Frames: %d | Duration: %fs", fileName, (animData.name != NULL)? animData.name : "NULL", animations[a].keyframeCount, animDuration);
RL_FREE(boneChannels);
RL_FREE(bones);
}
}
if (data->skins_count > 1)
{
TRACELOG(LOG_WARNING, "MODEL: [%s] Expected one unique skin to load animation data from, but found %i", fileName, data->skins_count);
}
cgltf_free(data);
}
UnloadFileData(fileData);
return animations;
}
#endif
#if SUPPORT_FILEFORMAT_VOX
static Model LoadVOX(const char *fileName)
{
Model model = { 0 };
int nbvertices = 0;
int meshescount = 0;
int dataSize = 0;
unsigned char *fileData = LoadFileData(fileName, &dataSize);
if (fileData == 0)
{
TRACELOG(LOG_WARNING, "MODEL: [%s] Failed to load VOX file", fileName);
return model;
}
VoxArray3D voxarray = { 0 };
int ret = Vox_LoadFromMemory(fileData, dataSize, &voxarray);
if (ret != VOX_SUCCESS)
{
UnloadFileData(fileData);
TRACELOG(LOG_WARNING, "MODEL: [%s] Failed to load VOX data", fileName);
return model;
}
else
{
nbvertices = voxarray.vertices.used;
meshescount = 1 + (nbvertices/65536);
TRACELOG(LOG_INFO, "MODEL: [%s] VOX data loaded successfully : %i vertices/%i meshes", fileName, nbvertices, meshescount);
}
model.transform = MatrixIdentity();
model.meshCount = meshescount;
model.meshes = (Mesh *)RL_CALLOC(model.meshCount, sizeof(Mesh));
model.meshMaterial = (int *)RL_CALLOC(model.meshCount, sizeof(int));
model.materialCount = 1;
model.materials = (Material *)RL_CALLOC(model.materialCount, sizeof(Material));
model.materials[0] = LoadMaterialDefault();
int verticesRemain = voxarray.vertices.used;
int verticesMax = 65532;
Vector3 *pvertices = (Vector3 *)voxarray.vertices.array;
Vector3 *pnormals = (Vector3 *)voxarray.normals.array;
Color *pcolors = (Color *)voxarray.colors.array;
unsigned short *pindices = voxarray.indices.array;
int size = 0;
for (int i = 0; i < meshescount; i++)
{
Mesh *pmesh = &model.meshes[i];
memset(pmesh, 0, sizeof(Mesh));
pmesh->vertexCount = (int)fmin(verticesMax, verticesRemain);
size = pmesh->vertexCount*sizeof(float)*3;
pmesh->vertices = (float *)RL_MALLOC(size);
memcpy(pmesh->vertices, pvertices, size);
pmesh->normals = (float *)RL_MALLOC(size);
memcpy(pmesh->normals, pnormals, size);
size = voxarray.indices.used*sizeof(unsigned short);
pmesh->indices = (unsigned short *)RL_MALLOC(size);
memcpy(pmesh->indices, pindices, size);
pmesh->triangleCount = (pmesh->vertexCount/4)*2;
size = pmesh->vertexCount*sizeof(Color);
pmesh->colors = (unsigned char *)RL_MALLOC(size);
memcpy(pmesh->colors, pcolors, size);
model.meshMaterial[i] = 0;
verticesRemain -= verticesMax;
pvertices += verticesMax;
pnormals += verticesMax;
pcolors += verticesMax;
}
Vox_FreeArrays(&voxarray);
UnloadFileData(fileData);
return model;
}
#endif
#if SUPPORT_FILEFORMAT_M3D
unsigned char *m3d_loaderhook(char *fn, unsigned int *len) { return LoadFileData((const char *)fn, (int *)len); }
void m3d_freehook(void *data) { UnloadFileData((unsigned char *)data); }
static Model LoadM3D(const char *fileName)
{
Model model = { 0 };
m3d_t *m3d = NULL;
m3dp_t *prop = NULL;
int i, j, k, l, n, mi = -2, vcolor = 0;
int dataSize = 0;
unsigned char *fileData = LoadFileData(fileName, &dataSize);
if (fileData != NULL)
{
m3d = m3d_load(fileData, m3d_loaderhook, m3d_freehook, NULL);
if (!m3d || M3D_ERR_ISFATAL(m3d->errcode))
{
TRACELOG(LOG_WARNING, "MODEL: [%s] Failed to load M3D data, error code %d", fileName, m3d? m3d->errcode : -2);
if (m3d) m3d_free(m3d);
UnloadFileData(fileData);
return model;
}
else TRACELOG(LOG_INFO, "MODEL: [%s] M3D data loaded successfully: %i faces/%i materials", fileName, m3d->numface, m3d->nummaterial);
if (!m3d->numface)
{
m3d_free(m3d);
UnloadFileData(fileData);
return model;
}
if (m3d->nummaterial > 0)
{
model.meshCount = model.materialCount = m3d->nummaterial;
TRACELOG(LOG_INFO, "MODEL: model has %i material meshes", model.materialCount);
}
else
{
model.meshCount = 1; model.materialCount = 0;
TRACELOG(LOG_INFO, "MODEL: No materials, putting all meshes in a default material");
}
model.materialCount++;
model.meshes = (Mesh *)RL_CALLOC(model.meshCount, sizeof(Mesh));
model.meshMaterial = (int *)RL_CALLOC(model.meshCount, sizeof(int));
model.materials = (Material *)RL_CALLOC(model.materialCount + 1, sizeof(Material));
model.materials[0] = LoadMaterialDefault();
for (i = l = 0, k = -1; i < (int)m3d->numface; i++, l++)
{
if (mi != m3d->face[i].materialid)
{
if (k + 1 >= model.meshCount)
{
model.meshCount++;
Mesh *tempMeshes = (Mesh *)RL_CALLOC(model.meshCount, sizeof(Mesh));
memcpy(tempMeshes, model.meshes, (model.meshCount - 1)*sizeof(Mesh));
RL_FREE(model.meshes);
model.meshes = tempMeshes;
int *tempMeshMaterial = (int *)RL_CALLOC(model.meshCount, sizeof(int));
memcpy(tempMeshMaterial, model.meshMaterial, (model.meshCount - 1)*sizeof(int));
RL_FREE(model.meshMaterial);
model.meshMaterial = tempMeshMaterial;
}
k++;
mi = m3d->face[i].materialid;
for (j = i, l = vcolor = 0; (j < (int)m3d->numface) && (mi == m3d->face[j].materialid); j++, l++)
{
if (!m3d->vertex[m3d->face[j].vertex[0]].color ||
!m3d->vertex[m3d->face[j].vertex[1]].color ||
!m3d->vertex[m3d->face[j].vertex[2]].color) vcolor = 1;
}
model.meshes[k].vertexCount = l*3;
model.meshes[k].triangleCount = l;
model.meshes[k].vertices = (float *)RL_CALLOC(model.meshes[k].vertexCount*3, sizeof(float));
model.meshes[k].texcoords = (float *)RL_CALLOC(model.meshes[k].vertexCount*2, sizeof(float));
model.meshes[k].normals = (float *)RL_CALLOC(model.meshes[k].vertexCount*3, sizeof(float));
if ((mi == M3D_UNDEF) || vcolor) model.meshes[k].colors = (unsigned char *)RL_CALLOC(model.meshes[k].vertexCount*4, sizeof(unsigned char));
if ((mi == M3D_UNDEF) && (model.meshes[k].colors != NULL))
{
for (int c = 0; c < model.meshes[k].vertexCount*4; c++) model.meshes[k].colors[c] = 255;
}
if (m3d->numbone && m3d->numskin)
{
model.meshes[k].boneIndices = (unsigned char *)RL_CALLOC(model.meshes[k].vertexCount*4, sizeof(unsigned char));
model.meshes[k].boneWeights = (float *)RL_CALLOC(model.meshes[k].vertexCount*4, sizeof(float));
#if !SUPPORT_GPU_SKINNING
model.meshes[k].animVertices = (float *)RL_CALLOC(model.meshes[k].vertexCount*3, sizeof(float));
model.meshes[k].animNormals = (float *)RL_CALLOC(model.meshes[k].vertexCount*3, sizeof(float));
#endif
}
model.meshMaterial[k] = mi + 1;
l = 0;
}
model.meshes[k].vertices[l*9 + 0] = m3d->vertex[m3d->face[i].vertex[0]].x*m3d->scale;
model.meshes[k].vertices[l*9 + 1] = m3d->vertex[m3d->face[i].vertex[0]].y*m3d->scale;
model.meshes[k].vertices[l*9 + 2] = m3d->vertex[m3d->face[i].vertex[0]].z*m3d->scale;
model.meshes[k].vertices[l*9 + 3] = m3d->vertex[m3d->face[i].vertex[1]].x*m3d->scale;
model.meshes[k].vertices[l*9 + 4] = m3d->vertex[m3d->face[i].vertex[1]].y*m3d->scale;
model.meshes[k].vertices[l*9 + 5] = m3d->vertex[m3d->face[i].vertex[1]].z*m3d->scale;
model.meshes[k].vertices[l*9 + 6] = m3d->vertex[m3d->face[i].vertex[2]].x*m3d->scale;
model.meshes[k].vertices[l*9 + 7] = m3d->vertex[m3d->face[i].vertex[2]].y*m3d->scale;
model.meshes[k].vertices[l*9 + 8] = m3d->vertex[m3d->face[i].vertex[2]].z*m3d->scale;
if (model.meshes[k].colors != NULL)
{
if (m3d->vertex[m3d->face[i].vertex[0]].color & 0xff000000)
memcpy(&model.meshes[k].colors[l*12 + 0], &m3d->vertex[m3d->face[i].vertex[0]].color, 4);
if (m3d->vertex[m3d->face[i].vertex[1]].color & 0xff000000)
memcpy(&model.meshes[k].colors[l*12 + 4], &m3d->vertex[m3d->face[i].vertex[1]].color, 4);
if (m3d->vertex[m3d->face[i].vertex[2]].color & 0xff000000)
memcpy(&model.meshes[k].colors[l*12 + 8], &m3d->vertex[m3d->face[i].vertex[2]].color, 4);
}
if (m3d->face[i].texcoord[0] != M3D_UNDEF)
{
model.meshes[k].texcoords[l*6 + 0] = m3d->tmap[m3d->face[i].texcoord[0]].u;
model.meshes[k].texcoords[l*6 + 1] = 1.0f - m3d->tmap[m3d->face[i].texcoord[0]].v;
model.meshes[k].texcoords[l*6 + 2] = m3d->tmap[m3d->face[i].texcoord[1]].u;
model.meshes[k].texcoords[l*6 + 3] = 1.0f - m3d->tmap[m3d->face[i].texcoord[1]].v;
model.meshes[k].texcoords[l*6 + 4] = m3d->tmap[m3d->face[i].texcoord[2]].u;
model.meshes[k].texcoords[l*6 + 5] = 1.0f - m3d->tmap[m3d->face[i].texcoord[2]].v;
}
if (m3d->face[i].normal[0] != M3D_UNDEF)
{
model.meshes[k].normals[l*9 + 0] = m3d->vertex[m3d->face[i].normal[0]].x;
model.meshes[k].normals[l*9 + 1] = m3d->vertex[m3d->face[i].normal[0]].y;
model.meshes[k].normals[l*9 + 2] = m3d->vertex[m3d->face[i].normal[0]].z;
model.meshes[k].normals[l*9 + 3] = m3d->vertex[m3d->face[i].normal[1]].x;
model.meshes[k].normals[l*9 + 4] = m3d->vertex[m3d->face[i].normal[1]].y;
model.meshes[k].normals[l*9 + 5] = m3d->vertex[m3d->face[i].normal[1]].z;
model.meshes[k].normals[l*9 + 6] = m3d->vertex[m3d->face[i].normal[2]].x;
model.meshes[k].normals[l*9 + 7] = m3d->vertex[m3d->face[i].normal[2]].y;
model.meshes[k].normals[l*9 + 8] = m3d->vertex[m3d->face[i].normal[2]].z;
}
if (m3d->numbone && m3d->numskin)
{
for (n = 0; n < 3; n++)
{
int skinid = m3d->vertex[m3d->face[i].vertex[n]].skinid;
if ((skinid != M3D_UNDEF) && (skinid < (int)m3d->numskin))
{
for (j = 0; j < 4; j++)
{
model.meshes[k].boneIndices[l*12 + n*4 + j] = m3d->skin[skinid].boneid[j];
model.meshes[k].boneWeights[l*12 + n*4 + j] = m3d->skin[skinid].weight[j];
}
}
else
{
model.meshes[k].boneIndices[l*12 + n*4] = m3d->numbone;
model.meshes[k].boneWeights[l*12 + n*4] = 1.0f;
}
}
}
}
for (i = 0; i < (int)m3d->nummaterial; i++)
{
model.materials[i + 1] = LoadMaterialDefault();
for (j = 0; j < m3d->material[i].numprop; j++)
{
prop = &m3d->material[i].prop[j];
switch (prop->type)
{
case m3dp_Kd:
{
memcpy(&model.materials[i + 1].maps[MATERIAL_MAP_DIFFUSE].color, &prop->value.color, 4);
model.materials[i + 1].maps[MATERIAL_MAP_DIFFUSE].value = 0.0f;
} break;
case m3dp_Ks:
{
memcpy(&model.materials[i + 1].maps[MATERIAL_MAP_SPECULAR].color, &prop->value.color, 4);
} break;
case m3dp_Ns:
{
model.materials[i + 1].maps[MATERIAL_MAP_SPECULAR].value = prop->value.fnum;
} break;
case m3dp_Ke:
{
memcpy(&model.materials[i + 1].maps[MATERIAL_MAP_EMISSION].color, &prop->value.color, 4);
model.materials[i + 1].maps[MATERIAL_MAP_EMISSION].value = 0.0f;
} break;
case m3dp_Pm:
{
model.materials[i + 1].maps[MATERIAL_MAP_METALNESS].value = prop->value.fnum;
} break;
case m3dp_Pr:
{
model.materials[i + 1].maps[MATERIAL_MAP_ROUGHNESS].value = prop->value.fnum;
} break;
case m3dp_Ps:
{
model.materials[i + 1].maps[MATERIAL_MAP_NORMAL].color = WHITE;
model.materials[i + 1].maps[MATERIAL_MAP_NORMAL].value = prop->value.fnum;
} break;
default:
{
if (prop->type >= 128)
{
Image image = { 0 };
image.data = m3d->texture[prop->value.textureid].d;
image.width = m3d->texture[prop->value.textureid].w;
image.height = m3d->texture[prop->value.textureid].h;
image.mipmaps = 1;
image.format = (m3d->texture[prop->value.textureid].f == 4)? PIXELFORMAT_UNCOMPRESSED_R8G8B8A8 :
((m3d->texture[prop->value.textureid].f == 3)? PIXELFORMAT_UNCOMPRESSED_R8G8B8 :
((m3d->texture[prop->value.textureid].f == 2)? PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA : PIXELFORMAT_UNCOMPRESSED_GRAYSCALE));
switch (prop->type)
{
case m3dp_map_Kd: model.materials[i + 1].maps[MATERIAL_MAP_DIFFUSE].texture = LoadTextureFromImage(image); break;
case m3dp_map_Ks: model.materials[i + 1].maps[MATERIAL_MAP_SPECULAR].texture = LoadTextureFromImage(image); break;
case m3dp_map_Ke: model.materials[i + 1].maps[MATERIAL_MAP_EMISSION].texture = LoadTextureFromImage(image); break;
case m3dp_map_Km: model.materials[i + 1].maps[MATERIAL_MAP_NORMAL].texture = LoadTextureFromImage(image); break;
case m3dp_map_Ka: model.materials[i + 1].maps[MATERIAL_MAP_OCCLUSION].texture = LoadTextureFromImage(image); break;
case m3dp_map_Pm: model.materials[i + 1].maps[MATERIAL_MAP_ROUGHNESS].texture = LoadTextureFromImage(image); break;
default: break;
}
}
} break;
}
}
}
if (m3d->numbone)
{
model.skeleton.boneCount = m3d->numbone + 1;
model.skeleton.bones = (BoneInfo *)RL_CALLOC(model.skeleton.boneCount, sizeof(BoneInfo));
model.skeleton.bindPose = (Transform *)RL_CALLOC(model.skeleton.boneCount, sizeof(Transform));
for (i = 0; i < (int)m3d->numbone; i++)
{
model.skeleton.bones[i].parent = m3d->bone[i].parent;
strncpy(model.skeleton.bones[i].name, m3d->bone[i].name, sizeof(model.skeleton.bones[i].name) - 1);
model.skeleton.bindPose[i].translation.x = m3d->vertex[m3d->bone[i].pos].x*m3d->scale;
model.skeleton.bindPose[i].translation.y = m3d->vertex[m3d->bone[i].pos].y*m3d->scale;
model.skeleton.bindPose[i].translation.z = m3d->vertex[m3d->bone[i].pos].z*m3d->scale;
model.skeleton.bindPose[i].rotation.x = m3d->vertex[m3d->bone[i].ori].x;
model.skeleton.bindPose[i].rotation.y = m3d->vertex[m3d->bone[i].ori].y;
model.skeleton.bindPose[i].rotation.z = m3d->vertex[m3d->bone[i].ori].z;
model.skeleton.bindPose[i].rotation.w = m3d->vertex[m3d->bone[i].ori].w;
model.skeleton.bindPose[i].rotation = QuaternionNormalize(model.skeleton.bindPose[i].rotation);
model.skeleton.bindPose[i].scale.x = model.skeleton.bindPose[i].scale.y = model.skeleton.bindPose[i].scale.z = 1.0f;
if (model.skeleton.bones[i].parent >= 0)
{
model.skeleton.bindPose[i].rotation = QuaternionMultiply(model.skeleton.bindPose[model.skeleton.bones[i].parent].rotation, model.skeleton.bindPose[i].rotation);
model.skeleton.bindPose[i].translation = Vector3RotateByQuaternion(model.skeleton.bindPose[i].translation, model.skeleton.bindPose[model.skeleton.bones[i].parent].rotation);
model.skeleton.bindPose[i].translation = Vector3Add(model.skeleton.bindPose[i].translation, model.skeleton.bindPose[model.skeleton.bones[i].parent].translation);
model.skeleton.bindPose[i].scale = Vector3Multiply(model.skeleton.bindPose[i].scale, model.skeleton.bindPose[model.skeleton.bones[i].parent].scale);
}
}
model.skeleton.bones[i].parent = -1;
memcpy(model.skeleton.bones[i].name, "NO BONE", 7);
model.skeleton.bindPose[i].translation.x = 0.0f;
model.skeleton.bindPose[i].translation.y = 0.0f;
model.skeleton.bindPose[i].translation.z = 0.0f;
model.skeleton.bindPose[i].rotation.x = 0.0f;
model.skeleton.bindPose[i].rotation.y = 0.0f;
model.skeleton.bindPose[i].rotation.z = 0.0f;
model.skeleton.bindPose[i].rotation.w = 1.0f;
model.skeleton.bindPose[i].scale.x = model.skeleton.bindPose[i].scale.y = model.skeleton.bindPose[i].scale.z = 1.0f;
}
if (m3d->numbone && m3d->numskin)
{
for (i = 0; i < model.meshCount; i++)
{
model.meshes[i].boneCount = model.skeleton.boneCount;
#if !SUPPORT_GPU_SKINNING
memcpy(model.meshes[i].animVertices, model.meshes[i].vertices, model.meshes[i].vertexCount*3*sizeof(float));
memcpy(model.meshes[i].animNormals, model.meshes[i].normals, model.meshes[i].vertexCount*3*sizeof(float));
#endif
}
model.currentPose = (Transform *)RL_CALLOC(model.skeleton.boneCount, sizeof(Transform));
model.boneMatrices = (Matrix *)RL_CALLOC(model.skeleton.boneCount, sizeof(Matrix));
for (int j = 0; j < model.skeleton.boneCount; j++) model.boneMatrices[j] = MatrixIdentity();
}
m3d_free(m3d);
UnloadFileData(fileData);
}
return model;
}
#define M3D_ANIMDELAY 17
static ModelAnimation *LoadModelAnimationsM3D(const char *fileName, int *animCount)
{
ModelAnimation *animations = NULL;
m3d_t *m3d = NULL;
int i = 0, j = 0;
*animCount = 0;
int dataSize = 0;
unsigned char *fileData = LoadFileData(fileName, &dataSize);
if (fileData != NULL)
{
m3d = m3d_load(fileData, m3d_loaderhook, m3d_freehook, NULL);
if (!m3d || M3D_ERR_ISFATAL(m3d->errcode))
{
TRACELOG(LOG_WARNING, "MODEL: [%s] Failed to load M3D data, error code %d", fileName, m3d? m3d->errcode : -2);
UnloadFileData(fileData);
return NULL;
}
else TRACELOG(LOG_INFO, "MODEL: [%s] M3D data loaded successfully: %i animations, %i bones, %i skins", fileName, m3d->numaction, m3d->numbone, m3d->numskin);
if (!m3d->numaction || !m3d->numbone)
{
m3d_free(m3d);
UnloadFileData(fileData);
return NULL;
}
animations = (ModelAnimation *)RL_CALLOC(m3d->numaction, sizeof(ModelAnimation));
*animCount = m3d->numaction;
for (unsigned int a = 0; a < m3d->numaction; a++)
{
animations[a].boneCount = m3d->numbone + 1;
BoneInfo *bones = (BoneInfo *)RL_CALLOC((m3d->numbone + 1), sizeof(BoneInfo));
animations[a].keyframeCount = m3d->action[a].durationmsec/M3D_ANIMDELAY;
animations[a].keyframePoses = (Transform **)RL_CALLOC(animations[a].keyframeCount, sizeof(Transform *));
strncpy(animations[a].name, m3d->action[a].name, sizeof(animations[a].name) - 1);
TRACELOG(LOG_INFO, "MODEL: [%s] Loaded animation: %s | Frames: %d | Duration: %fs", fileName, animations[a].name, animations[a].keyframeCount, m3d->action[a].durationmsec);
for (i = 0; i < (int)m3d->numbone; i++)
{
bones[i].parent = m3d->bone[i].parent;
strncpy(bones[i].name, m3d->bone[i].name, sizeof(bones[i].name) - 1);
}
bones[i].parent = -1;
memcpy(bones[i].name, "NO BONE", 7);
for (i = 0; i < animations[a].keyframeCount; i++)
{
animations[a].keyframePoses[i] = (Transform *)RL_CALLOC((m3d->numbone + 1), sizeof(Transform));
m3db_t *pose = m3d_pose(m3d, a, i*M3D_ANIMDELAY);
if (pose != NULL)
{
for (j = 0; j < (int)m3d->numbone; j++)
{
animations[a].keyframePoses[i][j].translation.x = m3d->vertex[pose[j].pos].x*m3d->scale;
animations[a].keyframePoses[i][j].translation.y = m3d->vertex[pose[j].pos].y*m3d->scale;
animations[a].keyframePoses[i][j].translation.z = m3d->vertex[pose[j].pos].z*m3d->scale;
animations[a].keyframePoses[i][j].rotation.x = m3d->vertex[pose[j].ori].x;
animations[a].keyframePoses[i][j].rotation.y = m3d->vertex[pose[j].ori].y;
animations[a].keyframePoses[i][j].rotation.z = m3d->vertex[pose[j].ori].z;
animations[a].keyframePoses[i][j].rotation.w = m3d->vertex[pose[j].ori].w;
animations[a].keyframePoses[i][j].rotation = QuaternionNormalize(animations[a].keyframePoses[i][j].rotation);
animations[a].keyframePoses[i][j].scale.x = animations[a].keyframePoses[i][j].scale.y = animations[a].keyframePoses[i][j].scale.z = 1.0f;
if (bones[j].parent >= 0)
{
animations[a].keyframePoses[i][j].rotation = QuaternionMultiply(animations[a].keyframePoses[i][bones[j].parent].rotation, animations[a].keyframePoses[i][j].rotation);
animations[a].keyframePoses[i][j].translation = Vector3RotateByQuaternion(animations[a].keyframePoses[i][j].translation, animations[a].keyframePoses[i][bones[j].parent].rotation);
animations[a].keyframePoses[i][j].translation = Vector3Add(animations[a].keyframePoses[i][j].translation, animations[a].keyframePoses[i][bones[j].parent].translation);
animations[a].keyframePoses[i][j].scale = Vector3Multiply(animations[a].keyframePoses[i][j].scale, animations[a].keyframePoses[i][bones[j].parent].scale);
}
}
animations[a].keyframePoses[i][j].translation.x = 0.0f;
animations[a].keyframePoses[i][j].translation.y = 0.0f;
animations[a].keyframePoses[i][j].translation.z = 0.0f;
animations[a].keyframePoses[i][j].rotation.x = 0.0f;
animations[a].keyframePoses[i][j].rotation.y = 0.0f;
animations[a].keyframePoses[i][j].rotation.z = 0.0f;
animations[a].keyframePoses[i][j].rotation.w = 1.0f;
animations[a].keyframePoses[i][j].scale.x = 1.0f;
animations[a].keyframePoses[i][j].scale.y = 1.0f;
animations[a].keyframePoses[i][j].scale.z = 1.0f;
RL_FREE(pose);
}
}
RL_FREE(bones);
}
m3d_free(m3d);
UnloadFileData(fileData);
}
return animations;
}
#endif
#endif