Skip to content

Commit d4dc038

Browse files
committed
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
1 parent bee3dc6 commit d4dc038

26 files changed

+1064
-819
lines changed

examples/examples_list.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ models;models_decals;★★★★;5.6-dev;5.6-dev;2025;2025;"JP Mortiboys";@them
173173
models;models_directional_billboard;★★☆☆;5.6-dev;5.6;2025;2025;"Robin";@RobinsAviary
174174
models;models_animation_blend_custom;★★★★;5.5;5.5;2026;2026;"dmitrii-brand";@dmitrii-brand
175175
models;models_animation_blending;☆☆☆☆;5.5;5.6-dev;2024;2024;"Kirandeep";@Kirandeep-Singh-Khehra
176-
models;models_animation_timming;★★☆☆;5.6;5.6;2026;2026;"Ramon Santamaria";@raysan5
176+
models;models_animation_timing;★★☆☆;5.6;5.6;2026;2026;"Ramon Santamaria";@raysan5
177177
shaders;shaders_ascii_rendering;★★☆☆;5.5;5.6;2025;2025;"Maicon Santana";@maiconpintoabreu
178178
shaders;shaders_basic_lighting;★★★★;3.0;4.2;2019;2025;"Chris Camacho";@chriscamacho
179179
shaders;shaders_model_shader;★★☆☆;1.3;3.7;2014;2025;"Ramon Santamaria";@raysan5

examples/models/models_animation_blend_custom.c

Lines changed: 168 additions & 110 deletions
Large diffs are not rendered by default.
58.4 KB
Loading

examples/models/models_animation_blending.c

Lines changed: 171 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@
2121

2222
#include "raylib.h"
2323

24-
#define clamp(x,a,b) ((x < a)? a : (x > b)? b : x)
24+
#define RAYGUI_IMPLEMENTATION
25+
#include "raygui.h" // Required for: UI controls
2526

2627
#if defined(PLATFORM_DESKTOP)
2728
#define GLSL_VERSION 330
@@ -43,31 +44,58 @@ int main(void)
4344

4445
// Define the camera to look into our 3d world
4546
Camera camera = { 0 };
46-
camera.position = (Vector3){ 8.0f, 8.0f, 8.0f }; // Camera position
47+
camera.position = (Vector3){ 6.0f, 6.0f, 6.0f }; // Camera position
4748
camera.target = (Vector3){ 0.0f, 2.0f, 0.0f }; // Camera looking at point
4849
camera.up = (Vector3){ 0.0f, 1.0f, 0.0f }; // Camera up vector (rotation towards target)
4950
camera.fovy = 45.0f; // Camera field-of-view Y
5051
camera.projection = CAMERA_PERSPECTIVE; // Camera projection type
5152

5253
// Load model
53-
Model characterModel = LoadModel("resources/models/gltf/robot.glb"); // Load character model
54-
54+
Model model = LoadModel("resources/models/gltf/robot.glb"); // Load character model
55+
Vector3 position = { 0.0f, 0.0f, 0.0f }; // Set model world position
56+
5557
// Load skinning shader
58+
// WARNING: It requires SUPPORT_GPU_SKINNING enabled on raylib (disabled by default)
5659
Shader skinningShader = LoadShader(TextFormat("resources/shaders/glsl%i/skinning.vs", GLSL_VERSION),
5760
TextFormat("resources/shaders/glsl%i/skinning.fs", GLSL_VERSION));
5861

5962
// Assign skinning shader to all materials shaders
60-
for (int i = 0; i < characterModel.materialCount; i++) characterModel.materials[i].shader = skinningShader;
63+
//for (int i = 0; i < model.materialCount; i++) model.materials[i].shader = skinningShader;
6164

