diff --git a/examples/others/iqm_loader/models_iqm_animation.c b/examples/models/models_animation.c similarity index 61% rename from examples/others/iqm_loader/models_iqm_animation.c rename to examples/models/models_animation.c index 18dd8577b..a75241b37 100644 --- a/examples/others/iqm_loader/models_iqm_animation.c +++ b/examples/models/models_animation.c @@ -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) * -* Copyright (c) 2018 @culacant and @raysan5 +* Copyright (c) 2019 Ramon Santamaria (@raysan5) and @culacant * ********************************************************************************************/ #include "raylib.h" -#define RIQM_IMPLEMENTATION -#include "riqm.h" - int main() { // Initialization @@ -21,7 +18,7 @@ int main() int screenWidth = 800; 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 Camera camera = { 0 }; @@ -31,26 +28,25 @@ int main() camera.fovy = 45.0f; // Camera field-of-view Y 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 - // NOTE: There is only 1 mesh and 1 material (both at index 0), thats what the 2 0's are - model = AnimatedModelAddTexture(model, "resources/guytex.png"); // REPLACE! - model = SetMeshMaterial(model, 0, 0); // REPLACE! + Model model = LoadModel("resources/guy/guy.iqm"); // Load the animated model mesh and basic data + Texture2D texture = LoadTexture("resources/guy/guytex.png"); // Load model texture and set material + SetMaterialTexture(&model.materials[0], MAP_DIFFUSE, texture); // Set model material map texture + + Vector3 position = { 0.0f, 0.0f, 0.0f }; // Set model position // Load animation data - Animation anim = LoadAnimationFromIQM("resources/guyanim.iqm"); - + int animsCount = 0; + ModelAnimation *anims = LoadModelAnimations("resources/guy/guyanim.iqm", &animsCount); int animFrameCounter = 0; - SetCameraMode(camera, CAMERA_FREE); // Set free camera mode + SetCameraMode(camera, CAMERA_FREE); // Set free camera mode - SetTargetFPS(60); // Set our game to run at 60 frames-per-second + SetTargetFPS(60); // Set our game to run at 60 frames-per-second //-------------------------------------------------------------------------------------- // Main game loop - while (!WindowShouldClose()) // Detect window close button or ESC key + while (!WindowShouldClose()) // Detect window close button or ESC key { // Update //---------------------------------------------------------------------------------- @@ -60,7 +56,8 @@ int main() if (IsKeyDown(KEY_SPACE)) { 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); - 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 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); EndDrawing(); @@ -88,8 +89,10 @@ int main() // De-Initialization //-------------------------------------------------------------------------------------- - UnloadAnimation(anim); // Unload animation data - UnloadAnimatedModel(model); // Unload animated model + // Unload model animations data + for (int i = 0; i < animsCount; i++) UnloadModelAnimation(anims[i]); + + UnloadModel(model); // Unload model CloseWindow(); // Close window and OpenGL context //-------------------------------------------------------------------------------------- diff --git a/examples/others/iqm_loader/resources/guy.blend b/examples/models/resources/guy/guy.blend similarity index 100% rename from examples/others/iqm_loader/resources/guy.blend rename to examples/models/resources/guy/guy.blend diff --git a/examples/others/iqm_loader/resources/guy.iqm b/examples/models/resources/guy/guy.iqm similarity index 100% rename from examples/others/iqm_loader/resources/guy.iqm rename to examples/models/resources/guy/guy.iqm diff --git a/examples/others/iqm_loader/resources/guyanim.iqm b/examples/models/resources/guy/guyanim.iqm similarity index 100% rename from examples/others/iqm_loader/resources/guyanim.iqm rename to examples/models/resources/guy/guyanim.iqm diff --git a/examples/others/iqm_loader/resources/guytex.png b/examples/models/resources/guy/guytex.png similarity index 100% rename from examples/others/iqm_loader/resources/guytex.png rename to examples/models/resources/guy/guytex.png diff --git a/examples/others/iqm_loader/riqm.h b/examples/others/iqm_loader/riqm.h index 694e3a958..41ef8a140 100644 --- a/examples/others/iqm_loader/riqm.h +++ b/examples/others/iqm_loader/riqm.h @@ -49,67 +49,13 @@ // 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; +#define BONE_NAME_LENGTH 32 // BoneInfo name string length +#define MESH_NAME_LENGTH 32 // Mesh name string length //---------------------------------------------------------------------------------- // 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 @@ -133,90 +79,6 @@ RIQMDEF void DrawAnimatedModelEx(AnimatedModel model, Vector3 position, Vector3 //---------------------------------------------------------------------------------- // 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 @@ -225,609 +87,12 @@ typedef enum { //---------------------------------------------------------------------------------- // 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 diff --git a/src/models.c b/src/models.c index 239ab5d84..e437a0ab8 100644 --- a/src/models.c +++ b/src/models.c @@ -697,6 +697,18 @@ void UnloadModel(Model model) 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) void UnloadMesh(Mesh *mesh) { @@ -759,6 +771,386 @@ void ExportMesh(Mesh mesh, const char *fileName) else TraceLog(LOG_WARNING, "Mesh could not be exported."); } +// Load materials from model file +Material *LoadMaterials(const char *fileName, int *materialCount) +{ + Material *materials = NULL; + unsigned int count = 0; + + // TODO: Support IQM and GLTF for materials parsing + +#if defined(SUPPORT_FILEFORMAT_MTL) + if (IsFileExtension(fileName, ".mtl")) + { + tinyobj_material_t *mats; + + int result = tinyobj_parse_mtl_file(&mats, &count, fileName); + + // TODO: Process materials to return + + tinyobj_materials_free(mats, count); + } +#else + TraceLog(LOG_WARNING, "[%s] Materials file not supported", fileName); +#endif + + // Set materials shader to default (DIFFUSE, SPECULAR, NORMAL) + for (int i = 0; i < count; i++) materials[i].shader = GetShaderDefault(); + + *materialCount = count; + return materials; +} + +// Load default material (Supports: DIFFUSE, SPECULAR, NORMAL maps) +Material LoadMaterialDefault(void) +{ + Material material = { 0 }; + + material.shader = GetShaderDefault(); + material.maps[MAP_DIFFUSE].texture = GetTextureDefault(); // White texture (1x1 pixel) + //material.maps[MAP_NORMAL].texture; // NOTE: By default, not set + //material.maps[MAP_SPECULAR].texture; // NOTE: By default, not set + + material.maps[MAP_DIFFUSE].color = WHITE; // Diffuse color + material.maps[MAP_SPECULAR].color = WHITE; // Specular color + + return material; +} + +// Unload material from memory +void UnloadMaterial(Material material) +{ + // Unload material shader (avoid unloading default shader, managed by raylib) + if (material.shader.id != GetShaderDefault().id) UnloadShader(material.shader); + + // Unload loaded texture maps (avoid unloading default texture, managed by raylib) + for (int i = 0; i < MAX_MATERIAL_MAPS; i++) + { + if (material.maps[i].texture.id != GetTextureDefault().id) rlDeleteTextures(material.maps[i].texture.id); + } +} + +// Set texture for a material map type (MAP_DIFFUSE, MAP_SPECULAR...) +// NOTE: Previous texture should be manually unloaded +void SetMaterialTexture(Material *material, int mapType, Texture2D texture) +{ + material->maps[mapType].texture = texture; +} + +// Set the material for a mesh +void SetModelMeshMaterial(Model *model, int meshId, int materialId) +{ + if (meshId >= model->meshCount) TraceLog(LOG_WARNING, "Mesh id greater than mesh count"); + else if (materialId >= model->materialCount) TraceLog(LOG_WARNING,"Material id greater than material count"); + else model->meshMaterial[meshId] = materialId; +} + +// Load model animations from file +ModelAnimation *LoadModelAnimations(const char *filename, int *animCount) +{ + ModelAnimation *animations = (ModelAnimation *)malloc(1*sizeof(ModelAnimation)); + int count = 1; + + #define IQM_MAGIC "INTERQUAKEMODEL" // IQM file magic number + #define IQM_VERSION 2 // only IQM version 2 supported + + typedef struct IQMHeader { + char magic[16]; + unsigned int version; + unsigned int filesize; + unsigned int flags; + unsigned int num_text, ofs_text; + unsigned int num_meshes, ofs_meshes; + unsigned int num_vertexarrays, num_vertexes, ofs_vertexarrays; + unsigned int num_triangles, ofs_triangles, ofs_adjacency; + unsigned int num_joints, ofs_joints; + unsigned int num_poses, ofs_poses; + unsigned int num_anims, ofs_anims; + unsigned int num_frames, num_framechannels, ofs_frames, ofs_bounds; + unsigned int num_comment, ofs_comment; + unsigned int num_extensions, ofs_extensions; + } IQMHeader; + + typedef struct IQMPose { + int parent; + unsigned int mask; + float channeloffset[10]; + float channelscale[10]; + } IQMPose; + + typedef struct IQMAnim { + unsigned int name; + unsigned int first_frame, num_frames; + float framerate; + unsigned int flags; + } IQMAnim; + + ModelAnimation animation = { 0 }; + + FILE *iqmFile; + IQMHeader iqm; + + iqmFile = fopen(filename,"rb"); + + if (!iqmFile) + { + TraceLog(LOG_ERROR, "[%s] Unable to open file", filename); + } + + // header + fread(&iqm, sizeof(IQMHeader), 1, iqmFile); + + if (strncmp(iqm.magic, IQM_MAGIC, sizeof(IQM_MAGIC))) + { + TraceLog(LOG_ERROR, "Magic Number \"%s\"does not match.", iqm.magic); + fclose(iqmFile); + } + + if (iqm.version != IQM_VERSION) + { + TraceLog(LOG_ERROR, "IQM version %i is incorrect.", iqm.version); + fclose(iqmFile); + } + + // header + if (iqm.num_anims > 1) TraceLog(LOG_WARNING, "More than 1 animation in file, only the first one will be loaded"); + + // bones + IQMPose *poses; + poses = malloc(sizeof(IQMPose)*iqm.num_poses); + fseek(iqmFile, iqm.ofs_poses, SEEK_SET); + fread(poses, sizeof(IQMPose)*iqm.num_poses, 1, iqmFile); + + animation.boneCount = iqm.num_poses; + animation.bones = malloc(sizeof(BoneInfo)*iqm.num_poses); + + for (int j = 0; j < iqm.num_poses; j++) + { + strcpy(animation.bones[j].name, "ANIMJOINTNAME"); + animation.bones[j].parent = poses[j].parent; + } + + // animations + IQMAnim anim = {0}; + fseek(iqmFile, iqm.ofs_anims, SEEK_SET); + fread(&anim, sizeof(IQMAnim), 1, iqmFile); + + animation.frameCount = anim.num_frames; + //animation.framerate = anim.framerate; + + // frameposes + unsigned short *framedata = malloc(sizeof(unsigned short)*iqm.num_frames*iqm.num_framechannels); + fseek(iqmFile, iqm.ofs_frames, SEEK_SET); + fread(framedata, sizeof(unsigned short)*iqm.num_frames*iqm.num_framechannels, 1, iqmFile); + + animation.framePoses = malloc(sizeof(Transform*)*anim.num_frames); + for (int j = 0; j < anim.num_frames; j++) animation.framePoses[j] = malloc(sizeof(Transform)*iqm.num_poses); + + int dcounter = anim.first_frame*iqm.num_framechannels; + + for (int frame = 0; frame < anim.num_frames; frame++) + { + for (int i = 0; i < iqm.num_poses; i++) + { + animation.framePoses[frame][i].translation.x = poses[i].channeloffset[0]; + + if (poses[i].mask & 0x01) + { + animation.framePoses[frame][i].translation.x += framedata[dcounter]*poses[i].channelscale[0]; + dcounter++; + } + + animation.framePoses[frame][i].translation.y = poses[i].channeloffset[1]; + + if (poses[i].mask & 0x02) + { + animation.framePoses[frame][i].translation.y += framedata[dcounter]*poses[i].channelscale[1]; + dcounter++; + } + + animation.framePoses[frame][i].translation.z = poses[i].channeloffset[2]; + + if (poses[i].mask & 0x04) + { + animation.framePoses[frame][i].translation.z += framedata[dcounter]*poses[i].channelscale[2]; + dcounter++; + } + + animation.framePoses[frame][i].rotation.x = poses[i].channeloffset[3]; + + if (poses[i].mask & 0x08) + { + animation.framePoses[frame][i].rotation.x += framedata[dcounter]*poses[i].channelscale[3]; + dcounter++; + } + + animation.framePoses[frame][i].rotation.y = poses[i].channeloffset[4]; + + if (poses[i].mask & 0x10) + { + animation.framePoses[frame][i].rotation.y += framedata[dcounter]*poses[i].channelscale[4]; + dcounter++; + } + + animation.framePoses[frame][i].rotation.z = poses[i].channeloffset[5]; + + if (poses[i].mask & 0x20) + { + animation.framePoses[frame][i].rotation.z += framedata[dcounter]*poses[i].channelscale[5]; + dcounter++; + } + + animation.framePoses[frame][i].rotation.w = poses[i].channeloffset[6]; + + if (poses[i].mask & 0x40) + { + animation.framePoses[frame][i].rotation.w += framedata[dcounter]*poses[i].channelscale[6]; + dcounter++; + } + + animation.framePoses[frame][i].scale.x = poses[i].channeloffset[7]; + + if (poses[i].mask & 0x80) + { + animation.framePoses[frame][i].scale.x += framedata[dcounter]*poses[i].channelscale[7]; + dcounter++; + } + + animation.framePoses[frame][i].scale.y = poses[i].channeloffset[8]; + + if (poses[i].mask & 0x100) + { + animation.framePoses[frame][i].scale.y += framedata[dcounter]*poses[i].channelscale[8]; + dcounter++; + } + + animation.framePoses[frame][i].scale.z = poses[i].channeloffset[9]; + + if (poses[i].mask & 0x200) + { + animation.framePoses[frame][i].scale.z += framedata[dcounter]*poses[i].channelscale[9]; + dcounter++; + } + + animation.framePoses[frame][i].rotation = QuaternionNormalize(animation.framePoses[frame][i].rotation); + } + } + + // Build frameposes + for (int frame = 0; frame < anim.num_frames; frame++) + { + for (int i = 0; i < animation.boneCount; i++) + { + if (animation.bones[i].parent >= 0) + { + animation.framePoses[frame][i].rotation = QuaternionMultiply(animation.framePoses[frame][animation.bones[i].parent].rotation, animation.framePoses[frame][i].rotation); + animation.framePoses[frame][i].translation = Vector3RotateByQuaternion(animation.framePoses[frame][i].translation, animation.framePoses[frame][animation.bones[i].parent].rotation); + animation.framePoses[frame][i].translation = Vector3Add(animation.framePoses[frame][i].translation, animation.framePoses[frame][animation.bones[i].parent].translation); + animation.framePoses[frame][i].scale = Vector3MultiplyV(animation.framePoses[frame][i].scale, animation.framePoses[frame][animation.bones[i].parent].scale); + } + } + } + + free(framedata); + free(poses); + + fclose(iqmFile); + + animations[0] = animation; + + *animCount = count; + return animations; +} + +// Update model animated vertex data (positions and normals) for a given frame +// NOTE: Updated data is uploaded to GPU +void UpdateModelAnimation(Model model, ModelAnimation anim, int frame) +{ + if (frame >= anim.frameCount) frame = frame%anim.frameCount; + + for (int m = 0; m < model.meshCount; m++) + { + Vector3 animVertex = { 0 }; + Vector3 animNormal = { 0 }; + + Vector3 inTranslation = { 0 }; + Quaternion inRotation = { 0 }; + Vector3 inScale = { 0 }; + + Vector3 outTranslation = { 0 }; + Quaternion outRotation = { 0 }; + Vector3 outScale = { 0 }; + + int vCounter = 0; + int boneCounter = 0; + int boneId = 0; + + for (int i = 0; i < model.meshes[m].vertexCount; i++) + { + boneId = model.meshes[m].boneIds[boneCounter]; + inTranslation = model.bindPose[boneId].translation; + inRotation = model.bindPose[boneId].rotation; + inScale = model.bindPose[boneId].scale; + outTranslation = anim.framePoses[frame][boneId].translation; + outRotation = anim.framePoses[frame][boneId].rotation; + outScale = anim.framePoses[frame][boneId].scale; + + // Vertices processing + // NOTE: We use meshes.vertices (default vertex position) to calculate meshes.animVertices (animated vertex position) + animVertex = (Vector3){ model.meshes[m].vertices[vCounter], model.meshes[m].vertices[vCounter + 1], model.meshes[m].vertices[vCounter + 2] }; + animVertex = Vector3MultiplyV(animVertex, outScale); + animVertex = Vector3Subtract(animVertex, inTranslation); + animVertex = Vector3RotateByQuaternion(animVertex, QuaternionMultiply(outRotation, QuaternionInvert(inRotation))); + animVertex = Vector3Add(animVertex, outTranslation); + model.meshes[m].animVertices[vCounter] = animVertex.x; + model.meshes[m].animVertices[vCounter + 1] = animVertex.y; + model.meshes[m].animVertices[vCounter + 2] = animVertex.z; + + // Normals processing + // NOTE: We use meshes.baseNormals (default normal) to calculate meshes.normals (animated normals) + animNormal = (Vector3){ model.meshes[m].normals[vCounter], model.meshes[m].normals[vCounter + 1], model.meshes[m].normals[vCounter + 2] }; + animNormal = Vector3RotateByQuaternion(animNormal, QuaternionMultiply(outRotation, QuaternionInvert(inRotation))); + model.meshes[m].animNormals[vCounter] = animNormal.x; + model.meshes[m].animNormals[vCounter + 1] = animNormal.y; + model.meshes[m].animNormals[vCounter + 2] = animNormal.z; + vCounter += 3; + + boneCounter += 4; + } + + // Upload new vertex data to GPU for model drawing + rlUpdateBuffer(model.meshes[m].vboId[0], model.meshes[m].animVertices, model.meshes[m].vertexCount*3*sizeof(float)); // Update vertex position + rlUpdateBuffer(model.meshes[m].vboId[2], model.meshes[m].animVertices, model.meshes[m].vertexCount*3*sizeof(float)); // Update vertex normals + } +} + +// Unload animation data +void UnloadModelAnimation(ModelAnimation anim) +{ + for (int i = 0; i < anim.frameCount; i++) free(anim.framePoses[i]); + + free(anim.bones); + free(anim.framePoses); +} + +// Check model animation skeleton match +// NOTE: Only number of bones and parent connections are checked +bool IsModelAnimationValid(Model model, ModelAnimation anim) +{ + int result = true; + + if (model.boneCount != anim.boneCount) result = false; + else + { + for (int i = 0; i < model.boneCount; i++) + { + if (model.bones[i].parent != anim.bones[i].parent) { result = false; break; } + } + } + + return result; +} + #if defined(SUPPORT_MESH_GENERATION) // Generate polygonal mesh Mesh GenMeshPoly(int sides, float radius) @@ -1807,59 +2199,124 @@ Mesh GenMeshCubicmap(Image cubicmap, Vector3 cubeSize) } #endif // SUPPORT_MESH_GENERATION -// Load material data (from file) -Material LoadMaterial(const char *fileName) +// Compute mesh bounding box limits +// NOTE: minVertex and maxVertex should be transformed by model transform matrix +BoundingBox MeshBoundingBox(Mesh mesh) { - Material material = { 0 }; + // Get min and max vertex to construct bounds (AABB) + Vector3 minVertex = { 0 }; + Vector3 maxVertex = { 0 }; -#if defined(SUPPORT_FILEFORMAT_MTL) - if (IsFileExtension(fileName, ".mtl")) + if (mesh.vertices != NULL) { - tinyobj_material_t *materials; - unsigned int materialCount = 0; + minVertex = (Vector3){ mesh.vertices[0], mesh.vertices[1], mesh.vertices[2] }; + maxVertex = (Vector3){ mesh.vertices[0], mesh.vertices[1], mesh.vertices[2] }; - int result = tinyobj_parse_mtl_file(&materials, &materialCount, fileName); - - // TODO: Process materials to return - - tinyobj_materials_free(materials, materialCount); + 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] }); + } } -#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) - material.shader = GetShaderDefault(); + // Create the bounding box + BoundingBox box = { 0 }; + box.min = minVertex; + box.max = maxVertex; - return material; + return box; } -// Load default material (Supports: DIFFUSE, SPECULAR, NORMAL maps) -Material LoadMaterialDefault(void) +// Compute mesh tangents +// NOTE: To calculate mesh tangents and binormals we need mesh vertex positions and texture coordinates +// Implementation base don: https://answers.unity.com/questions/7789/calculating-tangents-vector4.html +void MeshTangents(Mesh *mesh) { - Material material = { 0 }; + if (mesh->tangents == NULL) mesh->tangents = (float *)malloc(mesh->vertexCount*4*sizeof(float)); + else TraceLog(LOG_WARNING, "Mesh tangents already exist"); - material.shader = GetShaderDefault(); - material.maps[MAP_DIFFUSE].texture = GetTextureDefault(); // White texture (1x1 pixel) - //material.maps[MAP_NORMAL].texture; // NOTE: By default, not set - //material.maps[MAP_SPECULAR].texture; // NOTE: By default, not set + Vector3 *tan1 = (Vector3 *)malloc(mesh->vertexCount*sizeof(Vector3)); + Vector3 *tan2 = (Vector3 *)malloc(mesh->vertexCount*sizeof(Vector3)); - material.maps[MAP_DIFFUSE].color = WHITE; // Diffuse color - material.maps[MAP_SPECULAR].color = WHITE; // Specular color - - 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++) + for (int i = 0; i < mesh->vertexCount; i += 3) { - 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; } -// 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 //---------------------------------------------------------------------------------- diff --git a/src/raylib.h b/src/raylib.h index c365fa47f..55943bafe 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -752,7 +752,7 @@ typedef enum { MAP_IRRADIANCE, // NOTE: Uses GL_TEXTURE_CUBE_MAP MAP_PREFILTER, // NOTE: Uses GL_TEXTURE_CUBE_MAP MAP_BRDF -} TexmapIndex; +} MaterialMapType; #define MAP_DIFFUSE MAP_ALBEDO #define MAP_SPECULAR MAP_METALNESS @@ -1256,16 +1256,25 @@ RLAPI void DrawGizmo(Vector3 position); // Model loading/unloading functions RLAPI Model LoadModel(const char *fileName); // Load model from files (meshes and materials) 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) -// Mesh manipulation functions -RLAPI BoundingBox MeshBoundingBox(Mesh mesh); // Compute mesh bounding box limits -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) +// Mesh loading/unloading functions +RLAPI Mesh *LoadMeshes(const char *fileName, int *meshCount); // Load meshes from model 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 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 GenMeshCubicmap(Image cubicmap, Vector3 cubeSize); // Generate cubes-based map mesh from image data -// Material loading/unloading functions -RLAPI Material LoadMaterial(const char *fileName); // Load material from file -RLAPI Material LoadMaterialDefault(void); // Load default material (Supports: DIFFUSE, SPECULAR, NORMAL maps) -RLAPI void UnloadMaterial(Material material); // Unload material from GPU memory (VRAM) +// Mesh manipulation functions +RLAPI BoundingBox MeshBoundingBox(Mesh mesh); // Compute mesh bounding box limits +RLAPI void MeshTangents(Mesh *mesh); // Compute mesh tangents +RLAPI void MeshBinormals(Mesh *mesh); // Compute mesh binormals // Model drawing functions RLAPI void DrawModel(Model model, Vector3 position, float scale, Color tint); // Draw a model (with texture if set)