summaryrefslogtreecommitdiffstats
path: root/scene
diff options
context:
space:
mode:
Diffstat (limited to 'scene')
-rw-r--r--scene/2d/animated_sprite_2d.cpp16
-rw-r--r--scene/2d/area_2d.cpp38
-rw-r--r--scene/2d/area_2d.h3
-rw-r--r--scene/2d/collision_object_2d.cpp8
-rw-r--r--scene/2d/collision_object_2d.h2
-rw-r--r--scene/2d/navigation_agent_2d.cpp229
-rw-r--r--scene/2d/navigation_agent_2d.h17
-rw-r--r--scene/2d/navigation_link_2d.cpp12
-rw-r--r--scene/2d/navigation_link_2d.h1
-rw-r--r--scene/2d/navigation_obstacle_2d.cpp8
-rw-r--r--scene/2d/navigation_region_2d.cpp12
-rw-r--r--scene/2d/navigation_region_2d.h1
-rw-r--r--scene/2d/physics_body_2d.cpp8
-rw-r--r--scene/2d/polygon_2d.cpp30
-rw-r--r--scene/2d/sprite_2d.cpp55
-rw-r--r--scene/2d/tile_map.cpp228
-rw-r--r--scene/2d/tile_map.h7
-rw-r--r--scene/3d/area_3d.cpp44
-rw-r--r--scene/3d/area_3d.h2
-rw-r--r--scene/3d/camera_3d.h5
-rw-r--r--scene/3d/collision_object_3d.cpp7
-rw-r--r--scene/3d/collision_object_3d.h2
-rw-r--r--scene/3d/label_3d.cpp50
-rw-r--r--scene/3d/lightmap_gi.cpp2
-rw-r--r--scene/3d/navigation_agent_3d.cpp236
-rw-r--r--scene/3d/navigation_agent_3d.h17
-rw-r--r--scene/3d/navigation_link_3d.cpp12
-rw-r--r--scene/3d/navigation_link_3d.h2
-rw-r--r--scene/3d/navigation_obstacle_3d.cpp12
-rw-r--r--scene/3d/navigation_region_3d.cpp11
-rw-r--r--scene/3d/navigation_region_3d.h2
-rw-r--r--scene/3d/path_3d.cpp59
-rw-r--r--scene/3d/physics_body_3d.cpp16
-rw-r--r--scene/3d/sprite_3d.cpp133
-rw-r--r--scene/animation/animation_mixer.cpp230
-rw-r--r--scene/animation/animation_mixer.h103
-rw-r--r--scene/animation/animation_node_state_machine.cpp4
-rw-r--r--scene/animation/animation_player.cpp19
-rw-r--r--scene/animation/animation_player.h3
-rw-r--r--scene/animation/animation_tree.cpp41
-rw-r--r--scene/animation/animation_tree.h2
-rw-r--r--scene/animation/tween.cpp20
-rw-r--r--scene/animation/tween.h1
-rw-r--r--scene/debugger/scene_debugger.cpp5
-rw-r--r--scene/gui/color_picker.cpp23
-rw-r--r--scene/gui/graph_edit.cpp2
-rw-r--r--scene/gui/label.cpp28
-rw-r--r--scene/gui/label.h4
-rw-r--r--scene/gui/line_edit.cpp21
-rw-r--r--scene/gui/menu_bar.cpp48
-rw-r--r--scene/gui/popup_menu.cpp52
-rw-r--r--scene/gui/popup_menu.h6
-rw-r--r--scene/gui/rich_text_label.cpp10
-rw-r--r--scene/gui/tab_bar.cpp4
-rw-r--r--scene/gui/tab_container.cpp17
-rw-r--r--scene/gui/text_edit.cpp12
-rw-r--r--scene/gui/tree.cpp16
-rw-r--r--scene/gui/video_stream_player.cpp10
-rw-r--r--scene/gui/view_panner.cpp11
-rw-r--r--scene/main/canvas_item.cpp18
-rw-r--r--scene/main/node.cpp107
-rw-r--r--scene/main/resource_preloader.cpp5
-rw-r--r--scene/main/scene_tree.cpp19
-rw-r--r--scene/main/scene_tree.h1
-rw-r--r--scene/main/viewport.cpp37
-rw-r--r--scene/main/viewport.h2
-rw-r--r--scene/main/window.cpp32
-rw-r--r--scene/property_utils.cpp21
-rw-r--r--scene/register_scene_types.cpp12
-rw-r--r--scene/resources/animation.cpp789
-rw-r--r--scene/resources/animation.h14
-rw-r--r--scene/resources/camera_attributes.cpp9
-rw-r--r--scene/resources/importer_mesh.cpp18
-rw-r--r--scene/resources/material.cpp2
-rw-r--r--scene/resources/packed_scene.cpp4
-rw-r--r--scene/resources/particle_process_material.cpp9
-rw-r--r--scene/resources/primitive_meshes.cpp13
-rw-r--r--scene/resources/resource_format_text.cpp7
-rw-r--r--scene/resources/skeleton_modification_2d_twoboneik.cpp4
-rw-r--r--scene/resources/sky_material.cpp34
-rw-r--r--scene/resources/sky_material.h5
-rw-r--r--scene/resources/surface_tool.h2
-rw-r--r--scene/resources/text_line.cpp23
-rw-r--r--scene/resources/text_line.h4
-rw-r--r--scene/resources/text_paragraph.cpp25
-rw-r--r--scene/resources/text_paragraph.h4
-rw-r--r--scene/resources/tile_set.compat.inc48
-rw-r--r--scene/resources/tile_set.cpp174
-rw-r--r--scene/resources/tile_set.h29
-rw-r--r--scene/resources/video_stream.cpp1
-rw-r--r--scene/resources/visual_shader.cpp13
91 files changed, 2437 insertions, 1027 deletions
diff --git a/scene/2d/animated_sprite_2d.cpp b/scene/2d/animated_sprite_2d.cpp
index bf3783966b..08b315fa6c 100644
--- a/scene/2d/animated_sprite_2d.cpp
+++ b/scene/2d/animated_sprite_2d.cpp
@@ -381,6 +381,10 @@ float AnimatedSprite2D::get_playing_speed() const {
}
void AnimatedSprite2D::set_centered(bool p_center) {
+ if (centered == p_center) {
+ return;
+ }
+
centered = p_center;
queue_redraw();
item_rect_changed();
@@ -391,6 +395,10 @@ bool AnimatedSprite2D::is_centered() const {
}
void AnimatedSprite2D::set_offset(const Point2 &p_offset) {
+ if (offset == p_offset) {
+ return;
+ }
+
offset = p_offset;
queue_redraw();
item_rect_changed();
@@ -401,6 +409,10 @@ Point2 AnimatedSprite2D::get_offset() const {
}
void AnimatedSprite2D::set_flip_h(bool p_flip) {
+ if (hflip == p_flip) {
+ return;
+ }
+
hflip = p_flip;
queue_redraw();
}
@@ -410,6 +422,10 @@ bool AnimatedSprite2D::is_flipped_h() const {
}
void AnimatedSprite2D::set_flip_v(bool p_flip) {
+ if (vflip == p_flip) {
+ return;
+ }
+
vflip = p_flip;
queue_redraw();
}
diff --git a/scene/2d/area_2d.cpp b/scene/2d/area_2d.cpp
index 3aa2a71a2c..b1ff94dda4 100644
--- a/scene/2d/area_2d.cpp
+++ b/scene/2d/area_2d.cpp
@@ -166,6 +166,21 @@ void Area2D::_body_inout(int p_status, const RID &p_body, ObjectID p_instance, i
bool body_in = p_status == PhysicsServer2D::AREA_BODY_ADDED;
ObjectID objid = p_instance;
+ // Exit early if instance is invalid.
+ if (objid.is_null()) {
+ // Emit the appropriate signals.
+ lock_callback();
+ locked = true;
+ if (body_in) {
+ emit_signal(SceneStringNames::get_singleton()->body_shape_entered, p_body, (Node *)nullptr, p_body_shape, p_area_shape);
+ } else {
+ emit_signal(SceneStringNames::get_singleton()->body_shape_exited, p_body, (Node *)nullptr, p_body_shape, p_area_shape);
+ }
+ locked = false;
+ unlock_callback();
+ return;
+ }
+
Object *obj = ObjectDB::get_instance(objid);
Node *node = Object::cast_to<Node>(obj);
@@ -262,6 +277,21 @@ void Area2D::_area_inout(int p_status, const RID &p_area, ObjectID p_instance, i
bool area_in = p_status == PhysicsServer2D::AREA_BODY_ADDED;
ObjectID objid = p_instance;
+ // Exit early if instance is invalid.
+ if (objid.is_null()) {
+ // Emit the appropriate signals.
+ lock_callback();
+ locked = true;
+ if (area_in) {
+ emit_signal(SceneStringNames::get_singleton()->area_shape_entered, p_area, (Node *)nullptr, p_area_shape, p_self_shape);
+ } else {
+ emit_signal(SceneStringNames::get_singleton()->area_shape_exited, p_area, (Node *)nullptr, p_area_shape, p_self_shape);
+ }
+ locked = false;
+ unlock_callback();
+ return;
+ }
+
Object *obj = ObjectDB::get_instance(objid);
Node *node = Object::cast_to<Node>(obj);
@@ -384,11 +414,9 @@ void Area2D::_clear_monitoring() {
}
}
-void Area2D::_notification(int p_what) {
- switch (p_what) {
- case NOTIFICATION_EXIT_TREE: {
- _clear_monitoring();
- } break;
+void Area2D::_space_changed(const RID &p_new_space) {
+ if (p_new_space.is_null()) {
+ _clear_monitoring();
}
}
diff --git a/scene/2d/area_2d.h b/scene/2d/area_2d.h
index 421c29f758..9d6e04b706 100644
--- a/scene/2d/area_2d.h
+++ b/scene/2d/area_2d.h
@@ -133,10 +133,11 @@ private:
StringName audio_bus;
protected:
- void _notification(int p_what);
static void _bind_methods();
void _validate_property(PropertyInfo &p_property) const;
+ virtual void _space_changed(const RID &p_new_space) override;
+
public:
void set_gravity_space_override_mode(SpaceOverride p_mode);
SpaceOverride get_gravity_space_override_mode() const;
diff --git a/scene/2d/collision_object_2d.cpp b/scene/2d/collision_object_2d.cpp
index ab42c52913..2fbe4eb409 100644
--- a/scene/2d/collision_object_2d.cpp
+++ b/scene/2d/collision_object_2d.cpp
@@ -59,6 +59,7 @@ void CollisionObject2D::_notification(int p_what) {
} else {
PhysicsServer2D::get_singleton()->body_set_space(rid, space);
}
+ _space_changed(space);
}
_update_pickable();
@@ -102,6 +103,7 @@ void CollisionObject2D::_notification(int p_what) {
} else {
PhysicsServer2D::get_singleton()->body_set_space(rid, RID());
}
+ _space_changed(RID());
}
}
@@ -125,6 +127,7 @@ void CollisionObject2D::_notification(int p_what) {
} else {
PhysicsServer2D::get_singleton()->body_set_space(rid, space);
}
+ _space_changed(space);
} break;
case NOTIFICATION_DISABLED: {
@@ -246,6 +249,7 @@ void CollisionObject2D::_apply_disabled() {
} else {
PhysicsServer2D::get_singleton()->body_set_space(rid, RID());
}
+ _space_changed(RID());
}
}
} break;
@@ -272,6 +276,7 @@ void CollisionObject2D::_apply_enabled() {
} else {
PhysicsServer2D::get_singleton()->body_set_space(rid, space);
}
+ _space_changed(space);
}
} break;
@@ -569,6 +574,9 @@ void CollisionObject2D::set_body_mode(PhysicsServer2D::BodyMode p_mode) {
PhysicsServer2D::get_singleton()->body_set_mode(rid, p_mode);
}
+void CollisionObject2D::_space_changed(const RID &p_new_space) {
+}
+
void CollisionObject2D::_update_pickable() {
if (!is_inside_tree()) {
return;
diff --git a/scene/2d/collision_object_2d.h b/scene/2d/collision_object_2d.h
index 88429b145d..780793f289 100644
--- a/scene/2d/collision_object_2d.h
+++ b/scene/2d/collision_object_2d.h
@@ -109,6 +109,8 @@ protected:
void set_body_mode(PhysicsServer2D::BodyMode p_mode);
+ virtual void _space_changed(const RID &p_new_space);
+
GDVIRTUAL3(_input_event, Viewport *, Ref<InputEvent>, int)
GDVIRTUAL0(_mouse_enter)
GDVIRTUAL0(_mouse_exit)
diff --git a/scene/2d/navigation_agent_2d.cpp b/scene/2d/navigation_agent_2d.cpp
index 1b3b0bcef0..dbbba315d4 100644
--- a/scene/2d/navigation_agent_2d.cpp
+++ b/scene/2d/navigation_agent_2d.cpp
@@ -274,7 +274,6 @@ void NavigationAgent2D::_notification(int p_what) {
NavigationServer2D::get_singleton()->agent_set_velocity_forced(agent, velocity_forced);
}
}
- _check_distance_to_target();
}
#ifdef DEBUG_ENABLED
if (debug_path_dirty) {
@@ -294,6 +293,13 @@ NavigationAgent2D::NavigationAgent2D() {
NavigationServer2D::get_singleton()->agent_set_time_horizon_obstacles(agent, time_horizon_obstacles);
NavigationServer2D::get_singleton()->agent_set_radius(agent, radius);
NavigationServer2D::get_singleton()->agent_set_max_speed(agent, max_speed);
+ NavigationServer2D::get_singleton()->agent_set_avoidance_layers(agent, avoidance_layers);
+ NavigationServer2D::get_singleton()->agent_set_avoidance_mask(agent, avoidance_mask);
+ NavigationServer2D::get_singleton()->agent_set_avoidance_priority(agent, avoidance_priority);
+ NavigationServer2D::get_singleton()->agent_set_avoidance_enabled(agent, avoidance_enabled);
+ if (avoidance_enabled) {
+ NavigationServer2D::get_singleton()->agent_set_avoidance_callback(agent, callable_mp(this, &NavigationAgent2D::_avoidance_done));
+ }
// Preallocate query and result objects to improve performance.
navigation_query = Ref<NavigationPathQueryParameters2D>();
@@ -302,11 +308,6 @@ NavigationAgent2D::NavigationAgent2D() {
navigation_result = Ref<NavigationPathQueryResult2D>();
navigation_result.instantiate();
- set_avoidance_layers(avoidance_layers);
- set_avoidance_mask(avoidance_mask);
- set_avoidance_priority(avoidance_priority);
- set_avoidance_enabled(avoidance_enabled);
-
#ifdef DEBUG_ENABLED
NavigationServer2D::get_singleton()->connect(SNAME("navigation_debug_changed"), callable_mp(this, &NavigationAgent2D::_navigation_debug_changed));
#endif // DEBUG_ENABLED
@@ -556,7 +557,7 @@ Vector2 NavigationAgent2D::get_target_position() const {
}
Vector2 NavigationAgent2D::get_next_path_position() {
- update_navigation();
+ _update_navigation();
const Vector<Vector2> &navigation_path = navigation_result->get_path();
if (navigation_path.size() == 0) {
@@ -577,17 +578,25 @@ bool NavigationAgent2D::is_target_reached() const {
}
bool NavigationAgent2D::is_target_reachable() {
- return target_desired_distance >= get_final_position().distance_to(target_position);
+ _update_navigation();
+ return _is_target_reachable();
+}
+
+bool NavigationAgent2D::_is_target_reachable() const {
+ return target_desired_distance >= _get_final_position().distance_to(target_position);
}
bool NavigationAgent2D::is_navigation_finished() {
- update_navigation();
+ _update_navigation();
return navigation_finished;
}
Vector2 NavigationAgent2D::get_final_position() {
- update_navigation();
+ _update_navigation();
+ return _get_final_position();
+}
+Vector2 NavigationAgent2D::_get_final_position() const {
const Vector<Vector2> &navigation_path = navigation_result->get_path();
if (navigation_path.size() == 0) {
return Vector2();
@@ -625,7 +634,7 @@ PackedStringArray NavigationAgent2D::get_configuration_warnings() const {
return warnings;
}
-void NavigationAgent2D::update_navigation() {
+void NavigationAgent2D::_update_navigation() {
if (agent_parent == nullptr) {
return;
}
@@ -679,6 +688,7 @@ void NavigationAgent2D::update_navigation() {
debug_path_dirty = true;
#endif // DEBUG_ENABLED
navigation_finished = false;
+ last_waypoint_reached = false;
navigation_path_index = 0;
emit_signal(SNAME("path_changed"));
}
@@ -687,102 +697,145 @@ void NavigationAgent2D::update_navigation() {
return;
}
- // Check if we can advance the navigation path
- if (navigation_finished == false) {
- // Advances to the next far away position.
- const Vector<Vector2> &navigation_path = navigation_result->get_path();
- const Vector<int32_t> &navigation_path_types = navigation_result->get_path_types();
- const TypedArray<RID> &navigation_path_rids = navigation_result->get_path_rids();
- const Vector<int64_t> &navigation_path_owners = navigation_result->get_path_owner_ids();
+ // Check if the navigation has already finished.
+ if (navigation_finished) {
+ return;
+ }
- while (origin.distance_to(navigation_path[navigation_path_index]) < path_desired_distance) {
- Dictionary details;
+ // Check if we reached the target.
+ if (_is_within_target_distance(origin)) {
+ // Emit waypoint_reached in case we also moved within distance of a waypoint.
+ _advance_waypoints(origin);
+ _transition_to_target_reached();
+ _transition_to_navigation_finished();
+ } else {
+ // Advance waypoints if possible.
+ _advance_waypoints(origin);
+ // Keep navigation running even after reaching the last waypoint if the target is reachable.
+ if (last_waypoint_reached && !_is_target_reachable()) {
+ _transition_to_navigation_finished();
+ }
+ }
+}
- const Vector2 waypoint = navigation_path[navigation_path_index];
- details[SNAME("position")] = waypoint;
+void NavigationAgent2D::_advance_waypoints(const Vector2 &p_origin) {
+ if (last_waypoint_reached) {
+ return;
+ }
- int waypoint_type = -1;
- if (path_metadata_flags.has_flag(NavigationPathQueryParameters2D::PathMetadataFlags::PATH_METADATA_INCLUDE_TYPES)) {
- const NavigationPathQueryResult2D::PathSegmentType type = NavigationPathQueryResult2D::PathSegmentType(navigation_path_types[navigation_path_index]);
+ // Advance to the farthest possible waypoint.
+ while (_is_within_waypoint_distance(p_origin)) {
+ _trigger_waypoint_reached();
- details[SNAME("type")] = type;
- waypoint_type = type;
- }
+ if (_is_last_waypoint()) {
+ last_waypoint_reached = true;
+ break;
+ }
- if (path_metadata_flags.has_flag(NavigationPathQueryParameters2D::PathMetadataFlags::PATH_METADATA_INCLUDE_RIDS)) {
- details[SNAME("rid")] = navigation_path_rids[navigation_path_index];
- }
+ _move_to_next_waypoint();
+ }
+}
- if (path_metadata_flags.has_flag(NavigationPathQueryParameters2D::PathMetadataFlags::PATH_METADATA_INCLUDE_OWNERS)) {
- const ObjectID waypoint_owner_id = ObjectID(navigation_path_owners[navigation_path_index]);
+void NavigationAgent2D::_request_repath() {
+ navigation_result->reset();
+ target_reached = false;
+ navigation_finished = false;
+ last_waypoint_reached = false;
+ update_frame_id = 0;
+}
- // Get a reference to the owning object.
- Object *owner = nullptr;
- if (waypoint_owner_id.is_valid()) {
- owner = ObjectDB::get_instance(waypoint_owner_id);
- }
+bool NavigationAgent2D::_is_last_waypoint() const {
+ return navigation_path_index == navigation_result->get_path().size() - 1;
+}
- details[SNAME("owner")] = owner;
-
- if (waypoint_type == NavigationPathQueryResult2D::PATH_SEGMENT_TYPE_LINK) {
- const NavigationLink2D *navlink = Object::cast_to<NavigationLink2D>(owner);
- if (navlink) {
- Vector2 link_global_start_position = navlink->get_global_start_position();
- Vector2 link_global_end_position = navlink->get_global_end_position();
- if (waypoint.distance_to(link_global_start_position) < waypoint.distance_to(link_global_end_position)) {
- details[SNAME("link_entry_position")] = link_global_start_position;
- details[SNAME("link_exit_position")] = link_global_end_position;
- } else {
- details[SNAME("link_entry_position")] = link_global_end_position;
- details[SNAME("link_exit_position")] = link_global_start_position;
- }
- }
- }
- }
+void NavigationAgent2D::_move_to_next_waypoint() {
+ navigation_path_index += 1;
+}
- // Emit a signal for the waypoint
- emit_signal(SNAME("waypoint_reached"), details);
+bool NavigationAgent2D::_is_within_waypoint_distance(const Vector2 &p_origin) const {
+ const Vector<Vector2> &navigation_path = navigation_result->get_path();
+ return p_origin.distance_to(navigation_path[navigation_path_index]) < path_desired_distance;
+}
- // Emit a signal if we've reached a navigation link
- if (waypoint_type == NavigationPathQueryResult2D::PATH_SEGMENT_TYPE_LINK) {
- emit_signal(SNAME("link_reached"), details);
- }
+bool NavigationAgent2D::_is_within_target_distance(const Vector2 &p_origin) const {
+ return p_origin.distance_to(target_position) < target_desired_distance;
+}
+
+void NavigationAgent2D::_trigger_waypoint_reached() {
+ const Vector<Vector2> &navigation_path = navigation_result->get_path();
+ const Vector<int32_t> &navigation_path_types = navigation_result->get_path_types();
+ const TypedArray<RID> &navigation_path_rids = navigation_result->get_path_rids();
+ const Vector<int64_t> &navigation_path_owners = navigation_result->get_path_owner_ids();
+
+ Dictionary details;
- // Move to the next waypoint on the list
- navigation_path_index += 1;
-
- // Check to see if we've finished our route
- if (navigation_path_index == navigation_path.size()) {
- _check_distance_to_target();
- navigation_path_index -= 1;
- navigation_finished = true;
- target_position_submitted = false;
- if (avoidance_enabled) {
- NavigationServer2D::get_singleton()->agent_set_position(agent, agent_parent->get_global_position());
- NavigationServer2D::get_singleton()->agent_set_velocity(agent, Vector2(0.0, 0.0));
- NavigationServer2D::get_singleton()->agent_set_velocity_forced(agent, Vector2(0.0, 0.0));
+ const Vector2 waypoint = navigation_path[navigation_path_index];
+ details[SNAME("position")] = waypoint;
+
+ int waypoint_type = -1;
+ if (path_metadata_flags.has_flag(NavigationPathQueryParameters2D::PathMetadataFlags::PATH_METADATA_INCLUDE_TYPES)) {
+ const NavigationPathQueryResult2D::PathSegmentType type = NavigationPathQueryResult2D::PathSegmentType(navigation_path_types[navigation_path_index]);
+
+ details[SNAME("type")] = type;
+ waypoint_type = type;
+ }
+
+ if (path_metadata_flags.has_flag(NavigationPathQueryParameters2D::PathMetadataFlags::PATH_METADATA_INCLUDE_RIDS)) {
+ details[SNAME("rid")] = navigation_path_rids[navigation_path_index];
+ }
+
+ if (path_metadata_flags.has_flag(NavigationPathQueryParameters2D::PathMetadataFlags::PATH_METADATA_INCLUDE_OWNERS)) {
+ const ObjectID waypoint_owner_id = ObjectID(navigation_path_owners[navigation_path_index]);
+
+ // Get a reference to the owning object.
+ Object *owner = nullptr;
+ if (waypoint_owner_id.is_valid()) {
+ owner = ObjectDB::get_instance(waypoint_owner_id);
+ }
+
+ details[SNAME("owner")] = owner;
+
+ if (waypoint_type == NavigationPathQueryResult2D::PATH_SEGMENT_TYPE_LINK) {
+ const NavigationLink2D *navlink = Object::cast_to<NavigationLink2D>(owner);
+ if (navlink) {
+ Vector2 link_global_start_position = navlink->get_global_start_position();
+ Vector2 link_global_end_position = navlink->get_global_end_position();
+ if (waypoint.distance_to(link_global_start_position) < waypoint.distance_to(link_global_end_position)) {
+ details[SNAME("link_entry_position")] = link_global_start_position;
+ details[SNAME("link_exit_position")] = link_global_end_position;
+ } else {
+ details[SNAME("link_entry_position")] = link_global_end_position;
+ details[SNAME("link_exit_position")] = link_global_start_position;
}
- emit_signal(SNAME("navigation_finished"));
- break;
}
}
}
-}
-void NavigationAgent2D::_request_repath() {
- navigation_result->reset();
- target_reached = false;
- navigation_finished = false;
- update_frame_id = 0;
+ // Emit a signal for the waypoint.
+ emit_signal(SNAME("waypoint_reached"), details);
+
+ // Emit a signal if we've reached a navigation link.
+ if (waypoint_type == NavigationPathQueryResult2D::PATH_SEGMENT_TYPE_LINK) {
+ emit_signal(SNAME("link_reached"), details);
+ }
}
-void NavigationAgent2D::_check_distance_to_target() {
- if (!target_reached) {
- if (distance_to_target() < target_desired_distance) {
- target_reached = true;
- emit_signal(SNAME("target_reached"));
- }
+void NavigationAgent2D::_transition_to_navigation_finished() {
+ navigation_finished = true;
+ target_position_submitted = false;
+
+ if (avoidance_enabled) {
+ NavigationServer2D::get_singleton()->agent_set_position(agent, agent_parent->get_global_position());
+ NavigationServer2D::get_singleton()->agent_set_velocity(agent, Vector2(0.0, 0.0));
+ NavigationServer2D::get_singleton()->agent_set_velocity_forced(agent, Vector2(0.0, 0.0));
}
+
+ emit_signal(SNAME("navigation_finished"));
+}
+
+void NavigationAgent2D::_transition_to_target_reached() {
+ target_reached = true;
+ emit_signal(SNAME("target_reached"));
}
void NavigationAgent2D::set_avoidance_layers(uint32_t p_layers) {
diff --git a/scene/2d/navigation_agent_2d.h b/scene/2d/navigation_agent_2d.h
index 10c703168b..0e46086a81 100644
--- a/scene/2d/navigation_agent_2d.h
+++ b/scene/2d/navigation_agent_2d.h
@@ -88,6 +88,7 @@ class NavigationAgent2D : public Node {
bool target_reached = false;
bool navigation_finished = true;
+ bool last_waypoint_reached = false;
// No initialized on purpose
uint32_t update_frame_id = 0;
@@ -232,9 +233,21 @@ public:
float get_debug_path_custom_line_width() const;
private:
- void update_navigation();
+ bool _is_target_reachable() const;
+ Vector2 _get_final_position() const;
+
+ void _update_navigation();
+ void _advance_waypoints(const Vector2 &p_origin);
void _request_repath();
- void _check_distance_to_target();
+
+ bool _is_last_waypoint() const;
+ void _move_to_next_waypoint();
+ bool _is_within_waypoint_distance(const Vector2 &p_origin) const;
+ bool _is_within_target_distance(const Vector2 &p_origin) const;
+
+ void _trigger_waypoint_reached();
+ void _transition_to_navigation_finished();
+ void _transition_to_target_reached();
#ifdef DEBUG_ENABLED
void _navigation_debug_changed();
diff --git a/scene/2d/navigation_link_2d.cpp b/scene/2d/navigation_link_2d.cpp
index 95798b6856..04ba550888 100644
--- a/scene/2d/navigation_link_2d.cpp
+++ b/scene/2d/navigation_link_2d.cpp
@@ -36,6 +36,8 @@
#include "servers/navigation_server_3d.h"
void NavigationLink2D::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("get_rid"), &NavigationLink2D::get_rid);
+
ClassDB::bind_method(D_METHOD("set_enabled", "enabled"), &NavigationLink2D::set_enabled);
ClassDB::bind_method(D_METHOD("is_enabled"), &NavigationLink2D::is_enabled);
@@ -175,6 +177,10 @@ bool NavigationLink2D::_edit_is_selected_on_click(const Point2 &p_point, double
}
#endif // TOOLS_ENABLED
+RID NavigationLink2D::get_rid() const {
+ return link;
+}
+
void NavigationLink2D::set_enabled(bool p_enabled) {
if (enabled == p_enabled) {
return;
@@ -343,7 +349,13 @@ PackedStringArray NavigationLink2D::get_configuration_warnings() const {
NavigationLink2D::NavigationLink2D() {
link = NavigationServer2D::get_singleton()->link_create();
+
NavigationServer2D::get_singleton()->link_set_owner_id(link, get_instance_id());
+ NavigationServer2D::get_singleton()->link_set_enter_cost(link, enter_cost);
+ NavigationServer2D::get_singleton()->link_set_travel_cost(link, travel_cost);
+ NavigationServer2D::get_singleton()->link_set_navigation_layers(link, navigation_layers);
+ NavigationServer2D::get_singleton()->link_set_bidirectional(link, bidirectional);
+ NavigationServer2D::get_singleton()->link_set_enabled(link, enabled);
set_notify_transform(true);
set_hide_clip_children(true);
diff --git a/scene/2d/navigation_link_2d.h b/scene/2d/navigation_link_2d.h
index 4259740c90..2929691c04 100644
--- a/scene/2d/navigation_link_2d.h
+++ b/scene/2d/navigation_link_2d.h
@@ -61,6 +61,7 @@ public:
virtual Rect2 _edit_get_rect() const override;
virtual bool _edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const override;
#endif
+ RID get_rid() const;
void set_enabled(bool p_enabled);
bool is_enabled() const { return enabled; }
diff --git a/scene/2d/navigation_obstacle_2d.cpp b/scene/2d/navigation_obstacle_2d.cpp
index d993b8a400..60fb64a8e2 100644
--- a/scene/2d/navigation_obstacle_2d.cpp
+++ b/scene/2d/navigation_obstacle_2d.cpp
@@ -148,10 +148,10 @@ void NavigationObstacle2D::_notification(int p_what) {
NavigationObstacle2D::NavigationObstacle2D() {
obstacle = NavigationServer2D::get_singleton()->obstacle_create();
- set_radius(radius);
- set_vertices(vertices);
- set_avoidance_layers(avoidance_layers);
- set_avoidance_enabled(avoidance_enabled);
+ NavigationServer2D::get_singleton()->obstacle_set_radius(obstacle, radius);
+ NavigationServer2D::get_singleton()->obstacle_set_vertices(obstacle, vertices);
+ NavigationServer2D::get_singleton()->obstacle_set_avoidance_layers(obstacle, avoidance_layers);
+ NavigationServer2D::get_singleton()->obstacle_set_avoidance_enabled(obstacle, avoidance_enabled);
}
NavigationObstacle2D::~NavigationObstacle2D() {
diff --git a/scene/2d/navigation_region_2d.cpp b/scene/2d/navigation_region_2d.cpp
index 78733f04e4..8e4b6bfa19 100644
--- a/scene/2d/navigation_region_2d.cpp
+++ b/scene/2d/navigation_region_2d.cpp
@@ -35,6 +35,10 @@
#include "scene/resources/world_2d.h"
#include "servers/navigation_server_2d.h"
+RID NavigationRegion2D::get_rid() const {
+ return region;
+}
+
void NavigationRegion2D::set_enabled(bool p_enabled) {
if (enabled == p_enabled) {
return;
@@ -136,7 +140,7 @@ real_t NavigationRegion2D::get_travel_cost() const {
}
RID NavigationRegion2D::get_region_rid() const {
- return region;
+ return get_rid();
}
#ifdef TOOLS_ENABLED
@@ -165,6 +169,7 @@ void NavigationRegion2D::_notification(int p_what) {
case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: {
set_physics_process_internal(false);
+ _region_update_transform();
} break;
case NOTIFICATION_DRAW: {
@@ -279,6 +284,8 @@ PackedStringArray NavigationRegion2D::get_configuration_warnings() const {
}
void NavigationRegion2D::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("get_rid"), &NavigationRegion2D::get_rid);
+
ClassDB::bind_method(D_METHOD("set_navigation_polygon", "navigation_polygon"), &NavigationRegion2D::set_navigation_polygon);
ClassDB::bind_method(D_METHOD("get_navigation_polygon"), &NavigationRegion2D::get_navigation_polygon);
@@ -356,6 +363,9 @@ NavigationRegion2D::NavigationRegion2D() {
NavigationServer2D::get_singleton()->region_set_owner_id(region, get_instance_id());
NavigationServer2D::get_singleton()->region_set_enter_cost(region, get_enter_cost());
NavigationServer2D::get_singleton()->region_set_travel_cost(region, get_travel_cost());
+ NavigationServer2D::get_singleton()->region_set_navigation_layers(region, navigation_layers);
+ NavigationServer2D::get_singleton()->region_set_use_edge_connections(region, use_edge_connections);
+ NavigationServer2D::get_singleton()->region_set_enabled(region, enabled);
#ifdef DEBUG_ENABLED
NavigationServer2D::get_singleton()->connect(SNAME("map_changed"), callable_mp(this, &NavigationRegion2D::_navigation_map_changed));
diff --git a/scene/2d/navigation_region_2d.h b/scene/2d/navigation_region_2d.h
index 36e889877a..3b880dd00d 100644
--- a/scene/2d/navigation_region_2d.h
+++ b/scene/2d/navigation_region_2d.h
@@ -76,6 +76,7 @@ public:
virtual Rect2 _edit_get_rect() const override;
virtual bool _edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const override;
#endif
+ RID get_rid() const;
void set_enabled(bool p_enabled);
bool is_enabled() const;
diff --git a/scene/2d/physics_body_2d.cpp b/scene/2d/physics_body_2d.cpp
index 45533732c7..6af5a8dd80 100644
--- a/scene/2d/physics_body_2d.cpp
+++ b/scene/2d/physics_body_2d.cpp
@@ -451,10 +451,14 @@ void RigidBody2D::_body_state_changed(PhysicsDirectBodyState2D *p_state) {
if (GDVIRTUAL_IS_OVERRIDDEN(_integrate_forces)) {
_sync_body_state(p_state);
+ Transform2D old_transform = get_global_transform();
GDVIRTUAL_CALL(_integrate_forces, p_state);
+ Transform2D new_transform = get_global_transform();
- // Update the physics server with any new transform, to prevent it from being overwritten at the sync below.
- force_update_transform();
+ if (new_transform != old_transform) {
+ // Update the physics server with the new transform, to prevent it from being overwritten at the sync below.
+ PhysicsServer2D::get_singleton()->body_set_state(get_rid(), PhysicsServer2D::BODY_STATE_TRANSFORM, new_transform);
+ }
}
_sync_body_state(p_state);
diff --git a/scene/2d/polygon_2d.cpp b/scene/2d/polygon_2d.cpp
index bda7b495e1..ee186de5f1 100644
--- a/scene/2d/polygon_2d.cpp
+++ b/scene/2d/polygon_2d.cpp
@@ -101,7 +101,12 @@ void Polygon2D::_skeleton_bone_setup_changed() {
}
void Polygon2D::_notification(int p_what) {
+ if (p_what == NOTIFICATION_TRANSFORM_CHANGED && !Engine::get_singleton()->is_editor_hint()) {
+ return; // Mesh recreation for NOTIFICATION_TRANSFORM_CHANGED is only needed in editor.
+ }
+
switch (p_what) {
+ case NOTIFICATION_TRANSFORM_CHANGED:
case NOTIFICATION_DRAW: {
if (polygon.size() < 3) {
return;
@@ -364,7 +369,30 @@ void Polygon2D::_notification(int p_what) {
arr[RS::ARRAY_INDEX] = index_array;
- RS::get_singleton()->mesh_add_surface_from_arrays(mesh, RS::PRIMITIVE_TRIANGLES, arr, Array(), Dictionary(), RS::ARRAY_FLAG_USE_2D_VERTICES);
+ RS::SurfaceData sd;
+
+ if (skeleton_node) {
+ // Compute transform between mesh and skeleton for runtime AABB compute.
+ const Transform2D mesh_transform = get_global_transform();
+ const Transform2D skeleton_transform = skeleton_node->get_global_transform();
+ const Transform2D mesh_to_sk2d = mesh_transform * skeleton_transform.affine_inverse();
+
+ // Convert 2d transform to 3d.
+ sd.mesh_to_skeleton_xform.basis.rows[0][0] = mesh_to_sk2d.columns[0][0];
+ sd.mesh_to_skeleton_xform.basis.rows[0][1] = mesh_to_sk2d.columns[0][1];
+ sd.mesh_to_skeleton_xform.origin.x = mesh_to_sk2d.get_origin().x;
+
+ sd.mesh_to_skeleton_xform.basis.rows[1][0] = mesh_to_sk2d.columns[1][0];
+ sd.mesh_to_skeleton_xform.basis.rows[1][1] = mesh_to_sk2d.columns[1][1];
+ sd.mesh_to_skeleton_xform.origin.y = mesh_to_sk2d.get_origin().y;
+ }
+
+ Error err = RS::get_singleton()->mesh_create_surface_data_from_arrays(&sd, RS::PRIMITIVE_TRIANGLES, arr, Array(), Dictionary(), RS::ARRAY_FLAG_USE_2D_VERTICES);
+ if (err != OK) {
+ return;
+ }
+
+ RS::get_singleton()->mesh_add_surface(mesh, sd);
RS::get_singleton()->canvas_item_add_mesh(get_canvas_item(), mesh, Transform2D(), Color(1, 1, 1), texture.is_valid() ? texture->get_rid() : RID());
}
diff --git a/scene/2d/sprite_2d.cpp b/scene/2d/sprite_2d.cpp
index 7e6b43559c..d3be5b9c65 100644
--- a/scene/2d/sprite_2d.cpp
+++ b/scene/2d/sprite_2d.cpp
@@ -155,6 +155,10 @@ Ref<Texture2D> Sprite2D::get_texture() const {
}
void Sprite2D::set_centered(bool p_center) {
+ if (centered == p_center) {
+ return;
+ }
+
centered = p_center;
queue_redraw();
item_rect_changed();
@@ -165,6 +169,10 @@ bool Sprite2D::is_centered() const {
}
void Sprite2D::set_offset(const Point2 &p_offset) {
+ if (offset == p_offset) {
+ return;
+ }
+
offset = p_offset;
queue_redraw();
item_rect_changed();
@@ -175,6 +183,10 @@ Point2 Sprite2D::get_offset() const {
}
void Sprite2D::set_flip_h(bool p_flip) {
+ if (hflip == p_flip) {
+ return;
+ }
+
hflip = p_flip;
queue_redraw();
}
@@ -184,6 +196,10 @@ bool Sprite2D::is_flipped_h() const {
}
void Sprite2D::set_flip_v(bool p_flip) {
+ if (vflip == p_flip) {
+ return;
+ }
+
vflip = p_flip;
queue_redraw();
}
@@ -193,7 +209,7 @@ bool Sprite2D::is_flipped_v() const {
}
void Sprite2D::set_region_enabled(bool p_region_enabled) {
- if (p_region_enabled == region_enabled) {
+ if (region_enabled == p_region_enabled) {
return;
}
@@ -223,6 +239,10 @@ Rect2 Sprite2D::get_region_rect() const {
}
void Sprite2D::set_region_filter_clip_enabled(bool p_region_filter_clip_enabled) {
+ if (region_filter_clip_enabled == p_region_filter_clip_enabled) {
+ return;
+ }
+
region_filter_clip_enabled = p_region_filter_clip_enabled;
queue_redraw();
}
@@ -234,12 +254,12 @@ bool Sprite2D::is_region_filter_clip_enabled() const {
void Sprite2D::set_frame(int p_frame) {
ERR_FAIL_INDEX(p_frame, vframes * hframes);
- if (frame != p_frame) {
- item_rect_changed();
+ if (frame == p_frame) {
+ return;
}
frame = p_frame;
-
+ item_rect_changed();
emit_signal(SceneStringNames::get_singleton()->frame_changed);
}
@@ -260,7 +280,15 @@ Vector2i Sprite2D::get_frame_coords() const {
void Sprite2D::set_vframes(int p_amount) {
ERR_FAIL_COND_MSG(p_amount < 1, "Amount of vframes cannot be smaller than 1.");
+
+ if (vframes == p_amount) {
+ return;
+ }
+
vframes = p_amount;
+ if (frame >= vframes * hframes) {
+ frame = 0;
+ }
queue_redraw();
item_rect_changed();
notify_property_list_changed();
@@ -272,7 +300,26 @@ int Sprite2D::get_vframes() const {
void Sprite2D::set_hframes(int p_amount) {
ERR_FAIL_COND_MSG(p_amount < 1, "Amount of hframes cannot be smaller than 1.");
+
+ if (hframes == p_amount) {
+ return;
+ }
+
+ if (vframes > 1) {
+ // Adjust the frame to fit new sheet dimensions.
+ int original_column = frame % hframes;
+ if (original_column >= p_amount) {
+ // Frame's column was dropped, reset.
+ frame = 0;
+ } else {
+ int original_row = frame / hframes;
+ frame = original_row * p_amount + original_column;
+ }
+ }
hframes = p_amount;
+ if (frame >= vframes * hframes) {
+ frame = 0;
+ }
queue_redraw();
item_rect_changed();
notify_property_list_changed();
diff --git a/scene/2d/tile_map.cpp b/scene/2d/tile_map.cpp
index 1ea342c3f4..f2be91cbf3 100644
--- a/scene/2d/tile_map.cpp
+++ b/scene/2d/tile_map.cpp
@@ -447,6 +447,9 @@ void TileMapLayer::_rendering_update() {
for (KeyValue<Vector2i, CellData> &kv : tile_map) {
CellData &cell_data = kv.value;
for (const RID &occluder : cell_data.occluders) {
+ if (occluder.is_null()) {
+ continue;
+ }
Transform2D xform(0, tile_map_node->map_to_local(kv.key));
rs->canvas_light_occluder_attach_to_canvas(occluder, tile_map_node->get_canvas());
rs->canvas_light_occluder_set_transform(occluder, tile_map_node->get_global_transform() * xform);
@@ -565,6 +568,15 @@ void TileMapLayer::_rendering_occluders_update_cell(CellData &r_cell_data) {
const Ref<TileSet> &tile_set = tile_map_node->get_tileset();
RenderingServer *rs = RenderingServer::get_singleton();
+ // Free unused occluders then resize the occluders array.
+ for (uint32_t i = tile_set->get_occlusion_layers_count(); i < r_cell_data.occluders.size(); i++) {
+ RID occluder_id = r_cell_data.occluders[i];
+ if (occluder_id.is_valid()) {
+ rs->free(occluder_id);
+ }
+ }
+ r_cell_data.occluders.resize(tile_set->get_occlusion_layers_count());
+
TileSetSource *source;
if (tile_set->has_source(r_cell_data.cell.source_id)) {
source = *tile_set->get_source(r_cell_data.cell.source_id);
@@ -580,18 +592,35 @@ void TileMapLayer::_rendering_occluders_update_cell(CellData &r_cell_data) {
tile_data = atlas_source->get_tile_data(r_cell_data.cell.get_atlas_coords(), r_cell_data.cell.alternative_tile);
}
- // Update/create occluders.
- for (int i = 0; i < tile_set->get_occlusion_layers_count(); i++) {
- Transform2D xform;
- xform.set_origin(tile_map_node->map_to_local(r_cell_data.coords));
- if (tile_data->get_occluder(i).is_valid()) {
- RID occluder_id = rs->canvas_light_occluder_create();
- rs->canvas_light_occluder_set_enabled(occluder_id, node_visible);
- rs->canvas_light_occluder_set_transform(occluder_id, tile_map_node->get_global_transform() * xform);
- rs->canvas_light_occluder_set_polygon(occluder_id, tile_map_node->get_transformed_polygon(Ref<Resource>(tile_data->get_occluder(i)), r_cell_data.cell.alternative_tile)->get_rid());
- rs->canvas_light_occluder_attach_to_canvas(occluder_id, tile_map_node->get_canvas());
- rs->canvas_light_occluder_set_light_mask(occluder_id, tile_set->get_occlusion_layer_light_mask(i));
- r_cell_data.occluders.push_back(occluder_id);
+ // Transform flags.
+ bool flip_h = (r_cell_data.cell.alternative_tile & TileSetAtlasSource::TRANSFORM_FLIP_H);
+ bool flip_v = (r_cell_data.cell.alternative_tile & TileSetAtlasSource::TRANSFORM_FLIP_V);
+ bool transpose = (r_cell_data.cell.alternative_tile & TileSetAtlasSource::TRANSFORM_TRANSPOSE);
+
+ // Create, update or clear occluders.
+ for (uint32_t occlusion_layer_index = 0; occlusion_layer_index < r_cell_data.occluders.size(); occlusion_layer_index++) {
+ Ref<OccluderPolygon2D> occluder_polygon = tile_data->get_occluder(occlusion_layer_index);
+
+ RID &occluder = r_cell_data.occluders[occlusion_layer_index];
+
+ if (occluder_polygon.is_valid()) {
+ // Create or update occluder.
+ Transform2D xform;
+ xform.set_origin(tile_map_node->map_to_local(r_cell_data.coords));
+ if (!occluder.is_valid()) {
+ occluder = rs->canvas_light_occluder_create();
+ }
+ rs->canvas_light_occluder_set_enabled(occluder, node_visible);
+ rs->canvas_light_occluder_set_transform(occluder, tile_map_node->get_global_transform() * xform);
+ rs->canvas_light_occluder_set_polygon(occluder, tile_data->get_occluder(occlusion_layer_index, flip_h, flip_v, transpose)->get_rid());
+ rs->canvas_light_occluder_attach_to_canvas(occluder, tile_map_node->get_canvas());
+ rs->canvas_light_occluder_set_light_mask(occluder, tile_set->get_occlusion_layer_light_mask(occlusion_layer_index));
+ } else {
+ // Clear occluder.
+ if (occluder.is_valid()) {
+ rs->free(occluder);
+ occluder = RID();
+ }
}
}
@@ -686,6 +715,8 @@ void TileMapLayer::_physics_update() {
void TileMapLayer::_physics_notify_tilemap_change(TileMapLayer::DirtyFlags p_what) {
Transform2D gl_transform = tile_map_node->get_global_transform();
+ PhysicsServer2D *ps = PhysicsServer2D::get_singleton();
+
bool in_editor = false;
#ifdef TOOLS_ENABLED
in_editor = Engine::get_singleton()->is_editor_hint();
@@ -693,6 +724,7 @@ void TileMapLayer::_physics_notify_tilemap_change(TileMapLayer::DirtyFlags p_wha
if (p_what == DIRTY_FLAGS_TILE_MAP_XFORM) {
if (tile_map_node->is_inside_tree() && (!tile_map_node->is_collision_animatable() || in_editor)) {
+ // Move the collisison shapes along with the TileMap.
for (KeyValue<Vector2i, CellData> &kv : tile_map) {
const CellData &cell_data = kv.value;
@@ -700,12 +732,13 @@ void TileMapLayer::_physics_notify_tilemap_change(TileMapLayer::DirtyFlags p_wha
if (body.is_valid()) {
Transform2D xform(0, tile_map_node->map_to_local(bodies_coords[body]));
xform = gl_transform * xform;
- PhysicsServer2D::get_singleton()->body_set_state(body, PhysicsServer2D::BODY_STATE_TRANSFORM, xform);
+ ps->body_set_state(body, PhysicsServer2D::BODY_STATE_TRANSFORM, xform);
}
}
}
}
} else if (p_what == DIRTY_FLAGS_TILE_MAP_LOCAL_XFORM) {
+ // With collisions animatable, move the collisison shapes along with the TileMap only on local xform change (they are synchornized on physics tick instead).
if (tile_map_node->is_inside_tree() && tile_map_node->is_collision_animatable() && !in_editor) {
for (KeyValue<Vector2i, CellData> &kv : tile_map) {
const CellData &cell_data = kv.value;
@@ -714,7 +747,22 @@ void TileMapLayer::_physics_notify_tilemap_change(TileMapLayer::DirtyFlags p_wha
if (body.is_valid()) {
Transform2D xform(0, tile_map_node->map_to_local(bodies_coords[body]));
xform = gl_transform * xform;
- PhysicsServer2D::get_singleton()->body_set_state(body, PhysicsServer2D::BODY_STATE_TRANSFORM, xform);
+ ps->body_set_state(body, PhysicsServer2D::BODY_STATE_TRANSFORM, xform);
+ }
+ }
+ }
+ }
+ } else if (p_what == DIRTY_FLAGS_TILE_MAP_IN_TREE) {
+ // Changes in the tree may cause the space to change (e.g. when reparenting to a SubViewport).
+ if (tile_map_node->is_inside_tree()) {
+ RID space = tile_map_node->get_world_2d()->get_space();
+
+ for (KeyValue<Vector2i, CellData> &kv : tile_map) {
+ const CellData &cell_data = kv.value;
+
+ for (RID body : cell_data.bodies) {
+ if (body.is_valid()) {
+ ps->body_set_space(body, space);
}
}
}
@@ -758,8 +806,13 @@ void TileMapLayer::_physics_update_cell(CellData &r_cell_data) {
tile_data = atlas_source->get_tile_data(c.get_atlas_coords(), c.alternative_tile);
}
+ // Transform flags.
+ bool flip_h = (c.alternative_tile & TileSetAtlasSource::TRANSFORM_FLIP_H);
+ bool flip_v = (c.alternative_tile & TileSetAtlasSource::TRANSFORM_FLIP_V);
+ bool transpose = (c.alternative_tile & TileSetAtlasSource::TRANSFORM_TRANSPOSE);
+
// Free unused bodies then resize the bodies array.
- for (unsigned int i = tile_set->get_physics_layers_count(); i < r_cell_data.bodies.size(); i++) {
+ for (uint32_t i = tile_set->get_physics_layers_count(); i < r_cell_data.bodies.size(); i++) {
RID body = r_cell_data.bodies[i];
if (body.is_valid()) {
bodies_coords.erase(body);
@@ -768,7 +821,7 @@ void TileMapLayer::_physics_update_cell(CellData &r_cell_data) {
}
r_cell_data.bodies.resize(tile_set->get_physics_layers_count());
- for (int tile_set_physics_layer = 0; tile_set_physics_layer < tile_set->get_physics_layers_count(); tile_set_physics_layer++) {
+ for (uint32_t tile_set_physics_layer = 0; tile_set_physics_layer < (uint32_t)tile_set->get_physics_layers_count(); tile_set_physics_layer++) {
Ref<PhysicsMaterial> physics_material = tile_set->get_physics_layer_physics_material(tile_set_physics_layer);
uint32_t physics_layer = tile_set->get_physics_layer_collision_layer(tile_set_physics_layer);
uint32_t physics_mask = tile_set->get_physics_layer_collision_mask(tile_set_physics_layer);
@@ -822,8 +875,7 @@ void TileMapLayer::_physics_update_cell(CellData &r_cell_data) {
int shapes_count = tile_data->get_collision_polygon_shapes_count(tile_set_physics_layer, polygon_index);
for (int shape_index = 0; shape_index < shapes_count; shape_index++) {
// Add decomposed convex shapes.
- Ref<ConvexPolygonShape2D> shape = tile_data->get_collision_polygon_shape(tile_set_physics_layer, polygon_index, shape_index);
- shape = tile_map_node->get_transformed_polygon(Ref<Resource>(shape), c.alternative_tile);
+ Ref<ConvexPolygonShape2D> shape = tile_data->get_collision_polygon_shape(tile_set_physics_layer, polygon_index, shape_index, flip_h, flip_v, transpose);
ps->body_add_shape(body, shape->get_rid());
ps->body_set_shape_as_one_way_collision(body, body_shape_index, one_way_collision, one_way_collision_margin);
@@ -979,7 +1031,7 @@ void TileMapLayer::_navigation_update() {
void TileMapLayer::_navigation_clear_cell(CellData &r_cell_data) {
NavigationServer2D *ns = NavigationServer2D::get_singleton();
// Clear navigation shapes.
- for (unsigned int i = 0; i < r_cell_data.navigation_regions.size(); i++) {
+ for (uint32_t i = 0; i < r_cell_data.navigation_regions.size(); i++) {
const RID &region = r_cell_data.navigation_regions[i];
if (region.is_valid()) {
ns->region_set_map(region, RID());
@@ -1011,8 +1063,13 @@ void TileMapLayer::_navigation_update_cell(CellData &r_cell_data) {
tile_data = atlas_source->get_tile_data(c.get_atlas_coords(), c.alternative_tile);
}
+ // Transform flags.
+ bool flip_h = (c.alternative_tile & TileSetAtlasSource::TRANSFORM_FLIP_H);
+ bool flip_v = (c.alternative_tile & TileSetAtlasSource::TRANSFORM_FLIP_V);
+ bool transpose = (c.alternative_tile & TileSetAtlasSource::TRANSFORM_TRANSPOSE);
+
// Free unused regions then resize the regions array.
- for (unsigned int i = tile_set->get_navigation_layers_count(); i < r_cell_data.navigation_regions.size(); i++) {
+ for (uint32_t i = tile_set->get_navigation_layers_count(); i < r_cell_data.navigation_regions.size(); i++) {
RID &region = r_cell_data.navigation_regions[i];
if (region.is_valid()) {
ns->region_set_map(region, RID());
@@ -1023,10 +1080,8 @@ void TileMapLayer::_navigation_update_cell(CellData &r_cell_data) {
r_cell_data.navigation_regions.resize(tile_set->get_navigation_layers_count());
// Create, update or clear regions.
- for (unsigned int navigation_layer_index = 0; navigation_layer_index < r_cell_data.navigation_regions.size(); navigation_layer_index++) {
- Ref<NavigationPolygon> navigation_polygon;
- navigation_polygon = tile_data->get_navigation_polygon(navigation_layer_index);
- navigation_polygon = tile_map_node->get_transformed_polygon(Ref<Resource>(navigation_polygon), c.alternative_tile);
+ for (uint32_t navigation_layer_index = 0; navigation_layer_index < r_cell_data.navigation_regions.size(); navigation_layer_index++) {
+ Ref<NavigationPolygon> navigation_polygon = tile_data->get_navigation_polygon(navigation_layer_index, flip_h, flip_v, transpose);
RID &region = r_cell_data.navigation_regions[navigation_layer_index];
@@ -1119,9 +1174,11 @@ void TileMapLayer::_navigation_draw_cell_debug(const RID &p_canvas_item, const V
rs->canvas_item_add_set_transform(p_canvas_item, cell_to_quadrant);
for (int layer_index = 0; layer_index < tile_set->get_navigation_layers_count(); layer_index++) {
- Ref<NavigationPolygon> navigation_polygon = tile_data->get_navigation_polygon(layer_index);
+ bool flip_h = (c.alternative_tile & TileSetAtlasSource::TRANSFORM_FLIP_H);
+ bool flip_v = (c.alternative_tile & TileSetAtlasSource::TRANSFORM_FLIP_V);
+ bool transpose = (c.alternative_tile & TileSetAtlasSource::TRANSFORM_TRANSPOSE);
+ Ref<NavigationPolygon> navigation_polygon = tile_data->get_navigation_polygon(layer_index, flip_h, flip_v, transpose);
if (navigation_polygon.is_valid()) {
- navigation_polygon = tile_map_node->get_transformed_polygon(Ref<Resource>(navigation_polygon), c.alternative_tile);
Vector<Vector2> navigation_polygon_vertices = navigation_polygon->get_vertices();
if (navigation_polygon_vertices.size() < 3) {
continue;
@@ -1172,7 +1229,7 @@ void TileMapLayer::_scenes_update() {
const Ref<TileSet> &tile_set = tile_map_node->get_tileset();
// Check if we should cleanup everything.
- bool forced_cleanup = in_destructor || !enabled || !tile_map_node->is_inside_tree() || !tile_set.is_valid() || !tile_map_node->is_visible_in_tree();
+ bool forced_cleanup = in_destructor || !enabled || !tile_map_node->is_inside_tree() || !tile_set.is_valid();
if (forced_cleanup) {
// Clean everything.
@@ -3077,11 +3134,6 @@ void TileMap::_internal_update() {
return;
}
- // FIXME: This should only clear polygons that are no longer going to be used, but since it's difficult to determine,
- // the cache is never cleared at runtime to prevent invalidating used polygons.
- if (Engine::get_singleton()->is_editor_hint()) {
- polygon_cache.clear();
- }
// Update dirty quadrants on layers.
for (Ref<TileMapLayer> &layer : layers) {
layer->internal_update();
@@ -3231,7 +3283,7 @@ void TileMap::add_layer(int p_to_pos) {
new_layer.instantiate();
new_layer->set_tile_map(this);
layers.insert(p_to_pos, new_layer);
- for (unsigned int i = 0; i < layers.size(); i++) {
+ for (uint32_t i = 0; i < layers.size(); i++) {
layers[i]->set_layer_index_in_tile_map_node(i);
}
queue_internal_update();
@@ -3250,7 +3302,7 @@ void TileMap::move_layer(int p_layer, int p_to_pos) {
Ref<TileMapLayer> layer = layers[p_layer];
layers.insert(p_to_pos, layer);
layers.remove_at(p_to_pos < p_layer ? p_layer + 1 : p_layer);
- for (unsigned int i = 0; i < layers.size(); i++) {
+ for (uint32_t i = 0; i < layers.size(); i++) {
layers[i]->set_layer_index_in_tile_map_node(i);
}
queue_internal_update();
@@ -3270,7 +3322,7 @@ void TileMap::remove_layer(int p_layer) {
// Clear before removing the layer.
layers.remove_at(p_layer);
- for (unsigned int i = 0; i < layers.size(); i++) {
+ for (uint32_t i = 0; i < layers.size(); i++) {
layers[i]->set_layer_index_in_tile_map_node(i);
}
queue_internal_update();
@@ -3507,7 +3559,7 @@ Vector2i TileMap::get_coords_for_body_rid(RID p_physics_body) {
}
int TileMap::get_layer_for_body_rid(RID p_physics_body) {
- for (unsigned int i = 0; i < layers.size(); i++) {
+ for (uint32_t i = 0; i < layers.size(); i++) {
if (layers[i]->has_body_rid(p_physics_body)) {
return i;
}
@@ -3557,7 +3609,7 @@ Rect2 TileMap::_edit_get_rect() const {
bool changed = false;
Rect2 rect = layers[0]->get_rect(changed);
any_changed |= changed;
- for (unsigned int i = 1; i < layers.size(); i++) {
+ for (uint32_t i = 1; i < layers.size(); i++) {
rect = rect.merge(layers[i]->get_rect(changed));
any_changed |= changed;
}
@@ -3566,37 +3618,6 @@ Rect2 TileMap::_edit_get_rect() const {
}
#endif
-PackedVector2Array TileMap::_get_transformed_vertices(const PackedVector2Array &p_vertices, int p_alternative_id) {
- const Vector2 *r = p_vertices.ptr();
- int size = p_vertices.size();
-
- PackedVector2Array new_points;
- new_points.resize(size);
- Vector2 *w = new_points.ptrw();
-
- bool flip_h = (p_alternative_id & TileSetAtlasSource::TRANSFORM_FLIP_H);
- bool flip_v = (p_alternative_id & TileSetAtlasSource::TRANSFORM_FLIP_V);
- bool transpose = (p_alternative_id & TileSetAtlasSource::TRANSFORM_TRANSPOSE);
-
- for (int i = 0; i < size; i++) {
- Vector2 v;
- if (transpose) {
- v = Vector2(r[i].y, r[i].x);
- } else {
- v = r[i];
- }
-
- if (flip_h) {
- v.x *= -1;
- }
- if (flip_v) {
- v.y *= -1;
- }
- w[i] = v;
- }
- return new_points;
-}
-
bool TileMap::_set(const StringName &p_name, const Variant &p_value) {
Vector<String> components = String(p_name).split("/", true, 2);
if (p_name == "format") {
@@ -3604,8 +3625,9 @@ bool TileMap::_set(const StringName &p_name, const Variant &p_value) {
format = (TileMapLayer::DataFormat)(p_value.operator int64_t()); // Set format used for loading.
return true;
}
+ }
#ifndef DISABLE_DEPRECATED
- } else if (p_name == "tile_data") { // Kept for compatibility reasons.
+ else if (p_name == "tile_data") { // Kept for compatibility reasons.
if (p_value.is_array()) {
if (layers.size() == 0) {
Ref<TileMapLayer> new_layer;
@@ -3619,10 +3641,12 @@ bool TileMap::_set(const StringName &p_name, const Variant &p_value) {
return true;
}
return false;
- } else if (p_name == "rendering_quadrant_size") {
+ } else if (p_name == "cell_quadrant_size") {
set_rendering_quadrant_size(p_value);
+ return true;
+ }
#endif // DISABLE_DEPRECATED
- } else if (components.size() == 2 && components[0].begins_with("layer_") && components[0].trim_prefix("layer_").is_valid_int()) {
+ else if (components.size() == 2 && components[0].begins_with("layer_") && components[0].trim_prefix("layer_").is_valid_int()) {
int index = components[0].trim_prefix("layer_").to_int();
if (index < 0) {
return false;
@@ -3679,7 +3703,14 @@ bool TileMap::_get(const StringName &p_name, Variant &r_ret) const {
if (p_name == "format") {
r_ret = TileMapLayer::FORMAT_MAX - 1; // When saving, always save highest format.
return true;
- } else if (components.size() == 2 && components[0].begins_with("layer_") && components[0].trim_prefix("layer_").is_valid_int()) {
+ }
+#ifndef DISABLE_DEPRECATED
+ else if (p_name == "cell_quadrant_size") { // Kept for compatibility reasons.
+ r_ret = get_rendering_quadrant_size();
+ return true;
+ }
+#endif
+ else if (components.size() == 2 && components[0].begins_with("layer_") && components[0].trim_prefix("layer_").is_valid_int()) {
int index = components[0].trim_prefix("layer_").to_int();
if (index < 0 || index >= (int)layers.size()) {
return false;
@@ -3726,7 +3757,7 @@ void TileMap::_get_property_list(List<PropertyInfo> *p_list) const {
p_list->push_back(PropertyInfo(m_type, property_name, PROPERTY_HINT_NONE, m_hint, (get(property_name) == property_get_revert(property_name)) ? PROPERTY_USAGE_EDITOR : PROPERTY_USAGE_DEFAULT)); \
}
- for (unsigned int i = 0; i < layers.size(); i++) {
+ for (uint32_t i = 0; i < layers.size(); i++) {
MAKE_LAYER_PROPERTY(Variant::STRING, "name", "");
MAKE_LAYER_PROPERTY(Variant::BOOL, "enabled", "");
MAKE_LAYER_PROPERTY(Variant::COLOR, "modulate", "");
@@ -4585,57 +4616,6 @@ void TileMap::draw_cells_outline(Control *p_control, const RBSet<Vector2i> &p_ce
#undef DRAW_SIDE_IF_NEEDED
}
-Ref<Resource> TileMap::get_transformed_polygon(Ref<Resource> p_polygon, int p_alternative_id) {
- if (!bool(p_alternative_id & (TileSetAtlasSource::TRANSFORM_FLIP_H | TileSetAtlasSource::TRANSFORM_FLIP_V | TileSetAtlasSource::TRANSFORM_TRANSPOSE))) {
- return p_polygon;
- }
-
- {
- HashMap<Pair<Ref<Resource>, int>, Ref<Resource>, PairHash<Ref<Resource>, int>>::Iterator E = polygon_cache.find(Pair<Ref<Resource>, int>(p_polygon, p_alternative_id));
- if (E) {
- return E->value;
- }
- }
-
- Ref<ConvexPolygonShape2D> col = p_polygon;
- if (col.is_valid()) {
- Ref<ConvexPolygonShape2D> ret;
- ret.instantiate();
- ret->set_points(_get_transformed_vertices(col->get_points(), p_alternative_id));
- polygon_cache[Pair<Ref<Resource>, int>(p_polygon, p_alternative_id)] = ret;
- return ret;
- }
-
- Ref<NavigationPolygon> nav = p_polygon;
- if (nav.is_valid()) {
- PackedVector2Array new_points = _get_transformed_vertices(nav->get_vertices(), p_alternative_id);
- Ref<NavigationPolygon> ret;
- ret.instantiate();
- ret->set_vertices(new_points);
-
- PackedInt32Array indices;
- indices.resize(new_points.size());
- int *w = indices.ptrw();
- for (int i = 0; i < new_points.size(); i++) {
- w[i] = i;
- }
- ret->add_polygon(indices);
- polygon_cache[Pair<Ref<Resource>, int>(p_polygon, p_alternative_id)] = ret;
- return ret;
- }
-
- Ref<OccluderPolygon2D> ocd = p_polygon;
- if (ocd.is_valid()) {
- Ref<OccluderPolygon2D> ret;
- ret.instantiate();
- ret->set_polygon(_get_transformed_vertices(ocd->get_polygon(), p_alternative_id));
- polygon_cache[Pair<Ref<Resource>, int>(p_polygon, p_alternative_id)] = ret;
- return ret;
- }
-
- return p_polygon;
-}
-
PackedStringArray TileMap::get_configuration_warnings() const {
PackedStringArray warnings = Node::get_configuration_warnings();
diff --git a/scene/2d/tile_map.h b/scene/2d/tile_map.h
index 1136e4190d..1e4c6d0e66 100644
--- a/scene/2d/tile_map.h
+++ b/scene/2d/tile_map.h
@@ -104,7 +104,7 @@ struct CellData {
// Rendering.
Ref<RenderingQuadrant> rendering_quadrant;
SelfList<CellData> rendering_quadrant_list_element;
- List<RID> occluders;
+ LocalVector<RID> occluders;
// Physics.
LocalVector<RID> bodies;
@@ -472,10 +472,6 @@ private:
void _update_notify_local_transform();
- // Polygons.
- HashMap<Pair<Ref<Resource>, int>, Ref<Resource>, PairHash<Ref<Resource>, int>> polygon_cache;
- PackedVector2Array _get_transformed_vertices(const PackedVector2Array &p_vertices, int p_alternative_id);
-
protected:
bool _set(const StringName &p_name, const Variant &p_value);
bool _get(const StringName &p_name, Variant &r_ret) const;
@@ -619,7 +615,6 @@ public:
// Helpers?
TypedArray<Vector2i> get_surrounding_cells(const Vector2i &coords);
void draw_cells_outline(Control *p_control, const RBSet<Vector2i> &p_cells, Color p_color, Transform2D p_transform = Transform2D());
- Ref<Resource> get_transformed_polygon(Ref<Resource> p_polygon, int p_alternative_id);
// Virtual function to modify the TileData at runtime.
GDVIRTUAL2R(bool, _use_tile_data_runtime_update, int, Vector2i);
diff --git a/scene/3d/area_3d.cpp b/scene/3d/area_3d.cpp
index beb6892435..014c33cad0 100644
--- a/scene/3d/area_3d.cpp
+++ b/scene/3d/area_3d.cpp
@@ -223,6 +223,21 @@ void Area3D::_body_inout(int p_status, const RID &p_body, ObjectID p_instance, i
bool body_in = p_status == PhysicsServer3D::AREA_BODY_ADDED;
ObjectID objid = p_instance;
+ // Exit early if instance is invalid.
+ if (objid.is_null()) {
+ lock_callback();
+ locked = true;
+ // Emit the appropriate signals.
+ if (body_in) {
+ emit_signal(SceneStringNames::get_singleton()->body_shape_entered, p_body, (Node *)nullptr, p_body_shape, p_area_shape);
+ } else {
+ emit_signal(SceneStringNames::get_singleton()->body_shape_exited, p_body, (Node *)nullptr, p_body_shape, p_area_shape);
+ }
+ locked = false;
+ unlock_callback();
+ return;
+ }
+
Object *obj = ObjectDB::get_instance(objid);
Node *node = Object::cast_to<Node>(obj);
@@ -254,7 +269,7 @@ void Area3D::_body_inout(int p_status, const RID &p_body, ObjectID p_instance, i
E->value.shapes.insert(ShapePair(p_body_shape, p_area_shape));
}
- if (E->value.in_tree) {
+ if (!node || E->value.in_tree) {
emit_signal(SceneStringNames::get_singleton()->body_shape_entered, p_body, node, p_body_shape, p_area_shape);
}
@@ -276,7 +291,7 @@ void Area3D::_body_inout(int p_status, const RID &p_body, ObjectID p_instance, i
}
}
}
- if (node && in_tree) {
+ if (!node || in_tree) {
emit_signal(SceneStringNames::get_singleton()->body_shape_exited, p_body, obj, p_body_shape, p_area_shape);
}
}
@@ -347,12 +362,14 @@ void Area3D::_clear_monitoring() {
}
}
+void Area3D::_space_changed(const RID &p_new_space) {
+ if (p_new_space.is_null()) {
+ _clear_monitoring();
+ }
+}
+
void Area3D::_notification(int p_what) {
switch (p_what) {
- case NOTIFICATION_EXIT_TREE: {
- _clear_monitoring();
- } break;
-
case NOTIFICATION_ENTER_TREE: {
_initialize_wind();
} break;
@@ -412,6 +429,21 @@ void Area3D::_area_inout(int p_status, const RID &p_area, ObjectID p_instance, i
bool area_in = p_status == PhysicsServer3D::AREA_BODY_ADDED;
ObjectID objid = p_instance;
+ // Exit if instance is invalid.
+ if (objid.is_null()) {
+ lock_callback();
+ locked = true;
+ // Emit the appropriate signals.
+ if (area_in) {
+ emit_signal(SceneStringNames::get_singleton()->area_shape_entered, p_area, (Node *)nullptr, p_area_shape, p_self_shape);
+ } else {
+ emit_signal(SceneStringNames::get_singleton()->area_shape_exited, p_area, (Node *)nullptr, p_area_shape, p_self_shape);
+ }
+ locked = false;
+ unlock_callback();
+ return;
+ }
+
Object *obj = ObjectDB::get_instance(objid);
Node *node = Object::cast_to<Node>(obj);
diff --git a/scene/3d/area_3d.h b/scene/3d/area_3d.h
index 86602d3192..05c558e8f0 100644
--- a/scene/3d/area_3d.h
+++ b/scene/3d/area_3d.h
@@ -149,6 +149,8 @@ protected:
static void _bind_methods();
void _validate_property(PropertyInfo &p_property) const;
+ virtual void _space_changed(const RID &p_new_space) override;
+
public:
void set_gravity_space_override_mode(SpaceOverride p_mode);
SpaceOverride get_gravity_space_override_mode() const;
diff --git a/scene/3d/camera_3d.h b/scene/3d/camera_3d.h
index aa302ded4a..8de607806e 100644
--- a/scene/3d/camera_3d.h
+++ b/scene/3d/camera_3d.h
@@ -36,6 +36,11 @@
#include "scene/resources/camera_attributes.h"
#include "scene/resources/environment.h"
+#ifdef MINGW_ENABLED
+#undef near
+#undef far
+#endif
+
class Camera3D : public Node3D {
GDCLASS(Camera3D, Node3D);
diff --git a/scene/3d/collision_object_3d.cpp b/scene/3d/collision_object_3d.cpp
index bfe594adc2..4562ecfb5f 100644
--- a/scene/3d/collision_object_3d.cpp
+++ b/scene/3d/collision_object_3d.cpp
@@ -78,6 +78,7 @@ void CollisionObject3D::_notification(int p_what) {
} else {
PhysicsServer3D::get_singleton()->body_set_space(rid, space);
}
+ _space_changed(space);
}
_update_pickable();
@@ -117,6 +118,7 @@ void CollisionObject3D::_notification(int p_what) {
} else {
PhysicsServer3D::get_singleton()->body_set_space(rid, RID());
}
+ _space_changed(RID());
}
}
@@ -244,6 +246,7 @@ void CollisionObject3D::_apply_disabled() {
} else {
PhysicsServer3D::get_singleton()->body_set_space(rid, RID());
}
+ _space_changed(RID());
}
}
} break;
@@ -270,6 +273,7 @@ void CollisionObject3D::_apply_enabled() {
} else {
PhysicsServer3D::get_singleton()->body_set_space(rid, space);
}
+ _space_changed(space);
}
} break;
@@ -320,6 +324,9 @@ void CollisionObject3D::set_body_mode(PhysicsServer3D::BodyMode p_mode) {
PhysicsServer3D::get_singleton()->body_set_mode(rid, p_mode);
}
+void CollisionObject3D::_space_changed(const RID &p_new_space) {
+}
+
void CollisionObject3D::set_only_update_transform_changes(bool p_enable) {
only_update_transform_changes = p_enable;
}
diff --git a/scene/3d/collision_object_3d.h b/scene/3d/collision_object_3d.h
index ebcbb39e0d..b51423f021 100644
--- a/scene/3d/collision_object_3d.h
+++ b/scene/3d/collision_object_3d.h
@@ -116,6 +116,8 @@ protected:
void set_body_mode(PhysicsServer3D::BodyMode p_mode);
+ virtual void _space_changed(const RID &p_new_space);
+
void set_only_update_transform_changes(bool p_enable);
bool is_only_update_transform_changes_enabled() const;
diff --git a/scene/3d/label_3d.cpp b/scene/3d/label_3d.cpp
index 9eec2f5345..3fb472335e 100644
--- a/scene/3d/label_3d.cpp
+++ b/scene/3d/label_3d.cpp
@@ -326,28 +326,38 @@ Ref<TriangleMesh> Label3D::generate_triangle_mesh() const {
}
void Label3D::_generate_glyph_surfaces(const Glyph &p_glyph, Vector2 &r_offset, const Color &p_modulate, int p_priority, int p_outline_size) {
- for (int j = 0; j < p_glyph.repeat; j++) {
- Vector2 gl_of;
- Vector2 gl_sz;
- Rect2 gl_uv;
- Size2 texs;
- RID tex;
-
- if (p_glyph.font_rid != RID()) {
- tex = TS->font_get_glyph_texture_rid(p_glyph.font_rid, Vector2i(p_glyph.font_size, p_outline_size), p_glyph.index);
- if (tex != RID()) {
- gl_of = (TS->font_get_glyph_offset(p_glyph.font_rid, Vector2i(p_glyph.font_size, p_outline_size), p_glyph.index) + Vector2(p_glyph.x_off, p_glyph.y_off)) * pixel_size;
- gl_sz = TS->font_get_glyph_size(p_glyph.font_rid, Vector2i(p_glyph.font_size, p_outline_size), p_glyph.index) * pixel_size;
- gl_uv = TS->font_get_glyph_uv_rect(p_glyph.font_rid, Vector2i(p_glyph.font_size, p_outline_size), p_glyph.index);
- texs = TS->font_get_glyph_texture_size(p_glyph.font_rid, Vector2i(p_glyph.font_size, p_outline_size), p_glyph.index);
- }
- } else {
- gl_sz = TS->get_hex_code_box_size(p_glyph.font_size, p_glyph.index) * pixel_size;
- gl_of = Vector2(0, -gl_sz.y);
+ if (p_glyph.index == 0) {
+ r_offset.x += p_glyph.advance * pixel_size * p_glyph.repeat; // Non visual character, skip.
+ return;
+ }
+
+ Vector2 gl_of;
+ Vector2 gl_sz;
+ Rect2 gl_uv;
+ Size2 texs;
+ RID tex;
+
+ if (p_glyph.font_rid.is_valid()) {
+ tex = TS->font_get_glyph_texture_rid(p_glyph.font_rid, Vector2i(p_glyph.font_size, p_outline_size), p_glyph.index);
+ if (tex.is_valid()) {
+ gl_of = (TS->font_get_glyph_offset(p_glyph.font_rid, Vector2i(p_glyph.font_size, p_outline_size), p_glyph.index) + Vector2(p_glyph.x_off, p_glyph.y_off)) * pixel_size;
+ gl_sz = TS->font_get_glyph_size(p_glyph.font_rid, Vector2i(p_glyph.font_size, p_outline_size), p_glyph.index) * pixel_size;
+ gl_uv = TS->font_get_glyph_uv_rect(p_glyph.font_rid, Vector2i(p_glyph.font_size, p_outline_size), p_glyph.index);
+ texs = TS->font_get_glyph_texture_size(p_glyph.font_rid, Vector2i(p_glyph.font_size, p_outline_size), p_glyph.index);
}
+ } else {
+ gl_sz = TS->get_hex_code_box_size(p_glyph.font_size, p_glyph.index) * pixel_size;
+ gl_of = Vector2(0, -gl_sz.y);
+ }
- bool msdf = TS->font_is_multichannel_signed_distance_field(p_glyph.font_rid);
+ if (gl_uv.size.x <= 2 || gl_uv.size.y <= 2) {
+ r_offset.x += p_glyph.advance * pixel_size * p_glyph.repeat; // Nothing to draw.
+ return;
+ }
+ bool msdf = TS->font_is_multichannel_signed_distance_field(p_glyph.font_rid);
+
+ for (int j = 0; j < p_glyph.repeat; j++) {
SurfaceKey key = SurfaceKey(tex.get_id(), p_priority, p_outline_size);
if (!surfaces.has(key)) {
SurfaceData surf;
@@ -420,7 +430,7 @@ void Label3D::_generate_glyph_surfaces(const Glyph &p_glyph, Vector2 &r_offset,
}
}
- if (tex != RID()) {
+ if (tex.is_valid()) {
s.mesh_uvs.write[(s.offset * 4) + 3] = Vector2(gl_uv.position.x / texs.x, (gl_uv.position.y + gl_uv.size.y) / texs.y);
s.mesh_uvs.write[(s.offset * 4) + 2] = Vector2((gl_uv.position.x + gl_uv.size.x) / texs.x, (gl_uv.position.y + gl_uv.size.y) / texs.y);
s.mesh_uvs.write[(s.offset * 4) + 1] = Vector2((gl_uv.position.x + gl_uv.size.x) / texs.x, gl_uv.position.y / texs.y);
diff --git a/scene/3d/lightmap_gi.cpp b/scene/3d/lightmap_gi.cpp
index 4ba039becd..76933cd956 100644
--- a/scene/3d/lightmap_gi.cpp
+++ b/scene/3d/lightmap_gi.cpp
@@ -1523,7 +1523,7 @@ PackedStringArray LightmapGI::get_configuration_warnings() const {
PackedStringArray warnings = Node::get_configuration_warnings();
if (OS::get_singleton()->get_current_rendering_method() == "gl_compatibility") {
- warnings.push_back(RTR("LightmapGI nodes are not supported when using the GL Compatibility backend yet. Support will be added in a future release."));
+ warnings.push_back(RTR("Lightmap cannot be baked when using the GL Compatibility backend yet. Support will be added in a future release."));
return warnings;
}
diff --git a/scene/3d/navigation_agent_3d.cpp b/scene/3d/navigation_agent_3d.cpp
index b311495a7f..b67935018c 100644
--- a/scene/3d/navigation_agent_3d.cpp
+++ b/scene/3d/navigation_agent_3d.cpp
@@ -299,7 +299,6 @@ void NavigationAgent3D::_notification(int p_what) {
NavigationServer3D::get_singleton()->agent_set_velocity_forced(agent, velocity_forced);
}
}
- _check_distance_to_target();
}
#ifdef DEBUG_ENABLED
if (debug_path_dirty) {
@@ -327,6 +326,14 @@ NavigationAgent3D::NavigationAgent3D() {
NavigationServer3D::get_singleton()->agent_set_radius(agent, radius);
NavigationServer3D::get_singleton()->agent_set_height(agent, height);
NavigationServer3D::get_singleton()->agent_set_max_speed(agent, max_speed);
+ NavigationServer3D::get_singleton()->agent_set_avoidance_layers(agent, avoidance_layers);
+ NavigationServer3D::get_singleton()->agent_set_avoidance_mask(agent, avoidance_mask);
+ NavigationServer3D::get_singleton()->agent_set_avoidance_priority(agent, avoidance_priority);
+ NavigationServer3D::get_singleton()->agent_set_use_3d_avoidance(agent, use_3d_avoidance);
+ NavigationServer3D::get_singleton()->agent_set_avoidance_enabled(agent, avoidance_enabled);
+ if (avoidance_enabled) {
+ NavigationServer3D::get_singleton()->agent_set_avoidance_callback(agent, callable_mp(this, &NavigationAgent3D::_avoidance_done));
+ }
// Preallocate query and result objects to improve performance.
navigation_query = Ref<NavigationPathQueryParameters3D>();
@@ -335,12 +342,6 @@ NavigationAgent3D::NavigationAgent3D() {
navigation_result = Ref<NavigationPathQueryResult3D>();
navigation_result.instantiate();
- set_avoidance_layers(avoidance_layers);
- set_avoidance_mask(avoidance_mask);
- set_avoidance_priority(avoidance_priority);
- set_use_3d_avoidance(use_3d_avoidance);
- set_avoidance_enabled(avoidance_enabled);
-
#ifdef DEBUG_ENABLED
NavigationServer3D::get_singleton()->connect(SNAME("navigation_debug_changed"), callable_mp(this, &NavigationAgent3D::_navigation_debug_changed));
#endif // DEBUG_ENABLED
@@ -620,7 +621,7 @@ Vector3 NavigationAgent3D::get_target_position() const {
}
Vector3 NavigationAgent3D::get_next_path_position() {
- update_navigation();
+ _update_navigation();
const Vector<Vector3> &navigation_path = navigation_result->get_path();
if (navigation_path.size() == 0) {
@@ -641,22 +642,30 @@ bool NavigationAgent3D::is_target_reached() const {
}
bool NavigationAgent3D::is_target_reachable() {
- return target_desired_distance >= get_final_position().distance_to(target_position);
+ _update_navigation();
+ return _is_target_reachable();
+}
+
+bool NavigationAgent3D::_is_target_reachable() const {
+ return target_desired_distance >= _get_final_position().distance_to(target_position);
}
bool NavigationAgent3D::is_navigation_finished() {
- update_navigation();
+ _update_navigation();
return navigation_finished;
}
Vector3 NavigationAgent3D::get_final_position() {
- update_navigation();
+ _update_navigation();
+ return _get_final_position();
+}
+Vector3 NavigationAgent3D::_get_final_position() const {
const Vector<Vector3> &navigation_path = navigation_result->get_path();
if (navigation_path.size() == 0) {
return Vector3();
}
- return navigation_path[navigation_path.size() - 1];
+ return navigation_path[navigation_path.size() - 1] - Vector3(0, path_height_offset, 0);
}
void NavigationAgent3D::set_velocity_forced(Vector3 p_velocity) {
@@ -691,7 +700,7 @@ PackedStringArray NavigationAgent3D::get_configuration_warnings() const {
return warnings;
}
-void NavigationAgent3D::update_navigation() {
+void NavigationAgent3D::_update_navigation() {
if (agent_parent == nullptr) {
return;
}
@@ -747,6 +756,7 @@ void NavigationAgent3D::update_navigation() {
debug_path_dirty = true;
#endif // DEBUG_ENABLED
navigation_finished = false;
+ last_waypoint_reached = false;
navigation_path_index = 0;
emit_signal(SNAME("path_changed"));
}
@@ -755,103 +765,147 @@ void NavigationAgent3D::update_navigation() {
return;
}
- // Check if we can advance the navigation path
- if (navigation_finished == false) {
- // Advances to the next far away position.
- const Vector<Vector3> &navigation_path = navigation_result->get_path();
- const Vector<int32_t> &navigation_path_types = navigation_result->get_path_types();
- const TypedArray<RID> &navigation_path_rids = navigation_result->get_path_rids();
- const Vector<int64_t> &navigation_path_owners = navigation_result->get_path_owner_ids();
+ // Check if the navigation has already finished.
+ if (navigation_finished) {
+ return;
+ }
- while (origin.distance_to(navigation_path[navigation_path_index] - Vector3(0, path_height_offset, 0)) < path_desired_distance) {
- Dictionary details;
+ // Check if we reached the target.
+ if (_is_within_target_distance(origin)) {
+ // Emit waypoint_reached in case we also moved within distance of a waypoint.
+ _advance_waypoints(origin);
+ _transition_to_target_reached();
+ _transition_to_navigation_finished();
+ } else {
+ // Advance waypoints if possible.
+ _advance_waypoints(origin);
+ // Keep navigation running even after reaching the last waypoint if the target is reachable.
+ if (last_waypoint_reached && !_is_target_reachable()) {
+ _transition_to_navigation_finished();
+ }
+ }
+}
- const Vector3 waypoint = navigation_path[navigation_path_index];
- details[SNAME("position")] = waypoint;
+void NavigationAgent3D::_advance_waypoints(const Vector3 &p_origin) {
+ if (last_waypoint_reached) {
+ return;
+ }
- int waypoint_type = -1;
- if (path_metadata_flags.has_flag(NavigationPathQueryParameters3D::PathMetadataFlags::PATH_METADATA_INCLUDE_TYPES)) {
- const NavigationPathQueryResult3D::PathSegmentType type = NavigationPathQueryResult3D::PathSegmentType(navigation_path_types[navigation_path_index]);
+ // Advance to the farthest possible waypoint.
+ while (_is_within_waypoint_distance(p_origin)) {
+ _trigger_waypoint_reached();
- details[SNAME("type")] = type;
- waypoint_type = type;
- }
+ if (_is_last_waypoint()) {
+ last_waypoint_reached = true;
+ break;
+ }
- if (path_metadata_flags.has_flag(NavigationPathQueryParameters3D::PathMetadataFlags::PATH_METADATA_INCLUDE_RIDS)) {
- details[SNAME("rid")] = navigation_path_rids[navigation_path_index];
- }
+ _move_to_next_waypoint();
+ }
+}
- if (path_metadata_flags.has_flag(NavigationPathQueryParameters3D::PathMetadataFlags::PATH_METADATA_INCLUDE_OWNERS)) {
- const ObjectID waypoint_owner_id = ObjectID(navigation_path_owners[navigation_path_index]);
+void NavigationAgent3D::_request_repath() {
+ navigation_result->reset();
+ target_reached = false;
+ navigation_finished = false;
+ last_waypoint_reached = false;
+ update_frame_id = 0;
+}
- // Get a reference to the owning object.
- Object *owner = nullptr;
- if (waypoint_owner_id.is_valid()) {
- owner = ObjectDB::get_instance(waypoint_owner_id);
- }
+bool NavigationAgent3D::_is_last_waypoint() const {
+ return navigation_path_index == navigation_result->get_path().size() - 1;
+}
- details[SNAME("owner")] = owner;
-
- if (waypoint_type == NavigationPathQueryResult3D::PATH_SEGMENT_TYPE_LINK) {
- const NavigationLink3D *navlink = Object::cast_to<NavigationLink3D>(owner);
- if (navlink) {
- Vector3 link_global_start_position = navlink->get_global_start_position();
- Vector3 link_global_end_position = navlink->get_global_end_position();
- if (waypoint.distance_to(link_global_start_position) < waypoint.distance_to(link_global_end_position)) {
- details[SNAME("link_entry_position")] = link_global_start_position;
- details[SNAME("link_exit_position")] = link_global_end_position;
- } else {
- details[SNAME("link_entry_position")] = link_global_end_position;
- details[SNAME("link_exit_position")] = link_global_start_position;
- }
- }
- }
- }
+void NavigationAgent3D::_move_to_next_waypoint() {
+ navigation_path_index += 1;
+}
+
+bool NavigationAgent3D::_is_within_waypoint_distance(const Vector3 &p_origin) const {
+ const Vector<Vector3> &navigation_path = navigation_result->get_path();
+ Vector3 waypoint = navigation_path[navigation_path_index] - Vector3(0, path_height_offset, 0);
+ return p_origin.distance_to(waypoint) < path_desired_distance;
+}
- // Emit a signal for the waypoint
- emit_signal(SNAME("waypoint_reached"), details);
+bool NavigationAgent3D::_is_within_target_distance(const Vector3 &p_origin) const {
+ return p_origin.distance_to(target_position) < target_desired_distance;
+}
- // Emit a signal if we've reached a navigation link
- if (waypoint_type == NavigationPathQueryResult3D::PATH_SEGMENT_TYPE_LINK) {
- emit_signal(SNAME("link_reached"), details);
- }
+void NavigationAgent3D::_trigger_waypoint_reached() {
+ const Vector<Vector3> &navigation_path = navigation_result->get_path();
+ const Vector<int32_t> &navigation_path_types = navigation_result->get_path_types();
+ const TypedArray<RID> &navigation_path_rids = navigation_result->get_path_rids();
+ const Vector<int64_t> &navigation_path_owners = navigation_result->get_path_owner_ids();
+
+ Dictionary details;
+
+ const Vector3 waypoint = navigation_path[navigation_path_index];
+ details[SNAME("position")] = waypoint;
+
+ int waypoint_type = -1;
+ if (path_metadata_flags.has_flag(NavigationPathQueryParameters3D::PathMetadataFlags::PATH_METADATA_INCLUDE_TYPES)) {
+ const NavigationPathQueryResult3D::PathSegmentType type = NavigationPathQueryResult3D::PathSegmentType(navigation_path_types[navigation_path_index]);
+
+ details[SNAME("type")] = type;
+ waypoint_type = type;
+ }
+
+ if (path_metadata_flags.has_flag(NavigationPathQueryParameters3D::PathMetadataFlags::PATH_METADATA_INCLUDE_RIDS)) {
+ details[SNAME("rid")] = navigation_path_rids[navigation_path_index];
+ }
+
+ if (path_metadata_flags.has_flag(NavigationPathQueryParameters3D::PathMetadataFlags::PATH_METADATA_INCLUDE_OWNERS)) {
+ const ObjectID waypoint_owner_id = ObjectID(navigation_path_owners[navigation_path_index]);
- // Move to the next waypoint on the list
- navigation_path_index += 1;
-
- // Check to see if we've finished our route
- if (navigation_path_index == navigation_path.size()) {
- _check_distance_to_target();
- navigation_path_index -= 1;
- navigation_finished = true;
- target_position_submitted = false;
- if (avoidance_enabled) {
- NavigationServer3D::get_singleton()->agent_set_position(agent, agent_parent->get_global_transform().origin);
- NavigationServer3D::get_singleton()->agent_set_velocity(agent, Vector3(0.0, 0.0, 0.0));
- NavigationServer3D::get_singleton()->agent_set_velocity_forced(agent, Vector3(0.0, 0.0, 0.0));
- stored_y_velocity = 0.0;
+ // Get a reference to the owning object.
+ Object *owner = nullptr;
+ if (waypoint_owner_id.is_valid()) {
+ owner = ObjectDB::get_instance(waypoint_owner_id);
+ }
+
+ details[SNAME("owner")] = owner;
+
+ if (waypoint_type == NavigationPathQueryResult3D::PATH_SEGMENT_TYPE_LINK) {
+ const NavigationLink3D *navlink = Object::cast_to<NavigationLink3D>(owner);
+ if (navlink) {
+ Vector3 link_global_start_position = navlink->get_global_start_position();
+ Vector3 link_global_end_position = navlink->get_global_end_position();
+ if (waypoint.distance_to(link_global_start_position) < waypoint.distance_to(link_global_end_position)) {
+ details[SNAME("link_entry_position")] = link_global_start_position;
+ details[SNAME("link_exit_position")] = link_global_end_position;
+ } else {
+ details[SNAME("link_entry_position")] = link_global_end_position;
+ details[SNAME("link_exit_position")] = link_global_start_position;
}
- emit_signal(SNAME("navigation_finished"));
- break;
}
}
}
-}
-void NavigationAgent3D::_request_repath() {
- navigation_result->reset();
- target_reached = false;
- navigation_finished = false;
- update_frame_id = 0;
+ // Emit a signal for the waypoint.
+ emit_signal(SNAME("waypoint_reached"), details);
+
+ // Emit a signal if we've reached a navigation link.
+ if (waypoint_type == NavigationPathQueryResult3D::PATH_SEGMENT_TYPE_LINK) {
+ emit_signal(SNAME("link_reached"), details);
+ }
}
-void NavigationAgent3D::_check_distance_to_target() {
- if (!target_reached) {
- if (distance_to_target() < target_desired_distance) {
- target_reached = true;
- emit_signal(SNAME("target_reached"));
- }
+void NavigationAgent3D::_transition_to_navigation_finished() {
+ navigation_finished = true;
+ target_position_submitted = false;
+
+ if (avoidance_enabled) {
+ NavigationServer3D::get_singleton()->agent_set_position(agent, agent_parent->get_global_transform().origin);
+ NavigationServer3D::get_singleton()->agent_set_velocity(agent, Vector3(0.0, 0.0, 0.0));
+ NavigationServer3D::get_singleton()->agent_set_velocity_forced(agent, Vector3(0.0, 0.0, 0.0));
+ stored_y_velocity = 0.0;
}
+
+ emit_signal(SNAME("navigation_finished"));
+}
+
+void NavigationAgent3D::_transition_to_target_reached() {
+ target_reached = true;
+ emit_signal(SNAME("target_reached"));
}
void NavigationAgent3D::set_avoidance_layers(uint32_t p_layers) {
diff --git a/scene/3d/navigation_agent_3d.h b/scene/3d/navigation_agent_3d.h
index ff0498a2a8..4eaed83149 100644
--- a/scene/3d/navigation_agent_3d.h
+++ b/scene/3d/navigation_agent_3d.h
@@ -95,6 +95,7 @@ class NavigationAgent3D : public Node {
bool target_position_submitted = false;
bool target_reached = false;
bool navigation_finished = true;
+ bool last_waypoint_reached = false;
// No initialized on purpose
uint32_t update_frame_id = 0;
@@ -250,9 +251,21 @@ public:
float get_debug_path_custom_point_size() const;
private:
- void update_navigation();
+ bool _is_target_reachable() const;
+ Vector3 _get_final_position() const;
+
+ void _update_navigation();
+ void _advance_waypoints(const Vector3 &p_origin);
void _request_repath();
- void _check_distance_to_target();
+
+ bool _is_last_waypoint() const;
+ void _move_to_next_waypoint();
+ bool _is_within_waypoint_distance(const Vector3 &p_origin) const;
+ bool _is_within_target_distance(const Vector3 &p_origin) const;
+
+ void _trigger_waypoint_reached();
+ void _transition_to_navigation_finished();
+ void _transition_to_target_reached();
#ifdef DEBUG_ENABLED
void _navigation_debug_changed();
diff --git a/scene/3d/navigation_link_3d.cpp b/scene/3d/navigation_link_3d.cpp
index 70416ca93b..dc776ebea2 100644
--- a/scene/3d/navigation_link_3d.cpp
+++ b/scene/3d/navigation_link_3d.cpp
@@ -147,6 +147,8 @@ void NavigationLink3D::_update_debug_mesh() {
#endif // DEBUG_ENABLED
void NavigationLink3D::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("get_rid"), &NavigationLink3D::get_rid);
+
ClassDB::bind_method(D_METHOD("set_enabled", "enabled"), &NavigationLink3D::set_enabled);
ClassDB::bind_method(D_METHOD("is_enabled"), &NavigationLink3D::is_enabled);
@@ -263,7 +265,13 @@ void NavigationLink3D::_notification(int p_what) {
NavigationLink3D::NavigationLink3D() {
link = NavigationServer3D::get_singleton()->link_create();
+
NavigationServer3D::get_singleton()->link_set_owner_id(link, get_instance_id());
+ NavigationServer3D::get_singleton()->link_set_enter_cost(link, enter_cost);
+ NavigationServer3D::get_singleton()->link_set_travel_cost(link, travel_cost);
+ NavigationServer3D::get_singleton()->link_set_navigation_layers(link, navigation_layers);
+ NavigationServer3D::get_singleton()->link_set_bidirectional(link, bidirectional);
+ NavigationServer3D::get_singleton()->link_set_enabled(link, enabled);
set_notify_transform(true);
}
@@ -284,6 +292,10 @@ NavigationLink3D::~NavigationLink3D() {
#endif // DEBUG_ENABLED
}
+RID NavigationLink3D::get_rid() const {
+ return link;
+}
+
void NavigationLink3D::set_enabled(bool p_enabled) {
if (enabled == p_enabled) {
return;
diff --git a/scene/3d/navigation_link_3d.h b/scene/3d/navigation_link_3d.h
index ec92fb9dd9..1867082811 100644
--- a/scene/3d/navigation_link_3d.h
+++ b/scene/3d/navigation_link_3d.h
@@ -67,6 +67,8 @@ public:
NavigationLink3D();
~NavigationLink3D();
+ RID get_rid() const;
+
void set_enabled(bool p_enabled);
bool is_enabled() const { return enabled; }
diff --git a/scene/3d/navigation_obstacle_3d.cpp b/scene/3d/navigation_obstacle_3d.cpp
index 95881b1d5a..98cd5efef2 100644
--- a/scene/3d/navigation_obstacle_3d.cpp
+++ b/scene/3d/navigation_obstacle_3d.cpp
@@ -167,13 +167,11 @@ NavigationObstacle3D::NavigationObstacle3D() {
obstacle = NavigationServer3D::get_singleton()->obstacle_create();
NavigationServer3D::get_singleton()->obstacle_set_height(obstacle, height);
-
- set_radius(radius);
- set_height(height);
- set_vertices(vertices);
- set_avoidance_layers(avoidance_layers);
- set_avoidance_enabled(avoidance_enabled);
- set_use_3d_avoidance(use_3d_avoidance);
+ NavigationServer3D::get_singleton()->obstacle_set_radius(obstacle, radius);
+ NavigationServer3D::get_singleton()->obstacle_set_vertices(obstacle, vertices);
+ NavigationServer3D::get_singleton()->obstacle_set_avoidance_layers(obstacle, avoidance_layers);
+ NavigationServer3D::get_singleton()->obstacle_set_use_3d_avoidance(obstacle, use_3d_avoidance);
+ NavigationServer3D::get_singleton()->obstacle_set_avoidance_enabled(obstacle, avoidance_enabled);
#ifdef DEBUG_ENABLED
NavigationServer3D::get_singleton()->connect("avoidance_debug_changed", callable_mp(this, &NavigationObstacle3D::_update_fake_agent_radius_debug));
diff --git a/scene/3d/navigation_region_3d.cpp b/scene/3d/navigation_region_3d.cpp
index b376a4945e..94c0a2279a 100644
--- a/scene/3d/navigation_region_3d.cpp
+++ b/scene/3d/navigation_region_3d.cpp
@@ -33,6 +33,10 @@
#include "scene/resources/navigation_mesh_source_geometry_data_3d.h"
#include "servers/navigation_server_3d.h"
+RID NavigationRegion3D::get_rid() const {
+ return region;
+}
+
void NavigationRegion3D::set_enabled(bool p_enabled) {
if (enabled == p_enabled) {
return;
@@ -154,7 +158,7 @@ real_t NavigationRegion3D::get_travel_cost() const {
}
RID NavigationRegion3D::get_region_rid() const {
- return region;
+ return get_rid();
}
void NavigationRegion3D::_notification(int p_what) {
@@ -275,6 +279,8 @@ PackedStringArray NavigationRegion3D::get_configuration_warnings() const {
}
void NavigationRegion3D::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("get_rid"), &NavigationRegion3D::get_rid);
+
ClassDB::bind_method(D_METHOD("set_navigation_mesh", "navigation_mesh"), &NavigationRegion3D::set_navigation_mesh);
ClassDB::bind_method(D_METHOD("get_navigation_mesh"), &NavigationRegion3D::get_navigation_mesh);
@@ -410,6 +416,9 @@ NavigationRegion3D::NavigationRegion3D() {
NavigationServer3D::get_singleton()->region_set_owner_id(region, get_instance_id());
NavigationServer3D::get_singleton()->region_set_enter_cost(region, get_enter_cost());
NavigationServer3D::get_singleton()->region_set_travel_cost(region, get_travel_cost());
+ NavigationServer3D::get_singleton()->region_set_navigation_layers(region, navigation_layers);
+ NavigationServer3D::get_singleton()->region_set_use_edge_connections(region, use_edge_connections);
+ NavigationServer3D::get_singleton()->region_set_enabled(region, enabled);
#ifdef DEBUG_ENABLED
NavigationServer3D::get_singleton()->connect(SNAME("map_changed"), callable_mp(this, &NavigationRegion3D::_navigation_map_changed));
diff --git a/scene/3d/navigation_region_3d.h b/scene/3d/navigation_region_3d.h
index 02fe5524b2..fe9ee178ff 100644
--- a/scene/3d/navigation_region_3d.h
+++ b/scene/3d/navigation_region_3d.h
@@ -73,6 +73,8 @@ protected:
#endif // DISABLE_DEPRECATED
public:
+ RID get_rid() const;
+
void set_enabled(bool p_enabled);
bool is_enabled() const;
diff --git a/scene/3d/path_3d.cpp b/scene/3d/path_3d.cpp
index 6aea063096..e38375d339 100644
--- a/scene/3d/path_3d.cpp
+++ b/scene/3d/path_3d.cpp
@@ -93,24 +93,63 @@ void Path3D::_update_debug_mesh() {
return;
}
- Vector<Vector3> vertex_array;
+ real_t interval = 0.1;
+ const real_t length = curve->get_baked_length();
- for (int i = 1; i < curve->get_point_count(); i++) {
- Vector3 line_end = curve->get_point_position(i);
- Vector3 line_start = curve->get_point_position(i - 1);
- vertex_array.push_back(line_start);
- vertex_array.push_back(line_end);
+ if (length <= CMP_EPSILON) {
+ RS::get_singleton()->instance_set_visible(debug_instance, false);
+ return;
}
- Array mesh_array;
- mesh_array.resize(Mesh::ARRAY_MAX);
- mesh_array[Mesh::ARRAY_VERTEX] = vertex_array;
+ const int sample_count = int(length / interval) + 2;
+ interval = length / (sample_count - 1);
+
+ Vector<Vector3> ribbon;
+ ribbon.resize(sample_count);
+ Vector3 *ribbon_ptr = ribbon.ptrw();
+
+ Vector<Vector3> bones;
+ bones.resize(sample_count * 4);
+ Vector3 *bones_ptr = bones.ptrw();
+
+ for (int i = 0; i < sample_count; i++) {
+ const Transform3D r = curve->sample_baked_with_rotation(i * interval, true, true);
+
+ const Vector3 p1 = r.origin;
+ const Vector3 side = r.basis.get_column(0);
+ const Vector3 up = r.basis.get_column(1);
+ const Vector3 forward = r.basis.get_column(2);
+
+ // Path3D as a ribbon.
+ ribbon_ptr[i] = p1;
+
+ // Fish Bone.
+ const Vector3 p_left = p1 + (side + forward - up * 0.3) * 0.06;
+ const Vector3 p_right = p1 + (-side + forward - up * 0.3) * 0.06;
+
+ const int bone_idx = i * 4;
+
+ bones_ptr[bone_idx] = p1;
+ bones_ptr[bone_idx + 1] = p_left;
+ bones_ptr[bone_idx + 2] = p1;
+ bones_ptr[bone_idx + 3] = p_right;
+ }
+
+ Array ribbon_array;
+ ribbon_array.resize(Mesh::ARRAY_MAX);
+ ribbon_array[Mesh::ARRAY_VERTEX] = ribbon;
+
+ Array bone_array;
+ bone_array.resize(Mesh::ARRAY_MAX);
+ bone_array[Mesh::ARRAY_VERTEX] = bones;
debug_mesh->clear_surfaces();
- debug_mesh->add_surface_from_arrays(Mesh::PRIMITIVE_LINES, mesh_array);
+ debug_mesh->add_surface_from_arrays(Mesh::PRIMITIVE_LINE_STRIP, ribbon_array);
+ debug_mesh->add_surface_from_arrays(Mesh::PRIMITIVE_LINES, bone_array);
RS::get_singleton()->instance_set_base(debug_instance, debug_mesh->get_rid());
RS::get_singleton()->mesh_surface_set_material(debug_mesh->get_rid(), 0, st->get_debug_paths_material()->get_rid());
+ RS::get_singleton()->mesh_surface_set_material(debug_mesh->get_rid(), 1, st->get_debug_paths_material()->get_rid());
if (is_inside_tree()) {
RS::get_singleton()->instance_set_scenario(debug_instance, get_world_3d()->get_scenario());
RS::get_singleton()->instance_set_transform(debug_instance, get_global_transform());
diff --git a/scene/3d/physics_body_3d.cpp b/scene/3d/physics_body_3d.cpp
index e8d05f129f..ed64c16564 100644
--- a/scene/3d/physics_body_3d.cpp
+++ b/scene/3d/physics_body_3d.cpp
@@ -506,10 +506,14 @@ void RigidBody3D::_body_state_changed(PhysicsDirectBodyState3D *p_state) {
if (GDVIRTUAL_IS_OVERRIDDEN(_integrate_forces)) {
_sync_body_state(p_state);
+ Transform3D old_transform = get_global_transform();
GDVIRTUAL_CALL(_integrate_forces, p_state);
+ Transform3D new_transform = get_global_transform();
- // Update the physics server with any new transform, to prevent it from being overwritten at the sync below.
- force_update_transform();
+ if (new_transform != old_transform) {
+ // Update the physics server with the new transform, to prevent it from being overwritten at the sync below.
+ PhysicsServer3D::get_singleton()->body_set_state(get_rid(), PhysicsServer3D::BODY_STATE_TRANSFORM, new_transform);
+ }
}
_sync_body_state(p_state);
@@ -2945,10 +2949,14 @@ void PhysicalBone3D::_body_state_changed(PhysicsDirectBodyState3D *p_state) {
if (GDVIRTUAL_IS_OVERRIDDEN(_integrate_forces)) {
_sync_body_state(p_state);
+ Transform3D old_transform = get_global_transform();
GDVIRTUAL_CALL(_integrate_forces, p_state);
+ Transform3D new_transform = get_global_transform();
- // Update the physics server with any new transform, to prevent it from being overwritten at the sync below.
- force_update_transform();
+ if (new_transform != old_transform) {
+ // Update the physics server with the new transform, to prevent it from being overwritten at the sync below.
+ PhysicsServer3D::get_singleton()->body_set_state(get_rid(), PhysicsServer3D::BODY_STATE_TRANSFORM, new_transform);
+ }
}
_sync_body_state(p_state);
diff --git a/scene/3d/sprite_3d.cpp b/scene/3d/sprite_3d.cpp
index 920cf22b83..b8b0d31d45 100644
--- a/scene/3d/sprite_3d.cpp
+++ b/scene/3d/sprite_3d.cpp
@@ -286,6 +286,10 @@ void SpriteBase3D::draw_texture_rect(Ref<Texture2D> p_texture, Rect2 p_dst_rect,
}
void SpriteBase3D::set_centered(bool p_center) {
+ if (centered == p_center) {
+ return;
+ }
+
centered = p_center;
_queue_redraw();
}
@@ -295,6 +299,10 @@ bool SpriteBase3D::is_centered() const {
}
void SpriteBase3D::set_offset(const Point2 &p_offset) {
+ if (offset == p_offset) {
+ return;
+ }
+
offset = p_offset;
_queue_redraw();
}
@@ -304,6 +312,10 @@ Point2 SpriteBase3D::get_offset() const {
}
void SpriteBase3D::set_flip_h(bool p_flip) {
+ if (hflip == p_flip) {
+ return;
+ }
+
hflip = p_flip;
_queue_redraw();
}
@@ -313,6 +325,10 @@ bool SpriteBase3D::is_flipped_h() const {
}
void SpriteBase3D::set_flip_v(bool p_flip) {
+ if (vflip == p_flip) {
+ return;
+ }
+
vflip = p_flip;
_queue_redraw();
}
@@ -322,6 +338,10 @@ bool SpriteBase3D::is_flipped_v() const {
}
void SpriteBase3D::set_modulate(const Color &p_color) {
+ if (modulate == p_color) {
+ return;
+ }
+
modulate = p_color;
_propagate_color_changed();
_queue_redraw();
@@ -333,6 +353,11 @@ Color SpriteBase3D::get_modulate() const {
void SpriteBase3D::set_render_priority(int p_priority) {
ERR_FAIL_COND(p_priority < RS::MATERIAL_RENDER_PRIORITY_MIN || p_priority > RS::MATERIAL_RENDER_PRIORITY_MAX);
+
+ if (render_priority == p_priority) {
+ return;
+ }
+
render_priority = p_priority;
_queue_redraw();
}
@@ -342,6 +367,10 @@ int SpriteBase3D::get_render_priority() const {
}
void SpriteBase3D::set_pixel_size(real_t p_amount) {
+ if (pixel_size == p_amount) {
+ return;
+ }
+
pixel_size = p_amount;
_queue_redraw();
}
@@ -352,6 +381,11 @@ real_t SpriteBase3D::get_pixel_size() const {
void SpriteBase3D::set_axis(Vector3::Axis p_axis) {
ERR_FAIL_INDEX(p_axis, 3);
+
+ if (axis == p_axis) {
+ return;
+ }
+
axis = p_axis;
_queue_redraw();
}
@@ -445,6 +479,11 @@ Ref<TriangleMesh> SpriteBase3D::generate_triangle_mesh() const {
void SpriteBase3D::set_draw_flag(DrawFlags p_flag, bool p_enable) {
ERR_FAIL_INDEX(p_flag, FLAG_MAX);
+
+ if (flags[p_flag] == p_enable) {
+ return;
+ }
+
flags[p_flag] = p_enable;
_queue_redraw();
}
@@ -456,6 +495,11 @@ bool SpriteBase3D::get_draw_flag(DrawFlags p_flag) const {
void SpriteBase3D::set_alpha_cut_mode(AlphaCutMode p_mode) {
ERR_FAIL_INDEX(p_mode, ALPHA_CUT_MAX);
+
+ if (alpha_cut == p_mode) {
+ return;
+ }
+
alpha_cut = p_mode;
_queue_redraw();
}
@@ -465,10 +509,12 @@ SpriteBase3D::AlphaCutMode SpriteBase3D::get_alpha_cut_mode() const {
}
void SpriteBase3D::set_alpha_hash_scale(float p_hash_scale) {
- if (alpha_hash_scale != p_hash_scale) {
- alpha_hash_scale = p_hash_scale;
- _queue_redraw();
+ if (alpha_hash_scale == p_hash_scale) {
+ return;
}
+
+ alpha_hash_scale = p_hash_scale;
+ _queue_redraw();
}
float SpriteBase3D::get_alpha_hash_scale() const {
@@ -476,10 +522,12 @@ float SpriteBase3D::get_alpha_hash_scale() const {
}
void SpriteBase3D::set_alpha_scissor_threshold(float p_threshold) {
- if (alpha_scissor_threshold != p_threshold) {
- alpha_scissor_threshold = p_threshold;
- _queue_redraw();
+ if (alpha_scissor_threshold == p_threshold) {
+ return;
}
+
+ alpha_scissor_threshold = p_threshold;
+ _queue_redraw();
}
float SpriteBase3D::get_alpha_scissor_threshold() const {
@@ -487,10 +535,12 @@ float SpriteBase3D::get_alpha_scissor_threshold() const {
}
void SpriteBase3D::set_alpha_antialiasing(BaseMaterial3D::AlphaAntiAliasing p_alpha_aa) {
- if (alpha_antialiasing_mode != p_alpha_aa) {
- alpha_antialiasing_mode = p_alpha_aa;
- _queue_redraw();
+ if (alpha_antialiasing_mode == p_alpha_aa) {
+ return;
}
+
+ alpha_antialiasing_mode = p_alpha_aa;
+ _queue_redraw();
}
BaseMaterial3D::AlphaAntiAliasing SpriteBase3D::get_alpha_antialiasing() const {
@@ -498,10 +548,12 @@ BaseMaterial3D::AlphaAntiAliasing SpriteBase3D::get_alpha_antialiasing() const {
}
void SpriteBase3D::set_alpha_antialiasing_edge(float p_edge) {
- if (alpha_antialiasing_edge != p_edge) {
- alpha_antialiasing_edge = p_edge;
- _queue_redraw();
+ if (alpha_antialiasing_edge == p_edge) {
+ return;
}
+
+ alpha_antialiasing_edge = p_edge;
+ _queue_redraw();
}
float SpriteBase3D::get_alpha_antialiasing_edge() const {
@@ -510,6 +562,11 @@ float SpriteBase3D::get_alpha_antialiasing_edge() const {
void SpriteBase3D::set_billboard_mode(StandardMaterial3D::BillboardMode p_mode) {
ERR_FAIL_INDEX(p_mode, 3); // Cannot use BILLBOARD_PARTICLES.
+
+ if (billboard_mode == p_mode) {
+ return;
+ }
+
billboard_mode = p_mode;
_queue_redraw();
}
@@ -519,10 +576,12 @@ StandardMaterial3D::BillboardMode SpriteBase3D::get_billboard_mode() const {
}
void SpriteBase3D::set_texture_filter(StandardMaterial3D::TextureFilter p_filter) {
- if (texture_filter != p_filter) {
- texture_filter = p_filter;
- _queue_redraw();
+ if (texture_filter == p_filter) {
+ return;
}
+
+ texture_filter = p_filter;
+ _queue_redraw();
}
StandardMaterial3D::TextureFilter SpriteBase3D::get_texture_filter() const {
@@ -767,9 +826,12 @@ bool Sprite3D::is_region_enabled() const {
}
void Sprite3D::set_region_rect(const Rect2 &p_region_rect) {
- bool changed = region_rect != p_region_rect;
+ if (region_rect == p_region_rect) {
+ return;
+ }
+
region_rect = p_region_rect;
- if (region && changed) {
+ if (region) {
_queue_redraw();
}
}
@@ -781,10 +843,12 @@ Rect2 Sprite3D::get_region_rect() const {
void Sprite3D::set_frame(int p_frame) {
ERR_FAIL_INDEX(p_frame, int64_t(vframes) * hframes);
- frame = p_frame;
+ if (frame == p_frame) {
+ return;
+ }
+ frame = p_frame;
_queue_redraw();
-
emit_signal(SceneStringNames::get_singleton()->frame_changed);
}
@@ -804,8 +868,16 @@ Vector2i Sprite3D::get_frame_coords() const {
}
void Sprite3D::set_vframes(int p_amount) {
- ERR_FAIL_COND(p_amount < 1);
+ ERR_FAIL_COND_MSG(p_amount < 1, "Amount of vframes cannot be smaller than 1.");
+
+ if (vframes == p_amount) {
+ return;
+ }
+
vframes = p_amount;
+ if (frame >= vframes * hframes) {
+ frame = 0;
+ }
_queue_redraw();
notify_property_list_changed();
}
@@ -815,8 +887,27 @@ int Sprite3D::get_vframes() const {
}
void Sprite3D::set_hframes(int p_amount) {
- ERR_FAIL_COND(p_amount < 1);
+ ERR_FAIL_COND_MSG(p_amount < 1, "Amount of hframes cannot be smaller than 1.");
+
+ if (hframes == p_amount) {
+ return;
+ }
+
+ if (vframes > 1) {
+ // Adjust the frame to fit new sheet dimensions.
+ int original_column = frame % hframes;
+ if (original_column >= p_amount) {
+ // Frame's column was dropped, reset.
+ frame = 0;
+ } else {
+ int original_row = frame / hframes;
+ frame = original_row * p_amount + original_column;
+ }
+ }
hframes = p_amount;
+ if (frame >= vframes * hframes) {
+ frame = 0;
+ }
_queue_redraw();
notify_property_list_changed();
}
diff --git a/scene/animation/animation_mixer.cpp b/scene/animation/animation_mixer.cpp
index fb17dae832..da00d1dd7b 100644
--- a/scene/animation/animation_mixer.cpp
+++ b/scene/animation/animation_mixer.cpp
@@ -445,7 +445,7 @@ bool AnimationMixer::is_active() const {
void AnimationMixer::set_root_node(const NodePath &p_path) {
root_node = p_path;
- clear_caches();
+ _clear_caches();
}
NodePath AnimationMixer::get_root_node() const {
@@ -454,7 +454,7 @@ NodePath AnimationMixer::get_root_node() const {
void AnimationMixer::set_deterministic(bool p_deterministic) {
deterministic = p_deterministic;
- clear_caches();
+ _clear_caches();
}
bool AnimationMixer::is_deterministic() const {
@@ -563,7 +563,7 @@ void AnimationMixer::_clear_audio_streams() {
void AnimationMixer::_clear_playing_caches() {
for (const TrackCache *E : playing_caches) {
if (ObjectDB::get_instance(E->object_id)) {
- E->object->call(SNAME("stop"));
+ E->object->call(SNAME("stop"), true);
}
}
playing_caches.clear();
@@ -638,6 +638,10 @@ bool AnimationMixer::_update_caches() {
switch (track_type) {
case Animation::TYPE_VALUE: {
+ // If a value track without a key is cached first, the initial value cannot be determined.
+ // It is a corner case, but which may cause problems with blending.
+ ERR_CONTINUE_MSG(anim->track_get_key_count(i) == 0, "AnimationMixer: '" + String(E) + "', Value Track: '" + String(path) + "' must have at least one key to cache for blending.");
+
TrackCacheValue *track_value = memnew(TrackCacheValue);
if (resource.is_valid()) {
@@ -654,9 +658,6 @@ bool AnimationMixer::_update_caches() {
track = track_value;
- // If a value track without a key is cached first, the initial value cannot be determined.
- // It is a corner case, but which may cause problems with blending.
- ERR_CONTINUE_MSG(anim->track_get_key_count(i) == 0, "AnimationMixer: '" + String(E) + "', Value Track: '" + String(path) + "' must have at least one key to cache for blending.");
track_value->init_value = anim->track_get_key_value(i, 0);
track_value->init_value.zero();
@@ -667,6 +668,7 @@ bool AnimationMixer::_update_caches() {
track_value->init_value = reset_anim->track_get_key_value(rt, 0);
}
}
+
} break;
case Animation::TYPE_POSITION_3D:
case Animation::TYPE_ROTATION_3D:
@@ -812,6 +814,7 @@ bool AnimationMixer::_update_caches() {
track_bezier->init_value = (reset_anim->track_get_key_value(rt, 0).operator Array())[0];
}
}
+
} break;
case Animation::TYPE_AUDIO: {
TrackCacheAudio *track_audio = memnew(TrackCacheAudio);
@@ -868,43 +871,26 @@ bool AnimationMixer::_update_caches() {
track_value->is_continuous |= anim->value_track_get_update_mode(i) != Animation::UPDATE_DISCRETE;
track_value->is_using_angle |= anim->track_get_interpolation_type(i) == Animation::INTERPOLATION_LINEAR_ANGLE || anim->track_get_interpolation_type(i) == Animation::INTERPOLATION_CUBIC_ANGLE;
- // TODO: Currently, misc type cannot be blended. In the future,
- // it should have a separate blend weight, just as bool is converted to 0 and 1.
+ // TODO: Currently, misc type cannot be blended.
+ // In the future, it should have a separate blend weight, just as bool is converted to 0 and 1.
// Then, it should provide the correct precedence value.
+ bool skip_update_mode_warning = false;
if (track_value->is_continuous) {
- switch (track_value->init_value.get_type()) {
- case Variant::NIL:
- case Variant::STRING_NAME:
- case Variant::NODE_PATH:
- case Variant::RID:
- case Variant::OBJECT:
- case Variant::CALLABLE:
- case Variant::SIGNAL:
- case Variant::DICTIONARY:
- case Variant::ARRAY:
- case Variant::PACKED_BYTE_ARRAY:
- case Variant::PACKED_INT32_ARRAY:
- case Variant::PACKED_INT64_ARRAY:
- case Variant::PACKED_FLOAT32_ARRAY:
- case Variant::PACKED_FLOAT64_ARRAY:
- case Variant::PACKED_STRING_ARRAY:
- case Variant::PACKED_VECTOR2_ARRAY:
- case Variant::PACKED_VECTOR3_ARRAY:
- case Variant::PACKED_COLOR_ARRAY: {
- WARN_PRINT_ONCE_ED("AnimationMixer: '" + String(E) + "', Value Track: '" + String(path) + "' uses a non-numeric type as key value with UpdateMode.UPDATE_CONTINUOUS. This will not be blended correctly, so it is forced to UpdateMode.UPDATE_DISCRETE.");
- track_value->is_continuous = false;
- break;
- }
- default: {
- }
+ if (!Animation::is_variant_interpolatable(track_value->init_value)) {
+ WARN_PRINT_ONCE_ED("AnimationMixer: '" + String(E) + "', Value Track: '" + String(path) + "' uses a non-numeric type as key value with UpdateMode.UPDATE_CONTINUOUS. This will not be blended correctly, so it is forced to UpdateMode.UPDATE_DISCRETE.");
+ track_value->is_continuous = false;
+ skip_update_mode_warning = true;
+ }
+ if (track_value->init_value.is_string()) {
+ WARN_PRINT_ONCE_ED("AnimationMixer: '" + String(E) + "', Value Track: '" + String(path) + "' blends String types. This is an experimental algorithm.");
}
}
- if (was_continuous != track_value->is_continuous) {
- WARN_PRINT_ONCE_ED("Value Track: " + String(path) + " has different update modes between some animations may be blended. Blending prioritizes UpdateMode.UPDATE_CONTINUOUS, so the process treat UpdateMode.UPDATE_DISCRETE as UpdateMode.UPDATE_CONTINUOUS with InterpolationType.INTERPOLATION_NEAREST.");
+ if (!skip_update_mode_warning && was_continuous != track_value->is_continuous) {
+ WARN_PRINT_ONCE_ED("AnimationMixer: '" + String(E) + "', Value Track: '" + String(path) + "' has different update modes between some animations which may be blended together. Blending prioritizes UpdateMode.UPDATE_CONTINUOUS, so the process treats UpdateMode.UPDATE_DISCRETE as UpdateMode.UPDATE_CONTINUOUS with InterpolationType.INTERPOLATION_NEAREST.");
}
if (was_using_angle != track_value->is_using_angle) {
- WARN_PRINT_ONCE_ED("Value Track: " + String(path) + " has different interpolation types for rotation between some animations may be blended. Blending prioritizes angle interpolation, so the blending result uses the shortest path referenced to the initial (RESET animation) value.");
+ WARN_PRINT_ONCE_ED("AnimationMixer: '" + String(E) + "', Value Track: '" + String(path) + "' has different interpolation types for rotation between some animations which may be blended together. Blending prioritizes angle interpolation, so the blending result uses the shortest path referenced to the initial (RESET animation) value.");
}
}
@@ -950,9 +936,7 @@ bool AnimationMixer::_update_caches() {
void AnimationMixer::_process_animation(double p_delta, bool p_update_only) {
_blend_init();
if (_blend_pre_process(p_delta, track_count, track_map)) {
- if (!deterministic) {
- _blend_calc_total_weight();
- }
+ _blend_calc_total_weight();
_blend_process(p_delta, p_update_only);
_blend_apply();
_blend_post_process();
@@ -1024,7 +1008,8 @@ void AnimationMixer::_blend_init() {
} break;
case Animation::TYPE_VALUE: {
TrackCacheValue *t = static_cast<TrackCacheValue *>(track);
- t->value = t->init_value;
+ t->value = Animation::cast_to_blendwise(t->init_value);
+ t->element_size = t->init_value.is_string() ? (real_t)(t->init_value.operator String()).length() : 0;
} break;
case Animation::TYPE_BEZIER: {
TrackCacheBezier *t = static_cast<TrackCacheBezier *>(track);
@@ -1111,7 +1096,7 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) {
ERR_CONTINUE(blend_idx < 0 || blend_idx >= track_count);
real_t blend = blend_idx < track_weights.size() ? track_weights[blend_idx] * weight : weight;
if (!deterministic) {
- // If undeterministic, do normalization.
+ // If non-deterministic, do normalization.
// It would be better to make this if statement outside the for loop, but come here since too much code...
if (Math::is_zero_approx(track->total_weight)) {
continue;
@@ -1434,13 +1419,15 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) {
}
t->value = Math::fposmod(rot_a + (rot_b - rot_init) * (float)blend, (float)Math_TAU);
} else {
- if (t->init_value.get_type() == Variant::BOOL) {
- value = Animation::subtract_variant(value.operator real_t(), t->init_value.operator real_t());
- t->value = Animation::blend_variant(t->value.operator real_t(), value.operator real_t(), blend);
- } else {
- value = Animation::subtract_variant(value, t->init_value);
- t->value = Animation::blend_variant(t->value, value, blend);
+ value = Animation::cast_to_blendwise(value);
+ if (t->init_value.is_array()) {
+ t->element_size = MAX(t->element_size.operator int(), (value.operator Array()).size());
+ } else if (t->init_value.is_string()) {
+ real_t length = Animation::subtract_variant((real_t)(value.operator Array()).size(), (real_t)(t->init_value.operator String()).length());
+ t->element_size = Animation::blend_variant(t->element_size, length, blend);
}
+ value = Animation::subtract_variant(value, Animation::cast_to_blendwise(t->init_value));
+ t->value = Animation::blend_variant(t->value, value, blend);
}
} else {
if (seeked) {
@@ -1710,10 +1697,23 @@ void AnimationMixer::_blend_apply() {
break; // Don't overwrite the value set by UPDATE_DISCRETE.
}
- if (t->init_value.get_type() == Variant::BOOL) {
- t->object->set_indexed(t->subpath, t->value.operator real_t() >= 0.5);
- } else {
- t->object->set_indexed(t->subpath, t->value);
+ // Trim unused elements if init array/string is not blended.
+ if (t->value.is_array()) {
+ int actual_blended_size = (int)Math::round(Math::abs(t->element_size.operator real_t()));
+ if (actual_blended_size < (t->value.operator Array()).size()) {
+ real_t abs_weight = Math::abs(track->total_weight);
+ if (abs_weight >= 1.0) {
+ (t->value.operator Array()).resize(actual_blended_size);
+ } else if (t->init_value.is_string()) {
+ (t->value.operator Array()).resize(Animation::interpolate_variant((t->init_value.operator String()).length(), actual_blended_size, abs_weight));
+ }
+ }
+ }
+
+ // t->object isn't safe here, get instance from id (GH-85365).
+ Object *obj = ObjectDB::get_instance(t->object_id);
+ if (obj) {
+ obj->set_indexed(t->subpath, Animation::cast_from_blendwise(t->value, t->init_value.get_type()));
}
} break;
@@ -1789,7 +1789,7 @@ void AnimationMixer::_blend_apply() {
void AnimationMixer::_call_object(Object *p_object, const StringName &p_method, const Vector<Variant> &p_params, bool p_deferred) {
// Separate function to use alloca() more efficiently
- const Variant **argptrs = (const Variant **)alloca(sizeof(const Variant **) * p_params.size());
+ const Variant **argptrs = (const Variant **)alloca(sizeof(Variant *) * p_params.size());
const Variant *args = p_params.ptr();
uint32_t argcount = p_params.size();
for (uint32_t i = 0; i < argcount; i++) {
@@ -1874,7 +1874,6 @@ bool AnimationMixer::is_reset_on_save_enabled() const {
return reset_on_save;
}
-#ifdef TOOLS_ENABLED
bool AnimationMixer::can_apply_reset() const {
return has_animation(SceneStringNames::get_singleton()->RESET);
}
@@ -1935,7 +1934,6 @@ void AnimationMixer::_build_backup_track_cache() {
if (asp) {
t->object->call(SNAME("set_stream"), Ref<AudioStream>());
}
- track = memnew(TrackCache); // Make disable this track cache.
} break;
default: {
} // The rest don't matter.
@@ -1965,29 +1963,6 @@ Ref<AnimatedValuesBackup> AnimationMixer::make_backup() {
return backup;
}
-Ref<AnimatedValuesBackup> AnimationMixer::apply_reset(bool p_user_initiated) {
- if (!p_user_initiated && dummy) {
- return Ref<AnimatedValuesBackup>();
- }
- ERR_FAIL_COND_V(!can_apply_reset(), Ref<AnimatedValuesBackup>());
-
- Ref<Animation> reset_anim = animation_set[SceneStringNames::get_singleton()->RESET].animation;
- ERR_FAIL_COND_V(reset_anim.is_null(), Ref<AnimatedValuesBackup>());
-
- Ref<AnimatedValuesBackup> backup_current = make_backup();
- if (p_user_initiated) {
- EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
- ur->create_action(TTR("Animation Apply Reset"));
- ur->add_do_method(this, "_reset");
- ur->add_undo_method(this, "_restore", backup_current);
- ur->commit_action();
- } else {
- reset();
- }
-
- return backup_current;
-}
-
void AnimationMixer::reset() {
ERR_FAIL_COND(!can_apply_reset());
@@ -2011,11 +1986,36 @@ void AnimationMixer::reset() {
}
void AnimationMixer::restore(const Ref<AnimatedValuesBackup> &p_backup) {
+ ERR_FAIL_COND(p_backup.is_null());
track_cache = p_backup->get_data();
_blend_apply();
track_cache = HashMap<NodePath, AnimationMixer::TrackCache *>();
cache_valid = false;
}
+
+#ifdef TOOLS_ENABLED
+Ref<AnimatedValuesBackup> AnimationMixer::apply_reset(bool p_user_initiated) {
+ if (!p_user_initiated && dummy) {
+ return Ref<AnimatedValuesBackup>();
+ }
+ ERR_FAIL_COND_V(!can_apply_reset(), Ref<AnimatedValuesBackup>());
+
+ Ref<Animation> reset_anim = animation_set[SceneStringNames::get_singleton()->RESET].animation;
+ ERR_FAIL_COND_V(reset_anim.is_null(), Ref<AnimatedValuesBackup>());
+
+ Ref<AnimatedValuesBackup> backup_current = make_backup();
+ if (p_user_initiated) {
+ EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
+ ur->create_action(TTR("Animation Apply Reset"));
+ ur->add_do_method(this, "_reset");
+ ur->add_undo_method(this, "_restore", backup_current);
+ ur->commit_action();
+ } else {
+ reset();
+ }
+
+ return backup_current;
+}
#endif // TOOLS_ENABLED
/* -------------------------------------------- */
@@ -2134,6 +2134,9 @@ void AnimationMixer::_bind_methods() {
ADD_SIGNAL(MethodInfo(SNAME("animation_finished"), PropertyInfo(Variant::STRING_NAME, "anim_name")));
ADD_SIGNAL(MethodInfo(SNAME("animation_started"), PropertyInfo(Variant::STRING_NAME, "anim_name")));
ADD_SIGNAL(MethodInfo(SNAME("caches_cleared")));
+
+ ClassDB::bind_method(D_METHOD("_reset"), &AnimationMixer::reset);
+ ClassDB::bind_method(D_METHOD("_restore", "backup"), &AnimationMixer::restore);
}
AnimationMixer::AnimationMixer() {
@@ -2142,3 +2145,76 @@ AnimationMixer::AnimationMixer() {
AnimationMixer::~AnimationMixer() {
}
+
+void AnimatedValuesBackup::set_data(const HashMap<NodePath, AnimationMixer::TrackCache *> p_data) {
+ clear_data();
+
+ for (const KeyValue<NodePath, AnimationMixer::TrackCache *> &E : p_data) {
+ AnimationMixer::TrackCache *track = get_cache_copy(E.value);
+ if (!track) {
+ continue; // Some types of tracks do not get a copy and must be ignored.
+ }
+
+ data.insert(E.key, track);
+ }
+}
+
+HashMap<NodePath, AnimationMixer::TrackCache *> AnimatedValuesBackup::get_data() const {
+ HashMap<NodePath, AnimationMixer::TrackCache *> ret;
+ for (const KeyValue<NodePath, AnimationMixer::TrackCache *> &E : data) {
+ AnimationMixer::TrackCache *track = get_cache_copy(E.value);
+ ERR_CONTINUE(!track); // Backup shouldn't contain tracks that cannot be copied, this is a mistake.
+
+ ret.insert(E.key, track);
+ }
+ return ret;
+}
+
+void AnimatedValuesBackup::clear_data() {
+ for (KeyValue<NodePath, AnimationMixer::TrackCache *> &K : data) {
+ memdelete(K.value);
+ }
+ data.clear();
+}
+
+AnimationMixer::TrackCache *AnimatedValuesBackup::get_cache_copy(AnimationMixer::TrackCache *p_cache) const {
+ switch (p_cache->type) {
+ case Animation::TYPE_VALUE: {
+ AnimationMixer::TrackCacheValue *src = static_cast<AnimationMixer::TrackCacheValue *>(p_cache);
+ AnimationMixer::TrackCacheValue *tc = memnew(AnimationMixer::TrackCacheValue(*src));
+ return tc;
+ }
+
+ case Animation::TYPE_POSITION_3D:
+ case Animation::TYPE_ROTATION_3D:
+ case Animation::TYPE_SCALE_3D: {
+ AnimationMixer::TrackCacheTransform *src = static_cast<AnimationMixer::TrackCacheTransform *>(p_cache);
+ AnimationMixer::TrackCacheTransform *tc = memnew(AnimationMixer::TrackCacheTransform(*src));
+ return tc;
+ }
+
+ case Animation::TYPE_BLEND_SHAPE: {
+ AnimationMixer::TrackCacheBlendShape *src = static_cast<AnimationMixer::TrackCacheBlendShape *>(p_cache);
+ AnimationMixer::TrackCacheBlendShape *tc = memnew(AnimationMixer::TrackCacheBlendShape(*src));
+ return tc;
+ }
+
+ case Animation::TYPE_BEZIER: {
+ AnimationMixer::TrackCacheBezier *src = static_cast<AnimationMixer::TrackCacheBezier *>(p_cache);
+ AnimationMixer::TrackCacheBezier *tc = memnew(AnimationMixer::TrackCacheBezier(*src));
+ return tc;
+ }
+
+ case Animation::TYPE_AUDIO: {
+ AnimationMixer::TrackCacheAudio *src = static_cast<AnimationMixer::TrackCacheAudio *>(p_cache);
+ AnimationMixer::TrackCacheAudio *tc = memnew(AnimationMixer::TrackCacheAudio(*src));
+ return tc;
+ }
+
+ case Animation::TYPE_METHOD:
+ case Animation::TYPE_ANIMATION: {
+ // Nothing to do here.
+ } break;
+ }
+ return nullptr;
+}
diff --git a/scene/animation/animation_mixer.h b/scene/animation/animation_mixer.h
index 6aa050d1fb..0cd204b384 100644
--- a/scene/animation/animation_mixer.h
+++ b/scene/animation/animation_mixer.h
@@ -38,14 +38,12 @@
#include "scene/resources/animation_library.h"
#include "scene/resources/audio_stream_polyphonic.h"
-#ifdef TOOLS_ENABLED
class AnimatedValuesBackup;
-#endif // TOOLS_ENABLED
class AnimationMixer : public Node {
GDCLASS(AnimationMixer, Node);
-#ifdef TOOLS_ENABLED
friend AnimatedValuesBackup;
+#ifdef TOOLS_ENABLED
bool editing = false;
bool dummy = false;
#endif // TOOLS_ENABLED
@@ -143,6 +141,17 @@ protected:
Object *object = nullptr;
ObjectID object_id;
real_t total_weight = 0.0;
+
+ TrackCache() = default;
+ TrackCache(const TrackCache &p_other) :
+ root_motion(p_other.root_motion),
+ setup_pass(p_other.setup_pass),
+ type(p_other.type),
+ object(p_other.object),
+ object_id(p_other.object_id),
+ total_weight(p_other.total_weight) {}
+
+ virtual ~TrackCache() {}
};
struct TrackCacheTransform : public TrackCache {
@@ -161,9 +170,28 @@ protected:
Quaternion rot;
Vector3 scale;
+ TrackCacheTransform(const TrackCacheTransform &p_other) :
+ TrackCache(p_other),
+#ifndef _3D_DISABLED
+ node_3d(p_other.node_3d),
+ skeleton(p_other.skeleton),
+#endif
+ bone_idx(p_other.bone_idx),
+ loc_used(p_other.loc_used),
+ rot_used(p_other.rot_used),
+ scale_used(p_other.scale_used),
+ init_loc(p_other.init_loc),
+ init_rot(p_other.init_rot),
+ init_scale(p_other.init_scale),
+ loc(p_other.loc),
+ rot(p_other.rot),
+ scale(p_other.scale) {
+ }
+
TrackCacheTransform() {
type = Animation::TYPE_POSITION_3D;
}
+ ~TrackCacheTransform() {}
};
struct RootMotionCache {
@@ -177,7 +205,16 @@ protected:
float init_value = 0;
float value = 0;
int shape_index = -1;
+
+ TrackCacheBlendShape(const TrackCacheBlendShape &p_other) :
+ TrackCache(p_other),
+ mesh_3d(p_other.mesh_3d),
+ init_value(p_other.init_value),
+ value(p_other.value),
+ shape_index(p_other.shape_index) {}
+
TrackCacheBlendShape() { type = Animation::TYPE_BLEND_SHAPE; }
+ ~TrackCacheBlendShape() {}
};
struct TrackCacheValue : public TrackCache {
@@ -186,20 +223,45 @@ protected:
Vector<StringName> subpath;
bool is_continuous = false;
bool is_using_angle = false;
+ Variant element_size;
+
+ TrackCacheValue(const TrackCacheValue &p_other) :
+ TrackCache(p_other),
+ init_value(p_other.init_value),
+ value(p_other.value),
+ subpath(p_other.subpath),
+ is_continuous(p_other.is_continuous),
+ is_using_angle(p_other.is_using_angle),
+ element_size(p_other.element_size) {}
+
TrackCacheValue() { type = Animation::TYPE_VALUE; }
+ ~TrackCacheValue() {
+ // Clear ref to avoid leaking.
+ init_value = Variant();
+ value = Variant();
+ }
};
struct TrackCacheMethod : public TrackCache {
TrackCacheMethod() { type = Animation::TYPE_METHOD; }
+ ~TrackCacheMethod() {}
};
struct TrackCacheBezier : public TrackCache {
real_t init_value = 0.0;
real_t value = 0.0;
Vector<StringName> subpath;
+
+ TrackCacheBezier(const TrackCacheBezier &p_other) :
+ TrackCache(p_other),
+ init_value(p_other.init_value),
+ value(p_other.value),
+ subpath(p_other.subpath) {}
+
TrackCacheBezier() {
type = Animation::TYPE_BEZIER;
}
+ ~TrackCacheBezier() {}
};
// Audio stream information for each audio stream placed on the track.
@@ -225,9 +287,16 @@ protected:
Ref<AudioStreamPlaybackPolyphonic> audio_stream_playback;
HashMap<ObjectID, PlayingAudioTrackInfo> playing_streams; // Key is Animation resource ObjectID.
+ TrackCacheAudio(const TrackCacheAudio &p_other) :
+ TrackCache(p_other),
+ audio_stream(p_other.audio_stream),
+ audio_stream_playback(p_other.audio_stream_playback),
+ playing_streams(p_other.playing_streams) {}
+
TrackCacheAudio() {
type = Animation::TYPE_AUDIO;
}
+ ~TrackCacheAudio() {}
};
struct TrackCacheAnimation : public TrackCache {
@@ -236,6 +305,7 @@ protected:
TrackCacheAnimation() {
type = Animation::TYPE_ANIMATION;
}
+ ~TrackCacheAnimation() {}
};
RootMotionCache root_motion_cache;
@@ -350,35 +420,40 @@ public:
void set_reset_on_save_enabled(bool p_enabled);
bool is_reset_on_save_enabled() const;
+ bool can_apply_reset() const;
+ void _build_backup_track_cache();
+ Ref<AnimatedValuesBackup> make_backup();
+ void restore(const Ref<AnimatedValuesBackup> &p_backup);
+ void reset();
+
#ifdef TOOLS_ENABLED
+ Ref<AnimatedValuesBackup> apply_reset(bool p_user_initiated = false);
+
void set_editing(bool p_editing);
bool is_editing() const;
void set_dummy(bool p_dummy);
bool is_dummy() const;
-
- bool can_apply_reset() const;
- void _build_backup_track_cache();
- Ref<AnimatedValuesBackup> make_backup();
- Ref<AnimatedValuesBackup> apply_reset(bool p_user_initiated = false);
- void restore(const Ref<AnimatedValuesBackup> &p_backup);
- void reset();
#endif // TOOLS_ENABLED
+
AnimationMixer();
~AnimationMixer();
};
-#ifdef TOOLS_ENABLED
class AnimatedValuesBackup : public RefCounted {
GDCLASS(AnimatedValuesBackup, RefCounted);
HashMap<NodePath, AnimationMixer::TrackCache *> data;
public:
- void set_data(const HashMap<NodePath, AnimationMixer::TrackCache *> p_data) { data = p_data; };
- HashMap<NodePath, AnimationMixer::TrackCache *> get_data() const { return data; };
+ void set_data(const HashMap<NodePath, AnimationMixer::TrackCache *> p_data);
+ HashMap<NodePath, AnimationMixer::TrackCache *> get_data() const;
+ void clear_data();
+
+ AnimationMixer::TrackCache *get_cache_copy(AnimationMixer::TrackCache *p_cache) const;
+
+ ~AnimatedValuesBackup() { clear_data(); }
};
-#endif
VARIANT_ENUM_CAST(AnimationMixer::AnimationCallbackModeProcess);
VARIANT_ENUM_CAST(AnimationMixer::AnimationCallbackModeMethod);
diff --git a/scene/animation/animation_node_state_machine.cpp b/scene/animation/animation_node_state_machine.cpp
index dd5ff7912f..f4e1b3615c 100644
--- a/scene/animation/animation_node_state_machine.cpp
+++ b/scene/animation/animation_node_state_machine.cpp
@@ -860,7 +860,7 @@ double AnimationNodeStateMachinePlayback::_process(const String &p_base_path, An
pi.time = 0;
pi.seeked = true;
}
- fading_from_rem = p_state_machine->blend_node(p_state_machine->states[fading_from].node, fading_from, pi, AnimationNode::FILTER_IGNORE, true); // Blend values must be more than CMP_EPSILON to process discrete keys in edge.
+ fading_from_rem = p_state_machine->blend_node(p_state_machine->states[fading_from].node, fading_from, pi, AnimationNode::FILTER_IGNORE, true, p_test_only); // Blend values must be more than CMP_EPSILON to process discrete keys in edge.
// Guess playback position.
if (fading_from_rem > len_fade_from) { /// Weird but ok.
@@ -976,7 +976,7 @@ bool AnimationNodeStateMachinePlayback::_transition_to_next_recursive(AnimationT
pi.seeked = true;
pi.is_external_seeking = false;
pi.weight = 0;
- p_state_machine->blend_node(p_state_machine->states[current].node, current, pi, AnimationNode::FILTER_IGNORE, true);
+ p_state_machine->blend_node(p_state_machine->states[current].node, current, pi, AnimationNode::FILTER_IGNORE, true, p_test_only);
}
// Just get length to find next recursive.
diff --git a/scene/animation/animation_player.cpp b/scene/animation/animation_player.cpp
index df7044d005..02b74c9188 100644
--- a/scene/animation/animation_player.cpp
+++ b/scene/animation/animation_player.cpp
@@ -151,7 +151,7 @@ void AnimationPlayer::_notification(int p_what) {
if (!Engine::get_singleton()->is_editor_hint() && animation_set.has(autoplay)) {
set_active(true);
play(autoplay);
- seek(0, true);
+ _check_immediately_after_start();
}
} break;
}
@@ -233,7 +233,7 @@ void AnimationPlayer::_process_playback_data(PlaybackData &cd, double p_delta, f
pi.delta = delta;
pi.seeked = p_seeked;
}
- pi.is_external_seeking = false;
+ pi.is_external_seeking = true; // AnimationPlayer doesn't have internal seeking.
pi.looped_flag = looped_flag;
pi.weight = p_blend;
make_animation_instance(cd.from->name, pi);
@@ -522,8 +522,9 @@ void AnimationPlayer::seek(double p_time, bool p_update, bool p_update_only) {
return;
}
- playback.current.pos = p_time;
+ _check_immediately_after_start();
+ playback.current.pos = p_time;
if (!playback.current.from) {
if (playback.assigned) {
ERR_FAIL_COND_MSG(!animation_set.has(playback.assigned), vformat("Animation not found: %s.", playback.assigned));
@@ -537,6 +538,18 @@ void AnimationPlayer::seek(double p_time, bool p_update, bool p_update_only) {
playback.seeked = true;
if (p_update) {
_process_animation(0, p_update_only);
+ playback.seeked = false; // If animation was proceeded here, no more seek in internal process.
+ }
+}
+
+void AnimationPlayer::advance(double p_time) {
+ _check_immediately_after_start();
+ AnimationMixer::advance(p_time);
+}
+
+void AnimationPlayer::_check_immediately_after_start() {
+ if (playback.started) {
+ _process_animation(0); // Force process current key for Discrete/Method/Audio/AnimationPlayback. Then, started flag is cleared.
}
}
diff --git a/scene/animation/animation_player.h b/scene/animation/animation_player.h
index 51beb67260..74f9323e2b 100644
--- a/scene/animation/animation_player.h
+++ b/scene/animation/animation_player.h
@@ -111,6 +111,7 @@ private:
void _process_playback_data(PlaybackData &cd, double p_delta, float p_blend, bool p_seeked, bool p_started, bool p_is_current = false);
void _blend_playback_data(double p_delta, bool p_started);
void _stop_internal(bool p_reset, bool p_keep_state);
+ void _check_immediately_after_start();
bool playing = false;
@@ -183,6 +184,8 @@ public:
void get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const override;
+ virtual void advance(double p_time) override;
+
AnimationPlayer();
~AnimationPlayer();
};
diff --git a/scene/animation/animation_tree.cpp b/scene/animation/animation_tree.cpp
index bdadc4ecac..da0687dd70 100644
--- a/scene/animation/animation_tree.cpp
+++ b/scene/animation/animation_tree.cpp
@@ -33,6 +33,7 @@
#include "animation_blend_tree.h"
#include "core/config/engine.h"
+#include "scene/animation/animation_player.h"
#include "scene/scene_string_names.h"
void AnimationNode::get_parameter_list(List<PropertyInfo> *r_list) const {
@@ -100,9 +101,9 @@ void AnimationNode::blend_animation(const StringName &p_animation, AnimationMixe
process_state->tree->make_animation_instance(p_animation, p_playback_info);
}
-double AnimationNode::_pre_process(ProcessState *p_process_state, AnimationMixer::PlaybackInfo p_playback_info) {
+double AnimationNode::_pre_process(ProcessState *p_process_state, AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) {
process_state = p_process_state;
- double t = process(p_playback_info);
+ double t = process(p_playback_info, p_test_only);
process_state = nullptr;
return t;
}
@@ -152,7 +153,7 @@ double AnimationNode::blend_input(int p_input, AnimationMixer::PlaybackInfo p_pl
}
double AnimationNode::blend_node(Ref<AnimationNode> p_node, const StringName &p_subpath, AnimationMixer::PlaybackInfo p_playback_info, FilterAction p_filter, bool p_sync, bool p_test_only) {
- node_state.connections.clear();
+ p_node->node_state.connections.clear();
return _blend_node(p_node, p_subpath, this, p_playback_info, p_filter, p_sync, p_test_only, nullptr);
}
@@ -269,9 +270,9 @@ double AnimationNode::_blend_node(Ref<AnimationNode> p_node, const StringName &p
p_node->node_state.parent = new_parent;
if (!p_playback_info.seeked && !p_sync && !any_valid) {
p_playback_info.time = 0.0;
- return p_node->_pre_process(process_state, p_playback_info);
+ return p_node->_pre_process(process_state, p_playback_info, p_test_only);
}
- return p_node->_pre_process(process_state, p_playback_info);
+ return p_node->_pre_process(process_state, p_playback_info, p_test_only);
}
String AnimationNode::get_caption() const {
@@ -565,12 +566,12 @@ bool AnimationTree::_blend_pre_process(double p_delta, int p_track_count, const
if (started) {
// If started, seek.
pi.seeked = true;
- root_animation_node->_pre_process(&process_state, pi);
+ root_animation_node->_pre_process(&process_state, pi, false);
started = false;
- } else {
- pi.time = p_delta;
- root_animation_node->_pre_process(&process_state, pi);
}
+ pi.seeked = false;
+ pi.time = p_delta;
+ root_animation_node->_pre_process(&process_state, pi, false);
}
if (!process_state.valid) {
@@ -764,15 +765,16 @@ void AnimationTree::_setup_animation_player() {
return;
}
- AnimationMixer *mixer = Object::cast_to<AnimationMixer>(get_node_or_null(animation_player));
- if (mixer) {
- if (!mixer->is_connected(SNAME("caches_cleared"), callable_mp(this, &AnimationTree::_setup_animation_player))) {
- mixer->connect(SNAME("caches_cleared"), callable_mp(this, &AnimationTree::_setup_animation_player), CONNECT_DEFERRED);
+ // Using AnimationPlayer here is for compatibility. Changing to AnimationMixer needs extra work like error handling.
+ AnimationPlayer *player = Object::cast_to<AnimationPlayer>(get_node_or_null(animation_player));
+ if (player) {
+ if (!player->is_connected(SNAME("caches_cleared"), callable_mp(this, &AnimationTree::_setup_animation_player))) {
+ player->connect(SNAME("caches_cleared"), callable_mp(this, &AnimationTree::_setup_animation_player), CONNECT_DEFERRED);
}
- if (!mixer->is_connected(SNAME("animation_list_changed"), callable_mp(this, &AnimationTree::_setup_animation_player))) {
- mixer->connect(SNAME("animation_list_changed"), callable_mp(this, &AnimationTree::_setup_animation_player), CONNECT_DEFERRED);
+ if (!player->is_connected(SNAME("animation_list_changed"), callable_mp(this, &AnimationTree::_setup_animation_player))) {
+ player->connect(SNAME("animation_list_changed"), callable_mp(this, &AnimationTree::_setup_animation_player), CONNECT_DEFERRED);
}
- Node *root = mixer->get_node_or_null(mixer->get_root_node());
+ Node *root = player->get_node_or_null(player->get_root_node());
if (root) {
set_root_node(get_path_to(root, true));
}
@@ -780,9 +782,9 @@ void AnimationTree::_setup_animation_player() {
remove_animation_library(animation_libraries[0].name);
}
List<StringName> list;
- mixer->get_animation_library_list(&list);
+ player->get_animation_library_list(&list);
for (int i = 0; i < list.size(); i++) {
- Ref<AnimationLibrary> lib = mixer->get_animation_library(list[i]);
+ Ref<AnimationLibrary> lib = player->get_animation_library(list[i]);
if (lib.is_valid()) {
add_animation_library(list[i], lib);
}
@@ -799,6 +801,9 @@ void AnimationTree::_validate_property(PropertyInfo &p_property) const {
if (p_property.name == "root_node" || p_property.name.begins_with("libraries")) {
p_property.usage |= PROPERTY_USAGE_READ_ONLY;
}
+ if (p_property.name.begins_with("libraries")) {
+ p_property.usage &= ~PROPERTY_USAGE_STORAGE;
+ }
}
}
diff --git a/scene/animation/animation_tree.h b/scene/animation/animation_tree.h
index 0be487d3fd..87928e4d20 100644
--- a/scene/animation/animation_tree.h
+++ b/scene/animation/animation_tree.h
@@ -85,7 +85,7 @@ public:
void _set_filters(const Array &p_filters);
friend class AnimationNodeBlendTree;
double _blend_node(Ref<AnimationNode> p_node, const StringName &p_subpath, AnimationNode *p_new_parent, AnimationMixer::PlaybackInfo p_playback_info, FilterAction p_filter = FILTER_IGNORE, bool p_sync = true, bool p_test_only = false, real_t *r_activity = nullptr);
- double _pre_process(ProcessState *p_process_state, AnimationMixer::PlaybackInfo p_playback_info);
+ double _pre_process(ProcessState *p_process_state, AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false);
protected:
virtual double _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false);
diff --git a/scene/animation/tween.cpp b/scene/animation/tween.cpp
index 32028bcf28..8193bbf3f1 100644
--- a/scene/animation/tween.cpp
+++ b/scene/animation/tween.cpp
@@ -420,13 +420,8 @@ Variant Tween::interpolate_variant(const Variant &p_initial_val, const Variant &
ERR_FAIL_INDEX_V(p_trans, TransitionType::TRANS_MAX, Variant());
ERR_FAIL_INDEX_V(p_ease, EaseType::EASE_MAX, Variant());
- // Special case for bool.
- if (p_initial_val.get_type() == Variant::BOOL) {
- return run_equation(p_trans, p_ease, p_time, p_initial_val, p_delta_val, p_duration) >= 0.5;
- }
-
Variant ret = Animation::add_variant(p_initial_val, p_delta_val);
- ret = Animation::interpolate_variant(p_initial_val, ret, run_equation(p_trans, p_ease, p_time, 0.0, 1.0, p_duration));
+ ret = Animation::interpolate_variant(p_initial_val, ret, run_equation(p_trans, p_ease, p_time, 0.0, 1.0, p_duration), p_initial_val.is_string());
return ret;
}
@@ -554,9 +549,12 @@ void PropertyTweener::start() {
return;
}
- if (do_continue && Math::is_zero_approx(delay)) {
- initial_val = target_instance->get_indexed(property);
- do_continue = false;
+ if (do_continue) {
+ if (Math::is_zero_approx(delay)) {
+ initial_val = target_instance->get_indexed(property);
+ } else {
+ do_continue_delayed = true;
+ }
}
if (relative) {
@@ -581,10 +579,10 @@ bool PropertyTweener::step(double &r_delta) {
if (elapsed_time < delay) {
r_delta = 0;
return true;
- } else if (do_continue && !Math::is_zero_approx(delay)) {
+ } else if (do_continue_delayed && !Math::is_zero_approx(delay)) {
initial_val = target_instance->get_indexed(property);
delta_val = Animation::subtract_variant(final_val, initial_val);
- do_continue = false;
+ do_continue_delayed = false;
}
double time = MIN(elapsed_time - delay, duration);
diff --git a/scene/animation/tween.h b/scene/animation/tween.h
index 10c7a272ef..053b4fac46 100644
--- a/scene/animation/tween.h
+++ b/scene/animation/tween.h
@@ -225,6 +225,7 @@ private:
double delay = 0;
bool do_continue = true;
+ bool do_continue_delayed = false;
bool relative = false;
};
diff --git a/scene/debugger/scene_debugger.cpp b/scene/debugger/scene_debugger.cpp
index 79cd1056dd..5603b2dbe4 100644
--- a/scene/debugger/scene_debugger.cpp
+++ b/scene/debugger/scene_debugger.cpp
@@ -72,6 +72,11 @@ void SceneDebugger::deinitialize() {
}
}
+#ifdef MINGW_ENABLED
+#undef near
+#undef far
+#endif
+
#ifdef DEBUG_ENABLED
Error SceneDebugger::parse_message(void *p_user, const String &p_msg, const Array &p_args, bool &r_captured) {
SceneTree *scene_tree = SceneTree::get_singleton();
diff --git a/scene/gui/color_picker.cpp b/scene/gui/color_picker.cpp
index f250662be0..1d5c1c0ade 100644
--- a/scene/gui/color_picker.cpp
+++ b/scene/gui/color_picker.cpp
@@ -74,7 +74,7 @@ void ColorPicker::_notification(int p_what) {
sliders[i]->add_theme_constant_override(SNAME("center_grabber"), theme_cache.center_slider_grabbers);
}
alpha_label->set_custom_minimum_size(Size2(theme_cache.label_width, 0));
- alpha_label->add_theme_constant_override(SNAME("center_grabber"), theme_cache.center_slider_grabbers);
+ alpha_slider->add_theme_constant_override(SNAME("center_grabber"), theme_cache.center_slider_grabbers);
for (int i = 0; i < MODE_BUTTON_COUNT; i++) {
mode_btns[i]->begin_bulk_theme_override();
@@ -556,16 +556,17 @@ void ColorPicker::_html_submitted(const String &p_html) {
return;
}
- const Color previous_color = color;
- color = Color::from_string(p_html.strip_edges(), previous_color);
+ Color new_color = Color::from_string(p_html.strip_edges(), color);
if (!is_editing_alpha()) {
- color.a = previous_color.a;
+ new_color.a = color.a;
}
- if (color == previous_color) {
+ if (new_color.to_argb32() == color.to_argb32()) {
return;
}
+ color = new_color;
+
if (!is_inside_tree()) {
return;
}
@@ -693,6 +694,12 @@ void ColorPicker::set_picker_shape(PickerShapeType p_shape) {
current_shape = p_shape;
+#ifdef TOOLS_ENABLED
+ if (editor_settings) {
+ editor_settings->call(SNAME("set_project_metadata"), "color_picker", "picker_shape", current_shape);
+ }
+#endif
+
_copy_color_to_hsv();
_update_controls();
@@ -927,6 +934,12 @@ void ColorPicker::set_color_mode(ColorModeType p_mode) {
current_mode = p_mode;
+#ifdef TOOLS_ENABLED
+ if (editor_settings) {
+ editor_settings->call(SNAME("set_project_metadata"), "color_picker", "color_mode", current_mode);
+ }
+#endif
+
if (!is_inside_tree()) {
return;
}
diff --git a/scene/gui/graph_edit.cpp b/scene/gui/graph_edit.cpp
index 8dddbf78cf..69023d2056 100644
--- a/scene/gui/graph_edit.cpp
+++ b/scene/gui/graph_edit.cpp
@@ -2015,7 +2015,7 @@ GraphEdit::GraphEdit() {
top_layer->connect("focus_exited", callable_mp(panner.ptr(), &ViewPanner::release_pan_key));
connections_layer = memnew(Control);
- add_child(connections_layer, false);
+ add_child(connections_layer, false, INTERNAL_MODE_FRONT);
connections_layer->connect("draw", callable_mp(this, &GraphEdit::_connections_layer_draw));
connections_layer->set_name("_connection_layer");
connections_layer->set_disable_visibility_clip(true); // Necessary, so it can draw freely and be offset.
diff --git a/scene/gui/label.cpp b/scene/gui/label.cpp
index 2fbd29b048..3df0d97160 100644
--- a/scene/gui/label.cpp
+++ b/scene/gui/label.cpp
@@ -240,10 +240,12 @@ void Label::_shape() {
if (i < jst_to_line) {
TS->shaped_text_fit_to_width(lines_rid[i], width, line_jst_flags);
} else if (i == (visible_lines - 1)) {
+ TS->shaped_text_set_custom_ellipsis(lines_rid[i], (el_char.length() > 0) ? el_char[0] : 0x2026);
TS->shaped_text_overrun_trim_to_width(lines_rid[i], width, overrun_flags);
}
}
} else if (lines_hidden) {
+ TS->shaped_text_set_custom_ellipsis(lines_rid[visible_lines - 1], (el_char.length() > 0) ? el_char[0] : 0x2026);
TS->shaped_text_overrun_trim_to_width(lines_rid[visible_lines - 1], width, overrun_flags);
}
} else {
@@ -268,9 +270,11 @@ void Label::_shape() {
if (i < jst_to_line && horizontal_alignment == HORIZONTAL_ALIGNMENT_FILL) {
TS->shaped_text_fit_to_width(lines_rid[i], width, line_jst_flags);
overrun_flags.set_flag(TextServer::OVERRUN_JUSTIFICATION_AWARE);
+ TS->shaped_text_set_custom_ellipsis(lines_rid[i], (el_char.length() > 0) ? el_char[0] : 0x2026);
TS->shaped_text_overrun_trim_to_width(lines_rid[i], width, overrun_flags);
TS->shaped_text_fit_to_width(lines_rid[i], width, line_jst_flags | TextServer::JUSTIFICATION_CONSTRAIN_ELLIPSIS);
} else {
+ TS->shaped_text_set_custom_ellipsis(lines_rid[i], (el_char.length() > 0) ? el_char[0] : 0x2026);
TS->shaped_text_overrun_trim_to_width(lines_rid[i], width, overrun_flags);
}
}
@@ -887,6 +891,27 @@ TextServer::OverrunBehavior Label::get_text_overrun_behavior() const {
return overrun_behavior;
}
+void Label::set_ellipsis_char(const String &p_char) {
+ String c = p_char;
+ if (c.length() > 1) {
+ WARN_PRINT("Ellipsis must be exactly one character long (" + itos(c.length()) + " characters given).");
+ c = c.left(1);
+ }
+ if (el_char == c) {
+ return;
+ }
+ el_char = c;
+ lines_dirty = true;
+ queue_redraw();
+ if (clip || overrun_behavior != TextServer::OVERRUN_NO_TRIMMING) {
+ update_minimum_size();
+ }
+}
+
+String Label::get_ellipsis_char() const {
+ return el_char;
+}
+
String Label::get_text() const {
return text;
}
@@ -1007,6 +1032,8 @@ void Label::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_tab_stops"), &Label::get_tab_stops);
ClassDB::bind_method(D_METHOD("set_text_overrun_behavior", "overrun_behavior"), &Label::set_text_overrun_behavior);
ClassDB::bind_method(D_METHOD("get_text_overrun_behavior"), &Label::get_text_overrun_behavior);
+ ClassDB::bind_method(D_METHOD("set_ellipsis_char", "char"), &Label::set_ellipsis_char);
+ ClassDB::bind_method(D_METHOD("get_ellipsis_char"), &Label::get_ellipsis_char);
ClassDB::bind_method(D_METHOD("set_uppercase", "enable"), &Label::set_uppercase);
ClassDB::bind_method(D_METHOD("is_uppercase"), &Label::is_uppercase);
ClassDB::bind_method(D_METHOD("get_line_height", "line"), &Label::get_line_height, DEFVAL(-1));
@@ -1037,6 +1064,7 @@ void Label::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "clip_text"), "set_clip_text", "is_clipping_text");
ADD_PROPERTY(PropertyInfo(Variant::INT, "text_overrun_behavior", PROPERTY_HINT_ENUM, "Trim Nothing,Trim Characters,Trim Words,Ellipsis,Word Ellipsis"), "set_text_overrun_behavior", "get_text_overrun_behavior");
+ ADD_PROPERTY(PropertyInfo(Variant::STRING, "ellipsis_char"), "set_ellipsis_char", "get_ellipsis_char");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "uppercase"), "set_uppercase", "is_uppercase");
ADD_PROPERTY(PropertyInfo(Variant::PACKED_FLOAT32_ARRAY, "tab_stops"), "set_tab_stops", "get_tab_stops");
diff --git a/scene/gui/label.h b/scene/gui/label.h
index a5126c6b91..44443e3eb4 100644
--- a/scene/gui/label.h
+++ b/scene/gui/label.h
@@ -45,6 +45,7 @@ private:
TextServer::AutowrapMode autowrap_mode = TextServer::AUTOWRAP_OFF;
BitField<TextServer::JustificationFlag> jst_flags = TextServer::JUSTIFICATION_WORD_BOUND | TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_SKIP_LAST_LINE | TextServer::JUSTIFICATION_DO_NOT_SKIP_SINGLE_LINE;
bool clip = false;
+ String el_char = U"…";
TextServer::OverrunBehavior overrun_behavior = TextServer::OVERRUN_NO_TRIMMING;
Size2 minsize;
bool uppercase = false;
@@ -147,6 +148,9 @@ public:
void set_text_overrun_behavior(TextServer::OverrunBehavior p_behavior);
TextServer::OverrunBehavior get_text_overrun_behavior() const;
+ void set_ellipsis_char(const String &p_char);
+ String get_ellipsis_char() const;
+
void set_lines_skipped(int p_lines);
int get_lines_skipped() const;
diff --git a/scene/gui/line_edit.cpp b/scene/gui/line_edit.cpp
index 5ed1a9d5e3..5cca09bcf6 100644
--- a/scene/gui/line_edit.cpp
+++ b/scene/gui/line_edit.cpp
@@ -1914,12 +1914,15 @@ bool LineEdit::is_secret() const {
}
void LineEdit::set_secret_character(const String &p_string) {
- if (secret_character == p_string) {
+ String c = p_string;
+ if (c.length() > 1) {
+ WARN_PRINT("Secret character must be exactly one character long (" + itos(c.length()) + " characters given).");
+ c = c.left(1);
+ }
+ if (secret_character == c) {
return;
}
-
- secret_character = p_string;
- update_configuration_warnings();
+ secret_character = c;
_shape();
queue_redraw();
}
@@ -2285,14 +2288,8 @@ void LineEdit::_shape() {
if (text.length() == 0 && ime_text.length() == 0) {
t = placeholder_translated;
} else if (pass) {
- // TODO: Integrate with text server to add support for non-latin scripts.
- // Allow secret_character as empty strings, act like if a space was used as a secret character.
- String secret = " ";
- // Allow values longer than 1 character in the property, but trim characters after the first one.
- if (!secret_character.is_empty()) {
- secret = secret_character.left(1);
- }
- t = secret.repeat(text.length() + ime_text.length());
+ String s = (secret_character.length() > 0) ? secret_character.left(1) : U"•";
+ t = s.repeat(text.length() + ime_text.length());
} else {
if (ime_text.length() > 0) {
t = text.substr(0, caret_column) + ime_text + text.substr(caret_column, text.length());
diff --git a/scene/gui/menu_bar.cpp b/scene/gui/menu_bar.cpp
index 7418ba7333..7fa2653ed9 100644
--- a/scene/gui/menu_bar.cpp
+++ b/scene/gui/menu_bar.cpp
@@ -249,11 +249,13 @@ String MenuBar::bind_global_menu() {
Vector<PopupMenu *> popups = _get_popups();
for (int i = 0; i < menu_cache.size(); i++) {
String submenu_name = popups[i]->bind_global_menu();
- int index = ds->global_menu_add_submenu_item("_main", menu_cache[i].name, submenu_name, global_start_idx + i);
- ds->global_menu_set_item_tag("_main", index, global_menu_name + "#" + itos(i));
- ds->global_menu_set_item_hidden("_main", index, menu_cache[i].hidden);
- ds->global_menu_set_item_disabled("_main", index, menu_cache[i].disabled);
- ds->global_menu_set_item_tooltip("_main", index, menu_cache[i].tooltip);
+ if (!popups[i]->is_system_menu()) {
+ int index = ds->global_menu_add_submenu_item("_main", menu_cache[i].name, submenu_name, global_start_idx + i);
+ ds->global_menu_set_item_tag("_main", index, global_menu_name + "#" + itos(i));
+ ds->global_menu_set_item_hidden("_main", index, menu_cache[i].hidden);
+ ds->global_menu_set_item_disabled("_main", index, menu_cache[i].disabled);
+ ds->global_menu_set_item_tooltip("_main", index, menu_cache[i].tooltip);
+ }
}
return global_menu_name;
@@ -268,8 +270,10 @@ void MenuBar::unbind_global_menu() {
int global_start = _find_global_start_index();
Vector<PopupMenu *> popups = _get_popups();
for (int i = menu_cache.size() - 1; i >= 0; i--) {
- popups[i]->unbind_global_menu();
- ds->global_menu_remove_item("_main", global_start + i);
+ if (!popups[i]->is_system_menu()) {
+ popups[i]->unbind_global_menu();
+ ds->global_menu_remove_item("_main", global_start + i);
+ }
}
global_menu_name = String();
@@ -558,8 +562,10 @@ void MenuBar::add_child_notify(Node *p_child) {
if (!global_menu_name.is_empty()) {
String submenu_name = pm->bind_global_menu();
- int index = DisplayServer::get_singleton()->global_menu_add_submenu_item("_main", atr(menu.name), submenu_name, _find_global_start_index() + menu_cache.size() - 1);
- DisplayServer::get_singleton()->global_menu_set_item_tag("_main", index, global_menu_name + "#" + itos(menu_cache.size() - 1));
+ if (!pm->is_system_menu()) {
+ int index = DisplayServer::get_singleton()->global_menu_add_submenu_item("_main", atr(menu.name), submenu_name, _find_global_start_index() + menu_cache.size() - 1);
+ DisplayServer::get_singleton()->global_menu_set_item_tag("_main", index, global_menu_name + "#" + itos(menu_cache.size() - 1));
+ }
}
update_minimum_size();
}
@@ -587,14 +593,16 @@ void MenuBar::move_child_notify(Node *p_child) {
menu_cache.insert(new_idx, menu);
if (!global_menu_name.is_empty()) {
- int global_start = _find_global_start_index();
- if (old_idx != -1) {
- DisplayServer::get_singleton()->global_menu_remove_item("_main", global_start + old_idx);
- }
- if (new_idx != -1) {
- String submenu_name = pm->bind_global_menu();
- int index = DisplayServer::get_singleton()->global_menu_add_submenu_item("_main", atr(menu.name), submenu_name, global_start + new_idx);
- DisplayServer::get_singleton()->global_menu_set_item_tag("_main", index, global_menu_name + "#" + itos(new_idx));
+ if (!pm->is_system_menu()) {
+ int global_start = _find_global_start_index();
+ if (old_idx != -1) {
+ DisplayServer::get_singleton()->global_menu_remove_item("_main", global_start + old_idx);
+ }
+ if (new_idx != -1) {
+ String submenu_name = pm->bind_global_menu();
+ int index = DisplayServer::get_singleton()->global_menu_add_submenu_item("_main", atr(menu.name), submenu_name, global_start + new_idx);
+ DisplayServer::get_singleton()->global_menu_set_item_tag("_main", index, global_menu_name + "#" + itos(new_idx));
+ }
}
}
}
@@ -612,8 +620,10 @@ void MenuBar::remove_child_notify(Node *p_child) {
menu_cache.remove_at(idx);
if (!global_menu_name.is_empty()) {
- pm->unbind_global_menu();
- DisplayServer::get_singleton()->global_menu_remove_item("_main", _find_global_start_index() + idx);
+ if (!pm->is_system_menu()) {
+ pm->unbind_global_menu();
+ DisplayServer::get_singleton()->global_menu_remove_item("_main", _find_global_start_index() + idx);
+ }
}
p_child->remove_meta("_menu_name");
diff --git a/scene/gui/popup_menu.cpp b/scene/gui/popup_menu.cpp
index cf8082d137..d9c633b238 100644
--- a/scene/gui/popup_menu.cpp
+++ b/scene/gui/popup_menu.cpp
@@ -40,6 +40,8 @@
#include "scene/gui/menu_bar.h"
#include "scene/theme/theme_db.h"
+HashMap<String, PopupMenu *> PopupMenu::system_menus;
+
String PopupMenu::bind_global_menu() {
#ifdef TOOLS_ENABLED
if (is_part_of_edited_scene()) {
@@ -54,8 +56,20 @@ String PopupMenu::bind_global_menu() {
return global_menu_name; // Already bound;
}
- DisplayServer *ds = DisplayServer::get_singleton();
global_menu_name = "__PopupMenu#" + itos(get_instance_id());
+ if (system_menu_name.length() > 0) {
+ if (system_menus.has(system_menu_name)) {
+ WARN_PRINT(vformat("Attempting to bind PopupMenu to the special menu %s, but another menu is already bound to it. This menu: %s, current menu: %s", system_menu_name, this->get_description(), system_menus[system_menu_name]->get_description()));
+ } else {
+ const Dictionary &supported_special_names = DisplayServer::get_singleton()->global_menu_get_system_menu_roots();
+ if (supported_special_names.has(system_menu_name)) {
+ system_menus[system_menu_name] = this;
+ global_menu_name = system_menu_name;
+ }
+ }
+ }
+
+ DisplayServer *ds = DisplayServer::get_singleton();
ds->global_menu_set_popup_callbacks(global_menu_name, callable_mp(this, &PopupMenu::_about_to_popup), callable_mp(this, &PopupMenu::_about_to_close));
for (int i = 0; i < items.size(); i++) {
Item &item = items.write[i];
@@ -105,6 +119,10 @@ void PopupMenu::unbind_global_menu() {
return;
}
+ if (global_menu_name == system_menu_name && system_menus[system_menu_name] == this) {
+ system_menus.erase(system_menu_name);
+ }
+
for (int i = 0; i < items.size(); i++) {
Item &item = items.write[i];
if (!item.submenu.is_empty()) {
@@ -120,6 +138,24 @@ void PopupMenu::unbind_global_menu() {
global_menu_name = String();
}
+bool PopupMenu::is_system_menu() const {
+ return (global_menu_name == system_menu_name) && (system_menu_name.length() > 0);
+}
+
+void PopupMenu::set_system_menu_root(const String &p_special) {
+ if (is_inside_tree() && system_menu_name.length() > 0) {
+ unbind_global_menu();
+ }
+ system_menu_name = p_special;
+ if (is_inside_tree() && system_menu_name.length() > 0) {
+ bind_global_menu();
+ }
+}
+
+String PopupMenu::get_system_menu_root() const {
+ return system_menu_name;
+}
+
String PopupMenu::_get_accel_text(const Item &p_item) const {
if (p_item.shortcut.is_valid()) {
return p_item.shortcut->get_as_text();
@@ -954,6 +990,15 @@ void PopupMenu::_notification(int p_what) {
if (!is_embedded()) {
set_flag(FLAG_NO_FOCUS, true);
}
+ if (system_menu_name.length() > 0) {
+ bind_global_menu();
+ }
+ } break;
+
+ case NOTIFICATION_EXIT_TREE: {
+ if (system_menu_name.length() > 0) {
+ unbind_global_menu();
+ }
} break;
case NOTIFICATION_THEME_CHANGED:
@@ -2723,11 +2768,16 @@ void PopupMenu::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_allow_search", "allow"), &PopupMenu::set_allow_search);
ClassDB::bind_method(D_METHOD("get_allow_search"), &PopupMenu::get_allow_search);
+ ClassDB::bind_method(D_METHOD("is_system_menu"), &PopupMenu::is_system_menu);
+ ClassDB::bind_method(D_METHOD("set_system_menu_root", "special"), &PopupMenu::set_system_menu_root);
+ ClassDB::bind_method(D_METHOD("get_system_menu_root"), &PopupMenu::get_system_menu_root);
+
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "hide_on_item_selection"), "set_hide_on_item_selection", "is_hide_on_item_selection");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "hide_on_checkable_item_selection"), "set_hide_on_checkable_item_selection", "is_hide_on_checkable_item_selection");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "hide_on_state_item_selection"), "set_hide_on_state_item_selection", "is_hide_on_state_item_selection");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "submenu_popup_delay", PROPERTY_HINT_NONE, "suffix:s"), "set_submenu_popup_delay", "get_submenu_popup_delay");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "allow_search"), "set_allow_search", "get_allow_search");
+ ADD_PROPERTY(PropertyInfo(Variant::STRING, "system_menu_root", PROPERTY_HINT_ENUM, "Dock (macOS):_dock,Apple Menu(macOS):_apple,Window Menu(macOS):_window,Help Menu(macOS):_help"), "set_system_menu_root", "get_system_menu_root");
ADD_ARRAY_COUNT("Items", "item_count", "set_item_count", "get_item_count", "item_");
diff --git a/scene/gui/popup_menu.h b/scene/gui/popup_menu.h
index 3e542b5d8b..c1ab9544ea 100644
--- a/scene/gui/popup_menu.h
+++ b/scene/gui/popup_menu.h
@@ -40,6 +40,8 @@
class PopupMenu : public Popup {
GDCLASS(PopupMenu, Popup);
+ static HashMap<String, PopupMenu *> system_menus;
+
struct Item {
Ref<Texture2D> icon;
int icon_max_width = 0;
@@ -90,6 +92,7 @@ class PopupMenu : public Popup {
};
String global_menu_name;
+ String system_menu_name;
bool close_allowed = false;
bool activated_by_keyboard = false;
@@ -219,6 +222,9 @@ public:
String bind_global_menu();
void unbind_global_menu();
+ bool is_system_menu() const;
+ void set_system_menu_root(const String &p_special);
+ String get_system_menu_root() const;
void add_item(const String &p_label, int p_id = -1, Key p_accel = Key::NONE);
void add_icon_item(const Ref<Texture2D> &p_icon, const String &p_label, int p_id = -1, Key p_accel = Key::NONE);
diff --git a/scene/gui/rich_text_label.cpp b/scene/gui/rich_text_label.cpp
index dc96f8a594..fbc374e89e 100644
--- a/scene/gui/rich_text_label.cpp
+++ b/scene/gui/rich_text_label.cpp
@@ -1771,7 +1771,7 @@ void RichTextLabel::_scroll_changed(double) {
return;
}
- if (scroll_follow && vscroll->get_value() >= (vscroll->get_max() - vscroll->get_page())) {
+ if (scroll_follow && vscroll->get_value() >= (vscroll->get_max() - Math::round(vscroll->get_page()))) {
scroll_following = true;
} else {
scroll_following = false;
@@ -3121,6 +3121,8 @@ void RichTextLabel::_add_item(Item *p_item, bool p_enter, bool p_ensure_newline)
current_char_ofs += t->text.length();
} else if (p_item->type == ITEM_IMAGE) {
current_char_ofs++;
+ } else if (p_item->type == ITEM_NEWLINE) {
+ current_char_ofs++;
}
if (p_enter) {
@@ -6329,11 +6331,9 @@ Ref<RichTextEffect> RichTextLabel::_get_custom_effect_by_code(String p_bbcode_id
Dictionary RichTextLabel::parse_expressions_for_values(Vector<String> p_expressions) {
Dictionary d;
for (int i = 0; i < p_expressions.size(); i++) {
- String expression = p_expressions[i];
-
Array a;
- Vector<String> parts = expression.split("=", true);
- String key = parts[0];
+ Vector<String> parts = p_expressions[i].split("=", true);
+ const String &key = parts[0];
if (parts.size() != 2) {
return d;
}
diff --git a/scene/gui/tab_bar.cpp b/scene/gui/tab_bar.cpp
index 777ca96cc4..e9fbdbb312 100644
--- a/scene/gui/tab_bar.cpp
+++ b/scene/gui/tab_bar.cpp
@@ -1664,7 +1664,7 @@ bool TabBar::_set(const StringName &p_name, const Variant &p_value) {
} else if (property == "icon") {
set_tab_icon(tab_index, p_value);
return true;
- } else if (components[1] == "disabled") {
+ } else if (property == "disabled") {
set_tab_disabled(tab_index, p_value);
return true;
}
@@ -1683,7 +1683,7 @@ bool TabBar::_get(const StringName &p_name, Variant &r_ret) const {
} else if (property == "icon") {
r_ret = get_tab_icon(tab_index);
return true;
- } else if (components[1] == "disabled") {
+ } else if (property == "disabled") {
r_ret = is_tab_disabled(tab_index);
return true;
}
diff --git a/scene/gui/tab_container.cpp b/scene/gui/tab_container.cpp
index aa9400847f..0f461f4865 100644
--- a/scene/gui/tab_container.cpp
+++ b/scene/gui/tab_container.cpp
@@ -381,22 +381,29 @@ void TabContainer::move_tab_from_tab_container(TabContainer *p_from, int p_from_
// Get the tab properties before they get erased by the child removal.
String tab_title = p_from->get_tab_title(p_from_index);
Ref<Texture2D> tab_icon = p_from->get_tab_icon(p_from_index);
+ Ref<Texture2D> tab_button_icon = p_from->get_tab_button_icon(p_from_index);
bool tab_disabled = p_from->is_tab_disabled(p_from_index);
+ bool tab_hidden = p_from->is_tab_hidden(p_from_index);
Variant tab_metadata = p_from->get_tab_metadata(p_from_index);
+ int tab_icon_max_width = p_from->get_tab_bar()->get_tab_icon_max_width(p_from_index);
Control *moving_tabc = p_from->get_tab_control(p_from_index);
p_from->remove_child(moving_tabc);
add_child(moving_tabc, true);
- set_tab_title(get_tab_count() - 1, tab_title);
- set_tab_icon(get_tab_count() - 1, tab_icon);
- set_tab_disabled(get_tab_count() - 1, tab_disabled);
- set_tab_metadata(get_tab_count() - 1, tab_metadata);
-
if (p_to_index < 0 || p_to_index > get_tab_count() - 1) {
p_to_index = get_tab_count() - 1;
}
move_child(moving_tabc, get_tab_control(p_to_index)->get_index(false));
+
+ set_tab_title(p_to_index, tab_title);
+ set_tab_icon(p_to_index, tab_icon);
+ set_tab_button_icon(p_to_index, tab_button_icon);
+ set_tab_disabled(p_to_index, tab_disabled);
+ set_tab_hidden(p_to_index, tab_hidden);
+ set_tab_metadata(p_to_index, tab_metadata);
+ get_tab_bar()->set_tab_icon_max_width(p_to_index, tab_icon_max_width);
+
if (!is_tab_disabled(p_to_index)) {
set_current_tab(p_to_index);
}
diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp
index 86e726d9da..308250c592 100644
--- a/scene/gui/text_edit.cpp
+++ b/scene/gui/text_edit.cpp
@@ -770,7 +770,14 @@ void TextEdit::_notification(int p_what) {
Dictionary color_map = _get_line_syntax_highlighting(minimap_line);
Color line_background_color = text.get_line_background_color(minimap_line);
- line_background_color.a *= 0.6;
+
+ if (line_background_color != theme_cache.background_color) {
+ // Make non-default background colors more visible, such as error markers.
+ line_background_color.a = 1.0;
+ } else {
+ line_background_color.a *= 0.6;
+ }
+
Color current_color = theme_cache.font_color;
if (!editable) {
current_color = theme_cache.font_readonly_color;
@@ -5307,8 +5314,7 @@ int TextEdit::get_line_wrap_index_at_column(int p_line, int p_column) const {
Vector<String> lines = get_line_wrapped_text(p_line);
for (int i = 0; i < lines.size(); i++) {
wrap_index = i;
- String s = lines[wrap_index];
- col += s.length();
+ col += lines[wrap_index].length();
if (col > p_column) {
break;
}
diff --git a/scene/gui/tree.cpp b/scene/gui/tree.cpp
index 1d06ce05ae..8a243fd68f 100644
--- a/scene/gui/tree.cpp
+++ b/scene/gui/tree.cpp
@@ -70,19 +70,27 @@ void TreeItem::Cell::draw_icon(const RID &p_where, const Point2 &p_pos, const Si
}
void TreeItem::_changed_notify(int p_cell) {
- tree->item_changed(p_cell, this);
+ if (tree) {
+ tree->item_changed(p_cell, this);
+ }
}
void TreeItem::_changed_notify() {
- tree->item_changed(-1, this);
+ if (tree) {
+ tree->item_changed(-1, this);
+ }
}
void TreeItem::_cell_selected(int p_cell) {
- tree->item_selected(p_cell, this);
+ if (tree) {
+ tree->item_selected(p_cell, this);
+ }
}
void TreeItem::_cell_deselected(int p_cell) {
- tree->item_deselected(p_cell, this);
+ if (tree) {
+ tree->item_deselected(p_cell, this);
+ }
}
void TreeItem::_change_tree(Tree *p_tree) {
diff --git a/scene/gui/video_stream_player.cpp b/scene/gui/video_stream_player.cpp
index ac09844128..41a210e180 100644
--- a/scene/gui/video_stream_player.cpp
+++ b/scene/gui/video_stream_player.cpp
@@ -237,6 +237,12 @@ bool VideoStreamPlayer::has_loop() const {
void VideoStreamPlayer::set_stream(const Ref<VideoStream> &p_stream) {
stop();
+ // Make sure to handle stream changes seamlessly, e.g. when done via
+ // translation remapping.
+ if (stream.is_valid()) {
+ stream->disconnect_changed(callable_mp(this, &VideoStreamPlayer::set_stream));
+ }
+
AudioServer::get_singleton()->lock();
mix_buffer.resize(AudioServer::get_singleton()->thread_get_mix_buffer_size());
stream = p_stream;
@@ -248,6 +254,10 @@ void VideoStreamPlayer::set_stream(const Ref<VideoStream> &p_stream) {
}
AudioServer::get_singleton()->unlock();
+ if (stream.is_valid()) {
+ stream->connect_changed(callable_mp(this, &VideoStreamPlayer::set_stream).bind(stream));
+ }
+
if (!playback.is_null()) {
playback->set_paused(paused);
texture = playback->get_texture();
diff --git a/scene/gui/view_panner.cpp b/scene/gui/view_panner.cpp
index fc03f2d887..c61fa1d9b8 100644
--- a/scene/gui/view_panner.cpp
+++ b/scene/gui/view_panner.cpp
@@ -125,6 +125,17 @@ bool ViewPanner::gui_input(const Ref<InputEvent> &p_event, Rect2 p_canvas_rect)
Ref<InputEventPanGesture> pan_gesture = p_event;
if (pan_gesture.is_valid()) {
+ if (pan_gesture->is_ctrl_pressed()) {
+ // Zoom gesture.
+ float pan_zoom_factor = 1.02f;
+ float zoom_direction = pan_gesture->get_delta().x - pan_gesture->get_delta().y;
+ if (zoom_direction == 0.f) {
+ return true;
+ }
+ float zoom = zoom_direction < 0 ? 1.0 / pan_zoom_factor : pan_zoom_factor;
+ zoom_callback.call(zoom, pan_gesture->get_position(), p_event);
+ return true;
+ }
pan_callback.call(-pan_gesture->get_delta() * scroll_speed, p_event);
}
diff --git a/scene/main/canvas_item.cpp b/scene/main/canvas_item.cpp
index d2735b968b..4ee81e5cb0 100644
--- a/scene/main/canvas_item.cpp
+++ b/scene/main/canvas_item.cpp
@@ -891,18 +891,22 @@ void CanvasItem::_notify_transform(CanvasItem *p_node) {
* notification anyway).
*/
- if (p_node->block_transform_notify || p_node->_is_global_invalid()) {
+ if (/*p_node->xform_change.in_list() &&*/ p_node->_is_global_invalid()) {
return; //nothing to do
}
p_node->_set_global_invalid(true);
- if (p_node->notify_transform && !p_node->xform_change.in_list() && p_node->is_inside_tree()) {
- if (is_accessible_from_caller_thread()) {
- get_tree()->xform_change_list.add(&p_node->xform_change);
- } else {
- // Should be rare, but still needs to be handled.
- MessageQueue::get_singleton()->push_callable(callable_mp(p_node, &CanvasItem::_notify_transform_deferred));
+ if (p_node->notify_transform && !p_node->xform_change.in_list()) {
+ if (!p_node->block_transform_notify) {
+ if (p_node->is_inside_tree()) {
+ if (is_accessible_from_caller_thread()) {
+ get_tree()->xform_change_list.add(&p_node->xform_change);
+ } else {
+ // Should be rare, but still needs to be handled.
+ MessageQueue::get_singleton()->push_callable(callable_mp(p_node, &CanvasItem::_notify_transform_deferred));
+ }
+ }
}
}
diff --git a/scene/main/node.cpp b/scene/main/node.cpp
index e730f47607..820cb7571f 100644
--- a/scene/main/node.cpp
+++ b/scene/main/node.cpp
@@ -202,6 +202,11 @@ void Node::_notification(int p_notification) {
_clean_up_owner();
}
+ while (!data.owned.is_empty()) {
+ Node *n = data.owned.back()->get();
+ n->_clean_up_owner(); // This will change data.owned. So it's impossible to loop over the list in the usual manner.
+ }
+
if (data.parent) {
data.parent->remove_child(this);
}
@@ -1415,6 +1420,14 @@ void Node::add_child(Node *p_child, bool p_force_readable_name, InternalMode p_i
ERR_FAIL_COND_MSG(data.blocked > 0, "Parent node is busy setting up children, `add_child()` failed. Consider using `add_child.call_deferred(child)` instead.");
_validate_child_name(p_child, p_force_readable_name);
+
+#ifdef DEBUG_ENABLED
+ if (p_child->data.owner && !p_child->data.owner->is_ancestor_of(p_child)) {
+ // Owner of p_child should be ancestor of p_child.
+ WARN_PRINT(vformat("Adding '%s' as child to '%s' will make owner '%s' inconsistent. Consider unsetting the owner beforehand.", p_child->get_name(), get_name(), p_child->data.owner->get_name()));
+ }
+#endif // DEBUG_ENABLED
+
_add_child_nocheck(p_child, p_child->data.name, p_internal);
}
@@ -2515,44 +2528,6 @@ Node *Node::_duplicate(int p_flags, HashMap<const Node *, Node *> *r_duplimap) c
}
}
- for (List<const Node *>::Element *N = node_tree.front(); N; N = N->next()) {
- Node *current_node = node->get_node(get_path_to(N->get()));
- ERR_CONTINUE(!current_node);
-
- if (p_flags & DUPLICATE_SCRIPTS) {
- bool is_valid = false;
- Variant scr = N->get()->get(script_property_name, &is_valid);
- if (is_valid) {
- current_node->set(script_property_name, scr);
- }
- }
-
- List<PropertyInfo> plist;
- N->get()->get_property_list(&plist);
-
- for (const PropertyInfo &E : plist) {
- if (!(E.usage & PROPERTY_USAGE_STORAGE)) {
- continue;
- }
- String name = E.name;
- if (name == script_property_name) {
- continue;
- }
-
- Variant value = N->get()->get(name).duplicate(true);
-
- if (E.usage & PROPERTY_USAGE_ALWAYS_DUPLICATE) {
- Resource *res = Object::cast_to<Resource>(value);
- if (res) { // Duplicate only if it's a resource
- current_node->set(name, res->duplicate());
- }
-
- } else {
- current_node->set(name, value);
- }
- }
- }
-
if (get_name() != String()) {
node->set_name(get_name());
}
@@ -2618,6 +2593,62 @@ Node *Node::_duplicate(int p_flags, HashMap<const Node *, Node *> *r_duplimap) c
}
}
+ for (List<const Node *>::Element *N = node_tree.front(); N; N = N->next()) {
+ Node *current_node = node->get_node(get_path_to(N->get()));
+ ERR_CONTINUE(!current_node);
+
+ if (p_flags & DUPLICATE_SCRIPTS) {
+ bool is_valid = false;
+ Variant scr = N->get()->get(script_property_name, &is_valid);
+ if (is_valid) {
+ current_node->set(script_property_name, scr);
+ }
+ }
+
+ List<PropertyInfo> plist;
+ N->get()->get_property_list(&plist);
+
+ for (const PropertyInfo &E : plist) {
+ if (!(E.usage & PROPERTY_USAGE_STORAGE)) {
+ continue;
+ }
+ String name = E.name;
+ if (name == script_property_name) {
+ continue;
+ }
+
+ Variant value = N->get()->get(name).duplicate(true);
+
+ if (E.usage & PROPERTY_USAGE_ALWAYS_DUPLICATE) {
+ Resource *res = Object::cast_to<Resource>(value);
+ if (res) { // Duplicate only if it's a resource
+ current_node->set(name, res->duplicate());
+ }
+
+ } else {
+ // If property points to a node which is owned by a node we are duplicating, update its path.
+ if (value.get_type() == Variant::OBJECT) {
+ Node *property_node = Object::cast_to<Node>(value);
+ if (property_node && is_ancestor_of(property_node)) {
+ value = current_node->get_node_or_null(get_path_to(property_node));
+ }
+ } else if (value.get_type() == Variant::ARRAY) {
+ Array arr = value;
+ if (arr.get_typed_builtin() == Variant::OBJECT) {
+ for (int i = 0; i < arr.size(); i++) {
+ Node *property_node = Object::cast_to<Node>(arr[i]);
+ if (property_node && is_ancestor_of(property_node)) {
+ arr[i] = current_node->get_node_or_null(get_path_to(property_node));
+ }
+ }
+ value = arr;
+ }
+ }
+ current_node->set(name, value);
+ }
+ }
+ }
+
return node;
}
diff --git a/scene/main/resource_preloader.cpp b/scene/main/resource_preloader.cpp
index de42b63548..fe0e82370a 100644
--- a/scene/main/resource_preloader.cpp
+++ b/scene/main/resource_preloader.cpp
@@ -40,12 +40,11 @@ void ResourcePreloader::_set_resources(const Array &p_data) {
ERR_FAIL_COND(names.size() != resdata.size());
for (int i = 0; i < resdata.size(); i++) {
- String name = names[i];
Ref<Resource> resource = resdata[i];
ERR_CONTINUE(!resource.is_valid());
- resources[name] = resource;
+ resources[names[i]] = resource;
- //add_resource(name,resource);
+ //add_resource(names[i],resource);
}
}
diff --git a/scene/main/scene_tree.cpp b/scene/main/scene_tree.cpp
index d3347bc304..4417007b9d 100644
--- a/scene/main/scene_tree.cpp
+++ b/scene/main/scene_tree.cpp
@@ -515,6 +515,10 @@ bool SceneTree::process(double p_time) {
_flush_delete_queue();
+ if (unlikely(pending_new_scene)) {
+ _flush_scene_change();
+ }
+
process_timers(p_time, false); //go through timers
process_tweens(p_time, false);
@@ -550,10 +554,6 @@ bool SceneTree::process(double p_time) {
#endif // _3D_DISABLED
#endif // TOOLS_ENABLED
- if (unlikely(pending_new_scene)) {
- _flush_scene_change();
- }
-
return _quit;
}
@@ -1302,6 +1302,16 @@ bool SceneTree::has_group(const StringName &p_identifier) const {
return group_map.has(p_identifier);
}
+int SceneTree::get_node_count_in_group(const StringName &p_group) const {
+ _THREAD_SAFE_METHOD_
+ HashMap<StringName, Group>::ConstIterator E = group_map.find(p_group);
+ if (!E) {
+ return 0;
+ }
+
+ return E->value.nodes.size();
+}
+
Node *SceneTree::get_first_node_in_group(const StringName &p_group) {
_THREAD_SAFE_METHOD_
HashMap<StringName, Group>::Iterator E = group_map.find(p_group);
@@ -1617,6 +1627,7 @@ void SceneTree::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_nodes_in_group", "group"), &SceneTree::_get_nodes_in_group);
ClassDB::bind_method(D_METHOD("get_first_node_in_group", "group"), &SceneTree::get_first_node_in_group);
+ ClassDB::bind_method(D_METHOD("get_node_count_in_group", "group"), &SceneTree::get_node_count_in_group);
ClassDB::bind_method(D_METHOD("set_current_scene", "child_node"), &SceneTree::set_current_scene);
ClassDB::bind_method(D_METHOD("get_current_scene"), &SceneTree::get_current_scene);
diff --git a/scene/main/scene_tree.h b/scene/main/scene_tree.h
index e1597d3890..618ac275ff 100644
--- a/scene/main/scene_tree.h
+++ b/scene/main/scene_tree.h
@@ -385,6 +385,7 @@ public:
void get_nodes_in_group(const StringName &p_group, List<Node *> *p_list);
Node *get_first_node_in_group(const StringName &p_group);
bool has_group(const StringName &p_identifier) const;
+ int get_node_count_in_group(const StringName &p_group) const;
//void change_scene(const String& p_path);
//Node *get_loaded_scene();
diff --git a/scene/main/viewport.cpp b/scene/main/viewport.cpp
index 43bdb1395b..89ec5636ab 100644
--- a/scene/main/viewport.cpp
+++ b/scene/main/viewport.cpp
@@ -2451,6 +2451,14 @@ void Viewport::_gui_update_mouse_over() {
return;
}
+ if (gui.sending_mouse_enter_exit_notifications) {
+ // If notifications are already being sent, delay call to next frame.
+ if (get_tree() && !get_tree()->is_connected(SNAME("process_frame"), callable_mp(this, &Viewport::_gui_update_mouse_over))) {
+ get_tree()->connect(SNAME("process_frame"), callable_mp(this, &Viewport::_gui_update_mouse_over), CONNECT_ONE_SHOT);
+ }
+ return;
+ }
+
// Rebuild the mouse over hierarchy.
LocalVector<Control *> new_mouse_over_hierarchy;
LocalVector<Control *> needs_enter;
@@ -2507,6 +2515,8 @@ void Viewport::_gui_update_mouse_over() {
return;
}
+ gui.sending_mouse_enter_exit_notifications = true;
+
// Send Mouse Exit Self notification.
if (gui.mouse_over && !needs_exit.is_empty() && needs_exit[0] == (int)gui.mouse_over_hierarchy.size() - 1) {
gui.mouse_over->notification(Control::NOTIFICATION_MOUSE_EXIT_SELF);
@@ -2528,6 +2538,8 @@ void Viewport::_gui_update_mouse_over() {
for (int i = needs_enter.size() - 1; i >= 0; i--) {
needs_enter[i]->notification(Control::NOTIFICATION_MOUSE_ENTER);
}
+
+ gui.sending_mouse_enter_exit_notifications = false;
}
Window *Viewport::get_base_window() const {
@@ -3200,14 +3212,20 @@ void Viewport::_update_mouse_over(Vector2 p_pos) {
gui.mouse_over = over;
gui.mouse_over_hierarchy.reserve(gui.mouse_over_hierarchy.size() + over_ancestors.size());
+ gui.sending_mouse_enter_exit_notifications = true;
+
// Send Mouse Enter notifications to parents first.
for (int i = over_ancestors.size() - 1; i >= 0; i--) {
- over_ancestors[i]->notification(Control::NOTIFICATION_MOUSE_ENTER);
gui.mouse_over_hierarchy.push_back(over_ancestors[i]);
+ over_ancestors[i]->notification(Control::NOTIFICATION_MOUSE_ENTER);
}
// Send Mouse Enter Self notification.
- gui.mouse_over->notification(Control::NOTIFICATION_MOUSE_ENTER_SELF);
+ if (gui.mouse_over) {
+ gui.mouse_over->notification(Control::NOTIFICATION_MOUSE_ENTER_SELF);
+ }
+
+ gui.sending_mouse_enter_exit_notifications = false;
notify_embedded_viewports = true;
}
@@ -3250,6 +3268,12 @@ void Viewport::_mouse_leave_viewport() {
}
void Viewport::_drop_mouse_over(Control *p_until_control) {
+ if (gui.sending_mouse_enter_exit_notifications) {
+ // If notifications are already being sent, defer call.
+ callable_mp(this, &Viewport::_drop_mouse_over).call_deferred(p_until_control);
+ return;
+ }
+
_gui_cancel_tooltip();
SubViewportContainer *c = Object::cast_to<SubViewportContainer>(gui.mouse_over);
if (c) {
@@ -3261,6 +3285,8 @@ void Viewport::_drop_mouse_over(Control *p_until_control) {
v->_mouse_leave_viewport();
}
}
+
+ gui.sending_mouse_enter_exit_notifications = true;
if (gui.mouse_over && gui.mouse_over->is_inside_tree()) {
gui.mouse_over->notification(Control::NOTIFICATION_MOUSE_EXIT_SELF);
}
@@ -3274,6 +3300,7 @@ void Viewport::_drop_mouse_over(Control *p_until_control) {
}
}
gui.mouse_over_hierarchy.resize(notification_until);
+ gui.sending_mouse_enter_exit_notifications = false;
}
void Viewport::push_input(const Ref<InputEvent> &p_event, bool p_local_coords) {
@@ -3491,6 +3518,11 @@ Control *Viewport::gui_get_focus_owner() const {
return gui.key_focus;
}
+Control *Viewport::gui_get_hovered_control() const {
+ ERR_READ_THREAD_GUARD_V(nullptr);
+ return gui.mouse_over;
+}
+
void Viewport::set_msaa_2d(MSAA p_msaa) {
ERR_MAIN_THREAD_GUARD;
ERR_FAIL_INDEX(p_msaa, MSAA_MAX);
@@ -4530,6 +4562,7 @@ void Viewport::_bind_methods() {
ClassDB::bind_method(D_METHOD("gui_release_focus"), &Viewport::gui_release_focus);
ClassDB::bind_method(D_METHOD("gui_get_focus_owner"), &Viewport::gui_get_focus_owner);
+ ClassDB::bind_method(D_METHOD("gui_get_hovered_control"), &Viewport::gui_get_hovered_control);
ClassDB::bind_method(D_METHOD("set_disable_input", "disable"), &Viewport::set_disable_input);
ClassDB::bind_method(D_METHOD("is_input_disabled"), &Viewport::is_input_disabled);
diff --git a/scene/main/viewport.h b/scene/main/viewport.h
index 82a9bfc438..43a89c8a0b 100644
--- a/scene/main/viewport.h
+++ b/scene/main/viewport.h
@@ -362,6 +362,7 @@ private:
Control *key_focus = nullptr;
Control *mouse_over = nullptr;
LocalVector<Control *> mouse_over_hierarchy;
+ bool sending_mouse_enter_exit_notifications = false;
Window *subwindow_over = nullptr; // mouse_over and subwindow_over are mutually exclusive. At all times at least one of them is nullptr.
Window *windowmanager_window_over = nullptr; // Only used in root Viewport.
Control *drag_mouse_over = nullptr;
@@ -609,6 +610,7 @@ public:
void gui_release_focus();
Control *gui_get_focus_owner() const;
+ Control *gui_get_hovered_control() const;
PackedStringArray get_configuration_warnings() const override;
diff --git a/scene/main/window.cpp b/scene/main/window.cpp
index 0d554de6f4..98b207bd3c 100644
--- a/scene/main/window.cpp
+++ b/scene/main/window.cpp
@@ -284,7 +284,13 @@ void Window::set_title(const String &p_title) {
embedder->_sub_window_update(this);
} else if (window_id != DisplayServer::INVALID_WINDOW_ID) {
DisplayServer::get_singleton()->window_set_title(tr_title, window_id);
- _update_window_size();
+ if (keep_title_visible) {
+ Size2i title_size = DisplayServer::get_singleton()->window_get_title_size(tr_title, window_id);
+ Size2i size_limit = get_clamped_minimum_size();
+ if (title_size.x > size_limit.x || title_size.y > size_limit.y) {
+ _update_window_size();
+ }
+ }
}
}
@@ -681,6 +687,9 @@ void Window::_propagate_window_notification(Node *p_node, int p_notification) {
void Window::_event_callback(DisplayServer::WindowEvent p_event) {
switch (p_event) {
case DisplayServer::WINDOW_EVENT_MOUSE_ENTER: {
+ if (!is_inside_tree()) {
+ return;
+ }
Window *root = get_tree()->get_root();
if (root->gui.windowmanager_window_over) {
#ifdef DEV_ENABLED
@@ -696,6 +705,9 @@ void Window::_event_callback(DisplayServer::WindowEvent p_event) {
}
} break;
case DisplayServer::WINDOW_EVENT_MOUSE_EXIT: {
+ if (!is_inside_tree()) {
+ return;
+ }
Window *root = get_tree()->get_root();
if (!root->gui.windowmanager_window_over) {
#ifdef DEV_ENABLED
@@ -955,6 +967,10 @@ Size2i Window::_clamp_window_size(const Size2i &p_size) {
void Window::_update_window_size() {
Size2i size_limit = get_clamped_minimum_size();
+ if (!embedder && window_id != DisplayServer::INVALID_WINDOW_ID && keep_title_visible) {
+ Size2i title_size = DisplayServer::get_singleton()->window_get_title_size(tr_title, window_id);
+ size_limit = size_limit.max(title_size);
+ }
size = size.max(size_limit);
@@ -986,12 +1002,6 @@ void Window::_update_window_size() {
}
DisplayServer::get_singleton()->window_set_max_size(max_size_used, window_id);
-
- if (keep_title_visible) {
- Size2i title_size = DisplayServer::get_singleton()->window_get_title_size(tr_title, window_id);
- size_limit = size_limit.max(title_size);
- }
-
DisplayServer::get_singleton()->window_set_min_size(size_limit, window_id);
DisplayServer::get_singleton()->window_set_size(size, window_id);
}
@@ -1291,7 +1301,13 @@ void Window::_notification(int p_what) {
if (!embedder && window_id != DisplayServer::INVALID_WINDOW_ID) {
DisplayServer::get_singleton()->window_set_title(tr_title, window_id);
- _update_window_size();
+ if (keep_title_visible) {
+ Size2i title_size = DisplayServer::get_singleton()->window_get_title_size(tr_title, window_id);
+ Size2i size_limit = get_clamped_minimum_size();
+ if (title_size.x > size_limit.x || title_size.y > size_limit.y) {
+ _update_window_size();
+ }
+ }
}
} break;
diff --git a/scene/property_utils.cpp b/scene/property_utils.cpp
index 063e91df67..090c81aefe 100644
--- a/scene/property_utils.cpp
+++ b/scene/property_utils.cpp
@@ -74,10 +74,31 @@ Variant PropertyUtils::get_property_default_value(const Object *p_object, const
const SceneState::PackState &ia = states_stack[i];
bool found = false;
Variant value_in_ancestor = ia.state->get_property_value(ia.node, p_property, found);
+ const Vector<String> &deferred_properties = ia.state->get_node_deferred_nodepath_properties(ia.node);
if (found) {
if (r_is_valid) {
*r_is_valid = true;
}
+ // Replace properties stored as NodePaths with actual Nodes.
+ // Otherwise, the property value would be considered as overridden.
+ if (deferred_properties.has(p_property)) {
+ if (value_in_ancestor.get_type() == Variant::ARRAY) {
+ Array paths = value_in_ancestor;
+
+ bool valid = false;
+ Array array = node->get(p_property, &valid);
+ ERR_CONTINUE(!valid);
+ array = array.duplicate();
+
+ array.resize(paths.size());
+ for (int j = 0; j < array.size(); j++) {
+ array.set(j, node->get_node_or_null(paths[j]));
+ }
+ value_in_ancestor = array;
+ } else {
+ value_in_ancestor = node->get_node_or_null(value_in_ancestor);
+ }
+ }
return value_in_ancestor;
}
// Save script for later
diff --git a/scene/register_scene_types.cpp b/scene/register_scene_types.cpp
index 3727c788fd..2e6759063b 100644
--- a/scene/register_scene_types.cpp
+++ b/scene/register_scene_types.cpp
@@ -293,6 +293,8 @@ static Ref<ResourceFormatSaverShaderInclude> resource_saver_shader_include;
static Ref<ResourceFormatLoaderShaderInclude> resource_loader_shader_include;
void register_scene_types() {
+ OS::get_singleton()->benchmark_begin_measure("Scene", "Register Types");
+
SceneStringNames::create();
OS::get_singleton()->yield(); // may take time to init
@@ -1182,9 +1184,13 @@ void register_scene_types() {
}
SceneDebugger::initialize();
+
+ OS::get_singleton()->benchmark_end_measure("Scene", "Register Types");
}
void unregister_scene_types() {
+ OS::get_singleton()->benchmark_begin_measure("Scene", "Unregister Types");
+
SceneDebugger::deinitialize();
ResourceLoader::remove_resource_format_loader(resource_loader_texture_layered);
@@ -1227,10 +1233,16 @@ void unregister_scene_types() {
CanvasItemMaterial::finish_shaders();
ColorPicker::finish_shaders();
SceneStringNames::free();
+
+ OS::get_singleton()->benchmark_end_measure("Scene", "Unregister Types");
}
void register_scene_singletons() {
+ OS::get_singleton()->benchmark_begin_measure("Scene", "Register Singletons");
+
GDREGISTER_CLASS(ThemeDB);
Engine::get_singleton()->add_singleton(Engine::Singleton("ThemeDB", ThemeDB::get_singleton()));
+
+ OS::get_singleton()->benchmark_end_measure("Scene", "Register Singletons");
}
diff --git a/scene/resources/animation.cpp b/scene/resources/animation.cpp
index af1f9da2b5..52850cac4a 100644
--- a/scene/resources/animation.cpp
+++ b/scene/resources/animation.cpp
@@ -5476,469 +5476,638 @@ bool Animation::_fetch_compressed_by_index(uint32_t p_compressed_track, int p_in
}
// Helper math functions for Variant.
+bool Animation::is_variant_interpolatable(const Variant p_value) {
+ Variant::Type type = p_value.get_type();
+ return (type >= Variant::BOOL && type <= Variant::STRING_NAME) || type == Variant::ARRAY || type >= Variant::PACKED_INT32_ARRAY; // PackedByteArray is unsigned, so it would be better to ignore since blending uses float.
+}
+
+Variant Animation::cast_to_blendwise(const Variant p_value) {
+ switch (p_value.get_type()) {
+ case Variant::BOOL:
+ case Variant::INT: {
+ return p_value.operator double();
+ } break;
+ case Variant::STRING:
+ case Variant::STRING_NAME: {
+ return string_to_array(p_value);
+ } break;
+ case Variant::RECT2I: {
+ return p_value.operator Rect2();
+ } break;
+ case Variant::VECTOR2I: {
+ return p_value.operator Vector2();
+ } break;
+ case Variant::VECTOR3I: {
+ return p_value.operator Vector3();
+ } break;
+ case Variant::VECTOR4I: {
+ return p_value.operator Vector4();
+ } break;
+ case Variant::PACKED_INT32_ARRAY: {
+ return p_value.operator PackedFloat32Array();
+ } break;
+ case Variant::PACKED_INT64_ARRAY: {
+ return p_value.operator PackedFloat64Array();
+ } break;
+ default: {
+ } break;
+ }
+ return p_value;
+}
+
+Variant Animation::cast_from_blendwise(const Variant p_value, const Variant::Type p_type) {
+ switch (p_type) {
+ case Variant::BOOL: {
+ return p_value.operator real_t() >= 0.5;
+ } break;
+ case Variant::INT: {
+ return (int64_t)Math::round(p_value.operator double());
+ } break;
+ case Variant::STRING: {
+ return array_to_string(p_value);
+ } break;
+ case Variant::STRING_NAME: {
+ return StringName(array_to_string(p_value));
+ } break;
+ case Variant::RECT2I: {
+ return Rect2i(p_value.operator Rect2().round());
+ } break;
+ case Variant::VECTOR2I: {
+ return Vector2i(p_value.operator Vector2().round());
+ } break;
+ case Variant::VECTOR3I: {
+ return Vector3i(p_value.operator Vector3().round());
+ } break;
+ case Variant::VECTOR4I: {
+ return Vector4i(p_value.operator Vector4().round());
+ } break;
+ case Variant::PACKED_INT32_ARRAY: {
+ PackedFloat32Array old_val = p_value.operator PackedFloat32Array();
+ PackedInt32Array new_val;
+ new_val.resize(old_val.size());
+ int *new_val_w = new_val.ptrw();
+ for (int i = 0; i < old_val.size(); i++) {
+ new_val_w[i] = (int32_t)Math::round(old_val[i]);
+ }
+ return new_val;
+ } break;
+ case Variant::PACKED_INT64_ARRAY: {
+ PackedFloat64Array old_val = p_value.operator PackedFloat64Array();
+ PackedInt64Array new_val;
+ for (int i = 0; i < old_val.size(); i++) {
+ new_val.push_back((int64_t)Math::round(old_val[i]));
+ }
+ return new_val;
+ } break;
+ default: {
+ } break;
+ }
+ return p_value;
+}
+
+Variant Animation::string_to_array(const Variant p_value) {
+ if (!p_value.is_string()) {
+ return p_value;
+ };
+ const String &str = p_value.operator String();
+ PackedFloat32Array arr;
+ for (int i = 0; i < str.length(); i++) {
+ arr.push_back((float)str[i]);
+ }
+ return arr;
+}
+
+Variant Animation::array_to_string(const Variant p_value) {
+ if (!p_value.is_array()) {
+ return p_value;
+ };
+ const PackedFloat32Array &arr = p_value.operator PackedFloat32Array();
+ String str;
+ for (int i = 0; i < arr.size(); i++) {
+ char32_t c = (char32_t)Math::round(arr[i]);
+ if (c == 0 || (c & 0xfffff800) == 0xd800 || c > 0x10ffff) {
+ c = ' ';
+ }
+ str += c;
+ }
+ return str;
+}
+
Variant Animation::add_variant(const Variant &a, const Variant &b) {
if (a.get_type() != b.get_type()) {
- return a;
+ if (a.is_num() && b.is_num()) {
+ return add_variant(cast_to_blendwise(a), cast_to_blendwise(b));
+ } else if (!a.is_array()) {
+ return a;
+ }
}
switch (a.get_type()) {
case Variant::NIL: {
return Variant();
- }
- case Variant::BOOL: {
- return (a.operator real_t()) + (b.operator real_t()); // It is cast for interpolation.
- }
+ } break;
+ case Variant::FLOAT: {
+ return (a.operator double()) + (b.operator double());
+ } break;
case Variant::RECT2: {
const Rect2 ra = a.operator Rect2();
const Rect2 rb = b.operator Rect2();
return Rect2(ra.position + rb.position, ra.size + rb.size);
- }
- case Variant::RECT2I: {
- const Rect2i ra = a.operator Rect2i();
- const Rect2i rb = b.operator Rect2i();
- return Rect2i(ra.position + rb.position, ra.size + rb.size);
- }
+ } break;
case Variant::PLANE: {
const Plane pa = a.operator Plane();
const Plane pb = b.operator Plane();
return Plane(pa.normal + pb.normal, pa.d + pb.d);
- }
+ } break;
case Variant::AABB: {
const ::AABB aa = a.operator ::AABB();
const ::AABB ab = b.operator ::AABB();
return ::AABB(aa.position + ab.position, aa.size + ab.size);
- }
+ } break;
case Variant::BASIS: {
return (a.operator Basis()) * (b.operator Basis());
- }
+ } break;
case Variant::QUATERNION: {
return (a.operator Quaternion()) * (b.operator Quaternion());
- }
+ } break;
case Variant::TRANSFORM2D: {
return (a.operator Transform2D()) * (b.operator Transform2D());
- }
+ } break;
case Variant::TRANSFORM3D: {
return (a.operator Transform3D()) * (b.operator Transform3D());
- }
+ } break;
+ case Variant::INT:
+ case Variant::RECT2I:
+ case Variant::VECTOR2I:
+ case Variant::VECTOR3I:
+ case Variant::VECTOR4I:
+ case Variant::PACKED_INT32_ARRAY:
+ case Variant::PACKED_INT64_ARRAY: {
+ // Fallback the interpolatable value which needs casting.
+ return cast_from_blendwise(add_variant(cast_to_blendwise(a), cast_to_blendwise(b)), a.get_type());
+ } break;
+ case Variant::BOOL:
+ case Variant::STRING:
+ case Variant::STRING_NAME: {
+ // Specialized for Tween.
+ return b;
+ } break;
+ case Variant::PACKED_BYTE_ARRAY: {
+ // Skip.
+ } break;
default: {
- return Variant::evaluate(Variant::OP_ADD, a, b);
- }
+ if (a.is_array()) {
+ const Array arr_a = a.operator Array();
+ const Array arr_b = b.operator Array();
+
+ int min_size = arr_a.size();
+ int max_size = arr_b.size();
+ bool is_a_larger = inform_variant_array(min_size, max_size);
+
+ Array result;
+ result.set_typed(MAX(arr_a.get_typed_builtin(), arr_b.get_typed_builtin()), StringName(), Variant());
+ result.resize(min_size);
+ int i = 0;
+ for (; i < min_size; i++) {
+ result[i] = add_variant(arr_a[i], arr_b[i]);
+ }
+ if (min_size != max_size) {
+ // Process with last element of the lesser array.
+ // This is pretty funny and bizarre, but artists like to use it for polygon animation.
+ Variant lesser_last;
+ result.resize(max_size);
+ if (is_a_larger) {
+ if (i > 0) {
+ lesser_last = arr_b[i - 1];
+ } else {
+ Variant vz = arr_a[i];
+ vz.zero();
+ lesser_last = vz;
+ }
+ for (; i < max_size; i++) {
+ result[i] = add_variant(arr_a[i], lesser_last);
+ }
+ } else {
+ if (i > 0) {
+ lesser_last = arr_a[i - 1];
+ } else {
+ Variant vz = arr_b[i];
+ vz.zero();
+ lesser_last = vz;
+ }
+ for (; i < max_size; i++) {
+ result[i] = add_variant(lesser_last, arr_b[i]);
+ }
+ }
+ }
+ return result;
+ }
+ } break;
}
+ return Variant::evaluate(Variant::OP_ADD, a, b);
}
Variant Animation::subtract_variant(const Variant &a, const Variant &b) {
if (a.get_type() != b.get_type()) {
- return a;
+ if (a.is_num() && b.is_num()) {
+ return subtract_variant(cast_to_blendwise(a), cast_to_blendwise(b));
+ } else if (!a.is_array()) {
+ return a;
+ }
}
switch (a.get_type()) {
case Variant::NIL: {
return Variant();
- }
- case Variant::BOOL: {
- return (a.operator real_t()) - (b.operator real_t()); // It is cast for interpolation.
- }
+ } break;
+ case Variant::FLOAT: {
+ return (a.operator double()) - (b.operator double());
+ } break;
case Variant::RECT2: {
const Rect2 ra = a.operator Rect2();
const Rect2 rb = b.operator Rect2();
return Rect2(ra.position - rb.position, ra.size - rb.size);
- }
- case Variant::RECT2I: {
- const Rect2i ra = a.operator Rect2i();
- const Rect2i rb = b.operator Rect2i();
- return Rect2i(ra.position - rb.position, ra.size - rb.size);
- }
+ } break;
case Variant::PLANE: {
const Plane pa = a.operator Plane();
const Plane pb = b.operator Plane();
return Plane(pa.normal - pb.normal, pa.d - pb.d);
- }
+ } break;
case Variant::AABB: {
const ::AABB aa = a.operator ::AABB();
const ::AABB ab = b.operator ::AABB();
return ::AABB(aa.position - ab.position, aa.size - ab.size);
- }
+ } break;
case Variant::BASIS: {
return (b.operator Basis()).inverse() * (a.operator Basis());
- }
+ } break;
case Variant::QUATERNION: {
return (b.operator Quaternion()).inverse() * (a.operator Quaternion());
- }
+ } break;
case Variant::TRANSFORM2D: {
return (b.operator Transform2D()).affine_inverse() * (a.operator Transform2D());
- }
+ } break;
case Variant::TRANSFORM3D: {
return (b.operator Transform3D()).affine_inverse() * (a.operator Transform3D());
- }
+ } break;
+ case Variant::INT:
+ case Variant::RECT2I:
+ case Variant::VECTOR2I:
+ case Variant::VECTOR3I:
+ case Variant::VECTOR4I:
+ case Variant::PACKED_INT32_ARRAY:
+ case Variant::PACKED_INT64_ARRAY: {
+ // Fallback the interpolatable value which needs casting.
+ return cast_from_blendwise(subtract_variant(cast_to_blendwise(a), cast_to_blendwise(b)), a.get_type());
+ } break;
+ case Variant::BOOL:
+ case Variant::STRING:
+ case Variant::STRING_NAME: {
+ // Specialized for Tween.
+ return a;
+ } break;
+ case Variant::PACKED_BYTE_ARRAY: {
+ // Skip.
+ } break;
default: {
- return Variant::evaluate(Variant::OP_SUBTRACT, a, b);
- }
+ if (a.is_array()) {
+ const Array arr_a = a.operator Array();
+ const Array arr_b = b.operator Array();
+
+ int min_size = arr_a.size();
+ int max_size = arr_b.size();
+ bool is_a_larger = inform_variant_array(min_size, max_size);
+
+ Array result;
+ result.set_typed(MAX(arr_a.get_typed_builtin(), arr_b.get_typed_builtin()), StringName(), Variant());
+ result.resize(min_size);
+ int i = 0;
+ for (; i < min_size; i++) {
+ result[i] = subtract_variant(arr_a[i], arr_b[i]);
+ }
+ if (min_size != max_size) {
+ // Process with last element of the lesser array.
+ // This is pretty funny and bizarre, but artists like to use it for polygon animation.
+ Variant lesser_last;
+ result.resize(max_size);
+ if (is_a_larger) {
+ if (i > 0) {
+ lesser_last = arr_b[i - 1];
+ } else {
+ Variant vz = arr_a[i];
+ vz.zero();
+ lesser_last = vz;
+ }
+ for (; i < max_size; i++) {
+ result[i] = subtract_variant(arr_a[i], lesser_last);
+ }
+ } else {
+ if (i > 0) {
+ lesser_last = arr_a[i - 1];
+ } else {
+ Variant vz = arr_b[i];
+ vz.zero();
+ lesser_last = vz;
+ }
+ for (; i < max_size; i++) {
+ result[i] = subtract_variant(lesser_last, arr_b[i]);
+ }
+ }
+ }
+ return result;
+ }
+ } break;
}
+ return Variant::evaluate(Variant::OP_SUBTRACT, a, b);
}
Variant Animation::blend_variant(const Variant &a, const Variant &b, float c) {
if (a.get_type() != b.get_type()) {
if (a.is_num() && b.is_num()) {
- double va = a;
- double vb = b;
- return va + vb * c;
+ return blend_variant(cast_to_blendwise(a), cast_to_blendwise(b), c);
+ } else if (!a.is_array()) {
+ return a;
}
- return a;
}
switch (a.get_type()) {
case Variant::NIL: {
return Variant();
- }
- case Variant::INT: {
- return int64_t((a.operator int64_t()) + (b.operator int64_t()) * c + 0.5);
- }
+ } break;
case Variant::FLOAT: {
return (a.operator double()) + (b.operator double()) * c;
- }
+ } break;
case Variant::VECTOR2: {
return (a.operator Vector2()) + (b.operator Vector2()) * c;
- }
- case Variant::VECTOR2I: {
- const Vector2i va = a.operator Vector2i();
- const Vector2i vb = b.operator Vector2i();
- return Vector2i(int32_t(va.x + vb.x * c + 0.5), int32_t(va.y + vb.y * c + 0.5));
- }
+ } break;
case Variant::RECT2: {
const Rect2 ra = a.operator Rect2();
const Rect2 rb = b.operator Rect2();
return Rect2(ra.position + rb.position * c, ra.size + rb.size * c);
- }
- case Variant::RECT2I: {
- const Rect2i ra = a.operator Rect2i();
- const Rect2i rb = b.operator Rect2i();
- return Rect2i(int32_t(ra.position.x + rb.position.x * c + 0.5), int32_t(ra.position.y + rb.position.y * c + 0.5), int32_t(ra.size.x + rb.size.x * c + 0.5), int32_t(ra.size.y + rb.size.y * c + 0.5));
- }
+ } break;
case Variant::VECTOR3: {
return (a.operator Vector3()) + (b.operator Vector3()) * c;
- }
- case Variant::VECTOR3I: {
- const Vector3i va = a.operator Vector3i();
- const Vector3i vb = b.operator Vector3i();
- return Vector3i(int32_t(va.x + vb.x * c + 0.5), int32_t(va.y + vb.y * c + 0.5), int32_t(va.z + vb.z * c + 0.5));
- }
+ } break;
case Variant::VECTOR4: {
return (a.operator Vector4()) + (b.operator Vector4()) * c;
- }
- case Variant::VECTOR4I: {
- const Vector4i va = a.operator Vector4i();
- const Vector4i vb = b.operator Vector4i();
- return Vector4i(int32_t(va.x + vb.x * c + 0.5), int32_t(va.y + vb.y * c + 0.5), int32_t(va.z + vb.z * c + 0.5), int32_t(va.w + vb.w * c + 0.5));
- }
+ } break;
case Variant::PLANE: {
const Plane pa = a.operator Plane();
const Plane pb = b.operator Plane();
return Plane(pa.normal + pb.normal * c, pa.d + pb.d * c);
- }
+ } break;
case Variant::COLOR: {
return (a.operator Color()) + (b.operator Color()) * c;
- }
+ } break;
case Variant::AABB: {
const ::AABB aa = a.operator ::AABB();
const ::AABB ab = b.operator ::AABB();
return ::AABB(aa.position + ab.position * c, aa.size + ab.size * c);
- }
+ } break;
case Variant::BASIS: {
return (a.operator Basis()) + (b.operator Basis()) * c;
- }
+ } break;
case Variant::QUATERNION: {
return (a.operator Quaternion()) * Quaternion().slerp((b.operator Quaternion()), c);
- }
+ } break;
case Variant::TRANSFORM2D: {
return (a.operator Transform2D()) * Transform2D().interpolate_with((b.operator Transform2D()), c);
- }
+ } break;
case Variant::TRANSFORM3D: {
return (a.operator Transform3D()) * Transform3D().interpolate_with((b.operator Transform3D()), c);
- }
+ } break;
+ case Variant::BOOL:
+ case Variant::INT:
+ case Variant::RECT2I:
+ case Variant::VECTOR2I:
+ case Variant::VECTOR3I:
+ case Variant::VECTOR4I:
+ case Variant::PACKED_INT32_ARRAY:
+ case Variant::PACKED_INT64_ARRAY: {
+ // Fallback the interpolatable value which needs casting.
+ return cast_from_blendwise(blend_variant(cast_to_blendwise(a), cast_to_blendwise(b), c), a.get_type());
+ } break;
+ case Variant::STRING:
+ case Variant::STRING_NAME: {
+ Array arr_a = cast_to_blendwise(a);
+ Array arr_b = cast_to_blendwise(b);
+ int min_size = arr_a.size();
+ int max_size = arr_b.size();
+ bool is_a_larger = inform_variant_array(min_size, max_size);
+ int mid_size = interpolate_variant(arr_a.size(), arr_b.size(), c);
+ if (is_a_larger) {
+ arr_a.resize(mid_size);
+ } else {
+ arr_b.resize(mid_size);
+ }
+ return cast_from_blendwise(blend_variant(arr_a, arr_b, c), a.get_type());
+ } break;
+ case Variant::PACKED_BYTE_ARRAY: {
+ // Skip.
+ } break;
default: {
- return c < 0.5 ? a : b;
- }
+ if (a.is_array()) {
+ const Array arr_a = a.operator Array();
+ const Array arr_b = b.operator Array();
+
+ int min_size = arr_a.size();
+ int max_size = arr_b.size();
+ bool is_a_larger = inform_variant_array(min_size, max_size);
+
+ Array result;
+ result.set_typed(MAX(arr_a.get_typed_builtin(), arr_b.get_typed_builtin()), StringName(), Variant());
+ result.resize(min_size);
+ int i = 0;
+ for (; i < min_size; i++) {
+ result[i] = blend_variant(arr_a[i], arr_b[i], c);
+ }
+ if (min_size != max_size) {
+ // Process with last element of the lesser array.
+ // This is pretty funny and bizarre, but artists like to use it for polygon animation.
+ Variant lesser_last;
+ if (is_a_larger && !Math::is_equal_approx(c, 1.0f)) {
+ result.resize(max_size);
+ if (i > 0) {
+ lesser_last = arr_b[i - 1];
+ } else {
+ Variant vz = arr_a[i];
+ vz.zero();
+ lesser_last = vz;
+ }
+ for (; i < max_size; i++) {
+ result[i] = blend_variant(arr_a[i], lesser_last, c);
+ }
+ } else if (!is_a_larger && !Math::is_zero_approx(c)) {
+ result.resize(max_size);
+ if (i > 0) {
+ lesser_last = arr_a[i - 1];
+ } else {
+ Variant vz = arr_b[i];
+ vz.zero();
+ lesser_last = vz;
+ }
+ for (; i < max_size; i++) {
+ result[i] = blend_variant(lesser_last, arr_b[i], c);
+ }
+ }
+ }
+ return result;
+ }
+ } break;
}
+ return c < 0.5 ? a : b;
}
-Variant Animation::interpolate_variant(const Variant &a, const Variant &b, float c) {
+Variant Animation::interpolate_variant(const Variant &a, const Variant &b, float c, bool p_snap_array_element) {
if (a.get_type() != b.get_type()) {
if (a.is_num() && b.is_num()) {
- double va = a;
- double vb = b;
- return va + (vb - va) * c;
+ return interpolate_variant(cast_to_blendwise(a), cast_to_blendwise(b), c);
+ } else if (!a.is_array()) {
+ return a;
}
- return a;
}
switch (a.get_type()) {
case Variant::NIL: {
return Variant();
- }
- case Variant::INT: {
- const int64_t va = a.operator int64_t();
- return int64_t(va + ((b.operator int64_t()) - va) * c);
- }
+ } break;
case Variant::FLOAT: {
const double va = a.operator double();
return va + ((b.operator double()) - va) * c;
- }
+ } break;
case Variant::VECTOR2: {
return (a.operator Vector2()).lerp(b.operator Vector2(), c);
- }
- case Variant::VECTOR2I: {
- const Vector2i va = a.operator Vector2i();
- const Vector2i vb = b.operator Vector2i();
- return Vector2i(int32_t(va.x + (vb.x - va.x) * c), int32_t(va.y + (vb.y - va.y) * c));
- }
+ } break;
case Variant::RECT2: {
const Rect2 ra = a.operator Rect2();
const Rect2 rb = b.operator Rect2();
return Rect2(ra.position.lerp(rb.position, c), ra.size.lerp(rb.size, c));
- }
- case Variant::RECT2I: {
- const Rect2i ra = a.operator Rect2i();
- const Rect2i rb = b.operator Rect2i();
- return Rect2i(int32_t(ra.position.x + (rb.position.x - ra.position.x) * c), int32_t(ra.position.y + (rb.position.y - ra.position.y) * c), int32_t(ra.size.x + (rb.size.x - ra.size.x) * c), int32_t(ra.size.y + (rb.size.y - ra.size.y) * c));
- }
+ } break;
case Variant::VECTOR3: {
return (a.operator Vector3()).lerp(b.operator Vector3(), c);
- }
- case Variant::VECTOR3I: {
- const Vector3i va = a.operator Vector3i();
- const Vector3i vb = b.operator Vector3i();
- return Vector3i(int32_t(va.x + (vb.x - va.x) * c), int32_t(va.y + (vb.y - va.y) * c), int32_t(va.z + (vb.z - va.z) * c));
- }
+ } break;
case Variant::VECTOR4: {
return (a.operator Vector4()).lerp(b.operator Vector4(), c);
- }
- case Variant::VECTOR4I: {
- const Vector4i va = a.operator Vector4i();
- const Vector4i vb = b.operator Vector4i();
- return Vector4i(int32_t(va.x + (vb.x - va.x) * c), int32_t(va.y + (vb.y - va.y) * c), int32_t(va.z + (vb.z - va.z) * c), int32_t(va.w + (vb.w - va.w) * c));
- }
+ } break;
case Variant::PLANE: {
const Plane pa = a.operator Plane();
const Plane pb = b.operator Plane();
return Plane(pa.normal.lerp(pb.normal, c), pa.d + (pb.d - pa.d) * c);
- }
+ } break;
case Variant::COLOR: {
return (a.operator Color()).lerp(b.operator Color(), c);
- }
+ } break;
case Variant::AABB: {
const ::AABB aa = a.operator ::AABB();
const ::AABB ab = b.operator ::AABB();
return ::AABB(aa.position.lerp(ab.position, c), aa.size.lerp(ab.size, c));
- }
+ } break;
case Variant::BASIS: {
return (a.operator Basis()).lerp(b.operator Basis(), c);
- }
+ } break;
case Variant::QUATERNION: {
return (a.operator Quaternion()).slerp(b.operator Quaternion(), c);
- }
+ } break;
case Variant::TRANSFORM2D: {
return (a.operator Transform2D()).interpolate_with(b.operator Transform2D(), c);
- }
+ } break;
case Variant::TRANSFORM3D: {
return (a.operator Transform3D()).interpolate_with(b.operator Transform3D(), c);
- }
- case Variant::STRING: {
- // This is pretty funny and bizarre, but artists like to use it for typewriter effects.
- const String sa = a.operator String();
- const String sb = b.operator String();
- String dst;
- int sa_len = sa.length();
- int sb_len = sb.length();
- int csize = sa_len + (sb_len - sa_len) * c;
- if (csize == 0) {
- return "";
- }
- dst.resize(csize + 1);
- dst[csize] = 0;
- int split = csize / 2;
-
- for (int i = 0; i < csize; i++) {
- char32_t chr = ' ';
-
- if (i < split) {
- if (i < sa.length()) {
- chr = sa[i];
- } else if (i < sb.length()) {
- chr = sb[i];
- }
-
- } else {
- if (i < sb.length()) {
- chr = sb[i];
- } else if (i < sa.length()) {
- chr = sa[i];
- }
- }
-
- dst[i] = chr;
- }
-
- return dst;
- }
- case Variant::PACKED_INT32_ARRAY: {
- const Vector<int32_t> arr_a = a;
- const Vector<int32_t> arr_b = b;
- int sz = arr_a.size();
- if (sz == 0 || arr_b.size() != sz) {
- return a;
- } else {
- Vector<int32_t> v;
- v.resize(sz);
- {
- int32_t *vw = v.ptrw();
- const int32_t *ar = arr_a.ptr();
- const int32_t *br = arr_b.ptr();
-
- Variant va;
- for (int i = 0; i < sz; i++) {
- va = interpolate_variant(ar[i], br[i], c);
- vw[i] = va;
- }
- }
- return v;
- }
- }
+ } break;
+ case Variant::BOOL:
+ case Variant::INT:
+ case Variant::RECT2I:
+ case Variant::VECTOR2I:
+ case Variant::VECTOR3I:
+ case Variant::VECTOR4I:
+ case Variant::PACKED_INT32_ARRAY:
case Variant::PACKED_INT64_ARRAY: {
- const Vector<int64_t> arr_a = a;
- const Vector<int64_t> arr_b = b;
- int sz = arr_a.size();
- if (sz == 0 || arr_b.size() != sz) {
- return a;
- } else {
- Vector<int64_t> v;
- v.resize(sz);
- {
- int64_t *vw = v.ptrw();
- const int64_t *ar = arr_a.ptr();
- const int64_t *br = arr_b.ptr();
-
- Variant va;
- for (int i = 0; i < sz; i++) {
- va = interpolate_variant(ar[i], br[i], c);
- vw[i] = va;
- }
- }
- return v;
- }
- }
- case Variant::PACKED_FLOAT32_ARRAY: {
- const Vector<float> arr_a = a;
- const Vector<float> arr_b = b;
- int sz = arr_a.size();
- if (sz == 0 || arr_b.size() != sz) {
- return a;
- } else {
- Vector<float> v;
- v.resize(sz);
- {
- float *vw = v.ptrw();
- const float *ar = arr_a.ptr();
- const float *br = arr_b.ptr();
-
- Variant va;
- for (int i = 0; i < sz; i++) {
- va = interpolate_variant(ar[i], br[i], c);
- vw[i] = va;
- }
- }
- return v;
- }
- }
- case Variant::PACKED_FLOAT64_ARRAY: {
- const Vector<double> arr_a = a;
- const Vector<double> arr_b = b;
- int sz = arr_a.size();
- if (sz == 0 || arr_b.size() != sz) {
- return a;
+ // Fallback the interpolatable value which needs casting.
+ return cast_from_blendwise(interpolate_variant(cast_to_blendwise(a), cast_to_blendwise(b), c), a.get_type());
+ } break;
+ case Variant::STRING:
+ case Variant::STRING_NAME: {
+ Array arr_a = cast_to_blendwise(a);
+ Array arr_b = cast_to_blendwise(b);
+ int min_size = arr_a.size();
+ int max_size = arr_b.size();
+ bool is_a_larger = inform_variant_array(min_size, max_size);
+ int mid_size = interpolate_variant(arr_a.size(), arr_b.size(), c);
+ if (is_a_larger) {
+ arr_a.resize(mid_size);
} else {
- Vector<double> v;
- v.resize(sz);
- {
- double *vw = v.ptrw();
- const double *ar = arr_a.ptr();
- const double *br = arr_b.ptr();
-
- Variant va;
- for (int i = 0; i < sz; i++) {
- va = interpolate_variant(ar[i], br[i], c);
- vw[i] = va;
- }
- }
- return v;
+ arr_b.resize(mid_size);
}
- }
- case Variant::PACKED_VECTOR2_ARRAY: {
- const Vector<Vector2> arr_a = a;
- const Vector<Vector2> arr_b = b;
- int sz = arr_a.size();
- if (sz == 0 || arr_b.size() != sz) {
- return a;
- } else {
- Vector<Vector2> v;
- v.resize(sz);
- {
- Vector2 *vw = v.ptrw();
- const Vector2 *ar = arr_a.ptr();
- const Vector2 *br = arr_b.ptr();
-
- for (int i = 0; i < sz; i++) {
- vw[i] = ar[i].lerp(br[i], c);
- }
+ return cast_from_blendwise(interpolate_variant(arr_a, arr_b, c, true), a.get_type());
+ } break;
+ case Variant::PACKED_BYTE_ARRAY: {
+ // Skip.
+ } break;
+ default: {
+ if (a.is_array()) {
+ const Array arr_a = a.operator Array();
+ const Array arr_b = b.operator Array();
+
+ int min_size = arr_a.size();
+ int max_size = arr_b.size();
+ bool is_a_larger = inform_variant_array(min_size, max_size);
+
+ Array result;
+ result.set_typed(MAX(arr_a.get_typed_builtin(), arr_b.get_typed_builtin()), StringName(), Variant());
+ result.resize(min_size);
+ int i = 0;
+ for (; i < min_size; i++) {
+ result[i] = interpolate_variant(arr_a[i], arr_b[i], c);
}
- return v;
- }
- }
- case Variant::PACKED_VECTOR3_ARRAY: {
- const Vector<Vector3> arr_a = a;
- const Vector<Vector3> arr_b = b;
- int sz = arr_a.size();
- if (sz == 0 || arr_b.size() != sz) {
- return a;
- } else {
- Vector<Vector3> v;
- v.resize(sz);
- {
- Vector3 *vw = v.ptrw();
- const Vector3 *ar = arr_a.ptr();
- const Vector3 *br = arr_b.ptr();
-
- for (int i = 0; i < sz; i++) {
- vw[i] = ar[i].lerp(br[i], c);
+ if (min_size != max_size) {
+ // Process with last element of the lesser array.
+ // This is pretty funny and bizarre, but artists like to use it for polygon animation.
+ Variant lesser_last;
+ if (is_a_larger && !Math::is_equal_approx(c, 1.0f)) {
+ result.resize(max_size);
+ if (p_snap_array_element) {
+ c = 0;
+ }
+ if (i > 0) {
+ lesser_last = arr_b[i - 1];
+ } else {
+ Variant vz = arr_a[i];
+ vz.zero();
+ lesser_last = vz;
+ }
+ for (; i < max_size; i++) {
+ result[i] = interpolate_variant(arr_a[i], lesser_last, c);
+ }
+ } else if (!is_a_larger && !Math::is_zero_approx(c)) {
+ result.resize(max_size);
+ if (p_snap_array_element) {
+ c = 1;
+ }
+ if (i > 0) {
+ lesser_last = arr_a[i - 1];
+ } else {
+ Variant vz = arr_b[i];
+ vz.zero();
+ lesser_last = vz;
+ }
+ for (; i < max_size; i++) {
+ result[i] = interpolate_variant(lesser_last, arr_b[i], c);
+ }
}
}
- return v;
+ return result;
}
- }
- case Variant::PACKED_COLOR_ARRAY: {
- const Vector<Color> arr_a = a;
- const Vector<Color> arr_b = b;
- int sz = arr_a.size();
- if (sz == 0 || arr_b.size() != sz) {
- return a;
- } else {
- Vector<Color> v;
- v.resize(sz);
- {
- Color *vw = v.ptrw();
- const Color *ar = arr_a.ptr();
- const Color *br = arr_b.ptr();
+ } break;
+ }
+ return c < 0.5 ? a : b;
+}
- for (int i = 0; i < sz; i++) {
- vw[i] = ar[i].lerp(br[i], c);
- }
- }
- return v;
- }
- }
- default: {
- return c < 0.5 ? a : b;
- }
+bool Animation::inform_variant_array(int &r_min, int &r_max) {
+ if (r_min <= r_max) {
+ return false;
}
+ SWAP(r_min, r_max);
+ return true;
}
Animation::Animation() {
diff --git a/scene/resources/animation.h b/scene/resources/animation.h
index c128c14e49..2579b6c8ce 100644
--- a/scene/resources/animation.h
+++ b/scene/resources/animation.h
@@ -373,6 +373,8 @@ protected:
static void _bind_methods();
+ static bool inform_variant_array(int &r_min, int &r_max); // Returns true if max and min are swapped.
+
public:
int add_track(TrackType p_type, int p_at_pos = -1);
void remove_track(int p_track);
@@ -487,11 +489,19 @@ public:
void optimize(real_t p_allowed_velocity_err = 0.01, real_t p_allowed_angular_err = 0.01, int p_precision = 3);
void compress(uint32_t p_page_size = 8192, uint32_t p_fps = 120, float p_split_tolerance = 4.0); // 4.0 seems to be the split tolerance sweet spot from many tests
- // Helper math functions for Variant.
+ // Helper functions for Variant.
+ static bool is_variant_interpolatable(const Variant p_value);
+
+ static Variant cast_to_blendwise(const Variant p_value);
+ static Variant cast_from_blendwise(const Variant p_value, const Variant::Type p_type);
+
+ static Variant string_to_array(const Variant p_value);
+ static Variant array_to_string(const Variant p_value);
+
static Variant add_variant(const Variant &a, const Variant &b);
static Variant subtract_variant(const Variant &a, const Variant &b);
static Variant blend_variant(const Variant &a, const Variant &b, float c);
- static Variant interpolate_variant(const Variant &a, const Variant &b, float c);
+ static Variant interpolate_variant(const Variant &a, const Variant &b, float c, bool p_snap_array_element = false);
Animation();
~Animation();
diff --git a/scene/resources/camera_attributes.cpp b/scene/resources/camera_attributes.cpp
index 7c46729af3..af5df165b3 100644
--- a/scene/resources/camera_attributes.cpp
+++ b/scene/resources/camera_attributes.cpp
@@ -286,10 +286,10 @@ void CameraAttributesPractical::_bind_methods() {
ADD_GROUP("DOF Blur", "dof_blur_");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "dof_blur_far_enabled"), "set_dof_blur_far_enabled", "is_dof_blur_far_enabled");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "dof_blur_far_distance", PROPERTY_HINT_RANGE, "0.01,8192,0.01,exp,suffix:m"), "set_dof_blur_far_distance", "get_dof_blur_far_distance");
- ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "dof_blur_far_transition", PROPERTY_HINT_RANGE, "-1,8192,0.01,exp"), "set_dof_blur_far_transition", "get_dof_blur_far_transition");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "dof_blur_far_transition", PROPERTY_HINT_RANGE, "-1,8192,0.01"), "set_dof_blur_far_transition", "get_dof_blur_far_transition");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "dof_blur_near_enabled"), "set_dof_blur_near_enabled", "is_dof_blur_near_enabled");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "dof_blur_near_distance", PROPERTY_HINT_RANGE, "0.01,8192,0.01,exp,suffix:m"), "set_dof_blur_near_distance", "get_dof_blur_near_distance");
- ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "dof_blur_near_transition", PROPERTY_HINT_RANGE, "-1,8192,0.01,exp"), "set_dof_blur_near_transition", "get_dof_blur_near_transition");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "dof_blur_near_transition", PROPERTY_HINT_RANGE, "-1,8192,0.01"), "set_dof_blur_near_transition", "get_dof_blur_near_transition");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "dof_blur_amount", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_dof_blur_amount", "get_dof_blur_amount");
ADD_GROUP("Auto Exposure", "auto_exposure_");
@@ -373,6 +373,11 @@ real_t CameraAttributesPhysical::get_fov() const {
return frustum_fov;
}
+#ifdef MINGW_ENABLED
+#undef near
+#undef far
+#endif
+
void CameraAttributesPhysical::_update_frustum() {
//https://en.wikipedia.org/wiki/Circle_of_confusion#Circle_of_confusion_diameter_limit_based_on_d/1500
Vector2i sensor_size = Vector2i(36, 24); // Matches high-end DSLR, could be made variable if there is demand.
diff --git a/scene/resources/importer_mesh.cpp b/scene/resources/importer_mesh.cpp
index 1f4171c072..2ffb8da46c 100644
--- a/scene/resources/importer_mesh.cpp
+++ b/scene/resources/importer_mesh.cpp
@@ -418,11 +418,10 @@ void ImporterMesh::generate_lods(float p_normal_merge_angle, float p_normal_spli
}
}
- LocalVector<float> normal_weights;
- normal_weights.resize(merged_vertex_count);
- for (unsigned int j = 0; j < merged_vertex_count; j++) {
- normal_weights[j] = 2.0; // Give some weight to normal preservation, may be worth exposing as an import setting
- }
+ const float normal_weights[3] = {
+ // Give some weight to normal preservation, may be worth exposing as an import setting
+ 2.0f, 2.0f, 2.0f
+ };
Vector<float> merged_vertices_f32 = vector3_to_float32_array(merged_vertices_ptr, merged_vertex_count);
float scale = SurfaceTool::simplify_scale_func(merged_vertices_f32.ptr(), merged_vertex_count, sizeof(float) * 3);
@@ -460,12 +459,13 @@ void ImporterMesh::generate_lods(float p_normal_merge_angle, float p_normal_spli
(const uint32_t *)merged_indices_ptr, index_count,
merged_vertices_f32.ptr(), merged_vertex_count,
sizeof(float) * 3, // Vertex stride
+ merged_normals_f32.ptr(),
+ sizeof(float) * 3, // Attribute stride
+ normal_weights, 3,
index_target,
max_mesh_error,
simplify_options,
- &mesh_error,
- merged_normals_f32.ptr(),
- normal_weights.ptr(), 3);
+ &mesh_error);
if (new_index_count < last_index_count * 1.5f) {
index_target = index_target * 1.5f;
@@ -969,7 +969,7 @@ Vector<Ref<Shape3D>> ImporterMesh::convex_decompose(const Ref<MeshConvexDecompos
if (found_vertex) {
index = found_vertex->value;
} else {
- index = ++vertex_count;
+ index = vertex_count++;
vertex_map[vertex] = index;
vertex_w[index] = vertex;
}
diff --git a/scene/resources/material.cpp b/scene/resources/material.cpp
index 515ae67c59..1b74063ff4 100644
--- a/scene/resources/material.cpp
+++ b/scene/resources/material.cpp
@@ -3167,7 +3167,7 @@ bool StandardMaterial3D::_set(const StringName &p_name, const Variant &p_value)
{ "flags_use_shadow_to_opacity", "shadow_to_opacity" },
{ "flags_no_depth_test", "no_depth_test" },
{ "flags_use_point_size", "use_point_size" },
- { "flags_fixed_size", "fixed_Size" },
+ { "flags_fixed_size", "fixed_size" },
{ "flags_albedo_tex_force_srgb", "albedo_texture_force_srgb" },
{ "flags_do_not_receive_shadows", "disable_receive_shadows" },
{ "flags_disable_ambient_light", "disable_ambient_light" },
diff --git a/scene/resources/packed_scene.cpp b/scene/resources/packed_scene.cpp
index 2456212327..1f6e453e88 100644
--- a/scene/resources/packed_scene.cpp
+++ b/scene/resources/packed_scene.cpp
@@ -138,7 +138,7 @@ Node *SceneState::instantiate(GenEditState p_edit_state) const {
}
int nc = nodes.size();
- ERR_FAIL_COND_V(nc == 0, nullptr);
+ ERR_FAIL_COND_V_MSG(nc == 0, nullptr, vformat("Failed to instantiate scene state of \"%s\", node count is 0. Make sure the PackedScene resource is valid.", path));
const StringName *snames = nullptr;
int sname_count = names.size();
@@ -219,7 +219,7 @@ Node *SceneState::instantiate(GenEditState p_edit_state) const {
Ref<PackedScene> sdata = props[n.instance & FLAG_MASK];
ERR_FAIL_COND_V(!sdata.is_valid(), nullptr);
node = sdata->instantiate(p_edit_state == GEN_EDIT_STATE_DISABLED ? PackedScene::GEN_EDIT_STATE_DISABLED : PackedScene::GEN_EDIT_STATE_INSTANCE);
- ERR_FAIL_NULL_V(node, nullptr);
+ ERR_FAIL_NULL_V_MSG(node, nullptr, vformat("Failed to load scene dependency: \"%s\". Make sure the required scene is valid.", sdata->get_path()));
}
} else if (n.type == TYPE_INSTANTIATED) {
diff --git a/scene/resources/particle_process_material.cpp b/scene/resources/particle_process_material.cpp
index 2a70deee6c..4ed1980826 100644
--- a/scene/resources/particle_process_material.cpp
+++ b/scene/resources/particle_process_material.cpp
@@ -762,8 +762,11 @@ void ParticleProcessMaterial::_update_shader() {
code += "}\n";
- code += "vec3 process_radial_displacement(DynamicsParameters param, float lifetime, inout uint alt_seed, mat4 transform, mat4 emission_transform){\n";
+ code += "vec3 process_radial_displacement(DynamicsParameters param, float lifetime, inout uint alt_seed, mat4 transform, mat4 emission_transform, float delta){\n";
code += " vec3 radial_displacement = vec3(0.0);\n";
+ code += " if (delta < 0.001){\n";
+ code += " return radial_displacement;\n";
+ code += " }\n";
code += " float radial_displacement_multiplier = 1.0;\n";
if (tex_parameters[PARAM_RADIAL_VELOCITY].is_valid()) {
code += " radial_displacement_multiplier = texture(radial_velocity_curve, vec2(lifetime)).r;\n";
@@ -774,7 +777,7 @@ void ParticleProcessMaterial::_update_shader() {
code += " }else{radial_displacement = get_random_direction_from_spread(alt_seed, 360.0)* param.radial_velocity;} \n";
code += " if (radial_displacement_multiplier * param.radial_velocity < 0.0){\n // Prevent inwards velocity to flicker once the point is reached.";
code += " if (length(radial_displacement) > 0.01){\n";
- code += " radial_displacement = normalize(radial_displacement) * min(abs((radial_displacement_multiplier * param.radial_velocity)), length(transform[3].xyz - global_pivot));\n";
+ code += " radial_displacement = normalize(radial_displacement) * min(abs((radial_displacement_multiplier * param.radial_velocity)), length(transform[3].xyz - global_pivot) / delta);\n";
code += " }\n";
code += " \n";
code += " return radial_displacement;\n";
@@ -923,7 +926,7 @@ void ParticleProcessMaterial::_update_shader() {
}
code += " // calculate all velocity\n";
code += " \n";
- code += " controlled_displacement += process_radial_displacement(dynamic_params, lifetime_percent, alt_seed, TRANSFORM, EMISSION_TRANSFORM);\n";
+ code += " controlled_displacement += process_radial_displacement(dynamic_params, lifetime_percent, alt_seed, TRANSFORM, EMISSION_TRANSFORM, DELTA);\n";
code += " \n";
if (tex_parameters[PARAM_DIRECTIONAL_VELOCITY].is_valid()) {
code += " controlled_displacement += process_directional_displacement(dynamic_params, lifetime_percent, TRANSFORM, EMISSION_TRANSFORM);\n";
diff --git a/scene/resources/primitive_meshes.cpp b/scene/resources/primitive_meshes.cpp
index 13791d8c2b..8ad9eec25f 100644
--- a/scene/resources/primitive_meshes.cpp
+++ b/scene/resources/primitive_meshes.cpp
@@ -552,7 +552,7 @@ void CapsuleMesh::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radius", PROPERTY_HINT_RANGE, "0.001,100.0,0.001,or_greater,suffix:m"), "set_radius", "get_radius");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "height", PROPERTY_HINT_RANGE, "0.001,100.0,0.001,or_greater,suffix:m"), "set_height", "get_height");
ADD_PROPERTY(PropertyInfo(Variant::INT, "radial_segments", PROPERTY_HINT_RANGE, "1,100,1,or_greater"), "set_radial_segments", "get_radial_segments");
- ADD_PROPERTY(PropertyInfo(Variant::INT, "rings", PROPERTY_HINT_RANGE, "1,100,1,or_greater"), "set_rings", "get_rings");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "rings", PROPERTY_HINT_RANGE, "0,100,1,or_greater"), "set_rings", "get_rings");
ADD_LINKED_PROPERTY("radius", "height");
ADD_LINKED_PROPERTY("height", "radius");
@@ -594,7 +594,8 @@ int CapsuleMesh::get_radial_segments() const {
}
void CapsuleMesh::set_rings(const int p_rings) {
- rings = p_rings > 1 ? p_rings : 1;
+ ERR_FAIL_COND(p_rings < 0);
+ rings = p_rings;
_request_update();
}
@@ -1161,7 +1162,7 @@ void CylinderMesh::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "bottom_radius", PROPERTY_HINT_RANGE, "0,100,0.001,or_greater,suffix:m"), "set_bottom_radius", "get_bottom_radius");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "height", PROPERTY_HINT_RANGE, "0.001,100,0.001,or_greater,suffix:m"), "set_height", "get_height");
ADD_PROPERTY(PropertyInfo(Variant::INT, "radial_segments", PROPERTY_HINT_RANGE, "1,100,1,or_greater"), "set_radial_segments", "get_radial_segments");
- ADD_PROPERTY(PropertyInfo(Variant::INT, "rings", PROPERTY_HINT_RANGE, "1,100,1,or_greater"), "set_rings", "get_rings");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "rings", PROPERTY_HINT_RANGE, "0,100,1,or_greater"), "set_rings", "get_rings");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "cap_top"), "set_cap_top", "is_cap_top");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "cap_bottom"), "set_cap_bottom", "is_cap_bottom");
}
@@ -1206,7 +1207,8 @@ int CylinderMesh::get_radial_segments() const {
}
void CylinderMesh::set_rings(const int p_rings) {
- rings = p_rings > 0 ? p_rings : 0;
+ ERR_FAIL_COND(p_rings < 0);
+ rings = p_rings;
_request_update();
}
@@ -1919,7 +1921,8 @@ int SphereMesh::get_radial_segments() const {
}
void SphereMesh::set_rings(const int p_rings) {
- rings = p_rings > 1 ? p_rings : 1;
+ ERR_FAIL_COND(p_rings < 1);
+ rings = p_rings;
_request_update();
}
diff --git a/scene/resources/resource_format_text.cpp b/scene/resources/resource_format_text.cpp
index 19718f12be..037cd32f10 100644
--- a/scene/resources/resource_format_text.cpp
+++ b/scene/resources/resource_format_text.cpp
@@ -973,8 +973,7 @@ Error ResourceLoaderText::rename_dependencies(Ref<FileAccess> p_f, const String
}
if (p_map.has(path)) {
- String np = p_map[path];
- path = np;
+ path = p_map[path];
}
if (relative) {
@@ -1791,8 +1790,8 @@ Error ResourceFormatLoaderText::rename_dependencies(const String &p_path, const
err = loader.rename_dependencies(f, p_path, p_map);
}
- if (err == OK) {
- Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_RESOURCES);
+ Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_RESOURCES);
+ if (err == OK && da->file_exists(p_path + ".depren")) {
da->remove(p_path);
da->rename(p_path + ".depren", p_path);
}
diff --git a/scene/resources/skeleton_modification_2d_twoboneik.cpp b/scene/resources/skeleton_modification_2d_twoboneik.cpp
index d0b5625298..4458cdc0e3 100644
--- a/scene/resources/skeleton_modification_2d_twoboneik.cpp
+++ b/scene/resources/skeleton_modification_2d_twoboneik.cpp
@@ -468,8 +468,8 @@ void SkeletonModification2DTwoBoneIK::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_joint_two_bone_idx"), &SkeletonModification2DTwoBoneIK::get_joint_two_bone_idx);
ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "target_nodepath", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Node2D"), "set_target_node", "get_target_node");
- ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "target_minimum_distance", PROPERTY_HINT_RANGE, "0,100000000,0.01,suffix:m"), "set_target_minimum_distance", "get_target_minimum_distance");
- ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "target_maximum_distance", PROPERTY_HINT_NONE, "0,100000000,0.01,suffix:m"), "set_target_maximum_distance", "get_target_maximum_distance");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "target_minimum_distance", PROPERTY_HINT_RANGE, "0,100000000,0.01,suffix:px"), "set_target_minimum_distance", "get_target_minimum_distance");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "target_maximum_distance", PROPERTY_HINT_NONE, "0,100000000,0.01,suffix:px"), "set_target_maximum_distance", "get_target_maximum_distance");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "flip_bend_direction", PROPERTY_HINT_NONE, ""), "set_flip_bend_direction", "get_flip_bend_direction");
ADD_GROUP("", "");
}
diff --git a/scene/resources/sky_material.cpp b/scene/resources/sky_material.cpp
index 670ce152bd..640261d615 100644
--- a/scene/resources/sky_material.cpp
+++ b/scene/resources/sky_material.cpp
@@ -161,6 +161,15 @@ bool ProceduralSkyMaterial::get_use_debanding() const {
return use_debanding;
}
+void ProceduralSkyMaterial::set_energy_multiplier(float p_multiplier) {
+ global_energy_multiplier = p_multiplier;
+ RS::get_singleton()->material_set_param(_get_material(), "exposure", global_energy_multiplier);
+}
+
+float ProceduralSkyMaterial::get_energy_multiplier() const {
+ return global_energy_multiplier;
+}
+
Shader::Mode ProceduralSkyMaterial::get_shader_mode() const {
return Shader::MODE_SKY;
}
@@ -226,6 +235,9 @@ void ProceduralSkyMaterial::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_use_debanding", "use_debanding"), &ProceduralSkyMaterial::set_use_debanding);
ClassDB::bind_method(D_METHOD("get_use_debanding"), &ProceduralSkyMaterial::get_use_debanding);
+ ClassDB::bind_method(D_METHOD("set_energy_multiplier", "multiplier"), &ProceduralSkyMaterial::set_energy_multiplier);
+ ClassDB::bind_method(D_METHOD("get_energy_multiplier"), &ProceduralSkyMaterial::get_energy_multiplier);
+
ADD_GROUP("Sky", "sky_");
ADD_PROPERTY(PropertyInfo(Variant::COLOR, "sky_top_color", PROPERTY_HINT_COLOR_NO_ALPHA), "set_sky_top_color", "get_sky_top_color");
ADD_PROPERTY(PropertyInfo(Variant::COLOR, "sky_horizon_color", PROPERTY_HINT_COLOR_NO_ALPHA), "set_sky_horizon_color", "get_sky_horizon_color");
@@ -246,6 +258,7 @@ void ProceduralSkyMaterial::_bind_methods() {
ADD_GROUP("", "");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_debanding"), "set_use_debanding", "get_use_debanding");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "energy_multiplier", PROPERTY_HINT_RANGE, "0,128,0.01"), "set_energy_multiplier", "get_energy_multiplier");
}
void ProceduralSkyMaterial::cleanup_shader() {
@@ -280,6 +293,7 @@ uniform float ground_curve : hint_range(0, 1) = 0.02;
uniform float ground_energy = 1.0;
uniform float sun_angle_max = 30.0;
uniform float sun_curve : hint_range(0, 1) = 0.15;
+uniform float exposure : hint_range(0, 128) = 1.0;
void sky() {
float v_angle = acos(clamp(EYEDIR.y, -1.0, 1.0));
@@ -334,7 +348,7 @@ void sky() {
vec3 ground = mix(ground_horizon_color.rgb, ground_bottom_color.rgb, clamp(1.0 - pow(1.0 - c, 1.0 / ground_curve), 0.0, 1.0));
ground *= ground_energy;
- COLOR = mix(ground, sky, step(0.0, EYEDIR.y));
+ COLOR = mix(ground, sky, step(0.0, EYEDIR.y)) * exposure;
}
)",
i ? "render_mode use_debanding;" : ""));
@@ -358,6 +372,7 @@ ProceduralSkyMaterial::ProceduralSkyMaterial() {
set_sun_angle_max(30.0);
set_sun_curve(0.15);
set_use_debanding(true);
+ set_energy_multiplier(1.0);
}
ProceduralSkyMaterial::~ProceduralSkyMaterial() {
@@ -393,6 +408,15 @@ bool PanoramaSkyMaterial::is_filtering_enabled() const {
return filter;
}
+void PanoramaSkyMaterial::set_energy_multiplier(float p_multiplier) {
+ energy_multiplier = p_multiplier;
+ RS::get_singleton()->material_set_param(_get_material(), "exposure", energy_multiplier);
+}
+
+float PanoramaSkyMaterial::get_energy_multiplier() const {
+ return energy_multiplier;
+}
+
Shader::Mode PanoramaSkyMaterial::get_shader_mode() const {
return Shader::MODE_SKY;
}
@@ -420,8 +444,12 @@ void PanoramaSkyMaterial::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_filtering_enabled", "enabled"), &PanoramaSkyMaterial::set_filtering_enabled);
ClassDB::bind_method(D_METHOD("is_filtering_enabled"), &PanoramaSkyMaterial::is_filtering_enabled);
+ ClassDB::bind_method(D_METHOD("set_energy_multiplier", "multiplier"), &PanoramaSkyMaterial::set_energy_multiplier);
+ ClassDB::bind_method(D_METHOD("get_energy_multiplier"), &PanoramaSkyMaterial::get_energy_multiplier);
+
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "panorama", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_panorama", "get_panorama");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "filter"), "set_filtering_enabled", "is_filtering_enabled");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "energy_multiplier", PROPERTY_HINT_RANGE, "0,128,0.01"), "set_energy_multiplier", "get_energy_multiplier");
}
Mutex PanoramaSkyMaterial::shader_mutex;
@@ -447,9 +475,10 @@ void PanoramaSkyMaterial::_update_shader() {
shader_type sky;
uniform sampler2D source_panorama : %s, source_color, hint_default_black;
+uniform float exposure : hint_range(0, 128) = 1.0;
void sky() {
- COLOR = texture(source_panorama, SKY_COORDS).rgb;
+ COLOR = texture(source_panorama, SKY_COORDS).rgb * exposure;
}
)",
i ? "filter_linear" : "filter_nearest"));
@@ -460,6 +489,7 @@ void sky() {
}
PanoramaSkyMaterial::PanoramaSkyMaterial() {
+ set_energy_multiplier(1.0);
}
PanoramaSkyMaterial::~PanoramaSkyMaterial() {
diff --git a/scene/resources/sky_material.h b/scene/resources/sky_material.h
index f570dd998d..4ffb18c6b4 100644
--- a/scene/resources/sky_material.h
+++ b/scene/resources/sky_material.h
@@ -53,6 +53,7 @@ private:
float sun_angle_max = 0.0f;
float sun_curve = 0.0f;
bool use_debanding = true;
+ float global_energy_multiplier = 1.0f;
static Mutex shader_mutex;
static RID shader_cache[2];
@@ -103,6 +104,9 @@ public:
void set_use_debanding(bool p_use_debanding);
bool get_use_debanding() const;
+ void set_energy_multiplier(float p_multiplier);
+ float get_energy_multiplier() const;
+
virtual Shader::Mode get_shader_mode() const override;
virtual RID get_shader_rid() const override;
virtual RID get_rid() const override;
@@ -121,6 +125,7 @@ class PanoramaSkyMaterial : public Material {
private:
Ref<Texture2D> panorama;
+ float energy_multiplier = 1.0f;
static Mutex shader_mutex;
static RID shader_cache[2];
diff --git a/scene/resources/surface_tool.h b/scene/resources/surface_tool.h
index 2a8ad53525..9dfb298b9e 100644
--- a/scene/resources/surface_tool.h
+++ b/scene/resources/surface_tool.h
@@ -86,7 +86,7 @@ public:
static OptimizeVertexCacheFunc optimize_vertex_cache_func;
typedef size_t (*SimplifyFunc)(unsigned int *destination, const unsigned int *indices, size_t index_count, const float *vertex_positions, size_t vertex_count, size_t vertex_positions_stride, size_t target_index_count, float target_error, unsigned int options, float *r_error);
static SimplifyFunc simplify_func;
- typedef size_t (*SimplifyWithAttribFunc)(unsigned int *destination, const unsigned int *indices, size_t index_count, const float *vertex_data, size_t vertex_count, size_t vertex_stride, size_t target_index_count, float target_error, unsigned int options, float *result_error, const float *attributes, const float *attribute_weights, size_t attribute_count);
+ typedef size_t (*SimplifyWithAttribFunc)(unsigned int *destination, const unsigned int *indices, size_t index_count, const float *vertex_data, size_t vertex_count, size_t vertex_stride, const float *attributes, size_t attribute_stride, const float *attribute_weights, size_t attribute_count, size_t target_index_count, float target_error, unsigned int options, float *result_error);
static SimplifyWithAttribFunc simplify_with_attrib_func;
typedef float (*SimplifyScaleFunc)(const float *vertex_positions, size_t vertex_count, size_t vertex_positions_stride);
static SimplifyScaleFunc simplify_scale_func;
diff --git a/scene/resources/text_line.cpp b/scene/resources/text_line.cpp
index 98527da96b..76c0a28ea4 100644
--- a/scene/resources/text_line.cpp
+++ b/scene/resources/text_line.cpp
@@ -81,6 +81,10 @@ void TextLine::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::INT, "text_overrun_behavior", PROPERTY_HINT_ENUM, "Trim Nothing,Trim Characters,Trim Words,Ellipsis,Word Ellipsis"), "set_text_overrun_behavior", "get_text_overrun_behavior");
+ ClassDB::bind_method(D_METHOD("set_ellipsis_char", "char"), &TextLine::set_ellipsis_char);
+ ClassDB::bind_method(D_METHOD("get_ellipsis_char"), &TextLine::get_ellipsis_char);
+ ADD_PROPERTY(PropertyInfo(Variant::STRING, "ellipsis_char"), "set_ellipsis_char", "get_ellipsis_char");
+
ClassDB::bind_method(D_METHOD("get_objects"), &TextLine::get_objects);
ClassDB::bind_method(D_METHOD("get_object_rect", "key"), &TextLine::get_object_rect);
@@ -137,8 +141,10 @@ void TextLine::_shape() {
if (alignment == HORIZONTAL_ALIGNMENT_FILL) {
TS->shaped_text_fit_to_width(rid, width, flags);
overrun_flags.set_flag(TextServer::OVERRUN_JUSTIFICATION_AWARE);
+ TS->shaped_text_set_custom_ellipsis(rid, (el_char.length() > 0) ? el_char[0] : 0x2026);
TS->shaped_text_overrun_trim_to_width(rid, width, overrun_flags);
} else {
+ TS->shaped_text_set_custom_ellipsis(rid, (el_char.length() > 0) ? el_char[0] : 0x2026);
TS->shaped_text_overrun_trim_to_width(rid, width, overrun_flags);
}
} else if (alignment == HORIZONTAL_ALIGNMENT_FILL) {
@@ -306,6 +312,23 @@ TextServer::OverrunBehavior TextLine::get_text_overrun_behavior() const {
return overrun_behavior;
}
+void TextLine::set_ellipsis_char(const String &p_char) {
+ String c = p_char;
+ if (c.length() > 1) {
+ WARN_PRINT("Ellipsis must be exactly one character long (" + itos(c.length()) + " characters given).");
+ c = c.left(1);
+ }
+ if (el_char == c) {
+ return;
+ }
+ el_char = c;
+ dirty = true;
+}
+
+String TextLine::get_ellipsis_char() const {
+ return el_char;
+}
+
void TextLine::set_width(float p_width) {
width = p_width;
if (alignment == HORIZONTAL_ALIGNMENT_FILL || overrun_behavior != TextServer::OVERRUN_NO_TRIMMING) {
diff --git a/scene/resources/text_line.h b/scene/resources/text_line.h
index cc52d2e6a7..795bdeb15c 100644
--- a/scene/resources/text_line.h
+++ b/scene/resources/text_line.h
@@ -47,6 +47,7 @@ private:
float width = -1.0;
BitField<TextServer::JustificationFlag> flags = TextServer::JUSTIFICATION_WORD_BOUND | TextServer::JUSTIFICATION_KASHIDA;
HorizontalAlignment alignment = HORIZONTAL_ALIGNMENT_LEFT;
+ String el_char = U"…";
TextServer::OverrunBehavior overrun_behavior = TextServer::OVERRUN_TRIM_ELLIPSIS;
Vector<float> tab_stops;
@@ -90,6 +91,9 @@ public:
void set_text_overrun_behavior(TextServer::OverrunBehavior p_behavior);
TextServer::OverrunBehavior get_text_overrun_behavior() const;
+ void set_ellipsis_char(const String &p_char);
+ String get_ellipsis_char() const;
+
void set_width(float p_width);
float get_width() const;
diff --git a/scene/resources/text_paragraph.cpp b/scene/resources/text_paragraph.cpp
index 147c9044b8..5da47966dd 100644
--- a/scene/resources/text_paragraph.cpp
+++ b/scene/resources/text_paragraph.cpp
@@ -89,6 +89,10 @@ void TextParagraph::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::INT, "text_overrun_behavior", PROPERTY_HINT_ENUM, "Trim Nothing,Trim Characters,Trim Words,Ellipsis,Word Ellipsis"), "set_text_overrun_behavior", "get_text_overrun_behavior");
+ ClassDB::bind_method(D_METHOD("set_ellipsis_char", "char"), &TextParagraph::set_ellipsis_char);
+ ClassDB::bind_method(D_METHOD("get_ellipsis_char"), &TextParagraph::get_ellipsis_char);
+ ADD_PROPERTY(PropertyInfo(Variant::STRING, "ellipsis_char"), "set_ellipsis_char", "get_ellipsis_char");
+
ClassDB::bind_method(D_METHOD("set_width", "width"), &TextParagraph::set_width);
ClassDB::bind_method(D_METHOD("get_width"), &TextParagraph::get_width);
@@ -252,10 +256,12 @@ void TextParagraph::_shape_lines() {
if (i < jst_to_line) {
TS->shaped_text_fit_to_width(lines_rid[i], line_w, jst_flags);
} else if (i == (visible_lines - 1)) {
+ TS->shaped_text_set_custom_ellipsis(lines_rid[visible_lines - 1], (el_char.length() > 0) ? el_char[0] : 0x2026);
TS->shaped_text_overrun_trim_to_width(lines_rid[visible_lines - 1], line_w, overrun_flags);
}
}
} else if (lines_hidden) {
+ TS->shaped_text_set_custom_ellipsis(lines_rid[visible_lines - 1], (el_char.length() > 0) ? el_char[0] : 0x2026);
TS->shaped_text_overrun_trim_to_width(lines_rid[visible_lines - 1], (visible_lines - 1 <= dropcap_lines) ? (width - h_offset) : width, overrun_flags);
}
} else {
@@ -281,9 +287,11 @@ void TextParagraph::_shape_lines() {
if (i < jst_to_line && alignment == HORIZONTAL_ALIGNMENT_FILL) {
TS->shaped_text_fit_to_width(lines_rid[i], line_w, jst_flags);
overrun_flags.set_flag(TextServer::OVERRUN_JUSTIFICATION_AWARE);
+ TS->shaped_text_set_custom_ellipsis(lines_rid[i], (el_char.length() > 0) ? el_char[0] : 0x2026);
TS->shaped_text_overrun_trim_to_width(lines_rid[i], line_w, overrun_flags);
TS->shaped_text_fit_to_width(lines_rid[i], line_w, jst_flags | TextServer::JUSTIFICATION_CONSTRAIN_ELLIPSIS);
} else {
+ TS->shaped_text_set_custom_ellipsis(lines_rid[i], (el_char.length() > 0) ? el_char[0] : 0x2026);
TS->shaped_text_overrun_trim_to_width(lines_rid[i], line_w, overrun_flags);
}
}
@@ -501,6 +509,23 @@ TextServer::OverrunBehavior TextParagraph::get_text_overrun_behavior() const {
return overrun_behavior;
}
+void TextParagraph::set_ellipsis_char(const String &p_char) {
+ String c = p_char;
+ if (c.length() > 1) {
+ WARN_PRINT("Ellipsis must be exactly one character long (" + itos(c.length()) + " characters given).");
+ c = c.left(1);
+ }
+ if (el_char == c) {
+ return;
+ }
+ el_char = c;
+ lines_dirty = true;
+}
+
+String TextParagraph::get_ellipsis_char() const {
+ return el_char;
+}
+
void TextParagraph::set_width(float p_width) {
_THREAD_SAFE_METHOD_
diff --git a/scene/resources/text_paragraph.h b/scene/resources/text_paragraph.h
index 31fc95ac02..28c69967ac 100644
--- a/scene/resources/text_paragraph.h
+++ b/scene/resources/text_paragraph.h
@@ -56,6 +56,7 @@ private:
BitField<TextServer::LineBreakFlag> brk_flags = TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND;
BitField<TextServer::JustificationFlag> jst_flags = TextServer::JUSTIFICATION_WORD_BOUND | TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_SKIP_LAST_LINE | TextServer::JUSTIFICATION_DO_NOT_SKIP_SINGLE_LINE;
+ String el_char = U"…";
TextServer::OverrunBehavior overrun_behavior = TextServer::OVERRUN_NO_TRIMMING;
HorizontalAlignment alignment = HORIZONTAL_ALIGNMENT_LEFT;
@@ -112,6 +113,9 @@ public:
void set_text_overrun_behavior(TextServer::OverrunBehavior p_behavior);
TextServer::OverrunBehavior get_text_overrun_behavior() const;
+ void set_ellipsis_char(const String &p_char);
+ String get_ellipsis_char() const;
+
void set_width(float p_width);
float get_width() const;
diff --git a/scene/resources/tile_set.compat.inc b/scene/resources/tile_set.compat.inc
new file mode 100644
index 0000000000..873ae3aa93
--- /dev/null
+++ b/scene/resources/tile_set.compat.inc
@@ -0,0 +1,48 @@
+/**************************************************************************/
+/* tile_set.compat.inc */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#ifndef DISABLE_DEPRECATED
+
+#include "tile_set.h"
+
+Ref<NavigationPolygon> TileData::_get_navigation_polygon_bind_compat_84660(int p_layer_id) const {
+ return get_navigation_polygon(p_layer_id, false, false, false);
+}
+
+Ref<OccluderPolygon2D> TileData::_get_occluder_bind_compat_84660(int p_layer_id) const {
+ return get_occluder(p_layer_id, false, false, false);
+}
+
+void TileData::_bind_compatibility_methods() {
+ ClassDB::bind_compatibility_method(D_METHOD("get_navigation_polygon"), &TileData::_get_navigation_polygon_bind_compat_84660);
+ ClassDB::bind_compatibility_method(D_METHOD("get_occluder"), &TileData::_get_occluder_bind_compat_84660);
+}
+
+#endif
diff --git a/scene/resources/tile_set.cpp b/scene/resources/tile_set.cpp
index 7b4080517f..2de7cce5c7 100644
--- a/scene/resources/tile_set.cpp
+++ b/scene/resources/tile_set.cpp
@@ -29,6 +29,7 @@
/**************************************************************************/
#include "tile_set.h"
+#include "tile_set.compat.inc"
#include "core/io/marshalls.h"
#include "core/math/geometry_2d.h"
@@ -1839,20 +1840,25 @@ Vector<Vector<Ref<Texture2D>>> TileSet::generate_terrains_icons(Size2i p_size) {
// Generate the icons.
for (int terrain_set = 0; terrain_set < get_terrain_sets_count(); terrain_set++) {
for (int terrain = 0; terrain < get_terrains_count(terrain_set); terrain++) {
- Ref<Image> image;
- image.instantiate();
+ Ref<Image> dst_image;
+ dst_image.instantiate();
if (counts[terrain_set][terrain].count > 0) {
// Get the best tile.
- Ref<Texture2D> texture = counts[terrain_set][terrain].texture;
+ Ref<Texture2D> src_texture = counts[terrain_set][terrain].texture;
+ ERR_FAIL_COND_V(src_texture.is_null(), output);
+ Ref<Image> src_image = src_texture->get_image();
+ ERR_FAIL_COND_V(src_image.is_null(), output);
Rect2i region = counts[terrain_set][terrain].region;
- image->initialize_data(region.size.x, region.size.y, false, Image::FORMAT_RGBA8);
- image->blit_rect(texture->get_image(), region, Point2i());
- image->resize(p_size.x, p_size.y, Image::INTERPOLATE_NEAREST);
+
+ dst_image->initialize_data(region.size.x, region.size.y, false, src_image->get_format());
+ dst_image->blit_rect(src_image, region, Point2i());
+ dst_image->convert(Image::FORMAT_RGBA8);
+ dst_image->resize(p_size.x, p_size.y, Image::INTERPOLATE_NEAREST);
} else {
- image->initialize_data(1, 1, false, Image::FORMAT_RGBA8);
- image->set_pixel(0, 0, get_terrain_color(terrain_set, terrain));
+ dst_image->initialize_data(1, 1, false, Image::FORMAT_RGBA8);
+ dst_image->set_pixel(0, 0, get_terrain_color(terrain_set, terrain));
}
- Ref<ImageTexture> icon = ImageTexture::create_from_image(image);
+ Ref<ImageTexture> icon = ImageTexture::create_from_image(dst_image);
icon->set_size_override(p_size);
output.write[terrain_set].write[terrain] = icon;
}
@@ -5099,7 +5105,7 @@ void TileData::add_occlusion_layer(int p_to_pos) {
p_to_pos = occluders.size();
}
ERR_FAIL_INDEX(p_to_pos, occluders.size() + 1);
- occluders.insert(p_to_pos, Ref<OccluderPolygon2D>());
+ occluders.insert(p_to_pos, OcclusionLayerTileData());
}
void TileData::move_occlusion_layer(int p_from_index, int p_to_pos) {
@@ -5214,7 +5220,7 @@ void TileData::add_navigation_layer(int p_to_pos) {
p_to_pos = navigation.size();
}
ERR_FAIL_INDEX(p_to_pos, navigation.size() + 1);
- navigation.insert(p_to_pos, Ref<NavigationPolygon>());
+ navigation.insert(p_to_pos, NavigationLayerTileData());
}
void TileData::move_navigation_layer(int p_from_index, int p_to_pos) {
@@ -5360,13 +5366,35 @@ int TileData::get_y_sort_origin() const {
void TileData::set_occluder(int p_layer_id, Ref<OccluderPolygon2D> p_occluder_polygon) {
ERR_FAIL_INDEX(p_layer_id, occluders.size());
- occluders.write[p_layer_id] = p_occluder_polygon;
+ occluders.write[p_layer_id].occluder = p_occluder_polygon;
+ occluders.write[p_layer_id].transformed_occluders.clear();
emit_signal(SNAME("changed"));
}
-Ref<OccluderPolygon2D> TileData::get_occluder(int p_layer_id) const {
+Ref<OccluderPolygon2D> TileData::get_occluder(int p_layer_id, bool p_flip_h, bool p_flip_v, bool p_transpose) const {
ERR_FAIL_INDEX_V(p_layer_id, occluders.size(), Ref<OccluderPolygon2D>());
- return occluders[p_layer_id];
+
+ const OcclusionLayerTileData &layer_tile_data = occluders[p_layer_id];
+
+ int key = int(p_flip_h) | int(p_flip_v) << 1 | int(p_transpose) << 2;
+ if (key == 0) {
+ return layer_tile_data.occluder;
+ }
+
+ if (layer_tile_data.occluder.is_null()) {
+ return Ref<OccluderPolygon2D>();
+ }
+
+ HashMap<int, Ref<OccluderPolygon2D>>::Iterator I = layer_tile_data.transformed_occluders.find(key);
+ if (!I) {
+ Ref<OccluderPolygon2D> transformed_polygon;
+ transformed_polygon.instantiate();
+ transformed_polygon->set_polygon(get_transformed_vertices(layer_tile_data.occluder->get_polygon(), p_flip_h, p_flip_v, p_transpose));
+ layer_tile_data.transformed_occluders[key] = transformed_polygon;
+ return transformed_polygon;
+ } else {
+ return I->value;
+ }
}
// Physics
@@ -5426,22 +5454,25 @@ void TileData::set_collision_polygon_points(int p_layer_id, int p_polygon_index,
ERR_FAIL_INDEX(p_polygon_index, physics[p_layer_id].polygons.size());
ERR_FAIL_COND_MSG(p_polygon.size() != 0 && p_polygon.size() < 3, "Invalid polygon. Needs either 0 or more than 3 points.");
+ TileData::PhysicsLayerTileData::PolygonShapeTileData &polygon_shape_tile_data = physics.write[p_layer_id].polygons.write[p_polygon_index];
+
if (p_polygon.is_empty()) {
- physics.write[p_layer_id].polygons.write[p_polygon_index].shapes.clear();
+ polygon_shape_tile_data.shapes.clear();
} else {
// Decompose into convex shapes.
Vector<Vector<Vector2>> decomp = Geometry2D::decompose_polygon_in_convex(p_polygon);
ERR_FAIL_COND_MSG(decomp.is_empty(), "Could not decompose the polygon into convex shapes.");
- physics.write[p_layer_id].polygons.write[p_polygon_index].shapes.resize(decomp.size());
+ polygon_shape_tile_data.shapes.resize(decomp.size());
for (int i = 0; i < decomp.size(); i++) {
Ref<ConvexPolygonShape2D> shape;
shape.instantiate();
shape->set_points(decomp[i]);
- physics.write[p_layer_id].polygons.write[p_polygon_index].shapes[i] = shape;
+ polygon_shape_tile_data.shapes[i] = shape;
}
}
- physics.write[p_layer_id].polygons.write[p_polygon_index].polygon = p_polygon;
+ polygon_shape_tile_data.transformed_shapes.clear();
+ polygon_shape_tile_data.polygon = p_polygon;
emit_signal(SNAME("changed"));
}
@@ -5483,11 +5514,36 @@ int TileData::get_collision_polygon_shapes_count(int p_layer_id, int p_polygon_i
return physics[p_layer_id].polygons[p_polygon_index].shapes.size();
}
-Ref<ConvexPolygonShape2D> TileData::get_collision_polygon_shape(int p_layer_id, int p_polygon_index, int shape_index) const {
+Ref<ConvexPolygonShape2D> TileData::get_collision_polygon_shape(int p_layer_id, int p_polygon_index, int shape_index, bool p_flip_h, bool p_flip_v, bool p_transpose) const {
ERR_FAIL_INDEX_V(p_layer_id, physics.size(), Ref<ConvexPolygonShape2D>());
ERR_FAIL_INDEX_V(p_polygon_index, physics[p_layer_id].polygons.size(), Ref<ConvexPolygonShape2D>());
ERR_FAIL_INDEX_V(shape_index, (int)physics[p_layer_id].polygons[p_polygon_index].shapes.size(), Ref<ConvexPolygonShape2D>());
- return physics[p_layer_id].polygons[p_polygon_index].shapes[shape_index];
+
+ const PhysicsLayerTileData &layer_tile_data = physics[p_layer_id];
+ const PhysicsLayerTileData::PolygonShapeTileData &shapes_data = layer_tile_data.polygons[p_polygon_index];
+
+ int key = int(p_flip_h) | int(p_flip_v) << 1 | int(p_transpose) << 2;
+ if (key == 0) {
+ return shapes_data.shapes[shape_index];
+ }
+ if (shapes_data.shapes[shape_index].is_null()) {
+ return Ref<ConvexPolygonShape2D>();
+ }
+
+ HashMap<int, LocalVector<Ref<ConvexPolygonShape2D>>>::Iterator I = shapes_data.transformed_shapes.find(key);
+ if (!I) {
+ int size = shapes_data.shapes.size();
+ shapes_data.transformed_shapes[key].resize(size);
+ for (int i = 0; i < size; i++) {
+ Ref<ConvexPolygonShape2D> transformed_polygon;
+ transformed_polygon.instantiate();
+ transformed_polygon->set_points(get_transformed_vertices(shapes_data.shapes[shape_index]->get_points(), p_flip_h, p_flip_v, p_transpose));
+ shapes_data.transformed_shapes[key][i] = transformed_polygon;
+ }
+ return shapes_data.transformed_shapes[key][shape_index];
+ } else {
+ return I->value[shape_index];
+ }
}
// Terrain
@@ -5565,13 +5621,50 @@ TileSet::TerrainsPattern TileData::get_terrains_pattern() const {
// Navigation
void TileData::set_navigation_polygon(int p_layer_id, Ref<NavigationPolygon> p_navigation_polygon) {
ERR_FAIL_INDEX(p_layer_id, navigation.size());
- navigation.write[p_layer_id] = p_navigation_polygon;
+ navigation.write[p_layer_id].navigation_polygon = p_navigation_polygon;
+ navigation.write[p_layer_id].transformed_navigation_polygon.clear();
emit_signal(SNAME("changed"));
}
-Ref<NavigationPolygon> TileData::get_navigation_polygon(int p_layer_id) const {
+Ref<NavigationPolygon> TileData::get_navigation_polygon(int p_layer_id, bool p_flip_h, bool p_flip_v, bool p_transpose) const {
ERR_FAIL_INDEX_V(p_layer_id, navigation.size(), Ref<NavigationPolygon>());
- return navigation[p_layer_id];
+
+ const NavigationLayerTileData &layer_tile_data = navigation[p_layer_id];
+
+ int key = int(p_flip_h) | int(p_flip_v) << 1 | int(p_transpose) << 2;
+ if (key == 0) {
+ return layer_tile_data.navigation_polygon;
+ }
+
+ if (layer_tile_data.navigation_polygon.is_null()) {
+ return Ref<NavigationPolygon>();
+ }
+
+ HashMap<int, Ref<NavigationPolygon>>::Iterator I = layer_tile_data.transformed_navigation_polygon.find(key);
+ if (!I) {
+ Ref<NavigationPolygon> transformed_polygon;
+ transformed_polygon.instantiate();
+
+ PackedVector2Array new_points = get_transformed_vertices(layer_tile_data.navigation_polygon->get_vertices(), p_flip_h, p_flip_v, p_transpose);
+ transformed_polygon->set_vertices(new_points);
+
+ for (int i = 0; i < layer_tile_data.navigation_polygon->get_outline_count(); i++) {
+ PackedVector2Array new_outline = get_transformed_vertices(layer_tile_data.navigation_polygon->get_outline(i), p_flip_h, p_flip_v, p_transpose);
+ transformed_polygon->add_outline(new_outline);
+ }
+
+ PackedInt32Array indices;
+ indices.resize(new_points.size());
+ int *w = indices.ptrw();
+ for (int i = 0; i < new_points.size(); i++) {
+ w[i] = i;
+ }
+ transformed_polygon->add_polygon(indices);
+ layer_tile_data.transformed_navigation_polygon[key] = transformed_polygon;
+ return transformed_polygon;
+ } else {
+ return I->value;
+ }
}
// Misc
@@ -5610,6 +5703,33 @@ Variant TileData::get_custom_data_by_layer_id(int p_layer_id) const {
return custom_data[p_layer_id];
}
+PackedVector2Array TileData::get_transformed_vertices(const PackedVector2Array &p_vertices, bool p_flip_h, bool p_flip_v, bool p_transpose) {
+ const Vector2 *r = p_vertices.ptr();
+ int size = p_vertices.size();
+
+ PackedVector2Array new_points;
+ new_points.resize(size);
+ Vector2 *w = new_points.ptrw();
+
+ for (int i = 0; i < size; i++) {
+ Vector2 v;
+ if (p_transpose) {
+ v = Vector2(r[i].y, r[i].x);
+ } else {
+ v = r[i];
+ }
+
+ if (p_flip_h) {
+ v.x *= -1;
+ }
+ if (p_flip_v) {
+ v.y *= -1;
+ }
+ w[i] = v;
+ }
+ return new_points;
+}
+
bool TileData::_set(const StringName &p_name, const Variant &p_value) {
#ifndef DISABLE_DEPRECATED
if (p_name == "texture_offset") {
@@ -5840,7 +5960,7 @@ void TileData::_get_property_list(List<PropertyInfo> *p_list) const {
for (int i = 0; i < occluders.size(); i++) {
// occlusion_layer_%d/polygon
property_info = PropertyInfo(Variant::OBJECT, vformat("occlusion_layer_%d/%s", i, PNAME("polygon")), PROPERTY_HINT_RESOURCE_TYPE, "OccluderPolygon2D", PROPERTY_USAGE_DEFAULT);
- if (!occluders[i].is_valid()) {
+ if (occluders[i].occluder.is_null()) {
property_info.usage ^= PROPERTY_USAGE_STORAGE;
}
p_list->push_back(property_info);
@@ -5896,7 +6016,7 @@ void TileData::_get_property_list(List<PropertyInfo> *p_list) const {
p_list->push_back(PropertyInfo(Variant::NIL, GNAME("Navigation", ""), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_GROUP));
for (int i = 0; i < navigation.size(); i++) {
property_info = PropertyInfo(Variant::OBJECT, vformat("navigation_layer_%d/%s", i, PNAME("polygon")), PROPERTY_HINT_RESOURCE_TYPE, "NavigationPolygon", PROPERTY_USAGE_DEFAULT);
- if (!navigation[i].is_valid()) {
+ if (navigation[i].navigation_polygon.is_null()) {
property_info.usage ^= PROPERTY_USAGE_STORAGE;
}
p_list->push_back(property_info);
@@ -5937,7 +6057,7 @@ void TileData::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_y_sort_origin"), &TileData::get_y_sort_origin);
ClassDB::bind_method(D_METHOD("set_occluder", "layer_id", "occluder_polygon"), &TileData::set_occluder);
- ClassDB::bind_method(D_METHOD("get_occluder", "layer_id"), &TileData::get_occluder);
+ ClassDB::bind_method(D_METHOD("get_occluder", "layer_id", "flip_h", "flip_v", "transpose"), &TileData::get_occluder, DEFVAL(false), DEFVAL(false), DEFVAL(false));
// Physics.
ClassDB::bind_method(D_METHOD("set_constant_linear_velocity", "layer_id", "velocity"), &TileData::set_constant_linear_velocity);
@@ -5965,7 +6085,7 @@ void TileData::_bind_methods() {
// Navigation
ClassDB::bind_method(D_METHOD("set_navigation_polygon", "layer_id", "navigation_polygon"), &TileData::set_navigation_polygon);
- ClassDB::bind_method(D_METHOD("get_navigation_polygon", "layer_id"), &TileData::get_navigation_polygon);
+ ClassDB::bind_method(D_METHOD("get_navigation_polygon", "layer_id", "flip_h", "flip_v", "transpose"), &TileData::get_navigation_polygon, DEFVAL(false), DEFVAL(false), DEFVAL(false));
// Misc.
ClassDB::bind_method(D_METHOD("set_probability", "probability"), &TileData::set_probability);
diff --git a/scene/resources/tile_set.h b/scene/resources/tile_set.h
index 313c4df65d..a71982cd56 100644
--- a/scene/resources/tile_set.h
+++ b/scene/resources/tile_set.h
@@ -815,13 +815,18 @@ private:
Color modulate = Color(1.0, 1.0, 1.0, 1.0);
int z_index = 0;
int y_sort_origin = 0;
- Vector<Ref<OccluderPolygon2D>> occluders;
+ struct OcclusionLayerTileData {
+ Ref<OccluderPolygon2D> occluder;
+ mutable HashMap<int, Ref<OccluderPolygon2D>> transformed_occluders;
+ };
+ Vector<OcclusionLayerTileData> occluders;
// Physics
struct PhysicsLayerTileData {
struct PolygonShapeTileData {
LocalVector<Vector2> polygon;
LocalVector<Ref<ConvexPolygonShape2D>> shapes;
+ mutable HashMap<int, LocalVector<Ref<ConvexPolygonShape2D>>> transformed_shapes;
bool one_way = false;
float one_way_margin = 1.0;
};
@@ -839,7 +844,11 @@ private:
int terrain_peering_bits[16] = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 };
// Navigation
- Vector<Ref<NavigationPolygon>> navigation;
+ struct NavigationLayerTileData {
+ Ref<NavigationPolygon> navigation_polygon;
+ mutable HashMap<int, Ref<NavigationPolygon>> transformed_navigation_polygon;
+ };
+ Vector<NavigationLayerTileData> navigation;
// Misc
double probability = 1.0;
@@ -853,6 +862,13 @@ protected:
void _get_property_list(List<PropertyInfo> *p_list) const;
static void _bind_methods();
+#ifndef DISABLE_DEPRECATED
+ Ref<NavigationPolygon> _get_navigation_polygon_bind_compat_84660(int p_layer_id) const;
+ Ref<OccluderPolygon2D> _get_occluder_bind_compat_84660(int p_layer_id) const;
+
+ static void _bind_compatibility_methods();
+#endif
+
public:
// Not exposed.
void set_tile_set(const TileSet *p_tile_set);
@@ -901,7 +917,7 @@ public:
int get_y_sort_origin() const;
void set_occluder(int p_layer_id, Ref<OccluderPolygon2D> p_occluder_polygon);
- Ref<OccluderPolygon2D> get_occluder(int p_layer_id) const;
+ Ref<OccluderPolygon2D> get_occluder(int p_layer_id, bool p_flip_h = false, bool p_flip_v = false, bool p_transpose = false) const;
// Physics
void set_constant_linear_velocity(int p_layer_id, const Vector2 &p_velocity);
@@ -919,7 +935,7 @@ public:
void set_collision_polygon_one_way_margin(int p_layer_id, int p_polygon_index, float p_one_way_margin);
float get_collision_polygon_one_way_margin(int p_layer_id, int p_polygon_index) const;
int get_collision_polygon_shapes_count(int p_layer_id, int p_polygon_index) const;
- Ref<ConvexPolygonShape2D> get_collision_polygon_shape(int p_layer_id, int p_polygon_index, int shape_index) const;
+ Ref<ConvexPolygonShape2D> get_collision_polygon_shape(int p_layer_id, int p_polygon_index, int shape_index, bool p_flip_h = false, bool p_flip_v = false, bool p_transpose = false) const;
// Terrain
void set_terrain_set(int p_terrain_id);
@@ -934,7 +950,7 @@ public:
// Navigation
void set_navigation_polygon(int p_layer_id, Ref<NavigationPolygon> p_navigation_polygon);
- Ref<NavigationPolygon> get_navigation_polygon(int p_layer_id) const;
+ Ref<NavigationPolygon> get_navigation_polygon(int p_layer_id, bool p_flip_h = false, bool p_flip_v = false, bool p_transpose = false) const;
// Misc
void set_probability(float p_probability);
@@ -945,6 +961,9 @@ public:
Variant get_custom_data(String p_layer_name) const;
void set_custom_data_by_layer_id(int p_layer_id, Variant p_value);
Variant get_custom_data_by_layer_id(int p_layer_id) const;
+
+ // Polygons.
+ static PackedVector2Array get_transformed_vertices(const PackedVector2Array &p_vertices, bool p_flip_h, bool p_flip_v, bool p_transpose);
};
VARIANT_ENUM_CAST(TileSet::CellNeighbor);
diff --git a/scene/resources/video_stream.cpp b/scene/resources/video_stream.cpp
index dc8545426f..3b152d11ce 100644
--- a/scene/resources/video_stream.cpp
+++ b/scene/resources/video_stream.cpp
@@ -172,6 +172,7 @@ Ref<VideoStreamPlayback> VideoStream::instantiate_playback() {
void VideoStream::set_file(const String &p_file) {
file = p_file;
+ emit_changed();
}
String VideoStream::get_file() {
diff --git a/scene/resources/visual_shader.cpp b/scene/resources/visual_shader.cpp
index 423ef3867f..9fb6b364a7 100644
--- a/scene/resources/visual_shader.cpp
+++ b/scene/resources/visual_shader.cpp
@@ -962,12 +962,13 @@ void VisualShader::remove_node(Type p_type, int p_id) {
for (List<Connection>::Element *E = g->connections.front(); E;) {
List<Connection>::Element *N = E->next();
- if (E->get().from_node == p_id || E->get().to_node == p_id) {
- if (E->get().from_node == p_id) {
- g->nodes[E->get().to_node].prev_connected_nodes.erase(p_id);
- g->nodes[E->get().to_node].node->set_input_port_connected(E->get().to_port, false);
- } else if (E->get().to_node == p_id) {
- g->nodes[E->get().from_node].next_connected_nodes.erase(p_id);
+ const VisualShader::Connection &connection = E->get();
+ if (connection.from_node == p_id || connection.to_node == p_id) {
+ if (connection.from_node == p_id) {
+ g->nodes[connection.to_node].prev_connected_nodes.erase(p_id);
+ g->nodes[connection.to_node].node->set_input_port_connected(connection.to_port, false);
+ } else if (connection.to_node == p_id) {
+ g->nodes[connection.from_node].next_connected_nodes.erase(p_id);
}
g->connections.erase(E);
}