6265
// Load model animations
63-
int animsCount = 0;
64-
ModelAnimation *modelAnimations = LoadModelAnimations("resources/models/gltf/robot.glb", &animsCount);
66+
int animCount = 0;
67+
ModelAnimation *anims = LoadModelAnimations("resources/models/gltf/robot.glb", &animCount);
68+
69+
// Animation playing variables
70+
// NOTE: Two animations are played with a smooth transition between them
71+
int currentAnimPlaying = 0; // Current animation playing (0 o 1)
72+
int nextAnimToPlay = 1; // Next animation to play (to transition)
73+
bool animTransition = false; // Flag to register anim transition state
74+
75+
int animIndex0 = 10; // Current animation playing (walking)
76+
float animCurrentFrame0 = 0.0f; // Current animation frame (supporting interpolated frames)
77+
float animFrameSpeed0 = 0.5f; // Current animation play speed
78+
int animIndex1 = 6; // Next animation to play (running)
79+
float animCurrentFrame1 = 0.0f; // Next animation frame (supporting interpolated frames)
80+
float animFrameSpeed1 = 0.5f; // Next animation play speed
81+
82+
float animBlendFactor = 0.0f; // Blend factor from anim0[frame0] --> anim1[frame1], [0.0f..1.0f]
83+
// NOTE: 0.0f results in full anim0[] and 1.0f in full anim1[]
84+
85+
float animBlendTime = 2.0f; // Time to blend from one playing animation to another (in seconds)
86+
float animBlendTimeCounter = 0.0f; // Time counter (delta time)
6587

66-
// Define animation variables
67-
unsigned int animIndex0 = 0;
68-
unsigned int animIndex1 = 0;
69-
float animCurrentFrame = 0;
70-
float blendFactor = 0.5f;
88+
bool animPause = false; // Pause animation
89+
90+
// UI required variables
91+
char *animNames[64] = { 0 }; // Pointers to animation names for dropdown box
92+
for (int i = 0; i < animCount; i++) animNames[i] = anims[i].name;
93+
94+
bool dropdownEditMode0 = false;
95+
bool dropdownEditMode1 = false;
96+
float animFrameProgress0 = 0.0f;
97+
float animFrameProgress1 = 0.0f;
98+
float animBlendProgress = 0.0f;
7199

72100
SetTargetFPS(60); // Set our game to run at 60 frames-per-second
73101
//--------------------------------------------------------------------------------------
@@ -79,23 +107,100 @@ int main(void)
79107
//----------------------------------------------------------------------------------
80108
UpdateCamera(&camera, CAMERA_ORBITAL);
81109

