summaryrefslogtreecommitdiff
path: root/scene
diff options
context:
space:
mode:
Diffstat (limited to 'scene')
-rw-r--r--scene/2d/path_2d.cpp45
-rw-r--r--scene/2d/shape_cast_2d.cpp6
-rw-r--r--scene/2d/shape_cast_2d.h1
-rw-r--r--scene/3d/shape_cast_3d.cpp6
-rw-r--r--scene/3d/shape_cast_3d.h1
-rw-r--r--scene/3d/sprite_3d.cpp5
-rw-r--r--scene/gui/base_button.cpp19
-rw-r--r--scene/gui/base_button.h5
-rw-r--r--scene/gui/code_edit.cpp19
-rw-r--r--scene/gui/code_edit.h1
-rw-r--r--scene/gui/flow_container.cpp41
-rw-r--r--scene/gui/flow_container.h13
-rw-r--r--scene/gui/scroll_container.cpp107
-rw-r--r--scene/main/canvas_item.cpp31
-rw-r--r--scene/main/canvas_item.h7
-rw-r--r--scene/main/canvas_layer.cpp1
-rw-r--r--scene/main/multiplayer_peer.cpp3
-rw-r--r--scene/main/viewport.cpp36
-rw-r--r--scene/main/viewport.h8
-rw-r--r--scene/resources/curve.cpp41
-rw-r--r--scene/resources/curve.h1
21 files changed, 308 insertions, 89 deletions
diff --git a/scene/2d/path_2d.cpp b/scene/2d/path_2d.cpp
index c1044fdf5b..b5945a4562 100644
--- a/scene/2d/path_2d.cpp
+++ b/scene/2d/path_2d.cpp
@@ -175,51 +175,18 @@ void PathFollow2D::_update_transform() {
if (path_length == 0) {
return;
}
- Vector2 pos = c->sample_baked(progress, cubic);
if (rotates) {
- real_t ahead = progress + lookahead;
-
- if (loop && ahead >= path_length) {
- // If our lookahead will loop, we need to check if the path is closed.
- int point_count = c->get_point_count();
- if (point_count > 0) {
- Vector2 start_point = c->get_point_position(0);
- Vector2 end_point = c->get_point_position(point_count - 1);
- if (start_point == end_point) {
- // Since the path is closed we want to 'smooth off'
- // the corner at the start/end.
- // So we wrap the lookahead back round.
- ahead = Math::fmod(ahead, path_length);
- }
- }
- }
-
- Vector2 ahead_pos = c->sample_baked(ahead, cubic);
-
- Vector2 tangent_to_curve;
- if (ahead_pos == pos) {
- // This will happen at the end of non-looping or non-closed paths.
- // We'll try a look behind instead, in order to get a meaningful angle.
- tangent_to_curve =
- (pos - c->sample_baked(progress - lookahead, cubic)).normalized();
- } else {
- tangent_to_curve = (ahead_pos - pos).normalized();
- }
-
- Vector2 normal_of_curve = -tangent_to_curve.orthogonal();
-
- pos += tangent_to_curve * h_offset;
- pos += normal_of_curve * v_offset;
-
- set_rotation(tangent_to_curve.angle());
-
+ Transform2D xform = c->sample_baked_with_rotation(progress, cubic, loop, lookahead);
+ xform.translate_local(v_offset, h_offset);
+ set_rotation(xform[1].angle());
+ set_position(xform[2]);
} else {
+ Vector2 pos = c->sample_baked(progress, cubic);
pos.x += h_offset;
pos.y += v_offset;
+ set_position(pos);
}
-
- set_position(pos);
}
void PathFollow2D::_notification(int p_what) {
diff --git a/scene/2d/shape_cast_2d.cpp b/scene/2d/shape_cast_2d.cpp
index 6222b0db14..4c2e9e4c19 100644
--- a/scene/2d/shape_cast_2d.cpp
+++ b/scene/2d/shape_cast_2d.cpp
@@ -107,6 +107,11 @@ Object *ShapeCast2D::get_collider(int p_idx) const {
return ObjectDB::get_instance(result[p_idx].collider_id);
}
+RID ShapeCast2D::get_collider_rid(int p_idx) const {
+ ERR_FAIL_INDEX_V_MSG(p_idx, result.size(), RID(), "No collider RID found.");
+ return result[p_idx].rid;
+}
+
int ShapeCast2D::get_collider_shape(int p_idx) const {
ERR_FAIL_INDEX_V_MSG(p_idx, result.size(), -1, "No collider shape found.");
return result[p_idx].shape;
@@ -422,6 +427,7 @@ void ShapeCast2D::_bind_methods() {
ClassDB::bind_method(D_METHOD("force_shapecast_update"), &ShapeCast2D::force_shapecast_update);
ClassDB::bind_method(D_METHOD("get_collider", "index"), &ShapeCast2D::get_collider);
+ ClassDB::bind_method(D_METHOD("get_collider_rid", "index"), &ShapeCast2D::get_collider_rid);
ClassDB::bind_method(D_METHOD("get_collider_shape", "index"), &ShapeCast2D::get_collider_shape);
ClassDB::bind_method(D_METHOD("get_collision_point", "index"), &ShapeCast2D::get_collision_point);
ClassDB::bind_method(D_METHOD("get_collision_normal", "index"), &ShapeCast2D::get_collision_normal);
diff --git a/scene/2d/shape_cast_2d.h b/scene/2d/shape_cast_2d.h
index 7b55566b01..134f236d3b 100644
--- a/scene/2d/shape_cast_2d.h
+++ b/scene/2d/shape_cast_2d.h
@@ -104,6 +104,7 @@ public:
int get_collision_count() const;
Object *get_collider(int p_idx) const;
+ RID get_collider_rid(int p_idx) const;
int get_collider_shape(int p_idx) const;
Vector2 get_collision_point(int p_idx) const;
Vector2 get_collision_normal(int p_idx) const;
diff --git a/scene/3d/shape_cast_3d.cpp b/scene/3d/shape_cast_3d.cpp
index 03cbd984cd..267139c7d0 100644
--- a/scene/3d/shape_cast_3d.cpp
+++ b/scene/3d/shape_cast_3d.cpp
@@ -115,6 +115,7 @@ void ShapeCast3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("force_shapecast_update"), &ShapeCast3D::force_shapecast_update);
ClassDB::bind_method(D_METHOD("get_collider", "index"), &ShapeCast3D::get_collider);
+ ClassDB::bind_method(D_METHOD("get_collider_rid", "index"), &ShapeCast3D::get_collider_rid);
ClassDB::bind_method(D_METHOD("get_collider_shape", "index"), &ShapeCast3D::get_collider_shape);
ClassDB::bind_method(D_METHOD("get_collision_point", "index"), &ShapeCast3D::get_collision_point);
ClassDB::bind_method(D_METHOD("get_collision_normal", "index"), &ShapeCast3D::get_collision_normal);
@@ -282,6 +283,11 @@ Object *ShapeCast3D::get_collider(int p_idx) const {
return ObjectDB::get_instance(result[p_idx].collider_id);
}
+RID ShapeCast3D::get_collider_rid(int p_idx) const {
+ ERR_FAIL_INDEX_V_MSG(p_idx, result.size(), RID(), "No collider RID found.");
+ return result[p_idx].rid;
+}
+
int ShapeCast3D::get_collider_shape(int p_idx) const {
ERR_FAIL_INDEX_V_MSG(p_idx, result.size(), -1, "No collider shape found.");
return result[p_idx].shape;
diff --git a/scene/3d/shape_cast_3d.h b/scene/3d/shape_cast_3d.h
index 2526d8d32c..abe10c9b24 100644
--- a/scene/3d/shape_cast_3d.h
+++ b/scene/3d/shape_cast_3d.h
@@ -120,6 +120,7 @@ public:
int get_collision_count() const;
Object *get_collider(int p_idx) const;
+ RID get_collider_rid(int p_idx) const;
int get_collider_shape(int p_idx) const;
Vector3 get_collision_point(int p_idx) const;
Vector3 get_collision_normal(int p_idx) const;
diff --git a/scene/3d/sprite_3d.cpp b/scene/3d/sprite_3d.cpp
index be6eab2178..d69953fee5 100644
--- a/scene/3d/sprite_3d.cpp
+++ b/scene/3d/sprite_3d.cpp
@@ -680,6 +680,7 @@ void Sprite3D::set_region_enabled(bool p_region) {
region = p_region;
_queue_redraw();
+ notify_property_list_changed();
}
bool Sprite3D::is_region_enabled() const {
@@ -781,6 +782,10 @@ void Sprite3D::_validate_property(PropertyInfo &p_property) const {
if (p_property.name == "frame_coords") {
p_property.usage |= PROPERTY_USAGE_KEYING_INCREMENTS;
}
+
+ if (!region && (p_property.name == "region_rect")) {
+ p_property.usage = PROPERTY_USAGE_NO_EDITOR;
+ }
}
void Sprite3D::_bind_methods() {
diff --git a/scene/gui/base_button.cpp b/scene/gui/base_button.cpp
index 552345e4fe..1082fc4d6d 100644
--- a/scene/gui/base_button.cpp
+++ b/scene/gui/base_button.cpp
@@ -127,6 +127,7 @@ void BaseButton::_notification(int p_what) {
status.hovering = false;
status.press_attempt = false;
status.pressing_inside = false;
+ status.shortcut_press = false;
} break;
}
}
@@ -160,6 +161,7 @@ void BaseButton::on_action_event(Ref<InputEvent> p_event) {
if (action_mode == ACTION_MODE_BUTTON_PRESS) {
status.press_attempt = false;
status.pressing_inside = false;
+ status.shortcut_press = false;
}
status.pressed = !status.pressed;
_unpress_group();
@@ -185,6 +187,7 @@ void BaseButton::on_action_event(Ref<InputEvent> p_event) {
}
status.press_attempt = false;
status.pressing_inside = false;
+ status.shortcut_press = false;
emit_signal(SNAME("button_up"));
}
@@ -209,6 +212,7 @@ void BaseButton::set_disabled(bool p_disabled) {
}
status.press_attempt = false;
status.pressing_inside = false;
+ status.shortcut_press = false;
}
queue_redraw();
}
@@ -284,7 +288,7 @@ BaseButton::DrawMode BaseButton::get_draw_mode() const {
pressing = status.pressed;
}
- if (pressing) {
+ if ((shortcut_feedback || !status.shortcut_press) && pressing) {
return DRAW_PRESSED;
} else {
return DRAW_NORMAL;
@@ -350,6 +354,7 @@ void BaseButton::shortcut_input(const Ref<InputEvent> &p_event) {
ERR_FAIL_COND(p_event.is_null());
if (!is_disabled() && is_visible_in_tree() && !p_event->is_echo() && shortcut.is_valid() && shortcut->matches_event(p_event)) {
+ status.shortcut_press = true;
on_action_event(p_event);
accept_event();
}
@@ -389,6 +394,14 @@ bool BaseButton::_was_pressed_by_mouse() const {
return was_mouse_pressed;
}
+void BaseButton::set_shortcut_feedback(bool p_feedback) {
+ shortcut_feedback = p_feedback;
+}
+
+bool BaseButton::is_shortcut_feedback() const {
+ return shortcut_feedback;
+}
+
void BaseButton::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_pressed", "pressed"), &BaseButton::set_pressed);
ClassDB::bind_method(D_METHOD("is_pressed"), &BaseButton::is_pressed);
@@ -414,6 +427,9 @@ void BaseButton::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_button_group", "button_group"), &BaseButton::set_button_group);
ClassDB::bind_method(D_METHOD("get_button_group"), &BaseButton::get_button_group);
+ ClassDB::bind_method(D_METHOD("set_shortcut_feedback", "enabled"), &BaseButton::set_shortcut_feedback);
+ ClassDB::bind_method(D_METHOD("is_shortcut_feedback"), &BaseButton::is_shortcut_feedback);
+
GDVIRTUAL_BIND(_pressed);
GDVIRTUAL_BIND(_toggled, "button_pressed");
@@ -430,6 +446,7 @@ void BaseButton::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::INT, "button_mask", PROPERTY_HINT_FLAGS, "Mouse Left, Mouse Right, Mouse Middle"), "set_button_mask", "get_button_mask");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "keep_pressed_outside"), "set_keep_pressed_outside", "is_keep_pressed_outside");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "shortcut", PROPERTY_HINT_RESOURCE_TYPE, "Shortcut"), "set_shortcut", "get_shortcut");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "shortcut_feedback"), "set_shortcut_feedback", "is_shortcut_feedback");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "button_group", PROPERTY_HINT_RESOURCE_TYPE, "ButtonGroup"), "set_button_group", "get_button_group");
BIND_ENUM_CONSTANT(DRAW_NORMAL);
diff --git a/scene/gui/base_button.h b/scene/gui/base_button.h
index 7839239800..3acf535f54 100644
--- a/scene/gui/base_button.h
+++ b/scene/gui/base_button.h
@@ -53,6 +53,7 @@ private:
bool keep_pressed_outside = false;
Ref<Shortcut> shortcut;
ObjectID shortcut_context;
+ bool shortcut_feedback = true;
ActionMode action_mode = ACTION_MODE_BUTTON_RELEASE;
struct Status {
@@ -60,6 +61,7 @@ private:
bool hovering = false;
bool press_attempt = false;
bool pressing_inside = false;
+ bool shortcut_press = false;
bool disabled = false;
@@ -131,6 +133,9 @@ public:
void set_button_group(const Ref<ButtonGroup> &p_group);
Ref<ButtonGroup> get_button_group() const;
+ void set_shortcut_feedback(bool p_feedback);
+ bool is_shortcut_feedback() const;
+
BaseButton();
~BaseButton();
};
diff --git a/scene/gui/code_edit.cpp b/scene/gui/code_edit.cpp
index f61fa29a33..5f8f25154c 100644
--- a/scene/gui/code_edit.cpp
+++ b/scene/gui/code_edit.cpp
@@ -138,7 +138,7 @@ void CodeEdit::_notification(int p_what) {
code_completion_scroll_rect.position = code_completion_rect.position + Vector2(code_completion_rect.size.width, 0);
code_completion_scroll_rect.size = Vector2(scroll_width, code_completion_rect.size.height);
- code_completion_line_ofs = CLAMP(code_completion_current_selected - lines / 2, 0, code_completion_options_count - lines);
+ code_completion_line_ofs = CLAMP((code_completion_force_item_center < 0 ? code_completion_current_selected : code_completion_force_item_center) - lines / 2, 0, code_completion_options_count - lines);
RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2(code_completion_rect.position.x, code_completion_rect.position.y + (code_completion_current_selected - code_completion_line_ofs) * row_height), Size2(code_completion_rect.size.width, row_height)), code_completion_selected_color);
for (int i = 0; i < lines; i++) {
@@ -281,16 +281,22 @@ void CodeEdit::gui_input(const Ref<InputEvent> &p_gui_input) {
case MouseButton::WHEEL_UP: {
if (code_completion_current_selected > 0) {
code_completion_current_selected--;
+ code_completion_force_item_center = -1;
queue_redraw();
}
} break;
case MouseButton::WHEEL_DOWN: {
if (code_completion_current_selected < code_completion_options.size() - 1) {
code_completion_current_selected++;
+ code_completion_force_item_center = -1;
queue_redraw();
}
} break;
case MouseButton::LEFT: {
+ if (code_completion_force_item_center == -1) {
+ code_completion_force_item_center = code_completion_current_selected;
+ }
+
code_completion_current_selected = CLAMP(code_completion_line_ofs + (mb->get_position().y - code_completion_rect.position.y) / get_line_height(), 0, code_completion_options.size() - 1);
if (mb->is_double_click()) {
confirm_code_completion();
@@ -300,6 +306,7 @@ void CodeEdit::gui_input(const Ref<InputEvent> &p_gui_input) {
default:
break;
}
+
return;
} else if (code_completion_active && code_completion_scroll_rect.has_point(mb->get_position())) {
if (mb->get_button_index() != MouseButton::LEFT) {
@@ -448,6 +455,7 @@ void CodeEdit::gui_input(const Ref<InputEvent> &p_gui_input) {
} else {
code_completion_current_selected = code_completion_options.size() - 1;
}
+ code_completion_force_item_center = -1;
queue_redraw();
accept_event();
return;
@@ -458,30 +466,35 @@ void CodeEdit::gui_input(const Ref<InputEvent> &p_gui_input) {
} else {
code_completion_current_selected = 0;
}
+ code_completion_force_item_center = -1;
queue_redraw();
accept_event();
return;
}
if (k->is_action("ui_page_up", true)) {
code_completion_current_selected = MAX(0, code_completion_current_selected - code_completion_max_lines);
+ code_completion_force_item_center = -1;
queue_redraw();
accept_event();
return;
}
if (k->is_action("ui_page_down", true)) {
code_completion_current_selected = MIN(code_completion_options.size() - 1, code_completion_current_selected + code_completion_max_lines);
+ code_completion_force_item_center = -1;
queue_redraw();
accept_event();
return;
}
if (k->is_action("ui_home", true)) {
code_completion_current_selected = 0;
+ code_completion_force_item_center = -1;
queue_redraw();
accept_event();
return;
}
if (k->is_action("ui_end", true)) {
code_completion_current_selected = code_completion_options.size() - 1;
+ code_completion_force_item_center = -1;
queue_redraw();
accept_event();
return;
@@ -1978,6 +1991,7 @@ void CodeEdit::set_code_completion_selected_index(int p_index) {
}
ERR_FAIL_INDEX(p_index, code_completion_options.size());
code_completion_current_selected = p_index;
+ code_completion_force_item_center = -1;
queue_redraw();
}
@@ -2808,6 +2822,7 @@ void CodeEdit::_update_scroll_selected_line(float p_mouse_y) {
percent = CLAMP(percent, 0.0f, 1.0f);
code_completion_current_selected = (int)(percent * (code_completion_options.size() - 1));
+ code_completion_force_item_center = -1;
}
void CodeEdit::_filter_code_completion_candidates_impl() {
@@ -2867,6 +2882,7 @@ void CodeEdit::_filter_code_completion_candidates_impl() {
code_completion_longest_line = MIN(max_width, code_completion_max_width * font_size);
code_completion_current_selected = 0;
+ code_completion_force_item_center = -1;
code_completion_active = true;
queue_redraw();
return;
@@ -3123,6 +3139,7 @@ void CodeEdit::_filter_code_completion_candidates_impl() {
code_completion_longest_line = MIN(max_width, code_completion_max_width * font_size);
code_completion_current_selected = 0;
+ code_completion_force_item_center = -1;
code_completion_active = true;
queue_redraw();
}
diff --git a/scene/gui/code_edit.h b/scene/gui/code_edit.h
index 09c7ef80bf..cbbc13480e 100644
--- a/scene/gui/code_edit.h
+++ b/scene/gui/code_edit.h
@@ -214,6 +214,7 @@ private:
Vector<ScriptLanguage::CodeCompletionOption> code_completion_options;
int code_completion_line_ofs = 0;
int code_completion_current_selected = 0;
+ int code_completion_force_item_center = -1;
int code_completion_longest_line = 0;
Rect2i code_completion_rect;
Rect2i code_completion_scroll_rect;
diff --git a/scene/gui/flow_container.cpp b/scene/gui/flow_container.cpp
index b0d15aa7f4..44c5ec62f8 100644
--- a/scene/gui/flow_container.cpp
+++ b/scene/gui/flow_container.cpp
@@ -152,6 +152,28 @@ void FlowContainer::_resort() {
line_data = lines_data[current_line_idx];
}
+ // The first child of each line adds the offset caused by the alignment,
+ // but only if the line doesn't contain a child that expands.
+ if (child_idx_in_line == 0 && Math::is_equal_approx(line_data.stretch_ratio_total, 0)) {
+ int alignment_ofs = 0;
+ switch (alignment) {
+ case ALIGNMENT_CENTER:
+ alignment_ofs = line_data.stretch_avail / 2;
+ break;
+ case ALIGNMENT_END:
+ alignment_ofs = line_data.stretch_avail;
+ break;
+ default:
+ break;
+ }
+
+ if (vertical) { /* VERTICAL */
+ ofs.y += alignment_ofs;
+ } else { /* HORIZONTAL */
+ ofs.x += alignment_ofs;
+ }
+ }
+
if (vertical) { /* VERTICAL */
if (child->get_h_size_flags() & (SIZE_FILL | SIZE_SHRINK_CENTER | SIZE_SHRINK_END)) {
child_size.width = line_data.min_line_height;
@@ -282,6 +304,18 @@ int FlowContainer::get_line_count() const {
return cached_line_count;
}
+void FlowContainer::set_alignment(AlignmentMode p_alignment) {
+ if (alignment == p_alignment) {
+ return;
+ }
+ alignment = p_alignment;
+ _resort();
+}
+
+FlowContainer::AlignmentMode FlowContainer::get_alignment() const {
+ return alignment;
+}
+
void FlowContainer::set_vertical(bool p_vertical) {
ERR_FAIL_COND_MSG(is_fixed, "Can't change orientation of " + get_class() + ".");
vertical = p_vertical;
@@ -300,8 +334,15 @@ FlowContainer::FlowContainer(bool p_vertical) {
void FlowContainer::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_line_count"), &FlowContainer::get_line_count);
+ ClassDB::bind_method(D_METHOD("set_alignment", "alignment"), &FlowContainer::set_alignment);
+ ClassDB::bind_method(D_METHOD("get_alignment"), &FlowContainer::get_alignment);
ClassDB::bind_method(D_METHOD("set_vertical", "vertical"), &FlowContainer::set_vertical);
ClassDB::bind_method(D_METHOD("is_vertical"), &FlowContainer::is_vertical);
+ BIND_ENUM_CONSTANT(ALIGNMENT_BEGIN);
+ BIND_ENUM_CONSTANT(ALIGNMENT_CENTER);
+ BIND_ENUM_CONSTANT(ALIGNMENT_END);
+
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "alignment", PROPERTY_HINT_ENUM, "Begin,Center,End"), "set_alignment", "get_alignment");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "vertical"), "set_vertical", "is_vertical");
}
diff --git a/scene/gui/flow_container.h b/scene/gui/flow_container.h
index 536df27ad6..6a61e9b904 100644
--- a/scene/gui/flow_container.h
+++ b/scene/gui/flow_container.h
@@ -36,11 +36,19 @@
class FlowContainer : public Container {
GDCLASS(FlowContainer, Container);
+public:
+ enum AlignmentMode {
+ ALIGNMENT_BEGIN,
+ ALIGNMENT_CENTER,
+ ALIGNMENT_END
+ };
+
private:
int cached_size = 0;
int cached_line_count = 0;
bool vertical = false;
+ AlignmentMode alignment = ALIGNMENT_BEGIN;
struct ThemeCache {
int h_separation = 0;
@@ -61,6 +69,9 @@ protected:
public:
int get_line_count() const;
+ void set_alignment(AlignmentMode p_alignment);
+ AlignmentMode get_alignment() const;
+
void set_vertical(bool p_vertical);
bool is_vertical() const;
@@ -88,4 +99,6 @@ public:
FlowContainer(true) { is_fixed = true; }
};
+VARIANT_ENUM_CAST(FlowContainer::AlignmentMode);
+
#endif // FLOW_CONTAINER_H
diff --git a/scene/gui/scroll_container.cpp b/scene/gui/scroll_container.cpp
index a6aa4707ff..ec34a8d26d 100644
--- a/scene/gui/scroll_container.cpp
+++ b/scene/gui/scroll_container.cpp
@@ -107,45 +107,65 @@ void ScrollContainer::gui_input(const Ref<InputEvent> &p_gui_input) {
double prev_v_scroll = v_scroll->get_value();
double prev_h_scroll = h_scroll->get_value();
+ bool h_scroll_enabled = horizontal_scroll_mode != SCROLL_MODE_DISABLED;
+ bool v_scroll_enabled = vertical_scroll_mode != SCROLL_MODE_DISABLED;
Ref<InputEventMouseButton> mb = p_gui_input;
if (mb.is_valid()) {
- if (mb->get_button_index() == MouseButton::WHEEL_UP && mb->is_pressed()) {
- // only horizontal is enabled, scroll horizontally
- if (h_scroll->is_visible() && (!v_scroll->is_visible() || mb->is_shift_pressed())) {
- h_scroll->set_value(h_scroll->get_value() - h_scroll->get_page() / 8 * mb->get_factor());
- } else if (v_scroll->is_visible_in_tree()) {
- v_scroll->set_value(v_scroll->get_value() - v_scroll->get_page() / 8 * mb->get_factor());
+ if (mb->is_pressed()) {
+ bool scroll_value_modified = false;
+
+ bool v_scroll_hidden = !v_scroll->is_visible() && vertical_scroll_mode != SCROLL_MODE_SHOW_NEVER;
+ if (mb->get_button_index() == MouseButton::WHEEL_UP) {
+ // By default, the vertical orientation takes precedence. This is an exception.
+ if ((h_scroll_enabled && mb->is_shift_pressed()) || v_scroll_hidden) {
+ h_scroll->set_value(prev_h_scroll - h_scroll->get_page() / 8 * mb->get_factor());
+ scroll_value_modified = true;
+ } else if (v_scroll_enabled) {
+ v_scroll->set_value(prev_v_scroll - v_scroll->get_page() / 8 * mb->get_factor());
+ scroll_value_modified = true;
+ }
}
- }
-
- if (mb->get_button_index() == MouseButton::WHEEL_DOWN && mb->is_pressed()) {
- // only horizontal is enabled, scroll horizontally
- if (h_scroll->is_visible() && (!v_scroll->is_visible() || mb->is_shift_pressed())) {
- h_scroll->set_value(h_scroll->get_value() + h_scroll->get_page() / 8 * mb->get_factor());
- } else if (v_scroll->is_visible()) {
- v_scroll->set_value(v_scroll->get_value() + v_scroll->get_page() / 8 * mb->get_factor());
+ if (mb->get_button_index() == MouseButton::WHEEL_DOWN) {
+ if ((h_scroll_enabled && mb->is_shift_pressed()) || v_scroll_hidden) {
+ h_scroll->set_value(prev_h_scroll + h_scroll->get_page() / 8 * mb->get_factor());
+ scroll_value_modified = true;
+ } else if (v_scroll_enabled) {
+ v_scroll->set_value(prev_v_scroll + v_scroll->get_page() / 8 * mb->get_factor());
+ scroll_value_modified = true;
+ }
}
- }
- if (mb->get_button_index() == MouseButton::WHEEL_LEFT && mb->is_pressed()) {
- if (h_scroll->is_visible_in_tree()) {
- h_scroll->set_value(h_scroll->get_value() - h_scroll->get_page() * mb->get_factor() / 8);
+ bool h_scroll_hidden = !h_scroll->is_visible() && horizontal_scroll_mode != SCROLL_MODE_SHOW_NEVER;
+ if (mb->get_button_index() == MouseButton::WHEEL_LEFT) {
+ // By default, the horizontal orientation takes precedence. This is an exception.
+ if ((v_scroll_enabled && mb->is_shift_pressed()) || h_scroll_hidden) {
+ v_scroll->set_value(prev_v_scroll - v_scroll->get_page() / 8 * mb->get_factor());
+ scroll_value_modified = true;
+ } else if (h_scroll_enabled) {
+ h_scroll->set_value(prev_h_scroll - h_scroll->get_page() / 8 * mb->get_factor());
+ scroll_value_modified = true;
+ }
}
- }
-
- if (mb->get_button_index() == MouseButton::WHEEL_RIGHT && mb->is_pressed()) {
- if (h_scroll->is_visible_in_tree()) {
- h_scroll->set_value(h_scroll->get_value() + h_scroll->get_page() * mb->get_factor() / 8);
+ if (mb->get_button_index() == MouseButton::WHEEL_RIGHT) {
+ if ((v_scroll_enabled && mb->is_shift_pressed()) || h_scroll_hidden) {
+ v_scroll->set_value(prev_v_scroll + v_scroll->get_page() / 8 * mb->get_factor());
+ scroll_value_modified = true;
+ } else if (h_scroll_enabled) {
+ h_scroll->set_value(prev_h_scroll + h_scroll->get_page() / 8 * mb->get_factor());
+ scroll_value_modified = true;
+ }
}
- }
- if (v_scroll->get_value() != prev_v_scroll || h_scroll->get_value() != prev_h_scroll) {
- accept_event(); //accept event if scroll changed
+ if (scroll_value_modified && (v_scroll->get_value() != prev_v_scroll || h_scroll->get_value() != prev_h_scroll)) {
+ accept_event(); // Accept event if scroll changed.
+ return;
+ }
}
- if (!DisplayServer::get_singleton()->screen_is_touchscreen(DisplayServer::get_singleton()->window_get_current_screen(get_viewport()->get_window_id()))) {
+ bool screen_is_touchscreen = DisplayServer::get_singleton()->screen_is_touchscreen(DisplayServer::get_singleton()->window_get_current_screen(get_viewport()->get_window_id()));
+ if (!screen_is_touchscreen) {
return;
}
@@ -161,8 +181,8 @@ void ScrollContainer::gui_input(const Ref<InputEvent> &p_gui_input) {
drag_speed = Vector2();
drag_accum = Vector2();
last_drag_accum = Vector2();
- drag_from = Vector2(h_scroll->get_value(), v_scroll->get_value());
- drag_touching = DisplayServer::get_singleton()->screen_is_touchscreen(DisplayServer::get_singleton()->window_get_current_screen(get_viewport()->get_window_id()));
+ drag_from = Vector2(prev_h_scroll, prev_v_scroll);
+ drag_touching = screen_is_touchscreen;
drag_touching_deaccel = false;
beyond_deadzone = false;
time_since_motion = 0;
@@ -180,6 +200,7 @@ void ScrollContainer::gui_input(const Ref<InputEvent> &p_gui_input) {
}
}
}
+ return;
}
Ref<InputEventMouseMotion> mm = p_gui_input;
@@ -189,22 +210,22 @@ void ScrollContainer::gui_input(const Ref<InputEvent> &p_gui_input) {
Vector2 motion = mm->get_relative();
drag_accum -= motion;
- if (beyond_deadzone || (horizontal_scroll_mode != SCROLL_MODE_DISABLED && Math::abs(drag_accum.x) > deadzone) || (vertical_scroll_mode != SCROLL_MODE_DISABLED && Math::abs(drag_accum.y) > deadzone)) {
+ if (beyond_deadzone || (h_scroll_enabled && Math::abs(drag_accum.x) > deadzone) || (v_scroll_enabled && Math::abs(drag_accum.y) > deadzone)) {
if (!beyond_deadzone) {
propagate_notification(NOTIFICATION_SCROLL_BEGIN);
emit_signal(SNAME("scroll_started"));
beyond_deadzone = true;
- // resetting drag_accum here ensures smooth scrolling after reaching deadzone
+ // Resetting drag_accum here ensures smooth scrolling after reaching deadzone.
drag_accum = -motion;
}
Vector2 diff = drag_from + drag_accum;
- if (horizontal_scroll_mode != SCROLL_MODE_DISABLED) {
+ if (h_scroll_enabled) {
h_scroll->set_value(diff.x);
} else {
drag_accum.x = 0;
}
- if (vertical_scroll_mode != SCROLL_MODE_DISABLED) {
+ if (v_scroll_enabled) {
v_scroll->set_value(diff.y);
} else {
drag_accum.y = 0;
@@ -212,20 +233,26 @@ void ScrollContainer::gui_input(const Ref<InputEvent> &p_gui_input) {
time_since_motion = 0;
}
}
+
+ if (v_scroll->get_value() != prev_v_scroll || h_scroll->get_value() != prev_h_scroll) {
+ accept_event(); // Accept event if scroll changed.
+ }
+ return;
}
Ref<InputEventPanGesture> pan_gesture = p_gui_input;
if (pan_gesture.is_valid()) {
- if (h_scroll->is_visible_in_tree()) {
- h_scroll->set_value(h_scroll->get_value() + h_scroll->get_page() * pan_gesture->get_delta().x / 8);
+ if (h_scroll_enabled) {
+ h_scroll->set_value(prev_h_scroll + h_scroll->get_page() * pan_gesture->get_delta().x / 8);
}
- if (v_scroll->is_visible_in_tree()) {
- v_scroll->set_value(v_scroll->get_value() + v_scroll->get_page() * pan_gesture->get_delta().y / 8);
+ if (v_scroll_enabled) {
+ v_scroll->set_value(prev_v_scroll + v_scroll->get_page() * pan_gesture->get_delta().y / 8);
}
- }
- if (v_scroll->get_value() != prev_v_scroll || h_scroll->get_value() != prev_h_scroll) {
- accept_event(); //accept event if scroll changed
+ if (v_scroll->get_value() != prev_v_scroll || h_scroll->get_value() != prev_h_scroll) {
+ accept_event(); // Accept event if scroll changed.
+ }
+ return;
}
}
diff --git a/scene/main/canvas_item.cpp b/scene/main/canvas_item.cpp
index 0d3389c13d..3bf4e95e68 100644
--- a/scene/main/canvas_item.cpp
+++ b/scene/main/canvas_item.cpp
@@ -219,6 +219,7 @@ void CanvasItem::_enter_canvas() {
}
RenderingServer::get_singleton()->canvas_item_set_parent(canvas_item, canvas);
+ RenderingServer::get_singleton()->canvas_item_set_visibility_layer(canvas_item, visibility_layer);
canvas_group = "root_canvas" + itos(canvas.get_id());
@@ -236,6 +237,7 @@ void CanvasItem::_enter_canvas() {
canvas_layer = parent->canvas_layer;
RenderingServer::get_singleton()->canvas_item_set_parent(canvas_item, parent->get_canvas_item());
RenderingServer::get_singleton()->canvas_item_set_draw_index(canvas_item, get_index());
+ RenderingServer::get_singleton()->canvas_item_set_visibility_layer(canvas_item, visibility_layer);
}
pending_update = false;
@@ -977,6 +979,11 @@ void CanvasItem::_bind_methods() {
ClassDB::bind_method(D_METHOD("make_canvas_position_local", "screen_point"), &CanvasItem::make_canvas_position_local);
ClassDB::bind_method(D_METHOD("make_input_local", "event"), &CanvasItem::make_input_local);
+ ClassDB::bind_method(D_METHOD("set_visibility_layer", "layer"), &CanvasItem::set_visibility_layer);
+ ClassDB::bind_method(D_METHOD("get_visibility_layer"), &CanvasItem::get_visibility_layer);
+ ClassDB::bind_method(D_METHOD("set_visibility_layer_bit", "layer", "enabled"), &CanvasItem::set_visibility_layer_bit);
+ ClassDB::bind_method(D_METHOD("get_visibility_layer_bit", "layer"), &CanvasItem::get_visibility_layer_bit);
+
ClassDB::bind_method(D_METHOD("set_texture_filter", "mode"), &CanvasItem::set_texture_filter);
ClassDB::bind_method(D_METHOD("get_texture_filter"), &CanvasItem::get_texture_filter);
@@ -996,6 +1003,7 @@ void CanvasItem::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "top_level"), "set_as_top_level", "is_set_as_top_level");
ADD_PROPERTY(PropertyInfo(Variant::INT, "clip_children", PROPERTY_HINT_ENUM, "Disabled,Clip Only,Clip + Draw"), "set_clip_children_mode", "get_clip_children_mode");
ADD_PROPERTY(PropertyInfo(Variant::INT, "light_mask", PROPERTY_HINT_LAYERS_2D_RENDER), "set_light_mask", "get_light_mask");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "visibility_layer", PROPERTY_HINT_LAYERS_2D_RENDER), "set_visibility_layer", "get_visibility_layer");
ADD_GROUP("Texture", "texture_");
ADD_PROPERTY(PropertyInfo(Variant::INT, "texture_filter", PROPERTY_HINT_ENUM, "Inherit,Nearest,Linear,Nearest Mipmap,Linear Mipmap,Nearest Mipmap Anisotropic,Linear Mipmap Anisotropic"), "set_texture_filter", "get_texture_filter");
@@ -1094,6 +1102,29 @@ int CanvasItem::get_canvas_layer() const {
}
}
+void CanvasItem::set_visibility_layer(uint32_t p_visibility_layer) {
+ visibility_layer = p_visibility_layer;
+ RenderingServer::get_singleton()->canvas_item_set_visibility_layer(canvas_item, p_visibility_layer);
+}
+
+uint32_t CanvasItem::get_visibility_layer() const {
+ return visibility_layer;
+}
+
+void CanvasItem::set_visibility_layer_bit(uint32_t p_visibility_layer, bool p_enable) {
+ ERR_FAIL_INDEX(p_visibility_layer, 32);
+ if (p_enable) {
+ set_visibility_layer(visibility_layer | (1 << p_visibility_layer));
+ } else {
+ set_visibility_layer(visibility_layer & (~(1 << p_visibility_layer)));
+ }
+}
+
+bool CanvasItem::get_visibility_layer_bit(uint32_t p_visibility_layer) const {
+ ERR_FAIL_INDEX_V(p_visibility_layer, 32, false);
+ return (visibility_layer & (1 << p_visibility_layer));
+}
+
void CanvasItem::_refresh_texture_filter_cache() {
if (!is_inside_tree()) {
return;
diff --git a/scene/main/canvas_item.h b/scene/main/canvas_item.h
index 565ea930ce..4e78a175dc 100644
--- a/scene/main/canvas_item.h
+++ b/scene/main/canvas_item.h
@@ -89,6 +89,7 @@ private:
List<CanvasItem *>::Element *C = nullptr;
int light_mask = 1;
+ uint32_t visibility_layer = 1;
Window *window = nullptr;
bool visible = true;
@@ -223,6 +224,12 @@ public:
void set_self_modulate(const Color &p_self_modulate);
Color get_self_modulate() const;
+ void set_visibility_layer(uint32_t p_visibility_layer);
+ uint32_t get_visibility_layer() const;
+
+ void set_visibility_layer_bit(uint32_t p_visibility_layer, bool p_enable);
+ bool get_visibility_layer_bit(uint32_t p_visibility_layer) const;
+
/* DRAWING API */
void draw_dashed_line(const Point2 &p_from, const Point2 &p_to, const Color &p_color, real_t p_width = 1.0, real_t p_dash = 2.0);
diff --git a/scene/main/canvas_layer.cpp b/scene/main/canvas_layer.cpp
index 214efe432b..be5788739b 100644
--- a/scene/main/canvas_layer.cpp
+++ b/scene/main/canvas_layer.cpp
@@ -38,6 +38,7 @@ void CanvasLayer::set_layer(int p_xform) {
layer = p_xform;
if (viewport.is_valid()) {
RenderingServer::get_singleton()->viewport_set_canvas_stacking(viewport, canvas, layer, get_index());
+ vp->_gui_set_root_order_dirty();
}
}
diff --git a/scene/main/multiplayer_peer.cpp b/scene/main/multiplayer_peer.cpp
index b4e5b11abd..92ba3debd1 100644
--- a/scene/main/multiplayer_peer.cpp
+++ b/scene/main/multiplayer_peer.cpp
@@ -123,9 +123,6 @@ void MultiplayerPeer::_bind_methods() {
ADD_SIGNAL(MethodInfo("peer_connected", PropertyInfo(Variant::INT, "id")));
ADD_SIGNAL(MethodInfo("peer_disconnected", PropertyInfo(Variant::INT, "id")));
- ADD_SIGNAL(MethodInfo("server_disconnected"));
- ADD_SIGNAL(MethodInfo("connection_succeeded"));
- ADD_SIGNAL(MethodInfo("connection_failed"));
}
/*************/
diff --git a/scene/main/viewport.cpp b/scene/main/viewport.cpp
index 6af96a4147..2385e4f54a 100644
--- a/scene/main/viewport.cpp
+++ b/scene/main/viewport.cpp
@@ -362,6 +362,7 @@ void Viewport::_notification(int p_what) {
current_canvas = find_world_2d()->get_canvas();
RenderingServer::get_singleton()->viewport_attach_canvas(viewport, current_canvas);
+ RenderingServer::get_singleton()->viewport_set_canvas_cull_mask(viewport, canvas_cull_mask);
_update_audio_listener_2d();
#ifndef _3D_DISABLED
RenderingServer::get_singleton()->viewport_set_scenario(viewport, find_world_3d()->get_scenario());
@@ -993,11 +994,6 @@ void Viewport::set_world_2d(const Ref<World2D> &p_world_2d) {
return;
}
- if (parent && parent->find_world_2d() == p_world_2d) {
- WARN_PRINT("Unable to use parent world_2d as world_2d");
- return;
- }
-
if (is_inside_tree()) {
RenderingServer::get_singleton()->viewport_remove_canvas(viewport, current_canvas);
}
@@ -3249,6 +3245,29 @@ Transform2D Viewport::get_screen_transform() const {
return _get_input_pre_xform().affine_inverse() * get_final_transform();
}
+void Viewport::set_canvas_cull_mask(uint32_t p_canvas_cull_mask) {
+ canvas_cull_mask = p_canvas_cull_mask;
+ RenderingServer::get_singleton()->viewport_set_canvas_cull_mask(viewport, canvas_cull_mask);
+}
+
+uint32_t Viewport::get_canvas_cull_mask() const {
+ return canvas_cull_mask;
+}
+
+void Viewport::set_canvas_cull_mask_bit(uint32_t p_layer, bool p_enable) {
+ ERR_FAIL_INDEX(p_layer, 32);
+ if (p_enable) {
+ set_canvas_cull_mask(canvas_cull_mask | (1 << p_layer));
+ } else {
+ set_canvas_cull_mask(canvas_cull_mask & (~(1 << p_layer)));
+ }
+}
+
+bool Viewport::get_canvas_cull_mask_bit(uint32_t p_layer) const {
+ ERR_FAIL_INDEX_V(p_layer, 32, false);
+ return (canvas_cull_mask & (1 << p_layer));
+}
+
#ifndef _3D_DISABLED
AudioListener3D *Viewport::get_audio_listener_3d() const {
return audio_listener_3d;
@@ -3820,6 +3839,12 @@ void Viewport::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_embedding_subwindows", "enable"), &Viewport::set_embedding_subwindows);
ClassDB::bind_method(D_METHOD("is_embedding_subwindows"), &Viewport::is_embedding_subwindows);
+ ClassDB::bind_method(D_METHOD("set_canvas_cull_mask", "mask"), &Viewport::set_canvas_cull_mask);
+ ClassDB::bind_method(D_METHOD("get_canvas_cull_mask"), &Viewport::get_canvas_cull_mask);
+
+ ClassDB::bind_method(D_METHOD("set_canvas_cull_mask_bit", "layer", "enable"), &Viewport::set_canvas_cull_mask_bit);
+ ClassDB::bind_method(D_METHOD("get_canvas_cull_mask_bit", "layer"), &Viewport::get_canvas_cull_mask_bit);
+
ClassDB::bind_method(D_METHOD("set_default_canvas_item_texture_repeat", "mode"), &Viewport::set_default_canvas_item_texture_repeat);
ClassDB::bind_method(D_METHOD("get_default_canvas_item_texture_repeat"), &Viewport::get_default_canvas_item_texture_repeat);
@@ -3925,6 +3950,7 @@ void Viewport::_bind_methods() {
ADD_PROPERTYI(PropertyInfo(Variant::INT, "positional_shadow_atlas_quad_3", PROPERTY_HINT_ENUM, "Disabled,1 Shadow,4 Shadows,16 Shadows,64 Shadows,256 Shadows,1024 Shadows"), "set_positional_shadow_atlas_quadrant_subdiv", "get_positional_shadow_atlas_quadrant_subdiv", 3);
ADD_PROPERTY(PropertyInfo(Variant::TRANSFORM2D, "canvas_transform", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_canvas_transform", "get_canvas_transform");
ADD_PROPERTY(PropertyInfo(Variant::TRANSFORM2D, "global_canvas_transform", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_global_canvas_transform", "get_global_canvas_transform");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "canvas_cull_mask", PROPERTY_HINT_LAYERS_2D_RENDER), "set_canvas_cull_mask", "get_canvas_cull_mask");
ADD_SIGNAL(MethodInfo("size_changed"));
ADD_SIGNAL(MethodInfo("gui_focus_changed", PropertyInfo(Variant::OBJECT, "node", PROPERTY_HINT_RESOURCE_TYPE, "Control")));
diff --git a/scene/main/viewport.h b/scene/main/viewport.h
index 6f67649ea3..8911aea335 100644
--- a/scene/main/viewport.h
+++ b/scene/main/viewport.h
@@ -317,6 +317,8 @@ private:
SDFOversize sdf_oversize = SDF_OVERSIZE_120_PERCENT;
SDFScale sdf_scale = SDF_SCALE_50_PERCENT;
+ uint32_t canvas_cull_mask = 0xffffffff; // by default show everything
+
enum SubWindowDrag {
SUB_WINDOW_DRAG_DISABLED,
SUB_WINDOW_DRAG_MOVE,
@@ -639,6 +641,12 @@ public:
void pass_mouse_focus_to(Viewport *p_viewport, Control *p_control);
+ void set_canvas_cull_mask(uint32_t p_layers);
+ uint32_t get_canvas_cull_mask() const;
+
+ void set_canvas_cull_mask_bit(uint32_t p_layer, bool p_enable);
+ bool get_canvas_cull_mask_bit(uint32_t p_layer) const;
+
virtual Transform2D get_screen_transform() const;
#ifndef _3D_DISABLED
diff --git a/scene/resources/curve.cpp b/scene/resources/curve.cpp
index 49b78a091d..eda9af9dde 100644
--- a/scene/resources/curve.cpp
+++ b/scene/resources/curve.cpp
@@ -936,6 +936,46 @@ Vector2 Curve2D::sample_baked(real_t p_offset, bool p_cubic) const {
}
}
+Transform2D Curve2D::sample_baked_with_rotation(real_t p_offset, bool p_cubic, bool p_loop, real_t p_lookahead) const {
+ real_t path_length = get_baked_length(); // Ensure baked.
+ ERR_FAIL_COND_V_MSG(path_length == 0, Transform2D(), "Length of Curve2D is 0.");
+
+ Vector2 pos = sample_baked(p_offset, p_cubic);
+
+ real_t ahead = p_offset + p_lookahead;
+
+ if (p_loop && ahead >= path_length) {
+ // If our lookahead will loop, we need to check if the path is closed.
+ int point_count = get_point_count();
+ if (point_count > 0) {
+ Vector2 start_point = get_point_position(0);
+ Vector2 end_point = get_point_position(point_count - 1);
+ if (start_point == end_point) {
+ // Since the path is closed we want to 'smooth off'
+ // the corner at the start/end.
+ // So we wrap the lookahead back round.
+ ahead = Math::fmod(ahead, path_length);
+ }
+ }
+ }
+
+ Vector2 ahead_pos = sample_baked(ahead, p_cubic);
+
+ Vector2 tangent_to_curve;
+ if (ahead_pos == pos) {
+ // This will happen at the end of non-looping or non-closed paths.
+ // We'll try a look behind instead, in order to get a meaningful angle.
+ tangent_to_curve =
+ (pos - sample_baked(p_offset - p_lookahead, p_cubic)).normalized();
+ } else {
+ tangent_to_curve = (ahead_pos - pos).normalized();
+ }
+
+ Vector2 normal_of_curve = -tangent_to_curve.orthogonal();
+
+ return Transform2D(normal_of_curve, tangent_to_curve, pos);
+}
+
PackedVector2Array Curve2D::get_baked_points() const {
if (baked_cache_dirty) {
_bake();
@@ -1184,6 +1224,7 @@ void Curve2D::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_baked_length"), &Curve2D::get_baked_length);
ClassDB::bind_method(D_METHOD("sample_baked", "offset", "cubic"), &Curve2D::sample_baked, DEFVAL(false));
+ ClassDB::bind_method(D_METHOD("sample_baked_with_rotation", "offset", "cubic", "loop", "lookahead"), &Curve2D::sample_baked_with_rotation, DEFVAL(false), DEFVAL(true), DEFVAL(4.0));
ClassDB::bind_method(D_METHOD("get_baked_points"), &Curve2D::get_baked_points);
ClassDB::bind_method(D_METHOD("get_closest_point", "to_point"), &Curve2D::get_closest_point);
ClassDB::bind_method(D_METHOD("get_closest_offset", "to_point"), &Curve2D::get_closest_offset);
diff --git a/scene/resources/curve.h b/scene/resources/curve.h
index 88b6dda096..fa1d35aab1 100644
--- a/scene/resources/curve.h
+++ b/scene/resources/curve.h
@@ -216,6 +216,7 @@ public:
real_t get_baked_length() const;
Vector2 sample_baked(real_t p_offset, bool p_cubic = false) const;
+ Transform2D sample_baked_with_rotation(real_t p_offset, bool p_cubic = false, bool p_loop = true, real_t p_lookahead = 4.0) const;
PackedVector2Array get_baked_points() const; //useful for going through
Vector2 get_closest_point(const Vector2 &p_to_point) const;
real_t get_closest_offset(const Vector2 &p_to_point) const;