mirror of
https://github.com/raysan5/raylib.git
synced 2026-04-13 18:49:10 -04:00
WARNING: BREAKING: REDESIGNED: **Animation System** #4606
REVIEWED: Reorganized structures for a clearer distinction between "skeleton", "skin" and "skinning" data ADDED: New structures: `ModelSkeleton`, `ModelAnimPose` (alias `Transform*`) ADDED: Runtime data `currentPose` and `boneMatrices` to `Model` structure ADDED: Support animation frames-blending, for timing control ADDED: Support animations blending, between two animations REVIEWED: All models animation loading functions ADDED: `UpdateModelAnimationEx()` for two animations blending REMOVED: `UpdateModelAnimationBones*()`, simplified API REVIEWED: Shader attributes/uniforms names for animations, for consistency REVIEWED: Multiple tweaks on animations loading for consistency between formats ADDED: example: `models_animation_timing` ADDED: example: `models_animation_blending` REVIEWED: example: `models_animation_gpu_skinning` REVIEWED: example: `models_animation_blend_custom` REVIEWED: All animated models loading examples
This commit is contained in:
@ -21,7 +21,8 @@
|
||||
|
||||
#include "raylib.h"
|
||||
|
||||
#define clamp(x,a,b) ((x < a)? a : (x > b)? b : x)
|
||||
#define RAYGUI_IMPLEMENTATION
|
||||
#include "raygui.h" // Required for: UI controls
|
||||
|
||||
#if defined(PLATFORM_DESKTOP)
|
||||
#define GLSL_VERSION 330
|
||||
@ -43,31 +44,58 @@ int main(void)
|
||||
|
||||
// Define the camera to look into our 3d world
|
||||
Camera camera = { 0 };
|
||||
camera.position = (Vector3){ 8.0f, 8.0f, 8.0f }; // Camera position
|
||||
camera.position = (Vector3){ 6.0f, 6.0f, 6.0f }; // Camera position
|
||||
camera.target = (Vector3){ 0.0f, 2.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.projection = CAMERA_PERSPECTIVE; // Camera projection type
|
||||
|
||||
// Load model
|
||||
Model characterModel = LoadModel("resources/models/gltf/robot.glb"); // Load character model
|
||||
|
||||
Model model = LoadModel("resources/models/gltf/robot.glb"); // Load character model
|
||||
Vector3 position = { 0.0f, 0.0f, 0.0f }; // Set model world position
|
||||
|
||||
// Load skinning shader
|
||||
// WARNING: It requires SUPPORT_GPU_SKINNING enabled on raylib (disabled by default)
|
||||
Shader skinningShader = LoadShader(TextFormat("resources/shaders/glsl%i/skinning.vs", GLSL_VERSION),
|
||||
TextFormat("resources/shaders/glsl%i/skinning.fs", GLSL_VERSION));
|
||||
|
||||
// Assign skinning shader to all materials shaders
|
||||
for (int i = 0; i < characterModel.materialCount; i++) characterModel.materials[i].shader = skinningShader;
|
||||
//for (int i = 0; i < model.materialCount; i++) model.materials[i].shader = skinningShader;
|
||||
|
||||
// Load model animations
|
||||
int animsCount = 0;
|
||||
ModelAnimation *modelAnimations = LoadModelAnimations("resources/models/gltf/robot.glb", &animsCount);
|
||||
int animCount = 0;
|
||||
ModelAnimation *anims = LoadModelAnimations("resources/models/gltf/robot.glb", &animCount);
|
||||
|
||||
// Define animation variables
|
||||
unsigned int animIndex0 = 0;
|
||||
unsigned int animIndex1 = 0;
|
||||
float animCurrentFrame = 0;
|
||||
float blendFactor = 0.5f;
|
||||
// Animation playing variables
|
||||
// NOTE: Two animations are played with a smooth transition between them
|
||||
int currentAnimPlaying = 0; // Current animation playing (0 o 1)
|
||||
int nextAnimToPlay = 1; // Next animation to play (to transition)
|
||||
bool animTransition = false; // Flag to register anim transition state
|
||||
|
||||
int animIndex0 = 10; // Current animation playing (walking)
|
||||
float animCurrentFrame0 = 0.0f; // Current animation frame (supporting interpolated frames)
|
||||
float animFrameSpeed0 = 0.5f; // Current animation play speed
|
||||
int animIndex1 = 6; // Next animation to play (running)
|
||||
float animCurrentFrame1 = 0.0f; // Next animation frame (supporting interpolated frames)
|
||||
float animFrameSpeed1 = 0.5f; // Next animation play speed
|
||||
|
||||
float animBlendFactor = 0.0f; // Blend factor from anim0[frame0] --> anim1[frame1], [0.0f..1.0f]
|
||||
// NOTE: 0.0f results in full anim0[] and 1.0f in full anim1[]
|
||||
|
||||
float animBlendTime = 2.0f; // Time to blend from one playing animation to another (in seconds)
|
||||
float animBlendTimeCounter = 0.0f; // Time counter (delta time)
|
||||
|
||||
bool animPause = false; // Pause animation
|
||||
|
||||
// UI required variables
|
||||
char *animNames[64] = { 0 }; // Pointers to animation names for dropdown box
|
||||
for (int i = 0; i < animCount; i++) animNames[i] = anims[i].name;
|
||||
|
||||
bool dropdownEditMode0 = false;
|
||||
bool dropdownEditMode1 = false;
|
||||
float animFrameProgress0 = 0.0f;
|
||||
float animFrameProgress1 = 0.0f;
|
||||
float animBlendProgress = 0.0f;
|
||||
|
||||
SetTargetFPS(60); // Set our game to run at 60 frames-per-second
|
||||
//--------------------------------------------------------------------------------------
|
||||
@ -79,23 +107,100 @@ int main(void)
|
||||
//----------------------------------------------------------------------------------
|
||||
UpdateCamera(&camera, CAMERA_ORBITAL);
|
||||
|
||||
// Select current animation
|
||||
if (IsKeyPressed(KEY_T)) animIndex0 = (animIndex0 + 1)%animsCount;
|
||||
else if (IsKeyPressed(KEY_G)) animIndex0 = (animIndex0 + animsCount - 1)%animsCount;
|
||||
if (IsKeyPressed(KEY_Y)) animIndex1 = (animIndex1 + 1)%animsCount;
|
||||
else if (IsKeyPressed(KEY_H)) animIndex1 = (animIndex1 + animsCount - 1)%animsCount;
|
||||
|
||||
// Select blend factor
|
||||
if (IsKeyPressed(KEY_U)) blendFactor = clamp(blendFactor - 0.1, 0.0f, 1.0f);
|
||||
else if (IsKeyPressed(KEY_J)) blendFactor = clamp(blendFactor + 0.1, 0.0f, 1.0f);
|
||||
if (IsKeyPressed(KEY_P)) animPause = !animPause;
|
||||
|
||||
// Update animation
|
||||
animCurrentFrame += 0.2f;
|
||||
if (!animPause)
|
||||
{
|
||||
// Start transition from anim0[] to anim1[]
|
||||
if (IsKeyPressed(KEY_SPACE) && !animTransition)
|
||||
{
|
||||
if (currentAnimPlaying == 0)
|
||||
{
|
||||
// Transition anim0 --> anim1
|
||||
nextAnimToPlay = 1;
|
||||
animCurrentFrame1 = 0.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Transition anim1 --> anim0
|
||||
nextAnimToPlay = 0;
|
||||
animCurrentFrame0 = 0.0f;
|
||||
}
|
||||
|
||||
// Update bones
|
||||
// Note: Same animation frame index is used below. By default it loops both animations
|
||||
UpdateModelAnimationEx(characterModel, modelAnimations[animIndex0], animCurrentFrame,
|
||||
modelAnimations[animIndex1], animCurrentFrame, blendFactor);
|
||||
// Set animation transition
|
||||
animTransition = true;
|
||||
animBlendTimeCounter = 0.0f;
|
||||
animBlendFactor = 0.0f;
|
||||
}
|
||||
|
||||
if (animTransition)
|
||||
{
|
||||
// Playing anim0 and anim1 at the same time
|
||||
animCurrentFrame0 += animFrameSpeed0;
|
||||
if (animCurrentFrame0 >= anims[animIndex0].keyframeCount) animCurrentFrame0 = 0.0f;
|
||||
animCurrentFrame1 += animFrameSpeed1;
|
||||
if (animCurrentFrame1 >= anims[animIndex1].keyframeCount) animCurrentFrame1 = 0.0f;
|
||||
|
||||
// Increment blend factor over time to transition from anim0 --> anim1 over time
|
||||
// NOTE: Time blending could be other than linear, using some easing
|
||||
animBlendFactor = animBlendTimeCounter/animBlendTime;
|
||||
animBlendTimeCounter += GetFrameTime();
|
||||
animBlendProgress = animBlendFactor;
|
||||
|
||||
// Update model with animations blending
|
||||
if (nextAnimToPlay == 1)
|
||||
{
|
||||
// Blend anim0 --> anim1
|
||||
UpdateModelAnimationEx(model, anims[animIndex0], animCurrentFrame0,
|
||||
anims[animIndex1], animCurrentFrame1, animBlendFactor);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Blend anim1 --> anim0
|
||||
UpdateModelAnimationEx(model, anims[animIndex1], animCurrentFrame1,
|
||||
anims[animIndex0], animCurrentFrame0, animBlendFactor);
|
||||
}
|
||||
|
||||
// Check if transition completed
|
||||
if (animBlendFactor > 1.0f)
|
||||
{
|
||||
// Reset frame states
|
||||
if (currentAnimPlaying == 0) animCurrentFrame0 = 0.0f;
|
||||
else if (currentAnimPlaying == 1) animCurrentFrame1 = 0.0f;
|
||||
currentAnimPlaying = nextAnimToPlay; // Update current animation playing
|
||||
|
||||
animBlendFactor = 0.0f; // Reset blend factor
|
||||
animTransition = false; // Exit transition mode
|
||||
animBlendTimeCounter = 0.0f;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Play only one anim, the current one
|
||||
if (currentAnimPlaying == 0)
|
||||
{
|
||||
// Playing anim0 at defined speed
|
||||
animCurrentFrame0 += animFrameSpeed0;
|
||||
if (animCurrentFrame0 >= anims[animIndex0].keyframeCount) animCurrentFrame0 = 0.0f;
|
||||
UpdateModelAnimation(model, anims[animIndex0], animCurrentFrame0);
|
||||
//UpdateModelAnimationEx(model, anims[animIndex0], animCurrentFrame0,
|
||||
// anims[animIndex1], animCurrentFrame1, 0.0f);
|
||||
}
|
||||
else if (currentAnimPlaying == 1)
|
||||
{
|
||||
// Playing anim1 at defined speed
|
||||
animCurrentFrame1 += animFrameSpeed1;
|
||||
if (animCurrentFrame1 >= anims[animIndex1].keyframeCount) animCurrentFrame1 = 0.0f;
|
||||
UpdateModelAnimation(model, anims[animIndex1], animCurrentFrame1);
|
||||
//UpdateModelAnimationEx(model, anims[animIndex0], animCurrentFrame0,
|
||||
// anims[animIndex1], animCurrentFrame1, 1.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update progress bars values with current frame for each animation
|
||||
float animFrameProgress0 = animCurrentFrame0;
|
||||
float animFrameProgress1 = animCurrentFrame1;
|
||||
//----------------------------------------------------------------------------------
|
||||
|
||||
// Draw
|
||||
@ -106,16 +211,47 @@ int main(void)
|
||||
|
||||
BeginMode3D(camera);
|
||||
|
||||
DrawModel(characterModel, (Vector3){0.0f, 0.0f, 0.0f}, 1.0f, WHITE);
|
||||
DrawModel(model, position, 1.0f, WHITE); // Draw animated model
|
||||
|
||||
DrawGrid(10, 1.0f);
|
||||
|
||||
EndMode3D();
|
||||
|
||||
DrawText("Use the U/J to adjust blend factor", 10, 10, 20, GRAY);
|
||||
DrawText("Use the T/G to switch first animation", 10, 30, 20, GRAY);
|
||||
DrawText("Use the Y/H to switch second animation", 10, 50, 20, GRAY);
|
||||
DrawText(TextFormat("Animations: %s, %s", modelAnimations[animIndex0].name, modelAnimations[animIndex1].name), 10, 70, 20, BLACK);
|
||||
DrawText(TextFormat("Blend Factor: %f", blendFactor), 10, 86, 20, BLACK);
|
||||
if (animTransition) DrawText("ANIM TRANSITION BLENDING!", 170, 50, 30, BLUE);
|
||||
|
||||
// Draw UI elements
|
||||
//---------------------------------------------------------------------------------------------
|
||||
// Draw animation selectors for blending transition
|
||||
// NOTE: Transition does not start until requested
|
||||
GuiSetStyle(DROPDOWNBOX, DROPDOWN_ITEMS_SPACING, 1);
|
||||
if (GuiDropdownBox((Rectangle){ 10, 10, 160, 24 }, TextJoin(animNames, animCount, ";"),
|
||||
&animIndex0, dropdownEditMode0)) dropdownEditMode0 = !dropdownEditMode0;
|
||||
|
||||
// Blending process progress bar
|
||||
if (nextAnimToPlay == 1) GuiSetStyle(PROGRESSBAR, PROGRESS_SIDE, 0); // Left-->Right
|
||||
else GuiSetStyle(PROGRESSBAR, PROGRESS_SIDE, 1); // Right-->Left
|
||||
GuiProgressBar((Rectangle){ 180, 14, 440, 16 }, NULL, NULL, &animBlendProgress, 0.0f, 1.0f);
|
||||
GuiSetStyle(PROGRESSBAR, PROGRESS_SIDE, 0); // Reset to Left-->Right
|
||||
|
||||
if (GuiDropdownBox((Rectangle){ GetScreenWidth() - 170, 10, 160, 24 }, TextJoin(animNames, animCount, ";"),
|
||||
&animIndex1, dropdownEditMode1)) dropdownEditMode1 = !dropdownEditMode1;
|
||||
|
||||
// Draw playing timeline with keyframes for anim0[]
|
||||
GuiProgressBar((Rectangle){ 60, GetScreenHeight() - 60, GetScreenWidth() - 180, 20 }, "ANIM 0",
|
||||
TextFormat("FRAME: %.2f / %i", animFrameProgress0, anims[animIndex0].keyframeCount),
|
||||
&animFrameProgress0, 0.0f, (float)anims[animIndex0].keyframeCount);
|
||||
for (int i = 0; i < anims[animIndex0].keyframeCount; i++)
|
||||
DrawRectangle(60 + ((float)(GetScreenWidth() - 180)/(float)anims[animIndex0].keyframeCount)*(float)i,
|
||||
GetScreenHeight() - 60, 1, 20, BLUE);
|
||||
|
||||
// Draw playing timeline with keyframes for anim1[]
|
||||
GuiProgressBar((Rectangle){ 60, GetScreenHeight() - 30, GetScreenWidth() - 180, 20 }, "ANIM 1",
|
||||
TextFormat("FRAME: %.2f / %i", animFrameProgress1, anims[animIndex1].keyframeCount),
|
||||
&animFrameProgress1, 0.0f, (float)anims[animIndex1].keyframeCount);
|
||||
for (int i = 0; i < anims[animIndex1].keyframeCount; i++)
|
||||
DrawRectangle(60 + ((float)(GetScreenWidth() - 180)/(float)anims[animIndex1].keyframeCount)*(float)i,
|
||||
GetScreenHeight() - 30, 1, 20, BLUE);
|
||||
//---------------------------------------------------------------------------------------------
|
||||
|
||||
EndDrawing();
|
||||
//----------------------------------------------------------------------------------
|
||||
@ -123,9 +259,8 @@ int main(void)
|
||||
|
||||
// De-Initialization
|
||||
//--------------------------------------------------------------------------------------
|
||||
UnloadModelAnimations(modelAnimations, animsCount); // Unload model animation
|
||||
UnloadModel(characterModel); // Unload model and meshes/material
|
||||
|
||||
UnloadModelAnimations(anims, animCount); // Unload model animation
|
||||
UnloadModel(model); // Unload model and meshes/material
|
||||
UnloadShader(skinningShader); // Unload GPU skinning shader
|
||||
|
||||
CloseWindow(); // Close window and OpenGL context
|
||||
|
||||
Reference in New Issue
Block a user