From 7cd943fc43213abac48fbcee86cb6779bc2a2640 Mon Sep 17 00:00:00 2001 From: Yuri Rubinsky Date: Sun, 23 Apr 2023 14:53:45 +0300 Subject: [PATCH] Add animation playback preview to scene import settings --- editor/import/scene_import_settings.cpp | 217 ++++++++++++++++++++++-- editor/import/scene_import_settings.h | 20 +++ 2 files changed, 226 insertions(+), 11 deletions(-) diff --git a/editor/import/scene_import_settings.cpp b/editor/import/scene_import_settings.cpp index 92d287c54fe..28105d74205 100644 --- a/editor/import/scene_import_settings.cpp +++ b/editor/import/scene_import_settings.cpp @@ -64,7 +64,7 @@ class SceneImportSettingsData : public Object { current[p_name] = p_value; - // SceneImportSettings must decide if a new collider should be generated or not + // SceneImportSettings must decide if a new collider should be generated or not. if (category == ResourceImporterScene::INTERNAL_IMPORT_CATEGORY_MESH_3D_NODE) { SceneImportSettings::get_singleton()->request_generate_collider(); } @@ -350,8 +350,12 @@ void SceneImportSettings::_fill_scene(Node *p_node, TreeItem *p_parent_item) { category = ResourceImporterScene::INTERNAL_IMPORT_CATEGORY_MESH_3D_NODE; } else if (Object::cast_to(p_node)) { category = ResourceImporterScene::INTERNAL_IMPORT_CATEGORY_ANIMATION_NODE; + + animation_player = Object::cast_to(p_node); + animation_player->connect(SNAME("animation_finished"), callable_mp(this, &SceneImportSettings::_animation_finished)); } else if (Object::cast_to(p_node)) { category = ResourceImporterScene::INTERNAL_IMPORT_CATEGORY_SKELETON_3D_NODE; + skeletons.push_back(Object::cast_to(p_node)); } else { category = ResourceImporterScene::INTERNAL_IMPORT_CATEGORY_NODE; } @@ -413,7 +417,7 @@ void SceneImportSettings::_update_scene() { material_tree->clear(); mesh_tree->clear(); - //hidden roots + // Hidden roots. material_tree->create_item(); mesh_tree->create_item(); @@ -432,7 +436,7 @@ void SceneImportSettings::_update_view_gizmos() { MeshInstance3D *mesh_node = Object::cast_to(e.value.node); if (mesh_node == nullptr || mesh_node->get_mesh().is_null()) { - // Nothing to do + // Nothing to do. continue; } @@ -591,7 +595,7 @@ void SceneImportSettings::open_settings(const String &p_path, bool p_for_animati scene_import_settings_data->settings = nullptr; scene_import_settings_data->path = p_path; - // Visibility + // Visibility. data_mode->set_tab_hidden(1, p_for_animation); data_mode->set_tab_hidden(2, p_for_animation); if (p_for_animation) { @@ -691,12 +695,13 @@ void SceneImportSettings::_select(Tree *p_from, String p_type, String p_id) { scene_import_settings_data->hide_options = false; if (p_type == "Node") { - node_selected->hide(); //always hide just in case + node_selected->hide(); // Always hide just in case. mesh_preview->hide(); + _reset_animation(); + if (Object::cast_to(scene)) { Object::cast_to(scene)->show(); } - //NodeData &nd=node_map[p_id]; material_tree->deselect_all(); mesh_tree->deselect_all(); NodeData &nd = node_map[p_id]; @@ -734,12 +739,13 @@ void SceneImportSettings::_select(Tree *p_from, String p_type, String p_id) { } } } else if (p_type == "Animation") { - node_selected->hide(); //always hide just in case + node_selected->hide(); // Always hide just in case. mesh_preview->hide(); + _reset_animation(p_id); + if (Object::cast_to(scene)) { Object::cast_to(scene)->show(); } - //NodeData &nd=node_map[p_id]; material_tree->deselect_all(); mesh_tree->deselect_all(); AnimationData &ad = animation_map[p_id]; @@ -768,6 +774,7 @@ void SceneImportSettings::_select(Tree *p_from, String p_type, String p_id) { mesh_preview->set_mesh(md.mesh); mesh_preview->show(); + _reset_animation(); material_tree->deselect_all(); @@ -780,6 +787,7 @@ void SceneImportSettings::_select(Tree *p_from, String p_type, String p_id) { } mesh_preview->show(); + _reset_animation(); MaterialData &md = material_map[p_id]; @@ -836,7 +844,7 @@ void SceneImportSettings::_select(Tree *p_from, String p_type, String p_id) { if (scene_import_settings_data->settings) { for (const ResourceImporter::ImportOption &E : options) { scene_import_settings_data->defaults[E.option.name] = E.default_value; - //needed for visibility toggling (fails if something is missing) + // Needed for visibility toggling (fails if something is missing). if (scene_import_settings_data->settings->has(E.option.name)) { scene_import_settings_data->current[E.option.name] = (*scene_import_settings_data->settings)[E.option.name]; } else { @@ -850,6 +858,127 @@ void SceneImportSettings::_select(Tree *p_from, String p_type, String p_id) { scene_import_settings_data->notify_property_list_changed(); } +void SceneImportSettings::_inspector_property_edited(const String &p_name) { + if (p_name == "settings/loop_mode") { + if (!animation_map.has(selected_id)) { + return; + } + HashMap settings = animation_map[selected_id].settings; + if (settings.has(p_name)) { + animation_loop_mode = static_cast((int)settings[p_name]); + } else { + animation_loop_mode = Animation::LoopMode::LOOP_NONE; + } + } +} + +void SceneImportSettings::_reset_bone_transforms() { + for (Skeleton3D *skeleton : skeletons) { + skeleton->reset_bone_poses(); + } +} + +void SceneImportSettings::_play_animation() { + if (animation_player == nullptr) { + return; + } + StringName id = StringName(selected_id); + if (animation_player->has_animation(id)) { + if (animation_player->is_playing()) { + animation_player->pause(); + animation_play_button->set_icon(get_theme_icon(SNAME("MainPlay"), SNAME("EditorIcons"))); + set_process(false); + } else { + animation_player->play(id); + animation_play_button->set_icon(get_theme_icon(SNAME("Pause"), SNAME("EditorIcons"))); + set_process(true); + } + } +} + +void SceneImportSettings::_stop_current_animation() { + animation_pingpong = false; + animation_player->stop(); + animation_play_button->set_icon(get_theme_icon(SNAME("MainPlay"), SNAME("EditorIcons"))); + animation_slider->set_value_no_signal(0.0); + set_process(false); +} + +void SceneImportSettings::_reset_animation(const String &p_animation_name) { + if (p_animation_name.is_empty()) { + animation_preview->hide(); + + if (animation_player != nullptr && animation_player->is_playing()) { + animation_player->stop(); + } + animation_play_button->set_icon(get_theme_icon(SNAME("MainPlay"), SNAME("EditorIcons"))); + + _reset_bone_transforms(); + set_process(false); + } else { + _reset_bone_transforms(); + animation_preview->show(); + + animation_loop_mode = Animation::LoopMode::LOOP_NONE; + animation_pingpong = false; + + if (animation_map.has(p_animation_name)) { + HashMap settings = animation_map[p_animation_name].settings; + if (settings.has("settings/loop_mode")) { + animation_loop_mode = static_cast((int)settings["settings/loop_mode"]); + } + } + + if (animation_player->is_playing() && animation_loop_mode != Animation::LoopMode::LOOP_NONE) { + animation_player->play(p_animation_name); + } else { + animation_player->stop(true); + animation_play_button->set_icon(get_theme_icon(SNAME("MainPlay"), SNAME("EditorIcons"))); + animation_player->set_assigned_animation(p_animation_name); + animation_player->seek(0.0, true); + animation_slider->set_value_no_signal(0.0); + set_process(false); + } + } +} + +void SceneImportSettings::_animation_slider_value_changed(double p_value) { + if (animation_player == nullptr || !animation_map.has(selected_id) || animation_map[selected_id].animation.is_null()) { + return; + } + if (animation_player->is_playing()) { + animation_player->stop(); + animation_play_button->set_icon(get_theme_icon(SNAME("MainPlay"), SNAME("EditorIcons"))); + set_process(false); + } + animation_player->seek(p_value * animation_map[selected_id].animation->get_length(), true); +} + +void SceneImportSettings::_animation_finished(const StringName &p_name) { + Animation::LoopMode loop_mode = animation_loop_mode; + + switch (loop_mode) { + case Animation::LOOP_NONE: { + animation_play_button->set_icon(get_theme_icon(SNAME("MainPlay"), SNAME("EditorIcons"))); + animation_slider->set_value_no_signal(1.0); + set_process(false); + } break; + case Animation::LOOP_LINEAR: { + animation_player->play(p_name); + } break; + case Animation::LOOP_PINGPONG: { + if (animation_pingpong) { + animation_player->play(p_name); + } else { + animation_player->play_backwards(p_name); + } + animation_pingpong = !animation_pingpong; + } break; + default: { + } break; + } +} + void SceneImportSettings::_material_tree_selected() { if (selecting) { return; @@ -884,6 +1013,15 @@ void SceneImportSettings::_scene_tree_selected() { _select(scene_tree, type, import_id); } +void SceneImportSettings::_cleanup() { + skeletons.clear(); + if (animation_player != nullptr) { + animation_player->disconnect(SNAME("animation_finished"), callable_mp(this, &SceneImportSettings::_animation_finished)); + animation_player = nullptr; + } + set_process(false); +} + void SceneImportSettings::_viewport_input(const Ref &p_input) { float *rot_x = &cam_rot_x; float *rot_y = &cam_rot_y; @@ -1005,6 +1143,25 @@ void SceneImportSettings::_notification(int p_what) { action_menu->add_theme_style_override("normal", get_theme_stylebox("normal", "Button")); action_menu->add_theme_style_override("hover", get_theme_stylebox("hover", "Button")); action_menu->add_theme_style_override("pressed", get_theme_stylebox("pressed", "Button")); + + if (animation_player != nullptr && animation_player->is_playing()) { + animation_play_button->set_icon(get_theme_icon(SNAME("Pause"), SNAME("EditorIcons"))); + } else { + animation_play_button->set_icon(get_theme_icon(SNAME("MainPlay"), SNAME("EditorIcons"))); + } + animation_stop_button->set_icon(get_theme_icon(SNAME("Stop"), SNAME("EditorIcons"))); + } break; + + case NOTIFICATION_PROCESS: { + if (animation_player != nullptr) { + animation_slider->set_value_no_signal(animation_player->get_current_animation_position() / animation_player->get_current_animation_length()); + } + } break; + + case NOTIFICATION_VISIBILITY_CHANGED: { + if (!is_visible()) { + _cleanup(); + } } break; } } @@ -1331,16 +1488,53 @@ SceneImportSettings::SceneImportSettings() { material_tree->set_hide_root(true); + VBoxContainer *vp_vb = memnew(VBoxContainer); + vp_vb->set_h_size_flags(Control::SIZE_EXPAND_FILL); + vp_vb->set_v_size_flags(Control::SIZE_EXPAND_FILL); + vp_vb->set_anchors_and_offsets_preset(Control::LayoutPreset::PRESET_FULL_RECT); + property_split->add_child(vp_vb); + SubViewportContainer *vp_container = memnew(SubViewportContainer); - vp_container->set_h_size_flags(Control::SIZE_EXPAND_FILL); + vp_container->set_v_size_flags(Control::SIZE_EXPAND_FILL); vp_container->set_custom_minimum_size(Size2(10, 10)); vp_container->set_stretch(true); vp_container->connect("gui_input", callable_mp(this, &SceneImportSettings::_viewport_input)); - property_split->add_child(vp_container); + vp_vb->add_child(vp_container); base_viewport = memnew(SubViewport); vp_container->add_child(base_viewport); + animation_preview = memnew(PanelContainer); + animation_preview->set_h_size_flags(Control::SIZE_EXPAND_FILL); + vp_vb->add_child(animation_preview); + animation_preview->hide(); + + HBoxContainer *animation_hbox = memnew(HBoxContainer); + animation_preview->add_child(animation_hbox); + + animation_play_button = memnew(Button); + animation_hbox->add_child(animation_play_button); + animation_play_button->set_flat(true); + animation_play_button->set_focus_mode(Control::FOCUS_NONE); + animation_play_button->set_shortcut(ED_SHORTCUT("scene_import_settings/play_selected_animation", TTR("Selected Animation Play/Pause"), Key::SPACE)); + animation_play_button->connect(SNAME("pressed"), callable_mp(this, &SceneImportSettings::_play_animation)); + + animation_stop_button = memnew(Button); + animation_hbox->add_child(animation_stop_button); + animation_stop_button->set_flat(true); + animation_stop_button->set_focus_mode(Control::FOCUS_NONE); + animation_stop_button->connect(SNAME("pressed"), callable_mp(this, &SceneImportSettings::_stop_current_animation)); + + animation_slider = memnew(HSlider); + animation_hbox->add_child(animation_slider); + animation_slider->set_h_size_flags(Control::SIZE_EXPAND_FILL); + animation_slider->set_v_size_flags(Control::SIZE_EXPAND_FILL); + animation_slider->set_max(1.0); + animation_slider->set_step(1.0 / 100.0); + animation_slider->set_value_no_signal(0.0); + animation_slider->set_focus_mode(Control::FOCUS_NONE); + animation_slider->connect(SNAME("value_changed"), callable_mp(this, &SceneImportSettings::_animation_slider_value_changed)); + base_viewport->set_use_own_world_3d(true); camera = memnew(Camera3D); @@ -1406,6 +1600,7 @@ SceneImportSettings::SceneImportSettings() { inspector = memnew(EditorInspector); inspector->set_custom_minimum_size(Size2(300 * EDSCALE, 0)); + inspector->connect(SNAME("property_edited"), callable_mp(this, &SceneImportSettings::_inspector_property_edited)); property_split->add_child(inspector); diff --git a/editor/import/scene_import_settings.h b/editor/import/scene_import_settings.h index d65bb1404a0..b4954b90990 100644 --- a/editor/import/scene_import_settings.h +++ b/editor/import/scene_import_settings.h @@ -35,10 +35,13 @@ #include "scene/3d/camera_3d.h" #include "scene/3d/light_3d.h" #include "scene/3d/mesh_instance_3d.h" +#include "scene/3d/skeleton_3d.h" #include "scene/gui/dialogs.h" #include "scene/gui/item_list.h" #include "scene/gui/menu_button.h" #include "scene/gui/option_button.h" +#include "scene/gui/panel_container.h" +#include "scene/gui/slider.h" #include "scene/gui/split_container.h" #include "scene/gui/subviewport_container.h" #include "scene/gui/tab_container.h" @@ -85,6 +88,15 @@ class SceneImportSettings : public ConfirmationDialog { MeshInstance3D *mesh_preview = nullptr; Ref material_preview; + AnimationPlayer *animation_player = nullptr; + List skeletons; + PanelContainer *animation_preview = nullptr; + HSlider *animation_slider = nullptr; + Button *animation_play_button = nullptr; + Button *animation_stop_button = nullptr; + Animation::LoopMode animation_loop_mode = Animation::LOOP_NONE; + bool animation_pingpong = false; + Ref collider_mat; float cam_rot_x = 0.0f; @@ -151,9 +163,17 @@ class SceneImportSettings : public ConfirmationDialog { void _update_view_gizmos(); void _update_camera(); void _select(Tree *p_from, String p_type, String p_id); + void _inspector_property_edited(const String &p_name); + void _reset_bone_transforms(); + void _play_animation(); + void _stop_current_animation(); + void _reset_animation(const String &p_animation_name = ""); + void _animation_slider_value_changed(double p_value); + void _animation_finished(const StringName &p_name); void _material_tree_selected(); void _mesh_tree_selected(); void _scene_tree_selected(); + void _cleanup(); void _viewport_input(const Ref &p_input);