diff options
-rw-r--r-- | doc/classes/RichTextLabel.xml | 1 | ||||
-rw-r--r-- | doc/classes/Tween.xml | 3 | ||||
-rw-r--r-- | editor/plugins/sprite_frames_editor_plugin.cpp | 2 | ||||
-rw-r--r-- | modules/gdscript/gdscript_parser.cpp | 2 | ||||
-rw-r--r-- | scene/animation/easing_equations.h | 29 | ||||
-rw-r--r-- | scene/animation/tween.cpp | 2 | ||||
-rw-r--r-- | scene/animation/tween.h | 1 | ||||
-rw-r--r-- | scene/gui/color_picker.cpp | 108 | ||||
-rw-r--r-- | scene/gui/color_picker.h | 10 | ||||
-rw-r--r-- | scene/gui/rich_text_label.cpp | 38 | ||||
-rw-r--r-- | scene/gui/rich_text_label.h | 4 | ||||
-rw-r--r-- | scene/resources/curve.cpp | 4 | ||||
-rw-r--r-- | tests/scene/test_curve.h | 31 |
13 files changed, 181 insertions, 54 deletions
diff --git a/doc/classes/RichTextLabel.xml b/doc/classes/RichTextLabel.xml index c44f54749b..4a243a70ce 100644 --- a/doc/classes/RichTextLabel.xml +++ b/doc/classes/RichTextLabel.xml @@ -410,6 +410,7 @@ <param index="2" name="language" type="String" default="""" /> <param index="3" name="st_parser" type="int" enum="TextServer.StructuredTextParser" default="0" /> <param index="4" name="justification_flags" type="int" enum="TextServer.JustificationFlag" default="163" /> + <param index="5" name="tab_stops" type="PackedFloat32Array" default="PackedFloat32Array()" /> <description> Adds a [code][p][/code] tag to the tag stack. </description> diff --git a/doc/classes/Tween.xml b/doc/classes/Tween.xml index 7e313c3d4c..51efbd31d8 100644 --- a/doc/classes/Tween.xml +++ b/doc/classes/Tween.xml @@ -501,6 +501,9 @@ <constant name="TRANS_BACK" value="10" enum="TransitionType"> The animation is interpolated backing out at ends. </constant> + <constant name="TRANS_SPRING" value="11" enum="TransitionType"> + The animation is interpolated like a spring towards the end. + </constant> <constant name="EASE_IN" value="0" enum="EaseType"> The interpolation starts slowly and speeds up towards the end. </constant> diff --git a/editor/plugins/sprite_frames_editor_plugin.cpp b/editor/plugins/sprite_frames_editor_plugin.cpp index 9ff9b521d6..563398e512 100644 --- a/editor/plugins/sprite_frames_editor_plugin.cpp +++ b/editor/plugins/sprite_frames_editor_plugin.cpp @@ -1866,6 +1866,7 @@ SpriteFramesEditor::SpriteFramesEditor() { frame_list = memnew(ItemList); frame_list->set_v_size_flags(SIZE_EXPAND_FILL); frame_list->set_icon_mode(ItemList::ICON_MODE_TOP); + frame_list->set_texture_filter(TEXTURE_FILTER_NEAREST_WITH_MIPMAPS); frame_list->set_max_columns(0); frame_list->set_icon_mode(ItemList::ICON_MODE_TOP); @@ -1990,6 +1991,7 @@ SpriteFramesEditor::SpriteFramesEditor() { split_sheet_preview = memnew(TextureRect); split_sheet_preview->set_expand_mode(TextureRect::EXPAND_IGNORE_SIZE); + split_sheet_preview->set_texture_filter(TEXTURE_FILTER_NEAREST_WITH_MIPMAPS); split_sheet_preview->set_mouse_filter(MOUSE_FILTER_PASS); split_sheet_preview->connect("draw", callable_mp(this, &SpriteFramesEditor::_sheet_preview_draw)); split_sheet_preview->connect("gui_input", callable_mp(this, &SpriteFramesEditor::_sheet_preview_input)); diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp index 3bce258072..fdbd505975 100644 --- a/modules/gdscript/gdscript_parser.cpp +++ b/modules/gdscript/gdscript_parser.cpp @@ -4192,7 +4192,7 @@ String GDScriptParser::DataType::to_string() const { return class_type->fqcn; case SCRIPT: { if (is_meta_type) { - return script_type->get_class_name().operator String(); + return script_type != nullptr ? script_type->get_class_name().operator String() : ""; } String name = script_type != nullptr ? script_type->get_name() : ""; if (!name.is_empty()) { diff --git a/scene/animation/easing_equations.h b/scene/animation/easing_equations.h index 1b9c378b4f..9150191744 100644 --- a/scene/animation/easing_equations.h +++ b/scene/animation/easing_equations.h @@ -413,4 +413,33 @@ static real_t out_in(real_t t, real_t b, real_t c, real_t d) { } }; // namespace back +namespace spring { +static real_t out(real_t t, real_t b, real_t c, real_t d) { + t /= d; + real_t s = 1.0 - t; + t = (sin(t * Math_PI * (0.2 + 2.5 * t * t * t)) * pow(s, 2.2) + t) * (1.0 + (1.2 * s)); + return c * t + b; +} + +static real_t in(real_t t, real_t b, real_t c, real_t d) { + return c - out(d - t, 0, c, d) + b; +} + +static real_t in_out(real_t t, real_t b, real_t c, real_t d) { + if (t < d / 2) { + return in(t * 2, b, c / 2, d); + } + real_t h = c / 2; + return out(t * 2 - d, b + h, h, d); +} + +static real_t out_in(real_t t, real_t b, real_t c, real_t d) { + if (t < d / 2) { + return out(t * 2, b, c / 2, d); + } + real_t h = c / 2; + return in(t * 2 - d, b + h, h, d); +} +}; // namespace spring + #endif // EASING_EQUATIONS_H diff --git a/scene/animation/tween.cpp b/scene/animation/tween.cpp index 8266cfbd43..96e5da5a40 100644 --- a/scene/animation/tween.cpp +++ b/scene/animation/tween.cpp @@ -50,6 +50,7 @@ Tween::interpolater Tween::interpolaters[Tween::TRANS_MAX][Tween::EASE_MAX] = { { &circ::in, &circ::out, &circ::in_out, &circ::out_in }, { &bounce::in, &bounce::out, &bounce::in_out, &bounce::out_in }, { &back::in, &back::out, &back::in_out, &back::out_in }, + { &spring::in, &spring::out, &spring::in_out, &spring::out_in }, }; void Tweener::set_tween(const Ref<Tween> &p_tween) { @@ -483,6 +484,7 @@ void Tween::_bind_methods() { BIND_ENUM_CONSTANT(TRANS_CIRC); BIND_ENUM_CONSTANT(TRANS_BOUNCE); BIND_ENUM_CONSTANT(TRANS_BACK); + BIND_ENUM_CONSTANT(TRANS_SPRING); BIND_ENUM_CONSTANT(EASE_IN); BIND_ENUM_CONSTANT(EASE_OUT); diff --git a/scene/animation/tween.h b/scene/animation/tween.h index 67823da040..10c7a272ef 100644 --- a/scene/animation/tween.h +++ b/scene/animation/tween.h @@ -87,6 +87,7 @@ public: TRANS_CIRC, TRANS_BOUNCE, TRANS_BACK, + TRANS_SPRING, TRANS_MAX }; diff --git a/scene/gui/color_picker.cpp b/scene/gui/color_picker.cpp index 5e861ba45d..2a03786471 100644 --- a/scene/gui/color_picker.cpp +++ b/scene/gui/color_picker.cpp @@ -109,6 +109,13 @@ void ColorPicker::_notification(int p_what) { picker_window->hide(); } } break; + + case NOTIFICATION_INTERNAL_PROCESS: { + if (!is_picking_color) { + return; + } + set_pick_color(DisplayServer::get_singleton()->screen_get_pixel(DisplayServer::get_singleton()->mouse_get_position())); + } } } @@ -608,13 +615,14 @@ void ColorPicker::_update_presets() { #ifdef TOOLS_ENABLED if (editor_settings) { - // Only load preset buttons when the only child is the add-preset button. - if (preset_container->get_child_count() == 1) { - for (int i = 0; i < preset_cache.size(); i++) { - _add_preset_button(preset_size, preset_cache[i]); - } - _notification(NOTIFICATION_VISIBILITY_CHANGED); + // Rebuild swatch color buttons, keeping the add-preset button in the first position. + for (int i = 1; i < preset_container->get_child_count(); i++) { + preset_container->get_child(i)->queue_free(); } + for (int i = 0; i < preset_cache.size(); i++) { + _add_preset_button(preset_size, preset_cache[i]); + } + _notification(NOTIFICATION_VISIBILITY_CHANGED); } #endif } @@ -1421,30 +1429,6 @@ void ColorPicker::_recent_preset_pressed(const bool p_pressed, ColorPresetButton emit_signal(SNAME("color_changed"), p_preset->get_preset_color()); } -void ColorPicker::_picker_texture_input(const Ref<InputEvent> &p_event) { - if (!is_inside_tree()) { - return; - } - - Ref<InputEventMouseButton> bev = p_event; - if (bev.is_valid() && bev->get_button_index() == MouseButton::LEFT && !bev->is_pressed()) { - set_pick_color(picker_color); - emit_signal(SNAME("color_changed"), color); - picker_window->hide(); - } - - Ref<InputEventMouseMotion> mev = p_event; - if (mev.is_valid()) { - Ref<Image> img = picker_texture_rect->get_texture()->get_image(); - if (img.is_valid() && !img->is_empty()) { - Vector2 ofs = mev->get_position(); - picker_color = img->get_pixel(ofs.x, ofs.y); - picker_preview_style_box->set_bg_color(picker_color); - picker_preview_label->set_self_modulate(picker_color.get_luminance() < 0.5 ? Color(1.0f, 1.0f, 1.0f) : Color(0.0f, 0.0f, 0.0f)); - } - } -} - void ColorPicker::_text_changed(const String &) { text_changed = true; } @@ -1455,6 +1439,34 @@ void ColorPicker::_add_preset_pressed() { } void ColorPicker::_pick_button_pressed() { + is_picking_color = true; + set_process_internal(true); + + if (!picker_window) { + picker_window = memnew(Popup); + picker_window->set_size(Vector2i(1, 1)); + picker_window->connect("visibility_changed", callable_mp(this, &ColorPicker::_pick_finished)); + add_child(picker_window); + } + picker_window->popup(); +} + +void ColorPicker::_pick_finished() { + if (picker_window->is_visible()) { + return; + } + + if (Input::get_singleton()->is_key_pressed(Key::ESCAPE)) { + set_pick_color(old_color); + } else { + emit_signal(SNAME("color_changed"), color); + } + is_picking_color = false; + set_process_internal(false); + picker_window->hide(); +} + +void ColorPicker::_pick_button_pressed_legacy() { if (!is_inside_tree()) { return; } @@ -1469,7 +1481,7 @@ void ColorPicker::_pick_button_pressed() { picker_texture_rect->set_anchors_preset(Control::PRESET_FULL_RECT); picker_window->add_child(picker_texture_rect); picker_texture_rect->set_default_cursor_shape(CURSOR_POINTING_HAND); - picker_texture_rect->connect(SNAME("gui_input"), callable_mp(this, &ColorPicker::_picker_texture_input)); + picker_texture_rect->connect("gui_input", callable_mp(this, &ColorPicker::_picker_texture_input)); picker_preview = memnew(Panel); picker_preview->set_anchors_preset(Control::PRESET_CENTER_TOP); @@ -1529,6 +1541,30 @@ void ColorPicker::_pick_button_pressed() { picker_window->popup(); } +void ColorPicker::_picker_texture_input(const Ref<InputEvent> &p_event) { + if (!is_inside_tree()) { + return; + } + + Ref<InputEventMouseButton> bev = p_event; + if (bev.is_valid() && bev->get_button_index() == MouseButton::LEFT && !bev->is_pressed()) { + set_pick_color(picker_color); + emit_signal(SNAME("color_changed"), color); + picker_window->hide(); + } + + Ref<InputEventMouseMotion> mev = p_event; + if (mev.is_valid()) { + Ref<Image> img = picker_texture_rect->get_texture()->get_image(); + if (img.is_valid() && !img->is_empty()) { + Vector2 ofs = mev->get_position(); + picker_color = img->get_pixel(ofs.x, ofs.y); + picker_preview_style_box->set_bg_color(picker_color); + picker_preview_label->set_self_modulate(picker_color.get_luminance() < 0.5 ? Color(1.0f, 1.0f, 1.0f) : Color(0.0f, 0.0f, 0.0f)); + } + } +} + void ColorPicker::_html_focus_exit() { if (c_text->is_menu_visible()) { return; @@ -1692,8 +1728,14 @@ ColorPicker::ColorPicker() { btn_pick = memnew(Button); sample_hbc->add_child(btn_pick); - btn_pick->set_tooltip_text(RTR("Pick a color from the application window.")); - btn_pick->connect(SNAME("pressed"), callable_mp(this, &ColorPicker::_pick_button_pressed)); + if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_SCREEN_CAPTURE)) { + btn_pick->set_tooltip_text(RTR("Pick a color from the screen.")); + btn_pick->connect(SNAME("pressed"), callable_mp(this, &ColorPicker::_pick_button_pressed)); + } else { + // On unsupported platforms, use a legacy method for color picking. + btn_pick->set_tooltip_text(RTR("Pick a color from the application window.")); + btn_pick->connect(SNAME("pressed"), callable_mp(this, &ColorPicker::_pick_button_pressed_legacy)); + } sample = memnew(TextureRect); sample_hbc->add_child(sample); diff --git a/scene/gui/color_picker.h b/scene/gui/color_picker.h index 018ae10955..711a371688 100644 --- a/scene/gui/color_picker.h +++ b/scene/gui/color_picker.h @@ -122,11 +122,13 @@ private: Vector<ColorMode *> modes; Popup *picker_window = nullptr; + // Legacy color picking. TextureRect *picker_texture_rect = nullptr; Panel *picker_preview = nullptr; Label *picker_preview_label = nullptr; Ref<StyleBoxFlat> picker_preview_style_box; Color picker_color; + Control *uv_edit = nullptr; Control *w_edit = nullptr; AspectRatioContainer *wheel_edit = nullptr; @@ -183,6 +185,7 @@ private: Color color; Color old_color; + bool is_picking_color = false; bool display_old_color = false; bool deferred_mode_enabled = false; @@ -259,11 +262,14 @@ private: void _line_edit_input(const Ref<InputEvent> &p_event); void _preset_input(const Ref<InputEvent> &p_event, const Color &p_color); void _recent_preset_pressed(const bool pressed, ColorPresetButton *p_preset); - void _picker_texture_input(const Ref<InputEvent> &p_event); void _text_changed(const String &p_new_text); void _add_preset_pressed(); - void _pick_button_pressed(); void _html_focus_exit(); + void _pick_button_pressed(); + void _pick_finished(); + // Legacy color picking. + void _pick_button_pressed_legacy(); + void _picker_texture_input(const Ref<InputEvent> &p_event); inline int _get_preset_size(); void _add_preset_button(int p_size, const Color &p_color); diff --git a/scene/gui/rich_text_label.cpp b/scene/gui/rich_text_label.cpp index 185db1b972..e12180f2f9 100644 --- a/scene/gui/rich_text_label.cpp +++ b/scene/gui/rich_text_label.cpp @@ -277,7 +277,10 @@ float RichTextLabel::_resize_line(ItemFrame *p_frame, int p_line, const Ref<Font l.offset.x = _find_margin(l.from, p_base_font, p_base_font_size); l.text_buf->set_width(p_width - l.offset.x); - if (tab_size > 0) { // Align inline tabs. + PackedFloat32Array tab_stops = _find_tab_stops(l.from); + if (!tab_stops.is_empty()) { + l.text_buf->tab_align(tab_stops); + } else if (tab_size > 0) { // Align inline tabs. Vector<float> tabs; tabs.push_back(tab_size * p_base_font->get_char_size(' ', p_base_font_size).width); l.text_buf->tab_align(tabs); @@ -481,7 +484,10 @@ float RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font> l.text_buf->set_alignment(_find_alignment(l.from)); l.text_buf->set_direction(_find_direction(l.from)); - if (tab_size > 0) { // Align inline tabs. + PackedFloat32Array tab_stops = _find_tab_stops(l.from); + if (!tab_stops.is_empty()) { + l.text_buf->tab_align(tab_stops); + } else if (tab_size > 0) { // Align inline tabs. Vector<float> tabs; tabs.push_back(tab_size * p_base_font->get_char_size(' ', p_base_font_size).width); l.text_buf->tab_align(tabs); @@ -2468,6 +2474,21 @@ BitField<TextServer::JustificationFlag> RichTextLabel::_find_jst_flags(Item *p_i return default_jst_flags; } +PackedFloat32Array RichTextLabel::_find_tab_stops(Item *p_item) { + Item *item = p_item; + + while (item) { + if (item->type == ITEM_PARAGRAPH) { + ItemParagraph *p = static_cast<ItemParagraph *>(item); + return p->tab_stops; + } + + item = item->parent; + } + + return PackedFloat32Array(); +} + HorizontalAlignment RichTextLabel::_find_alignment(Item *p_item) { Item *item = p_item; @@ -3312,7 +3333,7 @@ void RichTextLabel::push_strikethrough() { _add_item(item, true); } -void RichTextLabel::push_paragraph(HorizontalAlignment p_alignment, Control::TextDirection p_direction, const String &p_language, TextServer::StructuredTextParser p_st_parser, BitField<TextServer::JustificationFlag> p_jst_flags) { +void RichTextLabel::push_paragraph(HorizontalAlignment p_alignment, Control::TextDirection p_direction, const String &p_language, TextServer::StructuredTextParser p_st_parser, BitField<TextServer::JustificationFlag> p_jst_flags, const PackedFloat32Array &p_tab_stops) { _stop_thread(); MutexLock data_lock(data_mutex); @@ -3324,6 +3345,7 @@ void RichTextLabel::push_paragraph(HorizontalAlignment p_alignment, Control::Tex item->language = p_language; item->st_parser = p_st_parser; item->jst_flags = p_jst_flags; + item->tab_stops = p_tab_stops; _add_item(item, true, true); } @@ -4094,6 +4116,7 @@ void RichTextLabel::append_text(const String &p_bbcode) { HorizontalAlignment alignment = HORIZONTAL_ALIGNMENT_LEFT; Control::TextDirection dir = Control::TEXT_DIRECTION_INHERITED; String lang; + PackedFloat32Array tab_stops; TextServer::StructuredTextParser st_parser_type = TextServer::STRUCTURED_TEXT_DEFAULT; BitField<TextServer::JustificationFlag> jst_flags = default_jst_flags; for (int i = 0; i < subtag.size(); i++) { @@ -4118,6 +4141,11 @@ void RichTextLabel::append_text(const String &p_bbcode) { jst_flags.set_flag(TextServer::JUSTIFICATION_DO_NOT_SKIP_SINGLE_LINE); } } + } else if (subtag_a[0] == "tab_stops") { + Vector<String> splitters; + splitters.push_back(","); + splitters.push_back(";"); + tab_stops = subtag_a[1].split_floats_mk(splitters); } else if (subtag_a[0] == "align") { if (subtag_a[1] == "l" || subtag_a[1] == "left") { alignment = HORIZONTAL_ALIGNMENT_LEFT; @@ -4157,7 +4185,7 @@ void RichTextLabel::append_text(const String &p_bbcode) { } } } - push_paragraph(alignment, dir, lang, st_parser_type, jst_flags); + push_paragraph(alignment, dir, lang, st_parser_type, jst_flags, tab_stops); pos = brk_end + 1; tag_stack.push_front("p"); } else if (tag == "url") { @@ -5406,7 +5434,7 @@ void RichTextLabel::_bind_methods() { ClassDB::bind_method(D_METHOD("push_color", "color"), &RichTextLabel::push_color); ClassDB::bind_method(D_METHOD("push_outline_size", "outline_size"), &RichTextLabel::push_outline_size); ClassDB::bind_method(D_METHOD("push_outline_color", "color"), &RichTextLabel::push_outline_color); - ClassDB::bind_method(D_METHOD("push_paragraph", "alignment", "base_direction", "language", "st_parser", "justification_flags"), &RichTextLabel::push_paragraph, DEFVAL(TextServer::DIRECTION_AUTO), DEFVAL(""), DEFVAL(TextServer::STRUCTURED_TEXT_DEFAULT), DEFVAL(TextServer::JUSTIFICATION_WORD_BOUND | TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_SKIP_LAST_LINE | TextServer::JUSTIFICATION_DO_NOT_SKIP_SINGLE_LINE)); + ClassDB::bind_method(D_METHOD("push_paragraph", "alignment", "base_direction", "language", "st_parser", "justification_flags", "tab_stops"), &RichTextLabel::push_paragraph, DEFVAL(TextServer::DIRECTION_AUTO), DEFVAL(""), DEFVAL(TextServer::STRUCTURED_TEXT_DEFAULT), DEFVAL(TextServer::JUSTIFICATION_WORD_BOUND | TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_SKIP_LAST_LINE | TextServer::JUSTIFICATION_DO_NOT_SKIP_SINGLE_LINE), DEFVAL(PackedFloat32Array())); ClassDB::bind_method(D_METHOD("push_indent", "level"), &RichTextLabel::push_indent); ClassDB::bind_method(D_METHOD("push_list", "level", "type", "capitalize", "bullet"), &RichTextLabel::push_list, DEFVAL(String::utf8("•"))); ClassDB::bind_method(D_METHOD("push_meta", "data"), &RichTextLabel::push_meta); diff --git a/scene/gui/rich_text_label.h b/scene/gui/rich_text_label.h index 3e3413d47a..f6cc9b2ac4 100644 --- a/scene/gui/rich_text_label.h +++ b/scene/gui/rich_text_label.h @@ -241,6 +241,7 @@ private: Control::TextDirection direction = Control::TEXT_DIRECTION_AUTO; TextServer::StructuredTextParser st_parser = TextServer::STRUCTURED_TEXT_DEFAULT; BitField<TextServer::JustificationFlag> jst_flags = TextServer::JUSTIFICATION_WORD_BOUND | TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_SKIP_LAST_LINE | TextServer::JUSTIFICATION_DO_NOT_SKIP_SINGLE_LINE; + PackedFloat32Array tab_stops; ItemParagraph() { type = ITEM_PARAGRAPH; } }; @@ -493,6 +494,7 @@ private: ItemDropcap *_find_dc_item(Item *p_item); int _find_list(Item *p_item, Vector<int> &r_index, Vector<ItemList *> &r_list); int _find_margin(Item *p_item, const Ref<Font> &p_base_font, int p_base_font_size); + PackedFloat32Array _find_tab_stops(Item *p_item); HorizontalAlignment _find_alignment(Item *p_item); BitField<TextServer::JustificationFlag> _find_jst_flags(Item *p_item); TextServer::Direction _find_direction(Item *p_item); @@ -596,7 +598,7 @@ public: void push_outline_color(const Color &p_color); void push_underline(); void push_strikethrough(); - void push_paragraph(HorizontalAlignment p_alignment, Control::TextDirection p_direction = Control::TEXT_DIRECTION_INHERITED, const String &p_language = "", TextServer::StructuredTextParser p_st_parser = TextServer::STRUCTURED_TEXT_DEFAULT, BitField<TextServer::JustificationFlag> p_jst_flags = TextServer::JUSTIFICATION_WORD_BOUND | TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_SKIP_LAST_LINE | TextServer::JUSTIFICATION_DO_NOT_SKIP_SINGLE_LINE); + void push_paragraph(HorizontalAlignment p_alignment, Control::TextDirection p_direction = Control::TEXT_DIRECTION_INHERITED, const String &p_language = "", TextServer::StructuredTextParser p_st_parser = TextServer::STRUCTURED_TEXT_DEFAULT, BitField<TextServer::JustificationFlag> p_jst_flags = TextServer::JUSTIFICATION_WORD_BOUND | TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_SKIP_LAST_LINE | TextServer::JUSTIFICATION_DO_NOT_SKIP_SINGLE_LINE, const PackedFloat32Array &p_tab_stops = PackedFloat32Array()); void push_indent(int p_level); void push_list(int p_level, ListType p_list, bool p_capitalize, const String &p_bullet = String::utf8("•")); void push_meta(const Variant &p_meta); diff --git a/scene/resources/curve.cpp b/scene/resources/curve.cpp index 97ab91e26d..a0bd22c79b 100644 --- a/scene/resources/curve.cpp +++ b/scene/resources/curve.cpp @@ -452,7 +452,7 @@ void Curve::bake() { _baked_cache.resize(_bake_resolution); for (int i = 1; i < _bake_resolution - 1; ++i) { - real_t x = i / static_cast<real_t>(_bake_resolution); + real_t x = i / static_cast<real_t>(_bake_resolution - 1); real_t y = sample(x); _baked_cache.write[i] = y; } @@ -489,7 +489,7 @@ real_t Curve::sample_baked(real_t p_offset) const { } // Get interpolation index - real_t fi = p_offset * _baked_cache.size(); + real_t fi = p_offset * (_baked_cache.size() - 1); int i = Math::floor(fi); if (i < 0) { i = 0; diff --git a/tests/scene/test_curve.h b/tests/scene/test_curve.h index bfa37e8f69..d67550f9f7 100644 --- a/tests/scene/test_curve.h +++ b/tests/scene/test_curve.h @@ -62,6 +62,7 @@ TEST_CASE("[Curve] Custom curve with free tangents") { curve->add_point(Vector2(0.25, 1)); curve->add_point(Vector2(0.5, 0)); curve->add_point(Vector2(0.75, 1)); + curve->set_bake_resolution(11); CHECK_MESSAGE( Math::is_zero_approx(curve->get_point_left_tangent(0)), @@ -82,41 +83,41 @@ TEST_CASE("[Curve] Custom curve with free tangents") { CHECK_MESSAGE( Math::is_zero_approx(curve->sample(-0.1)), - "Custom free curve should return the expected value at offset 0.1."); + "Custom free curve should return the expected value at offset -0.1."); CHECK_MESSAGE( curve->sample(0.1) == doctest::Approx((real_t)0.352), "Custom free curve should return the expected value at offset 0.1."); CHECK_MESSAGE( curve->sample(0.4) == doctest::Approx((real_t)0.352), - "Custom free curve should return the expected value at offset 0.1."); + "Custom free curve should return the expected value at offset 0.4."); CHECK_MESSAGE( curve->sample(0.7) == doctest::Approx((real_t)0.896), - "Custom free curve should return the expected value at offset 0.1."); + "Custom free curve should return the expected value at offset 0.7."); CHECK_MESSAGE( curve->sample(1) == doctest::Approx(1), - "Custom free curve should return the expected value at offset 0.1."); + "Custom free curve should return the expected value at offset 1."); CHECK_MESSAGE( curve->sample(2) == doctest::Approx(1), - "Custom free curve should return the expected value at offset 0.1."); + "Custom free curve should return the expected value at offset 2."); CHECK_MESSAGE( Math::is_zero_approx(curve->sample_baked(-0.1)), - "Custom free curve should return the expected baked value at offset 0.1."); + "Custom free curve should return the expected baked value at offset -0.1."); CHECK_MESSAGE( curve->sample_baked(0.1) == doctest::Approx((real_t)0.352), "Custom free curve should return the expected baked value at offset 0.1."); CHECK_MESSAGE( curve->sample_baked(0.4) == doctest::Approx((real_t)0.352), - "Custom free curve should return the expected baked value at offset 0.1."); + "Custom free curve should return the expected baked value at offset 0.4."); CHECK_MESSAGE( curve->sample_baked(0.7) == doctest::Approx((real_t)0.896), - "Custom free curve should return the expected baked value at offset 0.1."); + "Custom free curve should return the expected baked value at offset 0.7."); CHECK_MESSAGE( curve->sample_baked(1) == doctest::Approx(1), - "Custom free curve should return the expected baked value at offset 0.1."); + "Custom free curve should return the expected baked value at offset 1."); CHECK_MESSAGE( curve->sample_baked(2) == doctest::Approx(1), - "Custom free curve should return the expected baked value at offset 0.1."); + "Custom free curve should return the expected baked value at offset 2."); curve->remove_point(1); CHECK_MESSAGE( @@ -218,6 +219,16 @@ TEST_CASE("[Curve] Custom curve with linear tangents") { "Custom free curve should return the expected baked value at offset 0.7 after removing point at invalid index 10."); } +TEST_CASE("[Curve] Straight line offset test") { + Ref<Curve> curve = memnew(Curve); + curve->add_point(Vector2(0, 0)); + curve->add_point(Vector2(1, 1)); + + CHECK_MESSAGE( + curve->sample_baked(1.0 - (0.5 / curve->get_bake_resolution())) != curve->sample_baked(1), + "Straight line curve should return different baked values at offset 1 vs offset (1 - 0.5 / bake resolution) ."); +} + TEST_CASE("[Curve2D] Linear sampling should return exact value") { Ref<Curve2D> curve = memnew(Curve2D); real_t len = 2048.0; |