| 1 | /**************************************************************************/ | 
|---|
| 2 | /*  root_motion_editor_plugin.cpp                                         */ | 
|---|
| 3 | /**************************************************************************/ | 
|---|
| 4 | /*                         This file is part of:                          */ | 
|---|
| 5 | /*                             GODOT ENGINE                               */ | 
|---|
| 6 | /*                        https://godotengine.org                         */ | 
|---|
| 7 | /**************************************************************************/ | 
|---|
| 8 | /* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ | 
|---|
| 9 | /* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur.                  */ | 
|---|
| 10 | /*                                                                        */ | 
|---|
| 11 | /* Permission is hereby granted, free of charge, to any person obtaining  */ | 
|---|
| 12 | /* a copy of this software and associated documentation files (the        */ | 
|---|
| 13 | /* "Software"), to deal in the Software without restriction, including    */ | 
|---|
| 14 | /* without limitation the rights to use, copy, modify, merge, publish,    */ | 
|---|
| 15 | /* distribute, sublicense, and/or sell copies of the Software, and to     */ | 
|---|
| 16 | /* permit persons to whom the Software is furnished to do so, subject to  */ | 
|---|
| 17 | /* the following conditions:                                              */ | 
|---|
| 18 | /*                                                                        */ | 
|---|
| 19 | /* The above copyright notice and this permission notice shall be         */ | 
|---|
| 20 | /* included in all copies or substantial portions of the Software.        */ | 
|---|
| 21 | /*                                                                        */ | 
|---|
| 22 | /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,        */ | 
|---|
| 23 | /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF     */ | 
|---|
| 24 | /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ | 
|---|
| 25 | /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY   */ | 
|---|
| 26 | /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,   */ | 
|---|
| 27 | /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE      */ | 
|---|
| 28 | /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                 */ | 
|---|
| 29 | /**************************************************************************/ | 
|---|
| 30 |  | 
|---|
| 31 | #include "root_motion_editor_plugin.h" | 
|---|
| 32 | #include "editor/editor_node.h" | 
|---|
| 33 | #include "scene/animation/animation_player.h" | 
|---|
| 34 | #include "scene/animation/animation_tree.h" | 
|---|
| 35 | #include "scene/gui/button.h" | 
|---|
| 36 | #include "scene/gui/dialogs.h" | 
|---|
| 37 | #include "scene/gui/tree.h" | 
|---|
| 38 | #include "scene/main/window.h" | 
|---|
| 39 |  | 
|---|
| 40 | void EditorPropertyRootMotion::_confirmed() { | 
|---|
| 41 | TreeItem *ti = filters->get_selected(); | 
|---|
| 42 | if (!ti) { | 
|---|
| 43 | return; | 
|---|
| 44 | } | 
|---|
| 45 |  | 
|---|
| 46 | NodePath path = ti->get_metadata(0); | 
|---|
| 47 | emit_changed(get_edited_property(), path); | 
|---|
| 48 | update_property(); | 
|---|
| 49 | filter_dialog->hide(); //may come from activated | 
|---|
| 50 | } | 
|---|
| 51 |  | 
|---|
| 52 | void EditorPropertyRootMotion::_node_assign() { | 
|---|
| 53 | AnimationTree *atree = Object::cast_to<AnimationTree>(get_edited_object()); | 
|---|
| 54 | if (!atree->has_node(atree->get_animation_player())) { | 
|---|
| 55 | EditorNode::get_singleton()->show_warning(TTR( "AnimationTree has no path set to an AnimationPlayer")); | 
|---|
| 56 | return; | 
|---|
| 57 | } | 
|---|
| 58 | AnimationPlayer *player = Object::cast_to<AnimationPlayer>(atree->get_node(atree->get_animation_player())); | 
|---|
| 59 | if (!player) { | 
|---|
| 60 | EditorNode::get_singleton()->show_warning(TTR( "Path to AnimationPlayer is invalid")); | 
|---|
| 61 | return; | 
|---|
| 62 | } | 
|---|
| 63 |  | 
|---|
| 64 | Node *base = player->get_node(player->get_root()); | 
|---|
| 65 |  | 
|---|
| 66 | if (!base) { | 
|---|
| 67 | EditorNode::get_singleton()->show_warning(TTR( "Animation player has no valid root node path, so unable to retrieve track names.")); | 
|---|
| 68 | return; | 
|---|
| 69 | } | 
|---|
| 70 |  | 
|---|
| 71 | HashSet<String> paths; | 
|---|
| 72 | { | 
|---|
| 73 | List<StringName> animations; | 
|---|
| 74 | player->get_animation_list(&animations); | 
|---|
| 75 |  | 
|---|
| 76 | for (const StringName &E : animations) { | 
|---|
| 77 | Ref<Animation> anim = player->get_animation(E); | 
|---|
| 78 | for (int i = 0; i < anim->get_track_count(); i++) { | 
|---|
| 79 | String pathname = anim->track_get_path(i).get_concatenated_names(); | 
|---|
| 80 | if (!paths.has(pathname)) { | 
|---|
| 81 | paths.insert(pathname); | 
|---|
| 82 | } | 
|---|
| 83 | } | 
|---|
| 84 | } | 
|---|
| 85 | } | 
|---|
| 86 |  | 
|---|
| 87 | filters->clear(); | 
|---|
| 88 | TreeItem *root = filters->create_item(); | 
|---|
| 89 |  | 
|---|
| 90 | HashMap<String, TreeItem *> parenthood; | 
|---|
| 91 |  | 
|---|
| 92 | for (const String &E : paths) { | 
|---|
| 93 | NodePath path = E; | 
|---|
| 94 | TreeItem *ti = nullptr; | 
|---|
| 95 | String accum; | 
|---|
| 96 | for (int i = 0; i < path.get_name_count(); i++) { | 
|---|
| 97 | String name = path.get_name(i); | 
|---|
| 98 | if (!accum.is_empty()) { | 
|---|
| 99 | accum += "/"; | 
|---|
| 100 | } | 
|---|
| 101 | accum += name; | 
|---|
| 102 | if (!parenthood.has(accum)) { | 
|---|
| 103 | if (ti) { | 
|---|
| 104 | ti = filters->create_item(ti); | 
|---|
| 105 | } else { | 
|---|
| 106 | ti = filters->create_item(root); | 
|---|
| 107 | } | 
|---|
| 108 | parenthood[accum] = ti; | 
|---|
| 109 | ti->set_text(0, name); | 
|---|
| 110 | ti->set_selectable(0, false); | 
|---|
| 111 | ti->set_editable(0, false); | 
|---|
| 112 |  | 
|---|
| 113 | if (base->has_node(accum)) { | 
|---|
| 114 | Node *node = base->get_node(accum); | 
|---|
| 115 | ti->set_icon(0, EditorNode::get_singleton()->get_object_icon(node, "Node")); | 
|---|
| 116 | } | 
|---|
| 117 |  | 
|---|
| 118 | } else { | 
|---|
| 119 | ti = parenthood[accum]; | 
|---|
| 120 | } | 
|---|
| 121 | } | 
|---|
| 122 |  | 
|---|
| 123 | Node *node = nullptr; | 
|---|
| 124 | if (base->has_node(accum)) { | 
|---|
| 125 | node = base->get_node(accum); | 
|---|
| 126 | } | 
|---|
| 127 | if (!node) { | 
|---|
| 128 | continue; //no node, can't edit | 
|---|
| 129 | } | 
|---|
| 130 |  | 
|---|
| 131 | Skeleton3D *skeleton = Object::cast_to<Skeleton3D>(node); | 
|---|
| 132 | if (skeleton) { | 
|---|
| 133 | HashMap<int, TreeItem *> items; | 
|---|
| 134 | items.insert(-1, ti); | 
|---|
| 135 | Ref<Texture> bone_icon = get_editor_theme_icon(SNAME( "BoneAttachment3D")); | 
|---|
| 136 | Vector<int> bones_to_process = skeleton->get_parentless_bones(); | 
|---|
| 137 | while (bones_to_process.size() > 0) { | 
|---|
| 138 | int current_bone_idx = bones_to_process[0]; | 
|---|
| 139 | bones_to_process.erase(current_bone_idx); | 
|---|
| 140 |  | 
|---|
| 141 | Vector<int> current_bone_child_bones = skeleton->get_bone_children(current_bone_idx); | 
|---|
| 142 | int child_bone_size = current_bone_child_bones.size(); | 
|---|
| 143 | for (int i = 0; i < child_bone_size; i++) { | 
|---|
| 144 | bones_to_process.push_back(current_bone_child_bones[i]); | 
|---|
| 145 | } | 
|---|
| 146 |  | 
|---|
| 147 | const int parent_idx = skeleton->get_bone_parent(current_bone_idx); | 
|---|
| 148 | TreeItem *parent_item = items.find(parent_idx)->value; | 
|---|
| 149 |  | 
|---|
| 150 | TreeItem *joint_item = filters->create_item(parent_item); | 
|---|
| 151 | items.insert(current_bone_idx, joint_item); | 
|---|
| 152 |  | 
|---|
| 153 | joint_item->set_text(0, skeleton->get_bone_name(current_bone_idx)); | 
|---|
| 154 | joint_item->set_icon(0, bone_icon); | 
|---|
| 155 | joint_item->set_selectable(0, true); | 
|---|
| 156 | joint_item->set_metadata(0, accum + ":"+ skeleton->get_bone_name(current_bone_idx)); | 
|---|
| 157 | joint_item->set_collapsed(true); | 
|---|
| 158 | } | 
|---|
| 159 | } | 
|---|
| 160 | } | 
|---|
| 161 |  | 
|---|
| 162 | filters->ensure_cursor_is_visible(); | 
|---|
| 163 | filter_dialog->popup_centered_ratio(); | 
|---|
| 164 | } | 
|---|
| 165 |  | 
|---|
| 166 | void EditorPropertyRootMotion::_node_clear() { | 
|---|
| 167 | emit_changed(get_edited_property(), NodePath()); | 
|---|
| 168 | update_property(); | 
|---|
| 169 | } | 
|---|
| 170 |  | 
|---|
| 171 | void EditorPropertyRootMotion::update_property() { | 
|---|
| 172 | NodePath p = get_edited_property_value(); | 
|---|
| 173 | assign->set_tooltip_text(p); | 
|---|
| 174 | if (p == NodePath()) { | 
|---|
| 175 | assign->set_icon(Ref<Texture2D>()); | 
|---|
| 176 | assign->set_text(TTR( "Assign...")); | 
|---|
| 177 | assign->set_flat(false); | 
|---|
| 178 | return; | 
|---|
| 179 | } | 
|---|
| 180 |  | 
|---|
| 181 | assign->set_icon(Ref<Texture2D>()); | 
|---|
| 182 | assign->set_text(p); | 
|---|
| 183 | } | 
|---|
| 184 |  | 
|---|
| 185 | void EditorPropertyRootMotion::setup(const NodePath &p_base_hint) { | 
|---|
| 186 | base_hint = p_base_hint; | 
|---|
| 187 | } | 
|---|
| 188 |  | 
|---|
| 189 | void EditorPropertyRootMotion::_notification(int p_what) { | 
|---|
| 190 | switch (p_what) { | 
|---|
| 191 | case NOTIFICATION_ENTER_TREE: | 
|---|
| 192 | case NOTIFICATION_THEME_CHANGED: { | 
|---|
| 193 | Ref<Texture2D> t = get_editor_theme_icon(SNAME( "Clear")); | 
|---|
| 194 | clear->set_icon(t); | 
|---|
| 195 | } break; | 
|---|
| 196 | } | 
|---|
| 197 | } | 
|---|
| 198 |  | 
|---|
| 199 | void EditorPropertyRootMotion::_bind_methods() { | 
|---|
| 200 | } | 
|---|
| 201 |  | 
|---|
| 202 | EditorPropertyRootMotion::EditorPropertyRootMotion() { | 
|---|
| 203 | HBoxContainer *hbc = memnew(HBoxContainer); | 
|---|
| 204 | add_child(hbc); | 
|---|
| 205 | assign = memnew(Button); | 
|---|
| 206 | assign->set_h_size_flags(SIZE_EXPAND_FILL); | 
|---|
| 207 | assign->set_clip_text(true); | 
|---|
| 208 | assign->connect( "pressed", callable_mp(this, &EditorPropertyRootMotion::_node_assign)); | 
|---|
| 209 | hbc->add_child(assign); | 
|---|
| 210 |  | 
|---|
| 211 | clear = memnew(Button); | 
|---|
| 212 | clear->connect( "pressed", callable_mp(this, &EditorPropertyRootMotion::_node_clear)); | 
|---|
| 213 | hbc->add_child(clear); | 
|---|
| 214 |  | 
|---|
| 215 | filter_dialog = memnew(ConfirmationDialog); | 
|---|
| 216 | add_child(filter_dialog); | 
|---|
| 217 | filter_dialog->set_title(TTR( "Edit Filtered Tracks:")); | 
|---|
| 218 | filter_dialog->connect( "confirmed", callable_mp(this, &EditorPropertyRootMotion::_confirmed)); | 
|---|
| 219 |  | 
|---|
| 220 | filters = memnew(Tree); | 
|---|
| 221 | filter_dialog->add_child(filters); | 
|---|
| 222 | filters->set_v_size_flags(SIZE_EXPAND_FILL); | 
|---|
| 223 | filters->set_hide_root(true); | 
|---|
| 224 | filters->connect( "item_activated", callable_mp(this, &EditorPropertyRootMotion::_confirmed)); | 
|---|
| 225 | //filters->connect("item_edited", this, "_filter_edited"); | 
|---|
| 226 | } | 
|---|
| 227 |  | 
|---|
| 228 | ////////////////////////// | 
|---|
| 229 |  | 
|---|
| 230 | bool EditorInspectorRootMotionPlugin::can_handle(Object *p_object) { | 
|---|
| 231 | return true; // Can handle everything. | 
|---|
| 232 | } | 
|---|
| 233 |  | 
|---|
| 234 | bool 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) { | 
|---|
| 235 | if (p_path == "root_motion_track"&& p_object->is_class( "AnimationTree") && p_type == Variant::NODE_PATH) { | 
|---|
| 236 | EditorPropertyRootMotion *editor = memnew(EditorPropertyRootMotion); | 
|---|
| 237 | add_property_editor(p_path, editor); | 
|---|
| 238 | return true; | 
|---|
| 239 | } | 
|---|
| 240 |  | 
|---|
| 241 | return false; | 
|---|
| 242 | } | 
|---|
| 243 |  | 
|---|