diff options
Diffstat (limited to 'scene')
333 files changed, 18235 insertions, 8815 deletions
diff --git a/scene/2d/animated_sprite_2d.cpp b/scene/2d/animated_sprite_2d.cpp index d3783aadd1..d56c7b8811 100644 --- a/scene/2d/animated_sprite_2d.cpp +++ b/scene/2d/animated_sprite_2d.cpp @@ -138,8 +138,11 @@ void AnimatedSprite2D::_validate_property(PropertyInfo &property) const { if (property.name == "frame") { property.hint = PROPERTY_HINT_RANGE; - if (frames->has_animation(animation) && frames->get_frame_count(animation) > 1) { + if (frames->has_animation(animation) && frames->get_frame_count(animation) > 0) { property.hint_string = "0," + itos(frames->get_frame_count(animation) - 1) + ",1"; + } else { + // Avoid an error, `hint_string` is required for `PROPERTY_HINT_RANGE`. + property.hint_string = "0,0,1"; } property.usage |= PROPERTY_USAGE_KEYING_INCREMENTS; } @@ -388,6 +391,7 @@ void AnimatedSprite2D::play(const StringName &p_animation, const bool p_backward } } + is_over = false; set_playing(true); } @@ -443,6 +447,17 @@ TypedArray<String> AnimatedSprite2D::get_configuration_warnings() const { return warnings; } +void AnimatedSprite2D::get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const { + if (p_idx == 0 && p_function == "play" && frames.is_valid()) { + List<StringName> al; + frames->get_animation_list(&al); + for (const StringName &name : al) { + r_options->push_back(String(name).quote()); + } + } + Node::get_argument_options(p_function, p_idx, r_options); +} + void AnimatedSprite2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_sprite_frames", "sprite_frames"), &AnimatedSprite2D::set_sprite_frames); ClassDB::bind_method(D_METHOD("get_sprite_frames"), &AnimatedSprite2D::get_sprite_frames); @@ -485,7 +500,7 @@ void AnimatedSprite2D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "playing"), "set_playing", "is_playing"); ADD_GROUP("Offset", ""); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "centered"), "set_centered", "is_centered"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "offset"), "set_offset", "get_offset"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "offset", PROPERTY_HINT_NONE, "suffix:px"), "set_offset", "get_offset"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "flip_h"), "set_flip_h", "is_flipped_h"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "flip_v"), "set_flip_v", "is_flipped_v"); } diff --git a/scene/2d/animated_sprite_2d.h b/scene/2d/animated_sprite_2d.h index b3af931ea2..3a41f810dc 100644 --- a/scene/2d/animated_sprite_2d.h +++ b/scene/2d/animated_sprite_2d.h @@ -109,6 +109,8 @@ public: bool is_flipped_v() const; TypedArray<String> get_configuration_warnings() const override; + virtual void get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const override; + AnimatedSprite2D(); }; diff --git a/scene/2d/area_2d.cpp b/scene/2d/area_2d.cpp index 70b9b769cd..dfc1016c84 100644 --- a/scene/2d/area_2d.cpp +++ b/scene/2d/area_2d.cpp @@ -137,14 +137,14 @@ void Area2D::_body_enter_tree(ObjectID p_id) { Node *node = Object::cast_to<Node>(obj); ERR_FAIL_COND(!node); - Map<ObjectID, BodyState>::Element *E = body_map.find(p_id); + HashMap<ObjectID, BodyState>::Iterator E = body_map.find(p_id); ERR_FAIL_COND(!E); - ERR_FAIL_COND(E->get().in_tree); + ERR_FAIL_COND(E->value.in_tree); - E->get().in_tree = true; + E->value.in_tree = true; emit_signal(SceneStringNames::get_singleton()->body_entered, node); - for (int i = 0; i < E->get().shapes.size(); i++) { - emit_signal(SceneStringNames::get_singleton()->body_shape_entered, E->get().rid, node, E->get().shapes[i].body_shape, E->get().shapes[i].area_shape); + for (int i = 0; i < E->value.shapes.size(); i++) { + emit_signal(SceneStringNames::get_singleton()->body_shape_entered, E->value.rid, node, E->value.shapes[i].body_shape, E->value.shapes[i].area_shape); } } @@ -152,13 +152,13 @@ void Area2D::_body_exit_tree(ObjectID p_id) { Object *obj = ObjectDB::get_instance(p_id); Node *node = Object::cast_to<Node>(obj); ERR_FAIL_COND(!node); - Map<ObjectID, BodyState>::Element *E = body_map.find(p_id); + HashMap<ObjectID, BodyState>::Iterator E = body_map.find(p_id); ERR_FAIL_COND(!E); - ERR_FAIL_COND(!E->get().in_tree); - E->get().in_tree = false; + ERR_FAIL_COND(!E->value.in_tree); + E->value.in_tree = false; emit_signal(SceneStringNames::get_singleton()->body_exited, node); - for (int i = 0; i < E->get().shapes.size(); i++) { - emit_signal(SceneStringNames::get_singleton()->body_shape_exited, E->get().rid, node, E->get().shapes[i].body_shape, E->get().shapes[i].area_shape); + for (int i = 0; i < E->value.shapes.size(); i++) { + emit_signal(SceneStringNames::get_singleton()->body_shape_exited, E->value.rid, node, E->value.shapes[i].body_shape, E->value.shapes[i].area_shape); } } @@ -169,7 +169,7 @@ void Area2D::_body_inout(int p_status, const RID &p_body, ObjectID p_instance, i Object *obj = ObjectDB::get_instance(objid); Node *node = Object::cast_to<Node>(obj); - Map<ObjectID, BodyState>::Element *E = body_map.find(objid); + HashMap<ObjectID, BodyState>::Iterator E = body_map.find(objid); if (!body_in && !E) { return; //does not exist because it was likely removed from the tree @@ -180,36 +180,36 @@ void Area2D::_body_inout(int p_status, const RID &p_body, ObjectID p_instance, i if (body_in) { if (!E) { E = body_map.insert(objid, BodyState()); - E->get().rid = p_body; - E->get().rc = 0; - E->get().in_tree = node && node->is_inside_tree(); + E->value.rid = p_body; + E->value.rc = 0; + E->value.in_tree = node && node->is_inside_tree(); if (node) { node->connect(SceneStringNames::get_singleton()->tree_entered, callable_mp(this, &Area2D::_body_enter_tree), make_binds(objid)); node->connect(SceneStringNames::get_singleton()->tree_exiting, callable_mp(this, &Area2D::_body_exit_tree), make_binds(objid)); - if (E->get().in_tree) { + if (E->value.in_tree) { emit_signal(SceneStringNames::get_singleton()->body_entered, node); } } } - E->get().rc++; + E->value.rc++; if (node) { - E->get().shapes.insert(ShapePair(p_body_shape, p_area_shape)); + E->value.shapes.insert(ShapePair(p_body_shape, p_area_shape)); } - if (!node || E->get().in_tree) { + if (!node || E->value.in_tree) { emit_signal(SceneStringNames::get_singleton()->body_shape_entered, p_body, node, p_body_shape, p_area_shape); } } else { - E->get().rc--; + E->value.rc--; if (node) { - E->get().shapes.erase(ShapePair(p_body_shape, p_area_shape)); + E->value.shapes.erase(ShapePair(p_body_shape, p_area_shape)); } - bool in_tree = E->get().in_tree; - if (E->get().rc == 0) { - body_map.erase(E); + bool in_tree = E->value.in_tree; + if (E->value.rc == 0) { + body_map.remove(E); if (node) { node->disconnect(SceneStringNames::get_singleton()->tree_entered, callable_mp(this, &Area2D::_body_enter_tree)); node->disconnect(SceneStringNames::get_singleton()->tree_exiting, callable_mp(this, &Area2D::_body_exit_tree)); @@ -231,14 +231,14 @@ void Area2D::_area_enter_tree(ObjectID p_id) { Node *node = Object::cast_to<Node>(obj); ERR_FAIL_COND(!node); - Map<ObjectID, AreaState>::Element *E = area_map.find(p_id); + HashMap<ObjectID, AreaState>::Iterator E = area_map.find(p_id); ERR_FAIL_COND(!E); - ERR_FAIL_COND(E->get().in_tree); + ERR_FAIL_COND(E->value.in_tree); - E->get().in_tree = true; + E->value.in_tree = true; emit_signal(SceneStringNames::get_singleton()->area_entered, node); - for (int i = 0; i < E->get().shapes.size(); i++) { - emit_signal(SceneStringNames::get_singleton()->area_shape_entered, E->get().rid, node, E->get().shapes[i].area_shape, E->get().shapes[i].self_shape); + for (int i = 0; i < E->value.shapes.size(); i++) { + emit_signal(SceneStringNames::get_singleton()->area_shape_entered, E->value.rid, node, E->value.shapes[i].area_shape, E->value.shapes[i].self_shape); } } @@ -246,13 +246,13 @@ void Area2D::_area_exit_tree(ObjectID p_id) { Object *obj = ObjectDB::get_instance(p_id); Node *node = Object::cast_to<Node>(obj); ERR_FAIL_COND(!node); - Map<ObjectID, AreaState>::Element *E = area_map.find(p_id); + HashMap<ObjectID, AreaState>::Iterator E = area_map.find(p_id); ERR_FAIL_COND(!E); - ERR_FAIL_COND(!E->get().in_tree); - E->get().in_tree = false; + ERR_FAIL_COND(!E->value.in_tree); + E->value.in_tree = false; emit_signal(SceneStringNames::get_singleton()->area_exited, node); - for (int i = 0; i < E->get().shapes.size(); i++) { - emit_signal(SceneStringNames::get_singleton()->area_shape_exited, E->get().rid, node, E->get().shapes[i].area_shape, E->get().shapes[i].self_shape); + for (int i = 0; i < E->value.shapes.size(); i++) { + emit_signal(SceneStringNames::get_singleton()->area_shape_exited, E->value.rid, node, E->value.shapes[i].area_shape, E->value.shapes[i].self_shape); } } @@ -263,7 +263,7 @@ void Area2D::_area_inout(int p_status, const RID &p_area, ObjectID p_instance, i Object *obj = ObjectDB::get_instance(objid); Node *node = Object::cast_to<Node>(obj); - Map<ObjectID, AreaState>::Element *E = area_map.find(objid); + HashMap<ObjectID, AreaState>::Iterator E = area_map.find(objid); if (!area_in && !E) { return; //likely removed from the tree @@ -273,36 +273,36 @@ void Area2D::_area_inout(int p_status, const RID &p_area, ObjectID p_instance, i if (area_in) { if (!E) { E = area_map.insert(objid, AreaState()); - E->get().rid = p_area; - E->get().rc = 0; - E->get().in_tree = node && node->is_inside_tree(); + E->value.rid = p_area; + E->value.rc = 0; + E->value.in_tree = node && node->is_inside_tree(); if (node) { node->connect(SceneStringNames::get_singleton()->tree_entered, callable_mp(this, &Area2D::_area_enter_tree), make_binds(objid)); node->connect(SceneStringNames::get_singleton()->tree_exiting, callable_mp(this, &Area2D::_area_exit_tree), make_binds(objid)); - if (E->get().in_tree) { + if (E->value.in_tree) { emit_signal(SceneStringNames::get_singleton()->area_entered, node); } } } - E->get().rc++; + E->value.rc++; if (node) { - E->get().shapes.insert(AreaShapePair(p_area_shape, p_self_shape)); + E->value.shapes.insert(AreaShapePair(p_area_shape, p_self_shape)); } - if (!node || E->get().in_tree) { + if (!node || E->value.in_tree) { emit_signal(SceneStringNames::get_singleton()->area_shape_entered, p_area, node, p_area_shape, p_self_shape); } } else { - E->get().rc--; + E->value.rc--; if (node) { - E->get().shapes.erase(AreaShapePair(p_area_shape, p_self_shape)); + E->value.shapes.erase(AreaShapePair(p_area_shape, p_self_shape)); } - bool in_tree = E->get().in_tree; - if (E->get().rc == 0) { - area_map.erase(E); + bool in_tree = E->value.in_tree; + if (E->value.rc == 0) { + area_map.remove(E); if (node) { node->disconnect(SceneStringNames::get_singleton()->tree_entered, callable_mp(this, &Area2D::_area_enter_tree)); node->disconnect(SceneStringNames::get_singleton()->tree_exiting, callable_mp(this, &Area2D::_area_exit_tree)); @@ -323,7 +323,7 @@ void Area2D::_clear_monitoring() { ERR_FAIL_COND_MSG(locked, "This function can't be used during the in/out signal."); { - Map<ObjectID, BodyState> bmcopy = body_map; + HashMap<ObjectID, BodyState> bmcopy = body_map; body_map.clear(); //disconnect all monitored stuff @@ -351,7 +351,7 @@ void Area2D::_clear_monitoring() { } { - Map<ObjectID, AreaState> bmcopy = area_map; + HashMap<ObjectID, AreaState> bmcopy = area_map; area_map.clear(); //disconnect all monitored stuff @@ -461,20 +461,20 @@ TypedArray<Area2D> Area2D::get_overlapping_areas() const { bool Area2D::overlaps_area(Node *p_area) const { ERR_FAIL_NULL_V(p_area, false); - const Map<ObjectID, AreaState>::Element *E = area_map.find(p_area->get_instance_id()); + HashMap<ObjectID, AreaState>::ConstIterator E = area_map.find(p_area->get_instance_id()); if (!E) { return false; } - return E->get().in_tree; + return E->value.in_tree; } bool Area2D::overlaps_body(Node *p_body) const { ERR_FAIL_NULL_V(p_body, false); - const Map<ObjectID, BodyState>::Element *E = body_map.find(p_body->get_instance_id()); + HashMap<ObjectID, BodyState>::ConstIterator E = body_map.find(p_body->get_instance_id()); if (!E) { return false; } - return E->get().in_tree; + return E->value.in_tree; } void Area2D::set_audio_bus_override(bool p_override) { @@ -605,9 +605,9 @@ void Area2D::_bind_methods() { 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_point_center", PROPERTY_HINT_NONE, "suffix:px"), "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_PROPERTY(PropertyInfo(Variant::FLOAT, "gravity", PROPERTY_HINT_RANGE, U"-4096,4096,0.001,or_lesser,or_greater,suffix:px/s\u00B2"), "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"); diff --git a/scene/2d/area_2d.h b/scene/2d/area_2d.h index 68047ccebf..a584420ced 100644 --- a/scene/2d/area_2d.h +++ b/scene/2d/area_2d.h @@ -49,7 +49,7 @@ public: private: SpaceOverride gravity_space_override = SPACE_OVERRIDE_DISABLED; Vector2 gravity_vec; - real_t gravity; + real_t gravity = 0.0; bool gravity_is_point = false; real_t gravity_distance_scale = 0.0; @@ -94,7 +94,7 @@ private: VSet<ShapePair> shapes; }; - Map<ObjectID, BodyState> body_map; + HashMap<ObjectID, BodyState> body_map; void _area_inout(int p_status, const RID &p_area, ObjectID p_instance, int p_area_shape, int p_self_shape); @@ -126,7 +126,7 @@ private: VSet<AreaShapePair> shapes; }; - Map<ObjectID, AreaState> area_map; + HashMap<ObjectID, AreaState> area_map; void _clear_monitoring(); bool audio_bus_override = false; diff --git a/scene/2d/audio_listener_2d.cpp b/scene/2d/audio_listener_2d.cpp index eb463864e1..f7dd20d7c0 100644 --- a/scene/2d/audio_listener_2d.cpp +++ b/scene/2d/audio_listener_2d.cpp @@ -57,7 +57,7 @@ bool AudioListener2D::_get(const StringName &p_name, Variant &r_ret) const { } void AudioListener2D::_get_property_list(List<PropertyInfo> *p_list) const { - p_list->push_back(PropertyInfo(Variant::BOOL, "current")); + p_list->push_back(PropertyInfo(Variant::BOOL, PNAME("current"))); } void AudioListener2D::_notification(int p_what) { diff --git a/scene/2d/audio_stream_player_2d.cpp b/scene/2d/audio_stream_player_2d.cpp index c1328badfb..eaab58c4ae 100644 --- a/scene/2d/audio_stream_player_2d.cpp +++ b/scene/2d/audio_stream_player_2d.cpp @@ -30,6 +30,7 @@ #include "audio_stream_player_2d.h" +#include "core/config/project_settings.h" #include "scene/2d/area_2d.h" #include "scene/2d/audio_listener_2d.h" #include "scene/main/window.h" @@ -148,7 +149,7 @@ void AudioStreamPlayer2D::_update_panning() { Vector2 global_pos = get_global_position(); - Set<Viewport *> viewports = world_2d->get_viewports(); + HashSet<Viewport *> viewports = world_2d->get_viewports(); viewports.insert(get_viewport()); // TODO: This is a mediocre workaround for #50958. Remove when that bug is fixed! volume_vector.resize(4); @@ -186,7 +187,14 @@ void AudioStreamPlayer2D::_update_panning() { float multiplier = Math::pow(1.0f - dist / max_distance, attenuation); multiplier *= Math::db2linear(volume_db); //also apply player volume! - float pan = CLAMP((relative_to_listener.x + screen_size.x * 0.5) / screen_size.x, 0.0, 1.0); + float pan = relative_to_listener.x / screen_size.x; + // Don't let the panning effect extend (too far) beyond the screen. + pan = CLAMP(pan, -1, 1); + + // Bake in a constant factor here to allow the project setting defaults for 2d and 3d to be normalized to 1.0. + pan *= panning_strength * cached_global_panning_strength * 0.5f; + + pan = CLAMP(pan + 0.5, 0.0, 1.0); float l = 1.0 - pan; float r = pan; @@ -391,6 +399,15 @@ int AudioStreamPlayer2D::get_max_polyphony() const { return max_polyphony; } +void AudioStreamPlayer2D::set_panning_strength(float p_panning_strength) { + ERR_FAIL_COND_MSG(p_panning_strength < 0, "Panning strength must be a positive number."); + panning_strength = p_panning_strength; +} + +float AudioStreamPlayer2D::get_panning_strength() const { + return panning_strength; +} + void AudioStreamPlayer2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_stream", "stream"), &AudioStreamPlayer2D::set_stream); ClassDB::bind_method(D_METHOD("get_stream"), &AudioStreamPlayer2D::get_stream); @@ -432,17 +449,21 @@ void AudioStreamPlayer2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_max_polyphony", "max_polyphony"), &AudioStreamPlayer2D::set_max_polyphony); ClassDB::bind_method(D_METHOD("get_max_polyphony"), &AudioStreamPlayer2D::get_max_polyphony); + ClassDB::bind_method(D_METHOD("set_panning_strength", "panning_strength"), &AudioStreamPlayer2D::set_panning_strength); + ClassDB::bind_method(D_METHOD("get_panning_strength"), &AudioStreamPlayer2D::get_panning_strength); + ClassDB::bind_method(D_METHOD("get_stream_playback"), &AudioStreamPlayer2D::get_stream_playback); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "stream", PROPERTY_HINT_RESOURCE_TYPE, "AudioStream"), "set_stream", "get_stream"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "volume_db", PROPERTY_HINT_RANGE, "-80,24"), "set_volume_db", "get_volume_db"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "volume_db", PROPERTY_HINT_RANGE, "-80,24,suffix:dB"), "set_volume_db", "get_volume_db"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "pitch_scale", PROPERTY_HINT_RANGE, "0.01,4,0.01,or_greater"), "set_pitch_scale", "get_pitch_scale"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "playing", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "_set_playing", "is_playing"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "autoplay"), "set_autoplay", "is_autoplay_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "stream_paused", PROPERTY_HINT_NONE, ""), "set_stream_paused", "get_stream_paused"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "max_distance", PROPERTY_HINT_RANGE, "1,4096,1,or_greater,exp"), "set_max_distance", "get_max_distance"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "max_distance", PROPERTY_HINT_RANGE, "1,4096,1,or_greater,exp,suffix:px"), "set_max_distance", "get_max_distance"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "attenuation", PROPERTY_HINT_EXP_EASING, "attenuation"), "set_attenuation", "get_attenuation"); ADD_PROPERTY(PropertyInfo(Variant::INT, "max_polyphony", PROPERTY_HINT_NONE, ""), "set_max_polyphony", "get_max_polyphony"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "panning_strength", PROPERTY_HINT_RANGE, "0,3,0.01,or_greater"), "set_panning_strength", "get_panning_strength"); ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "bus", PROPERTY_HINT_ENUM, ""), "set_bus", "get_bus"); ADD_PROPERTY(PropertyInfo(Variant::INT, "area_mask", PROPERTY_HINT_LAYERS_2D_PHYSICS), "set_area_mask", "get_area_mask"); @@ -451,6 +472,7 @@ void AudioStreamPlayer2D::_bind_methods() { AudioStreamPlayer2D::AudioStreamPlayer2D() { AudioServer::get_singleton()->connect("bus_layout_changed", callable_mp(this, &AudioStreamPlayer2D::_bus_layout_changed)); + cached_global_panning_strength = ProjectSettings::get_singleton()->get("audio/general/2d_panning_strength"); } AudioStreamPlayer2D::~AudioStreamPlayer2D() { diff --git a/scene/2d/audio_stream_player_2d.h b/scene/2d/audio_stream_player_2d.h index 73b09e432f..a22782fe44 100644 --- a/scene/2d/audio_stream_player_2d.h +++ b/scene/2d/audio_stream_player_2d.h @@ -81,6 +81,9 @@ private: float max_distance = 2000.0; float attenuation = 1.0; + float panning_strength = 1.0f; + float cached_global_panning_strength = 1.0f; + protected: void _validate_property(PropertyInfo &property) const override; void _notification(int p_what); @@ -123,6 +126,9 @@ public: void set_max_polyphony(int p_max_polyphony); int get_max_polyphony() const; + void set_panning_strength(float p_panning_strength); + float get_panning_strength() const; + Ref<AudioStreamPlayback> get_stream_playback(); AudioStreamPlayer2D(); diff --git a/scene/2d/back_buffer_copy.cpp b/scene/2d/back_buffer_copy.cpp index c411aaf411..aa4ae01fd9 100644 --- a/scene/2d/back_buffer_copy.cpp +++ b/scene/2d/back_buffer_copy.cpp @@ -85,7 +85,7 @@ void BackBufferCopy::_bind_methods() { ClassDB::bind_method(D_METHOD("get_copy_mode"), &BackBufferCopy::get_copy_mode); ADD_PROPERTY(PropertyInfo(Variant::INT, "copy_mode", PROPERTY_HINT_ENUM, "Disabled,Rect,Viewport"), "set_copy_mode", "get_copy_mode"); - ADD_PROPERTY(PropertyInfo(Variant::RECT2, "rect"), "set_rect", "get_rect"); + ADD_PROPERTY(PropertyInfo(Variant::RECT2, "rect", PROPERTY_HINT_NONE, "suffix:px"), "set_rect", "get_rect"); BIND_ENUM_CONSTANT(COPY_MODE_DISABLED); BIND_ENUM_CONSTANT(COPY_MODE_RECT); diff --git a/scene/2d/camera_2d.cpp b/scene/2d/camera_2d.cpp index efde8d8a2b..76b354805c 100644 --- a/scene/2d/camera_2d.cpp +++ b/scene/2d/camera_2d.cpp @@ -57,7 +57,7 @@ void Camera2D::_update_scroll() { Size2 screen_size = _get_camera_screen_size(); Point2 screen_offset = (anchor_mode == ANCHOR_MODE_DRAG_CENTER ? (screen_size * 0.5) : Point2()); - get_tree()->call_group_flags(SceneTree::GROUP_CALL_REALTIME, group_name, "_camera_moved", xform, screen_offset); + get_tree()->call_group(group_name, "_camera_moved", xform, screen_offset); }; } @@ -421,7 +421,7 @@ bool Camera2D::is_current() const { void Camera2D::make_current() { if (is_inside_tree()) { - get_tree()->call_group_flags(SceneTree::GROUP_CALL_REALTIME, group_name, "_make_current", this); + get_tree()->call_group(group_name, "_make_current", this); } else { current = true; } @@ -430,7 +430,7 @@ void Camera2D::make_current() { void Camera2D::clear_current() { if (is_inside_tree()) { - get_tree()->call_group_flags(SceneTree::GROUP_CALL_REALTIME, group_name, "_make_current", (Object *)nullptr); + get_tree()->call_group(group_name, "_make_current", (Object *)nullptr); } else { current = false; } @@ -439,7 +439,7 @@ void Camera2D::clear_current() { void Camera2D::set_limit(Side p_side, int p_limit) { ERR_FAIL_INDEX((int)p_side, 4); limit[p_side] = p_limit; - update(); + _update_scroll(); } int Camera2D::get_limit(Side p_side) const { @@ -729,24 +729,24 @@ void Camera2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_margin_drawing_enabled", "margin_drawing_enabled"), &Camera2D::set_margin_drawing_enabled); ClassDB::bind_method(D_METHOD("is_margin_drawing_enabled"), &Camera2D::is_margin_drawing_enabled); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "offset"), "set_offset", "get_offset"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "offset", PROPERTY_HINT_NONE, "suffix:px"), "set_offset", "get_offset"); ADD_PROPERTY(PropertyInfo(Variant::INT, "anchor_mode", PROPERTY_HINT_ENUM, "Fixed TopLeft,Drag Center"), "set_anchor_mode", "get_anchor_mode"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "rotating"), "set_rotating", "is_rotating"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "current"), "set_current", "is_current"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "zoom"), "set_zoom", "get_zoom"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "zoom", PROPERTY_HINT_LINK), "set_zoom", "get_zoom"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "custom_viewport", PROPERTY_HINT_RESOURCE_TYPE, "Viewport", PROPERTY_USAGE_NONE), "set_custom_viewport", "get_custom_viewport"); ADD_PROPERTY(PropertyInfo(Variant::INT, "process_callback", PROPERTY_HINT_ENUM, "Physics,Idle"), "set_process_callback", "get_process_callback"); ADD_GROUP("Limit", "limit_"); - ADD_PROPERTYI(PropertyInfo(Variant::INT, "limit_left"), "set_limit", "get_limit", SIDE_LEFT); - ADD_PROPERTYI(PropertyInfo(Variant::INT, "limit_top"), "set_limit", "get_limit", SIDE_TOP); - ADD_PROPERTYI(PropertyInfo(Variant::INT, "limit_right"), "set_limit", "get_limit", SIDE_RIGHT); - ADD_PROPERTYI(PropertyInfo(Variant::INT, "limit_bottom"), "set_limit", "get_limit", SIDE_BOTTOM); + ADD_PROPERTYI(PropertyInfo(Variant::INT, "limit_left", PROPERTY_HINT_NONE, "suffix:px"), "set_limit", "get_limit", SIDE_LEFT); + ADD_PROPERTYI(PropertyInfo(Variant::INT, "limit_top", PROPERTY_HINT_NONE, "suffix:px"), "set_limit", "get_limit", SIDE_TOP); + ADD_PROPERTYI(PropertyInfo(Variant::INT, "limit_right", PROPERTY_HINT_NONE, "suffix:px"), "set_limit", "get_limit", SIDE_RIGHT); + ADD_PROPERTYI(PropertyInfo(Variant::INT, "limit_bottom", PROPERTY_HINT_NONE, "suffix:px"), "set_limit", "get_limit", SIDE_BOTTOM); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "limit_smoothed"), "set_limit_smoothing_enabled", "is_limit_smoothing_enabled"); ADD_GROUP("Smoothing", "smoothing_"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "smoothing_enabled"), "set_enable_follow_smoothing", "is_follow_smoothing_enabled"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "smoothing_speed"), "set_follow_smoothing", "get_follow_smoothing"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "smoothing_speed", PROPERTY_HINT_NONE, "suffix:px/s"), "set_follow_smoothing", "get_follow_smoothing"); ADD_GROUP("Drag", "drag_"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "drag_horizontal_enabled"), "set_drag_horizontal_enabled", "is_drag_horizontal_enabled"); diff --git a/scene/2d/canvas_group.cpp b/scene/2d/canvas_group.cpp index 37a858330c..bbf3fff0ad 100644 --- a/scene/2d/canvas_group.cpp +++ b/scene/2d/canvas_group.cpp @@ -75,8 +75,8 @@ void CanvasGroup::_bind_methods() { ClassDB::bind_method(D_METHOD("is_using_mipmaps"), &CanvasGroup::is_using_mipmaps); ADD_GROUP("Tweaks", ""); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "fit_margin", PROPERTY_HINT_RANGE, "0,1024,1.0,or_greater"), "set_fit_margin", "get_fit_margin"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "clear_margin", PROPERTY_HINT_RANGE, "0,1024,1.0,or_greater"), "set_clear_margin", "get_clear_margin"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "fit_margin", PROPERTY_HINT_RANGE, "0,1024,1.0,or_greater,suffix:px"), "set_fit_margin", "get_fit_margin"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "clear_margin", PROPERTY_HINT_RANGE, "0,1024,1.0,or_greater,suffix:px"), "set_clear_margin", "get_clear_margin"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_mipmaps"), "set_use_mipmaps", "is_using_mipmaps"); } diff --git a/scene/2d/collision_object_2d.cpp b/scene/2d/collision_object_2d.cpp index 50863f2c4d..a8c12f4893 100644 --- a/scene/2d/collision_object_2d.cpp +++ b/scene/2d/collision_object_2d.cpp @@ -606,7 +606,7 @@ void CollisionObject2D::_bind_methods() { ADD_SIGNAL(MethodInfo("mouse_shape_entered", PropertyInfo(Variant::INT, "shape_idx"))); ADD_SIGNAL(MethodInfo("mouse_shape_exited", PropertyInfo(Variant::INT, "shape_idx"))); - ADD_PROPERTY(PropertyInfo(Variant::INT, "disable_mode", PROPERTY_HINT_ENUM, "Remove,MakeStatic,KeepActive"), "set_disable_mode", "get_disable_mode"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "disable_mode", PROPERTY_HINT_ENUM, "Remove,Make Static,Keep Active"), "set_disable_mode", "get_disable_mode"); ADD_GROUP("Collision", "collision_"); ADD_PROPERTY(PropertyInfo(Variant::INT, "collision_layer", PROPERTY_HINT_LAYERS_2D_PHYSICS), "set_collision_layer", "get_collision_layer"); diff --git a/scene/2d/collision_object_2d.h b/scene/2d/collision_object_2d.h index f2b7eecc7b..997afee6c4 100644 --- a/scene/2d/collision_object_2d.h +++ b/scene/2d/collision_object_2d.h @@ -75,7 +75,7 @@ private: int total_subshapes = 0; - Map<uint32_t, ShapeData> shapes; + RBMap<uint32_t, ShapeData> shapes; bool only_update_transform_changes = false; // This is used for sync to physics. void _apply_disabled(); diff --git a/scene/2d/collision_polygon_2d.cpp b/scene/2d/collision_polygon_2d.cpp index c8986e3c94..8df29851e5 100644 --- a/scene/2d/collision_polygon_2d.cpp +++ b/scene/2d/collision_polygon_2d.cpp @@ -32,6 +32,7 @@ #include "collision_object_2d.h" #include "core/math/geometry_2d.h" +#include "scene/2d/area_2d.h" #include "scene/resources/concave_polygon_shape_2d.h" #include "scene/resources/convex_polygon_shape_2d.h" @@ -254,6 +255,9 @@ TypedArray<String> CollisionPolygon2D::get_configuration_warnings() const { warnings.push_back(RTR("Invalid polygon. At least 2 points are needed in 'Segments' build mode.")); } } + if (one_way_collision && Object::cast_to<Area2D>(get_parent())) { + warnings.push_back(RTR("The One Way Collision property will be ignored when the parent is an Area2D.")); + } return warnings; } @@ -276,6 +280,7 @@ void CollisionPolygon2D::set_one_way_collision(bool p_enable) { if (parent) { parent->shape_owner_set_one_way_collision(owner_id, p_enable); } + update_configuration_warnings(); } bool CollisionPolygon2D::is_one_way_collision_enabled() const { @@ -310,7 +315,7 @@ void CollisionPolygon2D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::PACKED_VECTOR2_ARRAY, "polygon"), "set_polygon", "get_polygon"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "disabled"), "set_disabled", "is_disabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "one_way_collision"), "set_one_way_collision", "is_one_way_collision_enabled"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "one_way_collision_margin", PROPERTY_HINT_RANGE, "0,128,0.1"), "set_one_way_collision_margin", "get_one_way_collision_margin"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "one_way_collision_margin", PROPERTY_HINT_RANGE, "0,128,0.1,suffix:px"), "set_one_way_collision_margin", "get_one_way_collision_margin"); BIND_ENUM_CONSTANT(BUILD_SOLIDS); BIND_ENUM_CONSTANT(BUILD_SEGMENTS); diff --git a/scene/2d/collision_shape_2d.cpp b/scene/2d/collision_shape_2d.cpp index dd47ae6cb5..9c0c26f6d9 100644 --- a/scene/2d/collision_shape_2d.cpp +++ b/scene/2d/collision_shape_2d.cpp @@ -31,6 +31,7 @@ #include "collision_shape_2d.h" #include "collision_object_2d.h" +#include "scene/2d/area_2d.h" #include "scene/resources/concave_polygon_shape_2d.h" #include "scene/resources/convex_polygon_shape_2d.h" @@ -176,6 +177,9 @@ TypedArray<String> CollisionShape2D::get_configuration_warnings() const { if (!shape.is_valid()) { warnings.push_back(RTR("A shape must be provided for CollisionShape2D to function. Please create a shape resource for it!")); } + if (one_way_collision && Object::cast_to<Area2D>(get_parent())) { + warnings.push_back(RTR("The One Way Collision property will be ignored when the parent is an Area2D.")); + } Ref<ConvexPolygonShape2D> convex = shape; Ref<ConcavePolygonShape2D> concave = shape; @@ -204,6 +208,7 @@ void CollisionShape2D::set_one_way_collision(bool p_enable) { if (parent) { parent->shape_owner_set_one_way_collision(owner_id, p_enable); } + update_configuration_warnings(); } bool CollisionShape2D::is_one_way_collision_enabled() const { @@ -234,7 +239,7 @@ void CollisionShape2D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "shape", PROPERTY_HINT_RESOURCE_TYPE, "Shape2D"), "set_shape", "get_shape"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "disabled"), "set_disabled", "is_disabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "one_way_collision"), "set_one_way_collision", "is_one_way_collision_enabled"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "one_way_collision_margin", PROPERTY_HINT_RANGE, "0,128,0.1"), "set_one_way_collision_margin", "get_one_way_collision_margin"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "one_way_collision_margin", PROPERTY_HINT_RANGE, "0,128,0.1,suffix:px"), "set_one_way_collision_margin", "get_one_way_collision_margin"); } CollisionShape2D::CollisionShape2D() { diff --git a/scene/2d/cpu_particles_2d.cpp b/scene/2d/cpu_particles_2d.cpp index bad1488d5a..4155d0797f 100644 --- a/scene/2d/cpu_particles_2d.cpp +++ b/scene/2d/cpu_particles_2d.cpp @@ -243,7 +243,7 @@ bool CPUParticles2D::get_fractional_delta() const { } TypedArray<String> CPUParticles2D::get_configuration_warnings() const { - TypedArray<String> warnings = Node::get_configuration_warnings(); + TypedArray<String> warnings = Node2D::get_configuration_warnings(); CanvasItemMaterial *mat = Object::cast_to<CanvasItemMaterial>(get_material().ptr()); @@ -314,6 +314,8 @@ void CPUParticles2D::set_param_max(Parameter p_param, real_t p_value) { if (parameters_min[p_param] > parameters_max[p_param]) { set_param_min(p_param, p_value); } + + update_configuration_warnings(); } real_t CPUParticles2D::get_param_max(Parameter p_param) const { @@ -374,6 +376,8 @@ void CPUParticles2D::set_param_curve(Parameter p_param, const Ref<Curve> &p_curv default: { } } + + update_configuration_warnings(); } Ref<Curve> CPUParticles2D::get_param_curve(Parameter p_param) const { @@ -743,7 +747,7 @@ void CPUParticles2D::_particles_process(double p_delta) { real_t angle1_rad = direction.angle() + Math::deg2rad((Math::randf() * 2.0 - 1.0) * spread); Vector2 rot = Vector2(Math::cos(angle1_rad), Math::sin(angle1_rad)); - p.velocity = rot * Math::lerp(parameters_min[PARAM_INITIAL_LINEAR_VELOCITY], parameters_min[PARAM_INITIAL_LINEAR_VELOCITY], (real_t)Math::randf()); + p.velocity = rot * Math::lerp(parameters_min[PARAM_INITIAL_LINEAR_VELOCITY], parameters_max[PARAM_INITIAL_LINEAR_VELOCITY], (real_t)Math::randf()); real_t base_angle = tex_angle * Math::lerp(parameters_min[PARAM_ANGLE], parameters_max[PARAM_ANGLE], p.angle_rand); p.rotation = Math::deg2rad(base_angle); @@ -788,8 +792,8 @@ void CPUParticles2D::_particles_process(double p_delta) { if (emission_shape == EMISSION_SHAPE_DIRECTED_POINTS && emission_normals.size() == pc) { Vector2 normal = emission_normals.get(random_idx); Transform2D m2; - m2.set_axis(0, normal); - m2.set_axis(1, normal.orthogonal()); + m2.columns[0] = normal; + m2.columns[1] = normal.orthogonal(); p.velocity = m2.basis_xform(p.velocity); } @@ -969,13 +973,13 @@ void CPUParticles2D::_particles_process(double p_delta) { if (particle_flags[PARTICLE_FLAG_ALIGN_Y_TO_VELOCITY]) { if (p.velocity.length() > 0.0) { - p.transform.elements[1] = p.velocity.normalized(); - p.transform.elements[0] = p.transform.elements[1].orthogonal(); + p.transform.columns[1] = p.velocity.normalized(); + p.transform.columns[0] = p.transform.columns[1].orthogonal(); } } else { - p.transform.elements[0] = Vector2(Math::cos(p.rotation), -Math::sin(p.rotation)); - p.transform.elements[1] = Vector2(Math::sin(p.rotation), Math::cos(p.rotation)); + p.transform.columns[0] = Vector2(Math::cos(p.rotation), -Math::sin(p.rotation)); + p.transform.columns[1] = Vector2(Math::sin(p.rotation), Math::cos(p.rotation)); } //scale by scale @@ -986,8 +990,8 @@ void CPUParticles2D::_particles_process(double p_delta) { if (base_scale.y < 0.00001) { base_scale.y = 0.00001; } - p.transform.elements[0] *= base_scale.x; - p.transform.elements[1] *= base_scale.y; + p.transform.columns[0] *= base_scale.x; + p.transform.columns[1] *= base_scale.y; p.transform[2] += p.velocity * local_delta; } @@ -1029,14 +1033,14 @@ void CPUParticles2D::_update_particle_data_buffer() { } if (r[idx].active) { - ptr[0] = t.elements[0][0]; - ptr[1] = t.elements[1][0]; + ptr[0] = t.columns[0][0]; + ptr[1] = t.columns[1][0]; ptr[2] = 0; - ptr[3] = t.elements[2][0]; - ptr[4] = t.elements[0][1]; - ptr[5] = t.elements[1][1]; + ptr[3] = t.columns[2][0]; + ptr[4] = t.columns[0][1]; + ptr[5] = t.columns[1][1]; ptr[6] = 0; - ptr[7] = t.elements[2][1]; + ptr[7] = t.columns[2][1]; } else { memset(ptr, 0, sizeof(float) * 8); @@ -1137,14 +1141,14 @@ void CPUParticles2D::_notification(int p_what) { Transform2D t = inv_emission_transform * r[i].transform; if (r[i].active) { - ptr[0] = t.elements[0][0]; - ptr[1] = t.elements[1][0]; + ptr[0] = t.columns[0][0]; + ptr[1] = t.columns[1][0]; ptr[2] = 0; - ptr[3] = t.elements[2][0]; - ptr[4] = t.elements[0][1]; - ptr[5] = t.elements[1][1]; + ptr[3] = t.columns[2][0]; + ptr[4] = t.columns[0][1]; + ptr[5] = t.columns[1][1]; ptr[6] = 0; - ptr[7] = t.elements[2][1]; + ptr[7] = t.columns[2][1]; } else { memset(ptr, 0, sizeof(float) * 8); @@ -1282,14 +1286,14 @@ void CPUParticles2D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "emitting"), "set_emitting", "is_emitting"); ADD_PROPERTY(PropertyInfo(Variant::INT, "amount", PROPERTY_HINT_RANGE, "1,1000000,1,exp"), "set_amount", "get_amount"); ADD_GROUP("Time", ""); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "lifetime", PROPERTY_HINT_RANGE, "0.01,600.0,0.01,or_greater"), "set_lifetime", "get_lifetime"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "lifetime", PROPERTY_HINT_RANGE, "0.01,600.0,0.01,or_greater,suffix:s"), "set_lifetime", "get_lifetime"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "one_shot"), "set_one_shot", "get_one_shot"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "preprocess", PROPERTY_HINT_RANGE, "0.00,600.0,0.01"), "set_pre_process_time", "get_pre_process_time"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "preprocess", PROPERTY_HINT_RANGE, "0.00,600.0,0.01,suffix:s"), "set_pre_process_time", "get_pre_process_time"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "speed_scale", PROPERTY_HINT_RANGE, "0,64,0.01"), "set_speed_scale", "get_speed_scale"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "explosiveness", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_explosiveness_ratio", "get_explosiveness_ratio"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "randomness", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_randomness_ratio", "get_randomness_ratio"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "lifetime_randomness", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_lifetime_randomness", "get_lifetime_randomness"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "fixed_fps", PROPERTY_HINT_RANGE, "0,1000,1"), "set_fixed_fps", "get_fixed_fps"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "fixed_fps", PROPERTY_HINT_RANGE, "0,1000,1,suffix:FPS"), "set_fixed_fps", "get_fixed_fps"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "fract_delta"), "set_fractional_delta", "get_fractional_delta"); ADD_GROUP("Drawing", ""); // No visibility_rect property contrarily to Particles2D, it's updated automatically. @@ -1305,7 +1309,7 @@ void CPUParticles2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_direction", "direction"), &CPUParticles2D::set_direction); ClassDB::bind_method(D_METHOD("get_direction"), &CPUParticles2D::get_direction); - ClassDB::bind_method(D_METHOD("set_spread", "degrees"), &CPUParticles2D::set_spread); + ClassDB::bind_method(D_METHOD("set_spread", "spread"), &CPUParticles2D::set_spread); ClassDB::bind_method(D_METHOD("get_spread"), &CPUParticles2D::get_spread); ClassDB::bind_method(D_METHOD("set_param_min", "param", "value"), &CPUParticles2D::set_param_min); @@ -1362,9 +1366,9 @@ void CPUParticles2D::_bind_methods() { ClassDB::bind_method(D_METHOD("convert_from_particles", "particles"), &CPUParticles2D::convert_from_particles); ADD_GROUP("Emission Shape", "emission_"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "emission_shape", PROPERTY_HINT_ENUM, "Point,Sphere,Sphere Surface,Box,Points,Directed Points", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), "set_emission_shape", "get_emission_shape"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "emission_sphere_radius", PROPERTY_HINT_RANGE, "0.01,128,0.01"), "set_emission_sphere_radius", "get_emission_sphere_radius"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "emission_rect_extents"), "set_emission_rect_extents", "get_emission_rect_extents"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "emission_shape", PROPERTY_HINT_ENUM, "Point,Sphere,Sphere Surface,Rectangle,Points,Directed Points", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), "set_emission_shape", "get_emission_shape"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "emission_sphere_radius", PROPERTY_HINT_RANGE, "0.01,128,0.01,suffix:px"), "set_emission_sphere_radius", "get_emission_sphere_radius"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "emission_rect_extents", PROPERTY_HINT_NONE, "suffix:px"), "set_emission_rect_extents", "get_emission_rect_extents"); ADD_PROPERTY(PropertyInfo(Variant::PACKED_VECTOR2_ARRAY, "emission_points"), "set_emission_points", "get_emission_points"); ADD_PROPERTY(PropertyInfo(Variant::PACKED_VECTOR2_ARRAY, "emission_normals"), "set_emission_normals", "get_emission_normals"); ADD_PROPERTY(PropertyInfo(Variant::PACKED_COLOR_ARRAY, "emission_colors"), "set_emission_colors", "get_emission_colors"); @@ -1374,10 +1378,10 @@ void CPUParticles2D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "direction"), "set_direction", "get_direction"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "spread", PROPERTY_HINT_RANGE, "0,180,0.01"), "set_spread", "get_spread"); ADD_GROUP("Gravity", ""); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "gravity"), "set_gravity", "get_gravity"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "gravity", PROPERTY_HINT_NONE, U"suffix:px/s\u00B2"), "set_gravity", "get_gravity"); ADD_GROUP("Initial Velocity", "initial_"); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "initial_velocity_min", PROPERTY_HINT_RANGE, "0,1000,0.01,or_greater"), "set_param_min", "get_param_min", PARAM_INITIAL_LINEAR_VELOCITY); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "initial_velocity_max", PROPERTY_HINT_RANGE, "0,1000,0.01,or_greater"), "set_param_max", "get_param_max", PARAM_INITIAL_LINEAR_VELOCITY); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "initial_velocity_min", PROPERTY_HINT_RANGE, "0,1000,0.01,or_greater,suffix:px/s"), "set_param_min", "get_param_min", PARAM_INITIAL_LINEAR_VELOCITY); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "initial_velocity_max", PROPERTY_HINT_RANGE, "0,1000,0.01,or_greater,suffix:px/s"), "set_param_max", "get_param_max", PARAM_INITIAL_LINEAR_VELOCITY); ADD_GROUP("Angular Velocity", "angular_"); ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angular_velocity_min", PROPERTY_HINT_RANGE, "-720,720,0.01,or_lesser,or_greater"), "set_param_min", "get_param_min", PARAM_ANGULAR_VELOCITY); ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angular_velocity_max", PROPERTY_HINT_RANGE, "-720,720,0.01,or_lesser,or_greater"), "set_param_max", "get_param_max", PARAM_ANGULAR_VELOCITY); diff --git a/scene/2d/cpu_particles_2d.h b/scene/2d/cpu_particles_2d.h index 7ae51e3966..51d58723b4 100644 --- a/scene/2d/cpu_particles_2d.h +++ b/scene/2d/cpu_particles_2d.h @@ -137,7 +137,7 @@ private: real_t randomness_ratio = 0.0; double lifetime_randomness = 0.0; double speed_scale = 1.0; - bool local_coords; + bool local_coords = false; int fixed_fps = 0; bool fractional_delta = true; diff --git a/scene/2d/gpu_particles_2d.cpp b/scene/2d/gpu_particles_2d.cpp index c69eeb52a8..a869cf2525 100644 --- a/scene/2d/gpu_particles_2d.cpp +++ b/scene/2d/gpu_particles_2d.cpp @@ -114,8 +114,8 @@ void GPUParticles2D::set_use_local_coordinates(bool p_enable) { void GPUParticles2D::_update_particle_emission_transform() { Transform2D xf2d = get_global_transform(); Transform3D xf; - xf.basis.set_axis(0, Vector3(xf2d.get_axis(0).x, xf2d.get_axis(0).y, 0)); - xf.basis.set_axis(1, Vector3(xf2d.get_axis(1).x, xf2d.get_axis(1).y, 0)); + xf.basis.set_column(0, Vector3(xf2d.columns[0].x, xf2d.columns[0].y, 0)); + xf.basis.set_column(1, Vector3(xf2d.columns[1].x, xf2d.columns[1].y, 0)); xf.set_origin(Vector3(xf2d.get_origin().x, xf2d.get_origin().y, 0)); RS::get_singleton()->particles_set_emission_transform(particles, xf); @@ -141,7 +141,6 @@ void GPUParticles2D::set_process_material(const Ref<Material> &p_material) { void GPUParticles2D::set_trail_enabled(bool p_enabled) { trail_enabled = p_enabled; RS::get_singleton()->particles_set_trails(particles, trail_enabled, trail_length); - update_configuration_warnings(); update(); RS::get_singleton()->particles_set_transform_align(particles, p_enabled ? RS::PARTICLES_TRANSFORM_ALIGN_Y_TO_VELOCITY : RS::PARTICLES_TRANSFORM_ALIGN_DISABLED); @@ -287,8 +286,17 @@ bool GPUParticles2D::get_fractional_delta() const { return fractional_delta; } +void GPUParticles2D::set_interpolate(bool p_enable) { + interpolate = p_enable; + RS::get_singleton()->particles_set_interpolate(particles, p_enable); +} + +bool GPUParticles2D::get_interpolate() const { + return interpolate; +} + TypedArray<String> GPUParticles2D::get_configuration_warnings() const { - TypedArray<String> warnings = Node::get_configuration_warnings(); + TypedArray<String> warnings = Node2D::get_configuration_warnings(); if (RenderingServer::get_singleton()->is_low_end()) { warnings.push_back(RTR("GPU-based particles are not supported by the OpenGL video driver.\nUse the CPUParticles2D node instead. You can use the \"Convert to CPUParticles2D\" option for this purpose.")); @@ -337,8 +345,8 @@ void GPUParticles2D::_validate_property(PropertyInfo &property) const { void GPUParticles2D::emit_particle(const Transform2D &p_transform2d, const Vector2 &p_velocity2d, const Color &p_color, const Color &p_custom, uint32_t p_emit_flags) { Transform3D transform; - transform.basis.set_axis(0, Vector3(p_transform2d.get_axis(0).x, p_transform2d.get_axis(0).y, 0)); - transform.basis.set_axis(1, Vector3(p_transform2d.get_axis(1).x, p_transform2d.get_axis(1).y, 0)); + transform.basis.set_column(0, Vector3(p_transform2d.columns[0].x, p_transform2d.columns[0].y, 0)); + transform.basis.set_column(1, Vector3(p_transform2d.columns[1].x, p_transform2d.columns[1].y, 0)); transform.set_origin(Vector3(p_transform2d.get_origin().x, p_transform2d.get_origin().y, 0)); Vector3 velocity = Vector3(p_velocity2d.x, p_velocity2d.y, 0); @@ -543,6 +551,7 @@ void GPUParticles2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_use_local_coordinates", "enable"), &GPUParticles2D::set_use_local_coordinates); ClassDB::bind_method(D_METHOD("set_fixed_fps", "fps"), &GPUParticles2D::set_fixed_fps); ClassDB::bind_method(D_METHOD("set_fractional_delta", "enable"), &GPUParticles2D::set_fractional_delta); + ClassDB::bind_method(D_METHOD("set_interpolate", "enable"), &GPUParticles2D::set_interpolate); ClassDB::bind_method(D_METHOD("set_process_material", "material"), &GPUParticles2D::set_process_material); ClassDB::bind_method(D_METHOD("set_speed_scale", "scale"), &GPUParticles2D::set_speed_scale); ClassDB::bind_method(D_METHOD("set_collision_base_size", "size"), &GPUParticles2D::set_collision_base_size); @@ -558,6 +567,7 @@ void GPUParticles2D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_use_local_coordinates"), &GPUParticles2D::get_use_local_coordinates); ClassDB::bind_method(D_METHOD("get_fixed_fps"), &GPUParticles2D::get_fixed_fps); ClassDB::bind_method(D_METHOD("get_fractional_delta"), &GPUParticles2D::get_fractional_delta); + ClassDB::bind_method(D_METHOD("get_interpolate"), &GPUParticles2D::get_interpolate); ClassDB::bind_method(D_METHOD("get_process_material"), &GPUParticles2D::get_process_material); ClassDB::bind_method(D_METHOD("get_speed_scale"), &GPUParticles2D::get_speed_scale); ClassDB::bind_method(D_METHOD("get_collision_base_size"), &GPUParticles2D::get_collision_base_size); @@ -594,23 +604,24 @@ void GPUParticles2D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::INT, "amount", PROPERTY_HINT_RANGE, "1,1000000,1,exp"), "set_amount", "get_amount"); ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "sub_emitter", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "GPUParticles2D"), "set_sub_emitter", "get_sub_emitter"); ADD_GROUP("Time", ""); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "lifetime", PROPERTY_HINT_RANGE, "0.01,600.0,0.01,or_greater"), "set_lifetime", "get_lifetime"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "lifetime", PROPERTY_HINT_RANGE, "0.01,600.0,0.01,or_greater,suffix:s"), "set_lifetime", "get_lifetime"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "one_shot"), "set_one_shot", "get_one_shot"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "preprocess", PROPERTY_HINT_RANGE, "0.00,600.0,0.01"), "set_pre_process_time", "get_pre_process_time"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "preprocess", PROPERTY_HINT_RANGE, "0.00,600.0,0.01,suffix:s"), "set_pre_process_time", "get_pre_process_time"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "speed_scale", PROPERTY_HINT_RANGE, "0,64,0.01"), "set_speed_scale", "get_speed_scale"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "explosiveness", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_explosiveness_ratio", "get_explosiveness_ratio"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "randomness", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_randomness_ratio", "get_randomness_ratio"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "fixed_fps", PROPERTY_HINT_RANGE, "0,1000,1"), "set_fixed_fps", "get_fixed_fps"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "fixed_fps", PROPERTY_HINT_RANGE, "0,1000,1,suffix:FPS"), "set_fixed_fps", "get_fixed_fps"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "interpolate"), "set_interpolate", "get_interpolate"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "fract_delta"), "set_fractional_delta", "get_fractional_delta"); ADD_GROUP("Collision", "collision_"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "collision_base_size", PROPERTY_HINT_RANGE, "0,128,0.01,or_greater"), "set_collision_base_size", "get_collision_base_size"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "collision_base_size", PROPERTY_HINT_RANGE, "0,128,0.01,or_greater,suffix:px"), "set_collision_base_size", "get_collision_base_size"); ADD_GROUP("Drawing", ""); - ADD_PROPERTY(PropertyInfo(Variant::RECT2, "visibility_rect"), "set_visibility_rect", "get_visibility_rect"); + ADD_PROPERTY(PropertyInfo(Variant::RECT2, "visibility_rect", PROPERTY_HINT_NONE, "suffix:px"), "set_visibility_rect", "get_visibility_rect"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "local_coords"), "set_use_local_coordinates", "get_use_local_coordinates"); ADD_PROPERTY(PropertyInfo(Variant::INT, "draw_order", PROPERTY_HINT_ENUM, "Index,Lifetime,Reverse Lifetime"), "set_draw_order", "get_draw_order"); ADD_GROUP("Trails", "trail_"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "trail_enabled"), "set_trail_enabled", "is_trail_enabled"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "trail_length_secs", PROPERTY_HINT_RANGE, "0.01,10,0.01"), "set_trail_length", "get_trail_length"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "trail_length_secs", PROPERTY_HINT_RANGE, "0.01,10,0.01,suffix:s"), "set_trail_length", "get_trail_length"); ADD_PROPERTY(PropertyInfo(Variant::INT, "trail_sections", PROPERTY_HINT_RANGE, "2,128,1"), "set_trail_sections", "get_trail_sections"); ADD_PROPERTY(PropertyInfo(Variant::INT, "trail_section_subdivisions", PROPERTY_HINT_RANGE, "1,1024,1"), "set_trail_section_subdivisions", "get_trail_section_subdivisions"); ADD_GROUP("Process Material", "process_"); @@ -644,6 +655,7 @@ GPUParticles2D::GPUParticles2D() { set_lifetime(1); set_fixed_fps(0); set_fractional_delta(true); + set_interpolate(true); set_pre_process_time(0); set_explosiveness_ratio(0); set_randomness_ratio(0); @@ -653,9 +665,6 @@ GPUParticles2D::GPUParticles2D() { set_speed_scale(1); set_fixed_fps(30); set_collision_base_size(collision_base_size); -#ifdef TOOLS_ENABLED - show_visibility_rect = false; -#endif } GPUParticles2D::~GPUParticles2D() { diff --git a/scene/2d/gpu_particles_2d.h b/scene/2d/gpu_particles_2d.h index fc95ae27b2..3c7f4cd9b5 100644 --- a/scene/2d/gpu_particles_2d.h +++ b/scene/2d/gpu_particles_2d.h @@ -47,19 +47,20 @@ public: private: RID particles; - bool one_shot; - int amount; - double lifetime; - double pre_process_time; - real_t explosiveness_ratio; - real_t randomness_ratio; - double speed_scale; + bool one_shot = false; + int amount = 0; + double lifetime = 0.0; + double pre_process_time = 0.0; + real_t explosiveness_ratio = 0.0; + real_t randomness_ratio = 0.0; + double speed_scale = 0.0; Rect2 visibility_rect; - bool local_coords; - int fixed_fps; - bool fractional_delta; + bool local_coords = false; + int fixed_fps = 0; + bool fractional_delta = false; + bool interpolate = true; #ifdef TOOLS_ENABLED - bool show_visibility_rect; + bool show_visibility_rect = false; #endif Ref<Material> process_material; @@ -133,6 +134,9 @@ public: void set_fractional_delta(bool p_enable); bool get_fractional_delta() const; + void set_interpolate(bool p_enable); + bool get_interpolate() const; + void set_draw_order(DrawOrder p_order); DrawOrder get_draw_order() const; diff --git a/scene/2d/joint_2d.cpp b/scene/2d/joint_2d.cpp index cd82b47333..7b9f7e14ca 100644 --- a/scene/2d/joint_2d.cpp +++ b/scene/2d/joint_2d.cpp @@ -50,6 +50,7 @@ void Joint2D::_disconnect_signals() { void Joint2D::_body_exit_tree() { _disconnect_signals(); _update_joint(true); + update_configuration_warnings(); } void Joint2D::_update_joint(bool p_only_free) { @@ -64,7 +65,6 @@ void Joint2D::_update_joint(bool p_only_free) { if (p_only_free || !is_inside_tree()) { PhysicsServer2D::get_singleton()->joint_clear(joint); warning = String(); - update_configuration_warnings(); return; } @@ -343,8 +343,8 @@ void GrooveJoint2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_initial_offset", "offset"), &GrooveJoint2D::set_initial_offset); ClassDB::bind_method(D_METHOD("get_initial_offset"), &GrooveJoint2D::get_initial_offset); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "length", PROPERTY_HINT_RANGE, "1,65535,1,exp"), "set_length", "get_length"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "initial_offset", PROPERTY_HINT_RANGE, "1,65535,1,exp"), "set_initial_offset", "get_initial_offset"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "length", PROPERTY_HINT_RANGE, "1,65535,1,exp,suffix:px"), "set_length", "get_length"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "initial_offset", PROPERTY_HINT_RANGE, "1,65535,1,exp,suffix:px"), "set_initial_offset", "get_initial_offset"); } GrooveJoint2D::GrooveJoint2D() { @@ -440,8 +440,8 @@ void DampedSpringJoint2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_damping", "damping"), &DampedSpringJoint2D::set_damping); ClassDB::bind_method(D_METHOD("get_damping"), &DampedSpringJoint2D::get_damping); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "length", PROPERTY_HINT_RANGE, "1,65535,1,exp"), "set_length", "get_length"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "rest_length", PROPERTY_HINT_RANGE, "0,65535,1,exp"), "set_rest_length", "get_rest_length"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "length", PROPERTY_HINT_RANGE, "1,65535,1,exp,suffix:px"), "set_length", "get_length"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "rest_length", PROPERTY_HINT_RANGE, "0,65535,1,exp,suffix:px"), "set_rest_length", "get_rest_length"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "stiffness", PROPERTY_HINT_RANGE, "0.1,64,0.1,exp"), "set_stiffness", "get_stiffness"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "damping", PROPERTY_HINT_RANGE, "0.01,16,0.01,exp"), "set_damping", "get_damping"); } diff --git a/scene/2d/light_2d.cpp b/scene/2d/light_2d.cpp index b09b1b5047..0481a58431 100644 --- a/scene/2d/light_2d.cpp +++ b/scene/2d/light_2d.cpp @@ -30,6 +30,11 @@ #include "light_2d.h" +void Light2D::owner_changed_notify() { + // For cases where owner changes _after_ entering tree (as example, editor editing). + _update_light_visibility(); +} + void Light2D::_update_light_visibility() { if (!is_inside_tree()) { return; @@ -425,9 +430,9 @@ void PointLight2D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_texture_scale"), &PointLight2D::get_texture_scale); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_texture", "get_texture"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "offset"), "set_texture_offset", "get_texture_offset"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "offset", PROPERTY_HINT_NONE, "suffix:px"), "set_texture_offset", "get_texture_offset"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "texture_scale", PROPERTY_HINT_RANGE, "0.01,50,0.01"), "set_texture_scale", "get_texture_scale"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "height", PROPERTY_HINT_RANGE, "0,1024,1,or_greater"), "set_height", "get_height"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "height", PROPERTY_HINT_RANGE, "0,1024,1,or_greater,suffix:px"), "set_height", "get_height"); } PointLight2D::PointLight2D() { @@ -449,8 +454,8 @@ void DirectionalLight2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_max_distance", "pixels"), &DirectionalLight2D::set_max_distance); ClassDB::bind_method(D_METHOD("get_max_distance"), &DirectionalLight2D::get_max_distance); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "height", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_height", "get_height"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "max_distance", PROPERTY_HINT_RANGE, "0,16384.0,1.0,or_greater"), "set_max_distance", "get_max_distance"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "height", PROPERTY_HINT_RANGE, "0,1,0.01,suffix:px"), "set_height", "get_height"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "max_distance", PROPERTY_HINT_RANGE, "0,16384.0,1.0,or_greater,suffix:px"), "set_max_distance", "get_max_distance"); } DirectionalLight2D::DirectionalLight2D() { diff --git a/scene/2d/light_2d.h b/scene/2d/light_2d.h index f7b1f420e3..a84b6516c0 100644 --- a/scene/2d/light_2d.h +++ b/scene/2d/light_2d.h @@ -73,6 +73,8 @@ private: void _update_light_visibility(); + virtual void owner_changed_notify() override; + protected: _FORCE_INLINE_ RID _get_light() const { return canvas_light; } void _notification(int p_what); diff --git a/scene/2d/light_occluder_2d.cpp b/scene/2d/light_occluder_2d.cpp index c4e57b375d..14188d7120 100644 --- a/scene/2d/light_occluder_2d.cpp +++ b/scene/2d/light_occluder_2d.cpp @@ -285,7 +285,6 @@ void LightOccluder2D::_bind_methods() { LightOccluder2D::LightOccluder2D() { occluder = RS::get_singleton()->canvas_light_occluder_create(); - mask = 1; set_notify_transform(true); set_as_sdf_collision(true); diff --git a/scene/2d/light_occluder_2d.h b/scene/2d/light_occluder_2d.h index 4f8c6d20df..4acfeaf781 100644 --- a/scene/2d/light_occluder_2d.h +++ b/scene/2d/light_occluder_2d.h @@ -81,10 +81,9 @@ class LightOccluder2D : public Node2D { GDCLASS(LightOccluder2D, Node2D); RID occluder; - bool enabled; - int mask; + int mask = 1; Ref<OccluderPolygon2D> occluder_polygon; - bool sdf_collision; + bool sdf_collision = false; void _poly_changed(); protected: diff --git a/scene/2d/line_2d.cpp b/scene/2d/line_2d.cpp index 8cbcc9acf6..06e5cbc97e 100644 --- a/scene/2d/line_2d.cpp +++ b/scene/2d/line_2d.cpp @@ -393,7 +393,7 @@ void Line2D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_antialiased"), &Line2D::get_antialiased); ADD_PROPERTY(PropertyInfo(Variant::PACKED_VECTOR2_ARRAY, "points"), "set_points", "get_points"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "width"), "set_width", "get_width"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "width", PROPERTY_HINT_NONE, "suffix:px"), "set_width", "get_width"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "width_curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_curve", "get_curve"); ADD_PROPERTY(PropertyInfo(Variant::COLOR, "default_color"), "set_default_color", "get_default_color"); ADD_GROUP("Fill", ""); diff --git a/scene/2d/navigation_agent_2d.cpp b/scene/2d/navigation_agent_2d.cpp index 91549d75f0..a5f7faffef 100644 --- a/scene/2d/navigation_agent_2d.cpp +++ b/scene/2d/navigation_agent_2d.cpp @@ -37,6 +37,12 @@ void NavigationAgent2D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_rid"), &NavigationAgent2D::get_rid); + ClassDB::bind_method(D_METHOD("set_avoidance_enabled", "enabled"), &NavigationAgent2D::set_avoidance_enabled); + ClassDB::bind_method(D_METHOD("get_avoidance_enabled"), &NavigationAgent2D::get_avoidance_enabled); + + ClassDB::bind_method(D_METHOD("set_path_desired_distance", "desired_distance"), &NavigationAgent2D::set_path_desired_distance); + ClassDB::bind_method(D_METHOD("get_path_desired_distance"), &NavigationAgent2D::get_path_desired_distance); + ClassDB::bind_method(D_METHOD("set_target_desired_distance", "desired_distance"), &NavigationAgent2D::set_target_desired_distance); ClassDB::bind_method(D_METHOD("get_target_desired_distance"), &NavigationAgent2D::get_target_desired_distance); @@ -58,8 +64,14 @@ void NavigationAgent2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_path_max_distance", "max_speed"), &NavigationAgent2D::set_path_max_distance); ClassDB::bind_method(D_METHOD("get_path_max_distance"), &NavigationAgent2D::get_path_max_distance); - ClassDB::bind_method(D_METHOD("set_navigable_layers", "navigable_layers"), &NavigationAgent2D::set_navigable_layers); - ClassDB::bind_method(D_METHOD("get_navigable_layers"), &NavigationAgent2D::get_navigable_layers); + ClassDB::bind_method(D_METHOD("set_navigation_layers", "navigation_layers"), &NavigationAgent2D::set_navigation_layers); + ClassDB::bind_method(D_METHOD("get_navigation_layers"), &NavigationAgent2D::get_navigation_layers); + + ClassDB::bind_method(D_METHOD("set_navigation_layer_value", "layer_number", "value"), &NavigationAgent2D::set_navigation_layer_value); + ClassDB::bind_method(D_METHOD("get_navigation_layer_value", "layer_number"), &NavigationAgent2D::get_navigation_layer_value); + + ClassDB::bind_method(D_METHOD("set_navigation_map", "navigation_map"), &NavigationAgent2D::set_navigation_map); + ClassDB::bind_method(D_METHOD("get_navigation_map"), &NavigationAgent2D::get_navigation_map); ClassDB::bind_method(D_METHOD("set_target_location", "location"), &NavigationAgent2D::set_target_location); ClassDB::bind_method(D_METHOD("get_target_location"), &NavigationAgent2D::get_target_location); @@ -75,14 +87,19 @@ void NavigationAgent2D::_bind_methods() { ClassDB::bind_method(D_METHOD("_avoidance_done", "new_velocity"), &NavigationAgent2D::_avoidance_done); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "target_desired_distance", PROPERTY_HINT_RANGE, "0.1,100,0.01"), "set_target_desired_distance", "get_target_desired_distance"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radius", PROPERTY_HINT_RANGE, "0.1,500,0.01"), "set_radius", "get_radius"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "neighbor_dist", PROPERTY_HINT_RANGE, "0.1,100000,0.01"), "set_neighbor_dist", "get_neighbor_dist"); + ADD_GROUP("Pathfinding", ""); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "path_desired_distance", PROPERTY_HINT_RANGE, "0.1,100,0.01,suffix:px"), "set_path_desired_distance", "get_path_desired_distance"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "target_desired_distance", PROPERTY_HINT_RANGE, "0.1,100,0.01,suffix:px"), "set_target_desired_distance", "get_target_desired_distance"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "path_max_distance", PROPERTY_HINT_RANGE, "10,100,1,suffix:px"), "set_path_max_distance", "get_path_max_distance"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "navigation_layers", PROPERTY_HINT_LAYERS_2D_NAVIGATION), "set_navigation_layers", "get_navigation_layers"); + + ADD_GROUP("Avoidance", ""); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "avoidance_enabled"), "set_avoidance_enabled", "get_avoidance_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radius", PROPERTY_HINT_RANGE, "0.1,500,0.01,suffix:px"), "set_radius", "get_radius"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "neighbor_dist", PROPERTY_HINT_RANGE, "0.1,100000,0.01,suffix:px"), "set_neighbor_dist", "get_neighbor_dist"); ADD_PROPERTY(PropertyInfo(Variant::INT, "max_neighbors", PROPERTY_HINT_RANGE, "1,10000,1"), "set_max_neighbors", "get_max_neighbors"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "time_horizon", PROPERTY_HINT_RANGE, "0.1,10000,0.01"), "set_time_horizon", "get_time_horizon"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "max_speed", PROPERTY_HINT_RANGE, "0.1,100000,0.01"), "set_max_speed", "get_max_speed"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "path_max_distance", PROPERTY_HINT_RANGE, "10,100,1"), "set_path_max_distance", "get_path_max_distance"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "navigable_layers", PROPERTY_HINT_LAYERS_2D_NAVIGATION), "set_navigable_layers", "get_navigable_layers"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "time_horizon", PROPERTY_HINT_RANGE, "0.1,10000,0.01,suffix:s"), "set_time_horizon", "get_time_horizon"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "max_speed", PROPERTY_HINT_RANGE, "0.1,100000,0.01,suffix:px/s"), "set_max_speed", "get_max_speed"); ADD_SIGNAL(MethodInfo("path_changed")); ADD_SIGNAL(MethodInfo("target_reached")); @@ -92,16 +109,50 @@ void NavigationAgent2D::_bind_methods() { void NavigationAgent2D::_notification(int p_what) { switch (p_what) { - case NOTIFICATION_READY: { - agent_parent = Object::cast_to<Node2D>(get_parent()); - if (agent_parent != nullptr) { - // place agent on navigation map first or else the RVO agent callback creation fails silently later - NavigationServer2D::get_singleton()->agent_set_map(get_rid(), agent_parent->get_world_2d()->get_navigation_map()); - NavigationServer2D::get_singleton()->agent_set_callback(agent, this, "_avoidance_done"); - } + case NOTIFICATION_POST_ENTER_TREE: { + // need to use POST_ENTER_TREE cause with normal ENTER_TREE not all required Nodes are ready. + // cannot use READY as ready does not get called if Node is readded to SceneTree + set_agent_parent(get_parent()); set_physics_process_internal(true); } break; + case NOTIFICATION_PARENTED: { + if (is_inside_tree() && (get_parent() != agent_parent)) { + // only react to PARENTED notifications when already inside_tree and parent changed, e.g. users switch nodes around + // PARENTED notification fires also when Node is added in scripts to a parent + // this would spam transforms fails and world fails while Node is outside SceneTree + // when node gets reparented when joining the tree POST_ENTER_TREE takes care of this + set_agent_parent(get_parent()); + set_physics_process_internal(true); + } + } break; + + case NOTIFICATION_UNPARENTED: { + // if agent has no parent no point in processing it until reparented + set_agent_parent(nullptr); + set_physics_process_internal(false); + } break; + + case NOTIFICATION_PAUSED: { + if (agent_parent && !agent_parent->can_process()) { + map_before_pause = NavigationServer2D::get_singleton()->agent_get_map(get_rid()); + NavigationServer2D::get_singleton()->agent_set_map(get_rid(), RID()); + } else if (agent_parent && agent_parent->can_process() && !(map_before_pause == RID())) { + NavigationServer2D::get_singleton()->agent_set_map(get_rid(), map_before_pause); + map_before_pause = RID(); + } + } break; + + case NOTIFICATION_UNPAUSED: { + if (agent_parent && !agent_parent->can_process()) { + map_before_pause = NavigationServer2D::get_singleton()->agent_get_map(get_rid()); + NavigationServer2D::get_singleton()->agent_set_map(get_rid(), RID()); + } else if (agent_parent && agent_parent->can_process() && !(map_before_pause == RID())) { + NavigationServer2D::get_singleton()->agent_set_map(get_rid(), map_before_pause); + map_before_pause = RID(); + } + } break; + case NOTIFICATION_EXIT_TREE: { agent_parent = nullptr; set_physics_process_internal(false); @@ -109,7 +160,11 @@ void NavigationAgent2D::_notification(int p_what) { case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: { if (agent_parent) { - NavigationServer2D::get_singleton()->agent_set_position(agent, agent_parent->get_global_position()); + if (avoidance_enabled) { + // agent_position on NavigationServer is avoidance only and has nothing to do with pathfinding + // no point in flooding NavigationServer queue with agent position updates that get send to the void if avoidance is not used + NavigationServer2D::get_singleton()->agent_set_position(agent, agent_parent->get_global_position()); + } _check_distance_to_target(); } } break; @@ -130,16 +185,85 @@ NavigationAgent2D::~NavigationAgent2D() { agent = RID(); // Pointless } -void NavigationAgent2D::set_navigable_layers(uint32_t p_layers) { - bool layers_changed = navigable_layers != p_layers; - navigable_layers = p_layers; - if (layers_changed) { +void NavigationAgent2D::set_avoidance_enabled(bool p_enabled) { + avoidance_enabled = p_enabled; + if (avoidance_enabled) { + NavigationServer2D::get_singleton()->agent_set_callback(agent, this, "_avoidance_done"); + } else { + NavigationServer2D::get_singleton()->agent_set_callback(agent, nullptr, "_avoidance_done"); + } +} + +bool NavigationAgent2D::get_avoidance_enabled() const { + return avoidance_enabled; +} + +void NavigationAgent2D::set_agent_parent(Node *p_agent_parent) { + // remove agent from any avoidance map before changing parent or there will be leftovers on the RVO map + NavigationServer2D::get_singleton()->agent_set_callback(agent, nullptr, "_avoidance_done"); + if (Object::cast_to<Node2D>(p_agent_parent) != nullptr) { + // place agent on navigation map first or else the RVO agent callback creation fails silently later + agent_parent = Object::cast_to<Node2D>(p_agent_parent); + if (map_override.is_valid()) { + NavigationServer2D::get_singleton()->agent_set_map(get_rid(), map_override); + } else { + NavigationServer2D::get_singleton()->agent_set_map(get_rid(), agent_parent->get_world_2d()->get_navigation_map()); + } + // create new avoidance callback if enabled + set_avoidance_enabled(avoidance_enabled); + } else { + agent_parent = nullptr; + NavigationServer2D::get_singleton()->agent_set_map(get_rid(), RID()); + } +} + +void NavigationAgent2D::set_navigation_layers(uint32_t p_navigation_layers) { + bool navigation_layers_changed = navigation_layers != p_navigation_layers; + navigation_layers = p_navigation_layers; + if (navigation_layers_changed) { _request_repath(); } } -uint32_t NavigationAgent2D::get_navigable_layers() const { - return navigable_layers; +uint32_t NavigationAgent2D::get_navigation_layers() const { + return navigation_layers; +} + +void NavigationAgent2D::set_navigation_layer_value(int p_layer_number, bool p_value) { + ERR_FAIL_COND_MSG(p_layer_number < 1, "Navigation layer number must be between 1 and 32 inclusive."); + ERR_FAIL_COND_MSG(p_layer_number > 32, "Navigation layer number must be between 1 and 32 inclusive."); + uint32_t _navigation_layers = get_navigation_layers(); + if (p_value) { + _navigation_layers |= 1 << (p_layer_number - 1); + } else { + _navigation_layers &= ~(1 << (p_layer_number - 1)); + } + set_navigation_layers(_navigation_layers); +} + +bool NavigationAgent2D::get_navigation_layer_value(int p_layer_number) const { + ERR_FAIL_COND_V_MSG(p_layer_number < 1, false, "Navigation layer number must be between 1 and 32 inclusive."); + ERR_FAIL_COND_V_MSG(p_layer_number > 32, false, "Navigation layer number must be between 1 and 32 inclusive."); + return get_navigation_layers() & (1 << (p_layer_number - 1)); +} + +void NavigationAgent2D::set_navigation_map(RID p_navigation_map) { + map_override = p_navigation_map; + NavigationServer2D::get_singleton()->agent_set_map(agent, map_override); + _request_repath(); +} + +RID NavigationAgent2D::get_navigation_map() const { + if (map_override.is_valid()) { + return map_override; + } else if (agent_parent != nullptr) { + return agent_parent->get_world_2d()->get_navigation_map(); + } + return RID(); +} + +void NavigationAgent2D::set_path_desired_distance(real_t p_dd) { + path_desired_distance = p_dd; } void NavigationAgent2D::set_target_desired_distance(real_t p_dd) { @@ -248,7 +372,7 @@ TypedArray<String> NavigationAgent2D::get_configuration_warnings() const { TypedArray<String> warnings = Node::get_configuration_warnings(); if (!Object::cast_to<Node2D>(get_parent())) { - warnings.push_back(RTR("The NavigationAgent2D can be used only under a Node2D node.")); + warnings.push_back(RTR("The NavigationAgent2D can be used only under a Node2D inheriting parent node.")); } return warnings; @@ -290,7 +414,11 @@ void NavigationAgent2D::update_navigation() { } if (reload_path) { - navigation_path = NavigationServer2D::get_singleton()->map_get_path(agent_parent->get_world_2d()->get_navigation_map(), o, target_location, true, navigable_layers); + if (map_override.is_valid()) { + navigation_path = NavigationServer2D::get_singleton()->map_get_path(map_override, o, target_location, true, navigation_layers); + } else { + navigation_path = NavigationServer2D::get_singleton()->map_get_path(agent_parent->get_world_2d()->get_navigation_map(), o, target_location, true, navigation_layers); + } navigation_finished = false; nav_path_index = 0; emit_signal(SNAME("path_changed")); @@ -303,7 +431,7 @@ void NavigationAgent2D::update_navigation() { // Check if we can advance the navigation path if (navigation_finished == false) { // Advances to the next far away location. - while (o.distance_to(navigation_path[nav_path_index]) < target_desired_distance) { + while (o.distance_to(navigation_path[nav_path_index]) < path_desired_distance) { nav_path_index += 1; if (nav_path_index == navigation_path.size()) { _check_distance_to_target(); diff --git a/scene/2d/navigation_agent_2d.h b/scene/2d/navigation_agent_2d.h index 2fb6fab91c..032a15cad2 100644 --- a/scene/2d/navigation_agent_2d.h +++ b/scene/2d/navigation_agent_2d.h @@ -41,21 +41,25 @@ class NavigationAgent2D : public Node { Node2D *agent_parent = nullptr; RID agent; + RID map_before_pause; + RID map_override; - uint32_t navigable_layers = 1; + bool avoidance_enabled = false; + uint32_t navigation_layers = 1; + real_t path_desired_distance = 1.0; real_t target_desired_distance = 1.0; - real_t radius; - real_t neighbor_dist; - int max_neighbors; - real_t time_horizon; - real_t max_speed; + real_t radius = 0.0; + real_t neighbor_dist = 0.0; + int max_neighbors = 0; + real_t time_horizon = 0.0; + real_t max_speed = 0.0; real_t path_max_distance = 3.0; Vector2 target_location; Vector<Vector2> navigation_path; - int nav_path_index; + int nav_path_index = 0; bool velocity_submitted = false; Vector2 prev_safe_velocity; /// The submitted target velocity @@ -63,7 +67,7 @@ class NavigationAgent2D : public Node { bool target_reached = false; bool navigation_finished = true; // No initialized on purpose - uint32_t update_frame_id; + uint32_t update_frame_id = 0; protected: static void _bind_methods(); @@ -77,8 +81,24 @@ public: return agent; } - void set_navigable_layers(uint32_t p_layers); - uint32_t get_navigable_layers() const; + void set_avoidance_enabled(bool p_enabled); + bool get_avoidance_enabled() const; + + void set_agent_parent(Node *p_agent_parent); + + void set_navigation_layers(uint32_t p_navigation_layers); + uint32_t get_navigation_layers() const; + + void set_navigation_layer_value(int p_layer_number, bool p_value); + bool get_navigation_layer_value(int p_layer_number) const; + + void set_navigation_map(RID p_navigation_map); + RID get_navigation_map() const; + + void set_path_desired_distance(real_t p_dd); + real_t get_path_desired_distance() const { + return path_desired_distance; + } void set_target_desired_distance(real_t p_dd); real_t get_target_desired_distance() const { diff --git a/scene/2d/navigation_obstacle_2d.cpp b/scene/2d/navigation_obstacle_2d.cpp index d1e5bc11bc..0320c6c917 100644 --- a/scene/2d/navigation_obstacle_2d.cpp +++ b/scene/2d/navigation_obstacle_2d.cpp @@ -31,10 +31,13 @@ #include "navigation_obstacle_2d.h" #include "scene/2d/collision_shape_2d.h" +#include "scene/2d/physics_body_2d.h" #include "scene/resources/world_2d.h" #include "servers/navigation_server_2d.h" void NavigationObstacle2D::_bind_methods() { + ClassDB::bind_method(D_METHOD("get_rid"), &NavigationObstacle2D::get_rid); + ClassDB::bind_method(D_METHOD("set_estimate_radius", "estimate_radius"), &NavigationObstacle2D::set_estimate_radius); ClassDB::bind_method(D_METHOD("is_radius_estimated"), &NavigationObstacle2D::is_radius_estimated); ClassDB::bind_method(D_METHOD("set_radius", "radius"), &NavigationObstacle2D::set_radius); @@ -78,8 +81,28 @@ void NavigationObstacle2D::_notification(int p_what) { parent_node2d = nullptr; } break; + case NOTIFICATION_PAUSED: { + if (parent_node2d && !parent_node2d->can_process()) { + map_before_pause = NavigationServer2D::get_singleton()->agent_get_map(get_rid()); + NavigationServer2D::get_singleton()->agent_set_map(get_rid(), RID()); + } else if (parent_node2d && parent_node2d->can_process() && !(map_before_pause == RID())) { + NavigationServer2D::get_singleton()->agent_set_map(get_rid(), map_before_pause); + map_before_pause = RID(); + } + } break; + + case NOTIFICATION_UNPAUSED: { + if (parent_node2d && !parent_node2d->can_process()) { + map_before_pause = NavigationServer2D::get_singleton()->agent_get_map(get_rid()); + NavigationServer2D::get_singleton()->agent_set_map(get_rid(), RID()); + } else if (parent_node2d && parent_node2d->can_process() && !(map_before_pause == RID())) { + NavigationServer2D::get_singleton()->agent_set_map(get_rid(), map_before_pause); + map_before_pause = RID(); + } + } break; + case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: { - if (parent_node2d) { + if (parent_node2d && parent_node2d->is_inside_tree()) { NavigationServer2D::get_singleton()->agent_set_position(agent, parent_node2d->get_global_position()); } } break; @@ -103,6 +126,11 @@ TypedArray<String> NavigationObstacle2D::get_configuration_warnings() const { warnings.push_back(RTR("The NavigationObstacle2D only serves to provide collision avoidance to a Node2D object.")); } + if (Object::cast_to<StaticBody2D>(get_parent())) { + warnings.push_back(RTR("The NavigationObstacle2D is intended for constantly moving bodies like CharacterBody2D or RigidDynamicBody2D as it creates only an RVO avoidance radius and does not follow scene geometry exactly." + "\nNot constantly moving or complete static objects should be captured with a refreshed NavigationPolygon so agents can not only avoid them but also move along those objects outline at high detail")); + } + return warnings; } @@ -122,13 +150,13 @@ void NavigationObstacle2D::reevaluate_agent_radius() { } real_t NavigationObstacle2D::estimate_agent_radius() const { - if (parent_node2d) { + if (parent_node2d && parent_node2d->is_inside_tree()) { // Estimate the radius of this physics body real_t radius = 0.0; for (int i(0); i < parent_node2d->get_child_count(); i++) { // For each collision shape CollisionShape2D *cs = Object::cast_to<CollisionShape2D>(parent_node2d->get_child(i)); - if (cs) { + if (cs && cs->is_inside_tree()) { // Take the distance between the Body center to the shape center real_t r = cs->get_transform().get_origin().length(); if (cs->get_shape().is_valid()) { @@ -139,6 +167,9 @@ real_t NavigationObstacle2D::estimate_agent_radius() const { r *= MAX(s.x, s.y); // Takes the biggest radius radius = MAX(radius, r); + } else if (cs && !cs->is_inside_tree()) { + WARN_PRINT("A CollisionShape2D of the NavigationObstacle2D parent node was not inside the SceneTree when estimating the obstacle radius." + "\nMove the NavigationObstacle2D to a child position below any CollisionShape2D node of the parent node so the CollisionShape2D is already inside the SceneTree."); } } Vector2 s = parent_node2d->get_global_scale(); diff --git a/scene/2d/navigation_obstacle_2d.h b/scene/2d/navigation_obstacle_2d.h index 2a0ef14e73..948cf5b61a 100644 --- a/scene/2d/navigation_obstacle_2d.h +++ b/scene/2d/navigation_obstacle_2d.h @@ -39,6 +39,7 @@ class NavigationObstacle2D : public Node { Node2D *parent_node2d = nullptr; RID agent; + RID map_before_pause; bool estimate_radius = true; real_t radius = 1.0; diff --git a/scene/2d/navigation_region_2d.cpp b/scene/2d/navigation_region_2d.cpp index 261d371dc4..6e8fd891cb 100644 --- a/scene/2d/navigation_region_2d.cpp +++ b/scene/2d/navigation_region_2d.cpp @@ -302,19 +302,19 @@ void NavigationPolygon::make_polygons_from_outlines() { polygons.clear(); vertices.clear(); - Map<Vector2, int> points; + HashMap<Vector2, int> points; for (List<TPPLPoly>::Element *I = out_poly.front(); I; I = I->next()) { TPPLPoly &tp = I->get(); struct Polygon p; for (int64_t i = 0; i < tp.GetNumPoints(); i++) { - Map<Vector2, int>::Element *E = points.find(tp[i]); + HashMap<Vector2, int>::Iterator E = points.find(tp[i]); if (!E) { E = points.insert(tp[i], vertices.size()); vertices.push_back(tp[i]); } - p.indices.push_back(E->get()); + p.indices.push_back(E->value); } polygons.push_back(p); @@ -331,6 +331,7 @@ void NavigationPolygon::_bind_methods() { ClassDB::bind_method(D_METHOD("get_polygon_count"), &NavigationPolygon::get_polygon_count); ClassDB::bind_method(D_METHOD("get_polygon", "idx"), &NavigationPolygon::get_polygon); ClassDB::bind_method(D_METHOD("clear_polygons"), &NavigationPolygon::clear_polygons); + ClassDB::bind_method(D_METHOD("get_mesh"), &NavigationPolygon::get_mesh); ClassDB::bind_method(D_METHOD("add_outline", "outline"), &NavigationPolygon::add_outline); ClassDB::bind_method(D_METHOD("add_outline_at_index", "outline", "index"), &NavigationPolygon::add_outline_at_index); @@ -379,12 +380,54 @@ bool NavigationRegion2D::is_enabled() const { return enabled; } -void NavigationRegion2D::set_layers(uint32_t p_layers) { - NavigationServer2D::get_singleton()->region_set_layers(region, p_layers); +void NavigationRegion2D::set_navigation_layers(uint32_t p_navigation_layers) { + NavigationServer2D::get_singleton()->region_set_navigation_layers(region, p_navigation_layers); } -uint32_t NavigationRegion2D::get_layers() const { - return NavigationServer2D::get_singleton()->region_get_layers(region); +uint32_t NavigationRegion2D::get_navigation_layers() const { + return NavigationServer2D::get_singleton()->region_get_navigation_layers(region); +} + +void NavigationRegion2D::set_navigation_layer_value(int p_layer_number, bool p_value) { + ERR_FAIL_COND_MSG(p_layer_number < 1, "Navigation layer number must be between 1 and 32 inclusive."); + ERR_FAIL_COND_MSG(p_layer_number > 32, "Navigation layer number must be between 1 and 32 inclusive."); + uint32_t _navigation_layers = get_navigation_layers(); + if (p_value) { + _navigation_layers |= 1 << (p_layer_number - 1); + } else { + _navigation_layers &= ~(1 << (p_layer_number - 1)); + } + set_navigation_layers(_navigation_layers); +} + +bool NavigationRegion2D::get_navigation_layer_value(int p_layer_number) const { + ERR_FAIL_COND_V_MSG(p_layer_number < 1, false, "Navigation layer number must be between 1 and 32 inclusive."); + ERR_FAIL_COND_V_MSG(p_layer_number > 32, false, "Navigation layer number must be between 1 and 32 inclusive."); + return get_navigation_layers() & (1 << (p_layer_number - 1)); +} + +void NavigationRegion2D::set_enter_cost(real_t p_enter_cost) { + ERR_FAIL_COND_MSG(p_enter_cost < 0.0, "The enter_cost must be positive."); + enter_cost = MAX(p_enter_cost, 0.0); + NavigationServer2D::get_singleton()->region_set_enter_cost(region, p_enter_cost); +} + +real_t NavigationRegion2D::get_enter_cost() const { + return enter_cost; +} + +void NavigationRegion2D::set_travel_cost(real_t p_travel_cost) { + ERR_FAIL_COND_MSG(p_travel_cost < 0.0, "The travel_cost must be positive."); + travel_cost = MAX(p_travel_cost, 0.0); + NavigationServer2D::get_singleton()->region_set_enter_cost(region, travel_cost); +} + +real_t NavigationRegion2D::get_travel_cost() const { + return travel_cost; +} + +RID NavigationRegion2D::get_region_rid() const { + return region; } ///////////////////////////// @@ -505,6 +548,9 @@ void NavigationRegion2D::_navpoly_changed() { if (is_inside_tree() && (Engine::get_singleton()->is_editor_hint() || get_tree()->is_debugging_navigation_hint())) { update(); } + if (navpoly.is_valid()) { + NavigationServer2D::get_singleton()->region_set_navpoly(region, navpoly); + } } void NavigationRegion2D::_map_changed(RID p_map) { if (enabled && get_world_2d()->get_navigation_map() == p_map) { @@ -531,19 +577,34 @@ void NavigationRegion2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_enabled", "enabled"), &NavigationRegion2D::set_enabled); ClassDB::bind_method(D_METHOD("is_enabled"), &NavigationRegion2D::is_enabled); - ClassDB::bind_method(D_METHOD("set_layers", "layers"), &NavigationRegion2D::set_layers); - ClassDB::bind_method(D_METHOD("get_layers"), &NavigationRegion2D::get_layers); + ClassDB::bind_method(D_METHOD("set_navigation_layers", "navigation_layers"), &NavigationRegion2D::set_navigation_layers); + ClassDB::bind_method(D_METHOD("get_navigation_layers"), &NavigationRegion2D::get_navigation_layers); + + ClassDB::bind_method(D_METHOD("set_navigation_layer_value", "layer_number", "value"), &NavigationRegion2D::set_navigation_layer_value); + ClassDB::bind_method(D_METHOD("get_navigation_layer_value", "layer_number"), &NavigationRegion2D::get_navigation_layer_value); + + ClassDB::bind_method(D_METHOD("get_region_rid"), &NavigationRegion2D::get_region_rid); + + ClassDB::bind_method(D_METHOD("set_enter_cost", "enter_cost"), &NavigationRegion2D::set_enter_cost); + ClassDB::bind_method(D_METHOD("get_enter_cost"), &NavigationRegion2D::get_enter_cost); + + ClassDB::bind_method(D_METHOD("set_travel_cost", "travel_cost"), &NavigationRegion2D::set_travel_cost); + ClassDB::bind_method(D_METHOD("get_travel_cost"), &NavigationRegion2D::get_travel_cost); ClassDB::bind_method(D_METHOD("_navpoly_changed"), &NavigationRegion2D::_navpoly_changed); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "navpoly", PROPERTY_HINT_RESOURCE_TYPE, "NavigationPolygon"), "set_navigation_polygon", "get_navigation_polygon"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "enabled"), "set_enabled", "is_enabled"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "layers", PROPERTY_HINT_LAYERS_2D_NAVIGATION), "set_layers", "get_layers"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "navigation_layers", PROPERTY_HINT_LAYERS_2D_NAVIGATION), "set_navigation_layers", "get_navigation_layers"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "enter_cost"), "set_enter_cost", "get_enter_cost"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "travel_cost"), "set_travel_cost", "get_travel_cost"); } NavigationRegion2D::NavigationRegion2D() { set_notify_transform(true); region = NavigationServer2D::get_singleton()->region_create(); + NavigationServer2D::get_singleton()->region_set_enter_cost(region, get_enter_cost()); + NavigationServer2D::get_singleton()->region_set_travel_cost(region, get_travel_cost()); } NavigationRegion2D::~NavigationRegion2D() { diff --git a/scene/2d/navigation_region_2d.h b/scene/2d/navigation_region_2d.h index 487a578401..3c9df91fe3 100644 --- a/scene/2d/navigation_region_2d.h +++ b/scene/2d/navigation_region_2d.h @@ -98,6 +98,9 @@ class NavigationRegion2D : public Node2D { RID region; Ref<NavigationPolygon> navpoly; + real_t enter_cost = 0.0; + real_t travel_cost = 1.0; + void _navpoly_changed(); void _map_changed(RID p_RID); @@ -114,8 +117,19 @@ public: void set_enabled(bool p_enabled); bool is_enabled() const; - void set_layers(uint32_t p_layers); - uint32_t get_layers() const; + void set_navigation_layers(uint32_t p_navigation_layers); + uint32_t get_navigation_layers() const; + + void set_navigation_layer_value(int p_layer_number, bool p_value); + bool get_navigation_layer_value(int p_layer_number) const; + + RID get_region_rid() const; + + void set_enter_cost(real_t p_enter_cost); + real_t get_enter_cost() const; + + void set_travel_cost(real_t p_travel_cost); + real_t get_travel_cost() const; void set_navigation_polygon(const Ref<NavigationPolygon> &p_navpoly); Ref<NavigationPolygon> get_navigation_polygon() const; diff --git a/scene/2d/node_2d.cpp b/scene/2d/node_2d.cpp index 42c8daa4c7..4599785ce4 100644 --- a/scene/2d/node_2d.cpp +++ b/scene/2d/node_2d.cpp @@ -111,7 +111,7 @@ void Node2D::_edit_set_rect(const Rect2 &p_edit_rect) { #endif void Node2D::_update_xform_values() { - position = transform.elements[2]; + position = transform.columns[2]; rotation = transform.get_rotation(); scale = transform.get_scale(); skew = transform.get_skew(); @@ -120,7 +120,7 @@ void Node2D::_update_xform_values() { void Node2D::_update_transform() { transform.set_rotation_scale_and_skew(rotation, scale, skew); - transform.elements[2] = position; + transform.columns[2] = position; RenderingServer::get_singleton()->canvas_item_set_transform(get_canvas_item(), transform); @@ -244,9 +244,9 @@ Point2 Node2D::get_global_position() const { } void Node2D::set_global_position(const Point2 &p_pos) { - CanvasItem *pi = get_parent_item(); - if (pi) { - Transform2D inv = pi->get_global_transform().affine_inverse(); + CanvasItem *parent = get_parent_item(); + if (parent) { + Transform2D inv = parent->get_global_transform().affine_inverse(); set_position(inv.xform(p_pos)); } else { set_position(p_pos); @@ -257,25 +257,48 @@ real_t Node2D::get_global_rotation() const { return get_global_transform().get_rotation(); } -void Node2D::set_global_rotation(real_t p_radians) { - CanvasItem *pi = get_parent_item(); - if (pi) { - const real_t parent_global_rot = pi->get_global_transform().get_rotation(); - set_rotation(p_radians - parent_global_rot); +real_t Node2D::get_global_skew() const { + return get_global_transform().get_skew(); +} + +void Node2D::set_global_rotation(const real_t p_radians) { + CanvasItem *parent = get_parent_item(); + if (parent) { + Transform2D parent_global_transform = parent->get_global_transform(); + Transform2D new_transform = parent_global_transform * get_transform(); + new_transform.set_rotation(p_radians); + new_transform = parent_global_transform.affine_inverse() * new_transform; + set_rotation(new_transform.get_rotation()); } else { set_rotation(p_radians); } } +void Node2D::set_global_skew(const real_t p_radians) { + CanvasItem *parent = get_parent_item(); + if (parent) { + Transform2D parent_global_transform = parent->get_global_transform(); + Transform2D new_transform = parent_global_transform * get_transform(); + new_transform.set_skew(p_radians); + new_transform = parent_global_transform.affine_inverse() * new_transform; + set_skew(new_transform.get_skew()); + } else { + set_skew(p_radians); + } +} + Size2 Node2D::get_global_scale() const { return get_global_transform().get_scale(); } void Node2D::set_global_scale(const Size2 &p_scale) { - CanvasItem *pi = get_parent_item(); - if (pi) { - const Size2 parent_global_scale = pi->get_global_transform().get_scale(); - set_scale(p_scale / parent_global_scale); + CanvasItem *parent = get_parent_item(); + if (parent) { + Transform2D parent_global_transform = parent->get_global_transform(); + Transform2D new_transform = parent_global_transform * get_transform(); + new_transform.set_scale(p_scale); + new_transform = parent_global_transform.affine_inverse() * new_transform; + set_scale(new_transform.get_scale()); } else { set_scale(p_scale); } @@ -295,9 +318,9 @@ void Node2D::set_transform(const Transform2D &p_transform) { } void Node2D::set_global_transform(const Transform2D &p_transform) { - CanvasItem *pi = get_parent_item(); - if (pi) { - set_transform(pi->get_global_transform().affine_inverse() * p_transform); + CanvasItem *parent = get_parent_item(); + if (parent) { + set_transform(parent->get_global_transform().affine_inverse() * p_transform); } else { set_transform(p_transform); } @@ -388,6 +411,8 @@ void Node2D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_global_position"), &Node2D::get_global_position); ClassDB::bind_method(D_METHOD("set_global_rotation", "radians"), &Node2D::set_global_rotation); ClassDB::bind_method(D_METHOD("get_global_rotation"), &Node2D::get_global_rotation); + ClassDB::bind_method(D_METHOD("set_global_skew", "radians"), &Node2D::set_global_skew); + ClassDB::bind_method(D_METHOD("get_global_skew"), &Node2D::get_global_skew); ClassDB::bind_method(D_METHOD("set_global_scale", "scale"), &Node2D::set_global_scale); ClassDB::bind_method(D_METHOD("get_global_scale"), &Node2D::get_global_scale); @@ -412,16 +437,17 @@ void Node2D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_relative_transform_to_parent", "parent"), &Node2D::get_relative_transform_to_parent); ADD_GROUP("Transform", ""); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "position", PROPERTY_HINT_RANGE, "-99999,99999,0.001,or_lesser,or_greater,noslider,suffix:px"), "set_position", "get_position"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "position", PROPERTY_HINT_RANGE, "-99999,99999,0.001,or_lesser,or_greater,no_slider,suffix:px"), "set_position", "get_position"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "rotation", PROPERTY_HINT_RANGE, "-360,360,0.1,or_lesser,or_greater,radians"), "set_rotation", "get_rotation"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "scale"), "set_scale", "get_scale"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "scale", PROPERTY_HINT_LINK), "set_scale", "get_scale"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "skew", PROPERTY_HINT_RANGE, "-89.9,89.9,0.1,radians"), "set_skew", "get_skew"); - ADD_PROPERTY(PropertyInfo(Variant::TRANSFORM2D, "transform", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_transform", "get_transform"); + ADD_PROPERTY(PropertyInfo(Variant::TRANSFORM2D, "transform", PROPERTY_HINT_NONE, "suffix:px", PROPERTY_USAGE_NONE), "set_transform", "get_transform"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "global_position", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_global_position", "get_global_position"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "global_rotation", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_global_rotation", "get_global_rotation"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "global_position", PROPERTY_HINT_NONE, "suffix:px", PROPERTY_USAGE_NONE), "set_global_position", "get_global_position"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "global_rotation", PROPERTY_HINT_NONE, "radians", PROPERTY_USAGE_NONE), "set_global_rotation", "get_global_rotation"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "global_scale", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_global_scale", "get_global_scale"); - ADD_PROPERTY(PropertyInfo(Variant::TRANSFORM2D, "global_transform", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_global_transform", "get_global_transform"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "global_skew", PROPERTY_HINT_NONE, "radians", PROPERTY_USAGE_NONE), "set_global_skew", "get_global_skew"); + ADD_PROPERTY(PropertyInfo(Variant::TRANSFORM2D, "global_transform", PROPERTY_HINT_NONE, "suffix:px", PROPERTY_USAGE_NONE), "set_global_transform", "get_global_transform"); ADD_GROUP("Ordering", ""); ADD_PROPERTY(PropertyInfo(Variant::INT, "z_index", PROPERTY_HINT_RANGE, itos(RS::CANVAS_ITEM_Z_MIN) + "," + itos(RS::CANVAS_ITEM_Z_MAX) + ",1"), "set_z_index", "get_z_index"); diff --git a/scene/2d/node_2d.h b/scene/2d/node_2d.h index 69d14f82ad..473c34768f 100644 --- a/scene/2d/node_2d.h +++ b/scene/2d/node_2d.h @@ -92,12 +92,14 @@ public: Point2 get_global_position() const; real_t get_global_rotation() const; + real_t get_global_skew() const; Size2 get_global_scale() const; void set_transform(const Transform2D &p_transform); void set_global_transform(const Transform2D &p_transform); void set_global_position(const Point2 &p_pos); - void set_global_rotation(real_t p_radians); + void set_global_rotation(const real_t p_radians); + void set_global_skew(const real_t p_radians); void set_global_scale(const Size2 &p_scale); void set_z_index(int p_z); diff --git a/scene/2d/parallax_background.cpp b/scene/2d/parallax_background.cpp index dcbb6507f5..bd5a01f5a4 100644 --- a/scene/2d/parallax_background.cpp +++ b/scene/2d/parallax_background.cpp @@ -163,25 +163,25 @@ Vector2 ParallaxBackground::get_final_offset() const { void ParallaxBackground::_bind_methods() { ClassDB::bind_method(D_METHOD("_camera_moved"), &ParallaxBackground::_camera_moved); - ClassDB::bind_method(D_METHOD("set_scroll_offset", "ofs"), &ParallaxBackground::set_scroll_offset); + ClassDB::bind_method(D_METHOD("set_scroll_offset", "offset"), &ParallaxBackground::set_scroll_offset); ClassDB::bind_method(D_METHOD("get_scroll_offset"), &ParallaxBackground::get_scroll_offset); - ClassDB::bind_method(D_METHOD("set_scroll_base_offset", "ofs"), &ParallaxBackground::set_scroll_base_offset); + ClassDB::bind_method(D_METHOD("set_scroll_base_offset", "offset"), &ParallaxBackground::set_scroll_base_offset); ClassDB::bind_method(D_METHOD("get_scroll_base_offset"), &ParallaxBackground::get_scroll_base_offset); ClassDB::bind_method(D_METHOD("set_scroll_base_scale", "scale"), &ParallaxBackground::set_scroll_base_scale); ClassDB::bind_method(D_METHOD("get_scroll_base_scale"), &ParallaxBackground::get_scroll_base_scale); - ClassDB::bind_method(D_METHOD("set_limit_begin", "ofs"), &ParallaxBackground::set_limit_begin); + ClassDB::bind_method(D_METHOD("set_limit_begin", "offset"), &ParallaxBackground::set_limit_begin); ClassDB::bind_method(D_METHOD("get_limit_begin"), &ParallaxBackground::get_limit_begin); - ClassDB::bind_method(D_METHOD("set_limit_end", "ofs"), &ParallaxBackground::set_limit_end); + ClassDB::bind_method(D_METHOD("set_limit_end", "offset"), &ParallaxBackground::set_limit_end); ClassDB::bind_method(D_METHOD("get_limit_end"), &ParallaxBackground::get_limit_end); ClassDB::bind_method(D_METHOD("set_ignore_camera_zoom", "ignore"), &ParallaxBackground::set_ignore_camera_zoom); ClassDB::bind_method(D_METHOD("is_ignore_camera_zoom"), &ParallaxBackground::is_ignore_camera_zoom); ADD_GROUP("Scroll", "scroll_"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "scroll_offset"), "set_scroll_offset", "get_scroll_offset"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "scroll_base_offset"), "set_scroll_base_offset", "get_scroll_base_offset"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "scroll_base_scale"), "set_scroll_base_scale", "get_scroll_base_scale"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "scroll_limit_begin"), "set_limit_begin", "get_limit_begin"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "scroll_limit_end"), "set_limit_end", "get_limit_end"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "scroll_offset", PROPERTY_HINT_NONE, "suffix:px"), "set_scroll_offset", "get_scroll_offset"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "scroll_base_offset", PROPERTY_HINT_NONE, "suffix:px"), "set_scroll_base_offset", "get_scroll_base_offset"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "scroll_base_scale", PROPERTY_HINT_LINK), "set_scroll_base_scale", "get_scroll_base_scale"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "scroll_limit_begin", PROPERTY_HINT_NONE, "suffix:px"), "set_limit_begin", "get_limit_begin"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "scroll_limit_end", PROPERTY_HINT_NONE, "suffix:px"), "set_limit_end", "get_limit_end"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "scroll_ignore_camera_zoom"), "set_ignore_camera_zoom", "is_ignore_camera_zoom"); } diff --git a/scene/2d/parallax_layer.cpp b/scene/2d/parallax_layer.cpp index 9e8ab224dc..f0aad1b8a4 100644 --- a/scene/2d/parallax_layer.cpp +++ b/scene/2d/parallax_layer.cpp @@ -158,8 +158,8 @@ void ParallaxLayer::_bind_methods() { ClassDB::bind_method(D_METHOD("get_mirroring"), &ParallaxLayer::get_mirroring); ADD_GROUP("Motion", "motion_"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "motion_scale"), "set_motion_scale", "get_motion_scale"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "motion_offset"), "set_motion_offset", "get_motion_offset"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "motion_scale", PROPERTY_HINT_LINK), "set_motion_scale", "get_motion_scale"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "motion_offset", PROPERTY_HINT_NONE, "suffix:px"), "set_motion_offset", "get_motion_offset"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "motion_mirroring"), "set_mirroring", "get_mirroring"); } diff --git a/scene/2d/path_2d.cpp b/scene/2d/path_2d.cpp index aa68349329..9862c4bfb1 100644 --- a/scene/2d/path_2d.cpp +++ b/scene/2d/path_2d.cpp @@ -87,13 +87,13 @@ bool Path2D::_edit_is_selected_on_click(const Point2 &p_point, double p_toleranc void Path2D::_notification(int p_what) { switch (p_what) { - // Draw the curve if navigation debugging is enabled. + // Draw the curve if path debugging is enabled. case NOTIFICATION_DRAW: { if (!curve.is_valid()) { break; } - if (!Engine::get_singleton()->is_editor_hint() && !get_tree()->is_debugging_navigation_hint()) { + if (!Engine::get_singleton()->is_editor_hint() && !get_tree()->is_debugging_paths_hint()) { return; } @@ -102,12 +102,10 @@ void Path2D::_notification(int p_what) { } #ifdef TOOLS_ENABLED - const real_t line_width = 2 * EDSCALE; + const real_t line_width = get_tree()->get_debug_paths_width() * EDSCALE; #else - const real_t line_width = 2; + const real_t line_width = get_tree()->get_debug_paths_width(); #endif - const Color color = Color(0.5, 0.6, 1.0, 0.7); - _cached_draw_pts.resize(curve->get_point_count() * 8); int count = 0; @@ -119,7 +117,7 @@ void Path2D::_notification(int p_what) { } } - draw_polyline(_cached_draw_pts, color, line_width, true); + draw_polyline(_cached_draw_pts, get_tree()->get_debug_paths_color(), line_width, true); } break; } } @@ -129,7 +127,7 @@ void Path2D::_curve_changed() { return; } - if (!Engine::get_singleton()->is_editor_hint() && !get_tree()->is_debugging_navigation_hint()) { + if (!Engine::get_singleton()->is_editor_hint() && !get_tree()->is_debugging_paths_hint()) { return; } @@ -295,7 +293,7 @@ void PathFollow2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_lookahead", "lookahead"), &PathFollow2D::set_lookahead); ClassDB::bind_method(D_METHOD("get_lookahead"), &PathFollow2D::get_lookahead); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "offset", PROPERTY_HINT_RANGE, "0,10000,0.01,or_lesser,or_greater"), "set_offset", "get_offset"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "offset", PROPERTY_HINT_RANGE, "0,10000,0.01,or_lesser,or_greater,suffix:px"), "set_offset", "get_offset"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "unit_offset", PROPERTY_HINT_RANGE, "0,1,0.0001,or_lesser,or_greater", PROPERTY_USAGE_EDITOR), "set_unit_offset", "get_unit_offset"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "h_offset"), "set_h_offset", "get_h_offset"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "v_offset"), "set_v_offset", "get_v_offset"); @@ -306,12 +304,13 @@ void PathFollow2D::_bind_methods() { } void PathFollow2D::set_offset(real_t p_offset) { + ERR_FAIL_COND(!isfinite(p_offset)); offset = p_offset; if (path) { if (path->get_curve().is_valid()) { real_t path_length = path->get_curve()->get_baked_length(); - if (loop) { + if (loop && path_length) { offset = Math::fposmod(offset, path_length); if (!Math::is_zero_approx(p_offset) && Math::is_zero_approx(offset)) { offset = path_length; diff --git a/scene/2d/physics_body_2d.cpp b/scene/2d/physics_body_2d.cpp index c0f2b6f07e..e60a5ed034 100644 --- a/scene/2d/physics_body_2d.cpp +++ b/scene/2d/physics_body_2d.cpp @@ -56,8 +56,10 @@ PhysicsBody2D::~PhysicsBody2D() { Ref<KinematicCollision2D> PhysicsBody2D::_move(const Vector2 &p_distance, bool p_test_only, real_t p_margin) { PhysicsServer2D::MotionParameters parameters(get_global_transform(), p_distance, p_margin); + parameters.recovery_as_collision = false; // Don't report collisions generated only from recovery. PhysicsServer2D::MotionResult result; + if (move_and_collide(parameters, result, p_test_only)) { // Create a new instance when the cached reference is invalid or still in use in script. if (motion_cache.is_null() || motion_cache->reference_get_count() > 1) { @@ -119,7 +121,7 @@ bool PhysicsBody2D::move_and_collide(const PhysicsServer2D::MotionParameters &p_ if (!p_test_only) { Transform2D gt = p_parameters.from; - gt.elements[2] += r_result.travel; + gt.columns[2] += r_result.travel; set_global_transform(gt); } @@ -139,15 +141,9 @@ bool PhysicsBody2D::test_move(const Transform2D &p_from, const Vector2 &p_distan } PhysicsServer2D::MotionParameters parameters(p_from, p_distance, p_margin); + parameters.recovery_as_collision = false; // Don't report collisions generated only from recovery. - 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; - } + return PhysicsServer2D::get_singleton()->body_test_motion(get_rid(), parameters, r); } TypedArray<PhysicsBody2D> PhysicsBody2D::get_collision_exceptions() { @@ -226,8 +222,8 @@ void StaticBody2D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_physics_material_override"), &StaticBody2D::get_physics_material_override); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "physics_material_override", PROPERTY_HINT_RESOURCE_TYPE, "PhysicsMaterial"), "set_physics_material_override", "get_physics_material_override"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "constant_linear_velocity"), "set_constant_linear_velocity", "get_constant_linear_velocity"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "constant_angular_velocity"), "set_constant_angular_velocity", "get_constant_angular_velocity"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "constant_linear_velocity", PROPERTY_HINT_NONE, "suffix:px/s"), "set_constant_linear_velocity", "get_constant_linear_velocity"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "constant_angular_velocity", PROPERTY_HINT_NONE, U"radians,suffix:\u00B0/s"), "set_constant_angular_velocity", "get_constant_angular_velocity"); } StaticBody2D::StaticBody2D(PhysicsServer2D::BodyMode p_mode) : @@ -335,17 +331,17 @@ void RigidDynamicBody2D::_body_enter_tree(ObjectID p_id) { ERR_FAIL_COND(!node); ERR_FAIL_COND(!contact_monitor); - Map<ObjectID, BodyState>::Element *E = contact_monitor->body_map.find(p_id); + HashMap<ObjectID, BodyState>::Iterator E = contact_monitor->body_map.find(p_id); ERR_FAIL_COND(!E); - ERR_FAIL_COND(E->get().in_scene); + ERR_FAIL_COND(E->value.in_scene); contact_monitor->locked = true; - E->get().in_scene = true; + E->value.in_scene = true; emit_signal(SceneStringNames::get_singleton()->body_entered, node); - for (int i = 0; i < E->get().shapes.size(); i++) { - emit_signal(SceneStringNames::get_singleton()->body_shape_entered, E->get().rid, node, E->get().shapes[i].body_shape, E->get().shapes[i].local_shape); + for (int i = 0; i < E->value.shapes.size(); i++) { + emit_signal(SceneStringNames::get_singleton()->body_shape_entered, E->value.rid, node, E->value.shapes[i].body_shape, E->value.shapes[i].local_shape); } contact_monitor->locked = false; @@ -356,17 +352,17 @@ void RigidDynamicBody2D::_body_exit_tree(ObjectID p_id) { Node *node = Object::cast_to<Node>(obj); ERR_FAIL_COND(!node); ERR_FAIL_COND(!contact_monitor); - Map<ObjectID, BodyState>::Element *E = contact_monitor->body_map.find(p_id); + HashMap<ObjectID, BodyState>::Iterator E = contact_monitor->body_map.find(p_id); ERR_FAIL_COND(!E); - ERR_FAIL_COND(!E->get().in_scene); - E->get().in_scene = false; + ERR_FAIL_COND(!E->value.in_scene); + E->value.in_scene = false; contact_monitor->locked = true; emit_signal(SceneStringNames::get_singleton()->body_exited, node); - for (int i = 0; i < E->get().shapes.size(); i++) { - emit_signal(SceneStringNames::get_singleton()->body_shape_exited, E->get().rid, node, E->get().shapes[i].body_shape, E->get().shapes[i].local_shape); + for (int i = 0; i < E->value.shapes.size(); i++) { + emit_signal(SceneStringNames::get_singleton()->body_shape_exited, E->value.rid, node, E->value.shapes[i].body_shape, E->value.shapes[i].local_shape); } contact_monitor->locked = false; @@ -380,45 +376,45 @@ void RigidDynamicBody2D::_body_inout(int p_status, const RID &p_body, ObjectID p Node *node = Object::cast_to<Node>(obj); ERR_FAIL_COND(!contact_monitor); - Map<ObjectID, BodyState>::Element *E = contact_monitor->body_map.find(objid); + HashMap<ObjectID, BodyState>::Iterator E = contact_monitor->body_map.find(objid); ERR_FAIL_COND(!body_in && !E); if (body_in) { if (!E) { E = contact_monitor->body_map.insert(objid, BodyState()); - E->get().rid = p_body; - //E->get().rc=0; - E->get().in_scene = node && node->is_inside_tree(); + E->value.rid = p_body; + //E->value.rc=0; + E->value.in_scene = node && node->is_inside_tree(); if (node) { node->connect(SceneStringNames::get_singleton()->tree_entered, callable_mp(this, &RigidDynamicBody2D::_body_enter_tree), make_binds(objid)); node->connect(SceneStringNames::get_singleton()->tree_exiting, callable_mp(this, &RigidDynamicBody2D::_body_exit_tree), make_binds(objid)); - if (E->get().in_scene) { + if (E->value.in_scene) { emit_signal(SceneStringNames::get_singleton()->body_entered, node); } } - //E->get().rc++; + //E->value.rc++; } if (node) { - E->get().shapes.insert(ShapePair(p_body_shape, p_local_shape)); + E->value.shapes.insert(ShapePair(p_body_shape, p_local_shape)); } - if (E->get().in_scene) { + if (E->value.in_scene) { emit_signal(SceneStringNames::get_singleton()->body_shape_entered, p_body, node, p_body_shape, p_local_shape); } } else { - //E->get().rc--; + //E->value.rc--; if (node) { - E->get().shapes.erase(ShapePair(p_body_shape, p_local_shape)); + E->value.shapes.erase(ShapePair(p_body_shape, p_local_shape)); } - bool in_scene = E->get().in_scene; + bool in_scene = E->value.in_scene; - if (E->get().shapes.is_empty()) { + if (E->value.shapes.is_empty()) { if (node) { node->disconnect(SceneStringNames::get_singleton()->tree_entered, callable_mp(this, &RigidDynamicBody2D::_body_enter_tree)); node->disconnect(SceneStringNames::get_singleton()->tree_exiting, callable_mp(this, &RigidDynamicBody2D::_body_exit_tree)); @@ -427,7 +423,7 @@ void RigidDynamicBody2D::_body_inout(int p_status, const RID &p_body, ObjectID p } } - contact_monitor->body_map.erase(E); + contact_monitor->body_map.remove(E); } if (node && in_scene) { emit_signal(SceneStringNames::get_singleton()->body_shape_exited, p_body, node, p_body_shape, p_local_shape); @@ -490,7 +486,7 @@ void RigidDynamicBody2D::_body_state_changed(PhysicsDirectBodyState2D *p_state) int local_shape = p_state->get_contact_local_shape(i); int shape = p_state->get_contact_collider_shape(i); - Map<ObjectID, BodyState>::Element *E = contact_monitor->body_map.find(obj); + HashMap<ObjectID, BodyState>::Iterator E = contact_monitor->body_map.find(obj); if (!E) { toadd[toadd_count].rid = rid; toadd[toadd_count].local_shape = local_shape; @@ -501,7 +497,7 @@ void RigidDynamicBody2D::_body_state_changed(PhysicsDirectBodyState2D *p_state) } ShapePair sp(shape, local_shape); - int idx = E->get().shapes.find(sp); + int idx = E->value.shapes.find(sp); if (idx == -1) { toadd[toadd_count].rid = rid; toadd[toadd_count].local_shape = local_shape; @@ -511,7 +507,7 @@ void RigidDynamicBody2D::_body_state_changed(PhysicsDirectBodyState2D *p_state) continue; } - E->get().shapes[idx].tagged = true; + E->value.shapes[idx].tagged = true; } //put the ones to remove @@ -924,7 +920,7 @@ TypedArray<String> RigidDynamicBody2D::get_configuration_warnings() const { TypedArray<String> warnings = CollisionObject2D::get_configuration_warnings(); - if (ABS(t.elements[0].length() - 1.0) > 0.05 || ABS(t.elements[1].length() - 1.0) > 0.05) { + if (ABS(t.columns[0].length() - 1.0) > 0.05 || ABS(t.columns[1].length() - 1.0) > 0.05) { warnings.push_back(RTR("Size changes to RigidDynamicBody2D will be overridden by the physics engine when running.\nChange the size in children collision shapes instead.")); } @@ -1018,10 +1014,10 @@ void RigidDynamicBody2D::_bind_methods() { GDVIRTUAL_BIND(_integrate_forces, "state"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "mass", PROPERTY_HINT_RANGE, "0.01,1000,0.01,or_greater,exp"), "set_mass", "get_mass"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "inertia", PROPERTY_HINT_RANGE, "0,1000,0.01,or_greater,exp"), "set_inertia", "get_inertia"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "mass", PROPERTY_HINT_RANGE, "0.01,1000,0.01,or_greater,exp,suffix:kg"), "set_mass", "get_mass"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "inertia", PROPERTY_HINT_RANGE, U"0,1000,0.01,or_greater,exp,suffix:kg\u22C5px\u00B2"), "set_inertia", "get_inertia"); ADD_PROPERTY(PropertyInfo(Variant::INT, "center_of_mass_mode", PROPERTY_HINT_ENUM, "Auto,Custom", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), "set_center_of_mass_mode", "get_center_of_mass_mode"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "center_of_mass", PROPERTY_HINT_RANGE, "-10,10,0.01,or_lesser,or_greater"), "set_center_of_mass", "get_center_of_mass"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "center_of_mass", PROPERTY_HINT_RANGE, "-10,10,0.01,or_lesser,or_greater,suffix:px"), "set_center_of_mass", "get_center_of_mass"); ADD_LINKED_PROPERTY("center_of_mass_mode", "center_of_mass"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "physics_material_override", PROPERTY_HINT_RESOURCE_TYPE, "PhysicsMaterial"), "set_physics_material_override", "get_physics_material_override"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "gravity_scale", PROPERTY_HINT_RANGE, "-128,128,0.01"), "set_gravity_scale", "get_gravity_scale"); @@ -1035,16 +1031,16 @@ void RigidDynamicBody2D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "freeze"), "set_freeze_enabled", "is_freeze_enabled"); ADD_PROPERTY(PropertyInfo(Variant::INT, "freeze_mode", PROPERTY_HINT_ENUM, "Static,Kinematic"), "set_freeze_mode", "get_freeze_mode"); ADD_GROUP("Linear", "linear_"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "linear_velocity"), "set_linear_velocity", "get_linear_velocity"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "linear_velocity", PROPERTY_HINT_NONE, "suffix:px/s"), "set_linear_velocity", "get_linear_velocity"); ADD_PROPERTY(PropertyInfo(Variant::INT, "linear_damp_mode", PROPERTY_HINT_ENUM, "Combine,Replace"), "set_linear_damp_mode", "get_linear_damp_mode"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "linear_damp", PROPERTY_HINT_RANGE, "-1,100,0.001,or_greater"), "set_linear_damp", "get_linear_damp"); ADD_GROUP("Angular", "angular_"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "angular_velocity"), "set_angular_velocity", "get_angular_velocity"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "angular_velocity", PROPERTY_HINT_NONE, "suffix:rad/s"), "set_angular_velocity", "get_angular_velocity"); ADD_PROPERTY(PropertyInfo(Variant::INT, "angular_damp_mode", PROPERTY_HINT_ENUM, "Combine,Replace"), "set_angular_damp_mode", "get_angular_damp_mode"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "angular_damp", PROPERTY_HINT_RANGE, "-1,100,0.001,or_greater"), "set_angular_damp", "get_angular_damp"); ADD_GROUP("Constant Forces", "constant_"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "constant_force"), "set_constant_force", "get_constant_force"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "constant_torque"), "set_constant_torque", "get_constant_torque"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "constant_force", PROPERTY_HINT_NONE, U"suffix:kg\u22C5px/s\u00B2"), "set_constant_force", "get_constant_force"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "constant_torque", PROPERTY_HINT_NONE, U"suffix:kg\u22C5px\u00B2/s\u00B2/rad"), "set_constant_torque", "get_constant_torque"); ADD_SIGNAL(MethodInfo("body_shape_entered", PropertyInfo(Variant::RID, "body_rid"), PropertyInfo(Variant::OBJECT, "body", PROPERTY_HINT_RESOURCE_TYPE, "Node"), PropertyInfo(Variant::INT, "body_shape_index"), PropertyInfo(Variant::INT, "local_shape_index"))); ADD_SIGNAL(MethodInfo("body_shape_exited", PropertyInfo(Variant::RID, "body_rid"), PropertyInfo(Variant::OBJECT, "body", PROPERTY_HINT_RESOURCE_TYPE, "Node"), PropertyInfo(Variant::INT, "body_shape_index"), PropertyInfo(Variant::INT, "local_shape_index"))); @@ -1106,7 +1102,7 @@ bool CharacterBody2D::move_and_slide() { Vector2 current_platform_velocity = platform_velocity; Transform2D gt = get_global_transform(); - previous_position = gt.elements[2]; + previous_position = gt.columns[2]; if ((on_floor || on_wall) && platform_rid.is_valid()) { bool excluded = false; @@ -1119,7 +1115,7 @@ bool CharacterBody2D::move_and_slide() { //this approach makes sure there is less delay between the actual body velocity and the one we saved PhysicsDirectBodyState2D *bs = PhysicsServer2D::get_singleton()->body_get_direct_state(platform_rid); if (bs) { - Vector2 local_position = gt.elements[2] - bs->get_transform().elements[2]; + Vector2 local_position = gt.columns[2] - bs->get_transform().columns[2]; current_platform_velocity = bs->get_velocity_at_local_position(local_position); } else { // Body is removed or destroyed, invalidate floor. @@ -1141,6 +1137,7 @@ bool CharacterBody2D::move_and_slide() { if (!current_platform_velocity.is_equal_approx(Vector2())) { PhysicsServer2D::MotionParameters parameters(get_global_transform(), current_platform_velocity * delta, margin); + parameters.recovery_as_collision = true; // Also report collisions generated only from recovery. parameters.exclude_bodies.insert(platform_rid); if (platform_object_id.is_valid()) { parameters.exclude_objects.insert(platform_object_id); @@ -1199,8 +1196,9 @@ void CharacterBody2D::_move_and_slide_grounded(double p_delta, bool p_was_on_flo for (int iteration = 0; iteration < max_slides; ++iteration) { PhysicsServer2D::MotionParameters parameters(get_global_transform(), motion, margin); + parameters.recovery_as_collision = true; // Also report collisions generated only from recovery. - Vector2 prev_position = parameters.from.elements[2]; + Vector2 prev_position = parameters.from.columns[2]; PhysicsServer2D::MotionResult result; bool collided = move_and_collide(parameters, result, false, !sliding_enabled); @@ -1227,7 +1225,7 @@ void CharacterBody2D::_move_and_slide_grounded(double p_delta, bool p_was_on_flo if (on_floor && floor_stop_on_slope && (velocity.normalized() + up_direction).length() < 0.01) { Transform2D gt = get_global_transform(); if (result.travel.length() <= margin + CMP_EPSILON) { - gt.elements[2] -= result.travel; + gt.columns[2] -= result.travel; } set_global_transform(gt); velocity = Vector2(); @@ -1249,11 +1247,11 @@ void CharacterBody2D::_move_and_slide_grounded(double p_delta, bool p_was_on_flo if (result.travel.length() <= margin + CMP_EPSILON) { // Cancels the motion. Transform2D gt = get_global_transform(); - gt.elements[2] -= result.travel; + gt.columns[2] -= result.travel; set_global_transform(gt); } // Determines if you are on the ground. - _snap_on_floor(true, false); + _snap_on_floor(true, false, true); velocity = Vector2(); last_motion = Vector2(); motion = Vector2(); @@ -1308,7 +1306,7 @@ void CharacterBody2D::_move_and_slide_grounded(double p_delta, bool p_was_on_flo can_apply_constant_speed = false; sliding_enabled = true; Transform2D gt = get_global_transform(); - gt.elements[2] = prev_position; + gt.columns[2] = prev_position; set_global_transform(gt); Vector2 motion_slide_norm = motion.slide(prev_floor_normal).normalized(); @@ -1355,6 +1353,7 @@ void CharacterBody2D::_move_and_slide_floating(double p_delta) { bool first_slide = true; for (int iteration = 0; iteration < max_slides; ++iteration) { PhysicsServer2D::MotionParameters parameters(get_global_transform(), motion, margin); + parameters.recovery_as_collision = true; // Also report collisions generated only from recovery. PhysicsServer2D::MotionResult result; bool collided = move_and_collide(parameters, result, false, false); @@ -1392,8 +1391,8 @@ void CharacterBody2D::_move_and_slide_floating(double p_delta) { } } -void CharacterBody2D::_snap_on_floor(bool was_on_floor, bool vel_dir_facing_up) { - if (on_floor || !was_on_floor || vel_dir_facing_up) { +void CharacterBody2D::_snap_on_floor(bool p_was_on_floor, bool p_vel_dir_facing_up, bool p_wall_as_floor) { + if (on_floor || !p_was_on_floor || p_vel_dir_facing_up) { return; } @@ -1401,11 +1400,13 @@ void CharacterBody2D::_snap_on_floor(bool was_on_floor, bool vel_dir_facing_up) real_t length = MAX(floor_snap_length, margin); PhysicsServer2D::MotionParameters parameters(get_global_transform(), -up_direction * length, margin); + parameters.recovery_as_collision = true; // Also report collisions generated only from recovery. parameters.collide_separation_ray = true; PhysicsServer2D::MotionResult result; if (move_and_collide(parameters, result, true, false)) { - if (result.get_angle(up_direction) <= floor_max_angle + FLOOR_ANGLE_THRESHOLD) { + if ((result.get_angle(up_direction) <= floor_max_angle + FLOOR_ANGLE_THRESHOLD) || + (p_wall_as_floor && result.get_angle(-up_direction) > floor_max_angle + FLOOR_ANGLE_THRESHOLD)) { on_floor = true; floor_normal = result.collision_normal; _set_platform_data(result); @@ -1420,14 +1421,14 @@ void CharacterBody2D::_snap_on_floor(bool was_on_floor, bool vel_dir_facing_up) } } - parameters.from.elements[2] += result.travel; + parameters.from.columns[2] += result.travel; set_global_transform(parameters.from); } } } -bool CharacterBody2D::_on_floor_if_snapped(bool was_on_floor, bool vel_dir_facing_up) { - if (up_direction == Vector2() || on_floor || !was_on_floor || vel_dir_facing_up) { +bool CharacterBody2D::_on_floor_if_snapped(bool p_was_on_floor, bool p_vel_dir_facing_up) { + if (up_direction == Vector2() || on_floor || !p_was_on_floor || p_vel_dir_facing_up) { return false; } @@ -1435,6 +1436,7 @@ bool CharacterBody2D::_on_floor_if_snapped(bool was_on_floor, bool vel_dir_facin real_t length = MAX(floor_snap_length, margin); PhysicsServer2D::MotionParameters parameters(get_global_transform(), -up_direction * length, margin); + parameters.recovery_as_collision = true; // Also report collisions generated only from recovery. parameters.collide_separation_ray = true; PhysicsServer2D::MotionResult result; @@ -1516,7 +1518,7 @@ const Vector2 &CharacterBody2D::get_last_motion() const { } Vector2 CharacterBody2D::get_position_delta() const { - return get_global_transform().elements[2] - previous_position; + return get_global_transform().columns[2] - previous_position; } const Vector2 &CharacterBody2D::get_real_velocity() const { @@ -1750,7 +1752,7 @@ void CharacterBody2D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::INT, "motion_mode", PROPERTY_HINT_ENUM, "Grounded,Floating", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), "set_motion_mode", "get_motion_mode"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "up_direction"), "set_up_direction", "get_up_direction"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "velocity", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_velocity", "get_velocity"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "velocity", PROPERTY_HINT_NONE, "suffix:px/s", PROPERTY_USAGE_NO_EDITOR), "set_velocity", "get_velocity"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "slide_on_ceiling"), "set_slide_on_ceiling_enabled", "is_slide_on_ceiling_enabled"); ADD_PROPERTY(PropertyInfo(Variant::INT, "max_slides", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_max_slides", "get_max_slides"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "wall_min_slide_angle", PROPERTY_HINT_RANGE, "0,180,0.1,radians", PROPERTY_USAGE_DEFAULT), "set_wall_min_slide_angle", "get_wall_min_slide_angle"); @@ -1759,12 +1761,12 @@ void CharacterBody2D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "floor_constant_speed"), "set_floor_constant_speed_enabled", "is_floor_constant_speed_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "floor_block_on_wall"), "set_floor_block_on_wall_enabled", "is_floor_block_on_wall_enabled"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "floor_max_angle", PROPERTY_HINT_RANGE, "0,180,0.1,radians"), "set_floor_max_angle", "get_floor_max_angle"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "floor_snap_length", PROPERTY_HINT_RANGE, "0,32,0.1,or_greater"), "set_floor_snap_length", "get_floor_snap_length"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "floor_snap_length", PROPERTY_HINT_RANGE, "0,32,0.1,or_greater,suffix:px"), "set_floor_snap_length", "get_floor_snap_length"); ADD_GROUP("Moving Platform", "moving_platform"); ADD_PROPERTY(PropertyInfo(Variant::INT, "moving_platform_apply_velocity_on_leave", PROPERTY_HINT_ENUM, "Always,Upward Only,Never", PROPERTY_USAGE_DEFAULT), "set_moving_platform_apply_velocity_on_leave", "get_moving_platform_apply_velocity_on_leave"); ADD_PROPERTY(PropertyInfo(Variant::INT, "moving_platform_floor_layers", PROPERTY_HINT_LAYERS_2D_PHYSICS), "set_moving_platform_floor_layers", "get_moving_platform_floor_layers"); ADD_PROPERTY(PropertyInfo(Variant::INT, "moving_platform_wall_layers", PROPERTY_HINT_LAYERS_2D_PHYSICS), "set_moving_platform_wall_layers", "get_moving_platform_wall_layers"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "collision/safe_margin", PROPERTY_HINT_RANGE, "0.001,256,0.001"), "set_safe_margin", "get_safe_margin"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "collision/safe_margin", PROPERTY_HINT_RANGE, "0.001,256,0.001,suffix:px"), "set_safe_margin", "get_safe_margin"); BIND_ENUM_CONSTANT(MOTION_MODE_GROUNDED); BIND_ENUM_CONSTANT(MOTION_MODE_FLOATING); diff --git a/scene/2d/physics_body_2d.h b/scene/2d/physics_body_2d.h index 8d9e31d4dd..7401fc7578 100644 --- a/scene/2d/physics_body_2d.h +++ b/scene/2d/physics_body_2d.h @@ -200,7 +200,7 @@ private: struct ContactMonitor { bool locked = false; - Map<ObjectID, BodyState> body_map; + HashMap<ObjectID, BodyState> body_map; }; ContactMonitor *contact_monitor = nullptr; @@ -441,11 +441,11 @@ private: Ref<KinematicCollision2D> _get_slide_collision(int p_bounce); Ref<KinematicCollision2D> _get_last_slide_collision(); const Vector2 &get_up_direction() const; - bool _on_floor_if_snapped(bool was_on_floor, bool vel_dir_facing_up); + bool _on_floor_if_snapped(bool p_was_on_floor, bool p_vel_dir_facing_up); void set_up_direction(const Vector2 &p_up_direction); void _set_collision_direction(const PhysicsServer2D::MotionResult &p_result); void _set_platform_data(const PhysicsServer2D::MotionResult &p_result); - void _snap_on_floor(bool was_on_floor, bool vel_dir_facing_up); + void _snap_on_floor(bool p_was_on_floor, bool p_vel_dir_facing_up, bool p_wall_as_floor = false); protected: void _notification(int p_what); diff --git a/scene/2d/polygon_2d.cpp b/scene/2d/polygon_2d.cpp index f9986c2f30..ba62941d3a 100644 --- a/scene/2d/polygon_2d.cpp +++ b/scene/2d/polygon_2d.cpp @@ -635,18 +635,19 @@ void Polygon2D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::COLOR, "color"), "set_color", "get_color"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "offset"), "set_offset", "get_offset"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "antialiased"), "set_antialiased", "get_antialiased"); - ADD_GROUP("Texture2D", ""); + + ADD_GROUP("Texture", "texture_"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_texture", "get_texture"); - ADD_GROUP("Texture2D", "texture_"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "texture_offset"), "set_texture_offset", "get_texture_offset"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "texture_scale"), "set_texture_scale", "get_texture_scale"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "texture_offset", PROPERTY_HINT_NONE, "suffix:px"), "set_texture_offset", "get_texture_offset"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "texture_scale", PROPERTY_HINT_LINK), "set_texture_scale", "get_texture_scale"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "texture_rotation", PROPERTY_HINT_RANGE, "-360,360,0.1,or_lesser,or_greater,radians"), "set_texture_rotation", "get_texture_rotation"); + ADD_GROUP("Skeleton", ""); ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "skeleton", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Skeleton2D"), "set_skeleton", "get_skeleton"); ADD_GROUP("Invert", "invert_"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "invert_enable"), "set_invert", "get_invert"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "invert_border", PROPERTY_HINT_RANGE, "0.1,16384,0.1"), "set_invert_border", "get_invert_border"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "invert_border", PROPERTY_HINT_RANGE, "0.1,16384,0.1,suffix:px"), "set_invert_border", "get_invert_border"); ADD_GROUP("Data", ""); ADD_PROPERTY(PropertyInfo(Variant::PACKED_VECTOR2_ARRAY, "polygon"), "set_polygon", "get_polygon"); diff --git a/scene/2d/position_2d.cpp b/scene/2d/position_2d.cpp index d946ea63bf..cfa4d0401e 100644 --- a/scene/2d/position_2d.cpp +++ b/scene/2d/position_2d.cpp @@ -113,7 +113,7 @@ void Position2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_gizmo_extents", "extents"), &Position2D::set_gizmo_extents); ClassDB::bind_method(D_METHOD("get_gizmo_extents"), &Position2D::get_gizmo_extents); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "gizmo_extents", PROPERTY_HINT_RANGE, "0,1000,0.1,or_greater"), "set_gizmo_extents", "get_gizmo_extents"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "gizmo_extents", PROPERTY_HINT_RANGE, "0,1000,0.1,or_greater,suffix:px"), "set_gizmo_extents", "get_gizmo_extents"); } Position2D::Position2D() { diff --git a/scene/2d/ray_cast_2d.cpp b/scene/2d/ray_cast_2d.cpp index 37db9211e1..8953813452 100644 --- a/scene/2d/ray_cast_2d.cpp +++ b/scene/2d/ray_cast_2d.cpp @@ -353,7 +353,7 @@ void RayCast2D::_bind_methods() { 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::VECTOR2, "target_position", PROPERTY_HINT_NONE, "suffix:px"), "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"); diff --git a/scene/2d/ray_cast_2d.h b/scene/2d/ray_cast_2d.h index 2c6f2d5c00..1fb97d89fe 100644 --- a/scene/2d/ray_cast_2d.h +++ b/scene/2d/ray_cast_2d.h @@ -44,7 +44,7 @@ class RayCast2D : public Node2D { int against_shape = 0; Vector2 collision_point; Vector2 collision_normal; - Set<RID> exclude; + HashSet<RID> exclude; uint32_t collision_mask = 1; bool exclude_parent_body = true; diff --git a/scene/2d/shape_cast_2d.cpp b/scene/2d/shape_cast_2d.cpp index a2f4b16ed3..ae810156a2 100644 --- a/scene/2d/shape_cast_2d.cpp +++ b/scene/2d/shape_cast_2d.cpp @@ -444,8 +444,8 @@ void ShapeCast2D::_bind_methods() { 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::VECTOR2, "target_position", PROPERTY_HINT_NONE, "suffix:px"), "set_target_position", "get_target_position"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "margin", PROPERTY_HINT_RANGE, "0,100,0.01,suffix:px"), "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"); diff --git a/scene/2d/shape_cast_2d.h b/scene/2d/shape_cast_2d.h index 15436d6e3d..7ff080aed0 100644 --- a/scene/2d/shape_cast_2d.h +++ b/scene/2d/shape_cast_2d.h @@ -46,7 +46,7 @@ class ShapeCast2D : public Node2D { RID shape_rid; Vector2 target_position = Vector2(0, 50); - Set<RID> exclude; + HashSet<RID> exclude; real_t margin = 0.0; uint32_t collision_mask = 1; bool exclude_parent_body = true; diff --git a/scene/2d/skeleton_2d.cpp b/scene/2d/skeleton_2d.cpp index aa039e07ee..cbacb7f579 100644 --- a/scene/2d/skeleton_2d.cpp +++ b/scene/2d/skeleton_2d.cpp @@ -81,14 +81,14 @@ bool Bone2D::_get(const StringName &p_path, Variant &r_ret) const { } void Bone2D::_get_property_list(List<PropertyInfo> *p_list) const { - p_list->push_back(PropertyInfo(Variant::BOOL, "auto_calculate_length_and_angle", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); + p_list->push_back(PropertyInfo(Variant::BOOL, PNAME("auto_calculate_length_and_angle"), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); if (!autocalculate_length_and_angle) { - p_list->push_back(PropertyInfo(Variant::FLOAT, "length", PROPERTY_HINT_RANGE, "1, 1024, 1", PROPERTY_USAGE_DEFAULT)); - p_list->push_back(PropertyInfo(Variant::FLOAT, "bone_angle", PROPERTY_HINT_RANGE, "-360, 360, 0.01", PROPERTY_USAGE_DEFAULT)); + p_list->push_back(PropertyInfo(Variant::FLOAT, PNAME("length"), PROPERTY_HINT_RANGE, "1, 1024, 1", PROPERTY_USAGE_DEFAULT)); + p_list->push_back(PropertyInfo(Variant::FLOAT, PNAME("bone_angle"), PROPERTY_HINT_RANGE, "-360, 360, 0.01", PROPERTY_USAGE_DEFAULT)); } #ifdef TOOLS_ENABLED - p_list->push_back(PropertyInfo(Variant::BOOL, "editor_settings/show_bone_gizmo", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); + p_list->push_back(PropertyInfo(Variant::BOOL, PNAME("editor_settings/show_bone_gizmo"), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); #endif // TOOLS_ENABLED } @@ -390,7 +390,7 @@ void Bone2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_bone_angle", "angle"), &Bone2D::set_bone_angle); ClassDB::bind_method(D_METHOD("get_bone_angle"), &Bone2D::get_bone_angle); - ADD_PROPERTY(PropertyInfo(Variant::TRANSFORM2D, "rest"), "set_rest", "get_rest"); + ADD_PROPERTY(PropertyInfo(Variant::TRANSFORM2D, "rest", PROPERTY_HINT_NONE, "suffix:px"), "set_rest", "get_rest"); } void Bone2D::set_rest(const Transform2D &p_rest) { @@ -560,7 +560,7 @@ bool Skeleton2D::_get(const StringName &p_path, Variant &r_ret) const { void Skeleton2D::_get_property_list(List<PropertyInfo> *p_list) const { p_list->push_back( - PropertyInfo(Variant::OBJECT, "modification_stack", + PropertyInfo(Variant::OBJECT, PNAME("modification_stack"), PROPERTY_HINT_RESOURCE_TYPE, "SkeletonModificationStack2D", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_DEFERRED_SET_RESOURCE | PROPERTY_USAGE_DO_NOT_SHARE_ON_DUPLICATE)); diff --git a/scene/2d/sprite_2d.cpp b/scene/2d/sprite_2d.cpp index facd164a0e..b3062ca02a 100644 --- a/scene/2d/sprite_2d.cpp +++ b/scene/2d/sprite_2d.cpp @@ -439,14 +439,14 @@ void Sprite2D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_texture", "get_texture"); ADD_GROUP("Offset", ""); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "centered"), "set_centered", "is_centered"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "offset"), "set_offset", "get_offset"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "offset", PROPERTY_HINT_NONE, "suffix:px"), "set_offset", "get_offset"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "flip_h"), "set_flip_h", "is_flipped_h"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "flip_v"), "set_flip_v", "is_flipped_v"); ADD_GROUP("Animation", ""); ADD_PROPERTY(PropertyInfo(Variant::INT, "hframes", PROPERTY_HINT_RANGE, "1,16384,1"), "set_hframes", "get_hframes"); ADD_PROPERTY(PropertyInfo(Variant::INT, "vframes", PROPERTY_HINT_RANGE, "1,16384,1"), "set_vframes", "get_vframes"); ADD_PROPERTY(PropertyInfo(Variant::INT, "frame"), "set_frame", "get_frame"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR2I, "frame_coords", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "set_frame_coords", "get_frame_coords"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2I, "frame_coords", PROPERTY_HINT_NONE, "suffix:px", PROPERTY_USAGE_EDITOR), "set_frame_coords", "get_frame_coords"); ADD_GROUP("Region", "region_"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "region_enabled"), "set_region_enabled", "is_region_enabled"); diff --git a/scene/2d/tile_map.cpp b/scene/2d/tile_map.cpp index cbbadf1178..cf8b6b8f94 100644 --- a/scene/2d/tile_map.cpp +++ b/scene/2d/tile_map.cpp @@ -34,45 +34,36 @@ #include "scene/resources/world_2d.h" #include "servers/navigation_server_2d.h" -Map<Vector2i, TileSet::CellNeighbor> TileMap::TerrainConstraint::get_overlapping_coords_and_peering_bits() const { - Map<Vector2i, TileSet::CellNeighbor> output; +HashMap<Vector2i, TileSet::CellNeighbor> TileMap::TerrainConstraint::get_overlapping_coords_and_peering_bits() const { + HashMap<Vector2i, TileSet::CellNeighbor> output; + + ERR_FAIL_COND_V(is_center_bit(), output); + Ref<TileSet> tile_set = tile_map->get_tileset(); ERR_FAIL_COND_V(!tile_set.is_valid(), output); TileSet::TileShape shape = tile_set->get_tile_shape(); if (shape == TileSet::TILE_SHAPE_SQUARE) { switch (bit) { - case 0: + case 1: output[base_cell_coords] = TileSet::CELL_NEIGHBOR_RIGHT_SIDE; output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_LEFT_SIDE; break; - case 1: + case 2: output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER; output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER; output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER)] = TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER; output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER; break; - case 2: + case 3: output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_SIDE; output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_SIDE; break; - case 3: - output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER; - output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER; - output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER)] = TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER; - output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_LEFT_SIDE)] = TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER; - break; default: ERR_FAIL_V(output); } } else if (shape == TileSet::TILE_SHAPE_ISOMETRIC) { switch (bit) { - case 0: - output[base_cell_coords] = TileSet::CELL_NEIGHBOR_RIGHT_CORNER; - output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_BOTTOM_CORNER; - output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_RIGHT_CORNER)] = TileSet::CELL_NEIGHBOR_LEFT_CORNER; - output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_CORNER; - break; case 1: output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE; output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE; @@ -95,25 +86,25 @@ Map<Vector2i, TileSet::CellNeighbor> TileMap::TerrainConstraint::get_overlapping TileSet::TileOffsetAxis offset_axis = tile_set->get_tile_offset_axis(); if (offset_axis == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) { switch (bit) { - case 0: + case 1: output[base_cell_coords] = TileSet::CELL_NEIGHBOR_RIGHT_SIDE; output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_LEFT_SIDE; break; - case 1: + case 2: output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER; output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER; output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_CORNER; break; - case 2: + case 3: output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE; output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE; break; - case 3: + case 4: output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_CORNER; output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER; output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER; break; - case 4: + case 5: output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE; output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE; break; @@ -122,25 +113,25 @@ Map<Vector2i, TileSet::CellNeighbor> TileMap::TerrainConstraint::get_overlapping } } else { switch (bit) { - case 0: + case 1: output[base_cell_coords] = TileSet::CELL_NEIGHBOR_RIGHT_CORNER; output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER; output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER; break; - case 1: + case 2: output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE; output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE; break; - case 2: + case 3: output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER; output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_LEFT_CORNER; output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER; break; - case 3: + case 4: output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_SIDE; output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_SIDE; break; - case 4: + case 5: output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE; output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE; break; @@ -152,6 +143,17 @@ Map<Vector2i, TileSet::CellNeighbor> TileMap::TerrainConstraint::get_overlapping return output; } +TileMap::TerrainConstraint::TerrainConstraint(const TileMap *p_tile_map, const Vector2i &p_position, int p_terrain) { + tile_map = p_tile_map; + + Ref<TileSet> tile_set = tile_map->get_tileset(); + ERR_FAIL_COND(!tile_set.is_valid()); + + bit = 0; + base_cell_coords = p_position; + terrain = p_terrain; +} + TileMap::TerrainConstraint::TerrainConstraint(const TileMap *p_tile_map, const Vector2i &p_position, const TileSet::CellNeighbor &p_bit, int p_terrain) { // The way we build the constraint make it easy to detect conflicting constraints. tile_map = p_tile_map; @@ -163,35 +165,35 @@ TileMap::TerrainConstraint::TerrainConstraint(const TileMap *p_tile_map, const V if (shape == TileSet::TILE_SHAPE_SQUARE) { switch (p_bit) { case TileSet::CELL_NEIGHBOR_RIGHT_SIDE: - bit = 0; + bit = 1; base_cell_coords = p_position; break; case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER: - bit = 1; + bit = 2; base_cell_coords = p_position; break; case TileSet::CELL_NEIGHBOR_BOTTOM_SIDE: - bit = 2; + bit = 3; base_cell_coords = p_position; break; case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER: - bit = 1; + bit = 2; base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_LEFT_SIDE); break; case TileSet::CELL_NEIGHBOR_LEFT_SIDE: - bit = 0; + bit = 1; base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_LEFT_SIDE); break; case TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER: - bit = 1; + bit = 2; base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER); break; case TileSet::CELL_NEIGHBOR_TOP_SIDE: - bit = 2; + bit = 3; base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_SIDE); break; case TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER: - bit = 1; + bit = 2; base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_SIDE); break; default: @@ -201,35 +203,35 @@ TileMap::TerrainConstraint::TerrainConstraint(const TileMap *p_tile_map, const V } else if (shape == TileSet::TILE_SHAPE_ISOMETRIC) { switch (p_bit) { case TileSet::CELL_NEIGHBOR_RIGHT_CORNER: - bit = 1; + bit = 2; base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE); break; case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE: - bit = 0; + bit = 1; base_cell_coords = p_position; break; case TileSet::CELL_NEIGHBOR_BOTTOM_CORNER: - bit = 1; + bit = 2; base_cell_coords = p_position; break; case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE: - bit = 2; + bit = 3; base_cell_coords = p_position; break; case TileSet::CELL_NEIGHBOR_LEFT_CORNER: - bit = 1; + bit = 2; base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE); break; case TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE: - bit = 0; + bit = 1; base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE); break; case TileSet::CELL_NEIGHBOR_TOP_CORNER: - bit = 1; + bit = 2; base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_CORNER); break; case TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE: - bit = 2; + bit = 3; base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE); break; default: @@ -242,51 +244,51 @@ TileMap::TerrainConstraint::TerrainConstraint(const TileMap *p_tile_map, const V if (offset_axis == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) { switch (p_bit) { case TileSet::CELL_NEIGHBOR_RIGHT_SIDE: - bit = 0; + bit = 1; base_cell_coords = p_position; break; case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER: - bit = 1; + bit = 2; base_cell_coords = p_position; break; case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE: - bit = 2; + bit = 3; base_cell_coords = p_position; break; case TileSet::CELL_NEIGHBOR_BOTTOM_CORNER: - bit = 3; + bit = 4; base_cell_coords = p_position; break; case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE: - bit = 4; + bit = 5; base_cell_coords = p_position; break; case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER: - bit = 1; + bit = 2; base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_LEFT_SIDE); break; case TileSet::CELL_NEIGHBOR_LEFT_SIDE: - bit = 0; + bit = 1; base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_LEFT_SIDE); break; case TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER: - bit = 3; + bit = 4; base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE); break; case TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE: - bit = 2; + bit = 3; base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE); break; case TileSet::CELL_NEIGHBOR_TOP_CORNER: - bit = 1; + bit = 2; base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE); break; case TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE: - bit = 4; + bit = 5; base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE); break; case TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER: - bit = 3; + bit = 4; base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE); break; default: @@ -296,51 +298,51 @@ TileMap::TerrainConstraint::TerrainConstraint(const TileMap *p_tile_map, const V } else { switch (p_bit) { case TileSet::CELL_NEIGHBOR_RIGHT_CORNER: - bit = 0; + bit = 1; base_cell_coords = p_position; break; case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE: - bit = 1; + bit = 2; base_cell_coords = p_position; break; case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER: - bit = 2; + bit = 3; base_cell_coords = p_position; break; case TileSet::CELL_NEIGHBOR_BOTTOM_SIDE: - bit = 3; + bit = 4; base_cell_coords = p_position; break; case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER: - bit = 0; + bit = 1; base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE); break; case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE: - bit = 4; + bit = 5; base_cell_coords = p_position; break; case TileSet::CELL_NEIGHBOR_LEFT_CORNER: - bit = 2; + bit = 3; base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE); break; case TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE: - bit = 1; + bit = 2; base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE); break; case TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER: - bit = 0; + bit = 1; base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE); break; case TileSet::CELL_NEIGHBOR_TOP_SIDE: - bit = 3; + bit = 4; base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_SIDE); break; case TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER: - bit = 2; + bit = 3; base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_SIDE); break; case TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE: - bit = 4; + bit = 5; base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE); break; default: @@ -742,7 +744,7 @@ Vector2i TileMap::_coords_to_quadrant_coords(int p_layer, const Vector2i &p_coor p_coords.y > 0 ? p_coords.y / quadrant_size : (p_coords.y - (quadrant_size - 1)) / quadrant_size); } -Map<Vector2i, TileMapQuadrant>::Element *TileMap::_create_quadrant(int p_layer, const Vector2i &p_qk) { +HashMap<Vector2i, TileMapQuadrant>::Iterator TileMap::_create_quadrant(int p_layer, const Vector2i &p_qk) { ERR_FAIL_INDEX_V(p_layer, (int)layers.size(), nullptr); TileMapQuadrant q; @@ -765,9 +767,9 @@ Map<Vector2i, TileMapQuadrant>::Element *TileMap::_create_quadrant(int p_layer, return layers[p_layer].quadrant_map.insert(p_qk, q); } -void TileMap::_make_quadrant_dirty(Map<Vector2i, TileMapQuadrant>::Element *Q) { +void TileMap::_make_quadrant_dirty(HashMap<Vector2i, TileMapQuadrant>::Iterator Q) { // Make the given quadrant dirty, then trigger an update later. - TileMapQuadrant &q = Q->get(); + TileMapQuadrant &q = Q->value; if (!q.dirty_list_element.in_list()) { layers[q.layer].dirty_quadrant_list.add(&q.dirty_list_element); } @@ -810,8 +812,8 @@ void TileMap::_update_dirty_quadrants() { for (SelfList<TileMapQuadrant> *q = dirty_quadrant_list.first(); q; q = q->next()) { q->self()->map_to_world.clear(); q->self()->world_to_map.clear(); - for (Set<Vector2i>::Element *E = q->self()->cells.front(); E; E = E->next()) { - Vector2i pk = E->get(); + for (const Vector2i &E : q->self()->cells) { + Vector2i pk = E; Vector2i pk_world_coords = map_to_world(pk); q->self()->map_to_world[pk] = pk_world_coords; q->self()->world_to_map[pk_world_coords] = pk; @@ -871,18 +873,18 @@ void TileMap::_recreate_layer_internals(int p_layer) { _rendering_update_layer(p_layer); // Recreate the quadrants. - const Map<Vector2i, TileMapCell> &tile_map = layers[p_layer].tile_map; + const HashMap<Vector2i, TileMapCell> &tile_map = layers[p_layer].tile_map; for (const KeyValue<Vector2i, TileMapCell> &E : tile_map) { Vector2i qk = _coords_to_quadrant_coords(p_layer, Vector2i(E.key.x, E.key.y)); - Map<Vector2i, TileMapQuadrant>::Element *Q = layers[p_layer].quadrant_map.find(qk); + HashMap<Vector2i, TileMapQuadrant>::Iterator Q = layers[p_layer].quadrant_map.find(qk); if (!Q) { Q = _create_quadrant(p_layer, qk); - layers[p_layer].dirty_quadrant_list.add(&Q->get().dirty_list_element); + layers[p_layer].dirty_quadrant_list.add(&Q->value.dirty_list_element); } Vector2i pk = E.key; - Q->get().cells.insert(pk); + Q->value.cells.insert(pk); _make_quadrant_dirty(Q); } @@ -896,9 +898,9 @@ void TileMap::_recreate_internals() { } } -void TileMap::_erase_quadrant(Map<Vector2i, TileMapQuadrant>::Element *Q) { +void TileMap::_erase_quadrant(HashMap<Vector2i, TileMapQuadrant>::Iterator Q) { // Remove a quadrant. - TileMapQuadrant *q = &(Q->get()); + TileMapQuadrant *q = &(Q->value); // Call the cleanup_quadrant method on plugins. if (tile_set.is_valid()) { @@ -917,7 +919,7 @@ void TileMap::_erase_quadrant(Map<Vector2i, TileMapQuadrant>::Element *Q) { RenderingServer *rs = RenderingServer::get_singleton(); rs->free(q->debug_canvas_item); - layers[q->layer].quadrant_map.erase(Q); + layers[q->layer].quadrant_map.remove(Q); rect_cache_dirty = true; } @@ -926,7 +928,7 @@ void TileMap::_clear_layer_internals(int p_layer) { // Clear quadrants. while (layers[p_layer].quadrant_map.size()) { - _erase_quadrant(layers[p_layer].quadrant_map.front()); + _erase_quadrant(layers[p_layer].quadrant_map.begin()); } // Clear the layers internals. @@ -954,15 +956,17 @@ void TileMap::_recompute_rect_cache() { } Rect2 r_total; + bool first = true; for (unsigned int layer = 0; layer < layers.size(); layer++) { - for (const Map<Vector2i, TileMapQuadrant>::Element *E = layers[layer].quadrant_map.front(); E; E = E->next()) { + for (const KeyValue<Vector2i, TileMapQuadrant> &E : layers[layer].quadrant_map) { Rect2 r; - r.position = map_to_world(E->key() * get_effective_quadrant_size(layer)); - r.expand_to(map_to_world((E->key() + Vector2i(1, 0)) * get_effective_quadrant_size(layer))); - r.expand_to(map_to_world((E->key() + Vector2i(1, 1)) * get_effective_quadrant_size(layer))); - r.expand_to(map_to_world((E->key() + Vector2i(0, 1)) * get_effective_quadrant_size(layer))); - if (E == layers[layer].quadrant_map.front()) { + r.position = map_to_world(E.key * get_effective_quadrant_size(layer)); + r.expand_to(map_to_world((E.key + Vector2i(1, 0)) * get_effective_quadrant_size(layer))); + r.expand_to(map_to_world((E.key + Vector2i(1, 1)) * get_effective_quadrant_size(layer))); + r.expand_to(map_to_world((E.key + Vector2i(0, 1)) * get_effective_quadrant_size(layer))); + if (first) { r_total = r; + first = false; } else { r_total = r_total.merge(r); } @@ -1086,7 +1090,7 @@ void TileMap::_rendering_update_dirty_quadrants(SelfList<TileMapQuadrant>::List q.occluders.clear(); // Those allow to group cell per material or z-index. - Ref<ShaderMaterial> prev_material; + Ref<Material> prev_material; int prev_z_index = 0; RID prev_canvas_item; @@ -1125,7 +1129,7 @@ void TileMap::_rendering_update_dirty_quadrants(SelfList<TileMapQuadrant>::List tile_data = atlas_source->get_tile_data(c.get_atlas_coords(), c.alternative_tile); } - Ref<ShaderMaterial> mat = tile_data->get_material(); + Ref<Material> mat = tile_data->get_material(); int z_index = tile_data->get_z_index(); // Quandrant pos. @@ -1154,6 +1158,7 @@ void TileMap::_rendering_update_dirty_quadrants(SelfList<TileMapQuadrant>::List rs->canvas_item_set_transform(canvas_item, xform); rs->canvas_item_set_light_mask(canvas_item, get_light_mask()); + rs->canvas_item_set_z_as_relative_to_parent(canvas_item, true); rs->canvas_item_set_z_index(canvas_item, z_index); rs->canvas_item_set_default_texture_filter(canvas_item, RS::CanvasItemTextureFilter(get_texture_filter())); @@ -1201,7 +1206,7 @@ void TileMap::_rendering_update_dirty_quadrants(SelfList<TileMapQuadrant>::List for (int layer = 0; layer < (int)layers.size(); layer++) { // Sort the quadrants coords per world coordinates - Map<Vector2i, Vector2i, TileMapQuadrant::CoordsWorldComparator> world_to_map; + RBMap<Vector2i, Vector2i, TileMapQuadrant::CoordsWorldComparator> world_to_map; for (const KeyValue<Vector2i, TileMapQuadrant> &E : layers[layer].quadrant_map) { world_to_map[map_to_world(E.key)] = E.key; } @@ -1248,8 +1253,8 @@ void TileMap::_rendering_draw_quadrant_debug(TileMapQuadrant *p_quadrant) { // Draw a placeholder for scenes needing one. RenderingServer *rs = RenderingServer::get_singleton(); Vector2 quadrant_pos = map_to_world(p_quadrant->coords * get_effective_quadrant_size(p_quadrant->layer)); - for (Set<Vector2i>::Element *E_cell = p_quadrant->cells.front(); E_cell; E_cell = E_cell->next()) { - const TileMapCell &c = get_cell(p_quadrant->layer, E_cell->get(), true); + for (const Vector2i &E_cell : p_quadrant->cells) { + const TileMapCell &c = get_cell(p_quadrant->layer, E_cell, true); TileSetSource *source; if (tile_set->has_source(c.source_id)) { @@ -1279,7 +1284,7 @@ void TileMap::_rendering_draw_quadrant_debug(TileMapQuadrant *p_quadrant) { // Draw a placeholder tile. Transform2D xform; - xform.set_origin(map_to_world(E_cell->get()) - quadrant_pos); + xform.set_origin(map_to_world(E_cell) - quadrant_pos); rs->canvas_item_add_set_transform(p_quadrant->debug_canvas_item, xform); rs->canvas_item_add_circle(p_quadrant->debug_canvas_item, Vector2(), MIN(tile_set->get_tile_size().x, tile_set->get_tile_size().y) / 4.0, color); } @@ -1462,8 +1467,8 @@ void TileMap::_physics_update_dirty_quadrants(SelfList<TileMapQuadrant>::List &r q.bodies.clear(); // Recreate bodies and shapes. - for (Set<Vector2i>::Element *E_cell = q.cells.front(); E_cell; E_cell = E_cell->next()) { - TileMapCell c = get_cell(q.layer, E_cell->get(), true); + for (const Vector2i &E_cell : q.cells) { + TileMapCell c = get_cell(q.layer, E_cell, true); TileSetSource *source; if (tile_set->has_source(c.source_id)) { @@ -1476,8 +1481,8 @@ void TileMap::_physics_update_dirty_quadrants(SelfList<TileMapQuadrant>::List &r TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source); if (atlas_source) { const TileData *tile_data; - if (q.runtime_tile_data_cache.has(E_cell->get())) { - tile_data = q.runtime_tile_data_cache[E_cell->get()]; + if (q.runtime_tile_data_cache.has(E_cell)) { + tile_data = q.runtime_tile_data_cache[E_cell]; } else { tile_data = atlas_source->get_tile_data(c.get_atlas_coords(), c.alternative_tile); } @@ -1488,12 +1493,12 @@ void TileMap::_physics_update_dirty_quadrants(SelfList<TileMapQuadrant>::List &r // Create the body. RID body = ps->body_create(); - bodies_coords[body] = E_cell->get(); + bodies_coords[body] = E_cell; ps->body_set_mode(body, collision_animatable ? PhysicsServer2D::BODY_MODE_KINEMATIC : PhysicsServer2D::BODY_MODE_STATIC); ps->body_set_space(body, space); Transform2D xform; - xform.set_origin(map_to_world(E_cell->get())); + xform.set_origin(map_to_world(E_cell)); xform = global_transform * xform; ps->body_set_state(body, PhysicsServer2D::BODY_STATE_TRANSFORM, xform); @@ -1659,8 +1664,8 @@ void TileMap::_navigation_update_dirty_quadrants(SelfList<TileMapQuadrant>::List q.navigation_regions.clear(); // Get the navigation polygons and create regions. - for (Set<Vector2i>::Element *E_cell = q.cells.front(); E_cell; E_cell = E_cell->next()) { - TileMapCell c = get_cell(q.layer, E_cell->get(), true); + for (const Vector2i &E_cell : q.cells) { + TileMapCell c = get_cell(q.layer, E_cell, true); TileSetSource *source; if (tile_set->has_source(c.source_id)) { @@ -1673,12 +1678,12 @@ void TileMap::_navigation_update_dirty_quadrants(SelfList<TileMapQuadrant>::List TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source); if (atlas_source) { const TileData *tile_data; - if (q.runtime_tile_data_cache.has(E_cell->get())) { - tile_data = q.runtime_tile_data_cache[E_cell->get()]; + if (q.runtime_tile_data_cache.has(E_cell)) { + tile_data = q.runtime_tile_data_cache[E_cell]; } else { tile_data = atlas_source->get_tile_data(c.get_atlas_coords(), c.alternative_tile); } - q.navigation_regions[E_cell->get()].resize(tile_set->get_navigation_layers_count()); + q.navigation_regions[E_cell].resize(tile_set->get_navigation_layers_count()); for (int layer_index = 0; layer_index < tile_set->get_navigation_layers_count(); layer_index++) { Ref<NavigationPolygon> navpoly; @@ -1686,13 +1691,13 @@ void TileMap::_navigation_update_dirty_quadrants(SelfList<TileMapQuadrant>::List if (navpoly.is_valid()) { Transform2D tile_transform; - tile_transform.set_origin(map_to_world(E_cell->get())); + tile_transform.set_origin(map_to_world(E_cell)); RID region = NavigationServer2D::get_singleton()->region_create(); NavigationServer2D::get_singleton()->region_set_map(region, get_world_2d()->get_navigation_map()); NavigationServer2D::get_singleton()->region_set_transform(region, tilemap_xform * tile_transform); NavigationServer2D::get_singleton()->region_set_navpoly(region, navpoly); - q.navigation_regions[E_cell->get()].write[layer_index] = region; + q.navigation_regions[E_cell].write[layer_index] = region; } } } @@ -1748,8 +1753,8 @@ void TileMap::_navigation_draw_quadrant_debug(TileMapQuadrant *p_quadrant) { Vector2 quadrant_pos = map_to_world(p_quadrant->coords * get_effective_quadrant_size(p_quadrant->layer)); - for (Set<Vector2i>::Element *E_cell = p_quadrant->cells.front(); E_cell; E_cell = E_cell->next()) { - TileMapCell c = get_cell(p_quadrant->layer, E_cell->get(), true); + for (const Vector2i &E_cell : p_quadrant->cells) { + TileMapCell c = get_cell(p_quadrant->layer, E_cell, true); TileSetSource *source; if (tile_set->has_source(c.source_id)) { @@ -1762,14 +1767,14 @@ void TileMap::_navigation_draw_quadrant_debug(TileMapQuadrant *p_quadrant) { TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source); if (atlas_source) { const TileData *tile_data; - if (p_quadrant->runtime_tile_data_cache.has(E_cell->get())) { - tile_data = p_quadrant->runtime_tile_data_cache[E_cell->get()]; + if (p_quadrant->runtime_tile_data_cache.has(E_cell)) { + tile_data = p_quadrant->runtime_tile_data_cache[E_cell]; } else { tile_data = atlas_source->get_tile_data(c.get_atlas_coords(), c.alternative_tile); } Transform2D xform; - xform.set_origin(map_to_world(E_cell->get()) - quadrant_pos); + xform.set_origin(map_to_world(E_cell) - quadrant_pos); rs->canvas_item_add_set_transform(p_quadrant->debug_canvas_item, xform); for (int layer_index = 0; layer_index < tile_set->get_navigation_layers_count(); layer_index++) { @@ -1823,8 +1828,8 @@ void TileMap::_scenes_update_dirty_quadrants(SelfList<TileMapQuadrant>::List &r_ q.scenes.clear(); // Recreate the scenes. - for (Set<Vector2i>::Element *E_cell = q.cells.front(); E_cell; E_cell = E_cell->next()) { - const TileMapCell &c = get_cell(q.layer, E_cell->get(), true); + for (const Vector2i &E_cell : q.cells) { + const TileMapCell &c = get_cell(q.layer, E_cell, true); TileSetSource *source; if (tile_set->has_source(c.source_id)) { @@ -1843,13 +1848,13 @@ void TileMap::_scenes_update_dirty_quadrants(SelfList<TileMapQuadrant>::List &r_ Control *scene_as_control = Object::cast_to<Control>(scene); Node2D *scene_as_node2d = Object::cast_to<Node2D>(scene); if (scene_as_control) { - scene_as_control->set_position(map_to_world(E_cell->get()) + scene_as_control->get_position()); + scene_as_control->set_position(map_to_world(E_cell) + scene_as_control->get_position()); } else if (scene_as_node2d) { Transform2D xform; - xform.set_origin(map_to_world(E_cell->get())); + xform.set_origin(map_to_world(E_cell)); scene_as_node2d->set_transform(xform * scene_as_node2d->get_transform()); } - q.scenes[E_cell->get()] = scene->get_name(); + q.scenes[E_cell] = scene->get_name(); } } } @@ -1881,8 +1886,8 @@ void TileMap::_scenes_draw_quadrant_debug(TileMapQuadrant *p_quadrant) { // Draw a placeholder for scenes needing one. RenderingServer *rs = RenderingServer::get_singleton(); Vector2 quadrant_pos = map_to_world(p_quadrant->coords * get_effective_quadrant_size(p_quadrant->layer)); - for (Set<Vector2i>::Element *E_cell = p_quadrant->cells.front(); E_cell; E_cell = E_cell->next()) { - const TileMapCell &c = get_cell(p_quadrant->layer, E_cell->get(), true); + for (const Vector2i &E_cell : p_quadrant->cells) { + const TileMapCell &c = get_cell(p_quadrant->layer, E_cell, true); TileSetSource *source; if (tile_set->has_source(c.source_id)) { @@ -1910,7 +1915,7 @@ void TileMap::_scenes_draw_quadrant_debug(TileMapQuadrant *p_quadrant) { // Draw a placeholder tile. Transform2D xform; - xform.set_origin(map_to_world(E_cell->get()) - quadrant_pos); + xform.set_origin(map_to_world(E_cell) - quadrant_pos); rs->canvas_item_add_set_transform(p_quadrant->debug_canvas_item, xform); rs->canvas_item_add_circle(p_quadrant->debug_canvas_item, Vector2(), MIN(tile_set->get_tile_size().x, tile_set->get_tile_size().y) / 4.0, color); } @@ -1923,9 +1928,9 @@ void TileMap::set_cell(int p_layer, const Vector2i &p_coords, int p_source_id, c ERR_FAIL_INDEX(p_layer, (int)layers.size()); // Set the current cell tile (using integer position). - Map<Vector2i, TileMapCell> &tile_map = layers[p_layer].tile_map; + HashMap<Vector2i, TileMapCell> &tile_map = layers[p_layer].tile_map; Vector2i pk(p_coords); - Map<Vector2i, TileMapCell>::Element *E = tile_map.find(pk); + HashMap<Vector2i, TileMapCell>::Iterator E = tile_map.find(pk); int source_id = p_source_id; Vector2i atlas_coords = p_atlas_coords; @@ -1946,7 +1951,7 @@ void TileMap::set_cell(int p_layer, const Vector2i &p_coords, int p_source_id, c // Get the quadrant Vector2i qk = _coords_to_quadrant_coords(p_layer, pk); - Map<Vector2i, TileMapQuadrant>::Element *Q = layers[p_layer].quadrant_map.find(qk); + HashMap<Vector2i, TileMapQuadrant>::Iterator Q = layers[p_layer].quadrant_map.find(qk); if (source_id == TileSet::INVALID_SOURCE) { // Erase existing cell in the tile map. @@ -1954,7 +1959,7 @@ void TileMap::set_cell(int p_layer, const Vector2i &p_coords, int p_source_id, c // Erase existing cell in the quadrant. ERR_FAIL_COND(!Q); - TileMapQuadrant &q = Q->get(); + TileMapQuadrant &q = Q->value; q.cells.erase(pk); @@ -1975,18 +1980,18 @@ void TileMap::set_cell(int p_layer, const Vector2i &p_coords, int p_source_id, c if (!Q) { Q = _create_quadrant(p_layer, qk); } - TileMapQuadrant &q = Q->get(); + TileMapQuadrant &q = Q->value; q.cells.insert(pk); } else { ERR_FAIL_COND(!Q); // TileMapQuadrant should exist... - if (E->get().source_id == source_id && E->get().get_atlas_coords() == atlas_coords && E->get().alternative_tile == alternative_tile) { + if (E->value.source_id == source_id && E->value.get_atlas_coords() == atlas_coords && E->value.alternative_tile == alternative_tile) { return; // Nothing changed. } } - TileMapCell &c = E->get(); + TileMapCell &c = E->value; c.source_id = source_id; c.set_atlas_coords(atlas_coords); @@ -2005,57 +2010,57 @@ int TileMap::get_cell_source_id(int p_layer, const Vector2i &p_coords, bool p_us ERR_FAIL_INDEX_V(p_layer, (int)layers.size(), TileSet::INVALID_SOURCE); // Get a cell source id from position - const Map<Vector2i, TileMapCell> &tile_map = layers[p_layer].tile_map; - const Map<Vector2i, TileMapCell>::Element *E = tile_map.find(p_coords); + const HashMap<Vector2i, TileMapCell> &tile_map = layers[p_layer].tile_map; + HashMap<Vector2i, TileMapCell>::ConstIterator E = tile_map.find(p_coords); if (!E) { return TileSet::INVALID_SOURCE; } if (p_use_proxies && tile_set.is_valid()) { - Array proxyed = tile_set->map_tile_proxy(E->get().source_id, E->get().get_atlas_coords(), E->get().alternative_tile); + Array proxyed = tile_set->map_tile_proxy(E->value.source_id, E->value.get_atlas_coords(), E->value.alternative_tile); return proxyed[0]; } - return E->get().source_id; + return E->value.source_id; } Vector2i TileMap::get_cell_atlas_coords(int p_layer, const Vector2i &p_coords, bool p_use_proxies) const { ERR_FAIL_INDEX_V(p_layer, (int)layers.size(), TileSetSource::INVALID_ATLAS_COORDS); // Get a cell source id from position - const Map<Vector2i, TileMapCell> &tile_map = layers[p_layer].tile_map; - const Map<Vector2i, TileMapCell>::Element *E = tile_map.find(p_coords); + const HashMap<Vector2i, TileMapCell> &tile_map = layers[p_layer].tile_map; + HashMap<Vector2i, TileMapCell>::ConstIterator E = tile_map.find(p_coords); if (!E) { return TileSetSource::INVALID_ATLAS_COORDS; } if (p_use_proxies && tile_set.is_valid()) { - Array proxyed = tile_set->map_tile_proxy(E->get().source_id, E->get().get_atlas_coords(), E->get().alternative_tile); + Array proxyed = tile_set->map_tile_proxy(E->value.source_id, E->value.get_atlas_coords(), E->value.alternative_tile); return proxyed[1]; } - return E->get().get_atlas_coords(); + return E->value.get_atlas_coords(); } int TileMap::get_cell_alternative_tile(int p_layer, const Vector2i &p_coords, bool p_use_proxies) const { ERR_FAIL_INDEX_V(p_layer, (int)layers.size(), TileSetSource::INVALID_TILE_ALTERNATIVE); // Get a cell source id from position - const Map<Vector2i, TileMapCell> &tile_map = layers[p_layer].tile_map; - const Map<Vector2i, TileMapCell>::Element *E = tile_map.find(p_coords); + const HashMap<Vector2i, TileMapCell> &tile_map = layers[p_layer].tile_map; + HashMap<Vector2i, TileMapCell>::ConstIterator E = tile_map.find(p_coords); if (!E) { return TileSetSource::INVALID_TILE_ALTERNATIVE; } if (p_use_proxies && tile_set.is_valid()) { - Array proxyed = tile_set->map_tile_proxy(E->get().source_id, E->get().get_atlas_coords(), E->get().alternative_tile); + Array proxyed = tile_set->map_tile_proxy(E->value.source_id, E->value.get_atlas_coords(), E->value.alternative_tile); return proxyed[2]; } - return E->get().alternative_tile; + return E->value.alternative_tile; } Ref<TileMapPattern> TileMap::get_pattern(int p_layer, TypedArray<Vector2i> p_coords_array) { @@ -2143,90 +2148,124 @@ void TileMap::set_pattern(int p_layer, Vector2i p_position, const Ref<TileMapPat TypedArray<Vector2i> used_cells = p_pattern->get_used_cells(); for (int i = 0; i < used_cells.size(); i++) { Vector2i coords = map_pattern(p_position, used_cells[i], p_pattern); - set_cell(p_layer, coords, p_pattern->get_cell_source_id(coords), p_pattern->get_cell_atlas_coords(coords), p_pattern->get_cell_alternative_tile(coords)); + set_cell(p_layer, coords, p_pattern->get_cell_source_id(used_cells[i]), p_pattern->get_cell_atlas_coords(used_cells[i]), p_pattern->get_cell_alternative_tile(used_cells[i])); } } -Set<TileSet::TerrainsPattern> TileMap::_get_valid_terrains_patterns_for_constraints(int p_terrain_set, const Vector2i &p_position, Set<TerrainConstraint> p_constraints) { +TileSet::TerrainsPattern TileMap::_get_best_terrain_pattern_for_constraints(int p_terrain_set, const Vector2i &p_position, RBSet<TerrainConstraint> p_constraints) { if (!tile_set.is_valid()) { - return Set<TileSet::TerrainsPattern>(); + return TileSet::TerrainsPattern(); } // Returns all tiles compatible with the given constraints. - Set<TileSet::TerrainsPattern> compatible_terrain_tile_patterns; - for (TileSet::TerrainsPattern &terrain_pattern : tile_set->get_terrains_pattern_set(p_terrain_set)) { - int valid = true; + RBMap<TileSet::TerrainsPattern, int> terrain_pattern_score; + RBSet<TileSet::TerrainsPattern> pattern_set = tile_set->get_terrains_pattern_set(p_terrain_set); + ERR_FAIL_COND_V(pattern_set.is_empty(), TileSet::TerrainsPattern()); + for (TileSet::TerrainsPattern &terrain_pattern : pattern_set) { + int score = 0; + + // Check the center bit constraint + TerrainConstraint terrain_constraint = TerrainConstraint(this, p_position, terrain_pattern.get_terrain()); + RBSet<TerrainConstraint>::Element *in_set_constraint_element = p_constraints.find(terrain_constraint); + if (in_set_constraint_element && in_set_constraint_element->get().get_terrain() != terrain_constraint.get_terrain()) { + score += in_set_constraint_element->get().get_priority(); + } + + // Check the surrounding bits for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { TileSet::CellNeighbor bit = TileSet::CellNeighbor(i); - if (tile_set->is_valid_peering_bit_terrain(p_terrain_set, bit)) { + if (tile_set->is_valid_terrain_peering_bit(p_terrain_set, bit)) { // Check if the bit is compatible with the constraints. - TerrainConstraint terrain_bit_constraint = TerrainConstraint(this, p_position, bit, terrain_pattern.get_terrain(bit)); - Set<TerrainConstraint>::Element *in_set_constraint_element = p_constraints.find(terrain_bit_constraint); + TerrainConstraint terrain_bit_constraint = TerrainConstraint(this, p_position, bit, terrain_pattern.get_terrain_peering_bit(bit)); + in_set_constraint_element = p_constraints.find(terrain_bit_constraint); if (in_set_constraint_element && in_set_constraint_element->get().get_terrain() != terrain_bit_constraint.get_terrain()) { - valid = false; - break; + score += in_set_constraint_element->get().get_priority(); } } } - if (valid) { - compatible_terrain_tile_patterns.insert(terrain_pattern); + terrain_pattern_score[terrain_pattern] = score; + } + + // Compute the minimum score + TileSet::TerrainsPattern min_score_pattern; + int min_score = INT32_MAX; + for (KeyValue<TileSet::TerrainsPattern, int> E : terrain_pattern_score) { + if (E.value < min_score) { + min_score_pattern = E.key; + min_score = E.value; + } + } + + return min_score_pattern; +} + +RBSet<TileMap::TerrainConstraint> TileMap::_get_terrain_constraints_from_added_pattern(Vector2i p_position, int p_terrain_set, TileSet::TerrainsPattern p_terrains_pattern) const { + if (!tile_set.is_valid()) { + return RBSet<TerrainConstraint>(); + } + + // Compute the constraints needed from the surrounding tiles. + RBSet<TerrainConstraint> output; + output.insert(TerrainConstraint(this, p_position, p_terrains_pattern.get_terrain())); + + for (uint32_t i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { + TileSet::CellNeighbor side = TileSet::CellNeighbor(i); + if (tile_set->is_valid_terrain_peering_bit(p_terrain_set, side)) { + TerrainConstraint c = TerrainConstraint(this, p_position, side, p_terrains_pattern.get_terrain_peering_bit(side)); + output.insert(c); } } - return compatible_terrain_tile_patterns; + return output; } -Set<TileMap::TerrainConstraint> TileMap::get_terrain_constraints_from_removed_cells_list(int p_layer, const Set<Vector2i> &p_to_replace, int p_terrain_set, bool p_ignore_empty_terrains) const { +RBSet<TileMap::TerrainConstraint> TileMap::_get_terrain_constraints_from_cells_list(int p_layer, const RBSet<Vector2i> &p_cell_list, int p_terrain_set, bool p_ignore_empty_terrains) const { if (!tile_set.is_valid()) { - return Set<TerrainConstraint>(); + return RBSet<TerrainConstraint>(); } - ERR_FAIL_INDEX_V(p_terrain_set, tile_set->get_terrain_sets_count(), Set<TerrainConstraint>()); - ERR_FAIL_INDEX_V(p_layer, (int)layers.size(), Set<TerrainConstraint>()); + ERR_FAIL_INDEX_V(p_terrain_set, tile_set->get_terrain_sets_count(), RBSet<TerrainConstraint>()); + ERR_FAIL_INDEX_V(p_layer, (int)layers.size(), RBSet<TerrainConstraint>()); - // Build a set of dummy constraints get the constrained points. - Set<TerrainConstraint> dummy_constraints; - for (Set<Vector2i>::Element *E = p_to_replace.front(); E; E = E->next()) { + // Build a set of dummy constraints to get the constrained points. + RBSet<TerrainConstraint> dummy_constraints; + for (const Vector2i &E : p_cell_list) { for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { // Iterates over sides. TileSet::CellNeighbor bit = TileSet::CellNeighbor(i); - if (tile_set->is_valid_peering_bit_terrain(p_terrain_set, bit)) { - dummy_constraints.insert(TerrainConstraint(this, E->get(), bit, -1)); + if (tile_set->is_valid_terrain_peering_bit(p_terrain_set, bit)) { + dummy_constraints.insert(TerrainConstraint(this, E, bit, -1)); } } } // For each constrained point, we get all overlapping tiles, and select the most adequate terrain for it. - Set<TerrainConstraint> constraints; - for (Set<TerrainConstraint>::Element *E = dummy_constraints.front(); E; E = E->next()) { - TerrainConstraint c = E->get(); - - Map<int, int> terrain_count; + RBSet<TerrainConstraint> constraints; + for (const TerrainConstraint &E_constraint : dummy_constraints) { + HashMap<int, int> terrain_count; // Count the number of occurrences per terrain. - Map<Vector2i, TileSet::CellNeighbor> overlapping_terrain_bits = c.get_overlapping_coords_and_peering_bits(); + HashMap<Vector2i, TileSet::CellNeighbor> overlapping_terrain_bits = E_constraint.get_overlapping_coords_and_peering_bits(); for (const KeyValue<Vector2i, TileSet::CellNeighbor> &E_overlapping : overlapping_terrain_bits) { - if (!p_to_replace.has(E_overlapping.key)) { - TileData *neighbor_tile_data = nullptr; - TileMapCell neighbor_cell = get_cell(p_layer, E_overlapping.key); - if (neighbor_cell.source_id != TileSet::INVALID_SOURCE) { - Ref<TileSetSource> source = tile_set->get_source(neighbor_cell.source_id); - Ref<TileSetAtlasSource> atlas_source = source; - if (atlas_source.is_valid()) { - TileData *tile_data = atlas_source->get_tile_data(neighbor_cell.get_atlas_coords(), neighbor_cell.alternative_tile); - if (tile_data && tile_data->get_terrain_set() == p_terrain_set) { - neighbor_tile_data = tile_data; - } + TileData *neighbor_tile_data = nullptr; + TileMapCell neighbor_cell = get_cell(p_layer, E_overlapping.key); + if (neighbor_cell.source_id != TileSet::INVALID_SOURCE) { + Ref<TileSetSource> source = tile_set->get_source(neighbor_cell.source_id); + Ref<TileSetAtlasSource> atlas_source = source; + if (atlas_source.is_valid()) { + TileData *tile_data = atlas_source->get_tile_data(neighbor_cell.get_atlas_coords(), neighbor_cell.alternative_tile); + if (tile_data && tile_data->get_terrain_set() == p_terrain_set) { + neighbor_tile_data = tile_data; } } + } - int terrain = neighbor_tile_data ? neighbor_tile_data->get_peering_bit_terrain(TileSet::CellNeighbor(E_overlapping.value)) : -1; - if (!p_ignore_empty_terrains || terrain >= 0) { - if (!terrain_count.has(terrain)) { - terrain_count[terrain] = 0; - } - terrain_count[terrain] += 1; + int terrain = neighbor_tile_data ? neighbor_tile_data->get_terrain_peering_bit(TileSet::CellNeighbor(E_overlapping.value)) : -1; + if (!p_ignore_empty_terrains || terrain >= 0) { + if (!terrain_count.has(terrain)) { + terrain_count[terrain] = 0; } + terrain_count[terrain] += 1; } } @@ -2242,154 +2281,332 @@ Set<TileMap::TerrainConstraint> TileMap::get_terrain_constraints_from_removed_ce // Set the adequate terrain. if (max > 0) { + TerrainConstraint c = E_constraint; c.set_terrain(max_terrain); constraints.insert(c); } } + // Add the centers as constraints + for (Vector2i E_coords : p_cell_list) { + TileData *tile_data = nullptr; + TileMapCell cell = get_cell(p_layer, E_coords); + if (cell.source_id != TileSet::INVALID_SOURCE) { + Ref<TileSetSource> source = tile_set->get_source(cell.source_id); + Ref<TileSetAtlasSource> atlas_source = source; + if (atlas_source.is_valid()) { + tile_data = atlas_source->get_tile_data(cell.get_atlas_coords(), cell.alternative_tile); + } + } + + int terrain = (tile_data && tile_data->get_terrain_set() == p_terrain_set) ? tile_data->get_terrain() : -1; + if (!p_ignore_empty_terrains || terrain >= 0) { + constraints.insert(TerrainConstraint(this, E_coords, terrain)); + } + } + return constraints; } -Set<TileMap::TerrainConstraint> TileMap::get_terrain_constraints_from_added_tile(Vector2i p_position, int p_terrain_set, TileSet::TerrainsPattern p_terrains_pattern) const { +HashMap<Vector2i, TileSet::TerrainsPattern> TileMap::terrain_fill_constraints(const Vector<Vector2i> &p_to_replace, int p_terrain_set, const RBSet<TerrainConstraint> p_constraints) { if (!tile_set.is_valid()) { - return Set<TerrainConstraint>(); + return HashMap<Vector2i, TileSet::TerrainsPattern>(); } - // Compute the constraints needed from the surrounding tiles. - Set<TerrainConstraint> output; - for (uint32_t i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { - TileSet::CellNeighbor side = TileSet::CellNeighbor(i); - if (tile_set->is_valid_peering_bit_terrain(p_terrain_set, side)) { - TerrainConstraint c = TerrainConstraint(this, p_position, side, p_terrains_pattern.get_terrain(side)); - output.insert(c); + // Copy the constraints set. + RBSet<TerrainConstraint> constraints = p_constraints; + + // Output map. + HashMap<Vector2i, TileSet::TerrainsPattern> output; + + // Add all positions to a set. + for (int i = 0; i < p_to_replace.size(); i++) { + const Vector2i &coords = p_to_replace[i]; + + // Select the best pattern for the given constraints + TileSet::TerrainsPattern pattern = _get_best_terrain_pattern_for_constraints(p_terrain_set, coords, constraints); + + // Update the constraint set with the new ones + RBSet<TerrainConstraint> new_constraints = _get_terrain_constraints_from_added_pattern(coords, p_terrain_set, pattern); + for (const TerrainConstraint &E_constraint : new_constraints) { + if (constraints.has(E_constraint)) { + constraints.erase(E_constraint); + } + TerrainConstraint c = E_constraint; + c.set_priority(5); + constraints.insert(c); } - } + output[coords] = pattern; + } return output; } -Map<Vector2i, TileSet::TerrainsPattern> TileMap::terrain_wave_function_collapse(const Set<Vector2i> &p_to_replace, int p_terrain_set, const Set<TerrainConstraint> p_constraints) { - if (!tile_set.is_valid()) { - return Map<Vector2i, TileSet::TerrainsPattern>(); +HashMap<Vector2i, TileSet::TerrainsPattern> TileMap::terrain_fill_connect(int p_layer, const Vector<Vector2i> &p_coords_array, int p_terrain_set, int p_terrain, bool p_ignore_empty_terrains) { + HashMap<Vector2i, TileSet::TerrainsPattern> output; + ERR_FAIL_COND_V(!tile_set.is_valid(), output); + ERR_FAIL_INDEX_V(p_terrain_set, tile_set->get_terrain_sets_count(), output); + + // Build list and set of tiles that can be modified (painted and their surroundings) + Vector<Vector2i> can_modify_list; + RBSet<Vector2i> can_modify_set; + RBSet<Vector2i> painted_set; + for (int i = p_coords_array.size() - 1; i >= 0; i--) { + const Vector2i &coords = p_coords_array[i]; + can_modify_list.push_back(coords); + can_modify_set.insert(coords); + painted_set.insert(coords); + } + for (Vector2i coords : p_coords_array) { + // Find the adequate neighbor + for (int j = 0; j < TileSet::CELL_NEIGHBOR_MAX; j++) { + TileSet::CellNeighbor bit = TileSet::CellNeighbor(j); + if (tile_set->is_valid_terrain_peering_bit(p_terrain_set, bit)) { + Vector2i neighbor = get_neighbor_cell(coords, bit); + if (!can_modify_set.has(neighbor)) { + can_modify_list.push_back(neighbor); + can_modify_set.insert(neighbor); + } + } + } } - // Copy the constraints set. - Set<TerrainConstraint> constraints = p_constraints; + // Build a set, out of the possibly modified tiles, of the one with a center bit that is set (or will be) to the painted terrain + RBSet<Vector2i> cells_with_terrain_center_bit; + for (Vector2i coords : can_modify_set) { + bool connect = false; + if (painted_set.has(coords)) { + connect = true; + } else { + // Get the center bit of the cell + TileData *tile_data = nullptr; + TileMapCell cell = get_cell(p_layer, coords); + if (cell.source_id != TileSet::INVALID_SOURCE) { + Ref<TileSetSource> source = tile_set->get_source(cell.source_id); + Ref<TileSetAtlasSource> atlas_source = source; + if (atlas_source.is_valid()) { + tile_data = atlas_source->get_tile_data(cell.get_atlas_coords(), cell.alternative_tile); + } + } - // Compute all acceptable patterns for each cell. - Map<Vector2i, Set<TileSet::TerrainsPattern>> per_cell_acceptable_tiles; - for (Vector2i cell : p_to_replace) { - per_cell_acceptable_tiles[cell] = _get_valid_terrains_patterns_for_constraints(p_terrain_set, cell, constraints); + if (tile_data && tile_data->get_terrain_set() == p_terrain_set && tile_data->get_terrain() == p_terrain) { + connect = true; + } + } + if (connect) { + cells_with_terrain_center_bit.insert(coords); + } } - // Output map. - Map<Vector2i, TileSet::TerrainsPattern> output; + RBSet<TerrainConstraint> constraints; - // Add all positions to a set. - Set<Vector2i> to_replace = Set<Vector2i>(p_to_replace); - while (!to_replace.is_empty()) { - // Compute the minimum number of tile possibilities for each cell. - int min_nb_possibilities = 100000000; - for (const KeyValue<Vector2i, Set<TileSet::TerrainsPattern>> &E : per_cell_acceptable_tiles) { - min_nb_possibilities = MIN(min_nb_possibilities, E.value.size()); - } - - // Get the set of possible cells to fill, out of the most constrained ones. - LocalVector<Vector2i> to_choose_from; - for (const KeyValue<Vector2i, Set<TileSet::TerrainsPattern>> &E : per_cell_acceptable_tiles) { - if (E.value.size() == min_nb_possibilities) { - to_choose_from.push_back(E.key); - } - } - - // Randomly a cell to fill out of the most constrained. - Vector2i selected_cell_to_replace = to_choose_from[Math::random(0, to_choose_from.size() - 1)]; - - // Get the list of acceptable patterns for the given cell. - Set<TileSet::TerrainsPattern> valid_tiles = per_cell_acceptable_tiles[selected_cell_to_replace]; - if (valid_tiles.is_empty()) { - break; // No possibilities :/ - } - - // Out of the possible patterns, prioritize the one which have the least amount of different terrains. - LocalVector<TileSet::TerrainsPattern> valid_tiles_with_least_amount_of_terrains; - int min_terrain_count = 10000; - LocalVector<int> terrains_counts; - int pattern_index = 0; - for (const TileSet::TerrainsPattern &pattern : valid_tiles) { - Set<int> terrains; - for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { - TileSet::CellNeighbor side = TileSet::CellNeighbor(i); - if (tile_set->is_valid_peering_bit_terrain(p_terrain_set, side)) { - terrains.insert(pattern.get_terrain(side)); + // Add new constraints from the path drawn. + for (Vector2i coords : p_coords_array) { + // Constraints on the center bit. + TerrainConstraint c = TerrainConstraint(this, coords, p_terrain); + c.set_priority(10); + constraints.insert(c); + + // Constraints on the connecting bits. + for (int j = 0; j < TileSet::CELL_NEIGHBOR_MAX; j++) { + TileSet::CellNeighbor bit = TileSet::CellNeighbor(j); + if (tile_set->is_valid_terrain_peering_bit(p_terrain_set, bit)) { + c = TerrainConstraint(this, coords, bit, p_terrain); + c.set_priority(10); + if ((int(bit) % 2) == 0) { + // Side peering bits: add the constraint if the center is of the same terrain + Vector2i neighbor = get_neighbor_cell(coords, bit); + if (cells_with_terrain_center_bit.has(neighbor)) { + constraints.insert(c); + } + } else { + // Corner peering bits: add the constraint if all tiles on the constraint has the same center bit + HashMap<Vector2i, TileSet::CellNeighbor> overlapping_terrain_bits = c.get_overlapping_coords_and_peering_bits(); + bool valid = true; + for (KeyValue<Vector2i, TileSet::CellNeighbor> kv : overlapping_terrain_bits) { + if (!cells_with_terrain_center_bit.has(kv.key)) { + valid = false; + break; + } + } + if (valid) { + constraints.insert(c); + } } } - min_terrain_count = MIN(min_terrain_count, terrains.size()); - terrains_counts.push_back(terrains.size()); - pattern_index++; } - pattern_index = 0; - for (const TileSet::TerrainsPattern &pattern : valid_tiles) { - if (terrains_counts[pattern_index] == min_terrain_count) { - valid_tiles_with_least_amount_of_terrains.push_back(pattern); + } + + // Fills in the constraint list from existing tiles. + for (TerrainConstraint c : _get_terrain_constraints_from_cells_list(p_layer, can_modify_set, p_terrain_set, p_ignore_empty_terrains)) { + constraints.insert(c); + } + + // Fill the terrains. + output = terrain_fill_constraints(can_modify_list, p_terrain_set, constraints); + return output; +} + +HashMap<Vector2i, TileSet::TerrainsPattern> TileMap::terrain_fill_path(int p_layer, const Vector<Vector2i> &p_path, int p_terrain_set, int p_terrain, bool p_ignore_empty_terrains) { + HashMap<Vector2i, TileSet::TerrainsPattern> output; + ERR_FAIL_COND_V(!tile_set.is_valid(), output); + ERR_FAIL_INDEX_V(p_terrain_set, tile_set->get_terrain_sets_count(), output); + + // Make sure the path is correct and build the peering bit list while doing it. + Vector<TileSet::CellNeighbor> neighbor_list; + for (int i = 0; i < p_path.size() - 1; i++) { + // Find the adequate neighbor + TileSet::CellNeighbor found_bit = TileSet::CELL_NEIGHBOR_MAX; + for (int j = 0; j < TileSet::CELL_NEIGHBOR_MAX; j++) { + TileSet::CellNeighbor bit = TileSet::CellNeighbor(j); + if (tile_set->is_valid_terrain_peering_bit(p_terrain_set, bit)) { + if (get_neighbor_cell(p_path[i], bit) == p_path[i + 1]) { + found_bit = bit; + break; + } + } + } + ERR_FAIL_COND_V_MSG(found_bit == TileSet::CELL_NEIGHBOR_MAX, output, vformat("Invalid terrain path, %s is not a neighbouring tile of %s", p_path[i + 1], p_path[i])); + neighbor_list.push_back(found_bit); + } + + // Build list and set of tiles that can be modified (painted and their surroundings) + Vector<Vector2i> can_modify_list; + RBSet<Vector2i> can_modify_set; + for (int i = p_path.size() - 1; i >= 0; i--) { + const Vector2i &coords = p_path[i]; + can_modify_list.push_back(coords); + can_modify_set.insert(coords); + } + for (Vector2i coords : p_path) { + // Find the adequate neighbor + for (int j = 0; j < TileSet::CELL_NEIGHBOR_MAX; j++) { + TileSet::CellNeighbor bit = TileSet::CellNeighbor(j); + if (tile_set->is_valid_terrain_peering_bit(p_terrain_set, bit)) { + Vector2i neighbor = get_neighbor_cell(coords, bit); + if (!can_modify_set.has(neighbor)) { + can_modify_list.push_back(neighbor); + can_modify_set.insert(neighbor); + } } - pattern_index++; } + } - // Randomly select a pattern out of the remaining ones. - TileSet::TerrainsPattern selected_terrain_tile_pattern = valid_tiles_with_least_amount_of_terrains[Math::random(0, valid_tiles_with_least_amount_of_terrains.size() - 1)]; + RBSet<TerrainConstraint> constraints; - // Set the selected cell into the output. - output[selected_cell_to_replace] = selected_terrain_tile_pattern; - to_replace.erase(selected_cell_to_replace); - per_cell_acceptable_tiles.erase(selected_cell_to_replace); + // Add new constraints from the path drawn. + for (Vector2i coords : p_path) { + // Constraints on the center bit + TerrainConstraint c = TerrainConstraint(this, coords, p_terrain); + c.set_priority(10); + constraints.insert(c); + } + for (int i = 0; i < p_path.size() - 1; i++) { + // Constraints on the peering bits. + TerrainConstraint c = TerrainConstraint(this, p_path[i], neighbor_list[i], p_terrain); + c.set_priority(10); + constraints.insert(c); + } - // Add the new constraints from the added tiles. - Set<TerrainConstraint> new_constraints = get_terrain_constraints_from_added_tile(selected_cell_to_replace, p_terrain_set, selected_terrain_tile_pattern); - for (Set<TerrainConstraint>::Element *E_constraint = new_constraints.front(); E_constraint; E_constraint = E_constraint->next()) { - constraints.insert(E_constraint->get()); - } + // Fills in the constraint list from existing tiles. + for (TerrainConstraint c : _get_terrain_constraints_from_cells_list(p_layer, can_modify_set, p_terrain_set, p_ignore_empty_terrains)) { + constraints.insert(c); + } - // Compute valid tiles again for neighbors. - for (uint32_t i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { - TileSet::CellNeighbor side = TileSet::CellNeighbor(i); - if (is_existing_neighbor(side)) { - Vector2i neighbor = get_neighbor_cell(selected_cell_to_replace, side); - if (to_replace.has(neighbor)) { - per_cell_acceptable_tiles[neighbor] = _get_valid_terrains_patterns_for_constraints(p_terrain_set, neighbor, constraints); + // Fill the terrains. + output = terrain_fill_constraints(can_modify_list, p_terrain_set, constraints); + return output; +} + +HashMap<Vector2i, TileSet::TerrainsPattern> TileMap::terrain_fill_pattern(int p_layer, const Vector<Vector2i> &p_coords_array, int p_terrain_set, TileSet::TerrainsPattern p_terrains_pattern, bool p_ignore_empty_terrains) { + HashMap<Vector2i, TileSet::TerrainsPattern> output; + ERR_FAIL_COND_V(!tile_set.is_valid(), output); + ERR_FAIL_INDEX_V(p_terrain_set, tile_set->get_terrain_sets_count(), output); + + // Build list and set of tiles that can be modified (painted and their surroundings). + Vector<Vector2i> can_modify_list; + RBSet<Vector2i> can_modify_set; + for (int i = p_coords_array.size() - 1; i >= 0; i--) { + const Vector2i &coords = p_coords_array[i]; + can_modify_list.push_back(coords); + can_modify_set.insert(coords); + } + for (Vector2i coords : p_coords_array) { + // Find the adequate neighbor + for (int j = 0; j < TileSet::CELL_NEIGHBOR_MAX; j++) { + TileSet::CellNeighbor bit = TileSet::CellNeighbor(j); + if (tile_set->is_valid_terrain_peering_bit(p_terrain_set, bit)) { + Vector2i neighbor = get_neighbor_cell(coords, bit); + if (!can_modify_set.has(neighbor)) { + can_modify_list.push_back(neighbor); + can_modify_set.insert(neighbor); } } } } + + // Add constraint by the new ones. + RBSet<TerrainConstraint> constraints; + + // Add new constraints from the path drawn. + for (Vector2i coords : p_coords_array) { + // Constraints on the center bit + RBSet<TerrainConstraint> added_constraints = _get_terrain_constraints_from_added_pattern(coords, p_terrain_set, p_terrains_pattern); + for (TerrainConstraint c : added_constraints) { + c.set_priority(10); + constraints.insert(c); + } + } + + // Fills in the constraint list from modified tiles border. + for (TerrainConstraint c : _get_terrain_constraints_from_cells_list(p_layer, can_modify_set, p_terrain_set, p_ignore_empty_terrains)) { + constraints.insert(c); + } + + // Fill the terrains. + output = terrain_fill_constraints(can_modify_list, p_terrain_set, constraints); return output; } -void TileMap::set_cells_from_surrounding_terrains(int p_layer, TypedArray<Vector2i> p_coords_array, int p_terrain_set, bool p_ignore_empty_terrains) { +void TileMap::set_cells_terrain_connect(int p_layer, TypedArray<Vector2i> p_cells, int p_terrain_set, int p_terrain, bool p_ignore_empty_terrains) { ERR_FAIL_COND(!tile_set.is_valid()); ERR_FAIL_INDEX(p_layer, (int)layers.size()); ERR_FAIL_INDEX(p_terrain_set, tile_set->get_terrain_sets_count()); - Set<Vector2i> coords_set; - for (int i = 0; i < p_coords_array.size(); i++) { - coords_set.insert(p_coords_array[i]); + Vector<Vector2i> vector_cells; + for (int i = 0; i < p_cells.size(); i++) { + vector_cells.push_back(p_cells[i]); } + HashMap<Vector2i, TileSet::TerrainsPattern> terrain_fill_output = terrain_fill_connect(p_layer, vector_cells, p_terrain_set, p_terrain, p_ignore_empty_terrains); + for (const KeyValue<Vector2i, TileSet::TerrainsPattern> &E : terrain_fill_output) { + TileMapCell c = tile_set->get_random_tile_from_terrains_pattern(p_terrain_set, E.value); + set_cell(p_layer, E.key, c.source_id, c.get_atlas_coords(), c.alternative_tile); + } +} - Set<TileMap::TerrainConstraint> constraints = get_terrain_constraints_from_removed_cells_list(p_layer, coords_set, p_terrain_set, p_ignore_empty_terrains); +void TileMap::set_cells_terrain_path(int p_layer, TypedArray<Vector2i> p_path, int p_terrain_set, int p_terrain, bool p_ignore_empty_terrains) { + ERR_FAIL_COND(!tile_set.is_valid()); + ERR_FAIL_INDEX(p_layer, (int)layers.size()); + ERR_FAIL_INDEX(p_terrain_set, tile_set->get_terrain_sets_count()); - Map<Vector2i, TileSet::TerrainsPattern> wfc_output = terrain_wave_function_collapse(coords_set, p_terrain_set, constraints); - for (const KeyValue<Vector2i, TileSet::TerrainsPattern> &kv : wfc_output) { - TileMapCell cell = tile_set->get_random_tile_from_terrains_pattern(p_terrain_set, kv.value); - set_cell(p_layer, kv.key, cell.source_id, cell.get_atlas_coords(), cell.alternative_tile); + Vector<Vector2i> vector_path; + for (int i = 0; i < p_path.size(); i++) { + vector_path.push_back(p_path[i]); + } + HashMap<Vector2i, TileSet::TerrainsPattern> terrain_fill_output = terrain_fill_path(p_layer, vector_path, p_terrain_set, p_terrain, p_ignore_empty_terrains); + for (const KeyValue<Vector2i, TileSet::TerrainsPattern> &E : terrain_fill_output) { + TileMapCell c = tile_set->get_random_tile_from_terrains_pattern(p_terrain_set, E.value); + set_cell(p_layer, E.key, c.source_id, c.get_atlas_coords(), c.alternative_tile); } } TileMapCell TileMap::get_cell(int p_layer, const Vector2i &p_coords, bool p_use_proxies) const { ERR_FAIL_INDEX_V(p_layer, (int)layers.size(), TileMapCell()); - const Map<Vector2i, TileMapCell> &tile_map = layers[p_layer].tile_map; + const HashMap<Vector2i, TileMapCell> &tile_map = layers[p_layer].tile_map; if (!tile_map.has(p_coords)) { return TileMapCell(); } else { - TileMapCell c = tile_map.find(p_coords)->get(); + TileMapCell c = tile_map.find(p_coords)->value; if (p_use_proxies && tile_set.is_valid()) { Array proxyed = tile_set->map_tile_proxy(c.source_id, c.get_atlas_coords(), c.alternative_tile); c.source_id = proxyed[0]; @@ -2400,7 +2617,7 @@ TileMapCell TileMap::get_cell(int p_layer, const Vector2i &p_coords, bool p_use_ } } -Map<Vector2i, TileMapQuadrant> *TileMap::get_quadrant_map(int p_layer) { +HashMap<Vector2i, TileMapQuadrant> *TileMap::get_quadrant_map(int p_layer) { ERR_FAIL_INDEX_V(p_layer, (int)layers.size(), nullptr); return &layers[p_layer].quadrant_map; @@ -2415,16 +2632,16 @@ void TileMap::fix_invalid_tiles() { ERR_FAIL_COND_MSG(tile_set.is_null(), "Cannot fix invalid tiles if Tileset is not open."); for (unsigned int i = 0; i < layers.size(); i++) { - const Map<Vector2i, TileMapCell> &tile_map = layers[i].tile_map; - Set<Vector2i> coords; + const HashMap<Vector2i, TileMapCell> &tile_map = layers[i].tile_map; + RBSet<Vector2i> coords; for (const KeyValue<Vector2i, TileMapCell> &E : tile_map) { TileSetSource *source = *tile_set->get_source(E.value.source_id); if (!source || !source->has_tile(E.value.get_atlas_coords()) || !source->has_alternative_tile(E.value.get_atlas_coords(), E.value.alternative_tile)) { coords.insert(E.key); } } - for (Set<Vector2i>::Element *E = coords.front(); E; E = E->next()) { - set_cell(i, E->get(), TileSet::INVALID_SOURCE, TileSetSource::INVALID_ATLAS_COORDS, TileSetSource::INVALID_TILE_ALTERNATIVE); + for (const Vector2i &E : coords) { + set_cell(i, E, TileSet::INVALID_SOURCE, TileSetSource::INVALID_ATLAS_COORDS, TileSetSource::INVALID_TILE_ALTERNATIVE); } } } @@ -2512,10 +2729,10 @@ void TileMap::_set_tile_data(int p_layer, const Vector<int> &p_data) { uint32_t v = decode_uint32(&local[4]); // Extract the transform flags that used to be in the tilemap. - bool flip_h = v & (1 << 29); - bool flip_v = v & (1 << 30); - bool transpose = v & (1 << 31); - v &= (1 << 29) - 1; + bool flip_h = v & (1UL << 29); + bool flip_v = v & (1UL << 30); + bool transpose = v & (1UL << 31); + v &= (1UL << 29) - 1; // Extract autotile/atlas coords. int16_t coord_x = 0; @@ -2546,7 +2763,7 @@ Vector<int> TileMap::_get_tile_data(int p_layer) const { ERR_FAIL_INDEX_V(p_layer, (int)layers.size(), Vector<int>()); // Export tile data to raw format - const Map<Vector2i, TileMapCell> &tile_map = layers[p_layer].tile_map; + const HashMap<Vector2i, TileMapCell> &tile_map = layers[p_layer].tile_map; Vector<int> data; data.resize(tile_map.size() * 3); int *w = data.ptrw(); @@ -2727,7 +2944,7 @@ void TileMap::_get_property_list(List<PropertyInfo> *p_list) const { p_list->push_back(PropertyInfo(Variant::BOOL, vformat("layer_%d/enabled", i), PROPERTY_HINT_NONE)); p_list->push_back(PropertyInfo(Variant::COLOR, vformat("layer_%d/modulate", i), PROPERTY_HINT_NONE)); p_list->push_back(PropertyInfo(Variant::BOOL, vformat("layer_%d/y_sort_enabled", i), PROPERTY_HINT_NONE)); - p_list->push_back(PropertyInfo(Variant::INT, vformat("layer_%d/y_sort_origin", i), PROPERTY_HINT_NONE)); + p_list->push_back(PropertyInfo(Variant::INT, vformat("layer_%d/y_sort_origin", i), PROPERTY_HINT_NONE, "suffix:px")); p_list->push_back(PropertyInfo(Variant::INT, vformat("layer_%d/z_index", i), PROPERTY_HINT_NONE)); p_list->push_back(PropertyInfo(Variant::OBJECT, vformat("layer_%d/tile_data", i), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR)); } @@ -3375,10 +3592,10 @@ Rect2 TileMap::get_used_rect() { // Not const because of cache used_rect_cache = Rect2i(); for (unsigned int i = 0; i < layers.size(); i++) { - const Map<Vector2i, TileMapCell> &tile_map = layers[i].tile_map; + const HashMap<Vector2i, TileMapCell> &tile_map = layers[i].tile_map; if (tile_map.size() > 0) { if (first) { - used_rect_cache = Rect2i(tile_map.front()->key().x, tile_map.front()->key().y, 0, 0); + used_rect_cache = Rect2i(tile_map.begin()->key.x, tile_map.begin()->key.y, 0, 0); first = false; } @@ -3448,8 +3665,8 @@ void TileMap::set_texture_filter(TextureFilter p_texture_filter) { // Set a default texture filter for the whole tilemap CanvasItem::set_texture_filter(p_texture_filter); for (unsigned int layer = 0; layer < layers.size(); layer++) { - for (Map<Vector2i, TileMapQuadrant>::Element *F = layers[layer].quadrant_map.front(); F; F = F->next()) { - TileMapQuadrant &q = F->get(); + for (HashMap<Vector2i, TileMapQuadrant>::Iterator F = layers[layer].quadrant_map.begin(); F; ++F) { + TileMapQuadrant &q = F->value; for (const RID &ci : q.canvas_items) { RenderingServer::get_singleton()->canvas_item_set_default_texture_filter(ci, RS::CanvasItemTextureFilter(p_texture_filter)); _make_quadrant_dirty(F); @@ -3463,8 +3680,8 @@ void TileMap::set_texture_repeat(CanvasItem::TextureRepeat p_texture_repeat) { // Set a default texture repeat for the whole tilemap CanvasItem::set_texture_repeat(p_texture_repeat); for (unsigned int layer = 0; layer < layers.size(); layer++) { - for (Map<Vector2i, TileMapQuadrant>::Element *F = layers[layer].quadrant_map.front(); F; F = F->next()) { - TileMapQuadrant &q = F->get(); + for (HashMap<Vector2i, TileMapQuadrant>::Iterator F = layers[layer].quadrant_map.begin(); F; ++F) { + TileMapQuadrant &q = F->value; for (const RID &ci : q.canvas_items) { RenderingServer::get_singleton()->canvas_item_set_default_texture_repeat(ci, RS::CanvasItemTextureRepeat(p_texture_repeat)); _make_quadrant_dirty(F); @@ -3512,7 +3729,7 @@ TypedArray<Vector2i> TileMap::get_surrounding_tiles(Vector2i coords) { return around; } -void TileMap::draw_cells_outline(Control *p_control, Set<Vector2i> p_cells, Color p_color, Transform2D p_transform) { +void TileMap::draw_cells_outline(Control *p_control, RBSet<Vector2i> p_cells, Color p_color, Transform2D p_transform) { if (!tile_set.is_valid()) { return; } @@ -3522,11 +3739,11 @@ void TileMap::draw_cells_outline(Control *p_control, Set<Vector2i> p_cells, Colo Vector<Vector2> polygon = tile_set->get_tile_shape_polygon(); TileSet::TileShape shape = tile_set->get_tile_shape(); - for (Set<Vector2i>::Element *E = p_cells.front(); E; E = E->next()) { - Vector2 center = map_to_world(E->get()); + for (const Vector2i &E : p_cells) { + Vector2 center = map_to_world(E); #define DRAW_SIDE_IF_NEEDED(side, polygon_index_from, polygon_index_to) \ - if (!p_cells.has(get_neighbor_cell(E->get(), side))) { \ + if (!p_cells.has(get_neighbor_cell(E, side))) { \ Vector2 from = p_transform.xform(center + polygon[polygon_index_from] * tile_size); \ Vector2 to = p_transform.xform(center + polygon[polygon_index_to] * tile_size); \ p_control->draw_line(from, to, p_color); \ @@ -3576,7 +3793,7 @@ TypedArray<String> TileMap::get_configuration_warnings() const { TypedArray<String> warnings = Node::get_configuration_warnings(); // Retrieve the set of Z index values with a Y-sorted layer. - Set<int> y_sorted_z_index; + RBSet<int> y_sorted_z_index; for (int layer = 0; layer < (int)layers.size(); layer++) { if (layers[layer].y_sort_enabled) { y_sorted_z_index.insert(layers[layer].z_index); @@ -3638,7 +3855,8 @@ void TileMap::_bind_methods() { ClassDB::bind_method(D_METHOD("map_pattern", "position_in_tilemap", "coords_in_pattern", "pattern"), &TileMap::map_pattern); ClassDB::bind_method(D_METHOD("set_pattern", "layer", "position", "pattern"), &TileMap::set_pattern); - ClassDB::bind_method(D_METHOD("set_cells_from_surrounding_terrains", "layer", "cells", "terrain_set", "ignore_empty_terrains"), &TileMap::set_cells_from_surrounding_terrains, DEFVAL(true)); + ClassDB::bind_method(D_METHOD("set_cells_terrain_connect", "layer", "cells", "terrain_set", "terrain", "ignore_empty_terrains"), &TileMap::set_cells_terrain_connect, DEFVAL(true)); + ClassDB::bind_method(D_METHOD("set_cells_terrain_path", "layer", "path", "terrain_set", "terrain", "ignore_empty_terrains"), &TileMap::set_cells_terrain_path, DEFVAL(true)); ClassDB::bind_method(D_METHOD("fix_invalid_tiles"), &TileMap::fix_invalid_tiles); ClassDB::bind_method(D_METHOD("clear_layer", "layer"), &TileMap::clear_layer); diff --git a/scene/2d/tile_map.h b/scene/2d/tile_map.h index a0655dea2a..012bf01df9 100644 --- a/scene/2d/tile_map.h +++ b/scene/2d/tile_map.h @@ -57,11 +57,11 @@ struct TileMapQuadrant { Vector2i coords; // TileMapCells - Set<Vector2i> cells; + RBSet<Vector2i> cells; // We need those two maps to sort by world position for rendering // This is kind of workaround, it would be better to sort the cells directly in the "cells" set instead. - Map<Vector2i, Vector2i> map_to_world; - Map<Vector2i, Vector2i, CoordsWorldComparator> world_to_map; + RBMap<Vector2i, Vector2i> map_to_world; + RBMap<Vector2i, Vector2i, CoordsWorldComparator> world_to_map; // Debug. RID debug_canvas_item; @@ -74,13 +74,13 @@ struct TileMapQuadrant { List<RID> bodies; // Navigation. - Map<Vector2i, Vector<RID>> navigation_regions; + HashMap<Vector2i, Vector<RID>> navigation_regions; // Scenes. - Map<Vector2i, String> scenes; + HashMap<Vector2i, String> scenes; // Runtime TileData cache. - Map<Vector2i, TileData *> runtime_tile_data_cache; + HashMap<Vector2i, TileData *> runtime_tile_data_cache; void operator=(const TileMapQuadrant &q) { layer = q.layer; @@ -119,6 +119,8 @@ public: int bit = -1; int terrain = -1; + int priority = 1; + public: bool operator<(const TerrainConstraint &p_other) const { if (base_cell_coords == p_other.base_cell_coords) { @@ -128,14 +130,18 @@ public: } String to_string() const { - return vformat("Constraint {pos:%s, bit:%d, terrain:%d}", base_cell_coords, bit, terrain); + return vformat("Constraint {pos:%s, bit:%d, terrain:%d, priority:%d}", base_cell_coords, bit, terrain, priority); } Vector2i get_base_cell_coords() const { return base_cell_coords; } - Map<Vector2i, TileSet::CellNeighbor> get_overlapping_coords_and_peering_bits() const; + bool is_center_bit() const { + return bit == 0; + } + + HashMap<Vector2i, TileSet::CellNeighbor> get_overlapping_coords_and_peering_bits() const; void set_terrain(int p_terrain) { terrain = p_terrain; @@ -145,8 +151,17 @@ public: return terrain; } - TerrainConstraint(const TileMap *p_tile_map, const Vector2i &p_position, const TileSet::CellNeighbor &p_bit, int p_terrain); - TerrainConstraint() {} + void set_priority(int p_priority) { + priority = p_priority; + } + + int get_priority() { + return priority; + } + + TerrainConstraint(const TileMap *p_tile_map, const Vector2i &p_position, int p_terrain); // For the center terrain bit + TerrainConstraint(const TileMap *p_tile_map, const Vector2i &p_position, const TileSet::CellNeighbor &p_bit, int p_terrain); // For peering bits + TerrainConstraint(){}; }; enum VisibilityMode { @@ -193,22 +208,22 @@ private: int y_sort_origin = 0; int z_index = 0; RID canvas_item; - Map<Vector2i, TileMapCell> tile_map; - Map<Vector2i, TileMapQuadrant> quadrant_map; + HashMap<Vector2i, TileMapCell> tile_map; + HashMap<Vector2i, TileMapQuadrant> quadrant_map; SelfList<TileMapQuadrant>::List dirty_quadrant_list; }; LocalVector<TileMapLayer> layers; int selected_layer = -1; // Mapping for RID to coords. - Map<RID, Vector2i> bodies_coords; + HashMap<RID, Vector2i> bodies_coords; // Quadrants and internals management. Vector2i _coords_to_quadrant_coords(int p_layer, const Vector2i &p_coords) const; - Map<Vector2i, TileMapQuadrant>::Element *_create_quadrant(int p_layer, const Vector2i &p_qk); + HashMap<Vector2i, TileMapQuadrant>::Iterator _create_quadrant(int p_layer, const Vector2i &p_qk); - void _make_quadrant_dirty(Map<Vector2i, TileMapQuadrant>::Element *Q); + void _make_quadrant_dirty(HashMap<Vector2i, TileMapQuadrant>::Iterator Q); void _make_all_quadrants_dirty(); void _queue_update_dirty_quadrants(); @@ -217,7 +232,7 @@ private: void _recreate_layer_internals(int p_layer); void _recreate_internals(); - void _erase_quadrant(Map<Vector2i, TileMapQuadrant>::Element *Q); + void _erase_quadrant(HashMap<Vector2i, TileMapQuadrant>::Iterator Q); void _clear_layer_internals(int p_layer); void _clear_internals(); @@ -251,7 +266,9 @@ private: void _scenes_draw_quadrant_debug(TileMapQuadrant *p_quadrant); // Terrains. - Set<TileSet::TerrainsPattern> _get_valid_terrains_patterns_for_constraints(int p_terrain_set, const Vector2i &p_position, Set<TerrainConstraint> p_constraints); + TileSet::TerrainsPattern _get_best_terrain_pattern_for_constraints(int p_terrain_set, const Vector2i &p_position, RBSet<TerrainConstraint> p_constraints); + RBSet<TerrainConstraint> _get_terrain_constraints_from_added_pattern(Vector2i p_position, int p_terrain_set, TileSet::TerrainsPattern p_terrains_pattern) const; + RBSet<TerrainConstraint> _get_terrain_constraints_from_cells_list(int p_layer, const RBSet<Vector2i> &p_on_map, int p_terrain_set, bool p_ignore_empty_terrains) const; // Set and get tiles from data arrays. void _set_tile_data(int p_layer, const Vector<int> &p_data); @@ -333,14 +350,17 @@ public: void set_pattern(int p_layer, Vector2i p_position, const Ref<TileMapPattern> p_pattern); // Terrains. - Set<TerrainConstraint> get_terrain_constraints_from_removed_cells_list(int p_layer, const Set<Vector2i> &p_to_replace, int p_terrain_set, bool p_ignore_empty_terrains = true) const; // Not exposed. - Set<TerrainConstraint> get_terrain_constraints_from_added_tile(Vector2i p_position, int p_terrain_set, TileSet::TerrainsPattern p_terrains_pattern) const; // Not exposed. - Map<Vector2i, TileSet::TerrainsPattern> terrain_wave_function_collapse(const Set<Vector2i> &p_to_replace, int p_terrain_set, const Set<TerrainConstraint> p_constraints); // Not exposed. - void set_cells_from_surrounding_terrains(int p_layer, TypedArray<Vector2i> p_coords_array, int p_terrain_set, bool p_ignore_empty_terrains = true); + HashMap<Vector2i, TileSet::TerrainsPattern> terrain_fill_constraints(const Vector<Vector2i> &p_to_replace, int p_terrain_set, const RBSet<TerrainConstraint> p_constraints); // Not exposed. + HashMap<Vector2i, TileSet::TerrainsPattern> terrain_fill_connect(int p_layer, const Vector<Vector2i> &p_coords_array, int p_terrain_set, int p_terrain, bool p_ignore_empty_terrains = true); // Not exposed. + HashMap<Vector2i, TileSet::TerrainsPattern> terrain_fill_path(int p_layer, const Vector<Vector2i> &p_coords_array, int p_terrain_set, int p_terrain, bool p_ignore_empty_terrains = true); // Not exposed. + HashMap<Vector2i, TileSet::TerrainsPattern> terrain_fill_pattern(int p_layer, const Vector<Vector2i> &p_coords_array, int p_terrain_set, TileSet::TerrainsPattern p_terrains_pattern, bool p_ignore_empty_terrains = true); // Not exposed. + + void set_cells_terrain_connect(int p_layer, TypedArray<Vector2i> p_cells, int p_terrain_set, int p_terrain, bool p_ignore_empty_terrains = true); + void set_cells_terrain_path(int p_layer, TypedArray<Vector2i> p_path, int p_terrain_set, int p_terrain, bool p_ignore_empty_terrains = true); // Not exposed to users TileMapCell get_cell(int p_layer, const Vector2i &p_coords, bool p_use_proxies = false) const; - Map<Vector2i, TileMapQuadrant> *get_quadrant_map(int p_layer); + HashMap<Vector2i, TileMapQuadrant> *get_quadrant_map(int p_layer); int get_effective_quadrant_size(int p_layer) const; //--- @@ -365,7 +385,7 @@ public: // For finding tiles from collision. Vector2i get_coords_for_body_rid(RID p_physics_body); - // Fixing a nclearing methods. + // Fixing and clearing methods. void fix_invalid_tiles(); // Clears tiles from a given layer @@ -377,7 +397,7 @@ public: // Helpers? TypedArray<Vector2i> get_surrounding_tiles(Vector2i coords); - void draw_cells_outline(Control *p_control, Set<Vector2i> p_cells, Color p_color, Transform2D p_transform = Transform2D()); + void draw_cells_outline(Control *p_control, RBSet<Vector2i> p_cells, Color p_color, Transform2D p_transform = Transform2D()); // Virtual function to modify the TileData at runtime GDVIRTUAL2R(bool, _use_tile_data_runtime_update, int, Vector2i); diff --git a/scene/2d/visible_on_screen_notifier_2d.cpp b/scene/2d/visible_on_screen_notifier_2d.cpp index 4bceaf71c6..1971dc1240 100644 --- a/scene/2d/visible_on_screen_notifier_2d.cpp +++ b/scene/2d/visible_on_screen_notifier_2d.cpp @@ -66,6 +66,7 @@ void VisibleOnScreenNotifier2D::set_rect(const Rect2 &p_rect) { if (is_inside_tree()) { RS::get_singleton()->canvas_item_set_visibility_notifier(get_canvas_item(), true, rect, callable_mp(this, &VisibleOnScreenNotifier2D::_visibility_enter), callable_mp(this, &VisibleOnScreenNotifier2D::_visibility_exit)); } + update(); } Rect2 VisibleOnScreenNotifier2D::get_rect() const { @@ -101,7 +102,7 @@ void VisibleOnScreenNotifier2D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_rect"), &VisibleOnScreenNotifier2D::get_rect); ClassDB::bind_method(D_METHOD("is_on_screen"), &VisibleOnScreenNotifier2D::is_on_screen); - ADD_PROPERTY(PropertyInfo(Variant::RECT2, "rect"), "set_rect", "get_rect"); + ADD_PROPERTY(PropertyInfo(Variant::RECT2, "rect", PROPERTY_HINT_NONE, "suffix:px"), "set_rect", "get_rect"); ADD_SIGNAL(MethodInfo("screen_entered")); ADD_SIGNAL(MethodInfo("screen_exited")); @@ -198,7 +199,7 @@ void VisibleOnScreenEnabler2D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_enable_node_path"), &VisibleOnScreenEnabler2D::get_enable_node_path); ADD_GROUP("Enabling", "enable_"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "enable_mode", PROPERTY_HINT_ENUM, "Inherit,Always,WhenPaused"), "set_enable_mode", "get_enable_mode"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "enable_mode", PROPERTY_HINT_ENUM, "Inherit,Always,When Paused"), "set_enable_mode", "get_enable_mode"); ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "enable_node_path"), "set_enable_node_path", "get_enable_node_path"); BIND_ENUM_CONSTANT(ENABLE_MODE_INHERIT); diff --git a/scene/2d/visible_on_screen_notifier_2d.h b/scene/2d/visible_on_screen_notifier_2d.h index e0d580f174..38b508e2f6 100644 --- a/scene/2d/visible_on_screen_notifier_2d.h +++ b/scene/2d/visible_on_screen_notifier_2d.h @@ -37,7 +37,7 @@ class Viewport; class VisibleOnScreenNotifier2D : public Node2D { GDCLASS(VisibleOnScreenNotifier2D, Node2D); - Set<Viewport *> viewports; + HashSet<Viewport *> viewports; Rect2 rect; diff --git a/scene/3d/area_3d.cpp b/scene/3d/area_3d.cpp index 78c968a3d4..fb0c59daa1 100644 --- a/scene/3d/area_3d.cpp +++ b/scene/3d/area_3d.cpp @@ -175,7 +175,7 @@ void Area3D::_initialize_wind() { Node3D *p_wind_source = Object::cast_to<Node3D>(get_node(wind_source_path)); ERR_FAIL_NULL(p_wind_source); Transform3D global_transform = p_wind_source->get_transform(); - wind_direction = -global_transform.basis.get_axis(Vector3::AXIS_Z).normalized(); + wind_direction = -global_transform.basis.get_column(Vector3::AXIS_Z).normalized(); wind_source = global_transform.origin; temp_magnitude = wind_force_magnitude; } @@ -192,14 +192,14 @@ void Area3D::_body_enter_tree(ObjectID p_id) { Node *node = Object::cast_to<Node>(obj); ERR_FAIL_COND(!node); - Map<ObjectID, BodyState>::Element *E = body_map.find(p_id); + HashMap<ObjectID, BodyState>::Iterator E = body_map.find(p_id); ERR_FAIL_COND(!E); - ERR_FAIL_COND(E->get().in_tree); + ERR_FAIL_COND(E->value.in_tree); - E->get().in_tree = true; + E->value.in_tree = true; emit_signal(SceneStringNames::get_singleton()->body_entered, node); - for (int i = 0; i < E->get().shapes.size(); i++) { - emit_signal(SceneStringNames::get_singleton()->body_shape_entered, E->get().rid, node, E->get().shapes[i].body_shape, E->get().shapes[i].area_shape); + for (int i = 0; i < E->value.shapes.size(); i++) { + emit_signal(SceneStringNames::get_singleton()->body_shape_entered, E->value.rid, node, E->value.shapes[i].body_shape, E->value.shapes[i].area_shape); } } @@ -207,13 +207,13 @@ void Area3D::_body_exit_tree(ObjectID p_id) { Object *obj = ObjectDB::get_instance(p_id); Node *node = Object::cast_to<Node>(obj); ERR_FAIL_COND(!node); - Map<ObjectID, BodyState>::Element *E = body_map.find(p_id); + HashMap<ObjectID, BodyState>::Iterator E = body_map.find(p_id); ERR_FAIL_COND(!E); - ERR_FAIL_COND(!E->get().in_tree); - E->get().in_tree = false; + ERR_FAIL_COND(!E->value.in_tree); + E->value.in_tree = false; emit_signal(SceneStringNames::get_singleton()->body_exited, node); - for (int i = 0; i < E->get().shapes.size(); i++) { - emit_signal(SceneStringNames::get_singleton()->body_shape_exited, E->get().rid, node, E->get().shapes[i].body_shape, E->get().shapes[i].area_shape); + for (int i = 0; i < E->value.shapes.size(); i++) { + emit_signal(SceneStringNames::get_singleton()->body_shape_exited, E->value.rid, node, E->value.shapes[i].body_shape, E->value.shapes[i].area_shape); } } @@ -224,7 +224,7 @@ void Area3D::_body_inout(int p_status, const RID &p_body, ObjectID p_instance, i Object *obj = ObjectDB::get_instance(objid); Node *node = Object::cast_to<Node>(obj); - Map<ObjectID, BodyState>::Element *E = body_map.find(objid); + HashMap<ObjectID, BodyState>::Iterator E = body_map.find(objid); if (!body_in && !E) { return; //likely removed from the tree @@ -235,36 +235,36 @@ void Area3D::_body_inout(int p_status, const RID &p_body, ObjectID p_instance, i if (body_in) { if (!E) { E = body_map.insert(objid, BodyState()); - E->get().rid = p_body; - E->get().rc = 0; - E->get().in_tree = node && node->is_inside_tree(); + E->value.rid = p_body; + E->value.rc = 0; + E->value.in_tree = node && node->is_inside_tree(); if (node) { node->connect(SceneStringNames::get_singleton()->tree_entered, callable_mp(this, &Area3D::_body_enter_tree), make_binds(objid)); node->connect(SceneStringNames::get_singleton()->tree_exiting, callable_mp(this, &Area3D::_body_exit_tree), make_binds(objid)); - if (E->get().in_tree) { + if (E->value.in_tree) { emit_signal(SceneStringNames::get_singleton()->body_entered, node); } } } - E->get().rc++; + E->value.rc++; if (node) { - E->get().shapes.insert(ShapePair(p_body_shape, p_area_shape)); + E->value.shapes.insert(ShapePair(p_body_shape, p_area_shape)); } - if (E->get().in_tree) { + if (E->value.in_tree) { emit_signal(SceneStringNames::get_singleton()->body_shape_entered, p_body, node, p_body_shape, p_area_shape); } } else { - E->get().rc--; + E->value.rc--; if (node) { - E->get().shapes.erase(ShapePair(p_body_shape, p_area_shape)); + E->value.shapes.erase(ShapePair(p_body_shape, p_area_shape)); } - bool in_tree = E->get().in_tree; - if (E->get().rc == 0) { - body_map.erase(E); + bool in_tree = E->value.in_tree; + if (E->value.rc == 0) { + body_map.remove(E); if (node) { node->disconnect(SceneStringNames::get_singleton()->tree_entered, callable_mp(this, &Area3D::_body_enter_tree)); node->disconnect(SceneStringNames::get_singleton()->tree_exiting, callable_mp(this, &Area3D::_body_exit_tree)); @@ -285,7 +285,7 @@ void Area3D::_clear_monitoring() { ERR_FAIL_COND_MSG(locked, "This function can't be used during the in/out signal."); { - Map<ObjectID, BodyState> bmcopy = body_map; + HashMap<ObjectID, BodyState> bmcopy = body_map; body_map.clear(); //disconnect all monitored stuff @@ -314,7 +314,7 @@ void Area3D::_clear_monitoring() { } { - Map<ObjectID, AreaState> bmcopy = area_map; + HashMap<ObjectID, AreaState> bmcopy = area_map; area_map.clear(); //disconnect all monitored stuff @@ -379,14 +379,14 @@ void Area3D::_area_enter_tree(ObjectID p_id) { Node *node = Object::cast_to<Node>(obj); ERR_FAIL_COND(!node); - Map<ObjectID, AreaState>::Element *E = area_map.find(p_id); + HashMap<ObjectID, AreaState>::Iterator E = area_map.find(p_id); ERR_FAIL_COND(!E); - ERR_FAIL_COND(E->get().in_tree); + ERR_FAIL_COND(E->value.in_tree); - E->get().in_tree = true; + E->value.in_tree = true; emit_signal(SceneStringNames::get_singleton()->area_entered, node); - for (int i = 0; i < E->get().shapes.size(); i++) { - emit_signal(SceneStringNames::get_singleton()->area_shape_entered, E->get().rid, node, E->get().shapes[i].area_shape, E->get().shapes[i].self_shape); + for (int i = 0; i < E->value.shapes.size(); i++) { + emit_signal(SceneStringNames::get_singleton()->area_shape_entered, E->value.rid, node, E->value.shapes[i].area_shape, E->value.shapes[i].self_shape); } } @@ -394,13 +394,13 @@ void Area3D::_area_exit_tree(ObjectID p_id) { Object *obj = ObjectDB::get_instance(p_id); Node *node = Object::cast_to<Node>(obj); ERR_FAIL_COND(!node); - Map<ObjectID, AreaState>::Element *E = area_map.find(p_id); + HashMap<ObjectID, AreaState>::Iterator E = area_map.find(p_id); ERR_FAIL_COND(!E); - ERR_FAIL_COND(!E->get().in_tree); - E->get().in_tree = false; + ERR_FAIL_COND(!E->value.in_tree); + E->value.in_tree = false; emit_signal(SceneStringNames::get_singleton()->area_exited, node); - for (int i = 0; i < E->get().shapes.size(); i++) { - emit_signal(SceneStringNames::get_singleton()->area_shape_exited, E->get().rid, node, E->get().shapes[i].area_shape, E->get().shapes[i].self_shape); + for (int i = 0; i < E->value.shapes.size(); i++) { + emit_signal(SceneStringNames::get_singleton()->area_shape_exited, E->value.rid, node, E->value.shapes[i].area_shape, E->value.shapes[i].self_shape); } } @@ -411,7 +411,7 @@ void Area3D::_area_inout(int p_status, const RID &p_area, ObjectID p_instance, i Object *obj = ObjectDB::get_instance(objid); Node *node = Object::cast_to<Node>(obj); - Map<ObjectID, AreaState>::Element *E = area_map.find(objid); + HashMap<ObjectID, AreaState>::Iterator E = area_map.find(objid); if (!area_in && !E) { return; //likely removed from the tree @@ -422,36 +422,36 @@ void Area3D::_area_inout(int p_status, const RID &p_area, ObjectID p_instance, i if (area_in) { if (!E) { E = area_map.insert(objid, AreaState()); - E->get().rid = p_area; - E->get().rc = 0; - E->get().in_tree = node && node->is_inside_tree(); + E->value.rid = p_area; + E->value.rc = 0; + E->value.in_tree = node && node->is_inside_tree(); if (node) { node->connect(SceneStringNames::get_singleton()->tree_entered, callable_mp(this, &Area3D::_area_enter_tree), make_binds(objid)); node->connect(SceneStringNames::get_singleton()->tree_exiting, callable_mp(this, &Area3D::_area_exit_tree), make_binds(objid)); - if (E->get().in_tree) { + if (E->value.in_tree) { emit_signal(SceneStringNames::get_singleton()->area_entered, node); } } } - E->get().rc++; + E->value.rc++; if (node) { - E->get().shapes.insert(AreaShapePair(p_area_shape, p_self_shape)); + E->value.shapes.insert(AreaShapePair(p_area_shape, p_self_shape)); } - if (!node || E->get().in_tree) { + if (!node || E->value.in_tree) { emit_signal(SceneStringNames::get_singleton()->area_shape_entered, p_area, node, p_area_shape, p_self_shape); } } else { - E->get().rc--; + E->value.rc--; if (node) { - E->get().shapes.erase(AreaShapePair(p_area_shape, p_self_shape)); + E->value.shapes.erase(AreaShapePair(p_area_shape, p_self_shape)); } - bool in_tree = E->get().in_tree; - if (E->get().rc == 0) { - area_map.erase(E); + bool in_tree = E->value.in_tree; + if (E->value.rc == 0) { + area_map.remove(E); if (node) { node->disconnect(SceneStringNames::get_singleton()->tree_entered, callable_mp(this, &Area3D::_area_enter_tree)); node->disconnect(SceneStringNames::get_singleton()->tree_exiting, callable_mp(this, &Area3D::_area_exit_tree)); @@ -524,20 +524,20 @@ TypedArray<Area3D> Area3D::get_overlapping_areas() const { bool Area3D::overlaps_area(Node *p_area) const { ERR_FAIL_NULL_V(p_area, false); - const Map<ObjectID, AreaState>::Element *E = area_map.find(p_area->get_instance_id()); + HashMap<ObjectID, AreaState>::ConstIterator E = area_map.find(p_area->get_instance_id()); if (!E) { return false; } - return E->get().in_tree; + return E->value.in_tree; } bool Area3D::overlaps_body(Node *p_body) const { ERR_FAIL_NULL_V(p_body, false); - const Map<ObjectID, BodyState>::Element *E = body_map.find(p_body->get_instance_id()); + HashMap<ObjectID, BodyState>::ConstIterator E = body_map.find(p_body->get_instance_id()); if (!E) { return false; } - return E->get().in_tree; + return E->value.in_tree; } void Area3D::set_audio_bus_override(bool p_override) { @@ -728,9 +728,9 @@ void Area3D::_bind_methods() { 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_point_center", PROPERTY_HINT_NONE, "suffix:m"), "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_PROPERTY(PropertyInfo(Variant::FLOAT, "gravity", PROPERTY_HINT_RANGE, U"-32,32,0.001,or_lesser,or_greater,suffix:m/s\u00B2"), "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"); diff --git a/scene/3d/area_3d.h b/scene/3d/area_3d.h index c2399985ff..3b892baf57 100644 --- a/scene/3d/area_3d.h +++ b/scene/3d/area_3d.h @@ -49,7 +49,7 @@ public: private: SpaceOverride gravity_space_override = SPACE_OVERRIDE_DISABLED; Vector3 gravity_vec; - real_t gravity; + real_t gravity = 0.0; bool gravity_is_point = false; real_t gravity_distance_scale = 0.0; @@ -98,7 +98,7 @@ private: VSet<ShapePair> shapes; }; - Map<ObjectID, BodyState> body_map; + HashMap<ObjectID, BodyState> body_map; void _area_inout(int p_status, const RID &p_area, ObjectID p_instance, int p_area_shape, int p_self_shape); @@ -130,7 +130,7 @@ private: VSet<AreaShapePair> shapes; }; - Map<ObjectID, AreaState> area_map; + HashMap<ObjectID, AreaState> area_map; void _clear_monitoring(); bool audio_bus_override = false; @@ -228,4 +228,4 @@ public: VARIANT_ENUM_CAST(Area3D::SpaceOverride); -#endif // AREA__H +#endif // AREA_3D_H diff --git a/scene/3d/audio_listener_3d.cpp b/scene/3d/audio_listener_3d.cpp index 1ead9bb384..4f3f403ab7 100644 --- a/scene/3d/audio_listener_3d.cpp +++ b/scene/3d/audio_listener_3d.cpp @@ -68,7 +68,7 @@ bool AudioListener3D::_get(const StringName &p_name, Variant &r_ret) const { } void AudioListener3D::_get_property_list(List<PropertyInfo> *p_list) const { - p_list->push_back(PropertyInfo(Variant::BOOL, "current")); + p_list->push_back(PropertyInfo(Variant::BOOL, PNAME("current"))); } void AudioListener3D::_update_listener() { diff --git a/scene/3d/audio_stream_player_3d.cpp b/scene/3d/audio_stream_player_3d.cpp index b17201f86b..824ea0407e 100644 --- a/scene/3d/audio_stream_player_3d.cpp +++ b/scene/3d/audio_stream_player_3d.cpp @@ -30,6 +30,7 @@ #include "audio_stream_player_3d.h" +#include "core/config/project_settings.h" #include "scene/3d/area_3d.h" #include "scene/3d/audio_listener_3d.h" #include "scene/3d/camera_3d.h" @@ -273,7 +274,8 @@ void AudioStreamPlayer3D::_notification(int p_what) { case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: { // Update anything related to position first, if possible of course. Vector<AudioFrame> volume_vector; - if (setplay.get() > 0 || (active.is_set() && last_mix_count != AudioServer::get_singleton()->get_mix_count())) { + if (setplay.get() > 0 || (active.is_set() && last_mix_count != AudioServer::get_singleton()->get_mix_count()) || force_update_panning) { + force_update_panning = false; volume_vector = _update_panning(); } @@ -281,7 +283,7 @@ void AudioStreamPlayer3D::_notification(int p_what) { active.set(); Ref<AudioStreamPlayback> new_playback = stream->instance_playback(); ERR_FAIL_COND_MSG(new_playback.is_null(), "Failed to instantiate playback."); - Map<StringName, Vector<AudioFrame>> bus_map; + HashMap<StringName, Vector<AudioFrame>> bus_map; bus_map[_get_actual_bus()] = volume_vector; AudioServer::get_singleton()->start_playback_stream(new_playback, bus_map, setplay.get(), actual_pitch_scale, linear_attenuation, attenuation_filter_cutoff_hz); stream_playbacks.push_back(new_playback); @@ -318,6 +320,7 @@ void AudioStreamPlayer3D::_notification(int p_what) { } } +// Interacts with PhysicsServer3D, so can only be called during _physics_process Area3D *AudioStreamPlayer3D::_get_overriding_area() { //check if any area is diverting sound into a bus Ref<World3D> world_3d = get_world_3d(); @@ -356,6 +359,7 @@ Area3D *AudioStreamPlayer3D::_get_overriding_area() { return nullptr; } +// Interacts with PhysicsServer3D, so can only be called during _physics_process StringName AudioStreamPlayer3D::_get_actual_bus() { Area3D *overriding_area = _get_overriding_area(); if (overriding_area && overriding_area->is_overriding_audio_bus() && !overriding_area->is_using_reverb_bus()) { @@ -364,6 +368,7 @@ StringName AudioStreamPlayer3D::_get_actual_bus() { return bus; } +// Interacts with PhysicsServer3D, so can only be called during _physics_process Vector<AudioFrame> AudioStreamPlayer3D::_update_panning() { Vector<AudioFrame> output_volume_vector; output_volume_vector.resize(4); @@ -387,7 +392,7 @@ Vector<AudioFrame> AudioStreamPlayer3D::_update_panning() { Ref<World3D> world_3d = get_world_3d(); ERR_FAIL_COND_V(world_3d.is_null(), output_volume_vector); - Set<Camera3D *> cameras = world_3d->get_cameras(); + HashSet<Camera3D *> cameras = world_3d->get_cameras(); cameras.insert(get_viewport()->get_camera_3d()); PhysicsDirectSpaceState3D *space_state = PhysicsServer3D::get_singleton()->space_get_direct_state(world_3d->get_space()); @@ -447,7 +452,7 @@ Vector<AudioFrame> AudioStreamPlayer3D::_update_panning() { if (emission_angle_enabled) { Vector3 listenertopos = global_pos - listener_node->get_global_transform().origin; - float c = listenertopos.normalized().dot(get_global_transform().basis.get_axis(2).normalized()); //it's z negative + float c = listenertopos.normalized().dot(get_global_transform().basis.get_column(2).normalized()); //it's z negative float angle = Math::rad2deg(Math::acos(c)); if (angle > emission_angle) { db_att -= -emission_angle_filter_attenuation_db; @@ -458,15 +463,16 @@ Vector<AudioFrame> AudioStreamPlayer3D::_update_panning() { for (Ref<AudioStreamPlayback> &playback : stream_playbacks) { AudioServer::get_singleton()->set_playback_highshelf_params(playback, linear_attenuation, attenuation_filter_cutoff_hz); } - //TODO: The lower the second parameter (tightness) the more the sound will "enclose" the listener (more undirected / playing from - // speakers not facing the source) - this could be made distance dependent. - _calc_output_vol(local_pos.normalized(), 4.0, output_volume_vector); + // Bake in a constant factor here to allow the project setting defaults for 2d and 3d to be normalized to 1.0. + float tightness = cached_global_panning_strength * 2.0f; + tightness *= panning_strength; + _calc_output_vol(local_pos.normalized(), tightness, output_volume_vector); for (unsigned int k = 0; k < 4; k++) { output_volume_vector.write[k] = multiplier * output_volume_vector[k]; } - Map<StringName, Vector<AudioFrame>> bus_volumes; + HashMap<StringName, Vector<AudioFrame>> bus_volumes; if (area) { if (area->is_overriding_audio_bus()) { //override audio bus @@ -537,6 +543,7 @@ float AudioStreamPlayer3D::get_unit_db() const { void AudioStreamPlayer3D::set_unit_size(float p_volume) { unit_size = p_volume; + update_gizmos(); } float AudioStreamPlayer3D::get_unit_size() const { @@ -665,6 +672,7 @@ void AudioStreamPlayer3D::_bus_layout_changed() { void AudioStreamPlayer3D::set_max_distance(float p_metres) { ERR_FAIL_COND(p_metres < 0.0); max_distance = p_metres; + update_gizmos(); } float AudioStreamPlayer3D::get_max_distance() const { @@ -725,6 +733,7 @@ float AudioStreamPlayer3D::get_attenuation_filter_db() const { void AudioStreamPlayer3D::set_attenuation_model(AttenuationModel p_model) { ERR_FAIL_INDEX((int)p_model, 4); attenuation_model = p_model; + update_gizmos(); } AudioStreamPlayer3D::AttenuationModel AudioStreamPlayer3D::get_attenuation_model() const { @@ -785,6 +794,15 @@ int AudioStreamPlayer3D::get_max_polyphony() const { return max_polyphony; } +void AudioStreamPlayer3D::set_panning_strength(float p_panning_strength) { + ERR_FAIL_COND_MSG(p_panning_strength < 0, "Panning strength must be a positive number."); + panning_strength = p_panning_strength; +} + +float AudioStreamPlayer3D::get_panning_strength() const { + return panning_strength; +} + void AudioStreamPlayer3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_stream", "stream"), &AudioStreamPlayer3D::set_stream); ClassDB::bind_method(D_METHOD("get_stream"), &AudioStreamPlayer3D::get_stream); @@ -850,28 +868,32 @@ void AudioStreamPlayer3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_max_polyphony", "max_polyphony"), &AudioStreamPlayer3D::set_max_polyphony); ClassDB::bind_method(D_METHOD("get_max_polyphony"), &AudioStreamPlayer3D::get_max_polyphony); + ClassDB::bind_method(D_METHOD("set_panning_strength", "panning_strength"), &AudioStreamPlayer3D::set_panning_strength); + ClassDB::bind_method(D_METHOD("get_panning_strength"), &AudioStreamPlayer3D::get_panning_strength); + ClassDB::bind_method(D_METHOD("get_stream_playback"), &AudioStreamPlayer3D::get_stream_playback); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "stream", PROPERTY_HINT_RESOURCE_TYPE, "AudioStream"), "set_stream", "get_stream"); ADD_PROPERTY(PropertyInfo(Variant::INT, "attenuation_model", PROPERTY_HINT_ENUM, "Inverse,Inverse Square,Logarithmic,Disabled"), "set_attenuation_model", "get_attenuation_model"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "unit_db", PROPERTY_HINT_RANGE, "-80,80"), "set_unit_db", "get_unit_db"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "unit_db", PROPERTY_HINT_RANGE, "-80,80,suffix:dB"), "set_unit_db", "get_unit_db"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "unit_size", PROPERTY_HINT_RANGE, "0.1,100,0.01,or_greater"), "set_unit_size", "get_unit_size"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "max_db", PROPERTY_HINT_RANGE, "-24,6"), "set_max_db", "get_max_db"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "max_db", PROPERTY_HINT_RANGE, "-24,6,suffix:dB"), "set_max_db", "get_max_db"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "pitch_scale", PROPERTY_HINT_RANGE, "0.01,4,0.01,or_greater"), "set_pitch_scale", "get_pitch_scale"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "playing", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "_set_playing", "is_playing"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "autoplay"), "set_autoplay", "is_autoplay_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "stream_paused", PROPERTY_HINT_NONE, ""), "set_stream_paused", "get_stream_paused"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "max_distance", PROPERTY_HINT_RANGE, "0,4096,0.01,or_greater"), "set_max_distance", "get_max_distance"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "max_distance", PROPERTY_HINT_RANGE, "0,4096,0.01,or_greater,suffix:m"), "set_max_distance", "get_max_distance"); ADD_PROPERTY(PropertyInfo(Variant::INT, "max_polyphony", PROPERTY_HINT_NONE, ""), "set_max_polyphony", "get_max_polyphony"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "panning_strength", PROPERTY_HINT_RANGE, "0,3,0.01,or_greater"), "set_panning_strength", "get_panning_strength"); ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "bus", PROPERTY_HINT_ENUM, ""), "set_bus", "get_bus"); ADD_PROPERTY(PropertyInfo(Variant::INT, "area_mask", PROPERTY_HINT_LAYERS_2D_PHYSICS), "set_area_mask", "get_area_mask"); ADD_GROUP("Emission Angle", "emission_angle"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "emission_angle_enabled"), "set_emission_angle_enabled", "is_emission_angle_enabled"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "emission_angle_degrees", PROPERTY_HINT_RANGE, "0.1,90,0.1,degrees"), "set_emission_angle", "get_emission_angle"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "emission_angle_filter_attenuation_db", PROPERTY_HINT_RANGE, "-80,0,0.1"), "set_emission_angle_filter_attenuation_db", "get_emission_angle_filter_attenuation_db"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "emission_angle_filter_attenuation_db", PROPERTY_HINT_RANGE, "-80,0,0.1,suffix:dB"), "set_emission_angle_filter_attenuation_db", "get_emission_angle_filter_attenuation_db"); ADD_GROUP("Attenuation Filter", "attenuation_filter_"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "attenuation_filter_cutoff_hz", PROPERTY_HINT_RANGE, "1,20500,1"), "set_attenuation_filter_cutoff_hz", "get_attenuation_filter_cutoff_hz"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "attenuation_filter_db", PROPERTY_HINT_RANGE, "-80,0,0.1"), "set_attenuation_filter_db", "get_attenuation_filter_db"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "attenuation_filter_cutoff_hz", PROPERTY_HINT_RANGE, "1,20500,1,suffix:Hz"), "set_attenuation_filter_cutoff_hz", "get_attenuation_filter_cutoff_hz"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "attenuation_filter_db", PROPERTY_HINT_RANGE, "-80,0,0.1,suffix:dB"), "set_attenuation_filter_db", "get_attenuation_filter_db"); ADD_GROUP("Doppler", "doppler_"); ADD_PROPERTY(PropertyInfo(Variant::INT, "doppler_tracking", PROPERTY_HINT_ENUM, "Disabled,Idle,Physics"), "set_doppler_tracking", "get_doppler_tracking"); @@ -891,6 +913,7 @@ AudioStreamPlayer3D::AudioStreamPlayer3D() { velocity_tracker.instantiate(); AudioServer::get_singleton()->connect("bus_layout_changed", callable_mp(this, &AudioStreamPlayer3D::_bus_layout_changed)); set_disable_scale(true); + cached_global_panning_strength = ProjectSettings::get_singleton()->get("audio/general/3d_panning_strength"); } AudioStreamPlayer3D::~AudioStreamPlayer3D() { diff --git a/scene/3d/audio_stream_player_3d.h b/scene/3d/audio_stream_player_3d.h index 53cdd2e630..85ece6d8d5 100644 --- a/scene/3d/audio_stream_player_3d.h +++ b/scene/3d/audio_stream_player_3d.h @@ -82,12 +82,13 @@ private: int max_polyphony = 1; uint64_t last_mix_count = -1; + bool force_update_panning = false; static void _calc_output_vol(const Vector3 &source_dir, real_t tightness, Vector<AudioFrame> &output); void _calc_reverb_vol(Area3D *area, Vector3 listener_area_pos, Vector<AudioFrame> direct_path_vol, Vector<AudioFrame> &reverb_vol); - static void _listener_changed_cb(void *self) { reinterpret_cast<AudioStreamPlayer3D *>(self)->_update_panning(); } + static void _listener_changed_cb(void *self) { reinterpret_cast<AudioStreamPlayer3D *>(self)->force_update_panning = true; } void _set_playing(bool p_enable); bool _is_active() const; @@ -115,6 +116,9 @@ private: float _get_attenuation_db(float p_distance) const; + float panning_strength = 1.0f; + float cached_global_panning_strength = 1.0f; + protected: void _validate_property(PropertyInfo &property) const override; void _notification(int p_what); @@ -181,6 +185,9 @@ public: void set_stream_paused(bool p_pause); bool get_stream_paused() const; + void set_panning_strength(float p_panning_strength); + float get_panning_strength() const; + Ref<AudioStreamPlayback> get_stream_playback(); AudioStreamPlayer3D(); diff --git a/scene/3d/bone_attachment_3d.cpp b/scene/3d/bone_attachment_3d.cpp index d0aeffb166..fbd5b5b65b 100644 --- a/scene/3d/bone_attachment_3d.cpp +++ b/scene/3d/bone_attachment_3d.cpp @@ -376,6 +376,24 @@ void BoneAttachment3D::on_bone_pose_update(int p_bone_index) { } } } +#ifdef TOOLS_ENABLED +void BoneAttachment3D::_notify_skeleton_bones_renamed(Node *p_base_scene, Skeleton3D *p_skeleton, Ref<BoneMap> p_bone_map) { + const Skeleton3D *parent = nullptr; + if (use_external_skeleton) { + if (external_skeleton_node_cache.is_valid()) { + parent = Object::cast_to<Skeleton3D>(ObjectDB::get_instance(external_skeleton_node_cache)); + } + } else { + parent = Object::cast_to<Skeleton3D>(get_parent()); + } + if (parent && parent == p_skeleton) { + StringName bn = p_bone_map->find_profile_bone_name(bone_name); + if (bn) { + set_bone_name(bn); + } + } +} +#endif // TOOLS_ENABLED BoneAttachment3D::BoneAttachment3D() { } @@ -398,6 +416,9 @@ void BoneAttachment3D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_use_external_skeleton"), &BoneAttachment3D::get_use_external_skeleton); ClassDB::bind_method(D_METHOD("set_external_skeleton", "external_skeleton"), &BoneAttachment3D::set_external_skeleton); ClassDB::bind_method(D_METHOD("get_external_skeleton"), &BoneAttachment3D::get_external_skeleton); +#ifdef TOOLS_ENABLED + ClassDB::bind_method(D_METHOD("_notify_skeleton_bones_renamed"), &BoneAttachment3D::_notify_skeleton_bones_renamed); +#endif // TOOLS_ENABLED ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "bone_name"), "set_bone_name", "get_bone_name"); ADD_PROPERTY(PropertyInfo(Variant::INT, "bone_idx"), "set_bone_idx", "get_bone_idx"); diff --git a/scene/3d/bone_attachment_3d.h b/scene/3d/bone_attachment_3d.h index 395dfde1d7..137360b141 100644 --- a/scene/3d/bone_attachment_3d.h +++ b/scene/3d/bone_attachment_3d.h @@ -32,6 +32,9 @@ #define BONE_ATTACHMENT_H #include "scene/3d/skeleton_3d.h" +#ifdef TOOLS_ENABLED +#include "scene/resources/bone_map.h" +#endif // TOOLS_ENABLED class BoneAttachment3D : public Node3D { GDCLASS(BoneAttachment3D, Node3D); @@ -68,6 +71,9 @@ protected: void _notification(int p_what); static void _bind_methods(); +#ifdef TOOLS_ENABLED + virtual void _notify_skeleton_bones_renamed(Node *p_base_scene, Skeleton3D *p_skeleton, Ref<BoneMap> p_bone_map); +#endif // TOOLS_ENABLED public: virtual TypedArray<String> get_configuration_warnings() const override; diff --git a/scene/3d/camera_3d.cpp b/scene/3d/camera_3d.cpp index 1f0c9acef5..10348b1eb6 100644 --- a/scene/3d/camera_3d.cpp +++ b/scene/3d/camera_3d.cpp @@ -144,8 +144,8 @@ void Camera3D::_notification(int p_what) { Transform3D Camera3D::get_camera_transform() const { Transform3D tr = get_global_transform().orthonormalized(); - tr.origin += tr.basis.get_axis(1) * v_offset; - tr.origin += tr.basis.get_axis(0) * h_offset; + tr.origin += tr.basis.get_column(1) * v_offset; + tr.origin += tr.basis.get_column(0) * h_offset; return tr; } @@ -217,8 +217,6 @@ void Camera3D::make_current() { } get_viewport()->_camera_3d_set(this); - - //get_scene()->call_group(SceneMainLoop::GROUP_CALL_REALTIME,camera_group,"_camera_make_current",this); } void Camera3D::clear_current(bool p_enable_next) { @@ -307,7 +305,7 @@ Vector3 Camera3D::project_ray_origin(const Point2 &p_pos) const { bool Camera3D::is_position_behind(const Vector3 &p_pos) const { Transform3D t = get_global_transform(); - Vector3 eyedir = -t.basis.get_axis(2).normalized(); + Vector3 eyedir = -t.basis.get_column(2).normalized(); return eyedir.dot(p_pos - t.origin) < near; } @@ -475,9 +473,9 @@ void Camera3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_near", "near"), &Camera3D::set_near); ClassDB::bind_method(D_METHOD("get_projection"), &Camera3D::get_projection); ClassDB::bind_method(D_METHOD("set_projection", "mode"), &Camera3D::set_projection); - ClassDB::bind_method(D_METHOD("set_h_offset", "ofs"), &Camera3D::set_h_offset); + ClassDB::bind_method(D_METHOD("set_h_offset", "offset"), &Camera3D::set_h_offset); ClassDB::bind_method(D_METHOD("get_h_offset"), &Camera3D::get_h_offset); - ClassDB::bind_method(D_METHOD("set_v_offset", "ofs"), &Camera3D::set_v_offset); + ClassDB::bind_method(D_METHOD("set_v_offset", "offset"), &Camera3D::set_v_offset); ClassDB::bind_method(D_METHOD("get_v_offset"), &Camera3D::get_v_offset); ClassDB::bind_method(D_METHOD("set_cull_mask", "mask"), &Camera3D::set_cull_mask); ClassDB::bind_method(D_METHOD("get_cull_mask"), &Camera3D::get_cull_mask); @@ -503,16 +501,16 @@ void Camera3D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::INT, "cull_mask", PROPERTY_HINT_LAYERS_3D_RENDER), "set_cull_mask", "get_cull_mask"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "environment", PROPERTY_HINT_RESOURCE_TYPE, "Environment"), "set_environment", "get_environment"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "effects", PROPERTY_HINT_RESOURCE_TYPE, "CameraEffects"), "set_effects", "get_effects"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "h_offset"), "set_h_offset", "get_h_offset"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "v_offset"), "set_v_offset", "get_v_offset"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "h_offset", PROPERTY_HINT_NONE, "suffix:m"), "set_h_offset", "get_h_offset"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "v_offset", PROPERTY_HINT_NONE, "suffix:m"), "set_v_offset", "get_v_offset"); ADD_PROPERTY(PropertyInfo(Variant::INT, "doppler_tracking", PROPERTY_HINT_ENUM, "Disabled,Idle,Physics"), "set_doppler_tracking", "get_doppler_tracking"); ADD_PROPERTY(PropertyInfo(Variant::INT, "projection", PROPERTY_HINT_ENUM, "Perspective,Orthogonal,Frustum"), "set_projection", "get_projection"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "current"), "set_current", "is_current"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "fov", PROPERTY_HINT_RANGE, "1,179,0.1,degrees"), "set_fov", "get_fov"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "size", PROPERTY_HINT_RANGE, "0.001,16384,0.001"), "set_size", "get_size"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "frustum_offset"), "set_frustum_offset", "get_frustum_offset"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "near", PROPERTY_HINT_RANGE, "0.001,10,0.001,or_greater,exp"), "set_near", "get_near"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "far", PROPERTY_HINT_RANGE, "0.01,4000,0.01,or_greater,exp"), "set_far", "get_far"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "size", PROPERTY_HINT_RANGE, "0.001,16384,0.01,suffix:m"), "set_size", "get_size"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "frustum_offset", PROPERTY_HINT_NONE, "suffix:m"), "set_frustum_offset", "get_frustum_offset"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "near", PROPERTY_HINT_RANGE, "0.001,10,0.001,or_greater,exp,suffix:m"), "set_near", "get_near"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "far", PROPERTY_HINT_RANGE, "0.01,4000,0.01,or_greater,exp,suffix:m"), "set_far", "get_far"); BIND_ENUM_CONSTANT(PROJECTION_PERSPECTIVE); BIND_ENUM_CONSTANT(PROJECTION_ORTHOGONAL); diff --git a/scene/3d/collision_object_3d.cpp b/scene/3d/collision_object_3d.cpp index 40c09593a4..a36357555a 100644 --- a/scene/3d/collision_object_3d.cpp +++ b/scene/3d/collision_object_3d.cpp @@ -345,9 +345,9 @@ void CollisionObject3D::_update_debug_shapes() { return; } - for (Set<uint32_t>::Element *shapedata_idx = debug_shapes_to_update.front(); shapedata_idx; shapedata_idx = shapedata_idx->next()) { - if (shapes.has(shapedata_idx->get())) { - ShapeData &shapedata = shapes[shapedata_idx->get()]; + for (const uint32_t &shapedata_idx : debug_shapes_to_update) { + if (shapes.has(shapedata_idx)) { + ShapeData &shapedata = shapes[shapedata_idx]; ShapeData::ShapeBase *shapes = shapedata.shapes.ptrw(); for (int i = 0; i < shapedata.shapes.size(); i++) { ShapeData::ShapeBase &s = shapes[i]; @@ -459,7 +459,7 @@ void CollisionObject3D::_bind_methods() { ADD_SIGNAL(MethodInfo("mouse_entered")); ADD_SIGNAL(MethodInfo("mouse_exited")); - ADD_PROPERTY(PropertyInfo(Variant::INT, "disable_mode", PROPERTY_HINT_ENUM, "Remove,MakeStatic,KeepActive"), "set_disable_mode", "get_disable_mode"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "disable_mode", PROPERTY_HINT_ENUM, "Remove,Make Static,Keep Active"), "set_disable_mode", "get_disable_mode"); ADD_GROUP("Collision", "collision_"); ADD_PROPERTY(PropertyInfo(Variant::INT, "collision_layer", PROPERTY_HINT_LAYERS_3D_PHYSICS), "set_collision_layer", "get_collision_layer"); diff --git a/scene/3d/collision_object_3d.h b/scene/3d/collision_object_3d.h index e92843d784..098f573551 100644 --- a/scene/3d/collision_object_3d.h +++ b/scene/3d/collision_object_3d.h @@ -71,14 +71,14 @@ private: int total_subshapes = 0; - Map<uint32_t, ShapeData> shapes; + RBMap<uint32_t, ShapeData> shapes; bool only_update_transform_changes = false; // This is used for sync to physics. bool capture_input_on_drag = false; bool ray_pickable = true; - Set<uint32_t> debug_shapes_to_update; + HashSet<uint32_t> debug_shapes_to_update; int debug_shapes_count = 0; Transform3D debug_shape_old_transform; diff --git a/scene/3d/collision_polygon_3d.cpp b/scene/3d/collision_polygon_3d.cpp index 5a286d7b55..bd6a70e566 100644 --- a/scene/3d/collision_polygon_3d.cpp +++ b/scene/3d/collision_polygon_3d.cpp @@ -200,10 +200,10 @@ void CollisionPolygon3D::_bind_methods() { ClassDB::bind_method(D_METHOD("_is_editable_3d_polygon"), &CollisionPolygon3D::_is_editable_3d_polygon); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "depth"), "set_depth", "get_depth"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "depth", PROPERTY_HINT_NONE, "suffix:m"), "set_depth", "get_depth"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "disabled"), "set_disabled", "is_disabled"); ADD_PROPERTY(PropertyInfo(Variant::PACKED_VECTOR2_ARRAY, "polygon"), "set_polygon", "get_polygon"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "margin", PROPERTY_HINT_RANGE, "0.001,10,0.001"), "set_margin", "get_margin"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "margin", PROPERTY_HINT_RANGE, "0.001,10,0.001,suffix:m"), "set_margin", "get_margin"); } CollisionPolygon3D::CollisionPolygon3D() { diff --git a/scene/3d/collision_shape_3d.cpp b/scene/3d/collision_shape_3d.cpp index d28e11a2e9..759997de7b 100644 --- a/scene/3d/collision_shape_3d.cpp +++ b/scene/3d/collision_shape_3d.cpp @@ -110,7 +110,7 @@ void CollisionShape3D::_notification(int p_what) { } } -void CollisionShape3D::resource_changed(RES res) { +void CollisionShape3D::resource_changed(Ref<Resource> res) { update_gizmos(); } diff --git a/scene/3d/collision_shape_3d.h b/scene/3d/collision_shape_3d.h index fbcabf6529..5c32230942 100644 --- a/scene/3d/collision_shape_3d.h +++ b/scene/3d/collision_shape_3d.h @@ -43,7 +43,7 @@ class CollisionShape3D : public Node3D { uint32_t owner_id = 0; CollisionObject3D *parent = nullptr; - void resource_changed(RES res); + void resource_changed(Ref<Resource> res); bool disabled = false; protected: diff --git a/scene/3d/cpu_particles_3d.cpp b/scene/3d/cpu_particles_3d.cpp index 0befda4168..4df0c37ad6 100644 --- a/scene/3d/cpu_particles_3d.cpp +++ b/scene/3d/cpu_particles_3d.cpp @@ -164,6 +164,8 @@ void CPUParticles3D::set_mesh(const Ref<Mesh> &p_mesh) { } else { RS::get_singleton()->multimesh_set_mesh(multimesh, RID()); } + + update_configuration_warnings(); } Ref<Mesh> CPUParticles3D::get_mesh() const { @@ -187,7 +189,7 @@ bool CPUParticles3D::get_fractional_delta() const { } TypedArray<String> CPUParticles3D::get_configuration_warnings() const { - TypedArray<String> warnings = Node::get_configuration_warnings(); + TypedArray<String> warnings = GeometryInstance3D::get_configuration_warnings(); bool mesh_found = false; bool anim_material_found = false; @@ -266,6 +268,8 @@ void CPUParticles3D::set_param_min(Parameter p_param, real_t p_value) { if (parameters_min[p_param] > parameters_max[p_param]) { set_param_max(p_param, p_value); } + + update_configuration_warnings(); } real_t CPUParticles3D::get_param_min(Parameter p_param) const { @@ -276,10 +280,13 @@ real_t CPUParticles3D::get_param_min(Parameter p_param) const { void CPUParticles3D::set_param_max(Parameter p_param, real_t p_value) { ERR_FAIL_INDEX(p_param, PARAM_MAX); + parameters_max[p_param] = p_value; if (parameters_min[p_param] > parameters_max[p_param]) { set_param_min(p_param, p_value); } + + update_configuration_warnings(); } real_t CPUParticles3D::get_param_max(Parameter p_param) const { @@ -340,6 +347,8 @@ void CPUParticles3D::set_param_curve(Parameter p_param, const Ref<Curve> &p_curv default: { } } + + update_configuration_warnings(); } Ref<Curve> CPUParticles3D::get_param_curve(Parameter p_param) const { @@ -833,8 +842,8 @@ void CPUParticles3D::_particles_process(double p_delta) { Vector3 normal = emission_normals.get(random_idx); Vector2 normal_2d(normal.x, normal.y); Transform2D m2; - m2.set_axis(0, normal_2d); - m2.set_axis(1, normal_2d.orthogonal()); + m2.columns[0] = normal_2d; + m2.columns[1] = normal_2d.orthogonal(); Vector2 velocity_2d(p.velocity.x, p.velocity.y); velocity_2d = m2.basis_xform(velocity_2d); p.velocity.x = velocity_2d.x; @@ -845,9 +854,9 @@ void CPUParticles3D::_particles_process(double p_delta) { Vector3 tangent = v0.cross(normal).normalized(); Vector3 bitangent = tangent.cross(normal).normalized(); Basis m3; - m3.set_axis(0, tangent); - m3.set_axis(1, bitangent); - m3.set_axis(2, normal); + m3.set_column(0, tangent); + m3.set_column(1, bitangent); + m3.set_column(2, normal); p.velocity = m3.xform(p.velocity); } } @@ -1068,33 +1077,33 @@ void CPUParticles3D::_particles_process(double p_delta) { if (particle_flags[PARTICLE_FLAG_DISABLE_Z]) { if (particle_flags[PARTICLE_FLAG_ALIGN_Y_TO_VELOCITY]) { if (p.velocity.length() > 0.0) { - p.transform.basis.set_axis(1, p.velocity.normalized()); + p.transform.basis.set_column(1, p.velocity.normalized()); } else { - p.transform.basis.set_axis(1, p.transform.basis.get_axis(1)); + p.transform.basis.set_column(1, p.transform.basis.get_column(1)); } - p.transform.basis.set_axis(0, p.transform.basis.get_axis(1).cross(p.transform.basis.get_axis(2)).normalized()); - p.transform.basis.set_axis(2, Vector3(0, 0, 1)); + p.transform.basis.set_column(0, p.transform.basis.get_column(1).cross(p.transform.basis.get_column(2)).normalized()); + p.transform.basis.set_column(2, Vector3(0, 0, 1)); } else { - p.transform.basis.set_axis(0, Vector3(Math::cos(p.custom[0]), -Math::sin(p.custom[0]), 0.0)); - p.transform.basis.set_axis(1, Vector3(Math::sin(p.custom[0]), Math::cos(p.custom[0]), 0.0)); - p.transform.basis.set_axis(2, Vector3(0, 0, 1)); + p.transform.basis.set_column(0, Vector3(Math::cos(p.custom[0]), -Math::sin(p.custom[0]), 0.0)); + p.transform.basis.set_column(1, Vector3(Math::sin(p.custom[0]), Math::cos(p.custom[0]), 0.0)); + p.transform.basis.set_column(2, Vector3(0, 0, 1)); } } else { //orient particle Y towards velocity if (particle_flags[PARTICLE_FLAG_ALIGN_Y_TO_VELOCITY]) { if (p.velocity.length() > 0.0) { - p.transform.basis.set_axis(1, p.velocity.normalized()); + p.transform.basis.set_column(1, p.velocity.normalized()); } else { - p.transform.basis.set_axis(1, p.transform.basis.get_axis(1).normalized()); + p.transform.basis.set_column(1, p.transform.basis.get_column(1).normalized()); } - if (p.transform.basis.get_axis(1) == p.transform.basis.get_axis(0)) { - p.transform.basis.set_axis(0, p.transform.basis.get_axis(1).cross(p.transform.basis.get_axis(2)).normalized()); - p.transform.basis.set_axis(2, p.transform.basis.get_axis(0).cross(p.transform.basis.get_axis(1)).normalized()); + if (p.transform.basis.get_column(1) == p.transform.basis.get_column(0)) { + p.transform.basis.set_column(0, p.transform.basis.get_column(1).cross(p.transform.basis.get_column(2)).normalized()); + p.transform.basis.set_column(2, p.transform.basis.get_column(0).cross(p.transform.basis.get_column(1)).normalized()); } else { - p.transform.basis.set_axis(2, p.transform.basis.get_axis(0).cross(p.transform.basis.get_axis(1)).normalized()); - p.transform.basis.set_axis(0, p.transform.basis.get_axis(1).cross(p.transform.basis.get_axis(2)).normalized()); + p.transform.basis.set_column(2, p.transform.basis.get_column(0).cross(p.transform.basis.get_column(1)).normalized()); + p.transform.basis.set_column(0, p.transform.basis.get_column(1).cross(p.transform.basis.get_column(2)).normalized()); } } else { p.transform.basis.orthonormalize(); @@ -1159,7 +1168,7 @@ void CPUParticles3D::_update_particle_data_buffer() { ERR_FAIL_NULL(get_viewport()); Camera3D *c = get_viewport()->get_camera_3d(); if (c) { - Vector3 dir = c->get_global_transform().basis.get_axis(2); //far away to close + Vector3 dir = c->get_global_transform().basis.get_column(2); //far away to close if (local_coords) { // will look different from Particles in editor as this is based on the camera in the scenetree @@ -1187,17 +1196,17 @@ void CPUParticles3D::_update_particle_data_buffer() { } if (r[idx].active) { - ptr[0] = t.basis.elements[0][0]; - ptr[1] = t.basis.elements[0][1]; - ptr[2] = t.basis.elements[0][2]; + ptr[0] = t.basis.rows[0][0]; + ptr[1] = t.basis.rows[0][1]; + ptr[2] = t.basis.rows[0][2]; ptr[3] = t.origin.x; - ptr[4] = t.basis.elements[1][0]; - ptr[5] = t.basis.elements[1][1]; - ptr[6] = t.basis.elements[1][2]; + ptr[4] = t.basis.rows[1][0]; + ptr[5] = t.basis.rows[1][1]; + ptr[6] = t.basis.rows[1][2]; ptr[7] = t.origin.y; - ptr[8] = t.basis.elements[2][0]; - ptr[9] = t.basis.elements[2][1]; - ptr[10] = t.basis.elements[2][2]; + ptr[8] = t.basis.rows[2][0]; + ptr[9] = t.basis.rows[2][1]; + ptr[10] = t.basis.rows[2][2]; ptr[11] = t.origin.z; } else { memset(ptr, 0, sizeof(Transform3D)); @@ -1293,17 +1302,17 @@ void CPUParticles3D::_notification(int p_what) { Transform3D t = inv_emission_transform * r[i].transform; if (r[i].active) { - ptr[0] = t.basis.elements[0][0]; - ptr[1] = t.basis.elements[0][1]; - ptr[2] = t.basis.elements[0][2]; + ptr[0] = t.basis.rows[0][0]; + ptr[1] = t.basis.rows[0][1]; + ptr[2] = t.basis.rows[0][2]; ptr[3] = t.origin.x; - ptr[4] = t.basis.elements[1][0]; - ptr[5] = t.basis.elements[1][1]; - ptr[6] = t.basis.elements[1][2]; + ptr[4] = t.basis.rows[1][0]; + ptr[5] = t.basis.rows[1][1]; + ptr[6] = t.basis.rows[1][2]; ptr[7] = t.origin.y; - ptr[8] = t.basis.elements[2][0]; - ptr[9] = t.basis.elements[2][1]; - ptr[10] = t.basis.elements[2][2]; + ptr[8] = t.basis.rows[2][0]; + ptr[9] = t.basis.rows[2][1]; + ptr[10] = t.basis.rows[2][2]; ptr[11] = t.origin.z; } else { memset(ptr, 0, sizeof(float) * 12); @@ -1439,14 +1448,14 @@ void CPUParticles3D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "emitting"), "set_emitting", "is_emitting"); ADD_PROPERTY(PropertyInfo(Variant::INT, "amount", PROPERTY_HINT_RANGE, "1,1000000,1,exp"), "set_amount", "get_amount"); ADD_GROUP("Time", ""); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "lifetime", PROPERTY_HINT_RANGE, "0.01,600.0,0.01,or_greater,exp"), "set_lifetime", "get_lifetime"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "lifetime", PROPERTY_HINT_RANGE, "0.01,600.0,0.01,or_greater,exp,suffix:s"), "set_lifetime", "get_lifetime"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "one_shot"), "set_one_shot", "get_one_shot"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "preprocess", PROPERTY_HINT_RANGE, "0.00,600.0,0.01,exp"), "set_pre_process_time", "get_pre_process_time"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "preprocess", PROPERTY_HINT_RANGE, "0.00,600.0,0.01,exp,suffix:s"), "set_pre_process_time", "get_pre_process_time"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "speed_scale", PROPERTY_HINT_RANGE, "0,64,0.01"), "set_speed_scale", "get_speed_scale"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "explosiveness", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_explosiveness_ratio", "get_explosiveness_ratio"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "randomness", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_randomness_ratio", "get_randomness_ratio"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "lifetime_randomness", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_lifetime_randomness", "get_lifetime_randomness"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "fixed_fps", PROPERTY_HINT_RANGE, "0,1000,1"), "set_fixed_fps", "get_fixed_fps"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "fixed_fps", PROPERTY_HINT_RANGE, "0,1000,1,suffix:FPS"), "set_fixed_fps", "get_fixed_fps"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "fract_delta"), "set_fractional_delta", "get_fractional_delta"); ADD_GROUP("Drawing", ""); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "local_coords"), "set_use_local_coordinates", "get_use_local_coordinates"); diff --git a/scene/3d/cpu_particles_3d.h b/scene/3d/cpu_particles_3d.h index 521b6c615e..7f225ee98d 100644 --- a/scene/3d/cpu_particles_3d.h +++ b/scene/3d/cpu_particles_3d.h @@ -174,9 +174,9 @@ private: Vector<Color> emission_colors; int emission_point_count = 0; Vector3 emission_ring_axis; - real_t emission_ring_height; - real_t emission_ring_radius; - real_t emission_ring_inner_radius; + real_t emission_ring_height = 0.0; + real_t emission_ring_radius = 0.0; + real_t emission_ring_inner_radius = 0.0; Ref<Curve> scale_curve_x; Ref<Curve> scale_curve_y; diff --git a/scene/3d/decal.cpp b/scene/3d/decal.cpp index a50f75f127..01cab493ec 100644 --- a/scene/3d/decal.cpp +++ b/scene/3d/decal.cpp @@ -72,7 +72,7 @@ real_t Decal::get_albedo_mix() const { } void Decal::set_upper_fade(real_t p_fade) { - upper_fade = p_fade; + upper_fade = MAX(p_fade, 0.0); RS::get_singleton()->decal_set_fade(decal, upper_fade, lower_fade); } @@ -81,7 +81,7 @@ real_t Decal::get_upper_fade() const { } void Decal::set_lower_fade(real_t p_fade) { - lower_fade = p_fade; + lower_fade = MAX(p_fade, 0.0); RS::get_singleton()->decal_set_fade(decal, upper_fade, lower_fade); } @@ -214,7 +214,7 @@ void Decal::_bind_methods() { ClassDB::bind_method(D_METHOD("set_cull_mask", "mask"), &Decal::set_cull_mask); ClassDB::bind_method(D_METHOD("get_cull_mask"), &Decal::get_cull_mask); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "extents", PROPERTY_HINT_RANGE, "0,1024,0.001,or_greater"), "set_extents", "get_extents"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "extents", PROPERTY_HINT_RANGE, "0,1024,0.001,or_greater,suffix:m"), "set_extents", "get_extents"); ADD_GROUP("Textures", "texture_"); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "texture_albedo", PROPERTY_HINT_RESOURCE_TYPE, "Texture"), "set_texture", "get_texture", TEXTURE_ALBEDO); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "texture_normal", PROPERTY_HINT_RESOURCE_TYPE, "Texture"), "set_texture", "get_texture", TEXTURE_NORMAL); @@ -232,8 +232,8 @@ void Decal::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "lower_fade", PROPERTY_HINT_EXP_EASING, "attenuation"), "set_lower_fade", "get_lower_fade"); ADD_GROUP("Distance Fade", "distance_fade_"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "distance_fade_enabled"), "set_enable_distance_fade", "is_distance_fade_enabled"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "distance_fade_begin"), "set_distance_fade_begin", "get_distance_fade_begin"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "distance_fade_length"), "set_distance_fade_length", "get_distance_fade_length"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "distance_fade_begin", PROPERTY_HINT_NONE, "suffix:m"), "set_distance_fade_begin", "get_distance_fade_begin"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "distance_fade_length", PROPERTY_HINT_NONE, "suffix:m"), "set_distance_fade_length", "get_distance_fade_length"); ADD_GROUP("Cull Mask", ""); ADD_PROPERTY(PropertyInfo(Variant::INT, "cull_mask", PROPERTY_HINT_LAYERS_3D_RENDER), "set_cull_mask", "get_cull_mask"); diff --git a/scene/3d/fog_volume.cpp b/scene/3d/fog_volume.cpp index 8d05254a25..1b329143b6 100644 --- a/scene/3d/fog_volume.cpp +++ b/scene/3d/fog_volume.cpp @@ -40,8 +40,8 @@ void FogVolume::_bind_methods() { ClassDB::bind_method(D_METHOD("set_material", "material"), &FogVolume::set_material); ClassDB::bind_method(D_METHOD("get_material"), &FogVolume::get_material); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "extents", PROPERTY_HINT_RANGE, "0.01,1024,0.01,or_greater"), "set_extents", "get_extents"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "shape", PROPERTY_HINT_ENUM, "Ellipsoid,Box,World"), "set_shape", "get_shape"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "extents", PROPERTY_HINT_RANGE, "0.01,1024,0.01,or_greater,suffix:m"), "set_extents", "get_extents"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "shape", PROPERTY_HINT_ENUM, "Ellipsoid (Local),Cone (Local),Cylinder (Local),Box (Local),World (Global)"), "set_shape", "get_shape"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "material", PROPERTY_HINT_RESOURCE_TYPE, "FogMaterial,ShaderMaterial"), "set_material", "get_material"); } diff --git a/scene/3d/gpu_particles_3d.cpp b/scene/3d/gpu_particles_3d.cpp index 33ce9fc6fe..b352114c7f 100644 --- a/scene/3d/gpu_particles_3d.cpp +++ b/scene/3d/gpu_particles_3d.cpp @@ -270,7 +270,7 @@ bool GPUParticles3D::get_interpolate() const { } TypedArray<String> GPUParticles3D::get_configuration_warnings() const { - TypedArray<String> warnings = Node::get_configuration_warnings(); + TypedArray<String> warnings = GeometryInstance3D::get_configuration_warnings(); if (RenderingServer::get_singleton()->is_low_end()) { warnings.push_back(RTR("GPU-based particles are not supported by the OpenGL video driver.\nUse the CPUParticles3D node instead. You can use the \"Convert to CPUParticles3D\" option for this purpose.")); @@ -567,25 +567,25 @@ void GPUParticles3D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::INT, "amount", PROPERTY_HINT_RANGE, "1,1000000,1,exp"), "set_amount", "get_amount"); ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "sub_emitter", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "GPUParticles3D"), "set_sub_emitter", "get_sub_emitter"); ADD_GROUP("Time", ""); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "lifetime", PROPERTY_HINT_RANGE, "0.01,600.0,0.01,or_greater,exp"), "set_lifetime", "get_lifetime"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "lifetime", PROPERTY_HINT_RANGE, "0.01,600.0,0.01,or_greater,exp,suffix:s"), "set_lifetime", "get_lifetime"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "one_shot"), "set_one_shot", "get_one_shot"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "preprocess", PROPERTY_HINT_RANGE, "0.00,600.0,0.01,exp"), "set_pre_process_time", "get_pre_process_time"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "preprocess", PROPERTY_HINT_RANGE, "0.00,600.0,0.01,exp,suffix:s"), "set_pre_process_time", "get_pre_process_time"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "speed_scale", PROPERTY_HINT_RANGE, "0,64,0.01"), "set_speed_scale", "get_speed_scale"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "explosiveness", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_explosiveness_ratio", "get_explosiveness_ratio"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "randomness", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_randomness_ratio", "get_randomness_ratio"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "fixed_fps", PROPERTY_HINT_RANGE, "0,1000,1"), "set_fixed_fps", "get_fixed_fps"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "fixed_fps", PROPERTY_HINT_RANGE, "0,1000,1,suffix:FPS"), "set_fixed_fps", "get_fixed_fps"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "interpolate"), "set_interpolate", "get_interpolate"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "fract_delta"), "set_fractional_delta", "get_fractional_delta"); ADD_GROUP("Collision", "collision_"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "collision_base_size", PROPERTY_HINT_RANGE, "0,128,0.01,or_greater"), "set_collision_base_size", "get_collision_base_size"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "collision_base_size", PROPERTY_HINT_RANGE, "0,128,0.01,or_greater,suffix:m"), "set_collision_base_size", "get_collision_base_size"); ADD_GROUP("Drawing", ""); - ADD_PROPERTY(PropertyInfo(Variant::AABB, "visibility_aabb"), "set_visibility_aabb", "get_visibility_aabb"); + ADD_PROPERTY(PropertyInfo(Variant::AABB, "visibility_aabb", PROPERTY_HINT_NONE, "suffix:m"), "set_visibility_aabb", "get_visibility_aabb"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "local_coords"), "set_use_local_coordinates", "get_use_local_coordinates"); ADD_PROPERTY(PropertyInfo(Variant::INT, "draw_order", PROPERTY_HINT_ENUM, "Index,Lifetime,Reverse Lifetime,View Depth"), "set_draw_order", "get_draw_order"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "transform_align", PROPERTY_HINT_ENUM, "Disabled,ZBillboard,YToVelocity,ZBillboardYToVelocity"), "set_transform_align", "get_transform_align"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "transform_align", PROPERTY_HINT_ENUM, "Disabled,Z-Billboard,Y to Velocity,Z-Billboard + Y to Velocity"), "set_transform_align", "get_transform_align"); ADD_GROUP("Trails", "trail_"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "trail_enabled"), "set_trail_enabled", "is_trail_enabled"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "trail_length_secs", PROPERTY_HINT_RANGE, "0.01,10,0.01"), "set_trail_length", "get_trail_length"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "trail_length_secs", PROPERTY_HINT_RANGE, "0.01,10,0.01,suffix:s"), "set_trail_length", "get_trail_length"); ADD_GROUP("Process Material", ""); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "process_material", PROPERTY_HINT_RESOURCE_TYPE, "ShaderMaterial,ParticlesMaterial"), "set_process_material", "get_process_material"); ADD_GROUP("Draw Passes", "draw_"); diff --git a/scene/3d/gpu_particles_3d.h b/scene/3d/gpu_particles_3d.h index f3eb52d124..adce45a0a9 100644 --- a/scene/3d/gpu_particles_3d.h +++ b/scene/3d/gpu_particles_3d.h @@ -61,16 +61,16 @@ private: RID particles; bool one_shot; - int amount; - double lifetime; - double pre_process_time; - real_t explosiveness_ratio; - real_t randomness_ratio; - double speed_scale; + int amount = 0; + double lifetime = 0.0; + double pre_process_time = 0.0; + real_t explosiveness_ratio = 0.0; + real_t randomness_ratio = 0.0; + double speed_scale = 0.0; AABB visibility_aabb; - bool local_coords; - int fixed_fps; - bool fractional_delta; + bool local_coords = false; + int fixed_fps = 0; + bool fractional_delta = false; bool interpolate = true; NodePath sub_emitter; real_t collision_base_size = 0.01; diff --git a/scene/3d/gpu_particles_collision_3d.cpp b/scene/3d/gpu_particles_collision_3d.cpp index 6f94df284a..da0789ccd5 100644 --- a/scene/3d/gpu_particles_collision_3d.cpp +++ b/scene/3d/gpu_particles_collision_3d.cpp @@ -66,7 +66,7 @@ void GPUParticlesCollisionSphere3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_radius", "radius"), &GPUParticlesCollisionSphere3D::set_radius); ClassDB::bind_method(D_METHOD("get_radius"), &GPUParticlesCollisionSphere3D::get_radius); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radius", PROPERTY_HINT_RANGE, "0.01,1024,0.01,or_greater"), "set_radius", "get_radius"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radius", PROPERTY_HINT_RANGE, "0.01,1024,0.01,or_greater,suffix:m"), "set_radius", "get_radius"); } void GPUParticlesCollisionSphere3D::set_radius(real_t p_radius) { @@ -96,7 +96,7 @@ void GPUParticlesCollisionBox3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_extents", "extents"), &GPUParticlesCollisionBox3D::set_extents); ClassDB::bind_method(D_METHOD("get_extents"), &GPUParticlesCollisionBox3D::get_extents); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "extents", PROPERTY_HINT_RANGE, "0.01,1024,0.01,or_greater"), "set_extents", "get_extents"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "extents", PROPERTY_HINT_RANGE, "0.01,1024,0.01,or_greater,suffix:m"), "set_extents", "get_extents"); } void GPUParticlesCollisionBox3D::set_extents(const Vector3 &p_extents) { @@ -514,9 +514,9 @@ void GPUParticlesCollisionSDF3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_thickness", "thickness"), &GPUParticlesCollisionSDF3D::set_thickness); ClassDB::bind_method(D_METHOD("get_thickness"), &GPUParticlesCollisionSDF3D::get_thickness); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "extents", PROPERTY_HINT_RANGE, "0.01,1024,0.01,or_greater"), "set_extents", "get_extents"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "resolution", PROPERTY_HINT_ENUM, "16,32,64,128,256,512"), "set_resolution", "get_resolution"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "thickness", PROPERTY_HINT_RANGE, "0.0,2.0,0.01"), "set_thickness", "get_thickness"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "extents", PROPERTY_HINT_RANGE, "0.01,1024,0.01,or_greater,suffix:m"), "set_extents", "get_extents"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "resolution", PROPERTY_HINT_ENUM, "16,32,64,128,256,512,suffix:px"), "set_resolution", "get_resolution"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "thickness", PROPERTY_HINT_RANGE, "0.0,2.0,0.01,suffix:m"), "set_thickness", "get_thickness"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture3D"), "set_texture", "get_texture"); BIND_ENUM_CONSTANT(RESOLUTION_16); @@ -594,8 +594,8 @@ void GPUParticlesCollisionHeightField3D::_notification(int p_what) { Camera3D *cam = get_viewport()->get_camera_3d(); if (cam) { Transform3D xform = get_global_transform(); - Vector3 x_axis = xform.basis.get_axis(Vector3::AXIS_X).normalized(); - Vector3 z_axis = xform.basis.get_axis(Vector3::AXIS_Z).normalized(); + Vector3 x_axis = xform.basis.get_column(Vector3::AXIS_X).normalized(); + Vector3 z_axis = xform.basis.get_column(Vector3::AXIS_Z).normalized(); float x_len = xform.basis.get_scale().x; float z_len = xform.basis.get_scale().z; @@ -640,18 +640,13 @@ void GPUParticlesCollisionHeightField3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_update_mode", "update_mode"), &GPUParticlesCollisionHeightField3D::set_update_mode); ClassDB::bind_method(D_METHOD("get_update_mode"), &GPUParticlesCollisionHeightField3D::get_update_mode); - ClassDB::bind_method(D_METHOD("set_follow_camera_mode", "enabled"), &GPUParticlesCollisionHeightField3D::set_follow_camera_mode); - ClassDB::bind_method(D_METHOD("is_follow_camera_mode_enabled"), &GPUParticlesCollisionHeightField3D::is_follow_camera_mode_enabled); + ClassDB::bind_method(D_METHOD("set_follow_camera_enabled", "enabled"), &GPUParticlesCollisionHeightField3D::set_follow_camera_enabled); + ClassDB::bind_method(D_METHOD("is_follow_camera_enabled"), &GPUParticlesCollisionHeightField3D::is_follow_camera_enabled); - ClassDB::bind_method(D_METHOD("set_follow_camera_push_ratio", "ratio"), &GPUParticlesCollisionHeightField3D::set_follow_camera_push_ratio); - ClassDB::bind_method(D_METHOD("get_follow_camera_push_ratio"), &GPUParticlesCollisionHeightField3D::get_follow_camera_push_ratio); - - ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "extents", PROPERTY_HINT_RANGE, "0.01,1024,0.01,or_greater"), "set_extents", "get_extents"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "resolution", PROPERTY_HINT_ENUM, "256,512,1024,2048,4096,8192"), "set_resolution", "get_resolution"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "update_mode", PROPERTY_HINT_ENUM, "WhenMoved,Always"), "set_update_mode", "get_update_mode"); - ADD_GROUP("Follow Camera", "follow_camera_"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "follow_camera_enabled"), "set_follow_camera_mode", "is_follow_camera_mode_enabled"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "follow_camera_push_ratio", PROPERTY_HINT_RANGE, "0.01,1,0.01"), "set_follow_camera_push_ratio", "get_follow_camera_push_ratio"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "extents", PROPERTY_HINT_RANGE, "0.01,1024,0.01,or_greater,suffix:m"), "set_extents", "get_extents"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "resolution", PROPERTY_HINT_ENUM, "256 (Fastest),512 (Fast),1024 (Average),2048 (Slow),4096 (Slower),8192 (Slowest)"), "set_resolution", "get_resolution"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "update_mode", PROPERTY_HINT_ENUM, "When Moved (Fast),Always (Slow)"), "set_update_mode", "get_update_mode"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "follow_camera_enabled"), "set_follow_camera_enabled", "is_follow_camera_enabled"); BIND_ENUM_CONSTANT(RESOLUTION_256); BIND_ENUM_CONSTANT(RESOLUTION_512); @@ -665,14 +660,6 @@ void GPUParticlesCollisionHeightField3D::_bind_methods() { BIND_ENUM_CONSTANT(UPDATE_MODE_ALWAYS); } -void GPUParticlesCollisionHeightField3D::set_follow_camera_push_ratio(float p_follow_camera_push_ratio) { - follow_camera_push_ratio = p_follow_camera_push_ratio; -} - -float GPUParticlesCollisionHeightField3D::get_follow_camera_push_ratio() const { - return follow_camera_push_ratio; -} - void GPUParticlesCollisionHeightField3D::set_extents(const Vector3 &p_extents) { extents = p_extents; RS::get_singleton()->particles_collision_set_box_extents(_get_collision(), extents); @@ -704,12 +691,12 @@ GPUParticlesCollisionHeightField3D::UpdateMode GPUParticlesCollisionHeightField3 return update_mode; } -void GPUParticlesCollisionHeightField3D::set_follow_camera_mode(bool p_enabled) { +void GPUParticlesCollisionHeightField3D::set_follow_camera_enabled(bool p_enabled) { follow_camera_mode = p_enabled; set_process_internal(follow_camera_mode || update_mode == UPDATE_MODE_ALWAYS); } -bool GPUParticlesCollisionHeightField3D::is_follow_camera_mode_enabled() const { +bool GPUParticlesCollisionHeightField3D::is_follow_camera_enabled() const { return follow_camera_mode; } @@ -798,7 +785,7 @@ void GPUParticlesAttractorSphere3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_radius", "radius"), &GPUParticlesAttractorSphere3D::set_radius); ClassDB::bind_method(D_METHOD("get_radius"), &GPUParticlesAttractorSphere3D::get_radius); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radius", PROPERTY_HINT_RANGE, "0.01,1024,0.01,or_greater"), "set_radius", "get_radius"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radius", PROPERTY_HINT_RANGE, "0.01,1024,0.01,or_greater,suffix:m"), "set_radius", "get_radius"); } void GPUParticlesAttractorSphere3D::set_radius(real_t p_radius) { @@ -828,7 +815,7 @@ void GPUParticlesAttractorBox3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_extents", "extents"), &GPUParticlesAttractorBox3D::set_extents); ClassDB::bind_method(D_METHOD("get_extents"), &GPUParticlesAttractorBox3D::get_extents); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "extents", PROPERTY_HINT_RANGE, "0.01,1024,0.01,or_greater"), "set_extents", "get_extents"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "extents", PROPERTY_HINT_RANGE, "0.01,1024,0.01,or_greater,suffix:m"), "set_extents", "get_extents"); } void GPUParticlesAttractorBox3D::set_extents(const Vector3 &p_extents) { @@ -861,7 +848,7 @@ void GPUParticlesAttractorVectorField3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_texture", "texture"), &GPUParticlesAttractorVectorField3D::set_texture); ClassDB::bind_method(D_METHOD("get_texture"), &GPUParticlesAttractorVectorField3D::get_texture); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "extents", PROPERTY_HINT_RANGE, "0.01,1024,0.01,or_greater"), "set_extents", "get_extents"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "extents", PROPERTY_HINT_RANGE, "0.01,1024,0.01,or_greater,suffix:m"), "set_extents", "get_extents"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture3D"), "set_texture", "get_texture"); } diff --git a/scene/3d/gpu_particles_collision_3d.h b/scene/3d/gpu_particles_collision_3d.h index fdd2fa4b18..4b2cb930fa 100644 --- a/scene/3d/gpu_particles_collision_3d.h +++ b/scene/3d/gpu_particles_collision_3d.h @@ -211,7 +211,6 @@ private: Vector3 extents = Vector3(1, 1, 1); Resolution resolution = RESOLUTION_1024; bool follow_camera_mode = false; - float follow_camera_push_ratio = 0.1; UpdateMode update_mode = UPDATE_MODE_WHEN_MOVED; @@ -229,11 +228,8 @@ public: void set_update_mode(UpdateMode p_update_mode); UpdateMode get_update_mode() const; - void set_follow_camera_mode(bool p_enabled); - bool is_follow_camera_mode_enabled() const; - - void set_follow_camera_push_ratio(float p_ratio); - float get_follow_camera_push_ratio() const; + void set_follow_camera_enabled(bool p_enabled); + bool is_follow_camera_enabled() const; virtual AABB get_aabb() const override; diff --git a/scene/3d/joint_3d.cpp b/scene/3d/joint_3d.cpp index c22e3f6d91..0b824ef28b 100644 --- a/scene/3d/joint_3d.cpp +++ b/scene/3d/joint_3d.cpp @@ -49,6 +49,7 @@ void Joint3D::_disconnect_signals() { void Joint3D::_body_exit_tree() { _disconnect_signals(); _update_joint(true); + update_configuration_warnings(); } void Joint3D::_update_joint(bool p_only_free) { @@ -65,7 +66,6 @@ void Joint3D::_update_joint(bool p_only_free) { if (p_only_free || !is_inside_tree()) { PhysicsServer3D::get_singleton()->joint_clear(joint); warning = String(); - update_configuration_warnings(); return; } @@ -331,7 +331,7 @@ void HingeJoint3D::_bind_methods() { ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angular_limit/relaxation", PROPERTY_HINT_RANGE, "0.01,16,0.01"), "set_param", "get_param", PARAM_LIMIT_RELAXATION); ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "motor/enable"), "set_flag", "get_flag", FLAG_ENABLE_MOTOR); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "motor/target_velocity", PROPERTY_HINT_RANGE, "-200,200,0.01,or_greater,or_lesser"), "set_param", "get_param", PARAM_MOTOR_TARGET_VELOCITY); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "motor/target_velocity", PROPERTY_HINT_RANGE, "-200,200,0.01,or_greater,or_lesser,suffix:m/s"), "set_param", "get_param", PARAM_MOTOR_TARGET_VELOCITY); ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "motor/max_impulse", PROPERTY_HINT_RANGE, "0.01,1024,0.01"), "set_param", "get_param", PARAM_MOTOR_MAX_IMPULSE); BIND_ENUM_CONSTANT(PARAM_BIAS); @@ -448,8 +448,8 @@ void SliderJoint3D::_bind_methods() { ClassDB::bind_method(D_METHOD("_set_lower_limit_angular", "lower_limit_angular"), &SliderJoint3D::_set_lower_limit_angular); ClassDB::bind_method(D_METHOD("_get_lower_limit_angular"), &SliderJoint3D::_get_lower_limit_angular); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "linear_limit/upper_distance", PROPERTY_HINT_RANGE, "-1024,1024,0.01"), "set_param", "get_param", PARAM_LINEAR_LIMIT_UPPER); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "linear_limit/lower_distance", PROPERTY_HINT_RANGE, "-1024,1024,0.01"), "set_param", "get_param", PARAM_LINEAR_LIMIT_LOWER); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "linear_limit/upper_distance", PROPERTY_HINT_RANGE, "-1024,1024,0.01,suffix:m"), "set_param", "get_param", PARAM_LINEAR_LIMIT_UPPER); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "linear_limit/lower_distance", PROPERTY_HINT_RANGE, "-1024,1024,0.01,suffix:m"), "set_param", "get_param", PARAM_LINEAR_LIMIT_LOWER); ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "linear_limit/softness", PROPERTY_HINT_RANGE, "0.01,16.0,0.01"), "set_param", "get_param", PARAM_LINEAR_LIMIT_SOFTNESS); ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "linear_limit/restitution", PROPERTY_HINT_RANGE, "0.01,16.0,0.01"), "set_param", "get_param", PARAM_LINEAR_LIMIT_RESTITUTION); ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "linear_limit/damping", PROPERTY_HINT_RANGE, "0,16.0,0.01"), "set_param", "get_param", PARAM_LINEAR_LIMIT_DAMPING); @@ -737,15 +737,18 @@ void Generic6DOFJoint3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_flag_z", "flag", "value"), &Generic6DOFJoint3D::set_flag_z); ClassDB::bind_method(D_METHOD("get_flag_z", "flag"), &Generic6DOFJoint3D::get_flag_z); + // X ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "linear_limit_x/enabled"), "set_flag_x", "get_flag_x", FLAG_ENABLE_LINEAR_LIMIT); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "linear_limit_x/upper_distance"), "set_param_x", "get_param_x", PARAM_LINEAR_UPPER_LIMIT); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "linear_limit_x/lower_distance"), "set_param_x", "get_param_x", PARAM_LINEAR_LOWER_LIMIT); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "linear_limit_x/upper_distance", PROPERTY_HINT_NONE, "suffix:m"), "set_param_x", "get_param_x", PARAM_LINEAR_UPPER_LIMIT); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "linear_limit_x/lower_distance", PROPERTY_HINT_NONE, "suffix:m"), "set_param_x", "get_param_x", PARAM_LINEAR_LOWER_LIMIT); ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "linear_limit_x/softness", PROPERTY_HINT_RANGE, "0.01,16,0.01"), "set_param_x", "get_param_x", PARAM_LINEAR_LIMIT_SOFTNESS); ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "linear_limit_x/restitution", PROPERTY_HINT_RANGE, "0.01,16,0.01"), "set_param_x", "get_param_x", PARAM_LINEAR_RESTITUTION); ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "linear_limit_x/damping", PROPERTY_HINT_RANGE, "0.01,16,0.01"), "set_param_x", "get_param_x", PARAM_LINEAR_DAMPING); + ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "linear_motor_x/enabled"), "set_flag_x", "get_flag_x", FLAG_ENABLE_LINEAR_MOTOR); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "linear_motor_x/target_velocity"), "set_param_x", "get_param_x", PARAM_LINEAR_MOTOR_TARGET_VELOCITY); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "linear_motor_x/target_velocity", PROPERTY_HINT_NONE, "suffix:m/s"), "set_param_x", "get_param_x", PARAM_LINEAR_MOTOR_TARGET_VELOCITY); ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "linear_motor_x/force_limit"), "set_param_x", "get_param_x", PARAM_LINEAR_MOTOR_FORCE_LIMIT); + ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "linear_spring_x/enabled"), "set_flag_x", "get_flag_x", FLAG_ENABLE_LINEAR_SPRING); ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "linear_spring_x/stiffness"), "set_param_x", "get_param_x", PARAM_LINEAR_SPRING_STIFFNESS); ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "linear_spring_x/damping"), "set_param_x", "get_param_x", PARAM_LINEAR_SPRING_DAMPING); @@ -759,56 +762,69 @@ void Generic6DOFJoint3D::_bind_methods() { ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angular_limit_x/damping", PROPERTY_HINT_RANGE, "0.01,16,0.01"), "set_param_x", "get_param_x", PARAM_ANGULAR_DAMPING); ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angular_limit_x/force_limit"), "set_param_x", "get_param_x", PARAM_ANGULAR_FORCE_LIMIT); ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angular_limit_x/erp"), "set_param_x", "get_param_x", PARAM_ANGULAR_ERP); + ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "angular_motor_x/enabled"), "set_flag_x", "get_flag_x", FLAG_ENABLE_MOTOR); ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angular_motor_x/target_velocity"), "set_param_x", "get_param_x", PARAM_ANGULAR_MOTOR_TARGET_VELOCITY); ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angular_motor_x/force_limit"), "set_param_x", "get_param_x", PARAM_ANGULAR_MOTOR_FORCE_LIMIT); + ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "angular_spring_x/enabled"), "set_flag_x", "get_flag_x", FLAG_ENABLE_ANGULAR_SPRING); ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angular_spring_x/stiffness"), "set_param_x", "get_param_x", PARAM_ANGULAR_SPRING_STIFFNESS); ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angular_spring_x/damping"), "set_param_x", "get_param_x", PARAM_ANGULAR_SPRING_DAMPING); ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angular_spring_x/equilibrium_point"), "set_param_x", "get_param_x", PARAM_ANGULAR_SPRING_EQUILIBRIUM_POINT); + // Y ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "linear_limit_y/enabled"), "set_flag_y", "get_flag_y", FLAG_ENABLE_LINEAR_LIMIT); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "linear_limit_y/upper_distance"), "set_param_y", "get_param_y", PARAM_LINEAR_UPPER_LIMIT); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "linear_limit_y/lower_distance"), "set_param_y", "get_param_y", PARAM_LINEAR_LOWER_LIMIT); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "linear_limit_y/upper_distance", PROPERTY_HINT_NONE, "suffix:m"), "set_param_y", "get_param_y", PARAM_LINEAR_UPPER_LIMIT); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "linear_limit_y/lower_distance", PROPERTY_HINT_NONE, "suffix:m"), "set_param_y", "get_param_y", PARAM_LINEAR_LOWER_LIMIT); ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "linear_limit_y/softness", PROPERTY_HINT_RANGE, "0.01,16,0.01"), "set_param_y", "get_param_y", PARAM_LINEAR_LIMIT_SOFTNESS); ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "linear_limit_y/restitution", PROPERTY_HINT_RANGE, "0.01,16,0.01"), "set_param_y", "get_param_y", PARAM_LINEAR_RESTITUTION); ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "linear_limit_y/damping", PROPERTY_HINT_RANGE, "0.01,16,0.01"), "set_param_y", "get_param_y", PARAM_LINEAR_DAMPING); + ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "linear_motor_y/enabled"), "set_flag_y", "get_flag_y", FLAG_ENABLE_LINEAR_MOTOR); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "linear_motor_y/target_velocity"), "set_param_y", "get_param_y", PARAM_LINEAR_MOTOR_TARGET_VELOCITY); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "linear_motor_y/target_velocity", PROPERTY_HINT_NONE, "suffix:m/s"), "set_param_y", "get_param_y", PARAM_LINEAR_MOTOR_TARGET_VELOCITY); ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "linear_motor_y/force_limit"), "set_param_y", "get_param_y", PARAM_LINEAR_MOTOR_FORCE_LIMIT); + ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "linear_spring_y/enabled"), "set_flag_y", "get_flag_y", FLAG_ENABLE_LINEAR_SPRING); ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "linear_spring_y/stiffness"), "set_param_y", "get_param_y", PARAM_LINEAR_SPRING_STIFFNESS); ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "linear_spring_y/damping"), "set_param_y", "get_param_y", PARAM_LINEAR_SPRING_DAMPING); ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "linear_spring_y/equilibrium_point"), "set_param_y", "get_param_y", PARAM_LINEAR_SPRING_EQUILIBRIUM_POINT); + ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "angular_limit_y/enabled"), "set_flag_y", "get_flag_y", FLAG_ENABLE_ANGULAR_LIMIT); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "angular_limit_y/upper_angle", PROPERTY_HINT_RANGE, "-180,180,0.01"), "_set_angular_hi_limit_y", "_get_angular_hi_limit_y"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "angular_limit_y/lower_angle", PROPERTY_HINT_RANGE, "-180,180,0.01"), "_set_angular_lo_limit_y", "_get_angular_lo_limit_y"); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angular_limit_y/softness", PROPERTY_HINT_RANGE, "0.01,16,0.01"), "set_param_y", "get_param_y", PARAM_ANGULAR_LIMIT_SOFTNESS); ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angular_limit_y/restitution", PROPERTY_HINT_RANGE, "0.01,16,0.01"), "set_param_y", "get_param_y", PARAM_ANGULAR_RESTITUTION); ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angular_limit_y/damping", PROPERTY_HINT_RANGE, "0.01,16,0.01"), "set_param_y", "get_param_y", PARAM_ANGULAR_DAMPING); ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angular_limit_y/force_limit"), "set_param_y", "get_param_y", PARAM_ANGULAR_FORCE_LIMIT); ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angular_limit_y/erp"), "set_param_y", "get_param_y", PARAM_ANGULAR_ERP); + ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "angular_motor_y/enabled"), "set_flag_y", "get_flag_y", FLAG_ENABLE_MOTOR); ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angular_motor_y/target_velocity"), "set_param_y", "get_param_y", PARAM_ANGULAR_MOTOR_TARGET_VELOCITY); ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angular_motor_y/force_limit"), "set_param_y", "get_param_y", PARAM_ANGULAR_MOTOR_FORCE_LIMIT); + ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "angular_spring_y/enabled"), "set_flag_y", "get_flag_y", FLAG_ENABLE_ANGULAR_SPRING); ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angular_spring_y/stiffness"), "set_param_y", "get_param_y", PARAM_ANGULAR_SPRING_STIFFNESS); ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angular_spring_y/damping"), "set_param_y", "get_param_y", PARAM_ANGULAR_SPRING_DAMPING); ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angular_spring_y/equilibrium_point"), "set_param_y", "get_param_y", PARAM_ANGULAR_SPRING_EQUILIBRIUM_POINT); + // Z ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "linear_limit_z/enabled"), "set_flag_z", "get_flag_z", FLAG_ENABLE_LINEAR_LIMIT); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "linear_limit_z/upper_distance"), "set_param_z", "get_param_z", PARAM_LINEAR_UPPER_LIMIT); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "linear_limit_z/lower_distance"), "set_param_z", "get_param_z", PARAM_LINEAR_LOWER_LIMIT); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "linear_limit_z/upper_distance", PROPERTY_HINT_NONE, "suffix:m"), "set_param_z", "get_param_z", PARAM_LINEAR_UPPER_LIMIT); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "linear_limit_z/lower_distance", PROPERTY_HINT_NONE, "suffix:m"), "set_param_z", "get_param_z", PARAM_LINEAR_LOWER_LIMIT); ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "linear_limit_z/softness", PROPERTY_HINT_RANGE, "0.01,16,0.01"), "set_param_z", "get_param_z", PARAM_LINEAR_LIMIT_SOFTNESS); ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "linear_limit_z/restitution", PROPERTY_HINT_RANGE, "0.01,16,0.01"), "set_param_z", "get_param_z", PARAM_LINEAR_RESTITUTION); ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "linear_limit_z/damping", PROPERTY_HINT_RANGE, "0.01,16,0.01"), "set_param_z", "get_param_z", PARAM_LINEAR_DAMPING); + ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "linear_motor_z/enabled"), "set_flag_z", "get_flag_z", FLAG_ENABLE_LINEAR_MOTOR); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "linear_motor_z/target_velocity"), "set_param_z", "get_param_z", PARAM_LINEAR_MOTOR_TARGET_VELOCITY); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "linear_motor_z/target_velocity", PROPERTY_HINT_NONE, "suffix:m/s"), "set_param_z", "get_param_z", PARAM_LINEAR_MOTOR_TARGET_VELOCITY); ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "linear_motor_z/force_limit"), "set_param_z", "get_param_z", PARAM_LINEAR_MOTOR_FORCE_LIMIT); + ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "linear_spring_z/enabled"), "set_flag_z", "get_flag_z", FLAG_ENABLE_LINEAR_SPRING); ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "linear_spring_z/stiffness"), "set_param_z", "get_param_z", PARAM_LINEAR_SPRING_STIFFNESS); ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "linear_spring_z/damping"), "set_param_z", "get_param_z", PARAM_LINEAR_SPRING_DAMPING); ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "linear_spring_z/equilibrium_point"), "set_param_z", "get_param_z", PARAM_LINEAR_SPRING_EQUILIBRIUM_POINT); + ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "angular_limit_z/enabled"), "set_flag_z", "get_flag_z", FLAG_ENABLE_ANGULAR_LIMIT); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "angular_limit_z/upper_angle", PROPERTY_HINT_RANGE, "-180,180,0.01"), "_set_angular_hi_limit_z", "_get_angular_hi_limit_z"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "angular_limit_z/lower_angle", PROPERTY_HINT_RANGE, "-180,180,0.01"), "_set_angular_lo_limit_z", "_get_angular_lo_limit_z"); @@ -817,9 +833,11 @@ void Generic6DOFJoint3D::_bind_methods() { ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angular_limit_z/damping", PROPERTY_HINT_RANGE, "0.01,16,0.01"), "set_param_z", "get_param_z", PARAM_ANGULAR_DAMPING); ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angular_limit_z/force_limit"), "set_param_z", "get_param_z", PARAM_ANGULAR_FORCE_LIMIT); ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angular_limit_z/erp"), "set_param_z", "get_param_z", PARAM_ANGULAR_ERP); + ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "angular_motor_z/enabled"), "set_flag_z", "get_flag_z", FLAG_ENABLE_MOTOR); ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angular_motor_z/target_velocity"), "set_param_z", "get_param_z", PARAM_ANGULAR_MOTOR_TARGET_VELOCITY); ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angular_motor_z/force_limit"), "set_param_z", "get_param_z", PARAM_ANGULAR_MOTOR_FORCE_LIMIT); + ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "angular_spring_z/enabled"), "set_flag_z", "get_flag_z", FLAG_ENABLE_ANGULAR_SPRING); ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angular_spring_z/stiffness"), "set_param_z", "get_param_z", PARAM_ANGULAR_SPRING_STIFFNESS); ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angular_spring_z/damping"), "set_param_z", "get_param_z", PARAM_ANGULAR_SPRING_DAMPING); diff --git a/scene/3d/label_3d.cpp b/scene/3d/label_3d.cpp new file mode 100644 index 0000000000..bc435c5451 --- /dev/null +++ b/scene/3d/label_3d.cpp @@ -0,0 +1,947 @@ +/*************************************************************************/ +/* label_3d.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 "label_3d.h" + +#include "core/core_string_names.h" +#include "scene/resources/theme.h" +#include "scene/scene_string_names.h" + +void Label3D::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_horizontal_alignment", "alignment"), &Label3D::set_horizontal_alignment); + ClassDB::bind_method(D_METHOD("get_horizontal_alignment"), &Label3D::get_horizontal_alignment); + + ClassDB::bind_method(D_METHOD("set_vertical_alignment", "alignment"), &Label3D::set_vertical_alignment); + ClassDB::bind_method(D_METHOD("get_vertical_alignment"), &Label3D::get_vertical_alignment); + + ClassDB::bind_method(D_METHOD("set_modulate", "modulate"), &Label3D::set_modulate); + ClassDB::bind_method(D_METHOD("get_modulate"), &Label3D::get_modulate); + + ClassDB::bind_method(D_METHOD("set_outline_modulate", "modulate"), &Label3D::set_outline_modulate); + ClassDB::bind_method(D_METHOD("get_outline_modulate"), &Label3D::get_outline_modulate); + + ClassDB::bind_method(D_METHOD("set_text", "text"), &Label3D::set_text); + ClassDB::bind_method(D_METHOD("get_text"), &Label3D::get_text); + + ClassDB::bind_method(D_METHOD("set_text_direction", "direction"), &Label3D::set_text_direction); + ClassDB::bind_method(D_METHOD("get_text_direction"), &Label3D::get_text_direction); + + ClassDB::bind_method(D_METHOD("set_language", "language"), &Label3D::set_language); + ClassDB::bind_method(D_METHOD("get_language"), &Label3D::get_language); + + ClassDB::bind_method(D_METHOD("set_structured_text_bidi_override", "parser"), &Label3D::set_structured_text_bidi_override); + ClassDB::bind_method(D_METHOD("get_structured_text_bidi_override"), &Label3D::get_structured_text_bidi_override); + + ClassDB::bind_method(D_METHOD("set_structured_text_bidi_override_options", "args"), &Label3D::set_structured_text_bidi_override_options); + ClassDB::bind_method(D_METHOD("get_structured_text_bidi_override_options"), &Label3D::get_structured_text_bidi_override_options); + + ClassDB::bind_method(D_METHOD("set_uppercase", "enable"), &Label3D::set_uppercase); + ClassDB::bind_method(D_METHOD("is_uppercase"), &Label3D::is_uppercase); + + ClassDB::bind_method(D_METHOD("set_render_priority", "priority"), &Label3D::set_render_priority); + ClassDB::bind_method(D_METHOD("get_render_priority"), &Label3D::get_render_priority); + + ClassDB::bind_method(D_METHOD("set_outline_render_priority", "priority"), &Label3D::set_outline_render_priority); + ClassDB::bind_method(D_METHOD("get_outline_render_priority"), &Label3D::get_outline_render_priority); + + ClassDB::bind_method(D_METHOD("set_font", "font"), &Label3D::set_font); + ClassDB::bind_method(D_METHOD("get_font"), &Label3D::get_font); + + ClassDB::bind_method(D_METHOD("set_font_size", "size"), &Label3D::set_font_size); + ClassDB::bind_method(D_METHOD("get_font_size"), &Label3D::get_font_size); + + ClassDB::bind_method(D_METHOD("set_outline_size", "outline_size"), &Label3D::set_outline_size); + ClassDB::bind_method(D_METHOD("get_outline_size"), &Label3D::get_outline_size); + + ClassDB::bind_method(D_METHOD("set_line_spacing", "line_spacing"), &Label3D::set_line_spacing); + ClassDB::bind_method(D_METHOD("get_line_spacing"), &Label3D::get_line_spacing); + + ClassDB::bind_method(D_METHOD("set_autowrap_mode", "autowrap_mode"), &Label3D::set_autowrap_mode); + ClassDB::bind_method(D_METHOD("get_autowrap_mode"), &Label3D::get_autowrap_mode); + + ClassDB::bind_method(D_METHOD("set_width", "width"), &Label3D::set_width); + ClassDB::bind_method(D_METHOD("get_width"), &Label3D::get_width); + + ClassDB::bind_method(D_METHOD("set_pixel_size", "pixel_size"), &Label3D::set_pixel_size); + ClassDB::bind_method(D_METHOD("get_pixel_size"), &Label3D::get_pixel_size); + + ClassDB::bind_method(D_METHOD("set_offset", "offset"), &Label3D::set_offset); + ClassDB::bind_method(D_METHOD("get_offset"), &Label3D::get_offset); + + ClassDB::bind_method(D_METHOD("set_draw_flag", "flag", "enabled"), &Label3D::set_draw_flag); + ClassDB::bind_method(D_METHOD("get_draw_flag", "flag"), &Label3D::get_draw_flag); + + ClassDB::bind_method(D_METHOD("set_billboard_mode", "mode"), &Label3D::set_billboard_mode); + ClassDB::bind_method(D_METHOD("get_billboard_mode"), &Label3D::get_billboard_mode); + + ClassDB::bind_method(D_METHOD("set_alpha_cut_mode", "mode"), &Label3D::set_alpha_cut_mode); + ClassDB::bind_method(D_METHOD("get_alpha_cut_mode"), &Label3D::get_alpha_cut_mode); + + ClassDB::bind_method(D_METHOD("set_alpha_scissor_threshold", "threshold"), &Label3D::set_alpha_scissor_threshold); + ClassDB::bind_method(D_METHOD("get_alpha_scissor_threshold"), &Label3D::get_alpha_scissor_threshold); + + ClassDB::bind_method(D_METHOD("set_texture_filter", "mode"), &Label3D::set_texture_filter); + ClassDB::bind_method(D_METHOD("get_texture_filter"), &Label3D::get_texture_filter); + + ClassDB::bind_method(D_METHOD("generate_triangle_mesh"), &Label3D::generate_triangle_mesh); + + ClassDB::bind_method(D_METHOD("_queue_update"), &Label3D::_queue_update); + ClassDB::bind_method(D_METHOD("_font_changed"), &Label3D::_font_changed); + ClassDB::bind_method(D_METHOD("_im_update"), &Label3D::_im_update); + + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "pixel_size", PROPERTY_HINT_RANGE, "0.0001,128,0.0001,suffix:m"), "set_pixel_size", "get_pixel_size"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "offset", PROPERTY_HINT_NONE, "suffix:px"), "set_offset", "get_offset"); + + ADD_GROUP("Flags", ""); + ADD_PROPERTY(PropertyInfo(Variant::INT, "billboard", PROPERTY_HINT_ENUM, "Disabled,Enabled,Y-Billboard"), "set_billboard_mode", "get_billboard_mode"); + ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "shaded"), "set_draw_flag", "get_draw_flag", FLAG_SHADED); + ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "double_sided"), "set_draw_flag", "get_draw_flag", FLAG_DOUBLE_SIDED); + ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "no_depth_test"), "set_draw_flag", "get_draw_flag", FLAG_DISABLE_DEPTH_TEST); + ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "fixed_size"), "set_draw_flag", "get_draw_flag", FLAG_FIXED_SIZE); + ADD_PROPERTY(PropertyInfo(Variant::INT, "alpha_cut", PROPERTY_HINT_ENUM, "Disabled,Discard,Opaque Pre-Pass"), "set_alpha_cut_mode", "get_alpha_cut_mode"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "alpha_scissor_threshold", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_alpha_scissor_threshold", "get_alpha_scissor_threshold"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "texture_filter", PROPERTY_HINT_ENUM, "Nearest,Linear,Nearest Mipmap,Linear Mipmap,Nearest Mipmap Anisotropic,Linear Mipmap Anisotropic"), "set_texture_filter", "get_texture_filter"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "render_priority", PROPERTY_HINT_RANGE, itos(RS::MATERIAL_RENDER_PRIORITY_MIN) + "," + itos(RS::MATERIAL_RENDER_PRIORITY_MAX) + ",1"), "set_render_priority", "get_render_priority"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "outline_render_priority", PROPERTY_HINT_RANGE, itos(RS::MATERIAL_RENDER_PRIORITY_MIN) + "," + itos(RS::MATERIAL_RENDER_PRIORITY_MAX) + ",1"), "set_outline_render_priority", "get_outline_render_priority"); + + ADD_GROUP("Text", ""); + ADD_PROPERTY(PropertyInfo(Variant::COLOR, "modulate"), "set_modulate", "get_modulate"); + ADD_PROPERTY(PropertyInfo(Variant::COLOR, "outline_modulate"), "set_outline_modulate", "get_outline_modulate"); + ADD_PROPERTY(PropertyInfo(Variant::STRING, "text", PROPERTY_HINT_MULTILINE_TEXT, ""), "set_text", "get_text"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "font", PROPERTY_HINT_RESOURCE_TYPE, "Font"), "set_font", "get_font"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "font_size", PROPERTY_HINT_RANGE, "1,256,1,or_greater,suffix:px"), "set_font_size", "get_font_size"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "outline_size", PROPERTY_HINT_RANGE, "0,127,1,suffix:px"), "set_outline_size", "get_outline_size"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "horizontal_alignment", PROPERTY_HINT_ENUM, "Left,Center,Right,Fill"), "set_horizontal_alignment", "get_horizontal_alignment"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "vertical_alignment", PROPERTY_HINT_ENUM, "Top,Center,Bottom"), "set_vertical_alignment", "get_vertical_alignment"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "uppercase"), "set_uppercase", "is_uppercase"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "line_spacing", PROPERTY_HINT_NONE, "suffix:px"), "set_line_spacing", "get_line_spacing"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "autowrap_mode", PROPERTY_HINT_ENUM, "Off,Arbitrary,Word,Word (Smart)"), "set_autowrap_mode", "get_autowrap_mode"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "width", PROPERTY_HINT_NONE, "suffix:px"), "set_width", "get_width"); + + ADD_GROUP("BiDi", ""); + ADD_PROPERTY(PropertyInfo(Variant::INT, "text_direction", PROPERTY_HINT_ENUM, "Auto,Left-to-Right,Right-to-Left"), "set_text_direction", "get_text_direction"); + ADD_PROPERTY(PropertyInfo(Variant::STRING, "language", PROPERTY_HINT_LOCALE_ID, ""), "set_language", "get_language"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "structured_text_bidi_override", PROPERTY_HINT_ENUM, "Default,URI,File,Email,List,None,Custom"), "set_structured_text_bidi_override", "get_structured_text_bidi_override"); + ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "structured_text_bidi_override_options"), "set_structured_text_bidi_override_options", "get_structured_text_bidi_override_options"); + + BIND_ENUM_CONSTANT(FLAG_SHADED); + BIND_ENUM_CONSTANT(FLAG_DOUBLE_SIDED); + BIND_ENUM_CONSTANT(FLAG_DISABLE_DEPTH_TEST); + BIND_ENUM_CONSTANT(FLAG_FIXED_SIZE); + BIND_ENUM_CONSTANT(FLAG_MAX); + + BIND_ENUM_CONSTANT(ALPHA_CUT_DISABLED); + BIND_ENUM_CONSTANT(ALPHA_CUT_DISCARD); + BIND_ENUM_CONSTANT(ALPHA_CUT_OPAQUE_PREPASS); +} + +void Label3D::_validate_property(PropertyInfo &property) const { + if (property.name == "material_override" || property.name == "material_overlay") { + property.usage = PROPERTY_USAGE_NO_EDITOR; + } +} + +void Label3D::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: { + if (!pending_update) { + _im_update(); + } + } break; + case NOTIFICATION_TRANSLATION_CHANGED: { + String new_text = tr(text); + if (new_text == xl_text) { + return; // Nothing new. + } + xl_text = new_text; + dirty_text = true; + _queue_update(); + } break; + } +} + +void Label3D::_im_update() { + _shape(); + + triangle_mesh.unref(); + update_gizmos(); + + pending_update = false; +} + +void Label3D::_queue_update() { + if (pending_update) { + return; + } + + pending_update = true; + call_deferred(SceneStringNames::get_singleton()->_im_update); +} + +AABB Label3D::get_aabb() const { + return aabb; +} + +Ref<TriangleMesh> Label3D::generate_triangle_mesh() const { + if (triangle_mesh.is_valid()) { + return triangle_mesh; + } + + Ref<Font> font = _get_font_or_default(); + if (font.is_null()) { + return Ref<TriangleMesh>(); + } + + Vector<Vector3> faces; + faces.resize(6); + Vector3 *facesw = faces.ptrw(); + + float total_h = 0.0; + float max_line_w = 0.0; + for (int i = 0; i < lines_rid.size(); i++) { + total_h += TS->shaped_text_get_size(lines_rid[i]).y + line_spacing; + max_line_w = MAX(max_line_w, TS->shaped_text_get_width(lines_rid[i])); + } + + float vbegin = 0; + switch (vertical_alignment) { + case VERTICAL_ALIGNMENT_FILL: + case VERTICAL_ALIGNMENT_TOP: { + // Nothing. + } break; + case VERTICAL_ALIGNMENT_CENTER: { + vbegin = (total_h - line_spacing) / 2.0; + } break; + case VERTICAL_ALIGNMENT_BOTTOM: { + vbegin = (total_h - line_spacing); + } break; + } + + Vector2 offset = Vector2(0, vbegin); + switch (horizontal_alignment) { + case HORIZONTAL_ALIGNMENT_LEFT: + break; + case HORIZONTAL_ALIGNMENT_FILL: + case HORIZONTAL_ALIGNMENT_CENTER: { + offset.x = -max_line_w / 2.0; + } break; + case HORIZONTAL_ALIGNMENT_RIGHT: { + offset.x = -max_line_w; + } break; + } + + Rect2 final_rect = Rect2(offset + lbl_offset, Size2(max_line_w, total_h)); + + if (final_rect.size.x == 0 || final_rect.size.y == 0) { + return Ref<TriangleMesh>(); + } + + real_t pixel_size = get_pixel_size(); + + Vector2 vertices[4] = { + + (final_rect.position + Vector2(0, -final_rect.size.y)) * pixel_size, + (final_rect.position + Vector2(final_rect.size.x, -final_rect.size.y)) * pixel_size, + (final_rect.position + Vector2(final_rect.size.x, 0)) * pixel_size, + final_rect.position * pixel_size, + + }; + + static const int indices[6] = { + 0, 1, 2, + 0, 2, 3 + }; + + for (int j = 0; j < 6; j++) { + int i = indices[j]; + Vector3 vtx; + vtx[0] = vertices[i][0]; + vtx[1] = vertices[i][1]; + facesw[j] = vtx; + } + + triangle_mesh = Ref<TriangleMesh>(memnew(TriangleMesh)); + triangle_mesh->create(faces); + + return triangle_mesh; +} + +void Label3D::_generate_glyph_surfaces(const Glyph &p_glyph, Vector2 &r_offset, const Color &p_modulate, int p_priority, int p_outline_size) { + for (int j = 0; j < p_glyph.repeat; j++) { + Vector2 gl_of; + Vector2 gl_sz; + Rect2 gl_uv; + Size2 texs; + RID tex; + + if (p_glyph.font_rid != RID()) { + tex = TS->font_get_glyph_texture_rid(p_glyph.font_rid, Vector2i(p_glyph.font_size, p_outline_size), p_glyph.index); + if (tex != RID()) { + gl_of = (TS->font_get_glyph_offset(p_glyph.font_rid, Vector2i(p_glyph.font_size, p_outline_size), p_glyph.index) + Vector2(p_glyph.x_off, p_glyph.y_off)) * pixel_size; + gl_sz = TS->font_get_glyph_size(p_glyph.font_rid, Vector2i(p_glyph.font_size, p_outline_size), p_glyph.index) * pixel_size; + gl_uv = TS->font_get_glyph_uv_rect(p_glyph.font_rid, Vector2i(p_glyph.font_size, p_outline_size), p_glyph.index); + texs = TS->font_get_glyph_texture_size(p_glyph.font_rid, Vector2i(p_glyph.font_size, p_outline_size), p_glyph.index); + } + } else { + gl_sz = TS->get_hex_code_box_size(p_glyph.font_size, p_glyph.index) * pixel_size; + gl_of = Vector2(0, -gl_sz.y); + } + + bool msdf = TS->font_is_multichannel_signed_distance_field(p_glyph.font_rid); + + SurfaceKey key = SurfaceKey(tex.get_id(), p_priority, p_outline_size); + if (!surfaces.has(key)) { + SurfaceData surf; + surf.material = RenderingServer::get_singleton()->material_create(); + // Set defaults for material, names need to match up those in StandardMaterial3D + RS::get_singleton()->material_set_param(surf.material, "albedo", Color(1, 1, 1, 1)); + RS::get_singleton()->material_set_param(surf.material, "specular", 0.5); + RS::get_singleton()->material_set_param(surf.material, "metallic", 0.0); + RS::get_singleton()->material_set_param(surf.material, "roughness", 1.0); + RS::get_singleton()->material_set_param(surf.material, "uv1_offset", Vector3(0, 0, 0)); + RS::get_singleton()->material_set_param(surf.material, "uv1_scale", Vector3(1, 1, 1)); + RS::get_singleton()->material_set_param(surf.material, "uv2_offset", Vector3(0, 0, 0)); + RS::get_singleton()->material_set_param(surf.material, "uv2_scale", Vector3(1, 1, 1)); + RS::get_singleton()->material_set_param(surf.material, "alpha_scissor_threshold", alpha_scissor_threshold); + if (msdf) { + RS::get_singleton()->material_set_param(surf.material, "msdf_pixel_range", TS->font_get_msdf_pixel_range(p_glyph.font_rid)); + RS::get_singleton()->material_set_param(surf.material, "msdf_outline_size", p_outline_size); + } + + RID shader_rid; + StandardMaterial3D::get_material_for_2d(get_draw_flag(FLAG_SHADED), true, get_draw_flag(FLAG_DOUBLE_SIDED), get_alpha_cut_mode() == ALPHA_CUT_DISCARD, get_alpha_cut_mode() == ALPHA_CUT_OPAQUE_PREPASS, get_billboard_mode() == StandardMaterial3D::BILLBOARD_ENABLED, get_billboard_mode() == StandardMaterial3D::BILLBOARD_FIXED_Y, msdf, get_draw_flag(FLAG_DISABLE_DEPTH_TEST), get_draw_flag(FLAG_FIXED_SIZE), texture_filter, &shader_rid); + + RS::get_singleton()->material_set_shader(surf.material, shader_rid); + RS::get_singleton()->material_set_param(surf.material, "texture_albedo", tex); + if (get_alpha_cut_mode() == ALPHA_CUT_DISABLED) { + RS::get_singleton()->material_set_render_priority(surf.material, p_priority); + } else { + surf.z_shift = p_priority * pixel_size; + } + + surfaces[key] = surf; + } + SurfaceData &s = surfaces[key]; + + s.mesh_vertices.resize((s.offset + 1) * 4); + s.mesh_normals.resize((s.offset + 1) * 4); + s.mesh_tangents.resize((s.offset + 1) * 16); + s.mesh_colors.resize((s.offset + 1) * 4); + s.mesh_uvs.resize((s.offset + 1) * 4); + + s.mesh_vertices.write[(s.offset * 4) + 3] = Vector3(r_offset.x + gl_of.x, r_offset.y - gl_of.y - gl_sz.y, s.z_shift); + s.mesh_vertices.write[(s.offset * 4) + 2] = Vector3(r_offset.x + gl_of.x + gl_sz.x, r_offset.y - gl_of.y - gl_sz.y, s.z_shift); + s.mesh_vertices.write[(s.offset * 4) + 1] = Vector3(r_offset.x + gl_of.x + gl_sz.x, r_offset.y - gl_of.y, s.z_shift); + s.mesh_vertices.write[(s.offset * 4) + 0] = Vector3(r_offset.x + gl_of.x, r_offset.y - gl_of.y, s.z_shift); + + for (int i = 0; i < 4; i++) { + s.mesh_normals.write[(s.offset * 4) + i] = Vector3(0.0, 0.0, 1.0); + s.mesh_tangents.write[(s.offset * 16) + (i * 4) + 0] = 1.0; + s.mesh_tangents.write[(s.offset * 16) + (i * 4) + 1] = 0.0; + s.mesh_tangents.write[(s.offset * 16) + (i * 4) + 2] = 0.0; + s.mesh_tangents.write[(s.offset * 16) + (i * 4) + 3] = 1.0; + s.mesh_colors.write[(s.offset * 4) + i] = p_modulate; + s.mesh_uvs.write[(s.offset * 4) + i] = Vector2(); + + if (aabb == AABB()) { + aabb.position = s.mesh_vertices[(s.offset * 4) + i]; + } else { + aabb.expand_to(s.mesh_vertices[(s.offset * 4) + i]); + } + } + + if (tex != RID()) { + s.mesh_uvs.write[(s.offset * 4) + 3] = Vector2(gl_uv.position.x / texs.x, (gl_uv.position.y + gl_uv.size.y) / texs.y); + s.mesh_uvs.write[(s.offset * 4) + 2] = Vector2((gl_uv.position.x + gl_uv.size.x) / texs.x, (gl_uv.position.y + gl_uv.size.y) / texs.y); + s.mesh_uvs.write[(s.offset * 4) + 1] = Vector2((gl_uv.position.x + gl_uv.size.x) / texs.x, gl_uv.position.y / texs.y); + s.mesh_uvs.write[(s.offset * 4) + 0] = Vector2(gl_uv.position.x / texs.x, gl_uv.position.y / texs.y); + } + + s.indices.resize((s.offset + 1) * 6); + s.indices.write[(s.offset * 6) + 0] = (s.offset * 4) + 0; + s.indices.write[(s.offset * 6) + 1] = (s.offset * 4) + 1; + s.indices.write[(s.offset * 6) + 2] = (s.offset * 4) + 2; + s.indices.write[(s.offset * 6) + 3] = (s.offset * 4) + 0; + s.indices.write[(s.offset * 6) + 4] = (s.offset * 4) + 2; + s.indices.write[(s.offset * 6) + 5] = (s.offset * 4) + 3; + + s.offset++; + r_offset.x += p_glyph.advance * pixel_size; + } +} + +void Label3D::_shape() { + // Clear mesh. + RS::get_singleton()->mesh_clear(mesh); + aabb = AABB(); + + // Clear materials. + for (const KeyValue<SurfaceKey, SurfaceData> &E : surfaces) { + RenderingServer::get_singleton()->free(E.value.material); + } + surfaces.clear(); + + Ref<Font> font = _get_font_or_default(); + ERR_FAIL_COND(font.is_null()); + + // Update text buffer. + if (dirty_text) { + TS->shaped_text_clear(text_rid); + TS->shaped_text_set_direction(text_rid, text_direction); + + String text = (uppercase) ? TS->string_to_upper(xl_text, language) : xl_text; + TS->shaped_text_add_string(text_rid, text, font->get_rids(), font_size, font->get_opentype_features(), language); + for (int i = 0; i < TextServer::SPACING_MAX; i++) { + TS->shaped_text_set_spacing(text_rid, TextServer::SpacingType(i), font->get_spacing(TextServer::SpacingType(i))); + } + + Array stt; + if (st_parser == TextServer::STRUCTURED_TEXT_CUSTOM) { + GDVIRTUAL_CALL(_structured_text_parser, st_args, text, stt); + } else { + stt = TS->parse_structured_text(st_parser, st_args, text); + } + TS->shaped_text_set_bidi_override(text_rid, stt); + + dirty_text = false; + dirty_font = false; + dirty_lines = true; + } else if (dirty_font) { + int spans = TS->shaped_get_span_count(text_rid); + for (int i = 0; i < spans; i++) { + TS->shaped_set_span_update_font(text_rid, i, font->get_rids(), font_size, font->get_opentype_features()); + } + for (int i = 0; i < TextServer::SPACING_MAX; i++) { + TS->shaped_text_set_spacing(text_rid, TextServer::SpacingType(i), font->get_spacing(TextServer::SpacingType(i))); + } + + dirty_font = false; + dirty_lines = true; + } + + if (dirty_lines) { + for (int i = 0; i < lines_rid.size(); i++) { + TS->free_rid(lines_rid[i]); + } + lines_rid.clear(); + + BitField<TextServer::LineBreakFlag> autowrap_flags = TextServer::BREAK_MANDATORY; + switch (autowrap_mode) { + case TextServer::AUTOWRAP_WORD_SMART: + autowrap_flags = TextServer::BREAK_WORD_BOUND | TextServer::BREAK_ADAPTIVE | TextServer::BREAK_MANDATORY; + break; + case TextServer::AUTOWRAP_WORD: + autowrap_flags = TextServer::BREAK_WORD_BOUND | TextServer::BREAK_MANDATORY; + break; + case TextServer::AUTOWRAP_ARBITRARY: + autowrap_flags = TextServer::BREAK_GRAPHEME_BOUND | TextServer::BREAK_MANDATORY; + break; + case TextServer::AUTOWRAP_OFF: + break; + } + PackedInt32Array line_breaks = TS->shaped_text_get_line_breaks(text_rid, width, 0, autowrap_flags); + + float max_line_w = 0.0; + for (int i = 0; i < line_breaks.size(); i = i + 2) { + RID line = TS->shaped_text_substr(text_rid, line_breaks[i], line_breaks[i + 1] - line_breaks[i]); + max_line_w = MAX(max_line_w, TS->shaped_text_get_width(line)); + lines_rid.push_back(line); + } + + if (horizontal_alignment == HORIZONTAL_ALIGNMENT_FILL) { + for (int i = 0; i < lines_rid.size() - 1; i++) { + TS->shaped_text_fit_to_width(lines_rid[i], (width > 0) ? width : max_line_w, TextServer::JUSTIFICATION_WORD_BOUND | TextServer::JUSTIFICATION_KASHIDA); + } + } + dirty_lines = false; + } + + // Generate surfaces and materials. + float total_h = 0.0; + for (int i = 0; i < lines_rid.size(); i++) { + total_h += (TS->shaped_text_get_size(lines_rid[i]).y + line_spacing) * pixel_size; + } + + float vbegin = 0.0; + switch (vertical_alignment) { + case VERTICAL_ALIGNMENT_FILL: + case VERTICAL_ALIGNMENT_TOP: { + // Nothing. + } break; + case VERTICAL_ALIGNMENT_CENTER: { + vbegin = (total_h - line_spacing * pixel_size) / 2.0; + } break; + case VERTICAL_ALIGNMENT_BOTTOM: { + vbegin = (total_h - line_spacing * pixel_size); + } break; + } + + Vector2 offset = Vector2(0, vbegin + lbl_offset.y * pixel_size); + for (int i = 0; i < lines_rid.size(); i++) { + const Glyph *glyphs = TS->shaped_text_get_glyphs(lines_rid[i]); + int gl_size = TS->shaped_text_get_glyph_count(lines_rid[i]); + float line_width = TS->shaped_text_get_width(lines_rid[i]) * pixel_size; + + switch (horizontal_alignment) { + case HORIZONTAL_ALIGNMENT_LEFT: + offset.x = 0.0; + break; + case HORIZONTAL_ALIGNMENT_FILL: + case HORIZONTAL_ALIGNMENT_CENTER: { + offset.x = -line_width / 2.0; + } break; + case HORIZONTAL_ALIGNMENT_RIGHT: { + offset.x = -line_width; + } break; + } + offset.x += lbl_offset.x * pixel_size; + offset.y -= TS->shaped_text_get_ascent(lines_rid[i]) * pixel_size; + + if (outline_modulate.a != 0.0 && outline_size > 0) { + // Outline surfaces. + Vector2 ol_offset = offset; + for (int j = 0; j < gl_size; j++) { + _generate_glyph_surfaces(glyphs[j], ol_offset, outline_modulate, outline_render_priority, outline_size); + } + } + + // Main text surfaces. + for (int j = 0; j < gl_size; j++) { + _generate_glyph_surfaces(glyphs[j], offset, modulate, render_priority); + } + offset.y -= (TS->shaped_text_get_descent(lines_rid[i]) + line_spacing) * pixel_size; + } + + for (const KeyValue<SurfaceKey, SurfaceData> &E : surfaces) { + Array mesh_array; + mesh_array.resize(RS::ARRAY_MAX); + mesh_array[RS::ARRAY_VERTEX] = E.value.mesh_vertices; + mesh_array[RS::ARRAY_NORMAL] = E.value.mesh_normals; + mesh_array[RS::ARRAY_TANGENT] = E.value.mesh_tangents; + mesh_array[RS::ARRAY_COLOR] = E.value.mesh_colors; + mesh_array[RS::ARRAY_TEX_UV] = E.value.mesh_uvs; + mesh_array[RS::ARRAY_INDEX] = E.value.indices; + + RS::SurfaceData sd; + RS::get_singleton()->mesh_create_surface_data_from_arrays(&sd, RS::PRIMITIVE_TRIANGLES, mesh_array); + + sd.material = E.value.material; + + RS::get_singleton()->mesh_add_surface(mesh, sd); + } +} + +void Label3D::set_text(const String &p_string) { + text = p_string; + xl_text = tr(p_string); + dirty_text = true; + _queue_update(); +} + +String Label3D::get_text() const { + return text; +} + +void Label3D::set_horizontal_alignment(HorizontalAlignment p_alignment) { + ERR_FAIL_INDEX((int)p_alignment, 4); + if (horizontal_alignment != p_alignment) { + if (horizontal_alignment == HORIZONTAL_ALIGNMENT_FILL || p_alignment == HORIZONTAL_ALIGNMENT_FILL) { + dirty_lines = true; // Reshape lines. + } + horizontal_alignment = p_alignment; + _queue_update(); + } +} + +HorizontalAlignment Label3D::get_horizontal_alignment() const { + return horizontal_alignment; +} + +void Label3D::set_vertical_alignment(VerticalAlignment p_alignment) { + ERR_FAIL_INDEX((int)p_alignment, 4); + if (vertical_alignment != p_alignment) { + vertical_alignment = p_alignment; + _queue_update(); + } +} + +VerticalAlignment Label3D::get_vertical_alignment() const { + return vertical_alignment; +} + +void Label3D::set_text_direction(TextServer::Direction p_text_direction) { + ERR_FAIL_COND((int)p_text_direction < -1 || (int)p_text_direction > 3); + if (text_direction != p_text_direction) { + text_direction = p_text_direction; + dirty_text = true; + _queue_update(); + } +} + +TextServer::Direction Label3D::get_text_direction() const { + return text_direction; +} + +void Label3D::set_language(const String &p_language) { + if (language != p_language) { + language = p_language; + dirty_text = true; + _queue_update(); + } +} + +String Label3D::get_language() const { + return language; +} + +void Label3D::set_structured_text_bidi_override(TextServer::StructuredTextParser p_parser) { + if (st_parser != p_parser) { + st_parser = p_parser; + dirty_text = true; + _queue_update(); + } +} + +TextServer::StructuredTextParser Label3D::get_structured_text_bidi_override() const { + return st_parser; +} + +void Label3D::set_structured_text_bidi_override_options(Array p_args) { + if (st_args != p_args) { + st_args = p_args; + dirty_text = true; + _queue_update(); + } +} + +Array Label3D::get_structured_text_bidi_override_options() const { + return st_args; +} + +void Label3D::set_uppercase(bool p_uppercase) { + if (uppercase != p_uppercase) { + uppercase = p_uppercase; + dirty_text = true; + _queue_update(); + } +} + +bool Label3D::is_uppercase() const { + return uppercase; +} + +void Label3D::set_render_priority(int p_priority) { + ERR_FAIL_COND(p_priority < RS::MATERIAL_RENDER_PRIORITY_MIN || p_priority > RS::MATERIAL_RENDER_PRIORITY_MAX); + if (render_priority != p_priority) { + render_priority = p_priority; + _queue_update(); + } +} + +int Label3D::get_render_priority() const { + return render_priority; +} + +void Label3D::set_outline_render_priority(int p_priority) { + ERR_FAIL_COND(p_priority < RS::MATERIAL_RENDER_PRIORITY_MIN || p_priority > RS::MATERIAL_RENDER_PRIORITY_MAX); + if (outline_render_priority != p_priority) { + outline_render_priority = p_priority; + _queue_update(); + } +} + +int Label3D::get_outline_render_priority() const { + return outline_render_priority; +} + +void Label3D::_font_changed() { + dirty_font = true; + _queue_update(); +} + +void Label3D::set_font(const Ref<Font> &p_font) { + if (font_override != p_font) { + if (font_override.is_valid()) { + font_override->disconnect(CoreStringNames::get_singleton()->changed, Callable(this, "_font_changed")); + } + font_override = p_font; + dirty_font = true; + if (font_override.is_valid()) { + font_override->connect(CoreStringNames::get_singleton()->changed, Callable(this, "_font_changed")); + } + _queue_update(); + } +} + +Ref<Font> Label3D::get_font() const { + return font_override; +} + +Ref<Font> Label3D::_get_font_or_default() const { + if (theme_font.is_valid()) { + theme_font->disconnect(CoreStringNames::get_singleton()->changed, Callable(const_cast<Label3D *>(this), "_font_changed")); + theme_font.unref(); + } + + if (font_override.is_valid()) { + return font_override; + } + + // Check the project-defined Theme resource. + if (Theme::get_project_default().is_valid()) { + List<StringName> theme_types; + Theme::get_project_default()->get_type_dependencies(get_class_name(), StringName(), &theme_types); + + for (const StringName &E : theme_types) { + if (Theme::get_project_default()->has_theme_item(Theme::DATA_TYPE_FONT, "font", E)) { + Ref<Font> f = Theme::get_project_default()->get_theme_item(Theme::DATA_TYPE_FONT, "font", E); + if (f.is_valid()) { + theme_font = f; + theme_font->connect(CoreStringNames::get_singleton()->changed, Callable(const_cast<Label3D *>(this), "_font_changed")); + } + return f; + } + } + } + + // Lastly, fall back on the items defined in the default Theme, if they exist. + { + List<StringName> theme_types; + Theme::get_default()->get_type_dependencies(get_class_name(), StringName(), &theme_types); + + for (const StringName &E : theme_types) { + if (Theme::get_default()->has_theme_item(Theme::DATA_TYPE_FONT, "font", E)) { + Ref<Font> f = Theme::get_default()->get_theme_item(Theme::DATA_TYPE_FONT, "font", E); + if (f.is_valid()) { + theme_font = f; + theme_font->connect(CoreStringNames::get_singleton()->changed, Callable(const_cast<Label3D *>(this), "_font_changed")); + } + return f; + } + } + } + + // If they don't exist, use any type to return the default/empty value. + Ref<Font> f = Theme::get_default()->get_theme_item(Theme::DATA_TYPE_FONT, "font", StringName()); + if (f.is_valid()) { + theme_font = f; + theme_font->connect(CoreStringNames::get_singleton()->changed, Callable(const_cast<Label3D *>(this), "_font_changed")); + } + return f; +} + +void Label3D::set_font_size(int p_size) { + if (font_size != p_size) { + font_size = p_size; + dirty_font = true; + _queue_update(); + } +} + +int Label3D::get_font_size() const { + return font_size; +} + +void Label3D::set_outline_size(int p_size) { + if (outline_size != p_size) { + outline_size = p_size; + _queue_update(); + } +} + +int Label3D::get_outline_size() const { + return outline_size; +} + +void Label3D::set_modulate(const Color &p_color) { + if (modulate != p_color) { + modulate = p_color; + _queue_update(); + } +} + +Color Label3D::get_modulate() const { + return modulate; +} + +void Label3D::set_outline_modulate(const Color &p_color) { + if (outline_modulate != p_color) { + outline_modulate = p_color; + _queue_update(); + } +} + +Color Label3D::get_outline_modulate() const { + return outline_modulate; +} + +void Label3D::set_autowrap_mode(TextServer::AutowrapMode p_mode) { + if (autowrap_mode != p_mode) { + autowrap_mode = p_mode; + dirty_lines = true; + _queue_update(); + } +} + +TextServer::AutowrapMode Label3D::get_autowrap_mode() const { + return autowrap_mode; +} + +void Label3D::set_width(float p_width) { + if (width != p_width) { + width = p_width; + dirty_lines = true; + _queue_update(); + } +} + +float Label3D::get_width() const { + return width; +} + +void Label3D::set_pixel_size(real_t p_amount) { + if (pixel_size != p_amount) { + pixel_size = p_amount; + _queue_update(); + } +} + +real_t Label3D::get_pixel_size() const { + return pixel_size; +} + +void Label3D::set_offset(const Point2 &p_offset) { + if (lbl_offset != p_offset) { + lbl_offset = p_offset; + _queue_update(); + } +} + +Point2 Label3D::get_offset() const { + return lbl_offset; +} + +void Label3D::set_line_spacing(float p_line_spacing) { + if (line_spacing != p_line_spacing) { + line_spacing = p_line_spacing; + _queue_update(); + } +} + +float Label3D::get_line_spacing() const { + return line_spacing; +} + +void Label3D::set_draw_flag(DrawFlags p_flag, bool p_enable) { + ERR_FAIL_INDEX(p_flag, FLAG_MAX); + if (flags[p_flag] != p_enable) { + flags[p_flag] = p_enable; + _queue_update(); + } +} + +bool Label3D::get_draw_flag(DrawFlags p_flag) const { + ERR_FAIL_INDEX_V(p_flag, FLAG_MAX, false); + return flags[p_flag]; +} + +void Label3D::set_billboard_mode(StandardMaterial3D::BillboardMode p_mode) { + ERR_FAIL_INDEX(p_mode, 3); + if (billboard_mode != p_mode) { + billboard_mode = p_mode; + _queue_update(); + } +} + +StandardMaterial3D::BillboardMode Label3D::get_billboard_mode() const { + return billboard_mode; +} + +void Label3D::set_alpha_cut_mode(AlphaCutMode p_mode) { + ERR_FAIL_INDEX(p_mode, 3); + if (alpha_cut != p_mode) { + alpha_cut = p_mode; + _queue_update(); + } +} + +void Label3D::set_texture_filter(StandardMaterial3D::TextureFilter p_filter) { + if (texture_filter != p_filter) { + texture_filter = p_filter; + _queue_update(); + } +} + +StandardMaterial3D::TextureFilter Label3D::get_texture_filter() const { + return texture_filter; +} + +Label3D::AlphaCutMode Label3D::get_alpha_cut_mode() const { + return alpha_cut; +} + +void Label3D::set_alpha_scissor_threshold(float p_threshold) { + if (alpha_scissor_threshold != p_threshold) { + alpha_scissor_threshold = p_threshold; + _queue_update(); + } +} + +float Label3D::get_alpha_scissor_threshold() const { + return alpha_scissor_threshold; +} + +Label3D::Label3D() { + for (int i = 0; i < FLAG_MAX; i++) { + flags[i] = (i == FLAG_DOUBLE_SIDED); + } + + text_rid = TS->create_shaped_text(); + + mesh = RenderingServer::get_singleton()->mesh_create(); + + set_cast_shadows_setting(SHADOW_CASTING_SETTING_OFF); + set_base(mesh); +} + +Label3D::~Label3D() { + for (int i = 0; i < lines_rid.size(); i++) { + TS->free_rid(lines_rid[i]); + } + lines_rid.clear(); + + TS->free_rid(text_rid); + + RenderingServer::get_singleton()->free(mesh); + for (KeyValue<SurfaceKey, SurfaceData> E : surfaces) { + RenderingServer::get_singleton()->free(E.value.material); + } + surfaces.clear(); +} diff --git a/scene/3d/label_3d.h b/scene/3d/label_3d.h new file mode 100644 index 0000000000..4498e89517 --- /dev/null +++ b/scene/3d/label_3d.h @@ -0,0 +1,247 @@ +/*************************************************************************/ +/* label_3d.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 LABEL_3D_H +#define LABEL_3D_H + +#include "scene/3d/visual_instance_3d.h" +#include "scene/resources/font.h" + +#include "servers/text_server.h" + +class Label3D : public GeometryInstance3D { + GDCLASS(Label3D, GeometryInstance3D); + +public: + enum DrawFlags { + FLAG_SHADED, + FLAG_DOUBLE_SIDED, + FLAG_DISABLE_DEPTH_TEST, + FLAG_FIXED_SIZE, + FLAG_MAX + }; + + enum AlphaCutMode { + ALPHA_CUT_DISABLED, + ALPHA_CUT_DISCARD, + ALPHA_CUT_OPAQUE_PREPASS + }; + +private: + real_t pixel_size = 0.01; + bool flags[FLAG_MAX] = {}; + AlphaCutMode alpha_cut = ALPHA_CUT_DISABLED; + float alpha_scissor_threshold = 0.5; + + AABB aabb; + + mutable Ref<TriangleMesh> triangle_mesh; + RID mesh; + struct SurfaceData { + PackedVector3Array mesh_vertices; + PackedVector3Array mesh_normals; + PackedFloat32Array mesh_tangents; + PackedColorArray mesh_colors; + PackedVector2Array mesh_uvs; + PackedInt32Array indices; + int offset = 0; + float z_shift = 0.0; + RID material; + }; + + struct SurfaceKey { + uint64_t texture_id; + int32_t priority; + int32_t outline_size; + + bool operator==(const SurfaceKey &p_b) const { + return (texture_id == p_b.texture_id) && (priority == p_b.priority) && (outline_size == p_b.outline_size); + } + + SurfaceKey(uint64_t p_texture_id, int p_priority, int p_outline_size) { + texture_id = p_texture_id; + priority = p_priority; + outline_size = p_outline_size; + } + }; + + struct SurfaceKeyHasher { + _FORCE_INLINE_ static uint32_t hash(const SurfaceKey &p_a) { + return hash_murmur3_buffer(&p_a, sizeof(SurfaceKey)); + } + }; + + HashMap<SurfaceKey, SurfaceData, SurfaceKeyHasher> surfaces; + + HorizontalAlignment horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER; + VerticalAlignment vertical_alignment = VERTICAL_ALIGNMENT_CENTER; + String text; + String xl_text; + bool uppercase = false; + + TextServer::AutowrapMode autowrap_mode = TextServer::AUTOWRAP_OFF; + float width = 500.0; + + int font_size = 16; + Ref<Font> font_override; + mutable Ref<Font> theme_font; + Color modulate = Color(1, 1, 1, 1); + Point2 lbl_offset; + int outline_render_priority = -1; + int render_priority = 0; + + int outline_size = 0; + Color outline_modulate = Color(0, 0, 0, 1); + + float line_spacing = 0.f; + + String language; + TextServer::Direction text_direction = TextServer::DIRECTION_AUTO; + TextServer::StructuredTextParser st_parser = TextServer::STRUCTURED_TEXT_DEFAULT; + Array st_args; + + RID text_rid; + Vector<RID> lines_rid; + + RID base_material; + StandardMaterial3D::BillboardMode billboard_mode = StandardMaterial3D::BILLBOARD_DISABLED; + StandardMaterial3D::TextureFilter texture_filter = StandardMaterial3D::TEXTURE_FILTER_LINEAR_WITH_MIPMAPS; + + bool pending_update = false; + + bool dirty_lines = true; + bool dirty_font = true; + bool dirty_text = true; + + void _generate_glyph_surfaces(const Glyph &p_glyph, Vector2 &r_offset, const Color &p_modulate, int p_priority = 0, int p_outline_size = 0); + +protected: + GDVIRTUAL2RC(Array, _structured_text_parser, Array, String) + + void _notification(int p_what); + + static void _bind_methods(); + + void _validate_property(PropertyInfo &property) const override; + + void _im_update(); + void _font_changed(); + void _queue_update(); + + void _shape(); + +public: + void set_horizontal_alignment(HorizontalAlignment p_alignment); + HorizontalAlignment get_horizontal_alignment() const; + + void set_vertical_alignment(VerticalAlignment p_alignment); + VerticalAlignment get_vertical_alignment() const; + + void set_render_priority(int p_priority); + int get_render_priority() const; + + void set_outline_render_priority(int p_priority); + int get_outline_render_priority() const; + + void set_text(const String &p_string); + String get_text() const; + + void set_text_direction(TextServer::Direction p_text_direction); + TextServer::Direction get_text_direction() const; + + void set_language(const String &p_language); + String get_language() const; + + void set_structured_text_bidi_override(TextServer::StructuredTextParser p_parser); + TextServer::StructuredTextParser get_structured_text_bidi_override() const; + + void set_structured_text_bidi_override_options(Array p_args); + Array get_structured_text_bidi_override_options() const; + + void set_uppercase(bool p_uppercase); + bool is_uppercase() const; + + void set_font(const Ref<Font> &p_font); + Ref<Font> get_font() const; + Ref<Font> _get_font_or_default() const; + + void set_font_size(int p_size); + int get_font_size() const; + + void set_outline_size(int p_size); + int get_outline_size() const; + + void set_line_spacing(float p_size); + float get_line_spacing() const; + + void set_modulate(const Color &p_color); + Color get_modulate() const; + + void set_outline_modulate(const Color &p_color); + Color get_outline_modulate() const; + + void set_autowrap_mode(TextServer::AutowrapMode p_mode); + TextServer::AutowrapMode get_autowrap_mode() const; + + void set_width(float p_width); + float get_width() const; + + void set_pixel_size(real_t p_amount); + real_t get_pixel_size() const; + + void set_offset(const Point2 &p_offset); + Point2 get_offset() const; + + void set_draw_flag(DrawFlags p_flag, bool p_enable); + bool get_draw_flag(DrawFlags p_flag) const; + + void set_alpha_cut_mode(AlphaCutMode p_mode); + AlphaCutMode get_alpha_cut_mode() const; + + void set_alpha_scissor_threshold(float p_threshold); + float get_alpha_scissor_threshold() const; + + void set_billboard_mode(StandardMaterial3D::BillboardMode p_mode); + StandardMaterial3D::BillboardMode get_billboard_mode() const; + + void set_texture_filter(StandardMaterial3D::TextureFilter p_filter); + StandardMaterial3D::TextureFilter get_texture_filter() const; + + virtual AABB get_aabb() const override; + Ref<TriangleMesh> generate_triangle_mesh() const; + + Label3D(); + ~Label3D(); +}; + +VARIANT_ENUM_CAST(Label3D::DrawFlags); +VARIANT_ENUM_CAST(Label3D::AlphaCutMode); + +#endif // LABEL_3D_H diff --git a/scene/3d/light_3d.cpp b/scene/3d/light_3d.cpp index c95806b2d0..6c999d85e2 100644 --- a/scene/3d/light_3d.cpp +++ b/scene/3d/light_3d.cpp @@ -176,6 +176,11 @@ Ref<Texture2D> Light3D::get_projector() const { return projector; } +void Light3D::owner_changed_notify() { + // For cases where owner changes _after_ entering tree (as example, editor editing). + _update_visibility(); +} + void Light3D::_update_visibility() { if (!is_inside_tree()) { return; @@ -277,13 +282,13 @@ void Light3D::_bind_methods() { ADD_GROUP("Light", "light_"); ADD_PROPERTY(PropertyInfo(Variant::COLOR, "light_color", PROPERTY_HINT_COLOR_NO_ALPHA), "set_color", "get_color"); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "light_energy", PROPERTY_HINT_RANGE, "0,16,0.01,or_greater"), "set_param", "get_param", PARAM_ENERGY); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "light_indirect_energy", PROPERTY_HINT_RANGE, "0,16,0.01,or_greater"), "set_param", "get_param", PARAM_INDIRECT_ENERGY); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "light_energy", PROPERTY_HINT_RANGE, "0,16,0.001,or_greater"), "set_param", "get_param", PARAM_ENERGY); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "light_indirect_energy", PROPERTY_HINT_RANGE, "0,16,0.001,or_greater"), "set_param", "get_param", PARAM_INDIRECT_ENERGY); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "light_projector", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_projector", "get_projector"); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "light_size", PROPERTY_HINT_RANGE, "0,1,0.01,or_greater"), "set_param", "get_param", PARAM_SIZE); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "light_angular_distance", PROPERTY_HINT_RANGE, "0,90,0.01"), "set_param", "get_param", PARAM_SIZE); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "light_size", PROPERTY_HINT_RANGE, "0,1,0.001,or_greater,suffix:m"), "set_param", "get_param", PARAM_SIZE); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "light_angular_distance", PROPERTY_HINT_RANGE, "0,90,0.01,degrees"), "set_param", "get_param", PARAM_SIZE); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "light_negative"), "set_negative", "is_negative"); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "light_specular", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param", "get_param", PARAM_SPECULAR); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "light_specular", PROPERTY_HINT_RANGE, "0,16,0.001,or_greater"), "set_param", "get_param", PARAM_SPECULAR); ADD_PROPERTY(PropertyInfo(Variant::INT, "light_bake_mode", PROPERTY_HINT_ENUM, "Disabled,Static (VoxelGI/SDFGI/LightmapGI),Dynamic (VoxelGI/SDFGI only)"), "set_bake_mode", "get_bake_mode"); ADD_PROPERTY(PropertyInfo(Variant::INT, "light_cull_mask", PROPERTY_HINT_LAYERS_3D_RENDER), "set_cull_mask", "get_cull_mask"); ADD_GROUP("Shadow", "shadow_"); @@ -291,14 +296,13 @@ void Light3D::_bind_methods() { ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "shadow_bias", PROPERTY_HINT_RANGE, "0,10,0.001"), "set_param", "get_param", PARAM_SHADOW_BIAS); ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "shadow_normal_bias", PROPERTY_HINT_RANGE, "0,10,0.001"), "set_param", "get_param", PARAM_SHADOW_NORMAL_BIAS); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "shadow_reverse_cull_face"), "set_shadow_reverse_cull_face", "get_shadow_reverse_cull_face"); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "shadow_transmittance_bias", PROPERTY_HINT_RANGE, "-16,16,0.01"), "set_param", "get_param", PARAM_TRANSMITTANCE_BIAS); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "shadow_fog_fade", PROPERTY_HINT_RANGE, "0.01,10,0.01"), "set_param", "get_param", PARAM_SHADOW_VOLUMETRIC_FOG_FADE); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "shadow_blur", PROPERTY_HINT_RANGE, "0.1,8,0.01"), "set_param", "get_param", PARAM_SHADOW_BLUR); - ADD_GROUP("Distance Fade", "distance_fade_"); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "shadow_transmittance_bias", PROPERTY_HINT_RANGE, "-16,16,0.001"), "set_param", "get_param", PARAM_TRANSMITTANCE_BIAS); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "shadow_fog_fade", PROPERTY_HINT_RANGE, "0.001,10,0.001"), "set_param", "get_param", PARAM_SHADOW_VOLUMETRIC_FOG_FADE); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "shadow_blur", PROPERTY_HINT_RANGE, "0,10,0.001"), "set_param", "get_param", PARAM_SHADOW_BLUR); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "distance_fade_enabled"), "set_enable_distance_fade", "is_distance_fade_enabled"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "distance_fade_begin", PROPERTY_HINT_RANGE, "0.0,4096.0,0.01,or_greater"), "set_distance_fade_begin", "get_distance_fade_begin"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "distance_fade_shadow", PROPERTY_HINT_RANGE, "0.0,4096.0,0.01,or_greater"), "set_distance_fade_shadow", "get_distance_fade_shadow"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "distance_fade_length", PROPERTY_HINT_RANGE, "0.0,4096.0,0.01,or_greater"), "set_distance_fade_length", "get_distance_fade_length"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "distance_fade_begin", PROPERTY_HINT_RANGE, "0.0,4096.0,0.01,or_greater,suffix:m"), "set_distance_fade_begin", "get_distance_fade_begin"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "distance_fade_shadow", PROPERTY_HINT_RANGE, "0.0,4096.0,0.01,or_greater,suffix:m"), "set_distance_fade_shadow", "get_distance_fade_shadow"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "distance_fade_length", PROPERTY_HINT_RANGE, "0.0,4096.0,0.01,or_greater,suffix:m"), "set_distance_fade_length", "get_distance_fade_length"); ADD_GROUP("Editor", ""); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "editor_only"), "set_editor_only", "is_editor_only"); ADD_GROUP("", ""); @@ -458,7 +462,7 @@ void DirectionalLight3D::_bind_methods() { ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "directional_shadow_split_2", PROPERTY_HINT_RANGE, "0,1,0.001"), "set_param", "get_param", PARAM_SHADOW_SPLIT_2_OFFSET); ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "directional_shadow_split_3", PROPERTY_HINT_RANGE, "0,1,0.001"), "set_param", "get_param", PARAM_SHADOW_SPLIT_3_OFFSET); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "directional_shadow_blend_splits"), "set_blend_splits", "is_blend_splits_enabled"); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "directional_shadow_fade_start", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param", "get_param", PARAM_SHADOW_FADE_START); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "directional_shadow_fade_start", PROPERTY_HINT_RANGE, "0,1,0.001"), "set_param", "get_param", PARAM_SHADOW_FADE_START); ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "directional_shadow_max_distance", PROPERTY_HINT_RANGE, "0,8192,0.1,or_greater,exp"), "set_param", "get_param", PARAM_SHADOW_MAX_DISTANCE); ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "directional_shadow_pancake_size", PROPERTY_HINT_RANGE, "0,1024,0.1,or_greater,exp"), "set_param", "get_param", PARAM_SHADOW_PANCAKE_SIZE); @@ -508,7 +512,7 @@ void OmniLight3D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_shadow_mode"), &OmniLight3D::get_shadow_mode); ADD_GROUP("Omni", "omni_"); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "omni_range", PROPERTY_HINT_RANGE, "0,4096,0.1,or_greater,exp"), "set_param", "get_param", PARAM_RANGE); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "omni_range", PROPERTY_HINT_RANGE, "0,4096,0.001,or_greater,exp"), "set_param", "get_param", PARAM_RANGE); ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "omni_attenuation", PROPERTY_HINT_EXP_EASING, "attenuation"), "set_param", "get_param", PARAM_ATTENUATION); ADD_PROPERTY(PropertyInfo(Variant::INT, "omni_shadow_mode", PROPERTY_HINT_ENUM, "Dual Paraboloid,Cube"), "set_shadow_mode", "get_shadow_mode"); @@ -539,8 +543,8 @@ TypedArray<String> SpotLight3D::get_configuration_warnings() const { void SpotLight3D::_bind_methods() { ADD_GROUP("Spot", "spot_"); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "spot_range", PROPERTY_HINT_RANGE, "0,4096,0.1,or_greater,exp"), "set_param", "get_param", PARAM_RANGE); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "spot_range", PROPERTY_HINT_RANGE, "0,4096,0.001,or_greater,exp,suffix:m"), "set_param", "get_param", PARAM_RANGE); ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "spot_attenuation", PROPERTY_HINT_EXP_EASING, "attenuation"), "set_param", "get_param", PARAM_ATTENUATION); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "spot_angle", PROPERTY_HINT_RANGE, "0,180,0.1,degrees"), "set_param", "get_param", PARAM_SPOT_ANGLE); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "spot_angle", PROPERTY_HINT_RANGE, "0,180,0.01,degrees"), "set_param", "get_param", PARAM_SPOT_ANGLE); ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "spot_angle_attenuation", PROPERTY_HINT_EXP_EASING, "attenuation"), "set_param", "get_param", PARAM_SPOT_ATTENUATION); } diff --git a/scene/3d/light_3d.h b/scene/3d/light_3d.h index 383fa644e5..6ff332df5a 100644 --- a/scene/3d/light_3d.h +++ b/scene/3d/light_3d.h @@ -85,6 +85,8 @@ private: // bind helpers + virtual void owner_changed_notify() override; + protected: RID light; diff --git a/scene/3d/lightmap_gi.cpp b/scene/3d/lightmap_gi.cpp index 8b457b683d..e805d28ed3 100644 --- a/scene/3d/lightmap_gi.cpp +++ b/scene/3d/lightmap_gi.cpp @@ -73,7 +73,7 @@ void LightmapGIData::clear_users() { } void LightmapGIData::_set_user_data(const Array &p_data) { - ERR_FAIL_COND(p_data.size() <= 0); + ERR_FAIL_COND(p_data.is_empty()); ERR_FAIL_COND((p_data.size() % 4) != 0); for (int i = 0; i < p_data.size(); i += 4) { @@ -92,6 +92,95 @@ Array LightmapGIData::_get_user_data() const { return ret; } +void LightmapGIData::_set_light_textures_data(const Array &p_data) { + ERR_FAIL_COND(p_data.is_empty()); + + if (p_data.size() == 1) { + set_light_texture(p_data[0]); + } else { + Vector<Ref<Image>> images; + for (int i = 0; i < p_data.size(); i++) { + Ref<TextureLayered> texture = p_data[i]; + for (int j = 0; j < texture->get_layers(); j++) { + images.push_back(texture->get_layer_data(j)); + } + } + + Ref<Texture2DArray> combined_texture; + combined_texture.instantiate(); + + combined_texture->create_from_images(images); + set_light_texture(combined_texture); + } +} + +Array LightmapGIData::_get_light_textures_data() const { + Array ret; + if (light_texture.is_null() || light_texture->get_layers() == 0) { + return ret; + } + + Vector<Ref<Image>> images; + for (int i = 0; i < light_texture->get_layers(); i++) { + images.push_back(light_texture->get_layer_data(i)); + } + + int slice_count = images.size(); + int slice_width = images[0]->get_width(); + int slice_height = images[0]->get_height(); + + int slices_per_texture = Image::MAX_HEIGHT / slice_height; + int texture_count = Math::ceil(slice_count / (float)slices_per_texture); + + ret.resize(texture_count); + + String base_name = get_path().get_basename(); + + int last_count = slice_count % slices_per_texture; + for (int i = 0; i < texture_count; i++) { + int texture_slice_count = (i == texture_count - 1 && last_count != 0) ? last_count : slices_per_texture; + + Ref<Image> texture_image; + texture_image.instantiate(); + + texture_image->create(slice_width, slice_height * texture_slice_count, false, images[0]->get_format()); + + for (int j = 0; j < texture_slice_count; j++) { + texture_image->blit_rect(images[i * slices_per_texture + j], Rect2i(0, 0, slice_width, slice_height), Point2i(0, slice_height * j)); + } + + String texture_path = texture_count > 1 ? base_name + "_" + itos(i) + ".exr" : base_name + ".exr"; + + Ref<ConfigFile> config; + config.instantiate(); + + if (FileAccess::exists(texture_path + ".import")) { + config->load(texture_path + ".import"); + } + + config->set_value("remap", "importer", "2d_array_texture"); + config->set_value("remap", "type", "CompressedTexture2DArray"); + if (!config->has_section_key("params", "compress/mode")) { + config->set_value("params", "compress/mode", 2); //user may want another compression, so leave it be + } + config->set_value("params", "compress/channel_pack", 1); + config->set_value("params", "mipmaps/generate", false); + config->set_value("params", "slices/horizontal", 1); + config->set_value("params", "slices/vertical", texture_slice_count); + + config->save(texture_path + ".import"); + + Error err = texture_image->save_exr(texture_path, false); + ERR_FAIL_COND_V(err, ret); + ResourceLoader::import(texture_path); + Ref<TextureLayered> t = ResourceLoader::load(texture_path); //if already loaded, it will be updated on refocus? + ERR_FAIL_COND_V(t.is_null(), ret); + ret[i] = t; + } + + return ret; +} + RID LightmapGIData::get_rid() const { return lightmap; } @@ -188,6 +277,9 @@ void LightmapGIData::_bind_methods() { ClassDB::bind_method(D_METHOD("set_light_texture", "light_texture"), &LightmapGIData::set_light_texture); ClassDB::bind_method(D_METHOD("get_light_texture"), &LightmapGIData::get_light_texture); + ClassDB::bind_method(D_METHOD("_set_light_textures_data", "data"), &LightmapGIData::_set_light_textures_data); + ClassDB::bind_method(D_METHOD("_get_light_textures_data"), &LightmapGIData::_get_light_textures_data); + ClassDB::bind_method(D_METHOD("set_uses_spherical_harmonics", "uses_spherical_harmonics"), &LightmapGIData::set_uses_spherical_harmonics); ClassDB::bind_method(D_METHOD("is_using_spherical_harmonics"), &LightmapGIData::is_using_spherical_harmonics); @@ -199,7 +291,8 @@ void LightmapGIData::_bind_methods() { ClassDB::bind_method(D_METHOD("_set_probe_data", "data"), &LightmapGIData::_set_probe_data); ClassDB::bind_method(D_METHOD("_get_probe_data"), &LightmapGIData::_get_probe_data); - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "light_texture", PROPERTY_HINT_RESOURCE_TYPE, "TextureLayered"), "set_light_texture", "get_light_texture"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "light_texture", PROPERTY_HINT_RESOURCE_TYPE, "TextureLayered", PROPERTY_USAGE_EDITOR), "set_light_texture", "get_light_texture"); // property usage default but no save + ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "light_textures", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_light_textures_data", "_get_light_textures_data"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "uses_spherical_harmonics", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "set_uses_spherical_harmonics", "is_using_spherical_harmonics"); ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "user_data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_user_data", "_get_user_data"); ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "probe_data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_probe_data", "_get_probe_data"); @@ -318,12 +411,11 @@ void LightmapGI::_find_meshes_and_lights(Node *p_at_node, Vector<MeshesFound> &m int LightmapGI::_bsp_get_simplex_side(const Vector<Vector3> &p_points, const LocalVector<BSPSimplex> &p_simplices, const Plane &p_plane, uint32_t p_simplex) const { int over = 0; int under = 0; - int coplanar = 0; const BSPSimplex &s = p_simplices[p_simplex]; for (int i = 0; i < 4; i++) { const Vector3 v = p_points[s.vertices[i]]; - if (p_plane.has_point(v)) { //coplanar - coplanar++; + if (p_plane.has_point(v)) { + // Coplanar. } else if (p_plane.is_point_over(v)) { over++; } else { @@ -573,7 +665,7 @@ void LightmapGI::_plot_triangle_into_octree(GenProbesOctree *p_cell, float p_cel } } -void LightmapGI::_gen_new_positions_from_octree(const GenProbesOctree *p_cell, float p_cell_size, const Vector<Vector3> &probe_positions, LocalVector<Vector3> &new_probe_positions, HashMap<Vector3i, bool, Vector3iHash> &positions_used, const AABB &p_bounds) { +void LightmapGI::_gen_new_positions_from_octree(const GenProbesOctree *p_cell, float p_cell_size, const Vector<Vector3> &probe_positions, LocalVector<Vector3> &new_probe_positions, HashMap<Vector3i, bool> &positions_used, const AABB &p_bounds) { for (int i = 0; i < 8; i++) { Vector3i pos = p_cell->offset; if (i & 1) { @@ -842,7 +934,7 @@ LightmapGI::BakeError LightmapGI::bake(Node *p_from_node, String p_image_data_pa } LocalVector<Vector3> new_probe_positions; - HashMap<Vector3i, bool, Vector3iHash> positions_used; + HashMap<Vector3i, bool> positions_used; for (uint32_t i = 0; i < 8; i++) { //insert bounding endpoints Vector3i pos; if (i & 1) { @@ -887,13 +979,13 @@ LightmapGI::BakeError LightmapGI::bake(Node *p_from_node, String p_image_data_pa Color linear_color = light->get_color().srgb_to_linear(); if (Object::cast_to<DirectionalLight3D>(light)) { DirectionalLight3D *l = Object::cast_to<DirectionalLight3D>(light); - lightmapper->add_directional_light(light->get_bake_mode() == Light3D::BAKE_STATIC, -xf.basis.get_axis(Vector3::AXIS_Z).normalized(), linear_color, l->get_param(Light3D::PARAM_ENERGY), l->get_param(Light3D::PARAM_SIZE)); + lightmapper->add_directional_light(light->get_bake_mode() == Light3D::BAKE_STATIC, -xf.basis.get_column(Vector3::AXIS_Z).normalized(), linear_color, l->get_param(Light3D::PARAM_ENERGY), l->get_param(Light3D::PARAM_SIZE), l->get_param(Light3D::PARAM_SHADOW_BLUR)); } else if (Object::cast_to<OmniLight3D>(light)) { OmniLight3D *l = Object::cast_to<OmniLight3D>(light); - lightmapper->add_omni_light(light->get_bake_mode() == Light3D::BAKE_STATIC, xf.origin, linear_color, l->get_param(Light3D::PARAM_ENERGY), l->get_param(Light3D::PARAM_RANGE), l->get_param(Light3D::PARAM_ATTENUATION), l->get_param(Light3D::PARAM_SIZE)); + lightmapper->add_omni_light(light->get_bake_mode() == Light3D::BAKE_STATIC, xf.origin, linear_color, l->get_param(Light3D::PARAM_ENERGY), l->get_param(Light3D::PARAM_RANGE), l->get_param(Light3D::PARAM_ATTENUATION), l->get_param(Light3D::PARAM_SIZE), l->get_param(Light3D::PARAM_SHADOW_BLUR)); } else if (Object::cast_to<SpotLight3D>(light)) { SpotLight3D *l = Object::cast_to<SpotLight3D>(light); - lightmapper->add_spot_light(light->get_bake_mode() == Light3D::BAKE_STATIC, xf.origin, -xf.basis.get_axis(Vector3::AXIS_Z).normalized(), linear_color, l->get_param(Light3D::PARAM_ENERGY), l->get_param(Light3D::PARAM_RANGE), l->get_param(Light3D::PARAM_ATTENUATION), l->get_param(Light3D::PARAM_SPOT_ANGLE), l->get_param(Light3D::PARAM_SPOT_ATTENUATION), l->get_param(Light3D::PARAM_SIZE)); + lightmapper->add_spot_light(light->get_bake_mode() == Light3D::BAKE_STATIC, xf.origin, -xf.basis.get_column(Vector3::AXIS_Z).normalized(), linear_color, l->get_param(Light3D::PARAM_ENERGY), l->get_param(Light3D::PARAM_RANGE), l->get_param(Light3D::PARAM_ATTENUATION), l->get_param(Light3D::PARAM_SPOT_ANGLE), l->get_param(Light3D::PARAM_SPOT_ATTENUATION), l->get_param(Light3D::PARAM_SIZE), l->get_param(Light3D::PARAM_SHADOW_BLUR)); } } for (int i = 0; i < probes_found.size(); i++) { @@ -954,53 +1046,6 @@ LightmapGI::BakeError LightmapGI::bake(Node *p_from_node, String p_image_data_pa return BAKE_ERROR_MESHES_INVALID; } - /* POSTBAKE: Save Textures */ - - Ref<TextureLayered> texture; - { - Vector<Ref<Image>> images; - for (int i = 0; i < lightmapper->get_bake_texture_count(); i++) { - images.push_back(lightmapper->get_bake_texture(i)); - } - //we assume they are all the same, so let's create a large one for saving - Ref<Image> large_image; - large_image.instantiate(); - - large_image->create(images[0]->get_width(), images[0]->get_height() * images.size(), false, images[0]->get_format()); - - for (int i = 0; i < lightmapper->get_bake_texture_count(); i++) { - large_image->blit_rect(images[i], Rect2(0, 0, images[i]->get_width(), images[i]->get_height()), Point2(0, images[i]->get_height() * i)); - } - - String base_path = p_image_data_path.get_basename() + ".exr"; - - Ref<ConfigFile> config; - - config.instantiate(); - if (FileAccess::exists(base_path + ".import")) { - config->load(base_path + ".import"); - } - - config->set_value("remap", "importer", "2d_array_texture"); - config->set_value("remap", "type", "CompressedTexture2DArray"); - if (!config->has_section_key("params", "compress/mode")) { - config->set_value("params", "compress/mode", 2); //user may want another compression, so leave it be - } - config->set_value("params", "compress/channel_pack", 1); - config->set_value("params", "mipmaps/generate", false); - config->set_value("params", "slices/horizontal", 1); - config->set_value("params", "slices/vertical", images.size()); - - config->save(base_path + ".import"); - - Error err = large_image->save_exr(base_path, false); - ERR_FAIL_COND_V(err, BAKE_ERROR_CANT_CREATE_IMAGE); - ResourceLoader::import(base_path); - Ref<Texture> t = ResourceLoader::load(base_path); //if already loaded, it will be updated on refocus? - ERR_FAIL_COND_V(t.is_null(), BAKE_ERROR_CANT_CREATE_IMAGE); - texture = t; - } - /* POSTBAKE: Save Light Data */ Ref<LightmapGIData> data; @@ -1012,6 +1057,17 @@ LightmapGI::BakeError LightmapGI::bake(Node *p_from_node, String p_image_data_pa data.instantiate(); } + Ref<Texture2DArray> texture; + { + Vector<Ref<Image>> images; + for (int i = 0; i < lightmapper->get_bake_texture_count(); i++) { + images.push_back(lightmapper->get_bake_texture(i)); + } + + texture.instantiate(); + texture->create_from_images(images); + } + data->set_light_texture(texture); data->set_uses_spherical_harmonics(directional); @@ -1162,8 +1218,8 @@ LightmapGI::BakeError LightmapGI::bake(Node *p_from_node, String p_image_data_pa /* Compute a BSP tree of the simplices, so it's easy to find the exact one */ } - Error err = ResourceSaver::save(p_image_data_path, data); data->set_path(p_image_data_path); + Error err = ResourceSaver::save(p_image_data_path, data); if (err != OK) { return BAKE_ERROR_CANT_CREATE_IMAGE; diff --git a/scene/3d/lightmap_gi.h b/scene/3d/lightmap_gi.h index d29d7a7c28..f7a23c776a 100644 --- a/scene/3d/lightmap_gi.h +++ b/scene/3d/lightmap_gi.h @@ -61,6 +61,8 @@ class LightmapGIData : public Resource { Array _get_user_data() const; void _set_probe_data(const Dictionary &p_data); Dictionary _get_probe_data() const; + void _set_light_textures_data(const Array &p_data); + Array _get_light_textures_data() const; protected: static void _bind_methods(); @@ -210,16 +212,8 @@ private: } }; - struct Vector3iHash { - _FORCE_INLINE_ static uint32_t hash(const Vector3i &p_vtx) { - uint32_t h = hash_djb2_one_32(p_vtx.x); - h = hash_djb2_one_32(p_vtx.y, h); - return hash_djb2_one_32(p_vtx.z, h); - } - }; - void _plot_triangle_into_octree(GenProbesOctree *p_cell, float p_cell_size, const Vector3 *p_triangle); - void _gen_new_positions_from_octree(const GenProbesOctree *p_cell, float p_cell_size, const Vector<Vector3> &probe_positions, LocalVector<Vector3> &new_probe_positions, HashMap<Vector3i, bool, Vector3iHash> &positions_used, const AABB &p_bounds); + void _gen_new_positions_from_octree(const GenProbesOctree *p_cell, float p_cell_size, const Vector<Vector3> &probe_positions, LocalVector<Vector3> &new_probe_positions, HashMap<Vector3i, bool> &positions_used, const AABB &p_bounds); protected: void _validate_property(PropertyInfo &property) const override; diff --git a/scene/3d/lightmapper.h b/scene/3d/lightmapper.h index f641c99ec1..9b973fd6bc 100644 --- a/scene/3d/lightmapper.h +++ b/scene/3d/lightmapper.h @@ -88,7 +88,9 @@ public: instID(INVALID_GEOMETRY_ID) {} /*! Tests if we hit something. */ - _FORCE_INLINE_ explicit operator bool() const { return geomID != INVALID_GEOMETRY_ID; } + _FORCE_INLINE_ explicit operator bool() const { + return geomID != INVALID_GEOMETRY_ID; + } public: Vector3 org; //!< Ray origin + tnear @@ -116,7 +118,7 @@ public: virtual void set_mesh_alpha_texture(Ref<Image> p_alpha_texture, unsigned int p_id) = 0; virtual void commit() = 0; - virtual void set_mesh_filter(const Set<int> &p_mesh_ids) = 0; + virtual void set_mesh_filter(const HashSet<int> &p_mesh_ids) = 0; virtual void clear_mesh_filter() = 0; static Ref<LightmapRaycaster> create(); @@ -174,9 +176,9 @@ public: }; virtual void add_mesh(const MeshData &p_mesh) = 0; - virtual void add_directional_light(bool p_static, const Vector3 &p_direction, const Color &p_color, float p_energy, float p_angular_distance) = 0; - virtual void add_omni_light(bool p_static, const Vector3 &p_position, const Color &p_color, float p_energy, float p_range, float p_attenuation, float p_size) = 0; - virtual void add_spot_light(bool p_static, const Vector3 &p_position, const Vector3 p_direction, const Color &p_color, float p_energy, float p_range, float p_attenuation, float p_spot_angle, float p_spot_attenuation, float p_size) = 0; + virtual void add_directional_light(bool p_static, const Vector3 &p_direction, const Color &p_color, float p_energy, float p_angular_distance, float p_shadow_blur) = 0; + virtual void add_omni_light(bool p_static, const Vector3 &p_position, const Color &p_color, float p_energy, float p_range, float p_attenuation, float p_size, float p_shadow_blur) = 0; + virtual void add_spot_light(bool p_static, const Vector3 &p_position, const Vector3 p_direction, const Color &p_color, float p_energy, float p_range, float p_attenuation, float p_spot_angle, float p_spot_attenuation, float p_size, float p_shadow_blur) = 0; virtual void add_probe(const Vector3 &p_position) = 0; virtual BakeError bake(BakeQuality p_quality, bool p_use_denoiser, int p_bounces, float p_bias, int p_max_texture_size, bool p_bake_sh, GenerateProbes p_generate_probes, const Ref<Image> &p_environment_panorama, const Basis &p_environment_transform, BakeStepFunc p_step_function = nullptr, void *p_step_userdata = nullptr) = 0; diff --git a/scene/3d/mesh_instance_3d.cpp b/scene/3d/mesh_instance_3d.cpp index 31c33a6b61..31993f898d 100644 --- a/scene/3d/mesh_instance_3d.cpp +++ b/scene/3d/mesh_instance_3d.cpp @@ -42,9 +42,9 @@ bool MeshInstance3D::_set(const StringName &p_name, const Variant &p_value) { return false; } - Map<StringName, int>::Element *E = blend_shape_properties.find(p_name); + HashMap<StringName, int>::Iterator E = blend_shape_properties.find(p_name); if (E) { - set_blend_shape_value(E->get(), p_value); + set_blend_shape_value(E->value, p_value); return true; } @@ -66,9 +66,9 @@ bool MeshInstance3D::_get(const StringName &p_name, Variant &r_ret) const { return false; } - const Map<StringName, int>::Element *E = blend_shape_properties.find(p_name); + HashMap<StringName, int>::ConstIterator E = blend_shape_properties.find(p_name); if (E) { - r_ret = get_blend_shape_value(E->get()); + r_ret = get_blend_shape_value(E->value); return true; } @@ -97,7 +97,7 @@ void MeshInstance3D::_get_property_list(List<PropertyInfo> *p_list) const { if (mesh.is_valid()) { for (int i = 0; i < mesh->get_surface_count(); i++) { - p_list->push_back(PropertyInfo(Variant::OBJECT, "surface_material_override/" + itos(i), PROPERTY_HINT_RESOURCE_TYPE, "BaseMaterial3D,ShaderMaterial", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_DEFERRED_SET_RESOURCE)); + p_list->push_back(PropertyInfo(Variant::OBJECT, vformat("%s/%d", PNAME("surface_material_override"), i), PROPERTY_HINT_RESOURCE_TYPE, "BaseMaterial3D,ShaderMaterial", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_DEFERRED_SET_RESOURCE)); } } } diff --git a/scene/3d/mesh_instance_3d.h b/scene/3d/mesh_instance_3d.h index 0bf5c32410..dc9c64fa41 100644 --- a/scene/3d/mesh_instance_3d.h +++ b/scene/3d/mesh_instance_3d.h @@ -47,7 +47,7 @@ protected: NodePath skeleton_path = NodePath(".."); LocalVector<float> blend_shape_tracks; - Map<StringName, int> blend_shape_properties; + HashMap<StringName, int> blend_shape_properties; Vector<Ref<Material>> surface_override_materials; void _mesh_changed(); diff --git a/scene/3d/navigation_agent_3d.cpp b/scene/3d/navigation_agent_3d.cpp index 86c11b3789..3752713d6a 100644 --- a/scene/3d/navigation_agent_3d.cpp +++ b/scene/3d/navigation_agent_3d.cpp @@ -35,6 +35,12 @@ void NavigationAgent3D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_rid"), &NavigationAgent3D::get_rid); + ClassDB::bind_method(D_METHOD("set_avoidance_enabled", "enabled"), &NavigationAgent3D::set_avoidance_enabled); + ClassDB::bind_method(D_METHOD("get_avoidance_enabled"), &NavigationAgent3D::get_avoidance_enabled); + + ClassDB::bind_method(D_METHOD("set_path_desired_distance", "desired_distance"), &NavigationAgent3D::set_path_desired_distance); + ClassDB::bind_method(D_METHOD("get_path_desired_distance"), &NavigationAgent3D::get_path_desired_distance); + ClassDB::bind_method(D_METHOD("set_target_desired_distance", "desired_distance"), &NavigationAgent3D::set_target_desired_distance); ClassDB::bind_method(D_METHOD("get_target_desired_distance"), &NavigationAgent3D::get_target_desired_distance); @@ -62,8 +68,14 @@ void NavigationAgent3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_path_max_distance", "max_speed"), &NavigationAgent3D::set_path_max_distance); ClassDB::bind_method(D_METHOD("get_path_max_distance"), &NavigationAgent3D::get_path_max_distance); - ClassDB::bind_method(D_METHOD("set_navigable_layers", "navigable_layers"), &NavigationAgent3D::set_navigable_layers); - ClassDB::bind_method(D_METHOD("get_navigable_layers"), &NavigationAgent3D::get_navigable_layers); + ClassDB::bind_method(D_METHOD("set_navigation_layers", "navigation_layers"), &NavigationAgent3D::set_navigation_layers); + ClassDB::bind_method(D_METHOD("get_navigation_layers"), &NavigationAgent3D::get_navigation_layers); + + ClassDB::bind_method(D_METHOD("set_navigation_layer_value", "layer_number", "value"), &NavigationAgent3D::set_navigation_layer_value); + ClassDB::bind_method(D_METHOD("get_navigation_layer_value", "layer_number"), &NavigationAgent3D::get_navigation_layer_value); + + ClassDB::bind_method(D_METHOD("set_navigation_map", "navigation_map"), &NavigationAgent3D::set_navigation_map); + ClassDB::bind_method(D_METHOD("get_navigation_map"), &NavigationAgent3D::get_navigation_map); ClassDB::bind_method(D_METHOD("set_target_location", "location"), &NavigationAgent3D::set_target_location); ClassDB::bind_method(D_METHOD("get_target_location"), &NavigationAgent3D::get_target_location); @@ -79,16 +91,21 @@ void NavigationAgent3D::_bind_methods() { ClassDB::bind_method(D_METHOD("_avoidance_done", "new_velocity"), &NavigationAgent3D::_avoidance_done); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "target_desired_distance", PROPERTY_HINT_RANGE, "0.1,100,0.01"), "set_target_desired_distance", "get_target_desired_distance"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radius", PROPERTY_HINT_RANGE, "0.1,100,0.01"), "set_radius", "get_radius"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "agent_height_offset", PROPERTY_HINT_RANGE, "-100.0,100,0.01"), "set_agent_height_offset", "get_agent_height_offset"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "neighbor_dist", PROPERTY_HINT_RANGE, "0.1,10000,0.01"), "set_neighbor_dist", "get_neighbor_dist"); + ADD_GROUP("Pathfinding", ""); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "path_desired_distance", PROPERTY_HINT_RANGE, "0.1,100,0.01,suffix:m"), "set_path_desired_distance", "get_path_desired_distance"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "target_desired_distance", PROPERTY_HINT_RANGE, "0.1,100,0.01,suffix:m"), "set_target_desired_distance", "get_target_desired_distance"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "agent_height_offset", PROPERTY_HINT_RANGE, "-100.0,100,0.01,suffix:m"), "set_agent_height_offset", "get_agent_height_offset"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "path_max_distance", PROPERTY_HINT_RANGE, "0.01,100,0.1,suffix:m"), "set_path_max_distance", "get_path_max_distance"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "navigation_layers", PROPERTY_HINT_LAYERS_3D_NAVIGATION), "set_navigation_layers", "get_navigation_layers"); + + ADD_GROUP("Avoidance", ""); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "avoidance_enabled"), "set_avoidance_enabled", "get_avoidance_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radius", PROPERTY_HINT_RANGE, "0.1,100,0.01,suffix:m"), "set_radius", "get_radius"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "neighbor_dist", PROPERTY_HINT_RANGE, "0.1,10000,0.01,suffix:m"), "set_neighbor_dist", "get_neighbor_dist"); ADD_PROPERTY(PropertyInfo(Variant::INT, "max_neighbors", PROPERTY_HINT_RANGE, "1,10000,1"), "set_max_neighbors", "get_max_neighbors"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "time_horizon", PROPERTY_HINT_RANGE, "0.01,100,0.01"), "set_time_horizon", "get_time_horizon"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "max_speed", PROPERTY_HINT_RANGE, "0.1,10000,0.01"), "set_max_speed", "get_max_speed"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "path_max_distance", PROPERTY_HINT_RANGE, "0.01,100,0.1"), "set_path_max_distance", "get_path_max_distance"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "time_horizon", PROPERTY_HINT_RANGE, "0.01,100,0.01,suffix:s"), "set_time_horizon", "get_time_horizon"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "max_speed", PROPERTY_HINT_RANGE, "0.1,10000,0.01,suffix:m/s"), "set_max_speed", "get_max_speed"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "ignore_y"), "set_ignore_y", "get_ignore_y"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "navigable_layers", PROPERTY_HINT_LAYERS_3D_NAVIGATION), "set_navigable_layers", "get_navigable_layers"); ADD_SIGNAL(MethodInfo("path_changed")); ADD_SIGNAL(MethodInfo("target_reached")); @@ -98,24 +115,62 @@ void NavigationAgent3D::_bind_methods() { void NavigationAgent3D::_notification(int p_what) { switch (p_what) { - case NOTIFICATION_READY: { - agent_parent = Object::cast_to<Node3D>(get_parent()); - if (agent_parent != nullptr) { - // place agent on navigation map first or else the RVO agent callback creation fails silently later - NavigationServer3D::get_singleton()->agent_set_map(get_rid(), agent_parent->get_world_3d()->get_navigation_map()); - NavigationServer3D::get_singleton()->agent_set_callback(agent, this, "_avoidance_done"); - } + case NOTIFICATION_POST_ENTER_TREE: { + // need to use POST_ENTER_TREE cause with normal ENTER_TREE not all required Nodes are ready. + // cannot use READY as ready does not get called if Node is readded to SceneTree + set_agent_parent(get_parent()); set_physics_process_internal(true); } break; + case NOTIFICATION_PARENTED: { + if (is_inside_tree() && (get_parent() != agent_parent)) { + // only react to PARENTED notifications when already inside_tree and parent changed, e.g. users switch nodes around + // PARENTED notification fires also when Node is added in scripts to a parent + // this would spam transforms fails and world fails while Node is outside SceneTree + // when node gets reparented when joining the tree POST_ENTER_TREE takes care of this + set_agent_parent(get_parent()); + set_physics_process_internal(true); + } + } break; + + case NOTIFICATION_UNPARENTED: { + // if agent has no parent no point in processing it until reparented + set_agent_parent(nullptr); + set_physics_process_internal(false); + } break; + case NOTIFICATION_EXIT_TREE: { - agent_parent = nullptr; + set_agent_parent(nullptr); set_physics_process_internal(false); } break; + case NOTIFICATION_PAUSED: { + if (agent_parent && !agent_parent->can_process()) { + map_before_pause = NavigationServer3D::get_singleton()->agent_get_map(get_rid()); + NavigationServer3D::get_singleton()->agent_set_map(get_rid(), RID()); + } else if (agent_parent && agent_parent->can_process() && !(map_before_pause == RID())) { + NavigationServer3D::get_singleton()->agent_set_map(get_rid(), map_before_pause); + map_before_pause = RID(); + } + } break; + + case NOTIFICATION_UNPAUSED: { + if (agent_parent && !agent_parent->can_process()) { + map_before_pause = NavigationServer3D::get_singleton()->agent_get_map(get_rid()); + NavigationServer3D::get_singleton()->agent_set_map(get_rid(), RID()); + } else if (agent_parent && agent_parent->can_process() && !(map_before_pause == RID())) { + NavigationServer3D::get_singleton()->agent_set_map(get_rid(), map_before_pause); + map_before_pause = RID(); + } + } break; + case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: { if (agent_parent) { - NavigationServer3D::get_singleton()->agent_set_position(agent, agent_parent->get_global_transform().origin); + if (avoidance_enabled) { + // agent_position on NavigationServer is avoidance only and has nothing to do with pathfinding + // no point in flooding NavigationServer queue with agent position updates that get send to the void if avoidance is not used + NavigationServer3D::get_singleton()->agent_set_position(agent, agent_parent->get_global_transform().origin); + } _check_distance_to_target(); } } break; @@ -137,16 +192,85 @@ NavigationAgent3D::~NavigationAgent3D() { agent = RID(); // Pointless } -void NavigationAgent3D::set_navigable_layers(uint32_t p_layers) { - bool layers_changed = navigable_layers != p_layers; - navigable_layers = p_layers; - if (layers_changed) { +void NavigationAgent3D::set_avoidance_enabled(bool p_enabled) { + avoidance_enabled = p_enabled; + if (avoidance_enabled) { + NavigationServer3D::get_singleton()->agent_set_callback(agent, this, "_avoidance_done"); + } else { + NavigationServer3D::get_singleton()->agent_set_callback(agent, nullptr, "_avoidance_done"); + } +} + +bool NavigationAgent3D::get_avoidance_enabled() const { + return avoidance_enabled; +} + +void NavigationAgent3D::set_agent_parent(Node *p_agent_parent) { + // remove agent from any avoidance map before changing parent or there will be leftovers on the RVO map + NavigationServer3D::get_singleton()->agent_set_callback(agent, nullptr, "_avoidance_done"); + if (Object::cast_to<Node3D>(p_agent_parent) != nullptr) { + // place agent on navigation map first or else the RVO agent callback creation fails silently later + agent_parent = Object::cast_to<Node3D>(p_agent_parent); + if (map_override.is_valid()) { + NavigationServer3D::get_singleton()->agent_set_map(get_rid(), map_override); + } else { + NavigationServer3D::get_singleton()->agent_set_map(get_rid(), agent_parent->get_world_3d()->get_navigation_map()); + } + // create new avoidance callback if enabled + set_avoidance_enabled(avoidance_enabled); + } else { + agent_parent = nullptr; + NavigationServer3D::get_singleton()->agent_set_map(get_rid(), RID()); + } +} + +void NavigationAgent3D::set_navigation_layers(uint32_t p_navigation_layers) { + bool navigation_layers_changed = navigation_layers != p_navigation_layers; + navigation_layers = p_navigation_layers; + if (navigation_layers_changed) { _request_repath(); } } -uint32_t NavigationAgent3D::get_navigable_layers() const { - return navigable_layers; +uint32_t NavigationAgent3D::get_navigation_layers() const { + return navigation_layers; +} + +void NavigationAgent3D::set_navigation_layer_value(int p_layer_number, bool p_value) { + ERR_FAIL_COND_MSG(p_layer_number < 1, "Navigation layer number must be between 1 and 32 inclusive."); + ERR_FAIL_COND_MSG(p_layer_number > 32, "Navigation layer number must be between 1 and 32 inclusive."); + uint32_t _navigation_layers = get_navigation_layers(); + if (p_value) { + _navigation_layers |= 1 << (p_layer_number - 1); + } else { + _navigation_layers &= ~(1 << (p_layer_number - 1)); + } + set_navigation_layers(_navigation_layers); +} + +bool NavigationAgent3D::get_navigation_layer_value(int p_layer_number) const { + ERR_FAIL_COND_V_MSG(p_layer_number < 1, false, "Navigation layer number must be between 1 and 32 inclusive."); + ERR_FAIL_COND_V_MSG(p_layer_number > 32, false, "Navigation layer number must be between 1 and 32 inclusive."); + return get_navigation_layers() & (1 << (p_layer_number - 1)); +} + +void NavigationAgent3D::set_navigation_map(RID p_navigation_map) { + map_override = p_navigation_map; + NavigationServer3D::get_singleton()->agent_set_map(agent, map_override); + _request_repath(); +} + +RID NavigationAgent3D::get_navigation_map() const { + if (map_override.is_valid()) { + return map_override; + } else if (agent_parent != nullptr) { + return agent_parent->get_world_3d()->get_navigation_map(); + } + return RID(); +} + +void NavigationAgent3D::set_path_desired_distance(real_t p_dd) { + path_desired_distance = p_dd; } void NavigationAgent3D::set_target_desired_distance(real_t p_dd) { @@ -263,7 +387,7 @@ TypedArray<String> NavigationAgent3D::get_configuration_warnings() const { TypedArray<String> warnings = Node::get_configuration_warnings(); if (!Object::cast_to<Node3D>(get_parent())) { - warnings.push_back(RTR("The NavigationAgent3D can be used only under a spatial node.")); + warnings.push_back(RTR("The NavigationAgent3D can be used only under a Node3D inheriting parent node.")); } return warnings; @@ -307,7 +431,11 @@ void NavigationAgent3D::update_navigation() { } if (reload_path) { - navigation_path = NavigationServer3D::get_singleton()->map_get_path(agent_parent->get_world_3d()->get_navigation_map(), o, target_location, true, navigable_layers); + if (map_override.is_valid()) { + navigation_path = NavigationServer3D::get_singleton()->map_get_path(map_override, o, target_location, true, navigation_layers); + } else { + navigation_path = NavigationServer3D::get_singleton()->map_get_path(agent_parent->get_world_3d()->get_navigation_map(), o, target_location, true, navigation_layers); + } navigation_finished = false; nav_path_index = 0; emit_signal(SNAME("path_changed")); @@ -320,7 +448,7 @@ void NavigationAgent3D::update_navigation() { // Check if we can advance the navigation path if (navigation_finished == false) { // Advances to the next far away location. - while (o.distance_to(navigation_path[nav_path_index] - Vector3(0, navigation_height_offset, 0)) < target_desired_distance) { + while (o.distance_to(navigation_path[nav_path_index] - Vector3(0, navigation_height_offset, 0)) < path_desired_distance) { nav_path_index += 1; if (nav_path_index == navigation_path.size()) { _check_distance_to_target(); diff --git a/scene/3d/navigation_agent_3d.h b/scene/3d/navigation_agent_3d.h index f4afebb36e..0a00d769c3 100644 --- a/scene/3d/navigation_agent_3d.h +++ b/scene/3d/navigation_agent_3d.h @@ -41,23 +41,27 @@ class NavigationAgent3D : public Node { Node3D *agent_parent = nullptr; RID agent; + RID map_before_pause; + RID map_override; - uint32_t navigable_layers = 1; + bool avoidance_enabled = false; + uint32_t navigation_layers = 1; + real_t path_desired_distance = 1.0; real_t target_desired_distance = 1.0; - real_t radius; + real_t radius = 0.0; real_t navigation_height_offset = 0.0; - bool ignore_y; - real_t neighbor_dist; - int max_neighbors; - real_t time_horizon; - real_t max_speed; + bool ignore_y = false; + real_t neighbor_dist = 0.0; + int max_neighbors = 0; + real_t time_horizon = 0.0; + real_t max_speed = 0.0; real_t path_max_distance = 3.0; Vector3 target_location; Vector<Vector3> navigation_path; - int nav_path_index; + int nav_path_index = 0; bool velocity_submitted = false; Vector3 prev_safe_velocity; /// The submitted target velocity @@ -65,7 +69,7 @@ class NavigationAgent3D : public Node { bool target_reached = false; bool navigation_finished = true; // No initialized on purpose - uint32_t update_frame_id; + uint32_t update_frame_id = 0; protected: static void _bind_methods(); @@ -79,8 +83,24 @@ public: return agent; } - void set_navigable_layers(uint32_t p_layers); - uint32_t get_navigable_layers() const; + void set_avoidance_enabled(bool p_enabled); + bool get_avoidance_enabled() const; + + void set_agent_parent(Node *p_agent_parent); + + void set_navigation_layers(uint32_t p_navigation_layers); + uint32_t get_navigation_layers() const; + + void set_navigation_layer_value(int p_layer_number, bool p_value); + bool get_navigation_layer_value(int p_layer_number) const; + + void set_navigation_map(RID p_navigation_map); + RID get_navigation_map() const; + + void set_path_desired_distance(real_t p_dd); + real_t get_path_desired_distance() const { + return path_desired_distance; + } void set_target_desired_distance(real_t p_dd); real_t get_target_desired_distance() const { diff --git a/scene/3d/navigation_obstacle_3d.cpp b/scene/3d/navigation_obstacle_3d.cpp index 78dbecc0c5..c6eda1f9cd 100644 --- a/scene/3d/navigation_obstacle_3d.cpp +++ b/scene/3d/navigation_obstacle_3d.cpp @@ -35,6 +35,8 @@ #include "servers/navigation_server_3d.h" void NavigationObstacle3D::_bind_methods() { + ClassDB::bind_method(D_METHOD("get_rid"), &NavigationObstacle3D::get_rid); + ClassDB::bind_method(D_METHOD("set_estimate_radius", "estimate_radius"), &NavigationObstacle3D::set_estimate_radius); ClassDB::bind_method(D_METHOD("is_radius_estimated"), &NavigationObstacle3D::is_radius_estimated); ClassDB::bind_method(D_METHOD("set_radius", "radius"), &NavigationObstacle3D::set_radius); @@ -78,8 +80,28 @@ void NavigationObstacle3D::_notification(int p_what) { parent_node3d = nullptr; } break; + case NOTIFICATION_PAUSED: { + if (parent_node3d && !parent_node3d->can_process()) { + map_before_pause = NavigationServer3D::get_singleton()->agent_get_map(get_rid()); + NavigationServer3D::get_singleton()->agent_set_map(get_rid(), RID()); + } else if (parent_node3d && parent_node3d->can_process() && !(map_before_pause == RID())) { + NavigationServer3D::get_singleton()->agent_set_map(get_rid(), map_before_pause); + map_before_pause = RID(); + } + } break; + + case NOTIFICATION_UNPAUSED: { + if (parent_node3d && !parent_node3d->can_process()) { + map_before_pause = NavigationServer3D::get_singleton()->agent_get_map(get_rid()); + NavigationServer3D::get_singleton()->agent_set_map(get_rid(), RID()); + } else if (parent_node3d && parent_node3d->can_process() && !(map_before_pause == RID())) { + NavigationServer3D::get_singleton()->agent_set_map(get_rid(), map_before_pause); + map_before_pause = RID(); + } + } break; + case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: { - if (parent_node3d) { + if (parent_node3d && parent_node3d->is_inside_tree()) { NavigationServer3D::get_singleton()->agent_set_position(agent, parent_node3d->get_global_transform().origin); PhysicsBody3D *rigid = Object::cast_to<PhysicsBody3D>(get_parent()); @@ -107,7 +129,12 @@ TypedArray<String> NavigationObstacle3D::get_configuration_warnings() const { TypedArray<String> warnings = Node::get_configuration_warnings(); if (!Object::cast_to<Node3D>(get_parent())) { - warnings.push_back(RTR("The NavigationObstacle3D only serves to provide collision avoidance to a spatial object.")); + warnings.push_back(RTR("The NavigationObstacle3D only serves to provide collision avoidance to a Node3D inheriting parent object.")); + } + + if (Object::cast_to<StaticBody3D>(get_parent())) { + warnings.push_back(RTR("The NavigationObstacle3D is intended for constantly moving bodies like CharacterBody3D or RigidDynamicBody3D as it creates only an RVO avoidance radius and does not follow scene geometry exactly." + "\nNot constantly moving or complete static objects should be (re)baked to a NavigationMesh so agents can not only avoid them but also move along those objects outline at high detail")); } return warnings; @@ -129,13 +156,13 @@ void NavigationObstacle3D::reevaluate_agent_radius() { } real_t NavigationObstacle3D::estimate_agent_radius() const { - if (parent_node3d) { + if (parent_node3d && parent_node3d->is_inside_tree()) { // Estimate the radius of this physics body real_t radius = 0.0; for (int i(0); i < parent_node3d->get_child_count(); i++) { // For each collision shape CollisionShape3D *cs = Object::cast_to<CollisionShape3D>(parent_node3d->get_child(i)); - if (cs) { + if (cs && cs->is_inside_tree()) { // Take the distance between the Body center to the shape center real_t r = cs->get_transform().origin.length(); if (cs->get_shape().is_valid()) { @@ -146,6 +173,9 @@ real_t NavigationObstacle3D::estimate_agent_radius() const { r *= MAX(s.x, MAX(s.y, s.z)); // Takes the biggest radius radius = MAX(radius, r); + } else if (cs && !cs->is_inside_tree()) { + WARN_PRINT("A CollisionShape3D of the NavigationObstacle3D parent node was not inside the SceneTree when estimating the obstacle radius." + "\nMove the NavigationObstacle3D to a child position below any CollisionShape3D node of the parent node so the CollisionShape3D is already inside the SceneTree."); } } diff --git a/scene/3d/navigation_obstacle_3d.h b/scene/3d/navigation_obstacle_3d.h index 542d603a0a..0ddde64c0e 100644 --- a/scene/3d/navigation_obstacle_3d.h +++ b/scene/3d/navigation_obstacle_3d.h @@ -38,6 +38,7 @@ class NavigationObstacle3D : public Node { Node3D *parent_node3d = nullptr; RID agent; + RID map_before_pause; bool estimate_radius = true; real_t radius = 1.0; diff --git a/scene/3d/navigation_region_3d.cpp b/scene/3d/navigation_region_3d.cpp index 215e18869a..2a8149c6f6 100644 --- a/scene/3d/navigation_region_3d.cpp +++ b/scene/3d/navigation_region_3d.cpp @@ -65,12 +65,54 @@ bool NavigationRegion3D::is_enabled() const { return enabled; } -void NavigationRegion3D::set_layers(uint32_t p_layers) { - NavigationServer3D::get_singleton()->region_set_layers(region, p_layers); +void NavigationRegion3D::set_navigation_layers(uint32_t p_navigation_layers) { + NavigationServer3D::get_singleton()->region_set_navigation_layers(region, p_navigation_layers); } -uint32_t NavigationRegion3D::get_layers() const { - return NavigationServer3D::get_singleton()->region_get_layers(region); +uint32_t NavigationRegion3D::get_navigation_layers() const { + return NavigationServer3D::get_singleton()->region_get_navigation_layers(region); +} + +void NavigationRegion3D::set_navigation_layer_value(int p_layer_number, bool p_value) { + ERR_FAIL_COND_MSG(p_layer_number < 1, "Navigation layer number must be between 1 and 32 inclusive."); + ERR_FAIL_COND_MSG(p_layer_number > 32, "Navigation layer number must be between 1 and 32 inclusive."); + uint32_t _navigation_layers = get_navigation_layers(); + if (p_value) { + _navigation_layers |= 1 << (p_layer_number - 1); + } else { + _navigation_layers &= ~(1 << (p_layer_number - 1)); + } + set_navigation_layers(_navigation_layers); +} + +bool NavigationRegion3D::get_navigation_layer_value(int p_layer_number) const { + ERR_FAIL_COND_V_MSG(p_layer_number < 1, false, "Navigation layer number must be between 1 and 32 inclusive."); + ERR_FAIL_COND_V_MSG(p_layer_number > 32, false, "Navigation layer number must be between 1 and 32 inclusive."); + return get_navigation_layers() & (1 << (p_layer_number - 1)); +} + +void NavigationRegion3D::set_enter_cost(real_t p_enter_cost) { + ERR_FAIL_COND_MSG(p_enter_cost < 0.0, "The enter_cost must be positive."); + enter_cost = MAX(p_enter_cost, 0.0); + NavigationServer3D::get_singleton()->region_set_enter_cost(region, p_enter_cost); +} + +real_t NavigationRegion3D::get_enter_cost() const { + return enter_cost; +} + +void NavigationRegion3D::set_travel_cost(real_t p_travel_cost) { + ERR_FAIL_COND_MSG(p_travel_cost < 0.0, "The travel_cost must be positive."); + travel_cost = MAX(p_travel_cost, 0.0); + NavigationServer3D::get_singleton()->region_set_enter_cost(region, travel_cost); +} + +real_t NavigationRegion3D::get_travel_cost() const { + return travel_cost; +} + +RID NavigationRegion3D::get_region_rid() const { + return region; } ///////////////////////////// @@ -127,6 +169,17 @@ void NavigationRegion3D::set_navigation_mesh(const Ref<NavigationMesh> &p_navmes NavigationServer3D::get_singleton()->region_set_navmesh(region, p_navmesh); + if (debug_view == nullptr && is_inside_tree() && navmesh.is_valid() && get_tree()->is_debugging_navigation_hint()) { + MeshInstance3D *dm = memnew(MeshInstance3D); + dm->set_mesh(navmesh->get_debug_mesh()); + if (is_enabled()) { + dm->set_material_override(get_tree()->get_debug_navigation_material()); + } else { + dm->set_material_override(get_tree()->get_debug_navigation_disabled_material()); + } + add_child(dm); + debug_view = dm; + } if (debug_view && navmesh.is_valid()) { Object::cast_to<MeshInstance3D>(debug_view)->set_mesh(navmesh->get_debug_mesh()); } @@ -161,13 +214,22 @@ void _bake_navigation_mesh(void *p_user_data) { } } -void NavigationRegion3D::bake_navigation_mesh() { +void NavigationRegion3D::bake_navigation_mesh(bool p_on_thread) { ERR_FAIL_COND_MSG(bake_thread.is_started(), "Unable to start another bake request. The navigation mesh bake thread is already baking a navigation mesh."); BakeThreadsArgs *args = memnew(BakeThreadsArgs); args->nav_region = this; - bake_thread.start(_bake_navigation_mesh, args); + if (p_on_thread && !OS::get_singleton()->can_use_threads()) { + WARN_PRINT("NavigationMesh bake 'on_thread' will be disabled as the current OS does not support multiple threads." + "\nAs a fallback the navigation mesh will bake on the main thread which can cause framerate issues."); + } + + if (p_on_thread && OS::get_singleton()->can_use_threads()) { + bake_thread.start(_bake_navigation_mesh, args); + } else { + _bake_navigation_mesh(args); + } } void NavigationRegion3D::_bake_finished(Ref<NavigationMesh> p_nav_mesh) { @@ -195,15 +257,28 @@ void NavigationRegion3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_enabled", "enabled"), &NavigationRegion3D::set_enabled); ClassDB::bind_method(D_METHOD("is_enabled"), &NavigationRegion3D::is_enabled); - ClassDB::bind_method(D_METHOD("set_layers", "layers"), &NavigationRegion3D::set_layers); - ClassDB::bind_method(D_METHOD("get_layers"), &NavigationRegion3D::get_layers); + ClassDB::bind_method(D_METHOD("set_navigation_layers", "navigation_layers"), &NavigationRegion3D::set_navigation_layers); + ClassDB::bind_method(D_METHOD("get_navigation_layers"), &NavigationRegion3D::get_navigation_layers); + + ClassDB::bind_method(D_METHOD("set_navigation_layer_value", "layer_number", "value"), &NavigationRegion3D::set_navigation_layer_value); + ClassDB::bind_method(D_METHOD("get_navigation_layer_value", "layer_number"), &NavigationRegion3D::get_navigation_layer_value); + + ClassDB::bind_method(D_METHOD("get_region_rid"), &NavigationRegion3D::get_region_rid); + + ClassDB::bind_method(D_METHOD("set_enter_cost", "enter_cost"), &NavigationRegion3D::set_enter_cost); + ClassDB::bind_method(D_METHOD("get_enter_cost"), &NavigationRegion3D::get_enter_cost); + + ClassDB::bind_method(D_METHOD("set_travel_cost", "travel_cost"), &NavigationRegion3D::set_travel_cost); + ClassDB::bind_method(D_METHOD("get_travel_cost"), &NavigationRegion3D::get_travel_cost); - ClassDB::bind_method(D_METHOD("bake_navigation_mesh"), &NavigationRegion3D::bake_navigation_mesh); + ClassDB::bind_method(D_METHOD("bake_navigation_mesh", "on_thread"), &NavigationRegion3D::bake_navigation_mesh, DEFVAL(true)); ClassDB::bind_method(D_METHOD("_bake_finished", "nav_mesh"), &NavigationRegion3D::_bake_finished); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "navmesh", PROPERTY_HINT_RESOURCE_TYPE, "NavigationMesh"), "set_navigation_mesh", "get_navigation_mesh"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "enabled"), "set_enabled", "is_enabled"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "layers", PROPERTY_HINT_LAYERS_3D_NAVIGATION), "set_layers", "get_layers"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "navigation_layers", PROPERTY_HINT_LAYERS_3D_NAVIGATION), "set_navigation_layers", "get_navigation_layers"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "enter_cost"), "set_enter_cost", "get_enter_cost"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "travel_cost"), "set_travel_cost", "get_travel_cost"); ADD_SIGNAL(MethodInfo("navigation_mesh_changed")); ADD_SIGNAL(MethodInfo("bake_finished")); @@ -217,6 +292,8 @@ void NavigationRegion3D::_navigation_changed() { NavigationRegion3D::NavigationRegion3D() { set_notify_transform(true); region = NavigationServer3D::get_singleton()->region_create(); + NavigationServer3D::get_singleton()->region_set_enter_cost(region, get_enter_cost()); + NavigationServer3D::get_singleton()->region_set_travel_cost(region, get_travel_cost()); } NavigationRegion3D::~NavigationRegion3D() { diff --git a/scene/3d/navigation_region_3d.h b/scene/3d/navigation_region_3d.h index 1a50bb5f64..aaaf5dd3b8 100644 --- a/scene/3d/navigation_region_3d.h +++ b/scene/3d/navigation_region_3d.h @@ -41,6 +41,9 @@ class NavigationRegion3D : public Node3D { RID region; Ref<NavigationMesh> navmesh; + real_t enter_cost = 0.0; + real_t travel_cost = 1.0; + Node *debug_view = nullptr; Thread bake_thread; @@ -54,15 +57,26 @@ public: void set_enabled(bool p_enabled); bool is_enabled() const; - void set_layers(uint32_t p_layers); - uint32_t get_layers() const; + void set_navigation_layers(uint32_t p_navigation_layers); + uint32_t get_navigation_layers() const; + + void set_navigation_layer_value(int p_layer_number, bool p_value); + bool get_navigation_layer_value(int p_layer_number) const; + + RID get_region_rid() const; + + void set_enter_cost(real_t p_enter_cost); + real_t get_enter_cost() const; + + void set_travel_cost(real_t p_travel_cost); + real_t get_travel_cost() const; void set_navigation_mesh(const Ref<NavigationMesh> &p_navmesh); Ref<NavigationMesh> get_navigation_mesh() const; - /// Bakes the navigation mesh in a dedicated thread; once done, automatically + /// Bakes the navigation mesh; once done, automatically /// sets the new navigation mesh and emits a signal - void bake_navigation_mesh(); + void bake_navigation_mesh(bool p_on_thread); void _bake_finished(Ref<NavigationMesh> p_nav_mesh); TypedArray<String> get_configuration_warnings() const override; diff --git a/scene/3d/node_3d.cpp b/scene/3d/node_3d.cpp index bbc977647e..04b1081516 100644 --- a/scene/3d/node_3d.cpp +++ b/scene/3d/node_3d.cpp @@ -85,12 +85,20 @@ void Node3D::_notify_dirty() { } void Node3D::_update_local_transform() const { - if (this->get_rotation_edit_mode() != ROTATION_EDIT_MODE_BASIS) { - data.local_transform = data.local_transform.orthogonalized(); - } - data.local_transform.basis.set_euler_scale(data.rotation, data.scale); + // This function is called when the local transform (data.local_transform) is dirty and the right value is contained in the Euler rotation and scale. + + data.local_transform.basis.set_euler_scale(data.euler_rotation, data.scale, data.euler_rotation_order); + + data.dirty &= ~DIRTY_LOCAL_TRANSFORM; +} + +void Node3D::_update_rotation_and_scale() const { + // This function is called when the Euler rotation (data.euler_rotation) is dirty and the right value is contained in the local transform + + data.scale = data.local_transform.basis.get_scale(); + data.euler_rotation = data.local_transform.basis.get_euler_normalized(data.euler_rotation_order); - data.dirty &= ~DIRTY_LOCAL; + data.dirty &= ~DIRTY_EULER_ROTATION_AND_SCALE; } void Node3D::_propagate_transform_changed(Node3D *p_origin) { @@ -113,7 +121,7 @@ void Node3D::_propagate_transform_changed(Node3D *p_origin) { #endif get_tree()->xform_change_list.add(&xform_change); } - data.dirty |= DIRTY_GLOBAL; + data.dirty |= DIRTY_GLOBAL_TRANSFORM; data.children_lock--; } @@ -137,12 +145,12 @@ void Node3D::_notification(int p_what) { if (data.top_level && !Engine::get_singleton()->is_editor_hint()) { if (data.parent) { data.local_transform = data.parent->get_global_transform() * get_transform(); - data.dirty = DIRTY_VECTORS; //global is always dirty upon entering a scene + data.dirty = DIRTY_EULER_ROTATION_AND_SCALE; // As local transform was updated, rot/scale should be dirty. } data.top_level_active = true; } - data.dirty |= DIRTY_GLOBAL; //global is always dirty upon entering a scene + data.dirty |= DIRTY_GLOBAL_TRANSFORM; // Global is always dirty upon entering a scene. _notify_dirty(); notification(NOTIFICATION_ENTER_WORLD); @@ -180,7 +188,7 @@ void Node3D::_notification(int p_what) { #ifdef TOOLS_ENABLED if (Engine::get_singleton()->is_editor_hint() && get_tree()->is_node_being_edited(this)) { - get_tree()->call_group_flags(0, SceneStringNames::get_singleton()->_spatial_editor_group, SceneStringNames::get_singleton()->_request_gizmo, this); + get_tree()->call_group_flags(SceneTree::GROUP_CALL_DEFERRED, SceneStringNames::get_singleton()->_spatial_editor_group, SceneStringNames::get_singleton()->_request_gizmo, this); } #endif } break; @@ -212,12 +220,49 @@ void Node3D::set_basis(const Basis &p_basis) { set_transform(Transform3D(p_basis, data.local_transform.origin)); } void Node3D::set_quaternion(const Quaternion &p_quaternion) { - set_transform(Transform3D(Basis(p_quaternion), data.local_transform.origin)); + if (data.dirty & DIRTY_EULER_ROTATION_AND_SCALE) { + // We need the scale part, so if these are dirty, update it + data.scale = data.local_transform.basis.get_scale(); + data.dirty &= ~DIRTY_EULER_ROTATION_AND_SCALE; + } + data.local_transform.basis = Basis(p_quaternion, data.scale); + // Rotscale should not be marked dirty because that would cause precision loss issues with the scale. Instead reconstruct rotation now. + data.euler_rotation = data.local_transform.basis.get_euler_normalized(data.euler_rotation_order); + + data.dirty = DIRTY_NONE; + + _propagate_transform_changed(this); + if (data.notify_local_transform) { + notification(NOTIFICATION_LOCAL_TRANSFORM_CHANGED); + } +} + +Vector3 Node3D::get_global_position() const { + return get_global_transform().get_origin(); +} + +void Node3D::set_global_position(const Vector3 &p_position) { + Transform3D transform = get_global_transform(); + transform.set_origin(p_position); + set_global_transform(transform); +} + +Vector3 Node3D::get_global_rotation() const { + return get_global_transform().get_basis().get_euler(); +} + +void Node3D::set_global_rotation(const Vector3 &p_euler_rad) { + Transform3D transform = get_global_transform(); + Basis new_basis = transform.get_basis(); + new_basis.set_euler(p_euler_rad); + transform.set_basis(new_basis); + set_global_transform(transform); } void Node3D::set_transform(const Transform3D &p_transform) { data.local_transform = p_transform; - data.dirty |= DIRTY_VECTORS; + data.dirty = DIRTY_EULER_ROTATION_AND_SCALE; // Make rot/scale dirty. + _propagate_transform_changed(this); if (data.notify_local_transform) { notification(NOTIFICATION_LOCAL_TRANSFORM_CHANGED); @@ -227,8 +272,13 @@ void Node3D::set_transform(const Transform3D &p_transform) { Basis Node3D::get_basis() const { return get_transform().basis; } + Quaternion Node3D::get_quaternion() const { - return Quaternion(get_transform().basis); + if (data.dirty & DIRTY_LOCAL_TRANSFORM) { + _update_local_transform(); + } + + return data.local_transform.basis.get_rotation_quaternion(); } void Node3D::set_global_transform(const Transform3D &p_transform) { @@ -240,7 +290,7 @@ void Node3D::set_global_transform(const Transform3D &p_transform) { } Transform3D Node3D::get_transform() const { - if (data.dirty & DIRTY_LOCAL) { + if (data.dirty & DIRTY_LOCAL_TRANSFORM) { _update_local_transform(); } @@ -249,8 +299,8 @@ Transform3D Node3D::get_transform() const { Transform3D Node3D::get_global_transform() const { ERR_FAIL_COND_V(!is_inside_tree(), Transform3D()); - if (data.dirty & DIRTY_GLOBAL) { - if (data.dirty & DIRTY_LOCAL) { + if (data.dirty & DIRTY_GLOBAL_TRANSFORM) { + if (data.dirty & DIRTY_LOCAL_TRANSFORM) { _update_local_transform(); } @@ -264,7 +314,7 @@ Transform3D Node3D::get_global_transform() const { data.global_transform.basis.orthonormalize(); } - data.dirty &= ~DIRTY_GLOBAL; + data.dirty &= ~DIRTY_GLOBAL_TRANSFORM; } return data.global_transform; @@ -314,13 +364,27 @@ void Node3D::set_rotation_edit_mode(RotationEditMode p_mode) { if (data.rotation_edit_mode == p_mode) { return; } + + bool transform_changed = false; + if (data.rotation_edit_mode == ROTATION_EDIT_MODE_BASIS && !(data.dirty & DIRTY_LOCAL_TRANSFORM)) { + data.local_transform.orthogonalize(); + transform_changed = true; + } + data.rotation_edit_mode = p_mode; - // Shearing is not allowed except in ROTATION_EDIT_MODE_BASIS. - data.dirty |= DIRTY_LOCAL; - _propagate_transform_changed(this); - if (data.notify_local_transform) { - notification(NOTIFICATION_LOCAL_TRANSFORM_CHANGED); + if (p_mode == ROTATION_EDIT_MODE_EULER && (data.dirty & DIRTY_EULER_ROTATION_AND_SCALE)) { + // If going to Euler mode, ensure that vectors are _not_ dirty, else the retrieved value may be wrong. + // Otherwise keep what is there, so switching back and forth between modes does not break the vectors. + + _update_rotation_and_scale(); + } + + if (transform_changed) { + _propagate_transform_changed(this); + if (data.notify_local_transform) { + notification(NOTIFICATION_LOCAL_TRANSFORM_CHANGED); + } } notify_property_list_changed(); @@ -333,38 +397,47 @@ Node3D::RotationEditMode Node3D::get_rotation_edit_mode() const { void Node3D::set_rotation_order(RotationOrder p_order) { Basis::EulerOrder order = Basis::EulerOrder(p_order); - if (data.rotation_order == order) { + if (data.euler_rotation_order == order) { return; } ERR_FAIL_INDEX(int32_t(order), 6); + bool transform_changed = false; - if (data.dirty & DIRTY_VECTORS) { - data.rotation = data.local_transform.basis.get_euler_normalized(order); - data.scale = data.local_transform.basis.get_scale(); - data.dirty &= ~DIRTY_VECTORS; + if (data.dirty & DIRTY_EULER_ROTATION_AND_SCALE) { + _update_rotation_and_scale(); + } else if (data.dirty & DIRTY_LOCAL_TRANSFORM) { + data.euler_rotation = Basis::from_euler(data.euler_rotation, data.euler_rotation_order).get_euler_normalized(order); + transform_changed = true; } else { - data.rotation = Basis::from_euler(data.rotation, data.rotation_order).get_euler_normalized(order); + data.dirty |= DIRTY_LOCAL_TRANSFORM; + transform_changed = true; } - data.rotation_order = order; - //changing rotation order should not affect transform + data.euler_rotation_order = order; - notify_property_list_changed(); //will change rotation + if (transform_changed) { + _propagate_transform_changed(this); + if (data.notify_local_transform) { + notification(NOTIFICATION_LOCAL_TRANSFORM_CHANGED); + } + } + notify_property_list_changed(); // Will change the rotation property. } Node3D::RotationOrder Node3D::get_rotation_order() const { - return RotationOrder(data.rotation_order); + return RotationOrder(data.euler_rotation_order); } void Node3D::set_rotation(const Vector3 &p_euler_rad) { - if (data.dirty & DIRTY_VECTORS) { + if (data.dirty & DIRTY_EULER_ROTATION_AND_SCALE) { + // Update scale only if rotation and scale are dirty, as rotation will be overridden. data.scale = data.local_transform.basis.get_scale(); - data.dirty &= ~DIRTY_VECTORS; + data.dirty &= ~DIRTY_EULER_ROTATION_AND_SCALE; } - data.rotation = p_euler_rad; - data.dirty |= DIRTY_LOCAL; + data.euler_rotation = p_euler_rad; + data.dirty = DIRTY_LOCAL_TRANSFORM; _propagate_transform_changed(this); if (data.notify_local_transform) { notification(NOTIFICATION_LOCAL_TRANSFORM_CHANGED); @@ -372,13 +445,14 @@ void Node3D::set_rotation(const Vector3 &p_euler_rad) { } void Node3D::set_scale(const Vector3 &p_scale) { - if (data.dirty & DIRTY_VECTORS) { - data.rotation = data.local_transform.basis.get_euler_normalized(data.rotation_order); - data.dirty &= ~DIRTY_VECTORS; + if (data.dirty & DIRTY_EULER_ROTATION_AND_SCALE) { + // Update rotation only if rotation and scale are dirty, as scale will be overridden. + data.euler_rotation = data.local_transform.basis.get_euler_normalized(data.euler_rotation_order); + data.dirty &= ~DIRTY_EULER_ROTATION_AND_SCALE; } data.scale = p_scale; - data.dirty |= DIRTY_LOCAL; + data.dirty = DIRTY_LOCAL_TRANSFORM; _propagate_transform_changed(this); if (data.notify_local_transform) { notification(NOTIFICATION_LOCAL_TRANSFORM_CHANGED); @@ -390,22 +464,16 @@ Vector3 Node3D::get_position() const { } Vector3 Node3D::get_rotation() const { - if (data.dirty & DIRTY_VECTORS) { - data.scale = data.local_transform.basis.get_scale(); - data.rotation = data.local_transform.basis.get_euler_normalized(data.rotation_order); - - data.dirty &= ~DIRTY_VECTORS; + if (data.dirty & DIRTY_EULER_ROTATION_AND_SCALE) { + _update_rotation_and_scale(); } - return data.rotation; + return data.euler_rotation; } Vector3 Node3D::get_scale() const { - if (data.dirty & DIRTY_VECTORS) { - data.scale = data.local_transform.basis.get_scale(); - data.rotation = data.local_transform.basis.get_euler_normalized(data.rotation_order); - - data.dirty &= ~DIRTY_VECTORS; + if (data.dirty & DIRTY_EULER_ROTATION_AND_SCALE) { + _update_rotation_and_scale(); } return data.scale; @@ -418,7 +486,7 @@ void Node3D::update_gizmos() { } if (data.gizmos.is_empty()) { - get_tree()->call_group_flags(0, SceneStringNames::get_singleton()->_spatial_editor_group, SceneStringNames::get_singleton()->_request_gizmo, this); + get_tree()->call_group_flags(SceneTree::GROUP_CALL_DEFERRED, SceneStringNames::get_singleton()->_spatial_editor_group, SceneStringNames::get_singleton()->_request_gizmo, this); return; } if (data.gizmos_dirty) { @@ -436,7 +504,7 @@ void Node3D::set_subgizmo_selection(Ref<Node3DGizmo> p_gizmo, int p_id, Transfor } if (Engine::get_singleton()->is_editor_hint() && get_tree()->is_node_being_edited(this)) { - get_tree()->call_group_flags(0, SceneStringNames::get_singleton()->_spatial_editor_group, SceneStringNames::get_singleton()->_set_subgizmo_selection, this, p_gizmo, p_id, p_transform); + get_tree()->call_group_flags(SceneTree::GROUP_CALL_DEFERRED, SceneStringNames::get_singleton()->_spatial_editor_group, SceneStringNames::get_singleton()->_set_subgizmo_selection, this, p_gizmo, p_id, p_transform); } #endif } @@ -452,7 +520,7 @@ void Node3D::clear_subgizmo_selection() { } if (Engine::get_singleton()->is_editor_hint() && get_tree()->is_node_being_edited(this)) { - get_tree()->call_group_flags(0, SceneStringNames::get_singleton()->_spatial_editor_group, SceneStringNames::get_singleton()->_clear_subgizmo_selection, this); + get_tree()->call_group_flags(SceneTree::GROUP_CALL_DEFERRED, SceneStringNames::get_singleton()->_spatial_editor_group, SceneStringNames::get_singleton()->_clear_subgizmo_selection, this); } #endif } @@ -865,14 +933,14 @@ Variant Node3D::property_get_revert(const String &p_name) { } else if (p_name == "quaternion") { Variant variant = PropertyUtils::get_property_default_value(this, "transform", &valid); if (valid && variant.get_type() == Variant::Type::TRANSFORM3D) { - r_ret = Quaternion(Transform3D(variant).get_basis()); + r_ret = Quaternion(Transform3D(variant).get_basis().get_rotation_quaternion()); } else { return Quaternion(); } } else if (p_name == "rotation") { Variant variant = PropertyUtils::get_property_default_value(this, "transform", &valid); if (valid && variant.get_type() == Variant::Type::TRANSFORM3D) { - r_ret = Transform3D(variant).get_basis().get_euler_normalized(data.rotation_order); + r_ret = Transform3D(variant).get_basis().get_euler_normalized(data.euler_rotation_order); } else { return Vector3(); } @@ -904,8 +972,14 @@ void Node3D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_quaternion"), &Node3D::get_quaternion); ClassDB::bind_method(D_METHOD("set_basis", "basis"), &Node3D::set_basis); ClassDB::bind_method(D_METHOD("get_basis"), &Node3D::get_basis); + ClassDB::bind_method(D_METHOD("set_global_transform", "global"), &Node3D::set_global_transform); ClassDB::bind_method(D_METHOD("get_global_transform"), &Node3D::get_global_transform); + ClassDB::bind_method(D_METHOD("set_global_position", "position"), &Node3D::set_global_position); + ClassDB::bind_method(D_METHOD("get_global_position"), &Node3D::get_global_position); + ClassDB::bind_method(D_METHOD("set_global_rotation", "radians"), &Node3D::set_global_rotation); + ClassDB::bind_method(D_METHOD("get_global_rotation"), &Node3D::get_global_rotation); + ClassDB::bind_method(D_METHOD("get_parent_node_3d"), &Node3D::get_parent_node_3d); ClassDB::bind_method(D_METHOD("set_ignore_transform_notification", "enabled"), &Node3D::set_ignore_transform_notification); ClassDB::bind_method(D_METHOD("set_as_top_level", "enable"), &Node3D::set_as_top_level); @@ -977,18 +1051,20 @@ void Node3D::_bind_methods() { BIND_ENUM_CONSTANT(ROTATION_ORDER_ZXY); BIND_ENUM_CONSTANT(ROTATION_ORDER_ZYX); - //ADD_PROPERTY( PropertyInfo(Variant::TRANSFORM3D,"transform/global",PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR ), "set_global_transform", "get_global_transform") ; ADD_GROUP("Transform", ""); - ADD_PROPERTY(PropertyInfo(Variant::TRANSFORM3D, "transform", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_transform", "get_transform"); - ADD_PROPERTY(PropertyInfo(Variant::TRANSFORM3D, "global_transform", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_global_transform", "get_global_transform"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "position", PROPERTY_HINT_RANGE, "-99999,99999,0.001,or_greater,or_lesser,noslider,suffix:m", PROPERTY_USAGE_EDITOR), "set_position", "get_position"); + ADD_PROPERTY(PropertyInfo(Variant::TRANSFORM3D, "transform", PROPERTY_HINT_NONE, "suffix:m", PROPERTY_USAGE_NO_EDITOR), "set_transform", "get_transform"); + ADD_PROPERTY(PropertyInfo(Variant::TRANSFORM3D, "global_transform", PROPERTY_HINT_NONE, "suffix:m", PROPERTY_USAGE_NONE), "set_global_transform", "get_global_transform"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "position", PROPERTY_HINT_RANGE, "-99999,99999,0.001,or_greater,or_lesser,no_slider,suffix:m", PROPERTY_USAGE_EDITOR), "set_position", "get_position"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "rotation", PROPERTY_HINT_RANGE, "-360,360,0.1,or_lesser,or_greater,radians", PROPERTY_USAGE_EDITOR), "set_rotation", "get_rotation"); ADD_PROPERTY(PropertyInfo(Variant::QUATERNION, "quaternion", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "set_quaternion", "get_quaternion"); ADD_PROPERTY(PropertyInfo(Variant::BASIS, "basis", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "set_basis", "get_basis"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "scale", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "set_scale", "get_scale"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "scale", PROPERTY_HINT_LINK, "", PROPERTY_USAGE_EDITOR), "set_scale", "get_scale"); ADD_PROPERTY(PropertyInfo(Variant::INT, "rotation_edit_mode", PROPERTY_HINT_ENUM, "Euler,Quaternion,Basis"), "set_rotation_edit_mode", "get_rotation_edit_mode"); ADD_PROPERTY(PropertyInfo(Variant::INT, "rotation_order", PROPERTY_HINT_ENUM, "XYZ,XZY,YXZ,YZX,ZXY,ZYX"), "set_rotation_order", "get_rotation_order"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "top_level"), "set_as_top_level", "is_set_as_top_level"); + + ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "global_position", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_global_position", "get_global_position"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "global_rotation", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_global_rotation", "get_global_rotation"); ADD_GROUP("Visibility", ""); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "visible"), "set_visible", "is_visible"); ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "visibility_parent", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "GeometryInstance3D"), "set_visibility_parent", "get_visibility_parent"); diff --git a/scene/3d/node_3d.h b/scene/3d/node_3d.h index 6d857a83ea..b1e129798d 100644 --- a/scene/3d/node_3d.h +++ b/scene/3d/node_3d.h @@ -52,6 +52,9 @@ class Node3D : public Node { GDCLASS(Node3D, Node); public: + // Edit mode for the rotation. + // THIS MODE ONLY AFFECTS HOW DATA IS EDITED AND SAVED + // IT DOES _NOT_ AFFECT THE TRANSFORM LOGIC (see comment in TransformDirty). enum RotationEditMode { ROTATION_EDIT_MODE_EULER, ROTATION_EDIT_MODE_QUATERNION, @@ -68,11 +71,27 @@ public: }; private: + // For the sake of ease of use, Node3D can operate with Transforms (Basis+Origin), Quaterinon/Scale and Euler Rotation/Scale. + // Transform and Quaterinon are stored in data.local_transform Basis (so quaternion is not really stored, but converted back/forth from 3x3 matrix on demand). + // Euler needs to be kept separate because converting to Basis and back may result in a different vector (which is troublesome for users + // editing in the inspector, not only because of the numerical precision loss but because they expect these rotations to be consistent, or support + // "redundant" rotations for animation interpolation, like going from 0 to 720 degrees). + // + // As such, the system works in a way where if the local transform is set (via transform/basis/quaternion), the EULER rotation and scale becomes dirty. + // It will remain dirty until reading back is attempted (for performance reasons). Likewise, if the Euler rotation scale are set, the local transform + // will become dirty (and again, will not become valid again until read). + // + // All this is transparent from outside the Node3D API, which allows everything to works by calling these functions in exchange. + // + // Additionally, setting either transform, quaternion, Euler rotation or scale makes the global transform dirty, which will be updated when read again. + // + // NOTE: Again, RotationEditMode is _independent_ of this mechanism, it is only meant to expose the right set of properties for editing (editor) and saving + // (to scene, in order to keep the same values and avoid data loss on conversions). It has zero influence in the logic described above. enum TransformDirty { DIRTY_NONE = 0, - DIRTY_VECTORS = 1, - DIRTY_LOCAL = 2, - DIRTY_GLOBAL = 4 + DIRTY_EULER_ROTATION_AND_SCALE = 1, + DIRTY_LOCAL_TRANSFORM = 2, + DIRTY_GLOBAL_TRANSFORM = 4 }; mutable SelfList<Node> xform_change; @@ -80,8 +99,8 @@ private: struct Data { mutable Transform3D global_transform; mutable Transform3D local_transform; - mutable Basis::EulerOrder rotation_order = Basis::EULER_ORDER_YXZ; - mutable Vector3 rotation; + mutable Basis::EulerOrder euler_rotation_order = Basis::EULER_ORDER_YXZ; + mutable Vector3 euler_rotation; mutable Vector3 scale = Vector3(1, 1, 1); mutable RotationEditMode rotation_edit_mode = ROTATION_EDIT_MODE_EULER; @@ -131,6 +150,7 @@ protected: _FORCE_INLINE_ void set_ignore_transform_notification(bool p_ignore) { data.ignore_notification = p_ignore; } _FORCE_INLINE_ void _update_local_transform() const; + _FORCE_INLINE_ void _update_rotation_and_scale() const; void _notification(int p_what); static void _bind_methods(); @@ -162,12 +182,18 @@ public: void set_rotation(const Vector3 &p_euler_rad); void set_scale(const Vector3 &p_scale); + void set_global_position(const Vector3 &p_position); + void set_global_rotation(const Vector3 &p_euler_rad); + Vector3 get_position() const; RotationOrder get_rotation_order() const; Vector3 get_rotation() const; Vector3 get_scale() const; + Vector3 get_global_position() const; + Vector3 get_global_rotation() const; + void set_transform(const Transform3D &p_transform); void set_basis(const Basis &p_basis); void set_quaternion(const Quaternion &p_quaternion); diff --git a/scene/3d/occluder_instance_3d.cpp b/scene/3d/occluder_instance_3d.cpp index f848eaab2e..66d0a8c4e2 100644 --- a/scene/3d/occluder_instance_3d.cpp +++ b/scene/3d/occluder_instance_3d.cpp @@ -219,7 +219,7 @@ void QuadOccluder3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_size", "size"), &QuadOccluder3D::set_size); ClassDB::bind_method(D_METHOD("get_size"), &QuadOccluder3D::get_size); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "size"), "set_size", "get_size"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "size", PROPERTY_HINT_NONE, "suffix:m"), "set_size", "get_size"); } QuadOccluder3D::QuadOccluder3D() { @@ -285,7 +285,7 @@ void BoxOccluder3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_size", "size"), &BoxOccluder3D::set_size); ClassDB::bind_method(D_METHOD("get_size"), &BoxOccluder3D::get_size); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "size"), "set_size", "get_size"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "size", PROPERTY_HINT_NONE, "suffix:m"), "set_size", "get_size"); } BoxOccluder3D::BoxOccluder3D() { @@ -354,7 +354,7 @@ void SphereOccluder3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_radius", "radius"), &SphereOccluder3D::set_radius); ClassDB::bind_method(D_METHOD("get_radius"), &SphereOccluder3D::get_radius); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radius"), "set_radius", "get_radius"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radius", PROPERTY_HINT_NONE, "suffix:m"), "set_radius", "get_radius"); } SphereOccluder3D::SphereOccluder3D() { @@ -736,7 +736,7 @@ void OccluderInstance3D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "occluder", PROPERTY_HINT_RESOURCE_TYPE, "Occluder3D"), "set_occluder", "get_occluder"); ADD_GROUP("Bake", "bake_"); ADD_PROPERTY(PropertyInfo(Variant::INT, "bake_mask", PROPERTY_HINT_LAYERS_3D_RENDER), "set_bake_mask", "get_bake_mask"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "bake_simplification_distance", PROPERTY_HINT_RANGE, "0.0,2.0,0.01"), "set_bake_simplification_distance", "get_bake_simplification_distance"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "bake_simplification_distance", PROPERTY_HINT_RANGE, "0.0,2.0,0.01,suffix:m"), "set_bake_simplification_distance", "get_bake_simplification_distance"); } OccluderInstance3D::OccluderInstance3D() { diff --git a/scene/3d/path_3d.cpp b/scene/3d/path_3d.cpp index 7a5cb26a29..1f10337b4c 100644 --- a/scene/3d/path_3d.cpp +++ b/scene/3d/path_3d.cpp @@ -30,6 +30,92 @@ #include "path_3d.h" +Path3D::Path3D() { + SceneTree *st = SceneTree::get_singleton(); + if (st && st->is_debugging_paths_hint()) { + debug_instance = RS::get_singleton()->instance_create(); + set_notify_transform(true); + _update_debug_mesh(); + } +} + +Path3D::~Path3D() { + if (debug_instance.is_valid()) { + RS::get_singleton()->free(debug_instance); + } + if (debug_mesh.is_valid()) { + RS::get_singleton()->free(debug_mesh->get_rid()); + } +} + +void Path3D::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: { + SceneTree *st = SceneTree::get_singleton(); + if (st && st->is_debugging_paths_hint()) { + _update_debug_mesh(); + } + } break; + + case NOTIFICATION_EXIT_TREE: { + SceneTree *st = SceneTree::get_singleton(); + if (st && st->is_debugging_paths_hint()) { + RS::get_singleton()->instance_set_visible(debug_instance, false); + } + } break; + + case NOTIFICATION_TRANSFORM_CHANGED: { + if (is_inside_tree() && debug_instance.is_valid()) { + RS::get_singleton()->instance_set_transform(debug_instance, get_global_transform()); + } + } break; + } +} + +void Path3D::_update_debug_mesh() { + SceneTree *st = SceneTree::get_singleton(); + if (!(st && st->is_debugging_paths_hint())) { + return; + } + + if (!debug_mesh.is_valid()) { + debug_mesh = Ref<ArrayMesh>(memnew(ArrayMesh)); + } + + if (!(curve.is_valid())) { + RS::get_singleton()->instance_set_visible(debug_instance, false); + return; + } + if (curve->get_point_count() < 2) { + RS::get_singleton()->instance_set_visible(debug_instance, false); + return; + } + + Vector<Vector3> vertex_array; + + for (int i = 1; i < curve->get_point_count(); i++) { + Vector3 line_end = curve->get_point_position(i); + Vector3 line_start = curve->get_point_position(i - 1); + vertex_array.push_back(line_start); + vertex_array.push_back(line_end); + } + + Array mesh_array; + mesh_array.resize(Mesh::ARRAY_MAX); + mesh_array[Mesh::ARRAY_VERTEX] = vertex_array; + + debug_mesh->clear_surfaces(); + debug_mesh->add_surface_from_arrays(Mesh::PRIMITIVE_LINES, mesh_array); + + RS::get_singleton()->instance_set_base(debug_instance, debug_mesh->get_rid()); + RS::get_singleton()->mesh_surface_set_material(debug_mesh->get_rid(), 0, st->get_debug_paths_material()->get_rid()); + if (is_inside_tree()) { + RS::get_singleton()->instance_set_scenario(debug_instance, get_world_3d()->get_scenario()); + RS::get_singleton()->instance_set_transform(debug_instance, get_global_transform()); + RS::get_singleton()->instance_set_visible(debug_instance, is_visible_in_tree()); + } +} + void Path3D::_curve_changed() { if (is_inside_tree() && Engine::get_singleton()->is_editor_hint()) { update_gizmos(); @@ -48,6 +134,10 @@ void Path3D::_curve_changed() { } } } + SceneTree *st = SceneTree::get_singleton(); + if (st && st->is_debugging_paths_hint()) { + _update_debug_mesh(); + } } void Path3D::set_curve(const Ref<Curve3D> &p_curve) { @@ -146,7 +236,7 @@ void PathFollow3D::_update_transform(bool p_update_xyz_rot) { Vector3 sideways = up.cross(forward).normalized(); up = forward.cross(sideways).normalized(); - t.basis.set(sideways, up, forward); + t.basis.set_columns(sideways, up, forward); t.basis.scale_local(scale); t.origin = pos + sideways * h_offset + up * v_offset; @@ -157,10 +247,14 @@ void PathFollow3D::_update_transform(bool p_update_xyz_rot) { // for a discussion about why not Frenet frame. t.origin = pos; - - if (p_update_xyz_rot && delta_offset != 0) { // Only update rotation if some parameter has changed - i.e. not on addition to scene tree. - Vector3 t_prev = (pos - c->interpolate_baked(offset - delta_offset, cubic)).normalized(); - Vector3 t_cur = (c->interpolate_baked(offset + delta_offset, cubic) - pos).normalized(); + if (p_update_xyz_rot && prev_offset != offset) { // Only update rotation if some parameter has changed - i.e. not on addition to scene tree. + real_t sample_distance = bi * 0.01; + Vector3 t_prev_pos_a = c->interpolate_baked(prev_offset - sample_distance, cubic); + Vector3 t_prev_pos_b = c->interpolate_baked(prev_offset + sample_distance, cubic); + Vector3 t_cur_pos_a = c->interpolate_baked(offset - sample_distance, cubic); + Vector3 t_cur_pos_b = c->interpolate_baked(offset + sample_distance, cubic); + Vector3 t_prev = (t_prev_pos_a - t_prev_pos_b).normalized(); + Vector3 t_cur = (t_cur_pos_a - t_cur_pos_b).normalized(); Vector3 axis = t_prev.cross(t_cur); real_t dot = t_prev.dot(t_cur); @@ -287,10 +381,10 @@ void PathFollow3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_loop", "loop"), &PathFollow3D::set_loop); ClassDB::bind_method(D_METHOD("has_loop"), &PathFollow3D::has_loop); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "offset", PROPERTY_HINT_RANGE, "0,10000,0.01,or_lesser,or_greater"), "set_offset", "get_offset"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "offset", PROPERTY_HINT_RANGE, "0,10000,0.01,or_lesser,or_greater,suffix:m"), "set_offset", "get_offset"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "unit_offset", PROPERTY_HINT_RANGE, "0,1,0.0001,or_lesser,or_greater", PROPERTY_USAGE_EDITOR), "set_unit_offset", "get_unit_offset"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "h_offset"), "set_h_offset", "get_h_offset"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "v_offset"), "set_v_offset", "get_v_offset"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "h_offset", PROPERTY_HINT_NONE, "suffix:m"), "set_h_offset", "get_h_offset"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "v_offset", PROPERTY_HINT_NONE, "suffix:m"), "set_v_offset", "get_v_offset"); ADD_PROPERTY(PropertyInfo(Variant::INT, "rotation_mode", PROPERTY_HINT_ENUM, "None,Y,XY,XYZ,Oriented"), "set_rotation_mode", "get_rotation_mode"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "cubic_interp"), "set_cubic_interpolation", "get_cubic_interpolation"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "loop"), "set_loop", "has_loop"); @@ -303,14 +397,15 @@ void PathFollow3D::_bind_methods() { } void PathFollow3D::set_offset(real_t p_offset) { - delta_offset = p_offset - offset; + ERR_FAIL_COND(!isfinite(p_offset)); + prev_offset = offset; offset = p_offset; if (path) { if (path->get_curve().is_valid()) { real_t path_length = path->get_curve()->get_baked_length(); - if (loop) { + if (loop && path_length) { offset = Math::fposmod(offset, path_length); if (!Math::is_zero_approx(p_offset) && Math::is_zero_approx(offset)) { offset = path_length; diff --git a/scene/3d/path_3d.h b/scene/3d/path_3d.h index e9ab557693..7c7284534e 100644 --- a/scene/3d/path_3d.h +++ b/scene/3d/path_3d.h @@ -41,14 +41,23 @@ class Path3D : public Node3D { void _curve_changed(); + RID debug_instance; + Ref<ArrayMesh> debug_mesh; + +private: + void _update_debug_mesh(); + protected: + void _notification(int p_what); + static void _bind_methods(); public: void set_curve(const Ref<Curve3D> &p_curve); Ref<Curve3D> get_curve() const; - Path3D() {} + Path3D(); + ~Path3D(); }; class PathFollow3D : public Node3D { @@ -65,7 +74,7 @@ public: private: Path3D *path = nullptr; - real_t delta_offset = 0.0; // Change in offset since last _update_transform. + real_t prev_offset = 0.0; // Offset during the last _update_transform. real_t offset = 0.0; real_t h_offset = 0.0; real_t v_offset = 0.0; diff --git a/scene/3d/physics_body_3d.cpp b/scene/3d/physics_body_3d.cpp index dee76aef10..30f7a025fa 100644 --- a/scene/3d/physics_body_3d.cpp +++ b/scene/3d/physics_body_3d.cpp @@ -94,8 +94,10 @@ void PhysicsBody3D::remove_collision_exception_with(Node *p_node) { Ref<KinematicCollision3D> PhysicsBody3D::_move(const Vector3 &p_distance, bool p_test_only, real_t p_margin, int p_max_collisions) { PhysicsServer3D::MotionParameters parameters(get_global_transform(), p_distance, p_margin); parameters.max_collisions = p_max_collisions; + parameters.recovery_as_collision = false; // Don't report collisions generated only from recovery. PhysicsServer3D::MotionResult result; + if (move_and_collide(parameters, result, p_test_only)) { // Create a new instance when the cached reference is invalid or still in use in script. if (motion_cache.is_null() || motion_cache->reference_get_count() > 1) { @@ -180,15 +182,9 @@ bool PhysicsBody3D::test_move(const Transform3D &p_from, const Vector3 &p_distan } PhysicsServer3D::MotionParameters parameters(p_from, p_distance, p_margin); + parameters.recovery_as_collision = false; // Don't report collisions generated only from recovery. - 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; - } + return PhysicsServer3D::get_singleton()->body_test_motion(get_rid(), parameters, r); } void PhysicsBody3D::set_axis_lock(PhysicsServer3D::BodyAxis p_axis, bool p_lock) { @@ -265,8 +261,8 @@ void StaticBody3D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_physics_material_override"), &StaticBody3D::get_physics_material_override); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "physics_material_override", PROPERTY_HINT_RESOURCE_TYPE, "PhysicsMaterial"), "set_physics_material_override", "get_physics_material_override"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "constant_linear_velocity"), "set_constant_linear_velocity", "get_constant_linear_velocity"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "constant_angular_velocity"), "set_constant_angular_velocity", "get_constant_angular_velocity"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "constant_linear_velocity", PROPERTY_HINT_NONE, "suffix:m/s"), "set_constant_linear_velocity", "get_constant_linear_velocity"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "constant_angular_velocity", PROPERTY_HINT_NONE, U"radians,suffix:\u00B0/s"), "set_constant_angular_velocity", "get_constant_angular_velocity"); } StaticBody3D::StaticBody3D(PhysicsServer3D::BodyMode p_mode) : @@ -386,18 +382,18 @@ void RigidDynamicBody3D::_body_enter_tree(ObjectID p_id) { ERR_FAIL_COND(!node); ERR_FAIL_COND(!contact_monitor); - Map<ObjectID, BodyState>::Element *E = contact_monitor->body_map.find(p_id); + HashMap<ObjectID, BodyState>::Iterator E = contact_monitor->body_map.find(p_id); ERR_FAIL_COND(!E); - ERR_FAIL_COND(E->get().in_tree); + ERR_FAIL_COND(E->value.in_tree); - E->get().in_tree = true; + E->value.in_tree = true; contact_monitor->locked = true; emit_signal(SceneStringNames::get_singleton()->body_entered, node); - for (int i = 0; i < E->get().shapes.size(); i++) { - emit_signal(SceneStringNames::get_singleton()->body_shape_entered, E->get().rid, node, E->get().shapes[i].body_shape, E->get().shapes[i].local_shape); + for (int i = 0; i < E->value.shapes.size(); i++) { + emit_signal(SceneStringNames::get_singleton()->body_shape_entered, E->value.rid, node, E->value.shapes[i].body_shape, E->value.shapes[i].local_shape); } contact_monitor->locked = false; @@ -408,17 +404,17 @@ void RigidDynamicBody3D::_body_exit_tree(ObjectID p_id) { Node *node = Object::cast_to<Node>(obj); ERR_FAIL_COND(!node); ERR_FAIL_COND(!contact_monitor); - Map<ObjectID, BodyState>::Element *E = contact_monitor->body_map.find(p_id); + HashMap<ObjectID, BodyState>::Iterator E = contact_monitor->body_map.find(p_id); ERR_FAIL_COND(!E); - ERR_FAIL_COND(!E->get().in_tree); - E->get().in_tree = false; + ERR_FAIL_COND(!E->value.in_tree); + E->value.in_tree = false; contact_monitor->locked = true; emit_signal(SceneStringNames::get_singleton()->body_exited, node); - for (int i = 0; i < E->get().shapes.size(); i++) { - emit_signal(SceneStringNames::get_singleton()->body_shape_exited, E->get().rid, node, E->get().shapes[i].body_shape, E->get().shapes[i].local_shape); + for (int i = 0; i < E->value.shapes.size(); i++) { + emit_signal(SceneStringNames::get_singleton()->body_shape_exited, E->value.rid, node, E->value.shapes[i].body_shape, E->value.shapes[i].local_shape); } contact_monitor->locked = false; @@ -432,43 +428,43 @@ void RigidDynamicBody3D::_body_inout(int p_status, const RID &p_body, ObjectID p Node *node = Object::cast_to<Node>(obj); ERR_FAIL_COND(!contact_monitor); - Map<ObjectID, BodyState>::Element *E = contact_monitor->body_map.find(objid); + HashMap<ObjectID, BodyState>::Iterator E = contact_monitor->body_map.find(objid); ERR_FAIL_COND(!body_in && !E); if (body_in) { if (!E) { E = contact_monitor->body_map.insert(objid, BodyState()); - E->get().rid = p_body; - //E->get().rc=0; - E->get().in_tree = node && node->is_inside_tree(); + E->value.rid = p_body; + //E->value.rc=0; + E->value.in_tree = node && node->is_inside_tree(); if (node) { node->connect(SceneStringNames::get_singleton()->tree_entered, callable_mp(this, &RigidDynamicBody3D::_body_enter_tree), make_binds(objid)); node->connect(SceneStringNames::get_singleton()->tree_exiting, callable_mp(this, &RigidDynamicBody3D::_body_exit_tree), make_binds(objid)); - if (E->get().in_tree) { + if (E->value.in_tree) { emit_signal(SceneStringNames::get_singleton()->body_entered, node); } } } - //E->get().rc++; + //E->value.rc++; if (node) { - E->get().shapes.insert(ShapePair(p_body_shape, p_local_shape)); + E->value.shapes.insert(ShapePair(p_body_shape, p_local_shape)); } - if (E->get().in_tree) { + if (E->value.in_tree) { emit_signal(SceneStringNames::get_singleton()->body_shape_entered, p_body, node, p_body_shape, p_local_shape); } } else { - //E->get().rc--; + //E->value.rc--; if (node) { - E->get().shapes.erase(ShapePair(p_body_shape, p_local_shape)); + E->value.shapes.erase(ShapePair(p_body_shape, p_local_shape)); } - bool in_tree = E->get().in_tree; + bool in_tree = E->value.in_tree; - if (E->get().shapes.is_empty()) { + if (E->value.shapes.is_empty()) { if (node) { node->disconnect(SceneStringNames::get_singleton()->tree_entered, callable_mp(this, &RigidDynamicBody3D::_body_enter_tree)); node->disconnect(SceneStringNames::get_singleton()->tree_exiting, callable_mp(this, &RigidDynamicBody3D::_body_exit_tree)); @@ -477,7 +473,7 @@ void RigidDynamicBody3D::_body_inout(int p_status, const RID &p_body, ObjectID p } } - contact_monitor->body_map.erase(E); + contact_monitor->body_map.remove(E); } if (node && in_tree) { emit_signal(SceneStringNames::get_singleton()->body_shape_exited, p_body, obj, p_body_shape, p_local_shape); @@ -543,7 +539,7 @@ void RigidDynamicBody3D::_body_state_changed(PhysicsDirectBodyState3D *p_state) //bool found=false; - Map<ObjectID, BodyState>::Element *E = contact_monitor->body_map.find(obj); + HashMap<ObjectID, BodyState>::Iterator E = contact_monitor->body_map.find(obj); if (!E) { toadd[toadd_count].rid = rid; toadd[toadd_count].local_shape = local_shape; @@ -554,7 +550,7 @@ void RigidDynamicBody3D::_body_state_changed(PhysicsDirectBodyState3D *p_state) } ShapePair sp(shape, local_shape); - int idx = E->get().shapes.find(sp); + int idx = E->value.shapes.find(sp); if (idx == -1) { toadd[toadd_count].rid = rid; toadd[toadd_count].local_shape = local_shape; @@ -564,7 +560,7 @@ void RigidDynamicBody3D::_body_state_changed(PhysicsDirectBodyState3D *p_state) continue; } - E->get().shapes[idx].tagged = true; + E->value.shapes[idx].tagged = true; } //put the ones to remove @@ -987,7 +983,7 @@ TypedArray<String> RigidDynamicBody3D::get_configuration_warnings() const { TypedArray<String> warnings = Node::get_configuration_warnings(); - if (ABS(t.basis.get_axis(0).length() - 1.0) > 0.05 || ABS(t.basis.get_axis(1).length() - 1.0) > 0.05 || ABS(t.basis.get_axis(2).length() - 1.0) > 0.05) { + if (ABS(t.basis.get_column(0).length() - 1.0) > 0.05 || ABS(t.basis.get_column(1).length() - 1.0) > 0.05 || ABS(t.basis.get_column(2).length() - 1.0) > 0.05) { warnings.push_back(RTR("Size changes to RigidDynamicBody will be overridden by the physics engine when running.\nChange the size in children collision shapes instead.")); } @@ -1084,10 +1080,10 @@ void RigidDynamicBody3D::_bind_methods() { GDVIRTUAL_BIND(_integrate_forces, "state"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "mass", PROPERTY_HINT_RANGE, "0.01,1000,0.01,or_greater,exp"), "set_mass", "get_mass"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "inertia", PROPERTY_HINT_RANGE, "0,1000,0.01,or_greater,exp"), "set_inertia", "get_inertia"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "mass", PROPERTY_HINT_RANGE, "0.01,1000,0.01,or_greater,exp,suffix:kg"), "set_mass", "get_mass"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "inertia", PROPERTY_HINT_RANGE, U"0,1000,0.01,or_greater,exp,suffix:kg\u22C5m\u00B2"), "set_inertia", "get_inertia"); ADD_PROPERTY(PropertyInfo(Variant::INT, "center_of_mass_mode", PROPERTY_HINT_ENUM, "Auto,Custom", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), "set_center_of_mass_mode", "get_center_of_mass_mode"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "center_of_mass", PROPERTY_HINT_RANGE, "-10,10,0.01,or_lesser,or_greater"), "set_center_of_mass", "get_center_of_mass"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "center_of_mass", PROPERTY_HINT_RANGE, "-10,10,0.01,or_lesser,or_greater,suffix:m"), "set_center_of_mass", "get_center_of_mass"); ADD_LINKED_PROPERTY("center_of_mass_mode", "center_of_mass"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "physics_material_override", PROPERTY_HINT_RESOURCE_TYPE, "PhysicsMaterial"), "set_physics_material_override", "get_physics_material_override"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "gravity_scale", PROPERTY_HINT_RANGE, "-128,128,0.01"), "set_gravity_scale", "get_gravity_scale"); @@ -1101,11 +1097,11 @@ void RigidDynamicBody3D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "freeze"), "set_freeze_enabled", "is_freeze_enabled"); ADD_PROPERTY(PropertyInfo(Variant::INT, "freeze_mode", PROPERTY_HINT_ENUM, "Static,Kinematic"), "set_freeze_mode", "get_freeze_mode"); ADD_GROUP("Linear", "linear_"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "linear_velocity"), "set_linear_velocity", "get_linear_velocity"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "linear_velocity", PROPERTY_HINT_NONE, "suffix:m/s"), "set_linear_velocity", "get_linear_velocity"); ADD_PROPERTY(PropertyInfo(Variant::INT, "linear_damp_mode", PROPERTY_HINT_ENUM, "Combine,Replace"), "set_linear_damp_mode", "get_linear_damp_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", "angular_"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "angular_velocity"), "set_angular_velocity", "get_angular_velocity"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "angular_velocity", PROPERTY_HINT_NONE, U"radians,suffix:\u00B0/s"), "set_angular_velocity", "get_angular_velocity"); ADD_PROPERTY(PropertyInfo(Variant::INT, "angular_damp_mode", PROPERTY_HINT_ENUM, "Combine,Replace"), "set_angular_damp_mode", "get_angular_damp_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("Constant Forces", "constant_"); @@ -1210,6 +1206,8 @@ bool CharacterBody3D::move_and_slide() { if (!current_platform_velocity.is_equal_approx(Vector3())) { PhysicsServer3D::MotionParameters parameters(get_global_transform(), current_platform_velocity * delta, margin); + parameters.recovery_as_collision = true; // Also report collisions generated only from recovery. + parameters.exclude_bodies.insert(platform_rid); if (platform_object_id.is_valid()) { parameters.exclude_objects.insert(platform_object_id); @@ -1273,6 +1271,7 @@ void CharacterBody3D::_move_and_slide_grounded(double p_delta, bool p_was_on_flo for (int iteration = 0; iteration < max_slides; ++iteration) { PhysicsServer3D::MotionParameters parameters(get_global_transform(), motion, margin); parameters.max_collisions = 4; + parameters.recovery_as_collision = true; // Also report collisions generated only from recovery. PhysicsServer3D::MotionResult result; bool collided = move_and_collide(parameters, result, false, !sliding_enabled); @@ -1517,6 +1516,7 @@ void CharacterBody3D::_move_and_slide_floating(double p_delta) { bool first_slide = true; for (int iteration = 0; iteration < max_slides; ++iteration) { PhysicsServer3D::MotionParameters parameters(get_global_transform(), motion, margin); + parameters.recovery_as_collision = true; // Also report collisions generated only from recovery. PhysicsServer3D::MotionResult result; bool collided = move_and_collide(parameters, result, false, false); @@ -1561,8 +1561,8 @@ void CharacterBody3D::_move_and_slide_floating(double p_delta) { } } -void CharacterBody3D::_snap_on_floor(bool was_on_floor, bool vel_dir_facing_up) { - if (collision_state.floor || !was_on_floor || vel_dir_facing_up) { +void CharacterBody3D::_snap_on_floor(bool p_was_on_floor, bool p_vel_dir_facing_up) { + if (collision_state.floor || !p_was_on_floor || p_vel_dir_facing_up) { return; } @@ -1571,6 +1571,7 @@ void CharacterBody3D::_snap_on_floor(bool was_on_floor, bool vel_dir_facing_up) PhysicsServer3D::MotionParameters parameters(get_global_transform(), -up_direction * length, margin); parameters.max_collisions = 4; + parameters.recovery_as_collision = true; // Also report collisions generated only from recovery. parameters.collide_separation_ray = true; PhysicsServer3D::MotionResult result; @@ -1596,8 +1597,8 @@ void CharacterBody3D::_snap_on_floor(bool was_on_floor, bool vel_dir_facing_up) } } -bool CharacterBody3D::_on_floor_if_snapped(bool was_on_floor, bool vel_dir_facing_up) { - if (up_direction == Vector3() || collision_state.floor || !was_on_floor || vel_dir_facing_up) { +bool CharacterBody3D::_on_floor_if_snapped(bool p_was_on_floor, bool p_vel_dir_facing_up) { + if (up_direction == Vector3() || collision_state.floor || !p_was_on_floor || p_vel_dir_facing_up) { return false; } @@ -1606,6 +1607,7 @@ bool CharacterBody3D::_on_floor_if_snapped(bool was_on_floor, bool vel_dir_facin PhysicsServer3D::MotionParameters parameters(get_global_transform(), -up_direction * length, margin); parameters.max_collisions = 4; + parameters.recovery_as_collision = true; // Also report collisions generated only from recovery. parameters.collide_separation_ray = true; PhysicsServer3D::MotionResult result; @@ -1997,7 +1999,7 @@ void CharacterBody3D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::INT, "motion_mode", PROPERTY_HINT_ENUM, "Grounded,Floating", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), "set_motion_mode", "get_motion_mode"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "up_direction"), "set_up_direction", "get_up_direction"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "slide_on_ceiling"), "set_slide_on_ceiling_enabled", "is_slide_on_ceiling_enabled"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "velocity", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_velocity", "get_velocity"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "velocity", PROPERTY_HINT_NONE, "suffix:m/s", PROPERTY_USAGE_NO_EDITOR), "set_velocity", "get_velocity"); ADD_PROPERTY(PropertyInfo(Variant::INT, "max_slides", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_max_slides", "get_max_slides"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "wall_min_slide_angle", PROPERTY_HINT_RANGE, "0,180,0.1,radians", PROPERTY_USAGE_DEFAULT), "set_wall_min_slide_angle", "get_wall_min_slide_angle"); ADD_GROUP("Floor", "floor_"); @@ -2005,12 +2007,12 @@ void CharacterBody3D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "floor_constant_speed"), "set_floor_constant_speed_enabled", "is_floor_constant_speed_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "floor_block_on_wall"), "set_floor_block_on_wall_enabled", "is_floor_block_on_wall_enabled"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "floor_max_angle", PROPERTY_HINT_RANGE, "0,180,0.1,radians"), "set_floor_max_angle", "get_floor_max_angle"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "floor_snap_length", PROPERTY_HINT_RANGE, "0,1,0.01,or_greater"), "set_floor_snap_length", "get_floor_snap_length"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "floor_snap_length", PROPERTY_HINT_RANGE, "0,1,0.01,or_greater,suffix:m"), "set_floor_snap_length", "get_floor_snap_length"); ADD_GROUP("Moving Platform", "moving_platform"); ADD_PROPERTY(PropertyInfo(Variant::INT, "moving_platform_apply_velocity_on_leave", PROPERTY_HINT_ENUM, "Always,Upward Only,Never", PROPERTY_USAGE_DEFAULT), "set_moving_platform_apply_velocity_on_leave", "get_moving_platform_apply_velocity_on_leave"); ADD_PROPERTY(PropertyInfo(Variant::INT, "moving_platform_floor_layers", PROPERTY_HINT_LAYERS_2D_PHYSICS), "set_moving_platform_floor_layers", "get_moving_platform_floor_layers"); ADD_PROPERTY(PropertyInfo(Variant::INT, "moving_platform_wall_layers", PROPERTY_HINT_LAYERS_2D_PHYSICS), "set_moving_platform_wall_layers", "get_moving_platform_wall_layers"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "collision/safe_margin", PROPERTY_HINT_RANGE, "0.001,256,0.001"), "set_safe_margin", "get_safe_margin"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "collision/safe_margin", PROPERTY_HINT_RANGE, "0.001,256,0.001,suffix:m"), "set_safe_margin", "get_safe_margin"); BIND_ENUM_CONSTANT(MOTION_MODE_GROUNDED); BIND_ENUM_CONSTANT(MOTION_MODE_FLOATING); @@ -2259,9 +2261,9 @@ bool PhysicalBone3D::PinJointData::_get(const StringName &p_name, Variant &r_ret void PhysicalBone3D::PinJointData::_get_property_list(List<PropertyInfo> *p_list) const { JointData::_get_property_list(p_list); - p_list->push_back(PropertyInfo(Variant::FLOAT, "joint_constraints/bias", PROPERTY_HINT_RANGE, "0.01,0.99,0.01")); - p_list->push_back(PropertyInfo(Variant::FLOAT, "joint_constraints/damping", PROPERTY_HINT_RANGE, "0.01,8.0,0.01")); - p_list->push_back(PropertyInfo(Variant::FLOAT, "joint_constraints/impulse_clamp", PROPERTY_HINT_RANGE, "0.0,64.0,0.01")); + p_list->push_back(PropertyInfo(Variant::FLOAT, PNAME("joint_constraints/bias"), PROPERTY_HINT_RANGE, "0.01,0.99,0.01")); + p_list->push_back(PropertyInfo(Variant::FLOAT, PNAME("joint_constraints/damping"), PROPERTY_HINT_RANGE, "0.01,8.0,0.01")); + p_list->push_back(PropertyInfo(Variant::FLOAT, PNAME("joint_constraints/impulse_clamp"), PROPERTY_HINT_RANGE, "0.0,64.0,0.01")); } bool PhysicalBone3D::ConeJointData::_set(const StringName &p_name, const Variant &p_value, RID j) { @@ -2331,11 +2333,11 @@ bool PhysicalBone3D::ConeJointData::_get(const StringName &p_name, Variant &r_re void PhysicalBone3D::ConeJointData::_get_property_list(List<PropertyInfo> *p_list) const { JointData::_get_property_list(p_list); - p_list->push_back(PropertyInfo(Variant::FLOAT, "joint_constraints/swing_span", PROPERTY_HINT_RANGE, "-180,180,0.01")); - p_list->push_back(PropertyInfo(Variant::FLOAT, "joint_constraints/twist_span", PROPERTY_HINT_RANGE, "-40000,40000,0.1,or_lesser,or_greater")); - p_list->push_back(PropertyInfo(Variant::FLOAT, "joint_constraints/bias", PROPERTY_HINT_RANGE, "0.01,16.0,0.01")); - p_list->push_back(PropertyInfo(Variant::FLOAT, "joint_constraints/softness", PROPERTY_HINT_RANGE, "0.01,16.0,0.01")); - p_list->push_back(PropertyInfo(Variant::FLOAT, "joint_constraints/relaxation", PROPERTY_HINT_RANGE, "0.01,16.0,0.01")); + p_list->push_back(PropertyInfo(Variant::FLOAT, PNAME("joint_constraints/swing_span"), PROPERTY_HINT_RANGE, "-180,180,0.01")); + p_list->push_back(PropertyInfo(Variant::FLOAT, PNAME("joint_constraints/twist_span"), PROPERTY_HINT_RANGE, "-40000,40000,0.1,or_lesser,or_greater")); + p_list->push_back(PropertyInfo(Variant::FLOAT, PNAME("joint_constraints/bias"), PROPERTY_HINT_RANGE, "0.01,16.0,0.01")); + p_list->push_back(PropertyInfo(Variant::FLOAT, PNAME("joint_constraints/softness"), PROPERTY_HINT_RANGE, "0.01,16.0,0.01")); + p_list->push_back(PropertyInfo(Variant::FLOAT, PNAME("joint_constraints/relaxation"), PROPERTY_HINT_RANGE, "0.01,16.0,0.01")); } bool PhysicalBone3D::HingeJointData::_set(const StringName &p_name, const Variant &p_value, RID j) { @@ -2413,12 +2415,12 @@ bool PhysicalBone3D::HingeJointData::_get(const StringName &p_name, Variant &r_r void PhysicalBone3D::HingeJointData::_get_property_list(List<PropertyInfo> *p_list) const { JointData::_get_property_list(p_list); - p_list->push_back(PropertyInfo(Variant::BOOL, "joint_constraints/angular_limit_enabled")); - p_list->push_back(PropertyInfo(Variant::FLOAT, "joint_constraints/angular_limit_upper", PROPERTY_HINT_RANGE, "-180,180,0.01")); - p_list->push_back(PropertyInfo(Variant::FLOAT, "joint_constraints/angular_limit_lower", PROPERTY_HINT_RANGE, "-180,180,0.01")); - p_list->push_back(PropertyInfo(Variant::FLOAT, "joint_constraints/angular_limit_bias", PROPERTY_HINT_RANGE, "0.01,0.99,0.01")); - p_list->push_back(PropertyInfo(Variant::FLOAT, "joint_constraints/angular_limit_softness", PROPERTY_HINT_RANGE, "0.01,16,0.01")); - p_list->push_back(PropertyInfo(Variant::FLOAT, "joint_constraints/angular_limit_relaxation", PROPERTY_HINT_RANGE, "0.01,16,0.01")); + p_list->push_back(PropertyInfo(Variant::BOOL, PNAME("joint_constraints/angular_limit_enabled"))); + p_list->push_back(PropertyInfo(Variant::FLOAT, PNAME("joint_constraints/angular_limit_upper"), PROPERTY_HINT_RANGE, "-180,180,0.01")); + p_list->push_back(PropertyInfo(Variant::FLOAT, PNAME("joint_constraints/angular_limit_lower"), PROPERTY_HINT_RANGE, "-180,180,0.01")); + p_list->push_back(PropertyInfo(Variant::FLOAT, PNAME("joint_constraints/angular_limit_bias"), PROPERTY_HINT_RANGE, "0.01,0.99,0.01")); + p_list->push_back(PropertyInfo(Variant::FLOAT, PNAME("joint_constraints/angular_limit_softness"), PROPERTY_HINT_RANGE, "0.01,16,0.01")); + p_list->push_back(PropertyInfo(Variant::FLOAT, PNAME("joint_constraints/angular_limit_relaxation"), PROPERTY_HINT_RANGE, "0.01,16,0.01")); } bool PhysicalBone3D::SliderJointData::_set(const StringName &p_name, const Variant &p_value, RID j) { @@ -2528,17 +2530,17 @@ bool PhysicalBone3D::SliderJointData::_get(const StringName &p_name, Variant &r_ void PhysicalBone3D::SliderJointData::_get_property_list(List<PropertyInfo> *p_list) const { JointData::_get_property_list(p_list); - p_list->push_back(PropertyInfo(Variant::FLOAT, "joint_constraints/linear_limit_upper")); - p_list->push_back(PropertyInfo(Variant::FLOAT, "joint_constraints/linear_limit_lower")); - p_list->push_back(PropertyInfo(Variant::FLOAT, "joint_constraints/linear_limit_softness", PROPERTY_HINT_RANGE, "0.01,16.0,0.01")); - p_list->push_back(PropertyInfo(Variant::FLOAT, "joint_constraints/linear_limit_restitution", PROPERTY_HINT_RANGE, "0.01,16.0,0.01")); - p_list->push_back(PropertyInfo(Variant::FLOAT, "joint_constraints/linear_limit_damping", PROPERTY_HINT_RANGE, "0,16.0,0.01")); + p_list->push_back(PropertyInfo(Variant::FLOAT, PNAME("joint_constraints/linear_limit_upper"))); + p_list->push_back(PropertyInfo(Variant::FLOAT, PNAME("joint_constraints/linear_limit_lower"))); + p_list->push_back(PropertyInfo(Variant::FLOAT, PNAME("joint_constraints/linear_limit_softness"), PROPERTY_HINT_RANGE, "0.01,16.0,0.01")); + p_list->push_back(PropertyInfo(Variant::FLOAT, PNAME("joint_constraints/linear_limit_restitution"), PROPERTY_HINT_RANGE, "0.01,16.0,0.01")); + p_list->push_back(PropertyInfo(Variant::FLOAT, PNAME("joint_constraints/linear_limit_damping"), PROPERTY_HINT_RANGE, "0,16.0,0.01")); - p_list->push_back(PropertyInfo(Variant::FLOAT, "joint_constraints/angular_limit_upper", PROPERTY_HINT_RANGE, "-180,180,0.01")); - p_list->push_back(PropertyInfo(Variant::FLOAT, "joint_constraints/angular_limit_lower", PROPERTY_HINT_RANGE, "-180,180,0.01")); - p_list->push_back(PropertyInfo(Variant::FLOAT, "joint_constraints/angular_limit_softness", PROPERTY_HINT_RANGE, "0.01,16.0,0.01")); - p_list->push_back(PropertyInfo(Variant::FLOAT, "joint_constraints/angular_limit_restitution", PROPERTY_HINT_RANGE, "0.01,16.0,0.01")); - p_list->push_back(PropertyInfo(Variant::FLOAT, "joint_constraints/angular_limit_damping", PROPERTY_HINT_RANGE, "0,16.0,0.01")); + p_list->push_back(PropertyInfo(Variant::FLOAT, PNAME("joint_constraints/angular_limit_upper"), PROPERTY_HINT_RANGE, "-180,180,0.01")); + p_list->push_back(PropertyInfo(Variant::FLOAT, PNAME("joint_constraints/angular_limit_lower"), PROPERTY_HINT_RANGE, "-180,180,0.01")); + p_list->push_back(PropertyInfo(Variant::FLOAT, PNAME("joint_constraints/angular_limit_softness"), PROPERTY_HINT_RANGE, "0.01,16.0,0.01")); + p_list->push_back(PropertyInfo(Variant::FLOAT, PNAME("joint_constraints/angular_limit_restitution"), PROPERTY_HINT_RANGE, "0.01,16.0,0.01")); + p_list->push_back(PropertyInfo(Variant::FLOAT, PNAME("joint_constraints/angular_limit_damping"), PROPERTY_HINT_RANGE, "0,16.0,0.01")); } bool PhysicalBone3D::SixDOFJointData::_set(const StringName &p_name, const Variant &p_value, RID j) { @@ -2778,29 +2780,30 @@ bool PhysicalBone3D::SixDOFJointData::_get(const StringName &p_name, Variant &r_ } void PhysicalBone3D::SixDOFJointData::_get_property_list(List<PropertyInfo> *p_list) const { - const StringName axis_names[] = { "x", "y", "z" }; + const StringName axis_names[] = { PNAME("x"), PNAME("y"), PNAME("z") }; for (int i = 0; i < 3; ++i) { - p_list->push_back(PropertyInfo(Variant::BOOL, "joint_constraints/" + axis_names[i] + "/linear_limit_enabled")); - p_list->push_back(PropertyInfo(Variant::FLOAT, "joint_constraints/" + axis_names[i] + "/linear_limit_upper")); - p_list->push_back(PropertyInfo(Variant::FLOAT, "joint_constraints/" + axis_names[i] + "/linear_limit_lower")); - p_list->push_back(PropertyInfo(Variant::FLOAT, "joint_constraints/" + axis_names[i] + "/linear_limit_softness", PROPERTY_HINT_RANGE, "0.01,16,0.01")); - p_list->push_back(PropertyInfo(Variant::BOOL, "joint_constraints/" + axis_names[i] + "/linear_spring_enabled")); - p_list->push_back(PropertyInfo(Variant::FLOAT, "joint_constraints/" + axis_names[i] + "/linear_spring_stiffness")); - p_list->push_back(PropertyInfo(Variant::FLOAT, "joint_constraints/" + axis_names[i] + "/linear_spring_damping")); - p_list->push_back(PropertyInfo(Variant::FLOAT, "joint_constraints/" + axis_names[i] + "/linear_equilibrium_point")); - p_list->push_back(PropertyInfo(Variant::FLOAT, "joint_constraints/" + axis_names[i] + "/linear_restitution", PROPERTY_HINT_RANGE, "0.01,16,0.01")); - p_list->push_back(PropertyInfo(Variant::FLOAT, "joint_constraints/" + axis_names[i] + "/linear_damping", PROPERTY_HINT_RANGE, "0.01,16,0.01")); - p_list->push_back(PropertyInfo(Variant::BOOL, "joint_constraints/" + axis_names[i] + "/angular_limit_enabled")); - p_list->push_back(PropertyInfo(Variant::FLOAT, "joint_constraints/" + axis_names[i] + "/angular_limit_upper", PROPERTY_HINT_RANGE, "-180,180,0.01")); - p_list->push_back(PropertyInfo(Variant::FLOAT, "joint_constraints/" + axis_names[i] + "/angular_limit_lower", PROPERTY_HINT_RANGE, "-180,180,0.01")); - p_list->push_back(PropertyInfo(Variant::FLOAT, "joint_constraints/" + axis_names[i] + "/angular_limit_softness", PROPERTY_HINT_RANGE, "0.01,16,0.01")); - p_list->push_back(PropertyInfo(Variant::FLOAT, "joint_constraints/" + axis_names[i] + "/angular_restitution", PROPERTY_HINT_RANGE, "0.01,16,0.01")); - p_list->push_back(PropertyInfo(Variant::FLOAT, "joint_constraints/" + axis_names[i] + "/angular_damping", PROPERTY_HINT_RANGE, "0.01,16,0.01")); - p_list->push_back(PropertyInfo(Variant::FLOAT, "joint_constraints/" + axis_names[i] + "/erp")); - p_list->push_back(PropertyInfo(Variant::BOOL, "joint_constraints/" + axis_names[i] + "/angular_spring_enabled")); - p_list->push_back(PropertyInfo(Variant::FLOAT, "joint_constraints/" + axis_names[i] + "/angular_spring_stiffness")); - p_list->push_back(PropertyInfo(Variant::FLOAT, "joint_constraints/" + axis_names[i] + "/angular_spring_damping")); - p_list->push_back(PropertyInfo(Variant::FLOAT, "joint_constraints/" + axis_names[i] + "/angular_equilibrium_point")); + const String prefix = vformat("%s/%s/", PNAME("joint_constraints"), axis_names[i]); + p_list->push_back(PropertyInfo(Variant::BOOL, prefix + PNAME("linear_limit_enabled"))); + p_list->push_back(PropertyInfo(Variant::FLOAT, prefix + PNAME("linear_limit_upper"))); + p_list->push_back(PropertyInfo(Variant::FLOAT, prefix + PNAME("linear_limit_lower"))); + p_list->push_back(PropertyInfo(Variant::FLOAT, prefix + PNAME("linear_limit_softness"), PROPERTY_HINT_RANGE, "0.01,16,0.01")); + p_list->push_back(PropertyInfo(Variant::BOOL, prefix + PNAME("linear_spring_enabled"))); + p_list->push_back(PropertyInfo(Variant::FLOAT, prefix + PNAME("linear_spring_stiffness"))); + p_list->push_back(PropertyInfo(Variant::FLOAT, prefix + PNAME("linear_spring_damping"))); + p_list->push_back(PropertyInfo(Variant::FLOAT, prefix + PNAME("linear_equilibrium_point"))); + p_list->push_back(PropertyInfo(Variant::FLOAT, prefix + PNAME("linear_restitution"), PROPERTY_HINT_RANGE, "0.01,16,0.01")); + p_list->push_back(PropertyInfo(Variant::FLOAT, prefix + PNAME("linear_damping"), PROPERTY_HINT_RANGE, "0.01,16,0.01")); + p_list->push_back(PropertyInfo(Variant::BOOL, prefix + PNAME("angular_limit_enabled"))); + p_list->push_back(PropertyInfo(Variant::FLOAT, prefix + PNAME("angular_limit_upper"), PROPERTY_HINT_RANGE, "-180,180,0.01")); + p_list->push_back(PropertyInfo(Variant::FLOAT, prefix + PNAME("angular_limit_lower"), PROPERTY_HINT_RANGE, "-180,180,0.01")); + p_list->push_back(PropertyInfo(Variant::FLOAT, prefix + PNAME("angular_limit_softness"), PROPERTY_HINT_RANGE, "0.01,16,0.01")); + p_list->push_back(PropertyInfo(Variant::FLOAT, prefix + PNAME("angular_restitution"), PROPERTY_HINT_RANGE, "0.01,16,0.01")); + p_list->push_back(PropertyInfo(Variant::FLOAT, prefix + PNAME("angular_damping"), PROPERTY_HINT_RANGE, "0.01,16,0.01")); + p_list->push_back(PropertyInfo(Variant::FLOAT, prefix + PNAME("erp"))); + p_list->push_back(PropertyInfo(Variant::BOOL, prefix + PNAME("angular_spring_enabled"))); + p_list->push_back(PropertyInfo(Variant::FLOAT, prefix + PNAME("angular_spring_stiffness"))); + p_list->push_back(PropertyInfo(Variant::FLOAT, prefix + PNAME("angular_spring_damping"))); + p_list->push_back(PropertyInfo(Variant::FLOAT, prefix + PNAME("angular_equilibrium_point"))); } } @@ -2847,9 +2850,9 @@ void PhysicalBone3D::_get_property_list(List<PropertyInfo> *p_list) const { names += parent->get_bone_name(i); } - p_list->push_back(PropertyInfo(Variant::STRING_NAME, "bone_name", PROPERTY_HINT_ENUM, names)); + p_list->push_back(PropertyInfo(Variant::STRING_NAME, PNAME("bone_name"), PROPERTY_HINT_ENUM, names)); } else { - p_list->push_back(PropertyInfo(Variant::STRING_NAME, "bone_name")); + p_list->push_back(PropertyInfo(Variant::STRING_NAME, PNAME("bone_name"))); } if (joint_data) { @@ -2981,12 +2984,12 @@ void PhysicalBone3D::_bind_methods() { ADD_GROUP("Joint", "joint_"); ADD_PROPERTY(PropertyInfo(Variant::INT, "joint_type", PROPERTY_HINT_ENUM, "None,PinJoint,ConeJoint,HingeJoint,SliderJoint,6DOFJoint"), "set_joint_type", "get_joint_type"); - ADD_PROPERTY(PropertyInfo(Variant::TRANSFORM3D, "joint_offset"), "set_joint_offset", "get_joint_offset"); + ADD_PROPERTY(PropertyInfo(Variant::TRANSFORM3D, "joint_offset", PROPERTY_HINT_NONE, "suffix:m"), "set_joint_offset", "get_joint_offset"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "joint_rotation", PROPERTY_HINT_RANGE, "-360,360,0.01,or_lesser,or_greater,radians"), "set_joint_rotation", "get_joint_rotation"); - ADD_PROPERTY(PropertyInfo(Variant::TRANSFORM3D, "body_offset"), "set_body_offset", "get_body_offset"); + ADD_PROPERTY(PropertyInfo(Variant::TRANSFORM3D, "body_offset", PROPERTY_HINT_NONE, "suffix:m"), "set_body_offset", "get_body_offset"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "mass", PROPERTY_HINT_RANGE, "0.01,1000,0.01,or_greater,exp"), "set_mass", "get_mass"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "mass", PROPERTY_HINT_RANGE, "0.01,1000,0.01,or_greater,exp,suffix:kg"), "set_mass", "get_mass"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "friction", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_friction", "get_friction"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "bounce", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_bounce", "get_bounce"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "gravity_scale", PROPERTY_HINT_RANGE, "-10,10,0.01"), "set_gravity_scale", "get_gravity_scale"); @@ -2995,8 +2998,8 @@ void PhysicalBone3D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "linear_damp", PROPERTY_HINT_RANGE, "0,100,0.001,or_greater"), "set_linear_damp", "get_linear_damp"); ADD_PROPERTY(PropertyInfo(Variant::INT, "angular_damp_mode", PROPERTY_HINT_ENUM, "Combine,Replace"), "set_angular_damp_mode", "get_angular_damp_mode"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "angular_damp", PROPERTY_HINT_RANGE, "0,100,0.001,or_greater"), "set_angular_damp", "get_angular_damp"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "linear_velocity"), "set_linear_velocity", "get_linear_velocity"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "angular_velocity"), "set_angular_velocity", "get_angular_velocity"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "linear_velocity", PROPERTY_HINT_NONE, "suffix:m/s"), "set_linear_velocity", "get_linear_velocity"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "angular_velocity", PROPERTY_HINT_NONE, U"radians,suffix:\u00B0/s"), "set_angular_velocity", "get_angular_velocity"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "can_sleep"), "set_can_sleep", "is_able_to_sleep"); BIND_ENUM_CONSTANT(DAMP_MODE_COMBINE); diff --git a/scene/3d/physics_body_3d.h b/scene/3d/physics_body_3d.h index 6ace681021..22dcb218bc 100644 --- a/scene/3d/physics_body_3d.h +++ b/scene/3d/physics_body_3d.h @@ -212,7 +212,7 @@ private: struct ContactMonitor { bool locked = false; - Map<ObjectID, BodyState> body_map; + HashMap<ObjectID, BodyState> body_map; }; ContactMonitor *contact_monitor = nullptr; @@ -474,11 +474,11 @@ private: Ref<KinematicCollision3D> _get_slide_collision(int p_bounce); Ref<KinematicCollision3D> _get_last_slide_collision(); const Vector3 &get_up_direction() const; - bool _on_floor_if_snapped(bool was_on_floor, bool vel_dir_facing_up); + bool _on_floor_if_snapped(bool p_was_on_floor, bool p_vel_dir_facing_up); void set_up_direction(const Vector3 &p_up_direction); void _set_collision_direction(const PhysicsServer3D::MotionResult &p_result, CollisionState &r_state, CollisionState p_apply_state = CollisionState(true, true, true)); void _set_platform_data(const PhysicsServer3D::MotionCollision &p_collision); - void _snap_on_floor(bool was_on_floor, bool vel_dir_facing_up); + void _snap_on_floor(bool p_was_on_floor, bool p_vel_dir_facing_up); protected: void _notification(int p_what); @@ -714,7 +714,9 @@ public: const JointData *get_joint_data() const; Skeleton3D *find_skeleton_parent(); - int get_bone_id() const { return bone_id; } + int get_bone_id() const { + return bone_id; + } void set_joint_type(JointType p_joint_type); JointType get_joint_type() const; diff --git a/scene/3d/ray_cast_3d.cpp b/scene/3d/ray_cast_3d.cpp index f5e08b92ca..2db5ab2d4e 100644 --- a/scene/3d/ray_cast_3d.cpp +++ b/scene/3d/ray_cast_3d.cpp @@ -340,7 +340,7 @@ void RayCast3D::_bind_methods() { 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::VECTOR3, "target_position"), "set_target_position", "get_target_position"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "target_position", PROPERTY_HINT_NONE, "suffix:m"), "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"); diff --git a/scene/3d/ray_cast_3d.h b/scene/3d/ray_cast_3d.h index ad85001591..c69c910efb 100644 --- a/scene/3d/ray_cast_3d.h +++ b/scene/3d/ray_cast_3d.h @@ -46,7 +46,7 @@ class RayCast3D : public Node3D { Vector3 collision_normal; Vector3 target_position = Vector3(0, -1, 0); - Set<RID> exclude; + HashSet<RID> exclude; uint32_t collision_mask = 1; bool exclude_parent_body = true; diff --git a/scene/3d/reflection_probe.cpp b/scene/3d/reflection_probe.cpp index 1bebd8e335..0a9d6cbbeb 100644 --- a/scene/3d/reflection_probe.cpp +++ b/scene/3d/reflection_probe.cpp @@ -229,9 +229,9 @@ void ReflectionProbe::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::INT, "update_mode", PROPERTY_HINT_ENUM, "Once (Fast),Always (Slow)"), "set_update_mode", "get_update_mode"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "intensity", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_intensity", "get_intensity"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "max_distance", PROPERTY_HINT_RANGE, "0,16384,0.1,or_greater,exp"), "set_max_distance", "get_max_distance"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "extents"), "set_extents", "get_extents"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "origin_offset"), "set_origin_offset", "get_origin_offset"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "max_distance", PROPERTY_HINT_RANGE, "0,16384,0.1,or_greater,exp,suffix:m"), "set_max_distance", "get_max_distance"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "extents", PROPERTY_HINT_NONE, "suffix:m"), "set_extents", "get_extents"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "origin_offset", PROPERTY_HINT_NONE, "suffix:m"), "set_origin_offset", "get_origin_offset"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "box_projection"), "set_enable_box_projection", "is_box_projection_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "interior"), "set_as_interior", "is_set_as_interior"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "enable_shadows"), "set_enable_shadows", "are_shadows_enabled"); diff --git a/scene/3d/skeleton_3d.cpp b/scene/3d/skeleton_3d.cpp index 783edf7fc6..b342660b85 100644 --- a/scene/3d/skeleton_3d.cpp +++ b/scene/3d/skeleton_3d.cpp @@ -153,14 +153,14 @@ bool Skeleton3D::_get(const StringName &p_path, Variant &r_ret) const { void Skeleton3D::_get_property_list(List<PropertyInfo> *p_list) const { for (int i = 0; i < bones.size(); i++) { - String prep = "bones/" + itos(i) + "/"; - p_list->push_back(PropertyInfo(Variant::STRING, prep + "name", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR)); - p_list->push_back(PropertyInfo(Variant::INT, prep + "parent", PROPERTY_HINT_RANGE, "-1," + itos(bones.size() - 1) + ",1", PROPERTY_USAGE_NO_EDITOR)); - p_list->push_back(PropertyInfo(Variant::TRANSFORM3D, prep + "rest", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR)); - p_list->push_back(PropertyInfo(Variant::BOOL, prep + "enabled", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR)); - p_list->push_back(PropertyInfo(Variant::VECTOR3, prep + "position", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR)); - p_list->push_back(PropertyInfo(Variant::QUATERNION, prep + "rotation", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR)); - p_list->push_back(PropertyInfo(Variant::VECTOR3, prep + "scale", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR)); + const String prep = vformat("%s/%d/", PNAME("bones"), i); + p_list->push_back(PropertyInfo(Variant::STRING, prep + PNAME("name"), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR)); + p_list->push_back(PropertyInfo(Variant::INT, prep + PNAME("parent"), PROPERTY_HINT_RANGE, "-1," + itos(bones.size() - 1) + ",1", PROPERTY_USAGE_NO_EDITOR)); + p_list->push_back(PropertyInfo(Variant::TRANSFORM3D, prep + PNAME("rest"), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR)); + p_list->push_back(PropertyInfo(Variant::BOOL, prep + PNAME("enabled"), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR)); + p_list->push_back(PropertyInfo(Variant::VECTOR3, prep + PNAME("position"), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR)); + p_list->push_back(PropertyInfo(Variant::QUATERNION, prep + PNAME("rotation"), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR)); + p_list->push_back(PropertyInfo(Variant::VECTOR3, prep + PNAME("scale"), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR)); } #ifndef _3D_DISABLED @@ -263,19 +263,19 @@ void Skeleton3D::_notification(int p_what) { force_update_all_bone_transforms(); // Update skins. - for (Set<SkinReference *>::Element *E = skin_bindings.front(); E; E = E->next()) { - const Skin *skin = E->get()->skin.operator->(); - RID skeleton = E->get()->skeleton; + for (SkinReference *E : skin_bindings) { + const Skin *skin = E->skin.operator->(); + RID skeleton = E->skeleton; uint32_t bind_count = skin->get_bind_count(); - if (E->get()->bind_count != bind_count) { + if (E->bind_count != bind_count) { RS::get_singleton()->skeleton_allocate_data(skeleton, bind_count); - E->get()->bind_count = bind_count; - E->get()->skin_bone_indices.resize(bind_count); - E->get()->skin_bone_indices_ptrs = E->get()->skin_bone_indices.ptrw(); + E->bind_count = bind_count; + E->skin_bone_indices.resize(bind_count); + E->skin_bone_indices_ptrs = E->skin_bone_indices.ptrw(); } - if (E->get()->skeleton_version != version) { + if (E->skeleton_version != version) { for (uint32_t i = 0; i < bind_count; i++) { StringName bind_name = skin->get_bind_name(i); @@ -284,7 +284,7 @@ void Skeleton3D::_notification(int p_what) { bool found = false; for (int j = 0; j < len; j++) { if (bonesptr[j].name == bind_name) { - E->get()->skin_bone_indices_ptrs[i] = j; + E->skin_bone_indices_ptrs[i] = j; found = true; break; } @@ -292,27 +292,27 @@ void Skeleton3D::_notification(int p_what) { if (!found) { ERR_PRINT("Skin bind #" + itos(i) + " contains named bind '" + String(bind_name) + "' but Skeleton3D has no bone by that name."); - E->get()->skin_bone_indices_ptrs[i] = 0; + E->skin_bone_indices_ptrs[i] = 0; } } else if (skin->get_bind_bone(i) >= 0) { int bind_index = skin->get_bind_bone(i); if (bind_index >= len) { ERR_PRINT("Skin bind #" + itos(i) + " contains bone index bind: " + itos(bind_index) + " , which is greater than the skeleton bone count: " + itos(len) + "."); - E->get()->skin_bone_indices_ptrs[i] = 0; + E->skin_bone_indices_ptrs[i] = 0; } else { - E->get()->skin_bone_indices_ptrs[i] = bind_index; + E->skin_bone_indices_ptrs[i] = bind_index; } } else { ERR_PRINT("Skin bind #" + itos(i) + " does not contain a name nor a bone index."); - E->get()->skin_bone_indices_ptrs[i] = 0; + E->skin_bone_indices_ptrs[i] = 0; } } - E->get()->skeleton_version = version; + E->skeleton_version = version; } for (uint32_t i = 0; i < bind_count; i++) { - uint32_t bone_index = E->get()->skin_bone_indices_ptrs[i]; + uint32_t bone_index = E->skin_bone_indices_ptrs[i]; ERR_CONTINUE(bone_index >= (uint32_t)len); rs->skeleton_bone_set_transform(skeleton, i, bonesptr[bone_index].pose_global * skin->get_bind_pose(i)); } @@ -326,13 +326,11 @@ void Skeleton3D::_notification(int p_what) { case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: { // This is active only if the skeleton animates the physical bones // and the state of the bone is not active. - if (Engine::get_singleton()->is_editor_hint()) { - if (animate_physical_bones) { - for (int i = 0; i < bones.size(); i += 1) { - if (bones[i].physical_bone) { - if (bones[i].physical_bone->is_simulating_physics() == false) { - bones[i].physical_bone->reset_to_rest_position(); - } + if (animate_physical_bones) { + for (int i = 0; i < bones.size(); i += 1) { + if (bones[i].physical_bone) { + if (bones[i].physical_bone->is_simulating_physics() == false) { + bones[i].physical_bone->reset_to_rest_position(); } } } @@ -509,6 +507,7 @@ void Skeleton3D::add_bone(const String &p_name) { bones.push_back(b); process_order_dirty = true; version++; + rest_dirty = true; _make_dirty(); update_gizmos(); } @@ -567,6 +566,7 @@ void Skeleton3D::set_bone_parent(int p_bone, int p_parent) { bones.write[p_bone].parent = p_parent; process_order_dirty = true; + rest_dirty = true; _make_dirty(); } @@ -585,6 +585,7 @@ void Skeleton3D::unparent_bone_and_rest(int p_bone) { bones.write[p_bone].parent = -1; process_order_dirty = true; + rest_dirty = true; _make_dirty(); } @@ -607,6 +608,7 @@ void Skeleton3D::set_bone_children(int p_bone, Vector<int> p_children) { bones.write[p_bone].child_bones = p_children; process_order_dirty = true; + rest_dirty = true; _make_dirty(); } @@ -616,6 +618,7 @@ void Skeleton3D::add_bone_child(int p_bone, int p_child) { bones.write[p_bone].child_bones.push_back(p_child); process_order_dirty = true; + rest_dirty = true; _make_dirty(); } @@ -631,10 +634,12 @@ void Skeleton3D::remove_bone_child(int p_bone, int p_child) { } process_order_dirty = true; + rest_dirty = true; _make_dirty(); } Vector<int> Skeleton3D::get_parentless_bones() { + _update_process_order(); return parentless_bones; } @@ -643,6 +648,7 @@ void Skeleton3D::set_bone_rest(int p_bone, const Transform3D &p_rest) { ERR_FAIL_INDEX(p_bone, bone_size); bones.write[p_bone].rest = p_rest; + rest_dirty = true; _make_dirty(); } Transform3D Skeleton3D::get_bone_rest(int p_bone) const { @@ -651,6 +657,14 @@ Transform3D Skeleton3D::get_bone_rest(int p_bone) const { return bones[p_bone].rest; } +Transform3D Skeleton3D::get_bone_global_rest(int p_bone) const { + const int bone_size = bones.size(); + ERR_FAIL_INDEX_V(p_bone, bone_size, Transform3D()); + if (rest_dirty) { + const_cast<Skeleton3D *>(this)->notification(NOTIFICATION_UPDATE_SKELETON); + } + return bones[p_bone].global_rest; +} void Skeleton3D::set_bone_enabled(int p_bone, bool p_enabled) { const int bone_size = bones.size(); @@ -752,8 +766,6 @@ void Skeleton3D::_make_dirty() { } void Skeleton3D::localize_rests() { - _update_process_order(); - Vector<int> bones_to_process = get_parentless_bones(); while (bones_to_process.size() > 0) { int current_bone_idx = bones_to_process[0]; @@ -945,7 +957,6 @@ Ref<Skin> Skeleton3D::create_skin_from_rest_transforms() { skin.instantiate(); skin->set_bind_count(bones.size()); - _update_process_order(); // Just in case. // Pose changed, rebuild cache of inverses. const Bone *bonesptr = bones.ptr(); @@ -985,9 +996,9 @@ Ref<Skin> Skeleton3D::create_skin_from_rest_transforms() { Ref<SkinReference> Skeleton3D::register_skin(const Ref<Skin> &p_skin) { ERR_FAIL_COND_V(p_skin.is_null(), Ref<SkinReference>()); - for (Set<SkinReference *>::Element *E = skin_bindings.front(); E; E = E->next()) { - if (E->get()->skin == p_skin) { - return Ref<SkinReference>(E->get()); + for (const SkinReference *E : skin_bindings) { + if (E->skin == p_skin) { + return Ref<SkinReference>(E); } } @@ -1058,6 +1069,9 @@ void Skeleton3D::force_update_bone_children_transforms(int p_bone_idx) { b.pose_global_no_override = b.pose_global; } } + if (rest_dirty) { + b.global_rest = b.parent >= 0 ? bonesptr[b.parent].global_rest * b.rest : b.rest; + } if (b.local_pose_override_amount >= CMP_EPSILON) { Transform3D override_local_pose; @@ -1088,6 +1102,7 @@ void Skeleton3D::force_update_bone_children_transforms(int p_bone_idx) { emit_signal(SceneStringNames::get_singleton()->bone_pose_changed, current_bone_idx); } + rest_dirty = false; } // Helper functions @@ -1206,6 +1221,7 @@ void Skeleton3D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_bone_rest", "bone_idx"), &Skeleton3D::get_bone_rest); ClassDB::bind_method(D_METHOD("set_bone_rest", "bone_idx", "rest"), &Skeleton3D::set_bone_rest); + ClassDB::bind_method(D_METHOD("get_bone_global_rest", "bone_idx"), &Skeleton3D::get_bone_global_rest); ClassDB::bind_method(D_METHOD("create_skin_from_rest_transforms"), &Skeleton3D::create_skin_from_rest_transforms); ClassDB::bind_method(D_METHOD("register_skin", "skin"), &Skeleton3D::register_skin); @@ -1283,7 +1299,7 @@ Skeleton3D::Skeleton3D() { Skeleton3D::~Skeleton3D() { // Some skins may remain bound. - for (Set<SkinReference *>::Element *E = skin_bindings.front(); E; E = E->next()) { - E->get()->skeleton_node = nullptr; + for (SkinReference *E : skin_bindings) { + E->skeleton_node = nullptr; } } diff --git a/scene/3d/skeleton_3d.h b/scene/3d/skeleton_3d.h index 279c3e49a2..cb4c82d232 100644 --- a/scene/3d/skeleton_3d.h +++ b/scene/3d/skeleton_3d.h @@ -77,6 +77,7 @@ private: int parent; Transform3D rest; + Transform3D global_rest; _FORCE_INLINE_ void update_pose_cache() { if (pose_cache_dirty) { @@ -130,18 +131,19 @@ private: } }; - Set<SkinReference *> skin_bindings; + HashSet<SkinReference *> skin_bindings; void _skin_changed(); bool animate_physical_bones = true; Vector<Bone> bones; - bool process_order_dirty; + bool process_order_dirty = false; Vector<int> parentless_bones; void _make_dirty(); bool dirty = false; + bool rest_dirty = false; bool show_rest_only = false; @@ -198,6 +200,7 @@ public: void set_bone_rest(int p_bone, const Transform3D &p_rest); Transform3D get_bone_rest(int p_bone) const; + Transform3D get_bone_global_rest(int p_bone) const; Transform3D get_bone_global_pose(int p_bone) const; Transform3D get_bone_global_pose_no_override(int p_bone) const; diff --git a/scene/3d/skeleton_ik_3d.cpp b/scene/3d/skeleton_ik_3d.cpp index f29b060069..2182c5ba11 100644 --- a/scene/3d/skeleton_ik_3d.cpp +++ b/scene/3d/skeleton_ik_3d.cpp @@ -140,7 +140,7 @@ void FabrikInverseKinematic::solve_simple(Task *p_task, bool p_solve_magnet, Vec } } -void FabrikInverseKinematic::solve_simple_backwards(Chain &r_chain, bool p_solve_magnet) { +void FabrikInverseKinematic::solve_simple_backwards(const Chain &r_chain, bool p_solve_magnet) { if (p_solve_magnet && !r_chain.middle_chain_item) { return; } @@ -391,12 +391,12 @@ void SkeletonIK3D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "root_bone"), "set_root_bone", "get_root_bone"); ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "tip_bone"), "set_tip_bone", "get_tip_bone"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "interpolation", PROPERTY_HINT_RANGE, "0,1,0.001"), "set_interpolation", "get_interpolation"); - ADD_PROPERTY(PropertyInfo(Variant::TRANSFORM3D, "target"), "set_target_transform", "get_target_transform"); + ADD_PROPERTY(PropertyInfo(Variant::TRANSFORM3D, "target", PROPERTY_HINT_NONE, "suffix:m"), "set_target_transform", "get_target_transform"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "override_tip_basis"), "set_override_tip_basis", "is_override_tip_basis"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_magnet"), "set_use_magnet", "is_using_magnet"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "magnet"), "set_magnet_position", "get_magnet_position"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "magnet", PROPERTY_HINT_NONE, "suffix:m"), "set_magnet_position", "get_magnet_position"); ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "target_node"), "set_target_node", "get_target_node"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "min_distance"), "set_min_distance", "get_min_distance"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "min_distance", PROPERTY_HINT_NONE, "suffix:m"), "set_min_distance", "get_min_distance"); ADD_PROPERTY(PropertyInfo(Variant::INT, "max_iterations"), "set_max_iterations", "get_max_iterations"); } diff --git a/scene/3d/skeleton_ik_3d.h b/scene/3d/skeleton_ik_3d.h index 3ced5c49d3..0f656187de 100644 --- a/scene/3d/skeleton_ik_3d.h +++ b/scene/3d/skeleton_ik_3d.h @@ -103,7 +103,7 @@ private: static void solve_simple(Task *p_task, bool p_solve_magnet, Vector3 p_origin_pos); /// Special solvers that solve only chains with one end effector - static void solve_simple_backwards(Chain &r_chain, bool p_solve_magnet); + static void solve_simple_backwards(const Chain &r_chain, bool p_solve_magnet); static void solve_simple_forwards(Chain &r_chain, bool p_solve_magnet, Vector3 p_origin_pos); public: diff --git a/scene/3d/soft_dynamic_body_3d.cpp b/scene/3d/soft_dynamic_body_3d.cpp index 6724754214..d68e7fd527 100644 --- a/scene/3d/soft_dynamic_body_3d.cpp +++ b/scene/3d/soft_dynamic_body_3d.cpp @@ -162,12 +162,13 @@ bool SoftDynamicBody3D::_get(const StringName &p_name, Variant &r_ret) const { void SoftDynamicBody3D::_get_property_list(List<PropertyInfo> *p_list) const { const int pinned_points_indices_size = pinned_points.size(); - p_list->push_back(PropertyInfo(Variant::PACKED_INT32_ARRAY, "pinned_points")); + p_list->push_back(PropertyInfo(Variant::PACKED_INT32_ARRAY, PNAME("pinned_points"))); for (int i = 0; i < pinned_points_indices_size; ++i) { - p_list->push_back(PropertyInfo(Variant::INT, "attachments/" + itos(i) + "/point_index")); - p_list->push_back(PropertyInfo(Variant::NODE_PATH, "attachments/" + itos(i) + "/spatial_attachment_path")); - p_list->push_back(PropertyInfo(Variant::VECTOR3, "attachments/" + itos(i) + "/offset")); + const String prefix = vformat("%s/%d/", PNAME("attachments"), i); + p_list->push_back(PropertyInfo(Variant::INT, prefix + PNAME("point_index"))); + p_list->push_back(PropertyInfo(Variant::NODE_PATH, prefix + PNAME("spatial_attachment_path"))); + p_list->push_back(PropertyInfo(Variant::VECTOR3, prefix + PNAME("offset"))); } } @@ -382,7 +383,7 @@ TypedArray<String> SoftDynamicBody3D::get_configuration_warnings() const { } Transform3D t = get_transform(); - if ((ABS(t.basis.get_axis(0).length() - 1.0) > 0.05 || ABS(t.basis.get_axis(1).length() - 1.0) > 0.05 || ABS(t.basis.get_axis(2).length() - 1.0) > 0.05)) { + if ((ABS(t.basis.get_column(0).length() - 1.0) > 0.05 || ABS(t.basis.get_column(1).length() - 1.0) > 0.05 || ABS(t.basis.get_column(2).length() - 1.0) > 0.05)) { warnings.push_back(RTR("Size changes to SoftDynamicBody3D will be overridden by the physics engine when running.\nChange the size in children collision shapes instead.")); } diff --git a/scene/3d/spring_arm_3d.cpp b/scene/3d/spring_arm_3d.cpp index 230801bd52..f855fce318 100644 --- a/scene/3d/spring_arm_3d.cpp +++ b/scene/3d/spring_arm_3d.cpp @@ -73,8 +73,8 @@ void SpringArm3D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::INT, "collision_mask", PROPERTY_HINT_LAYERS_3D_PHYSICS), "set_collision_mask", "get_collision_mask"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "shape", PROPERTY_HINT_RESOURCE_TYPE, "Shape3D"), "set_shape", "get_shape"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "spring_length"), "set_length", "get_length"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "margin"), "set_margin", "get_margin"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "spring_length", PROPERTY_HINT_NONE, "suffix:m"), "set_length", "get_length"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "margin", PROPERTY_HINT_NONE, "suffix:m"), "set_margin", "get_margin"); } real_t SpringArm3D::get_length() const { diff --git a/scene/3d/spring_arm_3d.h b/scene/3d/spring_arm_3d.h index b247ea1707..0b5307acf7 100644 --- a/scene/3d/spring_arm_3d.h +++ b/scene/3d/spring_arm_3d.h @@ -37,7 +37,7 @@ class SpringArm3D : public Node3D { GDCLASS(SpringArm3D, Node3D); Ref<Shape3D> shape; - Set<RID> excluded_objects; + HashSet<RID> excluded_objects; real_t spring_length = 1.0; real_t current_spring_length = 0.0; bool keep_child_basis = false; diff --git a/scene/3d/sprite_3d.cpp b/scene/3d/sprite_3d.cpp index 8d813e8b2b..cb6354f7a8 100644 --- a/scene/3d/sprite_3d.cpp +++ b/scene/3d/sprite_3d.cpp @@ -134,6 +134,16 @@ Color SpriteBase3D::get_modulate() const { return modulate; } +void SpriteBase3D::set_render_priority(int p_priority) { + ERR_FAIL_COND(p_priority < RS::MATERIAL_RENDER_PRIORITY_MIN || p_priority > RS::MATERIAL_RENDER_PRIORITY_MAX); + render_priority = p_priority; + _queue_update(); +} + +int SpriteBase3D::get_render_priority() const { + return render_priority; +} + void SpriteBase3D::set_pixel_size(real_t p_amount) { pixel_size = p_amount; _queue_update(); @@ -268,6 +278,17 @@ StandardMaterial3D::BillboardMode SpriteBase3D::get_billboard_mode() const { return billboard_mode; } +void SpriteBase3D::set_texture_filter(StandardMaterial3D::TextureFilter p_filter) { + if (texture_filter != p_filter) { + texture_filter = p_filter; + _queue_update(); + } +} + +StandardMaterial3D::TextureFilter SpriteBase3D::get_texture_filter() const { + return texture_filter; +} + void SpriteBase3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_centered", "centered"), &SpriteBase3D::set_centered); ClassDB::bind_method(D_METHOD("is_centered"), &SpriteBase3D::is_centered); @@ -284,6 +305,9 @@ void SpriteBase3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_modulate", "modulate"), &SpriteBase3D::set_modulate); ClassDB::bind_method(D_METHOD("get_modulate"), &SpriteBase3D::get_modulate); + ClassDB::bind_method(D_METHOD("set_render_priority", "priority"), &SpriteBase3D::set_render_priority); + ClassDB::bind_method(D_METHOD("get_render_priority"), &SpriteBase3D::get_render_priority); + ClassDB::bind_method(D_METHOD("set_pixel_size", "pixel_size"), &SpriteBase3D::set_pixel_size); ClassDB::bind_method(D_METHOD("get_pixel_size"), &SpriteBase3D::get_pixel_size); @@ -299,6 +323,9 @@ void SpriteBase3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_billboard_mode", "mode"), &SpriteBase3D::set_billboard_mode); ClassDB::bind_method(D_METHOD("get_billboard_mode"), &SpriteBase3D::get_billboard_mode); + ClassDB::bind_method(D_METHOD("set_texture_filter", "mode"), &SpriteBase3D::set_texture_filter); + ClassDB::bind_method(D_METHOD("get_texture_filter"), &SpriteBase3D::get_texture_filter); + ClassDB::bind_method(D_METHOD("get_item_rect"), &SpriteBase3D::get_item_rect); ClassDB::bind_method(D_METHOD("generate_triangle_mesh"), &SpriteBase3D::generate_triangle_mesh); @@ -306,22 +333,28 @@ void SpriteBase3D::_bind_methods() { ClassDB::bind_method(D_METHOD("_im_update"), &SpriteBase3D::_im_update); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "centered"), "set_centered", "is_centered"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "offset"), "set_offset", "get_offset"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "offset", PROPERTY_HINT_NONE, "suffix:px"), "set_offset", "get_offset"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "flip_h"), "set_flip_h", "is_flipped_h"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "flip_v"), "set_flip_v", "is_flipped_v"); ADD_PROPERTY(PropertyInfo(Variant::COLOR, "modulate"), "set_modulate", "get_modulate"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "pixel_size", PROPERTY_HINT_RANGE, "0.0001,128,0.0001"), "set_pixel_size", "get_pixel_size"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "pixel_size", PROPERTY_HINT_RANGE, "0.0001,128,0.0001,suffix:m"), "set_pixel_size", "get_pixel_size"); ADD_PROPERTY(PropertyInfo(Variant::INT, "axis", PROPERTY_HINT_ENUM, "X-Axis,Y-Axis,Z-Axis"), "set_axis", "get_axis"); ADD_GROUP("Flags", ""); ADD_PROPERTY(PropertyInfo(Variant::INT, "billboard", PROPERTY_HINT_ENUM, "Disabled,Enabled,Y-Billboard"), "set_billboard_mode", "get_billboard_mode"); ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "transparent"), "set_draw_flag", "get_draw_flag", FLAG_TRANSPARENT); ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "shaded"), "set_draw_flag", "get_draw_flag", FLAG_SHADED); ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "double_sided"), "set_draw_flag", "get_draw_flag", FLAG_DOUBLE_SIDED); + ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "no_depth_test"), "set_draw_flag", "get_draw_flag", FLAG_DISABLE_DEPTH_TEST); + ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "fixed_size"), "set_draw_flag", "get_draw_flag", FLAG_FIXED_SIZE); ADD_PROPERTY(PropertyInfo(Variant::INT, "alpha_cut", PROPERTY_HINT_ENUM, "Disabled,Discard,Opaque Pre-Pass"), "set_alpha_cut_mode", "get_alpha_cut_mode"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "texture_filter", PROPERTY_HINT_ENUM, "Nearest,Linear,Nearest Mipmap,Linear Mipmap,Nearest Mipmap Anisotropic,Linear Mipmap Anisotropic"), "set_texture_filter", "get_texture_filter"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "render_priority", PROPERTY_HINT_RANGE, itos(RS::MATERIAL_RENDER_PRIORITY_MIN) + "," + itos(RS::MATERIAL_RENDER_PRIORITY_MAX) + ",1"), "set_render_priority", "get_render_priority"); BIND_ENUM_CONSTANT(FLAG_TRANSPARENT); BIND_ENUM_CONSTANT(FLAG_SHADED); BIND_ENUM_CONSTANT(FLAG_DOUBLE_SIDED); + BIND_ENUM_CONSTANT(FLAG_DISABLE_DEPTH_TEST); + BIND_ENUM_CONSTANT(FLAG_FIXED_SIZE); BIND_ENUM_CONSTANT(FLAG_MAX); BIND_ENUM_CONSTANT(ALPHA_CUT_DISABLED); @@ -544,7 +577,7 @@ void Sprite3D::_draw() { value |= CLAMP(int((t.normal.y * 0.5 + 0.5) * 1023.0), 0, 1023) << 10; value |= CLAMP(int((t.normal.z * 0.5 + 0.5) * 1023.0), 0, 1023) << 20; if (t.d > 0) { - value |= 3 << 30; + value |= 3UL << 30; } v_tangent = value; } @@ -586,7 +619,7 @@ void Sprite3D::_draw() { set_aabb(aabb); RID shader_rid; - StandardMaterial3D::get_material_for_2d(get_draw_flag(FLAG_SHADED), get_draw_flag(FLAG_TRANSPARENT), get_draw_flag(FLAG_DOUBLE_SIDED), get_alpha_cut_mode() == ALPHA_CUT_DISCARD, get_alpha_cut_mode() == ALPHA_CUT_OPAQUE_PREPASS, get_billboard_mode() == StandardMaterial3D::BILLBOARD_ENABLED, get_billboard_mode() == StandardMaterial3D::BILLBOARD_FIXED_Y, &shader_rid); + StandardMaterial3D::get_material_for_2d(get_draw_flag(FLAG_SHADED), get_draw_flag(FLAG_TRANSPARENT), get_draw_flag(FLAG_DOUBLE_SIDED), get_alpha_cut_mode() == ALPHA_CUT_DISCARD, get_alpha_cut_mode() == ALPHA_CUT_OPAQUE_PREPASS, get_billboard_mode() == StandardMaterial3D::BILLBOARD_ENABLED, get_billboard_mode() == StandardMaterial3D::BILLBOARD_FIXED_Y, false, get_draw_flag(FLAG_DISABLE_DEPTH_TEST), get_draw_flag(FLAG_FIXED_SIZE), get_texture_filter(), &shader_rid); if (last_shader != shader_rid) { RS::get_singleton()->material_set_shader(get_material(), shader_rid); last_shader = shader_rid; @@ -595,6 +628,10 @@ void Sprite3D::_draw() { RS::get_singleton()->material_set_param(get_material(), "texture_albedo", texture->get_rid()); last_texture = texture->get_rid(); } + if (get_alpha_cut_mode() == ALPHA_CUT_DISABLED) { + RS::get_singleton()->material_set_render_priority(get_material(), get_render_priority()); + RS::get_singleton()->mesh_surface_set_material(mesh, 0, get_material()); + } } void Sprite3D::set_texture(const Ref<Texture2D> &p_texture) { @@ -755,10 +792,10 @@ void Sprite3D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::INT, "hframes", PROPERTY_HINT_RANGE, "1,16384,1"), "set_hframes", "get_hframes"); ADD_PROPERTY(PropertyInfo(Variant::INT, "vframes", PROPERTY_HINT_RANGE, "1,16384,1"), "set_vframes", "get_vframes"); ADD_PROPERTY(PropertyInfo(Variant::INT, "frame"), "set_frame", "get_frame"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "frame_coords", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "set_frame_coords", "get_frame_coords"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "frame_coords", PROPERTY_HINT_NONE, "suffix:px", PROPERTY_USAGE_EDITOR), "set_frame_coords", "get_frame_coords"); ADD_GROUP("Region", "region_"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "region_enabled"), "set_region_enabled", "is_region_enabled"); - ADD_PROPERTY(PropertyInfo(Variant::RECT2, "region_rect"), "set_region_rect", "get_region_rect"); + ADD_PROPERTY(PropertyInfo(Variant::RECT2, "region_rect", PROPERTY_HINT_NONE, "suffix:px"), "set_region_rect", "get_region_rect"); ADD_SIGNAL(MethodInfo("frame_changed")); ADD_SIGNAL(MethodInfo("texture_changed")); @@ -907,7 +944,7 @@ void AnimatedSprite3D::_draw() { value |= CLAMP(int((t.normal.y * 0.5 + 0.5) * 1023.0), 0, 1023) << 10; value |= CLAMP(int((t.normal.z * 0.5 + 0.5) * 1023.0), 0, 1023) << 20; if (t.d > 0) { - value |= 3 << 30; + value |= 3UL << 30; } v_tangent = value; } @@ -948,7 +985,7 @@ void AnimatedSprite3D::_draw() { set_aabb(aabb); RID shader_rid; - StandardMaterial3D::get_material_for_2d(get_draw_flag(FLAG_SHADED), get_draw_flag(FLAG_TRANSPARENT), get_draw_flag(FLAG_DOUBLE_SIDED), get_alpha_cut_mode() == ALPHA_CUT_DISCARD, get_alpha_cut_mode() == ALPHA_CUT_OPAQUE_PREPASS, get_billboard_mode() == StandardMaterial3D::BILLBOARD_ENABLED, get_billboard_mode() == StandardMaterial3D::BILLBOARD_FIXED_Y, &shader_rid); + StandardMaterial3D::get_material_for_2d(get_draw_flag(FLAG_SHADED), get_draw_flag(FLAG_TRANSPARENT), get_draw_flag(FLAG_DOUBLE_SIDED), get_alpha_cut_mode() == ALPHA_CUT_DISCARD, get_alpha_cut_mode() == ALPHA_CUT_OPAQUE_PREPASS, get_billboard_mode() == StandardMaterial3D::BILLBOARD_ENABLED, get_billboard_mode() == StandardMaterial3D::BILLBOARD_FIXED_Y, false, get_draw_flag(FLAG_DISABLE_DEPTH_TEST), get_draw_flag(FLAG_FIXED_SIZE), get_texture_filter(), &shader_rid); if (last_shader != shader_rid) { RS::get_singleton()->material_set_shader(get_material(), shader_rid); last_shader = shader_rid; @@ -957,6 +994,10 @@ void AnimatedSprite3D::_draw() { RS::get_singleton()->material_set_param(get_material(), "texture_albedo", texture->get_rid()); last_texture = texture->get_rid(); } + if (get_alpha_cut_mode() == ALPHA_CUT_DISABLED) { + RS::get_singleton()->material_set_render_priority(get_material(), get_render_priority()); + RS::get_singleton()->mesh_surface_set_material(mesh, 0, get_material()); + } } void AnimatedSprite3D::_validate_property(PropertyInfo &property) const { @@ -993,8 +1034,11 @@ void AnimatedSprite3D::_validate_property(PropertyInfo &property) const { if (property.name == "frame") { property.hint = PROPERTY_HINT_RANGE; - if (frames->has_animation(animation) && frames->get_frame_count(animation) > 1) { + if (frames->has_animation(animation) && frames->get_frame_count(animation) > 0) { property.hint_string = "0," + itos(frames->get_frame_count(animation) - 1) + ",1"; + } else { + // Avoid an error, `hint_string` is required for `PROPERTY_HINT_RANGE`. + property.hint_string = "0,0,1"; } property.usage |= PROPERTY_USAGE_KEYING_INCREMENTS; } @@ -1206,6 +1250,17 @@ TypedArray<String> AnimatedSprite3D::get_configuration_warnings() const { return warnings; } +void AnimatedSprite3D::get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const { + if (p_idx == 0 && p_function == "play" && frames.is_valid()) { + List<StringName> al; + frames->get_animation_list(&al); + for (const StringName &name : al) { + r_options->push_back(String(name).quote()); + } + } + Node::get_argument_options(p_function, p_idx, r_options); +} + void AnimatedSprite3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_sprite_frames", "sprite_frames"), &AnimatedSprite3D::set_sprite_frames); ClassDB::bind_method(D_METHOD("get_sprite_frames"), &AnimatedSprite3D::get_sprite_frames); diff --git a/scene/3d/sprite_3d.h b/scene/3d/sprite_3d.h index 92dbef0855..6ac85a7bbc 100644 --- a/scene/3d/sprite_3d.h +++ b/scene/3d/sprite_3d.h @@ -44,6 +44,8 @@ public: FLAG_TRANSPARENT, FLAG_SHADED, FLAG_DOUBLE_SIDED, + FLAG_DISABLE_DEPTH_TEST, + FLAG_FIXED_SIZE, FLAG_MAX }; @@ -69,6 +71,7 @@ private: bool vflip = false; Color modulate = Color(1, 1, 1, 1); + int render_priority = 0; Vector3::Axis axis = Vector3::AXIS_Z; real_t pixel_size = 0.01; @@ -80,6 +83,7 @@ private: bool flags[FLAG_MAX] = {}; AlphaCutMode alpha_cut = ALPHA_CUT_DISABLED; StandardMaterial3D::BillboardMode billboard_mode = StandardMaterial3D::BILLBOARD_DISABLED; + StandardMaterial3D::TextureFilter texture_filter = StandardMaterial3D::TEXTURE_FILTER_LINEAR_WITH_MIPMAPS; bool pending_update = false; void _im_update(); @@ -97,10 +101,10 @@ protected: uint32_t mesh_surface_offsets[RS::ARRAY_MAX]; PackedByteArray vertex_buffer; PackedByteArray attribute_buffer; - uint32_t vertex_stride; - uint32_t attrib_stride; - uint32_t skin_stride; - uint32_t mesh_surface_format; + uint32_t vertex_stride = 0; + uint32_t attrib_stride = 0; + uint32_t skin_stride = 0; + uint32_t mesh_surface_format = 0; void _queue_update(); @@ -117,6 +121,9 @@ public: void set_flip_v(bool p_flip); bool is_flipped_v() const; + void set_render_priority(int p_priority); + int get_render_priority() const; + void set_modulate(const Color &p_color); Color get_modulate() const; @@ -131,9 +138,13 @@ public: void set_alpha_cut_mode(AlphaCutMode p_mode); AlphaCutMode get_alpha_cut_mode() const; + void set_billboard_mode(StandardMaterial3D::BillboardMode p_mode); StandardMaterial3D::BillboardMode get_billboard_mode() const; + void set_texture_filter(StandardMaterial3D::TextureFilter p_filter); + StandardMaterial3D::TextureFilter get_texture_filter() const; + virtual Rect2 get_item_rect() const = 0; virtual AABB get_aabb() const override; @@ -237,6 +248,8 @@ public: virtual Rect2 get_item_rect() const override; virtual TypedArray<String> get_configuration_warnings() const override; + virtual void get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const override; + AnimatedSprite3D(); }; diff --git a/scene/3d/vehicle_body_3d.cpp b/scene/3d/vehicle_body_3d.cpp index f5a451ca04..42ed52c9f2 100644 --- a/scene/3d/vehicle_body_3d.cpp +++ b/scene/3d/vehicle_body_3d.cpp @@ -40,7 +40,7 @@ public: Vector3 m_0MinvJt; Vector3 m_1MinvJt; //Optimization: can be stored in the w/last component of one of the vectors - real_t m_Adiag; + real_t m_Adiag = 0.0; real_t getDiagonal() const { return m_Adiag; } @@ -90,8 +90,8 @@ void VehicleWheel3D::_notification(int p_what) { cb->wheels.push_back(this); m_chassisConnectionPointCS = get_transform().origin; - m_wheelDirectionCS = -get_transform().basis.get_axis(Vector3::AXIS_Y).normalized(); - m_wheelAxleCS = get_transform().basis.get_axis(Vector3::AXIS_X).normalized(); + m_wheelDirectionCS = -get_transform().basis.get_column(Vector3::AXIS_Y).normalized(); + m_wheelAxleCS = get_transform().basis.get_column(Vector3::AXIS_X).normalized(); } break; case NOTIFICATION_EXIT_TREE: { @@ -274,21 +274,21 @@ void VehicleWheel3D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_steering"), &VehicleWheel3D::get_steering); ADD_GROUP("Per-Wheel Motion", ""); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "engine_force", PROPERTY_HINT_RANGE, "-1024,1024.0,0.01,or_greater"), "set_engine_force", "get_engine_force"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "brake", PROPERTY_HINT_RANGE, "0.0,1.0,0.01"), "set_brake", "get_brake"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "steering", PROPERTY_HINT_RANGE, "-180,180.0,0.01"), "set_steering", "get_steering"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "engine_force", PROPERTY_HINT_RANGE, U"-1024,1024.0,0.01,or_greater,suffix:kg\u22C5m/s\u00B2 (N)"), "set_engine_force", "get_engine_force"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "brake", PROPERTY_HINT_RANGE, U"0.0,1.0,0.01,suffix:kg\u22C5m/s\u00B2 (N)"), "set_brake", "get_brake"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "steering", PROPERTY_HINT_RANGE, "-180,180.0,0.01,radians"), "set_steering", "get_steering"); ADD_GROUP("VehicleBody3D Motion", ""); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_as_traction"), "set_use_as_traction", "is_used_as_traction"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_as_steering"), "set_use_as_steering", "is_used_as_steering"); ADD_GROUP("Wheel", "wheel_"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "wheel_roll_influence"), "set_roll_influence", "get_roll_influence"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "wheel_radius"), "set_radius", "get_radius"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "wheel_rest_length"), "set_suspension_rest_length", "get_suspension_rest_length"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "wheel_radius", PROPERTY_HINT_NONE, "suffix:m"), "set_radius", "get_radius"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "wheel_rest_length", PROPERTY_HINT_NONE, "suffix:m"), "set_suspension_rest_length", "get_suspension_rest_length"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "wheel_friction_slip"), "set_friction_slip", "get_friction_slip"); ADD_GROUP("Suspension", "suspension_"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "suspension_travel"), "set_suspension_travel", "get_suspension_travel"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "suspension_travel", PROPERTY_HINT_NONE, "suffix:m"), "set_suspension_travel", "get_suspension_travel"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "suspension_stiffness"), "set_suspension_stiffness", "get_suspension_stiffness"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "suspension_max_force"), "set_suspension_max_force", "get_suspension_max_force"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "suspension_max_force", PROPERTY_HINT_NONE, U"suffix:kg\u22C5m/s\u00B2 (N)"), "set_suspension_max_force", "get_suspension_max_force"); ADD_GROUP("Damping", "damping_"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "damping_compression"), "set_damping_compression", "get_damping_compression"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "damping_relaxation"), "set_damping_relaxation", "get_damping_relaxation"); @@ -684,7 +684,7 @@ void VehicleBody3D::_update_friction(PhysicsDirectBodyState3D *s) { Basis wheelBasis0 = wheelInfo.m_worldTransform.basis; //get_global_transform().basis; - m_axle.write[i] = wheelBasis0.get_axis(Vector3::AXIS_X); + m_axle.write[i] = wheelBasis0.get_column(Vector3::AXIS_X); //m_axle[i] = wheelInfo.m_raycastInfo.m_wheelAxleWS; const Vector3 &surfNormalWS = wheelInfo.m_raycastInfo.m_contactNormalWS; @@ -918,9 +918,9 @@ void VehicleBody3D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_steering"), &VehicleBody3D::get_steering); ADD_GROUP("Motion", ""); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "engine_force", PROPERTY_HINT_RANGE, "-1024,1024.0,0.01,or_greater"), "set_engine_force", "get_engine_force"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "brake", PROPERTY_HINT_RANGE, "0.0,1.0,0.01"), "set_brake", "get_brake"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "steering", PROPERTY_HINT_RANGE, "-180,180.0,0.01"), "set_steering", "get_steering"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "engine_force", PROPERTY_HINT_RANGE, U"-1024,1024.0,0.01,or_greater,suffix:kg\u22C5m/s\u00B2 (N)"), "set_engine_force", "get_engine_force"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "brake", PROPERTY_HINT_RANGE, U"0.0,1.0,0.01,suffix:kg\u22C5m/s\u00B2 (N)"), "set_brake", "get_brake"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "steering", PROPERTY_HINT_RANGE, "-180,180.0,0.01,radians"), "set_steering", "get_steering"); } VehicleBody3D::VehicleBody3D() { diff --git a/scene/3d/vehicle_body_3d.h b/scene/3d/vehicle_body_3d.h index d2371d819b..0ef8bd7482 100644 --- a/scene/3d/vehicle_body_3d.h +++ b/scene/3d/vehicle_body_3d.h @@ -162,7 +162,7 @@ class VehicleBody3D : public RigidDynamicBody3D { real_t m_steeringValue = 0.0; real_t m_currentVehicleSpeedKmHour = 0.0; - Set<RID> exclude; + HashSet<RID> exclude; Vector<Vector3> m_forwardWS; Vector<Vector3> m_axle; diff --git a/scene/3d/visible_on_screen_notifier_3d.cpp b/scene/3d/visible_on_screen_notifier_3d.cpp index 41cd604a4f..bcf294e216 100644 --- a/scene/3d/visible_on_screen_notifier_3d.cpp +++ b/scene/3d/visible_on_screen_notifier_3d.cpp @@ -83,7 +83,7 @@ void VisibleOnScreenNotifier3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_aabb", "rect"), &VisibleOnScreenNotifier3D::set_aabb); ClassDB::bind_method(D_METHOD("is_on_screen"), &VisibleOnScreenNotifier3D::is_on_screen); - ADD_PROPERTY(PropertyInfo(Variant::AABB, "aabb"), "set_aabb", "get_aabb"); + ADD_PROPERTY(PropertyInfo(Variant::AABB, "aabb", PROPERTY_HINT_NONE, "suffix:m"), "set_aabb", "get_aabb"); ADD_SIGNAL(MethodInfo("screen_entered")); ADD_SIGNAL(MethodInfo("screen_exited")); diff --git a/scene/3d/visual_instance_3d.cpp b/scene/3d/visual_instance_3d.cpp index 669017c4b4..69917f6992 100644 --- a/scene/3d/visual_instance_3d.cpp +++ b/scene/3d/visual_instance_3d.cpp @@ -458,7 +458,7 @@ void GeometryInstance3D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "material_overlay", PROPERTY_HINT_RESOURCE_TYPE, "BaseMaterial3D,ShaderMaterial", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_DEFERRED_SET_RESOURCE), "set_material_overlay", "get_material_overlay"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "transparency", PROPERTY_HINT_RANGE, "0.0,1.0,0.01"), "set_transparency", "get_transparency"); ADD_PROPERTY(PropertyInfo(Variant::INT, "cast_shadow", PROPERTY_HINT_ENUM, "Off,On,Double-Sided,Shadows Only"), "set_cast_shadows_setting", "get_cast_shadows_setting"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "extra_cull_margin", PROPERTY_HINT_RANGE, "0,16384,0.01"), "set_extra_cull_margin", "get_extra_cull_margin"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "extra_cull_margin", PROPERTY_HINT_RANGE, "0,16384,0.01,suffix:m"), "set_extra_cull_margin", "get_extra_cull_margin"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "lod_bias", PROPERTY_HINT_RANGE, "0.001,128,0.001"), "set_lod_bias", "get_lod_bias"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "ignore_occlusion_culling"), "set_ignore_occlusion_culling", "is_ignoring_occlusion_culling"); ADD_GROUP("Global Illumination", "gi_"); @@ -466,12 +466,11 @@ void GeometryInstance3D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::INT, "gi_lightmap_scale", PROPERTY_HINT_ENUM, String::utf8("1×,2×,4×,8×")), "set_lightmap_scale", "get_lightmap_scale"); ADD_GROUP("Visibility Range", "visibility_range_"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "visibility_range_begin", PROPERTY_HINT_RANGE, "0.0,4096.0,0.01"), "set_visibility_range_begin", "get_visibility_range_begin"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "visibility_range_begin_margin", PROPERTY_HINT_RANGE, "0.0,4096.0,0.01"), "set_visibility_range_begin_margin", "get_visibility_range_begin_margin"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "visibility_range_end", PROPERTY_HINT_RANGE, "0.0,4096.0,0.01"), "set_visibility_range_end", "get_visibility_range_end"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "visibility_range_end_margin", PROPERTY_HINT_RANGE, "0.0,4096.0,0.01"), "set_visibility_range_end_margin", "get_visibility_range_end_margin"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "visibility_range_begin", PROPERTY_HINT_RANGE, "0.0,4096.0,0.01,suffix:m"), "set_visibility_range_begin", "get_visibility_range_begin"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "visibility_range_begin_margin", PROPERTY_HINT_RANGE, "0.0,4096.0,0.01,suffix:m"), "set_visibility_range_begin_margin", "get_visibility_range_begin_margin"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "visibility_range_end", PROPERTY_HINT_RANGE, "0.0,4096.0,0.01,suffix:m"), "set_visibility_range_end", "get_visibility_range_end"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "visibility_range_end_margin", PROPERTY_HINT_RANGE, "0.0,4096.0,0.01,suffix:m"), "set_visibility_range_end_margin", "get_visibility_range_end_margin"); ADD_PROPERTY(PropertyInfo(Variant::INT, "visibility_range_fade_mode", PROPERTY_HINT_ENUM, "Disabled,Self,Dependencies"), "set_visibility_range_fade_mode", "get_visibility_range_fade_mode"); - //ADD_SIGNAL( MethodInfo("visibility_changed")); BIND_ENUM_CONSTANT(SHADOW_CASTING_SETTING_OFF); BIND_ENUM_CONSTANT(SHADOW_CASTING_SETTING_ON); @@ -494,5 +493,13 @@ void GeometryInstance3D::_bind_methods() { } GeometryInstance3D::GeometryInstance3D() { - //RS::get_singleton()->instance_geometry_set_baked_light_texture_index(get_instance(),0); +} + +GeometryInstance3D::~GeometryInstance3D() { + if (material_overlay.is_valid()) { + set_material_overlay(Ref<Material>()); + } + if (material_override.is_valid()) { + set_material_override(Ref<Material>()); + } } diff --git a/scene/3d/visual_instance_3d.h b/scene/3d/visual_instance_3d.h index f8fd4378fe..9e0d9b9a2a 100644 --- a/scene/3d/visual_instance_3d.h +++ b/scene/3d/visual_instance_3d.h @@ -126,7 +126,7 @@ private: float extra_cull_margin = 0.0; LightmapScale lightmap_scale = LIGHTMAP_SCALE_1X; - GIMode gi_mode = GI_MODE_DISABLED; + GIMode gi_mode = GI_MODE_STATIC; bool ignore_occlusion_culling = false; const StringName *_instance_uniform_get_remap(const StringName p_name) const; @@ -188,6 +188,7 @@ public: TypedArray<String> get_configuration_warnings() const override; GeometryInstance3D(); + virtual ~GeometryInstance3D(); }; VARIANT_ENUM_CAST(GeometryInstance3D::ShadowCastingSetting); diff --git a/scene/3d/voxel_gi.cpp b/scene/3d/voxel_gi.cpp index 29e495ce1b..ae231026a7 100644 --- a/scene/3d/voxel_gi.cpp +++ b/scene/3d/voxel_gi.cpp @@ -272,7 +272,8 @@ VoxelGI::Subdiv VoxelGI::get_subdiv() const { } void VoxelGI::set_extents(const Vector3 &p_extents) { - extents = p_extents; + // Prevent very small extents as these break baking if other extents are set very high. + extents = Vector3(MAX(1.0, p_extents.x), MAX(1.0, p_extents.y), MAX(1.0, p_extents.z)); update_gizmos(); } @@ -476,7 +477,7 @@ void VoxelGI::_bind_methods() { ClassDB::set_method_flags(get_class_static(), _scs_create("debug_bake"), METHOD_FLAGS_DEFAULT | METHOD_FLAG_EDITOR); ADD_PROPERTY(PropertyInfo(Variant::INT, "subdiv", PROPERTY_HINT_ENUM, "64,128,256,512"), "set_subdiv", "get_subdiv"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "extents"), "set_extents", "get_extents"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "extents", PROPERTY_HINT_NONE, "suffix:m"), "set_extents", "get_extents"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "data", PROPERTY_HINT_RESOURCE_TYPE, "VoxelGIData", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_DO_NOT_SHARE_ON_DUPLICATE), "set_probe_data", "get_probe_data"); BIND_ENUM_CONSTANT(SUBDIV_64); diff --git a/scene/3d/voxelizer.cpp b/scene/3d/voxelizer.cpp index bda3868fbb..42a2a68e2d 100644 --- a/scene/3d/voxelizer.cpp +++ b/scene/3d/voxelizer.cpp @@ -592,7 +592,6 @@ void Voxelizer::_fixup_plot(int p_idx, int p_level) { bake_cells.write[p_idx].albedo[2] = 0; float alpha_average = 0; - int children_found = 0; for (int i = 0; i < 8; i++) { uint32_t child = bake_cells[p_idx].children[i]; @@ -603,8 +602,6 @@ void Voxelizer::_fixup_plot(int p_idx, int p_level) { _fixup_plot(child, p_level + 1); alpha_average += bake_cells[child].alpha; - - children_found++; } bake_cells.write[p_idx].alpha = alpha_average / 8.0; @@ -777,8 +774,8 @@ Vector<int> Voxelizer::get_voxel_gi_level_cell_count() const { /* dt of 1d function using squared distance */ static void edt(float *f, int stride, int n) { float *d = (float *)alloca(sizeof(float) * n + sizeof(int) * n + sizeof(float) * (n + 1)); - int *v = (int *)&(d[n]); - float *z = (float *)&v[n]; + int *v = reinterpret_cast<int *>(&(d[n])); + float *z = reinterpret_cast<float *>(&v[n]); int k = 0; v[0] = 0; diff --git a/scene/3d/voxelizer.h b/scene/3d/voxelizer.h index dc7569d17c..0179795ddc 100644 --- a/scene/3d/voxelizer.h +++ b/scene/3d/voxelizer.h @@ -86,7 +86,7 @@ private: Vector<Color> emission; }; - Map<Ref<Material>, MaterialCache> material_cache; + HashMap<Ref<Material>, MaterialCache> material_cache; AABB original_bounds; AABB po2_bounds; int axis_cell_size[3] = {}; diff --git a/scene/3d/world_environment.cpp b/scene/3d/world_environment.cpp index f638644628..fe9d9ae4dd 100644 --- a/scene/3d/world_environment.cpp +++ b/scene/3d/world_environment.cpp @@ -71,7 +71,7 @@ void WorldEnvironment::_update_current_environment() { } else { get_viewport()->find_world_3d()->set_environment(Ref<Environment>()); } - get_tree()->call_group("_world_environment_" + itos(get_viewport()->find_world_3d()->get_scenario().get_id()), "update_configuration_warnings"); + get_tree()->call_group_flags(SceneTree::GROUP_CALL_DEFERRED, "_world_environment_" + itos(get_viewport()->find_world_3d()->get_scenario().get_id()), "update_configuration_warnings"); } void WorldEnvironment::_update_current_camera_effects() { @@ -82,7 +82,7 @@ void WorldEnvironment::_update_current_camera_effects() { get_viewport()->find_world_3d()->set_camera_effects(Ref<CameraEffects>()); } - get_tree()->call_group("_world_camera_effects_" + itos(get_viewport()->find_world_3d()->get_scenario().get_id()), "update_configuration_warnings"); + get_tree()->call_group_flags(SceneTree::GROUP_CALL_DEFERRED, "_world_camera_effects_" + itos(get_viewport()->find_world_3d()->get_scenario().get_id()), "update_configuration_warnings"); } void WorldEnvironment::set_environment(const Ref<Environment> &p_environment) { diff --git a/scene/3d/xr_nodes.cpp b/scene/3d/xr_nodes.cpp index 3085d84643..1dad6078b4 100644 --- a/scene/3d/xr_nodes.cpp +++ b/scene/3d/xr_nodes.cpp @@ -577,7 +577,7 @@ Plane XRAnchor3D::get_plane() const { Vector3 location = get_position(); Basis orientation = get_transform().basis; - Plane plane(orientation.get_axis(1).normalized(), location); + Plane plane(orientation.get_column(1).normalized(), location); return plane; } diff --git a/scene/animation/animation_blend_space_1d.cpp b/scene/animation/animation_blend_space_1d.cpp index 849316c568..d9a5adc883 100644 --- a/scene/animation/animation_blend_space_1d.cpp +++ b/scene/animation/animation_blend_space_1d.cpp @@ -78,6 +78,9 @@ void AnimationNodeBlendSpace1D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_value_label", "text"), &AnimationNodeBlendSpace1D::set_value_label); ClassDB::bind_method(D_METHOD("get_value_label"), &AnimationNodeBlendSpace1D::get_value_label); + ClassDB::bind_method(D_METHOD("set_use_sync", "enable"), &AnimationNodeBlendSpace1D::set_use_sync); + ClassDB::bind_method(D_METHOD("is_using_sync"), &AnimationNodeBlendSpace1D::is_using_sync); + ClassDB::bind_method(D_METHOD("_add_blend_point", "index", "node"), &AnimationNodeBlendSpace1D::_add_blend_point); for (int i = 0; i < MAX_BLEND_POINTS; i++) { @@ -89,6 +92,7 @@ void AnimationNodeBlendSpace1D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "max_space", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_max_space", "get_max_space"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "snap", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_snap", "get_snap"); ADD_PROPERTY(PropertyInfo(Variant::STRING, "value_label", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_value_label", "get_value_label"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "sync", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_use_sync", "is_using_sync"); } void AnimationNodeBlendSpace1D::get_child_nodes(List<ChildNode> *r_child_nodes) { @@ -211,6 +215,14 @@ String AnimationNodeBlendSpace1D::get_value_label() const { return value_label; } +void AnimationNodeBlendSpace1D::set_use_sync(bool p_sync) { + sync = p_sync; +} + +bool AnimationNodeBlendSpace1D::is_using_sync() const { + return sync; +} + void AnimationNodeBlendSpace1D::_add_blend_point(int p_index, const Ref<AnimationRootNode> &p_node) { if (p_index == blend_points_used) { add_blend_point(p_node, 0); @@ -219,14 +231,14 @@ void AnimationNodeBlendSpace1D::_add_blend_point(int p_index, const Ref<Animatio } } -double AnimationNodeBlendSpace1D::process(double p_time, bool p_seek) { +double AnimationNodeBlendSpace1D::process(double p_time, bool p_seek, bool p_seek_root) { if (blend_points_used == 0) { return 0.0; } if (blend_points_used == 1) { // only one point available, just play that animation - return blend_node(blend_points[0].name, blend_points[0].node, p_time, p_seek, 1.0, FILTER_IGNORE, false); + return blend_node(blend_points[0].name, blend_points[0].node, p_time, p_seek, p_seek_root, 1.0, FILTER_IGNORE, true); } double blend_pos = get_parameter(blend_position); @@ -295,9 +307,12 @@ double AnimationNodeBlendSpace1D::process(double p_time, bool p_seek) { double max_time_remaining = 0.0; for (int i = 0; i < blend_points_used; i++) { - double remaining = blend_node(blend_points[i].name, blend_points[i].node, p_time, p_seek, weights[i], FILTER_IGNORE, false); - - max_time_remaining = MAX(max_time_remaining, remaining); + if (i == point_lower || i == point_higher) { + double remaining = blend_node(blend_points[i].name, blend_points[i].node, p_time, p_seek, p_seek_root, weights[i], FILTER_IGNORE, true); + max_time_remaining = MAX(max_time_remaining, remaining); + } else { + blend_node(blend_points[i].name, blend_points[i].node, p_time, p_seek, p_seek_root, 0, FILTER_IGNORE, sync); + } } return max_time_remaining; diff --git a/scene/animation/animation_blend_space_1d.h b/scene/animation/animation_blend_space_1d.h index 7038cece06..346e8a3a2f 100644 --- a/scene/animation/animation_blend_space_1d.h +++ b/scene/animation/animation_blend_space_1d.h @@ -63,6 +63,8 @@ class AnimationNodeBlendSpace1D : public AnimationRootNode { StringName blend_position = "blend_position"; protected: + bool sync = false; + virtual void _validate_property(PropertyInfo &property) const override; static void _bind_methods(); @@ -93,7 +95,10 @@ public: void set_value_label(const String &p_label); String get_value_label() const; - double process(double p_time, bool p_seek) override; + void set_use_sync(bool p_sync); + bool is_using_sync() const; + + double process(double p_time, bool p_seek, bool p_seek_root) override; String get_caption() const override; Ref<AnimationNode> get_child_by_name(const StringName &p_name) override; diff --git a/scene/animation/animation_blend_space_2d.cpp b/scene/animation/animation_blend_space_2d.cpp index a3aa3f6cc8..0f77befd9d 100644 --- a/scene/animation/animation_blend_space_2d.cpp +++ b/scene/animation/animation_blend_space_2d.cpp @@ -432,7 +432,7 @@ void AnimationNodeBlendSpace2D::_blend_triangle(const Vector2 &p_pos, const Vect r_weights[2] = w; } -double AnimationNodeBlendSpace2D::process(double p_time, bool p_seek) { +double AnimationNodeBlendSpace2D::process(double p_time, bool p_seek, bool p_seek_root) { _update_triangles(); Vector2 blend_pos = get_parameter(blend_position); @@ -502,7 +502,7 @@ double AnimationNodeBlendSpace2D::process(double p_time, bool p_seek) { for (int j = 0; j < 3; j++) { if (i == triangle_points[j]) { //blend with the given weight - double t = blend_node(blend_points[i].name, blend_points[i].node, p_time, p_seek, blend_weights[j], FILTER_IGNORE, false); + double t = blend_node(blend_points[i].name, blend_points[i].node, p_time, p_seek, p_seek_root, blend_weights[j], FILTER_IGNORE, true); if (first || t < mind) { mind = t; first = false; @@ -513,8 +513,7 @@ double AnimationNodeBlendSpace2D::process(double p_time, bool p_seek) { } if (!found) { - //ignore - blend_node(blend_points[i].name, blend_points[i].node, p_time, p_seek, 0, FILTER_IGNORE, false); + blend_node(blend_points[i].name, blend_points[i].node, p_time, p_seek, p_seek_root, 0, FILTER_IGNORE, sync); } } } else { @@ -539,16 +538,22 @@ double AnimationNodeBlendSpace2D::process(double p_time, bool p_seek) { 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); + from = length_internal - blend_node(blend_points[closest].name, blend_points[closest].node, p_time, false, p_seek_root, 0.0, FILTER_IGNORE, true); } - mind = blend_node(blend_points[new_closest].name, blend_points[new_closest].node, from, true, 1.0, FILTER_IGNORE, false); + mind = blend_node(blend_points[new_closest].name, blend_points[new_closest].node, from, true, p_seek_root, 1.0, FILTER_IGNORE, true); length_internal = from + mind; closest = new_closest; } else { - mind = blend_node(blend_points[closest].name, blend_points[closest].node, p_time, p_seek, 1.0, FILTER_IGNORE, false); + mind = blend_node(blend_points[closest].name, blend_points[closest].node, p_time, p_seek, p_seek_root, 1.0, FILTER_IGNORE, true); + } + + for (int i = 0; i < blend_points_used; i++) { + if (i != closest) { + blend_node(blend_points[i].name, blend_points[i].node, p_time, p_seek, p_seek_root, 0, FILTER_IGNORE, sync); + } } } @@ -604,6 +609,14 @@ AnimationNodeBlendSpace2D::BlendMode AnimationNodeBlendSpace2D::get_blend_mode() return blend_mode; } +void AnimationNodeBlendSpace2D::set_use_sync(bool p_sync) { + sync = p_sync; +} + +bool AnimationNodeBlendSpace2D::is_using_sync() const { + return sync; +} + void AnimationNodeBlendSpace2D::_bind_methods() { ClassDB::bind_method(D_METHOD("add_blend_point", "node", "pos", "at_index"), &AnimationNodeBlendSpace2D::add_blend_point, DEFVAL(-1)); ClassDB::bind_method(D_METHOD("set_blend_point_position", "point", "pos"), &AnimationNodeBlendSpace2D::set_blend_point_position); @@ -644,6 +657,9 @@ void AnimationNodeBlendSpace2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_blend_mode", "mode"), &AnimationNodeBlendSpace2D::set_blend_mode); ClassDB::bind_method(D_METHOD("get_blend_mode"), &AnimationNodeBlendSpace2D::get_blend_mode); + ClassDB::bind_method(D_METHOD("set_use_sync", "enable"), &AnimationNodeBlendSpace2D::set_use_sync); + ClassDB::bind_method(D_METHOD("is_using_sync"), &AnimationNodeBlendSpace2D::is_using_sync); + ClassDB::bind_method(D_METHOD("_update_triangles"), &AnimationNodeBlendSpace2D::_update_triangles); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "auto_triangles", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_auto_triangles", "get_auto_triangles"); @@ -661,6 +677,7 @@ void AnimationNodeBlendSpace2D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::STRING, "x_label", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_x_label", "get_x_label"); ADD_PROPERTY(PropertyInfo(Variant::STRING, "y_label", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_y_label", "get_y_label"); ADD_PROPERTY(PropertyInfo(Variant::INT, "blend_mode", PROPERTY_HINT_ENUM, "Interpolated,Discrete,Carry", PROPERTY_USAGE_NO_EDITOR), "set_blend_mode", "get_blend_mode"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "sync", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_use_sync", "is_using_sync"); ADD_SIGNAL(MethodInfo("triangles_updated")); BIND_ENUM_CONSTANT(BLEND_MODE_INTERPOLATED); diff --git a/scene/animation/animation_blend_space_2d.h b/scene/animation/animation_blend_space_2d.h index 1356656bf8..689b96e356 100644 --- a/scene/animation/animation_blend_space_2d.h +++ b/scene/animation/animation_blend_space_2d.h @@ -88,6 +88,8 @@ protected: void _tree_changed(); protected: + bool sync = false; + virtual void _validate_property(PropertyInfo &property) const override; static void _bind_methods(); @@ -126,7 +128,7 @@ public: void set_y_label(const String &p_label); String get_y_label() const; - virtual double process(double p_time, bool p_seek) override; + virtual double process(double p_time, bool p_seek, bool p_seek_root) override; virtual String get_caption() const override; Vector2 get_closest_point(const Vector2 &p_point); @@ -137,6 +139,9 @@ public: void set_blend_mode(BlendMode p_blend_mode); BlendMode get_blend_mode() const; + void set_use_sync(bool p_sync); + bool is_using_sync() const; + virtual Ref<AnimationNode> get_child_by_name(const StringName &p_name) override; AnimationNodeBlendSpace2D(); diff --git a/scene/animation/animation_blend_tree.cpp b/scene/animation/animation_blend_tree.cpp index 433f21f91f..d0aac931c0 100644 --- a/scene/animation/animation_blend_tree.cpp +++ b/scene/animation/animation_blend_tree.cpp @@ -64,7 +64,7 @@ void AnimationNodeAnimation::_validate_property(PropertyInfo &property) const { } } -double AnimationNodeAnimation::process(double p_time, bool p_seek) { +double AnimationNodeAnimation::process(double p_time, bool p_seek, bool p_seek_root) { AnimationPlayer *ap = state->player; ERR_FAIL_COND_V(!ap, 0); @@ -101,14 +101,14 @@ double AnimationNodeAnimation::process(double p_time, bool p_seek) { } } - if (anim->get_loop_mode() == Animation::LoopMode::LOOP_PINGPONG) { - if (anim_size) { + if (anim->get_loop_mode() == Animation::LOOP_PINGPONG) { + if (!Math::is_zero_approx(anim_size)) { if ((int)Math::floor(abs(time - prev_time) / anim_size) % 2 == 0) { - if (prev_time > 0 && time <= 0) { + if (prev_time >= 0 && time < 0) { backward = !backward; pingponged = -1; } - if (prev_time < anim_size && time >= anim_size) { + if (prev_time <= anim_size && time > anim_size) { backward = !backward; pingponged = 1; } @@ -116,22 +116,24 @@ double AnimationNodeAnimation::process(double p_time, bool p_seek) { time = Math::pingpong(time, anim_size); } } else { - if (anim->get_loop_mode() == Animation::LoopMode::LOOP_LINEAR) { - if (anim_size) { + if (anim->get_loop_mode() == Animation::LOOP_LINEAR) { + if (!Math::is_zero_approx(anim_size)) { time = Math::fposmod(time, anim_size); } } else if (time < 0) { + step += time; time = 0; } else if (time > anim_size) { + step += anim_size - time; time = anim_size; } backward = false; } if (play_mode == PLAY_MODE_FORWARD) { - blend_animation(animation, time, step, p_seek, 1.0, pingponged); + blend_animation(animation, time, step, p_seek, p_seek_root, 1.0, pingponged); } else { - blend_animation(animation, anim_size - time, -step, p_seek, 1.0, pingponged); + blend_animation(animation, anim_size - time, -step, p_seek, p_seek_root, 1.0, pingponged); } set_parameter(this->time, time); @@ -177,6 +179,26 @@ AnimationNodeAnimation::AnimationNodeAnimation() { //////////////////////////////////////////////////////// +void AnimationNodeSync::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_use_sync", "enable"), &AnimationNodeSync::set_use_sync); + ClassDB::bind_method(D_METHOD("is_using_sync"), &AnimationNodeSync::is_using_sync); + + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "sync"), "set_use_sync", "is_using_sync"); +} + +void AnimationNodeSync::set_use_sync(bool p_sync) { + sync = p_sync; +} + +bool AnimationNodeSync::is_using_sync() const { + return sync; +} + +AnimationNodeSync::AnimationNodeSync() { +} + +//////////////////////////////////////////////////////// + void AnimationNodeOneShot::get_parameter_list(List<PropertyInfo> *r_list) const { r_list->push_back(PropertyInfo(Variant::BOOL, active)); r_list->push_back(PropertyInfo(Variant::BOOL, prev_active, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE)); @@ -251,7 +273,7 @@ bool AnimationNodeOneShot::has_filter() const { return true; } -double AnimationNodeOneShot::process(double p_time, bool p_seek) { +double AnimationNodeOneShot::process(double p_time, bool p_seek, bool p_seek_root) { bool active = get_parameter(this->active); bool prev_active = get_parameter(this->prev_active); double time = get_parameter(this->time); @@ -274,7 +296,7 @@ double AnimationNodeOneShot::process(double p_time, bool p_seek) { } if (!active) { - return blend_input(0, p_time, p_seek, 1.0, FILTER_IGNORE, !sync); + return blend_input(0, p_time, p_seek, p_seek_root, 1.0, FILTER_IGNORE, sync); } } @@ -311,12 +333,12 @@ double AnimationNodeOneShot::process(double p_time, bool p_seek) { double main_rem; if (mix == MIX_MODE_ADD) { - main_rem = blend_input(0, p_time, p_seek, 1.0, FILTER_IGNORE, !sync); + main_rem = blend_input(0, p_time, p_seek, p_seek_root, 1.0, FILTER_IGNORE, sync); } else { - main_rem = blend_input(0, p_time, p_seek, 1.0 - blend, FILTER_BLEND, !sync); + main_rem = blend_input(0, p_time, p_seek, p_seek_root, 1.0 - blend, FILTER_BLEND, sync); } - double os_rem = blend_input(1, os_seek ? time : p_time, os_seek, blend, FILTER_PASS, false); + double os_rem = blend_input(1, os_seek ? time : p_time, os_seek, p_seek_root, blend, FILTER_PASS, true); if (do_start) { remaining = os_rem; @@ -341,14 +363,6 @@ double AnimationNodeOneShot::process(double p_time, bool p_seek) { return MAX(main_rem, remaining); } -void AnimationNodeOneShot::set_use_sync(bool p_sync) { - sync = p_sync; -} - -bool AnimationNodeOneShot::is_using_sync() const { - return sync; -} - void AnimationNodeOneShot::_bind_methods() { ClassDB::bind_method(D_METHOD("set_fadein_time", "time"), &AnimationNodeOneShot::set_fadein_time); ClassDB::bind_method(D_METHOD("get_fadein_time"), &AnimationNodeOneShot::get_fadein_time); @@ -368,22 +382,16 @@ void AnimationNodeOneShot::_bind_methods() { ClassDB::bind_method(D_METHOD("set_mix_mode", "mode"), &AnimationNodeOneShot::set_mix_mode); ClassDB::bind_method(D_METHOD("get_mix_mode"), &AnimationNodeOneShot::get_mix_mode); - ClassDB::bind_method(D_METHOD("set_use_sync", "enable"), &AnimationNodeOneShot::set_use_sync); - ClassDB::bind_method(D_METHOD("is_using_sync"), &AnimationNodeOneShot::is_using_sync); - ADD_PROPERTY(PropertyInfo(Variant::INT, "mix_mode", PROPERTY_HINT_ENUM, "Blend,Add"), "set_mix_mode", "get_mix_mode"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "fadein_time", PROPERTY_HINT_RANGE, "0,60,0.01,or_greater"), "set_fadein_time", "get_fadein_time"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "fadeout_time", PROPERTY_HINT_RANGE, "0,60,0.01,or_greater"), "set_fadeout_time", "get_fadeout_time"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "fadein_time", PROPERTY_HINT_RANGE, "0,60,0.01,or_greater,suffix:s"), "set_fadein_time", "get_fadein_time"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "fadeout_time", PROPERTY_HINT_RANGE, "0,60,0.01,or_greater,suffix:s"), "set_fadeout_time", "get_fadeout_time"); ADD_GROUP("Auto Restart", "autorestart_"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "autorestart"), "set_autorestart", "has_autorestart"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "autorestart_delay", PROPERTY_HINT_RANGE, "0,60,0.01,or_greater"), "set_autorestart_delay", "get_autorestart_delay"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "autorestart_random_delay", PROPERTY_HINT_RANGE, "0,60,0.01,or_greater"), "set_autorestart_random_delay", "get_autorestart_random_delay"); - - ADD_GROUP("", ""); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "sync"), "set_use_sync", "is_using_sync"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "autorestart_delay", PROPERTY_HINT_RANGE, "0,60,0.01,or_greater,suffix:s"), "set_autorestart_delay", "get_autorestart_delay"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "autorestart_random_delay", PROPERTY_HINT_RANGE, "0,60,0.01,or_greater,suffix:s"), "set_autorestart_random_delay", "get_autorestart_random_delay"); BIND_ENUM_CONSTANT(MIX_MODE_BLEND); BIND_ENUM_CONSTANT(MIX_MODE_ADD); @@ -408,31 +416,19 @@ String AnimationNodeAdd2::get_caption() const { return "Add2"; } -void AnimationNodeAdd2::set_use_sync(bool p_sync) { - sync = p_sync; -} - -bool AnimationNodeAdd2::is_using_sync() const { - return sync; -} - bool AnimationNodeAdd2::has_filter() const { return true; } -double AnimationNodeAdd2::process(double p_time, bool p_seek) { +double AnimationNodeAdd2::process(double p_time, bool p_seek, bool p_seek_root) { double amount = get_parameter(add_amount); - double rem0 = blend_input(0, p_time, p_seek, 1.0, FILTER_IGNORE, !sync); - blend_input(1, p_time, p_seek, amount, FILTER_PASS, !sync); + double rem0 = blend_input(0, p_time, p_seek, p_seek_root, 1.0, FILTER_IGNORE, sync); + blend_input(1, p_time, p_seek, p_seek_root, amount, FILTER_PASS, sync); return rem0; } void AnimationNodeAdd2::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_use_sync", "enable"), &AnimationNodeAdd2::set_use_sync); - ClassDB::bind_method(D_METHOD("is_using_sync"), &AnimationNodeAdd2::is_using_sync); - - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "sync"), "set_use_sync", "is_using_sync"); } AnimationNodeAdd2::AnimationNodeAdd2() { @@ -454,32 +450,20 @@ String AnimationNodeAdd3::get_caption() const { return "Add3"; } -void AnimationNodeAdd3::set_use_sync(bool p_sync) { - sync = p_sync; -} - -bool AnimationNodeAdd3::is_using_sync() const { - return sync; -} - bool AnimationNodeAdd3::has_filter() const { return true; } -double AnimationNodeAdd3::process(double p_time, bool p_seek) { +double AnimationNodeAdd3::process(double p_time, bool p_seek, bool p_seek_root) { double amount = get_parameter(add_amount); - blend_input(0, p_time, p_seek, MAX(0, -amount), FILTER_PASS, !sync); - double rem0 = blend_input(1, p_time, p_seek, 1.0, FILTER_IGNORE, !sync); - blend_input(2, p_time, p_seek, MAX(0, amount), FILTER_PASS, !sync); + blend_input(0, p_time, p_seek, p_seek_root, MAX(0, -amount), FILTER_PASS, sync); + double rem0 = blend_input(1, p_time, p_seek, p_seek_root, 1.0, FILTER_IGNORE, sync); + blend_input(2, p_time, p_seek, p_seek_root, MAX(0, amount), FILTER_PASS, sync); return rem0; } void AnimationNodeAdd3::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_use_sync", "enable"), &AnimationNodeAdd3::set_use_sync); - ClassDB::bind_method(D_METHOD("is_using_sync"), &AnimationNodeAdd3::is_using_sync); - - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "sync"), "set_use_sync", "is_using_sync"); } AnimationNodeAdd3::AnimationNodeAdd3() { @@ -502,32 +486,20 @@ String AnimationNodeBlend2::get_caption() const { return "Blend2"; } -double AnimationNodeBlend2::process(double p_time, bool p_seek) { +double AnimationNodeBlend2::process(double p_time, bool p_seek, bool p_seek_root) { double amount = get_parameter(blend_amount); - double rem0 = blend_input(0, p_time, p_seek, 1.0 - amount, FILTER_BLEND, !sync); - double rem1 = blend_input(1, p_time, p_seek, amount, FILTER_PASS, !sync); + double rem0 = blend_input(0, p_time, p_seek, p_seek_root, 1.0 - amount, FILTER_BLEND, sync); + double rem1 = blend_input(1, p_time, p_seek, p_seek_root, amount, FILTER_PASS, sync); return amount > 0.5 ? rem1 : rem0; //hacky but good enough } -void AnimationNodeBlend2::set_use_sync(bool p_sync) { - sync = p_sync; -} - -bool AnimationNodeBlend2::is_using_sync() const { - return sync; -} - bool AnimationNodeBlend2::has_filter() const { return true; } void AnimationNodeBlend2::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_use_sync", "enable"), &AnimationNodeBlend2::set_use_sync); - ClassDB::bind_method(D_METHOD("is_using_sync"), &AnimationNodeBlend2::is_using_sync); - - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "sync"), "set_use_sync", "is_using_sync"); } AnimationNodeBlend2::AnimationNodeBlend2() { @@ -549,36 +521,22 @@ String AnimationNodeBlend3::get_caption() const { return "Blend3"; } -void AnimationNodeBlend3::set_use_sync(bool p_sync) { - sync = p_sync; -} - -bool AnimationNodeBlend3::is_using_sync() const { - return sync; -} - -double AnimationNodeBlend3::process(double p_time, bool p_seek) { +double AnimationNodeBlend3::process(double p_time, bool p_seek, bool p_seek_root) { double amount = get_parameter(blend_amount); - double rem0 = blend_input(0, p_time, p_seek, MAX(0, -amount), FILTER_IGNORE, !sync); - double rem1 = blend_input(1, p_time, p_seek, 1.0 - ABS(amount), FILTER_IGNORE, !sync); - double rem2 = blend_input(2, p_time, p_seek, MAX(0, amount), FILTER_IGNORE, !sync); + double rem0 = blend_input(0, p_time, p_seek, p_seek_root, MAX(0, -amount), FILTER_IGNORE, sync); + double rem1 = blend_input(1, p_time, p_seek, p_seek_root, 1.0 - ABS(amount), FILTER_IGNORE, sync); + double rem2 = blend_input(2, p_time, p_seek, p_seek_root, MAX(0, amount), FILTER_IGNORE, sync); return amount > 0.5 ? rem2 : (amount < -0.5 ? rem0 : rem1); //hacky but good enough } void AnimationNodeBlend3::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_use_sync", "enable"), &AnimationNodeBlend3::set_use_sync); - ClassDB::bind_method(D_METHOD("is_using_sync"), &AnimationNodeBlend3::is_using_sync); - - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "sync"), "set_use_sync", "is_using_sync"); } AnimationNodeBlend3::AnimationNodeBlend3() { - blend_amount = "blend_amount"; add_input("-blend"); add_input("in"); add_input("+blend"); - sync = false; } ///////////////////////////////// @@ -595,12 +553,12 @@ String AnimationNodeTimeScale::get_caption() const { return "TimeScale"; } -double AnimationNodeTimeScale::process(double p_time, bool p_seek) { +double AnimationNodeTimeScale::process(double p_time, bool p_seek, bool p_seek_root) { double scale = get_parameter(this->scale); if (p_seek) { - return blend_input(0, p_time, true, 1.0, FILTER_IGNORE, false); + return blend_input(0, p_time, true, p_seek_root, 1.0, FILTER_IGNORE, true); } else { - return blend_input(0, p_time * scale, false, 1.0, FILTER_IGNORE, false); + return blend_input(0, p_time * scale, false, p_seek_root, 1.0, FILTER_IGNORE, true); } } @@ -625,16 +583,16 @@ String AnimationNodeTimeSeek::get_caption() const { return "Seek"; } -double AnimationNodeTimeSeek::process(double p_time, bool p_seek) { +double AnimationNodeTimeSeek::process(double p_time, bool p_seek, bool p_seek_root) { double seek_pos = get_parameter(this->seek_pos); if (p_seek) { - return blend_input(0, p_time, true, 1.0, FILTER_IGNORE, false); + return blend_input(0, p_time, true, p_seek_root, 1.0, FILTER_IGNORE, true); } else if (seek_pos >= 0) { - double ret = blend_input(0, seek_pos, true, 1.0, FILTER_IGNORE, false); + double ret = blend_input(0, seek_pos, true, true, 1.0, FILTER_IGNORE, true); set_parameter(this->seek_pos, -1.0); //reset return ret; } else { - return blend_input(0, p_time, false, 1.0, FILTER_IGNORE, false); + return blend_input(0, p_time, false, p_seek_root, 1.0, FILTER_IGNORE, true); } } @@ -726,7 +684,15 @@ float AnimationNodeTransition::get_cross_fade_time() const { return xfade; } -double AnimationNodeTransition::process(double p_time, bool p_seek) { +void AnimationNodeTransition::set_from_start(bool p_from_start) { + from_start = p_from_start; +} + +bool AnimationNodeTransition::is_from_start() const { + return from_start; +} + +double AnimationNodeTransition::process(double p_time, bool p_seek, bool p_seek_root) { int current = get_parameter(this->current); int prev = get_parameter(this->prev); int prev_current = get_parameter(this->prev_current); @@ -752,9 +718,15 @@ double AnimationNodeTransition::process(double p_time, bool p_seek) { double rem = 0.0; + for (int i = 0; i < enabled_inputs; i++) { + if (i != current && i != prev) { + blend_input(i, p_time, p_seek, p_seek_root, 0, FILTER_IGNORE, sync); + } + } + if (prev < 0) { // process current animation, check for transition - rem = blend_input(current, p_time, p_seek, 1.0, FILTER_IGNORE, false); + rem = blend_input(current, p_time, p_seek, p_seek_root, 1.0, FILTER_IGNORE, true); if (p_seek) { time = p_time; @@ -770,18 +742,18 @@ double AnimationNodeTransition::process(double p_time, bool p_seek) { float blend = xfade == 0 ? 0 : (prev_xfading / xfade); - if (!p_seek && switched) { //just switched, seek to start of current + if (from_start && !p_seek && switched) { //just switched, seek to start of current - rem = blend_input(current, 0, true, 1.0 - blend, FILTER_IGNORE, false); + rem = blend_input(current, 0, true, p_seek_root, 1.0 - blend, FILTER_IGNORE, true); } else { - rem = blend_input(current, p_time, p_seek, 1.0 - blend, FILTER_IGNORE, false); + rem = blend_input(current, p_time, p_seek, p_seek_root, 1.0 - blend, FILTER_IGNORE, true); } - if (p_seek) { // don't seek prev animation - blend_input(prev, 0, false, blend, FILTER_IGNORE, false); + if (p_seek) { + blend_input(prev, p_time, true, p_seek_root, blend, FILTER_IGNORE, true); time = p_time; } else { - blend_input(prev, p_time, false, blend, FILTER_IGNORE, false); + blend_input(prev, p_time, false, p_seek_root, blend, FILTER_IGNORE, true); time += p_time; prev_xfading -= p_time; if (prev_xfading < 0) { @@ -823,8 +795,12 @@ void AnimationNodeTransition::_bind_methods() { ClassDB::bind_method(D_METHOD("set_cross_fade_time", "time"), &AnimationNodeTransition::set_cross_fade_time); ClassDB::bind_method(D_METHOD("get_cross_fade_time"), &AnimationNodeTransition::get_cross_fade_time); + ClassDB::bind_method(D_METHOD("set_from_start", "from_start"), &AnimationNodeTransition::set_from_start); + ClassDB::bind_method(D_METHOD("is_from_start"), &AnimationNodeTransition::is_from_start); + ADD_PROPERTY(PropertyInfo(Variant::INT, "input_count", PROPERTY_HINT_RANGE, "0,64,1", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), "set_enabled_inputs", "get_enabled_inputs"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "xfade_time", PROPERTY_HINT_RANGE, "0,120,0.01"), "set_cross_fade_time", "get_cross_fade_time"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "xfade_time", PROPERTY_HINT_RANGE, "0,120,0.01,suffix:s"), "set_cross_fade_time", "get_cross_fade_time"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "from_start"), "set_from_start", "is_from_start"); for (int i = 0; i < MAX_INPUTS; i++) { ADD_PROPERTYI(PropertyInfo(Variant::STRING, "input_" + itos(i) + "/name", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_INTERNAL), "set_input_caption", "get_input_caption", i); @@ -844,8 +820,8 @@ String AnimationNodeOutput::get_caption() const { return "Output"; } -double AnimationNodeOutput::process(double p_time, bool p_seek) { - return blend_input(0, p_time, p_seek, 1.0); +double AnimationNodeOutput::process(double p_time, bool p_seek, bool p_seek_root) { + return blend_input(0, p_time, p_seek, p_seek_root, 1.0, FILTER_IGNORE, true); } AnimationNodeOutput::AnimationNodeOutput() { @@ -1057,9 +1033,9 @@ String AnimationNodeBlendTree::get_caption() const { return "BlendTree"; } -double AnimationNodeBlendTree::process(double p_time, bool p_seek) { +double AnimationNodeBlendTree::process(double p_time, bool p_seek, bool p_seek_root) { Ref<AnimationNodeOutput> output = nodes[SceneStringNames::get_singleton()->output].node; - return _blend_node("output", nodes[SceneStringNames::get_singleton()->output].connections, this, output, p_time, p_seek, 1.0); + return _blend_node("output", nodes[SceneStringNames::get_singleton()->output].connections, this, output, p_time, p_seek, p_seek_root, 1.0, FILTER_IGNORE, true); } void AnimationNodeBlendTree::get_node_list(List<StringName> *r_list) { diff --git a/scene/animation/animation_blend_tree.h b/scene/animation/animation_blend_tree.h index 2acacd7396..d35ff04f30 100644 --- a/scene/animation/animation_blend_tree.h +++ b/scene/animation/animation_blend_tree.h @@ -53,7 +53,7 @@ public: static Vector<String> (*get_editable_animation_list)(); virtual String get_caption() const override; - virtual double process(double p_time, bool p_seek) override; + virtual double process(double p_time, bool p_seek, bool p_seek_root) override; void set_animation(const StringName &p_name); StringName get_animation() const; @@ -77,8 +77,23 @@ private: VARIANT_ENUM_CAST(AnimationNodeAnimation::PlayMode) -class AnimationNodeOneShot : public AnimationNode { - GDCLASS(AnimationNodeOneShot, AnimationNode); +class AnimationNodeSync : public AnimationNode { + GDCLASS(AnimationNodeSync, AnimationNode); + +protected: + bool sync = false; + + static void _bind_methods(); + +public: + void set_use_sync(bool p_sync); + bool is_using_sync() const; + + AnimationNodeSync(); +}; + +class AnimationNodeOneShot : public AnimationNodeSync { + GDCLASS(AnimationNodeOneShot, AnimationNodeSync); public: enum MixMode { @@ -87,22 +102,20 @@ public: }; private: - float fade_in = 0.1; - float fade_out = 0.1; + float fade_in = 0.0; + float fade_out = 0.0; bool autorestart = false; float autorestart_delay = 1.0; float autorestart_random_delay = 0.0; MixMode mix = MIX_MODE_BLEND; - bool sync = false; - /* bool active; bool do_start; float time; float remaining;*/ - StringName active = "active"; + StringName active = PNAME("active"); StringName prev_active = "prev_active"; StringName time = "time"; StringName remaining = "remaining"; @@ -134,22 +147,18 @@ public: void set_mix_mode(MixMode p_mix); MixMode get_mix_mode() const; - void set_use_sync(bool p_sync); - bool is_using_sync() const; - virtual bool has_filter() const override; - virtual double process(double p_time, bool p_seek) override; + virtual double process(double p_time, bool p_seek, bool p_seek_root) override; AnimationNodeOneShot(); }; VARIANT_ENUM_CAST(AnimationNodeOneShot::MixMode) -class AnimationNodeAdd2 : public AnimationNode { - GDCLASS(AnimationNodeAdd2, AnimationNode); +class AnimationNodeAdd2 : public AnimationNodeSync { + GDCLASS(AnimationNodeAdd2, AnimationNodeSync); - StringName add_amount = "add_amount"; - bool sync = false; + StringName add_amount = PNAME("add_amount"); protected: static void _bind_methods(); @@ -160,20 +169,16 @@ public: virtual String get_caption() const override; - void set_use_sync(bool p_sync); - bool is_using_sync() const; - virtual bool has_filter() const override; - virtual double process(double p_time, bool p_seek) override; + virtual double process(double p_time, bool p_seek, bool p_seek_root) override; AnimationNodeAdd2(); }; -class AnimationNodeAdd3 : public AnimationNode { - GDCLASS(AnimationNodeAdd3, AnimationNode); +class AnimationNodeAdd3 : public AnimationNodeSync { + GDCLASS(AnimationNodeAdd3, AnimationNodeSync); - StringName add_amount = "add_amount"; - bool sync = false; + StringName add_amount = PNAME("add_amount"); protected: static void _bind_methods(); @@ -184,20 +189,16 @@ public: virtual String get_caption() const override; - void set_use_sync(bool p_sync); - bool is_using_sync() const; - virtual bool has_filter() const override; - virtual double process(double p_time, bool p_seek) override; + virtual double process(double p_time, bool p_seek, bool p_seek_root) override; AnimationNodeAdd3(); }; -class AnimationNodeBlend2 : public AnimationNode { - GDCLASS(AnimationNodeBlend2, AnimationNode); +class AnimationNodeBlend2 : public AnimationNodeSync { + GDCLASS(AnimationNodeBlend2, AnimationNodeSync); - StringName blend_amount = "blend_amount"; - bool sync = false; + StringName blend_amount = PNAME("blend_amount"); protected: static void _bind_methods(); @@ -207,20 +208,16 @@ public: virtual Variant get_parameter_default_value(const StringName &p_parameter) const override; virtual String get_caption() const override; - virtual double process(double p_time, bool p_seek) override; - - void set_use_sync(bool p_sync); - bool is_using_sync() const; + virtual double process(double p_time, bool p_seek, bool p_seek_root) override; virtual bool has_filter() const override; AnimationNodeBlend2(); }; -class AnimationNodeBlend3 : public AnimationNode { - GDCLASS(AnimationNodeBlend3, AnimationNode); +class AnimationNodeBlend3 : public AnimationNodeSync { + GDCLASS(AnimationNodeBlend3, AnimationNodeSync); - StringName blend_amount; - bool sync; + StringName blend_amount = PNAME("blend_amount"); protected: static void _bind_methods(); @@ -231,17 +228,14 @@ public: virtual String get_caption() const override; - void set_use_sync(bool p_sync); - bool is_using_sync() const; - - double process(double p_time, bool p_seek) override; + double process(double p_time, bool p_seek, bool p_seek_root) override; AnimationNodeBlend3(); }; class AnimationNodeTimeScale : public AnimationNode { GDCLASS(AnimationNodeTimeScale, AnimationNode); - StringName scale = "scale"; + StringName scale = PNAME("scale"); protected: static void _bind_methods(); @@ -252,7 +246,7 @@ public: virtual String get_caption() const override; - double process(double p_time, bool p_seek) override; + double process(double p_time, bool p_seek, bool p_seek_root) override; AnimationNodeTimeScale(); }; @@ -260,7 +254,7 @@ public: class AnimationNodeTimeSeek : public AnimationNode { GDCLASS(AnimationNodeTimeSeek, AnimationNode); - StringName seek_pos = "seek_position"; + StringName seek_pos = PNAME("seek_position"); protected: static void _bind_methods(); @@ -271,13 +265,13 @@ public: virtual String get_caption() const override; - double process(double p_time, bool p_seek) override; + double process(double p_time, bool p_seek, bool p_seek_root) override; AnimationNodeTimeSeek(); }; -class AnimationNodeTransition : public AnimationNode { - GDCLASS(AnimationNodeTransition, AnimationNode); +class AnimationNodeTransition : public AnimationNodeSync { + GDCLASS(AnimationNodeTransition, AnimationNodeSync); enum { MAX_INPUTS = 32 @@ -300,10 +294,11 @@ class AnimationNodeTransition : public AnimationNode { StringName prev_xfading = "prev_xfading"; StringName prev = "prev"; StringName time = "time"; - StringName current = "current"; + StringName current = PNAME("current"); StringName prev_current = "prev_current"; float xfade = 0.0; + bool from_start = true; void _update_inputs(); @@ -329,7 +324,10 @@ public: void set_cross_fade_time(float p_fade); float get_cross_fade_time() const; - double process(double p_time, bool p_seek) override; + void set_from_start(bool p_from_start); + bool is_from_start() const; + + double process(double p_time, bool p_seek, bool p_seek_root) override; AnimationNodeTransition(); }; @@ -339,7 +337,7 @@ class AnimationNodeOutput : public AnimationNode { public: virtual String get_caption() const override; - virtual double process(double p_time, bool p_seek) override; + virtual double process(double p_time, bool p_seek, bool p_seek_root) override; AnimationNodeOutput(); }; @@ -354,7 +352,7 @@ class AnimationNodeBlendTree : public AnimationRootNode { Vector<StringName> connections; }; - Map<StringName, Node> nodes; + HashMap<StringName, Node> nodes; Vector2 graph_offset; @@ -408,7 +406,7 @@ public: void get_node_connections(List<NodeConnection> *r_connections) const; virtual String get_caption() const override; - virtual double process(double p_time, bool p_seek) override; + virtual double process(double p_time, bool p_seek, bool p_seek_root) override; void get_node_list(List<StringName> *r_list); diff --git a/scene/animation/animation_node_state_machine.cpp b/scene/animation/animation_node_state_machine.cpp index 5ea7f4b7d9..fe08e849a1 100644 --- a/scene/animation/animation_node_state_machine.cpp +++ b/scene/animation/animation_node_state_machine.cpp @@ -68,6 +68,34 @@ StringName AnimationNodeStateMachineTransition::get_advance_condition_name() con return advance_condition_name; } +void AnimationNodeStateMachineTransition::set_advance_expression(const String &p_expression) { + advance_expression = p_expression; + + String advance_expression_stripped = advance_expression.strip_edges(); + if (advance_expression_stripped == String()) { + expression.unref(); + return; + } + + if (expression.is_null()) { + expression.instantiate(); + } + + expression->parse(advance_expression_stripped); +} + +String AnimationNodeStateMachineTransition::get_advance_expression() const { + return advance_expression; +} + +void AnimationNodeStateMachineTransition::set_advance_expression_base_node(const NodePath &p_expression_base_node) { + advance_expression_base_node = p_expression_base_node; +} + +NodePath AnimationNodeStateMachineTransition::get_advance_expression_base_node() const { + return advance_expression_base_node; +} + void AnimationNodeStateMachineTransition::set_xfade_time(float p_xfade) { ERR_FAIL_COND(p_xfade < 0); xfade = p_xfade; @@ -115,11 +143,22 @@ void AnimationNodeStateMachineTransition::_bind_methods() { ClassDB::bind_method(D_METHOD("set_priority", "priority"), &AnimationNodeStateMachineTransition::set_priority); ClassDB::bind_method(D_METHOD("get_priority"), &AnimationNodeStateMachineTransition::get_priority); + ClassDB::bind_method(D_METHOD("set_advance_expression", "text"), &AnimationNodeStateMachineTransition::set_advance_expression); + ClassDB::bind_method(D_METHOD("get_advance_expression"), &AnimationNodeStateMachineTransition::get_advance_expression); + + ClassDB::bind_method(D_METHOD("set_advance_expression_base_node", "path"), &AnimationNodeStateMachineTransition::set_advance_expression_base_node); + ClassDB::bind_method(D_METHOD("get_advance_expression_base_node"), &AnimationNodeStateMachineTransition::get_advance_expression_base_node); + + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "xfade_time", PROPERTY_HINT_RANGE, "0,240,0.01,suffix:s"), "set_xfade_time", "get_xfade_time"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "priority", PROPERTY_HINT_RANGE, "0,32,1"), "set_priority", "get_priority"); + ADD_GROUP("Switch", ""); ADD_PROPERTY(PropertyInfo(Variant::INT, "switch_mode", PROPERTY_HINT_ENUM, "Immediate,Sync,At End"), "set_switch_mode", "get_switch_mode"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "auto_advance"), "set_auto_advance", "has_auto_advance"); + ADD_GROUP("Advance", "advance_"); ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "advance_condition"), "set_advance_condition", "get_advance_condition"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "xfade_time", PROPERTY_HINT_RANGE, "0,240,0.01"), "set_xfade_time", "get_xfade_time"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "priority", PROPERTY_HINT_RANGE, "0,32,1"), "set_priority", "get_priority"); + ADD_PROPERTY(PropertyInfo(Variant::STRING, "advance_expression", PROPERTY_HINT_EXPRESSION, ""), "set_advance_expression", "get_advance_expression"); + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "advance_expression_base_node"), "set_advance_expression_base_node", "get_advance_expression_base_node"); + ADD_GROUP("Disabling", ""); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "disabled"), "set_disabled", "is_disabled"); BIND_ENUM_CONSTANT(SWITCH_MODE_IMMEDIATE); @@ -185,27 +224,29 @@ bool AnimationNodeStateMachinePlayback::_travel(AnimationNodeStateMachine *p_sta return true; //nothing to do } - loops_current = 0; // reset loops, so fade does not happen immediately - Vector2 current_pos = p_state_machine->states[current].position; Vector2 target_pos = p_state_machine->states[p_travel].position; - Map<StringName, AStarCost> cost_map; + HashMap<StringName, AStarCost> cost_map; List<int> open_list; //build open list for (int i = 0; i < p_state_machine->transitions.size(); i++) { - if (p_state_machine->transitions[i].from == current) { + if (p_state_machine->transitions[i].transition->is_disabled()) { + continue; + } + + if (p_state_machine->transitions[i].local_from == current) { open_list.push_back(i); - float cost = p_state_machine->states[p_state_machine->transitions[i].to].position.distance_to(current_pos); + float cost = p_state_machine->states[p_state_machine->transitions[i].local_to].position.distance_to(current_pos); cost *= p_state_machine->transitions[i].transition->get_priority(); AStarCost ap; ap.prev = current; ap.distance = cost; - cost_map[p_state_machine->transitions[i].to] = ap; + cost_map[p_state_machine->transitions[i].local_to] = ap; - if (p_state_machine->transitions[i].to == p_travel) { //prematurely found it! :D + if (p_state_machine->transitions[i].local_to == p_travel) { //prematurely found it! :D path.push_back(p_travel); return true; } @@ -224,8 +265,8 @@ bool AnimationNodeStateMachinePlayback::_travel(AnimationNodeStateMachine *p_sta float least_cost = 1e20; for (List<int>::Element *E = open_list.front(); E; E = E->next()) { - float cost = cost_map[p_state_machine->transitions[E->get()].to].distance; - cost += p_state_machine->states[p_state_machine->transitions[E->get()].to].position.distance_to(target_pos); + float cost = cost_map[p_state_machine->transitions[E->get()].local_to].distance; + cost += p_state_machine->states[p_state_machine->transitions[E->get()].local_to].position.distance_to(target_pos); if (cost < least_cost) { least_cost_transition = E; @@ -233,34 +274,38 @@ bool AnimationNodeStateMachinePlayback::_travel(AnimationNodeStateMachine *p_sta } } - StringName transition_prev = p_state_machine->transitions[least_cost_transition->get()].from; - StringName transition = p_state_machine->transitions[least_cost_transition->get()].to; + StringName transition_prev = p_state_machine->transitions[least_cost_transition->get()].local_from; + StringName transition = p_state_machine->transitions[least_cost_transition->get()].local_to; for (int i = 0; i < p_state_machine->transitions.size(); i++) { - if (p_state_machine->transitions[i].from != transition || p_state_machine->transitions[i].to == transition_prev) { + if (p_state_machine->transitions[i].transition->is_disabled()) { + continue; + } + + if (p_state_machine->transitions[i].local_from != transition || p_state_machine->transitions[i].local_to == transition_prev) { continue; //not interested on those } - float distance = p_state_machine->states[p_state_machine->transitions[i].from].position.distance_to(p_state_machine->states[p_state_machine->transitions[i].to].position); + float distance = p_state_machine->states[p_state_machine->transitions[i].local_from].position.distance_to(p_state_machine->states[p_state_machine->transitions[i].local_to].position); distance *= p_state_machine->transitions[i].transition->get_priority(); - distance += cost_map[p_state_machine->transitions[i].from].distance; + distance += cost_map[p_state_machine->transitions[i].local_from].distance; - if (cost_map.has(p_state_machine->transitions[i].to)) { + if (cost_map.has(p_state_machine->transitions[i].local_to)) { //oh this was visited already, can we win the cost? - if (distance < cost_map[p_state_machine->transitions[i].to].distance) { - cost_map[p_state_machine->transitions[i].to].distance = distance; - cost_map[p_state_machine->transitions[i].to].prev = p_state_machine->transitions[i].from; + if (distance < cost_map[p_state_machine->transitions[i].local_to].distance) { + cost_map[p_state_machine->transitions[i].local_to].distance = distance; + cost_map[p_state_machine->transitions[i].local_to].prev = p_state_machine->transitions[i].local_from; } } else { //add to open list AStarCost ac; - ac.prev = p_state_machine->transitions[i].from; + ac.prev = p_state_machine->transitions[i].local_from; ac.distance = distance; - cost_map[p_state_machine->transitions[i].to] = ac; + cost_map[p_state_machine->transitions[i].local_to] = ac; open_list.push_back(i); - if (p_state_machine->transitions[i].to == p_travel) { + if (p_state_machine->transitions[i].local_to == p_travel) { found_route = true; break; } @@ -286,7 +331,7 @@ bool AnimationNodeStateMachinePlayback::_travel(AnimationNodeStateMachine *p_sta return true; } -double AnimationNodeStateMachinePlayback::process(AnimationNodeStateMachine *p_state_machine, double p_time, bool p_seek) { +double AnimationNodeStateMachinePlayback::process(AnimationNodeStateMachine *p_state_machine, double p_time, bool p_seek, bool p_seek_root) { //if not playing and it can restart, then restart if (!playing && start_request == StringName()) { if (!stop_request && p_state_machine->start_node) { @@ -350,9 +395,8 @@ double AnimationNodeStateMachinePlayback::process(AnimationNodeStateMachine *p_s current = p_state_machine->start_node; } - len_current = p_state_machine->blend_node(current, p_state_machine->states[current].node, 0, true, 1.0, AnimationNode::FILTER_IGNORE, false); + len_current = p_state_machine->blend_node(current, p_state_machine->states[current].node, 0, true, p_seek_root, 1.0, AnimationNode::FILTER_IGNORE, true); pos_current = 0; - loops_current = 0; } if (!p_state_machine->states.has(current)) { @@ -376,10 +420,10 @@ double AnimationNodeStateMachinePlayback::process(AnimationNodeStateMachine *p_s } } - float rem = p_state_machine->blend_node(current, p_state_machine->states[current].node, p_time, p_seek, fade_blend, AnimationNode::FILTER_IGNORE, false); + float rem = p_state_machine->blend_node(current, p_state_machine->states[current].node, p_time, p_seek, p_seek_root, fade_blend, AnimationNode::FILTER_IGNORE, true); if (fading_from != StringName()) { - p_state_machine->blend_node(fading_from, p_state_machine->states[fading_from].node, p_time, p_seek, 1.0 - fade_blend, AnimationNode::FILTER_IGNORE, false); + p_state_machine->blend_node(fading_from, p_state_machine->states[fading_from].node, p_time, p_seek, p_seek_root, 1.0 - fade_blend, AnimationNode::FILTER_IGNORE, true); } //guess playback position @@ -388,12 +432,8 @@ double AnimationNodeStateMachinePlayback::process(AnimationNodeStateMachine *p_s } { //advance and loop check - float next_pos = len_current - rem; - - if (next_pos < pos_current) { - loops_current++; - } + end_loop = next_pos < pos_current; pos_current = next_pos; //looped } @@ -404,7 +444,11 @@ double AnimationNodeStateMachinePlayback::process(AnimationNodeStateMachine *p_s if (path.size()) { for (int i = 0; i < p_state_machine->transitions.size(); i++) { - if (p_state_machine->transitions[i].from == current && p_state_machine->transitions[i].to == path[0]) { + if (p_state_machine->transitions[i].transition->is_disabled()) { + continue; + } + + if (p_state_machine->transitions[i].local_from == current && p_state_machine->transitions[i].local_to == path[0]) { next_xfade = p_state_machine->transitions[i].transition->get_xfade_time(); switch_mode = p_state_machine->transitions[i].transition->get_switch_mode(); next = path[0]; @@ -413,17 +457,39 @@ double AnimationNodeStateMachinePlayback::process(AnimationNodeStateMachine *p_s } else { float priority_best = 1e20; int auto_advance_to = -1; + for (int i = 0; i < p_state_machine->transitions.size(); i++) { - bool auto_advance = false; - if (p_state_machine->transitions[i].transition->has_auto_advance()) { - auto_advance = true; + if (p_state_machine->transitions[i].transition->is_disabled()) { + continue; } - StringName advance_condition_name = p_state_machine->transitions[i].transition->get_advance_condition_name(); - if (advance_condition_name != StringName() && bool(p_state_machine->get_parameter(advance_condition_name))) { - auto_advance = true; + + // handles end_node: when end_node is reached in a sub state machine, find and activate the current_transition + if (force_auto_advance) { + if (p_state_machine->transitions[i].from == current_transition.from && p_state_machine->transitions[i].to == current_transition.to) { + auto_advance_to = i; + force_auto_advance = false; + break; + } } - if (p_state_machine->transitions[i].from == current && auto_advance) { + // handles start_node: if previous state machine is pointing to a node inside the current state machine, starts the current machine from start_node to prev_local_to + if (p_state_machine->start_node == current && p_state_machine->transitions[i].local_from == current) { + if (p_state_machine->prev_state_machine != nullptr) { + Ref<AnimationNodeStateMachinePlayback> prev_playback = p_state_machine->prev_state_machine->get_parameter("playback"); + + if (prev_playback.is_valid()) { + StringName prev_local_to = String(prev_playback->current_transition.next).replace_first(String(p_state_machine->state_machine_name) + "/", ""); + + if (p_state_machine->transitions[i].to == prev_local_to) { + auto_advance_to = i; + prev_playback->current_transition.next = StringName(); + break; + } + } + } + } + + if (p_state_machine->transitions[i].from == current && _check_advance_condition(p_state_machine, p_state_machine->transitions[i].transition)) { if (p_state_machine->transitions[i].transition->get_priority() <= priority_best) { priority_best = p_state_machine->transitions[i].transition->get_priority(); auto_advance_to = i; @@ -432,26 +498,69 @@ double AnimationNodeStateMachinePlayback::process(AnimationNodeStateMachine *p_s } if (auto_advance_to != -1) { - next = p_state_machine->transitions[auto_advance_to].to; + next = p_state_machine->transitions[auto_advance_to].local_to; + Transition tr; + tr.from = String(p_state_machine->state_machine_name) + "/" + String(p_state_machine->transitions[auto_advance_to].from); + tr.to = String(p_state_machine->transitions[auto_advance_to].to).replace_first("../", ""); + tr.next = p_state_machine->transitions[auto_advance_to].to; + current_transition = tr; next_xfade = p_state_machine->transitions[auto_advance_to].transition->get_xfade_time(); switch_mode = p_state_machine->transitions[auto_advance_to].transition->get_switch_mode(); } } + if (next == p_state_machine->end_node) { + AnimationNodeStateMachine *prev_state_machine = p_state_machine->prev_state_machine; + + if (prev_state_machine != nullptr) { + Ref<AnimationNodeStateMachinePlayback> prev_playback = prev_state_machine->get_parameter("playback"); + + if (prev_playback.is_valid()) { + if (next_xfade) { + prev_playback->current_transition = current_transition; + prev_playback->force_auto_advance = true; + + return rem; + } + float priority_best = 1e20; + int auto_advance_to = -1; + + for (int i = 0; i < prev_state_machine->transitions.size(); i++) { + if (prev_state_machine->transitions[i].transition->is_disabled()) { + continue; + } + + if (current_transition.next == prev_state_machine->end_node && _check_advance_condition(prev_state_machine, prev_state_machine->transitions[i].transition)) { + if (prev_state_machine->transitions[i].transition->get_priority() <= priority_best) { + priority_best = prev_state_machine->transitions[i].transition->get_priority(); + auto_advance_to = i; + } + } + } + + if (auto_advance_to != -1) { + if (prev_state_machine->transitions[auto_advance_to].transition->get_xfade_time()) { + return rem; + } + } + } + } + } + //if next, see when to transition if (next != StringName()) { bool goto_next = false; if (switch_mode == AnimationNodeStateMachineTransition::SWITCH_MODE_AT_END) { - goto_next = next_xfade >= (len_current - pos_current) || loops_current > 0; - if (loops_current > 0) { + goto_next = next_xfade >= (len_current - pos_current) || end_loop; + if (end_loop) { next_xfade = 0; } } else { goto_next = fading_from == StringName(); } - if (goto_next) { //loops should be used because fade time may be too small or zero and animation may have looped + if (goto_next) { //end_loop should be used because fade time may be too small or zero and animation may have looped if (next_xfade) { //time to fade, baby @@ -468,28 +577,71 @@ double AnimationNodeStateMachinePlayback::process(AnimationNodeStateMachine *p_s } current = next; if (switch_mode == AnimationNodeStateMachineTransition::SWITCH_MODE_SYNC) { - len_current = p_state_machine->blend_node(current, p_state_machine->states[current].node, 0, true, 0, AnimationNode::FILTER_IGNORE, false); + len_current = p_state_machine->blend_node(current, p_state_machine->states[current].node, 0, true, p_seek_root, 0, AnimationNode::FILTER_IGNORE, true); pos_current = MIN(pos_current, len_current); - p_state_machine->blend_node(current, p_state_machine->states[current].node, pos_current, true, 0, AnimationNode::FILTER_IGNORE, false); + p_state_machine->blend_node(current, p_state_machine->states[current].node, pos_current, true, p_seek_root, 0, AnimationNode::FILTER_IGNORE, true); } else { - len_current = p_state_machine->blend_node(current, p_state_machine->states[current].node, 0, true, 0, AnimationNode::FILTER_IGNORE, false); + len_current = p_state_machine->blend_node(current, p_state_machine->states[current].node, 0, true, p_seek_root, 0, AnimationNode::FILTER_IGNORE, true); pos_current = 0; } rem = len_current; //so it does not show 0 on transition - loops_current = 0; } } - //compute time left for transitions by using the end node - if (p_state_machine->end_node != StringName() && p_state_machine->end_node != current) { - rem = p_state_machine->blend_node(p_state_machine->end_node, p_state_machine->states[p_state_machine->end_node].node, 0, true, 0, AnimationNode::FILTER_IGNORE, false); + // time left must always be 1 because the end node don't length to compute + if (p_state_machine->end_node != current) { + rem = 1; + } else { + Ref<AnimationNodeStateMachinePlayback> prev_playback = p_state_machine->prev_state_machine->get_parameter("playback"); + + if (prev_playback.is_valid()) { + prev_playback->current_transition = current_transition; + prev_playback->force_auto_advance = true; + } } return rem; } +bool AnimationNodeStateMachinePlayback::_check_advance_condition(const Ref<AnimationNodeStateMachine> state_machine, const Ref<AnimationNodeStateMachineTransition> transition) const { + if (transition->has_auto_advance()) { + return true; + } + + StringName advance_condition_name = transition->get_advance_condition_name(); + + if (advance_condition_name != StringName() && bool(state_machine->get_parameter(advance_condition_name))) { + return true; + } + + if (transition->expression.is_valid()) { + AnimationTree *tree_base = state_machine->get_animation_tree(); + ERR_FAIL_COND_V(tree_base == nullptr, false); + + NodePath advance_expression_base_node_path; + if (!transition->advance_expression_base_node.is_empty()) { + advance_expression_base_node_path = transition->advance_expression_base_node; + } else { + advance_expression_base_node_path = tree_base->get_advance_expression_base_node(); + } + + Node *expression_base = tree_base->get_node_or_null(advance_expression_base_node_path); + if (expression_base) { + Ref<Expression> exp = transition->expression; + bool ret = exp->execute(Array(), tree_base, false, Engine::get_singleton()->is_editor_hint()); // Avoids allowing the user to crash the system with an expression by only allowing const calls. + if (!exp->has_execute_failed()) { + if (ret) { + return true; + } + } + } + } + + return false; +} + void AnimationNodeStateMachinePlayback::_bind_methods() { ClassDB::bind_method(D_METHOD("travel", "to_node"), &AnimationNodeStateMachinePlayback::travel); ClassDB::bind_method(D_METHOD("start", "node"), &AnimationNodeStateMachinePlayback::start); @@ -521,6 +673,23 @@ void AnimationNodeStateMachine::get_parameter_list(List<PropertyInfo> *r_list) c for (const StringName &E : advance_conditions) { r_list->push_back(PropertyInfo(Variant::BOOL, E)); } + + // for (const KeyValue<StringName, State> &E : states) { + // if (E->node == ansm) { + // for (int i = 0; i < E->node->transitions.size(); i++) { + // StringName ac = E->node->transitions[i].transition->get_advance_condition_name(); + // if (ac != StringName() && advance_conditions.find(ac) == nullptr) { + // advance_conditions.push_back(ac); + // } + // } + + // advance_conditions.sort_custom<StringName::AlphCompare>(); + + // for (const StringName &E : advance_conditions) { + // r_list->push_back(PropertyInfo(Variant::BOOL, E)); + // } + // } + // } } Variant AnimationNodeStateMachine::get_parameter_default_value(const StringName &p_parameter) const { @@ -544,6 +713,13 @@ void AnimationNodeStateMachine::add_node(const StringName &p_name, Ref<Animation states[p_name] = state; + Ref<AnimationNodeStateMachine> anodesm = p_node; + + if (anodesm.is_valid()) { + anodesm->state_machine_name = p_name; + anodesm->prev_state_machine = this; + } + emit_changed(); emit_signal(SNAME("tree_changed")); @@ -570,6 +746,14 @@ void AnimationNodeStateMachine::replace_node(const StringName &p_name, Ref<Anima p_node->connect("tree_changed", callable_mp(this, &AnimationNodeStateMachine::_tree_changed), varray(), CONNECT_REFERENCE_COUNTED); } +bool AnimationNodeStateMachine::can_edit_node(const StringName &p_name) const { + if (states.has(p_name)) { + return !(states[p_name].node->is_class("AnimationNodeStartState") || states[p_name].node->is_class("AnimationNodeEndState")); + } + + return true; +} + Ref<AnimationNode> AnimationNodeStateMachine::get_node(const StringName &p_name) const { ERR_FAIL_COND_V(!states.has(p_name), Ref<AnimationNode>()); @@ -610,36 +794,24 @@ bool AnimationNodeStateMachine::has_node(const StringName &p_name) const { void AnimationNodeStateMachine::remove_node(const StringName &p_name) { ERR_FAIL_COND(!states.has(p_name)); - { - Ref<AnimationNode> node = states[p_name].node; - - ERR_FAIL_COND(node.is_null()); - - node->disconnect("tree_changed", callable_mp(this, &AnimationNodeStateMachine::_tree_changed)); + if (!can_edit_node(p_name)) { + return; } - states.erase(p_name); - //path.erase(p_name); - for (int i = 0; i < transitions.size(); i++) { - if (transitions[i].from == p_name || transitions[i].to == p_name) { - transitions.write[i].transition->disconnect("advance_condition_changed", callable_mp(this, &AnimationNodeStateMachine::_tree_changed)); - transitions.remove_at(i); + if (transitions[i].local_from == p_name || transitions[i].local_to == p_name) { + remove_transition_by_index(i); i--; } } - if (start_node == p_name) { - start_node = StringName(); - } - - if (end_node == p_name) { - end_node = StringName(); + { + Ref<AnimationNode> node = states[p_name].node; + ERR_FAIL_COND(node.is_null()); + node->disconnect("tree_changed", callable_mp(this, &AnimationNodeStateMachine::_tree_changed)); } - /*if (playing && current == p_name) { - stop(); - }*/ + states.erase(p_name); emit_changed(); emit_signal(SNAME("tree_changed")); @@ -648,39 +820,73 @@ void AnimationNodeStateMachine::remove_node(const StringName &p_name) { void AnimationNodeStateMachine::rename_node(const StringName &p_name, const StringName &p_new_name) { ERR_FAIL_COND(!states.has(p_name)); ERR_FAIL_COND(states.has(p_new_name)); + ERR_FAIL_COND(!can_edit_node(p_name)); states[p_new_name] = states[p_name]; states.erase(p_name); + Ref<AnimationNodeStateMachine> anodesm = states[p_new_name].node; + if (anodesm.is_valid()) { + anodesm->state_machine_name = p_new_name; + } + for (int i = 0; i < transitions.size(); i++) { - if (transitions[i].from == p_name) { - transitions.write[i].from = p_new_name; + if (transitions[i].local_from == p_name) { + _rename_transition(transitions[i].from, String(transitions[i].from).replace_first(p_name, p_new_name)); } - if (transitions[i].to == p_name) { - transitions.write[i].to = p_new_name; + if (transitions[i].local_to == p_name) { + _rename_transition(transitions[i].to, String(transitions[i].to).replace_first(p_name, p_new_name)); } } - if (start_node == p_name) { - start_node = p_new_name; - } + emit_signal("tree_changed"); +} - if (end_node == p_name) { - end_node = p_new_name; +void AnimationNodeStateMachine::_rename_transition(const StringName &p_name, const StringName &p_new_name) { + if (updating_transitions) { + return; } - /*if (playing && current == p_name) { - current = p_new_name; - }*/ + updating_transitions = true; + for (int i = 0; i < transitions.size(); i++) { + if (transitions[i].from == p_name) { + Vector<String> path = String(transitions[i].to).split("/"); + if (path.size() > 1) { + if (path[0] == "..") { + prev_state_machine->_rename_transition(String(state_machine_name) + "/" + p_name, String(state_machine_name) + "/" + p_new_name); + } else { + ((Ref<AnimationNodeStateMachine>)states[transitions[i].local_to].node)->_rename_transition("../" + p_name, "../" + p_new_name); + } + } - //path.clear(); //clear path - emit_signal(SNAME("tree_changed")); + transitions.write[i].from = p_new_name; + } + + if (transitions[i].to == p_name) { + Vector<String> path = String(transitions[i].from).split("/"); + if (path.size() > 1) { + if (path[0] == "..") { + prev_state_machine->_rename_transition(String(state_machine_name) + "/" + p_name, String(state_machine_name) + "/" + p_new_name); + } else { + ((Ref<AnimationNodeStateMachine>)states[transitions[i].local_from].node)->_rename_transition("../" + p_name, "../" + p_new_name); + } + } + + transitions.write[i].to = p_new_name; + } + + updating_transitions = false; + } } void AnimationNodeStateMachine::get_node_list(List<StringName> *r_nodes) const { List<StringName> nodes; for (const KeyValue<StringName, State> &E : states) { + if (E.key == end_node && prev_state_machine == nullptr) { + continue; + } + nodes.push_back(E.key); } nodes.sort_custom<StringName::AlphCompare>(); @@ -690,9 +896,16 @@ void AnimationNodeStateMachine::get_node_list(List<StringName> *r_nodes) const { } } +AnimationNodeStateMachine *AnimationNodeStateMachine::get_prev_state_machine() const { + return prev_state_machine; +} + bool AnimationNodeStateMachine::has_transition(const StringName &p_from, const StringName &p_to) const { + StringName from = _get_shortest_path(p_from); + StringName to = _get_shortest_path(p_to); + for (int i = 0; i < transitions.size(); i++) { - if (transitions[i].from == p_from && transitions[i].to == p_to) { + if (transitions[i].from == from && transitions[i].to == to) { return true; } } @@ -700,32 +913,148 @@ bool AnimationNodeStateMachine::has_transition(const StringName &p_from, const S } int AnimationNodeStateMachine::find_transition(const StringName &p_from, const StringName &p_to) const { + StringName from = _get_shortest_path(p_from); + StringName to = _get_shortest_path(p_to); + for (int i = 0; i < transitions.size(); i++) { - if (transitions[i].from == p_from && transitions[i].to == p_to) { + if (transitions[i].from == from && transitions[i].to == to) { return i; } } return -1; } +bool AnimationNodeStateMachine::_can_connect(const StringName &p_name, Vector<AnimationNodeStateMachine *> p_parents) { + if (p_parents.is_empty()) { + AnimationNodeStateMachine *prev = this; + while (prev != nullptr) { + p_parents.push_back(prev); + prev = prev->prev_state_machine; + } + } + + if (states.has(p_name)) { + Ref<AnimationNodeStateMachine> anodesm = states[p_name].node; + + if (anodesm.is_valid() && p_parents.find(anodesm.ptr()) != -1) { + return false; + } + + return true; + } + + String name = p_name; + Vector<String> path = name.split("/"); + + if (path.size() < 2) { + return false; + } + + if (path[0] == "..") { + if (prev_state_machine != nullptr) { + return prev_state_machine->_can_connect(name.replace_first("../", ""), p_parents); + } + } else if (states.has(path[0])) { + Ref<AnimationNodeStateMachine> anodesm = states[path[0]].node; + if (anodesm.is_valid()) { + return anodesm->_can_connect(name.replace_first(path[0] + "/", ""), p_parents); + } + } + + return false; +} + +StringName AnimationNodeStateMachine::_get_shortest_path(const StringName &p_path) const { + // If p_path is something like StateMachine/../StateMachine2/State1, + // the result will be StateMachine2/State1. This avoid duplicate + // transitions when using add_transition. eg, this two calls is the same: + // + // add_transition("State1", "StateMachine/../State2", tr) + // add_transition("State1", "State2", tr) + // + // but the second call must be invalid because the transition already exists + + Vector<String> path = String(p_path).split("/"); + Vector<String> new_path; + + for (int i = 0; i < path.size(); i++) { + if (i > 0 && path[i] == ".." && new_path[i - 1] != "..") { + new_path.remove_at(i - 1); + } else { + new_path.push_back(path[i]); + } + } + + String result; + for (int i = 0; i < new_path.size(); i++) { + result += new_path[i] + "/"; + } + result.remove_at(result.length() - 1); + + return result; +} + void AnimationNodeStateMachine::add_transition(const StringName &p_from, const StringName &p_to, const Ref<AnimationNodeStateMachineTransition> &p_transition) { - ERR_FAIL_COND(p_from == p_to); - ERR_FAIL_COND(!states.has(p_from)); - ERR_FAIL_COND(!states.has(p_to)); + if (updating_transitions) { + return; + } + + StringName from = _get_shortest_path(p_from); + StringName to = _get_shortest_path(p_to); + Vector<String> path_from = String(from).split("/"); + Vector<String> path_to = String(to).split("/"); + + ERR_FAIL_COND(from == end_node || to == start_node); + ERR_FAIL_COND(from == to); + ERR_FAIL_COND(!_can_connect(from)); + ERR_FAIL_COND(!_can_connect(to)); ERR_FAIL_COND(p_transition.is_null()); for (int i = 0; i < transitions.size(); i++) { - ERR_FAIL_COND(transitions[i].from == p_from && transitions[i].to == p_to); + ERR_FAIL_COND(transitions[i].from == from && transitions[i].to == to); + } + + if (path_from.size() > 1 || path_to.size() > 1) { + ERR_FAIL_COND(path_from[0] == path_to[0]); } + updating_transitions = true; + + StringName local_from = String(from).get_slicec('/', 0); + StringName local_to = String(to).get_slicec('/', 0); + local_from = local_from == ".." ? "Start" : local_from; + local_to = local_to == ".." ? "End" : local_to; + Transition tr; - tr.from = p_from; - tr.to = p_to; + tr.from = from; + tr.to = to; + tr.local_from = local_from; + tr.local_to = local_to; tr.transition = p_transition; tr.transition->connect("advance_condition_changed", callable_mp(this, &AnimationNodeStateMachine::_tree_changed), varray(), CONNECT_REFERENCE_COUNTED); transitions.push_back(tr); + + // do recursive + if (path_from.size() > 1) { + StringName local_path = String(from).replace_first(path_from[0] + "/", ""); + if (path_from[0] == "..") { + prev_state_machine->add_transition(local_path, String(state_machine_name) + "/" + to, p_transition); + } else { + ((Ref<AnimationNodeStateMachine>)states[path_from[0]].node)->add_transition(local_path, "../" + to, p_transition); + } + } + if (path_to.size() > 1) { + StringName local_path = String(to).replace_first(path_to[0] + "/", ""); + if (path_to[0] == "..") { + prev_state_machine->add_transition(String(state_machine_name) + "/" + from, local_path, p_transition); + } else { + ((Ref<AnimationNodeStateMachine>)states[path_to[0]].node)->add_transition("../" + from, local_path, p_transition); + } + } + + updating_transitions = false; } Ref<AnimationNodeStateMachineTransition> AnimationNodeStateMachine::get_transition(int p_transition) const { @@ -748,44 +1077,52 @@ int AnimationNodeStateMachine::get_transition_count() const { } void AnimationNodeStateMachine::remove_transition(const StringName &p_from, const StringName &p_to) { + StringName from = _get_shortest_path(p_from); + StringName to = _get_shortest_path(p_to); + for (int i = 0; i < transitions.size(); i++) { - if (transitions[i].from == p_from && transitions[i].to == p_to) { - transitions.write[i].transition->disconnect("advance_condition_changed", callable_mp(this, &AnimationNodeStateMachine::_tree_changed)); - transitions.remove_at(i); + if (transitions[i].from == from && transitions[i].to == to) { + remove_transition_by_index(i); return; } } - - /*if (playing) { - path.clear(); - }*/ } -void AnimationNodeStateMachine::remove_transition_by_index(int p_transition) { +void AnimationNodeStateMachine::remove_transition_by_index(const int p_transition) { ERR_FAIL_INDEX(p_transition, transitions.size()); + Transition tr = transitions[p_transition]; transitions.write[p_transition].transition->disconnect("advance_condition_changed", callable_mp(this, &AnimationNodeStateMachine::_tree_changed)); transitions.remove_at(p_transition); - /*if (playing) { - path.clear(); - }*/ -} -void AnimationNodeStateMachine::set_start_node(const StringName &p_node) { - ERR_FAIL_COND(p_node != StringName() && !states.has(p_node)); - start_node = p_node; -} + Vector<String> path_from = String(tr.from).split("/"); + Vector<String> path_to = String(tr.to).split("/"); -String AnimationNodeStateMachine::get_start_node() const { - return start_node; -} + List<Vector<String>> paths; + paths.push_back(path_from); + paths.push_back(path_to); + + for (List<Vector<String>>::Element *E = paths.front(); E; E = E->next()) { + if (E->get()[0].size() > 1) { + if (E->get()[0] == "..") { + prev_state_machine->_remove_transition(tr.transition); + } else if (states.has(E->get()[0])) { + Ref<AnimationNodeStateMachine> anodesm = states[E->get()[0]].node; -void AnimationNodeStateMachine::set_end_node(const StringName &p_node) { - ERR_FAIL_COND(p_node != StringName() && !states.has(p_node)); - end_node = p_node; + if (anodesm.is_valid()) { + anodesm->_remove_transition(tr.transition); + } + } + } + } } -String AnimationNodeStateMachine::get_end_node() const { - return end_node; +void AnimationNodeStateMachine::_remove_transition(const Ref<AnimationNodeStateMachineTransition> p_transition) { + for (int i = 0; i < transitions.size(); i++) { + if (transitions[i].transition == p_transition) { + remove_transition_by_index(i); + return; + } + } } void AnimationNodeStateMachine::set_graph_offset(const Vector2 &p_offset) { @@ -796,17 +1133,29 @@ Vector2 AnimationNodeStateMachine::get_graph_offset() const { return graph_offset; } -double AnimationNodeStateMachine::process(double p_time, bool p_seek) { +double AnimationNodeStateMachine::process(double p_time, bool p_seek, bool p_seek_root) { Ref<AnimationNodeStateMachinePlayback> playback = get_parameter(this->playback); ERR_FAIL_COND_V(playback.is_null(), 0.0); - return playback->process(this, p_time, p_seek); + return playback->process(this, p_time, p_seek, p_seek_root); } String AnimationNodeStateMachine::get_caption() const { return "StateMachine"; } +bool AnimationNodeStateMachine::has_local_transition(const StringName &p_from, const StringName &p_to) const { + StringName from = _get_shortest_path(p_from); + StringName to = _get_shortest_path(p_to); + + for (int i = 0; i < transitions.size(); i++) { + if (transitions[i].local_from == from && transitions[i].local_to == to) { + return true; + } + } + return false; +} + Ref<AnimationNode> AnimationNodeStateMachine::get_child_by_name(const StringName &p_name) { return get_node(p_name); } @@ -839,12 +1188,6 @@ bool AnimationNodeStateMachine::_set(const StringName &p_name, const Variant &p_ add_transition(trans[i], trans[i + 1], trans[i + 2]); } return true; - } else if (name == "start_node") { - set_start_node(p_value); - return true; - } else if (name == "end_node") { - set_end_node(p_value); - return true; } else if (name == "graph_offset") { set_graph_offset(p_value); return true; @@ -860,7 +1203,7 @@ bool AnimationNodeStateMachine::_get(const StringName &p_name, Variant &r_ret) c String what = name.get_slicec('/', 2); if (what == "node") { - if (states.has(node_name)) { + if (states.has(node_name) && can_edit_node(node_name)) { r_ret = states[node_name].node; return true; } @@ -874,22 +1217,21 @@ bool AnimationNodeStateMachine::_get(const StringName &p_name, Variant &r_ret) c } } else if (name == "transitions") { Array trans; - trans.resize(transitions.size() * 3); - for (int i = 0; i < transitions.size(); i++) { - trans[i * 3 + 0] = transitions[i].from; - trans[i * 3 + 1] = transitions[i].to; - trans[i * 3 + 2] = transitions[i].transition; + String from = transitions[i].from; + String to = transitions[i].to; + + if (from.get_slicec('/', 0) == ".." || to.get_slicec('/', 0) == "..") { + continue; + } + + trans.push_back(from); + trans.push_back(to); + trans.push_back(transitions[i].transition); } r_ret = trans; return true; - } else if (name == "start_node") { - r_ret = get_start_node(); - return true; - } else if (name == "end_node") { - r_ret = get_end_node(); - return true; } else if (name == "graph_offset") { r_ret = get_graph_offset(); return true; @@ -911,8 +1253,6 @@ void AnimationNodeStateMachine::_get_property_list(List<PropertyInfo> *p_list) c } p_list->push_back(PropertyInfo(Variant::ARRAY, "transitions", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR)); - p_list->push_back(PropertyInfo(Variant::STRING_NAME, "start_node", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR)); - p_list->push_back(PropertyInfo(Variant::STRING_NAME, "end_node", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR)); p_list->push_back(PropertyInfo(Variant::VECTOR2, "graph_offset", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR)); } @@ -920,10 +1260,24 @@ void AnimationNodeStateMachine::reset_state() { states.clear(); transitions.clear(); playback = "playback"; - start_node = StringName(); - end_node = StringName(); + start_node = "Start"; + end_node = "End"; graph_offset = Vector2(); + Ref<AnimationNodeStartState> s; + s.instantiate(); + State start; + start.node = s; + start.position = Vector2(200, 100); + states[start_node] = start; + + Ref<AnimationNodeEndState> e; + e.instantiate(); + State end; + end.node = e; + end.position = Vector2(900, 100); + states[end_node] = end; + emit_changed(); emit_signal(SNAME("tree_changed")); } @@ -963,15 +1317,22 @@ void AnimationNodeStateMachine::_bind_methods() { ClassDB::bind_method(D_METHOD("remove_transition_by_index", "idx"), &AnimationNodeStateMachine::remove_transition_by_index); ClassDB::bind_method(D_METHOD("remove_transition", "from", "to"), &AnimationNodeStateMachine::remove_transition); - ClassDB::bind_method(D_METHOD("set_start_node", "name"), &AnimationNodeStateMachine::set_start_node); - ClassDB::bind_method(D_METHOD("get_start_node"), &AnimationNodeStateMachine::get_start_node); - - ClassDB::bind_method(D_METHOD("set_end_node", "name"), &AnimationNodeStateMachine::set_end_node); - ClassDB::bind_method(D_METHOD("get_end_node"), &AnimationNodeStateMachine::get_end_node); - ClassDB::bind_method(D_METHOD("set_graph_offset", "offset"), &AnimationNodeStateMachine::set_graph_offset); ClassDB::bind_method(D_METHOD("get_graph_offset"), &AnimationNodeStateMachine::get_graph_offset); } AnimationNodeStateMachine::AnimationNodeStateMachine() { + Ref<AnimationNodeStartState> s; + s.instantiate(); + State start; + start.node = s; + start.position = Vector2(200, 100); + states[start_node] = start; + + Ref<AnimationNodeEndState> e; + e.instantiate(); + State end; + end.node = e; + end.position = Vector2(900, 100); + states[end_node] = end; } diff --git a/scene/animation/animation_node_state_machine.h b/scene/animation/animation_node_state_machine.h index 3bae0fcffa..d4e58ca3d3 100644 --- a/scene/animation/animation_node_state_machine.h +++ b/scene/animation/animation_node_state_machine.h @@ -31,6 +31,7 @@ #ifndef ANIMATION_NODE_STATE_MACHINE_H #define ANIMATION_NODE_STATE_MACHINE_H +#include "core/math/expression.h" #include "scene/animation/animation_tree.h" class AnimationNodeStateMachineTransition : public Resource { @@ -51,6 +52,11 @@ private: float xfade = 0.0; bool disabled = false; int priority = 1; + String advance_expression; + NodePath advance_expression_base_node; + + friend class AnimationNodeStateMachinePlayback; + Ref<Expression> expression; protected: static void _bind_methods(); @@ -67,6 +73,12 @@ public: StringName get_advance_condition_name() const; + void set_advance_expression(const String &p_expression); + String get_advance_expression() const; + + void set_advance_expression_base_node(const NodePath &p_expression_base_node); + NodePath get_advance_expression_base_node() const; + void set_xfade_time(float p_xfade); float get_xfade_time() const; @@ -93,13 +105,19 @@ class AnimationNodeStateMachinePlayback : public Resource { StringName prev; }; - float len_total = 0.0; + struct Transition { + StringName from; + StringName to; + StringName next; + }; float len_current = 0.0; float pos_current = 0.0; - int loops_current = 0; + bool end_loop = false; StringName current; + Transition current_transition; + bool force_auto_advance = false; StringName fading_from; float fading_time = 0.0; @@ -114,7 +132,9 @@ class AnimationNodeStateMachinePlayback : public Resource { bool _travel(AnimationNodeStateMachine *p_state_machine, const StringName &p_travel); - double process(AnimationNodeStateMachine *p_state_machine, double p_time, bool p_seek); + double process(AnimationNodeStateMachine *p_state_machine, double p_time, bool p_seek, bool p_seek_root); + + bool _check_advance_condition(const Ref<AnimationNodeStateMachine> p_state_machine, const Ref<AnimationNodeStateMachineTransition> p_transition) const; protected: static void _bind_methods(); @@ -144,24 +164,30 @@ private: Vector2 position; }; - Map<StringName, State> states; + HashMap<StringName, State> states; struct Transition { StringName from; StringName to; + StringName local_from; + StringName local_to; Ref<AnimationNodeStateMachineTransition> transition; }; Vector<Transition> transitions; StringName playback = "playback"; - - StringName start_node; - StringName end_node; + StringName state_machine_name; + AnimationNodeStateMachine *prev_state_machine = nullptr; + bool updating_transitions = false; Vector2 graph_offset; void _tree_changed(); + void _remove_transition(const Ref<AnimationNodeStateMachineTransition> p_transition); + void _rename_transition(const StringName &p_name, const StringName &p_new_name); + bool _can_connect(const StringName &p_name, Vector<AnimationNodeStateMachine *> p_parents = Vector<AnimationNodeStateMachine *>()); + StringName _get_shortest_path(const StringName &p_path) const; protected: static void _bind_methods(); @@ -169,10 +195,14 @@ protected: bool _set(const StringName &p_name, const Variant &p_value); bool _get(const StringName &p_name, Variant &r_ret) const; void _get_property_list(List<PropertyInfo> *p_list) const; + bool _check_advance_condition(const Ref<AnimationNodeStateMachine> p_state_machine, const Ref<AnimationNodeStateMachineTransition> p_transition) const; virtual void reset_state() override; public: + StringName start_node = "Start"; + StringName end_node = "End"; + virtual void get_parameter_list(List<PropertyInfo> *r_list) const override; virtual Variant get_parameter_default_value(const StringName &p_parameter) const override; @@ -191,25 +221,24 @@ public: virtual void get_child_nodes(List<ChildNode> *r_child_nodes) override; bool has_transition(const StringName &p_from, const StringName &p_to) const; + bool has_local_transition(const StringName &p_from, const StringName &p_to) const; int find_transition(const StringName &p_from, const StringName &p_to) const; void add_transition(const StringName &p_from, const StringName &p_to, const Ref<AnimationNodeStateMachineTransition> &p_transition); Ref<AnimationNodeStateMachineTransition> get_transition(int p_transition) const; StringName get_transition_from(int p_transition) const; StringName get_transition_to(int p_transition) const; int get_transition_count() const; - void remove_transition_by_index(int p_transition); + void remove_transition_by_index(const int p_transition); void remove_transition(const StringName &p_from, const StringName &p_to); - void set_start_node(const StringName &p_node); - String get_start_node() const; + bool can_edit_node(const StringName &p_name) const; - void set_end_node(const StringName &p_node); - String get_end_node() const; + AnimationNodeStateMachine *get_prev_state_machine() const; void set_graph_offset(const Vector2 &p_offset); Vector2 get_graph_offset() const; - virtual double process(double p_time, bool p_seek) override; + virtual double process(double p_time, bool p_seek, bool p_seek_root) override; virtual String get_caption() const override; virtual Ref<AnimationNode> get_child_by_name(const StringName &p_name) override; diff --git a/scene/animation/animation_player.cpp b/scene/animation/animation_player.cpp index 6949e3681c..2e87dbf9da 100644 --- a/scene/animation/animation_player.cpp +++ b/scene/animation/animation_player.cpp @@ -163,7 +163,7 @@ bool AnimationPlayer::_get(const StringName &p_name, Variant &r_ret) const { for (int i = 0; i < keys.size(); i++) { array.push_back(keys[i].from); array.push_back(keys[i].to); - array.push_back(blend_times[keys[i]]); + array.push_back(blend_times.get(keys[i])); } r_ret = array; @@ -284,7 +284,12 @@ void AnimationPlayer::_ensure_node_caches(AnimationData *p_anim, Node *p_root_ov for (int i = 0; i < a->get_track_count(); i++) { p_anim->node_cache.write[i] = nullptr; - RES resource; + + if (!a->track_is_enabled(i)) { + continue; + } + + Ref<Resource> resource; Vector<StringName> leftover_path; Node *child = parent->get_node_and_resource(a->track_get_path(i), resource, leftover_path); ERR_CONTINUE_MSG(!child, "On Animation: '" + p_anim->name + "', couldn't resolve track: '" + String(a->track_get_path(i)) + "'."); // couldn't find the child node @@ -588,10 +593,10 @@ void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, double //StringName property=a->track_get_path(i).get_property(); - Map<StringName, TrackNodeCache::PropertyAnim>::Element *E = nc->property_anim.find(a->track_get_path(i).get_concatenated_subnames()); + HashMap<StringName, TrackNodeCache::PropertyAnim>::Iterator E = nc->property_anim.find(a->track_get_path(i).get_concatenated_subnames()); ERR_CONTINUE(!E); //should it continue, or create a new one? - TrackNodeCache::PropertyAnim *pa = &E->get(); + TrackNodeCache::PropertyAnim *pa = &E->value; Animation::UpdateMode update_mode = a->value_track_get_update_mode(i); @@ -738,10 +743,10 @@ void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, double continue; } - Map<StringName, TrackNodeCache::BezierAnim>::Element *E = nc->bezier_anim.find(a->track_get_path(i).get_concatenated_subnames()); + HashMap<StringName, TrackNodeCache::BezierAnim>::Iterator E = nc->bezier_anim.find(a->track_get_path(i).get_concatenated_subnames()); ERR_CONTINUE(!E); //should it continue, or create a new one? - TrackNodeCache::BezierAnim *ba = &E->get(); + TrackNodeCache::BezierAnim *ba = &E->value; real_t bezier = a->bezier_track_interpolate(i, p_time); if (ba->accum_pass != accum_pass) { @@ -832,7 +837,7 @@ void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, double nc->audio_start = p_time; } } else if (nc->audio_playing) { - bool loop = a->get_loop_mode() != Animation::LoopMode::LOOP_NONE; + bool loop = a->get_loop_mode() != Animation::LOOP_NONE; bool stop = false; @@ -883,15 +888,15 @@ void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, double double at_anim_pos = 0.0; switch (anim->get_loop_mode()) { - case Animation::LoopMode::LOOP_NONE: { + case Animation::LOOP_NONE: { at_anim_pos = MIN((double)anim->get_length(), p_time - pos); //seek to end } break; - case Animation::LoopMode::LOOP_LINEAR: { + case Animation::LOOP_LINEAR: { at_anim_pos = Math::fposmod(p_time - pos, (double)anim->get_length()); //seek to loop } break; - case Animation::LoopMode::LOOP_PINGPONG: { + case Animation::LOOP_PINGPONG: { at_anim_pos = Math::pingpong(p_time - pos, (double)anim->get_length()); } break; @@ -944,7 +949,7 @@ void AnimationPlayer::_animation_process_data(PlaybackData &cd, double p_delta, int pingponged = 0; switch (cd.from->animation->get_loop_mode()) { - case Animation::LoopMode::LOOP_NONE: { + case Animation::LOOP_NONE: { if (next_pos < 0) { next_pos = 0; } else if (next_pos > len) { @@ -969,7 +974,7 @@ void AnimationPlayer::_animation_process_data(PlaybackData &cd, double p_delta, } } break; - case Animation::LoopMode::LOOP_LINEAR: { + case Animation::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 @@ -980,7 +985,7 @@ void AnimationPlayer::_animation_process_data(PlaybackData &cd, double p_delta, } } break; - case Animation::LoopMode::LOOP_PINGPONG: { + case Animation::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; @@ -1240,8 +1245,6 @@ void AnimationPlayer::_animation_set_cache_update() { void AnimationPlayer::_animation_added(const StringName &p_name, const StringName &p_library) { _animation_set_cache_update(); - - update_configuration_warnings(); } void AnimationPlayer::_animation_removed(const StringName &p_name, const StringName &p_library) { @@ -1265,14 +1268,12 @@ void AnimationPlayer::_animation_removed(const StringName &p_name, const StringN blend_times.erase(to_erase.front()->get()); to_erase.pop_front(); } - - update_configuration_warnings(); } void AnimationPlayer::_rename_animation(const StringName &p_from_name, const StringName &p_to_name) { // Rename autoplay or blends if needed. List<BlendKey> to_erase; - Map<BlendKey, float> to_insert; + HashMap<BlendKey, float, BlendKey> to_insert; for (const KeyValue<BlendKey, float> &E : blend_times) { BlendKey bk = E.key; BlendKey new_bk = bk; @@ -1298,8 +1299,8 @@ void AnimationPlayer::_rename_animation(const StringName &p_from_name, const Str } while (to_insert.size()) { - blend_times[to_insert.front()->key()] = to_insert.front()->get(); - to_insert.erase(to_insert.front()); + blend_times[to_insert.begin()->key] = to_insert.begin()->value; + to_insert.remove(to_insert.begin()); } if (autoplay == p_from_name) { @@ -1317,7 +1318,6 @@ void AnimationPlayer::_animation_renamed(const StringName &p_name, const StringN _animation_set_cache_update(); _rename_animation(from_name, to_name); - update_configuration_warnings(); } Error AnimationPlayer::add_animation_library(const StringName &p_name, const Ref<AnimationLibrary> &p_animation_library) { @@ -1353,7 +1353,6 @@ Error AnimationPlayer::add_animation_library(const StringName &p_name, const Ref notify_property_list_changed(); - update_configuration_warnings(); return OK; } @@ -1383,7 +1382,6 @@ void AnimationPlayer::remove_animation_library(const StringName &p_name) { _animation_set_cache_update(); notify_property_list_changed(); - update_configuration_warnings(); } void AnimationPlayer::_ref_anim(const Ref<Animation> &p_anim) { @@ -1469,25 +1467,12 @@ void AnimationPlayer::get_animation_library_list(List<StringName> *p_libraries) } } -TypedArray<String> AnimationPlayer::get_configuration_warnings() const { - TypedArray<String> warnings = Node::get_configuration_warnings(); - - for (uint32_t i = 0; i < animation_libraries.size(); i++) { - for (const KeyValue<StringName, Ref<Animation>> &K : animation_libraries[i].library->animations) { - if (animation_set.has(K.key) && animation_set[K.key].animation_library != animation_libraries[i].name) { - warnings.push_back(vformat(RTR("Animation '%s' in library '%s' is unused because another animation with the same name exists in library '%s'."), K.key, animation_libraries[i].name, animation_set[K.key].animation_library)); - } - } - } - return warnings; -} - bool AnimationPlayer::has_animation(const StringName &p_name) const { return animation_set.has(p_name); } Ref<Animation> AnimationPlayer::get_animation(const StringName &p_name) const { - ERR_FAIL_COND_V(!animation_set.has(p_name), Ref<Animation>()); + ERR_FAIL_COND_V_MSG(!animation_set.has(p_name), Ref<Animation>(), vformat("Animation not found: %s.", p_name)); const AnimationData &data = animation_set[p_name]; @@ -1509,8 +1494,8 @@ void AnimationPlayer::get_animation_list(List<StringName> *p_animations) const { } void AnimationPlayer::set_blend_time(const StringName &p_animation1, const StringName &p_animation2, float p_time) { - ERR_FAIL_COND(!animation_set.has(p_animation1)); - ERR_FAIL_COND(!animation_set.has(p_animation2)); + ERR_FAIL_COND_MSG(!animation_set.has(p_animation1), vformat("Animation not found: %s.", p_animation1)); + ERR_FAIL_COND_MSG(!animation_set.has(p_animation2), vformat("Animation not found: %s.", p_animation2)); ERR_FAIL_COND_MSG(p_time < 0, "Blend time cannot be smaller than 0."); BlendKey bk; @@ -1567,7 +1552,7 @@ void AnimationPlayer::play(const StringName &p_name, float p_custom_blend, float name = playback.assigned; } - ERR_FAIL_COND_MSG(!animation_set.has(name), "Animation not found: " + name + "."); + ERR_FAIL_COND_MSG(!animation_set.has(name), vformat("Animation not found: %s.", name)); Playback &c = playback; @@ -1670,7 +1655,7 @@ void AnimationPlayer::set_assigned_animation(const String &p_anim) { if (is_playing()) { play(p_anim); } else { - ERR_FAIL_COND(!animation_set.has(p_anim)); + ERR_FAIL_COND_MSG(!animation_set.has(p_anim), vformat("Animation not found: %s.", p_anim)); playback.current.pos = 0; playback.current.from = &animation_set[p_anim]; playback.assigned = p_anim; @@ -1713,7 +1698,7 @@ float AnimationPlayer::get_playing_speed() const { void AnimationPlayer::seek(double p_time, bool p_update) { if (!playback.current.from) { if (playback.assigned) { - ERR_FAIL_COND(!animation_set.has(playback.assigned)); + ERR_FAIL_COND_MSG(!animation_set.has(playback.assigned), vformat("Animation not found: %s.", playback.assigned)); playback.current.from = &animation_set[playback.assigned]; } ERR_FAIL_COND(!playback.current.from); @@ -1729,7 +1714,7 @@ void AnimationPlayer::seek(double p_time, bool p_update) { void AnimationPlayer::seek_delta(double p_time, float p_delta) { if (!playback.current.from) { if (playback.assigned) { - ERR_FAIL_COND(!animation_set.has(playback.assigned)); + ERR_FAIL_COND_MSG(!animation_set.has(playback.assigned), vformat("Animation not found: %s.", playback.assigned)); playback.current.from = &animation_set[playback.assigned]; } ERR_FAIL_COND(!playback.current.from); @@ -1766,12 +1751,12 @@ void AnimationPlayer::_animation_changed() { } void AnimationPlayer::_stop_playing_caches() { - for (Set<TrackNodeCache *>::Element *E = playing_caches.front(); E; E = E->next()) { - if (E->get()->node && E->get()->audio_playing) { - E->get()->node->call(SNAME("stop")); + for (TrackNodeCache *E : playing_caches) { + if (E->node && E->audio_playing) { + E->node->call(SNAME("stop")); } - if (E->get()->node && E->get()->animation_playing) { - AnimationPlayer *player = Object::cast_to<AnimationPlayer>(E->get()->node); + if (E->node && E->animation_playing) { + AnimationPlayer *player = Object::cast_to<AnimationPlayer>(E->node); if (!player) { continue; } @@ -1899,7 +1884,7 @@ void AnimationPlayer::_set_process(bool p_process, bool p_force) { } void AnimationPlayer::animation_set_next(const StringName &p_animation, const StringName &p_next) { - ERR_FAIL_COND(!animation_set.has(p_animation)); + ERR_FAIL_COND_MSG(!animation_set.has(p_animation), vformat("Animation not found: %s.", p_animation)); animation_set[p_animation].next = p_next; } @@ -1929,7 +1914,7 @@ NodePath AnimationPlayer::get_root() const { void AnimationPlayer::get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const { String pf = p_function; - if (p_idx == 0 && (p_function == "play" || p_function == "play_backwards" || p_function == "remove_animation" || p_function == "has_animation" || p_function == "queue")) { + if (p_idx == 0 && (p_function == "play" || p_function == "play_backwards" || p_function == "has_animation" || p_function == "queue")) { List<StringName> al; get_animation_list(&al); for (const StringName &name : al) { @@ -2011,7 +1996,7 @@ Ref<AnimatedValuesBackup> AnimationPlayer::apply_reset(bool p_user_initiated) { Ref<AnimationLibrary> al; al.instantiate(); al->add_animation(SceneStringNames::get_singleton()->RESET, reset_anim); - aux_player->add_animation_library("default", al); + aux_player->add_animation_library("", al); aux_player->set_assigned_animation(SceneStringNames::get_singleton()->RESET); // Forcing the use of the original root because the scene where original player belongs may be not the active one Node *root = get_node(get_root()); @@ -2115,7 +2100,7 @@ void AnimationPlayer::_bind_methods() { ADD_GROUP("Playback Options", "playback_"); ADD_PROPERTY(PropertyInfo(Variant::INT, "playback_process_mode", PROPERTY_HINT_ENUM, "Physics,Idle,Manual"), "set_process_callback", "get_process_callback"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "playback_default_blend_time", PROPERTY_HINT_RANGE, "0,4096,0.01"), "set_default_blend_time", "get_default_blend_time"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "playback_default_blend_time", PROPERTY_HINT_RANGE, "0,4096,0.01,suffix:s"), "set_default_blend_time", "get_default_blend_time"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "playback_active", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_active", "is_active"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "playback_speed", PROPERTY_HINT_RANGE, "-64,64,0.01"), "set_speed_scale", "get_speed_scale"); ADD_PROPERTY(PropertyInfo(Variant::INT, "method_call_mode", PROPERTY_HINT_ENUM, "Deferred,Immediate"), "set_method_call_mode", "get_method_call_mode"); diff --git a/scene/animation/animation_player.h b/scene/animation/animation_player.h index 1d450175ad..d3eb37a345 100644 --- a/scene/animation/animation_player.h +++ b/scene/animation/animation_player.h @@ -94,7 +94,7 @@ private: struct TrackNodeCache { NodePath path; uint32_t id = 0; - RES resource; + Ref<Resource> resource; Node *node = nullptr; Node2D *node_2d = nullptr; #ifndef _3D_DISABLED @@ -132,7 +132,7 @@ private: Variant capture; }; - Map<StringName, PropertyAnim> property_anim; + HashMap<StringName, PropertyAnim> property_anim; struct BezierAnim { Vector<StringName> bezier_property; @@ -142,7 +142,7 @@ private: uint64_t accum_pass = 0; }; - Map<StringName, BezierAnim> bezier_anim; + HashMap<StringName, BezierAnim> bezier_anim; uint32_t last_setup_pass = 0; TrackNodeCache() {} @@ -153,6 +153,16 @@ private: int bone_idx = -1; int blend_shape_idx = -1; + static uint32_t hash(const TrackNodeCacheKey &p_key) { + uint32_t h = hash_one_uint64(p_key.id); + h = hash_murmur3_one_32(p_key.bone_idx, h); + return hash_fmix32(hash_murmur3_one_32(p_key.blend_shape_idx, h)); + } + + inline bool operator==(const TrackNodeCacheKey &p_right) const { + return id == p_right.id && bone_idx == p_right.bone_idx && blend_shape_idx == p_right.blend_shape_idx; + } + inline bool operator<(const TrackNodeCacheKey &p_right) const { if (id == p_right.id) { if (blend_shape_idx == p_right.blend_shape_idx) { @@ -166,7 +176,7 @@ private: } }; - Map<TrackNodeCacheKey, TrackNodeCache> node_cache_map; + HashMap<TrackNodeCacheKey, TrackNodeCache, TrackNodeCacheKey> node_cache_map; TrackNodeCache *cache_update[NODE_CACHE_UPDATE_MAX]; int cache_update_size = 0; @@ -174,7 +184,7 @@ private: int cache_update_prop_size = 0; TrackNodeCache::BezierAnim *cache_update_bezier[NODE_CACHE_UPDATE_MAX]; int cache_update_bezier_size = 0; - Set<TrackNodeCache *> playing_caches; + HashSet<TrackNodeCache *> playing_caches; uint64_t accum_pass = 1; float speed_scale = 1.0; @@ -189,7 +199,7 @@ private: uint64_t last_update = 0; }; - Map<StringName, AnimationData> animation_set; + HashMap<StringName, AnimationData> animation_set; struct AnimationLibraryData { StringName name; @@ -202,10 +212,22 @@ private: struct BlendKey { StringName from; StringName to; - bool operator<(const BlendKey &bk) const { return from == bk.from ? String(to) < String(bk.to) : String(from) < String(bk.from); } + static uint32_t hash(const BlendKey &p_key) { + return hash_one_uint64((uint64_t(p_key.from.hash()) << 32) | uint32_t(p_key.to.hash())); + } + bool operator==(const BlendKey &bk) const { + return from == bk.from && to == bk.to; + } + bool operator<(const BlendKey &bk) const { + if (from == bk.from) { + return to < bk.to; + } else { + return from < bk.from; + } + } }; - Map<BlendKey, float> blend_times; + HashMap<BlendKey, float, BlendKey> blend_times; struct PlaybackData { AnimationData *from = nullptr; @@ -366,8 +388,6 @@ public: bool can_apply_reset() const; #endif - TypedArray<String> get_configuration_warnings() const override; - AnimationPlayer(); ~AnimationPlayer(); }; diff --git a/scene/animation/animation_tree.cpp b/scene/animation/animation_tree.cpp index 572e767f44..4b80f571ae 100644 --- a/scene/animation/animation_tree.cpp +++ b/scene/animation/animation_tree.cpp @@ -88,7 +88,7 @@ void AnimationNode::get_child_nodes(List<ChildNode> *r_child_nodes) { } } -void AnimationNode::blend_animation(const StringName &p_animation, double p_time, double p_delta, bool p_seeked, real_t p_blend, int p_pingponged) { +void AnimationNode::blend_animation(const StringName &p_animation, double p_time, double p_delta, bool p_seeked, bool p_seek_root, real_t p_blend, int p_pingponged) { ERR_FAIL_COND(!state); ERR_FAIL_COND(!state->player->has_animation(p_animation)); @@ -115,17 +115,18 @@ void AnimationNode::blend_animation(const StringName &p_animation, double p_time anim_state.animation = animation; anim_state.seeked = p_seeked; anim_state.pingponged = p_pingponged; + anim_state.seek_root = p_seek_root; state->animation_states.push_back(anim_state); } -double AnimationNode::_pre_process(const StringName &p_base_path, AnimationNode *p_parent, State *p_state, double p_time, bool p_seek, const Vector<StringName> &p_connections) { +double AnimationNode::_pre_process(const StringName &p_base_path, AnimationNode *p_parent, State *p_state, double p_time, bool p_seek, bool p_seek_root, const Vector<StringName> &p_connections) { base_path = p_base_path; parent = p_parent; connections = p_connections; state = p_state; - double t = process(p_time, p_seek); + double t = process(p_time, p_seek, p_seek_root); state = nullptr; parent = nullptr; @@ -135,6 +136,11 @@ double AnimationNode::_pre_process(const StringName &p_base_path, AnimationNode return t; } +AnimationTree *AnimationNode::get_animation_tree() const { + ERR_FAIL_COND_V(!state, nullptr); + return state->tree; +} + void AnimationNode::make_invalid(const String &p_reason) { ERR_FAIL_COND(!state); state->valid = false; @@ -144,7 +150,7 @@ void AnimationNode::make_invalid(const String &p_reason) { state->invalid_reasons += String::utf8("• ") + p_reason; } -double AnimationNode::blend_input(int p_input, double p_time, bool p_seek, real_t p_blend, FilterAction p_filter, bool p_optimize) { +double AnimationNode::blend_input(int p_input, double p_time, bool p_seek, bool p_seek_root, real_t p_blend, FilterAction p_filter, bool p_sync) { ERR_FAIL_INDEX_V(p_input, inputs.size(), 0); ERR_FAIL_COND_V(!state, 0); @@ -163,7 +169,7 @@ double AnimationNode::blend_input(int p_input, double p_time, bool p_seek, real_ //inputs.write[p_input].last_pass = state->last_pass; real_t activity = 0.0; - double ret = _blend_node(node_name, blend_tree->get_node_connection_array(node_name), nullptr, node, p_time, p_seek, p_blend, p_filter, p_optimize, &activity); + double ret = _blend_node(node_name, blend_tree->get_node_connection_array(node_name), nullptr, node, p_time, p_seek, p_seek_root, p_blend, p_filter, p_sync, &activity); Vector<AnimationTree::Activity> *activity_ptr = state->tree->input_activity_map.getptr(base_path); @@ -174,11 +180,11 @@ double AnimationNode::blend_input(int p_input, double p_time, bool p_seek, real_ return ret; } -double AnimationNode::blend_node(const StringName &p_sub_path, Ref<AnimationNode> p_node, double p_time, bool p_seek, real_t p_blend, FilterAction p_filter, bool p_optimize) { - return _blend_node(p_sub_path, Vector<StringName>(), this, p_node, p_time, p_seek, p_blend, p_filter, p_optimize); +double AnimationNode::blend_node(const StringName &p_sub_path, Ref<AnimationNode> p_node, double p_time, bool p_seek, bool p_seek_root, real_t p_blend, FilterAction p_filter, bool p_sync) { + return _blend_node(p_sub_path, Vector<StringName>(), this, p_node, p_time, p_seek, p_seek_root, p_blend, p_filter, p_sync); } -double AnimationNode::_blend_node(const StringName &p_subpath, const Vector<StringName> &p_connections, AnimationNode *p_new_parent, Ref<AnimationNode> p_node, double p_time, bool p_seek, real_t p_blend, FilterAction p_filter, bool p_optimize, real_t *r_max) { +double AnimationNode::_blend_node(const StringName &p_subpath, const Vector<StringName> &p_connections, AnimationNode *p_new_parent, Ref<AnimationNode> p_node, double p_time, bool p_seek, bool p_seek_root, real_t p_blend, FilterAction p_filter, bool p_sync, real_t *r_max) { ERR_FAIL_COND_V(!p_node.is_valid(), 0); ERR_FAIL_COND_V(!state, 0); @@ -198,12 +204,11 @@ double AnimationNode::_blend_node(const StringName &p_subpath, const Vector<Stri blendw[i] = 0.0; //all to zero by default } - const NodePath *K = nullptr; - while ((K = filter.next(K))) { - if (!state->track_map.has(*K)) { + for (const KeyValue<NodePath, bool> &E : filter) { + if (!state->track_map.has(E.key)) { continue; } - int idx = state->track_map[*K]; + int idx = state->track_map[E.key]; blendw[idx] = 1.0; //filtered goes to one } @@ -276,7 +281,7 @@ double AnimationNode::_blend_node(const StringName &p_subpath, const Vector<Stri String new_path; AnimationNode *new_parent; - //this is the slowest part of processing, but as strings process in powers of 2, and the paths always exist, it will not result in that many allocations + // This is the slowest part of processing, but as strings process in powers of 2, and the paths always exist, it will not result in that many allocations. if (p_new_parent) { new_parent = p_new_parent; new_path = String(base_path) + String(p_subpath) + "/"; @@ -286,10 +291,15 @@ double AnimationNode::_blend_node(const StringName &p_subpath, const Vector<Stri new_path = String(parent->base_path) + String(p_subpath) + "/"; } - if (!p_seek && p_optimize && !any_valid) { - return p_node->_pre_process(new_path, new_parent, state, 0, p_seek, p_connections); + // If tracks for blending don't exist for one of the animations, Rest or RESET animation is blended as init animation instead. + // Then blend weight is 0 means that the init animation blend weight is 1. + // In that case, processing only the animation with the lacking track will not process the lacking track, and will not properly apply the Reset value. + // This means that all tracks which the animations in the branch that may be blended have must be processed. + // Therefore, the blending process must be executed even if the blend weight is 0. + if (!p_seek && !p_sync && !any_valid) { + return p_node->_pre_process(new_path, new_parent, state, 0, p_seek, p_seek_root, p_connections); } - return p_node->_pre_process(new_path, new_parent, state, p_time, p_seek, p_connections); + return p_node->_pre_process(new_path, new_parent, state, p_time, p_seek, p_seek_root, p_connections); } int AnimationNode::get_input_count() const { @@ -333,9 +343,9 @@ void AnimationNode::remove_input(int p_index) { emit_changed(); } -double AnimationNode::process(double p_time, bool p_seek) { +double AnimationNode::process(double p_time, bool p_seek, bool p_seek_root) { double ret; - if (GDVIRTUAL_CALL(_process, p_time, p_seek, ret)) { + if (GDVIRTUAL_CALL(_process, p_time, p_seek, p_seek_root, ret)) { return ret; } @@ -374,9 +384,8 @@ bool AnimationNode::has_filter() const { Array AnimationNode::_get_filters() const { Array paths; - const NodePath *K = nullptr; - while ((K = filter.next(K))) { - paths.push_back(String(*K)); //use strings, so sorting is possible + for (const KeyValue<NodePath, bool> &E : filter) { + paths.push_back(String(E.key)); //use strings, so sorting is possible } paths.sort(); //done so every time the scene is saved, it does not change @@ -420,9 +429,9 @@ 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", "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)); + ClassDB::bind_method(D_METHOD("blend_animation", "animation", "time", "delta", "seeked", "seek_root", "blend", "pingponged"), &AnimationNode::blend_animation, DEFVAL(0)); + ClassDB::bind_method(D_METHOD("blend_node", "name", "node", "time", "seek", "seek_root", "blend", "filter", "sync"), &AnimationNode::blend_node, DEFVAL(FILTER_IGNORE), DEFVAL(true)); + ClassDB::bind_method(D_METHOD("blend_input", "input_index", "time", "seek", "seek_root", "blend", "filter", "sync"), &AnimationNode::blend_input, DEFVAL(FILTER_IGNORE), DEFVAL(true)); ClassDB::bind_method(D_METHOD("set_parameter", "name", "value"), &AnimationNode::set_parameter); ClassDB::bind_method(D_METHOD("get_parameter", "name"), &AnimationNode::get_parameter); @@ -434,7 +443,7 @@ void AnimationNode::_bind_methods() { GDVIRTUAL_BIND(_get_parameter_list); GDVIRTUAL_BIND(_get_child_by_name, "name"); GDVIRTUAL_BIND(_get_parameter_default_value, "parameter"); - GDVIRTUAL_BIND(_process, "time", "seek"); + GDVIRTUAL_BIND(_process, "time", "seek", "seek_root"); GDVIRTUAL_BIND(_get_caption); GDVIRTUAL_BIND(_has_filter); @@ -488,9 +497,9 @@ void AnimationTree::set_active(bool p_active) { } if (!active && is_inside_tree()) { - for (Set<TrackCache *>::Element *E = playing_caches.front(); E; E = E->next()) { - if (ObjectDB::get_instance(E->get()->object_id)) { - E->get()->object->call(SNAME("stop")); + for (const TrackCache *E : playing_caches) { + if (ObjectDB::get_instance(E->object_id)) { + E->object->call(SNAME("stop")); } } @@ -541,9 +550,9 @@ bool AnimationTree::_update_caches(AnimationPlayer *player) { player->get_animation_list(&sname); Ref<Animation> reset_anim; - bool has_reset_anim = player->has_animation("RESET"); + bool has_reset_anim = player->has_animation(SceneStringNames::get_singleton()->RESET); if (has_reset_anim) { - reset_anim = player->get_animation("RESET"); + reset_anim = player->get_animation(SceneStringNames::get_singleton()->RESET); } for (const StringName &E : sname) { Ref<Animation> anim = player->get_animation(E); @@ -570,7 +579,7 @@ bool AnimationTree::_update_caches(AnimationPlayer *player) { } if (!track) { - RES resource; + Ref<Resource> resource; Vector<StringName> leftover_path; Node *child = parent->get_node_and_resource(path, resource, leftover_path); @@ -633,8 +642,7 @@ bool AnimationTree::_update_caches(AnimationPlayer *player) { track_xform->bone_idx = bone_idx; Transform3D rest = sk->get_bone_rest(bone_idx); track_xform->init_loc = rest.origin; - track_xform->ref_rot = rest.basis.get_rotation_quaternion(); - track_xform->init_rot = track_xform->ref_rot.log(); + track_xform->init_rot = rest.basis.get_rotation_quaternion(); track_xform->init_scale = rest.basis.get_scale(); } } @@ -667,8 +675,7 @@ bool AnimationTree::_update_caches(AnimationPlayer *player) { track_xform->init_loc = reset_anim->track_get_key_value(rt, 0); } break; case Animation::TYPE_ROTATION_3D: { - track_xform->ref_rot = reset_anim->track_get_key_value(rt, 0); - track_xform->init_rot = track_xform->ref_rot.log(); + track_xform->init_rot = reset_anim->track_get_key_value(rt, 0); } break; case Animation::TYPE_SCALE_3D: { track_xform->init_scale = reset_anim->track_get_key_value(rt, 0); @@ -805,11 +812,10 @@ bool AnimationTree::_update_caches(AnimationPlayer *player) { List<NodePath> to_delete; - const NodePath *K = nullptr; - while ((K = track_cache.next(K))) { - TrackCache *tc = track_cache[*K]; + for (const KeyValue<NodePath, TrackCache *> &K : track_cache) { + TrackCache *tc = track_cache[K.key]; if (tc->setup_pass != setup_pass) { - to_delete.push_back(*K); + to_delete.push_back(K.key); } } @@ -822,10 +828,9 @@ bool AnimationTree::_update_caches(AnimationPlayer *player) { state.track_map.clear(); - K = nullptr; int idx = 0; - while ((K = track_cache.next(K))) { - state.track_map[*K] = idx; + for (const KeyValue<NodePath, TrackCache *> &K : track_cache) { + state.track_map[K.key] = idx; idx++; } @@ -837,9 +842,8 @@ bool AnimationTree::_update_caches(AnimationPlayer *player) { } void AnimationTree::_clear_caches() { - const NodePath *K = nullptr; - while ((K = track_cache.next(K))) { - memdelete(track_cache[*K]); + for (KeyValue<NodePath, TrackCache *> &K : track_cache) { + memdelete(K.value); } playing_caches.clear(); @@ -866,7 +870,6 @@ void AnimationTree::_process_graph(double p_delta) { _update_properties(); //if properties need updating, update them //check all tracks, see if they need modification - root_motion_transform = Transform3D(); if (!root.is_valid()) { @@ -926,7 +929,6 @@ void AnimationTree::_process_graph(double p_delta) { state.valid = true; state.invalid_reasons = ""; state.animation_states.clear(); //will need to be re-created - state.valid = true; state.player = player; state.last_pass = process_pass; state.tree = this; @@ -945,11 +947,11 @@ void AnimationTree::_process_graph(double p_delta) { { if (started) { //if started, seek - root->_pre_process(SceneStringNames::get_singleton()->parameters_base_path, nullptr, &state, 0, true, Vector<StringName>()); + root->_pre_process(SceneStringNames::get_singleton()->parameters_base_path, nullptr, &state, 0, true, false, Vector<StringName>()); started = false; } - root->_pre_process(SceneStringNames::get_singleton()->parameters_base_path, nullptr, &state, p_delta, false, Vector<StringName>()); + root->_pre_process(SceneStringNames::get_singleton()->parameters_base_path, nullptr, &state, p_delta, false, false, Vector<StringName>()); } if (!state.valid) { @@ -969,9 +971,14 @@ void AnimationTree::_process_graph(double p_delta) { int pingponged = as.pingponged; #ifndef _3D_DISABLED bool backward = signbit(delta); + bool calc_root = !seeked || as.seek_root; #endif // _3D_DISABLED for (int i = 0; i < a->get_track_count(); i++) { + if (!a->track_is_enabled(i)) { + continue; + } + NodePath path = a->track_get_path(i); ERR_CONTINUE(!track_cache.has(path)); @@ -997,11 +1004,11 @@ void AnimationTree::_process_graph(double p_delta) { case Animation::TYPE_POSITION_3D: { #ifndef _3D_DISABLED TrackCacheTransform *t = static_cast<TrackCacheTransform *>(track); - if (track->root_motion) { + if (track->root_motion && calc_root) { if (t->process_pass != process_pass) { t->process_pass = process_pass; t->loc = Vector3(0, 0, 0); - t->rot = Quaternion(0, 0, 0, 0); + t->rot = Quaternion(0, 0, 0, 1); t->scale = Vector3(0, 0, 0); } double prev_time = time - delta; @@ -1059,7 +1066,7 @@ void AnimationTree::_process_graph(double p_delta) { } a->position_track_interpolate(i, 0, &loc[1]); t->loc += (loc[1] - loc[0]) * blend; - prev_time = 0; + prev_time = (double)a->get_length(); } } @@ -1093,11 +1100,11 @@ void AnimationTree::_process_graph(double p_delta) { case Animation::TYPE_ROTATION_3D: { #ifndef _3D_DISABLED TrackCacheTransform *t = static_cast<TrackCacheTransform *>(track); - if (track->root_motion) { + if (track->root_motion && calc_root) { if (t->process_pass != process_pass) { t->process_pass = process_pass; t->loc = Vector3(0, 0, 0); - t->rot = Quaternion(0, 0, 0, 0); + t->rot = Quaternion(0, 0, 0, 1); t->scale = Vector3(0, 0, 0); } double prev_time = time - delta; @@ -1144,7 +1151,7 @@ void AnimationTree::_process_graph(double p_delta) { continue; } a->rotation_track_interpolate(i, (double)a->get_length(), &rot[1]); - t->rot += (rot[1].log() - rot[0].log()) * blend; + t->rot = (t->rot * Quaternion().slerp(rot[0].inverse() * rot[1], blend)).normalized(); prev_time = 0; } } else { @@ -1154,8 +1161,8 @@ void AnimationTree::_process_graph(double p_delta) { continue; } a->rotation_track_interpolate(i, 0, &rot[1]); - t->rot += (rot[1].log() - rot[0].log()) * blend; - prev_time = 0; + t->rot = (t->rot * Quaternion().slerp(rot[0].inverse() * rot[1], blend)).normalized(); + prev_time = (double)a->get_length(); } } @@ -1165,7 +1172,7 @@ void AnimationTree::_process_graph(double p_delta) { } a->rotation_track_interpolate(i, time, &rot[1]); - t->rot += (rot[1].log() - rot[0].log()) * blend; + t->rot = (t->rot * Quaternion().slerp(rot[0].inverse() * rot[1], blend)).normalized(); prev_time = !backward ? 0 : (double)a->get_length(); } else { @@ -1182,21 +1189,18 @@ void AnimationTree::_process_graph(double p_delta) { continue; } - if (signbit(rot.dot(t->ref_rot))) { - rot = -rot; - } - t->rot += (rot.log() - t->init_rot) * blend; + t->rot = (t->rot * Quaternion().slerp(t->init_rot.inverse() * rot, blend)).normalized(); } #endif // _3D_DISABLED } break; case Animation::TYPE_SCALE_3D: { #ifndef _3D_DISABLED TrackCacheTransform *t = static_cast<TrackCacheTransform *>(track); - if (track->root_motion) { + if (track->root_motion && calc_root) { if (t->process_pass != process_pass) { t->process_pass = process_pass; t->loc = Vector3(0, 0, 0); - t->rot = Quaternion(0, 0, 0, 0); + t->rot = Quaternion(0, 0, 0, 1); t->scale = Vector3(0, 0, 0); } double prev_time = time - delta; @@ -1254,7 +1258,7 @@ void AnimationTree::_process_graph(double p_delta) { } a->scale_track_interpolate(i, 0, &scale[1]); t->scale += (scale[1] - scale[0]) * blend; - prev_time = 0; + prev_time = (double)a->get_length(); } } @@ -1311,8 +1315,7 @@ void AnimationTree::_process_graph(double p_delta) { Animation::UpdateMode update_mode = a->value_track_get_update_mode(i); - if (update_mode == Animation::UPDATE_CONTINUOUS || update_mode == Animation::UPDATE_CAPTURE) { //delta == 0 means seek - + if (update_mode == Animation::UPDATE_CONTINUOUS || update_mode == Animation::UPDATE_CAPTURE) { Variant value = a->value_track_interpolate(i, time); if (value == Variant()) { @@ -1334,12 +1337,21 @@ void AnimationTree::_process_graph(double p_delta) { if (blend < CMP_EPSILON) { continue; //nothing to blend } - List<int> 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); + if (seeked) { + int idx = a->track_find_key(i, time); + if (idx < 0) { + continue; + } + Variant value = a->track_get_key_value(i, idx); t->object->set_indexed(t->subpath, value); + } else { + List<int> 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); + t->object->set_indexed(t->subpath, value); + } } } @@ -1348,20 +1360,27 @@ void AnimationTree::_process_graph(double p_delta) { if (blend < CMP_EPSILON) { continue; //nothing to blend } - if (!seeked && Math::is_zero_approx(delta)) { - continue; - } TrackCacheMethod *t = static_cast<TrackCacheMethod *>(track); - List<int> 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); - Vector<Variant> params = a->method_track_get_params(i, F); + if (seeked) { + int idx = a->track_find_key(i, time); + if (idx < 0) { + continue; + } + StringName method = a->method_track_get_name(i, idx); + Vector<Variant> params = a->method_track_get_params(i, idx); if (can_call) { - _call_object(t->object, method, params, true); + _call_object(t->object, method, params, false); + } + } else { + List<int> 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); + Vector<Variant> params = a->method_track_get_params(i, F); + if (can_call) { + _call_object(t->object, method, params, true); + } } } } break; @@ -1453,7 +1472,7 @@ void AnimationTree::_process_graph(double p_delta) { t->start = time; } } else if (t->playing) { - bool loop = a->get_loop_mode() != Animation::LoopMode::LOOP_NONE; + bool loop = a->get_loop_mode() != Animation::LOOP_NONE; bool stop = false; @@ -1522,13 +1541,13 @@ void AnimationTree::_process_graph(double p_delta) { double at_anim_pos = 0.0; switch (anim->get_loop_mode()) { - case Animation::LoopMode::LOOP_NONE: { + case Animation::LOOP_NONE: { at_anim_pos = MAX((double)anim->get_length(), time - pos); //seek to end } break; - case Animation::LoopMode::LOOP_LINEAR: { + case Animation::LOOP_LINEAR: { at_anim_pos = Math::fposmod(time - pos, (double)anim->get_length()); //seek to loop } break; - case Animation::LoopMode::LOOP_PINGPONG: { + case Animation::LOOP_PINGPONG: { at_anim_pos = Math::pingpong(time - pos, (double)a->get_length()); } break; default: @@ -1574,9 +1593,8 @@ void AnimationTree::_process_graph(double p_delta) { { // finally, set the tracks - const NodePath *K = nullptr; - while ((K = track_cache.next(K))) { - TrackCache *track = track_cache[*K]; + for (const KeyValue<NodePath, TrackCache *> &K : track_cache) { + TrackCache *track = K.value; if (track->process_pass != process_pass) { continue; //not processed, ignore } @@ -1585,7 +1603,6 @@ void AnimationTree::_process_graph(double p_delta) { case Animation::TYPE_POSITION_3D: { #ifndef _3D_DISABLED TrackCacheTransform *t = static_cast<TrackCacheTransform *>(track); - t->rot = t->rot.exp(); if (t->root_motion) { Transform3D xform; @@ -1694,6 +1711,14 @@ NodePath AnimationTree::get_animation_player() const { return animation_player; } +void AnimationTree::set_advance_expression_base_node(const NodePath &p_advance_expression_base_node) { + advance_expression_base_node = p_advance_expression_base_node; +} + +NodePath AnimationTree::get_advance_expression_base_node() const { + return advance_expression_base_node; +} + bool AnimationTree::is_state_invalid() const { return !state.valid; } @@ -1889,6 +1914,9 @@ void AnimationTree::_bind_methods() { ClassDB::bind_method(D_METHOD("set_animation_player", "root"), &AnimationTree::set_animation_player); ClassDB::bind_method(D_METHOD("get_animation_player"), &AnimationTree::get_animation_player); + ClassDB::bind_method(D_METHOD("set_advance_expression_base_node", "node"), &AnimationTree::set_advance_expression_base_node); + ClassDB::bind_method(D_METHOD("get_advance_expression_base_node"), &AnimationTree::get_advance_expression_base_node); + ClassDB::bind_method(D_METHOD("set_root_motion_track", "path"), &AnimationTree::set_root_motion_track); ClassDB::bind_method(D_METHOD("get_root_motion_track"), &AnimationTree::get_root_motion_track); @@ -1902,6 +1930,8 @@ void AnimationTree::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "tree_root", PROPERTY_HINT_RESOURCE_TYPE, "AnimationRootNode"), "set_tree_root", "get_tree_root"); ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "anim_player", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "AnimationPlayer"), "set_animation_player", "get_animation_player"); + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "advance_expression_base_node", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Node"), "set_advance_expression_base_node", "get_advance_expression_base_node"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "active"), "set_active", "is_active"); ADD_PROPERTY(PropertyInfo(Variant::INT, "process_callback", PROPERTY_HINT_ENUM, "Physics,Idle,Manual"), "set_process_callback", "get_process_callback"); ADD_GROUP("Root Motion", "root_motion_"); diff --git a/scene/animation/animation_tree.h b/scene/animation/animation_tree.h index 524f735894..4f9a330a89 100644 --- a/scene/animation/animation_tree.h +++ b/scene/animation/animation_tree.h @@ -37,6 +37,8 @@ #include "scene/resources/animation.h" class AnimationNodeBlendTree; +class AnimationNodeStartState; +class AnimationNodeEndState; class AnimationPlayer; class AnimationTree; @@ -66,6 +68,7 @@ public: const Vector<real_t> *track_blends = nullptr; real_t blend = 0.0; bool seeked = false; + bool seek_root = false; int pingponged = 0; }; @@ -83,7 +86,7 @@ public: Vector<real_t> blends; State *state = nullptr; - double _pre_process(const StringName &p_base_path, AnimationNode *p_parent, State *p_state, double p_time, bool p_seek, const Vector<StringName> &p_connections); + double _pre_process(const StringName &p_base_path, AnimationNode *p_parent, State *p_state, double p_time, bool p_seek, bool p_seek_root, const Vector<StringName> &p_connections); //all this is temporary StringName base_path; @@ -96,14 +99,15 @@ public: Array _get_filters() const; void _set_filters(const Array &p_filters); friend class AnimationNodeBlendTree; - double _blend_node(const StringName &p_subpath, const Vector<StringName> &p_connections, AnimationNode *p_new_parent, Ref<AnimationNode> p_node, double p_time, bool p_seek, real_t p_blend, FilterAction p_filter = FILTER_IGNORE, bool p_optimize = true, real_t *r_max = nullptr); + double _blend_node(const StringName &p_subpath, const Vector<StringName> &p_connections, AnimationNode *p_new_parent, Ref<AnimationNode> p_node, double p_time, bool p_seek, bool p_seek_root, real_t p_blend, FilterAction p_filter = FILTER_IGNORE, bool p_sync = true, real_t *r_max = nullptr); protected: - void blend_animation(const StringName &p_animation, double p_time, double p_delta, bool p_seeked, real_t p_blend, int p_pingponged = 0); - double blend_node(const StringName &p_sub_path, Ref<AnimationNode> p_node, double p_time, bool p_seek, real_t p_blend, FilterAction p_filter = FILTER_IGNORE, bool p_optimize = true); - double blend_input(int p_input, double p_time, bool p_seek, real_t p_blend, FilterAction p_filter = FILTER_IGNORE, bool p_optimize = true); + void blend_animation(const StringName &p_animation, double p_time, double p_delta, bool p_seeked, bool p_seek_root, real_t p_blend, int p_pingponged = 0); + double blend_node(const StringName &p_sub_path, Ref<AnimationNode> p_node, double p_time, bool p_seek, bool p_seek_root, real_t p_blend, FilterAction p_filter = FILTER_IGNORE, bool p_sync = true); + double blend_input(int p_input, double p_time, bool p_seek, bool p_seek_root, real_t p_blend, FilterAction p_filter = FILTER_IGNORE, bool p_sync = true); void make_invalid(const String &p_reason); + AnimationTree *get_animation_tree() const; static void _bind_methods(); @@ -113,7 +117,7 @@ protected: GDVIRTUAL0RC(Array, _get_parameter_list) GDVIRTUAL1RC(Ref<AnimationNode>, _get_child_by_name, StringName) GDVIRTUAL1RC(Variant, _get_parameter_default_value, StringName) - GDVIRTUAL2RC(double, _process, double, bool) + GDVIRTUAL3RC(double, _process, double, bool, bool) GDVIRTUAL0RC(String, _get_caption) GDVIRTUAL0RC(bool, _has_filter) @@ -131,7 +135,7 @@ public: virtual void get_child_nodes(List<ChildNode> *r_child_nodes); - virtual double process(double p_time, bool p_seek); + virtual double process(double p_time, bool p_seek, bool p_seek_root); virtual String get_caption() const; int get_input_count() const; @@ -164,6 +168,14 @@ public: AnimationRootNode() {} }; +class AnimationNodeStartState : public AnimationRootNode { + GDCLASS(AnimationNodeStartState, AnimationRootNode); +}; + +class AnimationNodeEndState : public AnimationRootNode { + GDCLASS(AnimationNodeEndState, AnimationRootNode); +}; + class AnimationTree : public Node { GDCLASS(AnimationTree, Node); @@ -198,8 +210,7 @@ private: bool rot_used = false; bool scale_used = false; Vector3 init_loc = Vector3(0, 0, 0); - Quaternion ref_rot = Quaternion(0, 0, 0, 1); - Quaternion init_rot = Quaternion(0, 0, 0, 0); + Quaternion init_rot = Quaternion(0, 0, 0, 1); Vector3 init_scale = Vector3(1, 1, 1); Vector3 loc; Quaternion rot; @@ -257,9 +268,10 @@ private: }; HashMap<NodePath, TrackCache *> track_cache; - Set<TrackCache *> playing_caches; + HashSet<TrackCache *> playing_caches; Ref<AnimationNode> root; + NodePath advance_expression_base_node = NodePath(String(".")); AnimationProcessCallback process_callback = ANIMATION_PROCESS_IDLE; bool active = false; @@ -322,6 +334,9 @@ public: void set_animation_player(const NodePath &p_player); NodePath get_animation_player() const; + void set_advance_expression_base_node(const NodePath &p_advance_expression_base_node); + NodePath get_advance_expression_base_node() const; + TypedArray<String> get_configuration_warnings() const override; bool is_state_invalid() const; diff --git a/scene/animation/root_motion_view.cpp b/scene/animation/root_motion_view.cpp index 42adc1ea02..47f08219a9 100644 --- a/scene/animation/root_motion_view.cpp +++ b/scene/animation/root_motion_view.cpp @@ -114,9 +114,8 @@ void RootMotionView::_notification(int p_what) { first = false; transform.orthonormalize(); //don't want scale, too imprecise - transform.affine_invert(); - accumulated = transform * accumulated; + accumulated = accumulated * transform; accumulated.origin.x = Math::fposmod(accumulated.origin.x, cell_size); if (zero_y) { accumulated.origin.y = 0; @@ -134,9 +133,9 @@ void RootMotionView::_notification(int p_what) { Vector3 from(i * cell_size, 0, j * cell_size); Vector3 from_i((i + 1) * cell_size, 0, j * cell_size); Vector3 from_j(i * cell_size, 0, (j + 1) * cell_size); - from = accumulated.xform(from); - from_i = accumulated.xform(from_i); - from_j = accumulated.xform(from_j); + from = accumulated.xform_inv(from); + from_i = accumulated.xform_inv(from_i); + from_j = accumulated.xform_inv(from_j); Color c = color, c_i = color, c_j = color; c.a *= MAX(0, 1.0 - from.length() / radius); @@ -184,8 +183,8 @@ void RootMotionView::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "animation_path", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "AnimationTree"), "set_animation_path", "get_animation_path"); ADD_PROPERTY(PropertyInfo(Variant::COLOR, "color"), "set_color", "get_color"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "cell_size", PROPERTY_HINT_RANGE, "0.1,16,0.01,or_greater"), "set_cell_size", "get_cell_size"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radius", PROPERTY_HINT_RANGE, "0.1,16,0.01,or_greater"), "set_radius", "get_radius"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "cell_size", PROPERTY_HINT_RANGE, "0.1,16,0.01,or_greater,suffix:m"), "set_cell_size", "get_cell_size"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radius", PROPERTY_HINT_RANGE, "0.1,16,0.01,or_greater,suffix:m"), "set_radius", "get_radius"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "zero_y"), "set_zero_y", "get_zero_y"); } diff --git a/scene/animation/tween.cpp b/scene/animation/tween.cpp index ccc878a6ec..5457da472f 100644 --- a/scene/animation/tween.cpp +++ b/scene/animation/tween.cpp @@ -152,10 +152,6 @@ bool Tween::is_running() { return running; } -void Tween::set_valid(bool p_valid) { - valid = p_valid; -} - bool Tween::is_valid() { return valid; } @@ -282,14 +278,15 @@ bool Tween::step(float p_delta) { bool step_active = false; total_time += rem_delta; +#ifdef DEBUG_ENABLED + float initial_delta = rem_delta; + bool potential_infinite = false; +#endif + while (rem_delta > 0 && running) { float step_delta = rem_delta; step_active = false; -#ifdef DEBUG_ENABLED - float prev_delta = rem_delta; -#endif - for (Ref<Tweener> &tweener : tweeners.write[current_step]) { // Modified inside Tweener.step(). float temp_delta = rem_delta; @@ -314,17 +311,21 @@ bool Tween::step(float p_delta) { emit_signal(SNAME("loop_finished"), loops_done); current_step = 0; start_tweeners(); +#ifdef DEBUG_ENABLED + if (loops <= 0 && Math::is_equal_approx(rem_delta, initial_delta)) { + if (!potential_infinite) { + potential_infinite = true; + } else { + // Looped twice without using any time, this is 100% certain infinite loop. + ERR_FAIL_V_MSG(false, "Infinite loop detected. Check set_loops() description for more info."); + } + } +#endif } } else { start_tweeners(); } } - -#ifdef DEBUG_ENABLED - if (Math::is_equal_approx(rem_delta, prev_delta) && running && loops <= 0) { - ERR_FAIL_V_MSG(false, "Infinite loop detected. Check set_loops() description for more info."); - } -#endif } return true; @@ -334,7 +335,7 @@ bool Tween::can_process(bool p_tree_paused) const { if (is_bound && pause_mode == TWEEN_PAUSE_BOUND) { Node *bound_node = get_bound_node(); if (bound_node) { - return bound_node->can_process(); + return bound_node->is_inside_tree() && bound_node->can_process(); } } @@ -455,12 +456,12 @@ Variant Tween::interpolate_variant(Variant p_initial_val, Variant p_delta_val, f Transform2D d = p_delta_val; Transform2D r; - APPLY_EQUATION(elements[0][0]); - APPLY_EQUATION(elements[0][1]); - APPLY_EQUATION(elements[1][0]); - APPLY_EQUATION(elements[1][1]); - APPLY_EQUATION(elements[2][0]); - APPLY_EQUATION(elements[2][1]); + APPLY_EQUATION(columns[0][0]); + APPLY_EQUATION(columns[0][1]); + APPLY_EQUATION(columns[1][0]); + APPLY_EQUATION(columns[1][1]); + APPLY_EQUATION(columns[2][0]); + APPLY_EQUATION(columns[2][1]); return r; } @@ -495,15 +496,15 @@ Variant Tween::interpolate_variant(Variant p_initial_val, Variant p_delta_val, f Basis d = p_delta_val; Basis r; - APPLY_EQUATION(elements[0][0]); - APPLY_EQUATION(elements[0][1]); - APPLY_EQUATION(elements[0][2]); - APPLY_EQUATION(elements[1][0]); - APPLY_EQUATION(elements[1][1]); - APPLY_EQUATION(elements[1][2]); - APPLY_EQUATION(elements[2][0]); - APPLY_EQUATION(elements[2][1]); - APPLY_EQUATION(elements[2][2]); + APPLY_EQUATION(rows[0][0]); + APPLY_EQUATION(rows[0][1]); + APPLY_EQUATION(rows[0][2]); + APPLY_EQUATION(rows[1][0]); + APPLY_EQUATION(rows[1][1]); + APPLY_EQUATION(rows[1][2]); + APPLY_EQUATION(rows[2][0]); + APPLY_EQUATION(rows[2][1]); + APPLY_EQUATION(rows[2][2]); return r; } @@ -512,15 +513,15 @@ Variant Tween::interpolate_variant(Variant p_initial_val, Variant p_delta_val, f Transform3D d = p_delta_val; Transform3D r; - APPLY_EQUATION(basis.elements[0][0]); - APPLY_EQUATION(basis.elements[0][1]); - APPLY_EQUATION(basis.elements[0][2]); - APPLY_EQUATION(basis.elements[1][0]); - APPLY_EQUATION(basis.elements[1][1]); - APPLY_EQUATION(basis.elements[1][2]); - APPLY_EQUATION(basis.elements[2][0]); - APPLY_EQUATION(basis.elements[2][1]); - APPLY_EQUATION(basis.elements[2][2]); + APPLY_EQUATION(basis.rows[0][0]); + APPLY_EQUATION(basis.rows[0][1]); + APPLY_EQUATION(basis.rows[0][2]); + APPLY_EQUATION(basis.rows[1][0]); + APPLY_EQUATION(basis.rows[1][1]); + APPLY_EQUATION(basis.rows[1][2]); + APPLY_EQUATION(basis.rows[2][0]); + APPLY_EQUATION(basis.rows[2][1]); + APPLY_EQUATION(basis.rows[2][2]); APPLY_EQUATION(origin.x); APPLY_EQUATION(origin.y); APPLY_EQUATION(origin.z); @@ -569,12 +570,12 @@ Variant Tween::calculate_delta_value(Variant p_intial_val, Variant p_final_val) case Variant::TRANSFORM2D: { Transform2D i = p_intial_val; Transform2D f = p_final_val; - return Transform2D(f.elements[0][0] - i.elements[0][0], - f.elements[0][1] - i.elements[0][1], - f.elements[1][0] - i.elements[1][0], - f.elements[1][1] - i.elements[1][1], - f.elements[2][0] - i.elements[2][0], - f.elements[2][1] - i.elements[2][1]); + return Transform2D(f.columns[0][0] - i.columns[0][0], + f.columns[0][1] - i.columns[0][1], + f.columns[1][0] - i.columns[1][0], + f.columns[1][1] - i.columns[1][1], + f.columns[2][0] - i.columns[2][0], + f.columns[2][1] - i.columns[2][1]); } case Variant::AABB: { @@ -586,29 +587,29 @@ Variant Tween::calculate_delta_value(Variant p_intial_val, Variant p_final_val) case Variant::BASIS: { Basis i = p_intial_val; Basis f = p_final_val; - return Basis(f.elements[0][0] - i.elements[0][0], - f.elements[0][1] - i.elements[0][1], - f.elements[0][2] - i.elements[0][2], - f.elements[1][0] - i.elements[1][0], - f.elements[1][1] - i.elements[1][1], - f.elements[1][2] - i.elements[1][2], - f.elements[2][0] - i.elements[2][0], - f.elements[2][1] - i.elements[2][1], - f.elements[2][2] - i.elements[2][2]); + return Basis(f.rows[0][0] - i.rows[0][0], + f.rows[0][1] - i.rows[0][1], + f.rows[0][2] - i.rows[0][2], + f.rows[1][0] - i.rows[1][0], + f.rows[1][1] - i.rows[1][1], + f.rows[1][2] - i.rows[1][2], + f.rows[2][0] - i.rows[2][0], + f.rows[2][1] - i.rows[2][1], + f.rows[2][2] - i.rows[2][2]); } case Variant::TRANSFORM3D: { Transform3D i = p_intial_val; Transform3D f = p_final_val; - return Transform3D(f.basis.elements[0][0] - i.basis.elements[0][0], - f.basis.elements[0][1] - i.basis.elements[0][1], - f.basis.elements[0][2] - i.basis.elements[0][2], - f.basis.elements[1][0] - i.basis.elements[1][0], - f.basis.elements[1][1] - i.basis.elements[1][1], - f.basis.elements[1][2] - i.basis.elements[1][2], - f.basis.elements[2][0] - i.basis.elements[2][0], - f.basis.elements[2][1] - i.basis.elements[2][1], - f.basis.elements[2][2] - i.basis.elements[2][2], + return Transform3D(f.basis.rows[0][0] - i.basis.rows[0][0], + f.basis.rows[0][1] - i.basis.rows[0][1], + f.basis.rows[0][2] - i.basis.rows[0][2], + f.basis.rows[1][0] - i.basis.rows[1][0], + f.basis.rows[1][1] - i.basis.rows[1][1], + f.basis.rows[1][2] - i.basis.rows[1][2], + f.basis.rows[2][0] - i.basis.rows[2][0], + f.basis.rows[2][1] - i.basis.rows[2][1], + f.basis.rows[2][2] - i.basis.rows[2][2], f.origin.x - i.origin.x, f.origin.y - i.origin.y, f.origin.z - i.origin.z); @@ -648,7 +649,7 @@ void Tween::_bind_methods() { ClassDB::bind_method(D_METHOD("parallel"), &Tween::parallel); ClassDB::bind_method(D_METHOD("chain"), &Tween::chain); - ClassDB::bind_method(D_METHOD("interpolate_value", "initial_value", "delta_value", "elapsed_time", "duration", "trans_type", "ease_type"), &Tween::interpolate_variant); + ClassDB::bind_static_method("Tween", D_METHOD("interpolate_value", "initial_value", "delta_value", "elapsed_time", "duration", "trans_type", "ease_type"), &Tween::interpolate_variant); ADD_SIGNAL(MethodInfo("step_finished", PropertyInfo(Variant::INT, "idx"))); ADD_SIGNAL(MethodInfo("loop_finished", PropertyInfo(Variant::INT, "loop_count"))); @@ -679,6 +680,14 @@ void Tween::_bind_methods() { BIND_ENUM_CONSTANT(EASE_OUT_IN); } +Tween::Tween() { + ERR_FAIL_MSG("Tween can't be created directly. Use create_tween() method."); +} + +Tween::Tween(bool p_valid) { + valid = p_valid; +} + Ref<PropertyTweener> PropertyTweener::from(Variant p_value) { initial_val = p_value; do_continue = false; @@ -846,7 +855,7 @@ bool CallbackTweener::step(float &r_delta) { Callable::CallError ce; callback.call(nullptr, 0, result, ce); if (ce.error != Callable::CallError::CALL_OK) { - ERR_FAIL_V_MSG(false, "Error calling method from CallbackTweener: " + Variant::get_call_error_text(this, callback.get_method(), nullptr, 0, ce)); + ERR_FAIL_V_MSG(false, "Error calling method from CallbackTweener: " + Variant::get_callable_error_text(callback, nullptr, 0, ce)); } finished = true; @@ -917,7 +926,7 @@ bool MethodTweener::step(float &r_delta) { Callable::CallError ce; callback.call(argptr, 1, result, ce); if (ce.error != Callable::CallError::CALL_OK) { - ERR_FAIL_V_MSG(false, "Error calling method from MethodTweener: " + Variant::get_call_error_text(this, callback.get_method(), argptr, 1, ce)); + ERR_FAIL_V_MSG(false, "Error calling method from MethodTweener: " + Variant::get_callable_error_text(callback, argptr, 1, ce)); } if (time < duration) { diff --git a/scene/animation/tween.h b/scene/animation/tween.h index e28a499259..40268405cf 100644 --- a/scene/animation/tween.h +++ b/scene/animation/tween.h @@ -116,6 +116,9 @@ private: bool valid = false; bool default_parallel = false; bool parallel_enabled = false; +#ifdef DEBUG_ENABLED + bool is_infinite = false; +#endif typedef real_t (*interpolater)(real_t t, real_t b, real_t c, real_t d); static interpolater interpolaters[TRANS_MAX][EASE_MAX]; @@ -139,7 +142,6 @@ public: void kill(); bool is_running(); - void set_valid(bool p_valid); bool is_valid(); void clear(); @@ -160,8 +162,8 @@ public: Ref<Tween> parallel(); Ref<Tween> chain(); - real_t run_equation(TransitionType p_trans_type, EaseType p_ease_type, real_t t, real_t b, real_t c, real_t d); - Variant interpolate_variant(Variant p_initial_val, Variant p_delta_val, float p_time, float p_duration, Tween::TransitionType p_trans, Tween::EaseType p_ease); + static real_t run_equation(TransitionType p_trans_type, EaseType p_ease_type, real_t t, real_t b, real_t c, real_t d); + static Variant interpolate_variant(Variant p_initial_val, Variant p_delta_val, float p_time, float p_duration, Tween::TransitionType p_trans, Tween::EaseType p_ease); Variant calculate_delta_value(Variant p_intial_val, Variant p_final_val); bool step(float p_delta); @@ -169,7 +171,8 @@ public: Node *get_bound_node() const; float get_total_time() const; - Tween() {} + Tween(); + Tween(bool p_valid); }; VARIANT_ENUM_CAST(Tween::TweenPauseMode); diff --git a/scene/audio/audio_stream_player.cpp b/scene/audio/audio_stream_player.cpp index c8e8ff1cd1..efb647af29 100644 --- a/scene/audio/audio_stream_player.cpp +++ b/scene/audio/audio_stream_player.cpp @@ -349,7 +349,7 @@ void AudioStreamPlayer::_bind_methods() { ClassDB::bind_method(D_METHOD("get_stream_playback"), &AudioStreamPlayer::get_stream_playback); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "stream", PROPERTY_HINT_RESOURCE_TYPE, "AudioStream"), "set_stream", "get_stream"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "volume_db", PROPERTY_HINT_RANGE, "-80,24"), "set_volume_db", "get_volume_db"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "volume_db", PROPERTY_HINT_RANGE, "-80,24,suffix:dB"), "set_volume_db", "get_volume_db"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "pitch_scale", PROPERTY_HINT_RANGE, "0.01,4,0.01,or_greater"), "set_pitch_scale", "get_pitch_scale"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "playing", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "_set_playing", "is_playing"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "autoplay"), "set_autoplay", "is_autoplay_enabled"); diff --git a/scene/debugger/scene_debugger.cpp b/scene/debugger/scene_debugger.cpp index ac0d017a23..e9c33b1839 100644 --- a/scene/debugger/scene_debugger.cpp +++ b/scene/debugger/scene_debugger.cpp @@ -68,7 +68,7 @@ bool SceneDebugger::RPCProfilerFrame::deserialize(const Array &p_arr) { } class SceneDebugger::RPCProfiler : public EngineProfiler { - Map<ObjectID, RPCNodeInfo> rpc_node_data; + HashMap<ObjectID, RPCNodeInfo> rpc_node_data; uint64_t last_profile_time = 0; void init_node(const ObjectID p_node) { @@ -100,7 +100,7 @@ public: } } - void tick(double p_frame_time, double p_idle_time, double p_physics_time, double p_physics_frame_time) { + void tick(double p_frame_time, double p_process_time, double p_physics_time, double p_physics_frame_time) { uint64_t pt = OS::get_singleton()->get_ticks_msec(); if (pt - last_profile_time > 100) { last_profile_time = pt; @@ -345,22 +345,22 @@ void SceneDebugger::remove_from_cache(const String &p_filename, Node *p_node) { return; } - Map<String, Set<Node *>> &edit_cache = debugger->live_scene_edit_cache; - Map<String, Set<Node *>>::Element *E = edit_cache.find(p_filename); + HashMap<String, HashSet<Node *>> &edit_cache = debugger->live_scene_edit_cache; + HashMap<String, HashSet<Node *>>::Iterator E = edit_cache.find(p_filename); if (E) { - E->get().erase(p_node); - if (E->get().size() == 0) { - edit_cache.erase(E); + E->value.erase(p_node); + if (E->value.size() == 0) { + edit_cache.remove(E); } } - Map<Node *, Map<ObjectID, Node *>> &remove_list = debugger->live_edit_remove_list; - Map<Node *, Map<ObjectID, Node *>>::Element *F = remove_list.find(p_node); + HashMap<Node *, HashMap<ObjectID, Node *>> &remove_list = debugger->live_edit_remove_list; + HashMap<Node *, HashMap<ObjectID, Node *>>::Iterator F = remove_list.find(p_node); if (F) { - for (const KeyValue<ObjectID, Node *> &G : F->get()) { + for (const KeyValue<ObjectID, Node *> &G : F->value) { memdelete(G.value); } - remove_list.erase(F); + remove_list.remove(F); } } @@ -408,47 +408,47 @@ SceneDebuggerObject::SceneDebuggerObject(ObjectID p_id) { } void SceneDebuggerObject::_parse_script_properties(Script *p_script, ScriptInstance *p_instance) { - typedef Map<const Script *, Set<StringName>> ScriptMemberMap; - typedef Map<const Script *, Map<StringName, Variant>> ScriptConstantsMap; + typedef HashMap<const Script *, HashSet<StringName>> ScriptMemberMap; + typedef HashMap<const Script *, HashMap<StringName, Variant>> ScriptConstantsMap; ScriptMemberMap members; if (p_instance) { - members[p_script] = Set<StringName>(); + members[p_script] = HashSet<StringName>(); p_script->get_members(&(members[p_script])); } ScriptConstantsMap constants; - constants[p_script] = Map<StringName, Variant>(); + constants[p_script] = HashMap<StringName, Variant>(); p_script->get_constants(&(constants[p_script])); Ref<Script> base = p_script->get_base_script(); while (base.is_valid()) { if (p_instance) { - members[base.ptr()] = Set<StringName>(); + members[base.ptr()] = HashSet<StringName>(); base->get_members(&(members[base.ptr()])); } - constants[base.ptr()] = Map<StringName, Variant>(); + constants[base.ptr()] = HashMap<StringName, Variant>(); base->get_constants(&(constants[base.ptr()])); base = base->get_base_script(); } // Members - for (ScriptMemberMap::Element *sm = members.front(); sm; sm = sm->next()) { - for (Set<StringName>::Element *E = sm->get().front(); E; E = E->next()) { + for (KeyValue<const Script *, HashSet<StringName>> sm : members) { + for (const StringName &E : sm.value) { Variant m; - if (p_instance->get(E->get(), m)) { - String script_path = sm->key() == p_script ? "" : sm->key()->get_path().get_file() + "/"; - PropertyInfo pi(m.get_type(), "Members/" + script_path + E->get()); + if (p_instance->get(E, m)) { + String script_path = sm.key == p_script ? "" : sm.key->get_path().get_file() + "/"; + PropertyInfo pi(m.get_type(), "Members/" + script_path + E); properties.push_back(SceneDebuggerProperty(pi, m)); } } } // Constants - for (ScriptConstantsMap::Element *sc = constants.front(); sc; sc = sc->next()) { - for (const KeyValue<StringName, Variant> &E : sc->get()) { - String script_path = sc->key() == p_script ? "" : sc->key()->get_path().get_file() + "/"; + for (KeyValue<const Script *, HashMap<StringName, Variant>> &sc : constants) { + for (const KeyValue<StringName, Variant> &E : sc.value) { + String script_path = sc.key == p_script ? "" : sc.key->get_path().get_file() + "/"; if (E.value.get_type() == Variant::OBJECT) { Variant id = ((Object *)E.value)->get_instance_id(); PropertyInfo pi(id.get_type(), "Constants/" + E.key, PROPERTY_HINT_OBJECT_ID, "Object"); @@ -467,7 +467,7 @@ void SceneDebuggerObject::serialize(Array &r_arr, int p_max_size) { const PropertyInfo &pi = properties[i].first; Variant &var = properties[i].second; - RES res = var; + Ref<Resource> res = var; Array prop; prop.push_back(pi.name); @@ -529,7 +529,7 @@ void SceneDebuggerObject::deserialize(const Array &p_arr) { if (pinfo.type == Variant::OBJECT) { if (var.is_zero()) { - var = RES(); + var = Ref<Resource>(); } else if (var.get_type() == Variant::OBJECT) { if (((Object *)var)->is_class("EncodedObjectAsID")) { var = Object::cast_to<EncodedObjectAsID>(var)->get_object_id(); @@ -624,13 +624,13 @@ void LiveEditor::_node_set_func(int p_id, const StringName &p_prop, const Varian base = scene_tree->root->get_node(live_edit_root); } - Map<String, Set<Node *>>::Element *E = live_scene_edit_cache.find(live_edit_scene); + HashMap<String, HashSet<Node *>>::Iterator E = live_scene_edit_cache.find(live_edit_scene); if (!E) { return; //scene not editable } - for (Set<Node *>::Element *F = E->get().front(); F; F = F->next()) { - Node *n = F->get(); + for (Node *F : E->value) { + Node *n = F; if (base && !base->is_ancestor_of(n)) { continue; @@ -646,7 +646,7 @@ void LiveEditor::_node_set_func(int p_id, const StringName &p_prop, const Varian } void LiveEditor::_node_set_res_func(int p_id, const StringName &p_prop, const String &p_value) { - RES r = ResourceLoader::load(p_value); + Ref<Resource> r = ResourceLoader::load(p_value); if (!r.is_valid()) { return; } @@ -668,13 +668,13 @@ void LiveEditor::_node_call_func(int p_id, const StringName &p_method, const Var base = scene_tree->root->get_node(live_edit_root); } - Map<String, Set<Node *>>::Element *E = live_scene_edit_cache.find(live_edit_scene); + HashMap<String, HashSet<Node *>>::Iterator E = live_scene_edit_cache.find(live_edit_scene); if (!E) { return; //scene not editable } - for (Set<Node *>::Element *F = E->get().front(); F; F = F->next()) { - Node *n = F->get(); + for (Node *F : E->value) { + Node *n = F; if (base && !base->is_ancestor_of(n)) { continue; @@ -701,7 +701,7 @@ void LiveEditor::_res_set_func(int p_id, const StringName &p_prop, const Variant return; } - RES r = ResourceCache::get(resp); + Ref<Resource> r = ResourceCache::get_ref(resp); if (!r.is_valid()) { return; } @@ -710,7 +710,7 @@ void LiveEditor::_res_set_func(int p_id, const StringName &p_prop, const Variant } void LiveEditor::_res_set_res_func(int p_id, const StringName &p_prop, const String &p_value) { - RES r = ResourceLoader::load(p_value); + Ref<Resource> r = ResourceLoader::load(p_value); if (!r.is_valid()) { return; } @@ -728,7 +728,7 @@ void LiveEditor::_res_call_func(int p_id, const StringName &p_method, const Vari return; } - RES r = ResourceCache::get(resp); + Ref<Resource> r = ResourceCache::get_ref(resp); if (!r.is_valid()) { return; } @@ -753,13 +753,13 @@ void LiveEditor::_create_node_func(const NodePath &p_parent, const String &p_typ base = scene_tree->root->get_node(live_edit_root); } - Map<String, Set<Node *>>::Element *E = live_scene_edit_cache.find(live_edit_scene); + HashMap<String, HashSet<Node *>>::Iterator E = live_scene_edit_cache.find(live_edit_scene); if (!E) { return; //scene not editable } - for (Set<Node *>::Element *F = E->get().front(); F; F = F->next()) { - Node *n = F->get(); + for (Node *F : E->value) { + Node *n = F; if (base && !base->is_ancestor_of(n)) { continue; @@ -797,13 +797,13 @@ void LiveEditor::_instance_node_func(const NodePath &p_parent, const String &p_p base = scene_tree->root->get_node(live_edit_root); } - Map<String, Set<Node *>>::Element *E = live_scene_edit_cache.find(live_edit_scene); + HashMap<String, HashSet<Node *>>::Iterator E = live_scene_edit_cache.find(live_edit_scene); if (!E) { return; //scene not editable } - for (Set<Node *>::Element *F = E->get().front(); F; F = F->next()) { - Node *n = F->get(); + for (Node *F : E->value) { + Node *n = F; if (base && !base->is_ancestor_of(n)) { continue; @@ -835,15 +835,15 @@ void LiveEditor::_remove_node_func(const NodePath &p_at) { base = scene_tree->root->get_node(live_edit_root); } - Map<String, Set<Node *>>::Element *E = live_scene_edit_cache.find(live_edit_scene); + HashMap<String, HashSet<Node *>>::Iterator E = live_scene_edit_cache.find(live_edit_scene); if (!E) { return; //scene not editable } - for (Set<Node *>::Element *F = E->get().front(); F;) { - Set<Node *>::Element *N = F->next(); + Vector<Node *> to_delete; - Node *n = F->get(); + for (HashSet<Node *>::Iterator F = E->value.begin(); F; ++F) { + Node *n = *F; if (base && !base->is_ancestor_of(n)) { continue; @@ -854,9 +854,11 @@ void LiveEditor::_remove_node_func(const NodePath &p_at) { } Node *n2 = n->get_node(p_at); - memdelete(n2); + to_delete.push_back(n2); + } - F = N; + for (int i = 0; i < to_delete.size(); i++) { + memdelete(to_delete[i]); } } @@ -871,15 +873,14 @@ void LiveEditor::_remove_and_keep_node_func(const NodePath &p_at, ObjectID p_kee base = scene_tree->root->get_node(live_edit_root); } - Map<String, Set<Node *>>::Element *E = live_scene_edit_cache.find(live_edit_scene); + HashMap<String, HashSet<Node *>>::Iterator E = live_scene_edit_cache.find(live_edit_scene); if (!E) { return; //scene not editable } - for (Set<Node *>::Element *F = E->get().front(); F;) { - Set<Node *>::Element *N = F->next(); - - Node *n = F->get(); + Vector<Node *> to_remove; + for (HashSet<Node *>::Iterator F = E->value.begin(); F; ++F) { + Node *n = *F; if (base && !base->is_ancestor_of(n)) { continue; @@ -889,13 +890,14 @@ void LiveEditor::_remove_and_keep_node_func(const NodePath &p_at, ObjectID p_kee continue; } - Node *n2 = n->get_node(p_at); + to_remove.push_back(n); + } + for (int i = 0; i < to_remove.size(); i++) { + Node *n = to_remove[i]; + Node *n2 = n->get_node(p_at); n2->get_parent()->remove_child(n2); - live_edit_remove_list[n][p_keep_id] = n2; - - F = N; } } @@ -910,15 +912,16 @@ void LiveEditor::_restore_node_func(ObjectID p_id, const NodePath &p_at, int p_a base = scene_tree->root->get_node(live_edit_root); } - Map<String, Set<Node *>>::Element *E = live_scene_edit_cache.find(live_edit_scene); + HashMap<String, HashSet<Node *>>::Iterator E = live_scene_edit_cache.find(live_edit_scene); if (!E) { return; //scene not editable } - for (Set<Node *>::Element *F = E->get().front(); F;) { - Set<Node *>::Element *N = F->next(); + for (HashSet<Node *>::Iterator F = E->value.begin(); F;) { + HashSet<Node *>::Iterator N = F; + ++N; - Node *n = F->get(); + Node *n = *F; if (base && !base->is_ancestor_of(n)) { continue; @@ -929,23 +932,23 @@ void LiveEditor::_restore_node_func(ObjectID p_id, const NodePath &p_at, int p_a } Node *n2 = n->get_node(p_at); - Map<Node *, Map<ObjectID, Node *>>::Element *EN = live_edit_remove_list.find(n); + HashMap<Node *, HashMap<ObjectID, Node *>>::Iterator EN = live_edit_remove_list.find(n); if (!EN) { continue; } - Map<ObjectID, Node *>::Element *FN = EN->get().find(p_id); + HashMap<ObjectID, Node *>::Iterator FN = EN->value.find(p_id); if (!FN) { continue; } - n2->add_child(FN->get()); + n2->add_child(FN->value); - EN->get().erase(FN); + EN->value.remove(FN); - if (EN->get().size() == 0) { - live_edit_remove_list.erase(EN); + if (EN->value.size() == 0) { + live_edit_remove_list.remove(EN); } F = N; @@ -963,13 +966,13 @@ void LiveEditor::_duplicate_node_func(const NodePath &p_at, const String &p_new_ base = scene_tree->root->get_node(live_edit_root); } - Map<String, Set<Node *>>::Element *E = live_scene_edit_cache.find(live_edit_scene); + HashMap<String, HashSet<Node *>>::Iterator E = live_scene_edit_cache.find(live_edit_scene); if (!E) { return; //scene not editable } - for (Set<Node *>::Element *F = E->get().front(); F; F = F->next()) { - Node *n = F->get(); + for (Node *F : E->value) { + Node *n = F; if (base && !base->is_ancestor_of(n)) { continue; @@ -1002,13 +1005,13 @@ void LiveEditor::_reparent_node_func(const NodePath &p_at, const NodePath &p_new base = scene_tree->root->get_node(live_edit_root); } - Map<String, Set<Node *>>::Element *E = live_scene_edit_cache.find(live_edit_scene); + HashMap<String, HashSet<Node *>>::Iterator E = live_scene_edit_cache.find(live_edit_scene); if (!E) { return; //scene not editable } - for (Set<Node *>::Element *F = E->get().front(); F; F = F->next()) { - Node *n = F->get(); + for (Node *F : E->value) { + Node *n = F; if (base && !base->is_ancestor_of(n)) { continue; diff --git a/scene/debugger/scene_debugger.h b/scene/debugger/scene_debugger.h index 29d7da7d11..4ed126d36e 100644 --- a/scene/debugger/scene_debugger.h +++ b/scene/debugger/scene_debugger.h @@ -132,14 +132,14 @@ public: class LiveEditor { private: friend class SceneDebugger; - Map<int, NodePath> live_edit_node_path_cache; - Map<int, String> live_edit_resource_cache; + HashMap<int, NodePath> live_edit_node_path_cache; + HashMap<int, String> live_edit_resource_cache; NodePath live_edit_root; String live_edit_scene; - Map<String, Set<Node *>> live_scene_edit_cache; - Map<Node *, Map<ObjectID, Node *>> live_edit_remove_list; + HashMap<String, HashSet<Node *>> live_scene_edit_cache; + HashMap<Node *, HashMap<ObjectID, Node *>> live_edit_remove_list; void _send_tree(); diff --git a/scene/gui/aspect_ratio_container.cpp b/scene/gui/aspect_ratio_container.cpp index b59eda465e..75f19ac452 100644 --- a/scene/gui/aspect_ratio_container.cpp +++ b/scene/gui/aspect_ratio_container.cpp @@ -172,7 +172,7 @@ void AspectRatioContainer::_bind_methods() { ClassDB::bind_method(D_METHOD("set_alignment_vertical", "alignment_vertical"), &AspectRatioContainer::set_alignment_vertical); ClassDB::bind_method(D_METHOD("get_alignment_vertical"), &AspectRatioContainer::get_alignment_vertical); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "ratio"), "set_ratio", "get_ratio"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "ratio", PROPERTY_HINT_RANGE, "0.001,10.0,0.0001,or_greater"), "set_ratio", "get_ratio"); ADD_PROPERTY(PropertyInfo(Variant::INT, "stretch_mode", PROPERTY_HINT_ENUM, "Width Controls Height,Height Controls Width,Fit,Cover"), "set_stretch_mode", "get_stretch_mode"); ADD_GROUP("Alignment", "alignment_"); diff --git a/scene/gui/base_button.cpp b/scene/gui/base_button.cpp index 789c01adf3..776623f7ce 100644 --- a/scene/gui/base_button.cpp +++ b/scene/gui/base_button.cpp @@ -43,12 +43,12 @@ void BaseButton::_unpress_group() { status.pressed = true; } - for (Set<BaseButton *>::Element *E = button_group->buttons.front(); E; E = E->next()) { - if (E->get() == this) { + for (BaseButton *E : button_group->buttons) { + if (E == this) { continue; } - E->get()->set_pressed(false); + E->set_pressed(false); } } @@ -485,24 +485,24 @@ BaseButton::~BaseButton() { } void ButtonGroup::get_buttons(List<BaseButton *> *r_buttons) { - for (Set<BaseButton *>::Element *E = buttons.front(); E; E = E->next()) { - r_buttons->push_back(E->get()); + for (BaseButton *E : buttons) { + r_buttons->push_back(E); } } Array ButtonGroup::_get_buttons() { Array btns; - for (Set<BaseButton *>::Element *E = buttons.front(); E; E = E->next()) { - btns.push_back(E->get()); + for (const BaseButton *E : buttons) { + btns.push_back(E); } return btns; } BaseButton *ButtonGroup::get_pressed_button() { - for (Set<BaseButton *>::Element *E = buttons.front(); E; E = E->next()) { - if (E->get()->is_pressed()) { - return E->get(); + for (BaseButton *E : buttons) { + if (E->is_pressed()) { + return E; } } diff --git a/scene/gui/base_button.h b/scene/gui/base_button.h index f4f9b88868..ba3852ec98 100644 --- a/scene/gui/base_button.h +++ b/scene/gui/base_button.h @@ -143,7 +143,7 @@ VARIANT_ENUM_CAST(BaseButton::ActionMode) class ButtonGroup : public Resource { GDCLASS(ButtonGroup, Resource); friend class BaseButton; - Set<BaseButton *> buttons; + HashSet<BaseButton *> buttons; protected: static void _bind_methods(); diff --git a/scene/gui/box_container.cpp b/scene/gui/box_container.cpp index 251648da69..df695feba8 100644 --- a/scene/gui/box_container.cpp +++ b/scene/gui/box_container.cpp @@ -52,7 +52,7 @@ void BoxContainer::_resort() { int stretch_min = 0; int stretch_avail = 0; float stretch_ratio_total = 0.0; - Map<Control *, _MinSizeCache> min_size_cache; + HashMap<Control *, _MinSizeCache> min_size_cache; for (int i = 0; i < get_child_count(); i++) { Control *c = Object::cast_to<Control>(get_child(i)); diff --git a/scene/gui/button.cpp b/scene/gui/button.cpp index 29a0681f9c..a67f850a86 100644 --- a/scene/gui/button.cpp +++ b/scene/gui/button.cpp @@ -35,7 +35,7 @@ Size2 Button::get_minimum_size() const { Size2 minsize = text_buf->get_size(); - if (clip_text) { + if (clip_text || overrun_behavior != TextServer::OVERRUN_NO_TRIMMING) { minsize.width = 0; } @@ -53,18 +53,18 @@ Size2 Button::get_minimum_size() const { if (icon_alignment != HORIZONTAL_ALIGNMENT_CENTER) { minsize.width += _icon->get_width(); if (!xl_text.is_empty()) { - minsize.width += get_theme_constant(SNAME("hseparation")); + minsize.width += get_theme_constant(SNAME("h_separation")); } } else { minsize.width = MAX(minsize.width, _icon->get_width()); } } } - - Ref<Font> font = get_theme_font(SNAME("font")); - float font_height = font->get_height(get_theme_font_size(SNAME("font_size"))); - - minsize.height = MAX(font_height, minsize.height); + if (!xl_text.is_empty()) { + Ref<Font> font = get_theme_font(SNAME("font")); + float font_height = font->get_height(get_theme_font_size(SNAME("font_size"))); + minsize.height = MAX(font_height, minsize.height); + } return get_theme_stylebox(SNAME("normal"))->get_minimum_size() + minsize; } @@ -244,21 +244,22 @@ void Button::_notification(int p_what) { if (icon_align_rtl_checked == HORIZONTAL_ALIGNMENT_LEFT) { style_offset.x = style->get_margin(SIDE_LEFT); if (_internal_margin[SIDE_LEFT] > 0) { - icon_ofs_region = _internal_margin[SIDE_LEFT] + get_theme_constant(SNAME("hseparation")); + icon_ofs_region = _internal_margin[SIDE_LEFT] + get_theme_constant(SNAME("h_separation")); } } else if (icon_align_rtl_checked == HORIZONTAL_ALIGNMENT_CENTER) { style_offset.x = 0.0; } else if (icon_align_rtl_checked == HORIZONTAL_ALIGNMENT_RIGHT) { style_offset.x = -style->get_margin(SIDE_RIGHT); if (_internal_margin[SIDE_RIGHT] > 0) { - icon_ofs_region = -_internal_margin[SIDE_RIGHT] - get_theme_constant(SNAME("hseparation")); + icon_ofs_region = -_internal_margin[SIDE_RIGHT] - get_theme_constant(SNAME("h_separation")); } } style_offset.y = style->get_margin(SIDE_TOP); if (expand_icon) { Size2 _size = get_size() - style->get_offset() * 2; - _size.width -= get_theme_constant(SNAME("hseparation")) + icon_ofs_region; + int icon_text_separation = text.is_empty() ? 0 : get_theme_constant(SNAME("h_separation")); + _size.width -= icon_text_separation + icon_ofs_region; if (!clip_text && icon_align_rtl_checked != HORIZONTAL_ALIGNMENT_CENTER) { _size.width -= text_buf->get_size().width; } @@ -286,20 +287,20 @@ void Button::_notification(int p_what) { } } - Point2 icon_ofs = !_icon.is_null() ? Point2(icon_region.size.width + get_theme_constant(SNAME("hseparation")), 0) : Point2(); + Point2 icon_ofs = !_icon.is_null() ? Point2(icon_region.size.width + get_theme_constant(SNAME("h_separation")), 0) : Point2(); if (align_rtl_checked == HORIZONTAL_ALIGNMENT_CENTER && icon_align_rtl_checked == HORIZONTAL_ALIGNMENT_CENTER) { icon_ofs.x = 0.0; } int text_clip = size.width - style->get_minimum_size().width - icon_ofs.width; - text_buf->set_width(clip_text ? text_clip : -1); + text_buf->set_width((clip_text || overrun_behavior != TextServer::OVERRUN_NO_TRIMMING) ? text_clip : -1); - int text_width = MAX(1, clip_text ? MIN(text_clip, text_buf->get_size().x) : text_buf->get_size().x); + int text_width = MAX(1, (clip_text || overrun_behavior != TextServer::OVERRUN_NO_TRIMMING) ? MIN(text_clip, text_buf->get_size().x) : text_buf->get_size().x); if (_internal_margin[SIDE_LEFT] > 0) { - text_clip -= _internal_margin[SIDE_LEFT] + get_theme_constant(SNAME("hseparation")); + text_clip -= _internal_margin[SIDE_LEFT] + get_theme_constant(SNAME("h_separation")); } if (_internal_margin[SIDE_RIGHT] > 0) { - text_clip -= _internal_margin[SIDE_RIGHT] + get_theme_constant(SNAME("hseparation")); + text_clip -= _internal_margin[SIDE_RIGHT] + get_theme_constant(SNAME("h_separation")); } Point2 text_ofs = (size - style->get_minimum_size() - icon_ofs - text_buf->get_size() - Point2(_internal_margin[SIDE_RIGHT] - _internal_margin[SIDE_LEFT], 0)) / 2.0; @@ -313,7 +314,7 @@ void Button::_notification(int p_what) { icon_ofs.x = 0.0; } if (_internal_margin[SIDE_LEFT] > 0) { - text_ofs.x = style->get_margin(SIDE_LEFT) + icon_ofs.x + _internal_margin[SIDE_LEFT] + get_theme_constant(SNAME("hseparation")); + text_ofs.x = style->get_margin(SIDE_LEFT) + icon_ofs.x + _internal_margin[SIDE_LEFT] + get_theme_constant(SNAME("h_separation")); } else { text_ofs.x = style->get_margin(SIDE_LEFT) + icon_ofs.x; } @@ -330,7 +331,7 @@ void Button::_notification(int p_what) { } break; case HORIZONTAL_ALIGNMENT_RIGHT: { if (_internal_margin[SIDE_RIGHT] > 0) { - text_ofs.x = size.x - style->get_margin(SIDE_RIGHT) - text_width - _internal_margin[SIDE_RIGHT] - get_theme_constant(SNAME("hseparation")); + text_ofs.x = size.x - style->get_margin(SIDE_RIGHT) - text_width - _internal_margin[SIDE_RIGHT] - get_theme_constant(SNAME("h_separation")); } else { text_ofs.x = size.x - style->get_margin(SIDE_RIGHT) - text_width; } @@ -346,7 +347,6 @@ void Button::_notification(int p_what) { if (outline_size > 0 && font_outline_color.a > 0) { text_buf->draw_outline(ci, text_ofs, outline_size, font_outline_color); } - text_buf->draw(ci, text_ofs, color); } break; } @@ -362,7 +362,22 @@ void Button::_shape() { } else { text_buf->set_direction((TextServer::Direction)text_direction); } - text_buf->add_string(xl_text, font, font_size, opentype_features, (!language.is_empty()) ? language : TranslationServer::get_singleton()->get_tool_locale()); + text_buf->add_string(xl_text, font, font_size, language); + text_buf->set_text_overrun_behavior(overrun_behavior); +} + +void Button::set_text_overrun_behavior(TextServer::OverrunBehavior p_behavior) { + if (overrun_behavior != p_behavior) { + overrun_behavior = p_behavior; + _shape(); + + update(); + update_minimum_size(); + } +} + +TextServer::OverrunBehavior Button::get_text_overrun_behavior() const { + return overrun_behavior; } void Button::set_text(const String &p_text) { @@ -393,29 +408,6 @@ Control::TextDirection Button::get_text_direction() const { return text_direction; } -void Button::clear_opentype_features() { - opentype_features.clear(); - _shape(); - update(); -} - -void Button::set_opentype_feature(const String &p_name, int p_value) { - int32_t tag = TS->name_to_tag(p_name); - if (!opentype_features.has(tag) || (int)opentype_features[tag] != p_value) { - opentype_features[tag] = p_value; - _shape(); - update(); - } -} - -int Button::get_opentype_feature(const String &p_name) const { - int32_t tag = TS->name_to_tag(p_name); - if (!opentype_features.has(tag)) { - return -1; - } - return opentype_features[tag]; -} - void Button::set_language(const String &p_language) { if (language != p_language) { language = p_language; @@ -496,64 +488,13 @@ HorizontalAlignment Button::get_icon_alignment() const { return icon_alignment; } -bool Button::_set(const StringName &p_name, const Variant &p_value) { - String str = p_name; - if (str.begins_with("opentype_features/")) { - String name = str.get_slicec('/', 1); - int32_t tag = TS->name_to_tag(name); - int value = p_value; - if (value == -1) { - if (opentype_features.has(tag)) { - opentype_features.erase(tag); - _shape(); - update(); - } - } else { - if (!opentype_features.has(tag) || (int)opentype_features[tag] != value) { - opentype_features[tag] = value; - _shape(); - update(); - } - } - notify_property_list_changed(); - return true; - } - - return false; -} - -bool Button::_get(const StringName &p_name, Variant &r_ret) const { - String str = p_name; - if (str.begins_with("opentype_features/")) { - String name = str.get_slicec('/', 1); - int32_t tag = TS->name_to_tag(name); - if (opentype_features.has(tag)) { - r_ret = opentype_features[tag]; - return true; - } else { - r_ret = -1; - return true; - } - } - return false; -} - -void Button::_get_property_list(List<PropertyInfo> *p_list) const { - for (const Variant *ftr = opentype_features.next(nullptr); ftr != nullptr; ftr = opentype_features.next(ftr)) { - String name = TS->tag_to_name(*ftr); - p_list->push_back(PropertyInfo(Variant::INT, "opentype_features/" + name)); - } - p_list->push_back(PropertyInfo(Variant::NIL, "opentype_features/_new", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR)); -} - void Button::_bind_methods() { ClassDB::bind_method(D_METHOD("set_text", "text"), &Button::set_text); ClassDB::bind_method(D_METHOD("get_text"), &Button::get_text); + ClassDB::bind_method(D_METHOD("set_text_overrun_behavior", "overrun_behavior"), &Button::set_text_overrun_behavior); + ClassDB::bind_method(D_METHOD("get_text_overrun_behavior"), &Button::get_text_overrun_behavior); ClassDB::bind_method(D_METHOD("set_text_direction", "direction"), &Button::set_text_direction); ClassDB::bind_method(D_METHOD("get_text_direction"), &Button::get_text_direction); - ClassDB::bind_method(D_METHOD("set_opentype_feature", "tag", "value"), &Button::set_opentype_feature); - ClassDB::bind_method(D_METHOD("get_opentype_feature", "tag"), &Button::get_opentype_feature); - ClassDB::bind_method(D_METHOD("clear_opentype_features"), &Button::clear_opentype_features); ClassDB::bind_method(D_METHOD("set_language", "language"), &Button::set_language); ClassDB::bind_method(D_METHOD("get_language"), &Button::get_language); ClassDB::bind_method(D_METHOD("set_button_icon", "texture"), &Button::set_icon); @@ -570,19 +511,22 @@ void Button::_bind_methods() { ClassDB::bind_method(D_METHOD("is_expand_icon"), &Button::is_expand_icon); ADD_PROPERTY(PropertyInfo(Variant::STRING, "text", PROPERTY_HINT_MULTILINE_TEXT, "", PROPERTY_USAGE_DEFAULT_INTL), "set_text", "get_text"); - 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", PROPERTY_HINT_LOCALE_ID, ""), "set_language", "get_language"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "icon", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_button_icon", "get_button_icon"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "flat"), "set_flat", "is_flat"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "clip_text"), "set_clip_text", "get_clip_text"); ADD_PROPERTY(PropertyInfo(Variant::INT, "alignment", PROPERTY_HINT_ENUM, "Left,Center,Right"), "set_text_alignment", "get_text_alignment"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "text_overrun_behavior", PROPERTY_HINT_ENUM, "Trim Nothing,Trim Characters,Trim Words,Ellipsis,Word Ellipsis"), "set_text_overrun_behavior", "get_text_overrun_behavior"); ADD_PROPERTY(PropertyInfo(Variant::INT, "icon_alignment", PROPERTY_HINT_ENUM, "Left,Center,Right"), "set_icon_alignment", "get_icon_alignment"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "expand_icon"), "set_expand_icon", "is_expand_icon"); + + ADD_GROUP("BiDi", ""); + 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", PROPERTY_HINT_LOCALE_ID, ""), "set_language", "get_language"); } Button::Button(const String &p_text) { text_buf.instantiate(); - text_buf->set_flags(TextServer::BREAK_MANDATORY); + text_buf->set_break_flags(TextServer::BREAK_MANDATORY); set_mouse_filter(MOUSE_FILTER_STOP); set_text(p_text); diff --git a/scene/gui/button.h b/scene/gui/button.h index 1abf86c986..7a29cba677 100644 --- a/scene/gui/button.h +++ b/scene/gui/button.h @@ -43,9 +43,9 @@ private: String xl_text; Ref<TextParagraph> text_buf; - Dictionary opentype_features; String language; TextDirection text_direction = TEXT_DIRECTION_AUTO; + TextServer::OverrunBehavior overrun_behavior = TextServer::OVERRUN_NO_TRIMMING; Ref<Texture2D> icon; bool expand_icon = false; @@ -61,23 +61,18 @@ protected: void _notification(int p_what); static void _bind_methods(); - bool _set(const StringName &p_name, const Variant &p_value); - bool _get(const StringName &p_name, Variant &r_ret) const; - void _get_property_list(List<PropertyInfo> *p_list) const; - public: virtual Size2 get_minimum_size() const override; void set_text(const String &p_text); String get_text() const; + void set_text_overrun_behavior(TextServer::OverrunBehavior p_behavior); + TextServer::OverrunBehavior get_text_overrun_behavior() const; + void set_text_direction(TextDirection p_text_direction); TextDirection get_text_direction() const; - void set_opentype_feature(const String &p_name, int p_value); - int get_opentype_feature(const String &p_name) const; - void clear_opentype_features(); - void set_language(const String &p_language); String get_language() const; diff --git a/scene/gui/check_box.cpp b/scene/gui/check_box.cpp index 063a154bb2..cb80f5b5ef 100644 --- a/scene/gui/check_box.cpp +++ b/scene/gui/check_box.cpp @@ -75,7 +75,7 @@ Size2 CheckBox::get_minimum_size() const { Size2 tex_size = get_icon_size(); minsize.width += tex_size.width; if (get_text().length() > 0) { - minsize.width += get_theme_constant(SNAME("hseparation")); + minsize.width += get_theme_constant(SNAME("h_separation")); } Ref<StyleBox> sb = get_theme_stylebox(SNAME("normal")); minsize.height = MAX(minsize.height, tex_size.height + sb->get_margin(SIDE_TOP) + sb->get_margin(SIDE_BOTTOM)); @@ -110,7 +110,7 @@ void CheckBox::_notification(int p_what) { } else { ofs.x = sb->get_margin(SIDE_LEFT); } - ofs.y = int((get_size().height - get_icon_size().height) / 2) + get_theme_constant(SNAME("check_vadjust")); + ofs.y = int((get_size().height - get_icon_size().height) / 2) + get_theme_constant(SNAME("check_v_adjust")); if (is_pressed()) { on->draw(ci, ofs); diff --git a/scene/gui/check_button.cpp b/scene/gui/check_button.cpp index 527b0061ac..a09873ea4f 100644 --- a/scene/gui/check_button.cpp +++ b/scene/gui/check_button.cpp @@ -52,7 +52,7 @@ Size2 CheckButton::get_minimum_size() const { Size2 tex_size = get_icon_size(); minsize.width += tex_size.width; if (get_text().length() > 0) { - minsize.width += get_theme_constant(SNAME("hseparation")); + minsize.width += get_theme_constant(SNAME("h_separation")); } Ref<StyleBox> sb = get_theme_stylebox(SNAME("normal")); minsize.height = MAX(minsize.height, tex_size.height + sb->get_margin(SIDE_TOP) + sb->get_margin(SIDE_BOTTOM)); @@ -100,7 +100,7 @@ void CheckButton::_notification(int p_what) { } else { ofs.x = get_size().width - (tex_size.width + sb->get_margin(SIDE_RIGHT)); } - ofs.y = (get_size().height - tex_size.height) / 2 + get_theme_constant(SNAME("check_vadjust")); + ofs.y = (get_size().height - tex_size.height) / 2 + get_theme_constant(SNAME("check_v_adjust")); if (is_pressed()) { on->draw(ci, ofs); diff --git a/scene/gui/code_edit.cpp b/scene/gui/code_edit.cpp index 2e87e71972..8968c1cc17 100644 --- a/scene/gui/code_edit.cpp +++ b/scene/gui/code_edit.cpp @@ -46,7 +46,7 @@ void CodeEdit::_notification(int p_what) { line_spacing = get_theme_constant(SNAME("line_spacing")); set_gutter_width(main_gutter, get_line_height()); - set_gutter_width(line_number_gutter, (line_number_digits + 1) * font->get_char_size('0', 0, font_size).width); + set_gutter_width(line_number_gutter, (line_number_digits + 1) * font->get_char_size('0', font_size).width); set_gutter_width(fold_gutter, get_line_height() / 1.2); breakpoint_color = get_theme_color(SNAME("breakpoint_color")); @@ -68,6 +68,7 @@ void CodeEdit::_notification(int p_what) { code_completion_max_lines = get_theme_constant(SNAME("completion_lines")); code_completion_scroll_width = get_theme_constant(SNAME("completion_scroll_width")); code_completion_scroll_color = get_theme_color(SNAME("completion_scroll_color")); + code_completion_scroll_hovered_color = get_theme_color(SNAME("completion_scroll_hovered_color")); code_completion_background_color = get_theme_color(SNAME("completion_background_color")); code_completion_selected_color = get_theme_color(SNAME("completion_selected_color")); code_completion_existing_color = get_theme_color(SNAME("completion_existing_color")); @@ -85,7 +86,7 @@ void CodeEdit::_notification(int p_what) { if (line_length_guideline_columns.size() > 0) { const int xmargin_beg = style_normal->get_margin(SIDE_LEFT) + get_total_gutter_width(); const int xmargin_end = size.width - style_normal->get_margin(SIDE_RIGHT) - (is_drawing_minimap() ? get_minimap_width() : 0); - const float char_size = font->get_char_size('0', 0, font_size).width; + const float char_size = font->get_char_size('0', font_size).width; for (int i = 0; i < line_length_guideline_columns.size(); i++) { const int xoffset = xmargin_beg + char_size * (int)line_length_guideline_columns[i] - get_h_scroll(); @@ -106,7 +107,7 @@ void CodeEdit::_notification(int p_what) { const int code_completion_options_count = code_completion_options.size(); const int lines = MIN(code_completion_options_count, code_completion_max_lines); - const int icon_hsep = get_theme_constant(SNAME("hseparation"), SNAME("ItemList")); + const int icon_hsep = get_theme_constant(SNAME("h_separation"), SNAME("ItemList")); const Size2 icon_area_size(row_height, row_height); code_completion_rect.size.width = code_completion_longest_line + icon_hsep + icon_area_size.width + 2; @@ -122,7 +123,7 @@ void CodeEdit::_notification(int p_what) { } const int scroll_width = code_completion_options_count > code_completion_max_lines ? code_completion_scroll_width : 0; - const int code_completion_base_width = font->get_string_size(code_completion_base, font_size).width; + const int code_completion_base_width = font->get_string_size(code_completion_base, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size).width; if (caret_pos.x - code_completion_base_width + code_completion_rect.size.width + scroll_width > get_size().width) { code_completion_rect.position.x = get_size().width - code_completion_rect.size.width - scroll_width; } else { @@ -134,6 +135,9 @@ void CodeEdit::_notification(int p_what) { RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(code_completion_rect.position, code_completion_rect.size + Size2(scroll_width, 0)), code_completion_background_color); } + code_completion_scroll_rect.position = code_completion_rect.position + Vector2(code_completion_rect.size.width, 0); + code_completion_scroll_rect.size = Vector2(scroll_width, code_completion_rect.size.height); + code_completion_line_ofs = CLAMP(code_completion_current_selected - lines / 2, 0, code_completion_options_count - lines); RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2(code_completion_rect.position.x, code_completion_rect.position.y + (code_completion_current_selected - code_completion_line_ofs) * row_height), Size2(code_completion_rect.size.width, row_height)), code_completion_selected_color); @@ -174,8 +178,8 @@ void CodeEdit::_notification(int p_what) { for (int j = 0; j < code_completion_options[l].matches.size(); j++) { Pair<int, int> match = code_completion_options[l].matches[j]; - int match_offset = font->get_string_size(code_completion_options[l].display.substr(0, match.first), font_size).width; - int match_len = font->get_string_size(code_completion_options[l].display.substr(match.first, match.second), font_size).width; + int match_offset = font->get_string_size(code_completion_options[l].display.substr(0, match.first), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size).width; + int match_len = font->get_string_size(code_completion_options[l].display.substr(match.first, match.second), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size).width; draw_rect(Rect2(match_pos + Point2(match_offset, 0), Size2(match_len, row_height)), code_completion_existing_color); } @@ -185,9 +189,11 @@ void CodeEdit::_notification(int p_what) { /* Draw a small scroll rectangle to show a position in the options. */ if (scroll_width) { + Color scroll_color = is_code_completion_scroll_hovered || is_code_completion_scroll_pressed ? code_completion_scroll_hovered_color : code_completion_scroll_color; + float r = (float)code_completion_max_lines / code_completion_options_count; float o = (float)code_completion_line_ofs / code_completion_options_count; - draw_rect(Rect2(code_completion_rect.position.x + code_completion_rect.size.width, code_completion_rect.position.y + o * code_completion_rect.size.y, scroll_width, code_completion_rect.size.y * r), code_completion_scroll_color); + draw_rect(Rect2(code_completion_rect.position.x + code_completion_rect.size.width, code_completion_rect.position.y + o * code_completion_rect.size.y, scroll_width, code_completion_rect.size.y * r), scroll_color); } } @@ -202,11 +208,11 @@ void CodeEdit::_notification(int p_what) { int max_width = 0; for (int i = 0; i < line_count; i++) { - max_width = MAX(max_width, font->get_string_size(code_hint_lines[i], font_size).x); + max_width = MAX(max_width, font->get_string_size(code_hint_lines[i], HORIZONTAL_ALIGNMENT_LEFT, -1, font_size).x); } Size2 minsize = sb->get_minimum_size() + Size2(max_width, line_count * font_height + (line_spacing * line_count - 1)); - int offset = font->get_string_size(code_hint_lines[0].substr(0, code_hint_lines[0].find(String::chr(0xFFFF))), font_size).x; + int offset = font->get_string_size(code_hint_lines[0].substr(0, code_hint_lines[0].find(String::chr(0xFFFF))), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size).x; if (code_hint_xpos == -0xFFFF) { code_hint_xpos = get_caret_draw_pos().x - offset; } @@ -226,8 +232,8 @@ void CodeEdit::_notification(int p_what) { int begin = 0; int end = 0; if (line.contains(String::chr(0xFFFF))) { - begin = font->get_string_size(line.substr(0, line.find(String::chr(0xFFFF))), font_size).x; - end = font->get_string_size(line.substr(0, line.rfind(String::chr(0xFFFF))), font_size).x; + begin = font->get_string_size(line.substr(0, line.find(String::chr(0xFFFF))), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size).x; + end = font->get_string_size(line.substr(0, line.rfind(String::chr(0xFFFF))), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size).x; } Point2 round_ofs = hint_ofs + sb->get_offset() + Vector2(0, font->get_ascent(font_size) + font_height * i + yofs); @@ -260,6 +266,12 @@ void CodeEdit::gui_input(const Ref<InputEvent> &p_gui_input) { return; } + if (is_code_completion_scroll_pressed && mb->get_button_index() == MouseButton::LEFT) { + is_code_completion_scroll_pressed = false; + update(); + return; + } + if (code_completion_active && code_completion_rect.has_point(mb->get_position())) { if (!mb->is_pressed()) { return; @@ -289,7 +301,21 @@ void CodeEdit::gui_input(const Ref<InputEvent> &p_gui_input) { break; } return; + } else if (code_completion_active && code_completion_scroll_rect.has_point(mb->get_position())) { + if (mb->get_button_index() != MouseButton::LEFT) { + return; + } + + if (mb->is_pressed()) { + is_code_completion_scroll_pressed = true; + + _update_scroll_selected_line(mb->get_position().y); + update(); + } + + return; } + cancel_code_completion(); set_code_hint(""); @@ -354,9 +380,26 @@ void CodeEdit::gui_input(const Ref<InputEvent> &p_gui_input) { set_symbol_lookup_word_as_valid(false); } } + + bool scroll_hovered = code_completion_scroll_rect.has_point(mpos); + if (is_code_completion_scroll_hovered != scroll_hovered) { + is_code_completion_scroll_hovered = scroll_hovered; + update(); + } + + if (is_code_completion_scroll_pressed) { + _update_scroll_selected_line(mpos.y); + update(); + return; + } } Ref<InputEventKey> k = p_gui_input; + if (TextEdit::alt_input(p_gui_input)) { + accept_event(); + return; + } + bool update_code_completion = false; if (!k.is_valid()) { TextEdit::gui_input(p_gui_input); @@ -364,7 +407,7 @@ void CodeEdit::gui_input(const Ref<InputEvent> &p_gui_input) { } /* Ctrl + Hover symbols */ -#ifdef OSX_ENABLED +#ifdef MACOS_ENABLED if (k->get_keycode() == Key::META) { #else if (k->get_keycode() == Key::CTRL) { @@ -541,6 +584,10 @@ Control::CursorShape CodeEdit::get_cursor_shape(const Point2 &p_pos) const { return CURSOR_ARROW; } + if (code_completion_active && code_completion_scroll_rect.has_point(p_pos)) { + return CURSOR_ARROW; + } + Point2i pos = get_line_column_at_pos(p_pos, false); int line = pos.y; int col = pos.x; @@ -735,8 +782,8 @@ void CodeEdit::set_auto_indent_prefixes(const TypedArray<String> &p_prefixes) { TypedArray<String> CodeEdit::get_auto_indent_prefixes() const { TypedArray<String> prefixes; - for (const Set<char32_t>::Element *E = auto_indent_prefixes.front(); E; E = E->next()) { - prefixes.push_back(String::chr(E->get())); + for (const char32_t &E : auto_indent_prefixes) { + prefixes.push_back(String::chr(E)); } return prefixes; } @@ -994,7 +1041,8 @@ void CodeEdit::_new_line(bool p_split_current_line, bool p_above) { } /* Make sure this is the last char, trailing whitespace or comments are okay. */ - if (should_indent && (!is_whitespace(c) && is_in_comment(cl, cc) == -1)) { + /* Increment column for comments because the delimiter (#) should be ignored. */ + if (should_indent && (!is_whitespace(c) && is_in_comment(cl, line_col + 1) == -1)) { should_indent = false; } } @@ -1622,7 +1670,7 @@ Point2 CodeEdit::get_delimiter_start_position(int p_line, int p_column) const { start_position.y = -1; start_position.x = -1; - bool in_region = ((p_line <= 0 || delimiter_cache[p_line - 1].size() < 1) ? -1 : delimiter_cache[p_line - 1].back()->value()) != -1; + bool in_region = ((p_line <= 0 || delimiter_cache[p_line - 1].size() < 1) ? -1 : delimiter_cache[p_line - 1].back()->get()) != -1; /* Check the keys for this line. */ for (const KeyValue<int, int> &E : delimiter_cache[p_line]) { @@ -1746,8 +1794,8 @@ void CodeEdit::set_code_completion_prefixes(const TypedArray<String> &p_prefixes TypedArray<String> CodeEdit::get_code_completion_prefixes() const { TypedArray<String> prefixes; - for (const Set<char32_t>::Element *E = code_completion_prefixes.front(); E; E = E->next()) { - prefixes.push_back(String::chr(E->get())); + for (const char32_t &E : code_completion_prefixes) { + prefixes.push_back(String::chr(E)); } return prefixes; } @@ -1817,7 +1865,7 @@ void CodeEdit::request_code_completion(bool p_force) { } } -void CodeEdit::add_code_completion_option(CodeCompletionKind p_type, const String &p_display_text, const String &p_insert_text, const Color &p_text_color, const RES &p_icon, const Variant &p_value) { +void CodeEdit::add_code_completion_option(CodeCompletionKind p_type, const String &p_display_text, const String &p_insert_text, const Color &p_text_color, const Ref<Resource> &p_icon, const Variant &p_value) { ScriptLanguage::CodeCompletionOption completion_option; completion_option.kind = (ScriptLanguage::CodeCompletionKind)p_type; completion_option.display = p_display_text; @@ -2195,7 +2243,7 @@ void CodeEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("get_text_for_code_completion"), &CodeEdit::get_text_for_code_completion); ClassDB::bind_method(D_METHOD("request_code_completion", "force"), &CodeEdit::request_code_completion, DEFVAL(false)); - ClassDB::bind_method(D_METHOD("add_code_completion_option", "type", "display_text", "insert_text", "text_color", "icon", "value"), &CodeEdit::add_code_completion_option, DEFVAL(Color(1, 1, 1)), DEFVAL(RES()), DEFVAL(Variant::NIL)); + ClassDB::bind_method(D_METHOD("add_code_completion_option", "type", "display_text", "insert_text", "text_color", "icon", "value"), &CodeEdit::add_code_completion_option, DEFVAL(Color(1, 1, 1)), DEFVAL(Ref<Resource>()), DEFVAL(Variant::NIL)); ClassDB::bind_method(D_METHOD("update_code_completion_options", "force"), &CodeEdit::update_code_completion_options); ClassDB::bind_method(D_METHOD("get_code_completion_options"), &CodeEdit::get_code_completion_options); ClassDB::bind_method(D_METHOD("get_code_completion_option", "index"), &CodeEdit::get_code_completion_option); @@ -2396,7 +2444,7 @@ void CodeEdit::_update_delimiter_cache(int p_from_line, int p_to_line) { } } else { for (int i = start_line; i < end_line; i++) { - delimiter_cache.insert(i, Map<int, int>()); + delimiter_cache.insert(i, RBMap<int, int>()); } } } @@ -2533,7 +2581,7 @@ int CodeEdit::_is_in_delimiter(int p_line, int p_column, DelimiterType p_type) c int region = (p_line <= 0 || delimiter_cache[p_line - 1].size() < 1) ? -1 : delimiter_cache[p_line - 1].back()->value(); bool in_region = region != -1 && delimiters[region].type == p_type; - for (Map<int, int>::Element *E = delimiter_cache[p_line].front(); E; E = E->next()) { + for (RBMap<int, int>::Element *E = delimiter_cache[p_line].front(); E; E = E->next()) { /* If column is specified, loop until the key is larger then the column. */ if (p_column != -1) { if (E->key() > p_column) { @@ -2691,6 +2739,13 @@ TypedArray<String> CodeEdit::_get_delimiters(DelimiterType p_type) const { } /* Code Completion */ +void CodeEdit::_update_scroll_selected_line(float p_mouse_y) { + float percent = (float)(p_mouse_y - code_completion_scroll_rect.position.y) / code_completion_scroll_rect.size.height; + percent = CLAMP(percent, 0.0f, 1.0f); + + code_completion_current_selected = (int)(percent * (code_completion_options.size() - 1)); +} + void CodeEdit::_filter_code_completion_candidates_impl() { int line_height = get_line_height(); @@ -2740,7 +2795,7 @@ void CodeEdit::_filter_code_completion_candidates_impl() { offset = line_height; } - max_width = MAX(max_width, font->get_string_size(option.display, font_size).width + offset); + max_width = MAX(max_width, font->get_string_size(option.display, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size).width + offset); code_completion_options.push_back(option); } @@ -2851,7 +2906,7 @@ void CodeEdit::_filter_code_completion_candidates_impl() { if (string_to_complete.length() == 0) { code_completion_options.push_back(option); - max_width = MAX(max_width, font->get_string_size(option.display, font_size).width + offset); + max_width = MAX(max_width, font->get_string_size(option.display, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size).width + offset); continue; } @@ -2957,7 +3012,7 @@ void CodeEdit::_filter_code_completion_candidates_impl() { option.matches.append_array(ssq_matches); completion_options_subseq.push_back(option); } - max_width = MAX(max_width, font->get_string_size(option.display, font_size).width + offset); + max_width = MAX(max_width, font->get_string_size(option.display, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size).width + offset); } else if (!*ssq_lower) { // Matched the whole subsequence in s_lower. option.matches.clear(); @@ -2974,7 +3029,7 @@ void CodeEdit::_filter_code_completion_candidates_impl() { option.matches.append_array(ssq_lower_matches); completion_options_subseq_casei.push_back(option); } - max_width = MAX(max_width, font->get_string_size(option.display, font_size).width + offset); + max_width = MAX(max_width, font->get_string_size(option.display, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size).width + offset); } } @@ -3030,12 +3085,14 @@ void CodeEdit::_text_changed() { } if (font.is_valid()) { - set_gutter_width(line_number_gutter, (line_number_digits + 1) * font->get_char_size('0', 0, font_size).width); + set_gutter_width(line_number_gutter, (line_number_digits + 1) * font->get_char_size('0', font_size).width); } lc = get_line_count(); List<int> breakpoints; - breakpointed_lines.get_key_list(&breakpoints); + for (const KeyValue<int, bool> &E : breakpointed_lines) { + breakpoints.push_back(E.key); + } for (const int &line : breakpoints) { if (line < lines_edited_from || (line < lc && is_line_breakpointed(line))) { continue; diff --git a/scene/gui/code_edit.h b/scene/gui/code_edit.h index 596a065f12..a431d8a5b2 100644 --- a/scene/gui/code_edit.h +++ b/scene/gui/code_edit.h @@ -58,7 +58,7 @@ private: String indent_text = "\t"; bool auto_indent = false; - Set<char32_t> auto_indent_prefixes; + HashSet<char32_t> auto_indent_prefixes; bool indent_using_spaces = false; int _calculate_spaces_till_next_left_indent(int p_column) const; @@ -176,7 +176,7 @@ private: * ] * ] */ - Vector<Map<int, int>> delimiter_cache; + Vector<RBMap<int, int>> delimiter_cache; void _update_delimiter_cache(int p_from_line = 0, int p_to_line = -1); int _is_in_delimiter(int p_line, int p_column, DelimiterType p_type) const; @@ -203,22 +203,27 @@ private: int code_completion_max_lines = 7; int code_completion_scroll_width = 0; Color code_completion_scroll_color = Color(0, 0, 0, 0); + Color code_completion_scroll_hovered_color = Color(0, 0, 0, 0); Color code_completion_background_color = Color(0, 0, 0, 0); Color code_completion_selected_color = Color(0, 0, 0, 0); Color code_completion_existing_color = Color(0, 0, 0, 0); bool code_completion_active = false; + bool is_code_completion_scroll_hovered = false; + bool is_code_completion_scroll_pressed = false; Vector<ScriptLanguage::CodeCompletionOption> code_completion_options; int code_completion_line_ofs = 0; int code_completion_current_selected = 0; int code_completion_longest_line = 0; Rect2i code_completion_rect; + Rect2i code_completion_scroll_rect; - Set<char32_t> code_completion_prefixes; + HashSet<char32_t> code_completion_prefixes; List<ScriptLanguage::CodeCompletionOption> code_completion_option_submitted; List<ScriptLanguage::CodeCompletionOption> code_completion_option_sources; String code_completion_base; + void _update_scroll_selected_line(float p_mouse_y); void _filter_code_completion_candidates_impl(); /* Line length guidelines */ @@ -398,7 +403,7 @@ public: void request_code_completion(bool p_force = false); - void add_code_completion_option(CodeCompletionKind p_type, const String &p_display_text, const String &p_insert_text, const Color &p_text_color = Color(1, 1, 1), const RES &p_icon = RES(), const Variant &p_value = Variant::NIL); + void add_code_completion_option(CodeCompletionKind p_type, const String &p_display_text, const String &p_insert_text, const Color &p_text_color = Color(1, 1, 1), const Ref<Resource> &p_icon = Ref<Resource>(), const Variant &p_value = Variant::NIL); void update_code_completion_options(bool p_forced = false); TypedArray<Dictionary> get_code_completion_options() const; diff --git a/scene/gui/color_mode.cpp b/scene/gui/color_mode.cpp new file mode 100644 index 0000000000..af78d67e5a --- /dev/null +++ b/scene/gui/color_mode.cpp @@ -0,0 +1,330 @@ +/*************************************************************************/ +/* color_mode.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 "color_mode.h" + +#include "core/math/color.h" +#include "scene/gui/slider.h" +#include "thirdparty/misc/ok_color.h" + +ColorMode::ColorMode(ColorPicker *p_color_picker) { + color_picker = p_color_picker; +} + +String ColorModeRGB::get_slider_label(int idx) const { + ERR_FAIL_INDEX_V_MSG(idx, 3, String(), "Couldn't get slider label."); + return labels[idx]; +} + +float ColorModeRGB::get_slider_max(int idx) const { + ERR_FAIL_INDEX_V_MSG(idx, 4, 0, "Couldn't get slider max value."); + Color color = color_picker->get_pick_color(); + return next_power_of_2(MAX(255, color.components[idx] * 255.0)) - 1; +} + +float ColorModeRGB::get_slider_value(int idx) const { + ERR_FAIL_INDEX_V_MSG(idx, 4, 0, "Couldn't get slider value."); + return color_picker->get_pick_color().components[idx] * 255; +} + +Color ColorModeRGB::get_color() const { + Vector<float> values = color_picker->get_active_slider_values(); + Color color; + for (int i = 0; i < 4; i++) { + color.components[i] = values[i] / 255.0; + } + return color; +} + +void ColorModeRGB::slider_draw(int p_which) { + Vector<Vector2> pos; + pos.resize(4); + Vector<Color> col; + col.resize(4); + HSlider *slider = color_picker->get_slider(p_which); + Size2 size = slider->get_size(); + Color left_color; + Color right_color; + Color color = color_picker->get_pick_color(); + const real_t margin = 4 * color_picker->get_theme_default_base_scale(); + + if (p_which == ColorPicker::SLIDER_COUNT) { + slider->draw_texture_rect(color_picker->get_theme_icon(SNAME("sample_bg"), SNAME("ColorPicker")), Rect2(Point2(0, margin), Size2(size.x, margin)), true); + + left_color = color; + left_color.a = 0; + right_color = color; + right_color.a = 1; + } else { + left_color = Color( + p_which == 0 ? 0 : color.r, + p_which == 1 ? 0 : color.g, + p_which == 2 ? 0 : color.b); + right_color = Color( + p_which == 0 ? 1 : color.r, + p_which == 1 ? 1 : color.g, + p_which == 2 ? 1 : color.b); + } + + col.set(0, left_color); + col.set(1, right_color); + col.set(2, right_color); + col.set(3, left_color); + pos.set(0, Vector2(0, margin)); + pos.set(1, Vector2(size.x, margin)); + pos.set(2, Vector2(size.x, margin * 2)); + pos.set(3, Vector2(0, margin * 2)); + + slider->draw_polygon(pos, col); +} + +String ColorModeHSV::get_slider_label(int idx) const { + ERR_FAIL_INDEX_V_MSG(idx, 3, String(), "Couldn't get slider label."); + return labels[idx]; +} + +float ColorModeHSV::get_slider_max(int idx) const { + ERR_FAIL_INDEX_V_MSG(idx, 4, 0, "Couldn't get slider max value."); + return slider_max[idx]; +} + +float ColorModeHSV::get_slider_value(int idx) const { + switch (idx) { + case 0: + return color_picker->get_pick_color().get_h() * 360.0; + case 1: + return color_picker->get_pick_color().get_s() * 100.0; + case 2: + return color_picker->get_pick_color().get_v() * 100.0; + case 3: + return Math::round(color_picker->get_pick_color().components[3] * 255.0); + default: + ERR_FAIL_V_MSG(0, "Couldn't get slider value."); + } +} + +Color ColorModeHSV::get_color() const { + Vector<float> values = color_picker->get_active_slider_values(); + Color color; + color.set_hsv(values[0] / 360.0, values[1] / 100.0, values[2] / 100.0, values[3] / 255.0); + return color; +} + +void ColorModeHSV::slider_draw(int p_which) { + Vector<Vector2> pos; + pos.resize(4); + Vector<Color> col; + col.resize(4); + HSlider *slider = color_picker->get_slider(p_which); + Size2 size = slider->get_size(); + Color left_color; + Color right_color; + Color color = color_picker->get_pick_color(); + const real_t margin = 4 * color_picker->get_theme_default_base_scale(); + + if (p_which == ColorPicker::SLIDER_COUNT) { + slider->draw_texture_rect(color_picker->get_theme_icon(SNAME("sample_bg"), SNAME("ColorPicker")), Rect2(Point2(0, margin), Size2(size.x, margin)), true); + + left_color = color; + left_color.a = 0; + right_color = color; + right_color.a = 1; + } else if (p_which == 0) { + Ref<Texture2D> hue = color_picker->get_theme_icon(SNAME("color_hue"), SNAME("ColorPicker")); + slider->draw_set_transform(Point2(), -Math_PI / 2, Size2(1.0, 1.0)); + slider->draw_texture_rect(hue, Rect2(Vector2(margin * -2, 0), Vector2(slider->get_size().x, margin)), false, Color(1, 1, 1), true); + return; + } else { + Color s_col; + Color v_col; + s_col.set_hsv(color.get_h(), 0, color.get_v()); + left_color = (p_which == 1) ? s_col : Color(0, 0, 0); + s_col.set_hsv(color.get_h(), 1, color.get_v()); + v_col.set_hsv(color.get_h(), color.get_s(), 1); + right_color = (p_which == 1) ? s_col : v_col; + } + col.set(0, left_color); + col.set(1, right_color); + col.set(2, right_color); + col.set(3, left_color); + pos.set(0, Vector2(0, margin)); + pos.set(1, Vector2(size.x, margin)); + pos.set(2, Vector2(size.x, margin * 2)); + pos.set(3, Vector2(0, margin * 2)); + + slider->draw_polygon(pos, col); +} + +String ColorModeRAW::get_slider_label(int idx) const { + ERR_FAIL_INDEX_V_MSG(idx, 3, String(), "Couldn't get slider label."); + return labels[idx]; +} + +float ColorModeRAW::get_slider_max(int idx) const { + ERR_FAIL_INDEX_V_MSG(idx, 4, 0, "Couldn't get slider max value."); + return slider_max[idx]; +} + +float ColorModeRAW::get_slider_value(int idx) const { + ERR_FAIL_INDEX_V_MSG(idx, 4, 0, "Couldn't get slider value."); + return color_picker->get_pick_color().components[idx]; +} + +Color ColorModeRAW::get_color() const { + Vector<float> values = color_picker->get_active_slider_values(); + Color color; + for (int i = 0; i < 4; i++) { + color.components[i] = values[i]; + } + return color; +} + +void ColorModeRAW::slider_draw(int p_which) { + Vector<Vector2> pos; + pos.resize(4); + Vector<Color> col; + col.resize(4); + HSlider *slider = color_picker->get_slider(p_which); + Size2 size = slider->get_size(); + Color left_color; + Color right_color; + Color color = color_picker->get_pick_color(); + const real_t margin = 4 * color_picker->get_theme_default_base_scale(); + + if (p_which == ColorPicker::SLIDER_COUNT) { + slider->draw_texture_rect(color_picker->get_theme_icon(SNAME("sample_bg"), SNAME("ColorPicker")), Rect2(Point2(0, margin), Size2(size.x, margin)), true); + + left_color = color; + left_color.a = 0; + right_color = color; + right_color.a = 1; + + col.set(0, left_color); + col.set(1, right_color); + col.set(2, right_color); + col.set(3, left_color); + pos.set(0, Vector2(0, margin)); + pos.set(1, Vector2(size.x, margin)); + pos.set(2, Vector2(size.x, margin * 2)); + pos.set(3, Vector2(0, margin * 2)); + + slider->draw_polygon(pos, col); + } +} + +bool ColorModeRAW::apply_theme() const { + for (int i = 0; i < 4; i++) { + HSlider *slider = color_picker->get_slider(i); + slider->remove_theme_icon_override("grabber"); + slider->remove_theme_icon_override("grabber_highlight"); + slider->remove_theme_style_override("slider"); + slider->remove_theme_style_override("grabber_area"); + slider->remove_theme_style_override("grabber_area_highlight"); + } + + return true; +} + +String ColorModeOKHSL::get_slider_label(int idx) const { + ERR_FAIL_INDEX_V_MSG(idx, 3, String(), "Couldn't get slider label."); + return labels[idx]; +} + +float ColorModeOKHSL::get_slider_max(int idx) const { + ERR_FAIL_INDEX_V_MSG(idx, 4, 0, "Couldn't get slider max value."); + return slider_max[idx]; +} + +float ColorModeOKHSL::get_slider_value(int idx) const { + switch (idx) { + case 0: + return color_picker->get_pick_color().get_ok_hsl_h() * 360.0; + case 1: + return color_picker->get_pick_color().get_ok_hsl_s() * 100.0; + case 2: + return color_picker->get_pick_color().get_ok_hsl_l() * 100.0; + case 3: + return Math::round(color_picker->get_pick_color().components[3] * 255.0); + default: + ERR_FAIL_V_MSG(0, "Couldn't get slider value."); + } +} + +Color ColorModeOKHSL::get_color() const { + Vector<float> values = color_picker->get_active_slider_values(); + Color color; + color.set_ok_hsl(values[0] / 360.0, values[1] / 100.0, values[2] / 100.0, values[3] / 255.0); + return color; +} + +void ColorModeOKHSL::slider_draw(int p_which) { + Vector<Vector2> pos; + pos.resize(4); + Vector<Color> col; + col.resize(4); + HSlider *slider = color_picker->get_slider(p_which); + Size2 size = slider->get_size(); + Color left_color; + Color right_color; + Color color = color_picker->get_pick_color(); + const real_t margin = 4 * color_picker->get_theme_default_base_scale(); + + if (p_which == ColorPicker::SLIDER_COUNT) { + slider->draw_texture_rect(color_picker->get_theme_icon(SNAME("sample_bg"), SNAME("ColorPicker")), Rect2(Point2(0, margin), Size2(size.x, margin)), true); + + left_color = color; + left_color.a = 0; + right_color = color; + right_color.a = 1; + } else if (p_which == 0) { + Ref<Texture2D> hue = color_picker->get_theme_icon(SNAME("color_hue"), SNAME("ColorPicker")); + slider->draw_set_transform(Point2(), -Math_PI / 2, Size2(1.0, 1.0)); + slider->draw_texture_rect(hue, Rect2(Vector2(margin * -2, 0), Vector2(slider->get_size().x, margin)), false, Color(1, 1, 1), true); + return; + } else { + Color s_col; + Color v_col; + s_col.set_ok_hsl(color.get_h(), 0, color.get_v()); + left_color = (p_which == 1) ? s_col : Color(0, 0, 0); + s_col.set_ok_hsl(color.get_h(), 1, color.get_v()); + v_col.set_ok_hsl(color.get_h(), color.get_s(), 1); + right_color = (p_which == 1) ? s_col : v_col; + } + col.set(0, left_color); + col.set(1, right_color); + col.set(2, right_color); + col.set(3, left_color); + pos.set(0, Vector2(0, margin)); + pos.set(1, Vector2(size.x, margin)); + pos.set(2, Vector2(size.x, margin * 2)); + pos.set(3, Vector2(0, margin * 2)); + + slider->draw_polygon(pos, col); +} diff --git a/scene/gui/color_mode.h b/scene/gui/color_mode.h new file mode 100644 index 0000000000..8a19699c40 --- /dev/null +++ b/scene/gui/color_mode.h @@ -0,0 +1,143 @@ +/*************************************************************************/ +/* color_mode.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 COLOR_MODE_H +#define COLOR_MODE_H + +#include "scene/gui/color_picker.h" + +struct Color; + +class ColorMode { +public: + ColorPicker *color_picker = nullptr; + + virtual String get_name() const = 0; + + virtual int get_slider_count() const { return 3; }; + virtual float get_slider_step() const = 0; + virtual String get_slider_label(int idx) const = 0; + virtual float get_slider_max(int idx) const = 0; + virtual float get_slider_value(int idx) const = 0; + + virtual Color get_color() const = 0; + + virtual void slider_draw(int p_which) = 0; + virtual bool apply_theme() const { return false; } + virtual ColorPicker::PickerShapeType get_shape_override() const { return ColorPicker::SHAPE_MAX; } + + ColorMode(ColorPicker *p_color_picker); + virtual ~ColorMode(){}; +}; + +class ColorModeHSV : public ColorMode { +public: + String labels[3] = { "H", "S", "V" }; + float slider_max[4] = { 359, 100, 100, 255 }; + + virtual String get_name() const override { return "HSV"; } + + virtual float get_slider_step() const override { return 1.0; } + virtual String get_slider_label(int idx) const override; + virtual float get_slider_max(int idx) const override; + virtual float get_slider_value(int idx) const override; + + virtual Color get_color() const override; + + virtual void slider_draw(int p_which) override; + + ColorModeHSV(ColorPicker *p_color_picker) : + ColorMode(p_color_picker){}; +}; + +class ColorModeRGB : public ColorMode { +public: + String labels[3] = { "R", "G", "B" }; + + virtual String get_name() const override { return "RGB"; } + + virtual float get_slider_step() const override { return 1; } + virtual String get_slider_label(int idx) const override; + virtual float get_slider_max(int idx) const override; + virtual float get_slider_value(int idx) const override; + + virtual Color get_color() const override; + + virtual void slider_draw(int p_which) override; + + ColorModeRGB(ColorPicker *p_color_picker) : + ColorMode(p_color_picker){}; +}; + +class ColorModeRAW : public ColorMode { +public: + String labels[3] = { "R", "G", "B" }; + float slider_max[4] = { 100, 100, 100, 1 }; + + virtual String get_name() const override { return "RAW"; } + + virtual float get_slider_step() const override { return 0.01; } + virtual String get_slider_label(int idx) const override; + virtual float get_slider_max(int idx) const override; + virtual float get_slider_value(int idx) const override; + + virtual Color get_color() const override; + + virtual void slider_draw(int p_which) override; + virtual bool apply_theme() const override; + + ColorModeRAW(ColorPicker *p_color_picker) : + ColorMode(p_color_picker){}; +}; + +class ColorModeOKHSL : public ColorMode { +public: + String labels[3] = { "H", "S", "L" }; + float slider_max[4] = { 359, 100, 100, 255 }; + + virtual String get_name() const override { return "OKHSL"; } + + virtual float get_slider_step() const override { return 1.0; } + virtual String get_slider_label(int idx) const override; + virtual float get_slider_max(int idx) const override; + virtual float get_slider_value(int idx) const override; + + virtual Color get_color() const override; + + virtual void slider_draw(int p_which) override; + virtual ColorPicker::PickerShapeType get_shape_override() const override { return ColorPicker::SHAPE_OKHSL_CIRCLE; } + + ColorModeOKHSL(ColorPicker *p_color_picker) : + ColorMode(p_color_picker){}; + + ~ColorModeOKHSL(){}; +}; + +#endif // COLOR_MODE_H diff --git a/scene/gui/color_picker.cpp b/scene/gui/color_picker.cpp index 48fadb0cf8..bfe5ee335b 100644 --- a/scene/gui/color_picker.cpp +++ b/scene/gui/color_picker.cpp @@ -31,14 +31,18 @@ #include "color_picker.h" #include "core/input/input.h" +#include "core/math/color.h" #include "core/os/keyboard.h" #include "core/os/os.h" -#include "scene/main/window.h" +#include "scene/gui/color_mode.h" #ifdef TOOLS_ENABLED #include "editor/editor_settings.h" #endif +#include "thirdparty/misc/ok_color.h" +#include "thirdparty/misc/ok_color_shader.h" + List<Color> ColorPicker::preset_cache; void ColorPicker::_notification(int p_what) { @@ -71,10 +75,14 @@ void ColorPicker::_notification(int p_what) { wheel_edit->set_custom_minimum_size(Size2(get_theme_constant(SNAME("sv_width")), get_theme_constant(SNAME("sv_height")))); wheel_margin->add_theme_constant_override("margin_bottom", 8 * get_theme_default_base_scale()); - for (int i = 0; i < 4; i++) { + for (int i = 0; i < SLIDER_COUNT; i++) { labels[i]->set_custom_minimum_size(Size2(get_theme_constant(SNAME("label_width")), 0)); set_offset((Side)i, get_offset((Side)i) + get_theme_constant(SNAME("margin"))); } + alpha_label->set_custom_minimum_size(Size2(get_theme_constant(SNAME("label_width")), 0)); + set_offset((Side)0, get_offset((Side)0) + get_theme_constant(SNAME("margin"))); + + _reset_theme(); if (Engine::get_singleton()->is_editor_hint()) { // Adjust for the width of the "Script" icon. @@ -102,6 +110,7 @@ void ColorPicker::_notification(int p_what) { Ref<Shader> ColorPicker::wheel_shader; Ref<Shader> ColorPicker::circle_shader; +Ref<Shader> ColorPicker::circle_ok_color_shader; void ColorPicker::init_shaders() { wheel_shader.instantiate(); @@ -152,11 +161,36 @@ void fragment() { COLOR = vec4(mix(vec3(1.0), clamp(abs(fract(vec3((a - TAU) / TAU) + vec3(1.0, 2.0 / 3.0, 1.0 / 3.0)) * 6.0 - vec3(3.0)) - vec3(1.0), 0.0, 1.0), ((float(sqrt(x * x + y * y)) * 2.0)) / 1.0) * vec3(v), (b + b2 + b3 + b4) / 4.00); })"); + + circle_ok_color_shader.instantiate(); + circle_ok_color_shader->set_code(OK_COLOR_SHADER + R"( +// ColorPicker ok color hsv circle shader. + +uniform float v = 1.0; + +void fragment() { + float x = UV.x - 0.5; + float y = UV.y - 0.5; + x += 0.001; + y += 0.001; + float b = float(sqrt(x * x + y * y) < 0.5); + x -= 0.002; + float b2 = float(sqrt(x * x + y * y) < 0.5); + y -= 0.002; + float b3 = float(sqrt(x * x + y * y) < 0.5); + x += 0.002; + float b4 = float(sqrt(x * x + y * y) < 0.5); + float s = sqrt(x * x + y * y); + float h = atan(y, x) / (2.0*M_PI); + vec3 col = okhsl_to_srgb(vec3(h, s, v)); + COLOR = vec4(col, (b + b2 + b3 + b4) / 4.00); +})"); } void ColorPicker::finish_shaders() { wheel_shader.unref(); circle_shader.unref(); + circle_ok_color_shader.unref(); } void ColorPicker::set_focus_on_line_edit() { @@ -164,62 +198,38 @@ void ColorPicker::set_focus_on_line_edit() { } void ColorPicker::_update_controls() { - const char *rgb[3] = { "R", "G", "B" }; - const char *hsv[3] = { "H", "S", "V" }; + int mode_sliders_count = modes[current_mode]->get_slider_count(); - if (hsv_mode_enabled) { - for (int i = 0; i < 3; i++) { - labels[i]->set_text(hsv[i]); - } - } else { - for (int i = 0; i < 3; i++) { - labels[i]->set_text(rgb[i]); - } + for (int i = current_slider_count; i < mode_sliders_count; i++) { + sliders[i]->show(); + labels[i]->show(); + values[i]->show(); } - - if (hsv_mode_enabled) { - set_raw_mode(false); - btn_raw->set_disabled(true); - } else if (raw_mode_enabled) { - set_hsv_mode(false); - btn_hsv->set_disabled(true); - } else { - btn_raw->set_disabled(false); - btn_hsv->set_disabled(false); + for (int i = mode_sliders_count; i < current_slider_count; i++) { + sliders[i]->hide(); + labels[i]->hide(); + values[i]->hide(); } + current_slider_count = mode_sliders_count; - if (raw_mode_enabled) { - for (int i = 0; i < 3; i++) { - scroll[i]->remove_theme_icon_override("grabber"); - scroll[i]->remove_theme_icon_override("grabber_highlight"); - scroll[i]->remove_theme_style_override("slider"); - scroll[i]->remove_theme_style_override("grabber_area"); - scroll[i]->remove_theme_style_override("grabber_area_highlight"); - } - } else { - Ref<StyleBoxEmpty> style_box_empty(memnew(StyleBoxEmpty)); - Ref<Texture2D> bar_arrow = get_theme_icon(SNAME("bar_arrow")); - - for (int i = 0; i < 4; i++) { - scroll[i]->add_theme_icon_override("grabber", bar_arrow); - scroll[i]->add_theme_icon_override("grabber_highlight", bar_arrow); - scroll[i]->add_theme_style_override("slider", style_box_empty); - scroll[i]->add_theme_style_override("grabber_area", style_box_empty); - scroll[i]->add_theme_style_override("grabber_area_highlight", style_box_empty); - } + for (int i = 0; i < current_slider_count; i++) { + labels[i]->set_text(modes[current_mode]->get_slider_label(i)); } + alpha_label->set_text("A"); + + slider_theme_modified = modes[current_mode]->apply_theme(); if (edit_alpha) { - values[3]->show(); - scroll[3]->show(); - labels[3]->show(); + alpha_value->show(); + alpha_slider->show(); + alpha_label->show(); } else { - values[3]->hide(); - scroll[3]->hide(); - labels[3]->hide(); + alpha_value->hide(); + alpha_slider->hide(); + alpha_label->hide(); } - switch (picker_type) { + switch (_get_actual_shape()) { case SHAPE_HSV_RECTANGLE: wheel_edit->hide(); w_edit->show(); @@ -236,8 +246,15 @@ void ColorPicker::_update_controls() { wheel_edit->show(); w_edit->show(); uv_edit->hide(); - wheel->set_material(circle_mat); + circle_mat->set_shader(circle_shader); + break; + case SHAPE_OKHSL_CIRCLE: + wheel_edit->show(); + w_edit->show(); + uv_edit->hide(); + wheel->set_material(circle_mat); + circle_mat->set_shader(circle_ok_color_shader); break; default: { } @@ -246,11 +263,17 @@ void ColorPicker::_update_controls() { void ColorPicker::_set_pick_color(const Color &p_color, bool p_update_sliders) { color = p_color; - if (color != last_hsv) { - h = color.get_h(); - s = color.get_s(); - v = color.get_v(); - last_hsv = color; + if (color != last_color) { + if (_get_actual_shape() == SHAPE_OKHSL_CIRCLE) { + h = color.get_ok_hsl_h(); + s = color.get_ok_hsl_s(); + v = color.get_ok_hsl_l(); + } else { + h = color.get_h(); + s = color.get_s(); + v = color.get_v(); + } + last_color = color; } if (!is_inside_tree()) { @@ -297,24 +320,93 @@ void ColorPicker::_value_changed(double) { return; } - if (hsv_mode_enabled) { - h = scroll[0]->get_value() / 360.0; - s = scroll[1]->get_value() / 100.0; - v = scroll[2]->get_value() / 100.0; - color.set_hsv(h, s, v, scroll[3]->get_value() / 255.0); - - last_hsv = color; + color = modes[current_mode]->get_color(); - } else { - for (int i = 0; i < 4; i++) { - color.components[i] = scroll[i]->get_value() / (raw_mode_enabled ? 1.0 : 255.0); - } + if (current_mode == MODE_HSV || current_mode == MODE_OKHSL) { + h = sliders[0]->get_value() / 360.0; + s = sliders[1]->get_value() / 100.0; + v = sliders[2]->get_value() / 100.0; + last_color = color; } _set_pick_color(color, false); emit_signal(SNAME("color_changed"), color); } +void ColorPicker::add_mode(ColorMode *p_mode) { + modes.push_back(p_mode); + mode_option_button->add_item(RTR(p_mode->get_name())); +} + +void ColorPicker::create_slider(GridContainer *gc, int idx) { + Label *l = memnew(Label()); + l->set_v_size_flags(SIZE_SHRINK_CENTER); + gc->add_child(l); + + HSlider *s = memnew(HSlider); + s->set_v_size_flags(SIZE_SHRINK_CENTER); + s->set_focus_mode(FOCUS_NONE); + gc->add_child(s); + + SpinBox *v = memnew(SpinBox); + s->share(v); + gc->add_child(v); + v->get_line_edit()->connect("focus_entered", callable_mp(this, &ColorPicker::_focus_enter)); + v->get_line_edit()->connect("focus_exited", callable_mp(this, &ColorPicker::_focus_exit)); + + s->set_h_size_flags(SIZE_EXPAND_FILL); + + s->connect("value_changed", callable_mp(this, &ColorPicker::_value_changed)); + s->connect("draw", callable_mp(this, &ColorPicker::_slider_draw), make_binds(idx)); + + if (idx < SLIDER_COUNT) { + sliders[idx] = s; + values[idx] = v; + labels[idx] = l; + } else { + alpha_slider = s; + alpha_value = v; + alpha_label = l; + } +} + +HSlider *ColorPicker::get_slider(int idx) { + if (idx < SLIDER_COUNT) { + return sliders[idx]; + } + return alpha_slider; +} + +Vector<float> ColorPicker::get_active_slider_values() { + Vector<float> values; + for (int i = 0; i < current_slider_count; i++) { + values.push_back(sliders[i]->get_value()); + } + values.push_back(alpha_slider->get_value()); + return values; +} + +ColorPicker::PickerShapeType ColorPicker::_get_actual_shape() const { + return modes[current_mode]->get_shape_override() != SHAPE_MAX ? modes[current_mode]->get_shape_override() : current_shape; +} + +void ColorPicker::_reset_theme() { + Ref<StyleBoxEmpty> style_box_empty(memnew(StyleBoxEmpty)); + + for (int i = 0; i < SLIDER_COUNT; i++) { + sliders[i]->add_theme_icon_override("grabber", get_theme_icon(SNAME("bar_arrow"), SNAME("ColorPicker"))); + sliders[i]->add_theme_icon_override("grabber_highlight", get_theme_icon(SNAME("bar_arrow"), SNAME("ColorPicker"))); + sliders[i]->add_theme_style_override("slider", style_box_empty); + sliders[i]->add_theme_style_override("grabber_area", style_box_empty); + sliders[i]->add_theme_style_override("grabber_area_highlight", style_box_empty); + } + alpha_slider->add_theme_icon_override("grabber", get_theme_icon(SNAME("bar_arrow"), SNAME("ColorPicker"))); + alpha_slider->add_theme_icon_override("grabber_highlight", get_theme_icon(SNAME("bar_arrow"), SNAME("ColorPicker"))); + alpha_slider->add_theme_style_override("slider", style_box_empty); + alpha_slider->add_theme_style_override("grabber_area", style_box_empty); + alpha_slider->add_theme_style_override("grabber_area_highlight", style_box_empty); +} + void ColorPicker::_html_submitted(const String &p_html) { if (updating || text_is_constructor || !c_text->is_visible()) { return; @@ -338,36 +430,15 @@ void ColorPicker::_update_color(bool p_update_sliders) { updating = true; if (p_update_sliders) { - if (hsv_mode_enabled) { - for (int i = 0; i < 4; i++) { - scroll[i]->set_step(1.0); - } - - scroll[0]->set_max(359); - scroll[0]->set_value(h * 360.0); - scroll[1]->set_max(100); - scroll[1]->set_value(s * 100.0); - scroll[2]->set_max(100); - scroll[2]->set_value(v * 100.0); - scroll[3]->set_max(255); - scroll[3]->set_value(color.components[3] * 255.0); - } else { - for (int i = 0; i < 4; i++) { - if (raw_mode_enabled) { - scroll[i]->set_step(0.01); - scroll[i]->set_max(100); - if (i == 3) { - scroll[i]->set_max(1); - } - scroll[i]->set_value(color.components[i]); - } else { - scroll[i]->set_step(1); - const float byte_value = color.components[i] * 255.0; - scroll[i]->set_max(next_power_of_2(MAX(255, byte_value)) - 1); - scroll[i]->set_value(byte_value); - } - } + float step = modes[current_mode]->get_slider_step(); + for (int i = 0; i < current_slider_count; i++) { + sliders[i]->set_max(modes[current_mode]->get_slider_max(i)); + sliders[i]->set_step(step); + sliders[i]->set_value(modes[current_mode]->get_slider_value(i)); } + alpha_slider->set_max(modes[current_mode]->get_slider_max(current_slider_count)); + alpha_slider->set_step(step); + alpha_slider->set_value(modes[current_mode]->get_slider_value(current_slider_count)); } _update_text_value(); @@ -375,9 +446,10 @@ void ColorPicker::_update_color(bool p_update_sliders) { sample->update(); uv_edit->update(); w_edit->update(); - for (int i = 0; i < 4; i++) { - scroll[i]->update(); + for (int i = 0; i < current_slider_count; i++) { + sliders[i]->update(); } + alpha_slider->update(); wheel->update(); wheel_uv->update(); updating = false; @@ -423,20 +495,20 @@ Color ColorPicker::get_pick_color() const { return color; } -void ColorPicker::set_picker_shape(PickerShapeType p_picker_type) { - ERR_FAIL_INDEX(p_picker_type, SHAPE_MAX); - picker_type = p_picker_type; +void ColorPicker::set_picker_shape(PickerShapeType p_shape) { + ERR_FAIL_INDEX(p_shape, SHAPE_MAX); + current_shape = p_shape; _update_controls(); _update_color(); } ColorPicker::PickerShapeType ColorPicker::get_picker_shape() const { - return picker_type; + return current_shape; } inline int ColorPicker::_get_preset_size() { - return (int(get_minimum_size().width) - (preset_container->get_theme_constant(SNAME("hseparation")) * (preset_column_count - 1))) / preset_column_count; + return (int(get_minimum_size().width) - (preset_container->get_theme_constant(SNAME("h_separation")) * (preset_column_count - 1))) / preset_column_count; } void ColorPicker::_add_preset_button(int p_size, const Color &p_color) { @@ -448,6 +520,21 @@ void ColorPicker::_add_preset_button(int p_size, const Color &p_color) { preset_container->add_child(btn_preset); } +void ColorPicker::_set_color_mode(ColorModeType p_mode) { + if (slider_theme_modified) { + _reset_theme(); + } + + current_mode = p_mode; + + if (!is_inside_tree()) { + return; + } + + _update_controls(); + _update_color(); +} + void ColorPicker::add_preset(const Color &p_color) { if (presets.find(p_color)) { presets.move_to_back(presets.find(p_color)); @@ -507,46 +594,14 @@ PackedColorArray ColorPicker::get_presets() const { return arr; } -void ColorPicker::set_hsv_mode(bool p_enabled) { - if (hsv_mode_enabled == p_enabled || raw_mode_enabled) { - return; - } - hsv_mode_enabled = p_enabled; - if (btn_hsv->is_pressed() != p_enabled) { - btn_hsv->set_pressed(p_enabled); - } - - if (!is_inside_tree()) { - return; - } - - _update_controls(); - _update_color(); +void ColorPicker::set_color_mode(ColorModeType p_mode) { + ERR_FAIL_INDEX(p_mode, MODE_MAX); + mode_option_button->select(p_mode); + _set_color_mode(p_mode); } -bool ColorPicker::is_hsv_mode() const { - return hsv_mode_enabled; -} - -void ColorPicker::set_raw_mode(bool p_enabled) { - if (raw_mode_enabled == p_enabled || hsv_mode_enabled) { - return; - } - raw_mode_enabled = p_enabled; - if (btn_raw->is_pressed() != p_enabled) { - btn_raw->set_pressed(p_enabled); - } - - if (!is_inside_tree()) { - return; - } - - _update_controls(); - _update_color(); -} - -bool ColorPicker::is_raw_mode() const { - return raw_mode_enabled; +ColorPicker::ColorModeType ColorPicker::get_color_mode() const { + return current_mode; } void ColorPicker::set_deferred_mode(bool p_enabled) { @@ -633,6 +688,8 @@ void ColorPicker::_hsv_draw(int p_which, Control *c) { if (!c) { return; } + + PickerShapeType actual_shape = _get_actual_shape(); if (p_which == 0) { Vector<Point2> points; Vector<Color> colors; @@ -640,7 +697,7 @@ void ColorPicker::_hsv_draw(int p_which, Control *c) { Color col = color; Vector2 center = c->get_size() / 2.0; - switch (picker_type) { + switch (actual_shape) { case SHAPE_HSV_WHEEL: { points.resize(4); colors.resize(4); @@ -702,7 +759,7 @@ void ColorPicker::_hsv_draw(int p_which, Control *c) { Ref<Texture2D> cursor = get_theme_icon(SNAME("picker_cursor"), SNAME("ColorPicker")); int x; int y; - if (picker_type == SHAPE_VHS_CIRCLE) { + if (actual_shape == SHAPE_VHS_CIRCLE || actual_shape == SHAPE_OKHSL_CIRCLE) { x = center.x + (center.x * Math::cos(h * Math_TAU) * s) - (cursor->get_width() / 2); y = center.y + (center.y * Math::sin(h * Math_TAU) * s) - (cursor->get_height() / 2); } else { @@ -716,7 +773,7 @@ void ColorPicker::_hsv_draw(int p_which, Control *c) { c->draw_texture(cursor, Point2(x, y)); col.set_hsv(h, 1, 1); - if (picker_type == SHAPE_HSV_WHEEL) { + if (actual_shape == SHAPE_HSV_WHEEL) { points.resize(4); double h1 = h - (0.5 / 360); double h2 = h + (0.5 / 360); @@ -728,14 +785,33 @@ void ColorPicker::_hsv_draw(int p_which, Control *c) { } } else if (p_which == 1) { - if (picker_type == SHAPE_HSV_RECTANGLE) { + if (actual_shape == SHAPE_HSV_RECTANGLE) { Ref<Texture2D> hue = get_theme_icon(SNAME("color_hue"), SNAME("ColorPicker")); c->draw_texture_rect(hue, Rect2(Point2(), c->get_size())); int y = c->get_size().y - c->get_size().y * (1.0 - h); Color col; col.set_hsv(h, 1, 1); c->draw_line(Point2(0, y), Point2(c->get_size().x, y), col.inverted()); - } else if (picker_type == SHAPE_VHS_CIRCLE) { + } else if (actual_shape == SHAPE_OKHSL_CIRCLE) { + Vector<Point2> points; + Vector<Color> colors; + Color col; + col.set_ok_hsl(h, s, 1); + points.resize(4); + colors.resize(4); + points.set(0, Vector2()); + points.set(1, Vector2(c->get_size().x, 0)); + points.set(2, c->get_size()); + points.set(3, Vector2(0, c->get_size().y)); + colors.set(0, col); + colors.set(1, col); + colors.set(2, Color(0, 0, 0)); + colors.set(3, Color(0, 0, 0)); + c->draw_polygon(points, colors); + int y = c->get_size().y - c->get_size().y * CLAMP(v, 0, 1); + col.set_ok_hsl(h, 1, v); + c->draw_line(Point2(0, y), Point2(c->get_size().x, y), col.inverted()); + } else if (actual_shape == SHAPE_VHS_CIRCLE) { Vector<Point2> points; Vector<Color> colors; Color col; @@ -757,80 +833,25 @@ void ColorPicker::_hsv_draw(int p_which, Control *c) { } } else if (p_which == 2) { c->draw_rect(Rect2(Point2(), c->get_size()), Color(1, 1, 1)); - if (picker_type == SHAPE_VHS_CIRCLE) { + if (actual_shape == SHAPE_VHS_CIRCLE || actual_shape == SHAPE_OKHSL_CIRCLE) { circle_mat->set_shader_param("v", v); } } } void ColorPicker::_slider_draw(int p_which) { - Vector<Vector2> pos; - pos.resize(4); - Vector<Color> col; - col.resize(4); - Size2 size = scroll[p_which]->get_size(); - Color left_color; - Color right_color; - const real_t margin = 4 * get_theme_default_base_scale(); - - if (p_which == 3) { - scroll[p_which]->draw_texture_rect(get_theme_icon(SNAME("sample_bg"), SNAME("ColorPicker")), Rect2(Point2(0, margin), Size2(size.x, margin)), true); - - left_color = color; - left_color.a = 0; - right_color = color; - right_color.a = 1; - } else { - if (raw_mode_enabled) { - return; - } - if (hsv_mode_enabled) { - if (p_which == 0) { - Ref<Texture2D> hue = get_theme_icon(SNAME("color_hue"), SNAME("ColorPicker")); - scroll[p_which]->draw_set_transform(Point2(), -Math_PI / 2, Size2(1.0, 1.0)); - scroll[p_which]->draw_texture_rect(hue, Rect2(Vector2(margin * -2, 0), Vector2(scroll[p_which]->get_size().x, margin)), false, Color(1, 1, 1), true); - return; - } - Color s_col; - Color v_col; - s_col.set_hsv(h, 0, v); - left_color = (p_which == 1) ? s_col : Color(0, 0, 0); - s_col.set_hsv(h, 1, v); - v_col.set_hsv(h, s, 1); - right_color = (p_which == 1) ? s_col : v_col; - } else { - left_color = Color( - p_which == 0 ? 0 : color.r, - p_which == 1 ? 0 : color.g, - p_which == 2 ? 0 : color.b); - right_color = Color( - p_which == 0 ? 1 : color.r, - p_which == 1 ? 1 : color.g, - p_which == 2 ? 1 : color.b); - } - } - - col.set(0, left_color); - col.set(1, right_color); - col.set(2, right_color); - col.set(3, left_color); - pos.set(0, Vector2(0, margin)); - pos.set(1, Vector2(size.x, margin)); - pos.set(2, Vector2(size.x, margin * 2)); - pos.set(3, Vector2(0, margin * 2)); - - scroll[p_which]->draw_polygon(pos, col); + modes[current_mode]->slider_draw(p_which); } void ColorPicker::_uv_input(const Ref<InputEvent> &p_event, Control *c) { Ref<InputEventMouseButton> bev = p_event; + PickerShapeType current_picker = _get_actual_shape(); if (bev.is_valid()) { if (bev->is_pressed() && bev->get_button_index() == MouseButton::LEFT) { Vector2 center = c->get_size() / 2.0; - if (picker_type == SHAPE_VHS_CIRCLE) { + if (current_picker == SHAPE_VHS_CIRCLE || current_picker == SHAPE_OKHSL_CIRCLE) { real_t dist = center.distance_to(bev->get_position()); - if (dist <= center.x) { real_t rad = center.angle_to_point(bev->get_position()); h = ((rad >= 0) ? rad : (Math_TAU + rad)) / Math_TAU; @@ -867,8 +888,14 @@ void ColorPicker::_uv_input(const Ref<InputEvent> &p_event, Control *c) { } } changing_color = true; - color.set_hsv(h, s, v, color.a); - last_hsv = color; + if (current_picker == SHAPE_OKHSL_CIRCLE) { + color.set_ok_hsl(h, s, v, color.a); + } else { + color.set_hsv(h, s, v, color.a); + } + + last_color = color; + set_pick_color(color); _update_color(); if (!deferred_mode_enabled) { @@ -892,7 +919,7 @@ void ColorPicker::_uv_input(const Ref<InputEvent> &p_event, Control *c) { } Vector2 center = c->get_size() / 2.0; - if (picker_type == SHAPE_VHS_CIRCLE) { + if (current_picker == SHAPE_VHS_CIRCLE || current_picker == SHAPE_OKHSL_CIRCLE) { real_t dist = center.distance_to(mev->get_position()); real_t rad = center.angle_to_point(mev->get_position()); h = ((rad >= 0) ? rad : (Math_TAU + rad)) / Math_TAU; @@ -913,9 +940,12 @@ void ColorPicker::_uv_input(const Ref<InputEvent> &p_event, Control *c) { v = 1.0 - (y - corner_y) / real_size.y; } } - - color.set_hsv(h, s, v, color.a); - last_hsv = color; + if (current_picker != SHAPE_OKHSL_CIRCLE) { + color.set_hsv(h, s, v, color.a); + } else { + color.set_ok_hsl(h, s, v, color.a); + } + last_color = color; set_pick_color(color); _update_color(); if (!deferred_mode_enabled) { @@ -926,12 +956,13 @@ void ColorPicker::_uv_input(const Ref<InputEvent> &p_event, Control *c) { void ColorPicker::_w_input(const Ref<InputEvent> &p_event) { Ref<InputEventMouseButton> bev = p_event; + PickerShapeType actual_shape = _get_actual_shape(); if (bev.is_valid()) { 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) { + if (actual_shape == SHAPE_VHS_CIRCLE || actual_shape == SHAPE_OKHSL_CIRCLE) { v = 1.0 - (y / w_edit->get_size().height); } else { h = y / w_edit->get_size().height; @@ -939,8 +970,12 @@ void ColorPicker::_w_input(const Ref<InputEvent> &p_event) { } else { changing_color = false; } - color.set_hsv(h, s, v, color.a); - last_hsv = color; + if (actual_shape != SHAPE_OKHSL_CIRCLE) { + color.set_hsv(h, s, v, color.a); + } else { + color.set_ok_hsl(h, s, v, color.a); + } + last_color = color; set_pick_color(color); _update_color(); if (!deferred_mode_enabled) { @@ -957,13 +992,19 @@ void ColorPicker::_w_input(const Ref<InputEvent> &p_event) { return; } float y = CLAMP((float)mev->get_position().y, 0, w_edit->get_size().height); - if (picker_type == SHAPE_VHS_CIRCLE) { + if (actual_shape == SHAPE_VHS_CIRCLE || actual_shape == SHAPE_OKHSL_CIRCLE) { v = 1.0 - (y / w_edit->get_size().height); } else { h = y / w_edit->get_size().height; } - color.set_hsv(h, s, v, color.a); - last_hsv = color; + + if (current_mode == MODE_HSV) { + color.set_hsv(h, s, v, color.a); + } else if (current_mode == MODE_OKHSL) { + color.set_ok_hsl(h, s, v, color.a); + } + + last_color = color; set_pick_color(color); _update_color(); if (!deferred_mode_enabled) { @@ -1008,7 +1049,7 @@ void ColorPicker::_screen_input(const Ref<InputEvent> &p_event) { Ref<Image> img = r->get_texture()->get_image(); if (img.is_valid() && !img->is_empty()) { Vector2 ofs = mev->get_global_position() - r->get_visible_rect().get_position(); - Color c = img->get_pixel(ofs.x, r->get_visible_rect().size.height - ofs.y); + Color c = img->get_pixel(ofs.x, ofs.y); set_pick_color(c); } @@ -1030,11 +1071,13 @@ void ColorPicker::_screen_pick_pressed() { screen = memnew(Control); r->add_child(screen); screen->set_as_top_level(true); - screen->set_anchors_and_offsets_preset(Control::PRESET_WIDE); + screen->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT); screen->set_default_cursor_shape(CURSOR_POINTING_HAND); screen->connect("gui_input", callable_mp(this, &ColorPicker::_screen_input)); // It immediately toggles off in the first press otherwise. screen->call_deferred(SNAME("connect"), "hidden", Callable(btn_pick, "set_pressed"), varray(false)); + } else { + screen->show(); } screen->raise(); #ifndef _MSC_VER @@ -1051,21 +1094,30 @@ void ColorPicker::_focus_enter() { c_text->select(0, 0); } - for (int i = 0; i < 4; i++) { + for (int i = 0; i < current_slider_count; i++) { if (values[i]->get_line_edit()->has_focus() && !has_ctext_focus) { values[i]->get_line_edit()->select_all(); } else { values[i]->get_line_edit()->select(0, 0); } } + if (alpha_value->get_line_edit()->has_focus() && !has_ctext_focus) { + alpha_value->get_line_edit()->select_all(); + } else { + alpha_value->get_line_edit()->select(0, 0); + } } void ColorPicker::_focus_exit() { - for (int i = 0; i < 4; i++) { + for (int i = 0; i < current_slider_count; i++) { if (!values[i]->get_line_edit()->get_menu()->is_visible()) { values[i]->get_line_edit()->select(0, 0); } } + if (!alpha_value->get_line_edit()->get_menu()->is_visible()) { + alpha_value->get_line_edit()->select(0, 0); + } + c_text->select(0, 0); } @@ -1105,12 +1157,10 @@ bool ColorPicker::are_presets_visible() const { void ColorPicker::_bind_methods() { ClassDB::bind_method(D_METHOD("set_pick_color", "color"), &ColorPicker::set_pick_color); ClassDB::bind_method(D_METHOD("get_pick_color"), &ColorPicker::get_pick_color); - ClassDB::bind_method(D_METHOD("set_hsv_mode", "enabled"), &ColorPicker::set_hsv_mode); - ClassDB::bind_method(D_METHOD("is_hsv_mode"), &ColorPicker::is_hsv_mode); - ClassDB::bind_method(D_METHOD("set_raw_mode", "enabled"), &ColorPicker::set_raw_mode); - ClassDB::bind_method(D_METHOD("is_raw_mode"), &ColorPicker::is_raw_mode); ClassDB::bind_method(D_METHOD("set_deferred_mode", "mode"), &ColorPicker::set_deferred_mode); ClassDB::bind_method(D_METHOD("is_deferred_mode"), &ColorPicker::is_deferred_mode); + ClassDB::bind_method(D_METHOD("set_color_mode", "color_mode"), &ColorPicker::set_color_mode); + ClassDB::bind_method(D_METHOD("get_color_mode"), &ColorPicker::get_color_mode); ClassDB::bind_method(D_METHOD("set_edit_alpha", "show"), &ColorPicker::set_edit_alpha); ClassDB::bind_method(D_METHOD("is_editing_alpha"), &ColorPicker::is_editing_alpha); ClassDB::bind_method(D_METHOD("set_presets_enabled", "enabled"), &ColorPicker::set_presets_enabled); @@ -1120,15 +1170,14 @@ void ColorPicker::_bind_methods() { ClassDB::bind_method(D_METHOD("add_preset", "color"), &ColorPicker::add_preset); ClassDB::bind_method(D_METHOD("erase_preset", "color"), &ColorPicker::erase_preset); ClassDB::bind_method(D_METHOD("get_presets"), &ColorPicker::get_presets); - ClassDB::bind_method(D_METHOD("set_picker_shape", "picker"), &ColorPicker::set_picker_shape); + ClassDB::bind_method(D_METHOD("set_picker_shape", "shape"), &ColorPicker::set_picker_shape); ClassDB::bind_method(D_METHOD("get_picker_shape"), &ColorPicker::get_picker_shape); ADD_PROPERTY(PropertyInfo(Variant::COLOR, "color"), "set_pick_color", "get_pick_color"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "edit_alpha"), "set_edit_alpha", "is_editing_alpha"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "hsv_mode"), "set_hsv_mode", "is_hsv_mode"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "raw_mode"), "set_raw_mode", "is_raw_mode"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "color_mode", PROPERTY_HINT_ENUM, "RGB,HSV,RAW,OKHSL"), "set_color_mode", "get_color_mode"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "deferred_mode"), "set_deferred_mode", "is_deferred_mode"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "picker_shape", PROPERTY_HINT_ENUM, "HSV Rectangle,HSV Rectangle Wheel,VHS Circle"), "set_picker_shape", "get_picker_shape"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "picker_shape", PROPERTY_HINT_ENUM, "HSV Rectangle,HSV Rectangle Wheel,VHS Circle,OKHSL Circle"), "set_picker_shape", "get_picker_shape"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "presets_enabled"), "set_presets_enabled", "are_presets_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "presets_visible"), "set_presets_visible", "are_presets_visible"); @@ -1136,9 +1185,15 @@ void ColorPicker::_bind_methods() { ADD_SIGNAL(MethodInfo("preset_added", PropertyInfo(Variant::COLOR, "color"))); ADD_SIGNAL(MethodInfo("preset_removed", PropertyInfo(Variant::COLOR, "color"))); + BIND_ENUM_CONSTANT(MODE_RGB); + BIND_ENUM_CONSTANT(MODE_HSV); + BIND_ENUM_CONSTANT(MODE_RAW); + BIND_ENUM_CONSTANT(MODE_OKHSL); + BIND_ENUM_CONSTANT(SHAPE_HSV_RECTANGLE); BIND_ENUM_CONSTANT(SHAPE_HSV_WHEEL); BIND_ENUM_CONSTANT(SHAPE_VHS_CIRCLE); + BIND_ENUM_CONSTANT(SHAPE_OKHSL_CIRCLE); } ColorPicker::ColorPicker() : @@ -1147,6 +1202,7 @@ ColorPicker::ColorPicker() : add_child(hb_edit, false, INTERNAL_MODE_FRONT); hb_edit->set_v_size_flags(SIZE_EXPAND_FILL); + uv_edit = memnew(Control); hb_edit->add_child(uv_edit); uv_edit->connect("gui_input", callable_mp(this, &ColorPicker::_uv_input), make_binds(uv_edit)); uv_edit->set_mouse_filter(MOUSE_FILTER_PASS); @@ -1157,11 +1213,13 @@ ColorPicker::ColorPicker() : HBoxContainer *hb_smpl = memnew(HBoxContainer); add_child(hb_smpl, false, INTERNAL_MODE_FRONT); + sample = memnew(TextureRect); hb_smpl->add_child(sample); sample->set_h_size_flags(SIZE_EXPAND_FILL); sample->connect("gui_input", callable_mp(this, &ColorPicker::_sample_input)); sample->connect("draw", callable_mp(this, &ColorPicker::_sample_draw)); + btn_pick = memnew(Button); btn_pick->set_flat(true); hb_smpl->add_child(btn_pick); btn_pick->set_toggle_mode(true); @@ -1174,51 +1232,35 @@ ColorPicker::ColorPicker() : add_child(memnew(HSeparator), false, INTERNAL_MODE_FRONT); VBoxContainer *vbr = memnew(VBoxContainer); + add_child(vbr, false, INTERNAL_MODE_FRONT); vbr->set_h_size_flags(SIZE_EXPAND_FILL); - for (int i = 0; i < 4; i++) { - HBoxContainer *hbc = memnew(HBoxContainer); - - labels[i] = memnew(Label()); - labels[i]->set_custom_minimum_size(Size2(get_theme_constant(SNAME("label_width")), 0)); - labels[i]->set_v_size_flags(SIZE_SHRINK_CENTER); - hbc->add_child(labels[i]); - - scroll[i] = memnew(HSlider); - scroll[i]->set_v_size_flags(SIZE_SHRINK_CENTER); - scroll[i]->set_focus_mode(FOCUS_NONE); - hbc->add_child(scroll[i]); + GridContainer *gc = memnew(GridContainer); - values[i] = memnew(SpinBox); - scroll[i]->share(values[i]); - hbc->add_child(values[i]); - values[i]->get_line_edit()->connect("focus_entered", callable_mp(this, &ColorPicker::_focus_enter)); - values[i]->get_line_edit()->connect("focus_exited", callable_mp(this, &ColorPicker::_focus_exit)); + vbr->add_child(gc); + gc->set_h_size_flags(SIZE_EXPAND_FILL); + gc->set_columns(3); - scroll[i]->set_min(0); - scroll[i]->set_page(0); - scroll[i]->set_h_size_flags(SIZE_EXPAND_FILL); - - scroll[i]->connect("value_changed", callable_mp(this, &ColorPicker::_value_changed)); - scroll[i]->connect("draw", callable_mp(this, &ColorPicker::_slider_draw), make_binds(i)); - - vbr->add_child(hbc); + for (int i = 0; i < SLIDER_COUNT + 1; i++) { + create_slider(gc, i); } - labels[3]->set_text("A"); + alpha_label->set_text("A"); HBoxContainer *hhb = memnew(HBoxContainer); vbr->add_child(hhb); - hhb->add_child(btn_hsv); - btn_hsv->set_text(RTR("HSV")); - btn_hsv->connect("toggled", callable_mp(this, &ColorPicker::set_hsv_mode)); + mode_option_button = memnew(OptionButton); - hhb->add_child(btn_raw); - btn_raw->set_text(RTR("Raw")); - btn_raw->connect("toggled", callable_mp(this, &ColorPicker::set_raw_mode)); + hhb->add_child(mode_option_button); + add_mode(new ColorModeRGB(this)); + add_mode(new ColorModeHSV(this)); + add_mode(new ColorModeRAW(this)); + add_mode(new ColorModeOKHSL(this)); + mode_option_button->connect("item_selected", callable_mp(this, &ColorPicker::_set_color_mode)); + text_type = memnew(Button); hhb->add_child(text_type); text_type->set_text("#"); text_type->set_tooltip(RTR("Switch between hexadecimal and code values.")); @@ -1229,12 +1271,14 @@ ColorPicker::ColorPicker() : text_type->set_mouse_filter(MOUSE_FILTER_IGNORE); } + c_text = memnew(LineEdit); hhb->add_child(c_text); c_text->set_h_size_flags(SIZE_EXPAND_FILL); c_text->connect("text_submitted", callable_mp(this, &ColorPicker::_html_submitted)); c_text->connect("focus_entered", callable_mp(this, &ColorPicker::_focus_enter)); c_text->connect("focus_exited", callable_mp(this, &ColorPicker::_html_focus_exit)); + wheel_edit = memnew(AspectRatioContainer); wheel_edit->set_h_size_flags(SIZE_EXPAND_FILL); wheel_edit->set_v_size_flags(SIZE_EXPAND_FILL); hb_edit->add_child(wheel_edit); @@ -1244,41 +1288,53 @@ ColorPicker::ColorPicker() : circle_mat.instantiate(); circle_mat->set_shader(circle_shader); + wheel_margin = memnew(MarginContainer); wheel_margin->add_theme_constant_override("margin_bottom", 8); wheel_edit->add_child(wheel_margin); + wheel = memnew(Control); wheel_margin->add_child(wheel); wheel->set_mouse_filter(MOUSE_FILTER_PASS); wheel->connect("draw", callable_mp(this, &ColorPicker::_hsv_draw), make_binds(2, wheel)); + wheel_uv = memnew(Control); wheel_margin->add_child(wheel_uv); wheel_uv->connect("gui_input", callable_mp(this, &ColorPicker::_uv_input), make_binds(wheel_uv)); wheel_uv->connect("draw", callable_mp(this, &ColorPicker::_hsv_draw), make_binds(0, wheel_uv)); + w_edit = memnew(Control); hb_edit->add_child(w_edit); w_edit->set_h_size_flags(SIZE_FILL); w_edit->set_v_size_flags(SIZE_EXPAND_FILL); w_edit->connect("gui_input", callable_mp(this, &ColorPicker::_w_input)); w_edit->connect("draw", callable_mp(this, &ColorPicker::_hsv_draw), make_binds(1, w_edit)); - picker_type = SHAPE_HSV_RECTANGLE; _update_controls(); updating = false; set_pick_color(Color(1, 1, 1)); + preset_separator = memnew(HSeparator); add_child(preset_separator, false, INTERNAL_MODE_FRONT); + preset_container = memnew(GridContainer); preset_container->set_h_size_flags(SIZE_EXPAND_FILL); preset_container->set_columns(preset_column_count); add_child(preset_container, false, INTERNAL_MODE_FRONT); + btn_add_preset = memnew(Button); btn_add_preset->set_icon_alignment(HORIZONTAL_ALIGNMENT_CENTER); btn_add_preset->set_tooltip(RTR("Add current color as a preset.")); btn_add_preset->connect("pressed", callable_mp(this, &ColorPicker::_add_preset_pressed)); preset_container->add_child(btn_add_preset); } +ColorPicker::~ColorPicker() { + for (int i = 0; i < modes.size(); i++) { + delete modes[i]; + } +} + ///////////////// void ColorPickerButton::_about_to_popup() { @@ -1400,7 +1456,7 @@ void ColorPickerButton::_update_picker() { popup = memnew(PopupPanel); popup->set_wrap_controls(true); picker = memnew(ColorPicker); - picker->set_anchors_and_offsets_preset(PRESET_WIDE); + picker->set_anchors_and_offsets_preset(PRESET_FULL_RECT); popup->add_child(picker); add_child(popup, false, INTERNAL_MODE_FRONT); picker->connect("color_changed", callable_mp(this, &ColorPickerButton::_color_changed)); diff --git a/scene/gui/color_picker.h b/scene/gui/color_picker.h index 6f3e16009c..e219c78319 100644 --- a/scene/gui/color_picker.h +++ b/scene/gui/color_picker.h @@ -34,16 +34,23 @@ #include "scene/gui/aspect_ratio_container.h" #include "scene/gui/box_container.h" #include "scene/gui/button.h" -#include "scene/gui/check_button.h" +#include "scene/gui/control.h" #include "scene/gui/grid_container.h" #include "scene/gui/label.h" #include "scene/gui/line_edit.h" +#include "scene/gui/option_button.h" #include "scene/gui/popup.h" #include "scene/gui/separator.h" #include "scene/gui/slider.h" #include "scene/gui/spin_box.h" #include "scene/gui/texture_rect.h" +class ColorMode; +class ColorModeRGB; +class ColorModeHSV; +class ColorModeRAW; +class ColorModeOKHSL; + class ColorPresetButton : public BaseButton { GDCLASS(ColorPresetButton, BaseButton); @@ -64,45 +71,70 @@ class ColorPicker : public BoxContainer { GDCLASS(ColorPicker, BoxContainer); public: + enum ColorModeType { + MODE_RGB, + MODE_HSV, + MODE_RAW, + MODE_OKHSL, + + MODE_MAX + }; + enum PickerShapeType { SHAPE_HSV_RECTANGLE, SHAPE_HSV_WHEEL, SHAPE_VHS_CIRCLE, + SHAPE_OKHSL_CIRCLE, SHAPE_MAX }; + static const int SLIDER_COUNT = 4; + private: static Ref<Shader> wheel_shader; static Ref<Shader> circle_shader; + static Ref<Shader> circle_ok_color_shader; static List<Color> preset_cache; + int current_slider_count = SLIDER_COUNT; + + bool slider_theme_modified = true; + + Vector<ColorMode *> modes; + Control *screen = nullptr; - Control *uv_edit = memnew(Control); - Control *w_edit = memnew(Control); - AspectRatioContainer *wheel_edit = memnew(AspectRatioContainer); - MarginContainer *wheel_margin = memnew(MarginContainer); + Control *uv_edit = nullptr; + Control *w_edit = nullptr; + AspectRatioContainer *wheel_edit = nullptr; + MarginContainer *wheel_margin = nullptr; Ref<ShaderMaterial> wheel_mat; Ref<ShaderMaterial> circle_mat; - Control *wheel = memnew(Control); - Control *wheel_uv = memnew(Control); - TextureRect *sample = memnew(TextureRect); - GridContainer *preset_container = memnew(GridContainer); - HSeparator *preset_separator = memnew(HSeparator); - Button *btn_add_preset = memnew(Button); - Button *btn_pick = memnew(Button); - CheckButton *btn_hsv = memnew(CheckButton); - CheckButton *btn_raw = memnew(CheckButton); - HSlider *scroll[4]; - SpinBox *values[4]; - Label *labels[4]; - Button *text_type = memnew(Button); - LineEdit *c_text = memnew(LineEdit); + Control *wheel = nullptr; + Control *wheel_uv = nullptr; + TextureRect *sample = nullptr; + GridContainer *preset_container = nullptr; + HSeparator *preset_separator = nullptr; + Button *btn_add_preset = nullptr; + Button *btn_pick = nullptr; + + OptionButton *mode_option_button = nullptr; + + HSlider *sliders[SLIDER_COUNT]; + SpinBox *values[SLIDER_COUNT]; + Label *labels[SLIDER_COUNT]; + Button *text_type = nullptr; + LineEdit *c_text = nullptr; + + HSlider *alpha_slider = nullptr; + SpinBox *alpha_value = nullptr; + Label *alpha_label = nullptr; bool edit_alpha = true; Size2i ms; bool text_is_constructor = false; - PickerShapeType picker_type = SHAPE_HSV_WHEEL; + PickerShapeType current_shape = SHAPE_HSV_RECTANGLE; + ColorModeType current_mode = MODE_RGB; const int preset_column_count = 9; int prev_preset_size = 0; @@ -112,8 +144,6 @@ private: Color old_color; bool display_old_color = false; - bool raw_mode_enabled = false; - bool hsv_mode_enabled = false; bool deferred_mode_enabled = false; bool updating = true; bool changing_color = false; @@ -124,8 +154,11 @@ private: float h = 0.0; float s = 0.0; float v = 0.0; - Color last_hsv; + Color last_color; + PickerShapeType _get_actual_shape() const; + void create_slider(GridContainer *gc, int idx); + void _reset_theme(); void _html_submitted(const String &p_html); void _value_changed(double); void _update_controls(); @@ -150,17 +183,26 @@ private: inline int _get_preset_size(); void _add_preset_button(int p_size, const Color &p_color); + void _set_color_mode(ColorModeType p_mode); + protected: void _notification(int); static void _bind_methods(); public: + HSlider *get_slider(int idx); + Vector<float> get_active_slider_values(); + static void init_shaders(); static void finish_shaders(); + void add_mode(ColorMode *p_mode); + void set_edit_alpha(bool p_show); bool is_editing_alpha() const; + int get_preset_size(); + void _set_pick_color(const Color &p_color, bool p_update_sliders); void set_pick_color(const Color &p_color); Color get_pick_color() const; @@ -169,7 +211,7 @@ public: void set_display_old_color(bool p_enabled); bool is_displaying_old_color() const; - void set_picker_shape(PickerShapeType p_picker_type); + void set_picker_shape(PickerShapeType p_shape); PickerShapeType get_picker_shape() const; void add_preset(const Color &p_color); @@ -177,11 +219,8 @@ public: PackedColorArray get_presets() const; void _update_presets(); - void set_hsv_mode(bool p_enabled); - bool is_hsv_mode() const; - - void set_raw_mode(bool p_enabled); - bool is_raw_mode() const; + void set_color_mode(ColorModeType p_mode); + ColorModeType get_color_mode() const; void set_deferred_mode(bool p_enabled); bool is_deferred_mode() const; @@ -195,6 +234,7 @@ public: void set_focus_on_line_edit(); ColorPicker(); + ~ColorPicker(); }; class ColorPickerButton : public Button { @@ -235,4 +275,5 @@ public: }; VARIANT_ENUM_CAST(ColorPicker::PickerShapeType); +VARIANT_ENUM_CAST(ColorPicker::ColorModeType); #endif // COLOR_PICKER_H diff --git a/scene/gui/control.cpp b/scene/gui/control.cpp index 96d2b29fc1..686045901c 100644 --- a/scene/gui/control.cpp +++ b/scene/gui/control.cpp @@ -414,7 +414,7 @@ void Control::_get_property_list(List<PropertyInfo> *p_list) const { usage |= PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_CHECKED; } - p_list->push_back(PropertyInfo(Variant::INT, "theme_override_font_sizes/" + E, PROPERTY_HINT_RANGE, "1,256,1,or_greater", usage)); + p_list->push_back(PropertyInfo(Variant::INT, "theme_override_font_sizes/" + E, PROPERTY_HINT_RANGE, "1,256,1,or_greater,suffix:px", usage)); } } { @@ -471,6 +471,17 @@ void Control::_validate_property(PropertyInfo &property) const { property.hint_string = hint_string; } + if (property.name == "mouse_force_pass_scroll_events") { + // Disable force pass if the control is not stopping the event. + if (data.mouse_filter != MOUSE_FILTER_STOP) { + property.usage |= PROPERTY_USAGE_READ_ONLY; + } + } + + if (property.name == "scale") { + property.hint = PROPERTY_HINT_LINK; + } + // Validate which positioning properties should be displayed depending on the parent and the layout mode. Node *parent_node = get_parent_control(); if (!parent_node) { @@ -712,41 +723,40 @@ void Control::_notification(int p_notification) { data.parent_window = Object::cast_to<Window>(get_parent()); data.is_rtl_dirty = true; - Node *parent = this; //meh - Control *parent_control = nullptr; - bool subwindow = false; + if (data.theme.is_null()) { + if (data.parent && (data.parent->data.theme_owner || data.parent->data.theme_owner_window)) { + data.theme_owner = data.parent->data.theme_owner; + data.theme_owner_window = data.parent->data.theme_owner_window; + notification(NOTIFICATION_THEME_CHANGED); + } else if (data.parent_window && (data.parent_window->theme_owner || data.parent_window->theme_owner_window)) { + data.theme_owner = data.parent_window->theme_owner; + data.theme_owner_window = data.parent_window->theme_owner_window; + notification(NOTIFICATION_THEME_CHANGED); + } + } - while (parent) { - parent = parent->get_parent(); + CanvasItem *node = this; + bool has_parent_control = false; + while (!node->is_set_as_top_level()) { + CanvasItem *parent = Object::cast_to<CanvasItem>(node->get_parent()); if (!parent) { break; } - CanvasItem *ci = Object::cast_to<CanvasItem>(parent); - if (ci && ci->is_set_as_top_level()) { - subwindow = true; - break; - } - - parent_control = Object::cast_to<Control>(parent); - + Control *parent_control = Object::cast_to<Control>(parent); if (parent_control) { - break; - } else if (ci) { - } else { + has_parent_control = true; break; } + + node = parent; } - if (parent_control && !subwindow) { - //do nothing, has a parent control and not top_level - if (data.theme.is_null() && parent_control->data.theme_owner) { - data.theme_owner = parent_control->data.theme_owner; - notification(NOTIFICATION_THEME_CHANGED); - } + if (has_parent_control) { + // Do nothing, has a parent control. } else { - //is a regular root control or top_level + // Is a regular root control or top_level. Viewport *viewport = get_viewport(); ERR_FAIL_COND(!viewport); data.RI = viewport->_gui_add_root_control(this); @@ -757,7 +767,7 @@ void Control::_notification(int p_notification) { if (data.parent_canvas_item) { data.parent_canvas_item->connect("item_rect_changed", callable_mp(this, &Control::_size_changed)); } else { - //connect viewport + // Connect viewport. Viewport *viewport = get_viewport(); ERR_FAIL_COND(!viewport); viewport->connect("size_changed", callable_mp(this, &Control::_size_changed)); @@ -839,6 +849,7 @@ void Control::_notification(int p_notification) { } } else { data.minimum_size_valid = false; + _update_minimum_size(); _size_changed(); } } break; @@ -1104,7 +1115,7 @@ Ref<StyleBox> Control::get_theme_stylebox(const StringName &p_name, const String Ref<Font> Control::get_theme_font(const StringName &p_name, const StringName &p_theme_type) const { if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == data.theme_type_variation) { const Ref<Font> *font = data.font_override.getptr(p_name); - if (font && (*font)->get_data_count() > 0) { + if (font) { return *font; } } @@ -1501,14 +1512,15 @@ void Control::_set_layout_mode(LayoutMode p_mode) { bool list_changed = false; if (p_mode == LayoutMode::LAYOUT_MODE_POSITION || p_mode == LayoutMode::LAYOUT_MODE_ANCHORS) { - if (has_meta("_edit_layout_mode") && (int)get_meta("_edit_layout_mode") != (int)p_mode) { + if ((int)get_meta("_edit_layout_mode", p_mode) != (int)p_mode) { list_changed = true; } set_meta("_edit_layout_mode", (int)p_mode); if (p_mode == LayoutMode::LAYOUT_MODE_POSITION) { - set_meta("_edit_use_custom_anchors", false); + remove_meta("_edit_layout_mode"); + remove_meta("_edit_use_custom_anchors"); set_anchors_and_offsets_preset(LayoutPreset::PRESET_TOP_LEFT, LayoutPresetMode::PRESET_MODE_KEEP_SIZE); set_grow_direction_preset(LayoutPreset::PRESET_TOP_LEFT); } @@ -1589,22 +1601,22 @@ void Control::set_anchor_and_offset(Side p_side, real_t p_anchor, real_t p_pos, void Control::_set_anchors_layout_preset(int p_preset) { bool list_changed = false; - if (has_meta("_edit_layout_mode") && (int)get_meta("_edit_layout_mode") != (int)LayoutMode::LAYOUT_MODE_ANCHORS) { + if (get_meta("_edit_layout_mode", LayoutMode::LAYOUT_MODE_ANCHORS).operator int() != LayoutMode::LAYOUT_MODE_ANCHORS) { list_changed = true; - set_meta("_edit_layout_mode", (int)LayoutMode::LAYOUT_MODE_ANCHORS); + set_meta("_edit_layout_mode", LayoutMode::LAYOUT_MODE_ANCHORS); } if (p_preset == -1) { - if (!has_meta("_edit_use_custom_anchors") || !(bool)get_meta("_edit_use_custom_anchors")) { + if (!get_meta("_edit_use_custom_anchors", false)) { set_meta("_edit_use_custom_anchors", true); notify_property_list_changed(); } return; // Keep settings as is. } - if (!has_meta("_edit_use_custom_anchors") || (bool)get_meta("_edit_use_custom_anchors")) { + if (get_meta("_edit_use_custom_anchors", true)) { list_changed = true; - set_meta("_edit_use_custom_anchors", false); + remove_meta("_edit_use_custom_anchors"); } LayoutPreset preset = (LayoutPreset)p_preset; @@ -1630,7 +1642,7 @@ void Control::_set_anchors_layout_preset(int p_preset) { case PRESET_BOTTOM_WIDE: case PRESET_VCENTER_WIDE: case PRESET_HCENTER_WIDE: - case PRESET_WIDE: + case PRESET_FULL_RECT: set_offsets_preset(preset, LayoutPresetMode::PRESET_MODE_MINSIZE); break; } @@ -1645,7 +1657,7 @@ void Control::_set_anchors_layout_preset(int p_preset) { int Control::_get_anchors_layout_preset() const { // If the custom preset was selected by user, use it. - if (has_meta("_edit_use_custom_anchors") && (bool)get_meta("_edit_use_custom_anchors")) { + if ((bool)get_meta("_edit_use_custom_anchors", false)) { return -1; } @@ -1706,7 +1718,7 @@ int Control::_get_anchors_layout_preset() const { } if (left == ANCHOR_BEGIN && right == ANCHOR_END && top == ANCHOR_BEGIN && bottom == ANCHOR_END) { - return (int)LayoutPreset::PRESET_WIDE; + return (int)LayoutPreset::PRESET_FULL_RECT; } // Does not match any preset, return "Custom". @@ -1725,7 +1737,7 @@ void Control::set_anchors_preset(LayoutPreset p_preset, bool p_keep_offsets) { case PRESET_BOTTOM_WIDE: case PRESET_LEFT_WIDE: case PRESET_HCENTER_WIDE: - case PRESET_WIDE: + case PRESET_FULL_RECT: set_anchor(SIDE_LEFT, ANCHOR_BEGIN, p_keep_offsets); break; @@ -1753,7 +1765,7 @@ void Control::set_anchors_preset(LayoutPreset p_preset, bool p_keep_offsets) { case PRESET_RIGHT_WIDE: case PRESET_TOP_WIDE: case PRESET_VCENTER_WIDE: - case PRESET_WIDE: + case PRESET_FULL_RECT: set_anchor(SIDE_TOP, ANCHOR_BEGIN, p_keep_offsets); break; @@ -1795,7 +1807,7 @@ void Control::set_anchors_preset(LayoutPreset p_preset, bool p_keep_offsets) { case PRESET_RIGHT_WIDE: case PRESET_BOTTOM_WIDE: case PRESET_HCENTER_WIDE: - case PRESET_WIDE: + case PRESET_FULL_RECT: set_anchor(SIDE_RIGHT, ANCHOR_END, p_keep_offsets); break; } @@ -1823,7 +1835,7 @@ void Control::set_anchors_preset(LayoutPreset p_preset, bool p_keep_offsets) { case PRESET_RIGHT_WIDE: case PRESET_BOTTOM_WIDE: case PRESET_VCENTER_WIDE: - case PRESET_WIDE: + case PRESET_FULL_RECT: set_anchor(SIDE_BOTTOM, ANCHOR_END, p_keep_offsets); break; } @@ -1858,7 +1870,7 @@ void Control::set_offsets_preset(LayoutPreset p_preset, LayoutPresetMode p_resiz case PRESET_BOTTOM_WIDE: case PRESET_LEFT_WIDE: case PRESET_HCENTER_WIDE: - case PRESET_WIDE: + case PRESET_FULL_RECT: data.offset[0] = x * (0.0 - data.anchor[0]) + p_margin + parent_rect.position.x; break; @@ -1886,7 +1898,7 @@ void Control::set_offsets_preset(LayoutPreset p_preset, LayoutPresetMode p_resiz case PRESET_RIGHT_WIDE: case PRESET_TOP_WIDE: case PRESET_VCENTER_WIDE: - case PRESET_WIDE: + case PRESET_FULL_RECT: data.offset[1] = parent_rect.size.y * (0.0 - data.anchor[1]) + p_margin + parent_rect.position.y; break; @@ -1928,7 +1940,7 @@ void Control::set_offsets_preset(LayoutPreset p_preset, LayoutPresetMode p_resiz case PRESET_RIGHT_WIDE: case PRESET_BOTTOM_WIDE: case PRESET_HCENTER_WIDE: - case PRESET_WIDE: + case PRESET_FULL_RECT: data.offset[2] = x * (1.0 - data.anchor[2]) - p_margin + parent_rect.position.x; break; } @@ -1956,7 +1968,7 @@ void Control::set_offsets_preset(LayoutPreset p_preset, LayoutPresetMode p_resiz case PRESET_RIGHT_WIDE: case PRESET_BOTTOM_WIDE: case PRESET_VCENTER_WIDE: - case PRESET_WIDE: + case PRESET_FULL_RECT: data.offset[3] = parent_rect.size.y * (1.0 - data.anchor[3]) - p_margin + parent_rect.position.y; break; } @@ -1991,7 +2003,7 @@ void Control::set_grow_direction_preset(LayoutPreset p_preset) { case PRESET_BOTTOM_WIDE: case PRESET_VCENTER_WIDE: case PRESET_HCENTER_WIDE: - case PRESET_WIDE: + case PRESET_FULL_RECT: set_h_grow_direction(GrowDirection::GROW_DIRECTION_BOTH); break; } @@ -2019,7 +2031,7 @@ void Control::set_grow_direction_preset(LayoutPreset p_preset) { case PRESET_RIGHT_WIDE: case PRESET_VCENTER_WIDE: case PRESET_HCENTER_WIDE: - case PRESET_WIDE: + case PRESET_FULL_RECT: set_v_grow_direction(GrowDirection::GROW_DIRECTION_BOTH); break; } @@ -2070,7 +2082,7 @@ Point2 Control::get_global_position() const { Point2 Control::get_screen_position() const { ERR_FAIL_COND_V(!is_inside_tree(), Point2()); - Point2 global_pos = get_viewport()->get_canvas_transform().xform(get_global_position()); + Point2 global_pos = get_global_transform_with_canvas().get_origin(); Window *w = Object::cast_to<Window>(get_viewport()); if (w && !w->is_embedding_subwindows()) { global_pos += w->get_position(); @@ -2332,7 +2344,7 @@ void Control::set_focus_mode(FocusMode p_focus_mode) { static Control *_next_control(Control *p_from) { if (p_from->is_set_as_top_level()) { - return nullptr; // can't go above + return nullptr; // Can't go above. } Control *parent = Object::cast_to<Control>(p_from->get_parent()); @@ -2352,7 +2364,7 @@ static Control *_next_control(Control *p_from) { return c; } - //no next in parent, try the same in parent + // No next in parent, try the same in parent. return _next_control(parent); } @@ -2376,7 +2388,7 @@ Control *Control::find_next_valid_focus() const { } } - // find next child + // Find next child. Control *next_child = nullptr; @@ -2392,7 +2404,7 @@ Control *Control::find_next_valid_focus() const { if (!next_child) { next_child = _next_control(from); - if (!next_child) { //nothing else.. go up and find either window or subwindow + if (!next_child) { // Nothing else. Go up and find either window or subwindow. next_child = const_cast<Control *>(this); while (next_child && !next_child->is_set_as_top_level()) { next_child = cast_to<Control>(next_child->get_parent()); @@ -2410,7 +2422,7 @@ Control *Control::find_next_valid_focus() const { } } - if (next_child == this) { // no next control-> + if (next_child == from || next_child == this) { // No next control. return (get_focus_mode() == FOCUS_ALL) ? next_child : nullptr; } if (next_child) { @@ -2442,7 +2454,7 @@ static Control *_prev_control(Control *p_from) { return p_from; } - //no prev in parent, try the same in parent + // No prev in parent, try the same in parent. return _prev_control(child); } @@ -2466,12 +2478,12 @@ Control *Control::find_prev_valid_focus() const { } } - // find prev child + // Find prev child. Control *prev_child = nullptr; if (from->is_set_as_top_level() || !Object::cast_to<Control>(from->get_parent())) { - //find last of the children + // Find last of the children. prev_child = _prev_control(from); @@ -2494,7 +2506,7 @@ Control *Control::find_prev_valid_focus() const { } } - if (prev_child == this) { // no prev control-> + if (prev_child == from || prev_child == this) { // No prev control. return (get_focus_mode() == FOCUS_ALL) ? prev_child : nullptr; } @@ -2930,6 +2942,7 @@ int Control::get_v_size_flags() const { void Control::set_mouse_filter(MouseFilter p_filter) { ERR_FAIL_INDEX(p_filter, 3); data.mouse_filter = p_filter; + notify_property_list_changed(); update_configuration_warnings(); } @@ -2937,6 +2950,14 @@ Control::MouseFilter Control::get_mouse_filter() const { return data.mouse_filter; } +void Control::set_force_pass_scroll_events(bool p_force_pass_scroll_events) { + data.force_pass_scroll_events = p_force_pass_scroll_events; +} + +bool Control::is_force_pass_scroll_events() const { + return data.force_pass_scroll_events; +} + void Control::warp_mouse(const Point2 &p_position) { ERR_FAIL_COND(!is_inside_tree()); get_viewport()->warp_mouse(get_global_transform_with_canvas().xform(p_position)); @@ -2946,90 +2967,17 @@ bool Control::is_text_field() const { return false; } -Array Control::structured_text_parser(StructuredTextParser p_theme_type, const Array &p_args, const String &p_text) const { - Array ret; - switch (p_theme_type) { - case STRUCTURED_TEXT_URI: { - int prev = 0; - for (int i = 0; i < p_text.length(); i++) { - if ((p_text[i] == '\\') || (p_text[i] == '/') || (p_text[i] == '.') || (p_text[i] == ':') || (p_text[i] == '&') || (p_text[i] == '=') || (p_text[i] == '@') || (p_text[i] == '?') || (p_text[i] == '#')) { - if (prev != i) { - ret.push_back(Vector2i(prev, i)); - } - ret.push_back(Vector2i(i, i + 1)); - prev = i + 1; - } - } - if (prev != p_text.length()) { - ret.push_back(Vector2i(prev, p_text.length())); - } - } break; - case STRUCTURED_TEXT_FILE: { - int prev = 0; - for (int i = 0; i < p_text.length(); i++) { - if ((p_text[i] == '\\') || (p_text[i] == '/') || (p_text[i] == ':')) { - if (prev != i) { - ret.push_back(Vector2i(prev, i)); - } - ret.push_back(Vector2i(i, i + 1)); - prev = i + 1; - } - } - if (prev != p_text.length()) { - ret.push_back(Vector2i(prev, p_text.length())); - } - } break; - case STRUCTURED_TEXT_EMAIL: { - bool local = true; - int prev = 0; - for (int i = 0; i < p_text.length(); i++) { - if ((p_text[i] == '@') && local) { // Add full "local" as single context. - local = false; - ret.push_back(Vector2i(prev, i)); - ret.push_back(Vector2i(i, i + 1)); - prev = i + 1; - } else if (!local & (p_text[i] == '.')) { // Add each dot separated "domain" part as context. - if (prev != i) { - ret.push_back(Vector2i(prev, i)); - } - ret.push_back(Vector2i(i, i + 1)); - prev = i + 1; - } - } - if (prev != p_text.length()) { - ret.push_back(Vector2i(prev, p_text.length())); - } - } break; - case STRUCTURED_TEXT_LIST: { - if (p_args.size() == 1 && p_args[0].get_type() == Variant::STRING) { - Vector<String> tags = p_text.split(String(p_args[0])); - int prev = 0; - for (int i = 0; i < tags.size(); i++) { - if (prev != i) { - ret.push_back(Vector2i(prev, prev + tags[i].length())); - } - ret.push_back(Vector2i(prev + tags[i].length(), prev + tags[i].length() + 1)); - prev = prev + tags[i].length() + 1; - } - } - } break; - case STRUCTURED_TEXT_CUSTOM: { - Array r; - if (GDVIRTUAL_CALL(_structured_text_parser, p_args, p_text, r)) { - for (int i = 0; i < r.size(); i++) { - if (r[i].get_type() == Variant::VECTOR2I) { - ret.push_back(Vector2i(r[i])); - } - } - } - } break; - case STRUCTURED_TEXT_NONE: - case STRUCTURED_TEXT_DEFAULT: - default: { - ret.push_back(Vector2i(0, p_text.length())); +Array Control::structured_text_parser(TextServer::StructuredTextParser p_parser_type, const Array &p_args, const String &p_text) const { + if (p_parser_type == TextServer::STRUCTURED_TEXT_CUSTOM) { + Array ret; + if (GDVIRTUAL_CALL(_structured_text_parser, p_args, p_text, ret)) { + return ret; + } else { + return Array(); } + } else { + return TS->parse_structured_text(p_parser_type, p_args, p_text); } - return ret; } void Control::set_rotation(real_t p_radians) { @@ -3322,6 +3270,9 @@ void Control::_bind_methods() { ClassDB::bind_method(D_METHOD("set_mouse_filter", "filter"), &Control::set_mouse_filter); ClassDB::bind_method(D_METHOD("get_mouse_filter"), &Control::get_mouse_filter); + ClassDB::bind_method(D_METHOD("set_force_pass_scroll_events", "force_pass_scroll_events"), &Control::set_force_pass_scroll_events); + ClassDB::bind_method(D_METHOD("is_force_pass_scroll_events"), &Control::is_force_pass_scroll_events); + ClassDB::bind_method(D_METHOD("set_clip_contents", "enable"), &Control::set_clip_contents); ClassDB::bind_method(D_METHOD("is_clipping_contents"), &Control::is_clipping_contents); @@ -3344,12 +3295,12 @@ void Control::_bind_methods() { ADD_GROUP("Layout", ""); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "clip_contents"), "set_clip_contents", "is_clipping_contents"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "minimum_size"), "set_custom_minimum_size", "get_custom_minimum_size"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "custom_minimum_size", PROPERTY_HINT_NONE, "suffix:px"), "set_custom_minimum_size", "get_custom_minimum_size"); ADD_PROPERTY(PropertyInfo(Variant::INT, "layout_direction", PROPERTY_HINT_ENUM, "Inherited,Locale,Left-to-Right,Right-to-Left"), "set_layout_direction", "get_layout_direction"); ADD_PROPERTY(PropertyInfo(Variant::INT, "layout_mode", PROPERTY_HINT_ENUM, "Position,Anchors,Container,Uncontrolled", PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_layout_mode", "_get_layout_mode"); ADD_PROPERTY_DEFAULT("layout_mode", LayoutMode::LAYOUT_MODE_POSITION); - const String anchors_presets_options = "Custom:-1,PresetWide:15," + const String anchors_presets_options = "Custom:-1,PresetFullRect:15," "PresetTopLeft:0,PresetTopRight:1,PresetBottomRight:3,PresetBottomLeft:2," "PresetCenterLeft:4,PresetCenterTop:5,PresetCenterRight:6,PresetCenterBottom:7,PresetCenter:8," "PresetLeftWide:9,PresetTopWide:10,PresetRightWide:11,PresetBottomWide:12,PresetVCenterWide:13,PresetHCenterWide:14"; @@ -3364,22 +3315,22 @@ void Control::_bind_methods() { ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "anchor_bottom", PROPERTY_HINT_RANGE, "0,1,0.001,or_lesser,or_greater"), "_set_anchor", "get_anchor", SIDE_BOTTOM); ADD_SUBGROUP_INDENT("Anchor Offsets", "offset_", 1); - ADD_PROPERTYI(PropertyInfo(Variant::INT, "offset_left", PROPERTY_HINT_RANGE, "-4096,4096"), "set_offset", "get_offset", SIDE_LEFT); - ADD_PROPERTYI(PropertyInfo(Variant::INT, "offset_top", PROPERTY_HINT_RANGE, "-4096,4096"), "set_offset", "get_offset", SIDE_TOP); - ADD_PROPERTYI(PropertyInfo(Variant::INT, "offset_right", PROPERTY_HINT_RANGE, "-4096,4096"), "set_offset", "get_offset", SIDE_RIGHT); - ADD_PROPERTYI(PropertyInfo(Variant::INT, "offset_bottom", PROPERTY_HINT_RANGE, "-4096,4096"), "set_offset", "get_offset", SIDE_BOTTOM); + ADD_PROPERTYI(PropertyInfo(Variant::INT, "offset_left", PROPERTY_HINT_RANGE, "-4096,4096,suffix:px"), "set_offset", "get_offset", SIDE_LEFT); + ADD_PROPERTYI(PropertyInfo(Variant::INT, "offset_top", PROPERTY_HINT_RANGE, "-4096,4096,suffix:px"), "set_offset", "get_offset", SIDE_TOP); + ADD_PROPERTYI(PropertyInfo(Variant::INT, "offset_right", PROPERTY_HINT_RANGE, "-4096,4096,suffix:px"), "set_offset", "get_offset", SIDE_RIGHT); + ADD_PROPERTYI(PropertyInfo(Variant::INT, "offset_bottom", PROPERTY_HINT_RANGE, "-4096,4096,suffix:px"), "set_offset", "get_offset", SIDE_BOTTOM); ADD_SUBGROUP_INDENT("Grow Direction", "grow_", 1); ADD_PROPERTY(PropertyInfo(Variant::INT, "grow_horizontal", PROPERTY_HINT_ENUM, "Left,Right,Both"), "set_h_grow_direction", "get_h_grow_direction"); ADD_PROPERTY(PropertyInfo(Variant::INT, "grow_vertical", PROPERTY_HINT_ENUM, "Top,Bottom,Both"), "set_v_grow_direction", "get_v_grow_direction"); ADD_SUBGROUP("Transform", ""); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "size", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "_set_size", "get_size"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "position", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "_set_position", "get_position"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "global_position", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "_set_global_position", "get_global_position"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "size", PROPERTY_HINT_NONE, "suffix:px", PROPERTY_USAGE_EDITOR), "_set_size", "get_size"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "position", PROPERTY_HINT_NONE, "suffix:px", PROPERTY_USAGE_EDITOR), "_set_position", "get_position"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "global_position", PROPERTY_HINT_NONE, "suffix:px", PROPERTY_USAGE_NONE), "_set_global_position", "get_global_position"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "rotation", PROPERTY_HINT_RANGE, "-360,360,0.1,or_lesser,or_greater,radians"), "set_rotation", "get_rotation"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "scale"), "set_scale", "get_scale"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "pivot_offset"), "set_pivot_offset", "get_pivot_offset"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "pivot_offset", PROPERTY_HINT_NONE, "suffix:px"), "set_pivot_offset", "get_pivot_offset"); ADD_SUBGROUP("Container Sizing", "size_flags_"); ADD_PROPERTY(PropertyInfo(Variant::INT, "size_flags_horizontal", PROPERTY_HINT_FLAGS, "Fill:1,Expand:2,Shrink Center:4,Shrink End:8"), "set_h_size_flags", "get_h_size_flags"); @@ -3403,6 +3354,7 @@ void Control::_bind_methods() { ADD_GROUP("Mouse", "mouse_"); ADD_PROPERTY(PropertyInfo(Variant::INT, "mouse_filter", PROPERTY_HINT_ENUM, "Stop,Pass,Ignore"), "set_mouse_filter", "get_mouse_filter"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "mouse_force_pass_scroll_events"), "set_force_pass_scroll_events", "is_force_pass_scroll_events"); ADD_PROPERTY(PropertyInfo(Variant::INT, "mouse_default_cursor_shape", PROPERTY_HINT_ENUM, "Arrow,I-Beam,Pointing Hand,Cross,Wait,Busy,Drag,Can Drop,Forbidden,Vertical Resize,Horizontal Resize,Secondary Diagonal Resize,Main Diagonal Resize,Move,Vertical Split,Horizontal Split,Help"), "set_default_cursor_shape", "get_default_cursor_shape"); ADD_GROUP("Theme", "theme_"); @@ -3456,7 +3408,7 @@ void Control::_bind_methods() { BIND_ENUM_CONSTANT(PRESET_BOTTOM_WIDE); BIND_ENUM_CONSTANT(PRESET_VCENTER_WIDE); BIND_ENUM_CONSTANT(PRESET_HCENTER_WIDE); - BIND_ENUM_CONSTANT(PRESET_WIDE); + BIND_ENUM_CONSTANT(PRESET_FULL_RECT); BIND_ENUM_CONSTANT(PRESET_MODE_MINSIZE); BIND_ENUM_CONSTANT(PRESET_MODE_KEEP_WIDTH); @@ -3491,14 +3443,6 @@ void Control::_bind_methods() { BIND_ENUM_CONSTANT(TEXT_DIRECTION_LTR); BIND_ENUM_CONSTANT(TEXT_DIRECTION_RTL); - BIND_ENUM_CONSTANT(STRUCTURED_TEXT_DEFAULT); - BIND_ENUM_CONSTANT(STRUCTURED_TEXT_URI); - BIND_ENUM_CONSTANT(STRUCTURED_TEXT_FILE); - BIND_ENUM_CONSTANT(STRUCTURED_TEXT_EMAIL); - BIND_ENUM_CONSTANT(STRUCTURED_TEXT_LIST); - BIND_ENUM_CONSTANT(STRUCTURED_TEXT_NONE); - BIND_ENUM_CONSTANT(STRUCTURED_TEXT_CUSTOM); - ADD_SIGNAL(MethodInfo("resized")); ADD_SIGNAL(MethodInfo("gui_input", PropertyInfo(Variant::OBJECT, "event", PROPERTY_HINT_RESOURCE_TYPE, "InputEvent"))); ADD_SIGNAL(MethodInfo("mouse_entered")); diff --git a/scene/gui/control.h b/scene/gui/control.h index 4240d52b65..50cf9faeed 100644 --- a/scene/gui/control.h +++ b/scene/gui/control.h @@ -116,7 +116,7 @@ public: PRESET_BOTTOM_WIDE, PRESET_VCENTER_WIDE, PRESET_HCENTER_WIDE, - PRESET_WIDE + PRESET_FULL_RECT }; enum LayoutPresetMode { @@ -147,16 +147,6 @@ public: TEXT_DIRECTION_INHERITED, }; - enum StructuredTextParser { - STRUCTURED_TEXT_DEFAULT, - STRUCTURED_TEXT_URI, - STRUCTURED_TEXT_FILE, - STRUCTURED_TEXT_EMAIL, - STRUCTURED_TEXT_LIST, - STRUCTURED_TEXT_NONE, - STRUCTURED_TEXT_CUSTOM - }; - private: struct CComparator { bool operator()(const Control *p_a, const Control *p_b) const { @@ -200,6 +190,7 @@ private: Point2 custom_minimum_size; MouseFilter mouse_filter = MOUSE_FILTER_STOP; + bool force_pass_scroll_events = true; bool clip_contents = false; @@ -226,12 +217,12 @@ private: NodePath focus_prev; bool bulk_theme_override = false; - HashMap<StringName, Ref<Texture2D>> icon_override; - HashMap<StringName, Ref<StyleBox>> style_override; - HashMap<StringName, Ref<Font>> font_override; - HashMap<StringName, int> font_size_override; - HashMap<StringName, Color> color_override; - HashMap<StringName, int> constant_override; + Theme::ThemeIconMap icon_override; + Theme::ThemeStyleMap style_override; + Theme::ThemeFontMap font_override; + Theme::ThemeFontSizeMap font_size_override; + Theme::ThemeColorMap color_override; + Theme::ThemeConstantMap constant_override; } data; @@ -290,7 +281,7 @@ protected: //virtual void _window_gui_input(InputEvent p_event); - virtual Array structured_text_parser(StructuredTextParser p_theme_type, const Array &p_args, const String &p_text) const; + virtual Array structured_text_parser(TextServer::StructuredTextParser p_parser_type, const Array &p_args, const String &p_text) const; bool _set(const StringName &p_name, const Variant &p_value); bool _get(const StringName &p_name, Variant &r_ret) const; @@ -478,6 +469,9 @@ public: void set_mouse_filter(MouseFilter p_filter); MouseFilter get_mouse_filter() const; + void set_force_pass_scroll_events(bool p_force_pass_scroll_events); + bool is_force_pass_scroll_events() const; + /* SKINNING */ void begin_bulk_theme_override(); @@ -579,6 +573,5 @@ VARIANT_ENUM_CAST(Control::Anchor); VARIANT_ENUM_CAST(Control::LayoutMode); VARIANT_ENUM_CAST(Control::LayoutDirection); VARIANT_ENUM_CAST(Control::TextDirection); -VARIANT_ENUM_CAST(Control::StructuredTextParser); #endif diff --git a/scene/gui/dialogs.cpp b/scene/gui/dialogs.cpp index 0bb96a18a5..44e2bb89eb 100644 --- a/scene/gui/dialogs.cpp +++ b/scene/gui/dialogs.cpp @@ -154,11 +154,19 @@ bool AcceptDialog::get_close_on_escape() const { } void AcceptDialog::set_autowrap(bool p_autowrap) { - label->set_autowrap_mode(p_autowrap ? Label::AUTOWRAP_WORD : Label::AUTOWRAP_OFF); + label->set_autowrap_mode(p_autowrap ? TextServer::AUTOWRAP_WORD : TextServer::AUTOWRAP_OFF); } bool AcceptDialog::has_autowrap() { - return label->get_autowrap_mode() != Label::AUTOWRAP_OFF; + return label->get_autowrap_mode() != TextServer::AUTOWRAP_OFF; +} + +void AcceptDialog::set_ok_button_text(String p_ok_button_text) { + ok->set_text(p_ok_button_text); +} + +String AcceptDialog::get_ok_button_text() const { + return ok->get_text(); } void AcceptDialog::register_text_enter(Control *p_line_edit) { @@ -262,7 +270,7 @@ Button *AcceptDialog::add_button(const String &p_text, bool p_right, const Strin Button *AcceptDialog::add_cancel_button(const String &p_cancel) { String c = p_cancel; if (p_cancel.is_empty()) { - c = TTRC("Cancel"); + c = "Cancel"; } Button *b = swap_cancel_ok ? add_button(c, true) : add_button(c); b->connect("pressed", callable_mp(this, &AcceptDialog::_cancel_pressed)); @@ -306,11 +314,15 @@ void AcceptDialog::_bind_methods() { ClassDB::bind_method(D_METHOD("get_text"), &AcceptDialog::get_text); ClassDB::bind_method(D_METHOD("set_autowrap", "autowrap"), &AcceptDialog::set_autowrap); ClassDB::bind_method(D_METHOD("has_autowrap"), &AcceptDialog::has_autowrap); + ClassDB::bind_method(D_METHOD("set_ok_button_text", "text"), &AcceptDialog::set_ok_button_text); + ClassDB::bind_method(D_METHOD("get_ok_button_text"), &AcceptDialog::get_ok_button_text); ADD_SIGNAL(MethodInfo("confirmed")); ADD_SIGNAL(MethodInfo("cancelled")); ADD_SIGNAL(MethodInfo("custom_action", PropertyInfo(Variant::STRING_NAME, "action"))); + ADD_PROPERTY(PropertyInfo(Variant::STRING, "ok_button_text"), "set_ok_button_text", "get_ok_button_text"); + ADD_GROUP("Dialog", "dialog"); ADD_PROPERTY(PropertyInfo(Variant::STRING, "dialog_text", PROPERTY_HINT_MULTILINE_TEXT, "", PROPERTY_USAGE_DEFAULT_INTL), "set_text", "get_text"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "dialog_hide_on_ok"), "set_hide_on_ok", "get_hide_on_ok"); @@ -349,7 +361,7 @@ AcceptDialog::AcceptDialog() { hbc->add_spacer(); ok = memnew(Button); - ok->set_text(TTRC("OK")); + ok->set_text("OK"); hbc->add_child(ok); hbc->add_spacer(); @@ -365,8 +377,20 @@ AcceptDialog::~AcceptDialog() { // ConfirmationDialog +void ConfirmationDialog::set_cancel_button_text(String p_cancel_button_text) { + cancel->set_text(p_cancel_button_text); +} + +String ConfirmationDialog::get_cancel_button_text() const { + return cancel->get_text(); +} + void ConfirmationDialog::_bind_methods() { ClassDB::bind_method(D_METHOD("get_cancel_button"), &ConfirmationDialog::get_cancel_button); + ClassDB::bind_method(D_METHOD("set_cancel_button_text"), &ConfirmationDialog::set_cancel_button_text); + ClassDB::bind_method(D_METHOD("get_cancel_button_text"), &ConfirmationDialog::get_cancel_button_text); + + ADD_PROPERTY(PropertyInfo(Variant::STRING, "cancel_button_text"), "set_cancel_button_text", "get_cancel_button_text"); } Button *ConfirmationDialog::get_cancel_button() { diff --git a/scene/gui/dialogs.h b/scene/gui/dialogs.h index 41fd9c0a10..711361de88 100644 --- a/scene/gui/dialogs.h +++ b/scene/gui/dialogs.h @@ -97,6 +97,9 @@ public: void set_autowrap(bool p_autowrap); bool has_autowrap(); + void set_ok_button_text(String p_ok_button_text); + String get_ok_button_text() const; + AcceptDialog(); ~AcceptDialog(); }; @@ -110,6 +113,10 @@ protected: public: Button *get_cancel_button(); + + void set_cancel_button_text(String p_cancel_button_text); + String get_cancel_button_text() const; + ConfirmationDialog(); }; diff --git a/scene/gui/file_dialog.cpp b/scene/gui/file_dialog.cpp index c953dbf4c3..65bc359e2e 100644 --- a/scene/gui/file_dialog.cpp +++ b/scene/gui/file_dialog.cpp @@ -170,7 +170,11 @@ Vector<String> FileDialog::get_selected_files() const { }; void FileDialog::update_dir() { - dir->set_text(dir_access->get_current_dir(false)); + if (root_prefix.is_empty()) { + dir->set_text(dir_access->get_current_dir(false)); + } else { + dir->set_text(dir_access->get_current_dir(false).trim_prefix(root_prefix).trim_prefix("/")); + } if (drives->is_visible()) { if (dir_access->get_current_dir().is_network_share_path()) { @@ -188,10 +192,8 @@ void FileDialog::update_dir() { } void FileDialog::_dir_submitted(String p_dir) { - dir_access->change_dir(p_dir); + _change_dir(root_prefix.plus_file(p_dir)); file->set_text(""); - invalidate(); - update_dir(); _push_history(); } @@ -337,7 +339,7 @@ void FileDialog::_action_pressed() { } if (dir_access->file_exists(f)) { - confirm_save->set_text(TTRC("File exists, overwrite?")); + confirm_save->set_text(RTR("File exists, overwrite?")); confirm_save->popup_centered(Size2(200, 80)); } else { emit_signal(SNAME("file_selected"), f); @@ -378,9 +380,7 @@ bool FileDialog::_is_open_should_be_disabled() { } void FileDialog::_go_up() { - dir_access->change_dir(".."); - update_file_list(); - update_dir(); + _change_dir(".."); _push_history(); } @@ -390,9 +390,7 @@ void FileDialog::_go_back() { } local_history_pos--; - dir_access->change_dir(local_history[local_history_pos]); - update_file_list(); - update_dir(); + _change_dir(local_history[local_history_pos]); dir_prev->set_disabled(local_history_pos == 0); dir_next->set_disabled(local_history_pos == local_history.size() - 1); @@ -404,9 +402,7 @@ void FileDialog::_go_forward() { } local_history_pos++; - dir_access->change_dir(local_history[local_history_pos]); - update_file_list(); - update_dir(); + _change_dir(local_history[local_history_pos]); dir_prev->set_disabled(local_history_pos == 0); dir_next->set_disabled(local_history_pos == local_history.size() - 1); @@ -423,10 +419,10 @@ void FileDialog::deselect_all() { switch (mode) { case FILE_MODE_OPEN_FILE: case FILE_MODE_OPEN_FILES: - get_ok_button()->set_text(TTRC("Open")); + set_ok_button_text(RTR("Open")); break; case FILE_MODE_OPEN_DIR: - get_ok_button()->set_text(TTRC("Select Current Folder")); + set_ok_button_text(RTR("Select Current Folder")); break; case FILE_MODE_OPEN_ANY: case FILE_MODE_SAVE_FILE: @@ -450,7 +446,7 @@ void FileDialog::_tree_selected() { if (!d["dir"]) { file->set_text(d["name"]); } else if (mode == FILE_MODE_OPEN_DIR) { - get_ok_button()->set_text(TTRC("Select This Folder")); + set_ok_button_text(RTR("Select This Folder")); } get_ok_button()->set_disabled(_is_open_should_be_disabled()); @@ -465,12 +461,10 @@ void FileDialog::_tree_item_activated() { Dictionary d = ti->get_metadata(0); if (d["dir"]) { - dir_access->change_dir(d["name"]); + _change_dir(d["name"]); if (mode == FILE_MODE_OPEN_FILE || mode == FILE_MODE_OPEN_FILES || mode == FILE_MODE_OPEN_DIR || mode == FILE_MODE_OPEN_ANY) { file->set_text(""); } - call_deferred(SNAME("_update_file_list")); - call_deferred(SNAME("_update_dir")); _push_history(); } else { _action_pressed(); @@ -486,7 +480,12 @@ void FileDialog::update_file_name() { String filter_str = filters[idx]; String file_str = file->get_text(); String base_name = file_str.get_basename(); - file_str = base_name + "." + filter_str.strip_edges().to_lower(); + Vector<String> filter_substr = filter_str.split(";"); + if (filter_substr.size() >= 2) { + file_str = base_name + "." + filter_substr[0].strip_edges().get_extension().to_lower(); + } else { + file_str = base_name + "." + filter_str.strip_edges().get_extension().to_lower(); + } file->set_text(file_str); } } @@ -502,7 +501,7 @@ void FileDialog::update_file_list() { if (dir_access->is_readable(dir_access->get_current_dir().utf8().get_data())) { message->hide(); } else { - message->set_text(TTRC("You don't have permission to access contents of this folder.")); + message->set_text(RTR("You don't have permission to access contents of this folder.")); message->show(); } @@ -674,9 +673,13 @@ void FileDialog::clear_filters() { invalidate(); } -void FileDialog::add_filter(const String &p_filter) { +void FileDialog::add_filter(const String &p_filter, const String &p_description) { ERR_FAIL_COND_MSG(p_filter.begins_with("."), "Filter must be \"filename.extension\", can't start with dot."); - filters.push_back(p_filter); + if (p_description.is_empty()) { + filters.push_back(p_filter); + } else { + filters.push_back(vformat("%s ; %s", p_filter, p_description)); + } update_filters(); invalidate(); } @@ -704,9 +707,7 @@ String FileDialog::get_current_path() const { } void FileDialog::set_current_dir(const String &p_dir) { - dir_access->change_dir(p_dir); - update_dir(); - invalidate(); + _change_dir(p_dir); _push_history(); } @@ -732,6 +733,27 @@ void FileDialog::set_current_path(const String &p_path) { } } +void FileDialog::set_root_subfolder(const String &p_root) { + root_subfolder = p_root; + ERR_FAIL_COND_MSG(!dir_access->dir_exists(p_root), "root_subfolder must be an existing sub-directory."); + + local_history.clear(); + local_history_pos = -1; + + dir_access->change_dir(root_subfolder); + if (root_subfolder.is_empty()) { + root_prefix = ""; + } else { + root_prefix = dir_access->get_current_dir(); + } + invalidate(); + update_dir(); +} + +String FileDialog::get_root_subfolder() const { + return root_subfolder; +} + void FileDialog::set_mode_overrides_title(bool p_override) { mode_overrides_title = p_override; } @@ -746,35 +768,35 @@ void FileDialog::set_file_mode(FileMode p_mode) { mode = p_mode; switch (mode) { case FILE_MODE_OPEN_FILE: - get_ok_button()->set_text(TTRC("Open")); + set_ok_button_text(RTR("Open")); if (mode_overrides_title) { set_title(TTRC("Open a File")); } makedir->hide(); break; case FILE_MODE_OPEN_FILES: - get_ok_button()->set_text(TTRC("Open")); + set_ok_button_text(RTR("Open")); if (mode_overrides_title) { set_title(TTRC("Open File(s)")); } makedir->hide(); break; case FILE_MODE_OPEN_DIR: - get_ok_button()->set_text(TTRC("Select Current Folder")); + set_ok_button_text(RTR("Select Current Folder")); if (mode_overrides_title) { set_title(TTRC("Open a Directory")); } makedir->show(); break; case FILE_MODE_OPEN_ANY: - get_ok_button()->set_text(TTRC("Open")); + set_ok_button_text(RTR("Open")); if (mode_overrides_title) { set_title(TTRC("Open a File or Directory")); } makedir->show(); break; case FILE_MODE_SAVE_FILE: - get_ok_button()->set_text(TTRC("Save")); + set_ok_button_text(RTR("Save")); if (mode_overrides_title) { set_title(TTRC("Save a File")); } @@ -810,6 +832,8 @@ void FileDialog::set_access(Access p_access) { } break; } access = p_access; + root_prefix = ""; + root_subfolder = ""; _update_drives(); invalidate(); update_filters(); @@ -832,10 +856,8 @@ FileDialog::Access FileDialog::get_access() const { void FileDialog::_make_dir_confirm() { Error err = dir_access->make_dir(makedirname->get_text().strip_edges()); if (err == OK) { - dir_access->change_dir(makedirname->get_text().strip_edges()); - invalidate(); + _change_dir(makedirname->get_text().strip_edges()); update_filters(); - update_dir(); _push_history(); } else { mkdirerr->popup_centered(Size2(250, 50)); @@ -850,11 +872,25 @@ void FileDialog::_make_dir() { void FileDialog::_select_drive(int p_idx) { String d = drives->get_item_text(p_idx); - dir_access->change_dir(d); + _change_dir(d); file->set_text(""); + _push_history(); +} + +void FileDialog::_change_dir(const String &p_new_dir) { + if (root_prefix.is_empty()) { + dir_access->change_dir(p_new_dir); + } else { + String old_dir = dir_access->get_current_dir(); + dir_access->change_dir(p_new_dir); + if (!dir_access->get_current_dir(false).begins_with(root_prefix)) { + dir_access->change_dir(old_dir); + return; + } + } + invalidate(); update_dir(); - _push_history(); } void FileDialog::_update_drives(bool p_select) { @@ -887,7 +923,7 @@ void FileDialog::_bind_methods() { ClassDB::bind_method(D_METHOD("_cancel_pressed"), &FileDialog::_cancel_pressed); ClassDB::bind_method(D_METHOD("clear_filters"), &FileDialog::clear_filters); - ClassDB::bind_method(D_METHOD("add_filter", "filter"), &FileDialog::add_filter); + ClassDB::bind_method(D_METHOD("add_filter", "filter", "description"), &FileDialog::add_filter, DEFVAL("")); ClassDB::bind_method(D_METHOD("set_filters", "filters"), &FileDialog::set_filters); ClassDB::bind_method(D_METHOD("get_filters"), &FileDialog::get_filters); ClassDB::bind_method(D_METHOD("get_current_dir"), &FileDialog::get_current_dir); @@ -904,6 +940,8 @@ void FileDialog::_bind_methods() { ClassDB::bind_method(D_METHOD("get_line_edit"), &FileDialog::get_line_edit); ClassDB::bind_method(D_METHOD("set_access", "access"), &FileDialog::set_access); ClassDB::bind_method(D_METHOD("get_access"), &FileDialog::get_access); + ClassDB::bind_method(D_METHOD("set_root_subfolder", "dir"), &FileDialog::set_root_subfolder); + ClassDB::bind_method(D_METHOD("get_root_subfolder"), &FileDialog::get_root_subfolder); ClassDB::bind_method(D_METHOD("set_show_hidden_files", "show"), &FileDialog::set_show_hidden_files); ClassDB::bind_method(D_METHOD("is_showing_hidden_files"), &FileDialog::is_showing_hidden_files); ClassDB::bind_method(D_METHOD("_update_file_name"), &FileDialog::update_file_name); @@ -915,7 +953,8 @@ void FileDialog::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "mode_overrides_title"), "set_mode_overrides_title", "is_mode_overriding_title"); ADD_PROPERTY(PropertyInfo(Variant::INT, "file_mode", PROPERTY_HINT_ENUM, "Open File,Open Files,Open Folder,Open Any,Save"), "set_file_mode", "get_file_mode"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "access", PROPERTY_HINT_ENUM, "Resources,User data,File system"), "set_access", "get_access"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "access", PROPERTY_HINT_ENUM, "Resources,User Data,File System"), "set_access", "get_access"); + ADD_PROPERTY(PropertyInfo(Variant::STRING, "root_subfolder"), "set_root_subfolder", "get_root_subfolder"); ADD_PROPERTY(PropertyInfo(Variant::PACKED_STRING_ARRAY, "filters"), "set_filters", "get_filters"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "show_hidden_files"), "set_show_hidden_files", "is_showing_hidden_files"); ADD_PROPERTY(PropertyInfo(Variant::STRING, "current_dir", PROPERTY_HINT_DIR, "", PROPERTY_USAGE_NONE), "set_current_dir", "get_current_dir"); @@ -964,13 +1003,13 @@ FileDialog::FileDialog() { dir_prev = memnew(Button); dir_prev->set_flat(true); - dir_prev->set_tooltip(TTRC("Go to previous folder.")); + dir_prev->set_tooltip(RTR("Go to previous folder.")); dir_next = memnew(Button); dir_next->set_flat(true); - dir_next->set_tooltip(TTRC("Go to next folder.")); + dir_next->set_tooltip(RTR("Go to next folder.")); dir_up = memnew(Button); dir_up->set_flat(true); - dir_up->set_tooltip(TTRC("Go to parent folder.")); + dir_up->set_tooltip(RTR("Go to parent folder.")); hbc->add_child(dir_prev); hbc->add_child(dir_next); hbc->add_child(dir_up); @@ -978,7 +1017,7 @@ FileDialog::FileDialog() { dir_next->connect("pressed", callable_mp(this, &FileDialog::_go_forward)); dir_up->connect("pressed", callable_mp(this, &FileDialog::_go_up)); - hbc->add_child(memnew(Label(TTRC("Path:")))); + hbc->add_child(memnew(Label(RTR("Path:")))); drives_container = memnew(HBoxContainer); hbc->add_child(drives_container); @@ -988,13 +1027,13 @@ FileDialog::FileDialog() { hbc->add_child(drives); dir = memnew(LineEdit); - dir->set_structured_text_bidi_override(Control::STRUCTURED_TEXT_FILE); + dir->set_structured_text_bidi_override(TextServer::STRUCTURED_TEXT_FILE); hbc->add_child(dir); dir->set_h_size_flags(Control::SIZE_EXPAND_FILL); refresh = memnew(Button); refresh->set_flat(true); - refresh->set_tooltip(TTRC("Refresh files.")); + refresh->set_tooltip(RTR("Refresh files.")); refresh->connect("pressed", callable_mp(this, &FileDialog::update_file_list)); hbc->add_child(refresh); @@ -1002,7 +1041,7 @@ FileDialog::FileDialog() { show_hidden->set_flat(true); show_hidden->set_toggle_mode(true); show_hidden->set_pressed(is_showing_hidden_files()); - show_hidden->set_tooltip(TTRC("Toggle the visibility of hidden files.")); + show_hidden->set_tooltip(RTR("Toggle the visibility of hidden files.")); show_hidden->connect("toggled", callable_mp(this, &FileDialog::set_show_hidden_files)); hbc->add_child(show_hidden); @@ -1010,26 +1049,26 @@ FileDialog::FileDialog() { hbc->add_child(shortcuts_container); makedir = memnew(Button); - makedir->set_text(TTRC("Create Folder")); + makedir->set_text(RTR("Create Folder")); makedir->connect("pressed", callable_mp(this, &FileDialog::_make_dir)); hbc->add_child(makedir); vbox->add_child(hbc); tree = memnew(Tree); tree->set_hide_root(true); - vbox->add_margin_child(TTRC("Directories & Files:"), tree, true); + vbox->add_margin_child(RTR("Directories & Files:"), tree, true); message = memnew(Label); message->hide(); - message->set_anchors_and_offsets_preset(Control::PRESET_WIDE); + message->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT); message->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER); message->set_vertical_alignment(VERTICAL_ALIGNMENT_CENTER); tree->add_child(message); file_box = memnew(HBoxContainer); - file_box->add_child(memnew(Label(TTRC("File:")))); + file_box->add_child(memnew(Label(RTR("File:")))); file = memnew(LineEdit); - file->set_structured_text_bidi_override(Control::STRUCTURED_TEXT_FILE); + file->set_structured_text_bidi_override(TextServer::STRUCTURED_TEXT_FILE); file->set_stretch_ratio(4); file->set_h_size_flags(Control::SIZE_EXPAND_FILL); file_box->add_child(file); @@ -1058,22 +1097,22 @@ FileDialog::FileDialog() { confirm_save->connect("confirmed", callable_mp(this, &FileDialog::_save_confirm_pressed)); makedialog = memnew(ConfirmationDialog); - makedialog->set_title(TTRC("Create Folder")); + makedialog->set_title(RTR("Create Folder")); VBoxContainer *makevb = memnew(VBoxContainer); makedialog->add_child(makevb); makedirname = memnew(LineEdit); - makedirname->set_structured_text_bidi_override(Control::STRUCTURED_TEXT_FILE); - makevb->add_margin_child(TTRC("Name:"), makedirname); + makedirname->set_structured_text_bidi_override(TextServer::STRUCTURED_TEXT_FILE); + makevb->add_margin_child(RTR("Name:"), makedirname); add_child(makedialog, false, INTERNAL_MODE_FRONT); makedialog->register_text_enter(makedirname); makedialog->connect("confirmed", callable_mp(this, &FileDialog::_make_dir_confirm)); mkdirerr = memnew(AcceptDialog); - mkdirerr->set_text(TTRC("Could not create folder.")); + mkdirerr->set_text(RTR("Could not create folder.")); add_child(mkdirerr, false, INTERNAL_MODE_FRONT); exterr = memnew(AcceptDialog); - exterr->set_text(TTRC("Must use a valid extension.")); + exterr->set_text(RTR("Must use a valid extension.")); add_child(exterr, false, INTERNAL_MODE_FRONT); update_filters(); diff --git a/scene/gui/file_dialog.h b/scene/gui/file_dialog.h index 2e326d2949..017c9d8d4f 100644 --- a/scene/gui/file_dialog.h +++ b/scene/gui/file_dialog.h @@ -101,6 +101,8 @@ private: void _push_history(); bool mode_overrides_title = true; + String root_subfolder; + String root_prefix; static bool default_show_hidden_files; bool show_hidden_files = false; @@ -131,6 +133,7 @@ private: void _go_back(); void _go_forward(); + void _change_dir(const String &p_new_dir); void _update_drives(bool p_select = true); virtual void shortcut_input(const Ref<InputEvent> &p_event) override; @@ -148,7 +151,7 @@ protected: public: void popup_file_dialog(); void clear_filters(); - void add_filter(const String &p_filter); + void add_filter(const String &p_filter, const String &p_description = ""); void set_filters(const Vector<String> &p_filters); Vector<String> get_filters() const; @@ -162,6 +165,9 @@ public: void set_current_file(const String &p_file); void set_current_path(const String &p_path); + void set_root_subfolder(const String &p_root); + String get_root_subfolder() const; + void set_mode_overrides_title(bool p_override); bool is_mode_overriding_title() const; diff --git a/scene/gui/flow_container.cpp b/scene/gui/flow_container.cpp index 40aca555db..30b694da76 100644 --- a/scene/gui/flow_container.cpp +++ b/scene/gui/flow_container.cpp @@ -44,12 +44,12 @@ void FlowContainer::_resort() { return; } - int separation_horizontal = get_theme_constant(SNAME("hseparation")); - int separation_vertical = get_theme_constant(SNAME("vseparation")); + int separation_horizontal = get_theme_constant(SNAME("h_separation")); + int separation_vertical = get_theme_constant(SNAME("v_separation")); bool rtl = is_layout_rtl(); - Map<Control *, Size2i> children_minsize_cache; + HashMap<Control *, Size2i> children_minsize_cache; Vector<_LineData> lines_data; diff --git a/scene/gui/gradient_edit.cpp b/scene/gui/gradient_edit.cpp index 0690acbe16..9459bed63b 100644 --- a/scene/gui/gradient_edit.cpp +++ b/scene/gui/gradient_edit.cpp @@ -382,7 +382,7 @@ void GradientEdit::_color_changed(const Color &p_color) { emit_signal(SNAME("ramp_changed")); } -void GradientEdit::set_ramp(const Vector<real_t> &p_offsets, const Vector<Color> &p_colors) { +void GradientEdit::set_ramp(const Vector<float> &p_offsets, const Vector<Color> &p_colors) { ERR_FAIL_COND(p_offsets.size() != p_colors.size()); points.clear(); for (int i = 0; i < p_offsets.size(); i++) { @@ -396,8 +396,8 @@ void GradientEdit::set_ramp(const Vector<real_t> &p_offsets, const Vector<Color> update(); } -Vector<real_t> GradientEdit::get_offsets() const { - Vector<real_t> ret; +Vector<float> GradientEdit::get_offsets() const { + Vector<float> ret; for (int i = 0; i < points.size(); i++) { ret.push_back(points[i].offset); } diff --git a/scene/gui/gradient_edit.h b/scene/gui/gradient_edit.h index 4e3c6525f9..3badcd45ba 100644 --- a/scene/gui/gradient_edit.h +++ b/scene/gui/gradient_edit.h @@ -67,8 +67,8 @@ protected: static void _bind_methods(); public: - void set_ramp(const Vector<real_t> &p_offsets, const Vector<Color> &p_colors); - Vector<real_t> get_offsets() const; + void set_ramp(const Vector<float> &p_offsets, const Vector<Color> &p_colors); + Vector<float> get_offsets() const; Vector<Color> get_colors() const; void set_points(Vector<Gradient::Point> &p_points); Vector<Gradient::Point> &get_points(); diff --git a/scene/gui/graph_edit.cpp b/scene/gui/graph_edit.cpp index 8b7e0f22b9..e30759aa3e 100644 --- a/scene/gui/graph_edit.cpp +++ b/scene/gui/graph_edit.cpp @@ -426,8 +426,8 @@ void GraphEdit::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_TREE: case NOTIFICATION_THEME_CHANGED: { - port_grab_distance_horizontal = get_theme_constant(SNAME("port_grab_distance_horizontal")); - port_grab_distance_vertical = get_theme_constant(SNAME("port_grab_distance_vertical")); + port_hotzone_inner_extent = get_theme_constant("port_hotzone_inner_extent"); + port_hotzone_outer_extent = get_theme_constant("port_hotzone_outer_extent"); zoom_minus->set_icon(get_theme_icon(SNAME("minus"))); zoom_reset->set_icon(get_theme_icon(SNAME("reset"))); @@ -508,8 +508,9 @@ 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; + comment_node_rect.size *= zoom; + 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()) { @@ -517,13 +518,15 @@ void GraphEdit::_update_comment_enclosed_nodes_list(GraphNode *p_node, HashMap<S } Rect2 node_rect = gn->get_rect(); + node_rect.size *= zoom; + 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); + p_comment_enclosed_nodes.insert(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) { @@ -544,8 +547,7 @@ void GraphEdit::_set_position_of_comment_enclosed_nodes(GraphNode *p_node, HashM } 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()); + Ref<Texture2D> port_icon = get_theme_icon(SNAME("port"), SNAME("GraphNode")); for (int i = get_child_count() - 1; i >= 0; i--) { GraphNode *gn = Object::cast_to<GraphNode>(get_child(i)); @@ -553,14 +555,18 @@ bool GraphEdit::_filter_input(const Point2 &p_point) { continue; } - for (int j = 0; j < gn->get_connection_output_count(); j++) { - if (is_in_output_hotzone(gn, j, p_point / zoom, port_size)) { + for (int j = 0; j < gn->get_connection_input_count(); j++) { + Vector2i port_size = Vector2i(port_icon->get_width(), port_icon->get_height()); + port_size.height = MAX(port_size.height, gn->get_connection_input_height(j)); + if (is_in_input_hotzone(gn, j, p_point / zoom, port_size)) { return true; } } - for (int j = 0; j < gn->get_connection_input_count(); j++) { - if (is_in_input_hotzone(gn, j, p_point / zoom, port_size)) { + for (int j = 0; j < gn->get_connection_output_count(); j++) { + Vector2i port_size = Vector2i(port_icon->get_width(), port_icon->get_height()); + port_size.height = MAX(port_size.height, gn->get_connection_output_height(j)); + if (is_in_output_hotzone(gn, j, p_point / zoom, port_size)) { return true; } } @@ -572,8 +578,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() == 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()); + Ref<Texture2D> port_icon = get_theme_icon(SNAME("port"), SNAME("GraphNode")); connecting_valid = false; click_pos = mb->get_position() / zoom; @@ -585,6 +590,9 @@ void GraphEdit::_top_layer_input(const Ref<InputEvent> &p_ev) { for (int j = 0; j < gn->get_connection_output_count(); j++) { Vector2 pos = gn->get_connection_output_position(j) + gn->get_position(); + Vector2i port_size = Vector2i(port_icon->get_width(), port_icon->get_height()); + port_size.height = MAX(port_size.height, gn->get_connection_output_height(j)); + if (is_in_output_hotzone(gn, j, click_pos, port_size)) { if (valid_left_disconnect_types.has(gn->get_connection_output_type(j))) { //check disconnect @@ -629,6 +637,10 @@ void GraphEdit::_top_layer_input(const Ref<InputEvent> &p_ev) { for (int j = 0; j < gn->get_connection_input_count(); j++) { Vector2 pos = gn->get_connection_input_position(j) + gn->get_position(); + + Vector2i port_size = Vector2i(port_icon->get_width(), port_icon->get_height()); + port_size.height = MAX(port_size.height, gn->get_connection_input_height(j)); + if (is_in_input_hotzone(gn, j, click_pos, port_size)) { if (right_disconnects || valid_right_disconnect_types.has(gn->get_connection_input_type(j))) { //check disconnect @@ -682,11 +694,9 @@ void GraphEdit::_top_layer_input(const Ref<InputEvent> &p_ev) { connecting_valid = just_disconnected || click_pos.distance_to(connecting_to / zoom) > 20.0; if (connecting_valid) { - Ref<Texture2D> port = get_theme_icon(SNAME("port"), SNAME("GraphNode")); - Vector2i port_size = Vector2i(port->get_width(), port->get_height()); - Vector2 mpos = mm->get_position() / zoom; for (int i = get_child_count() - 1; i >= 0; i--) { + Ref<Texture2D> port_icon = get_theme_icon(SNAME("port"), SNAME("GraphNode")); GraphNode *gn = Object::cast_to<GraphNode>(get_child(i)); if (!gn) { continue; @@ -695,6 +705,9 @@ void GraphEdit::_top_layer_input(const Ref<InputEvent> &p_ev) { if (!connecting_out) { for (int j = 0; j < gn->get_connection_output_count(); j++) { Vector2 pos = gn->get_connection_output_position(j) + gn->get_position(); + Vector2i port_size = Vector2i(port_icon->get_width(), port_icon->get_height()); + port_size.height = MAX(port_size.height, gn->get_connection_output_height(j)); + int type = gn->get_connection_output_type(j); if ((type == connecting_type || valid_connection_types.has(ConnType(connecting_type, type))) && is_in_output_hotzone(gn, j, mpos, port_size)) { connecting_target = true; @@ -707,6 +720,9 @@ void GraphEdit::_top_layer_input(const Ref<InputEvent> &p_ev) { } else { for (int j = 0; j < gn->get_connection_input_count(); j++) { Vector2 pos = gn->get_connection_input_position(j) + gn->get_position(); + Vector2i port_size = Vector2i(port_icon->get_width(), port_icon->get_height()); + port_size.height = MAX(port_size.height, gn->get_connection_input_height(j)); + int type = gn->get_connection_input_type(j); if ((type == connecting_type || valid_connection_types.has(ConnType(connecting_type, type))) && is_in_input_hotzone(gn, j, mpos, port_size)) { connecting_target = true; @@ -754,19 +770,24 @@ void GraphEdit::_top_layer_input(const Ref<InputEvent> &p_ev) { } } -bool GraphEdit::_check_clickable_control(Control *p_control, const Vector2 &pos) { - if (p_control->is_set_as_top_level() || !p_control->is_visible()) { +bool GraphEdit::_check_clickable_control(Control *p_control, const Vector2 &mpos, const Vector2 &p_offset) { + if (p_control->is_set_as_top_level() || !p_control->is_visible() || !p_control->is_inside_tree()) { return false; } - if (!p_control->has_point(pos) || p_control->get_mouse_filter() == MOUSE_FILTER_IGNORE) { - //test children + Rect2 control_rect = p_control->get_rect(); + control_rect.size *= zoom; + control_rect.position *= zoom; + control_rect.position += p_offset; + + if (!control_rect.has_point(mpos) || p_control->get_mouse_filter() == MOUSE_FILTER_IGNORE) { + // Test children. for (int i = 0; i < p_control->get_child_count(); i++) { - Control *subchild = Object::cast_to<Control>(p_control->get_child(i)); - if (!subchild) { + Control *child_rect = Object::cast_to<Control>(p_control->get_child(i)); + if (!child_rect) { continue; } - if (_check_clickable_control(subchild, pos - subchild->get_position())) { + if (_check_clickable_control(child_rect, mpos, control_rect.position)) { return true; } } @@ -798,7 +819,13 @@ bool GraphEdit::is_in_output_hotzone(GraphNode *p_graph_node, int p_slot_index, } bool GraphEdit::is_in_port_hotzone(const Vector2 &pos, const Vector2 &p_mouse_pos, const Vector2i &p_port_size, bool p_left) { - if (!Rect2(pos.x - port_grab_distance_horizontal, pos.y - port_grab_distance_vertical, port_grab_distance_horizontal * 2, port_grab_distance_vertical * 2).has_point(p_mouse_pos)) { + Rect2 hotzone = Rect2( + pos.x - (p_left ? port_hotzone_outer_extent : port_hotzone_inner_extent), + pos.y - p_port_size.height / 2.0, + port_hotzone_inner_extent + port_hotzone_outer_extent, + p_port_size.height); + + if (!hotzone.has_point(p_mouse_pos)) { return false; } @@ -807,23 +834,17 @@ bool GraphEdit::is_in_port_hotzone(const Vector2 &pos, const Vector2 &p_mouse_po if (!child) { continue; } - Rect2 rect = child->get_rect(); - - // To prevent intersections with other nodes. - rect.position *= zoom; - rect.size *= zoom; - - if (rect.has_point(p_mouse_pos)) { - //check sub-controls - Vector2 subpos = p_mouse_pos - rect.position; + Rect2 child_rect = child->get_rect(); + child_rect.size *= zoom; + if (child_rect.has_point(p_mouse_pos * zoom)) { for (int j = 0; j < child->get_child_count(); j++) { Control *subchild = Object::cast_to<Control>(child->get_child(j)); if (!subchild) { continue; } - if (_check_clickable_control(subchild, subpos - subchild->get_position())) { + if (_check_clickable_control(subchild, p_mouse_pos * zoom, child_rect.position)) { return false; } } @@ -839,13 +860,23 @@ PackedVector2Array GraphEdit::get_connection_line(const Vector2 &p_from, const V return ret; } + float x_diff = (p_to.x - p_from.x); + float cp_offset = x_diff * lines_curvature; + if (x_diff < 0) { + cp_offset *= -1; + } + Curve2D curve; - Vector<Color> colors; curve.add_point(p_from); - curve.set_point_out(0, Vector2(60, 0)); + curve.set_point_out(0, Vector2(cp_offset, 0)); curve.add_point(p_to); - curve.set_point_in(1, Vector2(-60, 0)); - return curve.tessellate(); + curve.set_point_in(1, Vector2(-cp_offset, 0)); + + if (lines_curvature > 0) { + return curve.tessellate(5, 2.0); + } else { + return curve.tessellate(1); + } } void GraphEdit::_draw_connection_line(CanvasItem *p_where, const Vector2 &p_from, const Vector2 &p_to, const Color &p_color, const Color &p_to_color, float p_width, float p_zoom) { @@ -981,7 +1012,7 @@ void GraphEdit::_minimap_draw() { Ref<StyleBoxFlat> sb_minimap = minimap->get_theme_stylebox(SNAME("node"))->duplicate(); // Override default values with colors provided by the GraphNode's stylebox, if possible. - Ref<StyleBoxFlat> sbf = gn->get_theme_stylebox(gn->is_selected() ? "commentfocus" : "comment"); + Ref<StyleBoxFlat> sbf = gn->get_theme_stylebox(gn->is_selected() ? "comment_focus" : "comment"); if (sbf.is_valid()) { Color node_color = sbf->get_bg_color(); sb_minimap->set_bg_color(node_color); @@ -1004,7 +1035,7 @@ void GraphEdit::_minimap_draw() { Ref<StyleBoxFlat> sb_minimap = minimap->get_theme_stylebox(SNAME("node"))->duplicate(); // Override default values with colors provided by the GraphNode's stylebox, if possible. - Ref<StyleBoxFlat> sbf = gn->get_theme_stylebox(gn->is_selected() ? "selectedframe" : "frame"); + Ref<StyleBoxFlat> sbf = gn->get_theme_stylebox(gn->is_selected() ? "selected_frame" : "frame"); if (sbf.is_valid()) { Color node_color = sbf->get_border_color(); sb_minimap->set_bg_color(node_color); @@ -1343,7 +1374,19 @@ void GraphEdit::gui_input(const Ref<InputEvent> &p_ev) { emit_signal(SNAME("paste_nodes_request")); accept_event(); } else if (p_ev->is_action("ui_graph_delete")) { - emit_signal(SNAME("delete_nodes_request")); + TypedArray<StringName> nodes; + + for (int i = 0; i < get_child_count(); i++) { + GraphNode *gn = Object::cast_to<GraphNode>(get_child(i)); + if (!gn) { + continue; + } + if (gn->is_selected() && gn->is_close_button_visible()) { + nodes.push_back(gn->get_name()); + } + } + + emit_signal(SNAME("delete_nodes_request"), nodes); accept_event(); } } @@ -1654,6 +1697,15 @@ void GraphEdit::_minimap_toggled() { } } +void GraphEdit::set_connection_lines_curvature(float p_curvature) { + lines_curvature = p_curvature; + update(); +} + +float GraphEdit::get_connection_lines_curvature() const { + return lines_curvature; +} + void GraphEdit::set_connection_lines_thickness(float p_thickness) { lines_thickness = p_thickness; update(); @@ -1684,11 +1736,11 @@ void GraphEdit::set_warped_panning(bool p_warped) { warped_panning = p_warped; } -int GraphEdit::_set_operations(SET_OPERATIONS p_operation, Set<StringName> &r_u, const Set<StringName> &r_v) { +int GraphEdit::_set_operations(SET_OPERATIONS p_operation, HashSet<StringName> &r_u, const HashSet<StringName> &r_v) { switch (p_operation) { case GraphEdit::IS_EQUAL: { - for (Set<StringName>::Element *E = r_u.front(); E; E = E->next()) { - if (!r_v.has(E->get())) { + for (const StringName &E : r_u) { + if (!r_v.has(E)) { return 0; } } @@ -1698,25 +1750,28 @@ int GraphEdit::_set_operations(SET_OPERATIONS p_operation, Set<StringName> &r_u, if (r_u.size() == r_v.size() && !r_u.size()) { return 1; } - for (Set<StringName>::Element *E = r_u.front(); E; E = E->next()) { - if (!r_v.has(E->get())) { + for (const StringName &E : r_u) { + if (!r_v.has(E)) { return 0; } } return 1; } break; case GraphEdit::DIFFERENCE: { - for (Set<StringName>::Element *E = r_u.front(); E; E = E->next()) { - if (r_v.has(E->get())) { - r_u.erase(E->get()); + for (HashSet<StringName>::Iterator E = r_u.begin(); E;) { + HashSet<StringName>::Iterator N = E; + ++N; + if (r_v.has(*E)) { + r_u.remove(E); } + E = N; } return r_u.size(); } break; case GraphEdit::UNION: { - for (Set<StringName>::Element *E = r_v.front(); E; E = E->next()) { - if (!r_u.has(E->get())) { - r_u.insert(E->get()); + for (const StringName &E : r_v) { + if (!r_u.has(E)) { + r_u.insert(E); } } return r_u.size(); @@ -1727,28 +1782,28 @@ int GraphEdit::_set_operations(SET_OPERATIONS p_operation, Set<StringName> &r_u, return -1; } -HashMap<int, Vector<StringName>> GraphEdit::_layering(const Set<StringName> &r_selected_nodes, const HashMap<StringName, Set<StringName>> &r_upper_neighbours) { +HashMap<int, Vector<StringName>> GraphEdit::_layering(const HashSet<StringName> &r_selected_nodes, const HashMap<StringName, HashSet<StringName>> &r_upper_neighbours) { HashMap<int, Vector<StringName>> l; - Set<StringName> p = r_selected_nodes, q = r_selected_nodes, u, z; + HashSet<StringName> p = r_selected_nodes, q = r_selected_nodes, u, z; int current_layer = 0; bool selected = false; while (!_set_operations(GraphEdit::IS_EQUAL, q, u)) { _set_operations(GraphEdit::DIFFERENCE, p, u); - for (const Set<StringName>::Element *E = p.front(); E; E = E->next()) { - Set<StringName> n = r_upper_neighbours[E->get()]; + for (const StringName &E : p) { + HashSet<StringName> n = r_upper_neighbours[E]; if (_set_operations(GraphEdit::IS_SUBSET, n, z)) { Vector<StringName> t; - t.push_back(E->get()); + t.push_back(E); if (!l.has(current_layer)) { - l.set(current_layer, Vector<StringName>{}); + l.insert(current_layer, Vector<StringName>{}); } selected = true; t.append_array(l[current_layer]); - l.set(current_layer, t); - Set<StringName> V; - V.insert(E->get()); + l.insert(current_layer, t); + HashSet<StringName> V; + V.insert(E); _set_operations(GraphEdit::UNION, u, V); } } @@ -1789,10 +1844,10 @@ Vector<StringName> GraphEdit::_split(const Vector<StringName> &r_layer, const Ha return left; } -void GraphEdit::_horizontal_alignment(Dictionary &r_root, Dictionary &r_align, const HashMap<int, Vector<StringName>> &r_layers, const HashMap<StringName, Set<StringName>> &r_upper_neighbours, const Set<StringName> &r_selected_nodes) { - for (const Set<StringName>::Element *E = r_selected_nodes.front(); E; E = E->next()) { - r_root[E->get()] = E->get(); - r_align[E->get()] = E->get(); +void GraphEdit::_horizontal_alignment(Dictionary &r_root, Dictionary &r_align, const HashMap<int, Vector<StringName>> &r_layers, const HashMap<StringName, HashSet<StringName>> &r_upper_neighbours, const HashSet<StringName> &r_selected_nodes) { + for (const StringName &E : r_selected_nodes) { + r_root[E] = E; + r_align[E] = E; } if (r_layers.size() == 1) { @@ -1829,7 +1884,7 @@ void GraphEdit::_horizontal_alignment(Dictionary &r_root, Dictionary &r_align, c } } -void GraphEdit::_crossing_minimisation(HashMap<int, Vector<StringName>> &r_layers, const HashMap<StringName, Set<StringName>> &r_upper_neighbours) { +void GraphEdit::_crossing_minimisation(HashMap<int, Vector<StringName>> &r_layers, const HashMap<StringName, HashSet<StringName>> &r_upper_neighbours) { if (r_layers.size() == 1) { return; } @@ -1860,17 +1915,17 @@ void GraphEdit::_crossing_minimisation(HashMap<int, Vector<StringName>> &r_layer } d[q] = crossings; } - c.set(p, d); + c.insert(p, d); } - r_layers.set(i, _split(lower_layer, c)); + r_layers.insert(i, _split(lower_layer, c)); } } -void GraphEdit::_calculate_inner_shifts(Dictionary &r_inner_shifts, const Dictionary &r_root, const Dictionary &r_node_names, const Dictionary &r_align, const Set<StringName> &r_block_heads, const HashMap<StringName, Pair<int, int>> &r_port_info) { - for (const Set<StringName>::Element *E = r_block_heads.front(); E; E = E->next()) { +void GraphEdit::_calculate_inner_shifts(Dictionary &r_inner_shifts, const Dictionary &r_root, const Dictionary &r_node_names, const Dictionary &r_align, const HashSet<StringName> &r_block_heads, const HashMap<StringName, Pair<int, int>> &r_port_info) { + for (const StringName &E : r_block_heads) { real_t left = 0; - StringName u = E->get(); + StringName u = E; StringName v = r_align[u]; while (u != v && (StringName)r_root[u] != v) { String _connection = String(u) + " " + String(v); @@ -1891,11 +1946,11 @@ void GraphEdit::_calculate_inner_shifts(Dictionary &r_inner_shifts, const Dictio v = (StringName)r_align[v]; } - u = E->get(); + u = E; do { r_inner_shifts[u] = (real_t)r_inner_shifts[u] - left; u = (StringName)r_align[u]; - } while (u != E->get()); + } while (u != E); } } @@ -2026,7 +2081,7 @@ void GraphEdit::_place_block(StringName p_v, float p_delta, const HashMap<int, V threshold = _calculate_threshold(p_v, w, r_node_name, r_layers, r_root, r_align, r_inner_shift, threshold, r_node_positions); w = r_align[w]; } while (w != p_v); - r_node_positions.set(p_v, pos); + r_node_positions.insert(p_v, pos); } #undef PRED @@ -2040,7 +2095,7 @@ void GraphEdit::arrange_nodes() { } Dictionary node_names; - Set<StringName> selected_nodes; + HashSet<StringName> selected_nodes; for (int i = get_child_count() - 1; i >= 0; i--) { GraphNode *gn = Object::cast_to<GraphNode>(get_child(i)); @@ -2051,7 +2106,7 @@ void GraphEdit::arrange_nodes() { node_names[gn->get_name()] = gn; } - HashMap<StringName, Set<StringName>> upper_neighbours; + HashMap<StringName, HashSet<StringName>> upper_neighbours; HashMap<StringName, Pair<int, int>> port_info; Vector2 origin(FLT_MAX, FLT_MAX); @@ -2066,7 +2121,7 @@ void GraphEdit::arrange_nodes() { if (gn->is_selected()) { selected_nodes.insert(gn->get_name()); - Set<StringName> s; + HashSet<StringName> s; for (List<Connection>::Element *E = connections.front(); E; E = E->next()) { GraphNode *p_from = Object::cast_to<GraphNode>(node_names[E->get().from]); if (E->get().to == gn->get_name() && p_from->is_selected()) { @@ -2082,10 +2137,10 @@ void GraphEdit::arrange_nodes() { ports = p_ports; } } - port_info.set(_connection, ports); + port_info.insert(_connection, ports); } } - upper_neighbours.set(gn->get_name(), s); + upper_neighbours.insert(gn->get_name(), s); } } @@ -2103,35 +2158,35 @@ void GraphEdit::arrange_nodes() { HashMap<StringName, Vector2> new_positions; Vector2 default_position(FLT_MAX, FLT_MAX); Dictionary inner_shift; - Set<StringName> block_heads; + HashSet<StringName> block_heads; - for (const Set<StringName>::Element *E = selected_nodes.front(); E; E = E->next()) { - inner_shift[E->get()] = 0.0f; - sink[E->get()] = E->get(); - shift[E->get()] = FLT_MAX; - new_positions.set(E->get(), default_position); - if ((StringName)root[E->get()] == E->get()) { - block_heads.insert(E->get()); + for (const StringName &E : selected_nodes) { + inner_shift[E] = 0.0f; + sink[E] = E; + shift[E] = FLT_MAX; + new_positions.insert(E, default_position); + if ((StringName)root[E] == E) { + block_heads.insert(E); } } _calculate_inner_shifts(inner_shift, root, node_names, align, block_heads, port_info); - for (const Set<StringName>::Element *E = block_heads.front(); E; E = E->next()) { - _place_block(E->get(), gap_v, layers, root, align, node_names, inner_shift, sink, shift, new_positions); + for (const StringName &E : block_heads) { + _place_block(E, gap_v, layers, root, align, node_names, inner_shift, sink, shift, new_positions); } origin.y = Object::cast_to<GraphNode>(node_names[layers[0][0]])->get_position_offset().y - (new_positions[layers[0][0]].y + (float)inner_shift[layers[0][0]]); origin.x = Object::cast_to<GraphNode>(node_names[layers[0][0]])->get_position_offset().x; - for (const Set<StringName>::Element *E = block_heads.front(); E; E = E->next()) { - StringName u = E->get(); - float start_from = origin.y + new_positions[E->get()].y; + for (const StringName &E : block_heads) { + StringName u = E; + float start_from = origin.y + new_positions[E].y; do { Vector2 cal_pos; cal_pos.y = start_from + (real_t)inner_shift[u]; - new_positions.set(u, cal_pos); + new_positions.insert(u, cal_pos); u = align[u]; - } while (u != E->get()); + } while (u != E); } // Compute horizontal coordinates individually for layers to get uniform gap. @@ -2161,7 +2216,7 @@ void GraphEdit::arrange_nodes() { } cal_pos.x = current_node_start_pos; } - new_positions.set(layer[j], cal_pos); + new_positions.insert(layer[j], cal_pos); } start_from += largest_node_size + gap_h; @@ -2169,10 +2224,10 @@ void GraphEdit::arrange_nodes() { } emit_signal(SNAME("begin_node_move")); - for (const Set<StringName>::Element *E = selected_nodes.front(); E; E = E->next()) { - GraphNode *gn = Object::cast_to<GraphNode>(node_names[E->get()]); + for (const StringName &E : selected_nodes) { + GraphNode *gn = Object::cast_to<GraphNode>(node_names[E]); gn->set_drag(true); - Vector2 pos = (new_positions[E->get()]); + Vector2 pos = (new_positions[E]); if (is_using_snap()) { const int snap = get_snap(); @@ -2194,7 +2249,7 @@ void GraphEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("clear_connections"), &GraphEdit::clear_connections); ClassDB::bind_method(D_METHOD("force_connection_drag_end"), &GraphEdit::force_connection_drag_end); ClassDB::bind_method(D_METHOD("get_scroll_ofs"), &GraphEdit::get_scroll_ofs); - ClassDB::bind_method(D_METHOD("set_scroll_ofs", "ofs"), &GraphEdit::set_scroll_ofs); + ClassDB::bind_method(D_METHOD("set_scroll_ofs", "offset"), &GraphEdit::set_scroll_ofs); ClassDB::bind_method(D_METHOD("add_valid_right_disconnect_type", "type"), &GraphEdit::add_valid_right_disconnect_type); ClassDB::bind_method(D_METHOD("remove_valid_right_disconnect_type", "type"), &GraphEdit::remove_valid_right_disconnect_type); @@ -2229,6 +2284,9 @@ void GraphEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("set_use_snap", "enable"), &GraphEdit::set_use_snap); ClassDB::bind_method(D_METHOD("is_using_snap"), &GraphEdit::is_using_snap); + ClassDB::bind_method(D_METHOD("set_connection_lines_curvature", "curvature"), &GraphEdit::set_connection_lines_curvature); + ClassDB::bind_method(D_METHOD("get_connection_lines_curvature"), &GraphEdit::get_connection_lines_curvature); + ClassDB::bind_method(D_METHOD("set_connection_lines_thickness", "pixels"), &GraphEdit::set_connection_lines_thickness); ClassDB::bind_method(D_METHOD("get_connection_lines_thickness"), &GraphEdit::get_connection_lines_thickness); @@ -2259,13 +2317,14 @@ void GraphEdit::_bind_methods() { GDVIRTUAL_BIND(_get_connection_line, "from", "to") ADD_PROPERTY(PropertyInfo(Variant::BOOL, "right_disconnects"), "set_right_disconnects", "is_right_disconnects_enabled"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "scroll_offset"), "set_scroll_ofs", "get_scroll_ofs"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "snap_distance"), "set_snap", "get_snap"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "scroll_offset", PROPERTY_HINT_NONE, "suffix:px"), "set_scroll_ofs", "get_scroll_ofs"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "snap_distance", PROPERTY_HINT_NONE, "suffix:px"), "set_snap", "get_snap"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_snap"), "set_use_snap", "is_using_snap"); ADD_PROPERTY(PropertyInfo(Variant::INT, "panning_scheme", PROPERTY_HINT_ENUM, "Scroll Zooms,Scroll Pans"), "set_panning_scheme", "get_panning_scheme"); ADD_GROUP("Connection Lines", "connection_lines"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "connection_lines_thickness"), "set_connection_lines_thickness", "get_connection_lines_thickness"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "connection_lines_curvature"), "set_connection_lines_curvature", "get_connection_lines_curvature"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "connection_lines_thickness", PROPERTY_HINT_NONE, "suffix:px"), "set_connection_lines_thickness", "get_connection_lines_thickness"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "connection_lines_antialiased"), "set_connection_lines_antialiased", "is_connection_lines_antialiased"); ADD_GROUP("Zoom", ""); @@ -2277,7 +2336,7 @@ void GraphEdit::_bind_methods() { ADD_GROUP("Minimap", "minimap"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "minimap_enabled"), "set_minimap_enabled", "is_minimap_enabled"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "minimap_size"), "set_minimap_size", "get_minimap_size"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "minimap_size", PROPERTY_HINT_NONE, "suffix:px"), "set_minimap_size", "get_minimap_size"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "minimap_opacity"), "set_minimap_opacity", "get_minimap_opacity"); ADD_SIGNAL(MethodInfo("connection_request", PropertyInfo(Variant::STRING_NAME, "from"), PropertyInfo(Variant::INT, "from_slot"), PropertyInfo(Variant::STRING_NAME, "to"), PropertyInfo(Variant::INT, "to_slot"))); @@ -2290,10 +2349,10 @@ void GraphEdit::_bind_methods() { ADD_SIGNAL(MethodInfo("node_deselected", PropertyInfo(Variant::OBJECT, "node", PROPERTY_HINT_RESOURCE_TYPE, "Node"))); ADD_SIGNAL(MethodInfo("connection_to_empty", PropertyInfo(Variant::STRING_NAME, "from"), PropertyInfo(Variant::INT, "from_slot"), PropertyInfo(Variant::VECTOR2, "release_position"))); ADD_SIGNAL(MethodInfo("connection_from_empty", PropertyInfo(Variant::STRING_NAME, "to"), PropertyInfo(Variant::INT, "to_slot"), PropertyInfo(Variant::VECTOR2, "release_position"))); - ADD_SIGNAL(MethodInfo("delete_nodes_request")); + ADD_SIGNAL(MethodInfo("delete_nodes_request", PropertyInfo(Variant::ARRAY, "nodes", PROPERTY_HINT_ARRAY_TYPE, "StringName"))); ADD_SIGNAL(MethodInfo("begin_node_move")); ADD_SIGNAL(MethodInfo("end_node_move")); - ADD_SIGNAL(MethodInfo("scroll_offset_changed", PropertyInfo(Variant::VECTOR2, "ofs"))); + ADD_SIGNAL(MethodInfo("scroll_offset_changed", PropertyInfo(Variant::VECTOR2, "offset"))); ADD_SIGNAL(MethodInfo("connection_drag_started", PropertyInfo(Variant::STRING, "from"), PropertyInfo(Variant::STRING, "slot"), PropertyInfo(Variant::BOOL, "is_output"))); ADD_SIGNAL(MethodInfo("connection_drag_ended")); @@ -2317,7 +2376,7 @@ GraphEdit::GraphEdit() { top_layer = memnew(GraphEditFilter(this)); add_child(top_layer, false, INTERNAL_MODE_BACK); top_layer->set_mouse_filter(MOUSE_FILTER_PASS); - top_layer->set_anchors_and_offsets_preset(Control::PRESET_WIDE); + top_layer->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT); top_layer->connect("draw", callable_mp(this, &GraphEdit::_top_layer_draw)); top_layer->connect("gui_input", callable_mp(this, &GraphEdit::_top_layer_input)); top_layer->connect("focus_exited", callable_mp(panner.ptr(), &ViewPanner::release_pan_key)); diff --git a/scene/gui/graph_edit.h b/scene/gui/graph_edit.h index 18b9eeebd4..02e90e4717 100644 --- a/scene/gui/graph_edit.h +++ b/scene/gui/graph_edit.h @@ -124,8 +124,8 @@ private: HScrollBar *h_scroll = nullptr; VScrollBar *v_scroll = nullptr; - float port_grab_distance_horizontal = 0.0; - float port_grab_distance_vertical; + float port_hotzone_inner_extent = 0.0; + float port_hotzone_outer_extent = 0.0; Ref<ViewPanner> panner; bool warped_panning = true; @@ -142,7 +142,7 @@ private: bool connecting_target = false; Vector2 connecting_to; String connecting_target_to; - int connecting_target_index; + int connecting_target_index = 0; bool just_disconnected = false; bool connecting_valid = false; Vector2 click_pos; @@ -155,8 +155,9 @@ private: float zoom = 1.0; float zoom_step = 1.2; - float zoom_min; - float zoom_max; + // Proper values set in constructor. + float zoom_min = 0.0; + float zoom_max = 0.0; void _zoom_minus(); void _zoom_reset(); @@ -177,6 +178,7 @@ private: List<Connection> connections; float lines_thickness = 2.0f; + float lines_curvature = 0.5f; bool lines_antialiased = true; PackedVector2Array get_connection_line(const Vector2 &p_from, const Vector2 &p_to); @@ -206,7 +208,7 @@ private: Array _get_connection_list() const; - bool lines_on_bg; + bool lines_on_bg = false; struct ConnType { union { @@ -217,8 +219,11 @@ private: uint64_t key = 0; }; - bool operator<(const ConnType &p_type) const { - return key < p_type.key; + static uint32_t hash(const ConnType &p_conn) { + return hash_one_uint64(p_conn.key); + } + bool operator==(const ConnType &p_type) const { + return key == p_type.key; } ConnType(uint32_t a = 0, uint32_t b = 0) { @@ -227,9 +232,9 @@ private: } }; - Set<ConnType> valid_connection_types; - Set<int> valid_left_disconnect_types; - Set<int> valid_right_disconnect_types; + HashSet<ConnType, ConnType> valid_connection_types; + HashSet<int> valid_left_disconnect_types; + HashSet<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); @@ -246,7 +251,7 @@ private: friend class GraphEditMinimap; void _minimap_toggled(); - bool _check_clickable_control(Control *p_control, const Vector2 &pos); + bool _check_clickable_control(Control *p_control, const Vector2 &r_mouse_pos, const Vector2 &p_offset); bool arranging_graph = false; @@ -257,12 +262,12 @@ private: UNION, }; - int _set_operations(SET_OPERATIONS p_operation, Set<StringName> &r_u, const Set<StringName> &r_v); - HashMap<int, Vector<StringName>> _layering(const Set<StringName> &r_selected_nodes, const HashMap<StringName, Set<StringName>> &r_upper_neighbours); + int _set_operations(SET_OPERATIONS p_operation, HashSet<StringName> &r_u, const HashSet<StringName> &r_v); + HashMap<int, Vector<StringName>> _layering(const HashSet<StringName> &r_selected_nodes, const HashMap<StringName, HashSet<StringName>> &r_upper_neighbours); Vector<StringName> _split(const Vector<StringName> &r_layer, const HashMap<StringName, Dictionary> &r_crossings); - void _horizontal_alignment(Dictionary &r_root, Dictionary &r_align, const HashMap<int, Vector<StringName>> &r_layers, const HashMap<StringName, Set<StringName>> &r_upper_neighbours, const Set<StringName> &r_selected_nodes); - void _crossing_minimisation(HashMap<int, Vector<StringName>> &r_layers, const HashMap<StringName, Set<StringName>> &r_upper_neighbours); - void _calculate_inner_shifts(Dictionary &r_inner_shifts, const Dictionary &r_root, const Dictionary &r_node_names, const Dictionary &r_align, const Set<StringName> &r_block_heads, const HashMap<StringName, Pair<int, int>> &r_port_info); + void _horizontal_alignment(Dictionary &r_root, Dictionary &r_align, const HashMap<int, Vector<StringName>> &r_layers, const HashMap<StringName, HashSet<StringName>> &r_upper_neighbours, const HashSet<StringName> &r_selected_nodes); + void _crossing_minimisation(HashMap<int, Vector<StringName>> &r_layers, const HashMap<StringName, HashSet<StringName>> &r_upper_neighbours); + void _calculate_inner_shifts(Dictionary &r_inner_shifts, const Dictionary &r_root, const Dictionary &r_node_names, const Dictionary &r_align, const HashSet<StringName> &r_block_heads, const HashMap<StringName, Pair<int, int>> &r_port_info); float _calculate_threshold(StringName p_v, StringName p_w, const Dictionary &r_node_names, const HashMap<int, Vector<StringName>> &r_layers, const Dictionary &r_root, const Dictionary &r_align, const Dictionary &r_inner_shift, real_t p_current_threshold, const HashMap<StringName, Vector2> &r_node_positions); void _place_block(StringName p_v, float p_delta, const HashMap<int, Vector<StringName>> &r_layers, const Dictionary &r_root, const Dictionary &r_align, const Dictionary &r_node_name, const Dictionary &r_inner_shift, Dictionary &r_sink, Dictionary &r_shift, HashMap<StringName, Vector2> &r_node_positions); @@ -340,6 +345,9 @@ public: int get_snap() const; void set_snap(int p_snap); + void set_connection_lines_curvature(float p_curvature); + float get_connection_lines_curvature() const; + void set_connection_lines_thickness(float p_thickness); float get_connection_lines_thickness() const; diff --git a/scene/gui/graph_node.cpp b/scene/gui/graph_node.cpp index 51fb26b459..92016ca42e 100644 --- a/scene/gui/graph_node.cpp +++ b/scene/gui/graph_node.cpp @@ -44,26 +44,6 @@ struct _MinSizeCache { bool GraphNode::_set(const StringName &p_name, const Variant &p_value) { String str = p_name; - if (str.begins_with("opentype_features/")) { - String name = str.get_slicec('/', 1); - int32_t tag = TS->name_to_tag(name); - int value = p_value; - if (value == -1) { - if (opentype_features.has(tag)) { - opentype_features.erase(tag); - _shape(); - update(); - } - } else { - if (!opentype_features.has(tag) || (int)opentype_features[tag] != value) { - opentype_features[tag] = value; - _shape(); - update(); - } - } - notify_property_list_changed(); - return true; - } if (!str.begins_with("slot/")) { return false; @@ -93,29 +73,19 @@ bool GraphNode::_set(const StringName &p_name, const Variant &p_value) { si.color_right = p_value; } else if (what == "right_icon") { si.custom_slot_right = p_value; + } else if (what == "draw_stylebox") { + si.draw_stylebox = p_value; } else { return false; } - set_slot(idx, si.enable_left, si.type_left, si.color_left, si.enable_right, si.type_right, si.color_right, si.custom_slot_left, si.custom_slot_right); + set_slot(idx, si.enable_left, si.type_left, si.color_left, si.enable_right, si.type_right, si.color_right, si.custom_slot_left, si.custom_slot_right, si.draw_stylebox); update(); return true; } bool GraphNode::_get(const StringName &p_name, Variant &r_ret) const { String str = p_name; - if (str.begins_with("opentype_features/")) { - String name = str.get_slicec('/', 1); - int32_t tag = TS->name_to_tag(name); - if (opentype_features.has(tag)) { - r_ret = opentype_features[tag]; - return true; - } else { - r_ret = -1; - return true; - } - } - if (!str.begins_with("slot/")) { return false; } @@ -144,6 +114,8 @@ bool GraphNode::_get(const StringName &p_name, Variant &r_ret) const { r_ret = si.color_right; } else if (what == "right_icon") { r_ret = si.custom_slot_right; + } else if (what == "draw_stylebox") { + r_ret = si.draw_stylebox; } else { return false; } @@ -152,12 +124,6 @@ bool GraphNode::_get(const StringName &p_name, Variant &r_ret) const { } void GraphNode::_get_property_list(List<PropertyInfo> *p_list) const { - for (const Variant *ftr = opentype_features.next(nullptr); ftr != nullptr; ftr = opentype_features.next(ftr)) { - String name = TS->tag_to_name(*ftr); - p_list->push_back(PropertyInfo(Variant::INT, "opentype_features/" + name)); - } - p_list->push_back(PropertyInfo(Variant::NIL, "opentype_features/_new", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR)); - int idx = 0; for (int i = 0; i < get_child_count(); i++) { Control *c = Object::cast_to<Control>(get_child(i)); @@ -175,7 +141,7 @@ void GraphNode::_get_property_list(List<PropertyInfo> *p_list) const { p_list->push_back(PropertyInfo(Variant::INT, base + "right_type")); p_list->push_back(PropertyInfo(Variant::COLOR, base + "right_color")); p_list->push_back(PropertyInfo(Variant::OBJECT, base + "right_icon", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_STORE_IF_NULL)); - + p_list->push_back(PropertyInfo(Variant::BOOL, base + "draw_stylebox")); idx++; } } @@ -185,6 +151,7 @@ void GraphNode::_resort() { Size2i new_size = get_size(); Ref<StyleBox> sb = get_theme_stylebox(SNAME("frame")); + Ref<StyleBox> sb_slot = get_theme_stylebox(SNAME("slot")); int sep = get_theme_constant(SNAME("separation")); @@ -193,7 +160,7 @@ void GraphNode::_resort() { int stretch_min = 0; int stretch_avail = 0; float stretch_ratio_total = 0; - Map<Control *, _MinSizeCache> min_size_cache; + HashMap<Control *, _MinSizeCache> min_size_cache; for (int i = 0; i < get_child_count(); i++) { Control *c = Object::cast_to<Control>(get_child(i)); @@ -204,7 +171,7 @@ void GraphNode::_resort() { continue; } - Size2i size = c->get_combined_minimum_size(); + Size2i size = c->get_combined_minimum_size() + (slot_info[i].draw_stylebox ? sb_slot->get_minimum_size() : Size2()); _MinSizeCache msc; stretch_min += size.height; @@ -312,7 +279,9 @@ void GraphNode::_resort() { int size = to - from; - Rect2 rect(sb->get_margin(SIDE_LEFT), from, w, size); + float margin = sb->get_margin(SIDE_LEFT) + (slot_info[i].draw_stylebox ? sb_slot->get_margin(SIDE_LEFT) : 0); + float width = w - (slot_info[i].draw_stylebox ? sb_slot->get_minimum_size().x : 0); + Rect2 rect(margin, from, width, size); fit_child_in_rect(c, rect); cache_y.push_back(from - sb->get_margin(SIDE_TOP) + size * 0.5); @@ -351,14 +320,14 @@ void GraphNode::_notification(int p_what) { Ref<StyleBox> sb; if (comment) { - sb = get_theme_stylebox(selected ? "commentfocus" : "comment"); + sb = get_theme_stylebox(selected ? SNAME("comment_focus") : SNAME("comment")); } else { - sb = get_theme_stylebox(selected ? "selectedframe" : "frame"); + sb = get_theme_stylebox(selected ? SNAME("selected_frame") : SNAME("frame")); } - //sb=sb->duplicate(); - //sb->call("set_modulate",modulate); + Ref<StyleBox> sb_slot = get_theme_stylebox(SNAME("slot")); + Ref<Texture2D> port = get_theme_icon(SNAME("port")); Ref<Texture2D> close = get_theme_icon(SNAME("close")); Ref<Texture2D> resizer = get_theme_icon(SNAME("resizer")); @@ -389,13 +358,9 @@ void GraphNode::_notification(int p_what) { int w = get_size().width - sb->get_minimum_size().x; - if (show_close) { - w -= close->get_width(); - } - title_buf->draw(get_canvas_item(), Point2(sb->get_margin(SIDE_LEFT) + title_h_offset, -title_buf->get_size().y + title_offset), title_color); if (show_close) { - Vector2 cpos = Point2(w + sb->get_margin(SIDE_LEFT) + close_h_offset, -close->get_height() + close_offset); + Vector2 cpos = Point2(w + sb->get_margin(SIDE_LEFT) + close_h_offset - close->get_width(), -close->get_height() + close_offset); draw_texture(close, cpos, close_color); close_rect.position = cpos; close_rect.size = close->get_size(); @@ -411,7 +376,7 @@ void GraphNode::_notification(int p_what) { continue; } const Slot &s = slot_info[E.key]; - //left + // Left port. if (s.enable_left) { Ref<Texture2D> p = port; if (s.custom_slot_left.is_valid()) { @@ -419,6 +384,7 @@ void GraphNode::_notification(int p_what) { } p->draw(get_canvas_item(), icofs + Point2(edgeofs, cache_y[E.key]), s.color_left); } + // Right port. if (s.enable_right) { Ref<Texture2D> p = port; if (s.custom_slot_right.is_valid()) { @@ -426,6 +392,15 @@ void GraphNode::_notification(int p_what) { } p->draw(get_canvas_item(), icofs + Point2(get_size().x - edgeofs, cache_y[E.key]), s.color_right); } + + // Draw slot stylebox. + if (s.draw_stylebox) { + Control *c = Object::cast_to<Control>(get_child(E.key)); + Rect2 c_rect = c->get_rect(); + c_rect.position.x = sb->get_margin(SIDE_LEFT); + c_rect.size.width = w; + draw_style_box(sb_slot, c_rect); + } } if (resizable) { @@ -458,7 +433,7 @@ void GraphNode::_shape() { } else { title_buf->set_direction((TextServer::Direction)text_direction); } - title_buf->add_string(title, font, font_size, opentype_features, (!language.is_empty()) ? language : TranslationServer::get_singleton()->get_tool_locale()); + title_buf->add_string(title, font, font_size, language); } #ifdef TOOLS_ENABLED @@ -482,7 +457,7 @@ void GraphNode::_validate_property(PropertyInfo &property) const { } #endif -void GraphNode::set_slot(int p_idx, bool p_enable_left, int p_type_left, const Color &p_color_left, bool p_enable_right, int p_type_right, const Color &p_color_right, const Ref<Texture2D> &p_custom_left, const Ref<Texture2D> &p_custom_right) { +void GraphNode::set_slot(int p_idx, bool p_enable_left, int p_type_left, const Color &p_color_left, bool p_enable_right, int p_type_right, const Color &p_color_right, const Ref<Texture2D> &p_custom_left, const Ref<Texture2D> &p_custom_right, bool p_draw_stylebox) { ERR_FAIL_COND_MSG(p_idx < 0, vformat("Cannot set slot with p_idx (%d) lesser than zero.", p_idx)); if (!p_enable_left && p_type_left == 0 && p_color_left == Color(1, 1, 1, 1) && @@ -501,6 +476,7 @@ void GraphNode::set_slot(int p_idx, bool p_enable_left, int p_type_left, const C s.color_right = p_color_right; s.custom_slot_left = p_custom_left; s.custom_slot_right = p_custom_right; + s.draw_stylebox = p_draw_stylebox; slot_info[p_idx] = s; update(); connpos_dirty = true; @@ -622,16 +598,39 @@ Color GraphNode::get_slot_color_right(int p_idx) const { return slot_info[p_idx].color_right; } +bool GraphNode::is_slot_draw_stylebox(int p_idx) const { + if (!slot_info.has(p_idx)) { + return false; + } + return slot_info[p_idx].draw_stylebox; +} + +void GraphNode::set_slot_draw_stylebox(int p_idx, bool p_enable) { + ERR_FAIL_COND_MSG(p_idx < 0, vformat("Cannot set draw_stylebox for the slot with p_idx (%d) lesser than zero.", p_idx)); + + slot_info[p_idx].draw_stylebox = p_enable; + update(); + connpos_dirty = true; + + emit_signal(SNAME("slot_updated"), p_idx); +} + Size2 GraphNode::get_minimum_size() const { - int sep = get_theme_constant(SNAME("separation")); Ref<StyleBox> sb = get_theme_stylebox(SNAME("frame")); + Ref<StyleBox> sb_slot = get_theme_stylebox(SNAME("slot")); + + int sep = get_theme_constant(SNAME("separation")); + int title_h_offset = get_theme_constant(SNAME("title_h_offset")); + bool first = true; Size2 minsize; - minsize.x = title_buf->get_size().x; + minsize.x = title_buf->get_size().x + title_h_offset; if (show_close) { + int close_h_offset = get_theme_constant(SNAME("close_h_offset")); Ref<Texture2D> close = get_theme_icon(SNAME("close")); - minsize.x += sep + close->get_width(); + //TODO: Remove this magic number after GraphNode rework. + minsize.x += 12 + close->get_width() + close_h_offset; } for (int i = 0; i < get_child_count(); i++) { @@ -644,6 +643,9 @@ Size2 GraphNode::get_minimum_size() const { } Size2i size = c->get_combined_minimum_size(); + if (slot_info.has(i)) { + size += slot_info[i].draw_stylebox ? sb_slot->get_minimum_size() : Size2(); + } minsize.y += size.y; minsize.x = MAX(minsize.x, size.x); @@ -686,29 +688,6 @@ Control::TextDirection GraphNode::get_text_direction() const { return text_direction; } -void GraphNode::clear_opentype_features() { - opentype_features.clear(); - _shape(); - update(); -} - -void GraphNode::set_opentype_feature(const String &p_name, int p_value) { - int32_t tag = TS->name_to_tag(p_name); - if (!opentype_features.has(tag) || (int)opentype_features[tag] != p_value) { - opentype_features[tag] = p_value; - _shape(); - update(); - } -} - -int GraphNode::get_opentype_feature(const String &p_name) const { - int32_t tag = TS->name_to_tag(p_name); - if (!opentype_features.has(tag)) { - return -1; - } - return opentype_features[tag]; -} - void GraphNode::set_language(const String &p_language) { if (language != p_language) { language = p_language; @@ -792,6 +771,7 @@ void GraphNode::_connpos_update() { cc.pos = Point2i(edgeofs, y + h / 2); cc.type = slot_info[idx].type_left; cc.color = slot_info[idx].color_left; + cc.height = size.height; conn_input_cache.push_back(cc); } if (slot_info[idx].enable_right) { @@ -799,6 +779,7 @@ void GraphNode::_connpos_update() { cc.pos = Point2i(get_size().width - edgeofs, y + h / 2); cc.type = slot_info[idx].type_right; cc.color = slot_info[idx].color_right; + cc.height = size.height; conn_output_cache.push_back(cc); } } @@ -819,12 +800,13 @@ int GraphNode::get_connection_input_count() { return conn_input_cache.size(); } -int GraphNode::get_connection_output_count() { +int GraphNode::get_connection_input_height(int p_idx) { if (connpos_dirty) { _connpos_update(); } - return conn_output_cache.size(); + ERR_FAIL_INDEX_V(p_idx, conn_input_cache.size(), 0); + return conn_input_cache[p_idx].height; } Vector2 GraphNode::get_connection_input_position(int p_idx) { @@ -857,6 +839,23 @@ Color GraphNode::get_connection_input_color(int p_idx) { return conn_input_cache[p_idx].color; } +int GraphNode::get_connection_output_count() { + if (connpos_dirty) { + _connpos_update(); + } + + return conn_output_cache.size(); +} + +int GraphNode::get_connection_output_height(int p_idx) { + if (connpos_dirty) { + _connpos_update(); + } + + ERR_FAIL_INDEX_V(p_idx, conn_output_cache.size(), 0); + return conn_output_cache[p_idx].height; +} + Vector2 GraphNode::get_connection_output_position(int p_idx) { if (connpos_dirty) { _connpos_update(); @@ -983,13 +982,10 @@ void GraphNode::_bind_methods() { ClassDB::bind_method(D_METHOD("get_title"), &GraphNode::get_title); ClassDB::bind_method(D_METHOD("set_text_direction", "direction"), &GraphNode::set_text_direction); ClassDB::bind_method(D_METHOD("get_text_direction"), &GraphNode::get_text_direction); - ClassDB::bind_method(D_METHOD("set_opentype_feature", "tag", "value"), &GraphNode::set_opentype_feature); - ClassDB::bind_method(D_METHOD("get_opentype_feature", "tag"), &GraphNode::get_opentype_feature); - ClassDB::bind_method(D_METHOD("clear_opentype_features"), &GraphNode::clear_opentype_features); ClassDB::bind_method(D_METHOD("set_language", "language"), &GraphNode::set_language); ClassDB::bind_method(D_METHOD("get_language"), &GraphNode::get_language); - ClassDB::bind_method(D_METHOD("set_slot", "idx", "enable_left", "type_left", "color_left", "enable_right", "type_right", "color_right", "custom_left", "custom_right"), &GraphNode::set_slot, DEFVAL(Ref<Texture2D>()), DEFVAL(Ref<Texture2D>())); + ClassDB::bind_method(D_METHOD("set_slot", "idx", "enable_left", "type_left", "color_left", "enable_right", "type_right", "color_right", "custom_left", "custom_right", "enable"), &GraphNode::set_slot, DEFVAL(Ref<Texture2D>()), DEFVAL(Ref<Texture2D>()), DEFVAL(true)); ClassDB::bind_method(D_METHOD("clear_slot", "idx"), &GraphNode::clear_slot); ClassDB::bind_method(D_METHOD("clear_all_slots"), &GraphNode::clear_all_slots); @@ -1011,6 +1007,9 @@ void GraphNode::_bind_methods() { ClassDB::bind_method(D_METHOD("set_slot_color_right", "idx", "color_right"), &GraphNode::set_slot_color_right); ClassDB::bind_method(D_METHOD("get_slot_color_right", "idx"), &GraphNode::get_slot_color_right); + ClassDB::bind_method(D_METHOD("is_slot_draw_stylebox", "idx"), &GraphNode::is_slot_draw_stylebox); + ClassDB::bind_method(D_METHOD("set_slot_draw_stylebox", "idx", "draw_stylebox"), &GraphNode::set_slot_draw_stylebox); + ClassDB::bind_method(D_METHOD("set_position_offset", "offset"), &GraphNode::set_position_offset); ClassDB::bind_method(D_METHOD("get_position_offset"), &GraphNode::get_position_offset); @@ -1023,15 +1022,17 @@ void GraphNode::_bind_methods() { ClassDB::bind_method(D_METHOD("set_selected", "selected"), &GraphNode::set_selected); ClassDB::bind_method(D_METHOD("is_selected"), &GraphNode::is_selected); - ClassDB::bind_method(D_METHOD("get_connection_output_count"), &GraphNode::get_connection_output_count); ClassDB::bind_method(D_METHOD("get_connection_input_count"), &GraphNode::get_connection_input_count); + ClassDB::bind_method(D_METHOD("get_connection_input_height", "idx"), &GraphNode::get_connection_input_height); + ClassDB::bind_method(D_METHOD("get_connection_input_position", "idx"), &GraphNode::get_connection_input_position); + ClassDB::bind_method(D_METHOD("get_connection_input_type", "idx"), &GraphNode::get_connection_input_type); + ClassDB::bind_method(D_METHOD("get_connection_input_color", "idx"), &GraphNode::get_connection_input_color); + ClassDB::bind_method(D_METHOD("get_connection_output_count"), &GraphNode::get_connection_output_count); + ClassDB::bind_method(D_METHOD("get_connection_output_height", "idx"), &GraphNode::get_connection_output_height); ClassDB::bind_method(D_METHOD("get_connection_output_position", "idx"), &GraphNode::get_connection_output_position); ClassDB::bind_method(D_METHOD("get_connection_output_type", "idx"), &GraphNode::get_connection_output_type); ClassDB::bind_method(D_METHOD("get_connection_output_color", "idx"), &GraphNode::get_connection_output_color); - ClassDB::bind_method(D_METHOD("get_connection_input_position", "idx"), &GraphNode::get_connection_input_position); - ClassDB::bind_method(D_METHOD("get_connection_input_type", "idx"), &GraphNode::get_connection_input_type); - ClassDB::bind_method(D_METHOD("get_connection_input_color", "idx"), &GraphNode::get_connection_input_color); ClassDB::bind_method(D_METHOD("set_show_close_button", "show"), &GraphNode::set_show_close_button); ClassDB::bind_method(D_METHOD("is_close_button_visible"), &GraphNode::is_close_button_visible); @@ -1040,15 +1041,17 @@ void GraphNode::_bind_methods() { ClassDB::bind_method(D_METHOD("get_overlay"), &GraphNode::get_overlay); ADD_PROPERTY(PropertyInfo(Variant::STRING, "title"), "set_title", "get_title"); - 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", PROPERTY_HINT_LOCALE_ID, ""), "set_language", "get_language"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "position_offset"), "set_position_offset", "get_position_offset"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "position_offset", PROPERTY_HINT_NONE, "suffix:px"), "set_position_offset", "get_position_offset"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "show_close"), "set_show_close_button", "is_close_button_visible"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "resizable"), "set_resizable", "is_resizable"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "selected"), "set_selected", "is_selected"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "comment"), "set_comment", "is_comment"); ADD_PROPERTY(PropertyInfo(Variant::INT, "overlay", PROPERTY_HINT_ENUM, "Disabled,Breakpoint,Position"), "set_overlay", "get_overlay"); + ADD_GROUP("BiDi", ""); + 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", PROPERTY_HINT_LOCALE_ID, ""), "set_language", "get_language"); + ADD_SIGNAL(MethodInfo("position_offset_changed")); ADD_SIGNAL(MethodInfo("slot_updated", PropertyInfo(Variant::INT, "idx"))); ADD_SIGNAL(MethodInfo("dragged", PropertyInfo(Variant::VECTOR2, "from"), PropertyInfo(Variant::VECTOR2, "to"))); diff --git a/scene/gui/graph_node.h b/scene/gui/graph_node.h index 7eb5f27cff..0651eb5cc9 100644 --- a/scene/gui/graph_node.h +++ b/scene/gui/graph_node.h @@ -54,12 +54,12 @@ private: Color color_right = Color(1, 1, 1, 1); Ref<Texture2D> custom_slot_left; Ref<Texture2D> custom_slot_right; + bool draw_stylebox = true; }; String title; Ref<TextLine> title_buf; - Dictionary opentype_features; String language; TextDirection text_direction = TEXT_DIRECTION_AUTO; @@ -80,12 +80,13 @@ private: Vector2 pos; int type = 0; Color color; + int height; }; Vector<ConnCache> conn_input_cache; Vector<ConnCache> conn_output_cache; - Map<int, Slot> slot_info; + HashMap<int, Slot> slot_info; bool connpos_dirty = true; @@ -115,7 +116,7 @@ protected: public: bool has_point(const Point2 &p_point) const override; - void set_slot(int p_idx, bool p_enable_left, int p_type_left, const Color &p_color_left, bool p_enable_right, int p_type_right, const Color &p_color_right, const Ref<Texture2D> &p_custom_left = Ref<Texture2D>(), const Ref<Texture2D> &p_custom_right = Ref<Texture2D>()); + void set_slot(int p_idx, bool p_enable_left, int p_type_left, const Color &p_color_left, bool p_enable_right, int p_type_right, const Color &p_color_right, const Ref<Texture2D> &p_custom_left = Ref<Texture2D>(), const Ref<Texture2D> &p_custom_right = Ref<Texture2D>(), bool p_draw_stylebox = true); void clear_slot(int p_idx); void clear_all_slots(); @@ -137,16 +138,15 @@ public: void set_slot_color_right(int p_idx, const Color &p_color_right); Color get_slot_color_right(int p_idx) const; + bool is_slot_draw_stylebox(int p_idx) const; + void set_slot_draw_stylebox(int p_idx, bool p_enable); + void set_title(const String &p_title); String get_title() const; void set_text_direction(TextDirection p_text_direction); TextDirection get_text_direction() const; - void set_opentype_feature(const String &p_name, int p_value); - int get_opentype_feature(const String &p_name) const; - void clear_opentype_features(); - void set_language(const String &p_language); String get_language() const; @@ -163,10 +163,13 @@ public: bool is_close_button_visible() const; int get_connection_input_count(); - int get_connection_output_count(); + int get_connection_input_height(int p_idx); Vector2 get_connection_input_position(int p_idx); int get_connection_input_type(int p_idx); Color get_connection_input_color(int p_idx); + + int get_connection_output_count(); + int get_connection_output_height(int p_idx); Vector2 get_connection_output_position(int p_idx); int get_connection_output_type(int p_idx); Color get_connection_output_color(int p_idx); @@ -185,7 +188,9 @@ public: virtual Vector<int> get_allowed_size_flags_horizontal() const override; virtual Vector<int> get_allowed_size_flags_vertical() const override; - bool is_resizing() const { return resizing; } + bool is_resizing() const { + return resizing; + } GraphNode(); }; diff --git a/scene/gui/grid_container.cpp b/scene/gui/grid_container.cpp index 35bceea606..eaa6943ad2 100644 --- a/scene/gui/grid_container.cpp +++ b/scene/gui/grid_container.cpp @@ -29,19 +29,18 @@ /*************************************************************************/ #include "grid_container.h" +#include "core/templates/rb_set.h" void GridContainer::_notification(int p_what) { switch (p_what) { case NOTIFICATION_SORT_CHILDREN: { - Map<int, int> col_minw; // Max of min_width of all controls in each col (indexed by col). - Map<int, int> row_minh; // Max of min_height of all controls in each row (indexed by row). - Set<int> col_expanded; // Columns which have the SIZE_EXPAND flag set. - Set<int> row_expanded; // Rows which have the SIZE_EXPAND flag set. + RBMap<int, int> col_minw; // Max of min_width of all controls in each col (indexed by col). + RBMap<int, int> row_minh; // Max of min_height of all controls in each row (indexed by row). + RBSet<int> col_expanded; // Columns which have the SIZE_EXPAND flag set. + RBSet<int> row_expanded; // Rows which have the SIZE_EXPAND flag set. - int hsep = get_theme_constant(SNAME("hseparation")); - int vsep = get_theme_constant(SNAME("vseparation")); - int max_col = MIN(get_child_count(), columns); - int max_row = ceil((float)get_child_count() / (float)columns); + int hsep = get_theme_constant(SNAME("h_separation")); + int vsep = get_theme_constant(SNAME("v_separation")); // Compute the per-column/per-row data. int valid_controls_index = 0; @@ -78,6 +77,9 @@ void GridContainer::_notification(int p_what) { } } + int max_col = MIN(valid_controls_index, columns); + int max_row = ceil((float)valid_controls_index / (float)columns); + // Consider all empty columns expanded. for (int i = valid_controls_index; i < columns; i++) { col_expanded.insert(i); @@ -104,11 +106,11 @@ void GridContainer::_notification(int p_what) { // Check if all minwidth constraints are OK if we use the remaining space. can_fit = true; int max_index = col_expanded.front()->get(); - for (Set<int>::Element *E = col_expanded.front(); E; E = E->next()) { - if (col_minw[E->get()] > col_minw[max_index]) { - max_index = E->get(); + for (const int &E : col_expanded) { + if (col_minw[E] > col_minw[max_index]) { + max_index = E; } - if (can_fit && (remaining_space.width / col_expanded.size()) < col_minw[E->get()]) { + if (can_fit && (remaining_space.width / col_expanded.size()) < col_minw[E]) { can_fit = false; } } @@ -125,11 +127,11 @@ void GridContainer::_notification(int p_what) { // Check if all minheight constraints are OK if we use the remaining space. can_fit = true; int max_index = row_expanded.front()->get(); - for (Set<int>::Element *E = row_expanded.front(); E; E = E->next()) { - if (row_minh[E->get()] > row_minh[max_index]) { - max_index = E->get(); + for (const int &E : row_expanded) { + if (row_minh[E] > row_minh[max_index]) { + max_index = E; } - if (can_fit && (remaining_space.height / row_expanded.size()) < row_minh[E->get()]) { + if (can_fit && (remaining_space.height / row_expanded.size()) < row_minh[E]) { can_fit = false; } } @@ -142,13 +144,47 @@ void GridContainer::_notification(int p_what) { } // Finally, fit the nodes. - int col_expand = col_expanded.size() > 0 ? remaining_space.width / col_expanded.size() : 0; - int row_expand = row_expanded.size() > 0 ? remaining_space.height / row_expanded.size() : 0; + int col_remaining_pixel = 0; + int col_expand = 0; + if (col_expanded.size() > 0) { + col_expand = remaining_space.width / col_expanded.size(); + col_remaining_pixel = remaining_space.width - col_expanded.size() * col_expand; + } + + int row_remaining_pixel = 0; + int row_expand = 0; + if (row_expanded.size() > 0) { + row_expand = remaining_space.height / row_expanded.size(); + row_remaining_pixel = remaining_space.height - row_expanded.size() * row_expand; + } + bool rtl = is_layout_rtl(); int col_ofs = 0; int row_ofs = 0; + // Calculate the index of rows and columns that receive the remaining pixel. + int col_remaining_pixel_index = 0; + for (int i = 0; i < max_col; i++) { + if (col_remaining_pixel == 0) { + break; + } + if (col_expanded.has(i)) { + col_remaining_pixel_index = i + 1; + col_remaining_pixel--; + } + } + int row_remaining_pixel_index = 0; + for (int i = 0; i < max_row; i++) { + if (row_remaining_pixel == 0) { + break; + } + if (row_expanded.has(i)) { + row_remaining_pixel_index = i + 1; + row_remaining_pixel--; + } + } + valid_controls_index = 0; for (int i = 0; i < get_child_count(); i++) { Control *c = Object::cast_to<Control>(get_child(i)); @@ -167,17 +203,30 @@ void GridContainer::_notification(int p_what) { } if (row > 0) { row_ofs += (row_expanded.has(row - 1) ? row_expand : row_minh[row - 1]) + vsep; + + if (row_expanded.has(row - 1) && row - 1 < row_remaining_pixel_index) { + // Apply the remaining pixel of the previous row. + row_ofs++; + } } } + Size2 s(col_expanded.has(col) ? col_expand : col_minw[col], row_expanded.has(row) ? row_expand : row_minh[row]); + + // Add the remaining pixel to the expanding columns and rows, starting from left and top. + if (col_expanded.has(col) && col < col_remaining_pixel_index) { + s.x++; + } + if (row_expanded.has(row) && row < row_remaining_pixel_index) { + s.y++; + } + if (rtl) { - Size2 s(col_expanded.has(col) ? col_expand : col_minw[col], row_expanded.has(row) ? row_expand : row_minh[row]); Point2 p(col_ofs - s.width, row_ofs); fit_child_in_rect(c, Rect2(p, s)); col_ofs -= s.width + hsep; } else { Point2 p(col_ofs, row_ofs); - Size2 s(col_expanded.has(col) ? col_expand : col_minw[col], row_expanded.has(row) ? row_expand : row_minh[row]); fit_child_in_rect(c, Rect2(p, s)); col_ofs += s.width + hsep; } @@ -214,11 +263,11 @@ void GridContainer::_bind_methods() { } Size2 GridContainer::get_minimum_size() const { - Map<int, int> col_minw; - Map<int, int> row_minh; + RBMap<int, int> col_minw; + RBMap<int, int> row_minh; - int hsep = get_theme_constant(SNAME("hseparation")); - int vsep = get_theme_constant(SNAME("vseparation")); + int hsep = get_theme_constant(SNAME("h_separation")); + int vsep = get_theme_constant(SNAME("v_separation")); int max_row = 0; int max_col = 0; diff --git a/scene/gui/item_list.cpp b/scene/gui/item_list.cpp index 8c0f696a9f..d0a25972f8 100644 --- a/scene/gui/item_list.cpp +++ b/scene/gui/item_list.cpp @@ -43,11 +43,11 @@ void ItemList::_shape(int p_idx) { } else { item.text_buf->set_direction((TextServer::Direction)item.text_direction); } - item.text_buf->add_string(item.text, get_theme_font(SNAME("font")), get_theme_font_size(SNAME("font_size")), item.opentype_features, (!item.language.is_empty()) ? item.language : TranslationServer::get_singleton()->get_tool_locale()); + item.text_buf->add_string(item.text, get_theme_font(SNAME("font")), get_theme_font_size(SNAME("font_size")), item.language); if (icon_mode == ICON_MODE_TOP && max_text_lines > 0) { - item.text_buf->set_flags(TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND | TextServer::BREAK_GRAPHEME_BOUND); + item.text_buf->set_break_flags(TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND | TextServer::BREAK_GRAPHEME_BOUND); } else { - item.text_buf->set_flags(TextServer::BREAK_NONE); + item.text_buf->set_break_flags(TextServer::BREAK_NONE); } item.text_buf->set_text_overrun_behavior(text_overrun_behavior); item.text_buf->set_max_lines_visible(max_text_lines); @@ -117,35 +117,6 @@ Control::TextDirection ItemList::get_item_text_direction(int p_idx) const { return items[p_idx].text_direction; } -void ItemList::clear_item_opentype_features(int p_idx) { - ERR_FAIL_INDEX(p_idx, items.size()); - items.write[p_idx].opentype_features.clear(); - _shape(p_idx); - update(); -} - -void ItemList::set_item_opentype_feature(int p_idx, const String &p_name, int p_value) { - if (p_idx < 0) { - p_idx += get_item_count(); - } - ERR_FAIL_INDEX(p_idx, items.size()); - int32_t tag = TS->name_to_tag(p_name); - if (!items[p_idx].opentype_features.has(tag) || (int)items[p_idx].opentype_features[tag] != p_value) { - items.write[p_idx].opentype_features[tag] = p_value; - _shape(p_idx); - update(); - } -} - -int ItemList::get_item_opentype_feature(int p_idx, const String &p_name) const { - ERR_FAIL_INDEX_V(p_idx, items.size(), -1); - int32_t tag = TS->name_to_tag(p_name); - if (!items[p_idx].opentype_features.has(tag)) { - return -1; - } - return items[p_idx].opentype_features[tag]; -} - void ItemList::set_item_language(int p_idx, const String &p_language) { if (p_idx < 0) { p_idx += get_item_count(); @@ -499,10 +470,10 @@ void ItemList::set_max_text_lines(int p_lines) { max_text_lines = p_lines; for (int i = 0; i < items.size(); i++) { if (icon_mode == ICON_MODE_TOP && max_text_lines > 0) { - items.write[i].text_buf->set_flags(TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND | TextServer::BREAK_GRAPHEME_BOUND); + items.write[i].text_buf->set_break_flags(TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND | TextServer::BREAK_GRAPHEME_BOUND); items.write[i].text_buf->set_max_lines_visible(p_lines); } else { - items.write[i].text_buf->set_flags(TextServer::BREAK_NONE); + items.write[i].text_buf->set_break_flags(TextServer::BREAK_NONE); } } shape_changed = true; @@ -540,9 +511,9 @@ void ItemList::set_icon_mode(IconMode p_mode) { icon_mode = p_mode; for (int i = 0; i < items.size(); i++) { if (icon_mode == ICON_MODE_TOP && max_text_lines > 0) { - items.write[i].text_buf->set_flags(TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND | TextServer::BREAK_GRAPHEME_BOUND); + items.write[i].text_buf->set_break_flags(TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND | TextServer::BREAK_GRAPHEME_BOUND); } else { - items.write[i].text_buf->set_flags(TextServer::BREAK_NONE); + items.write[i].text_buf->set_break_flags(TextServer::BREAK_NONE); } } shape_changed = true; @@ -585,6 +556,9 @@ Size2 ItemList::Item::get_icon_size() const { void ItemList::gui_input(const Ref<InputEvent> &p_event) { ERR_FAIL_COND(p_event.is_null()); +#define CAN_SELECT(i) (items[i].selectable && !items[i].disabled) +#define IS_SAME_ROW(i, row) (i / current_columns == row) + double prev_scroll = scroll_bar->get_value(); Ref<InputEventMouseMotion> mm = p_event; @@ -603,7 +577,7 @@ void ItemList::gui_input(const Ref<InputEvent> &p_event) { return; } - if (mb.is_valid() && (mb->get_button_index() == MouseButton::LEFT || (allow_rmb_select && mb->get_button_index() == MouseButton::RIGHT)) && mb->is_pressed()) { + if (mb.is_valid() && mb->is_pressed()) { search_string = ""; //any mousepress cancels Vector2 pos = mb->get_position(); Ref<StyleBox> bg = get_theme_stylebox(SNAME("bg")); @@ -628,7 +602,7 @@ void ItemList::gui_input(const Ref<InputEvent> &p_event) { } } - if (closest != -1) { + if (closest != -1 && (mb->get_button_index() == MouseButton::LEFT || (allow_rmb_select && mb->get_button_index() == MouseButton::RIGHT))) { int i = closest; if (select_mode == SELECT_MULTI && items[i].selected && mb->is_command_pressed()) { @@ -642,55 +616,47 @@ void ItemList::gui_input(const Ref<InputEvent> &p_event) { SWAP(from, to); } for (int j = from; j <= to; j++) { + if (!CAN_SELECT(j)) { + continue; + } bool selected = !items[j].selected; select(j, false); if (selected) { emit_signal(SNAME("multi_selected"), j, true); } } + emit_signal(SNAME("item_clicked"), i, get_local_mouse_position(), mb->get_button_index()); - 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() == MouseButton::LEFT) { defer_select_single = i; return; } - 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; - + if (!items[i].selected || allow_reselect) { select(i, select_mode == SELECT_SINGLE || !mb->is_command_pressed()); - if (!selected || allow_reselect) { - if (select_mode == SELECT_SINGLE) { - emit_signal(SNAME("item_selected"), i); - } else { - emit_signal(SNAME("multi_selected"), i, true); - } + if (select_mode == SELECT_SINGLE) { + emit_signal(SNAME("item_selected"), i); + } else { + emit_signal(SNAME("multi_selected"), i, true); } + } - 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); - } + emit_signal(SNAME("item_clicked"), i, get_local_mouse_position(), mb->get_button_index()); + + if (mb->get_button_index() == MouseButton::LEFT && mb->is_double_click()) { + emit_signal(SNAME("item_activated"), i); } } return; + } else if (closest != -1) { + emit_signal(SNAME("item_clicked"), closest, get_local_mouse_position(), mb->get_button_index()); + } else { + // 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("empty_clicked"), get_local_mouse_position(), mb->get_button_index()); } - if (mb->get_button_index() == MouseButton::RIGHT) { - emit_signal(SNAME("rmb_clicked"), mb->get_position()); - - return; - } - - // 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() == MouseButton::WHEEL_UP && mb->is_pressed()) { scroll_bar->set_value(scroll_bar->get_value() - scroll_bar->get_page() * mb->get_factor() / 8); @@ -707,7 +673,7 @@ void ItemList::gui_input(const Ref<InputEvent> &p_event) { if (diff < uint64_t(ProjectSettings::get_singleton()->get("gui/timers/incremental_search_max_interval_msec")) * 2) { for (int i = current - 1; i >= 0; i--) { - if (items[i].text.begins_with(search_string)) { + if (CAN_SELECT(i) && items[i].text.begins_with(search_string)) { set_current(i); ensure_current_is_visible(); if (select_mode == SELECT_SINGLE) { @@ -723,7 +689,15 @@ void ItemList::gui_input(const Ref<InputEvent> &p_event) { } if (current >= current_columns) { - set_current(current - current_columns); + int next = current - current_columns; + while (next >= 0 && !CAN_SELECT(next)) { + next = next - current_columns; + } + if (next < 0) { + accept_event(); + return; + } + set_current(next); ensure_current_is_visible(); if (select_mode == SELECT_SINGLE) { emit_signal(SNAME("item_selected"), current); @@ -737,7 +711,7 @@ void ItemList::gui_input(const Ref<InputEvent> &p_event) { if (diff < uint64_t(ProjectSettings::get_singleton()->get("gui/timers/incremental_search_max_interval_msec")) * 2) { for (int i = current + 1; i < items.size(); i++) { - if (items[i].text.begins_with(search_string)) { + if (CAN_SELECT(i) && items[i].text.begins_with(search_string)) { set_current(i); ensure_current_is_visible(); if (select_mode == SELECT_SINGLE) { @@ -752,7 +726,15 @@ void ItemList::gui_input(const Ref<InputEvent> &p_event) { } if (current < items.size() - current_columns) { - set_current(current + current_columns); + int next = current + current_columns; + while (next < items.size() && !CAN_SELECT(next)) { + next = next + current_columns; + } + if (next >= items.size()) { + accept_event(); + return; + } + set_current(next); ensure_current_is_visible(); if (select_mode == SELECT_SINGLE) { emit_signal(SNAME("item_selected"), current); @@ -763,7 +745,7 @@ void ItemList::gui_input(const Ref<InputEvent> &p_event) { search_string = ""; //any mousepress cancels for (int i = 4; i > 0; i--) { - if (current - current_columns * i >= 0) { + if (current - current_columns * i >= 0 && CAN_SELECT(current - current_columns * i)) { set_current(current - current_columns * i); ensure_current_is_visible(); if (select_mode == SELECT_SINGLE) { @@ -777,7 +759,7 @@ void ItemList::gui_input(const Ref<InputEvent> &p_event) { search_string = ""; //any mousepress cancels for (int i = 4; i > 0; i--) { - if (current + current_columns * i < items.size()) { + if (current + current_columns * i < items.size() && CAN_SELECT(current + current_columns * i)) { set_current(current + current_columns * i); ensure_current_is_visible(); if (select_mode == SELECT_SINGLE) { @@ -792,7 +774,16 @@ void ItemList::gui_input(const Ref<InputEvent> &p_event) { search_string = ""; //any mousepress cancels if (current % current_columns != 0) { - set_current(current - 1); + int current_row = current / current_columns; + int next = current - 1; + while (!CAN_SELECT(next)) { + next = next - 1; + } + if (next < 0 || !IS_SAME_ROW(next, current_row)) { + accept_event(); + return; + } + set_current(next); ensure_current_is_visible(); if (select_mode == SELECT_SINGLE) { emit_signal(SNAME("item_selected"), current); @@ -803,7 +794,16 @@ void ItemList::gui_input(const Ref<InputEvent> &p_event) { search_string = ""; //any mousepress cancels if (current % current_columns != (current_columns - 1) && current + 1 < items.size()) { - set_current(current + 1); + int current_row = current / current_columns; + int next = current + 1; + while (!CAN_SELECT(next)) { + next = next + 1; + } + if (items.size() <= next || !IS_SAME_ROW(next, current_row)) { + accept_event(); + return; + } + set_current(next); ensure_current_is_visible(); if (select_mode == SELECT_SINGLE) { emit_signal(SNAME("item_selected"), current); @@ -879,6 +879,9 @@ void ItemList::gui_input(const Ref<InputEvent> &p_event) { if (scroll_bar->get_value() != prev_scroll) { accept_event(); //accept event if scroll changed } + +#undef CAN_SELECT +#undef IS_SAME_ROW } void ItemList::ensure_current_is_visible() { @@ -937,8 +940,8 @@ void ItemList::_notification(int p_what) { draw_style_box(bg, Rect2(Point2(), size)); - int hseparation = get_theme_constant(SNAME("hseparation")); - int vseparation = get_theme_constant(SNAME("vseparation")); + int hseparation = get_theme_constant(SNAME("h_separation")); + int vseparation = get_theme_constant(SNAME("v_separation")); int icon_margin = get_theme_constant(SNAME("icon_margin")); int line_separation = get_theme_constant(SNAME("line_separation")); Color font_outline_color = get_theme_color(SNAME("font_outline_color")); @@ -1518,7 +1521,7 @@ bool ItemList::has_auto_height() const { return auto_height; } -void ItemList::set_text_overrun_behavior(TextParagraph::OverrunBehavior p_behavior) { +void ItemList::set_text_overrun_behavior(TextServer::OverrunBehavior p_behavior) { if (text_overrun_behavior != p_behavior) { text_overrun_behavior = p_behavior; for (int i = 0; i < items.size(); i++) { @@ -1529,7 +1532,7 @@ void ItemList::set_text_overrun_behavior(TextParagraph::OverrunBehavior p_behavi } } -TextParagraph::OverrunBehavior ItemList::get_text_overrun_behavior() const { +TextServer::OverrunBehavior ItemList::get_text_overrun_behavior() const { return text_overrun_behavior; } @@ -1624,10 +1627,6 @@ void ItemList::_bind_methods() { ClassDB::bind_method(D_METHOD("set_item_text_direction", "idx", "direction"), &ItemList::set_item_text_direction); ClassDB::bind_method(D_METHOD("get_item_text_direction", "idx"), &ItemList::get_item_text_direction); - ClassDB::bind_method(D_METHOD("set_item_opentype_feature", "idx", "tag", "value"), &ItemList::set_item_opentype_feature); - ClassDB::bind_method(D_METHOD("get_item_opentype_feature", "idx", "tag"), &ItemList::get_item_opentype_feature); - ClassDB::bind_method(D_METHOD("clear_item_opentype_features", "idx"), &ItemList::clear_item_opentype_features); - ClassDB::bind_method(D_METHOD("set_item_language", "idx", "language"), &ItemList::set_item_language); ClassDB::bind_method(D_METHOD("get_item_language", "idx"), &ItemList::get_item_language); @@ -1731,11 +1730,11 @@ void ItemList::_bind_methods() { ADD_GROUP("Columns", ""); ADD_PROPERTY(PropertyInfo(Variant::INT, "max_columns", PROPERTY_HINT_RANGE, "0,10,1,or_greater"), "set_max_columns", "get_max_columns"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "same_column_width"), "set_same_column_width", "is_same_column_width"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "fixed_column_width", PROPERTY_HINT_RANGE, "0,100,1,or_greater"), "set_fixed_column_width", "get_fixed_column_width"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "fixed_column_width", PROPERTY_HINT_RANGE, "0,100,1,or_greater,suffix:px"), "set_fixed_column_width", "get_fixed_column_width"); ADD_GROUP("Icon", ""); ADD_PROPERTY(PropertyInfo(Variant::INT, "icon_mode", PROPERTY_HINT_ENUM, "Top,Left"), "set_icon_mode", "get_icon_mode"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "icon_scale"), "set_icon_scale", "get_icon_scale"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "fixed_icon_size"), "set_fixed_icon_size", "get_fixed_icon_size"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "fixed_icon_size", PROPERTY_HINT_NONE, "suffix:px"), "set_fixed_icon_size", "get_fixed_icon_size"); BIND_ENUM_CONSTANT(ICON_MODE_TOP); BIND_ENUM_CONSTANT(ICON_MODE_LEFT); @@ -1744,11 +1743,10 @@ void ItemList::_bind_methods() { BIND_ENUM_CONSTANT(SELECT_MULTI); ADD_SIGNAL(MethodInfo("item_selected", PropertyInfo(Variant::INT, "index"))); - ADD_SIGNAL(MethodInfo("item_rmb_selected", PropertyInfo(Variant::INT, "index"), PropertyInfo(Variant::VECTOR2, "at_position"))); + ADD_SIGNAL(MethodInfo("empty_clicked", PropertyInfo(Variant::VECTOR2, "at_position"), PropertyInfo(Variant::INT, "mouse_button_index"))); + ADD_SIGNAL(MethodInfo("item_clicked", PropertyInfo(Variant::INT, "index"), PropertyInfo(Variant::VECTOR2, "at_position"), PropertyInfo(Variant::INT, "mouse_button_index"))); ADD_SIGNAL(MethodInfo("multi_selected", PropertyInfo(Variant::INT, "index"), PropertyInfo(Variant::BOOL, "selected"))); ADD_SIGNAL(MethodInfo("item_activated", PropertyInfo(Variant::INT, "index"))); - ADD_SIGNAL(MethodInfo("rmb_clicked", PropertyInfo(Variant::VECTOR2, "at_position"))); - ADD_SIGNAL(MethodInfo("nothing_selected")); GLOBAL_DEF("gui/timers/incremental_search_max_interval_msec", 2000); ProjectSettings::get_singleton()->set_custom_property_info("gui/timers/incremental_search_max_interval_msec", PropertyInfo(Variant::INT, "gui/timers/incremental_search_max_interval_msec", PROPERTY_HINT_RANGE, "0,10000,1,or_greater")); // No negative numbers diff --git a/scene/gui/item_list.h b/scene/gui/item_list.h index ffbe7d055a..c7d87da0b5 100644 --- a/scene/gui/item_list.h +++ b/scene/gui/item_list.h @@ -58,7 +58,6 @@ private: Ref<Texture2D> tag_icon; String text; Ref<TextParagraph> text_buf; - Dictionary opentype_features; String language; TextDirection text_direction = TEXT_DIRECTION_AUTO; @@ -99,7 +98,7 @@ private: SelectMode select_mode = SELECT_SINGLE; IconMode icon_mode = ICON_MODE_LEFT; VScrollBar *scroll_bar = nullptr; - TextParagraph::OverrunBehavior text_overrun_behavior = TextParagraph::OVERRUN_TRIM_ELLIPSIS; + TextServer::OverrunBehavior text_overrun_behavior = TextServer::OVERRUN_TRIM_ELLIPSIS; uint64_t search_time_msec = 0; String search_string; @@ -145,10 +144,6 @@ public: void set_item_text_direction(int p_idx, TextDirection p_text_direction); TextDirection get_item_text_direction(int p_idx) const; - void set_item_opentype_feature(int p_idx, const String &p_name, int p_value); - int get_item_opentype_feature(int p_idx, const String &p_name) const; - void clear_item_opentype_features(int p_idx); - void set_item_language(int p_idx, const String &p_language); String get_item_language(int p_idx) const; @@ -188,8 +183,8 @@ public: void set_item_custom_fg_color(int p_idx, const Color &p_custom_fg_color); Color get_item_custom_fg_color(int p_idx) const; - void set_text_overrun_behavior(TextParagraph::OverrunBehavior p_behavior); - TextParagraph::OverrunBehavior get_text_overrun_behavior() const; + void set_text_overrun_behavior(TextServer::OverrunBehavior p_behavior); + TextServer::OverrunBehavior get_text_overrun_behavior() const; void select(int p_idx, bool p_single = true); void deselect(int p_idx); diff --git a/scene/gui/label.cpp b/scene/gui/label.cpp index cd6fc168c2..8094812203 100644 --- a/scene/gui/label.cpp +++ b/scene/gui/label.cpp @@ -31,24 +31,25 @@ #include "label.h" #include "core/config/project_settings.h" +#include "core/core_string_names.h" #include "core/string/print_string.h" #include "core/string/translation.h" #include "servers/text_server.h" -void Label::set_autowrap_mode(Label::AutowrapMode p_mode) { +void Label::set_autowrap_mode(TextServer::AutowrapMode p_mode) { if (autowrap_mode != p_mode) { autowrap_mode = p_mode; lines_dirty = true; } update(); - if (clip || overrun_behavior != OVERRUN_NO_TRIMMING) { + if (clip || overrun_behavior != TextServer::OVERRUN_NO_TRIMMING) { update_minimum_size(); } } -Label::AutowrapMode Label::get_autowrap_mode() const { +TextServer::AutowrapMode Label::get_autowrap_mode() const { return autowrap_mode; } @@ -64,17 +65,18 @@ bool Label::is_uppercase() const { } int Label::get_line_height(int p_line) const { - Ref<Font> font = get_theme_font(SNAME("font")); + Ref<Font> font = (settings.is_valid() && settings->get_font().is_valid()) ? settings->get_font() : get_theme_font(SNAME("font")); if (p_line >= 0 && p_line < lines_rid.size()) { - return TS->shaped_text_get_size(lines_rid[p_line]).y + font->get_spacing(TextServer::SPACING_TOP) + font->get_spacing(TextServer::SPACING_BOTTOM); + return TS->shaped_text_get_size(lines_rid[p_line]).y; } else if (lines_rid.size() > 0) { int h = 0; for (int i = 0; i < lines_rid.size(); i++) { - h = MAX(h, TS->shaped_text_get_size(lines_rid[i]).y) + font->get_spacing(TextServer::SPACING_TOP) + font->get_spacing(TextServer::SPACING_BOTTOM); + h = MAX(h, TS->shaped_text_get_size(lines_rid[i]).y); } return h; } else { - return font->get_height(get_theme_font_size(SNAME("font_size"))); + int font_size = settings.is_valid() ? settings->get_font_size() : get_theme_font_size(SNAME("font_size")); + return font->get_height(font_size); } } @@ -83,7 +85,6 @@ void Label::_shape() { int width = (get_size().width - style->get_minimum_size().width); if (dirty || font_dirty) { - String lang = (!language.is_empty()) ? language : TranslationServer::get_singleton()->get_tool_locale(); if (dirty) { TS->shaped_text_clear(text_rid); } @@ -92,21 +93,24 @@ void Label::_shape() { } else { TS->shaped_text_set_direction(text_rid, (TextServer::Direction)text_direction); } - const Ref<Font> &font = get_theme_font(SNAME("font")); - int font_size = get_theme_font_size(SNAME("font_size")); + const Ref<Font> &font = (settings.is_valid() && settings->get_font().is_valid()) ? settings->get_font() : get_theme_font(SNAME("font")); + int font_size = settings.is_valid() ? settings->get_font_size() : get_theme_font_size(SNAME("font_size")); ERR_FAIL_COND(font.is_null()); - String text = (uppercase) ? TS->string_to_upper(xl_text, lang) : xl_text; - if (visible_chars >= 0 && visible_chars_behavior == VC_CHARS_BEFORE_SHAPING) { + String text = (uppercase) ? TS->string_to_upper(xl_text, language) : xl_text; + if (visible_chars >= 0 && visible_chars_behavior == TextServer::VC_CHARS_BEFORE_SHAPING) { text = text.substr(0, visible_chars); } if (dirty) { - TS->shaped_text_add_string(text_rid, text, font->get_rids(), font_size, opentype_features, lang); + TS->shaped_text_add_string(text_rid, text, font->get_rids(), font_size, font->get_opentype_features(), language); } else { int spans = TS->shaped_get_span_count(text_rid); for (int i = 0; i < spans; i++) { - TS->shaped_set_span_update_font(text_rid, i, font->get_rids(), font_size, opentype_features); + TS->shaped_set_span_update_font(text_rid, i, font->get_rids(), font_size, font->get_opentype_features()); } } + for (int i = 0; i < TextServer::SPACING_MAX; i++) { + TS->shaped_text_set_spacing(text_rid, TextServer::SpacingType(i), font->get_spacing(TextServer::SpacingType(i))); + } TS->shaped_text_set_bidi_override(text_rid, structured_text_parser(st_parser, st_args, text)); dirty = false; font_dirty = false; @@ -119,18 +123,18 @@ void Label::_shape() { } lines_rid.clear(); - uint16_t autowrap_flags = TextServer::BREAK_MANDATORY; + BitField<TextServer::LineBreakFlag> autowrap_flags = TextServer::BREAK_MANDATORY; switch (autowrap_mode) { - case AUTOWRAP_WORD_SMART: - autowrap_flags = TextServer::BREAK_WORD_BOUND_ADAPTIVE | TextServer::BREAK_MANDATORY; + case TextServer::AUTOWRAP_WORD_SMART: + autowrap_flags = TextServer::BREAK_WORD_BOUND | TextServer::BREAK_ADAPTIVE | TextServer::BREAK_MANDATORY; break; - case AUTOWRAP_WORD: + case TextServer::AUTOWRAP_WORD: autowrap_flags = TextServer::BREAK_WORD_BOUND | TextServer::BREAK_MANDATORY; break; - case AUTOWRAP_ARBITRARY: + case TextServer::AUTOWRAP_ARBITRARY: autowrap_flags = TextServer::BREAK_GRAPHEME_BOUND | TextServer::BREAK_MANDATORY; break; - case AUTOWRAP_OFF: + case TextServer::AUTOWRAP_OFF: break; } PackedInt32Array line_breaks = TS->shaped_text_get_line_breaks(text_rid, width, 0, autowrap_flags); @@ -146,7 +150,7 @@ void Label::_shape() { return; } - if (autowrap_mode == AUTOWRAP_OFF) { + if (autowrap_mode == TextServer::AUTOWRAP_OFF) { minsize.width = 0.0f; for (int i = 0; i < lines_rid.size(); i++) { if (minsize.width < TS->shaped_text_get_size(lines_rid[i]).x) { @@ -156,35 +160,35 @@ void Label::_shape() { } if (lines_dirty) { - uint16_t overrun_flags = TextServer::OVERRUN_NO_TRIMMING; + BitField<TextServer::TextOverrunFlag> overrun_flags = TextServer::OVERRUN_NO_TRIM; switch (overrun_behavior) { - case OVERRUN_TRIM_WORD_ELLIPSIS: - overrun_flags |= TextServer::OVERRUN_TRIM; - overrun_flags |= TextServer::OVERRUN_TRIM_WORD_ONLY; - overrun_flags |= TextServer::OVERRUN_ADD_ELLIPSIS; + case TextServer::OVERRUN_TRIM_WORD_ELLIPSIS: + overrun_flags.set_flag(TextServer::OVERRUN_TRIM); + overrun_flags.set_flag(TextServer::OVERRUN_TRIM_WORD_ONLY); + overrun_flags.set_flag(TextServer::OVERRUN_ADD_ELLIPSIS); break; - case OVERRUN_TRIM_ELLIPSIS: - overrun_flags |= TextServer::OVERRUN_TRIM; - overrun_flags |= TextServer::OVERRUN_ADD_ELLIPSIS; + case TextServer::OVERRUN_TRIM_ELLIPSIS: + overrun_flags.set_flag(TextServer::OVERRUN_TRIM); + overrun_flags.set_flag(TextServer::OVERRUN_ADD_ELLIPSIS); break; - case OVERRUN_TRIM_WORD: - overrun_flags |= TextServer::OVERRUN_TRIM; - overrun_flags |= TextServer::OVERRUN_TRIM_WORD_ONLY; + case TextServer::OVERRUN_TRIM_WORD: + overrun_flags.set_flag(TextServer::OVERRUN_TRIM); + overrun_flags.set_flag(TextServer::OVERRUN_TRIM_WORD_ONLY); break; - case OVERRUN_TRIM_CHAR: - overrun_flags |= TextServer::OVERRUN_TRIM; + case TextServer::OVERRUN_TRIM_CHAR: + overrun_flags.set_flag(TextServer::OVERRUN_TRIM); break; - case OVERRUN_NO_TRIMMING: + case TextServer::OVERRUN_NO_TRIMMING: break; } // Fill after min_size calculation. - if (autowrap_mode != AUTOWRAP_OFF) { + if (autowrap_mode != TextServer::AUTOWRAP_OFF) { int visible_lines = get_visible_line_count(); bool lines_hidden = visible_lines > 0 && visible_lines < lines_rid.size(); if (lines_hidden) { - overrun_flags |= TextServer::OVERRUN_ENFORCE_ELLIPSIS; + overrun_flags.set_flag(TextServer::OVERRUN_ENFORCE_ELLIPSIS); } if (horizontal_alignment == HORIZONTAL_ALIGNMENT_FILL) { for (int i = 0; i < lines_rid.size(); i++) { @@ -202,7 +206,7 @@ void Label::_shape() { for (int i = 0; i < lines_rid.size(); i++) { if (horizontal_alignment == HORIZONTAL_ALIGNMENT_FILL) { TS->shaped_text_fit_to_width(lines_rid[i], width); - overrun_flags |= TextServer::OVERRUN_JUSTIFICATION_AWARE; + overrun_flags.set_flag(TextServer::OVERRUN_JUSTIFICATION_AWARE); TS->shaped_text_overrun_trim_to_width(lines_rid[i], width, overrun_flags); TS->shaped_text_fit_to_width(lines_rid[i], width, TextServer::JUSTIFICATION_WORD_BOUND | TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_CONSTRAIN_ELLIPSIS); } else { @@ -215,15 +219,14 @@ void Label::_shape() { _update_visible(); - if (autowrap_mode == AUTOWRAP_OFF || !clip || overrun_behavior == OVERRUN_NO_TRIMMING) { + if (autowrap_mode == TextServer::AUTOWRAP_OFF || !clip || overrun_behavior == TextServer::OVERRUN_NO_TRIMMING) { update_minimum_size(); } } void Label::_update_visible() { - int line_spacing = get_theme_constant(SNAME("line_spacing"), SNAME("Label")); + int line_spacing = settings.is_valid() ? settings->get_line_spacing() : get_theme_constant(SNAME("line_spacing"), SNAME("Label")); Ref<StyleBox> style = get_theme_stylebox(SNAME("normal"), SNAME("Label")); - Ref<Font> font = get_theme_font(SNAME("font")); int lines_visible = lines_rid.size(); if (max_lines_visible >= 0 && lines_visible > max_lines_visible) { @@ -233,7 +236,7 @@ void Label::_update_visible() { minsize.height = 0; int last_line = MIN(lines_rid.size(), lines_visible + lines_skipped); for (int64_t i = lines_skipped; i < last_line; i++) { - minsize.height += TS->shaped_text_get_size(lines_rid[i]).y + font->get_spacing(TextServer::SPACING_TOP) + font->get_spacing(TextServer::SPACING_BOTTOM) + line_spacing; + minsize.height += TS->shaped_text_get_size(lines_rid[i]).y + line_spacing; if (minsize.height > (get_size().height - style->get_minimum_size().height + line_spacing)) { break; } @@ -293,17 +296,19 @@ void Label::_notification(int p_what) { RID ci = get_canvas_item(); + bool has_settings = settings.is_valid(); + Size2 string_size; Size2 size = get_size(); Ref<StyleBox> style = get_theme_stylebox(SNAME("normal")); - Ref<Font> font = get_theme_font(SNAME("font")); - Color font_color = get_theme_color(SNAME("font_color")); - Color font_shadow_color = get_theme_color(SNAME("font_shadow_color")); - Point2 shadow_ofs(get_theme_constant(SNAME("shadow_offset_x")), get_theme_constant(SNAME("shadow_offset_y"))); - int line_spacing = get_theme_constant(SNAME("line_spacing")); - Color font_outline_color = get_theme_color(SNAME("font_outline_color")); - int outline_size = get_theme_constant(SNAME("outline_size")); - int shadow_outline_size = get_theme_constant(SNAME("shadow_outline_size")); + Ref<Font> font = (has_settings && settings->get_font().is_valid()) ? settings->get_font() : get_theme_font(SNAME("font")); + Color font_color = has_settings ? settings->get_font_color() : get_theme_color(SNAME("font_color")); + Color font_shadow_color = has_settings ? settings->get_shadow_color() : get_theme_color(SNAME("font_shadow_color")); + Point2 shadow_ofs = has_settings ? settings->get_shadow_offset() : Point2(get_theme_constant(SNAME("shadow_offset_x")), get_theme_constant(SNAME("shadow_offset_y"))); + int line_spacing = has_settings ? settings->get_line_spacing() : get_theme_constant(SNAME("line_spacing")); + Color font_outline_color = has_settings ? settings->get_outline_color() : get_theme_color(SNAME("font_outline_color")); + int outline_size = has_settings ? settings->get_outline_size() : get_theme_constant(SNAME("outline_size")); + int shadow_outline_size = has_settings ? settings->get_shadow_size() : get_theme_constant(SNAME("shadow_outline_size")); bool rtl = (TS->shaped_text_get_inferred_direction(text_rid) == TextServer::DIRECTION_RTL); bool rtl_layout = is_layout_rtl(); @@ -314,7 +319,7 @@ void Label::_notification(int p_what) { // Get number of lines to fit to the height. for (int64_t i = lines_skipped; i < lines_rid.size(); i++) { - total_h += TS->shaped_text_get_size(lines_rid[i]).y + font->get_spacing(TextServer::SPACING_TOP) + font->get_spacing(TextServer::SPACING_BOTTOM) + line_spacing; + total_h += TS->shaped_text_get_size(lines_rid[i]).y + line_spacing; if (total_h > (get_size().height - style->get_minimum_size().height + line_spacing)) { break; } @@ -326,15 +331,15 @@ void Label::_notification(int p_what) { } int last_line = MIN(lines_rid.size(), lines_visible + lines_skipped); - bool trim_chars = (visible_chars >= 0) && (visible_chars_behavior == VC_CHARS_AFTER_SHAPING); - bool trim_glyphs_ltr = (visible_chars >= 0) && ((visible_chars_behavior == VC_GLYPHS_LTR) || ((visible_chars_behavior == VC_GLYPHS_AUTO) && !rtl_layout)); - bool trim_glyphs_rtl = (visible_chars >= 0) && ((visible_chars_behavior == VC_GLYPHS_RTL) || ((visible_chars_behavior == VC_GLYPHS_AUTO) && rtl_layout)); + bool trim_chars = (visible_chars >= 0) && (visible_chars_behavior == TextServer::VC_CHARS_AFTER_SHAPING); + bool trim_glyphs_ltr = (visible_chars >= 0) && ((visible_chars_behavior == TextServer::VC_GLYPHS_LTR) || ((visible_chars_behavior == TextServer::VC_GLYPHS_AUTO) && !rtl_layout)); + bool trim_glyphs_rtl = (visible_chars >= 0) && ((visible_chars_behavior == TextServer::VC_GLYPHS_RTL) || ((visible_chars_behavior == TextServer::VC_GLYPHS_AUTO) && rtl_layout)); // Get real total height. int total_glyphs = 0; total_h = 0; for (int64_t i = lines_skipped; i < last_line; i++) { - total_h += TS->shaped_text_get_size(lines_rid[i]).y + font->get_spacing(TextServer::SPACING_TOP) + font->get_spacing(TextServer::SPACING_BOTTOM) + line_spacing; + total_h += TS->shaped_text_get_size(lines_rid[i]).y + line_spacing; total_glyphs += TS->shaped_text_get_glyph_count(lines_rid[i]) + TS->shaped_text_get_ellipsis_glyph_count(lines_rid[i]); } int visible_glyphs = total_glyphs * percent_visible; @@ -374,10 +379,10 @@ void Label::_notification(int p_what) { for (int i = lines_skipped; i < last_line; i++) { Size2 line_size = TS->shaped_text_get_size(lines_rid[i]); ofs.x = 0; - ofs.y += TS->shaped_text_get_ascent(lines_rid[i]) + font->get_spacing(TextServer::SPACING_TOP); + ofs.y += TS->shaped_text_get_ascent(lines_rid[i]); switch (horizontal_alignment) { case HORIZONTAL_ALIGNMENT_FILL: - if (rtl && autowrap_mode != AUTOWRAP_OFF) { + if (rtl && autowrap_mode != TextServer::AUTOWRAP_OFF) { ofs.x = int(size.width - style->get_margin(SIDE_RIGHT) - line_size.width); } else { ofs.x = style->get_offset().x; @@ -527,7 +532,7 @@ void Label::_notification(int p_what) { } } } - ofs.y += TS->shaped_text_get_descent(lines_rid[i]) + vsep + line_spacing + font->get_spacing(TextServer::SPACING_BOTTOM); + ofs.y += TS->shaped_text_get_descent(lines_rid[i]) + vsep + line_spacing; } } break; @@ -550,14 +555,16 @@ Size2 Label::get_minimum_size() const { Size2 min_size = minsize; - Ref<Font> font = get_theme_font(SNAME("font")); - min_size.height = MAX(min_size.height, font->get_height(get_theme_font_size(SNAME("font_size"))) + font->get_spacing(TextServer::SPACING_TOP) + font->get_spacing(TextServer::SPACING_BOTTOM)); + const Ref<Font> &font = (settings.is_valid() && settings->get_font().is_valid()) ? settings->get_font() : get_theme_font(SNAME("font")); + int font_size = settings.is_valid() ? settings->get_font_size() : get_theme_font_size(SNAME("font_size")); + + min_size.height = MAX(min_size.height, font->get_height(font_size) + font->get_spacing(TextServer::SPACING_TOP) + font->get_spacing(TextServer::SPACING_BOTTOM)); Size2 min_style = get_theme_stylebox(SNAME("normal"))->get_minimum_size(); - if (autowrap_mode != AUTOWRAP_OFF) { - return Size2(1, (clip || overrun_behavior != OVERRUN_NO_TRIMMING) ? 1 : min_size.height) + min_style; + if (autowrap_mode != TextServer::AUTOWRAP_OFF) { + return Size2(1, (clip || overrun_behavior != TextServer::OVERRUN_NO_TRIMMING) ? 1 : min_size.height) + min_style; } else { - if (clip || overrun_behavior != OVERRUN_NO_TRIMMING) { + if (clip || overrun_behavior != TextServer::OVERRUN_NO_TRIMMING) { min_size.width = 1; } return min_size + min_style; @@ -576,13 +583,12 @@ int Label::get_line_count() const { } int Label::get_visible_line_count() const { - Ref<Font> font = get_theme_font(SNAME("font")); Ref<StyleBox> style = get_theme_stylebox(SNAME("normal")); - int line_spacing = get_theme_constant(SNAME("line_spacing")); + int line_spacing = settings.is_valid() ? settings->get_line_spacing() : get_theme_constant(SNAME("line_spacing")); int lines_visible = 0; float total_h = 0.0; for (int64_t i = lines_skipped; i < lines_rid.size(); i++) { - total_h += TS->shaped_text_get_size(lines_rid[i]).y + font->get_spacing(TextServer::SPACING_TOP) + font->get_spacing(TextServer::SPACING_BOTTOM) + line_spacing; + total_h += TS->shaped_text_get_size(lines_rid[i]).y + line_spacing; if (total_h > (get_size().height - style->get_minimum_size().height + line_spacing)) { break; } @@ -639,6 +645,28 @@ void Label::set_text(const String &p_string) { update_minimum_size(); } +void Label::_invalidate() { + font_dirty = true; + update(); +} + +void Label::set_label_settings(const Ref<LabelSettings> &p_settings) { + if (settings != p_settings) { + if (settings.is_valid()) { + settings->disconnect(CoreStringNames::get_singleton()->changed, callable_mp(this, &Label::_invalidate)); + } + settings = p_settings; + if (settings.is_valid()) { + settings->connect(CoreStringNames::get_singleton()->changed, callable_mp(this, &Label::_invalidate), varray(), CONNECT_REFERENCE_COUNTED); + } + _invalidate(); + } +} + +Ref<LabelSettings> Label::get_label_settings() const { + return settings; +} + void Label::set_text_direction(Control::TextDirection p_text_direction) { ERR_FAIL_COND((int)p_text_direction < -1 || (int)p_text_direction > 3); if (text_direction != p_text_direction) { @@ -648,21 +676,21 @@ void Label::set_text_direction(Control::TextDirection p_text_direction) { } } -void Label::set_structured_text_bidi_override(Control::StructuredTextParser p_parser) { +void Label::set_structured_text_bidi_override(TextServer::StructuredTextParser p_parser) { if (st_parser != p_parser) { st_parser = p_parser; - font_dirty = true; + dirty = true; update(); } } -Control::StructuredTextParser Label::get_structured_text_bidi_override() const { +TextServer::StructuredTextParser Label::get_structured_text_bidi_override() const { return st_parser; } void Label::set_structured_text_bidi_override_options(Array p_args) { st_args = p_args; - font_dirty = true; + dirty = true; update(); } @@ -674,29 +702,6 @@ Control::TextDirection Label::get_text_direction() const { return text_direction; } -void Label::clear_opentype_features() { - opentype_features.clear(); - font_dirty = true; - update(); -} - -void Label::set_opentype_feature(const String &p_name, int p_value) { - int32_t tag = TS->name_to_tag(p_name); - if (!opentype_features.has(tag) || (int)opentype_features[tag] != p_value) { - opentype_features[tag] = p_value; - font_dirty = true; - update(); - } -} - -int Label::get_opentype_feature(const String &p_name) const { - int32_t tag = TS->name_to_tag(p_name); - if (!opentype_features.has(tag)) { - return -1; - } - return opentype_features[tag]; -} - void Label::set_language(const String &p_language) { if (language != p_language) { language = p_language; @@ -719,18 +724,18 @@ bool Label::is_clipping_text() const { return clip; } -void Label::set_text_overrun_behavior(Label::OverrunBehavior p_behavior) { +void Label::set_text_overrun_behavior(TextServer::OverrunBehavior p_behavior) { if (overrun_behavior != p_behavior) { overrun_behavior = p_behavior; lines_dirty = true; } update(); - if (clip || overrun_behavior != OVERRUN_NO_TRIMMING) { + if (clip || overrun_behavior != TextServer::OVERRUN_NO_TRIMMING) { update_minimum_size(); } } -Label::OverrunBehavior Label::get_text_overrun_behavior() const { +TextServer::OverrunBehavior Label::get_text_overrun_behavior() const { return overrun_behavior; } @@ -746,7 +751,7 @@ void Label::set_visible_characters(int p_amount) { } else { percent_visible = 1.0; } - if (visible_chars_behavior == VC_CHARS_BEFORE_SHAPING) { + if (visible_chars_behavior == TextServer::VC_CHARS_BEFORE_SHAPING) { dirty = true; } update(); @@ -766,7 +771,7 @@ void Label::set_percent_visible(float p_percent) { visible_chars = get_total_character_count() * p_percent; percent_visible = p_percent; } - if (visible_chars_behavior == VC_CHARS_BEFORE_SHAPING) { + if (visible_chars_behavior == TextServer::VC_CHARS_BEFORE_SHAPING) { dirty = true; } update(); @@ -777,11 +782,11 @@ float Label::get_percent_visible() const { return percent_visible; } -Label::VisibleCharactersBehavior Label::get_visible_characters_behavior() const { +TextServer::VisibleCharactersBehavior Label::get_visible_characters_behavior() const { return visible_chars_behavior; } -void Label::set_visible_characters_behavior(Label::VisibleCharactersBehavior p_behavior) { +void Label::set_visible_characters_behavior(TextServer::VisibleCharactersBehavior p_behavior) { if (visible_chars_behavior != p_behavior) { visible_chars_behavior = p_behavior; dirty = true; @@ -818,56 +823,6 @@ int Label::get_total_character_count() const { return xl_text.length(); } -bool Label::_set(const StringName &p_name, const Variant &p_value) { - String str = p_name; - if (str.begins_with("opentype_features/")) { - String name = str.get_slicec('/', 1); - int32_t tag = TS->name_to_tag(name); - int value = p_value; - if (value == -1) { - if (opentype_features.has(tag)) { - opentype_features.erase(tag); - font_dirty = true; - update(); - } - } else { - if (!opentype_features.has(tag) || (int)opentype_features[tag] != value) { - opentype_features[tag] = value; - font_dirty = true; - update(); - } - } - notify_property_list_changed(); - return true; - } - - return false; -} - -bool Label::_get(const StringName &p_name, Variant &r_ret) const { - String str = p_name; - if (str.begins_with("opentype_features/")) { - String name = str.get_slicec('/', 1); - int32_t tag = TS->name_to_tag(name); - if (opentype_features.has(tag)) { - r_ret = opentype_features[tag]; - return true; - } else { - r_ret = -1; - return true; - } - } - return false; -} - -void Label::_get_property_list(List<PropertyInfo> *p_list) const { - for (const Variant *ftr = opentype_features.next(nullptr); ftr != nullptr; ftr = opentype_features.next(ftr)) { - String name = TS->tag_to_name(*ftr); - p_list->push_back(PropertyInfo(Variant::INT, "opentype_features/" + name)); - } - p_list->push_back(PropertyInfo(Variant::NIL, "opentype_features/_new", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR)); -} - void Label::_bind_methods() { ClassDB::bind_method(D_METHOD("set_horizontal_alignment", "alignment"), &Label::set_horizontal_alignment); ClassDB::bind_method(D_METHOD("get_horizontal_alignment"), &Label::get_horizontal_alignment); @@ -875,11 +830,10 @@ void Label::_bind_methods() { ClassDB::bind_method(D_METHOD("get_vertical_alignment"), &Label::get_vertical_alignment); ClassDB::bind_method(D_METHOD("set_text", "text"), &Label::set_text); ClassDB::bind_method(D_METHOD("get_text"), &Label::get_text); + ClassDB::bind_method(D_METHOD("set_label_settings", "settings"), &Label::set_label_settings); + ClassDB::bind_method(D_METHOD("get_label_settings"), &Label::get_label_settings); ClassDB::bind_method(D_METHOD("set_text_direction", "direction"), &Label::set_text_direction); ClassDB::bind_method(D_METHOD("get_text_direction"), &Label::get_text_direction); - ClassDB::bind_method(D_METHOD("set_opentype_feature", "tag", "value"), &Label::set_opentype_feature); - ClassDB::bind_method(D_METHOD("get_opentype_feature", "tag"), &Label::get_opentype_feature); - ClassDB::bind_method(D_METHOD("clear_opentype_features"), &Label::clear_opentype_features); ClassDB::bind_method(D_METHOD("set_language", "language"), &Label::set_language); ClassDB::bind_method(D_METHOD("get_language"), &Label::get_language); ClassDB::bind_method(D_METHOD("set_autowrap_mode", "autowrap_mode"), &Label::set_autowrap_mode); @@ -909,24 +863,8 @@ void Label::_bind_methods() { ClassDB::bind_method(D_METHOD("set_structured_text_bidi_override_options", "args"), &Label::set_structured_text_bidi_override_options); ClassDB::bind_method(D_METHOD("get_structured_text_bidi_override_options"), &Label::get_structured_text_bidi_override_options); - BIND_ENUM_CONSTANT(AUTOWRAP_OFF); - BIND_ENUM_CONSTANT(AUTOWRAP_ARBITRARY); - BIND_ENUM_CONSTANT(AUTOWRAP_WORD); - BIND_ENUM_CONSTANT(AUTOWRAP_WORD_SMART); - - BIND_ENUM_CONSTANT(OVERRUN_NO_TRIMMING); - BIND_ENUM_CONSTANT(OVERRUN_TRIM_CHAR); - BIND_ENUM_CONSTANT(OVERRUN_TRIM_WORD); - BIND_ENUM_CONSTANT(OVERRUN_TRIM_ELLIPSIS); - BIND_ENUM_CONSTANT(OVERRUN_TRIM_WORD_ELLIPSIS); - - BIND_ENUM_CONSTANT(VC_CHARS_BEFORE_SHAPING); - BIND_ENUM_CONSTANT(VC_CHARS_AFTER_SHAPING); - BIND_ENUM_CONSTANT(VC_GLYPHS_AUTO); - BIND_ENUM_CONSTANT(VC_GLYPHS_LTR); - BIND_ENUM_CONSTANT(VC_GLYPHS_RTL); - ADD_PROPERTY(PropertyInfo(Variant::STRING, "text", PROPERTY_HINT_MULTILINE_TEXT, "", PROPERTY_USAGE_DEFAULT_INTL), "set_text", "get_text"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "label_settings", PROPERTY_HINT_RESOURCE_TYPE, "LabelSettings"), "set_label_settings", "get_label_settings"); ADD_PROPERTY(PropertyInfo(Variant::INT, "horizontal_alignment", PROPERTY_HINT_ENUM, "Left,Center,Right,Fill"), "set_horizontal_alignment", "get_horizontal_alignment"); ADD_PROPERTY(PropertyInfo(Variant::INT, "vertical_alignment", PROPERTY_HINT_ENUM, "Top,Center,Bottom,Fill"), "set_vertical_alignment", "get_vertical_alignment"); ADD_PROPERTY(PropertyInfo(Variant::INT, "autowrap_mode", PROPERTY_HINT_ENUM, "Off,Arbitrary,Word,Word (Smart)"), "set_autowrap_mode", "get_autowrap_mode"); @@ -941,11 +879,9 @@ void Label::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::INT, "visible_characters_behavior", PROPERTY_HINT_ENUM, "Characters Before Shaping,Characters After Shaping,Glyphs (Layout Direction),Glyphs (Left-to-Right),Glyphs (Right-to-Left)"), "set_visible_characters_behavior", "get_visible_characters_behavior"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "percent_visible", PROPERTY_HINT_RANGE, "0,1,0.001"), "set_percent_visible", "get_percent_visible"); - ADD_GROUP("Locale", ""); + ADD_GROUP("BiDi", ""); 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", PROPERTY_HINT_LOCALE_ID, ""), "set_language", "get_language"); - - ADD_GROUP("Structured Text", "structured_text_"); ADD_PROPERTY(PropertyInfo(Variant::INT, "structured_text_bidi_override", PROPERTY_HINT_ENUM, "Default,URI,File,Email,List,None,Custom"), "set_structured_text_bidi_override", "get_structured_text_bidi_override"); ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "structured_text_bidi_override_options"), "set_structured_text_bidi_override_options", "get_structured_text_bidi_override_options"); } diff --git a/scene/gui/label.h b/scene/gui/label.h index 0b931b3084..3734fce1bb 100644 --- a/scene/gui/label.h +++ b/scene/gui/label.h @@ -32,42 +32,19 @@ #define LABEL_H #include "scene/gui/control.h" +#include "scene/resources/label_settings.h" class Label : public Control { GDCLASS(Label, Control); -public: - enum AutowrapMode { - AUTOWRAP_OFF, - AUTOWRAP_ARBITRARY, - AUTOWRAP_WORD, - AUTOWRAP_WORD_SMART - }; - - enum OverrunBehavior { - OVERRUN_NO_TRIMMING, - OVERRUN_TRIM_CHAR, - OVERRUN_TRIM_WORD, - OVERRUN_TRIM_ELLIPSIS, - OVERRUN_TRIM_WORD_ELLIPSIS, - }; - - enum VisibleCharactersBehavior { - VC_CHARS_BEFORE_SHAPING, - VC_CHARS_AFTER_SHAPING, - VC_GLYPHS_AUTO, - VC_GLYPHS_LTR, - VC_GLYPHS_RTL, - }; - private: HorizontalAlignment horizontal_alignment = HORIZONTAL_ALIGNMENT_LEFT; VerticalAlignment vertical_alignment = VERTICAL_ALIGNMENT_TOP; String text; String xl_text; - AutowrapMode autowrap_mode = AUTOWRAP_OFF; + TextServer::AutowrapMode autowrap_mode = TextServer::AUTOWRAP_OFF; bool clip = false; - OverrunBehavior overrun_behavior = OVERRUN_NO_TRIMMING; + TextServer::OverrunBehavior overrun_behavior = TextServer::OVERRUN_NO_TRIMMING; Size2 minsize; bool uppercase = false; @@ -77,31 +54,29 @@ private: RID text_rid; Vector<RID> lines_rid; - Dictionary opentype_features; String language; TextDirection text_direction = TEXT_DIRECTION_AUTO; - Control::StructuredTextParser st_parser = STRUCTURED_TEXT_DEFAULT; + TextServer::StructuredTextParser st_parser = TextServer::STRUCTURED_TEXT_DEFAULT; Array st_args; float percent_visible = 1.0; - VisibleCharactersBehavior visible_chars_behavior = VC_CHARS_BEFORE_SHAPING; + TextServer::VisibleCharactersBehavior visible_chars_behavior = TextServer::VC_CHARS_BEFORE_SHAPING; int visible_chars = -1; int lines_skipped = 0; int max_lines_visible = -1; + Ref<LabelSettings> settings; + void _update_visible(); void _shape(); + void _invalidate(); protected: void _notification(int p_what); static void _bind_methods(); - bool _set(const StringName &p_name, const Variant &p_value); - bool _get(const StringName &p_name, Variant &r_ret) const; - void _get_property_list(List<PropertyInfo> *p_list) const; - public: virtual Size2 get_minimum_size() const override; @@ -114,30 +89,29 @@ public: void set_text(const String &p_string); String get_text() const; + void set_label_settings(const Ref<LabelSettings> &p_settings); + Ref<LabelSettings> get_label_settings() const; + void set_text_direction(TextDirection p_text_direction); TextDirection get_text_direction() const; - void set_opentype_feature(const String &p_name, int p_value); - int get_opentype_feature(const String &p_name) const; - void clear_opentype_features(); - void set_language(const String &p_language); String get_language() const; - void set_structured_text_bidi_override(Control::StructuredTextParser p_parser); - Control::StructuredTextParser get_structured_text_bidi_override() const; + void set_structured_text_bidi_override(TextServer::StructuredTextParser p_parser); + TextServer::StructuredTextParser get_structured_text_bidi_override() const; void set_structured_text_bidi_override_options(Array p_args); Array get_structured_text_bidi_override_options() const; - void set_autowrap_mode(AutowrapMode p_mode); - AutowrapMode get_autowrap_mode() const; + void set_autowrap_mode(TextServer::AutowrapMode p_mode); + TextServer::AutowrapMode get_autowrap_mode() const; void set_uppercase(bool p_uppercase); bool is_uppercase() const; - VisibleCharactersBehavior get_visible_characters_behavior() const; - void set_visible_characters_behavior(VisibleCharactersBehavior p_behavior); + TextServer::VisibleCharactersBehavior get_visible_characters_behavior() const; + void set_visible_characters_behavior(TextServer::VisibleCharactersBehavior p_behavior); void set_visible_characters(int p_amount); int get_visible_characters() const; @@ -146,8 +120,8 @@ public: void set_clip_text(bool p_clip); bool is_clipping_text() const; - void set_text_overrun_behavior(OverrunBehavior p_behavior); - OverrunBehavior get_text_overrun_behavior() const; + void set_text_overrun_behavior(TextServer::OverrunBehavior p_behavior); + TextServer::OverrunBehavior get_text_overrun_behavior() const; void set_percent_visible(float p_percent); float get_percent_visible() const; @@ -166,8 +140,4 @@ public: ~Label(); }; -VARIANT_ENUM_CAST(Label::AutowrapMode); -VARIANT_ENUM_CAST(Label::OverrunBehavior); -VARIANT_ENUM_CAST(Label::VisibleCharactersBehavior); - #endif diff --git a/scene/gui/line_edit.cpp b/scene/gui/line_edit.cpp index b3f051bb75..39f8f23cd8 100644 --- a/scene/gui/line_edit.cpp +++ b/scene/gui/line_edit.cpp @@ -312,7 +312,7 @@ void LineEdit::gui_input(const Ref<InputEvent> &p_event) { selection.end = text.length(); selection.double_click = true; last_dblclk = 0; - caret_column = selection.begin; + set_caret_column(selection.begin); if (!pass && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_CLIPBOARD_PRIMARY)) { DisplayServer::get_singleton()->clipboard_set_primary(text); } @@ -327,7 +327,7 @@ void LineEdit::gui_input(const Ref<InputEvent> &p_event) { selection.begin = words[i]; selection.end = words[i + 1]; selection.double_click = true; - caret_column = selection.end; + set_caret_column(selection.end); break; } } @@ -406,9 +406,45 @@ void LineEdit::gui_input(const Ref<InputEvent> &p_event) { if (k.is_valid()) { if (!k->is_pressed()) { + if (alt_start && k->get_keycode() == Key::ALT) { + alt_start = false; + if ((alt_code > 0x31 && alt_code < 0xd800) || (alt_code > 0xdfff && alt_code <= 0x10ffff)) { + char32_t ucodestr[2] = { (char32_t)alt_code, 0 }; + insert_text_at_caret(ucodestr); + } + accept_event(); + return; + } return; } + // Alt+ Unicode input: + if (k->is_alt_pressed()) { + if (!alt_start) { + if (k->get_keycode() == Key::KP_ADD) { + alt_start = true; + alt_code = 0; + accept_event(); + return; + } + } else { + if (k->get_keycode() >= Key::KEY_0 && k->get_keycode() <= Key::KEY_9) { + alt_code = alt_code << 4; + alt_code += (uint32_t)(k->get_keycode() - Key::KEY_0); + } + if (k->get_keycode() >= Key::KP_0 && k->get_keycode() <= Key::KP_9) { + alt_code = alt_code << 4; + alt_code += (uint32_t)(k->get_keycode() - Key::KP_0); + } + if (k->get_keycode() >= Key::A && k->get_keycode() <= Key::F) { + alt_code = alt_code << 4; + alt_code += (uint32_t)(k->get_keycode() - Key::A) + 10; + } + accept_event(); + return; + } + } + if (context_menu_enabled) { if (k->is_action("ui_menu", true)) { _ensure_menu(); @@ -659,7 +695,7 @@ bool LineEdit::_is_over_clear_button(const Point2 &p_pos) const { return false; } Ref<Texture2D> icon = Control::get_theme_icon(SNAME("clear")); - int x_ofs = get_theme_stylebox(SNAME("normal"))->get_offset().x; + int x_ofs = get_theme_stylebox(SNAME("normal"))->get_margin(SIDE_RIGHT); return p_pos.x > get_size().width - icon->get_width() - x_ofs; } @@ -708,6 +744,17 @@ void LineEdit::_notification(int p_what) { update(); } break; + case NOTIFICATION_INTERNAL_PROCESS: { + if (caret_blinking) { + caret_blink_timer += get_process_delta_time(); + + if (caret_blink_timer >= caret_blink_speed) { + caret_blink_timer = 0.0; + _toggle_draw_caret(); + } + } + } break; + case NOTIFICATION_DRAW: { if ((!has_focus() && !(menu && menu->has_focus()) && !caret_force_displayed) || !window_has_focus) { draw_caret = false; @@ -740,7 +787,7 @@ void LineEdit::_notification(int p_what) { int x_ofs = 0; bool using_placeholder = text.is_empty() && ime_text.is_empty(); float text_width = TS->shaped_text_get_size(text_rid).x; - float text_height = TS->shaped_text_get_size(text_rid).y + font->get_spacing(TextServer::SPACING_TOP) + font->get_spacing(TextServer::SPACING_BOTTOM); + float text_height = TS->shaped_text_get_size(text_rid).y; switch (alignment) { case HORIZONTAL_ALIGNMENT_FILL: @@ -955,8 +1002,9 @@ void LineEdit::_notification(int p_what) { case NOTIFICATION_FOCUS_ENTER: { if (!caret_force_displayed) { if (caret_blink_enabled) { - if (caret_blink_timer->is_stopped()) { - caret_blink_timer->start(); + if (!caret_blinking) { + caret_blinking = true; + caret_blink_timer = 0.0; } } else { draw_caret = true; @@ -974,7 +1022,7 @@ void LineEdit::_notification(int p_what) { case NOTIFICATION_FOCUS_EXIT: { if (caret_blink_enabled && !caret_force_displayed) { - caret_blink_timer->stop(); + caret_blinking = false; } if (get_viewport()->get_window_id() != DisplayServer::INVALID_WINDOW_ID && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_IME)) { @@ -1282,14 +1330,16 @@ bool LineEdit::is_caret_blink_enabled() const { void LineEdit::set_caret_blink_enabled(const bool p_enabled) { caret_blink_enabled = p_enabled; + set_process_internal(p_enabled); if (has_focus() || caret_force_displayed) { if (p_enabled) { - if (caret_blink_timer->is_stopped()) { - caret_blink_timer->start(); + if (!caret_blinking) { + caret_blinking = true; + caret_blink_timer = 0.0; } } else { - caret_blink_timer->stop(); + caret_blinking = false; } } @@ -1309,20 +1359,19 @@ void LineEdit::set_caret_force_displayed(const bool p_enabled) { } float LineEdit::get_caret_blink_speed() const { - return caret_blink_timer->get_wait_time(); + return caret_blink_speed; } void LineEdit::set_caret_blink_speed(const float p_speed) { ERR_FAIL_COND(p_speed <= 0); - caret_blink_timer->set_wait_time(p_speed); + caret_blink_speed = p_speed; } void LineEdit::_reset_caret_blink_timer() { if (caret_blink_enabled) { draw_caret = true; if (has_focus()) { - caret_blink_timer->stop(); - caret_blink_timer->start(); + caret_blink_timer = 0.0; update(); } } @@ -1402,29 +1451,6 @@ Control::TextDirection LineEdit::get_text_direction() const { return text_direction; } -void LineEdit::clear_opentype_features() { - opentype_features.clear(); - _shape(); - update(); -} - -void LineEdit::set_opentype_feature(const String &p_name, int p_value) { - int32_t tag = TS->name_to_tag(p_name); - if (!opentype_features.has(tag) || (int)opentype_features[tag] != p_value) { - opentype_features[tag] = p_value; - _shape(); - update(); - } -} - -int LineEdit::get_opentype_feature(const String &p_name) const { - int32_t tag = TS->name_to_tag(p_name); - if (!opentype_features.has(tag)) { - return -1; - } - return opentype_features[tag]; -} - void LineEdit::set_language(const String &p_language) { if (language != p_language) { language = p_language; @@ -1452,7 +1478,7 @@ bool LineEdit::get_draw_control_chars() const { return draw_control_chars; } -void LineEdit::set_structured_text_bidi_override(Control::StructuredTextParser p_parser) { +void LineEdit::set_structured_text_bidi_override(TextServer::StructuredTextParser p_parser) { if (st_parser != p_parser) { st_parser = p_parser; _shape(); @@ -1460,7 +1486,7 @@ void LineEdit::set_structured_text_bidi_override(Control::StructuredTextParser p } } -Control::StructuredTextParser LineEdit::get_structured_text_bidi_override() const { +TextServer::StructuredTextParser LineEdit::get_structured_text_bidi_override() const { return st_parser; } @@ -1639,7 +1665,7 @@ Size2 LineEdit::get_minimum_size() const { Size2 min_size; // Minimum size of text. - float em_space_size = font->get_char_size('M', 0, font_size).x; + float em_space_size = font->get_char_size('M', font_size).x; min_size.width = get_theme_constant(SNAME("minimum_character_width")) * em_space_size; if (expand_to_text_length) { @@ -1647,16 +1673,20 @@ Size2 LineEdit::get_minimum_size() const { min_size.width = MAX(min_size.width, full_width + em_space_size); } - min_size.height = MAX(TS->shaped_text_get_size(text_rid).y + font->get_spacing(TextServer::SPACING_TOP) + font->get_spacing(TextServer::SPACING_BOTTOM), font->get_height(font_size)); + min_size.height = MAX(TS->shaped_text_get_size(text_rid).y, font->get_height(font_size)); // Take icons into account. - bool using_placeholder = text.is_empty() && ime_text.is_empty(); - bool display_clear_icon = !using_placeholder && is_editable() && clear_button_enabled; - if (right_icon.is_valid() || display_clear_icon) { - Ref<Texture2D> r_icon = display_clear_icon ? Control::get_theme_icon(SNAME("clear")) : right_icon; - min_size.width += r_icon->get_width(); - min_size.height = MAX(min_size.height, r_icon->get_height()); + int icon_max_width = 0; + if (right_icon.is_valid()) { + min_size.height = MAX(min_size.height, right_icon->get_height()); + icon_max_width = right_icon->get_width(); + } + if (clear_button_enabled) { + Ref<Texture2D> clear_icon = Control::get_theme_icon(SNAME("clear")); + min_size.height = MAX(min_size.height, clear_icon->get_height()); + icon_max_width = MAX(icon_max_width, clear_icon->get_width()); } + min_size.width += icon_max_width; return style->get_minimum_size() + min_size; } @@ -2102,7 +2132,10 @@ void LineEdit::_shape() { const Ref<Font> &font = get_theme_font(SNAME("font")); int font_size = get_theme_font_size(SNAME("font_size")); ERR_FAIL_COND(font.is_null()); - TS->shaped_text_add_string(text_rid, t, font->get_rids(), font_size, opentype_features, (!language.is_empty()) ? language : TranslationServer::get_singleton()->get_tool_locale()); + TS->shaped_text_add_string(text_rid, t, font->get_rids(), font_size, font->get_opentype_features(), language); + for (int i = 0; i < TextServer::SPACING_MAX; i++) { + TS->shaped_text_set_spacing(text_rid, TextServer::SpacingType(i), font->get_spacing(TextServer::SpacingType(i))); + } TS->shaped_text_set_bidi_override(text_rid, structured_text_parser(st_parser, st_args, t)); full_width = TS->shaped_text_get_size(text_rid).x; @@ -2183,56 +2216,6 @@ Key LineEdit::_get_menu_action_accelerator(const String &p_action) { } } -bool LineEdit::_set(const StringName &p_name, const Variant &p_value) { - String str = p_name; - if (str.begins_with("opentype_features/")) { - String name = str.get_slicec('/', 1); - int32_t tag = TS->name_to_tag(name); - int value = p_value; - if (value == -1) { - if (opentype_features.has(tag)) { - opentype_features.erase(tag); - _shape(); - update(); - } - } else { - if (!opentype_features.has(tag) || (int)opentype_features[tag] != value) { - opentype_features[tag] = value; - _shape(); - update(); - } - } - notify_property_list_changed(); - return true; - } - - return false; -} - -bool LineEdit::_get(const StringName &p_name, Variant &r_ret) const { - String str = p_name; - if (str.begins_with("opentype_features/")) { - String name = str.get_slicec('/', 1); - int32_t tag = TS->name_to_tag(name); - if (opentype_features.has(tag)) { - r_ret = opentype_features[tag]; - return true; - } else { - r_ret = -1; - return true; - } - } - return false; -} - -void LineEdit::_get_property_list(List<PropertyInfo> *p_list) const { - for (const Variant *ftr = opentype_features.next(nullptr); ftr != nullptr; ftr = opentype_features.next(ftr)) { - String name = TS->tag_to_name(*ftr); - p_list->push_back(PropertyInfo(Variant::INT, "opentype_features/" + name)); - } - p_list->push_back(PropertyInfo(Variant::NIL, "opentype_features/_new", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR)); -} - void LineEdit::_validate_property(PropertyInfo &property) const { if (!caret_blink_enabled && property.name == "caret_blink_speed") { property.usage = PROPERTY_USAGE_NO_EDITOR; @@ -2258,9 +2241,6 @@ void LineEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("set_draw_control_chars", "enable"), &LineEdit::set_draw_control_chars); ClassDB::bind_method(D_METHOD("set_text_direction", "direction"), &LineEdit::set_text_direction); ClassDB::bind_method(D_METHOD("get_text_direction"), &LineEdit::get_text_direction); - ClassDB::bind_method(D_METHOD("set_opentype_feature", "tag", "value"), &LineEdit::set_opentype_feature); - ClassDB::bind_method(D_METHOD("get_opentype_feature", "tag"), &LineEdit::get_opentype_feature); - ClassDB::bind_method(D_METHOD("clear_opentype_features"), &LineEdit::clear_opentype_features); ClassDB::bind_method(D_METHOD("set_language", "language"), &LineEdit::set_language); ClassDB::bind_method(D_METHOD("get_language"), &LineEdit::get_language); ClassDB::bind_method(D_METHOD("set_structured_text_bidi_override", "parser"), &LineEdit::set_structured_text_bidi_override); @@ -2366,18 +2346,20 @@ void LineEdit::_bind_methods() { 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", PROPERTY_HINT_LOCALE_ID, ""), "set_language", "get_language"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "draw_control_chars"), "set_draw_control_chars", "get_draw_control_chars"); - ADD_GROUP("Structured Text", "structured_text_"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "structured_text_bidi_override", PROPERTY_HINT_ENUM, "Default,URI,File,Email,List,None,Custom"), "set_structured_text_bidi_override", "get_structured_text_bidi_override"); - ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "structured_text_bidi_override_options"), "set_structured_text_bidi_override_options", "get_structured_text_bidi_override_options"); + ADD_GROUP("Caret", "caret_"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "caret_blink"), "set_caret_blink_enabled", "is_caret_blink_enabled"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "caret_blink_speed", PROPERTY_HINT_RANGE, "0.1,10,0.01"), "set_caret_blink_speed", "get_caret_blink_speed"); ADD_PROPERTY(PropertyInfo(Variant::INT, "caret_column", PROPERTY_HINT_RANGE, "0,1000,1,or_greater"), "set_caret_column", "get_caret_column"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "caret_force_displayed"), "set_caret_force_displayed", "is_caret_force_displayed"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "caret_mid_grapheme"), "set_caret_mid_grapheme_enabled", "is_caret_mid_grapheme_enabled"); + + ADD_GROUP("BiDi", ""); + 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", PROPERTY_HINT_LOCALE_ID, ""), "set_language", "get_language"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "structured_text_bidi_override", PROPERTY_HINT_ENUM, "Default,URI,File,Email,List,None,Custom"), "set_structured_text_bidi_override", "get_structured_text_bidi_override"); + ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "structured_text_bidi_override_options"), "set_structured_text_bidi_override_options", "get_structured_text_bidi_override_options"); } void LineEdit::_ensure_menu() { @@ -2468,10 +2450,6 @@ LineEdit::LineEdit(const String &p_placeholder) { set_mouse_filter(MOUSE_FILTER_STOP); set_process_unhandled_key_input(true); - caret_blink_timer = memnew(Timer); - add_child(caret_blink_timer, false, INTERNAL_MODE_FRONT); - caret_blink_timer->set_wait_time(0.65); - caret_blink_timer->connect("timeout", callable_mp(this, &LineEdit::_toggle_draw_caret)); set_caret_blink_enabled(false); set_placeholder(p_placeholder); diff --git a/scene/gui/line_edit.h b/scene/gui/line_edit.h index b86ccd6421..557da35bfd 100644 --- a/scene/gui/line_edit.h +++ b/scene/gui/line_edit.h @@ -77,6 +77,9 @@ private: bool pass = false; bool text_changed_dirty = false; + bool alt_start = false; + uint32_t alt_code = 0; + String undo_text; String text; String placeholder; @@ -102,11 +105,10 @@ private: int scroll_offset = 0; int max_length = 0; // 0 for no maximum. - Dictionary opentype_features; String language; TextDirection text_direction = TEXT_DIRECTION_AUTO; TextDirection input_direction = TEXT_DIRECTION_LTR; - Control::StructuredTextParser st_parser = STRUCTURED_TEXT_DEFAULT; + TextServer::StructuredTextParser st_parser = TextServer::STRUCTURED_TEXT_DEFAULT; Array st_args; bool draw_control_chars = false; @@ -157,7 +159,9 @@ private: bool caret_blink_enabled = false; bool caret_force_displayed = false; bool draw_caret = true; - Timer *caret_blink_timer = nullptr; + float caret_blink_speed = 0.65; + double caret_blink_timer = 0.0; + bool caret_blinking = false; bool _is_over_clear_button(const Point2 &p_pos) const; @@ -205,9 +209,6 @@ protected: virtual void unhandled_key_input(const Ref<InputEvent> &p_event) override; virtual void gui_input(const Ref<InputEvent> &p_event) override; - bool _set(const StringName &p_name, const Variant &p_value); - bool _get(const StringName &p_name, Variant &r_ret) const; - void _get_property_list(List<PropertyInfo> *p_list) const; void _validate_property(PropertyInfo &property) const override; public: @@ -243,18 +244,14 @@ public: void set_text_direction(TextDirection p_text_direction); TextDirection get_text_direction() const; - void set_opentype_feature(const String &p_name, int p_value); - int get_opentype_feature(const String &p_name) const; - void clear_opentype_features(); - void set_language(const String &p_language); String get_language() const; void set_draw_control_chars(bool p_draw_control_chars); bool get_draw_control_chars() const; - void set_structured_text_bidi_override(Control::StructuredTextParser p_parser); - Control::StructuredTextParser get_structured_text_bidi_override() const; + void set_structured_text_bidi_override(TextServer::StructuredTextParser p_parser); + TextServer::StructuredTextParser get_structured_text_bidi_override() const; void set_structured_text_bidi_override_options(Array p_args); Array get_structured_text_bidi_override_options() const; diff --git a/scene/gui/link_button.cpp b/scene/gui/link_button.cpp index dc4f09d22d..30c0bb3321 100644 --- a/scene/gui/link_button.cpp +++ b/scene/gui/link_button.cpp @@ -43,7 +43,7 @@ void LinkButton::_shape() { text_buf->set_direction((TextServer::Direction)text_direction); } TS->shaped_text_set_bidi_override(text_buf->get_rid(), structured_text_parser(st_parser, st_args, xl_text)); - text_buf->add_string(xl_text, font, font_size, opentype_features, (!language.is_empty()) ? language : TranslationServer::get_singleton()->get_tool_locale()); + text_buf->add_string(xl_text, font, font_size, language); } void LinkButton::set_text(const String &p_text) { @@ -61,7 +61,7 @@ String LinkButton::get_text() const { return text; } -void LinkButton::set_structured_text_bidi_override(Control::StructuredTextParser p_parser) { +void LinkButton::set_structured_text_bidi_override(TextServer::StructuredTextParser p_parser) { if (st_parser != p_parser) { st_parser = p_parser; _shape(); @@ -69,7 +69,7 @@ void LinkButton::set_structured_text_bidi_override(Control::StructuredTextParser } } -Control::StructuredTextParser LinkButton::get_structured_text_bidi_override() const { +TextServer::StructuredTextParser LinkButton::get_structured_text_bidi_override() const { return st_parser; } @@ -96,29 +96,6 @@ Control::TextDirection LinkButton::get_text_direction() const { return text_direction; } -void LinkButton::clear_opentype_features() { - opentype_features.clear(); - _shape(); - update(); -} - -void LinkButton::set_opentype_feature(const String &p_name, int p_value) { - int32_t tag = TS->name_to_tag(p_name); - if (!opentype_features.has(tag) || (int)opentype_features[tag] != p_value) { - opentype_features[tag] = p_value; - _shape(); - update(); - } -} - -int LinkButton::get_opentype_feature(const String &p_name) const { - int32_t tag = TS->name_to_tag(p_name); - if (!opentype_features.has(tag)) { - return -1; - } - return opentype_features[tag]; -} - void LinkButton::set_language(const String &p_language) { if (language != p_language) { language = p_language; @@ -237,64 +214,11 @@ void LinkButton::_notification(int p_what) { } } -bool LinkButton::_set(const StringName &p_name, const Variant &p_value) { - String str = p_name; - if (str.begins_with("opentype_features/")) { - String name = str.get_slicec('/', 1); - int32_t tag = TS->name_to_tag(name); - int value = p_value; - if (value == -1) { - if (opentype_features.has(tag)) { - opentype_features.erase(tag); - _shape(); - update(); - } - } else { - if (!opentype_features.has(tag) || (int)opentype_features[tag] != value) { - opentype_features[tag] = value; - _shape(); - update(); - } - } - notify_property_list_changed(); - return true; - } - - return false; -} - -bool LinkButton::_get(const StringName &p_name, Variant &r_ret) const { - String str = p_name; - if (str.begins_with("opentype_features/")) { - String name = str.get_slicec('/', 1); - int32_t tag = TS->name_to_tag(name); - if (opentype_features.has(tag)) { - r_ret = opentype_features[tag]; - return true; - } else { - r_ret = -1; - return true; - } - } - return false; -} - -void LinkButton::_get_property_list(List<PropertyInfo> *p_list) const { - for (const Variant *ftr = opentype_features.next(nullptr); ftr != nullptr; ftr = opentype_features.next(ftr)) { - String name = TS->tag_to_name(*ftr); - p_list->push_back(PropertyInfo(Variant::INT, "opentype_features/" + name)); - } - p_list->push_back(PropertyInfo(Variant::NIL, "opentype_features/_new", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR)); -} - void LinkButton::_bind_methods() { ClassDB::bind_method(D_METHOD("set_text", "text"), &LinkButton::set_text); ClassDB::bind_method(D_METHOD("get_text"), &LinkButton::get_text); ClassDB::bind_method(D_METHOD("set_text_direction", "direction"), &LinkButton::set_text_direction); ClassDB::bind_method(D_METHOD("get_text_direction"), &LinkButton::get_text_direction); - ClassDB::bind_method(D_METHOD("set_opentype_feature", "tag", "value"), &LinkButton::set_opentype_feature); - ClassDB::bind_method(D_METHOD("get_opentype_feature", "tag"), &LinkButton::get_opentype_feature); - ClassDB::bind_method(D_METHOD("clear_opentype_features"), &LinkButton::clear_opentype_features); ClassDB::bind_method(D_METHOD("set_language", "language"), &LinkButton::set_language); ClassDB::bind_method(D_METHOD("get_language"), &LinkButton::get_language); ClassDB::bind_method(D_METHOD("set_underline_mode", "underline_mode"), &LinkButton::set_underline_mode); @@ -309,10 +233,11 @@ void LinkButton::_bind_methods() { BIND_ENUM_CONSTANT(UNDERLINE_MODE_NEVER); ADD_PROPERTY(PropertyInfo(Variant::STRING, "text"), "set_text", "get_text"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "underline", PROPERTY_HINT_ENUM, "Always,On Hover,Never"), "set_underline_mode", "get_underline_mode"); + + ADD_GROUP("BiDi", ""); 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", PROPERTY_HINT_LOCALE_ID, ""), "set_language", "get_language"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "underline", PROPERTY_HINT_ENUM, "Always,On Hover,Never"), "set_underline_mode", "get_underline_mode"); - ADD_GROUP("Structured Text", "structured_text_"); ADD_PROPERTY(PropertyInfo(Variant::INT, "structured_text_bidi_override", PROPERTY_HINT_ENUM, "Default,URI,File,Email,List,None,Custom"), "set_structured_text_bidi_override", "get_structured_text_bidi_override"); ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "structured_text_bidi_override_options"), "set_structured_text_bidi_override_options", "get_structured_text_bidi_override_options"); } diff --git a/scene/gui/link_button.h b/scene/gui/link_button.h index f996558f32..54a31f06ce 100644 --- a/scene/gui/link_button.h +++ b/scene/gui/link_button.h @@ -50,10 +50,9 @@ private: Ref<TextLine> text_buf; UnderlineMode underline_mode = UNDERLINE_MODE_ALWAYS; - Dictionary opentype_features; String language; TextDirection text_direction = TEXT_DIRECTION_AUTO; - Control::StructuredTextParser st_parser = STRUCTURED_TEXT_DEFAULT; + TextServer::StructuredTextParser st_parser = TextServer::STRUCTURED_TEXT_DEFAULT; Array st_args; void _shape(); @@ -63,16 +62,12 @@ protected: void _notification(int p_what); static void _bind_methods(); - bool _set(const StringName &p_name, const Variant &p_value); - bool _get(const StringName &p_name, Variant &r_ret) const; - void _get_property_list(List<PropertyInfo> *p_list) const; - public: void set_text(const String &p_text); String get_text() const; - void set_structured_text_bidi_override(Control::StructuredTextParser p_parser); - Control::StructuredTextParser get_structured_text_bidi_override() const; + void set_structured_text_bidi_override(TextServer::StructuredTextParser p_parser); + TextServer::StructuredTextParser get_structured_text_bidi_override() const; void set_structured_text_bidi_override_options(Array p_args); Array get_structured_text_bidi_override_options() const; @@ -80,10 +75,6 @@ public: void set_text_direction(TextDirection p_text_direction); TextDirection get_text_direction() const; - void set_opentype_feature(const String &p_name, int p_value); - int get_opentype_feature(const String &p_name) const; - void clear_opentype_features(); - void set_language(const String &p_language); String get_language() const; diff --git a/scene/gui/menu_button.cpp b/scene/gui/menu_button.cpp index 1feee017c2..316fee53fe 100644 --- a/scene/gui/menu_button.cpp +++ b/scene/gui/menu_button.cpp @@ -187,7 +187,7 @@ void MenuButton::_get_property_list(List<PropertyInfo> *p_list) const { pi.usage &= ~(popup->get_item_icon(i).is_null() ? PROPERTY_USAGE_STORAGE : 0); p_list->push_back(pi); - pi = PropertyInfo(Variant::INT, vformat("popup/item_%d/checkable", i), PROPERTY_HINT_ENUM, "No,As checkbox,As radio button"); + pi = PropertyInfo(Variant::INT, vformat("popup/item_%d/checkable", i), PROPERTY_HINT_ENUM, "No,As Checkbox,As Radio Button"); pi.usage &= ~(!popup->is_item_checkable(i) ? PROPERTY_USAGE_STORAGE : 0); p_list->push_back(pi); diff --git a/scene/gui/nine_patch_rect.cpp b/scene/gui/nine_patch_rect.cpp index 4f34ece86f..8fee10b19a 100644 --- a/scene/gui/nine_patch_rect.cpp +++ b/scene/gui/nine_patch_rect.cpp @@ -73,13 +73,13 @@ void NinePatchRect::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_texture", "get_texture"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "draw_center"), "set_draw_center", "is_draw_center_enabled"); - ADD_PROPERTY(PropertyInfo(Variant::RECT2, "region_rect"), "set_region_rect", "get_region_rect"); + ADD_PROPERTY(PropertyInfo(Variant::RECT2, "region_rect", PROPERTY_HINT_NONE, "suffix:px"), "set_region_rect", "get_region_rect"); ADD_GROUP("Patch Margin", "patch_margin_"); - ADD_PROPERTYI(PropertyInfo(Variant::INT, "patch_margin_left", PROPERTY_HINT_RANGE, "0,16384,1"), "set_patch_margin", "get_patch_margin", SIDE_LEFT); - ADD_PROPERTYI(PropertyInfo(Variant::INT, "patch_margin_top", PROPERTY_HINT_RANGE, "0,16384,1"), "set_patch_margin", "get_patch_margin", SIDE_TOP); - ADD_PROPERTYI(PropertyInfo(Variant::INT, "patch_margin_right", PROPERTY_HINT_RANGE, "0,16384,1"), "set_patch_margin", "get_patch_margin", SIDE_RIGHT); - ADD_PROPERTYI(PropertyInfo(Variant::INT, "patch_margin_bottom", PROPERTY_HINT_RANGE, "0,16384,1"), "set_patch_margin", "get_patch_margin", SIDE_BOTTOM); + ADD_PROPERTYI(PropertyInfo(Variant::INT, "patch_margin_left", PROPERTY_HINT_RANGE, "0,16384,1,suffix:px"), "set_patch_margin", "get_patch_margin", SIDE_LEFT); + ADD_PROPERTYI(PropertyInfo(Variant::INT, "patch_margin_top", PROPERTY_HINT_RANGE, "0,16384,1,suffix:px"), "set_patch_margin", "get_patch_margin", SIDE_TOP); + ADD_PROPERTYI(PropertyInfo(Variant::INT, "patch_margin_right", PROPERTY_HINT_RANGE, "0,16384,1,suffix:px"), "set_patch_margin", "get_patch_margin", SIDE_RIGHT); + ADD_PROPERTYI(PropertyInfo(Variant::INT, "patch_margin_bottom", PROPERTY_HINT_RANGE, "0,16384,1,suffix:px"), "set_patch_margin", "get_patch_margin", SIDE_BOTTOM); ADD_GROUP("Axis Stretch", "axis_stretch_"); ADD_PROPERTY(PropertyInfo(Variant::INT, "axis_stretch_horizontal", PROPERTY_HINT_ENUM, "Stretch,Tile,Tile Fit"), "set_h_axis_stretch_mode", "get_h_axis_stretch_mode"); ADD_PROPERTY(PropertyInfo(Variant::INT, "axis_stretch_vertical", PROPERTY_HINT_ENUM, "Stretch,Tile,Tile Fit"), "set_v_axis_stretch_mode", "get_v_axis_stretch_mode"); diff --git a/scene/gui/option_button.cpp b/scene/gui/option_button.cpp index 307696c44a..a86f2bdbc1 100644 --- a/scene/gui/option_button.cpp +++ b/scene/gui/option_button.cpp @@ -42,7 +42,7 @@ Size2 OptionButton::get_minimum_size() const { const Size2 arrow_size = Control::get_theme_icon(SNAME("arrow"))->get_size(); Size2 content_size = minsize - padding; - content_size.width += arrow_size.width + get_theme_constant(SNAME("hseparation")); + content_size.width += arrow_size.width + get_theme_constant(SNAME("h_separation")); content_size.height = MAX(content_size.height, arrow_size.height); minsize = content_size + padding; @@ -320,7 +320,7 @@ int OptionButton::get_selectable_item(bool p_from_last) const { } } } else { - for (int i = get_item_count() - 1; i >= 0; i++) { + for (int i = get_item_count() - 1; i >= 0; i--) { if (!is_item_disabled(i) && !is_item_separator(i)) { return i; } diff --git a/scene/gui/popup.cpp b/scene/gui/popup.cpp index b9e3e7814e..c4396f636a 100644 --- a/scene/gui/popup.cpp +++ b/scene/gui/popup.cpp @@ -111,6 +111,11 @@ void Popup::_close_pressed() { call_deferred(SNAME("hide")); } +void Popup::_post_popup() { + Window::_post_popup(); + popped_up = true; +} + void Popup::_bind_methods() { ADD_SIGNAL(MethodInfo("popup_hide")); } diff --git a/scene/gui/popup.h b/scene/gui/popup.h index 6211af4d20..b53c8be50f 100644 --- a/scene/gui/popup.h +++ b/scene/gui/popup.h @@ -48,8 +48,6 @@ class Popup : public Window { void _initialize_visible_parents(); void _deinitialize_visible_parents(); - void _parent_focused(); - protected: void _close_pressed(); virtual Rect2i _popup_adjust_rect() const override; @@ -57,6 +55,10 @@ protected: void _notification(int p_what); static void _bind_methods(); + virtual void _parent_focused(); + + virtual void _post_popup() override; + public: Popup(); ~Popup(); diff --git a/scene/gui/popup_menu.cpp b/scene/gui/popup_menu.cpp index ab496d058c..928bab8842 100644 --- a/scene/gui/popup_menu.cpp +++ b/scene/gui/popup_menu.cpp @@ -47,8 +47,8 @@ String PopupMenu::_get_accel_text(const Item &p_item) const { } Size2 PopupMenu::_get_contents_minimum_size() const { - int vseparation = get_theme_constant(SNAME("vseparation")); - int hseparation = get_theme_constant(SNAME("hseparation")); + int vseparation = get_theme_constant(SNAME("v_separation")); + int hseparation = get_theme_constant(SNAME("h_separation")); Size2 minsize = get_theme_stylebox(SNAME("panel"))->get_minimum_size(); // Accounts for margin in the margin container minsize.x += scroll_container->get_v_scroll_bar()->get_size().width * 2; // Adds a buffer so that the scrollbar does not render over the top of content @@ -129,7 +129,7 @@ int PopupMenu::_get_item_height(int p_item) const { } int PopupMenu::_get_items_total_height() const { - int vsep = get_theme_constant(SNAME("vseparation")); + int vsep = get_theme_constant(SNAME("v_separation")); // Get total height of all items by taking max of icon height and font height int items_total_height = 0; @@ -148,7 +148,7 @@ int PopupMenu::_get_mouse_over(const Point2 &p_over) const { Ref<StyleBox> style = get_theme_stylebox(SNAME("panel")); // Accounts for margin in the margin container - int vseparation = get_theme_constant(SNAME("vseparation")); + int vseparation = get_theme_constant(SNAME("v_separation")); Point2 ofs = style->get_offset() + Point2(0, vseparation / 2); @@ -179,7 +179,7 @@ void PopupMenu::_activate_submenu(int p_over, bool p_by_keyboard) { } Ref<StyleBox> style = get_theme_stylebox(SNAME("panel")); - int vsep = get_theme_constant(SNAME("vseparation")); + int vsep = get_theme_constant(SNAME("v_separation")); Point2 this_pos = get_position(); Rect2 this_rect(this_pos, get_size()); @@ -243,6 +243,29 @@ void PopupMenu::_activate_submenu(int p_over, bool p_by_keyboard) { } } +void PopupMenu::_parent_focused() { + if (is_embedded()) { + Point2 mouse_pos_adjusted; + Window *window_parent = Object::cast_to<Window>(get_parent()->get_viewport()); + while (window_parent) { + if (!window_parent->is_embedded()) { + mouse_pos_adjusted += window_parent->get_position(); + break; + } + + window_parent = Object::cast_to<Window>(window_parent->get_parent()->get_viewport()); + } + + Rect2 safe_area = DisplayServer::get_singleton()->window_get_popup_safe_rect(get_window_id()); + Point2 pos = DisplayServer::get_singleton()->mouse_get_position() - mouse_pos_adjusted; + if (safe_area == Rect2i() || !safe_area.has_point(pos)) { + Popup::_parent_focused(); + } else { + grab_focus(); + } + } +} + void PopupMenu::_submenu_timeout() { if (mouse_over == submenu_over) { _activate_submenu(mouse_over); @@ -504,8 +527,8 @@ void PopupMenu::_draw_items() { Ref<StyleBox> labeled_separator_left = get_theme_stylebox(SNAME("labeled_separator_left")); Ref<StyleBox> labeled_separator_right = get_theme_stylebox(SNAME("labeled_separator_right")); - int vseparation = get_theme_constant(SNAME("vseparation")); - int hseparation = get_theme_constant(SNAME("hseparation")); + int vseparation = get_theme_constant(SNAME("v_separation")); + int hseparation = get_theme_constant(SNAME("h_separation")); Color font_color = get_theme_color(SNAME("font_color")); Color font_disabled_color = get_theme_color(SNAME("font_disabled_color")); Color font_accelerator_color = get_theme_color(SNAME("font_accelerator_color")); @@ -737,11 +760,11 @@ void PopupMenu::_shape_item(int p_item) { } else { items.write[p_item].text_buf->set_direction((TextServer::Direction)items[p_item].text_direction); } - items.write[p_item].text_buf->add_string(items.write[p_item].xl_text, font, font_size, items[p_item].opentype_features, !items[p_item].language.is_empty() ? items[p_item].language : TranslationServer::get_singleton()->get_tool_locale()); + items.write[p_item].text_buf->add_string(items.write[p_item].xl_text, font, font_size, items[p_item].language); items.write[p_item].accel_text_buf->clear(); items.write[p_item].accel_text_buf->set_direction(is_layout_rtl() ? TextServer::DIRECTION_RTL : TextServer::DIRECTION_LTR); - items.write[p_item].accel_text_buf->add_string(_get_accel_text(items.write[p_item]), font, font_size, Dictionary(), TranslationServer::get_singleton()->get_tool_locale()); + items.write[p_item].accel_text_buf->add_string(_get_accel_text(items.write[p_item]), font, font_size); items.write[p_item].dirty = false; } } @@ -834,10 +857,10 @@ void PopupMenu::_notification(int p_what) { // Set margin on the margin container Ref<StyleBox> panel_style = get_theme_stylebox(SNAME("panel")); - margin_container->add_theme_constant_override("margin_top", panel_style->get_margin(Side::SIDE_TOP)); - margin_container->add_theme_constant_override("margin_bottom", panel_style->get_margin(Side::SIDE_BOTTOM)); margin_container->add_theme_constant_override("margin_left", panel_style->get_margin(Side::SIDE_LEFT)); + margin_container->add_theme_constant_override("margin_top", panel_style->get_margin(Side::SIDE_TOP)); margin_container->add_theme_constant_override("margin_right", panel_style->get_margin(Side::SIDE_RIGHT)); + margin_container->add_theme_constant_override("margin_bottom", panel_style->get_margin(Side::SIDE_BOTTOM)); } } break; } @@ -1044,29 +1067,6 @@ void PopupMenu::set_item_text_direction(int p_item, Control::TextDirection p_tex } } -void PopupMenu::clear_item_opentype_features(int p_item) { - if (p_item < 0) { - p_item += get_item_count(); - } - ERR_FAIL_INDEX(p_item, items.size()); - items.write[p_item].opentype_features.clear(); - items.write[p_item].dirty = true; - control->update(); -} - -void PopupMenu::set_item_opentype_feature(int p_item, const String &p_name, int p_value) { - if (p_item < 0) { - p_item += get_item_count(); - } - ERR_FAIL_INDEX(p_item, items.size()); - int32_t tag = TS->name_to_tag(p_name); - if (!items[p_item].opentype_features.has(tag) || (int)items[p_item].opentype_features[tag] != p_value) { - items.write[p_item].opentype_features[tag] = p_value; - items.write[p_item].dirty = true; - control->update(); - } -} - void PopupMenu::set_item_language(int p_item, const String &p_language) { if (p_item < 0) { p_item += get_item_count(); @@ -1172,15 +1172,6 @@ Control::TextDirection PopupMenu::get_item_text_direction(int p_item) const { return items[p_item].text_direction; } -int PopupMenu::get_item_opentype_feature(int p_item, const String &p_name) const { - ERR_FAIL_INDEX_V(p_item, items.size(), -1); - int32_t tag = TS->name_to_tag(p_name); - if (!items[p_item].opentype_features.has(tag)) { - return -1; - } - return items[p_item].opentype_features[tag]; -} - String PopupMenu::get_item_language(int p_item) const { ERR_FAIL_INDEX_V(p_item, items.size(), ""); return items[p_item].language; @@ -1251,6 +1242,11 @@ Ref<Shortcut> PopupMenu::get_item_shortcut(int p_idx) const { return items[p_idx].shortcut; } +int PopupMenu::get_item_horizontal_offset(int p_idx) const { + ERR_FAIL_INDEX_V(p_idx, items.size(), 0); + return items[p_idx].h_ofs; +} + int PopupMenu::get_item_state(int p_idx) const { ERR_FAIL_INDEX_V(p_idx, items.size(), -1); return items[p_idx].state; @@ -1316,7 +1312,7 @@ void PopupMenu::set_item_shortcut(int p_idx, const Ref<Shortcut> &p_shortcut, bo control->update(); } -void PopupMenu::set_item_h_offset(int p_idx, int p_offset) { +void PopupMenu::set_item_horizontal_offset(int p_idx, int p_offset) { if (p_idx < 0) { p_idx += get_item_count(); } @@ -1825,7 +1821,6 @@ void PopupMenu::_bind_methods() { ClassDB::bind_method(D_METHOD("set_item_text", "index", "text"), &PopupMenu::set_item_text); ClassDB::bind_method(D_METHOD("set_item_text_direction", "index", "direction"), &PopupMenu::set_item_text_direction); - ClassDB::bind_method(D_METHOD("set_item_opentype_feature", "index", "tag", "value"), &PopupMenu::set_item_opentype_feature); ClassDB::bind_method(D_METHOD("set_item_language", "index", "language"), &PopupMenu::set_item_language); ClassDB::bind_method(D_METHOD("set_item_icon", "index", "icon"), &PopupMenu::set_item_icon); ClassDB::bind_method(D_METHOD("set_item_checked", "index", "checked"), &PopupMenu::set_item_checked); @@ -1839,6 +1834,7 @@ void PopupMenu::_bind_methods() { ClassDB::bind_method(D_METHOD("set_item_as_radio_checkable", "index", "enable"), &PopupMenu::set_item_as_radio_checkable); ClassDB::bind_method(D_METHOD("set_item_tooltip", "index", "tooltip"), &PopupMenu::set_item_tooltip); ClassDB::bind_method(D_METHOD("set_item_shortcut", "index", "shortcut", "global"), &PopupMenu::set_item_shortcut, DEFVAL(false)); + ClassDB::bind_method(D_METHOD("set_item_horizontal_offset", "index", "offset"), &PopupMenu::set_item_horizontal_offset); ClassDB::bind_method(D_METHOD("set_item_multistate", "index", "state"), &PopupMenu::set_item_multistate); ClassDB::bind_method(D_METHOD("set_item_shortcut_disabled", "index", "disabled"), &PopupMenu::set_item_shortcut_disabled); @@ -1847,8 +1843,6 @@ void PopupMenu::_bind_methods() { ClassDB::bind_method(D_METHOD("get_item_text", "index"), &PopupMenu::get_item_text); ClassDB::bind_method(D_METHOD("get_item_text_direction", "index"), &PopupMenu::get_item_text_direction); - ClassDB::bind_method(D_METHOD("get_item_opentype_feature", "index", "tag"), &PopupMenu::get_item_opentype_feature); - ClassDB::bind_method(D_METHOD("clear_item_opentype_features", "index"), &PopupMenu::clear_item_opentype_features); ClassDB::bind_method(D_METHOD("get_item_language", "index"), &PopupMenu::get_item_language); ClassDB::bind_method(D_METHOD("get_item_icon", "index"), &PopupMenu::get_item_icon); ClassDB::bind_method(D_METHOD("is_item_checked", "index"), &PopupMenu::is_item_checked); @@ -1864,6 +1858,7 @@ void PopupMenu::_bind_methods() { ClassDB::bind_method(D_METHOD("is_item_shortcut_disabled", "index"), &PopupMenu::is_item_shortcut_disabled); ClassDB::bind_method(D_METHOD("get_item_tooltip", "index"), &PopupMenu::get_item_tooltip); ClassDB::bind_method(D_METHOD("get_item_shortcut", "index"), &PopupMenu::get_item_shortcut); + ClassDB::bind_method(D_METHOD("get_item_horizontal_offset", "index"), &PopupMenu::get_item_horizontal_offset); ClassDB::bind_method(D_METHOD("set_current_index", "index"), &PopupMenu::set_current_index); ClassDB::bind_method(D_METHOD("get_current_index"), &PopupMenu::get_current_index); @@ -1895,7 +1890,7 @@ void PopupMenu::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "hide_on_item_selection"), "set_hide_on_item_selection", "is_hide_on_item_selection"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "hide_on_checkable_item_selection"), "set_hide_on_checkable_item_selection", "is_hide_on_checkable_item_selection"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "hide_on_state_item_selection"), "set_hide_on_state_item_selection", "is_hide_on_state_item_selection"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "submenu_popup_delay"), "set_submenu_popup_delay", "get_submenu_popup_delay"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "submenu_popup_delay", PROPERTY_HINT_NONE, "suffix:s"), "set_submenu_popup_delay", "get_submenu_popup_delay"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "allow_search"), "set_allow_search", "get_allow_search"); ADD_ARRAY_COUNT("Items", "item_count", "set_item_count", "get_item_count", "item_"); @@ -1914,7 +1909,7 @@ void PopupMenu::popup(const Rect2 &p_bounds) { PopupMenu::PopupMenu() { // Margin Container margin_container = memnew(MarginContainer); - margin_container->set_anchors_and_offsets_preset(Control::PRESET_WIDE); + margin_container->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT); add_child(margin_container, false, INTERNAL_MODE_FRONT); margin_container->connect("draw", callable_mp(this, &PopupMenu::_draw_background)); @@ -1926,7 +1921,7 @@ PopupMenu::PopupMenu() { // The control which will display the items control = memnew(Control); control->set_clip_contents(false); - control->set_anchors_and_offsets_preset(Control::PRESET_WIDE); + control->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT); control->set_h_size_flags(Control::SIZE_EXPAND_FILL); control->set_v_size_flags(Control::SIZE_EXPAND_FILL); scroll_container->add_child(control, false, INTERNAL_MODE_FRONT); diff --git a/scene/gui/popup_menu.h b/scene/gui/popup_menu.h index 98d76875cb..daa38b0e6d 100644 --- a/scene/gui/popup_menu.h +++ b/scene/gui/popup_menu.h @@ -47,7 +47,6 @@ class PopupMenu : public Popup { Ref<TextLine> text_buf; Ref<TextLine> accel_text_buf; - Dictionary opentype_features; String language; Control::TextDirection text_direction = Control::TEXT_DIRECTION_AUTO; @@ -117,7 +116,7 @@ class PopupMenu : public Popup { bool hide_on_multistate_item_selection = false; Vector2 moved; - Map<Ref<Shortcut>, int> shortcut_refcount; + HashMap<Ref<Shortcut>, int> shortcut_refcount; void _ref_shortcut(Ref<Shortcut> p_sc); void _unref_shortcut(Ref<Shortcut> p_sc); @@ -148,6 +147,8 @@ public: // this value should be updated to reflect the new size. static const int ITEM_PROPERTY_SIZE = 10; + virtual void _parent_focused() override; + 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); @@ -169,8 +170,6 @@ public: void set_item_text(int p_idx, const String &p_text); void set_item_text_direction(int p_idx, Control::TextDirection p_text_direction); - void set_item_opentype_feature(int p_idx, const String &p_name, int p_value); - void clear_item_opentype_features(int p_idx); void set_item_language(int p_idx, const String &p_language); void set_item_icon(int p_idx, const Ref<Texture2D> &p_icon); void set_item_checked(int p_idx, bool p_checked); @@ -184,7 +183,7 @@ public: void set_item_as_radio_checkable(int p_idx, bool p_radio_checkable); void set_item_tooltip(int p_idx, const String &p_tooltip); void set_item_shortcut(int p_idx, const Ref<Shortcut> &p_shortcut, bool p_global = false); - void set_item_h_offset(int p_idx, int p_offset); + void set_item_horizontal_offset(int p_idx, int p_offset); void set_item_multistate(int p_idx, int p_state); void toggle_item_multistate(int p_idx); void set_item_shortcut_disabled(int p_idx, bool p_disabled); @@ -193,7 +192,6 @@ public: String get_item_text(int p_idx) const; Control::TextDirection get_item_text_direction(int p_idx) const; - int get_item_opentype_feature(int p_idx, const String &p_name) const; String get_item_language(int p_idx) const; int get_item_idx_from_text(const String &text) const; Ref<Texture2D> get_item_icon(int p_idx) const; @@ -210,6 +208,7 @@ public: bool is_item_shortcut_disabled(int p_idx) const; String get_item_tooltip(int p_idx) const; Ref<Shortcut> get_item_shortcut(int p_idx) const; + int get_item_horizontal_offset(int p_idx) const; int get_item_state(int p_idx) const; void set_current_index(int p_idx); diff --git a/scene/gui/progress_bar.cpp b/scene/gui/progress_bar.cpp index 50ffb3ca67..80859e8eb9 100644 --- a/scene/gui/progress_bar.cpp +++ b/scene/gui/progress_bar.cpp @@ -62,15 +62,42 @@ void ProgressBar::_notification(int p_what) { Color font_color = get_theme_color(SNAME("font_color")); draw_style_box(bg, Rect2(Point2(), get_size())); + float r = get_as_ratio(); - int mp = fg->get_minimum_size().width; - int p = r * (get_size().width - mp); - if (p > 0) { - if (is_layout_rtl()) { - draw_style_box(fg, Rect2(Point2(p, 0), Size2(fg->get_minimum_size().width, get_size().height))); - } else { - draw_style_box(fg, Rect2(Point2(0, 0), Size2(p + fg->get_minimum_size().width, get_size().height))); - } + + switch (mode) { + case FILL_BEGIN_TO_END: + case FILL_END_TO_BEGIN: { + int mp = fg->get_minimum_size().width; + int p = round(r * (get_size().width - mp)); + // We want FILL_BEGIN_TO_END to map to right to left when UI layout is RTL, + // and left to right otherwise. And likewise for FILL_END_TO_BEGIN. + bool right_to_left = is_layout_rtl() ? (mode == FILL_BEGIN_TO_END) : (mode == FILL_END_TO_BEGIN); + if (p > 0) { + if (right_to_left) { + int p_remaining = round((1.0 - r) * (get_size().width - mp)); + draw_style_box(fg, Rect2(Point2(p_remaining, 0), Size2(p + fg->get_minimum_size().width, get_size().height))); + } else { + draw_style_box(fg, Rect2(Point2(0, 0), Size2(p + fg->get_minimum_size().width, get_size().height))); + } + } + } break; + case FILL_TOP_TO_BOTTOM: + case FILL_BOTTOM_TO_TOP: { + int mp = fg->get_minimum_size().height; + int p = round(r * (get_size().height - mp)); + + if (p > 0) { + if (mode == FILL_TOP_TO_BOTTOM) { + draw_style_box(fg, Rect2(Point2(0, 0), Size2(get_size().width, p + fg->get_minimum_size().height))); + } else { + int p_remaining = round((1.0 - r) * (get_size().height - mp)); + draw_style_box(fg, Rect2(Point2(0, p_remaining), Size2(get_size().width, p + fg->get_minimum_size().height))); + } + } + } break; + case FILL_MODE_MAX: + break; } if (percent_visible) { @@ -88,8 +115,22 @@ void ProgressBar::_notification(int p_what) { } } +void ProgressBar::set_fill_mode(int p_fill) { + ERR_FAIL_INDEX(p_fill, FILL_MODE_MAX); + mode = (FillMode)p_fill; + update(); +} + +int ProgressBar::get_fill_mode() { + return mode; +} + void ProgressBar::set_percent_visible(bool p_visible) { + if (percent_visible == p_visible) { + return; + } percent_visible = p_visible; + update_minimum_size(); update(); } @@ -98,10 +139,18 @@ bool ProgressBar::is_percent_visible() const { } void ProgressBar::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_fill_mode", "mode"), &ProgressBar::set_fill_mode); + ClassDB::bind_method(D_METHOD("get_fill_mode"), &ProgressBar::get_fill_mode); ClassDB::bind_method(D_METHOD("set_percent_visible", "visible"), &ProgressBar::set_percent_visible); ClassDB::bind_method(D_METHOD("is_percent_visible"), &ProgressBar::is_percent_visible); + ADD_PROPERTY(PropertyInfo(Variant::INT, "fill_mode", PROPERTY_HINT_ENUM, "Begin to End,End to Begin,Top to Bottom,Bottom to Top"), "set_fill_mode", "get_fill_mode"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "percent_visible"), "set_percent_visible", "is_percent_visible"); + + BIND_ENUM_CONSTANT(FILL_BEGIN_TO_END); + BIND_ENUM_CONSTANT(FILL_END_TO_BEGIN); + BIND_ENUM_CONSTANT(FILL_TOP_TO_BOTTOM); + BIND_ENUM_CONSTANT(FILL_BOTTOM_TO_TOP); } ProgressBar::ProgressBar() { diff --git a/scene/gui/progress_bar.h b/scene/gui/progress_bar.h index 2d89163f78..5ba21ad7d5 100644 --- a/scene/gui/progress_bar.h +++ b/scene/gui/progress_bar.h @@ -43,11 +43,27 @@ protected: static void _bind_methods(); public: + enum FillMode { + FILL_BEGIN_TO_END, + FILL_END_TO_BEGIN, + FILL_TOP_TO_BOTTOM, + FILL_BOTTOM_TO_TOP, + FILL_MODE_MAX + }; + + void set_fill_mode(int p_fill); + int get_fill_mode(); + void set_percent_visible(bool p_visible); bool is_percent_visible() const; Size2 get_minimum_size() const override; ProgressBar(); + +private: + FillMode mode = FILL_BEGIN_TO_END; }; +VARIANT_ENUM_CAST(ProgressBar::FillMode); + #endif // PROGRESS_BAR_H diff --git a/scene/gui/range.cpp b/scene/gui/range.cpp index 8e66826e9d..fae6688452 100644 --- a/scene/gui/range.cpp +++ b/scene/gui/range.cpp @@ -50,8 +50,8 @@ void Range::_value_changed_notify() { } void Range::Shared::emit_value_changed() { - for (Set<Range *>::Element *E = owners.front(); E; E = E->next()) { - Range *r = E->get(); + for (Range *E : owners) { + Range *r = E; if (!r->is_inside_tree()) { continue; } @@ -70,8 +70,8 @@ void Range::_validate_values() { } void Range::Shared::emit_changed(const char *p_what) { - for (Set<Range *>::Element *E = owners.front(); E; E = E->next()) { - Range *r = E->get(); + for (Range *E : owners) { + Range *r = E; if (!r->is_inside_tree()) { continue; } diff --git a/scene/gui/range.h b/scene/gui/range.h index 46b0d39202..1274821bd1 100644 --- a/scene/gui/range.h +++ b/scene/gui/range.h @@ -45,7 +45,7 @@ class Range : public Control { bool exp_ratio = false; bool allow_greater = false; bool allow_lesser = false; - Set<Range *> owners; + HashSet<Range *> owners; void emit_value_changed(); void emit_changed(const char *p_what = ""); }; diff --git a/scene/gui/reference_rect.cpp b/scene/gui/reference_rect.cpp index ed79da5c22..5190a5a7d2 100644 --- a/scene/gui/reference_rect.cpp +++ b/scene/gui/reference_rect.cpp @@ -83,6 +83,6 @@ void ReferenceRect::_bind_methods() { ClassDB::bind_method(D_METHOD("set_editor_only", "enabled"), &ReferenceRect::set_editor_only); ADD_PROPERTY(PropertyInfo(Variant::COLOR, "border_color"), "set_border_color", "get_border_color"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "border_width", PROPERTY_HINT_RANGE, "0.0,5.0,0.1,or_greater"), "set_border_width", "get_border_width"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "border_width", PROPERTY_HINT_RANGE, "0.0,5.0,0.1,or_greater,suffix:px"), "set_border_width", "get_border_width"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "editor_only"), "set_editor_only", "get_editor_only"); } diff --git a/scene/gui/rich_text_label.cpp b/scene/gui/rich_text_label.cpp index ec13399f82..94e0944628 100644 --- a/scene/gui/rich_text_label.cpp +++ b/scene/gui/rich_text_label.cpp @@ -149,7 +149,12 @@ RichTextLabel::Item *RichTextLabel::_get_item_at_pos(RichTextLabel::Item *p_item return it; } } break; - case ITEM_NEWLINE: + case ITEM_NEWLINE: { + offset += 1; + if (offset == p_position) { + return it; + } + } break; case ITEM_IMAGE: case ITEM_TABLE: { offset += 1; @@ -209,9 +214,10 @@ String RichTextLabel::_letters(int p_num, bool p_capitalize) const { void RichTextLabel::_update_line_font(ItemFrame *p_frame, int p_line, const Ref<Font> &p_base_font, int p_base_font_size) { ERR_FAIL_COND(p_frame == nullptr); - ERR_FAIL_COND(p_line < 0 || p_line >= p_frame->lines.size()); + ERR_FAIL_COND(p_line < 0 || p_line >= (int)p_frame->lines.size()); - Line &l = p_frame->lines.write[p_line]; + Line &l = p_frame->lines[p_line]; + MutexLock lock(l.text_buf->get_mutex()); RID t = l.text_buf->get_rid(); int spans = TS->shaped_get_span_count(t); @@ -226,12 +232,14 @@ void RichTextLabel::_update_line_font(ItemFrame *p_frame, int p_line, const Ref< if (font_size == -1) { font_size = p_base_font_size; } - Dictionary font_ftr = _find_font_features(it); - TS->shaped_set_span_update_font(t, i, font->get_rids(), font_size, font_ftr); + TS->shaped_set_span_update_font(t, i, font->get_rids(), font_size, font->get_opentype_features()); + for (int j = 0; j < TextServer::SPACING_MAX; j++) { + TS->shaped_text_set_spacing(t, TextServer::SpacingType(j), font->get_spacing(TextServer::SpacingType(j))); + } } } - Item *it_to = (p_line + 1 < p_frame->lines.size()) ? p_frame->lines[p_line + 1].from : nullptr; + Item *it_to = (p_line + 1 < (int)p_frame->lines.size()) ? p_frame->lines[p_line + 1].from : nullptr; for (Item *it = l.from; it && it != it_to; it = _get_next_item(it)) { switch (it->type) { case ITEM_TABLE: { @@ -239,7 +247,7 @@ void RichTextLabel::_update_line_font(ItemFrame *p_frame, int p_line, const Ref< 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++) { + for (int i = 0; i < (int)frame->lines.size(); i++) { _update_line_font(frame, i, p_base_font, p_base_font_size); } } @@ -250,41 +258,44 @@ void RichTextLabel::_update_line_font(ItemFrame *p_frame, int p_line, const Ref< } } -void RichTextLabel::_resize_line(ItemFrame *p_frame, int p_line, const Ref<Font> &p_base_font, int p_base_font_size, int p_width) { - ERR_FAIL_COND(p_frame == nullptr); - ERR_FAIL_COND(p_line < 0 || p_line >= p_frame->lines.size()); +float RichTextLabel::_resize_line(ItemFrame *p_frame, int p_line, const Ref<Font> &p_base_font, int p_base_font_size, int p_width, float p_h) { + ERR_FAIL_COND_V(p_frame == nullptr, p_h); + ERR_FAIL_COND_V(p_line < 0 || p_line >= (int)p_frame->lines.size(), p_h); - Line &l = p_frame->lines.write[p_line]; + Line &l = p_frame->lines[p_line]; + MutexLock lock(l.text_buf->get_mutex()); l.offset.x = _find_margin(l.from, p_base_font, p_base_font_size); l.text_buf->set_width(p_width - l.offset.x); if (tab_size > 0) { // Align inline tabs. Vector<float> tabs; - tabs.push_back(tab_size * p_base_font->get_char_size(' ', 0, p_base_font_size).width); + tabs.push_back(tab_size * p_base_font->get_char_size(' ', p_base_font_size).width); l.text_buf->tab_align(tabs); } - Item *it_to = (p_line + 1 < p_frame->lines.size()) ? p_frame->lines[p_line + 1].from : nullptr; + Item *it_to = (p_line + 1 < (int)p_frame->lines.size()) ? p_frame->lines[p_line + 1].from : nullptr; for (Item *it = l.from; it && it != it_to; it = _get_next_item(it)) { switch (it->type) { case ITEM_TABLE: { ItemTable *table = static_cast<ItemTable *>(it); - int hseparation = get_theme_constant(SNAME("table_hseparation")); - int vseparation = get_theme_constant(SNAME("table_vseparation")); + int hseparation = get_theme_constant(SNAME("table_h_separation")); + int vseparation = get_theme_constant(SNAME("table_v_separation")); int col_count = table->columns.size(); for (int i = 0; i < col_count; i++) { - table->columns.write[i].width = 0; + table->columns[i].width = 0; } int idx = 0; 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++) { + float prev_h = 0; + for (int i = 0; i < (int)frame->lines.size(); i++) { + MutexLock sub_lock(frame->lines[i].text_buf->get_mutex()); int w = _find_margin(frame->lines[i].from, p_base_font, p_base_font_size) + 1; - _resize_line(frame, i, p_base_font, p_base_font_size, w); + prev_h = _resize_line(frame, i, p_base_font, p_base_font_size, w, prev_h); } idx++; } @@ -300,7 +311,7 @@ void RichTextLabel::_resize_line(ItemFrame *p_frame, int p_line, const Ref<Font> for (int i = 0; i < col_count; i++) { remaining_width -= table->columns[i].min_width; if (table->columns[i].max_width > table->columns[i].min_width) { - table->columns.write[i].expand = true; + table->columns[i].expand = true; } if (table->columns[i].expand) { total_ratio += table->columns[i].expand_ratio; @@ -309,9 +320,9 @@ void RichTextLabel::_resize_line(ItemFrame *p_frame, int p_line, const Ref<Font> // Assign actual widths. for (int i = 0; i < col_count; i++) { - table->columns.write[i].width = table->columns[i].min_width; + table->columns[i].width = table->columns[i].min_width; if (table->columns[i].expand && total_ratio > 0 && remaining_width > 0) { - table->columns.write[i].width += table->columns[i].expand_ratio * remaining_width / total_ratio; + table->columns[i].width += table->columns[i].expand_ratio * remaining_width / total_ratio; } table->total_width += table->columns[i].width + hseparation; } @@ -328,7 +339,7 @@ void RichTextLabel::_resize_line(ItemFrame *p_frame, int p_line, const Ref<Font> int dif = table->columns[i].width - table->columns[i].max_width; if (dif > 0) { table_need_fit = true; - table->columns.write[i].width = table->columns[i].max_width; + table->columns[i].width = table->columns[i].max_width; table->total_width -= dif; total_ratio -= table->columns[i].expand_ratio; } @@ -342,7 +353,7 @@ void RichTextLabel::_resize_line(ItemFrame *p_frame, int p_line, const Ref<Font> if (dif > 0) { int slice = table->columns[i].expand_ratio * remaining_width / total_ratio; int incr = MIN(dif, slice); - table->columns.write[i].width += incr; + table->columns[i].width += incr; table->total_width += incr; } } @@ -366,16 +377,14 @@ void RichTextLabel::_resize_line(ItemFrame *p_frame, int p_line, const Ref<Font> offset.x += frame->padding.position.x; float yofs = frame->padding.position.y; - for (int i = 0; i < frame->lines.size(); i++) { - frame->lines.write[i].text_buf->set_width(table->columns[column].width); - table->columns.write[column].width = MAX(table->columns.write[column].width, ceil(frame->lines[i].text_buf->get_size().x)); + float prev_h = 0; + for (int i = 0; i < (int)frame->lines.size(); i++) { + MutexLock sub_lock(frame->lines[i].text_buf->get_mutex()); + frame->lines[i].text_buf->set_width(table->columns[column].width); + table->columns[column].width = MAX(table->columns[column].width, ceil(frame->lines[i].text_buf->get_size().x)); - if (i > 0) { - frame->lines.write[i].offset.y = frame->lines[i - 1].offset.y + frame->lines[i - 1].text_buf->get_size().y + frame->lines[i - 1].text_buf->get_line_count() * get_theme_constant(SNAME("line_separation")); - } else { - frame->lines.write[i].offset.y = 0; - } - frame->lines.write[i].offset += offset; + frame->lines[i].offset.y = prev_h; + frame->lines[i].offset += offset; float h = frame->lines[i].text_buf->get_size().y + (frame->lines[i].text_buf->get_line_count() - 1) * get_theme_constant(SNAME("line_separation")); if (i > 0) { @@ -388,6 +397,7 @@ void RichTextLabel::_resize_line(ItemFrame *p_frame, int p_line, const Ref<Font> h = MIN(h, frame->max_size_over.y); } yofs += h; + prev_h = frame->lines[i].offset.y + frame->lines[i].text_buf->get_size().y + frame->lines[i].text_buf->get_line_count() * get_theme_constant(SNAME("line_separation")); } yofs += frame->padding.size.y; offset.x += table->columns[column].width + hseparation + frame->padding.size.x; @@ -410,37 +420,36 @@ void RichTextLabel::_resize_line(ItemFrame *p_frame, int p_line, const Ref<Font> } } - if (p_line > 0) { - l.offset.y = p_frame->lines[p_line - 1].offset.y + p_frame->lines[p_line - 1].text_buf->get_size().y + p_frame->lines[p_line - 1].text_buf->get_line_count() * get_theme_constant(SNAME("line_separation")); - } else { - l.offset.y = 0; - } + l.offset.y = p_h; + return _calculate_line_vertical_offset(l); } -void RichTextLabel::_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) { - ERR_FAIL_COND(p_frame == nullptr); - ERR_FAIL_COND(p_line < 0 || p_line >= p_frame->lines.size()); +float RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font> &p_base_font, int p_base_font_size, int p_width, float p_h, int *r_char_offset) { + ERR_FAIL_COND_V(p_frame == nullptr, p_h); + ERR_FAIL_COND_V(p_line < 0 || p_line >= (int)p_frame->lines.size(), p_h); - Line &l = p_frame->lines.write[p_line]; + Line &l = p_frame->lines[p_line]; + MutexLock lock(l.text_buf->get_mutex()); - uint16_t autowrap_flags = TextServer::BREAK_MANDATORY; + BitField<TextServer::LineBreakFlag> autowrap_flags = TextServer::BREAK_MANDATORY; switch (autowrap_mode) { - case AUTOWRAP_WORD_SMART: - autowrap_flags = TextServer::BREAK_WORD_BOUND_ADAPTIVE | TextServer::BREAK_MANDATORY; + case TextServer::AUTOWRAP_WORD_SMART: + autowrap_flags = TextServer::BREAK_WORD_BOUND | TextServer::BREAK_ADAPTIVE | TextServer::BREAK_MANDATORY; break; - case AUTOWRAP_WORD: + case TextServer::AUTOWRAP_WORD: autowrap_flags = TextServer::BREAK_WORD_BOUND | TextServer::BREAK_MANDATORY; break; - case AUTOWRAP_ARBITRARY: + case TextServer::AUTOWRAP_ARBITRARY: autowrap_flags = TextServer::BREAK_GRAPHEME_BOUND | TextServer::BREAK_MANDATORY; break; - case AUTOWRAP_OFF: + case TextServer::AUTOWRAP_OFF: break; } // Clear cache. l.text_buf->clear(); - l.text_buf->set_flags(autowrap_flags | TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND | TextServer::JUSTIFICATION_TRIM_EDGE_SPACES); + l.text_buf->set_break_flags(autowrap_flags); + l.text_buf->set_justification_flags(TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND | TextServer::JUSTIFICATION_TRIM_EDGE_SPACES); l.char_offset = *r_char_offset; l.char_count = 0; @@ -452,16 +461,16 @@ void RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font> if (tab_size > 0) { // Align inline tabs. Vector<float> tabs; - tabs.push_back(tab_size * p_base_font->get_char_size(' ', 0, p_base_font_size).width); + tabs.push_back(tab_size * p_base_font->get_char_size(' ', p_base_font_size).width); l.text_buf->tab_align(tabs); } // Shape current paragraph. String text; - Item *it_to = (p_line + 1 < p_frame->lines.size()) ? p_frame->lines[p_line + 1].from : nullptr; + Item *it_to = (p_line + 1 < (int)p_frame->lines.size()) ? p_frame->lines[p_line + 1].from : nullptr; int remaining_characters = visible_characters - l.char_offset; for (Item *it = l.from; it && it != it_to; it = _get_next_item(it)) { - if (visible_chars_behavior == VC_CHARS_BEFORE_SHAPING && visible_characters >= 0 && remaining_characters <= 0) { + if (visible_chars_behavior == TextServer::VC_CHARS_BEFORE_SHAPING && visible_characters >= 0 && remaining_characters <= 0) { break; } switch (it->type) { @@ -482,7 +491,7 @@ void RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font> if (font_size == -1) { font_size = p_base_font_size; } - l.text_buf->add_string("\n", font, font_size, Dictionary(), ""); + l.text_buf->add_string("\n", font, font_size); text += "\n"; l.char_count++; remaining_characters--; @@ -497,15 +506,14 @@ void RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font> if (font_size == -1) { font_size = p_base_font_size; } - Dictionary font_ftr = _find_font_features(it); String lang = _find_language(it); String tx = t->text; - if (visible_chars_behavior == VC_CHARS_BEFORE_SHAPING && visible_characters >= 0 && remaining_characters >= 0) { + if (visible_chars_behavior == TextServer::VC_CHARS_BEFORE_SHAPING && visible_characters >= 0 && remaining_characters >= 0) { tx = tx.substr(0, remaining_characters); } remaining_characters -= tx.length(); - l.text_buf->add_string(tx, font, font_size, font_ftr, lang, (uint64_t)it); + l.text_buf->add_string(tx, font, font_size, lang, (uint64_t)it); text += tx; l.char_count += tx.length(); } break; @@ -518,15 +526,15 @@ void RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font> } break; case ITEM_TABLE: { ItemTable *table = static_cast<ItemTable *>(it); - int hseparation = get_theme_constant(SNAME("table_hseparation")); - int vseparation = get_theme_constant(SNAME("table_vseparation")); + int hseparation = get_theme_constant(SNAME("table_h_separation")); + int vseparation = get_theme_constant(SNAME("table_v_separation")); int col_count = table->columns.size(); int t_char_count = 0; // Set minimums to zero. for (int i = 0; i < col_count; i++) { - table->columns.write[i].min_width = 0; - table->columns.write[i].max_width = 0; - table->columns.write[i].width = 0; + table->columns[i].min_width = 0; + table->columns[i].max_width = 0; + table->columns[i].width = 0; } // Compute minimum width for each cell. const int available_width = p_width - hseparation * (col_count - 1); @@ -537,17 +545,20 @@ void RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font> ItemFrame *frame = static_cast<ItemFrame *>(E); int column = idx % col_count; - for (int i = 0; i < frame->lines.size(); i++) { + float prev_h = 0; + for (int i = 0; i < (int)frame->lines.size(); i++) { + MutexLock sub_lock(frame->lines[i].text_buf->get_mutex()); + int char_offset = l.char_offset + l.char_count; int w = _find_margin(frame->lines[i].from, p_base_font, p_base_font_size) + 1; - _shape_line(frame, i, p_base_font, p_base_font_size, w, &char_offset); + prev_h = _shape_line(frame, i, p_base_font, p_base_font_size, w, prev_h, &char_offset); int cell_ch = (char_offset - (l.char_offset + l.char_count)); l.char_count += cell_ch; t_char_count += cell_ch; remaining_characters -= cell_ch; - table->columns.write[column].min_width = MAX(table->columns[column].min_width, ceil(frame->lines[i].text_buf->get_size().x)); - table->columns.write[column].max_width = MAX(table->columns[column].max_width, ceil(frame->lines[i].text_buf->get_non_wrapped_size().x)); + table->columns[column].min_width = MAX(table->columns[column].min_width, ceil(frame->lines[i].text_buf->get_size().x)); + table->columns[column].max_width = MAX(table->columns[column].max_width, ceil(frame->lines[i].text_buf->get_non_wrapped_size().x)); } idx++; } @@ -560,7 +571,7 @@ void RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font> for (int i = 0; i < col_count; i++) { remaining_width -= table->columns[i].min_width; if (table->columns[i].max_width > table->columns[i].min_width) { - table->columns.write[i].expand = true; + table->columns[i].expand = true; } if (table->columns[i].expand) { total_ratio += table->columns[i].expand_ratio; @@ -569,9 +580,9 @@ void RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font> // Assign actual widths. for (int i = 0; i < col_count; i++) { - table->columns.write[i].width = table->columns[i].min_width; + table->columns[i].width = table->columns[i].min_width; if (table->columns[i].expand && total_ratio > 0 && remaining_width > 0) { - table->columns.write[i].width += table->columns[i].expand_ratio * remaining_width / total_ratio; + table->columns[i].width += table->columns[i].expand_ratio * remaining_width / total_ratio; } table->total_width += table->columns[i].width + hseparation; } @@ -588,7 +599,7 @@ void RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font> int dif = table->columns[i].width - table->columns[i].max_width; if (dif > 0) { table_need_fit = true; - table->columns.write[i].width = table->columns[i].max_width; + table->columns[i].width = table->columns[i].max_width; table->total_width -= dif; total_ratio -= table->columns[i].expand_ratio; } @@ -602,7 +613,7 @@ void RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font> if (dif > 0) { int slice = table->columns[i].expand_ratio * remaining_width / total_ratio; int incr = MIN(dif, slice); - table->columns.write[i].width += incr; + table->columns[i].width += incr; table->total_width += incr; } } @@ -626,16 +637,15 @@ void RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font> offset.x += frame->padding.position.x; float yofs = frame->padding.position.y; - for (int i = 0; i < frame->lines.size(); i++) { - frame->lines.write[i].text_buf->set_width(table->columns[column].width); - table->columns.write[column].width = MAX(table->columns.write[column].width, ceil(frame->lines[i].text_buf->get_size().x)); + float prev_h = 0; + for (int i = 0; i < (int)frame->lines.size(); i++) { + MutexLock sub_lock(frame->lines[i].text_buf->get_mutex()); - if (i > 0) { - frame->lines.write[i].offset.y = frame->lines[i - 1].offset.y + frame->lines[i - 1].text_buf->get_size().y + frame->lines[i - 1].text_buf->get_line_count() * get_theme_constant(SNAME("line_separation")); - } else { - frame->lines.write[i].offset.y = 0; - } - frame->lines.write[i].offset += offset; + frame->lines[i].text_buf->set_width(table->columns[column].width); + table->columns[column].width = MAX(table->columns[column].width, ceil(frame->lines[i].text_buf->get_size().x)); + + frame->lines[i].offset.y = prev_h; + frame->lines[i].offset += offset; float h = frame->lines[i].text_buf->get_size().y + (frame->lines[i].text_buf->get_line_count() - 1) * get_theme_constant(SNAME("line_separation")); if (i > 0) { @@ -648,6 +658,7 @@ void RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font> h = MIN(h, frame->max_size_over.y); } yofs += h; + prev_h = frame->lines[i].offset.y + frame->lines[i].text_buf->get_size().y + frame->lines[i].text_buf->get_line_count() * get_theme_constant(SNAME("line_separation")); } yofs += frame->padding.size.y; offset.x += table->columns[column].width + hseparation + frame->padding.size.x; @@ -678,24 +689,22 @@ void RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font> *r_char_offset = l.char_offset + l.char_count; - if (p_line > 0) { - l.offset.y = p_frame->lines[p_line - 1].offset.y + p_frame->lines[p_line - 1].text_buf->get_size().y + p_frame->lines[p_line - 1].text_buf->get_line_count() * get_theme_constant(SNAME("line_separation")); - } else { - l.offset.y = 0; - } + l.offset.y = p_h; + return _calculate_line_vertical_offset(l); } 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, int &r_processed_glyphs) { ERR_FAIL_COND_V(p_frame == nullptr, 0); - ERR_FAIL_COND_V(p_line < 0 || p_line >= p_frame->lines.size(), 0); + ERR_FAIL_COND_V(p_line < 0 || p_line >= (int)p_frame->lines.size(), 0); Vector2 off; int line_spacing = get_theme_constant(SNAME("line_separation")); - Line &l = p_frame->lines.write[p_line]; + Line &l = p_frame->lines[p_line]; + MutexLock lock(l.text_buf->get_mutex()); Item *it_from = l.from; - Item *it_to = (p_line + 1 < p_frame->lines.size()) ? p_frame->lines[p_line + 1].from : nullptr; + Item *it_to = (p_line + 1 < (int)p_frame->lines.size()) ? p_frame->lines[p_line + 1].from : nullptr; if (it_from == nullptr) { return 0; @@ -705,9 +714,9 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o bool rtl = (l.text_buf->get_direction() == TextServer::DIRECTION_RTL); bool lrtl = is_layout_rtl(); - bool trim_chars = (visible_characters >= 0) && (visible_chars_behavior == VC_CHARS_AFTER_SHAPING); - bool trim_glyphs_ltr = (visible_characters >= 0) && ((visible_chars_behavior == VC_GLYPHS_LTR) || ((visible_chars_behavior == VC_GLYPHS_AUTO) && !lrtl)); - bool trim_glyphs_rtl = (visible_characters >= 0) && ((visible_chars_behavior == VC_GLYPHS_RTL) || ((visible_chars_behavior == VC_GLYPHS_AUTO) && lrtl)); + bool trim_chars = (visible_characters >= 0) && (visible_chars_behavior == TextServer::VC_CHARS_AFTER_SHAPING); + bool trim_glyphs_ltr = (visible_characters >= 0) && ((visible_chars_behavior == TextServer::VC_GLYPHS_LTR) || ((visible_chars_behavior == TextServer::VC_GLYPHS_AUTO) && !lrtl)); + bool trim_glyphs_rtl = (visible_characters >= 0) && ((visible_chars_behavior == TextServer::VC_GLYPHS_RTL) || ((visible_chars_behavior == TextServer::VC_GLYPHS_AUTO) && lrtl)); int total_glyphs = (trim_glyphs_ltr || trim_glyphs_rtl) ? get_total_glyph_count() : 0; int visible_glyphs = total_glyphs * percent_visible; @@ -835,7 +844,7 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o //draw_rect(Rect2(p_ofs + off, TS->shaped_text_get_size(rid)), Color(1,0,0), false, 2); //DEBUG_RECTS - off.y += TS->shaped_text_get_ascent(rid) + l.text_buf->get_spacing_top(); + off.y += TS->shaped_text_get_ascent(rid); // Draw inlined objects. Array objects = TS->shaped_text_get_objects(rid); for (int i = 0; i < objects.size(); i++) { @@ -853,7 +862,7 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o Color odd_row_bg = get_theme_color(SNAME("table_odd_row_bg")); Color even_row_bg = get_theme_color(SNAME("table_even_row_bg")); Color border = get_theme_color(SNAME("table_border")); - int hseparation = get_theme_constant(SNAME("table_hseparation")); + int hseparation = get_theme_constant(SNAME("table_h_separation")); int col_count = table->columns.size(); int row_count = table->rows.size(); @@ -877,7 +886,7 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o draw_rect(Rect2(p_ofs + rect.position + off + coff - frame->padding.position, Size2(table->columns[col].width + hseparation + frame->padding.position.x + frame->padding.size.x, table->rows[row])), (frame->border != Color(0, 0, 0, 0) ? frame->border : border), false); } - for (int j = 0; j < frame->lines.size(); j++) { + for (int j = 0; j < (int)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_outline_size, p_shadow_ofs, r_processed_glyphs); } idx++; @@ -1241,8 +1250,8 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o // Draw glyphs. for (int j = 0; j < glyphs[i].repeat; j++) { + bool skip = (trim_chars && l.char_offset + glyphs[i].end > visible_characters) || (trim_glyphs_ltr && (r_processed_glyphs >= visible_glyphs)) || (trim_glyphs_rtl && (r_processed_glyphs < total_glyphs - visible_glyphs)); if (visible) { - bool skip = (trim_chars && l.char_offset + glyphs[i].end > visible_characters) || (trim_glyphs_ltr && (r_processed_glyphs >= visible_glyphs)) || (trim_glyphs_rtl && (r_processed_glyphs < total_glyphs - visible_glyphs)); if (!skip) { if (frid != RID()) { TS->font_draw_glyph(frid, ci, glyphs[i].font_size, p_ofs + fx_offset + off, gl, selected ? selection_fg : font_color); @@ -1252,6 +1261,27 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o } r_processed_glyphs++; } + if (skip) { + // End underline/overline/strikethrough is previous glyph is skipped. + if (ul_started) { + ul_started = false; + float y_off = TS->shaped_text_get_underline_position(rid); + float underline_width = TS->shaped_text_get_underline_thickness(rid) * get_theme_default_base_scale(); + draw_line(ul_start + Vector2(0, y_off), p_ofs + Vector2(off.x, off.y + y_off), ul_color, underline_width); + } + if (dot_ul_started) { + dot_ul_started = false; + float y_off = TS->shaped_text_get_underline_position(rid); + float underline_width = TS->shaped_text_get_underline_thickness(rid) * get_theme_default_base_scale(); + draw_dashed_line(dot_ul_start + Vector2(0, y_off), p_ofs + Vector2(off.x, off.y + y_off), dot_ul_color, underline_width, underline_width * 2); + } + if (st_started) { + st_started = false; + float y_off = -TS->shaped_text_get_ascent(rid) + TS->shaped_text_get_size(rid).y / 2; + float underline_width = TS->shaped_text_get_underline_thickness(rid) * get_theme_default_base_scale(); + draw_line(st_start + Vector2(0, y_off), p_ofs + Vector2(off.x, off.y + y_off), st_color, underline_width); + } + } off.x += glyphs[i].advance; } } @@ -1276,7 +1306,7 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o // Draw foreground color box _draw_fbg_boxes(ci, rid, fbg_line_off, it_from, it_to, chr_range.x, chr_range.y, 1); - off.y += TS->shaped_text_get_descent(rid) + l.text_buf->get_spacing_bottom(); + off.y += TS->shaped_text_get_descent(rid); } return line_count; @@ -1299,22 +1329,12 @@ void RichTextLabel::_find_click(ItemFrame *p_frame, const Point2i &p_click, Item int vofs = vscroll->get_value(); // Search for the first line. - int from_line = 0; - - //TODO, change to binary search ? - while (from_line < main->lines.size()) { - if (main->lines[from_line].offset.y + main->lines[from_line].text_buf->get_size().y + main->lines[from_line].text_buf->get_line_count() * get_theme_constant(SNAME("line_separation")) >= vofs) { - break; - } - from_line++; - } - - if (from_line >= main->lines.size()) { - return; - } + int to_line = main->first_invalid_line.load(); + int from_line = _find_first_line(0, to_line, vofs); 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()) { + while (ofs.y < size.height && from_line < to_line) { + MutexLock lock(main->lines[from_line].text_buf->get_mutex()); _find_click_in_line(p_frame, from_line, ofs, text_rect.size.x, p_click, r_click_frame, r_click_line, r_click_item, r_click_char); ofs.y += main->lines[from_line].text_buf->get_size().y + main->lines[from_line].text_buf->get_line_count() * get_theme_constant(SNAME("line_separation")); if (((r_click_item != nullptr) && ((*r_click_item) != nullptr)) || ((r_click_frame != nullptr) && ((*r_click_frame) != nullptr))) { @@ -1330,8 +1350,12 @@ void RichTextLabel::_find_click(ItemFrame *p_frame, const Point2i &p_click, Item float RichTextLabel::_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, int *r_click_line, Item **r_click_item, int *r_click_char, bool p_table) { Vector2 off; + bool line_clicked = false; + float text_rect_begin = 0.0; int char_pos = -1; - Line &l = p_frame->lines.write[p_line]; + Line &l = p_frame->lines[p_line]; + MutexLock lock(l.text_buf->get_mutex()); + bool rtl = (l.text_buf->get_direction() == TextServer::DIRECTION_RTL); bool lrtl = is_layout_rtl(); @@ -1379,7 +1403,7 @@ float RichTextLabel::_find_click_in_line(ItemFrame *p_frame, int p_line, const V } break; } - off.y += TS->shaped_text_get_ascent(rid) + l.text_buf->get_spacing_top(); + off.y += TS->shaped_text_get_ascent(rid); Array objects = TS->shaped_text_get_objects(rid); for (int i = 0; i < objects.size(); i++) { @@ -1390,8 +1414,8 @@ float RichTextLabel::_find_click_in_line(ItemFrame *p_frame, int p_line, const V if (p_click.y >= rect.position.y && p_click.y <= rect.position.y + rect.size.y) { switch (it->type) { case ITEM_TABLE: { - int hseparation = get_theme_constant(SNAME("table_hseparation")); - int vseparation = get_theme_constant(SNAME("table_vseparation")); + int hseparation = get_theme_constant(SNAME("table_h_separation")); + int vseparation = get_theme_constant(SNAME("table_v_separation")); ItemTable *table = static_cast<ItemTable *>(it); @@ -1420,14 +1444,14 @@ float RichTextLabel::_find_click_in_line(ItemFrame *p_frame, int p_line, const V } } if (crect.has_point(p_click)) { - for (int j = 0; j < frame->lines.size(); j++) { + for (int j = 0; j < (int)frame->lines.size(); j++) { _find_click_in_line(frame, j, rect.position + Vector2(0, frame->lines[j].offset.y), rect.size.x, p_click, &table_click_frame, &table_click_line, &table_click_item, &table_click_char, true); if (table_click_frame && table_click_item) { // Save cell detected cell hit data. table_range = Vector2i(INT32_MAX, 0); for (Item *F : table->subitems) { ItemFrame *sub_frame = static_cast<ItemFrame *>(F); - for (int k = 0; k < sub_frame->lines.size(); k++) { + for (int k = 0; k < (int)sub_frame->lines.size(); k++) { table_range.x = MIN(table_range.x, sub_frame->lines[k].char_offset); table_range.y = MAX(table_range.y, sub_frame->lines[k].char_offset + sub_frame->lines[k].char_count); } @@ -1449,11 +1473,15 @@ float RichTextLabel::_find_click_in_line(ItemFrame *p_frame, int p_line, const V } Rect2 rect = Rect2(p_ofs + off - Vector2(0, TS->shaped_text_get_ascent(rid)) - p_frame->padding.position, TS->shaped_text_get_size(rid) + p_frame->padding.position + p_frame->padding.size); if (p_table) { - rect.size.y += get_theme_constant(SNAME("table_vseparation")); + rect.size.y += get_theme_constant(SNAME("table_v_separation")); } if (p_click.y >= rect.position.y && p_click.y <= rect.position.y + rect.size.y) { - char_pos = TS->shaped_text_hit_test_position(rid, p_click.x - rect.position.x); + if ((!rtl && p_click.x >= rect.position.x) || (rtl && p_click.x <= rect.position.x + rect.size.x)) { + char_pos = TS->shaped_text_hit_test_position(rid, p_click.x - rect.position.x); + } + line_clicked = true; + text_rect_begin = rtl ? rect.position.x + rect.size.x : rect.position.x; } // If table hit was detected, and line hit is in the table bounds use table hit. @@ -1476,27 +1504,42 @@ float RichTextLabel::_find_click_in_line(ItemFrame *p_frame, int p_line, const V return table_offy; } - off.y += TS->shaped_text_get_descent(rid) + l.text_buf->get_spacing_bottom() + get_theme_constant(SNAME("line_separation")); + off.y += TS->shaped_text_get_descent(rid) + get_theme_constant(SNAME("line_separation")); } // Text line hit. - if (char_pos >= 0) { + if (line_clicked) { // Find item. if (r_click_item != nullptr) { Item *it = p_frame->lines[p_line].from; - Item *it_to = (p_line + 1 < p_frame->lines.size()) ? p_frame->lines[p_line + 1].from : nullptr; - if (char_pos == p_frame->lines[p_line].char_count) { - // Selection after the end of line, select last item. - if (it_to != nullptr) { - *r_click_item = _get_prev_item(it_to); - } else { - for (Item *i = it; i; i = _get_next_item(i)) { - *r_click_item = i; + Item *it_to = (p_line + 1 < (int)p_frame->lines.size()) ? p_frame->lines[p_line + 1].from : nullptr; + if (char_pos >= 0) { + *r_click_item = _get_item_at_pos(it, it_to, char_pos); + } else { + int stop = text_rect_begin; + *r_click_item = _find_indentable(it); + while (*r_click_item) { + Ref<Font> font = _find_font(*r_click_item); + if (!font.is_valid()) { + font = get_theme_font(SNAME("normal_font")); + } + int font_size = _find_font_size(*r_click_item); + if (font_size == -1) { + font_size = get_theme_font_size(SNAME("normal_font_size")); } + if (rtl) { + stop += tab_size * font->get_char_size(' ', font_size).width; + if (stop > p_click.x) { + break; + } + } else { + stop -= tab_size * font->get_char_size(' ', font_size).width; + if (stop < p_click.x) { + break; + } + } + *r_click_item = _find_indentable((*r_click_item)->parent); } - } else { - // Selection in the line. - *r_click_item = _get_item_at_pos(it, it_to, char_pos); } } @@ -1532,28 +1575,6 @@ void RichTextLabel::_scroll_changed(double) { update(); } -void RichTextLabel::_update_scroll() { - int total_height = get_content_height(); - - bool exceeds = total_height > get_size().height && scroll_active; - - if (exceeds != scroll_visible) { - if (exceeds) { - scroll_visible = true; - scroll_w = vscroll->get_combined_minimum_size().width; - vscroll->show(); - vscroll->set_anchor_and_offset(SIDE_LEFT, ANCHOR_END, -scroll_w); - } else { - scroll_visible = false; - scroll_w = 0; - vscroll->hide(); - } - - main->first_resized_line = 0; //invalidate ALL - _validate_line_caches(main); - } -} - void RichTextLabel::_update_fx(RichTextLabel::ItemFrame *p_frame, double p_delta_time) { Item *it = p_frame; while (it) { @@ -1588,6 +1609,26 @@ void RichTextLabel::_update_fx(RichTextLabel::ItemFrame *p_frame, double p_delta } } +int RichTextLabel::_find_first_line(int p_from, int p_to, int p_vofs) const { + int l = p_from; + int r = p_to; + while (l < r) { + int m = Math::floor(double(l + r) / 2.0); + MutexLock lock(main->lines[m].text_buf->get_mutex()); + int ofs = _calculate_line_vertical_offset(main->lines[m]); + if (ofs < p_vofs) { + l = m + 1; + } else { + r = m; + } + } + return l; +} + +_FORCE_INLINE_ float RichTextLabel::_calculate_line_vertical_offset(const RichTextLabel::Line &line) const { + return line.get_height(get_theme_constant(SNAME("line_separation"))); +} + void RichTextLabel::_notification(int p_what) { switch (p_what) { case NOTIFICATION_MOUSE_EXIT: { @@ -1600,38 +1641,46 @@ void RichTextLabel::_notification(int p_what) { } break; case NOTIFICATION_RESIZED: { - main->first_resized_line = 0; //invalidate ALL + _stop_thread(); + main->first_resized_line.store(0); //invalidate ALL update(); } break; case NOTIFICATION_THEME_CHANGED: { - main->first_invalid_font_line = 0; //invalidate ALL + _stop_thread(); + main->first_invalid_font_line.store(0); //invalidate ALL update(); } break; case NOTIFICATION_ENTER_TREE: { + _stop_thread(); if (!text.is_empty()) { set_text(text); } - main->first_invalid_line = 0; //invalidate ALL + main->first_invalid_line.store(0); //invalidate ALL update(); } break; + case NOTIFICATION_PREDELETE: + case NOTIFICATION_EXIT_TREE: { + _stop_thread(); + } break; + case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: case NOTIFICATION_TRANSLATION_CHANGED: { - main->first_invalid_line = 0; //invalidate ALL + _stop_thread(); + main->first_invalid_line.store(0); //invalidate ALL update(); } break; - case NOTIFICATION_DRAW: { - _validate_line_caches(main); - _update_scroll(); + case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: { + update(); + } break; + case NOTIFICATION_DRAW: { RID ci = get_canvas_item(); - Size2 size = get_size(); - Rect2 text_rect = _get_text_rect(); draw_style_box(get_theme_stylebox(SNAME("normal")), Rect2(Point2(), size)); @@ -1641,22 +1690,42 @@ void RichTextLabel::_notification(int p_what) { RenderingServer::get_singleton()->canvas_item_add_clip_ignore(ci, false); } + // Start text shaping. + if (_validate_line_caches()) { + set_physics_process_internal(false); // Disable auto refresh, if text is fully processed. + } else { + // Draw loading progress bar. + if ((progress_delay > 0) && (OS::get_singleton()->get_ticks_msec() - loading_started >= (uint64_t)progress_delay)) { + Ref<StyleBox> bg = get_theme_stylebox(SNAME("bg"), SNAME("ProgressBar")); + Ref<StyleBox> fg = get_theme_stylebox(SNAME("fg"), SNAME("ProgressBar")); + Ref<StyleBox> style = get_theme_stylebox(SNAME("normal")); + + Vector2 p_size = Vector2(size.width - (style->get_offset().x + vscroll->get_combined_minimum_size().width) * 2, vscroll->get_combined_minimum_size().width); + Vector2 p_pos = Vector2(style->get_offset().x, size.height - style->get_offset().y - vscroll->get_combined_minimum_size().width); + + draw_style_box(bg, Rect2(p_pos, p_size)); + + bool right_to_left = is_layout_rtl(); + double r = loaded.load(); + int mp = fg->get_minimum_size().width; + int p = round(r * (p_size.width - mp)); + if (right_to_left) { + int p_remaining = round((1.0 - r) * (p_size.width - mp)); + draw_style_box(fg, Rect2(p_pos + Point2(p_remaining, 0), Size2(p + fg->get_minimum_size().width, p_size.height))); + } else { + draw_style_box(fg, Rect2(p_pos, Size2(p + fg->get_minimum_size().width, p_size.height))); + } + } + } + + // Draw main text. + Rect2 text_rect = _get_text_rect(); float vofs = vscroll->get_value(); // Search for the first line. - int from_line = 0; - - //TODO, change to binary search ? - while (from_line < main->lines.size()) { - if (main->lines[from_line].offset.y + main->lines[from_line].text_buf->get_size().y + main->lines[from_line].text_buf->get_line_count() * get_theme_constant(SNAME("line_separation")) >= vofs) { - break; - } - from_line++; - } + int to_line = main->first_invalid_line.load(); + int from_line = _find_first_line(0, to_line, vofs); - if (from_line >= main->lines.size()) { - break; //nothing to draw - } Ref<Font> base_font = get_theme_font(SNAME("normal_font")); Color base_color = get_theme_color(SNAME("default_color")); Color outline_color = get_theme_color(SNAME("font_outline_color")); @@ -1671,7 +1740,9 @@ void RichTextLabel::_notification(int p_what) { // New cache draw. Point2 ofs = text_rect.get_position() + Vector2(0, main->lines[from_line].offset.y - vofs); int processed_glyphs = 0; - while (ofs.y < size.height && from_line < main->lines.size()) { + while (ofs.y < size.height && from_line < to_line) { + MutexLock lock(main->lines[from_line].text_buf->get_mutex()); + 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, shadow_outline_size, shadow_ofs, processed_glyphs); ofs.y += main->lines[from_line].text_buf->get_size().y + main->lines[from_line].text_buf->get_line_count() * get_theme_constant(SNAME("line_separation")); @@ -1681,6 +1752,9 @@ void RichTextLabel::_notification(int p_what) { case NOTIFICATION_INTERNAL_PROCESS: { if (is_visible_in_tree()) { + if (!is_ready()) { + return; + } double dt = get_process_delta_time(); _update_fx(main, dt); update(); @@ -1708,18 +1782,6 @@ Control::CursorShape RichTextLabel::get_cursor_shape(const Point2 &p_pos) const return CURSOR_IBEAM; } - if (main->first_invalid_line < main->lines.size()) { - return get_default_cursor_shape(); //invalid - } - - if (main->first_invalid_font_line < main->lines.size()) { - return get_default_cursor_shape(); //invalid - } - - if (main->first_resized_line < main->lines.size()) { - return get_default_cursor_shape(); //invalid - } - Item *item = nullptr; bool outside = true; const_cast<RichTextLabel *>(this)->_find_click(main, p_pos, nullptr, nullptr, &item, nullptr, &outside); @@ -1727,7 +1789,6 @@ Control::CursorShape RichTextLabel::get_cursor_shape(const Point2 &p_pos) const if (item && !outside && const_cast<RichTextLabel *>(this)->_find_meta(item, nullptr)) { return CURSOR_POINTING_HAND; } - return get_default_cursor_shape(); } @@ -1737,16 +1798,6 @@ void RichTextLabel::gui_input(const Ref<InputEvent> &p_event) { Ref<InputEventMouseButton> b = p_event; if (b.is_valid()) { - if (main->first_invalid_line < main->lines.size()) { - return; - } - if (main->first_invalid_font_line < main->lines.size()) { - return; - } - if (main->first_resized_line < main->lines.size()) { - return; - } - if (b->get_button_index() == MouseButton::LEFT) { if (b->is_pressed() && !b->is_double_click()) { scroll_updated = false; @@ -1800,6 +1851,7 @@ void RichTextLabel::gui_input(const Ref<InputEvent> &p_event) { if (c_frame) { const Line &l = c_frame->lines[c_line]; + MutexLock lock(l.text_buf->get_mutex()); PackedInt32Array words = TS->shaped_text_get_word_breaks(l.text_buf->get_rid()); for (int i = 0; i < words.size(); i = i + 2) { if (c_index >= words[i] && c_index < words[i + 1]) { @@ -1841,8 +1893,7 @@ void RichTextLabel::gui_input(const Ref<InputEvent> &p_event) { deselect(); } } - - if (!b->is_double_click() && !scroll_updated) { + if (!b->is_double_click() && !scroll_updated && !selection.active) { Item *c_item = nullptr; bool outside = true; @@ -1945,18 +1996,7 @@ void RichTextLabel::gui_input(const Ref<InputEvent> &p_event) { } Ref<InputEventMouseMotion> m = p_event; - if (m.is_valid()) { - if (main->first_invalid_line < main->lines.size()) { - return; - } - if (main->first_invalid_font_line < main->lines.size()) { - return; - } - if (main->first_resized_line < main->lines.size()) { - return; - } - ItemFrame *c_frame = nullptr; int c_line = 0; Item *c_item = nullptr; @@ -2056,6 +2096,19 @@ void RichTextLabel::_find_frame(Item *p_item, ItemFrame **r_frame, int *r_line) } } +RichTextLabel::Item *RichTextLabel::_find_indentable(Item *p_item) { + Item *indentable = p_item; + + while (indentable) { + if (indentable->type == ITEM_INDENT || indentable->type == ITEM_LIST) { + return indentable; + } + indentable = indentable->parent; + } + + return indentable; +} + Ref<Font> RichTextLabel::_find_font(Item *p_item) { Item *fontitem = p_item; @@ -2101,21 +2154,6 @@ int RichTextLabel::_find_outline_size(Item *p_item, int p_default) { return p_default; } -Dictionary RichTextLabel::_find_font_features(Item *p_item) { - Item *ffitem = p_item; - - while (ffitem) { - if (ffitem->type == ITEM_FONT_FEATURES) { - ItemFontFeatures *fi = static_cast<ItemFontFeatures *>(ffitem); - return fi->opentype_features; - } - - ffitem = ffitem->parent; - } - - return Dictionary(); -} - RichTextLabel::ItemDropcap *RichTextLabel::_find_dc_item(Item *p_item) { Item *item = p_item; @@ -2192,7 +2230,7 @@ int RichTextLabel::_find_margin(Item *p_item, const Ref<Font> &p_base_font, int if (font_size == -1) { font_size = p_base_font_size; } - margin += tab_size * font->get_char_size(' ', 0, font_size).width; + margin += tab_size * font->get_char_size(' ', font_size).width; } else if (item->type == ITEM_LIST) { Ref<Font> font = _find_font(item); @@ -2203,7 +2241,7 @@ int RichTextLabel::_find_margin(Item *p_item, const Ref<Font> &p_base_font, int if (font_size == -1) { font_size = p_base_font_size; } - margin += tab_size * font->get_char_size(' ', 0, font_size).width; + margin += tab_size * font->get_char_size(' ', font_size).width; } item = item->parent; @@ -2248,7 +2286,7 @@ TextServer::Direction RichTextLabel::_find_direction(Item *p_item) { } } -Control::StructuredTextParser RichTextLabel::_find_stt(Item *p_item) { +TextServer::StructuredTextParser RichTextLabel::_find_stt(Item *p_item) { Item *item = p_item; while (item) { @@ -2434,93 +2472,218 @@ bool RichTextLabel::_find_layout_subitem(Item *from, Item *to) { return false; } -void RichTextLabel::_validate_line_caches(ItemFrame *p_frame) { - if (p_frame->first_invalid_line == p_frame->lines.size()) { +void RichTextLabel::_thread_function(void *self) { + RichTextLabel *rtl = reinterpret_cast<RichTextLabel *>(self); + rtl->_process_line_caches(); + rtl->updating.store(false); + rtl->call_deferred(SNAME("update")); +} + +void RichTextLabel::_stop_thread() { + if (threaded) { + stop_thread.store(true); + thread.wait_to_finish(); + } +} + +bool RichTextLabel::is_ready() const { + if (updating.load()) { + return false; + } + return (main->first_invalid_line.load() == (int)main->lines.size() && main->first_resized_line.load() == (int)main->lines.size() && main->first_invalid_font_line.load() == (int)main->lines.size()); +} + +void RichTextLabel::set_threaded(bool p_threaded) { + if (threaded != p_threaded) { + _stop_thread(); + threaded = p_threaded; + update(); + } +} + +bool RichTextLabel::is_threaded() const { + return threaded; +} + +void RichTextLabel::set_progress_bar_delay(int p_delay_ms) { + progress_delay = p_delay_ms; +} + +int RichTextLabel::get_progress_bar_delay() const { + return progress_delay; +} + +bool RichTextLabel::_validate_line_caches() { + if (updating.load()) { + return false; + } + if (main->first_invalid_line.load() == (int)main->lines.size()) { + MutexLock data_lock(data_mutex); + Rect2 text_rect = _get_text_rect(); + Ref<Font> base_font = get_theme_font(SNAME("normal_font")); int base_font_size = get_theme_font_size(SNAME("normal_font_size")); + int ctrl_height = get_size().height; // Update fonts. - if (p_frame->first_invalid_font_line != p_frame->lines.size()) { - for (int i = p_frame->first_invalid_font_line; i < p_frame->lines.size(); i++) { - _update_line_font(p_frame, i, base_font, base_font_size); + if (main->first_invalid_font_line.load() != (int)main->lines.size()) { + for (int i = main->first_invalid_font_line.load(); i < (int)main->lines.size(); i++) { + _update_line_font(main, i, base_font, base_font_size); } - p_frame->first_resized_line = p_frame->first_invalid_font_line; - p_frame->first_invalid_font_line = p_frame->lines.size(); + main->first_resized_line.store(main->first_invalid_font_line.load()); + main->first_invalid_font_line.store(main->lines.size()); } - if (p_frame->first_resized_line == p_frame->lines.size()) { - return; + if (main->first_resized_line.load() == (int)main->lines.size()) { + return true; } // Resize lines without reshaping. - Rect2 text_rect = _get_text_rect(); + int fi = main->first_resized_line.load(); + + float total_height = (fi == 0) ? 0 : _calculate_line_vertical_offset(main->lines[fi - 1]); + for (int i = fi; i < (int)main->lines.size(); i++) { + total_height = _resize_line(main, i, base_font, base_font_size, text_rect.get_size().width - scroll_w, total_height); + + updating_scroll = true; + bool exceeds = total_height > ctrl_height && scroll_active; + if (exceeds != scroll_visible) { + if (exceeds) { + scroll_visible = true; + scroll_w = vscroll->get_combined_minimum_size().width; + vscroll->show(); + vscroll->set_anchor_and_offset(SIDE_LEFT, ANCHOR_END, -scroll_w); + } else { + scroll_visible = false; + scroll_w = 0; + vscroll->hide(); + } - for (int i = p_frame->first_resized_line; i < p_frame->lines.size(); i++) { - _resize_line(p_frame, i, base_font, base_font_size, text_rect.get_size().width - scroll_w); - } + main->first_resized_line.store(0); - int total_height = 0; - if (p_frame->lines.size()) { - total_height = p_frame->lines[p_frame->lines.size() - 1].offset.y + p_frame->lines[p_frame->lines.size() - 1].text_buf->get_size().y + p_frame->lines[p_frame->lines.size() - 1].text_buf->get_line_count() * get_theme_constant(SNAME("line_separation")); - } + total_height = 0; + for (int j = 0; j <= i; j++) { + total_height = _resize_line(main, j, base_font, base_font_size, text_rect.get_size().width - scroll_w, total_height); - p_frame->first_resized_line = p_frame->lines.size(); + main->first_resized_line.store(j); + } + } - updating_scroll = true; - vscroll->set_max(total_height); - vscroll->set_page(text_rect.size.height); - if (scroll_follow && scroll_following) { - vscroll->set_value(total_height); + vscroll->set_max(total_height); + vscroll->set_page(text_rect.size.height); + if (scroll_follow && scroll_following) { + vscroll->set_value(total_height); + } + updating_scroll = false; + + main->first_resized_line.store(i); } - updating_scroll = false; + + main->first_resized_line.store(main->lines.size()); if (fit_content_height) { update_minimum_size(); } - return; + return true; } + stop_thread.store(false); + if (threaded) { + updating.store(true); + loaded.store(true); + thread.start(RichTextLabel::_thread_function, reinterpret_cast<void *>(this)); + loading_started = OS::get_singleton()->get_ticks_msec(); + set_physics_process_internal(true); + return false; + } else { + _process_line_caches(); + update(); + return true; + } +} +void RichTextLabel::_process_line_caches() { // Shape invalid lines. + if (!is_inside_tree()) { + return; + } + + MutexLock data_lock(data_mutex); Rect2 text_rect = _get_text_rect(); - Ref<Font> base_font = get_theme_font(SNAME("normal_font")); int base_font_size = get_theme_font_size(SNAME("normal_font_size")); + Ref<Font> base_font = get_theme_font(SNAME("normal_font")); + int ctrl_height = get_size().height; + int fi = main->first_invalid_line.load(); + int total_chars = (fi == 0) ? 0 : (main->lines[fi].char_offset + main->lines[fi].char_count); - int total_chars = (p_frame->first_invalid_line == 0) ? 0 : (p_frame->lines[p_frame->first_invalid_line].char_offset + p_frame->lines[p_frame->first_invalid_line].char_count); - for (int i = p_frame->first_invalid_line; i < p_frame->lines.size(); i++) { - _shape_line(p_frame, i, base_font, base_font_size, text_rect.get_size().width - scroll_w, &total_chars); - } + float total_height = (fi == 0) ? 0 : _calculate_line_vertical_offset(main->lines[fi - 1]); + for (int i = fi; i < (int)main->lines.size(); i++) { + total_height = _shape_line(main, i, base_font, base_font_size, text_rect.get_size().width - scroll_w, total_height, &total_chars); + updating_scroll = true; + bool exceeds = total_height > ctrl_height && scroll_active; + if (exceeds != scroll_visible) { + if (exceeds) { + scroll_visible = true; + scroll_w = vscroll->get_combined_minimum_size().width; + vscroll->show(); + vscroll->set_anchor_and_offset(SIDE_LEFT, ANCHOR_END, -scroll_w); + } else { + scroll_visible = false; + scroll_w = 0; + vscroll->hide(); + } + main->first_invalid_line.store(0); + main->first_resized_line.store(0); + main->first_invalid_font_line.store(0); - int total_height = 0; - if (p_frame->lines.size()) { - total_height = p_frame->lines[p_frame->lines.size() - 1].offset.y + p_frame->lines[p_frame->lines.size() - 1].text_buf->get_size().y + p_frame->lines[p_frame->lines.size() - 1].text_buf->get_line_count() * get_theme_constant(SNAME("line_separation")); - } + // since scroll was added or removed we need to resize all lines + total_height = 0; + for (int j = 0; j <= i; j++) { + total_height = _resize_line(main, j, base_font, base_font_size, text_rect.get_size().width - scroll_w, total_height); + + main->first_invalid_line.store(j); + main->first_resized_line.store(j); + main->first_invalid_font_line.store(j); + } + } + + vscroll->set_max(total_height); + vscroll->set_page(text_rect.size.height); + if (scroll_follow && scroll_following) { + vscroll->set_value(total_height); + } + updating_scroll = false; - p_frame->first_invalid_line = p_frame->lines.size(); - p_frame->first_resized_line = p_frame->lines.size(); - p_frame->first_invalid_font_line = p_frame->lines.size(); + main->first_invalid_line.store(i); + main->first_resized_line.store(i); + main->first_invalid_font_line.store(i); - updating_scroll = true; - vscroll->set_max(total_height); - vscroll->set_page(text_rect.size.height); - if (scroll_follow && scroll_following) { - vscroll->set_value(total_height); + if (stop_thread.load()) { + return; + } + loaded.store(double(i) / double(main->lines.size())); } - updating_scroll = false; + + main->first_invalid_line.store(main->lines.size()); + main->first_resized_line.store(main->lines.size()); + main->first_invalid_font_line.store(main->lines.size()); if (fit_content_height) { update_minimum_size(); } + emit_signal(SNAME("finished")); } void RichTextLabel::_invalidate_current_line(ItemFrame *p_frame) { - if (p_frame->lines.size() - 1 <= p_frame->first_invalid_line) { - p_frame->first_invalid_line = p_frame->lines.size() - 1; - update(); + if ((int)p_frame->lines.size() - 1 <= p_frame->first_invalid_line) { + p_frame->first_invalid_line = (int)p_frame->lines.size() - 1; } } void RichTextLabel::add_text(const String &p_text) { + _stop_thread(); + MutexLock data_lock(data_mutex); + if (current->type == ITEM_TABLE) { return; //can't add anything here } @@ -2564,13 +2727,14 @@ void RichTextLabel::add_text(const String &p_text) { _add_item(item, false); current_frame->lines.resize(current_frame->lines.size() + 1); if (item->type != ITEM_NEWLINE) { - current_frame->lines.write[current_frame->lines.size() - 1].from = item; + current_frame->lines[current_frame->lines.size() - 1].from = item; } _invalidate_current_line(current_frame); } pos = end + 1; } + update(); } void RichTextLabel::_add_item(Item *p_item, bool p_enter, bool p_ensure_newline) { @@ -2599,7 +2763,7 @@ void RichTextLabel::_add_item(Item *p_item, bool p_enter, bool p_ensure_newline) } if (current_frame->lines[current_frame->lines.size() - 1].from == nullptr) { - current_frame->lines.write[current_frame->lines.size() - 1].from = p_item; + current_frame->lines[current_frame->lines.size() - 1].from = p_item; } p_item->line = current_frame->lines.size() - 1; @@ -2608,6 +2772,7 @@ void RichTextLabel::_add_item(Item *p_item, bool p_enter, bool p_ensure_newline) if (fixed_width != -1) { update_minimum_size(); } + update(); } void RichTextLabel::_remove_item(Item *p_item, const int p_line, const int p_subitem_line) { @@ -2635,6 +2800,9 @@ void RichTextLabel::_remove_item(Item *p_item, const int p_line, const int p_sub } void RichTextLabel::add_image(const Ref<Texture2D> &p_image, const int p_width, const int p_height, const Color &p_color, InlineAlignment p_alignment) { + _stop_thread(); + MutexLock data_lock(data_mutex); + if (current->type == ITEM_TABLE) { return; } @@ -2674,6 +2842,9 @@ void RichTextLabel::add_image(const Ref<Texture2D> &p_image, const int p_width, } void RichTextLabel::add_newline() { + _stop_thread(); + MutexLock data_lock(data_mutex); + if (current->type == ITEM_TABLE) { return; } @@ -2682,10 +2853,14 @@ void RichTextLabel::add_newline() { _add_item(item, false); current_frame->lines.resize(current_frame->lines.size() + 1); _invalidate_current_line(current_frame); + update(); } bool RichTextLabel::remove_line(const int p_line) { - if (p_line >= current_frame->lines.size() || p_line < 0) { + _stop_thread(); + MutexLock data_lock(data_mutex); + + if (p_line >= (int)current_frame->lines.size() || p_line < 0) { return false; } @@ -2713,16 +2888,19 @@ bool RichTextLabel::remove_line(const int p_line) { } if (p_line == 0 && current->subitems.size() > 0) { - main->lines.write[0].from = main; + main->lines[0].from = main; } - main->first_invalid_line = 0; // p_line ??? + main->first_invalid_line.store(0); update(); return true; } void RichTextLabel::push_dropcap(const String &p_string, const Ref<Font> &p_font, int p_size, const Rect2 &p_dropcap_margins, const Color &p_color, int p_ol_size, const Color &p_ol_color) { + _stop_thread(); + MutexLock data_lock(data_mutex); + ERR_FAIL_COND(current->type == ITEM_TABLE); ERR_FAIL_COND(p_string.is_empty()); ERR_FAIL_COND(p_font.is_null()); @@ -2741,6 +2919,9 @@ void RichTextLabel::push_dropcap(const String &p_string, const Ref<Font> &p_font } void RichTextLabel::push_font(const Ref<Font> &p_font) { + _stop_thread(); + MutexLock data_lock(data_mutex); + ERR_FAIL_COND(current->type == ITEM_TABLE); ERR_FAIL_COND(p_font.is_null()); ItemFont *item = memnew(ItemFont); @@ -2785,6 +2966,9 @@ void RichTextLabel::push_mono() { } void RichTextLabel::push_font_size(int p_font_size) { + _stop_thread(); + MutexLock data_lock(data_mutex); + ERR_FAIL_COND(current->type == ITEM_TABLE); ItemFontSize *item = memnew(ItemFontSize); @@ -2792,23 +2976,21 @@ void RichTextLabel::push_font_size(int p_font_size) { _add_item(item, true); } -void RichTextLabel::push_font_features(const Dictionary &p_features) { - ERR_FAIL_COND(current->type == ITEM_TABLE); - ItemFontFeatures *item = memnew(ItemFontFeatures); - - item->opentype_features = p_features; - _add_item(item, true); -} +void RichTextLabel::push_outline_size(int p_ol_size) { + _stop_thread(); + MutexLock data_lock(data_mutex); -void RichTextLabel::push_outline_size(int p_font_size) { ERR_FAIL_COND(current->type == ITEM_TABLE); ItemOutlineSize *item = memnew(ItemOutlineSize); - item->outline_size = p_font_size; + item->outline_size = p_ol_size; _add_item(item, true); } void RichTextLabel::push_color(const Color &p_color) { + _stop_thread(); + MutexLock data_lock(data_mutex); + ERR_FAIL_COND(current->type == ITEM_TABLE); ItemColor *item = memnew(ItemColor); @@ -2817,6 +2999,9 @@ void RichTextLabel::push_color(const Color &p_color) { } void RichTextLabel::push_outline_color(const Color &p_color) { + _stop_thread(); + MutexLock data_lock(data_mutex); + ERR_FAIL_COND(current->type == ITEM_TABLE); ItemOutlineColor *item = memnew(ItemOutlineColor); @@ -2825,6 +3010,9 @@ void RichTextLabel::push_outline_color(const Color &p_color) { } void RichTextLabel::push_underline() { + _stop_thread(); + MutexLock data_lock(data_mutex); + ERR_FAIL_COND(current->type == ITEM_TABLE); ItemUnderline *item = memnew(ItemUnderline); @@ -2832,13 +3020,19 @@ void RichTextLabel::push_underline() { } void RichTextLabel::push_strikethrough() { + _stop_thread(); + MutexLock data_lock(data_mutex); + ERR_FAIL_COND(current->type == ITEM_TABLE); ItemStrikethrough *item = memnew(ItemStrikethrough); _add_item(item, true); } -void RichTextLabel::push_paragraph(HorizontalAlignment p_alignment, Control::TextDirection p_direction, const String &p_language, Control::StructuredTextParser p_st_parser) { +void RichTextLabel::push_paragraph(HorizontalAlignment p_alignment, Control::TextDirection p_direction, const String &p_language, TextServer::StructuredTextParser p_st_parser) { + _stop_thread(); + MutexLock data_lock(data_mutex); + ERR_FAIL_COND(current->type == ITEM_TABLE); ItemParagraph *item = memnew(ItemParagraph); @@ -2850,6 +3044,9 @@ void RichTextLabel::push_paragraph(HorizontalAlignment p_alignment, Control::Tex } void RichTextLabel::push_indent(int p_level) { + _stop_thread(); + MutexLock data_lock(data_mutex); + ERR_FAIL_COND(current->type == ITEM_TABLE); ERR_FAIL_COND(p_level < 0); @@ -2859,6 +3056,9 @@ void RichTextLabel::push_indent(int p_level) { } void RichTextLabel::push_list(int p_level, ListType p_list, bool p_capitalize) { + _stop_thread(); + MutexLock data_lock(data_mutex); + ERR_FAIL_COND(current->type == ITEM_TABLE); ERR_FAIL_COND(p_level < 0); @@ -2871,6 +3071,9 @@ void RichTextLabel::push_list(int p_level, ListType p_list, bool p_capitalize) { } void RichTextLabel::push_meta(const Variant &p_meta) { + _stop_thread(); + MutexLock data_lock(data_mutex); + ERR_FAIL_COND(current->type == ITEM_TABLE); ItemMeta *item = memnew(ItemMeta); @@ -2879,6 +3082,9 @@ void RichTextLabel::push_meta(const Variant &p_meta) { } void RichTextLabel::push_hint(const String &p_string) { + _stop_thread(); + MutexLock data_lock(data_mutex); + ERR_FAIL_COND(current->type == ITEM_TABLE); ItemHint *item = memnew(ItemHint); @@ -2887,20 +3093,26 @@ void RichTextLabel::push_hint(const String &p_string) { } void RichTextLabel::push_table(int p_columns, InlineAlignment p_alignment) { + _stop_thread(); + MutexLock data_lock(data_mutex); + ERR_FAIL_COND(p_columns < 1); ItemTable *item = memnew(ItemTable); item->columns.resize(p_columns); item->total_width = 0; item->inline_align = p_alignment; - for (int i = 0; i < item->columns.size(); i++) { - item->columns.write[i].expand = false; - item->columns.write[i].expand_ratio = 1; + for (int i = 0; i < (int)item->columns.size(); i++) { + item->columns[i].expand = false; + item->columns[i].expand_ratio = 1; } _add_item(item, true, false); } void RichTextLabel::push_fade(int p_start_index, int p_length) { + _stop_thread(); + MutexLock data_lock(data_mutex); + ItemFade *item = memnew(ItemFade); item->starting_index = p_start_index; item->length = p_length; @@ -2908,6 +3120,9 @@ void RichTextLabel::push_fade(int p_start_index, int p_length) { } void RichTextLabel::push_shake(int p_strength = 10, float p_rate = 24.0f) { + _stop_thread(); + MutexLock data_lock(data_mutex); + ItemShake *item = memnew(ItemShake); item->strength = p_strength; item->rate = p_rate; @@ -2915,6 +3130,9 @@ void RichTextLabel::push_shake(int p_strength = 10, float p_rate = 24.0f) { } void RichTextLabel::push_wave(float p_frequency = 1.0f, float p_amplitude = 10.0f) { + _stop_thread(); + MutexLock data_lock(data_mutex); + ItemWave *item = memnew(ItemWave); item->frequency = p_frequency; item->amplitude = p_amplitude; @@ -2922,6 +3140,9 @@ void RichTextLabel::push_wave(float p_frequency = 1.0f, float p_amplitude = 10.0 } void RichTextLabel::push_tornado(float p_frequency = 1.0f, float p_radius = 10.0f) { + _stop_thread(); + MutexLock data_lock(data_mutex); + ItemTornado *item = memnew(ItemTornado); item->frequency = p_frequency; item->radius = p_radius; @@ -2929,6 +3150,9 @@ void RichTextLabel::push_tornado(float p_frequency = 1.0f, float p_radius = 10.0 } void RichTextLabel::push_rainbow(float p_saturation, float p_value, float p_frequency) { + _stop_thread(); + MutexLock data_lock(data_mutex); + ItemRainbow *item = memnew(ItemRainbow); item->frequency = p_frequency; item->saturation = p_saturation; @@ -2937,6 +3161,9 @@ void RichTextLabel::push_rainbow(float p_saturation, float p_value, float p_freq } void RichTextLabel::push_bgcolor(const Color &p_color) { + _stop_thread(); + MutexLock data_lock(data_mutex); + ERR_FAIL_COND(current->type == ITEM_TABLE); ItemBGColor *item = memnew(ItemBGColor); @@ -2945,6 +3172,9 @@ void RichTextLabel::push_bgcolor(const Color &p_color) { } void RichTextLabel::push_fgcolor(const Color &p_color) { + _stop_thread(); + MutexLock data_lock(data_mutex); + ERR_FAIL_COND(current->type == ITEM_TABLE); ItemFGColor *item = memnew(ItemFGColor); @@ -2953,6 +3183,9 @@ void RichTextLabel::push_fgcolor(const Color &p_color) { } void RichTextLabel::push_customfx(Ref<RichTextEffect> p_custom_effect, Dictionary p_environment) { + _stop_thread(); + MutexLock data_lock(data_mutex); + ItemCustomFX *item = memnew(ItemCustomFX); item->custom_effect = p_custom_effect; item->char_fx_transform->environment = p_environment; @@ -2960,15 +3193,23 @@ void RichTextLabel::push_customfx(Ref<RichTextEffect> p_custom_effect, Dictionar } void RichTextLabel::set_table_column_expand(int p_column, bool p_expand, int p_ratio) { + _stop_thread(); + MutexLock data_lock(data_mutex); + ERR_FAIL_COND(current->type != ITEM_TABLE); + ItemTable *table = static_cast<ItemTable *>(current); - ERR_FAIL_INDEX(p_column, table->columns.size()); - table->columns.write[p_column].expand = p_expand; - table->columns.write[p_column].expand_ratio = p_ratio; + ERR_FAIL_INDEX(p_column, (int)table->columns.size()); + table->columns[p_column].expand = p_expand; + table->columns[p_column].expand_ratio = p_ratio; } void RichTextLabel::set_cell_row_background_color(const Color &p_odd_row_bg, const Color &p_even_row_bg) { + _stop_thread(); + MutexLock data_lock(data_mutex); + ERR_FAIL_COND(current->type != ITEM_FRAME); + ItemFrame *cell = static_cast<ItemFrame *>(current); ERR_FAIL_COND(!cell->cell); cell->odd_row_bg = p_odd_row_bg; @@ -2976,14 +3217,22 @@ void RichTextLabel::set_cell_row_background_color(const Color &p_odd_row_bg, con } void RichTextLabel::set_cell_border_color(const Color &p_color) { + _stop_thread(); + MutexLock data_lock(data_mutex); + ERR_FAIL_COND(current->type != ITEM_FRAME); + ItemFrame *cell = static_cast<ItemFrame *>(current); ERR_FAIL_COND(!cell->cell); cell->border = p_color; } void RichTextLabel::set_cell_size_override(const Size2 &p_min_size, const Size2 &p_max_size) { + _stop_thread(); + MutexLock data_lock(data_mutex); + ERR_FAIL_COND(current->type != ITEM_FRAME); + ItemFrame *cell = static_cast<ItemFrame *>(current); ERR_FAIL_COND(!cell->cell); cell->min_size_over = p_min_size; @@ -2991,13 +3240,20 @@ void RichTextLabel::set_cell_size_override(const Size2 &p_min_size, const Size2 } void RichTextLabel::set_cell_padding(const Rect2 &p_padding) { + _stop_thread(); + MutexLock data_lock(data_mutex); + ERR_FAIL_COND(current->type != ITEM_FRAME); + ItemFrame *cell = static_cast<ItemFrame *>(current); ERR_FAIL_COND(!cell->cell); cell->padding = p_padding; } void RichTextLabel::push_cell() { + _stop_thread(); + MutexLock data_lock(data_mutex); + ERR_FAIL_COND(current->type != ITEM_TABLE); ItemFrame *item = memnew(ItemFrame); @@ -3006,20 +3262,23 @@ void RichTextLabel::push_cell() { current_frame = item; item->cell = true; item->lines.resize(1); - item->lines.write[0].from = nullptr; - item->first_invalid_line = 0; // parent frame last line ??? + item->lines[0].from = nullptr; + item->first_invalid_line.store(0); // parent frame last line ??? } int RichTextLabel::get_current_table_column() const { ERR_FAIL_COND_V(current->type != ITEM_TABLE, -1); ItemTable *table = static_cast<ItemTable *>(current); - return table->subitems.size() % table->columns.size(); } void RichTextLabel::pop() { + _stop_thread(); + MutexLock data_lock(data_mutex); + ERR_FAIL_COND(!current->parent); + if (current->type == ITEM_FRAME) { current_frame = static_cast<ItemFrame *>(current)->parent_frame; } @@ -3027,12 +3286,15 @@ void RichTextLabel::pop() { } void RichTextLabel::clear() { + _stop_thread(); + MutexLock data_lock(data_mutex); + main->_clear_children(); current = main; current_frame = main; main->lines.clear(); main->lines.resize(1); - main->first_invalid_line = 0; + main->first_invalid_line.store(0); selection.click_frame = nullptr; selection.click_item = nullptr; @@ -3050,8 +3312,10 @@ void RichTextLabel::clear() { } void RichTextLabel::set_tab_size(int p_spaces) { + _stop_thread(); + tab_size = p_spaces; - main->first_resized_line = 0; + main->first_resized_line.store(0); update(); } @@ -3131,6 +3395,9 @@ void RichTextLabel::parse_bbcode(const String &p_bbcode) { } void RichTextLabel::append_text(const String &p_bbcode) { + _stop_thread(); + MutexLock data_lock(data_mutex); + int pos = 0; List<String> tag_stack; @@ -3146,21 +3413,34 @@ void RichTextLabel::append_text(const String &p_bbcode) { bool in_bold = false; bool in_italics = false; + bool after_list_open_tag = false; + bool after_list_close_tag = false; set_process_internal(false); - while (pos < p_bbcode.length()) { + while (pos <= p_bbcode.length()) { int brk_pos = p_bbcode.find("[", pos); if (brk_pos < 0) { brk_pos = p_bbcode.length(); } - if (brk_pos > pos) { - add_text(p_bbcode.substr(pos, brk_pos - pos)); + String text = brk_pos > pos ? p_bbcode.substr(pos, brk_pos - pos) : ""; + + // Trim the first newline character, it may be added later as needed. + if (after_list_close_tag || after_list_open_tag) { + text = text.trim_prefix("\n"); } if (brk_pos == p_bbcode.length()) { + // For tags that are not properly closed. + if (text.is_empty() && after_list_open_tag) { + text = "\n"; + } + + if (!text.is_empty()) { + add_text(text); + } break; //nothing else to add } @@ -3168,7 +3448,8 @@ void RichTextLabel::append_text(const String &p_bbcode) { if (brk_end == -1) { //no close, add the rest - add_text(p_bbcode.substr(brk_pos, p_bbcode.length() - brk_pos)); + text += p_bbcode.substr(brk_pos, p_bbcode.length() - brk_pos); + add_text(text); break; } @@ -3177,7 +3458,7 @@ void RichTextLabel::append_text(const String &p_bbcode) { // Find optional parameters. String bbcode_name; - typedef Map<String, String> OptionMap; + typedef HashMap<String, String> OptionMap; OptionMap bbcode_options; if (!split_tag_block.is_empty()) { bbcode_name = split_tag_block[0]; @@ -3214,18 +3495,60 @@ void RichTextLabel::append_text(const String &p_bbcode) { } if (!tag_ok) { - add_text("[" + tag); + text += "[" + tag; + add_text(text); + after_list_open_tag = false; + after_list_close_tag = false; pos = brk_end; continue; } + if (text.is_empty() && after_list_open_tag) { + text = "\n"; // Make empty list have at least one item. + } + after_list_open_tag = false; + + if (tag == "/ol" || tag == "/ul") { + if (!text.is_empty()) { + // Make sure text ends with a newline character, that is, the last item + // will wrap at the end of block. + if (!text.ends_with("\n")) { + text += "\n"; + } + } else if (!after_list_close_tag) { + text = "\n"; // Make the innermost list item wrap at the end of lists. + } + after_list_close_tag = true; + } else { + after_list_close_tag = false; + } + + if (!text.is_empty()) { + add_text(text); + } + tag_stack.pop_front(); pos = brk_end + 1; if (tag != "/img" && tag != "/dropcap") { pop(); } + continue; + } - } else if (tag == "b") { + if (tag == "ol" || tag.begins_with("ol ") || tag == "ul" || tag.begins_with("ul ")) { + if (text.is_empty() && after_list_open_tag) { + text = "\n"; // Make each list have at least one item at the beginning. + } + after_list_open_tag = true; + } else { + after_list_open_tag = false; + } + if (!text.is_empty()) { + add_text(text); + } + after_list_close_tag = false; + + if (tag == "b") { //use bold font in_bold = true; if (in_italics) { @@ -3463,7 +3786,7 @@ void RichTextLabel::append_text(const String &p_bbcode) { HorizontalAlignment alignment = HORIZONTAL_ALIGNMENT_LEFT; Control::TextDirection dir = Control::TEXT_DIRECTION_INHERITED; String lang; - Control::StructuredTextParser st_parser = STRUCTURED_TEXT_DEFAULT; + TextServer::StructuredTextParser st_parser = TextServer::STRUCTURED_TEXT_DEFAULT; for (int i = 0; i < subtag.size(); i++) { Vector<String> subtag_a = subtag[i].split("="); if (subtag_a.size() == 2) { @@ -3489,19 +3812,19 @@ void RichTextLabel::append_text(const String &p_bbcode) { lang = subtag_a[1]; } else if (subtag_a[0] == "st" || subtag_a[0] == "bidi_override") { if (subtag_a[1] == "d" || subtag_a[1] == "default") { - st_parser = STRUCTURED_TEXT_DEFAULT; + st_parser = TextServer::STRUCTURED_TEXT_DEFAULT; } else if (subtag_a[1] == "u" || subtag_a[1] == "uri") { - st_parser = STRUCTURED_TEXT_URI; + st_parser = TextServer::STRUCTURED_TEXT_URI; } else if (subtag_a[1] == "f" || subtag_a[1] == "file") { - st_parser = STRUCTURED_TEXT_FILE; + st_parser = TextServer::STRUCTURED_TEXT_FILE; } else if (subtag_a[1] == "e" || subtag_a[1] == "email") { - st_parser = STRUCTURED_TEXT_EMAIL; + st_parser = TextServer::STRUCTURED_TEXT_EMAIL; } else if (subtag_a[1] == "l" || subtag_a[1] == "list") { - st_parser = STRUCTURED_TEXT_LIST; + st_parser = TextServer::STRUCTURED_TEXT_LIST; } else if (subtag_a[1] == "n" || subtag_a[1] == "none") { - st_parser = STRUCTURED_TEXT_NONE; + st_parser = TextServer::STRUCTURED_TEXT_NONE; } else if (subtag_a[1] == "c" || subtag_a[1] == "custom") { - st_parser = STRUCTURED_TEXT_CUSTOM; + st_parser = TextServer::STRUCTURED_TEXT_CUSTOM; } } } @@ -3532,8 +3855,8 @@ void RichTextLabel::append_text(const String &p_bbcode) { tag_stack.push_front("hint"); } else if (tag.begins_with("dropcap")) { Vector<String> subtag = tag.substr(5, tag.length()).split(" "); - Ref<Font> f = get_theme_font(SNAME("normal_font")); int fs = get_theme_font_size(SNAME("normal_font_size")) * 3; + Ref<Font> f = get_theme_font(SNAME("normal_font")); Color color = get_theme_color(SNAME("default_color")); Color outline_color = get_theme_color(SNAME("outline_color")); int outline_size = get_theme_constant(SNAME("outline_size")); @@ -3620,9 +3943,9 @@ void RichTextLabel::append_text(const String &p_bbcode) { Ref<Texture2D> texture = ResourceLoader::load(image, "Texture2D"); if (texture.is_valid()) { Color color = Color(1.0, 1.0, 1.0); - OptionMap::Element *color_option = bbcode_options.find("color"); + OptionMap::Iterator color_option = bbcode_options.find("color"); if (color_option) { - color = Color::from_string(color_option->value(), color); + color = Color::from_string(color_option->value, color); } int width = 0; @@ -3636,14 +3959,14 @@ void RichTextLabel::append_text(const String &p_bbcode) { height = bbcode_value.substr(sep + 1).to_int(); } } else { - OptionMap::Element *width_option = bbcode_options.find("width"); + OptionMap::Iterator width_option = bbcode_options.find("width"); if (width_option) { - width = width_option->value().to_int(); + width = width_option->value.to_int(); } - OptionMap::Element *height_option = bbcode_options.find("height"); + OptionMap::Iterator height_option = bbcode_options.find("height"); if (height_option) { - height = height_option->value().to_int(); + height = height_option->value.to_int(); } } @@ -3666,78 +3989,141 @@ void RichTextLabel::append_text(const String &p_bbcode) { pos = brk_end + 1; tag_stack.push_front("outline_color"); - } else if (tag.begins_with("font=")) { - String fnt = tag.substr(5, tag.length()); - - Ref<Font> font = ResourceLoader::load(fnt, "Font"); - if (font.is_valid()) { - push_font(font); - } else { - push_font(normal_font); - } - - pos = brk_end + 1; - tag_stack.push_front("font"); } else if (tag.begins_with("font_size=")) { int fnt_size = tag.substr(10, tag.length()).to_int(); push_font_size(fnt_size); pos = brk_end + 1; tag_stack.push_front("font_size"); + } else if (tag.begins_with("opentype_features=")) { String fnt_ftr = tag.substr(18, tag.length()); Vector<String> subtag = fnt_ftr.split(","); - Dictionary ftrs; - for (int i = 0; i < subtag.size(); i++) { - Vector<String> subtag_a = subtag[i].split("="); - if (subtag_a.size() == 2) { - ftrs[TS->name_to_tag(subtag_a[0])] = subtag_a[1].to_int(); - } else if (subtag_a.size() == 1) { - ftrs[TS->name_to_tag(subtag_a[0])] = 1; + if (subtag.size() > 0) { + Ref<Font> font = _find_font(current); + if (font.is_null()) { + font = normal_font; } + Ref<FontVariation> fc; + fc.instantiate(); + fc->set_base_font(font); + Dictionary features; + for (int i = 0; i < subtag.size(); i++) { + Vector<String> subtag_a = subtag[i].split("="); + if (subtag_a.size() == 2) { + features[TS->name_to_tag(subtag_a[0])] = subtag_a[1].to_int(); + } else if (subtag_a.size() == 1) { + features[TS->name_to_tag(subtag_a[0])] = 1; + } + } + fc->set_opentype_features(features); + push_font(fc); } - push_font_features(ftrs); pos = brk_end + 1; tag_stack.push_front("opentype_features"); + + } else if (tag.begins_with("font=")) { + String fnt = tag.substr(5, tag.length()); + + Ref<Font> fc = ResourceLoader::load(fnt, "Font"); + if (fc.is_valid()) { + push_font(fc); + } + + pos = brk_end + 1; + tag_stack.push_front("font"); + } else if (tag.begins_with("font ")) { Vector<String> subtag = tag.substr(2, tag.length()).split(" "); + Ref<FontVariation> fc; + fc.instantiate(); for (int i = 1; i < subtag.size(); i++) { Vector<String> subtag_a = subtag[i].split("=", true, 2); if (subtag_a.size() == 2) { if (subtag_a[0] == "name" || subtag_a[0] == "n") { String fnt = subtag_a[1]; - Ref<Font> font = ResourceLoader::load(fnt, "Font"); - if (font.is_valid()) { - push_font(font); - } else { - push_font(normal_font); + Ref<Font> font_data = ResourceLoader::load(fnt, "Font"); + if (font_data.is_valid()) { + fc->set_base_font(font_data); } } else if (subtag_a[0] == "size" || subtag_a[0] == "s") { int fnt_size = subtag_a[1].to_int(); - push_font_size(fnt_size); + if (fnt_size > 0) { + push_font_size(fnt_size); + } + } else if (subtag_a[0] == "glyph_spacing" || subtag_a[0] == "gl") { + int spacing = subtag_a[1].to_int(); + fc->set_spacing(TextServer::SPACING_GLYPH, spacing); + } else if (subtag_a[0] == "space_spacing" || subtag_a[0] == "sp") { + int spacing = subtag_a[1].to_int(); + fc->set_spacing(TextServer::SPACING_SPACE, spacing); + } else if (subtag_a[0] == "top_spacing" || subtag_a[0] == "top") { + int spacing = subtag_a[1].to_int(); + fc->set_spacing(TextServer::SPACING_TOP, spacing); + } else if (subtag_a[0] == "bottom_spacing" || subtag_a[0] == "bt") { + int spacing = subtag_a[1].to_int(); + fc->set_spacing(TextServer::SPACING_BOTTOM, spacing); + } else if (subtag_a[0] == "embolden" || subtag_a[0] == "emb") { + float emb = subtag_a[1].to_float(); + fc->set_variation_embolden(emb); + } else if (subtag_a[0] == "face_index" || subtag_a[0] == "fi") { + int fi = subtag_a[1].to_int(); + fc->set_variation_face_index(fi); + } else if (subtag_a[0] == "slant" || subtag_a[0] == "sln") { + float slant = subtag_a[1].to_float(); + fc->set_variation_transform(Transform2D(1.0, slant, 0.0, 1.0, 0.0, 0.0)); + } else if (subtag_a[0] == "opentype_variation" || subtag_a[0] == "otv") { + Dictionary variations; + if (!subtag_a[1].is_empty()) { + Vector<String> variation_tags = subtag_a[1].split(","); + for (int j = 0; j < variation_tags.size(); j++) { + Vector<String> subtag_b = variation_tags[j].split("="); + if (subtag_b.size() == 2) { + variations[TS->name_to_tag(subtag_b[0])] = subtag_b[1].to_float(); + } + } + fc->set_variation_opentype(variations); + } + } else if (subtag_a[0] == "opentype_features" || subtag_a[0] == "otf") { + Dictionary features; + if (!subtag_a[1].is_empty()) { + Vector<String> feature_tags = subtag_a[1].split(","); + for (int j = 0; j < feature_tags.size(); j++) { + Vector<String> subtag_b = feature_tags[j].split("="); + if (subtag_b.size() == 2) { + features[TS->name_to_tag(subtag_b[0])] = subtag_b[1].to_float(); + } else if (subtag_b.size() == 1) { + features[TS->name_to_tag(subtag_b[0])] = 1; + } + } + fc->set_opentype_features(features); + } } } } - + push_font(fc); pos = brk_end + 1; tag_stack.push_front("font"); + } else if (tag.begins_with("outline_size=")) { int fnt_size = tag.substr(13, tag.length()).to_int(); - push_outline_size(fnt_size); + if (fnt_size > 0) { + push_outline_size(fnt_size); + } pos = brk_end + 1; tag_stack.push_front("outline_size"); } else if (bbcode_name == "fade") { int start_index = 0; - OptionMap::Element *start_option = bbcode_options.find("start"); + OptionMap::Iterator start_option = bbcode_options.find("start"); if (start_option) { - start_index = start_option->value().to_int(); + start_index = start_option->value.to_int(); } int length = 10; - OptionMap::Element *length_option = bbcode_options.find("length"); + OptionMap::Iterator length_option = bbcode_options.find("length"); if (length_option) { - length = length_option->value().to_int(); + length = length_option->value.to_int(); } push_fade(start_index, length); @@ -3745,15 +4131,15 @@ void RichTextLabel::append_text(const String &p_bbcode) { tag_stack.push_front("fade"); } else if (bbcode_name == "shake") { int strength = 5; - OptionMap::Element *strength_option = bbcode_options.find("level"); + OptionMap::Iterator strength_option = bbcode_options.find("level"); if (strength_option) { - strength = strength_option->value().to_int(); + strength = strength_option->value.to_int(); } float rate = 20.0f; - OptionMap::Element *rate_option = bbcode_options.find("rate"); + OptionMap::Iterator rate_option = bbcode_options.find("rate"); if (rate_option) { - rate = rate_option->value().to_float(); + rate = rate_option->value.to_float(); } push_shake(strength, rate); @@ -3762,15 +4148,15 @@ void RichTextLabel::append_text(const String &p_bbcode) { set_process_internal(true); } else if (bbcode_name == "wave") { float amplitude = 20.0f; - OptionMap::Element *amplitude_option = bbcode_options.find("amp"); + OptionMap::Iterator amplitude_option = bbcode_options.find("amp"); if (amplitude_option) { - amplitude = amplitude_option->value().to_float(); + amplitude = amplitude_option->value.to_float(); } float period = 5.0f; - OptionMap::Element *period_option = bbcode_options.find("freq"); + OptionMap::Iterator period_option = bbcode_options.find("freq"); if (period_option) { - period = period_option->value().to_float(); + period = period_option->value.to_float(); } push_wave(period, amplitude); @@ -3779,15 +4165,15 @@ void RichTextLabel::append_text(const String &p_bbcode) { set_process_internal(true); } else if (bbcode_name == "tornado") { float radius = 10.0f; - OptionMap::Element *radius_option = bbcode_options.find("radius"); + OptionMap::Iterator radius_option = bbcode_options.find("radius"); if (radius_option) { - radius = radius_option->value().to_float(); + radius = radius_option->value.to_float(); } float frequency = 1.0f; - OptionMap::Element *frequency_option = bbcode_options.find("freq"); + OptionMap::Iterator frequency_option = bbcode_options.find("freq"); if (frequency_option) { - frequency = frequency_option->value().to_float(); + frequency = frequency_option->value.to_float(); } push_tornado(frequency, radius); @@ -3796,21 +4182,21 @@ void RichTextLabel::append_text(const String &p_bbcode) { set_process_internal(true); } else if (bbcode_name == "rainbow") { float saturation = 0.8f; - OptionMap::Element *saturation_option = bbcode_options.find("sat"); + OptionMap::Iterator saturation_option = bbcode_options.find("sat"); if (saturation_option) { - saturation = saturation_option->value().to_float(); + saturation = saturation_option->value.to_float(); } float value = 0.8f; - OptionMap::Element *value_option = bbcode_options.find("val"); + OptionMap::Iterator value_option = bbcode_options.find("val"); if (value_option) { - value = value_option->value().to_float(); + value = value_option->value.to_float(); } float frequency = 1.0f; - OptionMap::Element *frequency_option = bbcode_options.find("freq"); + OptionMap::Iterator frequency_option = bbcode_options.find("freq"); if (frequency_option) { - frequency = frequency_option->value().to_float(); + frequency = frequency_option->value.to_float(); } push_rainbow(saturation, value, frequency); @@ -3871,9 +4257,13 @@ void RichTextLabel::append_text(const String &p_bbcode) { } void RichTextLabel::scroll_to_paragraph(int p_paragraph) { - ERR_FAIL_INDEX(p_paragraph, main->lines.size()); - _validate_line_caches(main); - vscroll->set_value(main->lines[p_paragraph].offset.y); + if (p_paragraph <= 0) { + vscroll->set_value(0); + } else if (p_paragraph >= main->first_invalid_line.load()) { + vscroll->set_value(vscroll->get_max()); + } else { + vscroll->set_value(main->lines[p_paragraph].offset.y); + } } int RichTextLabel::get_paragraph_count() const { @@ -3888,10 +4278,14 @@ int RichTextLabel::get_visible_paragraph_count() const { } void RichTextLabel::scroll_to_line(int p_line) { - _validate_line_caches(main); - + if (p_line <= 0) { + vscroll->set_value(0); + return; + } int line_count = 0; - for (int i = 0; i < main->lines.size(); i++) { + int to_line = main->first_invalid_line.load(); + for (int i = 0; i < to_line; i++) { + MutexLock lock(main->lines[i].text_buf->get_mutex()); if ((line_count <= p_line) && (line_count + main->lines[i].text_buf->get_line_count() >= p_line)) { float line_offset = 0.f; for (int j = 0; j < p_line - line_count; j++) { @@ -3902,11 +4296,14 @@ void RichTextLabel::scroll_to_line(int p_line) { } line_count += main->lines[i].text_buf->get_line_count(); } + vscroll->set_value(vscroll->get_max()); } float RichTextLabel::get_line_offset(int p_line) { int line_count = 0; - for (int i = 0; i < main->lines.size(); i++) { + int to_line = main->first_invalid_line.load(); + for (int i = 0; i < to_line; i++) { + MutexLock lock(main->lines[i].text_buf->get_mutex()); if ((line_count <= p_line) && (p_line <= line_count + main->lines[i].text_buf->get_line_count())) { float line_offset = 0.f; for (int j = 0; j < p_line - line_count; j++) { @@ -3920,7 +4317,8 @@ float RichTextLabel::get_line_offset(int p_line) { } float RichTextLabel::get_paragraph_offset(int p_paragraph) { - if (0 <= p_paragraph && p_paragraph < main->lines.size()) { + int to_line = main->first_invalid_line.load(); + if (0 <= p_paragraph && p_paragraph < to_line) { return main->lines[p_paragraph].offset.y; } return 0; @@ -3928,7 +4326,9 @@ float RichTextLabel::get_paragraph_offset(int p_paragraph) { int RichTextLabel::get_line_count() const { int line_count = 0; - for (int i = 0; i < main->lines.size(); i++) { + int to_line = main->first_invalid_line.load(); + for (int i = 0; i < to_line; i++) { + MutexLock lock(main->lines[i].text_buf->get_mutex()); line_count += main->lines[i].text_buf->get_line_count(); } return line_count; @@ -3989,13 +4389,13 @@ bool RichTextLabel::_search_table(ItemTable *p_table, List<Item *>::Element *p_f ERR_CONTINUE(E->get()->type != ITEM_FRAME); // Children should all be frames. ItemFrame *frame = static_cast<ItemFrame *>(E->get()); if (p_reverse_search) { - for (int i = frame->lines.size() - 1; i >= 0; i--) { + for (int i = (int)frame->lines.size() - 1; i >= 0; i--) { if (_search_line(frame, i, p_string, -1, p_reverse_search)) { return true; } } } else { - for (int i = 0; i < frame->lines.size(); i++) { + for (int i = 0; i < (int)frame->lines.size(); i++) { if (_search_line(frame, i, p_string, 0, p_reverse_search)) { return true; } @@ -4008,12 +4408,12 @@ bool RichTextLabel::_search_table(ItemTable *p_table, List<Item *>::Element *p_f bool RichTextLabel::_search_line(ItemFrame *p_frame, int p_line, const String &p_string, int p_char_idx, bool p_reverse_search) { ERR_FAIL_COND_V(p_frame == nullptr, false); - ERR_FAIL_COND_V(p_line < 0 || p_line >= p_frame->lines.size(), false); + ERR_FAIL_COND_V(p_line < 0 || p_line >= (int)p_frame->lines.size(), false); - Line &l = p_frame->lines.write[p_line]; + Line &l = p_frame->lines[p_line]; String text; - Item *it_to = (p_line + 1 < p_frame->lines.size()) ? p_frame->lines[p_line + 1].from : nullptr; + Item *it_to = (p_line + 1 < (int)p_frame->lines.size()) ? p_frame->lines[p_line + 1].from : nullptr; for (Item *it = l.from; it && it != it_to; it = _get_next_item(it)) { switch (it->type) { case ITEM_NEWLINE: { @@ -4071,7 +4471,8 @@ bool RichTextLabel::search(const String &p_string, bool p_from_selection, bool p int char_idx = p_search_previous ? -1 : 0; int current_line = 0; - int ending_line = main->lines.size() - 1; + int to_line = main->first_invalid_line.load(); + int ending_line = to_line - 1; if (p_from_selection && selection.active) { // First check to see if other results exist in current line char_idx = p_search_previous ? selection.from_char - 1 : selection.to_char; @@ -4120,8 +4521,8 @@ bool RichTextLabel::search(const String &p_string, bool p_from_selection, bool p while (current_line != ending_line) { // Wrap around if (current_line < 0) { - current_line = main->lines.size() - 1; - } else if (current_line >= main->lines.size()) { + current_line = to_line - 1; + } else if (current_line >= to_line) { current_line = 0; } @@ -4143,12 +4544,13 @@ bool RichTextLabel::search(const String &p_string, bool p_from_selection, bool p String RichTextLabel::_get_line_text(ItemFrame *p_frame, int p_line, Selection p_selection) const { String text; + ERR_FAIL_COND_V(p_frame == nullptr, text); - ERR_FAIL_COND_V(p_line < 0 || p_line >= p_frame->lines.size(), text); + ERR_FAIL_COND_V(p_line < 0 || p_line >= (int)p_frame->lines.size(), text); - Line &l = p_frame->lines.write[p_line]; + Line &l = p_frame->lines[p_line]; - Item *it_to = (p_line + 1 < p_frame->lines.size()) ? p_frame->lines[p_line + 1].from : nullptr; + Item *it_to = (p_line + 1 < (int)p_frame->lines.size()) ? p_frame->lines[p_line + 1].from : nullptr; int end_idx = 0; if (it_to != nullptr) { end_idx = it_to->index; @@ -4163,7 +4565,7 @@ String RichTextLabel::_get_line_text(ItemFrame *p_frame, int p_line, Selection p 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++) { + for (int i = 0; i < (int)frame->lines.size(); i++) { text += _get_line_text(frame, i, p_selection); } } @@ -4227,7 +4629,8 @@ String RichTextLabel::get_selected_text() const { } String text; - for (int i = 0; i < main->lines.size(); i++) { + int to_line = main->first_invalid_line.load(); + for (int i = 0; i < to_line; i++) { text += _get_line_text(main, i, selection); } return text; @@ -4368,32 +4771,40 @@ String RichTextLabel::get_parsed_text() const { void RichTextLabel::set_text_direction(Control::TextDirection p_text_direction) { ERR_FAIL_COND((int)p_text_direction < -1 || (int)p_text_direction > 3); + _stop_thread(); + if (text_direction != p_text_direction) { text_direction = p_text_direction; - main->first_invalid_line = 0; //invalidate ALL - _validate_line_caches(main); + main->first_invalid_line.store(0); //invalidate ALL + _validate_line_caches(); update(); } } -void RichTextLabel::set_structured_text_bidi_override(Control::StructuredTextParser p_parser) { +void RichTextLabel::set_structured_text_bidi_override(TextServer::StructuredTextParser p_parser) { if (st_parser != p_parser) { + _stop_thread(); + st_parser = p_parser; - main->first_invalid_line = 0; //invalidate ALL - _validate_line_caches(main); + main->first_invalid_line.store(0); //invalidate ALL + _validate_line_caches(); update(); } } -Control::StructuredTextParser RichTextLabel::get_structured_text_bidi_override() const { +TextServer::StructuredTextParser RichTextLabel::get_structured_text_bidi_override() const { return st_parser; } void RichTextLabel::set_structured_text_bidi_override_options(Array p_args) { - st_args = p_args; - main->first_invalid_line = 0; //invalidate ALL - _validate_line_caches(main); - update(); + if (st_args != p_args) { + _stop_thread(); + + st_args = p_args; + main->first_invalid_line.store(0); //invalidate ALL + _validate_line_caches(); + update(); + } } Array RichTextLabel::get_structured_text_bidi_override_options() const { @@ -4406,9 +4817,11 @@ Control::TextDirection RichTextLabel::get_text_direction() const { void RichTextLabel::set_language(const String &p_language) { if (language != p_language) { + _stop_thread(); + language = p_language; - main->first_invalid_line = 0; //invalidate ALL - _validate_line_caches(main); + main->first_invalid_line.store(0); //invalidate ALL + _validate_line_caches(); update(); } } @@ -4417,21 +4830,25 @@ String RichTextLabel::get_language() const { return language; } -void RichTextLabel::set_autowrap_mode(RichTextLabel::AutowrapMode p_mode) { +void RichTextLabel::set_autowrap_mode(TextServer::AutowrapMode p_mode) { if (autowrap_mode != p_mode) { + _stop_thread(); + autowrap_mode = p_mode; main->first_invalid_line = 0; //invalidate ALL - _validate_line_caches(main); + _validate_line_caches(); update(); } } -RichTextLabel::AutowrapMode RichTextLabel::get_autowrap_mode() const { +TextServer::AutowrapMode RichTextLabel::get_autowrap_mode() const { return autowrap_mode; } void RichTextLabel::set_percent_visible(float p_percent) { if (percent_visible != p_percent) { + _stop_thread(); + if (p_percent < 0 || p_percent >= 1) { visible_characters = -1; percent_visible = 1; @@ -4439,9 +4856,9 @@ void RichTextLabel::set_percent_visible(float p_percent) { visible_characters = get_total_character_count() * p_percent; percent_visible = p_percent; } - if (visible_chars_behavior == VC_CHARS_BEFORE_SHAPING) { - main->first_invalid_line = 0; //invalidate ALL - _validate_line_caches(main); + if (visible_chars_behavior == TextServer::VC_CHARS_BEFORE_SHAPING) { + main->first_invalid_line.store(0); //invalidate ALL + _validate_line_caches(); } update(); } @@ -4476,15 +4893,19 @@ void RichTextLabel::install_effect(const Variant effect) { int RichTextLabel::get_content_height() const { int total_height = 0; - if (main->lines.size()) { - total_height = main->lines[main->lines.size() - 1].offset.y + main->lines[main->lines.size() - 1].text_buf->get_size().y + main->lines[main->lines.size() - 1].text_buf->get_line_count() * get_theme_constant(SNAME("line_separation")); + int to_line = main->first_invalid_line.load(); + if (to_line) { + MutexLock lock(main->lines[to_line - 1].text_buf->get_mutex()); + total_height = main->lines[to_line - 1].offset.y + main->lines[to_line - 1].text_buf->get_size().y + main->lines[to_line - 1].text_buf->get_line_count() * get_theme_constant(SNAME("line_separation")); } return total_height; } int RichTextLabel::get_content_width() const { int total_width = 0; - for (int i = 0; i < main->lines.size(); i++) { + int to_line = main->first_invalid_line.load(); + for (int i = 0; i < to_line; i++) { + MutexLock lock(main->lines[i].text_buf->get_mutex()); total_width = MAX(total_width, main->lines[i].offset.x + main->lines[i].text_buf->get_size().x); } return total_width; @@ -4511,7 +4932,6 @@ void RichTextLabel::_bind_methods() { ClassDB::bind_method(D_METHOD("remove_line", "line"), &RichTextLabel::remove_line); ClassDB::bind_method(D_METHOD("push_font", "font"), &RichTextLabel::push_font); ClassDB::bind_method(D_METHOD("push_font_size", "font_size"), &RichTextLabel::push_font_size); - ClassDB::bind_method(D_METHOD("push_font_features", "opentype_features"), &RichTextLabel::push_font_features); ClassDB::bind_method(D_METHOD("push_normal"), &RichTextLabel::push_normal); ClassDB::bind_method(D_METHOD("push_bold"), &RichTextLabel::push_bold); ClassDB::bind_method(D_METHOD("push_bold_italics"), &RichTextLabel::push_bold_italics); @@ -4520,7 +4940,7 @@ void RichTextLabel::_bind_methods() { ClassDB::bind_method(D_METHOD("push_color", "color"), &RichTextLabel::push_color); ClassDB::bind_method(D_METHOD("push_outline_size", "outline_size"), &RichTextLabel::push_outline_size); ClassDB::bind_method(D_METHOD("push_outline_color", "color"), &RichTextLabel::push_outline_color); - ClassDB::bind_method(D_METHOD("push_paragraph", "alignment", "base_direction", "language", "st_parser"), &RichTextLabel::push_paragraph, DEFVAL(TextServer::DIRECTION_AUTO), DEFVAL(""), DEFVAL(STRUCTURED_TEXT_DEFAULT)); + ClassDB::bind_method(D_METHOD("push_paragraph", "alignment", "base_direction", "language", "st_parser"), &RichTextLabel::push_paragraph, DEFVAL(TextServer::DIRECTION_AUTO), DEFVAL(""), DEFVAL(TextServer::STRUCTURED_TEXT_DEFAULT)); ClassDB::bind_method(D_METHOD("push_indent", "level"), &RichTextLabel::push_indent); ClassDB::bind_method(D_METHOD("push_list", "level", "type", "capitalize"), &RichTextLabel::push_list); ClassDB::bind_method(D_METHOD("push_meta", "data"), &RichTextLabel::push_meta); @@ -4603,6 +5023,14 @@ void RichTextLabel::_bind_methods() { ClassDB::bind_method(D_METHOD("get_text"), &RichTextLabel::get_text); + ClassDB::bind_method(D_METHOD("is_ready"), &RichTextLabel::is_ready); + + ClassDB::bind_method(D_METHOD("set_threaded", "threaded"), &RichTextLabel::set_threaded); + ClassDB::bind_method(D_METHOD("is_threaded"), &RichTextLabel::is_threaded); + + ClassDB::bind_method(D_METHOD("set_progress_bar_delay", "delay_ms"), &RichTextLabel::set_progress_bar_delay); + ClassDB::bind_method(D_METHOD("get_progress_bar_delay"), &RichTextLabel::get_progress_bar_delay); + ClassDB::bind_method(D_METHOD("set_visible_characters", "amount"), &RichTextLabel::set_visible_characters); ClassDB::bind_method(D_METHOD("get_visible_characters"), &RichTextLabel::get_visible_characters); @@ -4643,6 +5071,9 @@ void RichTextLabel::_bind_methods() { // Note: set "bbcode_enabled" first, to avoid unnecessary "text" resets. ADD_PROPERTY(PropertyInfo(Variant::BOOL, "bbcode_enabled"), "set_use_bbcode", "is_using_bbcode"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "threaded"), "set_threaded", "is_threaded"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "progress_bar_delay", PROPERTY_HINT_NONE, "suffix:ms"), "set_progress_bar_delay", "get_progress_bar_delay"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "tab_size", PROPERTY_HINT_RANGE, "0,24,1"), "set_tab_size", "get_tab_size"); ADD_PROPERTY(PropertyInfo(Variant::STRING, "text", PROPERTY_HINT_MULTILINE_TEXT), "set_text", "get_text"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "fit_content_height"), "set_fit_content_height", "is_fit_content_height_enabled"); @@ -4664,11 +5095,9 @@ void RichTextLabel::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "context_menu_enabled"), "set_context_menu_enabled", "is_context_menu_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "shortcut_keys_enabled"), "set_shortcut_keys_enabled", "is_shortcut_keys_enabled"); - ADD_GROUP("Locale", ""); + ADD_GROUP("BiDi", ""); 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", PROPERTY_HINT_LOCALE_ID, ""), "set_language", "get_language"); - - ADD_GROUP("Structured Text", "structured_text_"); ADD_PROPERTY(PropertyInfo(Variant::INT, "structured_text_bidi_override", PROPERTY_HINT_ENUM, "Default,URI,File,Email,List,None,Custom"), "set_structured_text_bidi_override", "get_structured_text_bidi_override"); ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "structured_text_bidi_override_options"), "set_structured_text_bidi_override_options", "get_structured_text_bidi_override_options"); @@ -4676,10 +5105,7 @@ void RichTextLabel::_bind_methods() { ADD_SIGNAL(MethodInfo("meta_hover_started", PropertyInfo(Variant::NIL, "meta", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NIL_IS_VARIANT))); ADD_SIGNAL(MethodInfo("meta_hover_ended", PropertyInfo(Variant::NIL, "meta", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NIL_IS_VARIANT))); - BIND_ENUM_CONSTANT(AUTOWRAP_OFF); - BIND_ENUM_CONSTANT(AUTOWRAP_ARBITRARY); - BIND_ENUM_CONSTANT(AUTOWRAP_WORD); - BIND_ENUM_CONSTANT(AUTOWRAP_WORD_SMART); + ADD_SIGNAL(MethodInfo("finished")); BIND_ENUM_CONSTANT(LIST_NUMBERS); BIND_ENUM_CONSTANT(LIST_LETTERS); @@ -4713,29 +5139,27 @@ void RichTextLabel::_bind_methods() { BIND_ENUM_CONSTANT(ITEM_HINT); BIND_ENUM_CONSTANT(ITEM_DROPCAP); BIND_ENUM_CONSTANT(ITEM_CUSTOMFX); - - BIND_ENUM_CONSTANT(VC_CHARS_BEFORE_SHAPING); - BIND_ENUM_CONSTANT(VC_CHARS_AFTER_SHAPING); - BIND_ENUM_CONSTANT(VC_GLYPHS_AUTO); - BIND_ENUM_CONSTANT(VC_GLYPHS_LTR); - BIND_ENUM_CONSTANT(VC_GLYPHS_RTL); } -RichTextLabel::VisibleCharactersBehavior RichTextLabel::get_visible_characters_behavior() const { +TextServer::VisibleCharactersBehavior RichTextLabel::get_visible_characters_behavior() const { return visible_chars_behavior; } -void RichTextLabel::set_visible_characters_behavior(RichTextLabel::VisibleCharactersBehavior p_behavior) { +void RichTextLabel::set_visible_characters_behavior(TextServer::VisibleCharactersBehavior p_behavior) { if (visible_chars_behavior != p_behavior) { + _stop_thread(); + visible_chars_behavior = p_behavior; - main->first_invalid_line = 0; //invalidate ALL - _validate_line_caches(main); + main->first_invalid_line.store(0); //invalidate ALL + _validate_line_caches(); update(); } } void RichTextLabel::set_visible_characters(int p_visible) { if (visible_characters != p_visible) { + _stop_thread(); + visible_characters = p_visible; if (p_visible == -1) { percent_visible = 1; @@ -4745,9 +5169,9 @@ void RichTextLabel::set_visible_characters(int p_visible) { percent_visible = (float)p_visible / (float)total_char_count; } } - if (visible_chars_behavior == VC_CHARS_BEFORE_SHAPING) { - main->first_invalid_line = 0; //invalidate ALL - _validate_line_caches(main); + if (visible_chars_behavior == TextServer::VC_CHARS_BEFORE_SHAPING) { + main->first_invalid_line.store(0); //invalidate ALL + _validate_line_caches(); } update(); } @@ -4759,7 +5183,9 @@ int RichTextLabel::get_visible_characters() const { int RichTextLabel::get_character_line(int p_char) { int line_count = 0; - for (int i = 0; i < main->lines.size(); i++) { + int to_line = main->first_invalid_line.load(); + for (int i = 0; i < to_line; i++) { + MutexLock lock(main->lines[i].text_buf->get_mutex()); if (main->lines[i].char_offset < p_char && p_char <= main->lines[i].char_offset + main->lines[i].char_count) { for (int j = 0; j < main->lines[i].text_buf->get_line_count(); j++) { Vector2i range = main->lines[i].text_buf->get_line_range(j); @@ -4777,7 +5203,8 @@ int RichTextLabel::get_character_line(int p_char) { int RichTextLabel::get_character_paragraph(int p_char) { int para_count = 0; - for (int i = 0; i < main->lines.size(); i++) { + int to_line = main->first_invalid_line.load(); + for (int i = 0; i < to_line; i++) { if (main->lines[i].char_offset < p_char && p_char <= main->lines[i].char_offset + main->lines[i].char_count) { return para_count; } else { @@ -4802,7 +5229,6 @@ int RichTextLabel::get_total_character_count() const { } it = _get_next_item(it, true); } - return tc; } @@ -4812,7 +5238,8 @@ int RichTextLabel::get_total_glyph_count() const { while (it) { if (it->type == ITEM_FRAME) { ItemFrame *f = static_cast<ItemFrame *>(it); - for (int i = 0; i < f->lines.size(); i++) { + for (int i = 0; i < (int)f->lines.size(); i++) { + MutexLock lock(f->lines[i].text_buf->get_mutex()); tg += TS->shaped_text_get_glyph_count(f->lines[i].text_buf->get_rid()); } } @@ -4836,7 +5263,6 @@ Size2 RichTextLabel::get_minimum_size() const { } if (fit_content_height) { - const_cast<RichTextLabel *>(this)->_validate_line_caches(main); size.y += get_content_height(); } @@ -5034,10 +5460,10 @@ RichTextLabel::RichTextLabel(const String &p_text) { main->index = 0; current = main; main->lines.resize(1); - main->lines.write[0].from = main; - main->first_invalid_line = 0; - main->first_resized_line = 0; - main->first_invalid_font_line = 0; + main->lines[0].from = main; + main->first_invalid_line.store(0); + main->first_resized_line.store(0); + main->first_invalid_font_line.store(0); current_frame = main; vscroll = memnew(VScrollBar); @@ -5052,10 +5478,13 @@ RichTextLabel::RichTextLabel(const String &p_text) { vscroll->hide(); set_text(p_text); + updating.store(false); + stop_thread.store(false); set_clip_contents(true); } RichTextLabel::~RichTextLabel() { + _stop_thread(); memdelete(main); } diff --git a/scene/gui/rich_text_label.h b/scene/gui/rich_text_label.h index 856dd52b6e..c123f38c01 100644 --- a/scene/gui/rich_text_label.h +++ b/scene/gui/rich_text_label.h @@ -40,13 +40,6 @@ class RichTextLabel : public Control { GDCLASS(RichTextLabel, Control); public: - enum AutowrapMode { - AUTOWRAP_OFF, - AUTOWRAP_ARBITRARY, - AUTOWRAP_WORD, - AUTOWRAP_WORD_SMART - }; - enum ListType { LIST_NUMBERS, LIST_LETTERS, @@ -84,14 +77,6 @@ public: ITEM_CUSTOMFX }; - enum VisibleCharactersBehavior { - VC_CHARS_BEFORE_SHAPING, - VC_CHARS_AFTER_SHAPING, - VC_GLYPHS_AUTO, - VC_GLYPHS_LTR, - VC_GLYPHS_RTL, - }; - enum MenuItems { MENU_COPY, MENU_SELECT_ALL, @@ -117,6 +102,10 @@ private: int char_count = 0; Line() { text_buf.instantiate(); } + + _FORCE_INLINE_ float get_height(float line_separation) const { + return offset.y + text_buf->get_size().y + text_buf->get_line_count() * line_separation; + } }; struct Item { @@ -141,10 +130,10 @@ private: struct ItemFrame : public Item { bool cell = false; - Vector<Line> lines; - int first_invalid_line = 0; - int first_invalid_font_line = 0; - int first_resized_line = 0; + LocalVector<Line> lines; + std::atomic<int> first_invalid_line; + std::atomic<int> first_invalid_font_line; + std::atomic<int> first_resized_line; ItemFrame *parent_frame = nullptr; @@ -155,7 +144,12 @@ private: Size2 max_size_over = Size2(-1, -1); Rect2 padding; - ItemFrame() { type = ITEM_FRAME; } + ItemFrame() { + type = ITEM_FRAME; + first_invalid_line.store(0); + first_invalid_font_line.store(0); + first_resized_line.store(0); + } }; struct ItemText : public Item { @@ -192,11 +186,6 @@ private: ItemFontSize() { type = ITEM_FONT_SIZE; } }; - struct ItemFontFeatures : public Item { - Dictionary opentype_features; - ItemFontFeatures() { type = ITEM_FONT_FEATURES; } - }; - struct ItemColor : public Item { Color color; ItemColor() { type = ITEM_COLOR; } @@ -234,7 +223,7 @@ private: HorizontalAlignment alignment = HORIZONTAL_ALIGNMENT_LEFT; String language; Control::TextDirection direction = Control::TEXT_DIRECTION_AUTO; - Control::StructuredTextParser st_parser = STRUCTURED_TEXT_DEFAULT; + TextServer::StructuredTextParser st_parser = TextServer::STRUCTURED_TEXT_DEFAULT; ItemParagraph() { type = ITEM_PARAGRAPH; } }; @@ -263,8 +252,8 @@ private: int width = 0; }; - Vector<Column> columns; - Vector<float> rows; + LocalVector<Column> columns; + LocalVector<float> rows; int total_width = 0; int total_height = 0; @@ -363,9 +352,19 @@ private: Item *current = nullptr; ItemFrame *current_frame = nullptr; + Thread thread; + Mutex data_mutex; + bool threaded = false; + std::atomic<bool> stop_thread; + std::atomic<bool> updating; + std::atomic<double> loaded; + + uint64_t loading_started = 0; + int progress_delay = 1000; + VScrollBar *vscroll = nullptr; - AutowrapMode autowrap_mode = AUTOWRAP_WORD_SMART; + TextServer::AutowrapMode autowrap_mode = TextServer::AUTOWRAP_WORD_SMART; bool scroll_visible = false; bool scroll_follow = false; @@ -392,14 +391,18 @@ private: Array custom_effects; void _invalidate_current_line(ItemFrame *p_frame); - void _validate_line_caches(ItemFrame *p_frame); + + static void _thread_function(void *self); + void _stop_thread(); + bool _validate_line_caches(); + void _process_line_caches(); void _add_item(Item *p_item, bool p_enter = false, bool p_ensure_newline = false); void _remove_item(Item *p_item, const int p_line, const int p_subitem_line); String language; TextDirection text_direction = TEXT_DIRECTION_AUTO; - Control::StructuredTextParser st_parser = STRUCTURED_TEXT_DEFAULT; + TextServer::StructuredTextParser st_parser = TextServer::STRUCTURED_TEXT_DEFAULT; Array st_args; struct Selection { @@ -437,7 +440,7 @@ private: int visible_characters = -1; float percent_visible = 1.0; - VisibleCharactersBehavior visible_chars_behavior = VC_CHARS_BEFORE_SHAPING; + TextServer::VisibleCharactersBehavior visible_chars_behavior = TextServer::VC_CHARS_BEFORE_SHAPING; bool _is_click_inside_selection() const; void _find_click(ItemFrame *p_frame, const Point2i &p_click, ItemFrame **r_click_frame = nullptr, int *r_click_line = nullptr, Item **r_click_item = nullptr, int *r_click_char = nullptr, bool *r_outside = nullptr); @@ -446,8 +449,9 @@ private: bool _search_line(ItemFrame *p_frame, int p_line, const String &p_string, int p_char_idx, bool p_reverse_search); bool _search_table(ItemTable *p_table, List<Item *>::Element *p_from, const String &p_string, bool p_reverse_search); - 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); + float _shape_line(ItemFrame *p_frame, int p_line, const Ref<Font> &p_base_font, int p_base_font_size, int p_width, float p_h, int *r_char_offset); + float _resize_line(ItemFrame *p_frame, int p_line, const Ref<Font> &p_base_font, int p_base_font_size, int p_width, float p_h); + void _update_line_font(ItemFrame *p_frame, int p_line, const Ref<Font> &p_base_font, int p_base_font_size); 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, int &r_processed_glyphs); 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, bool p_table = false); @@ -455,11 +459,11 @@ private: String _roman(int p_num, bool p_capitalize) const; String _letters(int p_num, bool p_capitalize) const; + Item *_find_indentable(Item *p_item); Item *_get_item_at_pos(Item *p_item_from, Item *p_item_to, int p_position); void _find_frame(Item *p_item, ItemFrame **r_frame, int *r_line); - Ref<Font> _find_font(Item *p_item); int _find_font_size(Item *p_item); - Dictionary _find_font_features(Item *p_item); + Ref<Font> _find_font(Item *p_item); int _find_outline_size(Item *p_item, int p_default); ItemList *_find_list_item(Item *p_item); ItemDropcap *_find_dc_item(Item *p_item); @@ -467,7 +471,7 @@ private: int _find_margin(Item *p_item, const Ref<Font> &p_base_font, int p_base_font_size); HorizontalAlignment _find_alignment(Item *p_item); TextServer::Direction _find_direction(Item *p_item); - Control::StructuredTextParser _find_stt(Item *p_item); + TextServer::StructuredTextParser _find_stt(Item *p_item); String _find_language(Item *p_item); Color _find_color(Item *p_item, const Color &p_default_color); Color _find_outline_color(Item *p_item, const Color &p_default_color); @@ -480,9 +484,11 @@ private: bool _find_layout_subitem(Item *from, Item *to); void _fetch_item_fx_stack(Item *p_item, Vector<ItemFX *> &r_stack); - void _update_scroll(); void _update_fx(ItemFrame *p_frame, double p_delta_time); void _scroll_changed(double); + int _find_first_line(int p_from, int p_to, int p_vofs) const; + + _FORCE_INLINE_ float _calculate_line_vertical_offset(const Line &line) const; virtual void gui_input(const Ref<InputEvent> &p_event) override; virtual String get_tooltip(const Point2 &p_pos) const override; @@ -514,7 +520,6 @@ public: void push_dropcap(const String &p_string, const Ref<Font> &p_font, int p_size, const Rect2 &p_dropcap_margins = Rect2(), const Color &p_color = Color(1, 1, 1), int p_ol_size = 0, const Color &p_ol_color = Color(0, 0, 0, 0)); void push_font(const Ref<Font> &p_font); void push_font_size(int p_font_size); - void push_font_features(const Dictionary &p_features); void push_outline_size(int p_font_size); void push_normal(); void push_bold(); @@ -525,7 +530,7 @@ public: void push_outline_color(const Color &p_color); void push_underline(); void push_strikethrough(); - void push_paragraph(HorizontalAlignment p_alignment, Control::TextDirection p_direction = Control::TEXT_DIRECTION_INHERITED, const String &p_language = "", Control::StructuredTextParser p_st_parser = STRUCTURED_TEXT_DEFAULT); + void push_paragraph(HorizontalAlignment p_alignment, Control::TextDirection p_direction = Control::TEXT_DIRECTION_INHERITED, const String &p_language = "", TextServer::StructuredTextParser p_st_parser = TextServer::STRUCTURED_TEXT_DEFAULT); void push_indent(int p_level); void push_list(int p_level, ListType p_list, bool p_capitalize); void push_meta(const Variant &p_meta); @@ -611,6 +616,14 @@ public: bool is_deselect_on_focus_loss_enabled() const; void deselect(); + bool is_ready() const; + + void set_threaded(bool p_threaded); + bool is_threaded() const; + + void set_progress_bar_delay(int p_delay_ms); + int get_progress_bar_delay() const; + // Context menu. PopupMenu *get_menu() const; bool is_menu_visible() const; @@ -630,11 +643,11 @@ public: void set_language(const String &p_language); String get_language() const; - void set_autowrap_mode(AutowrapMode p_mode); - AutowrapMode get_autowrap_mode() const; + void set_autowrap_mode(TextServer::AutowrapMode p_mode); + TextServer::AutowrapMode get_autowrap_mode() const; - void set_structured_text_bidi_override(Control::StructuredTextParser p_parser); - Control::StructuredTextParser get_structured_text_bidi_override() const; + void set_structured_text_bidi_override(TextServer::StructuredTextParser p_parser); + TextServer::StructuredTextParser get_structured_text_bidi_override() const; void set_structured_text_bidi_override_options(Array p_args); Array get_structured_text_bidi_override_options() const; @@ -649,8 +662,8 @@ public: void set_percent_visible(float p_percent); float get_percent_visible() const; - VisibleCharactersBehavior get_visible_characters_behavior() const; - void set_visible_characters_behavior(VisibleCharactersBehavior p_behavior); + TextServer::VisibleCharactersBehavior get_visible_characters_behavior() const; + void set_visible_characters_behavior(TextServer::VisibleCharactersBehavior p_behavior); void set_effects(Array p_effects); Array get_effects(); @@ -664,9 +677,7 @@ public: ~RichTextLabel(); }; -VARIANT_ENUM_CAST(RichTextLabel::AutowrapMode); VARIANT_ENUM_CAST(RichTextLabel::ListType); VARIANT_ENUM_CAST(RichTextLabel::ItemType); -VARIANT_ENUM_CAST(RichTextLabel::VisibleCharactersBehavior); #endif // RICH_TEXT_LABEL_H diff --git a/scene/gui/scroll_bar.cpp b/scene/gui/scroll_bar.cpp index e1b0e8cca8..f387f91a8b 100644 --- a/scene/gui/scroll_bar.cpp +++ b/scene/gui/scroll_bar.cpp @@ -622,7 +622,7 @@ void ScrollBar::_bind_methods() { ADD_SIGNAL(MethodInfo("scrolling")); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "custom_step", PROPERTY_HINT_RANGE, "-1,4096"), "set_custom_step", "get_custom_step"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "custom_step", PROPERTY_HINT_RANGE, "-1,4096,suffix:px"), "set_custom_step", "get_custom_step"); } ScrollBar::ScrollBar(Orientation p_orientation) { diff --git a/scene/gui/scroll_container.cpp b/scene/gui/scroll_container.cpp index f0f6f8831b..629eec162b 100644 --- a/scene/gui/scroll_container.cpp +++ b/scene/gui/scroll_container.cpp @@ -579,8 +579,8 @@ void ScrollContainer::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "follow_focus"), "set_follow_focus", "is_following_focus"); ADD_GROUP("Scroll", "scroll_"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "scroll_horizontal"), "set_h_scroll", "get_h_scroll"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "scroll_vertical"), "set_v_scroll", "get_v_scroll"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "scroll_horizontal", PROPERTY_HINT_NONE, "suffix:px"), "set_h_scroll", "get_h_scroll"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "scroll_vertical", PROPERTY_HINT_NONE, "suffix:px"), "set_v_scroll", "get_v_scroll"); ADD_PROPERTY(PropertyInfo(Variant::INT, "horizontal_scroll_mode", PROPERTY_HINT_ENUM, "Disabled,Auto,Always Show,Never Show"), "set_horizontal_scroll_mode", "get_horizontal_scroll_mode"); ADD_PROPERTY(PropertyInfo(Variant::INT, "vertical_scroll_mode", PROPERTY_HINT_ENUM, "Disabled,Auto,Always Show,Never Show"), "set_vertical_scroll_mode", "get_vertical_scroll_mode"); ADD_PROPERTY(PropertyInfo(Variant::INT, "scroll_deadzone"), "set_deadzone", "get_deadzone"); diff --git a/scene/gui/slider.cpp b/scene/gui/slider.cpp index 4b680f72cf..64c07007dc 100644 --- a/scene/gui/slider.cpp +++ b/scene/gui/slider.cpp @@ -96,15 +96,15 @@ void Slider::gui_input(const Ref<InputEvent> &p_event) { if (grab.active) { Size2i size = get_size(); Ref<Texture2D> grabber = get_theme_icon(SNAME("grabber")); - float motion = (orientation == VERTICAL ? mm->get_position().y : mm->get_position().x) - grab.pos; + double motion = (orientation == VERTICAL ? mm->get_position().y : mm->get_position().x) - grab.pos; if (orientation == VERTICAL) { motion = -motion; } - float areasize = orientation == VERTICAL ? size.height - grabber->get_size().height : size.width - grabber->get_size().width; + double areasize = orientation == VERTICAL ? size.height - grabber->get_size().height : size.width - grabber->get_size().width; if (areasize <= 0) { return; } - float umotion = motion / float(areasize); + double umotion = motion / double(areasize); set_as_ratio(grab.uvalue + umotion); } } @@ -180,7 +180,7 @@ void Slider::_notification(int p_what) { if (orientation == VERTICAL) { int widget_width = style->get_minimum_size().width + style->get_center_size().width; - float areasize = size.height - grabber->get_size().height; + double areasize = size.height - grabber->get_size().height; style->draw(ci, Rect2i(Point2i(size.width / 2 - widget_width / 2, 0), Size2i(widget_width, size.height))); grabber_area->draw(ci, Rect2i(Point2i((size.width - widget_width) / 2, size.height - areasize * ratio - grabber->get_size().height / 2), Size2i(widget_width, areasize * ratio + grabber->get_size().height / 2))); @@ -197,7 +197,7 @@ void Slider::_notification(int p_what) { grabber->draw(ci, Point2i(size.width / 2 - grabber->get_size().width / 2, size.height - ratio * areasize - grabber->get_size().height)); } else { int widget_height = style->get_minimum_size().height + style->get_center_size().height; - float areasize = size.width - grabber->get_size().width; + double areasize = size.width - grabber->get_size().width; style->draw(ci, Rect2i(Point2i(0, (size.height - widget_height) / 2), Size2i(size.width, widget_height))); grabber_area->draw(ci, Rect2i(Point2i(0, (size.height - widget_height) / 2), Size2i(areasize * ratio + grabber->get_size().width / 2, widget_height))); @@ -218,11 +218,11 @@ void Slider::_notification(int p_what) { } } -void Slider::set_custom_step(float p_custom_step) { +void Slider::set_custom_step(double p_custom_step) { custom_step = p_custom_step; } -float Slider::get_custom_step() const { +double Slider::get_custom_step() const { return custom_step; } diff --git a/scene/gui/slider.h b/scene/gui/slider.h index 5fbfee2aec..5abaee27aa 100644 --- a/scene/gui/slider.h +++ b/scene/gui/slider.h @@ -38,14 +38,14 @@ class Slider : public Range { struct Grab { int pos = 0; - float uvalue = 0.0; + double uvalue = 0.0; bool active = false; } grab; int ticks = 0; bool mouse_inside = false; Orientation orientation; - float custom_step = -1.0; + double custom_step = -1.0; bool editable = true; bool scrollable = true; @@ -58,8 +58,8 @@ protected: public: virtual Size2 get_minimum_size() const override; - void set_custom_step(float p_custom_step); - float get_custom_step() const; + void set_custom_step(double p_custom_step); + double get_custom_step() const; void set_ticks(int p_count); int get_ticks() const; diff --git a/scene/gui/spin_box.cpp b/scene/gui/spin_box.cpp index e50d7e765c..a4733c455f 100644 --- a/scene/gui/spin_box.cpp +++ b/scene/gui/spin_box.cpp @@ -62,7 +62,7 @@ void SpinBox::_text_submitted(const String &p_string) { return; } - Variant value = expr->execute(Array(), nullptr, false); + Variant value = expr->execute(Array(), nullptr, false, true); if (value.get_type() != Variant::NIL) { set_value(value); } @@ -167,7 +167,7 @@ void SpinBox::gui_input(const Ref<InputEvent> &p_event) { 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) * SIGN(drag.diff_y); + double diff_y = -0.01 * Math::pow(ABS(drag.diff_y), 1.8) * 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); @@ -319,7 +319,7 @@ SpinBox::SpinBox() { line_edit = memnew(LineEdit); add_child(line_edit, false, INTERNAL_MODE_FRONT); - line_edit->set_anchors_and_offsets_preset(Control::PRESET_WIDE); + line_edit->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT); line_edit->set_mouse_filter(MOUSE_FILTER_PASS); line_edit->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_LEFT); diff --git a/scene/gui/spin_box.h b/scene/gui/spin_box.h index d118b28334..1b1abbcf8e 100644 --- a/scene/gui/spin_box.h +++ b/scene/gui/spin_box.h @@ -56,11 +56,11 @@ class SpinBox : public Range { void _line_edit_input(const Ref<InputEvent> &p_event); struct Drag { - float base_val = 0.0; + double base_val = 0.0; bool allowed = false; bool enabled = false; Vector2 capture_pos; - float diff_y = 0.0; + double diff_y = 0.0; } drag; void _line_edit_focus_exit(); diff --git a/scene/gui/split_container.cpp b/scene/gui/split_container.cpp index 6845d46721..d7aa516ee6 100644 --- a/scene/gui/split_container.cpp +++ b/scene/gui/split_container.cpp @@ -377,7 +377,7 @@ void SplitContainer::_bind_methods() { ADD_SIGNAL(MethodInfo("dragged", PropertyInfo(Variant::INT, "offset"))); - ADD_PROPERTY(PropertyInfo(Variant::INT, "split_offset"), "set_split_offset", "get_split_offset"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "split_offset", PROPERTY_HINT_NONE, "suffix:px"), "set_split_offset", "get_split_offset"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "collapsed"), "set_collapsed", "is_collapsed"); ADD_PROPERTY(PropertyInfo(Variant::INT, "dragger_visibility", PROPERTY_HINT_ENUM, "Visible,Hidden,Hidden and Collapsed"), "set_dragger_visibility", "get_dragger_visibility"); diff --git a/scene/gui/tab_bar.cpp b/scene/gui/tab_bar.cpp index ce2dca0ea3..d36a364677 100644 --- a/scene/gui/tab_bar.cpp +++ b/scene/gui/tab_bar.cpp @@ -49,7 +49,7 @@ Size2 TabBar::get_minimum_size() const { Ref<StyleBox> tab_disabled = get_theme_stylebox(SNAME("tab_disabled")); Ref<StyleBox> button_highlight = get_theme_stylebox(SNAME("button_highlight")); Ref<Texture2D> close = get_theme_icon(SNAME("close")); - int hseparation = get_theme_constant(SNAME("hseparation")); + int hseparation = get_theme_constant(SNAME("h_separation")); int y_margin = MAX(MAX(tab_unselected->get_minimum_size().height, tab_selected->get_minimum_size().height), tab_disabled->get_minimum_size().height); @@ -311,7 +311,7 @@ void TabBar::_shape(int p_tab) { tabs.write[p_tab].text_buf->set_direction((TextServer::Direction)tabs[p_tab].text_direction); } - tabs.write[p_tab].text_buf->add_string(tabs[p_tab].xl_text, font, font_size, tabs[p_tab].opentype_features, !tabs[p_tab].language.is_empty() ? tabs[p_tab].language : TranslationServer::get_singleton()->get_tool_locale()); + tabs.write[p_tab].text_buf->add_string(tabs[p_tab].xl_text, font, font_size, tabs[p_tab].language); } void TabBar::_notification(int p_what) { @@ -477,7 +477,7 @@ void TabBar::_draw_tab(Ref<StyleBox> &p_tab_style, Color &p_font_color, int p_in Color font_outline_color = get_theme_color(SNAME("font_outline_color")); int outline_size = get_theme_constant(SNAME("outline_size")); - int hseparation = get_theme_constant(SNAME("hseparation")); + int hseparation = get_theme_constant(SNAME("h_separation")); Rect2 sb_rect = Rect2(p_x, 0, tabs[p_index].size_cache, get_size().height); p_tab_style->draw(ci, sb_rect); @@ -667,48 +667,6 @@ Control::TextDirection TabBar::get_tab_text_direction(int p_tab) const { return tabs[p_tab].text_direction; } -void TabBar::clear_tab_opentype_features(int p_tab) { - ERR_FAIL_INDEX(p_tab, tabs.size()); - tabs.write[p_tab].opentype_features.clear(); - - _shape(p_tab); - _update_cache(); - _ensure_no_over_offset(); - if (scroll_to_selected) { - ensure_tab_visible(current); - } - update(); - update_minimum_size(); -} - -void TabBar::set_tab_opentype_feature(int p_tab, const String &p_name, int p_value) { - ERR_FAIL_INDEX(p_tab, tabs.size()); - - int32_t tag = TS->name_to_tag(p_name); - if (!tabs[p_tab].opentype_features.has(tag) || (int)tabs[p_tab].opentype_features[tag] != p_value) { - tabs.write[p_tab].opentype_features[tag] = p_value; - - _shape(p_tab); - _update_cache(); - _ensure_no_over_offset(); - if (scroll_to_selected) { - ensure_tab_visible(current); - } - update(); - update_minimum_size(); - } -} - -int TabBar::get_tab_opentype_feature(int p_tab, const String &p_name) const { - ERR_FAIL_INDEX_V(p_tab, tabs.size(), -1); - - int32_t tag = TS->name_to_tag(p_name); - if (!tabs[p_tab].opentype_features.has(tag)) { - return -1; - } - return tabs[p_tab].opentype_features[tag]; -} - void TabBar::set_tab_language(int p_tab, const String &p_language) { ERR_FAIL_INDEX(p_tab, tabs.size()); @@ -862,6 +820,7 @@ void TabBar::_update_hover() { void TabBar::_update_cache() { if (tabs.is_empty()) { + buttons_visible = false; return; } @@ -1272,7 +1231,7 @@ int TabBar::get_tab_width(int p_idx) const { Ref<StyleBox> tab_unselected = get_theme_stylebox(SNAME("tab_unselected")); Ref<StyleBox> tab_selected = get_theme_stylebox(SNAME("tab_selected")); Ref<StyleBox> tab_disabled = get_theme_stylebox(SNAME("tab_disabled")); - int hseparation = get_theme_constant(SNAME("hseparation")); + int hseparation = get_theme_constant(SNAME("h_separation")); Ref<StyleBox> style; @@ -1552,9 +1511,6 @@ void TabBar::_bind_methods() { ClassDB::bind_method(D_METHOD("get_tab_title", "tab_idx"), &TabBar::get_tab_title); ClassDB::bind_method(D_METHOD("set_tab_text_direction", "tab_idx", "direction"), &TabBar::set_tab_text_direction); ClassDB::bind_method(D_METHOD("get_tab_text_direction", "tab_idx"), &TabBar::get_tab_text_direction); - ClassDB::bind_method(D_METHOD("set_tab_opentype_feature", "tab_idx", "tag", "values"), &TabBar::set_tab_opentype_feature); - ClassDB::bind_method(D_METHOD("get_tab_opentype_feature", "tab_idx", "tag"), &TabBar::get_tab_opentype_feature); - ClassDB::bind_method(D_METHOD("clear_tab_opentype_features", "tab_idx"), &TabBar::clear_tab_opentype_features); ClassDB::bind_method(D_METHOD("set_tab_language", "tab_idx", "language"), &TabBar::set_tab_language); ClassDB::bind_method(D_METHOD("get_tab_language", "tab_idx"), &TabBar::get_tab_language); ClassDB::bind_method(D_METHOD("set_tab_icon", "tab_idx", "icon"), &TabBar::set_tab_icon); @@ -1605,7 +1561,7 @@ void TabBar::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::INT, "tab_alignment", PROPERTY_HINT_ENUM, "Left,Center,Right"), "set_tab_alignment", "get_tab_alignment"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "clip_tabs"), "set_clip_tabs", "get_clip_tabs"); ADD_PROPERTY(PropertyInfo(Variant::INT, "tab_close_display_policy", PROPERTY_HINT_ENUM, "Show Never,Show Active Only,Show Always"), "set_tab_close_display_policy", "get_tab_close_display_policy"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "max_tab_width", PROPERTY_HINT_RANGE, "0,99999,1"), "set_max_tab_width", "get_max_tab_width"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "max_tab_width", PROPERTY_HINT_RANGE, "0,99999,1,suffix:px"), "set_max_tab_width", "get_max_tab_width"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "scrolling_enabled"), "set_scrolling_enabled", "get_scrolling_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "drag_to_rearrange_enabled"), "set_drag_to_rearrange_enabled", "get_drag_to_rearrange_enabled"); ADD_PROPERTY(PropertyInfo(Variant::INT, "tabs_rearrange_group"), "set_tabs_rearrange_group", "get_tabs_rearrange_group"); diff --git a/scene/gui/tab_bar.h b/scene/gui/tab_bar.h index 548a2e62af..d123385e47 100644 --- a/scene/gui/tab_bar.h +++ b/scene/gui/tab_bar.h @@ -57,7 +57,6 @@ private: String text; String xl_text; - Dictionary opentype_features; String language; Control::TextDirection text_direction = Control::TEXT_DIRECTION_INHERITED; @@ -137,10 +136,6 @@ public: void set_tab_text_direction(int p_tab, TextDirection p_text_direction); TextDirection get_tab_text_direction(int p_tab) const; - void set_tab_opentype_feature(int p_tab, const String &p_name, int p_value); - int get_tab_opentype_feature(int p_tab, const String &p_name) const; - void clear_tab_opentype_features(int p_tab); - void set_tab_language(int p_tab, const String &p_language); String get_tab_language(int p_tab) const; diff --git a/scene/gui/tab_container.cpp b/scene/gui/tab_container.cpp index 84a62c71c2..12f91a9873 100644 --- a/scene/gui/tab_container.cpp +++ b/scene/gui/tab_container.cpp @@ -208,8 +208,8 @@ void TabContainer::_on_theme_changed() { tab_bar->add_theme_color_override(SNAME("font_disabled_color"), get_theme_color(SNAME("font_disabled_color"))); tab_bar->add_theme_color_override(SNAME("font_outline_color"), get_theme_color(SNAME("font_outline_color"))); tab_bar->add_theme_font_override(SNAME("font"), get_theme_font(SNAME("font"))); - tab_bar->add_theme_constant_override(SNAME("font_size"), get_theme_constant(SNAME("font_size"))); - tab_bar->add_theme_constant_override(SNAME("hseparation"), get_theme_constant(SNAME("icon_separation"))); + tab_bar->add_theme_font_size_override(SNAME("font_size"), get_theme_font_size(SNAME("font_size"))); + tab_bar->add_theme_constant_override(SNAME("h_separation"), get_theme_constant(SNAME("icon_separation"))); tab_bar->add_theme_constant_override(SNAME("outline_size"), get_theme_constant(SNAME("outline_size"))); _update_margins(); @@ -233,7 +233,7 @@ void TabContainer::_repaint() { if (i == current) { c->show(); - c->set_anchors_and_offsets_preset(Control::PRESET_WIDE); + c->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT); if (tabs_visible) { c->set_offset(SIDE_TOP, _get_top_margin()); @@ -312,7 +312,7 @@ Vector<Control *> TabContainer::_get_tab_controls() const { Vector<Control *> controls; for (int i = 0; i < get_child_count(); i++) { Control *control = Object::cast_to<Control>(get_child(i)); - if (!control || control->is_set_as_top_level() || control == tab_bar) { + if (!control || control->is_set_as_top_level() || control == tab_bar || control == child_removing) { continue; } @@ -523,7 +523,7 @@ void TabContainer::move_child_notify(Node *p_child) { Control *c = Object::cast_to<Control>(p_child); if (c && !c->is_set_as_top_level()) { int old_idx = -1; - String tab_name = c->has_meta("_tab_name") ? String(c->get_meta("_tab_name")) : String(c->get_name()); + String tab_name = String(c->get_meta("_tab_name", c->get_name())); // Find the previous tab index of the control. for (int i = 0; i < get_tab_count(); i++) { @@ -549,16 +549,19 @@ void TabContainer::remove_child_notify(Node *p_child) { return; } - tab_bar->remove_tab(get_tab_idx_from_control(c)); + int idx = get_tab_idx_from_control(c); + + // Before this, the tab control has not changed; after this, the tab control has changed. + child_removing = p_child; + tab_bar->remove_tab(idx); + child_removing = nullptr; _update_margins(); if (get_tab_count() == 0) { update(); } - if (p_child->has_meta("_tab_name")) { - p_child->remove_meta("_tab_name"); - } + p_child->remove_meta("_tab_name"); p_child->disconnect("renamed", callable_mp(this, &TabContainer::_refresh_tab_names)); // TabBar won't emit the "tab_changed" signal when not inside the tree. @@ -679,9 +682,7 @@ void TabContainer::set_tab_title(int p_tab, const String &p_title) { tab_bar->set_tab_title(p_tab, p_title); if (p_title == child->get_name()) { - if (child->has_meta("_tab_name")) { - child->remove_meta("_tab_name"); - } + child->remove_meta("_tab_name"); } else { child->set_meta("_tab_name", p_title); } @@ -731,6 +732,7 @@ void TabContainer::set_tab_hidden(int p_tab, bool p_hidden) { if (!get_clip_tabs()) { update_minimum_size(); } + call_deferred(SNAME("_repaint")); } bool TabContainer::is_tab_hidden(int p_tab) const { diff --git a/scene/gui/tab_container.h b/scene/gui/tab_container.h index 9adaa0d844..60c8130939 100644 --- a/scene/gui/tab_container.h +++ b/scene/gui/tab_container.h @@ -46,6 +46,7 @@ class TabContainer : public Container { bool drag_to_rearrange_enabled = false; bool use_hidden_tabs_for_min_size = false; bool theme_changing = false; + Node *child_removing = nullptr; int _get_top_margin() const; Vector<Control *> _get_tab_controls() const; diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp index ff23e44cb7..06553cd0f6 100644 --- a/scene/gui/text_edit.cpp +++ b/scene/gui/text_edit.cpp @@ -75,14 +75,6 @@ int TextEdit::Text::get_tab_size() const { return tab_size; } -void TextEdit::Text::set_font_features(const Dictionary &p_features) { - if (opentype_features.hash() == p_features.hash()) { - return; - } - opentype_features = p_features; - is_dirty = true; -} - void TextEdit::Text::set_direction_and_language(TextServer::Direction p_direction, const String &p_language) { if (direction == p_direction && language == p_language) { return; @@ -178,7 +170,7 @@ void TextEdit::Text::_calculate_max_line_width() { void TextEdit::Text::invalidate_cache(int p_line, int p_column, bool p_text_changed, const String &p_ime_text, const Array &p_bidi_override) { ERR_FAIL_INDEX(p_line, text.size()); - if (font.is_null() || font_size <= 0) { + if (font.is_null()) { return; // Not in tree? } @@ -191,14 +183,14 @@ void TextEdit::Text::invalidate_cache(int p_line, int p_column, bool p_text_chan text.write[p_line].data_buf->set_preserve_control(draw_control_chars); if (p_ime_text.length() > 0) { if (p_text_changed) { - text.write[p_line].data_buf->add_string(p_ime_text, font, font_size, opentype_features, language); + text.write[p_line].data_buf->add_string(p_ime_text, font, font_size, language); } if (!p_bidi_override.is_empty()) { TS->shaped_text_set_bidi_override(text.write[p_line].data_buf->get_rid(), p_bidi_override); } } else { if (p_text_changed) { - text.write[p_line].data_buf->add_string(text[p_line].data, font, font_size, opentype_features, language); + text.write[p_line].data_buf->add_string(text[p_line].data, font, font_size, language); } if (!text[p_line].bidi_override.is_empty()) { TS->shaped_text_set_bidi_override(text.write[p_line].data_buf->get_rid(), text[p_line].bidi_override); @@ -209,14 +201,17 @@ void TextEdit::Text::invalidate_cache(int p_line, int p_column, bool p_text_chan RID r = text.write[p_line].data_buf->get_rid(); int spans = TS->shaped_get_span_count(r); for (int i = 0; i < spans; i++) { - TS->shaped_set_span_update_font(r, i, font->get_rids(), font_size, opentype_features); + TS->shaped_set_span_update_font(r, i, font->get_rids(), font_size, font->get_opentype_features()); + } + for (int i = 0; i < TextServer::SPACING_MAX; i++) { + TS->shaped_text_set_spacing(r, TextServer::SpacingType(i), font->get_spacing(TextServer::SpacingType(i))); } } // Apply tab align. if (tab_size > 0) { Vector<float> tabs; - tabs.push_back(font->get_char_size(' ', 0, font_size).width * tab_size); + tabs.push_back(font->get_char_size(' ', font_size).width * tab_size); text.write[p_line].data_buf->tab_align(tabs); } @@ -255,7 +250,7 @@ void TextEdit::Text::invalidate_all_lines() { if (tab_size_dirty) { if (tab_size > 0) { Vector<float> tabs; - tabs.push_back(font->get_char_size(' ', 0, font_size).width * tab_size); + tabs.push_back(font->get_char_size(' ', font_size).width * tab_size); text.write[i].data_buf->tab_align(tabs); } // Tabs have changes, force width update. @@ -277,7 +272,7 @@ void TextEdit::Text::invalidate_font() { max_width = -1; line_height = -1; - if (!font.is_null() && font_size > 0) { + if (font.is_valid() && font_size > 0) { font_height = font->get_height(font_size); } @@ -295,7 +290,7 @@ void TextEdit::Text::invalidate_all() { max_width = -1; line_height = -1; - if (!font.is_null() && font_size > 0) { + if (font.is_valid() && font_size > 0) { font_height = font->get_height(font_size); } @@ -973,7 +968,7 @@ void TextEdit::_notification(int p_what) { // Give visual indication of empty selected line. if (selection.active && line >= selection.from_line && line <= selection.to_line && char_margin >= xmargin_beg) { - float char_w = font->get_char_size(' ', 0, font_size).width; + float char_w = font->get_char_size(' ', font_size).width; if (rtl) { RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(size.width - xmargin_beg - ofs_x - char_w, ofs_y, char_w, row_height), selection_color); } else { @@ -1071,7 +1066,7 @@ void TextEdit::_notification(int p_what) { // Draw line. RID rid = ldata->get_line_rid(line_wrap_index); - float text_height = TS->shaped_text_get_size(rid).y + font->get_spacing(TextServer::SPACING_TOP) + font->get_spacing(TextServer::SPACING_BOTTOM); + float text_height = TS->shaped_text_get_size(rid).y; if (rtl) { char_margin = size.width - char_margin - TS->shaped_text_get_size(rid).x; @@ -1222,7 +1217,7 @@ void TextEdit::_notification(int p_what) { if (brace_open_mismatch) { current_color = brace_mismatch_color; } - Rect2 rect = Rect2(char_pos, ofs_y + font->get_underline_position(font_size), glyphs[j].advance * glyphs[j].repeat, font->get_underline_thickness(font_size)); + Rect2 rect = Rect2(char_pos, ofs_y + font->get_underline_position(font_size), glyphs[j].advance * glyphs[j].repeat, MAX(font->get_underline_thickness(font_size) * get_theme_default_base_scale(), 1)); draw_rect(rect, current_color); } @@ -1231,7 +1226,7 @@ void TextEdit::_notification(int p_what) { if (brace_close_mismatch) { current_color = brace_mismatch_color; } - Rect2 rect = Rect2(char_pos, ofs_y + font->get_underline_position(font_size), glyphs[j].advance * glyphs[j].repeat, font->get_underline_thickness(font_size)); + Rect2 rect = Rect2(char_pos, ofs_y + font->get_underline_position(font_size), glyphs[j].advance * glyphs[j].repeat, MAX(font->get_underline_thickness(font_size) * get_theme_default_base_scale(), 1)); draw_rect(rect, current_color); } } @@ -1355,7 +1350,7 @@ void TextEdit::_notification(int p_what) { ts_caret.l_caret.size.y = caret_width; } if (ts_caret.l_caret.position.x >= TS->shaped_text_get_size(rid).x) { - ts_caret.l_caret.size.x = font->get_char_size('m', 0, font_size).x; + ts_caret.l_caret.size.x = font->get_char_size('m', font_size).x; } else { ts_caret.l_caret.size.x = 3 * caret_width; } @@ -1428,7 +1423,7 @@ void TextEdit::_notification(int p_what) { } } - if (draw_placeholder) { + if (!draw_placeholder) { line_drawing_cache[line] = cache_entry; } } @@ -1557,6 +1552,47 @@ void TextEdit::unhandled_key_input(const Ref<InputEvent> &p_event) { } } +bool TextEdit::alt_input(const Ref<InputEvent> &p_gui_input) { + Ref<InputEventKey> k = p_gui_input; + if (k.is_valid()) { + if (!k->is_pressed()) { + if (alt_start && k->get_keycode() == Key::ALT) { + alt_start = false; + if ((alt_code > 0x31 && alt_code < 0xd800) || (alt_code > 0xdfff && alt_code <= 0x10ffff)) { + handle_unicode_input(alt_code); + } + return true; + } + return false; + } + + if (k->is_alt_pressed()) { + if (!alt_start) { + if (k->get_keycode() == Key::KP_ADD) { + alt_start = true; + alt_code = 0; + return true; + } + } else { + if (k->get_keycode() >= Key::KEY_0 && k->get_keycode() <= Key::KEY_9) { + alt_code = alt_code << 4; + alt_code += (uint32_t)(k->get_keycode() - Key::KEY_0); + } + if (k->get_keycode() >= Key::KP_0 && k->get_keycode() <= Key::KP_9) { + alt_code = alt_code << 4; + alt_code += (uint32_t)(k->get_keycode() - Key::KP_0); + } + if (k->get_keycode() >= Key::A && k->get_keycode() <= Key::F) { + alt_code = alt_code << 4; + alt_code += (uint32_t)(k->get_keycode() - Key::A) + 10; + } + return true; + } + } + } + return false; +} + void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) { ERR_FAIL_COND(p_gui_input.is_null()); @@ -1640,7 +1676,7 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) { set_caret_column(col); selection.drag_attempt = false; - if (mb->is_shift_pressed() && (caret.column != prev_col || caret.line != prev_line)) { + if (selecting_enabled && mb->is_shift_pressed() && (caret.column != prev_col || caret.line != prev_line)) { if (!selection.active) { selection.active = true; selection.selecting_mode = SelectionMode::SELECTION_MODE_POINTER; @@ -1682,7 +1718,7 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) { update(); } - } else if (is_mouse_over_selection()) { + } else if (drag_and_drop_selection_enabled && is_mouse_over_selection()) { selection.selecting_mode = SelectionMode::SELECTION_MODE_NONE; selection.drag_attempt = true; } else { @@ -1708,7 +1744,6 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) { last_dblclk = OS::get_singleton()->get_ticks_msec(); last_dblclk_pos = mb->get_position(); } - update(); } @@ -1716,7 +1751,7 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) { paste_primary_clipboard(); } - if (mb->get_button_index() == MouseButton::RIGHT && context_menu_enabled) { + if (mb->get_button_index() == MouseButton::RIGHT && (context_menu_enabled || is_move_caret_on_right_click_enabled())) { _reset_caret_blink_timer(); Point2i pos = get_line_column_at_pos(mpos); @@ -1741,11 +1776,13 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) { } } - _generate_context_menu(); - menu->set_position(get_screen_position() + mpos); - menu->reset_size(); - menu->popup(); - grab_focus(); + if (context_menu_enabled) { + _generate_context_menu(); + menu->set_position(get_screen_position() + mpos); + menu->reset_size(); + menu->popup(); + grab_focus(); + } } } else { if (mb->get_button_index() == MouseButton::LEFT) { @@ -1864,6 +1901,10 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) { Ref<InputEventKey> k = p_gui_input; if (k.is_valid()) { + if (alt_input(p_gui_input)) { + accept_event(); + return; + } if (!k->is_pressed()) { return; } @@ -2314,15 +2355,7 @@ void TextEdit::_move_caret_to_line_start(bool p_select) { } if (caret.column == row_start_col || wi == 0) { // Compute whitespace symbols sequence length. - int current_line_whitespace_len = 0; - while (current_line_whitespace_len < text[caret.line].length()) { - char32_t c = text[caret.line][current_line_whitespace_len]; - if (c != '\t' && c != ' ') { - break; - } - current_line_whitespace_len++; - } - + int current_line_whitespace_len = get_first_non_whitespace_column(caret.line); if (get_caret_column() == current_line_whitespace_len) { set_caret_column(0); } else { @@ -2395,7 +2428,7 @@ void TextEdit::_move_caret_page_down(bool p_select) { } void TextEdit::_do_backspace(bool p_word, bool p_all_to_left) { - if (!editable || (caret.column == 0 && caret.line == 0)) { + if (!editable || (caret.column == 0 && caret.line == 0 && !has_selection())) { return; } @@ -2414,17 +2447,17 @@ void TextEdit::_do_backspace(bool p_word, bool p_all_to_left) { if (p_word) { int column = caret.column; // Check for the case "<word><space><caret>" and ignore the space. - // No need to check for column being 0 since it is cheked above. + // No need to check for column being 0 since it is checked above. if (is_whitespace(text[caret.line][caret.column - 1])) { column -= 1; } // Get a list with the indices of the word bounds of the given text line. const PackedInt32Array words = TS->shaped_text_get_word_breaks(text.get_line_data(caret.line)->get_rid()); if (words.is_empty() || column <= words[0]) { - // If "words" is empty, meaning no words are left, we can remove everything until the begining of the line. + // If "words" is empty, meaning no words are left, we can remove everything until the beginning of the line. column = 0; } else { - // Otherwise search for the first word break that is smaller than the index from we're currentlu deleteing + // Otherwise search for the first word break that is smaller than the index from which we're currently deleting. for (int i = words.size() - 2; i >= 0; i = i - 2) { if (words[i] < column) { column = words[i]; @@ -2460,6 +2493,10 @@ void TextEdit::_delete(bool p_word, bool p_all_to_right) { int next_column; if (p_all_to_right) { + if (caret.column == curline_len) { + return; + } + // Delete everything to right of caret next_column = curline_len; next_line = caret.line; @@ -2531,7 +2568,7 @@ void TextEdit::_update_placeholder() { placeholder_data_buf->set_width(text.get_width()); placeholder_data_buf->set_direction((TextServer::Direction)text_direction); placeholder_data_buf->set_preserve_control(draw_control_chars); - placeholder_data_buf->add_string(placeholder_text, font, font_size, opentype_features, language); + placeholder_data_buf->add_string(placeholder_text, font, font_size, language); placeholder_bidi_override = structured_text_parser(st_parser, st_args, placeholder_text); if (placeholder_bidi_override.is_empty()) { @@ -2540,7 +2577,7 @@ void TextEdit::_update_placeholder() { if (get_tab_size() > 0) { Vector<float> tabs; - tabs.push_back(font->get_char_size(' ', 0, font_size).width * get_tab_size()); + tabs.push_back(font->get_char_size(' ', font_size).width * get_tab_size()); placeholder_data_buf->tab_align(tabs); } @@ -2611,7 +2648,6 @@ void TextEdit::_update_caches() { dir = (TextServer::Direction)text_direction; } text.set_direction_and_language(dir, (!language.is_empty()) ? language : TranslationServer::get_singleton()->get_tool_locale()); - text.set_font_features(opentype_features); text.set_draw_control_chars(draw_control_chars); text.set_font(font); text.set_font_size(font_size); @@ -2626,7 +2662,11 @@ void TextEdit::_update_caches() { /* General overrides. */ Size2 TextEdit::get_minimum_size() const { - return style_normal->get_minimum_size(); + Size2 size = style_normal->get_minimum_size(); + if (fit_content_height) { + size.y += content_height_cache; + } + return size; } bool TextEdit::is_text_field() const { @@ -2812,33 +2852,6 @@ Control::TextDirection TextEdit::get_text_direction() const { return text_direction; } -void TextEdit::set_opentype_feature(const String &p_name, int p_value) { - int32_t tag = TS->name_to_tag(p_name); - if (!opentype_features.has(tag) || (int)opentype_features[tag] != p_value) { - opentype_features[tag] = p_value; - text.set_font_features(opentype_features); - text.invalidate_font(); - _update_placeholder(); - update(); - } -} - -int TextEdit::get_opentype_feature(const String &p_name) const { - int32_t tag = TS->name_to_tag(p_name); - if (!opentype_features.has(tag)) { - return -1; - } - return opentype_features[tag]; -} - -void TextEdit::clear_opentype_features() { - opentype_features.clear(); - text.set_font_features(opentype_features); - text.invalidate_font(); - _update_placeholder(); - update(); -} - void TextEdit::set_language(const String &p_language) { if (language != p_language) { language = p_language; @@ -2859,7 +2872,7 @@ String TextEdit::get_language() const { return language; } -void TextEdit::set_structured_text_bidi_override(Control::StructuredTextParser p_parser) { +void TextEdit::set_structured_text_bidi_override(TextServer::StructuredTextParser p_parser) { if (st_parser != p_parser) { st_parser = p_parser; for (int i = 0; i < text.size(); i++) { @@ -2869,7 +2882,7 @@ void TextEdit::set_structured_text_bidi_override(Control::StructuredTextParser p } } -Control::StructuredTextParser TextEdit::get_structured_text_bidi_override() const { +TextServer::StructuredTextParser TextEdit::get_structured_text_bidi_override() const { return st_parser; } @@ -3038,7 +3051,7 @@ void TextEdit::set_line(int p_line, const String &p_new_text) { _remove_text(p_line, 0, p_line, text[p_line].length()); _insert_text(p_line, 0, p_new_text); if (caret.line == p_line && caret.column > p_new_text.length()) { - set_caret_column(MIN(caret.column, p_new_text.length()), false); + set_caret_column(p_new_text.length(), false); } if (has_selection() && p_line == selection.to_line && selection.to_column > text[p_line].length()) { selection.to_column = text[p_line].length(); @@ -3122,6 +3135,7 @@ void TextEdit::insert_line_at(int p_at, const String &p_text) { ++selection.to_line; } } + update(); } void TextEdit::insert_text_at_caret(const String &p_text) { @@ -3817,7 +3831,7 @@ Point2i TextEdit::get_line_column_at_pos(const Point2i &p_pos, bool p_allow_out_ Point2i TextEdit::get_pos_at_line_column(int p_line, int p_column) const { Rect2i rect = get_rect_at_line_column(p_line, p_column); - return rect.position + Vector2i(0, get_line_height()); + return rect.position.x == -1 ? rect.position : rect.position + Vector2i(0, get_line_height()); } Rect2i TextEdit::get_rect_at_line_column(int p_line, int p_column) const { @@ -4055,12 +4069,12 @@ void TextEdit::set_caret_column(int p_col, bool p_adjust_viewport) { if (p_col < 0) { p_col = 0; } + if (p_col > get_line(caret.line).length()) { + p_col = get_line(caret.line).length(); + } bool caret_moved = caret.column != p_col; caret.column = p_col; - if (caret.column > get_line(caret.line).length()) { - caret.column = get_line(caret.line).length(); - } caret.last_fit_x = _get_column_x_offset_for_line(caret.column, caret.line); @@ -4120,6 +4134,14 @@ bool TextEdit::is_deselect_on_focus_loss_enabled() const { return deselect_on_focus_loss_enabled; } +void TextEdit::set_drag_and_drop_selection_enabled(const bool p_enabled) { + drag_and_drop_selection_enabled = p_enabled; +} + +bool TextEdit::is_drag_and_drop_selection_enabled() const { + return drag_and_drop_selection_enabled; +} + void TextEdit::set_override_selected_font_color(bool p_override_selected_font_color) { override_selected_font_color = p_override_selected_font_color; } @@ -4189,13 +4211,18 @@ void TextEdit::select_word_under_caret() { int end = 0; const PackedInt32Array words = TS->shaped_text_get_word_breaks(text.get_line_data(caret.line)->get_rid()); for (int i = 0; i < words.size(); i = i + 2) { - if ((words[i] < caret.column && words[i + 1] > caret.column) || (i == words.size() - 2 && caret.column == words[i + 1])) { + if ((words[i] <= caret.column && words[i + 1] >= caret.column) || (i == words.size() - 2 && caret.column == words[i + 1])) { begin = words[i]; end = words[i + 1]; break; } } + // No word found. + if (begin == 0 && end == 0) { + return; + } + select(caret.line, begin, caret.line, end); /* Move the caret to the end of the word for easier editing. */ set_caret_column(end, false); @@ -4271,10 +4298,12 @@ String TextEdit::get_selected_text() const { } int TextEdit::get_selection_line() const { + ERR_FAIL_COND_V(!selection.active, -1); return selection.selecting_line; } int TextEdit::get_selection_column() const { + ERR_FAIL_COND_V(!selection.active, -1); return selection.selecting_column; } @@ -4441,6 +4470,18 @@ float TextEdit::get_v_scroll_speed() const { return v_scroll_speed; } +void TextEdit::set_fit_content_height_enabled(const bool p_enabled) { + if (fit_content_height == p_enabled) { + return; + } + fit_content_height = p_enabled; + update_minimum_size(); +} + +bool TextEdit::is_fit_content_height_enabled() const { + return fit_content_height; +} + double TextEdit::get_scroll_pos_for_line(int p_line, int p_wrap_index) const { ERR_FAIL_INDEX_V(p_line, text.size(), 0); ERR_FAIL_COND_V(p_wrap_index < 0, 0); @@ -4476,9 +4517,13 @@ void TextEdit::set_line_as_center_visible(int p_line, int p_wrap_index) { ERR_FAIL_COND(p_wrap_index > get_line_wrap_count(p_line)); int visible_rows = get_visible_line_count(); - Point2i next_line = get_next_visible_line_index_offset_from(p_line, p_wrap_index, -visible_rows / 2); + Point2i next_line = get_next_visible_line_index_offset_from(p_line, p_wrap_index, (-visible_rows / 2) - 1); int first_line = p_line - next_line.x + 1; + if (first_line < 0) { + set_v_scroll(0); + return; + } set_v_scroll(get_scroll_pos_for_line(first_line, next_line.y)); } @@ -4490,6 +4535,12 @@ void TextEdit::set_line_as_last_visible(int p_line, int p_wrap_index) { Point2i next_line = get_next_visible_line_index_offset_from(p_line, p_wrap_index, -get_visible_line_count() - 1); int first_line = p_line - next_line.x + 1; + // Adding _get_visible_lines_offset is not 100% correct as we end up showing almost p_line + 1, however, it provides a + // better user experience. Therefore we need to special case < visible line count, else showing line 0 is impossible. + if (get_visible_line_count_in_range(0, p_line) < get_visible_line_count() + 1) { + set_v_scroll(0); + return; + } set_v_scroll(get_scroll_pos_for_line(first_line, next_line.y) + _get_visible_lines_offset()); } @@ -4696,9 +4747,7 @@ void TextEdit::add_gutter(int p_at) { gutters.insert(p_at, GutterInfo()); } - for (int i = 0; i < text.size() + 1; i++) { - text.add_gutter(p_at); - } + text.add_gutter(p_at); emit_signal(SNAME("gutter_added")); update(); } @@ -4708,9 +4757,7 @@ void TextEdit::remove_gutter(int p_gutter) { gutters.remove_at(p_gutter); - for (int i = 0; i < text.size() + 1; i++) { - text.remove_gutter(p_gutter); - } + text.remove_gutter(p_gutter); emit_signal(SNAME("gutter_removed")); update(); } @@ -4991,10 +5038,6 @@ void TextEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("set_text_direction", "direction"), &TextEdit::set_text_direction); ClassDB::bind_method(D_METHOD("get_text_direction"), &TextEdit::get_text_direction); - ClassDB::bind_method(D_METHOD("set_opentype_feature", "tag", "value"), &TextEdit::set_opentype_feature); - ClassDB::bind_method(D_METHOD("get_opentype_feature", "tag"), &TextEdit::get_opentype_feature); - ClassDB::bind_method(D_METHOD("clear_opentype_features"), &TextEdit::clear_opentype_features); - ClassDB::bind_method(D_METHOD("set_language", "language"), &TextEdit::set_language); ClassDB::bind_method(D_METHOD("get_language"), &TextEdit::get_language); @@ -5027,6 +5070,7 @@ void TextEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("set_text", "text"), &TextEdit::set_text); ClassDB::bind_method(D_METHOD("get_text"), &TextEdit::get_text); + ClassDB::bind_method(D_METHOD("get_line_count"), &TextEdit::get_line_count); ClassDB::bind_method(D_METHOD("set_placeholder", "text"), &TextEdit::set_placeholder); @@ -5187,6 +5231,9 @@ void TextEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("set_deselect_on_focus_loss_enabled", "enable"), &TextEdit::set_deselect_on_focus_loss_enabled); ClassDB::bind_method(D_METHOD("is_deselect_on_focus_loss_enabled"), &TextEdit::is_deselect_on_focus_loss_enabled); + ClassDB::bind_method(D_METHOD("set_drag_and_drop_selection_enabled", "enable"), &TextEdit::set_drag_and_drop_selection_enabled); + ClassDB::bind_method(D_METHOD("is_drag_and_drop_selection_enabled"), &TextEdit::is_drag_and_drop_selection_enabled); + ClassDB::bind_method(D_METHOD("set_override_selected_font_color", "override"), &TextEdit::set_override_selected_font_color); ClassDB::bind_method(D_METHOD("is_overriding_selected_font_color"), &TextEdit::is_overriding_selected_font_color); @@ -5230,7 +5277,7 @@ void TextEdit::_bind_methods() { /* Viewport. */ // Scrolling. - ClassDB::bind_method(D_METHOD("set_smooth_scroll_enable", "enable"), &TextEdit::set_smooth_scroll_enabled); + ClassDB::bind_method(D_METHOD("set_smooth_scroll_enabled", "enable"), &TextEdit::set_smooth_scroll_enabled); ClassDB::bind_method(D_METHOD("is_smooth_scroll_enabled"), &TextEdit::is_smooth_scroll_enabled); ClassDB::bind_method(D_METHOD("set_v_scroll", "value"), &TextEdit::set_v_scroll); @@ -5245,6 +5292,9 @@ void TextEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("set_v_scroll_speed", "speed"), &TextEdit::set_v_scroll_speed); ClassDB::bind_method(D_METHOD("get_v_scroll_speed"), &TextEdit::get_v_scroll_speed); + ClassDB::bind_method(D_METHOD("set_fit_content_height_enabled"), &TextEdit::set_fit_content_height_enabled); + ClassDB::bind_method(D_METHOD("is_fit_content_height_enabled"), &TextEdit::is_fit_content_height_enabled); + ClassDB::bind_method(D_METHOD("get_scroll_pos_for_line", "line", "wrap_index"), &TextEdit::get_scroll_pos_for_line, DEFVAL(0)); // Visible lines. @@ -5341,14 +5391,13 @@ void TextEdit::_bind_methods() { /* Inspector */ ADD_PROPERTY(PropertyInfo(Variant::STRING, "text", PROPERTY_HINT_MULTILINE_TEXT), "set_text", "get_text"); ADD_PROPERTY(PropertyInfo(Variant::STRING, "placeholder_text", PROPERTY_HINT_MULTILINE_TEXT), "set_placeholder", "get_placeholder"); - 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", PROPERTY_HINT_LOCALE_ID, ""), "set_language", "get_language"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "editable"), "set_editable", "is_editable"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "context_menu_enabled"), "set_context_menu_enabled", "is_context_menu_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "shortcut_keys_enabled"), "set_shortcut_keys_enabled", "is_shortcut_keys_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "selecting_enabled"), "set_selecting_enabled", "is_selecting_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "deselect_on_focus_loss_enabled"), "set_deselect_on_focus_loss_enabled", "is_deselect_on_focus_loss_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "drag_and_drop_selection_enabled"), "set_drag_and_drop_selection_enabled", "is_drag_and_drop_selection_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "virtual_keyboard_enabled"), "set_virtual_keyboard_enabled", "is_virtual_keyboard_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "middle_mouse_paste_enabled"), "set_middle_mouse_paste_enabled", "is_middle_mouse_paste_enabled"); ADD_PROPERTY(PropertyInfo(Variant::INT, "wrap_mode", PROPERTY_HINT_ENUM, "None,Boundary"), "set_line_wrapping_mode", "get_line_wrapping_mode"); @@ -5363,24 +5412,27 @@ void TextEdit::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "syntax_highlighter", PROPERTY_HINT_RESOURCE_TYPE, "SyntaxHighlighter", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_DO_NOT_SHARE_ON_DUPLICATE), "set_syntax_highlighter", "get_syntax_highlighter"); ADD_GROUP("Scroll", "scroll_"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "scroll_smooth"), "set_smooth_scroll_enable", "is_smooth_scroll_enabled"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "scroll_v_scroll_speed"), "set_v_scroll_speed", "get_v_scroll_speed"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "scroll_smooth"), "set_smooth_scroll_enabled", "is_smooth_scroll_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "scroll_v_scroll_speed", PROPERTY_HINT_NONE, "suffix:px/s"), "set_v_scroll_speed", "get_v_scroll_speed"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "scroll_past_end_of_file"), "set_scroll_past_end_of_file_enabled", "is_scroll_past_end_of_file_enabled"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "scroll_vertical"), "set_v_scroll", "get_v_scroll"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "scroll_horizontal"), "set_h_scroll", "get_h_scroll"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "scroll_vertical", PROPERTY_HINT_NONE, "suffix:px"), "set_v_scroll", "get_v_scroll"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "scroll_horizontal", PROPERTY_HINT_NONE, "suffix:px"), "set_h_scroll", "get_h_scroll"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "scroll_fit_content_height"), "set_fit_content_height_enabled", "is_fit_content_height_enabled"); ADD_GROUP("Minimap", "minimap_"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "minimap_draw"), "set_draw_minimap", "is_drawing_minimap"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "minimap_width"), "set_minimap_width", "get_minimap_width"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "minimap_width", PROPERTY_HINT_NONE, "suffix:px"), "set_minimap_width", "get_minimap_width"); ADD_GROUP("Caret", "caret_"); ADD_PROPERTY(PropertyInfo(Variant::INT, "caret_type", PROPERTY_HINT_ENUM, "Line,Block"), "set_caret_type", "get_caret_type"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "caret_blink"), "set_caret_blink_enabled", "is_caret_blink_enabled"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "caret_blink_speed", PROPERTY_HINT_RANGE, "0.1,10,0.01"), "set_caret_blink_speed", "get_caret_blink_speed"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "caret_blink_speed", PROPERTY_HINT_RANGE, "0.1,10,0.01,suffix:s"), "set_caret_blink_speed", "get_caret_blink_speed"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "caret_move_on_right_click"), "set_move_caret_on_right_click_enabled", "is_move_caret_on_right_click_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "caret_mid_grapheme"), "set_caret_mid_grapheme_enabled", "is_caret_mid_grapheme_enabled"); - ADD_GROUP("Structured Text", "structured_text_"); + ADD_GROUP("BiDi", ""); + 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", PROPERTY_HINT_LOCALE_ID, ""), "set_language", "get_language"); ADD_PROPERTY(PropertyInfo(Variant::INT, "structured_text_bidi_override", PROPERTY_HINT_ENUM, "Default,URI,File,Email,List,None,Custom"), "set_structured_text_bidi_override", "get_structured_text_bidi_override"); ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "structured_text_bidi_override_options"), "set_structured_text_bidi_override_options", "get_structured_text_bidi_override_options"); @@ -5405,60 +5457,6 @@ void TextEdit::_bind_methods() { ProjectSettings::get_singleton()->set_custom_property_info("gui/common/text_edit_undo_stack_max_size", PropertyInfo(Variant::INT, "gui/common/text_edit_undo_stack_max_size", PROPERTY_HINT_RANGE, "0,10000,1,or_greater")); // No negative numbers. } -bool TextEdit::_set(const StringName &p_name, const Variant &p_value) { - String str = p_name; - if (str.begins_with("opentype_features/")) { - String name = str.get_slicec('/', 1); - int32_t tag = TS->name_to_tag(name); - int value = p_value; - if (value == -1) { - if (opentype_features.has(tag)) { - opentype_features.erase(tag); - text.set_font_features(opentype_features); - text.invalidate_font(); - _update_placeholder(); - update(); - } - } else { - if (!opentype_features.has(tag) || (int)opentype_features[tag] != value) { - opentype_features[tag] = value; - text.set_font_features(opentype_features); - text.invalidate_font(); - _update_placeholder(); - update(); - } - } - notify_property_list_changed(); - return true; - } - - return false; -} - -bool TextEdit::_get(const StringName &p_name, Variant &r_ret) const { - String str = p_name; - if (str.begins_with("opentype_features/")) { - String name = str.get_slicec('/', 1); - int32_t tag = TS->name_to_tag(name); - if (opentype_features.has(tag)) { - r_ret = opentype_features[tag]; - return true; - } else { - r_ret = -1; - return true; - } - } - return false; -} - -void TextEdit::_get_property_list(List<PropertyInfo> *p_list) const { - for (const Variant *ftr = opentype_features.next(nullptr); ftr != nullptr; ftr = opentype_features.next(ftr)) { - String name = TS->tag_to_name(*ftr); - p_list->push_back(PropertyInfo(Variant::INT, "opentype_features/" + name)); - } - p_list->push_back(PropertyInfo(Variant::NIL, "opentype_features/_new", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR)); -} - /* Internal API for CodeEdit. */ // Line hiding. void TextEdit::_set_hiding_enabled(bool p_enabled) { @@ -5899,7 +5897,7 @@ int TextEdit::_get_column_x_offset_for_line(int p_char, int p_line) const { int row = 0; Vector<Vector2i> rows2 = text.get_line_wrap_ranges(p_line); for (int i = 0; i < rows2.size(); i++) { - if ((p_char >= rows2[i].x) && (p_char < rows2[i].y)) { + if ((p_char >= rows2[i].x) && (p_char <= rows2[i].y)) { row = i; break; } @@ -5983,8 +5981,8 @@ void TextEdit::_update_selection_mode_word() { selection.selected_word_beg = beg; selection.selected_word_end = end; selection.selected_word_origin = beg; - set_caret_line(selection.to_line, false); - set_caret_column(selection.to_column); + set_caret_line(line, false); + set_caret_column(end); } else { if ((col <= selection.selected_word_origin && line == selection.selecting_line) || line < selection.selecting_line) { selection.selecting_column = selection.selected_word_end; @@ -6040,6 +6038,10 @@ void TextEdit::_update_selection_mode_line() { } void TextEdit::_pre_shift_selection() { + if (!selecting_enabled) { + return; + } + if (!selection.active || selection.selecting_mode == SelectionMode::SELECTION_MODE_NONE) { selection.selecting_line = caret.line; selection.selecting_column = caret.column; @@ -6050,6 +6052,10 @@ void TextEdit::_pre_shift_selection() { } void TextEdit::_post_shift_selection() { + if (!selecting_enabled) { + return; + } + if (selection.active && selection.selecting_mode == SelectionMode::SELECTION_MODE_SHIFT) { select(selection.selecting_line, selection.selecting_column, caret.line, caret.column); update(); @@ -6121,6 +6127,11 @@ void TextEdit::_update_scrollbars() { total_width += minimap_width; } + content_height_cache = MAX(total_rows, 1) * get_line_height(); + if (fit_content_height) { + update_minimum_size(); + } + updating_scrolls = true; if (total_rows > visible_rows) { diff --git a/scene/gui/text_edit.h b/scene/gui/text_edit.h index b365e9c61c..6711cf8c7f 100644 --- a/scene/gui/text_edit.h +++ b/scene/gui/text_edit.h @@ -160,7 +160,6 @@ private: int font_size = -1; int font_height = 0; - Dictionary opentype_features; String language; TextServer::Direction direction = TextServer::DIRECTION_AUTO; bool draw_control_chars = false; @@ -180,7 +179,6 @@ private: int get_tab_size() const; void set_font(const Ref<Font> &p_font); void set_font_size(int p_font_size); - void set_font_features(const Dictionary &p_features); void set_direction_and_language(TextServer::Direction p_direction, const String &p_language); void set_draw_control_chars(bool p_enabled); @@ -247,6 +245,9 @@ private: bool setting_text = false; + bool alt_start = false; + uint32_t alt_code = 0; + // Text properties. String ime_text = ""; Point2 ime_selection; @@ -268,10 +269,9 @@ private: TextDirection text_direction = TEXT_DIRECTION_AUTO; TextDirection input_direction = TEXT_DIRECTION_LTR; - Dictionary opentype_features; String language = ""; - Control::StructuredTextParser st_parser = STRUCTURED_TEXT_DEFAULT; + TextServer::StructuredTextParser st_parser = TextServer::STRUCTURED_TEXT_DEFAULT; Array st_args; void _clear(); @@ -353,7 +353,7 @@ private: Vector<int> last_visible_chars; }; - Map<int, LineDrawingCache> line_drawing_cache; + HashMap<int, LineDrawingCache> line_drawing_cache; int _get_char_pos_for_line(int p_px, int p_line, int p_wrap_index = 0) const; @@ -419,6 +419,7 @@ private: bool selecting_enabled = true; bool deselect_on_focus_loss_enabled = true; + bool drag_and_drop_selection_enabled = true; Color font_selected_color = Color(1, 1, 1); Color selection_color = Color(1, 1, 1); @@ -452,6 +453,8 @@ private: HScrollBar *h_scroll = nullptr; VScrollBar *v_scroll = nullptr; + float content_height_cache = 0.0; + bool fit_content_height = false; bool scroll_past_end_of_file_enabled = false; // Smooth scrolling. @@ -508,7 +511,6 @@ private: /* Syntax highlighting. */ Ref<SyntaxHighlighter> syntax_highlighter; - Map<int, Dictionary> syntax_highlighting_cache; Dictionary _get_line_syntax_highlighting(int p_line); @@ -576,10 +578,6 @@ protected: static void _bind_methods(); - bool _set(const StringName &p_name, const Variant &p_value); - bool _get(const StringName &p_name, Variant &r_ret) const; - void _get_property_list(List<PropertyInfo> *p_list) const; - /* Internal API for CodeEdit, pending public API. */ // brace matching bool highlight_matching_braces_enabled = false; @@ -625,6 +623,7 @@ public: /* General overrides. */ virtual void unhandled_key_input(const Ref<InputEvent> &p_event) override; virtual void gui_input(const Ref<InputEvent> &p_gui_input) override; + bool alt_input(const Ref<InputEvent> &p_gui_input); virtual Size2 get_minimum_size() const override; virtual bool is_text_field() const override; virtual CursorShape get_cursor_shape(const Point2 &p_pos = Point2i()) const override; @@ -644,15 +643,11 @@ public: void set_text_direction(TextDirection p_text_direction); TextDirection get_text_direction() const; - void set_opentype_feature(const String &p_name, int p_value); - int get_opentype_feature(const String &p_name) const; - void clear_opentype_features(); - void set_language(const String &p_language); String get_language() const; - void set_structured_text_bidi_override(Control::StructuredTextParser p_parser); - Control::StructuredTextParser get_structured_text_bidi_override() const; + void set_structured_text_bidi_override(TextServer::StructuredTextParser p_parser); + TextServer::StructuredTextParser get_structured_text_bidi_override() const; void set_structured_text_bidi_override_options(Array p_args); Array get_structured_text_bidi_override_options() const; @@ -680,6 +675,7 @@ public: void set_text(const String &p_text); String get_text() const; + int get_line_count() const; void set_placeholder(const String &p_text); @@ -792,6 +788,9 @@ public: void set_deselect_on_focus_loss_enabled(const bool p_enabled); bool is_deselect_on_focus_loss_enabled() const; + void set_drag_and_drop_selection_enabled(const bool p_enabled); + bool is_drag_and_drop_selection_enabled() const; + void set_override_selected_font_color(bool p_override_selected_font_color); bool is_overriding_selected_font_color() const; @@ -844,6 +843,9 @@ public: void set_v_scroll_speed(float p_speed); float get_v_scroll_speed() const; + void set_fit_content_height_enabled(const bool p_enabled); + bool is_fit_content_height_enabled() const; + double get_scroll_pos_for_line(int p_line, int p_wrap_index = 0) const; // Visible lines. diff --git a/scene/gui/texture_progress_bar.cpp b/scene/gui/texture_progress_bar.cpp index f79c68671c..94e0a6f226 100644 --- a/scene/gui/texture_progress_bar.cpp +++ b/scene/gui/texture_progress_bar.cpp @@ -633,16 +633,16 @@ void TextureProgressBar::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "nine_patch_stretch"), "set_nine_patch_stretch", "get_nine_patch_stretch"); ADD_GROUP("Stretch Margin", "stretch_margin_"); - ADD_PROPERTYI(PropertyInfo(Variant::INT, "stretch_margin_left", PROPERTY_HINT_RANGE, "0,16384,1"), "set_stretch_margin", "get_stretch_margin", SIDE_LEFT); - ADD_PROPERTYI(PropertyInfo(Variant::INT, "stretch_margin_top", PROPERTY_HINT_RANGE, "0,16384,1"), "set_stretch_margin", "get_stretch_margin", SIDE_TOP); - ADD_PROPERTYI(PropertyInfo(Variant::INT, "stretch_margin_right", PROPERTY_HINT_RANGE, "0,16384,1"), "set_stretch_margin", "get_stretch_margin", SIDE_RIGHT); - ADD_PROPERTYI(PropertyInfo(Variant::INT, "stretch_margin_bottom", PROPERTY_HINT_RANGE, "0,16384,1"), "set_stretch_margin", "get_stretch_margin", SIDE_BOTTOM); + ADD_PROPERTYI(PropertyInfo(Variant::INT, "stretch_margin_left", PROPERTY_HINT_RANGE, "0,16384,1,suffix:px"), "set_stretch_margin", "get_stretch_margin", SIDE_LEFT); + ADD_PROPERTYI(PropertyInfo(Variant::INT, "stretch_margin_top", PROPERTY_HINT_RANGE, "0,16384,1,suffix:px"), "set_stretch_margin", "get_stretch_margin", SIDE_TOP); + ADD_PROPERTYI(PropertyInfo(Variant::INT, "stretch_margin_right", PROPERTY_HINT_RANGE, "0,16384,1,suffix:px"), "set_stretch_margin", "get_stretch_margin", SIDE_RIGHT); + ADD_PROPERTYI(PropertyInfo(Variant::INT, "stretch_margin_bottom", PROPERTY_HINT_RANGE, "0,16384,1,suffix:px"), "set_stretch_margin", "get_stretch_margin", SIDE_BOTTOM); ADD_GROUP("Textures", "texture_"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture_under", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_under_texture", "get_under_texture"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture_over", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_over_texture", "get_over_texture"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture_progress", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_progress_texture", "get_progress_texture"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "texture_progress_offset"), "set_texture_progress_offset", "get_texture_progress_offset"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "texture_progress_offset", PROPERTY_HINT_NONE, "suffix:px"), "set_texture_progress_offset", "get_texture_progress_offset"); ADD_GROUP("Tint", "tint_"); ADD_PROPERTY(PropertyInfo(Variant::COLOR, "tint_under"), "set_tint_under", "get_tint_under"); @@ -650,9 +650,9 @@ void TextureProgressBar::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::COLOR, "tint_progress"), "set_tint_progress", "get_tint_progress"); ADD_GROUP("Radial Fill", "radial_"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radial_initial_angle", PROPERTY_HINT_RANGE, "0.0,360.0,0.1,slider"), "set_radial_initial_angle", "get_radial_initial_angle"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radial_fill_degrees", PROPERTY_HINT_RANGE, "0.0,360.0,0.1,slider"), "set_fill_degrees", "get_fill_degrees"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "radial_center_offset"), "set_radial_center_offset", "get_radial_center_offset"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radial_initial_angle", PROPERTY_HINT_RANGE, "0.0,360.0,0.1,slider,degrees"), "set_radial_initial_angle", "get_radial_initial_angle"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radial_fill_degrees", PROPERTY_HINT_RANGE, "0.0,360.0,0.1,slider,degrees"), "set_fill_degrees", "get_fill_degrees"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "radial_center_offset", PROPERTY_HINT_NONE, "suffix:px"), "set_radial_center_offset", "get_radial_center_offset"); BIND_ENUM_CONSTANT(FILL_LEFT_TO_RIGHT); BIND_ENUM_CONSTANT(FILL_RIGHT_TO_LEFT); diff --git a/scene/gui/tree.cpp b/scene/gui/tree.cpp index 3c8ea4d6df..2c4cba4954 100644 --- a/scene/gui/tree.cpp +++ b/scene/gui/tree.cpp @@ -303,38 +303,7 @@ Control::TextDirection TreeItem::get_text_direction(int p_column) const { return cells[p_column].text_direction; } -void TreeItem::clear_opentype_features(int p_column) { - ERR_FAIL_INDEX(p_column, cells.size()); - - cells.write[p_column].opentype_features.clear(); - cells.write[p_column].dirty = true; - cells.write[p_column].cached_minimum_size_dirty = true; - - _changed_notify(p_column); -} - -void TreeItem::set_opentype_feature(int p_column, const String &p_name, int p_value) { - ERR_FAIL_INDEX(p_column, cells.size()); - int32_t tag = TS->name_to_tag(p_name); - if (!cells[p_column].opentype_features.has(tag) || (int)cells[p_column].opentype_features[tag] != p_value) { - cells.write[p_column].opentype_features[tag] = p_value; - cells.write[p_column].dirty = true; - cells.write[p_column].cached_minimum_size_dirty = true; - - _changed_notify(p_column); - } -} - -int TreeItem::get_opentype_feature(int p_column, const String &p_name) const { - ERR_FAIL_INDEX_V(p_column, cells.size(), -1); - int32_t tag = TS->name_to_tag(p_name); - if (!cells[p_column].opentype_features.has(tag)) { - return -1; - } - return cells[p_column].opentype_features[tag]; -} - -void TreeItem::set_structured_text_bidi_override(int p_column, Control::StructuredTextParser p_parser) { +void TreeItem::set_structured_text_bidi_override(int p_column, TextServer::StructuredTextParser p_parser) { ERR_FAIL_INDEX(p_column, cells.size()); if (cells[p_column].st_parser != p_parser) { @@ -346,8 +315,8 @@ void TreeItem::set_structured_text_bidi_override(int p_column, Control::Structur } } -Control::StructuredTextParser TreeItem::get_structured_text_bidi_override(int p_column) const { - ERR_FAIL_INDEX_V(p_column, cells.size(), Control::STRUCTURED_TEXT_NONE); +TextServer::StructuredTextParser TreeItem::get_structured_text_bidi_override(int p_column) const { + ERR_FAIL_INDEX_V(p_column, cells.size(), TextServer::STRUCTURED_TEXT_NONE); return cells[p_column].st_parser; } @@ -544,6 +513,21 @@ bool TreeItem::is_collapsed() { return collapsed; } +void TreeItem::set_visible(bool p_visible) { + if (visible == p_visible) { + return; + } + visible = p_visible; + if (tree) { + tree->update(); + _changed_notify(); + } +} + +bool TreeItem::is_visible() { + return visible; +} + void TreeItem::uncollapse_tree() { TreeItem *t = this; while (t) { @@ -646,7 +630,7 @@ TreeItem *TreeItem::get_first_child() const { return first_child; } -TreeItem *TreeItem::get_prev_visible(bool p_wrap) { +TreeItem *TreeItem::_get_prev_visible(bool p_wrap) { TreeItem *current = this; TreeItem *prev = current->get_prev(); @@ -682,7 +666,21 @@ TreeItem *TreeItem::get_prev_visible(bool p_wrap) { return current; } -TreeItem *TreeItem::get_next_visible(bool p_wrap) { +TreeItem *TreeItem::get_prev_visible(bool p_wrap) { + TreeItem *loop = this; + TreeItem *prev = this->_get_prev_visible(p_wrap); + while (prev && !prev->is_visible()) { + prev = prev->_get_prev_visible(p_wrap); + if (prev == loop) { + // Check that we haven't looped all the way around to the start. + prev = nullptr; + break; + } + } + return prev; +} + +TreeItem *TreeItem::_get_next_visible(bool p_wrap) { TreeItem *current = this; if (!current->collapsed && current->first_child) { @@ -709,12 +707,37 @@ TreeItem *TreeItem::get_next_visible(bool p_wrap) { return current; } +TreeItem *TreeItem::get_next_visible(bool p_wrap) { + TreeItem *loop = this; + TreeItem *next = this->_get_next_visible(p_wrap); + while (next && !next->is_visible()) { + next = next->_get_next_visible(p_wrap); + if (next == loop) { + // Check that we haven't looped all the way around to the start. + next = nullptr; + break; + } + } + return next; +} + TreeItem *TreeItem::get_child(int p_idx) { _create_children_cache(); ERR_FAIL_INDEX_V(p_idx, children_cache.size(), nullptr); return children_cache.get(p_idx); } +int TreeItem::get_visible_child_count() { + _create_children_cache(); + int visible_count = 0; + for (int i = 0; i < children_cache.size(); i++) { + if (children_cache[i]->is_visible()) { + visible_count += 1; + } + } + return visible_count; +} + int TreeItem::get_child_count() { _create_children_cache(); return children_cache.size(); @@ -1215,10 +1238,6 @@ void TreeItem::_bind_methods() { ClassDB::bind_method(D_METHOD("set_text_direction", "column", "direction"), &TreeItem::set_text_direction); ClassDB::bind_method(D_METHOD("get_text_direction", "column"), &TreeItem::get_text_direction); - ClassDB::bind_method(D_METHOD("set_opentype_feature", "column", "tag", "value"), &TreeItem::set_opentype_feature); - ClassDB::bind_method(D_METHOD("get_opentype_feature", "column", "tag"), &TreeItem::get_opentype_feature); - ClassDB::bind_method(D_METHOD("clear_opentype_features", "column"), &TreeItem::clear_opentype_features); - ClassDB::bind_method(D_METHOD("set_structured_text_bidi_override", "column", "parser"), &TreeItem::set_structured_text_bidi_override); ClassDB::bind_method(D_METHOD("get_structured_text_bidi_override", "column"), &TreeItem::get_structured_text_bidi_override); @@ -1256,6 +1275,9 @@ void TreeItem::_bind_methods() { ClassDB::bind_method(D_METHOD("set_collapsed", "enable"), &TreeItem::set_collapsed); ClassDB::bind_method(D_METHOD("is_collapsed"), &TreeItem::is_collapsed); + ClassDB::bind_method(D_METHOD("set_visible", "enable"), &TreeItem::set_visible); + ClassDB::bind_method(D_METHOD("is_visible"), &TreeItem::is_visible); + ClassDB::bind_method(D_METHOD("uncollapse_tree"), &TreeItem::uncollapse_tree); ClassDB::bind_method(D_METHOD("set_custom_minimum_height", "height"), &TreeItem::set_custom_minimum_height); @@ -1340,6 +1362,7 @@ void TreeItem::_bind_methods() { } ADD_PROPERTY(PropertyInfo(Variant::BOOL, "collapsed"), "set_collapsed", "is_collapsed"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "visible"), "set_visible", "is_visible"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "disable_folding"), "set_disable_folding", "is_folding_disabled"); ADD_PROPERTY(PropertyInfo(Variant::INT, "custom_minimum_height", PROPERTY_HINT_RANGE, "0,1000,1"), "set_custom_minimum_height", "get_custom_minimum_height"); @@ -1412,8 +1435,8 @@ void Tree::update_cache() { cache.font_color = get_theme_color(SNAME("font_color")); cache.font_selected_color = get_theme_color(SNAME("font_selected_color")); cache.drop_position_color = get_theme_color(SNAME("drop_position_color")); - cache.hseparation = get_theme_constant(SNAME("hseparation")); - cache.vseparation = get_theme_constant(SNAME("vseparation")); + cache.hseparation = get_theme_constant(SNAME("h_separation")); + cache.vseparation = get_theme_constant(SNAME("v_separation")); cache.item_margin = get_theme_constant(SNAME("item_margin")); cache.button_margin = get_theme_constant(SNAME("button_margin")); @@ -1445,7 +1468,7 @@ void Tree::update_cache() { } int Tree::compute_item_height(TreeItem *p_item) const { - if (p_item == root && hide_root) { + if ((p_item == root && hide_root) || !p_item->is_visible()) { return 0; } @@ -1506,6 +1529,9 @@ int Tree::compute_item_height(TreeItem *p_item) const { } int Tree::get_item_height(TreeItem *p_item) const { + if (!p_item->is_visible()) { + return 0; + } int height = compute_item_height(p_item); height += cache.vseparation; @@ -1606,7 +1632,7 @@ void Tree::update_column(int p_col) { columns.write[p_col].text_buf->set_direction((TextServer::Direction)columns[p_col].text_direction); } - columns.write[p_col].text_buf->add_string(columns[p_col].title, cache.font, cache.font_size, columns[p_col].opentype_features, !columns[p_col].language.is_empty() ? columns[p_col].language : TranslationServer::get_singleton()->get_tool_locale()); + columns.write[p_col].text_buf->add_string(columns[p_col].title, cache.font, cache.font_size, columns[p_col].language); } void Tree::update_item_cell(TreeItem *p_item, int p_col) { @@ -1664,7 +1690,7 @@ void Tree::update_item_cell(TreeItem *p_item, int p_col) { } else { font_size = cache.font_size; } - p_item->cells.write[p_col].text_buf->add_string(valtext, font, font_size, p_item->cells[p_col].opentype_features, !p_item->cells[p_col].language.is_empty() ? p_item->cells[p_col].language : TranslationServer::get_singleton()->get_tool_locale()); + p_item->cells.write[p_col].text_buf->add_string(valtext, font, font_size, p_item->cells[p_col].language); TS->shaped_text_set_bidi_override(p_item->cells[p_col].text_buf->get_rid(), structured_text_parser(p_item->cells[p_col].st_parser, p_item->cells[p_col].st_args, valtext)); p_item->cells.write[p_col].dirty = false; } @@ -1686,6 +1712,10 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 return -1; //draw no more! } + if (!p_item->is_visible()) { + return 0; + } + RID ci = get_canvas_item(); int htotal = 0; @@ -1754,19 +1784,16 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 for (int j = p_item->cells[i].buttons.size() - 1; j >= 0; j--) { Ref<Texture2D> b = p_item->cells[i].buttons[j].texture; Size2 s = b->get_size() + cache.button_pressed->get_minimum_size(); - if (s.height < label_h) { - s.height = label_h; - } Point2i o = Point2i(ofs + w - s.width, p_pos.y) - cache.offset + p_draw_ofs; if (cache.click_type == Cache::CLICK_BUTTON && cache.click_item == p_item && cache.click_column == i && cache.click_index == j && !p_item->cells[i].buttons[j].disabled) { - //being pressed + // Being pressed. Point2 od = o; if (rtl) { od.x = get_size().width - od.x - s.x; } - cache.button_pressed->draw(get_canvas_item(), Rect2(od, s)); + cache.button_pressed->draw(get_canvas_item(), Rect2(od.x, od.y, s.width, MAX(s.height, label_h))); } o.y += (label_h - s.height) / 2; @@ -2059,7 +2086,7 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 } } - if (!p_item->disable_folding && !hide_folding && p_item->first_child) { //has children, draw the guide box + if (!p_item->disable_folding && !hide_folding && p_item->first_child && p_item->get_visible_child_count() != 0) { //has visible children, draw the guide box Ref<Texture2D> arrow; @@ -2102,12 +2129,12 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 } // Draw relationship lines. - if (cache.draw_relationship_lines > 0 && (!hide_root || c->parent != root)) { + if (cache.draw_relationship_lines > 0 && (!hide_root || c->parent != root) && c->is_visible()) { int root_ofs = children_pos.x + ((p_item->disable_folding || hide_folding) ? cache.hseparation : cache.item_margin); int parent_ofs = p_pos.x + cache.item_margin; Point2i root_pos = Point2i(root_ofs, children_pos.y + label_h / 2) - cache.offset + p_draw_ofs; - if (c->get_first_child() != nullptr) { + if (c->get_visible_child_count() > 0) { root_pos -= Point2i(cache.arrow->get_width(), 0); } @@ -2385,6 +2412,11 @@ 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, MouseButton p_button, const Ref<InputEventWithModifiers> &p_mod) { + if (p_item && !p_item->is_visible()) { + // Skip any processing of invisible items. + return 0; + } + int item_h = compute_item_height(p_item) + cache.vseparation; bool skip = (p_item == root && hide_root); @@ -2494,7 +2526,6 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int cache.click_column = col; cache.click_pos = click_pos; update(); - //emit_signal(SNAME("button_pressed")); return -1; } @@ -2516,9 +2547,7 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int if (!c.selected || p_button == MouseButton::RIGHT) { p_item->select(col); emit_signal(SNAME("multi_selected"), p_item, col, true); - if (p_button == MouseButton::RIGHT) { - emit_signal(SNAME("item_rmb_selected"), get_local_mouse_position()); - } + emit_signal(SNAME("item_mouse_selected"), get_local_mouse_position(), p_button); //p_item->selected_signal.call(col); } else { @@ -2533,9 +2562,7 @@ 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 == MouseButton::RIGHT) { - emit_signal(SNAME("item_rmb_selected"), get_local_mouse_position()); - } + emit_signal(SNAME("item_mouse_selected"), get_local_mouse_position(), p_button); } else { int icount = _count_selected_items(root); @@ -2547,9 +2574,7 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int select_single_item(p_item, root, col); } - if (p_button == MouseButton::RIGHT) { - emit_signal(SNAME("item_rmb_selected"), get_local_mouse_position()); - } + emit_signal(SNAME("item_mouse_selected"), get_local_mouse_position(), p_button); } } @@ -2586,11 +2611,11 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int if (force_edit_checkbox_only_on_checkbox) { if (x < cache.checked->get_width()) { p_item->set_checked(col, !c.checked); - item_edited(col, p_item); + item_edited(col, p_item, p_button); } } else { p_item->set_checked(col, !c.checked); - item_edited(col, p_item); + item_edited(col, p_item, p_button); } click_handled = true; //p_item->edited_signal.call(col); @@ -2632,17 +2657,17 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int p_item->set_range(col, c.val + (up ? 1.0 : -1.0) * c.step); - item_edited(col, p_item); + item_edited(col, p_item, p_button); } else if (p_button == MouseButton::RIGHT) { p_item->set_range(col, (up ? c.max : c.min)); - item_edited(col, p_item); + item_edited(col, p_item, p_button); } else if (p_button == MouseButton::WHEEL_UP) { p_item->set_range(col, c.val + c.step); - item_edited(col, p_item); + item_edited(col, p_item, p_button); } else if (p_button == MouseButton::WHEEL_DOWN) { p_item->set_range(col, c.val - c.step); - item_edited(col, p_item); + item_edited(col, p_item, p_button); } //p_item->edited_signal.call(col); @@ -2673,7 +2698,7 @@ 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 == MouseButton::LEFT); + item_edited(col, p_item, p_button); } click_handled = true; return -1; @@ -2720,8 +2745,8 @@ 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 == MouseButton::RIGHT) { - emit_signal(SNAME("empty_rmb"), get_local_mouse_position()); + if (p_item == root) { + emit_signal(SNAME("empty_clicked"), get_local_mouse_position(), p_button); } } @@ -2790,6 +2815,9 @@ void Tree::value_editor_changed(double p_value) { TreeItem::Cell &c = popup_edited_item->cells.write[popup_edited_item_col]; c.val = p_value; + + text_editor->set_text(String::num(c.val, Math::range_step_decimals(c.step))); + item_edited(popup_edited_item_col, popup_edited_item); update(); } @@ -2942,6 +2970,15 @@ void Tree::_go_down() { accept_event(); } +bool Tree::_scroll(bool p_horizontal, float p_pages) { + ScrollBar *scroll = p_horizontal ? (ScrollBar *)h_scroll : (ScrollBar *)v_scroll; + + double prev_value = scroll->get_value(); + scroll->set_value(scroll->get_value() + scroll->get_page() * p_pages); + + return scroll->get_value() != prev_value; +} + void Tree::gui_input(const Ref<InputEvent> &p_event) { ERR_FAIL_COND(p_event.is_null()); @@ -3129,7 +3166,6 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) { } Ref<InputEventMouseMotion> mm = p_event; - if (mm.is_valid()) { if (cache.font.is_null()) { // avoid a strange case that may corrupt stuff update_cache(); @@ -3259,18 +3295,17 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) { } } - Ref<InputEventMouseButton> b = p_event; - - if (b.is_valid()) { + Ref<InputEventMouseButton> mb = p_event; + if (mb.is_valid()) { if (cache.font.is_null()) { // avoid a strange case that may corrupt stuff update_cache(); } bool rtl = is_layout_rtl(); - if (!b->is_pressed()) { - if (b->get_button_index() == MouseButton::LEFT) { - Point2 pos = b->get_position(); + if (!mb->is_pressed()) { + if (mb->get_button_index() == MouseButton::LEFT) { + Point2 pos = mb->get_position(); if (rtl) { pos.x = get_size().width - pos.x; } @@ -3305,7 +3340,7 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) { warp_mouse(range_drag_capture_pos); } else { Rect2 rect = get_selected()->get_meta("__focus_rect"); - Point2 mpos = b->get_position(); + Point2 mpos = mb->get_position(); int icon_size_x = 0; Ref<Texture2D> icon = get_selected()->get_icon(selected_col); if (icon.is_valid()) { @@ -3333,17 +3368,6 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) { pressing_for_editor = false; } - if (cache.click_type == Cache::CLICK_BUTTON && cache.click_item != nullptr) { - // make sure in case of wrong reference after reconstructing whole TreeItems - cache.click_item = get_item_at_position(cache.click_pos); - emit_signal(SNAME("button_pressed"), cache.click_item, cache.click_column, cache.click_id); - } - cache.click_type = Cache::CLICK_NONE; - cache.click_index = -1; - cache.click_id = -1; - cache.click_item = nullptr; - cache.click_column = 0; - if (drag_touching) { if (drag_speed == 0) { drag_touching_deaccel = false; @@ -3353,8 +3377,20 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) { drag_touching_deaccel = true; } } - update(); } + + if (cache.click_type == Cache::CLICK_BUTTON && cache.click_item != nullptr) { + // make sure in case of wrong reference after reconstructing whole TreeItems + cache.click_item = get_item_at_position(cache.click_pos); + emit_signal("button_clicked", cache.click_item, cache.click_column, cache.click_id, mb->get_button_index()); + } + + cache.click_type = Cache::CLICK_NONE; + cache.click_index = -1; + cache.click_id = -1; + cache.click_item = nullptr; + cache.click_column = 0; + update(); return; } @@ -3362,12 +3398,12 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) { return; } - switch (b->get_button_index()) { + switch (mb->get_button_index()) { case MouseButton::RIGHT: case MouseButton::LEFT: { Ref<StyleBox> bg = cache.bg; - Point2 pos = b->get_position(); + Point2 pos = mb->get_position(); if (rtl) { pos.x = get_size().width - pos.x; } @@ -3377,7 +3413,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() == MouseButton::LEFT) { + if (mb->get_button_index() == MouseButton::LEFT) { pos.x += cache.offset.x; int len = 0; for (int i = 0; i < columns.size(); i++) { @@ -3394,10 +3430,8 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) { break; } } + if (!root || (!root->get_first_child() && hide_root)) { - if (b->get_button_index() == MouseButton::RIGHT && allow_rmb_select) { - emit_signal(SNAME("empty_tree_rmb_selected"), get_local_mouse_position()); - } break; } @@ -3412,17 +3446,17 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) { cache.rtl = is_layout_rtl(); blocked++; - propagate_mouse_event(pos + cache.offset, 0, 0, x_limit + cache.offset.width, b->is_double_click(), root, b->get_button_index(), b); + propagate_mouse_event(pos + cache.offset, 0, 0, x_limit + cache.offset.width, mb->is_double_click(), root, mb->get_button_index(), mb); blocked--; if (pressing_for_editor) { - pressing_pos = b->get_position(); + pressing_pos = mb->get_position(); if (rtl) { pressing_pos.x = get_size().width - pressing_pos.x; } } - if (b->get_button_index() == MouseButton::RIGHT) { + if (mb->get_button_index() == MouseButton::RIGHT) { break; } @@ -3445,8 +3479,8 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) { set_physics_process_internal(true); } - 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()) { + if (mb->get_button_index() == MouseButton::LEFT) { + if (get_item_at_position(mb->get_position()) == nullptr && !mb->is_shift_pressed() && !mb->is_ctrl_pressed() && !mb->is_command_pressed()) { emit_signal(SNAME("nothing_selected")); } } @@ -3459,17 +3493,25 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) { } break; 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) { + if (_scroll(false, -mb->get_factor() / 8)) { accept_event(); } } break; 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) { + if (_scroll(false, mb->get_factor() / 8)) { + accept_event(); + } + + } break; + case MouseButton::WHEEL_LEFT: { + if (_scroll(true, -mb->get_factor() / 8)) { + accept_event(); + } + + } break; + case MouseButton::WHEEL_RIGHT: { + if (_scroll(true, mb->get_factor() / 8)) { accept_event(); } @@ -3914,16 +3956,15 @@ TreeItem *Tree::get_last_item() const { return last; } -void Tree::item_edited(int p_column, TreeItem *p_item, bool p_lmb) { +void Tree::item_edited(int p_column, TreeItem *p_item, MouseButton p_custom_mouse_index) { edited_item = p_item; edited_col = p_column; if (p_item != nullptr && p_column >= 0 && p_column < p_item->cells.size()) { edited_item->cells.write[p_column].dirty = true; } - if (p_lmb) { - emit_signal(SNAME("item_edited")); - } else { - emit_signal(SNAME("item_rmb_edited")); + emit_signal(SNAME("item_edited")); + if (p_custom_mouse_index != MouseButton::NONE) { + emit_signal(SNAME("custom_item_clicked"), p_custom_mouse_index); } } @@ -4136,7 +4177,7 @@ int Tree::get_column_minimum_width(int p_column) const { // Check if the visible title of the column is wider. if (show_column_titles) { - min_width = MAX(cache.font->get_string_size(columns[p_column].title, cache.font_size).width + cache.bg->get_margin(SIDE_LEFT) + cache.bg->get_margin(SIDE_RIGHT), min_width); + min_width = MAX(cache.font->get_string_size(columns[p_column].title, HORIZONTAL_ALIGNMENT_LEFT, -1, cache.font_size).width + cache.bg->get_margin(SIDE_LEFT) + cache.bg->get_margin(SIDE_RIGHT), min_width); } if (!columns[p_column].clip_content) { @@ -4150,7 +4191,7 @@ int Tree::get_column_minimum_width(int p_column) const { depth += 1; } else { TreeItem *common_parent = item->get_parent(); - while (common_parent != next->get_parent()) { + while (common_parent != next->get_parent() && common_parent) { common_parent = common_parent->get_parent(); depth -= 1; } @@ -4415,32 +4456,6 @@ Control::TextDirection Tree::get_column_title_direction(int p_column) const { return columns[p_column].text_direction; } -void Tree::clear_column_title_opentype_features(int p_column) { - ERR_FAIL_INDEX(p_column, columns.size()); - columns.write[p_column].opentype_features.clear(); - update_column(p_column); - update(); -} - -void Tree::set_column_title_opentype_feature(int p_column, const String &p_name, int p_value) { - ERR_FAIL_INDEX(p_column, columns.size()); - int32_t tag = TS->name_to_tag(p_name); - if (!columns[p_column].opentype_features.has(tag) || (int)columns[p_column].opentype_features[tag] != p_value) { - columns.write[p_column].opentype_features[tag] = p_value; - update_column(p_column); - update(); - } -} - -int Tree::get_column_title_opentype_feature(int p_column, const String &p_name) const { - ERR_FAIL_INDEX_V(p_column, columns.size(), -1); - int32_t tag = TS->name_to_tag(p_name); - if (!columns[p_column].opentype_features.has(tag)) { - return -1; - } - return columns[p_column].opentype_features[tag]; -} - void Tree::set_column_title_language(int p_column, const String &p_language) { ERR_FAIL_INDEX(p_column, columns.size()); if (columns[p_column].language != p_language) { @@ -4467,7 +4482,8 @@ Point2 Tree::get_scroll() const { } void Tree::scroll_to_item(TreeItem *p_item, bool p_center_on_item) { - if (!is_visible_in_tree()) { + ERR_FAIL_NULL(p_item); + if (!is_visible_in_tree() || !p_item->is_visible()) { return; // Hack to work around crash in get_item_rect() if Tree is not in tree. } @@ -4591,7 +4607,7 @@ void Tree::_do_incr_search(const String &p_add) { TreeItem *Tree::_find_item_at_pos(TreeItem *p_item, const Point2 &p_pos, int &r_column, int &h, int §ion) const { Point2 pos = p_pos; - if (root != p_item || !hide_root) { + if ((root != p_item || !hide_root) && p_item->is_visible()) { h = compute_item_height(p_item) + cache.vseparation; if (pos.y < h) { if (drop_mode_flags == DROP_MODE_ON_ITEM) { @@ -4624,7 +4640,7 @@ TreeItem *Tree::_find_item_at_pos(TreeItem *p_item, const Point2 &p_pos, int &r_ h = 0; } - if (p_item->is_collapsed()) { + if (p_item->is_collapsed() || !p_item->is_visible()) { return nullptr; // do not try children, it's collapsed } @@ -4926,10 +4942,6 @@ void Tree::_bind_methods() { ClassDB::bind_method(D_METHOD("set_column_title_direction", "column", "direction"), &Tree::set_column_title_direction); ClassDB::bind_method(D_METHOD("get_column_title_direction", "column"), &Tree::get_column_title_direction); - ClassDB::bind_method(D_METHOD("set_column_title_opentype_feature", "column", "tag", "value"), &Tree::set_column_title_opentype_feature); - ClassDB::bind_method(D_METHOD("get_column_title_opentype_feature", "column", "tag"), &Tree::get_column_title_opentype_feature); - ClassDB::bind_method(D_METHOD("clear_column_title_opentype_features", "column"), &Tree::clear_column_title_opentype_features); - ClassDB::bind_method(D_METHOD("set_column_title_language", "column", "language"), &Tree::set_column_title_language); ClassDB::bind_method(D_METHOD("get_column_title_language", "column"), &Tree::get_column_title_language); @@ -4968,17 +4980,15 @@ void Tree::_bind_methods() { ADD_SIGNAL(MethodInfo("item_selected")); ADD_SIGNAL(MethodInfo("cell_selected")); ADD_SIGNAL(MethodInfo("multi_selected", PropertyInfo(Variant::OBJECT, "item", PROPERTY_HINT_RESOURCE_TYPE, "TreeItem"), PropertyInfo(Variant::INT, "column"), PropertyInfo(Variant::BOOL, "selected"))); - ADD_SIGNAL(MethodInfo("item_rmb_selected", PropertyInfo(Variant::VECTOR2, "position"))); - ADD_SIGNAL(MethodInfo("empty_rmb", PropertyInfo(Variant::VECTOR2, "position"))); - ADD_SIGNAL(MethodInfo("empty_tree_rmb_selected", PropertyInfo(Variant::VECTOR2, "position"))); + ADD_SIGNAL(MethodInfo("item_mouse_selected", PropertyInfo(Variant::VECTOR2, "position"), PropertyInfo(Variant::INT, "mouse_button_index"))); + ADD_SIGNAL(MethodInfo("empty_clicked", PropertyInfo(Variant::VECTOR2, "position"), PropertyInfo(Variant::INT, "mouse_button_index"))); ADD_SIGNAL(MethodInfo("item_edited")); - ADD_SIGNAL(MethodInfo("item_rmb_edited")); + ADD_SIGNAL(MethodInfo("custom_item_clicked", PropertyInfo(Variant::INT, "mouse_button_index"))); ADD_SIGNAL(MethodInfo("item_custom_button_pressed")); ADD_SIGNAL(MethodInfo("item_double_clicked")); ADD_SIGNAL(MethodInfo("item_collapsed", PropertyInfo(Variant::OBJECT, "item", PROPERTY_HINT_RESOURCE_TYPE, "TreeItem"))); ADD_SIGNAL(MethodInfo("check_propagated_to_item", PropertyInfo(Variant::OBJECT, "item", PROPERTY_HINT_RESOURCE_TYPE, "TreeItem"), PropertyInfo(Variant::INT, "column"))); - //ADD_SIGNAL( MethodInfo("item_double_clicked" ) ); - ADD_SIGNAL(MethodInfo("button_pressed", PropertyInfo(Variant::OBJECT, "item", PROPERTY_HINT_RESOURCE_TYPE, "TreeItem"), PropertyInfo(Variant::INT, "column"), PropertyInfo(Variant::INT, "id"))); + ADD_SIGNAL(MethodInfo("button_clicked", PropertyInfo(Variant::OBJECT, "item", PROPERTY_HINT_RESOURCE_TYPE, "TreeItem"), PropertyInfo(Variant::INT, "column"), PropertyInfo(Variant::INT, "id"), PropertyInfo(Variant::INT, "mouse_button_index"))); ADD_SIGNAL(MethodInfo("custom_popup_edited", PropertyInfo(Variant::BOOL, "arrow_clicked"))); ADD_SIGNAL(MethodInfo("item_activated")); ADD_SIGNAL(MethodInfo("column_title_pressed", PropertyInfo(Variant::INT, "column"))); @@ -5008,7 +5018,7 @@ Tree::Tree() { popup_editor_vb = memnew(VBoxContainer); popup_editor->add_child(popup_editor_vb); popup_editor_vb->add_theme_constant_override("separation", 0); - popup_editor_vb->set_anchors_and_offsets_preset(PRESET_WIDE); + popup_editor_vb->set_anchors_and_offsets_preset(PRESET_FULL_RECT); text_editor = memnew(LineEdit); popup_editor_vb->add_child(text_editor); text_editor->set_v_size_flags(SIZE_EXPAND_FILL); diff --git a/scene/gui/tree.h b/scene/gui/tree.h index b704495444..1690e7ac57 100644 --- a/scene/gui/tree.h +++ b/scene/gui/tree.h @@ -63,9 +63,8 @@ private: String text; String suffix; Ref<TextLine> text_buf; - Dictionary opentype_features; String language; - Control::StructuredTextParser st_parser = Control::STRUCTURED_TEXT_DEFAULT; + TextServer::StructuredTextParser st_parser = TextServer::STRUCTURED_TEXT_DEFAULT; Array st_args; Control::TextDirection text_direction = Control::TEXT_DIRECTION_INHERITED; bool dirty = true; @@ -124,6 +123,7 @@ private: Vector<Cell> cells; bool collapsed = false; // won't show children + bool visible = true; bool disable_folding = false; int custom_min_height = 0; @@ -209,6 +209,9 @@ private: void _propagate_check_through_children(int p_column, bool p_checked, bool p_emit_signal); void _propagate_check_through_parents(int p_column, bool p_emit_signal); + TreeItem *_get_prev_visible(bool p_wrap = false); + TreeItem *_get_next_visible(bool p_wrap = false); + public: void set_text(int p_column, String p_text); String get_text(int p_column) const; @@ -216,12 +219,8 @@ public: void set_text_direction(int p_column, Control::TextDirection p_text_direction); Control::TextDirection get_text_direction(int p_column) const; - void set_opentype_feature(int p_column, const String &p_name, int p_value); - int get_opentype_feature(int p_column, const String &p_name) const; - void clear_opentype_features(int p_column); - - void set_structured_text_bidi_override(int p_column, Control::StructuredTextParser p_parser); - Control::StructuredTextParser get_structured_text_bidi_override(int p_column) const; + void set_structured_text_bidi_override(int p_column, TextServer::StructuredTextParser p_parser); + TextServer::StructuredTextParser get_structured_text_bidi_override(int p_column) const; void set_structured_text_bidi_override_options(int p_column, Array p_args); Array get_structured_text_bidi_override_options(int p_column) const; @@ -273,6 +272,9 @@ public: void set_collapsed(bool p_collapsed); bool is_collapsed(); + void set_visible(bool p_visible); + bool is_visible(); + void uncollapse_tree(); void set_custom_minimum_height(int p_height); @@ -335,6 +337,7 @@ public: TreeItem *get_next_visible(bool p_wrap = false); TreeItem *get_child(int p_idx); + int get_visible_child_count(); int get_child_count(); Array get_children(); int get_index(); @@ -421,7 +424,6 @@ private: bool clip_content = false; String title; Ref<TextLine> text_buf; - Dictionary opentype_features; String language; Control::TextDirection text_direction = Control::TEXT_DIRECTION_INHERITED; ColumnInfo() { @@ -466,7 +468,7 @@ private: void _notification(int p_what); - void item_edited(int p_column, TreeItem *p_item, bool p_lmb = true); + void item_edited(int p_column, TreeItem *p_item, MouseButton p_custom_mouse_index = MouseButton::NONE); void item_changed(int p_column, TreeItem *p_item); void item_selected(int p_column, TreeItem *p_item); void item_deselected(int p_column, TreeItem *p_item); @@ -608,6 +610,8 @@ private: void _go_down(); void _go_up(); + bool _scroll(bool p_horizontal, float p_pages); + protected: static void _bind_methods(); @@ -658,10 +662,6 @@ public: void set_column_title_direction(int p_column, Control::TextDirection p_text_direction); Control::TextDirection get_column_title_direction(int p_column) const; - void set_column_title_opentype_feature(int p_column, const String &p_name, int p_value); - int get_column_title_opentype_feature(int p_column, const String &p_name) const; - void clear_column_title_opentype_features(int p_column); - void set_column_title_language(int p_column, const String &p_language); String get_column_title_language(int p_column) const; diff --git a/scene/gui/video_stream_player.cpp b/scene/gui/video_stream_player.cpp index ca2dad71af..86334882fa 100644 --- a/scene/gui/video_stream_player.cpp +++ b/scene/gui/video_stream_player.cpp @@ -174,6 +174,28 @@ void VideoStreamPlayer::_notification(int p_notification) { Size2 s = expand ? get_size() : texture->get_size(); draw_texture_rect(texture, Rect2(Point2(), s), false); } break; + + case NOTIFICATION_PAUSED: { + if (is_playing() && !is_paused()) { + paused_from_tree = true; + if (playback.is_valid()) { + playback->set_paused(true); + set_process_internal(false); + } + last_audio_time = 0; + } + } break; + + case NOTIFICATION_UNPAUSED: { + if (paused_from_tree) { + paused_from_tree = false; + if (playback.is_valid()) { + playback->set_paused(false); + set_process_internal(true); + } + last_audio_time = 0; + } + } break; } } @@ -255,6 +277,10 @@ void VideoStreamPlayer::play() { playback->play(); set_process_internal(true); last_audio_time = 0; + + if (!can_process()) { + _notification(NOTIFICATION_PAUSED); + } } void VideoStreamPlayer::stop() { @@ -281,6 +307,14 @@ bool VideoStreamPlayer::is_playing() const { void VideoStreamPlayer::set_paused(bool p_paused) { paused = p_paused; + if (!p_paused && !can_process()) { + paused_from_tree = true; + return; + } else if (p_paused && paused_from_tree) { + paused_from_tree = false; + return; + } + if (playback.is_valid()) { playback->set_paused(p_paused); set_process_internal(!p_paused); @@ -443,12 +477,12 @@ void VideoStreamPlayer::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::INT, "audio_track", PROPERTY_HINT_RANGE, "0,128,1"), "set_audio_track", "get_audio_track"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "stream", PROPERTY_HINT_RESOURCE_TYPE, "VideoStream"), "set_stream", "get_stream"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "volume_db", PROPERTY_HINT_RANGE, "-80,24,0.01"), "set_volume_db", "get_volume_db"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "volume_db", PROPERTY_HINT_RANGE, "-80,24,0.01,suffix:dB"), "set_volume_db", "get_volume_db"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "volume", PROPERTY_HINT_RANGE, "0,15,0.01,exp", PROPERTY_USAGE_NONE), "set_volume", "get_volume"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "autoplay"), "set_autoplay", "has_autoplay"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "paused"), "set_paused", "is_paused"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "expand"), "set_expand", "has_expand"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "buffering_msec", PROPERTY_HINT_RANGE, "10,1000"), "set_buffering_msec", "get_buffering_msec"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "buffering_msec", PROPERTY_HINT_RANGE, "10,1000,suffix:ms"), "set_buffering_msec", "get_buffering_msec"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "stream_position", PROPERTY_HINT_RANGE, "0,1280000,0.1", PROPERTY_USAGE_NONE), "set_stream_position", "get_stream_position"); ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "bus", PROPERTY_HINT_ENUM, ""), "set_bus", "get_bus"); diff --git a/scene/gui/video_stream_player.h b/scene/gui/video_stream_player.h index 130b2901f1..913e7905b6 100644 --- a/scene/gui/video_stream_player.h +++ b/scene/gui/video_stream_player.h @@ -60,10 +60,11 @@ class VideoStreamPlayer : public Control { int wait_resampler_limit = 2; bool paused = false; + bool paused_from_tree = false; bool autoplay = false; float volume = 1.0; double last_audio_time = 0.0; - bool expand = true; + bool expand = false; bool loops = false; int buffering_ms = 500; int audio_track = 0; diff --git a/scene/main/canvas_item.cpp b/scene/main/canvas_item.cpp index 708359f106..e298805aca 100644 --- a/scene/main/canvas_item.cpp +++ b/scene/main/canvas_item.cpp @@ -230,16 +230,16 @@ void CanvasItem::_enter_canvas() { RenderingServer::get_singleton()->canvas_item_set_parent(canvas_item, canvas); - group = "root_canvas" + itos(canvas.get_id()); + canvas_group = "root_canvas" + itos(canvas.get_id()); - add_to_group(group); + add_to_group(canvas_group); if (canvas_layer) { canvas_layer->reset_sort_index(); } else { get_viewport()->gui_reset_canvas_sort_index(); } - get_tree()->call_group_flags(SceneTree::GROUP_CALL_UNIQUE, group, SNAME("_top_level_raise_self")); + get_tree()->call_group_flags(SceneTree::GROUP_CALL_UNIQUE | SceneTree::GROUP_CALL_DEFERRED, canvas_group, SNAME("_top_level_raise_self")); } else { CanvasItem *parent = get_parent_item(); @@ -258,7 +258,10 @@ void CanvasItem::_exit_canvas() { notification(NOTIFICATION_EXIT_CANVAS, true); //reverse the notification RenderingServer::get_singleton()->canvas_item_set_parent(canvas_item, RID()); canvas_layer = nullptr; - group = StringName(); + if (canvas_group != StringName()) { + remove_from_group(canvas_group); + canvas_group = StringName(); + } } void CanvasItem::_notification(int p_what) { @@ -319,8 +322,8 @@ void CanvasItem::_notification(int p_what) { break; } - if (group != StringName()) { - get_tree()->call_group_flags(SceneTree::GROUP_CALL_UNIQUE, group, "_top_level_raise_self"); + if (canvas_group != StringName()) { + get_tree()->call_group_flags(SceneTree::GROUP_CALL_UNIQUE | SceneTree::GROUP_CALL_DEFERRED, canvas_group, "_top_level_raise_self"); } else { CanvasItem *p = get_parent_item(); ERR_FAIL_COND(!p); @@ -658,24 +661,48 @@ void CanvasItem::draw_multimesh(const Ref<MultiMesh> &p_multimesh, const Ref<Tex RenderingServer::get_singleton()->canvas_item_add_multimesh(canvas_item, p_multimesh->get_rid(), texture_rid); } -void CanvasItem::draw_string(const Ref<Font> &p_font, const Point2 &p_pos, const String &p_text, HorizontalAlignment p_alignment, real_t p_width, int p_size, const Color &p_modulate, int p_outline_size, const Color &p_outline_modulate, uint16_t p_flags) const { +void CanvasItem::draw_string(const Ref<Font> &p_font, const Point2 &p_pos, const String &p_text, HorizontalAlignment p_alignment, float p_width, int p_font_size, const Color &p_modulate, BitField<TextServer::JustificationFlag> p_jst_flags, TextServer::Direction p_direction, TextServer::Orientation p_orientation) const { + ERR_FAIL_COND_MSG(!drawing, "Drawing is only allowed inside NOTIFICATION_DRAW, _draw() function or 'draw' signal."); + ERR_FAIL_COND(p_font.is_null()); + + p_font->draw_string(canvas_item, p_pos, p_text, p_alignment, p_width, p_font_size, p_modulate, p_jst_flags, p_direction, p_orientation); +} + +void CanvasItem::draw_multiline_string(const Ref<Font> &p_font, const Point2 &p_pos, const String &p_text, HorizontalAlignment p_alignment, float p_width, int p_font_size, int p_max_lines, const Color &p_modulate, BitField<TextServer::LineBreakFlag> p_brk_flags, BitField<TextServer::JustificationFlag> p_jst_flags, TextServer::Direction p_direction, TextServer::Orientation p_orientation) const { + ERR_FAIL_COND_MSG(!drawing, "Drawing is only allowed inside NOTIFICATION_DRAW, _draw() function or 'draw' signal."); + ERR_FAIL_COND(p_font.is_null()); + + p_font->draw_multiline_string(canvas_item, p_pos, p_text, p_alignment, p_width, p_font_size, p_max_lines, p_modulate, p_brk_flags, p_jst_flags, p_direction, p_orientation); +} + +void CanvasItem::draw_string_outline(const Ref<Font> &p_font, const Point2 &p_pos, const String &p_text, HorizontalAlignment p_alignment, float p_width, int p_font_size, int p_size, const Color &p_modulate, BitField<TextServer::JustificationFlag> p_jst_flags, TextServer::Direction p_direction, TextServer::Orientation p_orientation) const { + ERR_FAIL_COND_MSG(!drawing, "Drawing is only allowed inside NOTIFICATION_DRAW, _draw() function or 'draw' signal."); + ERR_FAIL_COND(p_font.is_null()); + + p_font->draw_string_outline(canvas_item, p_pos, p_text, p_alignment, p_width, p_font_size, p_size, p_modulate, p_jst_flags, p_direction, p_orientation); +} + +void CanvasItem::draw_multiline_string_outline(const Ref<Font> &p_font, const Point2 &p_pos, const String &p_text, HorizontalAlignment p_alignment, float p_width, int p_font_size, int p_max_lines, int p_size, const Color &p_modulate, BitField<TextServer::LineBreakFlag> p_brk_flags, BitField<TextServer::JustificationFlag> p_jst_flags, TextServer::Direction p_direction, TextServer::Orientation p_orientation) const { ERR_FAIL_COND_MSG(!drawing, "Drawing is only allowed inside NOTIFICATION_DRAW, _draw() function or 'draw' signal."); ERR_FAIL_COND(p_font.is_null()); - p_font->draw_string(canvas_item, p_pos, p_text, p_alignment, p_width, p_size, p_modulate, p_outline_size, p_outline_modulate, p_flags); + + p_font->draw_multiline_string_outline(canvas_item, p_pos, p_text, p_alignment, p_width, p_font_size, p_max_lines, p_size, p_modulate, p_brk_flags, p_jst_flags, p_direction, p_orientation); } -void CanvasItem::draw_multiline_string(const Ref<Font> &p_font, const Point2 &p_pos, const String &p_text, HorizontalAlignment p_alignment, real_t p_width, int p_max_lines, int p_size, const Color &p_modulate, int p_outline_size, const Color &p_outline_modulate, uint16_t p_flags) const { +void CanvasItem::draw_char(const Ref<Font> &p_font, const Point2 &p_pos, const String &p_char, int p_font_size, const Color &p_modulate) const { ERR_FAIL_COND_MSG(!drawing, "Drawing is only allowed inside NOTIFICATION_DRAW, _draw() function or 'draw' signal."); + ERR_FAIL_COND(p_char.length() != 1); ERR_FAIL_COND(p_font.is_null()); - p_font->draw_multiline_string(canvas_item, p_pos, p_text, p_alignment, p_width, p_max_lines, p_size, p_modulate, p_outline_size, p_outline_modulate, p_flags); + + p_font->draw_char(canvas_item, p_pos, p_char[0], p_font_size, p_modulate); } -real_t CanvasItem::draw_char(const Ref<Font> &p_font, const Point2 &p_pos, const String &p_char, const String &p_next, int p_size, const Color &p_modulate, int p_outline_size, const Color &p_outline_modulate) const { - ERR_FAIL_COND_V_MSG(!drawing, 0.f, "Drawing is only allowed inside NOTIFICATION_DRAW, _draw() function or 'draw' signal."); - ERR_FAIL_COND_V(p_font.is_null(), 0.f); - ERR_FAIL_COND_V(p_char.length() != 1, 0.f); +void CanvasItem::draw_char_outline(const Ref<Font> &p_font, const Point2 &p_pos, const String &p_char, int p_font_size, int p_size, const Color &p_modulate) const { + ERR_FAIL_COND_MSG(!drawing, "Drawing is only allowed inside NOTIFICATION_DRAW, _draw() function or 'draw' signal."); + ERR_FAIL_COND(p_char.length() != 1); + ERR_FAIL_COND(p_font.is_null()); - return p_font->draw_char(canvas_item, p_pos, p_char[0], p_next.get_data()[0], p_size, p_modulate, p_outline_size, p_outline_modulate); + p_font->draw_char_outline(canvas_item, p_pos, p_char[0], p_font_size, p_size, p_modulate); } void CanvasItem::_notify_transform(CanvasItem *p_node) { @@ -900,9 +927,12 @@ void CanvasItem::_bind_methods() { ClassDB::bind_method(D_METHOD("draw_primitive", "points", "colors", "uvs", "texture", "width"), &CanvasItem::draw_primitive, DEFVAL(Ref<Texture2D>()), DEFVAL(1.0)); ClassDB::bind_method(D_METHOD("draw_polygon", "points", "colors", "uvs", "texture"), &CanvasItem::draw_polygon, DEFVAL(PackedVector2Array()), DEFVAL(Ref<Texture2D>())); ClassDB::bind_method(D_METHOD("draw_colored_polygon", "points", "color", "uvs", "texture"), &CanvasItem::draw_colored_polygon, DEFVAL(PackedVector2Array()), DEFVAL(Ref<Texture2D>())); - ClassDB::bind_method(D_METHOD("draw_string", "font", "pos", "text", "alignment", "width", "size", "modulate", "outline_size", "outline_modulate", "flags"), &CanvasItem::draw_string, DEFVAL(HORIZONTAL_ALIGNMENT_LEFT), DEFVAL(-1), DEFVAL(Font::DEFAULT_FONT_SIZE), DEFVAL(Color(1, 1, 1)), DEFVAL(0), DEFVAL(Color(1, 1, 1, 0)), DEFVAL(TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND)); - ClassDB::bind_method(D_METHOD("draw_multiline_string", "font", "pos", "text", "alignment", "width", "max_lines", "size", "modulate", "outline_size", "outline_modulate", "flags"), &CanvasItem::draw_multiline_string, DEFVAL(HORIZONTAL_ALIGNMENT_LEFT), DEFVAL(-1), DEFVAL(-1), DEFVAL(Font::DEFAULT_FONT_SIZE), DEFVAL(Color(1, 1, 1)), DEFVAL(0), DEFVAL(Color(1, 1, 1, 0)), DEFVAL(TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND | TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND)); - ClassDB::bind_method(D_METHOD("draw_char", "font", "pos", "char", "next", "size", "modulate", "outline_size", "outline_modulate"), &CanvasItem::draw_char, DEFVAL(""), DEFVAL(Font::DEFAULT_FONT_SIZE), DEFVAL(Color(1, 1, 1)), DEFVAL(0), DEFVAL(Color(1, 1, 1, 0))); + ClassDB::bind_method(D_METHOD("draw_string", "font", "pos", "text", "alignment", "width", "font_size", "modulate", "jst_flags", "direction", "orientation"), &CanvasItem::draw_string, DEFVAL(HORIZONTAL_ALIGNMENT_LEFT), DEFVAL(-1), DEFVAL(Font::DEFAULT_FONT_SIZE), DEFVAL(Color(1.0, 1.0, 1.0)), DEFVAL(TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND), DEFVAL(TextServer::DIRECTION_AUTO), DEFVAL(TextServer::ORIENTATION_HORIZONTAL)); + ClassDB::bind_method(D_METHOD("draw_multiline_string", "font", "pos", "text", "alignment", "width", "font_size", "max_lines", "modulate", "brk_flags", "jst_flags", "direction", "orientation"), &CanvasItem::draw_multiline_string, DEFVAL(HORIZONTAL_ALIGNMENT_LEFT), DEFVAL(-1), DEFVAL(Font::DEFAULT_FONT_SIZE), DEFVAL(-1), DEFVAL(Color(1.0, 1.0, 1.0)), DEFVAL(TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND), DEFVAL(TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND), DEFVAL(TextServer::DIRECTION_AUTO), DEFVAL(TextServer::ORIENTATION_HORIZONTAL)); + ClassDB::bind_method(D_METHOD("draw_string_outline", "font", "pos", "text", "alignment", "width", "font_size", "size", "modulate", "jst_flags", "direction", "orientation"), &CanvasItem::draw_string_outline, DEFVAL(HORIZONTAL_ALIGNMENT_LEFT), DEFVAL(-1), DEFVAL(Font::DEFAULT_FONT_SIZE), DEFVAL(1), DEFVAL(Color(1.0, 1.0, 1.0)), DEFVAL(TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND), DEFVAL(TextServer::DIRECTION_AUTO), DEFVAL(TextServer::ORIENTATION_HORIZONTAL)); + ClassDB::bind_method(D_METHOD("draw_multiline_string_outline", "font", "pos", "text", "alignment", "width", "font_size", "max_lines", "size", "modulate", "brk_flags", "jst_flags", "direction", "orientation"), &CanvasItem::draw_multiline_string_outline, DEFVAL(HORIZONTAL_ALIGNMENT_LEFT), DEFVAL(-1), DEFVAL(Font::DEFAULT_FONT_SIZE), DEFVAL(-1), DEFVAL(1), DEFVAL(Color(1.0, 1.0, 1.0)), DEFVAL(TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND), DEFVAL(TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND), DEFVAL(TextServer::DIRECTION_AUTO), DEFVAL(TextServer::ORIENTATION_HORIZONTAL)); + ClassDB::bind_method(D_METHOD("draw_char", "font", "pos", "char", "font_size", "modulate"), &CanvasItem::draw_char, DEFVAL(Font::DEFAULT_FONT_SIZE), DEFVAL(Color(1.0, 1.0, 1.0))); + ClassDB::bind_method(D_METHOD("draw_char_outline", "font", "pos", "char", "font_size", "size", "modulate"), &CanvasItem::draw_char_outline, DEFVAL(Font::DEFAULT_FONT_SIZE), DEFVAL(-1), DEFVAL(Color(1.0, 1.0, 1.0))); ClassDB::bind_method(D_METHOD("draw_mesh", "mesh", "texture", "transform", "modulate"), &CanvasItem::draw_mesh, DEFVAL(Transform2D()), DEFVAL(Color(1, 1, 1, 1))); ClassDB::bind_method(D_METHOD("draw_multimesh", "multimesh", "texture"), &CanvasItem::draw_multimesh); ClassDB::bind_method(D_METHOD("draw_set_transform", "position", "rotation", "scale"), &CanvasItem::draw_set_transform, DEFVAL(0.0), DEFVAL(Size2(1.0, 1.0))); @@ -1293,7 +1323,7 @@ void CanvasTexture::_bind_methods() { ADD_GROUP("Diffuse", "diffuse_"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "diffuse_texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_diffuse_texture", "get_diffuse_texture"); - ADD_GROUP("Normalmap", "normal_"); + ADD_GROUP("NormalMap", "normal_"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "normal_texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_normal_texture", "get_normal_texture"); ADD_GROUP("Specular", "specular_"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "specular_texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_specular_texture", "get_specular_texture"); diff --git a/scene/main/canvas_item.h b/scene/main/canvas_item.h index ad64f1ab5e..f5df6512ee 100644 --- a/scene/main/canvas_item.h +++ b/scene/main/canvas_item.h @@ -70,7 +70,7 @@ private: mutable SelfList<Node> xform_change; RID canvas_item; - StringName group; + StringName canvas_group; CanvasLayer *canvas_layer = nullptr; @@ -235,9 +235,14 @@ public: void draw_mesh(const Ref<Mesh> &p_mesh, const Ref<Texture2D> &p_texture, const Transform2D &p_transform = Transform2D(), const Color &p_modulate = Color(1, 1, 1)); void draw_multimesh(const Ref<MultiMesh> &p_multimesh, const Ref<Texture2D> &p_texture); - void draw_string(const Ref<Font> &p_font, const Point2 &p_pos, const String &p_text, HorizontalAlignment p_alignment = HORIZONTAL_ALIGNMENT_LEFT, real_t p_width = -1, int p_size = Font::DEFAULT_FONT_SIZE, const Color &p_modulate = Color(1, 1, 1), int p_outline_size = 0, const Color &p_outline_modulate = Color(1, 1, 1, 0), uint16_t p_flags = TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND) const; - void draw_multiline_string(const Ref<Font> &p_font, const Point2 &p_pos, const String &p_text, HorizontalAlignment p_alignment = HORIZONTAL_ALIGNMENT_LEFT, real_t p_width = -1, int p_max_lines = -1, int p_size = Font::DEFAULT_FONT_SIZE, const Color &p_modulate = Color(1, 1, 1), int p_outline_size = 0, const Color &p_outline_modulate = Color(1, 1, 1, 0), uint16_t p_flags = TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND | TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND) const; - real_t draw_char(const Ref<Font> &p_font, const Point2 &p_pos, const String &p_char, const String &p_next = "", int p_size = Font::DEFAULT_FONT_SIZE, const Color &p_modulate = Color(1, 1, 1), int p_outline_size = 0, const Color &p_outline_modulate = Color(1, 1, 1, 0)) const; + void draw_string(const Ref<Font> &p_font, const Point2 &p_pos, const String &p_text, HorizontalAlignment p_alignment = HORIZONTAL_ALIGNMENT_LEFT, float p_width = -1, int p_font_size = Font::DEFAULT_FONT_SIZE, const Color &p_modulate = Color(1.0, 1.0, 1.0), BitField<TextServer::JustificationFlag> p_jst_flags = TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND, TextServer::Direction p_direction = TextServer::DIRECTION_AUTO, TextServer::Orientation p_orientation = TextServer::ORIENTATION_HORIZONTAL) const; + void draw_multiline_string(const Ref<Font> &p_font, const Point2 &p_pos, const String &p_text, HorizontalAlignment p_alignment = HORIZONTAL_ALIGNMENT_LEFT, float p_width = -1, int p_font_size = Font::DEFAULT_FONT_SIZE, int p_max_lines = -1, const Color &p_modulate = Color(1.0, 1.0, 1.0), BitField<TextServer::LineBreakFlag> p_brk_flags = TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND, BitField<TextServer::JustificationFlag> p_jst_flags = TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND, TextServer::Direction p_direction = TextServer::DIRECTION_AUTO, TextServer::Orientation p_orientation = TextServer::ORIENTATION_HORIZONTAL) const; + + void draw_string_outline(const Ref<Font> &p_font, const Point2 &p_pos, const String &p_text, HorizontalAlignment p_alignment = HORIZONTAL_ALIGNMENT_LEFT, float p_width = -1, int p_size = 1, int p_font_size = Font::DEFAULT_FONT_SIZE, const Color &p_modulate = Color(1.0, 1.0, 1.0), BitField<TextServer::JustificationFlag> p_jst_flags = TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND, TextServer::Direction p_direction = TextServer::DIRECTION_AUTO, TextServer::Orientation p_orientation = TextServer::ORIENTATION_HORIZONTAL) const; + void draw_multiline_string_outline(const Ref<Font> &p_font, const Point2 &p_pos, const String &p_text, HorizontalAlignment p_alignment = HORIZONTAL_ALIGNMENT_LEFT, float p_width = -1, int p_font_size = Font::DEFAULT_FONT_SIZE, int p_max_lines = -1, int p_size = 1, const Color &p_modulate = Color(1.0, 1.0, 1.0), BitField<TextServer::LineBreakFlag> p_brk_flags = TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND, BitField<TextServer::JustificationFlag> p_jst_flags = TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND, TextServer::Direction p_direction = TextServer::DIRECTION_AUTO, TextServer::Orientation p_orientation = TextServer::ORIENTATION_HORIZONTAL) const; + + void draw_char(const Ref<Font> &p_font, const Point2 &p_pos, const String &p_char, int p_font_size = Font::DEFAULT_FONT_SIZE, const Color &p_modulate = Color(1.0, 1.0, 1.0)) const; + void draw_char_outline(const Ref<Font> &p_font, const Point2 &p_pos, const String &p_char, int p_font_size = Font::DEFAULT_FONT_SIZE, int p_size = 1, const Color &p_modulate = Color(1.0, 1.0, 1.0)) const; void draw_set_transform(const Point2 &p_offset, real_t p_rot = 0.0, const Size2 &p_scale = Size2(1.0, 1.0)); void draw_set_transform_matrix(const Transform2D &p_matrix); diff --git a/scene/main/canvas_layer.cpp b/scene/main/canvas_layer.cpp index 7aa4d391f8..3a071ef542 100644 --- a/scene/main/canvas_layer.cpp +++ b/scene/main/canvas_layer.cpp @@ -96,7 +96,7 @@ void CanvasLayer::_update_xform() { } void CanvasLayer::_update_locrotscale() { - ofs = transform.elements[2]; + ofs = transform.columns[2]; rot = transform.get_rotation(); scale = transform.get_scale(); locrotscale_dirty = false; @@ -328,10 +328,10 @@ void CanvasLayer::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::INT, "layer", PROPERTY_HINT_RANGE, "-128,128,1"), "set_layer", "get_layer"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "visible"), "set_visible", "is_visible"); ADD_GROUP("Transform", ""); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "offset"), "set_offset", "get_offset"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "offset", PROPERTY_HINT_NONE, "suffix:px"), "set_offset", "get_offset"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "rotation", PROPERTY_HINT_RANGE, "-1080,1080,0.1,or_lesser,or_greater,radians"), "set_rotation", "get_rotation"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "scale"), "set_scale", "get_scale"); - ADD_PROPERTY(PropertyInfo(Variant::TRANSFORM2D, "transform"), "set_transform", "get_transform"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "scale", PROPERTY_HINT_LINK), "set_scale", "get_scale"); + ADD_PROPERTY(PropertyInfo(Variant::TRANSFORM2D, "transform", PROPERTY_HINT_NONE, "suffix:px"), "set_transform", "get_transform"); ADD_GROUP("", ""); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "custom_viewport", PROPERTY_HINT_RESOURCE_TYPE, "Viewport", PROPERTY_USAGE_NONE), "set_custom_viewport", "get_custom_viewport"); ADD_GROUP("Follow Viewport", "follow_viewport"); diff --git a/scene/main/http_request.cpp b/scene/main/http_request.cpp index 34b0e19d31..9a23bc65bf 100644 --- a/scene/main/http_request.cpp +++ b/scene/main/http_request.cpp @@ -607,12 +607,12 @@ void HTTPRequest::_bind_methods() { ClassDB::bind_method(D_METHOD("set_https_proxy", "host", "port"), &HTTPRequest::set_https_proxy); ADD_PROPERTY(PropertyInfo(Variant::STRING, "download_file", PROPERTY_HINT_FILE), "set_download_file", "get_download_file"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "download_chunk_size", PROPERTY_HINT_RANGE, "256,16777216"), "set_download_chunk_size", "get_download_chunk_size"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "download_chunk_size", PROPERTY_HINT_RANGE, "256,16777216,suffix:B"), "set_download_chunk_size", "get_download_chunk_size"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_threads"), "set_use_threads", "is_using_threads"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "accept_gzip"), "set_accept_gzip", "is_accepting_gzip"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "body_size_limit", PROPERTY_HINT_RANGE, "-1,2000000000"), "set_body_size_limit", "get_body_size_limit"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "body_size_limit", PROPERTY_HINT_RANGE, "-1,2000000000,suffix:B"), "set_body_size_limit", "get_body_size_limit"); ADD_PROPERTY(PropertyInfo(Variant::INT, "max_redirects", PROPERTY_HINT_RANGE, "-1,64"), "set_max_redirects", "get_max_redirects"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "timeout", PROPERTY_HINT_RANGE, "0,3600,0.1,or_greater"), "set_timeout", "get_timeout"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "timeout", PROPERTY_HINT_RANGE, "0,3600,0.1,or_greater,suffix:s"), "set_timeout", "get_timeout"); ADD_SIGNAL(MethodInfo("request_completed", PropertyInfo(Variant::INT, "result"), PropertyInfo(Variant::INT, "response_code"), PropertyInfo(Variant::PACKED_STRING_ARRAY, "headers"), PropertyInfo(Variant::PACKED_BYTE_ARRAY, "body"))); diff --git a/scene/main/missing_node.cpp b/scene/main/missing_node.cpp new file mode 100644 index 0000000000..395fdad9e4 --- /dev/null +++ b/scene/main/missing_node.cpp @@ -0,0 +1,98 @@ +/*************************************************************************/ +/* missing_node.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 "missing_node.h" + +bool MissingNode::_set(const StringName &p_name, const Variant &p_value) { + if (is_recording_properties()) { + properties.insert(p_name, p_value); + return true; //always valid to set (add) + } else { + if (!properties.has(p_name)) { + return false; + } + + properties[p_name] = p_value; + return true; + } +} + +bool MissingNode::_get(const StringName &p_name, Variant &r_ret) const { + if (!properties.has(p_name)) { + return false; + } + r_ret = properties[p_name]; + return true; +} + +void MissingNode::_get_property_list(List<PropertyInfo> *p_list) const { + for (const KeyValue<StringName, Variant> &E : properties) { + p_list->push_back(PropertyInfo(E.value.get_type(), E.key)); + } +} + +void MissingNode::set_original_class(const String &p_class) { + original_class = p_class; +} + +String MissingNode::get_original_class() const { + return original_class; +} + +void MissingNode::set_recording_properties(bool p_enable) { + recording_properties = p_enable; +} + +bool MissingNode::is_recording_properties() const { + return recording_properties; +} + +TypedArray<String> MissingNode::get_configuration_warnings() const { + // The mere existence of this node is warning. + TypedArray<String> ret; + ret.push_back(vformat(RTR("This node was saved as class type '%s', which was no longer available when this scene was loaded."), original_class)); + ret.push_back(RTR("Data from the original node is kept as a placeholder until this type of node is available again. It can hence be safely re-saved without risk of data loss.")); + return ret; +} + +void MissingNode::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_original_class", "name"), &MissingNode::set_original_class); + ClassDB::bind_method(D_METHOD("get_original_class"), &MissingNode::get_original_class); + + ClassDB::bind_method(D_METHOD("set_recording_properties", "enable"), &MissingNode::set_recording_properties); + ClassDB::bind_method(D_METHOD("is_recording_properties"), &MissingNode::is_recording_properties); + + // Expose, but not save. + ADD_PROPERTY(PropertyInfo(Variant::STRING, "original_class", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_original_class", "get_original_class"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "recording_properties", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_recording_properties", "is_recording_properties"); +} + +MissingNode::MissingNode() { +} diff --git a/scene/main/missing_node.h b/scene/main/missing_node.h new file mode 100644 index 0000000000..d200fbb47f --- /dev/null +++ b/scene/main/missing_node.h @@ -0,0 +1,63 @@ +/*************************************************************************/ +/* missing_node.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 MISSING_NODE_H +#define MISSING_NODE_H + +#include "core/io/missing_resource.h" +#include "scene/main/node.h" + +class MissingNode : public Node { + GDCLASS(MissingNode, Node) + HashMap<StringName, Variant> properties; + + String original_class; + bool recording_properties = false; + +protected: + bool _set(const StringName &p_name, const Variant &p_value); + bool _get(const StringName &p_name, Variant &r_ret) const; + void _get_property_list(List<PropertyInfo> *p_list) const; + + static void _bind_methods(); + +public: + void set_original_class(const String &p_class); + String get_original_class() const; + + void set_recording_properties(bool p_enable); + bool is_recording_properties() const; + + virtual TypedArray<String> get_configuration_warnings() const override; + + MissingNode(); +}; + +#endif // MISSING_NODE_H diff --git a/scene/main/node.cpp b/scene/main/node.cpp index 34bb1cde05..b4701637a4 100644 --- a/scene/main/node.cpp +++ b/scene/main/node.cpp @@ -257,6 +257,9 @@ void Node::_propagate_after_exit_tree() { } if (!found) { + if (data.unique_name_in_owner) { + _release_unique_name_in_owner(); + } data.owner->data.owned.erase(data.OW); data.owner = nullptr; } @@ -297,7 +300,7 @@ void Node::_propagate_exit_tree() { if (data.parent) { Variant c = this; const Variant *cptr = &c; - data.parent->emit_signalp(SNAME("child_exited_tree"), &cptr, 1); + data.parent->emit_signalp(SNAME("child_exiting_tree"), &cptr, 1); } // exit groups @@ -381,11 +384,7 @@ void Node::_move_child(Node *p_child, int p_pos, bool p_ignore_end) { for (int i = motion_from; i <= motion_to; i++) { data.children[i]->notification(NOTIFICATION_MOVED_IN_PARENT); } - for (const KeyValue<StringName, GroupData> &E : p_child->data.grouped) { - if (E.value.group) { - E.value.group->changed = true; - } - } + p_child->_propagate_groups_dirty(); data.blocked--; } @@ -405,6 +404,18 @@ void Node::raise() { } } +void Node::_propagate_groups_dirty() { + for (const KeyValue<StringName, GroupData> &E : data.grouped) { + if (E.value.group) { + E.value.group->changed = true; + } + } + + for (int i = 0; i < data.children.size(); i++) { + data.children[i]->_propagate_groups_dirty(); + } +} + void Node::add_child_notify(Node *p_child) { // to be used when not wanted } @@ -417,6 +428,9 @@ void Node::move_child_notify(Node *p_child) { // to be used when not wanted } +void Node::owner_changed_notify() { +} + void Node::set_physics_process(bool p_process) { if (data.physics_process == p_process) { return; @@ -425,9 +439,9 @@ void Node::set_physics_process(bool p_process) { data.physics_process = p_process; if (data.physics_process) { - add_to_group("physics_process", false); + add_to_group(SNAME("_physics_process"), false); } else { - remove_from_group("physics_process"); + remove_from_group(SNAME("_physics_process")); } } @@ -443,9 +457,9 @@ void Node::set_physics_process_internal(bool p_process_internal) { data.physics_process_internal = p_process_internal; if (data.physics_process_internal) { - add_to_group("physics_process_internal", false); + add_to_group(SNAME("_physics_process_internal"), false); } else { - remove_from_group("physics_process_internal"); + remove_from_group(SNAME("_physics_process_internal")); } } @@ -650,21 +664,10 @@ void Node::rpcp(int p_peer_id, const StringName &p_method, const Variant **p_arg } Ref<MultiplayerAPI> Node::get_multiplayer() const { - if (multiplayer.is_valid()) { - return multiplayer; - } if (!is_inside_tree()) { return Ref<MultiplayerAPI>(); } - return get_tree()->get_multiplayer(); -} - -Ref<MultiplayerAPI> Node::get_custom_multiplayer() const { - return multiplayer; -} - -void Node::set_custom_multiplayer(Ref<MultiplayerAPI> p_multiplayer) { - multiplayer = p_multiplayer; + return get_tree()->get_multiplayer(get_path()); } Vector<Multiplayer::RPCConfig> Node::get_node_rpc_methods() const { @@ -767,9 +770,9 @@ void Node::set_process(bool p_process) { data.process = p_process; if (data.process) { - add_to_group("process", false); + add_to_group(SNAME("_process"), false); } else { - remove_from_group("process"); + remove_from_group(SNAME("_process")); } } @@ -785,9 +788,9 @@ void Node::set_process_internal(bool p_process_internal) { data.process_internal = p_process_internal; if (data.process_internal) { - add_to_group("process_internal", false); + add_to_group(SNAME("_process_internal"), false); } else { - remove_from_group("process_internal"); + remove_from_group(SNAME("_process_internal")); } } @@ -804,19 +807,19 @@ void Node::set_process_priority(int p_priority) { } if (is_processing()) { - data.tree->make_group_changed("process"); + data.tree->make_group_changed(SNAME("_process")); } if (is_processing_internal()) { - data.tree->make_group_changed("process_internal"); + data.tree->make_group_changed(SNAME("_process_internal")); } if (is_physics_processing()) { - data.tree->make_group_changed("physics_process"); + data.tree->make_group_changed(SNAME("_physics_process")); } if (is_physics_processing_internal()) { - data.tree->make_group_changed("physics_process_internal"); + data.tree->make_group_changed(SNAME("_physics_process_internal")); } } @@ -917,12 +920,20 @@ void Node::set_name(const String &p_name) { String name = p_name.validate_node_name(); ERR_FAIL_COND(name.is_empty()); + + if (data.unique_name_in_owner && data.owner) { + _release_unique_name_in_owner(); + } data.name = name; if (data.parent) { data.parent->_validate_child_name(this, true); } + if (data.unique_name_in_owner && data.owner) { + _acquire_unique_name_in_owner(); + } + propagate_notification(NOTIFICATION_PATH_RENAMED); if (is_inside_tree()) { @@ -1303,6 +1314,24 @@ Node *Node::get_node_or_null(const NodePath &p_path) const { next = root; } + } else if (name.is_node_unique_name()) { + if (current->data.owned_unique_nodes.size()) { + // Has unique nodes in ownership + Node **unique = current->data.owned_unique_nodes.getptr(name); + if (!unique) { + return nullptr; + } + next = *unique; + } else if (current->data.owner) { + Node **unique = current->data.owner->data.owned_unique_nodes.getptr(name); + if (!unique) { + return nullptr; + } + next = *unique; + } else { + return nullptr; + } + } else { next = nullptr; @@ -1344,9 +1373,39 @@ bool Node::has_node(const NodePath &p_path) const { return get_node_or_null(p_path) != nullptr; } -TypedArray<Node> Node::find_nodes(const String &p_mask, const String &p_type, bool p_recursive, bool p_owned) const { +// Finds the first child node (in tree order) whose name matches the given pattern. +// Can be recursive or not, and limited to owned nodes. +Node *Node::find_child(const String &p_pattern, bool p_recursive, bool p_owned) const { + ERR_FAIL_COND_V(p_pattern.is_empty(), nullptr); + + Node *const *cptr = data.children.ptr(); + int ccount = data.children.size(); + for (int i = 0; i < ccount; i++) { + if (p_owned && !cptr[i]->data.owner) { + continue; + } + if (cptr[i]->data.name.operator String().match(p_pattern)) { + return cptr[i]; + } + + if (!p_recursive) { + continue; + } + + Node *ret = cptr[i]->find_child(p_pattern, true, p_owned); + if (ret) { + return ret; + } + } + return nullptr; +} + +// Finds child nodes based on their name using pattern matching, or class name, +// or both (either pattern or type can be left empty). +// Can be recursive or not, and limited to owned nodes. +TypedArray<Node> Node::find_children(const String &p_pattern, const String &p_type, bool p_recursive, bool p_owned) const { TypedArray<Node> ret; - ERR_FAIL_COND_V(p_mask.is_empty() && p_type.is_empty(), ret); + ERR_FAIL_COND_V(p_pattern.is_empty() && p_type.is_empty(), ret); Node *const *cptr = data.children.ptr(); int ccount = data.children.size(); @@ -1355,8 +1414,8 @@ TypedArray<Node> Node::find_nodes(const String &p_mask, const String &p_type, bo continue; } - if (!p_mask.is_empty()) { - if (!cptr[i]->data.name.operator String().match(p_mask)) { + if (!p_pattern.is_empty()) { + if (!cptr[i]->data.name.operator String().match(p_pattern)) { continue; } else if (p_type.is_empty()) { ret.append(cptr[i]); @@ -1378,7 +1437,7 @@ TypedArray<Node> Node::find_nodes(const String &p_mask, const String &p_type, bo } if (p_recursive) { - ret.append_array(cptr[i]->find_nodes(p_mask, p_type, true, p_owned)); + ret.append_array(cptr[i]->find_children(p_pattern, p_type, true, p_owned)); } } @@ -1389,10 +1448,10 @@ Node *Node::get_parent() const { return data.parent; } -Node *Node::find_parent(const String &p_mask) const { +Node *Node::find_parent(const String &p_pattern) const { Node *p = data.parent; while (p) { - if (p->data.name.operator String().match(p_mask)) { + if (p->data.name.operator String().match(p_pattern)) { return p; } p = p->data.parent; @@ -1496,10 +1555,60 @@ void Node::_set_owner_nocheck(Node *p_owner) { data.owner = p_owner; data.owner->data.owned.push_back(this); data.OW = data.owner->data.owned.back(); + + owner_changed_notify(); +} + +void Node::_release_unique_name_in_owner() { + ERR_FAIL_NULL(data.owner); // Sanity check. + StringName key = StringName(UNIQUE_NODE_PREFIX + data.name.operator String()); + Node **which = data.owner->data.owned_unique_nodes.getptr(key); + if (which == nullptr || *which != this) { + return; // Ignore. + } + data.owner->data.owned_unique_nodes.erase(key); +} + +void Node::_acquire_unique_name_in_owner() { + ERR_FAIL_NULL(data.owner); // Sanity check. + StringName key = StringName(UNIQUE_NODE_PREFIX + data.name.operator String()); + Node **which = data.owner->data.owned_unique_nodes.getptr(key); + if (which != nullptr && *which != this) { + String which_path = is_inside_tree() ? (*which)->get_path() : data.owner->get_path_to(*which); + WARN_PRINT(vformat(RTR("Setting node name '%s' to be unique within scene for '%s', but it's already claimed by '%s'.\n'%s' is no longer set as having a unique name."), + get_name(), is_inside_tree() ? get_path() : data.owner->get_path_to(this), which_path, which_path)); + data.unique_name_in_owner = false; + return; + } + data.owner->data.owned_unique_nodes[key] = this; +} + +void Node::set_unique_name_in_owner(bool p_enabled) { + if (data.unique_name_in_owner == p_enabled) { + return; + } + + if (data.unique_name_in_owner && data.owner != nullptr) { + _release_unique_name_in_owner(); + } + data.unique_name_in_owner = p_enabled; + + if (data.unique_name_in_owner && data.owner != nullptr) { + _acquire_unique_name_in_owner(); + } + + update_configuration_warnings(); +} + +bool Node::is_unique_name_in_owner() const { + return data.unique_name_in_owner; } void Node::set_owner(Node *p_owner) { if (data.owner) { + if (data.unique_name_in_owner) { + _release_unique_name_in_owner(); + } data.owner->data.owned.erase(data.OW); data.OW = nullptr; data.owner = nullptr; @@ -1526,6 +1635,10 @@ void Node::set_owner(Node *p_owner) { ERR_FAIL_COND(!owner_valid); _set_owner_nocheck(p_owner); + + if (data.unique_name_in_owner) { + _acquire_unique_name_in_owner(); + } } Node *Node::get_owner() const { @@ -1537,7 +1650,7 @@ Node *Node::find_common_parent_with(const Node *p_node) const { return const_cast<Node *>(p_node); } - Set<const Node *> visited; + HashSet<const Node *> visited; const Node *n = this; @@ -1569,7 +1682,7 @@ NodePath Node::get_path_to(const Node *p_node) const { return NodePath("."); } - Set<const Node *> visited; + HashSet<const Node *> visited; const Node *n = this; @@ -1663,15 +1776,15 @@ void Node::add_to_group(const StringName &p_identifier, bool p_persistent) { void Node::remove_from_group(const StringName &p_identifier) { ERR_FAIL_COND(!data.grouped.has(p_identifier)); - Map<StringName, GroupData>::Element *E = data.grouped.find(p_identifier); + HashMap<StringName, GroupData>::Iterator E = data.grouped.find(p_identifier); ERR_FAIL_COND(!E); if (data.tree) { - data.tree->remove_from_group(E->key(), this); + data.tree->remove_from_group(E->key, this); } - data.grouped.erase(E); + data.grouped.remove(E); } Array Node::_get_groups() const { @@ -1912,35 +2025,28 @@ Node *Node::get_deepest_editable_node(Node *p_start_node) const { #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); - } + Array pinned = get_meta("_edit_pinned_properties_", Array()); + StringName psa = get_property_store_alias(p_property); + 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_"); - } } } + + if (pinned.is_empty()) { + remove_meta("_edit_pinned_properties_"); + } else { + set_meta("_edit_pinned_properties_", pinned); + } } 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); + Array pinned = get_meta("_edit_pinned_properties_", Array()); + StringName psa = get_property_store_alias(p_property); return pinned.has(psa); } @@ -1949,7 +2055,7 @@ StringName Node::get_property_store_alias(const StringName &p_property) const { } #endif -void Node::get_storable_properties(Set<StringName> &r_storable_properties) const { +void Node::get_storable_properties(HashSet<StringName> &r_storable_properties) const { List<PropertyInfo> pi; get_property_list(&pi); for (List<PropertyInfo>::Element *E = pi.front(); E; E = E->next()) { @@ -1995,7 +2101,7 @@ bool Node::get_scene_instance_load_placeholder() const { return data.use_placeholder; } -Node *Node::_duplicate(int p_flags, Map<const Node *, Node *> *r_duplimap) const { +Node *Node::_duplicate(int p_flags, HashMap<const Node *, Node *> *r_duplimap) const { Node *node = nullptr; bool instantiated = false; @@ -2187,11 +2293,11 @@ Node *Node::duplicate(int p_flags) const { } #ifdef TOOLS_ENABLED -Node *Node::duplicate_from_editor(Map<const Node *, Node *> &r_duplimap) const { - return duplicate_from_editor(r_duplimap, Map<RES, RES>()); +Node *Node::duplicate_from_editor(HashMap<const Node *, Node *> &r_duplimap) const { + return duplicate_from_editor(r_duplimap, HashMap<Ref<Resource>, Ref<Resource>>()); } -Node *Node::duplicate_from_editor(Map<const Node *, Node *> &r_duplimap, const Map<RES, RES> &p_resource_remap) const { +Node *Node::duplicate_from_editor(HashMap<const Node *, Node *> &r_duplimap, const HashMap<Ref<Resource>, Ref<Resource>> &p_resource_remap) const { Node *dupe = _duplicate(DUPLICATE_SIGNALS | DUPLICATE_GROUPS | DUPLICATE_SCRIPTS | DUPLICATE_USE_INSTANCING | DUPLICATE_FROM_EDITOR, &r_duplimap); // This is used by SceneTreeDock's paste functionality. When pasting to foreign scene, resources are duplicated. @@ -2207,7 +2313,7 @@ Node *Node::duplicate_from_editor(Map<const Node *, Node *> &r_duplimap, const M return dupe; } -void Node::remap_node_resources(Node *p_node, const Map<RES, RES> &p_resource_remap) const { +void Node::remap_node_resources(Node *p_node, const HashMap<Ref<Resource>, Ref<Resource>> &p_resource_remap) const { List<PropertyInfo> props; p_node->get_property_list(&props); @@ -2218,7 +2324,7 @@ void Node::remap_node_resources(Node *p_node, const Map<RES, RES> &p_resource_re Variant v = p_node->get(E.name); if (v.is_ref_counted()) { - RES res = v; + Ref<Resource> res = v; if (res.is_valid()) { if (p_resource_remap.has(res)) { p_node->set(E.name, p_resource_remap[res]); @@ -2233,7 +2339,7 @@ void Node::remap_node_resources(Node *p_node, const Map<RES, RES> &p_resource_re } } -void Node::remap_nested_resources(RES p_resource, const Map<RES, RES> &p_resource_remap) const { +void Node::remap_nested_resources(Ref<Resource> p_resource, const HashMap<Ref<Resource>, Ref<Resource>> &p_resource_remap) const { List<PropertyInfo> props; p_resource->get_property_list(&props); @@ -2244,7 +2350,7 @@ void Node::remap_nested_resources(RES p_resource, const Map<RES, RES> &p_resourc Variant v = p_resource->get(E.name); if (v.is_ref_counted()) { - RES res = v; + Ref<Resource> res = v; if (res.is_valid()) { if (p_resource_remap.has(res)) { p_resource->set(E.name, p_resource_remap[res]); @@ -2393,7 +2499,7 @@ bool Node::has_node_and_resource(const NodePath &p_path) const { if (!has_node(p_path)) { return false; } - RES res; + Ref<Resource> res; Vector<StringName> leftover_path; Node *node = get_node_and_resource(p_path, res, leftover_path, false); @@ -2401,7 +2507,7 @@ bool Node::has_node_and_resource(const NodePath &p_path) const { } Array Node::_get_node_and_resource(const NodePath &p_path) { - RES res; + Ref<Resource> res; Vector<StringName> leftover_path; Node *node = get_node_and_resource(p_path, res, leftover_path, false); Array result; @@ -2423,9 +2529,9 @@ Array Node::_get_node_and_resource(const NodePath &p_path) { return result; } -Node *Node::get_node_and_resource(const NodePath &p_path, RES &r_res, Vector<StringName> &r_leftover_subpath, bool p_last_is_property) const { +Node *Node::get_node_and_resource(const NodePath &p_path, Ref<Resource> &r_res, Vector<StringName> &r_leftover_subpath, bool p_last_is_property) const { Node *node = get_node(p_path); - r_res = RES(); + r_res = Ref<Resource>(); r_leftover_subpath = Vector<StringName>(); if (!node) { return nullptr; @@ -2435,13 +2541,14 @@ Node *Node::get_node_and_resource(const NodePath &p_path, RES &r_res, Vector<Str int j = 0; // If not p_last_is_property, we shouldn't consider the last one as part of the resource for (; j < p_path.get_subname_count() - (int)p_last_is_property; j++) { - Variant new_res_v = j == 0 ? node->get(p_path.get_subname(j)) : r_res->get(p_path.get_subname(j)); + bool is_valid = false; + Variant new_res_v = j == 0 ? node->get(p_path.get_subname(j), &is_valid) : r_res->get(p_path.get_subname(j), &is_valid); - if (new_res_v.get_type() == Variant::NIL) { // Found nothing on that path + if (!is_valid) { // Found nothing on that path return nullptr; } - RES new_res = new_res_v; + Ref<Resource> new_res = new_res_v; if (new_res.is_null()) { // No longer a resource, assume property break; @@ -2585,16 +2692,16 @@ void Node::clear_internal_tree_resource_paths() { } TypedArray<String> Node::get_configuration_warnings() const { + TypedArray<String> ret; + Vector<String> warnings; if (GDVIRTUAL_CALL(_get_configuration_warnings, warnings)) { - TypedArray<String> ret; - ret.resize(warnings.size()); for (int i = 0; i < warnings.size(); i++) { - ret[i] = warnings[i]; + ret.push_back(warnings[i]); } - return ret; } - return Array(); + + return ret; } String Node::get_configuration_warnings_as_string() const { @@ -2701,8 +2808,9 @@ void Node::_bind_methods() { ClassDB::bind_method(D_METHOD("get_node", "path"), &Node::get_node); ClassDB::bind_method(D_METHOD("get_node_or_null", "path"), &Node::get_node_or_null); ClassDB::bind_method(D_METHOD("get_parent"), &Node::get_parent); - ClassDB::bind_method(D_METHOD("find_nodes", "mask", "type", "recursive", "owned"), &Node::find_nodes, DEFVAL(""), DEFVAL(true), DEFVAL(true)); - ClassDB::bind_method(D_METHOD("find_parent", "mask"), &Node::find_parent); + ClassDB::bind_method(D_METHOD("find_child", "pattern", "recursive", "owned"), &Node::find_child, DEFVAL(true), DEFVAL(true)); + ClassDB::bind_method(D_METHOD("find_children", "pattern", "type", "recursive", "owned"), &Node::find_children, DEFVAL(""), DEFVAL(true), DEFVAL(true)); + ClassDB::bind_method(D_METHOD("find_parent", "pattern"), &Node::find_parent); ClassDB::bind_method(D_METHOD("has_node_and_resource", "path"), &Node::has_node_and_resource); ClassDB::bind_method(D_METHOD("get_node_and_resource", "path"), &Node::_get_node_and_resource); @@ -2780,8 +2888,6 @@ void Node::_bind_methods() { ClassDB::bind_method(D_METHOD("is_multiplayer_authority"), &Node::is_multiplayer_authority); ClassDB::bind_method(D_METHOD("get_multiplayer"), &Node::get_multiplayer); - ClassDB::bind_method(D_METHOD("get_custom_multiplayer"), &Node::get_custom_multiplayer); - ClassDB::bind_method(D_METHOD("set_custom_multiplayer", "api"), &Node::set_custom_multiplayer); ClassDB::bind_method(D_METHOD("rpc_config", "method", "rpc_mode", "call_local", "transfer_mode", "channel"), &Node::rpc_config, DEFVAL(false), DEFVAL(Multiplayer::TRANSFER_MODE_RELIABLE), DEFVAL(0)); ClassDB::bind_method(D_METHOD("set_editor_description", "editor_description"), &Node::set_editor_description); @@ -2790,6 +2896,9 @@ 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); + ClassDB::bind_method(D_METHOD("set_unique_name_in_owner", "enable"), &Node::set_unique_name_in_owner); + ClassDB::bind_method(D_METHOD("is_unique_name_in_owner"), &Node::is_unique_name_in_owner); + #ifdef TOOLS_ENABLED ClassDB::bind_method(D_METHOD("_set_property_pinned", "property", "pinned"), &Node::set_property_pinned); #endif @@ -2877,13 +2986,13 @@ void Node::_bind_methods() { ADD_SIGNAL(MethodInfo("tree_exiting")); ADD_SIGNAL(MethodInfo("tree_exited")); ADD_SIGNAL(MethodInfo("child_entered_tree", PropertyInfo(Variant::OBJECT, "node", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT, "Node"))); - ADD_SIGNAL(MethodInfo("child_exited_tree", PropertyInfo(Variant::OBJECT, "node", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT, "Node"))); + ADD_SIGNAL(MethodInfo("child_exiting_tree", PropertyInfo(Variant::OBJECT, "node", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT, "Node"))); ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "name", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_name", "get_name"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "unique_name_in_owner", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_unique_name_in_owner", "is_unique_name_in_owner"); ADD_PROPERTY(PropertyInfo(Variant::STRING, "scene_file_path", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_scene_file_path", "get_scene_file_path"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "owner", PROPERTY_HINT_RESOURCE_TYPE, "Node", PROPERTY_USAGE_NONE), "set_owner", "get_owner"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "multiplayer", PROPERTY_HINT_RESOURCE_TYPE, "MultiplayerAPI", PROPERTY_USAGE_NONE), "", "get_multiplayer"); - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "custom_multiplayer", PROPERTY_HINT_RESOURCE_TYPE, "MultiplayerAPI", PROPERTY_USAGE_NONE), "set_custom_multiplayer", "get_custom_multiplayer"); ADD_GROUP("Process", "process_"); ADD_PROPERTY(PropertyInfo(Variant::INT, "process_mode", PROPERTY_HINT_ENUM, "Inherit,Pausable,When Paused,Always,Disabled"), "set_process_mode", "get_process_mode"); diff --git a/scene/main/node.h b/scene/main/node.h index 57b150e29a..3c4727f11c 100644 --- a/scene/main/node.h +++ b/scene/main/node.h @@ -32,7 +32,7 @@ #define NODE_H #include "core/string/node_path.h" -#include "core/templates/map.h" +#include "core/templates/rb_map.h" #include "core/variant/typed_array.h" #include "scene/main/scene_tree.h" @@ -99,6 +99,9 @@ private: Node *parent = nullptr; Node *owner = nullptr; Vector<Node *> children; + HashMap<StringName, Node *> owned_unique_nodes; + bool unique_name_in_owner = false; + int internal_children_front = 0; int internal_children_back = 0; int pos = -1; @@ -116,7 +119,7 @@ private: Viewport *viewport = nullptr; - Map<StringName, GroupData> grouped; + HashMap<StringName, GroupData> grouped; List<Node *>::Element *OW = nullptr; // Owned element. List<Node *> owned; @@ -171,10 +174,11 @@ private: void _propagate_after_exit_tree(); void _print_orphan_nodes(); void _propagate_process_owner(Node *p_owner, int p_pause_notification, int p_enabled_notification); + void _propagate_groups_dirty(); Array _get_node_and_resource(const NodePath &p_path); void _duplicate_signals(const Node *p_original, Node *p_copy) const; - Node *_duplicate(int p_flags, Map<const Node *, Node *> *r_duplimap = nullptr) const; + Node *_duplicate(int p_flags, HashMap<const Node *, Node *> *r_duplimap = nullptr) const; TypedArray<Node> _get_children(bool p_include_internal = true) const; Array _get_groups() const; @@ -193,6 +197,9 @@ private: _FORCE_INLINE_ bool _can_process(bool p_paused) const; _FORCE_INLINE_ bool _is_enabled() const; + void _release_unique_name_in_owner(); + void _acquire_unique_name_in_owner(); + protected: void _block() { data.blocked++; } void _unblock() { data.blocked--; } @@ -202,6 +209,7 @@ protected: virtual void add_child_notify(Node *p_child); virtual void remove_child_notify(Node *p_child); virtual void move_child_notify(Node *p_child); + virtual void owner_changed_notify(); void _propagate_replace_owner(Node *p_owner, Node *p_by_owner); @@ -304,12 +312,13 @@ public: bool has_node(const NodePath &p_path) const; Node *get_node(const NodePath &p_path) const; Node *get_node_or_null(const NodePath &p_path) const; - TypedArray<Node> find_nodes(const String &p_mask, const String &p_type = "", bool p_recursive = true, bool p_owned = true) const; + Node *find_child(const String &p_pattern, bool p_recursive = true, bool p_owned = true) const; + TypedArray<Node> find_children(const String &p_pattern, const String &p_type = "", bool p_recursive = true, bool p_owned = true) const; bool has_node_and_resource(const NodePath &p_path) const; - Node *get_node_and_resource(const NodePath &p_path, RES &r_res, Vector<StringName> &r_leftover_subpath, bool p_last_is_property = true) const; + Node *get_node_and_resource(const NodePath &p_path, Ref<Resource> &r_res, Vector<StringName> &r_leftover_subpath, bool p_last_is_property = true) const; Node *get_parent() const; - Node *find_parent(const String &p_mask) const; + Node *find_parent(const String &p_pattern) const; _FORCE_INLINE_ SceneTree *get_tree() const { ERR_FAIL_COND_V(!data.tree, nullptr); @@ -345,6 +354,9 @@ public: Node *get_owner() const; void get_owned_by(Node *p_by, List<Node *> *p_owned); + void set_unique_name_in_owner(bool p_enabled); + bool is_unique_name_in_owner() const; + void remove_and_skip(); int get_index(bool p_include_internal = true) const; @@ -368,7 +380,7 @@ public: 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; + void get_storable_properties(HashSet<StringName> &r_storable_properties) const; virtual String to_string() override; @@ -410,10 +422,10 @@ public: Node *duplicate(int p_flags = DUPLICATE_GROUPS | DUPLICATE_SIGNALS | DUPLICATE_SCRIPTS) const; #ifdef TOOLS_ENABLED - Node *duplicate_from_editor(Map<const Node *, Node *> &r_duplimap) const; - Node *duplicate_from_editor(Map<const Node *, Node *> &r_duplimap, const Map<RES, RES> &p_resource_remap) const; - void remap_node_resources(Node *p_node, const Map<RES, RES> &p_resource_remap) const; - void remap_nested_resources(RES p_resource, const Map<RES, RES> &p_resource_remap) const; + Node *duplicate_from_editor(HashMap<const Node *, Node *> &r_duplimap) const; + Node *duplicate_from_editor(HashMap<const Node *, Node *> &r_duplimap, const HashMap<Ref<Resource>, Ref<Resource>> &p_resource_remap) const; + void remap_node_resources(Node *p_node, const HashMap<Ref<Resource>, Ref<Resource>> &p_resource_remap) const; + void remap_nested_resources(Ref<Resource> p_resource, const HashMap<Ref<Resource>, Ref<Resource>> &p_resource_remap) const; #endif // used by editors, to save what has changed only @@ -505,8 +517,6 @@ public: void rpcp(int p_peer_id, const StringName &p_method, const Variant **p_arg, int p_argcount); Ref<MultiplayerAPI> get_multiplayer() const; - Ref<MultiplayerAPI> get_custom_multiplayer() const; - void set_custom_multiplayer(Ref<MultiplayerAPI> p_multiplayer); Node(); ~Node(); @@ -514,6 +524,6 @@ public: VARIANT_ENUM_CAST(Node::DuplicateFlags); -typedef Set<Node *, Node::Comparator> NodeSet; +typedef HashSet<Node *, Node::Comparator> NodeSet; #endif diff --git a/scene/main/resource_preloader.cpp b/scene/main/resource_preloader.cpp index 49010095ff..5512d0a84e 100644 --- a/scene/main/resource_preloader.cpp +++ b/scene/main/resource_preloader.cpp @@ -29,7 +29,7 @@ /*************************************************************************/ #include "resource_preloader.h" - +#include "core/templates/rb_set.h" void ResourcePreloader::_set_resources(const Array &p_data) { resources.clear(); @@ -41,7 +41,7 @@ void ResourcePreloader::_set_resources(const Array &p_data) { for (int i = 0; i < resdata.size(); i++) { String name = names[i]; - RES resource = resdata[i]; + Ref<Resource> resource = resdata[i]; ERR_CONTINUE(!resource.is_valid()); resources[name] = resource; @@ -55,16 +55,16 @@ Array ResourcePreloader::_get_resources() const { arr.resize(resources.size()); names.resize(resources.size()); - Set<String> sorted_names; + RBSet<String> sorted_names; - for (const KeyValue<StringName, RES> &E : resources) { + for (const KeyValue<StringName, Ref<Resource>> &E : resources) { sorted_names.insert(E.key); } int i = 0; - for (Set<String>::Element *E = sorted_names.front(); E; E = E->next()) { - names.set(i, E->get()); - arr[i] = resources[E->get()]; + for (const String &E : sorted_names) { + names.set(i, E); + arr[i] = resources[E]; i++; } @@ -74,7 +74,7 @@ Array ResourcePreloader::_get_resources() const { return res; } -void ResourcePreloader::add_resource(const StringName &p_name, const RES &p_resource) { +void ResourcePreloader::add_resource(const StringName &p_name, const Ref<Resource> &p_resource) { ERR_FAIL_COND(p_resource.is_null()); if (resources.has(p_name)) { StringName new_name; @@ -104,7 +104,7 @@ void ResourcePreloader::remove_resource(const StringName &p_name) { void ResourcePreloader::rename_resource(const StringName &p_from_name, const StringName &p_to_name) { ERR_FAIL_COND(!resources.has(p_from_name)); - RES res = resources[p_from_name]; + Ref<Resource> res = resources[p_from_name]; resources.erase(p_from_name); add_resource(p_to_name, res); @@ -114,8 +114,8 @@ bool ResourcePreloader::has_resource(const StringName &p_name) const { return resources.has(p_name); } -RES ResourcePreloader::get_resource(const StringName &p_name) const { - ERR_FAIL_COND_V(!resources.has(p_name), RES()); +Ref<Resource> ResourcePreloader::get_resource(const StringName &p_name) const { + ERR_FAIL_COND_V(!resources.has(p_name), Ref<Resource>()); return resources[p_name]; } @@ -123,15 +123,16 @@ Vector<String> ResourcePreloader::_get_resource_list() const { Vector<String> res; res.resize(resources.size()); int i = 0; - for (Map<StringName, RES>::Element *E = resources.front(); E; E = E->next(), i++) { - res.set(i, E->key()); + for (const KeyValue<StringName, Ref<Resource>> &E : resources) { + res.set(i, E.key); + i++; } return res; } void ResourcePreloader::get_resource_list(List<StringName> *p_list) { - for (const KeyValue<StringName, RES> &E : resources) { + for (const KeyValue<StringName, Ref<Resource>> &E : resources) { p_list->push_back(E.key); } } diff --git a/scene/main/resource_preloader.h b/scene/main/resource_preloader.h index aabb109d56..fe59bc8ae3 100644 --- a/scene/main/resource_preloader.h +++ b/scene/main/resource_preloader.h @@ -36,7 +36,7 @@ class ResourcePreloader : public Node { GDCLASS(ResourcePreloader, Node); - Map<StringName, RES> resources; + HashMap<StringName, Ref<Resource>> resources; void _set_resources(const Array &p_data); Array _get_resources() const; @@ -46,11 +46,11 @@ protected: static void _bind_methods(); public: - void add_resource(const StringName &p_name, const RES &p_resource); + void add_resource(const StringName &p_name, const Ref<Resource> &p_resource); void remove_resource(const StringName &p_name); void rename_resource(const StringName &p_from_name, const StringName &p_to_name); bool has_resource(const StringName &p_name) const; - RES get_resource(const StringName &p_name) const; + Ref<Resource> get_resource(const StringName &p_name) const; void get_resource_list(List<StringName> *p_list); diff --git a/scene/main/scene_tree.cpp b/scene/main/scene_tree.cpp index 151239c9e7..66482f65dc 100644 --- a/scene/main/scene_tree.cpp +++ b/scene/main/scene_tree.cpp @@ -34,6 +34,7 @@ #include "core/debugger/engine_debugger.h" #include "core/input/input.h" #include "core/io/dir_access.h" +#include "core/io/image_loader.h" #include "core/io/marshalls.h" #include "core/io/resource_loader.h" #include "core/multiplayer/multiplayer_api.h" @@ -65,7 +66,7 @@ void SceneTreeTimer::_bind_methods() { ClassDB::bind_method(D_METHOD("set_time_left", "time"), &SceneTreeTimer::set_time_left); ClassDB::bind_method(D_METHOD("get_time_left"), &SceneTreeTimer::get_time_left); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "time_left"), "set_time_left", "get_time_left"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "time_left", PROPERTY_HINT_NONE, "suffix:s"), "set_time_left", "get_time_left"); ADD_SIGNAL(MethodInfo("timeout")); } @@ -129,32 +130,32 @@ void SceneTree::node_renamed(Node *p_node) { } SceneTree::Group *SceneTree::add_to_group(const StringName &p_group, Node *p_node) { - Map<StringName, Group>::Element *E = group_map.find(p_group); + HashMap<StringName, Group>::Iterator E = group_map.find(p_group); if (!E) { E = group_map.insert(p_group, Group()); } - ERR_FAIL_COND_V_MSG(E->get().nodes.has(p_node), &E->get(), "Already in group: " + p_group + "."); - E->get().nodes.push_back(p_node); - //E->get().last_tree_version=0; - E->get().changed = true; - return &E->get(); + ERR_FAIL_COND_V_MSG(E->value.nodes.has(p_node), &E->value, "Already in group: " + p_group + "."); + E->value.nodes.push_back(p_node); + //E->value.last_tree_version=0; + E->value.changed = true; + return &E->value; } void SceneTree::remove_from_group(const StringName &p_group, Node *p_node) { - Map<StringName, Group>::Element *E = group_map.find(p_group); + HashMap<StringName, Group>::Iterator E = group_map.find(p_group); ERR_FAIL_COND(!E); - E->get().nodes.erase(p_node); - if (E->get().nodes.is_empty()) { - group_map.erase(E); + E->value.nodes.erase(p_node); + if (E->value.nodes.is_empty()) { + group_map.remove(E); } } void SceneTree::make_group_changed(const StringName &p_group) { - Map<StringName, Group>::Element *E = group_map.find(p_group); + HashMap<StringName, Group>::Iterator E = group_map.find(p_group); if (E) { - E->get().changed = true; + E->value.changed = true; } } @@ -173,17 +174,17 @@ void SceneTree::_flush_ugc() { ugc_locked = true; while (unique_group_calls.size()) { - Map<UGCall, Vector<Variant>>::Element *E = unique_group_calls.front(); + HashMap<UGCall, Vector<Variant>, UGCall>::Iterator E = unique_group_calls.begin(); - const Variant **argptrs = (const Variant **)alloca(E->get().size() * sizeof(Variant *)); + const Variant **argptrs = (const Variant **)alloca(E->value.size() * sizeof(Variant *)); - for (int i = 0; i < E->get().size(); i++) { - argptrs[i] = &E->get()[i]; + for (int i = 0; i < E->value.size(); i++) { + argptrs[i] = &E->value[i]; } - call_group_flagsp(GROUP_CALL_REALTIME, E->key().group, E->key().call, argptrs, E->get().size()); + call_group_flagsp(GROUP_CALL_DEFAULT, E->key.group, E->key.call, argptrs, E->value.size()); - unique_group_calls.erase(E); + unique_group_calls.remove(E); } ugc_locked = false; @@ -211,16 +212,16 @@ void SceneTree::_update_group_order(Group &g, bool p_use_priority) { } void SceneTree::call_group_flagsp(uint32_t p_call_flags, const StringName &p_group, const StringName &p_function, const Variant **p_args, int p_argcount) { - Map<StringName, Group>::Element *E = group_map.find(p_group); + HashMap<StringName, Group>::Iterator E = group_map.find(p_group); if (!E) { return; } - Group &g = E->get(); + Group &g = E->value; if (g.nodes.is_empty()) { return; } - if (p_call_flags & GROUP_CALL_UNIQUE && !(p_call_flags & GROUP_CALL_REALTIME)) { + if (p_call_flags & GROUP_CALL_UNIQUE && p_call_flags & GROUP_CALL_DEFERRED) { ERR_FAIL_COND(ugc_locked); UGCall ug; @@ -254,7 +255,7 @@ void SceneTree::call_group_flagsp(uint32_t p_call_flags, const StringName &p_gro continue; } - if (p_call_flags & GROUP_CALL_REALTIME) { + if (!(p_call_flags & GROUP_CALL_DEFERRED)) { Callable::CallError ce; nodes[i]->callp(p_function, p_args, p_argcount, ce); } else { @@ -268,7 +269,7 @@ void SceneTree::call_group_flagsp(uint32_t p_call_flags, const StringName &p_gro continue; } - if (p_call_flags & GROUP_CALL_REALTIME) { + if (!(p_call_flags & GROUP_CALL_DEFERRED)) { Callable::CallError ce; nodes[i]->callp(p_function, p_args, p_argcount, ce); } else { @@ -284,11 +285,11 @@ void SceneTree::call_group_flagsp(uint32_t p_call_flags, const StringName &p_gro } void SceneTree::notify_group_flags(uint32_t p_call_flags, const StringName &p_group, int p_notification) { - Map<StringName, Group>::Element *E = group_map.find(p_group); + HashMap<StringName, Group>::Iterator E = group_map.find(p_group); if (!E) { return; } - Group &g = E->get(); + Group &g = E->value; if (g.nodes.is_empty()) { return; } @@ -307,7 +308,7 @@ void SceneTree::notify_group_flags(uint32_t p_call_flags, const StringName &p_gr continue; } - if (p_call_flags & GROUP_CALL_REALTIME) { + if (!(p_call_flags & GROUP_CALL_DEFERRED)) { nodes[i]->notification(p_notification); } else { MessageQueue::get_singleton()->push_notification(nodes[i], p_notification); @@ -320,7 +321,7 @@ void SceneTree::notify_group_flags(uint32_t p_call_flags, const StringName &p_gr continue; } - if (p_call_flags & GROUP_CALL_REALTIME) { + if (!(p_call_flags & GROUP_CALL_DEFERRED)) { nodes[i]->notification(p_notification); } else { MessageQueue::get_singleton()->push_notification(nodes[i], p_notification); @@ -335,11 +336,11 @@ void SceneTree::notify_group_flags(uint32_t p_call_flags, const StringName &p_gr } void SceneTree::set_group_flags(uint32_t p_call_flags, const StringName &p_group, const String &p_name, const Variant &p_value) { - Map<StringName, Group>::Element *E = group_map.find(p_group); + HashMap<StringName, Group>::Iterator E = group_map.find(p_group); if (!E) { return; } - Group &g = E->get(); + Group &g = E->value; if (g.nodes.is_empty()) { return; } @@ -358,7 +359,7 @@ void SceneTree::set_group_flags(uint32_t p_call_flags, const StringName &p_group continue; } - if (p_call_flags & GROUP_CALL_REALTIME) { + if (!(p_call_flags & GROUP_CALL_DEFERRED)) { nodes[i]->set(p_name, p_value); } else { MessageQueue::get_singleton()->push_set(nodes[i], p_name, p_value); @@ -371,7 +372,7 @@ void SceneTree::set_group_flags(uint32_t p_call_flags, const StringName &p_group continue; } - if (p_call_flags & GROUP_CALL_REALTIME) { + if (!(p_call_flags & GROUP_CALL_DEFERRED)) { nodes[i]->set(p_name, p_value); } else { MessageQueue::get_singleton()->push_set(nodes[i], p_name, p_value); @@ -386,11 +387,11 @@ void SceneTree::set_group_flags(uint32_t p_call_flags, const StringName &p_group } void SceneTree::notify_group(const StringName &p_group, int p_notification) { - notify_group_flags(0, p_group, p_notification); + notify_group_flags(GROUP_CALL_DEFAULT, p_group, p_notification); } void SceneTree::set_group(const StringName &p_group, const String &p_name, const Variant &p_value) { - set_group_flags(0, p_group, p_name, p_value); + set_group_flags(GROUP_CALL_DEFAULT, p_group, p_name, p_value); } void SceneTree::initialize() { @@ -412,9 +413,9 @@ bool SceneTree::physics_process(double p_time) { emit_signal(SNAME("physics_frame")); - _notify_group_pause(SNAME("physics_process_internal"), Node::NOTIFICATION_INTERNAL_PHYSICS_PROCESS); - call_group_flags(GROUP_CALL_REALTIME, SNAME("_picking_viewports"), SNAME("_process_picking")); - _notify_group_pause(SNAME("physics_process"), Node::NOTIFICATION_PHYSICS_PROCESS); + _notify_group_pause(SNAME("_physics_process_internal"), Node::NOTIFICATION_INTERNAL_PHYSICS_PROCESS); + call_group(SNAME("_picking_viewports"), SNAME("_process_picking")); + _notify_group_pause(SNAME("_physics_process"), Node::NOTIFICATION_PHYSICS_PROCESS); _flush_ugc(); MessageQueue::get_singleton()->flush(); //small little hack @@ -438,6 +439,9 @@ bool SceneTree::process(double p_time) { if (multiplayer_poll) { multiplayer->poll(); + for (KeyValue<NodePath, Ref<MultiplayerAPI>> &E : custom_multiplayers) { + E.value->poll(); + } } emit_signal(SNAME("process_frame")); @@ -446,8 +450,8 @@ bool SceneTree::process(double p_time) { flush_transform_notifications(); - _notify_group_pause(SNAME("process_internal"), Node::NOTIFICATION_INTERNAL_PROCESS); - _notify_group_pause(SNAME("process"), Node::NOTIFICATION_PROCESS); + _notify_group_pause(SNAME("_process_internal"), Node::NOTIFICATION_INTERNAL_PROCESS); + _notify_group_pause(SNAME("_process"), Node::NOTIFICATION_PROCESS); _flush_ugc(); MessageQueue::get_singleton()->flush(); //small little hack @@ -624,10 +628,18 @@ void SceneTree::_notification(int p_notification) { } } +bool SceneTree::is_auto_accept_quit() const { + return accept_quit; +} + void SceneTree::set_auto_accept_quit(bool p_enable) { accept_quit = p_enable; } +bool SceneTree::is_quit_on_go_back() const { + return quit_on_go_back; +} + void SceneTree::set_quit_on_go_back(bool p_enable) { quit_on_go_back = p_enable; } @@ -648,6 +660,14 @@ bool SceneTree::is_debugging_collisions_hint() const { return debug_collisions_hint; } +void SceneTree::set_debug_paths_hint(bool p_enabled) { + debug_paths_hint = p_enabled; +} + +bool SceneTree::is_debugging_paths_hint() const { + return debug_paths_hint; +} + void SceneTree::set_debug_navigation_hint(bool p_enabled) { debug_navigation_hint = p_enabled; } @@ -673,6 +693,22 @@ Color SceneTree::get_debug_collision_contact_color() const { return debug_collision_contact_color; } +void SceneTree::set_debug_paths_color(const Color &p_color) { + debug_paths_color = p_color; +} + +Color SceneTree::get_debug_paths_color() const { + return debug_paths_color; +} + +void SceneTree::set_debug_paths_width(float p_width) { + debug_paths_width = p_width; +} + +float SceneTree::get_debug_paths_width() const { + return debug_paths_width; +} + void SceneTree::set_debug_navigation_color(const Color &p_color) { debug_navigation_color = p_color; } @@ -689,6 +725,23 @@ Color SceneTree::get_debug_navigation_disabled_color() const { return debug_navigation_disabled_color; } +Ref<Material> SceneTree::get_debug_paths_material() { + if (debug_paths_material.is_valid()) { + return debug_paths_material; + } + + Ref<StandardMaterial3D> _debug_material = Ref<StandardMaterial3D>(memnew(StandardMaterial3D)); + _debug_material->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED); + _debug_material->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA); + _debug_material->set_flag(StandardMaterial3D::FLAG_SRGB_VERTEX_COLOR, true); + _debug_material->set_flag(StandardMaterial3D::FLAG_ALBEDO_FROM_VERTEX_COLOR, true); + _debug_material->set_albedo(get_debug_paths_color()); + + debug_paths_material = _debug_material; + + return debug_paths_material; +} + Ref<Material> SceneTree::get_debug_navigation_material() { if (navigation_material.is_valid()) { return navigation_material; @@ -815,11 +868,11 @@ bool SceneTree::is_paused() const { } void SceneTree::_notify_group_pause(const StringName &p_group, int p_notification) { - Map<StringName, Group>::Element *E = group_map.find(p_group); + HashMap<StringName, Group>::Iterator E = group_map.find(p_group); if (!E) { return; } - Group &g = E->get(); + Group &g = E->value; if (g.nodes.is_empty()) { return; } @@ -859,11 +912,11 @@ void SceneTree::_notify_group_pause(const StringName &p_group, int p_notificatio } void SceneTree::_call_input_pause(const StringName &p_group, CallInputType p_call_type, const Ref<InputEvent> &p_input, Viewport *p_viewport) { - Map<StringName, Group>::Element *E = group_map.find(p_group); + HashMap<StringName, Group>::Iterator E = group_map.find(p_group); if (!E) { return; } - Group &g = E->get(); + Group &g = E->value; if (g.nodes.is_empty()) { return; } @@ -940,7 +993,7 @@ void SceneTree::_call_group(const Variant **p_args, int p_argcount, Callable::Ca StringName group = *p_args[0]; StringName method = *p_args[1]; - call_group_flagsp(0, group, method, p_args + 2, p_argcount - 2); + call_group_flagsp(GROUP_CALL_DEFAULT, group, method, p_args + 2, p_argcount - 2); } int64_t SceneTree::get_frame() const { @@ -949,20 +1002,20 @@ int64_t SceneTree::get_frame() const { Array SceneTree::_get_nodes_in_group(const StringName &p_group) { Array ret; - Map<StringName, Group>::Element *E = group_map.find(p_group); + HashMap<StringName, Group>::Iterator E = group_map.find(p_group); if (!E) { return ret; } - _update_group_order(E->get()); //update order just in case - int nc = E->get().nodes.size(); + _update_group_order(E->value); //update order just in case + int nc = E->value.nodes.size(); if (nc == 0) { return ret; } ret.resize(nc); - Node **ptr = E->get().nodes.ptrw(); + Node **ptr = E->value.nodes.ptrw(); for (int i = 0; i < nc; i++) { ret[i] = ptr[i]; } @@ -975,32 +1028,32 @@ bool SceneTree::has_group(const StringName &p_identifier) const { } Node *SceneTree::get_first_node_in_group(const StringName &p_group) { - Map<StringName, Group>::Element *E = group_map.find(p_group); + HashMap<StringName, Group>::Iterator E = group_map.find(p_group); if (!E) { - return nullptr; //no group + return nullptr; // No group. } - _update_group_order(E->get()); //update order just in case + _update_group_order(E->value); // Update order just in case. - if (E->get().nodes.size() == 0) { + if (E->value.nodes.is_empty()) { return nullptr; } - return E->get().nodes[0]; + return E->value.nodes[0]; } void SceneTree::get_nodes_in_group(const StringName &p_group, List<Node *> *p_list) { - Map<StringName, Group>::Element *E = group_map.find(p_group); + HashMap<StringName, Group>::Iterator E = group_map.find(p_group); if (!E) { return; } - _update_group_order(E->get()); //update order just in case - int nc = E->get().nodes.size(); + _update_group_order(E->value); //update order just in case + int nc = E->value.nodes.size(); if (nc == 0) { return; } - Node **ptr = E->get().nodes.ptrw(); + Node **ptr = E->value.nodes.ptrw(); for (int i = 0; i < nc; i++) { p_list->push_back(ptr[i]); } @@ -1113,9 +1166,7 @@ Ref<SceneTreeTimer> SceneTree::create_timer(double p_delay_sec, bool p_process_a } Ref<Tween> SceneTree::create_tween() { - Ref<Tween> tween; - tween.instantiate(); - tween->set_valid(true); + Ref<Tween> tween = memnew(Tween(true)); tweens.push_back(tween); return tween; } @@ -1133,8 +1184,50 @@ Array SceneTree::get_processed_tweens() { return ret; } -Ref<MultiplayerAPI> SceneTree::get_multiplayer() const { - return multiplayer; +Ref<MultiplayerAPI> SceneTree::get_multiplayer(const NodePath &p_for_path) const { + Ref<MultiplayerAPI> out = multiplayer; + for (const KeyValue<NodePath, Ref<MultiplayerAPI>> &E : custom_multiplayers) { + const Vector<StringName> snames = E.key.get_names(); + const Vector<StringName> tnames = p_for_path.get_names(); + if (tnames.size() < snames.size()) { + continue; + } + const StringName *sptr = snames.ptr(); + const StringName *nptr = tnames.ptr(); + bool valid = true; + for (int i = 0; i < snames.size(); i++) { + if (sptr[i] != nptr[i]) { + valid = false; + break; + } + } + if (valid) { + out = E.value; + break; + } + } + return out; +} + +void SceneTree::set_multiplayer(Ref<MultiplayerAPI> p_multiplayer, const NodePath &p_root_path) { + if (p_root_path.is_empty()) { + ERR_FAIL_COND(!p_multiplayer.is_valid()); + if (multiplayer.is_valid()) { + multiplayer->set_root_path(NodePath()); + } + multiplayer = p_multiplayer; + multiplayer->set_root_path("/" + root->get_name()); + } else { + if (p_multiplayer.is_valid()) { + custom_multiplayers[p_root_path] = p_multiplayer; + p_multiplayer->set_root_path(p_root_path); + } else { + if (custom_multiplayers.has(p_root_path)) { + custom_multiplayers[p_root_path]->set_root_path(NodePath()); + custom_multiplayers.erase(p_root_path); + } + } + } } void SceneTree::set_multiplayer_poll_enabled(bool p_enabled) { @@ -1145,22 +1238,19 @@ bool SceneTree::is_multiplayer_poll_enabled() const { return multiplayer_poll; } -void SceneTree::set_multiplayer(Ref<MultiplayerAPI> p_multiplayer) { - ERR_FAIL_COND(!p_multiplayer.is_valid()); - - multiplayer = p_multiplayer; - multiplayer->set_root_path("/" + root->get_name()); -} - void SceneTree::_bind_methods() { ClassDB::bind_method(D_METHOD("get_root"), &SceneTree::get_root); ClassDB::bind_method(D_METHOD("has_group", "name"), &SceneTree::has_group); + ClassDB::bind_method(D_METHOD("is_auto_accept_quit"), &SceneTree::is_auto_accept_quit); ClassDB::bind_method(D_METHOD("set_auto_accept_quit", "enabled"), &SceneTree::set_auto_accept_quit); + ClassDB::bind_method(D_METHOD("is_quit_on_go_back"), &SceneTree::is_quit_on_go_back); ClassDB::bind_method(D_METHOD("set_quit_on_go_back", "enabled"), &SceneTree::set_quit_on_go_back); ClassDB::bind_method(D_METHOD("set_debug_collisions_hint", "enable"), &SceneTree::set_debug_collisions_hint); ClassDB::bind_method(D_METHOD("is_debugging_collisions_hint"), &SceneTree::is_debugging_collisions_hint); + ClassDB::bind_method(D_METHOD("set_debug_paths_hint", "enable"), &SceneTree::set_debug_paths_hint); + ClassDB::bind_method(D_METHOD("is_debugging_paths_hint"), &SceneTree::is_debugging_paths_hint); ClassDB::bind_method(D_METHOD("set_debug_navigation_hint", "enable"), &SceneTree::set_debug_navigation_hint); ClassDB::bind_method(D_METHOD("is_debugging_navigation_hint"), &SceneTree::is_debugging_navigation_hint); @@ -1214,18 +1304,20 @@ void SceneTree::_bind_methods() { ClassDB::bind_method(D_METHOD("_change_scene"), &SceneTree::_change_scene); - ClassDB::bind_method(D_METHOD("set_multiplayer", "multiplayer"), &SceneTree::set_multiplayer); - ClassDB::bind_method(D_METHOD("get_multiplayer"), &SceneTree::get_multiplayer); + ClassDB::bind_method(D_METHOD("set_multiplayer", "multiplayer", "root_path"), &SceneTree::set_multiplayer, DEFVAL(NodePath())); + ClassDB::bind_method(D_METHOD("get_multiplayer", "for_path"), &SceneTree::get_multiplayer, DEFVAL(NodePath())); ClassDB::bind_method(D_METHOD("set_multiplayer_poll_enabled", "enabled"), &SceneTree::set_multiplayer_poll_enabled); ClassDB::bind_method(D_METHOD("is_multiplayer_poll_enabled"), &SceneTree::is_multiplayer_poll_enabled); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "auto_accept_quit"), "set_auto_accept_quit", "is_auto_accept_quit"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "quit_on_go_back"), "set_quit_on_go_back", "is_quit_on_go_back"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "debug_collisions_hint"), "set_debug_collisions_hint", "is_debugging_collisions_hint"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "debug_paths_hint"), "set_debug_paths_hint", "is_debugging_paths_hint"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "debug_navigation_hint"), "set_debug_navigation_hint", "is_debugging_navigation_hint"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "paused"), "set_pause", "is_paused"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "edited_scene_root", PROPERTY_HINT_RESOURCE_TYPE, "Node", PROPERTY_USAGE_NONE), "set_edited_scene_root", "get_edited_scene_root"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "current_scene", PROPERTY_HINT_RESOURCE_TYPE, "Node", PROPERTY_USAGE_NONE), "set_current_scene", "get_current_scene"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "root", PROPERTY_HINT_RESOURCE_TYPE, "Node", PROPERTY_USAGE_NONE), "", "get_root"); - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "multiplayer", PROPERTY_HINT_RESOURCE_TYPE, "MultiplayerAPI", PROPERTY_USAGE_NONE), "set_multiplayer", "get_multiplayer"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "multiplayer_poll"), "set_multiplayer_poll_enabled", "is_multiplayer_poll_enabled"); ADD_SIGNAL(MethodInfo("tree_changed")); @@ -1240,7 +1332,7 @@ void SceneTree::_bind_methods() { BIND_ENUM_CONSTANT(GROUP_CALL_DEFAULT); BIND_ENUM_CONSTANT(GROUP_CALL_REVERSE); - BIND_ENUM_CONSTANT(GROUP_CALL_REALTIME); + BIND_ENUM_CONSTANT(GROUP_CALL_DEFERRED); BIND_ENUM_CONSTANT(GROUP_CALL_UNIQUE); } @@ -1297,6 +1389,8 @@ SceneTree::SceneTree() { } debug_collisions_color = GLOBAL_DEF("debug/shapes/collision/shape_color", Color(0.0, 0.6, 0.7, 0.42)); debug_collision_contact_color = GLOBAL_DEF("debug/shapes/collision/contact_color", Color(1.0, 0.2, 0.1, 0.8)); + debug_paths_color = GLOBAL_DEF("debug/shapes/paths/geometry_color", Color(0.1, 1.0, 0.7, 0.4)); + debug_paths_width = GLOBAL_DEF("debug/shapes/paths/geometry_width", 2.0); debug_navigation_color = GLOBAL_DEF("debug/shapes/navigation/geometry_color", Color(0.1, 1.0, 0.7, 0.4)); debug_navigation_disabled_color = GLOBAL_DEF("debug/shapes/navigation/disabled_geometry_color", Color(1.0, 0.7, 0.1, 0.4)); collision_debug_contacts = GLOBAL_DEF("debug/shapes/collision/max_contacts_displayed", 10000); @@ -1326,14 +1420,17 @@ SceneTree::SceneTree() { root->set_as_audio_listener_2d(true); current_scene = nullptr; - const int msaa_mode = GLOBAL_DEF("rendering/anti_aliasing/quality/msaa", 0); + const int msaa_mode = GLOBAL_DEF_BASIC("rendering/anti_aliasing/quality/msaa", 0); ProjectSettings::get_singleton()->set_custom_property_info("rendering/anti_aliasing/quality/msaa", PropertyInfo(Variant::INT, "rendering/anti_aliasing/quality/msaa", PROPERTY_HINT_ENUM, String::utf8("Disabled (Fastest),2× (Average),4× (Slow),8× (Slowest)"))); root->set_msaa(Viewport::MSAA(msaa_mode)); - const int ssaa_mode = GLOBAL_DEF("rendering/anti_aliasing/quality/screen_space_aa", 0); + const int ssaa_mode = GLOBAL_DEF_BASIC("rendering/anti_aliasing/quality/screen_space_aa", 0); ProjectSettings::get_singleton()->set_custom_property_info("rendering/anti_aliasing/quality/screen_space_aa", PropertyInfo(Variant::INT, "rendering/anti_aliasing/quality/screen_space_aa", PROPERTY_HINT_ENUM, "Disabled (Fastest),FXAA (Fast)")); root->set_screen_space_aa(Viewport::ScreenSpaceAA(ssaa_mode)); + const bool use_taa = GLOBAL_DEF_BASIC("rendering/anti_aliasing/quality/use_taa", false); + root->set_use_taa(use_taa); + const bool use_debanding = GLOBAL_DEF("rendering/anti_aliasing/quality/use_debanding", false); root->set_use_debanding(use_debanding); @@ -1350,25 +1447,48 @@ SceneTree::SceneTree() { bool snap_2d_vertices = GLOBAL_DEF("rendering/2d/snap/snap_2d_vertices_to_pixel", false); root->set_snap_2d_vertices_to_pixel(snap_2d_vertices); - int shadowmap_size = GLOBAL_DEF("rendering/shadows/shadow_atlas/size", 4096); - ProjectSettings::get_singleton()->set_custom_property_info("rendering/shadows/shadow_atlas/size", PropertyInfo(Variant::INT, "rendering/shadows/shadow_atlas/size", PROPERTY_HINT_RANGE, "256,16384")); - GLOBAL_DEF("rendering/shadows/shadow_atlas/size.mobile", 2048); - bool shadowmap_16_bits = GLOBAL_DEF("rendering/shadows/shadow_atlas/16_bits", true); - int atlas_q0 = GLOBAL_DEF("rendering/shadows/shadow_atlas/quadrant_0_subdiv", 2); - int atlas_q1 = GLOBAL_DEF("rendering/shadows/shadow_atlas/quadrant_1_subdiv", 2); - int atlas_q2 = GLOBAL_DEF("rendering/shadows/shadow_atlas/quadrant_2_subdiv", 3); - int atlas_q3 = GLOBAL_DEF("rendering/shadows/shadow_atlas/quadrant_3_subdiv", 4); - ProjectSettings::get_singleton()->set_custom_property_info("rendering/shadows/shadow_atlas/quadrant_0_subdiv", PropertyInfo(Variant::INT, "rendering/shadows/shadow_atlas/quadrant_0_subdiv", PROPERTY_HINT_ENUM, "Disabled,1 Shadow,4 Shadows,16 Shadows,64 Shadows,256 Shadows,1024 Shadows")); - ProjectSettings::get_singleton()->set_custom_property_info("rendering/shadows/shadow_atlas/quadrant_1_subdiv", PropertyInfo(Variant::INT, "rendering/shadows/shadow_atlas/quadrant_1_subdiv", PROPERTY_HINT_ENUM, "Disabled,1 Shadow,4 Shadows,16 Shadows,64 Shadows,256 Shadows,1024 Shadows")); - ProjectSettings::get_singleton()->set_custom_property_info("rendering/shadows/shadow_atlas/quadrant_2_subdiv", PropertyInfo(Variant::INT, "rendering/shadows/shadow_atlas/quadrant_2_subdiv", PROPERTY_HINT_ENUM, "Disabled,1 Shadow,4 Shadows,16 Shadows,64 Shadows,256 Shadows,1024 Shadows")); - ProjectSettings::get_singleton()->set_custom_property_info("rendering/shadows/shadow_atlas/quadrant_3_subdiv", PropertyInfo(Variant::INT, "rendering/shadows/shadow_atlas/quadrant_3_subdiv", PROPERTY_HINT_ENUM, "Disabled,1 Shadow,4 Shadows,16 Shadows,64 Shadows,256 Shadows,1024 Shadows")); - - root->set_shadow_atlas_size(shadowmap_size); - root->set_shadow_atlas_16_bits(shadowmap_16_bits); - root->set_shadow_atlas_quadrant_subdiv(0, Viewport::ShadowAtlasQuadrantSubdiv(atlas_q0)); - root->set_shadow_atlas_quadrant_subdiv(1, Viewport::ShadowAtlasQuadrantSubdiv(atlas_q1)); - root->set_shadow_atlas_quadrant_subdiv(2, Viewport::ShadowAtlasQuadrantSubdiv(atlas_q2)); - root->set_shadow_atlas_quadrant_subdiv(3, Viewport::ShadowAtlasQuadrantSubdiv(atlas_q3)); + // We setup VRS for the main viewport here, in the editor this will have little effect. + const int vrs_mode = GLOBAL_DEF("rendering/vrs/mode", 0); + ProjectSettings::get_singleton()->set_custom_property_info("rendering/vrs/mode", PropertyInfo(Variant::INT, "rendering/vrs/mode", PROPERTY_HINT_ENUM, String::utf8("Disabled,Texture,XR"))); + root->set_vrs_mode(Viewport::VRSMode(vrs_mode)); + const String vrs_texture_path = String(GLOBAL_DEF("rendering/vrs/texture", String())).strip_edges(); + ProjectSettings::get_singleton()->set_custom_property_info("rendering/vrs/texture", + PropertyInfo(Variant::STRING, + "rendering/vrs/texture", + PROPERTY_HINT_FILE, "*.png")); + if (vrs_mode == 1 && !vrs_texture_path.is_empty()) { + Ref<Image> vrs_image; + vrs_image.instantiate(); + Error load_err = ImageLoader::load_image(vrs_texture_path, vrs_image); + if (load_err) { + ERR_PRINT("Non-existing or invalid VRS texture at '" + vrs_texture_path + "'."); + } else { + Ref<ImageTexture> vrs_texture; + vrs_texture.instantiate(); + vrs_texture->create_from_image(vrs_image); + root->set_vrs_texture(vrs_texture); + } + } + + int shadowmap_size = GLOBAL_DEF("rendering/shadows/positional_shadow/atlas_size", 4096); + ProjectSettings::get_singleton()->set_custom_property_info("rendering/shadows/positional_shadow/atlas_size", PropertyInfo(Variant::INT, "rendering/shadows/positional_shadow/atlas_size", PROPERTY_HINT_RANGE, "256,16384")); + GLOBAL_DEF("rendering/shadows/positional_shadow/atlas_size.mobile", 2048); + bool shadowmap_16_bits = GLOBAL_DEF("rendering/shadows/positional_shadow/atlas_16_bits", true); + int atlas_q0 = GLOBAL_DEF("rendering/shadows/positional_shadow/atlas_quadrant_0_subdiv", 2); + int atlas_q1 = GLOBAL_DEF("rendering/shadows/positional_shadow/atlas_quadrant_1_subdiv", 2); + int atlas_q2 = GLOBAL_DEF("rendering/shadows/positional_shadow/atlas_quadrant_2_subdiv", 3); + int atlas_q3 = GLOBAL_DEF("rendering/shadows/positional_shadow/atlas_quadrant_3_subdiv", 4); + ProjectSettings::get_singleton()->set_custom_property_info("rendering/shadows/positional_shadow/atlas_quadrant_0_subdiv", PropertyInfo(Variant::INT, "rendering/shadows/positional_shadow/atlas_quadrant_0_subdiv", PROPERTY_HINT_ENUM, "Disabled,1 Shadow,4 Shadows,16 Shadows,64 Shadows,256 Shadows,1024 Shadows")); + ProjectSettings::get_singleton()->set_custom_property_info("rendering/shadows/positional_shadow/atlas_quadrant_1_subdiv", PropertyInfo(Variant::INT, "rendering/shadows/positional_shadow/atlas_quadrant_1_subdiv", PROPERTY_HINT_ENUM, "Disabled,1 Shadow,4 Shadows,16 Shadows,64 Shadows,256 Shadows,1024 Shadows")); + ProjectSettings::get_singleton()->set_custom_property_info("rendering/shadows/positional_shadow/atlas_quadrant_2_subdiv", PropertyInfo(Variant::INT, "rendering/shadows/positional_shadow/atlas_quadrant_2_subdiv", PROPERTY_HINT_ENUM, "Disabled,1 Shadow,4 Shadows,16 Shadows,64 Shadows,256 Shadows,1024 Shadows")); + ProjectSettings::get_singleton()->set_custom_property_info("rendering/shadows/positional_shadow/atlas_quadrant_3_subdiv", PropertyInfo(Variant::INT, "rendering/shadows/positional_shadow/atlas_quadrant_3_subdiv", PROPERTY_HINT_ENUM, "Disabled,1 Shadow,4 Shadows,16 Shadows,64 Shadows,256 Shadows,1024 Shadows")); + + root->set_positional_shadow_atlas_size(shadowmap_size); + root->set_positional_shadow_atlas_16_bits(shadowmap_16_bits); + root->set_positional_shadow_atlas_quadrant_subdiv(0, Viewport::PositionalShadowAtlasQuadrantSubdiv(atlas_q0)); + root->set_positional_shadow_atlas_quadrant_subdiv(1, Viewport::PositionalShadowAtlasQuadrantSubdiv(atlas_q1)); + root->set_positional_shadow_atlas_quadrant_subdiv(2, Viewport::PositionalShadowAtlasQuadrantSubdiv(atlas_q2)); + root->set_positional_shadow_atlas_quadrant_subdiv(3, Viewport::PositionalShadowAtlasQuadrantSubdiv(atlas_q3)); Viewport::SDFOversize sdf_oversize = Viewport::SDFOversize(int(GLOBAL_DEF("rendering/2d/sdf/oversize", 1))); root->set_sdf_oversize(sdf_oversize); diff --git a/scene/main/scene_tree.h b/scene/main/scene_tree.h index 705ca6ebd3..a512feacc8 100644 --- a/scene/main/scene_tree.h +++ b/scene/main/scene_tree.h @@ -90,19 +90,20 @@ private: Window *root = nullptr; uint64_t tree_version = 1; - double physics_process_time = 1.0; - double process_time = 1.0; + double physics_process_time = 0.0; + double process_time = 0.0; bool accept_quit = true; bool quit_on_go_back = true; #ifdef DEBUG_ENABLED bool debug_collisions_hint = false; + bool debug_paths_hint = false; bool debug_navigation_hint = false; #endif bool paused = false; int root_lock = 0; - Map<StringName, Group> group_map; + HashMap<StringName, Group> group_map; bool _quit = false; bool initialized = false; @@ -121,16 +122,20 @@ private: StringName group; StringName call; + static uint32_t hash(const UGCall &p_val) { + return p_val.group.hash() ^ p_val.call.hash(); + } + bool operator==(const UGCall &p_with) const { return group == p_with.group && call == p_with.call; } bool operator<(const UGCall &p_with) const { return group == p_with.group ? call < p_with.call : group < p_with.group; } }; // Safety for when a node is deleted while a group is being called. int call_lock = 0; - Set<Node *> call_skip; // Skip erased nodes. + HashSet<Node *> call_skip; // Skip erased nodes. List<ObjectID> delete_queue; - Map<UGCall, Vector<Variant>> unique_group_calls; + HashMap<UGCall, Vector<Variant>, UGCall> unique_group_calls; bool ugc_locked = false; void _flush_ugc(); @@ -142,16 +147,18 @@ private: Color debug_collisions_color; Color debug_collision_contact_color; + Color debug_paths_color; + float debug_paths_width = 1.0f; Color debug_navigation_color; Color debug_navigation_disabled_color; Ref<ArrayMesh> debug_contact_mesh; + Ref<Material> debug_paths_material; Ref<Material> navigation_material; Ref<Material> navigation_disabled_material; Ref<Material> collision_material; int collision_debug_contacts; void _change_scene(Node *p_to); - //void _call_group(uint32_t p_call_flags,const StringName& p_group,const StringName& p_function,const Variant& p_arg1,const Variant& p_arg2); List<Ref<SceneTreeTimer>> timers; List<Ref<Tween>> tweens; @@ -159,6 +166,7 @@ private: ///network/// Ref<MultiplayerAPI> multiplayer; + HashMap<NodePath, Ref<MultiplayerAPI>> custom_multiplayers; bool multiplayer_poll = true; static SceneTree *singleton; @@ -224,7 +232,7 @@ public: enum GroupCallFlags { GROUP_CALL_DEFAULT = 0, GROUP_CALL_REVERSE = 1, - GROUP_CALL_REALTIME = 2, + GROUP_CALL_DEFERRED = 2, GROUP_CALL_UNIQUE = 4, }; @@ -234,17 +242,20 @@ public: void notify_group_flags(uint32_t p_call_flags, const StringName &p_group, int p_notification); void set_group_flags(uint32_t p_call_flags, const StringName &p_group, const String &p_name, const Variant &p_value); + // `notify_group()` is immediate by default since Godot 4.0. void notify_group(const StringName &p_group, int p_notification); + // `set_group()` is immediate by default since Godot 4.0. void set_group(const StringName &p_group, const String &p_name, const Variant &p_value); template <typename... VarArgs> + // `call_group()` is immediate by default since Godot 4.0. void call_group(const StringName &p_group, const StringName &p_function, VarArgs... p_args) { Variant args[sizeof...(p_args) + 1] = { p_args..., Variant() }; // +1 makes sure zero sized arrays are also supported. const Variant *argptrs[sizeof...(p_args) + 1]; for (uint32_t i = 0; i < sizeof...(p_args); i++) { argptrs[i] = &args[i]; } - call_group_flagsp(0, p_group, p_function, sizeof...(p_args) == 0 ? nullptr : (const Variant **)argptrs, sizeof...(p_args)); + call_group_flagsp(GROUP_CALL_DEFAULT, p_group, p_function, sizeof...(p_args) == 0 ? nullptr : (const Variant **)argptrs, sizeof...(p_args)); } template <typename... VarArgs> @@ -266,7 +277,10 @@ public: virtual void finalize() override; + bool is_auto_accept_quit() const; void set_auto_accept_quit(bool p_enable); + + bool is_quit_on_go_back() const; void set_quit_on_go_back(bool p_enable); void quit(int p_exit_code = EXIT_SUCCESS); @@ -287,12 +301,18 @@ public: void set_debug_collisions_hint(bool p_enabled); bool is_debugging_collisions_hint() const; + void set_debug_paths_hint(bool p_enabled); + bool is_debugging_paths_hint() const; + void set_debug_navigation_hint(bool p_enabled); bool is_debugging_navigation_hint() const; #else void set_debug_collisions_hint(bool p_enabled) {} bool is_debugging_collisions_hint() const { return false; } + void set_debug_paths_hint(bool p_enabled) {} + bool is_debugging_paths_hint() const { return false; } + void set_debug_navigation_hint(bool p_enabled) {} bool is_debugging_navigation_hint() const { return false; } #endif @@ -303,12 +323,19 @@ public: void set_debug_collision_contact_color(const Color &p_color); Color get_debug_collision_contact_color() const; + void set_debug_paths_color(const Color &p_color); + Color get_debug_paths_color() const; + + void set_debug_paths_width(float p_width); + float get_debug_paths_width() const; + void set_debug_navigation_color(const Color &p_color); Color get_debug_navigation_color() const; void set_debug_navigation_disabled_color(const Color &p_color); Color get_debug_navigation_disabled_color() const; + Ref<Material> get_debug_paths_material(); Ref<Material> get_debug_navigation_material(); Ref<Material> get_debug_navigation_disabled_material(); Ref<Material> get_debug_collision_material(); @@ -351,10 +378,10 @@ public: //network API - Ref<MultiplayerAPI> get_multiplayer() const; + Ref<MultiplayerAPI> get_multiplayer(const NodePath &p_for_path = NodePath()) const; + void set_multiplayer(Ref<MultiplayerAPI> p_multiplayer, const NodePath &p_root_path = NodePath()); void set_multiplayer_poll_enabled(bool p_enabled); bool is_multiplayer_poll_enabled() const; - void set_multiplayer(Ref<MultiplayerAPI> p_multiplayer); static void add_idle_callback(IdleCallback p_callback); diff --git a/scene/main/shader_globals_override.cpp b/scene/main/shader_globals_override.cpp index 9b85e9db38..0049359cad 100644 --- a/scene/main/shader_globals_override.cpp +++ b/scene/main/shader_globals_override.cpp @@ -155,7 +155,7 @@ void ShaderGlobalsOverride::_get_property_list(List<PropertyInfo> *p_list) const pinfo.type = Variant::VECTOR3; } break; case RS::GLOBAL_VAR_TYPE_VEC4: { - pinfo.type = Variant::PLANE; + pinfo.type = Variant::QUATERNION; } break; case RS::GLOBAL_VAR_TYPE_RECT2: { pinfo.type = Variant::RECT2; @@ -229,20 +229,19 @@ void ShaderGlobalsOverride::_activate() { active = true; add_to_group(SceneStringNames::get_singleton()->shader_overrides_group_active); - const StringName *K = nullptr; - while ((K = overrides.next(K))) { - Override *o = overrides.getptr(*K); + for (const KeyValue<StringName, Override> &E : overrides) { + const Override *o = &E.value; if (o->in_use && o->override.get_type() != Variant::NIL) { if (o->override.get_type() == Variant::OBJECT) { RID tex_rid = o->override; - RS::get_singleton()->global_variable_set_override(*K, tex_rid); + RS::get_singleton()->global_variable_set_override(E.key, tex_rid); } else { - RS::get_singleton()->global_variable_set_override(*K, o->override); + RS::get_singleton()->global_variable_set_override(E.key, o->override); } } - } - update_configuration_warnings(); //may have activated + update_configuration_warnings(); //may have activated + } } } @@ -256,18 +255,17 @@ void ShaderGlobalsOverride::_notification(int p_what) { case Node3D::NOTIFICATION_EXIT_TREE: { if (active) { //remove overrides - const StringName *K = nullptr; - while ((K = overrides.next(K))) { - Override *o = overrides.getptr(*K); + for (const KeyValue<StringName, Override> &E : overrides) { + const Override *o = &E.value; if (o->in_use) { - RS::get_singleton()->global_variable_set_override(*K, Variant()); + RS::get_singleton()->global_variable_set_override(E.key, Variant()); } } } remove_from_group(SceneStringNames::get_singleton()->shader_overrides_group_active); remove_from_group(SceneStringNames::get_singleton()->shader_overrides_group); - get_tree()->call_group(SceneStringNames::get_singleton()->shader_overrides_group, "_activate"); //another may want to activate when this is removed + get_tree()->call_group_flags(SceneTree::GROUP_CALL_DEFERRED, SceneStringNames::get_singleton()->shader_overrides_group, "_activate"); //another may want to activate when this is removed active = false; } break; } diff --git a/scene/main/timer.cpp b/scene/main/timer.cpp index 5a5747e122..bb9359ef59 100644 --- a/scene/main/timer.cpp +++ b/scene/main/timer.cpp @@ -216,11 +216,11 @@ void Timer::_bind_methods() { ADD_SIGNAL(MethodInfo("timeout")); ADD_PROPERTY(PropertyInfo(Variant::INT, "process_callback", PROPERTY_HINT_ENUM, "Physics,Idle"), "set_timer_process_callback", "get_timer_process_callback"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "wait_time", PROPERTY_HINT_RANGE, "0.001,4096,0.001,or_greater,exp"), "set_wait_time", "get_wait_time"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "wait_time", PROPERTY_HINT_RANGE, "0.001,4096,0.001,or_greater,exp,suffix:s"), "set_wait_time", "get_wait_time"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "one_shot"), "set_one_shot", "is_one_shot"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "autostart"), "set_autostart", "has_autostart"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "paused", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_paused", "is_paused"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "time_left", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "", "get_time_left"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "time_left", PROPERTY_HINT_NONE, "suffix:s", PROPERTY_USAGE_NONE), "", "get_time_left"); BIND_ENUM_CONSTANT(TIMER_PROCESS_PHYSICS); BIND_ENUM_CONSTANT(TIMER_PROCESS_IDLE); diff --git a/scene/main/viewport.cpp b/scene/main/viewport.cpp index b78f07b6d0..c2fa1ace8d 100644 --- a/scene/main/viewport.cpp +++ b/scene/main/viewport.cpp @@ -161,29 +161,6 @@ ViewportTexture::~ViewportTexture() { } } -///////////////////////////////////// - -// Aliases used to provide custom styles to tooltips in the default -// theme and editor theme. -// TooltipPanel is also used for custom tooltips, while TooltipLabel -// is only relevant for default tooltips. - -class TooltipPanel : public PopupPanel { - GDCLASS(TooltipPanel, PopupPanel); - -public: - TooltipPanel() {} -}; - -class TooltipLabel : public Label { - GDCLASS(TooltipLabel, Label); - -public: - TooltipLabel() {} -}; - -///////////////////////////////////// - void Viewport::_sub_window_update_order() { for (int i = 0; i < gui.sub_windows.size(); i++) { RS::get_singleton()->canvas_item_set_draw_index(gui.sub_windows[i].canvas_item, i); @@ -239,10 +216,10 @@ void Viewport::_sub_window_update(Window *p_window) { int font_size = p_window->get_theme_font_size(SNAME("title_font_size")); Color title_color = p_window->get_theme_color(SNAME("title_color")); int title_height = p_window->get_theme_constant(SNAME("title_height")); - int close_h_ofs = p_window->get_theme_constant(SNAME("close_h_ofs")); - int close_v_ofs = p_window->get_theme_constant(SNAME("close_v_ofs")); + int close_h_ofs = p_window->get_theme_constant(SNAME("close_h_offset")); + int close_v_ofs = p_window->get_theme_constant(SNAME("close_v_offset")); - TextLine title_text = TextLine(p_window->atr(p_window->get_title()), title_font, font_size, Dictionary(), TranslationServer::get_singleton()->get_tool_locale()); + TextLine title_text = TextLine(p_window->atr(p_window->get_title()), title_font, font_size); title_text.set_width(r.size.width - panel->get_minimum_size().x - close_h_ofs); title_text.set_direction(p_window->is_layout_rtl() ? TextServer::DIRECTION_RTL : TextServer::DIRECTION_LTR); int x = (r.size.width - title_text.get_size().x) / 2; @@ -412,9 +389,9 @@ void Viewport::_notification(int p_what) { #ifndef _3D_DISABLED if (audio_listener_3d_set.size() && !audio_listener_3d) { AudioListener3D *first = nullptr; - for (Set<AudioListener3D *>::Element *E = audio_listener_3d_set.front(); E; E = E->next()) { - if (first == nullptr || first->is_greater_than(E->get())) { - first = E->get(); + for (AudioListener3D *E : audio_listener_3d_set) { + if (first == nullptr || first->is_greater_than(E)) { + first = E; } } @@ -426,9 +403,9 @@ void Viewport::_notification(int p_what) { if (camera_3d_set.size() && !camera_3d) { // There are cameras but no current camera, pick first in tree and make it current. Camera3D *first = nullptr; - for (Set<Camera3D *>::Element *E = camera_3d_set.front(); E; E = E->next()) { - if (first == nullptr || first->is_greater_than(E->get())) { - first = E->get(); + for (Camera3D *E : camera_3d_set) { + if (first == nullptr || first->is_greater_than(E)) { + first = E; } } @@ -647,13 +624,13 @@ void Viewport::_process_picking() { uint64_t frame = get_tree()->get_frame(); PhysicsDirectSpaceState2D::ShapeResult res[64]; - for (Set<CanvasLayer *>::Element *E = canvas_layers.front(); E; E = E->next()) { + for (const CanvasLayer *E : canvas_layers) { Transform2D canvas_transform; ObjectID canvas_layer_id; - if (E->get()) { + if (E) { // A descendant CanvasLayer. - canvas_transform = E->get()->get_transform(); - canvas_layer_id = E->get()->get_instance_id(); + canvas_transform = E->get_transform(); + canvas_layer_id = E->get_instance_id(); } else { // This Viewport's builtin canvas. canvas_transform = get_canvas_transform(); @@ -675,23 +652,23 @@ void Viewport::_process_picking() { if (co && co->can_process()) { bool send_event = true; if (is_mouse) { - Map<ObjectID, uint64_t>::Element *F = physics_2d_mouseover.find(res[i].collider_id); + HashMap<ObjectID, uint64_t>::Iterator F = physics_2d_mouseover.find(res[i].collider_id); if (!F) { physics_2d_mouseover.insert(res[i].collider_id, frame); co->_mouse_enter(); } else { - F->get() = frame; + F->value = frame; // It was already hovered, so don't send the event if it's faked. if (mm.is_valid() && mm->get_device() == InputEvent::DEVICE_ID_INTERNAL) { send_event = false; } } - Map<Pair<ObjectID, int>, uint64_t, PairSort<ObjectID, int>>::Element *SF = physics_2d_shape_mouseover.find(Pair(res[i].collider_id, res[i].shape)); + HashMap<Pair<ObjectID, int>, uint64_t, PairHash<ObjectID, int>>::Iterator SF = physics_2d_shape_mouseover.find(Pair(res[i].collider_id, res[i].shape)); if (!SF) { physics_2d_shape_mouseover.insert(Pair(res[i].collider_id, res[i].shape), frame); co->_mouse_shape_enter(res[i].shape); } else { - SF->get() = frame; + SF->value = frame; } } @@ -1060,51 +1037,51 @@ Ref<ViewportTexture> Viewport::get_texture() const { return default_texture; } -void Viewport::set_shadow_atlas_size(int p_size) { - shadow_atlas_size = p_size; - RS::get_singleton()->viewport_set_shadow_atlas_size(viewport, p_size, shadow_atlas_16_bits); +void Viewport::set_positional_shadow_atlas_size(int p_size) { + positional_shadow_atlas_size = p_size; + RS::get_singleton()->viewport_set_positional_shadow_atlas_size(viewport, p_size, positional_shadow_atlas_16_bits); } -int Viewport::get_shadow_atlas_size() const { - return shadow_atlas_size; +int Viewport::get_positional_shadow_atlas_size() const { + return positional_shadow_atlas_size; } -void Viewport::set_shadow_atlas_16_bits(bool p_16_bits) { - if (shadow_atlas_16_bits == p_16_bits) { +void Viewport::set_positional_shadow_atlas_16_bits(bool p_16_bits) { + if (positional_shadow_atlas_16_bits == p_16_bits) { return; } - shadow_atlas_16_bits = p_16_bits; - RS::get_singleton()->viewport_set_shadow_atlas_size(viewport, shadow_atlas_size, shadow_atlas_16_bits); + positional_shadow_atlas_16_bits = p_16_bits; + RS::get_singleton()->viewport_set_positional_shadow_atlas_size(viewport, positional_shadow_atlas_size, positional_shadow_atlas_16_bits); } -bool Viewport::get_shadow_atlas_16_bits() const { - return shadow_atlas_16_bits; +bool Viewport::get_positional_shadow_atlas_16_bits() const { + return positional_shadow_atlas_16_bits; } -void Viewport::set_shadow_atlas_quadrant_subdiv(int p_quadrant, ShadowAtlasQuadrantSubdiv p_subdiv) { +void Viewport::set_positional_shadow_atlas_quadrant_subdiv(int p_quadrant, PositionalShadowAtlasQuadrantSubdiv p_subdiv) { ERR_FAIL_INDEX(p_quadrant, 4); ERR_FAIL_INDEX(p_subdiv, SHADOW_ATLAS_QUADRANT_SUBDIV_MAX); - if (shadow_atlas_quadrant_subdiv[p_quadrant] == p_subdiv) { + if (positional_shadow_atlas_quadrant_subdiv[p_quadrant] == p_subdiv) { return; } - shadow_atlas_quadrant_subdiv[p_quadrant] = p_subdiv; + positional_shadow_atlas_quadrant_subdiv[p_quadrant] = p_subdiv; static const int subdiv[SHADOW_ATLAS_QUADRANT_SUBDIV_MAX] = { 0, 1, 4, 16, 64, 256, 1024 }; - RS::get_singleton()->viewport_set_shadow_atlas_quadrant_subdivision(viewport, p_quadrant, subdiv[p_subdiv]); + RS::get_singleton()->viewport_set_positional_shadow_atlas_quadrant_subdivision(viewport, p_quadrant, subdiv[p_subdiv]); } -Viewport::ShadowAtlasQuadrantSubdiv Viewport::get_shadow_atlas_quadrant_subdiv(int p_quadrant) const { +Viewport::PositionalShadowAtlasQuadrantSubdiv Viewport::get_positional_shadow_atlas_quadrant_subdiv(int p_quadrant) const { ERR_FAIL_INDEX_V(p_quadrant, 4, SHADOW_ATLAS_QUADRANT_SUBDIV_DISABLED); - return shadow_atlas_quadrant_subdiv[p_quadrant]; + return positional_shadow_atlas_quadrant_subdiv[p_quadrant]; } Transform2D Viewport::_get_input_pre_xform() const { Transform2D pre_xf; if (to_screen_rect.size.x != 0 && to_screen_rect.size.y != 0) { - pre_xf.elements[2] = -to_screen_rect.position; + pre_xf.columns[2] = -to_screen_rect.position; pre_xf.scale(Vector2(size) / to_screen_rect.size); } @@ -1221,7 +1198,8 @@ void Viewport::_gui_show_tooltip() { } // Popup window which houses the tooltip content. - TooltipPanel *panel = memnew(TooltipPanel); + PopupPanel *panel = memnew(PopupPanel); + panel->set_theme_type_variation(SNAME("TooltipPanel")); // Controls can implement `make_custom_tooltip` to provide their own tooltip. // This should be a Control node which will be added as child to a TooltipPanel. @@ -1229,14 +1207,15 @@ void Viewport::_gui_show_tooltip() { // If no custom tooltip is given, use a default implementation. if (!base_tooltip) { - gui.tooltip_label = memnew(TooltipLabel); + gui.tooltip_label = memnew(Label); + gui.tooltip_label->set_theme_type_variation(SNAME("TooltipLabel")); gui.tooltip_label->set_auto_translate(gui.tooltip_control->is_auto_translating()); gui.tooltip_label->set_text(tooltip_text); base_tooltip = gui.tooltip_label; panel->connect("mouse_entered", callable_mp(this, &Viewport::_gui_cancel_tooltip)); } - base_tooltip->set_anchors_and_offsets_preset(Control::PRESET_WIDE); + base_tooltip->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT); panel->set_transient(true); panel->set_flag(Window::FLAG_NO_FOCUS, true); @@ -1277,21 +1256,19 @@ void Viewport::_gui_show_tooltip() { gui.tooltip_popup->child_controls_changed(); } -void Viewport::_gui_call_input(Control *p_control, const Ref<InputEvent> &p_input) { +bool Viewport::_gui_call_input(Control *p_control, const Ref<InputEvent> &p_input) { + bool stopped = false; Ref<InputEvent> ev = p_input; - // Mouse wheel events can't be stopped. - Ref<InputEventMouseButton> mb = p_input; + // Returns true if an event should be impacted by a control's mouse filter. + bool is_mouse_event = Ref<InputEventMouse>(p_input).is_valid(); - bool cant_stop_me_now = (mb.is_valid() && + Ref<InputEventMouseButton> mb = p_input; + bool is_scroll_event = mb.is_valid() && (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; - - bool ismouse = ev.is_valid() || Object::cast_to<InputEventMouseMotion>(*p_input) != nullptr; + mb->get_button_index() == MouseButton::WHEEL_RIGHT); CanvasItem *ci = p_control; while (ci) { @@ -1305,9 +1282,12 @@ void Viewport::_gui_call_input(Control *p_control, const Ref<InputEvent> &p_inpu break; } if (gui.key_event_accepted) { + stopped = true; break; } - if (!cant_stop_me_now && control->data.mouse_filter == Control::MOUSE_FILTER_STOP && ismouse) { + if (control->data.mouse_filter == Control::MOUSE_FILTER_STOP && is_mouse_event && !(is_scroll_event && control->data.force_pass_scroll_events)) { + // Mouse events are stopped by default with MOUSE_FILTER_STOP, unless we have a scroll event and force_pass_scroll_events set to true + stopped = true; break; } } @@ -1319,6 +1299,7 @@ void Viewport::_gui_call_input(Control *p_control, const Ref<InputEvent> &p_inpu ev = ev->xformed_by(ci->get_transform()); // Transform event upwards. ci = ci->get_parent_item(); } + return stopped; } void Viewport::_gui_call_notification(Control *p_control, int p_what) { @@ -1530,11 +1511,14 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { } } + bool stopped = false; if (gui.mouse_focus && gui.mouse_focus->can_process()) { - _gui_call_input(gui.mouse_focus, mb); + stopped = _gui_call_input(gui.mouse_focus, mb); } - set_input_as_handled(); + if (stopped) { + set_input_as_handled(); + } 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). @@ -1600,11 +1584,14 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { gui.forced_mouse_focus = false; } + bool stopped = false; if (mouse_focus && mouse_focus->can_process()) { - _gui_call_input(mouse_focus, mb); + stopped = _gui_call_input(mouse_focus, mb); } - set_input_as_handled(); + if (stopped) { + set_input_as_handled(); + } } } @@ -1767,11 +1754,14 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { ds_cursor_shape = (DisplayServer::CursorShape)cursor_shape; + bool stopped = false; if (over && over->can_process()) { - _gui_call_input(over, mm); + stopped = _gui_call_input(over, mm); } - set_input_as_handled(); + if (stopped) { + set_input_as_handled(); + } } if (gui.drag_data.get_type() != Variant::NIL) { @@ -1884,6 +1874,7 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { if (touch_event->is_pressed()) { Control *over = gui_find_control(pos); if (over) { + bool stopped = false; if (over->can_process()) { touch_event = touch_event->xformed_by(Transform2D()); // Make a copy. if (over == gui.mouse_focus) { @@ -1892,19 +1883,24 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { pos = over->get_global_transform_with_canvas().affine_inverse().xform(pos); } touch_event->set_position(pos); - _gui_call_input(over, touch_event); + stopped = _gui_call_input(over, touch_event); + } + if (stopped) { + set_input_as_handled(); } - set_input_as_handled(); return; } } else if (touch_event->get_index() == 0 && gui.last_mouse_focus) { + bool stopped = false; if (gui.last_mouse_focus->can_process()) { touch_event = touch_event->xformed_by(Transform2D()); // Make a copy. touch_event->set_position(gui.focus_inv_xform.xform(pos)); - _gui_call_input(gui.last_mouse_focus, touch_event); + stopped = _gui_call_input(gui.last_mouse_focus, touch_event); + } + if (stopped) { + set_input_as_handled(); } - set_input_as_handled(); return; } } @@ -1919,6 +1915,7 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { Control *over = gui_find_control(pos); if (over) { + bool stopped = false; if (over->can_process()) { gesture_event = gesture_event->xformed_by(Transform2D()); // Make a copy. if (over == gui.mouse_focus) { @@ -1927,9 +1924,11 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { pos = over->get_global_transform_with_canvas().affine_inverse().xform(pos); } gesture_event->set_position(pos); - _gui_call_input(over, gesture_event); + stopped = _gui_call_input(over, gesture_event); + } + if (stopped) { + set_input_as_handled(); } - set_input_as_handled(); return; } } @@ -1941,6 +1940,7 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { over = gui_find_control(drag_event->get_position()); } if (over) { + bool stopped = false; if (over->can_process()) { Transform2D localizer = over->get_global_transform_with_canvas().affine_inverse(); Size2 pos = localizer.xform(drag_event->get_position()); @@ -1953,10 +1953,12 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { drag_event->set_relative(rel); drag_event->set_position(pos); - _gui_call_input(over, drag_event); + stopped = _gui_call_input(over, drag_event); } - set_input_as_handled(); + if (stopped) { + set_input_as_handled(); + } return; } } @@ -2186,7 +2188,7 @@ void Viewport::_gui_control_grab_focus(Control *p_control) { // No need for change. return; } - get_tree()->call_group_flags(SceneTree::GROUP_CALL_REALTIME, "_viewports", "_gui_remove_focus_for_window", (Node *)get_base_window()); + get_tree()->call_group("_viewports", "_gui_remove_focus_for_window", (Node *)get_base_window()); gui.key_focus = p_control; emit_signal(SNAME("gui_focus_changed"), p_control); p_control->notification(Control::NOTIFICATION_FOCUS_ENTER); @@ -2250,14 +2252,14 @@ void Viewport::_drop_physics_mouseover(bool p_paused_only) { } void Viewport::_cleanup_mouseover_colliders(bool p_clean_all_frames, bool p_paused_only, uint64_t p_frame_reference) { - List<Map<ObjectID, uint64_t>::Element *> to_erase; + List<ObjectID> to_erase; - for (Map<ObjectID, uint64_t>::Element *E = physics_2d_mouseover.front(); E; E = E->next()) { - if (!p_clean_all_frames && E->get() == p_frame_reference) { + for (const KeyValue<ObjectID, uint64_t> &E : physics_2d_mouseover) { + if (!p_clean_all_frames && E.value == p_frame_reference) { continue; } - Object *o = ObjectDB::get_instance(E->key()); + Object *o = ObjectDB::get_instance(E.key); if (o) { CollisionObject2D *co = Object::cast_to<CollisionObject2D>(o); if (co && co->is_inside_tree()) { @@ -2267,7 +2269,7 @@ void Viewport::_cleanup_mouseover_colliders(bool p_clean_all_frames, bool p_paus co->_mouse_exit(); } } - to_erase.push_back(E); + to_erase.push_back(E.key); } while (to_erase.size()) { @@ -2276,24 +2278,24 @@ void Viewport::_cleanup_mouseover_colliders(bool p_clean_all_frames, bool p_paus } // Per-shape. - List<Map<Pair<ObjectID, int>, uint64_t, PairSort<ObjectID, int>>::Element *> shapes_to_erase; + List<Pair<ObjectID, int>> shapes_to_erase; - for (Map<Pair<ObjectID, int>, uint64_t, PairSort<ObjectID, int>>::Element *E = physics_2d_shape_mouseover.front(); E; E = E->next()) { - if (!p_clean_all_frames && E->get() == p_frame_reference) { + for (KeyValue<Pair<ObjectID, int>, uint64_t> &E : physics_2d_shape_mouseover) { + if (!p_clean_all_frames && E.value == p_frame_reference) { continue; } - Object *o = ObjectDB::get_instance(E->key().first); + Object *o = ObjectDB::get_instance(E.key.first); if (o) { CollisionObject2D *co = Object::cast_to<CollisionObject2D>(o); if (co && co->is_inside_tree()) { if (p_clean_all_frames && p_paused_only && co->can_process()) { continue; } - co->_mouse_shape_exit(E->key().second); + co->_mouse_shape_exit(E.key.second); } } - shapes_to_erase.push_back(E); + shapes_to_erase.push_back(E.key); } while (shapes_to_erase.size()) { @@ -2583,8 +2585,8 @@ bool Viewport::_sub_windows_forward_input(const Ref<InputEvent> &p_event) { if (title_bar.has_point(mb->get_position())) { click_on_window = true; - int close_h_ofs = sw.window->get_theme_constant(SNAME("close_h_ofs")); - int close_v_ofs = sw.window->get_theme_constant(SNAME("close_v_ofs")); + int close_h_ofs = sw.window->get_theme_constant(SNAME("close_h_offset")); + int close_v_ofs = sw.window->get_theme_constant(SNAME("close_v_offset")); Ref<Texture2D> close_icon = sw.window->get_theme_icon(SNAME("close")); Rect2 close_rect; @@ -2874,6 +2876,18 @@ Viewport::ScreenSpaceAA Viewport::get_screen_space_aa() const { return screen_space_aa; } +void Viewport::set_use_taa(bool p_use_taa) { + if (use_taa == p_use_taa) { + return; + } + use_taa = p_use_taa; + RS::get_singleton()->viewport_set_use_taa(viewport, p_use_taa); +} + +bool Viewport::is_using_taa() const { + return use_taa; +} + void Viewport::set_use_debanding(bool p_use_debanding) { if (use_debanding == p_use_debanding) { return; @@ -3066,6 +3080,41 @@ Viewport::DefaultCanvasItemTextureRepeat Viewport::get_default_canvas_item_textu return default_canvas_item_texture_repeat; } +void Viewport::set_vrs_mode(Viewport::VRSMode p_vrs_mode) { + // Note, set this even if not supported on this hardware, it will only be used if it is but we want to save the value as set by the user. + vrs_mode = p_vrs_mode; + + switch (p_vrs_mode) { + case VRS_TEXTURE: { + RS::get_singleton()->viewport_set_vrs_mode(viewport, RS::VIEWPORT_VRS_TEXTURE); + } break; + case VRS_XR: { + RS::get_singleton()->viewport_set_vrs_mode(viewport, RS::VIEWPORT_VRS_XR); + } break; + default: { + RS::get_singleton()->viewport_set_vrs_mode(viewport, RS::VIEWPORT_VRS_DISABLED); + } break; + } + + notify_property_list_changed(); +} + +Viewport::VRSMode Viewport::get_vrs_mode() const { + return vrs_mode; +} + +void Viewport::set_vrs_texture(Ref<Texture2D> p_texture) { + vrs_texture = p_texture; + + // TODO need to add something here in case the RID changes + RID tex = p_texture.is_valid() ? p_texture->get_rid() : RID(); + RS::get_singleton()->viewport_set_vrs_texture(viewport, tex); +} + +Ref<Texture2D> Viewport::get_vrs_texture() const { + return vrs_texture; +} + DisplayServer::WindowID Viewport::get_window_id() const { return DisplayServer::MAIN_WINDOW_ID; } @@ -3179,18 +3228,18 @@ void Viewport::_audio_listener_3d_remove(AudioListener3D *p_listener) { void Viewport::_audio_listener_3d_make_next_current(AudioListener3D *p_exclude) { if (audio_listener_3d_set.size() > 0) { - for (Set<AudioListener3D *>::Element *E = audio_listener_3d_set.front(); E; E = E->next()) { - if (p_exclude == E->get()) { + for (AudioListener3D *E : audio_listener_3d_set) { + if (p_exclude == E) { continue; } - if (!E->get()->is_inside_tree()) { + if (!E->is_inside_tree()) { continue; } if (audio_listener_3d != nullptr) { return; } - E->get()->make_current(); + E->make_current(); } } else { // Attempt to reset listener to the camera position. @@ -3267,18 +3316,18 @@ void Viewport::_camera_3d_remove(Camera3D *p_camera) { } void Viewport::_camera_3d_make_next_current(Camera3D *p_exclude) { - for (Set<Camera3D *>::Element *E = camera_3d_set.front(); E; E = E->next()) { - if (p_exclude == E->get()) { + for (Camera3D *E : camera_3d_set) { + if (p_exclude == E) { continue; } - if (!E->get()->is_inside_tree()) { + if (!E->is_inside_tree()) { continue; } if (camera_3d != nullptr) { return; } - E->get()->make_current(); + E->make_current(); } } @@ -3435,8 +3484,8 @@ void Viewport::_own_world_3d_changed() { _update_audio_listener_3d(); } -void Viewport::set_use_own_world_3d(bool p_world_3d) { - if (p_world_3d == own_world_3d.is_valid()) { +void Viewport::set_use_own_world_3d(bool p_use_own_world_3d) { + if (p_use_own_world_3d == own_world_3d.is_valid()) { return; } @@ -3444,18 +3493,18 @@ void Viewport::set_use_own_world_3d(bool p_world_3d) { _propagate_exit_world_3d(this); } - if (!p_world_3d) { - own_world_3d = Ref<World3D>(); - if (world_3d.is_valid()) { - world_3d->disconnect(CoreStringNames::get_singleton()->changed, callable_mp(this, &Viewport::_own_world_3d_changed)); - } - } else { + if (p_use_own_world_3d) { if (world_3d.is_valid()) { own_world_3d = world_3d->duplicate(); world_3d->connect(CoreStringNames::get_singleton()->changed, callable_mp(this, &Viewport::_own_world_3d_changed)); } else { own_world_3d = Ref<World3D>(memnew(World3D)); } + } else { + own_world_3d = Ref<World3D>(); + if (world_3d.is_valid()) { + world_3d->disconnect(CoreStringNames::get_singleton()->changed, callable_mp(this, &Viewport::_own_world_3d_changed)); + } } if (is_inside_tree()) { @@ -3609,6 +3658,9 @@ void Viewport::_bind_methods() { ClassDB::bind_method(D_METHOD("set_screen_space_aa", "screen_space_aa"), &Viewport::set_screen_space_aa); ClassDB::bind_method(D_METHOD("get_screen_space_aa"), &Viewport::get_screen_space_aa); + ClassDB::bind_method(D_METHOD("set_use_taa", "enable"), &Viewport::set_use_taa); + ClassDB::bind_method(D_METHOD("is_using_taa"), &Viewport::is_using_taa); + ClassDB::bind_method(D_METHOD("set_use_debanding", "enable"), &Viewport::set_use_debanding); ClassDB::bind_method(D_METHOD("is_using_debanding"), &Viewport::is_using_debanding); @@ -3650,11 +3702,11 @@ void Viewport::_bind_methods() { ClassDB::bind_method(D_METHOD("_gui_remove_focus_for_window"), &Viewport::_gui_remove_focus_for_window); ClassDB::bind_method(D_METHOD("_post_gui_grab_click_focus"), &Viewport::_post_gui_grab_click_focus); - ClassDB::bind_method(D_METHOD("set_shadow_atlas_size", "size"), &Viewport::set_shadow_atlas_size); - ClassDB::bind_method(D_METHOD("get_shadow_atlas_size"), &Viewport::get_shadow_atlas_size); + ClassDB::bind_method(D_METHOD("set_positional_shadow_atlas_size", "size"), &Viewport::set_positional_shadow_atlas_size); + ClassDB::bind_method(D_METHOD("get_positional_shadow_atlas_size"), &Viewport::get_positional_shadow_atlas_size); - ClassDB::bind_method(D_METHOD("set_shadow_atlas_16_bits", "enable"), &Viewport::set_shadow_atlas_16_bits); - ClassDB::bind_method(D_METHOD("get_shadow_atlas_16_bits"), &Viewport::get_shadow_atlas_16_bits); + ClassDB::bind_method(D_METHOD("set_positional_shadow_atlas_16_bits", "enable"), &Viewport::set_positional_shadow_atlas_16_bits); + ClassDB::bind_method(D_METHOD("get_positional_shadow_atlas_16_bits"), &Viewport::get_positional_shadow_atlas_16_bits); ClassDB::bind_method(D_METHOD("set_snap_controls_to_pixels", "enabled"), &Viewport::set_snap_controls_to_pixels); ClassDB::bind_method(D_METHOD("is_snap_controls_to_pixels_enabled"), &Viewport::is_snap_controls_to_pixels_enabled); @@ -3665,8 +3717,8 @@ void Viewport::_bind_methods() { ClassDB::bind_method(D_METHOD("set_snap_2d_vertices_to_pixel", "enabled"), &Viewport::set_snap_2d_vertices_to_pixel); ClassDB::bind_method(D_METHOD("is_snap_2d_vertices_to_pixel_enabled"), &Viewport::is_snap_2d_vertices_to_pixel_enabled); - ClassDB::bind_method(D_METHOD("set_shadow_atlas_quadrant_subdiv", "quadrant", "subdiv"), &Viewport::set_shadow_atlas_quadrant_subdiv); - ClassDB::bind_method(D_METHOD("get_shadow_atlas_quadrant_subdiv", "quadrant"), &Viewport::get_shadow_atlas_quadrant_subdiv); + ClassDB::bind_method(D_METHOD("set_positional_shadow_atlas_quadrant_subdiv", "quadrant", "subdiv"), &Viewport::set_positional_shadow_atlas_quadrant_subdiv); + ClassDB::bind_method(D_METHOD("get_positional_shadow_atlas_quadrant_subdiv", "quadrant"), &Viewport::get_positional_shadow_atlas_quadrant_subdiv); ClassDB::bind_method(D_METHOD("set_input_as_handled"), &Viewport::set_input_as_handled); ClassDB::bind_method(D_METHOD("is_input_handled"), &Viewport::is_input_handled); @@ -3724,6 +3776,12 @@ void Viewport::_bind_methods() { ClassDB::bind_method(D_METHOD("set_fsr_mipmap_bias", "fsr_mipmap_bias"), &Viewport::set_fsr_mipmap_bias); ClassDB::bind_method(D_METHOD("get_fsr_mipmap_bias"), &Viewport::get_fsr_mipmap_bias); + ClassDB::bind_method(D_METHOD("set_vrs_mode", "mode"), &Viewport::set_vrs_mode); + ClassDB::bind_method(D_METHOD("get_vrs_mode"), &Viewport::get_vrs_mode); + + ClassDB::bind_method(D_METHOD("set_vrs_texture", "texture"), &Viewport::set_vrs_texture); + ClassDB::bind_method(D_METHOD("get_vrs_texture"), &Viewport::get_vrs_texture); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "disable_3d"), "set_disable_3d", "is_3d_disabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_xr"), "set_use_xr", "is_using_xr"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "own_world_3d"), "set_use_own_world_3d", "is_using_own_world_3d"); @@ -3737,6 +3795,7 @@ void Viewport::_bind_methods() { ADD_GROUP("Rendering", ""); ADD_PROPERTY(PropertyInfo(Variant::INT, "msaa", PROPERTY_HINT_ENUM, String::utf8("Disabled (Fastest),2× (Average),4× (Slow),8× (Slowest)")), "set_msaa", "get_msaa"); ADD_PROPERTY(PropertyInfo(Variant::INT, "screen_space_aa", PROPERTY_HINT_ENUM, "Disabled (Fastest),FXAA (Fast)"), "set_screen_space_aa", "get_screen_space_aa"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_taa"), "set_use_taa", "is_using_taa"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_debanding"), "set_use_debanding", "is_using_debanding"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_occlusion_culling"), "set_use_occlusion_culling", "is_using_occlusion_culling"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "mesh_lod_threshold", PROPERTY_HINT_RANGE, "0,1024,0.1"), "set_mesh_lod_threshold", "get_mesh_lod_threshold"); @@ -3748,6 +3807,9 @@ void Viewport::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "fsr_mipmap_bias", PROPERTY_HINT_RANGE, "-2,2,0.1"), "set_fsr_mipmap_bias", "get_fsr_mipmap_bias"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "fsr_sharpness", PROPERTY_HINT_RANGE, "0,2,0.1"), "set_fsr_sharpness", "get_fsr_sharpness"); #endif + ADD_GROUP("Variable Rate Shading", "vrs_"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "vrs_mode", PROPERTY_HINT_ENUM, "Disabled,Texture,Depth buffer,XR"), "set_vrs_mode", "get_vrs_mode"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "vrs_texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_vrs_texture", "get_vrs_texture"); ADD_GROUP("Canvas Items", "canvas_item_"); ADD_PROPERTY(PropertyInfo(Variant::INT, "canvas_item_default_texture_filter", PROPERTY_HINT_ENUM, "Nearest,Linear,Linear Mipmap,Nearest Mipmap"), "set_default_canvas_item_texture_filter", "get_default_canvas_item_texture_filter"); ADD_PROPERTY(PropertyInfo(Variant::INT, "canvas_item_default_texture_repeat", PROPERTY_HINT_ENUM, "Disabled,Enabled,Mirror"), "set_default_canvas_item_texture_repeat", "get_default_canvas_item_texture_repeat"); @@ -3765,13 +3827,13 @@ void Viewport::_bind_methods() { ADD_GROUP("SDF", "sdf_"); ADD_PROPERTY(PropertyInfo(Variant::INT, "sdf_oversize", PROPERTY_HINT_ENUM, "100%,120%,150%,200%"), "set_sdf_oversize", "get_sdf_oversize"); ADD_PROPERTY(PropertyInfo(Variant::INT, "sdf_scale", PROPERTY_HINT_ENUM, "100%,50%,25%"), "set_sdf_scale", "get_sdf_scale"); - ADD_GROUP("Shadow Atlas", "shadow_atlas_"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "shadow_atlas_size"), "set_shadow_atlas_size", "get_shadow_atlas_size"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "shadow_atlas_16_bits"), "set_shadow_atlas_16_bits", "get_shadow_atlas_16_bits"); - ADD_PROPERTYI(PropertyInfo(Variant::INT, "shadow_atlas_quad_0", PROPERTY_HINT_ENUM, "Disabled,1 Shadow,4 Shadows,16 Shadows,64 Shadows,256 Shadows,1024 Shadows"), "set_shadow_atlas_quadrant_subdiv", "get_shadow_atlas_quadrant_subdiv", 0); - ADD_PROPERTYI(PropertyInfo(Variant::INT, "shadow_atlas_quad_1", PROPERTY_HINT_ENUM, "Disabled,1 Shadow,4 Shadows,16 Shadows,64 Shadows,256 Shadows,1024 Shadows"), "set_shadow_atlas_quadrant_subdiv", "get_shadow_atlas_quadrant_subdiv", 1); - ADD_PROPERTYI(PropertyInfo(Variant::INT, "shadow_atlas_quad_2", PROPERTY_HINT_ENUM, "Disabled,1 Shadow,4 Shadows,16 Shadows,64 Shadows,256 Shadows,1024 Shadows"), "set_shadow_atlas_quadrant_subdiv", "get_shadow_atlas_quadrant_subdiv", 2); - ADD_PROPERTYI(PropertyInfo(Variant::INT, "shadow_atlas_quad_3", PROPERTY_HINT_ENUM, "Disabled,1 Shadow,4 Shadows,16 Shadows,64 Shadows,256 Shadows,1024 Shadows"), "set_shadow_atlas_quadrant_subdiv", "get_shadow_atlas_quadrant_subdiv", 3); + ADD_GROUP("Positional Shadow Atlas", "positional_shadow_atlas_"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "positional_shadow_atlas_size"), "set_positional_shadow_atlas_size", "get_positional_shadow_atlas_size"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "positional_shadow_atlas_16_bits"), "set_positional_shadow_atlas_16_bits", "get_positional_shadow_atlas_16_bits"); + ADD_PROPERTYI(PropertyInfo(Variant::INT, "positional_shadow_atlas_quad_0", PROPERTY_HINT_ENUM, "Disabled,1 Shadow,4 Shadows,16 Shadows,64 Shadows,256 Shadows,1024 Shadows"), "set_positional_shadow_atlas_quadrant_subdiv", "get_positional_shadow_atlas_quadrant_subdiv", 0); + ADD_PROPERTYI(PropertyInfo(Variant::INT, "positional_shadow_atlas_quad_1", PROPERTY_HINT_ENUM, "Disabled,1 Shadow,4 Shadows,16 Shadows,64 Shadows,256 Shadows,1024 Shadows"), "set_positional_shadow_atlas_quadrant_subdiv", "get_positional_shadow_atlas_quadrant_subdiv", 1); + ADD_PROPERTYI(PropertyInfo(Variant::INT, "positional_shadow_atlas_quad_2", PROPERTY_HINT_ENUM, "Disabled,1 Shadow,4 Shadows,16 Shadows,64 Shadows,256 Shadows,1024 Shadows"), "set_positional_shadow_atlas_quadrant_subdiv", "get_positional_shadow_atlas_quadrant_subdiv", 2); + ADD_PROPERTYI(PropertyInfo(Variant::INT, "positional_shadow_atlas_quad_3", PROPERTY_HINT_ENUM, "Disabled,1 Shadow,4 Shadows,16 Shadows,64 Shadows,256 Shadows,1024 Shadows"), "set_positional_shadow_atlas_quadrant_subdiv", "get_positional_shadow_atlas_quadrant_subdiv", 3); ADD_PROPERTY(PropertyInfo(Variant::TRANSFORM2D, "canvas_transform", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_canvas_transform", "get_canvas_transform"); ADD_PROPERTY(PropertyInfo(Variant::TRANSFORM2D, "global_canvas_transform", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_global_canvas_transform", "get_global_canvas_transform"); @@ -3835,6 +3897,7 @@ void Viewport::_bind_methods() { BIND_ENUM_CONSTANT(DEBUG_DRAW_CLUSTER_DECALS); BIND_ENUM_CONSTANT(DEBUG_DRAW_CLUSTER_REFLECTION_PROBES); BIND_ENUM_CONSTANT(DEBUG_DRAW_OCCLUDERS) + BIND_ENUM_CONSTANT(DEBUG_DRAW_MOTION_VECTORS) BIND_ENUM_CONSTANT(DEFAULT_CANVAS_ITEM_TEXTURE_FILTER_NEAREST); BIND_ENUM_CONSTANT(DEFAULT_CANVAS_ITEM_TEXTURE_FILTER_LINEAR); @@ -3857,6 +3920,17 @@ void Viewport::_bind_methods() { BIND_ENUM_CONSTANT(SDF_SCALE_50_PERCENT); BIND_ENUM_CONSTANT(SDF_SCALE_25_PERCENT); BIND_ENUM_CONSTANT(SDF_SCALE_MAX); + + BIND_ENUM_CONSTANT(VRS_DISABLED); + BIND_ENUM_CONSTANT(VRS_TEXTURE); + BIND_ENUM_CONSTANT(VRS_XR); + BIND_ENUM_CONSTANT(VRS_MAX); +} + +void Viewport::_validate_property(PropertyInfo &property) const { + if (vrs_mode != VRS_TEXTURE && (property.name == "vrs_texture")) { + property.usage = PROPERTY_USAGE_NO_EDITOR; + } } Viewport::Viewport() { @@ -3872,15 +3946,15 @@ Viewport::Viewport() { canvas_layers.insert(nullptr); // This eases picking code (interpreted as the canvas of the Viewport). - set_shadow_atlas_size(shadow_atlas_size); + set_positional_shadow_atlas_size(positional_shadow_atlas_size); for (int i = 0; i < 4; i++) { - shadow_atlas_quadrant_subdiv[i] = SHADOW_ATLAS_QUADRANT_SUBDIV_MAX; + positional_shadow_atlas_quadrant_subdiv[i] = SHADOW_ATLAS_QUADRANT_SUBDIV_MAX; } - set_shadow_atlas_quadrant_subdiv(0, SHADOW_ATLAS_QUADRANT_SUBDIV_4); - set_shadow_atlas_quadrant_subdiv(1, SHADOW_ATLAS_QUADRANT_SUBDIV_4); - set_shadow_atlas_quadrant_subdiv(2, SHADOW_ATLAS_QUADRANT_SUBDIV_16); - set_shadow_atlas_quadrant_subdiv(3, SHADOW_ATLAS_QUADRANT_SUBDIV_64); + set_positional_shadow_atlas_quadrant_subdiv(0, SHADOW_ATLAS_QUADRANT_SUBDIV_4); + set_positional_shadow_atlas_quadrant_subdiv(1, SHADOW_ATLAS_QUADRANT_SUBDIV_4); + set_positional_shadow_atlas_quadrant_subdiv(2, SHADOW_ATLAS_QUADRANT_SUBDIV_16); + set_positional_shadow_atlas_quadrant_subdiv(3, SHADOW_ATLAS_QUADRANT_SUBDIV_64); set_mesh_lod_threshold(mesh_lod_threshold); @@ -3913,8 +3987,8 @@ Viewport::Viewport() { Viewport::~Viewport() { // Erase itself from viewport textures. - for (Set<ViewportTexture *>::Element *E = viewport_textures.front(); E; E = E->next()) { - E->get()->vp = nullptr; + for (ViewportTexture *E : viewport_textures) { + E->vp = nullptr; } RenderingServer::get_singleton()->free(viewport); } @@ -4030,8 +4104,8 @@ void SubViewport::_bind_methods() { ClassDB::bind_method(D_METHOD("set_clear_mode", "mode"), &SubViewport::set_clear_mode); ClassDB::bind_method(D_METHOD("get_clear_mode"), &SubViewport::get_clear_mode); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR2I, "size"), "set_size", "get_size"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR2I, "size_2d_override"), "set_size_2d_override", "get_size_2d_override"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2I, "size", PROPERTY_HINT_NONE, "suffix:px"), "set_size", "get_size"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2I, "size_2d_override", PROPERTY_HINT_NONE, "suffix:px"), "set_size_2d_override", "get_size_2d_override"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "size_2d_override_stretch"), "set_size_2d_override_stretch", "is_size_2d_override_stretch_enabled"); ADD_GROUP("Render Target", "render_target_"); ADD_PROPERTY(PropertyInfo(Variant::INT, "render_target_clear_mode", PROPERTY_HINT_ENUM, "Always,Never,Next Frame"), "set_clear_mode", "get_clear_mode"); diff --git a/scene/main/viewport.h b/scene/main/viewport.h index 32882fbb68..a43e3f3ee2 100644 --- a/scene/main/viewport.h +++ b/scene/main/viewport.h @@ -95,7 +95,7 @@ public: SCALING_3D_MODE_MAX }; - enum ShadowAtlasQuadrantSubdiv { + enum PositionalShadowAtlasQuadrantSubdiv { SHADOW_ATLAS_QUADRANT_SUBDIV_DISABLED, SHADOW_ATLAS_QUADRANT_SUBDIV_1, SHADOW_ATLAS_QUADRANT_SUBDIV_4, @@ -160,6 +160,7 @@ public: DEBUG_DRAW_CLUSTER_DECALS, DEBUG_DRAW_CLUSTER_REFLECTION_PROBES, DEBUG_DRAW_OCCLUDERS, + DEBUG_DRAW_MOTION_VECTORS, }; enum DefaultCanvasItemTextureFilter { @@ -196,6 +197,13 @@ public: SUBWINDOW_CANVAS_LAYER = 1024 }; + enum VRSMode { + VRS_DISABLED, + VRS_TEXTURE, + VRS_XR, + VRS_MAX + }; + private: friend class ViewportTexture; @@ -203,7 +211,7 @@ private: AudioListener2D *audio_listener_2d = nullptr; Camera2D *camera_2d = nullptr; - Set<CanvasLayer *> canvas_layers; + HashSet<CanvasLayer *> canvas_layers; RID viewport; RID current_canvas; @@ -230,7 +238,6 @@ private: Rect2 last_vp_rect; bool transparent_bg = false; - bool filter; bool gen_mipmaps = false; bool snap_controls_to_pixels = true; @@ -259,9 +266,9 @@ private: bool local_input_handled = false; // Collider to frame - Map<ObjectID, uint64_t> physics_2d_mouseover; + HashMap<ObjectID, uint64_t> physics_2d_mouseover; // Collider & shape to frame - Map<Pair<ObjectID, int>, uint64_t, PairSort<ObjectID, int>> physics_2d_shape_mouseover; + HashMap<Pair<ObjectID, int>, uint64_t, PairHash<ObjectID, int>> physics_2d_shape_mouseover; // Cleans up colliders corresponding to old frames or all of them. void _cleanup_mouseover_colliders(bool p_clean_all_frames, bool p_paused_only, uint64_t p_frame_reference = 0); @@ -286,12 +293,13 @@ private: DebugDraw debug_draw = DEBUG_DRAW_DISABLED; - int shadow_atlas_size = 2048; - bool shadow_atlas_16_bits = true; - ShadowAtlasQuadrantSubdiv shadow_atlas_quadrant_subdiv[4]; + int positional_shadow_atlas_size = 2048; + bool positional_shadow_atlas_16_bits = true; + PositionalShadowAtlasQuadrantSubdiv positional_shadow_atlas_quadrant_subdiv[4]; MSAA msaa = MSAA_DISABLED; ScreenSpaceAA screen_space_aa = SCREEN_SPACE_AA_DISABLED; + bool use_taa = false; Scaling3DMode scaling_3d_mode = SCALING_3D_MODE_BILINEAR; float scaling_3d_scale = 1.0; @@ -302,7 +310,7 @@ private: bool use_occlusion_culling = false; Ref<ViewportTexture> default_texture; - Set<ViewportTexture *> viewport_textures; + HashSet<ViewportTexture *> viewport_textures; SDFOversize sdf_oversize = SDF_OVERSIZE_120_PERCENT; SDFScale sdf_scale = SDF_SCALE_50_PERCENT; @@ -332,6 +340,10 @@ private: RID canvas_item; }; + // VRS + VRSMode vrs_mode = VRS_DISABLED; + Ref<Texture2D> vrs_texture; + struct GUI { // info used when this is a window @@ -382,7 +394,7 @@ private: bool disable_input = false; - void _gui_call_input(Control *p_control, const Ref<InputEvent> &p_input); + bool _gui_call_input(Control *p_control, const Ref<InputEvent> &p_input); void _gui_call_notification(Control *p_control, int p_what); void _gui_sort_roots(); @@ -501,14 +513,14 @@ public: Ref<ViewportTexture> get_texture() const; - void set_shadow_atlas_size(int p_size); - int get_shadow_atlas_size() const; + void set_positional_shadow_atlas_size(int p_size); + int get_positional_shadow_atlas_size() const; - void set_shadow_atlas_16_bits(bool p_16_bits); - bool get_shadow_atlas_16_bits() const; + void set_positional_shadow_atlas_16_bits(bool p_16_bits); + bool get_positional_shadow_atlas_16_bits() const; - void set_shadow_atlas_quadrant_subdiv(int p_quadrant, ShadowAtlasQuadrantSubdiv p_subdiv); - ShadowAtlasQuadrantSubdiv get_shadow_atlas_quadrant_subdiv(int p_quadrant) const; + void set_positional_shadow_atlas_quadrant_subdiv(int p_quadrant, PositionalShadowAtlasQuadrantSubdiv p_subdiv); + PositionalShadowAtlasQuadrantSubdiv get_positional_shadow_atlas_quadrant_subdiv(int p_quadrant) const; void set_msaa(MSAA p_msaa); MSAA get_msaa() const; @@ -516,6 +528,9 @@ public: void set_screen_space_aa(ScreenSpaceAA p_screen_space_aa); ScreenSpaceAA get_screen_space_aa() const; + void set_use_taa(bool p_use_taa); + bool is_using_taa() const; + void set_scaling_3d_mode(Scaling3DMode p_scaling_3d_mode); Scaling3DMode get_scaling_3d_mode() const; @@ -600,6 +615,14 @@ public: void set_default_canvas_item_texture_repeat(DefaultCanvasItemTextureRepeat p_repeat); DefaultCanvasItemTextureRepeat get_default_canvas_item_texture_repeat() const; + // VRS + + void set_vrs_mode(VRSMode p_vrs_mode); + VRSMode get_vrs_mode() const; + + void set_vrs_texture(Ref<Texture2D> p_texture); + Ref<Texture2D> get_vrs_texture() const; + virtual DisplayServer::WindowID get_window_id() const = 0; void set_embedding_subwindows(bool p_embed); @@ -616,7 +639,7 @@ public: bool use_xr = false; friend class AudioListener3D; AudioListener3D *audio_listener_3d = nullptr; - Set<AudioListener3D *> audio_listener_3d_set; + HashSet<AudioListener3D *> audio_listener_3d_set; bool is_audio_listener_3d_enabled = false; RID internal_audio_listener_3d; AudioListener3D *get_audio_listener_3d() const; @@ -651,7 +674,7 @@ public: friend class Camera3D; Camera3D *camera_3d = nullptr; - Set<Camera3D *> camera_3d_set; + HashSet<Camera3D *> camera_3d_set; Camera3D *get_camera_3d() const; void _camera_3d_transform_changed_notify(); void _camera_3d_set(Camera3D *p_camera); @@ -677,7 +700,7 @@ public: Ref<World3D> get_world_3d() const; Ref<World3D> find_world_3d() const; void _own_world_3d_changed(); - void set_use_own_world_3d(bool p_world_3d); + void set_use_own_world_3d(bool p_use_own_world_3d); bool is_using_own_world_3d() const; void _propagate_enter_world_3d(Node *p_node); void _propagate_exit_world_3d(Node *p_node); @@ -686,6 +709,7 @@ public: bool is_using_xr(); #endif // _3D_DISABLED + virtual void _validate_property(PropertyInfo &property) const override; Viewport(); ~Viewport(); }; @@ -742,12 +766,13 @@ public: }; VARIANT_ENUM_CAST(Viewport::Scaling3DMode); VARIANT_ENUM_CAST(SubViewport::UpdateMode); -VARIANT_ENUM_CAST(Viewport::ShadowAtlasQuadrantSubdiv); +VARIANT_ENUM_CAST(Viewport::PositionalShadowAtlasQuadrantSubdiv); VARIANT_ENUM_CAST(Viewport::MSAA); VARIANT_ENUM_CAST(Viewport::ScreenSpaceAA); VARIANT_ENUM_CAST(Viewport::DebugDraw); VARIANT_ENUM_CAST(Viewport::SDFScale); VARIANT_ENUM_CAST(Viewport::SDFOversize); +VARIANT_ENUM_CAST(Viewport::VRSMode); VARIANT_ENUM_CAST(SubViewport::ClearMode); VARIANT_ENUM_CAST(Viewport::RenderInfo); VARIANT_ENUM_CAST(Viewport::RenderInfoType); diff --git a/scene/main/window.cpp b/scene/main/window.cpp index 8b1a4680d2..73e8f537d9 100644 --- a/scene/main/window.cpp +++ b/scene/main/window.cpp @@ -111,31 +111,19 @@ Size2i Window::get_real_size() const { void Window::set_max_size(const Size2i &p_max_size) { max_size = p_max_size; - if (window_id != DisplayServer::INVALID_WINDOW_ID) { - DisplayServer::get_singleton()->window_set_max_size(max_size, window_id); - } _update_window_size(); } Size2i Window::get_max_size() const { - if (window_id != DisplayServer::INVALID_WINDOW_ID) { - max_size = DisplayServer::get_singleton()->window_get_max_size(window_id); - } return max_size; } void Window::set_min_size(const Size2i &p_min_size) { min_size = p_min_size; - if (!wrap_controls && window_id != DisplayServer::INVALID_WINDOW_ID) { - DisplayServer::get_singleton()->window_set_min_size(min_size, window_id); - } _update_window_size(); } Size2i Window::get_min_size() const { - if (window_id != DisplayServer::INVALID_WINDOW_ID) { - min_size = DisplayServer::get_singleton()->window_get_min_size(window_id); - } return min_size; } @@ -165,14 +153,26 @@ void Window::set_flag(Flags p_flag, bool p_enabled) { embedder->_sub_window_update(this); } else if (window_id != DisplayServer::INVALID_WINDOW_ID) { +#ifdef TOOLS_ENABLED + if ((p_flag != FLAG_POPUP) || !(Engine::get_singleton()->is_editor_hint() && get_tree()->get_edited_scene_root() && (get_tree()->get_edited_scene_root()->is_ancestor_of(this) || get_tree()->get_edited_scene_root() == this))) { + DisplayServer::get_singleton()->window_set_flag(DisplayServer::WindowFlags(p_flag), p_enabled, window_id); + } +#else DisplayServer::get_singleton()->window_set_flag(DisplayServer::WindowFlags(p_flag), p_enabled, window_id); +#endif } } bool Window::get_flag(Flags p_flag) const { ERR_FAIL_INDEX_V(p_flag, FLAG_MAX, false); if (window_id != DisplayServer::INVALID_WINDOW_ID) { +#ifdef TOOLS_ENABLED + if ((p_flag != FLAG_POPUP) || !(Engine::get_singleton()->is_editor_hint() && get_tree()->get_edited_scene_root() && (get_tree()->get_edited_scene_root()->is_ancestor_of(this) || get_tree()->get_edited_scene_root() == this))) { + flags[p_flag] = DisplayServer::get_singleton()->window_get_flag(DisplayServer::WindowFlags(p_flag), window_id); + } +#else flags[p_flag] = DisplayServer::get_singleton()->window_get_flag(DisplayServer::WindowFlags(p_flag), window_id); +#endif } return flags[p_flag]; } @@ -255,7 +255,15 @@ void Window::_make_window() { #endif DisplayServer::get_singleton()->window_set_title(tr_title, window_id); DisplayServer::get_singleton()->window_attach_instance_id(get_instance_id(), window_id); +#ifdef TOOLS_ENABLED + if (!(Engine::get_singleton()->is_editor_hint() && get_tree()->get_edited_scene_root() && (get_tree()->get_edited_scene_root()->is_ancestor_of(this) || get_tree()->get_edited_scene_root() == this))) { + DisplayServer::get_singleton()->window_set_exclusive(window_id, exclusive); + } else { + DisplayServer::get_singleton()->window_set_exclusive(window_id, false); + } +#else DisplayServer::get_singleton()->window_set_exclusive(window_id, exclusive); +#endif _update_window_size(); @@ -263,9 +271,9 @@ void Window::_make_window() { DisplayServer::get_singleton()->window_set_transient(window_id, transient_parent->window_id); } - for (Set<Window *>::Element *E = transient_children.front(); E; E = E->next()) { - if (E->get()->window_id != DisplayServer::INVALID_WINDOW_ID) { - DisplayServer::get_singleton()->window_set_transient(E->get()->window_id, transient_parent->window_id); + for (const Window *E : transient_children) { + if (E->window_id != DisplayServer::INVALID_WINDOW_ID) { + DisplayServer::get_singleton()->window_set_transient(E->window_id, transient_parent->window_id); } } @@ -290,9 +298,9 @@ void Window::_clear_window() { DisplayServer::get_singleton()->window_set_transient(window_id, DisplayServer::INVALID_WINDOW_ID); } - for (Set<Window *>::Element *E = transient_children.front(); E; E = E->next()) { - if (E->get()->window_id != DisplayServer::INVALID_WINDOW_ID) { - DisplayServer::get_singleton()->window_set_transient(E->get()->window_id, DisplayServer::INVALID_WINDOW_ID); + for (const Window *E : transient_children) { + if (E->window_id != DisplayServer::INVALID_WINDOW_ID) { + DisplayServer::get_singleton()->window_set_transient(E->window_id, DisplayServer::INVALID_WINDOW_ID); } } @@ -437,10 +445,12 @@ void Window::set_visible(bool p_visible) { if (transient_parent) { if (exclusive && visible) { #ifdef TOOLS_ENABLED - if (!(Engine::get_singleton()->is_editor_hint() && get_tree()->get_edited_scene_root() && get_tree()->get_edited_scene_root()->is_ancestor_of(this))) { + if (!(Engine::get_singleton()->is_editor_hint() && get_tree()->get_edited_scene_root() && (get_tree()->get_edited_scene_root()->is_ancestor_of(this) || get_tree()->get_edited_scene_root() == this))) { ERR_FAIL_COND_MSG(transient_parent->exclusive_child && transient_parent->exclusive_child != this, "Transient parent has another exclusive child."); transient_parent->exclusive_child = this; } +#else + transient_parent->exclusive_child = this; #endif } else { if (transient_parent->exclusive_child == this) { @@ -488,7 +498,13 @@ void Window::_make_transient() { window->transient_children.insert(this); if (is_inside_tree() && is_visible() && exclusive) { if (transient_parent->exclusive_child == nullptr) { +#ifdef TOOLS_ENABLED + if (!(Engine::get_singleton()->is_editor_hint() && get_tree()->get_edited_scene_root() && (get_tree()->get_edited_scene_root()->is_ancestor_of(this) || get_tree()->get_edited_scene_root() == this))) { + transient_parent->exclusive_child = this; + } +#else transient_parent->exclusive_child = this; +#endif } else if (transient_parent->exclusive_child != this) { ERR_PRINT("Making child transient exclusive, but parent has another exclusive child"); } @@ -531,13 +547,27 @@ void Window::set_exclusive(bool p_exclusive) { exclusive = p_exclusive; if (!embedder && window_id != DisplayServer::INVALID_WINDOW_ID) { +#ifdef TOOLS_ENABLED + if (!(Engine::get_singleton()->is_editor_hint() && get_tree()->get_edited_scene_root() && (get_tree()->get_edited_scene_root()->is_ancestor_of(this) || get_tree()->get_edited_scene_root() == this))) { + DisplayServer::get_singleton()->window_set_exclusive(window_id, exclusive); + } else { + DisplayServer::get_singleton()->window_set_exclusive(window_id, false); + } +#else DisplayServer::get_singleton()->window_set_exclusive(window_id, exclusive); +#endif } if (transient_parent) { if (p_exclusive && is_inside_tree() && is_visible()) { ERR_FAIL_COND_MSG(transient_parent->exclusive_child && transient_parent->exclusive_child != this, "Transient parent has another exclusive child."); +#ifdef TOOLS_ENABLED + if (!(Engine::get_singleton()->is_editor_hint() && get_tree()->get_edited_scene_root() && (get_tree()->get_edited_scene_root()->is_ancestor_of(this) || get_tree()->get_edited_scene_root() == this))) { + transient_parent->exclusive_child = this; + } +#else transient_parent->exclusive_child = this; +#endif } else { if (transient_parent->exclusive_child == this) { transient_parent->exclusive_child = nullptr; @@ -579,6 +609,7 @@ void Window::_update_window_size() { } else if (window_id != DisplayServer::INVALID_WINDOW_ID) { DisplayServer::get_singleton()->window_set_size(size, window_id); DisplayServer::get_singleton()->window_set_min_size(size_limit, window_id); + DisplayServer::get_singleton()->window_set_max_size(max_size, window_id); } //update the viewport @@ -681,7 +712,7 @@ void Window::_update_viewport_size() { } break; case CONTENT_SCALE_MODE_VIEWPORT: { - final_size = viewport_size; + final_size = (viewport_size / content_scale_factor).floor(); attach_to_screen_rect = Rect2(margin, screen_size); } break; @@ -791,6 +822,22 @@ void Window::_notification(int p_what) { emit_signal(SceneStringNames::get_singleton()->visibility_changed); RS::get_singleton()->viewport_set_active(get_viewport_rid(), true); } + + if (theme.is_null()) { + Control *parent_c = cast_to<Control>(get_parent()); + if (parent_c && (parent_c->data.theme_owner || parent_c->data.theme_owner_window)) { + theme_owner = parent_c->data.theme_owner; + theme_owner_window = parent_c->data.theme_owner_window; + notification(NOTIFICATION_THEME_CHANGED); + } else { + Window *parent_w = cast_to<Window>(get_parent()); + if (parent_w && (parent_w->theme_owner || parent_w->theme_owner_window)) { + theme_owner = parent_w->theme_owner; + theme_owner_window = parent_w->theme_owner_window; + notification(NOTIFICATION_THEME_CHANGED); + } + } + } } break; case NOTIFICATION_READY: { @@ -1045,7 +1092,9 @@ void Window::popup_centered_clamped(const Size2i &p_size, float p_fallback_ratio Rect2i popup_rect; popup_rect.size = Vector2i(MIN(size_ratio.x, p_size.x), MIN(size_ratio.y, p_size.y)); - popup_rect.position = parent_rect.position + (parent_rect.size - popup_rect.size) / 2; + if (parent_rect != Rect2()) { + popup_rect.position = parent_rect.position + (parent_rect.size - popup_rect.size) / 2; + } popup(popup_rect); } @@ -1069,7 +1118,10 @@ void Window::popup_centered(const Size2i &p_minsize) { Size2 contents_minsize = _get_contents_minimum_size(); popup_rect.size.x = MAX(p_minsize.x, contents_minsize.x); popup_rect.size.y = MAX(p_minsize.y, contents_minsize.y); - popup_rect.position = parent_rect.position + (parent_rect.size - popup_rect.size) / 2; + + if (parent_rect != Rect2()) { + popup_rect.position = parent_rect.position + (parent_rect.size - popup_rect.size) / 2; + } popup(popup_rect); } @@ -1091,8 +1143,10 @@ void Window::popup_centered_ratio(float p_ratio) { } Rect2i popup_rect; - popup_rect.size = parent_rect.size * p_ratio; - popup_rect.position = parent_rect.position + (parent_rect.size - popup_rect.size) / 2; + if (parent_rect != Rect2()) { + popup_rect.size = parent_rect.size * p_ratio; + popup_rect.position = parent_rect.position + (parent_rect.size - popup_rect.size) / 2; + } popup(popup_rect); } @@ -1104,7 +1158,7 @@ void Window::popup(const Rect2i &p_screen_rect) { // Send a focus-out notification when opening a Window Manager Popup. SceneTree *scene_tree = get_tree(); if (scene_tree) { - scene_tree->notify_group("_viewports", NOTIFICATION_WM_WINDOW_FOCUS_OUT); + scene_tree->notify_group_flags(SceneTree::GROUP_CALL_DEFERRED, "_viewports", NOTIFICATION_WM_WINDOW_FOCUS_OUT); } } @@ -1584,8 +1638,8 @@ void Window::_bind_methods() { ClassDB::bind_method(D_METHOD("popup_centered_clamped", "minsize", "fallback_ratio"), &Window::popup_centered_clamped, DEFVAL(Size2i()), DEFVAL(0.75)); ADD_PROPERTY(PropertyInfo(Variant::STRING, "title"), "set_title", "get_title"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR2I, "position"), "set_position", "get_position"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR2I, "size"), "set_size", "get_size"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2I, "position", PROPERTY_HINT_NONE, "suffix:px"), "set_position", "get_position"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2I, "size", PROPERTY_HINT_NONE, "suffix:px"), "set_size", "get_size"); ADD_PROPERTY(PropertyInfo(Variant::INT, "mode", PROPERTY_HINT_ENUM, "Windowed,Minimized,Maximized,Fullscreen"), "set_mode", "get_mode"); ADD_PROPERTY(PropertyInfo(Variant::INT, "current_screen"), "set_current_screen", "get_current_screen"); @@ -1602,8 +1656,8 @@ void Window::_bind_methods() { ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "popup_window"), "set_flag", "get_flag", FLAG_POPUP); ADD_GROUP("Limits", ""); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR2I, "min_size"), "set_min_size", "get_min_size"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR2I, "max_size"), "set_max_size", "get_max_size"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2I, "min_size", PROPERTY_HINT_NONE, "suffix:px"), "set_min_size", "get_min_size"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2I, "max_size", PROPERTY_HINT_NONE, "suffix:px"), "set_max_size", "get_max_size"); ADD_GROUP("Content Scale", "content_scale_"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2I, "content_scale_size"), "set_content_scale_size", "get_content_scale_size"); diff --git a/scene/main/window.h b/scene/main/window.h index f674f6425a..c060f1d79d 100644 --- a/scene/main/window.h +++ b/scene/main/window.h @@ -131,7 +131,7 @@ private: void _make_transient(); Window *transient_parent = nullptr; Window *exclusive_child = nullptr; - Set<Window *> transient_children; + HashSet<Window *> transient_children; friend class Control; Ref<Theme> theme; diff --git a/scene/multiplayer/multiplayer_spawner.cpp b/scene/multiplayer/multiplayer_spawner.cpp index 25ab27f3e7..8363d05e54 100644 --- a/scene/multiplayer/multiplayer_spawner.cpp +++ b/scene/multiplayer/multiplayer_spawner.cpp @@ -35,12 +35,106 @@ #include "scene/main/window.h" #include "scene/scene_string_names.h" +#ifdef TOOLS_ENABLED +/* This is editor only */ +bool MultiplayerSpawner::_set(const StringName &p_name, const Variant &p_value) { + if (p_name == "_spawnable_scene_count") { + spawnable_scenes.resize(p_value); + notify_property_list_changed(); + return true; + } else { + String ns = p_name; + if (ns.begins_with("scenes/")) { + uint32_t index = ns.get_slicec('/', 1).to_int(); + ERR_FAIL_UNSIGNED_INDEX_V(index, spawnable_scenes.size(), false); + spawnable_scenes[index].path = p_value; + return true; + } + } + return false; +} + +bool MultiplayerSpawner::_get(const StringName &p_name, Variant &r_ret) const { + if (p_name == "_spawnable_scene_count") { + r_ret = spawnable_scenes.size(); + return true; + } else { + String ns = p_name; + if (ns.begins_with("scenes/")) { + uint32_t index = ns.get_slicec('/', 1).to_int(); + ERR_FAIL_UNSIGNED_INDEX_V(index, spawnable_scenes.size(), false); + r_ret = spawnable_scenes[index].path; + return true; + } + } + return false; +} + +void MultiplayerSpawner::_get_property_list(List<PropertyInfo> *p_list) const { + p_list->push_back(PropertyInfo(Variant::INT, "_spawnable_scene_count", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_ARRAY, "Auto Spawn List,scenes/")); + List<String> exts; + ResourceLoader::get_recognized_extensions_for_type("PackedScene", &exts); + String ext_hint; + for (const String &E : exts) { + if (!ext_hint.is_empty()) { + ext_hint += ","; + } + ext_hint += "*." + E; + } + for (uint32_t i = 0; i < spawnable_scenes.size(); i++) { + p_list->push_back(PropertyInfo(Variant::STRING, "scenes/" + itos(i), PROPERTY_HINT_FILE, ext_hint, PROPERTY_USAGE_EDITOR)); + } +} +#endif +void MultiplayerSpawner::add_spawnable_scene(const String &p_path) { + SpawnableScene sc; + sc.path = p_path; + if (Engine::get_singleton()->is_editor_hint()) { + ERR_FAIL_COND(!FileAccess::exists(p_path)); + } else { + sc.cache = ResourceLoader::load(p_path); + ERR_FAIL_COND_MSG(sc.cache.is_null(), "Invalid spawnable scene: " + p_path); + } + spawnable_scenes.push_back(sc); +} +int MultiplayerSpawner::get_spawnable_scene_count() const { + return spawnable_scenes.size(); +} +String MultiplayerSpawner::get_spawnable_scene(int p_idx) const { + return spawnable_scenes[p_idx].path; +} +void MultiplayerSpawner::clear_spawnable_scenes() { + spawnable_scenes.clear(); +} + +Vector<String> MultiplayerSpawner::_get_spawnable_scenes() const { + Vector<String> ss; + ss.resize(spawnable_scenes.size()); + for (int i = 0; i < ss.size(); i++) { + ss.write[i] = spawnable_scenes[i].path; + } + return ss; +} + +void MultiplayerSpawner::_set_spawnable_scenes(const Vector<String> &p_scenes) { + clear_spawnable_scenes(); + for (int i = 0; i < p_scenes.size(); i++) { + add_spawnable_scene(p_scenes[i]); + } +} + void MultiplayerSpawner::_bind_methods() { - ClassDB::bind_method(D_METHOD("spawn", "data"), &MultiplayerSpawner::spawn, DEFVAL(Variant())); + ClassDB::bind_method(D_METHOD("add_spawnable_scene", "path"), &MultiplayerSpawner::add_spawnable_scene); + ClassDB::bind_method(D_METHOD("get_spawnable_scene_count"), &MultiplayerSpawner::get_spawnable_scene_count); + ClassDB::bind_method(D_METHOD("get_spawnable_scene", "path"), &MultiplayerSpawner::get_spawnable_scene); + ClassDB::bind_method(D_METHOD("clear_spawnable_scenes"), &MultiplayerSpawner::clear_spawnable_scenes); + + ClassDB::bind_method(D_METHOD("_get_spawnable_scenes"), &MultiplayerSpawner::_get_spawnable_scenes); + ClassDB::bind_method(D_METHOD("_set_spawnable_scenes", "scenes"), &MultiplayerSpawner::_set_spawnable_scenes); - ClassDB::bind_method(D_METHOD("get_spawnable_scenes"), &MultiplayerSpawner::get_spawnable_scenes); - ClassDB::bind_method(D_METHOD("set_spawnable_scenes", "scenes"), &MultiplayerSpawner::set_spawnable_scenes); - ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "replication", PROPERTY_HINT_ARRAY_TYPE, vformat("%s/%s:%s", Variant::OBJECT, PROPERTY_HINT_RESOURCE_TYPE, "PackedScene"), (PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE)), "set_spawnable_scenes", "get_spawnable_scenes"); + ADD_PROPERTY(PropertyInfo(Variant::PACKED_STRING_ARRAY, "_spawnable_scenes", PROPERTY_HINT_NONE, "", (PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_INTERNAL)), "_set_spawnable_scenes", "_get_spawnable_scenes"); + + ClassDB::bind_method(D_METHOD("spawn", "data"), &MultiplayerSpawner::spawn, DEFVAL(Variant())); ClassDB::bind_method(D_METHOD("get_spawn_path"), &MultiplayerSpawner::get_spawn_path); ClassDB::bind_method(D_METHOD("set_spawn_path", "path"), &MultiplayerSpawner::set_spawn_path); @@ -50,10 +144,6 @@ void MultiplayerSpawner::_bind_methods() { ClassDB::bind_method(D_METHOD("set_spawn_limit", "limit"), &MultiplayerSpawner::set_spawn_limit); ADD_PROPERTY(PropertyInfo(Variant::INT, "spawn_limit", PROPERTY_HINT_RANGE, "0,1024,1,or_greater"), "set_spawn_limit", "get_spawn_limit"); - ClassDB::bind_method(D_METHOD("set_auto_spawning", "enabled"), &MultiplayerSpawner::set_auto_spawning); - ClassDB::bind_method(D_METHOD("is_auto_spawning"), &MultiplayerSpawner::is_auto_spawning); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "auto_spawn"), "set_auto_spawning", "is_auto_spawning"); - GDVIRTUAL_BIND(_spawn_custom, "data"); ADD_SIGNAL(MethodInfo("despawned", PropertyInfo(Variant::INT, "scene_id"), PropertyInfo(Variant::OBJECT, "node", PROPERTY_HINT_RESOURCE_TYPE, "Node"))); @@ -75,7 +165,7 @@ void MultiplayerSpawner::_update_spawn_node() { Node *node = spawn_path.is_empty() && is_inside_tree() ? nullptr : get_node_or_null(spawn_path); if (node) { spawn_node = node->get_instance_id(); - if (auto_spawn) { + if (get_spawnable_scene_count() && !GDVIRTUAL_IS_OVERRIDDEN(_spawn_custom)) { node->connect("child_entered_tree", callable_mp(this, &MultiplayerSpawner::_node_added)); } } else { @@ -91,9 +181,9 @@ void MultiplayerSpawner::_notification(int p_what) { case NOTIFICATION_EXIT_TREE: { _update_spawn_node(); - const ObjectID *oid = nullptr; - while ((oid = tracked_nodes.next(oid))) { - Node *node = Object::cast_to<Node>(ObjectDB::get_instance(*oid)); + + for (const KeyValue<ObjectID, SpawnInfo> &E : tracked_nodes) { + Node *node = Object::cast_to<Node>(ObjectDB::get_instance(E.key)); ERR_CONTINUE(!node); node->disconnect(SceneStringNames::get_singleton()->tree_exiting, callable_mp(this, &MultiplayerSpawner::_node_exit)); // This is unlikely, but might still crash the engine. @@ -118,7 +208,7 @@ void MultiplayerSpawner::_node_added(Node *p_node) { if (!parent || p_node->get_parent() != parent) { return; } - int id = get_scene_id(p_node->get_scene_file_path()); + int id = find_spawnable_scene_index_from_path(p_node->get_scene_file_path()); if (id == INVALID_ID) { return; } @@ -127,23 +217,6 @@ void MultiplayerSpawner::_node_added(Node *p_node) { _track(p_node, Variant(), id); } -void MultiplayerSpawner::set_auto_spawning(bool p_enabled) { - auto_spawn = p_enabled; - _update_spawn_node(); -} - -bool MultiplayerSpawner::is_auto_spawning() const { - return auto_spawn; -} - -TypedArray<PackedScene> MultiplayerSpawner::get_spawnable_scenes() { - return spawnable_scenes; -} - -void MultiplayerSpawner::set_spawnable_scenes(TypedArray<PackedScene> p_scenes) { - spawnable_scenes = p_scenes; -} - NodePath MultiplayerSpawner::get_spawn_path() const { return spawn_path; } @@ -175,18 +248,16 @@ void MultiplayerSpawner::_node_exit(ObjectID p_id) { } } -int MultiplayerSpawner::get_scene_id(const String &p_scene) const { - for (int i = 0; i < spawnable_scenes.size(); i++) { - Ref<PackedScene> ps = spawnable_scenes[i]; - ERR_CONTINUE(ps.is_null()); - if (ps->get_path() == p_scene) { +int MultiplayerSpawner::find_spawnable_scene_index_from_path(const String &p_scene) const { + for (uint32_t i = 0; i < spawnable_scenes.size(); i++) { + if (spawnable_scenes[i].path == p_scene) { return i; } } return INVALID_ID; } -int MultiplayerSpawner::get_spawn_id(const ObjectID &p_id) const { +int MultiplayerSpawner::find_spawnable_scene_index_from_object(const ObjectID &p_id) const { const SpawnInfo *info = tracked_nodes.getptr(p_id); return info ? info->id : INVALID_ID; } @@ -198,8 +269,8 @@ const Variant MultiplayerSpawner::get_spawn_argument(const ObjectID &p_id) const Node *MultiplayerSpawner::instantiate_scene(int p_id) { ERR_FAIL_COND_V_MSG(spawn_limit && spawn_limit <= tracked_nodes.size(), nullptr, "Spawn limit reached!"); - ERR_FAIL_INDEX_V(p_id, spawnable_scenes.size(), nullptr); - Ref<PackedScene> scene = spawnable_scenes[p_id]; + ERR_FAIL_UNSIGNED_INDEX_V((uint32_t)p_id, spawnable_scenes.size(), nullptr); + Ref<PackedScene> scene = spawnable_scenes[p_id].cache; ERR_FAIL_COND_V(scene.is_null(), nullptr); return scene->instantiate(); } diff --git a/scene/multiplayer/multiplayer_spawner.h b/scene/multiplayer/multiplayer_spawner.h index 63948e39a5..2c0eb9a2f0 100644 --- a/scene/multiplayer/multiplayer_spawner.h +++ b/scene/multiplayer/multiplayer_spawner.h @@ -33,6 +33,7 @@ #include "scene/main/node.h" +#include "core/templates/local_vector.h" #include "core/variant/typed_array.h" #include "scene/resources/packed_scene.h" #include "scene/resources/scene_replication_config.h" @@ -46,8 +47,14 @@ public: }; private: - TypedArray<PackedScene> spawnable_scenes; - Set<ResourceUID::ID> spawnable_ids; + struct SpawnableScene { + String path; + Ref<PackedScene> cache; + }; + + LocalVector<SpawnableScene> spawnable_scenes; + + HashSet<ResourceUID::ID> spawnable_ids; NodePath spawn_path; struct SpawnInfo { @@ -62,7 +69,6 @@ private: ObjectID spawn_node; HashMap<ObjectID, SpawnInfo> tracked_nodes; - bool auto_spawn = false; uint32_t spawn_limit = 0; void _update_spawn_node(); @@ -71,24 +77,34 @@ private: void _node_exit(ObjectID p_id); void _node_ready(ObjectID p_id); + Vector<String> _get_spawnable_scenes() const; + void _set_spawnable_scenes(const Vector<String> &p_scenes); + protected: static void _bind_methods(); void _notification(int p_what); +#ifdef TOOLS_ENABLED + bool _set(const StringName &p_name, const Variant &p_value); + bool _get(const StringName &p_name, Variant &r_ret) const; + void _get_property_list(List<PropertyInfo> *p_list) const; +#endif public: Node *get_spawn_node() const { return spawn_node.is_valid() ? Object::cast_to<Node>(ObjectDB::get_instance(spawn_node)) : nullptr; } - TypedArray<PackedScene> get_spawnable_scenes(); - void set_spawnable_scenes(TypedArray<PackedScene> p_scenes); + + void add_spawnable_scene(const String &p_path); + int get_spawnable_scene_count() const; + String get_spawnable_scene(int p_idx) const; + void clear_spawnable_scenes(); + NodePath get_spawn_path() const; void set_spawn_path(const NodePath &p_path); uint32_t get_spawn_limit() const { return spawn_limit; } void set_spawn_limit(uint32_t p_limit) { spawn_limit = p_limit; } - bool is_auto_spawning() const; - void set_auto_spawning(bool p_enabled); const Variant get_spawn_argument(const ObjectID &p_id) const; - int get_spawn_id(const ObjectID &p_id) const; - int get_scene_id(const String &p_path) const; + int find_spawnable_scene_index_from_object(const ObjectID &p_id) const; + int find_spawnable_scene_index_from_path(const String &p_path) const; Node *spawn(const Variant &p_data = Variant()); Node *instantiate_custom(const Variant &p_data); Node *instantiate_scene(int p_idx); diff --git a/scene/multiplayer/multiplayer_synchronizer.cpp b/scene/multiplayer/multiplayer_synchronizer.cpp index 33e845a7a3..e1b7433968 100644 --- a/scene/multiplayer/multiplayer_synchronizer.cpp +++ b/scene/multiplayer/multiplayer_synchronizer.cpp @@ -43,6 +43,11 @@ Object *MultiplayerSynchronizer::_get_prop_target(Object *p_obj, const NodePath } void MultiplayerSynchronizer::_stop() { +#ifdef TOOLS_ENABLED + if (Engine::get_singleton()->is_editor_hint()) { + return; + } +#endif Node *node = is_inside_tree() ? get_node_or_null(root_path) : nullptr; if (node) { get_multiplayer()->replication_stop(node, this); @@ -50,9 +55,42 @@ void MultiplayerSynchronizer::_stop() { } void MultiplayerSynchronizer::_start() { +#ifdef TOOLS_ENABLED + if (Engine::get_singleton()->is_editor_hint()) { + return; + } +#endif Node *node = is_inside_tree() ? get_node_or_null(root_path) : nullptr; if (node) { get_multiplayer()->replication_start(node, this); + _update_process(); + } +} + +void MultiplayerSynchronizer::_update_process() { +#ifdef TOOLS_ENABLED + if (Engine::get_singleton()->is_editor_hint()) { + return; + } +#endif + Node *node = is_inside_tree() ? get_node_or_null(root_path) : nullptr; + if (!node) { + return; + } + set_process_internal(false); + set_physics_process_internal(false); + if (!visibility_filters.size()) { + return; + } + switch (visibility_update_mode) { + case VISIBILITY_PROCESS_IDLE: + set_process_internal(true); + break; + case VISIBILITY_PROCESS_PHYSICS: + set_physics_process_internal(true); + break; + case VISIBILITY_PROCESS_NONE: + break; } } @@ -85,18 +123,99 @@ Error MultiplayerSynchronizer::set_state(const List<NodePath> &p_properties, Obj return OK; } +bool MultiplayerSynchronizer::is_visibility_public() const { + return peer_visibility.has(0); +} + +void MultiplayerSynchronizer::set_visibility_public(bool p_visible) { + set_visibility_for(0, p_visible); +} + +bool MultiplayerSynchronizer::is_visible_to(int p_peer) { + if (visibility_filters.size()) { + Variant arg = p_peer; + const Variant *argv[1] = { &arg }; + for (Callable filter : visibility_filters) { + Variant ret; + Callable::CallError err; + filter.call(argv, 1, ret, err); + ERR_FAIL_COND_V(err.error != Callable::CallError::CALL_OK || ret.get_type() != Variant::BOOL, false); + if (!ret.operator bool()) { + return false; + } + } + } + return peer_visibility.has(0) || peer_visibility.has(p_peer); +} + +void MultiplayerSynchronizer::add_visibility_filter(Callable p_callback) { + visibility_filters.insert(p_callback); + _update_process(); +} + +void MultiplayerSynchronizer::remove_visibility_filter(Callable p_callback) { + visibility_filters.erase(p_callback); + _update_process(); +} + +void MultiplayerSynchronizer::set_visibility_for(int p_peer, bool p_visible) { + if (peer_visibility.has(p_peer) == p_visible) { + return; + } + if (p_visible) { + peer_visibility.insert(p_peer); + } else { + peer_visibility.erase(p_peer); + } + update_visibility(p_peer); +} + +bool MultiplayerSynchronizer::get_visibility_for(int p_peer) const { + return peer_visibility.has(p_peer); +} + +void MultiplayerSynchronizer::set_visibility_update_mode(VisibilityUpdateMode p_mode) { + visibility_update_mode = p_mode; + _update_process(); +} + +MultiplayerSynchronizer::VisibilityUpdateMode MultiplayerSynchronizer::get_visibility_update_mode() const { + return visibility_update_mode; +} + void MultiplayerSynchronizer::_bind_methods() { ClassDB::bind_method(D_METHOD("set_root_path", "path"), &MultiplayerSynchronizer::set_root_path); ClassDB::bind_method(D_METHOD("get_root_path"), &MultiplayerSynchronizer::get_root_path); - ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "root_path"), "set_root_path", "get_root_path"); ClassDB::bind_method(D_METHOD("set_replication_interval", "milliseconds"), &MultiplayerSynchronizer::set_replication_interval); ClassDB::bind_method(D_METHOD("get_replication_interval"), &MultiplayerSynchronizer::get_replication_interval); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "replication_interval", PROPERTY_HINT_RANGE, "0,5,0.001"), "set_replication_interval", "get_replication_interval"); ClassDB::bind_method(D_METHOD("set_replication_config", "config"), &MultiplayerSynchronizer::set_replication_config); ClassDB::bind_method(D_METHOD("get_replication_config"), &MultiplayerSynchronizer::get_replication_config); - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "resource", PROPERTY_HINT_RESOURCE_TYPE, "SceneReplicationConfig"), "set_replication_config", "get_replication_config"); + + ClassDB::bind_method(D_METHOD("set_visibility_update_mode", "mode"), &MultiplayerSynchronizer::set_visibility_update_mode); + ClassDB::bind_method(D_METHOD("get_visibility_update_mode"), &MultiplayerSynchronizer::get_visibility_update_mode); + ClassDB::bind_method(D_METHOD("update_visibility", "for_peer"), &MultiplayerSynchronizer::update_visibility, DEFVAL(0)); + + ClassDB::bind_method(D_METHOD("set_visibility_public", "visible"), &MultiplayerSynchronizer::set_visibility_public); + ClassDB::bind_method(D_METHOD("is_visibility_public"), &MultiplayerSynchronizer::is_visibility_public); + + ClassDB::bind_method(D_METHOD("add_visibility_filter", "filter"), &MultiplayerSynchronizer::add_visibility_filter); + ClassDB::bind_method(D_METHOD("remove_visibility_filter", "filter"), &MultiplayerSynchronizer::remove_visibility_filter); + ClassDB::bind_method(D_METHOD("set_visibility_for", "peer", "visible"), &MultiplayerSynchronizer::set_visibility_for); + ClassDB::bind_method(D_METHOD("get_visibility_for", "peer"), &MultiplayerSynchronizer::get_visibility_for); + + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "root_path"), "set_root_path", "get_root_path"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "replication_interval", PROPERTY_HINT_RANGE, "0,5,0.001,suffix:s"), "set_replication_interval", "get_replication_interval"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "replication_config", PROPERTY_HINT_RESOURCE_TYPE, "SceneReplicationConfig", PROPERTY_USAGE_NO_EDITOR), "set_replication_config", "get_replication_config"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "visibility_update_mode", PROPERTY_HINT_ENUM, "Idle,Physics,None"), "set_visibility_update_mode", "get_visibility_update_mode"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "public_visibility"), "set_visibility_public", "is_visibility_public"); + + BIND_ENUM_CONSTANT(VISIBILITY_PROCESS_IDLE); + BIND_ENUM_CONSTANT(VISIBILITY_PROCESS_PHYSICS); + BIND_ENUM_CONSTANT(VISIBILITY_PROCESS_NONE); + + ADD_SIGNAL(MethodInfo("visibility_changed", PropertyInfo(Variant::INT, "for_peer"))); } void MultiplayerSynchronizer::_notification(int p_what) { @@ -117,6 +236,11 @@ void MultiplayerSynchronizer::_notification(int p_what) { case NOTIFICATION_EXIT_TREE: { _stop(); } break; + + case NOTIFICATION_INTERNAL_PROCESS: + case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: { + update_visibility(0); + } break; } } @@ -141,6 +265,18 @@ Ref<SceneReplicationConfig> MultiplayerSynchronizer::get_replication_config() { return replication_config; } +void MultiplayerSynchronizer::update_visibility(int p_for_peer) { +#ifdef TOOLS_ENABLED + if (Engine::get_singleton()->is_editor_hint()) { + return; + } +#endif + Node *node = is_inside_tree() ? get_node_or_null(root_path) : nullptr; + if (node && get_multiplayer()->has_multiplayer_peer() && is_multiplayer_authority()) { + emit_signal(SNAME("visibility_changed"), p_for_peer); + } +} + void MultiplayerSynchronizer::set_root_path(const NodePath &p_path) { _stop(); root_path = p_path; @@ -161,3 +297,8 @@ void MultiplayerSynchronizer::set_multiplayer_authority(int p_peer_id, bool p_re Node::set_multiplayer_authority(p_peer_id, p_recursive); get_multiplayer()->replication_start(node, this); } + +MultiplayerSynchronizer::MultiplayerSynchronizer() { + // Publicly visible by default. + peer_visibility.insert(0); +} diff --git a/scene/multiplayer/multiplayer_synchronizer.h b/scene/multiplayer/multiplayer_synchronizer.h index e856745379..59f02b84c1 100644 --- a/scene/multiplayer/multiplayer_synchronizer.h +++ b/scene/multiplayer/multiplayer_synchronizer.h @@ -38,14 +38,25 @@ class MultiplayerSynchronizer : public Node { GDCLASS(MultiplayerSynchronizer, Node); +public: + enum VisibilityUpdateMode { + VISIBILITY_PROCESS_IDLE, + VISIBILITY_PROCESS_PHYSICS, + VISIBILITY_PROCESS_NONE, + }; + private: Ref<SceneReplicationConfig> replication_config; - NodePath root_path; + NodePath root_path = NodePath(".."); // Start with parent, like with AnimationPlayer. uint64_t interval_msec = 0; + VisibilityUpdateMode visibility_update_mode = VISIBILITY_PROCESS_IDLE; + HashSet<Callable> visibility_filters; + HashSet<int> peer_visibility; static Object *_get_prop_target(Object *p_obj, const NodePath &p_prop); void _start(); void _stop(); + void _update_process(); protected: static void _bind_methods(); @@ -66,7 +77,19 @@ public: NodePath get_root_path() const; virtual void set_multiplayer_authority(int p_peer_id, bool p_recursive = true) override; - MultiplayerSynchronizer() {} + bool is_visibility_public() const; + void set_visibility_public(bool p_public); + bool is_visible_to(int p_peer); + void set_visibility_for(int p_peer, bool p_visible); + bool get_visibility_for(int p_peer) const; + void update_visibility(int p_for_peer); + void set_visibility_update_mode(VisibilityUpdateMode p_mode); + void add_visibility_filter(Callable p_callback); + void remove_visibility_filter(Callable p_callback); + VisibilityUpdateMode get_visibility_update_mode() const; + + MultiplayerSynchronizer(); }; +VARIANT_ENUM_CAST(MultiplayerSynchronizer::VisibilityUpdateMode); #endif // MULTIPLAYER_SYNCHRONIZER_H diff --git a/scene/multiplayer/scene_cache_interface.cpp b/scene/multiplayer/scene_cache_interface.cpp index f05dc5a2da..79a7dc2d5a 100644 --- a/scene/multiplayer/scene_cache_interface.cpp +++ b/scene/multiplayer/scene_cache_interface.cpp @@ -50,10 +50,8 @@ void SceneCacheInterface::on_peer_change(int p_id, bool p_connected) { path_get_cache.erase(p_id); // Cleanup sent cache. // Some refactoring is needed to make this faster and do paths GC. - List<NodePath> keys; - path_send_cache.get_key_list(&keys); - for (const NodePath &E : keys) { - PathSentCache *psc = path_send_cache.getptr(E); + for (const KeyValue<NodePath, PathSentCache> &E : path_send_cache) { + PathSentCache *psc = path_send_cache.getptr(E.key); psc->confirmed_peers.erase(p_id); } } @@ -134,9 +132,9 @@ void SceneCacheInterface::process_confirm_path(int p_from, const uint8_t *p_pack PathSentCache *psc = path_send_cache.getptr(path); ERR_FAIL_COND_MSG(!psc, "Invalid packet received. Tries to confirm a path which was not found in cache."); - Map<int, bool>::Element *E = psc->confirmed_peers.find(p_from); + HashMap<int, bool>::Iterator E = psc->confirmed_peers.find(p_from); ERR_FAIL_COND_MSG(!E, "Invalid packet received. Source peer was not found in cache for the given path."); - E->get() = true; + E->value = true; } Error SceneCacheInterface::_send_confirm_path(Node *p_node, NodePath p_path, PathSentCache *psc, const List<int> &p_peers) { @@ -184,58 +182,69 @@ Error SceneCacheInterface::_send_confirm_path(Node *p_node, NodePath p_path, Pat bool SceneCacheInterface::is_cache_confirmed(NodePath p_path, int p_peer) { const PathSentCache *psc = path_send_cache.getptr(p_path); ERR_FAIL_COND_V(!psc, false); - const Map<int, bool>::Element *F = psc->confirmed_peers.find(p_peer); + HashMap<int, bool>::ConstIterator F = psc->confirmed_peers.find(p_peer); ERR_FAIL_COND_V(!F, false); // Should never happen. - return F->get(); + return F->value; } -bool SceneCacheInterface::send_object_cache(Object *p_obj, NodePath p_path, int p_peer_id, int &r_id) { +int SceneCacheInterface::make_object_cache(Object *p_obj) { Node *node = Object::cast_to<Node>(p_obj); - ERR_FAIL_COND_V(!node, false); + ERR_FAIL_COND_V(!node, -1); + NodePath for_path = multiplayer->get_root_path().rel_path_to(node->get_path()); // See if the path is cached. - PathSentCache *psc = path_send_cache.getptr(p_path); + PathSentCache *psc = path_send_cache.getptr(for_path); if (!psc) { // Path is not cached, create. - path_send_cache[p_path] = PathSentCache(); - psc = path_send_cache.getptr(p_path); + path_send_cache[for_path] = PathSentCache(); + psc = path_send_cache.getptr(for_path); psc->id = last_send_cache_id++; } - r_id = psc->id; + return psc->id; +} + +bool SceneCacheInterface::send_object_cache(Object *p_obj, int p_peer_id, int &r_id) { + Node *node = Object::cast_to<Node>(p_obj); + ERR_FAIL_COND_V(!node, false); + + r_id = make_object_cache(p_obj); + ERR_FAIL_COND_V(r_id < 0, false); + NodePath for_path = multiplayer->get_root_path().rel_path_to(node->get_path()); + PathSentCache *psc = path_send_cache.getptr(for_path); bool has_all_peers = true; List<int> peers_to_add; // If one is missing, take note to add it. if (p_peer_id > 0) { // Fast single peer check. - Map<int, bool>::Element *F = psc->confirmed_peers.find(p_peer_id); + HashMap<int, bool>::Iterator F = psc->confirmed_peers.find(p_peer_id); if (!F) { peers_to_add.push_back(p_peer_id); // Need to also be notified. has_all_peers = false; - } else if (!F->get()) { + } else if (!F->value) { has_all_peers = false; } } else { // Long and painful. - for (const Set<int>::Element *E = multiplayer->get_connected_peers().front(); E; E = E->next()) { - if (p_peer_id < 0 && E->get() == -p_peer_id) { + for (const int &E : multiplayer->get_connected_peers()) { + if (p_peer_id < 0 && E == -p_peer_id) { continue; // Continue, excluded. } - if (p_peer_id > 0 && E->get() != p_peer_id) { + if (p_peer_id > 0 && E != p_peer_id) { continue; // Continue, not for this peer. } - Map<int, bool>::Element *F = psc->confirmed_peers.find(E->get()); + HashMap<int, bool>::Iterator F = psc->confirmed_peers.find(E); if (!F) { - peers_to_add.push_back(E->get()); // Need to also be notified. + peers_to_add.push_back(E); // Need to also be notified. has_all_peers = false; - } else if (!F->get()) { + } else if (!F->value) { has_all_peers = false; } } } if (peers_to_add.size()) { - _send_confirm_path(node, p_path, psc, peers_to_add); + _send_confirm_path(node, for_path, psc, peers_to_add); } return has_all_peers; @@ -244,13 +253,13 @@ bool SceneCacheInterface::send_object_cache(Object *p_obj, NodePath p_path, int Object *SceneCacheInterface::get_cached_object(int p_from, uint32_t p_cache_id) { Node *root_node = SceneTree::get_singleton()->get_root()->get_node(multiplayer->get_root_path()); ERR_FAIL_COND_V(!root_node, nullptr); - Map<int, PathGetCache>::Element *E = path_get_cache.find(p_from); + HashMap<int, PathGetCache>::Iterator E = path_get_cache.find(p_from); ERR_FAIL_COND_V_MSG(!E, nullptr, vformat("No cache found for peer %d.", p_from)); - Map<int, PathGetCache::NodeInfo>::Element *F = E->get().nodes.find(p_cache_id); + HashMap<int, PathGetCache::NodeInfo>::Iterator F = E->value.nodes.find(p_cache_id); ERR_FAIL_COND_V_MSG(!F, nullptr, vformat("ID %d not found in cache of peer %d.", p_cache_id, p_from)); - PathGetCache::NodeInfo *ni = &F->get(); + PathGetCache::NodeInfo *ni = &F->value; Node *node = root_node->get_node(ni->path); if (!node) { ERR_PRINT("Failed to get cached path: " + String(ni->path) + "."); diff --git a/scene/multiplayer/scene_cache_interface.h b/scene/multiplayer/scene_cache_interface.h index d53cf387fb..6bfd683cf4 100644 --- a/scene/multiplayer/scene_cache_interface.h +++ b/scene/multiplayer/scene_cache_interface.h @@ -41,7 +41,7 @@ private: //path sent caches struct PathSentCache { - Map<int, bool> confirmed_peers; + HashMap<int, bool> confirmed_peers; int id; }; @@ -52,11 +52,11 @@ private: ObjectID instance; }; - Map<int, NodeInfo> nodes; + HashMap<int, NodeInfo> nodes; }; HashMap<NodePath, PathSentCache> path_send_cache; - Map<int, PathGetCache> path_get_cache; + HashMap<int, PathGetCache> path_get_cache; int last_send_cache_id = 1; protected: @@ -72,7 +72,8 @@ public: virtual void process_confirm_path(int p_from, const uint8_t *p_packet, int p_packet_len) override; // Returns true if all peers have cached path. - virtual bool send_object_cache(Object *p_obj, NodePath p_path, int p_target, int &p_id) override; + virtual bool send_object_cache(Object *p_obj, int p_target, int &p_id) override; + virtual int make_object_cache(Object *p_obj) override; virtual Object *get_cached_object(int p_from, uint32_t p_cache_id) override; virtual bool is_cache_confirmed(NodePath p_path, int p_peer) override; diff --git a/scene/multiplayer/scene_replication_interface.cpp b/scene/multiplayer/scene_replication_interface.cpp index 0764f136e4..c616c5bb85 100644 --- a/scene/multiplayer/scene_replication_interface.cpp +++ b/scene/multiplayer/scene_replication_interface.cpp @@ -49,9 +49,8 @@ void SceneReplicationInterface::make_default() { void SceneReplicationInterface::_free_remotes(int p_id) { const HashMap<uint32_t, ObjectID> remotes = rep_state->peer_get_remotes(p_id); - const uint32_t *k = nullptr; - while ((k = remotes.next(k))) { - Node *node = rep_state->get_node(remotes.get(*k)); + for (const KeyValue<uint32_t, ObjectID> &E : remotes) { + Node *node = rep_state->get_node(E.value); ERR_CONTINUE(!node); node->queue_delete(); } @@ -61,14 +60,13 @@ void SceneReplicationInterface::on_peer_change(int p_id, bool p_connected) { if (p_connected) { rep_state->on_peer_change(p_id, p_connected); for (const ObjectID &oid : rep_state->get_spawned_nodes()) { - _send_spawn(rep_state->get_node(oid), rep_state->get_spawner(oid), p_id); + _update_spawn_visibility(p_id, oid); } - for (const ObjectID &oid : rep_state->get_path_only_nodes()) { - Node *node = rep_state->get_node(oid); + for (const ObjectID &oid : rep_state->get_synced_nodes()) { MultiplayerSynchronizer *sync = rep_state->get_synchronizer(oid); - ERR_CONTINUE(!node || !sync); + ERR_CONTINUE(!sync); // ERR_BUG if (sync->is_multiplayer_authority()) { - rep_state->peer_add_node(p_id, oid); + _update_sync_visibility(p_id, oid); } } } else { @@ -98,7 +96,13 @@ Error SceneReplicationInterface::on_spawn(Object *p_obj, Variant p_config) { ERR_FAIL_COND_V(!spawner, ERR_INVALID_PARAMETER); Error err = rep_state->config_add_spawn(node, spawner); ERR_FAIL_COND_V(err != OK, err); - return _send_spawn(node, spawner, 0); + const ObjectID oid = node->get_instance_id(); + if (multiplayer->has_multiplayer_peer() && spawner->is_multiplayer_authority()) { + rep_state->ensure_net_id(oid); + _update_spawn_visibility(0, oid); + } + ERR_FAIL_COND_V(err != OK, err); + return OK; } Error SceneReplicationInterface::on_despawn(Object *p_obj, Variant p_config) { @@ -106,9 +110,19 @@ Error SceneReplicationInterface::on_despawn(Object *p_obj, Variant p_config) { ERR_FAIL_COND_V(!node || p_config.get_type() != Variant::OBJECT, ERR_INVALID_PARAMETER); MultiplayerSpawner *spawner = Object::cast_to<MultiplayerSpawner>(p_config.get_validated_object()); ERR_FAIL_COND_V(!p_obj || !spawner, ERR_INVALID_PARAMETER); - Error err = rep_state->config_del_spawn(node, spawner); - ERR_FAIL_COND_V(err != OK, err); - return _send_despawn(node, 0); + // Forcibly despawn to all peers that knowns me. + int len = 0; + Error err = _make_despawn_packet(node, len); + ERR_FAIL_COND_V(err != OK, ERR_BUG); + const ObjectID oid = p_obj->get_instance_id(); + for (int pid : rep_state->get_peers()) { + if (!rep_state->is_peer_spawn(pid, oid)) { + continue; + } + _send_raw(packet_cache.ptr(), len, pid, true); + } + // Also remove spawner tracking from the replication state. + return rep_state->config_del_spawn(node, spawner); } Error SceneReplicationInterface::on_replication_start(Object *p_obj, Variant p_config) { @@ -116,7 +130,15 @@ Error SceneReplicationInterface::on_replication_start(Object *p_obj, Variant p_c ERR_FAIL_COND_V(!node || p_config.get_type() != Variant::OBJECT, ERR_INVALID_PARAMETER); MultiplayerSynchronizer *sync = Object::cast_to<MultiplayerSynchronizer>(p_config.get_validated_object()); ERR_FAIL_COND_V(!sync, ERR_INVALID_PARAMETER); + + // Add to synchronizer list and setup visibility. rep_state->config_add_sync(node, sync); + const ObjectID oid = node->get_instance_id(); + sync->connect("visibility_changed", callable_mp(this, &SceneReplicationInterface::_visibility_changed), varray(oid)); + if (multiplayer->has_multiplayer_peer() && sync->is_multiplayer_authority()) { + _update_sync_visibility(0, oid); + } + // Try to apply initial state if spawning (hack to apply if before ready). if (pending_spawn == p_obj->get_instance_id()) { pending_spawn = ObjectID(); // Make sure this only happens once. @@ -128,9 +150,6 @@ Error SceneReplicationInterface::on_replication_start(Object *p_obj, Variant p_c ERR_FAIL_COND_V(err, err); err = MultiplayerSynchronizer::set_state(props, node, vars); ERR_FAIL_COND_V(err, err); - } else if (multiplayer->has_multiplayer_peer() && sync->is_multiplayer_authority()) { - // Either it's a spawn or a static sync, in any case add it to the list of known nodes. - rep_state->peer_add_node(0, p_obj->get_instance_id()); } return OK; } @@ -139,10 +158,103 @@ Error SceneReplicationInterface::on_replication_stop(Object *p_obj, Variant p_co Node *node = Object::cast_to<Node>(p_obj); ERR_FAIL_COND_V(!node || p_config.get_type() != Variant::OBJECT, ERR_INVALID_PARAMETER); MultiplayerSynchronizer *sync = Object::cast_to<MultiplayerSynchronizer>(p_config.get_validated_object()); - ERR_FAIL_COND_V(!p_obj || !sync, ERR_INVALID_PARAMETER); + ERR_FAIL_COND_V(!sync, ERR_INVALID_PARAMETER); + sync->disconnect("visibility_changed", callable_mp(this, &SceneReplicationInterface::_visibility_changed)); return rep_state->config_del_sync(node, sync); } +void SceneReplicationInterface::_visibility_changed(int p_peer, ObjectID p_oid) { + if (rep_state->is_spawned_node(p_oid)) { + _update_spawn_visibility(p_peer, p_oid); + } + if (rep_state->is_synced_node(p_oid)) { + _update_sync_visibility(p_peer, p_oid); + } +} + +Error SceneReplicationInterface::_update_sync_visibility(int p_peer, const ObjectID &p_oid) { + MultiplayerSynchronizer *sync = rep_state->get_synchronizer(p_oid); + ERR_FAIL_COND_V(!sync || !sync->is_multiplayer_authority(), ERR_BUG); + bool is_visible = sync->is_visible_to(p_peer); + if (p_peer == 0) { + for (int pid : rep_state->get_peers()) { + // Might be visible to this specific peer. + is_visible = is_visible || sync->is_visible_to(pid); + if (rep_state->is_peer_sync(pid, p_oid) == is_visible) { + continue; + } + if (is_visible) { + rep_state->peer_add_sync(pid, p_oid); + } else { + rep_state->peer_del_sync(pid, p_oid); + } + } + return OK; + } else { + if (is_visible == rep_state->is_peer_sync(p_peer, p_oid)) { + return OK; + } + if (is_visible) { + return rep_state->peer_add_sync(p_peer, p_oid); + } else { + return rep_state->peer_del_sync(p_peer, p_oid); + } + } +} + +Error SceneReplicationInterface::_update_spawn_visibility(int p_peer, const ObjectID &p_oid) { + MultiplayerSpawner *spawner = rep_state->get_spawner(p_oid); + MultiplayerSynchronizer *sync = rep_state->get_synchronizer(p_oid); + Node *node = Object::cast_to<Node>(ObjectDB::get_instance(p_oid)); + ERR_FAIL_COND_V(!node || !spawner || !spawner->is_multiplayer_authority(), ERR_BUG); + bool is_visible = !sync || sync->is_visible_to(p_peer); + // Spawn (and despawn) when needed. + HashSet<int> to_spawn; + HashSet<int> to_despawn; + if (p_peer) { + if (is_visible == rep_state->is_peer_spawn(p_peer, p_oid)) { + return OK; + } + if (is_visible) { + to_spawn.insert(p_peer); + } else { + to_despawn.insert(p_peer); + } + } else { + // Check visibility for each peers. + for (int pid : rep_state->get_peers()) { + bool peer_visible = is_visible || sync->is_visible_to(pid); + if (peer_visible == rep_state->is_peer_spawn(pid, p_oid)) { + continue; + } + if (peer_visible) { + to_spawn.insert(pid); + } else { + to_despawn.insert(pid); + } + } + } + if (to_spawn.size()) { + int len = 0; + _make_spawn_packet(node, len); + for (int pid : to_spawn) { + int path_id; + multiplayer->send_object_cache(spawner, pid, path_id); + _send_raw(packet_cache.ptr(), len, pid, true); + rep_state->peer_add_spawn(pid, p_oid); + } + } + if (to_despawn.size()) { + int len = 0; + _make_despawn_packet(node, len); + for (int pid : to_despawn) { + rep_state->peer_del_spawn(pid, p_oid); + _send_raw(packet_cache.ptr(), len, pid, true); + } + } + return OK; +} + Error SceneReplicationInterface::_send_raw(const uint8_t *p_buffer, int p_size, int p_peer, bool p_reliable) { ERR_FAIL_COND_V(!p_buffer || p_size < 1, ERR_INVALID_PARAMETER); ERR_FAIL_COND_V(!multiplayer, ERR_UNCONFIGURED); @@ -159,18 +271,20 @@ Error SceneReplicationInterface::_send_raw(const uint8_t *p_buffer, int p_size, return peer->put_packet(p_buffer, p_size); } -Error SceneReplicationInterface::_send_spawn(Node *p_node, MultiplayerSpawner *p_spawner, int p_peer) { - ERR_FAIL_COND_V(p_peer < 0, ERR_BUG); +Error SceneReplicationInterface::_make_spawn_packet(Node *p_node, int &r_len) { ERR_FAIL_COND_V(!multiplayer, ERR_BUG); - ERR_FAIL_COND_V(!p_spawner || !p_node, ERR_BUG); const ObjectID oid = p_node->get_instance_id(); - uint32_t nid = rep_state->ensure_net_id(oid); + MultiplayerSpawner *spawner = rep_state->get_spawner(oid); + ERR_FAIL_COND_V(!spawner || !p_node, ERR_BUG); + + uint32_t nid = rep_state->get_net_id(oid); + ERR_FAIL_COND_V(!nid, ERR_UNCONFIGURED); // Prepare custom arg and scene_id - uint8_t scene_id = p_spawner->get_spawn_id(oid); + uint8_t scene_id = spawner->find_spawnable_scene_index_from_object(oid); bool is_custom = scene_id == MultiplayerSpawner::INVALID_ID; - Variant spawn_arg = p_spawner->get_spawn_argument(oid); + Variant spawn_arg = spawner->get_spawn_argument(oid); int spawn_arg_size = 0; if (is_custom) { Error err = MultiplayerAPI::encode_and_compress_variant(spawn_arg, nullptr, spawn_arg_size, false); @@ -182,7 +296,8 @@ Error SceneReplicationInterface::_send_spawn(Node *p_node, MultiplayerSpawner *p Vector<Variant> state_vars; Vector<const Variant *> state_varp; MultiplayerSynchronizer *synchronizer = rep_state->get_synchronizer(oid); - if (synchronizer && synchronizer->get_replication_config().is_valid()) { + if (synchronizer) { + ERR_FAIL_COND_V(synchronizer->get_replication_config().is_null(), ERR_BUG); const List<NodePath> props = synchronizer->get_replication_config()->get_spawn_properties(); Error err = MultiplayerSynchronizer::get_state(props, p_node, state_vars, state_varp); ERR_FAIL_COND_V_MSG(err != OK, err, "Unable to retrieve spawn state."); @@ -190,13 +305,8 @@ Error SceneReplicationInterface::_send_spawn(Node *p_node, MultiplayerSpawner *p ERR_FAIL_COND_V_MSG(err != OK, err, "Unable to encode spawn state."); } - // Prepare simplified path. - NodePath rel_path = multiplayer->get_root_path().rel_path_to(p_spawner->get_path()); - - int path_id = 0; - multiplayer->send_object_cache(p_spawner, rel_path, p_peer, path_id); - - // Encode name and parent ID. + // Encode scene ID, path ID, net ID, node name. + int path_id = multiplayer->make_object_cache(spawner); CharString cname = p_node->get_name().operator String().utf8(); int nlen = encode_cstring(cname.get_data(), nullptr); MAKE_ROOM(1 + 1 + 4 + 4 + 4 + nlen + (is_custom ? 4 + spawn_arg_size : 0) + state_size); @@ -221,12 +331,11 @@ Error SceneReplicationInterface::_send_spawn(Node *p_node, MultiplayerSpawner *p ERR_FAIL_COND_V(err, err); ofs += state_size; } - Error err = _send_raw(ptr, ofs, p_peer, true); - ERR_FAIL_COND_V(err, err); - return rep_state->peer_add_node(p_peer, oid); + r_len = ofs; + return OK; } -Error SceneReplicationInterface::_send_despawn(Node *p_node, int p_peer) { +Error SceneReplicationInterface::_make_despawn_packet(Node *p_node, int &r_len) { const ObjectID oid = p_node->get_instance_id(); MAKE_ROOM(5); uint8_t *ptr = packet_cache.ptrw(); @@ -234,9 +343,8 @@ Error SceneReplicationInterface::_send_despawn(Node *p_node, int p_peer) { int ofs = 1; uint32_t nid = rep_state->get_net_id(oid); ofs += encode_uint32(nid, &ptr[ofs]); - Error err = _send_raw(ptr, ofs, p_peer, true); - ERR_FAIL_COND_V(err, err); - return rep_state->peer_del_node(p_peer, oid); + r_len = ofs; + return OK; } Error SceneReplicationInterface::on_spawn_receive(int p_from, const uint8_t *p_buffer, int p_buffer_len) { @@ -309,13 +417,16 @@ Error SceneReplicationInterface::on_despawn_receive(int p_from, const uint8_t *p Error err = rep_state->peer_del_remote(p_from, net_id, &node); ERR_FAIL_COND_V(err != OK, err); ERR_FAIL_COND_V(!node, ERR_BUG); + if (node->get_parent() != nullptr) { + node->get_parent()->remove_child(node); + } node->queue_delete(); return OK; } void SceneReplicationInterface::_send_sync(int p_peer, uint64_t p_msec) { - const Set<ObjectID> &known = rep_state->get_known_nodes(p_peer); - if (known.is_empty()) { + const HashSet<ObjectID> &to_sync = rep_state->get_peer_sync_nodes(p_peer); + if (to_sync.is_empty()) { return; } MAKE_ROOM(sync_mtu); @@ -325,14 +436,29 @@ void SceneReplicationInterface::_send_sync(int p_peer, uint64_t p_msec) { ofs += encode_uint16(rep_state->peer_sync_next(p_peer), &ptr[1]); // Can only send updates for already notified nodes. // This is a lazy implementation, we could optimize much more here with by grouping by replication config. - for (const ObjectID &oid : known) { + for (const ObjectID &oid : to_sync) { if (!rep_state->update_sync_time(oid, p_msec)) { continue; // nothing to sync. } MultiplayerSynchronizer *sync = rep_state->get_synchronizer(oid); - ERR_CONTINUE(!sync); + ERR_CONTINUE(!sync || !sync->get_replication_config().is_valid()); Node *node = rep_state->get_node(oid); ERR_CONTINUE(!node); + uint32_t net_id = rep_state->get_net_id(oid); + if (net_id == 0 || (net_id & 0x80000000)) { + int path_id = 0; + bool verified = multiplayer->send_object_cache(sync, p_peer, path_id); + ERR_CONTINUE_MSG(path_id < 0, "This should never happen!"); + if (net_id == 0) { + // First time path based ID. + net_id = path_id | 0x80000000; + rep_state->set_net_id(oid, net_id | 0x80000000); + } + if (!verified) { + // The path based sync is not yet confirmed, skipping. + continue; + } + } int size; Vector<Variant> vars; Vector<const Variant *> varp; @@ -349,16 +475,6 @@ void SceneReplicationInterface::_send_sync(int p_peer, uint64_t p_msec) { ofs = 3; } if (size) { - uint32_t net_id = rep_state->get_net_id(oid); - if (net_id == 0 || (net_id & 0x80000000)) { - // First time path based ID. - NodePath rel_path = multiplayer->get_root_path().rel_path_to(sync->get_path()); - int path_id = 0; - multiplayer->send_object_cache(sync, rel_path, p_peer, path_id); - ERR_CONTINUE_MSG(net_id && net_id != (uint32_t(path_id) | 0x80000000), "This should never happen!"); - net_id = path_id; - rep_state->set_net_id(oid, net_id | 0x80000000); - } ofs += encode_uint32(rep_state->get_net_id(oid), &ptr[ofs]); ofs += encode_uint32(size, &ptr[ofs]); MultiplayerAPI::encode_and_compress_variants(varp.ptrw(), varp.size(), &ptr[ofs], size); diff --git a/scene/multiplayer/scene_replication_interface.h b/scene/multiplayer/scene_replication_interface.h index 60ac95c93c..ad3a3be979 100644 --- a/scene/multiplayer/scene_replication_interface.h +++ b/scene/multiplayer/scene_replication_interface.h @@ -40,10 +40,13 @@ class SceneReplicationInterface : public MultiplayerReplicationInterface { private: void _send_sync(int p_peer, uint64_t p_msec); - Error _send_spawn(Node *p_node, MultiplayerSpawner *p_spawner, int p_peer); - Error _send_despawn(Node *p_node, int p_peer); + Error _make_spawn_packet(Node *p_node, int &r_len); + Error _make_despawn_packet(Node *p_node, int &r_len); Error _send_raw(const uint8_t *p_buffer, int p_size, int p_peer, bool p_reliable); + void _visibility_changed(int p_peer, ObjectID p_oid); + Error _update_sync_visibility(int p_peer, const ObjectID &p_oid); + Error _update_spawn_visibility(int p_peer, const ObjectID &p_oid); void _free_remotes(int p_peer); Ref<SceneReplicationState> rep_state; diff --git a/scene/multiplayer/scene_replication_state.cpp b/scene/multiplayer/scene_replication_state.cpp index b8dadeff24..f6a51ff9c7 100644 --- a/scene/multiplayer/scene_replication_state.cpp +++ b/scene/multiplayer/scene_replication_state.cpp @@ -55,9 +55,9 @@ void SceneReplicationState::_untrack(const ObjectID &p_id) { } // If we spawned or synced it, we need to remove it from any peer it was sent to. if (net_id || peer == 0) { - const int *k = nullptr; - while ((k = peers_info.next(k))) { - peers_info.get(*k).known_nodes.erase(p_id); + for (KeyValue<int, PeerInfo> &E : peers_info) { + E.value.sync_nodes.erase(p_id); + E.value.spawn_nodes.erase(p_id); } } } @@ -94,11 +94,6 @@ bool SceneReplicationState::update_sync_time(const ObjectID &p_id, uint64_t p_ms return false; } -const Set<ObjectID> SceneReplicationState::get_known_nodes(int p_peer) { - ERR_FAIL_COND_V(!peers_info.has(p_peer), Set<ObjectID>()); - return peers_info[p_peer].known_nodes; -} - uint32_t SceneReplicationState::get_net_id(const ObjectID &p_id) const { const TrackedNode *tnode = tracked_nodes.getptr(p_id); ERR_FAIL_COND_V(!tnode, 0); @@ -134,9 +129,8 @@ void SceneReplicationState::reset() { peers_info.clear(); known_peers.clear(); // Tracked nodes are cleared on deletion, here we only reset the ids so they can be later re-assigned. - const ObjectID *oid = nullptr; - while ((oid = tracked_nodes.next(oid))) { - TrackedNode &tobj = tracked_nodes[*oid]; + for (KeyValue<ObjectID, TrackedNode> &E : tracked_nodes) { + TrackedNode &tobj = E.value; tobj.net_id = 0; tobj.remote_peer = 0; tobj.last_sync = 0; @@ -149,8 +143,6 @@ Error SceneReplicationState::config_add_spawn(Node *p_node, MultiplayerSpawner * ERR_FAIL_COND_V(tobj.spawner != ObjectID(), ERR_ALREADY_IN_USE); tobj.spawner = p_spawner->get_instance_id(); spawned_nodes.insert(oid); - // The spawner may be notified after the synchronizer. - path_only_nodes.erase(oid); return OK; } @@ -161,6 +153,9 @@ Error SceneReplicationState::config_del_spawn(Node *p_node, MultiplayerSpawner * ERR_FAIL_COND_V(tobj.spawner != p_spawner->get_instance_id(), ERR_INVALID_PARAMETER); tobj.spawner = ObjectID(); spawned_nodes.erase(oid); + for (KeyValue<int, PeerInfo> &E : peers_info) { + E.value.spawn_nodes.erase(oid); + } return OK; } @@ -169,10 +164,7 @@ Error SceneReplicationState::config_add_sync(Node *p_node, MultiplayerSynchroniz TrackedNode &tobj = _track(oid); ERR_FAIL_COND_V(tobj.synchronizer != ObjectID(), ERR_ALREADY_IN_USE); tobj.synchronizer = p_sync->get_instance_id(); - // If it doesn't have a spawner, we might need to assign ID for this node using it's path. - if (tobj.spawner.is_null()) { - path_only_nodes.insert(oid); - } + synced_nodes.insert(oid); return OK; } @@ -182,40 +174,57 @@ Error SceneReplicationState::config_del_sync(Node *p_node, MultiplayerSynchroniz TrackedNode &tobj = _track(oid); ERR_FAIL_COND_V(tobj.synchronizer != p_sync->get_instance_id(), ERR_INVALID_PARAMETER); tobj.synchronizer = ObjectID(); - if (path_only_nodes.has(oid)) { - p_node->disconnect(SceneStringNames::get_singleton()->tree_exited, callable_mp(this, &SceneReplicationState::_untrack)); - _untrack(oid); - path_only_nodes.erase(oid); + synced_nodes.erase(oid); + for (KeyValue<int, PeerInfo> &E : peers_info) { + E.value.sync_nodes.erase(oid); } return OK; } -Error SceneReplicationState::peer_add_node(int p_peer, const ObjectID &p_id) { - if (p_peer) { - ERR_FAIL_COND_V(!peers_info.has(p_peer), ERR_INVALID_PARAMETER); - peers_info[p_peer].known_nodes.insert(p_id); - } else { - const int *pid = nullptr; - while ((pid = peers_info.next(pid))) { - peers_info.get(*pid).known_nodes.insert(p_id); - } - } +Error SceneReplicationState::peer_add_sync(int p_peer, const ObjectID &p_id) { + ERR_FAIL_COND_V(!peers_info.has(p_peer), ERR_INVALID_PARAMETER); + peers_info[p_peer].sync_nodes.insert(p_id); return OK; } -Error SceneReplicationState::peer_del_node(int p_peer, const ObjectID &p_id) { - if (p_peer) { - ERR_FAIL_COND_V(!peers_info.has(p_peer), ERR_INVALID_PARAMETER); - peers_info[p_peer].known_nodes.erase(p_id); - } else { - const int *pid = nullptr; - while ((pid = peers_info.next(pid))) { - peers_info.get(*pid).known_nodes.erase(p_id); - } - } +Error SceneReplicationState::peer_del_sync(int p_peer, const ObjectID &p_id) { + ERR_FAIL_COND_V(!peers_info.has(p_peer), ERR_INVALID_PARAMETER); + peers_info[p_peer].sync_nodes.erase(p_id); + return OK; +} + +const HashSet<ObjectID> SceneReplicationState::get_peer_sync_nodes(int p_peer) { + ERR_FAIL_COND_V(!peers_info.has(p_peer), HashSet<ObjectID>()); + return peers_info[p_peer].sync_nodes; +} + +bool SceneReplicationState::is_peer_sync(int p_peer, const ObjectID &p_id) const { + ERR_FAIL_COND_V(!peers_info.has(p_peer), false); + return peers_info[p_peer].sync_nodes.has(p_id); +} + +Error SceneReplicationState::peer_add_spawn(int p_peer, const ObjectID &p_id) { + ERR_FAIL_COND_V(!peers_info.has(p_peer), ERR_INVALID_PARAMETER); + peers_info[p_peer].spawn_nodes.insert(p_id); + return OK; +} + +Error SceneReplicationState::peer_del_spawn(int p_peer, const ObjectID &p_id) { + ERR_FAIL_COND_V(!peers_info.has(p_peer), ERR_INVALID_PARAMETER); + peers_info[p_peer].spawn_nodes.erase(p_id); return OK; } +const HashSet<ObjectID> SceneReplicationState::get_peer_spawn_nodes(int p_peer) { + ERR_FAIL_COND_V(!peers_info.has(p_peer), HashSet<ObjectID>()); + return peers_info[p_peer].spawn_nodes; +} + +bool SceneReplicationState::is_peer_spawn(int p_peer, const ObjectID &p_id) const { + ERR_FAIL_COND_V(!peers_info.has(p_peer), false); + return peers_info[p_peer].spawn_nodes.has(p_id); +} + Node *SceneReplicationState::peer_get_remote(int p_peer, uint32_t p_net_id) { PeerInfo *info = peers_info.getptr(p_peer); return info && info->recv_nodes.has(p_net_id) ? Object::cast_to<Node>(ObjectDB::get_instance(info->recv_nodes[p_net_id])) : nullptr; diff --git a/scene/multiplayer/scene_replication_state.h b/scene/multiplayer/scene_replication_state.h index 18e4d9fa39..7973b5c904 100644 --- a/scene/multiplayer/scene_replication_state.h +++ b/scene/multiplayer/scene_replication_state.h @@ -62,27 +62,30 @@ private: }; struct PeerInfo { - Set<ObjectID> known_nodes; + HashSet<ObjectID> sync_nodes; + HashSet<ObjectID> spawn_nodes; HashMap<uint32_t, ObjectID> recv_nodes; uint16_t last_sent_sync = 0; uint16_t last_recv_sync = 0; }; - Set<int> known_peers; + HashSet<int> known_peers; uint32_t last_net_id = 0; HashMap<ObjectID, TrackedNode> tracked_nodes; HashMap<int, PeerInfo> peers_info; - Set<ObjectID> spawned_nodes; - Set<ObjectID> path_only_nodes; + HashSet<ObjectID> spawned_nodes; + HashSet<ObjectID> synced_nodes; TrackedNode &_track(const ObjectID &p_id); void _untrack(const ObjectID &p_id); bool is_tracked(const ObjectID &p_id) const { return tracked_nodes.has(p_id); } public: - const Set<int> get_peers() const { return known_peers; } - const Set<ObjectID> get_spawned_nodes() const { return spawned_nodes; } - const Set<ObjectID> get_path_only_nodes() const { return path_only_nodes; } + const HashSet<int> get_peers() const { return known_peers; } + const HashSet<ObjectID> &get_spawned_nodes() const { return spawned_nodes; } + bool is_spawned_node(const ObjectID &p_id) const { return spawned_nodes.has(p_id); } + const HashSet<ObjectID> &get_synced_nodes() const { return synced_nodes; } + bool is_synced_node(const ObjectID &p_id) const { return synced_nodes.has(p_id); } MultiplayerSynchronizer *get_synchronizer(const ObjectID &p_id) { return tracked_nodes.has(p_id) ? tracked_nodes[p_id].get_synchronizer() : nullptr; } MultiplayerSpawner *get_spawner(const ObjectID &p_id) { return tracked_nodes.has(p_id) ? tracked_nodes[p_id].get_spawner() : nullptr; } @@ -90,7 +93,6 @@ public: bool update_last_node_sync(const ObjectID &p_id, uint16_t p_time); bool update_sync_time(const ObjectID &p_id, uint64_t p_msec); - const Set<ObjectID> get_known_nodes(int p_peer); uint32_t get_net_id(const ObjectID &p_id) const; void set_net_id(const ObjectID &p_id, uint32_t p_net_id); uint32_t ensure_net_id(const ObjectID &p_id); @@ -104,8 +106,17 @@ public: Error config_add_sync(Node *p_node, MultiplayerSynchronizer *p_sync); Error config_del_sync(Node *p_node, MultiplayerSynchronizer *p_sync); - Error peer_add_node(int p_peer, const ObjectID &p_id); - Error peer_del_node(int p_peer, const ObjectID &p_id); + Error peer_add_sync(int p_peer, const ObjectID &p_id); + Error peer_del_sync(int p_peer, const ObjectID &p_id); + + const HashSet<ObjectID> get_peer_sync_nodes(int p_peer); + bool is_peer_sync(int p_peer, const ObjectID &p_id) const; + + Error peer_add_spawn(int p_peer, const ObjectID &p_id); + Error peer_del_spawn(int p_peer, const ObjectID &p_id); + + const HashSet<ObjectID> get_peer_spawn_nodes(int p_peer); + bool is_peer_spawn(int p_peer, const ObjectID &p_id) const; const HashMap<uint32_t, ObjectID> peer_get_remotes(int p_peer) const; Node *peer_get_remote(int p_peer, uint32_t p_net_id); diff --git a/scene/multiplayer/scene_rpc_interface.cpp b/scene/multiplayer/scene_rpc_interface.cpp index 84700a82f3..144a10c665 100644 --- a/scene/multiplayer/scene_rpc_interface.cpp +++ b/scene/multiplayer/scene_rpc_interface.cpp @@ -302,12 +302,9 @@ void SceneRPCInterface::_send_rpc(Node *p_from, int p_to, uint16_t p_rpc_id, con ERR_FAIL_MSG("Attempt to call RPC with unknown peer ID: " + itos(p_to) + "."); } - NodePath from_path = multiplayer->get_root_path().rel_path_to(p_from->get_path()); - ERR_FAIL_COND_MSG(from_path.is_empty(), "Unable to send RPC. Relative path is empty. THIS IS LIKELY A BUG IN THE ENGINE!"); - // See if all peers have cached path (if so, call can be fast). int psc_id; - const bool has_all_peers = multiplayer->send_object_cache(p_from, from_path, p_to, psc_id); + const bool has_all_peers = multiplayer->send_object_cache(p_from, p_to, psc_id); // Create base packet, lots of hardcode because it must be tight. @@ -414,6 +411,7 @@ void SceneRPCInterface::_send_rpc(Node *p_from, int p_to, uint16_t p_rpc_id, con // Not all verified path, so send one by one. // Append path at the end, since we will need it for some packets. + NodePath from_path = multiplayer->get_root_path().rel_path_to(p_from->get_path()); CharString pname = String(from_path).utf8(); int path_len = encode_cstring(pname.get_data(), nullptr); MAKE_ROOM(ofs + path_len); diff --git a/scene/register_scene_types.cpp b/scene/register_scene_types.cpp index 127e734e6c..b1ef3d0f6f 100644 --- a/scene/register_scene_types.cpp +++ b/scene/register_scene_types.cpp @@ -129,6 +129,7 @@ #include "scene/main/canvas_layer.h" #include "scene/main/http_request.h" #include "scene/main/instance_placeholder.h" +#include "scene/main/missing_node.h" #include "scene/main/resource_preloader.h" #include "scene/main/scene_tree.h" #include "scene/main/timer.h" @@ -142,6 +143,7 @@ #include "scene/resources/animation_library.h" #include "scene/resources/audio_stream_sample.h" #include "scene/resources/bit_map.h" +#include "scene/resources/bone_map.h" #include "scene/resources/box_shape_3d.h" #include "scene/resources/camera_effects.h" #include "scene/resources/capsule_shape_2d.h" @@ -157,6 +159,7 @@ #include "scene/resources/gradient.h" #include "scene/resources/height_map_shape_3d.h" #include "scene/resources/immediate_mesh.h" +#include "scene/resources/label_settings.h" #include "scene/resources/material.h" #include "scene/resources/mesh.h" #include "scene/resources/mesh_data_tool.h" @@ -188,6 +191,7 @@ #include "scene/resources/skeleton_modification_3d_twoboneik.h" #include "scene/resources/skeleton_modification_stack_2d.h" #include "scene/resources/skeleton_modification_stack_3d.h" +#include "scene/resources/skeleton_profile.h" #include "scene/resources/sky.h" #include "scene/resources/sky_material.h" #include "scene/resources/sphere_shape_3d.h" @@ -226,6 +230,7 @@ #include "scene/3d/gpu_particles_collision_3d.h" #include "scene/3d/importer_mesh_instance_3d.h" #include "scene/3d/joint_3d.h" +#include "scene/3d/label_3d.h" #include "scene/3d/light_3d.h" #include "scene/3d/lightmap_gi.h" #include "scene/3d/lightmap_probe.h" @@ -301,6 +306,7 @@ void register_scene_types() { GDREGISTER_CLASS(Object); GDREGISTER_CLASS(Node); + GDREGISTER_VIRTUAL_CLASS(MissingNode); GDREGISTER_ABSTRACT_CLASS(InstancePlaceholder); GDREGISTER_ABSTRACT_CLASS(Viewport); @@ -434,6 +440,7 @@ void register_scene_types() { GDREGISTER_CLASS(AnimationNodeStateMachine); GDREGISTER_CLASS(AnimationNodeStateMachinePlayback); + GDREGISTER_CLASS(AnimationNodeSync); GDREGISTER_CLASS(AnimationNodeStateMachineTransition); GDREGISTER_CLASS(AnimationNodeOutput); GDREGISTER_CLASS(AnimationNodeOneShot); @@ -480,6 +487,7 @@ void register_scene_types() { GDREGISTER_ABSTRACT_CLASS(SpriteBase3D); GDREGISTER_CLASS(Sprite3D); GDREGISTER_CLASS(AnimatedSprite3D); + GDREGISTER_CLASS(Label3D); GDREGISTER_ABSTRACT_CLASS(Light3D); GDREGISTER_CLASS(DirectionalLight3D); GDREGISTER_CLASS(OmniLight3D); @@ -576,6 +584,7 @@ void register_scene_types() { GDREGISTER_CLASS(VisualShaderNodeColorConstant); GDREGISTER_CLASS(VisualShaderNodeVec2Constant); GDREGISTER_CLASS(VisualShaderNodeVec3Constant); + GDREGISTER_CLASS(VisualShaderNodeVec4Constant); GDREGISTER_CLASS(VisualShaderNodeTransformConstant); GDREGISTER_CLASS(VisualShaderNodeFloatOp); GDREGISTER_CLASS(VisualShaderNodeIntOp); @@ -620,6 +629,7 @@ void register_scene_types() { GDREGISTER_CLASS(VisualShaderNodeColorUniform); GDREGISTER_CLASS(VisualShaderNodeVec2Uniform); GDREGISTER_CLASS(VisualShaderNodeVec3Uniform); + GDREGISTER_CLASS(VisualShaderNodeVec4Uniform); GDREGISTER_CLASS(VisualShaderNodeTransformUniform); GDREGISTER_CLASS(VisualShaderNodeTextureUniform); GDREGISTER_CLASS(VisualShaderNodeTextureUniformTriplanar); @@ -748,6 +758,7 @@ void register_scene_types() { GDREGISTER_VIRTUAL_CLASS(Mesh); GDREGISTER_CLASS(ArrayMesh); + GDREGISTER_CLASS(PlaceholderMesh); GDREGISTER_CLASS(ImmediateMesh); GDREGISTER_CLASS(MultiMesh); GDREGISTER_CLASS(SurfaceTool); @@ -762,6 +773,7 @@ void register_scene_types() { GDREGISTER_CLASS(PrismMesh); GDREGISTER_CLASS(QuadMesh); GDREGISTER_CLASS(SphereMesh); + GDREGISTER_CLASS(TextMesh); GDREGISTER_CLASS(TubeTrailMesh); GDREGISTER_CLASS(RibbonTrailMesh); GDREGISTER_CLASS(PointMesh); @@ -769,6 +781,7 @@ void register_scene_types() { GDREGISTER_ABSTRACT_CLASS(BaseMaterial3D); GDREGISTER_CLASS(StandardMaterial3D); GDREGISTER_CLASS(ORMMaterial3D); + GDREGISTER_CLASS(PlaceholderMaterial); SceneTree::add_idle_callback(BaseMaterial3D::flush_changes); BaseMaterial3D::init_shaders(); @@ -833,13 +846,24 @@ void register_scene_types() { GDREGISTER_CLASS(CompressedCubemap); GDREGISTER_CLASS(CompressedCubemapArray); GDREGISTER_CLASS(CompressedTexture2DArray); + GDREGISTER_CLASS(PlaceholderTexture2D); + GDREGISTER_CLASS(PlaceholderTexture3D); + GDREGISTER_ABSTRACT_CLASS(PlaceholderTextureLayered); + GDREGISTER_CLASS(PlaceholderTexture2DArray); + GDREGISTER_CLASS(PlaceholderCubemap); + GDREGISTER_CLASS(PlaceholderCubemapArray); GDREGISTER_CLASS(Animation); GDREGISTER_CLASS(AnimationLibrary); - GDREGISTER_CLASS(FontData); - GDREGISTER_CLASS(Font); + + GDREGISTER_ABSTRACT_CLASS(Font); + GDREGISTER_CLASS(FontFile); + GDREGISTER_CLASS(FontVariation); + GDREGISTER_CLASS(Curve); + GDREGISTER_CLASS(LabelSettings); + GDREGISTER_CLASS(SceneReplicationConfig); GDREGISTER_CLASS(TextLine); @@ -856,6 +880,10 @@ void register_scene_types() { GDREGISTER_CLASS(BitMap); GDREGISTER_CLASS(Gradient); + GDREGISTER_CLASS(SkeletonProfile); + GDREGISTER_CLASS(SkeletonProfileHumanoid); + GDREGISTER_CLASS(BoneMap); + OS::get_singleton()->yield(); // may take time to init GDREGISTER_CLASS(AudioStreamPlayer); @@ -900,9 +928,9 @@ void register_scene_types() { ClassDB::add_compatibility_class("AnimationTreePlayer", "AnimationTree"); ClassDB::add_compatibility_class("BakedLightmap", "LightmapGI"); ClassDB::add_compatibility_class("BakedLightmapData", "LightmapGIData"); - ClassDB::add_compatibility_class("BitmapFont", "Font"); - ClassDB::add_compatibility_class("DynamicFont", "Font"); - ClassDB::add_compatibility_class("DynamicFontData", "FontData"); + ClassDB::add_compatibility_class("BitmapFont", "FontFile"); + ClassDB::add_compatibility_class("DynamicFont", "FontFile"); + ClassDB::add_compatibility_class("DynamicFontData", "FontFile"); ClassDB::add_compatibility_class("Navigation3D", "Node3D"); ClassDB::add_compatibility_class("Navigation2D", "Node2D"); ClassDB::add_compatibility_class("OpenSimplexNoise", "FastNoiseLite"); @@ -1062,15 +1090,15 @@ void register_scene_types() { OS::get_singleton()->yield(); // may take time to init for (int i = 0; i < 20; i++) { - GLOBAL_DEF_BASIC(vformat("layer_names/2d_render/layer_%d", i + 1), ""); - GLOBAL_DEF_BASIC(vformat("layer_names/3d_render/layer_%d", i + 1), ""); + GLOBAL_DEF_BASIC(vformat("%s/layer_%d", PNAME("layer_names/2d_render"), i + 1), ""); + GLOBAL_DEF_BASIC(vformat("%s/layer_%d", PNAME("layer_names/3d_render"), i + 1), ""); } for (int i = 0; i < 32; i++) { - GLOBAL_DEF_BASIC(vformat("layer_names/2d_physics/layer_%d", i + 1), ""); - GLOBAL_DEF_BASIC(vformat("layer_names/2d_navigation/layer_%d", i + 1), ""); - GLOBAL_DEF_BASIC(vformat("layer_names/3d_physics/layer_%d", i + 1), ""); - GLOBAL_DEF_BASIC(vformat("layer_names/3d_navigation/layer_%d", i + 1), ""); + GLOBAL_DEF_BASIC(vformat("%s/layer_%d", PNAME("layer_names/2d_physics"), i + 1), ""); + GLOBAL_DEF_BASIC(vformat("%s/layer_%d", PNAME("layer_names/2d_navigation"), i + 1), ""); + GLOBAL_DEF_BASIC(vformat("%s/layer_%d", PNAME("layer_names/3d_physics"), i + 1), ""); + GLOBAL_DEF_BASIC(vformat("%s/layer_%d", PNAME("layer_names/3d_navigation"), i + 1), ""); } if (RenderingServer::get_singleton()) { @@ -1081,8 +1109,6 @@ void register_scene_types() { SceneReplicationInterface::make_default(); SceneRPCInterface::make_default(); SceneCacheInterface::make_default(); - - NativeExtensionManager::get_singleton()->initialize_extensions(NativeExtension::INITIALIZATION_LEVEL_SCENE); } void initialize_theme() { @@ -1094,7 +1120,7 @@ void initialize_theme() { ProjectSettings::get_singleton()->set_custom_property_info("gui/theme/custom", PropertyInfo(Variant::STRING, "gui/theme/custom", PROPERTY_HINT_FILE, "*.tres,*.res,*.theme", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED)); String font_path = GLOBAL_DEF_RST("gui/theme/custom_font", ""); - ProjectSettings::get_singleton()->set_custom_property_info("gui/theme/custom_font", PropertyInfo(Variant::STRING, "gui/theme/custom_font", PROPERTY_HINT_FILE, "*.tres,*.res,*.font", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED)); + ProjectSettings::get_singleton()->set_custom_property_info("gui/theme/custom_font", PropertyInfo(Variant::STRING, "gui/theme/custom_font", PROPERTY_HINT_FILE, "*.tres,*.res", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED)); bool font_antialiased = (bool)GLOBAL_DEF_RST("gui/theme/default_font_antialiased", true); ProjectSettings::get_singleton()->set_custom_property_info("gui/theme/default_font_antialiased", PropertyInfo(Variant::BOOL, "gui/theme/default_font_antialiased", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED)); @@ -1105,6 +1131,9 @@ void initialize_theme() { TextServer::SubpixelPositioning font_subpixel_positioning = (TextServer::SubpixelPositioning)(int)GLOBAL_DEF_RST("gui/theme/default_font_subpixel_positioning", TextServer::SUBPIXEL_POSITIONING_AUTO); ProjectSettings::get_singleton()->set_custom_property_info("gui/theme/default_font_subpixel_positioning", PropertyInfo(Variant::INT, "gui/theme/default_font_subpixel_positioning", PROPERTY_HINT_ENUM, "Disabled,Auto,One half of a pixel,One quarter of a pixel", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED)); + const bool font_msdf = GLOBAL_DEF_RST("gui/theme/default_font_multichannel_signed_distance_field", false); + const bool font_generate_mipmaps = GLOBAL_DEF_RST("gui/theme/default_font_generate_mipmaps", false); + Ref<Font> font; if (!font_path.is_empty()) { font = ResourceLoader::load(font_path); @@ -1115,7 +1144,7 @@ void initialize_theme() { // Always make the default theme to avoid invalid default font/icon/style in the given theme. if (RenderingServer::get_singleton()) { - make_default_theme(default_theme_scale, font, font_subpixel_positioning, font_hinting, font_antialiased); + make_default_theme(default_theme_scale, font, font_subpixel_positioning, font_hinting, font_antialiased, font_msdf, font_generate_mipmaps); } if (!theme_path.is_empty()) { @@ -1132,8 +1161,6 @@ void initialize_theme() { } void unregister_scene_types() { - NativeExtensionManager::get_singleton()->deinitialize_extensions(NativeExtension::INITIALIZATION_LEVEL_SCENE); - SceneDebugger::deinitialize(); clear_default_theme(); diff --git a/scene/resources/animation.cpp b/scene/resources/animation.cpp index e045a379d2..7183accc66 100644 --- a/scene/resources/animation.cpp +++ b/scene/resources/animation.cpp @@ -3379,17 +3379,6 @@ Vector2 Animation::bezier_track_get_key_out_handle(int p_track, int p_index) con return bt->values[p_index].value.out_handle; } -static _FORCE_INLINE_ Vector2 _bezier_interp(real_t t, const Vector2 &start, const Vector2 &control_1, const Vector2 &control_2, const Vector2 &end) { - /* Formula from Wikipedia article on Bezier curves. */ - real_t omt = (1.0 - t); - real_t omt2 = omt * omt; - real_t omt3 = omt2 * omt; - real_t t2 = t * t; - real_t t3 = t2 * t; - - return start * omt3 + control_1 * omt2 * t * 3.0 + control_2 * omt * t2 * 3.0 + end * t3; -} - real_t Animation::bezier_track_interpolate(int p_track, double p_time) const { //this uses a different interpolation scheme ERR_FAIL_INDEX_V(p_track, tracks.size(), 0); @@ -3438,7 +3427,7 @@ real_t Animation::bezier_track_interpolate(int p_track, double p_time) const { for (int i = 0; i < iterations; i++) { real_t middle = (low + high) / 2; - Vector2 interp = _bezier_interp(middle, start, start_out, end_in, end); + Vector2 interp = start.bezier_interpolate(start_out, end_in, end, middle); if (interp.x < t) { low = middle; @@ -3448,14 +3437,14 @@ real_t Animation::bezier_track_interpolate(int p_track, double p_time) const { } //interpolate the result: - Vector2 low_pos = _bezier_interp(low, start, start_out, end_in, end); - Vector2 high_pos = _bezier_interp(high, start, start_out, end_in, end); + Vector2 low_pos = start.bezier_interpolate(start_out, end_in, end, low); + Vector2 high_pos = start.bezier_interpolate(start_out, end_in, end, high); real_t c = (t - low_pos.x) / (high_pos.x - low_pos.x); return low_pos.lerp(high_pos, c).y; } -int Animation::audio_track_insert_key(int p_track, double p_time, const RES &p_stream, real_t p_start_offset, real_t p_end_offset) { +int Animation::audio_track_insert_key(int p_track, double p_time, const Ref<Resource> &p_stream, real_t p_start_offset, real_t p_end_offset) { ERR_FAIL_INDEX_V(p_track, tracks.size(), -1); Track *t = tracks[p_track]; ERR_FAIL_COND_V(t->type != TYPE_AUDIO, -1); @@ -3481,7 +3470,7 @@ int Animation::audio_track_insert_key(int p_track, double p_time, const RES &p_s return key; } -void Animation::audio_track_set_key_stream(int p_track, int p_key, const RES &p_stream) { +void Animation::audio_track_set_key_stream(int p_track, int p_key, const Ref<Resource> &p_stream) { ERR_FAIL_INDEX(p_track, tracks.size()); Track *t = tracks[p_track]; ERR_FAIL_COND(t->type != TYPE_AUDIO); @@ -3531,14 +3520,14 @@ void Animation::audio_track_set_key_end_offset(int p_track, int p_key, real_t p_ emit_changed(); } -RES Animation::audio_track_get_key_stream(int p_track, int p_key) const { - ERR_FAIL_INDEX_V(p_track, tracks.size(), RES()); +Ref<Resource> Animation::audio_track_get_key_stream(int p_track, int p_key) const { + ERR_FAIL_INDEX_V(p_track, tracks.size(), Ref<Resource>()); const Track *t = tracks[p_track]; - ERR_FAIL_COND_V(t->type != TYPE_AUDIO, RES()); + ERR_FAIL_COND_V(t->type != TYPE_AUDIO, Ref<Resource>()); const AudioTrack *at = static_cast<const AudioTrack *>(t); - ERR_FAIL_INDEX_V(p_key, at->values.size(), RES()); + ERR_FAIL_INDEX_V(p_key, at->values.size(), Ref<Resource>()); return at->values[p_key].value.stream; } @@ -3827,9 +3816,9 @@ 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::FLOAT, "length", PROPERTY_HINT_RANGE, "0.001,99999,0.001,suffix:s"), "set_length", "get_length"); ADD_PROPERTY(PropertyInfo(Variant::INT, "loop_mode", PROPERTY_HINT_ENUM, "None,Linear,Ping-Pong"), "set_loop_mode", "get_loop_mode"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "step", PROPERTY_HINT_RANGE, "0,4096,0.001"), "set_step", "get_step"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "step", PROPERTY_HINT_RANGE, "0,4096,0.001,suffix:s"), "set_step", "get_step"); ADD_SIGNAL(MethodInfo("tracks_changed")); diff --git a/scene/resources/animation.h b/scene/resources/animation.h index f9a33da428..b4528ccd3a 100644 --- a/scene/resources/animation.h +++ b/scene/resources/animation.h @@ -180,7 +180,7 @@ private: /* AUDIO TRACK */ struct AudioKey { - RES stream; + Ref<Resource> stream; real_t start_offset = 0.0; //offset from start real_t end_offset = 0.0; //offset from end, if 0 then full length or infinite AudioKey() { @@ -436,11 +436,11 @@ public: real_t bezier_track_interpolate(int p_track, double p_time) const; - int audio_track_insert_key(int p_track, double p_time, const RES &p_stream, real_t p_start_offset = 0, real_t p_end_offset = 0); - void audio_track_set_key_stream(int p_track, int p_key, const RES &p_stream); + int audio_track_insert_key(int p_track, double p_time, const Ref<Resource> &p_stream, real_t p_start_offset = 0, real_t p_end_offset = 0); + void audio_track_set_key_stream(int p_track, int p_key, const Ref<Resource> &p_stream); void audio_track_set_key_start_offset(int p_track, int p_key, real_t p_offset); void audio_track_set_key_end_offset(int p_track, int p_key, real_t p_offset); - RES audio_track_get_key_stream(int p_track, int p_key) const; + Ref<Resource> audio_track_get_key_stream(int p_track, int p_key) const; real_t audio_track_get_key_start_offset(int p_track, int p_key) const; real_t audio_track_get_key_end_offset(int p_track, int p_key) const; diff --git a/scene/resources/animation_library.cpp b/scene/resources/animation_library.cpp index 229d9ab218..5f725b2fbe 100644 --- a/scene/resources/animation_library.cpp +++ b/scene/resources/animation_library.cpp @@ -30,8 +30,25 @@ #include "animation_library.h" +bool AnimationLibrary::is_valid_animation_name(const String &p_name) { + return !(p_name.is_empty() || p_name.contains("/") || p_name.contains(":") || p_name.contains(",") || p_name.contains("[")); +} + +bool AnimationLibrary::is_valid_library_name(const String &p_name) { + return !(p_name.contains("/") || p_name.contains(":") || p_name.contains(",") || p_name.contains("[")); +} + +String AnimationLibrary::validate_library_name(const String &p_name) { + String name = p_name; + const char *characters = "/:,["; + for (const char *p = characters; *p; p++) { + name = name.replace(String::chr(*p), "_"); + } + return name; +} + Error AnimationLibrary::add_animation(const StringName &p_name, const Ref<Animation> &p_animation) { - ERR_FAIL_COND_V_MSG(String(p_name).contains("/") || String(p_name).contains(":") || String(p_name).contains(",") || String(p_name).contains("["), ERR_INVALID_PARAMETER, "Invalid animation name: " + String(p_name) + "."); + ERR_FAIL_COND_V_MSG(!is_valid_animation_name(p_name), ERR_INVALID_PARAMETER, "Invalid animation name: '" + String(p_name) + "'."); ERR_FAIL_COND_V(p_animation.is_null(), ERR_INVALID_PARAMETER); if (animations.has(p_name)) { @@ -46,7 +63,7 @@ Error AnimationLibrary::add_animation(const StringName &p_name, const Ref<Animat } void AnimationLibrary::remove_animation(const StringName &p_name) { - ERR_FAIL_COND(!animations.has(p_name)); + ERR_FAIL_COND_MSG(!animations.has(p_name), vformat("Animation not found: %s.", p_name)); animations.erase(p_name); emit_signal(SNAME("animation_removed"), p_name); @@ -54,9 +71,9 @@ void AnimationLibrary::remove_animation(const StringName &p_name) { } void AnimationLibrary::rename_animation(const StringName &p_name, const StringName &p_new_name) { - ERR_FAIL_COND(!animations.has(p_name)); - ERR_FAIL_COND_MSG(String(p_new_name).contains("/") || String(p_new_name).contains(":") || String(p_new_name).contains(",") || String(p_new_name).contains("["), "Invalid animation name: " + String(p_new_name) + "."); - ERR_FAIL_COND(animations.has(p_new_name)); + ERR_FAIL_COND_MSG(!animations.has(p_name), vformat("Animation not found: %s.", p_name)); + ERR_FAIL_COND_MSG(!is_valid_animation_name(p_new_name), "Invalid animation name: '" + String(p_new_name) + "'."); + ERR_FAIL_COND_MSG(animations.has(p_new_name), vformat("Animation name \"%s\" already exists in library.", p_new_name)); animations.insert(p_new_name, animations[p_name]); animations.erase(p_name); @@ -126,9 +143,9 @@ void AnimationLibrary::_bind_methods() { ClassDB::bind_method(D_METHOD("_get_data"), &AnimationLibrary::_get_data); ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "_data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "_set_data", "_get_data"); - ADD_SIGNAL(MethodInfo("animation_added", PropertyInfo(Variant::OBJECT, "name", PROPERTY_HINT_RESOURCE_TYPE, "Animation"))); - ADD_SIGNAL(MethodInfo("animation_removed", PropertyInfo(Variant::OBJECT, "name", PROPERTY_HINT_RESOURCE_TYPE, "Animation"))); - ADD_SIGNAL(MethodInfo("animation_renamed", PropertyInfo(Variant::OBJECT, "name", PROPERTY_HINT_RESOURCE_TYPE, "Animation"), PropertyInfo(Variant::OBJECT, "to_name", PROPERTY_HINT_RESOURCE_TYPE, "Animation"))); + ADD_SIGNAL(MethodInfo("animation_added", PropertyInfo(Variant::STRING_NAME, "name"))); + ADD_SIGNAL(MethodInfo("animation_removed", PropertyInfo(Variant::STRING_NAME, "name"))); + ADD_SIGNAL(MethodInfo("animation_renamed", PropertyInfo(Variant::STRING_NAME, "name"), PropertyInfo(Variant::STRING_NAME, "to_name"))); } AnimationLibrary::AnimationLibrary() { } diff --git a/scene/resources/animation_library.h b/scene/resources/animation_library.h index 69ac5a97d2..7a69cd140a 100644 --- a/scene/resources/animation_library.h +++ b/scene/resources/animation_library.h @@ -43,12 +43,16 @@ class AnimationLibrary : public Resource { TypedArray<StringName> _get_animation_list() const; friend class AnimationPlayer; //for faster access - Map<StringName, Ref<Animation>> animations; + HashMap<StringName, Ref<Animation>> animations; protected: static void _bind_methods(); public: + static bool is_valid_animation_name(const String &p_name); + static bool is_valid_library_name(const String &p_name); + static String validate_library_name(const String &p_name); + Error add_animation(const StringName &p_name, const Ref<Animation> &p_animation); void remove_animation(const StringName &p_name); void rename_animation(const StringName &p_name, const StringName &p_new_name); diff --git a/scene/resources/bit_map.cpp b/scene/resources/bit_map.cpp index c2988c2e8c..1ff72825ac 100644 --- a/scene/resources/bit_map.cpp +++ b/scene/resources/bit_map.cpp @@ -170,8 +170,8 @@ Vector<Vector2> BitMap::_march_square(const Rect2i &rect, const Point2i &start) int curx = startx; int cury = starty; unsigned int count = 0; - Set<Point2i> case9s; - Set<Point2i> case6s; + HashSet<Point2i> case9s; + HashSet<Point2i> case6s; Vector<Vector2> _points; do { int sv = 0; diff --git a/scene/resources/bone_map.cpp b/scene/resources/bone_map.cpp new file mode 100644 index 0000000000..aff917b2d4 --- /dev/null +++ b/scene/resources/bone_map.cpp @@ -0,0 +1,181 @@ +/*************************************************************************/ +/* bone_map.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 "bone_map.h" + +bool BoneMap::_set(const StringName &p_path, const Variant &p_value) { + String path = p_path; + if (path.begins_with("bone_map/")) { + String which = path.get_slicec('/', 1); + set_skeleton_bone_name(which, p_value); + return true; + } + return true; +} + +bool BoneMap::_get(const StringName &p_path, Variant &r_ret) const { + String path = p_path; + if (path.begins_with("bone_map/")) { + String which = path.get_slicec('/', 1); + r_ret = get_skeleton_bone_name(which); + return true; + } + return true; +} + +void BoneMap::_get_property_list(List<PropertyInfo> *p_list) const { + HashMap<StringName, StringName>::ConstIterator E = bone_map.begin(); + while (E) { + p_list->push_back(PropertyInfo(Variant::STRING_NAME, "bone_map/" + E->key, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR)); + ++E; + } +} + +Ref<SkeletonProfile> BoneMap::get_profile() const { + return profile; +} + +void BoneMap::set_profile(const Ref<SkeletonProfile> &p_profile) { + bool is_changed = profile != p_profile; + if (is_changed) { + if (!profile.is_null() && profile->is_connected("profile_updated", callable_mp(this, &BoneMap::_update_profile))) { + profile->disconnect("profile_updated", callable_mp(this, &BoneMap::_update_profile)); + } + profile = p_profile; + if (!profile.is_null()) { + profile->connect("profile_updated", callable_mp(this, &BoneMap::_update_profile)); + } + _update_profile(); + } + notify_property_list_changed(); +} + +StringName BoneMap::get_skeleton_bone_name(StringName p_profile_bone_name) const { + ERR_FAIL_COND_V(!bone_map.has(p_profile_bone_name), StringName()); + return bone_map.get(p_profile_bone_name); +} + +void BoneMap::set_skeleton_bone_name(StringName p_profile_bone_name, const StringName p_skeleton_bone_name) { + ERR_FAIL_COND(!bone_map.has(p_profile_bone_name)); + bone_map.insert(p_profile_bone_name, p_skeleton_bone_name); + emit_signal("bone_map_updated"); +} + +StringName BoneMap::find_profile_bone_name(StringName p_skeleton_bone_name) const { + StringName profile_bone_name = StringName(); + HashMap<StringName, StringName>::ConstIterator E = bone_map.begin(); + while (E) { + if (E->value == p_skeleton_bone_name) { + profile_bone_name = E->key; + break; + } + ++E; + } + return profile_bone_name; +} + +int BoneMap::get_skeleton_bone_name_count(const StringName p_skeleton_bone_name) const { + int count = 0; + HashMap<StringName, StringName>::ConstIterator E = bone_map.begin(); + while (E) { + if (E->value == p_skeleton_bone_name) { + ++count; + } + ++E; + } + return count; +} + +void BoneMap::_update_profile() { + _validate_bone_map(); + emit_signal("profile_updated"); +} + +void BoneMap::_validate_bone_map() { + Ref<SkeletonProfile> current_profile = get_profile(); + if (current_profile.is_valid()) { + // Insert missing profile bones into bone map. + int len = current_profile->get_bone_size(); + StringName profile_bone_name; + for (int i = 0; i < len; i++) { + profile_bone_name = current_profile->get_bone_name(i); + if (!bone_map.has(profile_bone_name)) { + bone_map.insert(profile_bone_name, StringName()); + } + } + // Remove bones that do not exist in the profile from the map. + Vector<StringName> delete_bones; + StringName k; + HashMap<StringName, StringName>::ConstIterator E = bone_map.begin(); + while (E) { + k = E->key; + if (!current_profile->has_bone(k)) { + delete_bones.push_back(k); + } + ++E; + } + len = delete_bones.size(); + for (int i = 0; i < len; i++) { + bone_map.erase(delete_bones[i]); + } + } else { + bone_map.clear(); + } + emit_signal("retarget_option_updated"); +} + +void BoneMap::_bind_methods() { + ClassDB::bind_method(D_METHOD("get_profile"), &BoneMap::get_profile); + ClassDB::bind_method(D_METHOD("set_profile", "profile"), &BoneMap::set_profile); + + ClassDB::bind_method(D_METHOD("get_skeleton_bone_name", "profile_bone_name"), &BoneMap::get_skeleton_bone_name); + ClassDB::bind_method(D_METHOD("set_skeleton_bone_name", "profile_bone_name", "skeleton_bone_name"), &BoneMap::set_skeleton_bone_name); + + ClassDB::bind_method(D_METHOD("find_profile_bone_name", "skeleton_bone_name"), &BoneMap::find_profile_bone_name); + + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "profile", PROPERTY_HINT_RESOURCE_TYPE, "SkeletonProfile"), "set_profile", "get_profile"); + ADD_ARRAY("bonemap", "bonemap"); + + ADD_SIGNAL(MethodInfo("bone_map_updated")); + ADD_SIGNAL(MethodInfo("profile_updated")); +} + +void BoneMap::_validate_property(PropertyInfo &property) const { + // +} + +BoneMap::BoneMap() { + _validate_bone_map(); +} + +BoneMap::~BoneMap() { +} + +////////////////////////////////////// diff --git a/scene/resources/bone_map.h b/scene/resources/bone_map.h new file mode 100644 index 0000000000..17452dfc73 --- /dev/null +++ b/scene/resources/bone_map.h @@ -0,0 +1,70 @@ +/*************************************************************************/ +/* bone_map.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 BONE_MAP_H +#define BONE_MAP_H + +#include "skeleton_profile.h" + +class BoneMap : public Resource { + GDCLASS(BoneMap, Resource); + + Ref<SkeletonProfile> profile; + HashMap<StringName, StringName> bone_map; + + void _update_profile(); + void _validate_bone_map(); + +protected: + bool _get(const StringName &p_path, Variant &r_ret) const; + bool _set(const StringName &p_path, const Variant &p_value); + virtual void _validate_property(PropertyInfo &property) const override; + void _get_property_list(List<PropertyInfo> *p_list) const; + static void _bind_methods(); + +public: + int get_profile_type() const; + void set_profile_type(const int p_profile_type); + + Ref<SkeletonProfile> get_profile() const; + void set_profile(const Ref<SkeletonProfile> &p_profile); + + int get_skeleton_bone_name_count(const StringName p_skeleton_bone_name) const; + + StringName get_skeleton_bone_name(StringName p_profile_bone_name) const; + void set_skeleton_bone_name(StringName p_profile_bone_name, const StringName p_skeleton_bone_name); + + StringName find_profile_bone_name(StringName p_skeleton_bone_name) const; + + BoneMap(); + ~BoneMap(); +}; + +#endif // BONE_MAP_H diff --git a/scene/resources/box_shape_3d.cpp b/scene/resources/box_shape_3d.cpp index 1abbf366fd..aac334b4be 100644 --- a/scene/resources/box_shape_3d.cpp +++ b/scene/resources/box_shape_3d.cpp @@ -91,7 +91,7 @@ void BoxShape3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_size", "size"), &BoxShape3D::set_size); ClassDB::bind_method(D_METHOD("get_size"), &BoxShape3D::get_size); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "size"), "set_size", "get_size"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "size", PROPERTY_HINT_NONE, "suffix:m"), "set_size", "get_size"); } BoxShape3D::BoxShape3D() : diff --git a/scene/resources/camera_effects.cpp b/scene/resources/camera_effects.cpp index ebe2aa4dba..97617adbae 100644 --- a/scene/resources/camera_effects.cpp +++ b/scene/resources/camera_effects.cpp @@ -175,10 +175,10 @@ void CameraEffects::_bind_methods() { ADD_GROUP("DOF Blur", "dof_blur_"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "dof_blur_far_enabled"), "set_dof_blur_far_enabled", "is_dof_blur_far_enabled"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "dof_blur_far_distance", PROPERTY_HINT_RANGE, "0.01,8192,0.01,exp"), "set_dof_blur_far_distance", "get_dof_blur_far_distance"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "dof_blur_far_distance", PROPERTY_HINT_RANGE, "0.01,8192,0.01,exp,suffix:m"), "set_dof_blur_far_distance", "get_dof_blur_far_distance"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "dof_blur_far_transition", PROPERTY_HINT_RANGE, "0.01,8192,0.01,exp"), "set_dof_blur_far_transition", "get_dof_blur_far_transition"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "dof_blur_near_enabled"), "set_dof_blur_near_enabled", "is_dof_blur_near_enabled"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "dof_blur_near_distance", PROPERTY_HINT_RANGE, "0.01,8192,0.01,exp"), "set_dof_blur_near_distance", "get_dof_blur_near_distance"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "dof_blur_near_distance", PROPERTY_HINT_RANGE, "0.01,8192,0.01,exp,suffix:m"), "set_dof_blur_near_distance", "get_dof_blur_near_distance"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "dof_blur_near_transition", PROPERTY_HINT_RANGE, "0.01,8192,0.01,exp"), "set_dof_blur_near_transition", "get_dof_blur_near_transition"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "dof_blur_amount", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_dof_blur_amount", "get_dof_blur_amount"); diff --git a/scene/resources/canvas_item_material.cpp b/scene/resources/canvas_item_material.cpp index 2d668cdf7f..aa6cc4aded 100644 --- a/scene/resources/canvas_item_material.cpp +++ b/scene/resources/canvas_item_material.cpp @@ -34,7 +34,7 @@ Mutex CanvasItemMaterial::material_mutex; SelfList<CanvasItemMaterial>::List *CanvasItemMaterial::dirty_materials = nullptr; -Map<CanvasItemMaterial::MaterialKey, CanvasItemMaterial::ShaderData> CanvasItemMaterial::shader_map; +HashMap<CanvasItemMaterial::MaterialKey, CanvasItemMaterial::ShaderData, CanvasItemMaterial::MaterialKey> CanvasItemMaterial::shader_map; CanvasItemMaterial::ShaderNames *CanvasItemMaterial::shader_names = nullptr; void CanvasItemMaterial::init_shaders() { diff --git a/scene/resources/canvas_item_material.h b/scene/resources/canvas_item_material.h index e40e4392cb..160c67d6b1 100644 --- a/scene/resources/canvas_item_material.h +++ b/scene/resources/canvas_item_material.h @@ -63,8 +63,11 @@ private: uint32_t key = 0; - bool operator<(const MaterialKey &p_key) const { - return key < p_key.key; + static uint32_t hash(const MaterialKey &p_key) { + return hash_murmur3_one_32(p_key.key); + } + bool operator==(const MaterialKey &p_key) const { + return key == p_key.key; } }; @@ -81,7 +84,7 @@ private: int users = 0; }; - static Map<MaterialKey, ShaderData> shader_map; + static HashMap<MaterialKey, ShaderData, MaterialKey> shader_map; MaterialKey current_key; @@ -107,10 +110,10 @@ private: LightMode light_mode = LIGHT_MODE_NORMAL; bool particles_animation = false; - // Initialized in the constructor. - int particles_anim_h_frames; - int particles_anim_v_frames; - bool particles_anim_loop; + // Proper values set in constructor. + int particles_anim_h_frames = 0; + int particles_anim_v_frames = 0; + bool particles_anim_loop = false; protected: static void _bind_methods(); diff --git a/scene/resources/capsule_shape_2d.cpp b/scene/resources/capsule_shape_2d.cpp index c7bd4cb698..eb27ffaf35 100644 --- a/scene/resources/capsule_shape_2d.cpp +++ b/scene/resources/capsule_shape_2d.cpp @@ -109,8 +109,8 @@ void CapsuleShape2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_height", "height"), &CapsuleShape2D::set_height); ClassDB::bind_method(D_METHOD("get_height"), &CapsuleShape2D::get_height); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radius", PROPERTY_HINT_RANGE, "0.01,1024,0.01,or_greater"), "set_radius", "get_radius"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "height", PROPERTY_HINT_RANGE, "0.01,1024,0.01,or_greater"), "set_height", "get_height"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radius", PROPERTY_HINT_RANGE, "0.01,1024,0.01,or_greater,suffix:px"), "set_radius", "get_radius"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "height", PROPERTY_HINT_RANGE, "0.01,1024,0.01,or_greater,suffix:px"), "set_height", "get_height"); ADD_LINKED_PROPERTY("radius", "height"); ADD_LINKED_PROPERTY("height", "radius"); } diff --git a/scene/resources/capsule_shape_3d.cpp b/scene/resources/capsule_shape_3d.cpp index d708706ff2..214004824f 100644 --- a/scene/resources/capsule_shape_3d.cpp +++ b/scene/resources/capsule_shape_3d.cpp @@ -113,8 +113,8 @@ void CapsuleShape3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_height", "height"), &CapsuleShape3D::set_height); ClassDB::bind_method(D_METHOD("get_height"), &CapsuleShape3D::get_height); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radius", PROPERTY_HINT_RANGE, "0.001,100,0.001,or_greater"), "set_radius", "get_radius"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "height", PROPERTY_HINT_RANGE, "0.001,100,0.001,or_greater"), "set_height", "get_height"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radius", PROPERTY_HINT_RANGE, "0.001,100,0.001,or_greater,suffix:m"), "set_radius", "get_radius"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "height", PROPERTY_HINT_RANGE, "0.001,100,0.001,or_greater,suffix:m"), "set_height", "get_height"); ADD_LINKED_PROPERTY("radius", "height"); ADD_LINKED_PROPERTY("height", "radius"); } diff --git a/scene/resources/circle_shape_2d.cpp b/scene/resources/circle_shape_2d.cpp index c287de9ede..ff60162180 100644 --- a/scene/resources/circle_shape_2d.cpp +++ b/scene/resources/circle_shape_2d.cpp @@ -56,7 +56,7 @@ void CircleShape2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_radius", "radius"), &CircleShape2D::set_radius); ClassDB::bind_method(D_METHOD("get_radius"), &CircleShape2D::get_radius); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radius", PROPERTY_HINT_RANGE, "0.01,1024,0.01,or_greater"), "set_radius", "get_radius"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radius", PROPERTY_HINT_RANGE, "0.01,1024,0.01,or_greater,suffix:px"), "set_radius", "get_radius"); } Rect2 CircleShape2D::get_rect() const { diff --git a/scene/resources/concave_polygon_shape_3d.cpp b/scene/resources/concave_polygon_shape_3d.cpp index 3e178108c4..b91f0e4f1c 100644 --- a/scene/resources/concave_polygon_shape_3d.cpp +++ b/scene/resources/concave_polygon_shape_3d.cpp @@ -33,7 +33,7 @@ #include "servers/physics_server_3d.h" Vector<Vector3> ConcavePolygonShape3D::get_debug_mesh_lines() const { - Set<DrawEdge> edges; + HashSet<DrawEdge, DrawEdge> edges; int index_count = faces.size(); ERR_FAIL_COND_V((index_count % 3) != 0, Vector<Vector3>()); @@ -50,9 +50,9 @@ Vector<Vector3> ConcavePolygonShape3D::get_debug_mesh_lines() const { Vector<Vector3> points; points.resize(edges.size() * 2); int idx = 0; - for (Set<DrawEdge>::Element *E = edges.front(); E; E = E->next()) { - points.write[idx + 0] = E->get().a; - points.write[idx + 1] = E->get().b; + for (const DrawEdge &E : edges) { + points.write[idx + 0] = E.a; + points.write[idx + 1] = E.b; idx += 2; } diff --git a/scene/resources/concave_polygon_shape_3d.h b/scene/resources/concave_polygon_shape_3d.h index 5337deb5fb..a265590edd 100644 --- a/scene/resources/concave_polygon_shape_3d.h +++ b/scene/resources/concave_polygon_shape_3d.h @@ -42,12 +42,12 @@ class ConcavePolygonShape3D : public Shape3D { struct DrawEdge { Vector3 a; Vector3 b; - bool operator<(const DrawEdge &p_edge) const { - if (a == p_edge.a) { - return b < p_edge.b; - } else { - return a < p_edge.a; - } + static uint32_t hash(const DrawEdge &p_edge) { + uint32_t h = hash_murmur3_one_32(HashMapHasherDefault::hash(p_edge.a)); + return hash_murmur3_one_32(HashMapHasherDefault::hash(p_edge.b), h); + } + bool operator==(const DrawEdge &p_edge) const { + return (a == p_edge.a && b == p_edge.b); } DrawEdge(const Vector3 &p_a = Vector3(), const Vector3 &p_b = Vector3()) { diff --git a/scene/resources/curve.cpp b/scene/resources/curve.cpp index 6485c1ac77..da26a0261f 100644 --- a/scene/resources/curve.cpp +++ b/scene/resources/curve.cpp @@ -32,24 +32,25 @@ #include "core/core_string_names.h" -template <class T> -static _FORCE_INLINE_ T _bezier_interp(real_t p_t, T p_start, T p_control_1, T p_control_2, T p_end) { - /* Formula from Wikipedia article on Bezier curves. */ - real_t omt = (1.0 - p_t); - real_t omt2 = omt * omt; - real_t omt3 = omt2 * omt; - real_t t2 = p_t * p_t; - real_t t3 = t2 * p_t; - - return p_start * omt3 + p_control_1 * omt2 * p_t * 3.0 + p_control_2 * omt * t2 * 3.0 + p_end * t3; -} - const char *Curve::SIGNAL_RANGE_CHANGED = "range_changed"; Curve::Curve() { } -int Curve::add_point(Vector2 p_position, real_t p_left_tangent, real_t p_right_tangent, TangentMode p_left_mode, TangentMode p_right_mode) { +void Curve::set_point_count(int p_count) { + ERR_FAIL_COND(p_count < 0); + if (_points.size() >= p_count) { + _points.resize(p_count); + mark_dirty(); + } else { + for (int i = p_count - _points.size(); i > 0; i--) { + _add_point(Vector2()); + } + } + notify_property_list_changed(); +} + +int Curve::_add_point(Vector2 p_position, real_t p_left_tangent, real_t p_right_tangent, TangentMode p_left_mode, TangentMode p_right_mode) { // Add a point and preserve order // Curve bounds is in 0..1 @@ -100,6 +101,13 @@ int Curve::add_point(Vector2 p_position, real_t p_left_tangent, real_t p_right_t return ret; } +int Curve::add_point(Vector2 p_position, real_t p_left_tangent, real_t p_right_tangent, TangentMode p_left_mode, TangentMode p_right_mode) { + int ret = _add_point(p_position, p_left_tangent, p_right_tangent, p_left_mode, p_right_mode); + notify_property_list_changed(); + + return ret; +} + int Curve::get_index(real_t p_offset) const { // Lower-bound float binary search @@ -205,15 +213,21 @@ Curve::TangentMode Curve::get_point_right_mode(int p_index) const { return _points[p_index].right_mode; } -void Curve::remove_point(int p_index) { +void Curve::_remove_point(int p_index) { ERR_FAIL_INDEX(p_index, _points.size()); _points.remove_at(p_index); mark_dirty(); } +void Curve::remove_point(int p_index) { + _remove_point(p_index); + notify_property_list_changed(); +} + void Curve::clear_points() { _points.clear(); mark_dirty(); + notify_property_list_changed(); } void Curve::set_point_value(int p_index, real_t p_position) { @@ -226,8 +240,8 @@ void Curve::set_point_value(int p_index, real_t p_position) { int Curve::set_point_offset(int p_index, real_t p_offset) { ERR_FAIL_INDEX_V(p_index, _points.size(), -1); Point p = _points[p_index]; - remove_point(p_index); - int i = add_point(Vector2(p_offset, p.position.y)); + _remove_point(p_index); + int i = _add_point(Vector2(p_offset, p.position.y)); _points.write[i].left_tangent = p.left_tangent; _points.write[i].right_tangent = p.right_tangent; _points.write[i].left_mode = p.left_mode; @@ -350,7 +364,7 @@ real_t Curve::interpolate_local_nocheck(int p_index, real_t p_local_offset) cons real_t yac = a.position.y + d * a.right_tangent; real_t ybc = b.position.y - d * b.left_tangent; - real_t y = _bezier_interp(p_local_offset, a.position.y, yac, ybc, b.position.y); + real_t y = Math::bezier_interpolate(a.position.y, yac, ybc, b.position.y, p_local_offset); return y; } @@ -409,7 +423,6 @@ void Curve::set_data(const Array p_input) { p.position = p_input[i]; p.left_tangent = p_input[i + 1]; p.right_tangent = p_input[i + 2]; - // TODO For some reason the compiler won't convert from Variant to enum int left_mode = p_input[i + 3]; int right_mode = p_input[i + 4]; p.left_mode = (TangentMode)left_mode; @@ -417,6 +430,7 @@ void Curve::set_data(const Array p_input) { } mark_dirty(); + notify_property_list_changed(); } void Curve::bake() { @@ -490,8 +504,91 @@ void Curve::ensure_default_setup(real_t p_min, real_t p_max) { } } +bool Curve::_set(const StringName &p_name, const Variant &p_value) { + Vector<String> components = String(p_name).split("/", true, 2); + if (components.size() >= 2 && components[0].begins_with("point_") && components[0].trim_prefix("point_").is_valid_int()) { + int point_index = components[0].trim_prefix("point_").to_int(); + String property = components[1]; + if (property == "position") { + Vector2 position = p_value.operator Vector2(); + set_point_offset(point_index, position.x); + set_point_value(point_index, position.y); + return true; + } else if (property == "left_tangent") { + set_point_left_tangent(point_index, p_value); + return true; + } else if (property == "left_mode") { + int mode = p_value; + set_point_left_mode(point_index, (TangentMode)mode); + return true; + } else if (property == "right_tangent") { + set_point_right_tangent(point_index, p_value); + return true; + } else if (property == "right_mode") { + int mode = p_value; + set_point_right_mode(point_index, (TangentMode)mode); + return true; + } + } + return false; +} + +bool Curve::_get(const StringName &p_name, Variant &r_ret) const { + Vector<String> components = String(p_name).split("/", true, 2); + if (components.size() >= 2 && components[0].begins_with("point_") && components[0].trim_prefix("point_").is_valid_int()) { + int point_index = components[0].trim_prefix("point_").to_int(); + String property = components[1]; + if (property == "position") { + r_ret = get_point_position(point_index); + return true; + } else if (property == "left_tangent") { + r_ret = get_point_left_tangent(point_index); + return true; + } else if (property == "left_mode") { + r_ret = get_point_left_mode(point_index); + return true; + } else if (property == "right_tangent") { + r_ret = get_point_right_tangent(point_index); + return true; + } else if (property == "right_mode") { + r_ret = get_point_right_mode(point_index); + return true; + } + } + return false; +} + +void Curve::_get_property_list(List<PropertyInfo> *p_list) const { + for (int i = 0; i < _points.size(); i++) { + PropertyInfo pi = PropertyInfo(Variant::VECTOR2, vformat("point_%d/position", i)); + pi.usage &= ~PROPERTY_USAGE_STORAGE; + p_list->push_back(pi); + + if (i != 0) { + pi = PropertyInfo(Variant::FLOAT, vformat("point_%d/left_tangent", i)); + pi.usage &= ~PROPERTY_USAGE_STORAGE; + p_list->push_back(pi); + + pi = PropertyInfo(Variant::INT, vformat("point_%d/left_mode", i), PROPERTY_HINT_ENUM, "Free,Linear"); + pi.usage &= ~PROPERTY_USAGE_STORAGE; + p_list->push_back(pi); + } + + if (i != _points.size() - 1) { + pi = PropertyInfo(Variant::FLOAT, vformat("point_%d/right_tangent", i)); + pi.usage &= ~PROPERTY_USAGE_STORAGE; + p_list->push_back(pi); + + pi = PropertyInfo(Variant::INT, vformat("point_%d/right_mode", i), PROPERTY_HINT_ENUM, "Free,Linear"); + pi.usage &= ~PROPERTY_USAGE_STORAGE; + p_list->push_back(pi); + } + } +} + void Curve::_bind_methods() { ClassDB::bind_method(D_METHOD("get_point_count"), &Curve::get_point_count); + ClassDB::bind_method(D_METHOD("set_point_count", "count"), &Curve::set_point_count); ClassDB::bind_method(D_METHOD("add_point", "position", "left_tangent", "right_tangent", "left_mode", "right_mode"), &Curve::add_point, DEFVAL(0), DEFVAL(0), DEFVAL(TANGENT_FREE), DEFVAL(TANGENT_FREE)); ClassDB::bind_method(D_METHOD("remove_point", "index"), &Curve::remove_point); ClassDB::bind_method(D_METHOD("clear_points"), &Curve::clear_points); @@ -523,6 +620,7 @@ void Curve::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "max_value", PROPERTY_HINT_RANGE, "-1024,1024,0.01"), "set_max_value", "get_max_value"); ADD_PROPERTY(PropertyInfo(Variant::INT, "bake_resolution", PROPERTY_HINT_RANGE, "1,1000,1"), "set_bake_resolution", "get_bake_resolution"); ADD_PROPERTY(PropertyInfo(Variant::INT, "_data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_data", "_get_data"); + ADD_ARRAY_COUNT("Points", "point_count", "set_point_count", "get_point_count", "point_"); ADD_SIGNAL(MethodInfo(SIGNAL_RANGE_CHANGED)); @@ -535,7 +633,20 @@ int Curve2D::get_point_count() const { return points.size(); } -void Curve2D::add_point(const Vector2 &p_position, const Vector2 &p_in, const Vector2 &p_out, int p_atpos) { +void Curve2D::set_point_count(int p_count) { + ERR_FAIL_COND(p_count < 0); + if (points.size() >= p_count) { + points.resize(p_count); + mark_dirty(); + } else { + for (int i = p_count - points.size(); i > 0; i--) { + _add_point(Vector2()); + } + } + notify_property_list_changed(); +} + +void Curve2D::_add_point(const Vector2 &p_position, const Vector2 &p_in, const Vector2 &p_out, int p_atpos) { Point n; n.position = p_position; n.in = p_in; @@ -546,16 +657,19 @@ void Curve2D::add_point(const Vector2 &p_position, const Vector2 &p_in, const Ve points.push_back(n); } - baked_cache_dirty = true; - emit_signal(CoreStringNames::get_singleton()->changed); + mark_dirty(); +} + +void Curve2D::add_point(const Vector2 &p_position, const Vector2 &p_in, const Vector2 &p_out, int p_atpos) { + _add_point(p_position, p_in, p_out, p_atpos); + notify_property_list_changed(); } void Curve2D::set_point_position(int p_index, const Vector2 &p_position) { ERR_FAIL_INDEX(p_index, points.size()); points.write[p_index].position = p_position; - baked_cache_dirty = true; - emit_signal(CoreStringNames::get_singleton()->changed); + mark_dirty(); } Vector2 Curve2D::get_point_position(int p_index) const { @@ -567,8 +681,7 @@ void Curve2D::set_point_in(int p_index, const Vector2 &p_in) { ERR_FAIL_INDEX(p_index, points.size()); points.write[p_index].in = p_in; - baked_cache_dirty = true; - emit_signal(CoreStringNames::get_singleton()->changed); + mark_dirty(); } Vector2 Curve2D::get_point_in(int p_index) const { @@ -580,8 +693,7 @@ void Curve2D::set_point_out(int p_index, const Vector2 &p_out) { ERR_FAIL_INDEX(p_index, points.size()); points.write[p_index].out = p_out; - baked_cache_dirty = true; - emit_signal(CoreStringNames::get_singleton()->changed); + mark_dirty(); } Vector2 Curve2D::get_point_out(int p_index) const { @@ -589,18 +701,22 @@ Vector2 Curve2D::get_point_out(int p_index) const { return points[p_index].out; } -void Curve2D::remove_point(int p_index) { +void Curve2D::_remove_point(int p_index) { ERR_FAIL_INDEX(p_index, points.size()); points.remove_at(p_index); - baked_cache_dirty = true; - emit_signal(CoreStringNames::get_singleton()->changed); + mark_dirty(); +} + +void Curve2D::remove_point(int p_index) { + _remove_point(p_index); + notify_property_list_changed(); } void Curve2D::clear_points() { if (!points.is_empty()) { points.clear(); - baked_cache_dirty = true; - emit_signal(CoreStringNames::get_singleton()->changed); + mark_dirty(); + notify_property_list_changed(); } } @@ -619,7 +735,7 @@ Vector2 Curve2D::interpolate(int p_index, const real_t p_offset) const { Vector2 p3 = points[p_index + 1].position; Vector2 p2 = p3 + points[p_index + 1].in; - return _bezier_interp(p_offset, p0, p1, p2, p3); + return p0.bezier_interpolate(p1, p2, p3, p_offset); } Vector2 Curve2D::interpolatef(real_t p_findex) const { @@ -632,11 +748,16 @@ Vector2 Curve2D::interpolatef(real_t p_findex) const { return interpolate((int)p_findex, Math::fmod(p_findex, (real_t)1.0)); } -void Curve2D::_bake_segment2d(Map<real_t, Vector2> &r_bake, real_t p_begin, real_t p_end, const Vector2 &p_a, const Vector2 &p_out, const Vector2 &p_b, const Vector2 &p_in, int p_depth, int p_max_depth, real_t p_tol) const { +void Curve2D::mark_dirty() { + baked_cache_dirty = true; + emit_signal(CoreStringNames::get_singleton()->changed); +} + +void Curve2D::_bake_segment2d(RBMap<real_t, Vector2> &r_bake, real_t p_begin, real_t p_end, const Vector2 &p_a, const Vector2 &p_out, const Vector2 &p_b, const Vector2 &p_in, int p_depth, int p_max_depth, real_t p_tol) const { real_t mp = p_begin + (p_end - p_begin) * 0.5; - Vector2 beg = _bezier_interp(p_begin, p_a, p_a + p_out, p_b + p_in, p_b); - Vector2 mid = _bezier_interp(mp, p_a, p_a + p_out, p_b + p_in, p_b); - Vector2 end = _bezier_interp(p_end, p_a, p_a + p_out, p_b + p_in, p_b); + Vector2 beg = p_a.bezier_interpolate(p_a + p_out, p_b + p_in, p_b, p_begin); + Vector2 mid = p_a.bezier_interpolate(p_a + p_out, p_b + p_in, p_b, mp); + Vector2 end = p_a.bezier_interpolate(p_a + p_out, p_b + p_in, p_b, p_end); Vector2 na = (mid - beg).normalized(); Vector2 nb = (end - mid).normalized(); @@ -681,7 +802,8 @@ void Curve2D::_bake() const { List<Vector2> pointlist; List<real_t> distlist; - pointlist.push_back(position); //start always from origin + // Start always from origin. + pointlist.push_back(position); distlist.push_back(0.0); for (int i = 0; i < points.size() - 1; i++) { @@ -694,7 +816,7 @@ void Curve2D::_bake() const { np = 1.0; } - Vector2 npp = _bezier_interp(np, points[i].position, points[i].position + points[i].out, points[i + 1].position + points[i + 1].in, points[i + 1].position); + Vector2 npp = points[i].position.bezier_interpolate(points[i].position + points[i].out, points[i + 1].position + points[i + 1].in, points[i + 1].position, np); real_t d = position.distance_to(npp); if (d > bake_interval) { @@ -707,7 +829,7 @@ void Curve2D::_bake() const { real_t mid = low + (hi - low) * 0.5; for (int j = 0; j < iterations; j++) { - npp = _bezier_interp(mid, points[i].position, points[i].position + points[i].out, points[i + 1].position + points[i + 1].in, points[i + 1].position); + npp = points[i].position.bezier_interpolate(points[i].position + points[i].out, points[i + 1].position + points[i + 1].in, points[i + 1].position, mid); d = position.distance_to(npp); if (bake_interval < d) { @@ -728,15 +850,18 @@ void Curve2D::_bake() const { p = np; } } - } - Vector2 lastpos = points[points.size() - 1].position; + Vector2 npp = points[i + 1].position; + real_t d = position.distance_to(npp); + + position = npp; + dist += d; + + pointlist.push_back(position); + distlist.push_back(dist); + } - real_t rem = position.distance_to(lastpos); - dist += rem; baked_max_ofs = dist; - pointlist.push_back(lastpos); - distlist.push_back(dist); baked_point_cache.resize(pointlist.size()); baked_dist_cache.resize(distlist.size()); @@ -763,7 +888,7 @@ Vector2 Curve2D::interpolate_baked(real_t p_offset, bool p_cubic) const { _bake(); } - //validate// + // Validate: Curve may not have baked points. int pc = baked_point_cache.size(); ERR_FAIL_COND_V_MSG(pc == 0, Vector2(), "No points in Curve2D."); @@ -771,18 +896,19 @@ Vector2 Curve2D::interpolate_baked(real_t p_offset, bool p_cubic) const { return baked_point_cache.get(0); } - int bpc = baked_point_cache.size(); const Vector2 *r = baked_point_cache.ptr(); if (p_offset < 0) { return r[0]; } if (p_offset >= baked_max_ofs) { - return r[bpc - 1]; + return r[pc - 1]; } - int start = 0, end = bpc, idx = (end + start) / 2; - // binary search to find baked points + int start = 0; + int end = pc; + int idx = (end + start) / 2; + // Binary search to find baked points. while (start < idx) { real_t offset = baked_dist_cache[idx]; if (p_offset <= offset) { @@ -803,7 +929,7 @@ Vector2 Curve2D::interpolate_baked(real_t p_offset, bool p_cubic) const { if (p_cubic) { Vector2 pre = idx > 0 ? r[idx - 1] : r[idx]; - Vector2 post = (idx < (bpc - 2)) ? r[idx + 2] : r[idx + 1]; + Vector2 post = (idx < (pc - 2)) ? r[idx + 2] : r[idx + 1]; return r[idx].cubic_interpolate(r[idx + 1], pre, post, frac); } else { return r[idx].lerp(r[idx + 1], frac); @@ -820,8 +946,7 @@ PackedVector2Array Curve2D::get_baked_points() const { void Curve2D::set_bake_interval(real_t p_tolerance) { bake_interval = p_tolerance; - baked_cache_dirty = true; - emit_signal(CoreStringNames::get_singleton()->changed); + mark_dirty(); } real_t Curve2D::get_bake_interval() const { @@ -829,13 +954,13 @@ real_t Curve2D::get_bake_interval() const { } Vector2 Curve2D::get_closest_point(const Vector2 &p_to_point) const { - // Brute force method + // Brute force method. if (baked_cache_dirty) { _bake(); } - //validate// + // Validate: Curve may not have baked points. int pc = baked_point_cache.size(); ERR_FAIL_COND_V_MSG(pc == 0, Vector2(), "No points in Curve2D."); @@ -867,13 +992,13 @@ Vector2 Curve2D::get_closest_point(const Vector2 &p_to_point) const { } real_t Curve2D::get_closest_offset(const Vector2 &p_to_point) const { - // Brute force method + // Brute force method. if (baked_cache_dirty) { _bake(); } - //validate// + // Validate: Curve may not have baked points. int pc = baked_point_cache.size(); ERR_FAIL_COND_V_MSG(pc == 0, 0.0f, "No points in Curve2D."); @@ -940,7 +1065,8 @@ void Curve2D::_set_data(const Dictionary &p_data) { points.write[i].position = r[i * 3 + 2]; } - baked_cache_dirty = true; + mark_dirty(); + notify_property_list_changed(); } PackedVector2Array Curve2D::tessellate(int p_max_stages, real_t p_tolerance) const { @@ -949,7 +1075,9 @@ PackedVector2Array Curve2D::tessellate(int p_max_stages, real_t p_tolerance) con if (points.size() == 0) { return tess; } - Vector<Map<real_t, Vector2>> midpoints; + + // The current implementation requires a sorted map. + Vector<RBMap<real_t, Vector2>> midpoints; midpoints.resize(points.size() - 1); @@ -978,8 +1106,67 @@ PackedVector2Array Curve2D::tessellate(int p_max_stages, real_t p_tolerance) con return tess; } +bool Curve2D::_set(const StringName &p_name, const Variant &p_value) { + Vector<String> components = String(p_name).split("/", true, 2); + if (components.size() >= 2 && components[0].begins_with("point_") && components[0].trim_prefix("point_").is_valid_int()) { + int point_index = components[0].trim_prefix("point_").to_int(); + String property = components[1]; + if (property == "position") { + set_point_position(point_index, p_value); + return true; + } else if (property == "in") { + set_point_in(point_index, p_value); + return true; + } else if (property == "out") { + set_point_out(point_index, p_value); + return true; + } + } + return false; +} + +bool Curve2D::_get(const StringName &p_name, Variant &r_ret) const { + Vector<String> components = String(p_name).split("/", true, 2); + if (components.size() >= 2 && components[0].begins_with("point_") && components[0].trim_prefix("point_").is_valid_int()) { + int point_index = components[0].trim_prefix("point_").to_int(); + String property = components[1]; + if (property == "position") { + r_ret = get_point_position(point_index); + return true; + } else if (property == "in") { + r_ret = get_point_in(point_index); + return true; + } else if (property == "out") { + r_ret = get_point_out(point_index); + return true; + } + } + return false; +} + +void Curve2D::_get_property_list(List<PropertyInfo> *p_list) const { + for (int i = 0; i < points.size(); i++) { + PropertyInfo pi = PropertyInfo(Variant::VECTOR2, vformat("point_%d/position", i)); + pi.usage &= ~PROPERTY_USAGE_STORAGE; + p_list->push_back(pi); + + if (i != 0) { + pi = PropertyInfo(Variant::VECTOR2, vformat("point_%d/in", i)); + pi.usage &= ~PROPERTY_USAGE_STORAGE; + p_list->push_back(pi); + } + + if (i != points.size() - 1) { + pi = PropertyInfo(Variant::VECTOR2, vformat("point_%d/out", i)); + pi.usage &= ~PROPERTY_USAGE_STORAGE; + p_list->push_back(pi); + } + } +} + void Curve2D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_point_count"), &Curve2D::get_point_count); + ClassDB::bind_method(D_METHOD("set_point_count", "count"), &Curve2D::set_point_count); ClassDB::bind_method(D_METHOD("add_point", "position", "in", "out", "at_position"), &Curve2D::add_point, DEFVAL(Vector2()), DEFVAL(Vector2()), DEFVAL(-1)); ClassDB::bind_method(D_METHOD("set_point_position", "idx", "position"), &Curve2D::set_point_position); ClassDB::bind_method(D_METHOD("get_point_position", "idx"), &Curve2D::get_point_position); @@ -1007,13 +1194,10 @@ void Curve2D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "bake_interval", PROPERTY_HINT_RANGE, "0.01,512,0.01"), "set_bake_interval", "get_bake_interval"); ADD_PROPERTY(PropertyInfo(Variant::INT, "_data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_data", "_get_data"); + ADD_ARRAY_COUNT("Points", "point_count", "set_point_count", "get_point_count", "point_"); } -Curve2D::Curve2D() { - /* add_point(Vector2(-1,0,0)); - add_point(Vector2(0,2,0)); - add_point(Vector2(0,3,5));*/ -} +Curve2D::Curve2D() {} /***********************************************************************************/ /***********************************************************************************/ @@ -1026,7 +1210,20 @@ int Curve3D::get_point_count() const { return points.size(); } -void Curve3D::add_point(const Vector3 &p_position, const Vector3 &p_in, const Vector3 &p_out, int p_atpos) { +void Curve3D::set_point_count(int p_count) { + ERR_FAIL_COND(p_count < 0); + if (points.size() >= p_count) { + points.resize(p_count); + mark_dirty(); + } else { + for (int i = p_count - points.size(); i > 0; i--) { + _add_point(Vector3()); + } + } + notify_property_list_changed(); +} + +void Curve3D::_add_point(const Vector3 &p_position, const Vector3 &p_in, const Vector3 &p_out, int p_atpos) { Point n; n.position = p_position; n.in = p_in; @@ -1037,16 +1234,19 @@ void Curve3D::add_point(const Vector3 &p_position, const Vector3 &p_in, const Ve points.push_back(n); } - baked_cache_dirty = true; - emit_signal(CoreStringNames::get_singleton()->changed); + mark_dirty(); +} + +void Curve3D::add_point(const Vector3 &p_position, const Vector3 &p_in, const Vector3 &p_out, int p_atpos) { + _add_point(p_position, p_in, p_out, p_atpos); + notify_property_list_changed(); } void Curve3D::set_point_position(int p_index, const Vector3 &p_position) { ERR_FAIL_INDEX(p_index, points.size()); points.write[p_index].position = p_position; - baked_cache_dirty = true; - emit_signal(CoreStringNames::get_singleton()->changed); + mark_dirty(); } Vector3 Curve3D::get_point_position(int p_index) const { @@ -1058,8 +1258,7 @@ void Curve3D::set_point_tilt(int p_index, real_t p_tilt) { ERR_FAIL_INDEX(p_index, points.size()); points.write[p_index].tilt = p_tilt; - baked_cache_dirty = true; - emit_signal(CoreStringNames::get_singleton()->changed); + mark_dirty(); } real_t Curve3D::get_point_tilt(int p_index) const { @@ -1071,8 +1270,7 @@ void Curve3D::set_point_in(int p_index, const Vector3 &p_in) { ERR_FAIL_INDEX(p_index, points.size()); points.write[p_index].in = p_in; - baked_cache_dirty = true; - emit_signal(CoreStringNames::get_singleton()->changed); + mark_dirty(); } Vector3 Curve3D::get_point_in(int p_index) const { @@ -1084,8 +1282,7 @@ void Curve3D::set_point_out(int p_index, const Vector3 &p_out) { ERR_FAIL_INDEX(p_index, points.size()); points.write[p_index].out = p_out; - baked_cache_dirty = true; - emit_signal(CoreStringNames::get_singleton()->changed); + mark_dirty(); } Vector3 Curve3D::get_point_out(int p_index) const { @@ -1093,18 +1290,22 @@ Vector3 Curve3D::get_point_out(int p_index) const { return points[p_index].out; } -void Curve3D::remove_point(int p_index) { +void Curve3D::_remove_point(int p_index) { ERR_FAIL_INDEX(p_index, points.size()); points.remove_at(p_index); - baked_cache_dirty = true; - emit_signal(CoreStringNames::get_singleton()->changed); + mark_dirty(); +} + +void Curve3D::remove_point(int p_index) { + _remove_point(p_index); + notify_property_list_changed(); } void Curve3D::clear_points() { if (!points.is_empty()) { points.clear(); - baked_cache_dirty = true; - emit_signal(CoreStringNames::get_singleton()->changed); + mark_dirty(); + notify_property_list_changed(); } } @@ -1123,7 +1324,7 @@ Vector3 Curve3D::interpolate(int p_index, real_t p_offset) const { Vector3 p3 = points[p_index + 1].position; Vector3 p2 = p3 + points[p_index + 1].in; - return _bezier_interp(p_offset, p0, p1, p2, p3); + return p0.bezier_interpolate(p1, p2, p3, p_offset); } Vector3 Curve3D::interpolatef(real_t p_findex) const { @@ -1136,11 +1337,16 @@ Vector3 Curve3D::interpolatef(real_t p_findex) const { return interpolate((int)p_findex, Math::fmod(p_findex, (real_t)1.0)); } -void Curve3D::_bake_segment3d(Map<real_t, Vector3> &r_bake, real_t p_begin, real_t p_end, const Vector3 &p_a, const Vector3 &p_out, const Vector3 &p_b, const Vector3 &p_in, int p_depth, int p_max_depth, real_t p_tol) const { +void Curve3D::mark_dirty() { + baked_cache_dirty = true; + emit_signal(CoreStringNames::get_singleton()->changed); +} + +void Curve3D::_bake_segment3d(RBMap<real_t, Vector3> &r_bake, real_t p_begin, real_t p_end, const Vector3 &p_a, const Vector3 &p_out, const Vector3 &p_b, const Vector3 &p_in, int p_depth, int p_max_depth, real_t p_tol) const { real_t mp = p_begin + (p_end - p_begin) * 0.5; - Vector3 beg = _bezier_interp(p_begin, p_a, p_a + p_out, p_b + p_in, p_b); - Vector3 mid = _bezier_interp(mp, p_a, p_a + p_out, p_b + p_in, p_b); - Vector3 end = _bezier_interp(p_end, p_a, p_a + p_out, p_b + p_in, p_b); + Vector3 beg = p_a.bezier_interpolate(p_a + p_out, p_b + p_in, p_b, p_begin); + Vector3 mid = p_a.bezier_interpolate(p_a + p_out, p_b + p_in, p_b, mp); + Vector3 end = p_a.bezier_interpolate(p_a + p_out, p_b + p_in, p_b, p_end); Vector3 na = (mid - beg).normalized(); Vector3 nb = (end - mid).normalized(); @@ -1194,6 +1400,7 @@ void Curve3D::_bake() const { List<Plane> pointlist; List<real_t> distlist; + // Start always from origin. pointlist.push_back(Plane(position, points[0].tilt)); distlist.push_back(0.0); @@ -1207,7 +1414,7 @@ void Curve3D::_bake() const { np = 1.0; } - Vector3 npp = _bezier_interp(np, points[i].position, points[i].position + points[i].out, points[i + 1].position + points[i + 1].in, points[i + 1].position); + Vector3 npp = points[i].position.bezier_interpolate(points[i].position + points[i].out, points[i + 1].position + points[i + 1].in, points[i + 1].position, np); real_t d = position.distance_to(npp); if (d > bake_interval) { @@ -1220,7 +1427,7 @@ void Curve3D::_bake() const { real_t mid = low + (hi - low) * 0.5; for (int j = 0; j < iterations; j++) { - npp = _bezier_interp(mid, points[i].position, points[i].position + points[i].out, points[i + 1].position + points[i + 1].in, points[i + 1].position); + npp = points[i].position.bezier_interpolate(points[i].position + points[i].out, points[i + 1].position + points[i + 1].in, points[i + 1].position, mid); d = position.distance_to(npp); if (bake_interval < d) { @@ -1244,16 +1451,22 @@ void Curve3D::_bake() const { p = np; } } - } - Vector3 lastpos = points[points.size() - 1].position; - real_t lastilt = points[points.size() - 1].tilt; + Vector3 npp = points[i + 1].position; + real_t d = position.distance_to(npp); + + position = npp; + Plane post; + post.normal = position; + post.d = points[i + 1].tilt; + + dist += d; + + pointlist.push_back(post); + distlist.push_back(dist); + } - real_t rem = position.distance_to(lastpos); - dist += rem; baked_max_ofs = dist; - pointlist.push_back(Plane(lastpos, lastilt)); - distlist.push_back(dist); baked_point_cache.resize(pointlist.size()); Vector3 *w = baked_point_cache.ptrw(); @@ -1328,7 +1541,7 @@ Vector3 Curve3D::interpolate_baked(real_t p_offset, bool p_cubic) const { _bake(); } - //validate// + // Validate: Curve may not have baked points. int pc = baked_point_cache.size(); ERR_FAIL_COND_V_MSG(pc == 0, Vector3(), "No points in Curve3D."); @@ -1336,18 +1549,19 @@ Vector3 Curve3D::interpolate_baked(real_t p_offset, bool p_cubic) const { return baked_point_cache.get(0); } - int bpc = baked_point_cache.size(); const Vector3 *r = baked_point_cache.ptr(); if (p_offset < 0) { return r[0]; } if (p_offset >= baked_max_ofs) { - return r[bpc - 1]; + return r[pc - 1]; } - int start = 0, end = bpc, idx = (end + start) / 2; - // binary search to find baked points + int start = 0; + int end = pc; + int idx = (end + start) / 2; + // Binary search to find baked points. while (start < idx) { real_t offset = baked_dist_cache[idx]; if (p_offset <= offset) { @@ -1368,7 +1582,7 @@ Vector3 Curve3D::interpolate_baked(real_t p_offset, bool p_cubic) const { if (p_cubic) { Vector3 pre = idx > 0 ? r[idx - 1] : r[idx]; - Vector3 post = (idx < (bpc - 2)) ? r[idx + 2] : r[idx + 1]; + Vector3 post = (idx < (pc - 2)) ? r[idx + 2] : r[idx + 1]; return r[idx].cubic_interpolate(r[idx + 1], pre, post, frac); } else { return r[idx].lerp(r[idx + 1], frac); @@ -1380,7 +1594,7 @@ real_t Curve3D::interpolate_baked_tilt(real_t p_offset) const { _bake(); } - //validate// + // Validate: Curve may not have baked tilts. int pc = baked_tilt_cache.size(); ERR_FAIL_COND_V_MSG(pc == 0, 0, "No tilts in Curve3D."); @@ -1388,29 +1602,37 @@ real_t Curve3D::interpolate_baked_tilt(real_t p_offset) const { return baked_tilt_cache.get(0); } - int bpc = baked_tilt_cache.size(); const real_t *r = baked_tilt_cache.ptr(); if (p_offset < 0) { return r[0]; } if (p_offset >= baked_max_ofs) { - return r[bpc - 1]; + return r[pc - 1]; } - int idx = Math::floor((double)p_offset / (double)bake_interval); - real_t frac = Math::fmod(p_offset, bake_interval); - - if (idx >= bpc - 1) { - return r[bpc - 1]; - } else if (idx == bpc - 2) { - if (frac > 0) { - frac /= Math::fmod(baked_max_ofs, bake_interval); + int start = 0; + int end = pc; + int idx = (end + start) / 2; + // Binary search to find baked points. + while (start < idx) { + real_t offset = baked_dist_cache[idx]; + if (p_offset <= offset) { + end = idx; + } else { + start = idx; } - } else { - frac /= bake_interval; + idx = (end + start) / 2; } + real_t offset_begin = baked_dist_cache[idx]; + real_t offset_end = baked_dist_cache[idx + 1]; + + real_t idx_interval = offset_end - offset_begin; + ERR_FAIL_COND_V_MSG(p_offset < offset_begin || p_offset > offset_end, 0, "failed to find baked segment"); + + real_t frac = (p_offset - offset_begin) / idx_interval; + return Math::lerp(r[idx], r[idx + 1], (real_t)frac); } @@ -1419,8 +1641,7 @@ Vector3 Curve3D::interpolate_baked_up_vector(real_t p_offset, bool p_apply_tilt) _bake(); } - //validate// - // curve may not have baked up vectors + // Validate: Curve may not have baked up vectors. int count = baked_up_vector_cache.size(); ERR_FAIL_COND_V_MSG(count == 0, Vector3(0, 1, 0), "No up vectors in Curve3D."); @@ -1432,10 +1653,27 @@ Vector3 Curve3D::interpolate_baked_up_vector(real_t p_offset, bool p_apply_tilt) const Vector3 *rp = baked_point_cache.ptr(); const real_t *rt = baked_tilt_cache.ptr(); - real_t offset = CLAMP(p_offset, 0.0f, baked_max_ofs); + int start = 0; + int end = count; + int idx = (end + start) / 2; + // Binary search to find baked points. + while (start < idx) { + real_t offset = baked_dist_cache[idx]; + if (p_offset <= offset) { + end = idx; + } else { + start = idx; + } + idx = (end + start) / 2; + } + + real_t offset_begin = baked_dist_cache[idx]; + real_t offset_end = baked_dist_cache[idx + 1]; + + real_t idx_interval = offset_end - offset_begin; + ERR_FAIL_COND_V_MSG(p_offset < offset_begin || p_offset > offset_end, Vector3(0, 1, 0), "failed to find baked segment"); - int idx = Math::floor((double)offset / (double)bake_interval); - real_t frac = Math::fmod(offset, bake_interval) / bake_interval; + real_t frac = (p_offset - offset_begin) / idx_interval; if (idx == count - 1) { return p_apply_tilt ? r[idx].rotated((rp[idx] - rp[idx - 1]).normalized(), rt[idx]) : r[idx]; @@ -1486,13 +1724,13 @@ PackedVector3Array Curve3D::get_baked_up_vectors() const { } Vector3 Curve3D::get_closest_point(const Vector3 &p_to_point) const { - // Brute force method + // Brute force method. if (baked_cache_dirty) { _bake(); } - //validate// + // Validate: Curve may not have baked points. int pc = baked_point_cache.size(); ERR_FAIL_COND_V_MSG(pc == 0, Vector3(), "No points in Curve3D."); @@ -1524,13 +1762,13 @@ Vector3 Curve3D::get_closest_point(const Vector3 &p_to_point) const { } real_t Curve3D::get_closest_offset(const Vector3 &p_to_point) const { - // Brute force method + // Brute force method. if (baked_cache_dirty) { _bake(); } - //validate// + // Validate: Curve may not have baked points. int pc = baked_point_cache.size(); ERR_FAIL_COND_V_MSG(pc == 0, 0.0f, "No points in Curve3D."); @@ -1566,8 +1804,7 @@ real_t Curve3D::get_closest_offset(const Vector3 &p_to_point) const { void Curve3D::set_bake_interval(real_t p_tolerance) { bake_interval = p_tolerance; - baked_cache_dirty = true; - emit_signal(CoreStringNames::get_singleton()->changed); + mark_dirty(); } real_t Curve3D::get_bake_interval() const { @@ -1576,8 +1813,7 @@ real_t Curve3D::get_bake_interval() const { void Curve3D::set_up_vector_enabled(bool p_enable) { up_vector_enabled = p_enable; - baked_cache_dirty = true; - emit_signal(CoreStringNames::get_singleton()->changed); + mark_dirty(); } bool Curve3D::is_up_vector_enabled() const { @@ -1626,7 +1862,8 @@ void Curve3D::_set_data(const Dictionary &p_data) { points.write[i].tilt = rt[i]; } - baked_cache_dirty = true; + mark_dirty(); + notify_property_list_changed(); } PackedVector3Array Curve3D::tessellate(int p_max_stages, real_t p_tolerance) const { @@ -1635,7 +1872,7 @@ PackedVector3Array Curve3D::tessellate(int p_max_stages, real_t p_tolerance) con if (points.size() == 0) { return tess; } - Vector<Map<real_t, Vector3>> midpoints; + Vector<RBMap<real_t, Vector3>> midpoints; midpoints.resize(points.size() - 1); @@ -1664,8 +1901,77 @@ PackedVector3Array Curve3D::tessellate(int p_max_stages, real_t p_tolerance) con return tess; } +bool Curve3D::_set(const StringName &p_name, const Variant &p_value) { + Vector<String> components = String(p_name).split("/", true, 2); + if (components.size() >= 2 && components[0].begins_with("point_") && components[0].trim_prefix("point_").is_valid_int()) { + int point_index = components[0].trim_prefix("point_").to_int(); + String property = components[1]; + if (property == "position") { + set_point_position(point_index, p_value); + return true; + } else if (property == "in") { + set_point_in(point_index, p_value); + return true; + } else if (property == "out") { + set_point_out(point_index, p_value); + return true; + } else if (property == "tilt") { + set_point_tilt(point_index, p_value); + return true; + } + } + return false; +} + +bool Curve3D::_get(const StringName &p_name, Variant &r_ret) const { + Vector<String> components = String(p_name).split("/", true, 2); + if (components.size() >= 2 && components[0].begins_with("point_") && components[0].trim_prefix("point_").is_valid_int()) { + int point_index = components[0].trim_prefix("point_").to_int(); + String property = components[1]; + if (property == "position") { + r_ret = get_point_position(point_index); + return true; + } else if (property == "in") { + r_ret = get_point_in(point_index); + return true; + } else if (property == "out") { + r_ret = get_point_out(point_index); + return true; + } else if (property == "tilt") { + r_ret = get_point_tilt(point_index); + return true; + } + } + return false; +} + +void Curve3D::_get_property_list(List<PropertyInfo> *p_list) const { + for (int i = 0; i < points.size(); i++) { + PropertyInfo pi = PropertyInfo(Variant::VECTOR3, vformat("point_%d/position", i)); + pi.usage &= ~PROPERTY_USAGE_STORAGE; + p_list->push_back(pi); + + if (i != 0) { + pi = PropertyInfo(Variant::VECTOR3, vformat("point_%d/in", i)); + pi.usage &= ~PROPERTY_USAGE_STORAGE; + p_list->push_back(pi); + } + + if (i != points.size() - 1) { + pi = PropertyInfo(Variant::VECTOR3, vformat("point_%d/out", i)); + pi.usage &= ~PROPERTY_USAGE_STORAGE; + p_list->push_back(pi); + } + + pi = PropertyInfo(Variant::FLOAT, vformat("point_%d/tilt", i)); + pi.usage &= ~PROPERTY_USAGE_STORAGE; + p_list->push_back(pi); + } +} + void Curve3D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_point_count"), &Curve3D::get_point_count); + ClassDB::bind_method(D_METHOD("set_point_count", "count"), &Curve3D::set_point_count); ClassDB::bind_method(D_METHOD("add_point", "position", "in", "out", "at_position"), &Curve3D::add_point, DEFVAL(Vector3()), DEFVAL(Vector3()), DEFVAL(-1)); ClassDB::bind_method(D_METHOD("set_point_position", "idx", "position"), &Curve3D::set_point_position); ClassDB::bind_method(D_METHOD("get_point_position", "idx"), &Curve3D::get_point_position); @@ -1700,13 +2006,10 @@ void Curve3D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "bake_interval", PROPERTY_HINT_RANGE, "0.01,512,0.01"), "set_bake_interval", "get_bake_interval"); ADD_PROPERTY(PropertyInfo(Variant::INT, "_data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_data", "_get_data"); + ADD_ARRAY_COUNT("Points", "point_count", "set_point_count", "get_point_count", "point_"); ADD_GROUP("Up Vector", "up_vector_"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "up_vector_enabled"), "set_up_vector_enabled", "is_up_vector_enabled"); } -Curve3D::Curve3D() { - /* add_point(Vector3(-1,0,0)); - add_point(Vector3(0,2,0)); - add_point(Vector3(0,3,5));*/ -} +Curve3D::Curve3D() {} diff --git a/scene/resources/curve.h b/scene/resources/curve.h index 767900b843..08807b1b6e 100644 --- a/scene/resources/curve.h +++ b/scene/resources/curve.h @@ -76,12 +76,13 @@ public: int get_point_count() const { return _points.size(); } + void set_point_count(int p_count); + int add_point(Vector2 p_position, real_t left_tangent = 0, real_t right_tangent = 0, TangentMode left_mode = TANGENT_FREE, TangentMode right_mode = TANGENT_FREE); - void remove_point(int p_index); void clear_points(); @@ -126,11 +127,21 @@ public: void ensure_default_setup(real_t p_min, real_t p_max); + bool _set(const StringName &p_name, const Variant &p_value); + bool _get(const StringName &p_name, Variant &r_ret) const; + void _get_property_list(List<PropertyInfo> *p_list) const; + protected: static void _bind_methods(); private: void mark_dirty(); + int _add_point(Vector2 p_position, + real_t left_tangent = 0, + real_t right_tangent = 0, + TangentMode left_mode = TANGENT_FREE, + TangentMode right_mode = TANGENT_FREE); + void _remove_point(int p_index); Vector<Point> _points; bool _baked_cache_dirty = false; @@ -164,19 +175,29 @@ class Curve2D : public Resource { mutable Vector<real_t> baked_dist_cache; mutable real_t baked_max_ofs = 0.0; + void mark_dirty(); + void _bake() const; real_t bake_interval = 5.0; - void _bake_segment2d(Map<real_t, Vector2> &r_bake, real_t p_begin, real_t p_end, const Vector2 &p_a, const Vector2 &p_out, const Vector2 &p_b, const Vector2 &p_in, int p_depth, int p_max_depth, real_t p_tol) const; + void _bake_segment2d(RBMap<real_t, Vector2> &r_bake, real_t p_begin, real_t p_end, const Vector2 &p_a, const Vector2 &p_out, const Vector2 &p_b, const Vector2 &p_in, int p_depth, int p_max_depth, real_t p_tol) const; Dictionary _get_data() const; void _set_data(const Dictionary &p_data); + bool _set(const StringName &p_name, const Variant &p_value); + bool _get(const StringName &p_name, Variant &r_ret) const; + void _get_property_list(List<PropertyInfo> *p_list) const; + + void _add_point(const Vector2 &p_position, const Vector2 &p_in = Vector2(), const Vector2 &p_out = Vector2(), int p_atpos = -1); + void _remove_point(int p_index); + protected: static void _bind_methods(); public: int get_point_count() const; + void set_point_count(int p_count); void add_point(const Vector2 &p_position, const Vector2 &p_in = Vector2(), const Vector2 &p_out = Vector2(), int p_atpos = -1); void set_point_position(int p_index, const Vector2 &p_position); Vector2 get_point_position(int p_index) const; @@ -228,20 +249,30 @@ class Curve3D : public Resource { mutable Vector<real_t> baked_dist_cache; mutable real_t baked_max_ofs = 0.0; + void mark_dirty(); + void _bake() const; real_t bake_interval = 0.2; bool up_vector_enabled = true; - void _bake_segment3d(Map<real_t, Vector3> &r_bake, real_t p_begin, real_t p_end, const Vector3 &p_a, const Vector3 &p_out, const Vector3 &p_b, const Vector3 &p_in, int p_depth, int p_max_depth, real_t p_tol) const; + void _bake_segment3d(RBMap<real_t, Vector3> &r_bake, real_t p_begin, real_t p_end, const Vector3 &p_a, const Vector3 &p_out, const Vector3 &p_b, const Vector3 &p_in, int p_depth, int p_max_depth, real_t p_tol) const; Dictionary _get_data() const; void _set_data(const Dictionary &p_data); + bool _set(const StringName &p_name, const Variant &p_value); + bool _get(const StringName &p_name, Variant &r_ret) const; + void _get_property_list(List<PropertyInfo> *p_list) const; + + void _add_point(const Vector3 &p_position, const Vector3 &p_in = Vector3(), const Vector3 &p_out = Vector3(), int p_atpos = -1); + void _remove_point(int p_index); + protected: static void _bind_methods(); public: int get_point_count() const; + void set_point_count(int p_count); void add_point(const Vector3 &p_position, const Vector3 &p_in = Vector3(), const Vector3 &p_out = Vector3(), int p_atpos = -1); void set_point_position(int p_index, const Vector3 &p_position); Vector3 get_point_position(int p_index) const; diff --git a/scene/resources/cylinder_shape_3d.cpp b/scene/resources/cylinder_shape_3d.cpp index a1fe5c46fb..345df5ffed 100644 --- a/scene/resources/cylinder_shape_3d.cpp +++ b/scene/resources/cylinder_shape_3d.cpp @@ -100,8 +100,8 @@ void CylinderShape3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_height", "height"), &CylinderShape3D::set_height); ClassDB::bind_method(D_METHOD("get_height"), &CylinderShape3D::get_height); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "height", PROPERTY_HINT_RANGE, "0.001,100,0.001,or_greater"), "set_height", "get_height"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radius", PROPERTY_HINT_RANGE, "0.001,100,0.001,or_greater"), "set_radius", "get_radius"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "height", PROPERTY_HINT_RANGE, "0.001,100,0.001,or_greater,suffix:m"), "set_height", "get_height"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radius", PROPERTY_HINT_RANGE, "0.001,100,0.001,or_greater,suffix:m"), "set_radius", "get_radius"); } CylinderShape3D::CylinderShape3D() : diff --git a/scene/resources/default_theme/default_theme.cpp b/scene/resources/default_theme/default_theme.cpp index 69b8355497..520a0a04ed 100644 --- a/scene/resources/default_theme/default_theme.cpp +++ b/scene/resources/default_theme/default_theme.cpp @@ -76,7 +76,6 @@ static Ref<StyleBoxFlat> sb_expand(Ref<StyleBoxFlat> p_sbox, float p_left, float // See also `editor_generate_icon()` in `editor/editor_themes.cpp`. static Ref<ImageTexture> generate_icon(int p_index) { - Ref<ImageTexture> icon = memnew(ImageTexture); Ref<Image> img = memnew(Image); #ifdef MODULE_SVG_ENABLED @@ -87,9 +86,8 @@ static Ref<ImageTexture> generate_icon(int p_index) { ImageLoaderSVG img_loader; img_loader.create_image_from_string(img, default_theme_icons_sources[p_index], scale, upsample, false); #endif - icon->create_from_image(img); - return icon; + return ImageTexture::create_from_image(img); } static Ref<StyleBox> make_empty_stylebox(float p_margin_left = -1, float p_margin_top = -1, float p_margin_right = -1, float p_margin_bottom = -1) { @@ -177,7 +175,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_color("icon_focus_color", "Button", Color(1, 1, 1, 1)); theme->set_color("icon_disabled_color", "Button", Color(1, 1, 1, 0.4)); - theme->set_constant("hseparation", "Button", 2 * scale); + theme->set_constant("h_separation", "Button", 2 * scale); // LinkButton @@ -230,7 +228,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_color("font_disabled_color", "OptionButton", control_font_disabled_color); theme->set_color("font_outline_color", "OptionButton", Color(1, 1, 1)); - theme->set_constant("hseparation", "OptionButton", 2 * scale); + theme->set_constant("h_separation", "OptionButton", 2 * scale); theme->set_constant("arrow_margin", "OptionButton", 4 * scale); theme->set_constant("outline_size", "OptionButton", 0); @@ -252,7 +250,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_color("font_disabled_color", "MenuButton", Color(1, 1, 1, 0.3)); theme->set_color("font_outline_color", "MenuButton", Color(1, 1, 1)); - theme->set_constant("hseparation", "MenuButton", 3 * scale); + theme->set_constant("h_separation", "MenuButton", 3 * scale); theme->set_constant("outline_size", "MenuButton", 0); // CheckBox @@ -295,8 +293,8 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_color("font_disabled_color", "CheckBox", control_font_disabled_color); theme->set_color("font_outline_color", "CheckBox", Color(1, 1, 1)); - theme->set_constant("hseparation", "CheckBox", 4 * scale); - theme->set_constant("check_vadjust", "CheckBox", 0 * scale); + theme->set_constant("h_separation", "CheckBox", 4 * scale); + theme->set_constant("check_v_adjust", "CheckBox", 0 * scale); theme->set_constant("outline_size", "CheckBox", 0); // CheckButton @@ -335,8 +333,8 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_color("font_disabled_color", "CheckButton", control_font_disabled_color); theme->set_color("font_outline_color", "CheckButton", Color(1, 1, 1)); - theme->set_constant("hseparation", "CheckButton", 4 * scale); - theme->set_constant("check_vadjust", "CheckButton", 0 * scale); + theme->set_constant("h_separation", "CheckButton", 4 * scale); + theme->set_constant("check_v_adjust", "CheckButton", 0 * scale); theme->set_constant("outline_size", "CheckButton", 0); // Label @@ -467,6 +465,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_color("completion_selected_color", "CodeEdit", Color(0.26, 0.26, 0.27)); theme->set_color("completion_existing_color", "CodeEdit", Color(0.87, 0.87, 0.87, 0.13)); theme->set_color("completion_scroll_color", "CodeEdit", control_font_pressed_color * Color(1, 1, 1, 0.29)); + theme->set_color("completion_scroll_hovered_color", "CodeEdit", control_font_pressed_color * Color(1, 1, 1, 0.4)); theme->set_color("completion_font_color", "CodeEdit", Color(0.67, 0.67, 0.67)); theme->set_color("font_color", "CodeEdit", control_font_color); theme->set_color("font_selected_color", "CodeEdit", Color(0, 0, 0)); @@ -570,7 +569,6 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const // Window theme->set_stylebox("embedded_border", "Window", sb_expand(make_flat_stylebox(style_popup_color, 10, 28, 10, 8), 8, 32, 8, 6)); - theme->set_constant("scaleborder_size", "Window", 4 * scale); theme->set_font("title_font", "Window", Ref<Font>()); theme->set_font_size("title_font_size", "Window", -1); @@ -582,8 +580,8 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_icon("close", "Window", icons["close"]); theme->set_icon("close_pressed", "Window", icons["close_hl"]); - theme->set_constant("close_h_ofs", "Window", 18 * scale); - theme->set_constant("close_v_ofs", "Window", 24 * scale); + theme->set_constant("close_h_offset", "Window", 18 * scale); + theme->set_constant("close_v_offset", "Window", 24 * scale); // Dialogs @@ -605,7 +603,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_icon("file", "FileDialog", icons["file"]); theme->set_color("folder_icon_modulate", "FileDialog", Color(1, 1, 1)); theme->set_color("file_icon_modulate", "FileDialog", Color(1, 1, 1)); - theme->set_color("files_disabled", "FileDialog", Color(0, 0, 0, 0.7)); + theme->set_color("files_disabled", "FileDialog", Color(1, 1, 1, 0.25)); // Popup @@ -665,8 +663,8 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_color("font_outline_color", "PopupMenu", Color(1, 1, 1)); theme->set_color("font_separator_outline_color", "PopupMenu", Color(1, 1, 1)); - theme->set_constant("hseparation", "PopupMenu", 4 * scale); - theme->set_constant("vseparation", "PopupMenu", 4 * scale); + theme->set_constant("h_separation", "PopupMenu", 4 * scale); + theme->set_constant("v_separation", "PopupMenu", 4 * scale); theme->set_constant("outline_size", "PopupMenu", 0); theme->set_constant("separator_outline_size", "PopupMenu", 0); theme->set_constant("item_start_padding", "PopupMenu", 2 * scale); @@ -686,13 +684,15 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const graphnode_breakpoint->set_border_color(Color(0.9, 0.29, 0.3)); Ref<StyleBoxFlat> graphnode_position = make_flat_stylebox(style_pressed_color, 18, 42, 18, 12, 6, true, 4); graphnode_position->set_border_color(Color(0.98, 0.89, 0.27)); + Ref<StyleBoxEmpty> graphnode_slot = make_empty_stylebox(0, 0, 0, 0); theme->set_stylebox("frame", "GraphNode", graphnode_normal); - theme->set_stylebox("selectedframe", "GraphNode", graphnode_selected); + theme->set_stylebox("selected_frame", "GraphNode", graphnode_selected); theme->set_stylebox("comment", "GraphNode", graphnode_comment_normal); - theme->set_stylebox("commentfocus", "GraphNode", graphnode_comment_selected); + theme->set_stylebox("comment_focus", "GraphNode", graphnode_comment_selected); theme->set_stylebox("breakpoint", "GraphNode", graphnode_breakpoint); theme->set_stylebox("position", "GraphNode", graphnode_position); + theme->set_stylebox("slot", "GraphNode", graphnode_slot); theme->set_icon("port", "GraphNode", icons["graph_port"]); theme->set_icon("close", "GraphNode", icons["close"]); @@ -704,6 +704,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_constant("separation", "GraphNode", 2 * scale); theme->set_constant("title_offset", "GraphNode", 26 * scale); theme->set_constant("close_offset", "GraphNode", 22 * scale); + theme->set_constant("close_h_offset", "GraphNode", 22 * scale); theme->set_constant("port_offset", "GraphNode", 0); // Tree @@ -746,8 +747,8 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_color("children_hl_line_color", "Tree", Color(0.27, 0.27, 0.27)); theme->set_color("custom_button_font_highlight", "Tree", control_font_hover_color); - theme->set_constant("hseparation", "Tree", 4 * scale); - theme->set_constant("vseparation", "Tree", 4 * scale); + theme->set_constant("h_separation", "Tree", 4 * scale); + theme->set_constant("v_separation", "Tree", 4 * scale); theme->set_constant("item_margin", "Tree", 16 * scale); theme->set_constant("button_margin", "Tree", 4 * scale); theme->set_constant("draw_relationship_lines", "Tree", 0); @@ -764,8 +765,8 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_stylebox("bg", "ItemList", make_flat_stylebox(style_normal_color)); theme->set_stylebox("bg_focus", "ItemList", focus); - theme->set_constant("hseparation", "ItemList", 4); - theme->set_constant("vseparation", "ItemList", 2); + theme->set_constant("h_separation", "ItemList", 4); + theme->set_constant("v_separation", "ItemList", 2); theme->set_constant("icon_margin", "ItemList", 4); theme->set_constant("line_separation", "ItemList", 2 * scale); @@ -846,7 +847,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_color("font_outline_color", "TabBar", Color(1, 1, 1)); theme->set_color("drop_mark_color", "TabBar", Color(1, 1, 1)); - theme->set_constant("hseparation", "TabBar", 4 * scale); + theme->set_constant("h_separation", "TabBar", 4 * scale); theme->set_constant("outline_size", "TabBar", 0); // Separators @@ -896,7 +897,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_color("font_disabled_color", "ColorPickerButton", Color(0.9, 0.9, 0.9, 0.3)); theme->set_color("font_outline_color", "ColorPickerButton", Color(1, 1, 1)); - theme->set_constant("hseparation", "ColorPickerButton", 2 * scale); + theme->set_constant("h_separation", "ColorPickerButton", 2 * scale); theme->set_constant("outline_size", "ColorPickerButton", 0); // ColorPresetButton @@ -915,8 +916,8 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_stylebox("panel", "TooltipPanel", make_flat_stylebox(Color(0, 0, 0, 0.5), 2 * default_margin, 0.5 * default_margin, 2 * default_margin, 0.5 * default_margin)); - theme->set_font("font", "TooltipLabel", Ref<Font>()); theme->set_font_size("font_size", "TooltipLabel", -1); + theme->set_font("font", "TooltipLabel", Ref<Font>()); theme->set_color("font_color", "TooltipLabel", control_font_color); theme->set_color("font_shadow_color", "TooltipLabel", Color(0, 0, 0, 0)); @@ -936,7 +937,6 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_font("italics_font", "RichTextLabel", italics_font); theme->set_font("bold_italics_font", "RichTextLabel", bold_italics_font); theme->set_font("mono_font", "RichTextLabel", Ref<Font>()); - theme->set_font_size("normal_font_size", "RichTextLabel", -1); theme->set_font_size("bold_font_size", "RichTextLabel", -1); theme->set_font_size("italics_font_size", "RichTextLabel", -1); @@ -956,8 +956,8 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const 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); - theme->set_constant("table_vseparation", "RichTextLabel", 3 * scale); + theme->set_constant("table_h_separation", "RichTextLabel", 3 * scale); + theme->set_constant("table_v_separation", "RichTextLabel", 3 * scale); theme->set_constant("outline_size", "RichTextLabel", 0); @@ -976,16 +976,16 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_constant("margin_top", "MarginContainer", 0 * scale); theme->set_constant("margin_right", "MarginContainer", 0 * scale); theme->set_constant("margin_bottom", "MarginContainer", 0 * scale); - theme->set_constant("hseparation", "GridContainer", 4 * scale); - theme->set_constant("vseparation", "GridContainer", 4 * scale); + theme->set_constant("h_separation", "GridContainer", 4 * scale); + theme->set_constant("v_separation", "GridContainer", 4 * scale); theme->set_constant("separation", "HSplitContainer", 12 * scale); theme->set_constant("separation", "VSplitContainer", 12 * scale); theme->set_constant("autohide", "HSplitContainer", 1 * scale); theme->set_constant("autohide", "VSplitContainer", 1 * scale); - theme->set_constant("hseparation", "HFlowContainer", 4 * scale); - theme->set_constant("vseparation", "HFlowContainer", 4 * scale); - theme->set_constant("hseparation", "VFlowContainer", 4 * scale); - theme->set_constant("vseparation", "VFlowContainer", 4 * scale); + theme->set_constant("h_separation", "HFlowContainer", 4 * scale); + theme->set_constant("v_separation", "HFlowContainer", 4 * scale); + theme->set_constant("h_separation", "VFlowContainer", 4 * scale); + theme->set_constant("v_separation", "VFlowContainer", 4 * scale); theme->set_stylebox("panel", "PanelContainer", make_flat_stylebox(style_normal_color, 0, 0, 0, 0)); @@ -1001,13 +1001,11 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_color("selection_fill", "GraphEdit", Color(1, 1, 1, 0.3)); theme->set_color("selection_stroke", "GraphEdit", Color(1, 1, 1, 0.8)); theme->set_color("activity", "GraphEdit", Color(1, 1, 1)); - theme->set_constant("bezier_len_pos", "GraphEdit", 80 * scale); - theme->set_constant("bezier_len_neg", "GraphEdit", 160 * scale); // Visual Node Ports - theme->set_constant("port_grab_distance_horizontal", "GraphEdit", 24 * scale); - theme->set_constant("port_grab_distance_vertical", "GraphEdit", 26 * scale); + theme->set_constant("port_hotzone_inner_extent", "GraphEdit", 22 * scale); + theme->set_constant("port_hotzone_outer_extent", "GraphEdit", 26 * scale); theme->set_stylebox("bg", "GraphEditMinimap", make_flat_stylebox(Color(0.24, 0.24, 0.24), 0, 0, 0, 0)); Ref<StyleBoxFlat> style_minimap_camera = make_flat_stylebox(Color(0.65, 0.65, 0.65, 0.2), 0, 0, 0, 0, 0); @@ -1026,16 +1024,16 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const default_style = make_flat_stylebox(Color(1, 0.365, 0.365), 4, 4, 4, 4, 0, false, 2); } -void make_default_theme(float p_scale, Ref<Font> p_font, TextServer::SubpixelPositioning p_subpixel, TextServer::Hinting p_hinting, bool p_aa) { +void make_default_theme(float p_scale, Ref<Font> p_font, TextServer::SubpixelPositioning p_font_subpixel, TextServer::Hinting p_font_hinting, bool p_font_antialiased, bool p_font_msdf, bool p_font_generate_mipmaps) { Ref<Theme> t; t.instantiate(); Ref<StyleBox> default_style; Ref<Texture2D> default_icon; Ref<Font> default_font; - Ref<Font> bold_font; - Ref<Font> bold_italics_font; - Ref<Font> italics_font; + Ref<FontVariation> bold_font; + Ref<FontVariation> bold_italics_font; + Ref<FontVariation> italics_font; float default_scale = CLAMP(p_scale, 0.5, 8.0); if (p_font.is_valid()) { @@ -1045,42 +1043,31 @@ void make_default_theme(float p_scale, Ref<Font> p_font, TextServer::SubpixelPos // Use the default DynamicFont (separate from the editor font). // The default DynamicFont is chosen to have a small file size since it's // embedded in both editor and export template binaries. - Ref<Font> dynamic_font; + Ref<FontFile> dynamic_font; dynamic_font.instantiate(); - - Ref<FontData> dynamic_font_data; - dynamic_font_data.instantiate(); - dynamic_font_data->set_data_ptr(_font_OpenSans_SemiBold, _font_OpenSans_SemiBold_size); - dynamic_font_data->set_subpixel_positioning(p_subpixel); - dynamic_font_data->set_hinting(p_hinting); - dynamic_font_data->set_antialiased(p_aa); - dynamic_font->add_data(dynamic_font_data); + dynamic_font->set_data_ptr(_font_OpenSans_SemiBold, _font_OpenSans_SemiBold_size); + dynamic_font->set_subpixel_positioning(p_font_subpixel); + dynamic_font->set_hinting(p_font_hinting); + dynamic_font->set_antialiased(p_font_antialiased); + dynamic_font->set_multichannel_signed_distance_field(p_font_msdf); + dynamic_font->set_generate_mipmaps(p_font_generate_mipmaps); default_font = dynamic_font; } if (default_font.is_valid()) { bold_font.instantiate(); - for (int i = 0; i < default_font->get_data_count(); i++) { - Ref<FontData> data = default_font->get_data(i)->duplicate(); - data->set_embolden(1.2); - bold_font->add_data(data); - } + bold_font->set_base_font(default_font); + bold_font->set_variation_embolden(1.2); bold_italics_font.instantiate(); - for (int i = 0; i < default_font->get_data_count(); i++) { - Ref<FontData> data = default_font->get_data(i)->duplicate(); - data->set_embolden(1.2); - data->set_transform(Transform2D(1.0, 0.4, 0.0, 1.0, 0.0, 0.0)); - bold_italics_font->add_data(data); - } + bold_italics_font->set_base_font(default_font); + bold_italics_font->set_variation_embolden(1.2); + bold_italics_font->set_variation_transform(Transform2D(1.0, 0.2, 0.0, 1.0, 0.0, 0.0)); italics_font.instantiate(); - for (int i = 0; i < default_font->get_data_count(); i++) { - Ref<FontData> data = default_font->get_data(i)->duplicate(); - data->set_transform(Transform2D(1.0, 0.4, 0.0, 1.0, 0.0, 0.0)); - italics_font->add_data(data); - } + italics_font->set_base_font(default_font); + italics_font->set_variation_transform(Transform2D(1.0, 0.2, 0.0, 1.0, 0.0, 0.0)); } fill_default_theme(t, default_font, bold_font, bold_italics_font, italics_font, default_icon, default_style, default_scale); diff --git a/scene/resources/default_theme/default_theme.h b/scene/resources/default_theme/default_theme.h index a9e21dda3f..f777330a07 100644 --- a/scene/resources/default_theme/default_theme.h +++ b/scene/resources/default_theme/default_theme.h @@ -36,7 +36,7 @@ const int default_font_size = 16; void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const Ref<Font> &bold_font, const Ref<Font> &bold_italics_font, const Ref<Font> &italics_font, Ref<Texture2D> &default_icon, Ref<StyleBox> &default_style, float p_scale); -void make_default_theme(float p_scale, Ref<Font> p_font, TextServer::SubpixelPositioning p_subpixel, TextServer::Hinting p_hinting, bool p_aa); +void make_default_theme(float p_scale, Ref<Font> p_font, TextServer::SubpixelPositioning p_font_subpixel = TextServer::SUBPIXEL_POSITIONING_AUTO, TextServer::Hinting p_font_hinting = TextServer::HINTING_LIGHT, bool p_font_antialiased = true, bool p_font_msdf = false, bool p_font_generate_mipmaps = false); void clear_default_theme(); #endif diff --git a/scene/resources/environment.cpp b/scene/resources/environment.cpp index d92d34437e..854bd34d6a 100644 --- a/scene/resources/environment.cpp +++ b/scene/resources/environment.cpp @@ -1096,7 +1096,6 @@ void Environment::_validate_property(PropertyInfo &property) const { static const char *high_end_prefixes[] = { "auto_exposure_", - "tonemap_", "ssr_", "ssao_", nullptr @@ -1179,8 +1178,8 @@ void Environment::_bind_methods() { ADD_GROUP("Sky", "sky_"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "sky", PROPERTY_HINT_RESOURCE_TYPE, "Sky"), "set_sky", "get_sky"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "sky_custom_fov", PROPERTY_HINT_RANGE, "0,180,0.1"), "set_sky_custom_fov", "get_sky_custom_fov"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "sky_rotation"), "set_sky_rotation", "get_sky_rotation"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "sky_custom_fov", PROPERTY_HINT_RANGE, "0,180,0.1,degrees"), "set_sky_custom_fov", "get_sky_custom_fov"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "sky_rotation", PROPERTY_HINT_RANGE, "-360,360,0.1,or_lesser,or_greater,radians"), "set_sky_rotation", "get_sky_rotation"); // Ambient light @@ -1299,7 +1298,7 @@ void Environment::_bind_methods() { ADD_GROUP("SSIL", "ssil_"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "ssil_enabled"), "set_ssil_enabled", "is_ssil_enabled"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "ssil_radius", PROPERTY_HINT_RANGE, "0.01,16,0.01,or_greater"), "set_ssil_radius", "get_ssil_radius"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "ssil_radius", PROPERTY_HINT_RANGE, "0.01,16,0.01,or_greater,suffix:m"), "set_ssil_radius", "get_ssil_radius"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "ssil_intensity", PROPERTY_HINT_RANGE, "0,16,0.01,or_greater"), "set_ssil_intensity", "get_ssil_intensity"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "ssil_sharpness", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_ssil_sharpness", "get_ssil_sharpness"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "ssil_normal_rejection", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_ssil_normal_rejection", "get_ssil_normal_rejection"); @@ -1426,7 +1425,7 @@ void Environment::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "fog_density", PROPERTY_HINT_RANGE, "0,16,0.0001"), "set_fog_density", "get_fog_density"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "fog_aerial_perspective", PROPERTY_HINT_RANGE, "0,1,0.001"), "set_fog_aerial_perspective", "get_fog_aerial_perspective"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "fog_height", PROPERTY_HINT_RANGE, "-1024,1024,0.01,or_lesser,or_greater"), "set_fog_height", "get_fog_height"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "fog_height", PROPERTY_HINT_RANGE, "-1024,1024,0.01,or_lesser,or_greater,suffix:m"), "set_fog_height", "get_fog_height"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "fog_height_density", PROPERTY_HINT_RANGE, "-16,16,0.0001,or_lesser,or_greater"), "set_fog_height_density", "get_fog_height_density"); ClassDB::bind_method(D_METHOD("set_volumetric_fog_enabled", "enabled"), &Environment::set_volumetric_fog_enabled); @@ -1467,7 +1466,7 @@ void Environment::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "volumetric_fog_ambient_inject", PROPERTY_HINT_RANGE, "0.0,16,0.01,exp"), "set_volumetric_fog_ambient_inject", "get_volumetric_fog_ambient_inject"); ADD_SUBGROUP("Temporal Reprojection", "volumetric_fog_temporal_reprojection_"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "volumetric_fog_temporal_reprojection_enabled"), "set_volumetric_fog_temporal_reprojection_enabled", "is_volumetric_fog_temporal_reprojection_enabled"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "volumetric_fog_temporal_reprojection_amount", PROPERTY_HINT_RANGE, "0.0,0.999,0.001"), "set_volumetric_fog_temporal_reprojection_amount", "get_volumetric_fog_temporal_reprojection_amount"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "volumetric_fog_temporal_reprojection_amount", PROPERTY_HINT_RANGE, "0.5,0.99,0.001"), "set_volumetric_fog_temporal_reprojection_amount", "get_volumetric_fog_temporal_reprojection_amount"); // Adjustment diff --git a/scene/resources/fog_material.cpp b/scene/resources/fog_material.cpp index a05ef0c779..39ade85af6 100644 --- a/scene/resources/fog_material.cpp +++ b/scene/resources/fog_material.cpp @@ -148,11 +148,11 @@ void FogMaterial::_update_shader() { shader_type fog; uniform float density : hint_range(0, 1, 0.0001) = 1.0; -uniform vec4 albedo : hint_color = vec4(1.0); -uniform vec4 emission : hint_color = vec4(0, 0, 0, 1); +uniform vec4 albedo : source_color = vec4(1.0); +uniform vec4 emission : source_color = vec4(0, 0, 0, 1); uniform float height_falloff = 0.0; uniform float edge_fade = 0.1; -uniform sampler3D density_texture: hint_white; +uniform sampler3D density_texture: hint_default_white; void fog() { diff --git a/scene/resources/font.cpp b/scene/resources/font.cpp index efbe9c93f7..f61ac7fcaa 100644 --- a/scene/resources/font.cpp +++ b/scene/resources/font.cpp @@ -30,431 +30,566 @@ #include "font.h" +#include "core/core_string_names.h" #include "core/io/image_loader.h" #include "core/io/resource_loader.h" #include "core/string/translation.h" +#include "core/templates/hash_map.h" #include "core/templates/hashfuncs.h" #include "scene/resources/text_line.h" #include "scene/resources/text_paragraph.h" +#include "scene/resources/theme.h" -_FORCE_INLINE_ void FontData::_clear_cache() { - for (int i = 0; i < cache.size(); i++) { - if (cache[i].is_valid()) { - TS->free_rid(cache[i]); - cache.write[i] = RID(); - } - } +/*************************************************************************/ +/* Font */ +/*************************************************************************/ + +void Font::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_fallbacks", "fallbacks"), &Font::set_fallbacks); + ClassDB::bind_method(D_METHOD("get_fallbacks"), &Font::get_fallbacks); + + // Output. + ClassDB::bind_method(D_METHOD("find_variation", "variation_coordinates", "face_index", "strength", "transform"), &Font::find_variation, DEFVAL(0), DEFVAL(0.0), DEFVAL(Transform2D())); + ClassDB::bind_method(D_METHOD("get_rids"), &Font::get_rids); + + // Font metrics. + ClassDB::bind_method(D_METHOD("get_height", "font_size"), &Font::get_height, DEFVAL(DEFAULT_FONT_SIZE)); + ClassDB::bind_method(D_METHOD("get_ascent", "font_size"), &Font::get_ascent, DEFVAL(DEFAULT_FONT_SIZE)); + ClassDB::bind_method(D_METHOD("get_descent", "font_size"), &Font::get_descent, DEFVAL(DEFAULT_FONT_SIZE)); + ClassDB::bind_method(D_METHOD("get_underline_position", "font_size"), &Font::get_underline_position, DEFVAL(DEFAULT_FONT_SIZE)); + ClassDB::bind_method(D_METHOD("get_underline_thickness", "font_size"), &Font::get_underline_thickness, DEFVAL(DEFAULT_FONT_SIZE)); + + ClassDB::bind_method(D_METHOD("get_font_name"), &Font::get_font_name); + ClassDB::bind_method(D_METHOD("get_font_style_name"), &Font::get_font_style_name); + ClassDB::bind_method(D_METHOD("get_font_style"), &Font::get_font_style); + + ClassDB::bind_method(D_METHOD("get_spacing", "spacing"), &Font::get_spacing); + ClassDB::bind_method(D_METHOD("get_opentype_features"), &Font::get_opentype_features); + + // Drawing string. + ClassDB::bind_method(D_METHOD("set_cache_capacity", "single_line", "multi_line"), &Font::set_cache_capacity); + + ClassDB::bind_method(D_METHOD("get_string_size", "text", "alignment", "width", "font_size", "jst_flags", "direction", "orientation"), &Font::get_string_size, DEFVAL(HORIZONTAL_ALIGNMENT_LEFT), DEFVAL(-1), DEFVAL(DEFAULT_FONT_SIZE), DEFVAL(TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND), DEFVAL(TextServer::DIRECTION_AUTO), DEFVAL(TextServer::ORIENTATION_HORIZONTAL)); + ClassDB::bind_method(D_METHOD("get_multiline_string_size", "text", "alignment", "width", "font_size", "max_lines", "brk_flags", "jst_flags", "direction", "orientation"), &Font::get_multiline_string_size, DEFVAL(HORIZONTAL_ALIGNMENT_LEFT), DEFVAL(-1), DEFVAL(DEFAULT_FONT_SIZE), DEFVAL(-1), DEFVAL(TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND), DEFVAL(TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND), DEFVAL(TextServer::DIRECTION_AUTO), DEFVAL(TextServer::ORIENTATION_HORIZONTAL)); + + ClassDB::bind_method(D_METHOD("draw_string", "canvas_item", "pos", "text", "alignment", "width", "font_size", "modulate", "jst_flags", "direction", "orientation"), &Font::draw_string, DEFVAL(HORIZONTAL_ALIGNMENT_LEFT), DEFVAL(-1), DEFVAL(DEFAULT_FONT_SIZE), DEFVAL(Color(1.0, 1.0, 1.0)), DEFVAL(TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND), DEFVAL(TextServer::DIRECTION_AUTO), DEFVAL(TextServer::ORIENTATION_HORIZONTAL)); + ClassDB::bind_method(D_METHOD("draw_multiline_string", "canvas_item", "pos", "text", "alignment", "width", "font_size", "max_lines", "modulate", "brk_flags", "jst_flags", "direction", "orientation"), &Font::draw_multiline_string, DEFVAL(HORIZONTAL_ALIGNMENT_LEFT), DEFVAL(-1), DEFVAL(DEFAULT_FONT_SIZE), DEFVAL(-1), DEFVAL(Color(1.0, 1.0, 1.0)), DEFVAL(TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND), DEFVAL(TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND), DEFVAL(TextServer::DIRECTION_AUTO), DEFVAL(TextServer::ORIENTATION_HORIZONTAL)); + + ClassDB::bind_method(D_METHOD("draw_string_outline", "canvas_item", "pos", "text", "alignment", "width", "font_size", "size", "modulate", "jst_flags", "direction", "orientation"), &Font::draw_string_outline, DEFVAL(HORIZONTAL_ALIGNMENT_LEFT), DEFVAL(-1), DEFVAL(DEFAULT_FONT_SIZE), DEFVAL(1), DEFVAL(Color(1.0, 1.0, 1.0)), DEFVAL(TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND), DEFVAL(TextServer::DIRECTION_AUTO), DEFVAL(TextServer::ORIENTATION_HORIZONTAL)); + ClassDB::bind_method(D_METHOD("draw_multiline_string_outline", "canvas_item", "pos", "text", "alignment", "width", "font_size", "max_lines", "size", "modulate", "brk_flags", "jst_flags", "direction", "orientation"), &Font::draw_multiline_string_outline, DEFVAL(HORIZONTAL_ALIGNMENT_LEFT), DEFVAL(-1), DEFVAL(DEFAULT_FONT_SIZE), DEFVAL(-1), DEFVAL(1), DEFVAL(Color(1.0, 1.0, 1.0)), DEFVAL(TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND), DEFVAL(TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND), DEFVAL(TextServer::DIRECTION_AUTO), DEFVAL(TextServer::ORIENTATION_HORIZONTAL)); + + // Drawing char. + ClassDB::bind_method(D_METHOD("get_char_size", "char"), &Font::get_char_size); + ClassDB::bind_method(D_METHOD("draw_char", "canvas_item", "pos", "char", "modulate"), &Font::draw_char, DEFVAL(Color(1.0, 1.0, 1.0))); + ClassDB::bind_method(D_METHOD("draw_char_outline", "canvas_item", "pos", "char", "size", "modulate"), &Font::draw_char_outline, DEFVAL(-1), DEFVAL(Color(1.0, 1.0, 1.0))); + + // Helper functions. + ClassDB::bind_method(D_METHOD("has_char", "char"), &Font::has_char); + ClassDB::bind_method(D_METHOD("get_supported_chars"), &Font::get_supported_chars); + + ClassDB::bind_method(D_METHOD("is_language_supported", "language"), &Font::is_language_supported); + ClassDB::bind_method(D_METHOD("is_script_supported", "script"), &Font::is_script_supported); + + ClassDB::bind_method(D_METHOD("get_supported_feature_list"), &Font::get_supported_feature_list); + ClassDB::bind_method(D_METHOD("get_supported_variation_list"), &Font::get_supported_variation_list); + ClassDB::bind_method(D_METHOD("get_face_count"), &Font::get_face_count); } -_FORCE_INLINE_ void FontData::_ensure_rid(int p_cache_index) const { - if (unlikely(p_cache_index >= cache.size())) { - cache.resize(p_cache_index + 1); - } - if (unlikely(!cache[p_cache_index].is_valid())) { - cache.write[p_cache_index] = TS->create_font(); - TS->font_set_data_ptr(cache[p_cache_index], data_ptr, data_size); - TS->font_set_antialiased(cache[p_cache_index], antialiased); - TS->font_set_multichannel_signed_distance_field(cache[p_cache_index], msdf); - TS->font_set_msdf_pixel_range(cache[p_cache_index], msdf_pixel_range); - TS->font_set_msdf_size(cache[p_cache_index], msdf_size); - TS->font_set_fixed_size(cache[p_cache_index], fixed_size); - TS->font_set_force_autohinter(cache[p_cache_index], force_autohinter); - TS->font_set_hinting(cache[p_cache_index], hinting); - TS->font_set_subpixel_positioning(cache[p_cache_index], subpixel_positioning); - TS->font_set_embolden(cache[p_cache_index], embolden); - TS->font_set_transform(cache[p_cache_index], transform); - TS->font_set_oversampling(cache[p_cache_index], oversampling); +void Font::_update_rids_fb(const Ref<Font> &p_f, int p_depth) const { + ERR_FAIL_COND(p_depth > MAX_FALLBACK_DEPTH); + if (p_f.is_valid()) { + RID rid = p_f->_get_rid(); + if (rid.is_valid()) { + rids.push_back(rid); + } + const TypedArray<Font> &_fallbacks = p_f->get_fallbacks(); + for (int i = 0; i < _fallbacks.size(); i++) { + _update_rids_fb(_fallbacks[i], p_depth + 1); + } } } -void FontData::_bind_methods() { - ClassDB::bind_method(D_METHOD("load_bitmap_font", "path"), &FontData::load_bitmap_font); - ClassDB::bind_method(D_METHOD("load_dynamic_font", "path"), &FontData::load_dynamic_font); +void Font::_update_rids() const { + rids.clear(); + _update_rids_fb(const_cast<Font *>(this), 0); + dirty_rids = false; +} - ClassDB::bind_method(D_METHOD("set_data", "data"), &FontData::set_data); - ClassDB::bind_method(D_METHOD("get_data"), &FontData::get_data); +void Font::_invalidate_rids() { + rids.clear(); + dirty_rids = true; - ClassDB::bind_method(D_METHOD("set_antialiased", "antialiased"), &FontData::set_antialiased); - ClassDB::bind_method(D_METHOD("is_antialiased"), &FontData::is_antialiased); + cache.clear(); + cache_wrap.clear(); - 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); + emit_changed(); +} - 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); +bool Font::_is_cyclic(const Ref<Font> &p_f, int p_depth) const { + ERR_FAIL_COND_V(p_depth > MAX_FALLBACK_DEPTH, false); + if (p_f.is_null()) { + return false; + } + for (int i = 0; i < p_f->fallbacks.size(); i++) { + const Ref<Font> &f = p_f->fallbacks[i]; + if (f == this) { + return true; + } + return _is_cyclic(f, p_depth + 1); + } + return false; +} - 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); +void Font::reset_state() { + _invalidate_rids(); +} - 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); +// Fallbacks. +void Font::set_fallbacks(const TypedArray<Font> &p_fallbacks) { + ERR_FAIL_COND(_is_cyclic(this, 0)); + for (int i = 0; i < fallbacks.size(); i++) { + Ref<Font> f = fallbacks[i]; + if (f.is_valid()) { + f->disconnect(CoreStringNames::get_singleton()->changed, callable_mp(this, &Font::_invalidate_rids)); + } + } + fallbacks = p_fallbacks; + for (int i = 0; i < fallbacks.size(); i++) { + Ref<Font> f = fallbacks[i]; + if (f.is_valid()) { + f->connect(CoreStringNames::get_singleton()->changed, callable_mp(this, &Font::_invalidate_rids), varray(), CONNECT_REFERENCE_COUNTED); + } + } + _invalidate_rids(); +} - ClassDB::bind_method(D_METHOD("set_msdf_pixel_range", "msdf_pixel_range"), &FontData::set_msdf_pixel_range); - ClassDB::bind_method(D_METHOD("get_msdf_pixel_range"), &FontData::get_msdf_pixel_range); +TypedArray<Font> Font::get_fallbacks() const { + return fallbacks; +} - ClassDB::bind_method(D_METHOD("set_msdf_size", "msdf_size"), &FontData::set_msdf_size); - ClassDB::bind_method(D_METHOD("get_msdf_size"), &FontData::get_msdf_size); +// Output. +TypedArray<RID> Font::get_rids() const { + if (dirty_rids) { + _update_rids(); + } + return rids; +} - ClassDB::bind_method(D_METHOD("set_fixed_size", "fixed_size"), &FontData::set_fixed_size); - ClassDB::bind_method(D_METHOD("get_fixed_size"), &FontData::get_fixed_size); +// Drawing string. +real_t Font::get_height(int p_font_size) const { + if (dirty_rids) { + _update_rids(); + } + real_t ret = 0.f; + for (int i = 0; i < rids.size(); i++) { + ret = MAX(ret, TS->font_get_ascent(rids[i], p_font_size) + TS->font_get_descent(rids[i], p_font_size)); + } + return ret + get_spacing(TextServer::SPACING_BOTTOM) + get_spacing(TextServer::SPACING_TOP); +} - ClassDB::bind_method(D_METHOD("set_force_autohinter", "force_autohinter"), &FontData::set_force_autohinter); - ClassDB::bind_method(D_METHOD("is_force_autohinter"), &FontData::is_force_autohinter); +real_t Font::get_ascent(int p_font_size) const { + if (dirty_rids) { + _update_rids(); + } + real_t ret = 0.f; + for (int i = 0; i < rids.size(); i++) { + ret = MAX(ret, TS->font_get_ascent(rids[i], p_font_size)); + } + return ret + get_spacing(TextServer::SPACING_TOP); +} - ClassDB::bind_method(D_METHOD("set_hinting", "hinting"), &FontData::set_hinting); - ClassDB::bind_method(D_METHOD("get_hinting"), &FontData::get_hinting); +real_t Font::get_descent(int p_font_size) const { + if (dirty_rids) { + _update_rids(); + } + real_t ret = 0.f; + for (int i = 0; i < rids.size(); i++) { + ret = MAX(ret, TS->font_get_descent(rids[i], p_font_size)); + } + return ret + get_spacing(TextServer::SPACING_BOTTOM); +} - ClassDB::bind_method(D_METHOD("set_subpixel_positioning", "subpixel_positioning"), &FontData::set_subpixel_positioning); - ClassDB::bind_method(D_METHOD("get_subpixel_positioning"), &FontData::get_subpixel_positioning); +real_t Font::get_underline_position(int p_font_size) const { + if (dirty_rids) { + _update_rids(); + } + real_t ret = 0.f; + for (int i = 0; i < rids.size(); i++) { + ret = MAX(ret, TS->font_get_underline_position(rids[i], p_font_size)); + } + return ret + get_spacing(TextServer::SPACING_TOP); +} - ClassDB::bind_method(D_METHOD("set_embolden", "strength"), &FontData::set_embolden); - ClassDB::bind_method(D_METHOD("get_embolden"), &FontData::get_embolden); +real_t Font::get_underline_thickness(int p_font_size) const { + if (dirty_rids) { + _update_rids(); + } + real_t ret = 0.f; + for (int i = 0; i < rids.size(); i++) { + ret = MAX(ret, TS->font_get_underline_thickness(rids[i], p_font_size)); + } + return ret; +} - ClassDB::bind_method(D_METHOD("set_transform", "transform"), &FontData::set_transform); - ClassDB::bind_method(D_METHOD("get_transform"), &FontData::get_transform); +String Font::get_font_name() const { + return TS->font_get_name(_get_rid()); +} - ClassDB::bind_method(D_METHOD("set_oversampling", "oversampling"), &FontData::set_oversampling); - ClassDB::bind_method(D_METHOD("get_oversampling"), &FontData::get_oversampling); +String Font::get_font_style_name() const { + return TS->font_get_style_name(_get_rid()); +} - ClassDB::bind_method(D_METHOD("find_cache", "variation_coordinates"), &FontData::find_cache); +BitField<TextServer::FontStyle> Font::get_font_style() const { + return TS->font_get_style(_get_rid()); +} - ClassDB::bind_method(D_METHOD("get_cache_count"), &FontData::get_cache_count); - ClassDB::bind_method(D_METHOD("clear_cache"), &FontData::clear_cache); - ClassDB::bind_method(D_METHOD("remove_cache", "cache_index"), &FontData::remove_cache); +Dictionary Font::get_opentype_features() const { + return Dictionary(); +} - ClassDB::bind_method(D_METHOD("get_size_cache_list", "cache_index"), &FontData::get_size_cache_list); - ClassDB::bind_method(D_METHOD("clear_size_cache", "cache_index"), &FontData::clear_size_cache); - ClassDB::bind_method(D_METHOD("remove_size_cache", "cache_index", "size"), &FontData::remove_size_cache); +// Drawing string. +void Font::set_cache_capacity(int p_single_line, int p_multi_line) { + cache.set_capacity(p_single_line); + cache_wrap.set_capacity(p_multi_line); +} - ClassDB::bind_method(D_METHOD("set_variation_coordinates", "cache_index", "variation_coordinates"), &FontData::set_variation_coordinates); - ClassDB::bind_method(D_METHOD("get_variation_coordinates", "cache_index"), &FontData::get_variation_coordinates); +Size2 Font::get_string_size(const String &p_text, HorizontalAlignment p_alignment, float p_width, int p_font_size, BitField<TextServer::JustificationFlag> p_jst_flags, TextServer::Direction p_direction, TextServer::Orientation p_orientation) const { + uint64_t hash = p_text.hash64(); + hash = hash_djb2_one_64(p_font_size, hash); + if (p_alignment == HORIZONTAL_ALIGNMENT_FILL) { + hash = hash_djb2_one_64(hash_murmur3_one_float(p_width), hash); + hash = hash_djb2_one_64(p_jst_flags.operator uint32_t(), hash); + } + hash = hash_djb2_one_64(p_direction, hash); + hash = hash_djb2_one_64(p_orientation, hash); - ClassDB::bind_method(D_METHOD("set_ascent", "cache_index", "size", "ascent"), &FontData::set_ascent); - ClassDB::bind_method(D_METHOD("get_ascent", "cache_index", "size"), &FontData::get_ascent); + Ref<TextLine> buffer; + if (cache.has(hash)) { + buffer = cache.get(hash); + } else { + buffer.instantiate(); + buffer->set_direction(p_direction); + buffer->set_orientation(p_orientation); + buffer->add_string(p_text, Ref<Font>(this), p_font_size); + if (p_alignment == HORIZONTAL_ALIGNMENT_FILL) { + buffer->set_horizontal_alignment(p_alignment); + buffer->set_width(p_width); + buffer->set_flags(p_jst_flags); + } + cache.insert(hash, buffer); + } + return buffer->get_size(); +} - ClassDB::bind_method(D_METHOD("set_descent", "cache_index", "size", "descent"), &FontData::set_descent); - ClassDB::bind_method(D_METHOD("get_descent", "cache_index", "size"), &FontData::get_descent); +Size2 Font::get_multiline_string_size(const String &p_text, HorizontalAlignment p_alignment, float p_width, int p_font_size, int p_max_lines, BitField<TextServer::LineBreakFlag> p_brk_flags, BitField<TextServer::JustificationFlag> p_jst_flags, TextServer::Direction p_direction, TextServer::Orientation p_orientation) const { + uint64_t hash = p_text.hash64(); + hash = hash_djb2_one_64(p_font_size, hash); + hash = hash_djb2_one_64(hash_murmur3_one_float(p_width), hash); + hash = hash_djb2_one_64(p_brk_flags.operator uint32_t(), hash); + hash = hash_djb2_one_64(p_jst_flags.operator uint32_t(), hash); + hash = hash_djb2_one_64(p_direction, hash); + hash = hash_djb2_one_64(p_orientation, hash); - ClassDB::bind_method(D_METHOD("set_underline_position", "cache_index", "size", "underline_position"), &FontData::set_underline_position); - ClassDB::bind_method(D_METHOD("get_underline_position", "cache_index", "size"), &FontData::get_underline_position); + Ref<TextParagraph> lines_buffer; + if (cache_wrap.has(hash)) { + lines_buffer = cache_wrap.get(hash); + } else { + lines_buffer.instantiate(); + lines_buffer->set_direction(p_direction); + lines_buffer->set_orientation(p_orientation); + lines_buffer->add_string(p_text, Ref<Font>(this), p_font_size); + lines_buffer->set_width(p_width); + lines_buffer->set_break_flags(p_brk_flags); + lines_buffer->set_justification_flags(p_jst_flags); + cache_wrap.insert(hash, lines_buffer); + } - ClassDB::bind_method(D_METHOD("set_underline_thickness", "cache_index", "size", "underline_thickness"), &FontData::set_underline_thickness); - ClassDB::bind_method(D_METHOD("get_underline_thickness", "cache_index", "size"), &FontData::get_underline_thickness); + lines_buffer->set_alignment(p_alignment); + lines_buffer->set_max_lines_visible(p_max_lines); - ClassDB::bind_method(D_METHOD("set_scale", "cache_index", "size", "scale"), &FontData::set_scale); - ClassDB::bind_method(D_METHOD("get_scale", "cache_index", "size"), &FontData::get_scale); + return lines_buffer->get_size(); +} - ClassDB::bind_method(D_METHOD("set_spacing", "cache_index", "size", "spacing_type", "value"), &FontData::set_spacing); - ClassDB::bind_method(D_METHOD("get_spacing", "cache_index", "size", "spacing_type"), &FontData::get_spacing); +void Font::draw_string(RID p_canvas_item, const Point2 &p_pos, const String &p_text, HorizontalAlignment p_alignment, float p_width, int p_font_size, const Color &p_modulate, BitField<TextServer::JustificationFlag> p_jst_flags, TextServer::Direction p_direction, TextServer::Orientation p_orientation) const { + uint64_t hash = p_text.hash64(); + hash = hash_djb2_one_64(p_font_size, hash); + if (p_alignment == HORIZONTAL_ALIGNMENT_FILL) { + hash = hash_djb2_one_64(hash_murmur3_one_float(p_width), hash); + hash = hash_djb2_one_64(p_jst_flags.operator uint32_t(), hash); + } + hash = hash_djb2_one_64(p_direction, hash); + hash = hash_djb2_one_64(p_orientation, hash); - ClassDB::bind_method(D_METHOD("get_texture_count", "cache_index", "size"), &FontData::get_texture_count); - ClassDB::bind_method(D_METHOD("clear_textures", "cache_index", "size"), &FontData::clear_textures); - ClassDB::bind_method(D_METHOD("remove_texture", "cache_index", "size", "texture_index"), &FontData::remove_texture); + Ref<TextLine> buffer; + if (cache.has(hash)) { + buffer = cache.get(hash); + } else { + buffer.instantiate(); + buffer->set_direction(p_direction); + buffer->set_orientation(p_orientation); + buffer->add_string(p_text, Ref<Font>(this), p_font_size); + cache.insert(hash, buffer); + } - ClassDB::bind_method(D_METHOD("set_texture_image", "cache_index", "size", "texture_index", "image"), &FontData::set_texture_image); - ClassDB::bind_method(D_METHOD("get_texture_image", "cache_index", "size", "texture_index"), &FontData::get_texture_image); + Vector2 ofs = p_pos; + if (p_orientation == TextServer::ORIENTATION_HORIZONTAL) { + ofs.y -= buffer->get_line_ascent(); + } else { + ofs.x -= buffer->get_line_ascent(); + } - ClassDB::bind_method(D_METHOD("set_texture_offsets", "cache_index", "size", "texture_index", "offset"), &FontData::set_texture_offsets); - ClassDB::bind_method(D_METHOD("get_texture_offsets", "cache_index", "size", "texture_index"), &FontData::get_texture_offsets); + buffer->set_width(p_width); + buffer->set_horizontal_alignment(p_alignment); + if (p_alignment == HORIZONTAL_ALIGNMENT_FILL) { + buffer->set_flags(p_jst_flags); + } - ClassDB::bind_method(D_METHOD("get_glyph_list", "cache_index", "size"), &FontData::get_glyph_list); - ClassDB::bind_method(D_METHOD("clear_glyphs", "cache_index", "size"), &FontData::clear_glyphs); - ClassDB::bind_method(D_METHOD("remove_glyph", "cache_index", "size", "glyph"), &FontData::remove_glyph); + buffer->draw(p_canvas_item, ofs, p_modulate); +} - ClassDB::bind_method(D_METHOD("set_glyph_advance", "cache_index", "size", "glyph", "advance"), &FontData::set_glyph_advance); - ClassDB::bind_method(D_METHOD("get_glyph_advance", "cache_index", "size", "glyph"), &FontData::get_glyph_advance); +void Font::draw_multiline_string(RID p_canvas_item, const Point2 &p_pos, const String &p_text, HorizontalAlignment p_alignment, float p_width, int p_font_size, int p_max_lines, const Color &p_modulate, BitField<TextServer::LineBreakFlag> p_brk_flags, BitField<TextServer::JustificationFlag> p_jst_flags, TextServer::Direction p_direction, TextServer::Orientation p_orientation) const { + uint64_t hash = p_text.hash64(); + hash = hash_djb2_one_64(p_font_size, hash); + hash = hash_djb2_one_64(hash_murmur3_one_float(p_width), hash); + hash = hash_djb2_one_64(p_brk_flags.operator uint32_t(), hash); + hash = hash_djb2_one_64(p_jst_flags.operator uint32_t(), hash); + hash = hash_djb2_one_64(p_direction, hash); + hash = hash_djb2_one_64(p_orientation, hash); - ClassDB::bind_method(D_METHOD("set_glyph_offset", "cache_index", "size", "glyph", "offset"), &FontData::set_glyph_offset); - ClassDB::bind_method(D_METHOD("get_glyph_offset", "cache_index", "size", "glyph"), &FontData::get_glyph_offset); + Ref<TextParagraph> lines_buffer; + if (cache_wrap.has(hash)) { + lines_buffer = cache_wrap.get(hash); + } else { + lines_buffer.instantiate(); + lines_buffer->set_direction(p_direction); + lines_buffer->set_orientation(p_orientation); + lines_buffer->add_string(p_text, Ref<Font>(this), p_font_size); + lines_buffer->set_width(p_width); + lines_buffer->set_break_flags(p_brk_flags); + lines_buffer->set_justification_flags(p_jst_flags); + cache_wrap.insert(hash, lines_buffer); + } - ClassDB::bind_method(D_METHOD("set_glyph_size", "cache_index", "size", "glyph", "gl_size"), &FontData::set_glyph_size); - ClassDB::bind_method(D_METHOD("get_glyph_size", "cache_index", "size", "glyph"), &FontData::get_glyph_size); + Vector2 ofs = p_pos; + if (p_orientation == TextServer::ORIENTATION_HORIZONTAL) { + ofs.y -= lines_buffer->get_line_ascent(0); + } else { + ofs.x -= lines_buffer->get_line_ascent(0); + } - ClassDB::bind_method(D_METHOD("set_glyph_uv_rect", "cache_index", "size", "glyph", "uv_rect"), &FontData::set_glyph_uv_rect); - ClassDB::bind_method(D_METHOD("get_glyph_uv_rect", "cache_index", "size", "glyph"), &FontData::get_glyph_uv_rect); + lines_buffer->set_alignment(p_alignment); + lines_buffer->set_max_lines_visible(p_max_lines); - ClassDB::bind_method(D_METHOD("set_glyph_texture_idx", "cache_index", "size", "glyph", "texture_idx"), &FontData::set_glyph_texture_idx); - ClassDB::bind_method(D_METHOD("get_glyph_texture_idx", "cache_index", "size", "glyph"), &FontData::get_glyph_texture_idx); + lines_buffer->draw(p_canvas_item, ofs, p_modulate); +} - ClassDB::bind_method(D_METHOD("get_kerning_list", "cache_index", "size"), &FontData::get_kerning_list); - ClassDB::bind_method(D_METHOD("clear_kerning_map", "cache_index", "size"), &FontData::clear_kerning_map); - ClassDB::bind_method(D_METHOD("remove_kerning", "cache_index", "size", "glyph_pair"), &FontData::remove_kerning); +void Font::draw_string_outline(RID p_canvas_item, const Point2 &p_pos, const String &p_text, HorizontalAlignment p_alignment, float p_width, int p_font_size, int p_size, const Color &p_modulate, BitField<TextServer::JustificationFlag> p_jst_flags, TextServer::Direction p_direction, TextServer::Orientation p_orientation) const { + uint64_t hash = p_text.hash64(); + hash = hash_djb2_one_64(p_font_size, hash); + if (p_alignment == HORIZONTAL_ALIGNMENT_FILL) { + hash = hash_djb2_one_64(hash_murmur3_one_float(p_width), hash); + hash = hash_djb2_one_64(p_jst_flags.operator uint32_t(), hash); + } + hash = hash_djb2_one_64(p_direction, hash); + hash = hash_djb2_one_64(p_orientation, hash); - ClassDB::bind_method(D_METHOD("set_kerning", "cache_index", "size", "glyph_pair", "kerning"), &FontData::set_kerning); - ClassDB::bind_method(D_METHOD("get_kerning", "cache_index", "size", "glyph_pair"), &FontData::get_kerning); + Ref<TextLine> buffer; + if (cache.has(hash)) { + buffer = cache.get(hash); + } else { + buffer.instantiate(); + buffer->set_direction(p_direction); + buffer->set_orientation(p_orientation); + buffer->add_string(p_text, Ref<Font>(this), p_font_size); + cache.insert(hash, buffer); + } - ClassDB::bind_method(D_METHOD("render_range", "cache_index", "size", "start", "end"), &FontData::render_range); - ClassDB::bind_method(D_METHOD("render_glyph", "cache_index", "size", "index"), &FontData::render_glyph); + Vector2 ofs = p_pos; + if (p_orientation == TextServer::ORIENTATION_HORIZONTAL) { + ofs.y -= buffer->get_line_ascent(); + } else { + ofs.x -= buffer->get_line_ascent(); + } - ClassDB::bind_method(D_METHOD("get_cache_rid", "cache_index"), &FontData::get_cache_rid); + buffer->set_width(p_width); + buffer->set_horizontal_alignment(p_alignment); + if (p_alignment == HORIZONTAL_ALIGNMENT_FILL) { + buffer->set_flags(p_jst_flags); + } - ClassDB::bind_method(D_METHOD("is_language_supported", "language"), &FontData::is_language_supported); - ClassDB::bind_method(D_METHOD("set_language_support_override", "language", "supported"), &FontData::set_language_support_override); - ClassDB::bind_method(D_METHOD("get_language_support_override", "language"), &FontData::get_language_support_override); - ClassDB::bind_method(D_METHOD("remove_language_support_override", "language"), &FontData::remove_language_support_override); - ClassDB::bind_method(D_METHOD("get_language_support_overrides"), &FontData::get_language_support_overrides); + buffer->draw_outline(p_canvas_item, ofs, p_size, p_modulate); +} - ClassDB::bind_method(D_METHOD("is_script_supported", "script"), &FontData::is_script_supported); - ClassDB::bind_method(D_METHOD("set_script_support_override", "script", "supported"), &FontData::set_script_support_override); - ClassDB::bind_method(D_METHOD("get_script_support_override", "script"), &FontData::get_script_support_override); - ClassDB::bind_method(D_METHOD("remove_script_support_override", "script"), &FontData::remove_script_support_override); - ClassDB::bind_method(D_METHOD("get_script_support_overrides"), &FontData::get_script_support_overrides); +void Font::draw_multiline_string_outline(RID p_canvas_item, const Point2 &p_pos, const String &p_text, HorizontalAlignment p_alignment, float p_width, int p_font_size, int p_max_lines, int p_size, const Color &p_modulate, BitField<TextServer::LineBreakFlag> p_brk_flags, BitField<TextServer::JustificationFlag> p_jst_flags, TextServer::Direction p_direction, TextServer::Orientation p_orientation) const { + uint64_t hash = p_text.hash64(); + hash = hash_djb2_one_64(p_font_size, hash); + hash = hash_djb2_one_64(hash_murmur3_one_float(p_width), hash); + hash = hash_djb2_one_64(p_brk_flags.operator uint32_t(), hash); + hash = hash_djb2_one_64(p_jst_flags.operator uint32_t(), hash); + hash = hash_djb2_one_64(p_direction, hash); + hash = hash_djb2_one_64(p_orientation, hash); - ClassDB::bind_method(D_METHOD("set_opentype_feature_overrides", "overrides"), &FontData::set_opentype_feature_overrides); - ClassDB::bind_method(D_METHOD("get_opentype_feature_overrides"), &FontData::get_opentype_feature_overrides); + Ref<TextParagraph> lines_buffer; + if (cache_wrap.has(hash)) { + lines_buffer = cache_wrap.get(hash); + } else { + lines_buffer.instantiate(); + lines_buffer->set_direction(p_direction); + lines_buffer->set_orientation(p_orientation); + lines_buffer->add_string(p_text, Ref<Font>(this), p_font_size); + lines_buffer->set_width(p_width); + lines_buffer->set_break_flags(p_brk_flags); + lines_buffer->set_justification_flags(p_jst_flags); + cache_wrap.insert(hash, lines_buffer); + } - ClassDB::bind_method(D_METHOD("has_char", "char"), &FontData::has_char); - ClassDB::bind_method(D_METHOD("get_supported_chars"), &FontData::get_supported_chars); + Vector2 ofs = p_pos; + if (p_orientation == TextServer::ORIENTATION_HORIZONTAL) { + ofs.y -= lines_buffer->get_line_ascent(0); + } else { + ofs.x -= lines_buffer->get_line_ascent(0); + } - ClassDB::bind_method(D_METHOD("get_glyph_index", "size", "char", "variation_selector"), &FontData::get_glyph_index); + lines_buffer->set_alignment(p_alignment); + lines_buffer->set_max_lines_visible(p_max_lines); - ClassDB::bind_method(D_METHOD("get_supported_feature_list"), &FontData::get_supported_feature_list); - ClassDB::bind_method(D_METHOD("get_supported_variation_list"), &FontData::get_supported_variation_list); + lines_buffer->draw_outline(p_canvas_item, ofs, p_size, p_modulate); +} - ADD_PROPERTY(PropertyInfo(Variant::PACKED_BYTE_ARRAY, "data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE), "set_data", "get_data"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "antialiased", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE), "set_antialiased", "is_antialiased"); - ADD_PROPERTY(PropertyInfo(Variant::STRING, "font_name", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE), "set_font_name", "get_font_name"); - ADD_PROPERTY(PropertyInfo(Variant::STRING, "style_name", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE), "set_font_style_name", "get_font_style_name"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "font_style", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE), "set_font_style", "get_font_style"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "subpixel_positioning", PROPERTY_HINT_ENUM, "Disabled,Auto,One half of a pixel,One quarter of a pixel", PROPERTY_USAGE_STORAGE), "set_subpixel_positioning", "get_subpixel_positioning"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "embolden", PROPERTY_HINT_RANGE, "-2,2,0.01", PROPERTY_USAGE_STORAGE), "set_embolden", "get_embolden"); - ADD_PROPERTY(PropertyInfo(Variant::TRANSFORM2D, "transform", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE), "set_transform", "get_transform"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "multichannel_signed_distance_field", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE), "set_multichannel_signed_distance_field", "is_multichannel_signed_distance_field"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "msdf_pixel_range", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE), "set_msdf_pixel_range", "get_msdf_pixel_range"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "msdf_size", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE), "set_msdf_size", "get_msdf_size"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "force_autohinter", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE), "set_force_autohinter", "is_force_autohinter"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "hinting", PROPERTY_HINT_ENUM, "None,Light,Normal", PROPERTY_USAGE_STORAGE), "set_hinting", "get_hinting"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "oversampling", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE), "set_oversampling", "get_oversampling"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "fixed_size", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE), "set_fixed_size", "get_fixed_size"); - ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "opentype_feature_overrides", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE), "set_opentype_feature_overrides", "get_opentype_feature_overrides"); +// Drawing char. +Size2 Font::get_char_size(char32_t p_char, int p_font_size) const { + if (dirty_rids) { + _update_rids(); + } + for (int i = 0; i < rids.size(); i++) { + if (TS->font_has_char(rids[i], p_char)) { + int32_t glyph = TS->font_get_glyph_index(rids[i], p_font_size, p_char, 0); + return Size2(TS->font_get_glyph_advance(rids[i], p_font_size, glyph).x, get_height(p_font_size)); + } + } + return Size2(); } -bool FontData::_set(const StringName &p_name, const Variant &p_value) { - Vector<String> tokens = p_name.operator String().split("/"); - if (tokens.size() == 2 && tokens[0] == "language_support_override") { - String lang = tokens[1]; - set_language_support_override(lang, p_value); - return true; - } else if (tokens.size() == 2 && tokens[0] == "script_support_override") { - String script = tokens[1]; - set_script_support_override(script, p_value); - return true; - } else if (tokens.size() >= 3 && tokens[0] == "cache") { - int cache_index = tokens[1].to_int(); - if (tokens.size() == 3 && tokens[2] == "variation_coordinates") { - set_variation_coordinates(cache_index, p_value); - return true; +real_t Font::draw_char(RID p_canvas_item, const Point2 &p_pos, char32_t p_char, int p_font_size, const Color &p_modulate) const { + if (dirty_rids) { + _update_rids(); + } + for (int i = 0; i < rids.size(); i++) { + if (TS->font_has_char(rids[i], p_char)) { + int32_t glyph = TS->font_get_glyph_index(rids[i], p_font_size, p_char, 0); + TS->font_draw_glyph(rids[i], p_canvas_item, p_font_size, p_pos, glyph, p_modulate); + return TS->font_get_glyph_advance(rids[i], p_font_size, glyph).x; } - if (tokens.size() >= 5) { - Vector2i sz = Vector2i(tokens[2].to_int(), tokens[3].to_int()); - if (tokens[4] == "ascent") { - set_ascent(cache_index, sz.x, p_value); - return true; - } else if (tokens[4] == "descent") { - set_descent(cache_index, sz.x, p_value); - return true; - } else if (tokens[4] == "underline_position") { - set_underline_position(cache_index, sz.x, p_value); - return true; - } else if (tokens[4] == "underline_thickness") { - set_underline_thickness(cache_index, sz.x, p_value); - return true; - } else if (tokens[4] == "scale") { - set_scale(cache_index, sz.x, p_value); - return true; - } else if (tokens[4] == "spacing_glyph") { - set_spacing(cache_index, sz.x, TextServer::SPACING_GLYPH, p_value); - return true; - } else if (tokens[4] == "spacing_space") { - set_spacing(cache_index, sz.x, TextServer::SPACING_SPACE, p_value); - return true; - } else if (tokens.size() == 7 && tokens[4] == "textures") { - int texture_index = tokens[5].to_int(); - if (tokens[6] == "image") { - set_texture_image(cache_index, sz, texture_index, p_value); - return true; - } else if (tokens[6] == "offsets") { - set_texture_offsets(cache_index, sz, texture_index, p_value); - return true; - } - } else if (tokens.size() == 7 && tokens[4] == "glyphs") { - int32_t glyph_index = tokens[5].to_int(); - if (tokens[6] == "advance") { - set_glyph_advance(cache_index, sz.x, glyph_index, p_value); - return true; - } else if (tokens[6] == "offset") { - set_glyph_offset(cache_index, sz, glyph_index, p_value); - return true; - } else if (tokens[6] == "size") { - set_glyph_size(cache_index, sz, glyph_index, p_value); - return true; - } else if (tokens[6] == "uv_rect") { - set_glyph_uv_rect(cache_index, sz, glyph_index, p_value); - return true; - } else if (tokens[6] == "texture_idx") { - set_glyph_texture_idx(cache_index, sz, glyph_index, p_value); - return true; - } - } else if (tokens.size() == 7 && tokens[4] == "kerning_overrides") { - Vector2i gp = Vector2i(tokens[5].to_int(), tokens[6].to_int()); - set_kerning(cache_index, sz.x, gp, p_value); - return true; - } + } + return 0.f; +} + +real_t Font::draw_char_outline(RID p_canvas_item, const Point2 &p_pos, char32_t p_char, int p_font_size, int p_size, const Color &p_modulate) const { + if (dirty_rids) { + _update_rids(); + } + for (int i = 0; i < rids.size(); i++) { + if (TS->font_has_char(rids[i], p_char)) { + int32_t glyph = TS->font_get_glyph_index(rids[i], p_font_size, p_char, 0); + TS->font_draw_glyph_outline(rids[i], p_canvas_item, p_font_size, p_size, p_pos, glyph, p_modulate); + return TS->font_get_glyph_advance(rids[i], p_font_size, glyph).x; } } - return false; + return 0.f; } -bool FontData::_get(const StringName &p_name, Variant &r_ret) const { - Vector<String> tokens = p_name.operator String().split("/"); - if (tokens.size() == 2 && tokens[0] == "language_support_override") { - String lang = tokens[1]; - r_ret = get_language_support_override(lang); - return true; - } else if (tokens.size() == 2 && tokens[0] == "script_support_override") { - String script = tokens[1]; - r_ret = get_script_support_override(script); - return true; - } else if (tokens.size() >= 3 && tokens[0] == "cache") { - int cache_index = tokens[1].to_int(); - if (tokens.size() == 3 && tokens[2] == "variation_coordinates") { - r_ret = get_variation_coordinates(cache_index); +// Helper functions. +bool Font::has_char(char32_t p_char) const { + if (dirty_rids) { + _update_rids(); + } + for (int i = 0; i < rids.size(); i++) { + if (TS->font_has_char(rids[i], p_char)) { return true; } - if (tokens.size() >= 5) { - Vector2i sz = Vector2i(tokens[2].to_int(), tokens[3].to_int()); - if (tokens[4] == "ascent") { - r_ret = get_ascent(cache_index, sz.x); - return true; - } else if (tokens[4] == "descent") { - r_ret = get_descent(cache_index, sz.x); - return true; - } else if (tokens[4] == "underline_position") { - r_ret = get_underline_position(cache_index, sz.x); - return true; - } else if (tokens[4] == "underline_thickness") { - r_ret = get_underline_thickness(cache_index, sz.x); - return true; - } else if (tokens[4] == "scale") { - r_ret = get_scale(cache_index, sz.x); - return true; - } else if (tokens[4] == "spacing_glyph") { - r_ret = get_spacing(cache_index, sz.x, TextServer::SPACING_GLYPH); - return true; - } else if (tokens[4] == "spacing_space") { - r_ret = get_spacing(cache_index, sz.x, TextServer::SPACING_SPACE); - return true; - } else if (tokens.size() == 7 && tokens[4] == "textures") { - int texture_index = tokens[5].to_int(); - if (tokens[6] == "image") { - r_ret = get_texture_image(cache_index, sz, texture_index); - return true; - } else if (tokens[6] == "offsets") { - r_ret = get_texture_offsets(cache_index, sz, texture_index); - return true; - } - } else if (tokens.size() == 7 && tokens[4] == "glyphs") { - int32_t glyph_index = tokens[5].to_int(); - if (tokens[6] == "advance") { - r_ret = get_glyph_advance(cache_index, sz.x, glyph_index); - return true; - } else if (tokens[6] == "offset") { - r_ret = get_glyph_offset(cache_index, sz, glyph_index); - return true; - } else if (tokens[6] == "size") { - r_ret = get_glyph_size(cache_index, sz, glyph_index); - return true; - } else if (tokens[6] == "uv_rect") { - r_ret = get_glyph_uv_rect(cache_index, sz, glyph_index); - return true; - } else if (tokens[6] == "texture_idx") { - r_ret = get_glyph_texture_idx(cache_index, sz, glyph_index); - return true; - } - } else if (tokens.size() == 7 && tokens[4] == "kerning_overrides") { - Vector2i gp = Vector2i(tokens[5].to_int(), tokens[6].to_int()); - r_ret = get_kerning(cache_index, sz.x, gp); - return true; - } - } } return false; } -void FontData::_get_property_list(List<PropertyInfo> *p_list) const { - Vector<String> lang_over = get_language_support_overrides(); - for (int i = 0; i < lang_over.size(); i++) { - p_list->push_back(PropertyInfo(Variant::BOOL, "language_support_override/" + lang_over[i], PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE)); - } - Vector<String> scr_over = get_script_support_overrides(); - for (int i = 0; i < scr_over.size(); i++) { - p_list->push_back(PropertyInfo(Variant::BOOL, "script_support_override/" + scr_over[i], PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE)); +String Font::get_supported_chars() const { + if (dirty_rids) { + _update_rids(); } - for (int i = 0; i < cache.size(); i++) { - String prefix = "cache/" + itos(i) + "/"; - Array sizes = get_size_cache_list(i); - p_list->push_back(PropertyInfo(Variant::DICTIONARY, prefix + "variation_coordinates", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE)); - for (int j = 0; j < sizes.size(); j++) { - Vector2i sz = sizes[j]; - String prefix_sz = prefix + itos(sz.x) + "/" + itos(sz.y) + "/"; - if (sz.y == 0) { - p_list->push_back(PropertyInfo(Variant::FLOAT, prefix_sz + "ascent", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE)); - p_list->push_back(PropertyInfo(Variant::FLOAT, prefix_sz + "descent", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE)); - p_list->push_back(PropertyInfo(Variant::FLOAT, prefix_sz + "underline_position", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE)); - p_list->push_back(PropertyInfo(Variant::FLOAT, prefix_sz + "underline_thickness", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE)); - p_list->push_back(PropertyInfo(Variant::FLOAT, prefix_sz + "scale", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE)); - p_list->push_back(PropertyInfo(Variant::BOOL, prefix_sz + "spacing_glyph", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE)); - p_list->push_back(PropertyInfo(Variant::BOOL, prefix_sz + "spacing_space", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE)); - } - - int tx_cnt = get_texture_count(i, sz); - for (int k = 0; k < tx_cnt; k++) { - p_list->push_back(PropertyInfo(Variant::PACKED_INT32_ARRAY, prefix_sz + "textures/" + itos(k) + "/offsets", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE)); - p_list->push_back(PropertyInfo(Variant::OBJECT, prefix_sz + "textures/" + itos(k) + "/image", PROPERTY_HINT_RESOURCE_TYPE, "Image", PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_RESOURCE_NOT_PERSISTENT)); - } - Array glyphs = get_glyph_list(i, sz); - for (int k = 0; k < glyphs.size(); k++) { - const int32_t &gl = glyphs[k]; - if (sz.y == 0) { - p_list->push_back(PropertyInfo(Variant::VECTOR2, prefix_sz + "glyphs/" + itos(gl) + "/advance", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE)); - } - p_list->push_back(PropertyInfo(Variant::VECTOR2, prefix_sz + "glyphs/" + itos(gl) + "/offset", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE)); - p_list->push_back(PropertyInfo(Variant::VECTOR2, prefix_sz + "glyphs/" + itos(gl) + "/size", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE)); - p_list->push_back(PropertyInfo(Variant::RECT2, prefix_sz + "glyphs/" + itos(gl) + "/uv_rect", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE)); - p_list->push_back(PropertyInfo(Variant::INT, prefix_sz + "glyphs/" + itos(gl) + "/texture_idx", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE)); - } - if (sz.y == 0) { - Array kerning_map = get_kerning_list(i, sz.x); - for (int k = 0; k < kerning_map.size(); k++) { - const Vector2i &gl_pair = kerning_map[k]; - p_list->push_back(PropertyInfo(Variant::VECTOR2, prefix_sz + "kerning_overrides/" + itos(gl_pair.x) + "/" + itos(gl_pair.y), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE)); - } + String chars; + for (int i = 0; i < rids.size(); i++) { + String data_chars = TS->font_get_supported_chars(rids[i]); + for (int j = 0; j < data_chars.length(); j++) { + if (chars.find_char(data_chars[j]) == -1) { + chars += data_chars[j]; } } } + return chars; } -void FontData::reset_state() { - _clear_cache(); - data.clear(); - data_ptr = nullptr; - data_size = 0; - cache.clear(); +bool Font::is_language_supported(const String &p_language) const { + return TS->font_is_language_supported(_get_rid(), p_language); +} - antialiased = true; - msdf = false; - force_autohinter = false; - hinting = TextServer::HINTING_LIGHT; - subpixel_positioning = TextServer::SUBPIXEL_POSITIONING_DISABLED; - msdf_pixel_range = 14; - msdf_size = 128; - fixed_size = 0; - embolden = 0.f; - transform = Transform2D(); - oversampling = 0.f; +bool Font::is_script_supported(const String &p_script) const { + return TS->font_is_script_supported(_get_rid(), p_script); +} + +Dictionary Font::get_supported_feature_list() const { + return TS->font_supported_feature_list(_get_rid()); +} + +Dictionary Font::get_supported_variation_list() const { + return TS->font_supported_variation_list(_get_rid()); +} + +int64_t Font::get_face_count() const { + return TS->font_get_face_count(_get_rid()); +} + +Font::Font() { + cache.set_capacity(64); + cache_wrap.set_capacity(16); } -void FontData::_convert_packed_8bit(Ref<Image> &p_source, int p_page, int p_sz) { +Font::~Font() { + reset_state(); +} + +/*************************************************************************/ +/* FontFile */ +/*************************************************************************/ + +_FORCE_INLINE_ void FontFile::_clear_cache() { + for (int i = 0; i < cache.size(); i++) { + if (cache[i].is_valid()) { + TS->free_rid(cache[i]); + cache.write[i] = RID(); + } + } +} + +_FORCE_INLINE_ void FontFile::_ensure_rid(int p_cache_index) const { + if (unlikely(p_cache_index >= cache.size())) { + cache.resize(p_cache_index + 1); + } + if (unlikely(!cache[p_cache_index].is_valid())) { + cache.write[p_cache_index] = TS->create_font(); + TS->font_set_data_ptr(cache[p_cache_index], data_ptr, data_size); + TS->font_set_antialiased(cache[p_cache_index], antialiased); + TS->font_set_generate_mipmaps(cache[p_cache_index], mipmaps); + TS->font_set_multichannel_signed_distance_field(cache[p_cache_index], msdf); + TS->font_set_msdf_pixel_range(cache[p_cache_index], msdf_pixel_range); + TS->font_set_msdf_size(cache[p_cache_index], msdf_size); + TS->font_set_fixed_size(cache[p_cache_index], fixed_size); + TS->font_set_force_autohinter(cache[p_cache_index], force_autohinter); + TS->font_set_hinting(cache[p_cache_index], hinting); + TS->font_set_subpixel_positioning(cache[p_cache_index], subpixel_positioning); + TS->font_set_oversampling(cache[p_cache_index], oversampling); + } +} + +void FontFile::_convert_packed_8bit(Ref<Image> &p_source, int p_page, int p_sz) { int w = p_source->get_width(); int h = p_source->get_height(); @@ -501,7 +636,7 @@ void FontData::_convert_packed_8bit(Ref<Image> &p_source, int p_page, int p_sz) set_texture_image(0, Vector2i(p_sz, 0), p_page * 4 + 3, img_a); } -void FontData::_convert_packed_4bit(Ref<Image> &p_source, int p_page, int p_sz) { +void FontFile::_convert_packed_4bit(Ref<Image> &p_source, int p_page, int p_sz) { int w = p_source->get_width(); int h = p_source->get_height(); @@ -601,7 +736,7 @@ void FontData::_convert_packed_4bit(Ref<Image> &p_source, int p_page, int p_sz) set_texture_image(0, Vector2i(p_sz, 1), p_page * 4 + 3, img_ao); } -void FontData::_convert_rgba_4bit(Ref<Image> &p_source, int p_page, int p_sz) { +void FontFile::_convert_rgba_4bit(Ref<Image> &p_source, int p_page, int p_sz) { int w = p_source->get_width(); int h = p_source->get_height(); @@ -657,7 +792,7 @@ void FontData::_convert_rgba_4bit(Ref<Image> &p_source, int p_page, int p_sz) { set_texture_image(0, Vector2i(p_sz, 1), p_page, img_o); } -void FontData::_convert_mono_8bit(Ref<Image> &p_source, int p_page, int p_ch, int p_sz, int p_ol) { +void FontFile::_convert_mono_8bit(Ref<Image> &p_source, int p_page, int p_ch, int p_sz, int p_ol) { int w = p_source->get_width(); int h = p_source->get_height(); @@ -686,7 +821,7 @@ void FontData::_convert_mono_8bit(Ref<Image> &p_source, int p_page, int p_ch, in set_texture_image(0, Vector2i(p_sz, p_ol), p_page, img_g); } -void FontData::_convert_mono_4bit(Ref<Image> &p_source, int p_page, int p_ch, int p_sz, int p_ol) { +void FontFile::_convert_mono_4bit(Ref<Image> &p_source, int p_page, int p_ch, int p_sz, int p_ol) { int w = p_source->get_width(); int h = p_source->get_height(); @@ -729,12 +864,481 @@ void FontData::_convert_mono_4bit(Ref<Image> &p_source, int p_page, int p_ch, in set_texture_image(0, Vector2i(p_sz, p_ol), p_page, img_o); } +void FontFile::_bind_methods() { + ClassDB::bind_method(D_METHOD("load_bitmap_font", "path"), &FontFile::load_bitmap_font); + ClassDB::bind_method(D_METHOD("load_dynamic_font", "path"), &FontFile::load_dynamic_font); + + ClassDB::bind_method(D_METHOD("set_data", "data"), &FontFile::set_data); + ClassDB::bind_method(D_METHOD("get_data"), &FontFile::get_data); + + ClassDB::bind_method(D_METHOD("set_font_name", "name"), &FontFile::set_font_name); + ClassDB::bind_method(D_METHOD("set_font_style_name", "name"), &FontFile::set_font_style_name); + ClassDB::bind_method(D_METHOD("set_font_style", "style"), &FontFile::set_font_style); + + ClassDB::bind_method(D_METHOD("set_antialiased", "antialiased"), &FontFile::set_antialiased); + ClassDB::bind_method(D_METHOD("is_antialiased"), &FontFile::is_antialiased); + + ClassDB::bind_method(D_METHOD("set_generate_mipmaps", "generate_mipmaps"), &FontFile::set_generate_mipmaps); + ClassDB::bind_method(D_METHOD("get_generate_mipmaps"), &FontFile::get_generate_mipmaps); + + ClassDB::bind_method(D_METHOD("set_multichannel_signed_distance_field", "msdf"), &FontFile::set_multichannel_signed_distance_field); + ClassDB::bind_method(D_METHOD("is_multichannel_signed_distance_field"), &FontFile::is_multichannel_signed_distance_field); + + ClassDB::bind_method(D_METHOD("set_msdf_pixel_range", "msdf_pixel_range"), &FontFile::set_msdf_pixel_range); + ClassDB::bind_method(D_METHOD("get_msdf_pixel_range"), &FontFile::get_msdf_pixel_range); + + ClassDB::bind_method(D_METHOD("set_msdf_size", "msdf_size"), &FontFile::set_msdf_size); + ClassDB::bind_method(D_METHOD("get_msdf_size"), &FontFile::get_msdf_size); + + ClassDB::bind_method(D_METHOD("set_fixed_size", "fixed_size"), &FontFile::set_fixed_size); + ClassDB::bind_method(D_METHOD("get_fixed_size"), &FontFile::get_fixed_size); + + ClassDB::bind_method(D_METHOD("set_force_autohinter", "force_autohinter"), &FontFile::set_force_autohinter); + ClassDB::bind_method(D_METHOD("is_force_autohinter"), &FontFile::is_force_autohinter); + + ClassDB::bind_method(D_METHOD("set_hinting", "hinting"), &FontFile::set_hinting); + ClassDB::bind_method(D_METHOD("get_hinting"), &FontFile::get_hinting); + + ClassDB::bind_method(D_METHOD("set_subpixel_positioning", "subpixel_positioning"), &FontFile::set_subpixel_positioning); + ClassDB::bind_method(D_METHOD("get_subpixel_positioning"), &FontFile::get_subpixel_positioning); + + ClassDB::bind_method(D_METHOD("set_oversampling", "oversampling"), &FontFile::set_oversampling); + ClassDB::bind_method(D_METHOD("get_oversampling"), &FontFile::get_oversampling); + + ClassDB::bind_method(D_METHOD("get_cache_count"), &FontFile::get_cache_count); + ClassDB::bind_method(D_METHOD("clear_cache"), &FontFile::clear_cache); + ClassDB::bind_method(D_METHOD("remove_cache", "cache_index"), &FontFile::remove_cache); + + ClassDB::bind_method(D_METHOD("get_size_cache_list", "cache_index"), &FontFile::get_size_cache_list); + ClassDB::bind_method(D_METHOD("clear_size_cache", "cache_index"), &FontFile::clear_size_cache); + ClassDB::bind_method(D_METHOD("remove_size_cache", "cache_index", "size"), &FontFile::remove_size_cache); + + ClassDB::bind_method(D_METHOD("set_variation_coordinates", "cache_index", "variation_coordinates"), &FontFile::set_variation_coordinates); + ClassDB::bind_method(D_METHOD("get_variation_coordinates", "cache_index"), &FontFile::get_variation_coordinates); + + ClassDB::bind_method(D_METHOD("set_embolden", "cache_index", "strength"), &FontFile::set_embolden); + ClassDB::bind_method(D_METHOD("get_embolden", "cache_index"), &FontFile::get_embolden); + + ClassDB::bind_method(D_METHOD("set_transform", "cache_index", "transform"), &FontFile::set_transform); + ClassDB::bind_method(D_METHOD("get_transform", "cache_index"), &FontFile::get_transform); + + ClassDB::bind_method(D_METHOD("set_face_index", "cache_index", "face_index"), &FontFile::set_face_index); + ClassDB::bind_method(D_METHOD("get_face_index", "cache_index"), &FontFile::get_face_index); + + ClassDB::bind_method(D_METHOD("set_cache_ascent", "cache_index", "size", "ascent"), &FontFile::set_cache_ascent); + ClassDB::bind_method(D_METHOD("get_cache_ascent", "cache_index", "size"), &FontFile::get_cache_ascent); + + ClassDB::bind_method(D_METHOD("set_cache_descent", "cache_index", "size", "descent"), &FontFile::set_cache_descent); + ClassDB::bind_method(D_METHOD("get_cache_descent", "cache_index", "size"), &FontFile::get_cache_descent); + + ClassDB::bind_method(D_METHOD("set_cache_underline_position", "cache_index", "size", "underline_position"), &FontFile::set_cache_underline_position); + ClassDB::bind_method(D_METHOD("get_cache_underline_position", "cache_index", "size"), &FontFile::get_cache_underline_position); + + ClassDB::bind_method(D_METHOD("set_cache_underline_thickness", "cache_index", "size", "underline_thickness"), &FontFile::set_cache_underline_thickness); + ClassDB::bind_method(D_METHOD("get_cache_underline_thickness", "cache_index", "size"), &FontFile::get_cache_underline_thickness); + + ClassDB::bind_method(D_METHOD("set_cache_scale", "cache_index", "size", "scale"), &FontFile::set_cache_scale); + ClassDB::bind_method(D_METHOD("get_cache_scale", "cache_index", "size"), &FontFile::get_cache_scale); + + ClassDB::bind_method(D_METHOD("get_texture_count", "cache_index", "size"), &FontFile::get_texture_count); + ClassDB::bind_method(D_METHOD("clear_textures", "cache_index", "size"), &FontFile::clear_textures); + ClassDB::bind_method(D_METHOD("remove_texture", "cache_index", "size", "texture_index"), &FontFile::remove_texture); + + ClassDB::bind_method(D_METHOD("set_texture_image", "cache_index", "size", "texture_index", "image"), &FontFile::set_texture_image); + ClassDB::bind_method(D_METHOD("get_texture_image", "cache_index", "size", "texture_index"), &FontFile::get_texture_image); + + ClassDB::bind_method(D_METHOD("set_texture_offsets", "cache_index", "size", "texture_index", "offset"), &FontFile::set_texture_offsets); + ClassDB::bind_method(D_METHOD("get_texture_offsets", "cache_index", "size", "texture_index"), &FontFile::get_texture_offsets); + + ClassDB::bind_method(D_METHOD("get_glyph_list", "cache_index", "size"), &FontFile::get_glyph_list); + ClassDB::bind_method(D_METHOD("clear_glyphs", "cache_index", "size"), &FontFile::clear_glyphs); + ClassDB::bind_method(D_METHOD("remove_glyph", "cache_index", "size", "glyph"), &FontFile::remove_glyph); + + ClassDB::bind_method(D_METHOD("set_glyph_advance", "cache_index", "size", "glyph", "advance"), &FontFile::set_glyph_advance); + ClassDB::bind_method(D_METHOD("get_glyph_advance", "cache_index", "size", "glyph"), &FontFile::get_glyph_advance); + + ClassDB::bind_method(D_METHOD("set_glyph_offset", "cache_index", "size", "glyph", "offset"), &FontFile::set_glyph_offset); + ClassDB::bind_method(D_METHOD("get_glyph_offset", "cache_index", "size", "glyph"), &FontFile::get_glyph_offset); + + ClassDB::bind_method(D_METHOD("set_glyph_size", "cache_index", "size", "glyph", "gl_size"), &FontFile::set_glyph_size); + ClassDB::bind_method(D_METHOD("get_glyph_size", "cache_index", "size", "glyph"), &FontFile::get_glyph_size); + + ClassDB::bind_method(D_METHOD("set_glyph_uv_rect", "cache_index", "size", "glyph", "uv_rect"), &FontFile::set_glyph_uv_rect); + ClassDB::bind_method(D_METHOD("get_glyph_uv_rect", "cache_index", "size", "glyph"), &FontFile::get_glyph_uv_rect); + + ClassDB::bind_method(D_METHOD("set_glyph_texture_idx", "cache_index", "size", "glyph", "texture_idx"), &FontFile::set_glyph_texture_idx); + ClassDB::bind_method(D_METHOD("get_glyph_texture_idx", "cache_index", "size", "glyph"), &FontFile::get_glyph_texture_idx); + + ClassDB::bind_method(D_METHOD("get_kerning_list", "cache_index", "size"), &FontFile::get_kerning_list); + ClassDB::bind_method(D_METHOD("clear_kerning_map", "cache_index", "size"), &FontFile::clear_kerning_map); + ClassDB::bind_method(D_METHOD("remove_kerning", "cache_index", "size", "glyph_pair"), &FontFile::remove_kerning); + + ClassDB::bind_method(D_METHOD("set_kerning", "cache_index", "size", "glyph_pair", "kerning"), &FontFile::set_kerning); + ClassDB::bind_method(D_METHOD("get_kerning", "cache_index", "size", "glyph_pair"), &FontFile::get_kerning); + + ClassDB::bind_method(D_METHOD("render_range", "cache_index", "size", "start", "end"), &FontFile::render_range); + ClassDB::bind_method(D_METHOD("render_glyph", "cache_index", "size", "index"), &FontFile::render_glyph); + + ClassDB::bind_method(D_METHOD("set_language_support_override", "language", "supported"), &FontFile::set_language_support_override); + ClassDB::bind_method(D_METHOD("get_language_support_override", "language"), &FontFile::get_language_support_override); + ClassDB::bind_method(D_METHOD("remove_language_support_override", "language"), &FontFile::remove_language_support_override); + ClassDB::bind_method(D_METHOD("get_language_support_overrides"), &FontFile::get_language_support_overrides); + + ClassDB::bind_method(D_METHOD("set_script_support_override", "script", "supported"), &FontFile::set_script_support_override); + ClassDB::bind_method(D_METHOD("get_script_support_override", "script"), &FontFile::get_script_support_override); + ClassDB::bind_method(D_METHOD("remove_script_support_override", "script"), &FontFile::remove_script_support_override); + ClassDB::bind_method(D_METHOD("get_script_support_overrides"), &FontFile::get_script_support_overrides); + + ClassDB::bind_method(D_METHOD("set_opentype_feature_overrides", "overrides"), &FontFile::set_opentype_feature_overrides); + ClassDB::bind_method(D_METHOD("get_opentype_feature_overrides"), &FontFile::get_opentype_feature_overrides); + + ClassDB::bind_method(D_METHOD("get_glyph_index", "size", "char", "variation_selector"), &FontFile::get_glyph_index); + + ADD_PROPERTY(PropertyInfo(Variant::PACKED_BYTE_ARRAY, "data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE), "set_data", "get_data"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "generate_mipmaps", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE), "set_generate_mipmaps", "get_generate_mipmaps"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "antialiased", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE), "set_antialiased", "is_antialiased"); + ADD_PROPERTY(PropertyInfo(Variant::STRING, "font_name", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE), "set_font_name", "get_font_name"); + ADD_PROPERTY(PropertyInfo(Variant::STRING, "style_name", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE), "set_font_style_name", "get_font_style_name"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "font_style", PROPERTY_HINT_FLAGS, "Bold,Italic,Fixed Size", PROPERTY_USAGE_STORAGE), "set_font_style", "get_font_style"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "subpixel_positioning", PROPERTY_HINT_ENUM, "Disabled,Auto,One half of a pixel,One quarter of a pixel", PROPERTY_USAGE_STORAGE), "set_subpixel_positioning", "get_subpixel_positioning"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "multichannel_signed_distance_field", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE), "set_multichannel_signed_distance_field", "is_multichannel_signed_distance_field"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "msdf_pixel_range", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE), "set_msdf_pixel_range", "get_msdf_pixel_range"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "msdf_size", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE), "set_msdf_size", "get_msdf_size"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "force_autohinter", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE), "set_force_autohinter", "is_force_autohinter"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "hinting", PROPERTY_HINT_ENUM, "None,Light,Normal", PROPERTY_USAGE_STORAGE), "set_hinting", "get_hinting"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "oversampling", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE), "set_oversampling", "get_oversampling"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "fixed_size", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE), "set_fixed_size", "get_fixed_size"); + ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "opentype_feature_overrides", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE), "set_opentype_feature_overrides", "get_opentype_feature_overrides"); + ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "fallbacks", PROPERTY_HINT_ARRAY_TYPE, vformat("%s/%s:%s", Variant::OBJECT, PROPERTY_HINT_RESOURCE_TYPE, "Font"), PROPERTY_USAGE_STORAGE), "set_fallbacks", "get_fallbacks"); +} + +bool FontFile::_set(const StringName &p_name, const Variant &p_value) { + Vector<String> tokens = p_name.operator String().split("/"); + +#ifndef DISABLE_DEPRECATED + if (tokens.size() == 1 && tokens[0] == "font_path") { + // Compatibility, DynamicFontData. + load_dynamic_font(p_value); + } else if (tokens.size() == 1 && tokens[0] == "override_oversampling") { + set_oversampling(p_value); + } + if (tokens.size() == 1 && tokens[0] == "font_data") { + // Compatibility, DynamicFont. + Ref<Font> f = p_value; + if (f.is_valid()) { + fallbacks.push_back(f); + return true; + } + return false; + } else if (tokens.size() == 2 && tokens[0] == "fallback") { + // Compatibility, DynamicFont. + Ref<FontFile> f = p_value; + if (f.is_valid()) { + fallbacks.push_back(f); + return true; + } + return false; + } else if (tokens.size() == 1 && tokens[0] == "textures") { + // Compatibility, BitmapFont. + set_fixed_size(16); + Array textures = p_value; + for (int i = 0; i < textures.size(); i++) { + Ref<ImageTexture> tex = textures[i]; + ERR_CONTINUE(!tex.is_valid()); + set_texture_image(0, Vector2i(16, 0), i, tex->get_image()); + } + } else if (tokens.size() == 1 && tokens[0] == "chars") { + // Compatibility, BitmapFont. + set_fixed_size(16); + PackedInt32Array arr = p_value; + int len = arr.size(); + ERR_FAIL_COND_V(len % 9, false); + if (!len) { + return false; + } + int chars = len / 9; + for (int i = 0; i < chars; i++) { + const int32_t *data = &arr[i * 9]; + char32_t c = data[0]; + set_glyph_texture_idx(0, Vector2i(16, 0), c, data[1]); + set_glyph_uv_rect(0, Vector2i(16, 0), c, Rect2(data[2], data[3], data[4], data[5])); + set_glyph_offset(0, Vector2i(16, 0), c, Size2(data[6], data[7])); + set_glyph_advance(0, 16, c, Vector2(data[8], 0)); + } + } else if (tokens.size() == 1 && tokens[0] == "kernings") { + // Compatibility, BitmapFont. + set_fixed_size(16); + PackedInt32Array arr = p_value; + int len = arr.size(); + ERR_FAIL_COND_V(len % 3, false); + if (!len) { + return false; + } + for (int i = 0; i < len / 3; i++) { + const int32_t *data = &arr[i * 3]; + set_kerning(0, 16, Vector2i(data[0], data[1]), Vector2(data[2], 0)); + } + } else if (tokens.size() == 1 && tokens[0] == "height") { + // Compatibility, BitmapFont. + bmp_height = p_value; + set_fixed_size(16); + set_cache_descent(0, 16, bmp_height - bmp_ascent); + } else if (tokens.size() == 1 && tokens[0] == "ascent") { + // Compatibility, BitmapFont. + bmp_ascent = p_value; + set_fixed_size(16); + set_cache_ascent(0, 16, bmp_ascent); + set_cache_descent(0, 16, bmp_height - bmp_ascent); + } else if (tokens.size() == 1 && tokens[0] == "fallback") { + // Compatibility, BitmapFont. + Ref<Font> f = p_value; + if (f.is_valid()) { + fallbacks.push_back(f); + return true; + } + return false; + } +#endif // DISABLE_DEPRECATED + + if (tokens.size() == 2 && tokens[0] == "language_support_override") { + String lang = tokens[1]; + set_language_support_override(lang, p_value); + return true; + } else if (tokens.size() == 2 && tokens[0] == "script_support_override") { + String script = tokens[1]; + set_script_support_override(script, p_value); + return true; + } else if (tokens.size() >= 3 && tokens[0] == "cache") { + int cache_index = tokens[1].to_int(); + if (tokens.size() == 3 && tokens[2] == "variation_coordinates") { + set_variation_coordinates(cache_index, p_value); + return true; + } else if (tokens.size() == 3 && tokens[2] == "embolden") { + set_embolden(cache_index, p_value); + return true; + } else if (tokens.size() == 3 && tokens[2] == "face_index") { + set_face_index(cache_index, p_value); + return true; + } else if (tokens.size() == 3 && tokens[2] == "transform") { + set_transform(cache_index, p_value); + return true; + } + if (tokens.size() >= 5) { + Vector2i sz = Vector2i(tokens[2].to_int(), tokens[3].to_int()); + if (tokens[4] == "ascent") { + set_cache_ascent(cache_index, sz.x, p_value); + return true; + } else if (tokens[4] == "descent") { + set_cache_descent(cache_index, sz.x, p_value); + return true; + } else if (tokens[4] == "underline_position") { + set_cache_underline_position(cache_index, sz.x, p_value); + return true; + } else if (tokens[4] == "underline_thickness") { + set_cache_underline_thickness(cache_index, sz.x, p_value); + return true; + } else if (tokens[4] == "scale") { + set_cache_scale(cache_index, sz.x, p_value); + return true; + } else if (tokens.size() == 7 && tokens[4] == "textures") { + int texture_index = tokens[5].to_int(); + if (tokens[6] == "image") { + set_texture_image(cache_index, sz, texture_index, p_value); + return true; + } else if (tokens[6] == "offsets") { + set_texture_offsets(cache_index, sz, texture_index, p_value); + return true; + } + } else if (tokens.size() == 7 && tokens[4] == "glyphs") { + int32_t glyph_index = tokens[5].to_int(); + if (tokens[6] == "advance") { + set_glyph_advance(cache_index, sz.x, glyph_index, p_value); + return true; + } else if (tokens[6] == "offset") { + set_glyph_offset(cache_index, sz, glyph_index, p_value); + return true; + } else if (tokens[6] == "size") { + set_glyph_size(cache_index, sz, glyph_index, p_value); + return true; + } else if (tokens[6] == "uv_rect") { + set_glyph_uv_rect(cache_index, sz, glyph_index, p_value); + return true; + } else if (tokens[6] == "texture_idx") { + set_glyph_texture_idx(cache_index, sz, glyph_index, p_value); + return true; + } + } else if (tokens.size() == 7 && tokens[4] == "kerning_overrides") { + Vector2i gp = Vector2i(tokens[5].to_int(), tokens[6].to_int()); + set_kerning(cache_index, sz.x, gp, p_value); + return true; + } + } + } + return false; +} + +bool FontFile::_get(const StringName &p_name, Variant &r_ret) const { + Vector<String> tokens = p_name.operator String().split("/"); + if (tokens.size() == 2 && tokens[0] == "language_support_override") { + String lang = tokens[1]; + r_ret = get_language_support_override(lang); + return true; + } else if (tokens.size() == 2 && tokens[0] == "script_support_override") { + String script = tokens[1]; + r_ret = get_script_support_override(script); + return true; + } else if (tokens.size() >= 3 && tokens[0] == "cache") { + int cache_index = tokens[1].to_int(); + if (tokens.size() == 3 && tokens[2] == "variation_coordinates") { + r_ret = get_variation_coordinates(cache_index); + return true; + } else if (tokens.size() == 3 && tokens[2] == "embolden") { + r_ret = get_embolden(cache_index); + return true; + } else if (tokens.size() == 3 && tokens[2] == "face_index") { + r_ret = get_face_index(cache_index); + return true; + } else if (tokens.size() == 3 && tokens[2] == "transform") { + r_ret = get_transform(cache_index); + return true; + } + if (tokens.size() >= 5) { + Vector2i sz = Vector2i(tokens[2].to_int(), tokens[3].to_int()); + if (tokens[4] == "ascent") { + r_ret = get_cache_ascent(cache_index, sz.x); + return true; + } else if (tokens[4] == "descent") { + r_ret = get_cache_descent(cache_index, sz.x); + return true; + } else if (tokens[4] == "underline_position") { + r_ret = get_cache_underline_position(cache_index, sz.x); + return true; + } else if (tokens[4] == "underline_thickness") { + r_ret = get_cache_underline_thickness(cache_index, sz.x); + return true; + } else if (tokens[4] == "scale") { + r_ret = get_cache_scale(cache_index, sz.x); + return true; + } else if (tokens.size() == 7 && tokens[4] == "textures") { + int texture_index = tokens[5].to_int(); + if (tokens[6] == "image") { + r_ret = get_texture_image(cache_index, sz, texture_index); + return true; + } else if (tokens[6] == "offsets") { + r_ret = get_texture_offsets(cache_index, sz, texture_index); + return true; + } + } else if (tokens.size() == 7 && tokens[4] == "glyphs") { + int32_t glyph_index = tokens[5].to_int(); + if (tokens[6] == "advance") { + r_ret = get_glyph_advance(cache_index, sz.x, glyph_index); + return true; + } else if (tokens[6] == "offset") { + r_ret = get_glyph_offset(cache_index, sz, glyph_index); + return true; + } else if (tokens[6] == "size") { + r_ret = get_glyph_size(cache_index, sz, glyph_index); + return true; + } else if (tokens[6] == "uv_rect") { + r_ret = get_glyph_uv_rect(cache_index, sz, glyph_index); + return true; + } else if (tokens[6] == "texture_idx") { + r_ret = get_glyph_texture_idx(cache_index, sz, glyph_index); + return true; + } + } else if (tokens.size() == 7 && tokens[4] == "kerning_overrides") { + Vector2i gp = Vector2i(tokens[5].to_int(), tokens[6].to_int()); + r_ret = get_kerning(cache_index, sz.x, gp); + return true; + } + } + } + return false; +} + +void FontFile::_get_property_list(List<PropertyInfo> *p_list) const { + Vector<String> lang_over = get_language_support_overrides(); + for (int i = 0; i < lang_over.size(); i++) { + p_list->push_back(PropertyInfo(Variant::BOOL, "language_support_override/" + lang_over[i], PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE)); + } + Vector<String> scr_over = get_script_support_overrides(); + for (int i = 0; i < scr_over.size(); i++) { + p_list->push_back(PropertyInfo(Variant::BOOL, "script_support_override/" + scr_over[i], PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE)); + } + for (int i = 0; i < cache.size(); i++) { + String prefix = "cache/" + itos(i) + "/"; + Array sizes = get_size_cache_list(i); + p_list->push_back(PropertyInfo(Variant::DICTIONARY, prefix + "variation_coordinates", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE)); + p_list->push_back(PropertyInfo(Variant::INT, "face_index", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE)); + p_list->push_back(PropertyInfo(Variant::FLOAT, "embolden", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE)); + p_list->push_back(PropertyInfo(Variant::TRANSFORM2D, "transform", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE)); + + for (int j = 0; j < sizes.size(); j++) { + Vector2i sz = sizes[j]; + String prefix_sz = prefix + itos(sz.x) + "/" + itos(sz.y) + "/"; + if (sz.y == 0) { + p_list->push_back(PropertyInfo(Variant::FLOAT, prefix_sz + "ascent", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE)); + p_list->push_back(PropertyInfo(Variant::FLOAT, prefix_sz + "descent", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE)); + p_list->push_back(PropertyInfo(Variant::FLOAT, prefix_sz + "underline_position", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE)); + p_list->push_back(PropertyInfo(Variant::FLOAT, prefix_sz + "underline_thickness", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE)); + p_list->push_back(PropertyInfo(Variant::FLOAT, prefix_sz + "scale", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE)); + } + + int tx_cnt = get_texture_count(i, sz); + for (int k = 0; k < tx_cnt; k++) { + p_list->push_back(PropertyInfo(Variant::PACKED_INT32_ARRAY, prefix_sz + "textures/" + itos(k) + "/offsets", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE)); + p_list->push_back(PropertyInfo(Variant::OBJECT, prefix_sz + "textures/" + itos(k) + "/image", PROPERTY_HINT_RESOURCE_TYPE, "Image", PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_RESOURCE_NOT_PERSISTENT)); + } + Array glyphs = get_glyph_list(i, sz); + for (int k = 0; k < glyphs.size(); k++) { + const int32_t &gl = glyphs[k]; + if (sz.y == 0) { + p_list->push_back(PropertyInfo(Variant::VECTOR2, prefix_sz + "glyphs/" + itos(gl) + "/advance", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE)); + } + p_list->push_back(PropertyInfo(Variant::VECTOR2, prefix_sz + "glyphs/" + itos(gl) + "/offset", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE)); + p_list->push_back(PropertyInfo(Variant::VECTOR2, prefix_sz + "glyphs/" + itos(gl) + "/size", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE)); + p_list->push_back(PropertyInfo(Variant::RECT2, prefix_sz + "glyphs/" + itos(gl) + "/uv_rect", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE)); + p_list->push_back(PropertyInfo(Variant::INT, prefix_sz + "glyphs/" + itos(gl) + "/texture_idx", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE)); + } + if (sz.y == 0) { + Array kerning_map = get_kerning_list(i, sz.x); + for (int k = 0; k < kerning_map.size(); k++) { + const Vector2i &gl_pair = kerning_map[k]; + p_list->push_back(PropertyInfo(Variant::VECTOR2, prefix_sz + "kerning_overrides/" + itos(gl_pair.x) + "/" + itos(gl_pair.y), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE)); + } + } + } + } +} + +void FontFile::reset_state() { + _clear_cache(); + data.clear(); + data_ptr = nullptr; + data_size = 0; + cache.clear(); + + antialiased = true; + mipmaps = false; + msdf = false; + force_autohinter = false; + hinting = TextServer::HINTING_LIGHT; + subpixel_positioning = TextServer::SUBPIXEL_POSITIONING_DISABLED; + msdf_pixel_range = 14; + msdf_size = 128; + fixed_size = 0; + oversampling = 0.f; + + Font::reset_state(); +} + /*************************************************************************/ -Error FontData::load_bitmap_font(const String &p_path) { +Error FontFile::load_bitmap_font(const String &p_path) { reset_state(); antialiased = false; + mipmaps = false; msdf = false; force_autohinter = false; hinting = TextServer::HINTING_NONE; @@ -747,7 +1351,7 @@ Error FontData::load_bitmap_font(const String &p_path) { int height = 0; int ascent = 0; int outline = 0; - uint32_t st_flags = 0; + BitField<TextServer::FontStyle> st_flags = 0; String font_name; bool packed = false; @@ -773,10 +1377,10 @@ Error FontData::load_bitmap_font(const String &p_path) { uint8_t flags = f->get_8(); ERR_FAIL_COND_V_MSG(flags & 0x02, ERR_CANT_CREATE, RTR("Non-unicode version of BMFont is not supported.")); if (flags & (1 << 3)) { - st_flags |= TextServer::FONT_BOLD; + st_flags.set_flag(TextServer::FONT_BOLD); } if (flags & (1 << 2)) { - st_flags |= TextServer::FONT_ITALIC; + st_flags.set_flag(TextServer::FONT_ITALIC); } f->get_8(); // non-unicode charset, skip f->get_16(); // stretch_h, skip @@ -956,7 +1560,7 @@ Error FontData::load_bitmap_font(const String &p_path) { int delimiter = line.find(" "); String type = line.substr(0, delimiter); int pos = delimiter + 1; - Map<String, String> keys; + HashMap<String, String> keys; while (pos < line.size() && line[pos] == ' ') { pos++; @@ -1003,12 +1607,12 @@ Error FontData::load_bitmap_font(const String &p_path) { } if (keys.has("bold")) { if (keys["bold"].to_int()) { - st_flags |= TextServer::FONT_BOLD; + st_flags.set_flag(TextServer::FONT_BOLD); } } if (keys.has("italic")) { if (keys["italic"].to_int()) { - st_flags |= TextServer::FONT_ITALIC; + st_flags.set_flag(TextServer::FONT_ITALIC); } } if (keys.has("face")) { @@ -1193,13 +1797,13 @@ Error FontData::load_bitmap_font(const String &p_path) { set_font_name(font_name); set_font_style(st_flags); - set_ascent(0, base_size, ascent); - set_descent(0, base_size, height - ascent); + set_cache_ascent(0, base_size, ascent); + set_cache_descent(0, base_size, height - ascent); return OK; } -Error FontData::load_dynamic_font(const String &p_path) { +Error FontFile::load_dynamic_font(const String &p_path) { reset_state(); Vector<uint8_t> data = FileAccess::get_file_as_array(p_path); @@ -1208,7 +1812,7 @@ Error FontData::load_dynamic_font(const String &p_path) { return OK; } -void FontData::set_data_ptr(const uint8_t *p_data, size_t p_size) { +void FontFile::set_data_ptr(const uint8_t *p_data, size_t p_size) { data.clear(); data_ptr = p_data; data_size = p_size; @@ -1222,7 +1826,7 @@ void FontData::set_data_ptr(const uint8_t *p_data, size_t p_size) { } } -void FontData::set_data(const PackedByteArray &p_data) { +void FontFile::set_data(const PackedByteArray &p_data) { data = p_data; data_ptr = data.ptr(); data_size = data.size(); @@ -1236,7 +1840,7 @@ void FontData::set_data(const PackedByteArray &p_data) { } } -PackedByteArray FontData::get_data() const { +PackedByteArray FontFile::get_data() const { if (unlikely((size_t)data.size() != data_size)) { PackedByteArray *data_w = const_cast<PackedByteArray *>(&data); data_w->resize(data_size); @@ -1245,37 +1849,22 @@ PackedByteArray FontData::get_data() const { return data; } -void FontData::set_font_name(const String &p_name) { +void FontFile::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) { +void FontFile::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) { +void FontFile::set_font_style(BitField<TextServer::FontStyle> 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) { +void FontFile::set_antialiased(bool p_antialiased) { if (antialiased != p_antialiased) { antialiased = p_antialiased; for (int i = 0; i < cache.size(); i++) { @@ -1286,11 +1875,26 @@ void FontData::set_antialiased(bool p_antialiased) { } } -bool FontData::is_antialiased() const { +bool FontFile::is_antialiased() const { return antialiased; } -void FontData::set_multichannel_signed_distance_field(bool p_msdf) { +void FontFile::set_generate_mipmaps(bool p_generate_mipmaps) { + if (mipmaps != p_generate_mipmaps) { + mipmaps = p_generate_mipmaps; + for (int i = 0; i < cache.size(); i++) { + _ensure_rid(i); + TS->font_set_generate_mipmaps(cache[i], mipmaps); + } + emit_changed(); + } +} + +bool FontFile::get_generate_mipmaps() const { + return mipmaps; +} + +void FontFile::set_multichannel_signed_distance_field(bool p_msdf) { if (msdf != p_msdf) { msdf = p_msdf; for (int i = 0; i < cache.size(); i++) { @@ -1301,11 +1905,11 @@ void FontData::set_multichannel_signed_distance_field(bool p_msdf) { } } -bool FontData::is_multichannel_signed_distance_field() const { +bool FontFile::is_multichannel_signed_distance_field() const { return msdf; } -void FontData::set_msdf_pixel_range(int p_msdf_pixel_range) { +void FontFile::set_msdf_pixel_range(int p_msdf_pixel_range) { if (msdf_pixel_range != p_msdf_pixel_range) { msdf_pixel_range = p_msdf_pixel_range; for (int i = 0; i < cache.size(); i++) { @@ -1316,11 +1920,11 @@ void FontData::set_msdf_pixel_range(int p_msdf_pixel_range) { } } -int FontData::get_msdf_pixel_range() const { +int FontFile::get_msdf_pixel_range() const { return msdf_pixel_range; } -void FontData::set_msdf_size(int p_msdf_size) { +void FontFile::set_msdf_size(int p_msdf_size) { if (msdf_size != p_msdf_size) { msdf_size = p_msdf_size; for (int i = 0; i < cache.size(); i++) { @@ -1331,11 +1935,11 @@ void FontData::set_msdf_size(int p_msdf_size) { } } -int FontData::get_msdf_size() const { +int FontFile::get_msdf_size() const { return msdf_size; } -void FontData::set_fixed_size(int p_fixed_size) { +void FontFile::set_fixed_size(int p_fixed_size) { if (fixed_size != p_fixed_size) { fixed_size = p_fixed_size; for (int i = 0; i < cache.size(); i++) { @@ -1346,11 +1950,11 @@ void FontData::set_fixed_size(int p_fixed_size) { } } -int FontData::get_fixed_size() const { +int FontFile::get_fixed_size() const { return fixed_size; } -void FontData::set_force_autohinter(bool p_force_autohinter) { +void FontFile::set_force_autohinter(bool p_force_autohinter) { if (force_autohinter != p_force_autohinter) { force_autohinter = p_force_autohinter; for (int i = 0; i < cache.size(); i++) { @@ -1361,11 +1965,11 @@ void FontData::set_force_autohinter(bool p_force_autohinter) { } } -bool FontData::is_force_autohinter() const { +bool FontFile::is_force_autohinter() const { return force_autohinter; } -void FontData::set_hinting(TextServer::Hinting p_hinting) { +void FontFile::set_hinting(TextServer::Hinting p_hinting) { if (hinting != p_hinting) { hinting = p_hinting; for (int i = 0; i < cache.size(); i++) { @@ -1376,11 +1980,11 @@ void FontData::set_hinting(TextServer::Hinting p_hinting) { } } -TextServer::Hinting FontData::get_hinting() const { +TextServer::Hinting FontFile::get_hinting() const { return hinting; } -void FontData::set_subpixel_positioning(TextServer::SubpixelPositioning p_subpixel) { +void FontFile::set_subpixel_positioning(TextServer::SubpixelPositioning p_subpixel) { if (subpixel_positioning != p_subpixel) { subpixel_positioning = p_subpixel; for (int i = 0; i < cache.size(); i++) { @@ -1391,41 +1995,11 @@ void FontData::set_subpixel_positioning(TextServer::SubpixelPositioning p_subpix } } -TextServer::SubpixelPositioning FontData::get_subpixel_positioning() const { +TextServer::SubpixelPositioning FontFile::get_subpixel_positioning() const { return subpixel_positioning; } -void FontData::set_embolden(float p_strength) { - if (embolden != p_strength) { - embolden = p_strength; - for (int i = 0; i < cache.size(); i++) { - _ensure_rid(i); - TS->font_set_embolden(cache[i], embolden); - } - emit_changed(); - } -} - -float FontData::get_embolden() const { - return embolden; -} - -void FontData::set_transform(Transform2D p_transform) { - if (transform != p_transform) { - transform = p_transform; - for (int i = 0; i < cache.size(); i++) { - _ensure_rid(i); - TS->font_set_transform(cache[i], transform); - } - emit_changed(); - } -} - -Transform2D FontData::get_transform() const { - return transform; -} - -void FontData::set_oversampling(real_t p_oversampling) { +void FontFile::set_oversampling(real_t p_oversampling) { if (oversampling != p_oversampling) { oversampling = p_oversampling; for (int i = 0; i < cache.size(); i++) { @@ -1436,17 +2010,20 @@ void FontData::set_oversampling(real_t p_oversampling) { } } -real_t FontData::get_oversampling() const { +real_t FontFile::get_oversampling() const { return oversampling; } -RID FontData::find_cache(const Dictionary &p_variation_coordinates) const { +RID FontFile::find_variation(const Dictionary &p_variation_coordinates, int p_face_index, float p_strength, Transform2D p_transform) const { // Find existing variation cache. const Dictionary &supported_coords = get_supported_variation_list(); for (int i = 0; i < cache.size(); i++) { if (cache[i].is_valid()) { const Dictionary &cache_var = TS->font_get_variation_coordinates(cache[i]); bool match = true; + match = match && (TS->font_get_face_index(cache[i]) == p_face_index); + match = match && (TS->font_get_embolden(cache[i]) == p_strength); + match = match && (TS->font_get_transform(cache[i]) == p_transform); for (const Variant *V = supported_coords.next(nullptr); V && match; V = supported_coords.next(V)) { const Vector3 &def = supported_coords[*V]; @@ -1482,19 +2059,28 @@ RID FontData::find_cache(const Dictionary &p_variation_coordinates) const { int idx = cache.size(); _ensure_rid(idx); TS->font_set_variation_coordinates(cache[idx], p_variation_coordinates); + TS->font_set_face_index(cache[idx], p_face_index); + TS->font_set_embolden(cache[idx], p_strength); + TS->font_set_transform(cache[idx], p_transform); return cache[idx]; } -int FontData::get_cache_count() const { +RID FontFile::_get_rid() const { + _ensure_rid(0); + return cache[0]; +} + +int FontFile::get_cache_count() const { return cache.size(); } -void FontData::clear_cache() { +void FontFile::clear_cache() { _clear_cache(); cache.clear(); + emit_changed(); } -void FontData::remove_cache(int p_cache_index) { +void FontFile::remove_cache(int p_cache_index) { ERR_FAIL_INDEX(p_cache_index, cache.size()); if (cache[p_cache_index].is_valid()) { TS->free_rid(cache.write[p_cache_index]); @@ -1503,952 +2089,607 @@ void FontData::remove_cache(int p_cache_index) { emit_changed(); } -Array FontData::get_size_cache_list(int p_cache_index) const { +Array FontFile::get_size_cache_list(int p_cache_index) const { ERR_FAIL_COND_V(p_cache_index < 0, Array()); _ensure_rid(p_cache_index); return TS->font_get_size_cache_list(cache[p_cache_index]); } -void FontData::clear_size_cache(int p_cache_index) { +void FontFile::clear_size_cache(int p_cache_index) { ERR_FAIL_COND(p_cache_index < 0); _ensure_rid(p_cache_index); TS->font_clear_size_cache(cache[p_cache_index]); } -void FontData::remove_size_cache(int p_cache_index, const Vector2i &p_size) { +void FontFile::remove_size_cache(int p_cache_index, const Vector2i &p_size) { ERR_FAIL_COND(p_cache_index < 0); _ensure_rid(p_cache_index); TS->font_remove_size_cache(cache[p_cache_index], p_size); } -void FontData::set_variation_coordinates(int p_cache_index, const Dictionary &p_variation_coordinates) { +void FontFile::set_variation_coordinates(int p_cache_index, const Dictionary &p_variation_coordinates) { ERR_FAIL_COND(p_cache_index < 0); _ensure_rid(p_cache_index); TS->font_set_variation_coordinates(cache[p_cache_index], p_variation_coordinates); - emit_changed(); } -Dictionary FontData::get_variation_coordinates(int p_cache_index) const { +Dictionary FontFile::get_variation_coordinates(int p_cache_index) const { ERR_FAIL_COND_V(p_cache_index < 0, Dictionary()); _ensure_rid(p_cache_index); return TS->font_get_variation_coordinates(cache[p_cache_index]); } -void FontData::set_ascent(int p_cache_index, int p_size, real_t p_ascent) { +void FontFile::set_embolden(int p_cache_index, float p_strength) { + ERR_FAIL_COND(p_cache_index < 0); + _ensure_rid(p_cache_index); + TS->font_set_embolden(cache[p_cache_index], p_strength); +} + +float FontFile::get_embolden(int p_cache_index) const { + ERR_FAIL_COND_V(p_cache_index < 0, 0.f); + _ensure_rid(p_cache_index); + return TS->font_get_embolden(cache[p_cache_index]); +} + +void FontFile::set_transform(int p_cache_index, Transform2D p_transform) { + ERR_FAIL_COND(p_cache_index < 0); + _ensure_rid(p_cache_index); + TS->font_set_transform(cache[p_cache_index], p_transform); +} + +Transform2D FontFile::get_transform(int p_cache_index) const { + ERR_FAIL_COND_V(p_cache_index < 0, Transform2D()); + _ensure_rid(p_cache_index); + return TS->font_get_transform(cache[p_cache_index]); +} + +void FontFile::set_face_index(int p_cache_index, int64_t p_index) { + ERR_FAIL_COND(p_cache_index < 0); + ERR_FAIL_COND(p_index < 0); + ERR_FAIL_COND(p_index >= 0x7FFF); + + _ensure_rid(p_cache_index); + TS->font_set_face_index(cache[p_cache_index], p_index); +} + +int64_t FontFile::get_face_index(int p_cache_index) const { + ERR_FAIL_COND_V(p_cache_index < 0, 0); + _ensure_rid(p_cache_index); + return TS->font_get_face_index(cache[p_cache_index]); +} + +void FontFile::set_cache_ascent(int p_cache_index, int p_size, real_t p_ascent) { ERR_FAIL_COND(p_cache_index < 0); _ensure_rid(p_cache_index); TS->font_set_ascent(cache[p_cache_index], p_size, p_ascent); } -real_t FontData::get_ascent(int p_cache_index, int p_size) const { +real_t FontFile::get_cache_ascent(int p_cache_index, int p_size) const { ERR_FAIL_COND_V(p_cache_index < 0, 0.f); _ensure_rid(p_cache_index); return TS->font_get_ascent(cache[p_cache_index], p_size); } -void FontData::set_descent(int p_cache_index, int p_size, real_t p_descent) { +void FontFile::set_cache_descent(int p_cache_index, int p_size, real_t p_descent) { ERR_FAIL_COND(p_cache_index < 0); _ensure_rid(p_cache_index); TS->font_set_descent(cache[p_cache_index], p_size, p_descent); } -real_t FontData::get_descent(int p_cache_index, int p_size) const { +real_t FontFile::get_cache_descent(int p_cache_index, int p_size) const { ERR_FAIL_COND_V(p_cache_index < 0, 0.f); _ensure_rid(p_cache_index); return TS->font_get_descent(cache[p_cache_index], p_size); } -void FontData::set_underline_position(int p_cache_index, int p_size, real_t p_underline_position) { +void FontFile::set_cache_underline_position(int p_cache_index, int p_size, real_t p_underline_position) { ERR_FAIL_COND(p_cache_index < 0); _ensure_rid(p_cache_index); TS->font_set_underline_position(cache[p_cache_index], p_size, p_underline_position); } -real_t FontData::get_underline_position(int p_cache_index, int p_size) const { +real_t FontFile::get_cache_underline_position(int p_cache_index, int p_size) const { ERR_FAIL_COND_V(p_cache_index < 0, 0.f); _ensure_rid(p_cache_index); return TS->font_get_underline_position(cache[p_cache_index], p_size); } -void FontData::set_underline_thickness(int p_cache_index, int p_size, real_t p_underline_thickness) { +void FontFile::set_cache_underline_thickness(int p_cache_index, int p_size, real_t p_underline_thickness) { ERR_FAIL_COND(p_cache_index < 0); _ensure_rid(p_cache_index); TS->font_set_underline_thickness(cache[p_cache_index], p_size, p_underline_thickness); } -real_t FontData::get_underline_thickness(int p_cache_index, int p_size) const { +real_t FontFile::get_cache_underline_thickness(int p_cache_index, int p_size) const { ERR_FAIL_COND_V(p_cache_index < 0, 0.f); _ensure_rid(p_cache_index); return TS->font_get_underline_thickness(cache[p_cache_index], p_size); } -void FontData::set_scale(int p_cache_index, int p_size, real_t p_scale) { +void FontFile::set_cache_scale(int p_cache_index, int p_size, real_t p_scale) { ERR_FAIL_COND(p_cache_index < 0); _ensure_rid(p_cache_index); TS->font_set_scale(cache[p_cache_index], p_size, p_scale); } -real_t FontData::get_scale(int p_cache_index, int p_size) const { +real_t FontFile::get_cache_scale(int p_cache_index, int p_size) const { ERR_FAIL_COND_V(p_cache_index < 0, 0.f); _ensure_rid(p_cache_index); return TS->font_get_scale(cache[p_cache_index], p_size); } -void FontData::set_spacing(int p_cache_index, int p_size, TextServer::SpacingType p_spacing, int p_value) { - ERR_FAIL_COND(p_cache_index < 0); - _ensure_rid(p_cache_index); - TS->font_set_spacing(cache[p_cache_index], p_size, p_spacing, p_value); -} - -int FontData::get_spacing(int p_cache_index, int p_size, TextServer::SpacingType p_spacing) const { - ERR_FAIL_COND_V(p_cache_index < 0, 0); - _ensure_rid(p_cache_index); - return TS->font_get_spacing(cache[p_cache_index], p_size, p_spacing); -} - -int FontData::get_texture_count(int p_cache_index, const Vector2i &p_size) const { +int FontFile::get_texture_count(int p_cache_index, const Vector2i &p_size) const { ERR_FAIL_COND_V(p_cache_index < 0, 0); _ensure_rid(p_cache_index); return TS->font_get_texture_count(cache[p_cache_index], p_size); } -void FontData::clear_textures(int p_cache_index, const Vector2i &p_size) { +void FontFile::clear_textures(int p_cache_index, const Vector2i &p_size) { ERR_FAIL_COND(p_cache_index < 0); _ensure_rid(p_cache_index); TS->font_clear_textures(cache[p_cache_index], p_size); } -void FontData::remove_texture(int p_cache_index, const Vector2i &p_size, int p_texture_index) { +void FontFile::remove_texture(int p_cache_index, const Vector2i &p_size, int p_texture_index) { ERR_FAIL_COND(p_cache_index < 0); _ensure_rid(p_cache_index); TS->font_remove_texture(cache[p_cache_index], p_size, p_texture_index); } -void FontData::set_texture_image(int p_cache_index, const Vector2i &p_size, int p_texture_index, const Ref<Image> &p_image) { +void FontFile::set_texture_image(int p_cache_index, const Vector2i &p_size, int p_texture_index, const Ref<Image> &p_image) { ERR_FAIL_COND(p_cache_index < 0); _ensure_rid(p_cache_index); TS->font_set_texture_image(cache[p_cache_index], p_size, p_texture_index, p_image); } -Ref<Image> FontData::get_texture_image(int p_cache_index, const Vector2i &p_size, int p_texture_index) const { +Ref<Image> FontFile::get_texture_image(int p_cache_index, const Vector2i &p_size, int p_texture_index) const { ERR_FAIL_COND_V(p_cache_index < 0, Ref<Image>()); _ensure_rid(p_cache_index); return TS->font_get_texture_image(cache[p_cache_index], p_size, p_texture_index); } -void FontData::set_texture_offsets(int p_cache_index, const Vector2i &p_size, int p_texture_index, const PackedInt32Array &p_offset) { +void FontFile::set_texture_offsets(int p_cache_index, const Vector2i &p_size, int p_texture_index, const PackedInt32Array &p_offset) { ERR_FAIL_COND(p_cache_index < 0); _ensure_rid(p_cache_index); TS->font_set_texture_offsets(cache[p_cache_index], p_size, p_texture_index, p_offset); } -PackedInt32Array FontData::get_texture_offsets(int p_cache_index, const Vector2i &p_size, int p_texture_index) const { +PackedInt32Array FontFile::get_texture_offsets(int p_cache_index, const Vector2i &p_size, int p_texture_index) const { ERR_FAIL_COND_V(p_cache_index < 0, PackedInt32Array()); _ensure_rid(p_cache_index); return TS->font_get_texture_offsets(cache[p_cache_index], p_size, p_texture_index); } -Array FontData::get_glyph_list(int p_cache_index, const Vector2i &p_size) const { +Array FontFile::get_glyph_list(int p_cache_index, const Vector2i &p_size) const { ERR_FAIL_COND_V(p_cache_index < 0, Array()); _ensure_rid(p_cache_index); return TS->font_get_glyph_list(cache[p_cache_index], p_size); } -void FontData::clear_glyphs(int p_cache_index, const Vector2i &p_size) { +void FontFile::clear_glyphs(int p_cache_index, const Vector2i &p_size) { ERR_FAIL_COND(p_cache_index < 0); _ensure_rid(p_cache_index); TS->font_clear_glyphs(cache[p_cache_index], p_size); } -void FontData::remove_glyph(int p_cache_index, const Vector2i &p_size, int32_t p_glyph) { +void FontFile::remove_glyph(int p_cache_index, const Vector2i &p_size, int32_t p_glyph) { ERR_FAIL_COND(p_cache_index < 0); _ensure_rid(p_cache_index); TS->font_remove_glyph(cache[p_cache_index], p_size, p_glyph); } -void FontData::set_glyph_advance(int p_cache_index, int p_size, int32_t p_glyph, const Vector2 &p_advance) { +void FontFile::set_glyph_advance(int p_cache_index, int p_size, int32_t p_glyph, const Vector2 &p_advance) { ERR_FAIL_COND(p_cache_index < 0); _ensure_rid(p_cache_index); TS->font_set_glyph_advance(cache[p_cache_index], p_size, p_glyph, p_advance); } -Vector2 FontData::get_glyph_advance(int p_cache_index, int p_size, int32_t p_glyph) const { +Vector2 FontFile::get_glyph_advance(int p_cache_index, int p_size, int32_t p_glyph) const { ERR_FAIL_COND_V(p_cache_index < 0, Vector2()); _ensure_rid(p_cache_index); return TS->font_get_glyph_advance(cache[p_cache_index], p_size, p_glyph); } -void FontData::set_glyph_offset(int p_cache_index, const Vector2i &p_size, int32_t p_glyph, const Vector2 &p_offset) { +void FontFile::set_glyph_offset(int p_cache_index, const Vector2i &p_size, int32_t p_glyph, const Vector2 &p_offset) { ERR_FAIL_COND(p_cache_index < 0); _ensure_rid(p_cache_index); TS->font_set_glyph_offset(cache[p_cache_index], p_size, p_glyph, p_offset); } -Vector2 FontData::get_glyph_offset(int p_cache_index, const Vector2i &p_size, int32_t p_glyph) const { +Vector2 FontFile::get_glyph_offset(int p_cache_index, const Vector2i &p_size, int32_t p_glyph) const { ERR_FAIL_COND_V(p_cache_index < 0, Vector2()); _ensure_rid(p_cache_index); return TS->font_get_glyph_offset(cache[p_cache_index], p_size, p_glyph); } -void FontData::set_glyph_size(int p_cache_index, const Vector2i &p_size, int32_t p_glyph, const Vector2 &p_gl_size) { +void FontFile::set_glyph_size(int p_cache_index, const Vector2i &p_size, int32_t p_glyph, const Vector2 &p_gl_size) { ERR_FAIL_COND(p_cache_index < 0); _ensure_rid(p_cache_index); TS->font_set_glyph_size(cache[p_cache_index], p_size, p_glyph, p_gl_size); } -Vector2 FontData::get_glyph_size(int p_cache_index, const Vector2i &p_size, int32_t p_glyph) const { +Vector2 FontFile::get_glyph_size(int p_cache_index, const Vector2i &p_size, int32_t p_glyph) const { ERR_FAIL_COND_V(p_cache_index < 0, Vector2()); _ensure_rid(p_cache_index); return TS->font_get_glyph_size(cache[p_cache_index], p_size, p_glyph); } -void FontData::set_glyph_uv_rect(int p_cache_index, const Vector2i &p_size, int32_t p_glyph, const Rect2 &p_uv_rect) { +void FontFile::set_glyph_uv_rect(int p_cache_index, const Vector2i &p_size, int32_t p_glyph, const Rect2 &p_uv_rect) { ERR_FAIL_COND(p_cache_index < 0); _ensure_rid(p_cache_index); TS->font_set_glyph_uv_rect(cache[p_cache_index], p_size, p_glyph, p_uv_rect); } -Rect2 FontData::get_glyph_uv_rect(int p_cache_index, const Vector2i &p_size, int32_t p_glyph) const { +Rect2 FontFile::get_glyph_uv_rect(int p_cache_index, const Vector2i &p_size, int32_t p_glyph) const { ERR_FAIL_COND_V(p_cache_index < 0, Rect2()); _ensure_rid(p_cache_index); return TS->font_get_glyph_uv_rect(cache[p_cache_index], p_size, p_glyph); } -void FontData::set_glyph_texture_idx(int p_cache_index, const Vector2i &p_size, int32_t p_glyph, int p_texture_idx) { +void FontFile::set_glyph_texture_idx(int p_cache_index, const Vector2i &p_size, int32_t p_glyph, int p_texture_idx) { ERR_FAIL_COND(p_cache_index < 0); _ensure_rid(p_cache_index); TS->font_set_glyph_texture_idx(cache[p_cache_index], p_size, p_glyph, p_texture_idx); } -int FontData::get_glyph_texture_idx(int p_cache_index, const Vector2i &p_size, int32_t p_glyph) const { +int FontFile::get_glyph_texture_idx(int p_cache_index, const Vector2i &p_size, int32_t p_glyph) const { ERR_FAIL_COND_V(p_cache_index < 0, 0); _ensure_rid(p_cache_index); return TS->font_get_glyph_texture_idx(cache[p_cache_index], p_size, p_glyph); } -Array FontData::get_kerning_list(int p_cache_index, int p_size) const { +Array FontFile::get_kerning_list(int p_cache_index, int p_size) const { ERR_FAIL_COND_V(p_cache_index < 0, Array()); _ensure_rid(p_cache_index); return TS->font_get_kerning_list(cache[p_cache_index], p_size); } -void FontData::clear_kerning_map(int p_cache_index, int p_size) { +void FontFile::clear_kerning_map(int p_cache_index, int p_size) { ERR_FAIL_COND(p_cache_index < 0); _ensure_rid(p_cache_index); TS->font_clear_kerning_map(cache[p_cache_index], p_size); } -void FontData::remove_kerning(int p_cache_index, int p_size, const Vector2i &p_glyph_pair) { +void FontFile::remove_kerning(int p_cache_index, int p_size, const Vector2i &p_glyph_pair) { ERR_FAIL_COND(p_cache_index < 0); _ensure_rid(p_cache_index); TS->font_remove_kerning(cache[p_cache_index], p_size, p_glyph_pair); } -void FontData::set_kerning(int p_cache_index, int p_size, const Vector2i &p_glyph_pair, const Vector2 &p_kerning) { +void FontFile::set_kerning(int p_cache_index, int p_size, const Vector2i &p_glyph_pair, const Vector2 &p_kerning) { ERR_FAIL_COND(p_cache_index < 0); _ensure_rid(p_cache_index); TS->font_set_kerning(cache[p_cache_index], p_size, p_glyph_pair, p_kerning); } -Vector2 FontData::get_kerning(int p_cache_index, int p_size, const Vector2i &p_glyph_pair) const { +Vector2 FontFile::get_kerning(int p_cache_index, int p_size, const Vector2i &p_glyph_pair) const { ERR_FAIL_COND_V(p_cache_index < 0, Vector2()); _ensure_rid(p_cache_index); return TS->font_get_kerning(cache[p_cache_index], p_size, p_glyph_pair); } -void FontData::render_range(int p_cache_index, const Vector2i &p_size, char32_t p_start, char32_t p_end) { +void FontFile::render_range(int p_cache_index, const Vector2i &p_size, char32_t p_start, char32_t p_end) { ERR_FAIL_COND(p_cache_index < 0); _ensure_rid(p_cache_index); TS->font_render_range(cache[p_cache_index], p_size, p_start, p_end); } -void FontData::render_glyph(int p_cache_index, const Vector2i &p_size, int32_t p_index) { +void FontFile::render_glyph(int p_cache_index, const Vector2i &p_size, int32_t p_index) { ERR_FAIL_COND(p_cache_index < 0); _ensure_rid(p_cache_index); TS->font_render_glyph(cache[p_cache_index], p_size, p_index); } -RID FontData::get_cache_rid(int p_cache_index) const { - ERR_FAIL_COND_V(p_cache_index < 0, RID()); - _ensure_rid(p_cache_index); - return cache[p_cache_index]; -} - -bool FontData::is_language_supported(const String &p_language) const { - _ensure_rid(0); - return TS->font_is_language_supported(cache[0], p_language); -} - -void FontData::set_language_support_override(const String &p_language, bool p_supported) { +void FontFile::set_language_support_override(const String &p_language, bool p_supported) { _ensure_rid(0); TS->font_set_language_support_override(cache[0], p_language, p_supported); } -bool FontData::get_language_support_override(const String &p_language) const { +bool FontFile::get_language_support_override(const String &p_language) const { _ensure_rid(0); return TS->font_get_language_support_override(cache[0], p_language); } -void FontData::remove_language_support_override(const String &p_language) { +void FontFile::remove_language_support_override(const String &p_language) { _ensure_rid(0); TS->font_remove_language_support_override(cache[0], p_language); } -Vector<String> FontData::get_language_support_overrides() const { +Vector<String> FontFile::get_language_support_overrides() const { _ensure_rid(0); return TS->font_get_language_support_overrides(cache[0]); } -bool FontData::is_script_supported(const String &p_script) const { - _ensure_rid(0); - return TS->font_is_script_supported(cache[0], p_script); -} - -void FontData::set_script_support_override(const String &p_script, bool p_supported) { +void FontFile::set_script_support_override(const String &p_script, bool p_supported) { _ensure_rid(0); TS->font_set_script_support_override(cache[0], p_script, p_supported); } -bool FontData::get_script_support_override(const String &p_script) const { +bool FontFile::get_script_support_override(const String &p_script) const { _ensure_rid(0); return TS->font_get_script_support_override(cache[0], p_script); } -void FontData::remove_script_support_override(const String &p_script) { +void FontFile::remove_script_support_override(const String &p_script) { _ensure_rid(0); TS->font_remove_script_support_override(cache[0], p_script); } -Vector<String> FontData::get_script_support_overrides() const { +Vector<String> FontFile::get_script_support_overrides() const { _ensure_rid(0); return TS->font_get_script_support_overrides(cache[0]); } -void FontData::set_opentype_feature_overrides(const Dictionary &p_overrides) { +void FontFile::set_opentype_feature_overrides(const Dictionary &p_overrides) { _ensure_rid(0); TS->font_set_opentype_feature_overrides(cache[0], p_overrides); } -Dictionary FontData::get_opentype_feature_overrides() const { +Dictionary FontFile::get_opentype_feature_overrides() const { _ensure_rid(0); return TS->font_get_opentype_feature_overrides(cache[0]); } -bool FontData::has_char(char32_t p_char) const { - _ensure_rid(0); - return TS->font_has_char(cache[0], p_char); -} - -String FontData::get_supported_chars() const { - _ensure_rid(0); - return TS->font_get_supported_chars(cache[0]); -} - -int32_t FontData::get_glyph_index(int p_size, char32_t p_char, char32_t p_variation_selector) const { +int32_t FontFile::get_glyph_index(int p_size, char32_t p_char, char32_t p_variation_selector) const { _ensure_rid(0); return TS->font_get_glyph_index(cache[0], p_size, p_char, p_variation_selector); } -Dictionary FontData::get_supported_feature_list() const { - _ensure_rid(0); - return TS->font_supported_feature_list(cache[0]); -} - -Dictionary FontData::get_supported_variation_list() const { - _ensure_rid(0); - return TS->font_supported_variation_list(cache[0]); -} - -FontData::FontData() { +FontFile::FontFile() { /* NOP */ } -FontData::~FontData() { - _clear_cache(); +FontFile::~FontFile() { + reset_state(); } /*************************************************************************/ +/* FontVariation */ +/*************************************************************************/ -void Font::_data_changed() { - for (int i = 0; i < rids.size(); i++) { - rids.write[i] = RID(); - } - emit_changed(); -} - -void Font::_ensure_rid(int p_index) const { - // Find or create cache record. - if (!rids[p_index].is_valid() && data[p_index].is_valid()) { - rids.write[p_index] = data[p_index]->find_cache(variation_coordinates); - } -} +void FontVariation::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_base_font", "font"), &FontVariation::set_base_font); + ClassDB::bind_method(D_METHOD("get_base_font"), &FontVariation::get_base_font); -void Font::_bind_methods() { - ClassDB::bind_method(D_METHOD("add_data", "data"), &Font::add_data); - ClassDB::bind_method(D_METHOD("set_data", "idx", "data"), &Font::set_data); - ClassDB::bind_method(D_METHOD("get_data_count"), &Font::get_data_count); - ClassDB::bind_method(D_METHOD("get_data", "idx"), &Font::get_data); - ClassDB::bind_method(D_METHOD("get_data_rid", "idx"), &Font::get_data_rid); - ClassDB::bind_method(D_METHOD("clear_data"), &Font::clear_data); - ClassDB::bind_method(D_METHOD("remove_data", "idx"), &Font::remove_data); - - ClassDB::bind_method(D_METHOD("set_variation_coordinates", "variation_coordinates"), &Font::set_variation_coordinates); - ClassDB::bind_method(D_METHOD("get_variation_coordinates"), &Font::get_variation_coordinates); - ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "variation_coordinates"), "set_variation_coordinates", "get_variation_coordinates"); - - ClassDB::bind_method(D_METHOD("set_spacing", "spacing", "value"), &Font::set_spacing); - ClassDB::bind_method(D_METHOD("get_spacing", "spacing"), &Font::get_spacing); + ClassDB::bind_method(D_METHOD("set_variation_opentype", "coords"), &FontVariation::set_variation_opentype); + ClassDB::bind_method(D_METHOD("get_variation_opentype"), &FontVariation::get_variation_opentype); - ADD_GROUP("Extra Spacing", "spacing"); - ADD_PROPERTYI(PropertyInfo(Variant::INT, "spacing_top"), "set_spacing", "get_spacing", TextServer::SPACING_TOP); - ADD_PROPERTYI(PropertyInfo(Variant::INT, "spacing_bottom"), "set_spacing", "get_spacing", TextServer::SPACING_BOTTOM); + ClassDB::bind_method(D_METHOD("set_variation_embolden", "strength"), &FontVariation::set_variation_embolden); + ClassDB::bind_method(D_METHOD("get_variation_embolden"), &FontVariation::get_variation_embolden); - ClassDB::bind_method(D_METHOD("get_height", "size"), &Font::get_height, DEFVAL(DEFAULT_FONT_SIZE)); - ClassDB::bind_method(D_METHOD("get_ascent", "size"), &Font::get_ascent, DEFVAL(DEFAULT_FONT_SIZE)); - ClassDB::bind_method(D_METHOD("get_descent", "size"), &Font::get_descent, DEFVAL(DEFAULT_FONT_SIZE)); - ClassDB::bind_method(D_METHOD("get_underline_position", "size"), &Font::get_underline_position, DEFVAL(DEFAULT_FONT_SIZE)); - ClassDB::bind_method(D_METHOD("get_underline_thickness", "size"), &Font::get_underline_thickness, DEFVAL(DEFAULT_FONT_SIZE)); + ClassDB::bind_method(D_METHOD("set_variation_face_index", "face_index"), &FontVariation::set_variation_face_index); + ClassDB::bind_method(D_METHOD("get_variation_face_index"), &FontVariation::get_variation_face_index); - ClassDB::bind_method(D_METHOD("get_string_size", "text", "size", "alignment", "width", "flags"), &Font::get_string_size, DEFVAL(DEFAULT_FONT_SIZE), DEFVAL(HORIZONTAL_ALIGNMENT_LEFT), DEFVAL(-1), DEFVAL(TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND)); - ClassDB::bind_method(D_METHOD("get_multiline_string_size", "text", "width", "size", "flags"), &Font::get_multiline_string_size, DEFVAL(-1), DEFVAL(DEFAULT_FONT_SIZE), DEFVAL(TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND)); + ClassDB::bind_method(D_METHOD("set_variation_transform", "transform"), &FontVariation::set_variation_transform); + ClassDB::bind_method(D_METHOD("get_variation_transform"), &FontVariation::get_variation_transform); - ClassDB::bind_method(D_METHOD("draw_string", "canvas_item", "pos", "text", "alignment", "width", "size", "modulate", "outline_size", "outline_modulate", "flags"), &Font::draw_string, DEFVAL(HORIZONTAL_ALIGNMENT_LEFT), DEFVAL(-1), DEFVAL(DEFAULT_FONT_SIZE), DEFVAL(Color(1, 1, 1)), DEFVAL(0), DEFVAL(Color(1, 1, 1, 0)), DEFVAL(TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND)); - ClassDB::bind_method(D_METHOD("draw_multiline_string", "canvas_item", "pos", "text", "alignment", "width", "max_lines", "size", "modulate", "outline_size", "outline_modulate", "flags"), &Font::draw_multiline_string, DEFVAL(HORIZONTAL_ALIGNMENT_LEFT), DEFVAL(-1), DEFVAL(-1), DEFVAL(DEFAULT_FONT_SIZE), DEFVAL(Color(1, 1, 1)), DEFVAL(0), DEFVAL(Color(1, 1, 1, 0)), DEFVAL(TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND | TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND)); + ClassDB::bind_method(D_METHOD("set_opentype_features", "features"), &FontVariation::set_opentype_features); - ClassDB::bind_method(D_METHOD("get_char_size", "char", "next", "size"), &Font::get_char_size, DEFVAL(0), DEFVAL(DEFAULT_FONT_SIZE)); - ClassDB::bind_method(D_METHOD("draw_char", "canvas_item", "pos", "char", "next", "size", "modulate", "outline_size", "outline_modulate"), &Font::draw_char, DEFVAL(0), DEFVAL(DEFAULT_FONT_SIZE), DEFVAL(Color(1, 1, 1)), DEFVAL(0), DEFVAL(Color(1, 1, 1, 0))); + ClassDB::bind_method(D_METHOD("set_spacing", "spacing", "value"), &FontVariation::set_spacing); - ClassDB::bind_method(D_METHOD("has_char", "char"), &Font::has_char); - ClassDB::bind_method(D_METHOD("get_supported_chars"), &Font::get_supported_chars); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "base_font", PROPERTY_HINT_RESOURCE_TYPE, "Font"), "set_base_font", "get_base_font"); + ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "fallbacks", PROPERTY_HINT_ARRAY_TYPE, vformat("%s/%s:%s", Variant::OBJECT, PROPERTY_HINT_RESOURCE_TYPE, "Font")), "set_fallbacks", "get_fallbacks"); - ClassDB::bind_method(D_METHOD("update_changes"), &Font::update_changes); + ADD_GROUP("Variation", "variation"); + ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "variation_opentype"), "set_variation_opentype", "get_variation_opentype"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "variation_face_index"), "set_variation_face_index", "get_variation_face_index"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "variation_embolden", PROPERTY_HINT_RANGE, "-2,2,0.01"), "set_variation_embolden", "get_variation_embolden"); + ADD_PROPERTY(PropertyInfo(Variant::TRANSFORM2D, "variation_transform", PROPERTY_HINT_NONE, "suffix:px"), "set_variation_transform", "get_variation_transform"); - ClassDB::bind_method(D_METHOD("get_rids"), &Font::get_rids); -} + ADD_GROUP("OpenType Features", "opentype"); + ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "opentype_features"), "set_opentype_features", "get_opentype_features"); -bool Font::_set(const StringName &p_name, const Variant &p_value) { - Vector<String> tokens = p_name.operator String().split("/"); -#ifndef DISABLE_DEPRECATED - if (tokens.size() == 1 && tokens[0] == "font_data") { - // Compatibility, DynamicFont main data. - Ref<FontData> fd = p_value; - if (fd.is_valid()) { - add_data(fd); - return true; - } - return false; - } else if (tokens.size() == 2 && tokens[0] == "fallback") { - // Compatibility, DynamicFont fallback data. - Ref<FontData> fd = p_value; - if (fd.is_valid()) { - add_data(fd); - return true; - } - return false; - } else if (tokens.size() == 1 && tokens[0] == "fallback") { - // Compatibility, BitmapFont fallback data. - Ref<Font> f = p_value; - if (f.is_valid()) { - for (int i = 0; i < f->get_data_count(); i++) { - add_data(f->get_data(i)); - } - return true; - } - return false; - } -#endif /* DISABLE_DEPRECATED */ - if (tokens.size() == 2 && tokens[0] == "data") { - int idx = tokens[1].to_int(); - Ref<FontData> fd = p_value; - if (fd.is_valid()) { - if (idx == data.size()) { - add_data(fd); - return true; - } else if (idx >= 0 && idx < data.size()) { - set_data(idx, fd); - return true; - } else { - return false; - } - } else if (idx >= 0 && idx < data.size()) { - remove_data(idx); - return true; - } - } - return false; + ADD_GROUP("Extra Spacing", "spacing"); + ADD_PROPERTYI(PropertyInfo(Variant::INT, "spacing_glyph", PROPERTY_HINT_NONE, "suffix:px"), "set_spacing", "get_spacing", TextServer::SPACING_GLYPH); + ADD_PROPERTYI(PropertyInfo(Variant::INT, "spacing_space", PROPERTY_HINT_NONE, "suffix:px"), "set_spacing", "get_spacing", TextServer::SPACING_SPACE); + ADD_PROPERTYI(PropertyInfo(Variant::INT, "spacing_top", PROPERTY_HINT_NONE, "suffix:px"), "set_spacing", "get_spacing", TextServer::SPACING_TOP); + ADD_PROPERTYI(PropertyInfo(Variant::INT, "spacing_bottom", PROPERTY_HINT_NONE, "suffix:px"), "set_spacing", "get_spacing", TextServer::SPACING_BOTTOM); } -bool Font::_get(const StringName &p_name, Variant &r_ret) const { - Vector<String> tokens = p_name.operator String().split("/"); - if (tokens.size() == 2 && tokens[0] == "data") { - int idx = tokens[1].to_int(); +void FontVariation::_update_rids() const { + Ref<Font> f = _get_base_font_or_default(); - if (idx == data.size()) { - r_ret = Ref<FontData>(); - return true; - } else if (idx >= 0 && idx < data.size()) { - r_ret = get_data(idx); - return true; + rids.clear(); + if (fallbacks.is_empty() && f.is_valid()) { + RID rid = _get_rid(); + if (rid.is_valid()) { + rids.push_back(rid); } - } - - return false; -} -void Font::_get_property_list(List<PropertyInfo> *p_list) const { - for (int i = 0; i < data.size(); i++) { - p_list->push_back(PropertyInfo(Variant::OBJECT, "data/" + itos(i), PROPERTY_HINT_RESOURCE_TYPE, "FontData")); + const TypedArray<Font> &base_fallbacks = f->get_fallbacks(); + for (int i = 0; i < base_fallbacks.size(); i++) { + _update_rids_fb(base_fallbacks[i], 0); + } + } else { + _update_rids_fb(const_cast<FontVariation *>(this), 0); } - p_list->push_back(PropertyInfo(Variant::OBJECT, "data/" + itos(data.size()), PROPERTY_HINT_RESOURCE_TYPE, "FontData")); + dirty_rids = false; } -void Font::reset_state() { - for (int i = 0; i < data.size(); i++) { - if (data[i].is_valid()) { - data.write[i]->connect(SNAME("changed"), callable_mp(this, &Font::_data_changed), varray(), CONNECT_REFERENCE_COUNTED); - } +void FontVariation::reset_state() { + if (base_font.is_valid()) { + base_font->disconnect(CoreStringNames::get_singleton()->changed, callable_mp(reinterpret_cast<Font *>(this), &Font::_invalidate_rids)); + base_font.unref(); } - cache.clear(); - cache_wrap.clear(); - data.clear(); - rids.clear(); - variation_coordinates.clear(); - spacing_bottom = 0; - spacing_top = 0; -} - -Dictionary Font::get_feature_list() const { - Dictionary out; - for (int i = 0; i < data.size(); i++) { - Dictionary data_ftrs = data[i]->get_supported_feature_list(); - for (const Variant *ftr = data_ftrs.next(nullptr); ftr != nullptr; ftr = data_ftrs.next(ftr)) { - out[*ftr] = data_ftrs[*ftr]; - } + if (theme_font.is_valid()) { + theme_font->disconnect(CoreStringNames::get_singleton()->changed, callable_mp(reinterpret_cast<Font *>(this), &Font::_invalidate_rids)); + theme_font.unref(); } - return out; -} -void Font::add_data(const Ref<FontData> &p_data) { - ERR_FAIL_COND(p_data.is_null()); - data.push_back(p_data); - rids.push_back(RID()); + variation = Variation(); + opentype_features = Dictionary(); - if (data[data.size() - 1].is_valid()) { - data.write[data.size() - 1]->connect(SNAME("changed"), callable_mp(this, &Font::_data_changed), varray(), CONNECT_REFERENCE_COUNTED); - Dictionary data_var_list = p_data->get_supported_variation_list(); - for (int j = 0; j < data_var_list.size(); j++) { - int32_t tag = data_var_list.get_key_at_index(j); - Vector3i value = data_var_list.get_value_at_index(j); - if (!variation_coordinates.has(tag) && !variation_coordinates.has(TS->tag_to_name(tag))) { - variation_coordinates[TS->tag_to_name(tag)] = value.z; - } - } + for (int i = 0; i < TextServer::SPACING_MAX; i++) { + extra_spacing[i] = 0; } - cache.clear(); - cache_wrap.clear(); - - emit_changed(); - notify_property_list_changed(); + Font::reset_state(); } -void Font::set_data(int p_idx, const Ref<FontData> &p_data) { - ERR_FAIL_COND(p_data.is_null()); - ERR_FAIL_INDEX(p_idx, data.size()); - - if (data[p_idx].is_valid()) { - data.write[p_idx]->disconnect(SNAME("changed"), callable_mp(this, &Font::_data_changed)); - } - - data.write[p_idx] = p_data; - rids.write[p_idx] = RID(); - Dictionary data_var_list = p_data->get_supported_variation_list(); - for (int j = 0; j < data_var_list.size(); j++) { - int32_t tag = data_var_list.get_key_at_index(j); - Vector3i value = data_var_list.get_value_at_index(j); - if (!variation_coordinates.has(tag) && !variation_coordinates.has(TS->tag_to_name(tag))) { - variation_coordinates[TS->tag_to_name(tag)] = value.z; +void FontVariation::set_base_font(const Ref<Font> &p_font) { + if (base_font != p_font) { + if (base_font.is_valid()) { + base_font->disconnect(CoreStringNames::get_singleton()->changed, callable_mp(reinterpret_cast<Font *>(this), &Font::_invalidate_rids)); } + base_font = p_font; + if (base_font.is_valid()) { + base_font->connect(CoreStringNames::get_singleton()->changed, callable_mp(reinterpret_cast<Font *>(this), &Font::_invalidate_rids), varray(), CONNECT_REFERENCE_COUNTED); + } + _invalidate_rids(); + notify_property_list_changed(); } - - if (data[p_idx].is_valid()) { - data.write[p_idx]->connect(SNAME("changed"), callable_mp(this, &Font::_data_changed), varray(), CONNECT_REFERENCE_COUNTED); - } - - cache.clear(); - cache_wrap.clear(); - - emit_changed(); - notify_property_list_changed(); -} - -int Font::get_data_count() const { - return data.size(); -} - -Ref<FontData> Font::get_data(int p_idx) const { - ERR_FAIL_INDEX_V(p_idx, data.size(), Ref<FontData>()); - return data[p_idx]; } -RID Font::get_data_rid(int p_idx) const { - ERR_FAIL_INDEX_V(p_idx, data.size(), RID()); - _ensure_rid(p_idx); - return rids[p_idx]; +Ref<Font> FontVariation::get_base_font() const { + return base_font; } -void Font::clear_data() { - for (int i = 0; i < data.size(); i++) { - if (data[i].is_valid()) { - data.write[i]->connect(SNAME("changed"), callable_mp(this, &Font::_data_changed), varray(), CONNECT_REFERENCE_COUNTED); - } +Ref<Font> FontVariation::_get_base_font_or_default() const { + if (theme_font.is_valid()) { + theme_font->disconnect(CoreStringNames::get_singleton()->changed, callable_mp(reinterpret_cast<Font *>(const_cast<FontVariation *>(this)), &Font::_invalidate_rids)); + theme_font.unref(); } - data.clear(); - rids.clear(); -} - -void Font::remove_data(int p_idx) { - ERR_FAIL_INDEX(p_idx, data.size()); - if (data[p_idx].is_valid()) { - data.write[p_idx]->disconnect(SNAME("changed"), callable_mp(this, &Font::_data_changed)); + if (base_font.is_valid()) { + return base_font; } - data.remove_at(p_idx); - rids.remove_at(p_idx); - - cache.clear(); - cache_wrap.clear(); + // Check the project-defined Theme resource. + if (Theme::get_project_default().is_valid()) { + List<StringName> theme_types; + Theme::get_project_default()->get_type_dependencies(get_class_name(), StringName(), &theme_types); - emit_changed(); - notify_property_list_changed(); -} + for (const StringName &E : theme_types) { + if (Theme::get_project_default()->has_theme_item(Theme::DATA_TYPE_FONT, "font", E)) { + Ref<Font> f = Theme::get_project_default()->get_theme_item(Theme::DATA_TYPE_FONT, "font", E); + if (f.is_valid()) { + theme_font = f; + theme_font->connect(CoreStringNames::get_singleton()->changed, callable_mp(reinterpret_cast<Font *>(const_cast<FontVariation *>(this)), &Font::_invalidate_rids), varray(), CONNECT_REFERENCE_COUNTED); + } + return f; + } + } + } -void Font::set_variation_coordinates(const Dictionary &p_variation_coordinates) { - _data_changed(); - variation_coordinates = p_variation_coordinates; -} + // Lastly, fall back on the items defined in the default Theme, if they exist. + if (Theme::get_default().is_valid()) { + List<StringName> theme_types; + Theme::get_default()->get_type_dependencies(get_class_name(), StringName(), &theme_types); -Dictionary Font::get_variation_coordinates() const { - return variation_coordinates; -} + for (const StringName &E : theme_types) { + if (Theme::get_default()->has_theme_item(Theme::DATA_TYPE_FONT, "font", E)) { + Ref<Font> f = Theme::get_default()->get_theme_item(Theme::DATA_TYPE_FONT, "font", E); + if (f.is_valid()) { + theme_font = f; + theme_font->connect(CoreStringNames::get_singleton()->changed, callable_mp(reinterpret_cast<Font *>(const_cast<FontVariation *>(this)), &Font::_invalidate_rids), varray(), CONNECT_REFERENCE_COUNTED); + } + return f; + } + } -void Font::set_spacing(TextServer::SpacingType p_spacing, int p_value) { - _data_changed(); - switch (p_spacing) { - case TextServer::SPACING_TOP: { - spacing_top = p_value; - } break; - case TextServer::SPACING_BOTTOM: { - spacing_bottom = p_value; - } break; - default: { - ERR_FAIL_MSG("Invalid spacing type: " + itos(p_spacing)); - } break; + // If they don't exist, use any type to return the default/empty value. + Ref<Font> f = Theme::get_default()->get_theme_item(Theme::DATA_TYPE_FONT, "font", StringName()); + if (f.is_valid()) { + theme_font = f; + theme_font->connect(CoreStringNames::get_singleton()->changed, callable_mp(reinterpret_cast<Font *>(const_cast<FontVariation *>(this)), &Font::_invalidate_rids), varray(), CONNECT_REFERENCE_COUNTED); + } + return f; } -} -int Font::get_spacing(TextServer::SpacingType p_spacing) const { - switch (p_spacing) { - case TextServer::SPACING_TOP: { - return spacing_top; - } break; - case TextServer::SPACING_BOTTOM: { - return spacing_bottom; - } break; - default: { - ERR_FAIL_V_MSG(0, "Invalid spacing type: " + itos(p_spacing)); - } break; - } + return Ref<Font>(); } -real_t Font::get_height(int p_size) const { - real_t ret = 0.f; - for (int i = 0; i < data.size(); i++) { - _ensure_rid(i); - ret = MAX(ret, TS->font_get_ascent(rids[i], p_size) + TS->font_get_descent(rids[i], p_size)); +void FontVariation::set_variation_opentype(const Dictionary &p_coords) { + if (variation.opentype != p_coords) { + variation.opentype = p_coords; + _invalidate_rids(); } - return ret + spacing_bottom + spacing_top; } -real_t Font::get_ascent(int p_size) const { - real_t ret = 0.f; - for (int i = 0; i < data.size(); i++) { - _ensure_rid(i); - ret = MAX(ret, TS->font_get_ascent(rids[i], p_size)); - } - return ret + spacing_top; +Dictionary FontVariation::get_variation_opentype() const { + return variation.opentype; } -real_t Font::get_descent(int p_size) const { - real_t ret = 0.f; - for (int i = 0; i < data.size(); i++) { - _ensure_rid(i); - ret = MAX(ret, TS->font_get_descent(rids[i], p_size)); +void FontVariation::set_variation_embolden(float p_strength) { + if (variation.embolden != p_strength) { + variation.embolden = p_strength; + _invalidate_rids(); } - return ret + spacing_bottom; } -real_t Font::get_underline_position(int p_size) const { - real_t ret = 0.f; - for (int i = 0; i < data.size(); i++) { - _ensure_rid(i); - ret = MAX(ret, TS->font_get_underline_position(rids[i], p_size)); - } - return ret + spacing_top; +float FontVariation::get_variation_embolden() const { + return variation.embolden; } -real_t Font::get_underline_thickness(int p_size) const { - real_t ret = 0.f; - for (int i = 0; i < data.size(); i++) { - _ensure_rid(i); - ret = MAX(ret, TS->font_get_underline_thickness(rids[i], p_size)); +void FontVariation::set_variation_transform(Transform2D p_transform) { + if (variation.transform != p_transform) { + variation.transform = p_transform; + _invalidate_rids(); } - return ret; } -Size2 Font::get_string_size(const String &p_text, int p_size, HorizontalAlignment p_alignment, float p_width, uint16_t p_flags) const { - ERR_FAIL_COND_V(data.is_empty(), Size2()); - - for (int i = 0; i < data.size(); i++) { - _ensure_rid(i); - } - - uint64_t hash = p_text.hash64(); - if (p_alignment == HORIZONTAL_ALIGNMENT_FILL) { - hash = hash_djb2_one_64(hash_djb2_one_float(p_width), hash); - hash = hash_djb2_one_64(p_flags, hash); - } - hash = hash_djb2_one_64(p_size, hash); - - Ref<TextLine> buffer; - if (cache.has(hash)) { - buffer = cache.get(hash); - } else { - buffer.instantiate(); - buffer->add_string(p_text, Ref<Font>(this), p_size, Dictionary(), TranslationServer::get_singleton()->get_tool_locale()); - cache.insert(hash, buffer); - } - return buffer->get_size(); +Transform2D FontVariation::get_variation_transform() const { + return variation.transform; } -Size2 Font::get_multiline_string_size(const String &p_text, float p_width, int p_size, uint16_t p_flags) const { - ERR_FAIL_COND_V(data.is_empty(), Size2()); - - for (int i = 0; i < data.size(); i++) { - _ensure_rid(i); - } - - uint64_t hash = p_text.hash64(); - uint64_t wrp_hash = hash_djb2_one_64(hash_djb2_one_float(p_width), hash); - wrp_hash = hash_djb2_one_64(p_flags, wrp_hash); - wrp_hash = hash_djb2_one_64(p_size, wrp_hash); - - Ref<TextParagraph> lines_buffer; - if (cache_wrap.has(wrp_hash)) { - lines_buffer = cache_wrap.get(wrp_hash); - } else { - lines_buffer.instantiate(); - lines_buffer->add_string(p_text, Ref<Font>(this), p_size, Dictionary(), TranslationServer::get_singleton()->get_tool_locale()); - lines_buffer->set_width(p_width); - lines_buffer->set_flags(p_flags); - cache_wrap.insert(wrp_hash, lines_buffer); - } - - Size2 ret; - for (int i = 0; i < lines_buffer->get_line_count(); i++) { - Size2 line_size = lines_buffer->get_line_size(i); - if (lines_buffer->get_orientation() == TextServer::ORIENTATION_HORIZONTAL) { - ret.x = MAX(ret.x, line_size.x); - ret.y += line_size.y; - } else { - ret.y = MAX(ret.y, line_size.y); - ret.x += line_size.x; - } +void FontVariation::set_variation_face_index(int p_face_index) { + if (variation.face_index != p_face_index) { + variation.face_index = p_face_index; + _invalidate_rids(); } - return ret; } -void Font::draw_string(RID p_canvas_item, const Point2 &p_pos, const String &p_text, HorizontalAlignment p_alignment, float p_width, int p_size, const Color &p_modulate, int p_outline_size, const Color &p_outline_modulate, uint16_t p_flags) const { - ERR_FAIL_COND(data.is_empty()); - - for (int i = 0; i < data.size(); i++) { - _ensure_rid(i); - } - - uint64_t hash = p_text.hash64(); - if (p_alignment == HORIZONTAL_ALIGNMENT_FILL) { - hash = hash_djb2_one_64(hash_djb2_one_float(p_width), hash); - hash = hash_djb2_one_64(p_flags, hash); - } - hash = hash_djb2_one_64(p_size, hash); - - Ref<TextLine> buffer; - if (cache.has(hash)) { - buffer = cache.get(hash); - } else { - buffer.instantiate(); - buffer->add_string(p_text, Ref<Font>(this), p_size, Dictionary(), TranslationServer::get_singleton()->get_tool_locale()); - cache.insert(hash, buffer); - } - - Vector2 ofs = p_pos; - if (buffer->get_orientation() == TextServer::ORIENTATION_HORIZONTAL) { - ofs.y -= buffer->get_line_ascent(); - } else { - ofs.x -= buffer->get_line_ascent(); - } - - buffer->set_width(p_width); - buffer->set_horizontal_alignment(p_alignment); - buffer->set_flags(p_flags); - - if (p_outline_size > 0 && p_outline_modulate.a != 0.0f) { - buffer->draw_outline(p_canvas_item, ofs, p_outline_size, p_outline_modulate); - } - buffer->draw(p_canvas_item, ofs, p_modulate); +int FontVariation::get_variation_face_index() const { + return variation.face_index; } -void Font::draw_multiline_string(RID p_canvas_item, const Point2 &p_pos, const String &p_text, HorizontalAlignment p_alignment, float p_width, int p_max_lines, int p_size, const Color &p_modulate, int p_outline_size, const Color &p_outline_modulate, uint16_t p_flags) const { - ERR_FAIL_COND(data.is_empty()); - - for (int i = 0; i < data.size(); i++) { - _ensure_rid(i); - } - - uint64_t hash = p_text.hash64(); - uint64_t wrp_hash = hash_djb2_one_64(hash_djb2_one_float(p_width), hash); - wrp_hash = hash_djb2_one_64(p_flags, wrp_hash); - wrp_hash = hash_djb2_one_64(p_size, wrp_hash); - - Ref<TextParagraph> lines_buffer; - if (cache_wrap.has(wrp_hash)) { - lines_buffer = cache_wrap.get(wrp_hash); - } else { - lines_buffer.instantiate(); - lines_buffer->add_string(p_text, Ref<Font>(this), p_size, Dictionary(), TranslationServer::get_singleton()->get_tool_locale()); - lines_buffer->set_width(p_width); - lines_buffer->set_flags(p_flags); - cache_wrap.insert(wrp_hash, lines_buffer); - } - - lines_buffer->set_alignment(p_alignment); - - Vector2 lofs = p_pos; - for (int i = 0; i < lines_buffer->get_line_count(); i++) { - if (lines_buffer->get_orientation() == TextServer::ORIENTATION_HORIZONTAL) { - if (i == 0) { - lofs.y -= lines_buffer->get_line_ascent(0); - } - } else { - if (i == 0) { - lofs.x -= lines_buffer->get_line_ascent(0); - } - } - if (p_width > 0) { - lines_buffer->set_alignment(p_alignment); - } - - if (p_outline_size > 0 && p_outline_modulate.a != 0.0f) { - lines_buffer->draw_line_outline(p_canvas_item, lofs, i, p_outline_size, p_outline_modulate); - } - lines_buffer->draw_line(p_canvas_item, lofs, i, p_modulate); - - Size2 line_size = lines_buffer->get_line_size(i); - if (lines_buffer->get_orientation() == TextServer::ORIENTATION_HORIZONTAL) { - lofs.y += line_size.y; - } else { - lofs.x += line_size.x; - } - - if ((p_max_lines > 0) && (i >= p_max_lines)) { - return; - } +void FontVariation::set_opentype_features(const Dictionary &p_features) { + if (opentype_features != p_features) { + opentype_features = p_features; + _invalidate_rids(); } } -Size2 Font::get_char_size(char32_t p_char, char32_t p_next, int p_size) const { - for (int i = 0; i < data.size(); i++) { - _ensure_rid(i); - if (data[i]->has_char(p_char)) { - int32_t glyph_a = TS->font_get_glyph_index(rids[i], p_size, p_char, 0); - Size2 ret = Size2(TS->font_get_glyph_advance(rids[i], p_size, glyph_a).x, TS->font_get_ascent(rids[i], p_size) + TS->font_get_descent(rids[i], p_size)); - if ((p_next != 0) && data[i]->has_char(p_next)) { - int32_t glyph_b = TS->font_get_glyph_index(rids[i], p_size, p_next, 0); - ret.x -= TS->font_get_kerning(rids[i], p_size, Vector2i(glyph_a, glyph_b)).x; - } - return ret; - } - } - return Size2(); +Dictionary FontVariation::get_opentype_features() const { + return opentype_features; } -real_t Font::draw_char(RID p_canvas_item, const Point2 &p_pos, char32_t p_char, char32_t p_next, int p_size, const Color &p_modulate, int p_outline_size, const Color &p_outline_modulate) const { - for (int i = 0; i < data.size(); i++) { - _ensure_rid(i); - if (data[i]->has_char(p_char)) { - int32_t glyph_a = TS->font_get_glyph_index(rids[i], p_size, p_char, 0); - real_t ret = TS->font_get_glyph_advance(rids[i], p_size, glyph_a).x; - if ((p_next != 0) && data[i]->has_char(p_next)) { - int32_t glyph_b = TS->font_get_glyph_index(rids[i], p_size, p_next, 0); - ret -= TS->font_get_kerning(rids[i], p_size, Vector2i(glyph_a, glyph_b)).x; - } - - if (p_outline_size > 0 && p_outline_modulate.a != 0.0f) { - TS->font_draw_glyph_outline(rids[i], p_canvas_item, p_size, p_outline_size, p_pos, glyph_a, p_outline_modulate); - } - TS->font_draw_glyph(rids[i], p_canvas_item, p_size, p_pos, glyph_a, p_modulate); - return ret; - } +void FontVariation::set_spacing(TextServer::SpacingType p_spacing, int p_value) { + ERR_FAIL_INDEX((int)p_spacing, TextServer::SPACING_MAX); + if (extra_spacing[p_spacing] != p_value) { + extra_spacing[p_spacing] = p_value; + _invalidate_rids(); } - return 0; } -bool Font::has_char(char32_t p_char) const { - for (int i = 0; i < data.size(); i++) { - if (data[i]->has_char(p_char)) { - return true; - } - } - return false; +int FontVariation::get_spacing(TextServer::SpacingType p_spacing) const { + ERR_FAIL_INDEX_V((int)p_spacing, TextServer::SPACING_MAX, 0); + return extra_spacing[p_spacing]; } -String Font::get_supported_chars() const { - String chars; - for (int i = 0; i < data.size(); i++) { - String data_chars = data[i]->get_supported_chars(); - for (int j = 0; j < data_chars.length(); j++) { - if (chars.find_char(data_chars[j]) == -1) { - chars += data_chars[j]; - } - } +RID FontVariation::find_variation(const Dictionary &p_variation_coordinates, int p_face_index, float p_strength, Transform2D p_transform) const { + Ref<Font> f = _get_base_font_or_default(); + if (f.is_valid()) { + return f->find_variation(p_variation_coordinates, p_face_index, p_strength, p_transform); } - return chars; + return RID(); } -Array Font::get_rids() const { - Array _rids; - for (int i = 0; i < data.size(); i++) { - _ensure_rid(i); - if (rids[i].is_valid()) { - _rids.push_back(rids[i]); - } +RID FontVariation::_get_rid() const { + Ref<Font> f = _get_base_font_or_default(); + if (f.is_valid()) { + return f->find_variation(variation.opentype, variation.face_index, variation.embolden, variation.transform); } - return _rids; -} - -void Font::update_changes() { - emit_changed(); + return RID(); } -Font::Font() { - cache.set_capacity(128); - cache_wrap.set_capacity(32); +FontVariation::FontVariation() { + for (int i = 0; i < TextServer::SPACING_MAX; i++) { + extra_spacing[i] = 0; + } } -Font::~Font() { - clear_data(); - cache.clear(); - cache_wrap.clear(); +FontVariation::~FontVariation() { + reset_state(); } diff --git a/scene/resources/font.h b/scene/resources/font.h index 2aa12dd2de..7a42a4dfea 100644 --- a/scene/resources/font.h +++ b/scene/resources/font.h @@ -33,14 +33,107 @@ #include "core/io/resource.h" #include "core/templates/lru.h" -#include "core/templates/map.h" +#include "core/templates/rb_map.h" #include "scene/resources/texture.h" #include "servers/text_server.h" +class TextLine; +class TextParagraph; + +/*************************************************************************/ +/* Font */ /*************************************************************************/ -class FontData : public Resource { - GDCLASS(FontData, Resource); +class Font : public Resource { + GDCLASS(Font, Resource); + + // Shaped string cache. + mutable LRUCache<uint64_t, Ref<TextLine>> cache; + mutable LRUCache<uint64_t, Ref<TextParagraph>> cache_wrap; + +protected: + // Output. + mutable TypedArray<RID> rids; + mutable bool dirty_rids = true; + + // Fallbacks. + static constexpr int MAX_FALLBACK_DEPTH = 64; + TypedArray<Font> fallbacks; + + static void _bind_methods(); + + virtual void _update_rids_fb(const Ref<Font> &p_f, int p_depth) const; + virtual void _update_rids() const; + virtual bool _is_cyclic(const Ref<Font> &p_f, int p_depth) const; + + virtual void reset_state() override; + +public: + virtual void _invalidate_rids(); + + static constexpr int DEFAULT_FONT_SIZE = 16; + + // Fallbacks. + virtual void set_fallbacks(const TypedArray<Font> &p_fallbacks); + virtual TypedArray<Font> get_fallbacks() const; + + // Output. + virtual RID find_variation(const Dictionary &p_variation_coordinates, int p_face_index = 0, float p_strength = 0.0, Transform2D p_transform = Transform2D()) const { return RID(); }; + virtual RID _get_rid() const { return RID(); }; + virtual TypedArray<RID> get_rids() const; + + // Font metrics. + virtual real_t get_height(int p_font_size) const; + virtual real_t get_ascent(int p_font_size) const; + virtual real_t get_descent(int p_font_size) const; + virtual real_t get_underline_position(int p_font_size) const; + virtual real_t get_underline_thickness(int p_font_size) const; + + virtual String get_font_name() const; + virtual String get_font_style_name() const; + virtual BitField<TextServer::FontStyle> get_font_style() const; + + virtual int get_spacing(TextServer::SpacingType p_spacing) const { return 0; }; + virtual Dictionary get_opentype_features() const; + + // Drawing string. + virtual void set_cache_capacity(int p_single_line, int p_multi_line); + + virtual Size2 get_string_size(const String &p_text, HorizontalAlignment p_alignment = HORIZONTAL_ALIGNMENT_LEFT, float p_width = -1, int p_font_size = DEFAULT_FONT_SIZE, BitField<TextServer::JustificationFlag> p_jst_flags = TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND, TextServer::Direction p_direction = TextServer::DIRECTION_AUTO, TextServer::Orientation p_orientation = TextServer::ORIENTATION_HORIZONTAL) const; + virtual Size2 get_multiline_string_size(const String &p_text, HorizontalAlignment p_alignment = HORIZONTAL_ALIGNMENT_LEFT, float p_width = -1, int p_font_size = DEFAULT_FONT_SIZE, int p_max_lines = -1, BitField<TextServer::LineBreakFlag> p_brk_flags = TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND, BitField<TextServer::JustificationFlag> p_jst_flags = TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND, TextServer::Direction p_direction = TextServer::DIRECTION_AUTO, TextServer::Orientation p_orientation = TextServer::ORIENTATION_HORIZONTAL) const; + + virtual void draw_string(RID p_canvas_item, const Point2 &p_pos, const String &p_text, HorizontalAlignment p_alignment = HORIZONTAL_ALIGNMENT_LEFT, float p_width = -1, int p_font_size = DEFAULT_FONT_SIZE, const Color &p_modulate = Color(1.0, 1.0, 1.0), BitField<TextServer::JustificationFlag> p_jst_flags = TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND, TextServer::Direction p_direction = TextServer::DIRECTION_AUTO, TextServer::Orientation p_orientation = TextServer::ORIENTATION_HORIZONTAL) const; + virtual void draw_multiline_string(RID p_canvas_item, const Point2 &p_pos, const String &p_text, HorizontalAlignment p_alignment = HORIZONTAL_ALIGNMENT_LEFT, float p_width = -1, int p_font_size = DEFAULT_FONT_SIZE, int p_max_lines = -1, const Color &p_modulate = Color(1.0, 1.0, 1.0), BitField<TextServer::LineBreakFlag> p_brk_flags = TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND, BitField<TextServer::JustificationFlag> p_jst_flags = TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND, TextServer::Direction p_direction = TextServer::DIRECTION_AUTO, TextServer::Orientation p_orientation = TextServer::ORIENTATION_HORIZONTAL) const; + + virtual void draw_string_outline(RID p_canvas_item, const Point2 &p_pos, const String &p_text, HorizontalAlignment p_alignment = HORIZONTAL_ALIGNMENT_LEFT, float p_width = -1, int p_font_size = DEFAULT_FONT_SIZE, int p_size = 1, const Color &p_modulate = Color(1.0, 1.0, 1.0), BitField<TextServer::JustificationFlag> p_jst_flags = TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND, TextServer::Direction p_direction = TextServer::DIRECTION_AUTO, TextServer::Orientation p_orientation = TextServer::ORIENTATION_HORIZONTAL) const; + virtual void draw_multiline_string_outline(RID p_canvas_item, const Point2 &p_pos, const String &p_text, HorizontalAlignment p_alignment = HORIZONTAL_ALIGNMENT_LEFT, float p_width = -1, int p_font_size = DEFAULT_FONT_SIZE, int p_max_lines = -1, int p_size = 1, const Color &p_modulate = Color(1.0, 1.0, 1.0), BitField<TextServer::LineBreakFlag> p_brk_flags = TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND, BitField<TextServer::JustificationFlag> p_jst_flags = TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND, TextServer::Direction p_direction = TextServer::DIRECTION_AUTO, TextServer::Orientation p_orientation = TextServer::ORIENTATION_HORIZONTAL) const; + + // Drawing char. + virtual Size2 get_char_size(char32_t p_char, int p_font_size = DEFAULT_FONT_SIZE) const; + virtual real_t draw_char(RID p_canvas_item, const Point2 &p_pos, char32_t p_char, int p_font_size = DEFAULT_FONT_SIZE, const Color &p_modulate = Color(1.0, 1.0, 1.0)) const; + virtual real_t draw_char_outline(RID p_canvas_item, const Point2 &p_pos, char32_t p_char, int p_font_size = DEFAULT_FONT_SIZE, int p_size = 1, const Color &p_modulate = Color(1.0, 1.0, 1.0)) const; + + // Helper functions. + virtual bool has_char(char32_t p_char) const; + virtual String get_supported_chars() const; + + virtual bool is_language_supported(const String &p_language) const; + virtual bool is_script_supported(const String &p_script) const; + + virtual Dictionary get_supported_feature_list() const; + virtual Dictionary get_supported_variation_list() const; + virtual int64_t get_face_count() const; + + Font(); + ~Font(); +}; + +/*************************************************************************/ +/* FontFile */ +/*************************************************************************/ + +class FontFile : public Font { + GDCLASS(FontFile, Font); RES_BASE_EXTENSION("fontdata"); // Font source data. @@ -49,6 +142,7 @@ class FontData : public Resource { PackedByteArray data; bool antialiased = true; + bool mipmaps = false; bool msdf = false; int msdf_pixel_range = 16; int msdf_size = 48; @@ -57,8 +151,11 @@ class FontData : public Resource { TextServer::Hinting hinting = TextServer::HINTING_LIGHT; TextServer::SubpixelPositioning subpixel_positioning = TextServer::SUBPIXEL_POSITIONING_AUTO; real_t oversampling = 0.f; - real_t embolden = 0.f; - Transform2D transform; + +#ifndef DISABLE_DEPRECATED + real_t bmp_height = 0.0; + real_t bmp_ascent = 0.0; +#endif // Cache. mutable Vector<RID> cache; @@ -92,17 +189,15 @@ public: // 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_font_style(BitField<TextServer::FontStyle> p_style); virtual void set_antialiased(bool p_antialiased); virtual bool is_antialiased() const; + virtual void set_generate_mipmaps(bool p_generate_mipmaps); + virtual bool get_generate_mipmaps() const; + virtual void set_multichannel_signed_distance_field(bool p_msdf); virtual bool is_multichannel_signed_distance_field() const; @@ -124,17 +219,12 @@ public: virtual void set_subpixel_positioning(TextServer::SubpixelPositioning p_subpixel); virtual TextServer::SubpixelPositioning get_subpixel_positioning() const; - virtual void set_embolden(float p_strength); - virtual float get_embolden() const; - - virtual void set_transform(Transform2D p_transform); - virtual Transform2D get_transform() const; - virtual void set_oversampling(real_t p_oversampling); virtual real_t get_oversampling() const; // Cache. - virtual RID find_cache(const Dictionary &p_variation_coordinates) const; + virtual RID find_variation(const Dictionary &p_variation_coordinates, int p_face_index = 0, float p_strength = 0.0, Transform2D p_transform = Transform2D()) const override; + virtual RID _get_rid() const override; virtual int get_cache_count() const; virtual void clear_cache(); @@ -147,23 +237,29 @@ public: virtual void set_variation_coordinates(int p_cache_index, const Dictionary &p_variation_coordinates); virtual Dictionary get_variation_coordinates(int p_cache_index) const; - virtual void set_ascent(int p_cache_index, int p_size, real_t p_ascent); - virtual real_t get_ascent(int p_cache_index, int p_size) const; + virtual void set_embolden(int p_cache_index, float p_strength); + virtual float get_embolden(int p_cache_index) const; + + virtual void set_transform(int p_cache_index, Transform2D p_transform); + virtual Transform2D get_transform(int p_cache_index) const; - virtual void set_descent(int p_cache_index, int p_size, real_t p_descent); - virtual real_t get_descent(int p_cache_index, int p_size) const; + virtual void set_face_index(int p_cache_index, int64_t p_index); + virtual int64_t get_face_index(int p_cache_index) const; - virtual void set_underline_position(int p_cache_index, int p_size, real_t p_underline_position); - virtual real_t get_underline_position(int p_cache_index, int p_size) const; + virtual void set_cache_ascent(int p_cache_index, int p_size, real_t p_ascent); + virtual real_t get_cache_ascent(int p_cache_index, int p_size) const; - virtual void set_underline_thickness(int p_cache_index, int p_size, real_t p_underline_thickness); - virtual real_t get_underline_thickness(int p_cache_index, int p_size) const; + virtual void set_cache_descent(int p_cache_index, int p_size, real_t p_descent); + virtual real_t get_cache_descent(int p_cache_index, int p_size) const; - virtual void set_scale(int p_cache_index, int p_size, real_t p_scale); // Rendering scale for bitmap fonts (e.g. emoji fonts). - virtual real_t get_scale(int p_cache_index, int p_size) const; + virtual void set_cache_underline_position(int p_cache_index, int p_size, real_t p_underline_position); + virtual real_t get_cache_underline_position(int p_cache_index, int p_size) const; - virtual void set_spacing(int p_cache_index, int p_size, TextServer::SpacingType p_spacing, int p_value); - virtual int get_spacing(int p_cache_index, int p_size, TextServer::SpacingType p_spacing) const; + virtual void set_cache_underline_thickness(int p_cache_index, int p_size, real_t p_underline_thickness); + virtual real_t get_cache_underline_thickness(int p_cache_index, int p_size) const; + + virtual void set_cache_scale(int p_cache_index, int p_size, real_t p_scale); // Rendering scale for bitmap fonts (e.g. emoji fonts). + virtual real_t get_cache_scale(int p_cache_index, int p_size) const; virtual int get_texture_count(int p_cache_index, const Vector2i &p_size) const; virtual void clear_textures(int p_cache_index, const Vector2i &p_size); @@ -204,16 +300,12 @@ public: virtual void render_range(int p_cache_index, const Vector2i &p_size, char32_t p_start, char32_t p_end); virtual void render_glyph(int p_cache_index, const Vector2i &p_size, int32_t p_index); - virtual RID get_cache_rid(int p_cache_index) const; - // Language/script support override. - virtual bool is_language_supported(const String &p_language) const; virtual void set_language_support_override(const String &p_language, bool p_supported); virtual bool get_language_support_override(const String &p_language) const; virtual void remove_language_support_override(const String &p_language); virtual Vector<String> get_language_support_overrides() const; - virtual bool is_script_supported(const String &p_script) const; virtual void set_script_support_override(const String &p_script, bool p_supported); virtual bool get_script_support_override(const String &p_script) const; virtual void remove_script_support_override(const String &p_script); @@ -223,100 +315,70 @@ public: virtual Dictionary get_opentype_feature_overrides() const; // Base font properties. - virtual bool has_char(char32_t p_char) const; - virtual String get_supported_chars() const; - virtual int32_t get_glyph_index(int p_size, char32_t p_char, char32_t p_variation_selector = 0x0000) const; - virtual Dictionary get_supported_feature_list() const; - virtual Dictionary get_supported_variation_list() const; - - FontData(); - ~FontData(); + FontFile(); + ~FontFile(); }; /*************************************************************************/ +/* FontVariation */ +/*************************************************************************/ -class TextLine; -class TextParagraph; - -class Font : public Resource { - GDCLASS(Font, Resource); +class FontVariation : public Font { + GDCLASS(FontVariation, Font); - // Shaped string cache. - mutable LRUCache<uint64_t, Ref<TextLine>> cache; - mutable LRUCache<uint64_t, Ref<TextParagraph>> cache_wrap; + struct Variation { + Dictionary opentype; + real_t embolden = 0.f; + int face_index = 0; + Transform2D transform; + }; - // Font data cache. - Vector<Ref<FontData>> data; - mutable Vector<RID> rids; + mutable Ref<Font> theme_font; - // Font config. - Dictionary variation_coordinates; - int spacing_bottom = 0; - int spacing_top = 0; + Ref<Font> base_font; - _FORCE_INLINE_ void _data_changed(); - _FORCE_INLINE_ void _ensure_rid(int p_index) const; // Find or create cache record. + Variation variation; + Dictionary opentype_features; + int extra_spacing[TextServer::SPACING_MAX]; protected: static void _bind_methods(); - bool _set(const StringName &p_name, const Variant &p_value); - bool _get(const StringName &p_name, Variant &r_ret) const; - void _get_property_list(List<PropertyInfo> *p_list) const; + virtual void _update_rids() const override; virtual void reset_state() override; public: - static const int DEFAULT_FONT_SIZE = 16; - - Dictionary get_feature_list() const; - - // Font data. - virtual void add_data(const Ref<FontData> &p_data); - virtual void set_data(int p_idx, const Ref<FontData> &p_data); - virtual int get_data_count() const; - virtual Ref<FontData> get_data(int p_idx) const; - virtual RID get_data_rid(int p_idx) const; - virtual void clear_data(); - virtual void remove_data(int p_idx); + virtual void set_base_font(const Ref<Font> &p_font); + virtual Ref<Font> get_base_font() const; + virtual Ref<Font> _get_base_font_or_default() const; - // Font configuration. - virtual void set_variation_coordinates(const Dictionary &p_variation_coordinates); - virtual Dictionary get_variation_coordinates() const; + virtual void set_variation_opentype(const Dictionary &p_coords); + virtual Dictionary get_variation_opentype() const; - virtual void set_spacing(TextServer::SpacingType p_spacing, int p_value); - virtual int get_spacing(TextServer::SpacingType p_spacing) const; - - // Font metrics. - virtual real_t get_height(int p_size = DEFAULT_FONT_SIZE) const; - virtual real_t get_ascent(int p_size = DEFAULT_FONT_SIZE) const; - virtual real_t get_descent(int p_size = DEFAULT_FONT_SIZE) const; - virtual real_t get_underline_position(int p_size = DEFAULT_FONT_SIZE) const; - virtual real_t get_underline_thickness(int p_size = DEFAULT_FONT_SIZE) const; + virtual void set_variation_embolden(float p_strength); + virtual float get_variation_embolden() const; - // Drawing string. - virtual Size2 get_string_size(const String &p_text, int p_size = DEFAULT_FONT_SIZE, HorizontalAlignment p_alignment = HORIZONTAL_ALIGNMENT_LEFT, float p_width = -1, uint16_t p_flags = TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND) const; - virtual Size2 get_multiline_string_size(const String &p_text, float p_width = -1, int p_size = DEFAULT_FONT_SIZE, uint16_t p_flags = TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND) const; - - virtual void draw_string(RID p_canvas_item, const Point2 &p_pos, const String &p_text, HorizontalAlignment p_alignment = HORIZONTAL_ALIGNMENT_LEFT, float p_width = -1, int p_size = DEFAULT_FONT_SIZE, const Color &p_modulate = Color(1, 1, 1), int p_outline_size = 0, const Color &p_outline_modulate = Color(1, 1, 1, 0), uint16_t p_flags = TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND) const; - virtual void draw_multiline_string(RID p_canvas_item, const Point2 &p_pos, const String &p_text, HorizontalAlignment p_alignment = HORIZONTAL_ALIGNMENT_LEFT, float p_width = -1, int p_max_lines = -1, int p_size = DEFAULT_FONT_SIZE, const Color &p_modulate = Color(1, 1, 1), int p_outline_size = 0, const Color &p_outline_modulate = Color(1, 1, 1, 0), uint16_t p_flags = TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND | TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND) const; + virtual void set_variation_transform(Transform2D p_transform); + virtual Transform2D get_variation_transform() const; - // Helper functions. - virtual bool has_char(char32_t p_char) const; - virtual String get_supported_chars() const; + virtual void set_variation_face_index(int p_face_index); + virtual int get_variation_face_index() const; - // Drawing char. - virtual Size2 get_char_size(char32_t p_char, char32_t p_next = 0, int p_size = DEFAULT_FONT_SIZE) const; - virtual real_t draw_char(RID p_canvas_item, const Point2 &p_pos, char32_t p_char, char32_t p_next = 0, int p_size = DEFAULT_FONT_SIZE, const Color &p_modulate = Color(1, 1, 1), int p_outline_size = 0, const Color &p_outline_modulate = Color(1, 1, 1, 0)) const; + virtual void set_opentype_features(const Dictionary &p_features); + virtual Dictionary get_opentype_features() const override; - Array get_rids() const; + virtual void set_spacing(TextServer::SpacingType p_spacing, int p_value); + virtual int get_spacing(TextServer::SpacingType p_spacing) const override; - void update_changes(); + // Output. + virtual RID find_variation(const Dictionary &p_variation_coordinates, int p_face_index = 0, float p_strength = 0.0, Transform2D p_transform = Transform2D()) const override; + virtual RID _get_rid() const override; - Font(); - ~Font(); + FontVariation(); + ~FontVariation(); }; #endif /* FONT_H */ diff --git a/scene/resources/gradient.cpp b/scene/resources/gradient.cpp index caaa3d8628..a9c44dc6bf 100644 --- a/scene/resources/gradient.cpp +++ b/scene/resources/gradient.cpp @@ -157,7 +157,7 @@ void Gradient::reverse() { emit_signal(CoreStringNames::get_singleton()->changed); } -void Gradient::set_points(Vector<Gradient::Point> &p_points) { +void Gradient::set_points(const Vector<Gradient::Point> &p_points) { points = p_points; is_sorted = false; emit_signal(CoreStringNames::get_singleton()->changed); diff --git a/scene/resources/gradient.h b/scene/resources/gradient.h index c2085b3a13..e4bac15e4b 100644 --- a/scene/resources/gradient.h +++ b/scene/resources/gradient.h @@ -73,7 +73,7 @@ public: void add_point(float p_offset, const Color &p_color); void remove_point(int p_index); - void set_points(Vector<Point> &p_points); + void set_points(const Vector<Point> &p_points); Vector<Point> &get_points(); void reverse(); @@ -92,10 +92,6 @@ public: 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); @@ -161,10 +157,10 @@ public: 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); + float r = Math::cubic_interpolate(pointFirst.color.r, pointSecond.color.r, pointP0.color.r, pointP3.color.r, x); + float g = Math::cubic_interpolate(pointFirst.color.g, pointSecond.color.g, pointP0.color.g, pointP3.color.g, x); + float b = Math::cubic_interpolate(pointFirst.color.b, pointSecond.color.b, pointP0.color.b, pointP3.color.b, x); + float a = Math::cubic_interpolate(pointFirst.color.a, pointSecond.color.a, pointP0.color.a, pointP3.color.a, x); return Color(r, g, b, a); } break; diff --git a/scene/resources/immediate_mesh.cpp b/scene/resources/immediate_mesh.cpp index 28afef8638..044477e744 100644 --- a/scene/resources/immediate_mesh.cpp +++ b/scene/resources/immediate_mesh.cpp @@ -211,7 +211,7 @@ void ImmediateMesh::surface_end() { value |= CLAMP(int((t.normal.y * 0.5 + 0.5) * 1023.0), 0, 1023) << 10; value |= CLAMP(int((t.normal.z * 0.5 + 0.5) * 1023.0), 0, 1023) << 20; if (t.d > 0) { - value |= 3 << 30; + value |= 3UL << 30; } *tangent = value; diff --git a/scene/resources/importer_mesh.cpp b/scene/resources/importer_mesh.cpp index 60a9200176..293fdd6f05 100644 --- a/scene/resources/importer_mesh.cpp +++ b/scene/resources/importer_mesh.cpp @@ -306,7 +306,7 @@ void ImporterMesh::generate_lods(float p_normal_merge_angle, float p_normal_spli float normal_split_threshold = Math::cos(Math::deg2rad(p_normal_split_angle)); const Vector3 *normals_ptr = normals.ptr(); - Map<Vector3, LocalVector<Pair<int, int>>> unique_vertices; + HashMap<Vector3, LocalVector<Pair<int, int>>> unique_vertices; LocalVector<int> vertex_remap; LocalVector<int> vertex_inverse_remap; @@ -320,10 +320,10 @@ void ImporterMesh::generate_lods(float p_normal_merge_angle, float p_normal_spli const Vector3 &v = vertices_ptr[j]; const Vector3 &n = normals_ptr[j]; - Map<Vector3, LocalVector<Pair<int, int>>>::Element *E = unique_vertices.find(v); + HashMap<Vector3, LocalVector<Pair<int, int>>>::Iterator E = unique_vertices.find(v); if (E) { - const LocalVector<Pair<int, int>> &close_verts = E->get(); + const LocalVector<Pair<int, int>> &close_verts = E->value; bool found = false; for (unsigned int k = 0; k < close_verts.size(); k++) { @@ -331,6 +331,7 @@ void ImporterMesh::generate_lods(float p_normal_merge_angle, float p_normal_spli bool is_uvs_close = (!uvs_ptr || uvs_ptr[j].distance_squared_to(uvs_ptr[idx.second]) < CMP_EPSILON2); bool is_uv2s_close = (!uv2s_ptr || uv2s_ptr[j].distance_squared_to(uv2s_ptr[idx.second]) < CMP_EPSILON2); + ERR_FAIL_INDEX(idx.second, normals.size()); bool is_normals_close = normals[idx.second].dot(n) > normal_merge_threshold; if (is_uvs_close && is_uv2s_close && is_normals_close) { vertex_remap.push_back(idx.first); @@ -706,15 +707,15 @@ void ImporterMesh::create_shadow_mesh() { Vector<Vector3> vertices = surfaces[i].arrays[RS::ARRAY_VERTEX]; int vertex_count = vertices.size(); { - Map<Vector3, int> unique_vertices; + HashMap<Vector3, int> unique_vertices; const Vector3 *vptr = vertices.ptr(); for (int j = 0; j < vertex_count; j++) { const Vector3 &v = vptr[j]; - Map<Vector3, int>::Element *E = unique_vertices.find(v); + HashMap<Vector3, int>::Iterator E = unique_vertices.find(v); if (E) { - vertex_remap.push_back(E->get()); + vertex_remap.push_back(E->value); } else { int vcount = unique_vertices.size(); unique_vertices[v] = vcount; @@ -898,16 +899,16 @@ Vector<Ref<Shape3D>> ImporterMesh::convex_decompose(const Mesh::ConvexDecomposit Vector<uint32_t> indices; indices.resize(face_count * 3); { - Map<Vector3, uint32_t> vertex_map; + HashMap<Vector3, uint32_t> vertex_map; Vector3 *vertex_w = vertices.ptrw(); uint32_t *index_w = indices.ptrw(); for (int i = 0; i < face_count; i++) { for (int j = 0; j < 3; j++) { const Vector3 &vertex = faces[i].vertex[j]; - Map<Vector3, uint32_t>::Element *found_vertex = vertex_map.find(vertex); + HashMap<Vector3, uint32_t>::Iterator found_vertex = vertex_map.find(vertex); uint32_t index; if (found_vertex) { - index = found_vertex->get(); + index = found_vertex->value; } else { index = ++vertex_count; vertex_map[vertex] = index; @@ -960,7 +961,7 @@ Ref<NavigationMesh> ImporterMesh::create_navigation_mesh() { return Ref<NavigationMesh>(); } - Map<Vector3, int> unique_vertices; + HashMap<Vector3, int> unique_vertices; LocalVector<int> face_indices; for (int i = 0; i < faces.size(); i++) { @@ -1023,7 +1024,7 @@ Error ImporterMesh::lightmap_unwrap_cached(const Transform3D &p_base_transform, // Keep only the scale Basis basis = p_base_transform.get_basis(); - Vector3 scale = Vector3(basis.get_axis(0).length(), basis.get_axis(1).length(), basis.get_axis(2).length()); + Vector3 scale = Vector3(basis.get_column(0).length(), basis.get_column(1).length(), basis.get_column(2).length()); Transform3D transform; transform.scale(scale); @@ -1046,6 +1047,10 @@ Error ImporterMesh::lightmap_unwrap_cached(const Transform3D &p_base_transform, PackedVector3Array rnormals = arrays[Mesh::ARRAY_NORMAL]; + if (!rnormals.size()) { + continue; + } + int vertex_ofs = vertices.size() / 3; vertices.resize((vertex_ofs + vc) * 3); @@ -1086,6 +1091,9 @@ Error ImporterMesh::lightmap_unwrap_cached(const Transform3D &p_base_transform, } else { for (int j = 0; j < ic / 3; j++) { + ERR_FAIL_INDEX_V(rindices[j * 3 + 0], rvertices.size(), ERR_INVALID_DATA); + ERR_FAIL_INDEX_V(rindices[j * 3 + 1], rvertices.size(), ERR_INVALID_DATA); + ERR_FAIL_INDEX_V(rindices[j * 3 + 2], rvertices.size(), ERR_INVALID_DATA); Vector3 p0 = transform.xform(rvertices[rindices[j * 3 + 0]]); Vector3 p1 = transform.xform(rvertices[rindices[j * 3 + 1]]); Vector3 p2 = transform.xform(rvertices[rindices[j * 3 + 2]]); @@ -1185,7 +1193,7 @@ Error ImporterMesh::lightmap_unwrap_cached(const Transform3D &p_base_transform, for (unsigned int i = 0; i < surfaces_tools.size(); i++) { surfaces_tools[i]->index(); Array arrays = surfaces_tools[i]->commit_to_arrays(); - add_surface(surfaces_tools[i]->get_primitive(), arrays, Array(), Dictionary(), surfaces_tools[i]->get_material(), surfaces_tools[i]->get_meta("name")); + add_surface(surfaces_tools[i]->get_primitive_type(), arrays, Array(), Dictionary(), surfaces_tools[i]->get_material(), surfaces_tools[i]->get_meta("name")); } set_lightmap_size_hint(Size2(size_x, size_y)); @@ -1238,6 +1246,7 @@ void ImporterMesh::_bind_methods() { ClassDB::bind_method(D_METHOD("set_surface_name", "surface_idx", "name"), &ImporterMesh::set_surface_name); ClassDB::bind_method(D_METHOD("set_surface_material", "surface_idx", "material"), &ImporterMesh::set_surface_material); + ClassDB::bind_method(D_METHOD("generate_lods", "normal_merge_angle", "normal_split_angle"), &ImporterMesh::generate_lods); ClassDB::bind_method(D_METHOD("get_mesh", "base_mesh"), &ImporterMesh::get_mesh, DEFVAL(Ref<ArrayMesh>())); ClassDB::bind_method(D_METHOD("clear"), &ImporterMesh::clear); diff --git a/scene/resources/label_settings.cpp b/scene/resources/label_settings.cpp new file mode 100644 index 0000000000..e8b986b431 --- /dev/null +++ b/scene/resources/label_settings.cpp @@ -0,0 +1,187 @@ +/*************************************************************************/ +/* label_settings.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 "label_settings.h" + +#include "core/core_string_names.h" + +void LabelSettings::_font_changed() { + emit_changed(); +} + +void LabelSettings::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_line_spacing", "spacing"), &LabelSettings::set_line_spacing); + ClassDB::bind_method(D_METHOD("get_line_spacing"), &LabelSettings::get_line_spacing); + + ClassDB::bind_method(D_METHOD("set_font", "font"), &LabelSettings::set_font); + ClassDB::bind_method(D_METHOD("get_font"), &LabelSettings::get_font); + + ClassDB::bind_method(D_METHOD("set_font_size", "size"), &LabelSettings::set_font_size); + ClassDB::bind_method(D_METHOD("get_font_size"), &LabelSettings::get_font_size); + + ClassDB::bind_method(D_METHOD("set_font_color", "color"), &LabelSettings::set_font_color); + ClassDB::bind_method(D_METHOD("get_font_color"), &LabelSettings::get_font_color); + + ClassDB::bind_method(D_METHOD("set_outline_size", "size"), &LabelSettings::set_outline_size); + ClassDB::bind_method(D_METHOD("get_outline_size"), &LabelSettings::get_outline_size); + + ClassDB::bind_method(D_METHOD("set_outline_color", "color"), &LabelSettings::set_outline_color); + ClassDB::bind_method(D_METHOD("get_outline_color"), &LabelSettings::get_outline_color); + + ClassDB::bind_method(D_METHOD("set_shadow_size", "size"), &LabelSettings::set_shadow_size); + ClassDB::bind_method(D_METHOD("get_shadow_size"), &LabelSettings::get_shadow_size); + + ClassDB::bind_method(D_METHOD("set_shadow_color", "color"), &LabelSettings::set_shadow_color); + ClassDB::bind_method(D_METHOD("get_shadow_color"), &LabelSettings::get_shadow_color); + + ClassDB::bind_method(D_METHOD("set_shadow_offset", "offset"), &LabelSettings::set_shadow_offset); + ClassDB::bind_method(D_METHOD("get_shadow_offset"), &LabelSettings::get_shadow_offset); + + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "line_spacing", PROPERTY_HINT_NONE, "suffix:px"), "set_line_spacing", "get_line_spacing"); + + ADD_GROUP("Font", "font"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "font", PROPERTY_HINT_RESOURCE_TYPE, "Font"), "set_font", "get_font"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "font_size", PROPERTY_HINT_RANGE, "1,1024,1,or_greater,suffix:px"), "set_font_size", "get_font_size"); + ADD_PROPERTY(PropertyInfo(Variant::COLOR, "font_color"), "set_font_color", "get_font_color"); + + ADD_GROUP("Outline", "outline"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "outline_size", PROPERTY_HINT_RANGE, "0,127,1,or_greater,suffix:px"), "set_outline_size", "get_outline_size"); + ADD_PROPERTY(PropertyInfo(Variant::COLOR, "outline_color"), "set_outline_color", "get_outline_color"); + + ADD_GROUP("Shadow", "shadow"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "shadow_size", PROPERTY_HINT_RANGE, "0,127,1,or_greater,suffix:px"), "set_shadow_size", "get_shadow_size"); + ADD_PROPERTY(PropertyInfo(Variant::COLOR, "shadow_color"), "set_shadow_color", "get_shadow_color"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "shadow_offset", PROPERTY_HINT_NONE, "suffix:px"), "set_shadow_offset", "get_shadow_offset"); +} + +void LabelSettings::set_line_spacing(real_t p_spacing) { + if (line_spacing != p_spacing) { + line_spacing = p_spacing; + emit_changed(); + } +} + +real_t LabelSettings::get_line_spacing() const { + return line_spacing; +} + +void LabelSettings::set_font(const Ref<Font> &p_font) { + if (font != p_font) { + if (font.is_valid()) { + font->disconnect(CoreStringNames::get_singleton()->changed, callable_mp(this, &LabelSettings::_font_changed)); + } + font = p_font; + if (font.is_valid()) { + font->connect(CoreStringNames::get_singleton()->changed, callable_mp(this, &LabelSettings::_font_changed), varray(), CONNECT_REFERENCE_COUNTED); + } + emit_changed(); + } +} + +Ref<Font> LabelSettings::get_font() const { + return font; +} + +void LabelSettings::set_font_size(int p_size) { + if (font_size != p_size) { + font_size = p_size; + emit_changed(); + } +} + +int LabelSettings::get_font_size() const { + return font_size; +} + +void LabelSettings::set_font_color(const Color &p_color) { + if (font_color != p_color) { + font_color = p_color; + emit_changed(); + } +} + +Color LabelSettings::get_font_color() const { + return font_color; +} + +void LabelSettings::set_outline_size(int p_size) { + if (outline_size != p_size) { + outline_size = p_size; + emit_changed(); + } +} + +int LabelSettings::get_outline_size() const { + return outline_size; +} + +void LabelSettings::set_outline_color(const Color &p_color) { + if (outline_color != p_color) { + outline_color = p_color; + emit_changed(); + } +} + +Color LabelSettings::get_outline_color() const { + return outline_color; +} + +void LabelSettings::set_shadow_size(int p_size) { + if (shadow_size != p_size) { + shadow_size = p_size; + emit_changed(); + } +} + +int LabelSettings::get_shadow_size() const { + return shadow_size; +} + +void LabelSettings::set_shadow_color(const Color &p_color) { + if (shadow_color != p_color) { + shadow_color = p_color; + emit_changed(); + } +} + +Color LabelSettings::get_shadow_color() const { + return shadow_color; +} + +void LabelSettings::set_shadow_offset(const Vector2 &p_offset) { + if (shadow_offset != p_offset) { + shadow_offset = p_offset; + emit_changed(); + } +} + +Vector2 LabelSettings::get_shadow_offset() const { + return shadow_offset; +} diff --git a/scene/resources/label_settings.h b/scene/resources/label_settings.h new file mode 100644 index 0000000000..d2644a7484 --- /dev/null +++ b/scene/resources/label_settings.h @@ -0,0 +1,89 @@ +/*************************************************************************/ +/* label_settings.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 LABEL_SETTINGS_H +#define LABEL_SETTINGS_H + +#include "core/io/resource.h" +#include "font.h" + +/*************************************************************************/ + +class LabelSettings : public Resource { + GDCLASS(LabelSettings, Resource); + + real_t line_spacing = 0; + + Ref<Font> font; + int font_size = Font::DEFAULT_FONT_SIZE; + Color font_color = Color(0.875, 0.875, 0.875); + + int outline_size = 0; + Color outline_color = Color(1, 1, 1); + + int shadow_size = 0; + Color shadow_color = Color(1, 1, 1); + Vector2 shadow_offset = Vector2(1, 1); + + void _font_changed(); + +protected: + static void _bind_methods(); + +public: + void set_line_spacing(real_t p_spacing); + real_t get_line_spacing() const; + + void set_font(const Ref<Font> &p_font); + Ref<Font> get_font() const; + + void set_font_size(int p_size); + int get_font_size() const; + + void set_font_color(const Color &p_color); + Color get_font_color() const; + + void set_outline_size(int p_size); + int get_outline_size() const; + + void set_outline_color(const Color &p_color); + Color get_outline_color() const; + + void set_shadow_size(int p_size); + int get_shadow_size() const; + + void set_shadow_color(const Color &p_color); + Color get_shadow_color() const; + + void set_shadow_offset(const Vector2 &p_offset); + Vector2 get_shadow_offset() const; +}; + +#endif // LABEL_SETTINGS_H diff --git a/scene/resources/material.cpp b/scene/resources/material.cpp index 430626b008..b7a3b677f5 100644 --- a/scene/resources/material.cpp +++ b/scene/resources/material.cpp @@ -84,7 +84,7 @@ void Material::inspect_native_shader_code() { SceneTree *st = Object::cast_to<SceneTree>(OS::get_singleton()->get_main_loop()); RID shader = get_shader_rid(); if (st && shader.is_valid()) { - st->call_group("_native_shader_source_visualizer", "_inspect_shader", shader); + st->call_group_flags(SceneTree::GROUP_CALL_DEFERRED, "_native_shader_source_visualizer", "_inspect_shader", shader); } } @@ -187,9 +187,9 @@ bool ShaderMaterial::_get(const StringName &p_name, Variant &r_ret) const { } if (pr) { - const Map<StringName, Variant>::Element *E = param_cache.find(pr); + HashMap<StringName, Variant>::ConstIterator E = param_cache.find(pr); if (E) { - r_ret = E->get(); + r_ret = E->value; } else { r_ret = Variant(); } @@ -348,7 +348,7 @@ ShaderMaterial::~ShaderMaterial() { Mutex BaseMaterial3D::material_mutex; SelfList<BaseMaterial3D>::List *BaseMaterial3D::dirty_materials = nullptr; -Map<BaseMaterial3D::MaterialKey, BaseMaterial3D::ShaderData> BaseMaterial3D::shader_map; +HashMap<BaseMaterial3D::MaterialKey, BaseMaterial3D::ShaderData, BaseMaterial3D::MaterialKey> BaseMaterial3D::shader_map; BaseMaterial3D::ShaderNames *BaseMaterial3D::shader_names = nullptr; void BaseMaterial3D::init_shaders() { @@ -395,6 +395,9 @@ void BaseMaterial3D::init_shaders() { shader_names->distance_fade_min = "distance_fade_min"; shader_names->distance_fade_max = "distance_fade_max"; + shader_names->msdf_pixel_range = "msdf_pixel_range"; + shader_names->msdf_outline_size = "msdf_outline_size"; + shader_names->metallic_texture_channel = "metallic_texture_channel"; shader_names->ao_texture_channel = "ao_texture_channel"; shader_names->clearcoat_texture_channel = "clearcoat_texture_channel"; @@ -432,12 +435,10 @@ void BaseMaterial3D::init_shaders() { shader_names->albedo_texture_size = "albedo_texture_size"; } -Ref<StandardMaterial3D> BaseMaterial3D::materials_for_2d[BaseMaterial3D::MAX_MATERIALS_FOR_2D]; +HashMap<uint64_t, Ref<StandardMaterial3D>> BaseMaterial3D::materials_for_2d; void BaseMaterial3D::finish_shaders() { - for (int i = 0; i < MAX_MATERIALS_FOR_2D; i++) { - materials_for_2d[i].unref(); - } + materials_for_2d.clear(); memdelete(dirty_materials); dirty_materials = nullptr; @@ -471,24 +472,33 @@ void BaseMaterial3D::_update_shader() { } String texfilter_str; + // Force linear filtering for the heightmap texture, as the heightmap effect + // looks broken with nearest-neighbor filtering (with and without Deep Parallax). + String texfilter_height_str; switch (texture_filter) { case TEXTURE_FILTER_NEAREST: texfilter_str = "filter_nearest"; + texfilter_height_str = "filter_linear"; break; case TEXTURE_FILTER_LINEAR: texfilter_str = "filter_linear"; + texfilter_height_str = "filter_linear"; break; case TEXTURE_FILTER_NEAREST_WITH_MIPMAPS: texfilter_str = "filter_nearest_mipmap"; + texfilter_height_str = "filter_linear_mipmap"; break; case TEXTURE_FILTER_LINEAR_WITH_MIPMAPS: texfilter_str = "filter_linear_mipmap"; + texfilter_height_str = "filter_linear_mipmap"; break; case TEXTURE_FILTER_NEAREST_WITH_MIPMAPS_ANISOTROPIC: texfilter_str = "filter_nearest_mipmap_anisotropic"; + texfilter_height_str = "filter_linear_mipmap_anisotropic"; break; case TEXTURE_FILTER_LINEAR_WITH_MIPMAPS_ANISOTROPIC: texfilter_str = "filter_linear_mipmap_anisotropic"; + texfilter_height_str = "filter_linear_mipmap_anisotropic"; break; case TEXTURE_FILTER_MAX: break; // Internal value, skip. @@ -496,8 +506,10 @@ void BaseMaterial3D::_update_shader() { if (flags[FLAG_USE_TEXTURE_REPEAT]) { texfilter_str += ",repeat_enable"; + texfilter_height_str += ",repeat_enable"; } else { texfilter_str += ",repeat_disable"; + texfilter_height_str += ",repeat_disable"; } //must create a shader! @@ -630,8 +642,8 @@ void BaseMaterial3D::_update_shader() { code += ";\n"; - code += "uniform vec4 albedo : hint_color;\n"; - code += "uniform sampler2D texture_albedo : hint_albedo," + texfilter_str + ";\n"; + code += "uniform vec4 albedo : source_color;\n"; + code += "uniform sampler2D texture_albedo : source_color," + texfilter_str + ";\n"; if (grow_enabled) { code += "uniform float grow;\n"; } @@ -644,6 +656,11 @@ void BaseMaterial3D::_update_shader() { code += "uniform float distance_fade_max;\n"; } + if (flags[FLAG_ALBEDO_TEXTURE_MSDF]) { + code += "uniform float msdf_pixel_range;\n"; + code += "uniform float msdf_outline_size;\n"; + } + // alpha scissor is only valid if there is not antialiasing edge // alpha hash is valid whenever, but not with alpha scissor if (transparency == TRANSPARENCY_ALPHA_SCISSOR) { @@ -663,7 +680,7 @@ void BaseMaterial3D::_update_shader() { //TODO ALL HINTS if (!orm) { code += "uniform float roughness : hint_range(0,1);\n"; - code += "uniform sampler2D texture_metallic : hint_white," + texfilter_str + ";\n"; + code += "uniform sampler2D texture_metallic : hint_default_white," + texfilter_str + ";\n"; code += "uniform vec4 metallic_texture_channel;\n"; switch (roughness_texture_channel) { case TEXTURE_CHANNEL_RED: { @@ -698,8 +715,8 @@ void BaseMaterial3D::_update_shader() { } if (features[FEATURE_EMISSION]) { - code += "uniform sampler2D texture_emission : hint_black_albedo," + texfilter_str + ";\n"; - code += "uniform vec4 emission : hint_color;\n"; + code += "uniform sampler2D texture_emission : source_color, hint_default_black," + texfilter_str + ";\n"; + code += "uniform vec4 emission : source_color;\n"; code += "uniform float emission_energy;\n"; } @@ -716,48 +733,48 @@ void BaseMaterial3D::_update_shader() { if (features[FEATURE_RIM]) { code += "uniform float rim : hint_range(0,1);\n"; code += "uniform float rim_tint : hint_range(0,1);\n"; - code += "uniform sampler2D texture_rim : hint_white," + texfilter_str + ";\n"; + code += "uniform sampler2D texture_rim : hint_default_white," + texfilter_str + ";\n"; } if (features[FEATURE_CLEARCOAT]) { code += "uniform float clearcoat : hint_range(0,1);\n"; code += "uniform float clearcoat_roughness : hint_range(0,1);\n"; - code += "uniform sampler2D texture_clearcoat : hint_white," + texfilter_str + ";\n"; + code += "uniform sampler2D texture_clearcoat : hint_default_white," + texfilter_str + ";\n"; } if (features[FEATURE_ANISOTROPY]) { code += "uniform float anisotropy_ratio : hint_range(0,256);\n"; code += "uniform sampler2D texture_flowmap : hint_anisotropy," + texfilter_str + ";\n"; } if (features[FEATURE_AMBIENT_OCCLUSION]) { - code += "uniform sampler2D texture_ambient_occlusion : hint_white, " + texfilter_str + ";\n"; + code += "uniform sampler2D texture_ambient_occlusion : hint_default_white, " + texfilter_str + ";\n"; code += "uniform vec4 ao_texture_channel;\n"; code += "uniform float ao_light_affect;\n"; } if (features[FEATURE_DETAIL]) { - code += "uniform sampler2D texture_detail_albedo : hint_albedo," + texfilter_str + ";\n"; + code += "uniform sampler2D texture_detail_albedo : source_color," + texfilter_str + ";\n"; code += "uniform sampler2D texture_detail_normal : hint_normal," + texfilter_str + ";\n"; - code += "uniform sampler2D texture_detail_mask : hint_white," + texfilter_str + ";\n"; + code += "uniform sampler2D texture_detail_mask : hint_default_white," + texfilter_str + ";\n"; } if (features[FEATURE_SUBSURFACE_SCATTERING]) { code += "uniform float subsurface_scattering_strength : hint_range(0,1);\n"; - code += "uniform sampler2D texture_subsurface_scattering : hint_white," + texfilter_str + ";\n"; + code += "uniform sampler2D texture_subsurface_scattering : hint_default_white," + texfilter_str + ";\n"; } if (features[FEATURE_SUBSURFACE_TRANSMITTANCE]) { - code += "uniform vec4 transmittance_color : hint_color;\n"; + code += "uniform vec4 transmittance_color : source_color;\n"; code += "uniform float transmittance_depth;\n"; - code += "uniform sampler2D texture_subsurface_transmittance : hint_white," + texfilter_str + ";\n"; + code += "uniform sampler2D texture_subsurface_transmittance : hint_default_white," + texfilter_str + ";\n"; code += "uniform float transmittance_boost;\n"; } if (features[FEATURE_BACKLIGHT]) { - code += "uniform vec4 backlight : hint_color;\n"; - code += "uniform sampler2D texture_backlight : hint_black," + texfilter_str + ";\n"; + code += "uniform vec4 backlight : source_color;\n"; + code += "uniform sampler2D texture_backlight : hint_default_black," + texfilter_str + ";\n"; } if (features[FEATURE_HEIGHT_MAPPING]) { - code += "uniform sampler2D texture_heightmap : hint_black," + texfilter_str + ";\n"; + code += "uniform sampler2D texture_heightmap : hint_default_black," + texfilter_height_str + ";\n"; code += "uniform float heightmap_scale;\n"; code += "uniform int heightmap_min_layers;\n"; code += "uniform int heightmap_max_layers;\n"; @@ -911,6 +928,12 @@ void BaseMaterial3D::_update_shader() { code += "}\n"; code += "\n\n"; + if (flags[FLAG_ALBEDO_TEXTURE_MSDF]) { + code += "float msdf_median(float r, float g, float b, float a) {\n"; + code += " return min(max(min(r, g), min(max(r, g), b)), a);\n"; + code += "}\n"; + } + code += "\n\n"; if (flags[FLAG_UV1_USE_TRIPLANAR] || flags[FLAG_UV2_USE_TRIPLANAR]) { code += "vec4 triplanar_texture(sampler2D p_sampler,vec3 p_weights,vec3 p_triplanar_pos) {\n"; code += " vec4 samp=vec4(0.0);\n"; @@ -1010,7 +1033,30 @@ void BaseMaterial3D::_update_shader() { } } - if (flags[FLAG_ALBEDO_TEXTURE_FORCE_SRGB]) { + if (flags[FLAG_ALBEDO_TEXTURE_MSDF]) { + code += " {\n"; + code += " albedo_tex.rgb = mix(vec3(1.0 + 0.055) * pow(albedo_tex.rgb, vec3(1.0 / 2.4)) - vec3(0.055), vec3(12.92) * albedo_tex.rgb.rgb, lessThan(albedo_tex.rgb, vec3(0.0031308)));\n"; + code += " vec2 msdf_size = vec2(msdf_pixel_range) / vec2(textureSize(texture_albedo, 0));\n"; + if (flags[FLAG_USE_POINT_SIZE]) { + code += " vec2 dest_size = vec2(1.0) / fwidth(POINT_COORD);\n"; + } else { + if (flags[FLAG_UV1_USE_TRIPLANAR]) { + code += " vec2 dest_size = vec2(1.0) / fwidth(uv1_triplanar_pos);\n"; + } else { + code += " vec2 dest_size = vec2(1.0) / fwidth(base_uv);\n"; + } + } + code += " float px_size = max(0.5 * dot(msdf_size, dest_size), 1.0);\n"; + code += " float d = msdf_median(albedo_tex.r, albedo_tex.g, albedo_tex.b, albedo_tex.a) - 0.5;\n"; + code += " if (msdf_outline_size > 0.0) {\n"; + code += " float cr = clamp(msdf_outline_size, 0.0, msdf_pixel_range / 2.0) / msdf_pixel_range;\n"; + code += " albedo_tex.a = clamp((d + cr) * px_size, 0.0, 1.0);\n"; + code += " } else {\n"; + code += " albedo_tex.a = clamp(d * px_size + 0.5, 0.0, 1.0);\n"; + code += " }\n"; + code += " albedo_tex.rgb = vec3(1.0);\n"; + code += " }\n"; + } else if (flags[FLAG_ALBEDO_TEXTURE_FORCE_SRGB]) { code += " albedo_tex.rgb = mix(pow((albedo_tex.rgb + vec3(0.055)) * (1.0 / (1.0 + 0.055)),vec3(2.4)),albedo_tex.rgb.rgb * (1.0 / 12.92),lessThan(albedo_tex.rgb,vec3(0.04045)));\n"; } @@ -1753,8 +1799,6 @@ void BaseMaterial3D::_validate_property(PropertyInfo &property) const { _validate_high_end("refraction", property); _validate_high_end("subsurf_scatter", property); - _validate_high_end("anisotropy", property); - _validate_high_end("clearcoat", property); _validate_high_end("heightmap", property); if (property.name.begins_with("particles_anim_") && billboard_mode != BILLBOARD_PARTICLES) { @@ -1777,6 +1821,14 @@ void BaseMaterial3D::_validate_property(PropertyInfo &property) const { property.usage = PROPERTY_USAGE_NO_EDITOR; } + if (property.name == "msdf_pixel_range" && !flags[FLAG_ALBEDO_TEXTURE_MSDF]) { + property.usage = PROPERTY_USAGE_NO_EDITOR; + } + + if (property.name == "msdf_outline_size" && !flags[FLAG_ALBEDO_TEXTURE_MSDF]) { + property.usage = PROPERTY_USAGE_NO_EDITOR; + } + if ((property.name == "distance_fade_max_distance" || property.name == "distance_fade_min_distance") && distance_fade == DISTANCE_FADE_DISABLED) { property.usage = PROPERTY_USAGE_NO_EDITOR; } @@ -2125,35 +2177,45 @@ BaseMaterial3D::TextureChannel BaseMaterial3D::get_refraction_texture_channel() return refraction_texture_channel; } -Ref<Material> BaseMaterial3D::get_material_for_2d(bool p_shaded, bool p_transparent, bool p_double_sided, bool p_cut_alpha, bool p_opaque_prepass, bool p_billboard, bool p_billboard_y, RID *r_shader_rid) { - int version = 0; +Ref<Material> BaseMaterial3D::get_material_for_2d(bool p_shaded, bool p_transparent, bool p_double_sided, bool p_cut_alpha, bool p_opaque_prepass, bool p_billboard, bool p_billboard_y, bool p_msdf, bool p_no_depth, bool p_fixed_size, TextureFilter p_filter, RID *r_shader_rid) { + int64_t hash = 0; if (p_shaded) { - version = 1; + hash |= 1 << 0; } if (p_transparent) { - version |= 2; + hash |= 1 << 1; } if (p_cut_alpha) { - version |= 4; + hash |= 1 << 2; } if (p_opaque_prepass) { - version |= 8; + hash |= 1 << 3; } if (p_double_sided) { - version |= 16; + hash |= 1 << 4; } if (p_billboard) { - version |= 32; + hash |= 1 << 5; } if (p_billboard_y) { - version |= 64; + hash |= 1 << 6; + } + if (p_msdf) { + hash |= 1 << 7; + } + if (p_no_depth) { + hash |= 1 << 8; + } + if (p_fixed_size) { + hash |= 1 << 9; } + hash = hash_murmur3_one_64(p_filter, hash); - if (materials_for_2d[version].is_valid()) { + if (materials_for_2d.has(hash)) { if (r_shader_rid) { - *r_shader_rid = materials_for_2d[version]->get_shader_rid(); + *r_shader_rid = materials_for_2d[hash]->get_shader_rid(); } - return materials_for_2d[version]; + return materials_for_2d[hash]; } Ref<StandardMaterial3D> material; @@ -2164,18 +2226,22 @@ Ref<Material> BaseMaterial3D::get_material_for_2d(bool p_shaded, bool p_transpar material->set_cull_mode(p_double_sided ? CULL_DISABLED : CULL_BACK); material->set_flag(FLAG_SRGB_VERTEX_COLOR, true); material->set_flag(FLAG_ALBEDO_FROM_VERTEX_COLOR, true); + material->set_flag(FLAG_ALBEDO_TEXTURE_MSDF, p_msdf); + material->set_flag(FLAG_DISABLE_DEPTH_TEST, p_no_depth); + material->set_flag(FLAG_FIXED_SIZE, p_fixed_size); + material->set_texture_filter(p_filter); if (p_billboard || p_billboard_y) { material->set_flag(FLAG_BILLBOARD_KEEP_SCALE, true); material->set_billboard_mode(p_billboard_y ? BILLBOARD_FIXED_Y : BILLBOARD_ENABLED); } - materials_for_2d[version] = material; + materials_for_2d[hash] = material; if (r_shader_rid) { - *r_shader_rid = materials_for_2d[version]->get_shader_rid(); + *r_shader_rid = materials_for_2d[hash]->get_shader_rid(); } - return materials_for_2d[version]; + return materials_for_2d[hash]; } void BaseMaterial3D::set_on_top_of_alpha() { @@ -2203,6 +2269,24 @@ float BaseMaterial3D::get_proximity_fade_distance() const { return proximity_fade_distance; } +void BaseMaterial3D::set_msdf_pixel_range(float p_range) { + msdf_pixel_range = p_range; + RS::get_singleton()->material_set_param(_get_material(), shader_names->msdf_pixel_range, p_range); +} + +float BaseMaterial3D::get_msdf_pixel_range() const { + return msdf_pixel_range; +} + +void BaseMaterial3D::set_msdf_outline_size(float p_size) { + msdf_outline_size = p_size; + RS::get_singleton()->material_set_param(_get_material(), shader_names->msdf_outline_size, p_size); +} + +float BaseMaterial3D::get_msdf_outline_size() const { + return msdf_outline_size; +} + void BaseMaterial3D::set_distance_fade(DistanceFadeMode p_mode) { distance_fade = p_mode; _queue_shader_change(); @@ -2445,6 +2529,12 @@ void BaseMaterial3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_proximity_fade_distance", "distance"), &BaseMaterial3D::set_proximity_fade_distance); ClassDB::bind_method(D_METHOD("get_proximity_fade_distance"), &BaseMaterial3D::get_proximity_fade_distance); + ClassDB::bind_method(D_METHOD("set_msdf_pixel_range", "range"), &BaseMaterial3D::set_msdf_pixel_range); + ClassDB::bind_method(D_METHOD("get_msdf_pixel_range"), &BaseMaterial3D::get_msdf_pixel_range); + + ClassDB::bind_method(D_METHOD("set_msdf_outline_size", "size"), &BaseMaterial3D::set_msdf_outline_size); + ClassDB::bind_method(D_METHOD("get_msdf_outline_size"), &BaseMaterial3D::get_msdf_outline_size); + ClassDB::bind_method(D_METHOD("set_distance_fade", "mode"), &BaseMaterial3D::set_distance_fade); ClassDB::bind_method(D_METHOD("get_distance_fade"), &BaseMaterial3D::get_distance_fade); @@ -2478,7 +2568,8 @@ void BaseMaterial3D::_bind_methods() { ADD_GROUP("Albedo", "albedo_"); ADD_PROPERTY(PropertyInfo(Variant::COLOR, "albedo_color"), "set_albedo", "get_albedo"); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "albedo_texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_texture", "get_texture", TEXTURE_ALBEDO); - ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "albedo_tex_force_srgb"), "set_flag", "get_flag", FLAG_ALBEDO_TEXTURE_FORCE_SRGB); + ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "albedo_texture_force_srgb"), "set_flag", "get_flag", FLAG_ALBEDO_TEXTURE_FORCE_SRGB); + ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "albedo_texture_msdf"), "set_flag", "get_flag", FLAG_ALBEDO_TEXTURE_MSDF); ADD_GROUP("ORM", "orm_"); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "orm_texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_texture", "get_texture", TEXTURE_ORM); @@ -2575,14 +2666,14 @@ void BaseMaterial3D::_bind_methods() { ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "detail_normal", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_texture", "get_texture", TEXTURE_DETAIL_NORMAL); ADD_GROUP("UV1", "uv1_"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "uv1_scale"), "set_uv1_scale", "get_uv1_scale"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "uv1_scale", PROPERTY_HINT_LINK), "set_uv1_scale", "get_uv1_scale"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "uv1_offset"), "set_uv1_offset", "get_uv1_offset"); ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "uv1_triplanar"), "set_flag", "get_flag", FLAG_UV1_USE_TRIPLANAR); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "uv1_triplanar_sharpness", PROPERTY_HINT_EXP_EASING), "set_uv1_triplanar_blend_sharpness", "get_uv1_triplanar_blend_sharpness"); ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "uv1_world_triplanar"), "set_flag", "get_flag", FLAG_UV1_USE_WORLD_TRIPLANAR); ADD_GROUP("UV2", "uv2_"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "uv2_scale"), "set_uv2_scale", "get_uv2_scale"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "uv2_scale", PROPERTY_HINT_LINK), "set_uv2_scale", "get_uv2_scale"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "uv2_offset"), "set_uv2_offset", "get_uv2_offset"); ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "uv2_triplanar"), "set_flag", "get_flag", FLAG_UV2_USE_TRIPLANAR); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "uv2_triplanar_sharpness", PROPERTY_HINT_EXP_EASING), "set_uv2_triplanar_blend_sharpness", "get_uv2_triplanar_blend_sharpness"); @@ -2607,19 +2698,22 @@ void BaseMaterial3D::_bind_methods() { ADD_GROUP("Grow", "grow_"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "grow"), "set_grow_enabled", "is_grow_enabled"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "grow_amount", PROPERTY_HINT_RANGE, "-16,16,0.001"), "set_grow", "get_grow"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "grow_amount", PROPERTY_HINT_RANGE, "-16,16,0.001,suffix:m"), "set_grow", "get_grow"); ADD_GROUP("Transform", ""); ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "fixed_size"), "set_flag", "get_flag", FLAG_FIXED_SIZE); ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "use_point_size"), "set_flag", "get_flag", FLAG_USE_POINT_SIZE); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "point_size", PROPERTY_HINT_RANGE, "0.1,128,0.1"), "set_point_size", "get_point_size"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "point_size", PROPERTY_HINT_RANGE, "0.1,128,0.1,suffix:px"), "set_point_size", "get_point_size"); ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "use_particle_trails"), "set_flag", "get_flag", FLAG_PARTICLE_TRAILS_MODE); ADD_GROUP("Proximity Fade", "proximity_fade_"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "proximity_fade_enable"), "set_proximity_fade", "is_proximity_fade_enabled"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "proximity_fade_distance", PROPERTY_HINT_RANGE, "0,4096,0.01"), "set_proximity_fade_distance", "get_proximity_fade_distance"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "proximity_fade_distance", PROPERTY_HINT_RANGE, "0,4096,0.01,suffix:m"), "set_proximity_fade_distance", "get_proximity_fade_distance"); + ADD_GROUP("MSDF", "msdf_"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "msdf_pixel_range", PROPERTY_HINT_RANGE, "1,100,1"), "set_msdf_pixel_range", "get_msdf_pixel_range"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "msdf_outline_size", PROPERTY_HINT_RANGE, "1,250,1"), "set_msdf_outline_size", "get_msdf_outline_size"); ADD_GROUP("Distance Fade", "distance_fade_"); ADD_PROPERTY(PropertyInfo(Variant::INT, "distance_fade_mode", PROPERTY_HINT_ENUM, "Disabled,PixelAlpha,PixelDither,ObjectDither"), "set_distance_fade", "get_distance_fade"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "distance_fade_min_distance", PROPERTY_HINT_RANGE, "0,4096,0.01"), "set_distance_fade_min_distance", "get_distance_fade_min_distance"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "distance_fade_max_distance", PROPERTY_HINT_RANGE, "0,4096,0.01"), "set_distance_fade_max_distance", "get_distance_fade_max_distance"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "distance_fade_min_distance", PROPERTY_HINT_RANGE, "0,4096,0.01,suffix:m"), "set_distance_fade_min_distance", "get_distance_fade_min_distance"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "distance_fade_max_distance", PROPERTY_HINT_RANGE, "0,4096,0.01,suffix:m"), "set_distance_fade_max_distance", "get_distance_fade_max_distance"); BIND_ENUM_CONSTANT(TEXTURE_ALBEDO); BIND_ENUM_CONSTANT(TEXTURE_METALLIC); @@ -2715,6 +2809,7 @@ void BaseMaterial3D::_bind_methods() { BIND_ENUM_CONSTANT(FLAG_INVERT_HEIGHTMAP); BIND_ENUM_CONSTANT(FLAG_SUBSURFACE_MODE_SKIN); BIND_ENUM_CONSTANT(FLAG_PARTICLE_TRAILS_MODE); + BIND_ENUM_CONSTANT(FLAG_ALBEDO_TEXTURE_MSDF); BIND_ENUM_CONSTANT(FLAG_MAX); BIND_ENUM_CONSTANT(DIFFUSE_BURLEY); @@ -2804,6 +2899,7 @@ BaseMaterial3D::BaseMaterial3D(bool p_orm) : set_heightmap_deep_parallax_max_layers(32); set_heightmap_deep_parallax_flip_tangent(false); //also sets binormal + flags[FLAG_ALBEDO_TEXTURE_MSDF] = false; flags[FLAG_USE_TEXTURE_REPEAT] = true; is_initialized = true; @@ -2880,7 +2976,7 @@ bool StandardMaterial3D::_set(const StringName &p_name, const Variant &p_value) { "flags_no_depth_test", "no_depth_test" }, { "flags_use_point_size", "use_point_size" }, { "flags_fixed_size", "fixed_Size" }, - { "flags_albedo_tex_force_srg", "albedo_tex_force_srgb" }, + { "flags_albedo_tex_force_srgb", "albedo_texture_force_srgb" }, { "flags_do_not_receive_shadows", "disable_receive_shadows" }, { "flags_disable_ambient_light", "disable_ambient_light" }, { "params_diffuse_mode", "diffuse_mode" }, @@ -2923,4 +3019,7 @@ bool StandardMaterial3D::_set(const StringName &p_name, const Variant &p_value) return false; } + #endif // DISABLE_DEPRECATED + +/////////////////////// diff --git a/scene/resources/material.h b/scene/resources/material.h index 07227c037c..b845fd68c8 100644 --- a/scene/resources/material.h +++ b/scene/resources/material.h @@ -82,7 +82,7 @@ class ShaderMaterial : public Material { GDCLASS(ShaderMaterial, Material); Ref<Shader> shader; - Map<StringName, Variant> param_cache; + HashMap<StringName, Variant> param_cache; protected: bool _set(const StringName &p_name, const Variant &p_value); @@ -242,6 +242,7 @@ public: FLAG_INVERT_HEIGHTMAP, FLAG_SUBSURFACE_MODE_SKIN, FLAG_PARTICLE_TRAILS_MODE, + FLAG_ALBEDO_TEXTURE_MSDF, FLAG_MAX }; @@ -322,6 +323,9 @@ private: memset(this, 0, sizeof(MaterialKey)); } + static uint32_t hash(const MaterialKey &p_key) { + return hash_djb2_buffer((const uint8_t *)&p_key, sizeof(MaterialKey)); + } bool operator==(const MaterialKey &p_key) const { return memcmp(this, &p_key, sizeof(MaterialKey)) == 0; } @@ -336,7 +340,7 @@ private: int users = 0; }; - static Map<MaterialKey, ShaderData> shader_map; + static HashMap<MaterialKey, ShaderData, MaterialKey> shader_map; MaterialKey current_key; @@ -412,6 +416,8 @@ private: StringName uv2_blend_sharpness; StringName grow; StringName proximity_fade_distance; + StringName msdf_pixel_range; + StringName msdf_outline_size; StringName distance_fade_min; StringName distance_fade_max; StringName ao_light_affect; @@ -446,36 +452,36 @@ private: bool orm; Color albedo; - float specular; - float metallic; - float roughness; + float specular = 0.0f; + float metallic = 0.0f; + float roughness = 0.0f; Color emission; - float emission_energy; - float normal_scale; - float rim; - float rim_tint; - float clearcoat; - float clearcoat_roughness; - float anisotropy; - float heightmap_scale; - float subsurface_scattering_strength; - float transmittance_amount; + float emission_energy = 0.0f; + float normal_scale = 0.0f; + float rim = 0.0f; + float rim_tint = 0.0f; + float clearcoat = 0.0f; + float clearcoat_roughness = 0.0f; + float anisotropy = 0.0f; + float heightmap_scale = 0.0f; + float subsurface_scattering_strength = 0.0f; + float transmittance_amount = 0.0f; Color transmittance_color; - float transmittance_depth; - float transmittance_boost; + float transmittance_depth = 0.0f; + float transmittance_boost = 0.0f; Color backlight; - float refraction; - float point_size; - float alpha_scissor_threshold; - float alpha_hash_scale; - float alpha_antialiasing_edge; + float refraction = 0.0f; + float point_size = 0.0f; + float alpha_scissor_threshold = 0.0f; + float alpha_hash_scale = 0.0f; + float alpha_antialiasing_edge = 0.0f; bool grow_enabled = false; - float ao_light_affect; - float grow; - int particles_anim_h_frames; - int particles_anim_v_frames; - bool particles_anim_loop; + float ao_light_affect = 0.0f; + float grow = 0.0f; + int particles_anim_h_frames = 0; + int particles_anim_v_frames = 0; + bool particles_anim_loop = false; Transparency transparency = TRANSPARENCY_DISABLED; ShadingMode shading_mode = SHADING_MODE_PER_PIXEL; @@ -483,26 +489,29 @@ private: Vector3 uv1_scale; Vector3 uv1_offset; - float uv1_triplanar_sharpness; + float uv1_triplanar_sharpness = 0.0f; Vector3 uv2_scale; Vector3 uv2_offset; - float uv2_triplanar_sharpness; + float uv2_triplanar_sharpness = 0.0f; DetailUV detail_uv = DETAIL_UV_1; bool deep_parallax = false; - int deep_parallax_min_layers; - int deep_parallax_max_layers; + int deep_parallax_min_layers = 0; + int deep_parallax_max_layers = 0; bool heightmap_parallax_flip_tangent = false; bool heightmap_parallax_flip_binormal = false; bool proximity_fade_enabled = false; - float proximity_fade_distance; + float proximity_fade_distance = 0.0f; + + float msdf_pixel_range = 4.f; + float msdf_outline_size = 0.f; DistanceFadeMode distance_fade = DISTANCE_FADE_DISABLED; - float distance_fade_max_distance; - float distance_fade_min_distance; + float distance_fade_max_distance = 0.0f; + float distance_fade_min_distance = 0.0f; BlendMode blend_mode = BLEND_MODE_MIX; BlendMode detail_blend_mode = BLEND_MODE_MIX; @@ -527,9 +536,7 @@ private: _FORCE_INLINE_ void _validate_feature(const String &text, Feature feature, PropertyInfo &property) const; - static const int MAX_MATERIALS_FOR_2D = 128; - - static Ref<StandardMaterial3D> materials_for_2d[MAX_MATERIALS_FOR_2D]; //used by Sprite3D and other stuff + static HashMap<uint64_t, Ref<StandardMaterial3D>> materials_for_2d; //used by Sprite3D, Label3D and other stuff void _validate_high_end(const String &text, PropertyInfo &property) const; @@ -714,6 +721,12 @@ public: void set_proximity_fade_distance(float p_distance); float get_proximity_fade_distance() const; + void set_msdf_pixel_range(float p_range); + float get_msdf_pixel_range() const; + + void set_msdf_outline_size(float p_size); + float get_msdf_outline_size() const; + void set_distance_fade(DistanceFadeMode p_mode); DistanceFadeMode get_distance_fade() const; @@ -739,7 +752,7 @@ public: static void finish_shaders(); static void flush_changes(); - static Ref<Material> get_material_for_2d(bool p_shaded, bool p_transparent, bool p_double_sided, bool p_cut_alpha, bool p_opaque_prepass, bool p_billboard = false, bool p_billboard_y = false, RID *r_shader_rid = nullptr); + static Ref<Material> get_material_for_2d(bool p_shaded, bool p_transparent, bool p_double_sided, bool p_cut_alpha, bool p_opaque_prepass, bool p_billboard = false, bool p_billboard_y = false, bool p_msdf = false, bool p_no_depth = false, bool p_fixed_size = false, TextureFilter p_filter = TEXTURE_FILTER_LINEAR_WITH_MIPMAPS, RID *r_shader_rid = nullptr); virtual RID get_shader_rid() const override; @@ -787,6 +800,13 @@ public: BaseMaterial3D(true) {} }; +class PlaceholderMaterial : public Material { + GDCLASS(PlaceholderMaterial, Material) +public: + virtual RID get_shader_rid() const override { return RID(); } + virtual Shader::Mode get_shader_mode() const override { return Shader::MODE_CANVAS_ITEM; } +}; + ////////////////////// #endif diff --git a/scene/resources/mesh.cpp b/scene/resources/mesh.cpp index 3c67a20f50..3e7b0a2808 100644 --- a/scene/resources/mesh.cpp +++ b/scene/resources/mesh.cpp @@ -161,32 +161,48 @@ Ref<TriangleMesh> Mesh::generate_triangle_mesh() const { return triangle_mesh; } - int facecount = 0; + int faces_size = 0; for (int i = 0; i < get_surface_count(); i++) { - if (surface_get_primitive_type(i) != PRIMITIVE_TRIANGLES) { - continue; - } - - if (surface_get_format(i) & ARRAY_FORMAT_INDEX) { - facecount += surface_get_array_index_len(i); - } else { - facecount += surface_get_array_len(i); + switch (surface_get_primitive_type(i)) { + case PRIMITIVE_TRIANGLES: { + int len = (surface_get_format(i) & ARRAY_FORMAT_INDEX) ? surface_get_array_index_len(i) : surface_get_array_len(i); + // Don't error if zero, it's valid (we'll just skip it later). + ERR_CONTINUE_MSG((len % 3) != 0, vformat("Ignoring surface %d, incorrect %s count: %d (for PRIMITIVE_TRIANGLES).", i, (surface_get_format(i) & ARRAY_FORMAT_INDEX) ? "index" : "vertex", len)); + faces_size += len; + } break; + case PRIMITIVE_TRIANGLE_STRIP: { + int len = (surface_get_format(i) & ARRAY_FORMAT_INDEX) ? surface_get_array_index_len(i) : surface_get_array_len(i); + // Don't error if zero, it's valid (we'll just skip it later). + ERR_CONTINUE_MSG(len != 0 && len < 3, vformat("Ignoring surface %d, incorrect %s count: %d (for PRIMITIVE_TRIANGLE_STRIP).", i, (surface_get_format(i) & ARRAY_FORMAT_INDEX) ? "index" : "vertex", len)); + faces_size += (len == 0) ? 0 : (len - 2) * 3; + } break; + default: { + } break; } } - if (facecount == 0 || (facecount % 3) != 0) { + if (faces_size == 0) { return triangle_mesh; } Vector<Vector3> faces; - faces.resize(facecount); + faces.resize(faces_size); + Vector<int32_t> surface_indices; + surface_indices.resize(faces_size / 3); Vector3 *facesw = faces.ptrw(); + int32_t *surface_indicesw = surface_indices.ptrw(); int widx = 0; for (int i = 0; i < get_surface_count(); i++) { - if (surface_get_primitive_type(i) != PRIMITIVE_TRIANGLES) { + Mesh::PrimitiveType primitive = surface_get_primitive_type(i); + if (primitive != PRIMITIVE_TRIANGLES && primitive != PRIMITIVE_TRIANGLE_STRIP) { + continue; + } + int len = (surface_get_format(i) & ARRAY_FORMAT_INDEX) ? surface_get_array_index_len(i) : surface_get_array_len(i); + if ((primitive == PRIMITIVE_TRIANGLES && (len == 0 || (len % 3) != 0)) || (primitive == PRIMITIVE_TRIANGLE_STRIP && len < 3)) { + // Error was already shown, just skip (including zero). continue; } @@ -197,21 +213,45 @@ Ref<TriangleMesh> Mesh::generate_triangle_mesh() const { Vector<Vector3> vertices = a[ARRAY_VERTEX]; const Vector3 *vr = vertices.ptr(); + int32_t from_index = widx / 3; + if (surface_get_format(i) & ARRAY_FORMAT_INDEX) { int ic = surface_get_array_index_len(i); Vector<int> indices = a[ARRAY_INDEX]; const int *ir = indices.ptr(); - for (int j = 0; j < ic; j++) { - int index = ir[j]; - facesw[widx++] = vr[index]; + if (primitive == PRIMITIVE_TRIANGLES) { + for (int j = 0; j < ic; j++) { + int index = ir[j]; + facesw[widx++] = vr[index]; + } + } else { // PRIMITIVE_TRIANGLE_STRIP + for (int j = 2; j < ic; j++) { + facesw[widx++] = vr[ir[j - 2]]; + facesw[widx++] = vr[ir[j - 1]]; + facesw[widx++] = vr[ir[j]]; + } } } else { - for (int j = 0; j < vc; j++) { - facesw[widx++] = vr[j]; + if (primitive == PRIMITIVE_TRIANGLES) { + for (int j = 0; j < vc; j++) { + facesw[widx++] = vr[j]; + } + } else { // PRIMITIVE_TRIANGLE_STRIP + for (int j = 2; j < vc; j++) { + facesw[widx++] = vr[j - 2]; + facesw[widx++] = vr[j - 1]; + facesw[widx++] = vr[j]; + } } } + + int32_t to_index = widx / 3; + + for (int j = from_index; j < to_index; j++) { + surface_indicesw[j] = i; + } } triangle_mesh = Ref<TriangleMesh>(memnew(TriangleMesh)); @@ -455,7 +495,7 @@ Ref<Mesh> Mesh::create_outline(float p_margin) const { has_indices = true; } - Map<Vector3, Vector3> normal_accum; + HashMap<Vector3, Vector3> normal_accum; //fill normals with triangle normals for (int i = 0; i < vc; i += 3) { @@ -474,13 +514,13 @@ Ref<Mesh> Mesh::create_outline(float p_margin) const { Vector3 n = Plane(t[0], t[1], t[2]).normal; for (int j = 0; j < 3; j++) { - Map<Vector3, Vector3>::Element *E = normal_accum.find(t[j]); + HashMap<Vector3, Vector3>::Iterator E = normal_accum.find(t[j]); if (!E) { normal_accum[t[j]] = n; } else { - float d = n.dot(E->get()); + float d = n.dot(E->value); if (d < 1.0) { - E->get() += n * (1.0 - d); + E->value += n * (1.0 - d); } //E->get()+=n; } @@ -499,10 +539,10 @@ Ref<Mesh> Mesh::create_outline(float p_margin) const { for (int i = 0; i < vc2; i++) { Vector3 t = r[i]; - Map<Vector3, Vector3>::Element *E = normal_accum.find(t); + HashMap<Vector3, Vector3>::Iterator E = normal_accum.find(t); ERR_CONTINUE(!E); - t += E->get() * p_margin; + t += E->value * p_margin; r[i] = t; } @@ -1827,7 +1867,7 @@ Error ArrayMesh::lightmap_unwrap_cached(const Transform3D &p_base_transform, flo // Keep only the scale Basis basis = p_base_transform.get_basis(); - Vector3 scale = Vector3(basis.get_axis(0).length(), basis.get_axis(1).length(), basis.get_axis(2).length()); + Vector3 scale = Vector3(basis.get_column(0).length(), basis.get_column(1).length(), basis.get_column(2).length()); Transform3D transform; transform.scale(scale); @@ -2067,7 +2107,7 @@ void ArrayMesh::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::PACKED_STRING_ARRAY, "_blend_shape_names", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_blend_shape_names", "_get_blend_shape_names"); ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "_surfaces", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_surfaces", "_get_surfaces"); ADD_PROPERTY(PropertyInfo(Variant::INT, "blend_shape_mode", PROPERTY_HINT_ENUM, "Normalized,Relative"), "set_blend_shape_mode", "get_blend_shape_mode"); - ADD_PROPERTY(PropertyInfo(Variant::AABB, "custom_aabb", PROPERTY_HINT_NONE, ""), "set_custom_aabb", "get_custom_aabb"); + ADD_PROPERTY(PropertyInfo(Variant::AABB, "custom_aabb", PROPERTY_HINT_NONE, "suffix:m"), "set_custom_aabb", "get_custom_aabb"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "shadow_mesh", PROPERTY_HINT_RESOURCE_TYPE, "ArrayMesh"), "set_shadow_mesh", "get_shadow_mesh"); } @@ -2092,3 +2132,17 @@ ArrayMesh::~ArrayMesh() { RenderingServer::get_singleton()->free(mesh); } } +/////////////// + +void PlaceholderMesh::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_aabb", "aabb"), &PlaceholderMesh::set_aabb); + ADD_PROPERTY(PropertyInfo(Variant::AABB, "aabb", PROPERTY_HINT_NONE, "suffix:m"), "set_aabb", "get_aabb"); +} + +PlaceholderMesh::PlaceholderMesh() { + rid = RS::get_singleton()->mesh_create(); +} + +PlaceholderMesh::~PlaceholderMesh() { + RS::get_singleton()->free(rid); +} diff --git a/scene/resources/mesh.h b/scene/resources/mesh.h index 652c045a24..b166d12ead 100644 --- a/scene/resources/mesh.h +++ b/scene/resources/mesh.h @@ -328,4 +328,38 @@ VARIANT_ENUM_CAST(Mesh::ArrayCustomFormat); VARIANT_ENUM_CAST(Mesh::PrimitiveType); VARIANT_ENUM_CAST(Mesh::BlendShapeMode); +class PlaceholderMesh : public Mesh { + GDCLASS(PlaceholderMesh, Mesh); + + RID rid; + AABB aabb; + +protected: + static void _bind_methods(); + +public: + virtual int get_surface_count() const override { return 0; } + virtual int surface_get_array_len(int p_idx) const override { return 0; } + virtual int surface_get_array_index_len(int p_idx) const override { return 0; } + virtual Array surface_get_arrays(int p_surface) const override { return Array(); } + virtual Array surface_get_blend_shape_arrays(int p_surface) const override { return Array(); } + virtual Dictionary surface_get_lods(int p_surface) const override { return Dictionary(); } + virtual uint32_t surface_get_format(int p_idx) const override { return 0; } + virtual PrimitiveType surface_get_primitive_type(int p_idx) const override { return PRIMITIVE_TRIANGLES; } + virtual void surface_set_material(int p_idx, const Ref<Material> &p_material) override {} + virtual Ref<Material> surface_get_material(int p_idx) const override { return Ref<Material>(); } + virtual int get_blend_shape_count() const override { return 0; } + virtual StringName get_blend_shape_name(int p_index) const override { return StringName(); } + virtual void set_blend_shape_name(int p_index, const StringName &p_name) override {} + virtual RID get_rid() const override { return rid; } + virtual AABB get_aabb() const override { return aabb; } + void set_aabb(const AABB &p_aabb) { aabb = p_aabb; } + + virtual int get_builtin_bind_pose_count() const override { return 0; } + virtual Transform3D get_builtin_bind_pose(int p_index) const override { return Transform3D(); } + + PlaceholderMesh(); + ~PlaceholderMesh(); +}; + #endif diff --git a/scene/resources/mesh_data_tool.cpp b/scene/resources/mesh_data_tool.cpp index 594f723a1d..33d63adc71 100644 --- a/scene/resources/mesh_data_tool.cpp +++ b/scene/resources/mesh_data_tool.cpp @@ -150,7 +150,7 @@ Error MeshDataTool::create_from_surface(const Ref<ArrayMesh> &p_mesh, int p_surf vertices.write[i] = v; } - Map<Point2i, int> edge_indices; + HashMap<Point2i, int> edge_indices; for (int i = 0; i < icount; i += 3) { Vertex *v[3] = { &vertices.write[r[i + 0]], &vertices.write[r[i + 1]], &vertices.write[r[i + 2]] }; diff --git a/scene/resources/mesh_library.cpp b/scene/resources/mesh_library.cpp index 5168bf83eb..2d3f9d9afc 100644 --- a/scene/resources/mesh_library.cpp +++ b/scene/resources/mesh_library.cpp @@ -100,14 +100,14 @@ bool MeshLibrary::_get(const StringName &p_name, Variant &r_ret) const { void MeshLibrary::_get_property_list(List<PropertyInfo> *p_list) const { for (const KeyValue<int, Item> &E : item_map) { - String name = "item/" + itos(E.key) + "/"; - p_list->push_back(PropertyInfo(Variant::STRING, name + "name")); - p_list->push_back(PropertyInfo(Variant::OBJECT, name + "mesh", PROPERTY_HINT_RESOURCE_TYPE, "Mesh")); - p_list->push_back(PropertyInfo(Variant::TRANSFORM3D, name + "mesh_transform")); - p_list->push_back(PropertyInfo(Variant::ARRAY, name + "shapes")); - p_list->push_back(PropertyInfo(Variant::OBJECT, name + "navmesh", PROPERTY_HINT_RESOURCE_TYPE, "NavigationMesh")); - p_list->push_back(PropertyInfo(Variant::TRANSFORM3D, name + "navmesh_transform")); - p_list->push_back(PropertyInfo(Variant::OBJECT, name + "preview", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_EDITOR_HELPER)); + String name = vformat("%s/%d/", PNAME("item"), E.key); + p_list->push_back(PropertyInfo(Variant::STRING, name + PNAME("name"))); + p_list->push_back(PropertyInfo(Variant::OBJECT, name + PNAME("mesh"), PROPERTY_HINT_RESOURCE_TYPE, "Mesh")); + p_list->push_back(PropertyInfo(Variant::TRANSFORM3D, name + PNAME("mesh_transform"), PROPERTY_HINT_NONE, "suffix:m")); + p_list->push_back(PropertyInfo(Variant::ARRAY, name + PNAME("shapes"))); + p_list->push_back(PropertyInfo(Variant::OBJECT, name + PNAME("navmesh"), PROPERTY_HINT_RESOURCE_TYPE, "NavigationMesh")); + p_list->push_back(PropertyInfo(Variant::TRANSFORM3D, name + PNAME("navmesh_transform"), PROPERTY_HINT_NONE, "suffix:m")); + p_list->push_back(PropertyInfo(Variant::OBJECT, name + PNAME("preview"), PROPERTY_HINT_RESOURCE_TYPE, "Texture2D", PROPERTY_USAGE_DEFAULT)); } } diff --git a/scene/resources/mesh_library.h b/scene/resources/mesh_library.h index e0f2ab2114..4105bd6960 100644 --- a/scene/resources/mesh_library.h +++ b/scene/resources/mesh_library.h @@ -32,7 +32,7 @@ #define MESH_LIBRARY_H #include "core/io/resource.h" -#include "core/templates/map.h" +#include "core/templates/rb_map.h" #include "mesh.h" #include "scene/3d/navigation_region_3d.h" #include "shape_3d.h" @@ -56,7 +56,7 @@ public: Ref<NavigationMesh> navmesh; }; - Map<int, Item> item_map; + RBMap<int, Item> item_map; void _set_item_shapes(int p_item, const Array &p_shapes); Array _get_item_shapes(int p_item) const; diff --git a/scene/resources/multimesh.cpp b/scene/resources/multimesh.cpp index c30e748f66..e5fc61ade5 100644 --- a/scene/resources/multimesh.cpp +++ b/scene/resources/multimesh.cpp @@ -101,9 +101,9 @@ void MultiMesh::_set_transform_2d_array(const Vector<Vector2> &p_array) { for (int i = 0; i < len / 3; i++) { Transform2D t; - t.elements[0] = r[i * 3 + 0]; - t.elements[1] = r[i * 3 + 1]; - t.elements[2] = r[i * 3 + 2]; + t.columns[0] = r[i * 3 + 0]; + t.columns[1] = r[i * 3 + 1]; + t.columns[2] = r[i * 3 + 2]; set_instance_transform_2d(i, t); } @@ -125,9 +125,9 @@ Vector<Vector2> MultiMesh::_get_transform_2d_array() const { for (int i = 0; i < instance_count; i++) { Transform2D t = get_instance_transform_2d(i); - w[i * 3 + 0] = t.elements[0]; - w[i * 3 + 1] = t.elements[1]; - w[i * 3 + 2] = t.elements[2]; + w[i * 3 + 0] = t.columns[0]; + w[i * 3 + 1] = t.columns[1]; + w[i * 3 + 2] = t.columns[2]; } return xforms; diff --git a/scene/resources/navigation_mesh.cpp b/scene/resources/navigation_mesh.cpp index 552fa84bad..a808ead66b 100644 --- a/scene/resources/navigation_mesh.cpp +++ b/scene/resources/navigation_mesh.cpp @@ -38,6 +38,7 @@ void NavigationMesh::create_from_mesh(const Ref<Mesh> &p_mesh) { for (int i = 0; i < p_mesh->get_surface_count(); i++) { if (p_mesh->surface_get_primitive_type(i) != Mesh::PRIMITIVE_TRIANGLES) { + WARN_PRINT("A mesh surface was skipped when creating a NavigationMesh due to wrong primitive type in the source mesh. Mesh surface must be made out of triangles."); continue; } Array arr = p_mesh->surface_get_arrays(i); @@ -46,6 +47,7 @@ void NavigationMesh::create_from_mesh(const Ref<Mesh> &p_mesh) { Vector<Vector3> varr = arr[Mesh::ARRAY_VERTEX]; Vector<int> iarr = arr[Mesh::ARRAY_INDEX]; if (varr.size() == 0 || iarr.size() == 0) { + WARN_PRINT("A mesh surface was skipped when creating a NavigationMesh due to an empty vertex or index array."); continue; } @@ -229,7 +231,7 @@ float NavigationMesh::get_verts_per_poly() const { } void NavigationMesh::set_detail_sample_distance(float p_value) { - ERR_FAIL_COND(p_value < 0); + ERR_FAIL_COND(p_value < 0.1); detail_sample_distance = p_value; } @@ -270,6 +272,24 @@ bool NavigationMesh::get_filter_walkable_low_height_spans() const { return filter_walkable_low_height_spans; } +void NavigationMesh::set_filter_baking_aabb(const AABB &p_aabb) { + filter_baking_aabb = p_aabb; + notify_property_list_changed(); +} + +AABB NavigationMesh::get_filter_baking_aabb() const { + return filter_baking_aabb; +} + +void NavigationMesh::set_filter_baking_aabb_offset(const Vector3 &p_aabb_offset) { + filter_baking_aabb_offset = p_aabb_offset; + notify_property_list_changed(); +} + +Vector3 NavigationMesh::get_filter_baking_aabb_offset() const { + return filter_baking_aabb_offset; +} + void NavigationMesh::set_vertices(const Vector<Vector3> &p_vertices) { vertices = p_vertices; notify_property_list_changed(); @@ -338,7 +358,7 @@ Ref<Mesh> NavigationMesh::get_debug_mesh() { } } - Map<_EdgeKey, bool> edge_map; + HashMap<_EdgeKey, bool, _EdgeKey> edge_map; Vector<Vector3> tmeshfaces; tmeshfaces.resize(faces.size() * 3); @@ -356,10 +376,10 @@ Ref<Mesh> NavigationMesh::get_debug_mesh() { SWAP(ek.from, ek.to); } - Map<_EdgeKey, bool>::Element *F = edge_map.find(ek); + HashMap<_EdgeKey, bool, _EdgeKey>::Iterator F = edge_map.find(ek); if (F) { - F->get() = false; + F->value = false; } else { edge_map[ek] = true; @@ -467,6 +487,10 @@ void NavigationMesh::_bind_methods() { ClassDB::bind_method(D_METHOD("set_filter_walkable_low_height_spans", "filter_walkable_low_height_spans"), &NavigationMesh::set_filter_walkable_low_height_spans); ClassDB::bind_method(D_METHOD("get_filter_walkable_low_height_spans"), &NavigationMesh::get_filter_walkable_low_height_spans); + ClassDB::bind_method(D_METHOD("set_filter_baking_aabb", "baking_aabb"), &NavigationMesh::set_filter_baking_aabb); + ClassDB::bind_method(D_METHOD("get_filter_baking_aabb"), &NavigationMesh::get_filter_baking_aabb); + ClassDB::bind_method(D_METHOD("set_filter_baking_aabb_offset", "baking_aabb_offset"), &NavigationMesh::set_filter_baking_aabb_offset); + ClassDB::bind_method(D_METHOD("get_filter_baking_aabb_offset"), &NavigationMesh::get_filter_baking_aabb_offset); ClassDB::bind_method(D_METHOD("set_vertices", "vertices"), &NavigationMesh::set_vertices); ClassDB::bind_method(D_METHOD("get_vertices"), &NavigationMesh::get_vertices); @@ -484,29 +508,38 @@ void NavigationMesh::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::PACKED_VECTOR3_ARRAY, "vertices", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "set_vertices", "get_vertices"); ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "polygons", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_polygons", "_get_polygons"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "sample_partition_type/sample_partition_type", PROPERTY_HINT_ENUM, "Watershed,Monotone,Layers"), "set_sample_partition_type", "get_sample_partition_type"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "geometry/parsed_geometry_type", PROPERTY_HINT_ENUM, "Mesh Instances,Static Colliders,Both"), "set_parsed_geometry_type", "get_parsed_geometry_type"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "geometry/collision_mask", PROPERTY_HINT_LAYERS_3D_PHYSICS), "set_collision_mask", "get_collision_mask"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "geometry/source_geometry_mode", PROPERTY_HINT_ENUM, "Navmesh Children, Group With Children, Group Explicit"), "set_source_geometry_mode", "get_source_geometry_mode"); - ADD_PROPERTY(PropertyInfo(Variant::STRING, "geometry/source_group_name"), "set_source_group_name", "get_source_group_name"); - - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "cell/size", PROPERTY_HINT_RANGE, "0.1,1.0,0.01,or_greater"), "set_cell_size", "get_cell_size"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "cell/height", PROPERTY_HINT_RANGE, "0.1,1.0,0.01,or_greater"), "set_cell_height", "get_cell_height"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "agent/height", PROPERTY_HINT_RANGE, "0.1,5.0,0.01,or_greater"), "set_agent_height", "get_agent_height"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "agent/radius", PROPERTY_HINT_RANGE, "0.1,5.0,0.01,or_greater"), "set_agent_radius", "get_agent_radius"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "agent/max_climb", PROPERTY_HINT_RANGE, "0.1,5.0,0.01,or_greater"), "set_agent_max_climb", "get_agent_max_climb"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "agent/max_slope", PROPERTY_HINT_RANGE, "0.0,90.0,0.1"), "set_agent_max_slope", "get_agent_max_slope"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "region/min_size", PROPERTY_HINT_RANGE, "0.0,150.0,0.01,or_greater"), "set_region_min_size", "get_region_min_size"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "region/merge_size", PROPERTY_HINT_RANGE, "0.0,150.0,0.01,or_greater"), "set_region_merge_size", "get_region_merge_size"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "edge/max_length", PROPERTY_HINT_RANGE, "0.0,50.0,0.01,or_greater"), "set_edge_max_length", "get_edge_max_length"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "edge/max_error", PROPERTY_HINT_RANGE, "0.1,3.0,0.01,or_greater"), "set_edge_max_error", "get_edge_max_error"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "polygon/verts_per_poly", PROPERTY_HINT_RANGE, "3.0,12.0,1.0,or_greater"), "set_verts_per_poly", "get_verts_per_poly"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "detail/sample_distance", PROPERTY_HINT_RANGE, "0.0,16.0,0.01,or_greater"), "set_detail_sample_distance", "get_detail_sample_distance"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "detail/sample_max_error", PROPERTY_HINT_RANGE, "0.0,16.0,0.01,or_greater"), "set_detail_sample_max_error", "get_detail_sample_max_error"); - - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "filter/low_hanging_obstacles"), "set_filter_low_hanging_obstacles", "get_filter_low_hanging_obstacles"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "filter/ledge_spans"), "set_filter_ledge_spans", "get_filter_ledge_spans"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "filter/filter_walkable_low_height_spans"), "set_filter_walkable_low_height_spans", "get_filter_walkable_low_height_spans"); + ADD_GROUP("Sampling", "sample_"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "sample_partition_type", PROPERTY_HINT_ENUM, "Watershed,Monotone,Layers"), "set_sample_partition_type", "get_sample_partition_type"); + ADD_GROUP("Geometry", "geometry_"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "geometry_parsed_geometry_type", PROPERTY_HINT_ENUM, "Mesh Instances,Static Colliders,Both"), "set_parsed_geometry_type", "get_parsed_geometry_type"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "geometry_collision_mask", PROPERTY_HINT_LAYERS_3D_PHYSICS), "set_collision_mask", "get_collision_mask"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "geometry_source_geometry_mode", PROPERTY_HINT_ENUM, "NavMesh Children, Group With Children, Group Explicit"), "set_source_geometry_mode", "get_source_geometry_mode"); + ADD_PROPERTY(PropertyInfo(Variant::STRING, "geometry_source_group_name"), "set_source_group_name", "get_source_group_name"); + ADD_GROUP("Cells", "cell_"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "cell_size", PROPERTY_HINT_RANGE, "0.01,500.0,0.01,or_greater,suffix:m"), "set_cell_size", "get_cell_size"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "cell_height", PROPERTY_HINT_RANGE, "0.01,500.0,0.01,or_greater,suffix:m"), "set_cell_height", "get_cell_height"); + ADD_GROUP("Agents", "agent_"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "agent_height", PROPERTY_HINT_RANGE, "0.0,500.0,0.01,or_greater,suffix:m"), "set_agent_height", "get_agent_height"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "agent_radius", PROPERTY_HINT_RANGE, "0.0,500.0,0.01,or_greater,suffix:m"), "set_agent_radius", "get_agent_radius"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "agent_max_climb", PROPERTY_HINT_RANGE, "0.0,500.0,0.01,or_greater,suffix:m"), "set_agent_max_climb", "get_agent_max_climb"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "agent_max_slope", PROPERTY_HINT_RANGE, "0.02,90.0,0.01,degrees"), "set_agent_max_slope", "get_agent_max_slope"); + ADD_GROUP("Regions", "region_"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "region_min_size", PROPERTY_HINT_RANGE, "0.0,150.0,0.01,or_greater"), "set_region_min_size", "get_region_min_size"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "region_merge_size", PROPERTY_HINT_RANGE, "0.0,150.0,0.01,or_greater"), "set_region_merge_size", "get_region_merge_size"); + ADD_GROUP("Edges", "edge_"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "edge_max_length", PROPERTY_HINT_RANGE, "0.0,50.0,0.01,or_greater,suffix:m"), "set_edge_max_length", "get_edge_max_length"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "edge_max_error", PROPERTY_HINT_RANGE, "0.1,3.0,0.01,or_greater,suffix:m"), "set_edge_max_error", "get_edge_max_error"); + ADD_GROUP("Polygons", "polygon_"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "polygon_verts_per_poly", PROPERTY_HINT_RANGE, "3.0,12.0,1.0,or_greater"), "set_verts_per_poly", "get_verts_per_poly"); + ADD_GROUP("Details", "detail_"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "detail_sample_distance", PROPERTY_HINT_RANGE, "0.1,16.0,0.01,or_greater,suffix:m"), "set_detail_sample_distance", "get_detail_sample_distance"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "detail_sample_max_error", PROPERTY_HINT_RANGE, "0.0,16.0,0.01,or_greater,suffix:m"), "set_detail_sample_max_error", "get_detail_sample_max_error"); + ADD_GROUP("Filters", "filter_"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "filter_low_hanging_obstacles"), "set_filter_low_hanging_obstacles", "get_filter_low_hanging_obstacles"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "filter_ledge_spans"), "set_filter_ledge_spans", "get_filter_ledge_spans"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "filter_walkable_low_height_spans"), "set_filter_walkable_low_height_spans", "get_filter_walkable_low_height_spans"); + ADD_PROPERTY(PropertyInfo(Variant::AABB, "filter_baking_aabb"), "set_filter_baking_aabb", "get_filter_baking_aabb"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "filter_baking_aabb_offset"), "set_filter_baking_aabb_offset", "get_filter_baking_aabb_offset"); BIND_ENUM_CONSTANT(SAMPLE_PARTITION_WATERSHED); BIND_ENUM_CONSTANT(SAMPLE_PARTITION_MONOTONE); @@ -540,4 +573,41 @@ void NavigationMesh::_validate_property(PropertyInfo &property) const { } } +#ifndef DISABLE_DEPRECATED +bool NavigationMesh::_set(const StringName &p_name, const Variant &p_value) { + String name = p_name; + if (name.find("/") != -1) { + // Compatibility with pre-3.5 "category/path" property names. + name = name.replace("/", "_"); + if (name == "sample_partition_type_sample_partition_type") { + set("sample_partition_type", p_value); + } else if (name == "filter_filter_walkable_low_height_spans") { + set("filter_walkable_low_height_spans", p_value); + } else { + set(name, p_value); + } + + return true; + } + return false; +} + +bool NavigationMesh::_get(const StringName &p_name, Variant &r_ret) const { + String name = p_name; + if (name.find("/") != -1) { + // Compatibility with pre-3.5 "category/path" property names. + name = name.replace("/", "_"); + if (name == "sample_partition_type_sample_partition_type") { + r_ret = get("sample_partition_type"); + } else if (name == "filter_filter_walkable_low_height_spans") { + r_ret = get("filter_walkable_low_height_spans"); + } else { + r_ret = get(name); + } + return true; + } + return false; +} +#endif // DISABLE_DEPRECATED + NavigationMesh::NavigationMesh() {} diff --git a/scene/resources/navigation_mesh.h b/scene/resources/navigation_mesh.h index e43e8627e4..40b275c792 100644 --- a/scene/resources/navigation_mesh.h +++ b/scene/resources/navigation_mesh.h @@ -49,13 +49,24 @@ class NavigationMesh : public Resource { Vector3 from; Vector3 to; - bool operator<(const _EdgeKey &p_with) const { return from == p_with.from ? to < p_with.to : from < p_with.from; } + static uint32_t hash(const _EdgeKey &p_key) { + return HashMapHasherDefault::hash(p_key.from) ^ HashMapHasherDefault::hash(p_key.to); + } + + bool operator==(const _EdgeKey &p_with) const { + return HashMapComparatorDefault<Vector3>::compare(from, p_with.from) && HashMapComparatorDefault<Vector3>::compare(to, p_with.to); + } }; protected: static void _bind_methods(); virtual void _validate_property(PropertyInfo &property) const override; +#ifndef DISABLE_DEPRECATED + bool _set(const StringName &p_name, const Variant &p_value); + bool _get(const StringName &p_name, Variant &r_ret) const; +#endif // DISABLE_DEPRECATED + void _set_polygons(const Array &p_array); Array _get_polygons() const; @@ -82,13 +93,13 @@ public: }; protected: - float cell_size = 0.3f; - float cell_height = 0.2f; - float agent_height = 2.0f; - float agent_radius = 1.0f; - float agent_max_climb = 0.9f; + float cell_size = 0.25f; + float cell_height = 0.25f; + float agent_height = 1.5f; + float agent_radius = 0.5f; + float agent_max_climb = 0.25f; float agent_max_slope = 45.0f; - float region_min_size = 8.0f; + float region_min_size = 2.0f; float region_merge_size = 20.0f; float edge_max_length = 12.0f; float edge_max_error = 1.3f; @@ -106,6 +117,8 @@ protected: bool filter_low_hanging_obstacles = false; bool filter_ledge_spans = false; bool filter_walkable_low_height_spans = false; + AABB filter_baking_aabb; + Vector3 filter_baking_aabb_offset; public: // Recast settings @@ -175,6 +188,12 @@ public: void set_filter_walkable_low_height_spans(bool p_value); bool get_filter_walkable_low_height_spans() const; + void set_filter_baking_aabb(const AABB &p_aabb); + AABB get_filter_baking_aabb() const; + + void set_filter_baking_aabb_offset(const Vector3 &p_aabb_offset); + Vector3 get_filter_baking_aabb_offset() const; + void create_from_mesh(const Ref<Mesh> &p_mesh); void set_vertices(const Vector<Vector3> &p_vertices); diff --git a/scene/resources/packed_scene.cpp b/scene/resources/packed_scene.cpp index b1c2702a1e..2c58aa83a9 100644 --- a/scene/resources/packed_scene.cpp +++ b/scene/resources/packed_scene.cpp @@ -33,28 +33,28 @@ #include "core/config/engine.h" #include "core/config/project_settings.h" #include "core/core_string_names.h" +#include "core/io/missing_resource.h" #include "core/io/resource_loader.h" +#include "core/templates/local_vector.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/main/missing_node.h" #include "scene/property_utils.h" #define PACKED_SCENE_VERSION 2 - +#define META_POINTER_PROPERTY_BASE "metadata/_editor_prop_ptr_" 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_"); + Array pinned = p_node->get_meta("_edit_pinned_properties_", Array()); if (pinned.is_empty()) { return Array(); } - Set<StringName> storable_properties; + HashSet<StringName> storable_properties; p_node->get_storable_properties(storable_properties); int i = 0; do { @@ -107,12 +107,15 @@ Node *SceneState::instantiate(GenEditState p_edit_state) const { bool gen_node_path_cache = p_edit_state != GEN_EDIT_STATE_DISABLED && node_path_cache.is_empty(); - Map<Ref<Resource>, Ref<Resource>> resources_local_to_scene; + HashMap<Ref<Resource>, Ref<Resource>> resources_local_to_scene; + + LocalVector<DeferredNodePathProperties> deferred_node_paths; for (int i = 0; i < nc; i++) { const NodeData &n = nd[i]; Node *parent = nullptr; + String old_parent_path; if (i > 0) { ERR_FAIL_COND_V_MSG(n.parent == -1, nullptr, vformat("Invalid scene: node %s does not specify its parent node.", snames[n.name])); @@ -120,6 +123,8 @@ Node *SceneState::instantiate(GenEditState p_edit_state) const { #ifdef DEBUG_ENABLED if (!nparent && (n.parent & FLAG_ID_IS_PATH)) { WARN_PRINT(String("Parent path '" + String(node_paths[n.parent & FLAG_MASK]) + "' for node '" + String(snames[n.name]) + "' has vanished when instancing: '" + get_path() + "'.").ascii().get_data()); + old_parent_path = String(node_paths[n.parent & FLAG_MASK]).trim_prefix("./").replace("/", "@"); + nparent = ret_nodes[0]; } #endif parent = nparent; @@ -130,6 +135,7 @@ Node *SceneState::instantiate(GenEditState p_edit_state) const { } Node *node = nullptr; + MissingNode *missing_node = nullptr; if (i == 0 && base_scene_idx >= 0) { //scene inheritance on root node @@ -184,24 +190,33 @@ Node *SceneState::instantiate(GenEditState p_edit_state) const { memdelete(obj); obj = nullptr; } - WARN_PRINT(vformat("Node %s of type %s cannot be created. A placeholder will be created instead.", snames[n.name], snames[n.type]).ascii().get_data()); - if (n.parent >= 0 && n.parent < nc && ret_nodes[n.parent]) { - if (Object::cast_to<Control>(ret_nodes[n.parent])) { - obj = memnew(Control); - } else if (Object::cast_to<Node2D>(ret_nodes[n.parent])) { - obj = memnew(Node2D); + + if (ResourceLoader::is_creating_missing_resources_if_class_unavailable_enabled()) { + missing_node = memnew(MissingNode); + missing_node->set_original_class(snames[n.type]); + missing_node->set_recording_properties(true); + node = missing_node; + obj = missing_node; + } else { + WARN_PRINT(vformat("Node %s of type %s cannot be created. A placeholder will be created instead.", snames[n.name], snames[n.type]).ascii().get_data()); + if (n.parent >= 0 && n.parent < nc && ret_nodes[n.parent]) { + if (Object::cast_to<Control>(ret_nodes[n.parent])) { + obj = memnew(Control); + } else if (Object::cast_to<Node2D>(ret_nodes[n.parent])) { + obj = memnew(Node2D); #ifndef _3D_DISABLED - } else if (Object::cast_to<Node3D>(ret_nodes[n.parent])) { - obj = memnew(Node3D); + } else if (Object::cast_to<Node3D>(ret_nodes[n.parent])) { + obj = memnew(Node3D); #endif // _3D_DISABLED + } } - } - if (!obj) { - obj = memnew(Node); - } + if (!obj) { + obj = memnew(Node); + } - node = Object::cast_to<Node>(obj); + node = Object::cast_to<Node>(obj); + } } } @@ -214,11 +229,32 @@ Node *SceneState::instantiate(GenEditState p_edit_state) const { if (nprop_count) { const NodeData::Property *nprops = &n.properties[0]; + Dictionary missing_resource_properties; + for (int j = 0; j < nprop_count; j++) { bool valid; - ERR_FAIL_INDEX_V(nprops[j].name, sname_count, nullptr); + ERR_FAIL_INDEX_V(nprops[j].value, prop_count, nullptr); + if (nprops[j].name & FLAG_PATH_PROPERTY_IS_NODE) { + uint32_t name_idx = nprops[j].name & (FLAG_PATH_PROPERTY_IS_NODE - 1); + ERR_FAIL_UNSIGNED_INDEX_V(name_idx, (uint32_t)sname_count, nullptr); + if (Engine::get_singleton()->is_editor_hint()) { + // If editor, just set the metadata and be it + node->set(META_POINTER_PROPERTY_BASE + String(snames[name_idx]), props[nprops[j].value]); + } else { + // Do an actual deferred sed of the property path. + DeferredNodePathProperties dnp; + dnp.path = props[nprops[j].value]; + dnp.base = node; + dnp.property = snames[name_idx]; + deferred_node_paths.push_back(dnp); + } + continue; + } + + ERR_FAIL_INDEX_V(nprops[j].name, sname_count, nullptr); + if (snames[nprops[j].name] == CoreStringNames::get_singleton()->_script) { //work around to avoid old script variables from disappearing, should be the proper fix to: //https://github.com/godotengine/godot/issues/2958 @@ -243,10 +279,10 @@ Node *SceneState::instantiate(GenEditState p_edit_state) const { Ref<Resource> res = value; if (res.is_valid()) { if (res->is_local_to_scene()) { - Map<Ref<Resource>, Ref<Resource>>::Element *E = resources_local_to_scene.find(res); + HashMap<Ref<Resource>, Ref<Resource>>::Iterator E = resources_local_to_scene.find(res); if (E) { - value = E->get(); + value = E->value; } else { Node *base = i == 0 ? node : ret_nodes[0]; @@ -270,9 +306,24 @@ Node *SceneState::instantiate(GenEditState p_edit_state) const { } else if (p_edit_state == GEN_EDIT_STATE_INSTANCE) { value = value.duplicate(true); // Duplicate arrays and dictionaries for the editor } - node->set(snames[nprops[j].name], value, &valid); + + bool set_valid = true; + if (ResourceLoader::is_creating_missing_resources_if_class_unavailable_enabled() && value.get_type() == Variant::OBJECT) { + Ref<MissingResource> mr = value; + if (mr.is_valid()) { + missing_resource_properties[snames[nprops[j].name]] = mr; + set_valid = false; + } + } + + if (set_valid) { + node->set(snames[nprops[j].name], value, &valid); + } } } + if (!missing_resource_properties.is_empty()) { + node->set_meta(META_MISSING_RESOURCES, missing_resource_properties); + } } //name @@ -306,10 +357,17 @@ Node *SceneState::instantiate(GenEditState p_edit_state) const { } } + if (!old_parent_path.is_empty()) { + node->_set_name_nocheck(old_parent_path + "@" + node->get_name()); + } + if (n.owner >= 0) { NODE_FROM_ID(owner, n.owner); if (owner) { node->_set_owner_nocheck(owner); + if (node->data.unique_name_in_owner) { + node->_acquire_unique_name_in_owner(); + } } } @@ -321,6 +379,10 @@ Node *SceneState::instantiate(GenEditState p_edit_state) const { } } + if (missing_node) { + missing_node->set_recording_properties(false); + } + ret_nodes[i] = node; if (node && gen_node_path_cache && ret_nodes[0]) { @@ -329,6 +391,12 @@ Node *SceneState::instantiate(GenEditState p_edit_state) const { } } + for (uint32_t i = 0; i < deferred_node_paths.size(); i++) { + const DeferredNodePathProperties &dnp = deferred_node_paths[i]; + Node *other = dnp.base->get_node_or_null(dnp.path); + dnp.base->set(dnp.property, other); + } + for (KeyValue<Ref<Resource>, Ref<Resource>> &E : resources_local_to_scene) { E.value->setup_local_to_scene(); } @@ -390,7 +458,7 @@ Node *SceneState::instantiate(GenEditState p_edit_state) const { return ret_nodes[0]; } -static int _nm_get_string(const String &p_string, Map<StringName, int> &name_map) { +static int _nm_get_string(const String &p_string, HashMap<StringName, int> &name_map) { if (name_map.has(p_string)) { return name_map[p_string]; } @@ -410,7 +478,7 @@ static int _vm_get_variant(const Variant &p_variant, HashMap<Variant, int, Varia return idx; } -Error SceneState::_parse_node(Node *p_owner, Node *p_node, int p_parent_idx, Map<StringName, int> &name_map, HashMap<Variant, int, VariantHasher, VariantComparator> &variant_map, Map<Node *, int> &node_map, Map<Node *, int> &nodepath_map) { +Error SceneState::_parse_node(Node *p_owner, Node *p_node, int p_parent_idx, HashMap<StringName, int> &name_map, HashMap<Variant, int, VariantHasher, VariantComparator> &variant_map, HashMap<Node *, int> &node_map, HashMap<Node *, int> &nodepath_map) { // this function handles all the work related to properly packing scenes, be it // instantiated or inherited. // given the complexity of this process, an attempt will be made to properly @@ -482,33 +550,46 @@ Error SceneState::_parse_node(Node *p_owner, Node *p_node, int p_parent_idx, Map p_node->get_property_list(&plist); Array pinned_props = _sanitize_node_pinned_properties(p_node); + Dictionary missing_resource_properties = p_node->get_meta(META_MISSING_RESOURCES, Dictionary()); for (const PropertyInfo &E : plist) { if (!(E.usage & PROPERTY_USAGE_STORAGE)) { continue; } - Variant forced_value; + if (E.name == META_PROPERTY_MISSING_RESOURCES) { + continue; // Ignore this property when packing. + } + if (E.name.begins_with(META_POINTER_PROPERTY_BASE)) { + continue; // do not save. + } - // If instance or inheriting, not saving if property requested so, or it's meta - if (states_stack.size()) { + // If instance or inheriting, not saving if property requested so. + if (!states_stack.is_empty()) { if ((E.usage & PROPERTY_USAGE_NO_INSTANCE_STATE)) { 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; - } - } } StringName name = E.name; - Variant value = forced_value.get_type() == Variant::NIL ? p_node->get(name) : forced_value; + Variant value = p_node->get(name); + bool use_deferred_node_path_bit = false; - if (!pinned_props.has(name) && forced_value.get_type() == Variant::NIL) { + if (E.type == Variant::OBJECT && E.hint == PROPERTY_HINT_NODE_TYPE) { + value = p_node->get(META_POINTER_PROPERTY_BASE + E.name); + if (value.get_type() != Variant::NODE_PATH) { + continue; //was never set, ignore. + } + use_deferred_node_path_bit = true; + } else if (E.type == Variant::OBJECT && missing_resource_properties.has(E.name)) { + // Was this missing resource overridden? If so do not save the old value. + Ref<Resource> ures = value; + if (ures.is_null()) { + value = missing_resource_properties[E.name]; + } + } + + if (!pinned_props.has(name)) { bool is_valid_default = false; Variant default_value = PropertyUtils::get_property_default_value(p_node, name, &is_valid_default, &states_stack, true); if (is_valid_default && !PropertyUtils::is_property_value_different(value, default_value)) { @@ -519,6 +600,9 @@ Error SceneState::_parse_node(Node *p_owner, Node *p_node, int p_parent_idx, Map NodeData::Property prop; prop.name = _nm_get_string(name, name_map); prop.value = _vm_get_variant(value, variant_map); + if (use_deferred_node_path_bit) { + prop.name |= FLAG_PATH_PROPERTY_IS_NODE; + } nd.properties.push_back(prop); } @@ -563,11 +647,18 @@ Error SceneState::_parse_node(Node *p_owner, Node *p_node, int p_parent_idx, Map nd.owner = -1; } + MissingNode *missing_node = Object::cast_to<MissingNode>(p_node); + // Save the right type. If this node was created by an instance // then flag that the node should not be created but reused 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); + if (missing_node != nullptr) { + // It's a missing node (type non existent on load). + nd.type = _nm_get_string(missing_node->get_original_class(), name_map); + } else { + nd.type = _nm_get_string(p_node->get_class(), name_map); + } } else { // this node is part of an instantiated process, so do not save the type. // instead, save that it was instantiated @@ -622,7 +713,7 @@ Error SceneState::_parse_node(Node *p_owner, Node *p_node, int p_parent_idx, Map return OK; } -Error SceneState::_parse_connections(Node *p_owner, Node *p_node, Map<StringName, int> &name_map, HashMap<Variant, int, VariantHasher, VariantComparator> &variant_map, Map<Node *, int> &node_map, Map<Node *, int> &nodepath_map) { +Error SceneState::_parse_connections(Node *p_owner, Node *p_node, HashMap<StringName, int> &name_map, HashMap<Variant, int, VariantHasher, VariantComparator> &variant_map, HashMap<Node *, int> &node_map, HashMap<Node *, int> &nodepath_map) { if (p_node != p_owner && p_node->get_owner() && p_node->get_owner() != p_owner && !p_owner->is_editable_instance(p_node->get_owner())) { return OK; } @@ -829,10 +920,10 @@ Error SceneState::pack(Node *p_scene) { Node *scene = p_scene; - Map<StringName, int> name_map; + HashMap<StringName, int> name_map; HashMap<Variant, int, VariantHasher, VariantComparator> variant_map; - Map<Node *, int> node_map; - Map<Node *, int> nodepath_map; + HashMap<Node *, int> node_map; + HashMap<Node *, int> nodepath_map; // If using scene inheritance, pack the scene it inherits from. if (scene->get_scene_inherited_state().is_valid()) { @@ -863,10 +954,10 @@ Error SceneState::pack(Node *p_scene) { } variants.resize(variant_map.size()); - const Variant *K = nullptr; - while ((K = variant_map.next(K))) { - int idx = variant_map[*K]; - variants.write[idx] = *K; + + for (const KeyValue<Variant, int> &E : variant_map) { + int idx = E.value; + variants.write[idx] = E.key; } node_paths.resize(nodepath_map.size()); @@ -968,7 +1059,7 @@ Variant SceneState::get_property_value(int p_node, const StringName &p_property, const NodeData::Property *p = nodes[p_node].properties.ptr(); for (int i = 0; i < pc; i++) { - if (p_property == namep[p[i].name]) { + if (p_property == namep[p[i].name & FLAG_PROP_NAME_MASK]) { found = true; return variants[p[i].value]; } @@ -1359,7 +1450,19 @@ int SceneState::get_node_property_count(int p_idx) const { StringName SceneState::get_node_property_name(int p_idx, int p_prop) const { ERR_FAIL_INDEX_V(p_idx, nodes.size(), StringName()); ERR_FAIL_INDEX_V(p_prop, nodes[p_idx].properties.size(), StringName()); - return names[nodes[p_idx].properties[p_prop].name]; + return names[nodes[p_idx].properties[p_prop].name & FLAG_PROP_NAME_MASK]; +} + +Vector<String> SceneState::get_node_deferred_nodepath_properties(int p_idx) const { + Vector<String> ret; + ERR_FAIL_INDEX_V(p_idx, nodes.size(), ret); + for (int i = 0; i < nodes[p_idx].properties.size(); i++) { + uint32_t idx = nodes[p_idx].properties[i].name; + if (idx & FLAG_PATH_PROPERTY_IS_NODE) { + ret.push_back(names[idx & FLAG_PROP_NAME_MASK]); + } + } + return ret; } Variant SceneState::get_node_property_value(int p_idx, int p_prop) const { @@ -1505,13 +1608,16 @@ int SceneState::add_node(int p_parent, int p_owner, int p_type, int p_name, int return nodes.size() - 1; } -void SceneState::add_node_property(int p_node, int p_name, int p_value) { +void SceneState::add_node_property(int p_node, int p_name, int p_value, bool p_deferred_node_path) { ERR_FAIL_INDEX(p_node, nodes.size()); ERR_FAIL_INDEX(p_name, names.size()); ERR_FAIL_INDEX(p_value, variants.size()); NodeData::Property prop; prop.name = p_name; + if (p_deferred_node_path) { + prop.name |= FLAG_PATH_PROPERTY_IS_NODE; + } prop.value = p_value; nodes.write[p_node].properties.push_back(prop); } @@ -1549,6 +1655,10 @@ void SceneState::add_editable_instance(const NodePath &p_path) { editable_instances.push_back(p_path); } +String SceneState::get_meta_pointer_property(const String &p_property) { + return META_POINTER_PROPERTY_BASE + p_property; +} + Vector<String> SceneState::_get_node_groups(int p_idx) const { Vector<StringName> groups = get_node_groups(p_idx); Vector<String> ret; diff --git a/scene/resources/packed_scene.h b/scene/resources/packed_scene.h index 96222937d0..5f8001c871 100644 --- a/scene/resources/packed_scene.h +++ b/scene/resources/packed_scene.h @@ -42,7 +42,7 @@ class SceneState : public RefCounted { Vector<NodePath> node_paths; Vector<NodePath> editable_instances; mutable HashMap<NodePath, int> node_path_cache; - mutable Map<int, int> base_scene_node_remap; + mutable HashMap<int, int> base_scene_node_remap; int base_scene_idx = -1; @@ -69,6 +69,12 @@ class SceneState : public RefCounted { Vector<int> groups; }; + struct DeferredNodePathProperties { + Node *base = nullptr; + StringName property; + NodePath path; + }; + Vector<NodeData> nodes; struct ConnectionData { @@ -83,8 +89,8 @@ class SceneState : public RefCounted { Vector<ConnectionData> connections; - Error _parse_node(Node *p_owner, Node *p_node, int p_parent_idx, Map<StringName, int> &name_map, HashMap<Variant, int, VariantHasher, VariantComparator> &variant_map, Map<Node *, int> &node_map, Map<Node *, int> &nodepath_map); - Error _parse_connections(Node *p_owner, Node *p_node, Map<StringName, int> &name_map, HashMap<Variant, int, VariantHasher, VariantComparator> &variant_map, Map<Node *, int> &node_map, Map<Node *, int> &nodepath_map); + Error _parse_node(Node *p_owner, Node *p_node, int p_parent_idx, HashMap<StringName, int> &name_map, HashMap<Variant, int, VariantHasher, VariantComparator> &variant_map, HashMap<Node *, int> &node_map, HashMap<Node *, int> &nodepath_map); + Error _parse_connections(Node *p_owner, Node *p_node, HashMap<StringName, int> &name_map, HashMap<Variant, int, VariantHasher, VariantComparator> &variant_map, HashMap<Node *, int> &node_map, HashMap<Node *, int> &nodepath_map); String path; @@ -104,6 +110,8 @@ public: FLAG_ID_IS_PATH = (1 << 30), TYPE_INSTANCED = 0x7FFFFFFF, FLAG_INSTANCE_IS_PLACEHOLDER = (1 << 30), + FLAG_PATH_PROPERTY_IS_NODE = (1 << 30), + FLAG_PROP_NAME_MASK = FLAG_PATH_PROPERTY_IS_NODE - 1, FLAG_MASK = (1 << 24) - 1, }; @@ -157,6 +165,7 @@ public: int get_node_property_count(int p_idx) const; StringName get_node_property_name(int p_idx, int p_prop) const; Variant get_node_property_value(int p_idx, int p_prop) const; + Vector<String> get_node_deferred_nodepath_properties(int p_idx) const; int get_connection_count() const; NodePath get_connection_source(int p_idx) const; @@ -177,7 +186,7 @@ public: int add_value(const Variant &p_value); int add_node_path(const NodePath &p_path); int add_node(int p_parent, int p_owner, int p_type, int p_name, int p_instance, int p_index); - void add_node_property(int p_node, int p_name, int p_value); + void add_node_property(int p_node, int p_name, int p_value, bool p_deferred_node_path = false); void add_node_group(int p_node, int p_group); void set_base_scene(int p_idx); void add_connection(int p_from, int p_to, int p_signal, int p_method, int p_flags, int p_unbinds, const Vector<int> &p_binds); @@ -186,6 +195,9 @@ public: virtual void set_last_modified_time(uint64_t p_time) { last_modified_time = p_time; } uint64_t get_last_modified_time() const { return last_modified_time; } + // Used when saving pointers (saves a path property instead). + static String get_meta_pointer_property(const String &p_property); + SceneState(); }; @@ -225,7 +237,9 @@ public: virtual void set_path(const String &p_path, bool p_take_over = false) override; #ifdef TOOLS_ENABLED - virtual void set_last_modified_time(uint64_t p_time) override { state->set_last_modified_time(p_time); } + virtual void set_last_modified_time(uint64_t p_time) override { + state->set_last_modified_time(p_time); + } #endif Ref<SceneState> get_state() const; diff --git a/scene/resources/particles_material.cpp b/scene/resources/particles_material.cpp index 01a0411545..7a49b9b515 100644 --- a/scene/resources/particles_material.cpp +++ b/scene/resources/particles_material.cpp @@ -34,7 +34,7 @@ Mutex ParticlesMaterial::material_mutex; SelfList<ParticlesMaterial>::List *ParticlesMaterial::dirty_materials = nullptr; -Map<ParticlesMaterial::MaterialKey, ParticlesMaterial::ShaderData> ParticlesMaterial::shader_map; +HashMap<ParticlesMaterial::MaterialKey, ParticlesMaterial::ShaderData, ParticlesMaterial::MaterialKey> ParticlesMaterial::shader_map; ParticlesMaterial::ShaderNames *ParticlesMaterial::shader_names = nullptr; void ParticlesMaterial::init_shaders() { @@ -197,14 +197,14 @@ void ParticlesMaterial::_update_shader() { code += "uniform vec3 emission_box_extents;\n"; } break; case EMISSION_SHAPE_DIRECTED_POINTS: { - code += "uniform sampler2D emission_texture_normal : hint_black;\n"; + code += "uniform sampler2D emission_texture_normal : hint_default_black;\n"; [[fallthrough]]; } case EMISSION_SHAPE_POINTS: { - code += "uniform sampler2D emission_texture_points : hint_black;\n"; + code += "uniform sampler2D emission_texture_points : hint_default_black;\n"; code += "uniform int emission_texture_point_count;\n"; if (emission_color_texture.is_valid()) { - code += "uniform sampler2D emission_texture_color : hint_white;\n"; + code += "uniform sampler2D emission_texture_color : hint_default_white;\n"; } } break; case EMISSION_SHAPE_RING: { @@ -228,53 +228,53 @@ void ParticlesMaterial::_update_shader() { code += "uniform bool sub_emitter_keep_velocity;\n"; } - code += "uniform vec4 color_value : hint_color;\n"; + code += "uniform vec4 color_value : source_color;\n"; code += "uniform vec3 gravity;\n"; if (color_ramp.is_valid()) { - code += "uniform sampler2D color_ramp;\n"; + code += "uniform sampler2D color_ramp : repeat_disable;\n"; } if (color_initial_ramp.is_valid()) { - code += "uniform sampler2D color_initial_ramp;\n"; + code += "uniform sampler2D color_initial_ramp : repeat_disable;\n"; } if (tex_parameters[PARAM_INITIAL_LINEAR_VELOCITY].is_valid()) { - code += "uniform sampler2D linear_velocity_texture;\n"; + code += "uniform sampler2D linear_velocity_texture : repeat_disable;\n"; } if (tex_parameters[PARAM_ORBIT_VELOCITY].is_valid()) { - code += "uniform sampler2D orbit_velocity_texture;\n"; + code += "uniform sampler2D orbit_velocity_texture : repeat_disable;\n"; } if (tex_parameters[PARAM_ANGULAR_VELOCITY].is_valid()) { - code += "uniform sampler2D angular_velocity_texture;\n"; + code += "uniform sampler2D angular_velocity_texture : repeat_disable;\n"; } if (tex_parameters[PARAM_LINEAR_ACCEL].is_valid()) { - code += "uniform sampler2D linear_accel_texture;\n"; + code += "uniform sampler2D linear_accel_texture : repeat_disable;\n"; } if (tex_parameters[PARAM_RADIAL_ACCEL].is_valid()) { - code += "uniform sampler2D radial_accel_texture;\n"; + code += "uniform sampler2D radial_accel_texture : repeat_disable;\n"; } if (tex_parameters[PARAM_TANGENTIAL_ACCEL].is_valid()) { - code += "uniform sampler2D tangent_accel_texture;\n"; + code += "uniform sampler2D tangent_accel_texture : repeat_disable;\n"; } if (tex_parameters[PARAM_DAMPING].is_valid()) { - code += "uniform sampler2D damping_texture;\n"; + code += "uniform sampler2D damping_texture : repeat_disable;\n"; } if (tex_parameters[PARAM_ANGLE].is_valid()) { - code += "uniform sampler2D angle_texture;\n"; + code += "uniform sampler2D angle_texture : repeat_disable;\n"; } if (tex_parameters[PARAM_SCALE].is_valid()) { - code += "uniform sampler2D scale_texture;\n"; + code += "uniform sampler2D scale_texture : repeat_disable;\n"; } if (tex_parameters[PARAM_HUE_VARIATION].is_valid()) { - code += "uniform sampler2D hue_variation_texture;\n"; + code += "uniform sampler2D hue_variation_texture : repeat_disable;\n"; } if (tex_parameters[PARAM_ANIM_SPEED].is_valid()) { - code += "uniform sampler2D anim_speed_texture;\n"; + code += "uniform sampler2D anim_speed_texture : repeat_disable;\n"; } if (tex_parameters[PARAM_ANIM_OFFSET].is_valid()) { - code += "uniform sampler2D anim_offset_texture;\n"; + code += "uniform sampler2D anim_offset_texture : repeat_disable;\n"; } if (collision_enabled) { @@ -488,6 +488,12 @@ void ParticlesMaterial::_update_shader() { code += " float degree_to_rad = pi / 180.0;\n"; code += "\n"; + if (emission_shape == EMISSION_SHAPE_POINTS || emission_shape == EMISSION_SHAPE_DIRECTED_POINTS) { + code += " int point = min(emission_texture_point_count - 1, int(rand_from_seed(alt_seed) * float(emission_texture_point_count)));\n"; + code += " ivec2 emission_tex_size = textureSize(emission_texture_points, 0);\n"; + code += " ivec2 emission_tex_ofs = ivec2(point % emission_tex_size.x, point / emission_tex_size.x);\n"; + } + code += " CUSTOM.y += DELTA / LIFETIME;\n"; code += " float tv = CUSTOM.y / CUSTOM.w;\n"; if (tex_parameters[PARAM_INITIAL_LINEAR_VELOCITY].is_valid()) { @@ -722,11 +728,9 @@ void ParticlesMaterial::_update_shader() { code += " if (DELTA >= interval_rem) emit_count = 1;\n"; } break; case SUB_EMITTER_AT_COLLISION: { - //not implemented yet code += " if (COLLIDED) emit_count = 1;\n"; } break; case SUB_EMITTER_AT_END: { - //not implemented yet code += " float unit_delta = DELTA/LIFETIME;\n"; code += " float end_time = CUSTOM.w * 0.95;\n"; // if we do at the end we might miss it, as it can just get deactivated by emitter code += " if (CUSTOM.y < end_time && (CUSTOM.y + unit_delta) >= end_time) emit_count = sub_emitter_amount_at_end;\n"; @@ -1473,7 +1477,7 @@ void ParticlesMaterial::_bind_methods() { ADD_GROUP("Sub Emitter", "sub_emitter_"); ADD_PROPERTY(PropertyInfo(Variant::INT, "sub_emitter_mode", PROPERTY_HINT_ENUM, "Disabled,Constant,At End,At Collision"), "set_sub_emitter_mode", "get_sub_emitter_mode"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "sub_emitter_frequency", PROPERTY_HINT_RANGE, "0.01,100,0.01"), "set_sub_emitter_frequency", "get_sub_emitter_frequency"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "sub_emitter_frequency", PROPERTY_HINT_RANGE, "0.01,100,0.01,suffix:Hz"), "set_sub_emitter_frequency", "get_sub_emitter_frequency"); ADD_PROPERTY(PropertyInfo(Variant::INT, "sub_emitter_amount_at_end", PROPERTY_HINT_RANGE, "1,32,1"), "set_sub_emitter_amount_at_end", "get_sub_emitter_amount_at_end"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "sub_emitter_keep_velocity"), "set_sub_emitter_keep_velocity", "get_sub_emitter_keep_velocity"); diff --git a/scene/resources/particles_material.h b/scene/resources/particles_material.h index 57da344ce0..af45593f38 100644 --- a/scene/resources/particles_material.h +++ b/scene/resources/particles_material.h @@ -109,6 +109,14 @@ private: uint32_t key = 0; + static uint32_t hash(const MaterialKey &p_key) { + return hash_murmur3_one_32(p_key.key); + } + + bool operator==(const MaterialKey &p_key) const { + return key == p_key.key; + } + bool operator<(const MaterialKey &p_key) const { return key < p_key.key; } @@ -119,7 +127,7 @@ private: int users = 0; }; - static Map<MaterialKey, ShaderData> shader_map; + static HashMap<MaterialKey, ShaderData, MaterialKey> shader_map; MaterialKey current_key; @@ -230,8 +238,8 @@ private: bool is_initialized = false; Vector3 direction; - float spread; - float flatness; + float spread = 0.0f; + float flatness = 0.0f; float params_min[PARAM_MAX]; float params_max[PARAM_MAX]; @@ -244,34 +252,34 @@ private: bool particle_flags[PARTICLE_FLAG_MAX]; EmissionShape emission_shape; - float emission_sphere_radius; + float emission_sphere_radius = 0.0f; Vector3 emission_box_extents; Ref<Texture2D> emission_point_texture; Ref<Texture2D> emission_normal_texture; Ref<Texture2D> emission_color_texture; Vector3 emission_ring_axis; - real_t emission_ring_height; - real_t emission_ring_radius; - real_t emission_ring_inner_radius; + real_t emission_ring_height = 0.0f; + real_t emission_ring_radius = 0.0f; + real_t emission_ring_inner_radius = 0.0f; int emission_point_count = 1; - bool anim_loop; + bool anim_loop = false; Vector3 gravity; - double lifetime_randomness; + double lifetime_randomness = 0.0; SubEmitterMode sub_emitter_mode; - double sub_emitter_frequency; - int sub_emitter_amount_at_end; - bool sub_emitter_keep_velocity; + double sub_emitter_frequency = 0.0; + int sub_emitter_amount_at_end = 0; + bool sub_emitter_keep_velocity = false; //do not save emission points here - bool attractor_interaction_enabled; - bool collision_enabled; - bool collision_scale; - float collision_friction; - float collision_bounce; + bool attractor_interaction_enabled = false; + bool collision_enabled = false; + bool collision_scale = false; + float collision_friction = 0.0f; + float collision_bounce = 0.0f; protected: static void _bind_methods(); diff --git a/scene/resources/polygon_path_finder.cpp b/scene/resources/polygon_path_finder.cpp index 882afdb43d..29135e30c9 100644 --- a/scene/resources/polygon_path_finder.cpp +++ b/scene/resources/polygon_path_finder.cpp @@ -34,8 +34,8 @@ bool PolygonPathFinder::_is_point_inside(const Vector2 &p_point) const { int crosses = 0; - for (Set<Edge>::Element *E = edges.front(); E; E = E->next()) { - const Edge &e = E->get(); + for (const Edge &E : edges) { + const Edge &e = E; Vector2 a = points[e.points[0]].pos; Vector2 b = points[e.points[1]].pos; @@ -105,8 +105,8 @@ void PolygonPathFinder::setup(const Vector<Vector2> &p_points, const Vector<int> bool valid = true; - for (Set<Edge>::Element *E = edges.front(); E; E = E->next()) { - const Edge &e = E->get(); + for (const Edge &E : edges) { + const Edge &e = E; if (e.points[0] == i || e.points[1] == i || e.points[0] == j || e.points[1] == j) { continue; } @@ -137,11 +137,11 @@ Vector<Vector2> PolygonPathFinder::find_path(const Vector2 &p_from, const Vector Edge ignore_to_edge(-1, -1); if (!_is_point_inside(from)) { - float closest_dist = 1e20; + float closest_dist = 1e20f; Vector2 closest_point; - for (Set<Edge>::Element *E = edges.front(); E; E = E->next()) { - const Edge &e = E->get(); + for (const Edge &E : edges) { + const Edge &e = E; Vector2 seg[2] = { points[e.points[0]].pos, points[e.points[1]].pos @@ -151,7 +151,7 @@ Vector<Vector2> PolygonPathFinder::find_path(const Vector2 &p_from, const Vector float d = from.distance_squared_to(closest); if (d < closest_dist) { - ignore_from_edge = E->get(); + ignore_from_edge = E; closest_dist = d; closest_point = closest; } @@ -161,11 +161,11 @@ Vector<Vector2> PolygonPathFinder::find_path(const Vector2 &p_from, const Vector }; if (!_is_point_inside(to)) { - float closest_dist = 1e20; + float closest_dist = 1e20f; Vector2 closest_point; - for (Set<Edge>::Element *E = edges.front(); E; E = E->next()) { - const Edge &e = E->get(); + for (const Edge &E : edges) { + const Edge &e = E; Vector2 seg[2] = { points[e.points[0]].pos, points[e.points[1]].pos @@ -175,7 +175,7 @@ Vector<Vector2> PolygonPathFinder::find_path(const Vector2 &p_from, const Vector float d = to.distance_squared_to(closest); if (d < closest_dist) { - ignore_to_edge = E->get(); + ignore_to_edge = E; closest_dist = d; closest_point = closest; } @@ -188,8 +188,8 @@ Vector<Vector2> PolygonPathFinder::find_path(const Vector2 &p_from, const Vector { bool can_see_eachother = true; - for (Set<Edge>::Element *E = edges.front(); E; E = E->next()) { - const Edge &e = E->get(); + for (const Edge &E : edges) { + const Edge &e = E; if (e.points[0] == ignore_from_edge.points[0] && e.points[1] == ignore_from_edge.points[1]) { continue; } @@ -240,8 +240,8 @@ Vector<Vector2> PolygonPathFinder::find_path(const Vector2 &p_from, const Vector valid_b = false; } - for (Set<Edge>::Element *E = edges.front(); E; E = E->next()) { - const Edge &e = E->get(); + for (const Edge &E : edges) { + const Edge &e = E; if (e.points[0] == i || e.points[1] == i) { continue; @@ -289,14 +289,14 @@ Vector<Vector2> PolygonPathFinder::find_path(const Vector2 &p_from, const Vector } //solve graph - Set<int> open_list; + HashSet<int> open_list; points.write[aidx].distance = 0; points.write[aidx].prev = aidx; - for (Set<int>::Element *E = points[aidx].connections.front(); E; E = E->next()) { - open_list.insert(E->get()); - points.write[E->get()].distance = from.distance_to(points[E->get()].pos); - points.write[E->get()].prev = aidx; + for (const int &E : points[aidx].connections) { + open_list.insert(E); + points.write[E].distance = from.distance_to(points[E].pos); + points.write[E].prev = aidx; } bool found_route = false; @@ -312,14 +312,14 @@ Vector<Vector2> PolygonPathFinder::find_path(const Vector2 &p_from, const Vector float least_cost = 1e30; //this could be faster (cache previous results) - for (Set<int>::Element *E = open_list.front(); E; E = E->next()) { - const Point &p = points[E->get()]; + for (const int &E : open_list) { + const Point &p = points[E]; float cost = p.distance; cost += p.pos.distance_to(to); cost += p.penalty; if (cost < least_cost) { - least_cost_point = E->get(); + least_cost_point = E; least_cost = cost; } } @@ -327,8 +327,8 @@ Vector<Vector2> PolygonPathFinder::find_path(const Vector2 &p_from, const Vector const Point &np = points[least_cost_point]; //open the neighbours for search - for (Set<int>::Element *E = np.connections.front(); E; E = E->next()) { - Point &p = points.write[E->get()]; + for (const int &E : np.connections) { + Point &p = points.write[E]; float distance = np.pos.distance_to(p.pos) + np.distance; if (p.prev != -1) { @@ -343,9 +343,9 @@ Vector<Vector2> PolygonPathFinder::find_path(const Vector2 &p_from, const Vector p.prev = least_cost_point; p.distance = distance; - open_list.insert(E->get()); + open_list.insert(E); - if (E->get() == bidx) { + if (E == bidx) { //oh my reached end! stop algorithm found_route = true; break; @@ -459,8 +459,8 @@ Dictionary PolygonPathFinder::_get_data() const { { int *cw = c.ptrw(); int idx = 0; - for (Set<int>::Element *E = points[i].connections.front(); E; E = E->next()) { - cw[idx++] = E->get(); + for (const int &E : points[i].connections) { + cw[idx++] = E; } } connections[i] = c; @@ -469,9 +469,9 @@ Dictionary PolygonPathFinder::_get_data() const { { int *iw = ind.ptrw(); int idx = 0; - for (Set<Edge>::Element *E = edges.front(); E; E = E->next()) { - iw[idx++] = E->get().points[0]; - iw[idx++] = E->get().points[1]; + for (const Edge &E : edges) { + iw[idx++] = E.points[0]; + iw[idx++] = E.points[1]; } } @@ -489,11 +489,11 @@ bool PolygonPathFinder::is_point_inside(const Vector2 &p_point) const { } Vector2 PolygonPathFinder::get_closest_point(const Vector2 &p_point) const { - float closest_dist = 1e20; + float closest_dist = 1e20f; Vector2 closest_point; - for (Set<Edge>::Element *E = edges.front(); E; E = E->next()) { - const Edge &e = E->get(); + for (const Edge &E : edges) { + const Edge &e = E; Vector2 seg[2] = { points[e.points[0]].pos, points[e.points[1]].pos @@ -508,7 +508,7 @@ Vector2 PolygonPathFinder::get_closest_point(const Vector2 &p_point) const { } } - ERR_FAIL_COND_V(closest_dist == 1e20, Vector2()); + ERR_FAIL_COND_V(Math::is_equal_approx(closest_dist, 1e20f), Vector2()); return closest_point; } @@ -516,9 +516,9 @@ Vector2 PolygonPathFinder::get_closest_point(const Vector2 &p_point) const { Vector<Vector2> PolygonPathFinder::get_intersections(const Vector2 &p_from, const Vector2 &p_to) const { Vector<Vector2> inters; - for (Set<Edge>::Element *E = edges.front(); E; E = E->next()) { - Vector2 a = points[E->get().points[0]].pos; - Vector2 b = points[E->get().points[1]].pos; + for (const Edge &E : edges) { + Vector2 a = points[E.points[0]].pos; + Vector2 b = points[E.points[1]].pos; Vector2 res; if (Geometry2D::segment_intersects_segment(a, b, p_from, p_to, &res)) { diff --git a/scene/resources/polygon_path_finder.h b/scene/resources/polygon_path_finder.h index db96192917..0e22b53dcb 100644 --- a/scene/resources/polygon_path_finder.h +++ b/scene/resources/polygon_path_finder.h @@ -38,21 +38,23 @@ class PolygonPathFinder : public Resource { struct Point { Vector2 pos; - Set<int> connections; + HashSet<int> connections; float distance = 0.0; float penalty = 0.0; int prev = 0; }; - struct Edge { - int points[2] = {}; + union Edge { + struct { + int32_t points[2]; + }; + uint64_t key = 0; - _FORCE_INLINE_ bool operator<(const Edge &p_edge) const { - if (points[0] == p_edge.points[0]) { - return points[1] < p_edge.points[1]; - } else { - return points[0] < p_edge.points[0]; - } + _FORCE_INLINE_ bool operator==(const Edge &p_edge) const { + return key == p_edge.key; + } + _FORCE_INLINE_ static uint32_t hash(const Edge &p_edge) { + return hash_one_uint64(p_edge.key); } Edge(int a = 0, int b = 0) { @@ -68,7 +70,7 @@ class PolygonPathFinder : public Resource { Rect2 bounds; Vector<Point> points; - Set<Edge> edges; + HashSet<Edge, Edge> edges; bool _is_point_inside(const Vector2 &p_point) const; diff --git a/scene/resources/primitive_meshes.cpp b/scene/resources/primitive_meshes.cpp index 40edc5f198..68441afb1c 100644 --- a/scene/resources/primitive_meshes.cpp +++ b/scene/resources/primitive_meshes.cpp @@ -29,7 +29,12 @@ /*************************************************************************/ #include "primitive_meshes.h" + +#include "core/core_string_names.h" +#include "scene/resources/theme.h" #include "servers/rendering_server.h" +#include "thirdparty/misc/clipper.hpp" +#include "thirdparty/misc/polypartition.h" /** PrimitiveMesh @@ -214,7 +219,7 @@ void PrimitiveMesh::_bind_methods() { ClassDB::bind_method(D_METHOD("get_flip_faces"), &PrimitiveMesh::get_flip_faces); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "material", PROPERTY_HINT_RESOURCE_TYPE, "BaseMaterial3D,ShaderMaterial"), "set_material", "get_material"); - ADD_PROPERTY(PropertyInfo(Variant::AABB, "custom_aabb", PROPERTY_HINT_NONE, ""), "set_custom_aabb", "get_custom_aabb"); + ADD_PROPERTY(PropertyInfo(Variant::AABB, "custom_aabb", PROPERTY_HINT_NONE, "suffix:m"), "set_custom_aabb", "get_custom_aabb"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "flip_faces"), "set_flip_faces", "get_flip_faces"); GDVIRTUAL_BIND(_create_mesh_array); @@ -314,7 +319,7 @@ void CapsuleMesh::create_mesh_array(Array &p_arr, const float radius, const floa Vector3 p = Vector3(x * radius * w, y, -z * radius * w); points.push_back(p + Vector3(0.0, 0.5 * height - radius, 0.0)); normals.push_back(p.normalized()); - ADD_TANGENT(z, 0.0, x, 1.0) + ADD_TANGENT(-z, 0.0, -x, 1.0) uvs.push_back(Vector2(u, v * onethird)); point++; @@ -353,7 +358,7 @@ void CapsuleMesh::create_mesh_array(Array &p_arr, const float radius, const floa Vector3 p = Vector3(x * radius, y, -z * radius); points.push_back(p); normals.push_back(Vector3(x, 0.0, -z)); - ADD_TANGENT(z, 0.0, x, 1.0) + ADD_TANGENT(-z, 0.0, -x, 1.0) uvs.push_back(Vector2(u, onethird + (v * onethird))); point++; @@ -393,7 +398,7 @@ void CapsuleMesh::create_mesh_array(Array &p_arr, const float radius, const floa Vector3 p = Vector3(x * radius * w, y, -z * radius * w); points.push_back(p + Vector3(0.0, -0.5 * height + radius, 0.0)); normals.push_back(p.normalized()); - ADD_TANGENT(z, 0.0, x, 1.0) + ADD_TANGENT(-z, 0.0, -x, 1.0) uvs.push_back(Vector2(u2, twothirds + ((v - 1.0) * onethird))); point++; @@ -430,16 +435,19 @@ void CapsuleMesh::_bind_methods() { ClassDB::bind_method(D_METHOD("set_rings", "rings"), &CapsuleMesh::set_rings); ClassDB::bind_method(D_METHOD("get_rings"), &CapsuleMesh::get_rings); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radius", PROPERTY_HINT_RANGE, "0.001,100.0,0.001,or_greater"), "set_radius", "get_radius"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "height", PROPERTY_HINT_RANGE, "0.001,100.0,0.001,or_greater"), "set_height", "get_height"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radius", PROPERTY_HINT_RANGE, "0.001,100.0,0.001,or_greater,suffix:m"), "set_radius", "get_radius"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "height", PROPERTY_HINT_RANGE, "0.001,100.0,0.001,or_greater,suffix:m"), "set_height", "get_height"); ADD_PROPERTY(PropertyInfo(Variant::INT, "radial_segments", PROPERTY_HINT_RANGE, "1,100,1,or_greater"), "set_radial_segments", "get_radial_segments"); ADD_PROPERTY(PropertyInfo(Variant::INT, "rings", PROPERTY_HINT_RANGE, "1,100,1,or_greater"), "set_rings", "get_rings"); + + ADD_LINKED_PROPERTY("radius", "height"); + ADD_LINKED_PROPERTY("height", "radius"); } void CapsuleMesh::set_radius(const float p_radius) { radius = p_radius; if (radius > height * 0.5) { - radius = height * 0.5; + height = radius * 2.0; } _request_update(); } @@ -451,7 +459,7 @@ float CapsuleMesh::get_radius() const { void CapsuleMesh::set_height(const float p_height) { height = p_height; if (radius > height * 0.5) { - height = radius * 2; + radius = height * 0.5; } _request_update(); } @@ -691,7 +699,7 @@ void BoxMesh::_bind_methods() { ClassDB::bind_method(D_METHOD("set_subdivide_depth", "divisions"), &BoxMesh::set_subdivide_depth); ClassDB::bind_method(D_METHOD("get_subdivide_depth"), &BoxMesh::get_subdivide_depth); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "size"), "set_size", "get_size"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "size", PROPERTY_HINT_NONE, "suffix:m"), "set_size", "get_size"); ADD_PROPERTY(PropertyInfo(Variant::INT, "subdivide_width", PROPERTY_HINT_RANGE, "0,100,1,or_greater"), "set_subdivide_width", "get_subdivide_width"); ADD_PROPERTY(PropertyInfo(Variant::INT, "subdivide_height", PROPERTY_HINT_RANGE, "0,100,1,or_greater"), "set_subdivide_height", "get_subdivide_height"); ADD_PROPERTY(PropertyInfo(Variant::INT, "subdivide_depth", PROPERTY_HINT_RANGE, "0,100,1,or_greater"), "set_subdivide_depth", "get_subdivide_depth"); @@ -740,10 +748,10 @@ BoxMesh::BoxMesh() {} */ void CylinderMesh::_create_mesh_array(Array &p_arr) const { - create_mesh_array(p_arr, top_radius, bottom_radius, height, radial_segments, rings); + create_mesh_array(p_arr, top_radius, bottom_radius, height, radial_segments, rings, cap_top, cap_bottom); } -void CylinderMesh::create_mesh_array(Array &p_arr, float top_radius, float bottom_radius, float height, int radial_segments, int rings) { +void CylinderMesh::create_mesh_array(Array &p_arr, float top_radius, float bottom_radius, float height, int radial_segments, int rings, bool cap_top, bool cap_bottom) { int i, j, prevrow, thisrow, point; float x, y, z, u, v, radius; @@ -801,7 +809,7 @@ void CylinderMesh::create_mesh_array(Array &p_arr, float top_radius, float botto }; // add top - if (top_radius > 0.0) { + if (cap_top && top_radius > 0.0) { y = height * 0.5; thisrow = point; @@ -837,7 +845,7 @@ void CylinderMesh::create_mesh_array(Array &p_arr, float top_radius, float botto }; // add bottom - if (bottom_radius > 0.0) { + if (cap_bottom && bottom_radius > 0.0) { y = height * -0.5; thisrow = point; @@ -892,11 +900,19 @@ void CylinderMesh::_bind_methods() { ClassDB::bind_method(D_METHOD("set_rings", "rings"), &CylinderMesh::set_rings); ClassDB::bind_method(D_METHOD("get_rings"), &CylinderMesh::get_rings); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "top_radius", PROPERTY_HINT_RANGE, "0,100,0.001,or_greater"), "set_top_radius", "get_top_radius"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "bottom_radius", PROPERTY_HINT_RANGE, "0,100,0.001,or_greater"), "set_bottom_radius", "get_bottom_radius"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "height", PROPERTY_HINT_RANGE, "0.001,100,0.001,or_greater"), "set_height", "get_height"); + ClassDB::bind_method(D_METHOD("set_cap_top", "cap_top"), &CylinderMesh::set_cap_top); + ClassDB::bind_method(D_METHOD("is_cap_top"), &CylinderMesh::is_cap_top); + + ClassDB::bind_method(D_METHOD("set_cap_bottom", "cap_bottom"), &CylinderMesh::set_cap_bottom); + ClassDB::bind_method(D_METHOD("is_cap_bottom"), &CylinderMesh::is_cap_bottom); + + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "top_radius", PROPERTY_HINT_RANGE, "0,100,0.001,or_greater,suffix:m"), "set_top_radius", "get_top_radius"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "bottom_radius", PROPERTY_HINT_RANGE, "0,100,0.001,or_greater,suffix:m"), "set_bottom_radius", "get_bottom_radius"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "height", PROPERTY_HINT_RANGE, "0.001,100,0.001,or_greater,suffix:m"), "set_height", "get_height"); ADD_PROPERTY(PropertyInfo(Variant::INT, "radial_segments", PROPERTY_HINT_RANGE, "1,100,1,or_greater"), "set_radial_segments", "get_radial_segments"); ADD_PROPERTY(PropertyInfo(Variant::INT, "rings", PROPERTY_HINT_RANGE, "1,100,1,or_greater"), "set_rings", "get_rings"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "cap_top"), "set_cap_top", "is_cap_top"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "cap_bottom"), "set_cap_bottom", "is_cap_bottom"); } void CylinderMesh::set_top_radius(const float p_radius) { @@ -944,6 +960,24 @@ int CylinderMesh::get_rings() const { return rings; } +void CylinderMesh::set_cap_top(bool p_cap_top) { + cap_top = p_cap_top; + _request_update(); +} + +bool CylinderMesh::is_cap_top() const { + return cap_top; +} + +void CylinderMesh::set_cap_bottom(bool p_cap_bottom) { + cap_bottom = p_cap_bottom; + _request_update(); +} + +bool CylinderMesh::is_cap_bottom() const { + return cap_bottom; +} + CylinderMesh::CylinderMesh() {} /** @@ -1022,10 +1056,10 @@ void PlaneMesh::_bind_methods() { ClassDB::bind_method(D_METHOD("set_center_offset", "offset"), &PlaneMesh::set_center_offset); ClassDB::bind_method(D_METHOD("get_center_offset"), &PlaneMesh::get_center_offset); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "size"), "set_size", "get_size"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "size", PROPERTY_HINT_NONE, "suffix:m"), "set_size", "get_size"); ADD_PROPERTY(PropertyInfo(Variant::INT, "subdivide_width", PROPERTY_HINT_RANGE, "0,100,1,or_greater"), "set_subdivide_width", "get_subdivide_width"); ADD_PROPERTY(PropertyInfo(Variant::INT, "subdivide_depth", PROPERTY_HINT_RANGE, "0,100,1,or_greater"), "set_subdivide_depth", "get_subdivide_depth"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "center_offset"), "set_center_offset", "get_center_offset"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "center_offset", PROPERTY_HINT_NONE, "suffix:m"), "set_center_offset", "get_center_offset"); } void PlaneMesh::set_size(const Size2 &p_size) { @@ -1293,7 +1327,7 @@ void PrismMesh::_bind_methods() { ClassDB::bind_method(D_METHOD("get_subdivide_depth"), &PrismMesh::get_subdivide_depth); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "left_to_right", PROPERTY_HINT_RANGE, "-2.0,2.0,0.1"), "set_left_to_right", "get_left_to_right"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "size"), "set_size", "get_size"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "size", PROPERTY_HINT_NONE, "suffix:m"), "set_size", "get_size"); ADD_PROPERTY(PropertyInfo(Variant::INT, "subdivide_width", PROPERTY_HINT_RANGE, "0,100,1,or_greater"), "set_subdivide_width", "get_subdivide_width"); ADD_PROPERTY(PropertyInfo(Variant::INT, "subdivide_height", PROPERTY_HINT_RANGE, "0,100,1,or_greater"), "set_subdivide_height", "get_subdivide_height"); ADD_PROPERTY(PropertyInfo(Variant::INT, "subdivide_depth", PROPERTY_HINT_RANGE, "0,100,1,or_greater"), "set_subdivide_depth", "get_subdivide_depth"); @@ -1406,8 +1440,8 @@ void QuadMesh::_bind_methods() { ClassDB::bind_method(D_METHOD("set_center_offset", "center_offset"), &QuadMesh::set_center_offset); ClassDB::bind_method(D_METHOD("get_center_offset"), &QuadMesh::get_center_offset); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "size"), "set_size", "get_size"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "center_offset"), "set_center_offset", "get_center_offset"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "size", PROPERTY_HINT_NONE, "suffix:m"), "set_size", "get_size"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "center_offset", PROPERTY_HINT_NONE, "suffix:m"), "set_center_offset", "get_center_offset"); } uint32_t QuadMesh::surface_get_format(int p_idx) const { @@ -1533,8 +1567,8 @@ void SphereMesh::_bind_methods() { ClassDB::bind_method(D_METHOD("set_is_hemisphere", "is_hemisphere"), &SphereMesh::set_is_hemisphere); ClassDB::bind_method(D_METHOD("get_is_hemisphere"), &SphereMesh::get_is_hemisphere); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radius", PROPERTY_HINT_RANGE, "0.001,100.0,0.001,or_greater"), "set_radius", "get_radius"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "height", PROPERTY_HINT_RANGE, "0.001,100.0,0.001,or_greater"), "set_height", "get_height"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radius", PROPERTY_HINT_RANGE, "0.001,100.0,0.001,or_greater,suffix:m"), "set_radius", "get_radius"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "height", PROPERTY_HINT_RANGE, "0.001,100.0,0.001,or_greater,suffix:m"), "set_height", "get_height"); ADD_PROPERTY(PropertyInfo(Variant::INT, "radial_segments", PROPERTY_HINT_RANGE, "1,100,1,or_greater"), "set_radial_segments", "get_radial_segments"); ADD_PROPERTY(PropertyInfo(Variant::INT, "rings", PROPERTY_HINT_RANGE, "1,100,1,or_greater"), "set_rings", "get_rings"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "is_hemisphere"), "set_is_hemisphere", "get_is_hemisphere"); @@ -1912,12 +1946,12 @@ void TubeTrailMesh::_bind_methods() { ClassDB::bind_method(D_METHOD("set_curve", "curve"), &TubeTrailMesh::set_curve); ClassDB::bind_method(D_METHOD("get_curve"), &TubeTrailMesh::get_curve); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radius", PROPERTY_HINT_RANGE, "0.001,100.0,0.001,or_greater"), "set_radius", "get_radius"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radius", PROPERTY_HINT_RANGE, "0.001,100.0,0.001,or_greater,suffix:m"), "set_radius", "get_radius"); ADD_PROPERTY(PropertyInfo(Variant::INT, "radial_steps", PROPERTY_HINT_RANGE, "3,128,1"), "set_radial_steps", "get_radial_steps"); ADD_PROPERTY(PropertyInfo(Variant::INT, "sections", PROPERTY_HINT_RANGE, "2,128,1"), "set_sections", "get_sections"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "section_length", PROPERTY_HINT_RANGE, "0.001,1024.0,0.001,or_greater"), "set_section_length", "get_section_length"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "section_length", PROPERTY_HINT_RANGE, "0.001,1024.0,0.001,or_greater,suffix:m"), "set_section_length", "get_section_length"); ADD_PROPERTY(PropertyInfo(Variant::INT, "section_rings", PROPERTY_HINT_RANGE, "1,128,1"), "set_section_rings", "get_section_rings"); @@ -2139,9 +2173,9 @@ void RibbonTrailMesh::_bind_methods() { ClassDB::bind_method(D_METHOD("get_shape"), &RibbonTrailMesh::get_shape); ADD_PROPERTY(PropertyInfo(Variant::INT, "shape", PROPERTY_HINT_ENUM, "Flat,Cross"), "set_shape", "get_shape"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "size", PROPERTY_HINT_RANGE, "0.001,100.0,0.001,or_greater"), "set_size", "get_size"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "size", PROPERTY_HINT_RANGE, "0.001,100.0,0.001,or_greater,suffix:m"), "set_size", "get_size"); ADD_PROPERTY(PropertyInfo(Variant::INT, "sections", PROPERTY_HINT_RANGE, "2,128,1"), "set_sections", "get_sections"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "section_length", PROPERTY_HINT_RANGE, "0.001,1024.0,0.001,or_greater"), "set_section_length", "get_section_length"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "section_length", PROPERTY_HINT_RANGE, "0.001,1024.0,0.001,or_greater,suffix:m"), "set_section_length", "get_section_length"); ADD_PROPERTY(PropertyInfo(Variant::INT, "section_segments", PROPERTY_HINT_RANGE, "1,128,1"), "set_section_segments", "get_section_segments"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_curve", "get_curve"); @@ -2151,3 +2185,769 @@ void RibbonTrailMesh::_bind_methods() { RibbonTrailMesh::RibbonTrailMesh() { } + +/*************************************************************************/ +/* TextMesh */ +/*************************************************************************/ + +void TextMesh::_generate_glyph_mesh_data(const GlyphMeshKey &p_key, const Glyph &p_gl) const { + if (cache.has(p_key)) { + return; + } + + GlyphMeshData &gl_data = cache[p_key]; + + Dictionary d = TS->font_get_glyph_contours(p_gl.font_rid, p_gl.font_size, p_gl.index); + Vector2 origin = Vector2(p_gl.x_off, p_gl.y_off) * pixel_size; + + PackedVector3Array points = d["points"]; + PackedInt32Array contours = d["contours"]; + bool orientation = d["orientation"]; + + if (points.size() < 3 || contours.size() < 1) { + return; // No full contours, only glyph control points (or nothing), ignore. + } + + // Approximate Bezier curves as polygons. + // See https://freetype.org/freetype2/docs/glyphs/glyphs-6.html, for more info. + for (int i = 0; i < contours.size(); i++) { + int32_t start = (i == 0) ? 0 : (contours[i - 1] + 1); + int32_t end = contours[i]; + Vector<ContourPoint> polygon; + + for (int32_t j = start; j <= end; j++) { + if (points[j].z == TextServer::CONTOUR_CURVE_TAG_ON) { + // Point on the curve. + Vector2 p = Vector2(points[j].x, points[j].y) * pixel_size + origin; + polygon.push_back(ContourPoint(p, true)); + } else if (points[j].z == TextServer::CONTOUR_CURVE_TAG_OFF_CONIC) { + // Conic Bezier arc. + int32_t next = (j == end) ? start : (j + 1); + int32_t prev = (j == start) ? end : (j - 1); + Vector2 p0; + Vector2 p1 = Vector2(points[j].x, points[j].y); + Vector2 p2; + + // For successive conic OFF points add a virtual ON point in the middle. + if (points[prev].z == TextServer::CONTOUR_CURVE_TAG_OFF_CONIC) { + p0 = (Vector2(points[prev].x, points[prev].y) + Vector2(points[j].x, points[j].y)) / 2.0; + } else if (points[prev].z == TextServer::CONTOUR_CURVE_TAG_ON) { + p0 = Vector2(points[prev].x, points[prev].y); + } else { + ERR_FAIL_MSG(vformat("Invalid conic arc point sequence at %d:%d", i, j)); + } + if (points[next].z == TextServer::CONTOUR_CURVE_TAG_OFF_CONIC) { + p2 = (Vector2(points[j].x, points[j].y) + Vector2(points[next].x, points[next].y)) / 2.0; + } else if (points[next].z == TextServer::CONTOUR_CURVE_TAG_ON) { + p2 = Vector2(points[next].x, points[next].y); + } else { + ERR_FAIL_MSG(vformat("Invalid conic arc point sequence at %d:%d", i, j)); + } + + real_t step = CLAMP(curve_step / (p0 - p2).length(), 0.01, 0.5); + real_t t = step; + while (t < 1.0) { + real_t omt = (1.0 - t); + real_t omt2 = omt * omt; + real_t t2 = t * t; + + Vector2 point = p1 + omt2 * (p0 - p1) + t2 * (p2 - p1); + Vector2 p = point * pixel_size + origin; + polygon.push_back(ContourPoint(p, false)); + t += step; + } + } else if (points[j].z == TextServer::CONTOUR_CURVE_TAG_OFF_CUBIC) { + // Cubic Bezier arc. + int32_t cur = j; + int32_t next1 = (j == end) ? start : (j + 1); + int32_t next2 = (next1 == end) ? start : (next1 + 1); + int32_t prev = (j == start) ? end : (j - 1); + + // There must be exactly two OFF points and two ON points for each cubic arc. + if (points[prev].z != TextServer::CONTOUR_CURVE_TAG_ON) { + cur = (cur == 0) ? end : cur - 1; + next1 = (next1 == 0) ? end : next1 - 1; + next2 = (next2 == 0) ? end : next2 - 1; + prev = (prev == 0) ? end : prev - 1; + } else { + j++; + } + ERR_FAIL_COND_MSG(points[prev].z != TextServer::CONTOUR_CURVE_TAG_ON, vformat("Invalid cubic arc point sequence at %d:%d", i, prev)); + ERR_FAIL_COND_MSG(points[cur].z != TextServer::CONTOUR_CURVE_TAG_OFF_CUBIC, vformat("Invalid cubic arc point sequence at %d:%d", i, cur)); + ERR_FAIL_COND_MSG(points[next1].z != TextServer::CONTOUR_CURVE_TAG_OFF_CUBIC, vformat("Invalid cubic arc point sequence at %d:%d", i, next1)); + ERR_FAIL_COND_MSG(points[next2].z != TextServer::CONTOUR_CURVE_TAG_ON, vformat("Invalid cubic arc point sequence at %d:%d", i, next2)); + + Vector2 p0 = Vector2(points[prev].x, points[prev].y); + Vector2 p1 = Vector2(points[cur].x, points[cur].y); + Vector2 p2 = Vector2(points[next1].x, points[next1].y); + Vector2 p3 = Vector2(points[next2].x, points[next2].y); + + real_t step = CLAMP(curve_step / (p0 - p3).length(), 0.01, 0.5); + real_t t = step; + while (t < 1.0) { + real_t omt = (1.0 - t); + real_t omt2 = omt * omt; + real_t omt3 = omt2 * omt; + real_t t2 = t * t; + real_t t3 = t2 * t; + + Vector2 point = p0 * omt3 + p1 * omt2 * t * 3.0 + p2 * omt * t2 * 3.0 + p3 * t3; + Vector2 p = point * pixel_size + origin; + polygon.push_back(ContourPoint(p, false)); + t += step; + } + } else { + ERR_FAIL_MSG(vformat("Unknown point tag at %d:%d", i, j)); + } + } + + if (polygon.size() < 3) { + continue; // Skip glyph control points. + } + + if (!orientation) { + polygon.reverse(); + } + + gl_data.contours.push_back(polygon); + } + + // Calculate bounds. + List<TPPLPoly> in_poly; + for (int i = 0; i < gl_data.contours.size(); i++) { + TPPLPoly inp; + inp.Init(gl_data.contours[i].size()); + real_t length = 0.0; + for (int j = 0; j < gl_data.contours[i].size(); j++) { + int next = (j + 1 == gl_data.contours[i].size()) ? 0 : (j + 1); + + gl_data.min_p.x = MIN(gl_data.min_p.x, gl_data.contours[i][j].point.x); + gl_data.min_p.y = MIN(gl_data.min_p.y, gl_data.contours[i][j].point.y); + gl_data.max_p.x = MAX(gl_data.max_p.x, gl_data.contours[i][j].point.x); + gl_data.max_p.y = MAX(gl_data.max_p.y, gl_data.contours[i][j].point.y); + length += (gl_data.contours[i][next].point - gl_data.contours[i][j].point).length(); + + inp.GetPoint(j) = gl_data.contours[i][j].point; + } + TPPLOrientation poly_orient = inp.GetOrientation(); + if (poly_orient == TPPL_ORIENTATION_CW) { + inp.SetHole(true); + } + in_poly.push_back(inp); + gl_data.contours_info.push_back(ContourInfo(length, poly_orient == TPPL_ORIENTATION_CCW)); + } + + TPPLPartition tpart; + + //Decompose and triangulate. + List<TPPLPoly> out_poly; + if (tpart.ConvexPartition_HM(&in_poly, &out_poly) == 0) { + ERR_FAIL_MSG("Convex decomposing failed. Make sure the font doesn't contain self-intersecting lines, as these are not supported in TextMesh."); + } + List<TPPLPoly> out_tris; + for (List<TPPLPoly>::Element *I = out_poly.front(); I; I = I->next()) { + if (tpart.Triangulate_OPT(&(I->get()), &out_tris) == 0) { + ERR_FAIL_MSG("Triangulation failed. Make sure the font doesn't contain self-intersecting lines, as these are not supported in TextMesh."); + } + } + + for (List<TPPLPoly>::Element *I = out_tris.front(); I; I = I->next()) { + TPPLPoly &tp = I->get(); + ERR_FAIL_COND(tp.GetNumPoints() != 3); // Triangles only. + + for (int i = 0; i < 3; i++) { + gl_data.triangles.push_back(Vector2(tp.GetPoint(i).x, tp.GetPoint(i).y)); + } + } +} + +void TextMesh::_create_mesh_array(Array &p_arr) const { + Ref<Font> font = _get_font_or_default(); + ERR_FAIL_COND(font.is_null()); + + if (dirty_cache) { + cache.clear(); + dirty_cache = false; + } + + // Update text buffer. + if (dirty_text) { + TS->shaped_text_clear(text_rid); + TS->shaped_text_set_direction(text_rid, text_direction); + + String text = (uppercase) ? TS->string_to_upper(xl_text, language) : xl_text; + TS->shaped_text_add_string(text_rid, text, font->get_rids(), font_size, font->get_opentype_features(), language); + for (int i = 0; i < TextServer::SPACING_MAX; i++) { + TS->shaped_text_set_spacing(text_rid, TextServer::SpacingType(i), font->get_spacing(TextServer::SpacingType(i))); + } + + Array stt; + if (st_parser == TextServer::STRUCTURED_TEXT_CUSTOM) { + GDVIRTUAL_CALL(_structured_text_parser, st_args, text, stt); + } else { + stt = TS->parse_structured_text(st_parser, st_args, text); + } + TS->shaped_text_set_bidi_override(text_rid, stt); + + dirty_text = false; + dirty_font = false; + if (horizontal_alignment == HORIZONTAL_ALIGNMENT_FILL) { + TS->shaped_text_fit_to_width(text_rid, width, TextServer::JUSTIFICATION_WORD_BOUND | TextServer::JUSTIFICATION_KASHIDA); + } + } else if (dirty_font) { + int spans = TS->shaped_get_span_count(text_rid); + for (int i = 0; i < spans; i++) { + TS->shaped_set_span_update_font(text_rid, i, font->get_rids(), font_size, font->get_opentype_features()); + } + for (int i = 0; i < TextServer::SPACING_MAX; i++) { + TS->shaped_text_set_spacing(text_rid, TextServer::SpacingType(i), font->get_spacing(TextServer::SpacingType(i))); + } + + dirty_font = false; + if (horizontal_alignment == HORIZONTAL_ALIGNMENT_FILL) { + TS->shaped_text_fit_to_width(text_rid, width, TextServer::JUSTIFICATION_WORD_BOUND | TextServer::JUSTIFICATION_KASHIDA); + } + } + + Vector2 offset; + const Glyph *glyphs = TS->shaped_text_get_glyphs(text_rid); + int gl_size = TS->shaped_text_get_glyph_count(text_rid); + float line_width = TS->shaped_text_get_width(text_rid) * pixel_size; + + switch (horizontal_alignment) { + case HORIZONTAL_ALIGNMENT_LEFT: + offset.x = 0.0; + break; + case HORIZONTAL_ALIGNMENT_FILL: + case HORIZONTAL_ALIGNMENT_CENTER: { + offset.x = -line_width / 2.0; + } break; + case HORIZONTAL_ALIGNMENT_RIGHT: { + offset.x = -line_width; + } break; + } + + bool has_depth = !Math::is_zero_approx(depth); + + // Generate glyph data, precalculate size of the arrays and mesh bounds for UV. + int64_t p_size = 0; + int64_t i_size = 0; + + Vector2 min_p = Vector2(INFINITY, INFINITY); + Vector2 max_p = Vector2(-INFINITY, -INFINITY); + + Vector2 offset_pre = offset; + for (int i = 0; i < gl_size; i++) { + if (glyphs[i].index == 0) { + offset.x += glyphs[i].advance * pixel_size * glyphs[i].repeat; + continue; + } + if (glyphs[i].font_rid != RID()) { + GlyphMeshKey key = GlyphMeshKey(glyphs[i].font_rid.get_id(), glyphs[i].index); + _generate_glyph_mesh_data(key, glyphs[i]); + GlyphMeshData &gl_data = cache[key]; + + p_size += glyphs[i].repeat * gl_data.triangles.size() * ((has_depth) ? 2 : 1); + i_size += glyphs[i].repeat * gl_data.triangles.size() * ((has_depth) ? 2 : 1); + + if (has_depth) { + for (int j = 0; j < gl_data.contours.size(); j++) { + p_size += glyphs[i].repeat * gl_data.contours[j].size() * 4; + i_size += glyphs[i].repeat * gl_data.contours[j].size() * 6; + } + } + + for (int j = 0; j < glyphs[i].repeat; j++) { + min_p.x = MIN(gl_data.min_p.x + offset_pre.x, min_p.x); + min_p.y = MIN(gl_data.min_p.y + offset_pre.y, min_p.y); + max_p.x = MAX(gl_data.max_p.x + offset_pre.x, max_p.x); + max_p.y = MAX(gl_data.max_p.y + offset_pre.y, max_p.y); + + offset_pre.x += glyphs[i].advance * pixel_size; + } + } else { + p_size += glyphs[i].repeat * 4; + i_size += glyphs[i].repeat * 6; + + offset_pre.x += glyphs[i].advance * pixel_size * glyphs[i].repeat; + } + } + + Vector<Vector3> vertices; + Vector<Vector3> normals; + Vector<float> tangents; + Vector<Vector2> uvs; + Vector<int32_t> indices; + + vertices.resize(p_size); + normals.resize(p_size); + uvs.resize(p_size); + tangents.resize(p_size * 4); + indices.resize(i_size); + + Vector3 *vertices_ptr = vertices.ptrw(); + Vector3 *normals_ptr = normals.ptrw(); + float *tangents_ptr = tangents.ptrw(); + Vector2 *uvs_ptr = uvs.ptrw(); + int32_t *indices_ptr = indices.ptrw(); + + // Generate mesh. + int32_t p_idx = 0; + int32_t i_idx = 0; + for (int i = 0; i < gl_size; i++) { + if (glyphs[i].index == 0) { + offset.x += glyphs[i].advance * pixel_size * glyphs[i].repeat; + continue; + } + if (glyphs[i].font_rid != RID()) { + GlyphMeshKey key = GlyphMeshKey(glyphs[i].font_rid.get_id(), glyphs[i].index); + _generate_glyph_mesh_data(key, glyphs[i]); + const GlyphMeshData &gl_data = cache[key]; + + int64_t ts = gl_data.triangles.size(); + const Vector2 *ts_ptr = gl_data.triangles.ptr(); + + for (int j = 0; j < glyphs[i].repeat; j++) { + for (int k = 0; k < ts; k += 3) { + // Add front face. + for (int l = 0; l < 3; l++) { + Vector3 point = Vector3(ts_ptr[k + l].x + offset.x, -ts_ptr[k + l].y + offset.y, depth / 2.0); + vertices_ptr[p_idx] = point; + normals_ptr[p_idx] = Vector3(0.0, 0.0, 1.0); + if (has_depth) { + uvs_ptr[p_idx] = Vector2(Math::range_lerp(point.x, min_p.x, max_p.x, real_t(0.0), real_t(1.0)), Math::range_lerp(point.y, -min_p.y, -max_p.y, real_t(0.0), real_t(0.4))); + } else { + uvs_ptr[p_idx] = Vector2(Math::range_lerp(point.x, min_p.x, max_p.x, real_t(0.0), real_t(1.0)), Math::range_lerp(point.y, -min_p.y, -max_p.y, real_t(0.0), real_t(1.0))); + } + tangents_ptr[p_idx * 4 + 0] = 1.0; + tangents_ptr[p_idx * 4 + 1] = 0.0; + tangents_ptr[p_idx * 4 + 2] = 0.0; + tangents_ptr[p_idx * 4 + 3] = 1.0; + indices_ptr[i_idx++] = p_idx; + p_idx++; + } + if (has_depth) { + // Add back face. + for (int l = 2; l >= 0; l--) { + Vector3 point = Vector3(ts_ptr[k + l].x + offset.x, -ts_ptr[k + l].y + offset.y, -depth / 2.0); + vertices_ptr[p_idx] = point; + normals_ptr[p_idx] = Vector3(0.0, 0.0, -1.0); + uvs_ptr[p_idx] = Vector2(Math::range_lerp(point.x, min_p.x, max_p.x, real_t(0.0), real_t(1.0)), Math::range_lerp(point.y, -min_p.y, -max_p.y, real_t(0.4), real_t(0.8))); + tangents_ptr[p_idx * 4 + 0] = -1.0; + tangents_ptr[p_idx * 4 + 1] = 0.0; + tangents_ptr[p_idx * 4 + 2] = 0.0; + tangents_ptr[p_idx * 4 + 3] = 1.0; + indices_ptr[i_idx++] = p_idx; + p_idx++; + } + } + } + // Add sides. + if (has_depth) { + for (int k = 0; k < gl_data.contours.size(); k++) { + int64_t ps = gl_data.contours[k].size(); + const ContourPoint *ps_ptr = gl_data.contours[k].ptr(); + const ContourInfo &ps_info = gl_data.contours_info[k]; + real_t length = 0.0; + for (int l = 0; l < ps; l++) { + int prev = (l == 0) ? (ps - 1) : (l - 1); + int next = (l + 1 == ps) ? 0 : (l + 1); + Vector2 d1; + Vector2 d2 = (ps_ptr[next].point - ps_ptr[l].point).normalized(); + if (ps_ptr[l].sharp) { + d1 = d2; + } else { + d1 = (ps_ptr[l].point - ps_ptr[prev].point).normalized(); + } + real_t seg_len = (ps_ptr[next].point - ps_ptr[l].point).length(); + + Vector3 quad_faces[4] = { + Vector3(ps_ptr[l].point.x + offset.x, -ps_ptr[l].point.y + offset.y, -depth / 2.0), + Vector3(ps_ptr[next].point.x + offset.x, -ps_ptr[next].point.y + offset.y, -depth / 2.0), + Vector3(ps_ptr[l].point.x + offset.x, -ps_ptr[l].point.y + offset.y, depth / 2.0), + Vector3(ps_ptr[next].point.x + offset.x, -ps_ptr[next].point.y + offset.y, depth / 2.0), + }; + for (int m = 0; m < 4; m++) { + const Vector2 &d = ((m % 2) == 0) ? d1 : d2; + real_t u_pos = ((m % 2) == 0) ? length : length + seg_len; + vertices_ptr[p_idx + m] = quad_faces[m]; + normals_ptr[p_idx + m] = Vector3(d.y, d.x, 0.0); + if (m < 2) { + uvs_ptr[p_idx + m] = Vector2(Math::range_lerp(u_pos, 0, ps_info.length, real_t(0.0), real_t(1.0)), (ps_info.ccw) ? 0.8 : 0.9); + } else { + uvs_ptr[p_idx + m] = Vector2(Math::range_lerp(u_pos, 0, ps_info.length, real_t(0.0), real_t(1.0)), (ps_info.ccw) ? 0.9 : 1.0); + } + tangents_ptr[(p_idx + m) * 4 + 0] = d.x; + tangents_ptr[(p_idx + m) * 4 + 1] = -d.y; + tangents_ptr[(p_idx + m) * 4 + 2] = 0.0; + tangents_ptr[(p_idx + m) * 4 + 3] = 1.0; + } + + indices_ptr[i_idx++] = p_idx; + indices_ptr[i_idx++] = p_idx + 1; + indices_ptr[i_idx++] = p_idx + 2; + + indices_ptr[i_idx++] = p_idx + 1; + indices_ptr[i_idx++] = p_idx + 3; + indices_ptr[i_idx++] = p_idx + 2; + + length += seg_len; + p_idx += 4; + } + } + } + offset.x += glyphs[i].advance * pixel_size; + } + } else { + // Add fallback quad for missing glyphs. + for (int j = 0; j < glyphs[i].repeat; j++) { + Size2 sz = TS->get_hex_code_box_size(glyphs[i].font_size, glyphs[i].index) * pixel_size; + Vector3 quad_faces[4] = { + Vector3(offset.x, offset.y, 0.0), + Vector3(offset.x, sz.y + offset.y, 0.0), + Vector3(sz.x + offset.x, sz.y + offset.y, 0.0), + Vector3(sz.x + offset.x, offset.y, 0.0), + }; + for (int k = 0; k < 4; k++) { + vertices_ptr[p_idx + k] = quad_faces[k]; + normals_ptr[p_idx + k] = Vector3(0.0, 0.0, 1.0); + if (has_depth) { + uvs_ptr[p_idx + k] = Vector2(Math::range_lerp(quad_faces[k].x, min_p.x, max_p.x, real_t(0.0), real_t(1.0)), Math::range_lerp(quad_faces[k].y, -min_p.y, -max_p.y, real_t(0.0), real_t(0.4))); + } else { + uvs_ptr[p_idx + k] = Vector2(Math::range_lerp(quad_faces[k].x, min_p.x, max_p.x, real_t(0.0), real_t(1.0)), Math::range_lerp(quad_faces[k].y, -min_p.y, -max_p.y, real_t(0.0), real_t(1.0))); + } + tangents_ptr[(p_idx + k) * 4 + 0] = 1.0; + tangents_ptr[(p_idx + k) * 4 + 1] = 0.0; + tangents_ptr[(p_idx + k) * 4 + 2] = 0.0; + tangents_ptr[(p_idx + k) * 4 + 3] = 1.0; + } + + indices_ptr[i_idx++] = p_idx; + indices_ptr[i_idx++] = p_idx + 1; + indices_ptr[i_idx++] = p_idx + 2; + + indices_ptr[i_idx++] = p_idx + 0; + indices_ptr[i_idx++] = p_idx + 2; + indices_ptr[i_idx++] = p_idx + 3; + p_idx += 4; + + offset.x += glyphs[i].advance * pixel_size; + } + } + } + + if (p_size == 0) { + // If empty, add single triangle to suppress errors. + vertices.push_back(Vector3()); + normals.push_back(Vector3()); + uvs.push_back(Vector2()); + tangents.push_back(1.0); + tangents.push_back(0.0); + tangents.push_back(0.0); + tangents.push_back(1.0); + indices.push_back(0); + indices.push_back(0); + indices.push_back(0); + } + + p_arr[RS::ARRAY_VERTEX] = vertices; + p_arr[RS::ARRAY_NORMAL] = normals; + p_arr[RS::ARRAY_TANGENT] = tangents; + p_arr[RS::ARRAY_TEX_UV] = uvs; + p_arr[RS::ARRAY_INDEX] = indices; +} + +void TextMesh::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_horizontal_alignment", "alignment"), &TextMesh::set_horizontal_alignment); + ClassDB::bind_method(D_METHOD("get_horizontal_alignment"), &TextMesh::get_horizontal_alignment); + + ClassDB::bind_method(D_METHOD("set_text", "text"), &TextMesh::set_text); + ClassDB::bind_method(D_METHOD("get_text"), &TextMesh::get_text); + + ClassDB::bind_method(D_METHOD("set_font", "font"), &TextMesh::set_font); + ClassDB::bind_method(D_METHOD("get_font"), &TextMesh::get_font); + + ClassDB::bind_method(D_METHOD("set_font_size", "font_size"), &TextMesh::set_font_size); + ClassDB::bind_method(D_METHOD("get_font_size"), &TextMesh::get_font_size); + + ClassDB::bind_method(D_METHOD("set_depth", "depth"), &TextMesh::set_depth); + ClassDB::bind_method(D_METHOD("get_depth"), &TextMesh::get_depth); + + ClassDB::bind_method(D_METHOD("set_width", "width"), &TextMesh::set_width); + ClassDB::bind_method(D_METHOD("get_width"), &TextMesh::get_width); + + ClassDB::bind_method(D_METHOD("set_pixel_size", "pixel_size"), &TextMesh::set_pixel_size); + ClassDB::bind_method(D_METHOD("get_pixel_size"), &TextMesh::get_pixel_size); + + ClassDB::bind_method(D_METHOD("set_curve_step", "curve_step"), &TextMesh::set_curve_step); + ClassDB::bind_method(D_METHOD("get_curve_step"), &TextMesh::get_curve_step); + + ClassDB::bind_method(D_METHOD("set_text_direction", "direction"), &TextMesh::set_text_direction); + ClassDB::bind_method(D_METHOD("get_text_direction"), &TextMesh::get_text_direction); + + ClassDB::bind_method(D_METHOD("set_language", "language"), &TextMesh::set_language); + ClassDB::bind_method(D_METHOD("get_language"), &TextMesh::get_language); + + ClassDB::bind_method(D_METHOD("set_structured_text_bidi_override", "parser"), &TextMesh::set_structured_text_bidi_override); + ClassDB::bind_method(D_METHOD("get_structured_text_bidi_override"), &TextMesh::get_structured_text_bidi_override); + + ClassDB::bind_method(D_METHOD("set_structured_text_bidi_override_options", "args"), &TextMesh::set_structured_text_bidi_override_options); + ClassDB::bind_method(D_METHOD("get_structured_text_bidi_override_options"), &TextMesh::get_structured_text_bidi_override_options); + + ClassDB::bind_method(D_METHOD("set_uppercase", "enable"), &TextMesh::set_uppercase); + ClassDB::bind_method(D_METHOD("is_uppercase"), &TextMesh::is_uppercase); + + ClassDB::bind_method(D_METHOD("_font_changed"), &TextMesh::_font_changed); + ClassDB::bind_method(D_METHOD("_request_update"), &TextMesh::_request_update); + + ADD_GROUP("Text", ""); + ADD_PROPERTY(PropertyInfo(Variant::STRING, "text"), "set_text", "get_text"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "font", PROPERTY_HINT_RESOURCE_TYPE, "Font"), "set_font", "get_font"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "font_size", PROPERTY_HINT_RANGE, "1,256,1,or_greater,suffix:px"), "set_font_size", "get_font_size"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "horizontal_alignment", PROPERTY_HINT_ENUM, "Left,Center,Right,Fill"), "set_horizontal_alignment", "get_horizontal_alignment"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "uppercase"), "set_uppercase", "is_uppercase"); + + ADD_GROUP("Mesh", ""); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "pixel_size", PROPERTY_HINT_RANGE, "0.0001,128,0.0001,suffix:m"), "set_pixel_size", "get_pixel_size"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "curve_step", PROPERTY_HINT_RANGE, "0.1,10,0.1,suffix:px"), "set_curve_step", "get_curve_step"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "depth", PROPERTY_HINT_RANGE, "0.0,100.0,0.001,or_greater,suffix:m"), "set_depth", "get_depth"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "width", PROPERTY_HINT_NONE, "suffix:m"), "set_width", "get_width"); + + ADD_GROUP("BiDi", ""); + ADD_PROPERTY(PropertyInfo(Variant::INT, "text_direction", PROPERTY_HINT_ENUM, "Auto,Left-to-Right,Right-to-Left"), "set_text_direction", "get_text_direction"); + ADD_PROPERTY(PropertyInfo(Variant::STRING, "language", PROPERTY_HINT_LOCALE_ID, ""), "set_language", "get_language"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "structured_text_bidi_override", PROPERTY_HINT_ENUM, "Default,URI,File,Email,List,None,Custom"), "set_structured_text_bidi_override", "get_structured_text_bidi_override"); + ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "structured_text_bidi_override_options"), "set_structured_text_bidi_override_options", "get_structured_text_bidi_override_options"); +} + +void TextMesh::_notification(int p_what) { + switch (p_what) { + case MainLoop::NOTIFICATION_TRANSLATION_CHANGED: { + String new_text = tr(text); + if (new_text == xl_text) { + return; // Nothing new. + } + xl_text = new_text; + dirty_text = true; + _request_update(); + } break; + } +} + +TextMesh::TextMesh() { + primitive_type = PRIMITIVE_TRIANGLES; + text_rid = TS->create_shaped_text(); +} + +TextMesh::~TextMesh() { + TS->free_rid(text_rid); +} + +void TextMesh::set_horizontal_alignment(HorizontalAlignment p_alignment) { + ERR_FAIL_INDEX((int)p_alignment, 4); + if (horizontal_alignment != p_alignment) { + if (horizontal_alignment == HORIZONTAL_ALIGNMENT_FILL || p_alignment == HORIZONTAL_ALIGNMENT_FILL) { + dirty_text = true; + } + horizontal_alignment = p_alignment; + _request_update(); + } +} + +HorizontalAlignment TextMesh::get_horizontal_alignment() const { + return horizontal_alignment; +} + +void TextMesh::set_text(const String &p_string) { + if (text != p_string) { + text = p_string; + xl_text = tr(text); + dirty_text = true; + _request_update(); + } +} + +String TextMesh::get_text() const { + return text; +} + +void TextMesh::_font_changed() { + dirty_font = true; + dirty_cache = true; + call_deferred(SNAME("_request_update")); +} + +void TextMesh::set_font(const Ref<Font> &p_font) { + if (font_override != p_font) { + if (font_override.is_valid()) { + font_override->disconnect(CoreStringNames::get_singleton()->changed, Callable(this, "_font_changed")); + } + font_override = p_font; + dirty_font = true; + dirty_cache = true; + if (font_override.is_valid()) { + font_override->connect(CoreStringNames::get_singleton()->changed, Callable(this, "_font_changed")); + } + _request_update(); + } +} + +Ref<Font> TextMesh::get_font() const { + return font_override; +} + +Ref<Font> TextMesh::_get_font_or_default() const { + if (font_override.is_valid()) { + return font_override; + } + + // Check the project-defined Theme resource. + if (Theme::get_project_default().is_valid()) { + List<StringName> theme_types; + Theme::get_project_default()->get_type_dependencies(get_class_name(), StringName(), &theme_types); + + for (const StringName &E : theme_types) { + if (Theme::get_project_default()->has_theme_item(Theme::DATA_TYPE_FONT, "font", E)) { + return Theme::get_project_default()->get_theme_item(Theme::DATA_TYPE_FONT, "font", E); + } + } + } + + // Lastly, fall back on the items defined in the default Theme, if they exist. + { + List<StringName> theme_types; + Theme::get_default()->get_type_dependencies(get_class_name(), StringName(), &theme_types); + + for (const StringName &E : theme_types) { + if (Theme::get_default()->has_theme_item(Theme::DATA_TYPE_FONT, "font", E)) { + return Theme::get_default()->get_theme_item(Theme::DATA_TYPE_FONT, "font", E); + } + } + } + + // If they don't exist, use any type to return the default/empty value. + return Theme::get_default()->get_theme_item(Theme::DATA_TYPE_FONT, "font", StringName()); +} + +void TextMesh::set_font_size(int p_size) { + if (font_size != p_size) { + font_size = CLAMP(p_size, 1, 127); + dirty_font = true; + dirty_cache = true; + _request_update(); + } +} + +int TextMesh::get_font_size() const { + return font_size; +} + +void TextMesh::set_depth(real_t p_depth) { + if (depth != p_depth) { + depth = MAX(p_depth, 0.0); + _request_update(); + } +} + +real_t TextMesh::get_depth() const { + return depth; +} + +void TextMesh::set_width(real_t p_width) { + if (width != p_width) { + width = p_width; + if (horizontal_alignment == HORIZONTAL_ALIGNMENT_FILL) { + dirty_text = true; + } + _request_update(); + } +} + +real_t TextMesh::get_width() const { + return width; +} + +void TextMesh::set_pixel_size(real_t p_amount) { + if (pixel_size != p_amount) { + pixel_size = CLAMP(p_amount, 0.0001, 128.0); + dirty_cache = true; + _request_update(); + } +} + +real_t TextMesh::get_pixel_size() const { + return pixel_size; +} + +void TextMesh::set_curve_step(real_t p_step) { + if (curve_step != p_step) { + curve_step = CLAMP(p_step, 0.1, 10.0); + dirty_cache = true; + _request_update(); + } +} + +real_t TextMesh::get_curve_step() const { + return curve_step; +} + +void TextMesh::set_text_direction(TextServer::Direction p_text_direction) { + ERR_FAIL_COND((int)p_text_direction < -1 || (int)p_text_direction > 3); + if (text_direction != p_text_direction) { + text_direction = p_text_direction; + dirty_text = true; + _request_update(); + } +} + +TextServer::Direction TextMesh::get_text_direction() const { + return text_direction; +} + +void TextMesh::set_language(const String &p_language) { + if (language != p_language) { + language = p_language; + dirty_text = true; + _request_update(); + } +} + +String TextMesh::get_language() const { + return language; +} + +void TextMesh::set_structured_text_bidi_override(TextServer::StructuredTextParser p_parser) { + if (st_parser != p_parser) { + st_parser = p_parser; + dirty_text = true; + _request_update(); + } +} + +TextServer::StructuredTextParser TextMesh::get_structured_text_bidi_override() const { + return st_parser; +} + +void TextMesh::set_structured_text_bidi_override_options(Array p_args) { + if (st_args != p_args) { + st_args = p_args; + dirty_text = true; + _request_update(); + } +} + +Array TextMesh::get_structured_text_bidi_override_options() const { + return st_args; +} + +void TextMesh::set_uppercase(bool p_uppercase) { + if (uppercase != p_uppercase) { + uppercase = p_uppercase; + dirty_text = true; + _request_update(); + } +} + +bool TextMesh::is_uppercase() const { + return uppercase; +} diff --git a/scene/resources/primitive_meshes.h b/scene/resources/primitive_meshes.h index 8cd05c1740..cb93211756 100644 --- a/scene/resources/primitive_meshes.h +++ b/scene/resources/primitive_meshes.h @@ -31,7 +31,9 @@ #ifndef PRIMITIVE_MESHES_H #define PRIMITIVE_MESHES_H +#include "scene/resources/font.h" #include "scene/resources/mesh.h" +#include "servers/text_server.h" ///@TODO probably should change a few integers to unsigned integers... @@ -181,13 +183,15 @@ private: float height = 2.0; int radial_segments = 64; int rings = 4; + bool cap_top = true; + bool cap_bottom = true; protected: static void _bind_methods(); virtual void _create_mesh_array(Array &p_arr) const override; public: - static void create_mesh_array(Array &p_arr, float top_radius, float bottom_radius, float height, int radial_segments = 64, int rings = 4); + static void create_mesh_array(Array &p_arr, float top_radius, float bottom_radius, float height, int radial_segments = 64, int rings = 4, bool cap_top = true, bool cap_bottom = true); void set_top_radius(const float p_radius); float get_top_radius() const; @@ -204,6 +208,12 @@ public: void set_rings(const int p_rings); int get_rings() const; + void set_cap_top(bool p_cap_top); + bool is_cap_top() const; + + void set_cap_bottom(bool p_cap_bottom); + bool is_cap_bottom() const; + CylinderMesh(); }; @@ -247,7 +257,7 @@ class PrismMesh : public PrimitiveMesh { private: float left_to_right = 0.5; - Vector3 size = Vector3(2.0, 2.0, 2.0); + Vector3 size = Vector3(1.0, 1.0, 1.0); int subdivide_w = 0; int subdivide_h = 0; int subdivide_d = 0; @@ -309,8 +319,8 @@ class SphereMesh : public PrimitiveMesh { GDCLASS(SphereMesh, PrimitiveMesh); private: - float radius = 1.0; - float height = 2.0; + float radius = 0.5; + float height = 1.0; int radial_segments = 64; int rings = 32; bool is_hemisphere = false; @@ -358,7 +368,7 @@ class TubeTrailMesh : public PrimitiveMesh { GDCLASS(TubeTrailMesh, PrimitiveMesh); private: - float radius = 1.0; + float radius = 0.5; int radial_steps = 8; int sections = 5; float section_length = 0.2; @@ -447,5 +457,142 @@ public: RibbonTrailMesh(); }; +/** + Text... +*/ + +class TextMesh : public PrimitiveMesh { + GDCLASS(TextMesh, PrimitiveMesh); + +private: + struct ContourPoint { + Vector2 point; + bool sharp = false; + + ContourPoint(){}; + ContourPoint(const Vector2 &p_pt, bool p_sharp) { + point = p_pt; + sharp = p_sharp; + }; + }; + + struct ContourInfo { + real_t length = 0.0; + bool ccw = true; + ContourInfo(){}; + ContourInfo(real_t p_len, bool p_ccw) { + length = p_len; + ccw = p_ccw; + } + }; + + struct GlyphMeshKey { + uint64_t font_id; + uint32_t gl_id; + + bool operator==(const GlyphMeshKey &p_b) const { + return (font_id == p_b.font_id) && (gl_id == p_b.gl_id); + } + + GlyphMeshKey(uint64_t p_font_id, uint32_t p_gl_id) { + font_id = p_font_id; + gl_id = p_gl_id; + } + }; + + struct GlyphMeshKeyHasher { + _FORCE_INLINE_ static uint32_t hash(const GlyphMeshKey &p_a) { + return hash_murmur3_buffer(&p_a, sizeof(GlyphMeshKey)); + } + }; + + struct GlyphMeshData { + Vector<Vector2> triangles; + Vector<Vector<ContourPoint>> contours; + Vector<ContourInfo> contours_info; + Vector2 min_p = Vector2(INFINITY, INFINITY); + Vector2 max_p = Vector2(-INFINITY, -INFINITY); + }; + mutable HashMap<GlyphMeshKey, GlyphMeshData, GlyphMeshKeyHasher> cache; + + RID text_rid; + String text; + String xl_text; + + int font_size = 16; + Ref<Font> font_override; + float width = 500.0; + + HorizontalAlignment horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER; + bool uppercase = false; + String language; + TextServer::Direction text_direction = TextServer::DIRECTION_AUTO; + TextServer::StructuredTextParser st_parser = TextServer::STRUCTURED_TEXT_DEFAULT; + Array st_args; + + real_t depth = 0.05; + real_t pixel_size = 0.01; + real_t curve_step = 0.5; + + mutable bool dirty_text = true; + mutable bool dirty_font = true; + mutable bool dirty_cache = true; + + void _generate_glyph_mesh_data(const GlyphMeshKey &p_key, const Glyph &p_glyph) const; + void _font_changed(); + +protected: + static void _bind_methods(); + void _notification(int p_what); + + virtual void _create_mesh_array(Array &p_arr) const override; + +public: + GDVIRTUAL2RC(Array, _structured_text_parser, Array, String) + + TextMesh(); + ~TextMesh(); + + void set_horizontal_alignment(HorizontalAlignment p_alignment); + HorizontalAlignment get_horizontal_alignment() const; + + void set_text(const String &p_string); + String get_text() const; + + void set_font(const Ref<Font> &p_font); + Ref<Font> get_font() const; + Ref<Font> _get_font_or_default() const; + + void set_font_size(int p_size); + int get_font_size() const; + + void set_text_direction(TextServer::Direction p_text_direction); + TextServer::Direction get_text_direction() const; + + void set_language(const String &p_language); + String get_language() const; + + void set_structured_text_bidi_override(TextServer::StructuredTextParser p_parser); + TextServer::StructuredTextParser get_structured_text_bidi_override() const; + + void set_structured_text_bidi_override_options(Array p_args); + Array get_structured_text_bidi_override_options() const; + + void set_uppercase(bool p_uppercase); + bool is_uppercase() const; + + void set_width(real_t p_width); + real_t get_width() const; + + void set_depth(real_t p_depth); + real_t get_depth() const; + + void set_curve_step(real_t p_step); + real_t get_curve_step() const; + + void set_pixel_size(real_t p_amount); + real_t get_pixel_size() const; +}; + VARIANT_ENUM_CAST(RibbonTrailMesh::Shape) #endif diff --git a/scene/resources/rectangle_shape_2d.cpp b/scene/resources/rectangle_shape_2d.cpp index 5e88c9974c..a64b262cb4 100644 --- a/scene/resources/rectangle_shape_2d.cpp +++ b/scene/resources/rectangle_shape_2d.cpp @@ -101,7 +101,7 @@ void RectangleShape2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_size", "size"), &RectangleShape2D::set_size); ClassDB::bind_method(D_METHOD("get_size"), &RectangleShape2D::get_size); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "size"), "set_size", "get_size"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "size", PROPERTY_HINT_NONE, "suffix:px"), "set_size", "get_size"); } RectangleShape2D::RectangleShape2D() : diff --git a/scene/resources/resource_format_text.cpp b/scene/resources/resource_format_text.cpp index 4b0456681b..66afb001fb 100644 --- a/scene/resources/resource_format_text.cpp +++ b/scene/resources/resource_format_text.cpp @@ -32,6 +32,7 @@ #include "core/config/project_settings.h" #include "core/io/dir_access.h" +#include "core/io/missing_resource.h" #include "core/io/resource_format_binary.h" #include "core/version.h" @@ -39,6 +40,8 @@ // Version 3: new string ID for ext/subresources, breaks forward compat. #define FORMAT_VERSION 3 +#define BINARY_FORMAT_VERSION 4 + #include "core/io/dir_access.h" #include "core/version.h" @@ -65,12 +68,8 @@ Error ResourceLoaderText::_parse_sub_resource_dummy(DummyReadData *p_data, Varia String unique_id = token.value; if (!p_data->resource_map.has(unique_id)) { - Ref<DummyResource> dr; - dr.instantiate(); - dr->set_scene_unique_id(unique_id); - p_data->resource_map[unique_id] = dr; - uint32_t im_size = p_data->resource_index_map.size(); - p_data->resource_index_map.insert(dr, im_size); + r_err_str = "Found unique_id reference before mapping, sub-resources stored out of order in resource file"; + return ERR_PARSE_ERROR; } r_res = p_data->resource_map[unique_id]; @@ -150,7 +149,7 @@ Error ResourceLoaderText::_parse_ext_resource(VariantParser::Stream *p_stream, R if (ext_resources[id].cache.is_valid()) { r_res = ext_resources[id].cache; } else if (use_sub_threads) { - RES res = ResourceLoader::load_threaded_get(path); + Ref<Resource> res = ResourceLoader::load_threaded_get(path); if (res.is_null()) { if (ResourceLoader::get_abort_on_missing_resources()) { error = ERR_FILE_MISSING_DEPENDENCIES; @@ -171,7 +170,7 @@ Error ResourceLoaderText::_parse_ext_resource(VariantParser::Stream *p_stream, R return error; } } else { - r_res = RES(); + r_res = Ref<Resource>(); } VariantParser::get_token(p_stream, token, line, r_err_str); @@ -213,6 +212,15 @@ Ref<PackedScene> ResourceLoaderText::_parse_node_tag(VariantParser::ResourcePars type = SceneState::TYPE_INSTANCED; //no type? assume this was instantiated } + HashSet<StringName> path_properties; + + if (next_tag.fields.has("node_paths")) { + Vector<String> paths = next_tag.fields["node_paths"]; + for (int i = 0; i < paths.size(); i++) { + path_properties.insert(paths[i]); + } + } + if (next_tag.fields.has("instance")) { instance = packed_scene->get_state()->add_value(next_tag.fields["instance"]); @@ -277,9 +285,10 @@ Ref<PackedScene> ResourceLoaderText::_parse_node_tag(VariantParser::ResourcePars } if (!assign.is_empty()) { - int nameidx = packed_scene->get_state()->add_name(assign); + StringName assign_name = assign; + int nameidx = packed_scene->get_state()->add_name(assign_name); int valueidx = packed_scene->get_state()->add_value(value); - packed_scene->get_state()->add_node_property(node_id, nameidx, valueidx); + packed_scene->get_state()->add_node_property(node_id, nameidx, valueidx, path_properties.has(assign_name)); //it's assignment } else if (!next_tag.name.is_empty()) { break; @@ -460,7 +469,7 @@ Error ResourceLoaderText::load() { } } else { - RES res = ResourceLoader::load(path, type); + Ref<Resource> res = ResourceLoader::load(path, type); if (res.is_null()) { if (ResourceLoader::get_abort_on_missing_resources()) { @@ -527,28 +536,37 @@ Error ResourceLoaderText::load() { if (cache_mode == ResourceFormatLoader::CACHE_MODE_REPLACE && ResourceCache::has(path)) { //reuse existing - Resource *r = ResourceCache::get(path); - if (r && r->get_class() == type) { - res = Ref<Resource>(r); + Ref<Resource> cache = ResourceCache::get_ref(path); + if (cache.is_valid() && cache->get_class() == type) { + res = cache; res->reset_state(); do_assign = true; } } + MissingResource *missing_resource = nullptr; + if (res.is_null()) { //not reuse - if (cache_mode != ResourceFormatLoader::CACHE_MODE_IGNORE && ResourceCache::has(path)) { //only if it doesn't exist + Ref<Resource> cache = ResourceCache::get_ref(path); + if (cache_mode != ResourceFormatLoader::CACHE_MODE_IGNORE && cache.is_valid()) { //only if it doesn't exist //cached, do not assign - Resource *r = ResourceCache::get(path); - res = Ref<Resource>(r); + res = cache; } else { //create Object *obj = ClassDB::instantiate(type); if (!obj) { - error_text += "Can't create sub resource of type: " + type; - _printerr(); - error = ERR_FILE_CORRUPT; - return error; + if (ResourceLoader::is_creating_missing_resources_if_class_unavailable_enabled()) { + missing_resource = memnew(MissingResource); + missing_resource->set_original_class(type); + missing_resource->set_recording_properties(true); + obj = missing_resource; + } else { + error_text += "Can't create sub resource of type: " + type; + _printerr(); + error = ERR_FILE_CORRUPT; + return error; + } } Resource *r = Object::cast_to<Resource>(obj); @@ -572,6 +590,8 @@ Error ResourceLoaderText::load() { res->set_scene_unique_id(id); } + Dictionary missing_resource_properties; + while (true) { String assign; Variant value; @@ -585,7 +605,23 @@ Error ResourceLoaderText::load() { if (!assign.is_empty()) { if (do_assign) { - res->set(assign, value); + bool set_valid = true; + + if (value.get_type() == Variant::OBJECT && missing_resource != nullptr) { + // If the property being set is a missing resource (and the parent is not), + // then setting it will most likely not work. + // Instead, save it as metadata. + + Ref<MissingResource> mr = value; + if (mr.is_valid()) { + missing_resource_properties[assign] = mr; + set_valid = false; + } + } + + if (set_valid) { + res->set(assign, value); + } } //it's assignment } else if (!next_tag.name.is_empty()) { @@ -599,6 +635,14 @@ Error ResourceLoaderText::load() { } } + if (missing_resource) { + missing_resource->set_recording_properties(false); + } + + if (!missing_resource_properties.is_empty()) { + res->set_meta(META_MISSING_RESOURCES, missing_resource_properties); + } + if (progress && resources_total > 0) { *progress = resource_current / float(resources_total); } @@ -616,21 +660,28 @@ Error ResourceLoaderText::load() { return error; } - if (cache_mode == ResourceFormatLoader::CACHE_MODE_REPLACE && ResourceCache::has(local_path)) { - Resource *r = ResourceCache::get(local_path); - if (r->get_class() == res_type) { - r->reset_state(); - resource = Ref<Resource>(r); - } + Ref<Resource> cache = ResourceCache::get_ref(local_path); + if (cache_mode == ResourceFormatLoader::CACHE_MODE_REPLACE && cache.is_valid() && cache->get_class() == res_type) { + cache->reset_state(); + resource = cache; } + MissingResource *missing_resource = nullptr; + if (!resource.is_valid()) { Object *obj = ClassDB::instantiate(res_type); if (!obj) { - error_text += "Can't create sub resource of type: " + res_type; - _printerr(); - error = ERR_FILE_CORRUPT; - return error; + if (ResourceLoader::is_creating_missing_resources_if_class_unavailable_enabled()) { + missing_resource = memnew(MissingResource); + missing_resource->set_original_class(res_type); + missing_resource->set_recording_properties(true); + obj = missing_resource; + } else { + error_text += "Can't create sub resource of type: " + res_type; + _printerr(); + error = ERR_FILE_CORRUPT; + return error; + } } Resource *r = Object::cast_to<Resource>(obj); @@ -646,6 +697,8 @@ Error ResourceLoaderText::load() { resource_current++; + Dictionary missing_resource_properties; + while (true) { String assign; Variant value; @@ -668,7 +721,23 @@ Error ResourceLoaderText::load() { } if (!assign.is_empty()) { - resource->set(assign, value); + bool set_valid = true; + + if (value.get_type() == Variant::OBJECT && missing_resource != nullptr) { + // If the property being set is a missing resource (and the parent is not), + // then setting it will most likely not work. + // Instead, save it as metadata. + + Ref<MissingResource> mr = value; + if (mr.is_valid()) { + missing_resource_properties[assign] = mr; + set_valid = false; + } + } + + if (set_valid) { + resource->set(assign, value); + } //it's assignment } else if (!next_tag.name.is_empty()) { error = ERR_FILE_CORRUPT; @@ -676,14 +745,24 @@ Error ResourceLoaderText::load() { _printerr(); return error; } else { - error = OK; - if (progress && resources_total > 0) { - *progress = resource_current / float(resources_total); - } - - return error; + break; } } + + if (missing_resource) { + missing_resource->set_recording_properties(false); + } + + if (!missing_resource_properties.is_empty()) { + resource->set_meta(META_MISSING_RESOURCES, missing_resource_properties); + } + + error = OK; + if (progress && resources_total > 0) { + *progress = resource_current / float(resources_total); + } + + return error; } //for scene files @@ -794,7 +873,7 @@ void ResourceLoaderText::get_dependencies(Ref<FileAccess> p_f, List<String> *p_d } } -Error ResourceLoaderText::rename_dependencies(Ref<FileAccess> p_f, const String &p_path, const Map<String, String> &p_map) { +Error ResourceLoaderText::rename_dependencies(Ref<FileAccess> p_f, const String &p_path, const HashMap<String, String> &p_map) { open(p_f, true); ERR_FAIL_COND_V(error != OK, error); ignore_resource_parsing = true; @@ -890,7 +969,6 @@ Error ResourceLoaderText::rename_dependencies(Ref<FileAccess> p_f, const String fw->store_8(c); c = f->get_8(); } - f.unref(); bool all_ok = fw->get_error() == OK; @@ -898,10 +976,6 @@ Error ResourceLoaderText::rename_dependencies(Ref<FileAccess> p_f, const String return ERR_CANT_CREATE; } - Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_RESOURCES); - da->remove(p_path); - da->rename(p_path + ".depren", p_path); - return OK; } @@ -1010,7 +1084,7 @@ Error ResourceLoaderText::save_as_binary(Ref<FileAccess> p_f, const String &p_pa wf->store_32(0); //64 bits file, false for now wf->store_32(VERSION_MAJOR); wf->store_32(VERSION_MINOR); - static const int save_format_version = 3; //use format version 3 for saving + static const int save_format_version = BINARY_FORMAT_VERSION; wf->store_32(save_format_version); bs_save_unicode_string(wf, is_scene ? "PackedScene" : resource_type); @@ -1108,7 +1182,7 @@ Error ResourceLoaderText::save_as_binary(Ref<FileAccess> p_f, const String &p_pa while (next_tag.name == "sub_resource" || next_tag.name == "resource") { String type; - int id = -1; + String id; bool main_res; if (next_tag.name == "sub_resource") { @@ -1129,15 +1203,26 @@ Error ResourceLoaderText::save_as_binary(Ref<FileAccess> p_f, const String &p_pa type = next_tag.fields["type"]; id = next_tag.fields["id"]; main_res = false; + + if (!dummy_read.resource_map.has(id)) { + Ref<DummyResource> dr; + dr.instantiate(); + dr->set_scene_unique_id(id); + dummy_read.resource_map[id] = dr; + uint32_t im_size = dummy_read.resource_index_map.size(); + dummy_read.resource_index_map.insert(dr, im_size); + } + } else { type = res_type; - id = 0; //used for last anyway + String uid_text = ResourceUID::get_singleton()->id_to_text(res_uid); + id = type + "_" + uid_text.replace("uid://", "").replace("<invalid>", "0"); main_res = true; } local_offsets.push_back(wf2->get_position()); - bs_save_unicode_string(wf, "local://" + itos(id)); + bs_save_unicode_string(wf, "local://" + id); local_pointers_pos.push_back(wf->get_position()); wf->store_64(0); //temp local offset @@ -1164,7 +1249,7 @@ Error ResourceLoaderText::save_as_binary(Ref<FileAccess> p_f, const String &p_pa } if (!assign.is_empty()) { - Map<StringName, int> empty_string_map; //unused + HashMap<StringName, int> empty_string_map; //unused bs_save_unicode_string(wf2, assign, true); ResourceFormatSaverBinaryInstance::write_variant(wf2, value, dummy_read.resource_index_map, dummy_read.external_resources, empty_string_map); prop_count++; @@ -1206,7 +1291,8 @@ Error ResourceLoaderText::save_as_binary(Ref<FileAccess> p_f, const String &p_pa List<PropertyInfo> props; packed_scene->get_property_list(&props); - bs_save_unicode_string(wf, "local://0"); + String id = "PackedScene_" + ResourceUID::get_singleton()->id_to_text(res_uid).replace("uid://", "").replace("<invalid>", "0"); + bs_save_unicode_string(wf, "local://" + id); local_pointers_pos.push_back(wf->get_position()); wf->store_64(0); //temp local offset @@ -1225,7 +1311,7 @@ Error ResourceLoaderText::save_as_binary(Ref<FileAccess> p_f, const String &p_pa String name = E.name; Variant value = packed_scene->get(name); - Map<StringName, int> empty_string_map; //unused + HashMap<StringName, int> empty_string_map; //unused bs_save_unicode_string(wf2, name, true); ResourceFormatSaverBinaryInstance::write_variant(wf2, value, dummy_read.resource_index_map, dummy_read.external_resources, empty_string_map); prop_count++; @@ -1332,7 +1418,7 @@ ResourceUID::ID ResourceLoaderText::get_uid(Ref<FileAccess> p_f) { ///////////////////// -RES ResourceFormatLoaderText::load(const String &p_path, const String &p_original_path, Error *r_error, bool p_use_sub_threads, float *r_progress, CacheMode p_cache_mode) { +Ref<Resource> ResourceFormatLoaderText::load(const String &p_path, const String &p_original_path, Error *r_error, bool p_use_sub_threads, float *r_progress, CacheMode p_cache_mode) { if (r_error) { *r_error = ERR_CANT_OPEN; } @@ -1341,7 +1427,7 @@ RES ResourceFormatLoaderText::load(const String &p_path, const String &p_origina Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::READ, &err); - ERR_FAIL_COND_V_MSG(err != OK, RES(), "Cannot open file '" + p_path + "'."); + ERR_FAIL_COND_V_MSG(err != OK, Ref<Resource>(), "Cannot open file '" + p_path + "'."); ResourceLoaderText loader; String path = !p_original_path.is_empty() ? p_original_path : p_path; @@ -1358,7 +1444,7 @@ RES ResourceFormatLoaderText::load(const String &p_path, const String &p_origina if (err == OK) { return loader.get_resource(); } else { - return RES(); + return Ref<Resource>(); } } @@ -1368,9 +1454,12 @@ void ResourceFormatLoaderText::get_recognized_extensions_for_type(const String & return; } - if (p_type == "PackedScene") { + if (ClassDB::is_parent_class("PackedScene", p_type)) { p_extensions->push_back("tscn"); - } else { + } + + // Don't allow .tres for PackedScenes. + if (p_type != "PackedScene") { p_extensions->push_back("tres"); } } @@ -1436,16 +1525,27 @@ void ResourceFormatLoaderText::get_dependencies(const String &p_path, List<Strin loader.get_dependencies(f, p_dependencies, p_add_types); } -Error ResourceFormatLoaderText::rename_dependencies(const String &p_path, const Map<String, String> &p_map) { - Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::READ); - if (f.is_null()) { - ERR_FAIL_V(ERR_CANT_OPEN); +Error ResourceFormatLoaderText::rename_dependencies(const String &p_path, const HashMap<String, String> &p_map) { + Error err = OK; + { + Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::READ); + if (f.is_null()) { + ERR_FAIL_V(ERR_CANT_OPEN); + } + + ResourceLoaderText loader; + loader.local_path = ProjectSettings::get_singleton()->localize_path(p_path); + loader.res_path = loader.local_path; + err = loader.rename_dependencies(f, p_path, p_map); } - ResourceLoaderText loader; - loader.local_path = ProjectSettings::get_singleton()->localize_path(p_path); - loader.res_path = loader.local_path; - return loader.rename_dependencies(f, p_path, p_map); + if (err == OK) { + Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_RESOURCES); + da->remove(p_path); + da->rename(p_path + ".depren", p_path); + } + + return err; } ResourceFormatLoaderText *ResourceFormatLoaderText::singleton = nullptr; @@ -1475,24 +1575,24 @@ Error ResourceFormatLoaderText::convert_file_to_binary(const String &p_src_path, /*****************************************************************************************************/ /*****************************************************************************************************/ -String ResourceFormatSaverTextInstance::_write_resources(void *ud, const RES &p_resource) { +String ResourceFormatSaverTextInstance::_write_resources(void *ud, const Ref<Resource> &p_resource) { ResourceFormatSaverTextInstance *rsi = static_cast<ResourceFormatSaverTextInstance *>(ud); return rsi->_write_resource(p_resource); } -String ResourceFormatSaverTextInstance::_write_resource(const RES &res) { +String ResourceFormatSaverTextInstance::_write_resource(const Ref<Resource> &res) { if (external_resources.has(res)) { - return "ExtResource( \"" + external_resources[res] + "\" )"; + return "ExtResource(\"" + external_resources[res] + "\")"; } else { if (internal_resources.has(res)) { - return "SubResource( \"" + internal_resources[res] + "\" )"; + return "SubResource(\"" + internal_resources[res] + "\")"; } else if (!res->is_built_in()) { if (res->get_path() == local_path) { //circular reference attempt return "null"; } //external resource String path = relative_paths ? local_path.path_to_file(res->get_path()) : res->get_path(); - return "Resource( \"" + path + "\" )"; + return "Resource(\"" + path + "\")"; } else { ERR_FAIL_V_MSG("null", "Resource was not pre cached for the resource section, bug?"); //internal resource @@ -1503,7 +1603,7 @@ String ResourceFormatSaverTextInstance::_write_resource(const RES &res) { void ResourceFormatSaverTextInstance::_find_resources(const Variant &p_variant, bool p_main) { switch (p_variant.get_type()) { case Variant::OBJECT: { - RES res = p_variant; + Ref<Resource> res = p_variant; if (res.is_null() || external_resources.has(res)) { return; @@ -1540,7 +1640,7 @@ void ResourceFormatSaverTextInstance::_find_resources(const Variant &p_variant, Variant v = res->get(I->get().name); if (pi.usage & PROPERTY_USAGE_RESOURCE_NOT_PERSISTENT) { - RES sres = v; + Ref<Resource> sres = v; if (sres.is_valid()) { NonPersistentKey npk; npk.base = res; @@ -1584,7 +1684,16 @@ void ResourceFormatSaverTextInstance::_find_resources(const Variant &p_variant, } } -Error ResourceFormatSaverTextInstance::save(const String &p_path, const RES &p_resource, uint32_t p_flags) { +static String _resource_get_class(Ref<Resource> p_resource) { + Ref<MissingResource> missing_resource = p_resource; + if (missing_resource.is_valid()) { + return missing_resource->get_original_class(); + } else { + return p_resource->get_class(); + } +} + +Error ResourceFormatSaverTextInstance::save(const String &p_path, const Ref<Resource> &p_resource, uint32_t p_flags) { if (p_path.ends_with(".tscn")) { packed_scene = p_resource; } @@ -1625,7 +1734,7 @@ Error ResourceFormatSaverTextInstance::save(const String &p_path, const RES &p_r { String title = packed_scene.is_valid() ? "[gd_scene " : "[gd_resource "; if (packed_scene.is_null()) { - title += "type=\"" + p_resource->get_class() + "\" "; + title += "type=\"" + _resource_get_class(p_resource) + "\" "; } int load_steps = saved_resources.size() + external_resources.size(); @@ -1646,8 +1755,8 @@ Error ResourceFormatSaverTextInstance::save(const String &p_path, const RES &p_r #ifdef TOOLS_ENABLED // Keep order from cached ids. - Set<String> cached_ids_found; - for (KeyValue<RES, String> &E : external_resources) { + HashSet<String> cached_ids_found; + for (KeyValue<Ref<Resource>, String> &E : external_resources) { String cached_id = E.key->get_id_for_path(local_path); if (cached_id.is_empty() || cached_ids_found.has(cached_id)) { int sep_pos = E.value.find("_"); @@ -1663,7 +1772,7 @@ Error ResourceFormatSaverTextInstance::save(const String &p_path, const RES &p_r } } // Create IDs for non cached resources. - for (KeyValue<RES, String> &E : external_resources) { + for (KeyValue<Ref<Resource>, String> &E : external_resources) { if (cached_ids_found.has(E.value)) { // Already cached, go on. continue; } @@ -1685,14 +1794,14 @@ Error ResourceFormatSaverTextInstance::save(const String &p_path, const RES &p_r #else // Make sure to start from one, as it makes format more readable. int counter = 1; - for (KeyValue<RES, String> &E : external_resources) { + for (KeyValue<Ref<Resource>, String> &E : external_resources) { E.value = itos(counter++); } #endif Vector<ResourceSort> sorted_er; - for (const KeyValue<RES, String> &E : external_resources) { + for (const KeyValue<Ref<Resource>, String> &E : external_resources) { ResourceSort rs; rs.resource = E.key; rs.id = E.value; @@ -1718,10 +1827,10 @@ Error ResourceFormatSaverTextInstance::save(const String &p_path, const RES &p_r f->store_line(String()); // Separate. } - Set<String> used_unique_ids; + HashSet<String> used_unique_ids; - for (List<RES>::Element *E = saved_resources.front(); E; E = E->next()) { - RES res = E->get(); + for (List<Ref<Resource>>::Element *E = saved_resources.front(); E; E = E->next()) { + Ref<Resource> res = E->get(); if (E->next() && res->is_built_in()) { if (!res->get_scene_unique_id().is_empty()) { if (used_unique_ids.has(res->get_scene_unique_id())) { @@ -1733,8 +1842,8 @@ 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(); + for (List<Ref<Resource>>::Element *E = saved_resources.front(); E; E = E->next()) { + Ref<Resource> res = E->get(); ERR_CONTINUE(!resource_set.has(res)); bool main = (E->next() == nullptr); @@ -1749,7 +1858,7 @@ Error ResourceFormatSaverTextInstance::save(const String &p_path, const RES &p_r if (res->get_scene_unique_id().is_empty()) { String new_id; while (true) { - new_id = res->get_class() + "_" + Resource::generate_scene_unique_id(); + new_id = _resource_get_class(res) + "_" + Resource::generate_scene_unique_id(); if (!used_unique_ids.has(new_id)) { break; @@ -1761,7 +1870,7 @@ Error ResourceFormatSaverTextInstance::save(const String &p_path, const RES &p_r } String id = res->get_scene_unique_id(); - line += "type=\"" + res->get_class() + "\" id=\"" + id; + line += "type=\"" + _resource_get_class(res) + "\" id=\"" + id; f->store_line(line + "\"]"); if (takeover_paths) { res->set_path(p_path + "::" + id, true); @@ -1773,12 +1882,17 @@ Error ResourceFormatSaverTextInstance::save(const String &p_path, const RES &p_r #endif } + Dictionary missing_resource_properties = p_resource->get_meta(META_MISSING_RESOURCES, Dictionary()); + List<PropertyInfo> property_list; res->get_property_list(&property_list); for (List<PropertyInfo>::Element *PE = property_list.front(); PE; PE = PE->next()) { if (skip_editor && PE->get().name.begins_with("__editor")) { continue; } + if (PE->get().name == META_PROPERTY_MISSING_RESOURCES) { + continue; + } if (PE->get().usage & PROPERTY_USAGE_STORAGE) { String name = PE->get().name; @@ -1793,6 +1907,15 @@ Error ResourceFormatSaverTextInstance::save(const String &p_path, const RES &p_r } else { value = res->get(name); } + + if (PE->get().type == Variant::OBJECT && missing_resource_properties.has(PE->get().name)) { + // Was this missing resource overridden? If so do not save the old value. + Ref<Resource> ures = value; + if (ures.is_null()) { + value = missing_resource_properties[PE->get().name]; + } + } + Variant default_value = ClassDB::class_get_default_property_value(res->get_class(), name); if (default_value.get_type() != Variant::NIL && bool(Variant::evaluate(Variant::OP_EQUAL, value, default_value))) { @@ -1826,6 +1949,7 @@ Error ResourceFormatSaverTextInstance::save(const String &p_path, const RES &p_r Ref<PackedScene> instance = state->get_node_instance(i); String instance_placeholder = state->get_node_instance_placeholder(i); Vector<StringName> groups = state->get_node_groups(i); + Vector<String> deferred_node_paths = state->get_node_deferred_nodepath_properties(i); String header = "[node"; header += " name=\"" + String(name).c_escape() + "\""; @@ -1842,6 +1966,10 @@ Error ResourceFormatSaverTextInstance::save(const String &p_path, const RES &p_r header += " index=\"" + itos(index) + "\""; } + if (deferred_node_paths.size()) { + header += " node_paths=" + Variant(deferred_node_paths).get_construct_string(); + } + if (groups.size()) { // Write all groups on the same line as they're part of a section header. // This improves readability while not impacting VCS friendliness too much, @@ -1935,7 +2063,7 @@ Error ResourceFormatSaverTextInstance::save(const String &p_path, const RES &p_r return OK; } -Error ResourceFormatSaverText::save(const String &p_path, const RES &p_resource, uint32_t p_flags) { +Error ResourceFormatSaverText::save(const String &p_path, const Ref<Resource> &p_resource, uint32_t p_flags) { if (p_path.ends_with(".tscn") && !Ref<PackedScene>(p_resource).is_valid()) { return ERR_FILE_UNRECOGNIZED; } @@ -1944,11 +2072,11 @@ Error ResourceFormatSaverText::save(const String &p_path, const RES &p_resource, return saver.save(p_path, p_resource, p_flags); } -bool ResourceFormatSaverText::recognize(const RES &p_resource) const { +bool ResourceFormatSaverText::recognize(const Ref<Resource> &p_resource) const { return true; // All resources recognized! } -void ResourceFormatSaverText::get_recognized_extensions(const RES &p_resource, List<String> *p_extensions) const { +void ResourceFormatSaverText::get_recognized_extensions(const Ref<Resource> &p_resource, List<String> *p_extensions) const { if (Ref<PackedScene>(p_resource).is_valid()) { p_extensions->push_back("tscn"); // Text scene. } else { diff --git a/scene/resources/resource_format_text.h b/scene/resources/resource_format_text.h index c6543e616d..5c6a937bf2 100644 --- a/scene/resources/resource_format_text.h +++ b/scene/resources/resource_format_text.h @@ -48,7 +48,7 @@ class ResourceLoaderText { VariantParser::StreamFile stream; struct ExtResource { - RES cache; + Ref<Resource> cache; String path; String type; }; @@ -58,8 +58,8 @@ class ResourceLoaderText { bool ignore_resource_parsing = false; - Map<String, ExtResource> ext_resources; - Map<String, RES> int_resources; + HashMap<String, ExtResource> ext_resources; + HashMap<String, Ref<Resource>> int_resources; int resources_total = 0; int resource_current = 0; @@ -76,7 +76,7 @@ class ResourceLoaderText { ResourceUID::ID res_uid = ResourceUID::INVALID_ID; - Map<String, String> remaps; + HashMap<String, String> remaps; static Error _parse_sub_resources(void *p_self, VariantParser::Stream *p_stream, Ref<Resource> &r_res, int &line, String &r_err_str) { return reinterpret_cast<ResourceLoaderText *>(p_self)->_parse_sub_resource(p_stream, r_res, line, r_err_str); } static Error _parse_ext_resources(void *p_self, VariantParser::Stream *p_stream, Ref<Resource> &r_res, int &line, String &r_err_str) { return reinterpret_cast<ResourceLoaderText *>(p_self)->_parse_ext_resource(p_stream, r_res, line, r_err_str); } @@ -90,10 +90,10 @@ class ResourceLoaderText { }; struct DummyReadData { - Map<RES, int> external_resources; - Map<String, RES> rev_external_resources; - Map<RES, int> resource_index_map; - Map<String, RES> resource_map; + HashMap<Ref<Resource>, int> external_resources; + HashMap<String, Ref<Resource>> rev_external_resources; + HashMap<Ref<Resource>, int> resource_index_map; + HashMap<String, Ref<Resource>> resource_map; }; static Error _parse_sub_resource_dummys(void *p_self, VariantParser::Stream *p_stream, Ref<Resource> &r_res, int &line, String &r_err_str) { return _parse_sub_resource_dummy(static_cast<DummyReadData *>(p_self), p_stream, r_res, line, r_err_str); } @@ -108,7 +108,7 @@ class ResourceLoaderText { Error error = OK; - RES resource; + Ref<Resource> resource; Ref<PackedScene> _parse_node_tag(VariantParser::ResourceParser &parser); @@ -124,7 +124,7 @@ public: String recognize(Ref<FileAccess> p_f); ResourceUID::ID get_uid(Ref<FileAccess> p_f); void get_dependencies(Ref<FileAccess> p_f, List<String> *p_dependencies, bool p_add_types); - Error rename_dependencies(Ref<FileAccess> p_f, const String &p_path, const Map<String, String> &p_map); + Error rename_dependencies(Ref<FileAccess> p_f, const String &p_path, const HashMap<String, String> &p_map); Error save_as_binary(Ref<FileAccess> p_f, const String &p_path); ResourceLoaderText(); @@ -133,14 +133,14 @@ public: class ResourceFormatLoaderText : public ResourceFormatLoader { public: static ResourceFormatLoaderText *singleton; - virtual RES load(const String &p_path, const String &p_original_path = "", Error *r_error = nullptr, bool p_use_sub_threads = false, float *r_progress = nullptr, CacheMode p_cache_mode = CACHE_MODE_REUSE); + virtual Ref<Resource> load(const String &p_path, const String &p_original_path = "", Error *r_error = nullptr, bool p_use_sub_threads = false, float *r_progress = nullptr, CacheMode p_cache_mode = CACHE_MODE_REUSE); virtual void get_recognized_extensions_for_type(const String &p_type, List<String> *p_extensions) const; virtual void get_recognized_extensions(List<String> *p_extensions) const; virtual bool handles_type(const String &p_type) const; virtual String get_resource_type(const String &p_path) const; virtual ResourceUID::ID get_resource_uid(const String &p_path) const; virtual void get_dependencies(const String &p_path, List<String> *p_dependencies, bool p_add_types = false); - virtual Error rename_dependencies(const String &p_path, const Map<String, String> &p_map); + virtual Error rename_dependencies(const String &p_path, const HashMap<String, String> &p_map); static Error convert_file_to_binary(const String &p_src_path, const String &p_dst_path); @@ -158,20 +158,20 @@ class ResourceFormatSaverTextInstance { bool skip_editor = false; struct NonPersistentKey { //for resource properties generated on the fly - RES base; + Ref<Resource> base; StringName property; bool operator<(const NonPersistentKey &p_key) const { return base == p_key.base ? property < p_key.property : base < p_key.base; } }; - Map<NonPersistentKey, RES> non_persistent_map; + RBMap<NonPersistentKey, Ref<Resource>> non_persistent_map; - Set<RES> resource_set; - List<RES> saved_resources; - Map<RES, String> external_resources; - Map<RES, String> internal_resources; + HashSet<Ref<Resource>> resource_set; + List<Ref<Resource>> saved_resources; + HashMap<Ref<Resource>, String> external_resources; + HashMap<Ref<Resource>, String> internal_resources; struct ResourceSort { - RES resource; + Ref<Resource> resource; String id; bool operator<(const ResourceSort &p_right) const { return id.naturalnocasecmp_to(p_right.id) < 0; @@ -180,19 +180,19 @@ class ResourceFormatSaverTextInstance { void _find_resources(const Variant &p_variant, bool p_main = false); - static String _write_resources(void *ud, const RES &p_resource); - String _write_resource(const RES &res); + static String _write_resources(void *ud, const Ref<Resource> &p_resource); + String _write_resource(const Ref<Resource> &res); public: - Error save(const String &p_path, const RES &p_resource, uint32_t p_flags = 0); + Error save(const String &p_path, const Ref<Resource> &p_resource, uint32_t p_flags = 0); }; class ResourceFormatSaverText : public ResourceFormatSaver { public: static ResourceFormatSaverText *singleton; - virtual Error save(const String &p_path, const RES &p_resource, uint32_t p_flags = 0); - virtual bool recognize(const RES &p_resource) const; - virtual void get_recognized_extensions(const RES &p_resource, List<String> *p_extensions) const; + virtual Error save(const String &p_path, const Ref<Resource> &p_resource, uint32_t p_flags = 0); + virtual bool recognize(const Ref<Resource> &p_resource) const; + virtual void get_recognized_extensions(const Ref<Resource> &p_resource, List<String> *p_extensions) const; ResourceFormatSaverText(); }; diff --git a/scene/resources/scene_replication_config.cpp b/scene/resources/scene_replication_config.cpp index 2acc0f1922..6789f9f7d5 100644 --- a/scene/resources/scene_replication_config.cpp +++ b/scene/resources/scene_replication_config.cpp @@ -52,11 +52,19 @@ bool SceneReplicationConfig::_set(const StringName &p_name, const Variant &p_val ReplicationProperty &prop = properties[idx]; if (what == "sync") { prop.sync = p_value; - sync_props.push_back(prop.name); + if (prop.sync) { + sync_props.push_back(prop.name); + } else { + sync_props.erase(prop.name); + } return true; } else if (what == "spawn") { prop.spawn = p_value; - spawn_props.push_back(prop.name); + if (prop.spawn) { + spawn_props.push_back(prop.name); + } else { + spawn_props.erase(prop.name); + } return true; } } @@ -124,6 +132,15 @@ void SceneReplicationConfig::remove_property(const NodePath &p_path) { properties.erase(p_path); } +bool SceneReplicationConfig::has_property(const NodePath &p_path) const { + for (int i = 0; i < properties.size(); i++) { + if (properties[i].name == p_path) { + return true; + } + } + return false; +} + int SceneReplicationConfig::property_get_index(const NodePath &p_path) const { for (int i = 0; i < properties.size(); i++) { if (properties[i].name == p_path) { @@ -178,6 +195,7 @@ void SceneReplicationConfig::property_set_sync(const NodePath &p_path, bool p_en void SceneReplicationConfig::_bind_methods() { ClassDB::bind_method(D_METHOD("get_properties"), &SceneReplicationConfig::get_properties); ClassDB::bind_method(D_METHOD("add_property", "path", "index"), &SceneReplicationConfig::add_property, DEFVAL(-1)); + ClassDB::bind_method(D_METHOD("has_property", "path"), &SceneReplicationConfig::has_property); ClassDB::bind_method(D_METHOD("remove_property", "path"), &SceneReplicationConfig::remove_property); ClassDB::bind_method(D_METHOD("property_get_index", "path"), &SceneReplicationConfig::property_get_index); ClassDB::bind_method(D_METHOD("property_get_spawn", "path"), &SceneReplicationConfig::property_get_spawn); diff --git a/scene/resources/scene_replication_config.h b/scene/resources/scene_replication_config.h index b791be9414..ab3658d2a7 100644 --- a/scene/resources/scene_replication_config.h +++ b/scene/resources/scene_replication_config.h @@ -73,6 +73,7 @@ public: void add_property(const NodePath &p_path, int p_index = -1); void remove_property(const NodePath &p_path); + bool has_property(const NodePath &p_path) const; int property_get_index(const NodePath &p_path) const; bool property_get_spawn(const NodePath &p_path); diff --git a/scene/resources/segment_shape_2d.cpp b/scene/resources/segment_shape_2d.cpp index cea8ca1b29..d53c777492 100644 --- a/scene/resources/segment_shape_2d.cpp +++ b/scene/resources/segment_shape_2d.cpp @@ -88,8 +88,8 @@ void SegmentShape2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_b", "b"), &SegmentShape2D::set_b); ClassDB::bind_method(D_METHOD("get_b"), &SegmentShape2D::get_b); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "a"), "set_a", "get_a"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "b"), "set_b", "get_b"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "a", PROPERTY_HINT_NONE, "suffix:px"), "set_a", "get_a"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "b", PROPERTY_HINT_NONE, "suffix:px"), "set_b", "get_b"); } SegmentShape2D::SegmentShape2D() : diff --git a/scene/resources/separation_ray_shape_2d.cpp b/scene/resources/separation_ray_shape_2d.cpp index df7b0d969a..0d6aee47d8 100644 --- a/scene/resources/separation_ray_shape_2d.cpp +++ b/scene/resources/separation_ray_shape_2d.cpp @@ -89,7 +89,7 @@ void SeparationRayShape2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_slide_on_slope", "active"), &SeparationRayShape2D::set_slide_on_slope); ClassDB::bind_method(D_METHOD("get_slide_on_slope"), &SeparationRayShape2D::get_slide_on_slope); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "length", PROPERTY_HINT_RANGE, "0.01,1024,0.01,or_greater"), "set_length", "get_length"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "length", PROPERTY_HINT_RANGE, "0.01,1024,0.01,or_greater,suffix:px"), "set_length", "get_length"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "slide_on_slope"), "set_slide_on_slope", "get_slide_on_slope"); } diff --git a/scene/resources/separation_ray_shape_3d.cpp b/scene/resources/separation_ray_shape_3d.cpp index 736cb60c1c..7306d5b985 100644 --- a/scene/resources/separation_ray_shape_3d.cpp +++ b/scene/resources/separation_ray_shape_3d.cpp @@ -80,7 +80,7 @@ void SeparationRayShape3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_slide_on_slope", "active"), &SeparationRayShape3D::set_slide_on_slope); ClassDB::bind_method(D_METHOD("get_slide_on_slope"), &SeparationRayShape3D::get_slide_on_slope); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "length", PROPERTY_HINT_RANGE, "0.001,100,0.001,or_greater"), "set_length", "get_length"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "length", PROPERTY_HINT_RANGE, "0.001,100,0.001,or_greater,suffix:m"), "set_length", "get_length"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "slide_on_slope"), "set_slide_on_slope", "get_slide_on_slope"); } diff --git a/scene/resources/shader.cpp b/scene/resources/shader.cpp index 25a9278e66..d49157b1b8 100644 --- a/scene/resources/shader.cpp +++ b/scene/resources/shader.cpp @@ -100,7 +100,7 @@ RID Shader::get_rid() const { void Shader::set_default_texture_param(const StringName &p_param, const Ref<Texture2D> &p_texture, int p_index) { if (p_texture.is_valid()) { if (!default_textures.has(p_param)) { - default_textures[p_param] = Map<int, Ref<Texture2D>>(); + default_textures[p_param] = HashMap<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); @@ -126,7 +126,7 @@ Ref<Texture2D> Shader::get_default_texture_param(const StringName &p_param, int } void Shader::get_default_texture_param_list(List<StringName> *r_textures) const { - for (const KeyValue<StringName, Map<int, Ref<Texture2D>>> &E : default_textures) { + for (const KeyValue<StringName, HashMap<int, Ref<Texture2D>>> &E : default_textures) { r_textures->push_back(E.key); } } @@ -172,7 +172,7 @@ Shader::~Shader() { //////////// -RES ResourceFormatLoaderShader::load(const String &p_path, const String &p_original_path, Error *r_error, bool p_use_sub_threads, float *r_progress, CacheMode p_cache_mode) { +Ref<Resource> ResourceFormatLoaderShader::load(const String &p_path, const String &p_original_path, Error *r_error, bool p_use_sub_threads, float *r_progress, CacheMode p_cache_mode) { if (r_error) { *r_error = ERR_FILE_CANT_OPEN; } @@ -210,7 +210,7 @@ String ResourceFormatLoaderShader::get_resource_type(const String &p_path) const return ""; } -Error ResourceFormatSaverShader::save(const String &p_path, const RES &p_resource, uint32_t p_flags) { +Error ResourceFormatSaverShader::save(const String &p_path, const Ref<Resource> &p_resource, uint32_t p_flags) { Ref<Shader> shader = p_resource; ERR_FAIL_COND_V(shader.is_null(), ERR_INVALID_PARAMETER); @@ -229,7 +229,7 @@ Error ResourceFormatSaverShader::save(const String &p_path, const RES &p_resourc return OK; } -void ResourceFormatSaverShader::get_recognized_extensions(const RES &p_resource, List<String> *p_extensions) const { +void ResourceFormatSaverShader::get_recognized_extensions(const Ref<Resource> &p_resource, List<String> *p_extensions) const { if (const Shader *shader = Object::cast_to<Shader>(*p_resource)) { if (shader->is_text_shader()) { p_extensions->push_back("gdshader"); @@ -237,6 +237,6 @@ void ResourceFormatSaverShader::get_recognized_extensions(const RES &p_resource, } } -bool ResourceFormatSaverShader::recognize(const RES &p_resource) const { +bool ResourceFormatSaverShader::recognize(const Ref<Resource> &p_resource) const { return p_resource->get_class_name() == "Shader"; //only shader, not inherited } diff --git a/scene/resources/shader.h b/scene/resources/shader.h index d05ec06819..11c9f60ce8 100644 --- a/scene/resources/shader.h +++ b/scene/resources/shader.h @@ -58,8 +58,8 @@ private: // shaders keep a list of ShaderMaterial -> RenderingServer name translations, to make // 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, Map<int, Ref<Texture2D>>> default_textures; + mutable HashMap<StringName, StringName> params_cache; //map a shader param to a material param.. + HashMap<StringName, HashMap<int, Ref<Texture2D>>> default_textures; virtual void _update_shader() const; //used for visual shader protected: @@ -86,9 +86,9 @@ public: get_param_list(nullptr); } - const Map<StringName, StringName>::Element *E = params_cache.find(p_param); + const HashMap<StringName, StringName>::Iterator E = params_cache.find(p_param); if (E) { - return E->get(); + return E->value; } return StringName(); } @@ -103,7 +103,7 @@ VARIANT_ENUM_CAST(Shader::Mode); class ResourceFormatLoaderShader : public ResourceFormatLoader { public: - virtual RES load(const String &p_path, const String &p_original_path = "", Error *r_error = nullptr, bool p_use_sub_threads = false, float *r_progress = nullptr, CacheMode p_cache_mode = CACHE_MODE_REUSE); + virtual Ref<Resource> load(const String &p_path, const String &p_original_path = "", Error *r_error = nullptr, bool p_use_sub_threads = false, float *r_progress = nullptr, CacheMode p_cache_mode = CACHE_MODE_REUSE); virtual void get_recognized_extensions(List<String> *p_extensions) const; virtual bool handles_type(const String &p_type) const; virtual String get_resource_type(const String &p_path) const; @@ -111,9 +111,9 @@ public: class ResourceFormatSaverShader : public ResourceFormatSaver { public: - virtual Error save(const String &p_path, const RES &p_resource, uint32_t p_flags = 0); - virtual void get_recognized_extensions(const RES &p_resource, List<String> *p_extensions) const; - virtual bool recognize(const RES &p_resource) const; + virtual Error save(const String &p_path, const Ref<Resource> &p_resource, uint32_t p_flags = 0); + virtual void get_recognized_extensions(const Ref<Resource> &p_resource, List<String> *p_extensions) const; + virtual bool recognize(const Ref<Resource> &p_resource) const; }; #endif // SHADER_H diff --git a/scene/resources/shape_3d.cpp b/scene/resources/shape_3d.cpp index ffb2b27644..4423c1d7bb 100644 --- a/scene/resources/shape_3d.cpp +++ b/scene/resources/shape_3d.cpp @@ -117,7 +117,7 @@ void Shape3D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_debug_mesh"), &Shape3D::get_debug_mesh); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "custom_solver_bias", PROPERTY_HINT_RANGE, "0,1,0.001"), "set_custom_solver_bias", "get_custom_solver_bias"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "margin", PROPERTY_HINT_RANGE, "0.001,10,0.001"), "set_margin", "get_margin"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "margin", PROPERTY_HINT_RANGE, "0.001,10,0.001,suffix:m"), "set_margin", "get_margin"); } Shape3D::Shape3D() { diff --git a/scene/resources/skeleton_modification_2d_physicalbones.h b/scene/resources/skeleton_modification_2d_physicalbones.h index d53102fa5e..373ff666ee 100644 --- a/scene/resources/skeleton_modification_2d_physicalbones.h +++ b/scene/resources/skeleton_modification_2d_physicalbones.h @@ -52,7 +52,7 @@ private: bool _simulation_state_dirty = false; TypedArray<StringName> _simulation_state_dirty_names; - bool _simulation_state_dirty_process; + bool _simulation_state_dirty_process = false; void _update_simulation_state(); protected: diff --git a/scene/resources/skeleton_modification_2d_twoboneik.cpp b/scene/resources/skeleton_modification_2d_twoboneik.cpp index b08fd82381..d3c62e441f 100644 --- a/scene/resources/skeleton_modification_2d_twoboneik.cpp +++ b/scene/resources/skeleton_modification_2d_twoboneik.cpp @@ -464,8 +464,8 @@ void SkeletonModification2DTwoBoneIK::_bind_methods() { ClassDB::bind_method(D_METHOD("get_joint_two_bone_idx"), &SkeletonModification2DTwoBoneIK::get_joint_two_bone_idx); ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "target_nodepath", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Node2D"), "set_target_node", "get_target_node"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "target_minimum_distance", PROPERTY_HINT_RANGE, "0, 100000000, 0.01"), "set_target_minimum_distance", "get_target_minimum_distance"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "target_maximum_distance", PROPERTY_HINT_NONE, "0, 100000000, 0.01"), "set_target_maximum_distance", "get_target_maximum_distance"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "target_minimum_distance", PROPERTY_HINT_RANGE, "0,100000000,0.01,suffix:m"), "set_target_minimum_distance", "get_target_minimum_distance"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "target_maximum_distance", PROPERTY_HINT_NONE, "0,100000000,0.01,suffix:m"), "set_target_maximum_distance", "get_target_maximum_distance"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "flip_bend_direction", PROPERTY_HINT_NONE, ""), "set_flip_bend_direction", "get_flip_bend_direction"); ADD_GROUP("", ""); } diff --git a/scene/resources/skeleton_modification_stack_2d.cpp b/scene/resources/skeleton_modification_stack_2d.cpp index b944c244b6..38ec19828f 100644 --- a/scene/resources/skeleton_modification_stack_2d.cpp +++ b/scene/resources/skeleton_modification_stack_2d.cpp @@ -263,7 +263,7 @@ void SkeletonModificationStack2D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "enabled"), "set_enabled", "get_enabled"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "strength", PROPERTY_HINT_RANGE, "0, 1, 0.001"), "set_strength", "get_strength"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "modification_count", PROPERTY_HINT_RANGE, "0, 100, 1"), "set_modification_count", "get_modification_count"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "modification_count", PROPERTY_HINT_RANGE, "0, 100, 1", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_ARRAY, "Modifications,modifications/"), "set_modification_count", "get_modification_count"); } SkeletonModificationStack2D::SkeletonModificationStack2D() { diff --git a/scene/resources/skeleton_modification_stack_3d.cpp b/scene/resources/skeleton_modification_stack_3d.cpp index 7ccba1228c..44fbfc934e 100644 --- a/scene/resources/skeleton_modification_stack_3d.cpp +++ b/scene/resources/skeleton_modification_stack_3d.cpp @@ -217,7 +217,7 @@ void SkeletonModificationStack3D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "enabled"), "set_enabled", "get_enabled"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "strength", PROPERTY_HINT_RANGE, "0, 1, 0.001"), "set_strength", "get_strength"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "modification_count", PROPERTY_HINT_RANGE, "0, 100, 1"), "set_modification_count", "get_modification_count"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "modification_count", PROPERTY_HINT_RANGE, "0, 100, 1", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_ARRAY, "Modifications,modifications/"), "set_modification_count", "get_modification_count"); } SkeletonModificationStack3D::SkeletonModificationStack3D() { diff --git a/scene/resources/skeleton_profile.cpp b/scene/resources/skeleton_profile.cpp new file mode 100644 index 0000000000..0714de470c --- /dev/null +++ b/scene/resources/skeleton_profile.cpp @@ -0,0 +1,793 @@ +/*************************************************************************/ +/* skeleton_profile.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 "skeleton_profile.h" + +bool SkeletonProfile::_set(const StringName &p_path, const Variant &p_value) { + ERR_FAIL_COND_V(is_read_only, false); + String path = p_path; + + if (path.begins_with("groups/")) { + int which = path.get_slicec('/', 1).to_int(); + String what = path.get_slicec('/', 2); + ERR_FAIL_INDEX_V(which, groups.size(), false); + + if (what == "group_name") { + set_group_name(which, p_value); + } else if (what == "texture") { + set_texture(which, p_value); + } else { + return false; + } + } + + if (path.begins_with("bones/")) { + int which = path.get_slicec('/', 1).to_int(); + String what = path.get_slicec('/', 2); + ERR_FAIL_INDEX_V(which, bones.size(), false); + + if (what == "bone_name") { + set_bone_name(which, p_value); + } else if (what == "bone_parent") { + set_bone_parent(which, p_value); + } else if (what == "tail_direction") { + set_tail_direction(which, static_cast<TailDirection>((int)p_value)); + } else if (what == "bone_tail") { + set_bone_tail(which, p_value); + } else if (what == "reference_pose") { + set_reference_pose(which, p_value); + } else if (what == "handle_offset") { + set_handle_offset(which, p_value); + } else if (what == "group") { + set_group(which, p_value); + } else if (what == "require") { + set_require(which, p_value); + } else { + return false; + } + } + return true; +} + +bool SkeletonProfile::_get(const StringName &p_path, Variant &r_ret) const { + String path = p_path; + + if (path.begins_with("groups/")) { + int which = path.get_slicec('/', 1).to_int(); + String what = path.get_slicec('/', 2); + ERR_FAIL_INDEX_V(which, groups.size(), false); + + if (what == "group_name") { + r_ret = get_group_name(which); + } else if (what == "texture") { + r_ret = get_texture(which); + } else { + return false; + } + } + + if (path.begins_with("bones/")) { + int which = path.get_slicec('/', 1).to_int(); + String what = path.get_slicec('/', 2); + ERR_FAIL_INDEX_V(which, bones.size(), false); + + if (what == "bone_name") { + r_ret = get_bone_name(which); + } else if (what == "bone_parent") { + r_ret = get_bone_parent(which); + } else if (what == "tail_direction") { + r_ret = get_tail_direction(which); + } else if (what == "bone_tail") { + r_ret = get_bone_tail(which); + } else if (what == "reference_pose") { + r_ret = get_reference_pose(which); + } else if (what == "handle_offset") { + r_ret = get_handle_offset(which); + } else if (what == "group") { + r_ret = get_group(which); + } else if (what == "require") { + r_ret = is_require(which); + } else { + return false; + } + } + return true; +} + +void SkeletonProfile::_validate_property(PropertyInfo &property) const { + if (is_read_only) { + if (property.name == ("group_size") || property.name == ("bone_size")) { + property.usage = PROPERTY_USAGE_NO_EDITOR; + return; + } + } + + PackedStringArray split = property.name.split("/"); + if (split.size() == 3 && split[0] == "bones") { + if (split[2] == "bone_tail" && get_tail_direction(split[1].to_int()) != TAIL_DIRECTION_SPECIFIC_CHILD) { + property.usage = PROPERTY_USAGE_NONE; + } + } +} + +void SkeletonProfile::_get_property_list(List<PropertyInfo> *p_list) const { + if (is_read_only) { + return; + } + String group_names = ""; + for (int i = 0; i < groups.size(); i++) { + String path = "groups/" + itos(i) + "/"; + p_list->push_back(PropertyInfo(Variant::STRING_NAME, path + "group_name")); + p_list->push_back(PropertyInfo(Variant::OBJECT, path + "texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D")); + if (i > 0) { + group_names = group_names + ","; + } + group_names = group_names + groups[i].group_name; + } + for (int i = 0; i < bones.size(); i++) { + String path = "bones/" + itos(i) + "/"; + p_list->push_back(PropertyInfo(Variant::STRING_NAME, path + "bone_name")); + p_list->push_back(PropertyInfo(Variant::STRING_NAME, path + "bone_parent")); + p_list->push_back(PropertyInfo(Variant::INT, path + "tail_direction", PROPERTY_HINT_ENUM, "AverageChildren,SpecificChild,End")); + p_list->push_back(PropertyInfo(Variant::STRING_NAME, path + "bone_tail")); + p_list->push_back(PropertyInfo(Variant::TRANSFORM3D, path + "reference_pose")); + p_list->push_back(PropertyInfo(Variant::VECTOR2, path + "handle_offset")); + p_list->push_back(PropertyInfo(Variant::STRING_NAME, path + "group", PROPERTY_HINT_ENUM, group_names)); + p_list->push_back(PropertyInfo(Variant::BOOL, path + "require")); + } + + for (PropertyInfo &E : *p_list) { + _validate_property(E); + } +} + +int SkeletonProfile::get_group_size() { + return groups.size(); +} + +void SkeletonProfile::set_group_size(int p_size) { + if (is_read_only) { + return; + } + ERR_FAIL_COND(p_size < 0); + groups.resize(p_size); + emit_signal("profile_updated"); + notify_property_list_changed(); +} + +StringName SkeletonProfile::get_group_name(int p_group_idx) const { + ERR_FAIL_INDEX_V(p_group_idx, groups.size(), StringName()); + return groups[p_group_idx].group_name; +} + +void SkeletonProfile::set_group_name(int p_group_idx, const StringName p_group_name) { + if (is_read_only) { + return; + } + ERR_FAIL_INDEX(p_group_idx, groups.size()); + groups.write[p_group_idx].group_name = p_group_name; + emit_signal("profile_updated"); +} + +Ref<Texture2D> SkeletonProfile::get_texture(int p_group_idx) const { + ERR_FAIL_INDEX_V(p_group_idx, groups.size(), Ref<Texture2D>()); + return groups[p_group_idx].texture; +} + +void SkeletonProfile::set_texture(int p_group_idx, const Ref<Texture2D> &p_texture) { + if (is_read_only) { + return; + } + ERR_FAIL_INDEX(p_group_idx, groups.size()); + groups.write[p_group_idx].texture = p_texture; + emit_signal("profile_updated"); +} + +int SkeletonProfile::get_bone_size() { + return bones.size(); +} + +void SkeletonProfile::set_bone_size(int p_size) { + if (is_read_only) { + return; + } + ERR_FAIL_COND(p_size < 0); + bones.resize(p_size); + emit_signal("profile_updated"); + notify_property_list_changed(); +} + +int SkeletonProfile::find_bone(StringName p_bone_name) const { + if (p_bone_name == StringName()) { + return -1; + } + for (int i = 0; i < bones.size(); i++) { + if (bones[i].bone_name == p_bone_name) { + return i; + } + } + return -1; +} + +StringName SkeletonProfile::get_bone_name(int p_bone_idx) const { + ERR_FAIL_INDEX_V(p_bone_idx, bones.size(), StringName()); + return bones[p_bone_idx].bone_name; +} + +void SkeletonProfile::set_bone_name(int p_bone_idx, const StringName p_bone_name) { + if (is_read_only) { + return; + } + ERR_FAIL_INDEX(p_bone_idx, bones.size()); + bones.write[p_bone_idx].bone_name = p_bone_name; + emit_signal("profile_updated"); +} + +StringName SkeletonProfile::get_bone_parent(int p_bone_idx) const { + ERR_FAIL_INDEX_V(p_bone_idx, bones.size(), StringName()); + return bones[p_bone_idx].bone_parent; +} + +void SkeletonProfile::set_bone_parent(int p_bone_idx, const StringName p_bone_parent) { + if (is_read_only) { + return; + } + ERR_FAIL_INDEX(p_bone_idx, bones.size()); + bones.write[p_bone_idx].bone_parent = p_bone_parent; + emit_signal("profile_updated"); +} + +SkeletonProfile::TailDirection SkeletonProfile::get_tail_direction(int p_bone_idx) const { + ERR_FAIL_INDEX_V(p_bone_idx, bones.size(), TAIL_DIRECTION_AVERAGE_CHILDREN); + return bones[p_bone_idx].tail_direction; +} + +void SkeletonProfile::set_tail_direction(int p_bone_idx, const TailDirection p_tail_direction) { + if (is_read_only) { + return; + } + ERR_FAIL_INDEX(p_bone_idx, bones.size()); + bones.write[p_bone_idx].tail_direction = p_tail_direction; + emit_signal("profile_updated"); + notify_property_list_changed(); +} + +StringName SkeletonProfile::get_bone_tail(int p_bone_idx) const { + ERR_FAIL_INDEX_V(p_bone_idx, bones.size(), StringName()); + return bones[p_bone_idx].bone_tail; +} + +void SkeletonProfile::set_bone_tail(int p_bone_idx, const StringName p_bone_tail) { + if (is_read_only) { + return; + } + ERR_FAIL_INDEX(p_bone_idx, bones.size()); + bones.write[p_bone_idx].bone_tail = p_bone_tail; + emit_signal("profile_updated"); +} + +Transform3D SkeletonProfile::get_reference_pose(int p_bone_idx) const { + ERR_FAIL_INDEX_V(p_bone_idx, bones.size(), Transform3D()); + return bones[p_bone_idx].reference_pose; +} + +void SkeletonProfile::set_reference_pose(int p_bone_idx, const Transform3D p_reference_pose) { + if (is_read_only) { + return; + } + ERR_FAIL_INDEX(p_bone_idx, bones.size()); + bones.write[p_bone_idx].reference_pose = p_reference_pose; + emit_signal("profile_updated"); +} + +Vector2 SkeletonProfile::get_handle_offset(int p_bone_idx) const { + ERR_FAIL_INDEX_V(p_bone_idx, bones.size(), Vector2()); + return bones[p_bone_idx].handle_offset; +} + +void SkeletonProfile::set_handle_offset(int p_bone_idx, const Vector2 p_handle_offset) { + if (is_read_only) { + return; + } + ERR_FAIL_INDEX(p_bone_idx, bones.size()); + bones.write[p_bone_idx].handle_offset = p_handle_offset; + emit_signal("profile_updated"); +} + +StringName SkeletonProfile::get_group(int p_bone_idx) const { + ERR_FAIL_INDEX_V(p_bone_idx, bones.size(), StringName()); + return bones[p_bone_idx].group; +} + +void SkeletonProfile::set_group(int p_bone_idx, const StringName p_group) { + if (is_read_only) { + return; + } + ERR_FAIL_INDEX(p_bone_idx, bones.size()); + bones.write[p_bone_idx].group = p_group; + emit_signal("profile_updated"); +} + +bool SkeletonProfile::is_require(int p_bone_idx) const { + ERR_FAIL_INDEX_V(p_bone_idx, bones.size(), false); + return bones[p_bone_idx].require; +} + +void SkeletonProfile::set_require(int p_bone_idx, const bool p_require) { + if (is_read_only) { + return; + } + ERR_FAIL_INDEX(p_bone_idx, bones.size()); + bones.write[p_bone_idx].require = p_require; + emit_signal("profile_updated"); +} + +bool SkeletonProfile::has_bone(StringName p_bone_name) { + bool is_found = false; + for (int i = 0; i < bones.size(); i++) { + if (bones[i].bone_name == p_bone_name) { + is_found = true; + break; + } + } + return is_found; +} + +void SkeletonProfile::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_group_size", "size"), &SkeletonProfile::set_group_size); + ClassDB::bind_method(D_METHOD("get_group_size"), &SkeletonProfile::get_group_size); + + ClassDB::bind_method(D_METHOD("get_group_name", "group_idx"), &SkeletonProfile::get_group_name); + ClassDB::bind_method(D_METHOD("set_group_name", "group_idx", "group_name"), &SkeletonProfile::set_group_name); + + ClassDB::bind_method(D_METHOD("get_texture", "group_idx"), &SkeletonProfile::get_texture); + ClassDB::bind_method(D_METHOD("set_texture", "group_idx", "texture"), &SkeletonProfile::set_texture); + + ClassDB::bind_method(D_METHOD("set_bone_size", "size"), &SkeletonProfile::set_bone_size); + ClassDB::bind_method(D_METHOD("get_bone_size"), &SkeletonProfile::get_bone_size); + + ClassDB::bind_method(D_METHOD("find_bone", "bone_name"), &SkeletonProfile::find_bone); + + ClassDB::bind_method(D_METHOD("get_bone_name", "bone_idx"), &SkeletonProfile::get_bone_name); + ClassDB::bind_method(D_METHOD("set_bone_name", "bone_idx", "bone_name"), &SkeletonProfile::set_bone_name); + + ClassDB::bind_method(D_METHOD("get_bone_parent", "bone_idx"), &SkeletonProfile::get_bone_parent); + ClassDB::bind_method(D_METHOD("set_bone_parent", "bone_idx", "bone_parent"), &SkeletonProfile::set_bone_parent); + + ClassDB::bind_method(D_METHOD("get_tail_direction", "bone_idx"), &SkeletonProfile::get_tail_direction); + ClassDB::bind_method(D_METHOD("set_tail_direction", "bone_idx", "tail_direction"), &SkeletonProfile::set_tail_direction); + + ClassDB::bind_method(D_METHOD("get_bone_tail", "bone_idx"), &SkeletonProfile::get_bone_tail); + ClassDB::bind_method(D_METHOD("set_bone_tail", "bone_idx", "bone_tail"), &SkeletonProfile::set_bone_tail); + + ClassDB::bind_method(D_METHOD("get_reference_pose", "bone_idx"), &SkeletonProfile::get_reference_pose); + ClassDB::bind_method(D_METHOD("set_reference_pose", "bone_idx", "bone_name"), &SkeletonProfile::set_reference_pose); + + ClassDB::bind_method(D_METHOD("get_handle_offset", "bone_idx"), &SkeletonProfile::get_handle_offset); + ClassDB::bind_method(D_METHOD("set_handle_offset", "bone_idx", "handle_offset"), &SkeletonProfile::set_handle_offset); + + ClassDB::bind_method(D_METHOD("get_group", "bone_idx"), &SkeletonProfile::get_group); + ClassDB::bind_method(D_METHOD("set_group", "bone_idx", "group"), &SkeletonProfile::set_group); + + ADD_PROPERTY(PropertyInfo(Variant::INT, "group_size", PROPERTY_HINT_RANGE, "0,100,1", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_ARRAY, "Groups,groups/"), "set_group_size", "get_group_size"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "bone_size", PROPERTY_HINT_RANGE, "0,100,1", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_ARRAY, "Bones,bones/"), "set_bone_size", "get_bone_size"); + + ADD_SIGNAL(MethodInfo("profile_updated")); + + BIND_ENUM_CONSTANT(TAIL_DIRECTION_AVERAGE_CHILDREN); + BIND_ENUM_CONSTANT(TAIL_DIRECTION_SPECIFIC_CHILD); + BIND_ENUM_CONSTANT(TAIL_DIRECTION_END); +} + +SkeletonProfile::SkeletonProfile() { +} + +SkeletonProfile::~SkeletonProfile() { +} + +SkeletonProfileHumanoid::SkeletonProfileHumanoid() { + is_read_only = true; + + groups.resize(4); + + groups.write[0].group_name = "Body"; + groups.write[1].group_name = "Face"; + groups.write[2].group_name = "LeftHand"; + groups.write[3].group_name = "RightHand"; + + bones.resize(56); + + bones.write[0].bone_name = "Root"; + bones.write[0].reference_pose = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0); + bones.write[0].handle_offset = Vector2(0.5, 0.91); + bones.write[0].group = "Body"; + + bones.write[1].bone_name = "Hips"; + bones.write[1].bone_parent = "Root"; + bones.write[1].tail_direction = TAIL_DIRECTION_SPECIFIC_CHILD; + bones.write[1].bone_tail = "Spine"; + bones.write[1].reference_pose = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.75, 0); + bones.write[1].handle_offset = Vector2(0.5, 0.5); + bones.write[1].group = "Body"; + bones.write[1].require = true; + + bones.write[2].bone_name = "Spine"; + bones.write[2].bone_parent = "Hips"; + bones.write[2].reference_pose = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.1, 0); + bones.write[2].handle_offset = Vector2(0.5, 0.43); + bones.write[2].group = "Body"; + bones.write[2].require = true; + + bones.write[3].bone_name = "Chest"; + bones.write[3].bone_parent = "Spine"; + bones.write[3].reference_pose = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.1, 0); + bones.write[3].handle_offset = Vector2(0.5, 0.36); + bones.write[3].group = "Body"; + + bones.write[4].bone_name = "UpperChest"; + bones.write[4].bone_parent = "Chest"; + bones.write[4].reference_pose = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.1, 0); + bones.write[4].handle_offset = Vector2(0.5, 0.29); + bones.write[4].group = "Body"; + + bones.write[5].bone_name = "Neck"; + bones.write[5].bone_parent = "UpperChest"; + bones.write[5].tail_direction = TAIL_DIRECTION_SPECIFIC_CHILD; + bones.write[5].bone_tail = "Head"; + bones.write[5].reference_pose = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.1, 0); + bones.write[5].handle_offset = Vector2(0.5, 0.23); + bones.write[5].group = "Body"; + bones.write[5].require = true; + + bones.write[6].bone_name = "Head"; + bones.write[6].bone_parent = "Neck"; + bones.write[6].tail_direction = TAIL_DIRECTION_END; + bones.write[6].reference_pose = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.1, 0); + bones.write[6].handle_offset = Vector2(0.5, 0.18); + bones.write[6].group = "Body"; + bones.write[6].require = true; + + bones.write[7].bone_name = "LeftEye"; + bones.write[7].bone_parent = "Head"; + bones.write[7].reference_pose = Transform3D(1, 0, 0, 0, 0, -1, 0, 1, 0, 0.05, 0.15, 0); + bones.write[7].handle_offset = Vector2(0.6, 0.46); + bones.write[7].group = "Face"; + + bones.write[8].bone_name = "RightEye"; + bones.write[8].bone_parent = "Head"; + bones.write[8].reference_pose = Transform3D(1, 0, 0, 0, 0, -1, 0, 1, 0, -0.05, 0.15, 0); + bones.write[8].handle_offset = Vector2(0.37, 0.46); + bones.write[8].group = "Face"; + + bones.write[9].bone_name = "Jaw"; + bones.write[9].bone_parent = "Head"; + bones.write[9].reference_pose = Transform3D(-1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0.05, 0.05); + bones.write[9].handle_offset = Vector2(0.46, 0.75); + bones.write[9].group = "Face"; + + bones.write[10].bone_name = "LeftShoulder"; + bones.write[10].bone_parent = "UpperChest"; + bones.write[10].reference_pose = Transform3D(0, 1, 0, 0, 0, 1, 1, 0, 0, 0.05, 0.1, 0); + bones.write[10].handle_offset = Vector2(0.55, 0.235); + bones.write[10].group = "Body"; + bones.write[10].require = true; + + bones.write[11].bone_name = "LeftUpperArm"; + bones.write[11].bone_parent = "LeftShoulder"; + bones.write[11].reference_pose = Transform3D(-1, 0, 0, 0, 1, 0, 0, 0, -1, 0, 0.05, 0); + bones.write[11].handle_offset = Vector2(0.6, 0.24); + bones.write[11].group = "Body"; + bones.write[11].require = true; + + bones.write[12].bone_name = "LeftLowerArm"; + bones.write[12].bone_parent = "LeftUpperArm"; + bones.write[12].reference_pose = Transform3D(0, 0, -1, 0, 1, 0, 1, 0, 0, 0, 0.25, 0); + bones.write[12].handle_offset = Vector2(0.7, 0.24); + bones.write[12].group = "Body"; + bones.write[12].require = true; + + bones.write[13].bone_name = "LeftHand"; + bones.write[13].bone_parent = "LeftLowerArm"; + bones.write[13].tail_direction = TAIL_DIRECTION_SPECIFIC_CHILD; + bones.write[13].bone_tail = "LeftMiddleProximal"; + bones.write[13].reference_pose = Transform3D(0, 0, 1, 0, 1, 0, -1, 0, 0, 0, 0.25, 0); + bones.write[13].handle_offset = Vector2(0.82, 0.235); + bones.write[13].group = "Body"; + bones.write[13].require = true; + + bones.write[14].bone_name = "LeftThumbMetacarpal"; + bones.write[14].bone_parent = "LeftHand"; + bones.write[14].reference_pose = Transform3D(0, -0.577, 0.816, 0.707, 0.577, 0.408, -0.707, 0.577, 0.408, -0.025, 0, 0); + bones.write[14].handle_offset = Vector2(0.4, 0.8); + bones.write[14].group = "LeftHand"; + + bones.write[15].bone_name = "LeftThumbProximal"; + bones.write[15].bone_parent = "LeftThumbMetacarpal"; + bones.write[15].reference_pose = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.043, 0); + bones.write[15].handle_offset = Vector2(0.3, 0.69); + bones.write[15].group = "LeftHand"; + + bones.write[16].bone_name = "LeftThumbDistal"; + bones.write[16].bone_parent = "LeftThumbProximal"; + bones.write[16].reference_pose = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.043, 0); + bones.write[16].handle_offset = Vector2(0.23, 0.555); + bones.write[16].group = "LeftHand"; + + bones.write[17].bone_name = "LeftIndexProximal"; + bones.write[17].bone_parent = "LeftHand"; + bones.write[17].reference_pose = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.025, 0.075, 0); + bones.write[17].handle_offset = Vector2(0.413, 0.52); + bones.write[17].group = "LeftHand"; + + bones.write[18].bone_name = "LeftIndexIntermediate"; + bones.write[18].bone_parent = "LeftIndexProximal"; + bones.write[18].reference_pose = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.05, 0); + bones.write[18].handle_offset = Vector2(0.403, 0.36); + bones.write[18].group = "LeftHand"; + + bones.write[19].bone_name = "LeftIndexDistal"; + bones.write[19].bone_parent = "LeftIndexIntermediate"; + bones.write[19].reference_pose = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.025, 0); + bones.write[19].handle_offset = Vector2(0.403, 0.255); + bones.write[19].group = "LeftHand"; + + bones.write[20].bone_name = "LeftMiddleProximal"; + bones.write[20].bone_parent = "LeftHand"; + bones.write[20].reference_pose = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.075, 0); + bones.write[20].handle_offset = Vector2(0.5, 0.51); + bones.write[20].group = "LeftHand"; + + bones.write[21].bone_name = "LeftMiddleIntermediate"; + bones.write[21].bone_parent = "LeftMiddleProximal"; + bones.write[21].reference_pose = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.075, 0); + bones.write[21].handle_offset = Vector2(0.5, 0.345); + bones.write[21].group = "LeftHand"; + + bones.write[22].bone_name = "LeftMiddleDistal"; + bones.write[22].bone_parent = "LeftMiddleIntermediate"; + bones.write[22].reference_pose = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.025, 0); + bones.write[22].handle_offset = Vector2(0.5, 0.22); + bones.write[22].group = "LeftHand"; + + bones.write[23].bone_name = "LeftRingProximal"; + bones.write[23].bone_parent = "LeftHand"; + bones.write[23].reference_pose = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.025, 0.075, 0); + bones.write[23].handle_offset = Vector2(0.586, 0.52); + bones.write[23].group = "LeftHand"; + + bones.write[24].bone_name = "LeftRingIntermediate"; + bones.write[24].bone_parent = "LeftRingProximal"; + bones.write[24].reference_pose = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.05, 0); + bones.write[24].handle_offset = Vector2(0.59, 0.36); + bones.write[24].group = "LeftHand"; + + bones.write[25].bone_name = "LeftRingDistal"; + bones.write[25].bone_parent = "LeftRingIntermediate"; + bones.write[25].reference_pose = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.025, 0); + bones.write[25].handle_offset = Vector2(0.591, 0.25); + bones.write[25].group = "LeftHand"; + + bones.write[26].bone_name = "LeftLittleProximal"; + bones.write[26].bone_parent = "LeftHand"; + bones.write[26].reference_pose = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.05, 0.05, 0); + bones.write[26].handle_offset = Vector2(0.663, 0.543); + bones.write[26].group = "LeftHand"; + + bones.write[27].bone_name = "LeftLittleIntermediate"; + bones.write[27].bone_parent = "LeftLittleProximal"; + bones.write[27].reference_pose = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.05, 0); + bones.write[27].handle_offset = Vector2(0.672, 0.415); + bones.write[27].group = "LeftHand"; + + bones.write[28].bone_name = "LeftLittleDistal"; + bones.write[28].bone_parent = "LeftLittleIntermediate"; + bones.write[28].reference_pose = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.025, 0); + bones.write[28].handle_offset = Vector2(0.672, 0.32); + bones.write[28].group = "LeftHand"; + + bones.write[29].bone_name = "RightShoulder"; + bones.write[29].bone_parent = "UpperChest"; + bones.write[29].reference_pose = Transform3D(0, -1, 0, 0, 0, 1, -1, 0, 0, -0.05, 0.1, 0); + bones.write[29].handle_offset = Vector2(0.45, 0.235); + bones.write[29].group = "Body"; + bones.write[29].require = true; + + bones.write[30].bone_name = "RightUpperArm"; + bones.write[30].bone_parent = "RightShoulder"; + bones.write[30].reference_pose = Transform3D(-1, 0, 0, 0, 1, 0, 0, 0, -1, 0, 0.05, 0); + bones.write[30].handle_offset = Vector2(0.4, 0.24); + bones.write[30].group = "Body"; + bones.write[30].require = true; + + bones.write[31].bone_name = "RightLowerArm"; + bones.write[31].bone_parent = "RightUpperArm"; + bones.write[31].reference_pose = Transform3D(0, 0, 1, 0, 1, 0, -1, 0, 0, 0, 0.25, 0); + bones.write[31].handle_offset = Vector2(0.3, 0.24); + bones.write[31].group = "Body"; + bones.write[31].require = true; + + bones.write[32].bone_name = "RightHand"; + bones.write[32].bone_parent = "RightLowerArm"; + bones.write[32].tail_direction = TAIL_DIRECTION_SPECIFIC_CHILD; + bones.write[32].bone_tail = "RightMiddleProximal"; + bones.write[32].reference_pose = Transform3D(0, 0, -1, 0, 1, 0, 1, 0, 0, 0, 0.25, 0); + bones.write[32].handle_offset = Vector2(0.18, 0.235); + bones.write[32].group = "Body"; + bones.write[32].require = true; + + bones.write[33].bone_name = "RightThumbMetacarpal"; + bones.write[33].bone_parent = "RightHand"; + bones.write[33].reference_pose = Transform3D(0, 0.577, -0.816, -0.707, 0.577, 0.408, 0.707, 0.577, 0.408, 0.025, 0, 0); + bones.write[33].handle_offset = Vector2(0.6, 0.8); + bones.write[33].group = "RightHand"; + + bones.write[34].bone_name = "RightThumbProximal"; + bones.write[34].bone_parent = "RightThumbMetacarpal"; + bones.write[34].reference_pose = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.043, 0); + bones.write[34].handle_offset = Vector2(0.7, 0.69); + bones.write[34].group = "RightHand"; + + bones.write[35].bone_name = "RightThumbDistal"; + bones.write[35].bone_parent = "RightThumbProximal"; + bones.write[35].reference_pose = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.043, 0); + bones.write[35].handle_offset = Vector2(0.77, 0.555); + bones.write[35].group = "RightHand"; + + bones.write[36].bone_name = "RightIndexProximal"; + bones.write[36].bone_parent = "RightHand"; + bones.write[36].reference_pose = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.025, 0.075, 0); + bones.write[36].handle_offset = Vector2(0.587, 0.52); + bones.write[36].group = "RightHand"; + + bones.write[37].bone_name = "RightIndexIntermediate"; + bones.write[37].bone_parent = "RightIndexProximal"; + bones.write[37].reference_pose = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.05, 0); + bones.write[37].handle_offset = Vector2(0.597, 0.36); + bones.write[37].group = "RightHand"; + + bones.write[38].bone_name = "RightIndexDistal"; + bones.write[38].bone_parent = "RightIndexIntermediate"; + bones.write[38].reference_pose = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.025, 0); + bones.write[38].handle_offset = Vector2(0.597, 0.255); + bones.write[38].group = "RightHand"; + + bones.write[39].bone_name = "RightMiddleProximal"; + bones.write[39].bone_parent = "RightHand"; + bones.write[39].reference_pose = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.075, 0); + bones.write[39].handle_offset = Vector2(0.5, 0.51); + bones.write[39].group = "RightHand"; + + bones.write[40].bone_name = "RightMiddleIntermediate"; + bones.write[40].bone_parent = "RightMiddleProximal"; + bones.write[40].reference_pose = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.075, 0); + bones.write[40].handle_offset = Vector2(0.5, 0.345); + bones.write[40].group = "RightHand"; + + bones.write[41].bone_name = "RightMiddleDistal"; + bones.write[41].bone_parent = "RightMiddleIntermediate"; + bones.write[41].reference_pose = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.025, 0); + bones.write[41].handle_offset = Vector2(0.5, 0.22); + bones.write[41].group = "RightHand"; + + bones.write[42].bone_name = "RightRingProximal"; + bones.write[42].bone_parent = "RightHand"; + bones.write[42].reference_pose = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.025, 0.075, 0); + bones.write[42].handle_offset = Vector2(0.414, 0.52); + bones.write[42].group = "RightHand"; + + bones.write[43].bone_name = "RightRingIntermediate"; + bones.write[43].bone_parent = "RightRingProximal"; + bones.write[43].reference_pose = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.05, 0); + bones.write[43].handle_offset = Vector2(0.41, 0.36); + bones.write[43].group = "RightHand"; + + bones.write[44].bone_name = "RightRingDistal"; + bones.write[44].bone_parent = "RightRingIntermediate"; + bones.write[44].reference_pose = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.025, 0); + bones.write[44].handle_offset = Vector2(0.409, 0.25); + bones.write[44].group = "RightHand"; + + bones.write[45].bone_name = "RightLittleProximal"; + bones.write[45].bone_parent = "RightHand"; + bones.write[45].reference_pose = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.05, 0.05, 0); + bones.write[45].handle_offset = Vector2(0.337, 0.543); + bones.write[45].group = "RightHand"; + + bones.write[46].bone_name = "RightLittleIntermediate"; + bones.write[46].bone_parent = "RightLittleProximal"; + bones.write[46].reference_pose = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.05, 0); + bones.write[46].handle_offset = Vector2(0.328, 0.415); + bones.write[46].group = "RightHand"; + + bones.write[47].bone_name = "RightLittleDistal"; + bones.write[47].bone_parent = "RightLittleIntermediate"; + bones.write[47].reference_pose = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.025, 0); + bones.write[47].handle_offset = Vector2(0.328, 0.32); + bones.write[47].group = "RightHand"; + + bones.write[48].bone_name = "LeftUpperLeg"; + bones.write[48].bone_parent = "Hips"; + bones.write[48].reference_pose = Transform3D(-1, 0, 0, 0, -1, 0, 0, 0, 1, 0.1, 0, 0); + bones.write[48].handle_offset = Vector2(0.549, 0.49); + bones.write[48].group = "Body"; + bones.write[48].require = true; + + bones.write[49].bone_name = "LeftLowerLeg"; + bones.write[49].bone_parent = "LeftUpperLeg"; + bones.write[49].reference_pose = Transform3D(-1, 0, 0, 0, 1, 0, 0, 0, -1, 0, 0.375, 0); + bones.write[49].handle_offset = Vector2(0.548, 0.683); + bones.write[49].group = "Body"; + bones.write[49].require = true; + + bones.write[50].bone_name = "LeftFoot"; + bones.write[50].bone_parent = "LeftLowerLeg"; + bones.write[50].reference_pose = Transform3D(-1, 0, 0, 0, 0, -1, 0, -1, 0, 0, 0.375, 0); + bones.write[50].handle_offset = Vector2(0.545, 0.9); + bones.write[50].group = "Body"; + bones.write[50].require = true; + + bones.write[51].bone_name = "LeftToes"; + bones.write[51].bone_parent = "LeftFoot"; + bones.write[51].reference_pose = Transform3D(-1, 0, 0, 0, 1, 0, 0, 0, -1, 0, 0.15, 0); + bones.write[51].handle_offset = Vector2(0.545, 0.95); + bones.write[51].group = "Body"; + + bones.write[52].bone_name = "RightUpperLeg"; + bones.write[52].bone_parent = "Hips"; + bones.write[52].reference_pose = Transform3D(-1, 0, 0, 0, -1, 0, 0, 0, 1, -0.1, 0, 0); + bones.write[52].handle_offset = Vector2(0.451, 0.49); + bones.write[52].group = "Body"; + bones.write[52].require = true; + + bones.write[53].bone_name = "RightLowerLeg"; + bones.write[53].bone_parent = "RightUpperLeg"; + bones.write[53].reference_pose = Transform3D(-1, 0, 0, 0, 1, 0, 0, 0, -1, 0, 0.375, 0); + bones.write[53].handle_offset = Vector2(0.452, 0.683); + bones.write[53].group = "Body"; + bones.write[53].require = true; + + bones.write[54].bone_name = "RightFoot"; + bones.write[54].bone_parent = "RightLowerLeg"; + bones.write[54].reference_pose = Transform3D(-1, 0, 0, 0, 0, -1, 0, -1, 0, 0, 0.375, 0); + bones.write[54].handle_offset = Vector2(0.455, 0.9); + bones.write[54].group = "Body"; + bones.write[54].require = true; + + bones.write[55].bone_name = "RightToes"; + bones.write[55].bone_parent = "RightFoot"; + bones.write[55].reference_pose = Transform3D(-1, 0, 0, 0, 1, 0, 0, 0, -1, 0, 0.15, 0); + bones.write[55].handle_offset = Vector2(0.455, 0.95); + bones.write[55].group = "Body"; +} + +SkeletonProfileHumanoid::~SkeletonProfileHumanoid() { +} + +////////////////////////////////////// diff --git a/scene/resources/skeleton_profile.h b/scene/resources/skeleton_profile.h new file mode 100644 index 0000000000..d305311538 --- /dev/null +++ b/scene/resources/skeleton_profile.h @@ -0,0 +1,131 @@ +/*************************************************************************/ +/* skeleton_profile.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 SKELETON_PROFILE_H +#define SKELETON_PROFILE_H + +#include "texture.h" + +class SkeletonProfile : public Resource { + GDCLASS(SkeletonProfile, Resource); + +public: + enum TailDirection { + TAIL_DIRECTION_AVERAGE_CHILDREN, + TAIL_DIRECTION_SPECIFIC_CHILD, + TAIL_DIRECTION_END + }; + +protected: + // Note: SkeletonProfileHumanoid which extends SkeletonProfile exists to unify standard bone names. + // That is what is_read_only is for, so don't make it public. + bool is_read_only = false; + + struct SkeletonProfileGroup { + StringName group_name; + Ref<Texture2D> texture; + }; + + struct SkeletonProfileBone { + StringName bone_name; + StringName bone_parent; + TailDirection tail_direction = TAIL_DIRECTION_AVERAGE_CHILDREN; + StringName bone_tail; + Transform3D reference_pose; + Vector2 handle_offset; + StringName group; + bool require = false; + }; + + Vector<SkeletonProfileGroup> groups; + Vector<SkeletonProfileBone> bones; + + bool _get(const StringName &p_path, Variant &r_ret) const; + bool _set(const StringName &p_path, const Variant &p_value); + virtual void _validate_property(PropertyInfo &property) const override; + void _get_property_list(List<PropertyInfo> *p_list) const; + static void _bind_methods(); + +public: + int get_group_size(); + void set_group_size(int p_size); + + StringName get_group_name(int p_group_idx) const; + void set_group_name(int p_group_idx, const StringName p_group_name); + + Ref<Texture2D> get_texture(int p_group_idx) const; + void set_texture(int p_group_idx, const Ref<Texture2D> &p_texture); + + int get_bone_size(); + void set_bone_size(int p_size); + + int find_bone(const StringName p_bone_name) const; + + StringName get_bone_name(int p_bone_idx) const; + void set_bone_name(int p_bone_idx, const StringName p_bone_name); + + StringName get_bone_parent(int p_bone_idx) const; + void set_bone_parent(int p_bone_idx, const StringName p_bone_parent); + + TailDirection get_tail_direction(int p_bone_idx) const; + void set_tail_direction(int p_bone_idx, const TailDirection p_tail_direction); + + StringName get_bone_tail(int p_bone_idx) const; + void set_bone_tail(int p_bone_idx, const StringName p_bone_tail); + + Transform3D get_reference_pose(int p_bone_idx) const; + void set_reference_pose(int p_bone_idx, const Transform3D p_reference_pose); + + Vector2 get_handle_offset(int p_bone_idx) const; + void set_handle_offset(int p_bone_idx, const Vector2 p_handle_offset); + + StringName get_group(int p_bone_idx) const; + void set_group(int p_bone_idx, const StringName p_group); + + bool is_require(int p_bone_idx) const; + void set_require(int p_bone_idx, const bool p_require); + + bool has_bone(StringName p_bone_name); + + SkeletonProfile(); + ~SkeletonProfile(); +}; + +class SkeletonProfileHumanoid : public SkeletonProfile { + GDCLASS(SkeletonProfileHumanoid, SkeletonProfile); + +public: + SkeletonProfileHumanoid(); + ~SkeletonProfileHumanoid(); +}; + +VARIANT_ENUM_CAST(SkeletonProfile::TailDirection); + +#endif // SKELETON_PROFILE_H diff --git a/scene/resources/skin.cpp b/scene/resources/skin.cpp index 54ed71999c..1c04ba0cd4 100644 --- a/scene/resources/skin.cpp +++ b/scene/resources/skin.cpp @@ -130,11 +130,12 @@ bool Skin::_get(const StringName &p_name, Variant &r_ret) const { } void Skin::_get_property_list(List<PropertyInfo> *p_list) const { - p_list->push_back(PropertyInfo(Variant::INT, "bind_count", PROPERTY_HINT_RANGE, "0,16384,1,or_greater")); + p_list->push_back(PropertyInfo(Variant::INT, PNAME("bind_count"), PROPERTY_HINT_RANGE, "0,16384,1,or_greater")); for (int i = 0; i < get_bind_count(); i++) { - p_list->push_back(PropertyInfo(Variant::STRING_NAME, "bind/" + itos(i) + "/name")); - p_list->push_back(PropertyInfo(Variant::INT, "bind/" + itos(i) + "/bone", PROPERTY_HINT_RANGE, "0,16384,1,or_greater", get_bind_name(i) != StringName() ? PROPERTY_USAGE_NO_EDITOR : PROPERTY_USAGE_DEFAULT)); - p_list->push_back(PropertyInfo(Variant::TRANSFORM3D, "bind/" + itos(i) + "/pose")); + const String prefix = vformat("%s/%d/", PNAME("bind"), i); + p_list->push_back(PropertyInfo(Variant::STRING_NAME, prefix + PNAME("name"))); + p_list->push_back(PropertyInfo(Variant::INT, prefix + PNAME("bone"), PROPERTY_HINT_RANGE, "0,16384,1,or_greater", get_bind_name(i) != StringName() ? PROPERTY_USAGE_NO_EDITOR : PROPERTY_USAGE_DEFAULT)); + p_list->push_back(PropertyInfo(Variant::TRANSFORM3D, prefix + PNAME("pose"))); } } diff --git a/scene/resources/sky.cpp b/scene/resources/sky.cpp index 9cb6a16f5c..735134e27b 100644 --- a/scene/resources/sky.cpp +++ b/scene/resources/sky.cpp @@ -83,7 +83,7 @@ void Sky::_bind_methods() { ClassDB::bind_method(D_METHOD("get_material"), &Sky::get_material); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "sky_material", PROPERTY_HINT_RESOURCE_TYPE, "ShaderMaterial,PanoramaSkyMaterial,ProceduralSkyMaterial,PhysicalSkyMaterial"), "set_material", "get_material"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "process_mode", PROPERTY_HINT_ENUM, "Automatic,HighQuality,HighQualityIncremental,RealTime"), "set_process_mode", "get_process_mode"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "process_mode", PROPERTY_HINT_ENUM, "Automatic,High-Quality,High-Quality Incremental,Real-Time"), "set_process_mode", "get_process_mode"); ADD_PROPERTY(PropertyInfo(Variant::INT, "radiance_size", PROPERTY_HINT_ENUM, "32,64,128,256,512,1024,2048"), "set_radiance_size", "get_radiance_size"); BIND_ENUM_CONSTANT(RADIANCE_SIZE_32); diff --git a/scene/resources/sky_material.cpp b/scene/resources/sky_material.cpp index 4681d3d6e3..5d1a223cc7 100644 --- a/scene/resources/sky_material.cpp +++ b/scene/resources/sky_material.cpp @@ -144,6 +144,15 @@ float ProceduralSkyMaterial::get_sun_curve() const { return sun_curve; } +void ProceduralSkyMaterial::set_use_debanding(bool p_use_debanding) { + use_debanding = p_use_debanding; + RS::get_singleton()->material_set_param(_get_material(), "use_debanding", use_debanding); +} + +bool ProceduralSkyMaterial::get_use_debanding() const { + return use_debanding; +} + Shader::Mode ProceduralSkyMaterial::get_shader_mode() const { return Shader::MODE_SKY; } @@ -199,6 +208,9 @@ void ProceduralSkyMaterial::_bind_methods() { ClassDB::bind_method(D_METHOD("set_sun_curve", "curve"), &ProceduralSkyMaterial::set_sun_curve); ClassDB::bind_method(D_METHOD("get_sun_curve"), &ProceduralSkyMaterial::get_sun_curve); + ClassDB::bind_method(D_METHOD("set_use_debanding", "use_debanding"), &ProceduralSkyMaterial::set_use_debanding); + ClassDB::bind_method(D_METHOD("get_use_debanding"), &ProceduralSkyMaterial::get_use_debanding); + ADD_GROUP("Sky", "sky_"); ADD_PROPERTY(PropertyInfo(Variant::COLOR, "sky_top_color", PROPERTY_HINT_COLOR_NO_ALPHA), "set_sky_top_color", "get_sky_top_color"); ADD_PROPERTY(PropertyInfo(Variant::COLOR, "sky_horizon_color", PROPERTY_HINT_COLOR_NO_ALPHA), "set_sky_horizon_color", "get_sky_horizon_color"); @@ -214,8 +226,11 @@ void ProceduralSkyMaterial::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "ground_energy", PROPERTY_HINT_RANGE, "0,64,0.01"), "set_ground_energy", "get_ground_energy"); ADD_GROUP("Sun", "sun_"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "sun_angle_max", PROPERTY_HINT_RANGE, "0,360,0.01"), "set_sun_angle_max", "get_sun_angle_max"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "sun_angle_max", PROPERTY_HINT_RANGE, "0,360,0.01,degrees"), "set_sun_angle_max", "get_sun_angle_max"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "sun_curve", PROPERTY_HINT_EXP_EASING), "set_sun_curve", "get_sun_curve"); + + ADD_GROUP("", ""); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_debanding"), "set_use_debanding", "get_use_debanding"); } void ProceduralSkyMaterial::cleanup_shader() { @@ -235,18 +250,26 @@ void ProceduralSkyMaterial::_update_shader() { shader_type sky; -uniform vec4 sky_top_color : hint_color = vec4(0.385, 0.454, 0.55, 1.0); -uniform vec4 sky_horizon_color : hint_color = vec4(0.646, 0.656, 0.67, 1.0); +uniform vec4 sky_top_color : source_color = vec4(0.385, 0.454, 0.55, 1.0); +uniform vec4 sky_horizon_color : source_color = vec4(0.646, 0.656, 0.67, 1.0); uniform float sky_curve : hint_range(0, 1) = 0.15; uniform float sky_energy = 1.0; -uniform sampler2D sky_cover : hint_black_albedo; -uniform vec4 sky_cover_modulate : hint_color = vec4(1.0, 1.0, 1.0, 1.0); -uniform vec4 ground_bottom_color : hint_color = vec4(0.2, 0.169, 0.133, 1.0); -uniform vec4 ground_horizon_color : hint_color = vec4(0.646, 0.656, 0.67, 1.0); +uniform sampler2D sky_cover : source_color, hint_default_black; +uniform vec4 sky_cover_modulate : source_color = vec4(1.0, 1.0, 1.0, 1.0); +uniform vec4 ground_bottom_color : source_color = vec4(0.2, 0.169, 0.133, 1.0); +uniform vec4 ground_horizon_color : source_color = vec4(0.646, 0.656, 0.67, 1.0); uniform float ground_curve : hint_range(0, 1) = 0.02; uniform float ground_energy = 1.0; uniform float sun_angle_max = 30.0; uniform float sun_curve : hint_range(0, 1) = 0.15; +uniform bool use_debanding = true; + +// https://www.iryoku.com/next-generation-post-processing-in-call-of-duty-advanced-warfare +vec3 interleaved_gradient_noise(vec2 pos) { + const vec3 magic = vec3(0.06711056f, 0.00583715f, 52.9829189f); + float res = fract(magic.z * fract(dot(pos, magic.xy))) * 2.0 - 1.0; + return vec3(res, -res, res) / 255.0; +} void sky() { float v_angle = acos(clamp(EYEDIR.y, -1.0, 1.0)); @@ -302,6 +325,9 @@ void sky() { ground *= ground_energy; COLOR = mix(ground, sky, step(0.0, EYEDIR.y)); + if (use_debanding) { + COLOR += interleaved_gradient_noise(FRAGCOORD.xy); + } } )"); } @@ -322,6 +348,7 @@ ProceduralSkyMaterial::ProceduralSkyMaterial() { set_sun_angle_max(30.0); set_sun_curve(0.15); + set_use_debanding(true); } ProceduralSkyMaterial::~ProceduralSkyMaterial() { @@ -407,7 +434,7 @@ void PanoramaSkyMaterial::_update_shader() { shader_type sky; -uniform sampler2D source_panorama : %s, hint_black_albedo; +uniform sampler2D source_panorama : %s, source_color, hint_default_black; void sky() { COLOR = texture(source_panorama, SKY_COORDS).rgb; @@ -510,13 +537,13 @@ float PhysicalSkyMaterial::get_exposure() const { return exposure; } -void PhysicalSkyMaterial::set_dither_strength(float p_dither_strength) { - dither_strength = p_dither_strength; - RS::get_singleton()->material_set_param(_get_material(), "dither_strength", dither_strength); +void PhysicalSkyMaterial::set_use_debanding(bool p_use_debanding) { + use_debanding = p_use_debanding; + RS::get_singleton()->material_set_param(_get_material(), "use_debanding", use_debanding); } -float PhysicalSkyMaterial::get_dither_strength() const { - return dither_strength; +bool PhysicalSkyMaterial::get_use_debanding() const { + return use_debanding; } void PhysicalSkyMaterial::set_night_sky(const Ref<Texture2D> &p_night_sky) { @@ -578,8 +605,8 @@ void PhysicalSkyMaterial::_bind_methods() { ClassDB::bind_method(D_METHOD("set_exposure", "exposure"), &PhysicalSkyMaterial::set_exposure); ClassDB::bind_method(D_METHOD("get_exposure"), &PhysicalSkyMaterial::get_exposure); - ClassDB::bind_method(D_METHOD("set_dither_strength", "strength"), &PhysicalSkyMaterial::set_dither_strength); - ClassDB::bind_method(D_METHOD("get_dither_strength"), &PhysicalSkyMaterial::get_dither_strength); + ClassDB::bind_method(D_METHOD("set_use_debanding", "use_debanding"), &PhysicalSkyMaterial::set_use_debanding); + ClassDB::bind_method(D_METHOD("get_use_debanding"), &PhysicalSkyMaterial::get_use_debanding); ClassDB::bind_method(D_METHOD("set_night_sky", "night_sky"), &PhysicalSkyMaterial::set_night_sky); ClassDB::bind_method(D_METHOD("get_night_sky"), &PhysicalSkyMaterial::get_night_sky); @@ -597,7 +624,7 @@ void PhysicalSkyMaterial::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "sun_disk_scale", PROPERTY_HINT_RANGE, "0,360,0.01"), "set_sun_disk_scale", "get_sun_disk_scale"); ADD_PROPERTY(PropertyInfo(Variant::COLOR, "ground_color", PROPERTY_HINT_COLOR_NO_ALPHA), "set_ground_color", "get_ground_color"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "exposure", PROPERTY_HINT_RANGE, "0,128,0.01"), "set_exposure", "get_exposure"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "dither_strength", PROPERTY_HINT_RANGE, "0,10,0.01"), "set_dither_strength", "get_dither_strength"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_debanding"), "set_use_debanding", "get_use_debanding"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "night_sky", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_night_sky", "get_night_sky"); } @@ -619,18 +646,18 @@ void PhysicalSkyMaterial::_update_shader() { shader_type sky; uniform float rayleigh : hint_range(0, 64) = 2.0; -uniform vec4 rayleigh_color : hint_color = vec4(0.3, 0.405, 0.6, 1.0); +uniform vec4 rayleigh_color : source_color = vec4(0.3, 0.405, 0.6, 1.0); uniform float mie : hint_range(0, 1) = 0.005; uniform float mie_eccentricity : hint_range(-1, 1) = 0.8; -uniform vec4 mie_color : hint_color = vec4(0.69, 0.729, 0.812, 1.0); +uniform vec4 mie_color : source_color = vec4(0.69, 0.729, 0.812, 1.0); uniform float turbidity : hint_range(0, 1000) = 10.0; uniform float sun_disk_scale : hint_range(0, 360) = 1.0; -uniform vec4 ground_color : hint_color = vec4(0.1, 0.07, 0.034, 1.0); +uniform vec4 ground_color : source_color = vec4(0.1, 0.07, 0.034, 1.0); uniform float exposure : hint_range(0, 128) = 0.1; -uniform float dither_strength : hint_range(0, 10) = 1.0; +uniform bool use_debanding = true; -uniform sampler2D night_sky : hint_black_albedo; +uniform sampler2D night_sky : source_color, hint_default_black; const vec3 UP = vec3( 0.0, 1.0, 0.0 ); @@ -646,11 +673,11 @@ float henyey_greenstein(float cos_theta, float g) { return k * (1.0 - g * g) / (pow(1.0 + g * g - 2.0 * g * cos_theta, 1.5)); } -// From: https://www.shadertoy.com/view/4sfGzS credit to iq -float hash(vec3 p) { - p = fract( p * 0.3183099 + 0.1 ); - p *= 17.0; - return fract(p.x * p.y * p.z * (p.x + p.y + p.z)); +// https://www.iryoku.com/next-generation-post-processing-in-call-of-duty-advanced-warfare +vec3 interleaved_gradient_noise(vec2 pos) { + const vec3 magic = vec3(0.06711056f, 0.00583715f, 52.9829189f); + float res = fract(magic.z * fract(dot(pos, magic.xy))) * 2.0 - 1.0; + return vec3(res, -res, res) / 255.0; } void sky() { @@ -700,8 +727,9 @@ void sky() { vec3 color = (Lin + L0) * 0.04; COLOR = pow(color, vec3(1.0 / (1.2 + (1.2 * sun_fade)))); COLOR *= exposure; - // Make optional, eliminates banding. - COLOR += (hash(EYEDIR * 1741.9782) * 0.08 - 0.04) * 0.016 * dither_strength; + if (use_debanding) { + COLOR += interleaved_gradient_noise(FRAGCOORD.xy); + } } else { // There is no sun, so display night_sky and nothing else. COLOR = texture(night_sky, SKY_COORDS).xyz * 0.04; @@ -724,7 +752,7 @@ PhysicalSkyMaterial::PhysicalSkyMaterial() { set_sun_disk_scale(1.0); set_ground_color(Color(0.1, 0.07, 0.034)); set_exposure(0.1); - set_dither_strength(1.0); + set_use_debanding(true); } PhysicalSkyMaterial::~PhysicalSkyMaterial() { diff --git a/scene/resources/sky_material.h b/scene/resources/sky_material.h index 5c791a185a..5be8922ba4 100644 --- a/scene/resources/sky_material.h +++ b/scene/resources/sky_material.h @@ -40,18 +40,19 @@ class ProceduralSkyMaterial : public Material { private: Color sky_top_color; Color sky_horizon_color; - float sky_curve; - float sky_energy; + float sky_curve = 0.0f; + float sky_energy = 0.0f; Ref<Texture2D> sky_cover; Color sky_cover_modulate; Color ground_bottom_color; Color ground_horizon_color; - float ground_curve; - float ground_energy; + float ground_curve = 0.0f; + float ground_energy = 0.0f; - float sun_angle_max; - float sun_curve; + float sun_angle_max = 0.0f; + float sun_curve = 0.0f; + bool use_debanding = true; static Mutex shader_mutex; static RID shader; @@ -98,6 +99,9 @@ public: void set_sun_curve(float p_curve); float get_sun_curve() const; + void set_use_debanding(bool p_use_debanding); + bool get_use_debanding() const; + virtual Shader::Mode get_shader_mode() const override; virtual RID get_shader_rid() const override; virtual RID get_rid() const override; @@ -154,16 +158,16 @@ private: static Mutex shader_mutex; static RID shader; - float rayleigh; + float rayleigh = 0.0f; Color rayleigh_color; - float mie; - float mie_eccentricity; + float mie = 0.0f; + float mie_eccentricity = 0.0f; Color mie_color; - float turbidity; - float sun_disk_scale; + float turbidity = 0.0f; + float sun_disk_scale = 0.0f; Color ground_color; - float exposure; - float dither_strength; + float exposure = 0.0f; + bool use_debanding = true; Ref<Texture2D> night_sky; static void _update_shader(); mutable bool shader_set = false; @@ -199,8 +203,8 @@ public: void set_exposure(float p_exposure); float get_exposure() const; - void set_dither_strength(float p_dither_strength); - float get_dither_strength() const; + void set_use_debanding(bool p_use_debanding); + bool get_use_debanding() const; void set_night_sky(const Ref<Texture2D> &p_night_sky); Ref<Texture2D> get_night_sky() const; diff --git a/scene/resources/sphere_shape_3d.cpp b/scene/resources/sphere_shape_3d.cpp index 8de0dc1650..92efe3ce6f 100644 --- a/scene/resources/sphere_shape_3d.cpp +++ b/scene/resources/sphere_shape_3d.cpp @@ -78,10 +78,10 @@ void SphereShape3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_radius", "radius"), &SphereShape3D::set_radius); ClassDB::bind_method(D_METHOD("get_radius"), &SphereShape3D::get_radius); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radius", PROPERTY_HINT_RANGE, "0.001,100,0.001,or_greater"), "set_radius", "get_radius"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radius", PROPERTY_HINT_RANGE, "0.001,100,0.001,or_greater,suffix:m"), "set_radius", "get_radius"); } SphereShape3D::SphereShape3D() : Shape3D(PhysicsServer3D::get_singleton()->shape_create(PhysicsServer3D::SHAPE_SPHERE)) { - set_radius(1.0); + set_radius(0.5); } diff --git a/scene/resources/sphere_shape_3d.h b/scene/resources/sphere_shape_3d.h index ff6d883940..8f77378ef4 100644 --- a/scene/resources/sphere_shape_3d.h +++ b/scene/resources/sphere_shape_3d.h @@ -35,7 +35,7 @@ class SphereShape3D : public Shape3D { GDCLASS(SphereShape3D, Shape3D); - float radius; + float radius = 0.5f; protected: static void _bind_methods(); diff --git a/scene/resources/sprite_frames.cpp b/scene/resources/sprite_frames.cpp index ece126791e..55c9d7397d 100644 --- a/scene/resources/sprite_frames.cpp +++ b/scene/resources/sprite_frames.cpp @@ -33,38 +33,38 @@ #include "scene/scene_string_names.h" void SpriteFrames::add_frame(const StringName &p_anim, const Ref<Texture2D> &p_frame, int p_at_pos) { - Map<StringName, Anim>::Element *E = animations.find(p_anim); + HashMap<StringName, Anim>::Iterator E = animations.find(p_anim); ERR_FAIL_COND_MSG(!E, "Animation '" + String(p_anim) + "' doesn't exist."); - if (p_at_pos >= 0 && p_at_pos < E->get().frames.size()) { - E->get().frames.insert(p_at_pos, p_frame); + if (p_at_pos >= 0 && p_at_pos < E->value.frames.size()) { + E->value.frames.insert(p_at_pos, p_frame); } else { - E->get().frames.push_back(p_frame); + E->value.frames.push_back(p_frame); } emit_changed(); } int SpriteFrames::get_frame_count(const StringName &p_anim) const { - const Map<StringName, Anim>::Element *E = animations.find(p_anim); + HashMap<StringName, Anim>::ConstIterator E = animations.find(p_anim); ERR_FAIL_COND_V_MSG(!E, 0, "Animation '" + String(p_anim) + "' doesn't exist."); - return E->get().frames.size(); + return E->value.frames.size(); } void SpriteFrames::remove_frame(const StringName &p_anim, int p_idx) { - Map<StringName, Anim>::Element *E = animations.find(p_anim); + HashMap<StringName, Anim>::Iterator E = animations.find(p_anim); ERR_FAIL_COND_MSG(!E, "Animation '" + String(p_anim) + "' doesn't exist."); - E->get().frames.remove_at(p_idx); + E->value.frames.remove_at(p_idx); emit_changed(); } void SpriteFrames::clear(const StringName &p_anim) { - Map<StringName, Anim>::Element *E = animations.find(p_anim); + HashMap<StringName, Anim>::Iterator E = animations.find(p_anim); ERR_FAIL_COND_MSG(!E, "Animation '" + String(p_anim) + "' doesn't exist."); - E->get().frames.clear(); + E->value.frames.clear(); emit_changed(); } @@ -96,17 +96,6 @@ void SpriteFrames::rename_animation(const StringName &p_prev, const StringName & animations[p_next] = anim; } -Vector<String> SpriteFrames::_get_animation_list() const { - Vector<String> ret; - List<StringName> al; - get_animation_list(&al); - for (const StringName &E : al) { - ret.push_back(E); - } - - return ret; -} - void SpriteFrames::get_animation_list(List<StringName> *r_animations) const { for (const KeyValue<StringName, Anim> &E : animations) { r_animations->push_back(E.key); @@ -124,54 +113,45 @@ Vector<String> SpriteFrames::get_animation_names() const { void SpriteFrames::set_animation_speed(const StringName &p_anim, double p_fps) { ERR_FAIL_COND_MSG(p_fps < 0, "Animation speed cannot be negative (" + itos(p_fps) + ")."); - Map<StringName, Anim>::Element *E = animations.find(p_anim); + HashMap<StringName, Anim>::Iterator E = animations.find(p_anim); ERR_FAIL_COND_MSG(!E, "Animation '" + String(p_anim) + "' doesn't exist."); - E->get().speed = p_fps; + E->value.speed = p_fps; } double SpriteFrames::get_animation_speed(const StringName &p_anim) const { - const Map<StringName, Anim>::Element *E = animations.find(p_anim); + HashMap<StringName, Anim>::ConstIterator E = animations.find(p_anim); ERR_FAIL_COND_V_MSG(!E, 0, "Animation '" + String(p_anim) + "' doesn't exist."); - return E->get().speed; + return E->value.speed; } void SpriteFrames::set_animation_loop(const StringName &p_anim, bool p_loop) { - Map<StringName, Anim>::Element *E = animations.find(p_anim); + HashMap<StringName, Anim>::Iterator E = animations.find(p_anim); ERR_FAIL_COND_MSG(!E, "Animation '" + String(p_anim) + "' doesn't exist."); - E->get().loop = p_loop; + E->value.loop = p_loop; } bool SpriteFrames::get_animation_loop(const StringName &p_anim) const { - const Map<StringName, Anim>::Element *E = animations.find(p_anim); + HashMap<StringName, Anim>::ConstIterator E = animations.find(p_anim); ERR_FAIL_COND_V_MSG(!E, false, "Animation '" + String(p_anim) + "' doesn't exist."); - return E->get().loop; -} - -void SpriteFrames::_set_frames(const Array &p_frames) { - clear_all(); - Map<StringName, Anim>::Element *E = animations.find(SceneStringNames::get_singleton()->_default); - ERR_FAIL_COND(!E); - - E->get().frames.resize(p_frames.size()); - for (int i = 0; i < E->get().frames.size(); i++) { - E->get().frames.write[i] = p_frames[i]; - } -} - -Array SpriteFrames::_get_frames() const { - return Array(); + return E->value.loop; } Array SpriteFrames::_get_animations() const { Array anims; - for (const KeyValue<StringName, Anim> &E : animations) { + + List<StringName> sorted_names; + get_animation_list(&sorted_names); + sorted_names.sort_custom<StringName::AlphCompare>(); + + for (const StringName &name : sorted_names) { + const Anim &anim = animations[name]; Dictionary d; - d["name"] = E.key; - d["speed"] = E.value.speed; - d["loop"] = E.value.loop; + d["name"] = name; + d["speed"] = anim.speed; + d["loop"] = anim.loop; Array frames; - for (int i = 0; i < E.value.frames.size(); i++) { - frames.push_back(E.value.frames[i]); + for (int i = 0; i < anim.frames.size(); i++) { + frames.push_back(anim.frames[i]); } d["frames"] = frames; anims.push_back(d); @@ -195,7 +175,7 @@ void SpriteFrames::_set_animations(const Array &p_animations) { anim.loop = d["loop"]; Array frames = d["frames"]; for (int j = 0; j < frames.size(); j++) { - RES res = frames[j]; + Ref<Resource> res = frames[j]; anim.frames.push_back(res); } @@ -225,15 +205,12 @@ void SpriteFrames::_bind_methods() { ClassDB::bind_method(D_METHOD("clear", "anim"), &SpriteFrames::clear); ClassDB::bind_method(D_METHOD("clear_all"), &SpriteFrames::clear_all); - ClassDB::bind_method(D_METHOD("_set_frames"), &SpriteFrames::_set_frames); - ClassDB::bind_method(D_METHOD("_get_frames"), &SpriteFrames::_get_frames); - - ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "frames", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "_set_frames", "_get_frames"); //compatibility + // `animations` property is for serialization. ClassDB::bind_method(D_METHOD("_set_animations"), &SpriteFrames::_set_animations); ClassDB::bind_method(D_METHOD("_get_animations"), &SpriteFrames::_get_animations); - ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "animations", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_animations", "_get_animations"); //compatibility + ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "animations", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_animations", "_get_animations"); } SpriteFrames::SpriteFrames() { diff --git a/scene/resources/sprite_frames.h b/scene/resources/sprite_frames.h index 12b69afde1..87d84b70c0 100644 --- a/scene/resources/sprite_frames.h +++ b/scene/resources/sprite_frames.h @@ -42,16 +42,11 @@ class SpriteFrames : public Resource { Vector<Ref<Texture2D>> frames; }; - Map<StringName, Anim> animations; - - Array _get_frames() const; - void _set_frames(const Array &p_frames); + HashMap<StringName, Anim> animations; Array _get_animations() const; void _set_animations(const Array &p_animations); - Vector<String> _get_animation_list() const; - protected: static void _bind_methods(); @@ -73,24 +68,24 @@ public: void add_frame(const StringName &p_anim, const Ref<Texture2D> &p_frame, int p_at_pos = -1); int get_frame_count(const StringName &p_anim) const; _FORCE_INLINE_ Ref<Texture2D> get_frame(const StringName &p_anim, int p_idx) const { - const Map<StringName, Anim>::Element *E = animations.find(p_anim); + HashMap<StringName, Anim>::ConstIterator E = animations.find(p_anim); ERR_FAIL_COND_V_MSG(!E, Ref<Texture2D>(), "Animation '" + String(p_anim) + "' doesn't exist."); ERR_FAIL_COND_V(p_idx < 0, Ref<Texture2D>()); - if (p_idx >= E->get().frames.size()) { + if (p_idx >= E->value.frames.size()) { return Ref<Texture2D>(); } - return E->get().frames[p_idx]; + return E->value.frames[p_idx]; } void set_frame(const StringName &p_anim, int p_idx, const Ref<Texture2D> &p_frame) { - Map<StringName, Anim>::Element *E = animations.find(p_anim); + HashMap<StringName, Anim>::Iterator E = animations.find(p_anim); ERR_FAIL_COND_MSG(!E, "Animation '" + String(p_anim) + "' doesn't exist."); ERR_FAIL_COND(p_idx < 0); - if (p_idx >= E->get().frames.size()) { + if (p_idx >= E->value.frames.size()) { return; } - E->get().frames.write[p_idx] = p_frame; + E->value.frames.write[p_idx] = p_frame; } void remove_frame(const StringName &p_anim, int p_idx); void clear(const StringName &p_anim); diff --git a/scene/resources/style_box.cpp b/scene/resources/style_box.cpp index fe52761482..a53c299d00 100644 --- a/scene/resources/style_box.cpp +++ b/scene/resources/style_box.cpp @@ -122,11 +122,11 @@ void StyleBox::_bind_methods() { ClassDB::bind_method(D_METHOD("draw", "canvas_item", "rect"), &StyleBox::draw); - ADD_GROUP("Content Margin", "content_margin_"); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "content_margin_left", PROPERTY_HINT_RANGE, "-1,2048,1"), "set_default_margin", "get_default_margin", SIDE_LEFT); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "content_margin_right", PROPERTY_HINT_RANGE, "-1,2048,1"), "set_default_margin", "get_default_margin", SIDE_RIGHT); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "content_margin_top", PROPERTY_HINT_RANGE, "-1,2048,1"), "set_default_margin", "get_default_margin", SIDE_TOP); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "content_margin_bottom", PROPERTY_HINT_RANGE, "-1,2048,1"), "set_default_margin", "get_default_margin", SIDE_BOTTOM); + ADD_GROUP("Content Margins", "content_margin_"); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "content_margin_left", PROPERTY_HINT_RANGE, "-1,2048,1,suffix:px"), "set_default_margin", "get_default_margin", SIDE_LEFT); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "content_margin_top", PROPERTY_HINT_RANGE, "-1,2048,1,suffix:px"), "set_default_margin", "get_default_margin", SIDE_TOP); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "content_margin_right", PROPERTY_HINT_RANGE, "-1,2048,1,suffix:px"), "set_default_margin", "get_default_margin", SIDE_RIGHT); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "content_margin_bottom", PROPERTY_HINT_RANGE, "-1,2048,1,suffix:px"), "set_default_margin", "get_default_margin", SIDE_BOTTOM); GDVIRTUAL_BIND(_get_style_margin, "side") GDVIRTUAL_BIND(_test_mask, "point", "rect") @@ -315,20 +315,26 @@ void StyleBoxTexture::_bind_methods() { ClassDB::bind_method(D_METHOD("get_v_axis_stretch_mode"), &StyleBoxTexture::get_v_axis_stretch_mode); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_texture", "get_texture"); - ADD_PROPERTY(PropertyInfo(Variant::RECT2, "region_rect"), "set_region_rect", "get_region_rect"); - ADD_GROUP("Margin", "margin_"); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "margin_left", PROPERTY_HINT_RANGE, "0,2048,1"), "set_margin_size", "get_margin_size", SIDE_LEFT); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "margin_right", PROPERTY_HINT_RANGE, "0,2048,1"), "set_margin_size", "get_margin_size", SIDE_RIGHT); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "margin_top", PROPERTY_HINT_RANGE, "0,2048,1"), "set_margin_size", "get_margin_size", SIDE_TOP); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "margin_bottom", PROPERTY_HINT_RANGE, "0,2048,1"), "set_margin_size", "get_margin_size", SIDE_BOTTOM); - ADD_GROUP("Expand Margin", "expand_margin_"); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "expand_margin_left", PROPERTY_HINT_RANGE, "0,2048,1"), "set_expand_margin_size", "get_expand_margin_size", SIDE_LEFT); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "expand_margin_right", PROPERTY_HINT_RANGE, "0,2048,1"), "set_expand_margin_size", "get_expand_margin_size", SIDE_RIGHT); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "expand_margin_top", PROPERTY_HINT_RANGE, "0,2048,1"), "set_expand_margin_size", "get_expand_margin_size", SIDE_TOP); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "expand_margin_bottom", PROPERTY_HINT_RANGE, "0,2048,1"), "set_expand_margin_size", "get_expand_margin_size", SIDE_BOTTOM); + + ADD_GROUP("Margins", "margin_"); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "margin_left", PROPERTY_HINT_RANGE, "0,2048,1,suffix:px"), "set_margin_size", "get_margin_size", SIDE_LEFT); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "margin_top", PROPERTY_HINT_RANGE, "0,2048,1,suffix:px"), "set_margin_size", "get_margin_size", SIDE_TOP); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "margin_right", PROPERTY_HINT_RANGE, "0,2048,1,suffix:px"), "set_margin_size", "get_margin_size", SIDE_RIGHT); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "margin_bottom", PROPERTY_HINT_RANGE, "0,2048,1,suffix:px"), "set_margin_size", "get_margin_size", SIDE_BOTTOM); + + ADD_GROUP("Expand Margins", "expand_margin_"); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "expand_margin_left", PROPERTY_HINT_RANGE, "0,2048,1,suffix:px"), "set_expand_margin_size", "get_expand_margin_size", SIDE_LEFT); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "expand_margin_top", PROPERTY_HINT_RANGE, "0,2048,1,suffix:px"), "set_expand_margin_size", "get_expand_margin_size", SIDE_TOP); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "expand_margin_right", PROPERTY_HINT_RANGE, "0,2048,1,suffix:px"), "set_expand_margin_size", "get_expand_margin_size", SIDE_RIGHT); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "expand_margin_bottom", PROPERTY_HINT_RANGE, "0,2048,1,suffix:px"), "set_expand_margin_size", "get_expand_margin_size", SIDE_BOTTOM); + ADD_GROUP("Axis Stretch", "axis_stretch_"); ADD_PROPERTY(PropertyInfo(Variant::INT, "axis_stretch_horizontal", PROPERTY_HINT_ENUM, "Stretch,Tile,Tile Fit"), "set_h_axis_stretch_mode", "get_h_axis_stretch_mode"); ADD_PROPERTY(PropertyInfo(Variant::INT, "axis_stretch_vertical", PROPERTY_HINT_ENUM, "Stretch,Tile,Tile Fit"), "set_v_axis_stretch_mode", "get_v_axis_stretch_mode"); + + ADD_GROUP("Sub-Region", "region_"); + ADD_PROPERTY(PropertyInfo(Variant::RECT2, "region_rect", PROPERTY_HINT_NONE, "suffix:px"), "set_region_rect", "get_region_rect"); + ADD_GROUP("Modulate", "modulate_"); ADD_PROPERTY(PropertyInfo(Variant::COLOR, "modulate_color"), "set_modulate", "get_modulate"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "draw_center"), "set_draw_center", "is_draw_center_enabled"); @@ -457,6 +463,15 @@ bool StyleBoxFlat::is_draw_center_enabled() const { return draw_center; } +void StyleBoxFlat::set_skew(Vector2 p_skew) { + skew = p_skew; + emit_changed(); +} + +Vector2 StyleBoxFlat::get_skew() const { + return skew; +} + void StyleBoxFlat::set_shadow_color(const Color &p_color) { shadow_color = p_color; emit_changed(); @@ -542,7 +557,7 @@ inline void set_inner_corner_radius(const Rect2 style_rect, const Rect2 inner_re } inline void draw_ring(Vector<Vector2> &verts, Vector<int> &indices, Vector<Color> &colors, const Rect2 &style_rect, const real_t corner_radius[4], - const Rect2 &ring_rect, const Rect2 &inner_rect, const Color &inner_color, const Color &outer_color, const int corner_detail, const bool fill_center = false) { + const Rect2 &ring_rect, const Rect2 &inner_rect, const Color &inner_color, const Color &outer_color, const int corner_detail, const Vector2 &skew, bool fill_center = false) { int vert_offset = verts.size(); if (!vert_offset) { vert_offset = 0; @@ -586,9 +601,12 @@ inline void draw_ring(Vector<Vector2> &verts, Vector<int> &indices, Vector<Color color = outer_color; corner_point = outer_points[corner_index]; } - real_t x = radius * (real_t)cos((corner_index + detail / (double)adapted_corner_detail) * (Math_TAU / 4.0) + Math_PI) + corner_point.x; - real_t y = radius * (real_t)sin((corner_index + detail / (double)adapted_corner_detail) * (Math_TAU / 4.0) + Math_PI) + corner_point.y; - verts.push_back(Vector2(x, y)); + + const real_t x = radius * (real_t)cos((corner_index + detail / (double)adapted_corner_detail) * (Math_TAU / 4.0) + Math_PI) + corner_point.x; + const real_t y = radius * (real_t)sin((corner_index + detail / (double)adapted_corner_detail) * (Math_TAU / 4.0) + Math_PI) + corner_point.y; + const float x_skew = -skew.x * (y - ring_rect.get_center().y); + const float y_skew = -skew.y * (x - ring_rect.get_center().x); + verts.push_back(Vector2(x + x_skew, y + y_skew)); colors.push_back(color); } } @@ -666,10 +684,12 @@ void StyleBoxFlat::draw(RID p_canvas_item, const Rect2 &p_rect) const { return; } - bool rounded_corners = (corner_radius[0] > 0) || (corner_radius[1] > 0) || (corner_radius[2] > 0) || (corner_radius[3] > 0); - bool aa_on = rounded_corners && anti_aliased; + const bool rounded_corners = (corner_radius[0] > 0) || (corner_radius[1] > 0) || (corner_radius[2] > 0) || (corner_radius[3] > 0); + // Only enable antialiasing if it is actually needed. This improve performances + // and maximizes sharpness for non-skewed StyleBoxes with sharp corners. + const bool aa_on = (rounded_corners || !skew.is_equal_approx(Vector2())) && anti_aliased; - bool blend_on = blend_border && draw_border; + const bool blend_on = blend_border && draw_border; Color border_color_alpha = Color(border_color.r, border_color.g, border_color.b, 0); Color border_color_blend = (draw_center ? bg_color : border_color_alpha); @@ -716,24 +736,24 @@ void StyleBoxFlat::draw(RID p_canvas_item, const Rect2 &p_rect) const { Color shadow_color_transparent = Color(shadow_color.r, shadow_color.g, shadow_color.b, 0); draw_ring(verts, indices, colors, shadow_inner_rect, adapted_corner, - shadow_rect, shadow_inner_rect, shadow_color, shadow_color_transparent, corner_detail); + shadow_rect, shadow_inner_rect, shadow_color, shadow_color_transparent, corner_detail, skew); if (draw_center) { draw_ring(verts, indices, colors, shadow_inner_rect, adapted_corner, - shadow_inner_rect, shadow_inner_rect, shadow_color, shadow_color, corner_detail, true); + shadow_inner_rect, shadow_inner_rect, shadow_color, shadow_color, corner_detail, skew, true); } } // Create border (no AA). if (draw_border && !aa_on) { draw_ring(verts, indices, colors, border_style_rect, adapted_corner, - border_style_rect, infill_rect, border_color_inner, border_color, corner_detail); + border_style_rect, infill_rect, border_color_inner, border_color, corner_detail, skew); } // Create infill (no AA). if (draw_center && (!aa_on || blend_on || !draw_border)) { draw_ring(verts, indices, colors, border_style_rect, adapted_corner, - infill_rect, infill_rect, bg_color, bg_color, corner_detail, true); + infill_rect, infill_rect, bg_color, bg_color, corner_detail, skew, true); } if (aa_on) { @@ -765,7 +785,7 @@ void StyleBoxFlat::draw(RID p_canvas_item, const Rect2 &p_rect) const { aa_border_width[SIDE_RIGHT], aa_border_width[SIDE_BOTTOM]); // Create infill within AA border. draw_ring(verts, indices, colors, border_style_rect, adapted_corner, - infill_inner_rect_aa, infill_inner_rect_aa, bg_color, bg_color, corner_detail, true); + infill_inner_rect_aa, infill_inner_rect_aa, bg_color, bg_color, corner_detail, skew, true); } if (!blend_on || !draw_border) { @@ -776,7 +796,7 @@ void StyleBoxFlat::draw(RID p_canvas_item, const Rect2 &p_rect) const { // Create infill fake AA gradient. draw_ring(verts, indices, colors, style_rect, adapted_corner, - infill_rect_aa, infill_rect, bg_color, alpha_bg, corner_detail); + infill_rect_aa, infill_rect, bg_color, alpha_bg, corner_detail, skew); } } @@ -790,17 +810,17 @@ void StyleBoxFlat::draw(RID p_canvas_item, const Rect2 &p_rect) const { // Create border. draw_ring(verts, indices, colors, border_style_rect, adapted_corner, - border_style_rect_aa, ((blend_on) ? infill_rect : infill_rect_aa), border_color_inner, border_color, corner_detail); + border_style_rect_aa, ((blend_on) ? infill_rect : infill_rect_aa), border_color_inner, border_color, corner_detail, skew); if (!blend_on) { // Create inner border fake AA gradient. draw_ring(verts, indices, colors, border_style_rect, adapted_corner, - infill_rect_aa, infill_rect, border_color_blend, border_color, corner_detail); + infill_rect_aa, infill_rect, border_color_blend, border_color, corner_detail, skew); } // Create outer border fake AA gradient. draw_ring(verts, indices, colors, border_style_rect, adapted_corner, - style_rect_aa, border_style_rect_aa, border_color, border_color_alpha, corner_detail); + style_rect_aa, border_style_rect_aa, border_color, border_color_alpha, corner_detail, skew); } } @@ -858,6 +878,9 @@ void StyleBoxFlat::_bind_methods() { ClassDB::bind_method(D_METHOD("set_draw_center", "draw_center"), &StyleBoxFlat::set_draw_center); ClassDB::bind_method(D_METHOD("is_draw_center_enabled"), &StyleBoxFlat::is_draw_center_enabled); + ClassDB::bind_method(D_METHOD("set_skew", "skew"), &StyleBoxFlat::set_skew); + ClassDB::bind_method(D_METHOD("get_skew"), &StyleBoxFlat::get_skew); + ClassDB::bind_method(D_METHOD("set_shadow_color", "color"), &StyleBoxFlat::set_shadow_color); ClassDB::bind_method(D_METHOD("get_shadow_color"), &StyleBoxFlat::get_shadow_color); @@ -879,12 +902,13 @@ void StyleBoxFlat::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::COLOR, "bg_color"), "set_bg_color", "get_bg_color"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "draw_center"), "set_draw_center", "is_draw_center_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "skew"), "set_skew", "get_skew"); ADD_GROUP("Border Width", "border_width_"); - ADD_PROPERTYI(PropertyInfo(Variant::INT, "border_width_left", PROPERTY_HINT_RANGE, "0,1024,1"), "set_border_width", "get_border_width", SIDE_LEFT); - ADD_PROPERTYI(PropertyInfo(Variant::INT, "border_width_top", PROPERTY_HINT_RANGE, "0,1024,1"), "set_border_width", "get_border_width", SIDE_TOP); - ADD_PROPERTYI(PropertyInfo(Variant::INT, "border_width_right", PROPERTY_HINT_RANGE, "0,1024,1"), "set_border_width", "get_border_width", SIDE_RIGHT); - ADD_PROPERTYI(PropertyInfo(Variant::INT, "border_width_bottom", PROPERTY_HINT_RANGE, "0,1024,1"), "set_border_width", "get_border_width", SIDE_BOTTOM); + ADD_PROPERTYI(PropertyInfo(Variant::INT, "border_width_left", PROPERTY_HINT_RANGE, "0,1024,1,suffix:px"), "set_border_width", "get_border_width", SIDE_LEFT); + ADD_PROPERTYI(PropertyInfo(Variant::INT, "border_width_top", PROPERTY_HINT_RANGE, "0,1024,1,suffix:px"), "set_border_width", "get_border_width", SIDE_TOP); + ADD_PROPERTYI(PropertyInfo(Variant::INT, "border_width_right", PROPERTY_HINT_RANGE, "0,1024,1,suffix:px"), "set_border_width", "get_border_width", SIDE_RIGHT); + ADD_PROPERTYI(PropertyInfo(Variant::INT, "border_width_bottom", PROPERTY_HINT_RANGE, "0,1024,1,suffix:px"), "set_border_width", "get_border_width", SIDE_BOTTOM); ADD_GROUP("Border", "border_"); ADD_PROPERTY(PropertyInfo(Variant::COLOR, "border_color"), "set_border_color", "get_border_color"); @@ -892,27 +916,27 @@ void StyleBoxFlat::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "border_blend"), "set_border_blend", "get_border_blend"); ADD_GROUP("Corner Radius", "corner_radius_"); - ADD_PROPERTYI(PropertyInfo(Variant::INT, "corner_radius_top_left", PROPERTY_HINT_RANGE, "0,1024,1"), "set_corner_radius", "get_corner_radius", CORNER_TOP_LEFT); - ADD_PROPERTYI(PropertyInfo(Variant::INT, "corner_radius_top_right", PROPERTY_HINT_RANGE, "0,1024,1"), "set_corner_radius", "get_corner_radius", CORNER_TOP_RIGHT); - ADD_PROPERTYI(PropertyInfo(Variant::INT, "corner_radius_bottom_right", PROPERTY_HINT_RANGE, "0,1024,1"), "set_corner_radius", "get_corner_radius", CORNER_BOTTOM_RIGHT); - ADD_PROPERTYI(PropertyInfo(Variant::INT, "corner_radius_bottom_left", PROPERTY_HINT_RANGE, "0,1024,1"), "set_corner_radius", "get_corner_radius", CORNER_BOTTOM_LEFT); + ADD_PROPERTYI(PropertyInfo(Variant::INT, "corner_radius_top_left", PROPERTY_HINT_RANGE, "0,1024,1,suffix:px"), "set_corner_radius", "get_corner_radius", CORNER_TOP_LEFT); + ADD_PROPERTYI(PropertyInfo(Variant::INT, "corner_radius_top_right", PROPERTY_HINT_RANGE, "0,1024,1,suffix:px"), "set_corner_radius", "get_corner_radius", CORNER_TOP_RIGHT); + ADD_PROPERTYI(PropertyInfo(Variant::INT, "corner_radius_bottom_right", PROPERTY_HINT_RANGE, "0,1024,1,suffix:px"), "set_corner_radius", "get_corner_radius", CORNER_BOTTOM_RIGHT); + ADD_PROPERTYI(PropertyInfo(Variant::INT, "corner_radius_bottom_left", PROPERTY_HINT_RANGE, "0,1024,1,suffix:px"), "set_corner_radius", "get_corner_radius", CORNER_BOTTOM_LEFT); ADD_PROPERTY(PropertyInfo(Variant::INT, "corner_detail", PROPERTY_HINT_RANGE, "1,20,1"), "set_corner_detail", "get_corner_detail"); - ADD_GROUP("Expand Margin", "expand_margin_"); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "expand_margin_left", PROPERTY_HINT_RANGE, "0,2048,1"), "set_expand_margin", "get_expand_margin", SIDE_LEFT); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "expand_margin_right", PROPERTY_HINT_RANGE, "0,2048,1"), "set_expand_margin", "get_expand_margin", SIDE_RIGHT); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "expand_margin_top", PROPERTY_HINT_RANGE, "0,2048,1"), "set_expand_margin", "get_expand_margin", SIDE_TOP); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "expand_margin_bottom", PROPERTY_HINT_RANGE, "0,2048,1"), "set_expand_margin", "get_expand_margin", SIDE_BOTTOM); + ADD_GROUP("Expand Margins", "expand_margin_"); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "expand_margin_left", PROPERTY_HINT_RANGE, "0,2048,1,suffix:px"), "set_expand_margin", "get_expand_margin", SIDE_LEFT); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "expand_margin_top", PROPERTY_HINT_RANGE, "0,2048,1,suffix:px"), "set_expand_margin", "get_expand_margin", SIDE_TOP); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "expand_margin_right", PROPERTY_HINT_RANGE, "0,2048,1,suffix:px"), "set_expand_margin", "get_expand_margin", SIDE_RIGHT); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "expand_margin_bottom", PROPERTY_HINT_RANGE, "0,2048,1,suffix:px"), "set_expand_margin", "get_expand_margin", SIDE_BOTTOM); ADD_GROUP("Shadow", "shadow_"); ADD_PROPERTY(PropertyInfo(Variant::COLOR, "shadow_color"), "set_shadow_color", "get_shadow_color"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "shadow_size", PROPERTY_HINT_RANGE, "0,100,1,or_greater"), "set_shadow_size", "get_shadow_size"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "shadow_offset"), "set_shadow_offset", "get_shadow_offset"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "shadow_size", PROPERTY_HINT_RANGE, "0,100,1,or_greater,suffix:px"), "set_shadow_size", "get_shadow_size"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "shadow_offset", PROPERTY_HINT_NONE, "suffix:px"), "set_shadow_offset", "get_shadow_offset"); ADD_GROUP("Anti Aliasing", "anti_aliasing_"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "anti_aliasing"), "set_anti_aliased", "is_anti_aliased"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "anti_aliasing_size", PROPERTY_HINT_RANGE, "0.01,10,0.001"), "set_aa_size", "get_aa_size"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "anti_aliasing_size", PROPERTY_HINT_RANGE, "0.01,10,0.001,suffix:px"), "set_aa_size", "get_aa_size"); } StyleBoxFlat::StyleBoxFlat() {} @@ -977,9 +1001,9 @@ void StyleBoxLine::_bind_methods() { ClassDB::bind_method(D_METHOD("is_vertical"), &StyleBoxLine::is_vertical); ADD_PROPERTY(PropertyInfo(Variant::COLOR, "color"), "set_color", "get_color"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "grow_begin", PROPERTY_HINT_RANGE, "-300,300,1"), "set_grow_begin", "get_grow_begin"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "grow_end", PROPERTY_HINT_RANGE, "-300,300,1"), "set_grow_end", "get_grow_end"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "thickness", PROPERTY_HINT_RANGE, "0,10"), "set_thickness", "get_thickness"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "grow_begin", PROPERTY_HINT_RANGE, "-300,300,1,suffix:px"), "set_grow_begin", "get_grow_begin"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "grow_end", PROPERTY_HINT_RANGE, "-300,300,1,suffix:px"), "set_grow_end", "get_grow_end"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "thickness", PROPERTY_HINT_RANGE, "0,10,suffix:px"), "set_thickness", "get_thickness"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "vertical"), "set_vertical", "is_vertical"); } diff --git a/scene/resources/style_box.h b/scene/resources/style_box.h index 68ad41b69c..3b3654775f 100644 --- a/scene/resources/style_box.h +++ b/scene/resources/style_box.h @@ -155,6 +155,7 @@ class StyleBoxFlat : public StyleBox { bool draw_center = true; bool blend_border = false; + Vector2 skew; bool anti_aliased = true; int corner_detail = 8; @@ -200,6 +201,9 @@ public: void set_draw_center(bool p_enabled); bool is_draw_center_enabled() const; + void set_skew(Vector2 p_skew); + Vector2 get_skew() const; + void set_shadow_color(const Color &p_color); Color get_shadow_color() const; diff --git a/scene/resources/surface_tool.cpp b/scene/resources/surface_tool.cpp index 8ff1fde2cf..9829c7e86b 100644 --- a/scene/resources/surface_tool.cpp +++ b/scene/resources/surface_tool.cpp @@ -141,7 +141,8 @@ uint32_t SurfaceTool::VertexHasher::hash(const Vertex &p_vtx) { h = hash_djb2_buffer((const uint8_t *)p_vtx.bones.ptr(), p_vtx.bones.size() * sizeof(int), h); h = hash_djb2_buffer((const uint8_t *)p_vtx.weights.ptr(), p_vtx.weights.size() * sizeof(float), h); h = hash_djb2_buffer((const uint8_t *)&p_vtx.custom[0], sizeof(Color) * RS::ARRAY_CUSTOM_COUNT, h); - h = hash_djb2_one_32(p_vtx.smooth_group, h); + h = hash_murmur3_one_32(p_vtx.smooth_group, h); + h = hash_fmix32(h); return h; } @@ -315,19 +316,17 @@ void SurfaceTool::set_uv2(const Vector2 &p_uv2) { last_uv2 = p_uv2; } -void SurfaceTool::set_custom(int p_index, const Color &p_custom) { - ERR_FAIL_INDEX(p_index, RS::ARRAY_CUSTOM_COUNT); +void SurfaceTool::set_custom(int p_channel_index, const Color &p_custom) { + ERR_FAIL_INDEX(p_channel_index, RS::ARRAY_CUSTOM_COUNT); ERR_FAIL_COND(!begun); - ERR_FAIL_COND(last_custom_format[p_index] == CUSTOM_MAX); + ERR_FAIL_COND(last_custom_format[p_channel_index] == CUSTOM_MAX); static const uint32_t mask[RS::ARRAY_CUSTOM_COUNT] = { Mesh::ARRAY_FORMAT_CUSTOM0, Mesh::ARRAY_FORMAT_CUSTOM1, Mesh::ARRAY_FORMAT_CUSTOM2, Mesh::ARRAY_FORMAT_CUSTOM3 }; - static const uint32_t shift[RS::ARRAY_CUSTOM_COUNT] = { Mesh::ARRAY_FORMAT_CUSTOM0_SHIFT, Mesh::ARRAY_FORMAT_CUSTOM1_SHIFT, Mesh::ARRAY_FORMAT_CUSTOM2_SHIFT, Mesh::ARRAY_FORMAT_CUSTOM3_SHIFT }; - ERR_FAIL_COND(!first && !(format & mask[p_index])); + ERR_FAIL_COND(!first && !(format & mask[p_channel_index])); if (first) { - format |= mask[p_index]; - format |= last_custom_format[p_index] << shift[p_index]; + format |= mask[p_channel_index]; } - last_custom[p_index] = p_custom; + last_custom[p_channel_index] = p_custom; } void SurfaceTool::set_bones(const Vector<int> &p_bones) { @@ -689,7 +688,7 @@ Array SurfaceTool::commit_to_arrays() { return a; } -Ref<ArrayMesh> SurfaceTool::commit(const Ref<ArrayMesh> &p_existing, uint32_t p_flags) { +Ref<ArrayMesh> SurfaceTool::commit(const Ref<ArrayMesh> &p_existing, uint32_t p_compress_flags) { Ref<ArrayMesh> mesh; if (p_existing.is_valid()) { mesh = p_existing; @@ -707,7 +706,15 @@ Ref<ArrayMesh> SurfaceTool::commit(const Ref<ArrayMesh> &p_existing, uint32_t p_ Array a = commit_to_arrays(); - mesh->add_surface_from_arrays(primitive, a, Array(), Dictionary(), p_flags); + uint32_t compress_flags = (p_compress_flags >> RS::ARRAY_COMPRESS_FLAGS_BASE) << RS::ARRAY_COMPRESS_FLAGS_BASE; + static const uint32_t shift[RS::ARRAY_CUSTOM_COUNT] = { Mesh::ARRAY_FORMAT_CUSTOM0_SHIFT, Mesh::ARRAY_FORMAT_CUSTOM1_SHIFT, Mesh::ARRAY_FORMAT_CUSTOM2_SHIFT, Mesh::ARRAY_FORMAT_CUSTOM3_SHIFT }; + for (int i = 0; i < RS::ARRAY_CUSTOM_COUNT; i++) { + if (last_custom_format[i] != CUSTOM_MAX) { + compress_flags |= last_custom_format[i] << shift[i]; + } + } + + mesh->add_surface_from_arrays(primitive, a, Array(), Dictionary(), compress_flags); if (material.is_valid()) { mesh->surface_set_material(surface, material); @@ -988,9 +995,6 @@ void SurfaceTool::append_from(const Ref<Mesh> &p_existing, int p_surface, const for (int j = 0; j < RS::ARRAY_CUSTOM_COUNT; j++) { if (format & custom_mask[j]) { CustomFormat new_format = (CustomFormat)((format >> custom_shift[j]) & RS::ARRAY_FORMAT_CUSTOM_MASK); - if (last_custom_format[j] != CUSTOM_MAX && last_custom_format[j] != new_format) { - WARN_PRINT(vformat("Custom %d format %d mismatch when appending format %d", j, last_custom_format[j], new_format)); - } last_custom_format[j] = new_format; } } @@ -1165,7 +1169,7 @@ void SurfaceTool::generate_normals(bool p_flip) { for (int i = 0; i < 3; i++) { Vector3 *lv = vertex_hash.getptr(v[i]); if (!lv) { - vertex_hash.set(v[i], normal); + vertex_hash.insert(v[i], normal); } else { (*lv) += normal; } @@ -1220,22 +1224,24 @@ SurfaceTool::SkinWeightCount SurfaceTool::get_skin_weight_count() const { return skin_weights; } -void SurfaceTool::set_custom_format(int p_index, CustomFormat p_format) { - ERR_FAIL_INDEX(p_index, RS::ARRAY_CUSTOM_COUNT); - ERR_FAIL_COND(begun); - last_custom_format[p_index] = p_format; +void SurfaceTool::set_custom_format(int p_channel_index, CustomFormat p_format) { + ERR_FAIL_INDEX(p_channel_index, RS::ARRAY_CUSTOM_COUNT); + ERR_FAIL_COND(!begun); + ERR_FAIL_INDEX(p_format, CUSTOM_MAX + 1); + last_custom_format[p_channel_index] = p_format; } -Mesh::PrimitiveType SurfaceTool::get_primitive() const { +Mesh::PrimitiveType SurfaceTool::get_primitive_type() const { return primitive; } -SurfaceTool::CustomFormat SurfaceTool::get_custom_format(int p_index) const { - ERR_FAIL_INDEX_V(p_index, RS::ARRAY_CUSTOM_COUNT, CUSTOM_MAX); - return last_custom_format[p_index]; +SurfaceTool::CustomFormat SurfaceTool::get_custom_format(int p_channel_index) const { + ERR_FAIL_INDEX_V(p_channel_index, RS::ARRAY_CUSTOM_COUNT, CUSTOM_MAX); + return last_custom_format[p_channel_index]; } void SurfaceTool::optimize_indices_for_cache() { ERR_FAIL_COND(optimize_vertex_cache_func == nullptr); ERR_FAIL_COND(index_array.size() == 0); + ERR_FAIL_COND(primitive != Mesh::PRIMITIVE_TRIANGLES); ERR_FAIL_COND(index_array.size() % 3 != 0); LocalVector old_index_array = index_array; @@ -1243,8 +1249,8 @@ void SurfaceTool::optimize_indices_for_cache() { optimize_vertex_cache_func((unsigned int *)index_array.ptr(), (unsigned int *)old_index_array.ptr(), old_index_array.size(), vertex_array.size()); } -float SurfaceTool::get_max_axis_length() const { - ERR_FAIL_COND_V(vertex_array.size() == 0, 0); +AABB SurfaceTool::get_aabb() const { + ERR_FAIL_COND_V(vertex_array.size() == 0, AABB()); AABB aabb; for (uint32_t i = 0; i < vertex_array.size(); i++) { @@ -1255,7 +1261,7 @@ float SurfaceTool::get_max_axis_length() const { } } - return aabb.get_longest_axis_size(); + return aabb; } Vector<int> SurfaceTool::generate_lod(float p_threshold, int p_target_index_count) { Vector<int> lod; @@ -1288,8 +1294,8 @@ void SurfaceTool::_bind_methods() { ClassDB::bind_method(D_METHOD("set_skin_weight_count", "count"), &SurfaceTool::set_skin_weight_count); ClassDB::bind_method(D_METHOD("get_skin_weight_count"), &SurfaceTool::get_skin_weight_count); - ClassDB::bind_method(D_METHOD("set_custom_format", "index", "format"), &SurfaceTool::set_custom_format); - ClassDB::bind_method(D_METHOD("get_custom_format", "index"), &SurfaceTool::get_custom_format); + ClassDB::bind_method(D_METHOD("set_custom_format", "channel_index", "format"), &SurfaceTool::set_custom_format); + ClassDB::bind_method(D_METHOD("get_custom_format", "channel_index"), &SurfaceTool::get_custom_format); ClassDB::bind_method(D_METHOD("begin", "primitive"), &SurfaceTool::begin); @@ -1301,7 +1307,7 @@ void SurfaceTool::_bind_methods() { ClassDB::bind_method(D_METHOD("set_uv2", "uv2"), &SurfaceTool::set_uv2); ClassDB::bind_method(D_METHOD("set_bones", "bones"), &SurfaceTool::set_bones); ClassDB::bind_method(D_METHOD("set_weights", "weights"), &SurfaceTool::set_weights); - ClassDB::bind_method(D_METHOD("set_custom", "index", "custom"), &SurfaceTool::set_custom); + ClassDB::bind_method(D_METHOD("set_custom", "channel_index", "custom_color"), &SurfaceTool::set_custom); ClassDB::bind_method(D_METHOD("set_smooth_group", "index"), &SurfaceTool::set_smooth_group); ClassDB::bind_method(D_METHOD("add_triangle_fan", "vertices", "uvs", "colors", "uv2s", "normals", "tangents"), &SurfaceTool::add_triangle_fan, DEFVAL(Vector<Vector2>()), DEFVAL(Vector<Color>()), DEFVAL(Vector<Vector2>()), DEFVAL(Vector<Vector3>()), DEFVAL(Vector<Plane>())); @@ -1315,11 +1321,11 @@ void SurfaceTool::_bind_methods() { ClassDB::bind_method(D_METHOD("optimize_indices_for_cache"), &SurfaceTool::optimize_indices_for_cache); - ClassDB::bind_method(D_METHOD("get_max_axis_length"), &SurfaceTool::get_max_axis_length); + ClassDB::bind_method(D_METHOD("get_aabb"), &SurfaceTool::get_aabb); ClassDB::bind_method(D_METHOD("generate_lod", "nd_threshold", "target_index_count"), &SurfaceTool::generate_lod, DEFVAL(3)); ClassDB::bind_method(D_METHOD("set_material", "material"), &SurfaceTool::set_material); - ClassDB::bind_method(D_METHOD("get_primitive"), &SurfaceTool::get_primitive); + ClassDB::bind_method(D_METHOD("get_primitive_type"), &SurfaceTool::get_primitive_type); ClassDB::bind_method(D_METHOD("clear"), &SurfaceTool::clear); diff --git a/scene/resources/surface_tool.h b/scene/resources/surface_tool.h index bf4332ad2a..2d399ca3bf 100644 --- a/scene/resources/surface_tool.h +++ b/scene/resources/surface_tool.h @@ -154,10 +154,10 @@ public: void set_skin_weight_count(SkinWeightCount p_weights); SkinWeightCount get_skin_weight_count() const; - void set_custom_format(int p_index, CustomFormat p_format); - CustomFormat get_custom_format(int p_index) const; + void set_custom_format(int p_channel_index, CustomFormat p_format); + CustomFormat get_custom_format(int p_channel_index) const; - Mesh::PrimitiveType get_primitive() const; + Mesh::PrimitiveType get_primitive_type() const; void begin(Mesh::PrimitiveType p_primitive); @@ -166,7 +166,7 @@ public: void set_tangent(const Plane &p_tangent); void set_uv(const Vector2 &p_uv); void set_uv2(const Vector2 &p_uv2); - void set_custom(int p_index, const Color &p_custom); + void set_custom(int p_channel_index, const Color &p_custom); void set_bones(const Vector<int> &p_bones); void set_weights(const Vector<float> &p_weights); void set_smooth_group(uint32_t p_group); @@ -183,7 +183,7 @@ public: void generate_tangents(); void optimize_indices_for_cache(); - float get_max_axis_length() const; + AABB get_aabb() const; Vector<int> generate_lod(float p_threshold, int p_target_index_count = 3); void set_material(const Ref<Material> &p_material); @@ -199,7 +199,7 @@ public: void create_from(const Ref<Mesh> &p_existing, int p_surface); void create_from_blend_shape(const Ref<Mesh> &p_existing, int p_surface, const String &p_blend_shape_name); void append_from(const Ref<Mesh> &p_existing, int p_surface, const Transform3D &p_xform); - Ref<ArrayMesh> commit(const Ref<ArrayMesh> &p_existing = Ref<ArrayMesh>(), uint32_t p_flags = 0); + Ref<ArrayMesh> commit(const Ref<ArrayMesh> &p_existing = Ref<ArrayMesh>(), uint32_t p_compress_flags = 0); SurfaceTool(); }; diff --git a/scene/resources/syntax_highlighter.h b/scene/resources/syntax_highlighter.h index 143f1679c6..1243a9dbf7 100644 --- a/scene/resources/syntax_highlighter.h +++ b/scene/resources/syntax_highlighter.h @@ -41,7 +41,7 @@ class SyntaxHighlighter : public Resource { GDCLASS(SyntaxHighlighter, Resource) private: - Map<int, Dictionary> highlighting_cache; + RBMap<int, Dictionary> highlighting_cache; void _lines_edited_from(int p_from_line, int p_to_line); protected: @@ -83,7 +83,7 @@ private: bool line_only = false; }; Vector<ColorRegion> color_regions; - Map<int, int> color_region_cache; + HashMap<int, int> color_region_cache; Dictionary keywords; Dictionary member_keywords; diff --git a/scene/resources/text_file.cpp b/scene/resources/text_file.cpp index 96a47c37c4..0404e1f79b 100644 --- a/scene/resources/text_file.cpp +++ b/scene/resources/text_file.cpp @@ -64,7 +64,7 @@ Error TextFile::load_text(const String &p_path) { w[len] = 0; String s; - ERR_FAIL_COND_V_MSG(s.parse_utf8((const char *)w), ERR_INVALID_DATA, "Script '" + p_path + "' contains invalid unicode (UTF-8), so it was not loaded. Please ensure that scripts are saved in valid UTF-8 unicode."); + ERR_FAIL_COND_V_MSG(s.parse_utf8((const char *)w) != OK, ERR_INVALID_DATA, "Script '" + p_path + "' contains invalid unicode (UTF-8), so it was not loaded. Please ensure that scripts are saved in valid UTF-8 unicode."); text = s; path = p_path; return OK; diff --git a/scene/resources/text_line.cpp b/scene/resources/text_line.cpp index 337776fd47..823d742d72 100644 --- a/scene/resources/text_line.cpp +++ b/scene/resources/text_line.cpp @@ -36,7 +36,7 @@ void TextLine::_bind_methods() { ClassDB::bind_method(D_METHOD("set_direction", "direction"), &TextLine::set_direction); ClassDB::bind_method(D_METHOD("get_direction"), &TextLine::get_direction); - ADD_PROPERTY(PropertyInfo(Variant::INT, "direction", PROPERTY_HINT_ENUM, "Auto,Light-to-right,Right-to-left"), "set_direction", "get_direction"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "direction", PROPERTY_HINT_ENUM, "Auto,Left-to-right,Right-to-left"), "set_direction", "get_direction"); ClassDB::bind_method(D_METHOD("set_orientation", "orientation"), &TextLine::set_orientation); ClassDB::bind_method(D_METHOD("get_orientation"), &TextLine::get_orientation); @@ -55,7 +55,7 @@ void TextLine::_bind_methods() { ClassDB::bind_method(D_METHOD("set_bidi_override", "override"), &TextLine::set_bidi_override); - ClassDB::bind_method(D_METHOD("add_string", "text", "fonts", "size", "opentype_features", "language", "meta"), &TextLine::add_string, DEFVAL(Dictionary()), DEFVAL(""), DEFVAL(Variant())); + ClassDB::bind_method(D_METHOD("add_string", "text", "font", "font_size", "language", "meta"), &TextLine::add_string, DEFVAL(""), DEFVAL(Variant())); ClassDB::bind_method(D_METHOD("add_object", "key", "size", "inline_align", "length"), &TextLine::add_object, DEFVAL(INLINE_ALIGNMENT_CENTER), DEFVAL(1)); ClassDB::bind_method(D_METHOD("resize_object", "key", "size", "inline_align"), &TextLine::resize_object, DEFVAL(INLINE_ALIGNMENT_CENTER)); @@ -74,7 +74,7 @@ void TextLine::_bind_methods() { ClassDB::bind_method(D_METHOD("set_flags", "flags"), &TextLine::set_flags); ClassDB::bind_method(D_METHOD("get_flags"), &TextLine::get_flags); - ADD_PROPERTY(PropertyInfo(Variant::INT, "flags", PROPERTY_HINT_FLAGS, "Kashida Justify,Word Justify,Trim Edge Spaces After Justify,Justify Only After Last Tab"), "set_flags", "get_flags"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "flags", PROPERTY_HINT_FLAGS, "Kashida Justification,Word Justification,Trim Edge Spaces After Justification,Justify Only After Last Tab,Constrain Ellipsis"), "set_flags", "get_flags"); ClassDB::bind_method(D_METHOD("set_text_overrun_behavior", "overrun_behavior"), &TextLine::set_text_overrun_behavior); ClassDB::bind_method(D_METHOD("get_text_overrun_behavior"), &TextLine::get_text_overrun_behavior); @@ -98,12 +98,6 @@ void TextLine::_bind_methods() { ClassDB::bind_method(D_METHOD("draw_outline", "canvas", "pos", "outline_size", "color"), &TextLine::draw_outline, DEFVAL(1), DEFVAL(Color(1, 1, 1))); ClassDB::bind_method(D_METHOD("hit_test", "coords"), &TextLine::hit_test); - - BIND_ENUM_CONSTANT(OVERRUN_NO_TRIMMING); - BIND_ENUM_CONSTANT(OVERRUN_TRIM_CHAR); - BIND_ENUM_CONSTANT(OVERRUN_TRIM_WORD); - BIND_ENUM_CONSTANT(OVERRUN_TRIM_ELLIPSIS); - BIND_ENUM_CONSTANT(OVERRUN_TRIM_WORD_ELLIPSIS); } void TextLine::_shape() { @@ -112,32 +106,32 @@ void TextLine::_shape() { TS->shaped_text_tab_align(rid, tab_stops); } - uint16_t overrun_flags = TextServer::OVERRUN_NO_TRIMMING; - if (overrun_behavior != OVERRUN_NO_TRIMMING) { + BitField<TextServer::TextOverrunFlag> overrun_flags = TextServer::OVERRUN_NO_TRIM; + if (overrun_behavior != TextServer::OVERRUN_NO_TRIMMING) { switch (overrun_behavior) { - case OVERRUN_TRIM_WORD_ELLIPSIS: - overrun_flags |= TextServer::OVERRUN_TRIM; - overrun_flags |= TextServer::OVERRUN_TRIM_WORD_ONLY; - overrun_flags |= TextServer::OVERRUN_ADD_ELLIPSIS; + case TextServer::OVERRUN_TRIM_WORD_ELLIPSIS: + overrun_flags.set_flag(TextServer::OVERRUN_TRIM); + overrun_flags.set_flag(TextServer::OVERRUN_TRIM_WORD_ONLY); + overrun_flags.set_flag(TextServer::OVERRUN_ADD_ELLIPSIS); break; - case OVERRUN_TRIM_ELLIPSIS: - overrun_flags |= TextServer::OVERRUN_TRIM; - overrun_flags |= TextServer::OVERRUN_ADD_ELLIPSIS; + case TextServer::OVERRUN_TRIM_ELLIPSIS: + overrun_flags.set_flag(TextServer::OVERRUN_TRIM); + overrun_flags.set_flag(TextServer::OVERRUN_ADD_ELLIPSIS); break; - case OVERRUN_TRIM_WORD: - overrun_flags |= TextServer::OVERRUN_TRIM; - overrun_flags |= TextServer::OVERRUN_TRIM_WORD_ONLY; + case TextServer::OVERRUN_TRIM_WORD: + overrun_flags.set_flag(TextServer::OVERRUN_TRIM); + overrun_flags.set_flag(TextServer::OVERRUN_TRIM_WORD_ONLY); break; - case OVERRUN_TRIM_CHAR: - overrun_flags |= TextServer::OVERRUN_TRIM; + case TextServer::OVERRUN_TRIM_CHAR: + overrun_flags.set_flag(TextServer::OVERRUN_TRIM); break; - case OVERRUN_NO_TRIMMING: + case TextServer::OVERRUN_NO_TRIMMING: break; } if (alignment == HORIZONTAL_ALIGNMENT_FILL) { TS->shaped_text_fit_to_width(rid, width, flags); - overrun_flags |= TextServer::OVERRUN_JUSTIFICATION_AWARE; + overrun_flags.set_flag(TextServer::OVERRUN_JUSTIFICATION_AWARE); TS->shaped_text_overrun_trim_to_width(rid, width, overrun_flags); } else { TS->shaped_text_overrun_trim_to_width(rid, width, overrun_flags); @@ -155,8 +149,6 @@ RID TextLine::get_rid() const { void TextLine::clear() { TS->shaped_text_clear(rid); - spacing_top = 0; - spacing_bottom = 0; } void TextLine::set_preserve_invalid(bool p_enabled) { @@ -200,11 +192,12 @@ void TextLine::set_bidi_override(const Array &p_override) { dirty = true; } -bool TextLine::add_string(const String &p_text, const Ref<Font> &p_fonts, int p_size, const Dictionary &p_opentype_features, const String &p_language, const Variant &p_meta) { - ERR_FAIL_COND_V(p_fonts.is_null(), false); - bool res = TS->shaped_text_add_string(rid, p_text, p_fonts->get_rids(), p_size, p_opentype_features, p_language, p_meta); - spacing_top = p_fonts->get_spacing(TextServer::SPACING_TOP); - spacing_bottom = p_fonts->get_spacing(TextServer::SPACING_BOTTOM); +bool TextLine::add_string(const String &p_text, const Ref<Font> &p_font, int p_font_size, const String &p_language, const Variant &p_meta) { + ERR_FAIL_COND_V(p_font.is_null(), false); + bool res = TS->shaped_text_add_string(rid, p_text, p_font->get_rids(), p_font_size, p_font->get_opentype_features(), p_language, p_meta); + for (int i = 0; i < TextServer::SPACING_MAX; i++) { + TS->shaped_text_set_spacing(rid, TextServer::SpacingType(i), p_font->get_spacing(TextServer::SpacingType(i))); + } dirty = true; return res; } @@ -248,31 +241,31 @@ void TextLine::tab_align(const Vector<float> &p_tab_stops) { dirty = true; } -void TextLine::set_flags(uint16_t p_flags) { +void TextLine::set_flags(BitField<TextServer::JustificationFlag> p_flags) { if (flags != p_flags) { flags = p_flags; dirty = true; } } -uint16_t TextLine::get_flags() const { +BitField<TextServer::JustificationFlag> TextLine::get_flags() const { return flags; } -void TextLine::set_text_overrun_behavior(TextLine::OverrunBehavior p_behavior) { +void TextLine::set_text_overrun_behavior(TextServer::OverrunBehavior p_behavior) { if (overrun_behavior != p_behavior) { overrun_behavior = p_behavior; dirty = true; } } -TextLine::OverrunBehavior TextLine::get_text_overrun_behavior() const { +TextServer::OverrunBehavior TextLine::get_text_overrun_behavior() const { return overrun_behavior; } void TextLine::set_width(float p_width) { width = p_width; - if (alignment == HORIZONTAL_ALIGNMENT_FILL || overrun_behavior != OVERRUN_NO_TRIMMING) { + if (alignment == HORIZONTAL_ALIGNMENT_FILL || overrun_behavior != TextServer::OVERRUN_NO_TRIMMING) { dirty = true; } } @@ -284,20 +277,20 @@ float TextLine::get_width() const { Size2 TextLine::get_size() const { const_cast<TextLine *>(this)->_shape(); if (TS->shaped_text_get_orientation(rid) == TextServer::ORIENTATION_HORIZONTAL) { - return Size2(TS->shaped_text_get_size(rid).x, TS->shaped_text_get_size(rid).y + spacing_top + spacing_bottom); + return Size2(TS->shaped_text_get_size(rid).x, TS->shaped_text_get_size(rid).y); } else { - return Size2(TS->shaped_text_get_size(rid).x + spacing_top + spacing_bottom, TS->shaped_text_get_size(rid).y); + return Size2(TS->shaped_text_get_size(rid).x, TS->shaped_text_get_size(rid).y); } } float TextLine::get_line_ascent() const { const_cast<TextLine *>(this)->_shape(); - return TS->shaped_text_get_ascent(rid) + spacing_top; + return TS->shaped_text_get_ascent(rid); } float TextLine::get_line_descent() const { const_cast<TextLine *>(this)->_shape(); - return TS->shaped_text_get_descent(rid) + spacing_bottom; + return TS->shaped_text_get_descent(rid); } float TextLine::get_line_width() const { @@ -353,10 +346,10 @@ void TextLine::draw(RID p_canvas, const Vector2 &p_pos, const Color &p_color) co float clip_l; if (TS->shaped_text_get_orientation(rid) == TextServer::ORIENTATION_HORIZONTAL) { - ofs.y += TS->shaped_text_get_ascent(rid) + spacing_top; + ofs.y += TS->shaped_text_get_ascent(rid); clip_l = MAX(0, p_pos.x - ofs.x); } else { - ofs.x += TS->shaped_text_get_ascent(rid) + spacing_top; + ofs.x += TS->shaped_text_get_ascent(rid); clip_l = MAX(0, p_pos.y - ofs.y); } return TS->shaped_text_draw(rid, p_canvas, ofs, clip_l, clip_l + width, p_color); @@ -400,10 +393,10 @@ void TextLine::draw_outline(RID p_canvas, const Vector2 &p_pos, int p_outline_si float clip_l; if (TS->shaped_text_get_orientation(rid) == TextServer::ORIENTATION_HORIZONTAL) { - ofs.y += TS->shaped_text_get_ascent(rid) + spacing_top; + ofs.y += TS->shaped_text_get_ascent(rid); clip_l = MAX(0, p_pos.x - ofs.x); } else { - ofs.x += TS->shaped_text_get_ascent(rid) + spacing_top; + ofs.x += TS->shaped_text_get_ascent(rid); clip_l = MAX(0, p_pos.y - ofs.y); } return TS->shaped_text_draw_outline(rid, p_canvas, ofs, clip_l, clip_l + width, p_outline_size, p_color); @@ -415,11 +408,14 @@ int TextLine::hit_test(float p_coords) const { return TS->shaped_text_hit_test_position(rid, p_coords); } -TextLine::TextLine(const String &p_text, const Ref<Font> &p_fonts, int p_size, const Dictionary &p_opentype_features, const String &p_language, TextServer::Direction p_direction, TextServer::Orientation p_orientation) { +TextLine::TextLine(const String &p_text, const Ref<Font> &p_font, int p_font_size, const String &p_language, TextServer::Direction p_direction, TextServer::Orientation p_orientation) { rid = TS->create_shaped_text(p_direction, p_orientation); - spacing_top = p_fonts->get_spacing(TextServer::SPACING_TOP); - spacing_bottom = p_fonts->get_spacing(TextServer::SPACING_BOTTOM); - TS->shaped_text_add_string(rid, p_text, p_fonts->get_rids(), p_size, p_opentype_features, p_language); + if (p_font.is_valid()) { + TS->shaped_text_add_string(rid, p_text, p_font->get_rids(), p_font_size, p_font->get_opentype_features(), p_language); + for (int i = 0; i < TextServer::SPACING_MAX; i++) { + TS->shaped_text_set_spacing(rid, TextServer::SpacingType(i), p_font->get_spacing(TextServer::SpacingType(i))); + } + } } TextLine::TextLine() { diff --git a/scene/resources/text_line.h b/scene/resources/text_line.h index c5762db0f2..e70e82cf2b 100644 --- a/scene/resources/text_line.h +++ b/scene/resources/text_line.h @@ -39,26 +39,15 @@ class TextLine : public RefCounted { GDCLASS(TextLine, RefCounted); -public: - enum OverrunBehavior { - OVERRUN_NO_TRIMMING, - OVERRUN_TRIM_CHAR, - OVERRUN_TRIM_WORD, - OVERRUN_TRIM_ELLIPSIS, - OVERRUN_TRIM_WORD_ELLIPSIS, - }; - private: RID rid; - int spacing_top = 0; - int spacing_bottom = 0; bool dirty = true; float width = -1.0; - uint16_t flags = TextServer::JUSTIFICATION_WORD_BOUND | TextServer::JUSTIFICATION_KASHIDA; + BitField<TextServer::JustificationFlag> flags = TextServer::JUSTIFICATION_WORD_BOUND | TextServer::JUSTIFICATION_KASHIDA; HorizontalAlignment alignment = HORIZONTAL_ALIGNMENT_LEFT; - OverrunBehavior overrun_behavior = OVERRUN_TRIM_ELLIPSIS; + TextServer::OverrunBehavior overrun_behavior = TextServer::OVERRUN_TRIM_ELLIPSIS; Vector<float> tab_stops; @@ -86,7 +75,7 @@ public: void set_preserve_control(bool p_enabled); bool get_preserve_control() const; - bool add_string(const String &p_text, const Ref<Font> &p_fonts, int p_size, const Dictionary &p_opentype_features = Dictionary(), const String &p_language = "", const Variant &p_meta = Variant()); + bool add_string(const String &p_text, const Ref<Font> &p_font, int p_font_size, const String &p_language = "", const Variant &p_meta = Variant()); bool add_object(Variant p_key, const Size2 &p_size, InlineAlignment p_inline_align = INLINE_ALIGNMENT_CENTER, int p_length = 1); bool resize_object(Variant p_key, const Size2 &p_size, InlineAlignment p_inline_align = INLINE_ALIGNMENT_CENTER); @@ -95,11 +84,11 @@ public: void tab_align(const Vector<float> &p_tab_stops); - void set_flags(uint16_t p_flags); - uint16_t get_flags() const; + void set_flags(BitField<TextServer::JustificationFlag> p_flags); + BitField<TextServer::JustificationFlag> get_flags() const; - void set_text_overrun_behavior(OverrunBehavior p_behavior); - OverrunBehavior get_text_overrun_behavior() const; + void set_text_overrun_behavior(TextServer::OverrunBehavior p_behavior); + TextServer::OverrunBehavior get_text_overrun_behavior() const; void set_width(float p_width); float get_width() const; @@ -120,11 +109,9 @@ public: int hit_test(float p_coords) const; - TextLine(const String &p_text, const Ref<Font> &p_fonts, int p_size, const Dictionary &p_opentype_features = Dictionary(), const String &p_language = "", TextServer::Direction p_direction = TextServer::DIRECTION_AUTO, TextServer::Orientation p_orientation = TextServer::ORIENTATION_HORIZONTAL); + TextLine(const String &p_text, const Ref<Font> &p_font, int p_font_size, const String &p_language = "", TextServer::Direction p_direction = TextServer::DIRECTION_AUTO, TextServer::Orientation p_orientation = TextServer::ORIENTATION_HORIZONTAL); TextLine(); ~TextLine(); }; -VARIANT_ENUM_CAST(TextLine::OverrunBehavior); - #endif // TEXT_LINE_H diff --git a/scene/resources/text_paragraph.cpp b/scene/resources/text_paragraph.cpp index 61adaf43dd..43d3f329fa 100644 --- a/scene/resources/text_paragraph.cpp +++ b/scene/resources/text_paragraph.cpp @@ -60,10 +60,10 @@ void TextParagraph::_bind_methods() { ClassDB::bind_method(D_METHOD("set_bidi_override", "override"), &TextParagraph::set_bidi_override); - ClassDB::bind_method(D_METHOD("set_dropcap", "text", "fonts", "size", "dropcap_margins", "opentype_features", "language"), &TextParagraph::set_dropcap, DEFVAL(Rect2()), DEFVAL(Dictionary()), DEFVAL("")); + ClassDB::bind_method(D_METHOD("set_dropcap", "text", "font", "font_size", "dropcap_margins", "language"), &TextParagraph::set_dropcap, DEFVAL(Rect2()), DEFVAL("")); ClassDB::bind_method(D_METHOD("clear_dropcap"), &TextParagraph::clear_dropcap); - ClassDB::bind_method(D_METHOD("add_string", "text", "fonts", "size", "opentype_features", "language", "meta"), &TextParagraph::add_string, DEFVAL(Dictionary()), DEFVAL(""), DEFVAL(Variant())); + ClassDB::bind_method(D_METHOD("add_string", "text", "font", "font_size", "language", "meta"), &TextParagraph::add_string, DEFVAL(""), DEFVAL(Variant())); ClassDB::bind_method(D_METHOD("add_object", "key", "size", "inline_align", "length"), &TextParagraph::add_object, DEFVAL(INLINE_ALIGNMENT_CENTER), DEFVAL(1)); ClassDB::bind_method(D_METHOD("resize_object", "key", "size", "inline_align"), &TextParagraph::resize_object, DEFVAL(INLINE_ALIGNMENT_CENTER)); @@ -74,10 +74,15 @@ void TextParagraph::_bind_methods() { ClassDB::bind_method(D_METHOD("tab_align", "tab_stops"), &TextParagraph::tab_align); - ClassDB::bind_method(D_METHOD("set_flags", "flags"), &TextParagraph::set_flags); - ClassDB::bind_method(D_METHOD("get_flags"), &TextParagraph::get_flags); + ClassDB::bind_method(D_METHOD("set_break_flags", "flags"), &TextParagraph::set_break_flags); + ClassDB::bind_method(D_METHOD("get_break_flags"), &TextParagraph::get_break_flags); - ADD_PROPERTY(PropertyInfo(Variant::INT, "flags", PROPERTY_HINT_FLAGS, "Kashida Justify,Word Justify,Trim Edge Spaces After Justify,Justify Only After Last Tab,Break Mandatory,Break Words,Break Graphemes"), "set_flags", "get_flags"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "break_flags", PROPERTY_HINT_FLAGS, "Mandatory,Word Bound,Grapheme Bound,Adaptive"), "set_break_flags", "get_break_flags"); + + ClassDB::bind_method(D_METHOD("set_justification_flags", "flags"), &TextParagraph::set_justification_flags); + ClassDB::bind_method(D_METHOD("get_justification_flags"), &TextParagraph::get_justification_flags); + + ADD_PROPERTY(PropertyInfo(Variant::INT, "justification_flags", PROPERTY_HINT_FLAGS, "Kashida Justification,Word Justification,Trim Edge Spaces After Justification,Justify Only After Last Tab,Constrain Ellipsis"), "set_justification_flags", "get_justification_flags"); ClassDB::bind_method(D_METHOD("set_text_overrun_behavior", "overrun_behavior"), &TextParagraph::set_text_overrun_behavior); ClassDB::bind_method(D_METHOD("get_text_overrun_behavior"), &TextParagraph::get_text_overrun_behavior); @@ -113,9 +118,6 @@ void TextParagraph::_bind_methods() { ClassDB::bind_method(D_METHOD("get_line_underline_position", "line"), &TextParagraph::get_line_underline_position); ClassDB::bind_method(D_METHOD("get_line_underline_thickness", "line"), &TextParagraph::get_line_underline_thickness); - ClassDB::bind_method(D_METHOD("get_spacing_top"), &TextParagraph::get_spacing_top); - ClassDB::bind_method(D_METHOD("get_spacing_bottom"), &TextParagraph::get_spacing_bottom); - ClassDB::bind_method(D_METHOD("get_dropcap_size"), &TextParagraph::get_dropcap_size); ClassDB::bind_method(D_METHOD("get_dropcap_lines"), &TextParagraph::get_dropcap_lines); @@ -129,17 +131,11 @@ void TextParagraph::_bind_methods() { ClassDB::bind_method(D_METHOD("draw_dropcap_outline", "canvas", "pos", "outline_size", "color"), &TextParagraph::draw_dropcap_outline, DEFVAL(1), DEFVAL(Color(1, 1, 1))); ClassDB::bind_method(D_METHOD("hit_test", "coords"), &TextParagraph::hit_test); - - BIND_ENUM_CONSTANT(OVERRUN_NO_TRIMMING); - BIND_ENUM_CONSTANT(OVERRUN_TRIM_CHAR); - BIND_ENUM_CONSTANT(OVERRUN_TRIM_WORD); - BIND_ENUM_CONSTANT(OVERRUN_TRIM_ELLIPSIS); - BIND_ENUM_CONSTANT(OVERRUN_TRIM_WORD_ELLIPSIS); } void TextParagraph::_shape_lines() { if (lines_dirty) { - for (int i = 0; i < lines_rid.size(); i++) { + for (int i = 0; i < (int)lines_rid.size(); i++) { TS->free_rid(lines_rid[i]); } lines_rid.clear(); @@ -163,7 +159,7 @@ void TextParagraph::_shape_lines() { if (h_offset > 0) { // Dropcap, flow around. - PackedInt32Array line_breaks = TS->shaped_text_get_line_breaks(rid, width - h_offset, 0, flags); + PackedInt32Array line_breaks = TS->shaped_text_get_line_breaks(rid, width - h_offset, 0, brk_flags); for (int i = 0; i < line_breaks.size(); i = i + 2) { RID line = TS->shaped_text_substr(rid, line_breaks[i], line_breaks[i + 1] - line_breaks[i]); float h = (TS->shaped_text_get_orientation(line) == TextServer::ORIENTATION_HORIZONTAL) ? TS->shaped_text_get_size(line).y : TS->shaped_text_get_size(line).x; @@ -181,7 +177,7 @@ void TextParagraph::_shape_lines() { } } // Use fixed for the rest of lines. - PackedInt32Array line_breaks = TS->shaped_text_get_line_breaks(rid, width, start, flags); + PackedInt32Array line_breaks = TS->shaped_text_get_line_breaks(rid, width, start, brk_flags); for (int i = 0; i < line_breaks.size(); i = i + 2) { RID line = TS->shaped_text_substr(rid, line_breaks[i], line_breaks[i + 1] - line_breaks[i]); if (!tab_stops.is_empty()) { @@ -190,43 +186,43 @@ void TextParagraph::_shape_lines() { lines_rid.push_back(line); } - uint16_t overrun_flags = TextServer::OVERRUN_NO_TRIMMING; - if (overrun_behavior != OVERRUN_NO_TRIMMING) { + BitField<TextServer::TextOverrunFlag> overrun_flags = TextServer::OVERRUN_NO_TRIM; + if (overrun_behavior != TextServer::OVERRUN_NO_TRIMMING) { switch (overrun_behavior) { - case OVERRUN_TRIM_WORD_ELLIPSIS: - overrun_flags |= TextServer::OVERRUN_TRIM; - overrun_flags |= TextServer::OVERRUN_TRIM_WORD_ONLY; - overrun_flags |= TextServer::OVERRUN_ADD_ELLIPSIS; + case TextServer::OVERRUN_TRIM_WORD_ELLIPSIS: + overrun_flags.set_flag(TextServer::OVERRUN_TRIM); + overrun_flags.set_flag(TextServer::OVERRUN_TRIM_WORD_ONLY); + overrun_flags.set_flag(TextServer::OVERRUN_ADD_ELLIPSIS); break; - case OVERRUN_TRIM_ELLIPSIS: - overrun_flags |= TextServer::OVERRUN_TRIM; - overrun_flags |= TextServer::OVERRUN_ADD_ELLIPSIS; + case TextServer::OVERRUN_TRIM_ELLIPSIS: + overrun_flags.set_flag(TextServer::OVERRUN_TRIM); + overrun_flags.set_flag(TextServer::OVERRUN_ADD_ELLIPSIS); break; - case OVERRUN_TRIM_WORD: - overrun_flags |= TextServer::OVERRUN_TRIM; - overrun_flags |= TextServer::OVERRUN_TRIM_WORD_ONLY; + case TextServer::OVERRUN_TRIM_WORD: + overrun_flags.set_flag(TextServer::OVERRUN_TRIM); + overrun_flags.set_flag(TextServer::OVERRUN_TRIM_WORD_ONLY); break; - case OVERRUN_TRIM_CHAR: - overrun_flags |= TextServer::OVERRUN_TRIM; + case TextServer::OVERRUN_TRIM_CHAR: + overrun_flags.set_flag(TextServer::OVERRUN_TRIM); break; - case OVERRUN_NO_TRIMMING: + case TextServer::OVERRUN_NO_TRIMMING: break; } } - bool autowrap_enabled = ((flags & TextServer::BREAK_WORD_BOUND) == TextServer::BREAK_WORD_BOUND) || ((flags & TextServer::BREAK_GRAPHEME_BOUND) == TextServer::BREAK_GRAPHEME_BOUND); + bool autowrap_enabled = brk_flags.has_flag(TextServer::BREAK_WORD_BOUND) || brk_flags.has_flag(TextServer::BREAK_GRAPHEME_BOUND); // Fill after min_size calculation. if (autowrap_enabled) { - int visible_lines = (max_lines_visible >= 0) ? MIN(max_lines_visible, lines_rid.size()) : lines_rid.size(); - bool lines_hidden = visible_lines > 0 && visible_lines < lines_rid.size(); + int visible_lines = (max_lines_visible >= 0) ? MIN(max_lines_visible, (int)lines_rid.size()) : (int)lines_rid.size(); + bool lines_hidden = visible_lines > 0 && visible_lines < (int)lines_rid.size(); if (lines_hidden) { - overrun_flags |= TextServer::OVERRUN_ENFORCE_ELLIPSIS; + overrun_flags.set_flag(TextServer::OVERRUN_ENFORCE_ELLIPSIS); } if (alignment == HORIZONTAL_ALIGNMENT_FILL) { - for (int i = 0; i < lines_rid.size(); i++) { - if (i < visible_lines - 1 || lines_rid.size() == 1) { - TS->shaped_text_fit_to_width(lines_rid[i], width, flags); + for (int i = 0; i < (int)lines_rid.size(); i++) { + if (i < visible_lines - 1 || (int)lines_rid.size() == 1) { + TS->shaped_text_fit_to_width(lines_rid[i], width, jst_flags); } else if (i == (visible_lines - 1)) { TS->shaped_text_overrun_trim_to_width(lines_rid[visible_lines - 1], width, overrun_flags); } @@ -238,12 +234,12 @@ void TextParagraph::_shape_lines() { } else { // Autowrap disabled. - for (int i = 0; i < lines_rid.size(); i++) { + for (int i = 0; i < (int)lines_rid.size(); i++) { if (alignment == HORIZONTAL_ALIGNMENT_FILL) { - TS->shaped_text_fit_to_width(lines_rid[i], width, flags); - overrun_flags |= TextServer::OVERRUN_JUSTIFICATION_AWARE; + TS->shaped_text_fit_to_width(lines_rid[i], width, jst_flags); + overrun_flags.set_flag(TextServer::OVERRUN_JUSTIFICATION_AWARE); TS->shaped_text_overrun_trim_to_width(lines_rid[i], width, overrun_flags); - TS->shaped_text_fit_to_width(lines_rid[i], width, flags | TextServer::JUSTIFICATION_CONSTRAIN_ELLIPSIS); + TS->shaped_text_fit_to_width(lines_rid[i], width, jst_flags | TextServer::JUSTIFICATION_CONSTRAIN_ELLIPSIS); } else { TS->shaped_text_overrun_trim_to_width(lines_rid[i], width, overrun_flags); } @@ -258,8 +254,10 @@ RID TextParagraph::get_rid() const { } RID TextParagraph::get_line_rid(int p_line) const { + _THREAD_SAFE_METHOD_ + const_cast<TextParagraph *>(this)->_shape_lines(); - ERR_FAIL_COND_V(p_line < 0 || p_line >= lines_rid.size(), RID()); + ERR_FAIL_COND_V(p_line < 0 || p_line >= (int)lines_rid.size(), RID()); return lines_rid[p_line]; } @@ -268,9 +266,9 @@ RID TextParagraph::get_dropcap_rid() const { } void TextParagraph::clear() { - spacing_top = 0; - spacing_bottom = 0; - for (int i = 0; i < lines_rid.size(); i++) { + _THREAD_SAFE_METHOD_ + + for (int i = 0; i < (int)lines_rid.size(); i++) { TS->free_rid(lines_rid[i]); } lines_rid.clear(); @@ -279,106 +277,133 @@ void TextParagraph::clear() { } void TextParagraph::set_preserve_invalid(bool p_enabled) { + _THREAD_SAFE_METHOD_ + TS->shaped_text_set_preserve_invalid(rid, p_enabled); TS->shaped_text_set_preserve_invalid(dropcap_rid, p_enabled); lines_dirty = true; } bool TextParagraph::get_preserve_invalid() const { + _THREAD_SAFE_METHOD_ + return TS->shaped_text_get_preserve_invalid(rid); } void TextParagraph::set_preserve_control(bool p_enabled) { + _THREAD_SAFE_METHOD_ + TS->shaped_text_set_preserve_control(rid, p_enabled); TS->shaped_text_set_preserve_control(dropcap_rid, p_enabled); lines_dirty = true; } bool TextParagraph::get_preserve_control() const { + _THREAD_SAFE_METHOD_ + return TS->shaped_text_get_preserve_control(rid); } void TextParagraph::set_direction(TextServer::Direction p_direction) { + _THREAD_SAFE_METHOD_ + TS->shaped_text_set_direction(rid, p_direction); TS->shaped_text_set_direction(dropcap_rid, p_direction); lines_dirty = true; } TextServer::Direction TextParagraph::get_direction() const { + _THREAD_SAFE_METHOD_ + const_cast<TextParagraph *>(this)->_shape_lines(); return TS->shaped_text_get_direction(rid); } void TextParagraph::set_custom_punctuation(const String &p_punct) { + _THREAD_SAFE_METHOD_ + TS->shaped_text_set_custom_punctuation(rid, p_punct); lines_dirty = true; } String TextParagraph::get_custom_punctuation() const { + _THREAD_SAFE_METHOD_ + return TS->shaped_text_get_custom_punctuation(rid); } void TextParagraph::set_orientation(TextServer::Orientation p_orientation) { + _THREAD_SAFE_METHOD_ + TS->shaped_text_set_orientation(rid, p_orientation); TS->shaped_text_set_orientation(dropcap_rid, p_orientation); lines_dirty = true; } TextServer::Orientation TextParagraph::get_orientation() const { + _THREAD_SAFE_METHOD_ + const_cast<TextParagraph *>(this)->_shape_lines(); return TS->shaped_text_get_orientation(rid); } -bool TextParagraph::set_dropcap(const String &p_text, const Ref<Font> &p_fonts, int p_size, const Rect2 &p_dropcap_margins, const Dictionary &p_opentype_features, const String &p_language) { - ERR_FAIL_COND_V(p_fonts.is_null(), false); +bool TextParagraph::set_dropcap(const String &p_text, const Ref<Font> &p_font, int p_font_size, const Rect2 &p_dropcap_margins, const String &p_language) { + _THREAD_SAFE_METHOD_ + ERR_FAIL_COND_V(p_font.is_null(), false); TS->shaped_text_clear(dropcap_rid); dropcap_margins = p_dropcap_margins; - bool res = TS->shaped_text_add_string(dropcap_rid, p_text, p_fonts->get_rids(), p_size, p_opentype_features, p_language); + bool res = TS->shaped_text_add_string(dropcap_rid, p_text, p_font->get_rids(), p_font_size, p_font->get_opentype_features(), p_language); + for (int i = 0; i < TextServer::SPACING_MAX; i++) { + TS->shaped_text_set_spacing(dropcap_rid, TextServer::SpacingType(i), p_font->get_spacing(TextServer::SpacingType(i))); + } lines_dirty = true; return res; } void TextParagraph::clear_dropcap() { + _THREAD_SAFE_METHOD_ dropcap_margins = Rect2(); TS->shaped_text_clear(dropcap_rid); lines_dirty = true; } -bool TextParagraph::add_string(const String &p_text, const Ref<Font> &p_fonts, int p_size, const Dictionary &p_opentype_features, const String &p_language, const Variant &p_meta) { - ERR_FAIL_COND_V(p_fonts.is_null(), false); - bool res = TS->shaped_text_add_string(rid, p_text, p_fonts->get_rids(), p_size, p_opentype_features, p_language, p_meta); - spacing_top = p_fonts->get_spacing(TextServer::SPACING_TOP); - spacing_bottom = p_fonts->get_spacing(TextServer::SPACING_BOTTOM); +bool TextParagraph::add_string(const String &p_text, const Ref<Font> &p_font, int p_font_size, const String &p_language, const Variant &p_meta) { + _THREAD_SAFE_METHOD_ + ERR_FAIL_COND_V(p_font.is_null(), false); + bool res = TS->shaped_text_add_string(rid, p_text, p_font->get_rids(), p_font_size, p_font->get_opentype_features(), p_language, p_meta); + for (int i = 0; i < TextServer::SPACING_MAX; i++) { + TS->shaped_text_set_spacing(rid, TextServer::SpacingType(i), p_font->get_spacing(TextServer::SpacingType(i))); + } lines_dirty = true; return res; } -int TextParagraph::get_spacing_top() const { - return spacing_top; -} - -int TextParagraph::get_spacing_bottom() const { - return spacing_bottom; -} - void TextParagraph::set_bidi_override(const Array &p_override) { + _THREAD_SAFE_METHOD_ + TS->shaped_text_set_bidi_override(rid, p_override); lines_dirty = true; } bool TextParagraph::add_object(Variant p_key, const Size2 &p_size, InlineAlignment p_inline_align, int p_length) { + _THREAD_SAFE_METHOD_ + bool res = TS->shaped_text_add_object(rid, p_key, p_size, p_inline_align, p_length); lines_dirty = true; return res; } bool TextParagraph::resize_object(Variant p_key, const Size2 &p_size, InlineAlignment p_inline_align) { + _THREAD_SAFE_METHOD_ + bool res = TS->shaped_text_resize_object(rid, p_key, p_size, p_inline_align); lines_dirty = true; return res; } void TextParagraph::set_alignment(HorizontalAlignment p_alignment) { + _THREAD_SAFE_METHOD_ + if (alignment != p_alignment) { if (alignment == HORIZONTAL_ALIGNMENT_FILL || p_alignment == HORIZONTAL_ALIGNMENT_FILL) { alignment = p_alignment; @@ -394,33 +419,54 @@ HorizontalAlignment TextParagraph::get_alignment() const { } void TextParagraph::tab_align(const Vector<float> &p_tab_stops) { + _THREAD_SAFE_METHOD_ + tab_stops = p_tab_stops; lines_dirty = true; } -void TextParagraph::set_flags(uint16_t p_flags) { - if (flags != p_flags) { - flags = p_flags; +void TextParagraph::set_justification_flags(BitField<TextServer::JustificationFlag> p_flags) { + _THREAD_SAFE_METHOD_ + + if (jst_flags != p_flags) { + jst_flags = p_flags; + lines_dirty = true; + } +} + +BitField<TextServer::JustificationFlag> TextParagraph::get_justification_flags() const { + return jst_flags; +} + +void TextParagraph::set_break_flags(BitField<TextServer::LineBreakFlag> p_flags) { + _THREAD_SAFE_METHOD_ + + if (brk_flags != p_flags) { + brk_flags = p_flags; lines_dirty = true; } } -uint16_t TextParagraph::get_flags() const { - return flags; +BitField<TextServer::LineBreakFlag> TextParagraph::get_break_flags() const { + return brk_flags; } -void TextParagraph::set_text_overrun_behavior(TextParagraph::OverrunBehavior p_behavior) { +void TextParagraph::set_text_overrun_behavior(TextServer::OverrunBehavior p_behavior) { + _THREAD_SAFE_METHOD_ + if (overrun_behavior != p_behavior) { overrun_behavior = p_behavior; lines_dirty = true; } } -TextParagraph::OverrunBehavior TextParagraph::get_text_overrun_behavior() const { +TextServer::OverrunBehavior TextParagraph::get_text_overrun_behavior() const { return overrun_behavior; } void TextParagraph::set_width(float p_width) { + _THREAD_SAFE_METHOD_ + if (width != p_width) { width = p_width; lines_dirty = true; @@ -432,25 +478,29 @@ float TextParagraph::get_width() const { } Size2 TextParagraph::get_non_wrapped_size() const { + _THREAD_SAFE_METHOD_ + const_cast<TextParagraph *>(this)->_shape_lines(); if (TS->shaped_text_get_orientation(rid) == TextServer::ORIENTATION_HORIZONTAL) { - return Size2(TS->shaped_text_get_size(rid).x, TS->shaped_text_get_size(rid).y + spacing_top + spacing_bottom); + return Size2(TS->shaped_text_get_size(rid).x, TS->shaped_text_get_size(rid).y); } else { - return Size2(TS->shaped_text_get_size(rid).x + spacing_top + spacing_bottom, TS->shaped_text_get_size(rid).y); + return Size2(TS->shaped_text_get_size(rid).x, TS->shaped_text_get_size(rid).y); } } Size2 TextParagraph::get_size() const { + _THREAD_SAFE_METHOD_ + const_cast<TextParagraph *>(this)->_shape_lines(); Size2 size; - int visible_lines = (max_lines_visible >= 0) ? MIN(max_lines_visible, lines_rid.size()) : lines_rid.size(); + int visible_lines = (max_lines_visible >= 0) ? MIN(max_lines_visible, (int)lines_rid.size()) : (int)lines_rid.size(); for (int i = 0; i < visible_lines; i++) { Size2 lsize = TS->shaped_text_get_size(lines_rid[i]); if (TS->shaped_text_get_orientation(lines_rid[i]) == TextServer::ORIENTATION_HORIZONTAL) { size.x = MAX(size.x, lsize.x); - size.y += lsize.y + spacing_top + spacing_bottom; + size.y += lsize.y; } else { - size.x += lsize.x + spacing_top + spacing_bottom; + size.x += lsize.x; size.y = MAX(size.y, lsize.y); } } @@ -458,11 +508,15 @@ Size2 TextParagraph::get_size() const { } int TextParagraph::get_line_count() const { + _THREAD_SAFE_METHOD_ + const_cast<TextParagraph *>(this)->_shape_lines(); - return lines_rid.size(); + return (int)lines_rid.size(); } void TextParagraph::set_max_lines_visible(int p_lines) { + _THREAD_SAFE_METHOD_ + if (p_lines != max_lines_visible) { max_lines_visible = p_lines; lines_dirty = true; @@ -474,73 +528,93 @@ int TextParagraph::get_max_lines_visible() const { } Array TextParagraph::get_line_objects(int p_line) const { + _THREAD_SAFE_METHOD_ + const_cast<TextParagraph *>(this)->_shape_lines(); - ERR_FAIL_COND_V(p_line < 0 || p_line >= lines_rid.size(), Array()); + ERR_FAIL_COND_V(p_line < 0 || p_line >= (int)lines_rid.size(), Array()); return TS->shaped_text_get_objects(lines_rid[p_line]); } Rect2 TextParagraph::get_line_object_rect(int p_line, Variant p_key) const { + _THREAD_SAFE_METHOD_ + const_cast<TextParagraph *>(this)->_shape_lines(); - ERR_FAIL_COND_V(p_line < 0 || p_line >= lines_rid.size(), Rect2()); + ERR_FAIL_COND_V(p_line < 0 || p_line >= (int)lines_rid.size(), Rect2()); Rect2 xrect = TS->shaped_text_get_object_rect(lines_rid[p_line], p_key); for (int i = 0; i < p_line; i++) { Size2 lsize = TS->shaped_text_get_size(lines_rid[i]); if (TS->shaped_text_get_orientation(lines_rid[i]) == TextServer::ORIENTATION_HORIZONTAL) { - xrect.position.y += lsize.y + spacing_top + spacing_bottom; + xrect.position.y += lsize.y; } else { - xrect.position.x += lsize.x + spacing_top + spacing_bottom; + xrect.position.x += lsize.x; } } return xrect; } Size2 TextParagraph::get_line_size(int p_line) const { + _THREAD_SAFE_METHOD_ + const_cast<TextParagraph *>(this)->_shape_lines(); - ERR_FAIL_COND_V(p_line < 0 || p_line >= lines_rid.size(), Size2()); + ERR_FAIL_COND_V(p_line < 0 || p_line >= (int)lines_rid.size(), Size2()); if (TS->shaped_text_get_orientation(lines_rid[p_line]) == TextServer::ORIENTATION_HORIZONTAL) { - return Size2(TS->shaped_text_get_size(lines_rid[p_line]).x, TS->shaped_text_get_size(lines_rid[p_line]).y + spacing_top + spacing_bottom); + return Size2(TS->shaped_text_get_size(lines_rid[p_line]).x, TS->shaped_text_get_size(lines_rid[p_line]).y); } else { - return Size2(TS->shaped_text_get_size(lines_rid[p_line]).x + spacing_top + spacing_bottom, TS->shaped_text_get_size(lines_rid[p_line]).y); + return Size2(TS->shaped_text_get_size(lines_rid[p_line]).x, TS->shaped_text_get_size(lines_rid[p_line]).y); } } Vector2i TextParagraph::get_line_range(int p_line) const { + _THREAD_SAFE_METHOD_ + const_cast<TextParagraph *>(this)->_shape_lines(); - ERR_FAIL_COND_V(p_line < 0 || p_line >= lines_rid.size(), Vector2i()); + ERR_FAIL_COND_V(p_line < 0 || p_line >= (int)lines_rid.size(), Vector2i()); return TS->shaped_text_get_range(lines_rid[p_line]); } float TextParagraph::get_line_ascent(int p_line) const { + _THREAD_SAFE_METHOD_ + const_cast<TextParagraph *>(this)->_shape_lines(); - ERR_FAIL_COND_V(p_line < 0 || p_line >= lines_rid.size(), 0.f); - return TS->shaped_text_get_ascent(lines_rid[p_line]) + spacing_top; + ERR_FAIL_COND_V(p_line < 0 || p_line >= (int)lines_rid.size(), 0.f); + return TS->shaped_text_get_ascent(lines_rid[p_line]); } float TextParagraph::get_line_descent(int p_line) const { + _THREAD_SAFE_METHOD_ + const_cast<TextParagraph *>(this)->_shape_lines(); - ERR_FAIL_COND_V(p_line < 0 || p_line >= lines_rid.size(), 0.f); - return TS->shaped_text_get_descent(lines_rid[p_line]) + spacing_bottom; + ERR_FAIL_COND_V(p_line < 0 || p_line >= (int)lines_rid.size(), 0.f); + return TS->shaped_text_get_descent(lines_rid[p_line]); } float TextParagraph::get_line_width(int p_line) const { + _THREAD_SAFE_METHOD_ + const_cast<TextParagraph *>(this)->_shape_lines(); - ERR_FAIL_COND_V(p_line < 0 || p_line >= lines_rid.size(), 0.f); + ERR_FAIL_COND_V(p_line < 0 || p_line >= (int)lines_rid.size(), 0.f); return TS->shaped_text_get_width(lines_rid[p_line]); } float TextParagraph::get_line_underline_position(int p_line) const { + _THREAD_SAFE_METHOD_ + const_cast<TextParagraph *>(this)->_shape_lines(); - ERR_FAIL_COND_V(p_line < 0 || p_line >= lines_rid.size(), 0.f); + ERR_FAIL_COND_V(p_line < 0 || p_line >= (int)lines_rid.size(), 0.f); return TS->shaped_text_get_underline_position(lines_rid[p_line]); } float TextParagraph::get_line_underline_thickness(int p_line) const { + _THREAD_SAFE_METHOD_ + const_cast<TextParagraph *>(this)->_shape_lines(); - ERR_FAIL_COND_V(p_line < 0 || p_line >= lines_rid.size(), 0.f); + ERR_FAIL_COND_V(p_line < 0 || p_line >= (int)lines_rid.size(), 0.f); return TS->shaped_text_get_underline_thickness(lines_rid[p_line]); } Size2 TextParagraph::get_dropcap_size() const { + _THREAD_SAFE_METHOD_ + return TS->shaped_text_get_size(dropcap_rid) + dropcap_margins.size + dropcap_margins.position; } @@ -549,6 +623,8 @@ int TextParagraph::get_dropcap_lines() const { } void TextParagraph::draw(RID p_canvas, const Vector2 &p_pos, const Color &p_color, const Color &p_dc_color) const { + _THREAD_SAFE_METHOD_ + const_cast<TextParagraph *>(this)->_shape_lines(); Vector2 ofs = p_pos; float h_offset = 0.f; @@ -571,13 +647,13 @@ void TextParagraph::draw(RID p_canvas, const Vector2 &p_pos, const Color &p_colo TS->shaped_text_draw(dropcap_rid, p_canvas, dc_off + Vector2(0, TS->shaped_text_get_ascent(dropcap_rid) + dropcap_margins.size.y + dropcap_margins.position.y / 2), -1, -1, p_dc_color); } - int lines_visible = (max_lines_visible >= 0) ? MIN(max_lines_visible, lines_rid.size()) : lines_rid.size(); + int lines_visible = (max_lines_visible >= 0) ? MIN(max_lines_visible, (int)lines_rid.size()) : (int)lines_rid.size(); for (int i = 0; i < lines_visible; i++) { float l_width = width; if (TS->shaped_text_get_orientation(lines_rid[i]) == TextServer::ORIENTATION_HORIZONTAL) { ofs.x = p_pos.x; - ofs.y += TS->shaped_text_get_ascent(lines_rid[i]) + spacing_top; + ofs.y += TS->shaped_text_get_ascent(lines_rid[i]); if (i <= dropcap_lines) { if (TS->shaped_text_get_inferred_direction(dropcap_rid) == TextServer::DIRECTION_LTR) { ofs.x -= h_offset; @@ -586,7 +662,7 @@ void TextParagraph::draw(RID p_canvas, const Vector2 &p_pos, const Color &p_colo } } else { ofs.y = p_pos.y; - ofs.x += TS->shaped_text_get_ascent(lines_rid[i]) + spacing_top; + ofs.x += TS->shaped_text_get_ascent(lines_rid[i]); if (i <= dropcap_lines) { if (TS->shaped_text_get_inferred_direction(dropcap_rid) == TextServer::DIRECTION_LTR) { ofs.x -= h_offset; @@ -641,15 +717,17 @@ void TextParagraph::draw(RID p_canvas, const Vector2 &p_pos, const Color &p_colo TS->shaped_text_draw(lines_rid[i], p_canvas, ofs, clip_l, clip_l + l_width, p_color); if (TS->shaped_text_get_orientation(lines_rid[i]) == TextServer::ORIENTATION_HORIZONTAL) { ofs.x = p_pos.x; - ofs.y += TS->shaped_text_get_descent(lines_rid[i]) + spacing_bottom; + ofs.y += TS->shaped_text_get_descent(lines_rid[i]); } else { ofs.y = p_pos.y; - ofs.x += TS->shaped_text_get_descent(lines_rid[i]) + spacing_bottom; + ofs.x += TS->shaped_text_get_descent(lines_rid[i]); } } } void TextParagraph::draw_outline(RID p_canvas, const Vector2 &p_pos, int p_outline_size, const Color &p_color, const Color &p_dc_color) const { + _THREAD_SAFE_METHOD_ + const_cast<TextParagraph *>(this)->_shape_lines(); Vector2 ofs = p_pos; @@ -673,11 +751,11 @@ void TextParagraph::draw_outline(RID p_canvas, const Vector2 &p_pos, int p_outli TS->shaped_text_draw_outline(dropcap_rid, p_canvas, dc_off + Vector2(dropcap_margins.position.x, TS->shaped_text_get_ascent(dropcap_rid) + dropcap_margins.position.y), -1, -1, p_outline_size, p_dc_color); } - for (int i = 0; i < lines_rid.size(); i++) { + for (int i = 0; i < (int)lines_rid.size(); i++) { float l_width = width; if (TS->shaped_text_get_orientation(lines_rid[i]) == TextServer::ORIENTATION_HORIZONTAL) { ofs.x = p_pos.x; - ofs.y += TS->shaped_text_get_ascent(lines_rid[i]) + spacing_top; + ofs.y += TS->shaped_text_get_ascent(lines_rid[i]); if (i <= dropcap_lines) { if (TS->shaped_text_get_inferred_direction(dropcap_rid) == TextServer::DIRECTION_LTR) { ofs.x -= h_offset; @@ -686,7 +764,7 @@ void TextParagraph::draw_outline(RID p_canvas, const Vector2 &p_pos, int p_outli } } else { ofs.y = p_pos.y; - ofs.x += TS->shaped_text_get_ascent(lines_rid[i]) + spacing_top; + ofs.x += TS->shaped_text_get_ascent(lines_rid[i]); if (i <= dropcap_lines) { if (TS->shaped_text_get_inferred_direction(dropcap_rid) == TextServer::DIRECTION_LTR) { ofs.x -= h_offset; @@ -741,15 +819,17 @@ void TextParagraph::draw_outline(RID p_canvas, const Vector2 &p_pos, int p_outli TS->shaped_text_draw_outline(lines_rid[i], p_canvas, ofs, clip_l, clip_l + l_width, p_outline_size, p_color); if (TS->shaped_text_get_orientation(lines_rid[i]) == TextServer::ORIENTATION_HORIZONTAL) { ofs.x = p_pos.x; - ofs.y += TS->shaped_text_get_descent(lines_rid[i]) + spacing_bottom; + ofs.y += TS->shaped_text_get_descent(lines_rid[i]); } else { ofs.y = p_pos.y; - ofs.x += TS->shaped_text_get_descent(lines_rid[i]) + spacing_bottom; + ofs.x += TS->shaped_text_get_descent(lines_rid[i]); } } } int TextParagraph::hit_test(const Point2 &p_coords) const { + _THREAD_SAFE_METHOD_ + const_cast<TextParagraph *>(this)->_shape_lines(); Vector2 ofs; if (TS->shaped_text_get_orientation(rid) == TextServer::ORIENTATION_HORIZONTAL) { @@ -761,23 +841,25 @@ int TextParagraph::hit_test(const Point2 &p_coords) const { return 0; } } - for (int i = 0; i < lines_rid.size(); i++) { + for (int i = 0; i < (int)lines_rid.size(); i++) { if (TS->shaped_text_get_orientation(lines_rid[i]) == TextServer::ORIENTATION_HORIZONTAL) { if ((p_coords.y >= ofs.y) && (p_coords.y <= ofs.y + TS->shaped_text_get_size(lines_rid[i]).y)) { return TS->shaped_text_hit_test_position(lines_rid[i], p_coords.x); } - ofs.y += TS->shaped_text_get_size(lines_rid[i]).y + spacing_bottom + spacing_top; + ofs.y += TS->shaped_text_get_size(lines_rid[i]).y; } else { if ((p_coords.x >= ofs.x) && (p_coords.x <= ofs.x + TS->shaped_text_get_size(lines_rid[i]).x)) { return TS->shaped_text_hit_test_position(lines_rid[i], p_coords.y); } - ofs.y += TS->shaped_text_get_size(lines_rid[i]).x + spacing_bottom + spacing_top; + ofs.y += TS->shaped_text_get_size(lines_rid[i]).x; } } return TS->shaped_text_get_range(rid).y; } void TextParagraph::draw_dropcap(RID p_canvas, const Vector2 &p_pos, const Color &p_color) const { + _THREAD_SAFE_METHOD_ + Vector2 ofs = p_pos; float h_offset = 0.f; if (TS->shaped_text_get_orientation(dropcap_rid) == TextServer::ORIENTATION_HORIZONTAL) { @@ -800,6 +882,8 @@ void TextParagraph::draw_dropcap(RID p_canvas, const Vector2 &p_pos, const Color } void TextParagraph::draw_dropcap_outline(RID p_canvas, const Vector2 &p_pos, int p_outline_size, const Color &p_color) const { + _THREAD_SAFE_METHOD_ + Vector2 ofs = p_pos; float h_offset = 0.f; if (TS->shaped_text_get_orientation(dropcap_rid) == TextServer::ORIENTATION_HORIZONTAL) { @@ -822,37 +906,44 @@ void TextParagraph::draw_dropcap_outline(RID p_canvas, const Vector2 &p_pos, int } void TextParagraph::draw_line(RID p_canvas, const Vector2 &p_pos, int p_line, const Color &p_color) const { + _THREAD_SAFE_METHOD_ + const_cast<TextParagraph *>(this)->_shape_lines(); - ERR_FAIL_COND(p_line < 0 || p_line >= lines_rid.size()); + ERR_FAIL_COND(p_line < 0 || p_line >= (int)lines_rid.size()); Vector2 ofs = p_pos; if (TS->shaped_text_get_orientation(lines_rid[p_line]) == TextServer::ORIENTATION_HORIZONTAL) { - ofs.y += TS->shaped_text_get_ascent(lines_rid[p_line]) + spacing_top; + ofs.y += TS->shaped_text_get_ascent(lines_rid[p_line]); } else { - ofs.x += TS->shaped_text_get_ascent(lines_rid[p_line]) + spacing_top; + ofs.x += TS->shaped_text_get_ascent(lines_rid[p_line]); } return TS->shaped_text_draw(lines_rid[p_line], p_canvas, ofs, -1, -1, p_color); } void TextParagraph::draw_line_outline(RID p_canvas, const Vector2 &p_pos, int p_line, int p_outline_size, const Color &p_color) const { + _THREAD_SAFE_METHOD_ + const_cast<TextParagraph *>(this)->_shape_lines(); - ERR_FAIL_COND(p_line < 0 || p_line >= lines_rid.size()); + ERR_FAIL_COND(p_line < 0 || p_line >= (int)lines_rid.size()); Vector2 ofs = p_pos; if (TS->shaped_text_get_orientation(lines_rid[p_line]) == TextServer::ORIENTATION_HORIZONTAL) { - ofs.y += TS->shaped_text_get_ascent(lines_rid[p_line]) + spacing_top; + ofs.y += TS->shaped_text_get_ascent(lines_rid[p_line]); } else { - ofs.x += TS->shaped_text_get_ascent(lines_rid[p_line]) + spacing_top; + ofs.x += TS->shaped_text_get_ascent(lines_rid[p_line]); } return TS->shaped_text_draw_outline(lines_rid[p_line], p_canvas, ofs, -1, -1, p_outline_size, p_color); } -TextParagraph::TextParagraph(const String &p_text, const Ref<Font> &p_fonts, int p_size, const Dictionary &p_opentype_features, const String &p_language, float p_width, TextServer::Direction p_direction, TextServer::Orientation p_orientation) { +TextParagraph::TextParagraph(const String &p_text, const Ref<Font> &p_font, int p_font_size, const String &p_language, float p_width, TextServer::Direction p_direction, TextServer::Orientation p_orientation) { rid = TS->create_shaped_text(p_direction, p_orientation); - TS->shaped_text_add_string(rid, p_text, p_fonts->get_rids(), p_size, p_opentype_features, p_language); - spacing_top = p_fonts->get_spacing(TextServer::SPACING_TOP); - spacing_bottom = p_fonts->get_spacing(TextServer::SPACING_BOTTOM); + if (p_font.is_valid()) { + TS->shaped_text_add_string(rid, p_text, p_font->get_rids(), p_font_size, p_font->get_opentype_features(), p_language); + for (int i = 0; i < TextServer::SPACING_MAX; i++) { + TS->shaped_text_set_spacing(rid, TextServer::SpacingType(i), p_font->get_spacing(TextServer::SpacingType(i))); + } + } width = p_width; } @@ -862,7 +953,7 @@ TextParagraph::TextParagraph() { } TextParagraph::~TextParagraph() { - for (int i = 0; i < lines_rid.size(); i++) { + for (int i = 0; i < (int)lines_rid.size(); i++) { TS->free_rid(lines_rid[i]); } lines_rid.clear(); diff --git a/scene/resources/text_paragraph.h b/scene/resources/text_paragraph.h index 8a8a53943b..0fe82b4364 100644 --- a/scene/resources/text_paragraph.h +++ b/scene/resources/text_paragraph.h @@ -31,6 +31,7 @@ #ifndef TEXT_PARAGRAPH_H #define TEXT_PARAGRAPH_H +#include "core/templates/local_vector.h" #include "scene/resources/font.h" #include "servers/text_server.h" @@ -38,15 +39,7 @@ class TextParagraph : public RefCounted { GDCLASS(TextParagraph, RefCounted); - -public: - enum OverrunBehavior { - OVERRUN_NO_TRIMMING, - OVERRUN_TRIM_CHAR, - OVERRUN_TRIM_WORD, - OVERRUN_TRIM_ELLIPSIS, - OVERRUN_TRIM_WORD_ELLIPSIS, - }; + _THREAD_SAFE_CLASS_ private: RID dropcap_rid; @@ -54,17 +47,16 @@ private: Rect2 dropcap_margins; RID rid; - Vector<RID> lines_rid; - int spacing_top = 0; - int spacing_bottom = 0; + LocalVector<RID> lines_rid; bool lines_dirty = true; float width = -1.0; int max_lines_visible = -1; - uint16_t flags = TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND | TextServer::JUSTIFICATION_WORD_BOUND | TextServer::JUSTIFICATION_KASHIDA; - OverrunBehavior overrun_behavior = OVERRUN_NO_TRIMMING; + BitField<TextServer::LineBreakFlag> brk_flags = TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND; + BitField<TextServer::JustificationFlag> jst_flags = TextServer::JUSTIFICATION_WORD_BOUND | TextServer::JUSTIFICATION_KASHIDA; + TextServer::OverrunBehavior overrun_behavior = TextServer::OVERRUN_NO_TRIMMING; HorizontalAlignment alignment = HORIZONTAL_ALIGNMENT_LEFT; @@ -99,10 +91,10 @@ public: 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 = ""); + bool set_dropcap(const String &p_text, const Ref<Font> &p_font, int p_font_size, const Rect2 &p_dropcap_margins = Rect2(), const String &p_language = ""); void clear_dropcap(); - bool add_string(const String &p_text, const Ref<Font> &p_fonts, int p_size, const Dictionary &p_opentype_features = Dictionary(), const String &p_language = "", const Variant &p_meta = Variant()); + bool add_string(const String &p_text, const Ref<Font> &p_font, int p_font_size, const String &p_language = "", const Variant &p_meta = Variant()); bool add_object(Variant p_key, const Size2 &p_size, InlineAlignment p_inline_align = INLINE_ALIGNMENT_CENTER, int p_length = 1); bool resize_object(Variant p_key, const Size2 &p_size, InlineAlignment p_inline_align = INLINE_ALIGNMENT_CENTER); @@ -111,11 +103,14 @@ public: void tab_align(const Vector<float> &p_tab_stops); - void set_flags(uint16_t p_flags); - uint16_t get_flags() const; + void set_justification_flags(BitField<TextServer::JustificationFlag> p_flags); + BitField<TextServer::JustificationFlag> get_justification_flags() const; + + void set_break_flags(BitField<TextServer::LineBreakFlag> p_flags); + BitField<TextServer::LineBreakFlag> get_break_flags() const; - void set_text_overrun_behavior(OverrunBehavior p_behavior); - OverrunBehavior get_text_overrun_behavior() const; + void set_text_overrun_behavior(TextServer::OverrunBehavior p_behavior); + TextServer::OverrunBehavior get_text_overrun_behavior() const; void set_width(float p_width); float get_width() const; @@ -156,11 +151,11 @@ public: int hit_test(const Point2 &p_coords) const; - TextParagraph(const String &p_text, const Ref<Font> &p_fonts, int p_size, const Dictionary &p_opentype_features = Dictionary(), const String &p_language = "", float p_width = -1.f, TextServer::Direction p_direction = TextServer::DIRECTION_AUTO, TextServer::Orientation p_orientation = TextServer::ORIENTATION_HORIZONTAL); + Mutex &get_mutex() const { return _thread_safe_; }; + + TextParagraph(const String &p_text, const Ref<Font> &p_font, int p_font_size, const String &p_language = "", float p_width = -1.f, TextServer::Direction p_direction = TextServer::DIRECTION_AUTO, TextServer::Orientation p_orientation = TextServer::ORIENTATION_HORIZONTAL); TextParagraph(); ~TextParagraph(); }; -VARIANT_ENUM_CAST(TextParagraph::OverrunBehavior); - #endif // TEXT_PARAGRAPH_H diff --git a/scene/resources/texture.cpp b/scene/resources/texture.cpp index 4c20e07976..0aefe34f7d 100644 --- a/scene/resources/texture.cpp +++ b/scene/resources/texture.cpp @@ -139,7 +139,7 @@ void ImageTexture::reload_from_file() { img.instantiate(); if (ImageLoader::load_image(path, img) == OK) { - create_from_image(img); + set_image(img); } else { Resource::reload_from_file(); notify_property_list_changed(); @@ -149,37 +149,34 @@ void ImageTexture::reload_from_file() { bool ImageTexture::_set(const StringName &p_name, const Variant &p_value) { if (p_name == "image") { - create_from_image(p_value); - } else if (p_name == "size") { - Size2 s = p_value; - w = s.width; - h = s.height; - RenderingServer::get_singleton()->texture_set_size_override(texture, w, h); - } else { - return false; + set_image(p_value); + return true; } - - return true; + return false; } bool ImageTexture::_get(const StringName &p_name, Variant &r_ret) const { if (p_name == "image") { r_ret = get_image(); - } else if (p_name == "size") { - r_ret = Size2(w, h); - } else { - return false; + return true; } - - return true; + return false; } void ImageTexture::_get_property_list(List<PropertyInfo> *p_list) const { - p_list->push_back(PropertyInfo(Variant::OBJECT, "image", PROPERTY_HINT_RESOURCE_TYPE, "Image", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESOURCE_NOT_PERSISTENT)); - p_list->push_back(PropertyInfo(Variant::VECTOR2, "size", PROPERTY_HINT_NONE, "")); + p_list->push_back(PropertyInfo(Variant::OBJECT, PNAME("image"), PROPERTY_HINT_RESOURCE_TYPE, "Image", PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_RESOURCE_NOT_PERSISTENT)); } -void ImageTexture::create_from_image(const Ref<Image> &p_image) { +Ref<ImageTexture> ImageTexture::create_from_image(const Ref<Image> &p_image) { + ERR_FAIL_COND_V_MSG(p_image.is_null() || p_image->is_empty(), Ref<ImageTexture>(), "Invalid image"); + + Ref<ImageTexture> image_texture; + image_texture.instantiate(); + image_texture->set_image(p_image); + return image_texture; +} + +void ImageTexture::set_image(const Ref<Image> &p_image) { ERR_FAIL_COND_MSG(p_image.is_null() || p_image->is_empty(), "Invalid image"); w = p_image->get_width(); h = p_image->get_height(); @@ -303,8 +300,8 @@ bool ImageTexture::is_pixel_opaque(int p_x, int p_y) const { return true; } -void ImageTexture::set_size_override(const Size2 &p_size) { - Size2 s = p_size; +void ImageTexture::set_size_override(const Size2i &p_size) { + Size2i s = p_size; if (s.x != 0) { w = s.x; } @@ -323,9 +320,10 @@ void ImageTexture::set_path(const String &p_path, bool p_take_over) { } void ImageTexture::_bind_methods() { - ClassDB::bind_method(D_METHOD("create_from_image", "image"), &ImageTexture::create_from_image); + ClassDB::bind_static_method("ImageTexture", D_METHOD("create_from_image", "image"), &ImageTexture::create_from_image); ClassDB::bind_method(D_METHOD("get_format"), &ImageTexture::get_format); + ClassDB::bind_method(D_METHOD("set_image", "image"), &ImageTexture::set_image); ClassDB::bind_method(D_METHOD("update", "image"), &ImageTexture::update); ClassDB::bind_method(D_METHOD("set_size_override", "size"), &ImageTexture::set_size_override); } @@ -625,7 +623,7 @@ void PortableCompressedTexture2D::_bind_methods() { ClassDB::bind_static_method("PortableCompressedTexture2D", D_METHOD("is_keeping_all_compressed_buffers"), &PortableCompressedTexture2D::is_keeping_all_compressed_buffers); ADD_PROPERTY(PropertyInfo(Variant::PACKED_BYTE_ARRAY, "_data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "_set_data", "_get_data"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "size_override"), "set_size_override", "get_size_override"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "size_override", PROPERTY_HINT_NONE, "suffix:px"), "set_size_override", "get_size_override"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "keep_compressed_buffer"), "set_keep_compressed_buffer", "is_keeping_compressed_buffer"); BIND_ENUM_CONSTANT(COMPRESSION_MODE_LOSSLESS); @@ -1058,7 +1056,7 @@ CompressedTexture2D::~CompressedTexture2D() { } } -RES ResourceFormatLoaderCompressedTexture2D::load(const String &p_path, const String &p_original_path, Error *r_error, bool p_use_sub_threads, float *r_progress, CacheMode p_cache_mode) { +Ref<Resource> ResourceFormatLoaderCompressedTexture2D::load(const String &p_path, const String &p_original_path, Error *r_error, bool p_use_sub_threads, float *r_progress, CacheMode p_cache_mode) { Ref<CompressedTexture2D> st; st.instantiate(); Error err = st->load(p_path); @@ -1066,7 +1064,7 @@ RES ResourceFormatLoaderCompressedTexture2D::load(const String &p_path, const St *r_error = err; } if (err != OK) { - return RES(); + return Ref<Resource>(); } return st; @@ -1416,7 +1414,7 @@ CompressedTexture3D::~CompressedTexture3D() { ///////////////////////////// -RES ResourceFormatLoaderCompressedTexture3D::load(const String &p_path, const String &p_original_path, Error *r_error, bool p_use_sub_threads, float *r_progress, CacheMode p_cache_mode) { +Ref<Resource> ResourceFormatLoaderCompressedTexture3D::load(const String &p_path, const String &p_original_path, Error *r_error, bool p_use_sub_threads, float *r_progress, CacheMode p_cache_mode) { Ref<CompressedTexture3D> st; st.instantiate(); Error err = st->load(p_path); @@ -1424,7 +1422,7 @@ RES ResourceFormatLoaderCompressedTexture3D::load(const String &p_path, const St *r_error = err; } if (err != OK) { - return RES(); + return Ref<Resource>(); } return st; @@ -1545,8 +1543,8 @@ void AtlasTexture::_bind_methods() { ClassDB::bind_method(D_METHOD("has_filter_clip"), &AtlasTexture::has_filter_clip); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "atlas", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_atlas", "get_atlas"); - ADD_PROPERTY(PropertyInfo(Variant::RECT2, "region"), "set_region", "get_region"); - ADD_PROPERTY(PropertyInfo(Variant::RECT2, "margin"), "set_margin", "get_margin"); + ADD_PROPERTY(PropertyInfo(Variant::RECT2, "region", PROPERTY_HINT_NONE, "suffix:px"), "set_region", "get_region"); + ADD_PROPERTY(PropertyInfo(Variant::RECT2, "margin", PROPERTY_HINT_NONE, "suffix:px"), "set_margin", "get_margin"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "filter_clip"), "set_filter_clip", "has_filter_clip"); } @@ -1717,8 +1715,8 @@ void MeshTexture::draw(RID p_canvas_item, const Point2 &p_pos, const Color &p_mo Transform2D xform; xform.set_origin(p_pos); if (p_transpose) { - SWAP(xform.elements[0][1], xform.elements[1][0]); - SWAP(xform.elements[0][0], xform.elements[1][1]); + SWAP(xform.columns[0][1], xform.columns[1][0]); + SWAP(xform.columns[0][0], xform.columns[1][1]); } RenderingServer::get_singleton()->canvas_item_add_mesh(p_canvas_item, mesh->get_rid(), xform, p_modulate, base_texture->get_rid()); } @@ -1739,8 +1737,8 @@ void MeshTexture::draw_rect(RID p_canvas_item, const Rect2 &p_rect, bool p_tile, xform.set_scale(p_rect.size / size); if (p_transpose) { - SWAP(xform.elements[0][1], xform.elements[1][0]); - SWAP(xform.elements[0][0], xform.elements[1][1]); + SWAP(xform.columns[0][1], xform.columns[1][0]); + SWAP(xform.columns[0][0], xform.columns[1][1]); } RenderingServer::get_singleton()->canvas_item_add_mesh(p_canvas_item, mesh->get_rid(), xform, p_modulate, base_texture->get_rid()); } @@ -1761,8 +1759,8 @@ void MeshTexture::draw_rect_region(RID p_canvas_item, const Rect2 &p_rect, const xform.set_scale(p_rect.size / size); if (p_transpose) { - SWAP(xform.elements[0][1], xform.elements[1][0]); - SWAP(xform.elements[0][0], xform.elements[1][1]); + SWAP(xform.columns[0][1], xform.columns[1][0]); + SWAP(xform.columns[0][0], xform.columns[1][1]); } RenderingServer::get_singleton()->canvas_item_add_mesh(p_canvas_item, mesh->get_rid(), xform, p_modulate, base_texture->get_rid()); } @@ -1787,7 +1785,7 @@ void MeshTexture::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "mesh", PROPERTY_HINT_RESOURCE_TYPE, "Mesh"), "set_mesh", "get_mesh"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "base_texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_base_texture", "get_base_texture"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "image_size", PROPERTY_HINT_RANGE, "0,16384,1"), "set_image_size", "get_image_size"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "image_size", PROPERTY_HINT_RANGE, "0,16384,1,suffix:px"), "set_image_size", "get_image_size"); } MeshTexture::MeshTexture() { @@ -1806,7 +1804,7 @@ void CurveTexture::_bind_methods() { ClassDB::bind_method(D_METHOD("_update"), &CurveTexture::_update); - ADD_PROPERTY(PropertyInfo(Variant::INT, "width", PROPERTY_HINT_RANGE, "1,4096"), "set_width", "get_width"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "width", PROPERTY_HINT_RANGE, "1,4096,suffix:px"), "set_width", "get_width"); ADD_PROPERTY(PropertyInfo(Variant::INT, "texture_mode", PROPERTY_HINT_ENUM, "RGB,Red"), "set_texture_mode", "get_texture_mode"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_curve", "get_curve"); @@ -1954,7 +1952,7 @@ void CurveXYZTexture::_bind_methods() { ClassDB::bind_method(D_METHOD("_update"), &CurveXYZTexture::_update); - ADD_PROPERTY(PropertyInfo(Variant::INT, "width", PROPERTY_HINT_RANGE, "1,4096"), "set_width", "get_width"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "width", PROPERTY_HINT_RANGE, "1,4096,suffix:px"), "set_width", "get_width"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "curve_x", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_curve_x", "get_curve_x"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "curve_y", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_curve_y", "get_curve_y"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "curve_z", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_curve_z", "get_curve_z"); @@ -2160,8 +2158,8 @@ void GradientTexture1D::_bind_methods() { 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::OBJECT, "gradient", PROPERTY_HINT_RESOURCE_TYPE, "Gradient", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_EDITOR_INSTANTIATE_OBJECT), "set_gradient", "get_gradient"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "width", PROPERTY_HINT_RANGE, "1,16384,suffix:px"), "set_width", "get_width"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_hdr"), "set_use_hdr", "is_using_hdr"); } @@ -2249,7 +2247,7 @@ void GradientTexture1D::_update() { } void GradientTexture1D::set_width(int p_width) { - ERR_FAIL_COND(p_width <= 0); + ERR_FAIL_COND_MSG(p_width <= 0 || p_width > 16384, "Texture dimensions have to be within 1 to 16384 range."); width = p_width; _queue_update(); } @@ -2278,6 +2276,8 @@ Ref<Image> GradientTexture1D::get_image() const { return RenderingServer::get_singleton()->texture_2d_get(texture); } +////////////////// + GradientTexture2D::GradientTexture2D() { _queue_update(); } @@ -2299,7 +2299,8 @@ void GradientTexture2D::set_gradient(Ref<Gradient> p_gradient) { if (gradient.is_valid()) { gradient->connect(CoreStringNames::get_singleton()->changed, callable_mp(this, &GradientTexture2D::_queue_update)); } - _queue_update(); + _update(); + emit_changed(); } Ref<Gradient> GradientTexture2D::get_gradient() const { @@ -2410,6 +2411,7 @@ float GradientTexture2D::_get_gradient_offset_at(int x, int y) const { } void GradientTexture2D::set_width(int p_width) { + ERR_FAIL_COND_MSG(p_width <= 0 || p_width > 16384, "Texture dimensions have to be within 1 to 16384 range."); width = p_width; _queue_update(); } @@ -2419,6 +2421,7 @@ int GradientTexture2D::get_width() const { } void GradientTexture2D::set_height(int p_height) { + ERR_FAIL_COND_MSG(p_height <= 0 || p_height > 16384, "Texture dimensions have to be within 1 to 16384 range."); height = p_height; _queue_update(); } @@ -2512,8 +2515,8 @@ void GradientTexture2D::_bind_methods() { ClassDB::bind_method(D_METHOD("_update"), &GradientTexture2D::_update); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "gradient", PROPERTY_HINT_RESOURCE_TYPE, "Gradient", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_EDITOR_INSTANTIATE_OBJECT), "set_gradient", "get_gradient"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "width", PROPERTY_HINT_RANGE, "1,2048"), "set_width", "get_width"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "height", PROPERTY_HINT_RANGE, "1,2048"), "set_height", "get_height"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "width", PROPERTY_HINT_RANGE, "1,2048,or_greater,suffix:px"), "set_width", "get_width"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "height", PROPERTY_HINT_RANGE, "1,2048,or_greater,suffix:px"), "set_height", "get_height"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_hdr"), "set_use_hdr", "is_using_hdr"); ADD_GROUP("Fill", "fill_"); @@ -2833,7 +2836,7 @@ void AnimatedTexture::_bind_methods() { for (int i = 0; i < MAX_FRAMES; i++) { ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "frame_" + itos(i) + "/texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_INTERNAL), "set_frame_texture", "get_frame_texture", i); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "frame_" + itos(i) + "/delay_sec", PROPERTY_HINT_RANGE, "0.0,16.0,0.01", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_INTERNAL), "set_frame_delay", "get_frame_delay", i); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "frame_" + itos(i) + "/delay_sec", PROPERTY_HINT_RANGE, "0.0,16.0,0.01,suffix:s", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_INTERNAL), "set_frame_delay", "get_frame_delay", i); } BIND_CONSTANT(MAX_FRAMES); @@ -3023,12 +3026,12 @@ Error ImageTextureLayered::create_from_images(Vector<Ref<Image>> p_images) { } void ImageTextureLayered::update_layer(const Ref<Image> &p_image, int p_layer) { - ERR_FAIL_COND(texture.is_valid()); - ERR_FAIL_COND(p_image.is_null()); - ERR_FAIL_COND(p_image->get_format() != format); - ERR_FAIL_COND(p_image->get_width() != width || p_image->get_height() != height); - ERR_FAIL_INDEX(p_layer, layers); - ERR_FAIL_COND(p_image->has_mipmaps() != mipmaps); + ERR_FAIL_COND_MSG(texture.is_null(), "Texture is not initialized."); + ERR_FAIL_COND_MSG(p_image.is_null(), "Invalid image."); + ERR_FAIL_COND_MSG(p_image->get_format() != format, "Image format must match texture's image format."); + ERR_FAIL_COND_MSG(p_image->get_width() != width || p_image->get_height() != height, "Image size must match texture's image size."); + ERR_FAIL_COND_MSG(p_image->has_mipmaps() != mipmaps, "Image mipmap configuration must match texture's image mipmap configuration."); + ERR_FAIL_INDEX_MSG(p_layer, layers, "Layer index is out of bounds."); RS::get_singleton()->texture_2d_update(texture, p_image, p_layer); } @@ -3240,7 +3243,7 @@ CompressedTextureLayered::~CompressedTextureLayered() { ///////////////////////////////////////////////// -RES ResourceFormatLoaderCompressedTextureLayered::load(const String &p_path, const String &p_original_path, Error *r_error, bool p_use_sub_threads, float *r_progress, CacheMode p_cache_mode) { +Ref<Resource> ResourceFormatLoaderCompressedTextureLayered::load(const String &p_path, const String &p_original_path, Error *r_error, bool p_use_sub_threads, float *r_progress, CacheMode p_cache_mode) { Ref<CompressedTextureLayered> ct; if (p_path.get_extension().to_lower() == "ctexarray") { Ref<CompressedTexture2DArray> c; @@ -3258,14 +3261,14 @@ RES ResourceFormatLoaderCompressedTextureLayered::load(const String &p_path, con if (r_error) { *r_error = ERR_FILE_UNRECOGNIZED; } - return RES(); + return Ref<Resource>(); } Error err = ct->load(p_path); if (r_error) { *r_error = err; } if (err != OK) { - return RES(); + return Ref<Resource>(); } return ct; @@ -3392,3 +3395,148 @@ CameraTexture::~CameraTexture() { RenderingServer::get_singleton()->free(_texture); } } + +/////////////////////////// + +void PlaceholderTexture2D::set_size(Size2 p_size) { + size = p_size; +} + +int PlaceholderTexture2D::get_width() const { + return size.width; +} + +int PlaceholderTexture2D::get_height() const { + return size.height; +} + +bool PlaceholderTexture2D::has_alpha() const { + return false; +} + +Ref<Image> PlaceholderTexture2D::get_image() const { + return Ref<Image>(); +} + +RID PlaceholderTexture2D::get_rid() const { + return rid; +} + +void PlaceholderTexture2D::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_size", "size"), &PlaceholderTexture2D::set_size); + + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2I, "size", PROPERTY_HINT_NONE, "suffix:px"), "set_size", "get_size"); +} + +PlaceholderTexture2D::PlaceholderTexture2D() { + rid = RS::get_singleton()->texture_2d_placeholder_create(); +} + +PlaceholderTexture2D::~PlaceholderTexture2D() { + RS::get_singleton()->free(rid); +} + +/////////////////////////////////////////////// + +void PlaceholderTexture3D::set_size(const Vector3i &p_size) { + size = p_size; +} + +Vector3i PlaceholderTexture3D::get_size() const { + return size; +} + +Image::Format PlaceholderTexture3D::get_format() const { + return Image::FORMAT_RGB8; +} + +int PlaceholderTexture3D::get_width() const { + return size.x; +} + +int PlaceholderTexture3D::get_height() const { + return size.y; +} + +int PlaceholderTexture3D::get_depth() const { + return size.z; +} + +bool PlaceholderTexture3D::has_mipmaps() const { + return false; +} + +Vector<Ref<Image>> PlaceholderTexture3D::get_data() const { + return Vector<Ref<Image>>(); +} + +void PlaceholderTexture3D::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_size", "size"), &PlaceholderTexture3D::set_size); + ClassDB::bind_method(D_METHOD("get_size"), &PlaceholderTexture3D::get_size); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR3I, "size", PROPERTY_HINT_NONE, "suffix:px"), "set_size", "get_size"); +} + +PlaceholderTexture3D::PlaceholderTexture3D() { + rid = RS::get_singleton()->texture_3d_placeholder_create(); +} +PlaceholderTexture3D::~PlaceholderTexture3D() { + RS::get_singleton()->free(rid); +} + +///////////////////////////////////////////////// + +void PlaceholderTextureLayered::set_size(const Size2i &p_size) { + size = p_size; +} + +Size2i PlaceholderTextureLayered::get_size() const { + return size; +} + +void PlaceholderTextureLayered::set_layers(int p_layers) { + layers = p_layers; +} + +Image::Format PlaceholderTextureLayered::get_format() const { + return Image::FORMAT_RGB8; +} + +TextureLayered::LayeredType PlaceholderTextureLayered::get_layered_type() const { + return layered_type; +} + +int PlaceholderTextureLayered::get_width() const { + return size.x; +} + +int PlaceholderTextureLayered::get_height() const { + return size.y; +} + +int PlaceholderTextureLayered::get_layers() const { + return layers; +} + +bool PlaceholderTextureLayered::has_mipmaps() const { + return false; +} + +Ref<Image> PlaceholderTextureLayered::get_layer_data(int p_layer) const { + return Ref<Image>(); +} + +void PlaceholderTextureLayered::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_size", "size"), &PlaceholderTextureLayered::set_size); + ClassDB::bind_method(D_METHOD("get_size"), &PlaceholderTextureLayered::get_size); + ClassDB::bind_method(D_METHOD("set_layers", "layers"), &PlaceholderTextureLayered::set_layers); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2I, "size", PROPERTY_HINT_NONE, "suffix:px"), "set_size", "get_size"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "layers", PROPERTY_HINT_RANGE, "1,4096"), "set_layers", "get_layers"); +} + +PlaceholderTextureLayered::PlaceholderTextureLayered(LayeredType p_type) { + layered_type = p_type; + rid = RS::get_singleton()->texture_2d_layered_placeholder_create(RS::TextureLayeredType(layered_type)); +} +PlaceholderTextureLayered::~PlaceholderTextureLayered() { + RS::get_singleton()->free(rid); +} diff --git a/scene/resources/texture.h b/scene/resources/texture.h index 525e3ff979..b107a2a70d 100644 --- a/scene/resources/texture.h +++ b/scene/resources/texture.h @@ -110,7 +110,8 @@ protected: static void _bind_methods(); public: - void create_from_image(const Ref<Image> &p_image); + void set_image(const Ref<Image> &p_image); + static Ref<ImageTexture> create_from_image(const Ref<Image> &p_image); Image::Format get_format() const; @@ -129,7 +130,7 @@ public: bool is_pixel_opaque(int p_x, int p_y) const override; - void set_size_override(const Size2 &p_size); + void set_size_override(const Size2i &p_size); virtual void set_path(const String &p_path, bool p_take_over = false) override; @@ -287,7 +288,7 @@ public: class ResourceFormatLoaderCompressedTexture2D : public ResourceFormatLoader { public: - virtual RES load(const String &p_path, const String &p_original_path = "", Error *r_error = nullptr, bool p_use_sub_threads = false, float *r_progress = nullptr, CacheMode p_cache_mode = CACHE_MODE_REUSE); + virtual Ref<Resource> load(const String &p_path, const String &p_original_path = "", Error *r_error = nullptr, bool p_use_sub_threads = false, float *r_progress = nullptr, CacheMode p_cache_mode = CACHE_MODE_REUSE); virtual void get_recognized_extensions(List<String> *p_extensions) const; virtual bool handles_type(const String &p_type) const; virtual String get_resource_type(const String &p_path) const; @@ -552,7 +553,7 @@ public: class ResourceFormatLoaderCompressedTextureLayered : public ResourceFormatLoader { public: - virtual RES load(const String &p_path, const String &p_original_path = "", Error *r_error = nullptr, bool p_use_sub_threads = false, float *r_progress = nullptr, CacheMode p_cache_mode = CACHE_MODE_REUSE); + virtual Ref<Resource> load(const String &p_path, const String &p_original_path = "", Error *r_error = nullptr, bool p_use_sub_threads = false, float *r_progress = nullptr, CacheMode p_cache_mode = CACHE_MODE_REUSE); virtual void get_recognized_extensions(List<String> *p_extensions) const; virtual bool handles_type(const String &p_type) const; virtual String get_resource_type(const String &p_path) const; @@ -673,7 +674,7 @@ public: class ResourceFormatLoaderCompressedTexture3D : public ResourceFormatLoader { public: - virtual RES load(const String &p_path, const String &p_original_path = "", Error *r_error = nullptr, bool p_use_sub_threads = false, float *r_progress = nullptr, CacheMode p_cache_mode = CACHE_MODE_REUSE); + virtual Ref<Resource> load(const String &p_path, const String &p_original_path = "", Error *r_error = nullptr, bool p_use_sub_threads = false, float *r_progress = nullptr, CacheMode p_cache_mode = CACHE_MODE_REUSE); virtual void get_recognized_extensions(List<String> *p_extensions) const; virtual bool handles_type(const String &p_type) const; virtual String get_resource_type(const String &p_path) const; @@ -1011,4 +1012,98 @@ public: ~CameraTexture(); }; +class PlaceholderTexture2D : public Texture2D { + GDCLASS(PlaceholderTexture2D, Texture2D) + + RID rid; + Size2 size = Size2(1, 1); + +protected: + static void _bind_methods(); + +public: + void set_size(Size2 p_size); + + virtual int get_width() const override; + virtual int get_height() const override; + virtual RID get_rid() const override; + virtual bool has_alpha() const override; + + virtual Ref<Image> get_image() const override; + + PlaceholderTexture2D(); + ~PlaceholderTexture2D(); +}; + +class PlaceholderTexture3D : public Texture3D { + GDCLASS(PlaceholderTexture3D, Texture3D) + + RID rid; + Vector3i size = Vector3i(1, 1, 1); + +protected: + static void _bind_methods(); + +public: + void set_size(const Vector3i &p_size); + Vector3i get_size() const; + virtual Image::Format get_format() const override; + virtual int get_width() const override; + virtual int get_height() const override; + virtual int get_depth() const override; + virtual bool has_mipmaps() const override; + virtual Vector<Ref<Image>> get_data() const override; + + PlaceholderTexture3D(); + ~PlaceholderTexture3D(); +}; + +class PlaceholderTextureLayered : public TextureLayered { + GDCLASS(PlaceholderTextureLayered, TextureLayered) + + RID rid; + Size2i size = Size2i(1, 1); + int layers = 1; + LayeredType layered_type = LAYERED_TYPE_2D_ARRAY; + +protected: + static void _bind_methods(); + +public: + void set_size(const Size2i &p_size); + Size2i get_size() const; + void set_layers(int p_layers); + virtual Image::Format get_format() const override; + virtual LayeredType get_layered_type() const override; + virtual int get_width() const override; + virtual int get_height() const override; + virtual int get_layers() const override; + virtual bool has_mipmaps() const override; + virtual Ref<Image> get_layer_data(int p_layer) const override; + + PlaceholderTextureLayered(LayeredType p_type); + ~PlaceholderTextureLayered(); +}; + +class PlaceholderTexture2DArray : public PlaceholderTextureLayered { + GDCLASS(PlaceholderTexture2DArray, PlaceholderTextureLayered) +public: + PlaceholderTexture2DArray() : + PlaceholderTextureLayered(LAYERED_TYPE_2D_ARRAY) {} +}; + +class PlaceholderCubemap : public PlaceholderTextureLayered { + GDCLASS(PlaceholderCubemap, PlaceholderTextureLayered) +public: + PlaceholderCubemap() : + PlaceholderTextureLayered(LAYERED_TYPE_CUBEMAP) {} +}; + +class PlaceholderCubemapArray : public PlaceholderTextureLayered { + GDCLASS(PlaceholderCubemapArray, PlaceholderTextureLayered) +public: + PlaceholderCubemapArray() : + PlaceholderTextureLayered(LAYERED_TYPE_CUBEMAP_ARRAY) {} +}; + #endif diff --git a/scene/resources/theme.cpp b/scene/resources/theme.cpp index 373fbb94ea..39b77568cf 100644 --- a/scene/resources/theme.cpp +++ b/scene/resources/theme.cpp @@ -123,82 +123,64 @@ bool Theme::_get(const StringName &p_name, Variant &r_ret) const { void Theme::_get_property_list(List<PropertyInfo> *p_list) const { List<PropertyInfo> list; - const StringName *key = nullptr; - // Type variations. - while ((key = variation_map.next(key))) { - list.push_back(PropertyInfo(Variant::STRING_NAME, String() + *key + "/base_type")); + for (const KeyValue<StringName, StringName> &E : variation_map) { + list.push_back(PropertyInfo(Variant::STRING_NAME, String() + E.key + "/base_type")); } - key = nullptr; - // Icons. - while ((key = icon_map.next(key))) { - const StringName *key2 = nullptr; - - while ((key2 = icon_map[*key].next(key2))) { - list.push_back(PropertyInfo(Variant::OBJECT, String() + *key + "/icons/" + *key2, PROPERTY_HINT_RESOURCE_TYPE, "Texture2D", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_STORE_IF_NULL)); + for (const KeyValue<StringName, ThemeIconMap> &E : icon_map) { + for (const KeyValue<StringName, Ref<Texture2D>> &F : E.value) { + list.push_back(PropertyInfo(Variant::OBJECT, String() + E.key + "/icons/" + F.key, PROPERTY_HINT_RESOURCE_TYPE, "Texture2D", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_STORE_IF_NULL)); } } - key = nullptr; - // Styles. - while ((key = style_map.next(key))) { - const StringName *key2 = nullptr; - - while ((key2 = style_map[*key].next(key2))) { - list.push_back(PropertyInfo(Variant::OBJECT, String() + *key + "/styles/" + *key2, PROPERTY_HINT_RESOURCE_TYPE, "StyleBox", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_STORE_IF_NULL)); + for (const KeyValue<StringName, ThemeStyleMap> &E : style_map) { + for (const KeyValue<StringName, Ref<StyleBox>> &F : E.value) { + list.push_back(PropertyInfo(Variant::OBJECT, String() + E.key + "/styles/" + F.key, PROPERTY_HINT_RESOURCE_TYPE, "StyleBox", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_STORE_IF_NULL)); } } - key = nullptr; - // Fonts. - while ((key = font_map.next(key))) { - const StringName *key2 = nullptr; - - while ((key2 = font_map[*key].next(key2))) { - list.push_back(PropertyInfo(Variant::OBJECT, String() + *key + "/fonts/" + *key2, PROPERTY_HINT_RESOURCE_TYPE, "Font", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_STORE_IF_NULL)); + for (const KeyValue<StringName, ThemeFontMap> &E : font_map) { + for (const KeyValue<StringName, Ref<Font>> &F : E.value) { + list.push_back(PropertyInfo(Variant::OBJECT, String() + E.key + "/fonts/" + F.key, PROPERTY_HINT_RESOURCE_TYPE, "Font", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_STORE_IF_NULL)); } } - key = nullptr; - // Font sizes. - while ((key = font_size_map.next(key))) { - const StringName *key2 = nullptr; - - while ((key2 = font_size_map[*key].next(key2))) { - list.push_back(PropertyInfo(Variant::INT, String() + *key + "/font_sizes/" + *key2, PROPERTY_HINT_RANGE, "0,256,1,or_greater")); + for (const KeyValue<StringName, ThemeFontSizeMap> &E : font_size_map) { + for (const KeyValue<StringName, int> &F : E.value) { + list.push_back(PropertyInfo(Variant::INT, String() + E.key + "/font_sizes/" + F.key, PROPERTY_HINT_RANGE, "0,256,1,or_greater,suffix:px")); } } - key = nullptr; - // Colors. - while ((key = color_map.next(key))) { - const StringName *key2 = nullptr; - - while ((key2 = color_map[*key].next(key2))) { - list.push_back(PropertyInfo(Variant::COLOR, String() + *key + "/colors/" + *key2)); + for (const KeyValue<StringName, ThemeColorMap> &E : color_map) { + for (const KeyValue<StringName, Color> &F : E.value) { + list.push_back(PropertyInfo(Variant::COLOR, String() + E.key + "/colors/" + F.key)); } } - key = nullptr; - // Constants. - while ((key = constant_map.next(key))) { - const StringName *key2 = nullptr; - - while ((key2 = constant_map[*key].next(key2))) { - list.push_back(PropertyInfo(Variant::INT, String() + *key + "/constants/" + *key2)); + for (const KeyValue<StringName, ThemeConstantMap> &E : constant_map) { + for (const KeyValue<StringName, int> &F : E.value) { + list.push_back(PropertyInfo(Variant::INT, String() + E.key + "/constants/" + F.key)); } } // Sort and store properties. list.sort(); + String prev_type; for (const PropertyInfo &E : list) { + // Add groups for types so that their names are left unchanged in the inspector. + String current_type = E.name.get_slice("/", 0); + if (prev_type != current_type) { + p_list->push_back(PropertyInfo(Variant::NIL, current_type, PROPERTY_HINT_NONE, current_type + "/", PROPERTY_USAGE_GROUP)); + prev_type = current_type; + } + p_list->push_back(E); } } @@ -261,6 +243,27 @@ int Theme::get_fallback_font_size() { return fallback_font_size; } +bool Theme::is_valid_type_name(const String &p_name) { + for (int i = 0; i < p_name.length(); i++) { + if (!is_ascii_identifier_char(p_name[i])) { + return false; + } + } + return true; +} + +bool Theme::is_valid_item_name(const String &p_name) { + if (p_name.is_empty()) { + return false; + } + for (int i = 0; i < p_name.length(); i++) { + if (!is_ascii_identifier_char(p_name[i])) { + return false; + } + } + return true; +} + // Fallback values for theme item types, configurable per theme. void Theme::set_default_base_scale(float p_base_scale) { if (default_base_scale == p_base_scale) { @@ -326,6 +329,9 @@ bool Theme::has_default_font_size() const { // Icons. void Theme::set_icon(const StringName &p_name, const StringName &p_theme_type, const Ref<Texture2D> &p_icon) { + ERR_FAIL_COND_MSG(!is_valid_item_name(p_name), vformat("Invalid item name: '%s'", p_name)); + ERR_FAIL_COND_MSG(!is_valid_type_name(p_theme_type), vformat("Invalid type name: '%s'", p_theme_type)); + bool existing = false; if (icon_map[p_theme_type].has(p_name) && icon_map[p_theme_type][p_name].is_valid()) { existing = true; @@ -358,6 +364,8 @@ bool Theme::has_icon_nocheck(const StringName &p_name, const StringName &p_theme } void Theme::rename_icon(const StringName &p_old_name, const StringName &p_name, const StringName &p_theme_type) { + ERR_FAIL_COND_MSG(!is_valid_item_name(p_name), vformat("Invalid item name: '%s'", p_name)); + ERR_FAIL_COND_MSG(!is_valid_type_name(p_theme_type), vformat("Invalid type name: '%s'", p_theme_type)); ERR_FAIL_COND_MSG(!icon_map.has(p_theme_type), "Cannot rename the icon '" + String(p_old_name) + "' because the node type '" + String(p_theme_type) + "' does not exist."); ERR_FAIL_COND_MSG(icon_map[p_theme_type].has(p_name), "Cannot rename the icon '" + String(p_old_name) + "' because the new name '" + String(p_name) + "' already exists."); ERR_FAIL_COND_MSG(!icon_map[p_theme_type].has(p_old_name), "Cannot rename the icon '" + String(p_old_name) + "' because it does not exist."); @@ -388,18 +396,18 @@ void Theme::get_icon_list(StringName p_theme_type, List<StringName> *p_list) con return; } - const StringName *key = nullptr; - - while ((key = icon_map[p_theme_type].next(key))) { - p_list->push_back(*key); + for (const KeyValue<StringName, Ref<Texture2D>> &E : icon_map[p_theme_type]) { + p_list->push_back(E.key); } } void Theme::add_icon_type(const StringName &p_theme_type) { + ERR_FAIL_COND_MSG(!is_valid_type_name(p_theme_type), vformat("Invalid type name: '%s'", p_theme_type)); + if (icon_map.has(p_theme_type)) { return; } - icon_map[p_theme_type] = HashMap<StringName, Ref<Texture2D>>(); + icon_map[p_theme_type] = ThemeIconMap(); } void Theme::remove_icon_type(const StringName &p_theme_type) { @@ -409,9 +417,8 @@ void Theme::remove_icon_type(const StringName &p_theme_type) { _freeze_change_propagation(); - const StringName *L = nullptr; - while ((L = icon_map[p_theme_type].next(L))) { - Ref<Texture2D> icon = icon_map[p_theme_type][*L]; + for (const KeyValue<StringName, Ref<Texture2D>> &E : icon_map[p_theme_type]) { + Ref<Texture2D> icon = E.value; if (icon.is_valid()) { icon->disconnect("changed", callable_mp(this, &Theme::_emit_theme_changed)); } @@ -425,14 +432,16 @@ void Theme::remove_icon_type(const StringName &p_theme_type) { void Theme::get_icon_type_list(List<StringName> *p_list) const { ERR_FAIL_NULL(p_list); - const StringName *key = nullptr; - while ((key = icon_map.next(key))) { - p_list->push_back(*key); + for (const KeyValue<StringName, ThemeIconMap> &E : icon_map) { + p_list->push_back(E.key); } } // Styleboxes. void Theme::set_stylebox(const StringName &p_name, const StringName &p_theme_type, const Ref<StyleBox> &p_style) { + ERR_FAIL_COND_MSG(!is_valid_item_name(p_name), vformat("Invalid item name: '%s'", p_name)); + ERR_FAIL_COND_MSG(!is_valid_type_name(p_theme_type), vformat("Invalid type name: '%s'", p_theme_type)); + bool existing = false; if (style_map[p_theme_type].has(p_name) && style_map[p_theme_type][p_name].is_valid()) { existing = true; @@ -465,6 +474,8 @@ bool Theme::has_stylebox_nocheck(const StringName &p_name, const StringName &p_t } void Theme::rename_stylebox(const StringName &p_old_name, const StringName &p_name, const StringName &p_theme_type) { + ERR_FAIL_COND_MSG(!is_valid_item_name(p_name), vformat("Invalid item name: '%s'", p_name)); + ERR_FAIL_COND_MSG(!is_valid_type_name(p_theme_type), vformat("Invalid type name: '%s'", p_theme_type)); ERR_FAIL_COND_MSG(!style_map.has(p_theme_type), "Cannot rename the stylebox '" + String(p_old_name) + "' because the node type '" + String(p_theme_type) + "' does not exist."); ERR_FAIL_COND_MSG(style_map[p_theme_type].has(p_name), "Cannot rename the stylebox '" + String(p_old_name) + "' because the new name '" + String(p_name) + "' already exists."); ERR_FAIL_COND_MSG(!style_map[p_theme_type].has(p_old_name), "Cannot rename the stylebox '" + String(p_old_name) + "' because it does not exist."); @@ -495,18 +506,18 @@ void Theme::get_stylebox_list(StringName p_theme_type, List<StringName> *p_list) return; } - const StringName *key = nullptr; - - while ((key = style_map[p_theme_type].next(key))) { - p_list->push_back(*key); + for (const KeyValue<StringName, Ref<StyleBox>> &E : style_map[p_theme_type]) { + p_list->push_back(E.key); } } void Theme::add_stylebox_type(const StringName &p_theme_type) { + ERR_FAIL_COND_MSG(!is_valid_type_name(p_theme_type), vformat("Invalid type name: '%s'", p_theme_type)); + if (style_map.has(p_theme_type)) { return; } - style_map[p_theme_type] = HashMap<StringName, Ref<StyleBox>>(); + style_map[p_theme_type] = ThemeStyleMap(); } void Theme::remove_stylebox_type(const StringName &p_theme_type) { @@ -516,9 +527,8 @@ void Theme::remove_stylebox_type(const StringName &p_theme_type) { _freeze_change_propagation(); - const StringName *L = nullptr; - while ((L = style_map[p_theme_type].next(L))) { - Ref<StyleBox> style = style_map[p_theme_type][*L]; + for (const KeyValue<StringName, Ref<StyleBox>> &E : style_map[p_theme_type]) { + Ref<StyleBox> style = E.value; if (style.is_valid()) { style->disconnect("changed", callable_mp(this, &Theme::_emit_theme_changed)); } @@ -532,14 +542,16 @@ void Theme::remove_stylebox_type(const StringName &p_theme_type) { void Theme::get_stylebox_type_list(List<StringName> *p_list) const { ERR_FAIL_NULL(p_list); - const StringName *key = nullptr; - while ((key = style_map.next(key))) { - p_list->push_back(*key); + for (const KeyValue<StringName, ThemeStyleMap> &E : style_map) { + p_list->push_back(E.key); } } // Fonts. void Theme::set_font(const StringName &p_name, const StringName &p_theme_type, const Ref<Font> &p_font) { + ERR_FAIL_COND_MSG(!is_valid_item_name(p_name), vformat("Invalid item name: '%s'", p_name)); + ERR_FAIL_COND_MSG(!is_valid_type_name(p_theme_type), vformat("Invalid type name: '%s'", p_theme_type)); + bool existing = false; if (font_map[p_theme_type][p_name].is_valid()) { existing = true; @@ -574,6 +586,8 @@ bool Theme::has_font_nocheck(const StringName &p_name, const StringName &p_theme } void Theme::rename_font(const StringName &p_old_name, const StringName &p_name, const StringName &p_theme_type) { + ERR_FAIL_COND_MSG(!is_valid_item_name(p_name), vformat("Invalid item name: '%s'", p_name)); + ERR_FAIL_COND_MSG(!is_valid_type_name(p_theme_type), vformat("Invalid type name: '%s'", p_theme_type)); ERR_FAIL_COND_MSG(!font_map.has(p_theme_type), "Cannot rename the font '" + String(p_old_name) + "' because the node type '" + String(p_theme_type) + "' does not exist."); ERR_FAIL_COND_MSG(font_map[p_theme_type].has(p_name), "Cannot rename the font '" + String(p_old_name) + "' because the new name '" + String(p_name) + "' already exists."); ERR_FAIL_COND_MSG(!font_map[p_theme_type].has(p_old_name), "Cannot rename the font '" + String(p_old_name) + "' because it does not exist."); @@ -604,18 +618,18 @@ void Theme::get_font_list(StringName p_theme_type, List<StringName> *p_list) con return; } - const StringName *key = nullptr; - - while ((key = font_map[p_theme_type].next(key))) { - p_list->push_back(*key); + for (const KeyValue<StringName, Ref<Font>> &E : font_map[p_theme_type]) { + p_list->push_back(E.key); } } void Theme::add_font_type(const StringName &p_theme_type) { + ERR_FAIL_COND_MSG(!is_valid_type_name(p_theme_type), vformat("Invalid type name: '%s'", p_theme_type)); + if (font_map.has(p_theme_type)) { return; } - font_map[p_theme_type] = HashMap<StringName, Ref<Font>>(); + font_map[p_theme_type] = ThemeFontMap(); } void Theme::remove_font_type(const StringName &p_theme_type) { @@ -625,9 +639,8 @@ void Theme::remove_font_type(const StringName &p_theme_type) { _freeze_change_propagation(); - const StringName *L = nullptr; - while ((L = font_map[p_theme_type].next(L))) { - Ref<Font> font = font_map[p_theme_type][*L]; + for (const KeyValue<StringName, Ref<Font>> &E : font_map[p_theme_type]) { + Ref<Font> font = E.value; if (font.is_valid()) { font->disconnect("changed", callable_mp(this, &Theme::_emit_theme_changed)); } @@ -641,14 +654,16 @@ void Theme::remove_font_type(const StringName &p_theme_type) { void Theme::get_font_type_list(List<StringName> *p_list) const { ERR_FAIL_NULL(p_list); - const StringName *key = nullptr; - while ((key = font_map.next(key))) { - p_list->push_back(*key); + for (const KeyValue<StringName, ThemeFontMap> &E : font_map) { + p_list->push_back(E.key); } } // Font sizes. void Theme::set_font_size(const StringName &p_name, const StringName &p_theme_type, int p_font_size) { + ERR_FAIL_COND_MSG(!is_valid_item_name(p_name), vformat("Invalid item name: '%s'", p_name)); + ERR_FAIL_COND_MSG(!is_valid_type_name(p_theme_type), vformat("Invalid type name: '%s'", p_theme_type)); + bool existing = has_font_size_nocheck(p_name, p_theme_type); font_size_map[p_theme_type][p_name] = p_font_size; @@ -674,6 +689,8 @@ bool Theme::has_font_size_nocheck(const StringName &p_name, const StringName &p_ } void Theme::rename_font_size(const StringName &p_old_name, const StringName &p_name, const StringName &p_theme_type) { + ERR_FAIL_COND_MSG(!is_valid_item_name(p_name), vformat("Invalid item name: '%s'", p_name)); + ERR_FAIL_COND_MSG(!is_valid_type_name(p_theme_type), vformat("Invalid type name: '%s'", p_theme_type)); ERR_FAIL_COND_MSG(!font_size_map.has(p_theme_type), "Cannot rename the font size '" + String(p_old_name) + "' because the node type '" + String(p_theme_type) + "' does not exist."); ERR_FAIL_COND_MSG(font_size_map[p_theme_type].has(p_name), "Cannot rename the font size '" + String(p_old_name) + "' because the new name '" + String(p_name) + "' already exists."); ERR_FAIL_COND_MSG(!font_size_map[p_theme_type].has(p_old_name), "Cannot rename the font size '" + String(p_old_name) + "' because it does not exist."); @@ -700,18 +717,18 @@ void Theme::get_font_size_list(StringName p_theme_type, List<StringName> *p_list return; } - const StringName *key = nullptr; - - while ((key = font_size_map[p_theme_type].next(key))) { - p_list->push_back(*key); + for (const KeyValue<StringName, int> &E : font_size_map[p_theme_type]) { + p_list->push_back(E.key); } } void Theme::add_font_size_type(const StringName &p_theme_type) { + ERR_FAIL_COND_MSG(!is_valid_type_name(p_theme_type), vformat("Invalid type name: '%s'", p_theme_type)); + if (font_size_map.has(p_theme_type)) { return; } - font_size_map[p_theme_type] = HashMap<StringName, int>(); + font_size_map[p_theme_type] = ThemeFontSizeMap(); } void Theme::remove_font_size_type(const StringName &p_theme_type) { @@ -725,14 +742,16 @@ void Theme::remove_font_size_type(const StringName &p_theme_type) { void Theme::get_font_size_type_list(List<StringName> *p_list) const { ERR_FAIL_NULL(p_list); - const StringName *key = nullptr; - while ((key = font_size_map.next(key))) { - p_list->push_back(*key); + for (const KeyValue<StringName, ThemeFontSizeMap> &E : font_size_map) { + p_list->push_back(E.key); } } // Colors. void Theme::set_color(const StringName &p_name, const StringName &p_theme_type, const Color &p_color) { + ERR_FAIL_COND_MSG(!is_valid_item_name(p_name), vformat("Invalid item name: '%s'", p_name)); + ERR_FAIL_COND_MSG(!is_valid_type_name(p_theme_type), vformat("Invalid type name: '%s'", p_theme_type)); + bool existing = has_color_nocheck(p_name, p_theme_type); color_map[p_theme_type][p_name] = p_color; @@ -756,6 +775,8 @@ bool Theme::has_color_nocheck(const StringName &p_name, const StringName &p_them } void Theme::rename_color(const StringName &p_old_name, const StringName &p_name, const StringName &p_theme_type) { + ERR_FAIL_COND_MSG(!is_valid_item_name(p_name), vformat("Invalid item name: '%s'", p_name)); + ERR_FAIL_COND_MSG(!is_valid_type_name(p_theme_type), vformat("Invalid type name: '%s'", p_theme_type)); ERR_FAIL_COND_MSG(!color_map.has(p_theme_type), "Cannot rename the color '" + String(p_old_name) + "' because the node type '" + String(p_theme_type) + "' does not exist."); ERR_FAIL_COND_MSG(color_map[p_theme_type].has(p_name), "Cannot rename the color '" + String(p_old_name) + "' because the new name '" + String(p_name) + "' already exists."); ERR_FAIL_COND_MSG(!color_map[p_theme_type].has(p_old_name), "Cannot rename the color '" + String(p_old_name) + "' because it does not exist."); @@ -782,18 +803,18 @@ void Theme::get_color_list(StringName p_theme_type, List<StringName> *p_list) co return; } - const StringName *key = nullptr; - - while ((key = color_map[p_theme_type].next(key))) { - p_list->push_back(*key); + for (const KeyValue<StringName, Color> &E : color_map[p_theme_type]) { + p_list->push_back(E.key); } } void Theme::add_color_type(const StringName &p_theme_type) { + ERR_FAIL_COND_MSG(!is_valid_type_name(p_theme_type), vformat("Invalid type name: '%s'", p_theme_type)); + if (color_map.has(p_theme_type)) { return; } - color_map[p_theme_type] = HashMap<StringName, Color>(); + color_map[p_theme_type] = ThemeColorMap(); } void Theme::remove_color_type(const StringName &p_theme_type) { @@ -807,14 +828,16 @@ void Theme::remove_color_type(const StringName &p_theme_type) { void Theme::get_color_type_list(List<StringName> *p_list) const { ERR_FAIL_NULL(p_list); - const StringName *key = nullptr; - while ((key = color_map.next(key))) { - p_list->push_back(*key); + for (const KeyValue<StringName, ThemeColorMap> &E : color_map) { + p_list->push_back(E.key); } } // Theme constants. void Theme::set_constant(const StringName &p_name, const StringName &p_theme_type, int p_constant) { + ERR_FAIL_COND_MSG(!is_valid_item_name(p_name), vformat("Invalid item name: '%s'", p_name)); + ERR_FAIL_COND_MSG(!is_valid_type_name(p_theme_type), vformat("Invalid type name: '%s'", p_theme_type)); + bool existing = has_constant_nocheck(p_name, p_theme_type); constant_map[p_theme_type][p_name] = p_constant; @@ -838,6 +861,8 @@ bool Theme::has_constant_nocheck(const StringName &p_name, const StringName &p_t } void Theme::rename_constant(const StringName &p_old_name, const StringName &p_name, const StringName &p_theme_type) { + ERR_FAIL_COND_MSG(!is_valid_item_name(p_name), vformat("Invalid item name: '%s'", p_name)); + ERR_FAIL_COND_MSG(!is_valid_type_name(p_theme_type), vformat("Invalid type name: '%s'", p_theme_type)); ERR_FAIL_COND_MSG(!constant_map.has(p_theme_type), "Cannot rename the constant '" + String(p_old_name) + "' because the node type '" + String(p_theme_type) + "' does not exist."); ERR_FAIL_COND_MSG(constant_map[p_theme_type].has(p_name), "Cannot rename the constant '" + String(p_old_name) + "' because the new name '" + String(p_name) + "' already exists."); ERR_FAIL_COND_MSG(!constant_map[p_theme_type].has(p_old_name), "Cannot rename the constant '" + String(p_old_name) + "' because it does not exist."); @@ -864,18 +889,18 @@ void Theme::get_constant_list(StringName p_theme_type, List<StringName> *p_list) return; } - const StringName *key = nullptr; - - while ((key = constant_map[p_theme_type].next(key))) { - p_list->push_back(*key); + for (const KeyValue<StringName, int> &E : constant_map[p_theme_type]) { + p_list->push_back(E.key); } } void Theme::add_constant_type(const StringName &p_theme_type) { + ERR_FAIL_COND_MSG(!is_valid_type_name(p_theme_type), vformat("Invalid type name: '%s'", p_theme_type)); + if (constant_map.has(p_theme_type)) { return; } - constant_map[p_theme_type] = HashMap<StringName, int>(); + constant_map[p_theme_type] = ThemeConstantMap(); } void Theme::remove_constant_type(const StringName &p_theme_type) { @@ -889,9 +914,8 @@ void Theme::remove_constant_type(const StringName &p_theme_type) { void Theme::get_constant_type_list(List<StringName> *p_list) const { ERR_FAIL_NULL(p_list); - const StringName *key = nullptr; - while ((key = constant_map.next(key))) { - p_list->push_back(*key); + for (const KeyValue<StringName, ThemeConstantMap> &E : constant_map) { + p_list->push_back(E.key); } } @@ -1154,6 +1178,8 @@ void Theme::get_theme_item_type_list(DataType p_data_type, List<StringName> *p_l // Theme type variations. void Theme::set_type_variation(const StringName &p_theme_type, const StringName &p_base_type) { + ERR_FAIL_COND_MSG(!is_valid_type_name(p_theme_type), vformat("Invalid type name: '%s'", p_theme_type)); + ERR_FAIL_COND_MSG(!is_valid_type_name(p_base_type), vformat("Invalid type name: '%s'", p_base_type)); ERR_FAIL_COND_MSG(p_theme_type == StringName(), "An empty theme type cannot be marked as a variation of another type."); ERR_FAIL_COND_MSG(ClassDB::class_exists(p_theme_type), "A type associated with a built-in class cannot be marked as a variation of another type."); ERR_FAIL_COND_MSG(p_base_type == StringName(), "An empty theme type cannot be the base type of a variation. Use clear_type_variation() instead if you want to unmark '" + String(p_theme_type) + "' as a variation."); @@ -1246,51 +1272,43 @@ void Theme::remove_type(const StringName &p_theme_type) { void Theme::get_type_list(List<StringName> *p_list) const { ERR_FAIL_NULL(p_list); - Set<StringName> types; - const StringName *key = nullptr; + // This Set guarantees uniqueness. + // Because each map can have the same type defined, but for this method + // we only want one occurrence of each type. + HashSet<StringName> types; // Icons. - while ((key = icon_map.next(key))) { - types.insert(*key); + for (const KeyValue<StringName, ThemeIconMap> &E : icon_map) { + types.insert(E.key); } - key = nullptr; - - // StyleBoxes. - while ((key = style_map.next(key))) { - types.insert(*key); + // Styles. + for (const KeyValue<StringName, ThemeStyleMap> &E : style_map) { + types.insert(E.key); } - key = nullptr; - // Fonts. - while ((key = font_map.next(key))) { - types.insert(*key); + for (const KeyValue<StringName, ThemeFontMap> &E : font_map) { + types.insert(E.key); } - key = nullptr; - // Font sizes. - while ((key = font_size_map.next(key))) { - types.insert(*key); + for (const KeyValue<StringName, ThemeFontSizeMap> &E : font_size_map) { + types.insert(E.key); } - key = nullptr; - // Colors. - while ((key = color_map.next(key))) { - types.insert(*key); + for (const KeyValue<StringName, ThemeColorMap> &E : color_map) { + types.insert(E.key); } - key = nullptr; - // Constants. - while ((key = constant_map.next(key))) { - types.insert(*key); + for (const KeyValue<StringName, ThemeConstantMap> &E : constant_map) { + types.insert(E.key); } - for (Set<StringName>::Element *E = types.front(); E; E = E->next()) { - p_list->push_back(E->get()); + for (const StringName &E : types) { + p_list->push_back(E); } } @@ -1602,75 +1620,62 @@ void Theme::merge_with(const Ref<Theme> &p_other) { // Colors. { - const StringName *K = nullptr; - while ((K = p_other->color_map.next(K))) { - const StringName *L = nullptr; - while ((L = p_other->color_map[*K].next(L))) { - set_color(*L, *K, p_other->color_map[*K][*L]); + for (const KeyValue<StringName, ThemeColorMap> &E : p_other->color_map) { + for (const KeyValue<StringName, Color> &F : E.value) { + set_color(F.key, E.key, F.value); } } } // Constants. { - const StringName *K = nullptr; - while ((K = p_other->constant_map.next(K))) { - const StringName *L = nullptr; - while ((L = p_other->constant_map[*K].next(L))) { - set_constant(*L, *K, p_other->constant_map[*K][*L]); + for (const KeyValue<StringName, ThemeConstantMap> &E : p_other->constant_map) { + for (const KeyValue<StringName, int> &F : E.value) { + set_constant(F.key, E.key, F.value); } } } // Fonts. { - const StringName *K = nullptr; - while ((K = p_other->font_map.next(K))) { - const StringName *L = nullptr; - while ((L = p_other->font_map[*K].next(L))) { - set_font(*L, *K, p_other->font_map[*K][*L]); + for (const KeyValue<StringName, ThemeFontMap> &E : p_other->font_map) { + for (const KeyValue<StringName, Ref<Font>> &F : E.value) { + set_font(F.key, E.key, F.value); } } } // Font sizes. { - const StringName *K = nullptr; - while ((K = p_other->font_size_map.next(K))) { - const StringName *L = nullptr; - while ((L = p_other->font_size_map[*K].next(L))) { - set_font_size(*L, *K, p_other->font_size_map[*K][*L]); + for (const KeyValue<StringName, ThemeFontSizeMap> &E : p_other->font_size_map) { + for (const KeyValue<StringName, int> &F : E.value) { + set_font_size(F.key, E.key, F.value); } } } // Icons. { - const StringName *K = nullptr; - while ((K = p_other->icon_map.next(K))) { - const StringName *L = nullptr; - while ((L = p_other->icon_map[*K].next(L))) { - set_icon(*L, *K, p_other->icon_map[*K][*L]); + for (const KeyValue<StringName, ThemeIconMap> &E : p_other->icon_map) { + for (const KeyValue<StringName, Ref<Texture2D>> &F : E.value) { + set_icon(F.key, E.key, F.value); } } } // Styleboxes. { - const StringName *K = nullptr; - while ((K = p_other->style_map.next(K))) { - const StringName *L = nullptr; - while ((L = p_other->style_map[*K].next(L))) { - set_stylebox(*L, *K, p_other->style_map[*K][*L]); + for (const KeyValue<StringName, ThemeStyleMap> &E : p_other->style_map) { + for (const KeyValue<StringName, Ref<StyleBox>> &F : E.value) { + set_stylebox(F.key, E.key, F.value); } } } // Type variations. { - const StringName *K = nullptr; - while ((K = p_other->variation_map.next(K))) { - set_type_variation(*K, p_other->variation_map[*K]); + for (const KeyValue<StringName, StringName> &E : p_other->variation_map) { + set_type_variation(E.key, E.value); } } @@ -1680,12 +1685,10 @@ void Theme::merge_with(const Ref<Theme> &p_other) { void Theme::clear() { // These items need disconnecting. { - const StringName *K = nullptr; - while ((K = icon_map.next(K))) { - const StringName *L = nullptr; - while ((L = icon_map[*K].next(L))) { - Ref<Texture2D> icon = icon_map[*K][*L]; - if (icon.is_valid()) { + for (const KeyValue<StringName, ThemeIconMap> &E : icon_map) { + for (const KeyValue<StringName, Ref<Texture2D>> &F : E.value) { + if (F.value.is_valid()) { + Ref<Texture2D> icon = F.value; icon->disconnect("changed", callable_mp(this, &Theme::_emit_theme_changed)); } } @@ -1693,12 +1696,10 @@ void Theme::clear() { } { - const StringName *K = nullptr; - while ((K = style_map.next(K))) { - const StringName *L = nullptr; - while ((L = style_map[*K].next(L))) { - Ref<StyleBox> style = style_map[*K][*L]; - if (style.is_valid()) { + for (const KeyValue<StringName, ThemeStyleMap> &E : style_map) { + for (const KeyValue<StringName, Ref<StyleBox>> &F : E.value) { + if (F.value.is_valid()) { + Ref<StyleBox> style = F.value; style->disconnect("changed", callable_mp(this, &Theme::_emit_theme_changed)); } } @@ -1706,12 +1707,10 @@ void Theme::clear() { } { - const StringName *K = nullptr; - while ((K = font_map.next(K))) { - const StringName *L = nullptr; - while ((L = font_map[*K].next(L))) { - Ref<Font> font = font_map[*K][*L]; - if (font.is_valid()) { + for (const KeyValue<StringName, ThemeFontMap> &E : font_map) { + for (const KeyValue<StringName, Ref<Font>> &F : E.value) { + if (F.value.is_valid()) { + Ref<Font> font = F.value; font->disconnect("changed", callable_mp(this, &Theme::_emit_theme_changed)); } } @@ -1819,7 +1818,7 @@ void Theme::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "default_base_scale", PROPERTY_HINT_RANGE, "0.0,2.0,0.01,or_greater"), "set_default_base_scale", "get_default_base_scale"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "default_font", PROPERTY_HINT_RESOURCE_TYPE, "Font"), "set_default_font", "get_default_font"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "default_font_size", PROPERTY_HINT_RANGE, "0,256,1,or_greater"), "set_default_font_size", "get_default_font_size"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "default_font_size", PROPERTY_HINT_RANGE, "0,256,1,or_greater,suffix:px"), "set_default_font_size", "get_default_font_size"); BIND_ENUM_CONSTANT(DATA_TYPE_COLOR); BIND_ENUM_CONSTANT(DATA_TYPE_CONSTANT); diff --git a/scene/resources/theme.h b/scene/resources/theme.h index 9afe05007d..87d7d2fdea 100644 --- a/scene/resources/theme.h +++ b/scene/resources/theme.h @@ -47,6 +47,13 @@ class Theme : public Resource { #endif public: + using ThemeIconMap = HashMap<StringName, Ref<Texture2D>>; + using ThemeStyleMap = HashMap<StringName, Ref<StyleBox>>; + using ThemeFontMap = HashMap<StringName, Ref<Font>>; + using ThemeFontSizeMap = HashMap<StringName, int>; + using ThemeColorMap = HashMap<StringName, Color>; + using ThemeConstantMap = HashMap<StringName, int>; + enum DataType { DATA_TYPE_COLOR, DATA_TYPE_CONSTANT, @@ -62,12 +69,12 @@ private: void _emit_theme_changed(bool p_notify_list_changed = false); - HashMap<StringName, HashMap<StringName, Ref<Texture2D>>> icon_map; - HashMap<StringName, HashMap<StringName, Ref<StyleBox>>> style_map; - HashMap<StringName, HashMap<StringName, Ref<Font>>> font_map; - HashMap<StringName, HashMap<StringName, int>> font_size_map; - HashMap<StringName, HashMap<StringName, Color>> color_map; - HashMap<StringName, HashMap<StringName, int>> constant_map; + HashMap<StringName, ThemeIconMap> icon_map; + HashMap<StringName, ThemeStyleMap> style_map; + HashMap<StringName, ThemeFontMap> font_map; + HashMap<StringName, ThemeFontSizeMap> font_size_map; + HashMap<StringName, ThemeColorMap> color_map; + HashMap<StringName, ThemeConstantMap> constant_map; HashMap<StringName, StringName> variation_map; HashMap<StringName, List<StringName>> variation_base_map; @@ -137,6 +144,9 @@ public: static Ref<Font> get_fallback_font(); static int get_fallback_font_size(); + static bool is_valid_type_name(const String &p_name); + static bool is_valid_item_name(const String &p_name); + void set_default_base_scale(float p_base_scale); float get_default_base_scale() const; bool has_default_base_scale() const; diff --git a/scene/resources/tile_set.cpp b/scene/resources/tile_set.cpp index da9e1ef2f6..22b5ef0108 100644 --- a/scene/resources/tile_set.cpp +++ b/scene/resources/tile_set.cpp @@ -34,7 +34,7 @@ #include "core/io/marshalls.h" #include "core/math/geometry_2d.h" #include "core/templates/local_vector.h" - +#include "core/templates/rb_set.h" #include "scene/2d/navigation_region_2d.h" #include "scene/gui/control.h" #include "scene/resources/convex_polygon_shape_2d.h" @@ -236,6 +236,9 @@ bool TileSet::TerrainsPattern::operator<(const TerrainsPattern &p_terrains_patte return is_valid_bit[i] < p_terrains_pattern.is_valid_bit[i]; } } + if (terrain != p_terrains_pattern.terrain) { + return terrain < p_terrains_pattern.terrain; + } for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { if (is_valid_bit[i] && bits[i] != p_terrains_pattern.bits[i]) { return bits[i] < p_terrains_pattern.bits[i]; @@ -253,10 +256,23 @@ bool TileSet::TerrainsPattern::operator==(const TerrainsPattern &p_terrains_patt return false; } } + if (terrain != p_terrains_pattern.terrain) { + return false; + } return true; } -void TileSet::TerrainsPattern::set_terrain(TileSet::CellNeighbor p_peering_bit, int p_terrain) { +void TileSet::TerrainsPattern::set_terrain(int p_terrain) { + ERR_FAIL_COND(p_terrain < -1); + + terrain = p_terrain; +} + +int TileSet::TerrainsPattern::get_terrain() const { + return terrain; +} + +void TileSet::TerrainsPattern::set_terrain_peering_bit(TileSet::CellNeighbor p_peering_bit, int p_terrain) { ERR_FAIL_COND(p_peering_bit == TileSet::CELL_NEIGHBOR_MAX); ERR_FAIL_COND(!is_valid_bit[p_peering_bit]); ERR_FAIL_COND(p_terrain < -1); @@ -271,25 +287,27 @@ void TileSet::TerrainsPattern::set_terrain(TileSet::CellNeighbor p_peering_bit, bits[p_peering_bit] = p_terrain; } -int TileSet::TerrainsPattern::get_terrain(TileSet::CellNeighbor p_peering_bit) const { +int TileSet::TerrainsPattern::get_terrain_peering_bit(TileSet::CellNeighbor p_peering_bit) const { ERR_FAIL_COND_V(p_peering_bit == TileSet::CELL_NEIGHBOR_MAX, -1); ERR_FAIL_COND_V(!is_valid_bit[p_peering_bit], -1); return bits[p_peering_bit]; } -void TileSet::TerrainsPattern::set_terrains_from_array(Array p_terrains) { - int in_array_index = 0; +void TileSet::TerrainsPattern::from_array(Array p_terrains) { + set_terrain(p_terrains[0]); + int in_array_index = 1; for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { if (is_valid_bit[i]) { - ERR_FAIL_COND(in_array_index >= p_terrains.size()); - set_terrain(TileSet::CellNeighbor(i), p_terrains[in_array_index]); + ERR_FAIL_INDEX(in_array_index, p_terrains.size()); + set_terrain_peering_bit(TileSet::CellNeighbor(i), p_terrains[in_array_index]); in_array_index++; } } } -Array TileSet::TerrainsPattern::get_terrains_as_array() const { +Array TileSet::TerrainsPattern::as_array() const { Array output; + output.push_back(get_terrain()); for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { if (is_valid_bit[i]) { output.push_back(bits[i]); @@ -297,10 +315,11 @@ Array TileSet::TerrainsPattern::get_terrains_as_array() const { } return output; } + TileSet::TerrainsPattern::TerrainsPattern(const TileSet *p_tile_set, int p_terrain_set) { ERR_FAIL_COND(p_terrain_set < 0); for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { - is_valid_bit[i] = (p_tile_set->is_valid_peering_bit_terrain(p_terrain_set, TileSet::CellNeighbor(i))); + is_valid_bit[i] = (p_tile_set->is_valid_terrain_peering_bit(p_terrain_set, TileSet::CellNeighbor(i))); bits[i] = -1; } valid = true; @@ -410,11 +429,16 @@ void TileSet::_update_terrains_cache() { TileSet::TerrainsPattern terrains_pattern = tile_data->get_terrains_pattern(); + // Main terrain. + if (terrains_pattern.get_terrain() >= 0) { + per_terrain_pattern_tiles[terrain_set][terrains_pattern].insert(cell); + } + // Terrain bits. for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { CellNeighbor bit = CellNeighbor(i); - if (is_valid_peering_bit_terrain(terrain_set, bit)) { - int terrain = terrains_pattern.get_terrain(bit); + if (is_valid_terrain_peering_bit(terrain_set, bit)) { + int terrain = terrains_pattern.get_terrain_peering_bit(bit); if (terrain >= 0) { per_terrain_pattern_tiles[terrain_set][terrains_pattern].insert(cell); } @@ -822,7 +846,7 @@ Color TileSet::get_terrain_color(int p_terrain_set, int p_terrain_index) const { return terrain_sets[p_terrain_set].terrains[p_terrain_index].color; } -bool TileSet::is_valid_peering_bit_for_mode(TileSet::TerrainMode p_terrain_mode, TileSet::CellNeighbor p_peering_bit) const { +bool TileSet::is_valid_terrain_peering_bit_for_mode(TileSet::TerrainMode p_terrain_mode, TileSet::CellNeighbor p_peering_bit) const { if (tile_shape == TileSet::TILE_SHAPE_SQUARE) { if (p_terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS_AND_SIDES || p_terrain_mode == TileSet::TERRAIN_MODE_MATCH_SIDES) { if (p_peering_bit == TileSet::CELL_NEIGHBOR_RIGHT_SIDE || @@ -905,13 +929,13 @@ bool TileSet::is_valid_peering_bit_for_mode(TileSet::TerrainMode p_terrain_mode, return false; } -bool TileSet::is_valid_peering_bit_terrain(int p_terrain_set, TileSet::CellNeighbor p_peering_bit) const { +bool TileSet::is_valid_terrain_peering_bit(int p_terrain_set, TileSet::CellNeighbor p_peering_bit) const { if (p_terrain_set < 0 || p_terrain_set >= get_terrain_sets_count()) { return false; } TileSet::TerrainMode terrain_mode = get_terrain_set_mode(p_terrain_set); - return is_valid_peering_bit_for_mode(terrain_mode, p_peering_bit); + return is_valid_terrain_peering_bit_for_mode(terrain_mode, p_peering_bit); } // Navigation @@ -1345,19 +1369,19 @@ int TileSet::get_patterns_count() { return patterns.size(); } -Set<TileSet::TerrainsPattern> TileSet::get_terrains_pattern_set(int p_terrain_set) { - ERR_FAIL_INDEX_V(p_terrain_set, terrain_sets.size(), Set<TileSet::TerrainsPattern>()); +RBSet<TileSet::TerrainsPattern> TileSet::get_terrains_pattern_set(int p_terrain_set) { + ERR_FAIL_INDEX_V(p_terrain_set, terrain_sets.size(), RBSet<TileSet::TerrainsPattern>()); _update_terrains_cache(); - Set<TileSet::TerrainsPattern> output; - for (KeyValue<TileSet::TerrainsPattern, Set<TileMapCell>> kv : per_terrain_pattern_tiles[p_terrain_set]) { + RBSet<TileSet::TerrainsPattern> output; + for (KeyValue<TileSet::TerrainsPattern, RBSet<TileMapCell>> kv : per_terrain_pattern_tiles[p_terrain_set]) { output.insert(kv.key); } return output; } -Set<TileMapCell> TileSet::get_tiles_for_terrains_pattern(int p_terrain_set, TerrainsPattern p_terrain_tile_pattern) { - ERR_FAIL_INDEX_V(p_terrain_set, terrain_sets.size(), Set<TileMapCell>()); +RBSet<TileMapCell> TileSet::get_tiles_for_terrains_pattern(int p_terrain_set, TerrainsPattern p_terrain_tile_pattern) { + ERR_FAIL_INDEX_V(p_terrain_set, terrain_sets.size(), RBSet<TileMapCell>()); _update_terrains_cache(); return per_terrain_pattern_tiles[p_terrain_set][p_terrain_tile_pattern]; } @@ -1368,13 +1392,13 @@ TileMapCell TileSet::get_random_tile_from_terrains_pattern(int p_terrain_set, Ti // Count the sum of probabilities. double sum = 0.0; - Set<TileMapCell> set = per_terrain_pattern_tiles[p_terrain_set][p_terrain_tile_pattern]; - for (Set<TileMapCell>::Element *E = set.front(); E; E = E->next()) { - if (E->get().source_id >= 0) { - Ref<TileSetSource> source = sources[E->get().source_id]; + RBSet<TileMapCell> set = per_terrain_pattern_tiles[p_terrain_set][p_terrain_tile_pattern]; + for (const TileMapCell &E : set) { + if (E.source_id >= 0) { + Ref<TileSetSource> source = sources[E.source_id]; Ref<TileSetAtlasSource> atlas_source = source; if (atlas_source.is_valid()) { - TileData *tile_data = atlas_source->get_tile_data(E->get().get_atlas_coords(), E->get().alternative_tile); + TileData *tile_data = atlas_source->get_tile_data(E.get_atlas_coords(), E.alternative_tile); sum += tile_data->get_probability(); } else { sum += 1.0; @@ -1389,13 +1413,13 @@ TileMapCell TileSet::get_random_tile_from_terrains_pattern(int p_terrain_set, Ti double picked = Math::random(0.0, sum); // Pick the tile. - for (Set<TileMapCell>::Element *E = set.front(); E; E = E->next()) { - if (E->get().source_id >= 0) { - Ref<TileSetSource> source = sources[E->get().source_id]; + for (const TileMapCell &E : set) { + if (E.source_id >= 0) { + Ref<TileSetSource> source = sources[E.source_id]; Ref<TileSetAtlasSource> atlas_source = source; if (atlas_source.is_valid()) { - TileData *tile_data = atlas_source->get_tile_data(E->get().get_atlas_coords(), E->get().alternative_tile); + TileData *tile_data = atlas_source->get_tile_data(E.get_atlas_coords(), E.alternative_tile); count += tile_data->get_probability(); } else { count += 1.0; @@ -1405,7 +1429,7 @@ TileMapCell TileSet::get_random_tile_from_terrains_pattern(int p_terrain_set, Ti } if (count >= picked) { - return E->get(); + return E; } } @@ -1494,26 +1518,48 @@ void TileSet::draw_tile_shape(CanvasItem *p_canvas_item, Transform2D p_transform } } -Vector<Point2> TileSet::get_terrain_bit_polygon(int p_terrain_set, TileSet::CellNeighbor p_bit) { +Vector<Point2> TileSet::get_terrain_polygon(int p_terrain_set) { + if (tile_shape == TileSet::TILE_SHAPE_SQUARE) { + return _get_square_terrain_polygon(tile_size); + } else if (tile_shape == TileSet::TILE_SHAPE_ISOMETRIC) { + return _get_isometric_terrain_polygon(tile_size); + } else { + float overlap = 0.0; + switch (tile_shape) { + case TileSet::TILE_SHAPE_HEXAGON: + overlap = 0.25; + break; + case TileSet::TILE_SHAPE_HALF_OFFSET_SQUARE: + overlap = 0.0; + break; + default: + break; + } + return _get_half_offset_terrain_polygon(tile_size, overlap, tile_offset_axis); + } + return Vector<Point2>(); +} + +Vector<Point2> TileSet::get_terrain_peering_bit_polygon(int p_terrain_set, TileSet::CellNeighbor p_bit) { ERR_FAIL_COND_V(p_terrain_set < 0 || p_terrain_set >= get_terrain_sets_count(), Vector<Point2>()); TileSet::TerrainMode terrain_mode = get_terrain_set_mode(p_terrain_set); if (tile_shape == TileSet::TILE_SHAPE_SQUARE) { if (terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS_AND_SIDES) { - return _get_square_corner_or_side_terrain_bit_polygon(tile_size, p_bit); + return _get_square_corner_or_side_terrain_peering_bit_polygon(tile_size, p_bit); } else if (terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS) { - return _get_square_corner_terrain_bit_polygon(tile_size, p_bit); + return _get_square_corner_terrain_peering_bit_polygon(tile_size, p_bit); } else { // TileData::TERRAIN_MODE_MATCH_SIDES - return _get_square_side_terrain_bit_polygon(tile_size, p_bit); + return _get_square_side_terrain_peering_bit_polygon(tile_size, p_bit); } } else if (tile_shape == TileSet::TILE_SHAPE_ISOMETRIC) { if (terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS_AND_SIDES) { - return _get_isometric_corner_or_side_terrain_bit_polygon(tile_size, p_bit); + return _get_isometric_corner_or_side_terrain_peering_bit_polygon(tile_size, p_bit); } else if (terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS) { - return _get_isometric_corner_terrain_bit_polygon(tile_size, p_bit); + return _get_isometric_corner_terrain_peering_bit_polygon(tile_size, p_bit); } else { // TileData::TERRAIN_MODE_MATCH_SIDES - return _get_isometric_side_terrain_bit_polygon(tile_size, p_bit); + return _get_isometric_side_terrain_peering_bit_polygon(tile_size, p_bit); } } else { float overlap = 0.0; @@ -1528,11 +1574,11 @@ Vector<Point2> TileSet::get_terrain_bit_polygon(int p_terrain_set, TileSet::Cell break; } if (terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS_AND_SIDES) { - return _get_half_offset_corner_or_side_terrain_bit_polygon(tile_size, p_bit, overlap, tile_offset_axis); + return _get_half_offset_corner_or_side_terrain_peering_bit_polygon(tile_size, overlap, tile_offset_axis, p_bit); } else if (terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS) { - return _get_half_offset_corner_terrain_bit_polygon(tile_size, p_bit, overlap, tile_offset_axis); + return _get_half_offset_corner_terrain_peering_bit_polygon(tile_size, overlap, tile_offset_axis, p_bit); } else { // TileData::TERRAIN_MODE_MATCH_SIDES - return _get_half_offset_side_terrain_bit_polygon(tile_size, p_bit, overlap, tile_offset_axis); + return _get_half_offset_side_terrain_peering_bit_polygon(tile_size, overlap, tile_offset_axis, p_bit); } } } @@ -1544,30 +1590,68 @@ void TileSet::draw_terrains(CanvasItem *p_canvas_item, Transform2D p_transform, if (terrain_bits_meshes_dirty) { // Recompute the meshes. - terrain_bits_meshes.clear(); + terrain_peering_bits_meshes.clear(); for (int terrain_mode_index = 0; terrain_mode_index < 3; terrain_mode_index++) { TerrainMode terrain_mode = TerrainMode(terrain_mode_index); + + // Center terrain + Vector<Vector2> polygon; + if (tile_shape == TileSet::TILE_SHAPE_SQUARE) { + polygon = _get_square_terrain_polygon(tile_size); + } else if (tile_shape == TileSet::TILE_SHAPE_ISOMETRIC) { + polygon = _get_isometric_terrain_polygon(tile_size); + } else { + float overlap = 0.0; + switch (tile_shape) { + case TileSet::TILE_SHAPE_HEXAGON: + overlap = 0.25; + break; + case TileSet::TILE_SHAPE_HALF_OFFSET_SQUARE: + overlap = 0.0; + break; + default: + break; + } + polygon = _get_half_offset_terrain_polygon(tile_size, overlap, tile_offset_axis); + } + { + Ref<ArrayMesh> mesh; + mesh.instantiate(); + Vector<Vector2> uvs; + uvs.resize(polygon.size()); + Vector<Color> colors; + colors.resize(polygon.size()); + colors.fill(Color(1.0, 1.0, 1.0, 1.0)); + Array a; + a.resize(Mesh::ARRAY_MAX); + a[Mesh::ARRAY_VERTEX] = polygon; + a[Mesh::ARRAY_TEX_UV] = uvs; + a[Mesh::ARRAY_COLOR] = colors; + a[Mesh::ARRAY_INDEX] = Geometry2D::triangulate_polygon(polygon); + mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, a, Array(), Dictionary(), Mesh::ARRAY_FLAG_USE_2D_VERTICES); + terrain_meshes[terrain_mode] = mesh; + } + // Peering bits for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { CellNeighbor bit = CellNeighbor(i); - if (is_valid_peering_bit_for_mode(terrain_mode, bit)) { - Vector<Vector2> polygon; + if (is_valid_terrain_peering_bit_for_mode(terrain_mode, bit)) { if (tile_shape == TileSet::TILE_SHAPE_SQUARE) { if (terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS_AND_SIDES) { - polygon = _get_square_corner_or_side_terrain_bit_polygon(tile_size, bit); + polygon = _get_square_corner_or_side_terrain_peering_bit_polygon(tile_size, bit); } else if (terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS) { - polygon = _get_square_corner_terrain_bit_polygon(tile_size, bit); + polygon = _get_square_corner_terrain_peering_bit_polygon(tile_size, bit); } else { // TileData::TERRAIN_MODE_MATCH_SIDES - polygon = _get_square_side_terrain_bit_polygon(tile_size, bit); + polygon = _get_square_side_terrain_peering_bit_polygon(tile_size, bit); } } else if (tile_shape == TileSet::TILE_SHAPE_ISOMETRIC) { if (terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS_AND_SIDES) { - polygon = _get_isometric_corner_or_side_terrain_bit_polygon(tile_size, bit); + polygon = _get_isometric_corner_or_side_terrain_peering_bit_polygon(tile_size, bit); } else if (terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS) { - polygon = _get_isometric_corner_terrain_bit_polygon(tile_size, bit); + polygon = _get_isometric_corner_terrain_peering_bit_polygon(tile_size, bit); } else { // TileData::TERRAIN_MODE_MATCH_SIDES - polygon = _get_isometric_side_terrain_bit_polygon(tile_size, bit); + polygon = _get_isometric_side_terrain_peering_bit_polygon(tile_size, bit); } } else { float overlap = 0.0; @@ -1582,29 +1666,30 @@ void TileSet::draw_terrains(CanvasItem *p_canvas_item, Transform2D p_transform, break; } if (terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS_AND_SIDES) { - polygon = _get_half_offset_corner_or_side_terrain_bit_polygon(tile_size, bit, overlap, tile_offset_axis); + polygon = _get_half_offset_corner_or_side_terrain_peering_bit_polygon(tile_size, overlap, tile_offset_axis, bit); } else if (terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS) { - polygon = _get_half_offset_corner_terrain_bit_polygon(tile_size, bit, overlap, tile_offset_axis); + polygon = _get_half_offset_corner_terrain_peering_bit_polygon(tile_size, overlap, tile_offset_axis, bit); } else { // TileData::TERRAIN_MODE_MATCH_SIDES - polygon = _get_half_offset_side_terrain_bit_polygon(tile_size, bit, overlap, tile_offset_axis); + polygon = _get_half_offset_side_terrain_peering_bit_polygon(tile_size, overlap, tile_offset_axis, bit); } } - - Ref<ArrayMesh> mesh; - mesh.instantiate(); - Vector<Vector2> uvs; - uvs.resize(polygon.size()); - Vector<Color> colors; - colors.resize(polygon.size()); - colors.fill(Color(1.0, 1.0, 1.0, 1.0)); - Array a; - a.resize(Mesh::ARRAY_MAX); - a[Mesh::ARRAY_VERTEX] = polygon; - a[Mesh::ARRAY_TEX_UV] = uvs; - a[Mesh::ARRAY_COLOR] = colors; - a[Mesh::ARRAY_INDEX] = Geometry2D::triangulate_polygon(polygon); - mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, a, Array(), Dictionary(), Mesh::ARRAY_FLAG_USE_2D_VERTICES); - terrain_bits_meshes[terrain_mode][bit] = mesh; + { + Ref<ArrayMesh> mesh; + mesh.instantiate(); + Vector<Vector2> uvs; + uvs.resize(polygon.size()); + Vector<Color> colors; + colors.resize(polygon.size()); + colors.fill(Color(1.0, 1.0, 1.0, 1.0)); + Array a; + a.resize(Mesh::ARRAY_MAX); + a[Mesh::ARRAY_VERTEX] = polygon; + a[Mesh::ARRAY_TEX_UV] = uvs; + a[Mesh::ARRAY_COLOR] = colors; + a[Mesh::ARRAY_INDEX] = Geometry2D::triangulate_polygon(polygon); + mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, a, Array(), Dictionary(), Mesh::ARRAY_FLAG_USE_2D_VERTICES); + terrain_peering_bits_meshes[terrain_mode][bit] = mesh; + } } } } @@ -1618,14 +1703,21 @@ void TileSet::draw_terrains(CanvasItem *p_canvas_item, Transform2D p_transform, TileSet::TerrainMode terrain_mode = get_terrain_set_mode(terrain_set); RenderingServer::get_singleton()->canvas_item_add_set_transform(p_canvas_item->get_canvas_item(), p_transform); + int terrain_id = p_tile_data->get_terrain(); + if (terrain_id >= 0) { + Color color = get_terrain_color(terrain_set, terrain_id); + color.a = TERRAIN_ALPHA; + p_canvas_item->draw_mesh(terrain_meshes[terrain_mode], Ref<Texture2D>(), Transform2D(), color); + } + for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { CellNeighbor bit = CellNeighbor(i); - if (is_valid_peering_bit_terrain(terrain_set, bit)) { - int terrain_id = p_tile_data->get_peering_bit_terrain(bit); + if (is_valid_terrain_peering_bit(terrain_set, bit)) { + terrain_id = p_tile_data->get_terrain_peering_bit(bit); if (terrain_id >= 0) { Color color = get_terrain_color(terrain_set, terrain_id); color.a = TERRAIN_ALPHA; - p_canvas_item->draw_mesh(terrain_bits_meshes[terrain_mode][bit], Ref<Texture2D>(), Transform2D(), color); + p_canvas_item->draw_mesh(terrain_peering_bits_meshes[terrain_mode][bit], Ref<Texture2D>(), Transform2D(), color); } } } @@ -1670,13 +1762,16 @@ Vector<Vector<Ref<Texture2D>>> TileSet::generate_terrains_icons(Size2i p_size) { for (int terrain = 0; terrain < get_terrains_count(terrain_set); terrain++) { bit_counts[terrain] = 0; } + if (tile_data->get_terrain() >= 0) { + bit_counts[tile_data->get_terrain()] += 10; + } for (int terrain_bit = 0; terrain_bit < TileSet::CELL_NEIGHBOR_MAX; terrain_bit++) { TileSet::CellNeighbor cell_neighbor = TileSet::CellNeighbor(terrain_bit); - if (is_valid_peering_bit_terrain(terrain_set, cell_neighbor)) { - int terrain = tile_data->get_peering_bit_terrain(cell_neighbor); + if (is_valid_terrain_peering_bit(terrain_set, cell_neighbor)) { + int terrain = tile_data->get_terrain_peering_bit(cell_neighbor); if (terrain >= 0) { if (terrain >= (int)bit_counts.size()) { - WARN_PRINT(vformat("Invalid peering bit terrain: %d", terrain)); + WARN_PRINT(vformat("Invalid terrain peering bit: %d", terrain)); } else { bit_counts[terrain] += 1; } @@ -1706,19 +1801,16 @@ Vector<Vector<Ref<Texture2D>>> TileSet::generate_terrains_icons(Size2i p_size) { if (counts[terrain_set][terrain].count > 0) { // Get the best tile. Ref<Texture2D> texture = counts[terrain_set][terrain].texture; - Rect2 region = counts[terrain_set][terrain].region; + Rect2i region = counts[terrain_set][terrain].region; image->create(region.size.x, region.size.y, false, Image::FORMAT_RGBA8); - image->blit_rect(texture->get_image(), region, Point2()); + image->blit_rect(texture->get_image(), region, Point2i()); image->resize(p_size.x, p_size.y, Image::INTERPOLATE_NEAREST); } else { image->create(1, 1, false, Image::FORMAT_RGBA8); image->set_pixel(0, 0, get_terrain_color(terrain_set, terrain)); } - Ref<ImageTexture> icon; - icon.instantiate(); - icon->create_from_image(image); + Ref<ImageTexture> icon = ImageTexture::create_from_image(image); icon->set_size_override(p_size); - output.write[terrain_set].write[terrain] = icon; } } @@ -1730,7 +1822,17 @@ void TileSet::_source_changed() { emit_changed(); } -Vector<Point2> TileSet::_get_square_corner_or_side_terrain_bit_polygon(Vector2i p_size, TileSet::CellNeighbor p_bit) { +Vector<Point2> TileSet::_get_square_terrain_polygon(Vector2i p_size) { + Rect2 rect(-Vector2(p_size) / 6.0, Vector2(p_size) / 3.0); + return { + rect.position, + Vector2(rect.get_end().x, rect.position.y), + rect.get_end(), + Vector2(rect.position.x, rect.get_end().y) + }; +} + +Vector<Point2> TileSet::_get_square_corner_or_side_terrain_peering_bit_polygon(Vector2i p_size, TileSet::CellNeighbor p_bit) { Rect2 bit_rect; bit_rect.size = Vector2(p_size) / 3; switch (p_bit) { @@ -1773,7 +1875,7 @@ Vector<Point2> TileSet::_get_square_corner_or_side_terrain_bit_polygon(Vector2i return polygon; } -Vector<Point2> TileSet::_get_square_corner_terrain_bit_polygon(Vector2i p_size, TileSet::CellNeighbor p_bit) { +Vector<Point2> TileSet::_get_square_corner_terrain_peering_bit_polygon(Vector2i p_size, TileSet::CellNeighbor p_bit) { Vector2 unit = Vector2(p_size) / 6.0; Vector<Vector2> polygon; switch (p_bit) { @@ -1815,7 +1917,7 @@ Vector<Point2> TileSet::_get_square_corner_terrain_bit_polygon(Vector2i p_size, return polygon; } -Vector<Point2> TileSet::_get_square_side_terrain_bit_polygon(Vector2i p_size, TileSet::CellNeighbor p_bit) { +Vector<Point2> TileSet::_get_square_side_terrain_peering_bit_polygon(Vector2i p_size, TileSet::CellNeighbor p_bit) { Vector2 unit = Vector2(p_size) / 6.0; Vector<Vector2> polygon; switch (p_bit) { @@ -1849,7 +1951,17 @@ Vector<Point2> TileSet::_get_square_side_terrain_bit_polygon(Vector2i p_size, Ti return polygon; } -Vector<Point2> TileSet::_get_isometric_corner_or_side_terrain_bit_polygon(Vector2i p_size, TileSet::CellNeighbor p_bit) { +Vector<Point2> TileSet::_get_isometric_terrain_polygon(Vector2i p_size) { + Vector2 unit = Vector2(p_size) / 6.0; + return { + Vector2(1, 0) * unit, + Vector2(0, 1) * unit, + Vector2(-1, 0) * unit, + Vector2(0, -1) * unit, + }; +} + +Vector<Point2> TileSet::_get_isometric_corner_or_side_terrain_peering_bit_polygon(Vector2i p_size, TileSet::CellNeighbor p_bit) { Vector2 unit = Vector2(p_size) / 6.0; Vector<Vector2> polygon; switch (p_bit) { @@ -1907,7 +2019,7 @@ Vector<Point2> TileSet::_get_isometric_corner_or_side_terrain_bit_polygon(Vector return polygon; } -Vector<Point2> TileSet::_get_isometric_corner_terrain_bit_polygon(Vector2i p_size, TileSet::CellNeighbor p_bit) { +Vector<Point2> TileSet::_get_isometric_corner_terrain_peering_bit_polygon(Vector2i p_size, TileSet::CellNeighbor p_bit) { Vector2 unit = Vector2(p_size) / 6.0; Vector<Vector2> polygon; switch (p_bit) { @@ -1949,7 +2061,7 @@ Vector<Point2> TileSet::_get_isometric_corner_terrain_bit_polygon(Vector2i p_siz return polygon; } -Vector<Point2> TileSet::_get_isometric_side_terrain_bit_polygon(Vector2i p_size, TileSet::CellNeighbor p_bit) { +Vector<Point2> TileSet::_get_isometric_side_terrain_peering_bit_polygon(Vector2i p_size, TileSet::CellNeighbor p_bit) { Vector2 unit = Vector2(p_size) / 6.0; Vector<Vector2> polygon; switch (p_bit) { @@ -1983,7 +2095,30 @@ Vector<Point2> TileSet::_get_isometric_side_terrain_bit_polygon(Vector2i p_size, return polygon; } -Vector<Point2> TileSet::_get_half_offset_corner_or_side_terrain_bit_polygon(Vector2i p_size, TileSet::CellNeighbor p_bit, float p_overlap, TileSet::TileOffsetAxis p_offset_axis) { +Vector<Point2> TileSet::_get_half_offset_terrain_polygon(Vector2i p_size, float p_overlap, TileSet::TileOffsetAxis p_offset_axis) { + Vector2 unit = Vector2(p_size) / 6.0; + if (p_offset_axis == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) { + return { + Vector2(1, 1.0 - p_overlap * 2.0) * unit, + Vector2(0, 1) * unit, + Vector2(-1, 1.0 - p_overlap * 2.0) * unit, + Vector2(-1, -1.0 + p_overlap * 2.0) * unit, + Vector2(0, -1) * unit, + Vector2(1, -1.0 + p_overlap * 2.0) * unit, + }; + } else { + return { + Vector2(1, 0) * unit, + Vector2(1.0 - p_overlap * 2.0, -1) * unit, + Vector2(-1.0 + p_overlap * 2.0, -1) * unit, + Vector2(-1, 0) * unit, + Vector2(-1.0 + p_overlap * 2.0, 1) * unit, + Vector2(1.0 - p_overlap * 2.0, 1) * unit, + }; + } +} + +Vector<Point2> TileSet::_get_half_offset_corner_or_side_terrain_peering_bit_polygon(Vector2i p_size, float p_overlap, TileSet::TileOffsetAxis p_offset_axis, TileSet::CellNeighbor p_bit) { Vector<Vector2> point_list = { Vector2(3, (3.0 * (1.0 - p_overlap * 2.0)) / 2.0), Vector2(3, 3.0 * (1.0 - p_overlap * 2.0)), @@ -2006,12 +2141,11 @@ Vector<Point2> TileSet::_get_half_offset_corner_or_side_terrain_bit_polygon(Vect }; Vector2 unit = Vector2(p_size) / 6.0; - for (int i = 0; i < point_list.size(); i++) { - point_list.write[i] = point_list[i] * unit; - } - Vector<Vector2> polygon; if (p_offset_axis == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) { + for (int i = 0; i < point_list.size(); i++) { + point_list.write[i] = point_list[i] * unit; + } switch (p_bit) { case TileSet::CELL_NEIGHBOR_RIGHT_SIDE: polygon.push_back(point_list[17]); @@ -2071,10 +2205,8 @@ Vector<Point2> TileSet::_get_half_offset_corner_or_side_terrain_bit_polygon(Vect break; } } else { - if (p_offset_axis == TileSet::TILE_OFFSET_AXIS_VERTICAL) { - for (int i = 0; i < point_list.size(); i++) { - point_list.write[i] = Vector2(point_list[i].y, point_list[i].x); - } + for (int i = 0; i < point_list.size(); i++) { + point_list.write[i] = Vector2(point_list[i].y, point_list[i].x) * unit; } switch (p_bit) { case TileSet::CELL_NEIGHBOR_RIGHT_CORNER: @@ -2144,7 +2276,7 @@ Vector<Point2> TileSet::_get_half_offset_corner_or_side_terrain_bit_polygon(Vect return polygon; } -Vector<Point2> TileSet::_get_half_offset_corner_terrain_bit_polygon(Vector2i p_size, TileSet::CellNeighbor p_bit, float p_overlap, TileSet::TileOffsetAxis p_offset_axis) { +Vector<Point2> TileSet::_get_half_offset_corner_terrain_peering_bit_polygon(Vector2i p_size, float p_overlap, TileSet::TileOffsetAxis p_offset_axis, TileSet::CellNeighbor p_bit) { Vector<Vector2> point_list = { Vector2(3, 0), Vector2(3, 3.0 * (1.0 - p_overlap * 2.0)), @@ -2161,12 +2293,11 @@ Vector<Point2> TileSet::_get_half_offset_corner_terrain_bit_polygon(Vector2i p_s }; Vector2 unit = Vector2(p_size) / 6.0; - for (int i = 0; i < point_list.size(); i++) { - point_list.write[i] = point_list[i] * unit; - } - Vector<Vector2> polygon; if (p_offset_axis == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) { + for (int i = 0; i < point_list.size(); i++) { + point_list.write[i] = point_list[i] * unit; + } switch (p_bit) { case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER: polygon.push_back(point_list[0]); @@ -2202,10 +2333,8 @@ Vector<Point2> TileSet::_get_half_offset_corner_terrain_bit_polygon(Vector2i p_s break; } } else { - if (p_offset_axis == TileSet::TILE_OFFSET_AXIS_VERTICAL) { - for (int i = 0; i < point_list.size(); i++) { - point_list.write[i] = Vector2(point_list[i].y, point_list[i].x); - } + for (int i = 0; i < point_list.size(); i++) { + point_list.write[i] = Vector2(point_list[i].y, point_list[i].x) * unit; } switch (p_bit) { case TileSet::CELL_NEIGHBOR_RIGHT_CORNER: @@ -2251,7 +2380,7 @@ Vector<Point2> TileSet::_get_half_offset_corner_terrain_bit_polygon(Vector2i p_s return polygon; } -Vector<Point2> TileSet::_get_half_offset_side_terrain_bit_polygon(Vector2i p_size, TileSet::CellNeighbor p_bit, float p_overlap, TileSet::TileOffsetAxis p_offset_axis) { +Vector<Point2> TileSet::_get_half_offset_side_terrain_peering_bit_polygon(Vector2i p_size, float p_overlap, TileSet::TileOffsetAxis p_offset_axis, TileSet::CellNeighbor p_bit) { Vector<Vector2> point_list = { Vector2(3, 3.0 * (1.0 - p_overlap * 2.0)), Vector2(0, 3), @@ -2262,12 +2391,11 @@ Vector<Point2> TileSet::_get_half_offset_side_terrain_bit_polygon(Vector2i p_siz }; Vector2 unit = Vector2(p_size) / 6.0; - for (int i = 0; i < point_list.size(); i++) { - point_list.write[i] = point_list[i] * unit; - } - Vector<Vector2> polygon; if (p_offset_axis == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) { + for (int i = 0; i < point_list.size(); i++) { + point_list.write[i] = point_list[i] * unit; + } switch (p_bit) { case TileSet::CELL_NEIGHBOR_RIGHT_SIDE: polygon.push_back(point_list[5]); @@ -2297,10 +2425,8 @@ Vector<Point2> TileSet::_get_half_offset_side_terrain_bit_polygon(Vector2i p_siz break; } } else { - if (p_offset_axis == TileSet::TILE_OFFSET_AXIS_VERTICAL) { - for (int i = 0; i < point_list.size(); i++) { - point_list.write[i] = Vector2(point_list[i].y, point_list[i].x); - } + for (int i = 0; i < point_list.size(); i++) { + point_list.write[i] = Vector2(point_list[i].y, point_list[i].x) * unit; } switch (p_bit) { case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE: @@ -2391,7 +2517,7 @@ void TileSet::_compatibility_conversion() { value_array.push_back(alternative_tile); if (!compatibility_tilemap_mapping.has(E.key)) { - compatibility_tilemap_mapping[E.key] = Map<Array, Array>(); + compatibility_tilemap_mapping[E.key] = RBMap<Array, Array>(); } compatibility_tilemap_mapping[E.key][key_array] = value_array; compatibility_tilemap_mapping_tile_modes[E.key] = COMPATIBILITY_TILE_MODE_SINGLE_TILE; @@ -2483,7 +2609,7 @@ void TileSet::_compatibility_conversion() { value_array.push_back(alternative_tile); if (!compatibility_tilemap_mapping.has(E.key)) { - compatibility_tilemap_mapping[E.key] = Map<Array, Array>(); + compatibility_tilemap_mapping[E.key] = RBMap<Array, Array>(); } compatibility_tilemap_mapping[E.key][key_array] = value_array; compatibility_tilemap_mapping_tile_modes[E.key] = COMPATIBILITY_TILE_MODE_ATLAS_TILE; @@ -2571,7 +2697,7 @@ void TileSet::_compatibility_conversion() { for (const KeyValue<int, CompatibilityTileData *> &E : compatibility_data) { memdelete(E.value); } - compatibility_data = Map<int, CompatibilityTileData *>(); + compatibility_data = HashMap<int, CompatibilityTileData *>(); } Array TileSet::compatibility_tilemap_map(int p_tile_id, Vector2i p_coords, bool p_flip_h, bool p_flip_v, bool p_transpose) { @@ -2622,12 +2748,12 @@ bool TileSet::_set(const StringName &p_name, const Variant &p_value) { // Get or create the compatibility object CompatibilityTileData *ctd; - Map<int, CompatibilityTileData *>::Element *E = compatibility_data.find(id); + HashMap<int, CompatibilityTileData *>::Iterator E = compatibility_data.find(id); if (!E) { ctd = memnew(CompatibilityTileData); compatibility_data.insert(id, ctd); } else { - ctd = E->get(); + ctd = E->value; } if (components.size() < 2) { @@ -3099,7 +3225,7 @@ void TileSet::_get_property_list(List<PropertyInfo> *p_list) const { // Terrains. p_list->push_back(PropertyInfo(Variant::NIL, "Terrains", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_GROUP)); for (int terrain_set_index = 0; terrain_set_index < terrain_sets.size(); terrain_set_index++) { - p_list->push_back(PropertyInfo(Variant::INT, vformat("terrain_set_%d/mode", terrain_set_index), PROPERTY_HINT_ENUM, "Match corners and sides,Match corners,Match sides")); + p_list->push_back(PropertyInfo(Variant::INT, vformat("terrain_set_%d/mode", terrain_set_index), PROPERTY_HINT_ENUM, "Match Corners and Sides,Match Corners,Match Sides")); p_list->push_back(PropertyInfo(Variant::NIL, vformat("terrain_set_%d/terrains", terrain_set_index), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_ARRAY, vformat("terrain_set_%d/terrain_", terrain_set_index))); for (int terrain_index = 0; terrain_index < terrain_sets[terrain_set_index].terrains.size(); terrain_index++) { p_list->push_back(PropertyInfo(Variant::STRING, vformat("terrain_set_%d/terrain_%d/name", terrain_set_index, terrain_index))); @@ -3175,7 +3301,7 @@ void TileSet::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::INT, "tile_shape", PROPERTY_HINT_ENUM, "Square,Isometric,Half-Offset Square,Hexagon"), "set_tile_shape", "get_tile_shape"); ADD_PROPERTY(PropertyInfo(Variant::INT, "tile_layout", PROPERTY_HINT_ENUM, "Stacked,Stacked Offset,Stairs Right,Stairs Down,Diamond Right,Diamond Down"), "set_tile_layout", "get_tile_layout"); ADD_PROPERTY(PropertyInfo(Variant::INT, "tile_offset_axis", PROPERTY_HINT_ENUM, "Horizontal Offset,Vertical Offset"), "set_tile_offset_axis", "get_tile_offset_axis"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR2I, "tile_size"), "set_tile_size", "get_tile_size"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2I, "tile_size", PROPERTY_HINT_NONE, "suffix:px"), "set_tile_size", "get_tile_size"); // Rendering. ClassDB::bind_method(D_METHOD("set_uv_clipping", "uv_clipping"), &TileSet::set_uv_clipping); @@ -3264,16 +3390,10 @@ void TileSet::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "uv_clipping"), "set_uv_clipping", "is_uv_clipping"); ADD_ARRAY("occlusion_layers", "occlusion_layer_"); - ADD_GROUP("Physics", ""); + ADD_GROUP("", ""); ADD_ARRAY("physics_layers", "physics_layer_"); - - ADD_GROUP("Terrains", ""); ADD_ARRAY("terrain_sets", "terrain_set_"); - - ADD_GROUP("Navigation", ""); ADD_ARRAY("navigation_layers", "navigation_layer_"); - - ADD_GROUP("Custom Data", ""); ADD_ARRAY("custom_data_layers", "custom_data_layer_"); // -- Enum binding -- @@ -3807,7 +3927,7 @@ void TileSetAtlasSource::_get_property_list(List<PropertyInfo> *p_list) const { tile_property_list.push_back(property_info); // animation_frames_count. - tile_property_list.push_back(PropertyInfo(Variant::INT, "animation_frames_count", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NETWORK)); + tile_property_list.push_back(PropertyInfo(Variant::INT, "animation_frames_count", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE)); // animation_frame_*. bool store_durations = tiles[E_tile.key].animation_frames_durations.size() >= 2; @@ -4279,9 +4399,9 @@ void TileSetAtlasSource::_bind_methods() { 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::VECTOR2I, "margins", PROPERTY_HINT_NONE, "suffix:px", PROPERTY_USAGE_NO_EDITOR), "set_margins", "get_margins"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2I, "separation", PROPERTY_HINT_NONE, "suffix:px", PROPERTY_USAGE_NO_EDITOR), "set_separation", "get_separation"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2I, "texture_region_size", PROPERTY_HINT_NONE, "suffix:px", 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 @@ -4471,7 +4591,7 @@ void TileSetAtlasSource::_update_padded_texture() { if (!padded_texture.is_valid()) { padded_texture.instantiate(); } - padded_texture->create_from_image(image); + padded_texture->set_image(image); emit_changed(); } @@ -4801,6 +4921,10 @@ void TileData::move_terrain(int p_terrain_set, int p_from_index, int p_to_pos) { void TileData::remove_terrain(int p_terrain_set, int p_index) { if (terrain_set == p_terrain_set) { + if (terrain == p_index) { + terrain = -1; + } + for (int i = 0; i < 16; i++) { if (terrain_peering_bits[i] == p_index) { terrain_peering_bits[i] = -1; @@ -4935,11 +5059,11 @@ Vector2i TileData::get_texture_offset() const { return tex_offset; } -void TileData::set_material(Ref<ShaderMaterial> p_material) { +void TileData::set_material(Ref<Material> p_material) { material = p_material; emit_signal(SNAME("changed")); } -Ref<ShaderMaterial> TileData::get_material() const { +Ref<Material> TileData::get_material() const { return material; } @@ -5120,36 +5244,51 @@ int TileData::get_terrain_set() const { return terrain_set; } -void TileData::set_peering_bit_terrain(TileSet::CellNeighbor p_peering_bit, int p_terrain_index) { +void TileData::set_terrain(int p_terrain) { + ERR_FAIL_COND(terrain_set < 0); + ERR_FAIL_COND(p_terrain < -1); + if (tile_set) { + ERR_FAIL_COND(p_terrain >= tile_set->get_terrains_count(terrain_set)); + } + terrain = p_terrain; + emit_signal(SNAME("changed")); +} + +int TileData::get_terrain() const { + return terrain; +} + +void TileData::set_terrain_peering_bit(TileSet::CellNeighbor p_peering_bit, int p_terrain_index) { ERR_FAIL_INDEX(p_peering_bit, TileSet::CellNeighbor::CELL_NEIGHBOR_MAX); ERR_FAIL_COND(terrain_set < 0); ERR_FAIL_COND(p_terrain_index < -1); if (tile_set) { ERR_FAIL_COND(p_terrain_index >= tile_set->get_terrains_count(terrain_set)); - ERR_FAIL_COND(!is_valid_peering_bit_terrain(p_peering_bit)); + ERR_FAIL_COND(!is_valid_terrain_peering_bit(p_peering_bit)); } terrain_peering_bits[p_peering_bit] = p_terrain_index; emit_signal(SNAME("changed")); } -int TileData::get_peering_bit_terrain(TileSet::CellNeighbor p_peering_bit) const { - ERR_FAIL_COND_V(!is_valid_peering_bit_terrain(p_peering_bit), -1); +int TileData::get_terrain_peering_bit(TileSet::CellNeighbor p_peering_bit) const { + ERR_FAIL_COND_V(!is_valid_terrain_peering_bit(p_peering_bit), -1); return terrain_peering_bits[p_peering_bit]; } -bool TileData::is_valid_peering_bit_terrain(TileSet::CellNeighbor p_peering_bit) const { +bool TileData::is_valid_terrain_peering_bit(TileSet::CellNeighbor p_peering_bit) const { ERR_FAIL_COND_V(!tile_set, false); - return tile_set->is_valid_peering_bit_terrain(terrain_set, p_peering_bit); + return tile_set->is_valid_terrain_peering_bit(terrain_set, p_peering_bit); } TileSet::TerrainsPattern TileData::get_terrains_pattern() const { ERR_FAIL_COND_V(!tile_set, TileSet::TerrainsPattern()); TileSet::TerrainsPattern output(tile_set, terrain_set); + output.set_terrain(terrain); for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { - if (tile_set->is_valid_peering_bit_terrain(terrain_set, TileSet::CellNeighbor(i))) { - output.set_terrain(TileSet::CellNeighbor(i), get_peering_bit_terrain(TileSet::CellNeighbor(i))); + if (tile_set->is_valid_terrain_peering_bit(terrain_set, TileSet::CellNeighbor(i))) { + output.set_terrain_peering_bit(TileSet::CellNeighbor(i), get_terrain_peering_bit(TileSet::CellNeighbor(i))); } } return output; @@ -5299,7 +5438,7 @@ bool TileData::_set(const StringName &p_name, const Variant &p_value) { for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { TileSet::CellNeighbor bit = TileSet::CellNeighbor(i); if (components[1] == TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[i]) { - set_peering_bit_terrain(bit, p_value); + set_terrain_peering_bit(bit, p_value); return true; } } @@ -5461,9 +5600,9 @@ void TileData::_get_property_list(List<PropertyInfo> *p_list) const { p_list->push_back(PropertyInfo(Variant::NIL, "Terrains", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_GROUP)); for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { TileSet::CellNeighbor bit = TileSet::CellNeighbor(i); - if (is_valid_peering_bit_terrain(bit)) { + if (is_valid_terrain_peering_bit(bit)) { property_info = PropertyInfo(Variant::INT, "terrains_peering_bit/" + String(TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[i])); - if (get_peering_bit_terrain(bit) == -1) { + if (get_terrain_peering_bit(bit) == -1) { property_info.usage ^= PROPERTY_USAGE_STORAGE; } p_list->push_back(property_info); @@ -5537,8 +5676,10 @@ void TileData::_bind_methods() { // Terrain ClassDB::bind_method(D_METHOD("set_terrain_set", "terrain_set"), &TileData::set_terrain_set); ClassDB::bind_method(D_METHOD("get_terrain_set"), &TileData::get_terrain_set); - ClassDB::bind_method(D_METHOD("set_peering_bit_terrain", "peering_bit", "terrain"), &TileData::set_peering_bit_terrain); - ClassDB::bind_method(D_METHOD("get_peering_bit_terrain", "peering_bit"), &TileData::get_peering_bit_terrain); + ClassDB::bind_method(D_METHOD("set_terrain", "terrain"), &TileData::set_terrain); + ClassDB::bind_method(D_METHOD("get_terrain"), &TileData::get_terrain); + ClassDB::bind_method(D_METHOD("set_terrain_peering_bit", "peering_bit", "terrain"), &TileData::set_terrain_peering_bit); + ClassDB::bind_method(D_METHOD("get_terrain_peering_bit", "peering_bit"), &TileData::get_terrain_peering_bit); // Navigation ClassDB::bind_method(D_METHOD("set_navigation_polygon", "layer_id", "navigation_polygon"), &TileData::set_navigation_polygon); @@ -5558,14 +5699,15 @@ void TileData::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "flip_h"), "set_flip_h", "get_flip_h"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "flip_v"), "set_flip_v", "get_flip_v"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "transpose"), "set_transpose", "get_transpose"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR2I, "texture_offset"), "set_texture_offset", "get_texture_offset"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2I, "texture_offset", PROPERTY_HINT_NONE, "suffix:px"), "set_texture_offset", "get_texture_offset"); ADD_PROPERTY(PropertyInfo(Variant::COLOR, "modulate"), "set_modulate", "get_modulate"); - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "material", PROPERTY_HINT_RESOURCE_TYPE, "ShaderMaterial"), "set_material", "get_material"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "material", PROPERTY_HINT_RESOURCE_TYPE, "CanvasItemMaterial,ShaderMaterial"), "set_material", "get_material"); ADD_PROPERTY(PropertyInfo(Variant::INT, "z_index"), "set_z_index", "get_z_index"); ADD_PROPERTY(PropertyInfo(Variant::INT, "y_sort_origin"), "set_y_sort_origin", "get_y_sort_origin"); ADD_GROUP("Terrains", ""); ADD_PROPERTY(PropertyInfo(Variant::INT, "terrain_set"), "set_terrain_set", "get_terrain_set"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "terrain"), "set_terrain", "get_terrain"); ADD_GROUP("Miscellaneous", ""); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "probability"), "set_probability", "get_probability"); diff --git a/scene/resources/tile_set.h b/scene/resources/tile_set.h index 95de46c9ab..7368d2bd87 100644 --- a/scene/resources/tile_set.h +++ b/scene/resources/tile_set.h @@ -34,6 +34,7 @@ #include "core/io/resource.h" #include "core/object/object.h" #include "core/templates/local_vector.h" +#include "core/templates/rb_set.h" #include "scene/2d/light_occluder_2d.h" #include "scene/2d/navigation_region_2d.h" #include "scene/main/canvas_item.h" @@ -69,6 +70,11 @@ union TileMapCell { }; uint64_t _u64t; + + static uint32_t hash(const TileMapCell &p_hash) { + return hash_one_uint64(p_hash._u64t); + } + TileMapCell(int p_source_id = -1, Vector2i p_atlas_coords = Vector2i(-1, -1), int p_alternative_tile = -1) { // default are INVALID_SOURCE, INVALID_ATLAS_COORDS, INVALID_TILE_ALTERNATIVE source_id = p_source_id; set_atlas_coords(p_atlas_coords); @@ -103,13 +109,16 @@ union TileMapCell { bool operator!=(const TileMapCell &p_other) const { return !(source_id == p_other.source_id && coord_x == p_other.coord_x && coord_y == p_other.coord_y && alternative_tile == p_other.alternative_tile); } + bool operator==(const TileMapCell &p_other) const { + return source_id == p_other.source_id && coord_x == p_other.coord_x && coord_y == p_other.coord_y && alternative_tile == p_other.alternative_tile; + } }; class TileMapPattern : public Resource { GDCLASS(TileMapPattern, Resource); Vector2i size; - Map<Vector2i, TileMapCell> pattern; + HashMap<Vector2i, TileMapCell> pattern; void _set_tile_data(const Vector<int> &p_data); Vector<int> _get_tile_data() const; @@ -155,7 +164,7 @@ private: String name; Ref<Texture2D> texture; Vector2 tex_offset; - Ref<ShaderMaterial> material; + Ref<Material> material; Rect2 region; int tile_mode = 0; Color modulate = Color(1, 1, 1); @@ -166,11 +175,11 @@ private: Size2i autotile_tile_size = Size2i(16, 16); int autotile_spacing = 0; - Map<Vector2i, int> autotile_bitmask_flags; - Map<Vector2i, Ref<OccluderPolygon2D>> autotile_occluder_map; - Map<Vector2i, Ref<NavigationPolygon>> autotile_navpoly_map; - Map<Vector2i, int> autotile_priority_map; - Map<Vector2i, int> autotile_z_index_map; + HashMap<Vector2i, int> autotile_bitmask_flags; + HashMap<Vector2i, Ref<OccluderPolygon2D>> autotile_occluder_map; + HashMap<Vector2i, Ref<NavigationPolygon>> autotile_navpoly_map; + HashMap<Vector2i, int> autotile_priority_map; + HashMap<Vector2i, int> autotile_z_index_map; Vector<CompatibilityShapeData> shapes; Ref<OccluderPolygon2D> occluder; @@ -186,9 +195,9 @@ private: COMPATIBILITY_TILE_MODE_ATLAS_TILE, }; - Map<int, CompatibilityTileData *> compatibility_data; - Map<int, int> compatibility_tilemap_mapping_tile_modes; - Map<int, Map<Array, Array>> compatibility_tilemap_mapping; + HashMap<int, CompatibilityTileData *> compatibility_data; + HashMap<int, int> compatibility_tilemap_mapping_tile_modes; + HashMap<int, RBMap<Array, Array>> compatibility_tilemap_mapping; void _compatibility_conversion(); @@ -256,6 +265,7 @@ public: class TerrainsPattern { bool valid = false; + int terrain = -1; int bits[TileSet::CELL_NEIGHBOR_MAX]; bool is_valid_bit[TileSet::CELL_NEIGHBOR_MAX]; @@ -268,11 +278,14 @@ public: bool operator<(const TerrainsPattern &p_terrains_pattern) const; bool operator==(const TerrainsPattern &p_terrains_pattern) const; - void set_terrain(TileSet::CellNeighbor p_peering_bit, int p_terrain); - int get_terrain(TileSet::CellNeighbor p_peering_bit) const; + void set_terrain(int p_terrain); + int get_terrain() const; + + void set_terrain_peering_bit(TileSet::CellNeighbor p_peering_bit, int p_terrain); + int get_terrain_peering_bit(TileSet::CellNeighbor p_peering_bit) const; - void set_terrains_from_array(Array p_terrains); - Array get_terrains_as_array() const; + void from_array(Array p_terrains); + Array as_array() const; TerrainsPattern(const TileSet *p_tile_set, int p_terrain_set); TerrainsPattern() {} @@ -324,10 +337,11 @@ private: }; Vector<TerrainSet> terrain_sets; - Map<TerrainMode, Map<CellNeighbor, Ref<ArrayMesh>>> terrain_bits_meshes; + HashMap<TerrainMode, Ref<ArrayMesh>> terrain_meshes; + HashMap<TerrainMode, HashMap<CellNeighbor, Ref<ArrayMesh>>> terrain_peering_bits_meshes; bool terrain_bits_meshes_dirty = true; - LocalVector<Map<TileSet::TerrainsPattern, Set<TileMapCell>>> per_terrain_pattern_tiles; // Cached data. + LocalVector<RBMap<TileSet::TerrainsPattern, RBSet<TileMapCell>>> per_terrain_pattern_tiles; // Cached data. bool terrains_cache_dirty = true; void _update_terrains_cache(); @@ -343,10 +357,10 @@ private: Variant::Type type = Variant::NIL; }; Vector<CustomDataLayer> custom_data_layers; - Map<String, int> custom_data_layers_by_name; + HashMap<String, int> custom_data_layers_by_name; // Per Atlas source data. - Map<int, Ref<TileSetSource>> sources; + HashMap<int, Ref<TileSetSource>> sources; Vector<int> source_ids; int next_source_id = 0; // --------------------- @@ -357,22 +371,25 @@ private: void _source_changed(); // Tile proxies - Map<int, int> source_level_proxies; - Map<Array, Array> coords_level_proxies; - Map<Array, Array> alternative_level_proxies; + RBMap<int, int> source_level_proxies; + RBMap<Array, Array> coords_level_proxies; + RBMap<Array, Array> alternative_level_proxies; // Helpers - Vector<Point2> _get_square_corner_or_side_terrain_bit_polygon(Vector2i p_size, TileSet::CellNeighbor p_bit); - Vector<Point2> _get_square_corner_terrain_bit_polygon(Vector2i p_size, TileSet::CellNeighbor p_bit); - Vector<Point2> _get_square_side_terrain_bit_polygon(Vector2i p_size, TileSet::CellNeighbor p_bit); + Vector<Point2> _get_square_terrain_polygon(Vector2i p_size); + Vector<Point2> _get_square_corner_or_side_terrain_peering_bit_polygon(Vector2i p_size, TileSet::CellNeighbor p_bit); + Vector<Point2> _get_square_corner_terrain_peering_bit_polygon(Vector2i p_size, TileSet::CellNeighbor p_bit); + Vector<Point2> _get_square_side_terrain_peering_bit_polygon(Vector2i p_size, TileSet::CellNeighbor p_bit); - Vector<Point2> _get_isometric_corner_or_side_terrain_bit_polygon(Vector2i p_size, TileSet::CellNeighbor p_bit); - Vector<Point2> _get_isometric_corner_terrain_bit_polygon(Vector2i p_size, TileSet::CellNeighbor p_bit); - Vector<Point2> _get_isometric_side_terrain_bit_polygon(Vector2i p_size, TileSet::CellNeighbor p_bit); + Vector<Point2> _get_isometric_terrain_polygon(Vector2i p_size); + Vector<Point2> _get_isometric_corner_or_side_terrain_peering_bit_polygon(Vector2i p_size, TileSet::CellNeighbor p_bit); + Vector<Point2> _get_isometric_corner_terrain_peering_bit_polygon(Vector2i p_size, TileSet::CellNeighbor p_bit); + Vector<Point2> _get_isometric_side_terrain_peering_bit_polygon(Vector2i p_size, TileSet::CellNeighbor p_bit); - Vector<Point2> _get_half_offset_corner_or_side_terrain_bit_polygon(Vector2i p_size, TileSet::CellNeighbor p_bit, float p_overlap, TileSet::TileOffsetAxis p_offset_axis); - Vector<Point2> _get_half_offset_corner_terrain_bit_polygon(Vector2i p_size, TileSet::CellNeighbor p_bit, float p_overlap, TileSet::TileOffsetAxis p_offset_axis); - Vector<Point2> _get_half_offset_side_terrain_bit_polygon(Vector2i p_size, TileSet::CellNeighbor p_bit, float p_overlap, TileSet::TileOffsetAxis p_offset_axis); + Vector<Point2> _get_half_offset_terrain_polygon(Vector2i p_size, float p_overlap, TileSet::TileOffsetAxis p_offset_axis); + Vector<Point2> _get_half_offset_corner_or_side_terrain_peering_bit_polygon(Vector2i p_size, float p_overlap, TileSet::TileOffsetAxis p_offset_axis, TileSet::CellNeighbor p_bit); + Vector<Point2> _get_half_offset_corner_terrain_peering_bit_polygon(Vector2i p_size, float p_overlap, TileSet::TileOffsetAxis p_offset_axis, TileSet::CellNeighbor p_bit); + Vector<Point2> _get_half_offset_side_terrain_peering_bit_polygon(Vector2i p_size, float p_overlap, TileSet::TileOffsetAxis p_offset_axis, TileSet::CellNeighbor p_bit); protected: static void _bind_methods(); @@ -445,8 +462,8 @@ public: String get_terrain_name(int p_terrain_set, int p_terrain_index) const; void set_terrain_color(int p_terrain_set, int p_terrain_index, Color p_color); Color get_terrain_color(int p_terrain_set, int p_terrain_index) const; - bool is_valid_peering_bit_for_mode(TileSet::TerrainMode p_terrain_mode, TileSet::CellNeighbor p_peering_bit) const; - bool is_valid_peering_bit_terrain(int p_terrain_set, TileSet::CellNeighbor p_peering_bit) const; + bool is_valid_terrain_peering_bit_for_mode(TileSet::TerrainMode p_terrain_mode, TileSet::CellNeighbor p_peering_bit) const; + bool is_valid_terrain_peering_bit(int p_terrain_set, TileSet::CellNeighbor p_peering_bit) const; // Navigation int get_navigation_layers_count() const; @@ -499,15 +516,16 @@ public: int get_patterns_count(); // Terrains. - Set<TerrainsPattern> get_terrains_pattern_set(int p_terrain_set); - Set<TileMapCell> get_tiles_for_terrains_pattern(int p_terrain_set, TerrainsPattern p_terrain_tile_pattern); + RBSet<TerrainsPattern> get_terrains_pattern_set(int p_terrain_set); + RBSet<TileMapCell> get_tiles_for_terrains_pattern(int p_terrain_set, TerrainsPattern p_terrain_tile_pattern); TileMapCell get_random_tile_from_terrains_pattern(int p_terrain_set, TerrainsPattern p_terrain_tile_pattern); // Helpers Vector<Vector2> get_tile_shape_polygon(); void draw_tile_shape(CanvasItem *p_canvas_item, Transform2D p_transform, Color p_color, bool p_filled = false, Ref<Texture2D> p_texture = Ref<Texture2D>()); - Vector<Point2> get_terrain_bit_polygon(int p_terrain_set, TileSet::CellNeighbor p_bit); + Vector<Point2> get_terrain_polygon(int p_terrain_set); + Vector<Point2> get_terrain_peering_bit_polygon(int p_terrain_set, TileSet::CellNeighbor p_bit); void draw_terrains(CanvasItem *p_canvas_item, Transform2D p_transform, const TileData *p_tile_data); Vector<Vector<Ref<Texture2D>>> generate_terrains_icons(Size2i p_size); @@ -579,7 +597,7 @@ private: LocalVector<real_t> animation_frames_durations; // Alternatives - Map<int, TileData *> alternatives; + HashMap<int, TileData *> alternatives; Vector<int> alternatives_ids; int next_alternative_id = 1; }; @@ -589,9 +607,9 @@ private: Vector2i separation; Size2i texture_region_size = Size2i(16, 16); - Map<Vector2i, TileAlternativesData> tiles; + HashMap<Vector2i, TileAlternativesData> tiles; Vector<Vector2i> tiles_ids; - Map<Vector2i, Vector2i> _coords_mapping_cache; // Maps any coordinate to the including tile + HashMap<Vector2i, Vector2i> _coords_mapping_cache; // Maps any coordinate to the including tile TileData *_get_atlas_tile_data(Vector2i p_atlas_coords, int p_alternative_tile); const TileData *_get_atlas_tile_data(Vector2i p_atlas_coords, int p_alternative_tile) const; @@ -716,7 +734,7 @@ private: bool display_placeholder = false; }; Vector<int> scenes_ids; - Map<int, SceneData> scenes; + HashMap<int, SceneData> scenes; int next_scene_id = 1; void _compute_next_alternative_id(); @@ -765,7 +783,7 @@ private: bool flip_v = false; bool transpose = false; Vector2i tex_offset = Vector2i(); - Ref<ShaderMaterial> material = Ref<ShaderMaterial>(); + Ref<Material> material = Ref<Material>(); Color modulate = Color(1.0, 1.0, 1.0, 1.0); int z_index = 0; int y_sort_origin = 0; @@ -789,6 +807,7 @@ private: // Terrain int terrain_set = -1; + int terrain = -1; int terrain_peering_bits[16] = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }; // Navigation @@ -845,8 +864,8 @@ public: void set_texture_offset(Vector2i p_texture_offset); Vector2i get_texture_offset() const; - void set_material(Ref<ShaderMaterial> p_material); - Ref<ShaderMaterial> get_material() const; + void set_material(Ref<Material> p_material); + Ref<Material> get_material() const; void set_modulate(Color p_modulate); Color get_modulate() const; void set_z_index(int p_z_index); @@ -878,9 +897,11 @@ public: // Terrain void set_terrain_set(int p_terrain_id); int get_terrain_set() const; - void set_peering_bit_terrain(TileSet::CellNeighbor p_peering_bit, int p_terrain_id); - int get_peering_bit_terrain(TileSet::CellNeighbor p_peering_bit) const; - bool is_valid_peering_bit_terrain(TileSet::CellNeighbor p_peering_bit) const; + void set_terrain(int p_terrain_id); + int get_terrain() const; + void set_terrain_peering_bit(TileSet::CellNeighbor p_peering_bit, int p_terrain_id); + int get_terrain_peering_bit(TileSet::CellNeighbor p_peering_bit) const; + bool is_valid_terrain_peering_bit(TileSet::CellNeighbor p_peering_bit) const; TileSet::TerrainsPattern get_terrains_pattern() const; // Not exposed. diff --git a/scene/resources/visual_shader.cpp b/scene/resources/visual_shader.cpp index 129f76702e..b68cce9dda 100644 --- a/scene/resources/visual_shader.cpp +++ b/scene/resources/visual_shader.cpp @@ -30,6 +30,7 @@ #include "visual_shader.h" +#include "core/templates/rb_map.h" #include "core/templates/vmap.h" #include "servers/rendering/shader_types.h" #include "visual_shader_nodes.h" @@ -74,6 +75,10 @@ void VisualShaderNode::set_input_port_default_value(int p_port, const Variant &p Vector3 pv = p_prev_value; value = pv.x; } break; + case Variant::QUATERNION: { + Quaternion pv = p_prev_value; + value = pv.x; + } break; default: break; } @@ -94,6 +99,10 @@ void VisualShaderNode::set_input_port_default_value(int p_port, const Variant &p Vector3 pv = p_prev_value; value = (int)pv.x; } break; + case Variant::QUATERNION: { + Quaternion pv = p_prev_value; + value = (int)pv.x; + } break; default: break; } @@ -115,6 +124,10 @@ void VisualShaderNode::set_input_port_default_value(int p_port, const Variant &p Vector3 pv = p_prev_value; value = Vector2(pv.x, pv.y); } break; + case Variant::QUATERNION: { + Quaternion pv = p_prev_value; + value = Vector2(pv.x, pv.y); + } break; default: break; } @@ -136,6 +149,35 @@ void VisualShaderNode::set_input_port_default_value(int p_port, const Variant &p case Variant::VECTOR3: { value = p_prev_value; } break; + case Variant::QUATERNION: { + Quaternion pv = p_prev_value; + value = Vector3(pv.x, pv.y, pv.z); + } break; + default: + break; + } + } break; + case Variant::QUATERNION: { + switch (p_prev_value.get_type()) { + case Variant::INT: { + float pv = (float)(int)p_prev_value; + value = Quaternion(pv, pv, pv, pv); + } break; + case Variant::FLOAT: { + float pv = p_prev_value; + value = Quaternion(pv, pv, pv, pv); + } break; + case Variant::VECTOR2: { + Vector2 pv = p_prev_value; + value = Quaternion(pv.x, pv.y, pv.y, pv.y); + } break; + case Variant::VECTOR3: { + Vector3 pv = p_prev_value; + value = Quaternion(pv.x, pv.y, pv.z, pv.z); + } break; + case Variant::QUATERNION: { + value = p_prev_value; + } break; default: break; } @@ -253,6 +295,9 @@ int VisualShaderNode::get_expanded_output_port_count() const { case PORT_TYPE_VECTOR_3D: { count2 += 3; } break; + case PORT_TYPE_VECTOR_4D: { + count2 += 4; + } break; default: break; } @@ -301,8 +346,8 @@ Vector<StringName> VisualShaderNode::get_editable_properties() const { return Vector<StringName>(); } -Map<StringName, String> VisualShaderNode::get_editable_properties_names() const { - return Map<StringName, String>(); +HashMap<StringName, String> VisualShaderNode::get_editable_properties_names() const { + return HashMap<StringName, String>(); } Array VisualShaderNode::get_default_input_values() const { @@ -360,6 +405,7 @@ void VisualShaderNode::_bind_methods() { BIND_ENUM_CONSTANT(PORT_TYPE_SCALAR_INT); BIND_ENUM_CONSTANT(PORT_TYPE_VECTOR_2D); BIND_ENUM_CONSTANT(PORT_TYPE_VECTOR_3D); + BIND_ENUM_CONSTANT(PORT_TYPE_VECTOR_4D); BIND_ENUM_CONSTANT(PORT_TYPE_BOOLEAN); BIND_ENUM_CONSTANT(PORT_TYPE_TRANSFORM); BIND_ENUM_CONSTANT(PORT_TYPE_SAMPLER); @@ -667,86 +713,6 @@ VisualShader::VaryingType VisualShader::get_varying_type(const String &p_name) { return varyings[p_name].type; } -void VisualShader::set_engine_version(const Dictionary &p_engine_version) { - ERR_FAIL_COND(!p_engine_version.has("major")); - ERR_FAIL_COND(!p_engine_version.has("minor")); - engine_version["major"] = p_engine_version["major"]; - engine_version["minor"] = p_engine_version["minor"]; -} - -Dictionary VisualShader::get_engine_version() const { - return engine_version; -} - -#ifndef DISABLE_DEPRECATED - -void VisualShader::update_engine_version(const Dictionary &p_new_version) { - if (engine_version.is_empty()) { // before 4.0 - for (int i = 0; i < TYPE_MAX; i++) { - for (KeyValue<int, Node> &E : graph[i].nodes) { - Ref<VisualShaderNodeInput> input = Object::cast_to<VisualShaderNodeInput>(E.value.node.ptr()); - if (input.is_valid()) { - if (input->get_input_name() == "side") { - input->set_input_name("front_facing"); - } - } - Ref<VisualShaderNodeExpression> expression = Object::cast_to<VisualShaderNodeExpression>(E.value.node.ptr()); - if (expression.is_valid()) { - for (int j = 0; j < expression->get_input_port_count(); j++) { - int type = expression->get_input_port_type(j); - if (type > 0) { // + PORT_TYPE_SCALAR_INT + PORT_TYPE_VECTOR_2D - type += 2; - } - expression->set_input_port_type(j, type); - } - for (int j = 0; j < expression->get_output_port_count(); j++) { - int type = expression->get_output_port_type(j); - if (type > 0) { // + PORT_TYPE_SCALAR_INT + PORT_TYPE_VECTOR_2D - type += 2; - } - expression->set_output_port_type(j, type); - } - } - Ref<VisualShaderNodeStep> step = Object::cast_to<VisualShaderNodeStep>(E.value.node.ptr()); - if (step.is_valid()) { - int op_type = int(step->get_op_type()); - if (int(op_type) > 0) { // + OP_TYPE_VECTOR_2D + OP_TYPE_VECTOR_2D_SCALAR - op_type += 2; - } - step->set_op_type(VisualShaderNodeStep::OpType(op_type)); - } - Ref<VisualShaderNodeSmoothStep> sstep = Object::cast_to<VisualShaderNodeSmoothStep>(E.value.node.ptr()); - if (sstep.is_valid()) { - int op_type = int(sstep->get_op_type()); - if (int(op_type) > 0) { // + OP_TYPE_VECTOR_2D + OP_TYPE_VECTOR_2D_SCALAR - op_type += 2; - } - sstep->set_op_type(VisualShaderNodeSmoothStep::OpType(op_type)); - } - Ref<VisualShaderNodeMix> mix = Object::cast_to<VisualShaderNodeMix>(E.value.node.ptr()); - if (mix.is_valid()) { - int op_type = int(mix->get_op_type()); - if (int(op_type) > 0) { // + OP_TYPE_VECTOR_2D + OP_TYPE_VECTOR_2D_SCALAR - op_type += 2; - } - mix->set_op_type(VisualShaderNodeMix::OpType(op_type)); - } - Ref<VisualShaderNodeCompare> compare = Object::cast_to<VisualShaderNodeCompare>(E.value.node.ptr()); - if (compare.is_valid()) { - int ctype = int(compare->get_comparison_type()); - if (int(ctype) > 0) { // + CTYPE_SCALAR_INT + CTYPE_VECTOR_2D - ctype += 2; - } - compare->set_comparison_type(VisualShaderNodeCompare::ComparisonType(ctype)); - } - } - } - } - set_engine_version(p_new_version); -} - -#endif /* DISABLE_DEPRECATED */ - void VisualShader::add_node(Type p_type, const Ref<VisualShaderNode> &p_node, const Vector2 &p_position, int p_id) { ERR_FAIL_COND(p_node.is_null()); ERR_FAIL_COND(p_id < 2); @@ -767,7 +733,6 @@ void VisualShader::add_node(Type p_type, const Ref<VisualShaderNode> &p_node, co if (input.is_valid()) { input->shader_mode = shader_mode; input->shader_type = p_type; - input->connect("input_type_changed", callable_mp(this, &VisualShader::_input_type_changed), varray(p_type, p_id)); } n.node->connect("changed", callable_mp(this, &VisualShader::_queue_update)); @@ -837,11 +802,6 @@ void VisualShader::remove_node(Type p_type, int p_id) { Graph *g = &graph[p_type]; ERR_FAIL_COND(!g->nodes.has(p_id)); - Ref<VisualShaderNodeInput> input = g->nodes[p_id].node; - if (input.is_valid()) { - input->disconnect("input_type_changed", callable_mp(this, &VisualShader::_input_type_changed)); - } - g->nodes[p_id].node->disconnect("changed", callable_mp(this, &VisualShader::_queue_update)); g->nodes.erase(p_id); @@ -871,6 +831,48 @@ void VisualShader::replace_node(Type p_type, int p_id, const StringName &p_new_c return; } VisualShaderNode *vsn = Object::cast_to<VisualShaderNode>(ClassDB::instantiate(p_new_class)); + VisualShaderNode *prev_vsn = g->nodes[p_id].node.ptr(); + + // Update connection data. + for (int i = 0; i < vsn->get_output_port_count(); i++) { + if (i < prev_vsn->get_output_port_count()) { + if (prev_vsn->is_output_port_connected(i)) { + vsn->set_output_port_connected(i, true); + } + + if (prev_vsn->is_output_port_expandable(i) && prev_vsn->_is_output_port_expanded(i) && vsn->is_output_port_expandable(i)) { + vsn->_set_output_port_expanded(i, true); + + int component_count = 0; + switch (prev_vsn->get_output_port_type(i)) { + case VisualShaderNode::PORT_TYPE_VECTOR_2D: + component_count = 2; + break; + case VisualShaderNode::PORT_TYPE_VECTOR_3D: + component_count = 3; + break; + case VisualShaderNode::PORT_TYPE_VECTOR_4D: + component_count = 4; + break; + default: + break; + } + + for (int j = 0; j < component_count; j++) { + int sub_port = i + 1 + j; + + if (prev_vsn->is_output_port_connected(sub_port)) { + vsn->set_output_port_connected(sub_port, true); + } + } + + i += component_count; + } + } else { + break; + } + } + vsn->connect("changed", callable_mp(this, &VisualShader::_queue_update)); g->nodes[p_id].node = Ref<VisualShaderNode>(vsn); @@ -1139,16 +1141,16 @@ String VisualShader::generate_preview_shader(Type p_type, int p_node, int p_port StringBuilder global_code; StringBuilder global_code_per_node; - Map<Type, StringBuilder> global_code_per_func; + HashMap<Type, StringBuilder> global_code_per_func; StringBuilder code; - Set<StringName> classes; + HashSet<StringName> classes; global_code += String() + "shader_type canvas_item;\n"; String global_expressions; for (int i = 0, index = 0; i < TYPE_MAX; i++) { - for (Map<int, Node>::Element *E = graph[i].nodes.front(); E; E = E->next()) { - Ref<VisualShaderNodeGlobalExpression> global_expression = Object::cast_to<VisualShaderNodeGlobalExpression>(E->get().node.ptr()); + for (const KeyValue<int, Node> &E : graph[i].nodes) { + Ref<VisualShaderNodeGlobalExpression> global_expression = E.value.node; if (global_expression.is_valid()) { String expr = ""; expr += "// " + global_expression->get_caption() + ":" + itos(index++) + "\n"; @@ -1183,7 +1185,7 @@ String VisualShader::generate_preview_shader(Type p_type, int p_node, int p_port code += "\nvoid fragment() {\n"; - Set<int> processed; + HashSet<int> processed; Error err = _write_node(p_type, &global_code, &global_code_per_node, &global_code_per_func, code, default_tex_params, input_connections, output_connections, p_node, processed, true, classes); ERR_FAIL_COND_V(err != OK, String()); @@ -1203,6 +1205,9 @@ String VisualShader::generate_preview_shader(Type p_type, int p_node, int p_port case VisualShaderNode::PORT_TYPE_VECTOR_3D: { code += " COLOR.rgb = n_out" + itos(p_node) + "p" + itos(p_port) + ";\n"; } break; + case VisualShaderNode::PORT_TYPE_VECTOR_4D: { + code += " COLOR.rgb = n_out" + itos(p_node) + "p" + itos(p_port) + ".xyz;\n"; + } break; default: { code += " COLOR.rgb = vec3(0.0);\n"; } break; @@ -1505,11 +1510,11 @@ void VisualShader::reset_state() { } void VisualShader::_get_property_list(List<PropertyInfo> *p_list) const { //mode - p_list->push_back(PropertyInfo(Variant::INT, "mode", PROPERTY_HINT_ENUM, "Node3D,CanvasItem,Particles,Sky,Fog")); + p_list->push_back(PropertyInfo(Variant::INT, PNAME("mode"), PROPERTY_HINT_ENUM, "Node3D,CanvasItem,Particles,Sky,Fog")); //render modes - Map<String, String> blend_mode_enums; - Set<String> toggles; + HashMap<String, String> blend_mode_enums; + HashSet<String> toggles; const Vector<ShaderLanguage::ModeInfo> &rmodes = ShaderTypes::get_singleton()->get_modes(RenderingServer::ShaderMode(shader_mode)); @@ -1520,7 +1525,7 @@ void VisualShader::_get_property_list(List<PropertyInfo> *p_list) const { const String begin = String(info.name); for (int j = 0; j < info.options.size(); j++) { - const String option = String(info.options[j]); + const String option = String(info.options[j]).capitalize(); if (!blend_mode_enums.has(begin)) { blend_mode_enums[begin] = option; @@ -1534,15 +1539,15 @@ void VisualShader::_get_property_list(List<PropertyInfo> *p_list) const { } for (const KeyValue<String, String> &E : blend_mode_enums) { - p_list->push_back(PropertyInfo(Variant::INT, "modes/" + E.key, PROPERTY_HINT_ENUM, E.value)); + p_list->push_back(PropertyInfo(Variant::INT, vformat("%s/%s", PNAME("modes"), E.key), PROPERTY_HINT_ENUM, E.value)); } - for (Set<String>::Element *E = toggles.front(); E; E = E->next()) { - p_list->push_back(PropertyInfo(Variant::BOOL, "flags/" + E->get())); + for (const String &E : toggles) { + p_list->push_back(PropertyInfo(Variant::BOOL, vformat("%s/%s", PNAME("flags"), E))); } for (const KeyValue<String, Varying> &E : varyings) { - p_list->push_back(PropertyInfo(Variant::STRING, "varyings/" + E.key)); + p_list->push_back(PropertyInfo(Variant::STRING, vformat("%s/%s", PNAME("varyings"), E.key))); } for (int i = 0; i < TYPE_MAX; i++) { @@ -1569,7 +1574,7 @@ void VisualShader::_get_property_list(List<PropertyInfo> *p_list) const { } } -Error VisualShader::_write_node(Type type, StringBuilder *global_code, StringBuilder *global_code_per_node, Map<Type, StringBuilder> *global_code_per_func, StringBuilder &code, Vector<VisualShader::DefaultTextureParam> &def_tex_params, const VMap<ConnectionKey, const List<Connection>::Element *> &input_connections, const VMap<ConnectionKey, const List<Connection>::Element *> &output_connections, int node, Set<int> &processed, bool for_preview, Set<StringName> &r_classes) const { +Error VisualShader::_write_node(Type type, StringBuilder *global_code, StringBuilder *global_code_per_node, HashMap<Type, StringBuilder> *global_code_per_func, StringBuilder &code, Vector<VisualShader::DefaultTextureParam> &def_tex_params, const VMap<ConnectionKey, const List<Connection>::Element *> &input_connections, const VMap<ConnectionKey, const List<Connection>::Element *> &output_connections, int node, HashSet<int> &processed, bool for_preview, HashSet<StringName> &r_classes) const { const Ref<VisualShaderNode> vsnode = graph[type].nodes[node].node; if (vsnode->is_disabled()) { @@ -1687,11 +1692,14 @@ Error VisualShader::_write_node(Type type, StringBuilder *global_code, StringBui inputs[i] = "(" + src_var + " ? 1.0 : 0.0)"; } break; case VisualShaderNode::PORT_TYPE_VECTOR_2D: { - inputs[i] = "dot(" + src_var + ", vec2(0.333333, 0.333333))"; + inputs[i] = "dot(" + src_var + ", vec2(0.5, 0.5))"; } break; case VisualShaderNode::PORT_TYPE_VECTOR_3D: { inputs[i] = "dot(" + src_var + ", vec3(0.333333, 0.333333, 0.333333))"; } break; + case VisualShaderNode::PORT_TYPE_VECTOR_4D: { + inputs[i] = "dot(" + src_var + ", vec4(0.25, 0.25, 0.25, 0.25))"; + } break; default: break; } @@ -1705,11 +1713,14 @@ Error VisualShader::_write_node(Type type, StringBuilder *global_code, StringBui inputs[i] = "(" + src_var + " ? 1 : 0)"; } break; case VisualShaderNode::PORT_TYPE_VECTOR_2D: { - inputs[i] = "dot(float(" + src_var + "), vec2(0.333333, 0.333333))"; + inputs[i] = "dot(float(" + src_var + "), vec2(0.5, 0.5))"; } break; case VisualShaderNode::PORT_TYPE_VECTOR_3D: { inputs[i] = "dot(float(" + src_var + "), vec3(0.333333, 0.333333, 0.333333))"; } break; + case VisualShaderNode::PORT_TYPE_VECTOR_4D: { + inputs[i] = "dot(float(" + src_var + "), vec4(0.25, 0.25, 0.25, 0.25))"; + } break; default: break; } @@ -1728,6 +1739,9 @@ Error VisualShader::_write_node(Type type, StringBuilder *global_code, StringBui case VisualShaderNode::PORT_TYPE_VECTOR_3D: { inputs[i] = "all(bvec3(" + src_var + "))"; } break; + case VisualShaderNode::PORT_TYPE_VECTOR_4D: { + inputs[i] = "all(bvec4(" + src_var + "))"; + } break; default: break; } @@ -1743,7 +1757,8 @@ Error VisualShader::_write_node(Type type, StringBuilder *global_code, StringBui case VisualShaderNode::PORT_TYPE_BOOLEAN: { inputs[i] = "vec2(" + src_var + " ? 1.0 : 0.0)"; } break; - case VisualShaderNode::PORT_TYPE_VECTOR_3D: { + case VisualShaderNode::PORT_TYPE_VECTOR_3D: + case VisualShaderNode::PORT_TYPE_VECTOR_4D: { inputs[i] = "vec2(" + src_var + ".xy)"; } break; default: @@ -1765,6 +1780,30 @@ Error VisualShader::_write_node(Type type, StringBuilder *global_code, StringBui case VisualShaderNode::PORT_TYPE_VECTOR_2D: { inputs[i] = "vec3(" + src_var + ", 0.0)"; } break; + case VisualShaderNode::PORT_TYPE_VECTOR_4D: { + inputs[i] = "vec3(" + src_var + ".xyz)"; + } break; + default: + break; + } + } break; + case VisualShaderNode::PORT_TYPE_VECTOR_4D: { + switch (out_type) { + case VisualShaderNode::PORT_TYPE_SCALAR: { + inputs[i] = "vec4(" + src_var + ")"; + } break; + case VisualShaderNode::PORT_TYPE_SCALAR_INT: { + inputs[i] = "vec4(float(" + src_var + "))"; + } break; + case VisualShaderNode::PORT_TYPE_BOOLEAN: { + inputs[i] = "vec4(" + src_var + " ? 1.0 : 0.0)"; + } break; + case VisualShaderNode::PORT_TYPE_VECTOR_2D: { + inputs[i] = "vec4(" + src_var + ", 0.0, 0.0)"; + } break; + case VisualShaderNode::PORT_TYPE_VECTOR_3D: { + inputs[i] = "vec4(" + src_var + ", 0.0)"; + } break; default: break; } @@ -1799,6 +1838,10 @@ Error VisualShader::_write_node(Type type, StringBuilder *global_code, StringBui Vector3 val = defval; inputs[i] = "n_in" + itos(node) + "p" + itos(i); node_code += " vec3 " + inputs[i] + " = " + vformat("vec3(%.5f, %.5f, %.5f);\n", val.x, val.y, val.z); + } else if (defval.get_type() == Variant::QUATERNION) { + Quaternion val = defval; + inputs[i] = "n_in" + itos(node) + "p" + itos(i); + node_code += " vec4 " + inputs[i] + " = " + vformat("vec4(%.5f, %.5f, %.5f, %.5f);\n", val.x, val.y, val.z, val.w); } else if (defval.get_type() == Variant::TRANSFORM3D) { Transform3D val = defval; val.basis.transpose(); @@ -1823,7 +1866,7 @@ Error VisualShader::_write_node(Type type, StringBuilder *global_code, StringBui int output_count = vsnode->get_output_port_count(); int initial_output_count = output_count; - Map<int, bool> expanded_output_ports; + HashMap<int, bool> expanded_output_ports; for (int i = 0; i < initial_output_count; i++) { bool expanded = false; @@ -1838,6 +1881,9 @@ Error VisualShader::_write_node(Type type, StringBuilder *global_code, StringBui case VisualShaderNode::PORT_TYPE_VECTOR_3D: { output_count += 3; } break; + case VisualShaderNode::PORT_TYPE_VECTOR_4D: { + output_count += 4; + } break; default: break; } @@ -1865,6 +1911,9 @@ Error VisualShader::_write_node(Type type, StringBuilder *global_code, StringBui case VisualShaderNode::PORT_TYPE_VECTOR_3D: outputs[i] = "vec3 " + var_name; break; + case VisualShaderNode::PORT_TYPE_VECTOR_4D: + outputs[i] = "vec4 " + var_name; + break; case VisualShaderNode::PORT_TYPE_BOOLEAN: outputs[i] = "bool " + var_name; break; @@ -1882,6 +1931,9 @@ Error VisualShader::_write_node(Type type, StringBuilder *global_code, StringBui case VisualShaderNode::PORT_TYPE_VECTOR_3D: { j += 3; } break; + case VisualShaderNode::PORT_TYPE_VECTOR_4D: { + j += 4; + } break; default: break; } @@ -1904,6 +1956,9 @@ Error VisualShader::_write_node(Type type, StringBuilder *global_code, StringBui case VisualShaderNode::PORT_TYPE_VECTOR_3D: code += " vec3 " + outputs[i] + ";\n"; break; + case VisualShaderNode::PORT_TYPE_VECTOR_4D: + code += " vec4 " + outputs[i] + ";\n"; + break; case VisualShaderNode::PORT_TYPE_BOOLEAN: code += " bool " + outputs[i] + ";\n"; break; @@ -1921,6 +1976,9 @@ Error VisualShader::_write_node(Type type, StringBuilder *global_code, StringBui case VisualShaderNode::PORT_TYPE_VECTOR_3D: { j += 3; } break; + case VisualShaderNode::PORT_TYPE_VECTOR_4D: { + j += 4; + } break; default: break; } @@ -1932,29 +1990,19 @@ Error VisualShader::_write_node(Type type, StringBuilder *global_code, StringBui if (!node_code.is_empty()) { code += node_name; code += node_code; - code += "\n"; } for (int i = 0; i < output_count; i++) { - bool new_line_inserted = false; if (expanded_output_ports[i]) { switch (vsnode->get_output_port_type(i)) { case VisualShaderNode::PORT_TYPE_VECTOR_2D: { if (vsnode->is_output_port_connected(i + 1) || (for_preview && vsnode->get_output_port_for_preview() == (i + 1))) { // red-component - if (!new_line_inserted) { - code += "\n"; - new_line_inserted = true; - } String r = "n_out" + itos(node) + "p" + itos(i + 1); code += " float " + r + " = n_out" + itos(node) + "p" + itos(i) + ".r;\n"; outputs[i + 1] = r; } if (vsnode->is_output_port_connected(i + 2) || (for_preview && vsnode->get_output_port_for_preview() == (i + 2))) { // green-component - if (!new_line_inserted) { - code += "\n"; - new_line_inserted = true; - } String g = "n_out" + itos(node) + "p" + itos(i + 2); code += " float " + g + " = n_out" + itos(node) + "p" + itos(i) + ".g;\n"; outputs[i + 2] = g; @@ -1964,30 +2012,18 @@ Error VisualShader::_write_node(Type type, StringBuilder *global_code, StringBui } break; case VisualShaderNode::PORT_TYPE_VECTOR_3D: { if (vsnode->is_output_port_connected(i + 1) || (for_preview && vsnode->get_output_port_for_preview() == (i + 1))) { // red-component - if (!new_line_inserted) { - code += "\n"; - new_line_inserted = true; - } String r = "n_out" + itos(node) + "p" + itos(i + 1); code += " float " + r + " = n_out" + itos(node) + "p" + itos(i) + ".r;\n"; outputs[i + 1] = r; } if (vsnode->is_output_port_connected(i + 2) || (for_preview && vsnode->get_output_port_for_preview() == (i + 2))) { // green-component - if (!new_line_inserted) { - code += "\n"; - new_line_inserted = true; - } String g = "n_out" + itos(node) + "p" + itos(i + 2); code += " float " + g + " = n_out" + itos(node) + "p" + itos(i) + ".g;\n"; outputs[i + 2] = g; } if (vsnode->is_output_port_connected(i + 3) || (for_preview && vsnode->get_output_port_for_preview() == (i + 3))) { // blue-component - if (!new_line_inserted) { - code += "\n"; - new_line_inserted = true; - } String b = "n_out" + itos(node) + "p" + itos(i + 3); code += " float " + b + " = n_out" + itos(node) + "p" + itos(i) + ".b;\n"; outputs[i + 3] = b; @@ -1995,12 +2031,43 @@ Error VisualShader::_write_node(Type type, StringBuilder *global_code, StringBui i += 3; } break; + case VisualShaderNode::PORT_TYPE_VECTOR_4D: { + if (vsnode->is_output_port_connected(i + 1) || (for_preview && vsnode->get_output_port_for_preview() == (i + 1))) { // red-component + String r = "n_out" + itos(node) + "p" + itos(i + 1); + code += " float " + r + " = n_out" + itos(node) + "p" + itos(i) + ".r;\n"; + outputs[i + 1] = r; + } + + if (vsnode->is_output_port_connected(i + 2) || (for_preview && vsnode->get_output_port_for_preview() == (i + 2))) { // green-component + String g = "n_out" + itos(node) + "p" + itos(i + 2); + code += " float " + g + " = n_out" + itos(node) + "p" + itos(i) + ".g;\n"; + outputs[i + 2] = g; + } + + if (vsnode->is_output_port_connected(i + 3) || (for_preview && vsnode->get_output_port_for_preview() == (i + 3))) { // blue-component + String b = "n_out" + itos(node) + "p" + itos(i + 3); + code += " float " + b + " = n_out" + itos(node) + "p" + itos(i) + ".b;\n"; + outputs[i + 3] = b; + } + + if (vsnode->is_output_port_connected(i + 4) || (for_preview && vsnode->get_output_port_for_preview() == (i + 4))) { // alpha-component + String a = "n_out" + itos(node) + "p" + itos(i + 4); + code += " float " + a + " = n_out" + itos(node) + "p" + itos(i) + ".a;\n"; + outputs[i + 4] = a; + } + + i += 4; + } break; default: break; } } } + if (!node_code.is_empty()) { + code += "\n"; + } + code += "\n"; // processed.insert(node); @@ -2029,11 +2096,11 @@ void VisualShader::_update_shader() const { StringBuilder global_code; StringBuilder global_code_per_node; - Map<Type, StringBuilder> global_code_per_func; + HashMap<Type, StringBuilder> global_code_per_func; StringBuilder code; Vector<VisualShader::DefaultTextureParam> default_tex_params; - Set<StringName> classes; - Map<int, int> insertion_pos; + HashSet<StringName> classes; + HashMap<int, int> insertion_pos; static const char *shader_mode_str[Shader::MODE_MAX] = { "spatial", "canvas_item", "particles", "sky", "fog" }; global_code += String() + "shader_type " + shader_mode_str[shader_mode] + ";\n"; @@ -2077,18 +2144,18 @@ void VisualShader::_update_shader() const { static const char *func_name[TYPE_MAX] = { "vertex", "fragment", "light", "start", "process", "collide", "start_custom", "process_custom", "sky", "fog" }; String global_expressions; - Set<String> used_uniform_names; + HashSet<String> used_uniform_names; List<VisualShaderNodeUniform *> uniforms; - Map<int, List<int>> emitters; - Map<int, List<int>> varying_setters; + HashMap<int, List<int>> emitters; + HashMap<int, List<int>> varying_setters; for (int i = 0, index = 0; i < TYPE_MAX; i++) { if (!has_func_name(RenderingServer::ShaderMode(shader_mode), func_name[i])) { continue; } - for (Map<int, Node>::Element *E = graph[i].nodes.front(); E; E = E->next()) { - Ref<VisualShaderNodeGlobalExpression> global_expression = Object::cast_to<VisualShaderNodeGlobalExpression>(E->get().node.ptr()); + for (const KeyValue<int, Node> &E : graph[i].nodes) { + Ref<VisualShaderNodeGlobalExpression> global_expression = E.value.node; if (global_expression.is_valid()) { String expr = ""; expr += "// " + global_expression->get_caption() + ":" + itos(index++) + "\n"; @@ -2097,27 +2164,27 @@ void VisualShader::_update_shader() const { expr += "\n"; global_expressions += expr; } - Ref<VisualShaderNodeUniformRef> uniform_ref = Object::cast_to<VisualShaderNodeUniformRef>(E->get().node.ptr()); + Ref<VisualShaderNodeUniformRef> uniform_ref = E.value.node; if (uniform_ref.is_valid()) { used_uniform_names.insert(uniform_ref->get_uniform_name()); } - Ref<VisualShaderNodeUniform> uniform = Object::cast_to<VisualShaderNodeUniform>(E->get().node.ptr()); + Ref<VisualShaderNodeUniform> uniform = E.value.node; if (uniform.is_valid()) { uniforms.push_back(uniform.ptr()); } - Ref<VisualShaderNodeVaryingSetter> varying_setter = Object::cast_to<VisualShaderNodeVaryingSetter>(E->get().node.ptr()); + Ref<VisualShaderNodeVaryingSetter> varying_setter = E.value.node; if (varying_setter.is_valid() && varying_setter->is_input_port_connected(0)) { if (!varying_setters.has(i)) { varying_setters.insert(i, List<int>()); } - varying_setters[i].push_back(E->key()); + varying_setters[i].push_back(E.key); } - Ref<VisualShaderNodeParticleEmit> emit_particle = Object::cast_to<VisualShaderNodeParticleEmit>(E->get().node.ptr()); + Ref<VisualShaderNodeParticleEmit> emit_particle = E.value.node; if (emit_particle.is_valid()) { if (!emitters.has(i)) { emitters.insert(i, List<int>()); } - emitters[i].push_back(E->key()); + emitters[i].push_back(E.key); } } } @@ -2147,6 +2214,9 @@ void VisualShader::_update_shader() const { case VaryingType::VARYING_TYPE_VECTOR_3D: global_code += "vec3 "; break; + case VaryingType::VARYING_TYPE_VECTOR_4D: + global_code += "vec4 "; + break; case VaryingType::VARYING_TYPE_COLOR: global_code += "vec4 "; break; @@ -2162,8 +2232,8 @@ void VisualShader::_update_shader() const { global_code += "\n"; } - Map<int, String> code_map; - Set<int> empty_funcs; + HashMap<int, String> code_map; + HashSet<int> empty_funcs; for (int i = 0; i < TYPE_MAX; i++) { if (!has_func_name(RenderingServer::ShaderMode(shader_mode), func_name[i])) { @@ -2175,7 +2245,7 @@ void VisualShader::_update_shader() const { VMap<ConnectionKey, const List<Connection>::Element *> output_connections; StringBuilder func_code; - Set<int> processed; + HashSet<int> processed; bool is_empty_func = false; if (shader_mode != Shader::MODE_PARTICLES && shader_mode != Shader::MODE_SKY && shader_mode != Shader::MODE_FOG) { @@ -2207,6 +2277,9 @@ void VisualShader::_update_shader() const { case VaryingType::VARYING_TYPE_VECTOR_3D: code2 += "vec3(0.0)"; break; + case VaryingType::VARYING_TYPE_VECTOR_4D: + code2 += "vec4(0.0)"; + break; case VaryingType::VARYING_TYPE_COLOR: code2 += "vec4(0.0)"; break; @@ -2447,21 +2520,6 @@ void VisualShader::_queue_update() { call_deferred(SNAME("_update_shader")); } -void VisualShader::_input_type_changed(Type p_type, int p_id) { - ERR_FAIL_INDEX(p_type, TYPE_MAX); - //erase connections using this input, as type changed - Graph *g = &graph[p_type]; - - for (List<Connection>::Element *E = g->connections.front(); E;) { - List<Connection>::Element *N = E->next(); - if (E->get().from_node == p_id) { - g->connections.erase(E); - g->nodes[E->get().to_node].prev_connected_nodes.erase(p_id); - } - E = N; - } -} - void VisualShader::rebuild() { dirty.set(); _update_shader(); @@ -2491,9 +2549,6 @@ void VisualShader::_bind_methods() { ClassDB::bind_method(D_METHOD("get_node_connections", "type"), &VisualShader::_get_node_connections); - ClassDB::bind_method(D_METHOD("set_engine_version", "version"), &VisualShader::set_engine_version); - ClassDB::bind_method(D_METHOD("get_engine_version"), &VisualShader::get_engine_version); - ClassDB::bind_method(D_METHOD("set_graph_offset", "offset"), &VisualShader::set_graph_offset); ClassDB::bind_method(D_METHOD("get_graph_offset"), &VisualShader::get_graph_offset); @@ -2504,7 +2559,6 @@ void VisualShader::_bind_methods() { ClassDB::bind_method(D_METHOD("_update_shader"), &VisualShader::_update_shader); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "graph_offset", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_graph_offset", "get_graph_offset"); - ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "engine_version", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_engine_version", "get_engine_version"); ADD_PROPERTY_DEFAULT("code", ""); // Inherited from Shader, prevents showing default code as override in docs. @@ -2527,6 +2581,7 @@ void VisualShader::_bind_methods() { BIND_ENUM_CONSTANT(VARYING_TYPE_FLOAT); BIND_ENUM_CONSTANT(VARYING_TYPE_VECTOR_2D); BIND_ENUM_CONSTANT(VARYING_TYPE_VECTOR_3D); + BIND_ENUM_CONSTANT(VARYING_TYPE_VECTOR_4D); BIND_ENUM_CONSTANT(VARYING_TYPE_COLOR); BIND_ENUM_CONSTANT(VARYING_TYPE_TRANSFORM); BIND_ENUM_CONSTANT(VARYING_TYPE_MAX); @@ -2569,12 +2624,10 @@ const VisualShaderNodeInput::Port VisualShaderNodeInput::ports[] = { { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_3D, "binormal", "BINORMAL" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_2D, "uv", "UV" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_2D, "uv2", "UV2" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_3D, "color", "COLOR.rgb" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_SCALAR, "alpha", "COLOR.a" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_4D, "color", "COLOR" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_SCALAR, "point_size", "POINT_SIZE" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_SCALAR_INT, "instance_id", "INSTANCE_ID" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_3D, "instance_custom", "INSTANCE_CUSTOM.rgb" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_SCALAR, "instance_custom_alpha", "INSTANCE_CUSTOM.a" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_4D, "instance_custom", "INSTANCE_CUSTOM" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_SCALAR, "roughness", "ROUGHNESS" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_TRANSFORM, "model_matrix", "MODEL_MATRIX" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_TRANSFORM, "modelview_matrix", "MODELVIEW_MATRIX" }, @@ -2590,7 +2643,7 @@ const VisualShaderNodeInput::Port VisualShaderNodeInput::ports[] = { { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_SCALAR_INT, "view_right", "VIEW_RIGHT" }, // Node3D, Fragment - { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "fragcoord", "FRAGCOORD.xyz" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_4D, "fragcoord", "FRAGCOORD" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "vertex", "VERTEX" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "normal", "NORMAL" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "tangent", "TANGENT" }, @@ -2598,8 +2651,7 @@ const VisualShaderNodeInput::Port VisualShaderNodeInput::ports[] = { { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "view", "VIEW" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_2D, "uv", "UV" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_2D, "uv2", "UV2" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "color", "COLOR.rgb" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "alpha", "COLOR.a" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_4D, "color", "COLOR" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_2D, "point_coord", "POINT_COORD" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_2D, "screen_uv", "SCREEN_UV" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_TRANSFORM, "model_matrix", "MODEL_MATRIX" }, @@ -2619,7 +2671,7 @@ const VisualShaderNodeInput::Port VisualShaderNodeInput::ports[] = { { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR_INT, "view_right", "VIEW_RIGHT" }, // Node3D, Light - { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "fragcoord", "FRAGCOORD.xyz" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_4D, "fragcoord", "FRAGCOORD" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "normal", "NORMAL" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_2D, "uv", "UV" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_2D, "uv2", "UV2" }, @@ -2647,8 +2699,7 @@ const VisualShaderNodeInput::Port VisualShaderNodeInput::ports[] = { // Canvas Item, Vertex { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_2D, "vertex", "VERTEX" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_2D, "uv", "UV" }, - { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_3D, "color", "COLOR.rgb" }, - { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_SCALAR, "alpha", "COLOR.a" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_4D, "color", "COLOR" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_SCALAR, "point_size", "POINT_SIZE" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_2D, "texture_pixel_size", "TEXTURE_PIXEL_SIZE" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_TRANSFORM, "model_matrix", "MODEL_MATRIX" }, @@ -2656,16 +2707,14 @@ const VisualShaderNodeInput::Port VisualShaderNodeInput::ports[] = { { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_TRANSFORM, "screen_matrix", "SCREEN_MATRIX" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_SCALAR, "time", "TIME" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_BOOLEAN, "at_light_pass", "AT_LIGHT_PASS" }, - { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_3D, "instance_custom", "INSTANCE_CUSTOM.rgb" }, - { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_SCALAR, "instance_custom_alpha", "INSTANCE_CUSTOM.a" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_4D, "instance_custom", "INSTANCE_CUSTOM" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_SCALAR_INT, "instance_id", "INSTANCE_ID" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_SCALAR_INT, "vertex_id", "VERTEX_ID" }, // Canvas Item, Fragment - { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "fragcoord", "FRAGCOORD.xyz" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_4D, "fragcoord", "FRAGCOORD" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_2D, "uv", "UV" }, - { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "color", "COLOR.rgb" }, - { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "alpha", "COLOR.a" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_4D, "color", "COLOR" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_2D, "screen_uv", "SCREEN_UV" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_2D, "texture_pixel_size", "TEXTURE_PIXEL_SIZE" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_2D, "screen_pixel_size", "SCREEN_PIXEL_SIZE" }, @@ -2675,42 +2724,34 @@ const VisualShaderNodeInput::Port VisualShaderNodeInput::ports[] = { { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SAMPLER, "texture", "TEXTURE" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SAMPLER, "normal_texture", "NORMAL_TEXTURE" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SAMPLER, "screen_texture", "SCREEN_TEXTURE" }, - { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "specular_shininess", "SPECULAR_SHININESS.rgb" }, - { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "specular_shininess_alpha", "SPECULAR_SHININESS.a" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_4D, "specular_shininess", "SPECULAR_SHININESS" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SAMPLER, "specular_shininess_texture", "SPECULAR_SHININESS_TEXTURE" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_2D, "vertex", "VERTEX" }, // Canvas Item, Light - { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "fragcoord", "FRAGCOORD.xyz" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_4D, "fragcoord", "FRAGCOORD" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_2D, "uv", "UV" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "normal", "NORMAL" }, - { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "color", "COLOR.rgb" }, - { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_SCALAR, "alpha", "COLOR.a" }, - { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "light", "LIGHT.rgb" }, - { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_SCALAR, "light_alpha", "LIGHT.a" }, - { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "light_color", "LIGHT_COLOR.rgb" }, - { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_SCALAR, "light_color_alpha", "LIGHT_COLOR.a" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_4D, "color", "COLOR" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_4D, "light", "LIGHT" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_4D, "light_color", "LIGHT_COLOR" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "light_position", "LIGHT_POSITION" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "light_vertex", "LIGHT_VERTEX" }, - { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "shadow", "SHADOW_MODULATE.rgb" }, - { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_SCALAR, "shadow_alpha", "SHADOW_MODULATE.a" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_4D, "shadow", "SHADOW_MODULATE" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_2D, "screen_uv", "SCREEN_UV" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_2D, "texture_pixel_size", "TEXTURE_PIXEL_SIZE" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_2D, "point_coord", "POINT_COORD" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_SCALAR, "time", "TIME" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_SAMPLER, "texture", "TEXTURE" }, - { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "specular_shininess", "SPECULAR_SHININESS.rgb" }, - { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_SCALAR, "specular_shininess_alpha", "SPECULAR_SHININESS.a" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_4D, "specular_shininess", "SPECULAR_SHININESS" }, // Particles, Start { Shader::MODE_PARTICLES, VisualShader::TYPE_START, VisualShaderNode::PORT_TYPE_VECTOR_3D, "attractor_force", "ATTRACTOR_FORCE" }, - { Shader::MODE_PARTICLES, VisualShader::TYPE_START, VisualShaderNode::PORT_TYPE_VECTOR_3D, "color", "COLOR.rgb" }, - { Shader::MODE_PARTICLES, VisualShader::TYPE_START, VisualShaderNode::PORT_TYPE_SCALAR, "alpha", "COLOR.a" }, + { Shader::MODE_PARTICLES, VisualShader::TYPE_START, VisualShaderNode::PORT_TYPE_VECTOR_4D, "color", "COLOR" }, { Shader::MODE_PARTICLES, VisualShader::TYPE_START, VisualShaderNode::PORT_TYPE_VECTOR_3D, "velocity", "VELOCITY" }, { Shader::MODE_PARTICLES, VisualShader::TYPE_START, VisualShaderNode::PORT_TYPE_BOOLEAN, "restart", "RESTART" }, { Shader::MODE_PARTICLES, VisualShader::TYPE_START, VisualShaderNode::PORT_TYPE_BOOLEAN, "active", "ACTIVE" }, - { Shader::MODE_PARTICLES, VisualShader::TYPE_START, VisualShaderNode::PORT_TYPE_VECTOR_3D, "custom", "CUSTOM.rgb" }, - { Shader::MODE_PARTICLES, VisualShader::TYPE_START, VisualShaderNode::PORT_TYPE_SCALAR, "custom_alpha", "CUSTOM.a" }, + { Shader::MODE_PARTICLES, VisualShader::TYPE_START, VisualShaderNode::PORT_TYPE_VECTOR_4D, "custom", "CUSTOM" }, { Shader::MODE_PARTICLES, VisualShader::TYPE_START, VisualShaderNode::PORT_TYPE_TRANSFORM, "transform", "TRANSFORM" }, { Shader::MODE_PARTICLES, VisualShader::TYPE_START, VisualShaderNode::PORT_TYPE_SCALAR, "delta", "DELTA" }, { Shader::MODE_PARTICLES, VisualShader::TYPE_START, VisualShaderNode::PORT_TYPE_SCALAR, "lifetime", "LIFETIME" }, @@ -2720,13 +2761,11 @@ const VisualShaderNodeInput::Port VisualShaderNodeInput::ports[] = { // Particles, Start (Custom) { Shader::MODE_PARTICLES, VisualShader::TYPE_START_CUSTOM, VisualShaderNode::PORT_TYPE_VECTOR_3D, "attractor_force", "ATTRACTOR_FORCE" }, - { Shader::MODE_PARTICLES, VisualShader::TYPE_START_CUSTOM, VisualShaderNode::PORT_TYPE_VECTOR_3D, "color", "COLOR.rgb" }, - { Shader::MODE_PARTICLES, VisualShader::TYPE_START_CUSTOM, VisualShaderNode::PORT_TYPE_SCALAR, "alpha", "COLOR.a" }, + { Shader::MODE_PARTICLES, VisualShader::TYPE_START_CUSTOM, VisualShaderNode::PORT_TYPE_VECTOR_4D, "color", "COLOR" }, { Shader::MODE_PARTICLES, VisualShader::TYPE_START_CUSTOM, VisualShaderNode::PORT_TYPE_VECTOR_3D, "velocity", "VELOCITY" }, { Shader::MODE_PARTICLES, VisualShader::TYPE_START_CUSTOM, VisualShaderNode::PORT_TYPE_BOOLEAN, "restart", "RESTART" }, { Shader::MODE_PARTICLES, VisualShader::TYPE_START_CUSTOM, VisualShaderNode::PORT_TYPE_BOOLEAN, "active", "ACTIVE" }, - { Shader::MODE_PARTICLES, VisualShader::TYPE_START_CUSTOM, VisualShaderNode::PORT_TYPE_VECTOR_3D, "custom", "CUSTOM.rgb" }, - { Shader::MODE_PARTICLES, VisualShader::TYPE_START_CUSTOM, VisualShaderNode::PORT_TYPE_SCALAR, "custom_alpha", "CUSTOM.a" }, + { Shader::MODE_PARTICLES, VisualShader::TYPE_START_CUSTOM, VisualShaderNode::PORT_TYPE_VECTOR_4D, "custom", "CUSTOM" }, { Shader::MODE_PARTICLES, VisualShader::TYPE_START_CUSTOM, VisualShaderNode::PORT_TYPE_TRANSFORM, "transform", "TRANSFORM" }, { Shader::MODE_PARTICLES, VisualShader::TYPE_START_CUSTOM, VisualShaderNode::PORT_TYPE_SCALAR, "delta", "DELTA" }, { Shader::MODE_PARTICLES, VisualShader::TYPE_START_CUSTOM, VisualShaderNode::PORT_TYPE_SCALAR, "lifetime", "LIFETIME" }, @@ -2736,13 +2775,11 @@ const VisualShaderNodeInput::Port VisualShaderNodeInput::ports[] = { // Particles, Process { Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS, VisualShaderNode::PORT_TYPE_VECTOR_3D, "attractor_force", "ATTRACTOR_FORCE" }, - { Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS, VisualShaderNode::PORT_TYPE_VECTOR_3D, "color", "COLOR.rgb" }, - { Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS, VisualShaderNode::PORT_TYPE_SCALAR, "alpha", "COLOR.a" }, + { Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS, VisualShaderNode::PORT_TYPE_VECTOR_4D, "color", "COLOR" }, { Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS, VisualShaderNode::PORT_TYPE_VECTOR_3D, "velocity", "VELOCITY" }, { Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS, VisualShaderNode::PORT_TYPE_BOOLEAN, "restart", "RESTART" }, { Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS, VisualShaderNode::PORT_TYPE_BOOLEAN, "active", "ACTIVE" }, - { Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS, VisualShaderNode::PORT_TYPE_VECTOR_3D, "custom", "CUSTOM.rgb" }, - { Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS, VisualShaderNode::PORT_TYPE_SCALAR, "custom_alpha", "CUSTOM.a" }, + { Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS, VisualShaderNode::PORT_TYPE_VECTOR_4D, "custom", "CUSTOM" }, { Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS, VisualShaderNode::PORT_TYPE_TRANSFORM, "transform", "TRANSFORM" }, { Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS, VisualShaderNode::PORT_TYPE_SCALAR, "delta", "DELTA" }, { Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS, VisualShaderNode::PORT_TYPE_SCALAR, "lifetime", "LIFETIME" }, @@ -2752,13 +2789,11 @@ const VisualShaderNodeInput::Port VisualShaderNodeInput::ports[] = { // Particles, Process (Custom) { Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS_CUSTOM, VisualShaderNode::PORT_TYPE_VECTOR_3D, "attractor_force", "ATTRACTOR_FORCE" }, - { Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS_CUSTOM, VisualShaderNode::PORT_TYPE_VECTOR_3D, "color", "COLOR.rgb" }, - { Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS_CUSTOM, VisualShaderNode::PORT_TYPE_SCALAR, "alpha", "COLOR.a" }, + { Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS_CUSTOM, VisualShaderNode::PORT_TYPE_VECTOR_4D, "color", "COLOR" }, { Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS_CUSTOM, VisualShaderNode::PORT_TYPE_VECTOR_3D, "velocity", "VELOCITY" }, { Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS_CUSTOM, VisualShaderNode::PORT_TYPE_BOOLEAN, "restart", "RESTART" }, { Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS_CUSTOM, VisualShaderNode::PORT_TYPE_BOOLEAN, "active", "ACTIVE" }, - { Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS_CUSTOM, VisualShaderNode::PORT_TYPE_VECTOR_3D, "custom", "CUSTOM.rgb" }, - { Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS_CUSTOM, VisualShaderNode::PORT_TYPE_SCALAR, "custom_alpha", "CUSTOM.a" }, + { Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS_CUSTOM, VisualShaderNode::PORT_TYPE_VECTOR_4D, "custom", "CUSTOM" }, { Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS_CUSTOM, VisualShaderNode::PORT_TYPE_TRANSFORM, "transform", "TRANSFORM" }, { Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS_CUSTOM, VisualShaderNode::PORT_TYPE_SCALAR, "delta", "DELTA" }, { Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS_CUSTOM, VisualShaderNode::PORT_TYPE_SCALAR, "lifetime", "LIFETIME" }, @@ -2770,13 +2805,11 @@ const VisualShaderNodeInput::Port VisualShaderNodeInput::ports[] = { { Shader::MODE_PARTICLES, VisualShader::TYPE_COLLIDE, VisualShaderNode::PORT_TYPE_VECTOR_3D, "attractor_force", "ATTRACTOR_FORCE" }, { Shader::MODE_PARTICLES, VisualShader::TYPE_COLLIDE, VisualShaderNode::PORT_TYPE_SCALAR, "collision_depth", "COLLISION_DEPTH" }, { Shader::MODE_PARTICLES, VisualShader::TYPE_COLLIDE, VisualShaderNode::PORT_TYPE_VECTOR_3D, "collision_normal", "COLLISION_NORMAL" }, - { Shader::MODE_PARTICLES, VisualShader::TYPE_COLLIDE, VisualShaderNode::PORT_TYPE_VECTOR_3D, "color", "COLOR.rgb" }, - { Shader::MODE_PARTICLES, VisualShader::TYPE_COLLIDE, VisualShaderNode::PORT_TYPE_SCALAR, "alpha", "COLOR.a" }, + { Shader::MODE_PARTICLES, VisualShader::TYPE_COLLIDE, VisualShaderNode::PORT_TYPE_VECTOR_4D, "color", "COLOR" }, { Shader::MODE_PARTICLES, VisualShader::TYPE_COLLIDE, VisualShaderNode::PORT_TYPE_VECTOR_3D, "velocity", "VELOCITY" }, { Shader::MODE_PARTICLES, VisualShader::TYPE_COLLIDE, VisualShaderNode::PORT_TYPE_BOOLEAN, "restart", "RESTART" }, { Shader::MODE_PARTICLES, VisualShader::TYPE_COLLIDE, VisualShaderNode::PORT_TYPE_BOOLEAN, "active", "ACTIVE" }, - { Shader::MODE_PARTICLES, VisualShader::TYPE_COLLIDE, VisualShaderNode::PORT_TYPE_VECTOR_3D, "custom", "CUSTOM.rgb" }, - { Shader::MODE_PARTICLES, VisualShader::TYPE_COLLIDE, VisualShaderNode::PORT_TYPE_SCALAR, "custom_alpha", "CUSTOM.a" }, + { Shader::MODE_PARTICLES, VisualShader::TYPE_COLLIDE, VisualShaderNode::PORT_TYPE_VECTOR_4D, "custom", "CUSTOM" }, { Shader::MODE_PARTICLES, VisualShader::TYPE_COLLIDE, VisualShaderNode::PORT_TYPE_TRANSFORM, "transform", "TRANSFORM" }, { Shader::MODE_PARTICLES, VisualShader::TYPE_COLLIDE, VisualShaderNode::PORT_TYPE_SCALAR, "delta", "DELTA" }, { Shader::MODE_PARTICLES, VisualShader::TYPE_COLLIDE, VisualShaderNode::PORT_TYPE_SCALAR, "lifetime", "LIFETIME" }, @@ -2789,8 +2822,7 @@ const VisualShaderNodeInput::Port VisualShaderNodeInput::ports[] = { { Shader::MODE_SKY, VisualShader::TYPE_SKY, VisualShaderNode::PORT_TYPE_BOOLEAN, "at_half_res_pass", "AT_HALF_RES_PASS" }, { Shader::MODE_SKY, VisualShader::TYPE_SKY, VisualShaderNode::PORT_TYPE_BOOLEAN, "at_quarter_res_pass", "AT_QUARTER_RES_PASS" }, { Shader::MODE_SKY, VisualShader::TYPE_SKY, VisualShaderNode::PORT_TYPE_VECTOR_3D, "eyedir", "EYEDIR" }, - { Shader::MODE_SKY, VisualShader::TYPE_SKY, VisualShaderNode::PORT_TYPE_VECTOR_3D, "half_res_color", "HALF_RES_COLOR.rgb" }, - { Shader::MODE_SKY, VisualShader::TYPE_SKY, VisualShaderNode::PORT_TYPE_SCALAR, "half_res_alpha", "HALF_RES_COLOR.a" }, + { Shader::MODE_SKY, VisualShader::TYPE_SKY, VisualShaderNode::PORT_TYPE_VECTOR_4D, "half_res_color", "HALF_RES_COLOR" }, { Shader::MODE_SKY, VisualShader::TYPE_SKY, VisualShaderNode::PORT_TYPE_VECTOR_3D, "light0_color", "LIGHT0_COLOR" }, { Shader::MODE_SKY, VisualShader::TYPE_SKY, VisualShaderNode::PORT_TYPE_VECTOR_3D, "light0_direction", "LIGHT0_DIRECTION" }, { Shader::MODE_SKY, VisualShader::TYPE_SKY, VisualShaderNode::PORT_TYPE_BOOLEAN, "light0_enabled", "LIGHT0_ENABLED" }, @@ -2808,10 +2840,10 @@ const VisualShaderNodeInput::Port VisualShaderNodeInput::ports[] = { { Shader::MODE_SKY, VisualShader::TYPE_SKY, VisualShaderNode::PORT_TYPE_BOOLEAN, "light3_enabled", "LIGHT3_ENABLED" }, { Shader::MODE_SKY, VisualShader::TYPE_SKY, VisualShaderNode::PORT_TYPE_SCALAR, "light3_energy", "LIGHT3_ENERGY" }, { Shader::MODE_SKY, VisualShader::TYPE_SKY, VisualShaderNode::PORT_TYPE_VECTOR_3D, "position", "POSITION" }, - { Shader::MODE_SKY, VisualShader::TYPE_SKY, VisualShaderNode::PORT_TYPE_VECTOR_3D, "quarter_res_color", "QUARTER_RES_COLOR.rgb" }, - { Shader::MODE_SKY, VisualShader::TYPE_SKY, VisualShaderNode::PORT_TYPE_SCALAR, "quarter_res_alpha", "QUARTER_RES_COLOR.a" }, + { Shader::MODE_SKY, VisualShader::TYPE_SKY, VisualShaderNode::PORT_TYPE_VECTOR_4D, "quarter_res_color", "QUARTER_RES_COLOR" }, { Shader::MODE_SKY, VisualShader::TYPE_SKY, VisualShaderNode::PORT_TYPE_SAMPLER, "radiance", "RADIANCE" }, { Shader::MODE_SKY, VisualShader::TYPE_SKY, VisualShaderNode::PORT_TYPE_VECTOR_2D, "screen_uv", "SCREEN_UV" }, + { Shader::MODE_SKY, VisualShader::TYPE_SKY, VisualShaderNode::PORT_TYPE_VECTOR_4D, "fragcoord", "FRAGCOORD" }, { Shader::MODE_SKY, VisualShader::TYPE_SKY, VisualShaderNode::PORT_TYPE_VECTOR_2D, "sky_coords", "SKY_COORDS" }, { Shader::MODE_SKY, VisualShader::TYPE_SKY, VisualShaderNode::PORT_TYPE_SCALAR, "time", "TIME" }, @@ -2835,20 +2867,20 @@ const VisualShaderNodeInput::Port VisualShaderNodeInput::preview_ports[] = { { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_3D, "binormal", "vec3(1.0, 0.0, 0.0)" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_2D, "uv", "UV" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_2D, "uv2", "UV" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_3D, "color", "vec3(1.0)" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_4D, "color", "vec4(1.0)" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_SCALAR, "alpha", "1.0" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_2D, "viewport_size", "vec2(1.0)" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_SCALAR, "time", "TIME" }, // Spatial, Fragment - { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "fragcoord", "FRAGCOORD.rgb" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_4D, "fragcoord", "FRAGCOORD" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "normal", "vec3(0.0, 0.0, 1.0)" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "tangent", "vec3(0.0, 1.0, 0.0)" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "binormal", "vec3(1.0, 0.0, 0.0)" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_2D, "uv", "UV" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_2D, "uv2", "UV" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "color", "vec3(1.0)" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_4D, "color", "vec4(1.0)" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "alpha", "1.0" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_2D, "screen_uv", "SCREEN_UV" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_2D, "viewport_size", "vec2(1.0)" }, @@ -2856,7 +2888,7 @@ const VisualShaderNodeInput::Port VisualShaderNodeInput::preview_ports[] = { // Spatial, Light - { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "fragcoord", "FRAGCOORD.rgb" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_4D, "fragcoord", "FRAGCOORD" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "normal", "vec3(0.0, 0.0, 1.0)" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_2D, "uv", "UV" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_2D, "uv2", "UV" }, @@ -2867,25 +2899,25 @@ const VisualShaderNodeInput::Port VisualShaderNodeInput::preview_ports[] = { { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_2D, "vertex", "VERTEX" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_2D, "uv", "UV" }, - { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_3D, "color", "vec3(1.0)" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_4D, "color", "vec4(1.0)" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_SCALAR, "alpha", "1.0" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_SCALAR, "time", "TIME" }, // Canvas Item, Fragment - { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "fragcoord", "FRAGCOORD.rgb" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_4D, "fragcoord", "FRAGCOORD" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_2D, "uv", "UV" }, - { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "color", "vec3(1.0)" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_4D, "color", "vec3(1.0)" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "alpha", "1.0" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_2D, "screen_uv", "SCREEN_UV" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "time", "TIME" }, // Canvas Item, Light - { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "fragcoord", "FRAGCOORD.rgb" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_4D, "fragcoord", "FRAGCOORD" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_2D, "uv", "UV" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "normal", "vec3(0.0, 0.0, 1.0)" }, - { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "color", "vec3(1.0)" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_4D, "color", "vec4(1.0)" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_SCALAR, "alpha", "1.0" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_2D, "screen_uv", "SCREEN_UV" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_SCALAR, "time", "TIME" }, @@ -2927,7 +2959,7 @@ int VisualShaderNodeInput::get_output_port_count() const { } VisualShaderNodeInput::PortType VisualShaderNodeInput::get_output_port_type(int p_port) const { - return get_input_type_by_name(input_name); + return p_port == 0 ? get_input_type_by_name(input_name) : PORT_TYPE_SCALAR; } String VisualShaderNodeInput::get_output_port_name(int p_port) const { @@ -2938,6 +2970,22 @@ String VisualShaderNodeInput::get_caption() const { return "Input"; } +bool VisualShaderNodeInput::is_output_port_expandable(int p_port) const { + if (p_port == 0) { + switch (get_input_type_by_name(input_name)) { + case PORT_TYPE_VECTOR_2D: + return true; + case PORT_TYPE_VECTOR_3D: + return true; + case PORT_TYPE_VECTOR_4D: + return true; + default: + return false; + } + } + return false; +} + String VisualShaderNodeInput::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 { if (get_output_port_type(0) == PORT_TYPE_SAMPLER) { return ""; @@ -2970,6 +3018,9 @@ String VisualShaderNodeInput::generate_code(Shader::Mode p_mode, VisualShader::T case PORT_TYPE_VECTOR_3D: { code = " " + p_output_vars[0] + " = vec3(0.0);\n"; } break; + case PORT_TYPE_VECTOR_4D: { + code = " " + p_output_vars[0] + " = vec4(0.0);\n"; + } break; case PORT_TYPE_BOOLEAN: { code = " " + p_output_vars[0] + " = false;\n"; } break; @@ -3139,18 +3190,18 @@ VisualShaderNodeInput::VisualShaderNodeInput() { ////////////// UniformRef -List<VisualShaderNodeUniformRef::Uniform> uniforms; +RBMap<RID, List<VisualShaderNodeUniformRef::Uniform>> uniforms; -void VisualShaderNodeUniformRef::add_uniform(const String &p_name, UniformType p_type) { - uniforms.push_back({ p_name, p_type }); +void VisualShaderNodeUniformRef::add_uniform(RID p_shader_rid, const String &p_name, UniformType p_type) { + uniforms[p_shader_rid].push_back({ p_name, p_type }); } -void VisualShaderNodeUniformRef::clear_uniforms() { - uniforms.clear(); +void VisualShaderNodeUniformRef::clear_uniforms(RID p_shader_rid) { + uniforms[p_shader_rid].clear(); } -bool VisualShaderNodeUniformRef::has_uniform(const String &p_name) { - for (const VisualShaderNodeUniformRef::Uniform &E : uniforms) { +bool VisualShaderNodeUniformRef::has_uniform(RID p_shader_rid, const String &p_name) { + for (const VisualShaderNodeUniformRef::Uniform &E : uniforms[p_shader_rid]) { if (E.name == p_name) { return true; } @@ -3186,6 +3237,8 @@ int VisualShaderNodeUniformRef::get_output_port_count() const { return 1; case UniformType::UNIFORM_TYPE_VECTOR3: return 1; + case UniformType::UNIFORM_TYPE_VECTOR4: + return 1; case UniformType::UNIFORM_TYPE_TRANSFORM: return 1; case UniformType::UNIFORM_TYPE_COLOR: @@ -3210,6 +3263,8 @@ VisualShaderNodeUniformRef::PortType VisualShaderNodeUniformRef::get_output_port return PortType::PORT_TYPE_VECTOR_2D; case UniformType::UNIFORM_TYPE_VECTOR3: return PortType::PORT_TYPE_VECTOR_3D; + case UniformType::UNIFORM_TYPE_VECTOR4: + return PortType::PORT_TYPE_VECTOR_4D; case UniformType::UNIFORM_TYPE_TRANSFORM: return PortType::PORT_TYPE_TRANSFORM; case UniformType::UNIFORM_TYPE_COLOR: @@ -3239,6 +3294,8 @@ String VisualShaderNodeUniformRef::get_output_port_name(int p_port) const { return ""; case UniformType::UNIFORM_TYPE_VECTOR3: return ""; + case UniformType::UNIFORM_TYPE_VECTOR4: + return ""; case UniformType::UNIFORM_TYPE_TRANSFORM: return ""; case UniformType::UNIFORM_TYPE_COLOR: @@ -3257,14 +3314,24 @@ String VisualShaderNodeUniformRef::get_output_port_name(int p_port) const { return ""; } +void VisualShaderNodeUniformRef::set_shader_rid(const RID &p_shader_rid) { + shader_rid = p_shader_rid; +} + void VisualShaderNodeUniformRef::set_uniform_name(const String &p_name) { uniform_name = p_name; + if (shader_rid.is_valid()) { + update_uniform_type(); + } + emit_changed(); +} + +void VisualShaderNodeUniformRef::update_uniform_type() { if (uniform_name != "[None]") { uniform_type = get_uniform_type_by_name(uniform_name); } else { uniform_type = UniformType::UNIFORM_TYPE_FLOAT; } - emit_changed(); } String VisualShaderNodeUniformRef::get_uniform_name() const { @@ -3272,35 +3339,45 @@ String VisualShaderNodeUniformRef::get_uniform_name() const { } int VisualShaderNodeUniformRef::get_uniforms_count() const { - return uniforms.size(); + ERR_FAIL_COND_V(!shader_rid.is_valid(), 0); + + return uniforms[shader_rid].size(); } String VisualShaderNodeUniformRef::get_uniform_name_by_index(int p_idx) const { - if (p_idx >= 0 && p_idx < uniforms.size()) { - return uniforms[p_idx].name; + ERR_FAIL_COND_V(!shader_rid.is_valid(), String()); + + if (p_idx >= 0 && p_idx < uniforms[shader_rid].size()) { + return uniforms[shader_rid][p_idx].name; } return ""; } VisualShaderNodeUniformRef::UniformType VisualShaderNodeUniformRef::get_uniform_type_by_name(const String &p_name) const { - for (int i = 0; i < uniforms.size(); i++) { - if (uniforms[i].name == p_name) { - return uniforms[i].type; + ERR_FAIL_COND_V(!shader_rid.is_valid(), UNIFORM_TYPE_FLOAT); + + for (int i = 0; i < uniforms[shader_rid].size(); i++) { + if (uniforms[shader_rid][i].name == p_name) { + return uniforms[shader_rid][i].type; } } return UniformType::UNIFORM_TYPE_FLOAT; } VisualShaderNodeUniformRef::UniformType VisualShaderNodeUniformRef::get_uniform_type_by_index(int p_idx) const { - if (p_idx >= 0 && p_idx < uniforms.size()) { - return uniforms[p_idx].type; + ERR_FAIL_COND_V(!shader_rid.is_valid(), UNIFORM_TYPE_FLOAT); + + if (p_idx >= 0 && p_idx < uniforms[shader_rid].size()) { + return uniforms[shader_rid][p_idx].type; } return UniformType::UNIFORM_TYPE_FLOAT; } VisualShaderNodeUniformRef::PortType VisualShaderNodeUniformRef::get_port_type_by_index(int p_idx) const { - if (p_idx >= 0 && p_idx < uniforms.size()) { - switch (uniforms[p_idx].type) { + ERR_FAIL_COND_V(!shader_rid.is_valid(), PORT_TYPE_SCALAR); + + if (p_idx >= 0 && p_idx < uniforms[shader_rid].size()) { + switch (uniforms[shader_rid][p_idx].type) { case UniformType::UNIFORM_TYPE_FLOAT: return PORT_TYPE_SCALAR; case UniformType::UNIFORM_TYPE_INT: @@ -3311,6 +3388,8 @@ VisualShaderNodeUniformRef::PortType VisualShaderNodeUniformRef::get_port_type_b return PORT_TYPE_VECTOR_2D; case UniformType::UNIFORM_TYPE_VECTOR3: return PORT_TYPE_VECTOR_3D; + case UniformType::UNIFORM_TYPE_VECTOR4: + return PORT_TYPE_VECTOR_4D; case UniformType::UNIFORM_TYPE_TRANSFORM: return PORT_TYPE_TRANSFORM; case UniformType::UNIFORM_TYPE_COLOR: @@ -3380,94 +3459,94 @@ const VisualShaderNodeOutput::Port VisualShaderNodeOutput::ports[] = { //////////////////////////////////////////////////////////////////////// // Node3D, Vertex. //////////////////////////////////////////////////////////////////////// - { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_3D, "vertex", "VERTEX" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_3D, "normal", "NORMAL" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_3D, "tangent", "TANGENT" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_3D, "binormal", "BINORMAL" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_2D, "uv", "UV" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_2D, "uv2", "UV2" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_3D, "color", "COLOR.rgb" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_SCALAR, "alpha", "COLOR.a" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_SCALAR, "roughness", "ROUGHNESS" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_SCALAR, "point_size", "POINT_SIZE" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_TRANSFORM, "model_view_matrix", "MODELVIEW_MATRIX" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_3D, "Vertex", "VERTEX" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_3D, "Normal", "NORMAL" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_3D, "Tangent", "TANGENT" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_3D, "Binormal", "BINORMAL" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_2D, "UV", "UV" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_2D, "UV2", "UV2" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_3D, "Color", "COLOR.rgb" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_SCALAR, "Alpha", "COLOR.a" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_SCALAR, "Roughness", "ROUGHNESS" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_SCALAR, "Point Size", "POINT_SIZE" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_TRANSFORM, "Model View Matrix", "MODELVIEW_MATRIX" }, //////////////////////////////////////////////////////////////////////// // Node3D, Fragment. //////////////////////////////////////////////////////////////////////// - { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "albedo", "ALBEDO" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "alpha", "ALPHA" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "metallic", "METALLIC" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "roughness", "ROUGHNESS" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "specular", "SPECULAR" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "emission", "EMISSION" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "ao", "AO" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "ao_light_affect", "AO_LIGHT_AFFECT" }, - - { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "normal", "NORMAL" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "normal_map", "NORMAL_MAP" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "normal_map_depth", "NORMAL_MAP_DEPTH" }, - - { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "rim", "RIM" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "rim_tint", "RIM_TINT" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "clearcoat", "CLEARCOAT" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "clearcoat_roughness", "CLEARCOAT_ROUGHNESS" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "anisotropy", "ANISOTROPY" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_2D, "anisotropy_flow", "ANISOTROPY_FLOW" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "subsurf_scatter", "SSS_STRENGTH" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "backlight", "BACKLIGHT" }, - - { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "alpha_scissor_threshold", "ALPHA_SCISSOR_THRESHOLD" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "alpha_hash_scale", "ALPHA_HASH_SCALE" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "alpha_aa_edge", "ALPHA_ANTIALIASING_EDGE" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_2D, "alpha_uv", "ALPHA_TEXTURE_COORDINATE" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "Albedo", "ALBEDO" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "Alpha", "ALPHA" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "Metallic", "METALLIC" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "Roughness", "ROUGHNESS" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "Specular", "SPECULAR" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "Emission", "EMISSION" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "AO", "AO" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "AO Light Affect", "AO_LIGHT_AFFECT" }, + + { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "Normal", "NORMAL" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "Normal Map", "NORMAL_MAP" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "Normal Map Depth", "NORMAL_MAP_DEPTH" }, + + { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "Rim", "RIM" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "Rim Tint", "RIM_TINT" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "Clearcoat", "CLEARCOAT" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "Clearcoat Roughness", "CLEARCOAT_ROUGHNESS" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "Anisotropy", "ANISOTROPY" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_2D, "Anisotropy Flow", "ANISOTROPY_FLOW" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "Subsurf Scatter", "SSS_STRENGTH" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "Backlight", "BACKLIGHT" }, + + { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "Alpha Scissor Threshold", "ALPHA_SCISSOR_THRESHOLD" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "Alpha Hash Scale", "ALPHA_HASH_SCALE" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "Alpha AA Edge", "ALPHA_ANTIALIASING_EDGE" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_2D, "Alpha UV", "ALPHA_TEXTURE_COORDINATE" }, //////////////////////////////////////////////////////////////////////// // Node3D, Light. //////////////////////////////////////////////////////////////////////// - { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "diffuse", "DIFFUSE_LIGHT" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "specular", "SPECULAR_LIGHT" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_SCALAR, "alpha", "ALPHA" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "Diffuse", "DIFFUSE_LIGHT" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "Specular", "SPECULAR_LIGHT" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_SCALAR, "Alpha", "ALPHA" }, //////////////////////////////////////////////////////////////////////// // Canvas Item. //////////////////////////////////////////////////////////////////////// // Canvas Item, Vertex. //////////////////////////////////////////////////////////////////////// - { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_2D, "vertex", "VERTEX" }, - { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_2D, "uv", "UV" }, - { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_3D, "color", "COLOR.rgb" }, - { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_SCALAR, "alpha", "COLOR.a" }, - { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_SCALAR, "point_size", "POINT_SIZE" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_2D, "Vertex", "VERTEX" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_2D, "UV", "UV" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_3D, "Color", "COLOR.rgb" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_SCALAR, "Alpha", "COLOR.a" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_SCALAR, "Point Size", "POINT_SIZE" }, //////////////////////////////////////////////////////////////////////// // Canvas Item, Fragment. //////////////////////////////////////////////////////////////////////// - { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "color", "COLOR.rgb" }, - { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "alpha", "COLOR.a" }, - { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "normal", "NORMAL" }, - { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "normal_map", "NORMAL_MAP" }, - { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "normal_map_depth", "NORMAL_MAP_DEPTH" }, - { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "light_vertex", "LIGHT_VERTEX" }, - { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_2D, "shadow_vertex", "SHADOW_VERTEX" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "Color", "COLOR.rgb" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "Alpha", "COLOR.a" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "Normal", "NORMAL" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "Normal Map", "NORMAL_MAP" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "Normal Map Depth", "NORMAL_MAP_DEPTH" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "Light Vertex", "LIGHT_VERTEX" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_2D, "Shadow Vertex", "SHADOW_VERTEX" }, //////////////////////////////////////////////////////////////////////// // Canvas Item, Light. //////////////////////////////////////////////////////////////////////// - { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "light", "LIGHT.rgb" }, - { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_SCALAR, "light_alpha", "LIGHT.a" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "Light", "LIGHT.rgb" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_SCALAR, "Light Alpha", "LIGHT.a" }, //////////////////////////////////////////////////////////////////////// // Sky, Sky. //////////////////////////////////////////////////////////////////////// - { Shader::MODE_SKY, VisualShader::TYPE_SKY, VisualShaderNode::PORT_TYPE_VECTOR_3D, "color", "COLOR" }, - { Shader::MODE_SKY, VisualShader::TYPE_SKY, VisualShaderNode::PORT_TYPE_SCALAR, "alpha", "ALPHA" }, - { Shader::MODE_SKY, VisualShader::TYPE_SKY, VisualShaderNode::PORT_TYPE_VECTOR_3D, "fog", "FOG.rgb" }, - { Shader::MODE_SKY, VisualShader::TYPE_SKY, VisualShaderNode::PORT_TYPE_SCALAR, "fog_alpha", "FOG.a" }, + { Shader::MODE_SKY, VisualShader::TYPE_SKY, VisualShaderNode::PORT_TYPE_VECTOR_3D, "Color", "COLOR" }, + { Shader::MODE_SKY, VisualShader::TYPE_SKY, VisualShaderNode::PORT_TYPE_SCALAR, "Alpha", "ALPHA" }, + { Shader::MODE_SKY, VisualShader::TYPE_SKY, VisualShaderNode::PORT_TYPE_VECTOR_3D, "Fog", "FOG.rgb" }, + { Shader::MODE_SKY, VisualShader::TYPE_SKY, VisualShaderNode::PORT_TYPE_SCALAR, "Fog Alpha", "FOG.a" }, //////////////////////////////////////////////////////////////////////// // Fog, Fog. //////////////////////////////////////////////////////////////////////// - { Shader::MODE_FOG, VisualShader::TYPE_FOG, VisualShaderNode::PORT_TYPE_SCALAR, "density", "DENSITY" }, - { Shader::MODE_FOG, VisualShader::TYPE_FOG, VisualShaderNode::PORT_TYPE_VECTOR_3D, "albedo", "ALBEDO" }, - { Shader::MODE_FOG, VisualShader::TYPE_FOG, VisualShaderNode::PORT_TYPE_VECTOR_3D, "emission", "EMISSION" }, + { Shader::MODE_FOG, VisualShader::TYPE_FOG, VisualShaderNode::PORT_TYPE_SCALAR, "Density", "DENSITY" }, + { Shader::MODE_FOG, VisualShader::TYPE_FOG, VisualShaderNode::PORT_TYPE_VECTOR_3D, "Albedo", "ALBEDO" }, + { Shader::MODE_FOG, VisualShader::TYPE_FOG, VisualShaderNode::PORT_TYPE_VECTOR_3D, "Emission", "EMISSION" }, //////////////////////////////////////////////////////////////////////// { Shader::MODE_MAX, VisualShader::TYPE_MAX, VisualShaderNode::PORT_TYPE_TRANSFORM, nullptr, nullptr }, @@ -3511,7 +3590,7 @@ String VisualShaderNodeOutput::get_input_port_name(int p_port) const { while (ports[idx].mode != Shader::MODE_MAX) { if (ports[idx].mode == shader_mode && ports[idx].shader_type == shader_type) { if (count == p_port) { - return String(ports[idx].name).capitalize(); + return String(ports[idx].name); } count++; } @@ -3544,7 +3623,7 @@ bool VisualShaderNodeOutput::is_port_separator(int p_index) const { } if (shader_mode == Shader::MODE_SPATIAL && shader_type == VisualShader::TYPE_FRAGMENT) { String name = get_input_port_name(p_index); - return bool(name == "Ao" || name == "Normal" || name == "Rim" || name == "Clearcoat" || name == "Anisotropy" || name == "Subsurf Scatter" || name == "Alpha Scissor Threshold"); + return bool(name == "AO" || name == "Normal" || name == "Rim" || name == "Clearcoat" || name == "Anisotropy" || name == "Subsurf Scatter" || name == "Alpha Scissor Threshold"); } return false; } @@ -3697,6 +3776,11 @@ String VisualShaderNodeUniform::get_warning(Shader::Mode p_mode, VisualShader::T incompatible_type = true; } } break; + case RS::GLOBAL_VAR_TYPE_VEC4: { + if (!Object::cast_to<VisualShaderNodeVec4Uniform>(this)) { + incompatible_type = true; + } + } break; case RS::GLOBAL_VAR_TYPE_TRANSFORM: { if (!Object::cast_to<VisualShaderNodeTransformUniform>(this)) { incompatible_type = true; @@ -4404,6 +4488,9 @@ String VisualShaderNodeExpression::generate_code(Shader::Mode p_mode, VisualShad case PORT_TYPE_VECTOR_3D: tk = "vec3(0.0, 0.0, 0.0)"; break; + case PORT_TYPE_VECTOR_4D: + tk = "vec4(0.0, 0.0, 0.0, 0.0)"; + break; case PORT_TYPE_BOOLEAN: tk = "false"; break; @@ -4542,6 +4629,8 @@ String VisualShaderNodeVarying::get_type_str() const { return "vec2"; case VisualShader::VARYING_TYPE_VECTOR_3D: return "vec3"; + case VisualShader::VARYING_TYPE_VECTOR_4D: + return "vec4"; case VisualShader::VARYING_TYPE_COLOR: return "vec4"; case VisualShader::VARYING_TYPE_TRANSFORM: @@ -4558,6 +4647,8 @@ VisualShaderNodeVarying::PortType VisualShaderNodeVarying::get_port_type(VisualS return PORT_TYPE_VECTOR_2D; case VisualShader::VARYING_TYPE_VECTOR_3D: return PORT_TYPE_VECTOR_3D; + case VisualShader::VARYING_TYPE_VECTOR_4D: + return PORT_TYPE_VECTOR_4D; case VisualShader::VARYING_TYPE_COLOR: if (p_port == 1) { break; // scalar @@ -4718,6 +4809,9 @@ String VisualShaderNodeVaryingGetter::generate_code(Shader::Mode p_mode, VisualS case VisualShader::VARYING_TYPE_VECTOR_3D: from = "vec3(0.0)"; break; + case VisualShader::VARYING_TYPE_VECTOR_4D: + from = "vec4(0.0)"; + break; case VisualShader::VARYING_TYPE_COLOR: from = "vec3(0.0)"; from2 = "0.0"; diff --git a/scene/resources/visual_shader.h b/scene/resources/visual_shader.h index 2d4b2852e9..7ca4e5fc4a 100644 --- a/scene/resources/visual_shader.h +++ b/scene/resources/visual_shader.h @@ -44,8 +44,6 @@ class VisualShader : public Shader { friend class VisualShaderNodeVersionChecker; - Dictionary engine_version; - public: enum Type { TYPE_VERTEX, @@ -83,6 +81,7 @@ public: VARYING_TYPE_FLOAT, VARYING_TYPE_VECTOR_2D, VARYING_TYPE_VECTOR_3D, + VARYING_TYPE_VECTOR_4D, VARYING_TYPE_COLOR, VARYING_TYPE_TRANSFORM, VARYING_TYPE_MAX, @@ -126,7 +125,7 @@ private: }; struct Graph { - Map<int, Node> nodes; + RBMap<int, Node> nodes; List<Connection> connections; } graph[TYPE_MAX]; @@ -138,9 +137,9 @@ private: Vector2 graph_offset; HashMap<String, int> modes; - Set<StringName> flags; + HashSet<StringName> flags; - Map<String, Varying> varyings; + HashMap<String, Varying> varyings; List<Varying> varyings_list; mutable SafeFlag dirty; @@ -157,7 +156,7 @@ private: } }; - Error _write_node(Type p_type, StringBuilder *global_code, StringBuilder *global_code_per_node, Map<Type, StringBuilder> *global_code_per_func, StringBuilder &code, Vector<DefaultTextureParam> &def_tex_params, const VMap<ConnectionKey, const List<Connection>::Element *> &input_connections, const VMap<ConnectionKey, const List<Connection>::Element *> &output_connections, int node, Set<int> &processed, bool for_preview, Set<StringName> &r_classes) const; + Error _write_node(Type p_type, StringBuilder *global_code, StringBuilder *global_code_per_node, HashMap<Type, StringBuilder> *global_code_per_func, StringBuilder &code, Vector<DefaultTextureParam> &def_tex_params, const VMap<ConnectionKey, const List<Connection>::Element *> &input_connections, const VMap<ConnectionKey, const List<Connection>::Element *> &output_connections, int node, HashSet<int> &processed, bool for_preview, HashSet<StringName> &r_classes) const; void _input_type_changed(Type p_type, int p_id); bool has_func_name(RenderingServer::ShaderMode p_mode, const String &p_func_name) const; @@ -176,14 +175,6 @@ public: // internal methods void set_shader_type(Type p_type); Type get_shader_type() const; -public: - void set_engine_version(const Dictionary &p_version); - Dictionary get_engine_version() const; - -#ifndef DISABLE_DEPRECATED - void update_engine_version(const Dictionary &p_new_version); -#endif /* DISABLE_DEPRECATED */ - enum { NODE_ID_INVALID = -1, NODE_ID_OUTPUT = 0, @@ -254,10 +245,10 @@ class VisualShaderNode : public Resource { int port_preview = -1; - Map<int, Variant> default_input_values; - Map<int, bool> connected_input_ports; - Map<int, int> connected_output_ports; - Map<int, bool> expanded_output_ports; + HashMap<int, Variant> default_input_values; + HashMap<int, bool> connected_input_ports; + HashMap<int, int> connected_output_ports; + HashMap<int, bool> expanded_output_ports; protected: bool simple_decl = true; @@ -271,6 +262,7 @@ public: PORT_TYPE_SCALAR_INT, PORT_TYPE_VECTOR_2D, PORT_TYPE_VECTOR_3D, + PORT_TYPE_VECTOR_4D, PORT_TYPE_BOOLEAN, PORT_TYPE_TRANSFORM, PORT_TYPE_SAMPLER, @@ -326,7 +318,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 HashMap<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; @@ -446,6 +438,7 @@ 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 is_output_port_expandable(int p_port) const override; virtual String get_caption() const override; @@ -556,6 +549,7 @@ public: UNIFORM_TYPE_BOOLEAN, UNIFORM_TYPE_VECTOR2, UNIFORM_TYPE_VECTOR3, + UNIFORM_TYPE_VECTOR4, UNIFORM_TYPE_TRANSFORM, UNIFORM_TYPE_COLOR, UNIFORM_TYPE_SAMPLER, @@ -567,6 +561,7 @@ public: }; private: + RID shader_rid; String uniform_name = "[None]"; UniformType uniform_type = UniformType::UNIFORM_TYPE_FLOAT; @@ -574,9 +569,9 @@ protected: static void _bind_methods(); public: - static void add_uniform(const String &p_name, UniformType p_type); - static void clear_uniforms(); - static bool has_uniform(const String &p_name); + static void add_uniform(RID p_shader_rid, const String &p_name, UniformType p_type); + static void clear_uniforms(RID p_shader_rid); + static bool has_uniform(RID p_shader_rid, const String &p_name); public: virtual String get_caption() const override; @@ -589,9 +584,13 @@ public: virtual PortType get_output_port_type(int p_port) const override; virtual String get_output_port_name(int p_port) const override; + void set_shader_rid(const RID &p_shader); + void set_uniform_name(const String &p_name); String get_uniform_name() const; + void update_uniform_type(); + void _set_uniform_type(int p_uniform_type); int _get_uniform_type() const; @@ -676,9 +675,9 @@ protected: String name; }; - Map<int, Port> input_ports; - Map<int, Port> output_ports; - Map<int, Control *> controls; + HashMap<int, Port> input_ports; + HashMap<int, Port> output_ports; + HashMap<int, Control *> controls; protected: static void _bind_methods(); diff --git a/scene/resources/visual_shader_nodes.cpp b/scene/resources/visual_shader_nodes.cpp index 4e16353460..b8667f07fe 100644 --- a/scene/resources/visual_shader_nodes.cpp +++ b/scene/resources/visual_shader_nodes.cpp @@ -38,6 +38,8 @@ VisualShaderNodeVectorBase::PortType VisualShaderNodeVectorBase::get_input_port_ return PORT_TYPE_VECTOR_2D; case OP_TYPE_VECTOR_3D: return PORT_TYPE_VECTOR_3D; + case OP_TYPE_VECTOR_4D: + return PORT_TYPE_VECTOR_4D; default: break; } @@ -50,6 +52,8 @@ VisualShaderNodeVectorBase::PortType VisualShaderNodeVectorBase::get_output_port return PORT_TYPE_VECTOR_2D; case OP_TYPE_VECTOR_3D: return PORT_TYPE_VECTOR_3D; + case OP_TYPE_VECTOR_4D: + return PORT_TYPE_VECTOR_4D; default: break; } @@ -73,10 +77,11 @@ void VisualShaderNodeVectorBase::_bind_methods() { ClassDB::bind_method(D_METHOD("set_op_type", "type"), &VisualShaderNodeVectorBase::set_op_type); ClassDB::bind_method(D_METHOD("get_op_type"), &VisualShaderNodeVectorBase::get_op_type); - ADD_PROPERTY(PropertyInfo(Variant::INT, "op_type", PROPERTY_HINT_ENUM, "Vector2,Vector3"), "set_op_type", "get_op_type"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "op_type", PROPERTY_HINT_ENUM, "Vector2,Vector3,Vector4"), "set_op_type", "get_op_type"); BIND_ENUM_CONSTANT(OP_TYPE_VECTOR_2D); BIND_ENUM_CONSTANT(OP_TYPE_VECTOR_3D); + BIND_ENUM_CONSTANT(OP_TYPE_VECTOR_4D); BIND_ENUM_CONSTANT(OP_TYPE_MAX); } @@ -291,7 +296,7 @@ int VisualShaderNodeColorConstant::get_input_port_count() const { } VisualShaderNodeColorConstant::PortType VisualShaderNodeColorConstant::get_input_port_type(int p_port) const { - return PORT_TYPE_VECTOR_3D; + return PORT_TYPE_VECTOR_4D; } String VisualShaderNodeColorConstant::get_input_port_name(int p_port) const { @@ -299,15 +304,15 @@ String VisualShaderNodeColorConstant::get_input_port_name(int p_port) const { } int VisualShaderNodeColorConstant::get_output_port_count() const { - return 2; + return 1; } VisualShaderNodeColorConstant::PortType VisualShaderNodeColorConstant::get_output_port_type(int p_port) const { - return p_port == 0 ? PORT_TYPE_VECTOR_3D : PORT_TYPE_SCALAR; + return p_port == 0 ? PORT_TYPE_VECTOR_4D : PORT_TYPE_SCALAR; } String VisualShaderNodeColorConstant::get_output_port_name(int p_port) const { - return p_port == 0 ? "" : "alpha"; //no output port means the editor will be used as port + return ""; } bool VisualShaderNodeColorConstant::is_output_port_expandable(int p_port) const { @@ -318,11 +323,7 @@ bool VisualShaderNodeColorConstant::is_output_port_expandable(int p_port) const } String VisualShaderNodeColorConstant::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] + " = " + vformat("vec3(%.6f, %.6f, %.6f)", constant.r, constant.g, constant.b) + ";\n"; - code += " " + p_output_vars[1] + " = " + vformat("%.6f", constant.a) + ";\n"; - - return code; + return " " + p_output_vars[0] + " = " + vformat("vec4(%.6f, %.6f, %.6f, %.6f)", constant.r, constant.g, constant.b, constant.a) + ";\n"; } void VisualShaderNodeColorConstant::set_constant(const Color &p_constant) { @@ -477,6 +478,68 @@ void VisualShaderNodeVec3Constant::_bind_methods() { VisualShaderNodeVec3Constant::VisualShaderNodeVec3Constant() { } +////////////// Vector4 + +String VisualShaderNodeVec4Constant::get_caption() const { + return "Vector4Constant"; +} + +int VisualShaderNodeVec4Constant::get_input_port_count() const { + return 0; +} + +VisualShaderNodeVec4Constant::PortType VisualShaderNodeVec4Constant::get_input_port_type(int p_port) const { + return PORT_TYPE_VECTOR_4D; +} + +String VisualShaderNodeVec4Constant::get_input_port_name(int p_port) const { + return String(); +} + +int VisualShaderNodeVec4Constant::get_output_port_count() const { + return 1; +} + +VisualShaderNodeVec4Constant::PortType VisualShaderNodeVec4Constant::get_output_port_type(int p_port) const { + return PORT_TYPE_VECTOR_4D; +} + +String VisualShaderNodeVec4Constant::get_output_port_name(int p_port) const { + return ""; // No output port means the editor will be used as port. +} + +String VisualShaderNodeVec4Constant::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 { + return " " + p_output_vars[0] + " = " + vformat("vec4(%.6f, %.6f, %.6f, %.6f)", constant.x, constant.y, constant.z, constant.w) + ";\n"; +} + +void VisualShaderNodeVec4Constant::set_constant(const Quaternion &p_constant) { + if (constant.is_equal_approx(p_constant)) { + return; + } + constant = p_constant; + emit_changed(); +} + +Quaternion VisualShaderNodeVec4Constant::get_constant() const { + return constant; +} + +Vector<StringName> VisualShaderNodeVec4Constant::get_editable_properties() const { + Vector<StringName> props; + props.push_back("constant"); + return props; +} + +void VisualShaderNodeVec4Constant::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_constant", "constant"), &VisualShaderNodeVec4Constant::set_constant); + ClassDB::bind_method(D_METHOD("get_constant"), &VisualShaderNodeVec4Constant::get_constant); + + ADD_PROPERTY(PropertyInfo(Variant::QUATERNION, "constant"), "set_constant", "get_constant"); +} + +VisualShaderNodeVec4Constant::VisualShaderNodeVec4Constant() { +} + ////////////// Transform3D String VisualShaderNodeTransformConstant::get_caption() const { @@ -584,21 +647,25 @@ String VisualShaderNodeTexture::get_input_port_name(int p_port) const { } int VisualShaderNodeTexture::get_output_port_count() const { - return 2; + return 1; } VisualShaderNodeTexture::PortType VisualShaderNodeTexture::get_output_port_type(int p_port) const { - if (p_port == 0 && source == SOURCE_DEPTH) { - return PORT_TYPE_SCALAR; + switch (p_port) { + case 0: + return PORT_TYPE_VECTOR_4D; + default: + return PORT_TYPE_SCALAR; } - return p_port == 0 ? PORT_TYPE_VECTOR_3D : PORT_TYPE_SCALAR; } String VisualShaderNodeTexture::get_output_port_name(int p_port) const { - if (p_port == 0 && source == SOURCE_DEPTH) { - return "depth"; + switch (p_port) { + case 0: + return "color"; + default: + return ""; } - return p_port == 0 ? "rgb" : "alpha"; } bool VisualShaderNodeTexture::is_output_port_expandable(int p_port) const { @@ -633,7 +700,7 @@ String VisualShaderNodeTexture::generate_global(Shader::Mode p_mode, VisualShade case TYPE_DATA: break; case TYPE_COLOR: - u += " : hint_albedo"; + u += " : source_color"; break; case TYPE_NORMAL_MAP: u += " : hint_normal"; @@ -655,170 +722,116 @@ String VisualShaderNodeTexture::generate_code(Shader::Mode p_mode, VisualShader: default_uv = "vec2(0.0)"; } + String code; if (source == SOURCE_TEXTURE) { String id = make_unique_id(p_type, p_id, "tex"); - String code; if (p_input_vars[0].is_empty()) { // Use UV by default. - if (p_input_vars[1].is_empty()) { - code += " vec4 " + id + "_read = texture(" + id + ", " + default_uv + ");\n"; + code += " " + p_output_vars[0] + " = texture(" + id + ", " + default_uv + ");\n"; } else { - code += " vec4 " + id + "_read = textureLod(" + id + ", " + default_uv + ", " + p_input_vars[1] + ");\n"; + code += " " + p_output_vars[0] + " = textureLod(" + id + ", " + default_uv + ", " + p_input_vars[1] + ");\n"; } - } else if (p_input_vars[1].is_empty()) { //no lod - code += " vec4 " + id + "_read = texture(" + id + ", " + p_input_vars[0] + ");\n"; + code += " " + p_output_vars[0] + " = texture(" + id + ", " + p_input_vars[0] + ");\n"; } else { - code += " vec4 " + id + "_read = textureLod(" + id + ", " + p_input_vars[0] + ", " + p_input_vars[1] + ");\n"; + code += " " + p_output_vars[0] + " = textureLod(" + id + ", " + p_input_vars[0] + ", " + p_input_vars[1] + ");\n"; } - - code += " " + p_output_vars[0] + " = " + id + "_read.rgb;\n"; - code += " " + p_output_vars[1] + " = " + id + "_read.a;\n"; return code; } if (source == SOURCE_PORT) { String id = p_input_vars[2]; - - String code; - code += " {\n"; if (id.is_empty()) { - code += " vec4 " + id + "_tex_read = vec4(0.0);\n"; + code += " " + p_output_vars[0] + " = vec4(0.0);\n"; } else { if (p_input_vars[0].is_empty()) { // Use UV by default. - if (p_input_vars[1].is_empty()) { - code += " vec4 " + id + "_tex_read = texture(" + id + ", " + default_uv + ");\n"; + code += " " + p_output_vars[0] + " = texture(" + id + ", " + default_uv + ");\n"; } else { - code += " vec4 " + id + "_tex_read = textureLod(" + id + ", " + default_uv + ", " + p_input_vars[1] + ");\n"; + code += " " + p_output_vars[0] + " = textureLod(" + id + ", " + default_uv + ", " + p_input_vars[1] + ");\n"; } - } else if (p_input_vars[1].is_empty()) { //no lod - code += " vec4 " + id + "_tex_read = texture(" + id + ", " + p_input_vars[0] + ");\n"; + code += " " + p_output_vars[0] + " = texture(" + id + ", " + p_input_vars[0] + ");\n"; } else { - code += " vec4 " + id + "_tex_read = textureLod(" + id + ", " + p_input_vars[0] + ", " + p_input_vars[1] + ");\n"; + code += " " + p_output_vars[0] + " = textureLod(" + id + ", " + p_input_vars[0] + ", " + p_input_vars[1] + ");\n"; } - - code += " " + p_output_vars[0] + " = " + id + "_tex_read.rgb;\n"; - code += " " + p_output_vars[1] + " = " + id + "_tex_read.a;\n"; } - code += " }\n"; return code; } if (source == SOURCE_SCREEN && (p_mode == Shader::MODE_SPATIAL || p_mode == Shader::MODE_CANVAS_ITEM) && p_type == VisualShader::TYPE_FRAGMENT) { - String code = " {\n"; if (p_input_vars[0].is_empty() || p_for_preview) { // Use UV by default. - if (p_input_vars[1].is_empty()) { - code += " vec4 _tex_read = textureLod(SCREEN_TEXTURE, " + default_uv + ", 0.0 );\n"; + code += " " + p_output_vars[0] + " = textureLod(SCREEN_TEXTURE, " + default_uv + ", 0.0);\n"; } else { - code += " vec4 _tex_read = textureLod(SCREEN_TEXTURE, " + default_uv + ", " + p_input_vars[1] + ");\n"; + code += " " + p_output_vars[0] + " = textureLod(SCREEN_TEXTURE, " + default_uv + ", " + p_input_vars[1] + ");\n"; } - } else if (p_input_vars[1].is_empty()) { //no lod - code += " vec4 _tex_read = textureLod(SCREEN_TEXTURE, " + p_input_vars[0] + ", 0.0);\n"; + code += " " + p_output_vars[0] + " = textureLod(SCREEN_TEXTURE, " + p_input_vars[0] + ", 0.0);\n"; } else { - code += " vec4 _tex_read = textureLod(SCREEN_TEXTURE, " + p_input_vars[0] + ", " + p_input_vars[1] + ");\n"; + code += " " + p_output_vars[0] + " = textureLod(SCREEN_TEXTURE, " + p_input_vars[0] + ", " + p_input_vars[1] + ");\n"; } - - code += " " + p_output_vars[0] + " = _tex_read.rgb;\n"; - code += " " + p_output_vars[1] + " = _tex_read.a;\n"; - code += " }\n"; return code; } if (source == SOURCE_2D_TEXTURE && p_mode == Shader::MODE_CANVAS_ITEM && p_type == VisualShader::TYPE_FRAGMENT) { - String code = " {\n"; if (p_input_vars[0].is_empty()) { // Use UV by default. - if (p_input_vars[1].is_empty()) { - code += " vec4 _tex_read = texture(TEXTURE, " + default_uv + ");\n"; + code += " " + p_output_vars[0] + " = texture(TEXTURE, " + default_uv + ");\n"; } else { - code += " vec4 _tex_read = textureLod(TEXTURE, " + default_uv + ", " + p_input_vars[1] + ");\n"; + code += " " + p_output_vars[0] + " = textureLod(TEXTURE, " + default_uv + ", " + p_input_vars[1] + ");\n"; } - } else if (p_input_vars[1].is_empty()) { //no lod - code += " vec4 _tex_read = texture(TEXTURE, " + p_input_vars[0] + ");\n"; + code += " " + p_output_vars[0] + " = texture(TEXTURE, " + p_input_vars[0] + ");\n"; } else { - code += " vec4 _tex_read = textureLod(TEXTURE, " + p_input_vars[0] + ", " + p_input_vars[1] + ");\n"; + code += " " + p_output_vars[0] + " = textureLod(TEXTURE, " + p_input_vars[0] + ", " + p_input_vars[1] + ");\n"; } - - code += " " + p_output_vars[0] + " = _tex_read.rgb;\n"; - code += " " + p_output_vars[1] + " = _tex_read.a;\n"; - code += " }\n"; return code; } if (source == SOURCE_2D_NORMAL && p_mode == Shader::MODE_CANVAS_ITEM && p_type == VisualShader::TYPE_FRAGMENT) { - String code = " {\n"; if (p_input_vars[0].is_empty()) { // Use UV by default. - if (p_input_vars[1].is_empty()) { - code += " vec4 _tex_read = texture(NORMAL_TEXTURE, " + default_uv + ");\n"; + code += " " + p_output_vars[0] + " = texture(NORMAL_TEXTURE, " + default_uv + ");\n"; } else { - code += " vec4 _tex_read = textureLod(NORMAL_TEXTURE, " + default_uv + ", " + p_input_vars[1] + ");\n"; + code += " " + p_output_vars[0] + " = textureLod(NORMAL_TEXTURE, " + default_uv + ", " + p_input_vars[1] + ");\n"; } - } else if (p_input_vars[1].is_empty()) { //no lod - code += " vec4 _tex_read = texture(NORMAL_TEXTURE, " + p_input_vars[0] + ".xy);\n"; + code += " " + p_output_vars[0] + " = texture(NORMAL_TEXTURE, " + p_input_vars[0] + ".xy);\n"; } else { - code += " vec4 _tex_read = textureLod(NORMAL_TEXTURE, " + p_input_vars[0] + ".xy, " + p_input_vars[1] + ");\n"; + code += " " + p_output_vars[0] + " = textureLod(NORMAL_TEXTURE, " + p_input_vars[0] + ".xy, " + p_input_vars[1] + ");\n"; } - - code += " " + p_output_vars[0] + " = _tex_read.rgb;\n"; - code += " " + p_output_vars[1] + " = _tex_read.a;\n"; - code += " }\n"; return code; } - if (p_for_preview) // DEPTH_TEXTURE is not supported in preview(canvas_item) shader - { - if (source == SOURCE_DEPTH) { - String code; - code += " " + p_output_vars[0] + " = 0.0;\n"; - code += " " + p_output_vars[1] + " = 1.0;\n"; - return code; - } - } - - if (source == SOURCE_DEPTH && p_mode == Shader::MODE_SPATIAL && p_type == VisualShader::TYPE_FRAGMENT) { - String code = " {\n"; - if (p_input_vars[0].is_empty()) { // Use UV by default. - - if (p_input_vars[1].is_empty()) { - code += " float _depth = texture(DEPTH_TEXTURE, " + default_uv + ").r;\n"; + if (source == SOURCE_DEPTH) { + if (!p_for_preview && p_mode == Shader::MODE_SPATIAL && p_type == VisualShader::TYPE_FRAGMENT) { + code += " {\n"; + if (p_input_vars[0].is_empty()) { // Use UV by default. + if (p_input_vars[1].is_empty()) { + code += " float _depth = texture(DEPTH_TEXTURE, " + default_uv + ").r;\n"; + } else { + code += " float _depth = textureLod(DEPTH_TEXTURE, " + default_uv + ", " + p_input_vars[1] + ").r;\n"; + } + } else if (p_input_vars[1].is_empty()) { + //no lod + code += " float _depth = texture(DEPTH_TEXTURE, " + p_input_vars[0] + ".xy).r;\n"; } else { - code += " float _depth = textureLod(DEPTH_TEXTURE, " + default_uv + ", " + p_input_vars[1] + ").r;\n"; + code += " float _depth = textureLod(DEPTH_TEXTURE, " + p_input_vars[0] + ".xy, " + p_input_vars[1] + ").r;\n"; } - } else if (p_input_vars[1].is_empty()) { - //no lod - code += " float _depth = texture(DEPTH_TEXTURE, " + p_input_vars[0] + ".xy).r;\n"; - } else { - code += " float _depth = textureLod(DEPTH_TEXTURE, " + p_input_vars[0] + ".xy, " + p_input_vars[1] + ").r;\n"; + code += " " + p_output_vars[0] + " = vec4(_depth, _depth, _depth, 1.0);\n"; + code += " }\n"; + return code; } - - code += " " + p_output_vars[0] + " = _depth;\n"; - code += " " + p_output_vars[1] + " = 1.0;\n"; - code += " }\n"; - return code; - } else if (source == SOURCE_DEPTH) { - String code; - code += " " + p_output_vars[0] + " = 0.0;\n"; - code += " " + p_output_vars[1] + " = 1.0;\n"; - return code; } - //none - String code; - code += " " + p_output_vars[0] + " = vec3(0.0);\n"; - code += " " + p_output_vars[1] + " = 1.0;\n"; + code += " " + p_output_vars[0] + " = vec4(0.0);\n"; return code; } @@ -1157,15 +1170,15 @@ String VisualShaderNodeSample3D::get_input_port_name(int p_port) const { } int VisualShaderNodeSample3D::get_output_port_count() const { - return 2; + return 1; } VisualShaderNodeSample3D::PortType VisualShaderNodeSample3D::get_output_port_type(int p_port) const { - return p_port == 0 ? PORT_TYPE_VECTOR_3D : PORT_TYPE_SCALAR; + return p_port == 0 ? PORT_TYPE_VECTOR_4D : PORT_TYPE_SCALAR; } String VisualShaderNodeSample3D::get_output_port_name(int p_port) const { - return p_port == 0 ? "rgb" : "alpha"; + return "color"; } bool VisualShaderNodeSample3D::is_output_port_expandable(int p_port) const { @@ -1195,7 +1208,6 @@ String VisualShaderNodeSample3D::generate_code(Shader::Mode p_mode, VisualShader String code; if (source == SOURCE_TEXTURE || source == SOURCE_PORT) { String id; - code += " {\n"; if (source == SOURCE_TEXTURE) { id = make_unique_id(p_type, p_id, "tex3d"); } else { @@ -1204,27 +1216,22 @@ String VisualShaderNodeSample3D::generate_code(Shader::Mode p_mode, VisualShader if (!id.is_empty()) { if (p_input_vars[0].is_empty()) { // Use UV by default. if (p_input_vars[1].is_empty()) { - code += " vec4 " + id + "_tex_read = texture(" + id + ", " + default_uv + ");\n"; + code += " " + p_output_vars[0] + " = texture(" + id + ", " + default_uv + ");\n"; } else { - code += " vec4 " + id + "_tex_read = textureLod(" + id + ", " + default_uv + ", " + p_input_vars[1] + ");\n"; + code += " " + p_output_vars[0] + " = textureLod(" + id + ", " + default_uv + ", " + p_input_vars[1] + ");\n"; } } else if (p_input_vars[1].is_empty()) { //no lod - code += " vec4 " + id + "_tex_read = texture(" + id + ", " + p_input_vars[0] + ");\n"; + code += " " + p_output_vars[0] + " = texture(" + id + ", " + p_input_vars[0] + ");\n"; } else { - code += " vec4 " + id + "_tex_read = textureLod(" + id + ", " + p_input_vars[0] + ", " + p_input_vars[1] + ");\n"; + code += " " + p_output_vars[0] + " = textureLod(" + id + ", " + p_input_vars[0] + ", " + p_input_vars[1] + ");\n"; } } else { - code += " vec4 " + id + "_tex_read = vec4(0.0);\n"; + code += " " + p_output_vars[0] + " = vec4(0.0);\n"; } - - code += " " + p_output_vars[0] + " = " + id + "_tex_read.rgb;\n"; - code += " " + p_output_vars[1] + " = " + id + "_tex_read.a;\n"; - code += " }\n"; return code; } - code += " " + p_output_vars[0] + " = vec3(0.0);\n"; - code += " " + p_output_vars[1] + " = 1.0;\n"; + code += " " + p_output_vars[0] + " = vec4(0.0);\n"; return code; } @@ -1422,15 +1429,15 @@ String VisualShaderNodeCubemap::get_input_port_name(int p_port) const { } int VisualShaderNodeCubemap::get_output_port_count() const { - return 2; + return 1; } VisualShaderNodeCubemap::PortType VisualShaderNodeCubemap::get_output_port_type(int p_port) const { - return p_port == 0 ? PORT_TYPE_VECTOR_3D : PORT_TYPE_SCALAR; + return PORT_TYPE_VECTOR_4D; } String VisualShaderNodeCubemap::get_output_port_name(int p_port) const { - return p_port == 0 ? "rgb" : "alpha"; + return "color"; } bool VisualShaderNodeCubemap::is_output_port_expandable(int p_port) const { @@ -1456,7 +1463,7 @@ String VisualShaderNodeCubemap::generate_global(Shader::Mode p_mode, VisualShade case TYPE_DATA: break; case TYPE_COLOR: - u += " : hint_albedo"; + u += " : source_color"; break; case TYPE_NORMAL_MAP: u += " : hint_normal"; @@ -1484,37 +1491,28 @@ String VisualShaderNodeCubemap::generate_code(Shader::Mode p_mode, VisualShader: } else if (source == SOURCE_PORT) { id = p_input_vars[2]; } else { - return String(); + return code; } - code += " {\n"; - if (id.is_empty()) { - code += " vec4 " + id + "_read = vec4(0.0);\n"; - code += " " + p_output_vars[0] + " = " + id + "_read.rgb;\n"; - code += " " + p_output_vars[1] + " = " + id + "_read.a;\n"; - code += " }\n"; + code += " " + p_output_vars[0] + " = vec4(0.0);\n"; return code; } if (p_input_vars[0].is_empty()) { // Use UV by default. if (p_input_vars[1].is_empty()) { - code += " vec4 " + id + "_read = texture(" + id + ", " + default_uv + ");\n"; + code += " " + p_output_vars[0] + " = texture(" + id + ", " + default_uv + ");\n"; } else { - code += " vec4 " + id + "_read = textureLod(" + id + ", " + default_uv + ", " + p_input_vars[1] + " );\n"; + code += " " + p_output_vars[0] + " = textureLod(" + id + ", " + default_uv + ", " + p_input_vars[1] + ");\n"; } } else if (p_input_vars[1].is_empty()) { //no lod - code += " vec4 " + id + "_read = texture(" + id + ", " + p_input_vars[0] + ");\n"; + code += " " + p_output_vars[0] + " = texture(" + id + ", " + p_input_vars[0] + ");\n"; } else { - code += " vec4 " + id + "_read = textureLod(" + id + ", " + p_input_vars[0] + ", " + p_input_vars[1] + ");\n"; + code += " " + p_output_vars[0] + " = textureLod(" + id + ", " + p_input_vars[0] + ", " + p_input_vars[1] + ");\n"; } - code += " " + p_output_vars[0] + " = " + id + "_read.rgb;\n"; - code += " " + p_output_vars[1] + " = " + id + "_read.a;\n"; - code += " }\n"; - return code; } @@ -1891,8 +1889,10 @@ String VisualShaderNodeVectorOp::generate_code(Shader::Mode p_mode, VisualShader code += "min(" + p_input_vars[0] + ", " + p_input_vars[1] + ");\n"; break; case OP_CROSS: - if (op_type == OP_TYPE_VECTOR_2D) { // not supported + if (op_type == OP_TYPE_VECTOR_2D) { // Not supported. code += "vec2(0.0);\n"; + } else if (op_type == OP_TYPE_VECTOR_4D) { // Not supported. + code += "vec4(0.0);\n"; } else { code += "cross(" + p_input_vars[0] + ", " + p_input_vars[1] + ");\n"; } @@ -1901,8 +1901,10 @@ String VisualShaderNodeVectorOp::generate_code(Shader::Mode p_mode, VisualShader code += "atan(" + p_input_vars[0] + ", " + p_input_vars[1] + ");\n"; break; case OP_REFLECT: - if (op_type == OP_TYPE_VECTOR_2D) { // not supported + if (op_type == OP_TYPE_VECTOR_2D) { // Not supported. code += "vec2(0.0);\n"; + } else if (op_type == OP_TYPE_VECTOR_4D) { // Not supported. + code += "vec4(0.0);\n"; } else { code += "reflect(" + p_input_vars[0] + ", " + p_input_vars[1] + ");\n"; } @@ -1931,6 +1933,10 @@ void VisualShaderNodeVectorOp::set_op_type(OpType p_op_type) { set_input_port_default_value(0, Vector3(), get_input_port_default_value(0)); set_input_port_default_value(1, Vector3(), get_input_port_default_value(1)); } break; + case OP_TYPE_VECTOR_4D: { + set_input_port_default_value(0, Quaternion(), get_input_port_default_value(0)); + set_input_port_default_value(1, Quaternion(), get_input_port_default_value(1)); + } break; default: break; } @@ -1960,7 +1966,7 @@ Vector<StringName> VisualShaderNodeVectorOp::get_editable_properties() const { String VisualShaderNodeVectorOp::get_warning(Shader::Mode p_mode, VisualShader::Type p_type) const { bool invalid_type = false; - if (op_type == OP_TYPE_VECTOR_2D) { + if (op_type == OP_TYPE_VECTOR_2D || op_type == OP_TYPE_VECTOR_4D) { if (op == OP_CROSS || op == OP_REFLECT) { invalid_type = true; } @@ -2004,6 +2010,10 @@ VisualShaderNodeVectorOp::VisualShaderNodeVectorOp() { set_input_port_default_value(0, Vector3()); set_input_port_default_value(1, Vector3()); } break; + case OP_TYPE_VECTOR_4D: { + set_input_port_default_value(0, Quaternion()); + set_input_port_default_value(1, Quaternion()); + } break; default: break; } @@ -2452,7 +2462,7 @@ void VisualShaderNodeFloatFunc::_bind_methods() { ClassDB::bind_method(D_METHOD("set_function", "func"), &VisualShaderNodeFloatFunc::set_function); ClassDB::bind_method(D_METHOD("get_function"), &VisualShaderNodeFloatFunc::get_function); - ADD_PROPERTY(PropertyInfo(Variant::INT, "function", PROPERTY_HINT_ENUM, "Sin,Cos,Tan,ASin,ACos,ATan,SinH,CosH,TanH,Log,Exp,Sqrt,Abs,Sign,Floor,Round,Ceil,Frac,Saturate,Negate,ACosH,ASinH,ATanH,Degrees,Exp2,InverseSqrt,Log2,Radians,Reciprocal,RoundEven,Trunc,OneMinus"), "set_function", "get_function"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "function", PROPERTY_HINT_ENUM, "Sin,Cos,Tan,ASin,ACos,ATan,SinH,CosH,TanH,Log,Exp,Sqrt,Abs,Sign,Floor,Round,Ceil,Fract,Saturate,Negate,ACosH,ASinH,ATanH,Degrees,Exp2,InverseSqrt,Log2,Radians,Reciprocal,RoundEven,Trunc,OneMinus"), "set_function", "get_function"); BIND_ENUM_CONSTANT(FUNC_SIN); BIND_ENUM_CONSTANT(FUNC_COS); @@ -2471,7 +2481,7 @@ void VisualShaderNodeFloatFunc::_bind_methods() { BIND_ENUM_CONSTANT(FUNC_FLOOR); BIND_ENUM_CONSTANT(FUNC_ROUND); BIND_ENUM_CONSTANT(FUNC_CEIL); - BIND_ENUM_CONSTANT(FUNC_FRAC); + BIND_ENUM_CONSTANT(FUNC_FRACT); BIND_ENUM_CONSTANT(FUNC_SATURATE); BIND_ENUM_CONSTANT(FUNC_NEGATE); BIND_ENUM_CONSTANT(FUNC_ACOSH); @@ -2598,8 +2608,6 @@ String VisualShaderNodeVectorFunc::generate_code(Shader::Mode p_mode, VisualShad "", // FUNC_SATURATE "-($)", "1.0 / ($)", - "", // FUNC_RGB2HSV - "", // FUNC_HSV2RGB "abs($)", "acos($)", "acosh($)", @@ -2636,8 +2644,10 @@ String VisualShaderNodeVectorFunc::generate_code(Shader::Mode p_mode, VisualShad if (op_type == OP_TYPE_VECTOR_2D) { code = "max(min($, vec2(1.0)), vec2(0.0))"; - } else { + } else if (op_type == OP_TYPE_VECTOR_3D) { code = "max(min($, vec3(1.0)), vec3(0.0))"; + } else { + code = "max(min($, vec4(1.0)), vec4(0.0))"; } return " " + p_output_vars[0] + " = " + code.replace("$", p_input_vars[0]) + ";\n"; } @@ -2646,44 +2656,16 @@ String VisualShaderNodeVectorFunc::generate_code(Shader::Mode p_mode, VisualShad String code; if (op_type == OP_TYPE_VECTOR_2D) { - code = "vec2(1.0, 1.0) - $"; + code = "vec2(1.0) - $"; + } else if (op_type == OP_TYPE_VECTOR_3D) { + code = "vec3(1.0) - $"; } else { - code = "vec3(1.0, 1.0, 1.0) - $"; + code = "vec4(1.0) - $"; } return " " + p_output_vars[0] + " = " + code.replace("$", p_input_vars[0]) + ";\n"; } - String code; - - if (func == FUNC_RGB2HSV) { - if (op_type == OP_TYPE_VECTOR_2D) { // not supported - return " " + p_output_vars[0] + " = vec2(0.0);\n"; - } - code += " {\n"; - code += " vec3 c = " + p_input_vars[0] + ";\n"; - code += " vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0);\n"; - code += " vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g));\n"; - code += " vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r));\n"; - code += " float d = q.x - min(q.w, q.y);\n"; - code += " float e = 1.0e-10;\n"; - code += " " + p_output_vars[0] + " = vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x);\n"; - code += " }\n"; - } else if (func == FUNC_HSV2RGB) { - if (op_type == OP_TYPE_VECTOR_2D) { // not supported - return " " + p_output_vars[0] + " = vec2(0.0);\n"; - } - code += " {\n"; - code += " vec3 c = " + p_input_vars[0] + ";\n"; - code += " vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);\n"; - code += " vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);\n"; - code += " " + p_output_vars[0] + " = c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);\n"; - code += " }\n"; - - } else { - code += " " + p_output_vars[0] + " = " + String(funcs[func]).replace("$", p_input_vars[0]) + ";\n"; - } - - return code; + return " " + p_output_vars[0] + " = " + String(funcs[func]).replace("$", p_input_vars[0]) + ";\n"; } void VisualShaderNodeVectorFunc::set_op_type(OpType p_op_type) { @@ -2698,6 +2680,9 @@ void VisualShaderNodeVectorFunc::set_op_type(OpType p_op_type) { case OP_TYPE_VECTOR_3D: { set_input_port_default_value(0, Vector3(), get_input_port_default_value(0)); } break; + case OP_TYPE_VECTOR_4D: { + set_input_port_default_value(0, Quaternion(), get_input_port_default_value(0)); + } break; default: break; } @@ -2710,13 +2695,6 @@ void VisualShaderNodeVectorFunc::set_function(Function p_func) { if (func == p_func) { return; } - if (p_func == FUNC_RGB2HSV) { - simple_decl = false; - } else if (p_func == FUNC_HSV2RGB) { - simple_decl = false; - } else { - simple_decl = true; - } func = p_func; emit_changed(); } @@ -2731,34 +2709,16 @@ Vector<StringName> VisualShaderNodeVectorFunc::get_editable_properties() const { return props; } -String VisualShaderNodeVectorFunc::get_warning(Shader::Mode p_mode, VisualShader::Type p_type) const { - bool invalid_type = false; - - if (op_type == OP_TYPE_VECTOR_2D) { - if (func == FUNC_RGB2HSV || func == FUNC_HSV2RGB) { - invalid_type = true; - } - } - - if (invalid_type) { - return RTR("Invalid function for that type."); - } - - return String(); -} - void VisualShaderNodeVectorFunc::_bind_methods() { ClassDB::bind_method(D_METHOD("set_function", "func"), &VisualShaderNodeVectorFunc::set_function); ClassDB::bind_method(D_METHOD("get_function"), &VisualShaderNodeVectorFunc::get_function); - ADD_PROPERTY(PropertyInfo(Variant::INT, "function", PROPERTY_HINT_ENUM, "Normalize,Saturate,Negate,Reciprocal,RGB2HSV,HSV2RGB,Abs,ACos,ACosH,ASin,ASinH,ATan,ATanH,Ceil,Cos,CosH,Degrees,Exp,Exp2,Floor,Frac,InverseSqrt,Log,Log2,Radians,Round,RoundEven,Sign,Sin,SinH,Sqrt,Tan,TanH,Trunc,OneMinus"), "set_function", "get_function"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "function", PROPERTY_HINT_ENUM, "Normalize,Saturate,Negate,Reciprocal,Abs,ACos,ACosH,ASin,ASinH,ATan,ATanH,Ceil,Cos,CosH,Degrees,Exp,Exp2,Floor,Fract,InverseSqrt,Log,Log2,Radians,Round,RoundEven,Sign,Sin,SinH,Sqrt,Tan,TanH,Trunc,OneMinus"), "set_function", "get_function"); BIND_ENUM_CONSTANT(FUNC_NORMALIZE); BIND_ENUM_CONSTANT(FUNC_SATURATE); BIND_ENUM_CONSTANT(FUNC_NEGATE); BIND_ENUM_CONSTANT(FUNC_RECIPROCAL); - BIND_ENUM_CONSTANT(FUNC_RGB2HSV); - BIND_ENUM_CONSTANT(FUNC_HSV2RGB); BIND_ENUM_CONSTANT(FUNC_ABS); BIND_ENUM_CONSTANT(FUNC_ACOS); BIND_ENUM_CONSTANT(FUNC_ACOSH); @@ -2773,7 +2733,7 @@ void VisualShaderNodeVectorFunc::_bind_methods() { BIND_ENUM_CONSTANT(FUNC_EXP); BIND_ENUM_CONSTANT(FUNC_EXP2); BIND_ENUM_CONSTANT(FUNC_FLOOR); - BIND_ENUM_CONSTANT(FUNC_FRAC); + BIND_ENUM_CONSTANT(FUNC_FRACT); BIND_ENUM_CONSTANT(FUNC_INVERSE_SQRT); BIND_ENUM_CONSTANT(FUNC_LOG); BIND_ENUM_CONSTANT(FUNC_LOG2); @@ -2799,6 +2759,9 @@ VisualShaderNodeVectorFunc::VisualShaderNodeVectorFunc() { case OP_TYPE_VECTOR_3D: { set_input_port_default_value(0, Vector3()); } break; + case OP_TYPE_VECTOR_4D: { + set_input_port_default_value(0, Quaternion()); + } break; default: break; } @@ -2846,6 +2809,25 @@ String VisualShaderNodeColorFunc::generate_code(Shader::Mode p_mode, VisualShade code += " " + p_output_vars[0] + " = vec3(max2, max2, max2);\n"; code += " }\n"; break; + case FUNC_HSV2RGB: + code += " {\n"; + code += " vec3 c = " + p_input_vars[0] + ";\n"; + code += " vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);\n"; + code += " vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);\n"; + code += " " + p_output_vars[0] + " = c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);\n"; + code += " }\n"; + break; + case FUNC_RGB2HSV: + code += " {\n"; + code += " vec3 c = " + p_input_vars[0] + ";\n"; + code += " vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0);\n"; + code += " vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g));\n"; + code += " vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r));\n"; + code += " float d = q.x - min(q.w, q.y);\n"; + code += " float e = 1.0e-10;\n"; + code += " " + p_output_vars[0] + " = vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x);\n"; + code += " }\n"; + break; case FUNC_SEPIA: code += " {\n"; code += " vec3 c = " + p_input_vars[0] + ";\n"; @@ -2885,9 +2867,11 @@ void VisualShaderNodeColorFunc::_bind_methods() { ClassDB::bind_method(D_METHOD("set_function", "func"), &VisualShaderNodeColorFunc::set_function); ClassDB::bind_method(D_METHOD("get_function"), &VisualShaderNodeColorFunc::get_function); - ADD_PROPERTY(PropertyInfo(Variant::INT, "function", PROPERTY_HINT_ENUM, "Grayscale,Sepia"), "set_function", "get_function"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "function", PROPERTY_HINT_ENUM, "Grayscale,HSV2RGB,RGB2HSV,Sepia"), "set_function", "get_function"); BIND_ENUM_CONSTANT(FUNC_GRAYSCALE); + BIND_ENUM_CONSTANT(FUNC_HSV2RGB); + BIND_ENUM_CONSTANT(FUNC_RGB2HSV); BIND_ENUM_CONSTANT(FUNC_SEPIA); BIND_ENUM_CONSTANT(FUNC_MAX); } @@ -3189,6 +3173,9 @@ void VisualShaderNodeVectorLen::set_op_type(OpType p_op_type) { case OP_TYPE_VECTOR_3D: { set_input_port_default_value(0, Vector3(), get_input_port_default_value(0)); } break; + case OP_TYPE_VECTOR_4D: { + set_input_port_default_value(0, Quaternion(), get_input_port_default_value(0)); + } break; default: break; } @@ -3258,6 +3245,8 @@ VisualShaderNodeDerivativeFunc::PortType VisualShaderNodeDerivativeFunc::get_inp return PORT_TYPE_VECTOR_2D; case OP_TYPE_VECTOR_3D: return PORT_TYPE_VECTOR_3D; + case OP_TYPE_VECTOR_4D: + return PORT_TYPE_VECTOR_4D; default: break; } @@ -3278,6 +3267,8 @@ VisualShaderNodeDerivativeFunc::PortType VisualShaderNodeDerivativeFunc::get_out return PORT_TYPE_VECTOR_2D; case OP_TYPE_VECTOR_3D: return PORT_TYPE_VECTOR_3D; + case OP_TYPE_VECTOR_4D: + return PORT_TYPE_VECTOR_4D; default: break; } @@ -3315,6 +3306,9 @@ void VisualShaderNodeDerivativeFunc::set_op_type(OpType p_op_type) { case OP_TYPE_VECTOR_3D: { set_input_port_default_value(0, Vector3(), get_input_port_default_value(0)); } break; + case OP_TYPE_VECTOR_4D: { + set_input_port_default_value(0, Quaternion(), get_input_port_default_value(0)); + } break; default: break; } @@ -3353,12 +3347,13 @@ void VisualShaderNodeDerivativeFunc::_bind_methods() { ClassDB::bind_method(D_METHOD("set_function", "func"), &VisualShaderNodeDerivativeFunc::set_function); ClassDB::bind_method(D_METHOD("get_function"), &VisualShaderNodeDerivativeFunc::get_function); - ADD_PROPERTY(PropertyInfo(Variant::INT, "op_type", PROPERTY_HINT_ENUM, "Scalar,Vector2,Vector3"), "set_op_type", "get_op_type"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "op_type", PROPERTY_HINT_ENUM, "Scalar,Vector2,Vector3,Vector4"), "set_op_type", "get_op_type"); ADD_PROPERTY(PropertyInfo(Variant::INT, "function", PROPERTY_HINT_ENUM, "Sum,X,Y"), "set_function", "get_function"); BIND_ENUM_CONSTANT(OP_TYPE_SCALAR); BIND_ENUM_CONSTANT(OP_TYPE_VECTOR_2D); BIND_ENUM_CONSTANT(OP_TYPE_VECTOR_3D); + BIND_ENUM_CONSTANT(OP_TYPE_VECTOR_4D); BIND_ENUM_CONSTANT(OP_TYPE_MAX); BIND_ENUM_CONSTANT(FUNC_SUM); @@ -3389,6 +3384,8 @@ VisualShaderNodeClamp::PortType VisualShaderNodeClamp::get_input_port_type(int p return PORT_TYPE_VECTOR_2D; case OP_TYPE_VECTOR_3D: return PORT_TYPE_VECTOR_3D; + case OP_TYPE_VECTOR_4D: + return PORT_TYPE_VECTOR_4D; default: break; } @@ -3418,6 +3415,8 @@ VisualShaderNodeClamp::PortType VisualShaderNodeClamp::get_output_port_type(int return PORT_TYPE_VECTOR_2D; case OP_TYPE_VECTOR_3D: return PORT_TYPE_VECTOR_3D; + case OP_TYPE_VECTOR_4D: + return PORT_TYPE_VECTOR_4D; default: break; } @@ -3458,6 +3457,11 @@ void VisualShaderNodeClamp::set_op_type(OpType p_op_type) { set_input_port_default_value(1, Vector3(), get_input_port_default_value(1)); set_input_port_default_value(2, Vector3(), get_input_port_default_value(2)); break; + case OP_TYPE_VECTOR_4D: + set_input_port_default_value(0, Quaternion(), get_input_port_default_value(0)); + set_input_port_default_value(1, Quaternion(), get_input_port_default_value(1)); + set_input_port_default_value(2, Quaternion(), get_input_port_default_value(2)); + break; default: break; } @@ -3479,12 +3483,13 @@ void VisualShaderNodeClamp::_bind_methods() { ClassDB::bind_method(D_METHOD("set_op_type", "op_type"), &VisualShaderNodeClamp::set_op_type); ClassDB::bind_method(D_METHOD("get_op_type"), &VisualShaderNodeClamp::get_op_type); - ADD_PROPERTY(PropertyInfo(Variant::INT, "op_type", PROPERTY_HINT_ENUM, "Float,Int,Vector2,Vector3"), "set_op_type", "get_op_type"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "op_type", PROPERTY_HINT_ENUM, "Float,Int,Vector2,Vector3,Vector4"), "set_op_type", "get_op_type"); BIND_ENUM_CONSTANT(OP_TYPE_FLOAT); BIND_ENUM_CONSTANT(OP_TYPE_INT); BIND_ENUM_CONSTANT(OP_TYPE_VECTOR_2D); BIND_ENUM_CONSTANT(OP_TYPE_VECTOR_3D); + BIND_ENUM_CONSTANT(OP_TYPE_VECTOR_4D); BIND_ENUM_CONSTANT(OP_TYPE_MAX); } @@ -3541,6 +3546,11 @@ void VisualShaderNodeFaceForward::set_op_type(OpType p_op_type) { set_input_port_default_value(1, Vector3(), get_input_port_default_value(1)); set_input_port_default_value(2, Vector3(), get_input_port_default_value(2)); } break; + case OP_TYPE_VECTOR_4D: { + set_input_port_default_value(0, Quaternion(), get_input_port_default_value(0)); + set_input_port_default_value(1, Quaternion(), get_input_port_default_value(1)); + set_input_port_default_value(2, Quaternion(), get_input_port_default_value(2)); + } break; default: break; } @@ -3630,6 +3640,13 @@ VisualShaderNodeStep::PortType VisualShaderNodeStep::get_input_port_type(int p_p return PORT_TYPE_VECTOR_3D; } break; + case OP_TYPE_VECTOR_4D: + return PORT_TYPE_VECTOR_4D; + case OP_TYPE_VECTOR_4D_SCALAR: + if (p_port == 1) { + return PORT_TYPE_VECTOR_4D; + } + break; default: break; } @@ -3660,6 +3677,10 @@ VisualShaderNodeStep::PortType VisualShaderNodeStep::get_output_port_type(int p_ return PORT_TYPE_VECTOR_3D; case OP_TYPE_VECTOR_3D_SCALAR: return PORT_TYPE_VECTOR_3D; + case OP_TYPE_VECTOR_4D: + return PORT_TYPE_VECTOR_4D; + case OP_TYPE_VECTOR_4D_SCALAR: + return PORT_TYPE_VECTOR_4D; default: break; } @@ -3696,6 +3717,14 @@ void VisualShaderNodeStep::set_op_type(OpType p_op_type) { set_input_port_default_value(0, 0.0, get_input_port_default_value(0)); set_input_port_default_value(1, Vector3(), get_input_port_default_value(1)); } break; + case OP_TYPE_VECTOR_4D: { + set_input_port_default_value(0, Quaternion(), get_input_port_default_value(0)); + set_input_port_default_value(1, Quaternion(), get_input_port_default_value(1)); + } break; + case OP_TYPE_VECTOR_4D_SCALAR: { + set_input_port_default_value(0, 0.0, get_input_port_default_value(0)); + set_input_port_default_value(1, Quaternion(), get_input_port_default_value(1)); + } break; default: break; } @@ -3721,13 +3750,15 @@ void VisualShaderNodeStep::_bind_methods() { ClassDB::bind_method(D_METHOD("set_op_type", "op_type"), &VisualShaderNodeStep::set_op_type); ClassDB::bind_method(D_METHOD("get_op_type"), &VisualShaderNodeStep::get_op_type); - ADD_PROPERTY(PropertyInfo(Variant::INT, "op_type", PROPERTY_HINT_ENUM, "Scalar,Vector2,Vector2Scalar,Vector3,Vector3Scalar"), "set_op_type", "get_op_type"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "op_type", PROPERTY_HINT_ENUM, "Scalar,Vector2,Vector2Scalar,Vector3,Vector3Scalar,Vector4,Vector4Scalar"), "set_op_type", "get_op_type"); BIND_ENUM_CONSTANT(OP_TYPE_SCALAR); BIND_ENUM_CONSTANT(OP_TYPE_VECTOR_2D); BIND_ENUM_CONSTANT(OP_TYPE_VECTOR_2D_SCALAR); BIND_ENUM_CONSTANT(OP_TYPE_VECTOR_3D); BIND_ENUM_CONSTANT(OP_TYPE_VECTOR_3D_SCALAR); + BIND_ENUM_CONSTANT(OP_TYPE_VECTOR_4D); + BIND_ENUM_CONSTANT(OP_TYPE_VECTOR_4D_SCALAR); BIND_ENUM_CONSTANT(OP_TYPE_MAX); } @@ -3762,6 +3793,13 @@ VisualShaderNodeSmoothStep::PortType VisualShaderNodeSmoothStep::get_input_port_ return PORT_TYPE_VECTOR_3D; // x } break; + case OP_TYPE_VECTOR_4D: + return PORT_TYPE_VECTOR_4D; + case OP_TYPE_VECTOR_4D_SCALAR: + if (p_port == 2) { + return PORT_TYPE_VECTOR_4D; // x + } + break; default: break; } @@ -3794,6 +3832,10 @@ VisualShaderNodeSmoothStep::PortType VisualShaderNodeSmoothStep::get_output_port return PORT_TYPE_VECTOR_3D; case OP_TYPE_VECTOR_3D_SCALAR: return PORT_TYPE_VECTOR_3D; + case OP_TYPE_VECTOR_4D: + return PORT_TYPE_VECTOR_4D; + case OP_TYPE_VECTOR_4D_SCALAR: + return PORT_TYPE_VECTOR_4D; default: break; } @@ -3835,6 +3877,16 @@ void VisualShaderNodeSmoothStep::set_op_type(OpType p_op_type) { set_input_port_default_value(1, 0.0, get_input_port_default_value(1)); // edge1 set_input_port_default_value(2, Vector3(), get_input_port_default_value(2)); // x break; + case OP_TYPE_VECTOR_4D: + set_input_port_default_value(0, Quaternion(), get_input_port_default_value(0)); // edge0 + set_input_port_default_value(1, Quaternion(), get_input_port_default_value(1)); // edge1 + set_input_port_default_value(2, Quaternion(), get_input_port_default_value(2)); // x + break; + case OP_TYPE_VECTOR_4D_SCALAR: + set_input_port_default_value(0, 0.0, get_input_port_default_value(0)); // edge0 + set_input_port_default_value(1, 0.0, get_input_port_default_value(1)); // edge1 + set_input_port_default_value(2, Quaternion(), get_input_port_default_value(2)); // x + break; default: break; } @@ -3860,13 +3912,15 @@ void VisualShaderNodeSmoothStep::_bind_methods() { ClassDB::bind_method(D_METHOD("set_op_type", "op_type"), &VisualShaderNodeSmoothStep::set_op_type); ClassDB::bind_method(D_METHOD("get_op_type"), &VisualShaderNodeSmoothStep::get_op_type); - ADD_PROPERTY(PropertyInfo(Variant::INT, "op_type", PROPERTY_HINT_ENUM, "Scalar,Vector2,Vector2Scalar,Vector3,Vector3Scalar"), "set_op_type", "get_op_type"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "op_type", PROPERTY_HINT_ENUM, "Scalar,Vector2,Vector2Scalar,Vector3,Vector3Scalar,Vector4,Vector4Scalar"), "set_op_type", "get_op_type"); BIND_ENUM_CONSTANT(OP_TYPE_SCALAR); BIND_ENUM_CONSTANT(OP_TYPE_VECTOR_2D); BIND_ENUM_CONSTANT(OP_TYPE_VECTOR_2D_SCALAR); BIND_ENUM_CONSTANT(OP_TYPE_VECTOR_3D); BIND_ENUM_CONSTANT(OP_TYPE_VECTOR_3D_SCALAR); + BIND_ENUM_CONSTANT(OP_TYPE_VECTOR_4D); + BIND_ENUM_CONSTANT(OP_TYPE_VECTOR_4D_SCALAR); BIND_ENUM_CONSTANT(OP_TYPE_MAX); } @@ -3922,6 +3976,10 @@ void VisualShaderNodeVectorDistance::set_op_type(OpType p_op_type) { set_input_port_default_value(0, Vector3(), get_input_port_default_value(0)); // a set_input_port_default_value(1, Vector3(), get_input_port_default_value(1)); // b } break; + case OP_TYPE_VECTOR_4D: { + set_input_port_default_value(0, Quaternion(), get_input_port_default_value(0)); // a + set_input_port_default_value(1, Quaternion(), get_input_port_default_value(1)); // b + } break; default: break; } @@ -4016,6 +4074,13 @@ VisualShaderNodeMix::PortType VisualShaderNodeMix::get_input_port_type(int p_por break; } return PORT_TYPE_VECTOR_3D; + case OP_TYPE_VECTOR_4D: + return PORT_TYPE_VECTOR_4D; + case OP_TYPE_VECTOR_4D_SCALAR: + if (p_port == 2) { + break; + } + return PORT_TYPE_VECTOR_4D; default: break; } @@ -4046,6 +4111,10 @@ VisualShaderNodeMix::PortType VisualShaderNodeMix::get_output_port_type(int p_po return PORT_TYPE_VECTOR_3D; case OP_TYPE_VECTOR_3D_SCALAR: return PORT_TYPE_VECTOR_3D; + case OP_TYPE_VECTOR_4D: + return PORT_TYPE_VECTOR_4D; + case OP_TYPE_VECTOR_4D_SCALAR: + return PORT_TYPE_VECTOR_4D; default: break; } @@ -4087,6 +4156,16 @@ void VisualShaderNodeMix::set_op_type(OpType p_op_type) { set_input_port_default_value(1, Vector3(), get_input_port_default_value(1)); // b set_input_port_default_value(2, 0.0, get_input_port_default_value(2)); // weight } break; + case OP_TYPE_VECTOR_4D: { + set_input_port_default_value(0, Quaternion(), get_input_port_default_value(0)); // a + set_input_port_default_value(1, Quaternion(), get_input_port_default_value(1)); // b + set_input_port_default_value(2, Quaternion(), get_input_port_default_value(2)); // weight + } break; + case OP_TYPE_VECTOR_4D_SCALAR: { + set_input_port_default_value(0, Quaternion(), get_input_port_default_value(0)); // a + set_input_port_default_value(1, Quaternion(), get_input_port_default_value(1)); // b + set_input_port_default_value(2, 0.0, get_input_port_default_value(2)); // weight + } break; default: break; } @@ -4112,13 +4191,15 @@ void VisualShaderNodeMix::_bind_methods() { ClassDB::bind_method(D_METHOD("set_op_type", "op_type"), &VisualShaderNodeMix::set_op_type); ClassDB::bind_method(D_METHOD("get_op_type"), &VisualShaderNodeMix::get_op_type); - ADD_PROPERTY(PropertyInfo(Variant::INT, "op_type", PROPERTY_HINT_ENUM, "Scalar,Vector2,Vector2Scalar,Vector3,Vector3Scalar"), "set_op_type", "get_op_type"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "op_type", PROPERTY_HINT_ENUM, "Scalar,Vector2,Vector2Scalar,Vector3,Vector3Scalar,Vector4,Vector4Scalar"), "set_op_type", "get_op_type"); BIND_ENUM_CONSTANT(OP_TYPE_SCALAR); BIND_ENUM_CONSTANT(OP_TYPE_VECTOR_2D); BIND_ENUM_CONSTANT(OP_TYPE_VECTOR_2D_SCALAR); BIND_ENUM_CONSTANT(OP_TYPE_VECTOR_3D); BIND_ENUM_CONSTANT(OP_TYPE_VECTOR_3D_SCALAR); + BIND_ENUM_CONSTANT(OP_TYPE_VECTOR_4D); + BIND_ENUM_CONSTANT(OP_TYPE_VECTOR_4D_SCALAR); BIND_ENUM_CONSTANT(OP_TYPE_MAX); } @@ -4140,6 +4221,8 @@ int VisualShaderNodeVectorCompose::get_input_port_count() const { return 2; case OP_TYPE_VECTOR_3D: return 3; + case OP_TYPE_VECTOR_4D: + return 4; default: break; } @@ -4170,6 +4253,18 @@ String VisualShaderNodeVectorCompose::get_input_port_name(int p_port) const { return "z"; } } break; + case OP_TYPE_VECTOR_4D: { + switch (p_port) { + case 0: + return "x"; + case 1: + return "y"; + case 2: + return "z"; + case 3: + return "w"; + } + } break; default: break; } @@ -4205,6 +4300,15 @@ void VisualShaderNodeVectorCompose::set_op_type(OpType p_op_type) { set_input_port_default_value(1, p2); set_input_port_default_value(2, 0.0); } break; + case OP_TYPE_VECTOR_4D: { + float p1 = get_input_port_default_value(0); + float p2 = get_input_port_default_value(1); + + set_input_port_default_value(0, p1); + set_input_port_default_value(1, p2); + set_input_port_default_value(2, 0.0); + set_input_port_default_value(3, 0.0); + } break; default: break; } @@ -4221,6 +4325,9 @@ String VisualShaderNodeVectorCompose::generate_code(Shader::Mode p_mode, VisualS case OP_TYPE_VECTOR_3D: { code += " " + p_output_vars[0] + " = vec3(" + p_input_vars[0] + ", " + p_input_vars[1] + ", " + p_input_vars[2] + ");\n"; } break; + case OP_TYPE_VECTOR_4D: { + code += " " + p_output_vars[0] + " = vec4(" + p_input_vars[0] + ", " + p_input_vars[1] + ", " + p_input_vars[2] + ", " + p_input_vars[3] + ");\n"; + } break; default: break; } @@ -4301,6 +4408,8 @@ int VisualShaderNodeVectorDecompose::get_output_port_count() const { return 2; case OP_TYPE_VECTOR_3D: return 3; + case OP_TYPE_VECTOR_4D: + return 4; default: break; } @@ -4331,6 +4440,18 @@ String VisualShaderNodeVectorDecompose::get_output_port_name(int p_port) const { return "z"; } } break; + case OP_TYPE_VECTOR_4D: { + switch (p_port) { + case 0: + return "x"; + case 1: + return "y"; + case 2: + return "z"; + case 3: + return "w"; + } + } break; default: break; } @@ -4349,6 +4470,9 @@ void VisualShaderNodeVectorDecompose::set_op_type(OpType p_op_type) { case OP_TYPE_VECTOR_3D: { set_input_port_default_value(0, Vector3(), get_input_port_default_value(0)); } break; + case OP_TYPE_VECTOR_4D: { + set_input_port_default_value(0, Quaternion(), get_input_port_default_value(0)); + } break; default: break; } @@ -4368,6 +4492,12 @@ String VisualShaderNodeVectorDecompose::generate_code(Shader::Mode p_mode, Visua code += " " + p_output_vars[1] + " = " + p_input_vars[0] + ".y;\n"; code += " " + p_output_vars[2] + " = " + p_input_vars[0] + ".z;\n"; } break; + case OP_TYPE_VECTOR_4D: { + code += " " + p_output_vars[0] + " = " + p_input_vars[0] + ".x;\n"; + code += " " + p_output_vars[1] + " = " + p_input_vars[0] + ".y;\n"; + code += " " + p_output_vars[2] + " = " + p_input_vars[0] + ".z;\n"; + code += " " + p_output_vars[3] + " = " + p_input_vars[0] + ".w;\n"; + } break; default: break; } @@ -4932,7 +5062,7 @@ int VisualShaderNodeColorUniform::get_input_port_count() const { } VisualShaderNodeColorUniform::PortType VisualShaderNodeColorUniform::get_input_port_type(int p_port) const { - return PORT_TYPE_VECTOR_3D; + return PORT_TYPE_SCALAR; } String VisualShaderNodeColorUniform::get_input_port_name(int p_port) const { @@ -4940,15 +5070,22 @@ String VisualShaderNodeColorUniform::get_input_port_name(int p_port) const { } int VisualShaderNodeColorUniform::get_output_port_count() const { - return 2; + return 1; } VisualShaderNodeColorUniform::PortType VisualShaderNodeColorUniform::get_output_port_type(int p_port) const { - return p_port == 0 ? PORT_TYPE_VECTOR_3D : PORT_TYPE_SCALAR; + return p_port == 0 ? PORT_TYPE_VECTOR_4D : PORT_TYPE_SCALAR; } String VisualShaderNodeColorUniform::get_output_port_name(int p_port) const { - return p_port == 0 ? "color" : "alpha"; //no output port means the editor will be used as port + return "color"; +} + +bool VisualShaderNodeColorUniform::is_output_port_expandable(int p_port) const { + if (p_port == 0) { + return true; + } + return false; } void VisualShaderNodeColorUniform::set_default_value_enabled(bool p_enabled) { @@ -4976,7 +5113,7 @@ Color VisualShaderNodeColorUniform::get_default_value() const { } String VisualShaderNodeColorUniform::generate_global(Shader::Mode p_mode, VisualShader::Type p_type, int p_id) const { - String code = _get_qual_str() + "uniform vec4 " + get_uniform_name() + " : hint_color"; + String code = _get_qual_str() + "uniform vec4 " + get_uniform_name() + " : source_color"; if (default_value_enabled) { code += vformat(" = vec4(%.6f, %.6f, %.6f, %.6f)", default_value.r, default_value.g, default_value.b, default_value.a); } @@ -4985,9 +5122,7 @@ String VisualShaderNodeColorUniform::generate_global(Shader::Mode p_mode, Visual } String VisualShaderNodeColorUniform::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 = " " + p_output_vars[0] + " = " + get_uniform_name() + ".rgb;\n"; - code += " " + p_output_vars[1] + " = " + get_uniform_name() + ".a;\n"; - return code; + return " " + p_output_vars[0] + " = " + get_uniform_name() + ";\n"; } bool VisualShaderNodeColorUniform::is_show_prop_names() const { @@ -5225,6 +5360,106 @@ Vector<StringName> VisualShaderNodeVec3Uniform::get_editable_properties() const VisualShaderNodeVec3Uniform::VisualShaderNodeVec3Uniform() { } +////////////// Vector4 Uniform + +String VisualShaderNodeVec4Uniform::get_caption() const { + return "Vector4Uniform"; +} + +int VisualShaderNodeVec4Uniform::get_input_port_count() const { + return 0; +} + +VisualShaderNodeVec4Uniform::PortType VisualShaderNodeVec4Uniform::get_input_port_type(int p_port) const { + return PORT_TYPE_VECTOR_4D; +} + +String VisualShaderNodeVec4Uniform::get_input_port_name(int p_port) const { + return String(); +} + +int VisualShaderNodeVec4Uniform::get_output_port_count() const { + return 1; +} + +VisualShaderNodeVec4Uniform::PortType VisualShaderNodeVec4Uniform::get_output_port_type(int p_port) const { + return PORT_TYPE_VECTOR_4D; +} + +String VisualShaderNodeVec4Uniform::get_output_port_name(int p_port) const { + return ""; // No output port means the editor will be used as port. +} + +void VisualShaderNodeVec4Uniform::set_default_value_enabled(bool p_enabled) { + default_value_enabled = p_enabled; + emit_changed(); +} + +bool VisualShaderNodeVec4Uniform::is_default_value_enabled() const { + return default_value_enabled; +} + +void VisualShaderNodeVec4Uniform::set_default_value(const Quaternion &p_value) { + default_value = p_value; + emit_changed(); +} + +Quaternion VisualShaderNodeVec4Uniform::get_default_value() const { + return default_value; +} + +String VisualShaderNodeVec4Uniform::generate_global(Shader::Mode p_mode, VisualShader::Type p_type, int p_id) const { + String code = _get_qual_str() + "uniform vec4 " + get_uniform_name(); + if (default_value_enabled) { + code += vformat(" = vec4(%.6f, %.6f, %.6f, %.6f)", default_value.x, default_value.y, default_value.z, default_value.w); + } + code += ";\n"; + return code; +} + +String VisualShaderNodeVec4Uniform::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 { + return " " + p_output_vars[0] + " = " + get_uniform_name() + ";\n"; +} + +void VisualShaderNodeVec4Uniform::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_default_value_enabled", "enabled"), &VisualShaderNodeVec4Uniform::set_default_value_enabled); + ClassDB::bind_method(D_METHOD("is_default_value_enabled"), &VisualShaderNodeVec4Uniform::is_default_value_enabled); + + ClassDB::bind_method(D_METHOD("set_default_value", "value"), &VisualShaderNodeVec4Uniform::set_default_value); + ClassDB::bind_method(D_METHOD("get_default_value"), &VisualShaderNodeVec4Uniform::get_default_value); + + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "default_value_enabled"), "set_default_value_enabled", "is_default_value_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::QUATERNION, "default_value"), "set_default_value", "get_default_value"); +} + +bool VisualShaderNodeVec4Uniform::is_show_prop_names() const { + return true; +} + +bool VisualShaderNodeVec4Uniform::is_use_prop_slots() const { + return true; +} + +bool VisualShaderNodeVec4Uniform::is_qualifier_supported(Qualifier p_qual) const { + return true; // All qualifiers are supported. +} + +bool VisualShaderNodeVec4Uniform::is_convertible_to_constant() const { + return true; // Conversion is allowed. +} + +Vector<StringName> VisualShaderNodeVec4Uniform::get_editable_properties() const { + Vector<StringName> props = VisualShaderNodeUniform::get_editable_properties(); + props.push_back("default_value_enabled"); + if (default_value_enabled) { + props.push_back("default_value"); + } + return props; +} + +VisualShaderNodeVec4Uniform::VisualShaderNodeVec4Uniform() { +} + ////////////// Transform Uniform String VisualShaderNodeTransformUniform::get_caption() const { @@ -5276,9 +5511,9 @@ Transform3D VisualShaderNodeTransformUniform::get_default_value() const { String VisualShaderNodeTransformUniform::generate_global(Shader::Mode p_mode, VisualShader::Type p_type, int p_id) const { String code = _get_qual_str() + "uniform mat4 " + get_uniform_name(); if (default_value_enabled) { - Vector3 row0 = default_value.basis.get_row(0); - Vector3 row1 = default_value.basis.get_row(1); - Vector3 row2 = default_value.basis.get_row(2); + Vector3 row0 = default_value.basis.rows[0]; + Vector3 row1 = default_value.basis.rows[1]; + Vector3 row2 = default_value.basis.rows[2]; Vector3 origin = default_value.origin; code += " = mat4(" + vformat("vec4(%.6f, %.6f, %.6f, 0.0)", row0.x, row0.y, row0.z) + vformat(", vec4(%.6f, %.6f, %.6f, 0.0)", row1.x, row1.y, row1.z) + vformat(", vec4(%.6f, %.6f, %.6f, 0.0)", row2.x, row2.y, row2.z) + vformat(", vec4(%.6f, %.6f, %.6f, 1.0)", origin.x, origin.y, origin.z) + ")"; } @@ -5332,79 +5567,32 @@ Vector<StringName> VisualShaderNodeTransformUniform::get_editable_properties() c VisualShaderNodeTransformUniform::VisualShaderNodeTransformUniform() { } -////////////// Texture Uniform - -String VisualShaderNodeTextureUniform::get_caption() const { - return "TextureUniform"; -} - -int VisualShaderNodeTextureUniform::get_input_port_count() const { - return 2; -} - -VisualShaderNodeTextureUniform::PortType VisualShaderNodeTextureUniform::get_input_port_type(int p_port) const { - return p_port == 0 ? PORT_TYPE_VECTOR_2D : PORT_TYPE_SCALAR; -} - -String VisualShaderNodeTextureUniform::get_input_port_name(int p_port) const { - return p_port == 0 ? "uv" : "lod"; -} - -int VisualShaderNodeTextureUniform::get_output_port_count() const { - return 3; -} - -VisualShaderNodeTextureUniform::PortType VisualShaderNodeTextureUniform::get_output_port_type(int p_port) const { - switch (p_port) { - case 0: - return PORT_TYPE_VECTOR_3D; - case 1: - return PORT_TYPE_SCALAR; - case 2: - return PORT_TYPE_SAMPLER; - default: - return PORT_TYPE_SCALAR; - } -} - -String VisualShaderNodeTextureUniform::get_output_port_name(int p_port) const { - switch (p_port) { - case 0: - return "rgb"; - case 1: - return "alpha"; - case 2: - return "sampler2D"; - default: - return ""; - } -} +////////////// -String VisualShaderNodeTextureUniform::generate_global(Shader::Mode p_mode, VisualShader::Type p_type, int p_id) const { +String get_sampler_hint(VisualShaderNodeTextureUniform::TextureType p_texture_type, VisualShaderNodeTextureUniform::ColorDefault p_color_default, VisualShaderNodeTextureUniform::TextureFilter p_texture_filter, VisualShaderNodeTextureUniform::TextureRepeat p_texture_repeat) { + String code; bool has_colon = false; - String code = _get_qual_str() + "uniform sampler2D " + get_uniform_name(); // type { String type_code; - switch (texture_type) { - case TYPE_DATA: - if (color_default == COLOR_DEFAULT_BLACK) { - type_code = "hint_black"; + switch (p_texture_type) { + case VisualShaderNodeTextureUniform::TYPE_DATA: + if (p_color_default == VisualShaderNodeTextureUniform::COLOR_DEFAULT_BLACK) { + type_code = "hint_default_black"; } break; - case TYPE_COLOR: - if (color_default == COLOR_DEFAULT_BLACK) { - type_code = "hint_black_albedo"; - } else { - type_code = "hint_albedo"; + case VisualShaderNodeTextureUniform::TYPE_COLOR: + type_code = "source_color"; + if (p_color_default == VisualShaderNodeTextureUniform::COLOR_DEFAULT_BLACK) { + type_code += ", hint_default_black"; } break; - case TYPE_NORMAL_MAP: + case VisualShaderNodeTextureUniform::TYPE_NORMAL_MAP: type_code = "hint_normal"; break; - case TYPE_ANISOTROPY: + case VisualShaderNodeTextureUniform::TYPE_ANISOTROPY: type_code = "hint_anisotropy"; break; default: @@ -5421,23 +5609,23 @@ String VisualShaderNodeTextureUniform::generate_global(Shader::Mode p_mode, Visu { String filter_code; - switch (texture_filter) { - case FILTER_NEAREST: + switch (p_texture_filter) { + case VisualShaderNodeTextureUniform::FILTER_NEAREST: filter_code = "filter_nearest"; break; - case FILTER_LINEAR: + case VisualShaderNodeTextureUniform::FILTER_LINEAR: filter_code = "filter_linear"; break; - case FILTER_NEAREST_MIPMAP: + case VisualShaderNodeTextureUniform::FILTER_NEAREST_MIPMAP: filter_code = "filter_nearest_mipmap"; break; - case FILTER_LINEAR_MIPMAP: + case VisualShaderNodeTextureUniform::FILTER_LINEAR_MIPMAP: filter_code = "filter_linear_mipmap"; break; - case FILTER_NEAREST_MIPMAP_ANISOTROPIC: + case VisualShaderNodeTextureUniform::FILTER_NEAREST_MIPMAP_ANISOTROPIC: filter_code = "filter_nearest_mipmap_anisotropic"; break; - case FILTER_LINEAR_MIPMAP_ANISOTROPIC: + case VisualShaderNodeTextureUniform::FILTER_LINEAR_MIPMAP_ANISOTROPIC: filter_code = "filter_linear_mipmap_anisotropic"; break; default: @@ -5459,11 +5647,11 @@ String VisualShaderNodeTextureUniform::generate_global(Shader::Mode p_mode, Visu { String repeat_code; - switch (texture_repeat) { - case REPEAT_ENABLED: + switch (p_texture_repeat) { + case VisualShaderNodeTextureUniform::REPEAT_ENABLED: repeat_code = "repeat_enable"; break; - case REPEAT_DISABLED: + case VisualShaderNodeTextureUniform::REPEAT_DISABLED: repeat_code = "repeat_disable"; break; default: @@ -5480,43 +5668,60 @@ String VisualShaderNodeTextureUniform::generate_global(Shader::Mode p_mode, Visu } } - code += ";\n"; return code; } -bool VisualShaderNodeTextureUniform::is_code_generated() const { - return is_output_port_connected(0) || is_output_port_connected(1); // rgb or alpha +////////////// Texture Uniform + +String VisualShaderNodeTextureUniform::get_caption() const { + return "TextureUniform"; +} + +int VisualShaderNodeTextureUniform::get_input_port_count() const { + return 0; } -String VisualShaderNodeTextureUniform::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 default_uv; - if (p_mode == Shader::MODE_CANVAS_ITEM || p_mode == Shader::MODE_SPATIAL) { - default_uv = "UV"; - } else { - default_uv = "vec2(0.0)"; +VisualShaderNodeTextureUniform::PortType VisualShaderNodeTextureUniform::get_input_port_type(int p_port) const { + return PORT_TYPE_SCALAR; +} + +String VisualShaderNodeTextureUniform::get_input_port_name(int p_port) const { + return ""; +} + +int VisualShaderNodeTextureUniform::get_output_port_count() const { + return 1; +} + +VisualShaderNodeTextureUniform::PortType VisualShaderNodeTextureUniform::get_output_port_type(int p_port) const { + switch (p_port) { + case 0: + return PORT_TYPE_SAMPLER; + default: + return PORT_TYPE_SCALAR; } +} - String id = get_uniform_name(); - String code = " {\n"; - if (p_input_vars[0].is_empty()) { // Use UV by default. - if (p_input_vars[1].is_empty()) { - code += " vec4 n_tex_read = texture(" + id + ", " + default_uv + ");\n"; - } else { - code += " vec4 n_tex_read = textureLod(" + id + ", " + default_uv + ", " + p_input_vars[1] + ");\n"; - } - } else if (p_input_vars[1].is_empty()) { - //no lod - code += " vec4 n_tex_read = texture(" + id + ", " + p_input_vars[0] + ");\n"; - } else { - code += " vec4 n_tex_read = textureLod(" + id + ", " + p_input_vars[0] + ", " + p_input_vars[1] + ");\n"; +String VisualShaderNodeTextureUniform::get_output_port_name(int p_port) const { + switch (p_port) { + case 0: + return "sampler2D"; + default: + return ""; } +} - code += " " + p_output_vars[0] + " = n_tex_read.rgb;\n"; - code += " " + p_output_vars[1] + " = n_tex_read.a;\n"; - code += " }\n"; +String VisualShaderNodeTextureUniform::generate_global(Shader::Mode p_mode, VisualShader::Type p_type, int p_id) const { + String code = _get_qual_str() + "uniform sampler2D " + get_uniform_name(); + code += get_sampler_hint(texture_type, color_default, texture_filter, texture_repeat); + code += ";\n"; return code; } +String VisualShaderNodeTextureUniform::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 { + return ""; +} + void VisualShaderNodeTextureUniform::set_texture_type(TextureType p_texture_type) { ERR_FAIL_INDEX(int(p_texture_type), int(TYPE_MAX)); if (texture_type == p_texture_type) { @@ -5584,8 +5789,8 @@ bool VisualShaderNodeTextureUniform::is_show_prop_names() const { return true; } -Map<StringName, String> VisualShaderNodeTextureUniform::get_editable_properties_names() const { - Map<StringName, String> names; +HashMap<StringName, String> VisualShaderNodeTextureUniform::get_editable_properties_names() const { + HashMap<StringName, String> names; names.insert("texture_type", RTR("Type")); names.insert("color_default", RTR("Default Color")); names.insert("texture_filter", RTR("Filter")); @@ -5636,15 +5841,6 @@ void VisualShaderNodeTextureUniform::_bind_methods() { BIND_ENUM_CONSTANT(REPEAT_MAX); } -bool VisualShaderNodeTextureUniform::is_input_port_default(int p_port, Shader::Mode p_mode) const { - if (p_mode == Shader::MODE_CANVAS_ITEM || p_mode == Shader::MODE_SPATIAL) { - if (p_port == 0) { - return true; - } - } - return false; -} - bool VisualShaderNodeTextureUniform::is_qualifier_supported(Qualifier p_qual) const { switch (p_qual) { case Qualifier::QUAL_NONE: @@ -5664,7 +5860,6 @@ bool VisualShaderNodeTextureUniform::is_convertible_to_constant() const { } VisualShaderNodeTextureUniform::VisualShaderNodeTextureUniform() { - simple_decl = false; } ////////////// Texture Uniform (Triplanar) @@ -5677,7 +5872,7 @@ int VisualShaderNodeTextureUniformTriplanar::get_input_port_count() const { return 2; } -VisualShaderNodeTextureUniform::PortType VisualShaderNodeTextureUniformTriplanar::get_input_port_type(int p_port) const { +VisualShaderNodeTextureUniformTriplanar::PortType VisualShaderNodeTextureUniformTriplanar::get_input_port_type(int p_port) const { if (p_port == 0 || p_port == 1) { return PORT_TYPE_VECTOR_3D; } @@ -5693,6 +5888,32 @@ String VisualShaderNodeTextureUniformTriplanar::get_input_port_name(int p_port) return ""; } +int VisualShaderNodeTextureUniformTriplanar::get_output_port_count() const { + return 2; +} + +VisualShaderNodeTextureUniformTriplanar::PortType VisualShaderNodeTextureUniformTriplanar::get_output_port_type(int p_port) const { + switch (p_port) { + case 0: + return PORT_TYPE_VECTOR_4D; + case 1: + return PORT_TYPE_SAMPLER; + default: + return PORT_TYPE_SCALAR; + } +} + +String VisualShaderNodeTextureUniformTriplanar::get_output_port_name(int p_port) const { + switch (p_port) { + case 0: + return "color"; + case 1: + return "sampler2D"; + default: + return ""; + } +} + String VisualShaderNodeTextureUniformTriplanar::generate_global_per_node(Shader::Mode p_mode, int p_id) const { String code; @@ -5733,22 +5954,18 @@ String VisualShaderNodeTextureUniformTriplanar::generate_global_per_func(Shader: String VisualShaderNodeTextureUniformTriplanar::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 id = get_uniform_name(); - String code = " {\n"; + String code; if (p_input_vars[0].is_empty() && p_input_vars[1].is_empty()) { - code += " vec4 n_tex_read = triplanar_texture(" + id + ", triplanar_power_normal, triplanar_pos);\n"; + code += " " + p_output_vars[0] + " = triplanar_texture(" + id + ", triplanar_power_normal, triplanar_pos);\n"; } else if (!p_input_vars[0].is_empty() && p_input_vars[1].is_empty()) { - code += " vec4 n_tex_read = triplanar_texture(" + id + ", " + p_input_vars[0] + ", triplanar_pos);\n"; + code += " " + p_output_vars[0] + " = triplanar_texture(" + id + ", " + p_input_vars[0] + ", triplanar_pos);\n"; } else if (p_input_vars[0].is_empty() && !p_input_vars[1].is_empty()) { - code += " vec4 n_tex_read = triplanar_texture(" + id + ", triplanar_power_normal, " + p_input_vars[1] + ");\n"; + code += " " + p_output_vars[0] + " = triplanar_texture(" + id + ", triplanar_power_normal, " + p_input_vars[1] + ");\n"; } else { - code += " vec4 n_tex_read = triplanar_texture(" + id + ", " + p_input_vars[0] + ", " + p_input_vars[1] + ");\n"; + code += " " + p_output_vars[0] + " = triplanar_texture(" + id + ", " + p_input_vars[0] + ", " + p_input_vars[1] + ");\n"; } - code += " " + p_output_vars[0] + " = n_tex_read.rgb;\n"; - code += " " + p_output_vars[1] + " = n_tex_read.a;\n"; - code += " }\n"; - return code; } @@ -5770,63 +5987,14 @@ String VisualShaderNodeTexture2DArrayUniform::get_caption() const { return "Texture2DArrayUniform"; } -int VisualShaderNodeTexture2DArrayUniform::get_output_port_count() const { - return 1; -} - -VisualShaderNodeTexture2DArrayUniform::PortType VisualShaderNodeTexture2DArrayUniform::get_output_port_type(int p_port) const { - return PORT_TYPE_SAMPLER; -} - String VisualShaderNodeTexture2DArrayUniform::get_output_port_name(int p_port) const { return "sampler2DArray"; } -int VisualShaderNodeTexture2DArrayUniform::get_input_port_count() const { - return 0; -} - -VisualShaderNodeTexture2DArrayUniform::PortType VisualShaderNodeTexture2DArrayUniform::get_input_port_type(int p_port) const { - return PORT_TYPE_SCALAR; -} - -String VisualShaderNodeTexture2DArrayUniform::get_input_port_name(int p_port) const { - return ""; -} - -bool VisualShaderNodeTexture2DArrayUniform::is_input_port_default(int p_port, Shader::Mode p_mode) const { - return false; -} - String VisualShaderNodeTexture2DArrayUniform::generate_global(Shader::Mode p_mode, VisualShader::Type p_type, int p_id) const { String code = _get_qual_str() + "uniform sampler2DArray " + get_uniform_name(); - - switch (texture_type) { - case TYPE_DATA: - if (color_default == COLOR_DEFAULT_BLACK) { - code += " : hint_black;\n"; - } else { - code += ";\n"; - } - break; - case TYPE_COLOR: - if (color_default == COLOR_DEFAULT_BLACK) { - code += " : hint_black_albedo;\n"; - } else { - code += " : hint_albedo;\n"; - } - break; - case TYPE_NORMAL_MAP: - code += " : hint_normal;\n"; - break; - case TYPE_ANISOTROPY: - code += " : hint_anisotropy;\n"; - break; - default: - code += ";\n"; - break; - } - + code += get_sampler_hint(texture_type, color_default, texture_filter, texture_repeat); + code += ";\n"; return code; } @@ -5843,63 +6011,14 @@ String VisualShaderNodeTexture3DUniform::get_caption() const { return "Texture3DUniform"; } -int VisualShaderNodeTexture3DUniform::get_output_port_count() const { - return 1; -} - -VisualShaderNodeTexture3DUniform::PortType VisualShaderNodeTexture3DUniform::get_output_port_type(int p_port) const { - return PORT_TYPE_SAMPLER; -} - String VisualShaderNodeTexture3DUniform::get_output_port_name(int p_port) const { return "sampler3D"; } -int VisualShaderNodeTexture3DUniform::get_input_port_count() const { - return 0; -} - -VisualShaderNodeTexture3DUniform::PortType VisualShaderNodeTexture3DUniform::get_input_port_type(int p_port) const { - return PORT_TYPE_SCALAR; -} - -String VisualShaderNodeTexture3DUniform::get_input_port_name(int p_port) const { - return ""; -} - -bool VisualShaderNodeTexture3DUniform::is_input_port_default(int p_port, Shader::Mode p_mode) const { - return false; -} - String VisualShaderNodeTexture3DUniform::generate_global(Shader::Mode p_mode, VisualShader::Type p_type, int p_id) const { String code = _get_qual_str() + "uniform sampler3D " + get_uniform_name(); - - switch (texture_type) { - case TYPE_DATA: - if (color_default == COLOR_DEFAULT_BLACK) { - code += " : hint_black;\n"; - } else { - code += ";\n"; - } - break; - case TYPE_COLOR: - if (color_default == COLOR_DEFAULT_BLACK) { - code += " : hint_black_albedo;\n"; - } else { - code += " : hint_albedo;\n"; - } - break; - case TYPE_NORMAL_MAP: - code += " : hint_normal;\n"; - break; - case TYPE_ANISOTROPY: - code += " : hint_anisotropy;\n"; - break; - default: - code += ";\n"; - break; - } - + code += get_sampler_hint(texture_type, color_default, texture_filter, texture_repeat); + code += ";\n"; return code; } @@ -5916,63 +6035,14 @@ String VisualShaderNodeCubemapUniform::get_caption() const { return "CubemapUniform"; } -int VisualShaderNodeCubemapUniform::get_output_port_count() const { - return 1; -} - -VisualShaderNodeCubemapUniform::PortType VisualShaderNodeCubemapUniform::get_output_port_type(int p_port) const { - return PORT_TYPE_SAMPLER; -} - String VisualShaderNodeCubemapUniform::get_output_port_name(int p_port) const { return "samplerCube"; } -int VisualShaderNodeCubemapUniform::get_input_port_count() const { - return 0; -} - -VisualShaderNodeCubemapUniform::PortType VisualShaderNodeCubemapUniform::get_input_port_type(int p_port) const { - return PORT_TYPE_SCALAR; -} - -String VisualShaderNodeCubemapUniform::get_input_port_name(int p_port) const { - return ""; -} - -bool VisualShaderNodeCubemapUniform::is_input_port_default(int p_port, Shader::Mode p_mode) const { - return false; -} - String VisualShaderNodeCubemapUniform::generate_global(Shader::Mode p_mode, VisualShader::Type p_type, int p_id) const { String code = _get_qual_str() + "uniform samplerCube " + get_uniform_name(); - - switch (texture_type) { - case TYPE_DATA: - if (color_default == COLOR_DEFAULT_BLACK) { - code += " : hint_black;\n"; - } else { - code += ";\n"; - } - break; - case TYPE_COLOR: - if (color_default == COLOR_DEFAULT_BLACK) { - code += " : hint_black_albedo;\n"; - } else { - code += " : hint_albedo;\n"; - } - break; - case TYPE_NORMAL_MAP: - code += " : hint_normal;\n"; - break; - case TYPE_ANISOTROPY: - code += " : hint_anisotropy;\n"; - break; - default: - code += ";\n"; - break; - } - + code += get_sampler_hint(texture_type, color_default, texture_filter, texture_repeat); + code += ";\n"; return code; } @@ -6080,6 +6150,8 @@ VisualShaderNodeSwitch::PortType VisualShaderNodeSwitch::get_input_port_type(int return PORT_TYPE_VECTOR_2D; case OP_TYPE_VECTOR_3D: return PORT_TYPE_VECTOR_3D; + case OP_TYPE_VECTOR_4D: + return PORT_TYPE_VECTOR_4D; case OP_TYPE_BOOLEAN: return PORT_TYPE_BOOLEAN; case OP_TYPE_TRANSFORM: @@ -6116,6 +6188,8 @@ VisualShaderNodeSwitch::PortType VisualShaderNodeSwitch::get_output_port_type(in return PORT_TYPE_VECTOR_2D; case OP_TYPE_VECTOR_3D: return PORT_TYPE_VECTOR_3D; + case OP_TYPE_VECTOR_4D: + return PORT_TYPE_VECTOR_4D; case OP_TYPE_BOOLEAN: return PORT_TYPE_BOOLEAN; case OP_TYPE_TRANSFORM: @@ -6152,6 +6226,10 @@ void VisualShaderNodeSwitch::set_op_type(OpType p_op_type) { set_input_port_default_value(1, Vector3(1.0, 1.0, 1.0), get_input_port_default_value(1)); set_input_port_default_value(2, Vector3(0.0, 0.0, 0.0), get_input_port_default_value(2)); break; + case OP_TYPE_VECTOR_4D: + set_input_port_default_value(1, Quaternion(1.0, 1.0, 1.0, 1.0), get_input_port_default_value(1)); + set_input_port_default_value(2, Quaternion(0.0, 0.0, 0.0, 0.0), get_input_port_default_value(2)); + break; case OP_TYPE_BOOLEAN: set_input_port_default_value(1, true); set_input_port_default_value(2, false); @@ -6181,12 +6259,13 @@ void VisualShaderNodeSwitch::_bind_methods() { // static ClassDB::bind_method(D_METHOD("set_op_type", "type"), &VisualShaderNodeSwitch::set_op_type); ClassDB::bind_method(D_METHOD("get_op_type"), &VisualShaderNodeSwitch::get_op_type); - ADD_PROPERTY(PropertyInfo(Variant::INT, "op_type", PROPERTY_HINT_ENUM, "Float,Int,Vector2,Vector3,Boolean,Transform"), "set_op_type", "get_op_type"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "op_type", PROPERTY_HINT_ENUM, "Float,Int,Vector2,Vector3,Vector4,Boolean,Transform"), "set_op_type", "get_op_type"); BIND_ENUM_CONSTANT(OP_TYPE_FLOAT); BIND_ENUM_CONSTANT(OP_TYPE_INT); BIND_ENUM_CONSTANT(OP_TYPE_VECTOR_2D); BIND_ENUM_CONSTANT(OP_TYPE_VECTOR_3D); + BIND_ENUM_CONSTANT(OP_TYPE_VECTOR_4D); BIND_ENUM_CONSTANT(OP_TYPE_BOOLEAN); BIND_ENUM_CONSTANT(OP_TYPE_TRANSFORM); BIND_ENUM_CONSTANT(OP_TYPE_MAX); @@ -6420,6 +6499,8 @@ VisualShaderNodeCompare::PortType VisualShaderNodeCompare::get_input_port_type(i return PORT_TYPE_VECTOR_2D; case CTYPE_VECTOR_3D: return PORT_TYPE_VECTOR_3D; + case CTYPE_VECTOR_4D: + return PORT_TYPE_VECTOR_4D; case CTYPE_BOOLEAN: return PORT_TYPE_BOOLEAN; case CTYPE_TRANSFORM: @@ -6514,6 +6595,12 @@ String VisualShaderNodeCompare::generate_code(Shader::Mode p_mode, VisualShader: code += " " + p_output_vars[0] + " = " + String(conditions[condition]).replace("$", "_bv") + ";\n"; code += " }\n"; } break; + case CTYPE_VECTOR_4D: { + code += " {\n"; + code += " bvec4 _bv = " + String(functions[func]).replace("$", p_input_vars[0] + ", " + p_input_vars[1]) + ";\n"; + code += " " + p_output_vars[0] + " = " + String(conditions[condition]).replace("$", "_bv") + ";\n"; + code += " }\n"; + } break; case CTYPE_BOOLEAN: { if (func > FUNC_NOT_EQUAL) { return " " + p_output_vars[0] + " = false;\n"; @@ -6558,6 +6645,11 @@ void VisualShaderNodeCompare::set_comparison_type(ComparisonType p_comparison_ty set_input_port_default_value(1, Vector3(), get_input_port_default_value(1)); simple_decl = false; break; + case CTYPE_VECTOR_4D: + set_input_port_default_value(0, Quaternion(), get_input_port_default_value(0)); + set_input_port_default_value(1, Quaternion(), get_input_port_default_value(1)); + simple_decl = false; + break; case CTYPE_BOOLEAN: set_input_port_default_value(0, false); set_input_port_default_value(1, false); @@ -6609,7 +6701,7 @@ Vector<StringName> VisualShaderNodeCompare::get_editable_properties() const { Vector<StringName> props; props.push_back("type"); props.push_back("function"); - if (comparison_type == CTYPE_VECTOR_2D || comparison_type == CTYPE_VECTOR_3D) { + if (comparison_type == CTYPE_VECTOR_2D || comparison_type == CTYPE_VECTOR_3D || comparison_type == CTYPE_VECTOR_4D) { props.push_back("condition"); } return props; @@ -6625,7 +6717,7 @@ void VisualShaderNodeCompare::_bind_methods() { ClassDB::bind_method(D_METHOD("set_condition", "condition"), &VisualShaderNodeCompare::set_condition); ClassDB::bind_method(D_METHOD("get_condition"), &VisualShaderNodeCompare::get_condition); - ADD_PROPERTY(PropertyInfo(Variant::INT, "type", PROPERTY_HINT_ENUM, "Float,Int,Vector2,Vector3,Boolean,Transform"), "set_comparison_type", "get_comparison_type"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "type", PROPERTY_HINT_ENUM, "Float,Int,Vector2,Vector3,Vector4,Boolean,Transform"), "set_comparison_type", "get_comparison_type"); ADD_PROPERTY(PropertyInfo(Variant::INT, "function", PROPERTY_HINT_ENUM, "a == b,a != b,a > b,a >= b,a < b,a <= b"), "set_function", "get_function"); ADD_PROPERTY(PropertyInfo(Variant::INT, "condition", PROPERTY_HINT_ENUM, "All,Any"), "set_condition", "get_condition"); @@ -6633,6 +6725,7 @@ void VisualShaderNodeCompare::_bind_methods() { BIND_ENUM_CONSTANT(CTYPE_SCALAR_INT); BIND_ENUM_CONSTANT(CTYPE_VECTOR_2D); BIND_ENUM_CONSTANT(CTYPE_VECTOR_3D); + BIND_ENUM_CONSTANT(CTYPE_VECTOR_4D); BIND_ENUM_CONSTANT(CTYPE_BOOLEAN); BIND_ENUM_CONSTANT(CTYPE_TRANSFORM); BIND_ENUM_CONSTANT(CTYPE_MAX); @@ -6672,6 +6765,8 @@ VisualShaderNodeMultiplyAdd::PortType VisualShaderNodeMultiplyAdd::get_input_por return PORT_TYPE_VECTOR_2D; case OP_TYPE_VECTOR_3D: return PORT_TYPE_VECTOR_3D; + case OP_TYPE_VECTOR_4D: + return PORT_TYPE_VECTOR_4D; default: break; } @@ -6699,6 +6794,8 @@ VisualShaderNodeMultiplyAdd::PortType VisualShaderNodeMultiplyAdd::get_output_po return PORT_TYPE_VECTOR_2D; case OP_TYPE_VECTOR_3D: return PORT_TYPE_VECTOR_3D; + case OP_TYPE_VECTOR_4D: + return PORT_TYPE_VECTOR_4D; default: break; } @@ -6734,6 +6831,11 @@ void VisualShaderNodeMultiplyAdd::set_op_type(OpType p_op_type) { set_input_port_default_value(1, Vector3(), get_input_port_default_value(1)); set_input_port_default_value(2, Vector3(), get_input_port_default_value(2)); } break; + case OP_TYPE_VECTOR_4D: { + set_input_port_default_value(0, Quaternion(), get_input_port_default_value(0)); + set_input_port_default_value(1, Quaternion(), get_input_port_default_value(1)); + set_input_port_default_value(2, Quaternion(), get_input_port_default_value(2)); + } break; default: break; } @@ -6755,11 +6857,12 @@ void VisualShaderNodeMultiplyAdd::_bind_methods() { ClassDB::bind_method(D_METHOD("set_op_type", "type"), &VisualShaderNodeMultiplyAdd::set_op_type); ClassDB::bind_method(D_METHOD("get_op_type"), &VisualShaderNodeMultiplyAdd::get_op_type); - ADD_PROPERTY(PropertyInfo(Variant::INT, "op_type", PROPERTY_HINT_ENUM, "Scalar,Vector2,Vector3"), "set_op_type", "get_op_type"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "op_type", PROPERTY_HINT_ENUM, "Scalar,Vector2,Vector3,Vector4"), "set_op_type", "get_op_type"); BIND_ENUM_CONSTANT(OP_TYPE_SCALAR); BIND_ENUM_CONSTANT(OP_TYPE_VECTOR_2D); BIND_ENUM_CONSTANT(OP_TYPE_VECTOR_3D); + BIND_ENUM_CONSTANT(OP_TYPE_VECTOR_4D); BIND_ENUM_CONSTANT(OP_TYPE_MAX); } diff --git a/scene/resources/visual_shader_nodes.h b/scene/resources/visual_shader_nodes.h index eeeb91a3ee..1eb7b7240f 100644 --- a/scene/resources/visual_shader_nodes.h +++ b/scene/resources/visual_shader_nodes.h @@ -44,6 +44,7 @@ public: enum OpType { OP_TYPE_VECTOR_2D, OP_TYPE_VECTOR_3D, + OP_TYPE_VECTOR_4D, OP_TYPE_MAX, }; @@ -280,6 +281,36 @@ public: /////////////////////////////////////// +class VisualShaderNodeVec4Constant : public VisualShaderNodeConstant { + GDCLASS(VisualShaderNodeVec4Constant, VisualShaderNodeConstant); + Quaternion constant; + +protected: + static void _bind_methods(); + +public: + virtual String get_caption() 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 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 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_constant(const Quaternion &p_constant); + Quaternion get_constant() const; + + virtual Vector<StringName> get_editable_properties() const override; + + VisualShaderNodeVec4Constant(); +}; + +/////////////////////////////////////// + class VisualShaderNodeTransformConstant : public VisualShaderNodeConstant { GDCLASS(VisualShaderNodeTransformConstant, VisualShaderNodeConstant); Transform3D constant; @@ -903,7 +934,7 @@ public: FUNC_FLOOR, FUNC_ROUND, FUNC_CEIL, - FUNC_FRAC, + FUNC_FRACT, FUNC_SATURATE, FUNC_NEGATE, FUNC_ACOSH, @@ -1008,8 +1039,6 @@ public: FUNC_SATURATE, FUNC_NEGATE, FUNC_RECIPROCAL, - FUNC_RGB2HSV, - FUNC_HSV2RGB, FUNC_ABS, FUNC_ACOS, FUNC_ACOSH, @@ -1024,7 +1053,7 @@ public: FUNC_EXP, FUNC_EXP2, FUNC_FLOOR, - FUNC_FRAC, + FUNC_FRACT, FUNC_INVERSE_SQRT, FUNC_LOG, FUNC_LOG2, @@ -1064,7 +1093,6 @@ public: Function get_function() const; virtual Vector<StringName> get_editable_properties() const override; - String get_warning(Shader::Mode p_mode, VisualShader::Type p_type) const override; VisualShaderNodeVectorFunc(); }; @@ -1081,6 +1109,8 @@ class VisualShaderNodeColorFunc : public VisualShaderNode { public: enum Function { FUNC_GRAYSCALE, + FUNC_HSV2RGB, + FUNC_RGB2HSV, FUNC_SEPIA, FUNC_MAX, }; @@ -1282,6 +1312,7 @@ public: OP_TYPE_INT, OP_TYPE_VECTOR_2D, OP_TYPE_VECTOR_3D, + OP_TYPE_VECTOR_4D, OP_TYPE_MAX, }; @@ -1324,6 +1355,7 @@ public: OP_TYPE_SCALAR, OP_TYPE_VECTOR_2D, OP_TYPE_VECTOR_3D, + OP_TYPE_VECTOR_4D, OP_TYPE_MAX, }; @@ -1427,6 +1459,8 @@ public: OP_TYPE_VECTOR_2D_SCALAR, OP_TYPE_VECTOR_3D, OP_TYPE_VECTOR_3D_SCALAR, + OP_TYPE_VECTOR_4D, + OP_TYPE_VECTOR_4D_SCALAR, OP_TYPE_MAX, }; @@ -1471,6 +1505,8 @@ public: OP_TYPE_VECTOR_2D_SCALAR, OP_TYPE_VECTOR_3D, OP_TYPE_VECTOR_3D_SCALAR, + OP_TYPE_VECTOR_4D, + OP_TYPE_VECTOR_4D_SCALAR, OP_TYPE_MAX, }; @@ -1561,6 +1597,8 @@ public: OP_TYPE_VECTOR_2D_SCALAR, OP_TYPE_VECTOR_3D, OP_TYPE_VECTOR_3D_SCALAR, + OP_TYPE_VECTOR_4D, + OP_TYPE_VECTOR_4D_SCALAR, OP_TYPE_MAX, }; @@ -1883,6 +1921,8 @@ public: virtual PortType get_output_port_type(int p_port) const override; virtual String get_output_port_name(int p_port) const override; + bool is_output_port_expandable(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; @@ -1990,6 +2030,49 @@ public: /////////////////////////////////////// +class VisualShaderNodeVec4Uniform : public VisualShaderNodeUniform { + GDCLASS(VisualShaderNodeVec4Uniform, VisualShaderNodeUniform); + +private: + bool default_value_enabled = false; + Quaternion default_value; + +protected: + static void _bind_methods(); + +public: + virtual String get_caption() 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 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 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; + + virtual bool is_show_prop_names() const override; + virtual bool is_use_prop_slots() const override; + + void set_default_value_enabled(bool p_enabled); + bool is_default_value_enabled() const; + + void set_default_value(const Quaternion &p_value); + Quaternion get_default_value() const; + + bool is_qualifier_supported(Qualifier p_qual) const override; + bool is_convertible_to_constant() const override; + + virtual Vector<StringName> get_editable_properties() const override; + + VisualShaderNodeVec4Uniform(); +}; + +/////////////////////////////////////// + class VisualShaderNodeTransformUniform : public VisualShaderNodeUniform { GDCLASS(VisualShaderNodeTransformUniform, VisualShaderNodeUniform); @@ -2084,7 +2167,6 @@ public: 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 bool is_input_port_default(int p_port, Shader::Mode p_mode) const override; virtual int get_output_port_count() const override; virtual PortType get_output_port_type(int p_port) const override; @@ -2093,9 +2175,8 @@ public: 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; - virtual Map<StringName, String> get_editable_properties_names() const override; + virtual HashMap<StringName, String> get_editable_properties_names() const override; virtual bool is_show_prop_names() const override; - virtual bool is_code_generated() const override; Vector<StringName> get_editable_properties() const override; @@ -2134,6 +2215,10 @@ public: virtual PortType get_input_port_type(int p_port) const override; virtual String get_input_port_name(int p_port) 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 bool is_input_port_default(int p_port, Shader::Mode p_mode) const override; virtual String generate_global_per_node(Shader::Mode p_mode, int p_id) const override; @@ -2150,16 +2235,8 @@ class VisualShaderNodeTexture2DArrayUniform : public VisualShaderNodeTextureUnif public: virtual String get_caption() 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 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 is_input_port_default(int p_port, Shader::Mode p_mode) 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; @@ -2173,16 +2250,8 @@ class VisualShaderNodeTexture3DUniform : public VisualShaderNodeTextureUniform { public: virtual String get_caption() 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 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 is_input_port_default(int p_port, Shader::Mode p_mode) 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; @@ -2196,16 +2265,8 @@ class VisualShaderNodeCubemapUniform : public VisualShaderNodeTextureUniform { public: virtual String get_caption() 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 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 is_input_port_default(int p_port, Shader::Mode p_mode) 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; @@ -2248,6 +2309,7 @@ public: OP_TYPE_INT, OP_TYPE_VECTOR_2D, OP_TYPE_VECTOR_3D, + OP_TYPE_VECTOR_4D, OP_TYPE_BOOLEAN, OP_TYPE_TRANSFORM, OP_TYPE_MAX, @@ -2362,6 +2424,7 @@ public: CTYPE_SCALAR_INT, CTYPE_VECTOR_2D, CTYPE_VECTOR_3D, + CTYPE_VECTOR_4D, CTYPE_BOOLEAN, CTYPE_TRANSFORM, CTYPE_MAX, @@ -2431,6 +2494,7 @@ public: OP_TYPE_SCALAR, OP_TYPE_VECTOR_2D, OP_TYPE_VECTOR_3D, + OP_TYPE_VECTOR_4D, OP_TYPE_MAX, }; diff --git a/scene/resources/visual_shader_particle_nodes.cpp b/scene/resources/visual_shader_particle_nodes.cpp index 0879f2e735..bdfbb59fa6 100644 --- a/scene/resources/visual_shader_particle_nodes.cpp +++ b/scene/resources/visual_shader_particle_nodes.cpp @@ -74,8 +74,8 @@ Vector<StringName> VisualShaderNodeParticleEmitter::get_editable_properties() co return props; } -Map<StringName, String> VisualShaderNodeParticleEmitter::get_editable_properties_names() const { - Map<StringName, String> names; +HashMap<StringName, String> VisualShaderNodeParticleEmitter::get_editable_properties_names() const { + HashMap<StringName, String> names; names.insert("mode_2d", RTR("2D Mode")); return names; } @@ -470,7 +470,7 @@ void VisualShaderNodeParticleMeshEmitter::_update_texture(const Vector<Vector2> 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); + r_texture->set_image(image); } else { r_texture->update(image); } @@ -491,7 +491,7 @@ void VisualShaderNodeParticleMeshEmitter::_update_texture(const Vector<Vector3> 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); + r_texture->set_image(image); } else { r_texture->update(image); } @@ -511,7 +511,7 @@ void VisualShaderNodeParticleMeshEmitter::_update_texture(const Vector<Color> &p 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); + r_texture->set_image(image); } else { r_texture->update(image); } @@ -704,8 +704,8 @@ Vector<StringName> VisualShaderNodeParticleMeshEmitter::get_editable_properties( return props; } -Map<StringName, String> VisualShaderNodeParticleMeshEmitter::get_editable_properties_names() const { - Map<StringName, String> names = VisualShaderNodeParticleEmitter::get_editable_properties_names(); +HashMap<StringName, String> VisualShaderNodeParticleMeshEmitter::get_editable_properties_names() const { + HashMap<StringName, String> names = VisualShaderNodeParticleEmitter::get_editable_properties_names(); names.insert("mesh", RTR("Mesh")); names.insert("use_all_surfaces", RTR("Use All Surfaces")); diff --git a/scene/resources/visual_shader_particle_nodes.h b/scene/resources/visual_shader_particle_nodes.h index 0b91cba5e0..05a059373b 100644 --- a/scene/resources/visual_shader_particle_nodes.h +++ b/scene/resources/visual_shader_particle_nodes.h @@ -52,7 +52,7 @@ public: bool is_mode_2d() const; Vector<StringName> get_editable_properties() const override; - virtual Map<StringName, String> get_editable_properties_names() const override; + virtual HashMap<StringName, String> get_editable_properties_names() const override; bool is_show_prop_names() const override; VisualShaderNodeParticleEmitter(); @@ -153,7 +153,7 @@ public: int get_surface_index() const; Vector<StringName> get_editable_properties() const override; - Map<StringName, String> get_editable_properties_names() const override; + HashMap<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(); diff --git a/scene/resources/world_2d.cpp b/scene/resources/world_2d.cpp index 9d8e0f7547..4dfbe5f079 100644 --- a/scene/resources/world_2d.cpp +++ b/scene/resources/world_2d.cpp @@ -73,8 +73,8 @@ World2D::World2D() { // Create and configure space2D to be more friendly with pixels than meters space = PhysicsServer2D::get_singleton()->space_create(); PhysicsServer2D::get_singleton()->space_set_active(space, true); - PhysicsServer2D::get_singleton()->area_set_param(space, PhysicsServer2D::AREA_PARAM_GRAVITY, GLOBAL_DEF("physics/2d/default_gravity", 980.0)); - PhysicsServer2D::get_singleton()->area_set_param(space, PhysicsServer2D::AREA_PARAM_GRAVITY_VECTOR, GLOBAL_DEF("physics/2d/default_gravity_vector", Vector2(0, 1))); + PhysicsServer2D::get_singleton()->area_set_param(space, PhysicsServer2D::AREA_PARAM_GRAVITY, GLOBAL_DEF_BASIC("physics/2d/default_gravity", 980.0)); + PhysicsServer2D::get_singleton()->area_set_param(space, PhysicsServer2D::AREA_PARAM_GRAVITY_VECTOR, GLOBAL_DEF_BASIC("physics/2d/default_gravity_vector", Vector2(0, 1))); PhysicsServer2D::get_singleton()->area_set_param(space, PhysicsServer2D::AREA_PARAM_LINEAR_DAMP, GLOBAL_DEF("physics/2d/default_linear_damp", 0.1)); ProjectSettings::get_singleton()->set_custom_property_info("physics/2d/default_linear_damp", PropertyInfo(Variant::FLOAT, "physics/2d/default_linear_damp", PROPERTY_HINT_RANGE, "-1,100,0.001,or_greater")); PhysicsServer2D::get_singleton()->area_set_param(space, PhysicsServer2D::AREA_PARAM_ANGULAR_DAMP, GLOBAL_DEF("physics/2d/default_angular_damp", 1.0)); diff --git a/scene/resources/world_2d.h b/scene/resources/world_2d.h index 4a277c3d84..c04b8f6461 100644 --- a/scene/resources/world_2d.h +++ b/scene/resources/world_2d.h @@ -46,7 +46,7 @@ class World2D : public Resource { RID space; RID navigation_map; - Set<Viewport *> viewports; + HashSet<Viewport *> viewports; protected: static void _bind_methods(); @@ -62,7 +62,7 @@ public: PhysicsDirectSpaceState2D *get_direct_space_state(); - _FORCE_INLINE_ const Set<Viewport *> &get_viewports() { return viewports; } + _FORCE_INLINE_ const HashSet<Viewport *> &get_viewports() { return viewports; } World2D(); ~World2D(); diff --git a/scene/resources/world_3d.cpp b/scene/resources/world_3d.cpp index 0088236112..fb6dcd3d57 100644 --- a/scene/resources/world_3d.cpp +++ b/scene/resources/world_3d.cpp @@ -31,7 +31,6 @@ #include "world_3d.h" #include "core/config/project_settings.h" -#include "core/math/octree.h" #include "scene/3d/camera_3d.h" #include "scene/3d/visible_on_screen_notifier_3d.h" #include "scene/scene_string_names.h" @@ -141,8 +140,8 @@ World3D::World3D() { scenario = RenderingServer::get_singleton()->scenario_create(); PhysicsServer3D::get_singleton()->space_set_active(space, true); - PhysicsServer3D::get_singleton()->area_set_param(space, PhysicsServer3D::AREA_PARAM_GRAVITY, GLOBAL_DEF("physics/3d/default_gravity", 9.8)); - PhysicsServer3D::get_singleton()->area_set_param(space, PhysicsServer3D::AREA_PARAM_GRAVITY_VECTOR, GLOBAL_DEF("physics/3d/default_gravity_vector", Vector3(0, -1, 0))); + PhysicsServer3D::get_singleton()->area_set_param(space, PhysicsServer3D::AREA_PARAM_GRAVITY, GLOBAL_DEF_BASIC("physics/3d/default_gravity", 9.8)); + PhysicsServer3D::get_singleton()->area_set_param(space, PhysicsServer3D::AREA_PARAM_GRAVITY_VECTOR, GLOBAL_DEF_BASIC("physics/3d/default_gravity_vector", Vector3(0, -1, 0))); PhysicsServer3D::get_singleton()->area_set_param(space, PhysicsServer3D::AREA_PARAM_LINEAR_DAMP, GLOBAL_DEF("physics/3d/default_linear_damp", 0.1)); ProjectSettings::get_singleton()->set_custom_property_info("physics/3d/default_linear_damp", PropertyInfo(Variant::FLOAT, "physics/3d/default_linear_damp", PROPERTY_HINT_RANGE, "0,100,0.001,or_greater")); PhysicsServer3D::get_singleton()->area_set_param(space, PhysicsServer3D::AREA_PARAM_ANGULAR_DAMP, GLOBAL_DEF("physics/3d/default_angular_damp", 0.1)); @@ -150,8 +149,8 @@ World3D::World3D() { navigation_map = NavigationServer3D::get_singleton()->map_create(); NavigationServer3D::get_singleton()->map_set_active(navigation_map, true); - NavigationServer3D::get_singleton()->map_set_cell_size(navigation_map, GLOBAL_DEF("navigation/3d/default_cell_size", 0.3)); - NavigationServer3D::get_singleton()->map_set_edge_connection_margin(navigation_map, GLOBAL_DEF("navigation/3d/default_edge_connection_margin", 0.3)); + NavigationServer3D::get_singleton()->map_set_cell_size(navigation_map, GLOBAL_DEF("navigation/3d/default_cell_size", 0.25)); + NavigationServer3D::get_singleton()->map_set_edge_connection_margin(navigation_map, GLOBAL_DEF("navigation/3d/default_edge_connection_margin", 0.25)); } World3D::~World3D() { diff --git a/scene/resources/world_3d.h b/scene/resources/world_3d.h index b34b7a2bfb..08bc050349 100644 --- a/scene/resources/world_3d.h +++ b/scene/resources/world_3d.h @@ -53,7 +53,7 @@ private: Ref<Environment> fallback_environment; Ref<CameraEffects> camera_effects; - Set<Camera3D *> cameras; + HashSet<Camera3D *> cameras; protected: static void _bind_methods(); @@ -77,7 +77,7 @@ public: void set_camera_effects(const Ref<CameraEffects> &p_camera_effects); Ref<CameraEffects> get_camera_effects() const; - _FORCE_INLINE_ const Set<Camera3D *> &get_cameras() const { return cameras; } + _FORCE_INLINE_ const HashSet<Camera3D *> &get_cameras() const { return cameras; } PhysicsDirectSpaceState3D *get_direct_space_state(); diff --git a/scene/resources/world_boundary_shape_2d.cpp b/scene/resources/world_boundary_shape_2d.cpp index ac5be79d24..013ffb9e24 100644 --- a/scene/resources/world_boundary_shape_2d.cpp +++ b/scene/resources/world_boundary_shape_2d.cpp @@ -108,7 +108,7 @@ void WorldBoundaryShape2D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_distance"), &WorldBoundaryShape2D::get_distance); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "normal"), "set_normal", "get_normal"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "distance", PROPERTY_HINT_RANGE, "0.01,1024,0.01,or_greater"), "set_distance", "get_distance"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "distance", PROPERTY_HINT_RANGE, "0.01,1024,0.01,or_greater,suffix:px"), "set_distance", "get_distance"); } WorldBoundaryShape2D::WorldBoundaryShape2D() : diff --git a/scene/resources/world_boundary_shape_3d.cpp b/scene/resources/world_boundary_shape_3d.cpp index 09d41e8291..400cbae217 100644 --- a/scene/resources/world_boundary_shape_3d.cpp +++ b/scene/resources/world_boundary_shape_3d.cpp @@ -80,7 +80,7 @@ void WorldBoundaryShape3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_plane", "plane"), &WorldBoundaryShape3D::set_plane); ClassDB::bind_method(D_METHOD("get_plane"), &WorldBoundaryShape3D::get_plane); - ADD_PROPERTY(PropertyInfo(Variant::PLANE, "plane"), "set_plane", "get_plane"); + ADD_PROPERTY(PropertyInfo(Variant::PLANE, "plane", PROPERTY_HINT_NONE, "suffix:m"), "set_plane", "get_plane"); } WorldBoundaryShape3D::WorldBoundaryShape3D() : |