82-
// Select current animation
83-
if (IsKeyPressed(KEY_T)) animIndex0 = (animIndex0 + 1)%animsCount;
84-
else if (IsKeyPressed(KEY_G)) animIndex0 = (animIndex0 + animsCount - 1)%animsCount;
85-
if (IsKeyPressed(KEY_Y)) animIndex1 = (animIndex1 + 1)%animsCount;
86-
else if (IsKeyPressed(KEY_H)) animIndex1 = (animIndex1 + animsCount - 1)%animsCount;
87-
88-
// Select blend factor
89-
if (IsKeyPressed(KEY_U)) blendFactor = clamp(blendFactor - 0.1, 0.0f, 1.0f);
90-
else if (IsKeyPressed(KEY_J)) blendFactor = clamp(blendFactor + 0.1, 0.0f, 1.0f);
110+
if (IsKeyPressed(KEY_P)) animPause = !animPause;
111+
112+
if (!animPause)
113+
{
114+
// Start transition from anim0[] to anim1[]
115+
if (IsKeyPressed(KEY_SPACE) && !animTransition)
116+
{
117+
if (currentAnimPlaying == 0)
118+
{
119+
// Transition anim0 --> anim1
120+
nextAnimToPlay = 1;
121+
animCurrentFrame1 = 0.0f;
122+
}
123+
else
124+
{
125+
// Transition anim1 --> anim0
126+
nextAnimToPlay = 0;
127+
animCurrentFrame0 = 0.0f;
128+
}
129+
130+
// Set animation transition
131+
animTransition = true;
132+
animBlendTimeCounter = 0.0f;
133+
animBlendFactor = 0.0f;
134+
}
91135

92-
// Update animation
93-
animCurrentFrame += 0.2f;
136+
if (animTransition)
137+
{
138+
// Playing anim0 and anim1 at the same time
139+
animCurrentFrame0 += animFrameSpeed0;
140+
if (animCurrentFrame0 >= anims[animIndex0].keyframeCount) animCurrentFrame0 = 0.0f;
141+
animCurrentFrame1 += animFrameSpeed1;
142+
if (animCurrentFrame1 >= anims[animIndex1].keyframeCount) animCurrentFrame1 = 0.0f;
94143

95-
// Update bones
96-
// Note: Same animation frame index is used below. By default it loops both animations
97-
UpdateModelAnimationEx(characterModel, modelAnimations[animIndex0], animCurrentFrame,
98-
modelAnimations[animIndex1], animCurrentFrame, blendFactor);
144+
// Increment blend factor over time to transition from anim0 --> anim1 over time
145+
// NOTE: Time blending could be other than linear, using some easing
146+
animBlendFactor = animBlendTimeCounter/animBlendTime;
147+
animBlendTimeCounter += GetFrameTime();
148+
animBlendProgress = animBlendFactor;
149+
150+
// Update model with animations blending
151+
if (nextAnimToPlay == 1)
152+
{
153+
// Blend anim0 --> anim1
154+
UpdateModelAnimationEx(model, anims[animIndex0], animCurrentFrame0,
155+
anims[animIndex1], animCurrentFrame1, animBlendFactor);
156+
}
157+
else
158+
{
159+
// Blend anim1 --> anim0
160+
UpdateModelAnimationEx(model, anims[animIndex1], animCurrentFrame1,
161+
anims[animIndex0], animCurrentFrame0, animBlendFactor);
162+
}
163+
164+
// Check if transition completed
165+
if (animBlendFactor > 1.0f)
166+
{
167+
// Reset frame states
168+
if (currentAnimPlaying == 0) animCurrentFrame0 = 0.0f;
169+
else if (currentAnimPlaying == 1) animCurrentFrame1 = 0.0f;
170+
currentAnimPlaying = nextAnimToPlay; // Update current animation playing
171+
172+
animBlendFactor = 0.0f; // Reset blend factor
173+
animTransition = false; // Exit transition mode
174+
animBlendTimeCounter = 0.0f;
175+
}
176+
}
177+
else
178+
{
179+
// Play only one anim, the current one
180+
if (currentAnimPlaying == 0)
181+
{
182+
// Playing anim0 at defined speed
183+
animCurrentFrame0 += animFrameSpeed0;
184+
if (animCurrentFrame0 >= anims[animIndex0].keyframeCount) animCurrentFrame0 = 0.0f;
185+
UpdateModelAnimation(model, anims[animIndex0], animCurrentFrame0);
186+
//UpdateModelAnimationEx(model, anims[animIndex0], animCurrentFrame0,
187+
// anims[animIndex1], animCurrentFrame1, 0.0f);
188+
}
189+
else if (currentAnimPlaying == 1)
190+
{
191+
// Playing anim1 at defined speed
192+
animCurrentFrame1 += animFrameSpeed1;
193+
if (animCurrentFrame1 >= anims[animIndex1].keyframeCount) animCurrentFrame1 = 0.0f;
194+
UpdateModelAnimation(model, anims[animIndex1], animCurrentFrame1);
195+
//UpdateModelAnimationEx(model, anims[animIndex0], animCurrentFrame0,
196+
// anims[animIndex1], animCurrentFrame1, 1.0f);
197+
}
198+
}
199+
}
200+
201+
// Update progress bars values with current frame for each animation
202+
float animFrameProgress0 = animCurrentFrame0;
203+
float animFrameProgress1 = animCurrentFrame1;
99204
//----------------------------------------------------------------------------------
100205

101206
// Draw
@@ -106,26 +211,56 @@ int main(void)
106211

107212
BeginMode3D(camera);
108213

109-
DrawModel(characterModel, (Vector3){0.0f, 0.0f, 0.0f}, 1.0f, WHITE);
214+
DrawModel(model, position, 1.0f, WHITE); // Draw animated model
215+
110216
DrawGrid(10, 1.0f);
111217

112218
EndMode3D();
113219

