Sfoglia il codice sorgente

Gltf animation support (#1551)

* Added example for gltf animation and split some functions for loading model animations into IQM and GLTF similar to how models are being loaded.

* Removed wrongly duplicated function

* Bone loading for gltf model (not working at this point)

* Loading info about vertex to joint connection but animation is still not working

* Skeleton and pose is correctly loaded. Need to communicate about interpolation in GLTF

* The model almost looks like a real person on animation.

* Fixed model loading with bones.

Also updated license info on the model.

* Cleaned up some code and updated examples.

* Fix identation issues

* Fix identation issues

* Fix identation issues
pull/1557/head
hristo 4 anni fa
committed by GitHub
parent
commit
731ab15d57
Non sono state trovate chiavi note per questa firma nel database ID Chiave GPG: 4AEE18F83AFDEB23
6 ha cambiato i file con 692 aggiunte e 214 eliminazioni
  1. +115
    -0
      examples/models/models_gltf_animation.c
  2. +87
    -0
      examples/models/models_gltf_model.c
  3. BIN
      examples/models/resources/gltf/Avocado.glb
  4. +11
    -0
      examples/models/resources/gltf/LICENSE
  5. BIN
      examples/models/resources/gltf/rigged_figure.glb
  6. +479
    -214
      src/models.c

+ 115
- 0
examples/models/models_gltf_animation.c Vedi File

@ -0,0 +1,115 @@
/*******************************************************************************************
*
* raylib [models] example - Load 3d gltf model with animations and play them
*
* This example has been created using raylib 3.5 (www.raylib.com)
* raylib is licensed under an unmodified zlib/libpng license (View raylib.h for details)
*
* Example contributed by Hristo Stamenov (@object71) and reviewed by Ramon Santamaria (@raysan5)
*
* Copyright (c) 2021 Hristo Stamenov (@object71) and Ramon Santamaria (@raysan5)
*
********************************************************************************************
*
* To export a model from blender, make sure it is not posed, the vertices need to be in the
* same position as they would be in edit mode.
* and that the scale of your models is set to 0. Scaling can be done from the export menu.
*
********************************************************************************************/
#include "raylib.h"
#include <stdlib.h>
int main(void)
{
// Initialization
//--------------------------------------------------------------------------------------
const int screenWidth = 800;
const int screenHeight = 450;
InitWindow(screenWidth, screenHeight, "raylib [models] example - model animation");
// Define the camera to look into our 3d world
Camera camera = { 0 };
camera.position = (Vector3){ 10.0f, 10.0f, 10.0f }; // Camera position
camera.target = (Vector3){ 0.0f, 0.0f, 0.0f }; // Camera looking at point
camera.up = (Vector3){ 0.0f, 1.0f, 0.0f }; // Camera up vector (rotation towards target)
camera.fovy = 45.0f; // Camera field-of-view Y
camera.type = CAMERA_PERSPECTIVE; // Camera mode type
Model model = LoadModel("resources/gltf/rigged_figure.glb"); // 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
int animsCount = 0;
ModelAnimation *anims = LoadModelAnimations("resources/gltf/rigged_figure.glb", &animsCount);
int animFrameCounter = 0;
SetCameraMode(camera, CAMERA_FREE); // Set free camera mode
SetTargetFPS(60); // Set our game to run at 60 frames-per-second
//--------------------------------------------------------------------------------------
// Main game loop
while (!WindowShouldClose()) // Detect window close button or ESC key
{
// Update
//----------------------------------------------------------------------------------
UpdateCamera(&camera);
// Play animation when spacebar is held down
if (IsKeyDown(KEY_SPACE))
{
animFrameCounter++;
UpdateModelAnimation(model, anims[0], animFrameCounter);
if (animFrameCounter >= anims[0].frameCount) animFrameCounter = 0;
}
//----------------------------------------------------------------------------------
// Draw
//----------------------------------------------------------------------------------
BeginDrawing();
ClearBackground(RAYWHITE);
BeginMode3D(camera);
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++)
{
DrawSphere(anims[0].framePoses[animFrameCounter][i].translation, 0.01f, RED);
}
DrawGrid(10, 1.0f); // Draw a grid
EndMode3D();
DrawText("PRESS SPACE to PLAY MODEL ANIMATION", 10, 10, 20, MAROON);
DrawText("(cc4) Rigged Figure by @Cesium", screenWidth - 200, screenHeight - 20, 10, GRAY);
EndDrawing();
//----------------------------------------------------------------------------------
}
// De-Initialization
//--------------------------------------------------------------------------------------
// UnloadTexture(texture); // Unload texture
// Unload model animations data
for (int i = 0; i < animsCount; i++) UnloadModelAnimation(anims[i]);
RL_FREE(anims);
UnloadModel(model); // Unload model
CloseWindow(); // Close window and OpenGL context
//--------------------------------------------------------------------------------------
return 0;
}

+ 87
- 0
examples/models/models_gltf_model.c Vedi File

