06be400ca3
Just reviewing for a possible adaptation of AnimatedModel to default raylib Model...
834 lines
29 KiB
C
834 lines
29 KiB
C
/**********************************************************************************************
|
|
*
|
|
* riqm - InterQuake Model format (IQM) loader for animated meshes
|
|
*
|
|
* CONFIGURATION:
|
|
*
|
|
* #define RIQM_IMPLEMENTATION
|
|
* Generates the implementation of the library into the included file.
|
|
* If not defined, the library is in header only mode and can be included in other headers
|
|
* or source files without problems. But only ONE file should hold the implementation.
|
|
*
|
|
*
|
|
* LICENSE: zlib/libpng
|
|
*
|
|
* Copyright (c) 2018 Jonas Daeyaert (@culacant) and Ramon Santamaria (@raysan5)
|
|
*
|
|
* This software is provided "as-is", without any express or implied warranty. In no event
|
|
* will the authors be held liable for any damages arising from the use of this software.
|
|
*
|
|
* Permission is granted to anyone to use this software for any purpose, including commercial
|
|
* applications, and to alter it and redistribute it freely, subject to the following restrictions:
|
|
*
|
|
* 1. The origin of this software must not be misrepresented; you must not claim that you
|
|
* wrote the original software. If you use this software in a product, an acknowledgment
|
|
* in the product documentation would be appreciated but is not required.
|
|
*
|
|
* 2. Altered source versions must be plainly marked as such, and must not be misrepresented
|
|
* as being the original software.
|
|
*
|
|
* 3. This notice may not be removed or altered from any source distribution.
|
|
*
|
|
**********************************************************************************************/
|
|
|
|
#ifndef RIQM_H
|
|
#define RIQM_H
|
|
|
|
//#define RIQM_STATIC
|
|
#ifdef RIQM_STATIC
|
|
#define RIQMDEF static // Functions just visible to module including this file
|
|
#else
|
|
#ifdef __cplusplus
|
|
#define RIQMDEF extern "C" // Functions visible from other files (no name mangling of functions in C++)
|
|
#else
|
|
#define RIQMDEF extern // Functions visible from other files
|
|
#endif
|
|
#endif
|
|
|
|
//----------------------------------------------------------------------------------
|
|
// Types and Structures Definition
|
|
//----------------------------------------------------------------------------------
|
|
|
|
#define JOINT_NAME_LENGTH 32 // Joint 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
|
|
//----------------------------------------------------------------------------------
|
|
|
|
// 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
|
|
|
|
|
|
/***********************************************************************************
|
|
*
|
|
* RIQM IMPLEMENTATION
|
|
*
|
|
************************************************************************************/
|
|
|
|
#if defined(RIQM_IMPLEMENTATION)
|
|
|
|
//#include "utils.h" // Required for: fopen() Android mapping
|
|
|
|
#include <stdio.h> // Required for: FILE, fopen(), fclose(), feof(), fseek(), fread()
|
|
#include <stdlib.h> // Required for: malloc(), free()
|
|
#include <string.h> // Required for: strncmp(),strcpy()
|
|
|
|
#include "raymath.h" // Required for: Vector3, Quaternion functions
|
|
|
|
//----------------------------------------------------------------------------------
|
|
// 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
|
|
//----------------------------------------------------------------------------------
|
|
|
|
//----------------------------------------------------------------------------------
|
|
// Module specific Functions Declaration
|
|
//----------------------------------------------------------------------------------
|
|
static AnimatedModel LoadIQM(const char *filename);
|
|
|
|
#ifdef __cplusplus
|
|
extern "C" { // Prevents name mangling of functions
|
|
#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
|