diff options
Diffstat (limited to 'scene')
107 files changed, 3851 insertions, 1187 deletions
diff --git a/scene/2d/area_2d.cpp b/scene/2d/area_2d.cpp index 75a1723e04..8db1491953 100644 --- a/scene/2d/area_2d.cpp +++ b/scene/2d/area_2d.cpp @@ -33,13 +33,13 @@ #include "scene/scene_string_names.h" #include "servers/audio_server.h" -void Area2D::set_space_override_mode(SpaceOverride p_mode) { - space_override = p_mode; - PhysicsServer2D::get_singleton()->area_set_space_override_mode(get_rid(), PhysicsServer2D::AreaSpaceOverrideMode(p_mode)); +void Area2D::set_gravity_space_override_mode(SpaceOverride p_mode) { + gravity_space_override = p_mode; + PhysicsServer2D::get_singleton()->area_set_param(get_rid(), PhysicsServer2D::AREA_PARAM_GRAVITY_OVERRIDE_MODE, p_mode); } -Area2D::SpaceOverride Area2D::get_space_override_mode() const { - return space_override; +Area2D::SpaceOverride Area2D::get_gravity_space_override_mode() const { + return gravity_space_override; } void Area2D::set_gravity_is_point(bool p_enabled) { @@ -51,21 +51,30 @@ bool Area2D::is_gravity_a_point() const { return gravity_is_point; } -void Area2D::set_gravity_distance_scale(real_t p_scale) { +void Area2D::set_gravity_point_distance_scale(real_t p_scale) { gravity_distance_scale = p_scale; PhysicsServer2D::get_singleton()->area_set_param(get_rid(), PhysicsServer2D::AREA_PARAM_GRAVITY_DISTANCE_SCALE, p_scale); } -real_t Area2D::get_gravity_distance_scale() const { +real_t Area2D::get_gravity_point_distance_scale() const { return gravity_distance_scale; } -void Area2D::set_gravity_vector(const Vector2 &p_vec) { - gravity_vec = p_vec; - PhysicsServer2D::get_singleton()->area_set_param(get_rid(), PhysicsServer2D::AREA_PARAM_GRAVITY_VECTOR, p_vec); +void Area2D::set_gravity_point_center(const Vector2 &p_center) { + gravity_vec = p_center; + PhysicsServer2D::get_singleton()->area_set_param(get_rid(), PhysicsServer2D::AREA_PARAM_GRAVITY_VECTOR, p_center); } -Vector2 Area2D::get_gravity_vector() const { +const Vector2 &Area2D::get_gravity_point_center() const { + return gravity_vec; +} + +void Area2D::set_gravity_direction(const Vector2 &p_direction) { + gravity_vec = p_direction; + PhysicsServer2D::get_singleton()->area_set_param(get_rid(), PhysicsServer2D::AREA_PARAM_GRAVITY_VECTOR, p_direction); +} + +const Vector2 &Area2D::get_gravity_direction() const { return gravity_vec; } @@ -78,6 +87,24 @@ real_t Area2D::get_gravity() const { return gravity; } +void Area2D::set_linear_damp_space_override_mode(SpaceOverride p_mode) { + linear_damp_space_override = p_mode; + PhysicsServer2D::get_singleton()->area_set_param(get_rid(), PhysicsServer2D::AREA_PARAM_LINEAR_DAMP_OVERRIDE_MODE, p_mode); +} + +Area2D::SpaceOverride Area2D::get_linear_damp_space_override_mode() const { + return linear_damp_space_override; +} + +void Area2D::set_angular_damp_space_override_mode(SpaceOverride p_mode) { + angular_damp_space_override = p_mode; + PhysicsServer2D::get_singleton()->area_set_param(get_rid(), PhysicsServer2D::AREA_PARAM_ANGULAR_DAMP_OVERRIDE_MODE, p_mode); +} + +Area2D::SpaceOverride Area2D::get_angular_damp_space_override_mode() const { + return angular_damp_space_override; +} + void Area2D::set_linear_damp(real_t p_linear_damp) { linear_damp = p_linear_damp; PhysicsServer2D::get_singleton()->area_set_param(get_rid(), PhysicsServer2D::AREA_PARAM_LINEAR_DAMP, p_linear_damp); @@ -483,25 +510,56 @@ void Area2D::_validate_property(PropertyInfo &property) const { } property.hint_string = options; + } else if (property.name.begins_with("gravity") && property.name != "gravity_space_override") { + if (gravity_space_override == SPACE_OVERRIDE_DISABLED) { + property.usage = PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL; + } else { + if (gravity_is_point) { + if (property.name == "gravity_direction") { + property.usage = PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL; + } + } else { + if (property.name.begins_with("gravity_point_")) { + property.usage = PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL; + } + } + } + } else if (property.name.begins_with("linear_damp") && property.name != "linear_damp_space_override") { + if (linear_damp_space_override == SPACE_OVERRIDE_DISABLED) { + property.usage = PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL; + } + } else if (property.name.begins_with("angular_damp") && property.name != "angular_damp_space_override") { + if (angular_damp_space_override == SPACE_OVERRIDE_DISABLED) { + property.usage = PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL; + } } } void Area2D::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_space_override_mode", "space_override_mode"), &Area2D::set_space_override_mode); - ClassDB::bind_method(D_METHOD("get_space_override_mode"), &Area2D::get_space_override_mode); + ClassDB::bind_method(D_METHOD("set_gravity_space_override_mode", "space_override_mode"), &Area2D::set_gravity_space_override_mode); + ClassDB::bind_method(D_METHOD("get_gravity_space_override_mode"), &Area2D::get_gravity_space_override_mode); ClassDB::bind_method(D_METHOD("set_gravity_is_point", "enable"), &Area2D::set_gravity_is_point); ClassDB::bind_method(D_METHOD("is_gravity_a_point"), &Area2D::is_gravity_a_point); - ClassDB::bind_method(D_METHOD("set_gravity_distance_scale", "distance_scale"), &Area2D::set_gravity_distance_scale); - ClassDB::bind_method(D_METHOD("get_gravity_distance_scale"), &Area2D::get_gravity_distance_scale); + ClassDB::bind_method(D_METHOD("set_gravity_point_distance_scale", "distance_scale"), &Area2D::set_gravity_point_distance_scale); + ClassDB::bind_method(D_METHOD("get_gravity_point_distance_scale"), &Area2D::get_gravity_point_distance_scale); + + ClassDB::bind_method(D_METHOD("set_gravity_point_center", "center"), &Area2D::set_gravity_point_center); + ClassDB::bind_method(D_METHOD("get_gravity_point_center"), &Area2D::get_gravity_point_center); - ClassDB::bind_method(D_METHOD("set_gravity_vector", "vector"), &Area2D::set_gravity_vector); - ClassDB::bind_method(D_METHOD("get_gravity_vector"), &Area2D::get_gravity_vector); + ClassDB::bind_method(D_METHOD("set_gravity_direction", "direction"), &Area2D::set_gravity_direction); + ClassDB::bind_method(D_METHOD("get_gravity_direction"), &Area2D::get_gravity_direction); ClassDB::bind_method(D_METHOD("set_gravity", "gravity"), &Area2D::set_gravity); ClassDB::bind_method(D_METHOD("get_gravity"), &Area2D::get_gravity); + ClassDB::bind_method(D_METHOD("set_linear_damp_space_override_mode", "space_override_mode"), &Area2D::set_linear_damp_space_override_mode); + ClassDB::bind_method(D_METHOD("get_linear_damp_space_override_mode"), &Area2D::get_linear_damp_space_override_mode); + + ClassDB::bind_method(D_METHOD("set_angular_damp_space_override_mode", "space_override_mode"), &Area2D::set_angular_damp_space_override_mode); + ClassDB::bind_method(D_METHOD("get_angular_damp_space_override_mode"), &Area2D::get_angular_damp_space_override_mode); + ClassDB::bind_method(D_METHOD("set_linear_damp", "linear_damp"), &Area2D::set_linear_damp); ClassDB::bind_method(D_METHOD("get_linear_damp"), &Area2D::get_linear_damp); @@ -543,13 +601,20 @@ void Area2D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "monitorable"), "set_monitorable", "is_monitorable"); ADD_PROPERTY(PropertyInfo(Variant::INT, "priority", PROPERTY_HINT_RANGE, "0,128,1"), "set_priority", "get_priority"); - ADD_GROUP("Physics Overrides", ""); - ADD_PROPERTY(PropertyInfo(Variant::INT, "space_override", PROPERTY_HINT_ENUM, "Disabled,Combine,Combine-Replace,Replace,Replace-Combine"), "set_space_override_mode", "get_space_override_mode"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "gravity_point"), "set_gravity_is_point", "is_gravity_a_point"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "gravity_distance_scale", PROPERTY_HINT_RANGE, "0,1024,0.001,or_greater,exp"), "set_gravity_distance_scale", "get_gravity_distance_scale"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "gravity_vec"), "set_gravity_vector", "get_gravity_vector"); + ADD_GROUP("Gravity", "gravity_"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "gravity_space_override", PROPERTY_HINT_ENUM, "Disabled,Combine,Combine-Replace,Replace,Replace-Combine", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), "set_gravity_space_override_mode", "get_gravity_space_override_mode"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "gravity_point", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), "set_gravity_is_point", "is_gravity_a_point"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "gravity_point_distance_scale", PROPERTY_HINT_RANGE, "0,1024,0.001,or_greater,exp"), "set_gravity_point_distance_scale", "get_gravity_point_distance_scale"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "gravity_point_center"), "set_gravity_point_center", "get_gravity_point_center"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "gravity_direction"), "set_gravity_direction", "get_gravity_direction"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "gravity", PROPERTY_HINT_RANGE, "-4096,4096,0.001,or_lesser,or_greater"), "set_gravity", "get_gravity"); + + ADD_GROUP("Linear Damp", "linear_damp_"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "linear_damp_space_override", PROPERTY_HINT_ENUM, "Disabled,Combine,Combine-Replace,Replace,Replace-Combine", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), "set_linear_damp_space_override_mode", "get_linear_damp_space_override_mode"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "linear_damp", PROPERTY_HINT_RANGE, "0,100,0.001,or_greater"), "set_linear_damp", "get_linear_damp"); + + ADD_GROUP("Angular Damp", "angular_damp_"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "angular_damp_space_override", PROPERTY_HINT_ENUM, "Disabled,Combine,Combine-Replace,Replace,Replace-Combine", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), "set_angular_damp_space_override_mode", "get_angular_damp_space_override_mode"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "angular_damp", PROPERTY_HINT_RANGE, "0,100,0.001,or_greater"), "set_angular_damp", "get_angular_damp"); ADD_GROUP("Audio Bus", "audio_bus_"); @@ -566,7 +631,7 @@ void Area2D::_bind_methods() { Area2D::Area2D() : CollisionObject2D(PhysicsServer2D::get_singleton()->area_create(), true) { set_gravity(980); - set_gravity_vector(Vector2(0, 1)); + set_gravity_direction(Vector2(0, 1)); set_monitoring(true); set_monitorable(true); } diff --git a/scene/2d/area_2d.h b/scene/2d/area_2d.h index 2c29e4660d..98ba270a61 100644 --- a/scene/2d/area_2d.h +++ b/scene/2d/area_2d.h @@ -47,14 +47,19 @@ public: }; private: - SpaceOverride space_override = SPACE_OVERRIDE_DISABLED; + SpaceOverride gravity_space_override = SPACE_OVERRIDE_DISABLED; Vector2 gravity_vec; real_t gravity; bool gravity_is_point = false; real_t gravity_distance_scale = 0.0; + + SpaceOverride linear_damp_space_override = SPACE_OVERRIDE_DISABLED; + SpaceOverride angular_damp_space_override = SPACE_OVERRIDE_DISABLED; real_t linear_damp = 0.1; real_t angular_damp = 1.0; + int priority = 0; + bool monitoring = false; bool monitorable = false; bool locked = false; @@ -133,21 +138,30 @@ protected: void _validate_property(PropertyInfo &property) const override; public: - void set_space_override_mode(SpaceOverride p_mode); - SpaceOverride get_space_override_mode() const; + void set_gravity_space_override_mode(SpaceOverride p_mode); + SpaceOverride get_gravity_space_override_mode() const; void set_gravity_is_point(bool p_enabled); bool is_gravity_a_point() const; - void set_gravity_distance_scale(real_t p_scale); - real_t get_gravity_distance_scale() const; + void set_gravity_point_distance_scale(real_t p_scale); + real_t get_gravity_point_distance_scale() const; - void set_gravity_vector(const Vector2 &p_vec); - Vector2 get_gravity_vector() const; + void set_gravity_point_center(const Vector2 &p_center); + const Vector2 &get_gravity_point_center() const; + + void set_gravity_direction(const Vector2 &p_direction); + const Vector2 &get_gravity_direction() const; void set_gravity(real_t p_gravity); real_t get_gravity() const; + void set_linear_damp_space_override_mode(SpaceOverride p_mode); + SpaceOverride get_linear_damp_space_override_mode() const; + + void set_angular_damp_space_override_mode(SpaceOverride p_mode); + SpaceOverride get_angular_damp_space_override_mode() const; + void set_linear_damp(real_t p_linear_damp); real_t get_linear_damp() const; diff --git a/scene/2d/audio_stream_player_2d.cpp b/scene/2d/audio_stream_player_2d.cpp index f73d52152e..24da2ce9ce 100644 --- a/scene/2d/audio_stream_player_2d.cpp +++ b/scene/2d/audio_stream_player_2d.cpp @@ -112,7 +112,13 @@ StringName AudioStreamPlayer2D::_get_actual_bus() { PhysicsDirectSpaceState2D *space_state = PhysicsServer2D::get_singleton()->space_get_direct_state(world_2d->get_space()); PhysicsDirectSpaceState2D::ShapeResult sr[MAX_INTERSECT_AREAS]; - int areas = space_state->intersect_point(global_pos, sr, MAX_INTERSECT_AREAS, Set<RID>(), area_mask, false, true); + PhysicsDirectSpaceState2D::PointParameters point_params; + point_params.position = global_pos; + point_params.collision_mask = area_mask; + point_params.collide_with_bodies = false; + point_params.collide_with_areas = true; + + int areas = space_state->intersect_point(point_params, sr, MAX_INTERSECT_AREAS); for (int i = 0; i < areas; i++) { Area2D *area2d = Object::cast_to<Area2D>(sr[i].collider); diff --git a/scene/2d/camera_2d.cpp b/scene/2d/camera_2d.cpp index b4ee85120e..bf5671be19 100644 --- a/scene/2d/camera_2d.cpp +++ b/scene/2d/camera_2d.cpp @@ -232,6 +232,7 @@ void Camera2D::_notification(int p_what) { } break; case NOTIFICATION_ENTER_TREE: { + ERR_FAIL_COND(!is_inside_tree()); if (custom_viewport && ObjectDB::get_instance(custom_viewport_id)) { viewport = custom_viewport; } else { diff --git a/scene/2d/cpu_particles_2d.cpp b/scene/2d/cpu_particles_2d.cpp index bf26ec1f20..80c17b6e88 100644 --- a/scene/2d/cpu_particles_2d.cpp +++ b/scene/2d/cpu_particles_2d.cpp @@ -1168,7 +1168,7 @@ void CPUParticles2D::convert_from_particles(Node *p_particles) { set_color(material->get_color()); - Ref<GradientTexture> gt = material->get_color_ramp(); + Ref<GradientTexture1D> gt = material->get_color_ramp(); if (gt.is_valid()) { set_color_ramp(gt->get_gradient()); } diff --git a/scene/2d/navigation_region_2d.cpp b/scene/2d/navigation_region_2d.cpp index bdb6cda5af..204ed72878 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.angle_to_point(a); + real_t angle = a.angle_to_point(b); 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 a43d498a62..f0e51097db 100644 --- a/scene/2d/physics_body_2d.cpp +++ b/scene/2d/physics_body_2d.cpp @@ -133,9 +133,12 @@ bool PhysicsBody2D::test_move(const Transform2D &p_from, const Vector2 &p_linear ERR_FAIL_COND_V(!is_inside_tree(), false); PhysicsServer2D::MotionResult *r = nullptr; + PhysicsServer2D::MotionResult temp_result; if (r_collision.is_valid()) { // Needs const_cast because method bindings don't support non-const Ref. r = const_cast<PhysicsServer2D::MotionResult *>(&r_collision->result); + } else { + r = &temp_result; } // Hack in order to work with calling from _process as well as from _physics_process; calling from thread is risky. @@ -143,7 +146,14 @@ bool PhysicsBody2D::test_move(const Transform2D &p_from, const Vector2 &p_linear PhysicsServer2D::MotionParameters parameters(p_from, p_linear_velocity * delta, p_margin); - return PhysicsServer2D::get_singleton()->body_test_motion(get_rid(), parameters, r); + bool colliding = PhysicsServer2D::get_singleton()->body_test_motion(get_rid(), parameters, r); + + if (colliding) { + // Don't report collision when the whole motion is done. + return (r->collision_safe_fraction < 1.0); + } else { + return false; + } } TypedArray<PhysicsBody2D> PhysicsBody2D::get_collision_exceptions() { @@ -1101,6 +1111,10 @@ bool CharacterBody2D::move_and_slide() { if (bs) { Vector2 local_position = gt.elements[2] - bs->get_transform().elements[2]; current_platform_velocity = bs->get_velocity_at_local_position(local_position); + } else { + // Body is removed or destroyed, invalidate floor. + current_platform_velocity = Vector2(); + platform_rid = RID(); } } else { current_platform_velocity = Vector2(); @@ -1303,6 +1317,17 @@ void CharacterBody2D::_move_and_slide_grounded(double p_delta, bool p_was_on_flo _snap_on_floor(p_was_on_floor, vel_dir_facing_up); + // Scales the horizontal velocity according to the wall slope. + if (is_on_wall_only() && motion_slide_up.dot(motion_results.get(0).collision_normal) < 0) { + Vector2 slide_motion = motion_velocity.slide(motion_results.get(0).collision_normal); + if (motion_slide_up.dot(slide_motion) < 0) { + motion_velocity = up_direction * up_direction.dot(motion_velocity); + } else { + // Keeps the vertical motion from motion_velocity and add the horizontal motion of the projection. + motion_velocity = up_direction * up_direction.dot(motion_velocity) + slide_motion.slide(up_direction); + } + } + // Reset the gravity accumulation when touching the ground. if (on_floor && !vel_dir_facing_up) { motion_velocity = motion_velocity.slide(up_direction); diff --git a/scene/2d/ray_cast_2d.cpp b/scene/2d/ray_cast_2d.cpp index 0a8e9e2a58..f9830a8743 100644 --- a/scene/2d/ray_cast_2d.cpp +++ b/scene/2d/ray_cast_2d.cpp @@ -192,7 +192,17 @@ void RayCast2D::_update_raycast_state() { PhysicsDirectSpaceState2D::RayResult rr; bool prev_collision_state = collided; - if (dss->intersect_ray(gt.get_origin(), gt.xform(to), rr, exclude, collision_mask, collide_with_bodies, collide_with_areas)) { + + PhysicsDirectSpaceState2D::RayParameters ray_params; + ray_params.from = gt.get_origin(); + ray_params.to = gt.xform(to); + ray_params.exclude = exclude; + ray_params.collision_mask = collision_mask; + ray_params.collide_with_bodies = collide_with_bodies; + ray_params.collide_with_areas = collide_with_areas; + ray_params.hit_from_inside = hit_from_inside; + + if (dss->intersect_ray(ray_params, rr)) { collided = true; against = rr.collider_id; collision_point = rr.position; @@ -281,22 +291,30 @@ void RayCast2D::clear_exceptions() { exclude.clear(); } -void RayCast2D::set_collide_with_areas(bool p_clip) { - collide_with_areas = p_clip; +void RayCast2D::set_collide_with_areas(bool p_enabled) { + collide_with_areas = p_enabled; } bool RayCast2D::is_collide_with_areas_enabled() const { return collide_with_areas; } -void RayCast2D::set_collide_with_bodies(bool p_clip) { - collide_with_bodies = p_clip; +void RayCast2D::set_collide_with_bodies(bool p_enabled) { + collide_with_bodies = p_enabled; } bool RayCast2D::is_collide_with_bodies_enabled() const { return collide_with_bodies; } +void RayCast2D::set_hit_from_inside(bool p_enabled) { + hit_from_inside = p_enabled; +} + +bool RayCast2D::is_hit_from_inside_enabled() const { + return hit_from_inside; +} + void RayCast2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_enabled", "enabled"), &RayCast2D::set_enabled); ClassDB::bind_method(D_METHOD("is_enabled"), &RayCast2D::is_enabled); @@ -335,10 +353,14 @@ void RayCast2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_collide_with_bodies", "enable"), &RayCast2D::set_collide_with_bodies); ClassDB::bind_method(D_METHOD("is_collide_with_bodies_enabled"), &RayCast2D::is_collide_with_bodies_enabled); + ClassDB::bind_method(D_METHOD("set_hit_from_inside", "enable"), &RayCast2D::set_hit_from_inside); + ClassDB::bind_method(D_METHOD("is_hit_from_inside_enabled"), &RayCast2D::is_hit_from_inside_enabled); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "enabled"), "set_enabled", "is_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "exclude_parent"), "set_exclude_parent_body", "get_exclude_parent_body"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "target_position"), "set_target_position", "get_target_position"); ADD_PROPERTY(PropertyInfo(Variant::INT, "collision_mask", PROPERTY_HINT_LAYERS_2D_PHYSICS), "set_collision_mask", "get_collision_mask"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "hit_from_inside"), "set_hit_from_inside", "is_hit_from_inside_enabled"); ADD_GROUP("Collide With", "collide_with"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "collide_with_areas", PROPERTY_HINT_LAYERS_3D_PHYSICS), "set_collide_with_areas", "is_collide_with_areas_enabled"); diff --git a/scene/2d/ray_cast_2d.h b/scene/2d/ray_cast_2d.h index 65b6e7899b..3ee09fad32 100644 --- a/scene/2d/ray_cast_2d.h +++ b/scene/2d/ray_cast_2d.h @@ -51,6 +51,8 @@ class RayCast2D : public Node2D { bool collide_with_areas = false; bool collide_with_bodies = true; + bool hit_from_inside = false; + void _draw_debug_shape(); protected: @@ -65,6 +67,9 @@ public: void set_collide_with_bodies(bool p_clip); bool is_collide_with_bodies_enabled() const; + void set_hit_from_inside(bool p_enable); + bool is_hit_from_inside_enabled() const; + void set_enabled(bool p_enabled); bool is_enabled() const; diff --git a/scene/2d/shape_cast_2d.cpp b/scene/2d/shape_cast_2d.cpp new file mode 100644 index 0000000000..50b44eb4ef --- /dev/null +++ b/scene/2d/shape_cast_2d.cpp @@ -0,0 +1,459 @@ +/*************************************************************************/ +/* shape_cast_2d.cpp */ +/*************************************************************************/ +/* 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. */ +/*************************************************************************/ + +#include "shape_cast_2d.h" + +#include "core/config/engine.h" +#include "core/core_string_names.h" +#include "scene/2d/collision_object_2d.h" +#include "scene/2d/physics_body_2d.h" +#include "scene/resources/circle_shape_2d.h" +#include "servers/physics_2d/godot_physics_server_2d.h" + +void ShapeCast2D::set_target_position(const Vector2 &p_point) { + target_position = p_point; + if (is_inside_tree() && (Engine::get_singleton()->is_editor_hint() || get_tree()->is_debugging_collisions_hint())) { + update(); + } +} + +Vector2 ShapeCast2D::get_target_position() const { + return target_position; +} + +void ShapeCast2D::set_margin(real_t p_margin) { + margin = p_margin; +} + +real_t ShapeCast2D::get_margin() const { + return margin; +} + +void ShapeCast2D::set_max_results(int p_max_results) { + max_results = p_max_results; +} + +int ShapeCast2D::get_max_results() const { + return max_results; +} + +void ShapeCast2D::set_collision_mask(uint32_t p_mask) { + collision_mask = p_mask; +} + +uint32_t ShapeCast2D::get_collision_mask() const { + return collision_mask; +} + +void ShapeCast2D::set_collision_mask_value(int p_layer_number, bool p_value) { + ERR_FAIL_COND_MSG(p_layer_number < 1, "Collision layer number must be between 1 and 32 inclusive."); + ERR_FAIL_COND_MSG(p_layer_number > 32, "Collision layer number must be between 1 and 32 inclusive."); + uint32_t mask = get_collision_mask(); + if (p_value) { + mask |= 1 << (p_layer_number - 1); + } else { + mask &= ~(1 << (p_layer_number - 1)); + } + set_collision_mask(mask); +} + +bool ShapeCast2D::get_collision_mask_value(int p_layer_number) const { + ERR_FAIL_COND_V_MSG(p_layer_number < 1, false, "Collision layer number must be between 1 and 32 inclusive."); + ERR_FAIL_COND_V_MSG(p_layer_number > 32, false, "Collision layer number must be between 1 and 32 inclusive."); + return get_collision_mask() & (1 << (p_layer_number - 1)); +} + +int ShapeCast2D::get_collision_count() const { + return result.size(); +} + +bool ShapeCast2D::is_colliding() const { + return collided; +} + +Object *ShapeCast2D::get_collider(int p_idx) const { + ERR_FAIL_INDEX_V_MSG(p_idx, result.size(), nullptr, "No collider found."); + + if (result[p_idx].collider_id.is_null()) { + return nullptr; + } + return ObjectDB::get_instance(result[p_idx].collider_id); +} + +int ShapeCast2D::get_collider_shape(int p_idx) const { + ERR_FAIL_INDEX_V_MSG(p_idx, result.size(), -1, "No collider shape found."); + return result[p_idx].shape; +} + +Vector2 ShapeCast2D::get_collision_point(int p_idx) const { + ERR_FAIL_INDEX_V_MSG(p_idx, result.size(), Vector2(), "No collision point found."); + return result[p_idx].point; +} + +Vector2 ShapeCast2D::get_collision_normal(int p_idx) const { + ERR_FAIL_INDEX_V_MSG(p_idx, result.size(), Vector2(), "No collision normal found."); + return result[p_idx].normal; +} + +real_t ShapeCast2D::get_closest_collision_safe_fraction() const { + return collision_safe_fraction; +} + +real_t ShapeCast2D::get_closest_collision_unsafe_fraction() const { + return collision_unsafe_fraction; +} + +void ShapeCast2D::set_enabled(bool p_enabled) { + enabled = p_enabled; + update(); + if (is_inside_tree() && !Engine::get_singleton()->is_editor_hint()) { + set_physics_process_internal(p_enabled); + } + if (!p_enabled) { + collided = false; + } +} + +bool ShapeCast2D::is_enabled() const { + return enabled; +} + +void ShapeCast2D::set_shape(const Ref<Shape2D> &p_shape) { + shape = p_shape; + if (p_shape.is_valid()) { + shape->connect(CoreStringNames::get_singleton()->changed, callable_mp(this, &ShapeCast2D::_redraw_shape)); + shape_rid = shape->get_rid(); + } + update_configuration_warnings(); + update(); +} + +Ref<Shape2D> ShapeCast2D::get_shape() const { + return shape; +} + +void ShapeCast2D::set_exclude_parent_body(bool p_exclude_parent_body) { + if (exclude_parent_body == p_exclude_parent_body) { + return; + } + exclude_parent_body = p_exclude_parent_body; + + if (!is_inside_tree()) { + return; + } + if (Object::cast_to<CollisionObject2D>(get_parent())) { + if (exclude_parent_body) { + exclude.insert(Object::cast_to<CollisionObject2D>(get_parent())->get_rid()); + } else { + exclude.erase(Object::cast_to<CollisionObject2D>(get_parent())->get_rid()); + } + } +} + +bool ShapeCast2D::get_exclude_parent_body() const { + return exclude_parent_body; +} + +void ShapeCast2D::_redraw_shape() { + update(); +} + +void ShapeCast2D::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: { + if (enabled && !Engine::get_singleton()->is_editor_hint()) { + set_physics_process_internal(true); + } else { + set_physics_process_internal(false); + } + if (Object::cast_to<CollisionObject2D>(get_parent())) { + if (exclude_parent_body) { + exclude.insert(Object::cast_to<CollisionObject2D>(get_parent())->get_rid()); + } else { + exclude.erase(Object::cast_to<CollisionObject2D>(get_parent())->get_rid()); + } + } + } break; + case NOTIFICATION_EXIT_TREE: { + if (enabled) { + set_physics_process_internal(false); + } + } break; + + case NOTIFICATION_DRAW: { +#ifdef TOOLS_ENABLED + ERR_FAIL_COND(!is_inside_tree()); + if (!Engine::get_singleton()->is_editor_hint() && !get_tree()->is_debugging_collisions_hint()) { + break; + } + if (shape.is_null()) { + break; + } + Color draw_col = get_tree()->get_debug_collisions_color(); + if (!enabled) { + float g = draw_col.get_v(); + draw_col.r = g; + draw_col.g = g; + draw_col.b = g; + } + // Draw continuos chain of shapes along the cast. + const int steps = MAX(2, target_position.length() / shape->get_rect().get_size().length() * 4); + for (int i = 0; i <= steps; ++i) { + Vector2 t = (real_t(i) / steps) * target_position; + draw_set_transform(t, 0.0, Size2(1, 1)); + shape->draw(get_canvas_item(), draw_col); + } + draw_set_transform(Vector2(), 0.0, Size2(1, 1)); + + // Draw an arrow indicating where the ShapeCast is pointing to. + if (target_position != Vector2()) { + Transform2D xf; + xf.rotate(target_position.angle()); + xf.translate(Vector2(target_position.length(), 0)); + + draw_line(Vector2(), target_position, draw_col, 2); + Vector<Vector2> pts; + float tsize = 8; + pts.push_back(xf.xform(Vector2(tsize, 0))); + pts.push_back(xf.xform(Vector2(0, Math_SQRT12 * tsize))); + pts.push_back(xf.xform(Vector2(0, -Math_SQRT12 * tsize))); + Vector<Color> cols; + for (int i = 0; i < 3; i++) + cols.push_back(draw_col); + + draw_primitive(pts, cols, Vector<Vector2>()); + } +#endif + } break; + case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: { + if (!enabled) { + break; + } + _update_shapecast_state(); + } break; + } +} + +void ShapeCast2D::_update_shapecast_state() { + result.clear(); + + ERR_FAIL_COND_MSG(shape.is_null(), "Invalid shape."); + + Ref<World2D> w2d = get_world_2d(); + ERR_FAIL_COND(w2d.is_null()); + + PhysicsDirectSpaceState2D *dss = PhysicsServer2D::get_singleton()->space_get_direct_state(w2d->get_space()); + ERR_FAIL_COND(!dss); + + Transform2D gt = get_global_transform(); + + PhysicsDirectSpaceState2D::ShapeParameters params; + params.shape_rid = shape_rid; + params.transform = gt; + params.motion = gt.basis_xform(target_position); + params.margin = margin; + params.exclude = exclude; + params.collision_mask = collision_mask; + params.collide_with_bodies = collide_with_bodies; + params.collide_with_areas = collide_with_areas; + + collision_safe_fraction = 0.0; + collision_unsafe_fraction = 0.0; + + if (target_position != Vector2()) { + dss->cast_motion(params, collision_safe_fraction, collision_unsafe_fraction); + if (collision_unsafe_fraction < 1.0) { + // Move shape transform to the point of impact, + // so we can collect contact info at that point. + gt.set_origin(gt.get_origin() + params.motion * (collision_unsafe_fraction + CMP_EPSILON)); + params.transform = gt; + } + } + // Regardless of whether the shape is stuck or it's moved along + // the motion vector, we'll only consider static collisions from now on. + params.motion = Vector2(); + + bool intersected = true; + while (intersected && result.size() < max_results) { + PhysicsDirectSpaceState2D::ShapeRestInfo info; + intersected = dss->rest_info(params, &info); + if (intersected) { + result.push_back(info); + params.exclude.insert(info.rid); + } + } + collided = !result.is_empty(); +} + +void ShapeCast2D::force_shapecast_update() { + _update_shapecast_state(); +} + +void ShapeCast2D::add_exception_rid(const RID &p_rid) { + exclude.insert(p_rid); +} + +void ShapeCast2D::add_exception(const Object *p_object) { + ERR_FAIL_NULL(p_object); + const CollisionObject2D *co = Object::cast_to<CollisionObject2D>(p_object); + if (!co) { + return; + } + add_exception_rid(co->get_rid()); +} + +void ShapeCast2D::remove_exception_rid(const RID &p_rid) { + exclude.erase(p_rid); +} + +void ShapeCast2D::remove_exception(const Object *p_object) { + ERR_FAIL_NULL(p_object); + const CollisionObject2D *co = Object::cast_to<CollisionObject2D>(p_object); + if (!co) { + return; + } + remove_exception_rid(co->get_rid()); +} + +void ShapeCast2D::clear_exceptions() { + exclude.clear(); +} + +void ShapeCast2D::set_collide_with_areas(bool p_clip) { + collide_with_areas = p_clip; +} + +bool ShapeCast2D::is_collide_with_areas_enabled() const { + return collide_with_areas; +} + +void ShapeCast2D::set_collide_with_bodies(bool p_clip) { + collide_with_bodies = p_clip; +} + +bool ShapeCast2D::is_collide_with_bodies_enabled() const { + return collide_with_bodies; +} + +Array ShapeCast2D::_get_collision_result() const { + Array ret; + + for (int i = 0; i < result.size(); ++i) { + const PhysicsDirectSpaceState2D::ShapeRestInfo &sri = result[i]; + + Dictionary col; + col["point"] = sri.point; + col["normal"] = sri.normal; + col["rid"] = sri.rid; + col["collider"] = ObjectDB::get_instance(sri.collider_id); + col["collider_id"] = sri.collider_id; + col["shape"] = sri.shape; + col["linear_velocity"] = sri.linear_velocity; + + ret.push_back(col); + } + return ret; +} + +TypedArray<String> ShapeCast2D::get_configuration_warnings() const { + TypedArray<String> warnings = Node2D::get_configuration_warnings(); + + if (shape.is_null()) { + warnings.push_back(TTR("This node cannot interact with other objects unless a Shape2D is assigned.")); + } + return warnings; +} + +void ShapeCast2D::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_enabled", "enabled"), &ShapeCast2D::set_enabled); + ClassDB::bind_method(D_METHOD("is_enabled"), &ShapeCast2D::is_enabled); + + ClassDB::bind_method(D_METHOD("set_shape", "shape"), &ShapeCast2D::set_shape); + ClassDB::bind_method(D_METHOD("get_shape"), &ShapeCast2D::get_shape); + + ClassDB::bind_method(D_METHOD("set_target_position", "local_point"), &ShapeCast2D::set_target_position); + ClassDB::bind_method(D_METHOD("get_target_position"), &ShapeCast2D::get_target_position); + + ClassDB::bind_method(D_METHOD("set_margin", "margin"), &ShapeCast2D::set_margin); + ClassDB::bind_method(D_METHOD("get_margin"), &ShapeCast2D::get_margin); + + ClassDB::bind_method(D_METHOD("set_max_results", "max_results"), &ShapeCast2D::set_max_results); + ClassDB::bind_method(D_METHOD("get_max_results"), &ShapeCast2D::get_max_results); + + ClassDB::bind_method(D_METHOD("is_colliding"), &ShapeCast2D::is_colliding); + ClassDB::bind_method(D_METHOD("get_collision_count"), &ShapeCast2D::get_collision_count); + + ClassDB::bind_method(D_METHOD("force_shapecast_update"), &ShapeCast2D::force_shapecast_update); + + ClassDB::bind_method(D_METHOD("get_collider", "index"), &ShapeCast2D::get_collider); + ClassDB::bind_method(D_METHOD("get_collider_shape", "index"), &ShapeCast2D::get_collider_shape); + ClassDB::bind_method(D_METHOD("get_collision_point", "index"), &ShapeCast2D::get_collision_point); + ClassDB::bind_method(D_METHOD("get_collision_normal", "index"), &ShapeCast2D::get_collision_normal); + + ClassDB::bind_method(D_METHOD("get_closest_collision_safe_fraction"), &ShapeCast2D::get_closest_collision_safe_fraction); + ClassDB::bind_method(D_METHOD("get_closest_collision_unsafe_fraction"), &ShapeCast2D::get_closest_collision_unsafe_fraction); + + ClassDB::bind_method(D_METHOD("add_exception_rid", "rid"), &ShapeCast2D::add_exception_rid); + ClassDB::bind_method(D_METHOD("add_exception", "node"), &ShapeCast2D::add_exception); + + ClassDB::bind_method(D_METHOD("remove_exception_rid", "rid"), &ShapeCast2D::remove_exception_rid); + ClassDB::bind_method(D_METHOD("remove_exception", "node"), &ShapeCast2D::remove_exception); + + ClassDB::bind_method(D_METHOD("clear_exceptions"), &ShapeCast2D::clear_exceptions); + + ClassDB::bind_method(D_METHOD("set_collision_mask", "mask"), &ShapeCast2D::set_collision_mask); + ClassDB::bind_method(D_METHOD("get_collision_mask"), &ShapeCast2D::get_collision_mask); + + ClassDB::bind_method(D_METHOD("set_collision_mask_value", "layer_number", "value"), &ShapeCast2D::set_collision_mask_value); + ClassDB::bind_method(D_METHOD("get_collision_mask_value", "layer_number"), &ShapeCast2D::get_collision_mask_value); + + ClassDB::bind_method(D_METHOD("set_exclude_parent_body", "mask"), &ShapeCast2D::set_exclude_parent_body); + ClassDB::bind_method(D_METHOD("get_exclude_parent_body"), &ShapeCast2D::get_exclude_parent_body); + + ClassDB::bind_method(D_METHOD("set_collide_with_areas", "enable"), &ShapeCast2D::set_collide_with_areas); + ClassDB::bind_method(D_METHOD("is_collide_with_areas_enabled"), &ShapeCast2D::is_collide_with_areas_enabled); + + ClassDB::bind_method(D_METHOD("set_collide_with_bodies", "enable"), &ShapeCast2D::set_collide_with_bodies); + ClassDB::bind_method(D_METHOD("is_collide_with_bodies_enabled"), &ShapeCast2D::is_collide_with_bodies_enabled); + + ClassDB::bind_method(D_METHOD("_get_collision_result"), &ShapeCast2D::_get_collision_result); + + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "enabled"), "set_enabled", "is_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "shape", PROPERTY_HINT_RESOURCE_TYPE, "Shape2D"), "set_shape", "get_shape"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "exclude_parent"), "set_exclude_parent_body", "get_exclude_parent_body"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "target_position"), "set_target_position", "get_target_position"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "margin", PROPERTY_HINT_RANGE, "0,100,0.01"), "set_margin", "get_margin"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "max_results"), "set_max_results", "get_max_results"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "collision_mask", PROPERTY_HINT_LAYERS_2D_PHYSICS), "set_collision_mask", "get_collision_mask"); + ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "collision_result", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "", "_get_collision_result"); + ADD_GROUP("Collide With", "collide_with"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "collide_with_areas", PROPERTY_HINT_LAYERS_3D_PHYSICS), "set_collide_with_areas", "is_collide_with_areas_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "collide_with_bodies", PROPERTY_HINT_LAYERS_3D_PHYSICS), "set_collide_with_bodies", "is_collide_with_bodies_enabled"); +} diff --git a/scene/2d/shape_cast_2d.h b/scene/2d/shape_cast_2d.h new file mode 100644 index 0000000000..fca6b46155 --- /dev/null +++ b/scene/2d/shape_cast_2d.h @@ -0,0 +1,120 @@ +/*************************************************************************/ +/* shape_cast_2d.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. */ +/*************************************************************************/ + +#ifndef SHAPE_CAST_2D +#define SHAPE_CAST_2D + +#include "scene/2d/node_2d.h" +#include "scene/resources/shape_2d.h" + +class ShapeCast2D : public Node2D { + GDCLASS(ShapeCast2D, Node2D); + + bool enabled = true; + + Ref<Shape2D> shape; + RID shape_rid; + Vector2 target_position = Vector2(0, 50); + + Set<RID> exclude; + real_t margin = 0.0; + uint32_t collision_mask = 1; + bool exclude_parent_body = true; + bool collide_with_areas = false; + bool collide_with_bodies = true; + + // Result + int max_results = 32; + Vector<PhysicsDirectSpaceState2D::ShapeRestInfo> result; + bool collided = false; + real_t collision_safe_fraction = 1.0; + real_t collision_unsafe_fraction = 1.0; + + Array _get_collision_result() const; + void _redraw_shape(); + +protected: + void _notification(int p_what); + void _update_shapecast_state(); + static void _bind_methods(); + +public: + void set_collide_with_areas(bool p_clip); + bool is_collide_with_areas_enabled() const; + + void set_collide_with_bodies(bool p_clip); + bool is_collide_with_bodies_enabled() const; + + void set_enabled(bool p_enabled); + bool is_enabled() const; + + void set_shape(const Ref<Shape2D> &p_shape); + Ref<Shape2D> get_shape() const; + + void set_target_position(const Vector2 &p_point); + Vector2 get_target_position() const; + + void set_margin(real_t p_margin); + real_t get_margin() const; + + void set_max_results(int p_max_results); + int get_max_results() const; + + void set_collision_mask(uint32_t p_mask); + uint32_t get_collision_mask() const; + + void set_collision_mask_value(int p_layer_number, bool p_value); + bool get_collision_mask_value(int p_layer_number) const; + + void set_exclude_parent_body(bool p_exclude_parent_body); + bool get_exclude_parent_body() const; + + void force_shapecast_update(); + bool is_colliding() const; + + int get_collision_count() const; + Object *get_collider(int p_idx) const; + int get_collider_shape(int p_idx) const; + Vector2 get_collision_point(int p_idx) const; + Vector2 get_collision_normal(int p_idx) const; + + real_t get_closest_collision_safe_fraction() const; + real_t get_closest_collision_unsafe_fraction() const; + + void add_exception_rid(const RID &p_rid); + void add_exception(const Object *p_object); + void remove_exception_rid(const RID &p_rid); + void remove_exception(const Object *p_object); + void clear_exceptions(); + + TypedArray<String> get_configuration_warnings() const override; +}; + +#endif diff --git a/scene/2d/tile_map.cpp b/scene/2d/tile_map.cpp index 8af7637ffd..96c4164721 100644 --- a/scene/2d/tile_map.cpp +++ b/scene/2d/tile_map.cpp @@ -1259,7 +1259,7 @@ void TileMap::_rendering_draw_quadrant_debug(TileMapQuadrant *p_quadrant) { TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source); if (atlas_source) { Vector2i grid_size = atlas_source->get_atlas_grid_size(); - if (!atlas_source->get_texture().is_valid() || c.get_atlas_coords().x >= grid_size.x || c.get_atlas_coords().y >= grid_size.y) { + if (!atlas_source->get_runtime_texture().is_valid() || c.get_atlas_coords().x >= grid_size.x || c.get_atlas_coords().y >= grid_size.y) { // Generate a random color from the hashed values of the tiles. Array to_hash; to_hash.push_back(c.source_id); @@ -1299,7 +1299,7 @@ void TileMap::draw_tile(RID p_canvas_item, Vector2i p_position, const Ref<TileSe } // Get the texture. - Ref<Texture2D> tex = atlas_source->get_texture(); + Ref<Texture2D> tex = atlas_source->get_runtime_texture(); if (!tex.is_valid()) { return; } @@ -1321,7 +1321,7 @@ void TileMap::draw_tile(RID p_canvas_item, Vector2i p_position, const Ref<TileSe // Get destination rect. Rect2 dest_rect; - dest_rect.size = atlas_source->get_tile_texture_region(p_atlas_coords).size; + dest_rect.size = atlas_source->get_runtime_tile_texture_region(p_atlas_coords).size; dest_rect.size.x += FP_ADJUST; dest_rect.size.y += FP_ADJUST; @@ -1342,10 +1342,10 @@ void TileMap::draw_tile(RID p_canvas_item, Vector2i p_position, const Ref<TileSe // Draw the tile. if (p_frame >= 0) { - Rect2i source_rect = atlas_source->get_tile_texture_region(p_atlas_coords, p_frame); + Rect2i source_rect = atlas_source->get_runtime_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); + Rect2i source_rect = atlas_source->get_runtime_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); @@ -1355,7 +1355,7 @@ void TileMap::draw_tile(RID p_canvas_item, Vector2i p_position, const Ref<TileSe 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); + Rect2i source_rect = atlas_source->get_runtime_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; diff --git a/scene/3d/area_3d.cpp b/scene/3d/area_3d.cpp index e459c42e8c..073543638f 100644 --- a/scene/3d/area_3d.cpp +++ b/scene/3d/area_3d.cpp @@ -33,13 +33,13 @@ #include "scene/scene_string_names.h" #include "servers/audio_server.h" -void Area3D::set_space_override_mode(SpaceOverride p_mode) { - space_override = p_mode; - PhysicsServer3D::get_singleton()->area_set_space_override_mode(get_rid(), PhysicsServer3D::AreaSpaceOverrideMode(p_mode)); +void Area3D::set_gravity_space_override_mode(SpaceOverride p_mode) { + gravity_space_override = p_mode; + PhysicsServer3D::get_singleton()->area_set_param(get_rid(), PhysicsServer3D::AREA_PARAM_GRAVITY_OVERRIDE_MODE, p_mode); } -Area3D::SpaceOverride Area3D::get_space_override_mode() const { - return space_override; +Area3D::SpaceOverride Area3D::get_gravity_space_override_mode() const { + return gravity_space_override; } void Area3D::set_gravity_is_point(bool p_enabled) { @@ -51,21 +51,30 @@ bool Area3D::is_gravity_a_point() const { return gravity_is_point; } -void Area3D::set_gravity_distance_scale(real_t p_scale) { +void Area3D::set_gravity_point_distance_scale(real_t p_scale) { gravity_distance_scale = p_scale; PhysicsServer3D::get_singleton()->area_set_param(get_rid(), PhysicsServer3D::AREA_PARAM_GRAVITY_DISTANCE_SCALE, p_scale); } -real_t Area3D::get_gravity_distance_scale() const { +real_t Area3D::get_gravity_point_distance_scale() const { return gravity_distance_scale; } -void Area3D::set_gravity_vector(const Vector3 &p_vec) { - gravity_vec = p_vec; - PhysicsServer3D::get_singleton()->area_set_param(get_rid(), PhysicsServer3D::AREA_PARAM_GRAVITY_VECTOR, p_vec); +void Area3D::set_gravity_point_center(const Vector3 &p_center) { + gravity_vec = p_center; + PhysicsServer3D::get_singleton()->area_set_param(get_rid(), PhysicsServer3D::AREA_PARAM_GRAVITY_VECTOR, p_center); } -Vector3 Area3D::get_gravity_vector() const { +const Vector3 &Area3D::get_gravity_point_center() const { + return gravity_vec; +} + +void Area3D::set_gravity_direction(const Vector3 &p_direction) { + gravity_vec = p_direction; + PhysicsServer3D::get_singleton()->area_set_param(get_rid(), PhysicsServer3D::AREA_PARAM_GRAVITY_VECTOR, p_direction); +} + +const Vector3 &Area3D::get_gravity_direction() const { return gravity_vec; } @@ -78,6 +87,24 @@ real_t Area3D::get_gravity() const { return gravity; } +void Area3D::set_linear_damp_space_override_mode(SpaceOverride p_mode) { + linear_damp_space_override = p_mode; + PhysicsServer3D::get_singleton()->area_set_param(get_rid(), PhysicsServer3D::AREA_PARAM_LINEAR_DAMP_OVERRIDE_MODE, p_mode); +} + +Area3D::SpaceOverride Area3D::get_linear_damp_space_override_mode() const { + return linear_damp_space_override; +} + +void Area3D::set_angular_damp_space_override_mode(SpaceOverride p_mode) { + angular_damp_space_override = p_mode; + PhysicsServer3D::get_singleton()->area_set_param(get_rid(), PhysicsServer3D::AREA_PARAM_ANGULAR_DAMP_OVERRIDE_MODE, p_mode); +} + +Area3D::SpaceOverride Area3D::get_angular_damp_space_override_mode() const { + return angular_damp_space_override; +} + void Area3D::set_linear_damp(real_t p_linear_damp) { linear_damp = p_linear_damp; PhysicsServer3D::get_singleton()->area_set_param(get_rid(), PhysicsServer3D::AREA_PARAM_LINEAR_DAMP, p_linear_damp); @@ -579,27 +606,58 @@ void Area3D::_validate_property(PropertyInfo &property) const { } property.hint_string = options; + } else if (property.name.begins_with("gravity") && property.name != "gravity_space_override") { + if (gravity_space_override == SPACE_OVERRIDE_DISABLED) { + property.usage = PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL; + } else { + if (gravity_is_point) { + if (property.name == "gravity_direction") { + property.usage = PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL; + } + } else { + if (property.name.begins_with("gravity_point_")) { + property.usage = PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL; + } + } + } + } else if (property.name.begins_with("linear_damp") && property.name != "linear_damp_space_override") { + if (linear_damp_space_override == SPACE_OVERRIDE_DISABLED) { + property.usage = PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL; + } + } else if (property.name.begins_with("angular_damp") && property.name != "angular_damp_space_override") { + if (angular_damp_space_override == SPACE_OVERRIDE_DISABLED) { + property.usage = PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL; + } } CollisionObject3D::_validate_property(property); } void Area3D::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_space_override_mode", "enable"), &Area3D::set_space_override_mode); - ClassDB::bind_method(D_METHOD("get_space_override_mode"), &Area3D::get_space_override_mode); + ClassDB::bind_method(D_METHOD("set_gravity_space_override_mode", "space_override_mode"), &Area3D::set_gravity_space_override_mode); + ClassDB::bind_method(D_METHOD("get_gravity_space_override_mode"), &Area3D::get_gravity_space_override_mode); ClassDB::bind_method(D_METHOD("set_gravity_is_point", "enable"), &Area3D::set_gravity_is_point); ClassDB::bind_method(D_METHOD("is_gravity_a_point"), &Area3D::is_gravity_a_point); - ClassDB::bind_method(D_METHOD("set_gravity_distance_scale", "distance_scale"), &Area3D::set_gravity_distance_scale); - ClassDB::bind_method(D_METHOD("get_gravity_distance_scale"), &Area3D::get_gravity_distance_scale); + ClassDB::bind_method(D_METHOD("set_gravity_point_distance_scale", "distance_scale"), &Area3D::set_gravity_point_distance_scale); + ClassDB::bind_method(D_METHOD("get_gravity_point_distance_scale"), &Area3D::get_gravity_point_distance_scale); - ClassDB::bind_method(D_METHOD("set_gravity_vector", "vector"), &Area3D::set_gravity_vector); - ClassDB::bind_method(D_METHOD("get_gravity_vector"), &Area3D::get_gravity_vector); + ClassDB::bind_method(D_METHOD("set_gravity_point_center", "center"), &Area3D::set_gravity_point_center); + ClassDB::bind_method(D_METHOD("get_gravity_point_center"), &Area3D::get_gravity_point_center); + + ClassDB::bind_method(D_METHOD("set_gravity_direction", "direction"), &Area3D::set_gravity_direction); + ClassDB::bind_method(D_METHOD("get_gravity_direction"), &Area3D::get_gravity_direction); ClassDB::bind_method(D_METHOD("set_gravity", "gravity"), &Area3D::set_gravity); ClassDB::bind_method(D_METHOD("get_gravity"), &Area3D::get_gravity); + ClassDB::bind_method(D_METHOD("set_linear_damp_space_override_mode", "space_override_mode"), &Area3D::set_linear_damp_space_override_mode); + ClassDB::bind_method(D_METHOD("get_linear_damp_space_override_mode"), &Area3D::get_linear_damp_space_override_mode); + + ClassDB::bind_method(D_METHOD("set_angular_damp_space_override_mode", "space_override_mode"), &Area3D::set_angular_damp_space_override_mode); + ClassDB::bind_method(D_METHOD("get_angular_damp_space_override_mode"), &Area3D::get_angular_damp_space_override_mode); + ClassDB::bind_method(D_METHOD("set_angular_damp", "angular_damp"), &Area3D::set_angular_damp); ClassDB::bind_method(D_METHOD("get_angular_damp"), &Area3D::get_angular_damp); @@ -662,14 +720,23 @@ void Area3D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "monitorable"), "set_monitorable", "is_monitorable"); ADD_PROPERTY(PropertyInfo(Variant::INT, "priority", PROPERTY_HINT_RANGE, "0,128,1"), "set_priority", "get_priority"); - ADD_GROUP("Physics Overrides", ""); - ADD_PROPERTY(PropertyInfo(Variant::INT, "space_override", PROPERTY_HINT_ENUM, "Disabled,Combine,Combine-Replace,Replace,Replace-Combine"), "set_space_override_mode", "get_space_override_mode"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "gravity_point"), "set_gravity_is_point", "is_gravity_a_point"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "gravity_distance_scale", PROPERTY_HINT_RANGE, "0,1024,0.001,or_greater,exp"), "set_gravity_distance_scale", "get_gravity_distance_scale"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "gravity_vec"), "set_gravity_vector", "get_gravity_vector"); + ADD_GROUP("Gravity", "gravity_"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "gravity_space_override", PROPERTY_HINT_ENUM, "Disabled,Combine,Combine-Replace,Replace,Replace-Combine", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), "set_gravity_space_override_mode", "get_gravity_space_override_mode"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "gravity_point", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), "set_gravity_is_point", "is_gravity_a_point"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "gravity_point_distance_scale", PROPERTY_HINT_RANGE, "0,1024,0.001,or_greater,exp"), "set_gravity_point_distance_scale", "get_gravity_point_distance_scale"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "gravity_point_center"), "set_gravity_point_center", "get_gravity_point_center"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "gravity_direction"), "set_gravity_direction", "get_gravity_direction"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "gravity", PROPERTY_HINT_RANGE, "-32,32,0.001,or_lesser,or_greater"), "set_gravity", "get_gravity"); + + ADD_GROUP("Linear Damp", "linear_damp_"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "linear_damp_space_override", PROPERTY_HINT_ENUM, "Disabled,Combine,Combine-Replace,Replace,Replace-Combine", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), "set_linear_damp_space_override_mode", "get_linear_damp_space_override_mode"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "linear_damp", PROPERTY_HINT_RANGE, "0,100,0.001,or_greater"), "set_linear_damp", "get_linear_damp"); + + ADD_GROUP("Angular Damp", "angular_damp_"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "angular_damp_space_override", PROPERTY_HINT_ENUM, "Disabled,Combine,Combine-Replace,Replace,Replace-Combine", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), "set_angular_damp_space_override_mode", "get_angular_damp_space_override_mode"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "angular_damp", PROPERTY_HINT_RANGE, "0,100,0.001,or_greater"), "set_angular_damp", "get_angular_damp"); + + ADD_GROUP("Wind", "wind_"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "wind_force_magnitude", PROPERTY_HINT_RANGE, "0,10,0.001,or_greater"), "set_wind_force_magnitude", "get_wind_force_magnitude"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "wind_attenuation_factor", PROPERTY_HINT_RANGE, "0.0,3.0,0.001,or_greater"), "set_wind_attenuation_factor", "get_wind_attenuation_factor"); ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "wind_source_path", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Node3D"), "set_wind_source_path", "get_wind_source_path"); @@ -694,7 +761,7 @@ void Area3D::_bind_methods() { Area3D::Area3D() : CollisionObject3D(PhysicsServer3D::get_singleton()->area_create(), true) { set_gravity(9.8); - set_gravity_vector(Vector3(0, -1, 0)); + set_gravity_direction(Vector3(0, -1, 0)); set_monitoring(true); set_monitorable(true); } diff --git a/scene/3d/area_3d.h b/scene/3d/area_3d.h index 847d1c5966..7f31be2e17 100644 --- a/scene/3d/area_3d.h +++ b/scene/3d/area_3d.h @@ -47,17 +47,23 @@ public: }; private: - SpaceOverride space_override = SPACE_OVERRIDE_DISABLED; + SpaceOverride gravity_space_override = SPACE_OVERRIDE_DISABLED; Vector3 gravity_vec; real_t gravity; bool gravity_is_point = false; real_t gravity_distance_scale = 0.0; + + SpaceOverride linear_damp_space_override = SPACE_OVERRIDE_DISABLED; + SpaceOverride angular_damp_space_override = SPACE_OVERRIDE_DISABLED; real_t angular_damp = 0.1; real_t linear_damp = 0.1; + int priority = 0; + real_t wind_force_magnitude = 0.0; real_t wind_attenuation_factor = 0.0; NodePath wind_source_path; + bool monitoring = false; bool monitorable = false; bool locked = false; @@ -144,21 +150,30 @@ protected: static void _bind_methods(); public: - void set_space_override_mode(SpaceOverride p_mode); - SpaceOverride get_space_override_mode() const; + void set_gravity_space_override_mode(SpaceOverride p_mode); + SpaceOverride get_gravity_space_override_mode() const; void set_gravity_is_point(bool p_enabled); bool is_gravity_a_point() const; - void set_gravity_distance_scale(real_t p_scale); - real_t get_gravity_distance_scale() const; + void set_gravity_point_distance_scale(real_t p_scale); + real_t get_gravity_point_distance_scale() const; - void set_gravity_vector(const Vector3 &p_vec); - Vector3 get_gravity_vector() const; + void set_gravity_point_center(const Vector3 &p_center); + const Vector3 &get_gravity_point_center() const; + + void set_gravity_direction(const Vector3 &p_direction); + const Vector3 &get_gravity_direction() const; void set_gravity(real_t p_gravity); real_t get_gravity() const; + void set_linear_damp_space_override_mode(SpaceOverride p_mode); + SpaceOverride get_linear_damp_space_override_mode() const; + + void set_angular_damp_space_override_mode(SpaceOverride p_mode); + SpaceOverride get_angular_damp_space_override_mode() const; + void set_angular_damp(real_t p_angular_damp); real_t get_angular_damp() const; diff --git a/scene/3d/audio_stream_player_3d.cpp b/scene/3d/audio_stream_player_3d.cpp index b5e4eac5d5..34f748b197 100644 --- a/scene/3d/audio_stream_player_3d.cpp +++ b/scene/3d/audio_stream_player_3d.cpp @@ -327,7 +327,13 @@ Area3D *AudioStreamPlayer3D::_get_overriding_area() { PhysicsDirectSpaceState3D::ShapeResult sr[MAX_INTERSECT_AREAS]; - int areas = space_state->intersect_point(global_pos, sr, MAX_INTERSECT_AREAS, Set<RID>(), area_mask, false, true); + PhysicsDirectSpaceState3D::PointParameters point_params; + point_params.position = global_pos; + point_params.collision_mask = area_mask; + point_params.collide_with_bodies = false; + point_params.collide_with_areas = true; + + int areas = space_state->intersect_point(point_params, sr, MAX_INTERSECT_AREAS); for (int i = 0; i < areas; i++) { if (!sr[i].collider) { diff --git a/scene/3d/cpu_particles_3d.cpp b/scene/3d/cpu_particles_3d.cpp index 5f13ed3c66..d347d24c2c 100644 --- a/scene/3d/cpu_particles_3d.cpp +++ b/scene/3d/cpu_particles_3d.cpp @@ -1328,7 +1328,7 @@ void CPUParticles3D::convert_from_particles(Node *p_particles) { set_color(material->get_color()); - Ref<GradientTexture> gt = material->get_color_ramp(); + Ref<GradientTexture1D> gt = material->get_color_ramp(); if (gt.is_valid()) { set_color_ramp(gt->get_gradient()); } diff --git a/scene/3d/decal.cpp b/scene/3d/decal.cpp index c9f634a5e5..500bf4d8f5 100644 --- a/scene/3d/decal.cpp +++ b/scene/3d/decal.cpp @@ -175,9 +175,6 @@ TypedArray<String> Decal::get_configuration_warnings() const { } if (cull_mask == 0) { - // NOTE: This warning will not be emitted if none of the 20 checkboxes - // exposed in the editor are checked. This is because there are - // currently 12 unexposed layers in the editor inspector. warnings.push_back(TTR("The decal's Cull Mask has no bits enabled, which means the decal will not paint objects on any layer.\nTo resolve this, enable at least one bit in the Cull Mask property.")); } diff --git a/scene/3d/gpu_particles_collision_3d.cpp b/scene/3d/gpu_particles_collision_3d.cpp index 6ac9364b1a..2235de1599 100644 --- a/scene/3d/gpu_particles_collision_3d.cpp +++ b/scene/3d/gpu_particles_collision_3d.cpp @@ -256,10 +256,10 @@ void GPUParticlesCollisionSDF::_find_closest_distance(const Vector3 &p_pos, cons Vector2 pq1 = v1 - e1 * CLAMP(v1.dot(e1) / e1.dot(e1), 0.0, 1.0); Vector2 pq2 = v2 - e2 * CLAMP(v2.dot(e2) / e2.dot(e2), 0.0, 1.0); - float s = SGN(e0.x * e2.y - e0.y * e2.x); + float s = SIGN(e0.x * e2.y - e0.y * e2.x); Vector2 d2 = Vector2(pq0.dot(pq0), s * (v0.x * e0.y - v0.y * e0.x)).min(Vector2(pq1.dot(pq1), s * (v1.x * e1.y - v1.y * e1.x))).min(Vector2(pq2.dot(pq2), s * (v2.x * e2.y - v2.y * e2.x))); - inside_d = -Math::sqrt(d2.x) * SGN(d2.y); + inside_d = -Math::sqrt(d2.x) * SIGN(d2.y); } //make sure distance to planes is not shorter if inside @@ -288,7 +288,7 @@ void GPUParticlesCollisionSDF::_find_closest_distance(const Vector3 &p_pos, cons Vector3 nor = ba.cross(ac); inside_d = Math::sqrt( - (SGN(ba.cross(nor).dot(pa)) + SGN(cb.cross(nor).dot(pb)) + SGN(ac.cross(nor).dot(pc)) < 2.0) + (SIGN(ba.cross(nor).dot(pa)) + SIGN(cb.cross(nor).dot(pb)) + SIGN(ac.cross(nor).dot(pc)) < 2.0) ? MIN(MIN( Vector3_dot2(ba * CLAMP(ba.dot(pa) / Vector3_dot2(ba), 0.0, 1.0) - pa), Vector3_dot2(cb * CLAMP(cb.dot(pb) / Vector3_dot2(cb), 0.0, 1.0) - pb)), diff --git a/scene/3d/occluder_instance_3d.cpp b/scene/3d/occluder_instance_3d.cpp index 6c548edc74..aeac430cd9 100644 --- a/scene/3d/occluder_instance_3d.cpp +++ b/scene/3d/occluder_instance_3d.cpp @@ -329,9 +329,6 @@ TypedArray<String> OccluderInstance3D::get_configuration_warnings() const { } if (bake_mask == 0) { - // NOTE: This warning will not be emitted if none of the 20 checkboxes - // exposed in the editor are checked. This is because there are - // currently 12 unexposed layers in the editor inspector. warnings.push_back(TTR("The Bake Mask has no bits enabled, which means baking will not produce any occluder meshes for this OccluderInstance3D.\nTo resolve this, enable at least one bit in the Bake Mask property.")); } diff --git a/scene/3d/physics_body_3d.cpp b/scene/3d/physics_body_3d.cpp index d0506227b4..5cb7f431e3 100644 --- a/scene/3d/physics_body_3d.cpp +++ b/scene/3d/physics_body_3d.cpp @@ -174,9 +174,12 @@ bool PhysicsBody3D::test_move(const Transform3D &p_from, const Vector3 &p_linear ERR_FAIL_COND_V(!is_inside_tree(), false); PhysicsServer3D::MotionResult *r = nullptr; + PhysicsServer3D::MotionResult temp_result; if (r_collision.is_valid()) { // Needs const_cast because method bindings don't support non-const Ref. r = const_cast<PhysicsServer3D::MotionResult *>(&r_collision->result); + } else { + r = &temp_result; } // Hack in order to work with calling from _process as well as from _physics_process; calling from thread is risky @@ -184,7 +187,14 @@ bool PhysicsBody3D::test_move(const Transform3D &p_from, const Vector3 &p_linear PhysicsServer3D::MotionParameters parameters(p_from, p_linear_velocity * delta, p_margin); - return PhysicsServer3D::get_singleton()->body_test_motion(get_rid(), parameters, r); + bool colliding = PhysicsServer3D::get_singleton()->body_test_motion(get_rid(), parameters, r); + + if (colliding) { + // Don't report collision when the whole motion is done. + return (r->collision_safe_fraction < 1.0); + } else { + return false; + } } void PhysicsBody3D::set_axis_lock(PhysicsServer3D::BodyAxis p_axis, bool p_lock) { @@ -1145,6 +1155,10 @@ bool CharacterBody3D::move_and_slide() { if (bs) { Vector3 local_position = gt.origin - bs->get_transform().origin; current_platform_velocity = bs->get_velocity_at_local_position(local_position); + } else { + // Body is removed or destroyed, invalidate floor. + current_platform_velocity = Vector3(); + platform_rid = RID(); } } else { current_platform_velocity = Vector3(); diff --git a/scene/3d/ray_cast_3d.cpp b/scene/3d/ray_cast_3d.cpp index fd4c6e7416..bfa397a1f5 100644 --- a/scene/3d/ray_cast_3d.cpp +++ b/scene/3d/ray_cast_3d.cpp @@ -212,9 +212,17 @@ void RayCast3D::_update_raycast_state() { to = Vector3(0, 0.01, 0); } - PhysicsDirectSpaceState3D::RayResult rr; + PhysicsDirectSpaceState3D::RayParameters ray_params; + ray_params.from = gt.get_origin(); + ray_params.to = gt.xform(to); + ray_params.exclude = exclude; + ray_params.collision_mask = collision_mask; + ray_params.collide_with_bodies = collide_with_bodies; + ray_params.collide_with_areas = collide_with_areas; + ray_params.hit_from_inside = hit_from_inside; - if (dss->intersect_ray(gt.get_origin(), gt.xform(to), rr, exclude, collision_mask, collide_with_bodies, collide_with_areas)) { + PhysicsDirectSpaceState3D::RayResult rr; + if (dss->intersect_ray(ray_params, rr)) { collided = true; against = rr.collider_id; collision_point = rr.position; @@ -261,22 +269,30 @@ void RayCast3D::clear_exceptions() { exclude.clear(); } -void RayCast3D::set_collide_with_areas(bool p_clip) { - collide_with_areas = p_clip; +void RayCast3D::set_collide_with_areas(bool p_enabled) { + collide_with_areas = p_enabled; } bool RayCast3D::is_collide_with_areas_enabled() const { return collide_with_areas; } -void RayCast3D::set_collide_with_bodies(bool p_clip) { - collide_with_bodies = p_clip; +void RayCast3D::set_collide_with_bodies(bool p_enabled) { + collide_with_bodies = p_enabled; } bool RayCast3D::is_collide_with_bodies_enabled() const { return collide_with_bodies; } +void RayCast3D::set_hit_from_inside(bool p_enabled) { + hit_from_inside = p_enabled; +} + +bool RayCast3D::is_hit_from_inside_enabled() const { + return hit_from_inside; +} + void RayCast3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_enabled", "enabled"), &RayCast3D::set_enabled); ClassDB::bind_method(D_METHOD("is_enabled"), &RayCast3D::is_enabled); @@ -315,6 +331,9 @@ void RayCast3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_collide_with_bodies", "enable"), &RayCast3D::set_collide_with_bodies); ClassDB::bind_method(D_METHOD("is_collide_with_bodies_enabled"), &RayCast3D::is_collide_with_bodies_enabled); + ClassDB::bind_method(D_METHOD("set_hit_from_inside", "enable"), &RayCast3D::set_hit_from_inside); + ClassDB::bind_method(D_METHOD("is_hit_from_inside_enabled"), &RayCast3D::is_hit_from_inside_enabled); + ClassDB::bind_method(D_METHOD("set_debug_shape_custom_color", "debug_shape_custom_color"), &RayCast3D::set_debug_shape_custom_color); ClassDB::bind_method(D_METHOD("get_debug_shape_custom_color"), &RayCast3D::get_debug_shape_custom_color); @@ -325,6 +344,7 @@ void RayCast3D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "exclude_parent"), "set_exclude_parent_body", "get_exclude_parent_body"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "target_position"), "set_target_position", "get_target_position"); ADD_PROPERTY(PropertyInfo(Variant::INT, "collision_mask", PROPERTY_HINT_LAYERS_3D_PHYSICS), "set_collision_mask", "get_collision_mask"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "hit_from_inside"), "set_hit_from_inside", "is_hit_from_inside_enabled"); ADD_GROUP("Collide With", "collide_with"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "collide_with_areas", PROPERTY_HINT_LAYERS_3D_PHYSICS), "set_collide_with_areas", "is_collide_with_areas_enabled"); diff --git a/scene/3d/ray_cast_3d.h b/scene/3d/ray_cast_3d.h index 3828bfb4c4..5c2a61c35b 100644 --- a/scene/3d/ray_cast_3d.h +++ b/scene/3d/ray_cast_3d.h @@ -65,18 +65,23 @@ class RayCast3D : public Node3D { bool collide_with_areas = false; bool collide_with_bodies = true; + bool hit_from_inside = false; + protected: void _notification(int p_what); void _update_raycast_state(); static void _bind_methods(); public: - void set_collide_with_areas(bool p_clip); + void set_collide_with_areas(bool p_enabled); bool is_collide_with_areas_enabled() const; - void set_collide_with_bodies(bool p_clip); + void set_collide_with_bodies(bool p_enabled); bool is_collide_with_bodies_enabled() const; + void set_hit_from_inside(bool p_enabled); + bool is_hit_from_inside_enabled() const; + void set_enabled(bool p_enabled); bool is_enabled() const; diff --git a/scene/3d/reflection_probe.cpp b/scene/3d/reflection_probe.cpp index 40d4d822c9..f7f19596a7 100644 --- a/scene/3d/reflection_probe.cpp +++ b/scene/3d/reflection_probe.cpp @@ -94,7 +94,7 @@ void ReflectionProbe::set_extents(const Vector3 &p_extents) { } if (extents[i] - 0.01 < ABS(origin_offset[i])) { - origin_offset[i] = SGN(origin_offset[i]) * (extents[i] - 0.01); + origin_offset[i] = SIGN(origin_offset[i]) * (extents[i] - 0.01); } } @@ -113,7 +113,7 @@ void ReflectionProbe::set_origin_offset(const Vector3 &p_extents) { for (int i = 0; i < 3; i++) { if (extents[i] - 0.01 < ABS(origin_offset[i])) { - origin_offset[i] = SGN(origin_offset[i]) * (extents[i] - 0.01); + origin_offset[i] = SIGN(origin_offset[i]) * (extents[i] - 0.01); } } RS::get_singleton()->reflection_probe_set_extents(probe, extents); diff --git a/scene/3d/spring_arm_3d.cpp b/scene/3d/spring_arm_3d.cpp index 116cab19b1..0b5af8823b 100644 --- a/scene/3d/spring_arm_3d.cpp +++ b/scene/3d/spring_arm_3d.cpp @@ -149,10 +149,24 @@ void SpringArm3D::process_spring() { //use camera rotation, but spring arm position Transform3D base_transform = camera->get_global_transform(); base_transform.origin = get_global_transform().origin; - get_world_3d()->get_direct_space_state()->cast_motion(camera->get_pyramid_shape_rid(), base_transform, motion, 0, motion_delta, motion_delta_unsafe, excluded_objects, mask); + + PhysicsDirectSpaceState3D::ShapeParameters shape_params; + shape_params.shape_rid = camera->get_pyramid_shape_rid(); + shape_params.transform = base_transform; + shape_params.motion = motion; + shape_params.exclude = excluded_objects; + shape_params.collision_mask = mask; + + get_world_3d()->get_direct_space_state()->cast_motion(shape_params, motion_delta, motion_delta_unsafe); } else { + PhysicsDirectSpaceState3D::RayParameters ray_params; + ray_params.from = get_global_transform().origin; + ray_params.to = get_global_transform().origin + motion; + ray_params.exclude = excluded_objects; + ray_params.collision_mask = mask; + PhysicsDirectSpaceState3D::RayResult r; - bool intersected = get_world_3d()->get_direct_space_state()->intersect_ray(get_global_transform().origin, get_global_transform().origin + motion, r, excluded_objects, mask); + bool intersected = get_world_3d()->get_direct_space_state()->intersect_ray(ray_params, r); if (intersected) { real_t dist = get_global_transform().origin.distance_to(r.position); dist -= margin; @@ -160,7 +174,14 @@ void SpringArm3D::process_spring() { } } } else { - get_world_3d()->get_direct_space_state()->cast_motion(shape->get_rid(), get_global_transform(), motion, 0, motion_delta, motion_delta_unsafe, excluded_objects, mask); + PhysicsDirectSpaceState3D::ShapeParameters shape_params; + shape_params.shape_rid = shape->get_rid(); + shape_params.transform = get_global_transform(); + shape_params.motion = motion; + shape_params.exclude = excluded_objects; + shape_params.collision_mask = mask; + + get_world_3d()->get_direct_space_state()->cast_motion(shape_params, motion_delta, motion_delta_unsafe); } current_spring_length = spring_length * motion_delta; diff --git a/scene/3d/vehicle_body_3d.cpp b/scene/3d/vehicle_body_3d.cpp index 61cba17cde..90db093137 100644 --- a/scene/3d/vehicle_body_3d.cpp +++ b/scene/3d/vehicle_body_3d.cpp @@ -407,7 +407,13 @@ real_t VehicleBody3D::_ray_cast(int p_idx, PhysicsDirectBodyState3D *s) { PhysicsDirectSpaceState3D *ss = s->get_space_state(); - bool col = ss->intersect_ray(source, target, rr, exclude, get_collision_mask()); + PhysicsDirectSpaceState3D::RayParameters ray_params; + ray_params.from = source; + ray_params.to = target; + ray_params.exclude = exclude; + ray_params.collision_mask = get_collision_mask(); + + bool col = ss->intersect_ray(ray_params, rr); wheel.m_raycastInfo.m_groundObject = nullptr; diff --git a/scene/animation/animation_blend_space_2d.cpp b/scene/animation/animation_blend_space_2d.cpp index b91a2869e4..e621f06ce9 100644 --- a/scene/animation/animation_blend_space_2d.cpp +++ b/scene/animation/animation_blend_space_2d.cpp @@ -30,6 +30,7 @@ #include "animation_blend_space_2d.h" +#include "animation_blend_tree.h" #include "core/math/geometry_2d.h" void AnimationNodeBlendSpace2D::get_parameter_list(List<PropertyInfo> *r_list) const { @@ -531,6 +532,12 @@ double AnimationNodeBlendSpace2D::process(double p_time, bool p_seek) { if (new_closest != closest && new_closest != -1) { float from = 0.0; if (blend_mode == BLEND_MODE_DISCRETE_CARRY && closest != -1) { + //for ping-pong loop + Ref<AnimationNodeAnimation> na_c = static_cast<Ref<AnimationNodeAnimation>>(blend_points[closest].node); + Ref<AnimationNodeAnimation> na_n = static_cast<Ref<AnimationNodeAnimation>>(blend_points[new_closest].node); + if (!na_c.is_null() && !na_n.is_null()) { + na_n->set_backward(na_c->is_backward()); + } //see how much animation remains from = length_internal - blend_node(blend_points[closest].name, blend_points[closest].node, p_time, false, 0.0, FILTER_IGNORE, false); } diff --git a/scene/animation/animation_blend_tree.cpp b/scene/animation/animation_blend_tree.cpp index d18e1a92d8..d6c5d0b51c 100644 --- a/scene/animation/animation_blend_tree.cpp +++ b/scene/animation/animation_blend_tree.cpp @@ -30,6 +30,7 @@ #include "animation_blend_tree.h" +#include "scene/resources/animation.h" #include "scene/scene_string_names.h" void AnimationNodeAnimation::set_animation(const StringName &p_name) { @@ -83,30 +84,55 @@ double AnimationNodeAnimation::process(double p_time, bool p_seek) { } Ref<Animation> anim = ap->get_animation(animation); - - double step; + double anim_size = (double)anim->get_length(); + double step = 0.0; + double prev_time = time; + int pingponged = 0; + bool current_backward = signbit(p_time); if (p_seek) { + step = p_time - time; time = p_time; - step = 0; } else { - time = MAX(0, time + p_time); - step = p_time; + p_time *= backward ? -1.0 : 1.0; + if (!(time == anim_size && !current_backward) && !(time == 0 && current_backward)) { + time = time + p_time; + step = p_time; + } } - double anim_size = anim->get_length(); - - if (anim->has_loop()) { + if (anim->get_loop_mode() == Animation::LoopMode::LOOP_PINGPONG) { if (anim_size) { - time = Math::fposmod(time, anim_size); + if ((int)Math::floor(abs(time - prev_time) / anim_size) % 2 == 0) { + if (prev_time > 0 && time <= 0) { + backward = !backward; + pingponged = -1; + } + if (prev_time < anim_size && time >= anim_size) { + backward = !backward; + pingponged = 1; + } + } + time = Math::pingpong(time, anim_size); } - - } else if (time > anim_size) { - time = anim_size; + } else { + if (anim->get_loop_mode() == Animation::LoopMode::LOOP_LINEAR) { + if (anim_size) { + time = Math::fposmod(time, anim_size); + } + } else if (time < 0) { + time = 0; + } else if (time > anim_size) { + time = anim_size; + } + backward = false; } - blend_animation(animation, time, step, p_seek, 1.0); - + if (play_mode == PLAY_MODE_FORWARD) { + blend_animation(animation, time, step, p_seek, 1.0, pingponged); + } else { + blend_animation(animation, anim_size - time, -step, p_seek, 1.0, pingponged); + } set_parameter(this->time, time); return anim_size - time; @@ -116,11 +142,34 @@ String AnimationNodeAnimation::get_caption() const { return "Animation"; } +void AnimationNodeAnimation::set_play_mode(PlayMode p_play_mode) { + play_mode = p_play_mode; +} + +AnimationNodeAnimation::PlayMode AnimationNodeAnimation::get_play_mode() const { + return play_mode; +} + +void AnimationNodeAnimation::set_backward(bool p_backward) { + backward = p_backward; +} + +bool AnimationNodeAnimation::is_backward() const { + return backward; +} + void AnimationNodeAnimation::_bind_methods() { ClassDB::bind_method(D_METHOD("set_animation", "name"), &AnimationNodeAnimation::set_animation); ClassDB::bind_method(D_METHOD("get_animation"), &AnimationNodeAnimation::get_animation); + ClassDB::bind_method(D_METHOD("set_play_mode", "mode"), &AnimationNodeAnimation::set_play_mode); + ClassDB::bind_method(D_METHOD("get_play_mode"), &AnimationNodeAnimation::get_play_mode); + ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "animation"), "set_animation", "get_animation"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "play_mode", PROPERTY_HINT_ENUM, "Forward,Backward"), "set_play_mode", "get_play_mode"); + + BIND_ENUM_CONSTANT(PLAY_MODE_FORWARD); + BIND_ENUM_CONSTANT(PLAY_MODE_BACKWARD); } AnimationNodeAnimation::AnimationNodeAnimation() { @@ -533,7 +582,7 @@ AnimationNodeBlend3::AnimationNodeBlend3() { ///////////////////////////////// void AnimationNodeTimeScale::get_parameter_list(List<PropertyInfo> *r_list) const { - r_list->push_back(PropertyInfo(Variant::FLOAT, scale, PROPERTY_HINT_RANGE, "0,32,0.01,or_greater")); + r_list->push_back(PropertyInfo(Variant::FLOAT, scale, PROPERTY_HINT_RANGE, "-32,32,0.01,or_lesser,or_greater")); } Variant AnimationNodeTimeScale::get_parameter_default_value(const StringName &p_parameter) const { diff --git a/scene/animation/animation_blend_tree.h b/scene/animation/animation_blend_tree.h index 258443a999..e55dfb58ed 100644 --- a/scene/animation/animation_blend_tree.h +++ b/scene/animation/animation_blend_tree.h @@ -42,12 +42,12 @@ class AnimationNodeAnimation : public AnimationRootNode { uint64_t last_version = 0; bool skip = false; -protected: - void _validate_property(PropertyInfo &property) const override; - - static void _bind_methods(); - public: + enum PlayMode { + PLAY_MODE_FORWARD, + PLAY_MODE_BACKWARD + }; + void get_parameter_list(List<PropertyInfo> *r_list) const override; static Vector<String> (*get_editable_animation_list)(); @@ -58,9 +58,25 @@ public: void set_animation(const StringName &p_name); StringName get_animation() const; + void set_play_mode(PlayMode p_play_mode); + PlayMode get_play_mode() const; + + void set_backward(bool p_backward); + bool is_backward() const; + AnimationNodeAnimation(); + +protected: + void _validate_property(PropertyInfo &property) const override; + static void _bind_methods(); + +private: + PlayMode play_mode = PLAY_MODE_FORWARD; + bool backward = false; }; +VARIANT_ENUM_CAST(AnimationNodeAnimation::PlayMode) + class AnimationNodeOneShot : public AnimationNode { GDCLASS(AnimationNodeOneShot, AnimationNode); diff --git a/scene/animation/animation_player.cpp b/scene/animation/animation_player.cpp index f91422ac1a..93339711bd 100644 --- a/scene/animation/animation_player.cpp +++ b/scene/animation/animation_player.cpp @@ -398,12 +398,13 @@ void AnimationPlayer::_ensure_node_caches(AnimationData *p_anim, Node *p_root_ov } } -void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, double p_time, double p_delta, float p_interp, bool p_is_current, bool p_seeked, bool p_started) { +void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, double p_time, double p_delta, float p_interp, bool p_is_current, bool p_seeked, bool p_started, int p_pingponged) { _ensure_node_caches(p_anim); ERR_FAIL_COND(p_anim->node_cache.size() != p_anim->animation->get_track_count()); Animation *a = p_anim->animation.operator->(); bool can_call = is_inside_tree() && !Engine::get_singleton()->is_editor_hint(); + bool backward = signbit(p_delta); for (int i = 0; i < a->get_track_count(); i++) { // If an animation changes this animation (or it animates itself) @@ -557,8 +558,8 @@ void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, double continue; //eeh not worth it } - float first_key_time = a->track_get_key_time(i, 0); - float transition = 1.0; + double first_key_time = a->track_get_key_time(i, 0); + double transition = 1.0; int first_key = 0; if (first_key_time == 0.0) { @@ -566,13 +567,13 @@ void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, double if (key_count == 1) { continue; //with one key we can't do anything } - transition = a->track_get_key_transition(i, 0); + transition = (double)a->track_get_key_transition(i, 0); first_key_time = a->track_get_key_time(i, 1); first_key = 1; } if (p_time < first_key_time) { - float c = Math::ease(p_time / first_key_time, transition); + double c = Math::ease(p_time / first_key_time, transition); Variant first_value = a->track_get_key_value(i, first_key); Variant interp_value; Variant::interpolate(pa->capture, first_value, c, interp_value); @@ -614,7 +615,7 @@ void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, double } else if (p_is_current && p_delta != 0) { List<int> indices; - a->value_track_get_key_indices(i, p_time, p_delta, &indices); + a->value_track_get_key_indices(i, p_time, p_delta, &indices, p_pingponged); for (int &F : indices) { Variant value = a->track_get_key_value(i, F); @@ -673,7 +674,7 @@ void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, double List<int> indices; - a->method_track_get_key_indices(i, p_time, p_delta, &indices); + a->method_track_get_key_indices(i, p_time, p_delta, &indices, p_pingponged); for (int &E : indices) { StringName method = a->method_track_get_name(i, E); @@ -728,7 +729,7 @@ void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, double TrackNodeCache::BezierAnim *ba = &E->get(); - float bezier = a->bezier_track_interpolate(i, p_time); + real_t bezier = a->bezier_track_interpolate(i, p_time); if (ba->accum_pass != accum_pass) { ERR_CONTINUE(cache_update_bezier_size >= NODE_CACHE_UPDATE_MAX); cache_update_bezier[cache_update_bezier_size++] = ba; @@ -789,7 +790,7 @@ void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, double } else { //find stuff to play List<int> to_play; - a->track_get_key_indices_in_range(i, p_time, p_delta, &to_play); + a->track_get_key_indices_in_range(i, p_time, p_delta, &to_play, p_pingponged); if (to_play.size()) { int idx = to_play.back()->get(); @@ -817,12 +818,14 @@ void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, double nc->audio_start = p_time; } } else if (nc->audio_playing) { - bool loop = a->has_loop(); + bool loop = a->get_loop_mode() != Animation::LoopMode::LOOP_NONE; bool stop = false; - if (!loop && p_time < nc->audio_start) { - stop = true; + if (!loop) { + if ((p_time < nc->audio_start && !backward) || (p_time > nc->audio_start && backward)) { + stop = true; + } } else if (nc->audio_len > 0) { float len = nc->audio_start > p_time ? (a->get_length() - nc->audio_start) + p_time : p_time - nc->audio_start; @@ -863,12 +866,23 @@ void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, double Ref<Animation> anim = player->get_animation(anim_name); - double at_anim_pos; + double at_anim_pos = 0.0; - if (anim->has_loop()) { - at_anim_pos = Math::fposmod(p_time - pos, (double)anim->get_length()); //seek to loop - } else { - at_anim_pos = MIN((double)anim->get_length(), p_time - pos); //seek to end + switch (anim->get_loop_mode()) { + case Animation::LoopMode::LOOP_NONE: { + at_anim_pos = MIN((double)anim->get_length(), p_time - pos); //seek to end + } break; + + case Animation::LoopMode::LOOP_LINEAR: { + at_anim_pos = Math::fposmod(p_time - pos, (double)anim->get_length()); //seek to loop + } break; + + case Animation::LoopMode::LOOP_PINGPONG: { + at_anim_pos = Math::pingpong(p_time - pos, (double)anim->get_length()); + } break; + + default: + break; } if (player->is_playing() || p_seeked) { @@ -883,7 +897,7 @@ void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, double } else { //find stuff to play List<int> to_play; - a->track_get_key_indices_in_range(i, p_time, p_delta, &to_play); + a->track_get_key_indices_in_range(i, p_time, p_delta, &to_play, p_pingponged); if (to_play.size()) { int idx = to_play.back()->get(); @@ -913,46 +927,73 @@ void AnimationPlayer::_animation_process_data(PlaybackData &cd, double p_delta, double next_pos = cd.pos + delta; real_t len = cd.from->animation->get_length(); - bool loop = cd.from->animation->has_loop(); + int pingponged = 0; + + switch (cd.from->animation->get_loop_mode()) { + case Animation::LoopMode::LOOP_NONE: { + if (next_pos < 0) { + next_pos = 0; + } else if (next_pos > len) { + next_pos = len; + } - if (!loop) { - if (next_pos < 0) { - next_pos = 0; - } else if (next_pos > len) { - next_pos = len; - } + bool backwards = signbit(delta); // Negative zero means playing backwards too + delta = next_pos - cd.pos; // Fix delta (after determination of backwards because negative zero is lost here) - bool backwards = signbit(delta); // Negative zero means playing backwards too - delta = next_pos - cd.pos; // Fix delta (after determination of backwards because negative zero is lost here) + if (&cd == &playback.current) { + if (!backwards && cd.pos <= len && next_pos == len) { + //playback finished + end_reached = true; + end_notify = cd.pos < len; // Notify only if not already at the end + } - if (&cd == &playback.current) { - if (!backwards && cd.pos <= len && next_pos == len) { - //playback finished - end_reached = true; - end_notify = cd.pos < len; // Notify only if not already at the end + if (backwards && cd.pos >= 0 && next_pos == 0) { + //playback finished + end_reached = true; + end_notify = cd.pos > 0; // Notify only if not already at the beginning + } } + } break; - if (backwards && cd.pos >= 0 && next_pos == 0) { - //playback finished - end_reached = true; - end_notify = cd.pos > 0; // Notify only if not already at the beginning + case Animation::LoopMode::LOOP_LINEAR: { + double looped_next_pos = Math::fposmod(next_pos, (double)len); + if (looped_next_pos == 0 && next_pos != 0) { + // Loop multiples of the length to it, rather than 0 + // so state at time=length is previewable in the editor + next_pos = len; + } else { + next_pos = looped_next_pos; } - } + } break; - } else { - double looped_next_pos = Math::fposmod(next_pos, (double)len); - if (looped_next_pos == 0 && next_pos != 0) { - // Loop multiples of the length to it, rather than 0 - // so state at time=length is previewable in the editor - next_pos = len; - } else { - next_pos = looped_next_pos; - } + case Animation::LoopMode::LOOP_PINGPONG: { + if ((int)Math::floor(abs(next_pos - cd.pos) / len) % 2 == 0) { + if (next_pos < 0 && cd.pos >= 0) { + cd.speed_scale *= -1.0; + pingponged = -1; + } + if (next_pos > len && cd.pos <= len) { + cd.speed_scale *= -1.0; + pingponged = 1; + } + } + double looped_next_pos = Math::pingpong(next_pos, (double)len); + if (looped_next_pos == 0 && next_pos != 0) { + // Loop multiples of the length to it, rather than 0 + // so state at time=length is previewable in the editor + next_pos = len; + } else { + next_pos = looped_next_pos; + } + } break; + + default: + break; } cd.pos = next_pos; - _animation_process_animation(cd.from, cd.pos, delta, p_blend, &cd == &playback.current, p_seeked, p_started); + _animation_process_animation(cd.from, cd.pos, delta, p_blend, &cd == &playback.current, p_seeked, p_started, pingponged); } void AnimationPlayer::_animation_process2(double p_delta, bool p_started) { diff --git a/scene/animation/animation_player.h b/scene/animation/animation_player.h index d9d88b5510..ea04918988 100644 --- a/scene/animation/animation_player.h +++ b/scene/animation/animation_player.h @@ -231,7 +231,7 @@ private: NodePath root; - void _animation_process_animation(AnimationData *p_anim, double p_time, double p_delta, float p_interp, bool p_is_current = true, bool p_seeked = false, bool p_started = false); + void _animation_process_animation(AnimationData *p_anim, double p_time, double p_delta, float p_interp, bool p_is_current = true, bool p_seeked = false, bool p_started = false, int p_pingponged = 0); void _ensure_node_caches(AnimationData *p_anim, Node *p_root_override = nullptr); void _animation_process_data(PlaybackData &cd, double p_delta, float p_blend, bool p_seeked, bool p_started); diff --git a/scene/animation/animation_tree.cpp b/scene/animation/animation_tree.cpp index 4b71fe7a01..37e754148c 100644 --- a/scene/animation/animation_tree.cpp +++ b/scene/animation/animation_tree.cpp @@ -32,6 +32,7 @@ #include "animation_blend_tree.h" #include "core/config/engine.h" +#include "scene/resources/animation.h" #include "scene/scene_string_names.h" #include "servers/audio/audio_stream.h" @@ -87,7 +88,7 @@ void AnimationNode::get_child_nodes(List<ChildNode> *r_child_nodes) { } } -void AnimationNode::blend_animation(const StringName &p_animation, real_t p_time, real_t p_delta, bool p_seeked, real_t p_blend) { +void AnimationNode::blend_animation(const StringName &p_animation, real_t p_time, real_t p_delta, bool p_seeked, real_t p_blend, int p_pingponged) { ERR_FAIL_COND(!state); ERR_FAIL_COND(!state->player->has_animation(p_animation)); @@ -113,6 +114,7 @@ void AnimationNode::blend_animation(const StringName &p_animation, real_t p_time anim_state.time = p_time; anim_state.animation = animation; anim_state.seeked = p_seeked; + anim_state.pingponged = p_pingponged; state->animation_states.push_back(anim_state); } @@ -418,7 +420,7 @@ void AnimationNode::_bind_methods() { ClassDB::bind_method(D_METHOD("_set_filters", "filters"), &AnimationNode::_set_filters); ClassDB::bind_method(D_METHOD("_get_filters"), &AnimationNode::_get_filters); - ClassDB::bind_method(D_METHOD("blend_animation", "animation", "time", "delta", "seeked", "blend"), &AnimationNode::blend_animation); + ClassDB::bind_method(D_METHOD("blend_animation", "animation", "time", "delta", "seeked", "blend", "pingponged"), &AnimationNode::blend_animation, DEFVAL(0)); ClassDB::bind_method(D_METHOD("blend_node", "name", "node", "time", "seek", "blend", "filter", "optimize"), &AnimationNode::blend_node, DEFVAL(FILTER_IGNORE), DEFVAL(true)); ClassDB::bind_method(D_METHOD("blend_input", "input_index", "time", "seek", "blend", "filter", "optimize"), &AnimationNode::blend_input, DEFVAL(FILTER_IGNORE), DEFVAL(true)); @@ -898,6 +900,10 @@ void AnimationTree::_process_graph(real_t p_delta) { double delta = as.delta; real_t weight = as.blend; bool seeked = as.seeked; + int pingponged = as.pingponged; +#ifndef _3D_DISABLED + bool backward = signbit(delta); +#endif // _3D_DISABLED for (int i = 0; i < a->get_track_count(); i++) { NodePath path = a->track_get_path(i); @@ -939,27 +945,63 @@ void AnimationTree::_process_graph(real_t p_delta) { } if (track->root_motion) { - real_t prev_time = time - delta; - if (prev_time < 0) { - if (!a->has_loop()) { - prev_time = 0; - } else { - prev_time = a->get_length() + prev_time; + double prev_time = time - delta; + if (!backward) { + if (prev_time < 0) { + switch (a->get_loop_mode()) { + case Animation::LOOP_NONE: { + prev_time = 0; + } break; + case Animation::LOOP_LINEAR: { + prev_time = Math::fposmod(prev_time, (double)a->get_length()); + } break; + case Animation::LOOP_PINGPONG: { + prev_time = Math::pingpong(prev_time, (double)a->get_length()); + } break; + default: + break; + } + } + } else { + if (prev_time > a->get_length()) { + switch (a->get_loop_mode()) { + case Animation::LOOP_NONE: { + prev_time = (double)a->get_length(); + } break; + case Animation::LOOP_LINEAR: { + prev_time = Math::fposmod(prev_time, (double)a->get_length()); + } break; + case Animation::LOOP_PINGPONG: { + prev_time = Math::pingpong(prev_time, (double)a->get_length()); + } break; + default: + break; + } } } Vector3 loc[2]; - if (prev_time > time) { - Error err = a->position_track_interpolate(i, prev_time, &loc[0]); - if (err != OK) { - continue; + if (!backward) { + if (prev_time > time) { + Error err = a->position_track_interpolate(i, prev_time, &loc[0]); + if (err != OK) { + continue; + } + a->position_track_interpolate(i, (double)a->get_length(), &loc[1]); + t->loc += (loc[1] - loc[0]) * blend; + prev_time = 0; + } + } else { + if (prev_time < time) { + Error err = a->position_track_interpolate(i, prev_time, &loc[0]); + if (err != OK) { + continue; + } + a->position_track_interpolate(i, 0, &loc[1]); + t->loc += (loc[1] - loc[0]) * blend; + prev_time = 0; } - - a->position_track_interpolate(i, a->get_length(), &loc[1]); - - t->loc += (loc[1] - loc[0]) * blend; - prev_time = 0; } Error err = a->position_track_interpolate(i, prev_time, &loc[0]); @@ -968,17 +1010,13 @@ void AnimationTree::_process_graph(real_t p_delta) { } a->position_track_interpolate(i, time, &loc[1]); - t->loc += (loc[1] - loc[0]) * blend; - - prev_time = 0; + prev_time = !backward ? 0 : (double)a->get_length(); } else { Vector3 loc; Error err = a->position_track_interpolate(i, time, &loc); - //ERR_CONTINUE(err!=OK); //used for testing, should be removed - if (err != OK) { continue; } @@ -1000,29 +1038,65 @@ void AnimationTree::_process_graph(real_t p_delta) { } if (track->root_motion) { - real_t prev_time = time - delta; - if (prev_time < 0) { - if (!a->has_loop()) { - prev_time = 0; - } else { - prev_time = a->get_length() + prev_time; + double prev_time = time - delta; + if (!backward) { + if (prev_time < 0) { + switch (a->get_loop_mode()) { + case Animation::LOOP_NONE: { + prev_time = 0; + } break; + case Animation::LOOP_LINEAR: { + prev_time = Math::fposmod(prev_time, (double)a->get_length()); + } break; + case Animation::LOOP_PINGPONG: { + prev_time = Math::pingpong(prev_time, (double)a->get_length()); + } break; + default: + break; + } + } + } else { + if (prev_time > a->get_length()) { + switch (a->get_loop_mode()) { + case Animation::LOOP_NONE: { + prev_time = (double)a->get_length(); + } break; + case Animation::LOOP_LINEAR: { + prev_time = Math::fposmod(prev_time, (double)a->get_length()); + } break; + case Animation::LOOP_PINGPONG: { + prev_time = Math::pingpong(prev_time, (double)a->get_length()); + } break; + default: + break; + } } } Quaternion rot[2]; - if (prev_time > time) { - Error err = a->rotation_track_interpolate(i, prev_time, &rot[0]); - if (err != OK) { - continue; + if (!backward) { + if (prev_time > time) { + Error err = a->rotation_track_interpolate(i, prev_time, &rot[0]); + if (err != OK) { + continue; + } + a->rotation_track_interpolate(i, (double)a->get_length(), &rot[1]); + Quaternion q = Quaternion().slerp(rot[0].normalized().inverse() * rot[1].normalized(), blend).normalized(); + t->rot = (t->rot * q).normalized(); + prev_time = 0; + } + } else { + if (prev_time < time) { + Error err = a->rotation_track_interpolate(i, prev_time, &rot[0]); + if (err != OK) { + continue; + } + a->rotation_track_interpolate(i, 0, &rot[1]); + Quaternion q = Quaternion().slerp(rot[0].normalized().inverse() * rot[1].normalized(), blend).normalized(); + t->rot = (t->rot * q).normalized(); + prev_time = 0; } - - a->rotation_track_interpolate(i, a->get_length(), &rot[1]); - - Quaternion q = Quaternion().slerp(rot[0].normalized().inverse() * rot[1].normalized(), blend).normalized(); - t->rot = (t->rot * q).normalized(); - - prev_time = 0; } Error err = a->rotation_track_interpolate(i, prev_time, &rot[0]); @@ -1031,18 +1105,14 @@ void AnimationTree::_process_graph(real_t p_delta) { } a->rotation_track_interpolate(i, time, &rot[1]); - Quaternion q = Quaternion().slerp(rot[0].normalized().inverse() * rot[1].normalized(), blend).normalized(); t->rot = (t->rot * q).normalized(); - - prev_time = 0; + prev_time = !backward ? 0 : (double)a->get_length(); } else { Quaternion rot; Error err = a->rotation_track_interpolate(i, time, &rot); - //ERR_CONTINUE(err!=OK); //used for testing, should be removed - if (err != OK) { continue; } @@ -1071,28 +1141,63 @@ void AnimationTree::_process_graph(real_t p_delta) { } if (track->root_motion) { - real_t prev_time = time - delta; - if (prev_time < 0) { - if (!a->has_loop()) { - prev_time = 0; - } else { - prev_time = a->get_length() + prev_time; + double prev_time = time - delta; + if (!backward) { + if (prev_time < 0) { + switch (a->get_loop_mode()) { + case Animation::LOOP_NONE: { + prev_time = 0; + } break; + case Animation::LOOP_LINEAR: { + prev_time = Math::fposmod(prev_time, (double)a->get_length()); + } break; + case Animation::LOOP_PINGPONG: { + prev_time = Math::pingpong(prev_time, (double)a->get_length()); + } break; + default: + break; + } + } + } else { + if (prev_time > a->get_length()) { + switch (a->get_loop_mode()) { + case Animation::LOOP_NONE: { + prev_time = (double)a->get_length(); + } break; + case Animation::LOOP_LINEAR: { + prev_time = Math::fposmod(prev_time, (double)a->get_length()); + } break; + case Animation::LOOP_PINGPONG: { + prev_time = Math::pingpong(prev_time, (double)a->get_length()); + } break; + default: + break; + } } } Vector3 scale[2]; - if (prev_time > time) { - Error err = a->scale_track_interpolate(i, prev_time, &scale[0]); - if (err != OK) { - continue; + if (!backward) { + if (prev_time > time) { + Error err = a->scale_track_interpolate(i, prev_time, &scale[0]); + if (err != OK) { + continue; + } + a->scale_track_interpolate(i, (double)a->get_length(), &scale[1]); + t->scale += (scale[1] - scale[0]) * blend; + prev_time = 0; + } + } else { + if (prev_time < time) { + Error err = a->scale_track_interpolate(i, prev_time, &scale[0]); + if (err != OK) { + continue; + } + a->scale_track_interpolate(i, 0, &scale[1]); + t->scale += (scale[1] - scale[0]) * blend; + prev_time = 0; } - - a->scale_track_interpolate(i, a->get_length(), &scale[1]); - - t->scale += (scale[1] - scale[0]) * blend; - - prev_time = 0; } Error err = a->scale_track_interpolate(i, prev_time, &scale[0]); @@ -1101,17 +1206,13 @@ void AnimationTree::_process_graph(real_t p_delta) { } a->scale_track_interpolate(i, time, &scale[1]); - t->scale += (scale[1] - scale[0]) * blend; - - prev_time = 0; + prev_time = !backward ? 0 : (double)a->get_length(); } else { Vector3 scale; Error err = a->scale_track_interpolate(i, time, &scale); - //ERR_CONTINUE(err!=OK); //used for testing, should be removed - if (err != OK) { continue; } @@ -1164,7 +1265,7 @@ void AnimationTree::_process_graph(real_t p_delta) { } else { List<int> indices; - a->value_track_get_key_indices(i, time, delta, &indices); + a->value_track_get_key_indices(i, time, delta, &indices, pingponged); for (int &F : indices) { Variant value = a->track_get_key_value(i, F); @@ -1181,7 +1282,7 @@ void AnimationTree::_process_graph(real_t p_delta) { List<int> indices; - a->method_track_get_key_indices(i, time, delta, &indices); + a->method_track_get_key_indices(i, time, delta, &indices, pingponged); for (int &F : indices) { StringName method = a->method_track_get_name(i, F); @@ -1264,7 +1365,7 @@ void AnimationTree::_process_graph(real_t p_delta) { } else { //find stuff to play List<int> to_play; - a->track_get_key_indices_in_range(i, time, delta, &to_play); + a->track_get_key_indices_in_range(i, time, delta, &to_play, pingponged); if (to_play.size()) { int idx = to_play.back()->get(); @@ -1292,12 +1393,20 @@ void AnimationTree::_process_graph(real_t p_delta) { t->start = time; } } else if (t->playing) { - bool loop = a->has_loop(); + bool loop = a->get_loop_mode() != Animation::LoopMode::LOOP_NONE; bool stop = false; - if (!loop && time < t->start) { - stop = true; + if (!loop) { + if (delta > 0) { + if (time < t->start) { + stop = true; + } + } else if (delta < 0) { + if (time > t->start) { + stop = true; + } + } } else if (t->len > 0) { real_t len = t->start > time ? (a->get_length() - t->start) + time : time - t->start; @@ -1331,7 +1440,7 @@ void AnimationTree::_process_graph(real_t p_delta) { continue; } - if (delta == 0 || seeked) { + if (seeked) { //seek int idx = a->track_find_key(i, time); if (idx < 0) { @@ -1347,12 +1456,20 @@ void AnimationTree::_process_graph(real_t p_delta) { Ref<Animation> anim = player2->get_animation(anim_name); - real_t at_anim_pos; - - if (anim->has_loop()) { - at_anim_pos = Math::fposmod(time - pos, (double)anim->get_length()); //seek to loop - } else { - at_anim_pos = MAX(anim->get_length(), time - pos); //seek to end + real_t at_anim_pos = 0.0; + + switch (anim->get_loop_mode()) { + case Animation::LoopMode::LOOP_NONE: { + at_anim_pos = MAX((double)anim->get_length(), time - pos); //seek to end + } break; + case Animation::LoopMode::LOOP_LINEAR: { + at_anim_pos = Math::fposmod(time - pos, (double)anim->get_length()); //seek to loop + } break; + case Animation::LoopMode::LOOP_PINGPONG: { + at_anim_pos = Math::pingpong(time - pos, (double)a->get_length()); + } break; + default: + break; } if (player2->is_playing() || seeked) { @@ -1367,7 +1484,7 @@ void AnimationTree::_process_graph(real_t p_delta) { } else { //find stuff to play List<int> to_play; - a->track_get_key_indices_in_range(i, time, delta, &to_play); + a->track_get_key_indices_in_range(i, time, delta, &to_play, pingponged); if (to_play.size()) { int idx = to_play.back()->get(); diff --git a/scene/animation/animation_tree.h b/scene/animation/animation_tree.h index 5abea39d20..6fc051fa41 100644 --- a/scene/animation/animation_tree.h +++ b/scene/animation/animation_tree.h @@ -66,6 +66,7 @@ public: const Vector<real_t> *track_blends = nullptr; real_t blend = 0.0; bool seeked = false; + int pingponged = 0; }; struct State { @@ -98,9 +99,10 @@ public: real_t _blend_node(const StringName &p_subpath, const Vector<StringName> &p_connections, AnimationNode *p_new_parent, Ref<AnimationNode> p_node, real_t p_time, bool p_seek, real_t p_blend, FilterAction p_filter = FILTER_IGNORE, bool p_optimize = true, real_t *r_max = nullptr); protected: - void blend_animation(const StringName &p_animation, real_t p_time, real_t p_delta, bool p_seeked, real_t p_blend); + void blend_animation(const StringName &p_animation, real_t p_time, real_t p_delta, bool p_seeked, real_t p_blend, int p_pingponged = 0); real_t blend_node(const StringName &p_sub_path, Ref<AnimationNode> p_node, real_t p_time, bool p_seek, real_t p_blend, FilterAction p_filter = FILTER_IGNORE, bool p_optimize = true); real_t blend_input(int p_input, real_t p_time, bool p_seek, real_t p_blend, FilterAction p_filter = FILTER_IGNORE, bool p_optimize = true); + void make_invalid(const String &p_reason); static void _bind_methods(); diff --git a/scene/gui/base_button.cpp b/scene/gui/base_button.cpp index bef1011364..9f712ed478 100644 --- a/scene/gui/base_button.cpp +++ b/scene/gui/base_button.cpp @@ -62,7 +62,7 @@ void BaseButton::gui_input(const Ref<InputEvent> &p_event) { Ref<InputEventMouseButton> mouse_button = p_event; bool ui_accept = p_event->is_action("ui_accept") && !p_event->is_echo(); - bool button_masked = mouse_button.is_valid() && ((1 << (mouse_button->get_button_index() - 1)) & button_mask) != 0; + bool button_masked = mouse_button.is_valid() && (mouse_button_to_mask(mouse_button->get_button_index()) & button_mask) != MouseButton::NONE; if (button_masked || ui_accept) { on_action_event(p_event); return; @@ -313,11 +313,11 @@ BaseButton::ActionMode BaseButton::get_action_mode() const { return action_mode; } -void BaseButton::set_button_mask(int p_mask) { +void BaseButton::set_button_mask(MouseButton p_mask) { button_mask = p_mask; } -int BaseButton::get_button_mask() const { +MouseButton BaseButton::get_button_mask() const { return button_mask; } diff --git a/scene/gui/base_button.h b/scene/gui/base_button.h index cd6e78464f..3ea59c3ff9 100644 --- a/scene/gui/base_button.h +++ b/scene/gui/base_button.h @@ -45,7 +45,7 @@ public: }; private: - int button_mask = MOUSE_BUTTON_MASK_LEFT; + MouseButton button_mask = MouseButton::MASK_LEFT; bool toggle_mode = false; bool shortcut_in_tooltip = true; bool keep_pressed_outside = false; @@ -118,8 +118,8 @@ public: void set_keep_pressed_outside(bool p_on); bool is_keep_pressed_outside() const; - void set_button_mask(int p_mask); - int get_button_mask() const; + void set_button_mask(MouseButton p_mask); + MouseButton get_button_mask() const; void set_shortcut(const Ref<Shortcut> &p_shortcut); Ref<Shortcut> get_shortcut() const; diff --git a/scene/gui/code_edit.cpp b/scene/gui/code_edit.cpp index 046d256867..7fba94da4c 100644 --- a/scene/gui/code_edit.cpp +++ b/scene/gui/code_edit.cpp @@ -263,19 +263,19 @@ void CodeEdit::gui_input(const Ref<InputEvent> &p_gui_input) { } switch (mb->get_button_index()) { - case MOUSE_BUTTON_WHEEL_UP: { + case MouseButton::WHEEL_UP: { if (code_completion_current_selected > 0) { code_completion_current_selected--; update(); } } break; - case MOUSE_BUTTON_WHEEL_DOWN: { + case MouseButton::WHEEL_DOWN: { if (code_completion_current_selected < code_completion_options.size() - 1) { code_completion_current_selected++; update(); } } break; - case MOUSE_BUTTON_LEFT: { + case MouseButton::LEFT: { code_completion_current_selected = CLAMP(code_completion_line_ofs + (mb->get_position().y - code_completion_rect.position.y) / get_line_height(), 0, code_completion_options.size() - 1); if (mb->is_double_click()) { confirm_code_completion(); @@ -296,11 +296,11 @@ void CodeEdit::gui_input(const Ref<InputEvent> &p_gui_input) { mpos.x = get_size().x - mpos.x; } - Point2i pos = get_line_column_at_pos(mpos); + Point2i pos = get_line_column_at_pos(mpos, false); int line = pos.y; int col = pos.x; - if (mb->get_button_index() == MOUSE_BUTTON_LEFT) { + if (line != -1 && mb->get_button_index() == MouseButton::LEFT) { if (is_line_folded(line)) { int wrap_index = get_line_wrap_index_at_column(line, col); if (wrap_index == get_line_wrap_count(line)) { @@ -314,18 +314,20 @@ void CodeEdit::gui_input(const Ref<InputEvent> &p_gui_input) { } } } else { - if (mb->get_button_index() == MOUSE_BUTTON_LEFT) { + if (mb->get_button_index() == MouseButton::LEFT) { if (mb->is_command_pressed() && symbol_lookup_word != String()) { Vector2i mpos = mb->get_position(); if (is_layout_rtl()) { mpos.x = get_size().x - mpos.x; } - Point2i pos = get_line_column_at_pos(mpos); + Point2i pos = get_line_column_at_pos(mpos, false); int line = pos.y; int col = pos.x; - emit_signal(SNAME("symbol_lookup"), symbol_lookup_word, line, col); + if (line != -1) { + emit_signal(SNAME("symbol_lookup"), symbol_lookup_word, line, col); + } return; } } @@ -340,7 +342,7 @@ void CodeEdit::gui_input(const Ref<InputEvent> &p_gui_input) { } if (symbol_lookup_on_click_enabled) { - if (mm->is_command_pressed() && mm->get_button_mask() == 0 && !is_dragging_cursor()) { + if (mm->is_command_pressed() && mm->get_button_mask() == MouseButton::NONE && !is_dragging_cursor()) { symbol_lookup_new_word = get_word_at_pos(mpos); if (symbol_lookup_new_word != symbol_lookup_word) { emit_signal(SNAME("symbol_validate"), symbol_lookup_new_word); @@ -360,9 +362,9 @@ void CodeEdit::gui_input(const Ref<InputEvent> &p_gui_input) { /* Ctrl + Hover symbols */ #ifdef OSX_ENABLED - if (k->get_keycode() == KEY_META) { + if (k->get_keycode() == Key::META) { #else - if (k->get_keycode() == KEY_CTRL) { + if (k->get_keycode() == Key::CTRL) { #endif if (symbol_lookup_on_click_enabled) { if (k->is_pressed() && !is_dragging_cursor()) { @@ -378,7 +380,7 @@ void CodeEdit::gui_input(const Ref<InputEvent> &p_gui_input) { } /* If a modifier has been pressed, and nothing else, return. */ - if (!k->is_pressed() || k->get_keycode() == KEY_CTRL || k->get_keycode() == KEY_ALT || k->get_keycode() == KEY_SHIFT || k->get_keycode() == KEY_META) { + if (!k->is_pressed() || k->get_keycode() == Key::CTRL || k->get_keycode() == Key::ALT || k->get_keycode() == Key::SHIFT || k->get_keycode() == Key::META) { return; } @@ -536,11 +538,11 @@ Control::CursorShape CodeEdit::get_cursor_shape(const Point2 &p_pos) const { return CURSOR_ARROW; } - Point2i pos = get_line_column_at_pos(p_pos); + Point2i pos = get_line_column_at_pos(p_pos, false); int line = pos.y; int col = pos.x; - if (is_line_folded(line)) { + if (line != -1 && is_line_folded(line)) { int wrap_index = get_line_wrap_index_at_column(line, col); if (wrap_index == get_line_wrap_count(line)) { int eol_icon_width = folded_eol_icon->get_width(); @@ -2016,10 +2018,14 @@ bool CodeEdit::is_symbol_lookup_on_click_enabled() const { String CodeEdit::get_text_for_symbol_lookup() { Point2i mp = get_local_mouse_pos(); - Point2i pos = get_line_column_at_pos(mp); + Point2i pos = get_line_column_at_pos(mp, false); int line = pos.y; int col = pos.x; + if (line == -1) { + return String(); + } + StringBuilder lookup_text; const int text_size = get_line_count(); for (int i = 0; i < text_size; i++) { diff --git a/scene/gui/color_picker.cpp b/scene/gui/color_picker.cpp index 894175d559..5a378554c9 100644 --- a/scene/gui/color_picker.cpp +++ b/scene/gui/color_picker.cpp @@ -582,7 +582,7 @@ void ColorPicker::_update_text_value() { void ColorPicker::_sample_input(const Ref<InputEvent> &p_event) { const Ref<InputEventMouseButton> mb = p_event; - if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_LEFT) { + if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT) { const Rect2 rect_old = Rect2(Point2(), Size2(sample->get_size().width * 0.5, sample->get_size().height * 0.95)); if (rect_old.has_point(mb->get_position())) { // Revert to the old color when left-clicking the old color sample. @@ -827,13 +827,13 @@ void ColorPicker::_uv_input(const Ref<InputEvent> &p_event, Control *c) { Ref<InputEventMouseButton> bev = p_event; if (bev.is_valid()) { - if (bev->is_pressed() && bev->get_button_index() == MOUSE_BUTTON_LEFT) { + if (bev->is_pressed() && bev->get_button_index() == MouseButton::LEFT) { Vector2 center = c->get_size() / 2.0; if (picker_type == SHAPE_VHS_CIRCLE) { real_t dist = center.distance_to(bev->get_position()); if (dist <= center.x) { - real_t rad = bev->get_position().angle_to_point(center); + real_t rad = center.angle_to_point(bev->get_position()); h = ((rad >= 0) ? rad : (Math_TAU + rad)) / Math_TAU; s = CLAMP(dist / center.x, 0, 1); } else { @@ -850,7 +850,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 = bev->get_position().angle_to_point(center); + real_t rad = center.angle_to_point(bev->get_position()); h = ((rad >= 0) ? rad : (Math_TAU + rad)) / Math_TAU; spinning = true; } else { @@ -875,7 +875,7 @@ void ColorPicker::_uv_input(const Ref<InputEvent> &p_event, Control *c) { if (!deferred_mode_enabled) { emit_signal(SNAME("color_changed"), color); } - } else if (deferred_mode_enabled && !bev->is_pressed() && bev->get_button_index() == MOUSE_BUTTON_LEFT) { + } else if (deferred_mode_enabled && !bev->is_pressed() && bev->get_button_index() == MouseButton::LEFT) { emit_signal(SNAME("color_changed"), color); changing_color = false; spinning = false; @@ -895,12 +895,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 = mev->get_position().angle_to_point(center); + real_t rad = center.angle_to_point(mev->get_position()); h = ((rad >= 0) ? rad : (Math_TAU + rad)) / Math_TAU; s = CLAMP(dist / center.x, 0, 1); } else { if (spinning) { - real_t rad = mev->get_position().angle_to_point(center); + real_t rad = center.angle_to_point(mev->get_position()); 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; @@ -929,7 +929,7 @@ void ColorPicker::_w_input(const Ref<InputEvent> &p_event) { Ref<InputEventMouseButton> bev = p_event; if (bev.is_valid()) { - if (bev->is_pressed() && bev->get_button_index() == MOUSE_BUTTON_LEFT) { + if (bev->is_pressed() && bev->get_button_index() == MouseButton::LEFT) { changing_color = true; float y = CLAMP((float)bev->get_position().y, 0, w_edit->get_size().height); if (picker_type == SHAPE_VHS_CIRCLE) { @@ -946,7 +946,7 @@ void ColorPicker::_w_input(const Ref<InputEvent> &p_event) { _update_color(); if (!deferred_mode_enabled) { emit_signal(SNAME("color_changed"), color); - } else if (!bev->is_pressed() && bev->get_button_index() == MOUSE_BUTTON_LEFT) { + } else if (!bev->is_pressed() && bev->get_button_index() == MouseButton::LEFT) { emit_signal(SNAME("color_changed"), color); } } @@ -977,11 +977,11 @@ void ColorPicker::_preset_input(const Ref<InputEvent> &p_event, const Color &p_c Ref<InputEventMouseButton> bev = p_event; if (bev.is_valid()) { - if (bev->is_pressed() && bev->get_button_index() == MOUSE_BUTTON_LEFT) { + if (bev->is_pressed() && bev->get_button_index() == MouseButton::LEFT) { set_pick_color(p_color); _update_color(); emit_signal(SNAME("color_changed"), p_color); - } else if (bev->is_pressed() && bev->get_button_index() == MOUSE_BUTTON_RIGHT && presets_enabled) { + } else if (bev->is_pressed() && bev->get_button_index() == MouseButton::RIGHT && presets_enabled) { erase_preset(p_color); emit_signal(SNAME("preset_removed"), p_color); } @@ -994,7 +994,7 @@ void ColorPicker::_screen_input(const Ref<InputEvent> &p_event) { } Ref<InputEventMouseButton> bev = p_event; - if (bev.is_valid() && bev->get_button_index() == MOUSE_BUTTON_LEFT && !bev->is_pressed()) { + if (bev.is_valid() && bev->get_button_index() == MouseButton::LEFT && !bev->is_pressed()) { emit_signal(SNAME("color_changed"), color); screen->hide(); } diff --git a/scene/gui/dialogs.cpp b/scene/gui/dialogs.cpp index 71d2778cc3..f66d3f6835 100644 --- a/scene/gui/dialogs.cpp +++ b/scene/gui/dialogs.cpp @@ -44,7 +44,7 @@ void AcceptDialog::_input_from_window(const Ref<InputEvent> &p_event) { Ref<InputEventKey> key = p_event; - if (key.is_valid() && key->is_pressed() && key->get_keycode() == KEY_ESCAPE) { + if (key.is_valid() && key->is_pressed() && key->get_keycode() == Key::ESCAPE) { _cancel_pressed(); } } diff --git a/scene/gui/file_dialog.cpp b/scene/gui/file_dialog.cpp index 5f9f09fc50..44853fc006 100644 --- a/scene/gui/file_dialog.cpp +++ b/scene/gui/file_dialog.cpp @@ -110,7 +110,7 @@ void FileDialog::unhandled_input(const Ref<InputEvent> &p_event) { bool handled = true; switch (k->get_keycode()) { - case KEY_H: { + case Key::H: { if (k->is_command_pressed()) { set_show_hidden_files(!show_hidden_files); } else { @@ -118,10 +118,10 @@ void FileDialog::unhandled_input(const Ref<InputEvent> &p_event) { } } break; - case KEY_F5: { + case Key::F5: { invalidate(); } break; - case KEY_BACKSPACE: { + case Key::BACKSPACE: { _dir_submitted(".."); } break; default: { diff --git a/scene/gui/gradient_edit.cpp b/scene/gui/gradient_edit.cpp index 5d024d3be7..ae15b021a5 100644 --- a/scene/gui/gradient_edit.cpp +++ b/scene/gui/gradient_edit.cpp @@ -39,6 +39,10 @@ GradientEdit::GradientEdit() { picker = memnew(ColorPicker); popup->add_child(picker); + gradient_cache.instantiate(); + preview_texture.instantiate(); + + preview_texture->set_width(1024); add_child(popup, false, INTERNAL_MODE_FRONT); } @@ -47,7 +51,7 @@ int GradientEdit::_get_point_from_pos(int x) { int total_w = get_size().width - get_size().height - draw_spacing; float min_distance = 1e20; for (int i = 0; i < points.size(); i++) { - //Check if we clicked at point + // Check if we clicked at point. float distance = ABS(x - points[i].offset * total_w); float min = (draw_point_width / 2 * 1.7); //make it easier to grab if (distance <= min && distance < min_distance) { @@ -84,7 +88,7 @@ void GradientEdit::gui_input(const Ref<InputEvent> &p_event) { Ref<InputEventKey> k = p_event; - if (k.is_valid() && k->is_pressed() && k->get_keycode() == KEY_DELETE && grabbed != -1) { + if (k.is_valid() && k->is_pressed() && k->get_keycode() == Key::KEY_DELETE && grabbed != -1) { points.remove(grabbed); grabbed = -1; grabbing = false; @@ -94,15 +98,15 @@ void GradientEdit::gui_input(const Ref<InputEvent> &p_event) { } Ref<InputEventMouseButton> mb = p_event; - //Show color picker on double click. - if (mb.is_valid() && mb->get_button_index() == 1 && mb->is_double_click() && mb->is_pressed()) { + // Show color picker on double click. + if (mb.is_valid() && mb->get_button_index() == MouseButton::LEFT && mb->is_double_click() && mb->is_pressed()) { grabbed = _get_point_from_pos(mb->get_position().x); _show_color_picker(); accept_event(); } - //Delete point on right click - if (mb.is_valid() && mb->get_button_index() == 2 && mb->is_pressed()) { + // Delete point on right click. + if (mb.is_valid() && mb->get_button_index() == MouseButton::RIGHT && mb->is_pressed()) { grabbed = _get_point_from_pos(mb->get_position().x); if (grabbed != -1) { points.remove(grabbed); @@ -114,20 +118,20 @@ void GradientEdit::gui_input(const Ref<InputEvent> &p_event) { } } - //Hold alt key to duplicate selected color - if (mb.is_valid() && mb->get_button_index() == 1 && mb->is_pressed() && mb->is_alt_pressed()) { + // Hold alt key to duplicate selected color. + if (mb.is_valid() && mb->get_button_index() == MouseButton::LEFT && mb->is_pressed() && mb->is_alt_pressed()) { int x = mb->get_position().x; grabbed = _get_point_from_pos(x); if (grabbed != -1) { int total_w = get_size().width - get_size().height - draw_spacing; - Gradient::Point newPoint = points[grabbed]; - newPoint.offset = CLAMP(x / float(total_w), 0, 1); + Gradient::Point new_point = points[grabbed]; + new_point.offset = CLAMP(x / float(total_w), 0, 1); - points.push_back(newPoint); + points.push_back(new_point); points.sort(); for (int i = 0; i < points.size(); ++i) { - if (points[i].offset == newPoint.offset) { + if (points[i].offset == new_point.offset) { grabbed = i; break; } @@ -138,8 +142,8 @@ void GradientEdit::gui_input(const Ref<InputEvent> &p_event) { } } - //select - if (mb.is_valid() && mb->get_button_index() == 1 && mb->is_pressed()) { + // Select. + if (mb.is_valid() && mb->get_button_index() == MouseButton::LEFT && mb->is_pressed()) { update(); int x = mb->get_position().x; int total_w = get_size().width - get_size().height - draw_spacing; @@ -158,16 +162,16 @@ void GradientEdit::gui_input(const Ref<InputEvent> &p_event) { return; } - //insert - Gradient::Point newPoint; - newPoint.offset = CLAMP(x / float(total_w), 0, 1); + // Insert point. + Gradient::Point new_point; + new_point.offset = CLAMP(x / float(total_w), 0, 1); Gradient::Point prev; Gradient::Point next; int pos = -1; for (int i = 0; i < points.size(); i++) { - if (points[i].offset < newPoint.offset) { + if (points[i].offset < new_point.offset) { pos = i; } } @@ -191,12 +195,12 @@ void GradientEdit::gui_input(const Ref<InputEvent> &p_event) { prev = points[pos]; } - newPoint.color = prev.color.lerp(next.color, (newPoint.offset - prev.offset) / (next.offset - prev.offset)); + new_point.color = prev.color.lerp(next.color, (new_point.offset - prev.offset) / (next.offset - prev.offset)); - points.push_back(newPoint); + points.push_back(new_point); points.sort(); for (int i = 0; i < points.size(); i++) { - if (points[i].offset == newPoint.offset) { + if (points[i].offset == new_point.offset) { grabbed = i; break; } @@ -205,7 +209,7 @@ void GradientEdit::gui_input(const Ref<InputEvent> &p_event) { emit_signal(SNAME("ramp_changed")); } - if (mb.is_valid() && mb->get_button_index() == 1 && !mb->is_pressed()) { + if (mb.is_valid() && mb->get_button_index() == MouseButton::LEFT && !mb->is_pressed()) { if (grabbing) { grabbing = false; emit_signal(SNAME("ramp_changed")); @@ -223,7 +227,7 @@ void GradientEdit::gui_input(const Ref<InputEvent> &p_event) { float newofs = CLAMP(x / float(total_w), 0, 1); // Snap to "round" coordinates if holding Ctrl. - // Be more precise if holding Shift as well + // Be more precise if holding Shift as well. if (mm->is_ctrl_pressed()) { newofs = Math::snapped(newofs, mm->is_shift_pressed() ? 0.025 : 0.1); } else if (mm->is_shift_pressed()) { @@ -299,57 +303,22 @@ void GradientEdit::_notification(int p_what) { int h = get_size().y; if (w == 0 || h == 0) { - return; //Safety check. We have division by 'h'. And in any case there is nothing to draw with such size + return; // Safety check. We have division by 'h'. And in any case there is nothing to draw with such size. } int total_w = get_size().width - get_size().height - draw_spacing; - //Draw checker pattern for ramp + // Draw checker pattern for ramp. draw_texture_rect(get_theme_icon(SNAME("GuiMiniCheckerboard"), SNAME("EditorIcons")), Rect2(0, 0, total_w, h), true); - //Draw color ramp - Gradient::Point prev; - prev.offset = 0; - if (points.size() == 0) { - prev.color = Color(0, 0, 0); //Draw black rectangle if we have no points - } else { - prev.color = points[0].color; //Extend color of first point to the beginning. - } + // Draw color ramp. - for (int i = -1; i < points.size(); i++) { - Gradient::Point next; - //If there is no next point - if (i + 1 == points.size()) { - if (points.size() == 0) { - next.color = Color(0, 0, 0); //Draw black rectangle if we have no points - } else { - next.color = points[i].color; //Extend color of last point to the end. - } - next.offset = 1; - } else { - next = points[i + 1]; - } + gradient_cache->set_points(points); + gradient_cache->set_interpolation_mode(interpolation_mode); + preview_texture->set_gradient(gradient_cache); + draw_texture_rect(preview_texture, Rect2(0, 0, total_w, h)); - if (prev.offset == next.offset) { - prev = next; - continue; - } - - Vector<Vector2> points; - Vector<Color> colors; - points.push_back(Vector2(prev.offset * total_w, h)); - points.push_back(Vector2(prev.offset * total_w, 0)); - points.push_back(Vector2(next.offset * total_w, 0)); - points.push_back(Vector2(next.offset * total_w, h)); - colors.push_back(prev.color); - colors.push_back(prev.color); - colors.push_back(next.color); - colors.push_back(next.color); - draw_primitive(points, colors, Vector<Point2>()); - prev = next; - } - - //Draw point markers + // Draw point markers. for (int i = 0; i < points.size(); i++) { Color col = points[i].color.inverted(); col.a = 0.9; @@ -383,7 +352,7 @@ void GradientEdit::_notification(int p_what) { draw_line(Vector2(total_w + draw_spacing, h), Vector2(total_w + draw_spacing + h, 0), Color(1, 1, 1, 0.6)); } - //Draw borders around color ramp if in focus + // Draw borders around color ramp if in focus. if (has_focus()) { draw_line(Vector2(-1, -1), Vector2(total_w + 1, -1), Color(1, 1, 1, 0.6)); draw_line(Vector2(total_w + 1, -1), Vector2(total_w + 1, h + 1), Color(1, 1, 1, 0.6)); @@ -448,12 +417,21 @@ void GradientEdit::set_points(Vector<Gradient::Point> &p_points) { } points.clear(); points = p_points; + points.sort(); } Vector<Gradient::Point> &GradientEdit::get_points() { return points; } +void GradientEdit::set_interpolation_mode(Gradient::InterpolationMode p_interp_mode) { + interpolation_mode = p_interp_mode; +} + +Gradient::InterpolationMode GradientEdit::get_interpolation_mode() { + return interpolation_mode; +} + void GradientEdit::_bind_methods() { ADD_SIGNAL(MethodInfo("ramp_changed")); } diff --git a/scene/gui/gradient_edit.h b/scene/gui/gradient_edit.h index f3a39daaf6..66b60d87c7 100644 --- a/scene/gui/gradient_edit.h +++ b/scene/gui/gradient_edit.h @@ -45,6 +45,10 @@ class GradientEdit : public Control { bool grabbing = false; int grabbed = -1; Vector<Gradient::Point> points; + Gradient::InterpolationMode interpolation_mode = Gradient::GRADIENT_INTERPOLATE_LINEAR; + + Ref<Gradient> gradient_cache; + Ref<GradientTexture1D> preview_texture; // Make sure to use the scaled value below. const int BASE_SPACING = 3; @@ -69,6 +73,9 @@ public: Vector<Color> get_colors() const; void set_points(Vector<Gradient::Point> &p_points); Vector<Gradient::Point> &get_points(); + void set_interpolation_mode(Gradient::InterpolationMode p_interp_mode); + Gradient::InterpolationMode get_interpolation_mode(); + virtual Size2 get_minimum_size() const override; GradientEdit(); diff --git a/scene/gui/graph_edit.cpp b/scene/gui/graph_edit.cpp index 35e31be9af..e7d98a686f 100644 --- a/scene/gui/graph_edit.cpp +++ b/scene/gui/graph_edit.cpp @@ -150,7 +150,7 @@ void GraphEditMinimap::gui_input(const Ref<InputEvent> &p_ev) { Ref<InputEventMouseButton> mb = p_ev; Ref<InputEventMouseMotion> mm = p_ev; - if (mb.is_valid() && mb->get_button_index() == MOUSE_BUTTON_LEFT) { + if (mb.is_valid() && mb->get_button_index() == MouseButton::LEFT) { if (mb->is_pressed()) { is_pressing = true; @@ -501,6 +501,43 @@ void GraphEdit::_notification(int p_what) { } } +void GraphEdit::_update_comment_enclosed_nodes_list(GraphNode *p_node, HashMap<StringName, Vector<GraphNode *>> &p_comment_enclosed_nodes) { + Rect2 comment_node_rect = p_node->get_rect(); + Vector<GraphNode *> enclosed_nodes; + + for (int i = 0; i < get_child_count(); i++) { + GraphNode *gn = Object::cast_to<GraphNode>(get_child(i)); + if (!gn || gn->is_selected()) { + continue; + } + + Rect2 node_rect = gn->get_rect(); + bool included = comment_node_rect.encloses(node_rect); + if (included) { + enclosed_nodes.push_back(gn); + } + } + + p_comment_enclosed_nodes.set(p_node->get_name(), enclosed_nodes); +} + +void GraphEdit::_set_drag_comment_enclosed_nodes(GraphNode *p_node, HashMap<StringName, Vector<GraphNode *>> &p_comment_enclosed_nodes, bool p_drag) { + for (int i = 0; i < p_comment_enclosed_nodes[p_node->get_name()].size(); i++) { + p_comment_enclosed_nodes[p_node->get_name()][i]->set_drag(p_drag); + } +} + +void GraphEdit::_set_position_of_comment_enclosed_nodes(GraphNode *p_node, HashMap<StringName, Vector<GraphNode *>> &p_comment_enclosed_nodes, Vector2 p_drag_accum) { + for (int i = 0; i < p_comment_enclosed_nodes[p_node->get_name()].size(); i++) { + Vector2 pos = (p_comment_enclosed_nodes[p_node->get_name()][i]->get_drag_from() * zoom + drag_accum) / zoom; + if (is_using_snap() ^ Input::get_singleton()->is_key_pressed(Key::CTRL)) { + const int snap = get_snap(); + pos = pos.snapped(Vector2(snap, snap)); + } + p_comment_enclosed_nodes[p_node->get_name()][i]->set_position_offset(pos); + } +} + bool GraphEdit::_filter_input(const Point2 &p_point) { Ref<Texture2D> port = get_theme_icon(SNAME("port"), SNAME("GraphNode")); Vector2i port_size = Vector2i(port->get_width(), port->get_height()); @@ -531,7 +568,7 @@ bool GraphEdit::_filter_input(const Point2 &p_point) { void GraphEdit::_top_layer_input(const Ref<InputEvent> &p_ev) { Ref<InputEventMouseButton> mb = p_ev; - if (mb.is_valid() && mb->get_button_index() == MOUSE_BUTTON_LEFT && mb->is_pressed()) { + if (mb.is_valid() && mb->get_button_index() == MouseButton::LEFT && mb->is_pressed()) { Ref<Texture2D> port = get_theme_icon(SNAME("port"), SNAME("GraphNode")); Vector2i port_size = Vector2i(port->get_width(), port->get_height()); @@ -678,7 +715,7 @@ void GraphEdit::_top_layer_input(const Ref<InputEvent> &p_ev) { } } - if (mb.is_valid() && mb->get_button_index() == MOUSE_BUTTON_LEFT && !mb->is_pressed()) { + if (mb.is_valid() && mb->get_button_index() == MouseButton::LEFT && !mb->is_pressed()) { if (connecting_valid) { if (connecting && connecting_target) { String from = connecting_from; @@ -1031,7 +1068,7 @@ void GraphEdit::gui_input(const Ref<InputEvent> &p_ev) { ERR_FAIL_COND(p_ev.is_null()); Ref<InputEventMouseMotion> mm = p_ev; - if (mm.is_valid() && (mm->get_button_mask() & MOUSE_BUTTON_MASK_MIDDLE || (mm->get_button_mask() & MOUSE_BUTTON_MASK_LEFT && Input::get_singleton()->is_key_pressed(KEY_SPACE)))) { + if (mm.is_valid() && ((mm->get_button_mask() & MouseButton::MASK_MIDDLE) != MouseButton::NONE || ((mm->get_button_mask() & MouseButton::MASK_LEFT) != MouseButton::NONE && Input::get_singleton()->is_key_pressed(Key::SPACE)))) { Vector2i relative = Input::get_singleton()->warp_mouse_motion(mm, get_global_rect()); h_scroll->set_value(h_scroll->get_value() - relative.x); v_scroll->set_value(v_scroll->get_value() - relative.y); @@ -1052,12 +1089,15 @@ void GraphEdit::gui_input(const Ref<InputEvent> &p_ev) { // Snapping can be toggled temporarily by holding down Ctrl. // This is done here as to not toggle the grid when holding down Ctrl. - if (is_using_snap() ^ Input::get_singleton()->is_key_pressed(KEY_CTRL)) { + if (is_using_snap() ^ Input::get_singleton()->is_key_pressed(Key::CTRL)) { const int snap = get_snap(); pos = pos.snapped(Vector2(snap, snap)); } gn->set_position_offset(pos); + if (gn->is_comment()) { + _set_position_of_comment_enclosed_nodes(gn, comment_enclosed_nodes, drag_accum); + } } } } @@ -1101,7 +1141,7 @@ void GraphEdit::gui_input(const Ref<InputEvent> &p_ev) { Ref<InputEventMouseButton> b = p_ev; if (b.is_valid()) { - if (b->get_button_index() == MOUSE_BUTTON_RIGHT && b->is_pressed()) { + if (b->get_button_index() == MouseButton::RIGHT && b->is_pressed()) { if (box_selecting) { box_selecting = false; for (int i = get_child_count() - 1; i >= 0; i--) { @@ -1131,8 +1171,8 @@ void GraphEdit::gui_input(const Ref<InputEvent> &p_ev) { } } - if (b->get_button_index() == MOUSE_BUTTON_LEFT && !b->is_pressed() && dragging) { - if (!just_selected && drag_accum == Vector2() && Input::get_singleton()->is_key_pressed(KEY_CTRL)) { + if (b->get_button_index() == MouseButton::LEFT && !b->is_pressed() && dragging) { + if (!just_selected && drag_accum == Vector2() && Input::get_singleton()->is_key_pressed(Key::CTRL)) { //deselect current node for (int i = get_child_count() - 1; i >= 0; i--) { GraphNode *gn = Object::cast_to<GraphNode>(get_child(i)); @@ -1153,6 +1193,9 @@ void GraphEdit::gui_input(const Ref<InputEvent> &p_ev) { GraphNode *gn = Object::cast_to<GraphNode>(get_child(i)); if (gn && gn->is_selected()) { gn->set_drag(false); + if (gn->is_comment()) { + _set_drag_comment_enclosed_nodes(gn, comment_enclosed_nodes, false); + } } } } @@ -1170,7 +1213,7 @@ void GraphEdit::gui_input(const Ref<InputEvent> &p_ev) { connections_layer->update(); } - if (b->get_button_index() == MOUSE_BUTTON_LEFT && b->is_pressed()) { + if (b->get_button_index() == MouseButton::LEFT && b->is_pressed()) { GraphNode *gn = nullptr; for (int i = get_child_count() - 1; i >= 0; i--) { @@ -1196,7 +1239,7 @@ void GraphEdit::gui_input(const Ref<InputEvent> &p_ev) { dragging = true; drag_accum = Vector2(); just_selected = !gn->is_selected(); - if (!gn->is_selected() && !Input::get_singleton()->is_key_pressed(KEY_CTRL)) { + if (!gn->is_selected() && !Input::get_singleton()->is_key_pressed(Key::CTRL)) { for (int i = 0; i < get_child_count(); i++) { GraphNode *o_gn = Object::cast_to<GraphNode>(get_child(i)); if (o_gn) { @@ -1220,6 +1263,10 @@ void GraphEdit::gui_input(const Ref<InputEvent> &p_ev) { } if (o_gn->is_selected()) { o_gn->set_drag(true); + if (o_gn->is_comment()) { + _update_comment_enclosed_nodes_list(o_gn, comment_enclosed_nodes); + _set_drag_comment_enclosed_nodes(o_gn, comment_enclosed_nodes, true); + } } } @@ -1227,7 +1274,7 @@ void GraphEdit::gui_input(const Ref<InputEvent> &p_ev) { if (_filter_input(b->get_position())) { return; } - if (Input::get_singleton()->is_key_pressed(KEY_SPACE)) { + if (Input::get_singleton()->is_key_pressed(Key::SPACE)) { return; } @@ -1272,7 +1319,7 @@ void GraphEdit::gui_input(const Ref<InputEvent> &p_ev) { } } - if (b->get_button_index() == MOUSE_BUTTON_LEFT && !b->is_pressed() && box_selecting) { + if (b->get_button_index() == MouseButton::LEFT && !b->is_pressed() && box_selecting) { box_selecting = false; box_selecting_rect = Rect2(); previous_selected.clear(); @@ -1280,7 +1327,7 @@ void GraphEdit::gui_input(const Ref<InputEvent> &p_ev) { minimap->update(); } - int scroll_direction = (b->get_button_index() == MOUSE_BUTTON_WHEEL_DOWN) - (b->get_button_index() == MOUSE_BUTTON_WHEEL_UP); + int scroll_direction = (b->get_button_index() == MouseButton::WHEEL_DOWN) - (b->get_button_index() == MouseButton::WHEEL_UP); if (scroll_direction != 0) { if (b->is_ctrl_pressed()) { if (b->is_shift_pressed()) { diff --git a/scene/gui/graph_edit.h b/scene/gui/graph_edit.h index 44e50aa3c2..6c11f9df6a 100644 --- a/scene/gui/graph_edit.h +++ b/scene/gui/graph_edit.h @@ -217,6 +217,11 @@ private: Set<int> valid_left_disconnect_types; Set<int> valid_right_disconnect_types; + HashMap<StringName, Vector<GraphNode *>> comment_enclosed_nodes; + void _update_comment_enclosed_nodes_list(GraphNode *p_node, HashMap<StringName, Vector<GraphNode *>> &p_comment_enclosed_nodes); + void _set_drag_comment_enclosed_nodes(GraphNode *p_node, HashMap<StringName, Vector<GraphNode *>> &p_comment_enclosed_nodes, bool p_drag); + void _set_position_of_comment_enclosed_nodes(GraphNode *p_node, HashMap<StringName, Vector<GraphNode *>> &p_comment_enclosed_nodes, Vector2 p_pos); + HBoxContainer *zoom_hb; friend class GraphEditFilter; diff --git a/scene/gui/graph_node.cpp b/scene/gui/graph_node.cpp index 8462fd259e..e7094c89b1 100644 --- a/scene/gui/graph_node.cpp +++ b/scene/gui/graph_node.cpp @@ -894,7 +894,7 @@ void GraphNode::gui_input(const Ref<InputEvent> &p_ev) { if (mb.is_valid()) { 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) { + if (mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT) { Vector2 mpos = mb->get_position(); if (close_rect.size != Size2() && close_rect.has_point(mpos)) { //send focus to parent @@ -917,7 +917,7 @@ void GraphNode::gui_input(const Ref<InputEvent> &p_ev) { emit_signal(SNAME("raise_request")); } - if (!mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_LEFT) { + if (!mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT) { resizing = false; } } diff --git a/scene/gui/item_list.cpp b/scene/gui/item_list.cpp index 4215c9aff4..c0f2cdbef1 100644 --- a/scene/gui/item_list.cpp +++ b/scene/gui/item_list.cpp @@ -29,6 +29,7 @@ /*************************************************************************/ #include "item_list.h" + #include "core/config/project_settings.h" #include "core/os/os.h" #include "core/string/translation.h" @@ -546,7 +547,7 @@ void ItemList::gui_input(const Ref<InputEvent> &p_event) { Ref<InputEventMouseButton> mb = p_event; - if (defer_select_single >= 0 && mb.is_valid() && mb->get_button_index() == MOUSE_BUTTON_LEFT && !mb->is_pressed()) { + if (defer_select_single >= 0 && mb.is_valid() && mb->get_button_index() == MouseButton::LEFT && !mb->is_pressed()) { select(defer_select_single, true); emit_signal(SNAME("multi_selected"), defer_select_single, true); @@ -554,7 +555,7 @@ void ItemList::gui_input(const Ref<InputEvent> &p_event) { return; } - if (mb.is_valid() && (mb->get_button_index() == MOUSE_BUTTON_LEFT || (allow_rmb_select && mb->get_button_index() == MOUSE_BUTTON_RIGHT)) && mb->is_pressed()) { + if (mb.is_valid() && (mb->get_button_index() == MouseButton::LEFT || (allow_rmb_select && mb->get_button_index() == MouseButton::RIGHT)) && mb->is_pressed()) { search_string = ""; //any mousepress cancels Vector2 pos = mb->get_position(); Ref<StyleBox> bg = get_theme_stylebox(SNAME("bg")); @@ -600,16 +601,16 @@ void ItemList::gui_input(const Ref<InputEvent> &p_event) { } } - if (mb->get_button_index() == MOUSE_BUTTON_RIGHT) { + if (mb->get_button_index() == MouseButton::RIGHT) { emit_signal(SNAME("item_rmb_selected"), i, get_local_mouse_position()); } } else { - if (!mb->is_double_click() && !mb->is_command_pressed() && select_mode == SELECT_MULTI && items[i].selectable && !items[i].disabled && items[i].selected && mb->get_button_index() == MOUSE_BUTTON_LEFT) { + if (!mb->is_double_click() && !mb->is_command_pressed() && select_mode == SELECT_MULTI && items[i].selectable && !items[i].disabled && items[i].selected && mb->get_button_index() == MouseButton::LEFT) { defer_select_single = i; return; } - if (items[i].selected && mb->get_button_index() == MOUSE_BUTTON_RIGHT) { + if (items[i].selected && mb->get_button_index() == MouseButton::RIGHT) { emit_signal(SNAME("item_rmb_selected"), i, get_local_mouse_position()); } else { bool selected = items[i].selected; @@ -624,7 +625,7 @@ void ItemList::gui_input(const Ref<InputEvent> &p_event) { } } - if (mb->get_button_index() == MOUSE_BUTTON_RIGHT) { + if (mb->get_button_index() == MouseButton::RIGHT) { emit_signal(SNAME("item_rmb_selected"), i, get_local_mouse_position()); } else if (/*select_mode==SELECT_SINGLE &&*/ mb->is_double_click()) { emit_signal(SNAME("item_activated"), i); @@ -634,7 +635,7 @@ void ItemList::gui_input(const Ref<InputEvent> &p_event) { return; } - if (mb->get_button_index() == MOUSE_BUTTON_RIGHT) { + if (mb->get_button_index() == MouseButton::RIGHT) { emit_signal(SNAME("rmb_clicked"), mb->get_position()); return; @@ -643,10 +644,10 @@ void ItemList::gui_input(const Ref<InputEvent> &p_event) { // Since closest is null, more likely we clicked on empty space, so send signal to interested controls. Allows, for example, implement items deselecting. emit_signal(SNAME("nothing_selected")); } - if (mb.is_valid() && mb->get_button_index() == MOUSE_BUTTON_WHEEL_UP && mb->is_pressed()) { + if (mb.is_valid() && mb->get_button_index() == MouseButton::WHEEL_UP && mb->is_pressed()) { scroll_bar->set_value(scroll_bar->get_value() - scroll_bar->get_page() * mb->get_factor() / 8); } - if (mb.is_valid() && mb->get_button_index() == MOUSE_BUTTON_WHEEL_DOWN && mb->is_pressed()) { + if (mb.is_valid() && mb->get_button_index() == MouseButton::WHEEL_DOWN && mb->is_pressed()) { scroll_bar->set_value(scroll_bar->get_value() + scroll_bar->get_page() * mb->get_factor() / 8); } @@ -1605,7 +1606,7 @@ void ItemList::_bind_methods() { ClassDB::bind_method(D_METHOD("move_item", "from_idx", "to_idx"), &ItemList::move_item); - ClassDB::bind_method(D_METHOD("set_item_count"), &ItemList::set_item_count); + ClassDB::bind_method(D_METHOD("set_item_count", "count"), &ItemList::set_item_count); ClassDB::bind_method(D_METHOD("get_item_count"), &ItemList::get_item_count); ClassDB::bind_method(D_METHOD("remove_item", "idx"), &ItemList::remove_item); diff --git a/scene/gui/label.cpp b/scene/gui/label.cpp index b8cb618171..50908f6a77 100644 --- a/scene/gui/label.cpp +++ b/scene/gui/label.cpp @@ -243,11 +243,9 @@ inline void draw_glyph_outline(const Glyph &p_gl, const RID &p_canvas, const Col if (p_gl.font_rid != RID()) { if (p_font_shadow_color.a > 0) { TS->font_draw_glyph(p_gl.font_rid, p_canvas, p_gl.font_size, p_ofs + Vector2(p_gl.x_off, p_gl.y_off) + shadow_ofs, p_gl.index, p_font_shadow_color); - if (p_shadow_outline_size > 0) { - TS->font_draw_glyph_outline(p_gl.font_rid, p_canvas, p_gl.font_size, p_shadow_outline_size, p_ofs + Vector2(p_gl.x_off, p_gl.y_off) + Vector2(-shadow_ofs.x, shadow_ofs.y), p_gl.index, p_font_shadow_color); - TS->font_draw_glyph_outline(p_gl.font_rid, p_canvas, p_gl.font_size, p_shadow_outline_size, p_ofs + Vector2(p_gl.x_off, p_gl.y_off) + Vector2(shadow_ofs.x, -shadow_ofs.y), p_gl.index, p_font_shadow_color); - TS->font_draw_glyph_outline(p_gl.font_rid, p_canvas, p_gl.font_size, p_shadow_outline_size, p_ofs + Vector2(p_gl.x_off, p_gl.y_off) + Vector2(-shadow_ofs.x, -shadow_ofs.y), p_gl.index, p_font_shadow_color); - } + } + if (p_font_shadow_color.a > 0 && p_shadow_outline_size > 0) { + TS->font_draw_glyph_outline(p_gl.font_rid, p_canvas, p_gl.font_size, p_shadow_outline_size, p_ofs + Vector2(p_gl.x_off, p_gl.y_off) + shadow_ofs, p_gl.index, p_font_shadow_color); } if (p_font_outline_color.a != 0.0 && p_outline_size > 0) { TS->font_draw_glyph_outline(p_gl.font_rid, p_canvas, p_gl.font_size, p_outline_size, p_ofs + Vector2(p_gl.x_off, p_gl.y_off), p_gl.index, p_font_outline_color); @@ -397,7 +395,7 @@ void Label::_notification(int p_what) { int ellipsis_gl_size = TS->shaped_text_get_ellipsis_glyph_count(lines_rid[i]); // Draw outline. Note: Do not merge this into the single loop with the main text, to prevent overlaps. - if (font_shadow_color.a > 0 || (font_outline_color.a != 0.0 && outline_size > 0)) { + if ((outline_size > 0 && font_outline_color.a != 0) || (font_shadow_color.a != 0)) { Vector2 offset = ofs; // Draw RTL ellipsis string when necessary. if (rtl && ellipsis_pos >= 0) { diff --git a/scene/gui/line_edit.cpp b/scene/gui/line_edit.cpp index 0b85b4d3d2..124d5c7821 100644 --- a/scene/gui/line_edit.cpp +++ b/scene/gui/line_edit.cpp @@ -225,7 +225,7 @@ void LineEdit::gui_input(const Ref<InputEvent> &p_event) { // Ignore mouse clicks in IME input mode. return; } - if (b->is_pressed() && b->get_button_index() == MOUSE_BUTTON_RIGHT && context_menu_enabled) { + if (b->is_pressed() && b->get_button_index() == MouseButton::RIGHT && context_menu_enabled) { _ensure_menu(); menu->set_position(get_screen_transform().xform(get_local_mouse_position())); menu->set_size(Vector2(1, 1)); @@ -235,7 +235,7 @@ void LineEdit::gui_input(const Ref<InputEvent> &p_event) { return; } - if (is_middle_mouse_paste_enabled() && b->is_pressed() && b->get_button_index() == MOUSE_BUTTON_MIDDLE && is_editable() && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_CLIPBOARD_PRIMARY)) { + if (is_middle_mouse_paste_enabled() && b->is_pressed() && b->get_button_index() == MouseButton::MIDDLE && is_editable() && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_CLIPBOARD_PRIMARY)) { String paste_buffer = DisplayServer::get_singleton()->clipboard_get_primary().strip_escapes(); deselect(); @@ -254,7 +254,7 @@ void LineEdit::gui_input(const Ref<InputEvent> &p_event) { return; } - if (b->get_button_index() != MOUSE_BUTTON_LEFT) { + if (b->get_button_index() != MouseButton::LEFT) { return; } @@ -328,7 +328,7 @@ void LineEdit::gui_input(const Ref<InputEvent> &p_event) { update(); } else { - if (selection.enabled && !pass && b->get_button_index() == MOUSE_BUTTON_LEFT && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_CLIPBOARD_PRIMARY)) { + if (selection.enabled && !pass && b->get_button_index() == MouseButton::LEFT && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_CLIPBOARD_PRIMARY)) { DisplayServer::get_singleton()->clipboard_set_primary(text.substr(selection.begin, selection.end - selection.begin)); } if (!text.is_empty() && is_editable() && clear_button_enabled) { @@ -363,7 +363,7 @@ void LineEdit::gui_input(const Ref<InputEvent> &p_event) { } } - if (m->get_button_mask() & MOUSE_BUTTON_LEFT) { + if ((m->get_button_mask() & MouseButton::MASK_LEFT) != MouseButton::NONE) { if (selection.creating) { set_caret_at_pixel_pos(m->get_position().x); selection_fill_at_caret(); @@ -577,13 +577,12 @@ void LineEdit::drop_data(const Point2 &p_point, const Variant &p_data) { if (p_data.get_type() == Variant::STRING) { set_caret_at_pixel_pos(p_point.x); - int selected = selection.end - selection.begin; - text.erase(selection.begin, selected); + text = text.left(selection.begin) + text.substr(selection.end); _shape(); insert_text_at_caret(p_data); - selection.begin = caret_column - selected; + selection.begin = caret_column - (selection.end - selection.begin); selection.end = caret_column; } } @@ -664,7 +663,9 @@ void LineEdit::_notification(int p_what) { } Ref<Font> font = get_theme_font(SNAME("font")); - style->draw(ci, Rect2(Point2(), size)); + if (!flat) { + style->draw(ci, Rect2(Point2(), size)); + } if (has_focus()) { get_theme_stylebox(SNAME("focus"))->draw(ci, Rect2(Point2(), size)); @@ -1242,7 +1243,7 @@ void LineEdit::delete_char() { return; } - text.erase(caret_column - 1, 1); + text = text.left(caret_column - 1) + text.substr(caret_column); _shape(); set_caret_column(get_caret_column() - 1); @@ -1254,7 +1255,7 @@ void LineEdit::delete_text(int p_from_column, int p_to_column) { ERR_FAIL_COND_MSG(p_from_column < 0 || p_from_column > p_to_column || p_to_column > text.length(), vformat("Positional parameters (from: %d, to: %d) are inverted or outside the text length (%d).", p_from_column, p_to_column, text.length())); - text.erase(p_from_column, p_to_column - p_from_column); + text = text.left(p_from_column) + text.substr(p_to_column); _shape(); caret_column -= CLAMP(caret_column - p_from_column, 0, p_to_column - p_from_column); @@ -1966,6 +1967,17 @@ Ref<Texture2D> LineEdit::get_right_icon() { return right_icon; } +void LineEdit::set_flat(bool p_enabled) { + if (flat != p_enabled) { + flat = p_enabled; + update(); + } +} + +bool LineEdit::is_flat() const { + return flat; +} + void LineEdit::_text_changed() { _emit_text_change(); _clear_redo(); @@ -2058,25 +2070,25 @@ void LineEdit::_create_undo_state() { undo_stack.push_back(op); } -int LineEdit::_get_menu_action_accelerator(const String &p_action) { +Key LineEdit::_get_menu_action_accelerator(const String &p_action) { const List<Ref<InputEvent>> *events = InputMap::get_singleton()->action_get_events(p_action); if (!events) { - return 0; + return Key::NONE; } // Use first event in the list for the accelerator. const List<Ref<InputEvent>>::Element *first_event = events->front(); if (!first_event) { - return 0; + return Key::NONE; } const Ref<InputEventKey> event = first_event->get(); if (event.is_null()) { - return 0; + return Key::NONE; } // Use physical keycode if non-zero - if (event->get_physical_keycode() != 0) { + if (event->get_physical_keycode() != Key::NONE) { return event->get_physical_keycode_with_modifiers(); } else { return event->get_keycode_with_modifiers(); @@ -2214,6 +2226,8 @@ void LineEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("is_deselect_on_focus_loss_enabled"), &LineEdit::is_deselect_on_focus_loss_enabled); ClassDB::bind_method(D_METHOD("set_right_icon", "icon"), &LineEdit::set_right_icon); ClassDB::bind_method(D_METHOD("get_right_icon"), &LineEdit::get_right_icon); + ClassDB::bind_method(D_METHOD("set_flat", "enabled"), &LineEdit::set_flat); + ClassDB::bind_method(D_METHOD("is_flat"), &LineEdit::is_flat); ADD_SIGNAL(MethodInfo("text_changed", PropertyInfo(Variant::STRING, "new_text"))); ADD_SIGNAL(MethodInfo("text_change_rejected", PropertyInfo(Variant::STRING, "rejected_substring"))); @@ -2269,6 +2283,7 @@ void LineEdit::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "selecting_enabled"), "set_selecting_enabled", "is_selecting_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "deselect_on_focus_loss_enabled"), "set_deselect_on_focus_loss_enabled", "is_deselect_on_focus_loss_enabled"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "right_icon", PROPERTY_HINT_RESOURCE_TYPE, "Texture"), "set_right_icon", "get_right_icon"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "flat"), "set_flat", "is_flat"); ADD_PROPERTY(PropertyInfo(Variant::INT, "text_direction", PROPERTY_HINT_ENUM, "Auto,Left-to-Right,Right-to-Left,Inherited"), "set_text_direction", "get_text_direction"); ADD_PROPERTY(PropertyInfo(Variant::STRING, "language"), "set_language", "get_language"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "draw_control_chars"), "set_draw_control_chars", "get_draw_control_chars"); @@ -2329,21 +2344,21 @@ void LineEdit::_ensure_menu() { // Reorganize context menu. menu->clear(); if (editable) { - menu->add_item(RTR("Cut"), MENU_CUT, is_shortcut_keys_enabled() ? _get_menu_action_accelerator("ui_cut") : 0); + menu->add_item(RTR("Cut"), MENU_CUT, is_shortcut_keys_enabled() ? _get_menu_action_accelerator("ui_cut") : Key::NONE); } - menu->add_item(RTR("Copy"), MENU_COPY, is_shortcut_keys_enabled() ? _get_menu_action_accelerator("ui_copy") : 0); + menu->add_item(RTR("Copy"), MENU_COPY, is_shortcut_keys_enabled() ? _get_menu_action_accelerator("ui_copy") : Key::NONE); if (editable) { - menu->add_item(RTR("Paste"), MENU_PASTE, is_shortcut_keys_enabled() ? _get_menu_action_accelerator("ui_paste") : 0); + menu->add_item(RTR("Paste"), MENU_PASTE, is_shortcut_keys_enabled() ? _get_menu_action_accelerator("ui_paste") : Key::NONE); } menu->add_separator(); if (is_selecting_enabled()) { - menu->add_item(RTR("Select All"), MENU_SELECT_ALL, is_shortcut_keys_enabled() ? _get_menu_action_accelerator("ui_text_select_all") : 0); + menu->add_item(RTR("Select All"), MENU_SELECT_ALL, is_shortcut_keys_enabled() ? _get_menu_action_accelerator("ui_text_select_all") : Key::NONE); } if (editable) { menu->add_item(RTR("Clear"), MENU_CLEAR); menu->add_separator(); - menu->add_item(RTR("Undo"), MENU_UNDO, is_shortcut_keys_enabled() ? _get_menu_action_accelerator("ui_undo") : 0); - menu->add_item(RTR("Redo"), MENU_REDO, is_shortcut_keys_enabled() ? _get_menu_action_accelerator("ui_redo") : 0); + menu->add_item(RTR("Undo"), MENU_UNDO, is_shortcut_keys_enabled() ? _get_menu_action_accelerator("ui_undo") : Key::NONE); + menu->add_item(RTR("Redo"), MENU_REDO, is_shortcut_keys_enabled() ? _get_menu_action_accelerator("ui_redo") : Key::NONE); } menu->add_separator(); menu->add_submenu_item(RTR("Text Writing Direction"), "DirMenu"); diff --git a/scene/gui/line_edit.h b/scene/gui/line_edit.h index 3364e02e01..134d5f8f76 100644 --- a/scene/gui/line_edit.h +++ b/scene/gui/line_edit.h @@ -130,6 +130,7 @@ private: bool middle_mouse_paste_enabled = true; Ref<Texture2D> right_icon; + bool flat = false; struct Selection { int begin = 0; @@ -169,7 +170,7 @@ private: void _clear_redo(); void _create_undo_state(); - int _get_menu_action_accelerator(const String &p_action); + Key _get_menu_action_accelerator(const String &p_action); void _shape(); void _fit_to_width(); @@ -332,6 +333,9 @@ public: void set_right_icon(const Ref<Texture2D> &p_icon); Ref<Texture2D> get_right_icon(); + void set_flat(bool p_enabled); + bool is_flat() const; + virtual bool is_text_field() const override; void show_virtual_keyboard(); diff --git a/scene/gui/menu_button.cpp b/scene/gui/menu_button.cpp index 2561d10f25..39c7b04955 100644 --- a/scene/gui/menu_button.cpp +++ b/scene/gui/menu_button.cpp @@ -203,7 +203,7 @@ void MenuButton::_bind_methods() { ClassDB::bind_method(D_METHOD("is_switch_on_hover"), &MenuButton::is_switch_on_hover); ClassDB::bind_method(D_METHOD("set_disable_shortcuts", "disabled"), &MenuButton::set_disable_shortcuts); - ClassDB::bind_method(D_METHOD("set_item_count"), &MenuButton::set_item_count); + ClassDB::bind_method(D_METHOD("set_item_count", "count"), &MenuButton::set_item_count); ClassDB::bind_method(D_METHOD("get_item_count"), &MenuButton::get_item_count); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "switch_on_hover"), "set_switch_on_hover", "is_switch_on_hover"); diff --git a/scene/gui/popup.cpp b/scene/gui/popup.cpp index f54ff9228b..a48ad0f770 100644 --- a/scene/gui/popup.cpp +++ b/scene/gui/popup.cpp @@ -36,7 +36,7 @@ void Popup::_input_from_window(const Ref<InputEvent> &p_event) { Ref<InputEventKey> key = p_event; - if (key.is_valid() && key->is_pressed() && key->get_keycode() == KEY_ESCAPE) { + if (key.is_valid() && key->is_pressed() && key->get_keycode() == Key::ESCAPE) { _close_pressed(); } } diff --git a/scene/gui/popup_menu.cpp b/scene/gui/popup_menu.cpp index 4fef292f2f..7e9b545776 100644 --- a/scene/gui/popup_menu.cpp +++ b/scene/gui/popup_menu.cpp @@ -39,7 +39,7 @@ String PopupMenu::_get_accel_text(const Item &p_item) const { if (p_item.shortcut.is_valid()) { return p_item.shortcut->get_as_text(); - } else if (p_item.accel) { + } else if (p_item.accel != Key::NONE) { return keycode_get_string(p_item.accel); } return String(); @@ -74,7 +74,7 @@ Size2 PopupMenu::_get_contents_minimum_size() const { size.width += items[i].text_buf->get_size().x; size.height += vseparation; - if (items[i].accel || (items[i].shortcut.is_valid() && items[i].shortcut->has_valid_event())) { + if (items[i].accel != Key::NONE || (items[i].shortcut.is_valid() && items[i].shortcut->has_valid_event())) { int accel_w = hseparation * 2; accel_w += items[i].accel_text_buf->get_size().x; accel_max_w = MAX(accel_w, accel_max_w); @@ -358,15 +358,15 @@ void PopupMenu::gui_input(const Ref<InputEvent> &p_event) { return; } - int button_idx = b->get_button_index(); + MouseButton button_idx = b->get_button_index(); if (!b->is_pressed()) { // Activate the item on release of either the left mouse button or // any mouse button held down when the popup was opened. // This allows for opening the popup and triggering an action in a single mouse click. - if (button_idx == MOUSE_BUTTON_LEFT || (initial_button_mask & (1 << (button_idx - 1)))) { + if (button_idx == MouseButton::LEFT || (initial_button_mask & mouse_button_to_mask(button_idx)) != MouseButton::NONE) { bool was_during_grabbed_click = during_grabbed_click; during_grabbed_click = false; - initial_button_mask = 0; + initial_button_mask = MouseButton::NONE; // Disable clicks under a time threshold to avoid selection right when opening the popup. uint64_t now = OS::get_singleton()->get_ticks_msec(); @@ -637,7 +637,7 @@ void PopupMenu::_draw_items() { } // Accelerator / Shortcut - if (items[i].accel || (items[i].shortcut.is_valid() && items[i].shortcut->has_valid_event())) { + if (items[i].accel != Key::NONE || (items[i].shortcut.is_valid() && items[i].shortcut->has_valid_event())) { if (rtl) { item_ofs.x = scroll_width + style->get_margin(SIDE_LEFT) + item_end_padding; } else { @@ -813,7 +813,7 @@ void PopupMenu::_notification(int p_what) { item.id = p_id == -1 ? items.size() : p_id; \ item.accel = p_accel; -void PopupMenu::add_item(const String &p_label, int p_id, uint32_t p_accel) { +void PopupMenu::add_item(const String &p_label, int p_id, Key p_accel) { Item item; ITEM_SETUP_WITH_ACCEL(p_label, p_id, p_accel); items.push_back(item); @@ -823,7 +823,7 @@ void PopupMenu::add_item(const String &p_label, int p_id, uint32_t p_accel) { notify_property_list_changed(); } -void PopupMenu::add_icon_item(const Ref<Texture2D> &p_icon, const String &p_label, int p_id, uint32_t p_accel) { +void PopupMenu::add_icon_item(const Ref<Texture2D> &p_icon, const String &p_label, int p_id, Key p_accel) { Item item; ITEM_SETUP_WITH_ACCEL(p_label, p_id, p_accel); item.icon = p_icon; @@ -834,7 +834,7 @@ void PopupMenu::add_icon_item(const Ref<Texture2D> &p_icon, const String &p_labe notify_property_list_changed(); } -void PopupMenu::add_check_item(const String &p_label, int p_id, uint32_t p_accel) { +void PopupMenu::add_check_item(const String &p_label, int p_id, Key p_accel) { Item item; ITEM_SETUP_WITH_ACCEL(p_label, p_id, p_accel); item.checkable_type = Item::CHECKABLE_TYPE_CHECK_BOX; @@ -844,7 +844,7 @@ void PopupMenu::add_check_item(const String &p_label, int p_id, uint32_t p_accel child_controls_changed(); } -void PopupMenu::add_icon_check_item(const Ref<Texture2D> &p_icon, const String &p_label, int p_id, uint32_t p_accel) { +void PopupMenu::add_icon_check_item(const Ref<Texture2D> &p_icon, const String &p_label, int p_id, Key p_accel) { Item item; ITEM_SETUP_WITH_ACCEL(p_label, p_id, p_accel); item.icon = p_icon; @@ -855,7 +855,7 @@ void PopupMenu::add_icon_check_item(const Ref<Texture2D> &p_icon, const String & child_controls_changed(); } -void PopupMenu::add_radio_check_item(const String &p_label, int p_id, uint32_t p_accel) { +void PopupMenu::add_radio_check_item(const String &p_label, int p_id, Key p_accel) { Item item; ITEM_SETUP_WITH_ACCEL(p_label, p_id, p_accel); item.checkable_type = Item::CHECKABLE_TYPE_RADIO_BUTTON; @@ -865,7 +865,7 @@ void PopupMenu::add_radio_check_item(const String &p_label, int p_id, uint32_t p child_controls_changed(); } -void PopupMenu::add_icon_radio_check_item(const Ref<Texture2D> &p_icon, const String &p_label, int p_id, uint32_t p_accel) { +void PopupMenu::add_icon_radio_check_item(const Ref<Texture2D> &p_icon, const String &p_label, int p_id, Key p_accel) { Item item; ITEM_SETUP_WITH_ACCEL(p_label, p_id, p_accel); item.icon = p_icon; @@ -876,7 +876,7 @@ void PopupMenu::add_icon_radio_check_item(const Ref<Texture2D> &p_icon, const St child_controls_changed(); } -void PopupMenu::add_multistate_item(const String &p_label, int p_max_states, int p_default_state, int p_id, uint32_t p_accel) { +void PopupMenu::add_multistate_item(const String &p_label, int p_max_states, int p_default_state, int p_id, Key p_accel) { Item item; ITEM_SETUP_WITH_ACCEL(p_label, p_id, p_accel); item.max_states = p_max_states; @@ -1045,7 +1045,7 @@ void PopupMenu::set_item_id(int p_idx, int p_id) { child_controls_changed(); } -void PopupMenu::set_item_accelerator(int p_idx, uint32_t p_accel) { +void PopupMenu::set_item_accelerator(int p_idx, Key p_accel) { ERR_FAIL_INDEX(p_idx, items.size()); items.write[p_idx].accel = p_accel; items.write[p_idx].dirty = true; @@ -1121,8 +1121,8 @@ Ref<Texture2D> PopupMenu::get_item_icon(int p_idx) const { return items[p_idx].icon; } -uint32_t PopupMenu::get_item_accelerator(int p_idx) const { - ERR_FAIL_INDEX_V(p_idx, items.size(), 0); +Key PopupMenu::get_item_accelerator(int p_idx) const { + ERR_FAIL_INDEX_V(p_idx, items.size(), Key::NONE); return items[p_idx].accel; } @@ -1286,25 +1286,25 @@ int PopupMenu::get_item_count() const { } bool PopupMenu::activate_item_by_event(const Ref<InputEvent> &p_event, bool p_for_global_only) { - Key code = KEY_NONE; + Key code = Key::NONE; Ref<InputEventKey> k = p_event; if (k.is_valid()) { code = k->get_keycode(); - if (code == KEY_NONE) { + if (code == Key::NONE) { code = (Key)k->get_unicode(); } if (k->is_ctrl_pressed()) { - code |= KEY_MASK_CTRL; + code |= KeyModifierMask::CTRL; } if (k->is_alt_pressed()) { - code |= KEY_MASK_ALT; + code |= KeyModifierMask::ALT; } if (k->is_meta_pressed()) { - code |= KEY_MASK_META; + code |= KeyModifierMask::META; } if (k->is_shift_pressed()) { - code |= KEY_MASK_SHIFT; + code |= KeyModifierMask::SHIFT; } } @@ -1318,7 +1318,7 @@ bool PopupMenu::activate_item_by_event(const Ref<InputEvent> &p_event, bool p_fo return true; } - if (code != 0 && items[i].accel == code) { + if (code != Key::NONE && items[i].accel == code) { activate_item(i); return true; } @@ -1603,7 +1603,7 @@ bool PopupMenu::_set(const StringName &p_name, const Variant &p_value) { set_item_id(idx, id); set_item_metadata(idx, meta); set_item_as_separator(idx, sep); - set_item_accelerator(idx, accel); + set_item_accelerator(idx, (Key)accel); set_item_submenu(idx, subm); } } @@ -1733,7 +1733,7 @@ void PopupMenu::_bind_methods() { ClassDB::bind_method(D_METHOD("get_item_shortcut", "idx"), &PopupMenu::get_item_shortcut); ClassDB::bind_method(D_METHOD("get_current_index"), &PopupMenu::get_current_index); - ClassDB::bind_method(D_METHOD("set_item_count"), &PopupMenu::set_item_count); + ClassDB::bind_method(D_METHOD("set_item_count", "count"), &PopupMenu::set_item_count); ClassDB::bind_method(D_METHOD("get_item_count"), &PopupMenu::get_item_count); ClassDB::bind_method(D_METHOD("remove_item", "idx"), &PopupMenu::remove_item); diff --git a/scene/gui/popup_menu.h b/scene/gui/popup_menu.h index 80874ca1e0..22912fb59c 100644 --- a/scene/gui/popup_menu.h +++ b/scene/gui/popup_menu.h @@ -66,7 +66,7 @@ class PopupMenu : public Popup { Variant metadata; String submenu; String tooltip; - uint32_t accel = 0; + Key accel = Key::NONE; int _ofs_cache = 0; int _height_cache = 0; int h_ofs = 0; @@ -92,7 +92,7 @@ class PopupMenu : public Popup { Timer *submenu_timer; List<Rect2> autohide_areas; Vector<Item> items; - int initial_button_mask = 0; + MouseButton initial_button_mask = MouseButton::NONE; bool during_grabbed_click = false; int mouse_over = -1; int submenu_over = -1; @@ -148,14 +148,14 @@ public: // this value should be updated to reflect the new size. static const int ITEM_PROPERTY_SIZE = 10; - void add_item(const String &p_label, int p_id = -1, uint32_t p_accel = 0); - void add_icon_item(const Ref<Texture2D> &p_icon, const String &p_label, int p_id = -1, uint32_t p_accel = 0); - void add_check_item(const String &p_label, int p_id = -1, uint32_t p_accel = 0); - void add_icon_check_item(const Ref<Texture2D> &p_icon, const String &p_label, int p_id = -1, uint32_t p_accel = 0); - void add_radio_check_item(const String &p_label, int p_id = -1, uint32_t p_accel = 0); - void add_icon_radio_check_item(const Ref<Texture2D> &p_icon, const String &p_label, int p_id = -1, uint32_t p_accel = 0); + void add_item(const String &p_label, int p_id = -1, Key p_accel = Key::NONE); + void add_icon_item(const Ref<Texture2D> &p_icon, const String &p_label, int p_id = -1, Key p_accel = Key::NONE); + void add_check_item(const String &p_label, int p_id = -1, Key p_accel = Key::NONE); + void add_icon_check_item(const Ref<Texture2D> &p_icon, const String &p_label, int p_id = -1, Key p_accel = Key::NONE); + void add_radio_check_item(const String &p_label, int p_id = -1, Key p_accel = Key::NONE); + void add_icon_radio_check_item(const Ref<Texture2D> &p_icon, const String &p_label, int p_id = -1, Key p_accel = Key::NONE); - void add_multistate_item(const String &p_label, int p_max_states, int p_default_state = 0, int p_id = -1, uint32_t p_accel = 0); + void add_multistate_item(const String &p_label, int p_max_states, int p_default_state = 0, int p_id = -1, Key p_accel = Key::NONE); void add_shortcut(const Ref<Shortcut> &p_shortcut, int p_id = -1, bool p_global = false); void add_icon_shortcut(const Ref<Texture2D> &p_icon, const Ref<Shortcut> &p_shortcut, int p_id = -1, bool p_global = false); @@ -175,7 +175,7 @@ public: void set_item_icon(int p_idx, const Ref<Texture2D> &p_icon); void set_item_checked(int p_idx, bool p_checked); void set_item_id(int p_idx, int p_id); - void set_item_accelerator(int p_idx, uint32_t p_accel); + void set_item_accelerator(int p_idx, Key p_accel); void set_item_metadata(int p_idx, const Variant &p_meta); void set_item_disabled(int p_idx, bool p_disabled); void set_item_submenu(int p_idx, const String &p_submenu); @@ -200,7 +200,7 @@ public: bool is_item_checked(int p_idx) const; int get_item_id(int p_idx) const; int get_item_index(int p_id) const; - uint32_t get_item_accelerator(int p_idx) const; + Key get_item_accelerator(int p_idx) const; Variant get_item_metadata(int p_idx) const; bool is_item_disabled(int p_idx) const; String get_item_submenu(int p_idx) const; diff --git a/scene/gui/range.cpp b/scene/gui/range.cpp index 92d4261d8d..c4f05a7975 100644 --- a/scene/gui/range.cpp +++ b/scene/gui/range.cpp @@ -61,6 +61,11 @@ void Range::_changed_notify(const char *p_what) { update(); } +void Range::_validate_values() { + shared->max = MAX(shared->max, shared->min); + shared->page = CLAMP(shared->page, 0, shared->max - shared->min); +} + void Range::Shared::emit_changed(const char *p_what) { for (Set<Range *>::Element *E = owners.front(); E; E = E->next()) { Range *r = E->get(); @@ -100,6 +105,7 @@ void Range::set_value(double p_val) { void Range::set_min(double p_min) { shared->min = p_min; set_value(shared->val); + _validate_values(); shared->emit_changed("min"); @@ -109,6 +115,7 @@ void Range::set_min(double p_min) { void Range::set_max(double p_max) { shared->max = p_max; set_value(shared->val); + _validate_values(); shared->emit_changed("max"); } @@ -121,6 +128,7 @@ void Range::set_step(double p_step) { void Range::set_page(double p_page) { shared->page = p_page; set_value(shared->val); + _validate_values(); shared->emit_changed("page"); } @@ -270,6 +278,12 @@ void Range::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "rounded"), "set_use_rounded_values", "is_using_rounded_values"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "allow_greater"), "set_allow_greater", "is_greater_allowed"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "allow_lesser"), "set_allow_lesser", "is_lesser_allowed"); + + ADD_LINKED_PROPERTY("min_value", "value"); + ADD_LINKED_PROPERTY("min_value", "max_value"); + ADD_LINKED_PROPERTY("min_value", "page"); + ADD_LINKED_PROPERTY("max_value", "value"); + ADD_LINKED_PROPERTY("max_value", "page"); } void Range::set_use_rounded_values(bool p_enable) { diff --git a/scene/gui/range.h b/scene/gui/range.h index 7a129e88d6..0dc702b19c 100644 --- a/scene/gui/range.h +++ b/scene/gui/range.h @@ -59,6 +59,7 @@ class Range : public Control { void _value_changed_notify(); void _changed_notify(const char *p_what = ""); + void _validate_values(); protected: virtual void _value_changed(double) {} diff --git a/scene/gui/rich_text_label.cpp b/scene/gui/rich_text_label.cpp index f1efbbda98..f191dfecb4 100644 --- a/scene/gui/rich_text_label.cpp +++ b/scene/gui/rich_text_label.cpp @@ -36,7 +36,7 @@ #include "scene/scene_string_names.h" #include "servers/display_server.h" -#include "modules/modules_enabled.gen.h" +#include "modules/modules_enabled.gen.h" // For regex. #ifdef MODULE_REGEX_ENABLED #include "modules/regex/regex.h" #endif @@ -621,7 +621,7 @@ void RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font> } } -int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_ofs, int p_width, const Color &p_base_color, int p_outline_size, const Color &p_outline_color, const Color &p_font_shadow_color, bool p_shadow_as_outline, const Point2 &p_shadow_ofs) { +int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_ofs, int p_width, const Color &p_base_color, int p_outline_size, const Color &p_outline_color, const Color &p_font_shadow_color, int p_shadow_outline_size, const Point2 &p_shadow_ofs) { Vector2 off; ERR_FAIL_COND_V(p_frame == nullptr, 0); @@ -804,7 +804,7 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o } for (int j = 0; j < frame->lines.size(); j++) { - _draw_line(frame, j, p_ofs + rect.position + off + Vector2(0, frame->lines[j].offset.y), rect.size.x, p_base_color, p_outline_size, p_outline_color, p_font_shadow_color, p_shadow_as_outline, p_shadow_ofs); + _draw_line(frame, j, p_ofs + rect.position + off + Vector2(0, frame->lines[j].offset.y), rect.size.x, p_base_color, p_outline_size, p_outline_color, p_font_shadow_color, p_shadow_outline_size, p_shadow_ofs); } idx++; } @@ -824,7 +824,8 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o Item *it = _get_item_at_pos(it_from, it_to, glyphs[i].start); int size = _find_outline_size(it, p_outline_size); Color font_color = _find_outline_color(it, p_outline_color); - if (size <= 0) { + Color font_shadow_color = p_font_shadow_color; + if ((size <= 0 || font_color.a == 0) && (font_shadow_color.a == 0)) { gloff.x += glyphs[i].advance; continue; } @@ -871,9 +872,10 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o faded_visibility = faded_visibility < 0.0f ? 0.0f : faded_visibility; } font_color.a = faded_visibility; + font_shadow_color.a = faded_visibility; } - bool visible = (font_color.a != 0); + bool visible = (font_color.a != 0) || (font_shadow_color.a != 0); for (int j = 0; j < fx_stack.size(); j++) { ItemFX *item_fx = fx_stack[j]; @@ -942,18 +944,19 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o } } - Point2 shadow_ofs(get_theme_constant(SNAME("shadow_offset_x")), get_theme_constant(SNAME("shadow_offset_y"))); - // Draw glyph outlines. for (int j = 0; j < glyphs[i].repeat; j++) { if (visible) { if (frid != RID()) { - if (p_shadow_as_outline) { - TS->font_draw_glyph_outline(frid, ci, glyphs[i].font_size, size, p_ofs + fx_offset + gloff + Vector2(-shadow_ofs.x, shadow_ofs.y), gl, p_font_shadow_color); - TS->font_draw_glyph_outline(frid, ci, glyphs[i].font_size, size, p_ofs + fx_offset + gloff + Vector2(shadow_ofs.x, -shadow_ofs.y), gl, p_font_shadow_color); - TS->font_draw_glyph_outline(frid, ci, glyphs[i].font_size, size, p_ofs + fx_offset + gloff + Vector2(-shadow_ofs.x, -shadow_ofs.y), gl, p_font_shadow_color); + if (font_shadow_color.a > 0) { + TS->font_draw_glyph(frid, ci, glyphs[i].font_size, p_ofs + fx_offset + gloff + p_shadow_ofs, gl, font_shadow_color); + } + if (font_shadow_color.a > 0 && p_shadow_outline_size > 0) { + TS->font_draw_glyph_outline(frid, ci, glyphs[i].font_size, p_shadow_outline_size, p_ofs + fx_offset + gloff + p_shadow_ofs, gl, font_shadow_color); + } + if (font_color.a != 0.0 && size > 0) { + TS->font_draw_glyph_outline(frid, ci, glyphs[i].font_size, size, p_ofs + fx_offset + gloff, gl, font_color); } - TS->font_draw_glyph_outline(frid, ci, glyphs[i].font_size, size, p_ofs + fx_offset + gloff, gl, font_color); } } gloff.x += glyphs[i].advance; @@ -1470,7 +1473,7 @@ void RichTextLabel::_notification(int p_what) { Color outline_color = get_theme_color(SNAME("font_outline_color")); int outline_size = get_theme_constant(SNAME("outline_size")); Color font_shadow_color = get_theme_color(SNAME("font_shadow_color")); - bool use_outline = get_theme_constant(SNAME("shadow_as_outline")); + int shadow_outline_size = get_theme_constant(SNAME("shadow_outline_size")); Point2 shadow_ofs(get_theme_constant(SNAME("shadow_offset_x")), get_theme_constant(SNAME("shadow_offset_y"))); visible_paragraph_count = 0; @@ -1480,7 +1483,7 @@ void RichTextLabel::_notification(int p_what) { Point2 ofs = text_rect.get_position() + Vector2(0, main->lines[from_line].offset.y - vofs); while (ofs.y < size.height && from_line < main->lines.size()) { visible_paragraph_count++; - visible_line_count += _draw_line(main, from_line, ofs, text_rect.size.x, base_color, outline_size, outline_color, font_shadow_color, use_outline, shadow_ofs); + visible_line_count += _draw_line(main, from_line, ofs, text_rect.size.x, base_color, outline_size, outline_color, font_shadow_color, shadow_outline_size, shadow_ofs); ofs.y += main->lines[from_line].text_buf->get_size().y + get_theme_constant(SNAME("line_separation")); from_line++; } @@ -1542,7 +1545,7 @@ void RichTextLabel::gui_input(const Ref<InputEvent> &p_event) { return; } - if (b->get_button_index() == MOUSE_BUTTON_LEFT) { + if (b->get_button_index() == MouseButton::LEFT) { if (b->is_pressed() && !b->is_double_click()) { scroll_updated = false; ItemFrame *c_frame = nullptr; @@ -1633,12 +1636,12 @@ void RichTextLabel::gui_input(const Ref<InputEvent> &p_event) { } } - if (b->get_button_index() == MOUSE_BUTTON_WHEEL_UP) { + if (b->get_button_index() == MouseButton::WHEEL_UP) { if (scroll_active) { vscroll->set_value(vscroll->get_value() - vscroll->get_page() * b->get_factor() * 0.5 / 8); } } - if (b->get_button_index() == MOUSE_BUTTON_WHEEL_DOWN) { + if (b->get_button_index() == MouseButton::WHEEL_DOWN) { if (scroll_active) { vscroll->set_value(vscroll->get_value() + vscroll->get_page() * b->get_factor() * 0.5 / 8); } @@ -3798,45 +3801,32 @@ String RichTextLabel::_get_line_text(ItemFrame *p_frame, int p_line, Selection p } } for (Item *it = l.from; it && it != it_to; it = _get_next_item(it)) { + if (it->type == ITEM_TABLE) { + ItemTable *table = static_cast<ItemTable *>(it); + for (Item *E : table->subitems) { + ERR_CONTINUE(E->type != ITEM_FRAME); // Children should all be frames. + ItemFrame *frame = static_cast<ItemFrame *>(E); + for (int i = 0; i < frame->lines.size(); i++) { + text += _get_line_text(frame, i, p_selection); + } + } + } if ((p_selection.to_item != nullptr) && (p_selection.to_item->index < l.from->index)) { - break; + continue; } if ((p_selection.from_item != nullptr) && (p_selection.from_item->index >= end_idx)) { - break; + continue; } - switch (it->type) { - case ITEM_NEWLINE: { - text += "\n"; - } break; - case ITEM_TEXT: { - ItemText *t = (ItemText *)it; - text += t->text; - } break; - case ITEM_IMAGE: { - text += " "; - } break; - case ITEM_TABLE: { - ItemTable *table = static_cast<ItemTable *>(it); - int idx = 0; - int col_count = table->columns.size(); - for (Item *E : table->subitems) { - ERR_CONTINUE(E->type != ITEM_FRAME); // Children should all be frames. - ItemFrame *frame = static_cast<ItemFrame *>(E); - int column = idx % col_count; - - for (int i = 0; i < frame->lines.size(); i++) { - text += _get_line_text(frame, i, p_selection); - } - if (column == col_count - 1) { - text += "\n"; - } else { - text += " "; - } - idx++; - } - } break; - default: - break; + if (it->type == ITEM_DROPCAP) { + const ItemDropcap *dc = static_cast<ItemDropcap *>(it); + text += dc->text; + } else if (it->type == ITEM_TEXT) { + const ItemText *t = static_cast<ItemText *>(it); + text += t->text; + } else if (it->type == ITEM_NEWLINE) { + text += "\n"; + } else if (it->type == ITEM_IMAGE) { + text += " "; } } if ((l.from != nullptr) && (p_frame == p_selection.to_frame) && (p_selection.to_item != nullptr) && (p_selection.to_item->index >= l.from->index) && (p_selection.to_item->index < end_idx)) { diff --git a/scene/gui/rich_text_label.h b/scene/gui/rich_text_label.h index f3c4c11cc8..5b58f14d96 100644 --- a/scene/gui/rich_text_label.h +++ b/scene/gui/rich_text_label.h @@ -412,7 +412,7 @@ private: void _shape_line(ItemFrame *p_frame, int p_line, const Ref<Font> &p_base_font, int p_base_font_size, int p_width, int *r_char_offset); void _resize_line(ItemFrame *p_frame, int p_line, const Ref<Font> &p_base_font, int p_base_font_size, int p_width); - int _draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_ofs, int p_width, const Color &p_base_color, int p_outline_size, const Color &p_outline_color, const Color &p_font_shadow_color, bool p_shadow_as_outline, const Point2 &shadow_ofs); + int _draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_ofs, int p_width, const Color &p_base_color, int p_outline_size, const Color &p_outline_color, const Color &p_font_shadow_color, int p_shadow_outline_size, const Point2 &p_shadow_ofs); float _find_click_in_line(ItemFrame *p_frame, int p_line, const Vector2 &p_ofs, int p_width, const Point2i &p_click, ItemFrame **r_click_frame = nullptr, int *r_click_line = nullptr, Item **r_click_item = nullptr, int *r_click_char = nullptr); String _roman(int p_num, bool p_capitalize) const; diff --git a/scene/gui/scroll_bar.cpp b/scene/gui/scroll_bar.cpp index 4a3a6837d5..8c292e663e 100644 --- a/scene/gui/scroll_bar.cpp +++ b/scene/gui/scroll_bar.cpp @@ -54,17 +54,17 @@ void ScrollBar::gui_input(const Ref<InputEvent> &p_event) { if (b.is_valid()) { accept_event(); - if (b->get_button_index() == MOUSE_BUTTON_WHEEL_DOWN && b->is_pressed()) { + if (b->get_button_index() == MouseButton::WHEEL_DOWN && b->is_pressed()) { set_value(get_value() + get_page() / 4.0); accept_event(); } - if (b->get_button_index() == MOUSE_BUTTON_WHEEL_UP && b->is_pressed()) { + if (b->get_button_index() == MouseButton::WHEEL_UP && b->is_pressed()) { set_value(get_value() - get_page() / 4.0); accept_event(); } - if (b->get_button_index() != MOUSE_BUTTON_LEFT) { + if (b->get_button_index() != MouseButton::LEFT) { return; } @@ -525,7 +525,7 @@ void ScrollBar::_drag_node_input(const Ref<InputEvent> &p_input) { Ref<InputEventMouseButton> mb = p_input; if (mb.is_valid()) { - if (mb->get_button_index() != 1) { + if (mb->get_button_index() != MouseButton::LEFT) { return; } diff --git a/scene/gui/scroll_container.cpp b/scene/gui/scroll_container.cpp index 0c0ec39c7f..7b2ea46e17 100644 --- a/scene/gui/scroll_container.cpp +++ b/scene/gui/scroll_container.cpp @@ -92,7 +92,7 @@ void ScrollContainer::gui_input(const Ref<InputEvent> &p_gui_input) { Ref<InputEventMouseButton> mb = p_gui_input; if (mb.is_valid()) { - if (mb->get_button_index() == MOUSE_BUTTON_WHEEL_UP && mb->is_pressed()) { + if (mb->get_button_index() == MouseButton::WHEEL_UP && mb->is_pressed()) { // only horizontal is enabled, scroll horizontally if (h_scroll->is_visible() && (!v_scroll->is_visible() || mb->is_shift_pressed())) { h_scroll->set_value(h_scroll->get_value() - h_scroll->get_page() / 8 * mb->get_factor()); @@ -101,7 +101,7 @@ void ScrollContainer::gui_input(const Ref<InputEvent> &p_gui_input) { } } - if (mb->get_button_index() == MOUSE_BUTTON_WHEEL_DOWN && mb->is_pressed()) { + if (mb->get_button_index() == MouseButton::WHEEL_DOWN && mb->is_pressed()) { // only horizontal is enabled, scroll horizontally if (h_scroll->is_visible() && (!v_scroll->is_visible() || mb->is_shift_pressed())) { h_scroll->set_value(h_scroll->get_value() + h_scroll->get_page() / 8 * mb->get_factor()); @@ -110,13 +110,13 @@ void ScrollContainer::gui_input(const Ref<InputEvent> &p_gui_input) { } } - if (mb->get_button_index() == MOUSE_BUTTON_WHEEL_LEFT && mb->is_pressed()) { + if (mb->get_button_index() == MouseButton::WHEEL_LEFT && mb->is_pressed()) { if (h_scroll->is_visible_in_tree()) { h_scroll->set_value(h_scroll->get_value() - h_scroll->get_page() * mb->get_factor() / 8); } } - if (mb->get_button_index() == MOUSE_BUTTON_WHEEL_RIGHT && mb->is_pressed()) { + if (mb->get_button_index() == MouseButton::WHEEL_RIGHT && mb->is_pressed()) { if (h_scroll->is_visible_in_tree()) { h_scroll->set_value(h_scroll->get_value() + h_scroll->get_page() * mb->get_factor() / 8); } @@ -130,7 +130,7 @@ void ScrollContainer::gui_input(const Ref<InputEvent> &p_gui_input) { return; } - if (mb->get_button_index() != MOUSE_BUTTON_LEFT) { + if (mb->get_button_index() != MouseButton::LEFT) { return; } @@ -320,7 +320,9 @@ void ScrollContainer::_notification(int p_what) { }; if (p_what == NOTIFICATION_READY) { - get_viewport()->connect("gui_focus_changed", callable_mp(this, &ScrollContainer::_gui_focus_changed)); + Viewport *viewport = get_viewport(); + ERR_FAIL_COND(!viewport); + viewport->connect("gui_focus_changed", callable_mp(this, &ScrollContainer::_gui_focus_changed)); _update_dimensions(); } diff --git a/scene/gui/slider.cpp b/scene/gui/slider.cpp index 352f87954e..4cc425aad3 100644 --- a/scene/gui/slider.cpp +++ b/scene/gui/slider.cpp @@ -55,7 +55,7 @@ void Slider::gui_input(const Ref<InputEvent> &p_event) { Ref<InputEventMouseButton> mb = p_event; if (mb.is_valid()) { - if (mb->get_button_index() == MOUSE_BUTTON_LEFT) { + if (mb->get_button_index() == MouseButton::LEFT) { if (mb->is_pressed()) { Ref<Texture2D> grabber = get_theme_icon(mouse_inside || has_focus() ? "grabber_highlight" : "grabber"); grab.pos = orientation == VERTICAL ? mb->get_position().y : mb->get_position().x; @@ -74,10 +74,10 @@ void Slider::gui_input(const Ref<InputEvent> &p_event) { grab.active = false; } } else if (scrollable) { - if (mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_WHEEL_UP) { + if (mb->is_pressed() && mb->get_button_index() == MouseButton::WHEEL_UP) { grab_focus(); set_value(get_value() + get_step()); - } else if (mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_WHEEL_DOWN) { + } else if (mb->is_pressed() && mb->get_button_index() == MouseButton::WHEEL_DOWN) { grab_focus(); set_value(get_value() - get_step()); } diff --git a/scene/gui/spin_box.cpp b/scene/gui/spin_box.cpp index 0446e1b402..f30206c943 100644 --- a/scene/gui/spin_box.cpp +++ b/scene/gui/spin_box.cpp @@ -85,7 +85,7 @@ void SpinBox::_line_edit_input(const Ref<InputEvent> &p_event) { } void SpinBox::_range_click_timeout() { - if (!drag.enabled && Input::get_singleton()->is_mouse_button_pressed(MOUSE_BUTTON_LEFT)) { + if (!drag.enabled && Input::get_singleton()->is_mouse_button_pressed(MouseButton::LEFT)) { bool up = get_local_mouse_position().y < (get_size().height / 2); set_value(get_value() + (up ? get_step() : -get_step())); @@ -121,7 +121,7 @@ void SpinBox::gui_input(const Ref<InputEvent> &p_event) { bool up = mb->get_position().y < (get_size().height / 2); switch (mb->get_button_index()) { - case MOUSE_BUTTON_LEFT: { + case MouseButton::LEFT: { line_edit->grab_focus(); set_value(get_value() + (up ? get_step() : -get_step())); @@ -133,17 +133,17 @@ void SpinBox::gui_input(const Ref<InputEvent> &p_event) { drag.allowed = true; drag.capture_pos = mb->get_position(); } break; - case MOUSE_BUTTON_RIGHT: { + case MouseButton::RIGHT: { line_edit->grab_focus(); set_value((up ? get_max() : get_min())); } break; - case MOUSE_BUTTON_WHEEL_UP: { + case MouseButton::WHEEL_UP: { if (line_edit->has_focus()) { set_value(get_value() + get_step() * mb->get_factor()); accept_event(); } } break; - case MOUSE_BUTTON_WHEEL_DOWN: { + case MouseButton::WHEEL_DOWN: { if (line_edit->has_focus()) { set_value(get_value() - get_step() * mb->get_factor()); accept_event(); @@ -154,7 +154,7 @@ void SpinBox::gui_input(const Ref<InputEvent> &p_event) { } } - if (mb.is_valid() && !mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_LEFT) { + if (mb.is_valid() && !mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT) { //set_default_cursor_shape(CURSOR_ARROW); range_click_timer->stop(); _release_mouse(); @@ -163,10 +163,10 @@ void SpinBox::gui_input(const Ref<InputEvent> &p_event) { Ref<InputEventMouseMotion> mm = p_event; - if (mm.is_valid() && mm->get_button_mask() & MOUSE_BUTTON_MASK_LEFT) { + if (mm.is_valid() && (mm->get_button_mask() & MouseButton::MASK_LEFT) != MouseButton::NONE) { if (drag.enabled) { drag.diff_y += mm->get_relative().y; - float diff_y = -0.01 * Math::pow(ABS(drag.diff_y), 1.8f) * SGN(drag.diff_y); + float diff_y = -0.01 * Math::pow(ABS(drag.diff_y), 1.8f) * SIGN(drag.diff_y); set_value(CLAMP(drag.base_val + get_step() * diff_y, get_min(), get_max())); } else if (drag.allowed && drag.capture_pos.distance_to(mm->get_position()) > 2) { Input::get_singleton()->set_mouse_mode(Input::MOUSE_MODE_CAPTURED); diff --git a/scene/gui/split_container.cpp b/scene/gui/split_container.cpp index 4736a1ad37..6b53c0220e 100644 --- a/scene/gui/split_container.cpp +++ b/scene/gui/split_container.cpp @@ -216,7 +216,7 @@ void SplitContainer::gui_input(const Ref<InputEvent> &p_event) { Ref<InputEventMouseButton> mb = p_event; if (mb.is_valid()) { - if (mb->get_button_index() == MOUSE_BUTTON_LEFT) { + if (mb->get_button_index() == MouseButton::LEFT) { if (mb->is_pressed()) { int sep = get_theme_constant(SNAME("separation")); diff --git a/scene/gui/tab_bar.cpp b/scene/gui/tab_bar.cpp index 405fbdae75..1dda29f668 100644 --- a/scene/gui/tab_bar.cpp +++ b/scene/gui/tab_bar.cpp @@ -143,7 +143,7 @@ void TabBar::gui_input(const Ref<InputEvent> &p_event) { Ref<InputEventMouseButton> mb = p_event; if (mb.is_valid()) { - if (mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_WHEEL_UP && !mb->is_command_pressed()) { + if (mb->is_pressed() && mb->get_button_index() == MouseButton::WHEEL_UP && !mb->is_command_pressed()) { if (scrolling_enabled && buttons_visible) { if (offset > 0) { offset--; @@ -152,7 +152,7 @@ void TabBar::gui_input(const Ref<InputEvent> &p_event) { } } - if (mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_WHEEL_DOWN && !mb->is_command_pressed()) { + if (mb->is_pressed() && mb->get_button_index() == MouseButton::WHEEL_DOWN && !mb->is_command_pressed()) { if (scrolling_enabled && buttons_visible) { if (missing_right) { offset++; @@ -162,7 +162,7 @@ void TabBar::gui_input(const Ref<InputEvent> &p_event) { } } - if (rb_pressing && !mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_LEFT) { + if (rb_pressing && !mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT) { if (rb_hover != -1) { // pressed emit_signal(SNAME("tab_rmb_clicked"), rb_hover); @@ -172,7 +172,7 @@ void TabBar::gui_input(const Ref<InputEvent> &p_event) { update(); } - if (cb_pressing && !mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_LEFT) { + if (cb_pressing && !mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT) { if (cb_hover != -1) { // pressed emit_signal(SNAME("tab_close_pressed"), cb_hover); @@ -182,7 +182,7 @@ void TabBar::gui_input(const Ref<InputEvent> &p_event) { update(); } - if (mb->is_pressed() && (mb->get_button_index() == MOUSE_BUTTON_LEFT || (select_with_rmb && mb->get_button_index() == MOUSE_BUTTON_RIGHT))) { + if (mb->is_pressed() && (mb->get_button_index() == MouseButton::LEFT || (select_with_rmb && mb->get_button_index() == MouseButton::RIGHT))) { // clicks Point2 pos = mb->get_position(); diff --git a/scene/gui/tab_container.cpp b/scene/gui/tab_container.cpp index c8a0501d8a..ff53d91ea3 100644 --- a/scene/gui/tab_container.cpp +++ b/scene/gui/tab_container.cpp @@ -78,7 +78,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) { + if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT) { Point2 pos = mb->get_position(); Size2 size = get_size(); diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp index cb7a6c0978..74268707ff 100644 --- a/scene/gui/text_edit.cpp +++ b/scene/gui/text_edit.cpp @@ -1407,7 +1407,7 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) { } if (mb->is_pressed()) { - if (mb->get_button_index() == MOUSE_BUTTON_WHEEL_UP && !mb->is_command_pressed()) { + if (mb->get_button_index() == MouseButton::WHEEL_UP && !mb->is_command_pressed()) { if (mb->is_shift_pressed()) { h_scroll->set_value(h_scroll->get_value() - (100 * mb->get_factor())); } else if (mb->is_alt_pressed()) { @@ -1418,7 +1418,7 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) { _scroll_up(3 * mb->get_factor()); } } - if (mb->get_button_index() == MOUSE_BUTTON_WHEEL_DOWN && !mb->is_command_pressed()) { + if (mb->get_button_index() == MouseButton::WHEEL_DOWN && !mb->is_command_pressed()) { if (mb->is_shift_pressed()) { h_scroll->set_value(h_scroll->get_value() + (100 * mb->get_factor())); } else if (mb->is_alt_pressed()) { @@ -1429,13 +1429,13 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) { _scroll_down(3 * mb->get_factor()); } } - if (mb->get_button_index() == MOUSE_BUTTON_WHEEL_LEFT) { + if (mb->get_button_index() == MouseButton::WHEEL_LEFT) { h_scroll->set_value(h_scroll->get_value() - (100 * mb->get_factor())); } - if (mb->get_button_index() == MOUSE_BUTTON_WHEEL_RIGHT) { + if (mb->get_button_index() == MouseButton::WHEEL_RIGHT) { h_scroll->set_value(h_scroll->get_value() + (100 * mb->get_factor())); } - if (mb->get_button_index() == MOUSE_BUTTON_LEFT) { + if (mb->get_button_index() == MouseButton::LEFT) { _reset_caret_blink_timer(); Point2i pos = get_line_column_at_pos(mpos); @@ -1538,11 +1538,11 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) { update(); } - if (is_middle_mouse_paste_enabled() && mb->get_button_index() == MOUSE_BUTTON_MIDDLE && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_CLIPBOARD_PRIMARY)) { + if (is_middle_mouse_paste_enabled() && mb->get_button_index() == MouseButton::MIDDLE && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_CLIPBOARD_PRIMARY)) { paste_primary_clipboard(); } - if (mb->get_button_index() == MOUSE_BUTTON_RIGHT && context_menu_enabled) { + if (mb->get_button_index() == MouseButton::RIGHT && context_menu_enabled) { _reset_caret_blink_timer(); Point2i pos = get_line_column_at_pos(mpos); @@ -1574,7 +1574,7 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) { grab_focus(); } } else { - if (mb->get_button_index() == MOUSE_BUTTON_LEFT) { + if (mb->get_button_index() == MouseButton::LEFT) { dragging_minimap = false; dragging_selection = false; can_drag_minimap = false; @@ -1613,7 +1613,7 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) { mpos.x = get_size().x - mpos.x; } - if (mm->get_button_mask() & MOUSE_BUTTON_MASK_LEFT && get_viewport()->gui_get_drag_data() == Variant()) { // Ignore if dragging. + if ((mm->get_button_mask() & MouseButton::MASK_LEFT) != MouseButton::NONE && get_viewport()->gui_get_drag_data() == Variant()) { // Ignore if dragging. _reset_caret_blink_timer(); if (draw_minimap && !dragging_selection) { @@ -1681,7 +1681,7 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) { } // If a modifier has been pressed, and nothing else, return. - if (k->get_keycode() == KEY_CTRL || k->get_keycode() == KEY_ALT || k->get_keycode() == KEY_SHIFT || k->get_keycode() == KEY_META) { + if (k->get_keycode() == Key::CTRL || k->get_keycode() == Key::ALT || k->get_keycode() == Key::SHIFT || k->get_keycode() == Key::META) { return; } @@ -1898,7 +1898,7 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) { } // Handle Unicode (if no modifiers active). Tab has a value of 0x09. - if (allow_unicode_handling && editable && (k->get_unicode() >= 32 || k->get_keycode() == KEY_TAB)) { + if (allow_unicode_handling && editable && (k->get_unicode() >= 32 || k->get_keycode() == Key::TAB)) { handle_unicode_input(k->get_unicode()); accept_event(); return; @@ -2878,6 +2878,16 @@ Point2i TextEdit::get_next_visible_line_index_offset_from(int p_line_from, int p } } wrap_index = get_line_wrap_count(MIN(i, text.size() - 1)) - MAX(0, num_visible - p_visible_amount); + + // If we are a hidden line, then we are the last line as we cannot reach "p_visible_amount". + // This means we need to backtrack to get last visible line. + // Currently, line 0 cannot be hidden so this should always be valid. + int line = (p_line_from + num_total) - 1; + if (_is_line_hidden(line)) { + Point2i backtrack = get_next_visible_line_index_offset_from(line, 0, -1); + num_total = num_total - (backtrack.x - 1); + wrap_index = backtrack.y; + } } else { p_visible_amount = ABS(p_visible_amount); int i; @@ -3386,7 +3396,7 @@ String TextEdit::get_word_at_pos(const Vector2 &p_pos) const { return String(); } -Point2i TextEdit::get_line_column_at_pos(const Point2i &p_pos) const { +Point2i TextEdit::get_line_column_at_pos(const Point2i &p_pos, bool p_allow_out_of_bounds) const { float rows = p_pos.y; rows -= style_normal->get_margin(SIDE_TOP); rows /= get_line_height(); @@ -3396,8 +3406,9 @@ Point2i TextEdit::get_line_column_at_pos(const Point2i &p_pos) const { int wrap_index = 0; if (get_line_wrapping_mode() != LineWrappingMode::LINE_WRAPPING_NONE || _is_hiding_enabled()) { - Point2i f_ofs = get_next_visible_line_index_offset_from(first_vis_line, caret.wrap_ofs, rows + (1 * SGN(rows))); + Point2i f_ofs = get_next_visible_line_index_offset_from(first_vis_line, caret.wrap_ofs, rows + (1 * SIGN(rows))); wrap_index = f_ofs.y; + if (rows < 0) { row = first_vis_line - (f_ofs.x - 1); } else { @@ -3409,34 +3420,40 @@ Point2i TextEdit::get_line_column_at_pos(const Point2i &p_pos) const { row = 0; } - int col = 0; - if (row >= text.size()) { row = text.size() - 1; - col = text[row].size(); - } else { - int colx = p_pos.x - (style_normal->get_margin(SIDE_LEFT) + gutters_width + gutter_padding); - colx += caret.x_ofs; - col = _get_char_pos_for_line(colx, row, wrap_index); - if (get_line_wrapping_mode() != LineWrappingMode::LINE_WRAPPING_NONE && wrap_index < get_line_wrap_count(row)) { - // Move back one if we are at the end of the row. - Vector<String> rows2 = get_line_wrapped_text(row); - int row_end_col = 0; - for (int i = 0; i < wrap_index + 1; i++) { - row_end_col += rows2[i].length(); - } - if (col >= row_end_col) { - col -= 1; - } + } + + int visible_lines = get_visible_line_count_in_range(first_vis_line, row); + if (rows > visible_lines) { + if (!p_allow_out_of_bounds) { + return Point2i(-1, -1); } + return Point2i(text[row].size(), row); + } - RID text_rid = text.get_line_data(row)->get_line_rid(wrap_index); - if (is_layout_rtl()) { - colx = TS->shaped_text_get_size(text_rid).x - colx; + int col = 0; + int colx = p_pos.x - (style_normal->get_margin(SIDE_LEFT) + gutters_width + gutter_padding); + colx += caret.x_ofs; + col = _get_char_pos_for_line(colx, row, wrap_index); + if (get_line_wrapping_mode() != LineWrappingMode::LINE_WRAPPING_NONE && wrap_index < get_line_wrap_count(row)) { + // Move back one if we are at the end of the row. + Vector<String> rows2 = get_line_wrapped_text(row); + int row_end_col = 0; + for (int i = 0; i < wrap_index + 1; i++) { + row_end_col += rows2[i].length(); + } + if (col >= row_end_col) { + col -= 1; } - col = TS->shaped_text_hit_test_position(text_rid, colx); } + RID text_rid = text.get_line_data(row)->get_line_rid(wrap_index); + if (is_layout_rtl()) { + colx = TS->shaped_text_get_size(text_rid).x - colx; + } + col = TS->shaped_text_hit_test_position(text_rid, colx); + return Point2i(col, row); } @@ -3471,7 +3488,7 @@ int TextEdit::get_minimap_line_at_pos(const Point2i &p_pos) const { int row = minimap_line + Math::floor(rows); if (get_line_wrapping_mode() != LineWrappingMode::LINE_WRAPPING_NONE || _is_hiding_enabled()) { - int f_ofs = get_next_visible_line_index_offset_from(minimap_line, caret.wrap_ofs, rows + (1 * SGN(rows))).x - 1; + int f_ofs = get_next_visible_line_index_offset_from(minimap_line, caret.wrap_ofs, rows + (1 * SIGN(rows))).x - 1; if (rows < 0) { row = minimap_line - f_ofs; } else { @@ -3697,8 +3714,10 @@ void TextEdit::set_selection_mode(SelectionMode p_mode, int p_line, int p_column if (p_line >= 0) { ERR_FAIL_INDEX(p_line, text.size()); selection.selecting_line = p_line; + selection.selecting_column = CLAMP(selection.selecting_column, 0, text[selection.selecting_line].length()); } if (p_column >= 0) { + ERR_FAIL_INDEX(selection.selecting_line, text.size()); ERR_FAIL_INDEX(p_column, text[selection.selecting_line].length()); selection.selecting_column = p_column; } @@ -4010,15 +4029,7 @@ double TextEdit::get_scroll_pos_for_line(int p_line, int p_wrap_index) const { return p_line; } - // Count the number of visible lines up to this line. - double new_line_scroll_pos = 0.0; - int to = CLAMP(p_line, 0, text.size() - 1); - for (int i = 0; i < to; i++) { - if (!text.is_hidden(i)) { - new_line_scroll_pos++; - new_line_scroll_pos += get_line_wrap_count(i); - } - } + double new_line_scroll_pos = get_visible_line_count_in_range(0, CLAMP(p_line, 0, text.size() - 1)); new_line_scroll_pos += p_wrap_index; return new_line_scroll_pos; } @@ -4075,14 +4086,18 @@ int TextEdit::get_visible_line_count() const { return _get_control_height() / get_line_height(); } -int TextEdit::get_total_visible_line_count() const { +int TextEdit::get_visible_line_count_in_range(int p_from_line, int p_to_line) const { + ERR_FAIL_INDEX_V(p_from_line, text.size(), 0); + ERR_FAIL_INDEX_V(p_to_line, text.size(), 0); + ERR_FAIL_COND_V(p_from_line > p_to_line, 0); + /* Returns the total number of (lines + wrapped - hidden). */ if (!_is_hiding_enabled() && get_line_wrapping_mode() == LineWrappingMode::LINE_WRAPPING_NONE) { - return text.size(); + return p_to_line - p_from_line; } int total_rows = 0; - for (int i = 0; i < text.size(); i++) { + for (int i = p_from_line; i <= p_to_line; i++) { if (!text.is_hidden(i)) { total_rows++; total_rows += get_line_wrap_count(i); @@ -4091,6 +4106,10 @@ int TextEdit::get_total_visible_line_count() const { return total_rows; } +int TextEdit::get_total_visible_line_count() const { + return get_visible_line_count_in_range(0, text.size() - 1); +} + // Auto adjust void TextEdit::adjust_viewport_to_caret() { // Make sure Caret is visible on the screen. @@ -4681,7 +4700,7 @@ void TextEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("get_word_at_pos", "position"), &TextEdit::get_word_at_pos); - ClassDB::bind_method(D_METHOD("get_line_column_at_pos", "position"), &TextEdit::get_line_column_at_pos); + ClassDB::bind_method(D_METHOD("get_line_column_at_pos", "position", "allow_out_of_bounds"), &TextEdit::get_line_column_at_pos, DEFVAL(true)); ClassDB::bind_method(D_METHOD("get_minimap_line_at_pos", "position"), &TextEdit::get_minimap_line_at_pos); ClassDB::bind_method(D_METHOD("is_dragging_cursor"), &TextEdit::is_dragging_cursor); @@ -4805,6 +4824,7 @@ void TextEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("get_last_full_visible_line_wrap_index"), &TextEdit::get_last_full_visible_line_wrap_index); ClassDB::bind_method(D_METHOD("get_visible_line_count"), &TextEdit::get_visible_line_count); + ClassDB::bind_method(D_METHOD("get_visible_line_count_in_range", "from_line", "to_line"), &TextEdit::get_visible_line_count_in_range); ClassDB::bind_method(D_METHOD("get_total_visible_line_count"), &TextEdit::get_total_visible_line_count); // Auto adjust @@ -5249,21 +5269,21 @@ void TextEdit::_generate_context_menu() { // Reorganize context menu. menu->clear(); if (editable) { - menu->add_item(RTR("Cut"), MENU_CUT, is_shortcut_keys_enabled() ? _get_menu_action_accelerator("ui_cut") : 0); + menu->add_item(RTR("Cut"), MENU_CUT, is_shortcut_keys_enabled() ? _get_menu_action_accelerator("ui_cut") : Key::NONE); } - menu->add_item(RTR("Copy"), MENU_COPY, is_shortcut_keys_enabled() ? _get_menu_action_accelerator("ui_copy") : 0); + menu->add_item(RTR("Copy"), MENU_COPY, is_shortcut_keys_enabled() ? _get_menu_action_accelerator("ui_copy") : Key::NONE); if (editable) { - menu->add_item(RTR("Paste"), MENU_PASTE, is_shortcut_keys_enabled() ? _get_menu_action_accelerator("ui_paste") : 0); + menu->add_item(RTR("Paste"), MENU_PASTE, is_shortcut_keys_enabled() ? _get_menu_action_accelerator("ui_paste") : Key::NONE); } menu->add_separator(); if (is_selecting_enabled()) { - menu->add_item(RTR("Select All"), MENU_SELECT_ALL, is_shortcut_keys_enabled() ? _get_menu_action_accelerator("ui_text_select_all") : 0); + menu->add_item(RTR("Select All"), MENU_SELECT_ALL, is_shortcut_keys_enabled() ? _get_menu_action_accelerator("ui_text_select_all") : Key::NONE); } if (editable) { menu->add_item(RTR("Clear"), MENU_CLEAR); menu->add_separator(); - menu->add_item(RTR("Undo"), MENU_UNDO, is_shortcut_keys_enabled() ? _get_menu_action_accelerator("ui_undo") : 0); - menu->add_item(RTR("Redo"), MENU_REDO, is_shortcut_keys_enabled() ? _get_menu_action_accelerator("ui_redo") : 0); + menu->add_item(RTR("Undo"), MENU_UNDO, is_shortcut_keys_enabled() ? _get_menu_action_accelerator("ui_undo") : Key::NONE); + menu->add_item(RTR("Redo"), MENU_REDO, is_shortcut_keys_enabled() ? _get_menu_action_accelerator("ui_redo") : Key::NONE); } menu->add_separator(); menu->add_submenu_item(RTR("Text Writing Direction"), "DirMenu"); @@ -5284,25 +5304,25 @@ void TextEdit::_generate_context_menu() { } } -int TextEdit::_get_menu_action_accelerator(const String &p_action) { +Key TextEdit::_get_menu_action_accelerator(const String &p_action) { const List<Ref<InputEvent>> *events = InputMap::get_singleton()->action_get_events(p_action); if (!events) { - return 0; + return Key::NONE; } // Use first event in the list for the accelerator. const List<Ref<InputEvent>>::Element *first_event = events->front(); if (!first_event) { - return 0; + return Key::NONE; } const Ref<InputEventKey> event = first_event->get(); if (event.is_null()) { - return 0; + return Key::NONE; } // Use physical keycode if non-zero - if (event->get_physical_keycode() != 0) { + if (event->get_physical_keycode() != Key::NONE) { return event->get_physical_keycode_with_modifiers(); } else { return event->get_keycode_with_modifiers(); @@ -5457,10 +5477,10 @@ int TextEdit::_get_column_x_offset_for_line(int p_char, int p_line) const { /* Selection */ void TextEdit::_click_selection_held() { - // Warning: is_mouse_button_pressed(MOUSE_BUTTON_LEFT) returns false for double+ clicks, so this doesn't work for MODE_WORD + // Warning: is_mouse_button_pressed(MouseButton::LEFT) returns false for double+ clicks, so this doesn't work for MODE_WORD // and MODE_LINE. However, moving the mouse triggers _gui_input, which calls these functions too, so that's not a huge problem. // I'm unsure if there's an actual fix that doesn't have a ton of side effects. - if (Input::get_singleton()->is_mouse_button_pressed(MOUSE_BUTTON_LEFT) && selection.selecting_mode != SelectionMode::SELECTION_MODE_NONE) { + if (Input::get_singleton()->is_mouse_button_pressed(MouseButton::LEFT) && selection.selecting_mode != SelectionMode::SELECTION_MODE_NONE) { switch (selection.selecting_mode) { case SelectionMode::SELECTION_MODE_POINTER: { _update_selection_mode_pointer(); @@ -5760,7 +5780,7 @@ double TextEdit::_get_v_scroll_offset() const { } void TextEdit::_scroll_up(real_t p_delta) { - if (scrolling && smooth_scroll_enabled && SGN(target_v_scroll - v_scroll->get_value()) != SGN(-p_delta)) { + if (scrolling && smooth_scroll_enabled && SIGN(target_v_scroll - v_scroll->get_value()) != SIGN(-p_delta)) { scrolling = false; minimap_clicked = false; } @@ -5787,7 +5807,7 @@ void TextEdit::_scroll_up(real_t p_delta) { } void TextEdit::_scroll_down(real_t p_delta) { - if (scrolling && smooth_scroll_enabled && SGN(target_v_scroll - v_scroll->get_value()) != SGN(p_delta)) { + if (scrolling && smooth_scroll_enabled && SIGN(target_v_scroll - v_scroll->get_value()) != SIGN(p_delta)) { scrolling = false; minimap_clicked = false; } diff --git a/scene/gui/text_edit.h b/scene/gui/text_edit.h index 23f80efce0..09315ae395 100644 --- a/scene/gui/text_edit.h +++ b/scene/gui/text_edit.h @@ -280,7 +280,7 @@ private: PopupMenu *menu_ctl = nullptr; void _generate_context_menu(); - int _get_menu_action_accelerator(const String &p_action); + Key _get_menu_action_accelerator(const String &p_action); /* Versioning */ struct TextOperation { @@ -716,7 +716,7 @@ public: String get_word_at_pos(const Vector2 &p_pos) const; - Point2i get_line_column_at_pos(const Point2i &p_pos) const; + Point2i get_line_column_at_pos(const Point2i &p_pos, bool p_allow_out_of_bounds = true) const; int get_minimap_line_at_pos(const Point2i &p_pos) const; bool is_dragging_cursor() const; @@ -822,6 +822,7 @@ public: int get_last_full_visible_line_wrap_index() const; int get_visible_line_count() const; + int get_visible_line_count_in_range(int p_from, int p_to) const; int get_total_visible_line_count() const; // Auto Adjust diff --git a/scene/gui/tree.cpp b/scene/gui/tree.cpp index 1245a37c4d..27e617bdd0 100644 --- a/scene/gui/tree.cpp +++ b/scene/gui/tree.cpp @@ -1955,7 +1955,7 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 if (p_item->cells[i].custom_button) { if (cache.hover_item == p_item && cache.hover_cell == i) { - if (Input::get_singleton()->is_mouse_button_pressed(MOUSE_BUTTON_LEFT)) { + if (Input::get_singleton()->is_mouse_button_pressed(MouseButton::LEFT)) { draw_style_box(cache.custom_button_pressed, ir); } else { draw_style_box(cache.custom_button_hover, ir); @@ -2256,7 +2256,7 @@ Rect2 Tree::search_item_rect(TreeItem *p_from, TreeItem *p_item) { } void Tree::_range_click_timeout() { - if (range_item_last && !range_drag_enabled && Input::get_singleton()->is_mouse_button_pressed(MOUSE_BUTTON_LEFT)) { + if (range_item_last && !range_drag_enabled && Input::get_singleton()->is_mouse_button_pressed(MouseButton::LEFT)) { Point2 pos = get_local_mouse_position() - cache.bg->get_offset(); if (show_column_titles) { pos.y -= _get_title_button_height(); @@ -2284,7 +2284,7 @@ void Tree::_range_click_timeout() { propagate_mouse_activated = false; // done from outside, so signal handler can't clear the tree in the middle of emit (which is a common case) blocked++; - propagate_mouse_event(pos + cache.offset, 0, 0, x_limit + cache.offset.width, false, root, MOUSE_BUTTON_LEFT, mb); + propagate_mouse_event(pos + cache.offset, 0, 0, x_limit + cache.offset.width, false, root, MouseButton::LEFT, mb); blocked--; if (range_click_timer->is_one_shot()) { @@ -2307,7 +2307,7 @@ void Tree::_range_click_timeout() { } } -int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int x_limit, bool p_double_click, TreeItem *p_item, int p_button, const Ref<InputEventWithModifiers> &p_mod) { +int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int x_limit, bool p_double_click, TreeItem *p_item, MouseButton p_button, const Ref<InputEventWithModifiers> &p_mod) { int item_h = compute_item_height(p_item) + cache.vseparation; bool skip = (p_item == root && hide_root); @@ -2427,7 +2427,7 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int col_width -= w + cache.button_margin; } - if (p_button == MOUSE_BUTTON_LEFT || (p_button == MOUSE_BUTTON_RIGHT && allow_rmb_select)) { + if (p_button == MouseButton::LEFT || (p_button == MouseButton::RIGHT && allow_rmb_select)) { /* process selection */ if (p_double_click && (!c.editable || c.mode == TreeItem::CELL_MODE_CUSTOM || c.mode == TreeItem::CELL_MODE_ICON /*|| c.mode==TreeItem::CELL_MODE_CHECK*/)) { //it's confusing for check @@ -2439,10 +2439,10 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int } if (select_mode == SELECT_MULTI && p_mod->is_command_pressed() && c.selectable) { - if (!c.selected || p_button == MOUSE_BUTTON_RIGHT) { + if (!c.selected || p_button == MouseButton::RIGHT) { p_item->select(col); emit_signal(SNAME("multi_selected"), p_item, col, true); - if (p_button == MOUSE_BUTTON_RIGHT) { + if (p_button == MouseButton::RIGHT) { emit_signal(SNAME("item_rmb_selected"), get_local_mouse_position()); } @@ -2459,21 +2459,21 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int bool inrange = false; select_single_item(p_item, root, col, selected_item, &inrange); - if (p_button == MOUSE_BUTTON_RIGHT) { + if (p_button == MouseButton::RIGHT) { emit_signal(SNAME("item_rmb_selected"), get_local_mouse_position()); } } else { int icount = _count_selected_items(root); - if (select_mode == SELECT_MULTI && icount > 1 && p_button != MOUSE_BUTTON_RIGHT) { + if (select_mode == SELECT_MULTI && icount > 1 && p_button != MouseButton::RIGHT) { single_select_defer = p_item; single_select_defer_column = col; } else { - if (p_button != MOUSE_BUTTON_RIGHT || !c.selected) { + if (p_button != MouseButton::RIGHT || !c.selected) { select_single_item(p_item, root, col); } - if (p_button == MOUSE_BUTTON_RIGHT) { + if (p_button == MouseButton::RIGHT) { emit_signal(SNAME("item_rmb_selected"), get_local_mouse_position()); } } @@ -2543,7 +2543,7 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int /* touching the combo */ bool up = p_pos.y < (item_h / 2); - if (p_button == MOUSE_BUTTON_LEFT) { + if (p_button == MouseButton::LEFT) { if (range_click_timer->get_time_left() == 0) { range_item_last = p_item; range_up_last = up; @@ -2560,13 +2560,13 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int item_edited(col, p_item); - } else if (p_button == MOUSE_BUTTON_RIGHT) { + } else if (p_button == MouseButton::RIGHT) { p_item->set_range(col, (up ? c.max : c.min)); item_edited(col, p_item); - } else if (p_button == MOUSE_BUTTON_WHEEL_UP) { + } else if (p_button == MouseButton::WHEEL_UP) { p_item->set_range(col, c.val + c.step); item_edited(col, p_item); - } else if (p_button == MOUSE_BUTTON_WHEEL_DOWN) { + } else if (p_button == MouseButton::WHEEL_DOWN) { p_item->set_range(col, c.val - c.step); item_edited(col, p_item); } @@ -2599,14 +2599,14 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int } if (!p_item->cells[col].custom_button || !on_arrow) { - item_edited(col, p_item, p_button == MOUSE_BUTTON_LEFT); + item_edited(col, p_item, p_button == MouseButton::LEFT); } click_handled = true; return -1; } break; }; - if (!bring_up_editor || p_button != MOUSE_BUTTON_LEFT) { + if (!bring_up_editor || p_button != MouseButton::LEFT) { return -1; } @@ -2646,7 +2646,7 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int item_h += child_h; } } - if (p_item == root && p_button == MOUSE_BUTTON_RIGHT) { + if (p_item == root && p_button == MouseButton::RIGHT) { emit_signal(SNAME("empty_rmb"), get_local_mouse_position()); } } @@ -2655,9 +2655,9 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int } void Tree::_text_editor_modal_close() { - if (Input::get_singleton()->is_key_pressed(KEY_ESCAPE) || - Input::get_singleton()->is_key_pressed(KEY_KP_ENTER) || - Input::get_singleton()->is_key_pressed(KEY_ENTER)) { + if (Input::get_singleton()->is_key_pressed(Key::ESCAPE) || + Input::get_singleton()->is_key_pressed(Key::KP_ENTER) || + Input::get_singleton()->is_key_pressed(Key::ENTER)) { return; } @@ -3048,7 +3048,7 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) { return; } else { - if (k->get_keycode() != KEY_SHIFT) { + if (k->get_keycode() != Key::SHIFT) { last_keypress = 0; } } @@ -3164,7 +3164,7 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) { } else { const TreeItem::Cell &c = popup_edited_item->cells[popup_edited_item_col]; float diff_y = -mm->get_relative().y; - diff_y = Math::pow(ABS(diff_y), 1.8f) * SGN(diff_y); + diff_y = Math::pow(ABS(diff_y), 1.8f) * SIGN(diff_y); diff_y *= 0.1; range_drag_base = CLAMP(range_drag_base + c.step * diff_y, c.min, c.max); popup_edited_item->set_range(popup_edited_item_col, range_drag_base); @@ -3189,7 +3189,7 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) { bool rtl = is_layout_rtl(); if (!b->is_pressed()) { - if (b->get_button_index() == MOUSE_BUTTON_LEFT) { + if (b->get_button_index() == MouseButton::LEFT) { Point2 pos = b->get_position(); if (rtl) { pos.x = get_size().width - pos.x; @@ -3270,8 +3270,8 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) { } switch (b->get_button_index()) { - case MOUSE_BUTTON_RIGHT: - case MOUSE_BUTTON_LEFT: { + case MouseButton::RIGHT: + case MouseButton::LEFT: { Ref<StyleBox> bg = cache.bg; Point2 pos = b->get_position(); @@ -3284,7 +3284,7 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) { pos.y -= _get_title_button_height(); if (pos.y < 0) { - if (b->get_button_index() == MOUSE_BUTTON_LEFT) { + if (b->get_button_index() == MouseButton::LEFT) { pos.x += cache.offset.x; int len = 0; for (int i = 0; i < columns.size(); i++) { @@ -3302,7 +3302,7 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) { } } if (!root || (!root->get_first_child() && hide_root)) { - if (b->get_button_index() == MOUSE_BUTTON_RIGHT && allow_rmb_select) { + if (b->get_button_index() == MouseButton::RIGHT && allow_rmb_select) { emit_signal(SNAME("empty_tree_rmb_selected"), get_local_mouse_position()); } break; @@ -3329,7 +3329,7 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) { } } - if (b->get_button_index() == MOUSE_BUTTON_RIGHT) { + if (b->get_button_index() == MouseButton::RIGHT) { break; } @@ -3352,7 +3352,7 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) { set_physics_process_internal(true); } - if (b->get_button_index() == MOUSE_BUTTON_LEFT) { + if (b->get_button_index() == MouseButton::LEFT) { if (get_item_at_position(b->get_position()) == nullptr && !b->is_shift_pressed() && !b->is_ctrl_pressed() && !b->is_command_pressed()) { emit_signal(SNAME("nothing_selected")); } @@ -3365,7 +3365,7 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) { } } break; - case MOUSE_BUTTON_WHEEL_UP: { + case MouseButton::WHEEL_UP: { double prev_value = v_scroll->get_value(); v_scroll->set_value(v_scroll->get_value() - v_scroll->get_page() * b->get_factor() / 8); if (v_scroll->get_value() != prev_value) { @@ -3373,7 +3373,7 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) { } } break; - case MOUSE_BUTTON_WHEEL_DOWN: { + case MouseButton::WHEEL_DOWN: { double prev_value = v_scroll->get_value(); v_scroll->set_value(v_scroll->get_value() + v_scroll->get_page() * b->get_factor() / 8); if (v_scroll->get_value() != prev_value) { diff --git a/scene/gui/tree.h b/scene/gui/tree.h index 6ca9458e9b..2e4e1bd364 100644 --- a/scene/gui/tree.h +++ b/scene/gui/tree.h @@ -462,7 +462,7 @@ private: void draw_item_rect(TreeItem::Cell &p_cell, const Rect2i &p_rect, const Color &p_color, const Color &p_icon_color, int p_ol_size, const Color &p_ol_color); int draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 &p_draw_size, TreeItem *p_item); void select_single_item(TreeItem *p_selected, TreeItem *p_current, int p_col, TreeItem *p_prev = nullptr, bool *r_in_range = nullptr, bool p_force_deselect = false); - int propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int x_limit, bool p_double_click, TreeItem *p_item, int p_button, const Ref<InputEventWithModifiers> &p_mod); + int propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int x_limit, bool p_double_click, TreeItem *p_item, MouseButton p_button, const Ref<InputEventWithModifiers> &p_mod); void _text_editor_submit(String p_text); void _text_editor_modal_close(); void value_editor_changed(double p_value); diff --git a/scene/main/canvas_item.cpp b/scene/main/canvas_item.cpp index 5065684839..22e3c3bf24 100644 --- a/scene/main/canvas_item.cpp +++ b/scene/main/canvas_item.cpp @@ -274,6 +274,7 @@ void CanvasItem::_exit_canvas() { void CanvasItem::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_TREE: { + ERR_FAIL_COND(!is_inside_tree()); _update_texture_filter_changed(false); _update_texture_repeat_changed(false); diff --git a/scene/main/node.cpp b/scene/main/node.cpp index cb5f502b24..44420fcc31 100644 --- a/scene/main/node.cpp +++ b/scene/main/node.cpp @@ -927,7 +927,6 @@ void Node::_validate_child_name(Node *p_child, bool p_force_human_readable) { if (p_force_human_readable) { //this approach to autoset node names is human readable but very slow - //it's turned on while running in the editor StringName name = p_child->data.name; _generate_serial_child_name(p_child, name); @@ -1894,6 +1893,56 @@ Node *Node::get_deepest_editable_node(Node *p_start_node) const { return node; } +#ifdef TOOLS_ENABLED +void Node::set_property_pinned(const String &p_property, bool p_pinned) { + bool current_pinned = false; + bool has_pinned = has_meta("_edit_pinned_properties_"); + Array pinned; + String psa = get_property_store_alias(p_property); + if (has_pinned) { + pinned = get_meta("_edit_pinned_properties_"); + current_pinned = pinned.has(psa); + } + + if (current_pinned != p_pinned) { + if (p_pinned) { + pinned.append(psa); + if (!has_pinned) { + set_meta("_edit_pinned_properties_", pinned); + } + } else { + pinned.erase(psa); + if (pinned.is_empty()) { + remove_meta("_edit_pinned_properties_"); + } + } + } +} + +bool Node::is_property_pinned(const StringName &p_property) const { + if (!has_meta("_edit_pinned_properties_")) { + return false; + } + Array pinned = get_meta("_edit_pinned_properties_"); + String psa = get_property_store_alias(p_property); + return pinned.has(psa); +} + +StringName Node::get_property_store_alias(const StringName &p_property) const { + return p_property; +} +#endif + +void Node::get_storable_properties(Set<StringName> &r_storable_properties) const { + List<PropertyInfo> pi; + get_property_list(&pi); + for (List<PropertyInfo>::Element *E = pi.front(); E; E = E->next()) { + if ((E->get().usage & PROPERTY_USAGE_STORAGE)) { + r_storable_properties.insert(E->get().name); + } + } +} + String Node::to_string() { if (get_script_instance()) { bool valid; @@ -2745,6 +2794,10 @@ void Node::_bind_methods() { ClassDB::bind_method(D_METHOD("_set_import_path", "import_path"), &Node::set_import_path); ClassDB::bind_method(D_METHOD("_get_import_path"), &Node::get_import_path); +#ifdef TOOLS_ENABLED + ClassDB::bind_method(D_METHOD("_set_property_pinned", "property", "pinned"), &Node::set_property_pinned); +#endif + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "_import_path", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_import_path", "_get_import_path"); { @@ -2836,7 +2889,7 @@ void Node::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::INT, "process_priority"), "set_process_priority", "get_process_priority"); ADD_GROUP("Editor Description", "editor_"); - ADD_PROPERTY(PropertyInfo(Variant::STRING, "editor_description", PROPERTY_HINT_MULTILINE_TEXT, "", PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_INTERNAL), "set_editor_description", "get_editor_description"); + ADD_PROPERTY(PropertyInfo(Variant::STRING, "editor_description", PROPERTY_HINT_MULTILINE_TEXT), "set_editor_description", "get_editor_description"); GDVIRTUAL_BIND(_process, "delta"); GDVIRTUAL_BIND(_physics_process, "delta"); diff --git a/scene/main/node.h b/scene/main/node.h index c308ec4fac..2dd32a2e1d 100644 --- a/scene/main/node.h +++ b/scene/main/node.h @@ -363,6 +363,13 @@ public: bool is_editable_instance(const Node *p_node) const; Node *get_deepest_editable_node(Node *p_start_node) const; +#ifdef TOOLS_ENABLED + void set_property_pinned(const String &p_property, bool p_pinned); + bool is_property_pinned(const StringName &p_property) const; + virtual StringName get_property_store_alias(const StringName &p_property) const; +#endif + void get_storable_properties(Set<StringName> &r_storable_properties) const; + virtual String to_string() override; /* NOTIFICATIONS */ diff --git a/scene/main/viewport.cpp b/scene/main/viewport.cpp index cdf1f495e4..1ecc3c762a 100644 --- a/scene/main/viewport.cpp +++ b/scene/main/viewport.cpp @@ -584,9 +584,9 @@ void Viewport::_process_picking() { physics_last_mouse_state.meta = mb->is_meta_pressed(); if (mb->is_pressed()) { - physics_last_mouse_state.mouse_mask |= (MouseButton)(1 << (mb->get_button_index() - 1)); + physics_last_mouse_state.mouse_mask |= mouse_button_to_mask(mb->get_button_index()); } else { - physics_last_mouse_state.mouse_mask &= (MouseButton) ~(1 << (mb->get_button_index() - 1)); + physics_last_mouse_state.mouse_mask &= ~mouse_button_to_mask(mb->get_button_index()); // If touch mouse raised, assume we don't know last mouse pos until new events come if (mb->get_device() == InputEvent::DEVICE_ID_TOUCH_MOUSE) { @@ -638,7 +638,13 @@ void Viewport::_process_picking() { Vector2 point = canvas_transform.affine_inverse().xform(pos); - int rc = ss2d->intersect_point_on_canvas(point, canvas_layer_id, res, 64, Set<RID>(), 0xFFFFFFFF, true, true, true); + PhysicsDirectSpaceState2D::PointParameters point_params; + point_params.position = point; + point_params.canvas_instance_id = canvas_layer_id; + point_params.collide_with_areas = true; + point_params.pick_point = true; + + int rc = ss2d->intersect_point(point_params, res, 64); for (int i = 0; i < rc; i++) { if (res[i].collider_id.is_valid() && res[i].collider) { CollisionObject2D *co = Object::cast_to<CollisionObject2D>(res[i].collider); @@ -690,7 +696,7 @@ void Viewport::_process_picking() { if (co && camera_3d) { _collision_object_3d_input_event(co, camera_3d, ev, Vector3(), Vector3(), 0); captured = true; - if (mb.is_valid() && mb->get_button_index() == 1 && !mb->is_pressed()) { + if (mb.is_valid() && mb->get_button_index() == MouseButton::LEFT && !mb->is_pressed()) { physics_object_capture = ObjectID(); } @@ -706,7 +712,7 @@ void Viewport::_process_picking() { if (ObjectDB::get_instance(last_id) && last_object) { // Good, exists. _collision_object_3d_input_event(last_object, camera_3d, ev, result.position, result.normal, result.shape); - if (last_object->get_capture_input_on_drag() && mb.is_valid() && mb->get_button_index() == 1 && mb->is_pressed()) { + if (last_object->get_capture_input_on_drag() && mb.is_valid() && mb->get_button_index() == MouseButton::LEFT && mb->is_pressed()) { physics_object_capture = last_id; } } @@ -719,7 +725,13 @@ void Viewport::_process_picking() { PhysicsDirectSpaceState3D *space = PhysicsServer3D::get_singleton()->space_get_direct_state(find_world_3d()->get_space()); if (space) { - bool col = space->intersect_ray(from, from + dir * far, result, Set<RID>(), 0xFFFFFFFF, true, true, true); + PhysicsDirectSpaceState3D::RayParameters ray_params; + ray_params.from = from; + ray_params.to = from + dir * far; + ray_params.collide_with_areas = true; + ray_params.pick_ray = true; + + bool col = space->intersect_ray(ray_params, result); ObjectID new_collider; if (col) { CollisionObject3D *co = Object::cast_to<CollisionObject3D>(result.collider); @@ -728,7 +740,7 @@ void Viewport::_process_picking() { last_object = co; last_id = result.collider_id; new_collider = last_id; - if (co->get_capture_input_on_drag() && mb.is_valid() && mb->get_button_index() == 1 && mb->is_pressed()) { + if (co->get_capture_input_on_drag() && mb.is_valid() && mb->get_button_index() == MouseButton::LEFT && mb->is_pressed()) { physics_object_capture = last_id; } } @@ -1247,10 +1259,10 @@ void Viewport::_gui_call_input(Control *p_control, const Ref<InputEvent> &p_inpu Ref<InputEventMouseButton> mb = p_input; bool cant_stop_me_now = (mb.is_valid() && - (mb->get_button_index() == MOUSE_BUTTON_WHEEL_DOWN || - mb->get_button_index() == MOUSE_BUTTON_WHEEL_UP || - mb->get_button_index() == MOUSE_BUTTON_WHEEL_LEFT || - mb->get_button_index() == MOUSE_BUTTON_WHEEL_RIGHT)); + (mb->get_button_index() == MouseButton::WHEEL_DOWN || + mb->get_button_index() == MouseButton::WHEEL_UP || + mb->get_button_index() == MouseButton::WHEEL_LEFT || + mb->get_button_index() == MouseButton::WHEEL_RIGHT)); Ref<InputEventPanGesture> pn = p_input; cant_stop_me_now = pn.is_valid() || cant_stop_me_now; @@ -1433,21 +1445,21 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { gui.last_mouse_pos = mpos; if (mb->is_pressed()) { Size2 pos = mpos; - if (gui.mouse_focus_mask) { + if (gui.mouse_focus_mask != MouseButton::NONE) { // Do not steal mouse focus and stuff while a focus mask exists. - gui.mouse_focus_mask |= 1 << (mb->get_button_index() - 1); // Add the button to the mask. + gui.mouse_focus_mask |= mouse_button_to_mask(mb->get_button_index()); } else { gui.mouse_focus = gui_find_control(pos); gui.last_mouse_focus = gui.mouse_focus; if (!gui.mouse_focus) { - gui.mouse_focus_mask = 0; + gui.mouse_focus_mask = MouseButton::NONE; return; } - gui.mouse_focus_mask = 1 << (mb->get_button_index() - 1); + gui.mouse_focus_mask = mouse_button_to_mask(mb->get_button_index()); - if (mb->get_button_index() == MOUSE_BUTTON_LEFT) { + if (mb->get_button_index() == MouseButton::LEFT) { gui.drag_accum = Vector2(); gui.drag_attempted = false; } @@ -1470,7 +1482,7 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { } #endif - if (mb->get_button_index() == MOUSE_BUTTON_LEFT) { // Assign focus. + if (mb->get_button_index() == MouseButton::LEFT) { // Assign focus. CanvasItem *ci = gui.mouse_focus; while (ci) { Control *control = Object::cast_to<Control>(ci); @@ -1501,7 +1513,7 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { set_input_as_handled(); - if (gui.drag_data.get_type() != Variant::NIL && mb->get_button_index() == MOUSE_BUTTON_LEFT) { + if (gui.drag_data.get_type() != Variant::NIL && mb->get_button_index() == MouseButton::LEFT) { // Alternate drop use (when using force_drag(), as proposed by #5342). if (gui.mouse_focus) { _gui_drop(gui.mouse_focus, pos, false); @@ -1521,7 +1533,7 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { _gui_cancel_tooltip(); } else { - if (gui.drag_data.get_type() != Variant::NIL && mb->get_button_index() == MOUSE_BUTTON_LEFT) { + if (gui.drag_data.get_type() != Variant::NIL && mb->get_button_index() == MouseButton::LEFT) { if (gui.drag_mouse_over) { _gui_drop(gui.drag_mouse_over, gui.drag_mouse_over_pos, false); } @@ -1539,7 +1551,7 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { // Change mouse accordingly. } - gui.mouse_focus_mask &= ~(1 << (mb->get_button_index() - 1)); // Remove from mask. + gui.mouse_focus_mask &= ~mouse_button_to_mask(mb->get_button_index()); // Remove from mask. if (!gui.mouse_focus) { // Release event is only sent if a mouse focus (previously pressed button) exists. @@ -1558,7 +1570,7 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { // Disable mouse focus if needed before calling input, // this makes popups on mouse press event work better, // as the release will never be received otherwise. - if (gui.mouse_focus_mask == 0) { + if (gui.mouse_focus_mask == MouseButton::NONE) { gui.mouse_focus = nullptr; gui.forced_mouse_focus = false; } @@ -1578,7 +1590,7 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { over = gui_find_control(mpos); } - if (gui.mouse_focus_mask == 0 && over != gui.mouse_over) { + if (gui.mouse_focus_mask == MouseButton::NONE && over != gui.mouse_over) { if (gui.mouse_over) { _gui_call_notification(gui.mouse_over, Control::NOTIFICATION_MOUSE_EXIT); } @@ -1606,7 +1618,7 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { Control *over = nullptr; // Drag & drop. - if (!gui.drag_attempted && gui.mouse_focus && mm->get_button_mask() & MOUSE_BUTTON_MASK_LEFT) { + if (!gui.drag_attempted && gui.mouse_focus && (mm->get_button_mask() & MouseButton::MASK_LEFT) != MouseButton::NONE) { gui.drag_accum += mm->get_relative(); float len = gui.drag_accum.length(); if (len > 10) { @@ -1620,7 +1632,7 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { if (gui.drag_data.get_type() != Variant::NIL) { gui.mouse_focus = nullptr; gui.forced_mouse_focus = false; - gui.mouse_focus_mask = 0; + gui.mouse_focus_mask = MouseButton::NONE; break; } else { Control *drag_preview = _gui_get_drag_preview(); @@ -1689,7 +1701,7 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { mm->set_speed(speed); mm->set_relative(rel); - if (mm->get_button_mask() == 0) { + if (mm->get_button_mask() == MouseButton::NONE) { // Nothing pressed. bool can_tooltip = true; @@ -1742,7 +1754,7 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { Control *c = over; Vector2 cpos = pos; while (c) { - if (gui.mouse_focus_mask != 0 || c->has_point(cpos)) { + if (gui.mouse_focus_mask != MouseButton::NONE || c->has_point(cpos)) { cursor_shape = c->get_cursor_shape(cpos); } else { cursor_shape = Control::CURSOR_ARROW; @@ -1855,7 +1867,7 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { Transform2D localizer = gui.drag_mouse_over->get_global_transform_with_canvas().affine_inverse(); gui.drag_mouse_over_pos = localizer.xform(viewport_pos); - if (mm->get_button_mask() & MOUSE_BUTTON_MASK_LEFT) { + if ((mm->get_button_mask() & MouseButton::MASK_LEFT) != MouseButton::NONE) { bool can_drop = _gui_drop(gui.drag_mouse_over, gui.drag_mouse_over_pos, true); if (!can_drop) { @@ -2096,7 +2108,7 @@ void Viewport::_gui_remove_control(Control *p_control) { if (gui.mouse_focus == p_control) { gui.mouse_focus = nullptr; gui.forced_mouse_focus = false; - gui.mouse_focus_mask = 0; + gui.mouse_focus_mask = MouseButton::NONE; } if (gui.last_mouse_focus == p_control) { gui.last_mouse_focus = nullptr; @@ -2166,13 +2178,13 @@ void Viewport::_gui_accept_event() { void Viewport::_drop_mouse_focus() { Control *c = gui.mouse_focus; - int mask = gui.mouse_focus_mask; + MouseButton mask = gui.mouse_focus_mask; gui.mouse_focus = nullptr; gui.forced_mouse_focus = false; - gui.mouse_focus_mask = 0; + gui.mouse_focus_mask = MouseButton::NONE; for (int i = 0; i < 3; i++) { - if (mask & (1 << i)) { + if ((int)mask & (1 << i)) { Ref<InputEventMouseButton> mb; mb.instantiate(); mb->set_position(c->get_local_mouse_position()); @@ -2281,11 +2293,11 @@ void Viewport::_post_gui_grab_click_focus() { return; } - int mask = gui.mouse_focus_mask; + MouseButton mask = gui.mouse_focus_mask; Point2 click = gui.mouse_focus->get_global_transform_with_canvas().affine_inverse().xform(gui.last_mouse_pos); for (int i = 0; i < 3; i++) { - if (mask & (1 << i)) { + if ((int)mask & (1 << i)) { Ref<InputEventMouseButton> mb; mb.instantiate(); @@ -2303,7 +2315,7 @@ void Viewport::_post_gui_grab_click_focus() { click = gui.mouse_focus->get_global_transform_with_canvas().affine_inverse().xform(gui.last_mouse_pos); for (int i = 0; i < 3; i++) { - if (mask & (1 << i)) { + if ((int)mask & (1 << i)) { Ref<InputEventMouseButton> mb; mb.instantiate(); @@ -2400,7 +2412,7 @@ bool Viewport::_sub_windows_forward_input(const Ref<InputEvent> &p_event) { ERR_FAIL_COND_V(gui.subwindow_focused == nullptr, false); Ref<InputEventMouseButton> mb = p_event; - if (mb.is_valid() && !mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_LEFT) { + if (mb.is_valid() && !mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT) { if (gui.subwindow_drag == SUB_WINDOW_DRAG_CLOSE) { if (gui.subwindow_drag_close_rect.has_point(mb->get_position())) { // Close window. @@ -2525,7 +2537,7 @@ bool Viewport::_sub_windows_forward_input(const Ref<InputEvent> &p_event) { Ref<InputEventMouseButton> mb = p_event; // If the event is a mouse button, we need to check whether another window was clicked. - if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_LEFT) { + if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT) { bool click_on_window = false; for (int i = gui.sub_windows.size() - 1; i >= 0; i--) { SubWindow &sw = gui.sub_windows.write[i]; @@ -3029,7 +3041,7 @@ void Viewport::pass_mouse_focus_to(Viewport *p_viewport, Control *p_control) { gui.mouse_focus = nullptr; gui.forced_mouse_focus = false; - gui.mouse_focus_mask = 0; + gui.mouse_focus_mask = MouseButton::NONE; } } diff --git a/scene/main/viewport.h b/scene/main/viewport.h index 1f19ff04c9..5320aea02a 100644 --- a/scene/main/viewport.h +++ b/scene/main/viewport.h @@ -244,7 +244,7 @@ private: bool control = false; bool shift = false; bool meta = false; - int mouse_mask = 0; + MouseButton mouse_mask = MouseButton::NONE; } physics_last_mouse_state; @@ -327,7 +327,7 @@ private: Control *mouse_focus = nullptr; Control *last_mouse_focus = nullptr; Control *mouse_click_grabber = nullptr; - int mouse_focus_mask = 0; + MouseButton mouse_focus_mask = MouseButton::NONE; Control *key_focus = nullptr; Control *mouse_over = nullptr; Control *drag_mouse_over = nullptr; diff --git a/scene/main/window.cpp b/scene/main/window.cpp index a0f62c853f..5da5a183f7 100644 --- a/scene/main/window.cpp +++ b/scene/main/window.cpp @@ -895,7 +895,7 @@ void Window::_window_input(const Ref<InputEvent> &p_ev) { if (EngineDebugger::is_active()) { //quit from game window using F8 Ref<InputEventKey> k = p_ev; - if (k.is_valid() && k->is_pressed() && !k->is_echo() && k->get_keycode() == KEY_F8) { + if (k.is_valid() && k->is_pressed() && !k->is_echo() && k->get_keycode() == Key::F8) { EngineDebugger::get_singleton()->send_message("request_quit", Array()); } } diff --git a/scene/property_utils.cpp b/scene/property_utils.cpp new file mode 100644 index 0000000000..7df601492b --- /dev/null +++ b/scene/property_utils.cpp @@ -0,0 +1,188 @@ +/*************************************************************************/ +/* property_utils.cpp */ +/*************************************************************************/ +/* 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. */ +/*************************************************************************/ + +#include "property_utils.h" + +#include "core/config/engine.h" +#include "core/templates/local_vector.h" +#include "scene/resources/packed_scene.h" + +#ifdef TOOLS_ENABLED +#include "editor/editor_node.h" +#endif // TOOLS_ENABLED + +bool PropertyUtils::is_property_value_different(const Variant &p_a, const Variant &p_b) { + if (p_a.get_type() == Variant::FLOAT && p_b.get_type() == Variant::FLOAT) { + //this must be done because, as some scenes save as text, there might be a tiny difference in floats due to numerical error + return !Math::is_equal_approx((float)p_a, (float)p_b); + } else { + // For our purposes, treating null object as NIL is the right thing to do + const Variant &a = p_a.get_type() == Variant::OBJECT && (Object *)p_a == nullptr ? Variant() : p_a; + const Variant &b = p_b.get_type() == Variant::OBJECT && (Object *)p_b == nullptr ? Variant() : p_b; + return a != b; + } +} + +Variant PropertyUtils::get_property_default_value(const Object *p_object, const StringName &p_property, const Vector<SceneState::PackState> *p_states_stack_cache, bool p_update_exports, const Node *p_owner, bool *r_is_class_default) { + // This function obeys the way property values are set when an object is instantiated, + // which is the following (the latter wins): + // 1. Default value from builtin class + // 2. Default value from script exported variable (from the topmost script) + // 3. Value overrides from the instantiation/inheritance stack + + if (r_is_class_default) { + *r_is_class_default = false; + } + + Ref<Script> topmost_script; + + if (const Node *node = Object::cast_to<Node>(p_object)) { + // Check inheritance/instantiation ancestors + const Vector<SceneState::PackState> &states_stack = p_states_stack_cache ? *p_states_stack_cache : PropertyUtils::get_node_states_stack(node, p_owner); + for (int i = 0; i < states_stack.size(); ++i) { + const SceneState::PackState &ia = states_stack[i]; + bool found = false; + Variant value_in_ancestor = ia.state->get_property_value(ia.node, p_property, found); + if (found) { + return value_in_ancestor; + } + // Save script for later + bool has_script = false; + Variant script = ia.state->get_property_value(ia.node, SNAME("script"), has_script); + if (has_script) { + Ref<Script> scr = script; + if (scr.is_valid()) { + topmost_script = scr; + } + } + } + } + + // Let's see what default is set by the topmost script having a default, if any + if (topmost_script.is_null()) { + topmost_script = p_object->get_script(); + } + if (topmost_script.is_valid()) { + // Should be called in the editor only and not at runtime, + // otherwise it can cause problems because of missing instance state support + if (p_update_exports && Engine::get_singleton()->is_editor_hint()) { + topmost_script->update_exports(); + } + Variant default_value; + if (topmost_script->get_property_default_value(p_property, default_value)) { + return default_value; + } + } + + // Fall back to the default from the native class + if (r_is_class_default) { + *r_is_class_default = true; + } + return ClassDB::class_get_default_property_value(p_object->get_class_name(), p_property); +} + +// Like SceneState::PackState, but using a raw pointer to avoid the cost of +// updating the reference count during the internal work of the functions below +namespace { +struct _FastPackState { + SceneState *state = nullptr; + int node = -1; +}; +} // namespace + +static bool _collect_inheritance_chain(const Ref<SceneState> &p_state, const NodePath &p_path, LocalVector<_FastPackState> &r_states_stack) { + bool found = false; + + LocalVector<_FastPackState> inheritance_states; + + Ref<SceneState> state = p_state; + while (state.is_valid()) { + int node = state->find_node_by_path(p_path); + if (node >= 0) { + // This one has state for this node + inheritance_states.push_back({ state.ptr(), node }); + found = true; + } + state = state->get_base_scene_state(); + } + + for (int i = inheritance_states.size() - 1; i >= 0; --i) { + r_states_stack.push_back(inheritance_states[i]); + } + + return found; +} + +Vector<SceneState::PackState> PropertyUtils::get_node_states_stack(const Node *p_node, const Node *p_owner, bool *r_instantiated_by_owner) { + if (r_instantiated_by_owner) { + *r_instantiated_by_owner = true; + } + + LocalVector<_FastPackState> states_stack; + { + const Node *owner = p_owner; +#ifdef TOOLS_ENABLED + if (!p_owner && Engine::get_singleton()->is_editor_hint()) { + owner = EditorNode::get_singleton()->get_edited_scene(); + } +#endif + + const Node *n = p_node; + while (n) { + if (n == owner) { + const Ref<SceneState> &state = n->get_scene_inherited_state(); + if (_collect_inheritance_chain(state, n->get_path_to(p_node), states_stack)) { + if (r_instantiated_by_owner) { + *r_instantiated_by_owner = false; + } + } + break; + } else if (n->get_scene_file_path() != String()) { + const Ref<SceneState> &state = n->get_scene_instance_state(); + _collect_inheritance_chain(state, n->get_path_to(p_node), states_stack); + } + n = n->get_owner(); + } + } + + // Convert to the proper type for returning, inverting the vector on the go + // (it was more convenient to fill the vector in reverse order) + Vector<SceneState::PackState> states_stack_ret; + { + states_stack_ret.resize(states_stack.size()); + _FastPackState *ps = states_stack.ptr(); + for (int i = states_stack.size() - 1; i >= 0; --i) { + states_stack_ret.write[i].state.reference_ptr(ps->state); + states_stack_ret.write[i].node = ps->node; + ++ps; + } + } + return states_stack_ret; +} diff --git a/scene/property_utils.h b/scene/property_utils.h new file mode 100644 index 0000000000..fde9163548 --- /dev/null +++ b/scene/property_utils.h @@ -0,0 +1,51 @@ +/*************************************************************************/ +/* property_utils.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. */ +/*************************************************************************/ + +#ifndef PROPERTY_UTILS_H +#define PROPERTY_UTILS_H + +#include "scene/main/node.h" +#include "scene/resources/packed_scene.h" + +class PropertyUtils { +public: + static bool is_property_value_different(const Variant &p_a, const Variant &p_b); + // Gets the most pure default value, the one that would be set when the node has just been instantiated + static Variant get_property_default_value(const Object *p_object, const StringName &p_property, const Vector<SceneState::PackState> *p_states_stack_cache = nullptr, bool p_update_exports = false, const Node *p_owner = nullptr, bool *r_is_class_default = nullptr); + + // Gets the instance/inheritance states of this node, in order of precedence, + // that is, from the topmost (the most able to override values) to the lowermost + // (Note that in nested instancing the one with the greatest precedence is the furthest + // in the tree, since every owner found while traversing towards the root gets a chance + // to override property values.) + static Vector<SceneState::PackState> get_node_states_stack(const Node *p_node, const Node *p_owner = nullptr, bool *r_instantiated_by_owner = nullptr); +}; + +#endif // PROPERTY_UTILS_H diff --git a/scene/register_scene_types.cpp b/scene/register_scene_types.cpp index d44cd7ca63..056ace5e4e 100644 --- a/scene/register_scene_types.cpp +++ b/scene/register_scene_types.cpp @@ -63,6 +63,7 @@ #include "scene/2d/position_2d.h" #include "scene/2d/ray_cast_2d.h" #include "scene/2d/remote_transform_2d.h" +#include "scene/2d/shape_cast_2d.h" #include "scene/2d/skeleton_2d.h" #include "scene/2d/sprite_2d.h" #include "scene/2d/tile_map.h" @@ -628,6 +629,7 @@ void register_scene_types() { GDREGISTER_CLASS(VisualShaderNodeParticleSphereEmitter); GDREGISTER_CLASS(VisualShaderNodeParticleBoxEmitter); GDREGISTER_CLASS(VisualShaderNodeParticleRingEmitter); + GDREGISTER_CLASS(VisualShaderNodeParticleMeshEmitter); GDREGISTER_CLASS(VisualShaderNodeParticleMultiplyByAxisAngle); GDREGISTER_CLASS(VisualShaderNodeParticleConeVelocity); GDREGISTER_CLASS(VisualShaderNodeParticleRandomness); @@ -665,6 +667,7 @@ void register_scene_types() { GDREGISTER_CLASS(CollisionShape2D); GDREGISTER_CLASS(CollisionPolygon2D); GDREGISTER_CLASS(RayCast2D); + GDREGISTER_CLASS(ShapeCast2D); GDREGISTER_CLASS(VisibleOnScreenNotifier2D); GDREGISTER_CLASS(VisibleOnScreenEnabler2D); GDREGISTER_CLASS(Polygon2D); @@ -791,7 +794,7 @@ void register_scene_types() { GDREGISTER_CLASS(MeshTexture); GDREGISTER_CLASS(CurveTexture); GDREGISTER_CLASS(CurveXYZTexture); - GDREGISTER_CLASS(GradientTexture); + GDREGISTER_CLASS(GradientTexture1D); GDREGISTER_CLASS(GradientTexture2D); GDREGISTER_CLASS(ProxyTexture); GDREGISTER_CLASS(AnimatedTexture); @@ -922,6 +925,7 @@ void register_scene_types() { ClassDB::add_compatibility_class("EditorSpatialGizmo", "EditorNode3DGizmo"); ClassDB::add_compatibility_class("EditorSpatialGizmoPlugin", "EditorNode3DGizmoPlugin"); ClassDB::add_compatibility_class("Generic6DOFJoint", "Generic6DOFJoint3D"); + ClassDB::add_compatibility_class("GradientTexture", "GradientTexture1D"); ClassDB::add_compatibility_class("HeightMapShape", "HeightMapShape3D"); ClassDB::add_compatibility_class("HingeJoint", "HingeJoint3D"); ClassDB::add_compatibility_class("Joint", "Joint3D"); diff --git a/scene/resources/animation.cpp b/scene/resources/animation.cpp index 71d2774335..29aca6b321 100644 --- a/scene/resources/animation.cpp +++ b/scene/resources/animation.cpp @@ -317,7 +317,7 @@ bool Animation::_set(const StringName &p_name, const Variant &p_value) { Vector<real_t> times = d["times"]; Vector<real_t> values = d["points"]; - ERR_FAIL_COND_V(times.size() * 5 != values.size(), false); + ERR_FAIL_COND_V(times.size() * 6 != values.size(), false); if (times.size()) { int valcount = times.size(); @@ -330,11 +330,12 @@ bool Animation::_set(const StringName &p_name, const Variant &p_value) { for (int i = 0; i < valcount; i++) { bt->values.write[i].time = rt[i]; bt->values.write[i].transition = 0; //unused in bezier - bt->values.write[i].value.value = rv[i * 5 + 0]; - bt->values.write[i].value.in_handle.x = rv[i * 5 + 1]; - bt->values.write[i].value.in_handle.y = rv[i * 5 + 2]; - bt->values.write[i].value.out_handle.x = rv[i * 5 + 3]; - bt->values.write[i].value.out_handle.y = rv[i * 5 + 4]; + bt->values.write[i].value.value = rv[i * 6 + 0]; + bt->values.write[i].value.in_handle.x = rv[i * 6 + 1]; + bt->values.write[i].value.in_handle.y = rv[i * 6 + 2]; + bt->values.write[i].value.out_handle.x = rv[i * 6 + 3]; + bt->values.write[i].value.out_handle.y = rv[i * 6 + 4]; + bt->values.write[i].value.handle_mode = (HandleMode)rv[i * 6 + 5]; } } @@ -449,8 +450,8 @@ bool Animation::_get(const StringName &p_name, Variant &r_ret) const { return true; } else if (name == "length") { r_ret = length; - } else if (name == "loop") { - r_ret = loop; + } else if (name == "loop_mode") { + r_ret = loop_mode; } else if (name == "step") { r_ret = step; } else if (name.begins_with("tracks/")) { @@ -698,7 +699,7 @@ bool Animation::_get(const StringName &p_name, Variant &r_ret) const { int kk = bt->values.size(); key_times.resize(kk); - key_points.resize(kk * 5); + key_points.resize(kk * 6); real_t *wti = key_times.ptrw(); real_t *wpo = key_points.ptrw(); @@ -709,11 +710,12 @@ bool Animation::_get(const StringName &p_name, Variant &r_ret) const { for (int i = 0; i < kk; i++) { wti[idx] = vls[i].time; - wpo[idx * 5 + 0] = vls[i].value.value; - wpo[idx * 5 + 1] = vls[i].value.in_handle.x; - wpo[idx * 5 + 2] = vls[i].value.in_handle.y; - wpo[idx * 5 + 3] = vls[i].value.out_handle.x; - wpo[idx * 5 + 4] = vls[i].value.out_handle.y; + wpo[idx * 6 + 0] = vls[i].value.value; + wpo[idx * 6 + 1] = vls[i].value.in_handle.x; + wpo[idx * 6 + 2] = vls[i].value.in_handle.y; + wpo[idx * 6 + 3] = vls[i].value.out_handle.x; + wpo[idx * 6 + 4] = vls[i].value.out_handle.y; + wpo[idx * 6 + 5] = (double)vls[i].value.handle_mode; idx++; } @@ -1623,7 +1625,7 @@ void Animation::track_insert_key(int p_track, double p_time, const Variant &p_ke BezierTrack *bt = static_cast<BezierTrack *>(t); Array arr = p_key; - ERR_FAIL_COND(arr.size() != 5); + ERR_FAIL_COND(arr.size() != 6); TKey<BezierKey> k; k.time = p_time; @@ -1632,6 +1634,7 @@ void Animation::track_insert_key(int p_track, double p_time, const Variant &p_ke k.value.in_handle.y = arr[2]; k.value.out_handle.x = arr[3]; k.value.out_handle.y = arr[4]; + k.value.handle_mode = (HandleMode) int(arr[5]); _insert(p_time, bt->values, k); } break; @@ -1770,12 +1773,13 @@ Variant Animation::track_get_key_value(int p_track, int p_key_idx) const { ERR_FAIL_INDEX_V(p_key_idx, bt->values.size(), Variant()); Array arr; - arr.resize(5); + arr.resize(6); arr[0] = bt->values[p_key_idx].value.value; arr[1] = bt->values[p_key_idx].value.in_handle.x; arr[2] = bt->values[p_key_idx].value.in_handle.y; arr[3] = bt->values[p_key_idx].value.out_handle.x; arr[4] = bt->values[p_key_idx].value.out_handle.y; + arr[5] = (double)bt->values[p_key_idx].value.handle_mode; return arr; } break; @@ -2144,13 +2148,14 @@ void Animation::track_set_key_value(int p_track, int p_key_idx, const Variant &p ERR_FAIL_INDEX(p_key_idx, bt->values.size()); Array arr = p_value; - ERR_FAIL_COND(arr.size() != 5); + ERR_FAIL_COND(arr.size() != 6); bt->values.write[p_key_idx].value.value = arr[0]; bt->values.write[p_key_idx].value.in_handle.x = arr[1]; bt->values.write[p_key_idx].value.in_handle.y = arr[2]; bt->values.write[p_key_idx].value.out_handle.x = arr[3]; bt->values.write[p_key_idx].value.out_handle.y = arr[4]; + bt->values.write[p_key_idx].value.handle_mode = (HandleMode) int(arr[5]); } break; case TYPE_AUDIO: { @@ -2231,7 +2236,7 @@ void Animation::track_set_key_transition(int p_track, int p_key_idx, real_t p_tr } template <class K> -int Animation::_find(const Vector<K> &p_keys, double p_time) const { +int Animation::_find(const Vector<K> &p_keys, double p_time, bool p_backward) const { int len = p_keys.size(); if (len == 0) { return -2; @@ -2261,8 +2266,14 @@ int Animation::_find(const Vector<K> &p_keys, double p_time) const { } } - if (keys[middle].time > p_time) { - middle--; + if (!p_backward) { + if (keys[middle].time > p_time) { + middle--; + } + } else { + if (keys[middle].time < p_time) { + middle++; + } } return middle; @@ -2385,7 +2396,7 @@ real_t Animation::_cubic_interpolate(const real_t &p_pre_a, const real_t &p_a, c } template <class T> -T Animation::_interpolate(const Vector<TKey<T>> &p_keys, double p_time, InterpolationType p_interp, bool p_loop_wrap, bool *p_ok) const { +T Animation::_interpolate(const Vector<TKey<T>> &p_keys, double p_time, InterpolationType p_interp, bool p_loop_wrap, bool *p_ok, bool p_backward) const { int len = _find(p_keys, length) + 1; // try to find last key (there may be more past the end) if (len <= 0) { @@ -2403,7 +2414,7 @@ T Animation::_interpolate(const Vector<TKey<T>> &p_keys, double p_time, Interpol return p_keys[0].value; } - int idx = _find(p_keys, p_time); + int idx = _find(p_keys, p_time, p_backward); ERR_FAIL_COND_V(idx == -2, T()); @@ -2412,24 +2423,42 @@ T Animation::_interpolate(const Vector<TKey<T>> &p_keys, double p_time, Interpol real_t c = 0.0; // prepare for all cases of interpolation - if (loop && p_loop_wrap) { + if ((loop_mode == LOOP_LINEAR || loop_mode == LOOP_PINGPONG) && p_loop_wrap) { // loop - if (idx >= 0) { - if ((idx + 1) < len) { - next = idx + 1; - real_t delta = p_keys[next].time - p_keys[idx].time; - real_t from = p_time - p_keys[idx].time; - - if (Math::is_zero_approx(delta)) { - c = 0; + if (!p_backward) { + // no backward + if (idx >= 0) { + if (idx < len - 1) { + next = idx + 1; + real_t delta = p_keys[next].time - p_keys[idx].time; + real_t from = p_time - p_keys[idx].time; + + if (Math::is_zero_approx(delta)) { + c = 0; + } else { + c = from / delta; + } } else { - c = from / delta; - } + next = 0; + real_t delta = (length - p_keys[idx].time) + p_keys[next].time; + real_t from = p_time - p_keys[idx].time; + if (Math::is_zero_approx(delta)) { + c = 0; + } else { + c = from / delta; + } + } } else { + // on loop, behind first key + idx = len - 1; next = 0; - real_t delta = (length - p_keys[idx].time) + p_keys[next].time; - real_t from = p_time - p_keys[idx].time; + real_t endtime = (length - p_keys[idx].time); + if (endtime < 0) { // may be keys past the end + endtime = 0; + } + real_t delta = endtime + p_keys[next].time; + real_t from = endtime + p_time; if (Math::is_zero_approx(delta)) { c = 0; @@ -2437,49 +2466,81 @@ T Animation::_interpolate(const Vector<TKey<T>> &p_keys, double p_time, Interpol c = from / delta; } } - } else { - // on loop, behind first key - idx = len - 1; - next = 0; - real_t endtime = (length - p_keys[idx].time); - if (endtime < 0) { // may be keys past the end - endtime = 0; - } - real_t delta = endtime + p_keys[next].time; - real_t from = endtime + p_time; - - if (Math::is_zero_approx(delta)) { - c = 0; + // backward + if (idx <= len - 1) { + if (idx > 0) { + next = idx - 1; + real_t delta = (length - p_keys[next].time) - (length - p_keys[idx].time); + real_t from = (length - p_time) - (length - p_keys[idx].time); + + if (Math::is_zero_approx(delta)) + c = 0; + else + c = from / delta; + } else { + next = len - 1; + real_t delta = p_keys[idx].time + (length - p_keys[next].time); + real_t from = (length - p_time) - (length - p_keys[idx].time); + + if (Math::is_zero_approx(delta)) + c = 0; + else + c = from / delta; + } } else { - c = from / delta; + // on loop, in front of last key + idx = 0; + next = len - 1; + real_t endtime = p_keys[idx].time; + if (endtime > length) // may be keys past the end + endtime = length; + real_t delta = p_keys[next].time - endtime; + real_t from = p_time - endtime; + + if (Math::is_zero_approx(delta)) + c = 0; + else + c = from / delta; } } - } else { // no loop - - if (idx >= 0) { - if ((idx + 1) < len) { - next = idx + 1; - real_t delta = p_keys[next].time - p_keys[idx].time; - real_t from = p_time - p_keys[idx].time; - - if (Math::is_zero_approx(delta)) { - c = 0; + if (!p_backward) { + if (idx >= 0) { + if (idx < len - 1) { + next = idx + 1; + real_t delta = p_keys[next].time - p_keys[idx].time; + real_t from = p_time - p_keys[idx].time; + + if (Math::is_zero_approx(delta)) { + c = 0; + } else { + c = from / delta; + } } else { - c = from / delta; + next = idx; } - } else { - next = idx; + idx = next = 0; } - } else { - // only allow extending first key to anim start if looping - if (loop) { - idx = next = 0; + if (idx <= len - 1) { + if (idx > 0) { + next = idx - 1; + real_t delta = (length - p_keys[next].time) - (length - p_keys[idx].time); + real_t from = (length - p_time) - (length - p_keys[idx].time); + + if (Math::is_zero_approx(delta)) { + c = 0; + } else { + c = from / delta; + } + + } else { + next = idx; + } } else { - result = false; + idx = next = len - 1; } } } @@ -2583,7 +2644,7 @@ void Animation::_value_track_get_key_indices_in_range(const ValueTrack *vt, doub } } -void Animation::value_track_get_key_indices(int p_track, double p_time, double p_delta, List<int> *p_indices) const { +void Animation::value_track_get_key_indices(int p_track, double p_time, double p_delta, List<int> *p_indices, int p_pingponged) const { ERR_FAIL_INDEX(p_track, tracks.size()); Track *t = tracks[p_track]; ERR_FAIL_COND(t->type != TYPE_VALUE); @@ -2597,30 +2658,50 @@ void Animation::value_track_get_key_indices(int p_track, double p_time, double p SWAP(from_time, to_time); } - if (loop) { - from_time = Math::fposmod(from_time, length); - to_time = Math::fposmod(to_time, length); + switch (loop_mode) { + case LOOP_NONE: { + if (from_time < 0) { + from_time = 0; + } + if (from_time > length) { + from_time = length; + } - if (from_time > to_time) { - // handle loop by splitting - _value_track_get_key_indices_in_range(vt, from_time, length, p_indices); - _value_track_get_key_indices_in_range(vt, 0, to_time, p_indices); - return; - } - } else { - if (from_time < 0) { - from_time = 0; - } - if (from_time > length) { - from_time = length; - } + if (to_time < 0) { + to_time = 0; + } + if (to_time > length) { + to_time = length; + } + } break; + case LOOP_LINEAR: { + from_time = Math::fposmod(from_time, length); + to_time = Math::fposmod(to_time, length); - if (to_time < 0) { - to_time = 0; - } - if (to_time > length) { - to_time = length; - } + if (from_time > to_time) { + // handle loop by splitting + _value_track_get_key_indices_in_range(vt, from_time, length, p_indices); + _value_track_get_key_indices_in_range(vt, 0, to_time, p_indices); + return; + } + } break; + case LOOP_PINGPONG: { + from_time = Math::pingpong(from_time, length); + to_time = Math::pingpong(to_time, length); + + if (p_pingponged == -1) { + // handle loop by splitting + _value_track_get_key_indices_in_range(vt, 0, from_time, p_indices); + _value_track_get_key_indices_in_range(vt, 0, to_time, p_indices); + return; + } + if (p_pingponged == 1) { + // handle loop by splitting + _value_track_get_key_indices_in_range(vt, from_time, length, p_indices); + _value_track_get_key_indices_in_range(vt, to_time, length, p_indices); + return; + } + } break; } _value_track_get_key_indices_in_range(vt, from_time, to_time, p_indices); @@ -2679,7 +2760,7 @@ void Animation::_track_get_key_indices_in_range(const Vector<T> &p_array, double } } -void Animation::track_get_key_indices_in_range(int p_track, double p_time, double p_delta, List<int> *p_indices) const { +void Animation::track_get_key_indices_in_range(int p_track, double p_time, double p_delta, List<int> *p_indices, int p_pingponged) const { ERR_FAIL_INDEX(p_track, tracks.size()); const Track *t = tracks[p_track]; @@ -2690,114 +2771,255 @@ void Animation::track_get_key_indices_in_range(int p_track, double p_time, doubl SWAP(from_time, to_time); } - if (loop) { - if (from_time > length || from_time < 0) { - from_time = Math::fposmod(from_time, length); - } - - if (to_time > length || to_time < 0) { - to_time = Math::fposmod(to_time, length); - } - - if (from_time > to_time) { - // handle loop by splitting - - switch (t->type) { - case TYPE_POSITION_3D: { - const PositionTrack *tt = static_cast<const PositionTrack *>(t); - if (tt->compressed_track >= 0) { - _get_compressed_key_indices_in_range<3>(tt->compressed_track, from_time, length, p_indices); - _get_compressed_key_indices_in_range<3>(tt->compressed_track, 0, to_time, p_indices); - - } else { - _track_get_key_indices_in_range(tt->positions, from_time, length, p_indices); - _track_get_key_indices_in_range(tt->positions, 0, to_time, p_indices); - } - - } break; - case TYPE_ROTATION_3D: { - const RotationTrack *rt = static_cast<const RotationTrack *>(t); - if (rt->compressed_track >= 0) { - _get_compressed_key_indices_in_range<3>(rt->compressed_track, from_time, length, p_indices); - _get_compressed_key_indices_in_range<3>(rt->compressed_track, 0, to_time, p_indices); + switch (loop_mode) { + case LOOP_NONE: { + if (from_time < 0) { + from_time = 0; + } + if (from_time > length) { + from_time = length; + } - } else { - _track_get_key_indices_in_range(rt->rotations, from_time, length, p_indices); - _track_get_key_indices_in_range(rt->rotations, 0, to_time, p_indices); - } + if (to_time < 0) { + to_time = 0; + } + if (to_time > length) { + to_time = length; + } + } break; + case LOOP_LINEAR: { + if (from_time > length || from_time < 0) { + from_time = Math::fposmod(from_time, length); + } + if (to_time > length || to_time < 0) { + to_time = Math::fposmod(to_time, length); + } - } break; - case TYPE_SCALE_3D: { - const ScaleTrack *st = static_cast<const ScaleTrack *>(t); - if (st->compressed_track >= 0) { - _get_compressed_key_indices_in_range<3>(st->compressed_track, from_time, length, p_indices); - _get_compressed_key_indices_in_range<3>(st->compressed_track, 0, to_time, p_indices); + if (from_time > to_time) { + // handle loop by splitting + switch (t->type) { + case TYPE_POSITION_3D: { + const PositionTrack *tt = static_cast<const PositionTrack *>(t); + if (tt->compressed_track >= 0) { + _get_compressed_key_indices_in_range<3>(tt->compressed_track, from_time, length, p_indices); + _get_compressed_key_indices_in_range<3>(tt->compressed_track, 0, to_time, p_indices); + } else { + _track_get_key_indices_in_range(tt->positions, from_time, length, p_indices); + _track_get_key_indices_in_range(tt->positions, 0, to_time, p_indices); + } + } break; + case TYPE_ROTATION_3D: { + const RotationTrack *rt = static_cast<const RotationTrack *>(t); + if (rt->compressed_track >= 0) { + _get_compressed_key_indices_in_range<3>(rt->compressed_track, from_time, length, p_indices); + _get_compressed_key_indices_in_range<3>(rt->compressed_track, 0, to_time, p_indices); + } else { + _track_get_key_indices_in_range(rt->rotations, from_time, length, p_indices); + _track_get_key_indices_in_range(rt->rotations, 0, to_time, p_indices); + } + } break; + case TYPE_SCALE_3D: { + const ScaleTrack *st = static_cast<const ScaleTrack *>(t); + if (st->compressed_track >= 0) { + _get_compressed_key_indices_in_range<3>(st->compressed_track, from_time, length, p_indices); + _get_compressed_key_indices_in_range<3>(st->compressed_track, 0, to_time, p_indices); + } else { + _track_get_key_indices_in_range(st->scales, from_time, length, p_indices); + _track_get_key_indices_in_range(st->scales, 0, to_time, p_indices); + } + } break; + case TYPE_BLEND_SHAPE: { + const BlendShapeTrack *bst = static_cast<const BlendShapeTrack *>(t); + if (bst->compressed_track >= 0) { + _get_compressed_key_indices_in_range<1>(bst->compressed_track, from_time, length, p_indices); + _get_compressed_key_indices_in_range<1>(bst->compressed_track, 0, to_time, p_indices); + } else { + _track_get_key_indices_in_range(bst->blend_shapes, from_time, length, p_indices); + _track_get_key_indices_in_range(bst->blend_shapes, 0, to_time, p_indices); + } + } break; + case TYPE_VALUE: { + const ValueTrack *vt = static_cast<const ValueTrack *>(t); + _track_get_key_indices_in_range(vt->values, from_time, length, p_indices); + _track_get_key_indices_in_range(vt->values, 0, to_time, p_indices); + } break; + case TYPE_METHOD: { + const MethodTrack *mt = static_cast<const MethodTrack *>(t); + _track_get_key_indices_in_range(mt->methods, from_time, length, p_indices); + _track_get_key_indices_in_range(mt->methods, 0, to_time, p_indices); + } break; + case TYPE_BEZIER: { + const BezierTrack *bz = static_cast<const BezierTrack *>(t); + _track_get_key_indices_in_range(bz->values, from_time, length, p_indices); + _track_get_key_indices_in_range(bz->values, 0, to_time, p_indices); + } break; + case TYPE_AUDIO: { + const AudioTrack *ad = static_cast<const AudioTrack *>(t); + _track_get_key_indices_in_range(ad->values, from_time, length, p_indices); + _track_get_key_indices_in_range(ad->values, 0, to_time, p_indices); + } break; + case TYPE_ANIMATION: { + const AnimationTrack *an = static_cast<const AnimationTrack *>(t); + _track_get_key_indices_in_range(an->values, from_time, length, p_indices); + _track_get_key_indices_in_range(an->values, 0, to_time, p_indices); + } break; + } + return; + } + } break; + case LOOP_PINGPONG: { + if (from_time > length || from_time < 0) { + from_time = Math::pingpong(from_time, length); + } + if (to_time > length || to_time < 0) { + to_time = Math::pingpong(to_time, length); + } - } else { - _track_get_key_indices_in_range(st->scales, from_time, length, p_indices); - _track_get_key_indices_in_range(st->scales, 0, to_time, p_indices); + if ((int)Math::floor(abs(p_delta) / length) % 2 == 0) { + if (p_pingponged == -1) { + // handle loop by splitting + switch (t->type) { + case TYPE_POSITION_3D: { + const PositionTrack *tt = static_cast<const PositionTrack *>(t); + if (tt->compressed_track >= 0) { + _get_compressed_key_indices_in_range<3>(tt->compressed_track, 0, from_time, p_indices); + _get_compressed_key_indices_in_range<3>(tt->compressed_track, 0, to_time, p_indices); + } else { + _track_get_key_indices_in_range(tt->positions, 0, from_time, p_indices); + _track_get_key_indices_in_range(tt->positions, 0, to_time, p_indices); + } + } break; + case TYPE_ROTATION_3D: { + const RotationTrack *rt = static_cast<const RotationTrack *>(t); + if (rt->compressed_track >= 0) { + _get_compressed_key_indices_in_range<3>(rt->compressed_track, 0, from_time, p_indices); + _get_compressed_key_indices_in_range<3>(rt->compressed_track, 0, to_time, p_indices); + } else { + _track_get_key_indices_in_range(rt->rotations, 0, from_time, p_indices); + _track_get_key_indices_in_range(rt->rotations, 0, to_time, p_indices); + } + } break; + case TYPE_SCALE_3D: { + const ScaleTrack *st = static_cast<const ScaleTrack *>(t); + if (st->compressed_track >= 0) { + _get_compressed_key_indices_in_range<3>(st->compressed_track, 0, from_time, p_indices); + _get_compressed_key_indices_in_range<3>(st->compressed_track, 0, to_time, p_indices); + } else { + _track_get_key_indices_in_range(st->scales, 0, from_time, p_indices); + _track_get_key_indices_in_range(st->scales, 0, to_time, p_indices); + } + } break; + case TYPE_BLEND_SHAPE: { + const BlendShapeTrack *bst = static_cast<const BlendShapeTrack *>(t); + if (bst->compressed_track >= 0) { + _get_compressed_key_indices_in_range<1>(bst->compressed_track, 0, from_time, p_indices); + _get_compressed_key_indices_in_range<1>(bst->compressed_track, 0, to_time, p_indices); + } else { + _track_get_key_indices_in_range(bst->blend_shapes, 0, from_time, p_indices); + _track_get_key_indices_in_range(bst->blend_shapes, 0, to_time, p_indices); + } + } break; + case TYPE_VALUE: { + const ValueTrack *vt = static_cast<const ValueTrack *>(t); + _track_get_key_indices_in_range(vt->values, 0, from_time, p_indices); + _track_get_key_indices_in_range(vt->values, 0, to_time, p_indices); + } break; + case TYPE_METHOD: { + const MethodTrack *mt = static_cast<const MethodTrack *>(t); + _track_get_key_indices_in_range(mt->methods, 0, from_time, p_indices); + _track_get_key_indices_in_range(mt->methods, 0, to_time, p_indices); + } break; + case TYPE_BEZIER: { + const BezierTrack *bz = static_cast<const BezierTrack *>(t); + _track_get_key_indices_in_range(bz->values, 0, from_time, p_indices); + _track_get_key_indices_in_range(bz->values, 0, to_time, p_indices); + } break; + case TYPE_AUDIO: { + const AudioTrack *ad = static_cast<const AudioTrack *>(t); + _track_get_key_indices_in_range(ad->values, 0, from_time, p_indices); + _track_get_key_indices_in_range(ad->values, 0, to_time, p_indices); + } break; + case TYPE_ANIMATION: { + const AnimationTrack *an = static_cast<const AnimationTrack *>(t); + _track_get_key_indices_in_range(an->values, 0, from_time, p_indices); + _track_get_key_indices_in_range(an->values, 0, to_time, p_indices); + } break; } - - } break; - case TYPE_BLEND_SHAPE: { - const BlendShapeTrack *bst = static_cast<const BlendShapeTrack *>(t); - if (bst->compressed_track >= 0) { - _get_compressed_key_indices_in_range<1>(bst->compressed_track, from_time, length, p_indices); - _get_compressed_key_indices_in_range<1>(bst->compressed_track, 0, to_time, p_indices); - - } else { - _track_get_key_indices_in_range(bst->blend_shapes, from_time, length, p_indices); - _track_get_key_indices_in_range(bst->blend_shapes, 0, to_time, p_indices); + return; + } + if (p_pingponged == 1) { + // handle loop by splitting + switch (t->type) { + case TYPE_POSITION_3D: { + const PositionTrack *tt = static_cast<const PositionTrack *>(t); + if (tt->compressed_track >= 0) { + _get_compressed_key_indices_in_range<3>(tt->compressed_track, from_time, length, p_indices); + _get_compressed_key_indices_in_range<3>(tt->compressed_track, to_time, length, p_indices); + } else { + _track_get_key_indices_in_range(tt->positions, from_time, length, p_indices); + _track_get_key_indices_in_range(tt->positions, to_time, length, p_indices); + } + } break; + case TYPE_ROTATION_3D: { + const RotationTrack *rt = static_cast<const RotationTrack *>(t); + if (rt->compressed_track >= 0) { + _get_compressed_key_indices_in_range<3>(rt->compressed_track, from_time, length, p_indices); + _get_compressed_key_indices_in_range<3>(rt->compressed_track, to_time, length, p_indices); + } else { + _track_get_key_indices_in_range(rt->rotations, from_time, length, p_indices); + _track_get_key_indices_in_range(rt->rotations, to_time, length, p_indices); + } + } break; + case TYPE_SCALE_3D: { + const ScaleTrack *st = static_cast<const ScaleTrack *>(t); + if (st->compressed_track >= 0) { + _get_compressed_key_indices_in_range<3>(st->compressed_track, from_time, length, p_indices); + _get_compressed_key_indices_in_range<3>(st->compressed_track, to_time, length, p_indices); + } else { + _track_get_key_indices_in_range(st->scales, from_time, length, p_indices); + _track_get_key_indices_in_range(st->scales, to_time, length, p_indices); + } + } break; + case TYPE_BLEND_SHAPE: { + const BlendShapeTrack *bst = static_cast<const BlendShapeTrack *>(t); + if (bst->compressed_track >= 0) { + _get_compressed_key_indices_in_range<1>(bst->compressed_track, from_time, length, p_indices); + _get_compressed_key_indices_in_range<1>(bst->compressed_track, to_time, length, p_indices); + } else { + _track_get_key_indices_in_range(bst->blend_shapes, from_time, length, p_indices); + _track_get_key_indices_in_range(bst->blend_shapes, to_time, length, p_indices); + } + } break; + case TYPE_VALUE: { + const ValueTrack *vt = static_cast<const ValueTrack *>(t); + _track_get_key_indices_in_range(vt->values, from_time, length, p_indices); + _track_get_key_indices_in_range(vt->values, to_time, length, p_indices); + } break; + case TYPE_METHOD: { + const MethodTrack *mt = static_cast<const MethodTrack *>(t); + _track_get_key_indices_in_range(mt->methods, from_time, length, p_indices); + _track_get_key_indices_in_range(mt->methods, to_time, length, p_indices); + } break; + case TYPE_BEZIER: { + const BezierTrack *bz = static_cast<const BezierTrack *>(t); + _track_get_key_indices_in_range(bz->values, from_time, length, p_indices); + _track_get_key_indices_in_range(bz->values, to_time, length, p_indices); + } break; + case TYPE_AUDIO: { + const AudioTrack *ad = static_cast<const AudioTrack *>(t); + _track_get_key_indices_in_range(ad->values, from_time, length, p_indices); + _track_get_key_indices_in_range(ad->values, to_time, length, p_indices); + } break; + case TYPE_ANIMATION: { + const AnimationTrack *an = static_cast<const AnimationTrack *>(t); + _track_get_key_indices_in_range(an->values, from_time, length, p_indices); + _track_get_key_indices_in_range(an->values, to_time, length, p_indices); + } break; } - - } break; - case TYPE_VALUE: { - const ValueTrack *vt = static_cast<const ValueTrack *>(t); - _track_get_key_indices_in_range(vt->values, from_time, length, p_indices); - _track_get_key_indices_in_range(vt->values, 0, to_time, p_indices); - - } break; - case TYPE_METHOD: { - const MethodTrack *mt = static_cast<const MethodTrack *>(t); - _track_get_key_indices_in_range(mt->methods, from_time, length, p_indices); - _track_get_key_indices_in_range(mt->methods, 0, to_time, p_indices); - - } break; - case TYPE_BEZIER: { - const BezierTrack *bz = static_cast<const BezierTrack *>(t); - _track_get_key_indices_in_range(bz->values, from_time, length, p_indices); - _track_get_key_indices_in_range(bz->values, 0, to_time, p_indices); - - } break; - case TYPE_AUDIO: { - const AudioTrack *ad = static_cast<const AudioTrack *>(t); - _track_get_key_indices_in_range(ad->values, from_time, length, p_indices); - _track_get_key_indices_in_range(ad->values, 0, to_time, p_indices); - - } break; - case TYPE_ANIMATION: { - const AnimationTrack *an = static_cast<const AnimationTrack *>(t); - _track_get_key_indices_in_range(an->values, from_time, length, p_indices); - _track_get_key_indices_in_range(an->values, 0, to_time, p_indices); - - } break; + return; + } } - return; - } - } else { - if (from_time < 0) { - from_time = 0; - } - if (from_time > length) { - from_time = length; - } - - if (to_time < 0) { - to_time = 0; - } - if (to_time > length) { - to_time = length; - } + } break; } switch (t->type) { @@ -2808,7 +3030,6 @@ void Animation::track_get_key_indices_in_range(int p_track, double p_time, doubl } else { _track_get_key_indices_in_range(tt->positions, from_time, to_time, p_indices); } - } break; case TYPE_ROTATION_3D: { const RotationTrack *rt = static_cast<const RotationTrack *>(t); @@ -2817,7 +3038,6 @@ void Animation::track_get_key_indices_in_range(int p_track, double p_time, doubl } else { _track_get_key_indices_in_range(rt->rotations, from_time, to_time, p_indices); } - } break; case TYPE_SCALE_3D: { const ScaleTrack *st = static_cast<const ScaleTrack *>(t); @@ -2826,7 +3046,6 @@ void Animation::track_get_key_indices_in_range(int p_track, double p_time, doubl } else { _track_get_key_indices_in_range(st->scales, from_time, to_time, p_indices); } - } break; case TYPE_BLEND_SHAPE: { const BlendShapeTrack *bst = static_cast<const BlendShapeTrack *>(t); @@ -2835,32 +3054,26 @@ void Animation::track_get_key_indices_in_range(int p_track, double p_time, doubl } else { _track_get_key_indices_in_range(bst->blend_shapes, from_time, to_time, p_indices); } - } break; case TYPE_VALUE: { const ValueTrack *vt = static_cast<const ValueTrack *>(t); _track_get_key_indices_in_range(vt->values, from_time, to_time, p_indices); - } break; case TYPE_METHOD: { const MethodTrack *mt = static_cast<const MethodTrack *>(t); _track_get_key_indices_in_range(mt->methods, from_time, to_time, p_indices); - } break; case TYPE_BEZIER: { const BezierTrack *bz = static_cast<const BezierTrack *>(t); _track_get_key_indices_in_range(bz->values, from_time, to_time, p_indices); - } break; case TYPE_AUDIO: { const AudioTrack *ad = static_cast<const AudioTrack *>(t); _track_get_key_indices_in_range(ad->values, from_time, to_time, p_indices); - } break; case TYPE_ANIMATION: { const AnimationTrack *an = static_cast<const AnimationTrack *>(t); _track_get_key_indices_in_range(an->values, from_time, to_time, p_indices); - } break; } } @@ -2898,7 +3111,7 @@ void Animation::_method_track_get_key_indices_in_range(const MethodTrack *mt, do } } -void Animation::method_track_get_key_indices(int p_track, double p_time, double p_delta, List<int> *p_indices) const { +void Animation::method_track_get_key_indices(int p_track, double p_time, double p_delta, List<int> *p_indices, int p_pingponged) const { ERR_FAIL_INDEX(p_track, tracks.size()); Track *t = tracks[p_track]; ERR_FAIL_COND(t->type != TYPE_METHOD); @@ -2912,35 +3125,58 @@ void Animation::method_track_get_key_indices(int p_track, double p_time, double SWAP(from_time, to_time); } - if (loop) { - if (from_time > length || from_time < 0) { - from_time = Math::fposmod(from_time, length); - } + switch (loop_mode) { + case LOOP_NONE: { + if (from_time < 0) { + from_time = 0; + } + if (from_time > length) { + from_time = length; + } - if (to_time > length || to_time < 0) { - to_time = Math::fposmod(to_time, length); - } + if (to_time < 0) { + to_time = 0; + } + if (to_time > length) { + to_time = length; + } + } break; + case LOOP_LINEAR: { + if (from_time > length || from_time < 0) { + from_time = Math::fposmod(from_time, length); + } + if (to_time > length || to_time < 0) { + to_time = Math::fposmod(to_time, length); + } - if (from_time > to_time) { - // handle loop by splitting - _method_track_get_key_indices_in_range(mt, from_time, length, p_indices); - _method_track_get_key_indices_in_range(mt, 0, to_time, p_indices); - return; - } - } else { - if (from_time < 0) { - from_time = 0; - } - if (from_time > length) { - from_time = length; - } + if (from_time > to_time) { + // handle loop by splitting + _method_track_get_key_indices_in_range(mt, from_time, length, p_indices); + _method_track_get_key_indices_in_range(mt, 0, to_time, p_indices); + return; + } + } break; + case LOOP_PINGPONG: { + if (from_time > length || from_time < 0) { + from_time = Math::pingpong(from_time, length); + } + if (to_time > length || to_time < 0) { + to_time = Math::pingpong(to_time, length); + } - if (to_time < 0) { - to_time = 0; - } - if (to_time > length) { - to_time = length; - } + if (p_pingponged == -1) { + _method_track_get_key_indices_in_range(mt, 0, from_time, p_indices); + _method_track_get_key_indices_in_range(mt, 0, to_time, p_indices); + return; + } + if (p_pingponged == 1) { + _method_track_get_key_indices_in_range(mt, from_time, length, p_indices); + _method_track_get_key_indices_in_range(mt, to_time, length, p_indices); + return; + } + } break; + default: + break; } _method_track_get_key_indices_in_range(mt, from_time, to_time, p_indices); @@ -2972,7 +3208,7 @@ StringName Animation::method_track_get_name(int p_track, int p_key_idx) const { return pm->methods[p_key_idx].method; } -int Animation::bezier_track_insert_key(int p_track, double p_time, real_t p_value, const Vector2 &p_in_handle, const Vector2 &p_out_handle) { +int Animation::bezier_track_insert_key(int p_track, double p_time, real_t p_value, const Vector2 &p_in_handle, const Vector2 &p_out_handle, const HandleMode p_handle_mode) { ERR_FAIL_INDEX_V(p_track, tracks.size(), -1); Track *t = tracks[p_track]; ERR_FAIL_COND_V(t->type != TYPE_BEZIER, -1); @@ -2990,6 +3226,7 @@ int Animation::bezier_track_insert_key(int p_track, double p_time, real_t p_valu if (k.value.out_handle.x < 0) { k.value.out_handle.x = 0; } + k.value.handle_mode = p_handle_mode; int key = _insert(p_time, bt->values, k); @@ -2998,6 +3235,30 @@ int Animation::bezier_track_insert_key(int p_track, double p_time, real_t p_valu return key; } +void Animation::bezier_track_set_key_handle_mode(int p_track, int p_index, HandleMode p_mode, double p_balanced_value_time_ratio) { + ERR_FAIL_INDEX(p_track, tracks.size()); + Track *t = tracks[p_track]; + ERR_FAIL_COND(t->type != TYPE_BEZIER); + + BezierTrack *bt = static_cast<BezierTrack *>(t); + + ERR_FAIL_INDEX(p_index, bt->values.size()); + + bt->values.write[p_index].value.handle_mode = p_mode; + + if (p_mode == HANDLE_MODE_BALANCED) { + Transform2D xform; + xform.set_scale(Vector2(1.0, 1.0 / p_balanced_value_time_ratio)); + + Vector2 vec_in = xform.xform(bt->values[p_index].value.in_handle); + Vector2 vec_out = xform.xform(bt->values[p_index].value.out_handle); + + bt->values.write[p_index].value.in_handle = xform.affine_inverse().xform(-vec_out.normalized() * vec_in.length()); + } + + emit_changed(); +} + void Animation::bezier_track_set_key_value(int p_track, int p_index, real_t p_value) { ERR_FAIL_INDEX(p_track, tracks.size()); Track *t = tracks[p_track]; @@ -3011,7 +3272,7 @@ void Animation::bezier_track_set_key_value(int p_track, int p_index, real_t p_va emit_changed(); } -void Animation::bezier_track_set_key_in_handle(int p_track, int p_index, const Vector2 &p_handle) { +void Animation::bezier_track_set_key_in_handle(int p_track, int p_index, const Vector2 &p_handle, double p_balanced_value_time_ratio) { ERR_FAIL_INDEX(p_track, tracks.size()); Track *t = tracks[p_track]; ERR_FAIL_COND(t->type != TYPE_BEZIER); @@ -3020,14 +3281,26 @@ void Animation::bezier_track_set_key_in_handle(int p_track, int p_index, const V ERR_FAIL_INDEX(p_index, bt->values.size()); - bt->values.write[p_index].value.in_handle = p_handle; - if (bt->values[p_index].value.in_handle.x > 0) { - bt->values.write[p_index].value.in_handle.x = 0; + Vector2 in_handle = p_handle; + if (in_handle.x > 0) { + in_handle.x = 0; + } + bt->values.write[p_index].value.in_handle = in_handle; + + if (bt->values[p_index].value.handle_mode == HANDLE_MODE_BALANCED) { + Transform2D xform; + xform.set_scale(Vector2(1.0, 1.0 / p_balanced_value_time_ratio)); + + Vector2 vec_out = xform.xform(bt->values[p_index].value.out_handle); + Vector2 vec_in = xform.xform(in_handle); + + bt->values.write[p_index].value.out_handle = xform.affine_inverse().xform(-vec_in.normalized() * vec_out.length()); } + emit_changed(); } -void Animation::bezier_track_set_key_out_handle(int p_track, int p_index, const Vector2 &p_handle) { +void Animation::bezier_track_set_key_out_handle(int p_track, int p_index, const Vector2 &p_handle, double p_balanced_value_time_ratio) { ERR_FAIL_INDEX(p_track, tracks.size()); Track *t = tracks[p_track]; ERR_FAIL_COND(t->type != TYPE_BEZIER); @@ -3036,10 +3309,22 @@ void Animation::bezier_track_set_key_out_handle(int p_track, int p_index, const ERR_FAIL_INDEX(p_index, bt->values.size()); - bt->values.write[p_index].value.out_handle = p_handle; - if (bt->values[p_index].value.out_handle.x < 0) { - bt->values.write[p_index].value.out_handle.x = 0; + Vector2 out_handle = p_handle; + if (out_handle.x < 0) { + out_handle.x = 0; + } + bt->values.write[p_index].value.out_handle = out_handle; + + if (bt->values[p_index].value.handle_mode == HANDLE_MODE_BALANCED) { + Transform2D xform; + xform.set_scale(Vector2(1.0, 1.0 / p_balanced_value_time_ratio)); + + Vector2 vec_in = xform.xform(bt->values[p_index].value.in_handle); + Vector2 vec_out = xform.xform(out_handle); + + bt->values.write[p_index].value.in_handle = xform.affine_inverse().xform(-vec_out.normalized() * vec_in.length()); } + emit_changed(); } @@ -3055,6 +3340,18 @@ real_t Animation::bezier_track_get_key_value(int p_track, int p_index) const { return bt->values[p_index].value.value; } +int Animation::bezier_track_get_key_handle_mode(int p_track, int p_index) const { + ERR_FAIL_INDEX_V(p_track, tracks.size(), 0); + Track *t = tracks[p_track]; + ERR_FAIL_COND_V(t->type != TYPE_BEZIER, 0); + + BezierTrack *bt = static_cast<BezierTrack *>(t); + + ERR_FAIL_INDEX_V(p_index, bt->values.size(), 0); + + return bt->values[p_index].value.handle_mode; +} + Vector2 Animation::bezier_track_get_key_in_handle(int p_track, int p_index) const { ERR_FAIL_INDEX_V(p_track, tracks.size(), Vector2()); Track *t = tracks[p_track]; @@ -3326,13 +3623,13 @@ real_t Animation::get_length() const { return length; } -void Animation::set_loop(bool p_enabled) { - loop = p_enabled; +void Animation::set_loop_mode(Animation::LoopMode p_loop_mode) { + loop_mode = p_loop_mode; emit_changed(); } -bool Animation::has_loop() const { - return loop; +Animation::LoopMode Animation::get_loop_mode() const { + return loop_mode; } void Animation::track_set_imported(int p_track, bool p_imported) { @@ -3487,11 +3784,11 @@ void Animation::_bind_methods() { ClassDB::bind_method(D_METHOD("method_track_get_name", "track_idx", "key_idx"), &Animation::method_track_get_name); ClassDB::bind_method(D_METHOD("method_track_get_params", "track_idx", "key_idx"), &Animation::method_track_get_params); - ClassDB::bind_method(D_METHOD("bezier_track_insert_key", "track_idx", "time", "value", "in_handle", "out_handle"), &Animation::bezier_track_insert_key, DEFVAL(Vector2()), DEFVAL(Vector2())); + ClassDB::bind_method(D_METHOD("bezier_track_insert_key", "track_idx", "time", "value", "in_handle", "out_handle", "handle_mode"), &Animation::bezier_track_insert_key, DEFVAL(Vector2()), DEFVAL(Vector2()), DEFVAL(Animation::HandleMode::HANDLE_MODE_BALANCED)); ClassDB::bind_method(D_METHOD("bezier_track_set_key_value", "track_idx", "key_idx", "value"), &Animation::bezier_track_set_key_value); - ClassDB::bind_method(D_METHOD("bezier_track_set_key_in_handle", "track_idx", "key_idx", "in_handle"), &Animation::bezier_track_set_key_in_handle); - ClassDB::bind_method(D_METHOD("bezier_track_set_key_out_handle", "track_idx", "key_idx", "out_handle"), &Animation::bezier_track_set_key_out_handle); + ClassDB::bind_method(D_METHOD("bezier_track_set_key_in_handle", "track_idx", "key_idx", "in_handle", "balanced_value_time_ratio"), &Animation::bezier_track_set_key_in_handle, DEFVAL(1.0)); + ClassDB::bind_method(D_METHOD("bezier_track_set_key_out_handle", "track_idx", "key_idx", "out_handle", "balanced_value_time_ratio"), &Animation::bezier_track_set_key_out_handle, DEFVAL(1.0)); ClassDB::bind_method(D_METHOD("bezier_track_get_key_value", "track_idx", "key_idx"), &Animation::bezier_track_get_key_value); ClassDB::bind_method(D_METHOD("bezier_track_get_key_in_handle", "track_idx", "key_idx"), &Animation::bezier_track_get_key_in_handle); @@ -3507,6 +3804,9 @@ void Animation::_bind_methods() { ClassDB::bind_method(D_METHOD("audio_track_get_key_start_offset", "track_idx", "key_idx"), &Animation::audio_track_get_key_start_offset); ClassDB::bind_method(D_METHOD("audio_track_get_key_end_offset", "track_idx", "key_idx"), &Animation::audio_track_get_key_end_offset); + ClassDB::bind_method(D_METHOD("bezier_track_set_key_handle_mode", "track_idx", "key_idx", "key_handle_mode", "balanced_value_time_ratio"), &Animation::bezier_track_set_key_handle_mode, DEFVAL(1.0)); + ClassDB::bind_method(D_METHOD("bezier_track_get_key_handle_mode", "track_idx", "key_idx"), &Animation::bezier_track_get_key_handle_mode); + ClassDB::bind_method(D_METHOD("animation_track_insert_key", "track_idx", "time", "animation"), &Animation::animation_track_insert_key); ClassDB::bind_method(D_METHOD("animation_track_set_key_animation", "track_idx", "key_idx", "animation"), &Animation::animation_track_set_key_animation); ClassDB::bind_method(D_METHOD("animation_track_get_key_animation", "track_idx", "key_idx"), &Animation::animation_track_get_key_animation); @@ -3514,8 +3814,8 @@ void Animation::_bind_methods() { ClassDB::bind_method(D_METHOD("set_length", "time_sec"), &Animation::set_length); ClassDB::bind_method(D_METHOD("get_length"), &Animation::get_length); - ClassDB::bind_method(D_METHOD("set_loop", "enabled"), &Animation::set_loop); - ClassDB::bind_method(D_METHOD("has_loop"), &Animation::has_loop); + ClassDB::bind_method(D_METHOD("set_loop_mode", "loop_mode"), &Animation::set_loop_mode); + ClassDB::bind_method(D_METHOD("get_loop_mode"), &Animation::get_loop_mode); ClassDB::bind_method(D_METHOD("set_step", "size_sec"), &Animation::set_step); ClassDB::bind_method(D_METHOD("get_step"), &Animation::get_step); @@ -3526,7 +3826,7 @@ void Animation::_bind_methods() { ClassDB::bind_method(D_METHOD("compress", "page_size", "fps", "split_tolerance"), &Animation::compress, DEFVAL(8192), DEFVAL(120), DEFVAL(4.0)); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "length", PROPERTY_HINT_RANGE, "0.001,99999,0.001"), "set_length", "get_length"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "loop"), "set_loop", "has_loop"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "loop_mode"), "set_loop_mode", "get_loop_mode"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "step", PROPERTY_HINT_RANGE, "0,4096,0.001"), "set_step", "get_step"); ADD_SIGNAL(MethodInfo("tracks_changed")); @@ -3549,6 +3849,13 @@ void Animation::_bind_methods() { BIND_ENUM_CONSTANT(UPDATE_DISCRETE); BIND_ENUM_CONSTANT(UPDATE_TRIGGER); BIND_ENUM_CONSTANT(UPDATE_CAPTURE); + + BIND_ENUM_CONSTANT(LOOP_NONE); + BIND_ENUM_CONSTANT(LOOP_LINEAR); + BIND_ENUM_CONSTANT(LOOP_PINGPONG); + + BIND_ENUM_CONSTANT(HANDLE_MODE_FREE); + BIND_ENUM_CONSTANT(HANDLE_MODE_BALANCED); } void Animation::clear() { @@ -3556,7 +3863,7 @@ void Animation::clear() { memdelete(tracks[i]); } tracks.clear(); - loop = false; + loop_mode = LOOP_NONE; length = 1; compression.enabled = false; compression.bounds.clear(); diff --git a/scene/resources/animation.h b/scene/resources/animation.h index ee07fb19d3..8e4287e4fb 100644 --- a/scene/resources/animation.h +++ b/scene/resources/animation.h @@ -64,7 +64,17 @@ public: UPDATE_DISCRETE, UPDATE_TRIGGER, UPDATE_CAPTURE, + }; + + enum LoopMode { + LOOP_NONE, + LOOP_LINEAR, + LOOP_PINGPONG, + }; + enum HandleMode { + HANDLE_MODE_FREE, + HANDLE_MODE_BALANCED, }; private: @@ -152,10 +162,10 @@ private: }; /* BEZIER TRACK */ - struct BezierKey { Vector2 in_handle; //relative (x always <0) Vector2 out_handle; //relative (x always >0) + HandleMode handle_mode = HANDLE_MODE_BALANCED; real_t value = 0.0; }; @@ -208,7 +218,8 @@ private: int _insert(double p_time, T &p_keys, const V &p_value); template <class K> - inline int _find(const Vector<K> &p_keys, double p_time) const; + + inline int _find(const Vector<K> &p_keys, double p_time, bool p_backward = false) const; _FORCE_INLINE_ Vector3 _interpolate(const Vector3 &p_a, const Vector3 &p_b, real_t p_c) const; _FORCE_INLINE_ Quaternion _interpolate(const Quaternion &p_a, const Quaternion &p_b, real_t p_c) const; @@ -221,7 +232,7 @@ private: _FORCE_INLINE_ real_t _cubic_interpolate(const real_t &p_pre_a, const real_t &p_a, const real_t &p_b, const real_t &p_post_b, real_t p_c) const; template <class T> - _FORCE_INLINE_ T _interpolate(const Vector<TKey<T>> &p_keys, double p_time, InterpolationType p_interp, bool p_loop_wrap, bool *p_ok) const; + _FORCE_INLINE_ T _interpolate(const Vector<TKey<T>> &p_keys, double p_time, InterpolationType p_interp, bool p_loop_wrap, bool *p_ok, bool p_backward = false) const; template <class T> _FORCE_INLINE_ void _track_get_key_indices_in_range(const Vector<T> &p_array, double from_time, double to_time, List<int> *p_indices) const; @@ -231,7 +242,8 @@ private: double length = 1.0; real_t step = 0.1; - bool loop = false; + LoopMode loop_mode = LOOP_NONE; + int pingponged = 0; /* Animation compression page format (version 1): * @@ -412,11 +424,13 @@ public: void track_set_interpolation_type(int p_track, InterpolationType p_interp); InterpolationType track_get_interpolation_type(int p_track) const; - int bezier_track_insert_key(int p_track, double p_time, real_t p_value, const Vector2 &p_in_handle, const Vector2 &p_out_handle); + int bezier_track_insert_key(int p_track, double p_time, real_t p_value, const Vector2 &p_in_handle, const Vector2 &p_out_handle, const HandleMode p_handle_mode = HandleMode::HANDLE_MODE_BALANCED); + void bezier_track_set_key_handle_mode(int p_track, int p_index, HandleMode p_mode, double p_balanced_value_time_ratio = 1.0); void bezier_track_set_key_value(int p_track, int p_index, real_t p_value); - void bezier_track_set_key_in_handle(int p_track, int p_index, const Vector2 &p_handle); - void bezier_track_set_key_out_handle(int p_track, int p_index, const Vector2 &p_handle); + void bezier_track_set_key_in_handle(int p_track, int p_index, const Vector2 &p_handle, double p_balanced_value_time_ratio = 1.0); + void bezier_track_set_key_out_handle(int p_track, int p_index, const Vector2 &p_handle, double p_balanced_value_time_ratio = 1.0); real_t bezier_track_get_key_value(int p_track, int p_index) const; + int bezier_track_get_key_handle_mode(int p_track, int p_index) const; Vector2 bezier_track_get_key_in_handle(int p_track, int p_index) const; Vector2 bezier_track_get_key_out_handle(int p_track, int p_index) const; @@ -438,23 +452,23 @@ public: bool track_get_interpolation_loop_wrap(int p_track) const; Variant value_track_interpolate(int p_track, double p_time) const; - void value_track_get_key_indices(int p_track, double p_time, double p_delta, List<int> *p_indices) const; + void value_track_get_key_indices(int p_track, double p_time, double p_delta, List<int> *p_indices, int p_pingponged = 0) const; void value_track_set_update_mode(int p_track, UpdateMode p_mode); UpdateMode value_track_get_update_mode(int p_track) const; - void method_track_get_key_indices(int p_track, double p_time, double p_delta, List<int> *p_indices) const; + void method_track_get_key_indices(int p_track, double p_time, double p_delta, List<int> *p_indices, int p_pingponged = 0) const; Vector<Variant> method_track_get_params(int p_track, int p_key_idx) const; StringName method_track_get_name(int p_track, int p_key_idx) const; void copy_track(int p_track, Ref<Animation> p_to_animation); - void track_get_key_indices_in_range(int p_track, double p_time, double p_delta, List<int> *p_indices) const; + void track_get_key_indices_in_range(int p_track, double p_time, double p_delta, List<int> *p_indices, int p_pingponged = 0) const; void set_length(real_t p_length); real_t get_length() const; - void set_loop(bool p_enabled); - bool has_loop() const; + void set_loop_mode(LoopMode p_loop_mode); + LoopMode get_loop_mode() const; void set_step(real_t p_step); real_t get_step() const; @@ -471,5 +485,7 @@ public: VARIANT_ENUM_CAST(Animation::TrackType); VARIANT_ENUM_CAST(Animation::InterpolationType); VARIANT_ENUM_CAST(Animation::UpdateMode); +VARIANT_ENUM_CAST(Animation::HandleMode); +VARIANT_ENUM_CAST(Animation::LoopMode); #endif diff --git a/scene/resources/audio_stream_sample.cpp b/scene/resources/audio_stream_sample.cpp index 5cfc4a9d40..d3fab802c5 100644 --- a/scene/resources/audio_stream_sample.cpp +++ b/scene/resources/audio_stream_sample.cpp @@ -299,7 +299,7 @@ int AudioStreamPlaybackSample::mix(AudioFrame *p_buffer, float p_rate_scale, int if (loop_format != AudioStreamSample::LOOP_DISABLED && offset < loop_begin_fp) { /* loopstart reached */ - if (loop_format == AudioStreamSample::LOOP_PING_PONG) { + if (loop_format == AudioStreamSample::LOOP_PINGPONG) { /* bounce ping pong */ offset = loop_begin_fp + (loop_begin_fp - offset); increment = -increment; @@ -320,7 +320,7 @@ int AudioStreamPlaybackSample::mix(AudioFrame *p_buffer, float p_rate_scale, int if (loop_format != AudioStreamSample::LOOP_DISABLED && offset >= loop_end_fp) { /* loopend reached */ - if (loop_format == AudioStreamSample::LOOP_PING_PONG) { + if (loop_format == AudioStreamSample::LOOP_PINGPONG) { /* bounce ping pong */ offset = loop_end_fp - (offset - loop_end_fp); increment = -increment; @@ -650,7 +650,7 @@ void AudioStreamSample::_bind_methods() { BIND_ENUM_CONSTANT(LOOP_DISABLED); BIND_ENUM_CONSTANT(LOOP_FORWARD); - BIND_ENUM_CONSTANT(LOOP_PING_PONG); + BIND_ENUM_CONSTANT(LOOP_PINGPONG); BIND_ENUM_CONSTANT(LOOP_BACKWARD); } diff --git a/scene/resources/audio_stream_sample.h b/scene/resources/audio_stream_sample.h index 24198e3c98..0eb34be9bf 100644 --- a/scene/resources/audio_stream_sample.h +++ b/scene/resources/audio_stream_sample.h @@ -92,7 +92,7 @@ public: enum LoopMode { LOOP_DISABLED, LOOP_FORWARD, - LOOP_PING_PONG, + LOOP_PINGPONG, LOOP_BACKWARD }; diff --git a/scene/resources/default_theme/default_theme.cpp b/scene/resources/default_theme/default_theme.cpp index f21a070133..a1d76ef352 100644 --- a/scene/resources/default_theme/default_theme.cpp +++ b/scene/resources/default_theme/default_theme.cpp @@ -940,7 +940,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_constant("shadow_offset_x", "RichTextLabel", 1 * scale); theme->set_constant("shadow_offset_y", "RichTextLabel", 1 * scale); - theme->set_constant("shadow_as_outline", "RichTextLabel", 0 * scale); + theme->set_constant("shadow_outline_size", "RichTextLabel", 1 * scale); theme->set_constant("line_separation", "RichTextLabel", 0 * scale); theme->set_constant("table_hseparation", "RichTextLabel", 3 * scale); diff --git a/scene/resources/environment.cpp b/scene/resources/environment.cpp index ecc775df20..4c25ed589b 100644 --- a/scene/resources/environment.cpp +++ b/scene/resources/environment.cpp @@ -917,7 +917,7 @@ float Environment::get_adjustment_saturation() const { void Environment::set_adjustment_color_correction(Ref<Texture> p_color_correction) { adjustment_color_correction = p_color_correction; - Ref<GradientTexture> grad_tex = p_color_correction; + Ref<GradientTexture1D> grad_tex = p_color_correction; if (grad_tex.is_valid()) { if (!grad_tex->is_connected(CoreStringNames::get_singleton()->changed, callable_mp(this, &Environment::_update_adjustment))) { grad_tex->connect(CoreStringNames::get_singleton()->changed, callable_mp(this, &Environment::_update_adjustment)); diff --git a/scene/resources/font.cpp b/scene/resources/font.cpp index 819ae95715..d9de47afc7 100644 --- a/scene/resources/font.cpp +++ b/scene/resources/font.cpp @@ -70,6 +70,15 @@ void FontData::_bind_methods() { ClassDB::bind_method(D_METHOD("set_antialiased", "antialiased"), &FontData::set_antialiased); ClassDB::bind_method(D_METHOD("is_antialiased"), &FontData::is_antialiased); + ClassDB::bind_method(D_METHOD("set_font_name", "name"), &FontData::set_font_name); + ClassDB::bind_method(D_METHOD("get_font_name"), &FontData::get_font_name); + + ClassDB::bind_method(D_METHOD("set_font_style_name", "name"), &FontData::set_font_style_name); + ClassDB::bind_method(D_METHOD("get_font_style_name"), &FontData::get_font_style_name); + + ClassDB::bind_method(D_METHOD("set_font_style", "style"), &FontData::set_font_style); + ClassDB::bind_method(D_METHOD("get_font_style"), &FontData::get_font_style); + ClassDB::bind_method(D_METHOD("set_multichannel_signed_distance_field", "msdf"), &FontData::set_multichannel_signed_distance_field); ClassDB::bind_method(D_METHOD("is_multichannel_signed_distance_field"), &FontData::is_multichannel_signed_distance_field); @@ -190,6 +199,15 @@ bool FontData::_set(const StringName &p_name, const Variant &p_value) { } else if (tokens[0] == "antialiased") { set_antialiased(p_value); return true; + } else if (tokens[0] == "font_name") { + set_font_name(p_value); + return true; + } else if (tokens[0] == "style_name") { + set_font_style_name(p_value); + return true; + } else if (tokens[0] == "font_style") { + set_font_style(p_value); + return true; } else if (tokens[0] == "multichannel_signed_distance_field") { set_multichannel_signed_distance_field(p_value); return true; @@ -295,6 +313,15 @@ bool FontData::_get(const StringName &p_name, Variant &r_ret) const { } else if (tokens[0] == "antialiased") { r_ret = is_antialiased(); return true; + } else if (tokens[0] == "font_name") { + r_ret = get_font_name(); + return true; + } else if (tokens[0] == "style_name") { + r_ret = get_font_style_name(); + return true; + } else if (tokens[0] == "font_style") { + r_ret = get_font_style(); + return true; } else if (tokens[0] == "multichannel_signed_distance_field") { r_ret = is_multichannel_signed_distance_field(); return true; @@ -394,6 +421,9 @@ bool FontData::_get(const StringName &p_name, Variant &r_ret) const { void FontData::_get_property_list(List<PropertyInfo> *p_list) const { p_list->push_back(PropertyInfo(Variant::PACKED_BYTE_ARRAY, "data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE)); + p_list->push_back(PropertyInfo(Variant::STRING, "font_name", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE)); + p_list->push_back(PropertyInfo(Variant::STRING, "style_name", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE)); + p_list->push_back(PropertyInfo(Variant::INT, "font_style", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE)); p_list->push_back(PropertyInfo(Variant::BOOL, "antialiased", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE)); p_list->push_back(PropertyInfo(Variant::BOOL, "multichannel_signed_distance_field", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE)); p_list->push_back(PropertyInfo(Variant::INT, "msdf_pixel_range", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE)); @@ -510,6 +540,36 @@ PackedByteArray FontData::get_data() const { return data; } +void FontData::set_font_name(const String &p_name) { + _ensure_rid(0); + TS->font_set_name(cache[0], p_name); +} + +String FontData::get_font_name() const { + _ensure_rid(0); + return TS->font_get_name(cache[0]); +} + +void FontData::set_font_style_name(const String &p_name) { + _ensure_rid(0); + TS->font_set_style_name(cache[0], p_name); +} + +String FontData::get_font_style_name() const { + _ensure_rid(0); + return TS->font_get_style_name(cache[0]); +} + +void FontData::set_font_style(uint32_t p_style) { + _ensure_rid(0); + TS->font_set_style(cache[0], p_style); +} + +uint32_t FontData::get_font_style() const { + _ensure_rid(0); + return TS->font_get_style(cache[0]); +} + void FontData::set_antialiased(bool p_antialiased) { if (antialiased != p_antialiased) { antialiased = p_antialiased; diff --git a/scene/resources/font.h b/scene/resources/font.h index e1f1f6d742..4d9ee72c84 100644 --- a/scene/resources/font.h +++ b/scene/resources/font.h @@ -79,6 +79,15 @@ public: virtual PackedByteArray get_data() const; // Common properties. + virtual void set_font_name(const String &p_name); + virtual String get_font_name() const; + + virtual void set_font_style_name(const String &p_name); + virtual String get_font_style_name() const; + + virtual void set_font_style(uint32_t p_style); + virtual uint32_t get_font_style() const; + virtual void set_antialiased(bool p_antialiased); virtual bool is_antialiased() const; diff --git a/scene/resources/gradient.cpp b/scene/resources/gradient.cpp index 7b9b942142..4559b4ce0a 100644 --- a/scene/resources/gradient.cpp +++ b/scene/resources/gradient.cpp @@ -51,6 +51,8 @@ void Gradient::_bind_methods() { ClassDB::bind_method(D_METHOD("set_offset", "point", "offset"), &Gradient::set_offset); ClassDB::bind_method(D_METHOD("get_offset", "point"), &Gradient::get_offset); + ClassDB::bind_method(D_METHOD("reverse"), &Gradient::reverse); + ClassDB::bind_method(D_METHOD("set_color", "point", "color"), &Gradient::set_color); ClassDB::bind_method(D_METHOD("get_color", "point"), &Gradient::get_color); @@ -64,8 +66,18 @@ void Gradient::_bind_methods() { ClassDB::bind_method(D_METHOD("set_colors", "colors"), &Gradient::set_colors); ClassDB::bind_method(D_METHOD("get_colors"), &Gradient::get_colors); + ClassDB::bind_method(D_METHOD("set_interpolation_mode", "interpolation_mode"), &Gradient::set_interpolation_mode); + ClassDB::bind_method(D_METHOD("get_interpolation_mode"), &Gradient::get_interpolation_mode); + + ADD_PROPERTY(PropertyInfo(Variant::INT, "interpolation_mode", PROPERTY_HINT_ENUM, "Linear,Constant,Cubic"), "set_interpolation_mode", "get_interpolation_mode"); + + ADD_GROUP("Raw data", ""); ADD_PROPERTY(PropertyInfo(Variant::PACKED_FLOAT32_ARRAY, "offsets"), "set_offsets", "get_offsets"); ADD_PROPERTY(PropertyInfo(Variant::PACKED_COLOR_ARRAY, "colors"), "set_colors", "get_colors"); + + BIND_ENUM_CONSTANT(GRADIENT_INTERPOLATE_LINEAR); + BIND_ENUM_CONSTANT(GRADIENT_INTERPOLATE_CONSTANT); + BIND_ENUM_CONSTANT(GRADIENT_INTERPOLATE_CUBIC); } Vector<float> Gradient::get_offsets() const { @@ -86,6 +98,15 @@ Vector<Color> Gradient::get_colors() const { return colors; } +void Gradient::set_interpolation_mode(Gradient::InterpolationMode p_interp_mode) { + interpolation_mode = p_interp_mode; + emit_signal(CoreStringNames::get_singleton()->changed); +} + +Gradient::InterpolationMode Gradient::get_interpolation_mode() { + return interpolation_mode; +} + void Gradient::set_offsets(const Vector<float> &p_offsets) { points.resize(p_offsets.size()); for (int i = 0; i < points.size(); i++) { @@ -127,6 +148,15 @@ void Gradient::remove_point(int p_index) { emit_signal(CoreStringNames::get_singleton()->changed); } +void Gradient::reverse() { + for (int i = 0; i < points.size(); i++) { + points.write[i].offset = 1.0 - points[i].offset; + } + + _update_sorting(); + emit_signal(CoreStringNames::get_singleton()->changed); +} + void Gradient::set_points(Vector<Gradient::Point> &p_points) { points = p_points; is_sorted = false; diff --git a/scene/resources/gradient.h b/scene/resources/gradient.h index cf5b179c45..eb438d0bba 100644 --- a/scene/resources/gradient.h +++ b/scene/resources/gradient.h @@ -38,6 +38,12 @@ class Gradient : public Resource { OBJ_SAVE_TYPE(Gradient); public: + enum InterpolationMode { + GRADIENT_INTERPOLATE_LINEAR, + GRADIENT_INTERPOLATE_CONSTANT, + GRADIENT_INTERPOLATE_CUBIC, + }; + struct Point { float offset = 0.0; Color color; @@ -49,6 +55,8 @@ public: private: Vector<Point> points; bool is_sorted = true; + InterpolationMode interpolation_mode = GRADIENT_INTERPOLATE_LINEAR; + _FORCE_INLINE_ void _update_sorting() { if (!is_sorted) { points.sort(); @@ -65,9 +73,9 @@ public: void add_point(float p_offset, const Color &p_color); void remove_point(int p_index); - void set_points(Vector<Point> &p_points); Vector<Point> &get_points(); + void reverse(); void set_offset(int pos, const float offset); float get_offset(int pos); @@ -81,6 +89,13 @@ public: void set_colors(const Vector<Color> &p_colors); Vector<Color> get_colors() const; + void set_interpolation_mode(InterpolationMode p_interp_mode); + InterpolationMode get_interpolation_mode(); + + _FORCE_INLINE_ float cubic_interpolate(float p0, float p1, float p2, float p3, float x) { + return p1 + 0.5 * x * (p2 - p0 + x * (2.0 * p0 - 5.0 * p1 + 4.0 * p2 - p3 + x * (3.0 * (p1 - p2) + p3 - p0))); + } + _FORCE_INLINE_ Color get_color_at_offset(float p_offset) { if (points.is_empty()) { return Color(0, 0, 0, 1); @@ -88,7 +103,7 @@ public: _update_sorting(); - //binary search + // Binary search. int low = 0; int high = points.size() - 1; int middle = 0; @@ -111,7 +126,7 @@ public: } } - //return interpolated value + // Return interpolated value. if (points[middle].offset > p_offset) { middle--; } @@ -125,10 +140,44 @@ public: } const Point &pointFirst = points[first]; const Point &pointSecond = points[second]; - return pointFirst.color.lerp(pointSecond.color, (p_offset - pointFirst.offset) / (pointSecond.offset - pointFirst.offset)); + + switch (interpolation_mode) { + case GRADIENT_INTERPOLATE_LINEAR: { + return pointFirst.color.lerp(pointSecond.color, (p_offset - pointFirst.offset) / (pointSecond.offset - pointFirst.offset)); + } break; + case GRADIENT_INTERPOLATE_CONSTANT: { + return pointFirst.color; + } break; + case GRADIENT_INTERPOLATE_CUBIC: { + int p0 = first - 1; + int p3 = second + 1; + if (p3 >= points.size()) { + p3 = second; + } + if (p0 < 0) { + p0 = first; + } + const Point &pointP0 = points[p0]; + const Point &pointP3 = points[p3]; + + float x = (p_offset - pointFirst.offset) / (pointSecond.offset - pointFirst.offset); + float r = cubic_interpolate(pointP0.color.r, pointFirst.color.r, pointSecond.color.r, pointP3.color.r, x); + float g = cubic_interpolate(pointP0.color.g, pointFirst.color.g, pointSecond.color.g, pointP3.color.g, x); + float b = cubic_interpolate(pointP0.color.b, pointFirst.color.b, pointSecond.color.b, pointP3.color.b, x); + float a = cubic_interpolate(pointP0.color.a, pointFirst.color.a, pointSecond.color.a, pointP3.color.a, x); + + return Color(r, g, b, a); + } break; + default: { + // Fallback to linear interpolation. + return pointFirst.color.lerp(pointSecond.color, (p_offset - pointFirst.offset) / (pointSecond.offset - pointFirst.offset)); + } + } } int get_points_count() const; }; +VARIANT_ENUM_CAST(Gradient::InterpolationMode); + #endif // GRADIENT_H diff --git a/scene/resources/packed_scene.cpp b/scene/resources/packed_scene.cpp index 60cda637ca..3f4765d20f 100644 --- a/scene/resources/packed_scene.cpp +++ b/scene/resources/packed_scene.cpp @@ -34,10 +34,12 @@ #include "core/config/project_settings.h" #include "core/core_string_names.h" #include "core/io/resource_loader.h" +#include "editor/editor_inspector.h" #include "scene/2d/node_2d.h" #include "scene/3d/node_3d.h" #include "scene/gui/control.h" #include "scene/main/instance_placeholder.h" +#include "scene/property_utils.h" #define PACKED_SCENE_VERSION 2 @@ -45,6 +47,30 @@ bool SceneState::can_instantiate() const { return nodes.size() > 0; } +static Array _sanitize_node_pinned_properties(Node *p_node) { + if (!p_node->has_meta("_edit_pinned_properties_")) { + return Array(); + } + Array pinned = p_node->get_meta("_edit_pinned_properties_"); + if (pinned.is_empty()) { + return Array(); + } + Set<StringName> storable_properties; + p_node->get_storable_properties(storable_properties); + int i = 0; + do { + if (storable_properties.has(pinned[i])) { + i++; + } else { + pinned.remove(i); + } + } while (i < pinned.size()); + if (pinned.is_empty()) { + p_node->remove_meta("_edit_pinned_properties_"); + } + return pinned; +} + Node *SceneState::instantiate(GenEditState p_edit_state) const { // nodes where instancing failed (because something is missing) List<Node *> stray_instances; @@ -227,7 +253,7 @@ Node *SceneState::instantiate(GenEditState p_edit_state) const { } else { Node *base = i == 0 ? node : ret_nodes[0]; - if (p_edit_state == GEN_EDIT_STATE_MAIN) { + if (p_edit_state == GEN_EDIT_STATE_MAIN || p_edit_state == GEN_EDIT_STATE_MAIN_INHERITED) { //for the main scene, use the resource as is res->configure_for_local_scene(base, resources_local_to_scene); resources_local_to_scene[res] = res; @@ -289,6 +315,13 @@ Node *SceneState::instantiate(GenEditState p_edit_state) const { node->_set_owner_nocheck(owner); } } + + // we only want to deal with pinned flag if instancing as pure main (no instance, no inheriting) + if (p_edit_state == GEN_EDIT_STATE_MAIN) { + _sanitize_node_pinned_properties(node); + } else { + node->remove_meta("_edit_pinned_properties_"); + } } ret_nodes[i] = node; @@ -415,61 +448,22 @@ Error SceneState::_parse_node(Node *p_owner, Node *p_node, int p_parent_idx, Map // with the instance states, we can query for identical properties/groups // and only save what has changed - List<PackState> pack_state_stack; - - bool instantiated_by_owner = true; - - { - Node *n = p_node; - - while (n) { - if (n == p_owner) { - Ref<SceneState> state = n->get_scene_inherited_state(); - if (state.is_valid()) { - int node = state->find_node_by_path(n->get_path_to(p_node)); - if (node >= 0) { - //this one has state for this node, save - PackState ps; - ps.node = node; - ps.state = state; - pack_state_stack.push_back(ps); - instantiated_by_owner = false; - } - } - - if (p_node->get_scene_file_path() != String() && p_node->get_owner() == p_owner && instantiated_by_owner) { - if (p_node->get_scene_instance_load_placeholder()) { - //it's a placeholder, use the placeholder path - nd.instance = _vm_get_variant(p_node->get_scene_file_path(), variant_map); - nd.instance |= FLAG_INSTANCE_IS_PLACEHOLDER; - } else { - //must instance ourselves - Ref<PackedScene> instance = ResourceLoader::load(p_node->get_scene_file_path()); - if (!instance.is_valid()) { - return ERR_CANT_OPEN; - } + bool instantiated_by_owner = false; + Vector<SceneState::PackState> states_stack = PropertyUtils::get_node_states_stack(p_node, p_owner, &instantiated_by_owner); - nd.instance = _vm_get_variant(instance, variant_map); - } - } - n = nullptr; - } else { - if (n->get_scene_file_path() != String()) { - //is an instance - Ref<SceneState> state = n->get_scene_instance_state(); - if (state.is_valid()) { - int node = state->find_node_by_path(n->get_path_to(p_node)); - if (node >= 0) { - //this one has state for this node, save - PackState ps; - ps.node = node; - ps.state = state; - pack_state_stack.push_back(ps); - } - } - } - n = n->get_owner(); + if (p_node->get_scene_file_path() != String() && p_node->get_owner() == p_owner && instantiated_by_owner) { + if (p_node->get_scene_instance_load_placeholder()) { + //it's a placeholder, use the placeholder path + nd.instance = _vm_get_variant(p_node->get_scene_file_path(), variant_map); + nd.instance |= FLAG_INSTANCE_IS_PLACEHOLDER; + } else { + //must instance ourselves + Ref<PackedScene> instance = ResourceLoader::load(p_node->get_scene_file_path()); + if (!instance.is_valid()) { + return ERR_CANT_OPEN; } + + nd.instance = _vm_get_variant(instance, variant_map); } } @@ -478,88 +472,37 @@ Error SceneState::_parse_node(Node *p_owner, Node *p_node, int p_parent_idx, Map List<PropertyInfo> plist; p_node->get_property_list(&plist); - StringName type = p_node->get_class(); - Ref<Script> script = p_node->get_script(); - if (Engine::get_singleton()->is_editor_hint() && script.is_valid()) { - // Should be called in the editor only and not at runtime, - // otherwise it can cause problems because of missing instance state support. - script->update_exports(); - } + Array pinned_props = _sanitize_node_pinned_properties(p_node); for (const PropertyInfo &E : plist) { if (!(E.usage & PROPERTY_USAGE_STORAGE)) { continue; } - String name = E.name; - Variant value = p_node->get(E.name); - - bool isdefault = false; - Variant default_value = ClassDB::class_get_default_property_value(type, name); - - if (default_value.get_type() != Variant::NIL) { - isdefault = bool(Variant::evaluate(Variant::OP_EQUAL, value, default_value)); - } + Variant forced_value; - if (!isdefault && script.is_valid() && script->get_property_default_value(name, default_value)) { - isdefault = bool(Variant::evaluate(Variant::OP_EQUAL, value, default_value)); - } - // the version above makes more sense, because it does not rely on placeholder or usage flag - // in the script, just the default value function. - // if (E.usage & PROPERTY_USAGE_SCRIPT_DEFAULT_VALUE) { - // isdefault = true; //is script default value - // } - - if (pack_state_stack.size()) { - // we are on part of an instantiated subscene - // or part of instantiated scene. - // only save what has been changed - // only save changed properties in instance - - if ((E.usage & PROPERTY_USAGE_NO_INSTANCE_STATE) || E.name == "__meta__") { - //property has requested that no instance state is saved, sorry - //also, meta won't be overridden or saved + // If instance or inheriting, not saving if property requested so, or it's meta + if (states_stack.size()) { + if ((E.usage & PROPERTY_USAGE_NO_INSTANCE_STATE)) { continue; } - - bool exists = false; - Variant original; - - for (List<PackState>::Element *F = pack_state_stack.back(); F; F = F->prev()) { - //check all levels of pack to see if the property exists somewhere - const PackState &ps = F->get(); - - original = ps.state->get_property_value(ps.node, E.name, exists); - if (exists) { - break; - } - } - - if (exists) { - //check if already exists and did not change - if (value.get_type() == Variant::FLOAT && original.get_type() == Variant::FLOAT) { - //this must be done because, as some scenes save as text, there might be a tiny difference in floats due to numerical error - float a = value; - float b = original; - - if (Math::is_equal_approx(a, b)) { - continue; - } - } else if (bool(Variant::evaluate(Variant::OP_EQUAL, value, original))) { - continue; + // Meta is normally not saved in instances/inherited (see GH-12838), but we need to save the pinned list + if (E.name == "__meta__") { + if (pinned_props.size()) { + Dictionary meta_override; + meta_override["_edit_pinned_properties_"] = pinned_props; + forced_value = meta_override; } } + } - if (!exists && isdefault) { - //does not exist in original node, but it's the default value - //so safe to skip too. - continue; - } + StringName name = E.name; + Variant value = forced_value.get_type() == Variant::NIL ? p_node->get(name) : forced_value; - } else { - if (isdefault) { - //it's the default value, no point in saving it + if (!pinned_props.has(name) && forced_value.get_type() == Variant::NIL) { + Variant default_value = PropertyUtils::get_property_default_value(p_node, name, &states_stack, true); + if (!PropertyUtils::is_property_value_different(value, default_value)) { continue; } } @@ -585,10 +528,9 @@ Error SceneState::_parse_node(Node *p_owner, Node *p_node, int p_parent_idx, Map */ bool skip = false; - for (const PackState &F : pack_state_stack) { + for (const SceneState::PackState &ia : states_stack) { //check all levels of pack to see if the group was added somewhere - const PackState &ps = F; - if (ps.state->is_node_in_group(ps.node, gi.name)) { + if (ia.state->is_node_in_group(ia.node, gi.name)) { skip = true; break; } @@ -618,7 +560,7 @@ Error SceneState::_parse_node(Node *p_owner, Node *p_node, int p_parent_idx, Map // Save the right type. If this node was created by an instance // then flag that the node should not be created but reused - if (pack_state_stack.is_empty() && !is_editable_instance) { + if (states_stack.is_empty() && !is_editable_instance) { //this node is not part of an instancing process, so save the type nd.type = _nm_get_string(p_node->get_class(), name_map); } else { @@ -635,7 +577,7 @@ Error SceneState::_parse_node(Node *p_owner, Node *p_node, int p_parent_idx, Map bool save_node = nd.properties.size() || nd.groups.size(); // some local properties or groups exist save_node = save_node || p_node == p_owner; // owner is always saved - save_node = save_node || (p_node->get_owner() == p_owner && instantiated_by_owner); //part of scene and not instantiated + save_node = save_node || (p_node->get_owner() == p_owner && instantiated_by_owner); //part of scene and not instanced int idx = nodes.size(); int parent_node = NO_PARENT_SAVED; @@ -932,7 +874,7 @@ void SceneState::clear() { base_scene_idx = -1; } -Ref<SceneState> SceneState::_get_base_scene_state() const { +Ref<SceneState> SceneState::get_base_scene_state() const { if (base_scene_idx >= 0) { Ref<PackedScene> ps = variants[base_scene_idx]; if (ps.is_valid()) { @@ -947,8 +889,8 @@ int SceneState::find_node_by_path(const NodePath &p_node) const { ERR_FAIL_COND_V_MSG(node_path_cache.size() == 0, -1, "This operation requires the node cache to have been built."); if (!node_path_cache.has(p_node)) { - if (_get_base_scene_state().is_valid()) { - int idx = _get_base_scene_state()->find_node_by_path(p_node); + if (get_base_scene_state().is_valid()) { + int idx = get_base_scene_state()->find_node_by_path(p_node); if (idx != -1) { int rkey = _find_base_scene_node_remap_key(idx); if (rkey == -1) { @@ -963,11 +905,11 @@ int SceneState::find_node_by_path(const NodePath &p_node) const { int nid = node_path_cache[p_node]; - if (_get_base_scene_state().is_valid() && !base_scene_node_remap.has(nid)) { + if (get_base_scene_state().is_valid() && !base_scene_node_remap.has(nid)) { //for nodes that _do_ exist in current scene, still try to look for //the node in the instantiated scene, as a property may be missing //from the local one - int idx = _get_base_scene_state()->find_node_by_path(p_node); + int idx = get_base_scene_state()->find_node_by_path(p_node); if (idx != -1) { base_scene_node_remap[nid] = idx; } @@ -1007,7 +949,7 @@ Variant SceneState::get_property_value(int p_node, const StringName &p_property, //property not found, try on instance if (base_scene_node_remap.has(p_node)) { - return _get_base_scene_state()->get_property_value(base_scene_node_remap[p_node], p_property, found); + return get_base_scene_state()->get_property_value(base_scene_node_remap[p_node], p_property, found); } return Variant(); @@ -1026,7 +968,7 @@ bool SceneState::is_node_in_group(int p_node, const StringName &p_group) const { } if (base_scene_node_remap.has(p_node)) { - return _get_base_scene_state()->is_node_in_group(base_scene_node_remap[p_node], p_group); + return get_base_scene_state()->is_node_in_group(base_scene_node_remap[p_node], p_group); } return false; @@ -1065,7 +1007,7 @@ bool SceneState::is_connection(int p_node, const StringName &p_signal, int p_to_ } if (base_scene_node_remap.has(p_node) && base_scene_node_remap.has(p_to_node)) { - return _get_base_scene_state()->is_connection(base_scene_node_remap[p_node], p_signal, base_scene_node_remap[p_to_node], p_to_method); + return get_base_scene_state()->is_connection(base_scene_node_remap[p_node], p_signal, base_scene_node_remap[p_to_node], p_to_method); } return false; @@ -1488,7 +1430,7 @@ bool SceneState::has_connection(const NodePath &p_node_from, const StringName &p } } - ss = ss->_get_base_scene_state(); + ss = ss->get_base_scene_state(); } while (ss.is_valid()); return false; @@ -1610,6 +1552,7 @@ void SceneState::_bind_methods() { BIND_ENUM_CONSTANT(GEN_EDIT_STATE_DISABLED); BIND_ENUM_CONSTANT(GEN_EDIT_STATE_INSTANCE); BIND_ENUM_CONSTANT(GEN_EDIT_STATE_MAIN); + BIND_ENUM_CONSTANT(GEN_EDIT_STATE_MAIN_INHERITED); } SceneState::SceneState() { @@ -1651,7 +1594,7 @@ Node *PackedScene::instantiate(GenEditState p_edit_state) const { s->set_scene_instance_state(state); } - if (get_path() != "" && get_path().find("::") == -1) { + if (!is_built_in()) { s->set_scene_file_path(get_path()); } @@ -1701,6 +1644,7 @@ void PackedScene::_bind_methods() { BIND_ENUM_CONSTANT(GEN_EDIT_STATE_DISABLED); BIND_ENUM_CONSTANT(GEN_EDIT_STATE_INSTANCE); BIND_ENUM_CONSTANT(GEN_EDIT_STATE_MAIN); + BIND_ENUM_CONSTANT(GEN_EDIT_STATE_MAIN_INHERITED); } PackedScene::PackedScene() { diff --git a/scene/resources/packed_scene.h b/scene/resources/packed_scene.h index 55708f7914..a03da558e4 100644 --- a/scene/resources/packed_scene.h +++ b/scene/resources/packed_scene.h @@ -69,11 +69,6 @@ class SceneState : public RefCounted { Vector<int> groups; }; - struct PackState { - Ref<SceneState> state; - int node = -1; - }; - Vector<NodeData> nodes; struct ConnectionData { @@ -94,8 +89,6 @@ class SceneState : public RefCounted { uint64_t last_modified_time = 0; - _FORCE_INLINE_ Ref<SceneState> _get_base_scene_state() const; - static bool disable_placeholders; Vector<String> _get_node_groups(int p_idx) const; @@ -117,6 +110,12 @@ public: GEN_EDIT_STATE_DISABLED, GEN_EDIT_STATE_INSTANCE, GEN_EDIT_STATE_MAIN, + GEN_EDIT_STATE_MAIN_INHERITED, + }; + + struct PackState { + Ref<SceneState> state; + int node = -1; }; static void set_disable_placeholders(bool p_disable); @@ -139,6 +138,8 @@ public: bool can_instantiate() const; Node *instantiate(GenEditState p_edit_state) const; + Ref<SceneState> get_base_scene_state() const; + //unbuild API int get_node_count() const; @@ -207,6 +208,7 @@ public: GEN_EDIT_STATE_DISABLED, GEN_EDIT_STATE_INSTANCE, GEN_EDIT_STATE_MAIN, + GEN_EDIT_STATE_MAIN_INHERITED, }; Error pack(Node *p_scene); diff --git a/scene/resources/particles_material.cpp b/scene/resources/particles_material.cpp index d9ec0bfd69..e77f5a0be7 100644 --- a/scene/resources/particles_material.cpp +++ b/scene/resources/particles_material.cpp @@ -1413,7 +1413,7 @@ void ParticlesMaterial::_bind_methods() { ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "scale_curve", PROPERTY_HINT_RESOURCE_TYPE, "CurveTexture,CurveXYZTexture"), "set_param_texture", "get_param_texture", PARAM_SCALE); ADD_GROUP("Color", ""); ADD_PROPERTY(PropertyInfo(Variant::COLOR, "color"), "set_color", "get_color"); - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "color_ramp", PROPERTY_HINT_RESOURCE_TYPE, "GradientTexture"), "set_color_ramp", "get_color_ramp"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "color_ramp", PROPERTY_HINT_RESOURCE_TYPE, "GradientTexture1D"), "set_color_ramp", "get_color_ramp"); ADD_GROUP("Hue Variation", "hue_"); ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "hue_variation_min", PROPERTY_HINT_RANGE, "-1,1,0.01"), "set_param_min", "get_param_min", PARAM_HUE_VARIATION); diff --git a/scene/resources/resource_format_text.cpp b/scene/resources/resource_format_text.cpp index 77d915aef9..ea3b72af1b 100644 --- a/scene/resources/resource_format_text.cpp +++ b/scene/resources/resource_format_text.cpp @@ -1492,7 +1492,7 @@ String ResourceFormatSaverTextInstance::_write_resource(const RES &res) { } else { if (internal_resources.has(res)) { return "SubResource( \"" + internal_resources[res] + "\" )"; - } else if (res->get_path().length() && res->get_path().find("::") == -1) { + } else if (!res->is_built_in()) { if (res->get_path() == local_path) { //circular reference attempt return "null"; } @@ -1515,7 +1515,7 @@ void ResourceFormatSaverTextInstance::_find_resources(const Variant &p_variant, return; } - if (!p_main && (!bundle_resources) && res->get_path().length() && res->get_path().find("::") == -1) { + if (!p_main && (!bundle_resources) && !res->is_built_in()) { if (res->get_path() == local_path) { ERR_PRINT("Circular reference to resource being saved found: '" + local_path + "' will be null next time it's loaded."); return; @@ -1728,7 +1728,7 @@ Error ResourceFormatSaverTextInstance::save(const String &p_path, const RES &p_r for (List<RES>::Element *E = saved_resources.front(); E; E = E->next()) { RES res = E->get(); - if (E->next() && (res->get_path() == "" || res->get_path().find("::") != -1)) { + if (E->next() && res->is_built_in()) { if (res->get_scene_unique_id() != "") { if (used_unique_ids.has(res->get_scene_unique_id())) { res->set_scene_unique_id(""); // Repeated. diff --git a/scene/resources/shader.cpp b/scene/resources/shader.cpp index 9fab5791d6..84fa07e4f4 100644 --- a/scene/resources/shader.cpp +++ b/scene/resources/shader.cpp @@ -97,28 +97,36 @@ RID Shader::get_rid() const { return shader; } -void Shader::set_default_texture_param(const StringName &p_param, const Ref<Texture2D> &p_texture) { +void Shader::set_default_texture_param(const StringName &p_param, const Ref<Texture2D> &p_texture, int p_index) { if (p_texture.is_valid()) { - default_textures[p_param] = p_texture; - RS::get_singleton()->shader_set_default_texture_param(shader, p_param, p_texture->get_rid()); + if (!default_textures.has(p_param)) { + default_textures[p_param] = Map<int, Ref<Texture2D>>(); + } + default_textures[p_param][p_index] = p_texture; + RS::get_singleton()->shader_set_default_texture_param(shader, p_param, p_texture->get_rid(), p_index); } else { - default_textures.erase(p_param); - RS::get_singleton()->shader_set_default_texture_param(shader, p_param, RID()); + if (default_textures.has(p_param) && default_textures[p_param].has(p_index)) { + default_textures[p_param].erase(p_index); + + if (default_textures[p_param].is_empty()) { + default_textures.erase(p_param); + } + } + RS::get_singleton()->shader_set_default_texture_param(shader, p_param, RID(), p_index); } emit_changed(); } -Ref<Texture2D> Shader::get_default_texture_param(const StringName &p_param) const { - if (default_textures.has(p_param)) { - return default_textures[p_param]; - } else { - return Ref<Texture2D>(); +Ref<Texture2D> Shader::get_default_texture_param(const StringName &p_param, int p_index) const { + if (default_textures.has(p_param) && default_textures[p_param].has(p_index)) { + return default_textures[p_param][p_index]; } + return Ref<Texture2D>(); } void Shader::get_default_texture_param_list(List<StringName> *r_textures) const { - for (const KeyValue<StringName, Ref<Texture2D>> &E : default_textures) { + for (const KeyValue<StringName, Map<int, Ref<Texture2D>>> &E : default_textures) { r_textures->push_back(E.key); } } @@ -140,8 +148,8 @@ void Shader::_bind_methods() { ClassDB::bind_method(D_METHOD("set_code", "code"), &Shader::set_code); ClassDB::bind_method(D_METHOD("get_code"), &Shader::get_code); - ClassDB::bind_method(D_METHOD("set_default_texture_param", "param", "texture"), &Shader::set_default_texture_param); - ClassDB::bind_method(D_METHOD("get_default_texture_param", "param"), &Shader::get_default_texture_param); + ClassDB::bind_method(D_METHOD("set_default_texture_param", "param", "texture", "index"), &Shader::set_default_texture_param, DEFVAL(0)); + ClassDB::bind_method(D_METHOD("get_default_texture_param", "param", "index"), &Shader::get_default_texture_param, DEFVAL(0)); ClassDB::bind_method(D_METHOD("has_param", "name"), &Shader::has_param); diff --git a/scene/resources/shader.h b/scene/resources/shader.h index c0dc07b403..c688dc1bab 100644 --- a/scene/resources/shader.h +++ b/scene/resources/shader.h @@ -59,7 +59,7 @@ private: // conversion fast and save memory. mutable bool params_cache_dirty = true; mutable Map<StringName, StringName> params_cache; //map a shader param to a material param.. - Map<StringName, Ref<Texture2D>> default_textures; + Map<StringName, Map<int, Ref<Texture2D>>> default_textures; virtual void _update_shader() const; //used for visual shader protected: @@ -75,8 +75,8 @@ public: void get_param_list(List<PropertyInfo> *p_params) const; bool has_param(const StringName &p_param) const; - void set_default_texture_param(const StringName &p_param, const Ref<Texture2D> &p_texture); - Ref<Texture2D> get_default_texture_param(const StringName &p_param) const; + void set_default_texture_param(const StringName &p_param, const Ref<Texture2D> &p_texture, int p_index = 0); + Ref<Texture2D> get_default_texture_param(const StringName &p_param, int p_index = 0) const; void get_default_texture_param_list(List<StringName> *r_textures) const; virtual bool is_text_shader() const; diff --git a/scene/resources/skeleton_modification_2d_ccdik.cpp b/scene/resources/skeleton_modification_2d_ccdik.cpp index 6eab8d4afe..bea42109cb 100644 --- a/scene/resources/skeleton_modification_2d_ccdik.cpp +++ b/scene/resources/skeleton_modification_2d_ccdik.cpp @@ -205,8 +205,8 @@ void SkeletonModification2DCCDIK::_execute_ccdik_joint(int p_joint_idx, Node2D * } else { // How to rotate from the tip: get the difference of rotation needed from the tip to the target, from the perspective of the joint. // Because we are only using the offset, we do not need to account for the bone angle of the Bone2D node. - float joint_to_tip = operation_transform.get_origin().angle_to_point(p_tip->get_global_position()); - float joint_to_target = operation_transform.get_origin().angle_to_point(p_target->get_global_position()); + float joint_to_tip = p_tip->get_global_position().angle_to_point(operation_transform.get_origin()); + float joint_to_target = p_target->get_global_position().angle_to_point(operation_transform.get_origin()); operation_transform.set_rotation( operation_transform.get_rotation() + (joint_to_target - joint_to_tip)); } diff --git a/scene/resources/skeleton_modification_2d_jiggle.cpp b/scene/resources/skeleton_modification_2d_jiggle.cpp index 84abc9d020..31045455a3 100644 --- a/scene/resources/skeleton_modification_2d_jiggle.cpp +++ b/scene/resources/skeleton_modification_2d_jiggle.cpp @@ -194,9 +194,13 @@ void SkeletonModification2DJiggle::_execute_jiggle_joint(int p_joint_idx, Node2D PhysicsDirectSpaceState2D *space_state = PhysicsServer2D::get_singleton()->space_get_direct_state(world_2d->get_space()); PhysicsDirectSpaceState2D::RayResult ray_result; + PhysicsDirectSpaceState2D::RayParameters ray_params; + ray_params.from = operation_bone_trans.get_origin(); + ray_params.to = jiggle_data_chain[p_joint_idx].dynamic_position; + ray_params.collision_mask = collision_mask; + // Add exception support? - bool ray_hit = space_state->intersect_ray(operation_bone_trans.get_origin(), jiggle_data_chain[p_joint_idx].dynamic_position, - ray_result, Set<RID>(), collision_mask); + bool ray_hit = space_state->intersect_ray(ray_params, ray_result); if (ray_hit) { jiggle_data_chain.write[p_joint_idx].dynamic_position = jiggle_data_chain[p_joint_idx].last_noncollision_position; diff --git a/scene/resources/skeleton_modification_3d_jiggle.cpp b/scene/resources/skeleton_modification_3d_jiggle.cpp index a6bcb0176a..2535f2b987 100644 --- a/scene/resources/skeleton_modification_3d_jiggle.cpp +++ b/scene/resources/skeleton_modification_3d_jiggle.cpp @@ -206,8 +206,12 @@ void SkeletonModification3DJiggle::_execute_jiggle_joint(int p_joint_idx, Node3D Transform3D new_bone_trans_world = stack->skeleton->global_pose_to_world_transform(new_bone_trans); Transform3D dynamic_position_world = stack->skeleton->global_pose_to_world_transform(Transform3D(Basis(), jiggle_data_chain[p_joint_idx].dynamic_position)); - bool ray_hit = space_state->intersect_ray(new_bone_trans_world.origin, dynamic_position_world.get_origin(), - ray_result, Set<RID>(), collision_mask); + PhysicsDirectSpaceState3D::RayParameters ray_params; + ray_params.from = new_bone_trans_world.origin; + ray_params.to = dynamic_position_world.get_origin(); + ray_params.collision_mask = collision_mask; + + bool ray_hit = space_state->intersect_ray(ray_params, ray_result); if (ray_hit) { jiggle_data_chain[p_joint_idx].dynamic_position = jiggle_data_chain[p_joint_idx].last_noncollision_position; diff --git a/scene/resources/skeleton_modification_stack_2d.cpp b/scene/resources/skeleton_modification_stack_2d.cpp index db9fe62b4d..25c3e9d2ef 100644 --- a/scene/resources/skeleton_modification_stack_2d.cpp +++ b/scene/resources/skeleton_modification_stack_2d.cpp @@ -195,6 +195,7 @@ void SkeletonModificationStack2D::set_modification(int p_mod_idx, Ref<SkeletonMo } void SkeletonModificationStack2D::set_modification_count(int p_count) { + ERR_FAIL_COND_MSG(p_count < 0, "Modification count cannot be less than zero."); modifications.resize(p_count); notify_property_list_changed(); diff --git a/scene/resources/skeleton_modification_stack_3d.cpp b/scene/resources/skeleton_modification_stack_3d.cpp index c03210cf48..301811f0b6 100644 --- a/scene/resources/skeleton_modification_stack_3d.cpp +++ b/scene/resources/skeleton_modification_stack_3d.cpp @@ -149,6 +149,7 @@ void SkeletonModificationStack3D::set_modification(int p_mod_idx, Ref<SkeletonMo } void SkeletonModificationStack3D::set_modification_count(int p_count) { + ERR_FAIL_COND_MSG(p_count < 0, "Modification count cannot be less than zero."); modifications.resize(p_count); notify_property_list_changed(); } diff --git a/scene/resources/text_paragraph.cpp b/scene/resources/text_paragraph.cpp index fae1de94d3..1b7fc64267 100644 --- a/scene/resources/text_paragraph.cpp +++ b/scene/resources/text_paragraph.cpp @@ -38,6 +38,11 @@ void TextParagraph::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::INT, "direction", PROPERTY_HINT_ENUM, "Auto,Light-to-right,Right-to-left"), "set_direction", "get_direction"); + ClassDB::bind_method(D_METHOD("set_custom_punctuation", "custom_punctuation"), &TextParagraph::set_custom_punctuation); + ClassDB::bind_method(D_METHOD("get_custom_punctuation"), &TextParagraph::get_custom_punctuation); + + ADD_PROPERTY(PropertyInfo(Variant::STRING, "custom_punctuation"), "set_custom_punctuation", "get_custom_punctuation"); + ClassDB::bind_method(D_METHOD("set_orientation", "orientation"), &TextParagraph::set_orientation); ClassDB::bind_method(D_METHOD("get_orientation"), &TextParagraph::get_orientation); @@ -304,6 +309,15 @@ TextServer::Direction TextParagraph::get_direction() const { return TS->shaped_text_get_direction(rid); } +void TextParagraph::set_custom_punctuation(const String &p_punct) { + TS->shaped_text_set_custom_punctuation(rid, p_punct); + lines_dirty = true; +} + +String TextParagraph::get_custom_punctuation() const { + return TS->shaped_text_get_custom_punctuation(rid); +} + void TextParagraph::set_orientation(TextServer::Orientation p_orientation) { TS->shaped_text_set_orientation(rid, p_orientation); TS->shaped_text_set_orientation(dropcap_rid, p_orientation); diff --git a/scene/resources/text_paragraph.h b/scene/resources/text_paragraph.h index 701c9a17cd..4c4af43d14 100644 --- a/scene/resources/text_paragraph.h +++ b/scene/resources/text_paragraph.h @@ -96,6 +96,9 @@ public: void set_bidi_override(const Array &p_override); + void set_custom_punctuation(const String &p_punct); + String get_custom_punctuation() const; + bool set_dropcap(const String &p_text, const Ref<Font> &p_fonts, int p_size, const Rect2 &p_dropcap_margins = Rect2(), const Dictionary &p_opentype_features = Dictionary(), const String &p_language = ""); void clear_dropcap(); diff --git a/scene/resources/texture.cpp b/scene/resources/texture.cpp index 485074e283..311bd9524b 100644 --- a/scene/resources/texture.cpp +++ b/scene/resources/texture.cpp @@ -1729,53 +1729,53 @@ CurveXYZTexture::~CurveXYZTexture() { ////////////////// -GradientTexture::GradientTexture() { +GradientTexture1D::GradientTexture1D() { _queue_update(); } -GradientTexture::~GradientTexture() { +GradientTexture1D::~GradientTexture1D() { if (texture.is_valid()) { RS::get_singleton()->free(texture); } } -void GradientTexture::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_gradient", "gradient"), &GradientTexture::set_gradient); - ClassDB::bind_method(D_METHOD("get_gradient"), &GradientTexture::get_gradient); +void GradientTexture1D::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_gradient", "gradient"), &GradientTexture1D::set_gradient); + ClassDB::bind_method(D_METHOD("get_gradient"), &GradientTexture1D::get_gradient); - ClassDB::bind_method(D_METHOD("set_width", "width"), &GradientTexture::set_width); + ClassDB::bind_method(D_METHOD("set_width", "width"), &GradientTexture1D::set_width); // The `get_width()` method is already exposed by the parent class Texture2D. - ClassDB::bind_method(D_METHOD("set_use_hdr", "enabled"), &GradientTexture::set_use_hdr); - ClassDB::bind_method(D_METHOD("is_using_hdr"), &GradientTexture::is_using_hdr); + ClassDB::bind_method(D_METHOD("set_use_hdr", "enabled"), &GradientTexture1D::set_use_hdr); + ClassDB::bind_method(D_METHOD("is_using_hdr"), &GradientTexture1D::is_using_hdr); - ClassDB::bind_method(D_METHOD("_update"), &GradientTexture::_update); + ClassDB::bind_method(D_METHOD("_update"), &GradientTexture1D::_update); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "gradient", PROPERTY_HINT_RESOURCE_TYPE, "Gradient"), "set_gradient", "get_gradient"); ADD_PROPERTY(PropertyInfo(Variant::INT, "width", PROPERTY_HINT_RANGE, "1,4096"), "set_width", "get_width"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_hdr"), "set_use_hdr", "is_using_hdr"); } -void GradientTexture::set_gradient(Ref<Gradient> p_gradient) { +void GradientTexture1D::set_gradient(Ref<Gradient> p_gradient) { if (p_gradient == gradient) { return; } if (gradient.is_valid()) { - gradient->disconnect(CoreStringNames::get_singleton()->changed, callable_mp(this, &GradientTexture::_update)); + gradient->disconnect(CoreStringNames::get_singleton()->changed, callable_mp(this, &GradientTexture1D::_update)); } gradient = p_gradient; if (gradient.is_valid()) { - gradient->connect(CoreStringNames::get_singleton()->changed, callable_mp(this, &GradientTexture::_update)); + gradient->connect(CoreStringNames::get_singleton()->changed, callable_mp(this, &GradientTexture1D::_update)); } _update(); emit_changed(); } -Ref<Gradient> GradientTexture::get_gradient() const { +Ref<Gradient> GradientTexture1D::get_gradient() const { return gradient; } -void GradientTexture::_queue_update() { +void GradientTexture1D::_queue_update() { if (update_pending) { return; } @@ -1784,7 +1784,7 @@ void GradientTexture::_queue_update() { call_deferred(SNAME("_update")); } -void GradientTexture::_update() { +void GradientTexture1D::_update() { update_pending = false; if (gradient.is_null()) { @@ -1839,17 +1839,17 @@ void GradientTexture::_update() { emit_changed(); } -void GradientTexture::set_width(int p_width) { +void GradientTexture1D::set_width(int p_width) { ERR_FAIL_COND(p_width <= 0); width = p_width; _queue_update(); } -int GradientTexture::get_width() const { +int GradientTexture1D::get_width() const { return width; } -void GradientTexture::set_use_hdr(bool p_enabled) { +void GradientTexture1D::set_use_hdr(bool p_enabled) { if (p_enabled == use_hdr) { return; } @@ -1858,11 +1858,11 @@ void GradientTexture::set_use_hdr(bool p_enabled) { _queue_update(); } -bool GradientTexture::is_using_hdr() const { +bool GradientTexture1D::is_using_hdr() const { return use_hdr; } -Ref<Image> GradientTexture::get_image() const { +Ref<Image> GradientTexture1D::get_image() const { if (!texture.is_valid()) { return Ref<Image>(); } @@ -2871,15 +2871,6 @@ RID CameraTexture::get_rid() const { } } -void CameraTexture::set_flags(uint32_t p_flags) { - // not supported -} - -uint32_t CameraTexture::get_flags() const { - // not supported - return 0; -} - Ref<Image> CameraTexture::get_image() const { // not (yet) supported return Ref<Image>(); diff --git a/scene/resources/texture.h b/scene/resources/texture.h index 51567124c6..5b69711de6 100644 --- a/scene/resources/texture.h +++ b/scene/resources/texture.h @@ -669,8 +669,8 @@ public: ~CurveXYZTexture(); }; -class GradientTexture : public Texture2D { - GDCLASS(GradientTexture, Texture2D); +class GradientTexture1D : public Texture2D { + GDCLASS(GradientTexture1D, Texture2D); public: struct Point { @@ -710,8 +710,8 @@ public: virtual Ref<Image> get_image() const override; - GradientTexture(); - virtual ~GradientTexture(); + GradientTexture1D(); + virtual ~GradientTexture1D(); }; class GradientTexture2D : public Texture2D { @@ -900,9 +900,6 @@ public: virtual RID get_rid() const override; virtual bool has_alpha() const override; - virtual void set_flags(uint32_t p_flags); - virtual uint32_t get_flags() const; - virtual Ref<Image> get_image() const override; void set_camera_feed_id(int p_new_id); diff --git a/scene/resources/tile_set.cpp b/scene/resources/tile_set.cpp index d5eebb1fbf..8c92445338 100644 --- a/scene/resources/tile_set.cpp +++ b/scene/resources/tile_set.cpp @@ -212,7 +212,7 @@ void TileMapPattern::_bind_methods() { ClassDB::bind_method(D_METHOD("set_cell", "coords", "source_id", "atlas_coords", "alternative_tile"), &TileMapPattern::set_cell, DEFVAL(TileSet::INVALID_SOURCE), DEFVAL(TileSetSource::INVALID_ATLAS_COORDS), DEFVAL(TileSetSource::INVALID_TILE_ALTERNATIVE)); ClassDB::bind_method(D_METHOD("has_cell", "coords"), &TileMapPattern::has_cell); - ClassDB::bind_method(D_METHOD("remove_cell", "coords"), &TileMapPattern::remove_cell); + ClassDB::bind_method(D_METHOD("remove_cell", "coords", "update_size"), &TileMapPattern::remove_cell); ClassDB::bind_method(D_METHOD("get_cell_source_id", "coords"), &TileMapPattern::get_cell_source_id); ClassDB::bind_method(D_METHOD("get_cell_atlas_coords", "coords"), &TileMapPattern::get_cell_atlas_coords); ClassDB::bind_method(D_METHOD("get_cell_alternative_tile", "coords"), &TileMapPattern::get_cell_alternative_tile); @@ -3359,6 +3359,10 @@ void TileSetAtlasSource::set_tile_set(const TileSet *p_tile_set) { } } +const TileSet *TileSetAtlasSource::get_tile_set() const { + return tile_set; +} + void TileSetAtlasSource::notify_tile_data_properties_should_change() { // Set the TileSet on all TileData. for (KeyValue<Vector2i, TileAlternativesData> &E_tile : tiles) { @@ -3522,9 +3526,18 @@ void TileSetAtlasSource::reset_state() { } void TileSetAtlasSource::set_texture(Ref<Texture2D> p_texture) { + if (texture.is_valid()) { + texture->disconnect(SNAME("changed"), callable_mp(this, &TileSetAtlasSource::_queue_update_padded_texture)); + } + texture = p_texture; + if (texture.is_valid()) { + texture->connect(SNAME("changed"), callable_mp(this, &TileSetAtlasSource::_queue_update_padded_texture)); + } + _clear_tiles_outside_texture(); + _queue_update_padded_texture(); emit_changed(); } @@ -3541,8 +3554,10 @@ void TileSetAtlasSource::set_margins(Vector2i p_margins) { } _clear_tiles_outside_texture(); + _queue_update_padded_texture(); emit_changed(); } + Vector2i TileSetAtlasSource::get_margins() const { return margins; } @@ -3556,8 +3571,10 @@ void TileSetAtlasSource::set_separation(Vector2i p_separation) { } _clear_tiles_outside_texture(); + _queue_update_padded_texture(); emit_changed(); } + Vector2i TileSetAtlasSource::get_separation() const { return separation; } @@ -3571,12 +3588,27 @@ void TileSetAtlasSource::set_texture_region_size(Vector2i p_tile_size) { } _clear_tiles_outside_texture(); + _queue_update_padded_texture(); emit_changed(); } + Vector2i TileSetAtlasSource::get_texture_region_size() const { return texture_region_size; } +void TileSetAtlasSource::set_use_texture_padding(bool p_use_padding) { + if (use_texture_padding == p_use_padding) { + return; + } + use_texture_padding = p_use_padding; + _queue_update_padded_texture(); + emit_changed(); +} + +bool TileSetAtlasSource::get_use_texture_padding() const { + return use_texture_padding; +} + Vector2i TileSetAtlasSource::get_atlas_grid_size() const { Ref<Texture2D> texture = get_texture(); if (!texture.is_valid()) { @@ -3835,6 +3867,7 @@ void TileSetAtlasSource::create_tile(const Vector2i p_atlas_coords, const Vector tiles_ids.sort(); _create_coords_mapping_cache(p_atlas_coords); + _queue_update_padded_texture(); emit_signal(SNAME("changed")); } @@ -3855,6 +3888,8 @@ void TileSetAtlasSource::remove_tile(Vector2i p_atlas_coords) { tiles_ids.erase(p_atlas_coords); tiles_ids.sort(); + _queue_update_padded_texture(); + emit_signal(SNAME("changed")); } @@ -3883,6 +3918,7 @@ void TileSetAtlasSource::set_tile_animation_columns(const Vector2i p_atlas_coord tiles[p_atlas_coords].animation_columns = p_frame_columns; _create_coords_mapping_cache(p_atlas_coords); + _queue_update_padded_texture(); emit_signal(SNAME("changed")); } @@ -3905,6 +3941,7 @@ void TileSetAtlasSource::set_tile_animation_separation(const Vector2i p_atlas_co tiles[p_atlas_coords].animation_separation = p_separation; _create_coords_mapping_cache(p_atlas_coords); + _queue_update_padded_texture(); emit_signal(SNAME("changed")); } @@ -3932,19 +3969,24 @@ void TileSetAtlasSource::set_tile_animation_frames_count(const Vector2i p_atlas_ 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); + int old_size = tiles[p_atlas_coords].animation_frames_durations.size(); + if (p_frames_count == old_size) { + return; + } + 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); + _queue_update_padded_texture(); notify_property_list_changed(); @@ -4083,6 +4125,31 @@ Vector2i TileSetAtlasSource::get_tile_effective_texture_offset(Vector2i p_atlas_ return effective_texture_offset; } +// Getters for texture and tile region (padded or not) +Ref<Texture2D> TileSetAtlasSource::get_runtime_texture() const { + if (use_texture_padding) { + return padded_texture; + } else { + return texture; + } +} + +Rect2i TileSetAtlasSource::get_runtime_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()); + + Rect2i src_rect = get_tile_texture_region(p_atlas_coords, p_frame); + if (use_texture_padding) { + const TileAlternativesData &tad = tiles[p_atlas_coords]; + Vector2i frame_coords = p_atlas_coords + (tad.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)); + Vector2i base_pos = frame_coords * (texture_region_size + Vector2i(2, 2)) + Vector2i(1, 1); + + return Rect2i(base_pos, src_rect.size); + } else { + return src_rect; + } +} + void TileSetAtlasSource::move_tile_in_atlas(Vector2i p_atlas_coords, Vector2i p_new_atlas_coords, Vector2i p_new_size) { ERR_FAIL_COND_MSG(!tiles.has(p_atlas_coords), vformat("TileSetAtlasSource has no tile at %s.", String(p_atlas_coords))); @@ -4113,6 +4180,7 @@ void TileSetAtlasSource::move_tile_in_atlas(Vector2i p_atlas_coords, Vector2i p_ tiles[new_atlas_coords].size_in_atlas = new_size; _create_coords_mapping_cache(new_atlas_coords); + _queue_update_padded_texture(); emit_signal(SNAME("changed")); } @@ -4204,11 +4272,14 @@ void TileSetAtlasSource::_bind_methods() { ClassDB::bind_method(D_METHOD("get_separation"), &TileSetAtlasSource::get_separation); ClassDB::bind_method(D_METHOD("set_texture_region_size", "texture_region_size"), &TileSetAtlasSource::set_texture_region_size); ClassDB::bind_method(D_METHOD("get_texture_region_size"), &TileSetAtlasSource::get_texture_region_size); + ClassDB::bind_method(D_METHOD("set_use_texture_padding", "use_texture_padding"), &TileSetAtlasSource::set_use_texture_padding); + ClassDB::bind_method(D_METHOD("get_use_texture_padding"), &TileSetAtlasSource::get_use_texture_padding); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D", PROPERTY_USAGE_NO_EDITOR), "set_texture", "get_texture"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2I, "margins", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_margins", "get_margins"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2I, "separation", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_separation", "get_separation"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2I, "texture_region_size", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_texture_region_size", "get_texture_region_size"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_texture_padding", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_use_texture_padding", "get_use_texture_padding"); // Base tiles ClassDB::bind_method(D_METHOD("create_tile", "atlas_coords", "size"), &TileSetAtlasSource::create_tile, DEFVAL(Vector2i(1, 1))); @@ -4243,6 +4314,11 @@ void TileSetAtlasSource::_bind_methods() { // Helpers. ClassDB::bind_method(D_METHOD("get_atlas_grid_size"), &TileSetAtlasSource::get_atlas_grid_size); ClassDB::bind_method(D_METHOD("get_tile_texture_region", "atlas_coords", "frame"), &TileSetAtlasSource::get_tile_texture_region, DEFVAL(0)); + + // Getters for texture and tile region (padded or not) + ClassDB::bind_method(D_METHOD("_update_padded_texture"), &TileSetAtlasSource::_update_padded_texture); + ClassDB::bind_method(D_METHOD("get_runtime_texture"), &TileSetAtlasSource::get_runtime_texture); + ClassDB::bind_method(D_METHOD("get_runtime_tile_texture_region", "atlas_coords", "frame"), &TileSetAtlasSource::get_runtime_tile_texture_region); } TileSetAtlasSource::~TileSetAtlasSource() { @@ -4329,6 +4405,69 @@ void TileSetAtlasSource::_clear_tiles_outside_texture() { } } +void TileSetAtlasSource::_queue_update_padded_texture() { + padded_texture_needs_update = true; + call_deferred("_update_padded_texture"); +} + +void TileSetAtlasSource::_update_padded_texture() { + if (!padded_texture_needs_update) { + return; + } + padded_texture_needs_update = false; + padded_texture = Ref<ImageTexture>(); + + if (!texture.is_valid()) { + return; + } + + if (!use_texture_padding) { + return; + } + + Size2 size = get_atlas_grid_size() * (texture_region_size + Vector2i(2, 2)); + + Ref<Image> src = texture->get_image(); + + Ref<Image> image; + image.instantiate(); + image->create(size.x, size.y, false, Image::FORMAT_RGBA8); + + for (KeyValue<Vector2i, TileAlternativesData> kv : tiles) { + for (int frame = 0; frame < (int)kv.value.animation_frames_durations.size(); frame++) { + // Compute the source rects. + Rect2i src_rect = get_tile_texture_region(kv.key, frame); + + Rect2i top_src_rect = Rect2i(src_rect.position, Vector2i(src_rect.size.x, 1)); + Rect2i bottom_src_rect = Rect2i(src_rect.position + Vector2i(0, src_rect.size.y - 1), Vector2i(src_rect.size.x, 1)); + Rect2i left_src_rect = Rect2i(src_rect.position, Vector2i(1, src_rect.size.y)); + Rect2i right_src_rect = Rect2i(src_rect.position + Vector2i(src_rect.size.x - 1, 0), Vector2i(1, src_rect.size.y)); + + // Copy the tile and the paddings. + Vector2i frame_coords = kv.key + (kv.value.size_in_atlas + kv.value.animation_separation) * ((kv.value.animation_columns > 0) ? Vector2i(frame % kv.value.animation_columns, frame / kv.value.animation_columns) : Vector2i(frame, 0)); + Vector2i base_pos = frame_coords * (texture_region_size + Vector2i(2, 2)) + Vector2i(1, 1); + + image->blit_rect(*src, src_rect, base_pos); + + image->blit_rect(*src, top_src_rect, base_pos + Vector2i(0, -1)); + image->blit_rect(*src, bottom_src_rect, base_pos + Vector2i(0, src_rect.size.y)); + image->blit_rect(*src, left_src_rect, base_pos + Vector2i(-1, 0)); + image->blit_rect(*src, right_src_rect, base_pos + Vector2i(src_rect.size.x, 0)); + + image->set_pixelv(base_pos + Vector2i(-1, -1), src->get_pixelv(src_rect.position)); + image->set_pixelv(base_pos + Vector2i(src_rect.size.x, -1), src->get_pixelv(src_rect.position + Vector2i(src_rect.size.x - 1, 0))); + image->set_pixelv(base_pos + Vector2i(-1, src_rect.size.y), src->get_pixelv(src_rect.position + Vector2i(0, src_rect.size.y - 1))); + image->set_pixelv(base_pos + Vector2i(src_rect.size.x, src_rect.size.y), src->get_pixelv(src_rect.position + Vector2i(src_rect.size.x - 1, src_rect.size.y - 1))); + } + } + + if (!padded_texture.is_valid()) { + padded_texture.instantiate(); + } + padded_texture->create_from_image(image); + emit_changed(); +} + /////////////////////////////// TileSetScenesCollectionSource ////////////////////////////////////// void TileSetScenesCollectionSource::_compute_next_alternative_id() { @@ -4858,6 +4997,9 @@ real_t TileData::get_constant_angular_velocity(int p_layer_id) const { void TileData::set_collision_polygons_count(int p_layer_id, int p_polygons_count) { ERR_FAIL_INDEX(p_layer_id, physics.size()); ERR_FAIL_COND(p_polygons_count < 0); + if (p_polygons_count == physics.write[p_layer_id].polygons.size()) { + return; + } physics.write[p_layer_id].polygons.resize(p_polygons_count); notify_property_list_changed(); emit_signal(SNAME("changed")); @@ -5063,9 +5205,6 @@ bool TileData::_set(const StringName &p_name, const Variant &p_value) { ERR_FAIL_COND_V(layer_index < 0, false); if (components[1] == "polygon") { Ref<OccluderPolygon2D> polygon = p_value; - if (!polygon.is_valid()) { - return false; - } if (layer_index >= occluders.size()) { if (tile_set) { @@ -5137,9 +5276,6 @@ bool TileData::_set(const StringName &p_name, const Variant &p_value) { ERR_FAIL_COND_V(layer_index < 0, false); if (components[1] == "polygon") { Ref<NavigationPolygon> polygon = p_value; - if (!polygon.is_valid()) { - return false; - } if (layer_index >= navigation.size()) { if (tile_set) { diff --git a/scene/resources/tile_set.h b/scene/resources/tile_set.h index 71559074e7..d2238c26d2 100644 --- a/scene/resources/tile_set.h +++ b/scene/resources/tile_set.h @@ -603,6 +603,12 @@ private: void _clear_tiles_outside_texture(); + bool use_texture_padding = true; + Ref<ImageTexture> padded_texture; + bool padded_texture_needs_update = false; + void _queue_update_padded_texture(); + void _update_padded_texture(); + protected: bool _set(const StringName &p_name, const Variant &p_value); bool _get(const StringName &p_name, Variant &r_ret) const; @@ -613,6 +619,7 @@ protected: public: // Not exposed. virtual void set_tile_set(const TileSet *p_tile_set) override; + const TileSet *get_tile_set() const; virtual void notify_tile_data_properties_should_change() override; virtual void add_occlusion_layer(int p_index) override; virtual void move_occlusion_layer(int p_from_index, int p_to_pos) override; @@ -644,6 +651,10 @@ public: void set_texture_region_size(Vector2i p_tile_size); Vector2i get_texture_region_size() const; + // Padding. + void set_use_texture_padding(bool p_use_padding); + bool get_use_texture_padding() const; + // Base tiles. void create_tile(const Vector2i p_atlas_coords, const Vector2i p_size = Vector2i(1, 1)); void remove_tile(Vector2i p_atlas_coords); @@ -689,6 +700,10 @@ public: 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; + // Getters for texture and tile region (padded or not) + Ref<Texture2D> get_runtime_texture() const; + Rect2i get_runtime_tile_texture_region(Vector2i p_atlas_coords, int p_frame = 0) const; + ~TileSetAtlasSource(); }; @@ -766,7 +781,7 @@ private: }; Vector2 linear_velocity; - float angular_velocity = 0.0; + double angular_velocity = 0.0; Vector<PolygonShapeTileData> polygons; }; Vector<PhysicsLayerTileData> physics; diff --git a/scene/resources/visual_shader.cpp b/scene/resources/visual_shader.cpp index 1c7c96c894..87ff225de9 100644 --- a/scene/resources/visual_shader.cpp +++ b/scene/resources/visual_shader.cpp @@ -36,6 +36,11 @@ #include "visual_shader_particle_nodes.h" #include "visual_shader_sdf_nodes.h" +String make_unique_id(VisualShader::Type p_type, int p_id, const String &p_name) { + static const char *typepf[VisualShader::TYPE_MAX] = { "vtx", "frg", "lgt", "start", "process", "collide", "start_custom", "process_custom", "sky", "fog" }; + return p_name + "_" + String(typepf[p_type]) + "_" + itos(p_id); +} + bool VisualShaderNode::is_simple_decl() const { return simple_decl; } @@ -199,6 +204,10 @@ Vector<StringName> VisualShaderNode::get_editable_properties() const { return Vector<StringName>(); } +Map<StringName, String> VisualShaderNode::get_editable_properties_names() const { + return Map<StringName, String>(); +} + Array VisualShaderNode::get_default_input_values() const { Array ret; for (const KeyValue<int, Variant> &E : default_input_values) { @@ -350,7 +359,7 @@ String VisualShaderNodeCustom::generate_code(Shader::Mode p_mode, VisualShader:: } String code = " {\n"; String _code; - GDVIRTUAL_CALL(_get_code, input_vars, output_vars, (int)p_mode, (int)p_type, _code); + GDVIRTUAL_CALL(_get_code, input_vars, output_vars, p_mode, p_type, _code); bool nend = _code.ends_with("\n"); _code = _code.insert(0, " "); _code = _code.replace("\n", "\n "); @@ -367,7 +376,7 @@ String VisualShaderNodeCustom::generate_code(Shader::Mode p_mode, VisualShader:: String VisualShaderNodeCustom::generate_global_per_node(Shader::Mode p_mode, VisualShader::Type p_type, int p_id) const { String ret; - if (GDVIRTUAL_CALL(_get_global_code, (int)p_mode, ret)) { + if (GDVIRTUAL_CALL(_get_global_code, p_mode, ret)) { String code = "// " + get_caption() + "\n"; code += ret; code += "\n"; @@ -1825,6 +1834,8 @@ void VisualShader::_update_shader() const { code += " vec3 __vec3_buff2;\n"; code += " float __scalar_buff1;\n"; code += " float __scalar_buff2;\n"; + code += " int __scalar_ibuff;\n"; + code += " vec4 __vec4_buff;\n"; code += " vec3 __ndiff = normalize(__diff);\n\n"; } if (has_start) { @@ -1922,6 +1933,10 @@ void VisualShader::_update_shader() const { global_compute_code += " return mat4(vec4(oc * axis.x * axis.x + c, oc * axis.x * axis.y - axis.z * s, oc * axis.z * axis.x + axis.y * s, 0), vec4(oc * axis.x * axis.y + axis.z * s, oc * axis.y * axis.y + c, oc * axis.y * axis.z - axis.x * s, 0), vec4(oc * axis.z * axis.x - axis.y * s, oc * axis.y * axis.z + axis.x * s, oc * axis.z * axis.z + c, 0), vec4(0, 0, 0, 1));\n"; global_compute_code += "}\n\n"; + global_compute_code += "vec2 __get_random_unit_vec2(inout uint seed) {\n"; + global_compute_code += " return normalize(vec2(__rand_from_seed_m1_p1(seed), __rand_from_seed_m1_p1(seed)));\n"; + global_compute_code += "}\n\n"; + global_compute_code += "vec3 __get_random_unit_vec3(inout uint seed) {\n"; global_compute_code += " return normalize(vec3(__rand_from_seed_m1_p1(seed), __rand_from_seed_m1_p1(seed), __rand_from_seed_m1_p1(seed)));\n"; global_compute_code += "}\n\n"; @@ -1948,7 +1963,9 @@ void VisualShader::_update_shader() const { const_cast<VisualShader *>(this)->set_code(final_code); for (int i = 0; i < default_tex_params.size(); i++) { - const_cast<VisualShader *>(this)->set_default_texture_param(default_tex_params[i].name, default_tex_params[i].param); + for (int j = 0; j < default_tex_params[i].params.size(); j++) { + const_cast<VisualShader *>(this)->set_default_texture_param(default_tex_params[i].name, default_tex_params[i].params[j], j); + } } if (previous_code != final_code) { const_cast<VisualShader *>(this)->emit_signal(SNAME("changed")); @@ -3436,7 +3453,7 @@ void VisualShaderNodeGroupBase::add_input_port(int p_id, int p_type, const Strin count++; } - inputs.erase(index, count); + inputs = inputs.left(index) + inputs.substr(index + count); inputs = inputs.insert(index, itos(i)); index += inputs_strings[i].size(); } @@ -3459,7 +3476,7 @@ void VisualShaderNodeGroupBase::remove_input_port(int p_id) { } index += inputs_strings[i].size(); } - inputs.erase(index, count); + inputs = inputs.left(index) + inputs.substr(index + count); inputs_strings = inputs.split(";", false); inputs = inputs.substr(0, index); @@ -3512,7 +3529,7 @@ void VisualShaderNodeGroupBase::add_output_port(int p_id, int p_type, const Stri count++; } - outputs.erase(index, count); + outputs = outputs.left(index) + outputs.substr(index + count); outputs = outputs.insert(index, itos(i)); index += outputs_strings[i].size(); } @@ -3535,7 +3552,7 @@ void VisualShaderNodeGroupBase::remove_output_port(int p_id) { } index += outputs_strings[i].size(); } - outputs.erase(index, count); + outputs = outputs.left(index) + outputs.substr(index + count); outputs_strings = outputs.split(";", false); outputs = outputs.substr(0, index); @@ -3587,8 +3604,7 @@ void VisualShaderNodeGroupBase::set_input_port_type(int p_id, int p_type) { index += inputs_strings[i].size(); } - inputs.erase(index, count); - + inputs = inputs.left(index) + inputs.substr(index + count); inputs = inputs.insert(index, itos(p_type)); _apply_port_changes(); @@ -3623,8 +3639,7 @@ void VisualShaderNodeGroupBase::set_input_port_name(int p_id, const String &p_na index += inputs_strings[i].size(); } - inputs.erase(index, count); - + inputs = inputs.left(index) + inputs.substr(index + count); inputs = inputs.insert(index, p_name); _apply_port_changes(); @@ -3659,7 +3674,7 @@ void VisualShaderNodeGroupBase::set_output_port_type(int p_id, int p_type) { index += output_strings[i].size(); } - outputs.erase(index, count); + outputs = outputs.left(index) + outputs.substr(index + count); outputs = outputs.insert(index, itos(p_type)); @@ -3695,7 +3710,7 @@ void VisualShaderNodeGroupBase::set_output_port_name(int p_id, const String &p_n index += output_strings[i].size(); } - outputs.erase(index, count); + outputs = outputs.left(index) + outputs.substr(index + count); outputs = outputs.insert(index, p_name); diff --git a/scene/resources/visual_shader.h b/scene/resources/visual_shader.h index 19530e5a34..5bb9d45f39 100644 --- a/scene/resources/visual_shader.h +++ b/scene/resources/visual_shader.h @@ -70,7 +70,7 @@ public: struct DefaultTextureParam { StringName name; - Ref<Texture2D> param; + List<Ref<Texture2D>> params; }; private: @@ -272,6 +272,7 @@ public: void set_disabled(bool p_disabled = true); virtual Vector<StringName> get_editable_properties() const; + virtual Map<StringName, String> get_editable_properties_names() const; virtual Vector<VisualShader::DefaultTextureParam> get_default_texture_parameters(VisualShader::Type p_type, int p_id) const; virtual String generate_global(Shader::Mode p_mode, VisualShader::Type p_type, int p_id) const; @@ -327,8 +328,8 @@ protected: GDVIRTUAL0RC(int, _get_output_port_count) GDVIRTUAL1RC(int, _get_output_port_type, int) GDVIRTUAL1RC(String, _get_output_port_name, int) - GDVIRTUAL4RC(String, _get_code, Vector<String>, TypedArray<String>, int, int) - GDVIRTUAL1RC(String, _get_global_code, int) + GDVIRTUAL4RC(String, _get_code, Vector<String>, TypedArray<String>, Shader::Mode, VisualShader::Type) + GDVIRTUAL1RC(String, _get_global_code, Shader::Mode) GDVIRTUAL0RC(bool, _is_highend) protected: @@ -696,4 +697,6 @@ public: VisualShaderNodeGlobalExpression(); }; +extern String make_unique_id(VisualShader::Type p_type, int p_id, const String &p_name); + #endif // VISUAL_SHADER_H diff --git a/scene/resources/visual_shader_nodes.cpp b/scene/resources/visual_shader_nodes.cpp index c3d9ef7b04..951870fe34 100644 --- a/scene/resources/visual_shader_nodes.cpp +++ b/scene/resources/visual_shader_nodes.cpp @@ -494,15 +494,10 @@ String VisualShaderNodeTexture::get_input_port_default_hint(int p_port) const { return ""; } -static String make_unique_id(VisualShader::Type p_type, int p_id, const String &p_name) { - static const char *typepf[VisualShader::TYPE_MAX] = { "vtx", "frg", "lgt" }; - return p_name + "_" + String(typepf[p_type]) + "_" + itos(p_id); -} - Vector<VisualShader::DefaultTextureParam> VisualShaderNodeTexture::get_default_texture_parameters(VisualShader::Type p_type, int p_id) const { VisualShader::DefaultTextureParam dtp; dtp.name = make_unique_id(p_type, p_id, "tex"); - dtp.param = texture; + dtp.params.push_back(texture); Vector<VisualShader::DefaultTextureParam> ret; ret.push_back(dtp); return ret; @@ -900,7 +895,7 @@ String VisualShaderNodeCurveTexture::generate_code(Shader::Mode p_mode, VisualSh Vector<VisualShader::DefaultTextureParam> VisualShaderNodeCurveTexture::get_default_texture_parameters(VisualShader::Type p_type, int p_id) const { VisualShader::DefaultTextureParam dtp; dtp.name = make_unique_id(p_type, p_id, "curve"); - dtp.param = texture; + dtp.params.push_back(texture); Vector<VisualShader::DefaultTextureParam> ret; ret.push_back(dtp); return ret; @@ -985,7 +980,7 @@ String VisualShaderNodeCurveXYZTexture::generate_code(Shader::Mode p_mode, Visua Vector<VisualShader::DefaultTextureParam> VisualShaderNodeCurveXYZTexture::get_default_texture_parameters(VisualShader::Type p_type, int p_id) const { VisualShader::DefaultTextureParam dtp; dtp.name = make_unique_id(p_type, p_id, "curve3d"); - dtp.param = texture; + dtp.params.push_back(texture); Vector<VisualShader::DefaultTextureParam> ret; ret.push_back(dtp); return ret; @@ -1167,7 +1162,7 @@ String VisualShaderNodeTexture2DArray::get_input_port_name(int p_port) const { Vector<VisualShader::DefaultTextureParam> VisualShaderNodeTexture2DArray::get_default_texture_parameters(VisualShader::Type p_type, int p_id) const { VisualShader::DefaultTextureParam dtp; dtp.name = make_unique_id(p_type, p_id, "tex3d"); - dtp.param = texture_array; + dtp.params.push_back(texture_array); Vector<VisualShader::DefaultTextureParam> ret; ret.push_back(dtp); return ret; @@ -1224,7 +1219,7 @@ String VisualShaderNodeTexture3D::get_input_port_name(int p_port) const { Vector<VisualShader::DefaultTextureParam> VisualShaderNodeTexture3D::get_default_texture_parameters(VisualShader::Type p_type, int p_id) const { VisualShader::DefaultTextureParam dtp; dtp.name = make_unique_id(p_type, p_id, "tex3d"); - dtp.param = texture; + dtp.params.push_back(texture); Vector<VisualShader::DefaultTextureParam> ret; ret.push_back(dtp); return ret; @@ -1323,7 +1318,7 @@ bool VisualShaderNodeCubemap::is_output_port_expandable(int p_port) const { Vector<VisualShader::DefaultTextureParam> VisualShaderNodeCubemap::get_default_texture_parameters(VisualShader::Type p_type, int p_id) const { VisualShader::DefaultTextureParam dtp; dtp.name = make_unique_id(p_type, p_id, "cube"); - dtp.param = cube_map; + dtp.params.push_back(cube_map); Vector<VisualShader::DefaultTextureParam> ret; ret.push_back(dtp); return ret; diff --git a/scene/resources/visual_shader_particle_nodes.cpp b/scene/resources/visual_shader_particle_nodes.cpp index 18b933e5cf..1a829968e3 100644 --- a/scene/resources/visual_shader_particle_nodes.cpp +++ b/scene/resources/visual_shader_particle_nodes.cpp @@ -30,6 +30,8 @@ #include "visual_shader_particle_nodes.h" +#include "core/core_string_names.h" + // VisualShaderNodeParticleEmitter int VisualShaderNodeParticleEmitter::get_output_port_count() const { @@ -51,6 +53,38 @@ bool VisualShaderNodeParticleEmitter::has_output_port_preview(int p_port) const return false; } +void VisualShaderNodeParticleEmitter::set_mode_2d(bool p_enabled) { + mode_2d = p_enabled; + emit_changed(); +} + +bool VisualShaderNodeParticleEmitter::is_mode_2d() const { + return mode_2d; +} + +Vector<StringName> VisualShaderNodeParticleEmitter::get_editable_properties() const { + Vector<StringName> props; + props.push_back("mode_2d"); + return props; +} + +Map<StringName, String> VisualShaderNodeParticleEmitter::get_editable_properties_names() const { + Map<StringName, String> names; + names.insert("mode_2d", TTR("2D Mode")); + return names; +} + +bool VisualShaderNodeParticleEmitter::is_show_prop_names() const { + return true; +} + +void VisualShaderNodeParticleEmitter::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_mode_2d", "enabled"), &VisualShaderNodeParticleEmitter::set_mode_2d); + ClassDB::bind_method(D_METHOD("is_mode_2d"), &VisualShaderNodeParticleEmitter::is_mode_2d); + + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "mode_2d"), "set_mode_2d", "is_mode_2d"); +} + VisualShaderNodeParticleEmitter::VisualShaderNodeParticleEmitter() { } @@ -79,15 +113,27 @@ String VisualShaderNodeParticleSphereEmitter::get_input_port_name(int p_port) co String VisualShaderNodeParticleSphereEmitter::generate_global_per_node(Shader::Mode p_mode, VisualShader::Type p_type, int p_id) const { String code; + + code += "vec2 __get_random_point_in_circle(inout uint seed, float radius, float inner_radius) {\n"; + code += " return __get_random_unit_vec2(seed) * __randf_range(seed, inner_radius, radius);\n"; + code += "}\n\n"; + code += "vec3 __get_random_point_in_sphere(inout uint seed, float radius, float inner_radius) {\n"; code += " return __get_random_unit_vec3(seed) * __randf_range(seed, inner_radius, radius);\n"; code += "}\n\n"; + return code; } String VisualShaderNodeParticleSphereEmitter::generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview) const { String code; - code += " " + p_output_vars[0] + " = __get_random_point_in_sphere(__seed, " + (p_input_vars[0].is_empty() ? (String)get_input_port_default_value(0) : p_input_vars[0]) + ", " + (p_input_vars[1].is_empty() ? (String)get_input_port_default_value(1) : p_input_vars[1]) + ");\n"; + + if (mode_2d) { + code += " " + p_output_vars[0] + " = vec3(__get_random_point_in_circle(__seed, " + (p_input_vars[0].is_empty() ? (String)get_input_port_default_value(0) : p_input_vars[0]) + ", " + (p_input_vars[1].is_empty() ? (String)get_input_port_default_value(1) : p_input_vars[1]) + "), 0.0);\n"; + } else { + code += " " + p_output_vars[0] + " = __get_random_point_in_sphere(__seed, " + (p_input_vars[0].is_empty() ? (String)get_input_port_default_value(0) : p_input_vars[0]) + ", " + (p_input_vars[1].is_empty() ? (String)get_input_port_default_value(1) : p_input_vars[1]) + ");\n"; + } + return code; } @@ -122,16 +168,27 @@ String VisualShaderNodeParticleBoxEmitter::get_input_port_name(int p_port) const String VisualShaderNodeParticleBoxEmitter::generate_global_per_node(Shader::Mode p_mode, VisualShader::Type p_type, int p_id) const { String code; - code += "vec3 __get_random_point_in_box(inout uint seed, vec3 extents) {\n"; + + code += "vec2 __get_random_point_in_box2d(inout uint seed, vec2 extents) {\n"; + code += " vec2 half_extents = extents / 2.0;\n"; + code += " return vec2(__randf_range(seed, -half_extents.x, half_extents.x), __randf_range(seed, -half_extents.y, half_extents.y));\n"; + code += "}\n\n"; + + code += "vec3 __get_random_point_in_box3d(inout uint seed, vec3 extents) {\n"; code += " vec3 half_extents = extents / 2.0;\n"; code += " return vec3(__randf_range(seed, -half_extents.x, half_extents.x), __randf_range(seed, -half_extents.y, half_extents.y), __randf_range(seed, -half_extents.z, half_extents.z));\n"; code += "}\n\n"; + return code; } String VisualShaderNodeParticleBoxEmitter::generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview) const { String code; - code += " " + p_output_vars[0] + " = __get_random_point_in_box(__seed, " + (p_input_vars[0].is_empty() ? (String)get_input_port_default_value(0) : p_input_vars[0]) + ");\n"; + if (mode_2d) { + code += " " + p_output_vars[0] + " = vec3(__get_random_point_in_box2d(__seed, " + (p_input_vars[0].is_empty() ? (String)get_input_port_default_value(0) : p_input_vars[0]) + ".xy), 0.0);\n"; + } else { + code += " " + p_output_vars[0] + " = __get_random_point_in_box3d(__seed, " + (p_input_vars[0].is_empty() ? (String)get_input_port_default_value(0) : p_input_vars[0]) + ");\n"; + } return code; } @@ -166,17 +223,31 @@ String VisualShaderNodeParticleRingEmitter::get_input_port_name(int p_port) cons String VisualShaderNodeParticleRingEmitter::generate_global_per_node(Shader::Mode p_mode, VisualShader::Type p_type, int p_id) const { String code; - code += "vec3 __get_random_point_on_ring(inout uint seed, float radius, float inner_radius, float height) {\n"; - code += " float angle = __rand_from_seed(seed) * PI * 2.0;\n"; + + code += "vec2 __get_random_point_on_ring2d(inout uint seed, float radius, float inner_radius) {\n"; + code += " float angle = __rand_from_seed(seed) * TAU;\n"; + code += " vec2 ring = vec2(sin(angle), cos(angle)) * __randf_range(seed, inner_radius, radius);\n"; + code += " return vec2(ring.x, ring.y);\n"; + code += "}\n\n"; + + code += "vec3 __get_random_point_on_ring3d(inout uint seed, float radius, float inner_radius, float height) {\n"; + code += " float angle = __rand_from_seed(seed) * TAU;\n"; code += " vec2 ring = vec2(sin(angle), cos(angle)) * __randf_range(seed, inner_radius, radius);\n"; code += " return vec3(ring.x, __randf_range(seed, min(0.0, height), max(0.0, height)), ring.y);\n"; code += "}\n\n"; + return code; } String VisualShaderNodeParticleRingEmitter::generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview) const { String code; - code = " " + p_output_vars[0] + " = __get_random_point_on_ring(__seed, " + (p_input_vars[0].is_empty() ? (String)get_input_port_default_value(0) : p_input_vars[0]) + ", " + (p_input_vars[1].is_empty() ? (String)get_input_port_default_value(1) : p_input_vars[1]) + ", " + (p_input_vars[2].is_empty() ? (String)get_input_port_default_value(2) : p_input_vars[2]) + ");\n"; + + if (mode_2d) { + code = " " + p_output_vars[0] + " = vec3(__get_random_point_on_ring2d(__seed, " + (p_input_vars[0].is_empty() ? (String)get_input_port_default_value(0) : p_input_vars[0]) + ", " + (p_input_vars[1].is_empty() ? (String)get_input_port_default_value(1) : p_input_vars[1]) + "), 0.0);\n"; + } else { + code = " " + p_output_vars[0] + " = __get_random_point_on_ring3d(__seed, " + (p_input_vars[0].is_empty() ? (String)get_input_port_default_value(0) : p_input_vars[0]) + ", " + (p_input_vars[1].is_empty() ? (String)get_input_port_default_value(1) : p_input_vars[1]) + ", " + (p_input_vars[2].is_empty() ? (String)get_input_port_default_value(2) : p_input_vars[2]) + ");\n"; + } + return code; } @@ -186,6 +257,430 @@ VisualShaderNodeParticleRingEmitter::VisualShaderNodeParticleRingEmitter() { set_input_port_default_value(2, 0.0); } +// VisualShaderNodeParticleMeshEmitter + +String VisualShaderNodeParticleMeshEmitter::get_caption() const { + return "MeshEmitter"; +} + +int VisualShaderNodeParticleMeshEmitter::get_output_port_count() const { + return 6; +} + +VisualShaderNodeParticleBoxEmitter::PortType VisualShaderNodeParticleMeshEmitter::get_output_port_type(int p_port) const { + switch (p_port) { + case 0: + return PORT_TYPE_VECTOR; // position + case 1: + return PORT_TYPE_VECTOR; // normal + case 2: + return PORT_TYPE_VECTOR; // color + case 3: + return PORT_TYPE_SCALAR; // alpha + case 4: + return PORT_TYPE_VECTOR; // uv + case 5: + return PORT_TYPE_VECTOR; // uv2 + } + return PORT_TYPE_SCALAR; +} + +String VisualShaderNodeParticleMeshEmitter::get_output_port_name(int p_port) const { + switch (p_port) { + case 0: + return "position"; + case 1: + return "normal"; + case 2: + return "color"; + case 3: + return "alpha"; + case 4: + return "uv"; + case 5: + return "uv2"; + } + return String(); +} + +int VisualShaderNodeParticleMeshEmitter::get_input_port_count() const { + return 0; +} + +VisualShaderNodeParticleBoxEmitter::PortType VisualShaderNodeParticleMeshEmitter::get_input_port_type(int p_port) const { + return PORT_TYPE_SCALAR; +} + +String VisualShaderNodeParticleMeshEmitter::get_input_port_name(int p_port) const { + return String(); +} + +String VisualShaderNodeParticleMeshEmitter::generate_global(Shader::Mode p_mode, VisualShader::Type p_type, int p_id) const { + String code; + + if (mesh.is_valid()) { + if (is_output_port_connected(0)) { // position + code += "uniform sampler2D " + make_unique_id(p_type, p_id, "mesh_vx") + ";\n"; + } + + if (is_output_port_connected(1)) { // normal + code += "uniform sampler2D " + make_unique_id(p_type, p_id, "mesh_nm") + ";\n"; + } + + if (is_output_port_connected(2) || is_output_port_connected(3)) { // color & alpha + code += "uniform sampler2D " + make_unique_id(p_type, p_id, "mesh_col") + ";\n"; + } + + if (is_output_port_connected(4)) { // uv + code += "uniform sampler2D " + make_unique_id(p_type, p_id, "mesh_uv") + ";\n"; + } + + if (is_output_port_connected(5)) { // uv2 + code += "uniform sampler2D " + make_unique_id(p_type, p_id, "mesh_uv2") + ";\n"; + } + } + + return code; +} + +String VisualShaderNodeParticleMeshEmitter::_generate_code(VisualShader::Type p_type, int p_id, const String *p_output_vars, int p_index, const String &p_texture_name, bool p_ignore_mode2d) const { + String code; + if (is_output_port_connected(p_index)) { + if (mode_2d && !p_ignore_mode2d) { + code += " " + p_output_vars[p_index] + " = vec3("; + code += "texelFetch("; + code += make_unique_id(p_type, p_id, p_texture_name) + ", "; + code += "ivec2(__scalar_ibuff, 0), 0).xy, 0.0);\n"; + } else { + code += " " + p_output_vars[p_index] + " = texelFetch("; + code += make_unique_id(p_type, p_id, p_texture_name) + ", "; + code += "ivec2(__scalar_ibuff, 0), 0).xyz;\n"; + } + } + return code; +} + +String VisualShaderNodeParticleMeshEmitter::generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview) const { + String code; + code += " __scalar_ibuff = int(__rand_from_seed(__seed) * 65535.0) % " + itos(position_texture->get_width()) + ";\n"; + + code += _generate_code(p_type, p_id, p_output_vars, 0, "mesh_vx"); + code += _generate_code(p_type, p_id, p_output_vars, 1, "mesh_nm"); + + if (is_output_port_connected(2) || is_output_port_connected(3)) { + code += " __vec4_buff = texelFetch("; + code += make_unique_id(p_type, p_id, "mesh_col") + ", "; + code += "ivec2(__scalar_ibuff, 0), 0);\n"; + if (is_output_port_connected(2)) { + code += " " + p_output_vars[2] + " = __vec4_buff.rgb;\n"; + } else { + code += " " + p_output_vars[2] + " = vec3(0.0);\n"; + } + if (is_output_port_connected(3)) { + code += " " + p_output_vars[3] + " = __vec4_buff.a;\n"; + } else { + code += " " + p_output_vars[3] + " = 0.0;\n"; + } + } + + code += _generate_code(p_type, p_id, p_output_vars, 4, "mesh_uv", true); + code += _generate_code(p_type, p_id, p_output_vars, 5, "mesh_uv2", true); + + return code; +} + +Vector<VisualShader::DefaultTextureParam> VisualShaderNodeParticleMeshEmitter::get_default_texture_parameters(VisualShader::Type p_type, int p_id) const { + Vector<VisualShader::DefaultTextureParam> ret; + + if (is_output_port_connected(0)) { + VisualShader::DefaultTextureParam dtp; + dtp.name = make_unique_id(p_type, p_id, "mesh_vx"); + dtp.params.push_back(position_texture); + ret.push_back(dtp); + } + + if (is_output_port_connected(1)) { + VisualShader::DefaultTextureParam dtp; + dtp.name = make_unique_id(p_type, p_id, "mesh_nm"); + dtp.params.push_back(normal_texture); + ret.push_back(dtp); + } + + if (is_output_port_connected(2) || is_output_port_connected(3)) { + VisualShader::DefaultTextureParam dtp; + dtp.name = make_unique_id(p_type, p_id, "mesh_col"); + dtp.params.push_back(color_texture); + ret.push_back(dtp); + } + + if (is_output_port_connected(4)) { + VisualShader::DefaultTextureParam dtp; + dtp.name = make_unique_id(p_type, p_id, "mesh_uv"); + dtp.params.push_back(uv_texture); + ret.push_back(dtp); + } + + if (is_output_port_connected(5)) { + VisualShader::DefaultTextureParam dtp; + dtp.name = make_unique_id(p_type, p_id, "mesh_uv2"); + dtp.params.push_back(uv2_texture); + ret.push_back(dtp); + } + + return ret; +} + +void VisualShaderNodeParticleMeshEmitter::_update_texture(const Vector<Vector2> &p_array, Ref<ImageTexture> &r_texture) { + Ref<Image> image; + image.instantiate(); + + if (p_array.size() == 0) { + image->create(1, 1, false, Image::Format::FORMAT_RGBF); + } else { + image->create(p_array.size(), 1, false, Image::Format::FORMAT_RGBF); + } + + for (int i = 0; i < p_array.size(); i++) { + Vector2 v = p_array[i]; + image->set_pixel(i, 0, Color(v.x, v.y, 0)); + } + if (r_texture->get_width() != p_array.size() || p_array.size() == 0) { + r_texture->create_from_image(image); + } else { + r_texture->update(image); + } +} + +void VisualShaderNodeParticleMeshEmitter::_update_texture(const Vector<Vector3> &p_array, Ref<ImageTexture> &r_texture) { + Ref<Image> image; + image.instantiate(); + + if (p_array.size() == 0) { + image->create(1, 1, false, Image::Format::FORMAT_RGBF); + } else { + image->create(p_array.size(), 1, false, Image::Format::FORMAT_RGBF); + } + + for (int i = 0; i < p_array.size(); i++) { + Vector3 v = p_array[i]; + image->set_pixel(i, 0, Color(v.x, v.y, v.z)); + } + if (r_texture->get_width() != p_array.size() || p_array.size() == 0) { + r_texture->create_from_image(image); + } else { + r_texture->update(image); + } +} + +void VisualShaderNodeParticleMeshEmitter::_update_texture(const Vector<Color> &p_array, Ref<ImageTexture> &r_texture) { + Ref<Image> image; + image.instantiate(); + + if (p_array.size() == 0) { + image->create(1, 1, false, Image::Format::FORMAT_RGBA8); + } else { + image->create(p_array.size(), 1, false, Image::Format::FORMAT_RGBA8); + } + + for (int i = 0; i < p_array.size(); i++) { + image->set_pixel(i, 0, p_array[i]); + } + if (r_texture->get_width() != p_array.size() || p_array.size() == 0) { + r_texture->create_from_image(image); + } else { + r_texture->update(image); + } +} + +void VisualShaderNodeParticleMeshEmitter::_update_textures() { + if (!mesh.is_valid()) { + return; + } + + Vector<Vector3> vertices; + Vector<Vector3> normals; + Vector<Color> colors; + Vector<Vector2> uvs; + Vector<Vector2> uvs2; + + if (use_all_surfaces) { + for (int i = 0; i < max_surface_index; i++) { + // position + Array vertex_array = mesh->surface_get_arrays(i)[Mesh::ARRAY_VERTEX]; + for (int j = 0; j < vertex_array.size(); j++) { + vertices.push_back((Vector3)vertex_array[j]); + } + + // normal + Array normal_array = mesh->surface_get_arrays(i)[Mesh::ARRAY_NORMAL]; + for (int j = 0; j < normal_array.size(); j++) { + normals.push_back((Vector3)normal_array[j]); + } + + // color + Array color_array = mesh->surface_get_arrays(i)[Mesh::ARRAY_COLOR]; + for (int j = 0; j < color_array.size(); j++) { + colors.push_back((Color)color_array[j]); + } + + // uv + Array uv_array = mesh->surface_get_arrays(i)[Mesh::ARRAY_TEX_UV]; + for (int j = 0; j < uv_array.size(); j++) { + uvs.push_back((Vector2)uv_array[j]); + } + + // uv2 + Array uv2_array = mesh->surface_get_arrays(i)[Mesh::ARRAY_TEX_UV2]; + for (int j = 0; j < uv2_array.size(); j++) { + uvs2.push_back((Vector2)uv2_array[j]); + } + } + } else { + // position + Array vertex_array = mesh->surface_get_arrays(surface_index)[Mesh::ARRAY_VERTEX]; + for (int i = 0; i < vertex_array.size(); i++) { + vertices.push_back((Vector3)vertex_array[i]); + } + + // normal + Array normal_array = mesh->surface_get_arrays(surface_index)[Mesh::ARRAY_NORMAL]; + for (int i = 0; i < normal_array.size(); i++) { + normals.push_back((Vector3)normal_array[i]); + } + + // color + Array color_array = mesh->surface_get_arrays(surface_index)[Mesh::ARRAY_COLOR]; + for (int i = 0; i < color_array.size(); i++) { + colors.push_back((Color)color_array[i]); + } + + // uv + Array uv_array = mesh->surface_get_arrays(surface_index)[Mesh::ARRAY_TEX_UV]; + for (int j = 0; j < uv_array.size(); j++) { + uvs.push_back((Vector2)uv_array[j]); + } + + // uv2 + Array uv2_array = mesh->surface_get_arrays(surface_index)[Mesh::ARRAY_TEX_UV2]; + for (int j = 0; j < uv2_array.size(); j++) { + uvs2.push_back((Vector2)uv2_array[j]); + } + } + + _update_texture(vertices, position_texture); + _update_texture(normals, normal_texture); + _update_texture(colors, color_texture); + _update_texture(uvs, uv_texture); + _update_texture(uvs2, uv2_texture); +} + +void VisualShaderNodeParticleMeshEmitter::set_mesh(Ref<Mesh> p_mesh) { + if (mesh == p_mesh) { + return; + } + + if (p_mesh.is_valid()) { + max_surface_index = p_mesh->get_surface_count(); + } else { + max_surface_index = 0; + } + + if (mesh.is_valid()) { + Callable callable = callable_mp(this, &VisualShaderNodeParticleMeshEmitter::_update_textures); + + if (mesh->is_connected(CoreStringNames::get_singleton()->changed, callable)) { + mesh->disconnect(CoreStringNames::get_singleton()->changed, callable); + } + } + + mesh = p_mesh; + + if (mesh.is_valid()) { + Callable callable = callable_mp(this, &VisualShaderNodeParticleMeshEmitter::_update_textures); + + if (!mesh->is_connected(CoreStringNames::get_singleton()->changed, callable)) { + mesh->connect(CoreStringNames::get_singleton()->changed, callable); + } + } + + emit_changed(); +} + +Ref<Mesh> VisualShaderNodeParticleMeshEmitter::get_mesh() const { + return mesh; +} + +void VisualShaderNodeParticleMeshEmitter::set_use_all_surfaces(bool p_enabled) { + if (use_all_surfaces == p_enabled) { + return; + } + use_all_surfaces = p_enabled; + emit_changed(); +} + +bool VisualShaderNodeParticleMeshEmitter::is_use_all_surfaces() const { + return use_all_surfaces; +} + +void VisualShaderNodeParticleMeshEmitter::set_surface_index(int p_surface_index) { + if (p_surface_index == surface_index || p_surface_index < 0 || p_surface_index >= max_surface_index) { + return; + } + surface_index = p_surface_index; + emit_changed(); +} + +int VisualShaderNodeParticleMeshEmitter::get_surface_index() const { + return surface_index; +} + +Vector<StringName> VisualShaderNodeParticleMeshEmitter::get_editable_properties() const { + Vector<StringName> props = VisualShaderNodeParticleEmitter::get_editable_properties(); + + props.push_back("mesh"); + props.push_back("use_all_surfaces"); + if (!use_all_surfaces) { + props.push_back("surface_index"); + } + + return props; +} + +Map<StringName, String> VisualShaderNodeParticleMeshEmitter::get_editable_properties_names() const { + Map<StringName, String> names = VisualShaderNodeParticleEmitter::get_editable_properties_names(); + + names.insert("mesh", TTR("Mesh")); + names.insert("use_all_surfaces", TTR("Use All Surfaces")); + if (!use_all_surfaces) { + names.insert("surface_index", TTR("Surface Index")); + } + + return names; +} + +void VisualShaderNodeParticleMeshEmitter::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_mesh", "mesh"), &VisualShaderNodeParticleMeshEmitter::set_mesh); + ClassDB::bind_method(D_METHOD("get_mesh"), &VisualShaderNodeParticleMeshEmitter::get_mesh); + ClassDB::bind_method(D_METHOD("set_use_all_surfaces", "enabled"), &VisualShaderNodeParticleMeshEmitter::set_use_all_surfaces); + ClassDB::bind_method(D_METHOD("is_use_all_surfaces"), &VisualShaderNodeParticleMeshEmitter::is_use_all_surfaces); + ClassDB::bind_method(D_METHOD("set_surface_index", "surface_index"), &VisualShaderNodeParticleMeshEmitter::set_surface_index); + ClassDB::bind_method(D_METHOD("get_surface_index"), &VisualShaderNodeParticleMeshEmitter::get_surface_index); + + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "mesh", PROPERTY_HINT_RESOURCE_TYPE, "Mesh"), "set_mesh", "get_mesh"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_all_surfaces"), "set_use_all_surfaces", "is_use_all_surfaces"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "surface_index"), "set_surface_index", "get_surface_index"); +} + +VisualShaderNodeParticleMeshEmitter::VisualShaderNodeParticleMeshEmitter() { + connect(CoreStringNames::get_singleton()->changed, callable_mp(this, &VisualShaderNodeParticleMeshEmitter::_update_textures)); + + position_texture.instantiate(); + normal_texture.instantiate(); + color_texture.instantiate(); + uv_texture.instantiate(); + uv2_texture.instantiate(); +} + // VisualShaderNodeParticleMultiplyByAxisAngle void VisualShaderNodeParticleMultiplyByAxisAngle::_bind_methods() { @@ -265,7 +760,6 @@ bool VisualShaderNodeParticleMultiplyByAxisAngle::is_degrees_mode() const { Vector<StringName> VisualShaderNodeParticleMultiplyByAxisAngle::get_editable_properties() const { Vector<StringName> props; props.push_back("degrees_mode"); - props.push_back("axis_amount"); return props; } diff --git a/scene/resources/visual_shader_particle_nodes.h b/scene/resources/visual_shader_particle_nodes.h index b8bc7992cc..79459432f1 100644 --- a/scene/resources/visual_shader_particle_nodes.h +++ b/scene/resources/visual_shader_particle_nodes.h @@ -38,12 +38,23 @@ class VisualShaderNodeParticleEmitter : public VisualShaderNode { GDCLASS(VisualShaderNodeParticleEmitter, VisualShaderNode); +protected: + bool mode_2d = false; + static void _bind_methods(); + public: virtual int get_output_port_count() const override; virtual PortType get_output_port_type(int p_port) const override; virtual String get_output_port_name(int p_port) const override; virtual bool has_output_port_preview(int p_port) const override; + void set_mode_2d(bool p_enabled); + bool is_mode_2d() const; + + Vector<StringName> get_editable_properties() const override; + virtual Map<StringName, String> get_editable_properties_names() const override; + bool is_show_prop_names() const override; + VisualShaderNodeParticleEmitter(); }; @@ -95,6 +106,59 @@ public: VisualShaderNodeParticleRingEmitter(); }; +class VisualShaderNodeParticleMeshEmitter : public VisualShaderNodeParticleEmitter { + GDCLASS(VisualShaderNodeParticleMeshEmitter, VisualShaderNodeParticleEmitter); + Ref<Mesh> mesh; + bool use_all_surfaces = true; + int surface_index = 0; + int max_surface_index = 0; + + Ref<ImageTexture> position_texture; + Ref<ImageTexture> normal_texture; + Ref<ImageTexture> color_texture; + Ref<ImageTexture> uv_texture; + Ref<ImageTexture> uv2_texture; + + String _generate_code(VisualShader::Type p_type, int p_id, const String *p_output_vars, int p_index, const String &p_texture_name, bool p_ignore_mode2d = false) const; + + void _update_texture(const Vector<Vector2> &p_array, Ref<ImageTexture> &r_texture); + void _update_texture(const Vector<Vector3> &p_array, Ref<ImageTexture> &r_texture); + void _update_texture(const Vector<Color> &p_array, Ref<ImageTexture> &r_texture); + void _update_textures(); + +protected: + static void _bind_methods(); + +public: + virtual String get_caption() const override; + + virtual int get_output_port_count() const override; + virtual PortType get_output_port_type(int p_port) const override; + virtual String get_output_port_name(int p_port) const override; + + virtual int get_input_port_count() const override; + virtual PortType get_input_port_type(int p_port) const override; + virtual String get_input_port_name(int p_port) const override; + + virtual String generate_global(Shader::Mode p_mode, VisualShader::Type p_type, int p_id) const override; + virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; + + void set_mesh(Ref<Mesh> p_mesh); + Ref<Mesh> get_mesh() const; + + void set_use_all_surfaces(bool p_enabled); + bool is_use_all_surfaces() const; + + void set_surface_index(int p_surface_index); + int get_surface_index() const; + + Vector<StringName> get_editable_properties() const override; + Map<StringName, String> get_editable_properties_names() const override; + Vector<VisualShader::DefaultTextureParam> get_default_texture_parameters(VisualShader::Type p_type, int p_id) const override; + + VisualShaderNodeParticleMeshEmitter(); +}; + class VisualShaderNodeParticleMultiplyByAxisAngle : public VisualShaderNode { GDCLASS(VisualShaderNodeParticleMultiplyByAxisAngle, VisualShaderNode); bool degrees_mode = true; |