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 | |
43 | void 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 | |
63 | StringName BoneMapperButton::get_profile_bone_name() const { |
64 | return profile_bone_name; |
65 | } |
66 | |
67 | void 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 | |
86 | bool BoneMapperButton::is_require() const { |
87 | return require; |
88 | } |
89 | |
90 | void BoneMapperButton::_notification(int p_what) { |
91 | switch (p_what) { |
92 | case NOTIFICATION_ENTER_TREE: { |
93 | fetch_textures(); |
94 | } break; |
95 | } |
96 | } |
97 | |
98 | BoneMapperButton::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 | |
104 | BoneMapperButton::~BoneMapperButton() { |
105 | } |
106 | |
107 | void 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 | |
128 | void 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 | |
134 | void BoneMapperItem::_open_picker() { |
135 | emit_signal(SNAME("pick" ), profile_bone_name); |
136 | } |
137 | |
138 | void 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 | |
142 | void 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 | |
156 | void BoneMapperItem::_bind_methods() { |
157 | ADD_SIGNAL(MethodInfo("pick" , PropertyInfo(Variant::STRING_NAME, "profile_bone_name" ))); |
158 | } |
159 | |
160 | BoneMapperItem::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 | |
165 | BoneMapperItem::~BoneMapperItem() { |
166 | } |
167 | |
168 | void 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 | |
184 | void 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 | |
229 | void BonePicker::_confirm() { |
230 | _ok_pressed(); |
231 | } |
232 | |
233 | void BonePicker::(const Size2i &p_minsize) { |
234 | popup_centered(p_minsize); |
235 | } |
236 | |
237 | bool BonePicker::has_selected_bone() { |
238 | TreeItem *selected = bones->get_selected(); |
239 | if (!selected) { |
240 | return false; |
241 | } |
242 | return true; |
243 | } |
244 | |
245 | StringName 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 | |
253 | void BonePicker::_bind_methods() { |
254 | } |
255 | |
256 | void BonePicker::_notification(int p_what) { |
257 | switch (p_what) { |
258 | case NOTIFICATION_ENTER_TREE: { |
259 | create_editors(); |
260 | } break; |
261 | } |
262 | } |
263 | |
264 | BonePicker::BonePicker(Skeleton3D *p_skeleton) { |
265 | skeleton = p_skeleton; |
266 | } |
267 | |
268 | BonePicker::~BonePicker() { |
269 | } |
270 | |
271 | void 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 | |
330 | void 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 | |
350 | void 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 | |
355 | void 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 | |
362 | void BoneMapper::set_current_group_idx(int p_group_idx) { |
363 | current_group_idx = p_group_idx; |
364 | recreate_editor(); |
365 | } |
366 | |
367 | int BoneMapper::get_current_group_idx() const { |
368 | return current_group_idx; |
369 | } |
370 | |
371 | void BoneMapper::set_current_bone_idx(int p_bone_idx) { |
372 | current_bone_idx = p_bone_idx; |
373 | recreate_editor(); |
374 | } |
375 | |
376 | int BoneMapper::get_current_bone_idx() const { |
377 | return current_bone_idx; |
378 | } |
379 | |
380 | void 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 | |
439 | void 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 | |
450 | void 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 | |
468 | void 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 | |
520 | void 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 |
536 | int 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 | |
615 | BoneMapper::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 | |
640 | void BoneMapper::_run_auto_mapping() { |
641 | auto_mapping_process(bone_map); |
642 | recreate_items(); |
643 | } |
644 | |
645 | void 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 = 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 = 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 | |
1246 | void 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 | |
1251 | void 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 | |
1266 | void 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 | |
1275 | void 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 | |
1296 | BoneMapper::BoneMapper(Skeleton3D *p_skeleton, Ref<BoneMap> &p_bone_map) { |
1297 | skeleton = p_skeleton; |
1298 | bone_map = p_bone_map; |
1299 | } |
1300 | |
1301 | BoneMapper::~BoneMapper() { |
1302 | } |
1303 | |
1304 | void BoneMapEditor::create_editors() { |
1305 | if (!skeleton) { |
1306 | return; |
1307 | } |
1308 | bone_mapper = memnew(BoneMapper(skeleton, bone_map)); |
1309 | add_child(bone_mapper); |
1310 | } |
1311 | |
1312 | void 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 | |
1335 | void 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 | |
1347 | BoneMapEditor::BoneMapEditor(Ref<BoneMap> &p_bone_map) { |
1348 | bone_map = p_bone_map; |
1349 | } |
1350 | |
1351 | BoneMapEditor::~BoneMapEditor() { |
1352 | } |
1353 | |
1354 | bool EditorInspectorPluginBoneMap::can_handle(Object *p_object) { |
1355 | return Object::cast_to<BoneMap>(p_object) != nullptr; |
1356 | } |
1357 | |
1358 | void 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 | |
1368 | BoneMapEditorPlugin::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 | |