diff options
Diffstat (limited to 'scene')
134 files changed, 3350 insertions, 1416 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/cpu_particles_2d.cpp b/scene/2d/cpu_particles_2d.cpp index 1c193f991e..e04e6d7dce 100644 --- a/scene/2d/cpu_particles_2d.cpp +++ b/scene/2d/cpu_particles_2d.cpp @@ -881,7 +881,7 @@ void CPUParticles2D::_particles_process(double p_delta) { force += diff.length() > 0.0 ? diff.normalized() * (tex_radial_accel)*Math::lerp(parameters_min[PARAM_RADIAL_ACCEL], parameters_max[PARAM_RADIAL_ACCEL], rand_from_seed(alt_seed)) : Vector2(); //apply tangential acceleration; Vector2 yx = Vector2(diff.y, diff.x); - force += yx.length() > 0.0 ? yx.normalized() * (tex_tangential_accel * Math::lerp(parameters_min[PARAM_TANGENTIAL_ACCEL], parameters_max[PARAM_TANGENTIAL_ACCEL], rand_from_seed(alt_seed))) : Vector2(); + force += yx.length() > 0.0 ? (yx * Vector2(-1.0, 1.0)).normalized() * (tex_tangential_accel * Math::lerp(parameters_min[PARAM_TANGENTIAL_ACCEL], parameters_max[PARAM_TANGENTIAL_ACCEL], rand_from_seed(alt_seed))) : Vector2(); //apply attractor forces p.velocity += force * local_delta; //orbit velocity 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 baa3b0bb90..6af5a8dd80 100644 --- a/scene/2d/physics_body_2d.cpp +++ b/scene/2d/physics_body_2d.cpp @@ -431,7 +431,9 @@ struct _RigidBody2DInOut { void RigidBody2D::_sync_body_state(PhysicsDirectBodyState2D *p_state) { if (!freeze || freeze_mode != FREEZE_MODE_KINEMATIC) { + set_block_transform_notify(true); set_global_transform(p_state->get_transform()); + set_block_transform_notify(false); } linear_velocity = p_state->get_linear_velocity(); @@ -446,16 +448,20 @@ void RigidBody2D::_sync_body_state(PhysicsDirectBodyState2D *p_state) { void RigidBody2D::_body_state_changed(PhysicsDirectBodyState2D *p_state) { lock_callback(); - set_block_transform_notify(true); // don't want notify (would feedback loop) - 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(); + + 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); - set_block_transform_notify(false); // want it back if (contact_monitor) { contact_monitor->locked = true; 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/skeleton_2d.cpp b/scene/2d/skeleton_2d.cpp index 350fdad7d7..527bbaf956 100644 --- a/scene/2d/skeleton_2d.cpp +++ b/scene/2d/skeleton_2d.cpp @@ -48,12 +48,14 @@ bool Bone2D::_set(const StringName &p_path, const Variant &p_value) { } else if (path.begins_with("default_length")) { set_length(p_value); } - #ifdef TOOLS_ENABLED - if (path.begins_with("editor_settings/show_bone_gizmo")) { + else if (path.begins_with("editor_settings/show_bone_gizmo")) { _editor_set_show_bone_gizmo(p_value); } #endif // TOOLS_ENABLED + else { + return false; + } return true; } @@ -70,12 +72,14 @@ bool Bone2D::_get(const StringName &p_path, Variant &r_ret) const { } else if (path.begins_with("default_length")) { r_ret = get_length(); } - #ifdef TOOLS_ENABLED - if (path.begins_with("editor_settings/show_bone_gizmo")) { + else if (path.begins_with("editor_settings/show_bone_gizmo")) { r_ret = _editor_get_show_bone_gizmo(); } #endif // TOOLS_ENABLED + else { + return false; + } return true; } @@ -542,7 +546,7 @@ void Skeleton2D::_get_property_list(List<PropertyInfo> *p_list) const { PropertyInfo(Variant::OBJECT, PNAME("modification_stack"), PROPERTY_HINT_RESOURCE_TYPE, "SkeletonModificationStack2D", - PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_DEFERRED_SET_RESOURCE | PROPERTY_USAGE_ALWAYS_DUPLICATE)); + PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_ALWAYS_DUPLICATE)); } void Skeleton2D::_make_bone_setup_dirty() { 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 6eca32fbb6..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); @@ -472,7 +475,13 @@ void TileMapLayer::_rendering_quadrants_update_cell(CellData &r_cell_data, SelfL TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source); if (atlas_source && atlas_source->has_tile(r_cell_data.cell.get_atlas_coords()) && atlas_source->has_alternative_tile(r_cell_data.cell.get_atlas_coords(), r_cell_data.cell.alternative_tile)) { is_valid = true; - tile_y_sort_origin = atlas_source->get_tile_data(r_cell_data.cell.get_atlas_coords(), r_cell_data.cell.alternative_tile)->get_y_sort_origin(); + const TileData *tile_data; + if (r_cell_data.runtime_tile_data_cache) { + tile_data = r_cell_data.runtime_tile_data_cache; + } else { + tile_data = atlas_source->get_tile_data(r_cell_data.cell.get_atlas_coords(), r_cell_data.cell.alternative_tile); + } + tile_y_sort_origin = tile_data->get_y_sort_origin(); } } @@ -559,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); @@ -574,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(); + } } } @@ -652,7 +687,7 @@ void TileMapLayer::_physics_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. for (KeyValue<Vector2i, CellData> &kv : tile_map) { @@ -680,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(); @@ -687,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; @@ -694,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; @@ -708,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); } } } @@ -752,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); @@ -762,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); @@ -816,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); @@ -902,7 +960,7 @@ void TileMapLayer::_navigation_update() { NavigationServer2D *ns = NavigationServer2D::get_singleton(); // Check if we should cleanup everything. - bool forced_cleanup = in_destructor || !enabled || !navigation_enabled || !tile_map_node->is_inside_tree() || !tile_set.is_valid() || !tile_map_node->is_visible_in_tree(); + bool forced_cleanup = in_destructor || !enabled || !navigation_enabled || !tile_map_node->is_inside_tree() || !tile_set.is_valid(); // ----------- Layer level processing ----------- if (forced_cleanup) { @@ -973,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 ®ion = r_cell_data.navigation_regions[i]; if (region.is_valid()) { ns->region_set_map(region, RID()); @@ -1005,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 ®ion = r_cell_data.navigation_regions[i]; if (region.is_valid()) { ns->region_set_map(region, RID()); @@ -1017,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 ®ion = r_cell_data.navigation_regions[navigation_layer_index]; @@ -1113,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; @@ -1166,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. @@ -2492,6 +2555,11 @@ Vector2i TileMapLayer::get_coords_for_body_rid(RID p_physics_body) const { } TileMapLayer::~TileMapLayer() { + if (!tile_map_node) { + // Temporary layer. + return; + } + in_destructor = true; clear(); internal_update(); @@ -3067,7 +3135,6 @@ void TileMap::_internal_update() { } // Update dirty quadrants on layers. - polygon_cache.clear(); for (Ref<TileMapLayer> &layer : layers) { layer->internal_update(); } @@ -3216,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(); @@ -3235,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(); @@ -3255,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(); @@ -3492,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; } @@ -3542,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; } @@ -3551,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") { @@ -3589,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; @@ -3604,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; @@ -3664,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; @@ -3704,16 +3750,88 @@ bool TileMap::_get(const StringName &p_name, Variant &r_ret) const { void TileMap::_get_property_list(List<PropertyInfo> *p_list) const { p_list->push_back(PropertyInfo(Variant::INT, "format", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL)); p_list->push_back(PropertyInfo(Variant::NIL, "Layers", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_GROUP)); - for (unsigned int i = 0; i < layers.size(); i++) { - p_list->push_back(PropertyInfo(Variant::STRING, vformat("layer_%d/name", i), PROPERTY_HINT_NONE)); - p_list->push_back(PropertyInfo(Variant::BOOL, vformat("layer_%d/enabled", i), PROPERTY_HINT_NONE)); - p_list->push_back(PropertyInfo(Variant::COLOR, vformat("layer_%d/modulate", i), PROPERTY_HINT_NONE)); - p_list->push_back(PropertyInfo(Variant::BOOL, vformat("layer_%d/y_sort_enabled", i), PROPERTY_HINT_NONE)); - p_list->push_back(PropertyInfo(Variant::INT, vformat("layer_%d/y_sort_origin", i), PROPERTY_HINT_NONE, "suffix:px")); - p_list->push_back(PropertyInfo(Variant::INT, vformat("layer_%d/z_index", i), PROPERTY_HINT_NONE)); - p_list->push_back(PropertyInfo(Variant::BOOL, vformat("layer_%d/navigation_enabled", i), PROPERTY_HINT_NONE)); + +#define MAKE_LAYER_PROPERTY(m_type, m_name, m_hint) \ + { \ + const String property_name = vformat("layer_%d/" m_name, i); \ + 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 (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", ""); + MAKE_LAYER_PROPERTY(Variant::BOOL, "y_sort_enabled", ""); + MAKE_LAYER_PROPERTY(Variant::INT, "y_sort_origin", "suffix:px"); + MAKE_LAYER_PROPERTY(Variant::INT, "z_index", ""); + MAKE_LAYER_PROPERTY(Variant::BOOL, "navigation_enabled", ""); p_list->push_back(PropertyInfo(Variant::OBJECT, vformat("layer_%d/tile_data", i), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR)); } + +#undef MAKE_LAYER_PROPERTY +} + +bool TileMap::_property_can_revert(const StringName &p_name) const { + Vector<String> components = String(p_name).split("/", true, 2); + if (components.size() == 2 && components[0].begins_with("layer_")) { + int index = components[0].trim_prefix("layer_").to_int(); + if (index <= 0 || index >= (int)layers.size()) { + return false; + } + + if (components[1] == "name") { + return layers[index]->get_name() != default_layer->get_name(); + } else if (components[1] == "enabled") { + return layers[index]->is_enabled() != default_layer->is_enabled(); + } else if (components[1] == "modulate") { + return layers[index]->get_modulate() != default_layer->get_modulate(); + } else if (components[1] == "y_sort_enabled") { + return layers[index]->is_y_sort_enabled() != default_layer->is_y_sort_enabled(); + } else if (components[1] == "y_sort_origin") { + return layers[index]->get_y_sort_origin() != default_layer->get_y_sort_origin(); + } else if (components[1] == "z_index") { + return layers[index]->get_z_index() != default_layer->get_z_index(); + } else if (components[1] == "navigation_enabled") { + return layers[index]->is_navigation_enabled() != default_layer->is_navigation_enabled(); + } + } + + return false; +} + +bool TileMap::_property_get_revert(const StringName &p_name, Variant &r_property) const { + Vector<String> components = String(p_name).split("/", true, 2); + if (components.size() == 2 && components[0].begins_with("layer_")) { + int index = components[0].trim_prefix("layer_").to_int(); + if (index <= 0 || index >= (int)layers.size()) { + return false; + } + + if (components[1] == "name") { + r_property = default_layer->get_name(); + return true; + } else if (components[1] == "enabled") { + r_property = default_layer->is_enabled(); + return true; + } else if (components[1] == "modulate") { + r_property = default_layer->get_modulate(); + return true; + } else if (components[1] == "y_sort_enabled") { + r_property = default_layer->is_y_sort_enabled(); + return true; + } else if (components[1] == "y_sort_origin") { + r_property = default_layer->get_y_sort_origin(); + return true; + } else if (components[1] == "z_index") { + r_property = default_layer->get_z_index(); + return true; + } else if (components[1] == "navigation_enabled") { + r_property = default_layer->is_navigation_enabled(); + return true; + } + } + + return false; } Vector2 TileMap::map_to_local(const Vector2i &p_pos) const { @@ -4498,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(); @@ -4737,6 +4804,8 @@ TileMap::TileMap() { new_layer->set_tile_map(this); new_layer->set_layer_index_in_tile_map_node(0); layers.push_back(new_layer); + + default_layer.instantiate(); } TileMap::~TileMap() { diff --git a/scene/2d/tile_map.h b/scene/2d/tile_map.h index a16595629c..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; @@ -461,6 +461,7 @@ private: // Layers. LocalVector<Ref<TileMapLayer>> layers; + Ref<TileMapLayer> default_layer; // Dummy layer to fetch default values. int selected_layer = -1; bool pending_update = false; @@ -471,14 +472,12 @@ 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; void _get_property_list(List<PropertyInfo> *p_list) const; + bool _property_can_revert(const StringName &p_name) const; + bool _property_get_revert(const StringName &p_name, Variant &r_property) const; void _notification(int p_what); static void _bind_methods(); @@ -616,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/bone_attachment_3d.cpp b/scene/3d/bone_attachment_3d.cpp index 3b30bbf8b6..45de9b907c 100644 --- a/scene/3d/bone_attachment_3d.cpp +++ b/scene/3d/bone_attachment_3d.cpp @@ -333,7 +333,7 @@ void BoneAttachment3D::on_bone_pose_update(int p_bone_index) { } } #ifdef TOOLS_ENABLED -void BoneAttachment3D::_notify_skeleton_bones_renamed(Node *p_base_scene, Skeleton3D *p_skeleton, Dictionary p_rename_map) { +void BoneAttachment3D::notify_skeleton_bones_renamed(Node *p_base_scene, Skeleton3D *p_skeleton, Dictionary p_rename_map) { const Skeleton3D *parent = nullptr; if (use_external_skeleton) { if (external_skeleton_node_cache.is_valid()) { @@ -349,6 +349,16 @@ void BoneAttachment3D::_notify_skeleton_bones_renamed(Node *p_base_scene, Skelet } } } + +void BoneAttachment3D::notify_rebind_required() { + // Ensures bindings are properly updated after a scene reload. + _check_unbind(); + if (use_external_skeleton) { + _update_external_skeleton_cache(); + } + bone_idx = -1; + _check_bind(); +} #endif // TOOLS_ENABLED BoneAttachment3D::BoneAttachment3D() { @@ -370,9 +380,6 @@ void BoneAttachment3D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_use_external_skeleton"), &BoneAttachment3D::get_use_external_skeleton); ClassDB::bind_method(D_METHOD("set_external_skeleton", "external_skeleton"), &BoneAttachment3D::set_external_skeleton); ClassDB::bind_method(D_METHOD("get_external_skeleton"), &BoneAttachment3D::get_external_skeleton); -#ifdef TOOLS_ENABLED - ClassDB::bind_method(D_METHOD("_notify_skeleton_bones_renamed"), &BoneAttachment3D::_notify_skeleton_bones_renamed); -#endif // TOOLS_ENABLED ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "bone_name"), "set_bone_name", "get_bone_name"); ADD_PROPERTY(PropertyInfo(Variant::INT, "bone_idx"), "set_bone_idx", "get_bone_idx"); diff --git a/scene/3d/bone_attachment_3d.h b/scene/3d/bone_attachment_3d.h index 327cbaa0ab..1bf44c2756 100644 --- a/scene/3d/bone_attachment_3d.h +++ b/scene/3d/bone_attachment_3d.h @@ -65,11 +65,12 @@ protected: void _notification(int p_what); static void _bind_methods(); + +public: #ifdef TOOLS_ENABLED - virtual void _notify_skeleton_bones_renamed(Node *p_base_scene, Skeleton3D *p_skeleton, Dictionary p_rename_map); + virtual void notify_skeleton_bones_renamed(Node *p_base_scene, Skeleton3D *p_skeleton, Dictionary p_rename_map); #endif // TOOLS_ENABLED -public: virtual PackedStringArray get_configuration_warnings() const override; void set_bone_name(const String &p_name); @@ -88,6 +89,10 @@ public: virtual void on_bone_pose_update(int p_bone_index); +#ifdef TOOLS_ENABLED + virtual void notify_rebind_required(); +#endif + BoneAttachment3D(); }; diff --git a/scene/3d/camera_3d.cpp b/scene/3d/camera_3d.cpp index 37ceb9d1a1..8dc17a1c62 100644 --- a/scene/3d/camera_3d.cpp +++ b/scene/3d/camera_3d.cpp @@ -557,7 +557,7 @@ void Camera3D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::INT, "projection", PROPERTY_HINT_ENUM, "Perspective,Orthogonal,Frustum"), "set_projection", "get_projection"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "current"), "set_current", "is_current"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "fov", PROPERTY_HINT_RANGE, "1,179,0.1,degrees"), "set_fov", "get_fov"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "size", PROPERTY_HINT_RANGE, "0.001,16384,0.001,or_greater,suffix:m"), "set_size", "get_size"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "size", PROPERTY_HINT_RANGE, "0.001,100,0.001,or_greater,suffix:m"), "set_size", "get_size"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "frustum_offset", PROPERTY_HINT_NONE, "suffix:m"), "set_frustum_offset", "get_frustum_offset"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "near", PROPERTY_HINT_RANGE, "0.001,10,0.001,or_greater,exp,suffix:m"), "set_near", "get_near"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "far", PROPERTY_HINT_RANGE, "0.01,4000,0.01,or_greater,exp,suffix:m"), "set_far", "get_far"); 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/decal.cpp b/scene/3d/decal.cpp index fecf70fe5d..6878df21d8 100644 --- a/scene/3d/decal.cpp +++ b/scene/3d/decal.cpp @@ -225,10 +225,10 @@ void Decal::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "size", PROPERTY_HINT_RANGE, "0,1024,0.001,or_greater,suffix:m"), "set_size", "get_size"); ADD_GROUP("Textures", "texture_"); - ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "texture_albedo", PROPERTY_HINT_RESOURCE_TYPE, "Texture"), "set_texture", "get_texture", TEXTURE_ALBEDO); - ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "texture_normal", PROPERTY_HINT_RESOURCE_TYPE, "Texture"), "set_texture", "get_texture", TEXTURE_NORMAL); - ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "texture_orm", PROPERTY_HINT_RESOURCE_TYPE, "Texture"), "set_texture", "get_texture", TEXTURE_ORM); - ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "texture_emission", PROPERTY_HINT_RESOURCE_TYPE, "Texture"), "set_texture", "get_texture", TEXTURE_EMISSION); + ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "texture_albedo", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_texture", "get_texture", TEXTURE_ALBEDO); + ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "texture_normal", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_texture", "get_texture", TEXTURE_NORMAL); + ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "texture_orm", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_texture", "get_texture", TEXTURE_ORM); + ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "texture_emission", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_texture", "get_texture", TEXTURE_EMISSION); ADD_GROUP("Parameters", ""); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "emission_energy", PROPERTY_HINT_RANGE, "0,128,0.01"), "set_emission_energy", "get_emission_energy"); 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 5175363538..76933cd956 100644 --- a/scene/3d/lightmap_gi.cpp +++ b/scene/3d/lightmap_gi.cpp @@ -97,11 +97,16 @@ Array LightmapGIData::_get_user_data() const { return ret; } -void LightmapGIData::_set_light_textures_data(const Array &p_data) { - ERR_FAIL_COND(p_data.is_empty()); +void LightmapGIData::set_lightmap_textures(const TypedArray<TextureLayered> &p_data) { + light_textures = p_data; + if (p_data.is_empty()) { + light_texture = Ref<TextureLayered>(); + _reset_lightmap_textures(); + return; + } if (p_data.size() == 1) { - set_light_texture(p_data[0]); + light_texture = p_data[0]; } else { Vector<Ref<Image>> images; for (int i = 0; i < p_data.size(); i++) { @@ -116,73 +121,13 @@ void LightmapGIData::_set_light_textures_data(const Array &p_data) { combined_texture.instantiate(); combined_texture->create_from_images(images); - set_light_texture(combined_texture); + light_texture = combined_texture; } + _reset_lightmap_textures(); } -Array LightmapGIData::_get_light_textures_data() const { - Array ret; - if (light_texture.is_null() || light_texture->get_layers() == 0) { - return ret; - } - - Vector<Ref<Image>> images; - for (int i = 0; i < light_texture->get_layers(); i++) { - images.push_back(light_texture->get_layer_data(i)); - } - - int slice_count = images.size(); - int slice_width = images[0]->get_width(); - int slice_height = images[0]->get_height(); - - int slices_per_texture = Image::MAX_HEIGHT / slice_height; - int texture_count = Math::ceil(slice_count / (float)slices_per_texture); - - ret.resize(texture_count); - - String base_name = get_path().get_basename(); - - int last_count = slice_count % slices_per_texture; - for (int i = 0; i < texture_count; i++) { - int texture_slice_count = (i == texture_count - 1 && last_count != 0) ? last_count : slices_per_texture; - - Ref<Image> texture_image = Image::create_empty(slice_width, slice_height * texture_slice_count, false, images[0]->get_format()); - - for (int j = 0; j < texture_slice_count; j++) { - texture_image->blit_rect(images[i * slices_per_texture + j], Rect2i(0, 0, slice_width, slice_height), Point2i(0, slice_height * j)); - } - - String texture_path = texture_count > 1 ? base_name + "_" + itos(i) + ".exr" : base_name + ".exr"; - - Ref<ConfigFile> config; - config.instantiate(); - - if (FileAccess::exists(texture_path + ".import")) { - config->load(texture_path + ".import"); - } - - config->set_value("remap", "importer", "2d_array_texture"); - config->set_value("remap", "type", "CompressedTexture2DArray"); - if (!config->has_section_key("params", "compress/mode")) { - // User may want another compression, so leave it be, but default to VRAM uncompressed. - config->set_value("params", "compress/mode", 3); - } - config->set_value("params", "compress/channel_pack", 1); - config->set_value("params", "mipmaps/generate", false); - config->set_value("params", "slices/horizontal", 1); - config->set_value("params", "slices/vertical", texture_slice_count); - - config->save(texture_path + ".import"); - - Error err = texture_image->save_exr(texture_path, false); - ERR_FAIL_COND_V(err, ret); - ResourceLoader::import(texture_path); - Ref<TextureLayered> t = ResourceLoader::load(texture_path); //if already loaded, it will be updated on refocus? - ERR_FAIL_COND_V(t.is_null(), ret); - ret[i] = t; - } - - return ret; +TypedArray<TextureLayered> LightmapGIData::get_lightmap_textures() const { + return light_textures; } RID LightmapGIData::get_rid() const { @@ -193,18 +138,13 @@ void LightmapGIData::clear() { users.clear(); } -void LightmapGIData::set_light_texture(const Ref<TextureLayered> &p_light_texture) { - light_texture = p_light_texture; +void LightmapGIData::_reset_lightmap_textures() { RS::get_singleton()->lightmap_set_textures(lightmap, light_texture.is_valid() ? light_texture->get_rid() : RID(), uses_spherical_harmonics); } -Ref<TextureLayered> LightmapGIData::get_light_texture() const { - return light_texture; -} - void LightmapGIData::set_uses_spherical_harmonics(bool p_enable) { uses_spherical_harmonics = p_enable; - RS::get_singleton()->lightmap_set_textures(lightmap, light_texture.is_valid() ? light_texture->get_rid() : RID(), uses_spherical_harmonics); + _reset_lightmap_textures(); } bool LightmapGIData::is_using_spherical_harmonics() const { @@ -282,15 +222,35 @@ Dictionary LightmapGIData::_get_probe_data() const { return d; } +#ifndef DISABLE_DEPRECATED +void LightmapGIData::set_light_texture(const Ref<TextureLayered> &p_light_texture) { + TypedArray<TextureLayered> arr; + arr.append(p_light_texture); + set_lightmap_textures(arr); +} + +Ref<TextureLayered> LightmapGIData::get_light_texture() const { + if (light_textures.is_empty()) { + return Ref<TextureLayered>(); + } + return light_textures.get(0); +} + +void LightmapGIData::_set_light_textures_data(const Array &p_data) { + set_lightmap_textures(p_data); +} + +Array LightmapGIData::_get_light_textures_data() const { + return Array(light_textures); +} +#endif + void LightmapGIData::_bind_methods() { ClassDB::bind_method(D_METHOD("_set_user_data", "data"), &LightmapGIData::_set_user_data); ClassDB::bind_method(D_METHOD("_get_user_data"), &LightmapGIData::_get_user_data); - ClassDB::bind_method(D_METHOD("set_light_texture", "light_texture"), &LightmapGIData::set_light_texture); - ClassDB::bind_method(D_METHOD("get_light_texture"), &LightmapGIData::get_light_texture); - - ClassDB::bind_method(D_METHOD("_set_light_textures_data", "data"), &LightmapGIData::_set_light_textures_data); - ClassDB::bind_method(D_METHOD("_get_light_textures_data"), &LightmapGIData::_get_light_textures_data); + ClassDB::bind_method(D_METHOD("set_lightmap_textures", "light_textures"), &LightmapGIData::set_lightmap_textures); + ClassDB::bind_method(D_METHOD("get_lightmap_textures"), &LightmapGIData::get_lightmap_textures); ClassDB::bind_method(D_METHOD("set_uses_spherical_harmonics", "uses_spherical_harmonics"), &LightmapGIData::set_uses_spherical_harmonics); ClassDB::bind_method(D_METHOD("is_using_spherical_harmonics"), &LightmapGIData::is_using_spherical_harmonics); @@ -303,11 +263,21 @@ void LightmapGIData::_bind_methods() { ClassDB::bind_method(D_METHOD("_set_probe_data", "data"), &LightmapGIData::_set_probe_data); ClassDB::bind_method(D_METHOD("_get_probe_data"), &LightmapGIData::_get_probe_data); - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "light_texture", PROPERTY_HINT_RESOURCE_TYPE, "TextureLayered", PROPERTY_USAGE_EDITOR), "set_light_texture", "get_light_texture"); // property usage default but no save - ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "light_textures", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_light_textures_data", "_get_light_textures_data"); + ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "lightmap_textures", PROPERTY_HINT_ARRAY_TYPE, "TextureLayered", PROPERTY_USAGE_NO_EDITOR), "set_lightmap_textures", "get_lightmap_textures"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "uses_spherical_harmonics", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "set_uses_spherical_harmonics", "is_using_spherical_harmonics"); ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "user_data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_user_data", "_get_user_data"); ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "probe_data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_probe_data", "_get_probe_data"); + +#ifndef DISABLE_DEPRECATED + ClassDB::bind_method(D_METHOD("set_light_texture", "light_texture"), &LightmapGIData::set_light_texture); + ClassDB::bind_method(D_METHOD("get_light_texture"), &LightmapGIData::get_light_texture); + + ClassDB::bind_method(D_METHOD("_set_light_textures_data", "data"), &LightmapGIData::_set_light_textures_data); + ClassDB::bind_method(D_METHOD("_get_light_textures_data"), &LightmapGIData::_get_light_textures_data); + + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "light_texture", PROPERTY_HINT_RESOURCE_TYPE, "TextureLayered", PROPERTY_USAGE_EDITOR), "set_light_texture", "get_light_texture"); + ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "light_textures", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_light_textures_data", "_get_light_textures_data"); +#endif } LightmapGIData::LightmapGIData() { @@ -1099,6 +1069,68 @@ LightmapGI::BakeError LightmapGI::bake(Node *p_from_node, String p_image_data_pa return BAKE_ERROR_MESHES_INVALID; } + // POSTBAKE: Save Textures. + + TypedArray<TextureLayered> textures; + { + Vector<Ref<Image>> images; + images.resize(lightmapper->get_bake_texture_count()); + for (int i = 0; i < images.size(); i++) { + images.set(i, lightmapper->get_bake_texture(i)); + } + + int slice_count = images.size(); + int slice_width = images[0]->get_width(); + int slice_height = images[0]->get_height(); + + int slices_per_texture = Image::MAX_HEIGHT / slice_height; + int texture_count = Math::ceil(slice_count / (float)slices_per_texture); + + textures.resize(texture_count); + + String base_path = p_image_data_path.get_basename(); + + int last_count = slice_count % slices_per_texture; + for (int i = 0; i < texture_count; i++) { + int texture_slice_count = (i == texture_count - 1 && last_count != 0) ? last_count : slices_per_texture; + + Ref<Image> texture_image = Image::create_empty(slice_width, slice_height * texture_slice_count, false, images[0]->get_format()); + + for (int j = 0; j < texture_slice_count; j++) { + texture_image->blit_rect(images[i * slices_per_texture + j], Rect2i(0, 0, slice_width, slice_height), Point2i(0, slice_height * j)); + } + + String texture_path = texture_count > 1 ? base_path + "_" + itos(i) + ".exr" : base_path + ".exr"; + + Ref<ConfigFile> config; + config.instantiate(); + + if (FileAccess::exists(texture_path + ".import")) { + config->load(texture_path + ".import"); + } + + config->set_value("remap", "importer", "2d_array_texture"); + config->set_value("remap", "type", "CompressedTexture2DArray"); + if (!config->has_section_key("params", "compress/mode")) { + // User may want another compression, so leave it be, but default to VRAM uncompressed. + config->set_value("params", "compress/mode", 3); + } + config->set_value("params", "compress/channel_pack", 1); + config->set_value("params", "mipmaps/generate", false); + config->set_value("params", "slices/horizontal", 1); + config->set_value("params", "slices/vertical", texture_slice_count); + + config->save(texture_path + ".import"); + + Error err = texture_image->save_exr(texture_path, false); + ERR_FAIL_COND_V(err, BAKE_ERROR_CANT_CREATE_IMAGE); + ResourceLoader::import(texture_path); + Ref<TextureLayered> t = ResourceLoader::load(texture_path); // If already loaded, it will be updated on refocus? + ERR_FAIL_COND_V(t.is_null(), BAKE_ERROR_CANT_CREATE_IMAGE); + textures[i] = t; + } + } + /* POSTBAKE: Save Light Data */ Ref<LightmapGIData> gi_data; @@ -1110,18 +1142,7 @@ LightmapGI::BakeError LightmapGI::bake(Node *p_from_node, String p_image_data_pa gi_data.instantiate(); } - Ref<Texture2DArray> texture; - { - Vector<Ref<Image>> images; - for (int i = 0; i < lightmapper->get_bake_texture_count(); i++) { - images.push_back(lightmapper->get_bake_texture(i)); - } - - texture.instantiate(); - texture->create_from_images(images); - } - - gi_data->set_light_texture(texture); + gi_data->set_lightmap_textures(textures); gi_data->set_uses_spherical_harmonics(directional); for (int i = 0; i < lightmapper->get_bake_mesh_count(); i++) { @@ -1502,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/lightmap_gi.h b/scene/3d/lightmap_gi.h index b65d7f8c78..fec0075693 100644 --- a/scene/3d/lightmap_gi.h +++ b/scene/3d/lightmap_gi.h @@ -44,6 +44,7 @@ class LightmapGIData : public Resource { RES_BASE_EXTENSION("lmbake") Ref<TextureLayered> light_texture; + TypedArray<TextureLayered> light_textures; bool uses_spherical_harmonics = false; bool interior = false; @@ -65,8 +66,8 @@ class LightmapGIData : public Resource { Array _get_user_data() const; void _set_probe_data(const Dictionary &p_data); Dictionary _get_probe_data() const; - void _set_light_textures_data(const Array &p_data); - Array _get_light_textures_data() const; + + void _reset_lightmap_textures(); protected: static void _bind_methods(); @@ -80,9 +81,14 @@ public: int get_user_lightmap_slice_index(int p_user) const; void clear_users(); +#ifndef DISABLE_DEPRECATED void set_light_texture(const Ref<TextureLayered> &p_light_texture); Ref<TextureLayered> get_light_texture() const; + void _set_light_textures_data(const Array &p_data); + Array _get_light_textures_data() const; +#endif + void set_uses_spherical_harmonics(bool p_enable); bool is_using_spherical_harmonics() const; @@ -98,6 +104,9 @@ public: void clear(); + void set_lightmap_textures(const TypedArray<TextureLayered> &p_data); + TypedArray<TextureLayered> get_lightmap_textures() const; + virtual RID get_rid() const override; LightmapGIData(); ~LightmapGIData(); diff --git a/scene/3d/mesh_instance_3d.cpp b/scene/3d/mesh_instance_3d.cpp index 78b02d74d5..d83d55d121 100644 --- a/scene/3d/mesh_instance_3d.cpp +++ b/scene/3d/mesh_instance_3d.cpp @@ -99,7 +99,7 @@ void MeshInstance3D::_get_property_list(List<PropertyInfo> *p_list) const { if (mesh.is_valid()) { for (int i = 0; i < mesh->get_surface_count(); i++) { - p_list->push_back(PropertyInfo(Variant::OBJECT, vformat("%s/%d", PNAME("surface_material_override"), i), PROPERTY_HINT_RESOURCE_TYPE, "BaseMaterial3D,ShaderMaterial", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_DEFERRED_SET_RESOURCE)); + p_list->push_back(PropertyInfo(Variant::OBJECT, vformat("%s/%d", PNAME("surface_material_override"), i), PROPERTY_HINT_RESOURCE_TYPE, "BaseMaterial3D,ShaderMaterial", PROPERTY_USAGE_DEFAULT)); } } } diff --git a/scene/3d/multimesh_instance_3d.cpp b/scene/3d/multimesh_instance_3d.cpp index 2158005f5f..55d6e49e6c 100644 --- a/scene/3d/multimesh_instance_3d.cpp +++ b/scene/3d/multimesh_instance_3d.cpp @@ -49,6 +49,26 @@ Ref<MultiMesh> MultiMeshInstance3D::get_multimesh() const { return multimesh; } +Array MultiMeshInstance3D::get_meshes() const { + if (multimesh.is_null() || multimesh->get_mesh().is_null() || multimesh->get_transform_format() != MultiMesh::TransformFormat::TRANSFORM_3D) { + return Array(); + } + + int count = multimesh->get_visible_instance_count(); + if (count == -1) { + count = multimesh->get_instance_count(); + } + + Ref<Mesh> mesh = multimesh->get_mesh(); + + Array results; + for (int i = 0; i < count; i++) { + results.push_back(multimesh->get_instance_transform(i)); + results.push_back(mesh); + } + return results; +} + AABB MultiMeshInstance3D::get_aabb() const { if (multimesh.is_null()) { return AABB(); diff --git a/scene/3d/multimesh_instance_3d.h b/scene/3d/multimesh_instance_3d.h index cd18281b91..404f31d1e3 100644 --- a/scene/3d/multimesh_instance_3d.h +++ b/scene/3d/multimesh_instance_3d.h @@ -47,6 +47,8 @@ public: void set_multimesh(const Ref<MultiMesh> &p_multimesh); Ref<MultiMesh> get_multimesh() const; + Array get_meshes() const; + virtual AABB get_aabb() const override; MultiMeshInstance3D(); 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 a5f5ae6e61..ed64c16564 100644 --- a/scene/3d/physics_body_3d.cpp +++ b/scene/3d/physics_body_3d.cpp @@ -485,7 +485,9 @@ struct _RigidBodyInOut { }; void RigidBody3D::_sync_body_state(PhysicsDirectBodyState3D *p_state) { + set_ignore_transform_notification(true); set_global_transform(p_state->get_transform()); + set_ignore_transform_notification(false); linear_velocity = p_state->get_linear_velocity(); angular_velocity = p_state->get_angular_velocity(); @@ -501,16 +503,20 @@ void RigidBody3D::_sync_body_state(PhysicsDirectBodyState3D *p_state) { void RigidBody3D::_body_state_changed(PhysicsDirectBodyState3D *p_state) { lock_callback(); - set_ignore_transform_notification(true); - 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(); + + 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); - set_ignore_transform_notification(false); _on_transform_changed(); if (contact_monitor) { @@ -2927,7 +2933,10 @@ void PhysicalBone3D::_notification(int p_what) { } void PhysicalBone3D::_sync_body_state(PhysicsDirectBodyState3D *p_state) { + set_ignore_transform_notification(true); set_global_transform(p_state->get_transform()); + set_ignore_transform_notification(false); + linear_velocity = p_state->get_linear_velocity(); angular_velocity = p_state->get_angular_velocity(); } @@ -2937,16 +2946,20 @@ void PhysicalBone3D::_body_state_changed(PhysicsDirectBodyState3D *p_state) { return; } - set_ignore_transform_notification(true); - 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(); + + 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); - set_ignore_transform_notification(false); _on_transform_changed(); Transform3D global_transform(p_state->get_transform()); diff --git a/scene/3d/reflection_probe.cpp b/scene/3d/reflection_probe.cpp index e533f08861..2e34f6aad0 100644 --- a/scene/3d/reflection_probe.cpp +++ b/scene/3d/reflection_probe.cpp @@ -68,8 +68,9 @@ Color ReflectionProbe::get_ambient_color() const { } void ReflectionProbe::set_max_distance(float p_distance) { - max_distance = p_distance; - RS::get_singleton()->reflection_probe_set_max_distance(probe, p_distance); + max_distance = CLAMP(p_distance, 0.0, 262'144.0); + // Reflection rendering breaks if distance exceeds 262,144 units (due to floating-point precision with the near plane being 0.01). + RS::get_singleton()->reflection_probe_set_max_distance(probe, max_distance); } float ReflectionProbe::get_max_distance() const { diff --git a/scene/3d/soft_body_3d.cpp b/scene/3d/soft_body_3d.cpp index 1f12f96fb3..db2c0e1387 100644 --- a/scene/3d/soft_body_3d.cpp +++ b/scene/3d/soft_body_3d.cpp @@ -471,6 +471,7 @@ void SoftBody3D::_become_mesh_owner() { uint32_t surface_format = mesh->surface_get_format(0); surface_format |= Mesh::ARRAY_FLAG_USE_DYNAMIC_UPDATE; + surface_format &= ~Mesh::ARRAY_FLAG_COMPRESS_ATTRIBUTES; Ref<ArrayMesh> soft_mesh; soft_mesh.instantiate(); diff --git a/scene/3d/sprite_3d.cpp b/scene/3d/sprite_3d.cpp index befabfe7b9..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(); } @@ -889,7 +980,7 @@ void Sprite3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_hframes", "hframes"), &Sprite3D::set_hframes); ClassDB::bind_method(D_METHOD("get_hframes"), &Sprite3D::get_hframes); - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture"), "set_texture", "get_texture"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_texture", "get_texture"); ADD_GROUP("Animation", ""); ADD_PROPERTY(PropertyInfo(Variant::INT, "hframes", PROPERTY_HINT_RANGE, "1,16384,1"), "set_hframes", "get_hframes"); ADD_PROPERTY(PropertyInfo(Variant::INT, "vframes", PROPERTY_HINT_RANGE, "1,16384,1"), "set_vframes", "get_vframes"); diff --git a/scene/3d/visual_instance_3d.cpp b/scene/3d/visual_instance_3d.cpp index 8026b12c2b..3b1faca17e 100644 --- a/scene/3d/visual_instance_3d.cpp +++ b/scene/3d/visual_instance_3d.cpp @@ -501,8 +501,8 @@ void GeometryInstance3D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_aabb"), &GeometryInstance3D::get_aabb); ADD_GROUP("Geometry", ""); - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "material_override", PROPERTY_HINT_RESOURCE_TYPE, "BaseMaterial3D,ShaderMaterial", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_DEFERRED_SET_RESOURCE), "set_material_override", "get_material_override"); - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "material_overlay", PROPERTY_HINT_RESOURCE_TYPE, "BaseMaterial3D,ShaderMaterial", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_DEFERRED_SET_RESOURCE), "set_material_overlay", "get_material_overlay"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "material_override", PROPERTY_HINT_RESOURCE_TYPE, "BaseMaterial3D,ShaderMaterial", PROPERTY_USAGE_DEFAULT), "set_material_override", "get_material_override"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "material_overlay", PROPERTY_HINT_RESOURCE_TYPE, "BaseMaterial3D,ShaderMaterial", PROPERTY_USAGE_DEFAULT), "set_material_overlay", "get_material_overlay"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "transparency", PROPERTY_HINT_RANGE, "0.0,1.0,0.01"), "set_transparency", "get_transparency"); ADD_PROPERTY(PropertyInfo(Variant::INT, "cast_shadow", PROPERTY_HINT_ENUM, "Off,On,Double-Sided,Shadows Only"), "set_cast_shadows_setting", "get_cast_shadows_setting"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "extra_cull_margin", PROPERTY_HINT_RANGE, "0,16384,0.01,suffix:m"), "set_extra_cull_margin", "get_extra_cull_margin"); diff --git a/scene/3d/voxel_gi.cpp b/scene/3d/voxel_gi.cpp index 43906b2586..8250083a9f 100644 --- a/scene/3d/voxel_gi.cpp +++ b/scene/3d/voxel_gi.cpp @@ -314,9 +314,21 @@ Ref<CameraAttributes> VoxelGI::get_camera_attributes() const { return camera_attributes; } +static bool is_node_voxel_bakeable(Node3D *p_node) { + if (!p_node->is_visible_in_tree()) { + return false; + } + + GeometryInstance3D *geometry = Object::cast_to<GeometryInstance3D>(p_node); + if (geometry != nullptr && geometry->get_gi_mode() != GeometryInstance3D::GI_MODE_STATIC) { + return false; + } + return true; +} + void VoxelGI::_find_meshes(Node *p_at_node, List<PlotMesh> &plot_meshes) { MeshInstance3D *mi = Object::cast_to<MeshInstance3D>(p_at_node); - if (mi && mi->get_gi_mode() == GeometryInstance3D::GI_MODE_STATIC && mi->is_visible_in_tree()) { + if (mi && is_node_voxel_bakeable(mi)) { Ref<Mesh> mesh = mi->get_mesh(); if (mesh.is_valid()) { AABB aabb = mesh->get_aabb(); @@ -338,8 +350,15 @@ void VoxelGI::_find_meshes(Node *p_at_node, List<PlotMesh> &plot_meshes) { Node3D *s = Object::cast_to<Node3D>(p_at_node); if (s) { - if (s->is_visible_in_tree()) { - Array meshes = p_at_node->call("get_meshes"); + if (is_node_voxel_bakeable(s)) { + Array meshes; + MultiMeshInstance3D *multi_mesh = Object::cast_to<MultiMeshInstance3D>(p_at_node); + if (multi_mesh) { + meshes = multi_mesh->get_meshes(); + } else { + meshes = p_at_node->call("get_meshes"); + } + for (int i = 0; i < meshes.size(); i += 2) { Transform3D mxf = meshes[i]; Ref<Mesh> mesh = meshes[i + 1]; diff --git a/scene/3d/voxelizer.cpp b/scene/3d/voxelizer.cpp index fec4bc2b05..99392e9ba0 100644 --- a/scene/3d/voxelizer.cpp +++ b/scene/3d/voxelizer.cpp @@ -383,6 +383,8 @@ Voxelizer::MaterialCache Voxelizer::_get_material_cache(Ref<Material> p_material } void Voxelizer::plot_mesh(const Transform3D &p_xform, Ref<Mesh> &p_mesh, const Vector<Ref<Material>> &p_materials, const Ref<Material> &p_override_material) { + ERR_FAIL_COND_MSG(!p_xform.is_finite(), "Invalid mesh bake transform."); + for (int i = 0; i < p_mesh->get_surface_count(); i++) { if (p_mesh->surface_get_primitive_type(i) != Mesh::PRIMITIVE_TRIANGLES) { continue; //only triangles diff --git a/scene/animation/animation_mixer.cpp b/scene/animation/animation_mixer.cpp index 2ffa7b66a7..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,34 +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: { - 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."); } } @@ -941,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(); @@ -1015,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); @@ -1102,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; @@ -1425,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) { @@ -1701,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; @@ -1780,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++) { @@ -1865,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); } @@ -1926,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. @@ -1956,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()); @@ -1989,10 +1973,11 @@ void AnimationMixer::reset() { ERR_FAIL_NULL(root_node_object); AnimationPlayer *aux_player = memnew(AnimationPlayer); - EditorNode::get_singleton()->add_child(aux_player); + root_node_object->add_child(aux_player); Ref<AnimationLibrary> al; al.instantiate(); al->add_animation(SceneStringNames::get_singleton()->RESET, reset_anim); + aux_player->set_reset_on_save_enabled(false); aux_player->set_root_node(aux_player->get_path_to(root_node_object)); aux_player->add_animation_library("", al); aux_player->set_assigned_animation(SceneStringNames::get_singleton()->RESET); @@ -2001,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 /* -------------------------------------------- */ @@ -2124,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() { @@ -2132,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 b3285d4cfc..02b74c9188 100644 --- a/scene/animation/animation_player.cpp +++ b/scene/animation/animation_player.cpp @@ -151,6 +151,7 @@ void AnimationPlayer::_notification(int p_what) { if (!Engine::get_singleton()->is_editor_hint() && animation_set.has(autoplay)) { set_active(true); play(autoplay); + _check_immediately_after_start(); } } break; } @@ -232,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); @@ -521,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)); @@ -536,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 c778129eb6..8193bbf3f1 100644 --- a/scene/animation/tween.cpp +++ b/scene/animation/tween.cpp @@ -104,7 +104,15 @@ Ref<PropertyTweener> Tween::tween_property(const Object *p_target, const NodePat CHECK_VALID(); Vector<StringName> property_subnames = p_property.get_as_property_path().get_subnames(); - if (!_validate_type_match(p_target->get_indexed(property_subnames), p_to)) { +#ifdef DEBUG_ENABLED + bool prop_valid; + const Variant &prop_value = p_target->get_indexed(property_subnames, &prop_valid); + ERR_FAIL_COND_V_MSG(!prop_valid, nullptr, vformat("The tweened property \"%s\" does not exist in object \"%s\".", p_property, p_target)); +#else + const Variant &prop_value = p_target->get_indexed(property_subnames); +#endif + + if (!_validate_type_match(prop_value, p_to)) { return nullptr; } @@ -412,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; } @@ -546,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) { @@ -573,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/base_button.cpp b/scene/gui/base_button.cpp index f57afb66b3..66b14dc967 100644 --- a/scene/gui/base_button.cpp +++ b/scene/gui/base_button.cpp @@ -380,7 +380,7 @@ void BaseButton::shortcut_input(const Ref<InputEvent> &p_event) { queue_redraw(); accept_event(); - if (shortcut_feedback) { + if (shortcut_feedback && is_inside_tree()) { if (shortcut_feedback_timer == nullptr) { shortcut_feedback_timer = memnew(Timer); shortcut_feedback_timer->set_one_shot(true); diff --git a/scene/gui/color_picker.cpp b/scene/gui/color_picker.cpp index 669ce11e5d..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(); @@ -89,6 +89,10 @@ void ColorPicker::_notification(int p_what) { shape_popup->set_item_icon(shape_popup->get_item_index(SHAPE_VHS_CIRCLE), theme_cache.shape_circle); shape_popup->set_item_icon(shape_popup->get_item_index(SHAPE_OKHSL_CIRCLE), theme_cache.shape_circle); + if (current_shape != SHAPE_NONE) { + btn_shape->set_icon(shape_popup->get_item_icon(current_shape)); + } + internal_margin->begin_bulk_theme_override(); internal_margin->add_theme_constant_override(SNAME("margin_bottom"), theme_cache.content_margin); internal_margin->add_theme_constant_override(SNAME("margin_left"), theme_cache.content_margin); @@ -552,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; } @@ -689,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(); @@ -923,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; } @@ -1792,8 +1809,6 @@ ColorPicker::ColorPicker() { shape_popup->set_item_checked(current_shape, true); shape_popup->connect("id_pressed", callable_mp(this, &ColorPicker::set_picker_shape)); - btn_shape->set_icon(shape_popup->get_item_icon(current_shape)); - add_mode(new ColorModeRGB(this)); add_mode(new ColorModeHSV(this)); add_mode(new ColorModeRAW(this)); diff --git a/scene/gui/control.cpp b/scene/gui/control.cpp index c7ff5980cb..ed54bd000c 100644 --- a/scene/gui/control.cpp +++ b/scene/gui/control.cpp @@ -1653,6 +1653,7 @@ void Control::set_custom_minimum_size(const Size2 &p_custom) { data.custom_minimum_size = p_custom; update_minimum_size(); + update_configuration_warnings(); } Size2 Control::get_custom_minimum_size() const { @@ -1831,9 +1832,18 @@ bool Control::has_point(const Point2 &p_point) const { void Control::set_mouse_filter(MouseFilter p_filter) { ERR_MAIN_THREAD_GUARD; ERR_FAIL_INDEX(p_filter, 3); + + if (data.mouse_filter == p_filter) { + return; + } + data.mouse_filter = p_filter; notify_property_list_changed(); update_configuration_warnings(); + + if (get_viewport()) { + get_viewport()->_gui_update_mouse_over(); + } } Control::MouseFilter Control::get_mouse_filter() const { @@ -3568,6 +3578,8 @@ void Control::_bind_methods() { BIND_CONSTANT(NOTIFICATION_RESIZED); BIND_CONSTANT(NOTIFICATION_MOUSE_ENTER); BIND_CONSTANT(NOTIFICATION_MOUSE_EXIT); + BIND_CONSTANT(NOTIFICATION_MOUSE_ENTER_SELF); + BIND_CONSTANT(NOTIFICATION_MOUSE_EXIT_SELF); BIND_CONSTANT(NOTIFICATION_FOCUS_ENTER); BIND_CONSTANT(NOTIFICATION_FOCUS_EXIT); BIND_CONSTANT(NOTIFICATION_THEME_CHANGED); diff --git a/scene/gui/control.h b/scene/gui/control.h index abbdc42fa4..db1bd3a346 100644 --- a/scene/gui/control.h +++ b/scene/gui/control.h @@ -368,6 +368,8 @@ public: NOTIFICATION_SCROLL_BEGIN = 47, NOTIFICATION_SCROLL_END = 48, NOTIFICATION_LAYOUT_DIRECTION_CHANGED = 49, + NOTIFICATION_MOUSE_ENTER_SELF = 60, + NOTIFICATION_MOUSE_EXIT_SELF = 61, }; // Editor plugin interoperability. diff --git a/scene/gui/dialogs.cpp b/scene/gui/dialogs.cpp index 957a8f276e..3e827e76dc 100644 --- a/scene/gui/dialogs.cpp +++ b/scene/gui/dialogs.cpp @@ -42,6 +42,7 @@ void AcceptDialog::_input_from_window(const Ref<InputEvent> &p_event) { if (close_on_escape && p_event->is_action_pressed(SNAME("ui_cancel"), false, true)) { _cancel_pressed(); } + Window::_input_from_window(p_event); } void AcceptDialog::_parent_focused() { @@ -428,8 +429,6 @@ AcceptDialog::AcceptDialog() { ok_button->connect("pressed", callable_mp(this, &AcceptDialog::_ok_pressed)); set_title(TTRC("Alert!")); - - connect("window_input", callable_mp(this, &AcceptDialog::_input_from_window)); } AcceptDialog::~AcceptDialog() { diff --git a/scene/gui/dialogs.h b/scene/gui/dialogs.h index d5cbaaeef8..e28d6b7467 100644 --- a/scene/gui/dialogs.h +++ b/scene/gui/dialogs.h @@ -65,11 +65,11 @@ class AcceptDialog : public Window { static bool swap_cancel_ok; - void _input_from_window(const Ref<InputEvent> &p_event); void _parent_focused(); protected: virtual Size2 _get_contents_minimum_size() const override; + virtual void _input_from_window(const Ref<InputEvent> &p_event) override; void _notification(int p_what); static void _bind_methods(); 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/graph_edit_arranger.cpp b/scene/gui/graph_edit_arranger.cpp index 1dc778254b..3f2007f7e0 100644 --- a/scene/gui/graph_edit_arranger.cpp +++ b/scene/gui/graph_edit_arranger.cpp @@ -65,6 +65,9 @@ void GraphEditArranger::arrange_nodes() { float gap_v = 100.0f; float gap_h = 100.0f; + List<GraphEdit::Connection> connection_list; + graph_edit->get_connection_list(&connection_list); + for (int i = graph_edit->get_child_count() - 1; i >= 0; i--) { GraphNode *graph_element = Object::cast_to<GraphNode>(graph_edit->get_child(i)); if (!graph_element) { @@ -74,8 +77,6 @@ void GraphEditArranger::arrange_nodes() { if (graph_element->is_selected() || arrange_entire_graph) { selected_nodes.insert(graph_element->get_name()); HashSet<StringName> s; - List<GraphEdit::Connection> connection_list; - graph_edit->get_connection_list(&connection_list); for (List<GraphEdit::Connection>::Element *E = connection_list.front(); E; E = E->next()) { GraphNode *p_from = Object::cast_to<GraphNode>(node_names[E->get().from_node]); if (E->get().to_node == graph_element->get_name() && (p_from->is_selected() || arrange_entire_graph) && E->get().to_node != E->get().from_node) { @@ -85,12 +86,6 @@ void GraphEditArranger::arrange_nodes() { String s_connection = String(p_from->get_name()) + " " + String(E->get().to_node); StringName _connection(s_connection); Pair<int, int> ports(E->get().from_port, E->get().to_port); - if (port_info.has(_connection)) { - Pair<int, int> p_ports = port_info[_connection]; - if (p_ports.first < ports.first) { - ports = p_ports; - } - } port_info.insert(_connection, ports); } } @@ -216,13 +211,14 @@ int GraphEditArranger::_set_operations(SET_OPERATIONS p_operation, HashSet<Strin return 1; } break; case GraphEditArranger::DIFFERENCE: { - for (HashSet<StringName>::Iterator E = r_u.begin(); E;) { - HashSet<StringName>::Iterator N = E; - ++N; - if (r_v.has(*E)) { - r_u.remove(E); + Vector<StringName> common; + for (const StringName &E : r_u) { + if (r_v.has(E)) { + common.append(E); } - E = N; + } + for (const StringName &E : common) { + r_u.erase(E); } return r_u.size(); } break; @@ -260,9 +256,7 @@ HashMap<int, Vector<StringName>> GraphEditArranger::_layering(const HashSet<Stri selected = true; t.append_array(l[current_layer]); l.insert(current_layer, t); - HashSet<StringName> V; - V.insert(E); - _set_operations(GraphEditArranger::UNION, u, V); + u.insert(E); } } if (!selected) { diff --git a/scene/gui/item_list.cpp b/scene/gui/item_list.cpp index 343301e9c4..02d44caa1c 100644 --- a/scene/gui/item_list.cpp +++ b/scene/gui/item_list.cpp @@ -1431,11 +1431,11 @@ void ItemList::force_update_list_size() { } } - for (int j = items.size() - 1; j >= 0 && col > 0; j--, col--) { - items.write[j].rect_cache.size.y = max_h; - } - if (all_fit) { + for (int j = items.size() - 1; j >= 0 && col > 0; j--, col--) { + items.write[j].rect_cache.size.y = max_h; + } + float page = MAX(0, size.height - theme_cache.panel_style->get_minimum_size().height); float max = MAX(page, ofs.y + max_h); if (auto_height) { diff --git a/scene/gui/label.cpp b/scene/gui/label.cpp index 0d48cb1549..3df0d97160 100644 --- a/scene/gui/label.cpp +++ b/scene/gui/label.cpp @@ -33,6 +33,7 @@ #include "core/config/project_settings.h" #include "core/string/print_string.h" #include "core/string/translation.h" +#include "scene/gui/container.h" #include "scene/theme/theme_db.h" #include "servers/text_server.h" @@ -44,6 +45,7 @@ void Label::set_autowrap_mode(TextServer::AutowrapMode p_mode) { autowrap_mode = p_mode; lines_dirty = true; queue_redraw(); + update_configuration_warnings(); if (clip || overrun_behavior != TextServer::OVERRUN_NO_TRIMMING) { update_minimum_size(); @@ -238,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 { @@ -266,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); } } @@ -327,6 +333,19 @@ inline void draw_glyph_outline(const Glyph &p_gl, const RID &p_canvas, const Col PackedStringArray Label::get_configuration_warnings() const { PackedStringArray warnings = Control::get_configuration_warnings(); + // FIXME: This is not ideal and the sizing model should be fixed, + // but for now we have to warn about this impossible to resolve combination. + // See GH-83546. + if (is_inside_tree() && get_tree()->get_edited_scene_root() != this) { + // If the Label happens to be the root node of the edited scene, we don't need + // to check what its parent is. It's going to be some node from the editor tree + // and it can be a container, but that makes no difference to the user. + Container *parent_container = Object::cast_to<Container>(get_parent_control()); + if (parent_container && autowrap_mode != TextServer::AUTOWRAP_OFF && get_custom_minimum_size() == Size2()) { + warnings.push_back(RTR("Labels with autowrapping enabled must have a custom minimum size configured to work correctly inside a container.")); + } + } + // Ensure that the font can render all of the required glyphs. Ref<Font> font; if (settings.is_valid()) { @@ -872,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; } @@ -992,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)); @@ -1022,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 12ffafadf7..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()); @@ -2643,7 +2640,7 @@ void LineEdit::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "selecting_enabled"), "set_selecting_enabled", "is_selecting_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "deselect_on_focus_loss_enabled"), "set_deselect_on_focus_loss_enabled", "is_deselect_on_focus_loss_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "drag_and_drop_selection_enabled"), "set_drag_and_drop_selection_enabled", "is_drag_and_drop_selection_enabled"); - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "right_icon", PROPERTY_HINT_RESOURCE_TYPE, "Texture"), "set_right_icon", "get_right_icon"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "right_icon", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_right_icon", "get_right_icon"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "flat"), "set_flat", "is_flat"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "draw_control_chars"), "set_draw_control_chars", "get_draw_control_chars"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "select_all_on_focus"), "set_select_all_on_focus", "is_select_all_on_focus"); diff --git a/scene/gui/menu_bar.cpp b/scene/gui/menu_bar.cpp index 371d6c69af..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,9 +562,12 @@ 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(); } void MenuBar::move_child_notify(Node *p_child) { @@ -586,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)); + } } } } @@ -611,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"); @@ -621,6 +632,8 @@ void MenuBar::remove_child_notify(Node *p_child) { p_child->disconnect("renamed", callable_mp(this, &MenuBar::_refresh_menu_names)); p_child->disconnect("about_to_popup", callable_mp(this, &MenuBar::_popup_visibility_changed)); p_child->disconnect("popup_hide", callable_mp(this, &MenuBar::_popup_visibility_changed)); + + update_minimum_size(); } void MenuBar::_bind_methods() { @@ -808,6 +821,7 @@ void MenuBar::set_menu_title(int p_menu, const String &p_title) { if (!global_menu_name.is_empty()) { DisplayServer::get_singleton()->global_menu_set_item_text("_main", _find_global_start_index() + p_menu, atr(menu_cache[p_menu].name)); } + update_minimum_size(); } String MenuBar::get_menu_title(int p_menu) const { @@ -849,6 +863,7 @@ void MenuBar::set_menu_hidden(int p_menu, bool p_hidden) { if (!global_menu_name.is_empty()) { DisplayServer::get_singleton()->global_menu_set_item_hidden("_main", _find_global_start_index() + p_menu, p_hidden); } + update_minimum_size(); } bool MenuBar::is_menu_hidden(int p_menu) const { diff --git a/scene/gui/popup.cpp b/scene/gui/popup.cpp index b16e8371a2..8369bedda9 100644 --- a/scene/gui/popup.cpp +++ b/scene/gui/popup.cpp @@ -39,6 +39,7 @@ void Popup::_input_from_window(const Ref<InputEvent> &p_event) { if (get_flag(FLAG_POPUP) && p_event->is_action_pressed(SNAME("ui_cancel"), false, true)) { _close_pressed(); } + Window::_input_from_window(p_event); } void Popup::_initialize_visible_parents() { @@ -204,8 +205,6 @@ Popup::Popup() { set_flag(FLAG_BORDERLESS, true); set_flag(FLAG_RESIZE_DISABLED, true); set_flag(FLAG_POPUP, true); - - connect("window_input", callable_mp(this, &Popup::_input_from_window)); } Popup::~Popup() { diff --git a/scene/gui/popup.h b/scene/gui/popup.h index d524e448dd..25edca3657 100644 --- a/scene/gui/popup.h +++ b/scene/gui/popup.h @@ -47,14 +47,13 @@ class Popup : public Window { Ref<StyleBox> panel_style; } theme_cache; - void _input_from_window(const Ref<InputEvent> &p_event); - void _initialize_visible_parents(); void _deinitialize_visible_parents(); protected: void _close_pressed(); virtual Rect2i _popup_adjust_rect() const override; + virtual void _input_from_window(const Ref<InputEvent> &p_event) override; void _notification(int p_what); void _validate_property(PropertyInfo &p_property) const; diff --git a/scene/gui/popup_menu.cpp b/scene/gui/popup_menu.cpp index dfaf7d88b7..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(); @@ -378,9 +414,16 @@ void PopupMenu::_submenu_timeout() { submenu_over = -1; } -void PopupMenu::gui_input(const Ref<InputEvent> &p_event) { - ERR_FAIL_COND(p_event.is_null()); +void PopupMenu::_input_from_window(const Ref<InputEvent> &p_event) { + if (p_event.is_valid()) { + _input_from_window_internal(p_event); + } else { + WARN_PRINT_ONCE("PopupMenu has received an invalid InputEvent. Consider filtering invalid events out."); + } + Popup::_input_from_window(p_event); +} +void PopupMenu::_input_from_window_internal(const Ref<InputEvent> &p_event) { if (!items.is_empty()) { Input *input = Input::get_singleton(); Ref<InputEventJoypadMotion> joypadmotion_event = p_event; @@ -947,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: @@ -1487,6 +1539,11 @@ void PopupMenu::add_icon_radio_check_shortcut(const Ref<Texture2D> &p_icon, cons } void PopupMenu::add_submenu_item(const String &p_label, const String &p_submenu, int p_id) { + String submenu_name_safe = p_submenu.replace("@", "_"); // Allow special characters for auto-generated names. + if (submenu_name_safe.validate_node_name() != submenu_name_safe) { + ERR_FAIL_MSG(vformat("Invalid node name '%s' for a submenu, the following characters are not allowed:\n%s", p_submenu, String::get_invalid_node_name_characters(true))); + } + Item item; item.text = p_label; item.xl_text = atr(p_label); @@ -2186,6 +2243,7 @@ void PopupMenu::scroll_to_item(int p_idx) { } bool PopupMenu::activate_item_by_event(const Ref<InputEvent> &p_event, bool p_for_global_only) { + ERR_FAIL_COND_V(p_event.is_null(), false); Key code = Key::NONE; Ref<InputEventKey> k = p_event; @@ -2710,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_"); @@ -2793,8 +2856,6 @@ PopupMenu::PopupMenu() { scroll_container->add_child(control, false, INTERNAL_MODE_FRONT); control->connect("draw", callable_mp(this, &PopupMenu::_draw_items)); - connect("window_input", callable_mp(this, &PopupMenu::gui_input)); - submenu_timer = memnew(Timer); submenu_timer->set_wait_time(0.3); submenu_timer->set_one_shot(true); diff --git a/scene/gui/popup_menu.h b/scene/gui/popup_menu.h index 5d5f4a8322..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; @@ -112,7 +115,6 @@ class PopupMenu : public Popup { void _shape_item(int p_idx); - virtual void gui_input(const Ref<InputEvent> &p_event); void _activate_submenu(int p_over, bool p_by_keyboard = false); void _submenu_timeout(); @@ -191,10 +193,12 @@ class PopupMenu : public Popup { void _minimum_lifetime_timeout(); void _close_pressed(); void _menu_changed(); + void _input_from_window_internal(const Ref<InputEvent> &p_event); protected: virtual void add_child_notify(Node *p_child) override; virtual void remove_child_notify(Node *p_child) override; + virtual void _input_from_window(const Ref<InputEvent> &p_event) override; void _notification(int p_what); bool _set(const StringName &p_name, const Variant &p_value); @@ -218,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 7768c2d84e..fbc374e89e 100644 --- a/scene/gui/rich_text_label.cpp +++ b/scene/gui/rich_text_label.cpp @@ -307,7 +307,7 @@ float RichTextLabel::_resize_line(ItemFrame *p_frame, int p_line, const Ref<Font Size2 img_size = img->size; if (img->size_in_percent) { img_size = _get_image_size(img->image, p_width * img->rq_size.width / 100.f, p_width * img->rq_size.height / 100.f, img->region); - l.text_buf->resize_object((uint64_t)it, img_size, img->inline_align, 1); + l.text_buf->resize_object((uint64_t)it, img_size, img->inline_align); } } break; case ITEM_TABLE: { @@ -830,37 +830,6 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o prefix = segment + prefix; } } - if (!prefix.is_empty()) { - Ref<Font> font = theme_cache.normal_font; - int font_size = theme_cache.normal_font_size; - - ItemFont *font_it = _find_font(l.from); - if (font_it) { - if (font_it->font.is_valid()) { - font = font_it->font; - } - if (font_it->font_size > 0) { - font_size = font_it->font_size; - } - } - ItemFontSize *font_size_it = _find_font_size(l.from); - if (font_size_it && font_size_it->font_size > 0) { - font_size = font_size_it->font_size; - } - if (rtl) { - float offx = 0.0f; - if (!lrtl && p_frame == main) { // Skip Scrollbar. - offx -= scroll_w; - } - font->draw_string(ci, p_ofs + Vector2(p_width - l.offset.x + offx, l.text_buf->get_line_ascent(0)), " " + prefix, HORIZONTAL_ALIGNMENT_LEFT, l.offset.x, font_size, _find_color(l.from, p_base_color)); - } else { - float offx = 0.0f; - if (lrtl && p_frame == main) { // Skip Scrollbar. - offx += scroll_w; - } - font->draw_string(ci, p_ofs + Vector2(offx, l.text_buf->get_line_ascent(0)), prefix + " ", HORIZONTAL_ALIGNMENT_RIGHT, l.offset.x, font_size, _find_color(l.from, p_base_color)); - } - } // Draw dropcap. int dc_lines = l.text_buf->get_dropcap_lines(); @@ -924,6 +893,30 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o } break; } + if (!prefix.is_empty() && line == 0) { + Ref<Font> font = theme_cache.normal_font; + int font_size = theme_cache.normal_font_size; + + ItemFont *font_it = _find_font(l.from); + if (font_it) { + if (font_it->font.is_valid()) { + font = font_it->font; + } + if (font_it->font_size > 0) { + font_size = font_it->font_size; + } + } + ItemFontSize *font_size_it = _find_font_size(l.from); + if (font_size_it && font_size_it->font_size > 0) { + font_size = font_size_it->font_size; + } + if (rtl) { + font->draw_string(ci, p_ofs + Vector2(off.x + length, l.text_buf->get_line_ascent(0)), " " + prefix, HORIZONTAL_ALIGNMENT_LEFT, l.offset.x, font_size, _find_color(l.from, p_base_color)); + } else { + font->draw_string(ci, p_ofs + Vector2(off.x - l.offset.x, l.text_buf->get_line_ascent(0)), prefix + " ", HORIZONTAL_ALIGNMENT_RIGHT, l.offset.x, font_size, _find_color(l.from, p_base_color)); + } + } + if (line <= dc_lines) { if (rtl) { off.x -= h_off; @@ -977,11 +970,20 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o coff.x = rect.size.width - table->columns[col].width - coff.x; } if (row % 2 == 0) { - draw_rect(Rect2(p_ofs + rect.position + off + coff - frame->padding.position, Size2(table->columns[col].width + h_separation + frame->padding.position.x + frame->padding.size.x, table->rows[row])), (frame->odd_row_bg != Color(0, 0, 0, 0) ? frame->odd_row_bg : odd_row_bg), true); + Color c = frame->odd_row_bg != Color(0, 0, 0, 0) ? frame->odd_row_bg : odd_row_bg; + if (c.a > 0.0) { + draw_rect(Rect2(p_ofs + rect.position + off + coff - frame->padding.position, Size2(table->columns[col].width + h_separation + frame->padding.position.x + frame->padding.size.x, table->rows[row])), c, true); + } } else { - draw_rect(Rect2(p_ofs + rect.position + off + coff - frame->padding.position, Size2(table->columns[col].width + h_separation + frame->padding.position.x + frame->padding.size.x, table->rows[row])), (frame->even_row_bg != Color(0, 0, 0, 0) ? frame->even_row_bg : even_row_bg), true); + Color c = frame->even_row_bg != Color(0, 0, 0, 0) ? frame->even_row_bg : even_row_bg; + if (c.a > 0.0) { + draw_rect(Rect2(p_ofs + rect.position + off + coff - frame->padding.position, Size2(table->columns[col].width + h_separation + frame->padding.position.x + frame->padding.size.x, table->rows[row])), c, true); + } + } + Color bc = frame->border != Color(0, 0, 0, 0) ? frame->border : border; + if (bc.a > 0.0) { + draw_rect(Rect2(p_ofs + rect.position + off + coff - frame->padding.position, Size2(table->columns[col].width + h_separation + frame->padding.position.x + frame->padding.size.x, table->rows[row])), bc, false); } - draw_rect(Rect2(p_ofs + rect.position + off + coff - frame->padding.position, Size2(table->columns[col].width + h_separation + frame->padding.position.x + frame->padding.size.x, table->rows[row])), (frame->border != Color(0, 0, 0, 0) ? frame->border : border), false); } for (int j = 0; j < (int)frame->lines.size(); j++) { @@ -1204,14 +1206,17 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o Vector2 ul_start; bool ul_started = false; + Color ul_color_prev; Color ul_color; Vector2 dot_ul_start; bool dot_ul_started = false; + Color dot_ul_color_prev; Color dot_ul_color; Vector2 st_start; bool st_started = false; + Color st_color_prev; Color st_color; for (int i = 0; i < gl_size; i++) { @@ -1219,9 +1224,18 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o Item *it = _get_item_at_pos(it_from, it_to, glyphs[i].start); Color font_color = _find_color(it, p_base_color); if (_find_underline(it) || (_find_meta(it, nullptr) && underline_meta)) { - if (!ul_started) { + if (ul_started && font_color != ul_color_prev) { + float y_off = TS->shaped_text_get_underline_position(rid); + float underline_width = MAX(1.0, TS->shaped_text_get_underline_thickness(rid) * theme_cache.base_scale); + draw_line(ul_start + Vector2(0, y_off), p_ofs + Vector2(off.x, off.y + y_off), ul_color, underline_width); + ul_start = p_ofs + Vector2(off.x, off.y); + ul_color_prev = font_color; + ul_color = font_color; + ul_color.a *= 0.5; + } else if (!ul_started) { ul_started = true; ul_start = p_ofs + Vector2(off.x, off.y); + ul_color_prev = font_color; ul_color = font_color; ul_color.a *= 0.5; } @@ -1232,9 +1246,18 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o draw_line(ul_start + Vector2(0, y_off), p_ofs + Vector2(off.x, off.y + y_off), ul_color, underline_width); } if (_find_hint(it, nullptr) && underline_hint) { - if (!dot_ul_started) { + if (dot_ul_started && font_color != dot_ul_color_prev) { + float y_off = TS->shaped_text_get_underline_position(rid); + float underline_width = MAX(1.0, TS->shaped_text_get_underline_thickness(rid) * theme_cache.base_scale); + draw_dashed_line(dot_ul_start + Vector2(0, y_off), p_ofs + Vector2(off.x, off.y + y_off), dot_ul_color, underline_width, MAX(2.0, underline_width * 2)); + dot_ul_start = p_ofs + Vector2(off.x, off.y); + dot_ul_color_prev = font_color; + dot_ul_color = font_color; + dot_ul_color.a *= 0.5; + } else if (!dot_ul_started) { dot_ul_started = true; dot_ul_start = p_ofs + Vector2(off.x, off.y); + dot_ul_color_prev = font_color; dot_ul_color = font_color; dot_ul_color.a *= 0.5; } @@ -1245,9 +1268,18 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o draw_dashed_line(dot_ul_start + Vector2(0, y_off), p_ofs + Vector2(off.x, off.y + y_off), dot_ul_color, underline_width, MAX(2.0, underline_width * 2)); } if (_find_strikethrough(it)) { - if (!st_started) { + if (st_started && font_color != st_color_prev) { + float y_off = -TS->shaped_text_get_ascent(rid) + TS->shaped_text_get_size(rid).y / 2; + float underline_width = MAX(1.0, TS->shaped_text_get_underline_thickness(rid) * theme_cache.base_scale); + draw_line(st_start + Vector2(0, y_off), p_ofs + Vector2(off.x, off.y + y_off), st_color, underline_width); + st_start = p_ofs + Vector2(off.x, off.y); + st_color_prev = font_color; + st_color = font_color; + st_color.a *= 0.5; + } else if (!st_started) { st_started = true; st_start = p_ofs + Vector2(off.x, off.y); + st_color_prev = font_color; st_color = font_color; st_color.a *= 0.5; } @@ -1739,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; @@ -3089,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) { @@ -3193,6 +3227,9 @@ void RichTextLabel::add_image(const Ref<Texture2D> &p_image, int p_width, int p_ ERR_FAIL_COND(p_image.is_null()); ERR_FAIL_COND(p_image->get_width() == 0); ERR_FAIL_COND(p_image->get_height() == 0); + ERR_FAIL_COND(p_width < 0); + ERR_FAIL_COND(p_height < 0); + ItemImage *item = memnew(ItemImage); if (p_region.has_area()) { @@ -3226,6 +3263,9 @@ void RichTextLabel::update_image(const Variant &p_key, BitField<ImageUpdateMask> ERR_FAIL_COND(p_image->get_height() == 0); } + ERR_FAIL_COND(p_width < 0); + ERR_FAIL_COND(p_height < 0); + bool reshape = false; Item *it = main; @@ -3277,6 +3317,9 @@ void RichTextLabel::update_image(const Variant &p_key, BitField<ImageUpdateMask> } } if ((p_mask & UPDATE_SIZE) || (p_mask & UPDATE_REGION) || (p_mask & UPDATE_TEXTURE)) { + ERR_FAIL_COND(item->image.is_null()); + ERR_FAIL_COND(item->image->get_width() == 0); + ERR_FAIL_COND(item->image->get_height() == 0); Size2 new_size = _get_image_size(item->image, item->rq_size.width, item->rq_size.height, item->region); if (item->size != new_size) { reshape = true; @@ -6288,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 29e6d3d10d..e9fbdbb312 100644 --- a/scene/gui/tab_bar.cpp +++ b/scene/gui/tab_bar.cpp @@ -334,6 +334,12 @@ void TabBar::_shape(int p_tab) { void TabBar::_notification(int p_what) { switch (p_what) { + case NOTIFICATION_ENTER_TREE: { + if (scroll_to_selected) { + ensure_tab_visible(current); + } + } break; + case NOTIFICATION_INTERNAL_PROCESS: { Input *input = Input::get_singleton(); @@ -669,7 +675,7 @@ bool TabBar::select_previous_available() { if (target_tab < 0) { target_tab += get_tab_count(); } - if (!is_tab_disabled(target_tab)) { + if (!is_tab_disabled(target_tab) && !is_tab_hidden(target_tab)) { set_current_tab(target_tab); return true; } @@ -681,7 +687,7 @@ bool TabBar::select_next_available() { const int offset_end = (get_tab_count() - get_current_tab()); for (int i = 1; i < offset_end; i++) { int target_tab = (get_current_tab() + i) % get_tab_count(); - if (!is_tab_disabled(target_tab)) { + if (!is_tab_disabled(target_tab) && !is_tab_hidden(target_tab)) { set_current_tab(target_tab); return true; } @@ -1094,6 +1100,23 @@ void TabBar::remove_tab(int p_idx) { max_drawn_tab = 0; previous = 0; } else { + // Try to change to a valid tab if possible (without firing the `tab_selected` signal). + for (int i = current; i < tabs.size(); i++) { + if (!is_tab_disabled(i) && !is_tab_hidden(i)) { + current = i; + break; + } + } + // If nothing, try backwards. + if (is_tab_disabled(current) || is_tab_hidden(current)) { + for (int i = current - 1; i >= 0; i--) { + if (!is_tab_disabled(i) && !is_tab_hidden(i)) { + current = i; + break; + } + } + } + offset = MIN(offset, tabs.size() - 1); max_drawn_tab = MIN(max_drawn_tab, tabs.size() - 1); @@ -1641,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; } @@ -1660,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; } @@ -1745,7 +1768,10 @@ void TabBar::_bind_methods() { ADD_SIGNAL(MethodInfo("tab_hovered", PropertyInfo(Variant::INT, "tab"))); ADD_SIGNAL(MethodInfo("active_tab_rearranged", PropertyInfo(Variant::INT, "idx_to"))); - ADD_PROPERTY(PropertyInfo(Variant::INT, "current_tab", PROPERTY_HINT_RANGE, "-1,4096,1", PROPERTY_USAGE_EDITOR), "set_current_tab", "get_current_tab"); + // "current_tab" property must come after "tab_count", otherwise the property isn't loaded correctly. + ADD_ARRAY_COUNT("Tabs", "tab_count", "set_tab_count", "get_tab_count", "tab_"); + + ADD_PROPERTY(PropertyInfo(Variant::INT, "current_tab", PROPERTY_HINT_RANGE, "-1,4096,1"), "set_current_tab", "get_current_tab"); ADD_PROPERTY(PropertyInfo(Variant::INT, "tab_alignment", PROPERTY_HINT_ENUM, "Left,Center,Right"), "set_tab_alignment", "get_tab_alignment"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "clip_tabs"), "set_clip_tabs", "get_clip_tabs"); ADD_PROPERTY(PropertyInfo(Variant::INT, "tab_close_display_policy", PROPERTY_HINT_ENUM, "Show Never,Show Active Only,Show Always"), "set_tab_close_display_policy", "get_tab_close_display_policy"); @@ -1756,8 +1782,6 @@ void TabBar::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "scroll_to_selected"), "set_scroll_to_selected", "get_scroll_to_selected"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "select_with_rmb"), "set_select_with_rmb", "get_select_with_rmb"); - ADD_ARRAY_COUNT("Tabs", "tab_count", "set_tab_count", "get_tab_count", "tab_"); - BIND_ENUM_CONSTANT(ALIGNMENT_LEFT); BIND_ENUM_CONSTANT(ALIGNMENT_CENTER); BIND_ENUM_CONSTANT(ALIGNMENT_RIGHT); diff --git a/scene/gui/tab_container.cpp b/scene/gui/tab_container.cpp index c21a9d14cb..0f461f4865 100644 --- a/scene/gui/tab_container.cpp +++ b/scene/gui/tab_container.cpp @@ -143,6 +143,13 @@ void TabContainer::_notification(int p_what) { } } break; + case NOTIFICATION_POST_ENTER_TREE: { + if (setup_current_tab >= 0) { + set_current_tab(setup_current_tab); + setup_current_tab = -1; + } + } break; + case NOTIFICATION_READY: case NOTIFICATION_RESIZED: { _update_margins(); @@ -330,14 +337,23 @@ Vector<Control *> TabContainer::_get_tab_controls() const { } Variant TabContainer::_get_drag_data_fw(const Point2 &p_point, Control *p_from_control) { + if (!drag_to_rearrange_enabled) { + return Variant(); + } return tab_bar->_handle_get_drag_data("tab_container_tab", p_point); } bool TabContainer::_can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from_control) const { + if (!drag_to_rearrange_enabled) { + return false; + } return tab_bar->_handle_can_drop_data("tab_container_tab", p_point, p_data); } void TabContainer::_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from_control) { + if (!drag_to_rearrange_enabled) { + return; + } return tab_bar->_handle_drop_data("tab_container_tab", p_point, p_data, callable_mp(this, &TabContainer::_drag_move_tab), callable_mp(this, &TabContainer::_drag_move_tab_from)); } @@ -365,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); } @@ -519,6 +542,10 @@ int TabContainer::get_tab_count() const { } void TabContainer::set_current_tab(int p_current) { + if (!is_inside_tree()) { + setup_current_tab = p_current; + return; + } tab_bar->set_current_tab(p_current); } @@ -815,11 +842,11 @@ Popup *TabContainer::get_popup() const { } void TabContainer::set_drag_to_rearrange_enabled(bool p_enabled) { - tab_bar->set_drag_to_rearrange_enabled(p_enabled); + drag_to_rearrange_enabled = p_enabled; } bool TabContainer::get_drag_to_rearrange_enabled() const { - return tab_bar->get_drag_to_rearrange_enabled(); + return drag_to_rearrange_enabled; } void TabContainer::set_tabs_rearrange_group(int p_group_id) { @@ -903,7 +930,7 @@ void TabContainer::_bind_methods() { ADD_SIGNAL(MethodInfo("pre_popup_pressed")); ADD_PROPERTY(PropertyInfo(Variant::INT, "tab_alignment", PROPERTY_HINT_ENUM, "Left,Center,Right"), "set_tab_alignment", "get_tab_alignment"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "current_tab", PROPERTY_HINT_RANGE, "-1,4096,1", PROPERTY_USAGE_EDITOR), "set_current_tab", "get_current_tab"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "current_tab", PROPERTY_HINT_RANGE, "-1,4096,1"), "set_current_tab", "get_current_tab"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "clip_tabs"), "set_clip_tabs", "get_clip_tabs"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "tabs_visible"), "set_tabs_visible", "are_tabs_visible"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "all_tabs_in_front"), "set_all_tabs_in_front", "is_all_tabs_in_front"); diff --git a/scene/gui/tab_container.h b/scene/gui/tab_container.h index 5750c6b82e..450143cd0c 100644 --- a/scene/gui/tab_container.h +++ b/scene/gui/tab_container.h @@ -46,6 +46,8 @@ class TabContainer : public Container { bool use_hidden_tabs_for_min_size = false; bool theme_changing = false; Vector<Control *> children_removing; + bool drag_to_rearrange_enabled = false; + int setup_current_tab = -1; struct ThemeCache { int side_margin = 0; diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp index 479480011c..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; @@ -2948,6 +2955,8 @@ void TextEdit::_update_placeholder() { return; // Not in tree? } + const String placeholder_translated = atr(placeholder_text); + // Placeholder is generally smaller then text documents, and updates less so this should be fast enough for now. placeholder_data_buf->clear(); placeholder_data_buf->set_width(text.get_width()); @@ -2958,9 +2967,9 @@ void TextEdit::_update_placeholder() { placeholder_data_buf->set_direction((TextServer::Direction)text_direction); } placeholder_data_buf->set_preserve_control(draw_control_chars); - placeholder_data_buf->add_string(placeholder_text, theme_cache.font, theme_cache.font_size, language); + placeholder_data_buf->add_string(placeholder_translated, theme_cache.font, theme_cache.font_size, language); - placeholder_bidi_override = structured_text_parser(st_parser, st_args, placeholder_text); + placeholder_bidi_override = structured_text_parser(st_parser, st_args, placeholder_translated); if (placeholder_bidi_override.is_empty()) { TS->shaped_text_set_bidi_override(placeholder_data_buf->get_rid(), placeholder_bidi_override); } @@ -2985,7 +2994,7 @@ void TextEdit::_update_placeholder() { placeholder_wraped_rows.clear(); for (int i = 0; i <= wrap_amount; i++) { Vector2i line_range = placeholder_data_buf->get_line_range(i); - placeholder_wraped_rows.push_back(placeholder_text.substr(line_range.x, line_range.y - line_range.x)); + placeholder_wraped_rows.push_back(placeholder_translated.substr(line_range.x, line_range.y - line_range.x)); } } @@ -5305,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 2d3166270b..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) { @@ -2050,7 +2058,7 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 int text_width = item_width - theme_cache.inner_item_margin_left - theme_cache.inner_item_margin_right; if (p_item->cells[i].icon.is_valid()) { - text_width -= p_item->cells[i].get_icon_size().x + theme_cache.h_separation; + text_width -= _get_cell_icon_size(p_item->cells[i]).x + theme_cache.h_separation; } p_item->cells.write[i].text_buf->set_width(text_width); @@ -4544,6 +4552,8 @@ TreeItem *Tree::get_selected() const { void Tree::set_selected(TreeItem *p_item, int p_column) { ERR_FAIL_INDEX(p_column, columns.size()); ERR_FAIL_NULL(p_item); + ERR_FAIL_COND_MSG(p_item->get_tree() != this, "The provided TreeItem does not belong to this Tree. Ensure that the TreeItem is a part of the Tree before setting it as selected."); + select_single_item(p_item, get_root(), p_column); } 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 a350b97bc8..4ee81e5cb0 100644 --- a/scene/main/canvas_item.cpp +++ b/scene/main/canvas_item.cpp @@ -462,6 +462,10 @@ void CanvasItem::set_as_top_level(bool p_top_level) { _enter_canvas(); _notify_transform(); + + if (get_viewport()) { + get_viewport()->canvas_item_top_level_changed(); + } } void CanvasItem::_top_level_changed() { diff --git a/scene/main/node.cpp b/scene/main/node.cpp index f44e3de203..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); } @@ -1870,7 +1883,7 @@ void Node::_acquire_unique_name_in_owner() { Node **which = data.owner->data.owned_unique_nodes.getptr(key); if (which != nullptr && *which != this) { String which_path = is_inside_tree() ? (*which)->get_path() : data.owner->get_path_to(*which); - WARN_PRINT(vformat(RTR("Setting node name '%s' to be unique within scene for '%s', but it's already claimed by '%s'.\n'%s' is no longer set as having a unique name."), + WARN_PRINT(vformat("Setting node name '%s' to be unique within scene for '%s', but it's already claimed by '%s'.\n'%s' is no longer set as having a unique name.", get_name(), is_inside_tree() ? get_path() : data.owner->get_path_to(this), which_path, which_path)); data.unique_name_in_owner = false; return; @@ -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; } @@ -3442,7 +3473,6 @@ void Node::_bind_methods() { BIND_CONSTANT(NOTIFICATION_POST_ENTER_TREE); BIND_CONSTANT(NOTIFICATION_DISABLED); BIND_CONSTANT(NOTIFICATION_ENABLED); - BIND_CONSTANT(NOTIFICATION_NODE_RECACHE_REQUESTED); BIND_CONSTANT(NOTIFICATION_EDITOR_PRE_SAVE); BIND_CONSTANT(NOTIFICATION_EDITOR_POST_SAVE); diff --git a/scene/main/node.h b/scene/main/node.h index 94c6893170..cd1c31d784 100644 --- a/scene/main/node.h +++ b/scene/main/node.h @@ -350,7 +350,6 @@ public: NOTIFICATION_POST_ENTER_TREE = 27, NOTIFICATION_DISABLED = 28, NOTIFICATION_ENABLED = 29, - NOTIFICATION_NODE_RECACHE_REQUESTED = 30, //keep these linked to node NOTIFICATION_WM_MOUSE_ENTER = 1002, 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 ebe4f4c59d..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); @@ -1847,7 +1858,7 @@ SceneTree::SceneTree() { ProjectSettings::get_singleton()->set("rendering/environment/defaults/default_environment", ""); } else { // File was erased, notify user. - ERR_PRINT(RTR("Default Environment as specified in the project setting \"rendering/environment/defaults/default_environment\" could not be loaded.")); + ERR_PRINT("Default Environment as specified in the project setting \"rendering/environment/defaults/default_environment\" could not be loaded."); } } } 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 fd5aa65428..89ec5636ab 100644 --- a/scene/main/viewport.cpp +++ b/scene/main/viewport.cpp @@ -2408,8 +2408,8 @@ void Viewport::_gui_hide_control(Control *p_control) { if (gui.key_focus == p_control) { gui_release_focus(); } - if (gui.mouse_over == p_control) { - _drop_mouse_over(); + if (gui.mouse_over == p_control || gui.mouse_over_hierarchy.find(p_control) >= 0) { + _drop_mouse_over(p_control->get_parent_control()); } if (gui.drag_mouse_over == p_control) { gui.drag_mouse_over = nullptr; @@ -2431,8 +2431,8 @@ void Viewport::_gui_remove_control(Control *p_control) { if (gui.key_focus == p_control) { gui.key_focus = nullptr; } - if (gui.mouse_over == p_control) { - _drop_mouse_over(); + if (gui.mouse_over == p_control || gui.mouse_over_hierarchy.find(p_control) >= 0) { + _drop_mouse_over(p_control->get_parent_control()); } if (gui.drag_mouse_over == p_control) { gui.drag_mouse_over = nullptr; @@ -2442,6 +2442,106 @@ void Viewport::_gui_remove_control(Control *p_control) { } } +void Viewport::canvas_item_top_level_changed() { + _gui_update_mouse_over(); +} + +void Viewport::_gui_update_mouse_over() { + if (gui.mouse_over == nullptr || gui.mouse_over_hierarchy.is_empty()) { + 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; + LocalVector<int> needs_exit; + + CanvasItem *ancestor = gui.mouse_over; + bool removing = false; + bool reached_top = false; + while (ancestor) { + Control *ancestor_control = Object::cast_to<Control>(ancestor); + if (ancestor_control) { + int found = gui.mouse_over_hierarchy.find(ancestor_control); + if (found >= 0) { + // Remove the node if the propagation chain has been broken or it is now MOUSE_FILTER_IGNORE. + if (removing || ancestor_control->get_mouse_filter() == Control::MOUSE_FILTER_IGNORE) { + needs_exit.push_back(found); + } + } + if (found == 0) { + if (removing) { + // Stop if the chain has been broken and the top of the hierarchy has been reached. + break; + } + reached_top = true; + } + if (!removing && ancestor_control->get_mouse_filter() != Control::MOUSE_FILTER_IGNORE) { + new_mouse_over_hierarchy.push_back(ancestor_control); + // Add the node if it was not found and it is now not MOUSE_FILTER_IGNORE. + if (found < 0) { + needs_enter.push_back(ancestor_control); + } + } + if (ancestor_control->get_mouse_filter() == Control::MOUSE_FILTER_STOP) { + // MOUSE_FILTER_STOP breaks the propagation chain. + if (reached_top) { + break; + } + removing = true; + } + } + if (ancestor->is_set_as_top_level()) { + // Top level breaks the propagation chain. + if (reached_top) { + break; + } else { + removing = true; + ancestor = Object::cast_to<CanvasItem>(ancestor->get_parent()); + continue; + } + } + ancestor = ancestor->get_parent_item(); + } + if (needs_exit.is_empty() && needs_enter.is_empty()) { + 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); + gui.mouse_over = nullptr; + } + + // Send Mouse Exit notifications. + for (int exit_control_index : needs_exit) { + gui.mouse_over_hierarchy[exit_control_index]->notification(Control::NOTIFICATION_MOUSE_EXIT); + } + + // Update the mouse over hierarchy. + gui.mouse_over_hierarchy.resize(new_mouse_over_hierarchy.size()); + for (int i = 0; i < (int)new_mouse_over_hierarchy.size(); i++) { + gui.mouse_over_hierarchy[i] = new_mouse_over_hierarchy[new_mouse_over_hierarchy.size() - 1 - i]; + } + + // Send Mouse Enter notifications. + 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 { ERR_READ_THREAD_GUARD_V(nullptr); ERR_FAIL_COND_V(!is_inside_tree(), nullptr); @@ -3069,16 +3169,64 @@ void Viewport::_update_mouse_over(Vector2 p_pos) { // Look for Controls at mouse position. Control *over = gui_find_control(p_pos); bool notify_embedded_viewports = false; - if (over != gui.mouse_over) { - if (gui.mouse_over) { - _drop_mouse_over(); + if (over != gui.mouse_over || (!over && !gui.mouse_over_hierarchy.is_empty())) { + // Find the common ancestor of `gui.mouse_over` and `over`. + Control *common_ancestor = nullptr; + LocalVector<Control *> over_ancestors; + + if (over) { + // Get all ancestors that the mouse is currently over and need an enter signal. + CanvasItem *ancestor = over; + while (ancestor) { + Control *ancestor_control = Object::cast_to<Control>(ancestor); + if (ancestor_control) { + if (ancestor_control->get_mouse_filter() != Control::MOUSE_FILTER_IGNORE) { + int found = gui.mouse_over_hierarchy.find(ancestor_control); + if (found >= 0) { + common_ancestor = gui.mouse_over_hierarchy[found]; + break; + } + over_ancestors.push_back(ancestor_control); + } + if (ancestor_control->get_mouse_filter() == Control::MOUSE_FILTER_STOP) { + // MOUSE_FILTER_STOP breaks the propagation chain. + break; + } + } + if (ancestor->is_set_as_top_level()) { + // Top level breaks the propagation chain. + break; + } + ancestor = ancestor->get_parent_item(); + } + } + + if (gui.mouse_over || !gui.mouse_over_hierarchy.is_empty()) { + // Send Mouse Exit Self and Mouse Exit notifications. + _drop_mouse_over(common_ancestor); } else { _drop_physics_mouseover(); } - gui.mouse_over = over; if (over) { - over->notification(Control::NOTIFICATION_MOUSE_ENTER); + 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--) { + gui.mouse_over_hierarchy.push_back(over_ancestors[i]); + over_ancestors[i]->notification(Control::NOTIFICATION_MOUSE_ENTER); + } + + // Send Mouse Enter Self notification. + if (gui.mouse_over) { + gui.mouse_over->notification(Control::NOTIFICATION_MOUSE_ENTER_SELF); + } + + gui.sending_mouse_enter_exit_notifications = false; + notify_embedded_viewports = true; } } @@ -3119,7 +3267,13 @@ void Viewport::_mouse_leave_viewport() { notification(NOTIFICATION_VP_MOUSE_EXIT); } -void Viewport::_drop_mouse_over() { +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) { @@ -3131,10 +3285,22 @@ void Viewport::_drop_mouse_over() { v->_mouse_leave_viewport(); } } - if (gui.mouse_over->is_inside_tree()) { - gui.mouse_over->notification(Control::NOTIFICATION_MOUSE_EXIT); + + 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); } gui.mouse_over = nullptr; + + // Send Mouse Exit notifications to children first. Don't send to p_until_control or above. + int notification_until = p_until_control ? gui.mouse_over_hierarchy.find(p_until_control) + 1 : 0; + for (int i = gui.mouse_over_hierarchy.size() - 1; i >= notification_until; i--) { + if (gui.mouse_over_hierarchy[i]->is_inside_tree()) { + gui.mouse_over_hierarchy[i]->notification(Control::NOTIFICATION_MOUSE_EXIT); + } + } + 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) { @@ -3352,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); @@ -4019,17 +4190,6 @@ void Viewport::set_camera_3d_override_orthogonal(real_t p_size, real_t p_z_near, } } -void Viewport::set_disable_2d(bool p_disable) { - ERR_MAIN_THREAD_GUARD; - disable_2d = p_disable; - RenderingServer::get_singleton()->viewport_set_disable_2d(viewport, disable_2d); -} - -bool Viewport::is_2d_disabled() const { - ERR_READ_THREAD_GUARD_V(false); - return disable_2d; -} - void Viewport::set_disable_3d(bool p_disable) { ERR_MAIN_THREAD_GUARD; disable_3d = p_disable; @@ -4402,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); @@ -4472,9 +4633,6 @@ void Viewport::_bind_methods() { ClassDB::bind_method(D_METHOD("set_as_audio_listener_3d", "enable"), &Viewport::set_as_audio_listener_3d); ClassDB::bind_method(D_METHOD("is_audio_listener_3d"), &Viewport::is_audio_listener_3d); - ClassDB::bind_method(D_METHOD("set_disable_2d", "disable"), &Viewport::set_disable_2d); - ClassDB::bind_method(D_METHOD("is_2d_disabled"), &Viewport::is_2d_disabled); - ClassDB::bind_method(D_METHOD("set_disable_3d", "disable"), &Viewport::set_disable_3d); ClassDB::bind_method(D_METHOD("is_3d_disabled"), &Viewport::is_3d_disabled); @@ -4499,7 +4657,6 @@ void Viewport::_bind_methods() { ClassDB::bind_method(D_METHOD("set_vrs_texture", "texture"), &Viewport::set_vrs_texture); ClassDB::bind_method(D_METHOD("get_vrs_texture"), &Viewport::get_vrs_texture); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "disable_2d"), "set_disable_2d", "is_2d_disabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "disable_3d"), "set_disable_3d", "is_3d_disabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_xr"), "set_use_xr", "is_using_xr"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "own_world_3d"), "set_use_own_world_3d", "is_using_own_world_3d"); diff --git a/scene/main/viewport.h b/scene/main/viewport.h index 247c9e0928..43a89c8a0b 100644 --- a/scene/main/viewport.h +++ b/scene/main/viewport.h @@ -283,7 +283,6 @@ private: void _update_audio_listener_2d(); - bool disable_2d = false; bool disable_3d = false; void _propagate_viewport_notification(Node *p_node, int p_what); @@ -362,6 +361,8 @@ private: BitField<MouseButtonMask> mouse_focus_mask; 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; @@ -430,6 +431,7 @@ private: void _gui_remove_control(Control *p_control); void _gui_hide_control(Control *p_control); + void _gui_update_mouse_over(); void _gui_force_drag(Control *p_base, const Variant &p_data, Control *p_control); void _gui_set_drag_preview(Control *p_base, Control *p_control); @@ -456,7 +458,7 @@ private: void _canvas_layer_add(CanvasLayer *p_canvas_layer); void _canvas_layer_remove(CanvasLayer *p_canvas_layer); - void _drop_mouse_over(); + void _drop_mouse_over(Control *p_until_control = nullptr); void _drop_mouse_focus(); void _drop_physics_mouseover(bool p_paused_only = false); @@ -495,6 +497,7 @@ protected: public: void canvas_parent_mark_dirty(Node *p_node); + void canvas_item_top_level_changed(); uint64_t get_processed_events_count() const { return event_count; } @@ -607,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; @@ -739,9 +743,6 @@ public: void set_camera_3d_override_perspective(real_t p_fovy_degrees, real_t p_z_near, real_t p_z_far); void set_camera_3d_override_orthogonal(real_t p_size, real_t p_z_near, real_t p_z_far); - void set_disable_2d(bool p_disable); - bool is_2d_disabled() const; - void set_disable_3d(bool p_disable); bool is_3d_disabled() const; diff --git a/scene/main/window.cpp b/scene/main/window.cpp index 2c28dc31d6..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; @@ -1543,7 +1559,12 @@ void Window::_window_input(const Ref<InputEvent> &p_ev) { } } - if (p_ev->get_device() != InputEvent::DEVICE_ID_INTERNAL) { + // If the event needs to be handled in a Window-derived class, then it should overwrite + // `_input_from_window` instead of subscribing to the `window_input` signal, because the signal + // filters out internal events. + _input_from_window(p_ev); + + if (p_ev->get_device() != InputEvent::DEVICE_ID_INTERNAL && is_inside_tree()) { emit_signal(SceneStringNames::get_singleton()->window_input, p_ev); } diff --git a/scene/main/window.h b/scene/main/window.h index 8a54b6c7d3..3ef8d41b1b 100644 --- a/scene/main/window.h +++ b/scene/main/window.h @@ -239,6 +239,7 @@ protected: virtual void _post_popup() {} virtual void _update_theme_item_cache(); + virtual void _input_from_window(const Ref<InputEvent> &p_event) {} void _notification(int p_what); static void _bind_methods(); 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 b7c98b0ea9..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); @@ -1220,16 +1226,23 @@ void unregister_scene_types() { PhysicalSkyMaterial::cleanup_shader(); PanoramaSkyMaterial::cleanup_shader(); ProceduralSkyMaterial::cleanup_shader(); + FogMaterial::cleanup_shader(); #endif // _3D_DISABLED ParticleProcessMaterial::finish_shaders(); 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/animation_library.cpp b/scene/resources/animation_library.cpp index a69b1818d2..436bf88ec9 100644 --- a/scene/resources/animation_library.cpp +++ b/scene/resources/animation_library.cpp @@ -154,7 +154,8 @@ void AnimationLibrary::_bind_methods() { ClassDB::bind_method(D_METHOD("_set_data", "data"), &AnimationLibrary::_set_data); ClassDB::bind_method(D_METHOD("_get_data"), &AnimationLibrary::_get_data); - ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "_data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "_set_data", "_get_data"); + ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "_data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_data", "_get_data"); + ADD_SIGNAL(MethodInfo("animation_added", PropertyInfo(Variant::STRING_NAME, "name"))); ADD_SIGNAL(MethodInfo("animation_removed", PropertyInfo(Variant::STRING_NAME, "name"))); ADD_SIGNAL(MethodInfo("animation_renamed", PropertyInfo(Variant::STRING_NAME, "name"), PropertyInfo(Variant::STRING_NAME, "to_name"))); diff --git a/scene/resources/bone_map.cpp b/scene/resources/bone_map.cpp index 759d189bfa..5363b8ec79 100644 --- a/scene/resources/bone_map.cpp +++ b/scene/resources/bone_map.cpp @@ -37,7 +37,7 @@ bool BoneMap::_set(const StringName &p_path, const Variant &p_value) { set_skeleton_bone_name(which, p_value); return true; } - return true; + return false; } bool BoneMap::_get(const StringName &p_path, Variant &r_ret) const { @@ -47,7 +47,7 @@ bool BoneMap::_get(const StringName &p_path, Variant &r_ret) const { r_ret = get_skeleton_bone_name(which); return true; } - return true; + return false; } void BoneMap::_get_property_list(List<PropertyInfo> *p_list) const { 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/fog_material.cpp b/scene/resources/fog_material.cpp index aabaa54505..5e4f1970ee 100644 --- a/scene/resources/fog_material.cpp +++ b/scene/resources/fog_material.cpp @@ -82,7 +82,7 @@ float FogMaterial::get_edge_fade() const { void FogMaterial::set_density_texture(const Ref<Texture3D> &p_texture) { density_texture = p_texture; - RID tex_rid = p_texture.is_valid() ? p_texture->get_rid() : RID(); + Variant tex_rid = p_texture.is_valid() ? Variant(p_texture->get_rid()) : Variant(); RS::get_singleton()->material_set_param(_get_material(), "density_texture", tex_rid); } diff --git a/scene/resources/font.cpp b/scene/resources/font.cpp index 235f98b28e..13b22ae12c 100644 --- a/scene/resources/font.cpp +++ b/scene/resources/font.cpp @@ -1406,7 +1406,7 @@ Error FontFile::load_bitmap_font(const String &p_path) { oversampling = 1.0f; Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::READ); - ERR_FAIL_COND_V_MSG(f.is_null(), ERR_CANT_CREATE, vformat(RTR("Cannot open font from file: %s."), p_path)); + ERR_FAIL_COND_V_MSG(f.is_null(), ERR_CANT_CREATE, vformat("Cannot open font from file: %s.", p_path)); int base_size = 16; int height = 0; @@ -1425,7 +1425,7 @@ Error FontFile::load_bitmap_font(const String &p_path) { f->get_buffer((unsigned char *)&magic, 4); if (magic[0] == 'B' && magic[1] == 'M' && magic[2] == 'F') { // Binary BMFont file. - ERR_FAIL_COND_V_MSG(magic[3] != 3, ERR_CANT_CREATE, vformat(RTR("Version %d of BMFont is not supported (should be 3)."), (int)magic[3])); + ERR_FAIL_COND_V_MSG(magic[3] != 3, ERR_CANT_CREATE, vformat("Version %d of BMFont is not supported (should be 3).", (int)magic[3])); uint8_t block_type = f->get_8(); uint32_t block_size = f->get_32(); @@ -1435,7 +1435,7 @@ Error FontFile::load_bitmap_font(const String &p_path) { uint64_t off = f->get_position(); switch (block_type) { case 1: /* info */ { - ERR_FAIL_COND_V_MSG(block_size < 15, ERR_CANT_CREATE, RTR("Invalid BMFont info block size.")); + ERR_FAIL_COND_V_MSG(block_size < 15, ERR_CANT_CREATE, "Invalid BMFont info block size."); base_size = f->get_16(); if (base_size <= 0) { base_size = 16; @@ -1496,7 +1496,7 @@ Error FontFile::load_bitmap_font(const String &p_path) { set_fixed_size(base_size); } break; case 2: /* common */ { - ERR_FAIL_COND_V_MSG(block_size != 15, ERR_CANT_CREATE, RTR("Invalid BMFont common block size.")); + ERR_FAIL_COND_V_MSG(block_size != 15, ERR_CANT_CREATE, "Invalid BMFont common block size."); height = f->get_16(); ascent = f->get_16(); f->get_32(); // scale, skip @@ -1534,40 +1534,40 @@ Error FontFile::load_bitmap_font(const String &p_path) { Ref<Image> img; img.instantiate(); Error err = ImageLoader::load_image(file, img); - ERR_FAIL_COND_V_MSG(err != OK, ERR_FILE_CANT_READ, vformat(RTR("Can't load font texture: %s."), file)); + ERR_FAIL_COND_V_MSG(err != OK, ERR_FILE_CANT_READ, vformat("Can't load font texture: %s.", file)); if (packed) { if (ch[3] == 0) { // 4 x 8 bit monochrome, no outline outline = 0; - ERR_FAIL_COND_V_MSG(img->get_format() != Image::FORMAT_RGBA8, ERR_FILE_CANT_READ, RTR("Unsupported BMFont texture format.")); + ERR_FAIL_COND_V_MSG(img->get_format() != Image::FORMAT_RGBA8, ERR_FILE_CANT_READ, "Unsupported BMFont texture format."); _convert_packed_8bit(img, page, base_size); } else if ((ch[3] == 2) && (outline > 0)) { // 4 x 4 bit monochrome, gl + outline - ERR_FAIL_COND_V_MSG(img->get_format() != Image::FORMAT_RGBA8, ERR_FILE_CANT_READ, RTR("Unsupported BMFont texture format.")); + ERR_FAIL_COND_V_MSG(img->get_format() != Image::FORMAT_RGBA8, ERR_FILE_CANT_READ, "Unsupported BMFont texture format."); _convert_packed_4bit(img, page, base_size); } else { - ERR_FAIL_V_MSG(ERR_CANT_CREATE, RTR("Unsupported BMFont texture format.")); + ERR_FAIL_V_MSG(ERR_CANT_CREATE, "Unsupported BMFont texture format."); } } else { if ((ch[0] == 0) && (ch[1] == 0) && (ch[2] == 0) && (ch[3] == 0)) { // RGBA8 color, no outline outline = 0; - ERR_FAIL_COND_V_MSG(img->get_format() != Image::FORMAT_RGBA8, ERR_FILE_CANT_READ, RTR("Unsupported BMFont texture format.")); + ERR_FAIL_COND_V_MSG(img->get_format() != Image::FORMAT_RGBA8, ERR_FILE_CANT_READ, "Unsupported BMFont texture format."); set_texture_image(0, Vector2i(base_size, 0), page, img); } else if ((ch[0] == 2) && (ch[1] == 2) && (ch[2] == 2) && (ch[3] == 2) && (outline > 0)) { // RGBA4 color, gl + outline - ERR_FAIL_COND_V_MSG(img->get_format() != Image::FORMAT_RGBA8, ERR_FILE_CANT_READ, RTR("Unsupported BMFont texture format.")); + ERR_FAIL_COND_V_MSG(img->get_format() != Image::FORMAT_RGBA8, ERR_FILE_CANT_READ, "Unsupported BMFont texture format."); _convert_rgba_4bit(img, page, base_size); } else if ((first_gl_ch >= 0) && (first_ol_ch >= 0) && (outline > 0)) { // 1 x 8 bit monochrome, gl + outline - ERR_FAIL_COND_V_MSG(img->get_format() != Image::FORMAT_RGBA8 && img->get_format() != Image::FORMAT_L8, ERR_FILE_CANT_READ, RTR("Unsupported BMFont texture format.")); + ERR_FAIL_COND_V_MSG(img->get_format() != Image::FORMAT_RGBA8 && img->get_format() != Image::FORMAT_L8, ERR_FILE_CANT_READ, "Unsupported BMFont texture format."); _convert_mono_8bit(img, page, first_gl_ch, base_size, 0); _convert_mono_8bit(img, page, first_ol_ch, base_size, 1); } else if ((first_cm_ch >= 0) && (outline > 0)) { // 1 x 4 bit monochrome, gl + outline - ERR_FAIL_COND_V_MSG(img->get_format() != Image::FORMAT_RGBA8 && img->get_format() != Image::FORMAT_L8, ERR_FILE_CANT_READ, RTR("Unsupported BMFont texture format.")); + ERR_FAIL_COND_V_MSG(img->get_format() != Image::FORMAT_RGBA8 && img->get_format() != Image::FORMAT_L8, ERR_FILE_CANT_READ, "Unsupported BMFont texture format."); _convert_mono_4bit(img, page, first_cm_ch, base_size, 1); } else if (first_gl_ch >= 0) { // 1 x 8 bit monochrome, no outline outline = 0; - ERR_FAIL_COND_V_MSG(img->get_format() != Image::FORMAT_RGBA8 && img->get_format() != Image::FORMAT_L8, ERR_FILE_CANT_READ, RTR("Unsupported BMFont texture format.")); + ERR_FAIL_COND_V_MSG(img->get_format() != Image::FORMAT_RGBA8 && img->get_format() != Image::FORMAT_L8, ERR_FILE_CANT_READ, "Unsupported BMFont texture format."); _convert_mono_8bit(img, page, first_gl_ch, base_size, 0); } else { - ERR_FAIL_V_MSG(ERR_CANT_CREATE, RTR("Unsupported BMFont texture format.")); + ERR_FAIL_V_MSG(ERR_CANT_CREATE, "Unsupported BMFont texture format."); } } } @@ -1669,7 +1669,7 @@ Error FontFile::load_bitmap_font(const String &p_path) { } } break; default: { - ERR_FAIL_V_MSG(ERR_CANT_CREATE, RTR("Invalid BMFont block type.")); + ERR_FAIL_V_MSG(ERR_CANT_CREATE, "Invalid BMFont block type."); } break; } f->seek(off + block_size); @@ -1825,17 +1825,17 @@ Error FontFile::load_bitmap_font(const String &p_path) { Ref<Image> img; img.instantiate(); Error err = ImageLoader::load_image(file, img); - ERR_FAIL_COND_V_MSG(err != OK, ERR_FILE_CANT_READ, vformat(RTR("Can't load font texture: %s."), file)); + ERR_FAIL_COND_V_MSG(err != OK, ERR_FILE_CANT_READ, vformat("Can't load font texture: %s.", file)); if (packed) { if (ch[3] == 0) { // 4 x 8 bit monochrome, no outline outline = 0; - ERR_FAIL_COND_V_MSG(img->get_format() != Image::FORMAT_RGBA8, ERR_FILE_CANT_READ, RTR("Unsupported BMFont texture format.")); + ERR_FAIL_COND_V_MSG(img->get_format() != Image::FORMAT_RGBA8, ERR_FILE_CANT_READ, "Unsupported BMFont texture format."); _convert_packed_8bit(img, page, base_size); } else if ((ch[3] == 2) && (outline > 0)) { // 4 x 4 bit monochrome, gl + outline - ERR_FAIL_COND_V_MSG(img->get_format() != Image::FORMAT_RGBA8, ERR_FILE_CANT_READ, RTR("Unsupported BMFont texture format.")); + ERR_FAIL_COND_V_MSG(img->get_format() != Image::FORMAT_RGBA8, ERR_FILE_CANT_READ, "Unsupported BMFont texture format."); _convert_packed_4bit(img, page, base_size); } else { - ERR_FAIL_V_MSG(ERR_CANT_CREATE, RTR("Unsupported BMFont texture format.")); + ERR_FAIL_V_MSG(ERR_CANT_CREATE, "Unsupported BMFont texture format."); } } else { if ((ch[3] == 0) && (ch[0] == 4) && (ch[1] == 4) && (ch[2] == 4) && img->get_format() == Image::FORMAT_RGBA8) { // might be RGBA8 color, no outline (color part of the image should be sold white, but some apps designed for Godot 3 generate color fonts with this config) @@ -1843,24 +1843,24 @@ Error FontFile::load_bitmap_font(const String &p_path) { set_texture_image(0, Vector2i(base_size, 0), page, img); } else if ((ch[0] == 0) && (ch[1] == 0) && (ch[2] == 0) && (ch[3] == 0)) { // RGBA8 color, no outline outline = 0; - ERR_FAIL_COND_V_MSG(img->get_format() != Image::FORMAT_RGBA8, ERR_FILE_CANT_READ, RTR("Unsupported BMFont texture format.")); + ERR_FAIL_COND_V_MSG(img->get_format() != Image::FORMAT_RGBA8, ERR_FILE_CANT_READ, "Unsupported BMFont texture format."); set_texture_image(0, Vector2i(base_size, 0), page, img); } else if ((ch[0] == 2) && (ch[1] == 2) && (ch[2] == 2) && (ch[3] == 2) && (outline > 0)) { // RGBA4 color, gl + outline - ERR_FAIL_COND_V_MSG(img->get_format() != Image::FORMAT_RGBA8, ERR_FILE_CANT_READ, RTR("Unsupported BMFont texture format.")); + ERR_FAIL_COND_V_MSG(img->get_format() != Image::FORMAT_RGBA8, ERR_FILE_CANT_READ, "Unsupported BMFont texture format."); _convert_rgba_4bit(img, page, base_size); } else if ((first_gl_ch >= 0) && (first_ol_ch >= 0) && (outline > 0)) { // 1 x 8 bit monochrome, gl + outline - ERR_FAIL_COND_V_MSG(img->get_format() != Image::FORMAT_RGBA8 && img->get_format() != Image::FORMAT_L8, ERR_FILE_CANT_READ, RTR("Unsupported BMFont texture format.")); + ERR_FAIL_COND_V_MSG(img->get_format() != Image::FORMAT_RGBA8 && img->get_format() != Image::FORMAT_L8, ERR_FILE_CANT_READ, "Unsupported BMFont texture format."); _convert_mono_8bit(img, page, first_gl_ch, base_size, 0); _convert_mono_8bit(img, page, first_ol_ch, base_size, 1); } else if ((first_cm_ch >= 0) && (outline > 0)) { // 1 x 4 bit monochrome, gl + outline - ERR_FAIL_COND_V_MSG(img->get_format() != Image::FORMAT_RGBA8 && img->get_format() != Image::FORMAT_L8, ERR_FILE_CANT_READ, RTR("Unsupported BMFont texture format.")); + ERR_FAIL_COND_V_MSG(img->get_format() != Image::FORMAT_RGBA8 && img->get_format() != Image::FORMAT_L8, ERR_FILE_CANT_READ, "Unsupported BMFont texture format."); _convert_mono_4bit(img, page, first_cm_ch, base_size, 1); } else if (first_gl_ch >= 0) { // 1 x 8 bit monochrome, no outline outline = 0; - ERR_FAIL_COND_V_MSG(img->get_format() != Image::FORMAT_RGBA8 && img->get_format() != Image::FORMAT_L8, ERR_FILE_CANT_READ, RTR("Unsupported BMFont texture format.")); + ERR_FAIL_COND_V_MSG(img->get_format() != Image::FORMAT_RGBA8 && img->get_format() != Image::FORMAT_L8, ERR_FILE_CANT_READ, "Unsupported BMFont texture format."); _convert_mono_8bit(img, page, first_gl_ch, base_size, 0); } else { - ERR_FAIL_V_MSG(ERR_CANT_CREATE, RTR("Unsupported BMFont texture format.")); + ERR_FAIL_V_MSG(ERR_CANT_CREATE, "Unsupported BMFont texture format."); } } } diff --git a/scene/resources/immediate_mesh.cpp b/scene/resources/immediate_mesh.cpp index 3507df8bd8..dde556c264 100644 --- a/scene/resources/immediate_mesh.cpp +++ b/scene/resources/immediate_mesh.cpp @@ -166,7 +166,7 @@ void ImmediateMesh::surface_end() { normal_tangent_stride += sizeof(uint32_t); } uint32_t tangent_offset = 0; - if (uses_tangents) { + if (uses_tangents || uses_normals) { format |= ARRAY_FORMAT_TANGENT; tangent_offset = vertex_stride * vertices.size() + normal_tangent_stride; normal_tangent_stride += sizeof(uint32_t); @@ -202,9 +202,16 @@ void ImmediateMesh::surface_end() { *normal = value; } - if (uses_tangents) { + if (uses_tangents || uses_normals) { uint32_t *tangent = (uint32_t *)&surface_vertex_ptr[i * normal_tangent_stride + tangent_offset]; - Vector2 t = tangents[i].normal.octahedron_tangent_encode(tangents[i].d); + Vector2 t; + if (uses_tangents) { + t = tangents[i].normal.octahedron_tangent_encode(tangents[i].d); + } else { + Vector3 tan = Vector3(0.0, 1.0, 0.0).cross(normals[i].normalized()); + t = tan.octahedron_tangent_encode(1.0); + } + uint32_t value = 0; value |= (uint16_t)CLAMP(t.x * 65535, 0, 65535); value |= (uint16_t)CLAMP(t.y * 65535, 0, 65535) << 16; 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 6997777623..1b74063ff4 100644 --- a/scene/resources/material.cpp +++ b/scene/resources/material.cpp @@ -1930,7 +1930,7 @@ void BaseMaterial3D::set_texture(TextureParam p_param, const Ref<Texture2D> &p_t ERR_FAIL_INDEX(p_param, TEXTURE_MAX); textures[p_param] = p_texture; - RID rid = p_texture.is_valid() ? p_texture->get_rid() : RID(); + Variant rid = p_texture.is_valid() ? Variant(p_texture->get_rid()) : Variant(); RS::get_singleton()->material_set_param(_get_material(), shader_names->texture_names[p_param], rid); if (p_texture.is_valid() && p_param == TEXTURE_ALBEDO) { @@ -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/mesh.cpp b/scene/resources/mesh.cpp index 25a65b5cc4..6f12539a6d 100644 --- a/scene/resources/mesh.cpp +++ b/scene/resources/mesh.cpp @@ -870,6 +870,8 @@ void Mesh::_bind_methods() { BIND_BITFIELD_FLAG(ARRAY_FLAG_USE_8_BONE_WEIGHTS); BIND_BITFIELD_FLAG(ARRAY_FLAG_USES_EMPTY_VERTEX_ARRAY); + BIND_BITFIELD_FLAG(ARRAY_FLAG_COMPRESS_ATTRIBUTES); + BIND_ENUM_CONSTANT(BLEND_SHAPE_MODE_NORMALIZED); BIND_ENUM_CONSTANT(BLEND_SHAPE_MODE_RELATIVE); @@ -1019,7 +1021,7 @@ void _fix_array_compatibility(const Vector<uint8_t> &p_src, uint64_t p_old_forma uint32_t dst_offsets[Mesh::ARRAY_MAX]; RenderingServer::get_singleton()->mesh_surface_make_offsets_from_format(p_new_format & (~RS::ARRAY_FORMAT_INDEX), p_elements, 0, dst_offsets, dst_vertex_stride, dst_normal_tangent_stride, dst_attribute_stride, dst_skin_stride); - vertex_data.resize(dst_vertex_stride * p_elements); + vertex_data.resize((dst_vertex_stride + dst_normal_tangent_stride) * p_elements); attribute_data.resize(dst_attribute_stride * p_elements); skin_data.resize(dst_skin_stride * p_elements); @@ -1712,7 +1714,7 @@ bool ArrayMesh::_get(const StringName &p_name, Variant &r_ret) const { return true; } - return true; + return false; } void ArrayMesh::reset_state() { 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 9ceddbc7b1..4ed1980826 100644 --- a/scene/resources/particle_process_material.cpp +++ b/scene/resources/particle_process_material.cpp @@ -31,7 +31,6 @@ #include "particle_process_material.h" #include "core/version.h" -#include "scene/resources/curve_texture.h" Mutex ParticleProcessMaterial::material_mutex; SelfList<ParticleProcessMaterial>::List *ParticleProcessMaterial::dirty_materials = nullptr; @@ -87,9 +86,9 @@ void ParticleProcessMaterial::init_shaders() { shader_names->tangent_accel_texture = "tangent_accel_texture"; shader_names->damping_texture = "damping_texture"; shader_names->scale_texture = "scale_curve"; - shader_names->hue_variation_texture = "hue_variation_texture"; - shader_names->anim_speed_texture = "anim_speed_texture"; - shader_names->anim_offset_texture = "anim_offset_texture"; + shader_names->hue_variation_texture = "hue_rot_curve"; + shader_names->anim_speed_texture = "animation_speed_curve"; + shader_names->anim_offset_texture = "animation_offset_curve"; shader_names->directional_velocity_texture = "directional_velocity_curve"; shader_names->scale_over_velocity_texture = "scale_over_velocity_curve"; @@ -531,10 +530,10 @@ void ParticleProcessMaterial::_update_shader() { code += "\n"; code += "void calculate_initial_physical_params(inout PhysicalParameters params, inout uint alt_seed){\n"; - code += " params.linear_accel = mix(linear_accel_min, linear_accel_min ,rand_from_seed(alt_seed));\n"; - code += " params.radial_accel = mix(radial_accel_min, radial_accel_min,rand_from_seed(alt_seed));\n"; - code += " params.tangent_accel = mix(tangent_accel_min, tangent_accel_max,rand_from_seed(alt_seed));\n"; - code += " params.damping = mix(damping_min, damping_max,rand_from_seed(alt_seed));\n"; + code += " params.linear_accel = mix(linear_accel_min, linear_accel_max, rand_from_seed(alt_seed));\n"; + code += " params.radial_accel = mix(radial_accel_min, radial_accel_max, rand_from_seed(alt_seed));\n"; + code += " params.tangent_accel = mix(tangent_accel_min, tangent_accel_max, rand_from_seed(alt_seed));\n"; + code += " params.damping = mix(damping_min, damping_max, rand_from_seed(alt_seed));\n"; code += "}\n"; code += "\n"; code += "void calculate_initial_dynamics_params(inout DynamicsParameters params,inout uint alt_seed){\n"; @@ -672,13 +671,18 @@ void ParticleProcessMaterial::_update_shader() { code += " float orbit_amount = param.orbit_velocity;\n"; if (tex_parameters[PARAM_ORBIT_VELOCITY].is_valid()) { - code += " orbit_amount *= texture(orbit_velocity_curve, vec2(lifetime)).r;\n"; + CurveTexture *texture = Object::cast_to<CurveTexture>(tex_parameters[PARAM_ORBIT_VELOCITY].ptr()); + if (texture) { + code += " orbit_amount *= texture(orbit_velocity_curve, vec2(lifetime)).r;\n"; + } else { + code += " orbit_amount *= texture(orbit_velocity_curve, vec2(lifetime)).b;\n"; + } } code += " if (orbit_amount != 0.0) {\n"; code += " vec3 pos = transform[3].xyz;\n"; code += " vec3 org = emission_transform[3].xyz;\n"; code += " vec3 diff = pos - org;\n"; - code += " float ang = orbit_amount * pi * 2.0;\n"; + code += " float ang = orbit_amount * pi * 2.0 * delta;\n"; code += " mat2 rot = mat2(vec2(cos(ang), -sin(ang)), vec2(sin(ang), cos(ang)));\n"; code += " displacement.xy -= diff.xy;\n"; code += " displacement.xy += rot * diff.xy;\n"; @@ -687,8 +691,8 @@ void ParticleProcessMaterial::_update_shader() { code += " vec3 orbit_velocities = vec3(param.orbit_velocity);\n"; code += " orbit_velocities *= texture(orbit_velocity_curve, vec2(lifetime)).rgb;\n"; - code += " orbit_velocities *= degree_to_rad;\n"; - code += " orbit_velocities *= delta/total_lifetime; // we wanna process those by the delta angle\n"; + code += " orbit_velocities *= pi * 2.0;\n"; + code += " orbit_velocities *= delta; // we wanna process those by the delta angle\n"; code += " //vec3 local_velocity_pivot = ((emission_transform) * vec4(velocity_pivot,1.0)).xyz;\n"; code += " // X axis\n"; code += " vec3 local_pos = (inverse(emission_transform) * transform[3]).xyz;\n"; @@ -719,7 +723,7 @@ void ParticleProcessMaterial::_update_shader() { code += " local_pos -= velocity_pivot;\n"; code += " local_pos.z = 0.;\n"; code += " mat3 z_rotation_mat = mat3(\n"; - code += " vec3(cos(orbit_velocities.z),-sin(orbit_velocities.z),0.0),\n"; + code += " vec3(cos(orbit_velocities.z),sin(orbit_velocities.z),0.0),\n"; code += " vec3(-sin(orbit_velocities.z),cos(orbit_velocities.z), 0.0),\n"; code += " vec3(0.0,0.0,1.0)\n"; code += " );\n"; @@ -758,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"; @@ -770,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"; @@ -919,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"; @@ -958,21 +965,22 @@ void ParticleProcessMaterial::_update_shader() { code += " {\n"; code += " // copied from previous version\n"; code += " if (physics_params.damping > 0.0) {\n"; + code += " float v = length(VELOCITY);\n"; if (!particle_flags[PARTICLE_FLAG_DAMPING_AS_FRICTION]) { - code += " float v = length(VELOCITY);\n"; + code += " v -= physics_params.damping * DELTA;\n"; + } else { + code += " if (v > 0.001) {\n"; code += " // Realistic friction formula. We assume the mass of a particle to be 0.05kg.\n"; code += " float damp = v * v * physics_params.damping * 0.05 * DELTA;\n"; code += " v -= damp;\n"; - code += " if (v < 0.0) {\n"; - code += " VELOCITY = vec3(0.0);\n"; - code += " } else {\n"; - code += " VELOCITY = normalize(VELOCITY) * v;\n"; - code += " }\n"; - } else { - code += " if (length(VELOCITY) > 0.01){\n"; - code += " VELOCITY -= normalize(VELOCITY) * length(VELOCITY) * (physics_params.damping) * DELTA;\n"; - code += " }\n"; + code += " }\n"; } + + code += " if (v < 0.0) {\n"; + code += " VELOCITY = vec3(0.0);\n"; + code += " } else {\n"; + code += " VELOCITY = normalize(VELOCITY) * v;\n"; + code += " }\n"; code += " }\n"; code += " \n"; code += " }\n"; @@ -985,10 +993,6 @@ void ParticleProcessMaterial::_update_shader() { code += " VELOCITY = mix(VELOCITY,vec3(0.0),clamp(collision_friction, 0.0, 1.0));\n"; code += " } else {\n"; code += " VELOCITY = vec3(0.0);\n"; - // If turbulence is enabled, set the noise direction to up so the turbulence color is "neutral" - if (turbulence_enabled) { - code += " noise_direction = vec3(1.0, 0.0, 0.0);\n"; - } code += " }\n"; code += " }\n"; } else if (collision_mode == COLLISION_HIDE_ON_CONTACT) { @@ -996,7 +1000,6 @@ void ParticleProcessMaterial::_update_shader() { code += " ACTIVE = false;\n"; code += " }\n"; } - code += " vec3 final_velocity = controlled_displacement + VELOCITY;\n"; code += " \n"; code += " // turbulence before limiting\n"; if (turbulence_enabled) { @@ -1007,17 +1010,22 @@ void ParticleProcessMaterial::_update_shader() { } code += " \n"; code += " vec3 noise_direction = get_noise_direction(TRANSFORM[3].xyz);\n"; - // The following snippet causes massive performance hit. We don't need it as long as collision is disabled. - // Refer to GH-83744 for more info. - if (collision_mode != COLLISION_DISABLED) { + + // Godot detects when the COLLIDED keyword is used. If it's used anywhere in the shader then Godot will generate the screen space SDF for collisions. + // We don't need it as long as collision is disabled. Refer to GH-83744 for more info. + if (collision_mode == COLLISION_RIGID) { code += " if (!COLLIDED) {\n"; - code += " \n"; - code += " float vel_mag = length(final_velocity);\n"; - code += " float vel_infl = clamp(dynamic_params.turb_influence * turbulence_influence, 0.0,1.0);\n"; - code += " final_velocity = mix(final_velocity, normalize(noise_direction) * vel_mag * (1.0 + (1.0 - vel_infl) * 0.2), vel_infl);\n"; + } + code += " float vel_mag = length(VELOCITY);\n"; + code += " float vel_infl = clamp(dynamic_params.turb_influence * turbulence_influence, 0.0,1.0);\n"; + code += " VELOCITY = mix(VELOCITY, normalize(noise_direction) * vel_mag * (1.0 + (1.0 - vel_infl) * 0.2), vel_infl);\n"; + code += " vel_mag = length(controlled_displacement);\n"; + code += " controlled_displacement = mix(controlled_displacement, normalize(noise_direction) * vel_mag * (1.0 + (1.0 - vel_infl) * 0.2), vel_infl);\n"; + if (collision_mode == COLLISION_RIGID) { code += " }\n"; } } + code += " vec3 final_velocity = controlled_displacement + VELOCITY;\n"; code += " \n"; code += " // limit velocity\n"; if (velocity_limit_curve.is_valid()) { @@ -1375,7 +1383,7 @@ void ParticleProcessMaterial::set_param_texture(Parameter p_param, const Ref<Tex tex_parameters[p_param] = p_texture; - RID tex_rid = p_texture.is_valid() ? p_texture->get_rid() : RID(); + Variant tex_rid = p_texture.is_valid() ? Variant(p_texture->get_rid()) : Variant(); switch (p_param) { case PARAM_INITIAL_LINEAR_VELOCITY: { @@ -1387,7 +1395,7 @@ void ParticleProcessMaterial::set_param_texture(Parameter p_param, const Ref<Tex } break; case PARAM_ORBIT_VELOCITY: { RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->orbit_velocity_texture, tex_rid); - _adjust_curve_range(p_texture, -500, 500); + _adjust_curve_range(p_texture, -2, 2); notify_property_list_changed(); } break; case PARAM_LINEAR_ACCEL: { @@ -1471,7 +1479,7 @@ Color ParticleProcessMaterial::get_color() const { void ParticleProcessMaterial::set_color_ramp(const Ref<Texture2D> &p_texture) { color_ramp = p_texture; - RID tex_rid = p_texture.is_valid() ? p_texture->get_rid() : RID(); + Variant tex_rid = p_texture.is_valid() ? Variant(p_texture->get_rid()) : Variant(); RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->color_ramp, tex_rid); _queue_shader_change(); notify_property_list_changed(); @@ -1483,7 +1491,7 @@ Ref<Texture2D> ParticleProcessMaterial::get_color_ramp() const { void ParticleProcessMaterial::set_color_initial_ramp(const Ref<Texture2D> &p_texture) { color_initial_ramp = p_texture; - RID tex_rid = p_texture.is_valid() ? p_texture->get_rid() : RID(); + Variant tex_rid = p_texture.is_valid() ? Variant(p_texture->get_rid()) : Variant(); RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->color_initial_ramp, tex_rid); _queue_shader_change(); notify_property_list_changed(); @@ -1504,7 +1512,7 @@ void ParticleProcessMaterial::set_particle_flag(ParticleFlags p_particle_flag, b void ParticleProcessMaterial::set_alpha_curve(const Ref<Texture2D> &p_texture) { alpha_curve = p_texture; - RID tex_rid = p_texture.is_valid() ? p_texture->get_rid() : RID(); + Variant tex_rid = p_texture.is_valid() ? Variant(p_texture->get_rid()) : Variant(); RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->alpha_ramp, tex_rid); _queue_shader_change(); notify_property_list_changed(); @@ -1516,7 +1524,7 @@ Ref<Texture2D> ParticleProcessMaterial::get_alpha_curve() const { void ParticleProcessMaterial::set_emission_curve(const Ref<Texture2D> &p_texture) { emission_curve = p_texture; - RID tex_rid = p_texture.is_valid() ? p_texture->get_rid() : RID(); + Variant tex_rid = p_texture.is_valid() ? Variant(p_texture->get_rid()) : Variant(); RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->emission_ramp, tex_rid); _queue_shader_change(); notify_property_list_changed(); @@ -1528,7 +1536,7 @@ Ref<Texture2D> ParticleProcessMaterial::get_emission_curve() const { void ParticleProcessMaterial::set_velocity_limit_curve(const Ref<Texture2D> &p_texture) { velocity_limit_curve = p_texture; - RID tex_rid = p_texture.is_valid() ? p_texture->get_rid() : RID(); + Variant tex_rid = p_texture.is_valid() ? Variant(p_texture->get_rid()) : Variant(); RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->velocity_limit_curve, tex_rid); _queue_shader_change(); notify_property_list_changed(); @@ -1562,19 +1570,19 @@ void ParticleProcessMaterial::set_emission_box_extents(Vector3 p_extents) { void ParticleProcessMaterial::set_emission_point_texture(const Ref<Texture2D> &p_points) { emission_point_texture = p_points; - RID tex_rid = p_points.is_valid() ? p_points->get_rid() : RID(); + Variant tex_rid = p_points.is_valid() ? Variant(p_points->get_rid()) : Variant(); RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->emission_texture_points, tex_rid); } void ParticleProcessMaterial::set_emission_normal_texture(const Ref<Texture2D> &p_normals) { emission_normal_texture = p_normals; - RID tex_rid = p_normals.is_valid() ? p_normals->get_rid() : RID(); + Variant tex_rid = p_normals.is_valid() ? Variant(p_normals->get_rid()) : Variant(); RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->emission_texture_normal, tex_rid); } void ParticleProcessMaterial::set_emission_color_texture(const Ref<Texture2D> &p_colors) { emission_color_texture = p_colors; - RID tex_rid = p_colors.is_valid() ? p_colors->get_rid() : RID(); + Variant tex_rid = p_colors.is_valid() ? Variant(p_colors->get_rid()) : Variant(); RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->emission_texture_color, tex_rid); _queue_shader_change(); } @@ -2100,8 +2108,8 @@ void ParticleProcessMaterial::_bind_methods() { ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "directional_velocity_max", PROPERTY_HINT_RANGE, "-720,720,0.01,or_less,or_greater"), "set_param_max", "get_param_max", PARAM_DIRECTIONAL_VELOCITY); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "directional_velocity_curve", PROPERTY_HINT_RESOURCE_TYPE, "CurveXYZTexture"), "set_param_texture", "get_param_texture", PARAM_DIRECTIONAL_VELOCITY); ADD_SUBGROUP("Orbit Velocity", "orbit_"); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "orbit_velocity_min", PROPERTY_HINT_RANGE, "-1000,1000,0.01,or_less,or_greater"), "set_param_min", "get_param_min", PARAM_ORBIT_VELOCITY); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "orbit_velocity_max", PROPERTY_HINT_RANGE, "-1000,1000,0.01,or_less,or_greater"), "set_param_max", "get_param_max", PARAM_ORBIT_VELOCITY); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "orbit_velocity_min", PROPERTY_HINT_RANGE, "-2,2,0.001,or_less,or_greater"), "set_param_min", "get_param_min", PARAM_ORBIT_VELOCITY); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "orbit_velocity_max", PROPERTY_HINT_RANGE, "-2,2,0.001,or_less,or_greater"), "set_param_max", "get_param_max", PARAM_ORBIT_VELOCITY); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "orbit_velocity_curve", PROPERTY_HINT_RESOURCE_TYPE, "CurveTexture,CurveXYZTexture"), "set_param_texture", "get_param_texture", PARAM_ORBIT_VELOCITY); ADD_SUBGROUP("Radial Velocity", "radial_"); ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "radial_velocity_min", PROPERTY_HINT_RANGE, "-1000,1000,0.01,or_less,or_greater"), "set_param_min", "get_param_min", PARAM_RADIAL_VELOCITY); @@ -2162,11 +2170,11 @@ void ParticleProcessMaterial::_bind_methods() { ADD_GROUP("Turbulence", "turbulence_"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "turbulence_enabled"), "set_turbulence_enabled", "get_turbulence_enabled"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "turbulence_noise_strength", PROPERTY_HINT_RANGE, "0,20,0.01"), "set_turbulence_noise_strength", "get_turbulence_noise_strength"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "turbulence_noise_scale", PROPERTY_HINT_RANGE, "0,10,0.01"), "set_turbulence_noise_scale", "get_turbulence_noise_scale"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "turbulence_noise_scale", PROPERTY_HINT_RANGE, "0,10,0.001,or_greater"), "set_turbulence_noise_scale", "get_turbulence_noise_scale"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "turbulence_noise_speed"), "set_turbulence_noise_speed", "get_turbulence_noise_speed"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "turbulence_noise_speed_random", PROPERTY_HINT_RANGE, "0,4,0.01"), "set_turbulence_noise_speed_random", "get_turbulence_noise_speed_random"); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "turbulence_influence_min", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_min", "get_param_min", PARAM_TURB_VEL_INFLUENCE); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "turbulence_influence_max", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_max", "get_param_max", PARAM_TURB_VEL_INFLUENCE); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "turbulence_influence_min", PROPERTY_HINT_RANGE, "0,1,0.001"), "set_param_min", "get_param_min", PARAM_TURB_VEL_INFLUENCE); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "turbulence_influence_max", PROPERTY_HINT_RANGE, "0,1,0.001"), "set_param_max", "get_param_max", PARAM_TURB_VEL_INFLUENCE); ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "turbulence_initial_displacement_min", PROPERTY_HINT_RANGE, "-100,100,0.1"), "set_param_min", "get_param_min", PARAM_TURB_INIT_DISPLACEMENT); ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "turbulence_initial_displacement_max", PROPERTY_HINT_RANGE, "-100,100,0.1"), "set_param_max", "get_param_max", PARAM_TURB_INIT_DISPLACEMENT); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "turbulence_influence_over_life", PROPERTY_HINT_RESOURCE_TYPE, "CurveTexture"), "set_param_texture", "get_param_texture", PARAM_TURB_INFLUENCE_OVER_LIFE); diff --git a/scene/resources/particle_process_material.h b/scene/resources/particle_process_material.h index 83228104b2..5ed8b61c77 100644 --- a/scene/resources/particle_process_material.h +++ b/scene/resources/particle_process_material.h @@ -33,6 +33,7 @@ #include "core/templates/rid.h" #include "core/templates/self_list.h" +#include "scene/resources/curve_texture.h" #include "scene/resources/material.h" /* @@ -112,7 +113,7 @@ private: // Consider this when extending ParticleFlags, EmissionShape, or SubEmitterMode. uint64_t texture_mask : PARAM_MAX; uint64_t texture_color : 1; - uint64_t particle_flags : PARTICLE_FLAG_MAX - 1; + uint64_t particle_flags : PARTICLE_FLAG_MAX; uint64_t emission_shape : 3; uint64_t invalid_key : 1; uint64_t has_emission_color : 1; @@ -125,6 +126,7 @@ private: uint64_t alpha_curve : 1; uint64_t emission_curve : 1; uint64_t has_initial_ramp : 1; + uint64_t orbit_uses_curve_xyz : 1; MaterialKey() { memset(this, 0, sizeof(MaterialKey)); @@ -165,6 +167,8 @@ private: mk.alpha_curve = alpha_curve.is_valid() ? 1 : 0; mk.emission_curve = emission_curve.is_valid() ? 1 : 0; mk.has_initial_ramp = color_initial_ramp.is_valid() ? 1 : 0; + CurveXYZTexture *texture = Object::cast_to<CurveXYZTexture>(tex_parameters[PARAM_ORBIT_VELOCITY].ptr()); + mk.orbit_uses_curve_xyz = texture ? 1 : 0; for (int i = 0; i < PARAM_MAX; i++) { if (tex_parameters[i].is_valid()) { diff --git a/scene/resources/primitive_meshes.cpp b/scene/resources/primitive_meshes.cpp index b323710743..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(); } @@ -1300,7 +1302,11 @@ void PlaneMesh::_create_mesh_array(Array &p_arr) const { points.push_back(Vector3(-x, z, 0.0) + center_offset); } normals.push_back(normal); - ADD_TANGENT(1.0, 0.0, 0.0, 1.0); + if (orientation == FACE_X) { + ADD_TANGENT(0.0, 0.0, -1.0, 1.0); + } else { + ADD_TANGENT(1.0, 0.0, 0.0, 1.0); + } uvs.push_back(Vector2(1.0 - u, 1.0 - v)); /* 1.0 - uv to match orientation with Quad */ point++; @@ -1915,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(); } @@ -2722,7 +2729,6 @@ void TextMesh::_generate_glyph_mesh_data(const GlyphMeshKey &p_key, const Glyph GlyphMeshData &gl_data = cache[p_key]; Dictionary d = TS->font_get_glyph_contours(p_gl.font_rid, p_gl.font_size, p_gl.index); - Vector2 origin = Vector2(p_gl.x_off, p_gl.y_off) * pixel_size; PackedVector3Array points = d["points"]; PackedInt32Array contours = d["contours"]; @@ -2742,7 +2748,7 @@ void TextMesh::_generate_glyph_mesh_data(const GlyphMeshKey &p_key, const Glyph for (int32_t j = start; j <= end; j++) { if (points[j].z == TextServer::CONTOUR_CURVE_TAG_ON) { // Point on the curve. - Vector2 p = Vector2(points[j].x, points[j].y) * pixel_size + origin; + Vector2 p = Vector2(points[j].x, points[j].y) * pixel_size; polygon.push_back(ContourPoint(p, true)); } else if (points[j].z == TextServer::CONTOUR_CURVE_TAG_OFF_CONIC) { // Conic Bezier arc. @@ -2776,7 +2782,7 @@ void TextMesh::_generate_glyph_mesh_data(const GlyphMeshKey &p_key, const Glyph real_t t2 = t * t; Vector2 point = p1 + omt2 * (p0 - p1) + t2 * (p2 - p1); - Vector2 p = point * pixel_size + origin; + Vector2 p = point * pixel_size; polygon.push_back(ContourPoint(p, false)); t += step; } @@ -2810,7 +2816,7 @@ void TextMesh::_generate_glyph_mesh_data(const GlyphMeshKey &p_key, const Glyph real_t t = step; while (t < 1.0) { Vector2 point = p0.bezier_interpolate(p1, p2, p3, t); - Vector2 p = point * pixel_size + origin; + Vector2 p = point * pixel_size; polygon.push_back(ContourPoint(p, false)); t += step; } @@ -3045,6 +3051,7 @@ void TextMesh::_create_mesh_array(Array &p_arr) const { GlyphMeshKey key = GlyphMeshKey(glyphs[j].font_rid.get_id(), glyphs[j].index); _generate_glyph_mesh_data(key, glyphs[j]); GlyphMeshData &gl_data = cache[key]; + const Vector2 gl_of = Vector2(glyphs[j].x_off, glyphs[j].y_off) * pixel_size; p_size += glyphs[j].repeat * gl_data.triangles.size() * ((has_depth) ? 2 : 1); i_size += glyphs[j].repeat * gl_data.triangles.size() * ((has_depth) ? 2 : 1); @@ -3057,10 +3064,10 @@ void TextMesh::_create_mesh_array(Array &p_arr) const { } for (int r = 0; r < glyphs[j].repeat; r++) { - min_p.x = MIN(gl_data.min_p.x + offset.x, min_p.x); - min_p.y = MIN(gl_data.min_p.y - offset.y, min_p.y); - max_p.x = MAX(gl_data.max_p.x + offset.x, max_p.x); - max_p.y = MAX(gl_data.max_p.y - offset.y, max_p.y); + min_p.x = MIN(gl_data.min_p.x + offset.x + gl_of.x, min_p.x); + min_p.y = MIN(gl_data.min_p.y - offset.y + gl_of.y, min_p.y); + max_p.x = MAX(gl_data.max_p.x + offset.x + gl_of.x, max_p.x); + max_p.y = MAX(gl_data.max_p.y - offset.y + gl_of.y, max_p.y); offset.x += glyphs[j].advance * pixel_size; } @@ -3126,12 +3133,13 @@ void TextMesh::_create_mesh_array(Array &p_arr) const { int64_t ts = gl_data.triangles.size(); const Vector2 *ts_ptr = gl_data.triangles.ptr(); + const Vector2 gl_of = Vector2(glyphs[j].x_off, glyphs[j].y_off) * pixel_size; for (int r = 0; r < glyphs[j].repeat; r++) { for (int k = 0; k < ts; k += 3) { // Add front face. for (int l = 0; l < 3; l++) { - Vector3 point = Vector3(ts_ptr[k + l].x + offset.x, -ts_ptr[k + l].y + offset.y, depth / 2.0); + Vector3 point = Vector3(ts_ptr[k + l].x + offset.x + gl_of.x, -ts_ptr[k + l].y + offset.y - gl_of.y, depth / 2.0); vertices_ptr[p_idx] = point; normals_ptr[p_idx] = Vector3(0.0, 0.0, 1.0); if (has_depth) { @@ -3149,7 +3157,7 @@ void TextMesh::_create_mesh_array(Array &p_arr) const { if (has_depth) { // Add back face. for (int l = 2; l >= 0; l--) { - Vector3 point = Vector3(ts_ptr[k + l].x + offset.x, -ts_ptr[k + l].y + offset.y, -depth / 2.0); + Vector3 point = Vector3(ts_ptr[k + l].x + offset.x + gl_of.x, -ts_ptr[k + l].y + offset.y - gl_of.y, -depth / 2.0); vertices_ptr[p_idx] = point; normals_ptr[p_idx] = Vector3(0.0, 0.0, -1.0); uvs_ptr[p_idx] = Vector2(Math::remap(point.x, min_p.x, max_p.x, real_t(0.0), real_t(1.0)), Math::remap(point.y, -max_p.y, -min_p.y, real_t(0.8), real_t(0.4))); @@ -3182,10 +3190,10 @@ void TextMesh::_create_mesh_array(Array &p_arr) const { real_t seg_len = (ps_ptr[next].point - ps_ptr[l].point).length(); Vector3 quad_faces[4] = { - Vector3(ps_ptr[l].point.x + offset.x, -ps_ptr[l].point.y + offset.y, -depth / 2.0), - Vector3(ps_ptr[next].point.x + offset.x, -ps_ptr[next].point.y + offset.y, -depth / 2.0), - Vector3(ps_ptr[l].point.x + offset.x, -ps_ptr[l].point.y + offset.y, depth / 2.0), - Vector3(ps_ptr[next].point.x + offset.x, -ps_ptr[next].point.y + offset.y, depth / 2.0), + Vector3(ps_ptr[l].point.x + offset.x + gl_of.x, -ps_ptr[l].point.y + offset.y - gl_of.y, -depth / 2.0), + Vector3(ps_ptr[next].point.x + offset.x + gl_of.x, -ps_ptr[next].point.y + offset.y - gl_of.y, -depth / 2.0), + Vector3(ps_ptr[l].point.x + offset.x + gl_of.x, -ps_ptr[l].point.y + offset.y - gl_of.y, depth / 2.0), + Vector3(ps_ptr[next].point.x + offset.x + gl_of.x, -ps_ptr[next].point.y + offset.y - gl_of.y, depth / 2.0), }; for (int m = 0; m < 4; m++) { const Vector2 &d = ((m % 2) == 0) ? d1 : d2; diff --git a/scene/resources/resource_format_text.cpp b/scene/resources/resource_format_text.cpp index dd39f88352..037cd32f10 100644 --- a/scene/resources/resource_format_text.cpp +++ b/scene/resources/resource_format_text.cpp @@ -577,6 +577,8 @@ Error ResourceLoaderText::load() { if (do_assign) { if (cache_mode != ResourceFormatLoader::CACHE_MODE_IGNORE) { res->set_path(path, cache_mode == ResourceFormatLoader::CACHE_MODE_REPLACE); + } else if (!path.is_resource_file()) { + res->set_path_cache(path); } res->set_scene_unique_id(id); } @@ -971,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) { @@ -1789,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_ccdik.cpp b/scene/resources/skeleton_modification_2d_ccdik.cpp index 1d4a980a12..1ad8d0eccc 100644 --- a/scene/resources/skeleton_modification_2d_ccdik.cpp +++ b/scene/resources/skeleton_modification_2d_ccdik.cpp @@ -60,21 +60,23 @@ bool SkeletonModification2DCCDIK::_set(const StringName &p_path, const Variant & } else if (what == "constraint_in_localspace") { set_ccdik_joint_constraint_in_localspace(which, p_value); } - #ifdef TOOLS_ENABLED - if (what.begins_with("editor_draw_gizmo")) { + else if (what.begins_with("editor_draw_gizmo")) { set_ccdik_joint_editor_draw_gizmo(which, p_value); } #endif // TOOLS_ENABLED - - return true; + else { + return false; + } } - #ifdef TOOLS_ENABLED - if (path.begins_with("editor/draw_gizmo")) { + else if (path.begins_with("editor/draw_gizmo")) { set_editor_draw_gizmo(p_value); } #endif // TOOLS_ENABLED + else { + return false; + } return true; } @@ -104,21 +106,23 @@ bool SkeletonModification2DCCDIK::_get(const StringName &p_path, Variant &r_ret) } else if (what == "constraint_in_localspace") { r_ret = get_ccdik_joint_constraint_in_localspace(which); } - #ifdef TOOLS_ENABLED - if (what.begins_with("editor_draw_gizmo")) { + else if (what.begins_with("editor_draw_gizmo")) { r_ret = get_ccdik_joint_editor_draw_gizmo(which); } #endif // TOOLS_ENABLED - - return true; + else { + return false; + } } - #ifdef TOOLS_ENABLED - if (path.begins_with("editor/draw_gizmo")) { + else if (path.begins_with("editor/draw_gizmo")) { r_ret = get_editor_draw_gizmo(); } #endif // TOOLS_ENABLED + else { + return false; + } return true; } diff --git a/scene/resources/skeleton_modification_2d_fabrik.cpp b/scene/resources/skeleton_modification_2d_fabrik.cpp index 419a9a7989..dd1c4a91d5 100644 --- a/scene/resources/skeleton_modification_2d_fabrik.cpp +++ b/scene/resources/skeleton_modification_2d_fabrik.cpp @@ -51,7 +51,11 @@ bool SkeletonModification2DFABRIK::_set(const StringName &p_path, const Variant set_fabrik_joint_magnet_position(which, p_value); } else if (what == "use_target_rotation") { set_fabrik_joint_use_target_rotation(which, p_value); + } else { + return false; } + } else { + return false; } return true; @@ -73,8 +77,11 @@ bool SkeletonModification2DFABRIK::_get(const StringName &p_path, Variant &r_ret r_ret = get_fabrik_joint_magnet_position(which); } else if (what == "use_target_rotation") { r_ret = get_fabrik_joint_use_target_rotation(which); + } else { + return false; } - return true; + } else { + return false; } return true; } diff --git a/scene/resources/skeleton_modification_2d_jiggle.cpp b/scene/resources/skeleton_modification_2d_jiggle.cpp index 65217dabd0..2ace9577e4 100644 --- a/scene/resources/skeleton_modification_2d_jiggle.cpp +++ b/scene/resources/skeleton_modification_2d_jiggle.cpp @@ -57,14 +57,15 @@ bool SkeletonModification2DJiggle::_set(const StringName &p_path, const Variant set_jiggle_joint_use_gravity(which, p_value); } else if (what == "gravity") { set_jiggle_joint_gravity(which, p_value); + } else { + return false; } - return true; + } else if (path == "use_colliders") { + set_use_colliders(p_value); + } else if (path == "collision_mask") { + set_collision_mask(p_value); } else { - if (path == "use_colliders") { - set_use_colliders(p_value); - } else if (path == "collision_mask") { - set_collision_mask(p_value); - } + return false; } return true; } @@ -93,14 +94,15 @@ bool SkeletonModification2DJiggle::_get(const StringName &p_path, Variant &r_ret r_ret = get_jiggle_joint_use_gravity(which); } else if (what == "gravity") { r_ret = get_jiggle_joint_gravity(which); + } else { + return false; } - return true; + } else if (path == "use_colliders") { + r_ret = get_use_colliders(); + } else if (path == "collision_mask") { + r_ret = get_collision_mask(); } else { - if (path == "use_colliders") { - r_ret = get_use_colliders(); - } else if (path == "collision_mask") { - r_ret = get_collision_mask(); - } + return false; } return true; } diff --git a/scene/resources/skeleton_modification_2d_lookat.cpp b/scene/resources/skeleton_modification_2d_lookat.cpp index 3576164a03..8f6f6bc4ae 100644 --- a/scene/resources/skeleton_modification_2d_lookat.cpp +++ b/scene/resources/skeleton_modification_2d_lookat.cpp @@ -51,12 +51,14 @@ bool SkeletonModification2DLookAt::_set(const StringName &p_path, const Variant } else if (path.begins_with("additional_rotation")) { set_additional_rotation(Math::deg_to_rad(float(p_value))); } - #ifdef TOOLS_ENABLED - if (path.begins_with("editor/draw_gizmo")) { + else if (path.begins_with("editor/draw_gizmo")) { set_editor_draw_gizmo(p_value); } #endif // TOOLS_ENABLED + else { + return false; + } return true; } @@ -77,12 +79,14 @@ bool SkeletonModification2DLookAt::_get(const StringName &p_path, Variant &r_ret } else if (path.begins_with("additional_rotation")) { r_ret = Math::rad_to_deg(get_additional_rotation()); } - #ifdef TOOLS_ENABLED - if (path.begins_with("editor/draw_gizmo")) { + else if (path.begins_with("editor/draw_gizmo")) { r_ret = get_editor_draw_gizmo(); } #endif // TOOLS_ENABLED + else { + return false; + } return true; } diff --git a/scene/resources/skeleton_modification_2d_physicalbones.cpp b/scene/resources/skeleton_modification_2d_physicalbones.cpp index 5a72eb6bd8..e000e947cd 100644 --- a/scene/resources/skeleton_modification_2d_physicalbones.cpp +++ b/scene/resources/skeleton_modification_2d_physicalbones.cpp @@ -55,10 +55,10 @@ bool SkeletonModification2DPhysicalBones::_set(const StringName &p_path, const V if (what == "nodepath") { set_physical_bone_node(which, p_value); + return true; } - return true; } - return true; + return false; } bool SkeletonModification2DPhysicalBones::_get(const StringName &p_path, Variant &r_ret) const { @@ -79,10 +79,10 @@ bool SkeletonModification2DPhysicalBones::_get(const StringName &p_path, Variant if (what == "nodepath") { r_ret = get_physical_bone_node(which); + return true; } - return true; } - return true; + return false; } void SkeletonModification2DPhysicalBones::_get_property_list(List<PropertyInfo> *p_list) const { diff --git a/scene/resources/skeleton_modification_2d_stackholder.cpp b/scene/resources/skeleton_modification_2d_stackholder.cpp index 34d31bac8a..6d4cd290f1 100644 --- a/scene/resources/skeleton_modification_2d_stackholder.cpp +++ b/scene/resources/skeleton_modification_2d_stackholder.cpp @@ -37,12 +37,14 @@ bool SkeletonModification2DStackHolder::_set(const StringName &p_path, const Var if (path == "held_modification_stack") { set_held_modification_stack(p_value); } - #ifdef TOOLS_ENABLED - if (path == "editor/draw_gizmo") { + else if (path == "editor/draw_gizmo") { set_editor_draw_gizmo(p_value); } #endif // TOOLS_ENABLED + else { + return false; + } return true; } @@ -53,12 +55,14 @@ bool SkeletonModification2DStackHolder::_get(const StringName &p_path, Variant & if (path == "held_modification_stack") { r_ret = get_held_modification_stack(); } - #ifdef TOOLS_ENABLED - if (path == "editor/draw_gizmo") { + else if (path == "editor/draw_gizmo") { r_ret = get_editor_draw_gizmo(); } #endif // TOOLS_ENABLED + else { + return false; + } return true; } diff --git a/scene/resources/skeleton_modification_2d_twoboneik.cpp b/scene/resources/skeleton_modification_2d_twoboneik.cpp index 1e8eb9842c..4458cdc0e3 100644 --- a/scene/resources/skeleton_modification_2d_twoboneik.cpp +++ b/scene/resources/skeleton_modification_2d_twoboneik.cpp @@ -47,14 +47,16 @@ bool SkeletonModification2DTwoBoneIK::_set(const StringName &p_path, const Varia } else if (path == "joint_two_bone2d_node") { set_joint_two_bone2d_node(p_value); } - #ifdef TOOLS_ENABLED - if (path.begins_with("editor/draw_gizmo")) { + else if (path.begins_with("editor/draw_gizmo")) { set_editor_draw_gizmo(p_value); } else if (path.begins_with("editor/draw_min_max")) { set_editor_draw_min_max(p_value); } #endif // TOOLS_ENABLED + else { + return false; + } return true; } @@ -71,14 +73,16 @@ bool SkeletonModification2DTwoBoneIK::_get(const StringName &p_path, Variant &r_ } else if (path == "joint_two_bone2d_node") { r_ret = get_joint_two_bone2d_node(); } - #ifdef TOOLS_ENABLED - if (path.begins_with("editor/draw_gizmo")) { + else if (path.begins_with("editor/draw_gizmo")) { r_ret = get_editor_draw_gizmo(); } else if (path.begins_with("editor/draw_min_max")) { r_ret = get_editor_draw_min_max(); } #endif // TOOLS_ENABLED + else { + return false; + } return true; } @@ -464,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/skeleton_modification_stack_2d.cpp b/scene/resources/skeleton_modification_stack_2d.cpp index 71ddbc0898..dcc69d4831 100644 --- a/scene/resources/skeleton_modification_stack_2d.cpp +++ b/scene/resources/skeleton_modification_stack_2d.cpp @@ -37,7 +37,7 @@ void SkeletonModificationStack2D::_get_property_list(List<PropertyInfo> *p_list) PropertyInfo(Variant::OBJECT, "modifications/" + itos(i), PROPERTY_HINT_RESOURCE_TYPE, "SkeletonModification2D", - PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_DEFERRED_SET_RESOURCE | PROPERTY_USAGE_ALWAYS_DUPLICATE)); + PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_ALWAYS_DUPLICATE)); } } @@ -49,7 +49,7 @@ bool SkeletonModificationStack2D::_set(const StringName &p_path, const Variant & set_modification(mod_idx, p_value); return true; } - return true; + return false; } bool SkeletonModificationStack2D::_get(const StringName &p_path, Variant &r_ret) const { @@ -60,7 +60,7 @@ bool SkeletonModificationStack2D::_get(const StringName &p_path, Variant &r_ret) r_ret = get_modification(mod_idx); return true; } - return true; + return false; } void SkeletonModificationStack2D::setup() { 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 239ecb500e..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); @@ -175,17 +179,16 @@ void TextParagraph::_shape_lines() { for (int i = 0; i < line_breaks.size(); i = i + 2) { RID line = TS->shaped_text_substr(rid, line_breaks[i], line_breaks[i + 1] - line_breaks[i]); float h = (TS->shaped_text_get_orientation(line) == TextServer::ORIENTATION_HORIZONTAL) ? TS->shaped_text_get_size(line).y : TS->shaped_text_get_size(line).x; - if (v_offset < h) { - TS->free_rid(line); - break; - } if (!tab_stops.is_empty()) { TS->shaped_text_tab_align(line, tab_stops); } - dropcap_lines++; - v_offset -= h; start = line_breaks[i + 1]; lines_rid.push_back(line); + if (v_offset < h) { + break; + } + dropcap_lines++; + v_offset -= h; } } // Use fixed for the rest of lines. @@ -253,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 { @@ -282,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); } } @@ -502,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/theme.cpp b/scene/resources/theme.cpp index d2a1519d49..d57a0f6b38 100644 --- a/scene/resources/theme.cpp +++ b/scene/resources/theme.cpp @@ -1240,6 +1240,11 @@ void Theme::get_type_list(List<StringName> *p_list) const { types.insert(E.key); } + // Variations. + for (const KeyValue<StringName, StringName> &E : variation_map) { + types.insert(E.key); + } + for (const StringName &E : types) { p_list->push_back(E); } 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 69925fc19f..9fb6b364a7 100644 --- a/scene/resources/visual_shader.cpp +++ b/scene/resources/visual_shader.cpp @@ -962,14 +962,15 @@ 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) { - g->connections.erase(E); - 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); } E = N; } diff --git a/scene/resources/visual_shader_nodes.cpp b/scene/resources/visual_shader_nodes.cpp index 71b8af6625..ccd730eef2 100644 --- a/scene/resources/visual_shader_nodes.cpp +++ b/scene/resources/visual_shader_nodes.cpp @@ -6346,6 +6346,7 @@ String get_sampler_hint(VisualShaderNodeTextureParameter::TextureType p_texture_ if (!repeat_code.is_empty()) { if (!has_colon) { code += " : "; + has_colon = true; } else { code += ", "; } @@ -6353,6 +6354,7 @@ String get_sampler_hint(VisualShaderNodeTextureParameter::TextureType p_texture_ } } + // source { String source_code; |