Path: blob/master/editor/scene/3d/root_motion_editor_plugin.cpp
9902 views
/**************************************************************************/1/* root_motion_editor_plugin.cpp */2/**************************************************************************/3/* This file is part of: */4/* GODOT ENGINE */5/* https://godotengine.org */6/**************************************************************************/7/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */8/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */9/* */10/* Permission is hereby granted, free of charge, to any person obtaining */11/* a copy of this software and associated documentation files (the */12/* "Software"), to deal in the Software without restriction, including */13/* without limitation the rights to use, copy, modify, merge, publish, */14/* distribute, sublicense, and/or sell copies of the Software, and to */15/* permit persons to whom the Software is furnished to do so, subject to */16/* the following conditions: */17/* */18/* The above copyright notice and this permission notice shall be */19/* included in all copies or substantial portions of the Software. */20/* */21/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */22/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */23/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */24/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */25/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */26/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */27/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */28/**************************************************************************/2930#include "root_motion_editor_plugin.h"3132#include "editor/editor_node.h"33#include "editor/themes/editor_scale.h"34#include "scene/3d/skeleton_3d.h"35#include "scene/animation/animation_mixer.h"36#include "scene/gui/button.h"37#include "scene/gui/dialogs.h"38#include "scene/gui/tree.h"3940void EditorPropertyRootMotion::_confirmed() {41TreeItem *ti = filters->get_selected();42if (!ti) {43return;44}4546NodePath path = ti->get_metadata(0);47emit_changed(get_edited_property(), path);48update_property();49filter_dialog->hide(); //may come from activated50}5152void EditorPropertyRootMotion::_node_assign() {53AnimationMixer *mixer = Object::cast_to<AnimationMixer>(get_edited_object());54if (!mixer) {55EditorNode::get_singleton()->show_warning(TTR("Path to AnimationMixer is invalid"));56return;57}5859Node *base = mixer->get_node(mixer->get_root_node());6061if (!base) {62EditorNode::get_singleton()->show_warning(TTR("AnimationMixer has no valid root node path, so unable to retrieve track names."));63return;64}6566HashSet<String> paths;67{68List<StringName> animations;69mixer->get_animation_list(&animations);7071for (const StringName &E : animations) {72Ref<Animation> anim = mixer->get_animation(E);73for (int i = 0; i < anim->get_track_count(); i++) {74String pathname = anim->track_get_path(i).get_concatenated_names();75if (!paths.has(pathname)) {76paths.insert(pathname);77}78}79}80}8182filters->clear();83TreeItem *root = filters->create_item();8485HashMap<String, TreeItem *> parenthood;8687for (const String &E : paths) {88NodePath path = E;89TreeItem *ti = nullptr;90String accum;91for (int i = 0; i < path.get_name_count(); i++) {92String name = path.get_name(i);93if (!accum.is_empty()) {94accum += "/";95}96accum += name;97if (!parenthood.has(accum)) {98if (ti) {99ti = filters->create_item(ti);100} else {101ti = filters->create_item(root);102}103parenthood[accum] = ti;104ti->set_text(0, name);105ti->set_selectable(0, false);106ti->set_editable(0, false);107108if (base->has_node(accum)) {109Node *node = base->get_node(accum);110ti->set_icon(0, EditorNode::get_singleton()->get_object_icon(node, "Node"));111}112113} else {114ti = parenthood[accum];115}116}117118Node *node = nullptr;119if (base->has_node(accum)) {120node = base->get_node(accum);121}122if (!node) {123continue; //no node, can't edit124}125126Skeleton3D *skeleton = Object::cast_to<Skeleton3D>(node);127if (skeleton) {128HashMap<int, TreeItem *> items;129items.insert(-1, ti);130Ref<Texture> bone_icon = get_editor_theme_icon(SNAME("Bone"));131Vector<int> bones_to_process = skeleton->get_parentless_bones();132while (bones_to_process.size() > 0) {133int current_bone_idx = bones_to_process[0];134bones_to_process.erase(current_bone_idx);135136Vector<int> current_bone_child_bones = skeleton->get_bone_children(current_bone_idx);137int child_bone_size = current_bone_child_bones.size();138for (int i = 0; i < child_bone_size; i++) {139bones_to_process.push_back(current_bone_child_bones[i]);140}141142const int parent_idx = skeleton->get_bone_parent(current_bone_idx);143TreeItem *parent_item = items.find(parent_idx)->value;144145TreeItem *joint_item = filters->create_item(parent_item);146items.insert(current_bone_idx, joint_item);147148joint_item->set_text(0, skeleton->get_bone_name(current_bone_idx));149joint_item->set_icon(0, bone_icon);150joint_item->set_selectable(0, true);151joint_item->set_metadata(0, accum + ":" + skeleton->get_bone_name(current_bone_idx));152joint_item->set_collapsed(true);153}154}155}156157filters->ensure_cursor_is_visible();158filter_dialog->popup_centered(Size2(500, 500) * EDSCALE);159}160161void EditorPropertyRootMotion::_node_clear() {162emit_changed(get_edited_property(), NodePath());163update_property();164}165166void EditorPropertyRootMotion::update_property() {167NodePath p = get_edited_property_value();168assign->set_tooltip_text(String(p));169if (p == NodePath()) {170assign->set_button_icon(Ref<Texture2D>());171assign->set_text(TTR("Assign..."));172assign->set_flat(false);173return;174}175176assign->set_button_icon(Ref<Texture2D>());177assign->set_text(String(p));178}179180void EditorPropertyRootMotion::setup(const NodePath &p_base_hint) {181base_hint = p_base_hint;182}183184void EditorPropertyRootMotion::_notification(int p_what) {185switch (p_what) {186case NOTIFICATION_THEME_CHANGED: {187Ref<Texture2D> t = get_editor_theme_icon(SNAME("Clear"));188clear->set_button_icon(t);189} break;190}191}192193EditorPropertyRootMotion::EditorPropertyRootMotion() {194HBoxContainer *hbc = memnew(HBoxContainer);195add_child(hbc);196assign = memnew(Button);197assign->set_accessibility_name(TTRC("Assign"));198assign->set_h_size_flags(SIZE_EXPAND_FILL);199assign->set_clip_text(true);200assign->connect(SceneStringName(pressed), callable_mp(this, &EditorPropertyRootMotion::_node_assign));201hbc->add_child(assign);202203clear = memnew(Button);204clear->set_accessibility_name(TTRC("Clear"));205clear->connect(SceneStringName(pressed), callable_mp(this, &EditorPropertyRootMotion::_node_clear));206hbc->add_child(clear);207208filter_dialog = memnew(ConfirmationDialog);209add_child(filter_dialog);210filter_dialog->set_title(TTR("Edit Filtered Tracks:"));211filter_dialog->connect(SceneStringName(confirmed), callable_mp(this, &EditorPropertyRootMotion::_confirmed));212213filters = memnew(Tree);214filter_dialog->add_child(filters);215filters->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);216filters->set_v_size_flags(SIZE_EXPAND_FILL);217filters->set_hide_root(true);218filters->connect("item_activated", callable_mp(this, &EditorPropertyRootMotion::_confirmed));219//filters->connect("item_edited", this, "_filter_edited");220}221222//////////////////////////223224bool EditorInspectorRootMotionPlugin::can_handle(Object *p_object) {225return true; // Can handle everything.226}227228bool EditorInspectorRootMotionPlugin::parse_property(Object *p_object, const Variant::Type p_type, const String &p_path, const PropertyHint p_hint, const String &p_hint_text, const BitField<PropertyUsageFlags> p_usage, const bool p_wide) {229if (p_path == "root_motion_track" && p_object->is_class("AnimationMixer") && p_type == Variant::NODE_PATH) {230EditorPropertyRootMotion *editor = memnew(EditorPropertyRootMotion);231add_property_editor(p_path, editor);232return true;233}234235return false;236}237238239