114-
DrawText("Use the U/J to adjust blend factor", 10, 10, 20, GRAY);
115-
DrawText("Use the T/G to switch first animation", 10, 30, 20, GRAY);
116-
DrawText("Use the Y/H to switch second animation", 10, 50, 20, GRAY);
117-
DrawText(TextFormat("Animations: %s, %s", modelAnimations[animIndex0].name, modelAnimations[animIndex1].name), 10, 70, 20, BLACK);
118-
DrawText(TextFormat("Blend Factor: %f", blendFactor), 10, 86, 20, BLACK);
220+
if (animTransition) DrawText("ANIM TRANSITION BLENDING!", 170, 50, 30, BLUE);
221+
222+
// Draw UI elements
223+
//---------------------------------------------------------------------------------------------
224+
// Draw animation selectors for blending transition
225+
// NOTE: Transition does not start until requested
226+
GuiSetStyle(DROPDOWNBOX, DROPDOWN_ITEMS_SPACING, 1);
227+
if (GuiDropdownBox((Rectangle){ 10, 10, 160, 24 }, TextJoin(animNames, animCount, ";"),
228+
&animIndex0, dropdownEditMode0)) dropdownEditMode0 = !dropdownEditMode0;
229+
230+
// Blending process progress bar
231+
if (nextAnimToPlay == 1) GuiSetStyle(PROGRESSBAR, PROGRESS_SIDE, 0); // Left-->Right
232+
else GuiSetStyle(PROGRESSBAR, PROGRESS_SIDE, 1); // Right-->Left
233+
GuiProgressBar((Rectangle){ 180, 14, 440, 16 }, NULL, NULL, &animBlendProgress, 0.0f, 1.0f);
234+
GuiSetStyle(PROGRESSBAR, PROGRESS_SIDE, 0); // Reset to Left-->Right
235+
236+
if (GuiDropdownBox((Rectangle){ GetScreenWidth() - 170, 10, 160, 24 }, TextJoin(animNames, animCount, ";"),
237+
&animIndex1, dropdownEditMode1)) dropdownEditMode1 = !dropdownEditMode1;
238+
239+
// Draw playing timeline with keyframes for anim0[]
240+
GuiProgressBar((Rectangle){ 60, GetScreenHeight() - 60, GetScreenWidth() - 180, 20 }, "ANIM 0",
241+
TextFormat("FRAME: %.2f / %i", animFrameProgress0, anims[animIndex0].keyframeCount),
242+
&animFrameProgress0, 0.0f, (float)anims[animIndex0].keyframeCount);
243+
for (int i = 0; i < anims[animIndex0].keyframeCount; i++)
244+
DrawRectangle(60 + ((float)(GetScreenWidth() - 180)/(float)anims[animIndex0].keyframeCount)*(float)i,
245+
GetScreenHeight() - 60, 1, 20, BLUE);
246+
247+
// Draw playing timeline with keyframes for anim1[]
248+
GuiProgressBar((Rectangle){ 60, GetScreenHeight() - 30, GetScreenWidth() - 180, 20 }, "ANIM 1",
249+
TextFormat("FRAME: %.2f / %i", animFrameProgress1, anims[animIndex1].keyframeCount),
250+
&animFrameProgress1, 0.0f, (float)anims[animIndex1].keyframeCount);
251+
for (int i = 0; i < anims[animIndex1].keyframeCount; i++)
252+
DrawRectangle(60 + ((float)(GetScreenWidth() - 180)/(float)anims[animIndex1].keyframeCount)*(float)i,
253+
GetScreenHeight() - 30, 1, 20, BLUE);
254+
//---------------------------------------------------------------------------------------------
119255

120256
EndDrawing();
121257
//----------------------------------------------------------------------------------
122258
}
123259

124260
// De-Initialization
125261
//--------------------------------------------------------------------------------------
126-
UnloadModelAnimations(modelAnimations, animsCount); // Unload model animation
127-
UnloadModel(characterModel); // Unload model and meshes/material
128-
262+
UnloadModelAnimations(anims, animCount); // Unload model animation
263+
UnloadModel(model); // Unload model and meshes/material
129264
UnloadShader(skinningShader); // Unload GPU skinning shader
130265

131266
CloseWindow(); // Close window and OpenGL context
1.82 KB
Loading

examples/models/models_animation_gpu_skinning.c

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
*
1111
* WARNING: GPU skinning must be enabled in raylib with a compilation flag,
1212
* if not enabled, CPU skinning will be used instead
13-
* NOTE: Due to limitations in the Apple OpenGL driver, this feature does not work on MacOS
1413
*
1514
* Example licensed under an unmodified zlib/libpng license, which is an OSI-certified,
1615
* BSD-like license that allows static linking with closed source software
16.6 KB
Loading

0 commit comments

Comments
 (0)