@ -0,0 +1,87 @@
/*******************************************************************************************
*
* raylib [models] example - Load 3d gltf model
*
* This example has been created using raylib 3.5 (www.raylib.com)
* raylib is licensed under an unmodified zlib/libpng license (View raylib.h for details)
*
* Example contributed by Hristo Stamenov (@object71) and reviewed by Ramon Santamaria (@raysan5)
*
* Copyright (c) 2021 Hristo Stamenov (@object71) and Ramon Santamaria (@raysan5)
*
********************************************************************************************
*
* To export a model from blender, make sure it is not posed, the vertices need to be in the
* same position as they would be in edit mode.
* and that the scale of your models is set to 0. Scaling can be done from the export menu.
*
********************************************************************************************/
#include "raylib.h"
#include <stdlib.h>
int main(void)
{
// Initialization
//--------------------------------------------------------------------------------------
const int screenWidth = 800;
const int screenHeight = 450;
InitWindow(screenWidth, screenHeight, "raylib [models] example - model animation");
// Define the camera to look into our 3d world
Camera camera = { 0 };
camera.position = (Vector3){ 10.0f, 10.0f, 10.0f }; // Camera position
camera.target = (Vector3){ 0.0f, 0.0f, 0.0f }; // Camera looking at point
camera.up = (Vector3){ 0.0f, 1.0f, 0.0f }; // Camera up vector (rotation towards target)
camera.fovy = 45.0f; // Camera field-of-view Y
camera.type = CAMERA_PERSPECTIVE; // Camera mode type
Model model = LoadModel("resources/gltf/Avocado.glb"); // Load the animated model mesh and
Vector3 position = { 0.0f, 0.0f, 0.0f }; // Set model position
SetCameraMode(camera, CAMERA_FREE); // Set free camera mode
SetTargetFPS(60); // Set our game to run at 60 frames-per-second
//--------------------------------------------------------------------------------------
// Main game loop
while (!WindowShouldClose()) // Detect window close button or ESC key
{
// Update
//----------------------------------------------------------------------------------
UpdateCamera(&camera);
// Draw
//----------------------------------------------------------------------------------
BeginDrawing();
ClearBackground(RAYWHITE);
BeginMode3D(camera);
DrawModelEx(model, position, (Vector3){ 0.0f, 1.0f, 0.0f }, 180.0f, (Vector3){ 15.0f, 15.0f, 15.0f }, WHITE);
DrawGrid(10, 1.0f); // Draw a grid
EndMode3D();
DrawText("(cc0) Avocado by @Microsoft", screenWidth - 200, screenHeight - 20, 10, GRAY);
EndDrawing();
//----------------------------------------------------------------------------------
}
// De-Initialization
//--------------------------------------------------------------------------------------
UnloadModel(model); // Unload model
CloseWindow(); // Close window and OpenGL context
//--------------------------------------------------------------------------------------
return 0;
}

BIN
examples/models/resources/gltf/Avocado.glb Vedi File


+ 11
- 0
examples/models/resources/gltf/LICENSE Vedi File

