diff options
Diffstat (limited to 'editor')
23 files changed, 1308 insertions, 215 deletions
diff --git a/editor/animation_bezier_editor.cpp b/editor/animation_bezier_editor.cpp index 11e46152ef..9af8b907c4 100644 --- a/editor/animation_bezier_editor.cpp +++ b/editor/animation_bezier_editor.cpp @@ -41,7 +41,7 @@ float AnimationBezierTrackEdit::_bezier_h_to_pixel(float p_h) { float h = p_h; h = (h - v_scroll) / v_zoom; - h = (get_size().height / 2) - h; + h = (get_size().height / 2.0) - h; return h; } @@ -52,10 +52,10 @@ void AnimationBezierTrackEdit::_draw_track(int p_track, const Color &p_color) { int right_limit = get_size().width; //selection may have altered the order of keys - RBMap<float, int> key_order; + RBMap<real_t, int> key_order; for (int i = 0; i < animation->track_get_key_count(p_track); i++) { - float ofs = animation->track_get_key_time(p_track, i); + real_t ofs = animation->track_get_key_time(p_track, i); if (moving_selection && selection.has(IntPair(p_track, i))) { ofs += moving_selection_offset.x; } @@ -63,7 +63,7 @@ void AnimationBezierTrackEdit::_draw_track(int p_track, const Color &p_color) { key_order[ofs] = i; } - for (RBMap<float, int>::Element *E = key_order.front(); E; E = E->next()) { + for (RBMap<real_t, int>::Element *E = key_order.front(); E; E = E->next()) { int i = E->get(); if (!E->next()) { @@ -75,7 +75,7 @@ void AnimationBezierTrackEdit::_draw_track(int p_track, const Color &p_color) { float offset = animation->track_get_key_time(p_track, i); float height = animation->bezier_track_get_key_value(p_track, i); Vector2 out_handle = animation->bezier_track_get_key_out_handle(p_track, i); - if (p_track == moving_handle_track && moving_handle != 0 && moving_handle_key == i) { + if (p_track == moving_handle_track && (moving_handle == -1 || moving_handle == 1) && moving_handle_key == i) { out_handle = moving_handle_right; } @@ -89,7 +89,7 @@ void AnimationBezierTrackEdit::_draw_track(int p_track, const Color &p_color) { float offset_n = animation->track_get_key_time(p_track, i_n); float height_n = animation->bezier_track_get_key_value(p_track, i_n); Vector2 in_handle = animation->bezier_track_get_key_in_handle(p_track, i_n); - if (p_track == moving_handle_track && moving_handle != 0 && moving_handle_key == i_n) { + if (p_track == moving_handle_track && (moving_handle == -1 || moving_handle == 1) && moving_handle_key == i_n) { in_handle = moving_handle_left; } @@ -139,7 +139,7 @@ void AnimationBezierTrackEdit::_draw_track(int p_track, const Color &p_color) { //narrow high and low as much as possible for (int k = 0; k < iterations; k++) { - float middle = (low + high) / 2; + float middle = (low + high) / 2.0; Vector2 interp = start.bezier_interpolate(out_handle, in_handle, end, middle); @@ -316,7 +316,7 @@ void AnimationBezierTrackEdit::_notification(int p_what) { int h = MAX(text_buf.get_size().y, icon->get_height()); - draw_texture(icon, Point2(ofs, vofs + int(h - icon->get_height()) / 2)); + draw_texture(icon, Point2(ofs, vofs + int(h - icon->get_height()) / 2.0)); ofs += icon->get_width(); margin = icon->get_width(); @@ -403,29 +403,29 @@ void AnimationBezierTrackEdit::_notification(int p_what) { Vector2 string_pos = Point2(margin, vofs); text_buf.draw(get_canvas_item(), string_pos, cc); - float icon_start_height = vofs + rect.size.y / 2; - Rect2 remove_rect = Rect2(remove_hpos, icon_start_height - remove->get_height() / 2, remove->get_width(), remove->get_height()); + float icon_start_height = vofs + rect.size.y / 2.0; + Rect2 remove_rect = Rect2(remove_hpos, icon_start_height - remove->get_height() / 2.0, remove->get_width(), remove->get_height()); if (read_only) { draw_texture(remove, remove_rect.position, dc); } else { draw_texture(remove, remove_rect.position); } - Rect2 lock_rect = Rect2(lock_hpos, icon_start_height - lock->get_height() / 2, lock->get_width(), lock->get_height()); + Rect2 lock_rect = Rect2(lock_hpos, icon_start_height - lock->get_height() / 2.0, lock->get_width(), lock->get_height()); if (locked_tracks.has(current_track)) { draw_texture(lock, lock_rect.position); } else { draw_texture(unlock, lock_rect.position); } - Rect2 visible_rect = Rect2(visibility_hpos, icon_start_height - visible->get_height() / 2, visible->get_width(), visible->get_height()); + Rect2 visible_rect = Rect2(visibility_hpos, icon_start_height - visible->get_height() / 2.0, visible->get_width(), visible->get_height()); if (hidden_tracks.has(current_track)) { draw_texture(hidden, visible_rect.position); } else { draw_texture(visible, visible_rect.position); } - Rect2 solo_rect = Rect2(solo_hpos, icon_start_height - solo->get_height() / 2, solo->get_width(), solo->get_height()); + Rect2 solo_rect = Rect2(solo_hpos, icon_start_height - solo->get_height() / 2.0, solo->get_width(), solo->get_height()); draw_texture(solo, solo_rect.position); RBMap<int, Rect2> track_icons; @@ -456,7 +456,7 @@ void AnimationBezierTrackEdit::_notification(int p_what) { bool first = true; int prev_iv = 0; for (int i = font->get_height(font_size); i < get_size().height; i++) { - float ofs = get_size().height / 2 - i; + float ofs = get_size().height / 2.0 - i; ofs *= v_zoom; ofs += v_scroll; @@ -495,7 +495,7 @@ void AnimationBezierTrackEdit::_notification(int p_what) { Vector2 pos((offset - timeline->get_value()) * scale + limit, _bezier_h_to_pixel(value)); if (pos.x >= limit && pos.x <= right_limit) { - draw_texture(point, pos - point->get_size() / 2, E.value); + draw_texture(point, pos - point->get_size() / 2.0, E.value); } } } @@ -547,14 +547,15 @@ void AnimationBezierTrackEdit::_notification(int p_what) { Vector2 pos((offset - timeline->get_value()) * scale + limit, _bezier_h_to_pixel(value)); Vector2 in_vec = animation->bezier_track_get_key_in_handle(i, j); - if (moving_handle != 0 && moving_handle_track == i && moving_handle_key == j) { + + if ((moving_handle == 1 || moving_handle == -1) && moving_handle_track == i && moving_handle_key == j) { in_vec = moving_handle_left; } Vector2 pos_in(((offset + in_vec.x) - timeline->get_value()) * scale + limit, _bezier_h_to_pixel(value + in_vec.y)); Vector2 out_vec = animation->bezier_track_get_key_out_handle(i, j); - if (moving_handle != 0 && moving_handle_track == i && moving_handle_key == j) { + if ((moving_handle == 1 || moving_handle == -1) && moving_handle_track == i && moving_handle_key == j) { out_vec = moving_handle_right; } @@ -569,7 +570,7 @@ void AnimationBezierTrackEdit::_notification(int p_what) { ep.track = i; ep.key = j; if (pos.x >= limit && pos.x <= right_limit) { - ep.point_rect.position = (pos - bezier_icon->get_size() / 2).floor(); + ep.point_rect.position = (pos - bezier_icon->get_size() / 2.0).floor(); ep.point_rect.size = bezier_icon->get_size(); if (selection.has(IntPair(i, j))) { draw_texture(selected_icon, ep.point_rect.position); @@ -584,18 +585,22 @@ void AnimationBezierTrackEdit::_notification(int p_what) { } ep.point_rect = ep.point_rect.grow(ep.point_rect.size.width * 0.5); } + ep.point_rect = ep.point_rect.grow(ep.point_rect.size.width * 0.5); + if (i == selected_track || selection.has(IntPair(i, j))) { - if (pos_in.x >= limit && pos_in.x <= right_limit) { - ep.in_rect.position = (pos_in - bezier_handle_icon->get_size() / 2).floor(); - ep.in_rect.size = bezier_handle_icon->get_size(); - draw_texture(bezier_handle_icon, ep.in_rect.position); - ep.in_rect = ep.in_rect.grow(ep.in_rect.size.width * 0.5); - } - if (pos_out.x >= limit && pos_out.x <= right_limit) { - ep.out_rect.position = (pos_out - bezier_handle_icon->get_size() / 2).floor(); - ep.out_rect.size = bezier_handle_icon->get_size(); - draw_texture(bezier_handle_icon, ep.out_rect.position); - ep.out_rect = ep.out_rect.grow(ep.out_rect.size.width * 0.5); + if (animation->bezier_track_get_key_handle_mode(i, j) != Animation::HANDLE_MODE_LINEAR) { + if (pos_in.x >= limit && pos_in.x <= right_limit) { + ep.in_rect.position = (pos_in - bezier_handle_icon->get_size() / 2.0).floor(); + ep.in_rect.size = bezier_handle_icon->get_size(); + draw_texture(bezier_handle_icon, ep.in_rect.position); + ep.in_rect = ep.in_rect.grow(ep.in_rect.size.width * 0.5); + } + if (pos_out.x >= limit && pos_out.x <= right_limit) { + ep.out_rect.position = (pos_out - bezier_handle_icon->get_size() / 2.0).floor(); + ep.out_rect.size = bezier_handle_icon->get_size(); + draw_texture(bezier_handle_icon, ep.out_rect.position); + ep.out_rect = ep.out_rect.grow(ep.out_rect.size.width * 0.5); + } } } if (!locked_tracks.has(i)) { @@ -664,7 +669,6 @@ void AnimationBezierTrackEdit::set_editor(AnimationTrackEditor *p_editor) { editor = p_editor; connect("clear_selection", Callable(editor, "_clear_selection").bind(false)); connect("select_key", Callable(editor, "_key_selected"), CONNECT_DEFERRED); - connect("deselect_key", Callable(editor, "_key_deselected"), CONNECT_DEFERRED); } void AnimationBezierTrackEdit::_play_position_draw() { @@ -685,7 +689,7 @@ void AnimationBezierTrackEdit::_play_position_draw() { } } -void AnimationBezierTrackEdit::set_play_position(float p_pos) { +void AnimationBezierTrackEdit::set_play_position(real_t p_pos) { play_position_pos = p_pos; play_position->update(); } @@ -786,13 +790,14 @@ void AnimationBezierTrackEdit::_clear_selection() { update(); } -void AnimationBezierTrackEdit::_change_selected_keys_handle_mode(Animation::HandleMode p_mode) { +void AnimationBezierTrackEdit::_change_selected_keys_handle_mode(Animation::HandleMode p_mode, bool p_auto) { undo_redo->create_action(TTR("Update Selected Key Handles")); - double ratio = timeline->get_zoom_scale() * v_zoom; - for (const IntPair &E : selection) { - const IntPair track_key_pair = E; - undo_redo->add_undo_method(animation.ptr(), "bezier_track_set_key_handle_mode", track_key_pair.first, track_key_pair.second, animation->bezier_track_get_key_handle_mode(track_key_pair.first, track_key_pair.second), ratio); - undo_redo->add_do_method(animation.ptr(), "bezier_track_set_key_handle_mode", track_key_pair.first, track_key_pair.second, p_mode, ratio); + for (SelectionSet::Element *E = selection.back(); E; E = E->prev()) { + const IntPair track_key_pair = E->get(); + undo_redo->add_undo_method(editor, "_bezier_track_set_key_handle_mode", animation.ptr(), track_key_pair.first, track_key_pair.second, animation->bezier_track_get_key_handle_mode(track_key_pair.first, track_key_pair.second), Animation::HANDLE_SET_MODE_NONE); + undo_redo->add_undo_method(animation.ptr(), "bezier_track_set_key_in_handle", track_key_pair.first, track_key_pair.second, animation->bezier_track_get_key_in_handle(track_key_pair.first, track_key_pair.second)); + undo_redo->add_undo_method(animation.ptr(), "bezier_track_set_key_out_handle", track_key_pair.first, track_key_pair.second, animation->bezier_track_get_key_out_handle(track_key_pair.first, track_key_pair.second)); + undo_redo->add_do_method(editor, "_bezier_track_set_key_handle_mode", animation.ptr(), track_key_pair.first, track_key_pair.second, p_mode, p_auto ? Animation::HANDLE_SET_MODE_AUTO : Animation::HANDLE_SET_MODE_RESET); } undo_redo->commit_action(); } @@ -804,7 +809,7 @@ void AnimationBezierTrackEdit::_clear_selection_for_anim(const Ref<Animation> &p _clear_selection(); } -void AnimationBezierTrackEdit::_select_at_anim(const Ref<Animation> &p_anim, int p_track, float p_pos) { +void AnimationBezierTrackEdit::_select_at_anim(const Ref<Animation> &p_anim, int p_track, real_t p_pos) { if (!(animation == p_anim)) { return; } @@ -813,7 +818,7 @@ void AnimationBezierTrackEdit::_select_at_anim(const Ref<Animation> &p_anim, int ERR_FAIL_COND(idx < 0); selection.insert(IntPair(p_track, idx)); - emit_signal(SNAME("select_key"), p_track, idx, true); + emit_signal(SNAME("select_key"), idx, true, p_track); update(); } @@ -869,16 +874,16 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) { return; } - float minimum_time = INFINITY; - float maximum_time = -INFINITY; - float minimum_value = INFINITY; - float maximum_value = -INFINITY; + real_t minimum_time = INFINITY; + real_t maximum_time = -INFINITY; + real_t minimum_value = INFINITY; + real_t maximum_value = -INFINITY; for (const IntPair &E : selection) { IntPair key_pair = E; - float time = animation->track_get_key_time(key_pair.first, key_pair.second); - float value = animation->bezier_track_get_key_value(key_pair.first, key_pair.second); + real_t time = animation->track_get_key_time(key_pair.first, key_pair.second); + real_t value = animation->bezier_track_get_key_value(key_pair.first, key_pair.second); minimum_time = MIN(time, minimum_time); maximum_time = MAX(time, maximum_time); @@ -888,8 +893,8 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) { float width = get_size().width - timeline->get_name_limit() - timeline->get_buttons_width(); float padding = width * 0.1; - float desired_scale = (width - padding / 2) / (maximum_time - minimum_time); - minimum_time = MAX(0, minimum_time - (padding / 2) / desired_scale); + float desired_scale = (width - padding / 2.0) / (maximum_time - minimum_time); + minimum_time = MAX(0, minimum_time - (padding / 2.0) / desired_scale); float zv = Math::pow(100 / desired_scale, 0.125f); if (zv < 1) { @@ -943,7 +948,12 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) { menu->add_icon_item(get_theme_icon(SNAME("Remove"), SNAME("EditorIcons")), TTR("Delete Selected Key(s)"), MENU_KEY_DELETE); menu->add_separator(); menu->add_icon_item(get_theme_icon(SNAME("BezierHandlesFree"), SNAME("EditorIcons")), TTR("Make Handles Free"), MENU_KEY_SET_HANDLE_FREE); + menu->add_icon_item(get_theme_icon(SNAME("BezierHandlesLinear"), SNAME("EditorIcons")), TTR("Make Handles Linear"), MENU_KEY_SET_HANDLE_LINEAR); menu->add_icon_item(get_theme_icon(SNAME("BezierHandlesBalanced"), SNAME("EditorIcons")), TTR("Make Handles Balanced"), MENU_KEY_SET_HANDLE_BALANCED); + menu->add_icon_item(get_theme_icon(SNAME("BezierHandlesMirror"), SNAME("EditorIcons")), TTR("Make Handles Mirrored"), MENU_KEY_SET_HANDLE_MIRRORED); + menu->add_separator(); + menu->add_icon_item(get_theme_icon(SNAME("BezierHandlesBalanced"), SNAME("EditorIcons")), TTR("Make Handles Balanced (Auto Tangent)"), MENU_KEY_SET_HANDLE_AUTO_BALANCED); + menu->add_icon_item(get_theme_icon(SNAME("BezierHandlesMirror"), SNAME("EditorIcons")), TTR("Make Handles Mirrored (Auto Tangent)"), MENU_KEY_SET_HANDLE_AUTO_MIRRORED); } if (menu->get_item_count()) { @@ -985,9 +995,10 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) { for (int i = 0; i < animation->track_get_key_count(track); ++i) { undo_redo->add_undo_method( - animation.ptr(), - "bezier_track_insert_key", - track, animation->track_get_key_time(track, i), + this, + "_bezier_track_insert_key", + track, + animation->track_get_key_time(track, i), animation->bezier_track_get_key_value(track, i), animation->bezier_track_get_key_in_handle(track, i), animation->bezier_track_get_key_out_handle(track, i), @@ -1094,6 +1105,9 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) { moving_selection = false; moving_selection_from_key = pair.second; moving_selection_from_track = pair.first; + moving_handle_track = pair.first; + moving_handle_left = animation->bezier_track_get_key_in_handle(pair.first, pair.second); + moving_handle_right = animation->bezier_track_get_key_out_handle(pair.first, pair.second); moving_selection_offset = Vector2(); select_single_attempt = pair; update(); @@ -1103,10 +1117,12 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) { moving_selection_from_key = pair.second; moving_selection_from_track = pair.first; moving_selection_offset = Vector2(); - set_animation_and_track(animation, pair.first, read_only); + moving_handle_track = pair.first; + moving_handle_left = animation->bezier_track_get_key_in_handle(pair.first, pair.second); + moving_handle_right = animation->bezier_track_get_key_out_handle(pair.first, pair.second); selection.clear(); selection.insert(pair); - update(); + set_animation_and_track(animation, pair.first, read_only); } return; } @@ -1138,24 +1154,23 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) { //insert new point if (mb->get_position().x >= limit && mb->get_position().x < get_size().width && mb->is_command_pressed()) { Array new_point; - new_point.resize(6); + new_point.resize(5); - float h = (get_size().height / 2 - mb->get_position().y) * v_zoom + v_scroll; + float h = (get_size().height / 2.0 - mb->get_position().y) * v_zoom + v_scroll; new_point[0] = h; new_point[1] = -0.25; new_point[2] = 0; new_point[3] = 0.25; new_point[4] = 0; - new_point[5] = 0; - float time = ((mb->get_position().x - limit) / timeline->get_zoom_scale()) + timeline->get_value(); + real_t time = ((mb->get_position().x - limit) / timeline->get_zoom_scale()) + timeline->get_value(); while (animation->track_find_key(selected_track, time, true) != -1) { time += 0.001; } undo_redo->create_action(TTR("Add Bezier Point")); - undo_redo->add_do_method(animation.ptr(), "track_insert_key", selected_track, time, new_point); + undo_redo->add_do_method(animation.ptr(), "bezier_track_insert_key", selected_track, time, new_point); undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", selected_track, time); undo_redo->commit_action(); @@ -1219,10 +1234,10 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) { //select by clicking on curve int track_count = animation->get_track_count(); - float animation_length = animation->get_length(); + real_t animation_length = animation->get_length(); animation->set_length(real_t(INT_MAX)); //bezier_track_interpolate doesn't find keys if they exist beyond anim length - float time = ((mb->get_position().x - limit) / timeline->get_zoom_scale()) + timeline->get_value(); + real_t time = ((mb->get_position().x - limit) / timeline->get_zoom_scale()) + timeline->get_value(); for (int i = 0; i < track_count; ++i) { if (animation->track_get_type(i) != Animation::TrackType::TYPE_BEZIER || hidden_tracks.has(i) || locked_tracks.has(i)) { @@ -1246,20 +1261,6 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) { update(); } - if (moving_handle != 0 && mb.is_valid() && !mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT) { - if (!read_only) { - undo_redo->create_action(TTR("Move Bezier Points")); - undo_redo->add_do_method(animation.ptr(), "bezier_track_set_key_in_handle", selected_track, moving_handle_key, moving_handle_left); - undo_redo->add_do_method(animation.ptr(), "bezier_track_set_key_out_handle", selected_track, moving_handle_key, moving_handle_right); - undo_redo->add_undo_method(animation.ptr(), "bezier_track_set_key_in_handle", selected_track, moving_handle_key, animation->bezier_track_get_key_in_handle(selected_track, moving_handle_key)); - undo_redo->add_undo_method(animation.ptr(), "bezier_track_set_key_out_handle", selected_track, moving_handle_key, animation->bezier_track_get_key_out_handle(selected_track, moving_handle_key)); - undo_redo->commit_action(); - - moving_handle = 0; - update(); - } - } - if (moving_selection_attempt && mb.is_valid() && !mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT) { if (!read_only) { if (moving_selection) { @@ -1268,13 +1269,14 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) { undo_redo->create_action(TTR("Move Bezier Points")); List<AnimMoveRestore> to_restore; + List<Animation::HandleMode> to_restore_handle_modes; // 1-remove the keys for (SelectionSet::Element *E = selection.back(); E; E = E->prev()) { undo_redo->add_do_method(animation.ptr(), "track_remove_key", E->get().first, E->get().second); } // 2- remove overlapped keys for (SelectionSet::Element *E = selection.back(); E; E = E->prev()) { - float newtime = editor->snap_time(animation->track_get_key_time(E->get().first, E->get().second) + moving_selection_offset.x); + real_t newtime = editor->snap_time(animation->track_get_key_time(E->get().first, E->get().second) + moving_selection_offset.x); int idx = animation->track_find_key(E->get().first, newtime, true); if (idx == -1) { @@ -1293,33 +1295,62 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) { amr.time = newtime; to_restore.push_back(amr); + to_restore_handle_modes.push_back(animation->bezier_track_get_key_handle_mode(E->get().first, idx)); } // 3-move the keys (re insert them) for (SelectionSet::Element *E = selection.back(); E; E = E->prev()) { - float newpos = editor->snap_time(animation->track_get_key_time(E->get().first, E->get().second) + moving_selection_offset.x); + real_t newpos = editor->snap_time(animation->track_get_key_time(E->get().first, E->get().second) + moving_selection_offset.x); Array key = animation->track_get_key_value(E->get().first, E->get().second); - float h = key[0]; + real_t h = key[0]; h += moving_selection_offset.y; key[0] = h; - undo_redo->add_do_method(animation.ptr(), "track_insert_key", E->get().first, newpos, key, 1); + undo_redo->add_do_method( + this, + "_bezier_track_insert_key", + E->get().first, + newpos, + key[0], + Vector2(key[1], key[2]), + Vector2(key[3], key[4]), + animation->bezier_track_get_key_handle_mode(E->get().first, E->get().second)); } // 4-(undo) remove inserted keys for (SelectionSet::Element *E = selection.back(); E; E = E->prev()) { - float newpos = editor->snap_time(animation->track_get_key_time(E->get().first, E->get().second) + moving_selection_offset.x); + real_t newpos = editor->snap_time(animation->track_get_key_time(E->get().first, E->get().second) + moving_selection_offset.x); undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", E->get().first, newpos); } // 5-(undo) reinsert keys for (SelectionSet::Element *E = selection.back(); E; E = E->prev()) { - float oldpos = animation->track_get_key_time(E->get().first, E->get().second); - undo_redo->add_undo_method(animation.ptr(), "track_insert_key", E->get().first, oldpos, animation->track_get_key_value(E->get().first, E->get().second), 1); + real_t oldpos = animation->track_get_key_time(E->get().first, E->get().second); + Array key = animation->track_get_key_value(E->get().first, E->get().second); + undo_redo->add_undo_method( + this, + "_bezier_track_insert_key", + E->get().first, + oldpos, + key[0], + Vector2(key[1], key[2]), + Vector2(key[3], key[4]), + animation->bezier_track_get_key_handle_mode(E->get().first, E->get().second)); } // 6-(undo) reinsert overlapped keys - for (const AnimMoveRestore &amr : to_restore) { + for (int i = 0; i < to_restore.size(); i++) { + const AnimMoveRestore &amr = to_restore[i]; + Array key = amr.key; undo_redo->add_undo_method(animation.ptr(), "track_insert_key", amr.track, amr.time, amr.key, 1); + undo_redo->add_undo_method( + this, + "_bezier_track_insert_key", + amr.track, + amr.time, + key[0], + Vector2(key[1], key[2]), + Vector2(key[3], key[4]), + to_restore_handle_modes[i]); } undo_redo->add_do_method(this, "_clear_selection_for_anim", animation); @@ -1328,8 +1359,8 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) { // 7-reselect for (SelectionSet::Element *E = selection.back(); E; E = E->prev()) { - float oldpos = animation->track_get_key_time(E->get().first, E->get().second); - float newpos = editor->snap_time(oldpos + moving_selection_offset.x); + real_t oldpos = animation->track_get_key_time(E->get().first, E->get().second); + real_t newpos = editor->snap_time(oldpos + moving_selection_offset.x); undo_redo->add_do_method(this, "_select_at_anim", animation, E->get().first, newpos); undo_redo->add_undo_method(this, "_select_at_anim", animation, E->get().first, oldpos); @@ -1356,12 +1387,16 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) { select_single_attempt = IntPair(-1, -1); } - float y = (get_size().height / 2 - mm->get_position().y) * v_zoom + v_scroll; + float y = (get_size().height / 2.0 - mm->get_position().y) * v_zoom + v_scroll; float x = editor->snap_time(((mm->get_position().x - limit) / timeline->get_zoom_scale()) + timeline->get_value()); if (!read_only) { moving_selection_offset = Vector2(x - animation->track_get_key_time(moving_selection_from_track, moving_selection_from_key), y - animation->bezier_track_get_key_value(moving_selection_from_track, moving_selection_from_key)); } + + additional_moving_handle_lefts.clear(); + additional_moving_handle_rights.clear(); + update(); } @@ -1380,8 +1415,8 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) { update(); } - if (moving_handle != 0 && mm.is_valid()) { - float y = (get_size().height / 2 - mm->get_position().y) * v_zoom + v_scroll; + if ((moving_handle == 1 || moving_handle == -1) && mm.is_valid()) { + float y = (get_size().height / 2.0 - mm->get_position().y) * v_zoom + v_scroll; float x = editor->snap_time((mm->get_position().x - timeline->get_name_limit()) / timeline->get_zoom_scale()) + timeline->get_value(); Vector2 key_pos = Vector2(animation->track_get_key_time(selected_track, moving_handle_key), animation->bezier_track_get_key_value(selected_track, moving_handle_key)); @@ -1394,8 +1429,10 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) { if (moving_handle == -1) { moving_handle_left = moving_handle_value; - if (animation->bezier_track_get_key_handle_mode(moving_handle_track, moving_handle_key) == Animation::HANDLE_MODE_BALANCED) { - double ratio = timeline->get_zoom_scale() * v_zoom; + Animation::HandleMode handle_mode = animation->bezier_track_get_key_handle_mode(moving_handle_track, moving_handle_key); + + if (handle_mode == Animation::HANDLE_MODE_BALANCED) { + real_t ratio = timeline->get_zoom_scale() * v_zoom; Transform2D xform; xform.set_scale(Vector2(1.0, 1.0 / ratio)); @@ -1403,12 +1440,16 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) { Vector2 vec_in = xform.xform(moving_handle_left); moving_handle_right = xform.affine_inverse().xform(-vec_in.normalized() * vec_out.length()); + } else if (handle_mode == Animation::HANDLE_MODE_MIRRORED) { + moving_handle_right = -moving_handle_left; } } else if (moving_handle == 1) { moving_handle_right = moving_handle_value; - if (animation->bezier_track_get_key_handle_mode(moving_handle_track, moving_handle_key) == Animation::HANDLE_MODE_BALANCED) { - double ratio = timeline->get_zoom_scale() * v_zoom; + Animation::HandleMode handle_mode = animation->bezier_track_get_key_handle_mode(moving_handle_track, moving_handle_key); + + if (handle_mode == Animation::HANDLE_MODE_BALANCED) { + real_t ratio = timeline->get_zoom_scale() * v_zoom; Transform2D xform; xform.set_scale(Vector2(1.0, 1.0 / ratio)); @@ -1416,26 +1457,26 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) { Vector2 vec_out = xform.xform(moving_handle_right); moving_handle_left = xform.affine_inverse().xform(-vec_out.normalized() * vec_in.length()); + } else if (handle_mode == Animation::HANDLE_MODE_MIRRORED) { + moving_handle_left = -moving_handle_right; } } update(); } - bool is_finishing_key_handle_drag = moving_handle != 0 && mb.is_valid() && !mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT; - if (is_finishing_key_handle_drag) { + if ((moving_handle == -1 || moving_handle == 1) && mb.is_valid() && !mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT) { if (!read_only) { undo_redo->create_action(TTR("Move Bezier Points")); if (moving_handle == -1) { - double ratio = timeline->get_zoom_scale() * v_zoom; + real_t ratio = timeline->get_zoom_scale() * v_zoom; undo_redo->add_do_method(animation.ptr(), "bezier_track_set_key_in_handle", moving_handle_track, moving_handle_key, moving_handle_left, ratio); undo_redo->add_undo_method(animation.ptr(), "bezier_track_set_key_in_handle", moving_handle_track, moving_handle_key, animation->bezier_track_get_key_in_handle(moving_handle_track, moving_handle_key), ratio); } else if (moving_handle == 1) { - double ratio = timeline->get_zoom_scale() * v_zoom; + real_t ratio = timeline->get_zoom_scale() * v_zoom; undo_redo->add_do_method(animation.ptr(), "bezier_track_set_key_out_handle", moving_handle_track, moving_handle_key, moving_handle_right, ratio); undo_redo->add_undo_method(animation.ptr(), "bezier_track_set_key_out_handle", moving_handle_track, moving_handle_key, animation->bezier_track_get_key_out_handle(moving_handle_track, moving_handle_key), ratio); } undo_redo->commit_action(); - moving_handle = 0; update(); } @@ -1469,7 +1510,7 @@ void AnimationBezierTrackEdit::_zoom_callback(Vector2 p_scroll_vec, Vector2 p_or timeline->get_zoom()->set_value(timeline->get_zoom()->get_value() * 1.05); } } - v_scroll = v_scroll + (p_origin.y - get_size().y / 2) * (v_zoom - v_zoom_orig); + v_scroll = v_scroll + (p_origin.y - get_size().y / 2.0) * (v_zoom - v_zoom_orig); update(); } @@ -1478,20 +1519,19 @@ void AnimationBezierTrackEdit::_menu_selected(int p_index) { case MENU_KEY_INSERT: { if (animation->get_track_count() > 0) { Array new_point; - new_point.resize(6); + new_point.resize(5); - float h = (get_size().height / 2 - menu_insert_key.y) * v_zoom + v_scroll; + float h = (get_size().height / 2.0 - menu_insert_key.y) * v_zoom + v_scroll; new_point[0] = h; new_point[1] = -0.25; new_point[2] = 0; new_point[3] = 0.25; new_point[4] = 0; - new_point[5] = Animation::HANDLE_MODE_BALANCED; int limit = timeline->get_name_limit(); - float time = ((menu_insert_key.x - limit) / timeline->get_zoom_scale()) + timeline->get_value(); + real_t time = ((menu_insert_key.x - limit) / timeline->get_zoom_scale()) + timeline->get_value(); while (animation->track_find_key(selected_track, time, true) != -1) { time += 0.001; @@ -1501,8 +1541,8 @@ void AnimationBezierTrackEdit::_menu_selected(int p_index) { undo_redo->add_do_method(animation.ptr(), "track_insert_key", selected_track, time, new_point); undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", selected_track, time); undo_redo->commit_action(); + update(); } - } break; case MENU_KEY_DUPLICATE: { duplicate_selection(); @@ -1513,9 +1553,21 @@ void AnimationBezierTrackEdit::_menu_selected(int p_index) { case MENU_KEY_SET_HANDLE_FREE: { _change_selected_keys_handle_mode(Animation::HANDLE_MODE_FREE); } break; + case MENU_KEY_SET_HANDLE_LINEAR: { + _change_selected_keys_handle_mode(Animation::HANDLE_MODE_LINEAR); + } break; case MENU_KEY_SET_HANDLE_BALANCED: { _change_selected_keys_handle_mode(Animation::HANDLE_MODE_BALANCED); } break; + case MENU_KEY_SET_HANDLE_MIRRORED: { + _change_selected_keys_handle_mode(Animation::HANDLE_MODE_MIRRORED); + } break; + case MENU_KEY_SET_HANDLE_AUTO_BALANCED: { + _change_selected_keys_handle_mode(Animation::HANDLE_MODE_BALANCED, true); + } break; + case MENU_KEY_SET_HANDLE_AUTO_MIRRORED: { + _change_selected_keys_handle_mode(Animation::HANDLE_MODE_MIRRORED, true); + } break; } } @@ -1524,9 +1576,9 @@ void AnimationBezierTrackEdit::duplicate_selection() { return; } - float top_time = 1e10; + real_t top_time = 1e10; for (SelectionSet::Element *E = selection.back(); E; E = E->prev()) { - float t = animation->track_get_key_time(E->get().first, E->get().second); + real_t t = animation->track_get_key_time(E->get().first, E->get().second); if (t < top_time) { top_time = t; } @@ -1534,17 +1586,17 @@ void AnimationBezierTrackEdit::duplicate_selection() { undo_redo->create_action(TTR("Anim Duplicate Keys")); - List<Pair<int, float>> new_selection_values; + List<Pair<int, real_t>> new_selection_values; for (SelectionSet::Element *E = selection.back(); E; E = E->prev()) { - float t = animation->track_get_key_time(E->get().first, E->get().second); - float dst_time = t + (timeline->get_play_position() - top_time); + real_t t = animation->track_get_key_time(E->get().first, E->get().second); + real_t dst_time = t + (timeline->get_play_position() - top_time); int existing_idx = animation->track_find_key(E->get().first, dst_time, true); undo_redo->add_do_method(animation.ptr(), "track_insert_key", E->get().first, dst_time, animation->track_get_key_value(E->get().first, E->get().second), animation->track_get_key_transition(E->get().first, E->get().second)); undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", E->get().first, dst_time); - Pair<int, float> p; + Pair<int, real_t> p; p.first = E->get().first; p.second = dst_time; new_selection_values.push_back(p); @@ -1559,9 +1611,9 @@ void AnimationBezierTrackEdit::duplicate_selection() { //reselect duplicated selection.clear(); - for (const Pair<int, float> &E : new_selection_values) { + for (const Pair<int, real_t> &E : new_selection_values) { int track = E.first; - float time = E.second; + real_t time = E.second; int existing_idx = animation->track_find_key(track, time, true); @@ -1591,18 +1643,24 @@ void AnimationBezierTrackEdit::delete_selection() { } } +void AnimationBezierTrackEdit::_bezier_track_insert_key(int p_track, double p_time, real_t p_value, const Vector2 &p_in_handle, const Vector2 &p_out_handle, const Animation::HandleMode p_handle_mode) { + ERR_FAIL_COND(animation.is_null()); + int idx = animation->bezier_track_insert_key(p_track, p_time, p_value, p_in_handle, p_out_handle); + animation->bezier_track_set_key_handle_mode(p_track, idx, p_handle_mode); +} + void AnimationBezierTrackEdit::_bind_methods() { - ClassDB::bind_method("_clear_selection", &AnimationBezierTrackEdit::_clear_selection); - ClassDB::bind_method("_clear_selection_for_anim", &AnimationBezierTrackEdit::_clear_selection_for_anim); - ClassDB::bind_method("_select_at_anim", &AnimationBezierTrackEdit::_select_at_anim); - ClassDB::bind_method("_update_hidden_tracks_after", &AnimationBezierTrackEdit::_update_hidden_tracks_after); - ClassDB::bind_method("_update_locked_tracks_after", &AnimationBezierTrackEdit::_update_locked_tracks_after); + ClassDB::bind_method(D_METHOD("_clear_selection"), &AnimationBezierTrackEdit::_clear_selection); + ClassDB::bind_method(D_METHOD("_clear_selection_for_anim"), &AnimationBezierTrackEdit::_clear_selection_for_anim); + ClassDB::bind_method(D_METHOD("_select_at_anim"), &AnimationBezierTrackEdit::_select_at_anim); + ClassDB::bind_method(D_METHOD("_update_hidden_tracks_after"), &AnimationBezierTrackEdit::_update_hidden_tracks_after); + ClassDB::bind_method(D_METHOD("_update_locked_tracks_after"), &AnimationBezierTrackEdit::_update_locked_tracks_after); + ClassDB::bind_method(D_METHOD("_bezier_track_insert_key"), &AnimationBezierTrackEdit::_bezier_track_insert_key); ADD_SIGNAL(MethodInfo("timeline_changed", PropertyInfo(Variant::FLOAT, "position"), PropertyInfo(Variant::BOOL, "drag"))); ADD_SIGNAL(MethodInfo("remove_request", PropertyInfo(Variant::INT, "track"))); ADD_SIGNAL(MethodInfo("insert_key", PropertyInfo(Variant::FLOAT, "offset"))); - ADD_SIGNAL(MethodInfo("select_key", PropertyInfo(Variant::INT, "track"), PropertyInfo(Variant::INT, "index"), PropertyInfo(Variant::BOOL, "single"))); - ADD_SIGNAL(MethodInfo("deselect_key", PropertyInfo(Variant::INT, "track"), PropertyInfo(Variant::INT, "index"))); + ADD_SIGNAL(MethodInfo("select_key", PropertyInfo(Variant::INT, "index"), PropertyInfo(Variant::BOOL, "single"), PropertyInfo(Variant::INT, "track"))); ADD_SIGNAL(MethodInfo("clear_selection")); ADD_SIGNAL(MethodInfo("close_request")); diff --git a/editor/animation_bezier_editor.h b/editor/animation_bezier_editor.h index 3e94b4fa84..beb7a5e9c6 100644 --- a/editor/animation_bezier_editor.h +++ b/editor/animation_bezier_editor.h @@ -32,7 +32,7 @@ #define ANIMATION_BEZIER_EDITOR_H #include "animation_track_editor.h" -#include "core/templates/rb_set.h" +#include "core/templates/hashfuncs.h" class EditorUndoRedoManager; class ViewPanner; @@ -45,14 +45,18 @@ class AnimationBezierTrackEdit : public Control { MENU_KEY_DUPLICATE, MENU_KEY_DELETE, MENU_KEY_SET_HANDLE_FREE, + MENU_KEY_SET_HANDLE_LINEAR, MENU_KEY_SET_HANDLE_BALANCED, + MENU_KEY_SET_HANDLE_MIRRORED, + MENU_KEY_SET_HANDLE_AUTO_BALANCED, + MENU_KEY_SET_HANDLE_AUTO_MIRRORED, }; AnimationTimelineEdit *timeline = nullptr; Ref<EditorUndoRedoManager> undo_redo; Node *root = nullptr; Control *play_position = nullptr; //separate control used to draw so updates for only position changed are much faster - float play_position_pos = 0; + real_t play_position_pos = 0; Ref<Animation> animation; bool read_only = false; @@ -112,25 +116,37 @@ class AnimationBezierTrackEdit : public Control { Vector2 box_selection_from; Vector2 box_selection_to; - int moving_handle = 0; //0 no move -1 or +1 out + int moving_handle = 0; //0 no move -1 or +1 out, 2 both (drawing only) int moving_handle_key = 0; int moving_handle_track = 0; Vector2 moving_handle_left; Vector2 moving_handle_right; int moving_handle_mode = 0; // value from Animation::HandleMode + struct PairHasher { + static _FORCE_INLINE_ uint32_t hash(const Pair<int, int> &p_value) { + int32_t hash = 23; + hash = hash * 31 * hash_one_uint64(p_value.first); + hash = hash * 31 * hash_one_uint64(p_value.second); + return hash; + } + }; + + HashMap<Pair<int, int>, Vector2, PairHasher> additional_moving_handle_lefts; + HashMap<Pair<int, int>, Vector2, PairHasher> additional_moving_handle_rights; + void _clear_selection(); void _clear_selection_for_anim(const Ref<Animation> &p_anim); - void _select_at_anim(const Ref<Animation> &p_anim, int p_track, float p_pos); - void _change_selected_keys_handle_mode(Animation::HandleMode p_mode); + void _select_at_anim(const Ref<Animation> &p_anim, int p_track, real_t p_pos); + void _change_selected_keys_handle_mode(Animation::HandleMode p_mode, bool p_auto = false); Vector2 menu_insert_key; struct AnimMoveRestore { int track = 0; - float time = 0; + double time = 0; Variant key; - float transition = 0; + real_t transition = 0; }; AnimationTrackEditor *editor = nullptr; @@ -145,7 +161,7 @@ class AnimationBezierTrackEdit : public Control { Vector<EditPoint> edit_points; - struct SelectionCompare { + struct PairCompare { bool operator()(const IntPair &lh, const IntPair &rh) { if (lh.first == rh.first) { return lh.second < rh.second; @@ -155,7 +171,7 @@ class AnimationBezierTrackEdit : public Control { } }; - typedef RBSet<IntPair, SelectionCompare> SelectionSet; + typedef RBSet<IntPair, PairCompare> SelectionSet; SelectionSet selection; @@ -187,12 +203,14 @@ public: void set_root(Node *p_root); void set_filtered(bool p_filtered); - void set_play_position(float p_pos); + void set_play_position(real_t p_pos); void update_play_position(); void duplicate_selection(); void delete_selection(); + void _bezier_track_insert_key(int p_track, double p_time, real_t p_value, const Vector2 &p_in_handle, const Vector2 &p_out_handle, const Animation::HandleMode p_handle_mode); + AnimationBezierTrackEdit(); }; diff --git a/editor/animation_track_editor.cpp b/editor/animation_track_editor.cpp index d95fe64a09..7fa7b45f85 100644 --- a/editor/animation_track_editor.cpp +++ b/editor/animation_track_editor.cpp @@ -65,12 +65,12 @@ public: } static void _bind_methods() { - ClassDB::bind_method("_update_obj", &AnimationTrackKeyEdit::_update_obj); - ClassDB::bind_method("_key_ofs_changed", &AnimationTrackKeyEdit::_key_ofs_changed); - ClassDB::bind_method("_hide_script_from_inspector", &AnimationTrackKeyEdit::_hide_script_from_inspector); - ClassDB::bind_method("get_root_path", &AnimationTrackKeyEdit::get_root_path); - ClassDB::bind_method("_dont_undo_redo", &AnimationTrackKeyEdit::_dont_undo_redo); - ClassDB::bind_method("_read_only", &AnimationTrackKeyEdit::_read_only); + ClassDB::bind_method(D_METHOD("_update_obj"), &AnimationTrackKeyEdit::_update_obj); + ClassDB::bind_method(D_METHOD("_key_ofs_changed"), &AnimationTrackKeyEdit::_key_ofs_changed); + ClassDB::bind_method(D_METHOD("_hide_script_from_inspector"), &AnimationTrackKeyEdit::_hide_script_from_inspector); + ClassDB::bind_method(D_METHOD("get_root_path"), &AnimationTrackKeyEdit::get_root_path); + ClassDB::bind_method(D_METHOD("_dont_undo_redo"), &AnimationTrackKeyEdit::_dont_undo_redo); + ClassDB::bind_method(D_METHOD("_read_only"), &AnimationTrackKeyEdit::_read_only); } void _fix_node_path(Variant &value) { @@ -351,8 +351,8 @@ public: setting = true; undo_redo->create_action(TTR("Anim Change Keyframe Value"), UndoRedo::MERGE_ENDS); int prev = animation->bezier_track_get_key_handle_mode(track, key); - undo_redo->add_do_method(animation.ptr(), "bezier_track_set_key_handle_mode", track, key, value); - undo_redo->add_undo_method(animation.ptr(), "bezier_track_set_key_handle_mode", track, key, prev); + undo_redo->add_do_method(this, "_bezier_track_set_key_handle_mode", animation.ptr(), track, key, value); + undo_redo->add_undo_method(this, "_bezier_track_set_key_handle_mode", animation.ptr(), track, key, prev); undo_redo->add_do_method(this, "_update_obj", animation); undo_redo->add_undo_method(this, "_update_obj", animation); undo_redo->commit_action(); @@ -637,10 +637,16 @@ public: } break; case Animation::TYPE_BEZIER: { + Animation::HandleMode hm = animation->bezier_track_get_key_handle_mode(track, key); p_list->push_back(PropertyInfo(Variant::FLOAT, PNAME("value"))); - p_list->push_back(PropertyInfo(Variant::VECTOR2, PNAME("in_handle"))); - p_list->push_back(PropertyInfo(Variant::VECTOR2, PNAME("out_handle"))); - p_list->push_back(PropertyInfo(Variant::INT, PNAME("handle_mode"), PROPERTY_HINT_ENUM, "Free,Balanced")); + if (hm == Animation::HANDLE_MODE_LINEAR) { + p_list->push_back(PropertyInfo(Variant::VECTOR2, PNAME("in_handle"), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_READ_ONLY)); + p_list->push_back(PropertyInfo(Variant::VECTOR2, PNAME("out_handle"), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_READ_ONLY)); + } else { + p_list->push_back(PropertyInfo(Variant::VECTOR2, PNAME("in_handle"))); + p_list->push_back(PropertyInfo(Variant::VECTOR2, PNAME("out_handle"))); + } + p_list->push_back(PropertyInfo(Variant::INT, PNAME("handle_mode"), PROPERTY_HINT_ENUM, "Free,Linear,Balanced,Mirrored")); } break; case Animation::TYPE_AUDIO: { @@ -726,12 +732,12 @@ public: } static void _bind_methods() { - ClassDB::bind_method("_update_obj", &AnimationMultiTrackKeyEdit::_update_obj); - ClassDB::bind_method("_key_ofs_changed", &AnimationMultiTrackKeyEdit::_key_ofs_changed); - ClassDB::bind_method("_hide_script_from_inspector", &AnimationMultiTrackKeyEdit::_hide_script_from_inspector); - ClassDB::bind_method("get_root_path", &AnimationMultiTrackKeyEdit::get_root_path); - ClassDB::bind_method("_dont_undo_redo", &AnimationMultiTrackKeyEdit::_dont_undo_redo); - ClassDB::bind_method("_read_only", &AnimationMultiTrackKeyEdit::_read_only); + ClassDB::bind_method(D_METHOD("_update_obj"), &AnimationMultiTrackKeyEdit::_update_obj); + ClassDB::bind_method(D_METHOD("_key_ofs_changed"), &AnimationMultiTrackKeyEdit::_key_ofs_changed); + ClassDB::bind_method(D_METHOD("_hide_script_from_inspector"), &AnimationMultiTrackKeyEdit::_hide_script_from_inspector); + ClassDB::bind_method(D_METHOD("get_root_path"), &AnimationMultiTrackKeyEdit::get_root_path); + ClassDB::bind_method(D_METHOD("_dont_undo_redo"), &AnimationMultiTrackKeyEdit::_dont_undo_redo); + ClassDB::bind_method(D_METHOD("_read_only"), &AnimationMultiTrackKeyEdit::_read_only); } void _fix_node_path(Variant &value, NodePath &base) { @@ -972,8 +978,8 @@ public: undo_redo->create_action(TTR("Anim Multi Change Keyframe Value"), UndoRedo::MERGE_ENDS); } Vector2 prev = animation->bezier_track_get_key_in_handle(track, key); - undo_redo->add_do_method(animation.ptr(), "bezier_track_set_key_in_handle", track, key, value); - undo_redo->add_undo_method(animation.ptr(), "bezier_track_set_key_in_handle", track, key, prev); + undo_redo->add_do_method(this, "_bezier_track_set_key_in_handle", track, key, value); + undo_redo->add_undo_method(this, "_bezier_track_set_key_in_handle", track, key, prev); update_obj = true; } else if (name == "out_handle") { const Variant &value = p_value; @@ -983,8 +989,8 @@ public: undo_redo->create_action(TTR("Anim Multi Change Keyframe Value"), UndoRedo::MERGE_ENDS); } Vector2 prev = animation->bezier_track_get_key_out_handle(track, key); - undo_redo->add_do_method(animation.ptr(), "bezier_track_set_key_out_handle", track, key, value); - undo_redo->add_undo_method(animation.ptr(), "bezier_track_set_key_out_handle", track, key, prev); + undo_redo->add_do_method(this, "_bezier_track_set_key_out_handle", track, key, value); + undo_redo->add_undo_method(this, "_bezier_track_set_key_out_handle", track, key, prev); update_obj = true; } else if (name == "handle_mode") { const Variant &value = p_value; @@ -994,8 +1000,8 @@ public: undo_redo->create_action(TTR("Anim Multi Change Keyframe Value"), UndoRedo::MERGE_ENDS); } int prev = animation->bezier_track_get_key_handle_mode(track, key); - undo_redo->add_do_method(animation.ptr(), "bezier_track_set_key_handle_mode", track, key, value); - undo_redo->add_undo_method(animation.ptr(), "bezier_track_set_key_handle_mode", track, key, prev); + undo_redo->add_do_method(this, "_bezier_track_set_key_handle_mode", animation.ptr(), track, key, value); + undo_redo->add_undo_method(this, "_bezier_track_set_key_handle_mode", animation.ptr(), track, key, prev); update_obj = true; } } break; @@ -1326,7 +1332,7 @@ public: p_list->push_back(PropertyInfo(Variant::FLOAT, "value")); p_list->push_back(PropertyInfo(Variant::VECTOR2, "in_handle")); p_list->push_back(PropertyInfo(Variant::VECTOR2, "out_handle")); - p_list->push_back(PropertyInfo(Variant::INT, "handle_mode", PROPERTY_HINT_ENUM, "Free,Balanced")); + p_list->push_back(PropertyInfo(Variant::INT, "handle_mode", PROPERTY_HINT_ENUM, "Free,Linear,Balanced,Mirrored")); } break; case Animation::TYPE_AUDIO: { p_list->push_back(PropertyInfo(Variant::OBJECT, "stream", PROPERTY_HINT_RESOURCE_TYPE, "AudioStream")); @@ -2726,9 +2732,15 @@ String AnimationTrackEdit::get_tooltip(const Point2 &p_pos) const { case Animation::HANDLE_MODE_FREE: { text += TTR("Handle mode: Free\n"); } break; + case Animation::HANDLE_MODE_LINEAR: { + text += TTR("Handle mode: Linear\n"); + } break; case Animation::HANDLE_MODE_BALANCED: { text += TTR("Handle mode: Balanced\n"); } break; + case Animation::HANDLE_MODE_MIRRORED: { + text += TTR("Handle mode: Mirrored\n"); + } break; } } break; case Animation::TYPE_AUDIO: { @@ -3259,7 +3271,6 @@ void AnimationTrackEdit::_bind_methods() { ADD_SIGNAL(MethodInfo("insert_key", PropertyInfo(Variant::FLOAT, "offset"))); ADD_SIGNAL(MethodInfo("select_key", PropertyInfo(Variant::INT, "index"), PropertyInfo(Variant::BOOL, "single"))); ADD_SIGNAL(MethodInfo("deselect_key", PropertyInfo(Variant::INT, "index"))); - ADD_SIGNAL(MethodInfo("bezier_edit")); ADD_SIGNAL(MethodInfo("move_selection_begin")); ADD_SIGNAL(MethodInfo("move_selection", PropertyInfo(Variant::FLOAT, "offset"))); @@ -3422,7 +3433,8 @@ void AnimationTrackEditor::set_animation(const Ref<Animation> &p_anim, bool p_re track_edits[_get_track_selected()]->release_focus(); } if (animation.is_valid()) { - animation->disconnect("changed", callable_mp(this, &AnimationTrackEditor::_animation_changed)); + animation->disconnect("tracks_changed", callable_mp(this, &AnimationTrackEditor::_animation_changed)); + animation->disconnect("changed", callable_mp(this, &AnimationTrackEditor::_sync_animation_change)); _clear_selection(); } animation = p_anim; @@ -3433,7 +3445,8 @@ void AnimationTrackEditor::set_animation(const Ref<Animation> &p_anim, bool p_re _update_tracks(); if (animation.is_valid()) { - animation->connect("changed", callable_mp(this, &AnimationTrackEditor::_animation_changed)); + animation->connect("tracks_changed", callable_mp(this, &AnimationTrackEditor::_animation_changed), CONNECT_DEFERRED); + animation->connect("changed", callable_mp(this, &AnimationTrackEditor::_sync_animation_change), CONNECT_DEFERRED); hscroll->show(); edit->set_disabled(read_only); @@ -4348,13 +4361,12 @@ AnimationTrackEditor::TrackIndices AnimationTrackEditor::_confirm_insert(InsertD } break; case Animation::TYPE_BEZIER: { Array array; - array.resize(6); + array.resize(5); array[0] = p_id.value; array[1] = -0.25; array[2] = 0; array[3] = 0.25; array[4] = 0; - array[5] = Animation::HANDLE_MODE_BALANCED; value = array; bezier_edit_icon->set_disabled(false); @@ -4617,11 +4629,19 @@ void AnimationTrackEditor::_update_tracks() { } } +void AnimationTrackEditor::_sync_animation_change() { + bezier_edit->update(); +} + void AnimationTrackEditor::_animation_changed() { if (animation_changing_awaiting_update) { return; // All will be updated, don't bother with anything. } + if (key_edit) { + _update_key_edit(); + } + if (key_edit && key_edit->setting) { // If editing a key, just update the edited track, makes refresh less costly. if (key_edit->track < track_edits.size()) { @@ -5081,13 +5101,12 @@ void AnimationTrackEditor::_insert_key_from_track(float p_ofs, int p_track) { Variant value; _find_hint_for_track(p_track, bp, &value); Array arr; - arr.resize(6); + arr.resize(5); arr[0] = value; arr[1] = -0.25; arr[2] = 0; arr[3] = 0.25; arr[4] = 0; - arr[5] = 0; undo_redo->create_action(TTR("Add Track Key")); undo_redo->add_do_method(animation.ptr(), "track_insert_key", p_track, p_ofs, arr); @@ -5566,6 +5585,13 @@ void AnimationTrackEditor::_bezier_edit(int p_for_track) { // Search everything within the track and curve - edit it. } +void AnimationTrackEditor::_bezier_track_set_key_handle_mode(Animation *p_anim, int p_track, int p_index, Animation::HandleMode p_mode, Animation::HandleSetMode p_set_mode) { + if (!p_anim) { + return; + } + p_anim->bezier_track_set_key_handle_mode(p_track, p_index, p_mode, p_set_mode); +} + void AnimationTrackEditor::_anim_duplicate_keys(bool transpose) { // Duplicait! if (selection.size() && animation.is_valid() && (!transpose || (_get_track_selected() >= 0 && _get_track_selected() < animation->get_track_count()))) { @@ -6416,15 +6442,17 @@ void AnimationTrackEditor::_select_all_tracks_for_copy() { } void AnimationTrackEditor::_bind_methods() { - ClassDB::bind_method("_animation_update", &AnimationTrackEditor::_animation_update); - ClassDB::bind_method("_track_grab_focus", &AnimationTrackEditor::_track_grab_focus); - ClassDB::bind_method("_update_tracks", &AnimationTrackEditor::_update_tracks); - ClassDB::bind_method("_clear_selection_for_anim", &AnimationTrackEditor::_clear_selection_for_anim); - ClassDB::bind_method("_select_at_anim", &AnimationTrackEditor::_select_at_anim); - - ClassDB::bind_method("_key_selected", &AnimationTrackEditor::_key_selected); // Still used by some connect_compat. - ClassDB::bind_method("_key_deselected", &AnimationTrackEditor::_key_deselected); // Still used by some connect_compat. - ClassDB::bind_method("_clear_selection", &AnimationTrackEditor::_clear_selection); // Still used by some connect_compat. + ClassDB::bind_method(D_METHOD("_animation_update"), &AnimationTrackEditor::_animation_update); + ClassDB::bind_method(D_METHOD("_track_grab_focus"), &AnimationTrackEditor::_track_grab_focus); + ClassDB::bind_method(D_METHOD("_update_tracks"), &AnimationTrackEditor::_update_tracks); + ClassDB::bind_method(D_METHOD("_clear_selection_for_anim"), &AnimationTrackEditor::_clear_selection_for_anim); + ClassDB::bind_method(D_METHOD("_select_at_anim"), &AnimationTrackEditor::_select_at_anim); + + ClassDB::bind_method(D_METHOD("_key_selected"), &AnimationTrackEditor::_key_selected); // Still used by some connect_compat. + ClassDB::bind_method(D_METHOD("_key_deselected"), &AnimationTrackEditor::_key_deselected); // Still used by some connect_compat. + ClassDB::bind_method(D_METHOD("_clear_selection"), &AnimationTrackEditor::_clear_selection); // Still used by some connect_compat. + + ClassDB::bind_method(D_METHOD("_bezier_track_set_key_handle_mode", "animation", "track_idx", "key_idx", "key_handle_mode", "key_handle_set_mode"), &AnimationTrackEditor::_bezier_track_set_key_handle_mode, DEFVAL(Animation::HANDLE_SET_MODE_NONE)); ADD_SIGNAL(MethodInfo("timeline_changed", PropertyInfo(Variant::FLOAT, "position"), PropertyInfo(Variant::BOOL, "drag"), PropertyInfo(Variant::BOOL, "timeline_only"))); ADD_SIGNAL(MethodInfo("keying_changed")); diff --git a/editor/animation_track_editor.h b/editor/animation_track_editor.h index 5ebf25899f..9cf3269fd0 100644 --- a/editor/animation_track_editor.h +++ b/editor/animation_track_editor.h @@ -324,8 +324,9 @@ class AnimationTrackEditor : public VBoxContainer { Vector<AnimationTrackEditGroup *> groups; bool animation_changing_awaiting_update = false; - void _animation_update(); + void _animation_update(); // Updated by AnimationTrackEditor(this) int _get_track_selected(); + void _sync_animation_change(); void _animation_changed(); void _update_tracks(); @@ -449,6 +450,7 @@ class AnimationTrackEditor : public VBoxContainer { void _toggle_bezier_edit(); void _cancel_bezier_edit(); void _bezier_edit(int p_for_track); + void _bezier_track_set_key_handle_mode(Animation *p_anim, int p_track, int p_index, Animation::HandleMode p_mode, Animation::HandleSetMode p_set_mode = Animation::HANDLE_SET_MODE_NONE); ////////////// edit menu stuff diff --git a/editor/editor_sectioned_inspector.cpp b/editor/editor_sectioned_inspector.cpp index cbca3e9dcd..1faefb5af7 100644 --- a/editor/editor_sectioned_inspector.cpp +++ b/editor/editor_sectioned_inspector.cpp @@ -113,11 +113,11 @@ class SectionedInspectorFilter : public Object { } } - bool property_can_revert(const String &p_name) { + bool property_can_revert(const StringName &p_name) { return edited->property_can_revert(section + "/" + p_name); } - Variant property_get_revert(const String &p_name) { + Variant property_get_revert(const StringName &p_name) { return edited->property_get_revert(section + "/" + p_name); } diff --git a/editor/editor_themes.cpp b/editor/editor_themes.cpp index 3da9899052..0c17469e86 100644 --- a/editor/editor_themes.cpp +++ b/editor/editor_themes.cpp @@ -1849,14 +1849,14 @@ Ref<Theme> create_custom_theme(const Ref<Theme> p_theme) { return theme; } -Ref<ImageTexture> create_unscaled_default_project_icon() { -#ifdef MODULE_SVG_ENABLED +/** + * Returns the SVG code for the default project icon. + */ +String get_default_project_icon() { for (int i = 0; i < editor_icons_count; i++) { - // ESCALE should never affect size of the icon if (strcmp(editor_icons_names[i], "DefaultProjectIcon") == 0) { - return editor_generate_icon(i, false, 1.0); + return String(editor_icons_sources[i]); } } -#endif - return Ref<ImageTexture>(memnew(ImageTexture)); + return String(); } diff --git a/editor/editor_themes.h b/editor/editor_themes.h index 95184b9d4a..1c69761435 100644 --- a/editor/editor_themes.h +++ b/editor/editor_themes.h @@ -38,6 +38,6 @@ Ref<Theme> create_editor_theme(Ref<Theme> p_theme = nullptr); Ref<Theme> create_custom_theme(Ref<Theme> p_theme = nullptr); -Ref<ImageTexture> create_unscaled_default_project_icon(); +String get_default_project_icon(); #endif // EDITOR_THEMES_H diff --git a/editor/icons/BezierHandlesBalanced.svg b/editor/icons/BezierHandlesBalanced.svg index 911029e431..b1778b1a5e 100644 --- a/editor/icons/BezierHandlesBalanced.svg +++ b/editor/icons/BezierHandlesBalanced.svg @@ -1 +1 @@ -<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m1.7627119 13.627119s1.2881355-6.847458 6.5762712-8.1355935c5.0847459.9491522 5.9661009 8.1355925 5.9661009 8.1355925" fill="none" stroke="#5fb2ff" stroke-miterlimit="4.9" stroke-width="1.7"/><ellipse cx="1.898304" cy="13.491526" fill="#e0e0e0" rx="1.267586" ry="1.199789"/><ellipse cx="14.237288" cy="13.491526" fill="#e0e0e0" rx="1.267586" ry="1.199789"/><path d="m7.4559186 5.1473018-4.7355323 1.5541798" fill="none" stroke="#5fb2ff" stroke-width=".618"/><path d="m10.790357 4.2063094-2.5009748.9433136" fill="none" stroke="#5fb2ff" stroke-width=".614897"/><g fill="#e0e0e0"><ellipse cx="8.271187" cy="4.779661" rx="1.267586" ry="1.199789"/><path d="m1.7157324 5.8754878a1.2675855 1.1997888 0 0 0 -1.26757806 1.1992188 1.2675855 1.1997888 0 0 0 1.26757806 1.1992187 1.2675855 1.1997888 0 0 0 1.2675781-1.1992187 1.2675855 1.1997888 0 0 0 -1.2675781-1.1992188zm.00195.4238282a.84677333.80148375 0 0 1 .8476593.8007812.84677333.80148375 0 0 1 -.8476562.8007812.84677333.80148375 0 0 1 -.84765616-.8007812.84677333.80148375 0 0 1 .84765616-.8007812z"/><path d="m11.909414 2.4642073a1.2836218 1.231838 0 0 0 -1.283614 1.2312528 1.2836218 1.231838 0 0 0 1.283614 1.2312527 1.2836218 1.231838 0 0 0 1.283614-1.2312527 1.2836218 1.231838 0 0 0 -1.283614-1.2312528zm.002.4351497a.85748593.82289328 0 0 1 .858383.8221719.85748593.82289328 0 0 1 -.85838.822172.85748593.82289328 0 0 1 -.858379-.822172.85748593.82289328 0 0 1 .858379-.8221719z"/></g></svg> +<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m1.7627119 13.627119s1.2881355-6.847458 6.5762712-8.1355935c5.0847459.9491522 5.9661009 8.1355925 5.9661009 8.1355925" fill="none" stroke="#87b1d7" stroke-miterlimit="4.9" stroke-width=".5"/><path d="m2.4962504 7.6963851 10.1811806-3.7166314" fill="none" stroke="#61b2ff" stroke-width="1.5"/><g fill="#e0e0e0"><ellipse cx="1.898304" cy="13.491526" rx="1.267586" ry="1.199789"/><ellipse cx="14.237288" cy="13.491526" rx="1.267586" ry="1.199789"/><ellipse cx="8.338983" cy="5.491526" rx="1.267586" ry="1.199789"/><path d="m1.6910776 6.7273a1.2675855 1.1997888 0 0 0 -1.26757808 1.1992188 1.2675855 1.1997888 0 0 0 1.26757808 1.1992187 1.2675855 1.1997888 0 0 0 1.2675781-1.1992187 1.2675855 1.1997888 0 0 0 -1.2675781-1.1992188zm.00195.4238282a.84677333.80148375 0 0 1 .8476593.8007812.84677333.80148375 0 0 1 -.8476562.8007812.84677333.80148375 0 0 1 -.84765618-.8007812.84677333.80148375 0 0 1 .84765618-.8007812z"/><path d="m13.40948 2.2963899a1.2836218 1.231838 0 0 0 -1.283614 1.2312528 1.2836218 1.231838 0 0 0 1.283614 1.2312526 1.2836218 1.231838 0 0 0 1.283614-1.2312526 1.2836218 1.231838 0 0 0 -1.283614-1.2312528zm.002.4351497a.85748593.82289328 0 0 1 .858383.8221719.85748593.82289328 0 0 1 -.85838.8221719.85748593.82289328 0 0 1 -.858379-.8221719.85748593.82289328 0 0 1 .858379-.8221719z"/></g></svg> diff --git a/editor/icons/BezierHandlesFree.svg b/editor/icons/BezierHandlesFree.svg index 6e91288c79..c7bff530ae 100644 --- a/editor/icons/BezierHandlesFree.svg +++ b/editor/icons/BezierHandlesFree.svg @@ -1 +1 @@ -<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m1.7627119 13.627119s1.2881355-6.847458 6.5762712-8.1355935c5.0847459.9491522 5.9661009 8.1355925 5.9661009 8.1355925" fill="none" stroke="#5fb2ff" stroke-miterlimit="4.9" stroke-width="1.7"/><ellipse cx="1.898304" cy="13.491526" fill="#e0e0e0" rx="1.267586" ry="1.199789"/><ellipse cx="14.237288" cy="13.491526" fill="#e0e0e0" rx="1.267586" ry="1.199789"/><path d="m7.6850253 4.7560401-3.776127.6607599" fill="none" stroke="#5fb2ff" stroke-width=".805138"/><path d="m11.695505 2.3941651-2.999121 2.2935078" fill="none" stroke="#5fb2ff" stroke-width=".730798"/><g fill="#e0e0e0"><ellipse cx="8.271187" cy="4.779661" rx="1.267586" ry="1.199789"/><path d="m2.4961199 4.3976698a1.1997888 1.2675855 80.074672 0 0 -1.0419038 1.3997559 1.1997888 1.2675855 80.074672 0 0 1.4553094.9627848 1.1997888 1.2675855 80.074672 0 0 1.0419037-1.3997558 1.1997888 1.2675855 80.074672 0 0 -1.4553093-.9627849zm.074974.4171488a.80148375.84677333 80.074672 0 1 .9729986.6426896.80148375.84677333 80.074672 0 1 -.6969432.934902.80148375.84677333 80.074672 0 1 -.9729958-.6426902.80148375.84677333 80.074672 0 1 .6969432-.934902z"/><path d="m11.838896.64428913a1.231838 1.2836218 52.593897 0 0 -.271701 1.75779027 1.231838 1.2836218 52.593897 0 0 1.767576.1983008 1.231838 1.2836218 52.593897 0 0 .271701-1.75779027 1.231838 1.2836218 52.593897 0 0 -1.767576-.1983008zm.265925.3444462a.82289328.85748593 52.593897 0 1 1.181294.13165847.82289328.85748593 52.593897 0 1 -.182417 1.1745241.82289328.85748593 52.593897 0 1 -1.181291-.1316609.82289328.85748593 52.593897 0 1 .182417-1.17452347z"/></g></svg> +<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m1.7627119 13.627119s1.3064631-5.1979735 6.5945988-6.486109c5.0847463.9491522 5.9477733 6.486108 5.9477733 6.486108" fill="none" stroke="#87b1d7" stroke-miterlimit="4.9" stroke-width=".5"/><path d="m2.3554991 8.5165019 6.0018116-1.3754919 2.0717113-4.6377276" fill="none" stroke="#61b3ff" stroke-width="1.5"/><g fill="#e0e0e0"><ellipse cx="1.898304" cy="13.491526" rx="1.267586" ry="1.199789"/><ellipse cx="14.237288" cy="13.491526" rx="1.267586" ry="1.199789"/><ellipse cx="8.35731" cy="7.14101" rx="1.267586" ry="1.199789"/><path d="m1.3048251 7.4400522a1.1997888 1.2675855 80.074672 0 0 -1.04190379 1.3997559 1.1997888 1.2675855 80.074672 0 0 1.45530939.9627848 1.1997888 1.2675855 80.074672 0 0 1.0419037-1.3997558 1.1997888 1.2675855 80.074672 0 0 -1.4553093-.9627849zm.074974.4171488a.80148375.84677333 80.074672 0 1 .9729986.6426896.80148375.84677333 80.074672 0 1 -.6969432.934902.80148375.84677333 80.074672 0 1 -.97299579-.6426902.80148375.84677333 80.074672 0 1 .69694319-.934902z"/><path d="m10.024463.73592688a1.231838 1.2836218 52.593897 0 0 -.2717015 1.75779042 1.231838 1.2836218 52.593897 0 0 1.7675765.1983008 1.231838 1.2836218 52.593897 0 0 .271701-1.75779042 1.231838 1.2836218 52.593897 0 0 -1.767576-.1983008zm.265925.34444622a.82289328.85748593 52.593897 0 1 1.181294.1316585.82289328.85748593 52.593897 0 1 -.182417 1.1745242.82289328.85748593 52.593897 0 1 -1.181291-.1316609.82289328.85748593 52.593897 0 1 .182417-1.1745236z"/></g></svg> diff --git a/editor/icons/BezierHandlesLinear.svg b/editor/icons/BezierHandlesLinear.svg new file mode 100644 index 0000000000..2667779dcb --- /dev/null +++ b/editor/icons/BezierHandlesLinear.svg @@ -0,0 +1 @@ +<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m8.2711868 4.7796612-6.3728828 8.7118648z" fill="none" stroke="#87b1d7" stroke-miterlimit="4.9" stroke-width=".5"/><ellipse cx="1.898304" cy="13.491526" fill="#e0e0e0" rx="1.267586" ry="1.199789"/><path d="m14.237288 13.491526-5.9661012-8.7118648" fill="none" stroke="#87b1d7" stroke-miterlimit="4.9" stroke-width=".5"/><path d="m5.6316733 8.3879317 2.6395135-3.6082705 2.4416832 3.5654122" fill="none" stroke="#61b2ff" stroke-width="1.5"/><g fill="#e0e0e0"><ellipse cx="14.237288" cy="13.491526" rx="1.267586" ry="1.199789"/><ellipse cx="8.271187" cy="4.779661" rx="1.267586" ry="1.199789"/><path d="m5.0847454 7.9363749a1.2675855 1.1997888 0 0 0 -1.2675781 1.1992188 1.2675855 1.1997888 0 0 0 1.2675781 1.1992183 1.2675855 1.1997888 0 0 0 1.2675781-1.1992183 1.2675855 1.1997888 0 0 0 -1.2675781-1.1992188zm.00195.4238282a.84677333.80148375 0 0 1 .8476593.8007812.84677333.80148375 0 0 1 -.8476562.8007812.84677333.80148375 0 0 1 -.8476562-.8007812.84677333.80148375 0 0 1 .8476562-.8007812z"/><path d="m11.254237 7.9043407a1.2836218 1.231838 0 0 0 -1.2836135 1.2312528 1.2836218 1.231838 0 0 0 1.2836135 1.2312525 1.2836218 1.231838 0 0 0 1.283614-1.2312525 1.2836218 1.231838 0 0 0 -1.283614-1.2312528zm.002.4351497a.85748593.82289328 0 0 1 .858383.8221719.85748593.82289328 0 0 1 -.85838.822172.85748593.82289328 0 0 1 -.858379-.822172.85748593.82289328 0 0 1 .858379-.8221719z"/></g></svg> diff --git a/editor/icons/BezierHandlesMirror.svg b/editor/icons/BezierHandlesMirror.svg index 9180e31921..07817f7247 100644 --- a/editor/icons/BezierHandlesMirror.svg +++ b/editor/icons/BezierHandlesMirror.svg @@ -1 +1 @@ -<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m1.7627119 13.627119s1.2881355-6.847458 6.5762712-8.1355935c5.0847459.9491522 5.9661009 8.1355925 5.9661009 8.1355925" fill="none" stroke="#5fb2ff" stroke-miterlimit="4.9" stroke-width="1.7"/><ellipse cx="1.898304" cy="13.491526" fill="#e0e0e0" rx="1.267586" ry="1.199789"/><ellipse cx="14.237288" cy="13.491526" fill="#e0e0e0" rx="1.267586" ry="1.199789"/><path d="m8.2033896 4.6779662h-3.8335021" fill="none" stroke="#5fb2ff" stroke-width=".805138"/><path d="m11.931789 4.6440679h-3.7283994" fill="none" stroke="#5fb2ff" stroke-width=".716709"/><g fill="#e0e0e0"><ellipse cx="8.271187" cy="4.779661" rx="1.267586" ry="1.199789"/><path d="m3.1539157 3.4305762a1.2675855 1.1997888 0 0 0 -1.2675781 1.1992188 1.2675855 1.1997888 0 0 0 1.2675781 1.1992187 1.2675855 1.1997888 0 0 0 1.2675781-1.1992187 1.2675855 1.1997888 0 0 0 -1.2675781-1.1992188zm.00195.4238282a.84677333.80148375 0 0 1 .8476593.8007812.84677333.80148375 0 0 1 -.8476562.8007812.84677333.80148375 0 0 1 -.8476562-.8007812.84677333.80148375 0 0 1 .8476562-.8007812z"/><path d="m13.093969 3.3750567a1.2675855 1.1997888 0 0 0 -1.267578 1.1992188 1.2675855 1.1997888 0 0 0 1.267578 1.1992187 1.2675855 1.1997888 0 0 0 1.267578-1.1992187 1.2675855 1.1997888 0 0 0 -1.267578-1.1992188zm.002.4238282a.84677333.80148375 0 0 1 .847659.8007812.84677333.80148375 0 0 1 -.847656.8007812.84677333.80148375 0 0 1 -.847656-.8007812.84677333.80148375 0 0 1 .847656-.8007812z"/></g></svg> +<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m1.7627119 13.627119s1.2881355-6.847458 6.5762712-8.1355935c5.0847459.9491522 5.9661009 8.1355925 5.9661009 8.1355925" fill="none" stroke="#87b1d7" stroke-miterlimit="4.9" stroke-width=".5"/><ellipse cx="1.898304" cy="13.491526" fill="#e0e0e0" rx="1.267586" ry="1.199789"/><ellipse cx="14.237288" cy="13.491526" fill="#e0e0e0" rx="1.267586" ry="1.199789"/><path d="m8.3389831 5.4915255-5.8519685.0395137" fill="none" stroke="#5fb2ff" stroke-width="1.5"/><path d="m13.814033 5.4419288-5.4750499.0156984" fill="none" stroke="#5fb2ff" stroke-width="1.5"/><g fill="#e0e0e0"><ellipse cx="8.40678" cy="5.593221" rx="1.267586" ry="1.199789"/><path d="m1.6400247 4.2441355a1.2675855 1.1997888 0 0 0 -1.26757814 1.1992188 1.2675855 1.1997888 0 0 0 1.26757814 1.1992187 1.2675855 1.1997888 0 0 0 1.2675781-1.1992187 1.2675855 1.1997888 0 0 0 -1.2675781-1.1992188zm.00195.4238282a.84677333.80148375 0 0 1 .8476593.8007812.84677333.80148375 0 0 1 -.8476562.8007812.84677333.80148375 0 0 1 -.84765624-.8007812.84677333.80148375 0 0 1 .84765624-.8007812z"/><path d="m14.659116 4.188616a1.2675855 1.1997888 0 0 0 -1.267578 1.1992188 1.2675855 1.1997888 0 0 0 1.267578 1.1992187 1.2675855 1.1997888 0 0 0 1.267578-1.1992187 1.2675855 1.1997888 0 0 0 -1.267578-1.1992188zm.002.4238282a.84677333.80148375 0 0 1 .847659.8007812.84677333.80148375 0 0 1 -.847656.8007812.84677333.80148375 0 0 1 -.847656-.8007812.84677333.80148375 0 0 1 .847656-.8007812z"/></g></svg> diff --git a/editor/icons/CurveIn.svg b/editor/icons/CurveIn.svg index 2ad44dc654..fefad9ce6c 100644 --- a/editor/icons/CurveIn.svg +++ b/editor/icons/CurveIn.svg @@ -1 +1 @@ -<svg height="12" viewBox="0 0 12 12" width="12" xmlns="http://www.w3.org/2000/svg"><path d="m2 1050.4c5 0 8-3 8-8" fill="none" stroke="#e0e0e0" stroke-linecap="round" stroke-width="2" transform="translate(0 -1040.4)"/></svg> +<svg height="12" viewBox="0 0 12 12" width="12" xmlns="http://www.w3.org/2000/svg"><path d="m2 1050.4c5 0 8-3 8-8" fill="none" stroke="#80ff45" stroke-linecap="round" stroke-width="2" transform="translate(0 -1040.4)"/></svg> diff --git a/editor/icons/CurveInOut.svg b/editor/icons/CurveInOut.svg index 292dac4573..f099cb83f1 100644 --- a/editor/icons/CurveInOut.svg +++ b/editor/icons/CurveInOut.svg @@ -1 +1 @@ -<svg height="12" viewBox="0 0 12 12" width="12" xmlns="http://www.w3.org/2000/svg"><path d="m2 1050.4c5 0 3-8 8-8" fill="none" stroke="#e0e0e0" stroke-linecap="round" stroke-width="2" transform="translate(0 -1040.4)"/></svg> +<svg height="12" viewBox="0 0 12 12" width="12" xmlns="http://www.w3.org/2000/svg"><path d="m2 1050.4c5 0 3-8 8-8" fill="none" stroke="#45d7ff" stroke-linecap="round" stroke-width="2" transform="translate(0 -1040.4)"/></svg> diff --git a/editor/icons/CurveLinear.svg b/editor/icons/CurveLinear.svg index 3c1fb2a0e2..41d37c9329 100644 --- a/editor/icons/CurveLinear.svg +++ b/editor/icons/CurveLinear.svg @@ -1 +1 @@ -<svg height="12" viewBox="0 0 12 12" width="12" xmlns="http://www.w3.org/2000/svg"><path d="m2 1050.4 8-8" fill="none" stroke="#e0e0e0" stroke-linecap="round" stroke-width="2" transform="translate(0 -1040.4)"/></svg> +<svg height="12" viewBox="0 0 12 12" width="12" xmlns="http://www.w3.org/2000/svg"><path d="m2 1050.4 8-8" fill="none" stroke="#ffe345" stroke-linecap="round" stroke-width="2" transform="translate(0 -1040.4)"/></svg> diff --git a/editor/icons/CurveOut.svg b/editor/icons/CurveOut.svg index dfa9a26144..19710aa38d 100644 --- a/editor/icons/CurveOut.svg +++ b/editor/icons/CurveOut.svg @@ -1 +1 @@ -<svg height="12" viewBox="0 0 12 12" width="12" xmlns="http://www.w3.org/2000/svg"><path d="m2 1050.4c0-5 3-8 8-8" fill="none" stroke="#e0e0e0" stroke-linecap="round" stroke-width="2" transform="translate(0 -1040.4)"/></svg> +<svg height="12" viewBox="0 0 12 12" width="12" xmlns="http://www.w3.org/2000/svg"><path d="m2 1050.4c0-5 3-8 8-8" fill="none" stroke="#45ffa2" stroke-linecap="round" stroke-width="2" transform="translate(0 -1040.4)"/></svg> diff --git a/editor/icons/CurveOutIn.svg b/editor/icons/CurveOutIn.svg index 9a6463d0e9..7f200432bf 100644 --- a/editor/icons/CurveOutIn.svg +++ b/editor/icons/CurveOutIn.svg @@ -1 +1 @@ -<svg height="12" viewBox="0 0 12 12" width="12" xmlns="http://www.w3.org/2000/svg"><path d="m2 1050.4c0-5 8-3 8-8" fill="none" stroke="#e0e0e0" stroke-linecap="round" stroke-width="2" transform="translate(0 -1040.4)"/></svg> +<svg height="12" viewBox="0 0 12 12" width="12" xmlns="http://www.w3.org/2000/svg"><path d="m2 1050.4c0-5 8-3 8-8" fill="none" stroke="#ff4596" stroke-linecap="round" stroke-width="2" transform="translate(0 -1040.4)"/></svg> diff --git a/editor/plugins/bone_map_editor_plugin.cpp b/editor/plugins/bone_map_editor_plugin.cpp index 70775c1ee2..5db9249af1 100644 --- a/editor/plugins/bone_map_editor_plugin.cpp +++ b/editor/plugins/bone_map_editor_plugin.cpp @@ -47,6 +47,9 @@ void BoneMapperButton::fetch_textures() { set_offset(SIDE_TOP, 0); set_offset(SIDE_BOTTOM, 0); + // Hack to avoid handle color darkening... + set_modulate(EditorSettings::get_singleton()->is_dark_theme() ? Color(1, 1, 1) : Color(4.25, 4.25, 4.25)); + circle = memnew(TextureRect); circle->set_texture(get_theme_icon(SNAME("BoneMapperHandleCircle"), SNAME("EditorIcons"))); add_child(circle); @@ -98,14 +101,24 @@ BoneMapperButton::~BoneMapperButton() { } void BoneMapperItem::create_editor() { - skeleton_bone_selector = memnew(EditorPropertyTextEnum); - skeleton_bone_selector->setup(skeleton_bone_names, false, true); + HBoxContainer *hbox = memnew(HBoxContainer); + add_child(hbox); + + skeleton_bone_selector = memnew(EditorPropertyText); skeleton_bone_selector->set_label(profile_bone_name); skeleton_bone_selector->set_selectable(false); + skeleton_bone_selector->set_h_size_flags(SIZE_EXPAND_FILL); skeleton_bone_selector->set_object_and_property(bone_map.ptr(), "bone_map/" + String(profile_bone_name)); skeleton_bone_selector->update_property(); skeleton_bone_selector->connect("property_changed", callable_mp(this, &BoneMapperItem::_value_changed)); - add_child(skeleton_bone_selector); + hbox->add_child(skeleton_bone_selector); + + picker_button = memnew(Button); + picker_button->set_icon(get_theme_icon(SNAME("ClassList"), SNAME("EditorIcons"))); + picker_button->connect("pressed", callable_mp(this, &BoneMapperItem::_open_picker)); + hbox->add_child(picker_button); + + add_child(memnew(HSeparator)); } void BoneMapperItem::_update_property() { @@ -114,6 +127,10 @@ void BoneMapperItem::_update_property() { } } +void BoneMapperItem::_open_picker() { + emit_signal(SNAME("pick"), profile_bone_name); +} + void BoneMapperItem::_value_changed(const String &p_property, Variant p_value, const String &p_name, bool p_changing) { bone_map->set(p_property, p_value); } @@ -133,25 +150,153 @@ void BoneMapperItem::_notification(int p_what) { } void BoneMapperItem::_bind_methods() { + ADD_SIGNAL(MethodInfo("pick", PropertyInfo(Variant::STRING_NAME, "profile_bone_name"))); } -BoneMapperItem::BoneMapperItem(Ref<BoneMap> &p_bone_map, PackedStringArray p_skeleton_bone_names, const StringName &p_profile_bone_name) { +BoneMapperItem::BoneMapperItem(Ref<BoneMap> &p_bone_map, const StringName &p_profile_bone_name) { bone_map = p_bone_map; - skeleton_bone_names = p_skeleton_bone_names; profile_bone_name = p_profile_bone_name; } BoneMapperItem::~BoneMapperItem() { } +void BonePicker::create_editors() { + set_title(TTR("Bone Picker:")); + + VBoxContainer *vbox = memnew(VBoxContainer); + add_child(vbox); + + bones = memnew(Tree); + bones->set_select_mode(Tree::SELECT_SINGLE); + bones->set_v_size_flags(Control::SIZE_EXPAND_FILL); + bones->set_hide_root(true); + bones->connect("item_activated", callable_mp(this, &BonePicker::_confirm)); + vbox->add_child(bones); + + create_bones_tree(skeleton); +} + +void BonePicker::create_bones_tree(Skeleton3D *p_skeleton) { + bones->clear(); + + if (!p_skeleton) { + return; + } + + TreeItem *root = bones->create_item(); + + HashMap<int, TreeItem *> items; + + items.insert(-1, root); + + Ref<Texture> bone_icon = get_theme_icon(SNAME("BoneAttachment3D"), SNAME("EditorIcons")); + + Vector<int> bones_to_process = p_skeleton->get_parentless_bones(); + bool is_first = true; + while (bones_to_process.size() > 0) { + int current_bone_idx = bones_to_process[0]; + bones_to_process.erase(current_bone_idx); + + Vector<int> current_bone_child_bones = p_skeleton->get_bone_children(current_bone_idx); + int child_bone_size = current_bone_child_bones.size(); + for (int i = 0; i < child_bone_size; i++) { + bones_to_process.push_back(current_bone_child_bones[i]); + } + + const int parent_idx = p_skeleton->get_bone_parent(current_bone_idx); + TreeItem *parent_item = items.find(parent_idx)->value; + + TreeItem *joint_item = bones->create_item(parent_item); + items.insert(current_bone_idx, joint_item); + + joint_item->set_text(0, p_skeleton->get_bone_name(current_bone_idx)); + joint_item->set_icon(0, bone_icon); + joint_item->set_selectable(0, true); + joint_item->set_metadata(0, "bones/" + itos(current_bone_idx)); + if (is_first) { + is_first = false; + } else { + joint_item->set_collapsed(true); + } + } +} + +void BonePicker::_confirm() { + _ok_pressed(); +} + +void BonePicker::popup_bones_tree(const Size2i &p_minsize) { + popup_centered(p_minsize); +} + +bool BonePicker::has_selected_bone() { + TreeItem *selected = bones->get_selected(); + if (!selected) { + return false; + } + return true; +} + +StringName BonePicker::get_selected_bone() { + TreeItem *selected = bones->get_selected(); + if (!selected) { + return StringName(); + } + return selected->get_text(0); +} + +void BonePicker::_bind_methods() { +} + +void BonePicker::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: { + create_editors(); + } break; + } +} + +BonePicker::BonePicker(Skeleton3D *p_skeleton) { + skeleton = p_skeleton; +} + +BonePicker::~BonePicker() { +} + void BoneMapper::create_editor() { + // Create Bone picker. + picker = memnew(BonePicker(skeleton)); + picker->connect("confirmed", callable_mp(this, &BoneMapper::_apply_picker_selection)); + add_child(picker, false, INTERNAL_MODE_FRONT); + + profile_selector = memnew(EditorPropertyResource); + profile_selector->setup(bone_map.ptr(), "profile", "SkeletonProfile"); + profile_selector->set_label("Profile"); + profile_selector->set_selectable(false); + profile_selector->set_object_and_property(bone_map.ptr(), "profile"); + profile_selector->update_property(); + profile_selector->connect("property_changed", callable_mp(this, &BoneMapper::_profile_changed)); + add_child(profile_selector); + add_child(memnew(HSeparator)); + + HBoxContainer *group_hbox = memnew(HBoxContainer); + add_child(group_hbox); + profile_group_selector = memnew(EditorPropertyEnum); profile_group_selector->set_label("Group"); profile_group_selector->set_selectable(false); + profile_group_selector->set_h_size_flags(SIZE_EXPAND_FILL); profile_group_selector->set_object_and_property(this, "current_group_idx"); profile_group_selector->update_property(); profile_group_selector->connect("property_changed", callable_mp(this, &BoneMapper::_value_changed)); - add_child(profile_group_selector); + group_hbox->add_child(profile_group_selector); + + clear_mapping_button = memnew(Button); + clear_mapping_button->set_icon(get_theme_icon(SNAME("Clear"), SNAME("EditorIcons"))); + clear_mapping_button->set_tooltip(TTR("Clear mappings in current group.")); + clear_mapping_button->connect("pressed", callable_mp(this, &BoneMapper::_clear_mapping_current_group)); + group_hbox->add_child(clear_mapping_button); bone_mapper_field = memnew(AspectRatioContainer); bone_mapper_field->set_stretch_mode(AspectRatioContainer::STRETCH_FIT); @@ -175,9 +320,6 @@ void BoneMapper::create_editor() { mapper_item_vbox = memnew(VBoxContainer); add_child(mapper_item_vbox); - separator = memnew(HSeparator); - add_child(separator); - recreate_items(); } @@ -201,6 +343,18 @@ void BoneMapper::update_group_idx() { } } +void BoneMapper::_pick_bone(const StringName &p_bone_name) { + picker_key_name = p_bone_name; + picker->popup_bones_tree(Size2(500, 500) * EDSCALE); +} + +void BoneMapper::_apply_picker_selection() { + if (!picker->has_selected_bone()) { + return; + } + bone_map->set_skeleton_bone_name(picker_key_name, picker->get_selected_bone()); +} + void BoneMapper::set_current_group_idx(int p_group_idx) { current_group_idx = p_group_idx; recreate_editor(); @@ -282,6 +436,7 @@ void BoneMapper::clear_items() { // Clear items. int len = bone_mapper_items.size(); for (int i = 0; i < len; i++) { + bone_mapper_items[i]->disconnect("pick", callable_mp(this, &BoneMapper::_pick_bone)); mapper_item_vbox->remove_child(bone_mapper_items[i]); memdelete(bone_mapper_items[i]); } @@ -293,16 +448,11 @@ void BoneMapper::recreate_items() { // Create items by profile. Ref<SkeletonProfile> profile = bone_map->get_profile(); if (profile.is_valid()) { - PackedStringArray skeleton_bone_names; - int len = skeleton->get_bone_count(); - for (int i = 0; i < len; i++) { - skeleton_bone_names.push_back(skeleton->get_bone_name(i)); - } - - len = profile->get_bone_size(); + int len = profile->get_bone_size(); for (int i = 0; i < len; i++) { StringName bn = profile->get_bone_name(i); - bone_mapper_items.append(memnew(BoneMapperItem(bone_map, skeleton_bone_names, bn))); + bone_mapper_items.append(memnew(BoneMapperItem(bone_map, bn))); + bone_mapper_items[i]->connect("pick", callable_mp(this, &BoneMapper::_pick_bone), CONNECT_DEFERRED); mapper_item_vbox->add_child(bone_mapper_items[i]); } } @@ -363,11 +513,754 @@ void BoneMapper::_update_state() { } } +void BoneMapper::_clear_mapping_current_group() { + if (bone_map.is_valid()) { + Ref<SkeletonProfile> profile = bone_map->get_profile(); + if (profile.is_valid() && profile->get_group_size() > 0) { + int len = profile->get_bone_size(); + for (int i = 0; i < len; i++) { + if (profile->get_group(i) == profile->get_group_name(current_group_idx)) { + bone_map->_set_skeleton_bone_name(profile->get_bone_name(i), StringName()); + } + } + recreate_items(); + } + } +} + +#ifdef MODULE_REGEX_ENABLED +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) { + // There may be multiple candidates hit by existing the subsidiary bone. + // The one with the shortest name is probably the original. + LocalVector<String> hit_list; + String shortest = ""; + + for (int word_idx = 0; word_idx < p_picklist.size(); word_idx++) { + RegEx re = RegEx(p_picklist[word_idx]); + if (p_child == -1) { + Vector<int> bones_to_process = p_parent == -1 ? p_skeleton->get_parentless_bones() : p_skeleton->get_bone_children(p_parent); + while (bones_to_process.size() > 0) { + int idx = bones_to_process[0]; + bones_to_process.erase(idx); + Vector<int> children = p_skeleton->get_bone_children(idx); + for (int i = 0; i < children.size(); i++) { + bones_to_process.push_back(children[i]); + } + + if (p_children_count == 0 && children.size() > 0) { + continue; + } + if (p_children_count > 0 && children.size() < p_children_count) { + continue; + } + + String bn = skeleton->get_bone_name(idx); + if (!re.search(bn.to_lower()).is_null() && guess_bone_segregation(bn) == p_segregation) { + hit_list.push_back(bn); + } + } + + if (hit_list.size() > 0) { + shortest = hit_list[0]; + for (uint32_t i = 0; i < hit_list.size(); i++) { + if (hit_list[i].length() < shortest.length()) { + shortest = hit_list[i]; // Prioritize parent. + } + } + } + } else { + int idx = skeleton->get_bone_parent(p_child); + while (idx != p_parent && idx >= 0) { + Vector<int> children = p_skeleton->get_bone_children(idx); + if (p_children_count == 0 && children.size() > 0) { + continue; + } + if (p_children_count > 0 && children.size() < p_children_count) { + continue; + } + + String bn = skeleton->get_bone_name(idx); + if (!re.search(bn.to_lower()).is_null() && guess_bone_segregation(bn) == p_segregation) { + hit_list.push_back(bn); + } + idx = skeleton->get_bone_parent(idx); + } + + if (hit_list.size() > 0) { + shortest = hit_list[0]; + for (uint32_t i = 0; i < hit_list.size(); i++) { + if (hit_list[i].length() <= shortest.length()) { + shortest = hit_list[i]; // Prioritize parent. + } + } + } + } + + if (shortest != "") { + break; + } + } + + if (shortest == "") { + return -1; + } + + return skeleton->find_bone(shortest); +} + +BoneMapper::BoneSegregation BoneMapper::guess_bone_segregation(String p_bone_name) { + String fixed_bn = p_bone_name.camelcase_to_underscore().to_lower(); + + LocalVector<String> left_words; + left_words.push_back("(?<![a-zA-Z])left"); + left_words.push_back("(?<![a-zA-Z0-9])l(?![a-zA-Z0-9])"); + + LocalVector<String> right_words; + right_words.push_back("(?<![a-zA-Z])right"); + right_words.push_back("(?<![a-zA-Z0-9])r(?![a-zA-Z0-9])"); + + for (uint32_t i = 0; i < left_words.size(); i++) { + RegEx re_l = RegEx(left_words[i]); + if (!re_l.search(fixed_bn).is_null()) { + return BONE_SEGREGATION_LEFT; + } + RegEx re_r = RegEx(right_words[i]); + if (!re_r.search(fixed_bn).is_null()) { + return BONE_SEGREGATION_RIGHT; + } + } + + return BONE_SEGREGATION_NONE; +} + +void BoneMapper::_run_auto_mapping() { + auto_mapping_process(bone_map); + recreate_items(); +} + +void BoneMapper::auto_mapping_process(Ref<BoneMap> &p_bone_map) { + WARN_PRINT("Run auto mapping."); + + int bone_idx = -1; + Vector<String> picklist; // Use Vector<String> because match words have priority. + Vector<int> search_path; + + // 1. Guess Hips + picklist.push_back("hip"); + picklist.push_back("pelvis"); + picklist.push_back("waist"); + picklist.push_back("torso"); + int hips = search_bone_by_name(skeleton, picklist); + if (hips == -1) { + WARN_PRINT("Auto Mapping couldn't guess Hips. Abort auto mapping."); + return; // If there is no Hips, we cannot guess bone after then. + } else { + p_bone_map->_set_skeleton_bone_name("Hips", skeleton->get_bone_name(hips)); + } + picklist.clear(); + + // 2. Guess Root + bone_idx = skeleton->get_bone_parent(hips); + while (bone_idx >= 0) { + search_path.push_back(bone_idx); + bone_idx = skeleton->get_bone_parent(bone_idx); + } + if (search_path.size() == 0) { + bone_idx = -1; + } else if (search_path.size() == 1) { + bone_idx = search_path[0]; // It is only one bone which can be root. + } else { + bool found = false; + for (int i = 0; i < search_path.size(); i++) { + RegEx re = RegEx("root"); + if (!re.search(skeleton->get_bone_name(search_path[i]).to_lower()).is_null()) { + bone_idx = search_path[i]; // Name match is preferred. + found = true; + break; + } + } + if (!found) { + for (int i = 0; i < search_path.size(); i++) { + if (Vector3(0, 0, 0).is_equal_approx(skeleton->get_bone_global_rest(search_path[i]).origin)) { + bone_idx = search_path[i]; // The bone existing at the origin is appropriate as a root. + found = true; + break; + } + } + } + if (!found) { + bone_idx = search_path[search_path.size() - 1]; // Ambiguous, but most parental bone selected. + } + } + if (bone_idx == -1) { + WARN_PRINT("Auto Mapping couldn't guess Root."); // Root is not required, so continue. + } else { + p_bone_map->_set_skeleton_bone_name("Root", skeleton->get_bone_name(bone_idx)); + } + bone_idx = -1; + search_path.clear(); + + // 3. Guess Neck + picklist.push_back("neck"); + picklist.push_back("head"); // For no neck model. + picklist.push_back("face"); // Same above. + int neck = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_NONE, hips); + picklist.clear(); + + // 4. Guess Head + picklist.push_back("head"); + picklist.push_back("face"); + int head = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_NONE, neck); + if (head == -1) { + search_path = skeleton->get_bone_children(neck); + if (search_path.size() == 1) { + head = search_path[0]; // Maybe only one child of the Neck is Head. + } + } + if (head == -1) { + if (neck != -1) { + head = neck; // The head animation should have more movement. + neck = -1; + p_bone_map->_set_skeleton_bone_name("Head", skeleton->get_bone_name(head)); + } else { + WARN_PRINT("Auto Mapping couldn't guess Neck or Head."); // Continued for guessing on the other bones. But abort when guessing spines step. + } + } else { + p_bone_map->_set_skeleton_bone_name("Neck", skeleton->get_bone_name(neck)); + p_bone_map->_set_skeleton_bone_name("Head", skeleton->get_bone_name(head)); + } + picklist.clear(); + search_path.clear(); + + int neck_or_head = neck != -1 ? neck : (head != -1 ? head : -1); + if (neck_or_head != -1) { + // 4-1. Guess Eyes + picklist.push_back("eye(?!.*(brow|lash|lid))"); + bone_idx = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_LEFT, neck_or_head); + if (bone_idx == -1) { + WARN_PRINT("Auto Mapping couldn't guess LeftEye."); + } else { + p_bone_map->_set_skeleton_bone_name("LeftEye", skeleton->get_bone_name(bone_idx)); + } + bone_idx = -1; + + bone_idx = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_RIGHT, neck_or_head); + if (bone_idx == -1) { + WARN_PRINT("Auto Mapping couldn't guess RightEye."); + } else { + p_bone_map->_set_skeleton_bone_name("RightEye", skeleton->get_bone_name(bone_idx)); + } + bone_idx = -1; + picklist.clear(); + + // 4-2. Guess Jaw + picklist.push_back("jaw"); + bone_idx = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_NONE, neck_or_head); + if (bone_idx == -1) { + WARN_PRINT("Auto Mapping couldn't guess Jaw."); + } else { + p_bone_map->_set_skeleton_bone_name("Jaw", skeleton->get_bone_name(bone_idx)); + } + bone_idx = -1; + picklist.clear(); + } + + // 5. Guess Foots + picklist.push_back("foot"); + picklist.push_back("ankle"); + int left_foot = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_LEFT, hips); + if (left_foot == -1) { + WARN_PRINT("Auto Mapping couldn't guess LeftFoot."); + } else { + p_bone_map->_set_skeleton_bone_name("LeftFoot", skeleton->get_bone_name(left_foot)); + } + int right_foot = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_RIGHT, hips); + if (right_foot == -1) { + WARN_PRINT("Auto Mapping couldn't guess RightFoot."); + } else { + p_bone_map->_set_skeleton_bone_name("RightFoot", skeleton->get_bone_name(right_foot)); + } + picklist.clear(); + + // 5-1. Guess LowerLegs + picklist.push_back("(low|under).*leg"); + picklist.push_back("knee"); + picklist.push_back("shin"); + picklist.push_back("calf"); + picklist.push_back("leg"); + int left_lower_leg = -1; + if (left_foot != -1) { + left_lower_leg = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_LEFT, hips, left_foot); + } + if (left_lower_leg == -1) { + WARN_PRINT("Auto Mapping couldn't guess LeftLowerLeg."); + } else { + p_bone_map->_set_skeleton_bone_name("LeftLowerLeg", skeleton->get_bone_name(left_lower_leg)); + } + int right_lower_leg = -1; + if (right_foot != -1) { + right_lower_leg = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_RIGHT, hips, right_foot); + } + if (right_lower_leg == -1) { + WARN_PRINT("Auto Mapping couldn't guess RightLowerLeg."); + } else { + p_bone_map->_set_skeleton_bone_name("RightLowerLeg", skeleton->get_bone_name(right_lower_leg)); + } + picklist.clear(); + + // 5-2. Guess UpperLegs + picklist.push_back("up.*leg"); + picklist.push_back("thigh"); + picklist.push_back("leg"); + if (left_lower_leg != -1) { + bone_idx = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_LEFT, hips, left_lower_leg); + } + if (bone_idx == -1) { + WARN_PRINT("Auto Mapping couldn't guess LeftUpperLeg."); + } else { + p_bone_map->_set_skeleton_bone_name("LeftUpperLeg", skeleton->get_bone_name(bone_idx)); + } + bone_idx = -1; + if (right_lower_leg != -1) { + bone_idx = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_RIGHT, hips, right_lower_leg); + } + if (bone_idx == -1) { + WARN_PRINT("Auto Mapping couldn't guess RightUpperLeg."); + } else { + p_bone_map->_set_skeleton_bone_name("RightUpperLeg", skeleton->get_bone_name(bone_idx)); + } + bone_idx = -1; + picklist.clear(); + + // 5-3. Guess Toes + picklist.push_back("toe"); + picklist.push_back("ball"); + if (left_foot != -1) { + bone_idx = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_LEFT, left_foot); + if (bone_idx == -1) { + search_path = skeleton->get_bone_children(left_foot); + if (search_path.size() == 1) { + bone_idx = search_path[0]; // Maybe only one child of the Foot is Toes. + } + search_path.clear(); + } + } + if (bone_idx == -1) { + WARN_PRINT("Auto Mapping couldn't guess LeftToes."); + } else { + p_bone_map->_set_skeleton_bone_name("LeftToes", skeleton->get_bone_name(bone_idx)); + } + bone_idx = -1; + if (right_foot != -1) { + bone_idx = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_RIGHT, right_foot); + if (bone_idx == -1) { + search_path = skeleton->get_bone_children(right_foot); + if (search_path.size() == 1) { + bone_idx = search_path[0]; // Maybe only one child of the Foot is Toes. + } + search_path.clear(); + } + } + if (bone_idx == -1) { + WARN_PRINT("Auto Mapping couldn't guess RightToes."); + } else { + p_bone_map->_set_skeleton_bone_name("RightToes", skeleton->get_bone_name(bone_idx)); + } + bone_idx = -1; + picklist.clear(); + + // 6. Guess Hands + picklist.push_back("hand"); + picklist.push_back("wrist"); + picklist.push_back("palm"); + picklist.push_back("fingers"); + int left_hand_or_palm = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_LEFT, hips, -1, 5); + if (left_hand_or_palm == -1) { + // Ambiguous, but try again for fewer finger models. + left_hand_or_palm = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_LEFT, hips); + } + int left_hand = left_hand_or_palm; // Check for the presence of a wrist, since bones with five children may be palmar. + while (left_hand != -1) { + bone_idx = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_LEFT, hips, left_hand); + if (bone_idx == -1) { + break; + } + left_hand = bone_idx; + } + if (left_hand == -1) { + WARN_PRINT("Auto Mapping couldn't guess LeftHand."); + } else { + p_bone_map->_set_skeleton_bone_name("LeftHand", skeleton->get_bone_name(left_hand)); + } + bone_idx = -1; + int right_hand_or_palm = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_RIGHT, hips, -1, 5); + if (right_hand_or_palm == -1) { + // Ambiguous, but try again for fewer finger models. + right_hand_or_palm = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_RIGHT, hips); + } + int right_hand = right_hand_or_palm; + while (right_hand != -1) { + bone_idx = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_RIGHT, hips, right_hand); + if (bone_idx == -1) { + break; + } + right_hand = bone_idx; + } + if (right_hand == -1) { + WARN_PRINT("Auto Mapping couldn't guess RightHand."); + } else { + p_bone_map->_set_skeleton_bone_name("RightHand", skeleton->get_bone_name(right_hand)); + } + bone_idx = -1; + picklist.clear(); + + // 6-1. Guess Finger + bool named_finger_is_found = false; + LocalVector<String> fingers; + fingers.push_back("thumb|pollex"); + fingers.push_back("index|fore"); + fingers.push_back("middle"); + fingers.push_back("ring"); + fingers.push_back("little|pinkie|pinky"); + if (left_hand_or_palm != -1) { + LocalVector<LocalVector<String>> left_fingers_map; + left_fingers_map.resize(5); + left_fingers_map[0].push_back("LeftThumbMetacarpal"); + left_fingers_map[0].push_back("LeftThumbProximal"); + left_fingers_map[0].push_back("LeftThumbDistal"); + left_fingers_map[1].push_back("LeftIndexProximal"); + left_fingers_map[1].push_back("LeftIndexIntermediate"); + left_fingers_map[1].push_back("LeftIndexDistal"); + left_fingers_map[2].push_back("LeftMiddleProximal"); + left_fingers_map[2].push_back("LeftMiddleIntermediate"); + left_fingers_map[2].push_back("LeftMiddleDistal"); + left_fingers_map[3].push_back("LeftRingProximal"); + left_fingers_map[3].push_back("LeftRingIntermediate"); + left_fingers_map[3].push_back("LeftRingDistal"); + left_fingers_map[4].push_back("LeftLittleProximal"); + left_fingers_map[4].push_back("LeftLittleIntermediate"); + left_fingers_map[4].push_back("LeftLittleDistal"); + for (int i = 0; i < 5; i++) { + picklist.push_back(fingers[i]); + int finger = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_LEFT, left_hand_or_palm, -1, 0); + if (finger != -1) { + while (finger != left_hand_or_palm && finger >= 0) { + search_path.push_back(finger); + finger = skeleton->get_bone_parent(finger); + } + search_path.reverse(); + if (search_path.size() == 1) { + p_bone_map->_set_skeleton_bone_name(left_fingers_map[i][0], skeleton->get_bone_name(search_path[0])); + named_finger_is_found = true; + } else if (search_path.size() == 2) { + p_bone_map->_set_skeleton_bone_name(left_fingers_map[i][0], skeleton->get_bone_name(search_path[0])); + p_bone_map->_set_skeleton_bone_name(left_fingers_map[i][1], skeleton->get_bone_name(search_path[1])); + named_finger_is_found = true; + } else if (search_path.size() >= 3) { + search_path = search_path.slice(-3); // Eliminate the possibility of carpal bone. + p_bone_map->_set_skeleton_bone_name(left_fingers_map[i][0], skeleton->get_bone_name(search_path[0])); + p_bone_map->_set_skeleton_bone_name(left_fingers_map[i][1], skeleton->get_bone_name(search_path[1])); + p_bone_map->_set_skeleton_bone_name(left_fingers_map[i][2], skeleton->get_bone_name(search_path[2])); + named_finger_is_found = true; + } + } + picklist.clear(); + search_path.clear(); + } + + // It is a bit corner case, but possibly the finger names are sequentially numbered... + if (!named_finger_is_found) { + picklist.push_back("finger"); + RegEx finger_re = RegEx("finger"); + search_path = skeleton->get_bone_children(left_hand_or_palm); + Vector<String> finger_names; + for (int i = 0; i < search_path.size(); i++) { + String bn = skeleton->get_bone_name(search_path[i]); + if (!finger_re.search(bn.to_lower()).is_null()) { + finger_names.push_back(bn); + } + } + finger_names.sort(); // Order by lexicographic, normal use cases never have more than 10 fingers in one hand. + search_path.clear(); + for (int i = 0; i < finger_names.size(); i++) { + if (i >= 5) { + break; + } + int finger_root = skeleton->find_bone(finger_names[i]); + int finger = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_LEFT, finger_root, -1, 0); + if (finger != -1) { + while (finger != finger_root && finger >= 0) { + search_path.push_back(finger); + finger = skeleton->get_bone_parent(finger); + } + } + search_path.push_back(finger_root); + search_path.reverse(); + if (search_path.size() == 1) { + p_bone_map->_set_skeleton_bone_name(left_fingers_map[i][0], skeleton->get_bone_name(search_path[0])); + } else if (search_path.size() == 2) { + p_bone_map->_set_skeleton_bone_name(left_fingers_map[i][0], skeleton->get_bone_name(search_path[0])); + p_bone_map->_set_skeleton_bone_name(left_fingers_map[i][1], skeleton->get_bone_name(search_path[1])); + } else if (search_path.size() >= 3) { + search_path = search_path.slice(-3); // Eliminate the possibility of carpal bone. + p_bone_map->_set_skeleton_bone_name(left_fingers_map[i][0], skeleton->get_bone_name(search_path[0])); + p_bone_map->_set_skeleton_bone_name(left_fingers_map[i][1], skeleton->get_bone_name(search_path[1])); + p_bone_map->_set_skeleton_bone_name(left_fingers_map[i][2], skeleton->get_bone_name(search_path[2])); + } + search_path.clear(); + } + picklist.clear(); + } + } + named_finger_is_found = false; + if (right_hand_or_palm != -1) { + LocalVector<LocalVector<String>> right_fingers_map; + right_fingers_map.resize(5); + right_fingers_map[0].push_back("RightThumbMetacarpal"); + right_fingers_map[0].push_back("RightThumbProximal"); + right_fingers_map[0].push_back("RightThumbDistal"); + right_fingers_map[1].push_back("RightIndexProximal"); + right_fingers_map[1].push_back("RightIndexIntermediate"); + right_fingers_map[1].push_back("RightIndexDistal"); + right_fingers_map[2].push_back("RightMiddleProximal"); + right_fingers_map[2].push_back("RightMiddleIntermediate"); + right_fingers_map[2].push_back("RightMiddleDistal"); + right_fingers_map[3].push_back("RightRingProximal"); + right_fingers_map[3].push_back("RightRingIntermediate"); + right_fingers_map[3].push_back("RightRingDistal"); + right_fingers_map[4].push_back("RightLittleProximal"); + right_fingers_map[4].push_back("RightLittleIntermediate"); + right_fingers_map[4].push_back("RightLittleDistal"); + for (int i = 0; i < 5; i++) { + picklist.push_back(fingers[i]); + int finger = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_RIGHT, right_hand_or_palm, -1, 0); + if (finger != -1) { + while (finger != right_hand_or_palm && finger >= 0) { + search_path.push_back(finger); + finger = skeleton->get_bone_parent(finger); + } + search_path.reverse(); + if (search_path.size() == 1) { + p_bone_map->_set_skeleton_bone_name(right_fingers_map[i][0], skeleton->get_bone_name(search_path[0])); + named_finger_is_found = true; + } else if (search_path.size() == 2) { + p_bone_map->_set_skeleton_bone_name(right_fingers_map[i][0], skeleton->get_bone_name(search_path[0])); + p_bone_map->_set_skeleton_bone_name(right_fingers_map[i][1], skeleton->get_bone_name(search_path[1])); + named_finger_is_found = true; + } else if (search_path.size() >= 3) { + search_path = search_path.slice(-3); // Eliminate the possibility of carpal bone. + p_bone_map->_set_skeleton_bone_name(right_fingers_map[i][0], skeleton->get_bone_name(search_path[0])); + p_bone_map->_set_skeleton_bone_name(right_fingers_map[i][1], skeleton->get_bone_name(search_path[1])); + p_bone_map->_set_skeleton_bone_name(right_fingers_map[i][2], skeleton->get_bone_name(search_path[2])); + named_finger_is_found = true; + } + } + picklist.clear(); + search_path.clear(); + } + + // It is a bit corner case, but possibly the finger names are sequentially numbered... + if (!named_finger_is_found) { + picklist.push_back("finger"); + RegEx finger_re = RegEx("finger"); + search_path = skeleton->get_bone_children(right_hand_or_palm); + Vector<String> finger_names; + for (int i = 0; i < search_path.size(); i++) { + String bn = skeleton->get_bone_name(search_path[i]); + if (!finger_re.search(bn.to_lower()).is_null()) { + finger_names.push_back(bn); + } + } + finger_names.sort(); // Order by lexicographic, normal use cases never have more than 10 fingers in one hand. + search_path.clear(); + for (int i = 0; i < finger_names.size(); i++) { + if (i >= 5) { + break; + } + int finger_root = skeleton->find_bone(finger_names[i]); + int finger = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_RIGHT, finger_root, -1, 0); + if (finger != -1) { + while (finger != finger_root && finger >= 0) { + search_path.push_back(finger); + finger = skeleton->get_bone_parent(finger); + } + } + search_path.push_back(finger_root); + search_path.reverse(); + if (search_path.size() == 1) { + p_bone_map->_set_skeleton_bone_name(right_fingers_map[i][0], skeleton->get_bone_name(search_path[0])); + } else if (search_path.size() == 2) { + p_bone_map->_set_skeleton_bone_name(right_fingers_map[i][0], skeleton->get_bone_name(search_path[0])); + p_bone_map->_set_skeleton_bone_name(right_fingers_map[i][1], skeleton->get_bone_name(search_path[1])); + } else if (search_path.size() >= 3) { + search_path = search_path.slice(-3); // Eliminate the possibility of carpal bone. + p_bone_map->_set_skeleton_bone_name(right_fingers_map[i][0], skeleton->get_bone_name(search_path[0])); + p_bone_map->_set_skeleton_bone_name(right_fingers_map[i][1], skeleton->get_bone_name(search_path[1])); + p_bone_map->_set_skeleton_bone_name(right_fingers_map[i][2], skeleton->get_bone_name(search_path[2])); + } + search_path.clear(); + } + picklist.clear(); + } + } + + // 7. Guess Arms + picklist.push_back("shoulder"); + picklist.push_back("clavicle"); + picklist.push_back("collar"); + int left_shoulder = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_LEFT, hips); + if (left_shoulder == -1) { + WARN_PRINT("Auto Mapping couldn't guess LeftShoulder."); + } else { + p_bone_map->_set_skeleton_bone_name("LeftShoulder", skeleton->get_bone_name(left_shoulder)); + } + int right_shoulder = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_RIGHT, hips); + if (right_shoulder == -1) { + WARN_PRINT("Auto Mapping couldn't guess RightShoulder."); + } else { + p_bone_map->_set_skeleton_bone_name("RightShoulder", skeleton->get_bone_name(right_shoulder)); + } + picklist.clear(); + + // 7-1. Guess LowerArms + picklist.push_back("(low|fore).*arm"); + picklist.push_back("elbow"); + picklist.push_back("arm"); + int left_lower_arm = -1; + if (left_shoulder != -1 && left_hand_or_palm != -1) { + left_lower_arm = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_LEFT, left_shoulder, left_hand_or_palm); + } + if (left_lower_arm == -1) { + WARN_PRINT("Auto Mapping couldn't guess LeftLowerArm."); + } else { + p_bone_map->_set_skeleton_bone_name("LeftLowerArm", skeleton->get_bone_name(left_lower_arm)); + } + int right_lower_arm = -1; + if (right_shoulder != -1 && right_hand_or_palm != -1) { + right_lower_arm = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_RIGHT, right_shoulder, right_hand_or_palm); + } + if (right_lower_arm == -1) { + WARN_PRINT("Auto Mapping couldn't guess RightLowerArm."); + } else { + p_bone_map->_set_skeleton_bone_name("RightLowerArm", skeleton->get_bone_name(right_lower_arm)); + } + picklist.clear(); + + // 7-2. Guess UpperArms + picklist.push_back("up.*arm"); + picklist.push_back("arm"); + if (left_shoulder != -1 && left_lower_arm != -1) { + bone_idx = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_LEFT, left_shoulder, left_lower_arm); + } + if (bone_idx == -1) { + WARN_PRINT("Auto Mapping couldn't guess LeftUpperArm."); + } else { + p_bone_map->_set_skeleton_bone_name("LeftUpperArm", skeleton->get_bone_name(bone_idx)); + } + bone_idx = -1; + if (right_shoulder != -1 && right_lower_arm != -1) { + bone_idx = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_RIGHT, right_shoulder, right_lower_arm); + } + if (bone_idx == -1) { + WARN_PRINT("Auto Mapping couldn't guess RightUpperArm."); + } else { + p_bone_map->_set_skeleton_bone_name("RightUpperArm", skeleton->get_bone_name(bone_idx)); + } + bone_idx = -1; + picklist.clear(); + + // 8. Guess UpperChest or Chest + if (neck_or_head == -1) { + return; // Abort. + } + int chest_or_upper_chest = skeleton->get_bone_parent(neck_or_head); + bool is_appropriate = true; + if (left_shoulder != -1) { + bone_idx = skeleton->get_bone_parent(left_shoulder); + bool detect = false; + while (bone_idx != hips && bone_idx >= 0) { + if (bone_idx == chest_or_upper_chest) { + detect = true; + break; + } + bone_idx = skeleton->get_bone_parent(bone_idx); + } + if (!detect) { + is_appropriate = false; + } + bone_idx = -1; + } + if (right_shoulder != -1) { + bone_idx = skeleton->get_bone_parent(right_shoulder); + bool detect = false; + while (bone_idx != hips && bone_idx >= 0) { + if (bone_idx == chest_or_upper_chest) { + detect = true; + break; + } + bone_idx = skeleton->get_bone_parent(bone_idx); + } + if (!detect) { + is_appropriate = false; + } + bone_idx = -1; + } + if (!is_appropriate) { + if (skeleton->get_bone_parent(left_shoulder) == skeleton->get_bone_parent(right_shoulder)) { + chest_or_upper_chest = skeleton->get_bone_parent(left_shoulder); + } else { + chest_or_upper_chest = -1; + } + } + if (chest_or_upper_chest == -1) { + WARN_PRINT("Auto Mapping couldn't guess Chest or UpperChest. Abort auto mapping."); + return; // Will be not able to guess Spines. + } + + // 9. Guess Spines + bone_idx = skeleton->get_bone_parent(chest_or_upper_chest); + while (bone_idx != hips && bone_idx >= 0) { + search_path.push_back(bone_idx); + bone_idx = skeleton->get_bone_parent(bone_idx); + } + search_path.reverse(); + if (search_path.size() == 0) { + p_bone_map->_set_skeleton_bone_name("Spine", skeleton->get_bone_name(chest_or_upper_chest)); // Maybe chibi model...? + } else if (search_path.size() == 1) { + p_bone_map->_set_skeleton_bone_name("Spine", skeleton->get_bone_name(search_path[0])); + p_bone_map->_set_skeleton_bone_name("Chest", skeleton->get_bone_name(chest_or_upper_chest)); + } else if (search_path.size() >= 2) { + p_bone_map->_set_skeleton_bone_name("Spine", skeleton->get_bone_name(search_path[0])); + p_bone_map->_set_skeleton_bone_name("Chest", skeleton->get_bone_name(search_path[search_path.size() - 1])); // Probably UppeChest's parent is appropriate. + p_bone_map->_set_skeleton_bone_name("UpperChest", skeleton->get_bone_name(chest_or_upper_chest)); + } + bone_idx = -1; + search_path.clear(); + + WARN_PRINT("Finish auto mapping."); +} +#endif // MODULE_REGEX_ENABLED + void BoneMapper::_value_changed(const String &p_property, Variant p_value, const String &p_name, bool p_changing) { set(p_property, p_value); recreate_editor(); } +void BoneMapper::_profile_changed(const String &p_property, Variant p_value, const String &p_name, bool p_changing) { + bone_map->set(p_property, p_value); + + // Run auto mapping when setting SkeletonProfileHumanoid by GUI Editor. + Ref<SkeletonProfile> profile = bone_map->get_profile(); + if (profile.is_valid()) { + SkeletonProfileHumanoid *hmn = Object::cast_to<SkeletonProfileHumanoid>(profile.ptr()); + if (hmn) { +#ifdef MODULE_REGEX_ENABLED + _run_auto_mapping(); +#endif // MODULE_REGEX_ENABLED + } + } +} + void BoneMapper::_bind_methods() { ClassDB::bind_method(D_METHOD("set_current_group_idx", "current_group_idx"), &BoneMapper::set_current_group_idx); ClassDB::bind_method(D_METHOD("get_current_group_idx"), &BoneMapper::get_current_group_idx); @@ -444,10 +1337,6 @@ void BoneMapEditor::_notification(int p_what) { create_editors(); } break; case NOTIFICATION_EXIT_TREE: { - if (bone_mapper) { - remove_child(bone_mapper); - bone_mapper->queue_delete(); - } skeleton = nullptr; } break; } diff --git a/editor/plugins/bone_map_editor_plugin.h b/editor/plugins/bone_map_editor_plugin.h index 339547ea10..0541ce6eac 100644 --- a/editor/plugins/bone_map_editor_plugin.h +++ b/editor/plugins/bone_map_editor_plugin.h @@ -34,6 +34,12 @@ #include "editor/editor_node.h" #include "editor/editor_plugin.h" #include "editor/editor_properties.h" + +#include "modules/modules_enabled.gen.h" // For regex. +#ifdef MODULE_REGEX_ENABLED +#include "modules/regex/regex.h" +#endif + #include "scene/3d/skeleton_3d.h" #include "scene/gui/color_rect.h" #include "scene/gui/dialogs.h" @@ -79,12 +85,13 @@ class BoneMapperItem : public VBoxContainer { int button_id = -1; StringName profile_bone_name; - PackedStringArray skeleton_bone_names; Ref<BoneMap> bone_map; - EditorPropertyTextEnum *skeleton_bone_selector; + EditorPropertyText *skeleton_bone_selector; + Button *picker_button; void _update_property(); + void _open_picker(); protected: void _notification(int p_what); @@ -95,20 +102,49 @@ protected: public: void assign_button_id(int p_button_id); - BoneMapperItem(Ref<BoneMap> &p_bone_map, PackedStringArray p_skeleton_bone_names, const StringName &p_profile_bone_name = StringName()); + BoneMapperItem(Ref<BoneMap> &p_bone_map, const StringName &p_profile_bone_name = StringName()); ~BoneMapperItem(); }; +class BonePicker : public AcceptDialog { + GDCLASS(BonePicker, AcceptDialog); + + Skeleton3D *skeleton = nullptr; + Tree *bones = nullptr; + +public: + void popup_bones_tree(const Size2i &p_minsize = Size2i()); + bool has_selected_bone(); + StringName get_selected_bone(); + +protected: + void _notification(int p_what); + static void _bind_methods(); + + void _confirm(); + +private: + void create_editors(); + void create_bones_tree(Skeleton3D *p_skeleton); + +public: + BonePicker(Skeleton3D *p_skeleton); + ~BonePicker(); +}; + class BoneMapper : public VBoxContainer { GDCLASS(BoneMapper, VBoxContainer); Skeleton3D *skeleton; Ref<BoneMap> bone_map; + EditorPropertyResource *profile_selector; + Vector<BoneMapperItem *> bone_mapper_items; + Button *clear_mapping_button; + VBoxContainer *mapper_item_vbox; - HSeparator *separator; int current_group_idx = 0; int current_bone_idx = -1; @@ -126,10 +162,31 @@ class BoneMapper : public VBoxContainer { void update_group_idx(); void _update_state(); + /* Bone picker */ + BonePicker *picker = nullptr; + StringName picker_key_name; + void _pick_bone(const StringName &p_bone_name); + void _apply_picker_selection(); + void _clear_mapping_current_group(); + +#ifdef MODULE_REGEX_ENABLED + /* For auto mapping */ + enum BoneSegregation { + BONE_SEGREGATION_NONE, + BONE_SEGREGATION_LEFT, + BONE_SEGREGATION_RIGHT + }; + int search_bone_by_name(Skeleton3D *p_skeleton, Vector<String> p_picklist, BoneSegregation p_segregation = BONE_SEGREGATION_NONE, int p_parent = -1, int p_child = -1, int p_children_count = -1); + BoneSegregation guess_bone_segregation(String p_bone_name); + void auto_mapping_process(Ref<BoneMap> &p_bone_map); + void _run_auto_mapping(); +#endif // MODULE_REGEX_ENABLED + protected: void _notification(int p_what); static void _bind_methods(); virtual void _value_changed(const String &p_property, Variant p_value, const String &p_name, bool p_changing); + virtual void _profile_changed(const String &p_property, Variant p_value, const String &p_name, bool p_changing); public: void set_current_group_idx(int p_group_idx); diff --git a/editor/plugins/node_3d_editor_plugin.cpp b/editor/plugins/node_3d_editor_plugin.cpp index 2798f3d93e..5aa1046166 100644 --- a/editor/plugins/node_3d_editor_plugin.cpp +++ b/editor/plugins/node_3d_editor_plugin.cpp @@ -46,6 +46,7 @@ #include "editor/scene_tree_dock.h" #include "scene/3d/camera_3d.h" #include "scene/3d/collision_shape_3d.h" +#include "scene/3d/decal.h" #include "scene/3d/light_3d.h" #include "scene/3d/mesh_instance_3d.h" #include "scene/3d/physics_body_3d.h" @@ -2972,6 +2973,13 @@ void Node3DEditorViewport::_menu_option(int p_option) { xform.scale_basis(sp->get_scale()); } + if (Object::cast_to<Decal>(E)) { + // Adjust rotation to match Decal's default orientation. + // This makes the decal "look" in the same direction as the camera, + // rather than pointing down relative to the camera orientation. + xform.basis.rotate_local(Vector3(1, 0, 0), Math_TAU * 0.25); + } + undo_redo->add_do_method(sp, "set_global_transform", xform); undo_redo->add_undo_method(sp, "set_global_transform", sp->get_global_gizmo_transform()); } @@ -2999,7 +3007,16 @@ void Node3DEditorViewport::_menu_option(int p_option) { continue; } - undo_redo->add_do_method(sp, "set_rotation", camera_transform.basis.get_euler_normalized()); + Basis basis = camera_transform.basis; + + if (Object::cast_to<Decal>(E)) { + // Adjust rotation to match Decal's default orientation. + // This makes the decal "look" in the same direction as the camera, + // rather than pointing down relative to the camera orientation. + basis.rotate_local(Vector3(1, 0, 0), Math_TAU * 0.25); + } + + undo_redo->add_do_method(sp, "set_rotation", basis.get_euler_normalized()); undo_redo->add_undo_method(sp, "set_rotation", sp->get_rotation()); } undo_redo->commit_action(); @@ -4113,6 +4130,7 @@ bool Node3DEditorViewport::can_drop_data_fw(const Point2 &p_point, const Variant continue; } Ref<PackedScene> scn = res; + Ref<Mesh> mesh = res; Ref<Material> mat = res; Ref<Texture2D> tex = res; if (scn.is_valid()) { @@ -4131,6 +4149,8 @@ bool Node3DEditorViewport::can_drop_data_fw(const Point2 &p_point, const Variant spatial_editor->set_preview_material(mat); break; + } else if (mesh.is_valid()) { + // Let the mesh pass. } else if (tex.is_valid()) { Ref<StandardMaterial3D> new_mat = memnew(StandardMaterial3D); new_mat->set_texture(BaseMaterial3D::TEXTURE_ALBEDO, tex); @@ -7615,7 +7635,7 @@ void Node3DEditor::_load_default_preview_settings() { environ_tonemap_button->set_pressed(true); environ_ao_button->set_pressed(false); environ_gi_button->set_pressed(false); - sun_max_distance->set_value(250); + sun_max_distance->set_value(100); sun_color->set_pick_color(Color(1, 1, 1)); sun_energy->set_value(1.0); diff --git a/editor/plugins/skeleton_3d_editor_plugin.cpp b/editor/plugins/skeleton_3d_editor_plugin.cpp index 1e4ef217f0..c25f2bb25c 100644 --- a/editor/plugins/skeleton_3d_editor_plugin.cpp +++ b/editor/plugins/skeleton_3d_editor_plugin.cpp @@ -31,7 +31,6 @@ #include "skeleton_3d_editor_plugin.h" #include "core/io/resource_saver.h" -#include "editor/editor_file_dialog.h" #include "editor/editor_node.h" #include "editor/editor_properties.h" #include "editor/editor_scale.h" diff --git a/editor/plugins/skeleton_3d_editor_plugin.h b/editor/plugins/skeleton_3d_editor_plugin.h index f51d4e60e8..9747ed8374 100644 --- a/editor/plugins/skeleton_3d_editor_plugin.h +++ b/editor/plugins/skeleton_3d_editor_plugin.h @@ -31,6 +31,7 @@ #ifndef SKELETON_3D_EDITOR_PLUGIN_H #define SKELETON_3D_EDITOR_PLUGIN_H +#include "editor/editor_file_dialog.h" #include "editor/editor_plugin.h" #include "editor/editor_properties.h" #include "node_3d_editor_plugin.h" diff --git a/editor/project_manager.cpp b/editor/project_manager.cpp index 46eb7ac17c..cce71d9508 100644 --- a/editor/project_manager.cpp +++ b/editor/project_manager.cpp @@ -484,12 +484,20 @@ private: project_features.sort(); initial_settings["application/config/features"] = project_features; initial_settings["application/config/name"] = project_name->get_text().strip_edges(); - initial_settings["application/config/icon"] = "res://icon.png"; + initial_settings["application/config/icon"] = "res://icon.svg"; if (ProjectSettings::get_singleton()->save_custom(dir.plus_file("project.godot"), initial_settings, Vector<String>(), false) != OK) { set_message(TTR("Couldn't create project.godot in project path."), MESSAGE_ERROR); } else { - ResourceSaver::save(create_unscaled_default_project_icon(), dir.plus_file("icon.png")); + // Store default project icon in SVG format. + Error err; + Ref<FileAccess> fa_icon = FileAccess::open(dir.plus_file("icon.svg"), FileAccess::WRITE, &err); + fa_icon->store_string(get_default_project_icon()); + + if (err != OK) { + set_message(TTR("Couldn't create icon.svg in project path."), MESSAGE_ERROR); + } + EditorVCSInterface::create_vcs_metadata_files(EditorVCSInterface::VCSMetadata(vcs_metadata_selection->get_selected()), dir); } } else if (mode == MODE_INSTALL) { diff --git a/editor/scene_tree_editor.cpp b/editor/scene_tree_editor.cpp index b977b012a8..00fd0c3aac 100644 --- a/editor/scene_tree_editor.cpp +++ b/editor/scene_tree_editor.cpp @@ -279,7 +279,19 @@ void SceneTreeEditor::_add_nodes(Node *p_node, TreeItem *p_parent) { warning_icon = SNAME("NodeWarnings4Plus"); } - item->add_button(0, get_theme_icon(warning_icon, SNAME("EditorIcons")), BUTTON_WARNING, false, TTR("Node configuration warning:") + "\n" + warning); + // Improve looks on tooltip, extra spacing on non-bullet point newlines. + const String bullet_point = String::utf8("• "); + int next_newline = 0; + while (next_newline != -1) { + next_newline = warning.find("\n", next_newline + 2); + if (warning.substr(next_newline + 1, bullet_point.length()) != bullet_point) { + warning = warning.insert(next_newline + 1, " "); + } + } + + String newline = (num_warnings == 1 ? "\n" : "\n\n"); + + item->add_button(0, get_theme_icon(warning_icon, SNAME("EditorIcons")), BUTTON_WARNING, false, TTR("Node configuration warning:") + newline + warning); } if (p_node->is_unique_name_in_owner()) { |