BIG UPDATE: New models functions for animations!
Multiple functions added and some reviewed to adapt to the new multi-mesh, multi-material and animated models.
This commit is contained in:
parent
38a13b76d1
commit
92733d6695
8 changed files with 547 additions and 936 deletions
|
@ -1,19 +1,16 @@
|
||||||
/*******************************************************************************************
|
/*******************************************************************************************
|
||||||
*
|
*
|
||||||
* raylib [models] example - Load IQM 3d model with animations and play them
|
* raylib [models] example - Load 3d model with animations and play them
|
||||||
*
|
*
|
||||||
* This example has been created using raylib 2.0 (www.raylib.com)
|
* This example has been created using raylib 2.5 (www.raylib.com)
|
||||||
* raylib is licensed under an unmodified zlib/libpng license (View raylib.h for details)
|
* raylib is licensed under an unmodified zlib/libpng license (View raylib.h for details)
|
||||||
*
|
*
|
||||||
* Copyright (c) 2018 @culacant and @raysan5
|
* Copyright (c) 2019 Ramon Santamaria (@raysan5) and @culacant
|
||||||
*
|
*
|
||||||
********************************************************************************************/
|
********************************************************************************************/
|
||||||
|
|
||||||
#include "raylib.h"
|
#include "raylib.h"
|
||||||
|
|
||||||
#define RIQM_IMPLEMENTATION
|
|
||||||
#include "riqm.h"
|
|
||||||
|
|
||||||
int main()
|
int main()
|
||||||
{
|
{
|
||||||
// Initialization
|
// Initialization
|
||||||
|
@ -21,7 +18,7 @@ int main()
|
||||||
int screenWidth = 800;
|
int screenWidth = 800;
|
||||||
int screenHeight = 450;
|
int screenHeight = 450;
|
||||||
|
|
||||||
InitWindow(screenWidth, screenHeight, "raylib [models] example - iqm animation");
|
InitWindow(screenWidth, screenHeight, "raylib [models] example - model animation");
|
||||||
|
|
||||||
// Define the camera to look into our 3d world
|
// Define the camera to look into our 3d world
|
||||||
Camera camera = { 0 };
|
Camera camera = { 0 };
|
||||||
|
@ -31,17 +28,16 @@ int main()
|
||||||
camera.fovy = 45.0f; // Camera field-of-view Y
|
camera.fovy = 45.0f; // Camera field-of-view Y
|
||||||
camera.type = CAMERA_PERSPECTIVE; // Camera mode type
|
camera.type = CAMERA_PERSPECTIVE; // Camera mode type
|
||||||
|
|
||||||
// Load the animated model mesh and basic data
|
|
||||||
AnimatedModel model = LoadAnimatedModel("resources/guy.iqm");
|
|
||||||
|
|
||||||
// Load model texture and set material
|
Model model = LoadModel("resources/guy/guy.iqm"); // Load the animated model mesh and basic data
|
||||||
// NOTE: There is only 1 mesh and 1 material (both at index 0), thats what the 2 0's are
|
Texture2D texture = LoadTexture("resources/guy/guytex.png"); // Load model texture and set material
|
||||||
model = AnimatedModelAddTexture(model, "resources/guytex.png"); // REPLACE!
|
SetMaterialTexture(&model.materials[0], MAP_DIFFUSE, texture); // Set model material map texture
|
||||||
model = SetMeshMaterial(model, 0, 0); // REPLACE!
|
|
||||||
|
Vector3 position = { 0.0f, 0.0f, 0.0f }; // Set model position
|
||||||
|
|
||||||
// Load animation data
|
// Load animation data
|
||||||
Animation anim = LoadAnimationFromIQM("resources/guyanim.iqm");
|
int animsCount = 0;
|
||||||
|
ModelAnimation *anims = LoadModelAnimations("resources/guy/guyanim.iqm", &animsCount);
|
||||||
int animFrameCounter = 0;
|
int animFrameCounter = 0;
|
||||||
|
|
||||||
SetCameraMode(camera, CAMERA_FREE); // Set free camera mode
|
SetCameraMode(camera, CAMERA_FREE); // Set free camera mode
|
||||||
|
@ -60,7 +56,8 @@ int main()
|
||||||
if (IsKeyDown(KEY_SPACE))
|
if (IsKeyDown(KEY_SPACE))
|
||||||
{
|
{
|
||||||
animFrameCounter++;
|
animFrameCounter++;
|
||||||
AnimateModel(model, anim, animFrameCounter); // Animate the model with animation data and frame
|
UpdateModelAnimation(model, anims[0], animFrameCounter);
|
||||||
|
if (animFrameCounter >= anims[0].frameCount) animFrameCounter = 0;
|
||||||
}
|
}
|
||||||
//----------------------------------------------------------------------------------
|
//----------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
@ -72,14 +69,18 @@ int main()
|
||||||
|
|
||||||
BeginMode3D(camera);
|
BeginMode3D(camera);
|
||||||
|
|
||||||
DrawAnimatedModel(model, Vector3Zero(), 1.0f, WHITE); // Draw animated model
|
DrawModelEx(model, position, (Vector3){ 1.0f, 0.0f, 0.0f }, -90.0f, (Vector3){ 1.0f, 1.0f, 1.0f }, WHITE);
|
||||||
|
|
||||||
|
for (int i = 0; i < model.boneCount; i++)
|
||||||
|
{
|
||||||
|
DrawCube(anims[0].framePoses[animFrameCounter][i].translation, 0.2f, 0.2f, 0.2f, RED);
|
||||||
|
}
|
||||||
|
|
||||||
DrawGrid(10, 1.0f); // Draw a grid
|
DrawGrid(10, 1.0f); // Draw a grid
|
||||||
|
|
||||||
EndMode3D();
|
EndMode3D();
|
||||||
|
|
||||||
DrawText("PRESS SPACE to PLAY IQM MODEL ANIMATION", 10, 10, 20, MAROON);
|
DrawText("PRESS SPACE to PLAY MODEL ANIMATION", 10, 10, 20, MAROON);
|
||||||
|
|
||||||
DrawText("(c) Guy IQM 3D model by @culacant", screenWidth - 200, screenHeight - 20, 10, GRAY);
|
DrawText("(c) Guy IQM 3D model by @culacant", screenWidth - 200, screenHeight - 20, 10, GRAY);
|
||||||
|
|
||||||
EndDrawing();
|
EndDrawing();
|
||||||
|
@ -88,8 +89,10 @@ int main()
|
||||||
|
|
||||||
// De-Initialization
|
// De-Initialization
|
||||||
//--------------------------------------------------------------------------------------
|
//--------------------------------------------------------------------------------------
|
||||||
UnloadAnimation(anim); // Unload animation data
|
// Unload model animations data
|
||||||
UnloadAnimatedModel(model); // Unload animated model
|
for (int i = 0; i < animsCount; i++) UnloadModelAnimation(anims[i]);
|
||||||
|
|
||||||
|
UnloadModel(model); // Unload model
|
||||||
|
|
||||||
CloseWindow(); // Close window and OpenGL context
|
CloseWindow(); // Close window and OpenGL context
|
||||||
//--------------------------------------------------------------------------------------
|
//--------------------------------------------------------------------------------------
|
Before Width: | Height: | Size: 295 KiB After Width: | Height: | Size: 295 KiB |
|
@ -49,67 +49,13 @@
|
||||||
// Types and Structures Definition
|
// Types and Structures Definition
|
||||||
//----------------------------------------------------------------------------------
|
//----------------------------------------------------------------------------------
|
||||||
|
|
||||||
#define JOINT_NAME_LENGTH 32 // Joint name string length
|
#define BONE_NAME_LENGTH 32 // BoneInfo name string length
|
||||||
#define MESH_NAME_LENGTH 32 // Mesh name string length
|
#define MESH_NAME_LENGTH 32 // Mesh name string length
|
||||||
|
|
||||||
typedef struct Joint {
|
|
||||||
char name[JOINT_NAME_LENGTH];
|
|
||||||
int parent;
|
|
||||||
} Joint;
|
|
||||||
|
|
||||||
typedef struct Pose {
|
|
||||||
Vector3 translation;
|
|
||||||
Quaternion rotation;
|
|
||||||
Vector3 scale;
|
|
||||||
} Pose;
|
|
||||||
|
|
||||||
typedef struct Animation {
|
|
||||||
int jointCount; // Number of joints (bones)
|
|
||||||
Joint *joints; // Joints array
|
|
||||||
// NOTE: Joints in anims do not have names
|
|
||||||
|
|
||||||
int frameCount; // Number of animation frames
|
|
||||||
float framerate; // Frame change speed
|
|
||||||
|
|
||||||
Pose **framepose; // Poses array by frame (and one pose by joint)
|
|
||||||
} Animation;
|
|
||||||
|
|
||||||
// Animated Model type
|
|
||||||
typedef struct AnimatedModel {
|
|
||||||
Matrix transform; // Local transform matrix
|
|
||||||
|
|
||||||
int meshCount; // Number of meshes
|
|
||||||
Mesh *meshes; // Meshes array
|
|
||||||
|
|
||||||
int materialCount; // Number of materials
|
|
||||||
Material *materials; // Materials array
|
|
||||||
|
|
||||||
int *meshMaterialId; // Mesh materials ids
|
|
||||||
|
|
||||||
// Animation required data
|
|
||||||
int jointCount; // Number of joints (and keyposes)
|
|
||||||
Joint *joints; // Mesh joints (bones)
|
|
||||||
Pose *basepose; // Mesh base-poses by joint
|
|
||||||
} AnimatedModel;
|
|
||||||
|
|
||||||
//----------------------------------------------------------------------------------
|
//----------------------------------------------------------------------------------
|
||||||
// Module Functions Declaration
|
// Module Functions Declaration
|
||||||
//----------------------------------------------------------------------------------
|
//----------------------------------------------------------------------------------
|
||||||
|
|
||||||
// Loading/Unloading functions
|
|
||||||
RIQMDEF AnimatedModel LoadAnimatedModel(const char *filename);
|
|
||||||
RIQMDEF void UnloadAnimatedModel(AnimatedModel model);
|
|
||||||
RIQMDEF Animation LoadAnimation(const char *filename);
|
|
||||||
RIQMDEF void UnloadAnimation(Animation anim);
|
|
||||||
|
|
||||||
RIQMDEF AnimatedModel AnimatedModelAddTexture(AnimatedModel model, const char *filename); // GENERIC!
|
|
||||||
RIQMDEF AnimatedModel SetMeshMaterial(AnimatedModel model, int meshid, int textureid); // GENERIC!
|
|
||||||
|
|
||||||
// Usage functionality
|
|
||||||
RIQMDEF bool CheckSkeletonsMatch(AnimatedModel model, Animation anim);
|
|
||||||
RIQMDEF void AnimateModel(AnimatedModel model, Animation anim, int frame);
|
|
||||||
RIQMDEF void DrawAnimatedModel(AnimatedModel model, Vector3 position, float scale, Color tint);
|
|
||||||
RIQMDEF void DrawAnimatedModelEx(AnimatedModel model, Vector3 position, Vector3 rotationAxis, float rotationAngle, Vector3 scale, Color tint);
|
|
||||||
|
|
||||||
#endif // RIQM_H
|
#endif // RIQM_H
|
||||||
|
|
||||||
|
@ -133,90 +79,6 @@ RIQMDEF void DrawAnimatedModelEx(AnimatedModel model, Vector3 position, Vector3
|
||||||
//----------------------------------------------------------------------------------
|
//----------------------------------------------------------------------------------
|
||||||
// Defines and Macros
|
// Defines and Macros
|
||||||
//----------------------------------------------------------------------------------
|
//----------------------------------------------------------------------------------
|
||||||
#define IQM_MAGIC "INTERQUAKEMODEL" // IQM file magic number
|
|
||||||
#define IQM_VERSION 2 // only IQM version 2 supported
|
|
||||||
#define ANIMJOINTNAME "ANIMJOINT" // default joint name (used in Animation)
|
|
||||||
|
|
||||||
//----------------------------------------------------------------------------------
|
|
||||||
// Types and Structures Definition
|
|
||||||
//----------------------------------------------------------------------------------
|
|
||||||
// iqm file structs
|
|
||||||
typedef struct IQMHeader {
|
|
||||||
char magic[16];
|
|
||||||
unsigned int version;
|
|
||||||
unsigned int filesize;
|
|
||||||
unsigned int flags;
|
|
||||||
unsigned int num_text, ofs_text;
|
|
||||||
unsigned int num_meshes, ofs_meshes;
|
|
||||||
unsigned int num_vertexarrays, num_vertexes, ofs_vertexarrays;
|
|
||||||
unsigned int num_triangles, ofs_triangles, ofs_adjacency;
|
|
||||||
unsigned int num_joints, ofs_joints;
|
|
||||||
unsigned int num_poses, ofs_poses;
|
|
||||||
unsigned int num_anims, ofs_anims;
|
|
||||||
unsigned int num_frames, num_framechannels, ofs_frames, ofs_bounds;
|
|
||||||
unsigned int num_comment, ofs_comment;
|
|
||||||
unsigned int num_extensions, ofs_extensions;
|
|
||||||
} IQMHeader;
|
|
||||||
|
|
||||||
typedef struct IQMMesh {
|
|
||||||
unsigned int name;
|
|
||||||
unsigned int material;
|
|
||||||
unsigned int first_vertex, num_vertexes;
|
|
||||||
unsigned int first_triangle, num_triangles;
|
|
||||||
} IQMMesh;
|
|
||||||
|
|
||||||
typedef struct IQMTriangle {
|
|
||||||
unsigned int vertex[3];
|
|
||||||
} IQMTriangle;
|
|
||||||
|
|
||||||
typedef struct IQMAdjacency { // adjacency unused by default
|
|
||||||
unsigned int triangle[3];
|
|
||||||
} IQMAdjacency;
|
|
||||||
|
|
||||||
typedef struct IQMJoint {
|
|
||||||
unsigned int name;
|
|
||||||
int parent;
|
|
||||||
float translate[3], rotate[4], scale[3];
|
|
||||||
} IQMJoint;
|
|
||||||
|
|
||||||
typedef struct IQMPose {
|
|
||||||
int parent;
|
|
||||||
unsigned int mask;
|
|
||||||
float channeloffset[10];
|
|
||||||
float channelscale[10];
|
|
||||||
} IQMPose;
|
|
||||||
|
|
||||||
typedef struct IQMAnim {
|
|
||||||
unsigned int name;
|
|
||||||
unsigned int first_frame, num_frames;
|
|
||||||
float framerate;
|
|
||||||
unsigned int flags;
|
|
||||||
} IQMAnim;
|
|
||||||
|
|
||||||
typedef struct IQMVertexArray {
|
|
||||||
unsigned int type;
|
|
||||||
unsigned int flags;
|
|
||||||
unsigned int format;
|
|
||||||
unsigned int size;
|
|
||||||
unsigned int offset;
|
|
||||||
} IQMVertexArray;
|
|
||||||
|
|
||||||
typedef struct IQMBounds { // bounds unused by default
|
|
||||||
float bbmin[3], bbmax[3];
|
|
||||||
float xyradius, radius;
|
|
||||||
} IQMBounds;
|
|
||||||
|
|
||||||
|
|
||||||
typedef enum {
|
|
||||||
IQM_POSITION = 0,
|
|
||||||
IQM_TEXCOORD = 1,
|
|
||||||
IQM_NORMAL = 2,
|
|
||||||
IQM_TANGENT = 3, // tangents unused by default
|
|
||||||
IQM_BLENDINDEXES = 4,
|
|
||||||
IQM_BLENDWEIGHTS = 5,
|
|
||||||
IQM_COLOR = 6, // vertex colors unused by default
|
|
||||||
IQM_CUSTOM = 0x10 // custom vertex values unused by default
|
|
||||||
} IQMVertexType;
|
|
||||||
|
|
||||||
//----------------------------------------------------------------------------------
|
//----------------------------------------------------------------------------------
|
||||||
// Global Variables Definition
|
// Global Variables Definition
|
||||||
|
@ -225,609 +87,12 @@ typedef enum {
|
||||||
//----------------------------------------------------------------------------------
|
//----------------------------------------------------------------------------------
|
||||||
// Module specific Functions Declaration
|
// Module specific Functions Declaration
|
||||||
//----------------------------------------------------------------------------------
|
//----------------------------------------------------------------------------------
|
||||||
static AnimatedModel LoadIQM(const char *filename);
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
extern "C" { // Prevents name mangling of functions
|
extern "C" { // Prevents name mangling of functions
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Load .iqm file and initialize animated model
|
|
||||||
AnimatedModel LoadAnimatedModel(const char *filename)
|
|
||||||
{
|
|
||||||
AnimatedModel out = LoadIQM(filename);
|
|
||||||
|
|
||||||
for (int i = 0; i < out.meshCount; i++) rlLoadMesh(&out.meshes[i], false);
|
|
||||||
|
|
||||||
out.transform = MatrixIdentity();
|
|
||||||
out.meshMaterialId = malloc(sizeof(int)*out.meshCount);
|
|
||||||
out.materials = NULL;
|
|
||||||
out.materialCount = 0;
|
|
||||||
|
|
||||||
for (int i = 0; i < out.meshCount; i++) out.meshMaterialId[i] = -1;
|
|
||||||
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add a texture to an animated model
|
|
||||||
AnimatedModel AnimatedModelAddTexture(AnimatedModel model, const char *filename)
|
|
||||||
{
|
|
||||||
Texture2D texture = LoadTexture(filename);
|
|
||||||
|
|
||||||
model.materials = realloc(model.materials, sizeof(Material)*(model.materialCount + 1));
|
|
||||||
model.materials[model.materialCount] = LoadMaterialDefault();
|
|
||||||
model.materials[model.materialCount].maps[MAP_DIFFUSE].texture = texture;
|
|
||||||
model.materialCount++;
|
|
||||||
|
|
||||||
return model;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the material for a meshes
|
|
||||||
AnimatedModel SetMeshMaterial(AnimatedModel model, int meshid, int textureid)
|
|
||||||
{
|
|
||||||
if (meshid > model.meshCount)
|
|
||||||
{
|
|
||||||
TraceLog(LOG_WARNING, "MeshId greater than meshCount\n");
|
|
||||||
return model;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (textureid > model.materialCount)
|
|
||||||
{
|
|
||||||
TraceLog(LOG_WARNING,"textureid greater than materialCount\n");
|
|
||||||
return model;
|
|
||||||
}
|
|
||||||
|
|
||||||
model.meshMaterialId[meshid] = textureid;
|
|
||||||
|
|
||||||
return model;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load animations from a .iqm file
|
|
||||||
Animation LoadAnimationFromIQM(const char *filename)
|
|
||||||
{
|
|
||||||
Animation animation = { 0 };
|
|
||||||
|
|
||||||
FILE *iqmFile;
|
|
||||||
IQMHeader iqm;
|
|
||||||
|
|
||||||
iqmFile = fopen(filename,"rb");
|
|
||||||
|
|
||||||
if (!iqmFile)
|
|
||||||
{
|
|
||||||
TraceLog(LOG_ERROR, "[%s] Unable to open file", filename);
|
|
||||||
return animation;
|
|
||||||
}
|
|
||||||
|
|
||||||
// header
|
|
||||||
fread(&iqm, sizeof(IQMHeader), 1, iqmFile);
|
|
||||||
|
|
||||||
if (strncmp(iqm.magic, IQM_MAGIC, sizeof(IQM_MAGIC)))
|
|
||||||
{
|
|
||||||
TraceLog(LOG_ERROR, "Magic Number \"%s\"does not match.", iqm.magic);
|
|
||||||
fclose(iqmFile);
|
|
||||||
return animation;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (iqm.version != IQM_VERSION)
|
|
||||||
{
|
|
||||||
TraceLog(LOG_ERROR, "IQM version %i is incorrect.", iqm.version);
|
|
||||||
fclose(iqmFile);
|
|
||||||
return animation;
|
|
||||||
}
|
|
||||||
|
|
||||||
// header
|
|
||||||
if (iqm.num_anims > 1) TraceLog(LOG_WARNING, "More than 1 animation in file, only the first one will get loaded");
|
|
||||||
|
|
||||||
// joints
|
|
||||||
IQMPose *poses;
|
|
||||||
poses = malloc(sizeof(IQMPose)*iqm.num_poses);
|
|
||||||
fseek(iqmFile, iqm.ofs_poses, SEEK_SET);
|
|
||||||
fread(poses, sizeof(IQMPose)*iqm.num_poses, 1, iqmFile);
|
|
||||||
|
|
||||||
animation.jointCount = iqm.num_poses;
|
|
||||||
animation.joints = malloc(sizeof(Joint)*iqm.num_poses);
|
|
||||||
|
|
||||||
for (int j = 0; j < iqm.num_poses; j++)
|
|
||||||
{
|
|
||||||
strcpy(animation.joints[j].name, ANIMJOINTNAME);
|
|
||||||
animation.joints[j].parent = poses[j].parent;
|
|
||||||
}
|
|
||||||
|
|
||||||
// animations
|
|
||||||
IQMAnim anim = {0};
|
|
||||||
fseek(iqmFile, iqm.ofs_anims, SEEK_SET);
|
|
||||||
fread(&anim, sizeof(IQMAnim), 1, iqmFile);
|
|
||||||
|
|
||||||
animation.frameCount = anim.num_frames;
|
|
||||||
animation.framerate = anim.framerate;
|
|
||||||
|
|
||||||
// frameposes
|
|
||||||
unsigned short *framedata = malloc(sizeof(unsigned short)*iqm.num_frames*iqm.num_framechannels);
|
|
||||||
fseek(iqmFile, iqm.ofs_frames, SEEK_SET);
|
|
||||||
fread(framedata, sizeof(unsigned short)*iqm.num_frames*iqm.num_framechannels, 1, iqmFile);
|
|
||||||
|
|
||||||
animation.framepose = malloc(sizeof(Pose*)*anim.num_frames);
|
|
||||||
for (int j = 0; j < anim.num_frames; j++) animation.framepose[j] = malloc(sizeof(Pose)*iqm.num_poses);
|
|
||||||
|
|
||||||
int dcounter = anim.first_frame*iqm.num_framechannels;
|
|
||||||
|
|
||||||
for (int frame = 0; frame < anim.num_frames; frame++)
|
|
||||||
{
|
|
||||||
for (int i = 0; i < iqm.num_poses; i++)
|
|
||||||
{
|
|
||||||
animation.framepose[frame][i].translation.x = poses[i].channeloffset[0];
|
|
||||||
|
|
||||||
if (poses[i].mask & 0x01)
|
|
||||||
{
|
|
||||||
animation.framepose[frame][i].translation.x += framedata[dcounter]*poses[i].channelscale[0];
|
|
||||||
dcounter++;
|
|
||||||
}
|
|
||||||
|
|
||||||
animation.framepose[frame][i].translation.y = poses[i].channeloffset[1];
|
|
||||||
|
|
||||||
if (poses[i].mask & 0x02)
|
|
||||||
{
|
|
||||||
animation.framepose[frame][i].translation.y += framedata[dcounter]*poses[i].channelscale[1];
|
|
||||||
dcounter++;
|
|
||||||
}
|
|
||||||
|
|
||||||
animation.framepose[frame][i].translation.z = poses[i].channeloffset[2];
|
|
||||||
|
|
||||||
if (poses[i].mask & 0x04)
|
|
||||||
{
|
|
||||||
animation.framepose[frame][i].translation.z += framedata[dcounter]*poses[i].channelscale[2];
|
|
||||||
dcounter++;
|
|
||||||
}
|
|
||||||
|
|
||||||
animation.framepose[frame][i].rotation.x = poses[i].channeloffset[3];
|
|
||||||
|
|
||||||
if (poses[i].mask & 0x08)
|
|
||||||
{
|
|
||||||
animation.framepose[frame][i].rotation.x += framedata[dcounter]*poses[i].channelscale[3];
|
|
||||||
dcounter++;
|
|
||||||
}
|
|
||||||
|
|
||||||
animation.framepose[frame][i].rotation.y = poses[i].channeloffset[4];
|
|
||||||
|
|
||||||
if (poses[i].mask & 0x10)
|
|
||||||
{
|
|
||||||
animation.framepose[frame][i].rotation.y += framedata[dcounter]*poses[i].channelscale[4];
|
|
||||||
dcounter++;
|
|
||||||
}
|
|
||||||
|
|
||||||
animation.framepose[frame][i].rotation.z = poses[i].channeloffset[5];
|
|
||||||
|
|
||||||
if (poses[i].mask & 0x20)
|
|
||||||
{
|
|
||||||
animation.framepose[frame][i].rotation.z += framedata[dcounter]*poses[i].channelscale[5];
|
|
||||||
dcounter++;
|
|
||||||
}
|
|
||||||
|
|
||||||
animation.framepose[frame][i].rotation.w = poses[i].channeloffset[6];
|
|
||||||
|
|
||||||
if (poses[i].mask & 0x40)
|
|
||||||
{
|
|
||||||
animation.framepose[frame][i].rotation.w += framedata[dcounter]*poses[i].channelscale[6];
|
|
||||||
dcounter++;
|
|
||||||
}
|
|
||||||
|
|
||||||
animation.framepose[frame][i].scale.x = poses[i].channeloffset[7];
|
|
||||||
|
|
||||||
if (poses[i].mask & 0x80)
|
|
||||||
{
|
|
||||||
animation.framepose[frame][i].scale.x += framedata[dcounter]*poses[i].channelscale[7];
|
|
||||||
dcounter++;
|
|
||||||
}
|
|
||||||
|
|
||||||
animation.framepose[frame][i].scale.y = poses[i].channeloffset[8];
|
|
||||||
|
|
||||||
if (poses[i].mask & 0x100)
|
|
||||||
{
|
|
||||||
animation.framepose[frame][i].scale.y += framedata[dcounter]*poses[i].channelscale[8];
|
|
||||||
dcounter++;
|
|
||||||
}
|
|
||||||
|
|
||||||
animation.framepose[frame][i].scale.z = poses[i].channeloffset[9];
|
|
||||||
|
|
||||||
if (poses[i].mask & 0x200)
|
|
||||||
{
|
|
||||||
animation.framepose[frame][i].scale.z += framedata[dcounter]*poses[i].channelscale[9];
|
|
||||||
dcounter++;
|
|
||||||
}
|
|
||||||
|
|
||||||
animation.framepose[frame][i].rotation = QuaternionNormalize(animation.framepose[frame][i].rotation);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build frameposes
|
|
||||||
for (int frame = 0; frame < anim.num_frames; frame++)
|
|
||||||
{
|
|
||||||
for (int i = 0; i < animation.jointCount; i++)
|
|
||||||
{
|
|
||||||
if (animation.joints[i].parent >= 0)
|
|
||||||
{
|
|
||||||
animation.framepose[frame][i].rotation = QuaternionMultiply(animation.framepose[frame][animation.joints[i].parent].rotation, animation.framepose[frame][i].rotation);
|
|
||||||
animation.framepose[frame][i].translation = Vector3RotateByQuaternion(animation.framepose[frame][i].translation, animation.framepose[frame][animation.joints[i].parent].rotation);
|
|
||||||
animation.framepose[frame][i].translation = Vector3Add(animation.framepose[frame][i].translation, animation.framepose[frame][animation.joints[i].parent].translation);
|
|
||||||
animation.framepose[frame][i].scale = Vector3MultiplyV(animation.framepose[frame][i].scale, animation.framepose[frame][animation.joints[i].parent].scale);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
free(framedata);
|
|
||||||
free(poses);
|
|
||||||
|
|
||||||
fclose(iqmFile);
|
|
||||||
|
|
||||||
return animation;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unload animated model
|
|
||||||
void UnloadAnimatedModel(AnimatedModel model)
|
|
||||||
{
|
|
||||||
free(model.materials);
|
|
||||||
free(model.meshMaterialId);
|
|
||||||
free(model.joints);
|
|
||||||
free(model.basepose);
|
|
||||||
|
|
||||||
for (int i = 0; i < model.meshCount; i++) rlUnloadMesh(&model.meshes[i]);
|
|
||||||
|
|
||||||
free(model.meshes);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unload animation
|
|
||||||
void UnloadAnimation(Animation anim)
|
|
||||||
{
|
|
||||||
free(anim.joints);
|
|
||||||
free(anim.framepose);
|
|
||||||
|
|
||||||
for (int i = 0; i < anim.frameCount; i++) free(anim.framepose[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if skeletons match, only parents and jointCount are checked
|
|
||||||
bool CheckSkeletonsMatch(AnimatedModel model, Animation anim)
|
|
||||||
{
|
|
||||||
if (model.jointCount != anim.jointCount) return 0;
|
|
||||||
|
|
||||||
for (int i = 0; i < model.jointCount; i++)
|
|
||||||
{
|
|
||||||
if (model.joints[i].parent != anim.joints[i].parent) return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calculate the animated vertex positions and normals based on an animation at a given frame
|
|
||||||
void AnimateModel(AnimatedModel model, Animation anim, int frame)
|
|
||||||
{
|
|
||||||
if (frame >= anim.frameCount) frame = frame%anim.frameCount;
|
|
||||||
|
|
||||||
for (int m = 0; m < model.meshCount; m++)
|
|
||||||
{
|
|
||||||
Vector3 outv = {0};
|
|
||||||
Vector3 outn = {0};
|
|
||||||
|
|
||||||
Vector3 baset = {0};
|
|
||||||
Quaternion baser = {0};
|
|
||||||
Vector3 bases = {0};
|
|
||||||
|
|
||||||
Vector3 outt = {0};
|
|
||||||
Quaternion outr = {0};
|
|
||||||
Vector3 outs = {0};
|
|
||||||
|
|
||||||
int vcounter = 0;
|
|
||||||
int wcounter = 0;
|
|
||||||
int weightId = 0;
|
|
||||||
|
|
||||||
for (int i = 0; i < model.meshes[m].vertexCount; i++)
|
|
||||||
{
|
|
||||||
weightId = model.meshes[m].weightId[wcounter];
|
|
||||||
baset = model.basepose[weightId].translation;
|
|
||||||
baser = model.basepose[weightId].rotation;
|
|
||||||
bases = model.basepose[weightId].scale;
|
|
||||||
outt = anim.framepose[frame][weightId].translation;
|
|
||||||
outr = anim.framepose[frame][weightId].rotation;
|
|
||||||
outs = anim.framepose[frame][weightId].scale;
|
|
||||||
|
|
||||||
// vertices
|
|
||||||
// NOTE: We use meshes.baseVertices (default position) to calculate meshes.vertices (animated position)
|
|
||||||
outv = (Vector3){ model.meshes[m].baseVertices[vcounter], model.meshes[m].baseVertices[vcounter + 1], model.meshes[m].baseVertices[vcounter + 2] };
|
|
||||||
outv = Vector3MultiplyV(outv, outs);
|
|
||||||
outv = Vector3Subtract(outv, baset);
|
|
||||||
outv = Vector3RotateByQuaternion(outv, QuaternionMultiply(outr, QuaternionInvert(baser)));
|
|
||||||
outv = Vector3Add(outv, outt);
|
|
||||||
model.meshes[m].vertices[vcounter] = outv.x;
|
|
||||||
model.meshes[m].vertices[vcounter + 1] = outv.y;
|
|
||||||
model.meshes[m].vertices[vcounter + 2] = outv.z;
|
|
||||||
|
|
||||||
// normals
|
|
||||||
// NOTE: We use meshes.baseNormals (default normal) to calculate meshes.normals (animated normals)
|
|
||||||
outn = (Vector3){ model.meshes[m].baseNormals[vcounter], model.meshes[m].baseNormals[vcounter + 1], model.meshes[m].baseNormals[vcounter + 2] };
|
|
||||||
outn = Vector3RotateByQuaternion(outn, QuaternionMultiply(outr, QuaternionInvert(baser)));
|
|
||||||
model.meshes[m].normals[vcounter] = outn.x;
|
|
||||||
model.meshes[m].normals[vcounter + 1] = outn.y;
|
|
||||||
model.meshes[m].normals[vcounter + 2] = outn.z;
|
|
||||||
vcounter += 3;
|
|
||||||
wcounter += 4;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Draw an animated model
|
|
||||||
void DrawAnimatedModel(AnimatedModel model, Vector3 position, float scale, Color tint)
|
|
||||||
{
|
|
||||||
Vector3 vScale = { scale, scale, scale };
|
|
||||||
Vector3 rotationAxis = { 1.0f, 0.0f,0.0f };
|
|
||||||
|
|
||||||
DrawAnimatedModelEx(model, position, rotationAxis, -90.0f, vScale, tint);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Draw an animated model with extended parameters
|
|
||||||
void DrawAnimatedModelEx(AnimatedModel model, Vector3 position, Vector3 rotationAxis, float rotationAngle, Vector3 scale, Color tint)
|
|
||||||
{
|
|
||||||
if (model.materialCount == 0)
|
|
||||||
{
|
|
||||||
TraceLog(LOG_WARNING,"No materials set, can't draw animated meshes\n");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
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++)
|
|
||||||
{
|
|
||||||
rlUpdateMesh(model.meshes[i], 0, model.meshes[i].vertexCount); // Update vertex position
|
|
||||||
rlUpdateMesh(model.meshes[i], 2, model.meshes[i].vertexCount); // Update vertex normals
|
|
||||||
rlDrawMesh(model.meshes[i], model.materials[model.meshMaterialId[i]], model.transform); // Draw meshes
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load animated model meshes from IQM file
|
|
||||||
static AnimatedModel LoadIQM(const char *filename)
|
|
||||||
{
|
|
||||||
AnimatedModel model = { 0 };
|
|
||||||
|
|
||||||
FILE *iqmFile;
|
|
||||||
IQMHeader iqm;
|
|
||||||
|
|
||||||
IQMMesh *imesh;
|
|
||||||
IQMTriangle *tri;
|
|
||||||
IQMVertexArray *va;
|
|
||||||
IQMJoint *ijoint;
|
|
||||||
|
|
||||||
float *vertex;
|
|
||||||
float *normal;
|
|
||||||
float *text;
|
|
||||||
char *blendi;
|
|
||||||
unsigned char *blendw;
|
|
||||||
|
|
||||||
iqmFile = fopen(filename, "rb");
|
|
||||||
|
|
||||||
if (!iqmFile)
|
|
||||||
{
|
|
||||||
TraceLog(LOG_ERROR, "[%s] Unable to open file", filename);
|
|
||||||
return model;
|
|
||||||
}
|
|
||||||
|
|
||||||
// header
|
|
||||||
fread(&iqm,sizeof(IQMHeader), 1, iqmFile);
|
|
||||||
|
|
||||||
if (strncmp(iqm.magic, IQM_MAGIC, sizeof(IQM_MAGIC)))
|
|
||||||
{
|
|
||||||
TraceLog(LOG_ERROR, "Magic Number \"%s\"does not match.", iqm.magic);
|
|
||||||
fclose(iqmFile);
|
|
||||||
return model;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(iqm.version != IQM_VERSION)
|
|
||||||
{
|
|
||||||
TraceLog(LOG_ERROR, "IQM version %i is incorrect.", iqm.version);
|
|
||||||
fclose(iqmFile);
|
|
||||||
return model;
|
|
||||||
}
|
|
||||||
|
|
||||||
// meshes
|
|
||||||
imesh = malloc(sizeof(IQMMesh)*iqm.num_meshes);
|
|
||||||
fseek(iqmFile, iqm.ofs_meshes, SEEK_SET);
|
|
||||||
fread(imesh, sizeof(IQMMesh)*iqm.num_meshes, 1, iqmFile);
|
|
||||||
|
|
||||||
model.meshCount = iqm.num_meshes;
|
|
||||||
model.meshes = malloc(sizeof(Mesh)*iqm.num_meshes);
|
|
||||||
|
|
||||||
char name[MESH_NAME_LENGTH];
|
|
||||||
|
|
||||||
for (int i = 0; i < iqm.num_meshes; i++)
|
|
||||||
{
|
|
||||||
fseek(iqmFile,iqm.ofs_text+imesh[i].name,SEEK_SET);
|
|
||||||
fread(name, sizeof(char)*MESH_NAME_LENGTH, 1, iqmFile); // Mesh name not used...
|
|
||||||
model.meshes[i].vertexCount = imesh[i].num_vertexes;
|
|
||||||
|
|
||||||
model.meshes[i].baseVertices = malloc(sizeof(float)*imesh[i].num_vertexes*3); // Default IQM base position
|
|
||||||
model.meshes[i].baseNormals = malloc(sizeof(float)*imesh[i].num_vertexes*3); // Default IQM base normal
|
|
||||||
|
|
||||||
model.meshes[i].texcoords = malloc(sizeof(float)*imesh[i].num_vertexes*2);
|
|
||||||
model.meshes[i].weightId = malloc(sizeof(int)*imesh[i].num_vertexes*4);
|
|
||||||
model.meshes[i].weightBias = malloc(sizeof(float)*imesh[i].num_vertexes*4);
|
|
||||||
|
|
||||||
model.meshes[i].triangleCount = imesh[i].num_triangles;
|
|
||||||
model.meshes[i].indices = malloc(sizeof(unsigned short)*imesh[i].num_triangles*3);
|
|
||||||
|
|
||||||
// What we actually process for rendering, should be updated transforming meshes.vertices and meshes.normals
|
|
||||||
model.meshes[i].vertices = malloc(sizeof(float)*imesh[i].num_vertexes*3);
|
|
||||||
model.meshes[i].normals = malloc(sizeof(float)*imesh[i].num_vertexes*3);
|
|
||||||
}
|
|
||||||
|
|
||||||
// tris
|
|
||||||
tri = malloc(sizeof(IQMTriangle)*iqm.num_triangles);
|
|
||||||
fseek(iqmFile, iqm.ofs_triangles, SEEK_SET);
|
|
||||||
fread(tri, sizeof(IQMTriangle)*iqm.num_triangles, 1, iqmFile);
|
|
||||||
|
|
||||||
for (int m = 0; m < iqm.num_meshes; m++)
|
|
||||||
{
|
|
||||||
int tcounter = 0;
|
|
||||||
|
|
||||||
for (int i = imesh[m].first_triangle; i < imesh[m].first_triangle+imesh[m].num_triangles; i++)
|
|
||||||
{
|
|
||||||
// IQM triangles are stored counter clockwise, but raylib sets opengl to clockwise drawing, so we swap them around
|
|
||||||
model.meshes[m].indices[tcounter+2] = tri[i].vertex[0] - imesh[m].first_vertex;
|
|
||||||
model.meshes[m].indices[tcounter+1] = tri[i].vertex[1] - imesh[m].first_vertex;
|
|
||||||
model.meshes[m].indices[tcounter] = tri[i].vertex[2] - imesh[m].first_vertex;
|
|
||||||
tcounter += 3;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// vertarrays
|
|
||||||
va = malloc(sizeof(IQMVertexArray)*iqm.num_vertexarrays);
|
|
||||||
fseek(iqmFile, iqm.ofs_vertexarrays, SEEK_SET);
|
|
||||||
fread(va, sizeof(IQMVertexArray)*iqm.num_vertexarrays, 1, iqmFile);
|
|
||||||
|
|
||||||
for (int i = 0; i < iqm.num_vertexarrays; i++)
|
|
||||||
{
|
|
||||||
switch (va[i].type)
|
|
||||||
{
|
|
||||||
case IQM_POSITION:
|
|
||||||
{
|
|
||||||
vertex = malloc(sizeof(float)*iqm.num_vertexes*3);
|
|
||||||
fseek(iqmFile, va[i].offset, SEEK_SET);
|
|
||||||
fread(vertex, sizeof(float)*iqm.num_vertexes*3, 1, iqmFile);
|
|
||||||
|
|
||||||
for (int m = 0; m < iqm.num_meshes; m++)
|
|
||||||
{
|
|
||||||
int vcounter = 0;
|
|
||||||
for (int i = imesh[m].first_vertex*3; i < (imesh[m].first_vertex + imesh[m].num_vertexes)*3; i++)
|
|
||||||
{
|
|
||||||
model.meshes[m].vertices[vcounter] = vertex[i];
|
|
||||||
model.meshes[m].baseVertices[vcounter] = vertex[i];
|
|
||||||
vcounter++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} break;
|
|
||||||
case IQM_NORMAL:
|
|
||||||
{
|
|
||||||
normal = malloc(sizeof(float)*iqm.num_vertexes*3);
|
|
||||||
fseek(iqmFile, va[i].offset, SEEK_SET);
|
|
||||||
fread(normal, sizeof(float)*iqm.num_vertexes*3, 1, iqmFile);
|
|
||||||
|
|
||||||
for (int m = 0; m < iqm.num_meshes; m++)
|
|
||||||
{
|
|
||||||
int vcounter = 0;
|
|
||||||
for (int i = imesh[m].first_vertex*3; i < (imesh[m].first_vertex + imesh[m].num_vertexes)*3; i++)
|
|
||||||
{
|
|
||||||
model.meshes[m].normals[vcounter] = normal[i];
|
|
||||||
model.meshes[m].baseNormals[vcounter] = normal[i];
|
|
||||||
vcounter++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} break;
|
|
||||||
case IQM_TEXCOORD:
|
|
||||||
{
|
|
||||||
text = malloc(sizeof(float)*iqm.num_vertexes*2);
|
|
||||||
fseek(iqmFile, va[i].offset, SEEK_SET);
|
|
||||||
fread(text, sizeof(float)*iqm.num_vertexes*2, 1, iqmFile);
|
|
||||||
|
|
||||||
for (int m = 0; m < iqm.num_meshes; m++)
|
|
||||||
{
|
|
||||||
int vcounter = 0;
|
|
||||||
for (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 = malloc(sizeof(char)*iqm.num_vertexes*4);
|
|
||||||
fseek(iqmFile, va[i].offset, SEEK_SET);
|
|
||||||
fread(blendi, sizeof(char)*iqm.num_vertexes*4, 1, iqmFile);
|
|
||||||
|
|
||||||
for (int m = 0; m < iqm.num_meshes; m++)
|
|
||||||
{
|
|
||||||
int vcounter = 0;
|
|
||||||
for (int i = imesh[m].first_vertex*4; i < (imesh[m].first_vertex + imesh[m].num_vertexes)*4; i++)
|
|
||||||
{
|
|
||||||
model.meshes[m].weightId[vcounter] = blendi[i];
|
|
||||||
vcounter++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} break;
|
|
||||||
case IQM_BLENDWEIGHTS:
|
|
||||||
{
|
|
||||||
blendw = malloc(sizeof(unsigned char)*iqm.num_vertexes*4);
|
|
||||||
fseek(iqmFile,va[i].offset,SEEK_SET);
|
|
||||||
fread(blendw,sizeof(unsigned char)*iqm.num_vertexes*4,1,iqmFile);
|
|
||||||
|
|
||||||
for (int m = 0; m < iqm.num_meshes; m++)
|
|
||||||
{
|
|
||||||
int vcounter = 0;
|
|
||||||
for (int i = imesh[m].first_vertex*4; i < (imesh[m].first_vertex + imesh[m].num_vertexes)*4; i++)
|
|
||||||
{
|
|
||||||
model.meshes[m].weightBias[vcounter] = blendw[i]/255.0f;
|
|
||||||
vcounter++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// joints, include base poses
|
|
||||||
ijoint = malloc(sizeof(IQMJoint)*iqm.num_joints);
|
|
||||||
fseek(iqmFile, iqm.ofs_joints, SEEK_SET);
|
|
||||||
fread(ijoint, sizeof(IQMJoint)*iqm.num_joints, 1, iqmFile);
|
|
||||||
|
|
||||||
model.jointCount = iqm.num_joints;
|
|
||||||
model.joints = malloc(sizeof(Joint)*iqm.num_joints);
|
|
||||||
model.basepose = malloc(sizeof(Pose)*iqm.num_joints);
|
|
||||||
|
|
||||||
for (int i = 0; i < iqm.num_joints; i++)
|
|
||||||
{
|
|
||||||
// joints
|
|
||||||
model.joints[i].parent = ijoint[i].parent;
|
|
||||||
fseek(iqmFile, iqm.ofs_text + ijoint[i].name, SEEK_SET);
|
|
||||||
fread(model.joints[i].name,sizeof(char)*JOINT_NAME_LENGTH, 1, iqmFile);
|
|
||||||
|
|
||||||
// basepose
|
|
||||||
model.basepose[i].translation.x = ijoint[i].translate[0];
|
|
||||||
model.basepose[i].translation.y = ijoint[i].translate[1];
|
|
||||||
model.basepose[i].translation.z = ijoint[i].translate[2];
|
|
||||||
|
|
||||||
model.basepose[i].rotation.x = ijoint[i].rotate[0];
|
|
||||||
model.basepose[i].rotation.y = ijoint[i].rotate[1];
|
|
||||||
model.basepose[i].rotation.z = ijoint[i].rotate[2];
|
|
||||||
model.basepose[i].rotation.w = ijoint[i].rotate[3];
|
|
||||||
|
|
||||||
model.basepose[i].scale.x = ijoint[i].scale[0];
|
|
||||||
model.basepose[i].scale.y = ijoint[i].scale[1];
|
|
||||||
model.basepose[i].scale.z = ijoint[i].scale[2];
|
|
||||||
}
|
|
||||||
|
|
||||||
// build base pose
|
|
||||||
for (int i = 0; i < model.jointCount; i++)
|
|
||||||
{
|
|
||||||
if (model.joints[i].parent >= 0)
|
|
||||||
{
|
|
||||||
model.basepose[i].rotation = QuaternionMultiply(model.basepose[model.joints[i].parent].rotation, model.basepose[i].rotation);
|
|
||||||
model.basepose[i].translation = Vector3RotateByQuaternion(model.basepose[i].translation, model.basepose[model.joints[i].parent].rotation);
|
|
||||||
model.basepose[i].translation = Vector3Add(model.basepose[i].translation, model.basepose[model.joints[i].parent].translation);
|
|
||||||
model.basepose[i].scale = Vector3MultiplyV(model.basepose[i].scale, model.basepose[model.joints[i].parent].scale);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fclose(iqmFile);
|
|
||||||
free(imesh);
|
|
||||||
free(tri);
|
|
||||||
free(va);
|
|
||||||
free(vertex);
|
|
||||||
free(normal);
|
|
||||||
free(text);
|
|
||||||
free(blendi);
|
|
||||||
free(blendw);
|
|
||||||
free(ijoint);
|
|
||||||
|
|
||||||
return model;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
660
src/models.c
660
src/models.c
|
@ -697,6 +697,18 @@ void UnloadModel(Model model)
|
||||||
TraceLog(LOG_INFO, "Unloaded model data from RAM and VRAM");
|
TraceLog(LOG_INFO, "Unloaded model data from RAM and VRAM");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Load meshes from model file
|
||||||
|
Mesh *LoadMeshes(const char *fileName, int *meshCount)
|
||||||
|
{
|
||||||
|
Mesh *meshes = NULL;
|
||||||
|
int count = 0;
|
||||||
|
|
||||||
|
// TODO: Load meshes from file (OBJ, IQM, GLTF)
|
||||||
|
|
||||||
|
*meshCount = count;
|
||||||
|
return meshes;
|
||||||
|
}
|
||||||
|
|
||||||
// Unload mesh from memory (RAM and/or VRAM)
|
// Unload mesh from memory (RAM and/or VRAM)
|
||||||
void UnloadMesh(Mesh *mesh)
|
void UnloadMesh(Mesh *mesh)
|
||||||
{
|
{
|
||||||
|
@ -759,6 +771,386 @@ void ExportMesh(Mesh mesh, const char *fileName)
|
||||||
else TraceLog(LOG_WARNING, "Mesh could not be exported.");
|
else TraceLog(LOG_WARNING, "Mesh could not be exported.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Load materials from model file
|
||||||
|
Material *LoadMaterials(const char *fileName, int *materialCount)
|
||||||
|
{
|
||||||
|
Material *materials = NULL;
|
||||||
|
unsigned int count = 0;
|
||||||
|
|
||||||
|
// TODO: Support IQM and GLTF for materials parsing
|
||||||
|
|
||||||
|
#if defined(SUPPORT_FILEFORMAT_MTL)
|
||||||
|
if (IsFileExtension(fileName, ".mtl"))
|
||||||
|
{
|
||||||
|
tinyobj_material_t *mats;
|
||||||
|
|
||||||
|
int result = tinyobj_parse_mtl_file(&mats, &count, fileName);
|
||||||
|
|
||||||
|
// TODO: Process materials to return
|
||||||
|
|
||||||
|
tinyobj_materials_free(mats, count);
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
TraceLog(LOG_WARNING, "[%s] Materials file not supported", fileName);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Set materials shader to default (DIFFUSE, SPECULAR, NORMAL)
|
||||||
|
for (int i = 0; i < count; i++) materials[i].shader = GetShaderDefault();
|
||||||
|
|
||||||
|
*materialCount = count;
|
||||||
|
return materials;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load default material (Supports: DIFFUSE, SPECULAR, NORMAL maps)
|
||||||
|
Material LoadMaterialDefault(void)
|
||||||
|
{
|
||||||
|
Material material = { 0 };
|
||||||
|
|
||||||
|
material.shader = GetShaderDefault();
|
||||||
|
material.maps[MAP_DIFFUSE].texture = GetTextureDefault(); // White texture (1x1 pixel)
|
||||||
|
//material.maps[MAP_NORMAL].texture; // NOTE: By default, not set
|
||||||
|
//material.maps[MAP_SPECULAR].texture; // NOTE: By default, not set
|
||||||
|
|
||||||
|
material.maps[MAP_DIFFUSE].color = WHITE; // Diffuse color
|
||||||
|
material.maps[MAP_SPECULAR].color = WHITE; // Specular color
|
||||||
|
|
||||||
|
return material;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unload material from memory
|
||||||
|
void UnloadMaterial(Material material)
|
||||||
|
{
|
||||||
|
// Unload material shader (avoid unloading default shader, managed by raylib)
|
||||||
|
if (material.shader.id != GetShaderDefault().id) UnloadShader(material.shader);
|
||||||
|
|
||||||
|
// Unload loaded texture maps (avoid unloading default texture, managed by raylib)
|
||||||
|
for (int i = 0; i < MAX_MATERIAL_MAPS; i++)
|
||||||
|
{
|
||||||
|
if (material.maps[i].texture.id != GetTextureDefault().id) rlDeleteTextures(material.maps[i].texture.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set texture for a material map type (MAP_DIFFUSE, MAP_SPECULAR...)
|
||||||
|
// NOTE: Previous texture should be manually unloaded
|
||||||
|
void SetMaterialTexture(Material *material, int mapType, Texture2D texture)
|
||||||
|
{
|
||||||
|
material->maps[mapType].texture = texture;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the material for a mesh
|
||||||
|
void SetModelMeshMaterial(Model *model, int meshId, int materialId)
|
||||||
|
{
|
||||||
|
if (meshId >= model->meshCount) TraceLog(LOG_WARNING, "Mesh id greater than mesh count");
|
||||||
|
else if (materialId >= model->materialCount) TraceLog(LOG_WARNING,"Material id greater than material count");
|
||||||
|
else model->meshMaterial[meshId] = materialId;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load model animations from file
|
||||||
|
ModelAnimation *LoadModelAnimations(const char *filename, int *animCount)
|
||||||
|
{
|
||||||
|
ModelAnimation *animations = (ModelAnimation *)malloc(1*sizeof(ModelAnimation));
|
||||||
|
int count = 1;
|
||||||
|
|
||||||
|
#define IQM_MAGIC "INTERQUAKEMODEL" // IQM file magic number
|
||||||
|
#define IQM_VERSION 2 // only IQM version 2 supported
|
||||||
|
|
||||||
|
typedef struct IQMHeader {
|
||||||
|
char magic[16];
|
||||||
|
unsigned int version;
|
||||||
|
unsigned int filesize;
|
||||||
|
unsigned int flags;
|
||||||
|
unsigned int num_text, ofs_text;
|
||||||
|
unsigned int num_meshes, ofs_meshes;
|
||||||
|
unsigned int num_vertexarrays, num_vertexes, ofs_vertexarrays;
|
||||||
|
unsigned int num_triangles, ofs_triangles, ofs_adjacency;
|
||||||
|
unsigned int num_joints, ofs_joints;
|
||||||
|
unsigned int num_poses, ofs_poses;
|
||||||
|
unsigned int num_anims, ofs_anims;
|
||||||
|
unsigned int num_frames, num_framechannels, ofs_frames, ofs_bounds;
|
||||||
|
unsigned int num_comment, ofs_comment;
|
||||||
|
unsigned int num_extensions, ofs_extensions;
|
||||||
|
} IQMHeader;
|
||||||
|
|
||||||
|
typedef struct IQMPose {
|
||||||
|
int parent;
|
||||||
|
unsigned int mask;
|
||||||
|
float channeloffset[10];
|
||||||
|
float channelscale[10];
|
||||||
|
} IQMPose;
|
||||||
|
|
||||||
|
typedef struct IQMAnim {
|
||||||
|
unsigned int name;
|
||||||
|
unsigned int first_frame, num_frames;
|
||||||
|
float framerate;
|
||||||
|
unsigned int flags;
|
||||||
|
} IQMAnim;
|
||||||
|
|
||||||
|
ModelAnimation animation = { 0 };
|
||||||
|
|
||||||
|
FILE *iqmFile;
|
||||||
|
IQMHeader iqm;
|
||||||
|
|
||||||
|
iqmFile = fopen(filename,"rb");
|
||||||
|
|
||||||
|
if (!iqmFile)
|
||||||
|
{
|
||||||
|
TraceLog(LOG_ERROR, "[%s] Unable to open file", filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
// header
|
||||||
|
fread(&iqm, sizeof(IQMHeader), 1, iqmFile);
|
||||||
|
|
||||||
|
if (strncmp(iqm.magic, IQM_MAGIC, sizeof(IQM_MAGIC)))
|
||||||
|
{
|
||||||
|
TraceLog(LOG_ERROR, "Magic Number \"%s\"does not match.", iqm.magic);
|
||||||
|
fclose(iqmFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (iqm.version != IQM_VERSION)
|
||||||
|
{
|
||||||
|
TraceLog(LOG_ERROR, "IQM version %i is incorrect.", iqm.version);
|
||||||
|
fclose(iqmFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
// header
|
||||||
|
if (iqm.num_anims > 1) TraceLog(LOG_WARNING, "More than 1 animation in file, only the first one will be loaded");
|
||||||
|
|
||||||
|
// bones
|
||||||
|
IQMPose *poses;
|
||||||
|
poses = malloc(sizeof(IQMPose)*iqm.num_poses);
|
||||||
|
fseek(iqmFile, iqm.ofs_poses, SEEK_SET);
|
||||||
|
fread(poses, sizeof(IQMPose)*iqm.num_poses, 1, iqmFile);
|
||||||
|
|
||||||
|
animation.boneCount = iqm.num_poses;
|
||||||
|
animation.bones = malloc(sizeof(BoneInfo)*iqm.num_poses);
|
||||||
|
|
||||||
|
for (int j = 0; j < iqm.num_poses; j++)
|
||||||
|
{
|
||||||
|
strcpy(animation.bones[j].name, "ANIMJOINTNAME");
|
||||||
|
animation.bones[j].parent = poses[j].parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
// animations
|
||||||
|
IQMAnim anim = {0};
|
||||||
|
fseek(iqmFile, iqm.ofs_anims, SEEK_SET);
|
||||||
|
fread(&anim, sizeof(IQMAnim), 1, iqmFile);
|
||||||
|
|
||||||
|
animation.frameCount = anim.num_frames;
|
||||||
|
//animation.framerate = anim.framerate;
|
||||||
|
|
||||||
|
// frameposes
|
||||||
|
unsigned short *framedata = malloc(sizeof(unsigned short)*iqm.num_frames*iqm.num_framechannels);
|
||||||
|
fseek(iqmFile, iqm.ofs_frames, SEEK_SET);
|
||||||
|
fread(framedata, sizeof(unsigned short)*iqm.num_frames*iqm.num_framechannels, 1, iqmFile);
|
||||||
|
|
||||||
|
animation.framePoses = malloc(sizeof(Transform*)*anim.num_frames);
|
||||||
|
for (int j = 0; j < anim.num_frames; j++) animation.framePoses[j] = malloc(sizeof(Transform)*iqm.num_poses);
|
||||||
|
|
||||||
|
int dcounter = anim.first_frame*iqm.num_framechannels;
|
||||||
|
|
||||||
|
for (int frame = 0; frame < anim.num_frames; frame++)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < iqm.num_poses; i++)
|
||||||
|
{
|
||||||
|
animation.framePoses[frame][i].translation.x = poses[i].channeloffset[0];
|
||||||
|
|
||||||
|
if (poses[i].mask & 0x01)
|
||||||
|
{
|
||||||
|
animation.framePoses[frame][i].translation.x += framedata[dcounter]*poses[i].channelscale[0];
|
||||||
|
dcounter++;
|
||||||
|
}
|
||||||
|
|
||||||
|
animation.framePoses[frame][i].translation.y = poses[i].channeloffset[1];
|
||||||
|
|
||||||
|
if (poses[i].mask & 0x02)
|
||||||
|
{
|
||||||
|
animation.framePoses[frame][i].translation.y += framedata[dcounter]*poses[i].channelscale[1];
|
||||||
|
dcounter++;
|
||||||
|
}
|
||||||
|
|
||||||
|
animation.framePoses[frame][i].translation.z = poses[i].channeloffset[2];
|
||||||
|
|
||||||
|
if (poses[i].mask & 0x04)
|
||||||
|
{
|
||||||
|
animation.framePoses[frame][i].translation.z += framedata[dcounter]*poses[i].channelscale[2];
|
||||||
|
dcounter++;
|
||||||
|
}
|
||||||
|
|
||||||
|
animation.framePoses[frame][i].rotation.x = poses[i].channeloffset[3];
|
||||||
|
|
||||||
|
if (poses[i].mask & 0x08)
|
||||||
|
{
|
||||||
|
animation.framePoses[frame][i].rotation.x += framedata[dcounter]*poses[i].channelscale[3];
|
||||||
|
dcounter++;
|
||||||
|
}
|
||||||
|
|
||||||
|
animation.framePoses[frame][i].rotation.y = poses[i].channeloffset[4];
|
||||||
|
|
||||||
|
if (poses[i].mask & 0x10)
|
||||||
|
{
|
||||||
|
animation.framePoses[frame][i].rotation.y += framedata[dcounter]*poses[i].channelscale[4];
|
||||||
|
dcounter++;
|
||||||
|
}
|
||||||
|
|
||||||
|
animation.framePoses[frame][i].rotation.z = poses[i].channeloffset[5];
|
||||||
|
|
||||||
|
if (poses[i].mask & 0x20)
|
||||||
|
{
|
||||||
|
animation.framePoses[frame][i].rotation.z += framedata[dcounter]*poses[i].channelscale[5];
|
||||||
|
dcounter++;
|
||||||
|
}
|
||||||
|
|
||||||
|
animation.framePoses[frame][i].rotation.w = poses[i].channeloffset[6];
|
||||||
|
|
||||||
|
if (poses[i].mask & 0x40)
|
||||||
|
{
|
||||||
|
animation.framePoses[frame][i].rotation.w += framedata[dcounter]*poses[i].channelscale[6];
|
||||||
|
dcounter++;
|
||||||
|
}
|
||||||
|
|
||||||
|
animation.framePoses[frame][i].scale.x = poses[i].channeloffset[7];
|
||||||
|
|
||||||
|
if (poses[i].mask & 0x80)
|
||||||
|
{
|
||||||
|
animation.framePoses[frame][i].scale.x += framedata[dcounter]*poses[i].channelscale[7];
|
||||||
|
dcounter++;
|
||||||
|
}
|
||||||
|
|
||||||
|
animation.framePoses[frame][i].scale.y = poses[i].channeloffset[8];
|
||||||
|
|
||||||
|
if (poses[i].mask & 0x100)
|
||||||
|
{
|
||||||
|
animation.framePoses[frame][i].scale.y += framedata[dcounter]*poses[i].channelscale[8];
|
||||||
|
dcounter++;
|
||||||
|
}
|
||||||
|
|
||||||
|
animation.framePoses[frame][i].scale.z = poses[i].channeloffset[9];
|
||||||
|
|
||||||
|
if (poses[i].mask & 0x200)
|
||||||
|
{
|
||||||
|
animation.framePoses[frame][i].scale.z += framedata[dcounter]*poses[i].channelscale[9];
|
||||||
|
dcounter++;
|
||||||
|
}
|
||||||
|
|
||||||
|
animation.framePoses[frame][i].rotation = QuaternionNormalize(animation.framePoses[frame][i].rotation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build frameposes
|
||||||
|
for (int frame = 0; frame < anim.num_frames; frame++)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < animation.boneCount; i++)
|
||||||
|
{
|
||||||
|
if (animation.bones[i].parent >= 0)
|
||||||
|
{
|
||||||
|
animation.framePoses[frame][i].rotation = QuaternionMultiply(animation.framePoses[frame][animation.bones[i].parent].rotation, animation.framePoses[frame][i].rotation);
|
||||||
|
animation.framePoses[frame][i].translation = Vector3RotateByQuaternion(animation.framePoses[frame][i].translation, animation.framePoses[frame][animation.bones[i].parent].rotation);
|
||||||
|
animation.framePoses[frame][i].translation = Vector3Add(animation.framePoses[frame][i].translation, animation.framePoses[frame][animation.bones[i].parent].translation);
|
||||||
|
animation.framePoses[frame][i].scale = Vector3MultiplyV(animation.framePoses[frame][i].scale, animation.framePoses[frame][animation.bones[i].parent].scale);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
free(framedata);
|
||||||
|
free(poses);
|
||||||
|
|
||||||
|
fclose(iqmFile);
|
||||||
|
|
||||||
|
animations[0] = animation;
|
||||||
|
|
||||||
|
*animCount = count;
|
||||||
|
return animations;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update model animated vertex data (positions and normals) for a given frame
|
||||||
|
// NOTE: Updated data is uploaded to GPU
|
||||||
|
void UpdateModelAnimation(Model model, ModelAnimation anim, int frame)
|
||||||
|
{
|
||||||
|
if (frame >= anim.frameCount) frame = frame%anim.frameCount;
|
||||||
|
|
||||||
|
for (int m = 0; m < model.meshCount; m++)
|
||||||
|
{
|
||||||
|
Vector3 animVertex = { 0 };
|
||||||
|
Vector3 animNormal = { 0 };
|
||||||
|
|
||||||
|
Vector3 inTranslation = { 0 };
|
||||||
|
Quaternion inRotation = { 0 };
|
||||||
|
Vector3 inScale = { 0 };
|
||||||
|
|
||||||
|
Vector3 outTranslation = { 0 };
|
||||||
|
Quaternion outRotation = { 0 };
|
||||||
|
Vector3 outScale = { 0 };
|
||||||
|
|
||||||
|
int vCounter = 0;
|
||||||
|
int boneCounter = 0;
|
||||||
|
int boneId = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < model.meshes[m].vertexCount; i++)
|
||||||
|
{
|
||||||
|
boneId = model.meshes[m].boneIds[boneCounter];
|
||||||
|
inTranslation = model.bindPose[boneId].translation;
|
||||||
|
inRotation = model.bindPose[boneId].rotation;
|
||||||
|
inScale = model.bindPose[boneId].scale;
|
||||||
|
outTranslation = anim.framePoses[frame][boneId].translation;
|
||||||
|
outRotation = anim.framePoses[frame][boneId].rotation;
|
||||||
|
outScale = anim.framePoses[frame][boneId].scale;
|
||||||
|
|
||||||
|
// Vertices processing
|
||||||
|
// NOTE: We use meshes.vertices (default vertex position) to calculate meshes.animVertices (animated vertex position)
|
||||||
|
animVertex = (Vector3){ model.meshes[m].vertices[vCounter], model.meshes[m].vertices[vCounter + 1], model.meshes[m].vertices[vCounter + 2] };
|
||||||
|
animVertex = Vector3MultiplyV(animVertex, outScale);
|
||||||
|
animVertex = Vector3Subtract(animVertex, inTranslation);
|
||||||
|
animVertex = Vector3RotateByQuaternion(animVertex, QuaternionMultiply(outRotation, QuaternionInvert(inRotation)));
|
||||||
|
animVertex = Vector3Add(animVertex, outTranslation);
|
||||||
|
model.meshes[m].animVertices[vCounter] = animVertex.x;
|
||||||
|
model.meshes[m].animVertices[vCounter + 1] = animVertex.y;
|
||||||
|
model.meshes[m].animVertices[vCounter + 2] = animVertex.z;
|
||||||
|
|
||||||
|
// Normals processing
|
||||||
|
// NOTE: We use meshes.baseNormals (default normal) to calculate meshes.normals (animated normals)
|
||||||
|
animNormal = (Vector3){ model.meshes[m].normals[vCounter], model.meshes[m].normals[vCounter + 1], model.meshes[m].normals[vCounter + 2] };
|
||||||
|
animNormal = Vector3RotateByQuaternion(animNormal, QuaternionMultiply(outRotation, QuaternionInvert(inRotation)));
|
||||||
|
model.meshes[m].animNormals[vCounter] = animNormal.x;
|
||||||
|
model.meshes[m].animNormals[vCounter + 1] = animNormal.y;
|
||||||
|
model.meshes[m].animNormals[vCounter + 2] = animNormal.z;
|
||||||
|
vCounter += 3;
|
||||||
|
|
||||||
|
boneCounter += 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Upload new vertex data to GPU for model drawing
|
||||||
|
rlUpdateBuffer(model.meshes[m].vboId[0], model.meshes[m].animVertices, model.meshes[m].vertexCount*3*sizeof(float)); // Update vertex position
|
||||||
|
rlUpdateBuffer(model.meshes[m].vboId[2], model.meshes[m].animVertices, model.meshes[m].vertexCount*3*sizeof(float)); // Update vertex normals
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unload animation data
|
||||||
|
void UnloadModelAnimation(ModelAnimation anim)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < anim.frameCount; i++) free(anim.framePoses[i]);
|
||||||
|
|
||||||
|
free(anim.bones);
|
||||||
|
free(anim.framePoses);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check model animation skeleton match
|
||||||
|
// NOTE: Only number of bones and parent connections are checked
|
||||||
|
bool IsModelAnimationValid(Model model, ModelAnimation anim)
|
||||||
|
{
|
||||||
|
int result = true;
|
||||||
|
|
||||||
|
if (model.boneCount != anim.boneCount) result = false;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for (int i = 0; i < model.boneCount; i++)
|
||||||
|
{
|
||||||
|
if (model.bones[i].parent != anim.bones[i].parent) { result = false; break; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
#if defined(SUPPORT_MESH_GENERATION)
|
#if defined(SUPPORT_MESH_GENERATION)
|
||||||
// Generate polygonal mesh
|
// Generate polygonal mesh
|
||||||
Mesh GenMeshPoly(int sides, float radius)
|
Mesh GenMeshPoly(int sides, float radius)
|
||||||
|
@ -1807,59 +2199,124 @@ Mesh GenMeshCubicmap(Image cubicmap, Vector3 cubeSize)
|
||||||
}
|
}
|
||||||
#endif // SUPPORT_MESH_GENERATION
|
#endif // SUPPORT_MESH_GENERATION
|
||||||
|
|
||||||
// Load material data (from file)
|
// Compute mesh bounding box limits
|
||||||
Material LoadMaterial(const char *fileName)
|
// NOTE: minVertex and maxVertex should be transformed by model transform matrix
|
||||||
|
BoundingBox MeshBoundingBox(Mesh mesh)
|
||||||
{
|
{
|
||||||
Material material = { 0 };
|
// Get min and max vertex to construct bounds (AABB)
|
||||||
|
Vector3 minVertex = { 0 };
|
||||||
|
Vector3 maxVertex = { 0 };
|
||||||
|
|
||||||
#if defined(SUPPORT_FILEFORMAT_MTL)
|
if (mesh.vertices != NULL)
|
||||||
if (IsFileExtension(fileName, ".mtl"))
|
|
||||||
{
|
{
|
||||||
tinyobj_material_t *materials;
|
minVertex = (Vector3){ mesh.vertices[0], mesh.vertices[1], mesh.vertices[2] };
|
||||||
unsigned int materialCount = 0;
|
maxVertex = (Vector3){ mesh.vertices[0], mesh.vertices[1], mesh.vertices[2] };
|
||||||
|
|
||||||
int result = tinyobj_parse_mtl_file(&materials, &materialCount, fileName);
|
for (int i = 1; i < mesh.vertexCount; i++)
|
||||||
|
{
|
||||||
// TODO: Process materials to return
|
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] });
|
||||||
tinyobj_materials_free(materials, materialCount);
|
}
|
||||||
}
|
}
|
||||||
#else
|
|
||||||
TraceLog(LOG_WARNING, "[%s] Material fileformat not supported, it can't be loaded", fileName);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Our material uses the default shader (DIFFUSE, SPECULAR, NORMAL)
|
// Create the bounding box
|
||||||
material.shader = GetShaderDefault();
|
BoundingBox box = { 0 };
|
||||||
|
box.min = minVertex;
|
||||||
|
box.max = maxVertex;
|
||||||
|
|
||||||
return material;
|
return box;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load default material (Supports: DIFFUSE, SPECULAR, NORMAL maps)
|
// Compute mesh tangents
|
||||||
Material LoadMaterialDefault(void)
|
// NOTE: To calculate mesh tangents and binormals we need mesh vertex positions and texture coordinates
|
||||||
|
// Implementation base don: https://answers.unity.com/questions/7789/calculating-tangents-vector4.html
|
||||||
|
void MeshTangents(Mesh *mesh)
|
||||||
{
|
{
|
||||||
Material material = { 0 };
|
if (mesh->tangents == NULL) mesh->tangents = (float *)malloc(mesh->vertexCount*4*sizeof(float));
|
||||||
|
else TraceLog(LOG_WARNING, "Mesh tangents already exist");
|
||||||
|
|
||||||
material.shader = GetShaderDefault();
|
Vector3 *tan1 = (Vector3 *)malloc(mesh->vertexCount*sizeof(Vector3));
|
||||||
material.maps[MAP_DIFFUSE].texture = GetTextureDefault(); // White texture (1x1 pixel)
|
Vector3 *tan2 = (Vector3 *)malloc(mesh->vertexCount*sizeof(Vector3));
|
||||||
//material.maps[MAP_NORMAL].texture; // NOTE: By default, not set
|
|
||||||
//material.maps[MAP_SPECULAR].texture; // NOTE: By default, not set
|
|
||||||
|
|
||||||
material.maps[MAP_DIFFUSE].color = WHITE; // Diffuse color
|
for (int i = 0; i < mesh->vertexCount; i += 3)
|
||||||
material.maps[MAP_SPECULAR].color = WHITE; // Specular color
|
|
||||||
|
|
||||||
return material;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unload material from memory
|
|
||||||
void UnloadMaterial(Material material)
|
|
||||||
{
|
|
||||||
// Unload material shader (avoid unloading default shader, managed by raylib)
|
|
||||||
if (material.shader.id != GetShaderDefault().id) UnloadShader(material.shader);
|
|
||||||
|
|
||||||
// Unload loaded texture maps (avoid unloading default texture, managed by raylib)
|
|
||||||
for (int i = 0; i < MAX_MATERIAL_MAPS; i++)
|
|
||||||
{
|
{
|
||||||
if (material.maps[i].texture.id != GetTextureDefault().id) rlDeleteTextures(material.maps[i].texture.id);
|
// Get triangle vertices
|
||||||
|
Vector3 v1 = { mesh->vertices[(i + 0)*3 + 0], mesh->vertices[(i + 0)*3 + 1], mesh->vertices[(i + 0)*3 + 2] };
|
||||||
|
Vector3 v2 = { mesh->vertices[(i + 1)*3 + 0], mesh->vertices[(i + 1)*3 + 1], mesh->vertices[(i + 1)*3 + 2] };
|
||||||
|
Vector3 v3 = { mesh->vertices[(i + 2)*3 + 0], mesh->vertices[(i + 2)*3 + 1], mesh->vertices[(i + 2)*3 + 2] };
|
||||||
|
|
||||||
|
// Get triangle texcoords
|
||||||
|
Vector2 uv1 = { mesh->texcoords[(i + 0)*2 + 0], mesh->texcoords[(i + 0)*2 + 1] };
|
||||||
|
Vector2 uv2 = { mesh->texcoords[(i + 1)*2 + 0], mesh->texcoords[(i + 1)*2 + 1] };
|
||||||
|
Vector2 uv3 = { mesh->texcoords[(i + 2)*2 + 0], mesh->texcoords[(i + 2)*2 + 1] };
|
||||||
|
|
||||||
|
float x1 = v2.x - v1.x;
|
||||||
|
float y1 = v2.y - v1.y;
|
||||||
|
float z1 = v2.z - v1.z;
|
||||||
|
float x2 = v3.x - v1.x;
|
||||||
|
float y2 = v3.y - v1.y;
|
||||||
|
float z2 = v3.z - v1.z;
|
||||||
|
|
||||||
|
float s1 = uv2.x - uv1.x;
|
||||||
|
float t1 = uv2.y - uv1.y;
|
||||||
|
float s2 = uv3.x - uv1.x;
|
||||||
|
float t2 = uv3.y - uv1.y;
|
||||||
|
|
||||||
|
float div = s1*t2 - s2*t1;
|
||||||
|
float r = (div == 0.0f)? 0.0f : 1.0f/div;
|
||||||
|
|
||||||
|
Vector3 sdir = { (t2*x1 - t1*x2)*r, (t2*y1 - t1*y2)*r, (t2*z1 - t1*z2)*r };
|
||||||
|
Vector3 tdir = { (s1*x2 - s2*x1)*r, (s1*y2 - s2*y1)*r, (s1*z2 - s2*z1)*r };
|
||||||
|
|
||||||
|
tan1[i + 0] = sdir;
|
||||||
|
tan1[i + 1] = sdir;
|
||||||
|
tan1[i + 2] = sdir;
|
||||||
|
|
||||||
|
tan2[i + 0] = tdir;
|
||||||
|
tan2[i + 1] = tdir;
|
||||||
|
tan2[i + 2] = tdir;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute tangents considering normals
|
||||||
|
for (int i = 0; i < mesh->vertexCount; ++i)
|
||||||
|
{
|
||||||
|
Vector3 normal = { mesh->normals[i*3 + 0], mesh->normals[i*3 + 1], mesh->normals[i*3 + 2] };
|
||||||
|
Vector3 tangent = tan1[i];
|
||||||
|
|
||||||
|
// TODO: Review, not sure if tangent computation is right, just used reference proposed maths...
|
||||||
|
#if defined(COMPUTE_TANGENTS_METHOD_01)
|
||||||
|
Vector3 tmp = Vector3Subtract(tangent, Vector3Multiply(normal, Vector3DotProduct(normal, tangent)));
|
||||||
|
tmp = Vector3Normalize(tmp);
|
||||||
|
mesh->tangents[i*4 + 0] = tmp.x;
|
||||||
|
mesh->tangents[i*4 + 1] = tmp.y;
|
||||||
|
mesh->tangents[i*4 + 2] = tmp.z;
|
||||||
|
mesh->tangents[i*4 + 3] = 1.0f;
|
||||||
|
#else
|
||||||
|
Vector3OrthoNormalize(&normal, &tangent);
|
||||||
|
mesh->tangents[i*4 + 0] = tangent.x;
|
||||||
|
mesh->tangents[i*4 + 1] = tangent.y;
|
||||||
|
mesh->tangents[i*4 + 2] = tangent.z;
|
||||||
|
mesh->tangents[i*4 + 3] = (Vector3DotProduct(Vector3CrossProduct(normal, tangent), tan2[i]) < 0.0f)? -1.0f : 1.0f;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
free(tan1);
|
||||||
|
free(tan2);
|
||||||
|
|
||||||
|
TraceLog(LOG_INFO, "Tangents computed for mesh");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute mesh binormals (aka bitangent)
|
||||||
|
void MeshBinormals(Mesh *mesh)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < mesh->vertexCount; i++)
|
||||||
|
{
|
||||||
|
Vector3 normal = { mesh->normals[i*3 + 0], mesh->normals[i*3 + 1], mesh->normals[i*3 + 2] };
|
||||||
|
Vector3 tangent = { mesh->tangents[i*4 + 0], mesh->tangents[i*4 + 1], mesh->tangents[i*4 + 2] };
|
||||||
|
float tangentW = mesh->tangents[i*4 + 3];
|
||||||
|
|
||||||
|
// TODO: Register computed binormal in mesh->binormal?
|
||||||
|
// Vector3 binormal = Vector3Multiply(Vector3CrossProduct(normal, tangent), tangentW);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2239,129 +2696,6 @@ RayHitInfo GetCollisionRayGround(Ray ray, float groundHeight)
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compute mesh bounding box limits
|
|
||||||
// NOTE: minVertex and maxVertex should be transformed by model transform matrix
|
|
||||||
BoundingBox MeshBoundingBox(Mesh mesh)
|
|
||||||
{
|
|
||||||
// Get min and max vertex to construct bounds (AABB)
|
|
||||||
Vector3 minVertex = { 0 };
|
|
||||||
Vector3 maxVertex = { 0 };
|
|
||||||
|
|
||||||
printf("Mesh vertex count: %i\n", mesh.vertexCount);
|
|
||||||
|
|
||||||
if (mesh.vertices != NULL)
|
|
||||||
{
|
|
||||||
minVertex = (Vector3){ mesh.vertices[0], mesh.vertices[1], mesh.vertices[2] };
|
|
||||||
maxVertex = (Vector3){ mesh.vertices[0], mesh.vertices[1], mesh.vertices[2] };
|
|
||||||
|
|
||||||
for (int i = 1; i < mesh.vertexCount; i++)
|
|
||||||
{
|
|
||||||
minVertex = Vector3Min(minVertex, (Vector3){ mesh.vertices[i*3], mesh.vertices[i*3 + 1], mesh.vertices[i*3 + 2] });
|
|
||||||
maxVertex = Vector3Max(maxVertex, (Vector3){ mesh.vertices[i*3], mesh.vertices[i*3 + 1], mesh.vertices[i*3 + 2] });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create the bounding box
|
|
||||||
BoundingBox box = { 0 };
|
|
||||||
box.min = minVertex;
|
|
||||||
box.max = maxVertex;
|
|
||||||
|
|
||||||
return box;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compute mesh tangents
|
|
||||||
// NOTE: To calculate mesh tangents and binormals we need mesh vertex positions and texture coordinates
|
|
||||||
// Implementation base don: https://answers.unity.com/questions/7789/calculating-tangents-vector4.html
|
|
||||||
void MeshTangents(Mesh *mesh)
|
|
||||||
{
|
|
||||||
if (mesh->tangents == NULL) mesh->tangents = (float *)malloc(mesh->vertexCount*4*sizeof(float));
|
|
||||||
else TraceLog(LOG_WARNING, "Mesh tangents already exist");
|
|
||||||
|
|
||||||
Vector3 *tan1 = (Vector3 *)malloc(mesh->vertexCount*sizeof(Vector3));
|
|
||||||
Vector3 *tan2 = (Vector3 *)malloc(mesh->vertexCount*sizeof(Vector3));
|
|
||||||
|
|
||||||
for (int i = 0; i < mesh->vertexCount; i += 3)
|
|
||||||
{
|
|
||||||
// Get triangle vertices
|
|
||||||
Vector3 v1 = { mesh->vertices[(i + 0)*3 + 0], mesh->vertices[(i + 0)*3 + 1], mesh->vertices[(i + 0)*3 + 2] };
|
|
||||||
Vector3 v2 = { mesh->vertices[(i + 1)*3 + 0], mesh->vertices[(i + 1)*3 + 1], mesh->vertices[(i + 1)*3 + 2] };
|
|
||||||
Vector3 v3 = { mesh->vertices[(i + 2)*3 + 0], mesh->vertices[(i + 2)*3 + 1], mesh->vertices[(i + 2)*3 + 2] };
|
|
||||||
|
|
||||||
// Get triangle texcoords
|
|
||||||
Vector2 uv1 = { mesh->texcoords[(i + 0)*2 + 0], mesh->texcoords[(i + 0)*2 + 1] };
|
|
||||||
Vector2 uv2 = { mesh->texcoords[(i + 1)*2 + 0], mesh->texcoords[(i + 1)*2 + 1] };
|
|
||||||
Vector2 uv3 = { mesh->texcoords[(i + 2)*2 + 0], mesh->texcoords[(i + 2)*2 + 1] };
|
|
||||||
|
|
||||||
float x1 = v2.x - v1.x;
|
|
||||||
float y1 = v2.y - v1.y;
|
|
||||||
float z1 = v2.z - v1.z;
|
|
||||||
float x2 = v3.x - v1.x;
|
|
||||||
float y2 = v3.y - v1.y;
|
|
||||||
float z2 = v3.z - v1.z;
|
|
||||||
|
|
||||||
float s1 = uv2.x - uv1.x;
|
|
||||||
float t1 = uv2.y - uv1.y;
|
|
||||||
float s2 = uv3.x - uv1.x;
|
|
||||||
float t2 = uv3.y - uv1.y;
|
|
||||||
|
|
||||||
float div = s1*t2 - s2*t1;
|
|
||||||
float r = (div == 0.0f)? 0.0f : 1.0f/div;
|
|
||||||
|
|
||||||
Vector3 sdir = { (t2*x1 - t1*x2)*r, (t2*y1 - t1*y2)*r, (t2*z1 - t1*z2)*r };
|
|
||||||
Vector3 tdir = { (s1*x2 - s2*x1)*r, (s1*y2 - s2*y1)*r, (s1*z2 - s2*z1)*r };
|
|
||||||
|
|
||||||
tan1[i + 0] = sdir;
|
|
||||||
tan1[i + 1] = sdir;
|
|
||||||
tan1[i + 2] = sdir;
|
|
||||||
|
|
||||||
tan2[i + 0] = tdir;
|
|
||||||
tan2[i + 1] = tdir;
|
|
||||||
tan2[i + 2] = tdir;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compute tangents considering normals
|
|
||||||
for (int i = 0; i < mesh->vertexCount; ++i)
|
|
||||||
{
|
|
||||||
Vector3 normal = { mesh->normals[i*3 + 0], mesh->normals[i*3 + 1], mesh->normals[i*3 + 2] };
|
|
||||||
Vector3 tangent = tan1[i];
|
|
||||||
|
|
||||||
// TODO: Review, not sure if tangent computation is right, just used reference proposed maths...
|
|
||||||
#if defined(COMPUTE_TANGENTS_METHOD_01)
|
|
||||||
Vector3 tmp = Vector3Subtract(tangent, Vector3Multiply(normal, Vector3DotProduct(normal, tangent)));
|
|
||||||
tmp = Vector3Normalize(tmp);
|
|
||||||
mesh->tangents[i*4 + 0] = tmp.x;
|
|
||||||
mesh->tangents[i*4 + 1] = tmp.y;
|
|
||||||
mesh->tangents[i*4 + 2] = tmp.z;
|
|
||||||
mesh->tangents[i*4 + 3] = 1.0f;
|
|
||||||
#else
|
|
||||||
Vector3OrthoNormalize(&normal, &tangent);
|
|
||||||
mesh->tangents[i*4 + 0] = tangent.x;
|
|
||||||
mesh->tangents[i*4 + 1] = tangent.y;
|
|
||||||
mesh->tangents[i*4 + 2] = tangent.z;
|
|
||||||
mesh->tangents[i*4 + 3] = (Vector3DotProduct(Vector3CrossProduct(normal, tangent), tan2[i]) < 0.0f)? -1.0f : 1.0f;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
free(tan1);
|
|
||||||
free(tan2);
|
|
||||||
|
|
||||||
TraceLog(LOG_INFO, "Tangents computed for mesh");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compute mesh binormals (aka bitangent)
|
|
||||||
void MeshBinormals(Mesh *mesh)
|
|
||||||
{
|
|
||||||
for (int i = 0; i < mesh->vertexCount; i++)
|
|
||||||
{
|
|
||||||
Vector3 normal = { mesh->normals[i*3 + 0], mesh->normals[i*3 + 1], mesh->normals[i*3 + 2] };
|
|
||||||
Vector3 tangent = { mesh->tangents[i*4 + 0], mesh->tangents[i*4 + 1], mesh->tangents[i*4 + 2] };
|
|
||||||
float tangentW = mesh->tangents[i*4 + 3];
|
|
||||||
|
|
||||||
// TODO: Register computed binormal in mesh->binormal?
|
|
||||||
// Vector3 binormal = Vector3Multiply(Vector3CrossProduct(normal, tangent), tangentW);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//----------------------------------------------------------------------------------
|
//----------------------------------------------------------------------------------
|
||||||
// Module specific Functions Definition
|
// Module specific Functions Definition
|
||||||
//----------------------------------------------------------------------------------
|
//----------------------------------------------------------------------------------
|
||||||
|
|
33
src/raylib.h
33
src/raylib.h
|
@ -752,7 +752,7 @@ typedef enum {
|
||||||
MAP_IRRADIANCE, // NOTE: Uses GL_TEXTURE_CUBE_MAP
|
MAP_IRRADIANCE, // NOTE: Uses GL_TEXTURE_CUBE_MAP
|
||||||
MAP_PREFILTER, // NOTE: Uses GL_TEXTURE_CUBE_MAP
|
MAP_PREFILTER, // NOTE: Uses GL_TEXTURE_CUBE_MAP
|
||||||
MAP_BRDF
|
MAP_BRDF
|
||||||
} TexmapIndex;
|
} MaterialMapType;
|
||||||
|
|
||||||
#define MAP_DIFFUSE MAP_ALBEDO
|
#define MAP_DIFFUSE MAP_ALBEDO
|
||||||
#define MAP_SPECULAR MAP_METALNESS
|
#define MAP_SPECULAR MAP_METALNESS
|
||||||
|
@ -1256,16 +1256,25 @@ RLAPI void DrawGizmo(Vector3 position);
|
||||||
// Model loading/unloading functions
|
// Model loading/unloading functions
|
||||||
RLAPI Model LoadModel(const char *fileName); // Load model from files (meshes and materials)
|
RLAPI Model LoadModel(const char *fileName); // Load model from files (meshes and materials)
|
||||||
RLAPI Model LoadModelFromMesh(Mesh mesh); // Load model from generated mesh
|
RLAPI Model LoadModelFromMesh(Mesh mesh); // Load model from generated mesh
|
||||||
//RLAPI void LoadModelAnimations(const char fileName, ModelAnimation *anims, int *animsCount); // Load model animations from file
|
|
||||||
//RLAPI void UpdateModelAnimation(Model model, ModelAnimation anim, int frame); // Update model animation pose
|
|
||||||
RLAPI void UnloadModel(Model model); // Unload model from memory (RAM and/or VRAM)
|
RLAPI void UnloadModel(Model model); // Unload model from memory (RAM and/or VRAM)
|
||||||
|
|
||||||
// Mesh manipulation functions
|
// Mesh loading/unloading functions
|
||||||
RLAPI BoundingBox MeshBoundingBox(Mesh mesh); // Compute mesh bounding box limits
|
RLAPI Mesh *LoadMeshes(const char *fileName, int *meshCount); // Load meshes from model file
|
||||||
RLAPI void MeshTangents(Mesh *mesh); // Compute mesh tangents
|
|
||||||
RLAPI void MeshBinormals(Mesh *mesh); // Compute mesh binormals
|
|
||||||
RLAPI void UnloadMesh(Mesh *mesh); // Unload mesh from memory (RAM and/or VRAM)
|
|
||||||
RLAPI void ExportMesh(Mesh mesh, const char *fileName); // Export mesh data to file
|
RLAPI void ExportMesh(Mesh mesh, const char *fileName); // Export mesh data to file
|
||||||
|
RLAPI void UnloadMesh(Mesh *mesh); // Unload mesh from memory (RAM and/or VRAM)
|
||||||
|
|
||||||
|
// Material loading/unloading functions
|
||||||
|
RLAPI Material *LoadMaterials(const char *fileName, int *materialCount); // Load materials from model file
|
||||||
|
RLAPI Material LoadMaterialDefault(void); // Load default material (Supports: DIFFUSE, SPECULAR, NORMAL maps)
|
||||||
|
RLAPI void UnloadMaterial(Material material); // Unload material from GPU memory (VRAM)
|
||||||
|
RLAPI void SetMaterialTexture(Material *material, int mapType, Texture2D texture); // Set texture for a material map type (MAP_DIFFUSE, MAP_SPECULAR...)
|
||||||
|
RLAPI void SetModelMeshMaterial(Model *model, int meshId, int materialId); // Set material for a mesh
|
||||||
|
|
||||||
|
// Model animations loading/unloading functions
|
||||||
|
RLAPI ModelAnimation *LoadModelAnimations(const char *fileName, int *animsCount); // Load model animations from file
|
||||||
|
RLAPI void UpdateModelAnimation(Model model, ModelAnimation anim, int frame); // Update model animation pose
|
||||||
|
RLAPI void UnloadModelAnimation(ModelAnimation anim); // Unload animation data
|
||||||
|
RLAPI bool IsModelAnimationValid(Model model, ModelAnimation anim); // Check model animation skeleton match
|
||||||
|
|
||||||
// Mesh generation functions
|
// Mesh generation functions
|
||||||
RLAPI Mesh GenMeshPoly(int sides, float radius); // Generate polygonal mesh
|
RLAPI Mesh GenMeshPoly(int sides, float radius); // Generate polygonal mesh
|
||||||
|
@ -1279,10 +1288,10 @@ RLAPI Mesh GenMeshKnot(float radius, float size, int radSeg, int sides);
|
||||||
RLAPI Mesh GenMeshHeightmap(Image heightmap, Vector3 size); // Generate heightmap mesh from image data
|
RLAPI Mesh GenMeshHeightmap(Image heightmap, Vector3 size); // Generate heightmap mesh from image data
|
||||||
RLAPI Mesh GenMeshCubicmap(Image cubicmap, Vector3 cubeSize); // Generate cubes-based map mesh from image data
|
RLAPI Mesh GenMeshCubicmap(Image cubicmap, Vector3 cubeSize); // Generate cubes-based map mesh from image data
|
||||||
|
|
||||||
// Material loading/unloading functions
|
// Mesh manipulation functions
|
||||||
RLAPI Material LoadMaterial(const char *fileName); // Load material from file
|
RLAPI BoundingBox MeshBoundingBox(Mesh mesh); // Compute mesh bounding box limits
|
||||||
RLAPI Material LoadMaterialDefault(void); // Load default material (Supports: DIFFUSE, SPECULAR, NORMAL maps)
|
RLAPI void MeshTangents(Mesh *mesh); // Compute mesh tangents
|
||||||
RLAPI void UnloadMaterial(Material material); // Unload material from GPU memory (VRAM)
|
RLAPI void MeshBinormals(Mesh *mesh); // Compute mesh binormals
|
||||||
|
|
||||||
// Model drawing functions
|
// Model drawing functions
|
||||||
RLAPI void DrawModel(Model model, Vector3 position, float scale, Color tint); // Draw a model (with texture if set)
|
RLAPI void DrawModel(Model model, Vector3 position, float scale, Color tint); // Draw a model (with texture if set)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue