Skip to content

Commit f39e0a9

Browse files
committed
Finish skeletal animations.
1 parent ee8a2ee commit f39e0a9

14 files changed

Lines changed: 844 additions & 192 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ Supported platforms: Windows (x32, x64), Linux (x32, x64 and ARM64).
1313
- [X] Simple editor
1414
- [X] GLTF/GLB import
1515
- [X] Audio
16-
- [ ] Skeletal animations
16+
- [X] Skeletal animations
1717
- [ ] Particle effects
1818
- [ ] Simple physics engine
1919

src/editor/src/obj_picking.c

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,12 +109,19 @@ obj_picking_find_obj_under_cursor(
109109
break;
110110
}
111111

112-
te_model* child = model_get_child_model(model);
113-
if (child != NULL) {
112+
// Test child models.
113+
unsigned int child_idx = 0;
114+
while (true) {
115+
te_model* child = model_get_child_model(model, child_idx);
116+
if (child == NULL) {
117+
break;
118+
}
119+
114120
if (test_model_hit(
115121
&info, frustum, gizmo, camera_world_pos, camera_world_ray, child)) {
116122
break;
117123
}
124+
child_idx += 1;
118125
}
119126
}
120127

src/editor/src/ui/property_inspector.c

Lines changed: 115 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@
44
#include <stdio.h>
55
#include <type_database.h>
66
#include <game/game_object_info.h>
7+
#include <game/model.h>
8+
#include <game/skeleton.h>
79
#include <io/log.h>
10+
#include <io/filesystem.h>
811
#include <ui/theme.h>
912
#include <ui/editor_ui.h>
1013
#include <ui/world_inspector.h>
@@ -235,6 +238,31 @@ on_button_pilot_camera_clicked(te_button_widget* button) {
235238
property_inspector_hide(inspector);
236239
}
237240

241+
static void on_preview_animation_selected(void* custom, const char* absolute_path_to_file) {
242+
te_property_inspector* inspector = custom;
243+
244+
te_model* model = inspector->obj;
245+
te_skeleton* skeleton = model_get_skeleton(model);
246+
247+
char* relative_path_to_anim = filesystem_convert_path_to_relative(absolute_path_to_file);
248+
249+
skeleton_load_animations(skeleton, relative_path_to_anim);
250+
251+
const char* filename = filesystem_find_filename(relative_path_to_anim, false, NULL);
252+
skeleton_play_animation(skeleton, filename, true);
253+
254+
free(relative_path_to_anim);
255+
}
256+
257+
static void
258+
on_button_preview_animation_clicked(te_button_widget* button) {
259+
te_property_inspector* inspector = widget_get_custom_ptr(button_widget_get_widget(button));
260+
261+
te_editor* editor = editor_ui_get_editor(inspector->ui);
262+
editor_show_file_dialog(
263+
editor, inspector, on_preview_animation_selected, NULL, TE_FDM_SELECT_EXISTING_FILE);
264+
}
265+
238266
void
239267
property_inspector_show(te_property_inspector* inspector, void* obj, const char* obj_type_id) {
240268
if (inspector->obj == obj) {
@@ -274,46 +302,90 @@ property_inspector_show(te_property_inspector* inspector, void* obj, const char*
274302
pos[0] = hpadding;
275303
pos[1] = theme_get_vertical_padding() / 2.0f;
276304

277-
if (type_info->get_game_object_info != NULL && type_info->get_game_object_info(obj)->type == TE_GOT_CAMERA) {
278-
// Add a button to pilot the camera.
279-
te_button_widget* button = button_widget_create();
280-
{
281-
te_widget* widget = button_widget_get_widget(button);
282-
widget_set_custom_ptr(widget, inspector);
283-
widget_set_parent(widget, inspector->right_panel);
284-
widget_set_relative_position(widget, pos);
285-
widget_set_relative_size(widget, size);
286-
}
305+
if (type_info->get_game_object_info != NULL) {
306+
if (type_info->get_game_object_info(obj)->type == TE_GOT_CAMERA) {
307+
// Add a button to pilot the camera.
308+
te_button_widget* button = button_widget_create();
309+
{
310+
te_widget* widget = button_widget_get_widget(button);
311+
widget_set_custom_ptr(widget, inspector);
312+
widget_set_parent(widget, inspector->right_panel);
313+
widget_set_relative_position(widget, pos);
314+
widget_set_relative_size(widget, size);
315+
}
287316

288-
vec4 color;
289-
theme_get_button_color(color);
290-
button_widget_set_color(button, color);
317+
vec4 color;
318+
theme_get_button_color(color);
319+
button_widget_set_color(button, color);
291320

292-
theme_get_button_color_hovered(color);
293-
button_widget_set_color_hovered(button, color);
321+
theme_get_button_color_hovered(color);
322+
button_widget_set_color_hovered(button, color);
294323

295-
theme_get_button_color_pressed(color);
296-
button_widget_set_color_pressed(button, color);
324+
theme_get_button_color_pressed(color);
325+
button_widget_set_color_pressed(button, color);
297326

298-
button_widget_set_on_clicked(button, on_button_pilot_camera_clicked);
327+
button_widget_set_on_clicked(button, on_button_pilot_camera_clicked);
299328

300-
// Button text.
329+
// Button text.
301330

302-
te_text_widget* text_widget = text_widget_create();
303-
{
304-
te_widget* widget = text_widget_get_widget(text_widget);
305-
widget_set_parent(widget, button_widget_get_widget(button));
306-
widget_set_relative_position(widget, (vec2){hpadding, 0.0f});
307-
widget_set_relative_size(widget, (vec2){1.0f - hpadding, 1.0f});
308-
}
331+
te_text_widget* text_widget = text_widget_create();
332+
{
333+
te_widget* widget = text_widget_get_widget(text_widget);
334+
widget_set_parent(widget, button_widget_get_widget(button));
335+
widget_set_relative_position(widget, (vec2){hpadding, 0.0f});
336+
widget_set_relative_size(widget, (vec2){1.0f - hpadding, 1.0f});
337+
}
309338

310-
text_widget_set_text_height(text_widget, theme_get_text_height());
339+
text_widget_set_text_height(text_widget, theme_get_text_height());
340+
341+
unsigned int text_len;
342+
wchar_t* text = wchar_from_char("Pilot camera", &text_len);
343+
text_widget_set_text_own(text_widget, text, text_len);
344+
345+
pos[1] += size[1];
346+
} else if (
347+
type_info->get_game_object_info(obj)->type == TE_GOT_MODEL
348+
&& model_get_skeleton(obj) != NULL) {
349+
// Add a button to preview skeleton animation.
350+
te_button_widget* button = button_widget_create();
351+
{
352+
te_widget* widget = button_widget_get_widget(button);
353+
widget_set_custom_ptr(widget, inspector);
354+
widget_set_parent(widget, inspector->right_panel);
355+
widget_set_relative_position(widget, pos);
356+
widget_set_relative_size(widget, size);
357+
}
311358

312-
unsigned int text_len;
313-
wchar_t* text = wchar_from_char("Pilot camera", &text_len);
314-
text_widget_set_text_own(text_widget, text, text_len);
359+
vec4 color;
360+
theme_get_button_color(color);
361+
button_widget_set_color(button, color);
315362

316-
pos[1] += size[1];
363+
theme_get_button_color_hovered(color);
364+
button_widget_set_color_hovered(button, color);
365+
366+
theme_get_button_color_pressed(color);
367+
button_widget_set_color_pressed(button, color);
368+
369+
button_widget_set_on_clicked(button, on_button_preview_animation_clicked);
370+
371+
// Button text.
372+
373+
te_text_widget* text_widget = text_widget_create();
374+
{
375+
te_widget* widget = text_widget_get_widget(text_widget);
376+
widget_set_parent(widget, button_widget_get_widget(button));
377+
widget_set_relative_position(widget, (vec2){hpadding, 0.0f});
378+
widget_set_relative_size(widget, (vec2){1.0f - hpadding, 1.0f});
379+
}
380+
381+
text_widget_set_text_height(text_widget, theme_get_text_height());
382+
383+
unsigned int text_len;
384+
wchar_t* text = wchar_from_char("Preview animation", &text_len);
385+
text_widget_set_text_own(text_widget, text, text_len);
386+
387+
pos[1] += size[1];
388+
}
317389
}
318390

319391
for (unsigned int var_idx = 0; var_idx < type_info->variable_count; var_idx++) {
@@ -585,6 +657,18 @@ property_inspector_show(te_property_inspector* inspector, void* obj, const char*
585657

586658
void
587659
property_inspector_hide(te_property_inspector* inspector) {
660+
{
661+
// Check if we have a model with skeleton animation preview playing.
662+
const te_type_info* type_info = type_database_get_type_info(inspector->obj_type_id);
663+
if (type_info != NULL && type_info->get_game_object_info != NULL
664+
&& type_info->get_game_object_info(inspector->obj)->type == TE_GOT_MODEL) {
665+
te_skeleton* skeleton = model_get_skeleton(inspector->obj);
666+
if (skeleton != NULL) {
667+
skeleton_unload_animations(skeleton);
668+
}
669+
}
670+
}
671+
588672
inspector->obj = NULL;
589673
inspector->obj_type_id = NULL;
590674

src/editor/src/ui/world_inspector.c

Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -425,7 +425,7 @@ rebuild_item_list_to_display_world_objects(te_world_inspector* inspector) {
425425
}
426426

427427
if (info->type == TE_GOT_MODEL) {
428-
// Models are special because they can have child model or attached camera.
428+
// Models are special because they can have child models or attached camera.
429429
te_model* model = info->game_object;
430430

431431
te_camera* attached_camera = model_get_attached_camera(model);
@@ -434,9 +434,17 @@ rebuild_item_list_to_display_world_objects(te_world_inspector* inspector) {
434434
inspector->item_list_count += 1;
435435
}
436436

437-
te_model* child_model = model_get_child_model(model);
438-
if (child_model != NULL && model_is_serialization_allowed(child_model)) {
439-
inspector->item_list_count += 1;
437+
unsigned int child_idx = 0;
438+
while (true) {
439+
te_model* child_model = model_get_child_model(model, child_idx);
440+
if (child_model == NULL) {
441+
break;
442+
}
443+
444+
if (model_is_serialization_allowed(child_model)) {
445+
inspector->item_list_count += 1;
446+
}
447+
child_idx += 1;
440448
}
441449
}
442450

@@ -479,14 +487,27 @@ rebuild_item_list_to_display_world_objects(te_world_inspector* inspector) {
479487
item_info->game_object_info =
480488
camera_get_game_object_info(attached_camera);
481489
item_info->widget = NULL;
490+
491+
item_idx += 1;
482492
}
483493

484-
te_model* child_model = model_get_child_model(model);
485-
if (child_model != NULL && model_is_serialization_allowed(child_model)) {
486-
te_world_item_info* item_info = &world_items[item_idx];
487-
item_info->indent = 1;
488-
item_info->game_object_info = model_get_game_object_info(child_model);
489-
item_info->widget = NULL;
494+
unsigned int child_idx = 0;
495+
while (true) {
496+
te_model* child_model = model_get_child_model(model, child_idx);
497+
if (child_model == NULL) {
498+
break;
499+
}
500+
501+
if (model_is_serialization_allowed(child_model)) {
502+
te_world_item_info* item_info = &world_items[item_idx];
503+
item_info->indent = 1;
504+
item_info->game_object_info =
505+
model_get_game_object_info(child_model);
506+
item_info->widget = NULL;
507+
508+
item_idx += 1;
509+
}
510+
child_idx += 1;
490511
}
491512
}
492513
}

src/engine_lib/include/game/model.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,8 @@ void model_get_uv_offset(te_model* model, vec2 uv_offset);
150150
// Specify NULL as parent to detach.
151151
void model_set_parent(te_model* model, te_model* new_parent);
152152
te_model* model_get_parent(te_model* model);
153-
te_model* model_get_child_model(te_model* model);
153+
te_model* model_get_child_model(te_model* model, unsigned int index); // if returns NULL for 0 index then has no children
154+
unsigned int model_get_child_model_count(te_model* model);
154155

155156
// Same as @ref model_set_parent but attaches a camera. Specify NULL to detach the camera.
156157
// After attached do not attempt to despawn this camera using the world because the world only operates on "root" cameras.

src/engine_lib/include/game/skeleton.h

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,25 @@
33
#include <cglm/mat4.h>
44
#include <cglm/vec3.h>
55

6+
struct te_game_manager;
7+
68
// Skeleton for skeletal animations.
79
typedef struct te_skeleton te_skeleton;
10+
typedef struct te_skeleton_animation te_skeleton_animation;
811

9-
// Preloads animations from the specified directory (relative to the `res` directory)
12+
// Preloads animation(s) from the specified file/directory (relative to the `res` directory)
1013
// to be later played using @ref skeleton_play_animation.
11-
void skeleton_load_animations(te_skeleton* skeleton, const char* relative_path_to_dir);
14+
void skeleton_load_animations(te_skeleton* skeleton, const char* relative_path);
1215

1316
// Plays an animation (that was previously loaded using @ref skeleton_load_animations)
1417
// accepts animation file name (without extension).
15-
void skeleton_play_animation(te_skeleton* skeleton, const char* anim_file_name);
18+
void skeleton_play_animation(te_skeleton* skeleton, const char* anim_name, bool loop);
1619

17-
// Updates inverse bind pose matrices for all skeleton bones and calculates skinning matrices
18-
// based on the current state of a skeleton (if playing an animation fills matrices
19-
// with animated bone transforms at the current animation time).
20-
//
21-
// Provided @ref skinning_mats must have N matrices where N is the number of bones the skeleton has.
22-
void skeleton_update(te_skeleton* skeleton);
20+
// Stops any currently playing animation.
21+
void skeleton_stop_animation(te_skeleton* skeleton);
22+
23+
// Stops playing animation and unloads all previously loaded animations (from @ref skeleton_load_animations).
24+
void skeleton_unload_animations(te_skeleton* skeleton);
2325

2426
// Returns the total number of bones the skeleton has.
2527
unsigned int skeleton_get_bone_count(te_skeleton* skeleton);
@@ -54,5 +56,12 @@ enum te_keyframe_interpolation_type {
5456
};
5557

5658
// Loads skeleton from a file relative to the `res` directory.
57-
te_skeleton* prv_skeleton_create(const char* relative_path);
59+
te_skeleton* prv_skeleton_create(const char* relative_path, struct te_game_manager* game_manager);
5860
void prv_skeleton_destroy(te_skeleton* skeleton);
61+
62+
// Updates inverse bind pose matrices for all skeleton bones and calculates skinning matrices
63+
// based on the current state of a skeleton (if playing an animation fills matrices
64+
// with animated bone transforms at the current animation time).
65+
//
66+
// Provided @ref skinning_mats must have N matrices where N is the number of bones the skeleton has.
67+
void prv_skeleton_update(te_skeleton* skeleton, float dt);

src/engine_lib/include/game_manager.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,15 @@ struct te_world* game_manager_create_world(te_game_manager* game_manager, const
2121
// Destroys a world previously created using @ref game_manager_create_world.
2222
void game_manager_destroy_world(te_game_manager* game_manager, struct te_world* world);
2323

24+
// Registers a custom function to be called every frame.
25+
// Returns a unique ID of the callback that you need to use to unregister the callback
26+
// in @ref game_manager_unregister_tick_callback.
27+
unsigned int game_manager_add_tick_callback(te_game_manager* game_manager,
28+
void* custom, void (*on_tick)(void* custom, float delta_time_sec));
29+
30+
// Unregisters a callback that was previously registered using @ref game_manager_register_tick_callback.
31+
void game_manager_remove_tick_callback(te_game_manager* game_manager, unsigned int callback_id);
32+
2433
// Returns window that owns game manager.
2534
// Always valid pointer to the window. You should not free/destroy returned pointer.
2635
struct te_window* game_manager_get_window(te_game_manager* game_manager);

src/engine_lib/include/io/filesystem.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ typedef struct te_filesystem_entry {
1515
// ------------------------------------------------------------------------------------------------
1616

1717
bool filesystem_does_path_exists(const char* path);
18+
bool filesystem_path_is_directory(const char* path);
1819

1920
// Recursively creates directories for the specified path (if directories did not existed before).
2021
void filesystem_ensure_dirs_exist(const char* path);
@@ -28,6 +29,7 @@ void filesystem_copy_file(const char* src, const char* dst);
2829

2930
// Returns pointer to the first character of the filename from the specified path string.
3031
// Also works for directories.
32+
// Specify NULL as `ret_len` to ignore returned string length.
3133
// Returns NULL if filename not found.
3234
const char*
3335
filesystem_find_filename(const char* path, bool include_extension, unsigned int* ret_len);

0 commit comments

Comments
 (0)