1/**************************************************************************/
2/* bone_map_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 "bone_map_editor_plugin.h"
32
33#include "editor/editor_scale.h"
34#include "editor/editor_settings.h"
35#include "editor/import/post_import_plugin_skeleton_renamer.h"
36#include "editor/import/post_import_plugin_skeleton_rest_fixer.h"
37#include "editor/import/post_import_plugin_skeleton_track_organizer.h"
38#include "editor/import/scene_import_settings.h"
39#include "scene/gui/aspect_ratio_container.h"
40#include "scene/gui/separator.h"
41#include "scene/gui/texture_rect.h"
42
43void BoneMapperButton::fetch_textures() {
44 if (selected) {
45 set_texture_normal(get_editor_theme_icon(SNAME("BoneMapperHandleSelected")));
46 } else {
47 set_texture_normal(get_editor_theme_icon(SNAME("BoneMapperHandle")));
48 }
49 set_offset(SIDE_LEFT, 0);
50 set_offset(SIDE_RIGHT, 0);
51 set_offset(SIDE_TOP, 0);
52 set_offset(SIDE_BOTTOM, 0);
53
54 // Hack to avoid handle color darkening...
55 set_modulate(EditorSettings::get_singleton()->is_dark_theme() ? Color(1, 1, 1) : Color(4.25, 4.25, 4.25));
56
57 circle = memnew(TextureRect);
58 circle->set_texture(get_editor_theme_icon(SNAME("BoneMapperHandleCircle")));
59 add_child(circle);
60 set_state(BONE_MAP_STATE_UNSET);
61}
62
63StringName BoneMapperButton::get_profile_bone_name() const {
64 return profile_bone_name;
65}
66
67void BoneMapperButton::set_state(BoneMapState p_state) {
68 switch (p_state) {
69 case BONE_MAP_STATE_UNSET: {
70 circle->set_modulate(EDITOR_GET("editors/bone_mapper/handle_colors/unset"));
71 } break;
72 case BONE_MAP_STATE_SET: {
73 circle->set_modulate(EDITOR_GET("editors/bone_mapper/handle_colors/set"));
74 } break;
75 case BONE_MAP_STATE_MISSING: {
76 circle->set_modulate(EDITOR_GET("editors/bone_mapper/handle_colors/missing"));
77 } break;
78 case BONE_MAP_STATE_ERROR: {
79 circle->set_modulate(EDITOR_GET("editors/bone_mapper/handle_colors/error"));
80 } break;
81 default: {
82 } break;
83 }
84}
85
86bool BoneMapperButton::is_require() const {
87 return require;
88}
89
90void BoneMapperButton::_notification(int p_what) {
91 switch (p_what) {
92 case NOTIFICATION_ENTER_TREE: {
93 fetch_textures();
94 } break;
95 }
96}
97
98BoneMapperButton::BoneMapperButton(const StringName p_profile_bone_name, bool p_require, bool p_selected) {
99 profile_bone_name = p_profile_bone_name;
100 require = p_require;
101 selected = p_selected;
102}
103
104BoneMapperButton::~BoneMapperButton() {
105}
106
107void BoneMapperItem::create_editor() {
108 HBoxContainer *hbox = memnew(HBoxContainer);
109 add_child(hbox);
110
111 skeleton_bone_selector = memnew(EditorPropertyText);
112 skeleton_bone_selector->set_label(profile_bone_name);
113 skeleton_bone_selector->set_selectable(false);
114 skeleton_bone_selector->set_h_size_flags(SIZE_EXPAND_FILL);
115 skeleton_bone_selector->set_object_and_property(bone_map.ptr(), "bone_map/" + String(profile_bone_name));
116 skeleton_bone_selector->update_property();
117 skeleton_bone_selector->connect("property_changed", callable_mp(this, &BoneMapperItem::_value_changed));
118 hbox->add_child(skeleton_bone_selector);
119
120 picker_button = memnew(Button);
121 picker_button->set_icon(get_editor_theme_icon(SNAME("ClassList")));
122 picker_button->connect("pressed", callable_mp(this, &BoneMapperItem::_open_picker));
123 hbox->add_child(picker_button);
124
125 add_child(memnew(HSeparator));
126}
127
128void BoneMapperItem::_update_property() {
129 if (skeleton_bone_selector->get_edited_object() && skeleton_bone_selector->get_edited_property()) {
130 skeleton_bone_selector->update_property();
131 }
132}
133
134void BoneMapperItem::_open_picker() {
135 emit_signal(SNAME("pick"), profile_bone_name);
136}
137
138void BoneMapperItem::_value_changed(const String &p_property, Variant p_value, const String &p_name, bool p_changing) {
139 bone_map->set(p_property, p_value);
140}
141
142void BoneMapperItem::_notification(int p_what) {
143 switch (p_what) {
144 case NOTIFICATION_ENTER_TREE: {
145 create_editor();
146 bone_map->connect("bone_map_updated", callable_mp(this, &BoneMapperItem::_update_property));
147 } break;
148 case NOTIFICATION_EXIT_TREE: {
149 if (!bone_map.is_null() && bone_map->is_connected("bone_map_updated", callable_mp(this, &BoneMapperItem::_update_property))) {
150 bone_map->disconnect("bone_map_updated", callable_mp(this, &BoneMapperItem::_update_property));
151 }
152 } break;
153 }
154}
155
156void BoneMapperItem::_bind_methods() {
157 ADD_SIGNAL(MethodInfo("pick", PropertyInfo(Variant::STRING_NAME, "profile_bone_name")));
158}
159
160BoneMapperItem::BoneMapperItem(Ref<BoneMap> &p_bone_map, const StringName &p_profile_bone_name) {
161 bone_map = p_bone_map;
162 profile_bone_name = p_profile_bone_name;
163}
164
165BoneMapperItem::~BoneMapperItem() {
166}
167
168void BonePicker::create_editors() {
169 set_title(TTR("Bone Picker:"));
170
171 VBoxContainer *vbox = memnew(VBoxContainer);
172 add_child(vbox);
173
174 bones = memnew(Tree);
175 bones->set_select_mode(Tree::SELECT_SINGLE);
176 bones->set_v_size_flags(Control::SIZE_EXPAND_FILL);
177 bones->set_hide_root(true);
178 bones->connect("item_activated", callable_mp(this, &BonePicker::_confirm));
179 vbox->add_child(bones);
180
181 create_bones_tree(skeleton);
182}
183
184void BonePicker::create_bones_tree(Skeleton3D *p_skeleton) {
185 bones->clear();
186
187 if (!p_skeleton) {
188 return;
189 }
190
191 TreeItem *root = bones->create_item();
192
193 HashMap<int, TreeItem *> items;
194
195 items.insert(-1, root);
196
197 Ref<Texture> bone_icon = get_editor_theme_icon(SNAME("BoneAttachment3D"));
198
199 Vector<int> bones_to_process = p_skeleton->get_parentless_bones();
200 bool is_first = true;
201 while (bones_to_process.size() > 0) {
202 int current_bone_idx = bones_to_process[0];
203 bones_to_process.erase(current_bone_idx);
204
205 Vector<int> current_bone_child_bones = p_skeleton->get_bone_children(current_bone_idx);
206 int child_bone_size = current_bone_child_bones.size();
207 for (int i = 0; i < child_bone_size; i++) {
208 bones_to_process.push_back(current_bone_child_bones[i]);
209 }
210
211 const int parent_idx = p_skeleton->get_bone_parent(current_bone_idx);
212 TreeItem *parent_item = items.find(parent_idx)->value;
213
214 TreeItem *joint_item = bones->create_item(parent_item);
215 items.insert(current_bone_idx, joint_item);
216
217 joint_item->set_text(0, p_skeleton->get_bone_name(current_bone_idx));
218 joint_item->set_icon(0, bone_icon);
219 joint_item->set_selectable(0, true);
220 joint_item->set_metadata(0, "bones/" + itos(current_bone_idx));
221 if (is_first) {
222 is_first = false;
223 } else {
224 joint_item->set_collapsed(true);
225 }
226 }
227}
228
229void BonePicker::_confirm() {
230 _ok_pressed();
231}
232
233void BonePicker::popup_bones_tree(const Size2i &p_minsize) {
234 popup_centered(p_minsize);
235}
236
237bool BonePicker::has_selected_bone() {
238 TreeItem *selected = bones->get_selected();
239 if (!selected) {
240 return false;
241 }
242 return true;
243}
244
245StringName BonePicker::get_selected_bone() {
246 TreeItem *selected = bones->get_selected();
247 if (!selected) {
248 return StringName();
249 }
250 return selected->get_text(0);
251}
252
253void BonePicker::_bind_methods() {
254}
255
256void BonePicker::_notification(int p_what) {
257 switch (p_what) {
258 case NOTIFICATION_ENTER_TREE: {
259 create_editors();
260 } break;
261 }
262}
263
264BonePicker::BonePicker(Skeleton3D *p_skeleton) {
265 skeleton = p_skeleton;
266}
267
268BonePicker::~BonePicker() {
269}
270
271void BoneMapper::create_editor() {
272 // Create Bone picker.
273 picker = memnew(BonePicker(skeleton));
274 picker->connect("confirmed", callable_mp(this, &BoneMapper::_apply_picker_selection));
275 add_child(picker, false, INTERNAL_MODE_FRONT);
276
277 profile_selector = memnew(EditorPropertyResource);
278 profile_selector->setup(bone_map.ptr(), "profile", "SkeletonProfile");
279 profile_selector->set_label("Profile");
280 profile_selector->set_selectable(false);
281 profile_selector->set_object_and_property(bone_map.ptr(), "profile");
282 profile_selector->update_property();
283 profile_selector->connect("property_changed", callable_mp(this, &BoneMapper::_profile_changed));
284 add_child(profile_selector);
285 add_child(memnew(HSeparator));
286
287 HBoxContainer *group_hbox = memnew(HBoxContainer);
288 add_child(group_hbox);
289
290 profile_group_selector = memnew(EditorPropertyEnum);
291 profile_group_selector->set_label("Group");
292 profile_group_selector->set_selectable(false);
293 profile_group_selector->set_h_size_flags(SIZE_EXPAND_FILL);
294 profile_group_selector->set_object_and_property(this, "current_group_idx");
295 profile_group_selector->update_property();
296 profile_group_selector->connect("property_changed", callable_mp(this, &BoneMapper::_value_changed));
297 group_hbox->add_child(profile_group_selector);
298
299 clear_mapping_button = memnew(Button);
300 clear_mapping_button->set_icon(get_editor_theme_icon(SNAME("Clear")));
301 clear_mapping_button->set_tooltip_text(TTR("Clear mappings in current group."));
302 clear_mapping_button->connect("pressed", callable_mp(this, &BoneMapper::_clear_mapping_current_group));
303 group_hbox->add_child(clear_mapping_button);
304
305 bone_mapper_field = memnew(AspectRatioContainer);
306 bone_mapper_field->set_stretch_mode(AspectRatioContainer::STRETCH_FIT);
307 bone_mapper_field->set_custom_minimum_size(Vector2(0, 256.0) * EDSCALE);
308 bone_mapper_field->set_h_size_flags(Control::SIZE_FILL);
309 add_child(bone_mapper_field);
310
311 profile_bg = memnew(ColorRect);
312 profile_bg->set_color(Color(0, 0, 0, 1));
313 profile_bg->set_h_size_flags(Control::SIZE_FILL);
314 profile_bg->set_v_size_flags(Control::SIZE_FILL);
315 bone_mapper_field->add_child(profile_bg);
316
317 profile_texture = memnew(TextureRect);
318 profile_texture->set_stretch_mode(TextureRect::STRETCH_KEEP_ASPECT_CENTERED);
319 profile_texture->set_expand_mode(TextureRect::EXPAND_IGNORE_SIZE);
320 profile_texture->set_h_size_flags(Control::SIZE_FILL);
321 profile_texture->set_v_size_flags(Control::SIZE_FILL);
322 bone_mapper_field->add_child(profile_texture);
323
324 mapper_item_vbox = memnew(VBoxContainer);
325 add_child(mapper_item_vbox);
326
327 recreate_items();
328}
329
330void BoneMapper::update_group_idx() {
331 if (!bone_map->get_profile().is_valid()) {
332 return;
333 }
334
335 PackedStringArray group_names;
336 int len = bone_map->get_profile()->get_group_size();
337 for (int i = 0; i < len; i++) {
338 group_names.push_back(bone_map->get_profile()->get_group_name(i));
339 }
340 if (current_group_idx >= len) {
341 current_group_idx = 0;
342 }
343 if (len > 0) {
344 profile_group_selector->setup(group_names);
345 profile_group_selector->update_property();
346 profile_group_selector->set_read_only(false);
347 }
348}
349
350void BoneMapper::_pick_bone(const StringName &p_bone_name) {
351 picker_key_name = p_bone_name;
352 picker->popup_bones_tree(Size2(500, 500) * EDSCALE);
353}
354
355void BoneMapper::_apply_picker_selection() {
356 if (!picker->has_selected_bone()) {
357 return;
358 }
359 bone_map->set_skeleton_bone_name(picker_key_name, picker->get_selected_bone());
360}
361
362void BoneMapper::set_current_group_idx(int p_group_idx) {
363 current_group_idx = p_group_idx;
364 recreate_editor();
365}
366
367int BoneMapper::get_current_group_idx() const {
368 return current_group_idx;
369}
370
371void BoneMapper::set_current_bone_idx(int p_bone_idx) {
372 current_bone_idx = p_bone_idx;
373 recreate_editor();
374}
375
376int BoneMapper::get_current_bone_idx() const {
377 return current_bone_idx;
378}
379
380void BoneMapper::recreate_editor() {
381 // Clear buttons.
382 int len = bone_mapper_buttons.size();
383 for (int i = 0; i < len; i++) {
384 profile_texture->remove_child(bone_mapper_buttons[i]);
385 memdelete(bone_mapper_buttons[i]);
386 }
387 bone_mapper_buttons.clear();
388
389 // Organize mapper items.
390 len = bone_mapper_items.size();
391 for (int i = 0; i < len; i++) {
392 bone_mapper_items[i]->set_visible(current_bone_idx == i);
393 }
394
395 Ref<SkeletonProfile> profile = bone_map->get_profile();
396 if (profile.is_valid()) {
397 SkeletonProfileHumanoid *hmn = Object::cast_to<SkeletonProfileHumanoid>(profile.ptr());
398 if (hmn) {
399 StringName hmn_group_name = profile->get_group_name(current_group_idx);
400 if (hmn_group_name == "Body") {
401 profile_texture->set_texture(get_editor_theme_icon(SNAME("BoneMapHumanBody")));
402 } else if (hmn_group_name == "Face") {
403 profile_texture->set_texture(get_editor_theme_icon(SNAME("BoneMapHumanFace")));
404 } else if (hmn_group_name == "LeftHand") {
405 profile_texture->set_texture(get_editor_theme_icon(SNAME("BoneMapHumanLeftHand")));
406 } else if (hmn_group_name == "RightHand") {
407 profile_texture->set_texture(get_editor_theme_icon(SNAME("BoneMapHumanRightHand")));
408 }
409 } else {
410 profile_texture->set_texture(profile->get_texture(current_group_idx));
411 }
412 } else {
413 profile_texture->set_texture(Ref<Texture2D>());
414 }
415
416 if (!profile.is_valid()) {
417 return;
418 }
419
420 for (int i = 0; i < len; i++) {
421 if (profile->get_group(i) == profile->get_group_name(current_group_idx)) {
422 BoneMapperButton *mb = memnew(BoneMapperButton(profile->get_bone_name(i), profile->is_require(i), current_bone_idx == i));
423 mb->connect("pressed", callable_mp(this, &BoneMapper::set_current_bone_idx).bind(i), CONNECT_DEFERRED);
424 mb->set_h_grow_direction(GROW_DIRECTION_BOTH);
425 mb->set_v_grow_direction(GROW_DIRECTION_BOTH);
426 Vector2 vc = profile->get_handle_offset(i);
427 bone_mapper_buttons.push_back(mb);
428 profile_texture->add_child(mb);
429 mb->set_anchor(SIDE_LEFT, vc.x);
430 mb->set_anchor(SIDE_RIGHT, vc.x);
431 mb->set_anchor(SIDE_TOP, vc.y);
432 mb->set_anchor(SIDE_BOTTOM, vc.y);
433 }
434 }
435
436 _update_state();
437}
438
439void BoneMapper::clear_items() {
440 // Clear items.
441 int len = bone_mapper_items.size();
442 for (int i = 0; i < len; i++) {
443 bone_mapper_items[i]->disconnect("pick", callable_mp(this, &BoneMapper::_pick_bone));
444 mapper_item_vbox->remove_child(bone_mapper_items[i]);
445 memdelete(bone_mapper_items[i]);
446 }
447 bone_mapper_items.clear();
448}
449
450void BoneMapper::recreate_items() {
451 clear_items();
452 // Create items by profile.
453 Ref<SkeletonProfile> profile = bone_map->get_profile();
454 if (profile.is_valid()) {
455 int len = profile->get_bone_size();
456 for (int i = 0; i < len; i++) {
457 StringName bn = profile->get_bone_name(i);
458 bone_mapper_items.append(memnew(BoneMapperItem(bone_map, bn)));
459 bone_mapper_items[i]->connect("pick", callable_mp(this, &BoneMapper::_pick_bone), CONNECT_DEFERRED);
460 mapper_item_vbox->add_child(bone_mapper_items[i]);
461 }
462 }
463
464 update_group_idx();
465 recreate_editor();
466}
467
468void BoneMapper::_update_state() {
469 int len = bone_mapper_buttons.size();
470 for (int i = 0; i < len; i++) {
471 StringName pbn = bone_mapper_buttons[i]->get_profile_bone_name();
472 StringName sbn = bone_map->get_skeleton_bone_name(pbn);
473 int bone_idx = skeleton->find_bone(sbn);
474 if (bone_idx >= 0) {
475 if (bone_map->get_skeleton_bone_name_count(sbn) == 1) {
476 Ref<SkeletonProfile> prof = bone_map->get_profile();
477
478 StringName parent_name = prof->get_bone_parent(prof->find_bone(pbn));
479 Vector<int> prof_parent_bones;
480 while (parent_name != StringName()) {
481 prof_parent_bones.push_back(skeleton->find_bone(bone_map->get_skeleton_bone_name(parent_name)));
482 if (prof->find_bone(parent_name) == -1) {
483 break;
484 }
485 parent_name = prof->get_bone_parent(prof->find_bone(parent_name));
486 }
487
488 int parent_id = skeleton->get_bone_parent(bone_idx);
489 Vector<int> skel_parent_bones;
490 while (parent_id >= 0) {
491 skel_parent_bones.push_back(parent_id);
492 parent_id = skeleton->get_bone_parent(parent_id);
493 }
494
495 bool is_broken = false;
496 for (int j = 0; j < prof_parent_bones.size(); j++) {
497 if (prof_parent_bones[j] != -1 && !skel_parent_bones.has(prof_parent_bones[j])) {
498 is_broken = true;
499 }
500 }
501
502 if (is_broken) {
503 bone_mapper_buttons[i]->set_state(BoneMapperButton::BONE_MAP_STATE_ERROR);
504 } else {
505 bone_mapper_buttons[i]->set_state(BoneMapperButton::BONE_MAP_STATE_SET);
506 }
507 } else {
508 bone_mapper_buttons[i]->set_state(BoneMapperButton::BONE_MAP_STATE_ERROR);
509 }
510 } else {
511 if (bone_mapper_buttons[i]->is_require()) {
512 bone_mapper_buttons[i]->set_state(BoneMapperButton::BONE_MAP_STATE_MISSING);
513 } else {
514 bone_mapper_buttons[i]->set_state(BoneMapperButton::BONE_MAP_STATE_UNSET);
515 }
516 }
517 }
518}
519
520void BoneMapper::_clear_mapping_current_group() {
521 if (bone_map.is_valid()) {
522 Ref<SkeletonProfile> profile = bone_map->get_profile();
523 if (profile.is_valid() && profile->get_group_size() > 0) {
524 int len = profile->get_bone_size();
525 for (int i = 0; i < len; i++) {
526 if (profile->get_group(i) == profile->get_group_name(current_group_idx)) {
527 bone_map->_set_skeleton_bone_name(profile->get_bone_name(i), StringName());
528 }
529 }
530 recreate_items();
531 }
532 }
533}
534
535#ifdef MODULE_REGEX_ENABLED
536int BoneMapper::search_bone_by_name(Skeleton3D *p_skeleton, Vector<String> p_picklist, BoneSegregation p_segregation, int p_parent, int p_child, int p_children_count) {
537 // There may be multiple candidates hit by existing the subsidiary bone.
538 // The one with the shortest name is probably the original.
539 LocalVector<String> hit_list;
540 String shortest = "";
541
542 for (int word_idx = 0; word_idx < p_picklist.size(); word_idx++) {
543 RegEx re = RegEx(p_picklist[word_idx]);
544 if (p_child == -1) {
545 Vector<int> bones_to_process = p_parent == -1 ? p_skeleton->get_parentless_bones() : p_skeleton->get_bone_children(p_parent);
546 while (bones_to_process.size() > 0) {
547 int idx = bones_to_process[0];
548 bones_to_process.erase(idx);
549 Vector<int> children = p_skeleton->get_bone_children(idx);
550 for (int i = 0; i < children.size(); i++) {
551 bones_to_process.push_back(children[i]);
552 }
553
554 if (p_children_count == 0 && children.size() > 0) {
555 continue;
556 }
557 if (p_children_count > 0 && children.size() < p_children_count) {
558 continue;
559 }
560
561 String bn = skeleton->get_bone_name(idx);
562 if (!re.search(bn.to_lower()).is_null() && guess_bone_segregation(bn) == p_segregation) {
563 hit_list.push_back(bn);
564 }
565 }
566
567 if (hit_list.size() > 0) {
568 shortest = hit_list[0];
569 for (const String &hit : hit_list) {
570 if (hit.length() < shortest.length()) {
571 shortest = hit; // Prioritize parent.
572 }
573 }
574 }
575 } else {
576 int idx = skeleton->get_bone_parent(p_child);
577 while (idx != p_parent && idx >= 0) {
578 Vector<int> children = p_skeleton->get_bone_children(idx);
579 if (p_children_count == 0 && children.size() > 0) {
580 continue;
581 }
582 if (p_children_count > 0 && children.size() < p_children_count) {
583 continue;
584 }
585
586 String bn = skeleton->get_bone_name(idx);
587 if (!re.search(bn.to_lower()).is_null() && guess_bone_segregation(bn) == p_segregation) {
588 hit_list.push_back(bn);
589 }
590 idx = skeleton->get_bone_parent(idx);
591 }
592
593 if (hit_list.size() > 0) {
594 shortest = hit_list[0];
595 for (const String &hit : hit_list) {
596 if (hit.length() <= shortest.length()) {
597 shortest = hit; // Prioritize parent.
598 }
599 }
600 }
601 }
602
603 if (shortest != "") {
604 break;
605 }
606 }
607
608 if (shortest == "") {
609 return -1;
610 }
611
612 return skeleton->find_bone(shortest);
613}
614
615BoneMapper::BoneSegregation BoneMapper::guess_bone_segregation(String p_bone_name) {
616 String fixed_bn = p_bone_name.to_snake_case();
617
618 LocalVector<String> left_words;
619 left_words.push_back("(?<![a-zA-Z])left");
620 left_words.push_back("(?<![a-zA-Z0-9])l(?![a-zA-Z0-9])");
621
622 LocalVector<String> right_words;
623 right_words.push_back("(?<![a-zA-Z])right");
624 right_words.push_back("(?<![a-zA-Z0-9])r(?![a-zA-Z0-9])");
625
626 for (uint32_t i = 0; i < left_words.size(); i++) {
627 RegEx re_l = RegEx(left_words[i]);
628 if (!re_l.search(fixed_bn).is_null()) {
629 return BONE_SEGREGATION_LEFT;
630 }
631 RegEx re_r = RegEx(right_words[i]);
632 if (!re_r.search(fixed_bn).is_null()) {
633 return BONE_SEGREGATION_RIGHT;
634 }
635 }
636
637 return BONE_SEGREGATION_NONE;
638}
639
640void BoneMapper::_run_auto_mapping() {
641 auto_mapping_process(bone_map);
642 recreate_items();
643}
644
645void BoneMapper::auto_mapping_process(Ref<BoneMap> &p_bone_map) {
646 WARN_PRINT("Run auto mapping.");
647
648 int bone_idx = -1;
649 Vector<String> picklist; // Use Vector<String> because match words have priority.
650 Vector<int> search_path;
651
652 // 1. Guess Hips
653 picklist.push_back("hip");
654 picklist.push_back("pelvis");
655 picklist.push_back("waist");
656 picklist.push_back("torso");
657 int hips = search_bone_by_name(skeleton, picklist);
658 if (hips == -1) {
659 WARN_PRINT("Auto Mapping couldn't guess Hips. Abort auto mapping.");
660 return; // If there is no Hips, we cannot guess bone after then.
661 } else {
662 p_bone_map->_set_skeleton_bone_name("Hips", skeleton->get_bone_name(hips));
663 }
664 picklist.clear();
665
666 // 2. Guess Root
667 bone_idx = skeleton->get_bone_parent(hips);
668 while (bone_idx >= 0) {
669 search_path.push_back(bone_idx);
670 bone_idx = skeleton->get_bone_parent(bone_idx);
671 }
672 if (search_path.size() == 0) {
673 bone_idx = -1;
674 } else if (search_path.size() == 1) {
675 bone_idx = search_path[0]; // It is only one bone which can be root.
676 } else {
677 bool found = false;
678 for (int i = 0; i < search_path.size(); i++) {
679 RegEx re = RegEx("root");
680 if (!re.search(skeleton->get_bone_name(search_path[i]).to_lower()).is_null()) {
681 bone_idx = search_path[i]; // Name match is preferred.
682 found = true;
683 break;
684 }
685 }
686 if (!found) {
687 for (int i = 0; i < search_path.size(); i++) {
688 if (skeleton->get_bone_global_rest(search_path[i]).origin.is_zero_approx()) {
689 bone_idx = search_path[i]; // The bone existing at the origin is appropriate as a root.
690 found = true;
691 break;
692 }
693 }
694 }
695 if (!found) {
696 bone_idx = search_path[search_path.size() - 1]; // Ambiguous, but most parental bone selected.
697 }
698 }
699 if (bone_idx == -1) {
700 WARN_PRINT("Auto Mapping couldn't guess Root."); // Root is not required, so continue.
701 } else {
702 p_bone_map->_set_skeleton_bone_name("Root", skeleton->get_bone_name(bone_idx));
703 }
704 bone_idx = -1;
705 search_path.clear();
706
707 // 3. Guess Neck
708 picklist.push_back("neck");
709 picklist.push_back("head"); // For no neck model.
710 picklist.push_back("face"); // Same above.
711 int neck = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_NONE, hips);
712 picklist.clear();
713
714 // 4. Guess Head
715 picklist.push_back("head");
716 picklist.push_back("face");
717 int head = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_NONE, neck);
718 if (head == -1) {
719 search_path = skeleton->get_bone_children(neck);
720 if (search_path.size() == 1) {
721 head = search_path[0]; // Maybe only one child of the Neck is Head.
722 }
723 }
724 if (head == -1) {
725 if (neck != -1) {
726 head = neck; // The head animation should have more movement.
727 neck = -1;
728 p_bone_map->_set_skeleton_bone_name("Head", skeleton->get_bone_name(head));
729 } else {
730 WARN_PRINT("Auto Mapping couldn't guess Neck or Head."); // Continued for guessing on the other bones. But abort when guessing spines step.
731 }
732 } else {
733 p_bone_map->_set_skeleton_bone_name("Neck", skeleton->get_bone_name(neck));
734 p_bone_map->_set_skeleton_bone_name("Head", skeleton->get_bone_name(head));
735 }
736 picklist.clear();
737 search_path.clear();
738
739 int neck_or_head = neck != -1 ? neck : (head != -1 ? head : -1);
740 if (neck_or_head != -1) {
741 // 4-1. Guess Eyes
742 picklist.push_back("eye(?!.*(brow|lash|lid))");
743 bone_idx = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_LEFT, neck_or_head);
744 if (bone_idx == -1) {
745 WARN_PRINT("Auto Mapping couldn't guess LeftEye.");
746 } else {
747 p_bone_map->_set_skeleton_bone_name("LeftEye", skeleton->get_bone_name(bone_idx));
748 }
749
750 bone_idx = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_RIGHT, neck_or_head);
751 if (bone_idx == -1) {
752 WARN_PRINT("Auto Mapping couldn't guess RightEye.");
753 } else {
754 p_bone_map->_set_skeleton_bone_name("RightEye", skeleton->get_bone_name(bone_idx));
755 }
756 picklist.clear();
757
758 // 4-2. Guess Jaw
759 picklist.push_back("jaw");
760 bone_idx = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_NONE, neck_or_head);
761 if (bone_idx == -1) {
762 WARN_PRINT("Auto Mapping couldn't guess Jaw.");
763 } else {
764 p_bone_map->_set_skeleton_bone_name("Jaw", skeleton->get_bone_name(bone_idx));
765 }
766 bone_idx = -1;
767 picklist.clear();
768 }
769
770 // 5. Guess Foots
771 picklist.push_back("foot");
772 picklist.push_back("ankle");
773 int left_foot = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_LEFT, hips);
774 if (left_foot == -1) {
775 WARN_PRINT("Auto Mapping couldn't guess LeftFoot.");
776 } else {
777 p_bone_map->_set_skeleton_bone_name("LeftFoot", skeleton->get_bone_name(left_foot));
778 }
779 int right_foot = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_RIGHT, hips);
780 if (right_foot == -1) {
781 WARN_PRINT("Auto Mapping couldn't guess RightFoot.");
782 } else {
783 p_bone_map->_set_skeleton_bone_name("RightFoot", skeleton->get_bone_name(right_foot));
784 }
785 picklist.clear();
786
787 // 5-1. Guess LowerLegs
788 picklist.push_back("(low|under).*leg");
789 picklist.push_back("knee");
790 picklist.push_back("shin");
791 picklist.push_back("calf");
792 picklist.push_back("leg");
793 int left_lower_leg = -1;
794 if (left_foot != -1) {
795 left_lower_leg = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_LEFT, hips, left_foot);
796 }
797 if (left_lower_leg == -1) {
798 WARN_PRINT("Auto Mapping couldn't guess LeftLowerLeg.");
799 } else {
800 p_bone_map->_set_skeleton_bone_name("LeftLowerLeg", skeleton->get_bone_name(left_lower_leg));
801 }
802 int right_lower_leg = -1;
803 if (right_foot != -1) {
804 right_lower_leg = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_RIGHT, hips, right_foot);
805 }
806 if (right_lower_leg == -1) {
807 WARN_PRINT("Auto Mapping couldn't guess RightLowerLeg.");
808 } else {
809 p_bone_map->_set_skeleton_bone_name("RightLowerLeg", skeleton->get_bone_name(right_lower_leg));
810 }
811 picklist.clear();
812
813 // 5-2. Guess UpperLegs
814 picklist.push_back("up.*leg");
815 picklist.push_back("thigh");
816 picklist.push_back("leg");
817 if (left_lower_leg != -1) {
818 bone_idx = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_LEFT, hips, left_lower_leg);
819 }
820 if (bone_idx == -1) {
821 WARN_PRINT("Auto Mapping couldn't guess LeftUpperLeg.");
822 } else {
823 p_bone_map->_set_skeleton_bone_name("LeftUpperLeg", skeleton->get_bone_name(bone_idx));
824 }
825 bone_idx = -1;
826 if (right_lower_leg != -1) {
827 bone_idx = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_RIGHT, hips, right_lower_leg);
828 }
829 if (bone_idx == -1) {
830 WARN_PRINT("Auto Mapping couldn't guess RightUpperLeg.");
831 } else {
832 p_bone_map->_set_skeleton_bone_name("RightUpperLeg", skeleton->get_bone_name(bone_idx));
833 }
834 bone_idx = -1;
835 picklist.clear();
836
837 // 5-3. Guess Toes
838 picklist.push_back("toe");
839 picklist.push_back("ball");
840 if (left_foot != -1) {
841 bone_idx = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_LEFT, left_foot);
842 if (bone_idx == -1) {
843 search_path = skeleton->get_bone_children(left_foot);
844 if (search_path.size() == 1) {
845 bone_idx = search_path[0]; // Maybe only one child of the Foot is Toes.
846 }
847 search_path.clear();
848 }
849 }
850 if (bone_idx == -1) {
851 WARN_PRINT("Auto Mapping couldn't guess LeftToes.");
852 } else {
853 p_bone_map->_set_skeleton_bone_name("LeftToes", skeleton->get_bone_name(bone_idx));
854 }
855 bone_idx = -1;
856 if (right_foot != -1) {
857 bone_idx = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_RIGHT, right_foot);
858 if (bone_idx == -1) {
859 search_path = skeleton->get_bone_children(right_foot);
860 if (search_path.size() == 1) {
861 bone_idx = search_path[0]; // Maybe only one child of the Foot is Toes.
862 }
863 search_path.clear();
864 }
865 }
866 if (bone_idx == -1) {
867 WARN_PRINT("Auto Mapping couldn't guess RightToes.");
868 } else {
869 p_bone_map->_set_skeleton_bone_name("RightToes", skeleton->get_bone_name(bone_idx));
870 }
871 bone_idx = -1;
872 picklist.clear();
873
874 // 6. Guess Hands
875 picklist.push_back("hand");
876 picklist.push_back("wrist");
877 picklist.push_back("palm");
878 picklist.push_back("fingers");
879 int left_hand_or_palm = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_LEFT, hips, -1, 5);
880 if (left_hand_or_palm == -1) {
881 // Ambiguous, but try again for fewer finger models.
882 left_hand_or_palm = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_LEFT, hips);
883 }
884 int left_hand = left_hand_or_palm; // Check for the presence of a wrist, since bones with five children may be palmar.
885 while (left_hand != -1) {
886 bone_idx = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_LEFT, hips, left_hand);
887 if (bone_idx == -1) {
888 break;
889 }
890 left_hand = bone_idx;
891 }
892 if (left_hand == -1) {
893 WARN_PRINT("Auto Mapping couldn't guess LeftHand.");
894 } else {
895 p_bone_map->_set_skeleton_bone_name("LeftHand", skeleton->get_bone_name(left_hand));
896 }
897 bone_idx = -1;
898 int right_hand_or_palm = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_RIGHT, hips, -1, 5);
899 if (right_hand_or_palm == -1) {
900 // Ambiguous, but try again for fewer finger models.
901 right_hand_or_palm = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_RIGHT, hips);
902 }
903 int right_hand = right_hand_or_palm;
904 while (right_hand != -1) {
905 bone_idx = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_RIGHT, hips, right_hand);
906 if (bone_idx == -1) {
907 break;
908 }
909 right_hand = bone_idx;
910 }
911 if (right_hand == -1) {
912 WARN_PRINT("Auto Mapping couldn't guess RightHand.");
913 } else {
914 p_bone_map->_set_skeleton_bone_name("RightHand", skeleton->get_bone_name(right_hand));
915 }
916 bone_idx = -1;
917 picklist.clear();
918
919 // 6-1. Guess Finger
920 bool named_finger_is_found = false;
921 LocalVector<String> fingers;
922 fingers.push_back("thumb|pollex");
923 fingers.push_back("index|fore");
924 fingers.push_back("middle");
925 fingers.push_back("ring");
926 fingers.push_back("little|pinkie|pinky");
927 if (left_hand_or_palm != -1) {
928 LocalVector<LocalVector<String>> left_fingers_map;
929 left_fingers_map.resize(5);
930 left_fingers_map[0].push_back("LeftThumbMetacarpal");
931 left_fingers_map[0].push_back("LeftThumbProximal");
932 left_fingers_map[0].push_back("LeftThumbDistal");
933 left_fingers_map[1].push_back("LeftIndexProximal");
934 left_fingers_map[1].push_back("LeftIndexIntermediate");
935 left_fingers_map[1].push_back("LeftIndexDistal");
936 left_fingers_map[2].push_back("LeftMiddleProximal");
937 left_fingers_map[2].push_back("LeftMiddleIntermediate");
938 left_fingers_map[2].push_back("LeftMiddleDistal");
939 left_fingers_map[3].push_back("LeftRingProximal");
940 left_fingers_map[3].push_back("LeftRingIntermediate");
941 left_fingers_map[3].push_back("LeftRingDistal");
942 left_fingers_map[4].push_back("LeftLittleProximal");
943 left_fingers_map[4].push_back("LeftLittleIntermediate");
944 left_fingers_map[4].push_back("LeftLittleDistal");
945 for (int i = 0; i < 5; i++) {
946 picklist.push_back(fingers[i]);
947 int finger = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_LEFT, left_hand_or_palm, -1, 0);
948 if (finger != -1) {
949 while (finger != left_hand_or_palm && finger >= 0) {
950 search_path.push_back(finger);
951 finger = skeleton->get_bone_parent(finger);
952 }
953 search_path.reverse();
954 if (search_path.size() == 1) {
955 p_bone_map->_set_skeleton_bone_name(left_fingers_map[i][0], skeleton->get_bone_name(search_path[0]));
956 named_finger_is_found = true;
957 } else if (search_path.size() == 2) {
958 p_bone_map->_set_skeleton_bone_name(left_fingers_map[i][0], skeleton->get_bone_name(search_path[0]));
959 p_bone_map->_set_skeleton_bone_name(left_fingers_map[i][1], skeleton->get_bone_name(search_path[1]));
960 named_finger_is_found = true;
961 } else if (search_path.size() >= 3) {
962 search_path = search_path.slice(-3); // Eliminate the possibility of carpal bone.
963 p_bone_map->_set_skeleton_bone_name(left_fingers_map[i][0], skeleton->get_bone_name(search_path[0]));
964 p_bone_map->_set_skeleton_bone_name(left_fingers_map[i][1], skeleton->get_bone_name(search_path[1]));
965 p_bone_map->_set_skeleton_bone_name(left_fingers_map[i][2], skeleton->get_bone_name(search_path[2]));
966 named_finger_is_found = true;
967 }
968 }
969 picklist.clear();
970 search_path.clear();
971 }
972
973 // It is a bit corner case, but possibly the finger names are sequentially numbered...
974 if (!named_finger_is_found) {
975 picklist.push_back("finger");
976 RegEx finger_re = RegEx("finger");
977 search_path = skeleton->get_bone_children(left_hand_or_palm);
978 Vector<String> finger_names;
979 for (int i = 0; i < search_path.size(); i++) {
980 String bn = skeleton->get_bone_name(search_path[i]);
981 if (!finger_re.search(bn.to_lower()).is_null()) {
982 finger_names.push_back(bn);
983 }
984 }
985 finger_names.sort(); // Order by lexicographic, normal use cases never have more than 10 fingers in one hand.
986 search_path.clear();
987 for (int i = 0; i < finger_names.size(); i++) {
988 if (i >= 5) {
989 break;
990 }
991 int finger_root = skeleton->find_bone(finger_names[i]);
992 int finger = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_LEFT, finger_root, -1, 0);
993 if (finger != -1) {
994 while (finger != finger_root && finger >= 0) {
995 search_path.push_back(finger);
996 finger = skeleton->get_bone_parent(finger);
997 }
998 }
999 search_path.push_back(finger_root);
1000 search_path.reverse();
1001 if (search_path.size() == 1) {
1002 p_bone_map->_set_skeleton_bone_name(left_fingers_map[i][0], skeleton->get_bone_name(search_path[0]));
1003 } else if (search_path.size() == 2) {
1004 p_bone_map->_set_skeleton_bone_name(left_fingers_map[i][0], skeleton->get_bone_name(search_path[0]));
1005 p_bone_map->_set_skeleton_bone_name(left_fingers_map[i][1], skeleton->get_bone_name(search_path[1]));
1006 } else if (search_path.size() >= 3) {
1007 search_path = search_path.slice(-3); // Eliminate the possibility of carpal bone.
1008 p_bone_map->_set_skeleton_bone_name(left_fingers_map[i][0], skeleton->get_bone_name(search_path[0]));
1009 p_bone_map->_set_skeleton_bone_name(left_fingers_map[i][1], skeleton->get_bone_name(search_path[1]));
1010 p_bone_map->_set_skeleton_bone_name(left_fingers_map[i][2], skeleton->get_bone_name(search_path[2]));
1011 }
1012 search_path.clear();
1013 }
1014 picklist.clear();
1015 }
1016 }
1017 named_finger_is_found = false;
1018 if (right_hand_or_palm != -1) {
1019 LocalVector<LocalVector<String>> right_fingers_map;
1020 right_fingers_map.resize(5);
1021 right_fingers_map[0].push_back("RightThumbMetacarpal");
1022 right_fingers_map[0].push_back("RightThumbProximal");
1023 right_fingers_map[0].push_back("RightThumbDistal");
1024 right_fingers_map[1].push_back("RightIndexProximal");
1025 right_fingers_map[1].push_back("RightIndexIntermediate");
1026 right_fingers_map[1].push_back("RightIndexDistal");
1027 right_fingers_map[2].push_back("RightMiddleProximal");
1028 right_fingers_map[2].push_back("RightMiddleIntermediate");
1029 right_fingers_map[2].push_back("RightMiddleDistal");
1030 right_fingers_map[3].push_back("RightRingProximal");
1031 right_fingers_map[3].push_back("RightRingIntermediate");
1032 right_fingers_map[3].push_back("RightRingDistal");
1033 right_fingers_map[4].push_back("RightLittleProximal");
1034 right_fingers_map[4].push_back("RightLittleIntermediate");
1035 right_fingers_map[4].push_back("RightLittleDistal");
1036 for (int i = 0; i < 5; i++) {
1037 picklist.push_back(fingers[i]);
1038 int finger = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_RIGHT, right_hand_or_palm, -1, 0);
1039 if (finger != -1) {
1040 while (finger != right_hand_or_palm && finger >= 0) {
1041 search_path.push_back(finger);
1042 finger = skeleton->get_bone_parent(finger);
1043 }
1044 search_path.reverse();
1045 if (search_path.size() == 1) {
1046 p_bone_map->_set_skeleton_bone_name(right_fingers_map[i][0], skeleton->get_bone_name(search_path[0]));
1047 named_finger_is_found = true;
1048 } else if (search_path.size() == 2) {
1049 p_bone_map->_set_skeleton_bone_name(right_fingers_map[i][0], skeleton->get_bone_name(search_path[0]));
1050 p_bone_map->_set_skeleton_bone_name(right_fingers_map[i][1], skeleton->get_bone_name(search_path[1]));
1051 named_finger_is_found = true;
1052 } else if (search_path.size() >= 3) {
1053 search_path = search_path.slice(-3); // Eliminate the possibility of carpal bone.
1054 p_bone_map->_set_skeleton_bone_name(right_fingers_map[i][0], skeleton->get_bone_name(search_path[0]));
1055 p_bone_map->_set_skeleton_bone_name(right_fingers_map[i][1], skeleton->get_bone_name(search_path[1]));
1056 p_bone_map->_set_skeleton_bone_name(right_fingers_map[i][2], skeleton->get_bone_name(search_path[2]));
1057 named_finger_is_found = true;
1058 }
1059 }
1060 picklist.clear();
1061 search_path.clear();
1062 }
1063
1064 // It is a bit corner case, but possibly the finger names are sequentially numbered...
1065 if (!named_finger_is_found) {
1066 picklist.push_back("finger");
1067 RegEx finger_re = RegEx("finger");
1068 search_path = skeleton->get_bone_children(right_hand_or_palm);
1069 Vector<String> finger_names;
1070 for (int i = 0; i < search_path.size(); i++) {
1071 String bn = skeleton->get_bone_name(search_path[i]);
1072 if (!finger_re.search(bn.to_lower()).is_null()) {
1073 finger_names.push_back(bn);
1074 }
1075 }
1076 finger_names.sort(); // Order by lexicographic, normal use cases never have more than 10 fingers in one hand.
1077 search_path.clear();
1078 for (int i = 0; i < finger_names.size(); i++) {
1079 if (i >= 5) {
1080 break;
1081 }
1082 int finger_root = skeleton->find_bone(finger_names[i]);
1083 int finger = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_RIGHT, finger_root, -1, 0);
1084 if (finger != -1) {
1085 while (finger != finger_root && finger >= 0) {
1086 search_path.push_back(finger);
1087 finger = skeleton->get_bone_parent(finger);
1088 }
1089 }
1090 search_path.push_back(finger_root);
1091 search_path.reverse();
1092 if (search_path.size() == 1) {
1093 p_bone_map->_set_skeleton_bone_name(right_fingers_map[i][0], skeleton->get_bone_name(search_path[0]));
1094 } else if (search_path.size() == 2) {
1095 p_bone_map->_set_skeleton_bone_name(right_fingers_map[i][0], skeleton->get_bone_name(search_path[0]));
1096 p_bone_map->_set_skeleton_bone_name(right_fingers_map[i][1], skeleton->get_bone_name(search_path[1]));
1097 } else if (search_path.size() >= 3) {
1098 search_path = search_path.slice(-3); // Eliminate the possibility of carpal bone.
1099 p_bone_map->_set_skeleton_bone_name(right_fingers_map[i][0], skeleton->get_bone_name(search_path[0]));
1100 p_bone_map->_set_skeleton_bone_name(right_fingers_map[i][1], skeleton->get_bone_name(search_path[1]));
1101 p_bone_map->_set_skeleton_bone_name(right_fingers_map[i][2], skeleton->get_bone_name(search_path[2]));
1102 }
1103 search_path.clear();
1104 }
1105 picklist.clear();
1106 }
1107 }
1108
1109 // 7. Guess Arms
1110 picklist.push_back("shoulder");
1111 picklist.push_back("clavicle");
1112 picklist.push_back("collar");
1113 int left_shoulder = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_LEFT, hips);
1114 if (left_shoulder == -1) {
1115 WARN_PRINT("Auto Mapping couldn't guess LeftShoulder.");
1116 } else {
1117 p_bone_map->_set_skeleton_bone_name("LeftShoulder", skeleton->get_bone_name(left_shoulder));
1118 }
1119 int right_shoulder = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_RIGHT, hips);
1120 if (right_shoulder == -1) {
1121 WARN_PRINT("Auto Mapping couldn't guess RightShoulder.");
1122 } else {
1123 p_bone_map->_set_skeleton_bone_name("RightShoulder", skeleton->get_bone_name(right_shoulder));
1124 }
1125 picklist.clear();
1126
1127 // 7-1. Guess LowerArms
1128 picklist.push_back("(low|fore).*arm");
1129 picklist.push_back("elbow");
1130 picklist.push_back("arm");
1131 int left_lower_arm = -1;
1132 if (left_shoulder != -1 && left_hand_or_palm != -1) {
1133 left_lower_arm = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_LEFT, left_shoulder, left_hand_or_palm);
1134 }
1135 if (left_lower_arm == -1) {
1136 WARN_PRINT("Auto Mapping couldn't guess LeftLowerArm.");
1137 } else {
1138 p_bone_map->_set_skeleton_bone_name("LeftLowerArm", skeleton->get_bone_name(left_lower_arm));
1139 }
1140 int right_lower_arm = -1;
1141 if (right_shoulder != -1 && right_hand_or_palm != -1) {
1142 right_lower_arm = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_RIGHT, right_shoulder, right_hand_or_palm);
1143 }
1144 if (right_lower_arm == -1) {
1145 WARN_PRINT("Auto Mapping couldn't guess RightLowerArm.");
1146 } else {
1147 p_bone_map->_set_skeleton_bone_name("RightLowerArm", skeleton->get_bone_name(right_lower_arm));
1148 }
1149 picklist.clear();
1150
1151 // 7-2. Guess UpperArms
1152 picklist.push_back("up.*arm");
1153 picklist.push_back("arm");
1154 if (left_shoulder != -1 && left_lower_arm != -1) {
1155 bone_idx = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_LEFT, left_shoulder, left_lower_arm);
1156 }
1157 if (bone_idx == -1) {
1158 WARN_PRINT("Auto Mapping couldn't guess LeftUpperArm.");
1159 } else {
1160 p_bone_map->_set_skeleton_bone_name("LeftUpperArm", skeleton->get_bone_name(bone_idx));
1161 }
1162 bone_idx = -1;
1163 if (right_shoulder != -1 && right_lower_arm != -1) {
1164 bone_idx = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_RIGHT, right_shoulder, right_lower_arm);
1165 }
1166 if (bone_idx == -1) {
1167 WARN_PRINT("Auto Mapping couldn't guess RightUpperArm.");
1168 } else {
1169 p_bone_map->_set_skeleton_bone_name("RightUpperArm", skeleton->get_bone_name(bone_idx));
1170 }
1171 bone_idx = -1;
1172 picklist.clear();
1173
1174 // 8. Guess UpperChest or Chest
1175 if (neck_or_head == -1) {
1176 return; // Abort.
1177 }
1178 int chest_or_upper_chest = skeleton->get_bone_parent(neck_or_head);
1179 bool is_appropriate = true;
1180 if (left_shoulder != -1) {
1181 bone_idx = skeleton->get_bone_parent(left_shoulder);
1182 bool detect = false;
1183 while (bone_idx != hips && bone_idx >= 0) {
1184 if (bone_idx == chest_or_upper_chest) {
1185 detect = true;
1186 break;
1187 }
1188 bone_idx = skeleton->get_bone_parent(bone_idx);
1189 }
1190 if (!detect) {
1191 is_appropriate = false;
1192 }
1193 bone_idx = -1;
1194 }
1195 if (right_shoulder != -1) {
1196 bone_idx = skeleton->get_bone_parent(right_shoulder);
1197 bool detect = false;
1198 while (bone_idx != hips && bone_idx >= 0) {
1199 if (bone_idx == chest_or_upper_chest) {
1200 detect = true;
1201 break;
1202 }
1203 bone_idx = skeleton->get_bone_parent(bone_idx);
1204 }
1205 if (!detect) {
1206 is_appropriate = false;
1207 }
1208 bone_idx = -1;
1209 }
1210 if (!is_appropriate) {
1211 if (skeleton->get_bone_parent(left_shoulder) == skeleton->get_bone_parent(right_shoulder)) {
1212 chest_or_upper_chest = skeleton->get_bone_parent(left_shoulder);
1213 } else {
1214 chest_or_upper_chest = -1;
1215 }
1216 }
1217 if (chest_or_upper_chest == -1) {
1218 WARN_PRINT("Auto Mapping couldn't guess Chest or UpperChest. Abort auto mapping.");
1219 return; // Will be not able to guess Spines.
1220 }
1221
1222 // 9. Guess Spines
1223 bone_idx = skeleton->get_bone_parent(chest_or_upper_chest);
1224 while (bone_idx != hips && bone_idx >= 0) {
1225 search_path.push_back(bone_idx);
1226 bone_idx = skeleton->get_bone_parent(bone_idx);
1227 }
1228 search_path.reverse();
1229 if (search_path.size() == 0) {
1230 p_bone_map->_set_skeleton_bone_name("Spine", skeleton->get_bone_name(chest_or_upper_chest)); // Maybe chibi model...?
1231 } else if (search_path.size() == 1) {
1232 p_bone_map->_set_skeleton_bone_name("Spine", skeleton->get_bone_name(search_path[0]));
1233 p_bone_map->_set_skeleton_bone_name("Chest", skeleton->get_bone_name(chest_or_upper_chest));
1234 } else if (search_path.size() >= 2) {
1235 p_bone_map->_set_skeleton_bone_name("Spine", skeleton->get_bone_name(search_path[0]));
1236 p_bone_map->_set_skeleton_bone_name("Chest", skeleton->get_bone_name(search_path[search_path.size() - 1])); // Probably UppeChest's parent is appropriate.
1237 p_bone_map->_set_skeleton_bone_name("UpperChest", skeleton->get_bone_name(chest_or_upper_chest));
1238 }
1239 bone_idx = -1;
1240 search_path.clear();
1241
1242 WARN_PRINT("Finish auto mapping.");
1243}
1244#endif // MODULE_REGEX_ENABLED
1245
1246void BoneMapper::_value_changed(const String &p_property, Variant p_value, const String &p_name, bool p_changing) {
1247 set(p_property, p_value);
1248 recreate_editor();
1249}
1250
1251void BoneMapper::_profile_changed(const String &p_property, Variant p_value, const String &p_name, bool p_changing) {
1252 bone_map->set(p_property, p_value);
1253
1254 // Run auto mapping when setting SkeletonProfileHumanoid by GUI Editor.
1255 Ref<SkeletonProfile> profile = bone_map->get_profile();
1256 if (profile.is_valid()) {
1257 SkeletonProfileHumanoid *hmn = Object::cast_to<SkeletonProfileHumanoid>(profile.ptr());
1258 if (hmn) {
1259#ifdef MODULE_REGEX_ENABLED
1260 _run_auto_mapping();
1261#endif // MODULE_REGEX_ENABLED
1262 }
1263 }
1264}
1265
1266void BoneMapper::_bind_methods() {
1267 ClassDB::bind_method(D_METHOD("set_current_group_idx", "current_group_idx"), &BoneMapper::set_current_group_idx);
1268 ClassDB::bind_method(D_METHOD("get_current_group_idx"), &BoneMapper::get_current_group_idx);
1269 ClassDB::bind_method(D_METHOD("set_current_bone_idx", "current_bone_idx"), &BoneMapper::set_current_bone_idx);
1270 ClassDB::bind_method(D_METHOD("get_current_bone_idx"), &BoneMapper::get_current_bone_idx);
1271 ADD_PROPERTY(PropertyInfo(Variant::INT, "current_group_idx"), "set_current_group_idx", "get_current_group_idx");
1272 ADD_PROPERTY(PropertyInfo(Variant::INT, "current_bone_idx"), "set_current_bone_idx", "get_current_bone_idx");
1273}
1274
1275void BoneMapper::_notification(int p_what) {
1276 switch (p_what) {
1277 case NOTIFICATION_ENTER_TREE: {
1278 create_editor();
1279 bone_map->connect("bone_map_updated", callable_mp(this, &BoneMapper::_update_state));
1280 bone_map->connect("profile_updated", callable_mp(this, &BoneMapper::recreate_items));
1281 } break;
1282 case NOTIFICATION_EXIT_TREE: {
1283 clear_items();
1284 if (!bone_map.is_null()) {
1285 if (bone_map->is_connected("bone_map_updated", callable_mp(this, &BoneMapper::_update_state))) {
1286 bone_map->disconnect("bone_map_updated", callable_mp(this, &BoneMapper::_update_state));
1287 }
1288 if (bone_map->is_connected("profile_updated", callable_mp(this, &BoneMapper::recreate_items))) {
1289 bone_map->disconnect("profile_updated", callable_mp(this, &BoneMapper::recreate_items));
1290 }
1291 }
1292 }
1293 }
1294}
1295
1296BoneMapper::BoneMapper(Skeleton3D *p_skeleton, Ref<BoneMap> &p_bone_map) {
1297 skeleton = p_skeleton;
1298 bone_map = p_bone_map;
1299}
1300
1301BoneMapper::~BoneMapper() {
1302}
1303
1304void BoneMapEditor::create_editors() {
1305 if (!skeleton) {
1306 return;
1307 }
1308 bone_mapper = memnew(BoneMapper(skeleton, bone_map));
1309 add_child(bone_mapper);
1310}
1311
1312void BoneMapEditor::fetch_objects() {
1313 skeleton = nullptr;
1314 // Hackey... but it may be the easiest way to get a selected object from "ImporterScene".
1315 SceneImportSettings *si = SceneImportSettings::get_singleton();
1316 if (!si) {
1317 return;
1318 }
1319 if (!si->is_visible()) {
1320 return;
1321 }
1322 Node *selected = si->get_selected_node();
1323 if (selected) {
1324 Skeleton3D *sk = Object::cast_to<Skeleton3D>(selected);
1325 if (!sk) {
1326 return;
1327 }
1328 skeleton = sk;
1329 } else {
1330 // Editor should not exist.
1331 skeleton = nullptr;
1332 }
1333}
1334
1335void BoneMapEditor::_notification(int p_what) {
1336 switch (p_what) {
1337 case NOTIFICATION_ENTER_TREE: {
1338 fetch_objects();
1339 create_editors();
1340 } break;
1341 case NOTIFICATION_EXIT_TREE: {
1342 skeleton = nullptr;
1343 } break;
1344 }
1345}
1346
1347BoneMapEditor::BoneMapEditor(Ref<BoneMap> &p_bone_map) {
1348 bone_map = p_bone_map;
1349}
1350
1351BoneMapEditor::~BoneMapEditor() {
1352}
1353
1354bool EditorInspectorPluginBoneMap::can_handle(Object *p_object) {
1355 return Object::cast_to<BoneMap>(p_object) != nullptr;
1356}
1357
1358void EditorInspectorPluginBoneMap::parse_begin(Object *p_object) {
1359 BoneMap *bm = Object::cast_to<BoneMap>(p_object);
1360 if (!bm) {
1361 return;
1362 }
1363 Ref<BoneMap> r(bm);
1364 editor = memnew(BoneMapEditor(r));
1365 add_custom_control(editor);
1366}
1367
1368BoneMapEditorPlugin::BoneMapEditorPlugin() {
1369 // Register properties in editor settings.
1370 EDITOR_DEF("editors/bone_mapper/handle_colors/unset", Color(0.3, 0.3, 0.3));
1371 EDITOR_DEF("editors/bone_mapper/handle_colors/set", Color(0.1, 0.6, 0.25));
1372 EDITOR_DEF("editors/bone_mapper/handle_colors/missing", Color(0.8, 0.2, 0.8));
1373 EDITOR_DEF("editors/bone_mapper/handle_colors/error", Color(0.8, 0.2, 0.2));
1374
1375 Ref<EditorInspectorPluginBoneMap> inspector_plugin;
1376 inspector_plugin.instantiate();
1377 add_inspector_plugin(inspector_plugin);
1378
1379 Ref<PostImportPluginSkeletonTrackOrganizer> post_import_plugin_track_organizer;
1380 post_import_plugin_track_organizer.instantiate();
1381 add_scene_post_import_plugin(post_import_plugin_track_organizer);
1382
1383 Ref<PostImportPluginSkeletonRenamer> post_import_plugin_renamer;
1384 post_import_plugin_renamer.instantiate();
1385 add_scene_post_import_plugin(post_import_plugin_renamer);
1386
1387 Ref<PostImportPluginSkeletonRestFixer> post_import_plugin_rest_fixer;
1388 post_import_plugin_rest_fixer.instantiate();
1389 add_scene_post_import_plugin(post_import_plugin_rest_fixer);
1390}
1391