diff options
Diffstat (limited to 'scene')
80 files changed, 2446 insertions, 888 deletions
diff --git a/scene/2d/listener_2d.cpp b/scene/2d/audio_listener_2d.cpp index 444f05f2b1..f16e359a1d 100644 --- a/scene/2d/listener_2d.cpp +++ b/scene/2d/audio_listener_2d.cpp @@ -1,5 +1,5 @@ /*************************************************************************/ -/* listener_2d.cpp */ +/* audio_listener_2d.cpp */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,9 +28,9 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#include "listener_2d.h" +#include "audio_listener_2d.h" -bool Listener2D::_set(const StringName &p_name, const Variant &p_value) { +bool AudioListener2D::_set(const StringName &p_name, const Variant &p_value) { if (p_name == "current") { if (p_value.operator bool()) { make_current(); @@ -43,7 +43,7 @@ bool Listener2D::_set(const StringName &p_name, const Variant &p_value) { return true; } -bool Listener2D::_get(const StringName &p_name, Variant &r_ret) const { +bool AudioListener2D::_get(const StringName &p_name, Variant &r_ret) const { if (p_name == "current") { if (is_inside_tree() && get_tree()->is_node_being_edited(this)) { r_ret = current; @@ -56,11 +56,11 @@ bool Listener2D::_get(const StringName &p_name, Variant &r_ret) const { return true; } -void Listener2D::_get_property_list(List<PropertyInfo> *p_list) const { +void AudioListener2D::_get_property_list(List<PropertyInfo> *p_list) const { p_list->push_back(PropertyInfo(Variant::BOOL, "current")); } -void Listener2D::_notification(int p_what) { +void AudioListener2D::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_TREE: { if (!get_tree()->is_node_being_edited(this) && current) { @@ -80,33 +80,33 @@ void Listener2D::_notification(int p_what) { } } -void Listener2D::make_current() { +void AudioListener2D::make_current() { current = true; if (!is_inside_tree()) { return; } - get_viewport()->_listener_2d_set(this); + get_viewport()->_audio_listener_2d_set(this); } -void Listener2D::clear_current() { +void AudioListener2D::clear_current() { current = false; if (!is_inside_tree()) { return; } - get_viewport()->_listener_2d_remove(this); + get_viewport()->_audio_listener_2d_remove(this); } -bool Listener2D::is_current() const { +bool AudioListener2D::is_current() const { if (is_inside_tree() && !get_tree()->is_node_being_edited(this)) { - return get_viewport()->get_listener_2d() == this; + return get_viewport()->get_audio_listener_2d() == this; } else { return current; } return false; } -void Listener2D::_bind_methods() { - ClassDB::bind_method(D_METHOD("make_current"), &Listener2D::make_current); - ClassDB::bind_method(D_METHOD("clear_current"), &Listener2D::clear_current); - ClassDB::bind_method(D_METHOD("is_current"), &Listener2D::is_current); +void AudioListener2D::_bind_methods() { + ClassDB::bind_method(D_METHOD("make_current"), &AudioListener2D::make_current); + ClassDB::bind_method(D_METHOD("clear_current"), &AudioListener2D::clear_current); + ClassDB::bind_method(D_METHOD("is_current"), &AudioListener2D::is_current); } diff --git a/scene/2d/listener_2d.h b/scene/2d/audio_listener_2d.h index 0289a8087d..875887acc6 100644 --- a/scene/2d/listener_2d.h +++ b/scene/2d/audio_listener_2d.h @@ -1,5 +1,5 @@ /*************************************************************************/ -/* listener_2d.h */ +/* audio_listener_2d.h */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -34,8 +34,8 @@ #include "scene/2d/node_2d.h" #include "scene/main/window.h" -class Listener2D : public Node2D { - GDCLASS(Listener2D, Node2D); +class AudioListener2D : public Node2D { + GDCLASS(AudioListener2D, Node2D); private: bool current = false; diff --git a/scene/2d/audio_stream_player_2d.cpp b/scene/2d/audio_stream_player_2d.cpp index 8401909384..bddc342c1a 100644 --- a/scene/2d/audio_stream_player_2d.cpp +++ b/scene/2d/audio_stream_player_2d.cpp @@ -31,7 +31,7 @@ #include "audio_stream_player_2d.h" #include "scene/2d/area_2d.h" -#include "scene/2d/listener_2d.h" +#include "scene/2d/audio_listener_2d.h" #include "scene/main/window.h" void AudioStreamPlayer2D::_notification(int p_what) { @@ -156,7 +156,7 @@ void AudioStreamPlayer2D::_update_panning() { Vector2 relative_to_listener; //screen in global is used for attenuation - Listener2D *listener = vp->get_listener_2d(); + AudioListener2D *listener = vp->get_audio_listener_2d(); if (listener) { listener_in_global = listener->get_global_position(); relative_to_listener = global_pos - listener_in_global; diff --git a/scene/2d/camera_2d.cpp b/scene/2d/camera_2d.cpp index bf91ce8e65..8195d98f55 100644 --- a/scene/2d/camera_2d.cpp +++ b/scene/2d/camera_2d.cpp @@ -198,7 +198,7 @@ Transform2D Camera2D::get_camera_transform() { screen_rect.position += offset; } - camera_screen_center = screen_rect.position + screen_rect.size * 0.5; + camera_screen_center = screen_rect.get_center(); Transform2D xform; xform.scale_basis(zoom); diff --git a/scene/2d/cpu_particles_2d.cpp b/scene/2d/cpu_particles_2d.cpp index b836497627..bf26ec1f20 100644 --- a/scene/2d/cpu_particles_2d.cpp +++ b/scene/2d/cpu_particles_2d.cpp @@ -155,7 +155,7 @@ void CPUParticles2D::_update_mesh_texture() { Vector<Vector2> vertices; vertices.push_back(-tex_size * 0.5); vertices.push_back(-tex_size * 0.5 + Vector2(tex_size.x, 0)); - vertices.push_back(-tex_size * 0.5 + Vector2(tex_size.x, tex_size.y)); + vertices.push_back(-tex_size * 0.5 + tex_size); vertices.push_back(-tex_size * 0.5 + Vector2(0, tex_size.y)); Vector<Vector2> uvs; AtlasTexture *atlas_texure = Object::cast_to<AtlasTexture>(*texture); @@ -727,7 +727,7 @@ void CPUParticles2D::_particles_process(double p_delta) { p.hue_rot_rand = Math::randf(); p.anim_offset_rand = Math::randf(); - real_t angle1_rad = Math::atan2(direction.y, direction.x) + Math::deg2rad((Math::randf() * 2.0 - 1.0) * spread); + real_t angle1_rad = direction.angle() + Math::deg2rad((Math::randf() * 2.0 - 1.0) * spread); Vector2 rot = Vector2(Math::cos(angle1_rad), Math::sin(angle1_rad)); p.velocity = rot * Math::lerp(parameters_min[PARAM_INITIAL_LINEAR_VELOCITY], parameters_min[PARAM_INITIAL_LINEAR_VELOCITY], Math::randf()); diff --git a/scene/2d/navigation_region_2d.cpp b/scene/2d/navigation_region_2d.cpp index 72ea6541e3..cbf0d50c4e 100644 --- a/scene/2d/navigation_region_2d.cpp +++ b/scene/2d/navigation_region_2d.cpp @@ -464,7 +464,7 @@ void NavigationRegion2D::_notification(int p_what) { draw_line(a, b, doors_color); // Draw a circle to illustrate the margins. - real_t angle = (b - a).angle(); + real_t angle = b.angle_to_point(a); draw_arc(a, radius, angle + Math_PI / 2.0, angle - Math_PI / 2.0 + Math_TAU, 10, doors_color); draw_arc(b, radius, angle - Math_PI / 2.0, angle + Math_PI / 2.0, 10, doors_color); } diff --git a/scene/2d/physics_body_2d.cpp b/scene/2d/physics_body_2d.cpp index d1674460e9..c07a999588 100644 --- a/scene/2d/physics_body_2d.cpp +++ b/scene/2d/physics_body_2d.cpp @@ -34,8 +34,8 @@ #include "scene/scene_string_names.h" void PhysicsBody2D::_bind_methods() { - ClassDB::bind_method(D_METHOD("move_and_collide", "rel_vec", "test_only", "safe_margin"), &PhysicsBody2D::_move, DEFVAL(false), DEFVAL(0.08)); - ClassDB::bind_method(D_METHOD("test_move", "from", "rel_vec", "collision", "safe_margin"), &PhysicsBody2D::test_move, DEFVAL(Variant()), DEFVAL(0.08)); + ClassDB::bind_method(D_METHOD("move_and_collide", "linear_velocity", "test_only", "safe_margin"), &PhysicsBody2D::_move, DEFVAL(false), DEFVAL(0.08)); + ClassDB::bind_method(D_METHOD("test_move", "from", "linear_velocity", "collision", "safe_margin"), &PhysicsBody2D::test_move, DEFVAL(Variant()), DEFVAL(0.08)); ClassDB::bind_method(D_METHOD("get_collision_exceptions"), &PhysicsBody2D::get_collision_exceptions); ClassDB::bind_method(D_METHOD("add_collision_exception_with", "body"), &PhysicsBody2D::add_collision_exception_with); @@ -57,8 +57,12 @@ PhysicsBody2D::~PhysicsBody2D() { Ref<KinematicCollision2D> PhysicsBody2D::_move(const Vector2 &p_motion, bool p_test_only, real_t p_margin) { PhysicsServer2D::MotionResult result; - if (move_and_collide(p_motion, result, p_margin, p_test_only)) { - if (motion_cache.is_null()) { + // Hack in order to work with calling from _process as well as from _physics_process; calling from thread is risky. + double delta = Engine::get_singleton()->is_in_physics_frame() ? get_physics_process_delta_time() : get_process_delta_time(); + + if (move_and_collide(p_motion * delta, result, p_margin, p_test_only)) { + // Create a new instance when the cached reference is invalid or still in use in script. + if (motion_cache.is_null() || motion_cache->reference_get_count() > 1) { motion_cache.instantiate(); motion_cache->owner = this; } @@ -132,7 +136,10 @@ bool PhysicsBody2D::test_move(const Transform2D &p_from, const Vector2 &p_motion r = const_cast<PhysicsServer2D::MotionResult *>(&r_collision->result); } - return PhysicsServer2D::get_singleton()->body_test_motion(get_rid(), p_from, p_motion, p_margin, r); + // Hack in order to work with calling from _process as well as from _physics_process; calling from thread is risky. + double delta = Engine::get_singleton()->is_in_physics_frame() ? get_physics_process_delta_time() : get_process_delta_time(); + + return PhysicsServer2D::get_singleton()->body_test_motion(get_rid(), p_from, p_motion * delta, p_margin, r); } TypedArray<PhysicsBody2D> PhysicsBody2D::get_collision_exceptions() { @@ -1126,9 +1133,7 @@ void CharacterBody2D::_move_and_slide_grounded(double p_delta, bool p_was_on_flo if (on_floor && floor_stop_on_slope && (linear_velocity.normalized() + up_direction).length() < 0.01) { Transform2D gt = get_global_transform(); - if (result.travel.length() > margin) { - gt.elements[2] -= result.travel.slide(up_direction); - } else { + if (result.travel.length() <= margin + CMP_EPSILON) { gt.elements[2] -= result.travel; } set_global_transform(gt); @@ -1147,7 +1152,7 @@ void CharacterBody2D::_move_and_slide_grounded(double p_delta, bool p_was_on_flo // Avoid to move forward on a wall if floor_block_on_wall is true. if (p_was_on_floor && !on_floor && !vel_dir_facing_up) { // If the movement is large the body can be prevented from reaching the walls. - if (result.travel.length() <= margin) { + if (result.travel.length() <= margin + CMP_EPSILON) { // Cancels the motion. Transform2D gt = get_global_transform(); gt.elements[2] -= result.travel; @@ -1276,13 +1281,16 @@ void CharacterBody2D::_move_and_slide_free(double p_delta) { } void CharacterBody2D::_snap_on_floor(bool was_on_floor, bool vel_dir_facing_up) { - if (Math::is_equal_approx(floor_snap_length, 0) || on_floor || !was_on_floor || vel_dir_facing_up) { + if (on_floor || !was_on_floor || vel_dir_facing_up) { return; } + // Snap by at least collision margin to keep floor state consistent. + real_t length = MAX(floor_snap_length, margin); + Transform2D gt = get_global_transform(); PhysicsServer2D::MotionResult result; - if (move_and_collide(up_direction * -floor_snap_length, result, margin, true, false, true)) { + if (move_and_collide(-up_direction * length, result, margin, true, false, true)) { bool apply = true; if (result.get_angle(up_direction) <= floor_max_angle + FLOOR_ANGLE_THRESHOLD) { on_floor = true; @@ -1310,12 +1318,15 @@ void CharacterBody2D::_snap_on_floor(bool was_on_floor, bool vel_dir_facing_up) } bool CharacterBody2D::_on_floor_if_snapped(bool was_on_floor, bool vel_dir_facing_up) { - if (Math::is_equal_approx(floor_snap_length, 0) || up_direction == Vector2() || on_floor || !was_on_floor || vel_dir_facing_up) { + if (up_direction == Vector2() || on_floor || !was_on_floor || vel_dir_facing_up) { return false; } + // Snap by at least collision margin to keep floor state consistent. + real_t length = MAX(floor_snap_length, margin); + PhysicsServer2D::MotionResult result; - if (move_and_collide(up_direction * -floor_snap_length, result, margin, true, false, true)) { + if (move_and_collide(-up_direction * length, result, margin, true, false, true)) { if (result.get_angle(up_direction) <= floor_max_angle + FLOOR_ANGLE_THRESHOLD) { return true; } @@ -1406,7 +1417,8 @@ Ref<KinematicCollision2D> CharacterBody2D::_get_slide_collision(int p_bounce) { slide_colliders.resize(p_bounce + 1); } - if (slide_colliders[p_bounce].is_null()) { + // Create a new instance when the cached reference is invalid or still in use in script. + if (slide_colliders[p_bounce].is_null() || slide_colliders[p_bounce]->reference_get_count() > 1) { slide_colliders.write[p_bounce].instantiate(); slide_colliders.write[p_bounce]->owner = this; } @@ -1603,7 +1615,7 @@ void CharacterBody2D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "floor_constant_speed"), "set_floor_constant_speed_enabled", "is_floor_constant_speed_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "floor_block_on_wall"), "set_floor_block_on_wall_enabled", "is_floor_block_on_wall_enabled"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "floor_max_angle", PROPERTY_HINT_RANGE, "0,180,0.1,radians"), "set_floor_max_angle", "get_floor_max_angle"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "floor_snap_length", PROPERTY_HINT_RANGE, "0,1000,0.1"), "set_floor_snap_length", "get_floor_snap_length"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "floor_snap_length", PROPERTY_HINT_RANGE, "0,32,0.1,or_greater"), "set_floor_snap_length", "get_floor_snap_length"); ADD_GROUP("Moving platform", "moving_platform"); ADD_PROPERTY(PropertyInfo(Variant::INT, "moving_platform_floor_layers", PROPERTY_HINT_LAYERS_2D_PHYSICS), "set_moving_platform_floor_layers", "get_moving_platform_floor_layers"); ADD_PROPERTY(PropertyInfo(Variant::INT, "moving_platform_wall_layers", PROPERTY_HINT_LAYERS_2D_PHYSICS), "set_moving_platform_wall_layers", "get_moving_platform_wall_layers"); diff --git a/scene/2d/physics_body_2d.h b/scene/2d/physics_body_2d.h index 3ae483edf9..e789ac4cb7 100644 --- a/scene/2d/physics_body_2d.h +++ b/scene/2d/physics_body_2d.h @@ -92,7 +92,7 @@ class AnimatableBody2D : public StaticBody2D { GDCLASS(AnimatableBody2D, StaticBody2D); private: - bool sync_to_physics = false; + bool sync_to_physics = true; Transform2D last_valid_transform; @@ -335,14 +335,14 @@ private: real_t margin = 0.08; MotionMode motion_mode = MOTION_MODE_GROUNDED; - bool floor_stop_on_slope = false; bool floor_constant_speed = false; + bool floor_stop_on_slope = true; bool floor_block_on_wall = true; bool slide_on_ceiling = true; int max_slides = 4; int platform_layer; real_t floor_max_angle = Math::deg2rad((real_t)45.0); - float floor_snap_length = 0; + real_t floor_snap_length = 1; real_t free_mode_min_slide_angle = Math::deg2rad((real_t)15.0); Vector2 up_direction = Vector2(0.0, -1.0); uint32_t moving_platform_floor_layers = UINT32_MAX; diff --git a/scene/2d/skeleton_2d.cpp b/scene/2d/skeleton_2d.cpp index 4bbbc3575d..63a0fb9b89 100644 --- a/scene/2d/skeleton_2d.cpp +++ b/scene/2d/skeleton_2d.cpp @@ -456,7 +456,7 @@ void Bone2D::calculate_length_and_rotation() { if (child) { Vector2 child_local_pos = to_local(child->get_global_position()); length = child_local_pos.length(); - bone_angle = Math::atan2(child_local_pos.normalized().y, child_local_pos.normalized().x); + bone_angle = child_local_pos.normalized().angle(); calculated = true; break; } diff --git a/scene/2d/tile_map.cpp b/scene/2d/tile_map.cpp index a139a92ab4..929233e4e0 100644 --- a/scene/2d/tile_map.cpp +++ b/scene/2d/tile_map.cpp @@ -891,7 +891,7 @@ void TileMap::_rendering_update_dirty_quadrants(SelfList<TileMapQuadrant>::List modulate.a *= 0.3; } } - draw_tile(canvas_item, E_cell->key() - position, tile_set, c.source_id, c.get_atlas_coords(), c.alternative_tile, modulate); + draw_tile(canvas_item, E_cell->key() - position, tile_set, c.source_id, c.get_atlas_coords(), c.alternative_tile, -1, modulate); // --- Occluders --- for (int i = 0; i < tile_set->get_occlusion_layers_count(); i++) { @@ -1008,15 +1008,19 @@ void TileMap::_rendering_draw_quadrant_debug(TileMapQuadrant *p_quadrant) { } } -void TileMap::draw_tile(RID p_canvas_item, Vector2i p_position, const Ref<TileSet> p_tile_set, int p_atlas_source_id, Vector2i p_atlas_coords, int p_alternative_tile, Color p_modulation) { +void TileMap::draw_tile(RID p_canvas_item, Vector2i p_position, const Ref<TileSet> p_tile_set, int p_atlas_source_id, Vector2i p_atlas_coords, int p_alternative_tile, int p_frame, Color p_modulation) { ERR_FAIL_COND(!p_tile_set.is_valid()); ERR_FAIL_COND(!p_tile_set->has_source(p_atlas_source_id)); ERR_FAIL_COND(!p_tile_set->get_source(p_atlas_source_id)->has_tile(p_atlas_coords)); ERR_FAIL_COND(!p_tile_set->get_source(p_atlas_source_id)->has_alternative_tile(p_atlas_coords, p_alternative_tile)); - TileSetSource *source = *p_tile_set->get_source(p_atlas_source_id); TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source); if (atlas_source) { + // Check for the frame. + if (p_frame >= 0) { + ERR_FAIL_INDEX(p_frame, atlas_source->get_tile_animation_frames_count(p_atlas_coords)); + } + // Get the texture. Ref<Texture2D> tex = atlas_source->get_texture(); if (!tex.is_valid()) { @@ -1032,13 +1036,15 @@ void TileMap::draw_tile(RID p_canvas_item, Vector2i p_position, const Ref<TileSe // Get tile data. TileData *tile_data = Object::cast_to<TileData>(atlas_source->get_tile_data(p_atlas_coords, p_alternative_tile)); - // Compute the offset - Rect2i source_rect = atlas_source->get_tile_texture_region(p_atlas_coords); + // Get the tile modulation. + Color modulate = tile_data->get_modulate() * p_modulation; + + // Compute the offset. Vector2i tile_offset = atlas_source->get_tile_effective_texture_offset(p_atlas_coords, p_alternative_tile); - // Compute the destination rectangle in the CanvasItem. + // Get destination rect. Rect2 dest_rect; - dest_rect.size = source_rect.size; + dest_rect.size = atlas_source->get_tile_texture_region(p_atlas_coords).size; dest_rect.size.x += FP_ADJUST; dest_rect.size.y += FP_ADJUST; @@ -1057,12 +1063,28 @@ void TileMap::draw_tile(RID p_canvas_item, Vector2i p_position, const Ref<TileSe dest_rect.size.y = -dest_rect.size.y; } - // Get the tile modulation. - Color modulate = tile_data->get_modulate(); - modulate = Color(modulate.r * p_modulation.r, modulate.g * p_modulation.g, modulate.b * p_modulation.b, modulate.a * p_modulation.a); - // Draw the tile. - tex->draw_rect_region(p_canvas_item, dest_rect, source_rect, modulate, transpose, p_tile_set->is_uv_clipping()); + if (p_frame >= 0) { + Rect2i source_rect = atlas_source->get_tile_texture_region(p_atlas_coords, p_frame); + tex->draw_rect_region(p_canvas_item, dest_rect, source_rect, modulate, transpose, p_tile_set->is_uv_clipping()); + } else if (atlas_source->get_tile_animation_frames_count(p_atlas_coords) == 1) { + Rect2i source_rect = atlas_source->get_tile_texture_region(p_atlas_coords, 0); + tex->draw_rect_region(p_canvas_item, dest_rect, source_rect, modulate, transpose, p_tile_set->is_uv_clipping()); + } else { + real_t speed = atlas_source->get_tile_animation_speed(p_atlas_coords); + real_t animation_duration = atlas_source->get_tile_animation_total_duration(p_atlas_coords) / speed; + real_t time = 0.0; + for (int frame = 0; frame < atlas_source->get_tile_animation_frames_count(p_atlas_coords); frame++) { + real_t frame_duration = atlas_source->get_tile_animation_frame_duration(p_atlas_coords, frame) / speed; + RenderingServer::get_singleton()->canvas_item_add_animation_slice(p_canvas_item, animation_duration, time, time + frame_duration, 0.0); + + Rect2i source_rect = atlas_source->get_tile_texture_region(p_atlas_coords, frame); + tex->draw_rect_region(p_canvas_item, dest_rect, source_rect, modulate, transpose, p_tile_set->is_uv_clipping()); + + time += frame_duration; + } + RenderingServer::get_singleton()->canvas_item_add_animation_slice(p_canvas_item, 1.0, 0.0, 1.0, 0.0); + } } } @@ -2012,10 +2034,22 @@ bool TileMap::_set(const StringName &p_name, const Variant &p_value) { return false; } 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()) { + if (index < 0) { return false; } + if (index >= (int)layers.size()) { + _clear_internals(); + while (index >= (int)layers.size()) { + layers.push_back(TileMapLayer()); + } + _recreate_internals(); + + notify_property_list_changed(); + emit_signal(SNAME("changed")); + update_configuration_warnings(); + } + if (components[1] == "name") { set_layer_name(index, p_value); return true; diff --git a/scene/2d/tile_map.h b/scene/2d/tile_map.h index 3ac50fc7cc..ca38baa550 100644 --- a/scene/2d/tile_map.h +++ b/scene/2d/tile_map.h @@ -305,7 +305,7 @@ public: void set_quadrant_size(int p_size); int get_quadrant_size() const; - static void draw_tile(RID p_canvas_item, Vector2i p_position, const Ref<TileSet> p_tile_set, int p_atlas_source_id, Vector2i p_atlas_coords, int p_alternative_tile, Color p_modulation = Color(1.0, 1.0, 1.0, 1.0)); + static void draw_tile(RID p_canvas_item, Vector2i p_position, const Ref<TileSet> p_tile_set, int p_atlas_source_id, Vector2i p_atlas_coords, int p_alternative_tile, int p_frame = -1, Color p_modulation = Color(1.0, 1.0, 1.0, 1.0)); // Layers management. int get_layers_count() const; diff --git a/scene/3d/listener_3d.cpp b/scene/3d/audio_listener_3d.cpp index 8ae1f1940f..b2319e40d7 100644 --- a/scene/3d/listener_3d.cpp +++ b/scene/3d/audio_listener_3d.cpp @@ -1,5 +1,5 @@ /*************************************************************************/ -/* listener_3d.cpp */ +/* audio_listener_3d.cpp */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,18 +28,18 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#include "listener_3d.h" +#include "audio_listener_3d.h" #include "scene/main/viewport.h" -void Listener3D::_update_audio_listener_state() { +void AudioListener3D::_update_audio_listener_state() { } -void Listener3D::_request_listener_update() { +void AudioListener3D::_request_listener_update() { _update_listener(); } -bool Listener3D::_set(const StringName &p_name, const Variant &p_value) { +bool AudioListener3D::_set(const StringName &p_name, const Variant &p_value) { if (p_name == "current") { if (p_value.operator bool()) { make_current(); @@ -53,7 +53,7 @@ bool Listener3D::_set(const StringName &p_name, const Variant &p_value) { return true; } -bool Listener3D::_get(const StringName &p_name, Variant &r_ret) const { +bool AudioListener3D::_get(const StringName &p_name, Variant &r_ret) const { if (p_name == "current") { if (is_inside_tree() && get_tree()->is_node_being_edited(this)) { r_ret = current; @@ -67,20 +67,20 @@ bool Listener3D::_get(const StringName &p_name, Variant &r_ret) const { return true; } -void Listener3D::_get_property_list(List<PropertyInfo> *p_list) const { +void AudioListener3D::_get_property_list(List<PropertyInfo> *p_list) const { p_list->push_back(PropertyInfo(Variant::BOOL, "current")); } -void Listener3D::_update_listener() { +void AudioListener3D::_update_listener() { if (is_inside_tree() && is_current()) { get_viewport()->_listener_transform_3d_changed_notify(); } } -void Listener3D::_notification(int p_what) { +void AudioListener3D::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_WORLD: { - bool first_listener = get_viewport()->_listener_3d_add(this); + bool first_listener = get_viewport()->_audio_listener_3d_add(this); if (!get_tree()->is_node_being_edited(this) && (current || first_listener)) { make_current(); } @@ -99,41 +99,41 @@ void Listener3D::_notification(int p_what) { } } - get_viewport()->_listener_3d_remove(this); + get_viewport()->_audio_listener_3d_remove(this); } break; } } -Transform3D Listener3D::get_listener_transform() const { +Transform3D AudioListener3D::get_listener_transform() const { return get_global_transform().orthonormalized(); } -void Listener3D::make_current() { +void AudioListener3D::make_current() { current = true; if (!is_inside_tree()) { return; } - get_viewport()->_listener_3d_set(this); + get_viewport()->_audio_listener_3d_set(this); } -void Listener3D::clear_current() { +void AudioListener3D::clear_current() { current = false; if (!is_inside_tree()) { return; } - if (get_viewport()->get_listener_3d() == this) { - get_viewport()->_listener_3d_set(nullptr); - get_viewport()->_listener_3d_make_next_current(this); + if (get_viewport()->get_audio_listener_3d() == this) { + get_viewport()->_audio_listener_3d_set(nullptr); + get_viewport()->_audio_listener_3d_make_next_current(this); } } -bool Listener3D::is_current() const { +bool AudioListener3D::is_current() const { if (is_inside_tree() && !get_tree()->is_node_being_edited(this)) { - return get_viewport()->get_listener_3d() == this; + return get_viewport()->get_audio_listener_3d() == this; } else { return current; } @@ -141,16 +141,16 @@ bool Listener3D::is_current() const { return false; } -void Listener3D::_bind_methods() { - ClassDB::bind_method(D_METHOD("make_current"), &Listener3D::make_current); - ClassDB::bind_method(D_METHOD("clear_current"), &Listener3D::clear_current); - ClassDB::bind_method(D_METHOD("is_current"), &Listener3D::is_current); - ClassDB::bind_method(D_METHOD("get_listener_transform"), &Listener3D::get_listener_transform); +void AudioListener3D::_bind_methods() { + ClassDB::bind_method(D_METHOD("make_current"), &AudioListener3D::make_current); + ClassDB::bind_method(D_METHOD("clear_current"), &AudioListener3D::clear_current); + ClassDB::bind_method(D_METHOD("is_current"), &AudioListener3D::is_current); + ClassDB::bind_method(D_METHOD("get_listener_transform"), &AudioListener3D::get_listener_transform); } -Listener3D::Listener3D() { +AudioListener3D::AudioListener3D() { set_notify_transform(true); } -Listener3D::~Listener3D() { +AudioListener3D::~AudioListener3D() { } diff --git a/scene/3d/listener_3d.h b/scene/3d/audio_listener_3d.h index 08c08aa0cb..492cacb0e9 100644 --- a/scene/3d/listener_3d.h +++ b/scene/3d/audio_listener_3d.h @@ -1,5 +1,5 @@ /*************************************************************************/ -/* listener_3d.h */ +/* audio_listener_3d.h */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -33,8 +33,8 @@ #include "scene/3d/node_3d.h" -class Listener3D : public Node3D { - GDCLASS(Listener3D, Node3D); +class AudioListener3D : public Node3D { + GDCLASS(AudioListener3D, Node3D); private: bool force_change = false; @@ -66,8 +66,8 @@ public: void set_visible_layers(uint32_t p_layers); uint32_t get_visible_layers() const; - Listener3D(); - ~Listener3D(); + AudioListener3D(); + ~AudioListener3D(); }; #endif diff --git a/scene/3d/audio_stream_player_3d.cpp b/scene/3d/audio_stream_player_3d.cpp index 907c6cd03a..a54b10ba70 100644 --- a/scene/3d/audio_stream_player_3d.cpp +++ b/scene/3d/audio_stream_player_3d.cpp @@ -31,8 +31,8 @@ #include "audio_stream_player_3d.h" #include "scene/3d/area_3d.h" +#include "scene/3d/audio_listener_3d.h" #include "scene/3d/camera_3d.h" -#include "scene/3d/listener_3d.h" #include "scene/main/viewport.h" // Based on "A Novel Multichannel Panning Method for Standard and Arbitrary Loudspeaker Configurations" by Ramy Sadek and Chris Kyriakakis (2004) @@ -391,7 +391,7 @@ Vector<AudioFrame> AudioStreamPlayer3D::_update_panning() { bool listener_is_camera = true; Node3D *listener_node = camera; - Listener3D *listener = vp->get_listener_3d(); + AudioListener3D *listener = vp->get_audio_listener_3d(); if (listener) { listener_node = listener; listener_is_camera = false; @@ -713,15 +713,6 @@ AudioStreamPlayer3D::AttenuationModel AudioStreamPlayer3D::get_attenuation_model return attenuation_model; } -void AudioStreamPlayer3D::set_out_of_range_mode(OutOfRangeMode p_mode) { - ERR_FAIL_INDEX((int)p_mode, 2); - out_of_range_mode = p_mode; -} - -AudioStreamPlayer3D::OutOfRangeMode AudioStreamPlayer3D::get_out_of_range_mode() const { - return out_of_range_mode; -} - void AudioStreamPlayer3D::set_doppler_tracking(DopplerTracking p_tracking) { if (doppler_tracking == p_tracking) { return; @@ -832,9 +823,6 @@ void AudioStreamPlayer3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_attenuation_model", "model"), &AudioStreamPlayer3D::set_attenuation_model); ClassDB::bind_method(D_METHOD("get_attenuation_model"), &AudioStreamPlayer3D::get_attenuation_model); - ClassDB::bind_method(D_METHOD("set_out_of_range_mode", "mode"), &AudioStreamPlayer3D::set_out_of_range_mode); - ClassDB::bind_method(D_METHOD("get_out_of_range_mode"), &AudioStreamPlayer3D::get_out_of_range_mode); - ClassDB::bind_method(D_METHOD("set_doppler_tracking", "mode"), &AudioStreamPlayer3D::set_doppler_tracking); ClassDB::bind_method(D_METHOD("get_doppler_tracking"), &AudioStreamPlayer3D::get_doppler_tracking); @@ -856,7 +844,6 @@ void AudioStreamPlayer3D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "autoplay"), "set_autoplay", "is_autoplay_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "stream_paused", PROPERTY_HINT_NONE, ""), "set_stream_paused", "get_stream_paused"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "max_distance", PROPERTY_HINT_RANGE, "0,4096,1,or_greater,exp"), "set_max_distance", "get_max_distance"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "out_of_range_mode", PROPERTY_HINT_ENUM, "Mix,Pause"), "set_out_of_range_mode", "get_out_of_range_mode"); ADD_PROPERTY(PropertyInfo(Variant::INT, "max_polyphony", PROPERTY_HINT_NONE, ""), "set_max_polyphony", "get_max_polyphony"); ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "bus", PROPERTY_HINT_ENUM, ""), "set_bus", "get_bus"); ADD_PROPERTY(PropertyInfo(Variant::INT, "area_mask", PROPERTY_HINT_LAYERS_2D_PHYSICS), "set_area_mask", "get_area_mask"); @@ -875,9 +862,6 @@ void AudioStreamPlayer3D::_bind_methods() { BIND_ENUM_CONSTANT(ATTENUATION_LOGARITHMIC); BIND_ENUM_CONSTANT(ATTENUATION_DISABLED); - BIND_ENUM_CONSTANT(OUT_OF_RANGE_MIX); - BIND_ENUM_CONSTANT(OUT_OF_RANGE_PAUSE); - BIND_ENUM_CONSTANT(DOPPLER_TRACKING_DISABLED); BIND_ENUM_CONSTANT(DOPPLER_TRACKING_IDLE_STEP); BIND_ENUM_CONSTANT(DOPPLER_TRACKING_PHYSICS_STEP); diff --git a/scene/3d/audio_stream_player_3d.h b/scene/3d/audio_stream_player_3d.h index abd5a841b2..697bbe2381 100644 --- a/scene/3d/audio_stream_player_3d.h +++ b/scene/3d/audio_stream_player_3d.h @@ -51,11 +51,6 @@ public: ATTENUATION_DISABLED, }; - enum OutOfRangeMode { - OUT_OF_RANGE_MIX, - OUT_OF_RANGE_PAUSE, - }; - enum DopplerTracking { DOPPLER_TRACKING_DISABLED, DOPPLER_TRACKING_IDLE_STEP, @@ -118,8 +113,6 @@ private: DopplerTracking doppler_tracking = DOPPLER_TRACKING_DISABLED; - OutOfRangeMode out_of_range_mode = OUT_OF_RANGE_MIX; - float _get_attenuation_db(float p_distance) const; protected: @@ -182,9 +175,6 @@ public: void set_attenuation_model(AttenuationModel p_model); AttenuationModel get_attenuation_model() const; - void set_out_of_range_mode(OutOfRangeMode p_mode); - OutOfRangeMode get_out_of_range_mode() const; - void set_doppler_tracking(DopplerTracking p_tracking); DopplerTracking get_doppler_tracking() const; @@ -198,6 +188,5 @@ public: }; VARIANT_ENUM_CAST(AudioStreamPlayer3D::AttenuationModel) -VARIANT_ENUM_CAST(AudioStreamPlayer3D::OutOfRangeMode) VARIANT_ENUM_CAST(AudioStreamPlayer3D::DopplerTracking) #endif // AUDIO_STREAM_PLAYER_3D_H diff --git a/scene/3d/bone_attachment_3d.cpp b/scene/3d/bone_attachment_3d.cpp index 70361f4787..afd11482e3 100644 --- a/scene/3d/bone_attachment_3d.cpp +++ b/scene/3d/bone_attachment_3d.cpp @@ -110,7 +110,7 @@ TypedArray<String> BoneAttachment3D::get_configuration_warnings() const { } else { Skeleton3D *parent = Object::cast_to<Skeleton3D>(get_parent()); if (!parent) { - warnings.append(TTR("Parent node is not a Skeleton3D node! Please use an extenral Skeleton3D if you intend to use the BoneAttachment3D without it being a child of a Skeleton3D node.")); + warnings.append(TTR("Parent node is not a Skeleton3D node! Please use an external Skeleton3D if you intend to use the BoneAttachment3D without it being a child of a Skeleton3D node.")); } } @@ -161,7 +161,7 @@ void BoneAttachment3D::_check_bind() { bone_idx = sk->find_bone(bone_name); } if (bone_idx != -1) { - sk->call_deferred("connect", "bone_pose_changed", callable_mp(this, &BoneAttachment3D::on_bone_pose_update)); + sk->call_deferred(SNAME("connect"), "bone_pose_changed", callable_mp(this, &BoneAttachment3D::on_bone_pose_update)); bound = true; call_deferred(SNAME("on_bone_pose_update"), bone_idx); } diff --git a/scene/3d/gpu_particles_3d.cpp b/scene/3d/gpu_particles_3d.cpp index baf28ae102..32a62d8c7e 100644 --- a/scene/3d/gpu_particles_3d.cpp +++ b/scene/3d/gpu_particles_3d.cpp @@ -469,7 +469,7 @@ void GPUParticles3D::_skinning_changed() { if (draw_pass.is_valid() && draw_pass->get_builtin_bind_pose_count() > 0) { xforms.resize(draw_pass->get_builtin_bind_pose_count()); for (int j = 0; j < draw_pass->get_builtin_bind_pose_count(); j++) { - xforms.write[i] = draw_pass->get_builtin_bind_pose(j); + xforms.write[j] = draw_pass->get_builtin_bind_pose(j); } break; } diff --git a/scene/3d/gpu_particles_collision_3d.cpp b/scene/3d/gpu_particles_collision_3d.cpp index a34a30913e..4fa34615bf 100644 --- a/scene/3d/gpu_particles_collision_3d.cpp +++ b/scene/3d/gpu_particles_collision_3d.cpp @@ -420,7 +420,7 @@ Ref<Image> GPUParticlesCollisionSDF::bake() { } //test against original bounds - if (!Geometry3D::triangle_box_overlap(aabb.position + aabb.size * 0.5, aabb.size * 0.5, face.vertex)) { + if (!Geometry3D::triangle_box_overlap(aabb.get_center(), aabb.size * 0.5, face.vertex)) { continue; } @@ -438,7 +438,7 @@ Ref<Image> GPUParticlesCollisionSDF::bake() { } //test against original bounds - if (!Geometry3D::triangle_box_overlap(aabb.position + aabb.size * 0.5, aabb.size * 0.5, face.vertex)) { + if (!Geometry3D::triangle_box_overlap(aabb.get_center(), aabb.size * 0.5, face.vertex)) { continue; } diff --git a/scene/3d/lightmap_gi.cpp b/scene/3d/lightmap_gi.cpp index 7dd083e314..b56f0fd145 100644 --- a/scene/3d/lightmap_gi.cpp +++ b/scene/3d/lightmap_gi.cpp @@ -467,7 +467,7 @@ int32_t LightmapGI::_compute_bsp_tree(const Vector<Vector3> &p_points, const Loc } } if (i == 0) { - centers.push_back(bounds.position + bounds.size * 0.5); + centers.push_back(bounds.get_center()); } else { bounds_all.merge_with(bounds); } @@ -555,7 +555,7 @@ void LightmapGI::_plot_triangle_into_octree(GenProbesOctree *p_cell, float p_cel subcell.position = Vector3(pos) * p_cell_size; subcell.size = Vector3(half_size, half_size, half_size) * p_cell_size; - if (!Geometry3D::triangle_box_overlap(subcell.position + subcell.size * 0.5, subcell.size * 0.5, p_triangle)) { + if (!Geometry3D::triangle_box_overlap(subcell.get_center(), subcell.size * 0.5, p_triangle)) { continue; } diff --git a/scene/3d/physics_body_3d.cpp b/scene/3d/physics_body_3d.cpp index 10dceb89ff..bbf2c81a92 100644 --- a/scene/3d/physics_body_3d.cpp +++ b/scene/3d/physics_body_3d.cpp @@ -38,8 +38,8 @@ #endif void PhysicsBody3D::_bind_methods() { - ClassDB::bind_method(D_METHOD("move_and_collide", "rel_vec", "test_only", "safe_margin"), &PhysicsBody3D::_move, DEFVAL(false), DEFVAL(0.001)); - ClassDB::bind_method(D_METHOD("test_move", "from", "rel_vec", "collision", "safe_margin"), &PhysicsBody3D::test_move, DEFVAL(Variant()), DEFVAL(0.001)); + ClassDB::bind_method(D_METHOD("move_and_collide", "linear_velocity", "test_only", "safe_margin", "max_collisions"), &PhysicsBody3D::_move, DEFVAL(false), DEFVAL(0.001), DEFVAL(1)); + ClassDB::bind_method(D_METHOD("test_move", "from", "linear_velocity", "collision", "safe_margin", "max_collisions"), &PhysicsBody3D::test_move, DEFVAL(Variant()), DEFVAL(0.001), DEFVAL(1)); ClassDB::bind_method(D_METHOD("set_axis_lock", "axis", "lock"), &PhysicsBody3D::set_axis_lock); ClassDB::bind_method(D_METHOD("get_axis_lock", "axis"), &PhysicsBody3D::get_axis_lock); @@ -95,10 +95,14 @@ void PhysicsBody3D::remove_collision_exception_with(Node *p_node) { PhysicsServer3D::get_singleton()->body_remove_collision_exception(get_rid(), collision_object->get_rid()); } -Ref<KinematicCollision3D> PhysicsBody3D::_move(const Vector3 &p_motion, bool p_test_only, real_t p_margin) { +Ref<KinematicCollision3D> PhysicsBody3D::_move(const Vector3 &p_motion, bool p_test_only, real_t p_margin, int p_max_collisions) { PhysicsServer3D::MotionResult result; - if (move_and_collide(p_motion, result, p_margin, p_test_only)) { - if (motion_cache.is_null()) { + // Hack in order to work with calling from _process as well as from _physics_process; calling from thread is risky + double delta = Engine::get_singleton()->is_in_physics_frame() ? get_physics_process_delta_time() : get_process_delta_time(); + + if (move_and_collide(p_motion * delta, result, p_margin, p_test_only, p_max_collisions)) { + // Create a new instance when the cached reference is invalid or still in use in script. + if (motion_cache.is_null() || motion_cache->reference_get_count() > 1) { motion_cache.instantiate(); motion_cache->owner = this; } @@ -111,9 +115,9 @@ Ref<KinematicCollision3D> PhysicsBody3D::_move(const Vector3 &p_motion, bool p_t return Ref<KinematicCollision3D>(); } -bool PhysicsBody3D::move_and_collide(const Vector3 &p_motion, PhysicsServer3D::MotionResult &r_result, real_t p_margin, bool p_test_only, bool p_cancel_sliding, bool p_collide_separation_ray, const Set<RID> &p_exclude) { +bool PhysicsBody3D::move_and_collide(const Vector3 &p_motion, PhysicsServer3D::MotionResult &r_result, real_t p_margin, bool p_test_only, int p_max_collisions, bool p_cancel_sliding, bool p_collide_separation_ray, const Set<RID> &p_exclude) { Transform3D gt = get_global_transform(); - bool colliding = PhysicsServer3D::get_singleton()->body_test_motion(get_rid(), gt, p_motion, p_margin, &r_result, p_collide_separation_ray, p_exclude); + bool colliding = PhysicsServer3D::get_singleton()->body_test_motion(get_rid(), gt, p_motion, p_margin, &r_result, p_max_collisions, p_collide_separation_ray, p_exclude); // Restore direction of motion to be along original motion, // in order to avoid sliding due to recovery, @@ -125,9 +129,9 @@ bool PhysicsBody3D::move_and_collide(const Vector3 &p_motion, PhysicsServer3D::M if (colliding) { // Can't just use margin as a threshold because collision depth is calculated on unsafe motion, // so even in normal resting cases the depth can be a bit more than the margin. - precision += motion_length * (r_result.collision_unsafe_fraction - r_result.collision_safe_fraction); + precision += motion_length * (r_result.unsafe_fraction - r_result.safe_fraction); - if (r_result.collision_depth > (real_t)p_margin + precision) { + if (r_result.collisions[0].depth > (real_t)p_margin + precision) { p_cancel_sliding = false; } } @@ -167,7 +171,7 @@ bool PhysicsBody3D::move_and_collide(const Vector3 &p_motion, PhysicsServer3D::M return colliding; } -bool PhysicsBody3D::test_move(const Transform3D &p_from, const Vector3 &p_motion, const Ref<KinematicCollision3D> &r_collision, real_t p_margin) { +bool PhysicsBody3D::test_move(const Transform3D &p_from, const Vector3 &p_motion, const Ref<KinematicCollision3D> &r_collision, real_t p_margin, int p_max_collisions) { ERR_FAIL_COND_V(!is_inside_tree(), false); PhysicsServer3D::MotionResult *r = nullptr; @@ -176,7 +180,10 @@ bool PhysicsBody3D::test_move(const Transform3D &p_from, const Vector3 &p_motion r = const_cast<PhysicsServer3D::MotionResult *>(&r_collision->result); } - return PhysicsServer3D::get_singleton()->body_test_motion(get_rid(), p_from, p_motion, p_margin, r); + // Hack in order to work with calling from _process as well as from _physics_process; calling from thread is risky + double delta = Engine::get_singleton()->is_in_physics_frame() ? get_physics_process_delta_time() : get_process_delta_time(); + + return PhysicsServer3D::get_singleton()->body_test_motion(get_rid(), p_from, p_motion * delta, p_margin, r, p_max_collisions); } void PhysicsBody3D::set_axis_lock(PhysicsServer3D::BodyAxis p_axis, bool p_lock) { @@ -1076,64 +1083,115 @@ void RigidDynamicBody3D::_reload_physics_characteristics() { #define FLOOR_ANGLE_THRESHOLD 0.01 bool CharacterBody3D::move_and_slide() { - bool was_on_floor = on_floor; - // Hack in order to work with calling from _process as well as from _physics_process; calling from thread is risky double delta = Engine::get_singleton()->is_in_physics_frame() ? get_physics_process_delta_time() : get_process_delta_time(); - + previous_position = get_global_transform().origin; for (int i = 0; i < 3; i++) { if (locked_axis & (1 << i)) { linear_velocity[i] = 0.0; } } - Vector3 current_floor_velocity = floor_velocity; - if ((on_floor || on_wall) && on_floor_body.is_valid()) { - //this approach makes sure there is less delay between the actual body velocity and the one we saved - PhysicsDirectBodyState3D *bs = PhysicsServer3D::get_singleton()->body_get_direct_state(on_floor_body); - if (bs) { - Transform3D gt = get_global_transform(); - Vector3 local_position = gt.origin - bs->get_transform().origin; - current_floor_velocity = bs->get_velocity_at_local_position(local_position); + Vector3 current_platform_velocity = platform_velocity; + + if ((collision_state.floor || collision_state.wall) && platform_rid.is_valid()) { + bool excluded = false; + if (collision_state.floor) { + excluded = (moving_platform_floor_layers & platform_layer) == 0; + } else if (collision_state.wall) { + excluded = (moving_platform_wall_layers & platform_layer) == 0; + } + if (!excluded) { + //this approach makes sure there is less delay between the actual body velocity and the one we saved + PhysicsDirectBodyState3D *bs = PhysicsServer3D::get_singleton()->body_get_direct_state(platform_rid); + if (bs) { + Transform3D gt = get_global_transform(); + Vector3 local_position = gt.origin - bs->get_transform().origin; + current_platform_velocity = bs->get_velocity_at_local_position(local_position); + } + } else { + current_platform_velocity = Vector3(); } } motion_results.clear(); - on_floor = false; - on_ceiling = false; - on_wall = false; - floor_normal = Vector3(); - floor_velocity = Vector3(); - if (!current_floor_velocity.is_equal_approx(Vector3()) && on_floor_body.is_valid()) { + bool was_on_floor = collision_state.floor; + collision_state.state = 0; + + if (!current_platform_velocity.is_equal_approx(Vector3())) { PhysicsServer3D::MotionResult floor_result; Set<RID> exclude; - exclude.insert(on_floor_body); - if (move_and_collide(current_floor_velocity * delta, floor_result, margin, false, false, false, exclude)) { + exclude.insert(platform_rid); + if (move_and_collide(current_platform_velocity * delta, floor_result, margin, false, 1, false, false, exclude)) { motion_results.push_back(floor_result); - _set_collision_direction(floor_result); + + CollisionState result_state; + _set_collision_direction(floor_result, result_state); } } - on_floor_body = RID(); - Vector3 motion = linear_velocity * delta; + if (motion_mode == MOTION_MODE_GROUNDED) { + _move_and_slide_grounded(delta, was_on_floor); + } else { + _move_and_slide_free(delta); + } + + // Compute real velocity. + real_velocity = get_position_delta() / delta; + + if (moving_platform_apply_velocity_on_leave != PLATFORM_VEL_ON_LEAVE_NEVER) { + // Add last platform velocity when just left a moving platform. + if (!collision_state.floor && !collision_state.wall) { + if (moving_platform_apply_velocity_on_leave == PLATFORM_VEL_ON_LEAVE_UPWARD_ONLY && current_platform_velocity.dot(up_direction) < 0) { + current_platform_velocity = current_platform_velocity.slide(up_direction); + } + linear_velocity += current_platform_velocity; + } + } + + // Reset the gravity accumulation when touching the ground. + if (collision_state.floor && linear_velocity.dot(up_direction) <= 0) { + linear_velocity = linear_velocity.slide(up_direction); + } + + return motion_results.size() > 0; +} + +void CharacterBody3D::_move_and_slide_grounded(double p_delta, bool p_was_on_floor) { + Vector3 motion = linear_velocity * p_delta; + Vector3 motion_slide_up = motion.slide(up_direction); + Vector3 prev_floor_normal = floor_normal; + + platform_rid = RID(); + platform_velocity = Vector3(); + floor_normal = Vector3(); + wall_normal = Vector3(); // No sliding on first attempt to keep floor motion stable when possible, - // when stop on slope is enabled. + // When stop on slope is enabled or when there is no up direction. bool sliding_enabled = !floor_stop_on_slope; + // Constant speed can be applied only the first time sliding is enabled. + bool can_apply_constant_speed = sliding_enabled; + bool first_slide = true; + bool vel_dir_facing_up = linear_velocity.dot(up_direction) > 0; + Vector3 total_travel; for (int iteration = 0; iteration < max_slides; ++iteration) { PhysicsServer3D::MotionResult result; - bool collided = move_and_collide(motion, result, margin, false, !sliding_enabled); + bool collided = move_and_collide(motion, result, margin, false, 4, !sliding_enabled); + if (collided) { motion_results.push_back(result); - _set_collision_direction(result); - if (on_floor && floor_stop_on_slope && (linear_velocity.normalized() + up_direction).length() < 0.01) { + CollisionState previous_state = collision_state; + + CollisionState result_state; + _set_collision_direction(result, result_state); + + if (collision_state.floor && floor_stop_on_slope && (linear_velocity.normalized() + up_direction).length() < 0.01) { Transform3D gt = get_global_transform(); - if (result.travel.length() > margin) { - gt.origin -= result.travel.slide(up_direction); - } else { + if (result.travel.length() <= margin + CMP_EPSILON) { gt.origin -= result.travel; } set_global_transform(gt); @@ -1144,96 +1202,360 @@ bool CharacterBody3D::move_and_slide() { if (result.remainder.is_equal_approx(Vector3())) { motion = Vector3(); + last_motion = result.travel; break; } - if (sliding_enabled || !on_floor) { - Vector3 slide_motion = result.remainder.slide(result.collision_normal); - if (slide_motion.dot(linear_velocity) > 0.0) { - motion = slide_motion; - } else { - motion = Vector3(); + // Apply regular sliding by default. + bool apply_default_sliding = true; + + // Wall collision checks. + if (result_state.wall && (motion_slide_up.dot(wall_normal) <= 0)) { + // Move on floor only checks. + if (floor_block_on_wall) { + // Needs horizontal motion from current motion instead of motion_slide_up + // to properly test the angle and avoid standing on slopes + Vector3 horizontal_motion = motion.slide(up_direction); + Vector3 horizontal_normal = wall_normal.slide(up_direction).normalized(); + real_t motion_angle = Math::abs(Math::acos(-horizontal_normal.dot(horizontal_motion.normalized()))); + + // Avoid to move forward on a wall if floor_block_on_wall is true. + // Applies only when the motion angle is under 90 degrees, + // in order to avoid blocking lateral motion along a wall. + if (motion_angle < .5 * Math_PI) { + apply_default_sliding = false; + + if (p_was_on_floor && !vel_dir_facing_up) { + // Cancel the motion. + Transform3D gt = get_global_transform(); + real_t travel_total = result.travel.length(); + real_t cancel_dist_max = MIN(0.1, margin * 20); + if (travel_total <= margin + CMP_EPSILON) { + gt.origin -= result.travel; + } else if (travel_total < cancel_dist_max) { // If the movement is large the body can be prevented from reaching the walls. + gt.origin -= result.travel.slide(up_direction); + // Keep remaining motion in sync with amount canceled. + motion = motion.slide(up_direction); + } + set_global_transform(gt); + result.travel = Vector3(); // Cancel for constant speed computation. + + // Determines if you are on the ground, and limits the possibility of climbing on the walls because of the approximations. + _snap_on_floor(true, false); + } else { + // If the movement is not cancelled we only keep the remaining. + motion = result.remainder; + } + + // Apply slide on forward in order to allow only lateral motion on next step. + Vector3 forward = wall_normal.slide(up_direction).normalized(); + motion = motion.slide(forward); + // Avoid accelerating when you jump on the wall and smooth falling. + linear_velocity = linear_velocity.slide(forward); + + // Allow only lateral motion along previous floor when already on floor. + // Fixes slowing down when moving in diagonal against an inclined wall. + if (p_was_on_floor && !vel_dir_facing_up && (motion.dot(up_direction) > 0.0)) { + // Slide along the corner between the wall and previous floor. + Vector3 floor_side = prev_floor_normal.cross(wall_normal); + if (floor_side != Vector3()) { + motion = floor_side * motion.dot(floor_side); + } + } + + // Stop all motion when a second wall is hit (unless sliding down or jumping), + // in order to avoid jittering in corner cases. + bool stop_all_motion = previous_state.wall && !vel_dir_facing_up; + + // Allow sliding when the body falls. + if (!collision_state.floor && motion.dot(up_direction) < 0) { + Vector3 slide_motion = motion.slide(wall_normal); + // Test again to allow sliding only if the result goes downwards. + // Fixes jittering issues at the bottom of inclined walls. + if (slide_motion.dot(up_direction) < 0) { + stop_all_motion = false; + motion = slide_motion; + } + } + + if (stop_all_motion) { + motion = Vector3(); + linear_velocity = Vector3(); + } + } + } + + // Stop horizontal motion when under wall slide threshold. + if (p_was_on_floor && (wall_min_slide_angle > 0.0) && result_state.wall) { + Vector3 horizontal_normal = wall_normal.slide(up_direction).normalized(); + real_t motion_angle = Math::abs(Math::acos(-horizontal_normal.dot(motion_slide_up.normalized()))); + if (motion_angle < wall_min_slide_angle) { + motion = up_direction * motion.dot(up_direction); + linear_velocity = up_direction * linear_velocity.dot(up_direction); + + apply_default_sliding = false; + } + } + } + + if (apply_default_sliding) { + // Regular sliding, the last part of the test handle the case when you don't want to slide on the ceiling. + if ((sliding_enabled || !collision_state.floor) && (!collision_state.ceiling || slide_on_ceiling || !vel_dir_facing_up)) { + const PhysicsServer3D::MotionCollision &collision = result.collisions[0]; + Vector3 slide_motion = result.remainder.slide(collision.normal); + if (slide_motion.dot(linear_velocity) > 0.0) { + motion = slide_motion; + } else { + motion = Vector3(); + } + if (slide_on_ceiling && result_state.ceiling) { + // Apply slide only in the direction of the input motion, otherwise just stop to avoid jittering when moving against a wall. + if (vel_dir_facing_up) { + linear_velocity = linear_velocity.slide(collision.normal); + } else { + // Avoid acceleration in slope when falling. + linear_velocity = up_direction * up_direction.dot(linear_velocity); + } + } + } + // No sliding on first attempt to keep floor motion stable when possible. + else { + motion = result.remainder; + if (result_state.ceiling && !slide_on_ceiling && vel_dir_facing_up) { + linear_velocity = linear_velocity.slide(up_direction); + motion = motion.slide(up_direction); + } } - } else { - motion = result.remainder; } + + // Apply Constant Speed. + if (p_was_on_floor && floor_constant_speed && collision_state.floor && !motion.is_equal_approx(Vector3())) { + motion = motion.normalized() * MAX(0, (motion_slide_up.length() - result.travel.slide(up_direction).length() - total_travel.slide(up_direction).length())); + } + + total_travel += result.travel; } + // When you move forward in a downward slope you don’t collide because you will be in the air. + // This test ensures that constant speed is applied, only if the player is still on the ground after the snap is applied. + else if (floor_constant_speed && first_slide && _on_floor_if_snapped(p_was_on_floor, vel_dir_facing_up)) { + can_apply_constant_speed = false; + sliding_enabled = true; + Transform3D gt = get_global_transform(); + gt.origin = gt.origin - result.travel; + set_global_transform(gt); + Vector3 motion_slide_norm = motion.slide(prev_floor_normal).normalized(); + motion = motion_slide_norm * (motion_slide_up.length()); + collided = true; + } + + can_apply_constant_speed = !can_apply_constant_speed && !sliding_enabled; sliding_enabled = true; + first_slide = false; + + if (!motion.is_equal_approx(Vector3())) { + last_motion = motion; + } if (!collided || motion.is_equal_approx(Vector3())) { break; } } - if (was_on_floor && !on_floor && !snap.is_equal_approx(Vector3())) { - // Apply snap. - Transform3D gt = get_global_transform(); + _snap_on_floor(p_was_on_floor, vel_dir_facing_up); + + // Reset the gravity accumulation when touching the ground. + if (collision_state.floor && !vel_dir_facing_up) { + linear_velocity = linear_velocity.slide(up_direction); + } +} + +void CharacterBody3D::_move_and_slide_free(double p_delta) { + Vector3 motion = linear_velocity * p_delta; + + platform_rid = RID(); + floor_normal = Vector3(); + platform_velocity = Vector3(); + + bool first_slide = true; + for (int iteration = 0; iteration < max_slides; ++iteration) { PhysicsServer3D::MotionResult result; - if (move_and_collide(snap, result, margin, true, false, true)) { - bool apply = true; - if (up_direction != Vector3()) { - if (result.get_angle(up_direction) <= floor_max_angle + FLOOR_ANGLE_THRESHOLD) { - on_floor = true; - floor_normal = result.collision_normal; - on_floor_body = result.collider; - floor_velocity = result.collider_velocity; - if (floor_stop_on_slope) { - // move and collide may stray the object a bit because of pre un-stucking, - // so only ensure that motion happens on floor direction in this case. - if (result.travel.length() > margin) { - result.travel = result.travel.project(up_direction); - } else { - result.travel = Vector3(); - } - } - } else { - apply = false; //snapped with floor direction, but did not snap to a floor, do not snap. + + bool collided = move_and_collide(motion, result, margin, false, 1, false); + + if (collided) { + motion_results.push_back(result); + + CollisionState result_state; + _set_collision_direction(result, result_state); + + if (wall_min_slide_angle != 0 && Math::acos(wall_normal.dot(-linear_velocity.normalized())) < wall_min_slide_angle + FLOOR_ANGLE_THRESHOLD) { + motion = Vector3(); + if (result.travel.length() < margin + CMP_EPSILON) { + Transform3D gt = get_global_transform(); + gt.origin -= result.travel; + set_global_transform(gt); } + } else if (first_slide) { + Vector3 motion_slide_norm = result.remainder.slide(wall_normal).normalized(); + motion = motion_slide_norm * (motion.length() - result.travel.length()); + } else { + motion = result.remainder.slide(wall_normal); } - if (apply) { - gt.origin += result.travel; - set_global_transform(gt); + + if (motion.dot(linear_velocity) <= 0.0) { + motion = Vector3(); } } + + first_slide = false; + + if (!collided || motion.is_equal_approx(Vector3())) { + break; + } } +} - if (!on_floor && !on_wall) { - // Add last platform velocity when just left a moving platform. - linear_velocity += current_floor_velocity; +void CharacterBody3D::_snap_on_floor(bool was_on_floor, bool vel_dir_facing_up) { + if (collision_state.floor || !was_on_floor || vel_dir_facing_up) { + return; } - // Reset the gravity accumulation when touching the ground. - if (on_floor && linear_velocity.dot(up_direction) <= 0) { - linear_velocity = linear_velocity.slide(up_direction); + // Snap by at least collision margin to keep floor state consistent. + real_t length = MAX(floor_snap_length, margin); + + Transform3D gt = get_global_transform(); + PhysicsServer3D::MotionResult result; + if (move_and_collide(-up_direction * length, result, margin, true, 4, false, true)) { + CollisionState result_state; + // Apply direction for floor only. + _set_collision_direction(result, result_state, CollisionState(true, false, false)); + + if (result_state.floor) { + if (floor_stop_on_slope) { + // move and collide may stray the object a bit because of pre un-stucking, + // so only ensure that motion happens on floor direction in this case. + if (result.travel.length() > margin) { + result.travel = up_direction * up_direction.dot(result.travel); + } else { + result.travel = Vector3(); + } + } + + gt.origin += result.travel; + set_global_transform(gt); + } } +} - return motion_results.size() > 0; +bool CharacterBody3D::_on_floor_if_snapped(bool was_on_floor, bool vel_dir_facing_up) { + if (up_direction == Vector3() || collision_state.floor || !was_on_floor || vel_dir_facing_up) { + return false; + } + + // Snap by at least collision margin to keep floor state consistent. + real_t length = MAX(floor_snap_length, margin); + + PhysicsServer3D::MotionResult result; + if (move_and_collide(-up_direction * length, result, margin, true, 4, false, true)) { + CollisionState result_state; + // Don't apply direction for any type. + _set_collision_direction(result, result_state, CollisionState()); + + return result_state.floor; + } + + return false; } -void CharacterBody3D::_set_collision_direction(const PhysicsServer3D::MotionResult &p_result) { - if (up_direction == Vector3()) { - //all is a wall - on_wall = true; - } else { - if (p_result.get_angle(up_direction) <= floor_max_angle + FLOOR_ANGLE_THRESHOLD) { //floor - on_floor = true; - floor_normal = p_result.collision_normal; - on_floor_body = p_result.collider; - floor_velocity = p_result.collider_velocity; - } else if (p_result.get_angle(-up_direction) <= floor_max_angle + FLOOR_ANGLE_THRESHOLD) { //ceiling - on_ceiling = true; - } else { - on_wall = true; +void CharacterBody3D::_set_collision_direction(const PhysicsServer3D::MotionResult &p_result, CollisionState &r_state, CollisionState p_apply_state) { + r_state.state = 0; + + real_t wall_depth = -1.0; + real_t floor_depth = -1.0; + + bool was_on_wall = collision_state.wall; + Vector3 prev_wall_normal = wall_normal; + int wall_collision_count = 0; + Vector3 combined_wall_normal; + + for (int i = p_result.collision_count - 1; i >= 0; i--) { + const PhysicsServer3D::MotionCollision &collision = p_result.collisions[i]; + + if (motion_mode == MOTION_MODE_GROUNDED) { + // Check if any collision is floor. + real_t floor_angle = collision.get_angle(up_direction); + if (floor_angle <= floor_max_angle + FLOOR_ANGLE_THRESHOLD) { + r_state.floor = true; + if (p_apply_state.floor && collision.depth > floor_depth) { + collision_state.floor = true; + floor_normal = collision.normal; + floor_depth = collision.depth; + _set_platform_data(collision); + } + continue; + } + + // Check if any collision is ceiling. + real_t ceiling_angle = collision.get_angle(-up_direction); + if (ceiling_angle <= floor_max_angle + FLOOR_ANGLE_THRESHOLD) { + r_state.ceiling = true; + if (p_apply_state.ceiling) { + collision_state.ceiling = true; + } + continue; + } + } + + // Collision is wall by default. + r_state.wall = true; + + if (p_apply_state.wall && collision.depth > wall_depth) { + collision_state.wall = true; + wall_depth = collision.depth; + wall_normal = collision.normal; + // Don't apply wall velocity when the collider is a CharacterBody3D. - if (Object::cast_to<CharacterBody3D>(ObjectDB::get_instance(p_result.collider_id)) == nullptr) { - on_floor_body = p_result.collider; - floor_velocity = p_result.collider_velocity; + if (Object::cast_to<CharacterBody3D>(ObjectDB::get_instance(collision.collider_id)) == nullptr) { + _set_platform_data(collision); + } + } + + // Collect normal for calculating average. + combined_wall_normal += collision.normal; + wall_collision_count++; + } + + if (r_state.wall) { + if (wall_collision_count > 1 && !r_state.floor) { + // Check if wall normals cancel out to floor support. + if (!r_state.floor && motion_mode == MOTION_MODE_GROUNDED) { + combined_wall_normal.normalize(); + real_t floor_angle = Math::acos(combined_wall_normal.dot(up_direction)); + if (floor_angle <= floor_max_angle + FLOOR_ANGLE_THRESHOLD) { + r_state.floor = true; + r_state.wall = false; + if (p_apply_state.floor) { + collision_state.floor = true; + floor_normal = combined_wall_normal; + } + if (p_apply_state.wall) { + collision_state.wall = was_on_wall; + wall_normal = prev_wall_normal; + } + return; + } } } } } +void CharacterBody3D::_set_platform_data(const PhysicsServer3D::MotionCollision &p_collision) { + platform_rid = p_collision.collider; + platform_velocity = p_collision.collider_velocity; + platform_layer = PhysicsServer3D::get_singleton()->body_get_collision_layer(platform_rid); +} + void CharacterBody3D::set_safe_margin(real_t p_margin) { margin = p_margin; } @@ -1251,40 +1573,56 @@ void CharacterBody3D::set_linear_velocity(const Vector3 &p_velocity) { } bool CharacterBody3D::is_on_floor() const { - return on_floor; + return collision_state.floor; } bool CharacterBody3D::is_on_floor_only() const { - return on_floor && !on_wall && !on_ceiling; + return collision_state.floor && !collision_state.wall && !collision_state.ceiling; } bool CharacterBody3D::is_on_wall() const { - return on_wall; + return collision_state.wall; } bool CharacterBody3D::is_on_wall_only() const { - return on_wall && !on_floor && !on_ceiling; + return collision_state.wall && !collision_state.floor && !collision_state.ceiling; } bool CharacterBody3D::is_on_ceiling() const { - return on_ceiling; + return collision_state.ceiling; } bool CharacterBody3D::is_on_ceiling_only() const { - return on_ceiling && !on_floor && !on_wall; + return collision_state.ceiling && !collision_state.floor && !collision_state.wall; } Vector3 CharacterBody3D::get_floor_normal() const { return floor_normal; } +Vector3 CharacterBody3D::get_wall_normal() const { + return wall_normal; +} + +Vector3 CharacterBody3D::get_last_motion() const { + return last_motion; +} + +Vector3 CharacterBody3D::get_position_delta() const { + return get_transform().origin - previous_position; +} + +Vector3 CharacterBody3D::get_real_velocity() const { + return real_velocity; +}; + real_t CharacterBody3D::get_floor_angle(const Vector3 &p_up_direction) const { ERR_FAIL_COND_V(p_up_direction == Vector3(), 0); return Math::acos(floor_normal.dot(p_up_direction)); } Vector3 CharacterBody3D::get_platform_velocity() const { - return floor_velocity; + return platform_velocity; } int CharacterBody3D::get_slide_collision_count() const { @@ -1302,7 +1640,8 @@ Ref<KinematicCollision3D> CharacterBody3D::_get_slide_collision(int p_bounce) { slide_colliders.resize(p_bounce + 1); } - if (slide_colliders[p_bounce].is_null()) { + // Create a new instance when the cached reference is invalid or still in use in script. + if (slide_colliders[p_bounce].is_null() || slide_colliders[p_bounce]->reference_get_count() > 1) { slide_colliders.write[p_bounce].instantiate(); slide_colliders.write[p_bounce]->owner = this; } @@ -1326,6 +1665,62 @@ void CharacterBody3D::set_floor_stop_on_slope_enabled(bool p_enabled) { floor_stop_on_slope = p_enabled; } +bool CharacterBody3D::is_floor_constant_speed_enabled() const { + return floor_constant_speed; +} + +void CharacterBody3D::set_floor_constant_speed_enabled(bool p_enabled) { + floor_constant_speed = p_enabled; +} + +bool CharacterBody3D::is_floor_block_on_wall_enabled() const { + return floor_block_on_wall; +} + +void CharacterBody3D::set_floor_block_on_wall_enabled(bool p_enabled) { + floor_block_on_wall = p_enabled; +} + +bool CharacterBody3D::is_slide_on_ceiling_enabled() const { + return slide_on_ceiling; +} + +void CharacterBody3D::set_slide_on_ceiling_enabled(bool p_enabled) { + slide_on_ceiling = p_enabled; +} + +uint32_t CharacterBody3D::get_moving_platform_floor_layers() const { + return moving_platform_floor_layers; +} + +void CharacterBody3D::set_moving_platform_floor_layers(uint32_t p_exclude_layers) { + moving_platform_floor_layers = p_exclude_layers; +} + +uint32_t CharacterBody3D::get_moving_platform_wall_layers() const { + return moving_platform_wall_layers; +} + +void CharacterBody3D::set_moving_platform_wall_layers(uint32_t p_exclude_layers) { + moving_platform_wall_layers = p_exclude_layers; +} + +void CharacterBody3D::set_motion_mode(MotionMode p_mode) { + motion_mode = p_mode; +} + +CharacterBody3D::MotionMode CharacterBody3D::get_motion_mode() const { + return motion_mode; +} + +void CharacterBody3D::set_moving_platform_apply_velocity_on_leave(MovingPlatformApplyVelocityOnLeave p_on_leave_apply_velocity) { + moving_platform_apply_velocity_on_leave = p_on_leave_apply_velocity; +} + +CharacterBody3D::MovingPlatformApplyVelocityOnLeave CharacterBody3D::get_moving_platform_apply_velocity_on_leave() const { + return moving_platform_apply_velocity_on_leave; +} + int CharacterBody3D::get_max_slides() const { return max_slides; } @@ -1343,12 +1738,21 @@ void CharacterBody3D::set_floor_max_angle(real_t p_radians) { floor_max_angle = p_radians; } -const Vector3 &CharacterBody3D::get_snap() const { - return snap; +real_t CharacterBody3D::get_floor_snap_length() { + return floor_snap_length; +} + +void CharacterBody3D::set_floor_snap_length(real_t p_floor_snap_length) { + ERR_FAIL_COND(p_floor_snap_length < 0); + floor_snap_length = p_floor_snap_length; +} + +real_t CharacterBody3D::get_wall_min_slide_angle() const { + return wall_min_slide_angle; } -void CharacterBody3D::set_snap(const Vector3 &p_snap) { - snap = p_snap; +void CharacterBody3D::set_wall_min_slide_angle(real_t p_radians) { + wall_min_slide_angle = p_radians; } const Vector3 &CharacterBody3D::get_up_direction() const { @@ -1356,6 +1760,7 @@ const Vector3 &CharacterBody3D::get_up_direction() const { } void CharacterBody3D::set_up_direction(const Vector3 &p_up_direction) { + ERR_FAIL_COND_MSG(p_up_direction == Vector3(), "up_direction can't be equal to Vector3.ZERO, consider using Free motion mode instead."); up_direction = p_up_direction.normalized(); } @@ -1363,12 +1768,10 @@ void CharacterBody3D::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_TREE: { // Reset move_and_slide() data. - on_floor = false; - on_floor_body = RID(); - on_ceiling = false; - on_wall = false; + collision_state.state = 0; + platform_rid = RID(); motion_results.clear(); - floor_velocity = Vector3(); + platform_velocity = Vector3(); } break; } } @@ -1383,14 +1786,32 @@ void CharacterBody3D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_safe_margin"), &CharacterBody3D::get_safe_margin); ClassDB::bind_method(D_METHOD("is_floor_stop_on_slope_enabled"), &CharacterBody3D::is_floor_stop_on_slope_enabled); ClassDB::bind_method(D_METHOD("set_floor_stop_on_slope_enabled", "enabled"), &CharacterBody3D::set_floor_stop_on_slope_enabled); + ClassDB::bind_method(D_METHOD("set_floor_constant_speed_enabled", "enabled"), &CharacterBody3D::set_floor_constant_speed_enabled); + ClassDB::bind_method(D_METHOD("is_floor_constant_speed_enabled"), &CharacterBody3D::is_floor_constant_speed_enabled); + ClassDB::bind_method(D_METHOD("set_floor_block_on_wall_enabled", "enabled"), &CharacterBody3D::set_floor_block_on_wall_enabled); + ClassDB::bind_method(D_METHOD("is_floor_block_on_wall_enabled"), &CharacterBody3D::is_floor_block_on_wall_enabled); + ClassDB::bind_method(D_METHOD("set_slide_on_ceiling_enabled", "enabled"), &CharacterBody3D::set_slide_on_ceiling_enabled); + ClassDB::bind_method(D_METHOD("is_slide_on_ceiling_enabled"), &CharacterBody3D::is_slide_on_ceiling_enabled); + + ClassDB::bind_method(D_METHOD("set_moving_platform_floor_layers", "exclude_layer"), &CharacterBody3D::set_moving_platform_floor_layers); + ClassDB::bind_method(D_METHOD("get_moving_platform_floor_layers"), &CharacterBody3D::get_moving_platform_floor_layers); + ClassDB::bind_method(D_METHOD("set_moving_platform_wall_layers", "exclude_layer"), &CharacterBody3D::set_moving_platform_wall_layers); + ClassDB::bind_method(D_METHOD("get_moving_platform_wall_layers"), &CharacterBody3D::get_moving_platform_wall_layers); + ClassDB::bind_method(D_METHOD("get_max_slides"), &CharacterBody3D::get_max_slides); ClassDB::bind_method(D_METHOD("set_max_slides", "max_slides"), &CharacterBody3D::set_max_slides); ClassDB::bind_method(D_METHOD("get_floor_max_angle"), &CharacterBody3D::get_floor_max_angle); ClassDB::bind_method(D_METHOD("set_floor_max_angle", "radians"), &CharacterBody3D::set_floor_max_angle); - ClassDB::bind_method(D_METHOD("get_snap"), &CharacterBody3D::get_snap); - ClassDB::bind_method(D_METHOD("set_snap", "snap"), &CharacterBody3D::set_snap); + ClassDB::bind_method(D_METHOD("get_floor_snap_length"), &CharacterBody3D::get_floor_snap_length); + ClassDB::bind_method(D_METHOD("set_floor_snap_length", "floor_snap_length"), &CharacterBody3D::set_floor_snap_length); + ClassDB::bind_method(D_METHOD("get_wall_min_slide_angle"), &CharacterBody3D::get_wall_min_slide_angle); + ClassDB::bind_method(D_METHOD("set_wall_min_slide_angle", "radians"), &CharacterBody3D::set_wall_min_slide_angle); ClassDB::bind_method(D_METHOD("get_up_direction"), &CharacterBody3D::get_up_direction); ClassDB::bind_method(D_METHOD("set_up_direction", "up_direction"), &CharacterBody3D::set_up_direction); + ClassDB::bind_method(D_METHOD("set_motion_mode", "mode"), &CharacterBody3D::set_motion_mode); + ClassDB::bind_method(D_METHOD("get_motion_mode"), &CharacterBody3D::get_motion_mode); + ClassDB::bind_method(D_METHOD("set_moving_platform_apply_velocity_on_leave", "on_leave_apply_velocity"), &CharacterBody3D::set_moving_platform_apply_velocity_on_leave); + ClassDB::bind_method(D_METHOD("get_moving_platform_apply_velocity_on_leave"), &CharacterBody3D::get_moving_platform_apply_velocity_on_leave); ClassDB::bind_method(D_METHOD("is_on_floor"), &CharacterBody3D::is_on_floor); ClassDB::bind_method(D_METHOD("is_on_floor_only"), &CharacterBody3D::is_on_floor_only); @@ -1399,21 +1820,49 @@ void CharacterBody3D::_bind_methods() { ClassDB::bind_method(D_METHOD("is_on_wall"), &CharacterBody3D::is_on_wall); ClassDB::bind_method(D_METHOD("is_on_wall_only"), &CharacterBody3D::is_on_wall_only); ClassDB::bind_method(D_METHOD("get_floor_normal"), &CharacterBody3D::get_floor_normal); + ClassDB::bind_method(D_METHOD("get_wall_normal"), &CharacterBody3D::get_wall_normal); + ClassDB::bind_method(D_METHOD("get_last_motion"), &CharacterBody3D::get_last_motion); + ClassDB::bind_method(D_METHOD("get_position_delta"), &CharacterBody3D::get_position_delta); + ClassDB::bind_method(D_METHOD("get_real_velocity"), &CharacterBody3D::get_real_velocity); ClassDB::bind_method(D_METHOD("get_floor_angle", "up_direction"), &CharacterBody3D::get_floor_angle, DEFVAL(Vector3(0.0, 1.0, 0.0))); ClassDB::bind_method(D_METHOD("get_platform_velocity"), &CharacterBody3D::get_platform_velocity); - ClassDB::bind_method(D_METHOD("get_slide_collision_count"), &CharacterBody3D::get_slide_collision_count); ClassDB::bind_method(D_METHOD("get_slide_collision", "slide_idx"), &CharacterBody3D::_get_slide_collision); ClassDB::bind_method(D_METHOD("get_last_slide_collision"), &CharacterBody3D::_get_last_slide_collision); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "linear_velocity"), "set_linear_velocity", "get_linear_velocity"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "max_slides", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_max_slides", "get_max_slides"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "snap"), "set_snap", "get_snap"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "motion_mode", PROPERTY_HINT_ENUM, "Grounded,Free", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), "set_motion_mode", "get_motion_mode"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "up_direction"), "set_up_direction", "get_up_direction"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "slide_on_ceiling"), "set_slide_on_ceiling_enabled", "is_slide_on_ceiling_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "linear_velocity", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_linear_velocity", "get_linear_velocity"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "max_slides", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_max_slides", "get_max_slides"); + ADD_GROUP("Free Mode", "free_mode_"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "wall_min_slide_angle", PROPERTY_HINT_RANGE, "0,180,0.1,radians", PROPERTY_USAGE_DEFAULT), "set_wall_min_slide_angle", "get_wall_min_slide_angle"); ADD_GROUP("Floor", "floor_"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "floor_max_angle", PROPERTY_HINT_RANGE, "0,180,0.1,radians"), "set_floor_max_angle", "get_floor_max_angle"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "floor_stop_on_slope"), "set_floor_stop_on_slope_enabled", "is_floor_stop_on_slope_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "floor_constant_speed"), "set_floor_constant_speed_enabled", "is_floor_constant_speed_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "floor_block_on_wall"), "set_floor_block_on_wall_enabled", "is_floor_block_on_wall_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "floor_max_angle", PROPERTY_HINT_RANGE, "0,180,0.1,radians"), "set_floor_max_angle", "get_floor_max_angle"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "floor_snap_length", PROPERTY_HINT_RANGE, "0,1,0.01,or_greater"), "set_floor_snap_length", "get_floor_snap_length"); + ADD_GROUP("Moving platform", "moving_platform"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "moving_platform_apply_velocity_on_leave", PROPERTY_HINT_ENUM, "Always,Upward Only,Never", PROPERTY_USAGE_DEFAULT), "set_moving_platform_apply_velocity_on_leave", "get_moving_platform_apply_velocity_on_leave"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "moving_platform_floor_layers", PROPERTY_HINT_LAYERS_2D_PHYSICS), "set_moving_platform_floor_layers", "get_moving_platform_floor_layers"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "moving_platform_wall_layers", PROPERTY_HINT_LAYERS_2D_PHYSICS), "set_moving_platform_wall_layers", "get_moving_platform_wall_layers"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "collision/safe_margin", PROPERTY_HINT_RANGE, "0.001,256,0.001"), "set_safe_margin", "get_safe_margin"); + + BIND_ENUM_CONSTANT(MOTION_MODE_GROUNDED); + BIND_ENUM_CONSTANT(MOTION_MODE_FREE); + + BIND_ENUM_CONSTANT(PLATFORM_VEL_ON_LEAVE_ALWAYS); + BIND_ENUM_CONSTANT(PLATFORM_VEL_ON_LEAVE_UPWARD_ONLY); + BIND_ENUM_CONSTANT(PLATFORM_VEL_ON_LEAVE_NEVER); +} + +void CharacterBody3D::_validate_property(PropertyInfo &property) const { + if (motion_mode == MOTION_MODE_FREE) { + if (property.name.begins_with("floor_") || property.name == "up_direction" || property.name == "slide_on_ceiling") { + property.usage = PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL; + } + } } CharacterBody3D::CharacterBody3D() : @@ -1430,14 +1879,6 @@ CharacterBody3D::~CharacterBody3D() { /////////////////////////////////////// -Vector3 KinematicCollision3D::get_position() const { - return result.collision_point; -} - -Vector3 KinematicCollision3D::get_normal() const { - return result.collision_normal; -} - Vector3 KinematicCollision3D::get_travel() const { return result.travel; } @@ -1446,41 +1887,60 @@ Vector3 KinematicCollision3D::get_remainder() const { return result.remainder; } -real_t KinematicCollision3D::get_angle(const Vector3 &p_up_direction) const { +int KinematicCollision3D::get_collision_count() const { + return result.collision_count; +} + +Vector3 KinematicCollision3D::get_position(int p_collision_index) const { + ERR_FAIL_INDEX_V(p_collision_index, result.collision_count, Vector3()); + return result.collisions[p_collision_index].position; +} + +Vector3 KinematicCollision3D::get_normal(int p_collision_index) const { + ERR_FAIL_INDEX_V(p_collision_index, result.collision_count, Vector3()); + return result.collisions[p_collision_index].normal; +} + +real_t KinematicCollision3D::get_angle(int p_collision_index, const Vector3 &p_up_direction) const { + ERR_FAIL_INDEX_V(p_collision_index, result.collision_count, 0.0); ERR_FAIL_COND_V(p_up_direction == Vector3(), 0); - return result.get_angle(p_up_direction); + return result.collisions[p_collision_index].get_angle(p_up_direction); } -Object *KinematicCollision3D::get_local_shape() const { +Object *KinematicCollision3D::get_local_shape(int p_collision_index) const { + ERR_FAIL_INDEX_V(p_collision_index, result.collision_count, nullptr); if (!owner) { return nullptr; } - uint32_t ownerid = owner->shape_find_owner(result.collision_local_shape); + uint32_t ownerid = owner->shape_find_owner(result.collisions[p_collision_index].local_shape); return owner->shape_owner_get_owner(ownerid); } -Object *KinematicCollision3D::get_collider() const { - if (result.collider_id.is_valid()) { - return ObjectDB::get_instance(result.collider_id); +Object *KinematicCollision3D::get_collider(int p_collision_index) const { + ERR_FAIL_INDEX_V(p_collision_index, result.collision_count, nullptr); + if (result.collisions[p_collision_index].collider_id.is_valid()) { + return ObjectDB::get_instance(result.collisions[p_collision_index].collider_id); } return nullptr; } -ObjectID KinematicCollision3D::get_collider_id() const { - return result.collider_id; +ObjectID KinematicCollision3D::get_collider_id(int p_collision_index) const { + ERR_FAIL_INDEX_V(p_collision_index, result.collision_count, ObjectID()); + return result.collisions[p_collision_index].collider_id; } -RID KinematicCollision3D::get_collider_rid() const { - return result.collider; +RID KinematicCollision3D::get_collider_rid(int p_collision_index) const { + ERR_FAIL_INDEX_V(p_collision_index, result.collision_count, RID()); + return result.collisions[p_collision_index].collider; } -Object *KinematicCollision3D::get_collider_shape() const { - Object *collider = get_collider(); +Object *KinematicCollision3D::get_collider_shape(int p_collision_index) const { + Object *collider = get_collider(p_collision_index); if (collider) { CollisionObject3D *obj2d = Object::cast_to<CollisionObject3D>(collider); if (obj2d) { - uint32_t ownerid = obj2d->shape_find_owner(result.collider_shape); + uint32_t ownerid = obj2d->shape_find_owner(result.collisions[p_collision_index].collider_shape); return obj2d->shape_owner_get_owner(ownerid); } } @@ -1488,45 +1948,101 @@ Object *KinematicCollision3D::get_collider_shape() const { return nullptr; } -int KinematicCollision3D::get_collider_shape_index() const { - return result.collider_shape; +int KinematicCollision3D::get_collider_shape_index(int p_collision_index) const { + ERR_FAIL_INDEX_V(p_collision_index, result.collision_count, 0); + return result.collisions[p_collision_index].collider_shape; } -Vector3 KinematicCollision3D::get_collider_velocity() const { - return result.collider_velocity; +Vector3 KinematicCollision3D::get_collider_velocity(int p_collision_index) const { + ERR_FAIL_INDEX_V(p_collision_index, result.collision_count, Vector3()); + return result.collisions[p_collision_index].collider_velocity; } -Variant KinematicCollision3D::get_collider_metadata() const { +Variant KinematicCollision3D::get_collider_metadata(int p_collision_index) const { + ERR_FAIL_INDEX_V(p_collision_index, result.collision_count, Variant()); return Variant(); } +Vector3 KinematicCollision3D::get_best_position() const { + return result.collision_count ? get_position() : Vector3(); +} + +Vector3 KinematicCollision3D::get_best_normal() const { + return result.collision_count ? get_normal() : Vector3(); +} + +Object *KinematicCollision3D::get_best_local_shape() const { + return result.collision_count ? get_local_shape() : nullptr; +} + +Object *KinematicCollision3D::get_best_collider() const { + return result.collision_count ? get_collider() : nullptr; +} + +ObjectID KinematicCollision3D::get_best_collider_id() const { + return result.collision_count ? get_collider_id() : ObjectID(); +} + +RID KinematicCollision3D::get_best_collider_rid() const { + return result.collision_count ? get_collider_rid() : RID(); +} + +Object *KinematicCollision3D::get_best_collider_shape() const { + return result.collision_count ? get_collider_shape() : nullptr; +} + +int KinematicCollision3D::get_best_collider_shape_index() const { + return result.collision_count ? get_collider_shape_index() : 0; +} + +Vector3 KinematicCollision3D::get_best_collider_velocity() const { + return result.collision_count ? get_collider_velocity() : Vector3(); +} + +Variant KinematicCollision3D::get_best_collider_metadata() const { + return result.collision_count ? get_collider_metadata() : Variant(); +} + void KinematicCollision3D::_bind_methods() { - ClassDB::bind_method(D_METHOD("get_position"), &KinematicCollision3D::get_position); - ClassDB::bind_method(D_METHOD("get_normal"), &KinematicCollision3D::get_normal); ClassDB::bind_method(D_METHOD("get_travel"), &KinematicCollision3D::get_travel); ClassDB::bind_method(D_METHOD("get_remainder"), &KinematicCollision3D::get_remainder); - ClassDB::bind_method(D_METHOD("get_angle", "up_direction"), &KinematicCollision3D::get_angle, DEFVAL(Vector3(0.0, 1.0, 0.0))); - ClassDB::bind_method(D_METHOD("get_local_shape"), &KinematicCollision3D::get_local_shape); - ClassDB::bind_method(D_METHOD("get_collider"), &KinematicCollision3D::get_collider); - ClassDB::bind_method(D_METHOD("get_collider_id"), &KinematicCollision3D::get_collider_id); - ClassDB::bind_method(D_METHOD("get_collider_rid"), &KinematicCollision3D::get_collider_rid); - ClassDB::bind_method(D_METHOD("get_collider_shape"), &KinematicCollision3D::get_collider_shape); - ClassDB::bind_method(D_METHOD("get_collider_shape_index"), &KinematicCollision3D::get_collider_shape_index); - ClassDB::bind_method(D_METHOD("get_collider_velocity"), &KinematicCollision3D::get_collider_velocity); - ClassDB::bind_method(D_METHOD("get_collider_metadata"), &KinematicCollision3D::get_collider_metadata); - - ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "position"), "", "get_position"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "normal"), "", "get_normal"); + ClassDB::bind_method(D_METHOD("get_collision_count"), &KinematicCollision3D::get_collision_count); + ClassDB::bind_method(D_METHOD("get_position", "collision_index"), &KinematicCollision3D::get_position, DEFVAL(0)); + ClassDB::bind_method(D_METHOD("get_normal", "collision_index"), &KinematicCollision3D::get_normal, DEFVAL(0)); + ClassDB::bind_method(D_METHOD("get_angle", "collision_index", "up_direction"), &KinematicCollision3D::get_angle, DEFVAL(0), DEFVAL(Vector3(0.0, 1.0, 0.0))); + ClassDB::bind_method(D_METHOD("get_local_shape", "collision_index"), &KinematicCollision3D::get_local_shape, DEFVAL(0)); + ClassDB::bind_method(D_METHOD("get_collider", "collision_index"), &KinematicCollision3D::get_collider, DEFVAL(0)); + ClassDB::bind_method(D_METHOD("get_collider_id", "collision_index"), &KinematicCollision3D::get_collider_id, DEFVAL(0)); + ClassDB::bind_method(D_METHOD("get_collider_rid", "collision_index"), &KinematicCollision3D::get_collider_rid, DEFVAL(0)); + ClassDB::bind_method(D_METHOD("get_collider_shape", "collision_index"), &KinematicCollision3D::get_collider_shape, DEFVAL(0)); + ClassDB::bind_method(D_METHOD("get_collider_shape_index", "collision_index"), &KinematicCollision3D::get_collider_shape_index, DEFVAL(0)); + ClassDB::bind_method(D_METHOD("get_collider_velocity", "collision_index"), &KinematicCollision3D::get_collider_velocity, DEFVAL(0)); + ClassDB::bind_method(D_METHOD("get_collider_metadata", "collision_index"), &KinematicCollision3D::get_collider_metadata, DEFVAL(0)); + + ClassDB::bind_method(D_METHOD("get_best_position"), &KinematicCollision3D::get_best_position); + ClassDB::bind_method(D_METHOD("get_best_normal"), &KinematicCollision3D::get_best_normal); + ClassDB::bind_method(D_METHOD("get_best_local_shape"), &KinematicCollision3D::get_best_local_shape); + ClassDB::bind_method(D_METHOD("get_best_collider"), &KinematicCollision3D::get_best_collider); + ClassDB::bind_method(D_METHOD("get_best_collider_id"), &KinematicCollision3D::get_best_collider_id); + ClassDB::bind_method(D_METHOD("get_best_collider_rid"), &KinematicCollision3D::get_best_collider_rid); + ClassDB::bind_method(D_METHOD("get_best_collider_shape"), &KinematicCollision3D::get_best_collider_shape); + ClassDB::bind_method(D_METHOD("get_best_collider_shape_index"), &KinematicCollision3D::get_best_collider_shape_index); + ClassDB::bind_method(D_METHOD("get_best_collider_velocity"), &KinematicCollision3D::get_best_collider_velocity); + ClassDB::bind_method(D_METHOD("get_best_collider_metadata"), &KinematicCollision3D::get_best_collider_metadata); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "travel"), "", "get_travel"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "remainder"), "", "get_remainder"); - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "local_shape"), "", "get_local_shape"); - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "collider"), "", "get_collider"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "collider_id"), "", "get_collider_id"); - ADD_PROPERTY(PropertyInfo(Variant::RID, "collider_rid"), "", "get_collider_rid"); - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "collider_shape"), "", "get_collider_shape"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "collider_shape_index"), "", "get_collider_shape_index"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "collider_velocity"), "", "get_collider_velocity"); - ADD_PROPERTY(PropertyInfo(Variant::NIL, "collider_metadata", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NIL_IS_VARIANT), "", "get_collider_metadata"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "collision_count"), "", "get_collision_count"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "position"), "", "get_best_position"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "normal"), "", "get_best_normal"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "local_shape"), "", "get_best_local_shape"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "collider"), "", "get_best_collider"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "collider_id"), "", "get_best_collider_id"); + ADD_PROPERTY(PropertyInfo(Variant::RID, "collider_rid"), "", "get_best_collider_rid"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "collider_shape"), "", "get_best_collider_shape"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "collider_shape_index"), "", "get_best_collider_shape_index"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "collider_velocity"), "", "get_best_collider_velocity"); + ADD_PROPERTY(PropertyInfo(Variant::NIL, "collider_metadata", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NIL_IS_VARIANT), "", "get_best_collider_metadata"); } /////////////////////////////////////// diff --git a/scene/3d/physics_body_3d.h b/scene/3d/physics_body_3d.h index 9d6c8cd35e..a53147cb8f 100644 --- a/scene/3d/physics_body_3d.h +++ b/scene/3d/physics_body_3d.h @@ -50,11 +50,11 @@ protected: uint16_t locked_axis = 0; - Ref<KinematicCollision3D> _move(const Vector3 &p_motion, bool p_test_only = false, real_t p_margin = 0.001); + Ref<KinematicCollision3D> _move(const Vector3 &p_motion, bool p_test_only = false, real_t p_margin = 0.001, int p_max_collisions = 1); public: - bool move_and_collide(const Vector3 &p_motion, PhysicsServer3D::MotionResult &r_result, real_t p_margin, bool p_test_only = false, bool p_cancel_sliding = true, bool p_collide_separation_ray = false, const Set<RID> &p_exclude = Set<RID>()); - bool test_move(const Transform3D &p_from, const Vector3 &p_motion, const Ref<KinematicCollision3D> &r_collision = Ref<KinematicCollision3D>(), real_t p_margin = 0.001); + bool move_and_collide(const Vector3 &p_motion, PhysicsServer3D::MotionResult &r_result, real_t p_margin, bool p_test_only = false, int p_max_collisions = 1, bool p_cancel_sliding = true, bool p_collide_separation_ray = false, const Set<RID> &p_exclude = Set<RID>()); + bool test_move(const Transform3D &p_from, const Vector3 &p_motion, const Ref<KinematicCollision3D> &r_collision = Ref<KinematicCollision3D>(), real_t p_margin = 0.001, int p_max_collisions = 1); void set_axis_lock(PhysicsServer3D::BodyAxis p_axis, bool p_lock); bool get_axis_lock(PhysicsServer3D::BodyAxis p_axis) const; @@ -105,7 +105,7 @@ private: Vector3 linear_velocity; Vector3 angular_velocity; - bool sync_to_physics = false; + bool sync_to_physics = true; Transform3D last_valid_transform; @@ -316,76 +316,148 @@ class KinematicCollision3D; class CharacterBody3D : public PhysicsBody3D { GDCLASS(CharacterBody3D, PhysicsBody3D); +public: + enum MotionMode { + MOTION_MODE_GROUNDED, + MOTION_MODE_FREE, + }; + enum MovingPlatformApplyVelocityOnLeave { + PLATFORM_VEL_ON_LEAVE_ALWAYS, + PLATFORM_VEL_ON_LEAVE_UPWARD_ONLY, + PLATFORM_VEL_ON_LEAVE_NEVER, + }; + bool move_and_slide(); + + virtual Vector3 get_linear_velocity() const override; + void set_linear_velocity(const Vector3 &p_velocity); + + bool is_on_floor() const; + bool is_on_floor_only() const; + bool is_on_wall() const; + bool is_on_wall_only() const; + bool is_on_ceiling() const; + bool is_on_ceiling_only() const; + Vector3 get_last_motion() const; + Vector3 get_position_delta() const; + Vector3 get_floor_normal() const; + Vector3 get_wall_normal() const; + Vector3 get_real_velocity() const; + real_t get_floor_angle(const Vector3 &p_up_direction = Vector3(0.0, 1.0, 0.0)) const; + Vector3 get_platform_velocity() const; + + int get_slide_collision_count() const; + PhysicsServer3D::MotionResult get_slide_collision(int p_bounce) const; + + CharacterBody3D(); + ~CharacterBody3D(); + private: real_t margin = 0.001; + MotionMode motion_mode = MOTION_MODE_GROUNDED; + MovingPlatformApplyVelocityOnLeave moving_platform_apply_velocity_on_leave = PLATFORM_VEL_ON_LEAVE_ALWAYS; + union CollisionState { + uint32_t state = 0; + struct { + bool floor; + bool wall; + bool ceiling; + }; + + CollisionState() { + } - bool floor_stop_on_slope = false; - int max_slides = 4; + CollisionState(bool p_floor, bool p_wall, bool p_ceiling) { + floor = p_floor; + wall = p_wall; + ceiling = p_ceiling; + } + }; + + CollisionState collision_state; + bool floor_constant_speed = false; + bool floor_stop_on_slope = true; + bool floor_block_on_wall = true; + bool slide_on_ceiling = true; + int max_slides = 6; + int platform_layer; + RID platform_rid; + uint32_t moving_platform_floor_layers = UINT32_MAX; + uint32_t moving_platform_wall_layers = 0; + real_t floor_snap_length = 0.1; real_t floor_max_angle = Math::deg2rad((real_t)45.0); - Vector3 snap; + real_t wall_min_slide_angle = Math::deg2rad((real_t)15.0); Vector3 up_direction = Vector3(0.0, 1.0, 0.0); - Vector3 linear_velocity; - Vector3 floor_normal; - Vector3 floor_velocity; - RID on_floor_body; - bool on_floor = false; - bool on_ceiling = false; - bool on_wall = false; + Vector3 wall_normal; + Vector3 last_motion; + Vector3 platform_velocity; + Vector3 previous_position; + Vector3 real_velocity; + Vector<PhysicsServer3D::MotionResult> motion_results; Vector<Ref<KinematicCollision3D>> slide_colliders; - Ref<KinematicCollision3D> _get_slide_collision(int p_bounce); - Ref<KinematicCollision3D> _get_last_slide_collision(); - - void _set_collision_direction(const PhysicsServer3D::MotionResult &p_result); - void set_safe_margin(real_t p_margin); real_t get_safe_margin() const; bool is_floor_stop_on_slope_enabled() const; void set_floor_stop_on_slope_enabled(bool p_enabled); + bool is_floor_constant_speed_enabled() const; + void set_floor_constant_speed_enabled(bool p_enabled); + + bool is_floor_block_on_wall_enabled() const; + void set_floor_block_on_wall_enabled(bool p_enabled); + + bool is_slide_on_ceiling_enabled() const; + void set_slide_on_ceiling_enabled(bool p_enabled); + int get_max_slides() const; void set_max_slides(int p_max_slides); real_t get_floor_max_angle() const; void set_floor_max_angle(real_t p_radians); - const Vector3 &get_snap() const; - void set_snap(const Vector3 &p_snap); + real_t get_floor_snap_length(); + void set_floor_snap_length(real_t p_floor_snap_length); - const Vector3 &get_up_direction() const; - void set_up_direction(const Vector3 &p_up_direction); + real_t get_wall_min_slide_angle() const; + void set_wall_min_slide_angle(real_t p_radians); -protected: - void _notification(int p_what); - static void _bind_methods(); + uint32_t get_moving_platform_floor_layers() const; + void set_moving_platform_floor_layers(const uint32_t p_exclude_layer); -public: - bool move_and_slide(); + uint32_t get_moving_platform_wall_layers() const; + void set_moving_platform_wall_layers(const uint32_t p_exclude_layer); - virtual Vector3 get_linear_velocity() const override; - void set_linear_velocity(const Vector3 &p_velocity); + void set_motion_mode(MotionMode p_mode); + MotionMode get_motion_mode() const; - bool is_on_floor() const; - bool is_on_floor_only() const; - bool is_on_wall() const; - bool is_on_wall_only() const; - bool is_on_ceiling() const; - bool is_on_ceiling_only() const; - Vector3 get_floor_normal() const; - real_t get_floor_angle(const Vector3 &p_up_direction = Vector3(0.0, 1.0, 0.0)) const; - Vector3 get_platform_velocity() const; + void set_moving_platform_apply_velocity_on_leave(MovingPlatformApplyVelocityOnLeave p_on_leave_velocity); + MovingPlatformApplyVelocityOnLeave get_moving_platform_apply_velocity_on_leave() const; - int get_slide_collision_count() const; - PhysicsServer3D::MotionResult get_slide_collision(int p_bounce) const; + void _move_and_slide_free(double p_delta); + void _move_and_slide_grounded(double p_delta, bool p_was_on_floor); - CharacterBody3D(); - ~CharacterBody3D(); + Ref<KinematicCollision3D> _get_slide_collision(int p_bounce); + Ref<KinematicCollision3D> _get_last_slide_collision(); + const Vector3 &get_up_direction() const; + bool _on_floor_if_snapped(bool was_on_floor, bool vel_dir_facing_up); + void set_up_direction(const Vector3 &p_up_direction); + void _set_collision_direction(const PhysicsServer3D::MotionResult &p_result, CollisionState &r_state, CollisionState p_apply_state = CollisionState(true, true, true)); + void _set_platform_data(const PhysicsServer3D::MotionCollision &p_collision); + void _snap_on_floor(bool was_on_floor, bool vel_dir_facing_up); + +protected: + void _notification(int p_what); + static void _bind_methods(); + virtual void _validate_property(PropertyInfo &property) const override; }; +VARIANT_ENUM_CAST(CharacterBody3D::MotionMode); +VARIANT_ENUM_CAST(CharacterBody3D::MovingPlatformApplyVelocityOnLeave); + class KinematicCollision3D : public RefCounted { GDCLASS(KinematicCollision3D, RefCounted); @@ -398,19 +470,31 @@ protected: static void _bind_methods(); public: - Vector3 get_position() const; - Vector3 get_normal() const; Vector3 get_travel() const; Vector3 get_remainder() const; - real_t get_angle(const Vector3 &p_up_direction = Vector3(0.0, 1.0, 0.0)) const; - Object *get_local_shape() const; - Object *get_collider() const; - ObjectID get_collider_id() const; - RID get_collider_rid() const; - Object *get_collider_shape() const; - int get_collider_shape_index() const; - Vector3 get_collider_velocity() const; - Variant get_collider_metadata() const; + int get_collision_count() const; + Vector3 get_position(int p_collision_index = 0) const; + Vector3 get_normal(int p_collision_index = 0) const; + real_t get_angle(int p_collision_index = 0, const Vector3 &p_up_direction = Vector3(0.0, 1.0, 0.0)) const; + Object *get_local_shape(int p_collision_index = 0) const; + Object *get_collider(int p_collision_index = 0) const; + ObjectID get_collider_id(int p_collision_index = 0) const; + RID get_collider_rid(int p_collision_index = 0) const; + Object *get_collider_shape(int p_collision_index = 0) const; + int get_collider_shape_index(int p_collision_index = 0) const; + Vector3 get_collider_velocity(int p_collision_index = 0) const; + Variant get_collider_metadata(int p_collision_index = 0) const; + + Vector3 get_best_position() const; + Vector3 get_best_normal() const; + Object *get_best_local_shape() const; + Object *get_best_collider() const; + ObjectID get_best_collider_id() const; + RID get_best_collider_rid() const; + Object *get_best_collider_shape() const; + int get_best_collider_shape_index() const; + Vector3 get_best_collider_velocity() const; + Variant get_best_collider_metadata() const; }; class PhysicalBone3D : public PhysicsBody3D { diff --git a/scene/3d/skeleton_3d.cpp b/scene/3d/skeleton_3d.cpp index 0f5de621ea..b7a79a2645 100644 --- a/scene/3d/skeleton_3d.cpp +++ b/scene/3d/skeleton_3d.cpp @@ -520,6 +520,7 @@ void Skeleton3D::set_bone_parent(int p_bone, int p_parent) { const int bone_size = bones.size(); ERR_FAIL_INDEX(p_bone, bone_size); ERR_FAIL_COND(p_parent != -1 && (p_parent < 0)); + ERR_FAIL_COND(p_bone == p_parent); bones.write[p_bone].parent = p_parent; process_order_dirty = true; diff --git a/scene/3d/voxelizer.cpp b/scene/3d/voxelizer.cpp index 2d32379d69..04f371f4b2 100644 --- a/scene/3d/voxelizer.cpp +++ b/scene/3d/voxelizer.cpp @@ -173,7 +173,7 @@ void Voxelizer::_plot_face(int p_idx, int p_level, int p_x, int p_y, int p_z, co //could not in any way get texture information.. so use closest point to center Face3 f(p_vtx[0], p_vtx[1], p_vtx[2]); - Vector3 inters = f.get_closest_point_to(p_aabb.position + p_aabb.size * 0.5); + Vector3 inters = f.get_closest_point_to(p_aabb.get_center()); Vector3 lnormal; Vector2 uv; @@ -434,7 +434,7 @@ void Voxelizer::plot_mesh(const Transform3D &p_xform, Ref<Mesh> &p_mesh, const V } //test against original bounds - if (!Geometry3D::triangle_box_overlap(original_bounds.position + original_bounds.size * 0.5, original_bounds.size * 0.5, vtxs)) { + if (!Geometry3D::triangle_box_overlap(original_bounds.get_center(), original_bounds.size * 0.5, vtxs)) { continue; } //plot @@ -466,7 +466,7 @@ void Voxelizer::plot_mesh(const Transform3D &p_xform, Ref<Mesh> &p_mesh, const V } //test against original bounds - if (!Geometry3D::triangle_box_overlap(original_bounds.position + original_bounds.size * 0.5, original_bounds.size * 0.5, vtxs)) { + if (!Geometry3D::triangle_box_overlap(original_bounds.get_center(), original_bounds.size * 0.5, vtxs)) { continue; } //plot face @@ -885,7 +885,7 @@ Vector<uint8_t> Voxelizer::get_sdf_3d_image() const { void Voxelizer::_debug_mesh(int p_idx, int p_level, const AABB &p_aabb, Ref<MultiMesh> &p_multimesh, int &idx) { if (p_level == cell_subdiv - 1) { - Vector3 center = p_aabb.position + p_aabb.size * 0.5; + Vector3 center = p_aabb.get_center(); Transform3D xform; xform.origin = center; xform.basis.scale(p_aabb.size * 0.5); diff --git a/scene/animation/SCsub b/scene/animation/SCsub index cc33a5af84..d0aa0bc8aa 100644 --- a/scene/animation/SCsub +++ b/scene/animation/SCsub @@ -6,11 +6,8 @@ Import("env") thirdparty_obj = [] -thirdparty_sources = "#thirdparty/misc/easing_equations.cpp" - env_thirdparty = env.Clone() env_thirdparty.disable_warnings() -env_thirdparty.add_source_files(thirdparty_obj, thirdparty_sources) env.scene_sources += thirdparty_obj # Godot source files diff --git a/scene/animation/animation_blend_tree.cpp b/scene/animation/animation_blend_tree.cpp index d11387902a..af186072ac 100644 --- a/scene/animation/animation_blend_tree.cpp +++ b/scene/animation/animation_blend_tree.cpp @@ -248,27 +248,26 @@ double AnimationNodeOneShot::process(double p_time, bool p_seek) { if (fade_in > 0) { blend = time / fade_in; } else { - blend = 0; //wtf + blend = 0; } - - } else if (!do_start && remaining < fade_out) { - if (fade_out) { + } else if (!do_start && remaining <= fade_out) { + if (fade_out > 0) { blend = (remaining / fade_out); } else { - blend = 1.0; + blend = 0; } } else { blend = 1.0; } - float main_rem; + double main_rem; if (mix == MIX_MODE_ADD) { main_rem = blend_input(0, p_time, p_seek, 1.0, FILTER_IGNORE, !sync); } else { main_rem = blend_input(0, p_time, p_seek, 1.0 - blend, FILTER_BLEND, !sync); } - float os_rem = blend_input(1, os_seek ? time : p_time, os_seek, blend, FILTER_PASS, false); + double os_rem = blend_input(1, os_seek ? time : p_time, os_seek, blend, FILTER_PASS, false); if (do_start) { remaining = os_rem; @@ -718,7 +717,7 @@ double AnimationNodeTransition::process(double p_time, bool p_seek) { } else { // cross-fading from prev to current - float blend = xfade ? (prev_xfading / xfade) : 1; + float blend = xfade == 0 ? 0 : (prev_xfading / xfade); if (!p_seek && switched) { //just switched, seek to start of current diff --git a/scene/animation/animation_tree.cpp b/scene/animation/animation_tree.cpp index 88fb960164..9ca8d478b1 100644 --- a/scene/animation/animation_tree.cpp +++ b/scene/animation/animation_tree.cpp @@ -958,7 +958,7 @@ void AnimationTree::_process_graph(real_t p_delta) { Variant::interpolate(t->value, value, blend, t->value); - } else if (delta != 0) { + } else { List<int> indices; a->value_track_get_key_indices(i, time, delta, &indices); diff --git a/scene/animation/easing_equations.h b/scene/animation/easing_equations.h new file mode 100644 index 0000000000..c38d083b7f --- /dev/null +++ b/scene/animation/easing_equations.h @@ -0,0 +1,405 @@ +/*************************************************************************/ +/* easing_equations.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* 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. */ +/*************************************************************************/ + +/* + * Derived from Robert Penner's easing equations: http://robertpenner.com/easing/ + * + * Copyright (c) 2001 Robert Penner + * + * 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 EASING_EQUATIONS_H +#define EASING_EQUATIONS_H + +namespace linear { +static real_t in(real_t t, real_t b, real_t c, real_t d) { + return c * t / d + b; +} +}; // namespace linear + +namespace sine { +static real_t in(real_t t, real_t b, real_t c, real_t d) { + return -c * cos(t / d * (Math_PI / 2)) + c + b; +} + +static real_t out(real_t t, real_t b, real_t c, real_t d) { + return c * sin(t / d * (Math_PI / 2)) + b; +} + +static real_t in_out(real_t t, real_t b, real_t c, real_t d) { + return -c / 2 * (cos(Math_PI * t / d) - 1) + b; +} + +static real_t out_in(real_t t, real_t b, real_t c, real_t d) { + if (t < d / 2) { + return out(t * 2, b, c / 2, d); + } + return in(t * 2 - d, b + c / 2, c / 2, d); +} +}; // namespace sine + +namespace quint { +static real_t in(real_t t, real_t b, real_t c, real_t d) { + return c * pow(t / d, 5) + b; +} + +static real_t out(real_t t, real_t b, real_t c, real_t d) { + return c * (pow(t / d - 1, 5) + 1) + b; +} + +static real_t in_out(real_t t, real_t b, real_t c, real_t d) { + t = t / d * 2; + + if (t < 1) { + return c / 2 * pow(t, 5) + b; + } + return c / 2 * (pow(t - 2, 5) + 2) + b; +} + +static real_t out_in(real_t t, real_t b, real_t c, real_t d) { + if (t < d / 2) { + return out(t * 2, b, c / 2, d); + } + return in(t * 2 - d, b + c / 2, c / 2, d); +} +}; // namespace quint + +namespace quart { +static real_t in(real_t t, real_t b, real_t c, real_t d) { + return c * pow(t / d, 4) + b; +} + +static real_t out(real_t t, real_t b, real_t c, real_t d) { + return -c * (pow(t / d - 1, 4) - 1) + b; +} + +static real_t in_out(real_t t, real_t b, real_t c, real_t d) { + t = t / d * 2; + + if (t < 1) { + return c / 2 * pow(t, 4) + b; + } + return -c / 2 * (pow(t - 2, 4) - 2) + b; +} + +static real_t out_in(real_t t, real_t b, real_t c, real_t d) { + if (t < d / 2) { + return out(t * 2, b, c / 2, d); + } + return in(t * 2 - d, b + c / 2, c / 2, d); +} +}; // namespace quart + +namespace quad { +static real_t in(real_t t, real_t b, real_t c, real_t d) { + return c * pow(t / d, 2) + b; +} + +static real_t out(real_t t, real_t b, real_t c, real_t d) { + t /= d; + return -c * t * (t - 2) + b; +} + +static real_t in_out(real_t t, real_t b, real_t c, real_t d) { + t = t / d * 2; + + if (t < 1) { + return c / 2 * pow(t, 2) + b; + } + return -c / 2 * ((t - 1) * (t - 3) - 1) + b; +} + +static real_t out_in(real_t t, real_t b, real_t c, real_t d) { + if (t < d / 2) { + return out(t * 2, b, c / 2, d); + } + return in(t * 2 - d, b + c / 2, c / 2, d); +} +}; // namespace quad + +namespace expo { +static real_t in(real_t t, real_t b, real_t c, real_t d) { + if (t == 0) { + return b; + } + return c * pow(2, 10 * (t / d - 1)) + b - c * 0.001; +} + +static real_t out(real_t t, real_t b, real_t c, real_t d) { + if (t == d) { + return b + c; + } + return c * 1.001 * (-pow(2, -10 * t / d) + 1) + b; +} + +static real_t in_out(real_t t, real_t b, real_t c, real_t d) { + if (t == 0) { + return b; + } + + if (t == d) { + return b + c; + } + + t = t / d * 2; + + if (t < 1) { + return c / 2 * pow(2, 10 * (t - 1)) + b - c * 0.0005; + } + return c / 2 * 1.0005 * (-pow(2, -10 * (t - 1)) + 2) + b; +} + +static real_t out_in(real_t t, real_t b, real_t c, real_t d) { + if (t < d / 2) { + return out(t * 2, b, c / 2, d); + } + return in(t * 2 - d, b + c / 2, c / 2, d); +} +}; // namespace expo + +namespace elastic { +static real_t in(real_t t, real_t b, real_t c, real_t d) { + if (t == 0) { + return b; + } + + t /= d; + if (t == 1) { + return b + c; + } + + t -= 1; + float p = d * 0.3f; + float a = c * pow(2, 10 * t); + float s = p / 4; + + return -(a * sin((t * d - s) * (2 * Math_PI) / p)) + b; +} + +static real_t out(real_t t, real_t b, real_t c, real_t d) { + if (t == 0) { + return b; + } + + t /= d; + if (t == 1) { + return b + c; + } + + float p = d * 0.3f; + float s = p / 4; + + return (c * pow(2, -10 * t) * sin((t * d - s) * (2 * Math_PI) / p) + c + b); +} + +static real_t in_out(real_t t, real_t b, real_t c, real_t d) { + if (t == 0) { + return b; + } + + if ((t /= d / 2) == 2) { + return b + c; + } + + float p = d * (0.3f * 1.5f); + float a = c; + float s = p / 4; + + if (t < 1) { + t -= 1; + a *= pow(2, 10 * t); + return -0.5f * (a * sin((t * d - s) * (2 * Math_PI) / p)) + b; + } + + t -= 1; + a *= pow(2, -10 * t); + return a * sin((t * d - s) * (2 * Math_PI) / p) * 0.5f + c + b; +} + +static real_t out_in(real_t t, real_t b, real_t c, real_t d) { + if (t < d / 2) { + return out(t * 2, b, c / 2, d); + } + return in(t * 2 - d, b + c / 2, c / 2, d); +} +}; // namespace elastic + +namespace cubic { +static real_t in(real_t t, real_t b, real_t c, real_t d) { + t /= d; + return c * t * t * t + b; +} + +static real_t out(real_t t, real_t b, real_t c, real_t d) { + t = t / d - 1; + return c * (t * t * t + 1) + b; +} + +static real_t in_out(real_t t, real_t b, real_t c, real_t d) { + t /= d / 2; + if (t < 1) { + return c / 2 * t * t * t + b; + } + + t -= 2; + return c / 2 * (t * t * t + 2) + b; +} + +static real_t out_in(real_t t, real_t b, real_t c, real_t d) { + if (t < d / 2) { + return out(t * 2, b, c / 2, d); + } + return in(t * 2 - d, b + c / 2, c / 2, d); +} +}; // namespace cubic + +namespace circ { +static real_t in(real_t t, real_t b, real_t c, real_t d) { + t /= d; + return -c * (sqrt(1 - t * t) - 1) + b; +} + +static real_t out(real_t t, real_t b, real_t c, real_t d) { + t = t / d - 1; + return c * sqrt(1 - t * t) + b; +} + +static real_t in_out(real_t t, real_t b, real_t c, real_t d) { + t /= d / 2; + if (t < 1) { + return -c / 2 * (sqrt(1 - t * t) - 1) + b; + } + + t -= 2; + return c / 2 * (sqrt(1 - t * t) + 1) + b; +} + +static real_t out_in(real_t t, real_t b, real_t c, real_t d) { + if (t < d / 2) { + return out(t * 2, b, c / 2, d); + } + return in(t * 2 - d, b + c / 2, c / 2, d); +} +}; // namespace circ + +namespace bounce { +static real_t out(real_t t, real_t b, real_t c, real_t d) { + t /= d; + + if (t < (1 / 2.75f)) { + return c * (7.5625f * t * t) + b; + } + + if (t < (2 / 2.75f)) { + t -= 1.5f / 2.75f; + return c * (7.5625f * t * t + 0.75f) + b; + } + + if (t < (2.5 / 2.75)) { + t -= 2.25f / 2.75f; + return c * (7.5625f * t * t + 0.9375f) + b; + } + + t -= 2.625f / 2.75f; + return c * (7.5625f * t * t + 0.984375f) + b; +} + +static real_t in(real_t t, real_t b, real_t c, real_t d) { + return c - out(d - t, 0, c, d) + b; +} + +static real_t in_out(real_t t, real_t b, real_t c, real_t d) { + if (t < d / 2) { + return in(t * 2, b, c / 2, d); + } + return out(t * 2 - d, b + c / 2, c / 2, d); +} + +static real_t out_in(real_t t, real_t b, real_t c, real_t d) { + if (t < d / 2) { + return out(t * 2, b, c / 2, d); + } + return in(t * 2 - d, b + c / 2, c / 2, d); +} +}; // namespace bounce + +namespace back { +static real_t in(real_t t, real_t b, real_t c, real_t d) { + float s = 1.70158f; + t /= d; + + return c * t * t * ((s + 1) * t - s) + b; +} + +static real_t out(real_t t, real_t b, real_t c, real_t d) { + float s = 1.70158f; + t = t / d - 1; + + return c * (t * t * ((s + 1) * t + s) + 1) + b; +} + +static real_t in_out(real_t t, real_t b, real_t c, real_t d) { + float s = 1.70158f * 1.525f; + t /= d / 2; + + if (t < 1) { + return c / 2 * (t * t * ((s + 1) * t - s)) + b; + } + + t -= 2; + return c / 2 * (t * t * ((s + 1) * t + s) + 2) + b; +} + +static real_t out_in(real_t t, real_t b, real_t c, real_t d) { + if (t < d / 2) { + return out(t * 2, b, c / 2, d); + } + return in(t * 2 - d, b + c / 2, c / 2, d); +} +}; // namespace back + +#endif diff --git a/scene/animation/tween.cpp b/scene/animation/tween.cpp index 2847031375..c43b83747b 100644 --- a/scene/animation/tween.cpp +++ b/scene/animation/tween.cpp @@ -30,8 +30,23 @@ #include "tween.h" +#include "scene/animation/easing_equations.h" #include "scene/main/node.h" +Tween::interpolater Tween::interpolaters[Tween::TRANS_MAX][Tween::EASE_MAX] = { + { &linear::in, &linear::in, &linear::in, &linear::in }, // Linear is the same for each easing. + { &sine::in, &sine::out, &sine::in_out, &sine::out_in }, + { &quint::in, &quint::out, &quint::in_out, &quint::out_in }, + { &quart::in, &quart::out, &quart::in_out, &quart::out_in }, + { &quad::in, &quad::out, &quad::in_out, &quad::out_in }, + { &expo::in, &expo::out, &expo::in_out, &expo::out_in }, + { &elastic::in, &elastic::out, &elastic::in_out, &elastic::out_in }, + { &cubic::in, &cubic::out, &cubic::in_out, &cubic::out_in }, + { &circ::in, &circ::out, &circ::in_out, &circ::out_in }, + { &bounce::in, &bounce::out, &bounce::in_out, &bounce::out_in }, + { &back::in, &back::out, &back::in_out, &back::out_in }, +}; + void Tweener::set_tween(Ref<Tween> p_tween) { tween = p_tween; } @@ -317,6 +332,16 @@ bool Tween::should_pause() { return pause_mode != TWEEN_PAUSE_PROCESS; } +real_t Tween::run_equation(TransitionType p_trans_type, EaseType p_ease_type, real_t p_time, real_t p_initial, real_t p_delta, real_t p_duration) { + if (p_duration == 0) { + // Special case to avoid dividing by 0 in equations. + return p_initial + p_delta; + } + + interpolater func = interpolaters[p_trans_type][p_ease_type]; + return func(p_time, p_initial, p_delta, p_duration); +} + Variant Tween::interpolate_variant(Variant p_initial_val, Variant p_delta_val, float p_time, float p_duration, TransitionType p_trans, EaseType p_ease) { ERR_FAIL_INDEX_V(p_trans, TransitionType::TRANS_MAX, Variant()); ERR_FAIL_INDEX_V(p_ease, EaseType::EASE_MAX, Variant()); diff --git a/scene/gui/code_edit.cpp b/scene/gui/code_edit.cpp index e7769f9372..c7d1d7ef82 100644 --- a/scene/gui/code_edit.cpp +++ b/scene/gui/code_edit.cpp @@ -227,17 +227,17 @@ void CodeEdit::_notification(int p_what) { end = font->get_string_size(line.substr(0, line.rfind(String::chr(0xFFFF))), font_size).x; } - Point2 round_ofs = hint_ofs + sb->get_offset() + Vector2(0, font->get_ascent() + font_height * i + yofs); + Point2 round_ofs = hint_ofs + sb->get_offset() + Vector2(0, font->get_ascent(font_size) + font_height * i + yofs); round_ofs = round_ofs.round(); draw_string(font, round_ofs, line.replace(String::chr(0xFFFF), ""), HALIGN_LEFT, -1, font_size, font_color); if (end > 0) { // Draw an underline for the currently edited function parameter. - const Vector2 b = hint_ofs + sb->get_offset() + Vector2(begin, font_height + font_height * i + line_spacing); + const Vector2 b = hint_ofs + sb->get_offset() + Vector2(begin, font_height + font_height * i + yofs); draw_line(b, b + Vector2(end - begin, 0), font_color, 2); // Draw a translucent text highlight as well. const Rect2 highlight_rect = Rect2( - hint_ofs + sb->get_offset() + Vector2(begin, 0), + b - Vector2(0, font_height), Vector2(end - begin, font_height)); draw_rect(highlight_rect, font_color * Color(1, 1, 1, 0.2)); } @@ -296,7 +296,7 @@ void CodeEdit::gui_input(const Ref<InputEvent> &p_gui_input) { mpos.x = get_size().x - mpos.x; } - Point2i pos = get_line_column_at_pos(Point2i(mpos.x, mpos.y)); + Point2i pos = get_line_column_at_pos(mpos); int line = pos.y; int col = pos.x; @@ -321,7 +321,7 @@ void CodeEdit::gui_input(const Ref<InputEvent> &p_gui_input) { mpos.x = get_size().x - mpos.x; } - Point2i pos = get_line_column_at_pos(Point2i(mpos.x, mpos.y)); + Point2i pos = get_line_column_at_pos(mpos); int line = pos.y; int col = pos.x; @@ -467,7 +467,7 @@ void CodeEdit::gui_input(const Ref<InputEvent> &p_gui_input) { } /* MISC */ - if (k->is_action("ui_cancel", true)) { + if (!code_hint.is_empty() && k->is_action("ui_cancel", true)) { set_code_hint(""); accept_event(); return; @@ -1725,14 +1725,17 @@ bool CodeEdit::is_code_completion_enabled() const { void CodeEdit::set_code_completion_prefixes(const TypedArray<String> &p_prefixes) { code_completion_prefixes.clear(); for (int i = 0; i < p_prefixes.size(); i++) { - code_completion_prefixes.insert(p_prefixes[i]); + const String prefix = p_prefixes[i]; + + ERR_CONTINUE_MSG(prefix.is_empty(), "Code completion prefix cannot be empty."); + code_completion_prefixes.insert(prefix[0]); } } TypedArray<String> CodeEdit::get_code_completion_prefixes() const { TypedArray<String> prefixes; - for (Set<String>::Element *E = code_completion_prefixes.front(); E; E = E->next()) { - prefixes.push_back(E->get()); + for (const Set<char32_t>::Element *E = code_completion_prefixes.front(); E; E = E->next()) { + prefixes.push_back(String::chr(E->get())); } return prefixes; } @@ -1795,9 +1798,9 @@ void CodeEdit::request_code_completion(bool p_force) { String line = get_line(get_caret_line()); int ofs = CLAMP(get_caret_column(), 0, line.length()); - if (ofs > 0 && (is_in_string(get_caret_line(), ofs) != -1 || _is_char(line[ofs - 1]) || code_completion_prefixes.has(String::chr(line[ofs - 1])))) { + if (ofs > 0 && (is_in_string(get_caret_line(), ofs) != -1 || _is_char(line[ofs - 1]) || code_completion_prefixes.has(line[ofs - 1]))) { emit_signal(SNAME("request_code_completion")); - } else if (ofs > 1 && line[ofs - 1] == ' ' && code_completion_prefixes.has(String::chr(line[ofs - 2]))) { + } else if (ofs > 1 && line[ofs - 1] == ' ' && code_completion_prefixes.has(line[ofs - 2])) { emit_signal(SNAME("request_code_completion")); } } @@ -1969,7 +1972,7 @@ void CodeEdit::confirm_code_completion(bool p_replace) { end_complex_operation(); cancel_code_completion(); - if (code_completion_prefixes.has(String::chr(last_completion_char))) { + if (code_completion_prefixes.has(last_completion_char)) { request_code_completion(); } } @@ -2764,7 +2767,7 @@ void CodeEdit::_filter_code_completion_candidates_impl() { bool prev_is_word = false; /* Cancel if we are at the close of a string. */ - if (in_string == -1 && first_quote_col == cofs - 1) { + if (caret_column > 0 && in_string == -1 && first_quote_col == cofs - 1) { cancel_code_completion(); return; /* In a string, therefore we are trying to complete the string text. */ @@ -2790,9 +2793,9 @@ void CodeEdit::_filter_code_completion_candidates_impl() { /* If all else fails, check for a prefix. */ /* Single space between caret and prefix is okay. */ bool prev_is_prefix = false; - if (cofs > 0 && code_completion_prefixes.has(String::chr(line[cofs - 1]))) { + if (cofs > 0 && code_completion_prefixes.has(line[cofs - 1])) { prev_is_prefix = true; - } else if (cofs > 1 && line[cofs - 1] == ' ' && code_completion_prefixes.has(String::chr(line[cofs - 2]))) { + } else if (cofs > 1 && line[cofs - 1] == ' ' && code_completion_prefixes.has(line[cofs - 2])) { prev_is_prefix = true; } diff --git a/scene/gui/code_edit.h b/scene/gui/code_edit.h index 740548d559..d8eccb7d32 100644 --- a/scene/gui/code_edit.h +++ b/scene/gui/code_edit.h @@ -214,7 +214,7 @@ private: int code_completion_longest_line = 0; Rect2i code_completion_rect; - Set<String> code_completion_prefixes; + Set<char32_t> code_completion_prefixes; List<ScriptCodeCompletionOption> code_completion_option_submitted; List<ScriptCodeCompletionOption> code_completion_option_sources; String code_completion_base; diff --git a/scene/gui/color_picker.cpp b/scene/gui/color_picker.cpp index 1afb0b8e9d..611035fff9 100644 --- a/scene/gui/color_picker.cpp +++ b/scene/gui/color_picker.cpp @@ -827,7 +827,7 @@ void ColorPicker::_uv_input(const Ref<InputEvent> &p_event, Control *c) { real_t dist = center.distance_to(bev->get_position()); if (dist <= center.x) { - real_t rad = Math::atan2(bev->get_position().y - center.y, bev->get_position().x - center.x); + real_t rad = bev->get_position().angle_to_point(center); h = ((rad >= 0) ? rad : (Math_TAU + rad)) / Math_TAU; s = CLAMP(dist / center.x, 0, 1); } else { @@ -844,7 +844,7 @@ void ColorPicker::_uv_input(const Ref<InputEvent> &p_event, Control *c) { real_t dist = center.distance_to(bev->get_position()); if (dist >= center.x * 0.84 && dist <= center.x) { - real_t rad = Math::atan2(bev->get_position().y - center.y, bev->get_position().x - center.x); + real_t rad = bev->get_position().angle_to_point(center); h = ((rad >= 0) ? rad : (Math_TAU + rad)) / Math_TAU; spinning = true; } else { @@ -889,12 +889,12 @@ void ColorPicker::_uv_input(const Ref<InputEvent> &p_event, Control *c) { Vector2 center = c->get_size() / 2.0; if (picker_type == SHAPE_VHS_CIRCLE) { real_t dist = center.distance_to(mev->get_position()); - real_t rad = Math::atan2(mev->get_position().y - center.y, mev->get_position().x - center.x); + real_t rad = mev->get_position().angle_to_point(center); h = ((rad >= 0) ? rad : (Math_TAU + rad)) / Math_TAU; s = CLAMP(dist / center.x, 0, 1); } else { if (spinning) { - real_t rad = Math::atan2(mev->get_position().y - center.y, mev->get_position().x - center.x); + real_t rad = mev->get_position().angle_to_point(center); h = ((rad >= 0) ? rad : (Math_TAU + rad)) / Math_TAU; } else { real_t corner_x = (c == wheel_uv) ? center.x - Math_SQRT12 * c->get_size().width * 0.42 : 0; @@ -996,7 +996,7 @@ void ColorPicker::_screen_input(const Ref<InputEvent> &p_event) { Ref<InputEventMouseMotion> mev = p_event; if (mev.is_valid()) { Viewport *r = get_tree()->get_root(); - if (!r->get_visible_rect().has_point(Point2(mev->get_global_position().x, mev->get_global_position().y))) { + if (!r->get_visible_rect().has_point(mev->get_global_position())) { return; } @@ -1308,6 +1308,8 @@ void ColorPickerButton::_modal_closed() { void ColorPickerButton::pressed() { _update_picker(); + Size2 size = get_size() * get_viewport()->get_canvas_transform().get_scale(); + popup->set_as_minsize(); picker->_update_presets(); @@ -1319,13 +1321,13 @@ void ColorPickerButton::pressed() { if (i > 1) { cp_rect.position.y = get_screen_position().y - cp_rect.size.y; } else { - cp_rect.position.y = get_screen_position().y + get_size().height; + cp_rect.position.y = get_screen_position().y + size.height; } if (i & 1) { cp_rect.position.x = get_screen_position().x; } else { - cp_rect.position.x = get_screen_position().x - MAX(0, (cp_rect.size.x - get_size().x)); + cp_rect.position.x = get_screen_position().x - MAX(0, (cp_rect.size.x - size.x)); } if (usable_rect.encloses(cp_rect)) { diff --git a/scene/gui/container.cpp b/scene/gui/container.cpp index c97434f69b..11941529cd 100644 --- a/scene/gui/container.cpp +++ b/scene/gui/container.cpp @@ -96,17 +96,18 @@ void Container::fit_child_in_rect(Control *p_child, const Rect2 &p_rect) { ERR_FAIL_COND(!p_child); ERR_FAIL_COND(p_child->get_parent() != this); + bool rtl = is_layout_rtl(); Size2 minsize = p_child->get_combined_minimum_size(); Rect2 r = p_rect; if (!(p_child->get_h_size_flags() & SIZE_FILL)) { r.size.x = minsize.width; if (p_child->get_h_size_flags() & SIZE_SHRINK_END) { - r.position.x += p_rect.size.width - minsize.width; + r.position.x += rtl ? 0 : (p_rect.size.width - minsize.width); } else if (p_child->get_h_size_flags() & SIZE_SHRINK_CENTER) { r.position.x += Math::floor((p_rect.size.x - minsize.width) / 2); } else { - r.position.x += 0; + r.position.x += rtl ? (p_rect.size.width - minsize.width) : 0; } } diff --git a/scene/gui/graph_edit.cpp b/scene/gui/graph_edit.cpp index b9b02b1427..bca3b2059d 100644 --- a/scene/gui/graph_edit.cpp +++ b/scene/gui/graph_edit.cpp @@ -697,7 +697,7 @@ void GraphEdit::_top_layer_input(const Ref<InputEvent> &p_ev) { } else if (!just_disconnected) { String from = connecting_from; int from_slot = connecting_index; - Vector2 ofs = Vector2(mb->get_position().x, mb->get_position().y); + Vector2 ofs = mb->get_position(); if (!connecting_out) { emit_signal(SNAME("connection_from_empty"), from, from_slot, ofs); @@ -2103,7 +2103,7 @@ void GraphEdit::arrange_nodes() { largest_node_size = 0.0f; } - emit_signal("begin_node_move"); + emit_signal(SNAME("begin_node_move")); for (const Set<StringName>::Element *E = selected_nodes.front(); E; E = E->next()) { GraphNode *gn = Object::cast_to<GraphNode>(node_names[E->get()]); gn->set_drag(true); @@ -2116,7 +2116,7 @@ void GraphEdit::arrange_nodes() { gn->set_position_offset(pos); gn->set_drag(false); } - emit_signal("end_node_move"); + emit_signal(SNAME("end_node_move")); arranging_graph = false; } diff --git a/scene/gui/graph_node.cpp b/scene/gui/graph_node.cpp index 08c8c60d7a..ecf735d7f5 100644 --- a/scene/gui/graph_node.cpp +++ b/scene/gui/graph_node.cpp @@ -31,6 +31,9 @@ #include "graph_node.h" #include "core/string/translation.h" +#ifdef TOOLS_ENABLED +#include "graph_edit.h" +#endif struct _MinSizeCache { int min_size; @@ -458,6 +461,27 @@ void GraphNode::_shape() { title_buf->add_string(title, font, font_size, opentype_features, (language != "") ? language : TranslationServer::get_singleton()->get_tool_locale()); } +#ifdef TOOLS_ENABLED +void GraphNode::_edit_set_position(const Point2 &p_position) { + GraphEdit *graph = Object::cast_to<GraphEdit>(get_parent()); + if (graph) { + Point2 offset = (p_position + graph->get_scroll_ofs()) * graph->get_zoom(); + set_position_offset(offset); + } + set_position(p_position); +} + +void GraphNode::_validate_property(PropertyInfo &property) const { + Control::_validate_property(property); + GraphEdit *graph = Object::cast_to<GraphEdit>(get_parent()); + if (graph) { + if (property.name == "rect_position") { + property.usage |= PROPERTY_USAGE_READ_ONLY; + } + } +} +#endif + void GraphNode::set_slot(int p_idx, bool p_enable_left, int p_type_left, const Color &p_color_left, bool p_enable_right, int p_type_right, const Color &p_color_right, const Ref<Texture2D> &p_custom_left, const Ref<Texture2D> &p_custom_right) { ERR_FAIL_COND_MSG(p_idx < 0, vformat("Cannot set slot with p_idx (%d) lesser than zero.", p_idx)); @@ -871,7 +895,7 @@ void GraphNode::gui_input(const Ref<InputEvent> &p_ev) { ERR_FAIL_COND_MSG(get_parent_control() == nullptr, "GraphNode must be the child of a GraphEdit node."); if (mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_LEFT) { - Vector2 mpos = Vector2(mb->get_position().x, mb->get_position().y); + Vector2 mpos = mb->get_position(); if (close_rect.size != Size2() && close_rect.has_point(mpos)) { //send focus to parent get_parent_control()->grab_focus(); diff --git a/scene/gui/graph_node.h b/scene/gui/graph_node.h index c7c7006bfc..2238cfdb56 100644 --- a/scene/gui/graph_node.h +++ b/scene/gui/graph_node.h @@ -98,6 +98,11 @@ private: Overlay overlay = OVERLAY_DISABLED; +#ifdef TOOLS_ENABLED + void _edit_set_position(const Point2 &p_position) override; + void _validate_property(PropertyInfo &property) const override; +#endif + protected: virtual void gui_input(const Ref<InputEvent> &p_ev) override; void _notification(int p_what); diff --git a/scene/gui/label.cpp b/scene/gui/label.cpp index 5600816b2d..18cde25e55 100644 --- a/scene/gui/label.cpp +++ b/scene/gui/label.cpp @@ -92,8 +92,12 @@ void Label::_shape() { const Ref<Font> &font = get_theme_font(SNAME("font")); int font_size = get_theme_font_size(SNAME("font_size")); ERR_FAIL_COND(font.is_null()); - TS->shaped_text_add_string(text_rid, (uppercase) ? xl_text.to_upper() : xl_text, font->get_rids(), font_size, opentype_features, (language != "") ? language : TranslationServer::get_singleton()->get_tool_locale()); - TS->shaped_text_set_bidi_override(text_rid, structured_text_parser(st_parser, st_args, xl_text)); + String text = (uppercase) ? xl_text.to_upper() : xl_text; + if (visible_chars >= 0) { + text = text.substr(0, visible_chars); + } + TS->shaped_text_add_string(text_rid, text, font->get_rids(), font_size, opentype_features, (language != "") ? language : TranslationServer::get_singleton()->get_tool_locale()); + TS->shaped_text_set_bidi_override(text_rid, structured_text_parser(st_parser, st_args, text)); dirty = false; lines_dirty = true; } @@ -258,11 +262,18 @@ void Label::_notification(int p_what) { return; // Nothing new. } xl_text = new_text; + if (percent_visible < 1) { + visible_chars = get_total_character_count() * percent_visible; + } dirty = true; update(); } + if (p_what == NOTIFICATION_LAYOUT_DIRECTION_CHANGED) { + update(); + } + if (p_what == NOTIFICATION_DRAW) { if (clip) { RenderingServer::get_singleton()->canvas_item_set_clip(get_canvas_item(), true); @@ -286,6 +297,7 @@ void Label::_notification(int p_what) { int outline_size = get_theme_constant(SNAME("outline_size")); int shadow_outline_size = get_theme_constant(SNAME("shadow_outline_size")); bool rtl = TS->shaped_text_get_direction(text_rid); + bool rtl_layout = is_layout_rtl(); style->draw(ci, Rect2(Point2(0, 0), get_size())); @@ -342,24 +354,6 @@ void Label::_notification(int p_what) { } } - int visible_glyphs = -1; - int glyhps_drawn = 0; - if (percent_visible < 1) { - int total_glyphs = 0; - for (int i = lines_skipped; i < last_line; i++) { - const Vector<TextServer::Glyph> visual = TS->shaped_text_get_glyphs(lines_rid[i]); - const TextServer::Glyph *glyphs = visual.ptr(); - int gl_size = visual.size(); - for (int j = 0; j < gl_size; j++) { - if ((glyphs[j].flags & TextServer::GRAPHEME_IS_VIRTUAL) != TextServer::GRAPHEME_IS_VIRTUAL) { - total_glyphs++; - } - } - } - - visible_glyphs = MIN(total_glyphs, visible_chars); - } - Vector2 ofs; ofs.y = style->get_offset().y + vbegin; for (int i = lines_skipped; i < last_line; i++) { @@ -375,13 +369,21 @@ void Label::_notification(int p_what) { } break; case ALIGN_LEFT: { - ofs.x = style->get_offset().x; + if (rtl_layout) { + ofs.x = int(size.width - style->get_margin(SIDE_RIGHT) - line_size.width); + } else { + ofs.x = style->get_offset().x; + } } break; case ALIGN_CENTER: { ofs.x = int(size.width - line_size.width) / 2; } break; case ALIGN_RIGHT: { - ofs.x = int(size.width - style->get_margin(SIDE_RIGHT) - line_size.width); + if (rtl_layout) { + ofs.x = style->get_offset().x; + } else { + ofs.x = int(size.width - style->get_margin(SIDE_RIGHT) - line_size.width); + } } break; } @@ -407,14 +409,6 @@ void Label::_notification(int p_what) { // Draw main text. for (int j = 0; j < gl_size; j++) { for (int k = 0; k < glyphs[j].repeat; k++) { - if (visible_glyphs != -1) { - if ((glyphs[j].flags & TextServer::GRAPHEME_IS_VIRTUAL) != TextServer::GRAPHEME_IS_VIRTUAL) { - if (glyhps_drawn >= visible_glyphs) { - return; - } - } - } - // Trim when necessary. if (trim_data.trim_pos >= 0) { if (rtl) { @@ -431,7 +425,6 @@ void Label::_notification(int p_what) { // Draw glyph outlines and shadow. draw_glyph_outline(glyphs[j], ci, font_color, font_shadow_color, font_outline_color, shadow_outline_size, outline_size, offset, shadow_ofs); offset.x += glyphs[j].advance; - glyhps_drawn++; } } // Draw LTR ellipsis string when necessary. @@ -462,14 +455,6 @@ void Label::_notification(int p_what) { // Draw main text. for (int j = 0; j < gl_size; j++) { for (int k = 0; k < glyphs[j].repeat; k++) { - if (visible_glyphs != -1) { - if ((glyphs[j].flags & TextServer::GRAPHEME_IS_VIRTUAL) != TextServer::GRAPHEME_IS_VIRTUAL) { - if (glyhps_drawn >= visible_glyphs) { - return; - } - } - } - // Trim when necessary. if (trim_data.trim_pos >= 0) { if (rtl) { @@ -486,7 +471,6 @@ void Label::_notification(int p_what) { // Draw glyph outlines and shadow. draw_glyph(glyphs[j], ci, font_color, ofs); ofs.x += glyphs[j].advance; - glyhps_drawn++; } } // Draw LTR ellipsis string when necessary. @@ -709,16 +693,16 @@ String Label::get_text() const { } void Label::set_visible_characters(int p_amount) { - visible_chars = p_amount; - if (get_total_character_count() > 0) { - percent_visible = (float)p_amount / (float)get_total_character_count(); - } else { - percent_visible = 1.0; - } - if (p_amount == -1) { - lines_dirty = true; + if (visible_chars != p_amount) { + visible_chars = p_amount; + if (get_total_character_count() > 0) { + percent_visible = (float)p_amount / (float)get_total_character_count(); + } else { + percent_visible = 1.0; + } + dirty = true; + update(); } - update(); } int Label::get_visible_characters() const { @@ -726,15 +710,17 @@ int Label::get_visible_characters() const { } void Label::set_percent_visible(float p_percent) { - if (p_percent < 0 || p_percent >= 1) { - visible_chars = -1; - percent_visible = 1; - lines_dirty = true; - } else { - visible_chars = get_total_character_count() * p_percent; - percent_visible = p_percent; + if (percent_visible != p_percent) { + if (p_percent < 0 || p_percent >= 1) { + visible_chars = -1; + percent_visible = 1; + } else { + visible_chars = get_total_character_count() * p_percent; + percent_visible = p_percent; + } + dirty = true; + update(); } - update(); } float Label::get_percent_visible() const { @@ -889,7 +875,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::BOOL, "uppercase"), "set_uppercase", "is_uppercase"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "visible_characters", PROPERTY_HINT_RANGE, "-1,128000,1", PROPERTY_USAGE_EDITOR), "set_visible_characters", "get_visible_characters"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "visible_characters", PROPERTY_HINT_RANGE, "-1,128000,1"), "set_visible_characters", "get_visible_characters"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "percent_visible", PROPERTY_HINT_RANGE, "0,1,0.001"), "set_percent_visible", "get_percent_visible"); ADD_PROPERTY(PropertyInfo(Variant::INT, "lines_skipped", PROPERTY_HINT_RANGE, "0,999,1"), "set_lines_skipped", "get_lines_skipped"); ADD_PROPERTY(PropertyInfo(Variant::INT, "max_lines_visible", PROPERTY_HINT_RANGE, "-1,999,1"), "set_max_lines_visible", "get_max_lines_visible"); diff --git a/scene/gui/line_edit.cpp b/scene/gui/line_edit.cpp index d9acbeb828..09128770d5 100644 --- a/scene/gui/line_edit.cpp +++ b/scene/gui/line_edit.cpp @@ -260,24 +260,29 @@ void LineEdit::gui_input(const Ref<InputEvent> &p_event) { } else { if (selecting_enabled) { - if (!b->is_double_click() && (OS::get_singleton()->get_ticks_msec() - selection.last_dblclk) < 600) { + const int triple_click_timeout = 600; + const int triple_click_tolerance = 5; + const bool is_triple_click = !b->is_double_click() && (OS::get_singleton()->get_ticks_msec() - last_dblclk) < triple_click_timeout && b->get_position().distance_to(last_dblclk_pos) < triple_click_tolerance; + + if (is_triple_click && text.length()) { // Triple-click select all. selection.enabled = true; selection.begin = 0; selection.end = text.length(); selection.double_click = true; - selection.last_dblclk = 0; + last_dblclk = 0; caret_column = selection.begin; } else if (b->is_double_click()) { // Double-click select word. + last_dblclk = OS::get_singleton()->get_ticks_msec(); + last_dblclk_pos = b->get_position(); Vector<Vector2i> words = TS->shaped_text_get_word_breaks(text_rid); for (int i = 0; i < words.size(); i++) { - if (words[i].x < caret_column && words[i].y > caret_column) { + if ((words[i].x < caret_column && words[i].y > caret_column) || (i == words.size() - 1 && caret_column == words[i].y)) { selection.enabled = true; selection.begin = words[i].x; selection.end = words[i].y; selection.double_click = true; - selection.last_dblclk = OS::get_singleton()->get_ticks_msec(); caret_column = selection.end; break; } diff --git a/scene/gui/line_edit.h b/scene/gui/line_edit.h index e364a79c83..9f28c84f50 100644 --- a/scene/gui/line_edit.h +++ b/scene/gui/line_edit.h @@ -136,7 +136,6 @@ private: bool creating = false; bool double_click = false; bool drag_attempt = false; - uint64_t last_dblclk = 0; } selection; struct TextOperation { @@ -153,6 +152,9 @@ private: bool pressing_inside = false; } clear_button_status; + uint64_t last_dblclk = 0; + Vector2 last_dblclk_pos; + bool caret_blink_enabled = false; bool caret_force_displayed = false; bool draw_caret = true; diff --git a/scene/gui/menu_button.cpp b/scene/gui/menu_button.cpp index 737ba84617..ceb2092e3a 100644 --- a/scene/gui/menu_button.cpp +++ b/scene/gui/menu_button.cpp @@ -87,15 +87,17 @@ void MenuButton::_popup_visibility_changed(bool p_visible) { void MenuButton::pressed() { emit_signal(SNAME("about_to_popup")); - Size2 size = get_size(); + Size2 size = get_size() * get_viewport()->get_canvas_transform().get_scale(); + popup->set_size(Size2(size.width, 0)); Point2 gp = get_screen_position(); - gp.y += get_size().y; - + gp.y += size.y; + if (is_layout_rtl()) { + gp.x += size.width - popup->get_size().width; + } popup->set_position(gp); + popup->set_parent_rect(Rect2(Point2(gp - popup->get_position()), size)); - popup->set_size(Size2(size.width, 0)); - popup->set_parent_rect(Rect2(Point2(gp - popup->get_position()), get_size())); popup->take_mouse_focus(); popup->popup(); } diff --git a/scene/gui/option_button.cpp b/scene/gui/option_button.cpp index d16e96dbec..2adeb2d947 100644 --- a/scene/gui/option_button.cpp +++ b/scene/gui/option_button.cpp @@ -115,7 +115,7 @@ void OptionButton::_selected(int p_which) { } void OptionButton::pressed() { - Size2 size = get_size(); + Size2 size = get_size() * get_viewport()->get_canvas_transform().get_scale(); popup->set_position(get_screen_position() + Size2(0, size.height * get_global_transform().get_scale().y)); popup->set_size(Size2(size.width, 0)); popup->popup(); diff --git a/scene/gui/popup.cpp b/scene/gui/popup.cpp index e9414598a2..be05fd5a60 100644 --- a/scene/gui/popup.cpp +++ b/scene/gui/popup.cpp @@ -194,7 +194,7 @@ Popup::~Popup() { } Size2 PopupPanel::_get_contents_minimum_size() const { - Ref<StyleBox> p = get_theme_stylebox("panel", get_class_name()); + Ref<StyleBox> p = get_theme_stylebox(SNAME("panel"), get_class_name()); Size2 ms; @@ -217,7 +217,7 @@ Size2 PopupPanel::_get_contents_minimum_size() const { } void PopupPanel::_update_child_rects() { - Ref<StyleBox> p = get_theme_stylebox("panel", get_class_name()); + Ref<StyleBox> p = get_theme_stylebox(SNAME("panel"), get_class_name()); Vector2 cpos(p->get_offset()); Vector2 csize(get_size() - p->get_minimum_size()); @@ -244,9 +244,9 @@ void PopupPanel::_update_child_rects() { void PopupPanel::_notification(int p_what) { if (p_what == NOTIFICATION_THEME_CHANGED) { - panel->add_theme_style_override("panel", get_theme_stylebox("panel", get_class_name())); + panel->add_theme_style_override("panel", get_theme_stylebox(SNAME("panel"), get_class_name())); } else if (p_what == NOTIFICATION_READY || p_what == NOTIFICATION_ENTER_TREE) { - panel->add_theme_style_override("panel", get_theme_stylebox("panel", get_class_name())); + panel->add_theme_style_override("panel", get_theme_stylebox(SNAME("panel"), get_class_name())); _update_child_rects(); } else if (p_what == NOTIFICATION_WM_SIZE_CHANGED) { _update_child_rects(); diff --git a/scene/gui/rich_text_effect.cpp b/scene/gui/rich_text_effect.cpp index 236d106af8..076fa132c0 100644 --- a/scene/gui/rich_text_effect.cpp +++ b/scene/gui/rich_text_effect.cpp @@ -90,6 +90,12 @@ void CharFXTransform::_bind_methods() { ClassDB::bind_method(D_METHOD("get_glyph_index"), &CharFXTransform::get_glyph_index); ClassDB::bind_method(D_METHOD("set_glyph_index", "glyph_index"), &CharFXTransform::set_glyph_index); + ClassDB::bind_method(D_METHOD("get_glyph_count"), &CharFXTransform::get_glyph_count); + ClassDB::bind_method(D_METHOD("set_glyph_count", "glyph_count"), &CharFXTransform::set_glyph_count); + + ClassDB::bind_method(D_METHOD("get_glyph_flags"), &CharFXTransform::get_glyph_flags); + ClassDB::bind_method(D_METHOD("set_glyph_flags", "glyph_flags"), &CharFXTransform::set_glyph_flags); + ClassDB::bind_method(D_METHOD("get_font"), &CharFXTransform::get_font); ClassDB::bind_method(D_METHOD("set_font", "font"), &CharFXTransform::set_font); @@ -101,5 +107,7 @@ void CharFXTransform::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::COLOR, "color"), "set_color", "get_color"); ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "env"), "set_environment", "get_environment"); ADD_PROPERTY(PropertyInfo(Variant::INT, "glyph_index"), "set_glyph_index", "get_glyph_index"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "glyph_count"), "set_glyph_count", "get_glyph_count"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "glyph_flags"), "set_glyph_flags", "get_glyph_flags"); ADD_PROPERTY(PropertyInfo(Variant::RID, "font"), "set_font", "get_font"); } diff --git a/scene/gui/rich_text_effect.h b/scene/gui/rich_text_effect.h index f5506542bb..5681f9b193 100644 --- a/scene/gui/rich_text_effect.h +++ b/scene/gui/rich_text_effect.h @@ -50,6 +50,8 @@ public: double elapsed_time = 0.0f; Dictionary environment; uint32_t glyph_index = 0; + uint16_t glyph_flags = 0; + uint8_t glyph_count = 0; RID font; CharFXTransform(); @@ -57,19 +59,31 @@ public: Vector2i get_range() { return range; } void set_range(const Vector2i &p_range) { range = p_range; } + double get_elapsed_time() { return elapsed_time; } void set_elapsed_time(double p_elapsed_time) { elapsed_time = p_elapsed_time; } + bool is_visible() { return visibility; } void set_visibility(bool p_visibility) { visibility = p_visibility; } + bool is_outline() { return outline; } void set_outline(bool p_outline) { outline = p_outline; } + Point2 get_offset() { return offset; } void set_offset(Point2 p_offset) { offset = p_offset; } + Color get_color() { return color; } void set_color(Color p_color) { color = p_color; } uint32_t get_glyph_index() const { return glyph_index; }; void set_glyph_index(uint32_t p_glyph_index) { glyph_index = p_glyph_index; }; + + uint16_t get_glyph_flags() const { return glyph_index; }; + void set_glyph_flags(uint16_t p_glyph_flags) { glyph_flags = p_glyph_flags; }; + + uint8_t get_glyph_count() const { return glyph_count; }; + void set_glyph_count(uint8_t p_glyph_count) { glyph_count = p_glyph_count; }; + RID get_font() const { return font; }; void set_font(RID p_font) { font = p_font; }; diff --git a/scene/gui/rich_text_label.cpp b/scene/gui/rich_text_label.cpp index 562bac60c2..277026cc28 100644 --- a/scene/gui/rich_text_label.cpp +++ b/scene/gui/rich_text_label.cpp @@ -333,7 +333,7 @@ void RichTextLabel::_resize_line(ItemFrame *p_frame, int p_line, const Ref<Font> } else { frame->lines.write[i].offset.y = 0; } - frame->lines.write[i].offset += Vector2(offset.x, offset.y); + frame->lines.write[i].offset += offset; float h = frame->lines[i].text_buf->get_size().y; if (frame->min_size_over.y > 0) { @@ -399,8 +399,9 @@ void RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font> // Shape current paragraph. String text; Item *it_to = (p_line + 1 < p_frame->lines.size()) ? p_frame->lines[p_line + 1].from : nullptr; + int remaining_characters = visible_characters - l.char_offset; for (Item *it = l.from; it && it != it_to; it = _get_next_item(it)) { - if (visible_characters >= 0 && l.char_offset + l.char_count > visible_characters) { + if (visible_characters >= 0 && remaining_characters <= 0) { break; } switch (it->type) { @@ -427,7 +428,8 @@ void RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font> } l.text_buf->add_string("\n", font, font_size, Dictionary(), ""); text += "\n"; - l.char_count += 1; + l.char_count++; + remaining_characters--; } break; case ITEM_TEXT: { ItemText *t = (ItemText *)it; @@ -442,9 +444,10 @@ void RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font> Dictionary font_ftr = _find_font_features(it); String lang = _find_language(it); String tx = t->text; - if (visible_characters >= 0 && l.char_offset + l.char_count + tx.length() > visible_characters) { - tx = tx.substr(0, l.char_offset + l.char_count + tx.length() - visible_characters); + if (visible_characters >= 0 && remaining_characters >= 0) { + tx = tx.substr(0, remaining_characters); } + remaining_characters -= tx.length(); l.text_buf->add_string(tx, font, font_size, font_ftr, lang); text += tx; @@ -454,7 +457,8 @@ void RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font> ItemImage *img = (ItemImage *)it; l.text_buf->add_object((uint64_t)it, img->image->get_size(), img->inline_align, 1); text += String::chr(0xfffc); - l.char_count += 1; + l.char_count++; + remaining_characters--; } break; case ITEM_TABLE: { ItemTable *table = static_cast<ItemTable *>(it); @@ -483,6 +487,7 @@ void RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font> int cell_ch = (char_offset - (l.char_offset + l.char_count)); l.char_count += cell_ch; t_char_count += cell_ch; + remaining_characters -= cell_ch; table->columns.write[column].min_width = MAX(table->columns[column].min_width, ceil(frame->lines[i].text_buf->get_size().x)); table->columns.write[column].max_width = MAX(table->columns[column].max_width, ceil(frame->lines[i].text_buf->get_non_wraped_size().x)); @@ -573,7 +578,7 @@ void RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font> } else { frame->lines.write[i].offset.y = 0; } - frame->lines.write[i].offset += Vector2(offset.x, offset.y); + frame->lines.write[i].offset += offset; float h = frame->lines[i].text_buf->get_size().y; if (frame->min_size_over.y > 0) { @@ -847,6 +852,21 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o Point2 fx_offset = Vector2(glyphs[i].x_off, glyphs[i].y_off); RID frid = glyphs[i].font_rid; uint32_t gl = glyphs[i].index; + uint16_t gl_fl = glyphs[i].flags; + uint8_t gl_cn = glyphs[i].count; + bool cprev = false; + if (gl_cn == 0) { // Parts of the same cluster, always connected. + cprev = true; + } + if (gl_fl & TextServer::GRAPHEME_IS_RTL) { // Check if previous grapheme cluster is connected. + if (i > 0 && (glyphs[i - 1].flags & TextServer::GRAPHEME_IS_CONNECTED)) { + cprev = true; + } + } else { + if (glyphs[i].flags & TextServer::GRAPHEME_IS_CONNECTED) { + cprev = true; + } + } //Apply fx. float faded_visibility = 1.0f; @@ -875,6 +895,8 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o charfx->outline = true; charfx->font = frid; charfx->glyph_index = gl; + charfx->glyph_flags = gl_fl; + charfx->glyph_count = gl_cn; charfx->offset = fx_offset; charfx->color = font_color; @@ -890,25 +912,34 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o } else if (item_fx->type == ITEM_SHAKE) { ItemShake *item_shake = static_cast<ItemShake *>(item_fx); - uint64_t char_current_rand = item_shake->offset_random(glyphs[i].start); - uint64_t char_previous_rand = item_shake->offset_previous_random(glyphs[i].start); - uint64_t max_rand = 2147483647; - double current_offset = Math::range_lerp(char_current_rand % max_rand, 0, max_rand, 0.0f, 2.f * (float)Math_PI); - double previous_offset = Math::range_lerp(char_previous_rand % max_rand, 0, max_rand, 0.0f, 2.f * (float)Math_PI); - double n_time = (double)(item_shake->elapsed_time / (0.5f / item_shake->rate)); - n_time = (n_time > 1.0) ? 1.0 : n_time; - fx_offset += Point2(Math::lerp(Math::sin(previous_offset), Math::sin(current_offset), n_time), Math::lerp(Math::cos(previous_offset), Math::cos(current_offset), n_time)) * (float)item_shake->strength / 10.0f; + if (!cprev) { + uint64_t char_current_rand = item_shake->offset_random(glyphs[i].start); + uint64_t char_previous_rand = item_shake->offset_previous_random(glyphs[i].start); + uint64_t max_rand = 2147483647; + double current_offset = Math::range_lerp(char_current_rand % max_rand, 0, max_rand, 0.0f, 2.f * (float)Math_PI); + double previous_offset = Math::range_lerp(char_previous_rand % max_rand, 0, max_rand, 0.0f, 2.f * (float)Math_PI); + double n_time = (double)(item_shake->elapsed_time / (0.5f / item_shake->rate)); + n_time = (n_time > 1.0) ? 1.0 : n_time; + item_shake->prev_off = Point2(Math::lerp(Math::sin(previous_offset), Math::sin(current_offset), n_time), Math::lerp(Math::cos(previous_offset), Math::cos(current_offset), n_time)) * (float)item_shake->strength / 10.0f; + } + fx_offset += item_shake->prev_off; } else if (item_fx->type == ITEM_WAVE) { ItemWave *item_wave = static_cast<ItemWave *>(item_fx); - double value = Math::sin(item_wave->frequency * item_wave->elapsed_time + ((p_ofs.x + off.x) / 50)) * (item_wave->amplitude / 10.0f); - fx_offset += Point2(0, 1) * value; + if (!cprev) { + double value = Math::sin(item_wave->frequency * item_wave->elapsed_time + ((p_ofs.x + gloff.x) / 50)) * (item_wave->amplitude / 10.0f); + item_wave->prev_off = Point2(0, 1) * value; + } + fx_offset += item_wave->prev_off; } else if (item_fx->type == ITEM_TORNADO) { ItemTornado *item_tornado = static_cast<ItemTornado *>(item_fx); - double torn_x = Math::sin(item_tornado->frequency * item_tornado->elapsed_time + ((p_ofs.x + gloff.x) / 50)) * (item_tornado->radius); - double torn_y = Math::cos(item_tornado->frequency * item_tornado->elapsed_time + ((p_ofs.x + gloff.x) / 50)) * (item_tornado->radius); - fx_offset += Point2(torn_x, torn_y); + if (!cprev) { + double torn_x = Math::sin(item_tornado->frequency * item_tornado->elapsed_time + ((p_ofs.x + gloff.x) / 50)) * (item_tornado->radius); + double torn_y = Math::cos(item_tornado->frequency * item_tornado->elapsed_time + ((p_ofs.x + gloff.x) / 50)) * (item_tornado->radius); + item_tornado->prev_off = Point2(torn_x, torn_y); + } + fx_offset += item_tornado->prev_off; } else if (item_fx->type == ITEM_RAINBOW) { ItemRainbow *item_rainbow = static_cast<ItemRainbow *>(item_fx); @@ -999,6 +1030,21 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o Point2 fx_offset = Vector2(glyphs[i].x_off, glyphs[i].y_off); RID frid = glyphs[i].font_rid; uint32_t gl = glyphs[i].index; + uint16_t gl_fl = glyphs[i].flags; + uint8_t gl_cn = glyphs[i].count; + bool cprev = false; + if (gl_cn == 0) { // Parts of the same grapheme cluster, always connected. + cprev = true; + } + if (gl_fl & TextServer::GRAPHEME_IS_RTL) { // Check if previous grapheme cluster is connected. + if (i > 0 && (glyphs[i - 1].flags & TextServer::GRAPHEME_IS_CONNECTED)) { + cprev = true; + } + } else { + if (glyphs[i].flags & TextServer::GRAPHEME_IS_CONNECTED) { + cprev = true; + } + } //Apply fx. float faded_visibility = 1.0f; @@ -1027,6 +1073,8 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o charfx->outline = false; charfx->font = frid; charfx->glyph_index = gl; + charfx->glyph_flags = gl_fl; + charfx->glyph_count = gl_cn; charfx->offset = fx_offset; charfx->color = font_color; @@ -1042,25 +1090,34 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o } else if (item_fx->type == ITEM_SHAKE) { ItemShake *item_shake = static_cast<ItemShake *>(item_fx); - uint64_t char_current_rand = item_shake->offset_random(glyphs[i].start); - uint64_t char_previous_rand = item_shake->offset_previous_random(glyphs[i].start); - uint64_t max_rand = 2147483647; - double current_offset = Math::range_lerp(char_current_rand % max_rand, 0, max_rand, 0.0f, 2.f * (float)Math_PI); - double previous_offset = Math::range_lerp(char_previous_rand % max_rand, 0, max_rand, 0.0f, 2.f * (float)Math_PI); - double n_time = (double)(item_shake->elapsed_time / (0.5f / item_shake->rate)); - n_time = (n_time > 1.0) ? 1.0 : n_time; - fx_offset += Point2(Math::lerp(Math::sin(previous_offset), Math::sin(current_offset), n_time), Math::lerp(Math::cos(previous_offset), Math::cos(current_offset), n_time)) * (float)item_shake->strength / 10.0f; + if (!cprev) { + uint64_t char_current_rand = item_shake->offset_random(glyphs[i].start); + uint64_t char_previous_rand = item_shake->offset_previous_random(glyphs[i].start); + uint64_t max_rand = 2147483647; + double current_offset = Math::range_lerp(char_current_rand % max_rand, 0, max_rand, 0.0f, 2.f * (float)Math_PI); + double previous_offset = Math::range_lerp(char_previous_rand % max_rand, 0, max_rand, 0.0f, 2.f * (float)Math_PI); + double n_time = (double)(item_shake->elapsed_time / (0.5f / item_shake->rate)); + n_time = (n_time > 1.0) ? 1.0 : n_time; + item_shake->prev_off = Point2(Math::lerp(Math::sin(previous_offset), Math::sin(current_offset), n_time), Math::lerp(Math::cos(previous_offset), Math::cos(current_offset), n_time)) * (float)item_shake->strength / 10.0f; + } + fx_offset += item_shake->prev_off; } else if (item_fx->type == ITEM_WAVE) { ItemWave *item_wave = static_cast<ItemWave *>(item_fx); - double value = Math::sin(item_wave->frequency * item_wave->elapsed_time + ((p_ofs.x + off.x) / 50)) * (item_wave->amplitude / 10.0f); - fx_offset += Point2(0, 1) * value; + if (!cprev) { + double value = Math::sin(item_wave->frequency * item_wave->elapsed_time + ((p_ofs.x + off.x) / 50)) * (item_wave->amplitude / 10.0f); + item_wave->prev_off = Point2(0, 1) * value; + } + fx_offset += item_wave->prev_off; } else if (item_fx->type == ITEM_TORNADO) { ItemTornado *item_tornado = static_cast<ItemTornado *>(item_fx); - double torn_x = Math::sin(item_tornado->frequency * item_tornado->elapsed_time + ((p_ofs.x + off.x) / 50)) * (item_tornado->radius); - double torn_y = Math::cos(item_tornado->frequency * item_tornado->elapsed_time + ((p_ofs.x + off.x) / 50)) * (item_tornado->radius); - fx_offset += Point2(torn_x, torn_y); + if (!cprev) { + double torn_x = Math::sin(item_tornado->frequency * item_tornado->elapsed_time + ((p_ofs.x + off.x) / 50)) * (item_tornado->radius); + double torn_y = Math::cos(item_tornado->frequency * item_tornado->elapsed_time + ((p_ofs.x + off.x) / 50)) * (item_tornado->radius); + item_tornado->prev_off = Point2(torn_x, torn_y); + } + fx_offset += item_tornado->prev_off; } else if (item_fx->type == ITEM_RAINBOW) { ItemRainbow *item_rainbow = static_cast<ItemRainbow *>(item_fx); @@ -1374,8 +1431,8 @@ void RichTextLabel::_notification(int p_what) { } break; case NOTIFICATION_THEME_CHANGED: case NOTIFICATION_ENTER_TREE: { - if (bbcode != "") { - set_bbcode(bbcode); + if (text != "") { + set_text(text); } main->first_invalid_line = 0; //invalidate ALL @@ -2321,8 +2378,7 @@ void RichTextLabel::add_image(const Ref<Texture2D> &p_image, const int p_width, item->size.width = p_image->get_width() * p_height / p_image->get_height(); } else { // keep original width and height - item->size.height = p_image->get_height(); - item->size.width = p_image->get_width(); + item->size = p_image->get_size(); } } @@ -2767,10 +2823,10 @@ bool RichTextLabel::is_scroll_following() const { Error RichTextLabel::parse_bbcode(const String &p_bbcode) { clear(); - return append_bbcode(p_bbcode); + return append_text(p_bbcode); } -Error RichTextLabel::append_bbcode(const String &p_bbcode) { +Error RichTextLabel::append_text(const String &p_bbcode) { int pos = 0; List<String> tag_stack; @@ -3824,8 +3880,8 @@ int RichTextLabel::get_selection_to() const { return selection.to_frame->lines[selection.to_line].char_offset + selection.to_char - 1; } -void RichTextLabel::set_bbcode(const String &p_bbcode) { - bbcode = p_bbcode; +void RichTextLabel::set_text(const String &p_bbcode) { + text = p_bbcode; if (is_inside_tree() && use_bbcode) { parse_bbcode(p_bbcode); } else { // raw text @@ -3834,8 +3890,8 @@ void RichTextLabel::set_bbcode(const String &p_bbcode) { } } -String RichTextLabel::get_bbcode() const { - return bbcode; +String RichTextLabel::get_text() const { + return text; } void RichTextLabel::set_use_bbcode(bool p_enable) { @@ -3843,19 +3899,24 @@ void RichTextLabel::set_use_bbcode(bool p_enable) { return; } use_bbcode = p_enable; - set_bbcode(bbcode); notify_property_list_changed(); + set_text(text); } bool RichTextLabel::is_using_bbcode() const { return use_bbcode; } -String RichTextLabel::get_text() { +String RichTextLabel::get_parsed_text() const { String text = ""; Item *it = main; while (it) { - if (it->type == ITEM_TEXT) { + if (it->type == ITEM_DROPCAP) { + const ItemDropcap *dc = (ItemDropcap *)it; + if (dc != nullptr) { + text += dc->text; + } + } else if (it->type == ITEM_TEXT) { ItemText *t = static_cast<ItemText *>(it); text += t->text; } else if (it->type == ITEM_NEWLINE) { @@ -3870,11 +3931,6 @@ String RichTextLabel::get_text() { return text; } -void RichTextLabel::set_text(const String &p_string) { - clear(); - add_text(p_string); -} - void RichTextLabel::set_text_direction(Control::TextDirection p_text_direction) { ERR_FAIL_COND((int)p_text_direction < -1 || (int)p_text_direction > 3); if (text_direction != p_text_direction) { @@ -3931,7 +3987,6 @@ void RichTextLabel::set_percent_visible(float p_percent) { if (p_percent < 0 || p_percent >= 1) { visible_characters = -1; percent_visible = 1; - } else { visible_characters = get_total_character_count() * p_percent; percent_visible = p_percent; @@ -3948,8 +4003,8 @@ float RichTextLabel::get_percent_visible() const { void RichTextLabel::set_effects(Array p_effects) { custom_effects = p_effects; - if ((bbcode != "") && use_bbcode) { - parse_bbcode(bbcode); + if ((text != "") && use_bbcode) { + parse_bbcode(text); } } @@ -3963,8 +4018,8 @@ void RichTextLabel::install_effect(const Variant effect) { if (rteffect.is_valid()) { custom_effects.push_back(effect); - if ((bbcode != "") && use_bbcode) { - parse_bbcode(bbcode); + if ((text != "") && use_bbcode) { + parse_bbcode(text); } } } @@ -3977,14 +4032,20 @@ int RichTextLabel::get_content_height() const { return total_height; } -void RichTextLabel::_validate_property(PropertyInfo &property) const { - if (!use_bbcode && property.name == "bbcode_text") { - property.usage = PROPERTY_USAGE_NOEDITOR; +#ifndef DISABLE_DEPRECATED +// People will be very angry, if their texts get erased, because of #39148. (3.x -> 4.0) +// Altough some people may not used bbcode_text, so we only overwrite, if bbcode_text is not empty +bool RichTextLabel::_set(const StringName &p_name, const Variant &p_value) { + if (p_name == "bbcode_text" && !((String)p_value).is_empty()) { + set_text(p_value); + return true; } + return false; } +#endif void RichTextLabel::_bind_methods() { - ClassDB::bind_method(D_METHOD("get_text"), &RichTextLabel::get_text); + ClassDB::bind_method(D_METHOD("get_parsed_text"), &RichTextLabel::get_parsed_text); ClassDB::bind_method(D_METHOD("add_text", "text"), &RichTextLabel::add_text); ClassDB::bind_method(D_METHOD("set_text", "text"), &RichTextLabel::set_text); ClassDB::bind_method(D_METHOD("add_image", "image", "width", "height", "color", "inline_align"), &RichTextLabel::add_image, DEFVAL(0), DEFVAL(0), DEFVAL(Color(1.0, 1.0, 1.0)), DEFVAL(INLINE_ALIGN_CENTER)); @@ -4062,10 +4123,9 @@ void RichTextLabel::_bind_methods() { ClassDB::bind_method(D_METHOD("get_selected_text"), &RichTextLabel::get_selected_text); ClassDB::bind_method(D_METHOD("parse_bbcode", "bbcode"), &RichTextLabel::parse_bbcode); - ClassDB::bind_method(D_METHOD("append_bbcode", "bbcode"), &RichTextLabel::append_bbcode); + ClassDB::bind_method(D_METHOD("append_text", "bbcode"), &RichTextLabel::append_text); - ClassDB::bind_method(D_METHOD("set_bbcode", "text"), &RichTextLabel::set_bbcode); - ClassDB::bind_method(D_METHOD("get_bbcode"), &RichTextLabel::get_bbcode); + ClassDB::bind_method(D_METHOD("get_text"), &RichTextLabel::get_text); ClassDB::bind_method(D_METHOD("set_visible_characters", "amount"), &RichTextLabel::set_visible_characters); ClassDB::bind_method(D_METHOD("get_visible_characters"), &RichTextLabel::get_visible_characters); @@ -4092,16 +4152,13 @@ void RichTextLabel::_bind_methods() { ClassDB::bind_method(D_METHOD("get_effects"), &RichTextLabel::get_effects); ClassDB::bind_method(D_METHOD("install_effect", "effect"), &RichTextLabel::install_effect); - ADD_GROUP("BBCode", "bbcode_"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "bbcode_enabled"), "set_use_bbcode", "is_using_bbcode"); - ADD_PROPERTY(PropertyInfo(Variant::STRING, "bbcode_text", PROPERTY_HINT_MULTILINE_TEXT), "set_bbcode", "get_bbcode"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "visible_characters", PROPERTY_HINT_RANGE, "-1,128000,1"), "set_visible_characters", "get_visible_characters"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "percent_visible", PROPERTY_HINT_RANGE, "0,1,0.001"), "set_percent_visible", "get_percent_visible"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "meta_underlined"), "set_meta_underline", "is_meta_underlined"); ADD_PROPERTY(PropertyInfo(Variant::INT, "tab_size", PROPERTY_HINT_RANGE, "0,24,1"), "set_tab_size", "get_tab_size"); ADD_PROPERTY(PropertyInfo(Variant::STRING, "text", PROPERTY_HINT_MULTILINE_TEXT), "set_text", "get_text"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "bbcode_enabled"), "set_use_bbcode", "is_using_bbcode"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "fit_content_height"), "set_fit_content_height", "is_fit_content_height_enabled"); @@ -4163,16 +4220,20 @@ void RichTextLabel::_bind_methods() { } void RichTextLabel::set_visible_characters(int p_visible) { - visible_characters = p_visible; - if (p_visible == -1) { - percent_visible = 1; - } else { - int total_char_count = get_total_character_count(); - if (total_char_count > 0) { - percent_visible = (float)p_visible / (float)total_char_count; + if (visible_characters != p_visible) { + visible_characters = p_visible; + if (p_visible == -1) { + percent_visible = 1; + } else { + int total_char_count = get_total_character_count(); + if (total_char_count > 0) { + percent_visible = (float)p_visible / (float)total_char_count; + } } + main->first_invalid_line = 0; //invalidate ALL + _validate_line_caches(main); + update(); } - update(); } int RichTextLabel::get_visible_characters() const { @@ -4180,9 +4241,19 @@ int RichTextLabel::get_visible_characters() const { } int RichTextLabel::get_total_character_count() const { + // Note: Do not use line buffer "char_count", it includes only visible characters. int tc = 0; - for (int i = 0; i < current_frame->lines.size(); i++) { - tc += current_frame->lines[i].char_count; + Item *it = main; + while (it) { + if (it->type == ITEM_TEXT) { + ItemText *t = static_cast<ItemText *>(it); + tc += t->text.length(); + } else if (it->type == ITEM_NEWLINE) { + tc++; + } else if (it->type == ITEM_IMAGE) { + tc++; + } + it = _get_next_item(it, true); } return tc; diff --git a/scene/gui/rich_text_label.h b/scene/gui/rich_text_label.h index f25a8bf193..94f02a3989 100644 --- a/scene/gui/rich_text_label.h +++ b/scene/gui/rich_text_label.h @@ -85,7 +85,6 @@ public: protected: void _notification(int p_what); static void _bind_methods(); - void _validate_property(PropertyInfo &property) const override; private: struct Item; @@ -268,6 +267,7 @@ private: float rate = 0.0f; uint64_t _current_rng = 0; uint64_t _previous_rng = 0; + Vector2 prev_off; ItemShake() { type = ITEM_SHAKE; } @@ -290,6 +290,7 @@ private: struct ItemWave : public ItemFX { float frequency = 1.0f; float amplitude = 1.0f; + Vector2 prev_off; ItemWave() { type = ITEM_WAVE; } }; @@ -297,6 +298,7 @@ private: struct ItemTornado : public ItemFX { float radius = 1.0f; float frequency = 1.0f; + Vector2 prev_off; ItemTornado() { type = ITEM_TORNADO; } }; @@ -452,16 +454,19 @@ private: virtual Dictionary parse_expressions_for_values(Vector<String> p_expressions); void _draw_fbg_boxes(RID p_ci, RID p_rid, Vector2 line_off, Item *it_from, Item *it_to, int start, int end, int fbg_flag); - +#ifndef DISABLE_DEPRECATED + // Kept for compatibility from 3.x to 4.0. + bool _set(const StringName &p_name, const Variant &p_value); +#endif bool use_bbcode = false; - String bbcode; + String text; int fixed_width = -1; bool fit_content_height = false; public: - String get_text(); + String get_parsed_text() const; void add_text(const String &p_text); void add_image(const Ref<Texture2D> &p_image, const int p_width = 0, const int p_height = 0, const Color &p_color = Color(1.0, 1.0, 1.0), InlineAlign p_align = INLINE_ALIGN_CENTER); void add_newline(); @@ -548,15 +553,13 @@ public: void selection_copy(); Error parse_bbcode(const String &p_bbcode); - Error append_bbcode(const String &p_bbcode); + Error append_text(const String &p_bbcode); void set_use_bbcode(bool p_enable); bool is_using_bbcode() const; - void set_bbcode(const String &p_bbcode); - String get_bbcode() const; - - void set_text(const String &p_string); + void set_text(const String &p_bbcode); + String get_text() const; void set_text_direction(TextDirection p_text_direction); TextDirection get_text_direction() const; diff --git a/scene/gui/scroll_bar.cpp b/scene/gui/scroll_bar.cpp index 4edf373fbf..4a3a6837d5 100644 --- a/scene/gui/scroll_bar.cpp +++ b/scene/gui/scroll_bar.cpp @@ -560,7 +560,7 @@ void ScrollBar::_drag_node_input(const Ref<InputEvent> &p_input) { if (mm.is_valid()) { if (drag_node_touching && !drag_node_touching_deaccel) { - Vector2 motion = Vector2(mm->get_relative().x, mm->get_relative().y); + Vector2 motion = mm->get_relative(); drag_node_accum -= motion; Vector2 diff = drag_node_from + drag_node_accum; diff --git a/scene/gui/scroll_container.cpp b/scene/gui/scroll_container.cpp index 0c051f61e2..0c0ec39c7f 100644 --- a/scene/gui/scroll_container.cpp +++ b/scene/gui/scroll_container.cpp @@ -167,7 +167,7 @@ void ScrollContainer::gui_input(const Ref<InputEvent> &p_gui_input) { if (mm.is_valid()) { if (drag_touching && !drag_touching_deaccel) { - Vector2 motion = Vector2(mm->get_relative().x, mm->get_relative().y); + Vector2 motion = mm->get_relative(); drag_accum -= motion; if (beyond_deadzone || (scroll_h && Math::abs(drag_accum.x) > deadzone) || (scroll_v && Math::abs(drag_accum.y) > deadzone)) { diff --git a/scene/gui/spin_box.cpp b/scene/gui/spin_box.cpp index 1074d0d8a0..0aec017649 100644 --- a/scene/gui/spin_box.cpp +++ b/scene/gui/spin_box.cpp @@ -68,6 +68,15 @@ void SpinBox::_text_submitted(const String &p_string) { _value_changed(0); } +void SpinBox::_text_changed(const String &p_string) { + int cursor_pos = line_edit->get_caret_column(); + + _text_submitted(p_string); + + // Line edit 'set_text' method resets the cursor position so we need to undo that. + line_edit->set_caret_column(cursor_pos); +} + LineEdit *SpinBox::get_line_edit() { return line_edit; } @@ -244,6 +253,24 @@ String SpinBox::get_prefix() const { return prefix; } +void SpinBox::set_update_on_text_changed(bool p_update) { + if (update_on_text_changed == p_update) { + return; + } + + update_on_text_changed = p_update; + + if (p_update) { + line_edit->connect("text_changed", callable_mp(this, &SpinBox::_text_changed), Vector<Variant>(), CONNECT_DEFERRED); + } else { + line_edit->disconnect("text_changed", callable_mp(this, &SpinBox::_text_changed)); + } +} + +bool SpinBox::get_update_on_text_changed() const { + return update_on_text_changed; +} + void SpinBox::set_editable(bool p_editable) { line_edit->set_editable(p_editable); } @@ -267,11 +294,14 @@ void SpinBox::_bind_methods() { ClassDB::bind_method(D_METHOD("get_prefix"), &SpinBox::get_prefix); ClassDB::bind_method(D_METHOD("set_editable", "editable"), &SpinBox::set_editable); ClassDB::bind_method(D_METHOD("is_editable"), &SpinBox::is_editable); + ClassDB::bind_method(D_METHOD("set_update_on_text_changed"), &SpinBox::set_update_on_text_changed); + ClassDB::bind_method(D_METHOD("get_update_on_text_changed"), &SpinBox::get_update_on_text_changed); ClassDB::bind_method(D_METHOD("apply"), &SpinBox::apply); ClassDB::bind_method(D_METHOD("get_line_edit"), &SpinBox::get_line_edit); ADD_PROPERTY(PropertyInfo(Variant::INT, "align", PROPERTY_HINT_ENUM, "Left,Center,Right,Fill"), "set_align", "get_align"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "editable"), "set_editable", "is_editable"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "update_on_text_changed"), "set_update_on_text_changed", "get_update_on_text_changed"); ADD_PROPERTY(PropertyInfo(Variant::STRING, "prefix"), "set_prefix", "get_prefix"); ADD_PROPERTY(PropertyInfo(Variant::STRING, "suffix"), "set_suffix", "get_suffix"); } @@ -284,7 +314,6 @@ SpinBox::SpinBox() { line_edit->set_mouse_filter(MOUSE_FILTER_PASS); line_edit->set_align(LineEdit::ALIGN_LEFT); - //connect("value_changed",this,"_value_changed"); line_edit->connect("text_submitted", callable_mp(this, &SpinBox::_text_submitted), Vector<Variant>(), CONNECT_DEFERRED); line_edit->connect("focus_exited", callable_mp(this, &SpinBox::_line_edit_focus_exit), Vector<Variant>(), CONNECT_DEFERRED); line_edit->connect("gui_input", callable_mp(this, &SpinBox::_line_edit_input)); diff --git a/scene/gui/spin_box.h b/scene/gui/spin_box.h index 9ec3885f1f..9828b894da 100644 --- a/scene/gui/spin_box.h +++ b/scene/gui/spin_box.h @@ -40,6 +40,7 @@ class SpinBox : public Range { LineEdit *line_edit; int last_w = 0; + bool update_on_text_changed = false; Timer *range_click_timer; void _range_click_timeout(); @@ -47,6 +48,8 @@ class SpinBox : public Range { void _text_submitted(const String &p_string); virtual void _value_changed(double) override; + void _text_changed(const String &p_string); + String prefix; String suffix; @@ -88,6 +91,9 @@ public: void set_prefix(const String &p_prefix); String get_prefix() const; + void set_update_on_text_changed(bool p_update); + bool get_update_on_text_changed() const; + void apply(); SpinBox(); diff --git a/scene/gui/tab_container.cpp b/scene/gui/tab_container.cpp index 137ce7e96f..c8a0501d8a 100644 --- a/scene/gui/tab_container.cpp +++ b/scene/gui/tab_container.cpp @@ -79,7 +79,7 @@ void TabContainer::gui_input(const Ref<InputEvent> &p_event) { Popup *popup = get_popup(); if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_LEFT) { - Point2 pos(mb->get_position().x, mb->get_position().y); + Point2 pos = mb->get_position(); Size2 size = get_size(); // Click must be on tabs in the tab header area. @@ -190,7 +190,7 @@ void TabContainer::gui_input(const Ref<InputEvent> &p_event) { Ref<InputEventMouseMotion> mm = p_event; if (mm.is_valid()) { - Point2 pos(mm->get_position().x, mm->get_position().y); + Point2 pos = mm->get_position(); Size2 size = get_size(); // Mouse must be on tabs in the tab header area. @@ -538,7 +538,6 @@ void TabContainer::_notification(int p_what) { void TabContainer::_draw_tab(Ref<StyleBox> &p_tab_style, Color &p_font_color, int p_index, float p_x) { Control *control = get_tab_control(p_index); RID canvas = get_canvas_item(); - Ref<Font> font = get_theme_font(SNAME("font")); Color font_outline_color = get_theme_color(SNAME("font_outline_color")); int outline_size = get_theme_constant(SNAME("outline_size")); int icon_text_distance = get_theme_constant(SNAME("icon_separation")); @@ -1134,7 +1133,6 @@ Size2 TabContainer::get_minimum_size() const { Ref<StyleBox> tab_unselected = get_theme_stylebox(SNAME("tab_unselected")); Ref<StyleBox> tab_selected = get_theme_stylebox(SNAME("tab_selected")); Ref<StyleBox> tab_disabled = get_theme_stylebox(SNAME("tab_disabled")); - Ref<Font> font = get_theme_font(SNAME("font")); if (tabs_visible) { ms.y += MAX(MAX(tab_unselected->get_minimum_size().y, tab_selected->get_minimum_size().y), tab_disabled->get_minimum_size().y); @@ -1212,6 +1210,8 @@ void TabContainer::_bind_methods() { ClassDB::bind_method(D_METHOD("get_tab_icon", "tab_idx"), &TabContainer::get_tab_icon); ClassDB::bind_method(D_METHOD("set_tab_disabled", "tab_idx", "disabled"), &TabContainer::set_tab_disabled); ClassDB::bind_method(D_METHOD("get_tab_disabled", "tab_idx"), &TabContainer::get_tab_disabled); + ClassDB::bind_method(D_METHOD("set_tab_hidden", "tab_idx", "hidden"), &TabContainer::set_tab_hidden); + ClassDB::bind_method(D_METHOD("get_tab_hidden", "tab_idx"), &TabContainer::get_tab_hidden); ClassDB::bind_method(D_METHOD("get_tab_idx_at_point", "point"), &TabContainer::get_tab_idx_at_point); ClassDB::bind_method(D_METHOD("set_popup", "popup"), &TabContainer::set_popup); ClassDB::bind_method(D_METHOD("get_popup"), &TabContainer::get_popup); diff --git a/scene/gui/tabs.cpp b/scene/gui/tabs.cpp index 3ca2d1c1e9..ef34bec347 100644 --- a/scene/gui/tabs.cpp +++ b/scene/gui/tabs.cpp @@ -98,29 +98,45 @@ void Tabs::gui_input(const Ref<InputEvent> &p_event) { if (mm.is_valid()) { Point2 pos = mm->get_position(); - highlight_arrow = -1; if (buttons_visible) { Ref<Texture2D> incr = get_theme_icon(SNAME("increment")); Ref<Texture2D> decr = get_theme_icon(SNAME("decrement")); if (is_layout_rtl()) { if (pos.x < decr->get_width()) { - highlight_arrow = 1; + if (highlight_arrow != 1) { + highlight_arrow = 1; + update(); + } } else if (pos.x < incr->get_width() + decr->get_width()) { - highlight_arrow = 0; + if (highlight_arrow != 0) { + highlight_arrow = 0; + update(); + } + } else if (highlight_arrow != -1) { + highlight_arrow = -1; + update(); } } else { int limit_minus_buttons = get_size().width - incr->get_width() - decr->get_width(); if (pos.x > limit_minus_buttons + decr->get_width()) { - highlight_arrow = 1; + if (highlight_arrow != 1) { + highlight_arrow = 1; + update(); + } } else if (pos.x > limit_minus_buttons) { - highlight_arrow = 0; + if (highlight_arrow != 0) { + highlight_arrow = 0; + update(); + } + } else if (highlight_arrow != -1) { + highlight_arrow = -1; + update(); } } } _update_hover(); - update(); return; } @@ -167,7 +183,7 @@ void Tabs::gui_input(const Ref<InputEvent> &p_event) { if (mb->is_pressed() && (mb->get_button_index() == MOUSE_BUTTON_LEFT || (select_with_rmb && mb->get_button_index() == MOUSE_BUTTON_RIGHT))) { // clicks - Point2 pos(mb->get_position().x, mb->get_position().y); + Point2 pos = mb->get_position(); if (buttons_visible) { Ref<Texture2D> incr = get_theme_icon(SNAME("increment")); @@ -241,6 +257,7 @@ void Tabs::_shape(int p_tab) { tabs.write[p_tab].xl_text = atr(tabs[p_tab].text); tabs.write[p_tab].text_buf->clear(); + tabs.write[p_tab].text_buf->set_width(-1); if (tabs[p_tab].text_direction == Control::TEXT_DIRECTION_INHERITED) { tabs.write[p_tab].text_buf->set_direction(is_layout_rtl() ? TextServer::DIRECTION_RTL : TextServer::DIRECTION_LTR); } else { @@ -529,7 +546,6 @@ bool Tabs::get_offset_buttons_visible() const { void Tabs::set_tab_title(int p_tab, const String &p_title) { ERR_FAIL_INDEX(p_tab, tabs.size()); tabs.write[p_tab].text = p_title; - tabs.write[p_tab].xl_text = atr(p_title); _shape(p_tab); update(); minimum_size_changed(); diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp index 06dfc31621..5b13e1da0b 100644 --- a/scene/gui/text_edit.cpp +++ b/scene/gui/text_edit.cpp @@ -1444,7 +1444,7 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) { if (mb->get_button_index() == MOUSE_BUTTON_LEFT) { _reset_caret_blink_timer(); - Point2i pos = get_line_column_at_pos(Point2i(mpos.x, mpos.y)); + Point2i pos = get_line_column_at_pos(mpos); int row = pos.y; int col = pos.x; @@ -1547,7 +1547,7 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) { if (mb->get_button_index() == MOUSE_BUTTON_RIGHT && context_menu_enabled) { _reset_caret_blink_timer(); - Point2i pos = get_line_column_at_pos(Point2i(mpos.x, mpos.y)); + Point2i pos = get_line_column_at_pos(mpos); int row = pos.y; int col = pos.x; @@ -3689,7 +3689,7 @@ void TextEdit::select_word_under_caret() { int end = 0; const Vector<Vector2i> words = TS->shaped_text_get_word_breaks(text.get_line_data(caret.line)->get_rid()); for (int i = 0; i < words.size(); i++) { - if (words[i].x <= caret.column && words[i].y >= caret.column) { + if ((words[i].x < caret.column && words[i].y > caret.column) || (i == words.size() - 1 && caret.column == words[i].y)) { begin = words[i].x; end = words[i].y; break; @@ -5385,7 +5385,7 @@ void TextEdit::_update_selection_mode_pointer() { dragging_selection = true; Point2 mp = get_local_mouse_pos(); - Point2i pos = get_line_column_at_pos(Point2i(mp.x, mp.y)); + Point2i pos = get_line_column_at_pos(mp); int line = pos.y; int col = pos.x; @@ -5402,7 +5402,7 @@ void TextEdit::_update_selection_mode_word() { dragging_selection = true; Point2 mp = get_local_mouse_pos(); - Point2i pos = get_line_column_at_pos(Point2i(mp.x, mp.y)); + Point2i pos = get_line_column_at_pos(mp); int line = pos.y; int col = pos.x; @@ -5411,7 +5411,7 @@ void TextEdit::_update_selection_mode_word() { int end = beg; Vector<Vector2i> words = TS->shaped_text_get_word_breaks(text.get_line_data(line)->get_rid()); for (int i = 0; i < words.size(); i++) { - if (words[i].x < caret_pos && words[i].y > caret_pos) { + if ((words[i].x < caret_pos && words[i].y > caret_pos) || (i == words.size() - 1 && caret_pos == words[i].y)) { beg = words[i].x; end = words[i].y; break; @@ -5450,7 +5450,7 @@ void TextEdit::_update_selection_mode_line() { dragging_selection = true; Point2 mp = get_local_mouse_pos(); - Point2i pos = get_line_column_at_pos(Point2i(mp.x, mp.y)); + Point2i pos = get_line_column_at_pos(mp); int line = pos.y; int col = pos.x; @@ -5765,7 +5765,7 @@ void TextEdit::_update_minimap_hover() { return; } - const int row = get_minimap_line_at_pos(Point2i(mp.x, mp.y)); + const int row = get_minimap_line_at_pos(mp); const bool new_hovering_minimap = row >= get_first_visible_line() && row <= get_last_full_visible_line(); if (new_hovering_minimap != hovering_minimap) { @@ -5786,7 +5786,7 @@ void TextEdit::_update_minimap_click() { minimap_clicked = true; dragging_minimap = true; - int row = get_minimap_line_at_pos(Point2i(mp.x, mp.y)); + int row = get_minimap_line_at_pos(mp); if (row >= get_first_visible_line() && (row < get_last_full_visible_line() || row >= (text.size() - 1))) { minimap_scroll_ratio = v_scroll->get_as_ratio(); diff --git a/scene/gui/texture_progress_bar.cpp b/scene/gui/texture_progress_bar.cpp index 286f01ee33..35a098c6fd 100644 --- a/scene/gui/texture_progress_bar.cpp +++ b/scene/gui/texture_progress_bar.cpp @@ -471,7 +471,7 @@ void TextureProgressBar::_notification(int p_what) { Vector<Point2> uvs; Vector<Point2> points; uvs.push_back(get_relative_center()); - points.push_back(progress_offset + Point2(s.x * get_relative_center().x, s.y * get_relative_center().y)); + points.push_back(progress_offset + s * get_relative_center()); for (int i = 0; i < pts.size(); i++) { Point2 uv = unit_val_to_uv(pts[i]); if (uvs.find(uv) >= 0) { @@ -493,8 +493,7 @@ void TextureProgressBar::_notification(int p_what) { p = progress->get_size(); } - p.x *= get_relative_center().x; - p.y *= get_relative_center().y; + p *= get_relative_center(); p = p.floor(); draw_line(p - Point2(8, 0), p + Point2(8, 0), Color(0.9, 0.5, 0.5), 2); draw_line(p - Point2(0, 8), p + Point2(0, 8), Color(0.9, 0.5, 0.5), 2); diff --git a/scene/gui/tree.cpp b/scene/gui/tree.cpp index f62c09925d..7d7596635c 100644 --- a/scene/gui/tree.cpp +++ b/scene/gui/tree.cpp @@ -144,6 +144,7 @@ void TreeItem::_change_tree(Tree *p_tree) { /* cell mode */ void TreeItem::set_cell_mode(int p_column, TreeCellMode p_mode) { ERR_FAIL_INDEX(p_column, cells.size()); + Cell &c = cells.write[p_column]; c.mode = p_mode; c.min = 0; @@ -155,8 +156,9 @@ void TreeItem::set_cell_mode(int p_column, TreeCellMode p_mode) { c.text = ""; c.dirty = true; c.icon_max_w = 0; + c.cached_minimum_size_dirty = true; + _changed_notify(p_column); - cached_minimum_size_dirty = true; } TreeItem::TreeCellMode TreeItem::get_cell_mode(int p_column) const { @@ -167,22 +169,27 @@ TreeItem::TreeCellMode TreeItem::get_cell_mode(int p_column) const { /* check mode */ void TreeItem::set_checked(int p_column, bool p_checked) { ERR_FAIL_INDEX(p_column, cells.size()); + cells.write[p_column].checked = p_checked; cells.write[p_column].indeterminate = false; + cells.write[p_column].cached_minimum_size_dirty = true; + _changed_notify(p_column); - cached_minimum_size_dirty = true; } void TreeItem::set_indeterminate(int p_column, bool p_indeterminate) { ERR_FAIL_INDEX(p_column, cells.size()); + // Prevent uncheck if indeterminate set to false twice if (p_indeterminate == cells[p_column].indeterminate) { return; } + cells.write[p_column].indeterminate = p_indeterminate; cells.write[p_column].checked = false; + cells.write[p_column].cached_minimum_size_dirty = true; + _changed_notify(p_column); - cached_minimum_size_dirty = true; } bool TreeItem::is_checked(int p_column) const { @@ -214,8 +221,10 @@ void TreeItem::set_text(int p_column, String p_text) { } cells.write[p_column].step = 0; } + + cells.write[p_column].cached_minimum_size_dirty = true; + _changed_notify(p_column); - cached_minimum_size_dirty = true; } String TreeItem::get_text(int p_column) const { @@ -231,7 +240,7 @@ void TreeItem::set_text_direction(int p_column, Control::TextDirection p_text_di cells.write[p_column].dirty = true; _changed_notify(p_column); } - cached_minimum_size_dirty = true; + cells.write[p_column].cached_minimum_size_dirty = true; } Control::TextDirection TreeItem::get_text_direction(int p_column) const { @@ -241,10 +250,12 @@ Control::TextDirection TreeItem::get_text_direction(int p_column) const { void TreeItem::clear_opentype_features(int p_column) { ERR_FAIL_INDEX(p_column, cells.size()); + cells.write[p_column].opentype_features.clear(); cells.write[p_column].dirty = true; + cells.write[p_column].cached_minimum_size_dirty = true; + _changed_notify(p_column); - cached_minimum_size_dirty = true; } void TreeItem::set_opentype_feature(int p_column, const String &p_name, int p_value) { @@ -253,8 +264,9 @@ void TreeItem::set_opentype_feature(int p_column, const String &p_name, int p_va if (!cells[p_column].opentype_features.has(tag) || (int)cells[p_column].opentype_features[tag] != p_value) { cells.write[p_column].opentype_features[tag] = p_value; cells.write[p_column].dirty = true; + cells.write[p_column].cached_minimum_size_dirty = true; + _changed_notify(p_column); - cached_minimum_size_dirty = true; } } @@ -269,11 +281,13 @@ int TreeItem::get_opentype_feature(int p_column, const String &p_name) const { void TreeItem::set_structured_text_bidi_override(int p_column, Control::StructuredTextParser p_parser) { ERR_FAIL_INDEX(p_column, cells.size()); + if (cells[p_column].st_parser != p_parser) { cells.write[p_column].st_parser = p_parser; cells.write[p_column].dirty = true; + cells.write[p_column].cached_minimum_size_dirty = true; + _changed_notify(p_column); - cached_minimum_size_dirty = true; } } @@ -284,10 +298,12 @@ Control::StructuredTextParser TreeItem::get_structured_text_bidi_override(int p_ void TreeItem::set_structured_text_bidi_override_options(int p_column, Array p_args) { ERR_FAIL_INDEX(p_column, cells.size()); + cells.write[p_column].st_args = p_args; cells.write[p_column].dirty = true; + cells.write[p_column].cached_minimum_size_dirty = true; + _changed_notify(p_column); - cached_minimum_size_dirty = true; } Array TreeItem::get_structured_text_bidi_override_options(int p_column) const { @@ -297,11 +313,13 @@ Array TreeItem::get_structured_text_bidi_override_options(int p_column) const { void TreeItem::set_language(int p_column, const String &p_language) { ERR_FAIL_INDEX(p_column, cells.size()); + if (cells[p_column].language != p_language) { cells.write[p_column].language = p_language; cells.write[p_column].dirty = true; + cells.write[p_column].cached_minimum_size_dirty = true; + _changed_notify(p_column); - cached_minimum_size_dirty = true; } } @@ -312,10 +330,11 @@ String TreeItem::get_language(int p_column) const { void TreeItem::set_suffix(int p_column, String p_suffix) { ERR_FAIL_INDEX(p_column, cells.size()); + cells.write[p_column].suffix = p_suffix; + cells.write[p_column].cached_minimum_size_dirty = true; _changed_notify(p_column); - cached_minimum_size_dirty = true; } String TreeItem::get_suffix(int p_column) const { @@ -325,9 +344,11 @@ String TreeItem::get_suffix(int p_column) const { void TreeItem::set_icon(int p_column, const Ref<Texture2D> &p_icon) { ERR_FAIL_INDEX(p_column, cells.size()); + cells.write[p_column].icon = p_icon; + cells.write[p_column].cached_minimum_size_dirty = true; + _changed_notify(p_column); - cached_minimum_size_dirty = true; } Ref<Texture2D> TreeItem::get_icon(int p_column) const { @@ -337,9 +358,11 @@ Ref<Texture2D> TreeItem::get_icon(int p_column) const { void TreeItem::set_icon_region(int p_column, const Rect2 &p_icon_region) { ERR_FAIL_INDEX(p_column, cells.size()); + cells.write[p_column].icon_region = p_icon_region; + cells.write[p_column].cached_minimum_size_dirty = true; + _changed_notify(p_column); - cached_minimum_size_dirty = true; } Rect2 TreeItem::get_icon_region(int p_column) const { @@ -360,9 +383,11 @@ Color TreeItem::get_icon_modulate(int p_column) const { void TreeItem::set_icon_max_width(int p_column, int p_max) { ERR_FAIL_INDEX(p_column, cells.size()); + cells.write[p_column].icon_max_w = p_max; + cells.write[p_column].cached_minimum_size_dirty = true; + _changed_notify(p_column); - cached_minimum_size_dirty = true; } int TreeItem::get_icon_max_width(int p_column) const { @@ -474,8 +499,11 @@ void TreeItem::uncollapse_tree() { void TreeItem::set_custom_minimum_height(int p_height) { custom_min_height = p_height; + + for (Cell &c : cells) + c.cached_minimum_size_dirty = true; + _changed_notify(); - cached_minimum_size_dirty = true; } int TreeItem::get_custom_minimum_height() const { @@ -799,8 +827,9 @@ void TreeItem::add_button(int p_column, const Ref<Texture2D> &p_button, int p_id button.disabled = p_disabled; button.tooltip = p_tooltip; cells.write[p_column].buttons.push_back(button); + cells.write[p_column].cached_minimum_size_dirty = true; + _changed_notify(p_column); - cached_minimum_size_dirty = true; } int TreeItem::get_button_count(int p_column) const { @@ -843,8 +872,9 @@ void TreeItem::set_button(int p_column, int p_idx, const Ref<Texture2D> &p_butto ERR_FAIL_INDEX(p_column, cells.size()); ERR_FAIL_INDEX(p_idx, cells[p_column].buttons.size()); cells.write[p_column].buttons.write[p_idx].texture = p_button; + cells.write[p_column].cached_minimum_size_dirty = true; + _changed_notify(p_column); - cached_minimum_size_dirty = true; } void TreeItem::set_button_color(int p_column, int p_idx, const Color &p_color) { @@ -859,8 +889,9 @@ void TreeItem::set_button_disabled(int p_column, int p_idx, bool p_disabled) { ERR_FAIL_INDEX(p_idx, cells[p_column].buttons.size()); cells.write[p_column].buttons.write[p_idx].disabled = p_disabled; + cells.write[p_column].cached_minimum_size_dirty = true; + _changed_notify(p_column); - cached_minimum_size_dirty = true; } bool TreeItem::is_button_disabled(int p_column, int p_idx) const { @@ -872,9 +903,11 @@ bool TreeItem::is_button_disabled(int p_column, int p_idx) const { void TreeItem::set_editable(int p_column, bool p_editable) { ERR_FAIL_INDEX(p_column, cells.size()); + cells.write[p_column].editable = p_editable; + cells.write[p_column].cached_minimum_size_dirty = true; + _changed_notify(p_column); - cached_minimum_size_dirty = true; } bool TreeItem::is_editable(int p_column) { @@ -906,8 +939,9 @@ void TreeItem::clear_custom_color(int p_column) { void TreeItem::set_custom_font(int p_column, const Ref<Font> &p_font) { ERR_FAIL_INDEX(p_column, cells.size()); + cells.write[p_column].custom_font = p_font; - cached_minimum_size_dirty = true; + cells.write[p_column].cached_minimum_size_dirty = true; } Ref<Font> TreeItem::get_custom_font(int p_column) const { @@ -917,8 +951,9 @@ Ref<Font> TreeItem::get_custom_font(int p_column) const { void TreeItem::set_custom_font_size(int p_column, int p_font_size) { ERR_FAIL_INDEX(p_column, cells.size()); + cells.write[p_column].custom_font_size = p_font_size; - cached_minimum_size_dirty = true; + cells.write[p_column].cached_minimum_size_dirty = true; } int TreeItem::get_custom_font_size(int p_column) const { @@ -961,8 +996,9 @@ Color TreeItem::get_custom_bg_color(int p_column) const { void TreeItem::set_custom_as_button(int p_column, bool p_button) { ERR_FAIL_INDEX(p_column, cells.size()); + cells.write[p_column].custom_button = p_button; - cached_minimum_size_dirty = true; + cells.write[p_column].cached_minimum_size_dirty = true; } bool TreeItem::is_custom_set_as_button(int p_column) const { @@ -972,9 +1008,11 @@ bool TreeItem::is_custom_set_as_button(int p_column) const { void TreeItem::set_text_align(int p_column, TextAlign p_align) { ERR_FAIL_INDEX(p_column, cells.size()); + cells.write[p_column].text_align = p_align; + cells.write[p_column].cached_minimum_size_dirty = true; + _changed_notify(p_column); - cached_minimum_size_dirty = true; } TreeItem::TextAlign TreeItem::get_text_align(int p_column) const { @@ -984,9 +1022,11 @@ TreeItem::TextAlign TreeItem::get_text_align(int p_column) const { void TreeItem::set_expand_right(int p_column, bool p_enable) { ERR_FAIL_INDEX(p_column, cells.size()); + cells.write[p_column].expand_right = p_enable; + cells.write[p_column].cached_minimum_size_dirty = true; + _changed_notify(p_column); - cached_minimum_size_dirty = true; } bool TreeItem::get_expand_right(int p_column) const { @@ -996,8 +1036,11 @@ bool TreeItem::get_expand_right(int p_column) const { void TreeItem::set_disable_folding(bool p_disable) { disable_folding = p_disable; + + for (Cell &c : cells) + c.cached_minimum_size_dirty = true; + _changed_notify(0); - cached_minimum_size_dirty = true; } bool TreeItem::is_folding_disabled() const { @@ -1009,14 +1052,12 @@ Size2 TreeItem::get_minimum_size(int p_column) { Tree *tree = get_tree(); ERR_FAIL_COND_V(!tree, Size2()); - if (cached_minimum_size_dirty) { - Size2 size; + const TreeItem::Cell &cell = cells[p_column]; - // Default offset? - //size.width += (disable_folding || tree->hide_folding) ? tree->cache.hseparation : tree->cache.item_margin; + if (cell.cached_minimum_size_dirty) { + Size2 size; // Text. - const TreeItem::Cell &cell = cells[p_column]; if (!cell.text.is_empty()) { if (cell.dirty) { tree->update_item_cell(this, p_column); @@ -1052,11 +1093,11 @@ Size2 TreeItem::get_minimum_size(int p_column) { size.width += (cell.buttons.size() - 1) * tree->cache.button_margin; } - cached_minimum_size = size; - cached_minimum_size_dirty = false; + cells.write[p_column].cached_minimum_size = size; + cells.write[p_column].cached_minimum_size_dirty = false; } - return cached_minimum_size; + return cell.cached_minimum_size; } Variant TreeItem::_call_recursive_bind(const Variant **p_args, int p_argcount, Callable::CallError &r_error) { @@ -1337,10 +1378,6 @@ void Tree::update_cache() { cache.title_button_color = get_theme_color(SNAME("title_button_color")); v_scroll->set_custom_step(cache.font->get_height(cache.font_size)); - - for (TreeItem *item = get_root(); item; item = item->get_next()) { - item->cached_minimum_size_dirty = true; - } } int Tree::compute_item_height(TreeItem *p_item) const { @@ -1712,7 +1749,7 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 } if ((select_mode == SELECT_ROW && selected_item == p_item) || p_item->cells[i].selected || !p_item->has_meta("__focus_rect")) { - Rect2i r(cell_rect.position, cell_rect.size); + Rect2i r = cell_rect; p_item->set_meta("__focus_rect", Rect2(r.position, r.size)); @@ -1968,7 +2005,8 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 arrow = cache.arrow; } - Point2 apos = p_pos + p_draw_ofs + Point2i(0, (label_h - arrow->get_height()) / 2) - cache.offset; + Point2 apos = p_pos + Point2i(0, (label_h - arrow->get_height()) / 2) - cache.offset + p_draw_ofs; + apos.x += cache.item_margin - arrow->get_width(); if (rtl) { apos.x = get_size().width - apos.x - arrow->get_width(); @@ -4000,10 +4038,12 @@ TreeItem *Tree::get_next_selected(TreeItem *p_item) { int Tree::get_column_minimum_width(int p_column) const { ERR_FAIL_INDEX_V(p_column, columns.size(), -1); + // Use the custom minimum width. int min_width = columns[p_column].custom_min_width; + // Check if the visible title of the column is wider. if (show_column_titles) { - min_width = MAX(cache.font->get_string_size(columns[p_column].title).width, min_width); + min_width = MAX(cache.font->get_string_size(columns[p_column].title).width + cache.bg->get_margin(SIDE_LEFT) + cache.bg->get_margin(SIDE_RIGHT), min_width); } if (!columns[p_column].clip_content) { @@ -4028,7 +4068,11 @@ int Tree::get_column_minimum_width(int p_column) const { Size2 item_size = item->get_minimum_size(p_column); if (p_column == 0) { item_size.width += cache.item_margin * depth; + } else { + item_size.width += cache.hseparation; } + + // Check if the item is wider. min_width = MAX(min_width, item_size.width); } } @@ -4068,9 +4112,6 @@ int Tree::get_column_width(int p_column) const { } } - if (p_column < columns.size() - 1) { - column_width += cache.hseparation; - } return column_width; } diff --git a/scene/gui/tree.h b/scene/gui/tree.h index 85fed941dc..c4a6b6b058 100644 --- a/scene/gui/tree.h +++ b/scene/gui/tree.h @@ -95,6 +95,9 @@ private: bool expand_right = false; Color icon_color = Color(1, 1, 1); + Size2i cached_minimum_size; + bool cached_minimum_size_dirty = true; + TextAlign text_align = ALIGN_LEFT; Variant meta; @@ -130,9 +133,6 @@ private: bool disable_folding = false; int custom_min_height = 0; - Size2i cached_minimum_size; - bool cached_minimum_size_dirty = true; - TreeItem *parent = nullptr; // parent item TreeItem *prev = nullptr; // previous in list TreeItem *next = nullptr; // next in list diff --git a/scene/main/node.cpp b/scene/main/node.cpp index 05409b7bfe..f5d2d2f2a2 100644 --- a/scene/main/node.cpp +++ b/scene/main/node.cpp @@ -70,7 +70,9 @@ void Node::_notification(int p_notification) { if (data.parent) { data.process_owner = data.parent->data.process_owner; } else { - data.process_owner = nullptr; + ERR_PRINT("The root node can't be set to Inherit process mode, reverting to Pausable instead."); + data.process_mode = PROCESS_MODE_PAUSABLE; + data.process_owner = this; } } else { data.process_owner = this; @@ -436,18 +438,18 @@ void Node::set_process_mode(ProcessMode p_mode) { bool prev_can_process = can_process(); bool prev_enabled = _is_enabled(); - data.process_mode = p_mode; - - if (data.process_mode == PROCESS_MODE_INHERIT) { + if (p_mode == PROCESS_MODE_INHERIT) { if (data.parent) { - data.process_owner = data.parent->data.owner; + data.process_owner = data.parent->data.process_owner; } else { - data.process_owner = nullptr; + ERR_FAIL_MSG("The root node can't be set to Inherit process mode."); } } else { data.process_owner = this; } + data.process_mode = p_mode; + bool next_can_process = can_process(); bool next_enabled = _is_enabled(); @@ -702,6 +704,9 @@ bool Node::_can_process(bool p_paused) const { process_mode = data.process_mode; } + // The owner can't be set to inherit, must be a bug. + ERR_FAIL_COND_V(process_mode == PROCESS_MODE_INHERIT, false); + if (process_mode == PROCESS_MODE_DISABLED) { return false; } else if (process_mode == PROCESS_MODE_ALWAYS) { diff --git a/scene/main/scene_tree.cpp b/scene/main/scene_tree.cpp index e1b1b356a9..6e78193717 100644 --- a/scene/main/scene_tree.cpp +++ b/scene/main/scene_tree.cpp @@ -1322,6 +1322,7 @@ SceneTree::SceneTree() { // Create with mainloop. root = memnew(Window); + root->set_process_mode(Node::PROCESS_MODE_PAUSABLE); root->set_name("root"); #ifndef _3D_DISABLED if (!root->get_world_3d().is_valid()) { diff --git a/scene/main/viewport.cpp b/scene/main/viewport.cpp index fb86d37280..b3b743370b 100644 --- a/scene/main/viewport.cpp +++ b/scene/main/viewport.cpp @@ -35,13 +35,13 @@ #include "core/object/message_queue.h" #include "core/string/translation.h" #include "core/templates/pair.h" +#include "scene/2d/audio_listener_2d.h" #include "scene/2d/camera_2d.h" #include "scene/2d/collision_object_2d.h" -#include "scene/2d/listener_2d.h" #ifndef _3D_DISABLED +#include "scene/3d/audio_listener_3d.h" #include "scene/3d/camera_3d.h" #include "scene/3d/collision_object_3d.h" -#include "scene/3d/listener_3d.h" #include "scene/3d/world_environment.h" #endif // _3D_DISABLED #include "scene/gui/control.h" @@ -381,10 +381,10 @@ void Viewport::_notification(int p_what) { current_canvas = find_world_2d()->get_canvas(); RenderingServer::get_singleton()->viewport_attach_canvas(viewport, current_canvas); - _update_listener_2d(); + _update_audio_listener_2d(); #ifndef _3D_DISABLED RenderingServer::get_singleton()->viewport_set_scenario(viewport, find_world_3d()->get_scenario()); - _update_listener_3d(); + _update_audio_listener_3d(); #endif // _3D_DISABLED add_to_group("_viewports"); @@ -408,9 +408,9 @@ void Viewport::_notification(int p_what) { } break; case NOTIFICATION_READY: { #ifndef _3D_DISABLED - if (listener_3d_set.size() && !listener_3d) { - Listener3D *first = nullptr; - for (Set<Listener3D *>::Element *E = listener_3d_set.front(); E; E = E->next()) { + if (audio_listener_3d_set.size() && !audio_listener_3d) { + AudioListener3D *first = nullptr; + for (Set<AudioListener3D *>::Element *E = audio_listener_3d_set.front(); E; E = E->next()) { if (first == nullptr || first->is_greater_than(E->get())) { first = E->get(); } @@ -822,27 +822,27 @@ Rect2 Viewport::get_visible_rect() const { return r; } -void Viewport::_update_listener_2d() { +void Viewport::_update_audio_listener_2d() { if (AudioServer::get_singleton()) { AudioServer::get_singleton()->notify_listener_changed(); } } void Viewport::set_as_audio_listener_2d(bool p_enable) { - if (p_enable == audio_listener_2d) { + if (p_enable == is_audio_listener_2d_enabled) { return; } - audio_listener_2d = p_enable; - _update_listener_2d(); + is_audio_listener_2d_enabled = p_enable; + _update_audio_listener_2d(); } bool Viewport::is_audio_listener_2d() const { - return audio_listener_2d; + return is_audio_listener_2d_enabled; } -Listener2D *Viewport::get_listener_2d() const { - return listener_2d; +AudioListener2D *Viewport::get_audio_listener_2d() const { + return audio_listener_2d; } void Viewport::enable_canvas_transform_override(bool p_enable) { @@ -909,18 +909,18 @@ void Viewport::_camera_2d_set(Camera2D *p_camera_2d) { camera_2d = p_camera_2d; } -void Viewport::_listener_2d_set(Listener2D *p_listener) { - if (listener_2d == p_listener) { +void Viewport::_audio_listener_2d_set(AudioListener2D *p_listener) { + if (audio_listener_2d == p_listener) { return; - } else if (listener_2d) { - listener_2d->clear_current(); + } else if (audio_listener_2d) { + audio_listener_2d->clear_current(); } - listener_2d = p_listener; + audio_listener_2d = p_listener; } -void Viewport::_listener_2d_remove(Listener2D *p_listener) { - if (listener_2d == p_listener) { - listener_2d = nullptr; +void Viewport::_audio_listener_2d_remove(AudioListener2D *p_listener) { + if (audio_listener_2d == p_listener) { + audio_listener_2d = nullptr; } } @@ -962,7 +962,7 @@ void Viewport::set_world_2d(const Ref<World2D> &p_world_2d) { world_2d = Ref<World2D>(memnew(World2D)); } - _update_listener_2d(); + _update_audio_listener_2d(); if (is_inside_tree()) { current_canvas = find_world_2d()->get_canvas(); @@ -1972,35 +1972,30 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { Control *from = gui.key_focus ? gui.key_focus : nullptr; - // Keyboard focus. - Ref<InputEventKey> k = p_event; - // Need to check for mods, otherwise any combination of alt/ctrl/shift+<up/down/left/right/etc> is handled here when it shouldn't be. - bool mods = k.is_valid() && (k->is_ctrl_pressed() || k->is_alt_pressed() || k->is_shift_pressed() || k->is_meta_pressed()); - if (from && p_event->is_pressed()) { Control *next = nullptr; - if (p_event->is_action_pressed("ui_focus_next", true)) { + if (p_event->is_action_pressed("ui_focus_next", true, true)) { next = from->find_next_valid_focus(); } - if (p_event->is_action_pressed("ui_focus_prev", true)) { + if (p_event->is_action_pressed("ui_focus_prev", true, true)) { next = from->find_prev_valid_focus(); } - if (!mods && p_event->is_action_pressed("ui_up", true)) { + if (p_event->is_action_pressed("ui_up", true, true)) { next = from->_get_focus_neighbor(SIDE_TOP); } - if (!mods && p_event->is_action_pressed("ui_left", true)) { + if (p_event->is_action_pressed("ui_left", true, true)) { next = from->_get_focus_neighbor(SIDE_LEFT); } - if (!mods && p_event->is_action_pressed("ui_right", true)) { + if (p_event->is_action_pressed("ui_right", true, true)) { next = from->_get_focus_neighbor(SIDE_RIGHT); } - if (!mods && p_event->is_action_pressed("ui_down", true)) { + if (p_event->is_action_pressed("ui_down", true, true)) { next = from->_get_focus_neighbor(SIDE_BOTTOM); } @@ -3047,24 +3042,24 @@ Viewport::SDFScale Viewport::get_sdf_scale() const { } #ifndef _3D_DISABLED -Listener3D *Viewport::get_listener_3d() const { - return listener_3d; +AudioListener3D *Viewport::get_audio_listener_3d() const { + return audio_listener_3d; } void Viewport::set_as_audio_listener_3d(bool p_enable) { - if (p_enable == audio_listener_3d) { + if (p_enable == is_audio_listener_3d_enabled) { return; } - audio_listener_3d = p_enable; - _update_listener_3d(); + is_audio_listener_3d_enabled = p_enable; + _update_audio_listener_3d(); } bool Viewport::is_audio_listener_3d() const { - return audio_listener_3d; + return is_audio_listener_3d_enabled; } -void Viewport::_update_listener_3d() { +void Viewport::_update_audio_listener_3d() { if (AudioServer::get_singleton()) { AudioServer::get_singleton()->notify_listener_changed(); } @@ -3073,39 +3068,39 @@ void Viewport::_update_listener_3d() { void Viewport::_listener_transform_3d_changed_notify() { } -void Viewport::_listener_3d_set(Listener3D *p_listener) { - if (listener_3d == p_listener) { +void Viewport::_audio_listener_3d_set(AudioListener3D *p_listener) { + if (audio_listener_3d == p_listener) { return; } - listener_3d = p_listener; + audio_listener_3d = p_listener; - _update_listener_3d(); + _update_audio_listener_3d(); _listener_transform_3d_changed_notify(); } -bool Viewport::_listener_3d_add(Listener3D *p_listener) { - listener_3d_set.insert(p_listener); - return listener_3d_set.size() == 1; +bool Viewport::_audio_listener_3d_add(AudioListener3D *p_listener) { + audio_listener_3d_set.insert(p_listener); + return audio_listener_3d_set.size() == 1; } -void Viewport::_listener_3d_remove(Listener3D *p_listener) { - listener_3d_set.erase(p_listener); - if (listener_3d == p_listener) { - listener_3d = nullptr; +void Viewport::_audio_listener_3d_remove(AudioListener3D *p_listener) { + audio_listener_3d_set.erase(p_listener); + if (audio_listener_3d == p_listener) { + audio_listener_3d = nullptr; } } -void Viewport::_listener_3d_make_next_current(Listener3D *p_exclude) { - if (listener_3d_set.size() > 0) { - for (Set<Listener3D *>::Element *E = listener_3d_set.front(); E; E = E->next()) { +void Viewport::_audio_listener_3d_make_next_current(AudioListener3D *p_exclude) { + if (audio_listener_3d_set.size() > 0) { + for (Set<AudioListener3D *>::Element *E = audio_listener_3d_set.front(); E; E = E->next()) { if (p_exclude == E->get()) { continue; } if (!E->get()->is_inside_tree()) { continue; } - if (listener_3d != nullptr) { + if (audio_listener_3d != nullptr) { return; } @@ -3114,7 +3109,7 @@ void Viewport::_listener_3d_make_next_current(Listener3D *p_exclude) { } else { // Attempt to reset listener to the camera position. if (camera_3d != nullptr) { - _update_listener_3d(); + _update_audio_listener_3d(); _camera_3d_transform_changed_notify(); } } @@ -3168,7 +3163,7 @@ void Viewport::_camera_3d_set(Camera3D *p_camera) { camera_3d->notification(Camera3D::NOTIFICATION_BECAME_CURRENT); } - _update_listener_3d(); + _update_audio_listener_3d(); _camera_3d_transform_changed_notify(); } @@ -3330,7 +3325,7 @@ void Viewport::set_world_3d(const Ref<World3D> &p_world_3d) { RenderingServer::get_singleton()->viewport_set_scenario(viewport, find_world_3d()->get_scenario()); } - _update_listener_3d(); + _update_audio_listener_3d(); } void Viewport::_own_world_3d_changed() { @@ -3351,7 +3346,7 @@ void Viewport::_own_world_3d_changed() { RenderingServer::get_singleton()->viewport_set_scenario(viewport, find_world_3d()->get_scenario()); } - _update_listener_3d(); + _update_audio_listener_3d(); } void Viewport::set_use_own_world_3d(bool p_world_3d) { @@ -3385,7 +3380,7 @@ void Viewport::set_use_own_world_3d(bool p_world_3d) { RenderingServer::get_singleton()->viewport_set_scenario(viewport, find_world_3d()->get_scenario()); } - _update_listener_3d(); + _update_audio_listener_3d(); } bool Viewport::is_using_own_world_3d() const { diff --git a/scene/main/viewport.h b/scene/main/viewport.h index bfb52c4b98..3732f9cfd1 100644 --- a/scene/main/viewport.h +++ b/scene/main/viewport.h @@ -37,11 +37,11 @@ #ifndef _3D_DISABLED class Camera3D; class CollisionObject3D; -class Listener3D; +class AudioListener3D; class World3D; #endif // _3D_DISABLED -class Listener2D; +class AudioListener2D; class Camera2D; class CanvasItem; class CanvasLayer; @@ -202,7 +202,7 @@ private: Viewport *parent = nullptr; - Listener2D *listener_2d = nullptr; + AudioListener2D *audio_listener_2d = nullptr; Camera2D *camera_2d = nullptr; Set<CanvasLayer *> canvas_layers; @@ -210,8 +210,8 @@ private: RID current_canvas; RID subwindow_canvas; - bool audio_listener_2d = false; - RID internal_listener_2d; + bool is_audio_listener_2d_enabled = false; + RID internal_audio_listener_2d; bool override_canvas_transform = false; @@ -274,7 +274,7 @@ private: StringName unhandled_input_group; StringName unhandled_key_input_group; - void _update_listener_2d(); + void _update_audio_listener_2d(); bool disable_3d = false; @@ -418,9 +418,9 @@ private: bool _gui_drop(Control *p_at_control, Point2 p_at_pos, bool p_just_check); - friend class Listener2D; - void _listener_2d_set(Listener2D *p_listener); - void _listener_2d_remove(Listener2D *p_listener); + friend class AudioListener2D; + void _audio_listener_2d_set(AudioListener2D *p_listener); + void _audio_listener_2d_remove(AudioListener2D *p_listener); friend class Camera2D; void _camera_2d_set(Camera2D *p_camera_2d); @@ -463,7 +463,7 @@ protected: public: uint64_t get_processed_events_count() const { return event_count; } - Listener2D *get_listener_2d() const; + AudioListener2D *get_audio_listener_2d() const; Camera2D *get_camera_2d() const; void set_as_audio_listener_2d(bool p_enable); bool is_audio_listener_2d() const; @@ -593,20 +593,20 @@ public: #ifndef _3D_DISABLED bool use_xr = false; Scale3D scale_3d = SCALE_3D_DISABLED; - friend class Listener3D; - Listener3D *listener_3d = nullptr; - Set<Listener3D *> listener_3d_set; - bool audio_listener_3d = false; - RID internal_listener_3d; - Listener3D *get_listener_3d() const; + friend class AudioListener3D; + AudioListener3D *audio_listener_3d = nullptr; + Set<AudioListener3D *> audio_listener_3d_set; + bool is_audio_listener_3d_enabled = false; + RID internal_audio_listener_3d; + AudioListener3D *get_audio_listener_3d() const; void set_as_audio_listener_3d(bool p_enable); bool is_audio_listener_3d() const; - void _update_listener_3d(); + void _update_audio_listener_3d(); void _listener_transform_3d_changed_notify(); - void _listener_3d_set(Listener3D *p_listener); - bool _listener_3d_add(Listener3D *p_listener); //true if first - void _listener_3d_remove(Listener3D *p_listener); - void _listener_3d_make_next_current(Listener3D *p_exclude); + void _audio_listener_3d_set(AudioListener3D *p_listener); + bool _audio_listener_3d_add(AudioListener3D *p_listener); //true if first + void _audio_listener_3d_remove(AudioListener3D *p_listener); + void _audio_listener_3d_make_next_current(AudioListener3D *p_exclude); void _collision_object_3d_input_event(CollisionObject3D *p_object, Camera3D *p_camera, const Ref<InputEvent> &p_input_event, const Vector3 &p_pos, const Vector3 &p_normal, int p_shape); diff --git a/scene/register_scene_types.cpp b/scene/register_scene_types.cpp index db4d3eea20..1e89b17044 100644 --- a/scene/register_scene_types.cpp +++ b/scene/register_scene_types.cpp @@ -36,6 +36,7 @@ #include "core/os/os.h" #include "scene/2d/animated_sprite_2d.h" #include "scene/2d/area_2d.h" +#include "scene/2d/audio_listener_2d.h" #include "scene/2d/audio_stream_player_2d.h" #include "scene/2d/back_buffer_copy.h" #include "scene/2d/camera_2d.h" @@ -49,7 +50,6 @@ #include "scene/2d/light_2d.h" #include "scene/2d/light_occluder_2d.h" #include "scene/2d/line_2d.h" -#include "scene/2d/listener_2d.h" #include "scene/2d/mesh_instance_2d.h" #include "scene/2d/multimesh_instance_2d.h" #include "scene/2d/navigation_agent_2d.h" @@ -205,6 +205,7 @@ #ifndef _3D_DISABLED #include "scene/3d/area_3d.h" +#include "scene/3d/audio_listener_3d.h" #include "scene/3d/audio_stream_player_3d.h" #include "scene/3d/bone_attachment_3d.h" #include "scene/3d/camera_3d.h" @@ -217,7 +218,6 @@ #include "scene/3d/light_3d.h" #include "scene/3d/lightmap_gi.h" #include "scene/3d/lightmap_probe.h" -#include "scene/3d/listener_3d.h" #include "scene/3d/mesh_instance_3d.h" #include "scene/3d/multimesh_instance_3d.h" #include "scene/3d/navigation_agent_3d.h" @@ -445,7 +445,7 @@ void register_scene_types() { GDREGISTER_VIRTUAL_CLASS(GeometryInstance3D); GDREGISTER_CLASS(Camera3D); GDREGISTER_CLASS(ClippedCamera3D); - GDREGISTER_CLASS(Listener3D); + GDREGISTER_CLASS(AudioListener3D); GDREGISTER_CLASS(XRCamera3D); GDREGISTER_CLASS(XRController3D); GDREGISTER_CLASS(XRAnchor3D); @@ -672,7 +672,7 @@ void register_scene_types() { OS::get_singleton()->yield(); //may take time to init GDREGISTER_CLASS(Camera2D); - GDREGISTER_CLASS(Listener2D); + GDREGISTER_CLASS(AudioListener2D); GDREGISTER_VIRTUAL_CLASS(Joint2D); GDREGISTER_CLASS(PinJoint2D); GDREGISTER_CLASS(GrooveJoint2D); @@ -920,7 +920,7 @@ void register_scene_types() { ClassDB::add_compatibility_class("KinematicCollision", "KinematicCollision3D"); ClassDB::add_compatibility_class("Light", "Light3D"); ClassDB::add_compatibility_class("LineShape2D", "WorldBoundaryShape2D"); - ClassDB::add_compatibility_class("Listener", "Listener3D"); + ClassDB::add_compatibility_class("Listener", "AudioListener3D"); ClassDB::add_compatibility_class("MeshInstance", "MeshInstance3D"); ClassDB::add_compatibility_class("MultiMeshInstance", "MultiMeshInstance3D"); ClassDB::add_compatibility_class("NavigationAgent", "NavigationAgent3D"); diff --git a/scene/resources/bit_map.cpp b/scene/resources/bit_map.cpp index de557494c3..49ed9dcb82 100644 --- a/scene/resources/bit_map.cpp +++ b/scene/resources/bit_map.cpp @@ -48,7 +48,7 @@ void BitMap::create_from_image_alpha(const Ref<Image> &p_image, float p_threshol img->convert(Image::FORMAT_LA8); ERR_FAIL_COND(img->get_format() != Image::FORMAT_LA8); - create(Size2(img->get_width(), img->get_height())); + create(img->get_size()); const uint8_t *r = img->get_data().ptr(); uint8_t *w = bitmask.ptrw(); diff --git a/scene/resources/canvas_item_material.cpp b/scene/resources/canvas_item_material.cpp index 7501efea9e..fa95ab0e79 100644 --- a/scene/resources/canvas_item_material.cpp +++ b/scene/resources/canvas_item_material.cpp @@ -161,7 +161,7 @@ void CanvasItemMaterial::flush_changes() { void CanvasItemMaterial::_queue_shader_change() { MutexLock lock(material_mutex); - if (!element.in_list()) { + if (is_initialized && !element.in_list()) { dirty_materials->add(&element); } } @@ -287,6 +287,7 @@ CanvasItemMaterial::CanvasItemMaterial() : set_particles_anim_loop(false); current_key.invalid_key = 1; + is_initialized = true; _queue_shader_change(); } diff --git a/scene/resources/canvas_item_material.h b/scene/resources/canvas_item_material.h index 0a813e0ae5..37cd4de136 100644 --- a/scene/resources/canvas_item_material.h +++ b/scene/resources/canvas_item_material.h @@ -102,6 +102,7 @@ private: _FORCE_INLINE_ void _queue_shader_change(); _FORCE_INLINE_ bool _is_shader_dirty() const; + bool is_initialized = false; BlendMode blend_mode = BLEND_MODE_MIX; LightMode light_mode = LIGHT_MODE_NORMAL; bool particles_animation = false; diff --git a/scene/resources/default_theme/default_theme.cpp b/scene/resources/default_theme/default_theme.cpp index fa3824e6eb..492e0512e4 100644 --- a/scene/resources/default_theme/default_theme.cpp +++ b/scene/resources/default_theme/default_theme.cpp @@ -51,7 +51,7 @@ static Ref<StyleBoxTexture> make_stylebox(T p_src, float p_left, float p_top, fl } else { texture = Ref<ImageTexture>(memnew(ImageTexture)); Ref<Image> img = memnew(Image(p_src)); - const Size2 orig_size = Size2(img->get_width(), img->get_height()); + const Size2 orig_size = img->get_size(); img->convert(Image::FORMAT_RGBA8); img->resize(orig_size.x * scale, orig_size.y * scale); @@ -97,7 +97,7 @@ template <class T> static Ref<Texture2D> make_icon(T p_src) { Ref<ImageTexture> texture(memnew(ImageTexture)); Ref<Image> img = memnew(Image(p_src)); - const Size2 orig_size = Size2(img->get_width(), img->get_height()); + const Size2 orig_size = img->get_size(); img->convert(Image::FORMAT_RGBA8); img->resize(orig_size.x * scale, orig_size.y * scale); texture->create_from_image(img); @@ -438,6 +438,8 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_color("caret_color", "TextEdit", control_font_color); theme->set_color("caret_background_color", "TextEdit", Color(0, 0, 0)); theme->set_color("word_highlighted_color", "TextEdit", Color(0.8, 0.9, 0.9, 0.15)); + theme->set_color("search_result_color", "TextEdit", Color(0.3, 0.3, 0.3)); + theme->set_color("search_result_border_color", "TextEdit", Color(0.3, 0.3, 0.3, 0.4)); theme->set_constant("line_spacing", "TextEdit", 4 * scale); theme->set_constant("outline_size", "TextEdit", 0); @@ -483,6 +485,8 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_color("line_number_color", "CodeEdit", Color(0.67, 0.67, 0.67, 0.4)); theme->set_color("word_highlighted_color", "CodeEdit", Color(0.8, 0.9, 0.9, 0.15)); theme->set_color("line_length_guideline_color", "CodeEdit", Color(0.3, 0.5, 0.8, 0.1)); + theme->set_color("search_result_color", "CodeEdit", Color(0.3, 0.3, 0.3)); + theme->set_color("search_result_border_color", "CodeEdit", Color(0.3, 0.3, 0.3, 0.4)); theme->set_constant("completion_lines", "CodeEdit", 7); theme->set_constant("completion_max_width", "CodeEdit", 50); diff --git a/scene/resources/font.cpp b/scene/resources/font.cpp index 7af8e900fd..9b403a18f0 100644 --- a/scene/resources/font.cpp +++ b/scene/resources/font.cpp @@ -689,227 +689,272 @@ void FontData::remove_cache(int p_cache_index) { } Array FontData::get_size_cache_list(int p_cache_index) const { + ERR_FAIL_COND_V(p_cache_index < 0, Array()); _ensure_rid(p_cache_index); return TS->font_get_size_cache_list(cache[p_cache_index]); } void FontData::clear_size_cache(int p_cache_index) { + ERR_FAIL_COND(p_cache_index < 0); _ensure_rid(p_cache_index); TS->font_clear_size_cache(cache[p_cache_index]); } void FontData::remove_size_cache(int p_cache_index, const Vector2i &p_size) { + ERR_FAIL_COND(p_cache_index < 0); _ensure_rid(p_cache_index); TS->font_remove_size_cache(cache[p_cache_index], p_size); } void FontData::set_variation_coordinates(int p_cache_index, const Dictionary &p_variation_coordinates) { + ERR_FAIL_COND(p_cache_index < 0); _ensure_rid(p_cache_index); TS->font_set_variation_coordinates(cache[p_cache_index], p_variation_coordinates); emit_changed(); } Dictionary FontData::get_variation_coordinates(int p_cache_index) const { + ERR_FAIL_COND_V(p_cache_index < 0, Dictionary()); _ensure_rid(p_cache_index); return TS->font_get_variation_coordinates(cache[p_cache_index]); } void FontData::set_ascent(int p_cache_index, int p_size, real_t p_ascent) { + ERR_FAIL_COND(p_cache_index < 0); _ensure_rid(p_cache_index); TS->font_set_ascent(cache[p_cache_index], p_size, p_ascent); } real_t FontData::get_ascent(int p_cache_index, int p_size) const { + ERR_FAIL_COND_V(p_cache_index < 0, 0.f); _ensure_rid(p_cache_index); return TS->font_get_ascent(cache[p_cache_index], p_size); } void FontData::set_descent(int p_cache_index, int p_size, real_t p_descent) { + ERR_FAIL_COND(p_cache_index < 0); _ensure_rid(p_cache_index); TS->font_set_descent(cache[p_cache_index], p_size, p_descent); } real_t FontData::get_descent(int p_cache_index, int p_size) const { + ERR_FAIL_COND_V(p_cache_index < 0, 0.f); _ensure_rid(p_cache_index); return TS->font_get_descent(cache[p_cache_index], p_size); } void FontData::set_underline_position(int p_cache_index, int p_size, real_t p_underline_position) { + ERR_FAIL_COND(p_cache_index < 0); _ensure_rid(p_cache_index); TS->font_set_underline_position(cache[p_cache_index], p_size, p_underline_position); } real_t FontData::get_underline_position(int p_cache_index, int p_size) const { + ERR_FAIL_COND_V(p_cache_index < 0, 0.f); _ensure_rid(p_cache_index); return TS->font_get_underline_position(cache[p_cache_index], p_size); } void FontData::set_underline_thickness(int p_cache_index, int p_size, real_t p_underline_thickness) { + ERR_FAIL_COND(p_cache_index < 0); _ensure_rid(p_cache_index); TS->font_set_underline_thickness(cache[p_cache_index], p_size, p_underline_thickness); } real_t FontData::get_underline_thickness(int p_cache_index, int p_size) const { + ERR_FAIL_COND_V(p_cache_index < 0, 0.f); _ensure_rid(p_cache_index); return TS->font_get_underline_thickness(cache[p_cache_index], p_size); } void FontData::set_scale(int p_cache_index, int p_size, real_t p_scale) { + ERR_FAIL_COND(p_cache_index < 0); _ensure_rid(p_cache_index); TS->font_set_scale(cache[p_cache_index], p_size, p_scale); } real_t FontData::get_scale(int p_cache_index, int p_size) const { + ERR_FAIL_COND_V(p_cache_index < 0, 0.f); _ensure_rid(p_cache_index); return TS->font_get_scale(cache[p_cache_index], p_size); } void FontData::set_spacing(int p_cache_index, int p_size, TextServer::SpacingType p_spacing, int p_value) { + ERR_FAIL_COND(p_cache_index < 0); _ensure_rid(p_cache_index); TS->font_set_spacing(cache[p_cache_index], p_size, p_spacing, p_value); } int FontData::get_spacing(int p_cache_index, int p_size, TextServer::SpacingType p_spacing) const { + ERR_FAIL_COND_V(p_cache_index < 0, 0); _ensure_rid(p_cache_index); return TS->font_get_spacing(cache[p_cache_index], p_size, p_spacing); } int FontData::get_texture_count(int p_cache_index, const Vector2i &p_size) const { + ERR_FAIL_COND_V(p_cache_index < 0, 0); _ensure_rid(p_cache_index); return TS->font_get_texture_count(cache[p_cache_index], p_size); } void FontData::clear_textures(int p_cache_index, const Vector2i &p_size) { + ERR_FAIL_COND(p_cache_index < 0); _ensure_rid(p_cache_index); TS->font_clear_textures(cache[p_cache_index], p_size); } void FontData::remove_texture(int p_cache_index, const Vector2i &p_size, int p_texture_index) { + ERR_FAIL_COND(p_cache_index < 0); _ensure_rid(p_cache_index); TS->font_remove_texture(cache[p_cache_index], p_size, p_texture_index); } void FontData::set_texture_image(int p_cache_index, const Vector2i &p_size, int p_texture_index, const Ref<Image> &p_image) { + ERR_FAIL_COND(p_cache_index < 0); _ensure_rid(p_cache_index); TS->font_set_texture_image(cache[p_cache_index], p_size, p_texture_index, p_image); } Ref<Image> FontData::get_texture_image(int p_cache_index, const Vector2i &p_size, int p_texture_index) const { + ERR_FAIL_COND_V(p_cache_index < 0, Ref<Image>()); _ensure_rid(p_cache_index); return TS->font_get_texture_image(cache[p_cache_index], p_size, p_texture_index); } void FontData::set_texture_offsets(int p_cache_index, const Vector2i &p_size, int p_texture_index, const PackedInt32Array &p_offset) { + ERR_FAIL_COND(p_cache_index < 0); _ensure_rid(p_cache_index); TS->font_set_texture_offsets(cache[p_cache_index], p_size, p_texture_index, p_offset); } PackedInt32Array FontData::get_texture_offsets(int p_cache_index, const Vector2i &p_size, int p_texture_index) const { + ERR_FAIL_COND_V(p_cache_index < 0, PackedInt32Array()); _ensure_rid(p_cache_index); return TS->font_get_texture_offsets(cache[p_cache_index], p_size, p_texture_index); } Array FontData::get_glyph_list(int p_cache_index, const Vector2i &p_size) const { + ERR_FAIL_COND_V(p_cache_index < 0, Array()); _ensure_rid(p_cache_index); return TS->font_get_glyph_list(cache[p_cache_index], p_size); } void FontData::clear_glyphs(int p_cache_index, const Vector2i &p_size) { + ERR_FAIL_COND(p_cache_index < 0); _ensure_rid(p_cache_index); TS->font_clear_glyphs(cache[p_cache_index], p_size); } void FontData::remove_glyph(int p_cache_index, const Vector2i &p_size, int32_t p_glyph) { + ERR_FAIL_COND(p_cache_index < 0); _ensure_rid(p_cache_index); TS->font_remove_glyph(cache[p_cache_index], p_size, p_glyph); } void FontData::set_glyph_advance(int p_cache_index, int p_size, int32_t p_glyph, const Vector2 &p_advance) { + ERR_FAIL_COND(p_cache_index < 0); _ensure_rid(p_cache_index); TS->font_set_glyph_advance(cache[p_cache_index], p_size, p_glyph, p_advance); } Vector2 FontData::get_glyph_advance(int p_cache_index, int p_size, int32_t p_glyph) const { + ERR_FAIL_COND_V(p_cache_index < 0, Vector2()); _ensure_rid(p_cache_index); return TS->font_get_glyph_advance(cache[p_cache_index], p_size, p_glyph); } void FontData::set_glyph_offset(int p_cache_index, const Vector2i &p_size, int32_t p_glyph, const Vector2 &p_offset) { + ERR_FAIL_COND(p_cache_index < 0); _ensure_rid(p_cache_index); TS->font_set_glyph_offset(cache[p_cache_index], p_size, p_glyph, p_offset); } Vector2 FontData::get_glyph_offset(int p_cache_index, const Vector2i &p_size, int32_t p_glyph) const { + ERR_FAIL_COND_V(p_cache_index < 0, Vector2()); _ensure_rid(p_cache_index); return TS->font_get_glyph_offset(cache[p_cache_index], p_size, p_glyph); } void FontData::set_glyph_size(int p_cache_index, const Vector2i &p_size, int32_t p_glyph, const Vector2 &p_gl_size) { + ERR_FAIL_COND(p_cache_index < 0); _ensure_rid(p_cache_index); TS->font_set_glyph_size(cache[p_cache_index], p_size, p_glyph, p_gl_size); } Vector2 FontData::get_glyph_size(int p_cache_index, const Vector2i &p_size, int32_t p_glyph) const { + ERR_FAIL_COND_V(p_cache_index < 0, Vector2()); _ensure_rid(p_cache_index); return TS->font_get_glyph_size(cache[p_cache_index], p_size, p_glyph); } void FontData::set_glyph_uv_rect(int p_cache_index, const Vector2i &p_size, int32_t p_glyph, const Rect2 &p_uv_rect) { + ERR_FAIL_COND(p_cache_index < 0); _ensure_rid(p_cache_index); TS->font_set_glyph_uv_rect(cache[p_cache_index], p_size, p_glyph, p_uv_rect); } Rect2 FontData::get_glyph_uv_rect(int p_cache_index, const Vector2i &p_size, int32_t p_glyph) const { + ERR_FAIL_COND_V(p_cache_index < 0, Rect2()); _ensure_rid(p_cache_index); return TS->font_get_glyph_uv_rect(cache[p_cache_index], p_size, p_glyph); } void FontData::set_glyph_texture_idx(int p_cache_index, const Vector2i &p_size, int32_t p_glyph, int p_texture_idx) { + ERR_FAIL_COND(p_cache_index < 0); _ensure_rid(p_cache_index); TS->font_set_glyph_texture_idx(cache[p_cache_index], p_size, p_glyph, p_texture_idx); } int FontData::get_glyph_texture_idx(int p_cache_index, const Vector2i &p_size, int32_t p_glyph) const { + ERR_FAIL_COND_V(p_cache_index < 0, 0); _ensure_rid(p_cache_index); return TS->font_get_glyph_texture_idx(cache[p_cache_index], p_size, p_glyph); } Array FontData::get_kerning_list(int p_cache_index, int p_size) const { + ERR_FAIL_COND_V(p_cache_index < 0, Array()); _ensure_rid(p_cache_index); return TS->font_get_kerning_list(cache[p_cache_index], p_size); } void FontData::clear_kerning_map(int p_cache_index, int p_size) { + ERR_FAIL_COND(p_cache_index < 0); _ensure_rid(p_cache_index); TS->font_clear_kerning_map(cache[p_cache_index], p_size); } void FontData::remove_kerning(int p_cache_index, int p_size, const Vector2i &p_glyph_pair) { + ERR_FAIL_COND(p_cache_index < 0); _ensure_rid(p_cache_index); TS->font_remove_kerning(cache[p_cache_index], p_size, p_glyph_pair); } void FontData::set_kerning(int p_cache_index, int p_size, const Vector2i &p_glyph_pair, const Vector2 &p_kerning) { + ERR_FAIL_COND(p_cache_index < 0); _ensure_rid(p_cache_index); TS->font_set_kerning(cache[p_cache_index], p_size, p_glyph_pair, p_kerning); } Vector2 FontData::get_kerning(int p_cache_index, int p_size, const Vector2i &p_glyph_pair) const { + ERR_FAIL_COND_V(p_cache_index < 0, Vector2()); _ensure_rid(p_cache_index); return TS->font_get_kerning(cache[p_cache_index], p_size, p_glyph_pair); } void FontData::render_range(int p_cache_index, const Vector2i &p_size, char32_t p_start, char32_t p_end) { + ERR_FAIL_COND(p_cache_index < 0); _ensure_rid(p_cache_index); TS->font_render_range(cache[p_cache_index], p_size, p_start, p_end); } void FontData::render_glyph(int p_cache_index, const Vector2i &p_size, int32_t p_index) { + ERR_FAIL_COND(p_cache_index < 0); _ensure_rid(p_cache_index); TS->font_render_glyph(cache[p_cache_index], p_size, p_index); } RID FontData::get_cache_rid(int p_cache_index) const { + ERR_FAIL_COND_V(p_cache_index < 0, RID()); _ensure_rid(p_cache_index); return cache[p_cache_index]; } @@ -1008,10 +1053,8 @@ void Font::_data_changed() { void Font::_ensure_rid(int p_index) const { // Find or create cache record. - for (int i = 0; i < rids.size(); i++) { - if (!rids[i].is_valid() && data[i].is_valid()) { - rids.write[i] = data[i]->find_cache(variation_coordinates); - } + if (!rids[p_index].is_valid() && data[p_index].is_valid()) { + rids.write[p_index] = data[p_index]->find_cache(variation_coordinates); } } @@ -1171,6 +1214,14 @@ void Font::add_data(const Ref<FontData> &p_data) { if (data[data.size() - 1].is_valid()) { data.write[data.size() - 1]->connect(SNAME("changed"), callable_mp(this, &Font::_data_changed), varray(), CONNECT_REFERENCE_COUNTED); + Dictionary data_var_list = p_data->get_supported_variation_list(); + for (int j = 0; j < data_var_list.size(); j++) { + int32_t tag = data_var_list.get_key_at_index(j); + Vector3i value = data_var_list.get_value_at_index(j); + if (!variation_coordinates.has(tag) && !variation_coordinates.has(TS->tag_to_name(tag))) { + variation_coordinates[TS->tag_to_name(tag)] = value.z; + } + } } cache.clear(); @@ -1190,6 +1241,14 @@ void Font::set_data(int p_idx, const Ref<FontData> &p_data) { data.write[p_idx] = p_data; rids.write[p_idx] = RID(); + Dictionary data_var_list = p_data->get_supported_variation_list(); + for (int j = 0; j < data_var_list.size(); j++) { + int32_t tag = data_var_list.get_key_at_index(j); + Vector3i value = data_var_list.get_value_at_index(j); + if (!variation_coordinates.has(tag) && !variation_coordinates.has(TS->tag_to_name(tag))) { + variation_coordinates[TS->tag_to_name(tag)] = value.z; + } + } if (data[p_idx].is_valid()) { data.write[p_idx]->connect(SNAME("changed"), callable_mp(this, &Font::_data_changed), varray(), CONNECT_REFERENCE_COUNTED); diff --git a/scene/resources/material.cpp b/scene/resources/material.cpp index 77a68151c4..3a6af3afb0 100644 --- a/scene/resources/material.cpp +++ b/scene/resources/material.cpp @@ -790,12 +790,10 @@ void BaseMaterial3D::_update_shader() { } } break; case BILLBOARD_FIXED_Y: { - code += " MODELVIEW_MATRIX = INV_CAMERA_MATRIX * mat4(CAMERA_MATRIX[0],WORLD_MATRIX[1],vec4(normalize(cross(CAMERA_MATRIX[0].xyz,WORLD_MATRIX[1].xyz)), 0.0),WORLD_MATRIX[3]);\n"; + code += " MODELVIEW_MATRIX = INV_CAMERA_MATRIX * mat4(vec4(normalize(cross(vec3(0.0, 1.0, 0.0), CAMERA_MATRIX[2].xyz)),0.0),vec4(0.0, 1.0, 0.0, 0.0),vec4(normalize(cross(CAMERA_MATRIX[0].xyz, vec3(0.0, 1.0, 0.0))),0.0),WORLD_MATRIX[3]);\n"; if (flags[FLAG_BILLBOARD_KEEP_SCALE]) { - code += " MODELVIEW_MATRIX = MODELVIEW_MATRIX * mat4(vec4(length(WORLD_MATRIX[0].xyz), 0.0, 0.0, 0.0),vec4(0.0, 1.0, 0.0, 0.0),vec4(0.0, 0.0, length(WORLD_MATRIX[2].xyz), 0.0), vec4(0.0, 0.0, 0.0, 1.0));\n"; - } else { - code += " MODELVIEW_MATRIX = MODELVIEW_MATRIX * mat4(vec4(1.0, 0.0, 0.0, 0.0),vec4(0.0, 1.0/length(WORLD_MATRIX[1].xyz), 0.0, 0.0), vec4(0.0, 0.0, 1.0, 0.0),vec4(0.0, 0.0, 0.0 ,1.0));\n"; + code += " MODELVIEW_MATRIX = MODELVIEW_MATRIX * mat4(vec4(length(WORLD_MATRIX[0].xyz), 0.0, 0.0, 0.0),vec4(0.0, length(WORLD_MATRIX[1].xyz), 0.0, 0.0),vec4(0.0, 0.0, length(WORLD_MATRIX[2].xyz), 0.0),vec4(0.0, 0.0, 0.0, 1.0));\n"; } } break; case BILLBOARD_PARTICLES: { @@ -1303,7 +1301,7 @@ void BaseMaterial3D::flush_changes() { void BaseMaterial3D::_queue_shader_change() { MutexLock lock(material_mutex); - if (!element.in_list()) { + if (is_initialized && !element.in_list()) { dirty_materials->add(&element); } } @@ -2779,6 +2777,7 @@ BaseMaterial3D::BaseMaterial3D(bool p_orm) : flags[FLAG_USE_TEXTURE_REPEAT] = true; + is_initialized = true; _queue_shader_change(); } diff --git a/scene/resources/material.h b/scene/resources/material.h index e2838e1399..5d7a5324ca 100644 --- a/scene/resources/material.h +++ b/scene/resources/material.h @@ -440,6 +440,7 @@ private: _FORCE_INLINE_ void _queue_shader_change(); _FORCE_INLINE_ bool _is_shader_dirty() const; + bool is_initialized = false; bool orm; Color albedo; diff --git a/scene/resources/packed_scene.cpp b/scene/resources/packed_scene.cpp index e74f759855..59faa50114 100644 --- a/scene/resources/packed_scene.cpp +++ b/scene/resources/packed_scene.cpp @@ -99,8 +99,9 @@ Node *SceneState::instantiate(GenEditState p_edit_state) const { #endif parent = nparent; } else { - // i == 0 is root node. Confirm that it doesn't have a parent defined. + // i == 0 is root node. ERR_FAIL_COND_V_MSG(n.parent != -1, nullptr, vformat("Invalid scene: root node %s cannot specify a parent node.", snames[n.name])); + ERR_FAIL_COND_V_MSG(n.type == TYPE_INSTANCED && base_scene_idx < 0, nullptr, vformat("Invalid scene: root node %s in an instance, but there's no base scene.", snames[n.name])); } Node *node = nullptr; diff --git a/scene/resources/particles_material.cpp b/scene/resources/particles_material.cpp index 0495a9e92c..d9ec0bfd69 100644 --- a/scene/resources/particles_material.cpp +++ b/scene/resources/particles_material.cpp @@ -741,7 +741,7 @@ void ParticlesMaterial::flush_changes() { void ParticlesMaterial::_queue_shader_change() { MutexLock lock(material_mutex); - if (!element.in_list()) { + if (is_initialized && !element.in_list()) { dirty_materials->add(&element); } } @@ -1533,6 +1533,7 @@ ParticlesMaterial::ParticlesMaterial() : current_key.invalid_key = 1; + is_initialized = true; _queue_shader_change(); } diff --git a/scene/resources/particles_material.h b/scene/resources/particles_material.h index 8ab26aff77..36bc456978 100644 --- a/scene/resources/particles_material.h +++ b/scene/resources/particles_material.h @@ -226,6 +226,7 @@ private: _FORCE_INLINE_ void _queue_shader_change(); _FORCE_INLINE_ bool _is_shader_dirty() const; + bool is_initialized = false; Vector3 direction; float spread; float flatness; diff --git a/scene/resources/primitive_meshes.cpp b/scene/resources/primitive_meshes.cpp index e7da41db9d..f8be00f5fb 100644 --- a/scene/resources/primitive_meshes.cpp +++ b/scene/resources/primitive_meshes.cpp @@ -1390,6 +1390,12 @@ void QuadMesh::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "center_offset"), "set_center_offset", "get_center_offset"); } +uint32_t QuadMesh::surface_get_format(int p_idx) const { + ERR_FAIL_INDEX_V(p_idx, 1, 0); + + return RS::ARRAY_FORMAT_VERTEX | RS::ARRAY_FORMAT_NORMAL | RS::ARRAY_FORMAT_TANGENT | RS::ARRAY_FORMAT_TEX_UV; +} + QuadMesh::QuadMesh() { primitive_type = PRIMITIVE_TRIANGLES; } @@ -1460,7 +1466,7 @@ void SphereMesh::_create_mesh_array(Array &p_arr) const { } else { Vector3 p = Vector3(x * radius * w, y, z * radius * w); points.push_back(p); - Vector3 normal = Vector3(x * radius * w * scale, y / scale, z * radius * w * scale); + Vector3 normal = Vector3(x * w * scale, radius * (y / scale), z * w * scale); normals.push_back(normal.normalized()); }; ADD_TANGENT(z, 0.0, -x, 1.0) diff --git a/scene/resources/primitive_meshes.h b/scene/resources/primitive_meshes.h index 7915cb0028..d447dad97a 100644 --- a/scene/resources/primitive_meshes.h +++ b/scene/resources/primitive_meshes.h @@ -285,6 +285,8 @@ protected: virtual void _create_mesh_array(Array &p_arr) const override; public: + virtual uint32_t surface_get_format(int p_idx) const override; + QuadMesh(); void set_size(const Size2 &p_size); diff --git a/scene/resources/skeleton_modification_2d.cpp b/scene/resources/skeleton_modification_2d.cpp index e533fb054a..7ac40b497d 100644 --- a/scene/resources/skeleton_modification_2d.cpp +++ b/scene/resources/skeleton_modification_2d.cpp @@ -96,37 +96,25 @@ float SkeletonModification2D::clamp_angle(float p_angle, float p_min_bound, floa p_max_bound = Math_TAU + p_max_bound; } if (p_min_bound > p_max_bound) { - float tmp = p_min_bound; - p_min_bound = p_max_bound; - p_max_bound = tmp; + SWAP(p_min_bound, p_max_bound); } + bool is_beyond_bounds = (p_angle < p_min_bound || p_angle > p_max_bound); + bool is_within_bounds = (p_angle > p_min_bound && p_angle < p_max_bound); + // Note: May not be the most optimal way to clamp, but it always constraints to the nearest angle. - if (p_invert == false) { - if (p_angle < p_min_bound || p_angle > p_max_bound) { - Vector2 min_bound_vec = Vector2(Math::cos(p_min_bound), Math::sin(p_min_bound)); - Vector2 max_bound_vec = Vector2(Math::cos(p_max_bound), Math::sin(p_max_bound)); - Vector2 angle_vec = Vector2(Math::cos(p_angle), Math::sin(p_angle)); - - if (angle_vec.distance_squared_to(min_bound_vec) <= angle_vec.distance_squared_to(max_bound_vec)) { - p_angle = p_min_bound; - } else { - p_angle = p_max_bound; - } - } - } else { - if (p_angle > p_min_bound && p_angle < p_max_bound) { - Vector2 min_bound_vec = Vector2(Math::cos(p_min_bound), Math::sin(p_min_bound)); - Vector2 max_bound_vec = Vector2(Math::cos(p_max_bound), Math::sin(p_max_bound)); - Vector2 angle_vec = Vector2(Math::cos(p_angle), Math::sin(p_angle)); + if ((!p_invert && is_beyond_bounds) || (p_invert && is_within_bounds)) { + Vector2 min_bound_vec = Vector2(Math::cos(p_min_bound), Math::sin(p_min_bound)); + Vector2 max_bound_vec = Vector2(Math::cos(p_max_bound), Math::sin(p_max_bound)); + Vector2 angle_vec = Vector2(Math::cos(p_angle), Math::sin(p_angle)); - if (angle_vec.distance_squared_to(min_bound_vec) <= angle_vec.distance_squared_to(max_bound_vec)) { - p_angle = p_min_bound; - } else { - p_angle = p_max_bound; - } + if (angle_vec.distance_squared_to(min_bound_vec) <= angle_vec.distance_squared_to(max_bound_vec)) { + p_angle = p_min_bound; + } else { + p_angle = p_max_bound; } } + return p_angle; } @@ -152,9 +140,7 @@ void SkeletonModification2D::editor_draw_angle_constraints(Bone2D *p_operation_b arc_angle_max = (Math_PI * 2) + arc_angle_max; } if (arc_angle_min > arc_angle_max) { - float tmp = arc_angle_min; - arc_angle_min = arc_angle_max; - arc_angle_max = tmp; + SWAP(arc_angle_min, arc_angle_max); } arc_angle_min += p_operation_bone->get_bone_angle(); arc_angle_max += p_operation_bone->get_bone_angle(); diff --git a/scene/resources/skeleton_modification_2d_twoboneik.cpp b/scene/resources/skeleton_modification_2d_twoboneik.cpp index 88d80a501f..4f752896a9 100644 --- a/scene/resources/skeleton_modification_2d_twoboneik.cpp +++ b/scene/resources/skeleton_modification_2d_twoboneik.cpp @@ -144,7 +144,7 @@ void SkeletonModification2DTwoBoneIK::_execute(float p_delta) { // With modifications by TwistedTwigleg Vector2 target_difference = target->get_global_position() - joint_one_bone->get_global_position(); float joint_one_to_target = target_difference.length(); - float angle_atan = Math::atan2(target_difference.y, target_difference.x); + float angle_atan = target_difference.angle(); float bone_one_length = joint_one_bone->get_length() * MIN(joint_one_bone->get_global_scale().x, joint_one_bone->get_global_scale().y); float bone_two_length = joint_two_bone->get_length() * MIN(joint_two_bone->get_global_scale().x, joint_two_bone->get_global_scale().y); diff --git a/scene/resources/skeleton_modification_3d.cpp b/scene/resources/skeleton_modification_3d.cpp index ee02ede2d5..b476952d86 100644 --- a/scene/resources/skeleton_modification_3d.cpp +++ b/scene/resources/skeleton_modification_3d.cpp @@ -72,37 +72,25 @@ real_t SkeletonModification3D::clamp_angle(real_t p_angle, real_t p_min_bound, r p_max_bound = Math_TAU + p_max_bound; } if (p_min_bound > p_max_bound) { - real_t tmp = p_min_bound; - p_min_bound = p_max_bound; - p_max_bound = tmp; + SWAP(p_min_bound, p_max_bound); } + bool is_beyond_bounds = (p_angle < p_min_bound || p_angle > p_max_bound); + bool is_within_bounds = (p_angle > p_min_bound && p_angle < p_max_bound); + // Note: May not be the most optimal way to clamp, but it always constraints to the nearest angle. - if (p_invert == false) { - if (p_angle < p_min_bound || p_angle > p_max_bound) { - Vector2 min_bound_vec = Vector2(Math::cos(p_min_bound), Math::sin(p_min_bound)); - Vector2 max_bound_vec = Vector2(Math::cos(p_max_bound), Math::sin(p_max_bound)); - Vector2 angle_vec = Vector2(Math::cos(p_angle), Math::sin(p_angle)); - - if (angle_vec.distance_squared_to(min_bound_vec) <= angle_vec.distance_squared_to(max_bound_vec)) { - p_angle = p_min_bound; - } else { - p_angle = p_max_bound; - } - } - } else { - if (p_angle > p_min_bound && p_angle < p_max_bound) { - Vector2 min_bound_vec = Vector2(Math::cos(p_min_bound), Math::sin(p_min_bound)); - Vector2 max_bound_vec = Vector2(Math::cos(p_max_bound), Math::sin(p_max_bound)); - Vector2 angle_vec = Vector2(Math::cos(p_angle), Math::sin(p_angle)); - - if (angle_vec.distance_squared_to(min_bound_vec) <= angle_vec.distance_squared_to(max_bound_vec)) { - p_angle = p_min_bound; - } else { - p_angle = p_max_bound; - } + if ((!p_invert && is_beyond_bounds) || (p_invert && is_within_bounds)) { + Vector2 min_bound_vec = Vector2(Math::cos(p_min_bound), Math::sin(p_min_bound)); + Vector2 max_bound_vec = Vector2(Math::cos(p_max_bound), Math::sin(p_max_bound)); + Vector2 angle_vec = Vector2(Math::cos(p_angle), Math::sin(p_angle)); + + if (angle_vec.distance_squared_to(min_bound_vec) <= angle_vec.distance_squared_to(max_bound_vec)) { + p_angle = p_min_bound; + } else { + p_angle = p_max_bound; } } + return p_angle; } diff --git a/scene/resources/surface_tool.cpp b/scene/resources/surface_tool.cpp index d5e370568d..a8cd872408 100644 --- a/scene/resources/surface_tool.cpp +++ b/scene/resources/surface_tool.cpp @@ -1176,6 +1176,7 @@ Vector<int> SurfaceTool::generate_lod(float p_threshold, int p_target_index_coun ERR_FAIL_COND_V(simplify_func == nullptr, lod); ERR_FAIL_COND_V(vertex_array.size() == 0, lod); ERR_FAIL_COND_V(index_array.size() == 0, lod); + ERR_FAIL_COND_V(index_array.size() % 3 != 0, lod); lod.resize(index_array.size()); LocalVector<float> vertices; //uses floats diff --git a/scene/resources/texture.cpp b/scene/resources/texture.cpp index 3dc32632cc..80cab99373 100644 --- a/scene/resources/texture.cpp +++ b/scene/resources/texture.cpp @@ -1248,6 +1248,14 @@ bool AtlasTexture::is_pixel_opaque(int p_x, int p_y) const { return atlas->is_pixel_opaque(x, y); } +Ref<Image> AtlasTexture::get_image() const { + if (!atlas.is_valid()) { + return Ref<Image>(); + } + + return atlas->get_image()->get_rect(region); +} + AtlasTexture::AtlasTexture() {} ///////////////////////////////////////// diff --git a/scene/resources/texture.h b/scene/resources/texture.h index 93f4e2de5a..862b9a47a6 100644 --- a/scene/resources/texture.h +++ b/scene/resources/texture.h @@ -250,6 +250,8 @@ public: bool is_pixel_opaque(int p_x, int p_y) const override; + virtual Ref<Image> get_image() const override; + AtlasTexture(); }; diff --git a/scene/resources/tile_set.cpp b/scene/resources/tile_set.cpp index 918fc3fe9c..67cbd0e094 100644 --- a/scene/resources/tile_set.cpp +++ b/scene/resources/tile_set.cpp @@ -3119,7 +3119,7 @@ Vector2i TileSetAtlasSource::get_atlas_grid_size() const { } bool TileSetAtlasSource::_set(const StringName &p_name, const Variant &p_value) { - Vector<String> components = String(p_name).split("/", true, 2); + Vector<String> components = String(p_name).split("/", true, 3); // Compute the vector2i if we have coordinates. Vector<String> coords_split = components[0].split(":"); @@ -3138,8 +3138,32 @@ bool TileSetAtlasSource::_set(const StringName &p_name, const Variant &p_value) // Properties. if (components[1] == "size_in_atlas") { move_tile_in_atlas(coords, coords, p_value); + return true; } else if (components[1] == "next_alternative_id") { tiles[coords].next_alternative_id = p_value; + return true; + } else if (components[1] == "animation_columns") { + set_tile_animation_columns(coords, p_value); + return true; + } else if (components[1] == "animation_separation") { + set_tile_animation_separation(coords, p_value); + return true; + } else if (components[1] == "animation_speed") { + set_tile_animation_speed(coords, p_value); + return true; + } else if (components[1] == "animation_frames_count") { + set_tile_animation_frames_count(coords, p_value); + return true; + } else if (components.size() >= 3 && components[1].begins_with("animation_frame_") && components[1].trim_prefix("animation_frame_").is_valid_int()) { + int frame = components[1].trim_prefix("animation_frame_").to_int(); + if (components[2] == "duration") { + if (frame >= get_tile_animation_frames_count(coords)) { + set_tile_animation_frames_count(coords, frame + 1); + } + set_tile_animation_frame_duration(coords, frame, p_value); + return true; + } + return false; } else if (components[1].is_valid_int()) { int alternative_id = components[1].to_int(); if (alternative_id != TileSetSource::INVALID_TILE_ALTERNATIVE) { @@ -3185,6 +3209,28 @@ bool TileSetAtlasSource::_get(const StringName &p_name, Variant &r_ret) const { } else if (components[1] == "next_alternative_id") { r_ret = tiles[coords].next_alternative_id; return true; + } else if (components[1] == "animation_columns") { + r_ret = get_tile_animation_columns(coords); + return true; + } else if (components[1] == "animation_separation") { + r_ret = get_tile_animation_separation(coords); + return true; + } else if (components[1] == "animation_speed") { + r_ret = get_tile_animation_speed(coords); + return true; + } else if (components[1] == "animation_frames_count") { + r_ret = get_tile_animation_frames_count(coords); + return true; + } else if (components.size() >= 3 && components[1].begins_with("animation_frame_") && components[1].trim_prefix("animation_frame_").is_valid_int()) { + int frame = components[1].trim_prefix("animation_frame_").to_int(); + if (frame < 0 || frame >= get_tile_animation_frames_count(coords)) { + return false; + } + if (components[2] == "duration") { + r_ret = get_tile_animation_frame_duration(coords, frame); + return true; + } + return false; } else if (components[1].is_valid_int()) { int alternative_id = components[1].to_int(); if (alternative_id != TileSetSource::INVALID_TILE_ALTERNATIVE && tiles[coords].alternatives.has(alternative_id)) { @@ -3226,6 +3272,40 @@ void TileSetAtlasSource::_get_property_list(List<PropertyInfo> *p_list) const { } tile_property_list.push_back(property_info); + // animation_columns. + property_info = PropertyInfo(Variant::INT, "animation_columns", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR); + if (E_tile->get().animation_columns == 0) { + property_info.usage ^= PROPERTY_USAGE_STORAGE; + } + tile_property_list.push_back(property_info); + + // animation_separation. + property_info = PropertyInfo(Variant::INT, "animation_separation", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR); + if (E_tile->get().animation_separation == Vector2i()) { + property_info.usage ^= PROPERTY_USAGE_STORAGE; + } + tile_property_list.push_back(property_info); + + // animation_speed. + property_info = PropertyInfo(Variant::FLOAT, "animation_speed", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR); + if (E_tile->get().animation_speed == 1.0) { + property_info.usage ^= PROPERTY_USAGE_STORAGE; + } + tile_property_list.push_back(property_info); + + // animation_frames_count. + tile_property_list.push_back(PropertyInfo(Variant::INT, "animation_frames_count", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NETWORK)); + + // animation_frame_*. + bool store_durations = tiles[E_tile->key()].animation_frames_durations.size() >= 2; + for (int i = 0; i < (int)tiles[E_tile->key()].animation_frames_durations.size(); i++) { + property_info = PropertyInfo(Variant::FLOAT, vformat("animation_frame_%d/duration", i), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR); + if (!store_durations) { + property_info.usage ^= PROPERTY_USAGE_STORAGE; + } + tile_property_list.push_back(property_info); + } + for (Map<int, TileData *>::Element *E_alternative = E_tile->get().alternatives.front(); E_alternative; E_alternative = E_alternative->next()) { // Add a dummy property to show the alternative exists. tile_property_list.push_back(PropertyInfo(Variant::INT, vformat("%d", E_alternative->key()), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR)); @@ -3234,11 +3314,10 @@ void TileSetAtlasSource::_get_property_list(List<PropertyInfo> *p_list) const { List<PropertyInfo> alternative_property_list; E_alternative->get()->get_property_list(&alternative_property_list); for (PropertyInfo &alternative_property_info : alternative_property_list) { - bool valid; - Variant default_value = ClassDB::class_get_default_property_value("TileData", alternative_property_info.name, &valid); + Variant default_value = ClassDB::class_get_default_property_value("TileData", alternative_property_info.name); Variant value = E_alternative->get()->get(alternative_property_info.name); - if (valid && value == default_value) { - property_info.usage ^= PROPERTY_USAGE_STORAGE; + if (default_value.get_type() != Variant::NIL && bool(Variant::evaluate(Variant::OP_EQUAL, value, default_value))) { + alternative_property_info.usage ^= PROPERTY_USAGE_STORAGE; } alternative_property_info.name = vformat("%s/%s", vformat("%d", E_alternative->key()), alternative_property_info.name); tile_property_list.push_back(alternative_property_info); @@ -3257,33 +3336,27 @@ void TileSetAtlasSource::create_tile(const Vector2i p_atlas_coords, const Vector // Create a tile if it does not exists. ERR_FAIL_COND(p_atlas_coords.x < 0 || p_atlas_coords.y < 0); ERR_FAIL_COND(p_size.x <= 0 || p_size.y <= 0); - for (int x = 0; x < p_size.x; x++) { - for (int y = 0; y < p_size.y; y++) { - Vector2i coords = p_atlas_coords + Vector2i(x, y); - ERR_FAIL_COND_MSG(tiles.has(coords), vformat("Cannot create tile at position %s with size %s. Already a tile present at %s.", p_atlas_coords, p_size, coords)); - } - } + + bool room_for_tile = has_room_for_tile(p_atlas_coords, p_size, 1, Vector2i(), 1); + ERR_FAIL_COND_MSG(!room_for_tile, "Cannot create tile, tiles are already present in the space the tile would cover."); + + // Initialize the tile data. + TileAlternativesData tad; + tad.size_in_atlas = p_size; + tad.animation_frames_durations.push_back(1.0); + tad.alternatives[0] = memnew(TileData); + tad.alternatives[0]->set_tile_set(tile_set); + tad.alternatives[0]->set_allow_transform(false); + tad.alternatives[0]->connect("changed", callable_mp((Resource *)this, &TileSetAtlasSource::emit_changed)); + tad.alternatives[0]->notify_property_list_changed(); + tad.alternatives_ids.append(0); // Create and resize the tile. - tiles.insert(p_atlas_coords, TileSetAtlasSource::TileAlternativesData()); + tiles.insert(p_atlas_coords, tad); tiles_ids.append(p_atlas_coords); tiles_ids.sort(); - tiles[p_atlas_coords].size_in_atlas = p_size; - tiles[p_atlas_coords].alternatives[0] = memnew(TileData); - tiles[p_atlas_coords].alternatives[0]->set_tile_set(tile_set); - tiles[p_atlas_coords].alternatives[0]->set_allow_transform(false); - tiles[p_atlas_coords].alternatives[0]->connect("changed", callable_mp((Resource *)this, &TileSetAtlasSource::emit_changed)); - tiles[p_atlas_coords].alternatives[0]->notify_property_list_changed(); - tiles[p_atlas_coords].alternatives_ids.append(0); - - // Add all covered positions to the mapping cache - for (int x = 0; x < p_size.x; x++) { - for (int y = 0; y < p_size.y; y++) { - Vector2i coords = p_atlas_coords + Vector2i(x, y); - _coords_mapping_cache[coords] = p_atlas_coords; - } - } + _create_coords_mapping_cache(p_atlas_coords); emit_signal(SNAME("changed")); } @@ -3292,14 +3365,7 @@ void TileSetAtlasSource::remove_tile(Vector2i p_atlas_coords) { ERR_FAIL_COND_MSG(!tiles.has(p_atlas_coords), vformat("TileSetAtlasSource has no tile at %s.", String(p_atlas_coords))); // Remove all covered positions from the mapping cache - Size2i size = tiles[p_atlas_coords].size_in_atlas; - - for (int x = 0; x < size.x; x++) { - for (int y = 0; y < size.y; y++) { - Vector2i coords = p_atlas_coords + Vector2i(x, y); - _coords_mapping_cache.erase(coords); - } - } + _clear_coords_mapping_cache(p_atlas_coords); // Free tile data. for (Map<int, TileData *>::Element *E_tile_data = tiles[p_atlas_coords].alternatives.front(); E_tile_data; E_tile_data = E_tile_data->next()) { @@ -3326,6 +3392,118 @@ Vector2i TileSetAtlasSource::get_tile_at_coords(Vector2i p_atlas_coords) const { return _coords_mapping_cache[p_atlas_coords]; } +void TileSetAtlasSource::set_tile_animation_columns(const Vector2i p_atlas_coords, int p_frame_columns) { + ERR_FAIL_COND_MSG(!tiles.has(p_atlas_coords), vformat("TileSetAtlasSource has no tile at %s.", Vector2i(p_atlas_coords))); + ERR_FAIL_COND(p_frame_columns < 0); + + TileAlternativesData &tad = tiles[p_atlas_coords]; + bool room_for_tile = has_room_for_tile(p_atlas_coords, tad.size_in_atlas, p_frame_columns, tad.animation_separation, tad.animation_frames_durations.size(), p_atlas_coords); + ERR_FAIL_COND_MSG(!room_for_tile, "Cannot set animation columns count, tiles are already present in the space the tile would cover."); + + _clear_coords_mapping_cache(p_atlas_coords); + + tiles[p_atlas_coords].animation_columns = p_frame_columns; + + _create_coords_mapping_cache(p_atlas_coords); + + emit_signal(SNAME("changed")); +} + +int TileSetAtlasSource::get_tile_animation_columns(const Vector2i p_atlas_coords) const { + ERR_FAIL_COND_V_MSG(!tiles.has(p_atlas_coords), 1, vformat("TileSetAtlasSource has no tile at %s.", Vector2i(p_atlas_coords))); + return tiles[p_atlas_coords].animation_columns; +} + +void TileSetAtlasSource::set_tile_animation_separation(const Vector2i p_atlas_coords, const Vector2i p_separation) { + ERR_FAIL_COND_MSG(!tiles.has(p_atlas_coords), vformat("TileSetAtlasSource has no tile at %s.", Vector2i(p_atlas_coords))); + ERR_FAIL_COND(p_separation.x < 0 || p_separation.y < 0); + + TileAlternativesData &tad = tiles[p_atlas_coords]; + bool room_for_tile = has_room_for_tile(p_atlas_coords, tad.size_in_atlas, tad.animation_columns, p_separation, tad.animation_frames_durations.size(), p_atlas_coords); + ERR_FAIL_COND_MSG(!room_for_tile, "Cannot set animation columns count, tiles are already present in the space the tile would cover."); + + _clear_coords_mapping_cache(p_atlas_coords); + + tiles[p_atlas_coords].animation_separation = p_separation; + + _create_coords_mapping_cache(p_atlas_coords); + + emit_signal(SNAME("changed")); +} + +Vector2i TileSetAtlasSource::get_tile_animation_separation(const Vector2i p_atlas_coords) const { + ERR_FAIL_COND_V_MSG(!tiles.has(p_atlas_coords), Vector2i(), vformat("TileSetAtlasSource has no tile at %s.", Vector2i(p_atlas_coords))); + return tiles[p_atlas_coords].animation_separation; +} + +void TileSetAtlasSource::set_tile_animation_speed(const Vector2i p_atlas_coords, real_t p_speed) { + ERR_FAIL_COND_MSG(!tiles.has(p_atlas_coords), vformat("TileSetAtlasSource has no tile at %s.", Vector2i(p_atlas_coords))); + ERR_FAIL_COND(p_speed <= 0); + + tiles[p_atlas_coords].animation_speed = p_speed; + + emit_signal(SNAME("changed")); +} + +real_t TileSetAtlasSource::get_tile_animation_speed(const Vector2i p_atlas_coords) const { + ERR_FAIL_COND_V_MSG(!tiles.has(p_atlas_coords), 1.0, vformat("TileSetAtlasSource has no tile at %s.", Vector2i(p_atlas_coords))); + return tiles[p_atlas_coords].animation_speed; +} + +void TileSetAtlasSource::set_tile_animation_frames_count(const Vector2i p_atlas_coords, int p_frames_count) { + ERR_FAIL_COND_MSG(!tiles.has(p_atlas_coords), vformat("TileSetAtlasSource has no tile at %s.", Vector2i(p_atlas_coords))); + ERR_FAIL_COND(p_frames_count < 1); + + TileAlternativesData &tad = tiles[p_atlas_coords]; + bool room_for_tile = has_room_for_tile(p_atlas_coords, tad.size_in_atlas, tad.animation_columns, tad.animation_separation, p_frames_count, p_atlas_coords); + ERR_FAIL_COND_MSG(!room_for_tile, "Cannot set animation columns count, tiles are already present in the space the tile would cover."); + + _clear_coords_mapping_cache(p_atlas_coords); + + int old_size = tiles[p_atlas_coords].animation_frames_durations.size(); + tiles[p_atlas_coords].animation_frames_durations.resize(p_frames_count); + for (int i = old_size; i < p_frames_count; i++) { + tiles[p_atlas_coords].animation_frames_durations[i] = 1.0; + } + + _create_coords_mapping_cache(p_atlas_coords); + + notify_property_list_changed(); + + emit_signal(SNAME("changed")); +} + +int TileSetAtlasSource::get_tile_animation_frames_count(const Vector2i p_atlas_coords) const { + ERR_FAIL_COND_V_MSG(!tiles.has(p_atlas_coords), 1, vformat("TileSetAtlasSource has no tile at %s.", Vector2i(p_atlas_coords))); + return tiles[p_atlas_coords].animation_frames_durations.size(); +} + +void TileSetAtlasSource::set_tile_animation_frame_duration(const Vector2i p_atlas_coords, int p_frame_index, real_t p_duration) { + ERR_FAIL_COND_MSG(!tiles.has(p_atlas_coords), vformat("TileSetAtlasSource has no tile at %s.", Vector2i(p_atlas_coords))); + ERR_FAIL_INDEX(p_frame_index, (int)tiles[p_atlas_coords].animation_frames_durations.size()); + ERR_FAIL_COND(p_duration <= 0.0); + + tiles[p_atlas_coords].animation_frames_durations[p_frame_index] = p_duration; + + emit_signal(SNAME("changed")); +} + +real_t TileSetAtlasSource::get_tile_animation_frame_duration(const Vector2i p_atlas_coords, int p_frame_index) const { + ERR_FAIL_COND_V_MSG(!tiles.has(p_atlas_coords), 1, vformat("TileSetAtlasSource has no tile at %s.", Vector2i(p_atlas_coords))); + ERR_FAIL_INDEX_V(p_frame_index, (int)tiles[p_atlas_coords].animation_frames_durations.size(), 0.0); + return tiles[p_atlas_coords].animation_frames_durations[p_frame_index]; +} + +real_t TileSetAtlasSource::get_tile_animation_total_duration(const Vector2i p_atlas_coords) const { + ERR_FAIL_COND_V_MSG(!tiles.has(p_atlas_coords), 1, vformat("TileSetAtlasSource has no tile at %s.", Vector2i(p_atlas_coords))); + + real_t sum = 0.0; + for (int frame = 0; frame < (int)tiles[p_atlas_coords].animation_frames_durations.size(); frame++) { + sum += tiles[p_atlas_coords].animation_frames_durations[frame]; + } + return sum; +} + Vector2i TileSetAtlasSource::get_tile_size_in_atlas(Vector2i p_atlas_coords) const { ERR_FAIL_COND_V_MSG(!tiles.has(p_atlas_coords), Vector2i(-1, -1), vformat("TileSetAtlasSource has no tile at %s.", String(p_atlas_coords))); @@ -3341,16 +3519,34 @@ Vector2i TileSetAtlasSource::get_tile_id(int p_index) const { return tiles_ids[p_index]; } -Rect2i TileSetAtlasSource::get_tile_texture_region(Vector2i p_atlas_coords) const { +bool TileSetAtlasSource::has_room_for_tile(Vector2i p_atlas_coords, Vector2i p_size, int p_animation_columns, Vector2i p_animation_separation, int p_frames_count, Vector2i p_ignored_tile) const { + for (int frame = 0; frame < p_frames_count; frame++) { + Vector2i frame_coords = p_atlas_coords + (p_size + p_animation_separation) * ((p_animation_columns > 0) ? Vector2i(frame % p_animation_columns, frame / p_animation_columns) : Vector2i(frame, 0)); + for (int x = 0; x < p_size.x; x++) { + for (int y = 0; y < p_size.y; y++) { + Vector2i coords = frame_coords + Vector2i(x, y); + if (_coords_mapping_cache.has(coords) && _coords_mapping_cache[coords] != p_ignored_tile) { + return false; + } + } + } + } + return true; +} + +Rect2i TileSetAtlasSource::get_tile_texture_region(Vector2i p_atlas_coords, int p_frame) const { ERR_FAIL_COND_V_MSG(!tiles.has(p_atlas_coords), Rect2i(), vformat("TileSetAtlasSource has no tile at %s.", String(p_atlas_coords))); + ERR_FAIL_INDEX_V(p_frame, (int)tiles[p_atlas_coords].animation_frames_durations.size(), Rect2i()); + + const TileAlternativesData &tad = tiles[p_atlas_coords]; - Vector2i size_in_atlas = tiles[p_atlas_coords].size_in_atlas; + Vector2i size_in_atlas = tad.size_in_atlas; Vector2 region_size = texture_region_size * size_in_atlas + separation * (size_in_atlas - Vector2i(1, 1)); - Vector2 origin = margins + (p_atlas_coords * (texture_region_size + separation)); + Vector2i frame_coords = p_atlas_coords + (size_in_atlas + tad.animation_separation) * ((tad.animation_columns > 0) ? Vector2i(p_frame % tad.animation_columns, p_frame / tad.animation_columns) : Vector2i(p_frame, 0)); + Vector2 origin = margins + (frame_coords * (texture_region_size + separation)); return Rect2(origin, region_size); - ; } Vector2i TileSetAtlasSource::get_tile_effective_texture_offset(Vector2i p_atlas_coords, int p_alternative_tile) const { @@ -3369,56 +3565,23 @@ Vector2i TileSetAtlasSource::get_tile_effective_texture_offset(Vector2i p_atlas_ return effective_texture_offset; } -bool TileSetAtlasSource::can_move_tile_in_atlas(Vector2i p_atlas_coords, Vector2i p_new_atlas_coords, Vector2i p_new_size) const { - ERR_FAIL_COND_V_MSG(!tiles.has(p_atlas_coords), false, vformat("TileSetAtlasSource has no tile at %s.", String(p_atlas_coords))); - - Vector2i new_atlas_coords = (p_new_atlas_coords != INVALID_ATLAS_COORDS) ? p_new_atlas_coords : p_atlas_coords; - if (new_atlas_coords.x < 0 || new_atlas_coords.y < 0) { - return false; - } - - Vector2i size = (p_new_size != Vector2i(-1, -1)) ? p_new_size : tiles[p_atlas_coords].size_in_atlas; - ERR_FAIL_COND_V(size.x <= 0 || size.y <= 0, false); - - Size2i grid_size = get_atlas_grid_size(); - if (new_atlas_coords.x + size.x > grid_size.x || new_atlas_coords.y + size.y > grid_size.y) { - return false; - } - - Rect2i new_rect = Rect2i(new_atlas_coords, size); - // Check if the new tile can fit in the new rect. - for (int x = new_rect.position.x; x < new_rect.get_end().x; x++) { - for (int y = new_rect.position.y; y < new_rect.get_end().y; y++) { - Vector2i coords = get_tile_at_coords(Vector2i(x, y)); - if (coords != p_atlas_coords && coords != TileSetSource::INVALID_ATLAS_COORDS) { - return false; - } - } - } - - return true; -} - void TileSetAtlasSource::move_tile_in_atlas(Vector2i p_atlas_coords, Vector2i p_new_atlas_coords, Vector2i p_new_size) { - bool can_move = can_move_tile_in_atlas(p_atlas_coords, p_new_atlas_coords, p_new_size); - ERR_FAIL_COND_MSG(!can_move, vformat("Cannot move tile at position %s with size %s. Tile already present.", p_new_atlas_coords, p_new_size)); + ERR_FAIL_COND_MSG(!tiles.has(p_atlas_coords), vformat("TileSetAtlasSource has no tile at %s.", String(p_atlas_coords))); + + TileAlternativesData &tad = tiles[p_atlas_coords]; // Compute the actual new rect from arguments. Vector2i new_atlas_coords = (p_new_atlas_coords != INVALID_ATLAS_COORDS) ? p_new_atlas_coords : p_atlas_coords; - Vector2i size = (p_new_size != Vector2i(-1, -1)) ? p_new_size : tiles[p_atlas_coords].size_in_atlas; + Vector2i new_size = (p_new_size != Vector2i(-1, -1)) ? p_new_size : tad.size_in_atlas; - if (new_atlas_coords == p_atlas_coords && size == tiles[p_atlas_coords].size_in_atlas) { + if (new_atlas_coords == p_atlas_coords && new_size == tad.size_in_atlas) { return; } - // Remove all covered positions from the mapping cache. - Size2i old_size = tiles[p_atlas_coords].size_in_atlas; - for (int x = 0; x < old_size.x; x++) { - for (int y = 0; y < old_size.y; y++) { - Vector2i coords = p_atlas_coords + Vector2i(x, y); - _coords_mapping_cache.erase(coords); - } - } + bool room_for_tile = has_room_for_tile(new_atlas_coords, new_size, tad.animation_columns, tad.animation_separation, tad.animation_frames_durations.size(), p_atlas_coords); + ERR_FAIL_COND_MSG(!room_for_tile, vformat("Cannot move tile at position %s with size %s. Tile already present.", new_atlas_coords, new_size)); + + _clear_coords_mapping_cache(p_atlas_coords); // Move the tile and update its size. if (new_atlas_coords != p_atlas_coords) { @@ -3429,15 +3592,9 @@ void TileSetAtlasSource::move_tile_in_atlas(Vector2i p_atlas_coords, Vector2i p_ tiles_ids.append(new_atlas_coords); tiles_ids.sort(); } - tiles[new_atlas_coords].size_in_atlas = size; + tiles[new_atlas_coords].size_in_atlas = new_size; - // Add all covered positions to the mapping cache again. - for (int x = 0; x < size.x; x++) { - for (int y = 0; y < size.y; y++) { - Vector2i coords = new_atlas_coords + Vector2i(x, y); - _coords_mapping_cache[coords] = new_atlas_coords; - } - } + _create_coords_mapping_cache(new_atlas_coords); emit_signal(SNAME("changed")); } @@ -3566,12 +3723,25 @@ void TileSetAtlasSource::_bind_methods() { // Base tiles ClassDB::bind_method(D_METHOD("create_tile", "atlas_coords", "size"), &TileSetAtlasSource::create_tile, DEFVAL(Vector2i(1, 1))); ClassDB::bind_method(D_METHOD("remove_tile", "atlas_coords"), &TileSetAtlasSource::remove_tile); // Remove a tile. If p_tile_key.alternative_tile if different from 0, remove the alternative - ClassDB::bind_method(D_METHOD("can_move_tile_in_atlas", "atlas_coords", "new_atlas_coords", "new_size"), &TileSetAtlasSource::can_move_tile_in_atlas, DEFVAL(INVALID_ATLAS_COORDS), DEFVAL(Vector2i(-1, -1))); ClassDB::bind_method(D_METHOD("move_tile_in_atlas", "atlas_coords", "new_atlas_coords", "new_size"), &TileSetAtlasSource::move_tile_in_atlas, DEFVAL(INVALID_ATLAS_COORDS), DEFVAL(Vector2i(-1, -1))); ClassDB::bind_method(D_METHOD("get_tile_size_in_atlas", "atlas_coords"), &TileSetAtlasSource::get_tile_size_in_atlas); + ClassDB::bind_method(D_METHOD("has_room_for_tile", "atlas_coords", "size", "animation_columns", "animation_separation", "frames_count", "ignored_tile"), &TileSetAtlasSource::has_room_for_tile, DEFVAL(INVALID_ATLAS_COORDS)); + ClassDB::bind_method(D_METHOD("get_tile_at_coords", "atlas_coords"), &TileSetAtlasSource::get_tile_at_coords); + ClassDB::bind_method(D_METHOD("set_tile_animation_columns", "atlas_coords", "frame_columns"), &TileSetAtlasSource::set_tile_animation_columns); + ClassDB::bind_method(D_METHOD("get_tile_animation_columns", "atlas_coords"), &TileSetAtlasSource::get_tile_animation_columns); + ClassDB::bind_method(D_METHOD("set_tile_animation_separation", "atlas_coords", "separation"), &TileSetAtlasSource::set_tile_animation_separation); + ClassDB::bind_method(D_METHOD("get_tile_animation_separation", "atlas_coords"), &TileSetAtlasSource::get_tile_animation_separation); + ClassDB::bind_method(D_METHOD("set_tile_animation_speed", "atlas_coords", "speed"), &TileSetAtlasSource::set_tile_animation_speed); + ClassDB::bind_method(D_METHOD("get_tile_animation_speed", "atlas_coords"), &TileSetAtlasSource::get_tile_animation_speed); + ClassDB::bind_method(D_METHOD("set_tile_animation_frames_count", "atlas_coords", "frames_count"), &TileSetAtlasSource::set_tile_animation_frames_count); + ClassDB::bind_method(D_METHOD("get_tile_animation_frames_count", "atlas_coords"), &TileSetAtlasSource::get_tile_animation_frames_count); + ClassDB::bind_method(D_METHOD("set_tile_animation_frame_duration", "atlas_coords", "frame_index", "duration"), &TileSetAtlasSource::set_tile_animation_frame_duration); + ClassDB::bind_method(D_METHOD("get_tile_animation_frame_duration", "atlas_coords", "frame_index"), &TileSetAtlasSource::get_tile_animation_frame_duration); + ClassDB::bind_method(D_METHOD("get_tile_animation_total_duration", "atlas_coords"), &TileSetAtlasSource::get_tile_animation_total_duration); + // Alternative tiles ClassDB::bind_method(D_METHOD("create_alternative_tile", "atlas_coords", "alternative_id_override"), &TileSetAtlasSource::create_alternative_tile, DEFVAL(INVALID_TILE_ALTERNATIVE)); ClassDB::bind_method(D_METHOD("remove_alternative_tile", "atlas_coords", "alternative_tile"), &TileSetAtlasSource::remove_alternative_tile); @@ -3584,7 +3754,7 @@ void TileSetAtlasSource::_bind_methods() { ClassDB::bind_method(D_METHOD("get_atlas_grid_size"), &TileSetAtlasSource::get_atlas_grid_size); ClassDB::bind_method(D_METHOD("has_tiles_outside_texture"), &TileSetAtlasSource::has_tiles_outside_texture); ClassDB::bind_method(D_METHOD("clear_tiles_outside_texture"), &TileSetAtlasSource::clear_tiles_outside_texture); - ClassDB::bind_method(D_METHOD("get_tile_texture_region", "atlas_coords"), &TileSetAtlasSource::get_tile_texture_region); + ClassDB::bind_method(D_METHOD("get_tile_texture_region", "atlas_coords", "frame"), &TileSetAtlasSource::get_tile_texture_region, DEFVAL(0)); } TileSetAtlasSource::~TileSetAtlasSource() { @@ -3618,6 +3788,45 @@ void TileSetAtlasSource::_compute_next_alternative_id(const Vector2i p_atlas_coo }; } +void TileSetAtlasSource::_clear_coords_mapping_cache(Vector2i p_atlas_coords) { + ERR_FAIL_COND_MSG(!tiles.has(p_atlas_coords), vformat("TileSetAtlasSource has no tile at %s.", Vector2i(p_atlas_coords))); + TileAlternativesData &tad = tiles[p_atlas_coords]; + for (int frame = 0; frame < (int)tad.animation_frames_durations.size(); frame++) { + Vector2i frame_coords = p_atlas_coords + (tad.size_in_atlas + tad.animation_separation) * ((tad.animation_columns > 0) ? Vector2i(frame % tad.animation_columns, frame / tad.animation_columns) : Vector2i(frame, 0)); + for (int x = 0; x < tad.size_in_atlas.x; x++) { + for (int y = 0; y < tad.size_in_atlas.y; y++) { + Vector2i coords = frame_coords + Vector2i(x, y); + if (!_coords_mapping_cache.has(coords)) { + WARN_PRINT(vformat("TileSetAtlasSource has no cached tile at position %s, the position cache might be corrupted.", coords)); + } else { + if (_coords_mapping_cache[coords] != p_atlas_coords) { + WARN_PRINT(vformat("The position cache at position %s is pointing to a wrong tile, the position cache might be corrupted.", coords)); + } + _coords_mapping_cache.erase(coords); + } + } + } + } +} + +void TileSetAtlasSource::_create_coords_mapping_cache(Vector2i p_atlas_coords) { + ERR_FAIL_COND_MSG(!tiles.has(p_atlas_coords), vformat("TileSetAtlasSource has no tile at %s.", Vector2i(p_atlas_coords))); + + TileAlternativesData &tad = tiles[p_atlas_coords]; + for (int frame = 0; frame < (int)tad.animation_frames_durations.size(); frame++) { + Vector2i frame_coords = p_atlas_coords + (tad.size_in_atlas + tad.animation_separation) * ((tad.animation_columns > 0) ? Vector2i(frame % tad.animation_columns, frame / tad.animation_columns) : Vector2i(frame, 0)); + for (int x = 0; x < tad.size_in_atlas.x; x++) { + for (int y = 0; y < tad.size_in_atlas.y; y++) { + Vector2i coords = frame_coords + Vector2i(x, y); + if (_coords_mapping_cache.has(coords)) { + WARN_PRINT(vformat("The cache already has a tile for position %s, the position cache might be corrupted.", coords)); + } + _coords_mapping_cache[coords] = p_atlas_coords; + } + } + } +} + /////////////////////////////// TileSetScenesCollectionSource ////////////////////////////////////// void TileSetScenesCollectionSource::_compute_next_alternative_id() { diff --git a/scene/resources/tile_set.h b/scene/resources/tile_set.h index ba7207241a..46cb1fb36d 100644 --- a/scene/resources/tile_set.h +++ b/scene/resources/tile_set.h @@ -447,16 +447,23 @@ public: class TileSetAtlasSource : public TileSetSource { GDCLASS(TileSetAtlasSource, TileSetSource); -public: +private: struct TileAlternativesData { Vector2i size_in_atlas = Vector2i(1, 1); Vector2i texture_offset; + + // Animation + int animation_columns = 0; + Vector2i animation_separation; + real_t animation_speed = 1.0; + LocalVector<real_t> animation_frames_durations; + + // Alternatives Map<int, TileData *> alternatives; Vector<int> alternatives_ids; int next_alternative_id = 1; }; -private: Ref<Texture2D> texture; Vector2i margins; Vector2i separation; @@ -471,6 +478,9 @@ private: void _compute_next_alternative_id(const Vector2i p_atlas_coords); + void _create_coords_mapping_cache(Vector2i p_atlas_coords); + void _clear_coords_mapping_cache(Vector2i p_atlas_coords); + protected: bool _set(const StringName &p_name, const Variant &p_value); bool _get(const StringName &p_name, Variant &r_ret) const; @@ -513,18 +523,32 @@ public: Vector2i get_texture_region_size() const; // Base tiles. - void create_tile(const Vector2i p_atlas_coords, const Vector2i p_size = Vector2i(1, 1)); // Create a tile if it does not exists, or add alternative tile if it does. - void remove_tile(Vector2i p_atlas_coords); // Remove a tile. If p_tile_key.alternative_tile if different from 0, remove the alternative + void create_tile(const Vector2i p_atlas_coords, const Vector2i p_size = Vector2i(1, 1)); + void remove_tile(Vector2i p_atlas_coords); virtual bool has_tile(Vector2i p_atlas_coords) const override; - bool can_move_tile_in_atlas(Vector2i p_atlas_coords, Vector2i p_new_atlas_coords = INVALID_ATLAS_COORDS, Vector2i p_new_size = Vector2i(-1, -1)) const; void move_tile_in_atlas(Vector2i p_atlas_coords, Vector2i p_new_atlas_coords = INVALID_ATLAS_COORDS, Vector2i p_new_size = Vector2i(-1, -1)); Vector2i get_tile_size_in_atlas(Vector2i p_atlas_coords) const; virtual int get_tiles_count() const override; virtual Vector2i get_tile_id(int p_index) const override; + bool has_room_for_tile(Vector2i p_atlas_coords, Vector2i p_size, int p_animation_columns, Vector2i p_animation_separation, int p_frames_count, Vector2i p_ignored_tile = INVALID_ATLAS_COORDS) const; + Vector2i get_tile_at_coords(Vector2i p_atlas_coords) const; + // Animation. + void set_tile_animation_columns(const Vector2i p_atlas_coords, int p_frame_columns); + int get_tile_animation_columns(const Vector2i p_atlas_coords) const; + void set_tile_animation_separation(const Vector2i p_atlas_coords, const Vector2i p_separation); + Vector2i get_tile_animation_separation(const Vector2i p_atlas_coords) const; + void set_tile_animation_speed(const Vector2i p_atlas_coords, real_t p_speed); + real_t get_tile_animation_speed(const Vector2i p_atlas_coords) const; + void set_tile_animation_frames_count(const Vector2i p_atlas_coords, int p_frames_count); + int get_tile_animation_frames_count(const Vector2i p_atlas_coords) const; + void set_tile_animation_frame_duration(const Vector2i p_atlas_coords, int p_frame_index, real_t p_duration); + real_t get_tile_animation_frame_duration(const Vector2i p_atlas_coords, int p_frame_index) const; + real_t get_tile_animation_total_duration(const Vector2i p_atlas_coords) const; + // Alternative tiles. int create_alternative_tile(const Vector2i p_atlas_coords, int p_alternative_id_override = -1); void remove_alternative_tile(const Vector2i p_atlas_coords, int p_alternative_tile); @@ -542,7 +566,7 @@ public: Vector2i get_atlas_grid_size() const; bool has_tiles_outside_texture(); void clear_tiles_outside_texture(); - Rect2i get_tile_texture_region(Vector2i p_atlas_coords) const; + Rect2i get_tile_texture_region(Vector2i p_atlas_coords, int p_frame = 0) const; Vector2i get_tile_effective_texture_offset(Vector2i p_atlas_coords, int p_alternative_tile) const; ~TileSetAtlasSource(); |