Path: blob/master/editor/scene/3d/bone_map_editor_plugin.cpp
9903 views
/**************************************************************************/1/* bone_map_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 "bone_map_editor_plugin.h"3132#include "editor/import/3d/post_import_plugin_skeleton_renamer.h"33#include "editor/import/3d/post_import_plugin_skeleton_rest_fixer.h"34#include "editor/import/3d/post_import_plugin_skeleton_track_organizer.h"35#include "editor/import/3d/scene_import_settings.h"36#include "editor/settings/editor_settings.h"37#include "editor/themes/editor_scale.h"38#include "editor/themes/editor_theme_manager.h"39#include "scene/gui/aspect_ratio_container.h"40#include "scene/gui/separator.h"41#include "scene/gui/texture_rect.h"4243#include "modules/regex/regex.h"4445void BoneMapperButton::fetch_textures() {46if (selected) {47set_texture_normal(get_editor_theme_icon(SNAME("BoneMapperHandleSelected")));48} else {49set_texture_normal(get_editor_theme_icon(SNAME("BoneMapperHandle")));50}51set_offset(SIDE_LEFT, 0);52set_offset(SIDE_RIGHT, 0);53set_offset(SIDE_TOP, 0);54set_offset(SIDE_BOTTOM, 0);5556// Hack to avoid handle color darkening...57set_modulate(EditorThemeManager::is_dark_theme() ? Color(1, 1, 1) : Color(4.25, 4.25, 4.25));5859circle = memnew(TextureRect);60circle->set_texture(get_editor_theme_icon(SNAME("BoneMapperHandleCircle")));61add_child(circle);62set_state(BONE_MAP_STATE_UNSET);63}6465StringName BoneMapperButton::get_profile_bone_name() const {66return profile_bone_name;67}6869void BoneMapperButton::set_state(BoneMapState p_state) {70switch (p_state) {71case BONE_MAP_STATE_UNSET: {72circle->set_modulate(EDITOR_GET("editors/bone_mapper/handle_colors/unset"));73} break;74case BONE_MAP_STATE_SET: {75circle->set_modulate(EDITOR_GET("editors/bone_mapper/handle_colors/set"));76} break;77case BONE_MAP_STATE_MISSING: {78circle->set_modulate(EDITOR_GET("editors/bone_mapper/handle_colors/missing"));79} break;80case BONE_MAP_STATE_ERROR: {81circle->set_modulate(EDITOR_GET("editors/bone_mapper/handle_colors/error"));82} break;83default: {84} break;85}86}8788bool BoneMapperButton::is_require() const {89return require;90}9192void BoneMapperButton::_notification(int p_what) {93switch (p_what) {94case NOTIFICATION_ENTER_TREE: {95fetch_textures();96} break;97}98}99100BoneMapperButton::BoneMapperButton(const StringName &p_profile_bone_name, bool p_require, bool p_selected) {101profile_bone_name = p_profile_bone_name;102require = p_require;103selected = p_selected;104}105106void BoneMapperItem::create_editor() {107HBoxContainer *hbox = memnew(HBoxContainer);108add_child(hbox);109110skeleton_bone_selector = memnew(EditorPropertyText);111skeleton_bone_selector->set_label(profile_bone_name);112skeleton_bone_selector->set_selectable(false);113skeleton_bone_selector->set_h_size_flags(SIZE_EXPAND_FILL);114skeleton_bone_selector->set_object_and_property(bone_map.ptr(), "bone_map/" + String(profile_bone_name));115skeleton_bone_selector->update_property();116skeleton_bone_selector->connect("property_changed", callable_mp(this, &BoneMapperItem::_value_changed));117hbox->add_child(skeleton_bone_selector);118119picker_button = memnew(Button);120picker_button->set_button_icon(get_editor_theme_icon(SNAME("ClassList")));121picker_button->connect(SceneStringName(pressed), callable_mp(this, &BoneMapperItem::_open_picker));122hbox->add_child(picker_button);123124add_child(memnew(HSeparator));125}126127void BoneMapperItem::_update_property() {128if (skeleton_bone_selector->get_edited_object() && skeleton_bone_selector->get_edited_property()) {129skeleton_bone_selector->update_property();130}131}132133void BoneMapperItem::_open_picker() {134emit_signal(SNAME("pick"), profile_bone_name);135}136137void BoneMapperItem::_value_changed(const String &p_property, const Variant &p_value, const String &p_name, bool p_changing) {138bone_map->set(p_property, p_value);139}140141void BoneMapperItem::_notification(int p_what) {142switch (p_what) {143case NOTIFICATION_ENTER_TREE: {144create_editor();145bone_map->connect("bone_map_updated", callable_mp(this, &BoneMapperItem::_update_property));146} break;147case NOTIFICATION_EXIT_TREE: {148if (bone_map.is_valid() && bone_map->is_connected("bone_map_updated", callable_mp(this, &BoneMapperItem::_update_property))) {149bone_map->disconnect("bone_map_updated", callable_mp(this, &BoneMapperItem::_update_property));150}151} break;152}153}154155void BoneMapperItem::_bind_methods() {156ADD_SIGNAL(MethodInfo("pick", PropertyInfo(Variant::STRING_NAME, "profile_bone_name")));157}158159BoneMapperItem::BoneMapperItem(Ref<BoneMap> &p_bone_map, const StringName &p_profile_bone_name) {160bone_map = p_bone_map;161profile_bone_name = p_profile_bone_name;162}163164void BonePicker::create_editors() {165set_title(TTR("Bone Picker:"));166167VBoxContainer *vbox = memnew(VBoxContainer);168add_child(vbox);169170bones = memnew(Tree);171bones->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);172bones->set_select_mode(Tree::SELECT_SINGLE);173bones->set_v_size_flags(Control::SIZE_EXPAND_FILL);174bones->set_hide_root(true);175bones->connect("item_activated", callable_mp(this, &BonePicker::_confirm));176vbox->add_child(bones);177178create_bones_tree(skeleton);179}180181void BonePicker::create_bones_tree(Skeleton3D *p_skeleton) {182bones->clear();183184if (!p_skeleton) {185return;186}187188TreeItem *root = bones->create_item();189190HashMap<int, TreeItem *> items;191192items.insert(-1, root);193194Ref<Texture> bone_icon = get_editor_theme_icon(SNAME("Bone"));195196Vector<int> bones_to_process = p_skeleton->get_parentless_bones();197bool is_first = true;198while (bones_to_process.size() > 0) {199int current_bone_idx = bones_to_process[0];200bones_to_process.erase(current_bone_idx);201202Vector<int> current_bone_child_bones = p_skeleton->get_bone_children(current_bone_idx);203int child_bone_size = current_bone_child_bones.size();204for (int i = 0; i < child_bone_size; i++) {205bones_to_process.push_back(current_bone_child_bones[i]);206}207208const int parent_idx = p_skeleton->get_bone_parent(current_bone_idx);209TreeItem *parent_item = items.find(parent_idx)->value;210211TreeItem *joint_item = bones->create_item(parent_item);212items.insert(current_bone_idx, joint_item);213214joint_item->set_text(0, p_skeleton->get_bone_name(current_bone_idx));215joint_item->set_icon(0, bone_icon);216joint_item->set_selectable(0, true);217joint_item->set_metadata(0, "bones/" + itos(current_bone_idx));218if (is_first) {219is_first = false;220} else {221joint_item->set_collapsed(true);222}223}224}225226void BonePicker::_confirm() {227_ok_pressed();228}229230void BonePicker::popup_bones_tree(const Size2i &p_minsize) {231popup_centered(p_minsize);232}233234bool BonePicker::has_selected_bone() {235TreeItem *selected = bones->get_selected();236if (!selected) {237return false;238}239return true;240}241242StringName BonePicker::get_selected_bone() {243TreeItem *selected = bones->get_selected();244if (!selected) {245return StringName();246}247return selected->get_text(0);248}249250void BonePicker::_notification(int p_what) {251switch (p_what) {252case NOTIFICATION_ENTER_TREE: {253create_editors();254} break;255}256}257258BonePicker::BonePicker(Skeleton3D *p_skeleton) {259skeleton = p_skeleton;260}261262void BoneMapper::create_editor() {263// Create Bone picker.264picker = memnew(BonePicker(skeleton));265picker->connect(SceneStringName(confirmed), callable_mp(this, &BoneMapper::_apply_picker_selection));266add_child(picker, false, INTERNAL_MODE_FRONT);267268profile_selector = memnew(EditorPropertyResource);269profile_selector->setup(bone_map.ptr(), "profile", "SkeletonProfile");270profile_selector->set_label("Profile");271profile_selector->set_selectable(false);272profile_selector->set_object_and_property(bone_map.ptr(), "profile");273profile_selector->update_property();274profile_selector->connect("property_changed", callable_mp(this, &BoneMapper::_profile_changed));275add_child(profile_selector);276add_child(memnew(HSeparator));277278HBoxContainer *group_hbox = memnew(HBoxContainer);279add_child(group_hbox);280281profile_group_selector = memnew(EditorPropertyEnum);282profile_group_selector->set_label("Group");283profile_group_selector->set_selectable(false);284profile_group_selector->set_h_size_flags(SIZE_EXPAND_FILL);285profile_group_selector->set_object_and_property(this, "current_group_idx");286profile_group_selector->update_property();287profile_group_selector->connect("property_changed", callable_mp(this, &BoneMapper::_value_changed));288group_hbox->add_child(profile_group_selector);289290clear_mapping_button = memnew(Button);291clear_mapping_button->set_button_icon(get_editor_theme_icon(SNAME("Clear")));292clear_mapping_button->set_tooltip_text(TTR("Clear mappings in current group."));293clear_mapping_button->connect(SceneStringName(pressed), callable_mp(this, &BoneMapper::_clear_mapping_current_group));294group_hbox->add_child(clear_mapping_button);295296bone_mapper_field = memnew(AspectRatioContainer);297bone_mapper_field->set_stretch_mode(AspectRatioContainer::STRETCH_FIT);298bone_mapper_field->set_custom_minimum_size(Vector2(0, 256.0) * EDSCALE);299bone_mapper_field->set_h_size_flags(Control::SIZE_FILL);300add_child(bone_mapper_field);301302profile_bg = memnew(ColorRect);303profile_bg->set_color(Color(0, 0, 0, 1));304profile_bg->set_h_size_flags(Control::SIZE_FILL);305profile_bg->set_v_size_flags(Control::SIZE_FILL);306bone_mapper_field->add_child(profile_bg);307308profile_texture = memnew(TextureRect);309profile_texture->set_stretch_mode(TextureRect::STRETCH_KEEP_ASPECT_CENTERED);310profile_texture->set_expand_mode(TextureRect::EXPAND_IGNORE_SIZE);311profile_texture->set_h_size_flags(Control::SIZE_FILL);312profile_texture->set_v_size_flags(Control::SIZE_FILL);313bone_mapper_field->add_child(profile_texture);314315mapper_item_vbox = memnew(VBoxContainer);316add_child(mapper_item_vbox);317318recreate_items();319}320321void BoneMapper::update_group_idx() {322if (bone_map->get_profile().is_null()) {323return;324}325326PackedStringArray group_names;327int len = bone_map->get_profile()->get_group_size();328for (int i = 0; i < len; i++) {329group_names.push_back(bone_map->get_profile()->get_group_name(i));330}331if (current_group_idx >= len) {332current_group_idx = 0;333}334if (len > 0) {335profile_group_selector->setup(group_names);336profile_group_selector->update_property();337profile_group_selector->set_read_only(false);338}339}340341void BoneMapper::_pick_bone(const StringName &p_bone_name) {342picker_key_name = p_bone_name;343picker->popup_bones_tree(Size2(500, 500) * EDSCALE);344}345346void BoneMapper::_apply_picker_selection() {347if (!picker->has_selected_bone()) {348return;349}350bone_map->set_skeleton_bone_name(picker_key_name, picker->get_selected_bone());351}352353void BoneMapper::set_current_group_idx(int p_group_idx) {354current_group_idx = p_group_idx;355recreate_editor();356}357358int BoneMapper::get_current_group_idx() const {359return current_group_idx;360}361362void BoneMapper::set_current_bone_idx(int p_bone_idx) {363current_bone_idx = p_bone_idx;364recreate_editor();365}366367int BoneMapper::get_current_bone_idx() const {368return current_bone_idx;369}370371void BoneMapper::recreate_editor() {372// Clear buttons.373int len = bone_mapper_buttons.size();374for (int i = 0; i < len; i++) {375profile_texture->remove_child(bone_mapper_buttons[i]);376memdelete(bone_mapper_buttons[i]);377}378bone_mapper_buttons.clear();379380// Organize mapper items.381len = bone_mapper_items.size();382for (int i = 0; i < len; i++) {383bone_mapper_items[i]->set_visible(current_bone_idx == i);384}385386Ref<SkeletonProfile> profile = bone_map->get_profile();387if (profile.is_valid()) {388SkeletonProfileHumanoid *hmn = Object::cast_to<SkeletonProfileHumanoid>(profile.ptr());389if (hmn) {390StringName hmn_group_name = profile->get_group_name(current_group_idx);391if (hmn_group_name == "Body") {392profile_texture->set_texture(get_editor_theme_icon(SNAME("BoneMapHumanBody")));393} else if (hmn_group_name == "Face") {394profile_texture->set_texture(get_editor_theme_icon(SNAME("BoneMapHumanFace")));395} else if (hmn_group_name == "LeftHand") {396profile_texture->set_texture(get_editor_theme_icon(SNAME("BoneMapHumanLeftHand")));397} else if (hmn_group_name == "RightHand") {398profile_texture->set_texture(get_editor_theme_icon(SNAME("BoneMapHumanRightHand")));399}400} else {401profile_texture->set_texture(profile->get_texture(current_group_idx));402}403} else {404profile_texture->set_texture(Ref<Texture2D>());405}406407if (profile.is_null()) {408return;409}410411for (int i = 0; i < len; i++) {412if (profile->get_group(i) == profile->get_group_name(current_group_idx)) {413BoneMapperButton *mb = memnew(BoneMapperButton(profile->get_bone_name(i), profile->is_required(i), current_bone_idx == i));414mb->connect(SceneStringName(pressed), callable_mp(this, &BoneMapper::set_current_bone_idx).bind(i), CONNECT_DEFERRED);415mb->set_h_grow_direction(GROW_DIRECTION_BOTH);416mb->set_v_grow_direction(GROW_DIRECTION_BOTH);417Vector2 vc = profile->get_handle_offset(i);418bone_mapper_buttons.push_back(mb);419profile_texture->add_child(mb);420mb->set_anchor(SIDE_LEFT, vc.x);421mb->set_anchor(SIDE_RIGHT, vc.x);422mb->set_anchor(SIDE_TOP, vc.y);423mb->set_anchor(SIDE_BOTTOM, vc.y);424}425}426427_update_state();428}429430void BoneMapper::clear_items() {431// Clear items.432int len = bone_mapper_items.size();433for (int i = 0; i < len; i++) {434bone_mapper_items[i]->disconnect("pick", callable_mp(this, &BoneMapper::_pick_bone));435mapper_item_vbox->remove_child(bone_mapper_items[i]);436memdelete(bone_mapper_items[i]);437}438bone_mapper_items.clear();439}440441void BoneMapper::recreate_items() {442clear_items();443// Create items by profile.444Ref<SkeletonProfile> profile = bone_map->get_profile();445if (profile.is_valid()) {446int len = profile->get_bone_size();447for (int i = 0; i < len; i++) {448StringName bn = profile->get_bone_name(i);449bone_mapper_items.append(memnew(BoneMapperItem(bone_map, bn)));450bone_mapper_items[i]->connect("pick", callable_mp(this, &BoneMapper::_pick_bone), CONNECT_DEFERRED);451mapper_item_vbox->add_child(bone_mapper_items[i]);452}453}454455update_group_idx();456recreate_editor();457}458459void BoneMapper::_update_state() {460int len = bone_mapper_buttons.size();461for (int i = 0; i < len; i++) {462StringName pbn = bone_mapper_buttons[i]->get_profile_bone_name();463StringName sbn = bone_map->get_skeleton_bone_name(pbn);464int bone_idx = skeleton->find_bone(sbn);465if (bone_idx >= 0) {466if (bone_map->get_skeleton_bone_name_count(sbn) == 1) {467Ref<SkeletonProfile> prof = bone_map->get_profile();468469StringName parent_name = prof->get_bone_parent(prof->find_bone(pbn));470Vector<int> prof_parent_bones;471while (parent_name != StringName()) {472prof_parent_bones.push_back(skeleton->find_bone(bone_map->get_skeleton_bone_name(parent_name)));473if (prof->find_bone(parent_name) == -1) {474break;475}476parent_name = prof->get_bone_parent(prof->find_bone(parent_name));477}478479int parent_id = skeleton->get_bone_parent(bone_idx);480Vector<int> skel_parent_bones;481while (parent_id >= 0) {482skel_parent_bones.push_back(parent_id);483parent_id = skeleton->get_bone_parent(parent_id);484}485486bool is_broken = false;487for (int j = 0; j < prof_parent_bones.size(); j++) {488if (prof_parent_bones[j] != -1 && !skel_parent_bones.has(prof_parent_bones[j])) {489is_broken = true;490}491}492493if (is_broken) {494bone_mapper_buttons[i]->set_state(BoneMapperButton::BONE_MAP_STATE_ERROR);495} else {496bone_mapper_buttons[i]->set_state(BoneMapperButton::BONE_MAP_STATE_SET);497}498} else {499bone_mapper_buttons[i]->set_state(BoneMapperButton::BONE_MAP_STATE_ERROR);500}501} else {502if (bone_mapper_buttons[i]->is_require()) {503bone_mapper_buttons[i]->set_state(BoneMapperButton::BONE_MAP_STATE_MISSING);504} else {505bone_mapper_buttons[i]->set_state(BoneMapperButton::BONE_MAP_STATE_UNSET);506}507}508}509}510511void BoneMapper::_clear_mapping_current_group() {512if (bone_map.is_valid()) {513Ref<SkeletonProfile> profile = bone_map->get_profile();514if (profile.is_valid() && profile->get_group_size() > 0) {515int len = profile->get_bone_size();516for (int i = 0; i < len; i++) {517if (profile->get_group(i) == profile->get_group_name(current_group_idx)) {518bone_map->_set_skeleton_bone_name(profile->get_bone_name(i), StringName());519}520}521recreate_items();522}523}524}525526bool BoneMapper::is_match_with_bone_name(const String &p_bone_name, const String &p_word) {527RegEx re = RegEx(p_word);528return re.search(p_bone_name.to_lower()).is_valid();529}530531int BoneMapper::search_bone_by_name(Skeleton3D *p_skeleton, const Vector<String> &p_picklist, BoneSegregation p_segregation, int p_parent, int p_child, int p_children_count) {532// There may be multiple candidates hit by existing the subsidiary bone.533// The one with the shortest name is probably the original.534LocalVector<String> hit_list;535String shortest = "";536537for (int word_idx = 0; word_idx < p_picklist.size(); word_idx++) {538if (p_child == -1) {539Vector<int> bones_to_process = p_parent == -1 ? p_skeleton->get_parentless_bones() : p_skeleton->get_bone_children(p_parent);540while (bones_to_process.size() > 0) {541int idx = bones_to_process[0];542bones_to_process.erase(idx);543Vector<int> children = p_skeleton->get_bone_children(idx);544for (int i = 0; i < children.size(); i++) {545bones_to_process.push_back(children[i]);546}547548if (p_children_count == 0 && children.size() > 0) {549continue;550}551if (p_children_count > 0 && children.size() < p_children_count) {552continue;553}554555String bn = skeleton->get_bone_name(idx);556if (is_match_with_bone_name(bn, p_picklist[word_idx]) && guess_bone_segregation(bn) == p_segregation) {557hit_list.push_back(bn);558}559}560561if (hit_list.size() > 0) {562shortest = hit_list[0];563for (const String &hit : hit_list) {564if (hit.length() < shortest.length()) {565shortest = hit; // Prioritize parent.566}567}568}569} else {570int idx = skeleton->get_bone_parent(p_child);571while (idx != p_parent && idx >= 0) {572Vector<int> children = p_skeleton->get_bone_children(idx);573if (p_children_count == 0 && children.size() > 0) {574continue;575}576if (p_children_count > 0 && children.size() < p_children_count) {577continue;578}579580String bn = skeleton->get_bone_name(idx);581if (is_match_with_bone_name(bn, p_picklist[word_idx]) && guess_bone_segregation(bn) == p_segregation) {582hit_list.push_back(bn);583}584idx = skeleton->get_bone_parent(idx);585}586587if (hit_list.size() > 0) {588shortest = hit_list[0];589for (const String &hit : hit_list) {590if (hit.length() <= shortest.length()) {591shortest = hit; // Prioritize parent.592}593}594}595}596597if (shortest != "") {598break;599}600}601602if (shortest == "") {603return -1;604}605606return skeleton->find_bone(shortest);607}608609BoneMapper::BoneSegregation BoneMapper::guess_bone_segregation(const String &p_bone_name) {610String fixed_bn = p_bone_name.to_snake_case();611612LocalVector<String> left_words;613left_words.push_back("(?<![a-zA-Z])left");614left_words.push_back("(?<![a-zA-Z0-9])l(?![a-zA-Z0-9])");615616LocalVector<String> right_words;617right_words.push_back("(?<![a-zA-Z])right");618right_words.push_back("(?<![a-zA-Z0-9])r(?![a-zA-Z0-9])");619620for (uint32_t i = 0; i < left_words.size(); i++) {621RegEx re_l = RegEx(left_words[i]);622if (re_l.search(fixed_bn).is_valid()) {623return BONE_SEGREGATION_LEFT;624}625RegEx re_r = RegEx(right_words[i]);626if (re_r.search(fixed_bn).is_valid()) {627return BONE_SEGREGATION_RIGHT;628}629}630631return BONE_SEGREGATION_NONE;632}633634void BoneMapper::_run_auto_mapping() {635auto_mapping_process(bone_map);636recreate_items();637}638639void BoneMapper::auto_mapping_process(Ref<BoneMap> &p_bone_map) {640WARN_PRINT("Run auto mapping.");641642int bone_idx = -1;643Vector<String> picklist; // Use Vector<String> because match words have priority.644Vector<int> search_path;645646// 1. Guess Hips647picklist.push_back("hip");648picklist.push_back("pelvis");649picklist.push_back("waist");650picklist.push_back("torso");651picklist.push_back("spine");652int hips = search_bone_by_name(skeleton, picklist);653if (hips == -1) {654WARN_PRINT("Auto Mapping couldn't guess Hips. Abort auto mapping.");655return; // If there is no Hips, we cannot guess bone after then.656} else {657p_bone_map->_set_skeleton_bone_name("Hips", skeleton->get_bone_name(hips));658}659picklist.clear();660661// 2. Guess Root662bone_idx = skeleton->get_bone_parent(hips);663while (bone_idx >= 0) {664search_path.push_back(bone_idx);665bone_idx = skeleton->get_bone_parent(bone_idx);666}667if (search_path.is_empty()) {668bone_idx = -1;669} else if (search_path.size() == 1) {670bone_idx = search_path[0]; // It is only one bone which can be root.671} else {672bool found = false;673for (int i = 0; i < search_path.size(); i++) {674RegEx re = RegEx("root");675if (re.search(skeleton->get_bone_name(search_path[i]).to_lower()).is_valid()) {676bone_idx = search_path[i]; // Name match is preferred.677found = true;678break;679}680}681if (!found) {682for (int i = 0; i < search_path.size(); i++) {683if (skeleton->get_bone_global_rest(search_path[i]).origin.is_zero_approx()) {684bone_idx = search_path[i]; // The bone existing at the origin is appropriate as a root.685found = true;686break;687}688}689}690if (!found) {691bone_idx = search_path[search_path.size() - 1]; // Ambiguous, but most parental bone selected.692}693}694if (bone_idx == -1) {695WARN_PRINT("Auto Mapping couldn't guess Root."); // Root is not required, so continue.696} else {697p_bone_map->_set_skeleton_bone_name("Root", skeleton->get_bone_name(bone_idx));698}699bone_idx = -1;700search_path.clear();701702// 3. Guess Foots703picklist.push_back("foot");704picklist.push_back("ankle");705int left_foot = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_LEFT, hips);706if (left_foot == -1) {707WARN_PRINT("Auto Mapping couldn't guess LeftFoot.");708} else {709p_bone_map->_set_skeleton_bone_name("LeftFoot", skeleton->get_bone_name(left_foot));710}711int right_foot = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_RIGHT, hips);712if (right_foot == -1) {713WARN_PRINT("Auto Mapping couldn't guess RightFoot.");714} else {715p_bone_map->_set_skeleton_bone_name("RightFoot", skeleton->get_bone_name(right_foot));716}717picklist.clear();718719// 3-1. Guess LowerLegs720picklist.push_back("(low|under).*leg");721picklist.push_back("knee");722picklist.push_back("shin");723picklist.push_back("calf");724picklist.push_back("leg");725int left_lower_leg = -1;726if (left_foot != -1) {727left_lower_leg = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_LEFT, hips, left_foot);728}729if (left_lower_leg == -1) {730WARN_PRINT("Auto Mapping couldn't guess LeftLowerLeg.");731} else {732p_bone_map->_set_skeleton_bone_name("LeftLowerLeg", skeleton->get_bone_name(left_lower_leg));733}734int right_lower_leg = -1;735if (right_foot != -1) {736right_lower_leg = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_RIGHT, hips, right_foot);737}738if (right_lower_leg == -1) {739WARN_PRINT("Auto Mapping couldn't guess RightLowerLeg.");740} else {741p_bone_map->_set_skeleton_bone_name("RightLowerLeg", skeleton->get_bone_name(right_lower_leg));742}743picklist.clear();744745// 3-2. Guess UpperLegs746picklist.push_back("up.*leg");747picklist.push_back("thigh");748picklist.push_back("leg");749if (left_lower_leg != -1) {750bone_idx = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_LEFT, hips, left_lower_leg);751}752if (bone_idx == -1) {753WARN_PRINT("Auto Mapping couldn't guess LeftUpperLeg.");754} else {755p_bone_map->_set_skeleton_bone_name("LeftUpperLeg", skeleton->get_bone_name(bone_idx));756}757bone_idx = -1;758if (right_lower_leg != -1) {759bone_idx = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_RIGHT, hips, right_lower_leg);760}761if (bone_idx == -1) {762WARN_PRINT("Auto Mapping couldn't guess RightUpperLeg.");763} else {764p_bone_map->_set_skeleton_bone_name("RightUpperLeg", skeleton->get_bone_name(bone_idx));765}766bone_idx = -1;767picklist.clear();768769// 3-3. Guess Toes770picklist.push_back("toe");771picklist.push_back("ball");772if (left_foot != -1) {773bone_idx = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_LEFT, left_foot);774if (bone_idx == -1) {775search_path = skeleton->get_bone_children(left_foot);776if (search_path.size() == 1) {777bone_idx = search_path[0]; // Maybe only one child of the Foot is Toes.778}779search_path.clear();780}781}782if (bone_idx == -1) {783WARN_PRINT("Auto Mapping couldn't guess LeftToes.");784} else {785p_bone_map->_set_skeleton_bone_name("LeftToes", skeleton->get_bone_name(bone_idx));786}787bone_idx = -1;788if (right_foot != -1) {789bone_idx = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_RIGHT, right_foot);790if (bone_idx == -1) {791search_path = skeleton->get_bone_children(right_foot);792if (search_path.size() == 1) {793bone_idx = search_path[0]; // Maybe only one child of the Foot is Toes.794}795search_path.clear();796}797}798if (bone_idx == -1) {799WARN_PRINT("Auto Mapping couldn't guess RightToes.");800} else {801p_bone_map->_set_skeleton_bone_name("RightToes", skeleton->get_bone_name(bone_idx));802}803bone_idx = -1;804picklist.clear();805806// 4. Guess Hands807picklist.push_back("hand");808picklist.push_back("wrist");809picklist.push_back("palm");810picklist.push_back("fingers");811int left_hand_or_palm = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_LEFT, hips, -1, 5);812if (left_hand_or_palm == -1) {813// Ambiguous, but try again for fewer finger models.814left_hand_or_palm = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_LEFT, hips);815}816int left_hand = left_hand_or_palm; // Check for the presence of a wrist, since bones with five children may be palmar.817while (left_hand != -1) {818bone_idx = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_LEFT, hips, left_hand);819if (bone_idx == -1) {820break;821}822left_hand = bone_idx;823}824if (left_hand == -1) {825WARN_PRINT("Auto Mapping couldn't guess LeftHand.");826} else {827p_bone_map->_set_skeleton_bone_name("LeftHand", skeleton->get_bone_name(left_hand));828}829bone_idx = -1;830int right_hand_or_palm = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_RIGHT, hips, -1, 5);831if (right_hand_or_palm == -1) {832// Ambiguous, but try again for fewer finger models.833right_hand_or_palm = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_RIGHT, hips);834}835int right_hand = right_hand_or_palm;836while (right_hand != -1) {837bone_idx = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_RIGHT, hips, right_hand);838if (bone_idx == -1) {839break;840}841right_hand = bone_idx;842}843if (right_hand == -1) {844WARN_PRINT("Auto Mapping couldn't guess RightHand.");845} else {846p_bone_map->_set_skeleton_bone_name("RightHand", skeleton->get_bone_name(right_hand));847}848bone_idx = -1;849picklist.clear();850851// 4-1. Guess Finger852int tips_index = -1;853bool thumb_tips_size = false;854bool named_finger_is_found = false;855LocalVector<String> fingers;856fingers.push_back("thumb|pollex");857fingers.push_back("index|fore");858fingers.push_back("middle");859fingers.push_back("ring");860fingers.push_back("little|pinkie|pinky");861if (left_hand_or_palm != -1) {862LocalVector<LocalVector<String>> left_fingers_map;863left_fingers_map.resize(5);864left_fingers_map[0].push_back("LeftThumbMetacarpal");865left_fingers_map[0].push_back("LeftThumbProximal");866left_fingers_map[0].push_back("LeftThumbDistal");867left_fingers_map[1].push_back("LeftIndexProximal");868left_fingers_map[1].push_back("LeftIndexIntermediate");869left_fingers_map[1].push_back("LeftIndexDistal");870left_fingers_map[2].push_back("LeftMiddleProximal");871left_fingers_map[2].push_back("LeftMiddleIntermediate");872left_fingers_map[2].push_back("LeftMiddleDistal");873left_fingers_map[3].push_back("LeftRingProximal");874left_fingers_map[3].push_back("LeftRingIntermediate");875left_fingers_map[3].push_back("LeftRingDistal");876left_fingers_map[4].push_back("LeftLittleProximal");877left_fingers_map[4].push_back("LeftLittleIntermediate");878left_fingers_map[4].push_back("LeftLittleDistal");879for (int i = 0; i < 5; i++) {880picklist.push_back(fingers[i]);881int finger = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_LEFT, left_hand_or_palm, -1, 0);882if (finger != -1) {883while (finger != left_hand_or_palm && finger >= 0) {884search_path.push_back(finger);885finger = skeleton->get_bone_parent(finger);886}887// Tips detection by name matching with "distal" from root.888for (int j = search_path.size() - 1; j >= 0; j--) {889if (RegEx("distal").search(skeleton->get_bone_name(search_path[j]).to_lower()).is_valid()) {890tips_index = j - 1;891break;892}893}894// Tips detection by name matching with "tip|leaf" from end.895if (tips_index < 0) {896for (int j = 0; j < search_path.size(); j++) {897if (RegEx("tip|leaf").search(skeleton->get_bone_name(search_path[j]).to_lower()).is_valid()) {898tips_index = j;899break;900}901}902}903// Tips detection by thumb children size.904if (tips_index < 0) {905if (i == 0) {906thumb_tips_size = MAX(0, search_path.size() - 3);907}908tips_index = thumb_tips_size - 1;909}910// Remove tips.911for (int j = 0; j <= tips_index; j++) {912search_path.remove_at(0);913}914search_path.reverse();915if (search_path.size() == 1) {916p_bone_map->_set_skeleton_bone_name(left_fingers_map[i][0], skeleton->get_bone_name(search_path[0]));917named_finger_is_found = true;918} else if (search_path.size() == 2) {919p_bone_map->_set_skeleton_bone_name(left_fingers_map[i][0], skeleton->get_bone_name(search_path[0]));920p_bone_map->_set_skeleton_bone_name(left_fingers_map[i][1], skeleton->get_bone_name(search_path[1]));921named_finger_is_found = true;922} else if (search_path.size() >= 3) {923search_path = search_path.slice(-3); // Eliminate the possibility of carpal bone.924p_bone_map->_set_skeleton_bone_name(left_fingers_map[i][0], skeleton->get_bone_name(search_path[0]));925p_bone_map->_set_skeleton_bone_name(left_fingers_map[i][1], skeleton->get_bone_name(search_path[1]));926p_bone_map->_set_skeleton_bone_name(left_fingers_map[i][2], skeleton->get_bone_name(search_path[2]));927named_finger_is_found = true;928}929}930picklist.clear();931search_path.clear();932}933934// It is a bit corner case, but possibly the finger names are sequentially numbered...935if (!named_finger_is_found) {936picklist.push_back("finger");937RegEx finger_re = RegEx("finger");938search_path = skeleton->get_bone_children(left_hand_or_palm);939Vector<String> finger_names;940for (int i = 0; i < search_path.size(); i++) {941String bn = skeleton->get_bone_name(search_path[i]);942if (finger_re.search(bn.to_lower()).is_valid()) {943finger_names.push_back(bn);944}945}946finger_names.sort(); // Order by lexicographic, normal use cases never have more than 10 fingers in one hand.947search_path.clear();948for (int i = 0; i < finger_names.size(); i++) {949if (i >= 5) {950break;951}952int finger_root = skeleton->find_bone(finger_names[i]);953int finger = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_LEFT, finger_root, -1, 0);954if (finger != -1) {955while (finger != finger_root && finger >= 0) {956search_path.push_back(finger);957finger = skeleton->get_bone_parent(finger);958}959}960search_path.push_back(finger_root);961// Tips detection by thumb children size.962if (i == 0) {963thumb_tips_size = MAX(0, search_path.size() - 3);964}965tips_index = thumb_tips_size - 1;966for (int j = 0; j <= tips_index; j++) {967search_path.remove_at(0);968}969search_path.reverse();970if (search_path.size() == 1) {971p_bone_map->_set_skeleton_bone_name(left_fingers_map[i][0], skeleton->get_bone_name(search_path[0]));972} else if (search_path.size() == 2) {973p_bone_map->_set_skeleton_bone_name(left_fingers_map[i][0], skeleton->get_bone_name(search_path[0]));974p_bone_map->_set_skeleton_bone_name(left_fingers_map[i][1], skeleton->get_bone_name(search_path[1]));975} else if (search_path.size() >= 3) {976search_path = search_path.slice(-3); // Eliminate the possibility of carpal bone.977p_bone_map->_set_skeleton_bone_name(left_fingers_map[i][0], skeleton->get_bone_name(search_path[0]));978p_bone_map->_set_skeleton_bone_name(left_fingers_map[i][1], skeleton->get_bone_name(search_path[1]));979p_bone_map->_set_skeleton_bone_name(left_fingers_map[i][2], skeleton->get_bone_name(search_path[2]));980}981search_path.clear();982}983picklist.clear();984}985}986987tips_index = -1;988thumb_tips_size = false;989named_finger_is_found = false;990if (right_hand_or_palm != -1) {991LocalVector<LocalVector<String>> right_fingers_map;992right_fingers_map.resize(5);993right_fingers_map[0].push_back("RightThumbMetacarpal");994right_fingers_map[0].push_back("RightThumbProximal");995right_fingers_map[0].push_back("RightThumbDistal");996right_fingers_map[1].push_back("RightIndexProximal");997right_fingers_map[1].push_back("RightIndexIntermediate");998right_fingers_map[1].push_back("RightIndexDistal");999right_fingers_map[2].push_back("RightMiddleProximal");1000right_fingers_map[2].push_back("RightMiddleIntermediate");1001right_fingers_map[2].push_back("RightMiddleDistal");1002right_fingers_map[3].push_back("RightRingProximal");1003right_fingers_map[3].push_back("RightRingIntermediate");1004right_fingers_map[3].push_back("RightRingDistal");1005right_fingers_map[4].push_back("RightLittleProximal");1006right_fingers_map[4].push_back("RightLittleIntermediate");1007right_fingers_map[4].push_back("RightLittleDistal");1008for (int i = 0; i < 5; i++) {1009picklist.push_back(fingers[i]);1010int finger = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_RIGHT, right_hand_or_palm, -1, 0);1011if (finger != -1) {1012while (finger != right_hand_or_palm && finger >= 0) {1013search_path.push_back(finger);1014finger = skeleton->get_bone_parent(finger);1015}1016// Tips detection by name matching with "distal" from root.1017for (int j = search_path.size() - 1; j >= 0; j--) {1018if (RegEx("distal").search(skeleton->get_bone_name(search_path[j]).to_lower()).is_valid()) {1019tips_index = j - 1;1020break;1021}1022}1023// Tips detection by name matching with "tip|leaf" from end.1024if (tips_index < 0) {1025for (int j = 0; j < search_path.size(); j++) {1026if (RegEx("tip|leaf").search(skeleton->get_bone_name(search_path[j]).to_lower()).is_valid()) {1027tips_index = j;1028break;1029}1030}1031}1032// Tips detection by thumb children size.1033if (tips_index < 0) {1034if (i == 0) {1035thumb_tips_size = MAX(0, search_path.size() - 3);1036}1037tips_index = thumb_tips_size - 1;1038}1039// Remove tips.1040for (int j = 0; j <= tips_index; j++) {1041search_path.remove_at(0);1042}1043search_path.reverse();1044if (search_path.size() == 1) {1045p_bone_map->_set_skeleton_bone_name(right_fingers_map[i][0], skeleton->get_bone_name(search_path[0]));1046named_finger_is_found = true;1047} else if (search_path.size() == 2) {1048p_bone_map->_set_skeleton_bone_name(right_fingers_map[i][0], skeleton->get_bone_name(search_path[0]));1049p_bone_map->_set_skeleton_bone_name(right_fingers_map[i][1], skeleton->get_bone_name(search_path[1]));1050named_finger_is_found = true;1051} else if (search_path.size() >= 3) {1052search_path = search_path.slice(-3); // Eliminate the possibility of carpal bone.1053p_bone_map->_set_skeleton_bone_name(right_fingers_map[i][0], skeleton->get_bone_name(search_path[0]));1054p_bone_map->_set_skeleton_bone_name(right_fingers_map[i][1], skeleton->get_bone_name(search_path[1]));1055p_bone_map->_set_skeleton_bone_name(right_fingers_map[i][2], skeleton->get_bone_name(search_path[2]));1056named_finger_is_found = true;1057}1058}1059picklist.clear();1060search_path.clear();1061}10621063// It is a bit corner case, but possibly the finger names are sequentially numbered...1064if (!named_finger_is_found) {1065picklist.push_back("finger");1066RegEx finger_re = RegEx("finger");1067search_path = skeleton->get_bone_children(right_hand_or_palm);1068Vector<String> finger_names;1069for (int i = 0; i < search_path.size(); i++) {1070String bn = skeleton->get_bone_name(search_path[i]);1071if (finger_re.search(bn.to_lower()).is_valid()) {1072finger_names.push_back(bn);1073}1074}1075finger_names.sort(); // Order by lexicographic, normal use cases never have more than 10 fingers in one hand.1076search_path.clear();1077for (int i = 0; i < finger_names.size(); i++) {1078if (i >= 5) {1079break;1080}1081int finger_root = skeleton->find_bone(finger_names[i]);1082int finger = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_RIGHT, finger_root, -1, 0);1083if (finger != -1) {1084while (finger != finger_root && finger >= 0) {1085search_path.push_back(finger);1086finger = skeleton->get_bone_parent(finger);1087}1088}1089search_path.push_back(finger_root);1090// Tips detection by thumb children size.1091if (i == 0) {1092thumb_tips_size = MAX(0, search_path.size() - 3);1093}1094tips_index = thumb_tips_size - 1;1095for (int j = 0; j <= tips_index; j++) {1096search_path.remove_at(0);1097}1098search_path.reverse();1099if (search_path.size() == 1) {1100p_bone_map->_set_skeleton_bone_name(right_fingers_map[i][0], skeleton->get_bone_name(search_path[0]));1101} else if (search_path.size() == 2) {1102p_bone_map->_set_skeleton_bone_name(right_fingers_map[i][0], skeleton->get_bone_name(search_path[0]));1103p_bone_map->_set_skeleton_bone_name(right_fingers_map[i][1], skeleton->get_bone_name(search_path[1]));1104} else if (search_path.size() >= 3) {1105search_path = search_path.slice(-3); // Eliminate the possibility of carpal bone.1106p_bone_map->_set_skeleton_bone_name(right_fingers_map[i][0], skeleton->get_bone_name(search_path[0]));1107p_bone_map->_set_skeleton_bone_name(right_fingers_map[i][1], skeleton->get_bone_name(search_path[1]));1108p_bone_map->_set_skeleton_bone_name(right_fingers_map[i][2], skeleton->get_bone_name(search_path[2]));1109}1110search_path.clear();1111}1112picklist.clear();1113}1114}11151116// 5. Guess Arms1117picklist.push_back("shoulder");1118picklist.push_back("clavicle");1119picklist.push_back("collar");1120int left_shoulder = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_LEFT, hips);1121if (left_shoulder == -1) {1122WARN_PRINT("Auto Mapping couldn't guess LeftShoulder.");1123} else {1124p_bone_map->_set_skeleton_bone_name("LeftShoulder", skeleton->get_bone_name(left_shoulder));1125}1126int right_shoulder = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_RIGHT, hips);1127if (right_shoulder == -1) {1128WARN_PRINT("Auto Mapping couldn't guess RightShoulder.");1129} else {1130p_bone_map->_set_skeleton_bone_name("RightShoulder", skeleton->get_bone_name(right_shoulder));1131}1132picklist.clear();11331134// 5-1. Guess LowerArms1135picklist.push_back("(low|fore).*arm");1136picklist.push_back("elbow");1137picklist.push_back("arm");1138int left_lower_arm = -1;1139if (left_shoulder != -1 && left_hand_or_palm != -1) {1140left_lower_arm = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_LEFT, left_shoulder, left_hand_or_palm);1141}1142if (left_lower_arm == -1) {1143WARN_PRINT("Auto Mapping couldn't guess LeftLowerArm.");1144} else {1145p_bone_map->_set_skeleton_bone_name("LeftLowerArm", skeleton->get_bone_name(left_lower_arm));1146}1147int right_lower_arm = -1;1148if (right_shoulder != -1 && right_hand_or_palm != -1) {1149right_lower_arm = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_RIGHT, right_shoulder, right_hand_or_palm);1150}1151if (right_lower_arm == -1) {1152WARN_PRINT("Auto Mapping couldn't guess RightLowerArm.");1153} else {1154p_bone_map->_set_skeleton_bone_name("RightLowerArm", skeleton->get_bone_name(right_lower_arm));1155}1156picklist.clear();11571158// 5-2. Guess UpperArms1159picklist.push_back("up.*arm");1160picklist.push_back("arm");1161if (left_shoulder != -1 && left_lower_arm != -1) {1162bone_idx = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_LEFT, left_shoulder, left_lower_arm);1163}1164if (bone_idx == -1) {1165WARN_PRINT("Auto Mapping couldn't guess LeftUpperArm.");1166} else {1167p_bone_map->_set_skeleton_bone_name("LeftUpperArm", skeleton->get_bone_name(bone_idx));1168}1169bone_idx = -1;1170if (right_shoulder != -1 && right_lower_arm != -1) {1171bone_idx = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_RIGHT, right_shoulder, right_lower_arm);1172}1173if (bone_idx == -1) {1174WARN_PRINT("Auto Mapping couldn't guess RightUpperArm.");1175} else {1176p_bone_map->_set_skeleton_bone_name("RightUpperArm", skeleton->get_bone_name(bone_idx));1177}1178bone_idx = -1;1179picklist.clear();11801181// 6. Guess Neck1182picklist.push_back("neck");1183picklist.push_back("head"); // For no neck model.1184picklist.push_back("face"); // Same above.1185int neck = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_NONE, hips);1186picklist.clear();1187if (neck == -1) {1188// If it can't expect by name, search child spine of where the right and left shoulders (or hands) cross.1189int ls_idx = left_shoulder != -1 ? left_shoulder : (left_hand_or_palm != -1 ? left_hand_or_palm : -1);1190int rs_idx = right_shoulder != -1 ? right_shoulder : (right_hand_or_palm != -1 ? right_hand_or_palm : -1);1191if (ls_idx != -1 && rs_idx != -1) {1192bool detect = false;1193while (ls_idx != hips && ls_idx >= 0 && rs_idx != hips && rs_idx >= 0) {1194ls_idx = skeleton->get_bone_parent(ls_idx);1195rs_idx = skeleton->get_bone_parent(rs_idx);1196if (ls_idx == rs_idx) {1197detect = true;1198break;1199}1200}1201if (detect) {1202Vector<int> children = skeleton->get_bone_children(ls_idx);1203children.erase(ls_idx);1204children.erase(rs_idx);1205String word = "spine"; // It would be better to limit the search with "spine" because it could be mistaken with breast, wing and etc...1206for (int i = 0; i < children.size(); i++) {1207bone_idx = children[i];1208if (is_match_with_bone_name(skeleton->get_bone_name(bone_idx), word)) {1209neck = bone_idx;1210break;1211};1212}1213bone_idx = -1;1214}1215}1216}12171218// 7. Guess Head1219picklist.push_back("head");1220picklist.push_back("face");1221int head = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_NONE, neck);1222if (head == -1) {1223if (neck != -1) {1224search_path = skeleton->get_bone_children(neck);1225if (search_path.size() == 1) {1226head = search_path[0]; // Maybe only one child of the Neck is Head.1227}1228}1229}1230if (head == -1) {1231if (neck != -1) {1232head = neck; // The head animation should have more movement.1233neck = -1;1234p_bone_map->_set_skeleton_bone_name("Head", skeleton->get_bone_name(head));1235} else {1236WARN_PRINT("Auto Mapping couldn't guess Neck or Head."); // Continued for guessing on the other bones. But abort when guessing spines step.1237}1238} else {1239p_bone_map->_set_skeleton_bone_name("Neck", skeleton->get_bone_name(neck));1240p_bone_map->_set_skeleton_bone_name("Head", skeleton->get_bone_name(head));1241}1242picklist.clear();1243search_path.clear();12441245int neck_or_head = neck != -1 ? neck : (head != -1 ? head : -1);1246if (neck_or_head != -1) {1247// 7-1. Guess Eyes1248picklist.push_back("eye(?!.*(brow|lash|lid))");1249bone_idx = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_LEFT, neck_or_head);1250if (bone_idx == -1) {1251WARN_PRINT("Auto Mapping couldn't guess LeftEye.");1252} else {1253p_bone_map->_set_skeleton_bone_name("LeftEye", skeleton->get_bone_name(bone_idx));1254}12551256bone_idx = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_RIGHT, neck_or_head);1257if (bone_idx == -1) {1258WARN_PRINT("Auto Mapping couldn't guess RightEye.");1259} else {1260p_bone_map->_set_skeleton_bone_name("RightEye", skeleton->get_bone_name(bone_idx));1261}1262picklist.clear();12631264// 7-2. Guess Jaw1265picklist.push_back("jaw");1266bone_idx = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_NONE, neck_or_head);1267if (bone_idx == -1) {1268WARN_PRINT("Auto Mapping couldn't guess Jaw.");1269} else {1270p_bone_map->_set_skeleton_bone_name("Jaw", skeleton->get_bone_name(bone_idx));1271}1272bone_idx = -1;1273picklist.clear();1274}12751276// 8. Guess UpperChest or Chest1277if (neck_or_head == -1) {1278return; // Abort.1279}1280int chest_or_upper_chest = skeleton->get_bone_parent(neck_or_head);1281bool is_appropriate = true;1282if (left_shoulder != -1) {1283bone_idx = skeleton->get_bone_parent(left_shoulder);1284bool detect = false;1285while (bone_idx != hips && bone_idx >= 0) {1286if (bone_idx == chest_or_upper_chest) {1287detect = true;1288break;1289}1290bone_idx = skeleton->get_bone_parent(bone_idx);1291}1292if (!detect) {1293is_appropriate = false;1294}1295bone_idx = -1;1296}1297if (right_shoulder != -1) {1298bone_idx = skeleton->get_bone_parent(right_shoulder);1299bool detect = false;1300while (bone_idx != hips && bone_idx >= 0) {1301if (bone_idx == chest_or_upper_chest) {1302detect = true;1303break;1304}1305bone_idx = skeleton->get_bone_parent(bone_idx);1306}1307if (!detect) {1308is_appropriate = false;1309}1310bone_idx = -1;1311}1312if (!is_appropriate) {1313if (skeleton->get_bone_parent(left_shoulder) == skeleton->get_bone_parent(right_shoulder)) {1314chest_or_upper_chest = skeleton->get_bone_parent(left_shoulder);1315} else {1316chest_or_upper_chest = -1;1317}1318}1319if (chest_or_upper_chest == -1) {1320WARN_PRINT("Auto Mapping couldn't guess Chest or UpperChest. Abort auto mapping.");1321return; // Will be not able to guess Spines.1322}13231324// 9. Guess Spines1325bone_idx = skeleton->get_bone_parent(chest_or_upper_chest);1326while (bone_idx != hips && bone_idx >= 0) {1327search_path.push_back(bone_idx);1328bone_idx = skeleton->get_bone_parent(bone_idx);1329}1330search_path.reverse();1331if (search_path.is_empty()) {1332p_bone_map->_set_skeleton_bone_name("Spine", skeleton->get_bone_name(chest_or_upper_chest)); // Maybe chibi model...?1333} else if (search_path.size() == 1) {1334p_bone_map->_set_skeleton_bone_name("Spine", skeleton->get_bone_name(search_path[0]));1335p_bone_map->_set_skeleton_bone_name("Chest", skeleton->get_bone_name(chest_or_upper_chest));1336} else if (search_path.size() >= 2) {1337p_bone_map->_set_skeleton_bone_name("Spine", skeleton->get_bone_name(search_path[0]));1338p_bone_map->_set_skeleton_bone_name("Chest", skeleton->get_bone_name(search_path[search_path.size() - 1])); // Probably UppeChest's parent is appropriate.1339p_bone_map->_set_skeleton_bone_name("UpperChest", skeleton->get_bone_name(chest_or_upper_chest));1340}1341bone_idx = -1;1342search_path.clear();13431344WARN_PRINT("Finish auto mapping.");1345}13461347void BoneMapper::_value_changed(const String &p_property, const Variant &p_value, const String &p_name, bool p_changing) {1348set(p_property, p_value);1349recreate_editor();1350}13511352void BoneMapper::_profile_changed(const String &p_property, const Variant &p_value, const String &p_name, bool p_changing) {1353bone_map->set(p_property, p_value);13541355// Run auto mapping when setting SkeletonProfileHumanoid by GUI Editor.1356Ref<SkeletonProfile> profile = bone_map->get_profile();1357if (profile.is_valid()) {1358SkeletonProfileHumanoid *hmn = Object::cast_to<SkeletonProfileHumanoid>(profile.ptr());1359if (hmn) {1360_run_auto_mapping();1361}1362}1363}13641365void BoneMapper::_bind_methods() {1366ClassDB::bind_method(D_METHOD("set_current_group_idx", "current_group_idx"), &BoneMapper::set_current_group_idx);1367ClassDB::bind_method(D_METHOD("get_current_group_idx"), &BoneMapper::get_current_group_idx);1368ClassDB::bind_method(D_METHOD("set_current_bone_idx", "current_bone_idx"), &BoneMapper::set_current_bone_idx);1369ClassDB::bind_method(D_METHOD("get_current_bone_idx"), &BoneMapper::get_current_bone_idx);1370ADD_PROPERTY(PropertyInfo(Variant::INT, "current_group_idx"), "set_current_group_idx", "get_current_group_idx");1371ADD_PROPERTY(PropertyInfo(Variant::INT, "current_bone_idx"), "set_current_bone_idx", "get_current_bone_idx");1372}13731374void BoneMapper::_notification(int p_what) {1375switch (p_what) {1376case NOTIFICATION_ENTER_TREE: {1377create_editor();1378bone_map->connect("bone_map_updated", callable_mp(this, &BoneMapper::_update_state));1379bone_map->connect("profile_updated", callable_mp(this, &BoneMapper::recreate_items));1380} break;1381case NOTIFICATION_EXIT_TREE: {1382clear_items();1383if (bone_map.is_valid()) {1384if (bone_map->is_connected("bone_map_updated", callable_mp(this, &BoneMapper::_update_state))) {1385bone_map->disconnect("bone_map_updated", callable_mp(this, &BoneMapper::_update_state));1386}1387if (bone_map->is_connected("profile_updated", callable_mp(this, &BoneMapper::recreate_items))) {1388bone_map->disconnect("profile_updated", callable_mp(this, &BoneMapper::recreate_items));1389}1390}1391}1392}1393}13941395BoneMapper::BoneMapper(Skeleton3D *p_skeleton, Ref<BoneMap> &p_bone_map) {1396skeleton = p_skeleton;1397bone_map = p_bone_map;1398}13991400void BoneMapEditor::create_editors() {1401if (!skeleton) {1402return;1403}1404bone_mapper = memnew(BoneMapper(skeleton, bone_map));1405add_child(bone_mapper);1406}14071408void BoneMapEditor::fetch_objects() {1409skeleton = nullptr;1410// Hackey... but it may be the easiest way to get a selected object from "ImporterScene".1411SceneImportSettingsDialog *si = SceneImportSettingsDialog::get_singleton();1412if (!si) {1413return;1414}1415if (!si->is_visible()) {1416return;1417}1418Node *selected = si->get_selected_node();1419if (selected) {1420Skeleton3D *sk = Object::cast_to<Skeleton3D>(selected);1421if (!sk) {1422return;1423}1424skeleton = sk;1425} else {1426// Editor should not exist.1427skeleton = nullptr;1428}1429}14301431void BoneMapEditor::_notification(int p_what) {1432switch (p_what) {1433case NOTIFICATION_ENTER_TREE: {1434fetch_objects();1435create_editors();1436} break;1437case NOTIFICATION_EXIT_TREE: {1438skeleton = nullptr;1439} break;1440}1441}14421443BoneMapEditor::BoneMapEditor(Ref<BoneMap> &p_bone_map) {1444bone_map = p_bone_map;1445}14461447bool EditorInspectorPluginBoneMap::can_handle(Object *p_object) {1448return Object::cast_to<BoneMap>(p_object) != nullptr;1449}14501451void EditorInspectorPluginBoneMap::parse_begin(Object *p_object) {1452BoneMap *bm = Object::cast_to<BoneMap>(p_object);1453if (!bm) {1454return;1455}1456Ref<BoneMap> r(bm);1457editor = memnew(BoneMapEditor(r));1458add_custom_control(editor);1459}14601461BoneMapEditorPlugin::BoneMapEditorPlugin() {1462Ref<EditorInspectorPluginBoneMap> inspector_plugin;1463inspector_plugin.instantiate();1464add_inspector_plugin(inspector_plugin);14651466Ref<PostImportPluginSkeletonTrackOrganizer> post_import_plugin_track_organizer;1467post_import_plugin_track_organizer.instantiate();1468add_scene_post_import_plugin(post_import_plugin_track_organizer);14691470Ref<PostImportPluginSkeletonRenamer> post_import_plugin_renamer;1471post_import_plugin_renamer.instantiate();1472add_scene_post_import_plugin(post_import_plugin_renamer);14731474Ref<PostImportPluginSkeletonRestFixer> post_import_plugin_rest_fixer;1475post_import_plugin_rest_fixer.instantiate();1476add_scene_post_import_plugin(post_import_plugin_rest_fixer);1477}147814791480