@ -0,0 +1,11 @@
Rigged Figure model has been created by Cesium (https://cesium.com/cesiumjs/),
and licensed as Creative Commons Attribution 4.0 International License.
Check for details: http://creativecommons.org/licenses/by/4.0/
Avocado model is provided by Microsoft
and licensed as CC0 Universal Public Domain
Check for details: https://creativecommons.org/publicdomain/zero/1.0/
GLTF sample models for testing are taken from: https://github.com/KhronosGroup/glTF-Sample-Models/

BIN
examples/models/resources/gltf/rigged_figure.glb Vedi File


+ 479
- 214
src/models.c Vedi File

@ -114,9 +114,11 @@ static Model LoadOBJ(const char *fileName); // Load OBJ mesh data
#endif #endif
#if defined(SUPPORT_FILEFORMAT_IQM) #if defined(SUPPORT_FILEFORMAT_IQM)
static Model LoadIQM(const char *fileName); // Load IQM mesh data static Model LoadIQM(const char *fileName); // Load IQM mesh data
static ModelAnimation *LoadIQMModelAnimations(const char *fileName, int *animCount); // Load IQM animation data
#endif #endif
#if defined(SUPPORT_FILEFORMAT_GLTF) #if defined(SUPPORT_FILEFORMAT_GLTF)
static Model LoadGLTF(const char *fileName); // Load GLTF mesh data static Model LoadGLTF(const char *fileName); // Load GLTF mesh data
static ModelAnimation *LoadGLTFModelAnimations(const char *fileName, int *animCount); // Load GLTF animation data
#endif #endif
//---------------------------------------------------------------------------------- //----------------------------------------------------------------------------------
@ -852,7 +854,7 @@ Mesh *LoadMeshes(const char *fileName, int *meshCount)
// Upload mesh vertex data to GPU // Upload mesh vertex data to GPU
void UploadMesh(Mesh *mesh) void UploadMesh(Mesh *mesh)
{ {
rlLoadMesh(o">&mesh, false); // Static mesh by default
rlLoadMesh(mesh, false); // Static mesh by default
} }
// Unload mesh from memory (RAM and/or VRAM) // Unload mesh from memory (RAM and/or VRAM)
@ -1014,211 +1016,15 @@ void SetModelMeshMaterial(Model *model, int meshId, int materialId)
// Load model animations from file // Load model animations from file
ModelAnimation *LoadModelAnimations(const char *fileName, int *animCount) ModelAnimation *LoadModelAnimations(const char *fileName, int *animCount)
{ {
#define IQM_MAGIC "INTERQUAKEMODEL" // IQM file magic number
#define IQM_VERSION 2 // only IQM version 2 supported
unsigned int fileSize = 0;
unsigned char *fileData = LoadFileData(fileName, &fileSize);
unsigned char *fileDataPtr = fileData;
typedef struct IQMHeader {
char magic[16];
unsigned int version;
unsigned int filesize;
unsigned int flags;
unsigned int num_text, ofs_text;
unsigned int num_meshes, ofs_meshes;
unsigned int num_vertexarrays, num_vertexes, ofs_vertexarrays;
unsigned int num_triangles, ofs_triangles, ofs_adjacency;
unsigned int num_joints, ofs_joints;
unsigned int num_poses, ofs_poses;
unsigned int num_anims, ofs_anims;
unsigned int num_frames, num_framechannels, ofs_frames, ofs_bounds;
unsigned int num_comment, ofs_comment;
unsigned int num_extensions, ofs_extensions;
} IQMHeader;
typedef struct IQMPose {
int parent;
unsigned int mask;
float channeloffset[10];
float channelscale[10];
} IQMPose;
typedef struct IQMAnim {
unsigned int name;
unsigned int first_frame, num_frames;
float framerate;
unsigned int flags;
} IQMAnim;
// In case file can not be read, return an empty model
if (fileDataPtr == NULL) return NULL;
// Read IQM header
IQMHeader *iqmHeader = (IQMHeader *)fileDataPtr;
if (memcmp(iqmHeader->magic, IQM_MAGIC, sizeof(IQM_MAGIC)) != 0)
{
TRACELOG(LOG_WARNING, "MODEL: [%s] IQM file is not a valid model", fileName);
return NULL;
}
if (iqmHeader->version != IQM_VERSION)
{
TRACELOG(LOG_WARNING, "MODEL: [%s] IQM file version not supported (%i)", fileName, iqmHeader->version);
return NULL;
}
// Get bones data
IQMPose *poses = RL_MALLOC(iqmHeader->num_poses*sizeof(IQMPose));
//fseek(iqmFile, iqmHeader->ofs_poses, SEEK_SET);
//fread(poses, iqmHeader->num_poses*sizeof(IQMPose), 1, iqmFile);
memcpy(poses, fileDataPtr + iqmHeader->ofs_poses, iqmHeader->num_poses*sizeof(IQMPose));
// Get animations data
*animCount = iqmHeader->num_anims;
IQMAnim *anim = RL_MALLOC(iqmHeader->num_anims*sizeof(IQMAnim));
//fseek(iqmFile, iqmHeader->ofs_anims, SEEK_SET);
//fread(anim, iqmHeader->num_anims*sizeof(IQMAnim), 1, iqmFile);
memcpy(anim, fileDataPtr + iqmHeader->ofs_anims, iqmHeader->num_anims*sizeof(IQMAnim));
ModelAnimation *animations = RL_MALLOC(iqmHeader->num_anims*sizeof(ModelAnimation));
// frameposes
unsigned short *framedata = RL_MALLOC(iqmHeader->num_frames*iqmHeader->num_framechannels*sizeof(unsigned short));
//fseek(iqmFile, iqmHeader->ofs_frames, SEEK_SET);
//fread(framedata, iqmHeader->num_frames*iqmHeader->num_framechannels*sizeof(unsigned short), 1, iqmFile);
memcpy(framedata, fileDataPtr + iqmHeader->ofs_frames, iqmHeader->num_frames*iqmHeader->num_framechannels*sizeof(unsigned short));
for (unsigned int a = 0; a < iqmHeader->num_anims; a++)
{
animations[a].frameCount = anim[a].num_frames;
animations[a].boneCount = iqmHeader->num_poses;
animations[a].bones = RL_MALLOC(iqmHeader->num_poses*sizeof(BoneInfo));
animations[a].framePoses = RL_MALLOC(anim[a].num_frames*sizeof(Transform *));
//animations[a].framerate = anim.framerate; // TODO: Use framerate?
for (unsigned int j = 0; j < iqmHeader->num_poses; j++)
{
strcpy(animations[a].bones[j].name, "ANIMJOINTNAME");
animations[a].bones[j].parent = poses[j].parent;
}
for (unsigned int j = 0; j < anim[a].num_frames; j++) animations[a].framePoses[j] = RL_MALLOC(iqmHeader->num_poses*sizeof(Transform));
int dcounter = anim[a].first_frame*iqmHeader->num_framechannels;
for (unsigned int frame = 0; frame < anim[a].num_frames; frame++)
{
for (unsigned int i = 0; i < iqmHeader->num_poses; i++)
{
animations[a].framePoses[frame][i].translation.x = poses[i].channeloffset[0];
if (poses[i].mask & 0x01)
{
animations[a].framePoses[frame][i].translation.x += framedata[dcounter]*poses[i].channelscale[0];
dcounter++;
}
animations[a].framePoses[frame][i].translation.y = poses[i].channeloffset[1];
if (poses[i].mask & 0x02)
{
animations[a].framePoses[frame][i].translation.y += framedata[dcounter]*poses[i].channelscale[1];
dcounter++;
}
animations[a].framePoses[frame][i].translation.z = poses[i].channeloffset[2];
if (poses[i].mask & 0x04)
{
animations[a].framePoses[frame][i].translation.z += framedata[dcounter]*poses[i].channelscale[2];
dcounter++;
}
animations[a].framePoses[frame][i].rotation.x = poses[i].channeloffset[3];
if (poses[i].mask & 0x08)
{
animations[a].framePoses[frame][i].rotation.x += framedata[dcounter]*poses[i].channelscale[3];
dcounter++;
}
animations[a].framePoses[frame][i].rotation.y = poses[i].channeloffset[4];
if (poses[i].mask & 0x10)
{
animations[a].framePoses[frame][i].rotation.y += framedata[dcounter]*poses[i].channelscale[4];
dcounter++;
}
animations[a].framePoses[frame][i].rotation.z = poses[i].channeloffset[5];
if (poses[i].mask & 0x20)
{
animations[a].framePoses[frame][i].rotation.z += framedata[dcounter]*poses[i].channelscale[5];
dcounter++;
}
animations[a].framePoses[frame][i].rotation.w = poses[i].channeloffset[6];
if (poses[i].mask & 0x40)
{
animations[a].framePoses[frame][i].rotation.w += framedata[dcounter]*poses[i].channelscale[6];
dcounter++;
}
animations[a].framePoses[frame][i].scale.x = poses[i].channeloffset[7];
if (poses[i].mask & 0x80)
{
animations[a].framePoses[frame][i].scale.x += framedata[dcounter]*poses[i].channelscale[7];
dcounter++;
}
animations[a].framePoses[frame][i].scale.y = poses[i].channeloffset[8];
if (poses[i].mask & 0x100)
{
animations[a].framePoses[frame][i].scale.y += framedata[dcounter]*poses[i].channelscale[8];
dcounter++;
}
animations[a].framePoses[frame][i].scale.z = poses[i].channeloffset[9];
if (poses[i].mask & 0x200)
{
animations[a].framePoses[frame][i].scale.z += framedata[dcounter]*poses[i].channelscale[9];
dcounter++;
}
animations[a].framePoses[frame][i].rotation = QuaternionNormalize(animations[a].framePoses[frame][i].rotation);
}
}
// Build frameposes
for (unsigned int frame = 0; frame < anim[a].num_frames; frame++)
{
for (int i = 0; i < animations[a].boneCount; i++)
{
if (animations[a].bones[i].parent >= 0)
{
animations[a].framePoses[frame][i].rotation = QuaternionMultiply(animations[a].framePoses[frame][animations[a].bones[i].parent].rotation, animations[a].framePoses[frame][i].rotation);
animations[a].framePoses[frame][i].translation = Vector3RotateByQuaternion(animations[a].framePoses[frame][i].translation, animations[a].framePoses[frame][animations[a].bones[i].parent].rotation);
animations[a].framePoses[frame][i].translation = Vector3Add(animations[a].framePoses[frame][i].translation, animations[a].framePoses[frame][animations[a].bones[i].parent].translation);
animations[a].framePoses[frame][i].scale = Vector3Multiply(animations[a].framePoses[frame][i].scale, animations[a].framePoses[frame][animations[a].bones[i].parent].scale);
}
}
}
}
RL_FREE(fileData);
RL_FREE(framedata);
RL_FREE(poses);
RL_FREE(anim);
ModelAnimation *animations = NULL;
#if defined(SUPPORT_FILEFORMAT_IQM)
if (IsFileExtension(fileName, ".iqm")) animations = LoadIQMModelAnimations(fileName, animCount);
#endif
#if defined(SUPPORT_FILEFORMAT_GLTF)
if (IsFileExtension(fileName, ".gltf;.glb")) animations = LoadGLTFModelAnimations(fileName, animCount);
#endif
return animations; return animations;
} }
@ -3554,6 +3360,218 @@ static Model LoadIQM(const char *fileName)
return model; return model;
} }
// Load IQM animation data
static ModelAnimation* LoadIQMModelAnimations(const char* fileName, int* animCount)
{
#define IQM_MAGIC "INTERQUAKEMODEL" // IQM file magic number
#define IQM_VERSION 2 // only IQM version 2 supported
unsigned int fileSize = 0;
unsigned char *fileData = LoadFileData(fileName, &fileSize);
unsigned char *fileDataPtr = fileData;
typedef struct IQMHeader {
char magic[16];
unsigned int version;
unsigned int filesize;
unsigned int flags;
unsigned int num_text, ofs_text;
unsigned int num_meshes, ofs_meshes;
unsigned int num_vertexarrays, num_vertexes, ofs_vertexarrays;
unsigned int num_triangles, ofs_triangles, ofs_adjacency;
unsigned int num_joints, ofs_joints;
unsigned int num_poses, ofs_poses;
unsigned int num_anims, ofs_anims;
unsigned int num_frames, num_framechannels, ofs_frames, ofs_bounds;
unsigned int num_comment, ofs_comment;
unsigned int num_extensions, ofs_extensions;
} IQMHeader;
typedef struct IQMPose {
int parent;
unsigned int mask;
float channeloffset[10];
float channelscale[10];
} IQMPose;
typedef struct IQMAnim {
unsigned int name;
unsigned int first_frame, num_frames;
float framerate;
unsigned int flags;
} IQMAnim;
// In case file can not be read, return an empty model
if (fileDataPtr == NULL) return NULL;
// Read IQM header
IQMHeader *iqmHeader = (IQMHeader *)fileDataPtr;
if (memcmp(iqmHeader->magic, IQM_MAGIC, sizeof(IQM_MAGIC)) != 0)
{
TRACELOG(LOG_WARNING, "MODEL: [%s] IQM file is not a valid model", fileName);
return NULL;
}
if (iqmHeader->version != IQM_VERSION)
{
TRACELOG(LOG_WARNING, "MODEL: [%s] IQM file version not supported (%i)", fileName, iqmHeader->version);
return NULL;
}
// Get bones data
IQMPose *poses = RL_MALLOC(iqmHeader->num_poses*sizeof(IQMPose));
//fseek(iqmFile, iqmHeader->ofs_poses, SEEK_SET);
//fread(poses, iqmHeader->num_poses*sizeof(IQMPose), 1, iqmFile);
memcpy(poses, fileDataPtr + iqmHeader->ofs_poses, iqmHeader->num_poses*sizeof(IQMPose));
// Get animations data
*animCount = iqmHeader->num_anims;
IQMAnim *anim = RL_MALLOC(iqmHeader->num_anims*sizeof(IQMAnim));
//fseek(iqmFile, iqmHeader->ofs_anims, SEEK_SET);
//fread(anim, iqmHeader->num_anims*sizeof(IQMAnim), 1, iqmFile);
memcpy(anim, fileDataPtr + iqmHeader->ofs_anims, iqmHeader->num_anims*sizeof(IQMAnim));
ModelAnimation *animations = RL_MALLOC(iqmHeader->num_anims*sizeof(ModelAnimation));
// frameposes
unsigned short *framedata = RL_MALLOC(iqmHeader->num_frames*iqmHeader->num_framechannels*sizeof(unsigned short));
//fseek(iqmFile, iqmHeader->ofs_frames, SEEK_SET);
//fread(framedata, iqmHeader->num_frames*iqmHeader->num_framechannels*sizeof(unsigned short), 1, iqmFile);
memcpy(framedata, fileDataPtr + iqmHeader->ofs_frames, iqmHeader->num_frames*iqmHeader->num_framechannels*sizeof(unsigned short));
for (unsigned int a = 0; a < iqmHeader->num_anims; a++)
{
animations[a].frameCount = anim[a].num_frames;
animations[a].boneCount = iqmHeader->num_poses;
animations[a].bones = RL_MALLOC(iqmHeader->num_poses*sizeof(BoneInfo));
animations[a].framePoses = RL_MALLOC(anim[a].num_frames*sizeof(Transform *));
//animations[a].framerate = anim.framerate; // TODO: Use framerate?
for (unsigned int j = 0; j < iqmHeader->num_poses; j++)
{
strcpy(animations[a].bones[j].name, "ANIMJOINTNAME");
animations[a].bones[j].parent = poses[j].parent;
}
for (unsigned int j = 0; j < anim[a].num_frames; j++) animations[a].framePoses[j] = RL_MALLOC(iqmHeader->num_poses*sizeof(Transform));
int dcounter = anim[a].first_frame*iqmHeader->num_framechannels;
for (unsigned int frame = 0; frame < anim[a].num_frames; frame++)
{
for (unsigned int i = 0; i < iqmHeader->num_poses; i++)
{
animations[a].framePoses[frame][i].translation.x = poses[i].channeloffset[0];
if (poses[i].mask & 0x01)
{
animations[a].framePoses[frame][i].translation.x += framedata[dcounter]*poses[i].channelscale[0];
dcounter++;
}
animations[a].framePoses[frame][i].translation.y = poses[i].channeloffset[1];
if (poses[i].mask & 0x02)
{
animations[a].framePoses[frame][i].translation.y += framedata[dcounter]*poses[i].channelscale[1];
dcounter++;
}
animations[a].framePoses[frame][i].translation.z = poses[i].channeloffset[2];
if (poses[i].mask & 0x04)
{
animations[a].framePoses[frame][i].translation.z += framedata[dcounter]*poses[i].channelscale[2];
dcounter++;
}
animations[a].framePoses[frame][i].rotation.x = poses[i].channeloffset[3];
if (poses[i].mask & 0x08)
{
animations[a].framePoses[frame][i].rotation.x += framedata[dcounter]*poses[i].channelscale[3];
dcounter++;
}
animations[a].framePoses[frame][i].rotation.y = poses[i].channeloffset[4];
if (poses[i].mask & 0x10)
{
animations[a].framePoses[frame][i].rotation.y += framedata[dcounter]*poses[i].channelscale[4];
dcounter++;
}
animations[a].framePoses[frame][i].rotation.z = poses[i].channeloffset[5];
if (poses[i].mask & 0x20)
{
animations[a].framePoses[frame][i].rotation.z += framedata[dcounter]*poses[i].channelscale[5];
dcounter++;
}
animations[a].framePoses[frame][i].rotation.w = poses[i].channeloffset[6];
if (poses[i].mask & 0x40)
{
animations[a].framePoses[frame][i].rotation.w += framedata[dcounter]*poses[i].channelscale[6];
dcounter++;
}
animations[a].framePoses[frame][i].scale.x = poses[i].channeloffset[7];
if (poses[i].mask & 0x80)
{
animations[a].framePoses[frame][i].scale.x += framedata[dcounter]*poses[i].channelscale[7];
dcounter++;
}
animations[a].framePoses[frame][i].scale.y = poses[i].channeloffset[8];
if (poses[i].mask & 0x100)
{
animations[a].framePoses[frame][i].scale.y += framedata[dcounter]*poses[i].channelscale[8];
dcounter++;
}
animations[a].framePoses[frame][i].scale.z = poses[i].channeloffset[9];
if (poses[i].mask & 0x200)
{
animations[a].framePoses[frame][i].scale.z += framedata[dcounter]*poses[i].channelscale[9];
dcounter++;
}
animations[a].framePoses[frame][i].rotation = QuaternionNormalize(animations[a].framePoses[frame][i].rotation);
}
}
// Build frameposes
for (unsigned int frame = 0; frame < anim[a].num_frames; frame++)
{
for (int i = 0; i < animations[a].boneCount; i++)
{
if (animations[a].bones[i].parent >= 0)
{
animations[a].framePoses[frame][i].rotation = QuaternionMultiply(animations[a].framePoses[frame][animations[a].bones[i].parent].rotation, animations[a].framePoses[frame][i].rotation);
animations[a].framePoses[frame][i].translation = Vector3RotateByQuaternion(animations[a].framePoses[frame][i].translation, animations[a].framePoses[frame][animations[a].bones[i].parent].rotation);
animations[a].framePoses[frame][i].translation = Vector3Add(animations[a].framePoses[frame][i].translation, animations[a].framePoses[frame][animations[a].bones[i].parent].translation);
animations[a].framePoses[frame][i].scale = Vector3Multiply(animations[a].framePoses[frame][i].scale, animations[a].framePoses[frame][animations[a].bones[i].parent].scale);
}
}
}
}
RL_FREE(fileData);
RL_FREE(framedata);
RL_FREE(poses);
RL_FREE(anim);
return animations;
}
#endif #endif
#if defined(SUPPORT_FILEFORMAT_GLTF) #if defined(SUPPORT_FILEFORMAT_GLTF)
@ -3763,7 +3781,8 @@ static Model LoadGLTF(const char *fileName)
int primitivesCount = 0; int primitivesCount = 0;
for (unsigned int i = 0; i < data->meshes_count; i++) primitivesCount += (int)data->meshes[i].primitives_count;
for (unsigned int i = 0; i < data->meshes_count; i++)
primitivesCount += (int)data->meshes[i].primitives_count;
// Process glTF data and map to model // Process glTF data and map to model
model.meshCount = primitivesCount; model.meshCount = primitivesCount;
@ -3771,9 +3790,65 @@ static Model LoadGLTF(const char *fileName)
model.materialCount = (int)data->materials_count + 1; model.materialCount = (int)data->materials_count + 1;
model.materials = RL_MALLOC(model.materialCount*sizeof(Material)); model.materials = RL_MALLOC(model.materialCount*sizeof(Material));
model.meshMaterial = RL_MALLOC(model.meshCount*sizeof(int)); model.meshMaterial = RL_MALLOC(model.meshCount*sizeof(int));
for (int i = 0; i < model.meshCount; i++) model.meshes[i].vboId = (unsigned int *)RL_CALLOC(DEFAULT_MESH_VERTEX_BUFFERS, sizeof(unsigned int));
model.boneCount = data->nodes_count;
model.bones = RL_CALLOC(model.boneCount, sizeof(BoneInfo));
model.bindPose = RL_CALLOC(model.boneCount, sizeof(Transform));
for (int i = 0; i < model.meshCount; i++)
model.meshes[i].vboId = (unsigned int *)RL_CALLOC(DEFAULT_MESH_VERTEX_BUFFERS, sizeof(unsigned int));
for (unsigned int j = 0; j < data->nodes_count; j++)
{
strcpy(model.bones[j].name, data->nodes[j].name == 0 ? "ANIMJOINT" : data->nodes[j].name);
model.bones[j].parent = j != 0 ? data->nodes[j].parent - data->nodes : 0;
}
for (unsigned int i = 0; i < data->nodes_count; i++)
{
if(data->nodes[i].has_translation)
{
memcpy(&model.bindPose[i].translation, data->nodes[i].translation, 3 * sizeof(float));
}
else
{
model.bindPose[i].translation = Vector3Zero();
}
if(data->nodes[i].has_rotation)
{
memcpy(&model.bindPose[i].rotation, data->nodes[i].rotation, 4 * sizeof(float));
}
else
{
model.bindPose[i].rotation = QuaternionIdentity();
}
model.bindPose[i].rotation = QuaternionNormalize(model.bindPose[i].rotation);
if(data->nodes[i].has_scale)
{
memcpy(&model.bindPose[i].scale, data->nodes[i].scale, 3 * sizeof(float));
}
else
{
model.bindPose[i].scale = Vector3One();
}
}
for (int i = 0; i < model.boneCount; i++)
{
Transform* currentTransform = model.bindPose + i;
BoneInfo* currentBone = model.bones + i;
Transform* parentTransform = model.bindPose + currentBone->parent;
if (currentBone->parent >= 0)
{
currentTransform->rotation = QuaternionMultiply(parentTransform->rotation, currentTransform->rotation);
currentTransform->translation = Vector3RotateByQuaternion(currentTransform->translation, parentTransform->rotation);
currentTransform->translation = Vector3Add(currentTransform->translation, parentTransform->translation);
currentTransform->scale = Vector3Multiply(parentTransform->scale, parentTransform->scale);
}
}
for (int i = 0; i < model.materialCount - 1; i++) for (int i = 0; i < model.materialCount - 1; i++)
{ {
model.materials[i] = LoadMaterialDefault(); model.materials[i] = LoadMaterialDefault();
@ -3854,16 +3929,22 @@ static Model LoadGLTF(const char *fileName)
{ {
cgltf_accessor *acc = data->meshes[i].primitives[p].attributes[j].data; cgltf_accessor *acc = data->meshes[i].primitives[p].attributes[j].data;
model.meshes[primitiveIndex].vertexCount = (int)acc->count; model.meshes[primitiveIndex].vertexCount = (int)acc->count;
model.meshes[primitiveIndex].vertices = RL_MALLOC(model.meshes[primitiveIndex].vertexCount*3*sizeof(float));
int bufferSize = model.meshes[primitiveIndex].vertexCount * 3 * sizeof(float);
model.meshes[primitiveIndex].vertices = RL_MALLOC(bufferSize);
model.meshes[primitiveIndex].animVertices = RL_MALLOC(bufferSize);
LOAD_ACCESSOR(float, 3, acc, model.meshes[primitiveIndex].vertices)
LOAD_ACCESSOR(float, 3, acc, model.meshes[primitiveIndex].vertices);
memcpy(model.meshes[primitiveIndex].animVertices, model.meshes[primitiveIndex].vertices, bufferSize);
} }
else if (data->meshes[i].primitives[p].attributes[j].type == cgltf_attribute_type_normal) else if (data->meshes[i].primitives[p].attributes[j].type == cgltf_attribute_type_normal)
{ {
cgltf_accessor *acc = data->meshes[i].primitives[p].attributes[j].data; cgltf_accessor *acc = data->meshes[i].primitives[p].attributes[j].data;
model.meshes[primitiveIndex].normals = RL_MALLOC(acc->count*3*sizeof(float));
int bufferSize = acc->count*3*sizeof(float);
model.meshes[primitiveIndex].normals = RL_MALLOC(bufferSize);
model.meshes[primitiveIndex].animNormals = RL_MALLOC(bufferSize);
LOAD_ACCESSOR(float, 3, acc, model.meshes[primitiveIndex].normals)
LOAD_ACCESSOR(float, 3, acc, model.meshes[primitiveIndex].normals);
memcpy(model.meshes[primitiveIndex].animNormals, model.meshes[primitiveIndex].normals, bufferSize);
} }
else if (data->meshes[i].primitives[p].attributes[j].type == cgltf_attribute_type_texcoord) else if (data->meshes[i].primitives[p].attributes[j].type == cgltf_attribute_type_texcoord)
{ {
@ -3880,6 +3961,45 @@ static Model LoadGLTF(const char *fileName)
TRACELOG(LOG_WARNING, "MODEL: [%s] glTF texture coordinates must be float", fileName); TRACELOG(LOG_WARNING, "MODEL: [%s] glTF texture coordinates must be float", fileName);
} }
} }
else if (data->meshes[i].primitives[p].attributes[j].type == cgltf_attribute_type_joints)
{
cgltf_accessor *acc = data->meshes[i].primitives[p].attributes[j].data;
if(acc->component_type == cgltf_component_type_r_16u)
{
model.meshes[primitiveIndex].boneIds = RL_MALLOC(sizeof(int) * acc->count * 4);
short* bones = RL_MALLOC(sizeof(short) * acc->count * 4);
LOAD_ACCESSOR(short, 4, acc, bones);
for(int a = 0; a < acc->count * 4; a ++)
{
cgltf_node* skinJoint = data->skins->joints[bones[a]];
for(int k = 0; k < data->nodes_count; k++)
{
if(&(data->nodes[k]) == skinJoint)
{
model.meshes[primitiveIndex].boneIds[a] = k;
break;
}
}
}
RL_FREE(bones);
}
else
{
// TODO: Support other size of bone index?
TRACELOG(LOG_WARNING, "MODEL: [%s] glTF bones in unexpected format", fileName);
}
}
else if (data->meshes[i].primitives[p].attributes[j].type == cgltf_attribute_type_weights)
{
cgltf_accessor *acc = data->meshes[i].primitives[p].attributes[j].data;
model.meshes[primitiveIndex].boneWeights = RL_MALLOC(acc->count*4*sizeof(float));
LOAD_ACCESSOR(float, 4, acc, model.meshes[primitiveIndex].boneWeights)
}
} }
cgltf_accessor *acc = data->meshes[i].primitives[p].indices; cgltf_accessor *acc = data->meshes[i].primitives[p].indices;
@ -3913,9 +4033,12 @@ static Model LoadGLTF(const char *fileName)
{ {
model.meshMaterial[primitiveIndex] = model.materialCount - 1;; model.meshMaterial[primitiveIndex] = model.materialCount - 1;;
} }
// if(data->meshes[i].)
primitiveIndex++; primitiveIndex++;
} }
} }
cgltf_free(data); cgltf_free(data);
@ -3926,4 +4049,146 @@ static Model LoadGLTF(const char *fileName)
return model; return model;
} }
// LoadGLTF loads in animation data from given filename
static ModelAnimation* LoadGLTFModelAnimations(const char *fileName, int *animCount)
{
/***********************************************************************************
Function implemented by Hristo Stamenov (@object71)
Features:
- Supports .gltf and .glb files
Some restrictions (not exhaustive):
- ...
*************************************************************************************/
// glTF file loading
unsigned int dataSize = 0;
unsigned char *fileData = LoadFileData(fileName, &dataSize);
ModelAnimation *animations = NULL;
if (fileData == NULL) return animations;
// glTF data loading
cgltf_options options = { 0 };
cgltf_data *data = NULL;
cgltf_result result = cgltf_parse(&options, fileData, dataSize, &data);
if (result == cgltf_result_success)
{
TRACELOG(LOG_INFO, "MODEL: [%s] glTF animations (%s) count: %i", fileName, (data->file_type == 2)? "glb" :
"gltf", data->animations_count);
animations = RL_MALLOC(data->animations_count*sizeof(ModelAnimation));
for (unsigned int a = 0; a < data->animations_count; a++)
{
cgltf_animation *animation = data->animations + a;
ModelAnimation *output = animations + a;
output->frameCount = animation->channels->sampler->input->count;
output->boneCount = data->nodes_count;
output->bones = RL_MALLOC(output->boneCount*sizeof(BoneInfo));
output->framePoses = RL_MALLOC(output->frameCount*sizeof(Transform *));
for (unsigned int j = 0; j < data->nodes_count; j++)
{
strcpy(output->bones[j].name, data->nodes[j].name == 0 ? "ANIMJOINT" : data->nodes[j].name);
output->bones[j].parent = j != 0 ? (int)(data->nodes[j].parent - data->nodes) : 0;
}
for (unsigned int j = 0; j < output->frameCount; j++)
output->framePoses[j] = RL_MALLOC(output->frameCount*data->nodes_count*sizeof(Transform));
for (unsigned int frame = 0; frame < output->frameCount; frame++)
{
for (unsigned int i = 0; i < data->nodes_count; i++)
{
output->framePoses[frame][i].translation = Vector3Zero();
output->framePoses[frame][i].rotation = QuaternionIdentity();
output->framePoses[frame][i].rotation = QuaternionNormalize(output->framePoses[frame][i].rotation);
output->framePoses[frame][i].scale = Vector3One();
}
}
for(int channelId = 0; channelId < animation->channels_count; channelId++)
{
cgltf_animation_channel* channel = animation->channels + channelId;
cgltf_animation_sampler* sampler = channel->sampler;
int boneId = channel->target_node - data->nodes;
for(int frame = 0; frame < output->frameCount; frame++)
{
if(channel->target_path == cgltf_animation_path_type_translation) {
Vector3 translation;
if(cgltf_accessor_read_float(sampler->output, frame, (float*)&translation, 3))
{
output->framePoses[frame][boneId].translation = translation;
}
else if (output->frameCount == 2)
{
memcpy(&translation, frame == 0 ? &(sampler->output->min) : &(sampler->output->max), 3 * sizeof(float));
output->framePoses[frame][boneId].translation = translation;
}
}
if(channel->target_path == cgltf_animation_path_type_rotation) {
Quaternion rotation;
if(cgltf_accessor_read_float(sampler->output, frame, (float*)&rotation, 4))
{
output->framePoses[frame][boneId].rotation = rotation;
output->framePoses[frame][boneId].rotation = QuaternionNormalize(output->framePoses[frame][boneId].rotation);
}
else if (output->frameCount == 2)
{
memcpy(&rotation, frame == 0 ? &(sampler->output->min) : &(sampler->output->max), 4 * sizeof(float));
output->framePoses[frame][boneId].rotation = rotation;
output->framePoses[frame][boneId].rotation = QuaternionNormalize(output->framePoses[frame][boneId].rotation);
}
}
if(channel->target_path == cgltf_animation_path_type_scale) {
Vector3 scale;
if(cgltf_accessor_read_float(sampler->output, frame, (float*)&scale, 4))
{
output->framePoses[frame][boneId].scale = scale;
}
else if (output->frameCount == 2)
{
memcpy(&scale, frame == 0 ? &(sampler->output->min) : &(sampler->output->max), 3 * sizeof(float));
output->framePoses[frame][boneId].scale = scale;
}
}
}
}
// Build frameposes
for (unsigned int frame = 0; frame < output->frameCount; frame++)
{
for (int i = 0; i < output->boneCount; i++)
{
if (output->bones[i].parent >= 0)
{
output->framePoses[frame][i].rotation = QuaternionMultiply(output->framePoses[frame][output->bones[i].parent].rotation, output->framePoses[frame][i].rotation);
output->framePoses[frame][i].translation = Vector3RotateByQuaternion(output->framePoses[frame][i].translation, output->framePoses[frame][output->bones[i].parent].rotation);
output->framePoses[frame][i].translation = Vector3Add(output->framePoses[frame][i].translation, output->framePoses[frame][output->bones[i].parent].translation);
output->framePoses[frame][i].scale = Vector3Multiply(output->framePoses[frame][i].scale, output->framePoses[frame][output->bones[i].parent].scale);
}
}
}
}
cgltf_free(data);
}
else TRACELOG(LOG_WARNING, ": [%s] Failed to load glTF data", fileName);
RL_FREE(fileData);
return animations;
}
#endif #endif

Caricamento…
Annulla
Salva